Hướng dẫn

Google Sheets Nâng Cao Bài 5: Apps Script - Tự Động Hóa Với JavaScript

Tuân HoangTuân Hoang
18 phút đọc
Google Sheets Nâng Cao Bài 5: Apps Script - Tự Động Hóa Với JavaScript

Google Sheets Nâng Cao Bài 5: Apps Script - Tự Động Hóa Với JavaScript

Chào mừng bạn đến với Bài 5 trong series Google Sheets Nâng Cao của SheetStore Academy! Nếu bạn đã hoàn thành Bài 4: Hàm QUERY, giờ là lúc bước sang cấp độ hoàn toàn mới — Google Apps Script, công cụ cho phép bạn viết code JavaScript để tự động hóa mọi thứ trong spreadsheet.

Sau bài học này, bạn sẽ biết cách tạo menu tùy chỉnh, trigger tự động, gửi email báo cáo hàng ngày, và xây dựng những quy trình tự động hóa thực sự tiết kiệm hàng giờ làm việc mỗi tuần.

1. Apps Script là gì và tại sao bạn cần?

Google Apps Script là nền tảng lập trình dựa trên JavaScript, được tích hợp sẵn trong hệ sinh thái Google Workspace. Bạn có thể dùng nó để:

  • Tự động hóa các tác vụ lặp đi lặp lại trong Google Sheets, Docs, Gmail
  • Tạo báo cáo tự động gửi email mỗi sáng
  • Kết nối Google Sheets với các dịch vụ bên ngoài qua API
  • Xây dựng ứng dụng web đơn giản (Web App) từ dữ liệu spreadsheet
  • Tạo workflow phức tạp mà công thức thông thường không thể làm được

Apps Script vs Công thức thông thường

Tiêu chíCông thức (VLOOKUP, QUERY...)Apps Script
Độ khóDễ đến trung bìnhCần biết lập trình cơ bản
Tự động hóaChỉ tính toán khi có dữ liệuChạy theo schedule, trigger
Gửi emailKhông thểDễ dàng với MailApp
Kết nối APIKhông thểHỗ trợ đầy đủ UrlFetchApp
Tạo UIKhông thểCó thể với HtmlService
Xử lý dữ liệu lớnChậm khi >10,000 rowsNhanh hơn nhiều

Lưu ý quan trọng: Apps Script dùng cú pháp JavaScript ES6+, nếu bạn đã học JavaScript hay bất kỳ ngôn ngữ lập trình nào, bạn sẽ cảm thấy quen thuộc ngay.

2. Mở Script Editor lần đầu

Để bắt đầu, mở Script Editor từ bên trong Google Sheets:

  1. Mở Google Sheets của bạn
  2. Trên thanh menu, click Extensions (Tiện ích mở rộng)
  3. Chọn Apps Script
  4. Trình soạn thảo code sẽ mở trong tab mới

Giao diện Script Editor

Script Editor có các thành phần chính:

  • Code editor (giữa màn hình): Nơi bạn viết code JavaScript
  • Project files (trái): Danh sách các file script (.gs)
  • Toolbar (trên): Nút Run, Debug, Save
  • Log (dưới): Output của console.log và lỗi
  • Function dropdown: Chọn function muốn chạy

File mặc định: Code.gs

Khi mở lần đầu, bạn sẽ thấy file Code.gs với nội dung mặc định:

function myFunction() {
  // Code cua ban o day
}

Mỗi file script trong Apps Script có đuôi .gs (Google Script), nhưng thực chất là JavaScript thuần.

3. Viết Function Đầu Tiên: Hello World

Hãy bắt đầu với ví dụ đơn giản nhất — hiển thị thông báo trên spreadsheet:

function helloWorld() {
  // Hien thi dialog don gian
  SpreadsheetApp.getUi().alert('Xin chao tu Apps Script!');
}

Để chạy function này:

  1. Copy code vào editor và Save (Ctrl+S)
  2. Chọn helloWorld trong dropdown function
  3. Click nút Run (▶)
  4. Lần đầu sẽ yêu cầu cấp quyền — click Review permissions → chọn tài khoản Google → Allow
  5. Quay lại spreadsheet, bạn sẽ thấy hộp thoại thông báo

Sử dụng Logger để debug

function debugExample() {
  // In ra log de debug - xem o View > Logs
  console.log('Bat dau chay script...');

  const tenToi = 'Nguyen Van A';
  const soLuong = 100;

  console.log('Ten:', tenToi);
  console.log('So luong:', soLuong);
  console.log('Tong:', soLuong * 50000);

  Logger.log('Function da chay xong!');
}

Xem log output tại: View → Execution log trong Script Editor.

Cấu trúc cơ bản của Apps Script

// Khai bao bien toan cuc
const SHEET_NAME = 'Du lieu chinh';
const EMAIL_BAO_CAO = 'admin@company.com';

// Function chinh
function doSomething() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName(SHEET_NAME);

  console.log('Ten spreadsheet:', spreadsheet.getName());
  console.log('So hang co du lieu:', sheet.getLastRow());
}

4. Đọc và Ghi Dữ Liệu vào Spreadsheet

Đây là tính năng cốt lõi — SpreadsheetApp cho phép bạn đọc và ghi dữ liệu vào bất kỳ ô nào trong spreadsheet.

4.1 Các cách lấy Range (vùng ô)

function layDuLieu() {
  const sheet = SpreadsheetApp.getActiveSheet();

  // Lay mot o cu the
  const oA1 = sheet.getRange('A1');
  const oB2 = sheet.getRange(2, 2); // row 2, col 2 = B2

  // Lay mot vung o
  const vungA1C10 = sheet.getRange('A1:C10');

  // Lay toan bo du lieu co trong sheet
  const tatCaDuLieu = sheet.getDataRange();

  console.log('So hang cuoi co data:', sheet.getLastRow());
  console.log('So cot cuoi co data:', sheet.getLastColumn());
}

4.2 Đọc giá trị (getValues / getValue)

function docDuLieu() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');

  // Doc mot o -- getValue() tra ve gia tri don
  const tenSanPham = sheet.getRange('A2').getValue();
  console.log('Ten san pham:', tenSanPham);

  // Doc nhieu o -- getValues() tra ve mang 2 chieu
  const duLieu = sheet.getRange('A2:C11').getValues();

  // duLieu la [[row1col1, row1col2, row1col3], [row2col1, ...], ...]
  console.log('So hang:', duLieu.length);
  console.log('So cot moi hang:', duLieu[0].length);

  // Lap qua tung hang
  duLieu.forEach(function(hang, index) {
    const ten = hang[0];     // Cot A
    const soLuong = hang[1]; // Cot B
    const donGia = hang[2];  // Cot C

    if (ten) {
      console.log('Hang ' + (index + 2) + ': ' + ten + ' - SL: ' + soLuong + ' - Gia: ' + donGia);
    }
  });
}

4.3 Ghi giá trị (setValues / setValue)

function ghiDuLieu() {
  const sheet = SpreadsheetApp.getActiveSheet();

  // Ghi mot gia tri vao o don
  sheet.getRange('A1').setValue('Ngay cap nhat');
  sheet.getRange('B1').setValue(new Date());

  // Ghi nhieu o cung luc (hieu qua hon ghi tung o)
  const duLieuGhi = [
    ['Ten SP', 'So luong', 'Don gia', 'Thanh tien'],
    ['San pham A', 10, 50000, 500000],
    ['San pham B', 5, 120000, 600000],
    ['San pham C', 20, 35000, 700000],
  ];

  // Ghi toan bo mang vao vung A1:D4
  sheet.getRange(1, 1, duLieuGhi.length, duLieuGhi[0].length).setValues(duLieuGhi);

  // Them mot hang moi vao cuoi
  const hangMoi = ['San pham D', 8, 80000, 640000];
  const dongCuoi = sheet.getLastRow() + 1;
  sheet.getRange(dongCuoi, 1, 1, hangMoi.length).setValues([hangMoi]);

  console.log('Da ghi xong du lieu!');
}

4.4 Thao tác nâng cao với Range

function thaoTacNangCao() {
  const sheet = SpreadsheetApp.getActiveSheet();

  // Xoa noi dung o (giu dinh dang)
  sheet.getRange('A5:C10').clearContent();

  // Sap xep du lieu theo cot A (tang dan)
  const vungDuLieu = sheet.getRange('A2:D100');
  vungDuLieu.sort({ column: 1, ascending: true });

  // Dat mau nen va dinh dang header
  sheet.getRange('A1:D1').setBackground('#4285f4');
  sheet.getRange('A1:D1').setFontColor('#ffffff');
  sheet.getRange('A1:D1').setFontWeight('bold');

  // Dinh dang so tien VND
  sheet.getRange('D2:D100').setNumberFormat('#,##0');

  // Tu dong can chinh do rong cot
  sheet.autoResizeColumns(1, 4);

  SpreadsheetApp.flush(); // Ap dung tat ca thay doi ngay lap tuc
}

4.5 Làm việc với nhiều Sheet

function lam_viec_nhieu_sheet() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();

  // Lay tat ca sheets
  const tatCaSheets = spreadsheet.getSheets();
  console.log('So sheet:', tatCaSheets.length);

  // Lay sheet theo ten
  const sheetBanHang = spreadsheet.getSheetByName('Ban hang');
  const sheetBaoCao = spreadsheet.getSheetByName('Bao cao');

  // Tao sheet moi neu chua ton tai
  let sheetLog = spreadsheet.getSheetByName('Log');
  if (!sheetLog) {
    sheetLog = spreadsheet.insertSheet('Log');
    sheetLog.getRange('A1:C1').setValues([['Thoi gian', 'Hanh dong', 'Nguoi thuc hien']]);
  }

  // Sao chep du lieu tu sheet nay sang sheet khac
  if (sheetBanHang && sheetBaoCao) {
    const duLieuNguon = sheetBanHang.getDataRange().getValues();
    sheetBaoCao.clearContents();
    sheetBaoCao.getRange(1, 1, duLieuNguon.length, duLieuNguon[0].length).setValues(duLieuNguon);
  }
}

5. Tạo Custom Menu trong Spreadsheet

Thay vì phải vào Script Editor để chạy code, bạn có thể tạo menu tùy chỉnh ngay trong spreadsheet để người dùng bình thường cũng dùng được.

// Function nay tu dong chay khi mo spreadsheet
function onOpen() {
  const ui = SpreadsheetApp.getUi();

  ui.createMenu('Cong cu tu dong')
    .addItem('Tao bao cao hom nay', 'taoBaoCaoHomNay')
    .addItem('Gui email bao cao', 'guiEmailBaoCao')
    .addSeparator()
    .addSubMenu(
      ui.createMenu('Quan ly du lieu')
        .addItem('Cap nhat du lieu', 'capNhatDuLieu')
        .addItem('Xoa du lieu cu', 'xoaDuLieuCu')
        .addItem('Kiem tra du lieu', 'kiemTraDuLieu')
    )
    .addSeparator()
    .addItem('Huong dan su dung', 'hienHuongDan')
    .addToUi();
}

// Cac function tuong ung voi menu items
function taoBaoCaoHomNay() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Bao cao');
  if (!sheet) {
    SpreadsheetApp.getUi().alert('Khong tim thay sheet "Bao cao"!');
    return;
  }

  const homNay = Utilities.formatDate(new Date(), 'Asia/Ho_Chi_Minh', 'dd/MM/yyyy');
  sheet.getRange('A1').setValue('BAO CAO NGAY ' + homNay);

  SpreadsheetApp.getUi().alert('Da tao bao cao ngay ' + homNay);
}

function hienHuongDan() {
  const htmlContent = HtmlService.createHtmlOutput(
    '<h2>Huong dan su dung</h2>' +
    '<p>1. Chon menu Cong cu tu dong</p>' +
    '<p>2. Click vao chuc nang can dung</p>' +
    '<p>Lien he: support@sheetstore.vn</p>'
  ).setWidth(400).setHeight(300);

  SpreadsheetApp.getUi().showModalDialog(htmlContent, 'Huong dan su dung');
}

Lưu ý: Sau khi save code có onOpen(), hãy reload lại tab Google Sheets để menu xuất hiện.

6. Triggers: Tự Động Chạy Code Theo Điều Kiện

Triggers (trình kích hoạt) cho phép function của bạn tự động chạy mà không cần can thiệp thủ công.

6.1 Simple Triggers (Tích hợp sẵn)

// Chay khi mo spreadsheet
function onOpen(e) {
  console.log('Spreadsheet vua duoc mo boi:', Session.getActiveUser().getEmail());
}

// Chay khi co chinh sua o (real-time)
function onEdit(e) {
  // e.range: o vua duoc sua
  // e.value: gia tri moi
  // e.oldValue: gia tri cu

  const range = e.range;
  const sheet = range.getSheet();

  // Vi du: Tu dong ghi timestamp khi sua cot A
  if (sheet.getName() === 'Don hang' && range.getColumn() === 1) {
    const dongVuaSua = range.getRow();
    const colTimestamp = 10; // Cot J
    sheet.getRange(dongVuaSua, colTimestamp).setValue(new Date());
  }
}

// Chay khi form Google duoc submit
function onFormSubmit(e) {
  const values = e.namedValues;
  const ten = values['Ho va ten'][0];
  const email = values['Email'][0];

  console.log('Form submit tu:', ten, email);
}

6.2 Installable Triggers (Tạo từ code)

// Tao trigger chay hang ngay luc 8 gio sang
function taoTriggerHangNgay() {
  // Xoa trigger cu truoc (tranh duplicate)
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(function(trigger) {
    if (trigger.getHandlerFunction() === 'guiBaoCaoSang') {
      ScriptApp.deleteTrigger(trigger);
    }
  });

  // Tao trigger moi -- chay moi ngay luc 8-9 AM (gio VN)
  ScriptApp.newTrigger('guiBaoCaoSang')
    .timeBased()
    .atHour(8)
    .everyDays(1)
    .inTimezone('Asia/Ho_Chi_Minh')
    .create();

  console.log('Da tao trigger gui bao cao sang hang ngay');
}

// Tao trigger chay moi gio
function taoTriggerMoiGio() {
  ScriptApp.newTrigger('capNhatDuLieuTuDong')
    .timeBased()
    .everyHours(1)
    .create();
}

// Tao trigger chay moi thu 2 luc 9 gio sang
function taoTriggerHangTuan() {
  ScriptApp.newTrigger('baoCaoTuanDo')
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(9)
    .create();
}

// Xem danh sach tat ca triggers hien co
function xemTatCaTriggers() {
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(function(trigger, index) {
    console.log('Trigger ' + (index + 1) + ':');
    console.log('  Function:', trigger.getHandlerFunction());
    console.log('  Type:', trigger.getEventType());
  });
}

6.3 Quản lý Triggers qua UI

Ngoài code, bạn có thể quản lý triggers qua giao diện: Trong Script Editor → click icon đồng hồ ⏰ bên trái (Triggers) → Add Trigger.

7. Gửi Email Tự Động với MailApp

MailApp là service tích hợp trong Apps Script cho phép gửi email trực tiếp từ tài khoản Gmail của bạn.

7.1 Gửi email cơ bản

function guiEmailCoBan() {
  MailApp.sendEmail({
    to: 'khachhang@email.com',
    subject: 'Xac nhan don hang #DH001',
    body: 'Cam on ban da dat hang. Chung toi se xu ly trong 24 gio.',
  });

  console.log('Email da duoc gui!');
}

7.2 Gửi email HTML có định dạng đẹp

function guiEmailHTML() {
  const ngayHienTai = Utilities.formatDate(new Date(), 'Asia/Ho_Chi_Minh', 'dd/MM/yyyy');

  const noiDungHTML = '<div style="font-family: Arial; max-width: 600px;">' +
    '<h2 style="color: #4285f4;">Bao Cao Ban Hang Hom Nay</h2>' +
    '<p>Kinh gui Ban Giam Doc,</p>' +
    '<table border="1" cellpadding="8" style="border-collapse: collapse; width: 100%;">' +
    '<tr style="background: #f1f3f4;">' +
    '<th>Chi tieu</th><th>Thuc te</th><th>Ke hoach</th><th>Ty le</th>' +
    '</tr>' +
    '<tr>' +
    '<td>Doanh thu</td>' +
    '<td style="color: green;"><b>45,200,000d</b></td>' +
    '<td>50,000,000d</td>' +
    '<td>90.4%</td>' +
    '</tr>' +
    '</table>' +
    '<p style="color: #666; font-size: 12px;">Email tu dong tu he thong - ' + ngayHienTai + '</p>' +
    '</div>';

  MailApp.sendEmail({
    to: 'bgd@company.com',
    cc: 'ketoan@company.com',
    subject: '[Bao cao] Ban hang ngay ' + ngayHienTai,
    htmlBody: noiDungHTML,
  });
}

7.3 Gửi email hàng loạt (Mail merge)

function guiEmailHangLoat() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Danh sach');
  const duLieu = sheet.getDataRange().getValues();
  let soEmailDaGui = 0;

  for (let i = 1; i < duLieu.length; i++) {
    const ten = duLieu[i][0];
    const email = duLieu[i][1];
    const soTien = duLieu[i][2];
    const trangThai = duLieu[i][3];

    // Chi gui cho khach chua duoc thong bao
    if (ten && email && trangThai !== 'Da gui') {
      const noiDung = 'Kinh chao ' + ten + ',

' +
        'So tien thanh toan: ' + soTien.toLocaleString('vi-VN') + 'd

' +
        'Tran trong,
Team SheetStore';

      try {
        MailApp.sendEmail({
          to: email,
          subject: 'Thong bao thanh toan tu SheetStore',
          body: noiDung,
        });

        // Cap nhat trang thai
        sheet.getRange(i + 1, 4).setValue('Da gui');
        sheet.getRange(i + 1, 5).setValue(new Date());
        soEmailDaGui++;

        // Dung 1 giay de tranh vuot quota Gmail
        Utilities.sleep(1000);

      } catch (error) {
        console.error('Loi gui email cho', email, ':', error.message);
        sheet.getRange(i + 1, 4).setValue('Loi: ' + error.message);
      }
    }
  }

  SpreadsheetApp.getUi().alert('Da gui ' + soEmailDaGui + ' email thanh cong!');
}

Giới hạn quota Gmail: Tài khoản Gmail thường có thể gửi 100 email/ngày, tài khoản Google Workspace được 1,500 email/ngày.

8. Ví Dụ Thực Tế: Báo Cáo Hàng Ngày và Invoice Tự Động

8.1 Hệ thống báo cáo bán hàng tự động hàng ngày

Đây là ví dụ hoàn chỉnh — mỗi sáng lúc 8 giờ, script tự động tổng hợp dữ liệu bán hàng hôm qua và gửi email báo cáo cho quản lý.

Cấu trúc spreadsheet cần có:

  • Sheet "Don hang": Cột A=Ngày, B=Mã đơn, C=Khách hàng, D=Doanh thu, E=Trạng thái
  • Sheet "Cau hinh": B1=Email nhận báo cáo, B2=Tên công ty
// ============================================
// HE THONG BAO CAO BAN HANG TU DONG
// Cai dat: chay setupTrigger() mot lan
// ============================================

function setupTrigger() {
  // Xoa trigger cu
  ScriptApp.getProjectTriggers().forEach(function(t) {
    if (t.getHandlerFunction() === 'guiBaoCaoSang') ScriptApp.deleteTrigger(t);
  });

  // Tao trigger chay luc 8AM hang ngay
  ScriptApp.newTrigger('guiBaoCaoSang')
    .timeBased().atHour(8).everyDays(1)
    .inTimezone('Asia/Ho_Chi_Minh').create();

  Logger.log('Trigger da duoc cai dat. Bao cao se gui luc 8AM moi ngay.');
}

function guiBaoCaoSang() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheetDonHang = ss.getSheetByName('Don hang');
  const sheetCauHinh = ss.getSheetByName('Cau hinh');

  if (!sheetDonHang || !sheetCauHinh) {
    Logger.log('Loi: Khong tim thay sheet can thiet');
    return;
  }

  const emailNhan = sheetCauHinh.getRange('B1').getValue();
  const tenCongTy = sheetCauHinh.getRange('B2').getValue();

  // Lay ngay hom qua
  const homQua = new Date();
  homQua.setDate(homQua.getDate() - 1);
  const ngayStr = Utilities.formatDate(homQua, 'Asia/Ho_Chi_Minh', 'dd/MM/yyyy');

  // Tong hop du lieu
  const duLieu = sheetDonHang.getDataRange().getValues();
  let tongDoanhthu = 0, soDon = 0, soHoanThanh = 0;
  const danhSachDon = [];

  duLieu.slice(1).forEach(function(hang) {
    const ngayDon = hang[0];
    if (!ngayDon) return;

    const ngayDonStr = Utilities.formatDate(new Date(ngayDon), 'Asia/Ho_Chi_Minh', 'dd/MM/yyyy');
    if (ngayDonStr === ngayStr) {
      soDon++;
      tongDoanhthu += parseFloat(hang[3]) || 0;
      if (hang[4] === 'Hoan thanh') soHoanThanh++;
      danhSachDon.push(hang);
    }
  });

  // Tao noi dung email HTML
  const htmlBangDon = danhSachDon.map(function(don) {
    return '<tr>' +
      '<td style="padding:6px">' + don[1] + '</td>' +
      '<td style="padding:6px">' + don[2] + '</td>' +
      '<td style="padding:6px;text-align:right">' + parseInt(don[3]).toLocaleString('vi-VN') + 'd</td>' +
      '<td style="padding:6px;color:' + (don[4] === 'Hoan thanh' ? 'green' : 'orange') + '">' + don[4] + '</td>' +
      '</tr>';
  }).join('');

  const htmlBaoCao = '<div style="font-family:Arial;max-width:700px">' +
    '<div style="background:#1a73e8;color:white;padding:20px">' +
    '<h2>Bao Cao Ban Hang Ngay ' + ngayStr + '</h2>' +
    '<p>' + tenCongTy + '</p>' +
    '</div>' +
    '<div style="padding:20px">' +
    '<p>Tong doanh thu: <b>' + parseInt(tongDoanhthu).toLocaleString('vi-VN') + 'd</b></p>' +
    '<p>Tong don hang: <b>' + soDon + '</b> | Don hoan thanh: <b>' + soHoanThanh + '</b></p>' +
    (danhSachDon.length > 0 ?
      '<table width="100%" border="1" style="border-collapse:collapse">' +
      '<thead><tr style="background:#1a73e8;color:white">' +
      '<th style="padding:8px">Ma don</th>' +
      '<th style="padding:8px">Khach hang</th>' +
      '<th style="padding:8px">Doanh thu</th>' +
      '<th style="padding:8px">Trang thai</th>' +
      '</tr></thead>' +
      '<tbody>' + htmlBangDon + '</tbody></table>'
      : '<p>Khong co don hang nao ngay hom qua.</p>') +
    '</div></div>';

  if (emailNhan) {
    MailApp.sendEmail({
      to: emailNhan,
      subject: '[Bao cao ' + ngayStr + '] Doanh thu: ' + parseInt(tongDoanhthu).toLocaleString('vi-VN') + 'd | ' + soDon + ' don',
      htmlBody: htmlBaoCao,
    });
    Logger.log('Da gui bao cao ngay ' + ngayStr + ' den ' + emailNhan);
  }
}

8.2 Tạo Invoice từ Form Data tự động

// Khi Google Form duoc submit, tu dong tao invoice
function taoInvoiceTuForm(e) {
  const values = e.namedValues;
  const khachHang = values['Ten khach hang'][0];
  const email = values['Email khach hang'][0];
  const sanPham = values['San pham'][0];
  const soLuong = parseInt(values['So luong'][0]);
  const donGia = parseInt(values['Don gia'][0]);
  const thanhTien = soLuong * donGia;

  // Tao ma invoice tu dong
  const now = new Date();
  const maInvoice = 'INV-' + now.getFullYear() +
    '-' + String(now.getMonth() + 1).padStart(2, '0') +
    '-' + Math.floor(Math.random() * 9000 + 1000);

  // Ghi vao sheet Invoices
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheetInvoice = ss.getSheetByName('Invoices') || ss.insertSheet('Invoices');

  sheetInvoice.appendRow([
    maInvoice, new Date(), khachHang, email,
    sanPham, soLuong, donGia, thanhTien, 'Cho thanh toan'
  ]);

  // Gui email xac nhan cho khach
  const emailNoiDung = '<div style="font-family:Arial;max-width:500px">' +
    '<h2 style="color:#4285f4">Invoice #' + maInvoice + '</h2>' +
    '<p>Kinh chao <b>' + khachHang + '</b>,</p>' +
    '<p>Cam on ban da dat hang tai <a href="https://sheet.com.vn">SheetStore</a>!</p>' +
    '<table border="1" cellpadding="8" style="border-collapse:collapse;width:100%">' +
    '<tr><th>San pham</th><td>' + sanPham + '</td></tr>' +
    '<tr><th>So luong</th><td>' + soLuong + '</td></tr>' +
    '<tr><th>Don gia</th><td>' + donGia.toLocaleString('vi-VN') + 'd</td></tr>' +
    '<tr><th><b>Thanh tien</b></th><td style="color:green"><b>' + thanhTien.toLocaleString('vi-VN') + 'd</b></td></tr>' +
    '</table>' +
    '<p>Vui long thanh toan trong vong 3 ngay lam viec.</p>' +
    '</div>';

  MailApp.sendEmail({
    to: email,
    subject: '[SheetStore] Invoice ' + maInvoice + ' - ' + sanPham,
    htmlBody: emailNoiDung,
  });

  Logger.log('Invoice ' + maInvoice + ' da duoc tao va gui den ' + email);
}

9. Tips Nâng Cao và Tài Nguyên Học Thêm

9.1 Best Practices để code hiệu quả

  • Batch operations: Thay vì ghi từng ô một (chậm), hãy gom lại và ghi một lần với setValues()
  • Cache dữ liệu: Đọc dữ liệu vào biến một lần, không gọi getValues() nhiều lần trong vòng lặp
  • Error handling: Luôn dùng try-catch để xử lý lỗi, tránh script crash giữa chừng
  • Thời gian chạy tối đa: Apps Script giới hạn 6 phút/lần chạy — chia nhỏ task nếu cần xử lý data lớn
// CACH SAI (cham -- moi getRange la mot API call)
for (let i = 1; i <= 100; i++) {
  sheet.getRange(i, 1).setValue('Gia tri ' + i); // 100 API calls!
}

// CACH DUNG (nhanh -- chi 1 API call)
const duLieu = [];
for (let i = 1; i <= 100; i++) {
  duLieu.push(['Gia tri ' + i]);
}
sheet.getRange(1, 1, 100, 1).setValues(duLieu); // 1 API call!

9.2 Sử dụng PropertiesService để lưu cài đặt

// Luu cai dat vinh vien (khong mat khi refresh)
function luuCaiDat() {
  const props = PropertiesService.getScriptProperties();
  props.setProperties({
    'email_nhan': 'bgd@company.com',
    'ten_cong_ty': 'Cong ty ABC',
    'so_lan_chay': '0'
  });
}

function docCaiDat() {
  const props = PropertiesService.getScriptProperties();
  const email = props.getProperty('email_nhan');
  const soLan = parseInt(props.getProperty('so_lan_chay') || '0') + 1;
  props.setProperty('so_lan_chay', soLan.toString());

  console.log('Email nhan:', email);
  console.log('So lan da chay:', soLan);
}

9.3 Tài nguyên học Apps Script

  • Google Apps Script Official Docs
  • Apps Script Reference: SpreadsheetApp, MailApp, UrlFetchApp
  • Google Apps Script Samples trên GitHub
  • YouTube: "Google Apps Script Tutorial" (tiếng Việt có nhiều channel)

9.4 Kết nối với SheetStore

Nếu bạn muốn tiết kiệm thời gian, hãy khám phá các template Google Sheets sẵn có tại SheetStore — đã được tích hợp Apps Script tự động hóa, chỉ cần mua và sử dụng ngay. Xem thêm bảng giá các gói của chúng tôi.

Cũng có thể xem Bài 4: Hàm QUERY chuyên sâu nếu bạn chưa học, hoặc tiếp tục với Bài 3: IMPORTRANGE để ôn lại kiến thức nền tảng.

Tổng Kết Bài 5

Qua bài học này, bạn đã nắm được:

  • Apps Script là gì và cách mở Script Editor
  • Viết function cơ bản, sử dụng Logger để debug
  • Đọc/ghi dữ liệu hiệu quả với getValues() và setValues()
  • Tạo Custom Menu cho phép người dùng chạy script dễ dàng
  • Cài đặt Triggers để tự động hóa theo lịch hoặc sự kiện
  • Gửi email tự động với MailApp, kể cả email HTML đẹp
  • Ví dụ thực tế: Báo cáo hàng ngày và Invoice tự động

Apps Script mở ra một thế giới hoàn toàn mới cho Google Sheets — từ một công cụ bảng tính đơn giản, bạn có thể biến nó thành một hệ thống quản lý doanh nghiệp hoàn chỉnh. Đây cũng là lý do nhiều doanh nghiệp vừa và nhỏ chọn Google Sheets + Apps Script thay vì các phần mềm đắt tiền.

Khám phá thêm tại phần mềm quản lý bán hàng trên SheetStore để thấy Apps Script được ứng dụng thực tế như thế nào trong các template chuyên nghiệp.

Chia sẻ bài viết:

Tuân Hoang

Tuân Hoang

Đội ngũ SheetStore

Bạn thấy bài viết hữu ích?

Đăng ký nhận thông báo khi có bài viết mới.

Nhận thông báo khi có bài viết mới. Không spam, hứa luôn! 😊

Bình luận (0)

Vui lòng đăng nhập để tham gia thảo luận