Quản lý bán hàng bằng Google Sheet: báo cáo doanh thu, tồn kho tự động (có file mẫu)
Quản lý bán hàng bằng Google Sheet là cách gọn và miễn phí cho cửa hàng nhỏ. Shop bán trên Shopee, TikTok Shop và tại quầy thường ghi đơn vào Google Sheets, rồi cuối tháng ngồi cộng tay để biết bán được bao nhiêu, lãi bao nhiêu, mặt hàng nào sắp hết. Bài này đi qua ba mức tự động hóa việc đó với Gemini và Google Sheets: nhờ Gemini viết công thức, dựng bảng tổng hợp có biểu đồ, và cuối cùng là Apps Script tự tạo báo cáo tháng, tự gửi email khi hàng dưới mức tồn tối thiểu.
Mọi ví dụ chạy trên cùng một file mẫu: 348 dòng đơn hàng giả lập của một cửa hàng phụ kiện máy tính trong tháng 9 và 10/2026. Tải file mẫu (.xlsx), mở bằng Google Sheets rồi làm theo, số liệu bạn thấy sẽ khớp với số trong bài.
| Mức | Bạn làm gì | Hợp với ai |
|---|---|---|
| 1. Công thức | Mô tả bằng lời, Gemini viết SUMIFS, QUERY, công thức tồn kho | Ai dùng Sheets cũng làm được, kể cả tài khoản miễn phí |
| 2. Bảng tổng hợp và biểu đồ | Pivot theo tháng, theo kênh, tô đỏ hàng sắp hết | Chủ shop cần nhìn số nhanh mỗi tuần |
| 3. Apps Script và macro | Một nút bấm tạo báo cáo tháng, mỗi sáng tự gửi email cảnh báo tồn kho | Người muốn bỏ hẳn việc làm tay hàng tháng |
File quản lý bán hàng bằng Google Sheet: 3 trang tính là đủ
Gemini và Apps Script đều làm việc tốt nhất khi dữ liệu ở dạng bảng thẳng: mỗi dòng một bản ghi, dòng đầu là tiêu đề, không gộp ô, không chèn dòng tổng giữa bảng. File mẫu chia làm 3 trang tính:
| Trang tính | Các cột | Ghi chú |
|---|---|---|
| DonHang | Ngày, Mã đơn, Mã SP, Tên sản phẩm, Số lượng, Đơn giá, Thành tiền, Kênh bán | Mỗi sản phẩm trong đơn là một dòng |
| SanPham | Mã SP, Tên sản phẩm, Giá vốn, Giá bán, Tồn đầu kỳ, Tồn tối thiểu | Mỗi mã chỉ một dòng |
| NhapHang | Ngày, Mã SP, Số lượng nhập | Mỗi lần nhập hàng một dòng |
Cột Ngày phải là kiểu ngày thật chứ không phải chữ. Cách kiểm tra: chọn cột, nhìn góc dưới bên phải; Sheets hiện Tổng hoặc Trung bình là đúng, chỉ hiện Số lượng là đang lưu dạng chữ. Sửa bằng Định dạng > Số > Ngày. Đơn tải từ Shopee hay TikTok Shop về thì dán vào DonHang theo đúng thứ tự cột trên, cột thừa xóa đi.
Mức 1: nhờ Gemini viết công thức
Có hai chỗ để hỏi. Tài khoản Google AI Pro có biểu tượng Gemini ở góc trên bên phải Google Sheets, mở bảng bên cạnh và Gemini đọc được dữ liệu đang mở. Theo thông báo Workspace tháng 4/2026, khả năng tạo và sửa bảng tính trực tiếp trong Sheets có trên gói Google AI Pro và Ultra cá nhân nhưng hiện chỉ chạy với tiếng Anh, nên câu lệnh tiếng Việt có thể chưa được nhận. Cách chắc chắn hơn, dùng được cả với tài khoản miễn phí: mở gemini.google.com, mô tả cấu trúc bảng và nhờ viết công thức, rồi dán công thức về Sheets.
Mẫu câu hỏi cho Gemini, thay phần trong ngoặc vuông cho đúng bảng của bạn:
Tôi có Google Sheets cài tiếng Việt (dùng dấu ; ngăn cách đối số).
Trang tính DonHang: A Ngày, B Mã đơn, C Mã SP, D Tên sản phẩm, E Số lượng, F Đơn giá, G Thành tiền, H Kênh bán. Dữ liệu từ dòng 2.
Viết công thức đặt ở ô [B4] trang tính [BaoCao] để tính [tổng doanh thu của tháng có ngày đầu tháng ở ô B3].
Giải thích ngắn từng phần của công thức.

Doanh thu một tháng
Ô B3 ghi ngày đầu tháng (01/10/2026). Công thức cộng cột Thành tiền của các dòng có ngày từ B3 đến trước ngày đầu tháng sau:
=SUMIFS(DonHang!G:G; DonHang!A:A; ">="&B3; DonHang!A:A; "<"&EDATE(B3;1))
Với file mẫu, tháng 10/2026 ra 87.181.000đ, tháng 9 ra 59.997.000đ. Dùng mốc “từ ngày đầu tháng đến trước ngày đầu tháng sau” thay cho hàm MONTH() để không phải lo năm khác nhau có cùng số tháng.
Quản lý tồn kho bằng Google Sheet: tồn hiện tại của từng mã
Ở trang tính SanPham, cột G, dòng 2:
=E2 + SUMIF(NhapHang!B:B; A2; NhapHang!C:C) - SUMIF(DonHang!C:C; A2; DonHang!E:E)
Nghĩa là tồn đầu kỳ cộng tổng đã nhập trừ tổng đã bán của mã đó. Kéo xuống hết danh sách. Cột H thêm =IF(G2<F2; "Cần nhập"; "Đủ hàng"). File mẫu cho ra 3 mã cần nhập: bàn phím cơ còn 3 (tối thiểu 5), sạc dự phòng còn 6 (tối thiểu 8), giá đỡ laptop còn 4 (tối thiểu 6).
Top 5 sản phẩm theo doanh thu bằng QUERY
QUERY là hàm riêng của Google Sheets, viết gần giống câu lệnh SQL. Một công thức trả về cả bảng:
=QUERY(DonHang!A:H; "select C, D, sum(E), sum(G) where A >= date '2026-10-01' and A < date '2026-11-01' group by C, D order by sum(G) desc limit 5 label sum(E) 'Số lượng', sum(G) 'Doanh thu'"; 1)
Muốn theo kênh bán thì đổi C, D thành H ở cả select và group by. QUERY sai cú pháp rất khó đọc lỗi, nên đây là chỗ đáng nhờ Gemini viết nhất: chỉ cần nói “top 5 sản phẩm tháng 10 theo doanh thu, kèm số lượng”.
Mức 2: báo cáo doanh thu hàng tháng bằng bảng tổng hợp và biểu đồ
- Bảng tổng hợp (pivot): chọn vùng dữ liệu DonHang, vào Chèn > Bảng tổng hợp > Trang tính mới. Hàng chọn Mã SP, Giá trị chọn Thành tiền (SUM), Cột chọn Kênh bán. Bộ lọc thêm Ngày, lọc theo điều kiện “ngày nằm trong khoảng” để xem từng tháng.
- Biểu đồ: chọn bảng tổng hợp, Chèn > Biểu đồ. Doanh thu theo sản phẩm hợp với biểu đồ thanh ngang, tỷ trọng theo kênh hợp với biểu đồ cột chồng hơn biểu đồ tròn khi có trên 3 kênh.
- Tô đỏ hàng sắp hết: ở SanPham chọn A2:H11, Định dạng > Định dạng có điều kiện > Công thức tùy chỉnh là
=$G2<$F2, chọn nền đỏ nhạt. Dấu $ trước G và F giữ nguyên cột để cả dòng cùng đổi màu.
Gemini trong Sheets (gói AI Pro) có thể tạo bảng tổng hợp và biểu đồ khi bạn mô tả bằng tiếng Anh, ví dụ “Create a pivot table of revenue by product and sales channel for October 2026”. Kết quả nên kiểm tra lại tổng với công thức SUMIFS ở mức 1 trước khi dùng để báo cáo.

Mức 3: Apps Script Google Sheet tự làm báo cáo và gửi cảnh báo
Apps Script là ngôn ngữ lập trình (dựa trên JavaScript) Google cài sẵn trong mọi bảng tính, miễn phí, chạy trên máy chủ của Google. Đoạn mã dưới thêm vào bảng tính một menu tên “Hộp AI” với 5 việc:
- Tạo báo cáo tháng này, tháng trước hoặc tháng bất kỳ: trang tính mới BaoCao_2026_10 có số đơn, doanh thu, giá vốn, lãi gộp, bảng theo sản phẩm, bảng theo kênh và biểu đồ.
- Cập nhật tồn kho: trang tính TonKho liệt kê từng mã, dòng thiếu hàng tô đỏ và đưa lên đầu, kèm gợi ý số lượng nhập (đủ gấp đôi mức tối thiểu).
- Gửi cảnh báo tồn kho ngay qua Gmail.
- Bật cảnh báo mỗi sáng: tạo lịch chạy tự động khoảng 7 giờ sáng hằng ngày, chỉ gửi email khi có hàng dưới mức tối thiểu.
- Cài khóa API Gemini (không bắt buộc): có khóa thì mỗi báo cáo tháng có thêm một đoạn nhận xét do Gemini viết dựa trên số liệu.
Bước 1. Mở trình chỉnh sửa Apps Script
Trong bảng tính, vào Tiện ích mở rộng > Apps Script. Một thẻ mới mở ra với tệp Mã.gs đang có sẵn hàm myFunction trống. Xóa hết nội dung đó.

Bước 2. Dán mã và lưu
Dán toàn bộ đoạn mã dưới đây vào, bấm biểu tượng lưu (hoặc Ctrl+S). Tên trang tính và thứ tự cột phải giống file mẫu; khác thì sửa trong phần CAU_HINH ở đầu mã.
/**
* Hộp AI: báo cáo doanh thu và tồn kho tự động cho Google Sheets.
* Dùng với file mẫu có 3 trang tính: DonHang, SanPham, NhapHang.
* Hướng dẫn: https://itech3s.com/quan-ly-ban-hang-bang-google-sheet/
*
* Cột của từng trang tính (dòng 1 là tiêu đề):
* DonHang : A Ngày | B Mã đơn | C Mã SP | D Tên sản phẩm | E Số lượng | F Đơn giá | G Thành tiền | H Kênh bán
* SanPham : A Mã SP | B Tên sản phẩm | C Giá vốn | D Giá bán | E Tồn đầu kỳ | F Tồn tối thiểu
* NhapHang: A Ngày | B Mã SP | C Số lượng nhập
*/
const CAU_HINH = {
sheetDonHang: 'DonHang',
sheetSanPham: 'SanPham',
sheetNhapHang: 'NhapHang',
emailNhanCanhBao: '', // để trống: gửi về email chủ file
gioGuiCanhBao: 7, // 7 giờ sáng theo múi giờ của dự án
modelGemini: 'gemini-flash-latest'
};
/* ---------- Menu riêng trên thanh công cụ ---------- */
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Hộp AI')
.addItem('Tạo báo cáo tháng này', 'taoBaoCaoThangNay')
.addItem('Tạo báo cáo tháng trước', 'taoBaoCaoThangTruoc')
.addItem('Tạo báo cáo tháng bất kỳ...', 'taoBaoCaoTheoThang')
.addItem('Cập nhật tồn kho', 'capNhatTonKho')
.addSeparator()
.addItem('Gửi cảnh báo tồn kho ngay', 'guiCanhBaoTonKho')
.addItem('Bật cảnh báo tồn kho mỗi sáng', 'batCanhBaoHangNgay')
.addSeparator()
.addItem('Cài khóa API Gemini (không bắt buộc)', 'caiKhoaGemini')
.addToUi();
}
function taoBaoCaoThangNay() {
const d = new Date();
taoBaoCao_(d.getFullYear(), d.getMonth() + 1);
}
function taoBaoCaoThangTruoc() {
const d = new Date();
d.setDate(1);
d.setMonth(d.getMonth() - 1);
taoBaoCao_(d.getFullYear(), d.getMonth() + 1);
}
function taoBaoCaoTheoThang() {
const ui = SpreadsheetApp.getUi();
const tl = ui.prompt('Tạo báo cáo', 'Nhập tháng theo dạng MM/YYYY, ví dụ 10/2026:', ui.ButtonSet.OK_CANCEL);
if (tl.getSelectedButton() !== ui.Button.OK) return;
const m = tl.getResponseText().trim().match(/^(\d{1,2})\/(\d{4})$/);
if (!m || Number(m[1]) < 1 || Number(m[1]) > 12) { ui.alert('Sai định dạng. Ví dụ đúng: 10/2026'); return; }
taoBaoCao_(Number(m[2]), Number(m[1]));
}
/* ---------- Xử lý số liệu (không phụ thuộc Google Sheets) ---------- */
function layNgay_(v) {
if (v instanceof Date) return v;
const s = String(v).trim();
const m = s.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); // dạng 05/10/2026
if (m) return new Date(Number(m[3]), Number(m[2]) - 1, Number(m[1]));
const k = s.match(/^(\d{4})-(\d{2})-(\d{2})/); // dạng 2026-10-05
if (k) return new Date(Number(k[1]), Number(k[2]) - 1, Number(k[3]));
return new Date(s);
}
function tongHopThang(donHang, sanPham, nam, thang) {
const giaVon = {};
sanPham.forEach(function (r) { giaVon[r[0]] = Number(r[2]) || 0; });
const theoSP = {}, theoKenh = {};
let soDon = 0, doanhThu = 0, tongGiaVon = 0;
donHang.forEach(function (r) {
const ngay = layNgay_(r[0]);
if (ngay.getFullYear() !== nam || ngay.getMonth() + 1 !== thang) return;
const ma = r[2], sl = Number(r[4]) || 0, tien = Number(r[6]) || 0, kenh = r[7] || 'Khác';
const von = sl * (giaVon[ma] || 0);
soDon++; doanhThu += tien; tongGiaVon += von;
if (!theoSP[ma]) theoSP[ma] = { ten: r[3], soLuong: 0, doanhThu: 0, giaVon: 0 };
theoSP[ma].soLuong += sl; theoSP[ma].doanhThu += tien; theoSP[ma].giaVon += von;
theoKenh[kenh] = (theoKenh[kenh] || 0) + tien;
});
return { soDon: soDon, doanhThu: doanhThu, giaVon: tongGiaVon, laiGop: doanhThu - tongGiaVon, theoSP: theoSP, theoKenh: theoKenh };
}
function tinhTonKho(sanPham, nhapHang, donHang) {
const nhap = {}, ban = {};
nhapHang.forEach(function (r) { nhap[r[1]] = (nhap[r[1]] || 0) + (Number(r[2]) || 0); });
donHang.forEach(function (r) { ban[r[2]] = (ban[r[2]] || 0) + (Number(r[4]) || 0); });
return sanPham.map(function (r) {
const ma = r[0], tonDau = Number(r[4]) || 0, toiThieu = Number(r[5]) || 0;
const ton = tonDau + (nhap[ma] || 0) - (ban[ma] || 0);
return { ma: ma, ten: r[1], tonDau: tonDau, nhap: nhap[ma] || 0, ban: ban[ma] || 0, ton: ton, toiThieu: toiThieu, canNhap: Math.max(0, toiThieu * 2 - ton), thieu: ton < toiThieu };
});
}
/* ---------- Đọc và ghi Google Sheets ---------- */
function docBang_(ten) {
const sh = SpreadsheetApp.getActive().getSheetByName(ten);
if (!sh) throw new Error('Không tìm thấy trang tính "' + ten + '". Kiểm tra lại tên trang tính.');
return sh.getDataRange().getValues().slice(1).filter(function (r) { return r[0] !== '' && r[0] !== null; });
}
function trangMoi_(ten) {
const ss = SpreadsheetApp.getActive();
const cu = ss.getSheetByName(ten);
if (cu) ss.deleteSheet(cu);
return ss.insertSheet(ten);
}
function taoBaoCao_(nam, thang) {
const kq = tongHopThang(docBang_(CAU_HINH.sheetDonHang), docBang_(CAU_HINH.sheetSanPham), nam, thang);
const ten = 'BaoCao_' + nam + '_' + ('0' + thang).slice(-2);
const sh = trangMoi_(ten);
sh.getRange('A1').setValue('BÁO CÁO DOANH THU THÁNG ' + thang + '/' + nam).setFontSize(14).setFontWeight('bold');
const tyLe = kq.doanhThu ? kq.laiGop / kq.doanhThu : 0;
sh.getRange('A3:B7').setValues([
['Số đơn', kq.soDon],
['Doanh thu', kq.doanhThu],
['Giá vốn', kq.giaVon],
['Lãi gộp', kq.laiGop],
['Tỷ suất lãi gộp', tyLe]
]);
sh.getRange('B4:B6').setNumberFormat('#,##0');
sh.getRange('B7').setNumberFormat('0.0%');
sh.getRange('A3:A7').setFontWeight('bold');
const sp = Object.keys(kq.theoSP).map(function (ma) {
const x = kq.theoSP[ma];
return [ma, x.ten, x.soLuong, x.doanhThu, x.doanhThu - x.giaVon];
}).sort(function (a, b) { return b[3] - a[3]; });
sh.getRange('A9:E9').setValues([['Mã SP', 'Sản phẩm', 'Số lượng bán', 'Doanh thu', 'Lãi gộp']]).setFontWeight('bold').setBackground('#EFEBFF');
if (sp.length) {
sh.getRange(10, 1, sp.length, 5).setValues(sp);
sh.getRange(10, 4, sp.length, 2).setNumberFormat('#,##0');
}
const kenh = Object.keys(kq.theoKenh).map(function (k) { return [k, kq.theoKenh[k], kq.doanhThu ? kq.theoKenh[k] / kq.doanhThu : 0]; })
.sort(function (a, b) { return b[1] - a[1]; });
sh.getRange('G9:I9').setValues([['Kênh bán', 'Doanh thu', 'Tỷ trọng']]).setFontWeight('bold').setBackground('#EFEBFF');
if (kenh.length) {
sh.getRange(10, 7, kenh.length, 3).setValues(kenh);
sh.getRange(10, 8, kenh.length, 1).setNumberFormat('#,##0');
sh.getRange(10, 9, kenh.length, 1).setNumberFormat('0.0%');
}
if (sp.length) {
const bieuDo = sh.newChart().asBarChart()
.addRange(sh.getRange(9, 2, sp.length + 1, 1))
.addRange(sh.getRange(9, 4, sp.length + 1, 1))
.setNumHeaders(1)
.setOption('title', 'Doanh thu theo sản phẩm, tháng ' + thang + '/' + nam)
.setOption('legend', { position: 'none' })
.setPosition(3, 11, 0, 0)
.build();
sh.insertChart(bieuDo);
}
const nhanXet = nhanXetBangGemini_(kq, sp, kenh, thang, nam);
if (nhanXet) {
const dong = 11 + Math.max(sp.length, kenh.length);
sh.getRange(dong, 1).setValue('Nhận xét của Gemini (cần đọc lại trước khi dùng)').setFontWeight('bold');
sh.getRange(dong + 1, 1, 1, 9).merge().setValue(nhanXet).setWrap(true).setVerticalAlignment('top');
sh.setRowHeight(dong + 1, 160);
}
sh.autoResizeColumns(1, 9);
sh.activate();
SpreadsheetApp.getActive().toast('Đã tạo trang tính ' + ten, 'Hộp AI', 5);
}
function capNhatTonKho() {
const ds = tinhTonKho(docBang_(CAU_HINH.sheetSanPham), docBang_(CAU_HINH.sheetNhapHang), docBang_(CAU_HINH.sheetDonHang))
.sort(function (a, b) { return (b.thieu - a.thieu) || (a.ton - b.ton); });
const sh = trangMoi_('TonKho');
sh.getRange('A1:H1').setValues([['Mã SP', 'Sản phẩm', 'Tồn đầu kỳ', 'Đã nhập', 'Đã bán', 'Tồn hiện tại', 'Tồn tối thiểu', 'Gợi ý nhập thêm']])
.setFontWeight('bold').setBackground('#EFEBFF');
if (ds.length) {
sh.getRange(2, 1, ds.length, 8).setValues(ds.map(function (x) { return [x.ma, x.ten, x.tonDau, x.nhap, x.ban, x.ton, x.toiThieu, x.canNhap]; }));
ds.forEach(function (x, i) { if (x.thieu) sh.getRange(i + 2, 1, 1, 8).setBackground('#FDE2E1'); });
}
sh.getRange('J1').setValue('Cập nhật lúc ' + Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'HH:mm dd/MM/yyyy'));
sh.autoResizeColumns(1, 8);
sh.activate();
}
function guiCanhBaoTonKho() {
const thieu = tinhTonKho(docBang_(CAU_HINH.sheetSanPham), docBang_(CAU_HINH.sheetNhapHang), docBang_(CAU_HINH.sheetDonHang))
.filter(function (x) { return x.thieu; });
if (!thieu.length) return;
const nguoiNhan = CAU_HINH.emailNhanCanhBao || Session.getEffectiveUser().getEmail();
const dong = thieu.map(function (x) {
return '<tr><td>' + x.ma + '</td><td>' + x.ten + '</td><td align="right">' + x.ton + '</td><td align="right">' + x.toiThieu + '</td><td align="right">' + x.canNhap + '</td></tr>';
}).join('');
MailApp.sendEmail({
to: nguoiNhan,
subject: 'Cảnh báo tồn kho: ' + thieu.length + ' sản phẩm dưới mức tối thiểu',
htmlBody: '<p>Các sản phẩm sau đang dưới mức tồn tối thiểu:</p>' +
'<table border="1" cellpadding="6" style="border-collapse:collapse"><tr><th>Mã</th><th>Sản phẩm</th><th>Tồn</th><th>Tối thiểu</th><th>Gợi ý nhập</th></tr>' + dong + '</table>' +
'<p><a href="' + SpreadsheetApp.getActive().getUrl() + '">Mở bảng tính</a></p>'
});
}
function batCanhBaoHangNgay() {
ScriptApp.getProjectTriggers().forEach(function (t) {
if (t.getHandlerFunction() === 'guiCanhBaoTonKho') ScriptApp.deleteTrigger(t);
});
ScriptApp.newTrigger('guiCanhBaoTonKho').timeBased().everyDays(1).atHour(CAU_HINH.gioGuiCanhBao).create();
SpreadsheetApp.getActive().toast('Đã bật: mỗi ngày khoảng ' + CAU_HINH.gioGuiCanhBao + ' giờ sáng sẽ gửi cảnh báo nếu có hàng dưới mức tối thiểu.', 'Hộp AI', 8);
}
/* ---------- Gemini viết nhận xét (không bắt buộc) ---------- */
function caiKhoaGemini() {
const ui = SpreadsheetApp.getUi();
const tl = ui.prompt('Khóa API Gemini', 'Dán khóa API tạo ở aistudio.google.com (để trống rồi bấm OK để xóa khóa):', ui.ButtonSet.OK_CANCEL);
if (tl.getSelectedButton() !== ui.Button.OK) return;
const khoa = tl.getResponseText().trim();
const p = PropertiesService.getScriptProperties();
if (khoa) p.setProperty('GEMINI_API_KEY', khoa); else p.deleteProperty('GEMINI_API_KEY');
ui.alert(khoa ? 'Đã lưu khóa. Báo cáo tạo sau sẽ có phần nhận xét.' : 'Đã xóa khóa.');
}
function nhanXetBangGemini_(kq, sp, kenh, thang, nam) {
const khoa = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
if (!khoa) return '';
const soLieu = 'Tháng ' + thang + '/' + nam + ': ' + kq.soDon + ' đơn, doanh thu ' + kq.doanhThu + ' đồng, lãi gộp ' + kq.laiGop + ' đồng.\n' +
'Theo sản phẩm (mã, tên, số lượng, doanh thu, lãi gộp):\n' + sp.map(function (r) { return r.join(' | '); }).join('\n') + '\n' +
'Theo kênh (kênh, doanh thu, tỷ trọng):\n' + kenh.map(function (r) { return r[0] + ' | ' + r[1] + ' | ' + (r[2] * 100).toFixed(1) + '%'; }).join('\n');
const prompt = 'Bạn là kế toán quản trị của một cửa hàng nhỏ ở Việt Nam. Dựa đúng vào số liệu dưới, viết nhận xét tiếng Việt tối đa 120 chữ gồm: ' +
'2 điểm nổi bật, 1 rủi ro, 2 việc nên làm tháng tới. Không bịa số liệu không có trong bảng.\n\n' + soLieu;
try {
const res = UrlFetchApp.fetch('https://generativelanguage.googleapis.com/v1beta/models/' + CAU_HINH.modelGemini + ':generateContent', {
method: 'post',
contentType: 'application/json',
headers: { 'x-goog-api-key': khoa },
payload: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }),
muteHttpExceptions: true
});
const data = JSON.parse(res.getContentText());
if (res.getResponseCode() !== 200) return 'Không gọi được Gemini: ' + (data.error ? data.error.message : res.getResponseCode());
return data.candidates[0].content.parts.map(function (p) { return p.text || ''; }).join('').trim();
} catch (e) {
return 'Không gọi được Gemini: ' + e.message;
}
}
Bước 3. Cấp quyền lần đầu
Quay lại bảng tính, tải lại trang. Menu “Hộp AI” hiện ra cạnh mục Trợ giúp. Nếu chưa thấy, trong trình chỉnh sửa Apps Script chọn hàm onOpen ở ô chọn hàm rồi bấm Chạy: Google hỏi cấp quyền ngay tại đó, chạy xong menu sẽ hiện. Bấm một mục bất kỳ, Google hỏi cấp quyền: chọn tài khoản, gặp màn hình “Google chưa xác minh ứng dụng này” thì bấm Nâng cao > Đi tới (tên dự án) > Cho phép. Cảnh báo này xuất hiện với mọi đoạn mã tự viết chưa gửi Google duyệt. Mã chỉ đọc ghi bảng tính này, gửi email từ Gmail của bạn và (nếu bạn cài khóa) gọi Gemini API; trước khi cấp quyền cho bất kỳ đoạn mã nào, nên đọc qua xem nó gửi dữ liệu đi đâu.
Bước 4. Chạy thử
Bấm Hộp AI > Tạo báo cáo tháng bất kỳ, nhập 10/2026. Với file mẫu, trang tính BaoCao_2026_10 hiện ra sau vài giây: 182 dòng đơn, doanh thu 87.181.000đ, lãi gộp 37.035.000đ, đứng đầu là sạc dự phòng với 17.940.000đ. Số này khớp với công thức SUMIFS ở mức 1, đó là cách kiểm tra mã chạy đúng.

Bấm tiếp Gửi cảnh báo tồn kho ngay: hộp thư nhận một email liệt kê 3 mã dưới mức tối thiểu kèm đường dẫn mở bảng tính. Thấy ổn thì bấm Bật cảnh báo mỗi sáng.

Thêm nhận xét bằng Gemini API (không bắt buộc)
Vào Google AI Studio, tạo khóa API, rồi bấm Hộp AI > Cài khóa API Gemini và dán vào. Khóa được lưu trong Script Properties của dự án, không nằm trong mã hay trong ô bảng tính, nên chia sẻ file cho người khác xem cũng không lộ khóa. Báo cáo tạo sau đó có thêm đoạn nhận xét: 2 điểm nổi bật, 1 rủi ro, 2 việc nên làm.
Hai điều cần biết trước khi bật. Khóa API dùng gói miễn phí thì Google có thể dùng nội dung gửi lên để cải thiện sản phẩm, nên số liệu nhạy cảm chỉ gửi khi đã bật thanh toán cho dự án API. Nhận xét do AI viết có thể đọc sai số, phải đọc lại trước khi gửi cho ai. Tên mô hình trong mã là gemini-flash-latest, Google đổi tên mô hình khá thường xuyên; báo lỗi không tìm thấy mô hình thì xem tên mới trên AI Studio và sửa dòng modelGemini.
Macro: ghi lại thao tác lặp lại, không cần viết mã
Có những việc làm tay mỗi tuần như định dạng lại cột tiền, sắp xếp theo ngày, xóa bộ lọc. Google Sheets ghi được các thao tác đó thành macro:
- Tiện ích mở rộng > Macro > Ghi macro. Chọn “Sử dụng tham chiếu tương đối” nếu muốn macro chạy theo ô đang chọn.
- Làm các thao tác như bình thường, bấm Lưu, đặt tên và phím tắt dạng Ctrl+Alt+Shift+số.
- Lần sau bấm phím tắt hoặc vào Tiện ích mở rộng > Macro để chạy lại.
Macro được lưu thành một hàm Apps Script trong cùng dự án. Mở Tiện ích mở rộng > Apps Script sẽ thấy tệp macros.gs. Đây là cách học Apps Script nhanh nhất: ghi một macro, mở mã ra, dán vào Gemini và hỏi “giải thích từng dòng và sửa để chạy cho mọi trang tính bắt đầu bằng BaoCao_”.
Nhờ Gemini viết Apps Script cho việc riêng của bạn
Đoạn mã trên là khung. Việc riêng của từng shop (tính hoa hồng nhân viên, đối soát tiền Shopee về, xuất PDF gửi kế toán) thì nhờ Gemini viết thêm. Câu lệnh càng rõ cấu trúc dữ liệu, mã càng ít phải sửa:
Viết Google Apps Script cho bảng tính có các trang tính sau (dòng 1 là tiêu đề):
- DonHang: A Ngày, B Mã đơn, C Mã SP, D Tên sản phẩm, E Số lượng, F Đơn giá, G Thành tiền, H Kênh bán
- [trang tính khác nếu có]
Việc cần làm: [mô tả kết quả mong muốn, ví dụ: tạo trang tính tổng hợp doanh thu theo từng tuần của tháng đang chọn, mỗi kênh một cột].
Yêu cầu:
- Thêm mục vào menu "Hộp AI" có sẵn (hàm onOpen đã có, chỉ viết hàm mới và dòng addItem cần thêm).
- Đọc dữ liệu một lần bằng getValues, xử lý trong mảng, ghi một lần bằng setValues.
- Nhãn và thông báo bằng tiếng Việt.
- Không xóa dữ liệu gốc. Nếu trang tính kết quả đã có thì ghi đè trang tính kết quả đó.
- Giải thích ngắn cách chạy và cách kiểm tra kết quả.
Khi mã báo lỗi, chép nguyên dòng lỗi trong Nhật ký thực thi (có số dòng) gửi lại Gemini cùng đoạn mã. Đừng chỉ nói “không chạy được”.
Lỗi thường gặp
| Hiện tượng | Nguyên nhân và cách sửa |
|---|---|
| Công thức Gemini đưa báo lỗi phân tích cú pháp | Gemini viết dấu phẩy, Sheets tiếng Việt cần dấu chấm phẩy. Đổi , thành ; giữa các đối số, hoặc ghi rõ “dùng dấu ;” trong câu hỏi. |
| SUMIFS theo tháng ra 0 | Cột Ngày đang là chữ. Định dạng lại thành Ngày, hoặc dùng DATEVALUE khi nhập từ file khác. |
| Không thấy menu Hộp AI | Chưa tải lại bảng tính sau khi lưu mã, hoặc mở file bằng tài khoản khác không có quyền sửa. Cách nhanh: trong Apps Script chọn hàm onOpen, bấm Chạy. |
| ReferenceError: s is not defined (hoặc chữ khác) ở dòng cuối | Lúc bấm Ctrl+S, phím Ctrl chưa ăn nên chữ s bị gõ vào cuối mã. Bấm vào số dòng trong thông báo lỗi, xóa ký tự thừa, lưu lại. |
| Lỗi Không tìm thấy trang tính | Tên trang tính khác tên trong CAU_HINH, kể cả dấu cách thừa ở cuối tên. |
| Exceeded maximum execution time | Một lần chạy Apps Script tối đa 6 phút. Với vài chục nghìn dòng, chia dữ liệu theo năm sang file khác, hoặc nhờ Gemini viết lại phần chậm (thường do gọi getValue trong vòng lặp). |
| Không nhận được email cảnh báo | Không có mã nào dưới mức tối thiểu thì mã không gửi. Tài khoản Gmail thường gửi được tối đa 100 người nhận mỗi ngày qua Apps Script. |
Tài khoản miễn phí làm được tới đâu
| Miễn phí | Google AI Pro | |
|---|---|---|
| Hỏi Gemini viết công thức, Apps Script trên gemini.google.com | Có | Có, mô hình mạnh hơn và hạn mức cao hơn |
| Gemini ngay trong Google Sheets (bảng bên cạnh) | Chưa có trên tài khoản Gmail miễn phí | Có |
| Gemini tạo bảng tổng hợp, biểu đồ trực tiếp trong Sheets | Không | Có, hiện chỉ với câu lệnh tiếng Anh |
| Apps Script, macro, lịch chạy tự động | Có | Có |
| Gemini API cho đoạn nhận xét | Gói miễn phí của AI Studio, tách riêng | Tách riêng, gói AI Pro không bao gồm API |
Nói cách khác, toàn bộ mức 1 và mức 3 trong bài làm được với tài khoản miễn phí. Gói AI Pro giúp phần hỏi đáp nhanh hơn vì Gemini đọc thẳng bảng tính đang mở, không phải chép cấu trúc bảng qua lại.
Làm báo cáo, soạn email, đọc tài liệu mỗi ngày? Google AI Pro mở Gemini ngay trong Sheets, Docs và Gmail, kèm Deep Research và 5TB lưu trữ dùng chung. Hộp AI nâng cấp trên Gmail của bạn qua nhóm gia đình, từ 79.000đ/tháng, dùng thử 3 ngày, kích hoạt xong mới thanh toán.
Câu hỏi thường gặp
Dữ liệu bán hàng trong Google Sheets có bị gửi cho Gemini không?
Công thức và Apps Script chạy trong tài khoản Google của bạn, không gửi dữ liệu đi đâu. Chỉ khi bạn hỏi Gemini (dán số liệu vào, hoặc dùng Gemini trong Sheets) hay cài khóa API cho phần nhận xét thì số liệu mới được gửi tới Gemini. Không muốn gửi số liệu thật thì hỏi Gemini bằng cấu trúc cột và vài dòng giả.
File Excel có dùng được đoạn Apps Script này không?
Không. Apps Script chỉ chạy trong Google Sheets. Mở file Excel trên Google Drive rồi lưu dưới dạng Google Trang tính là dùng được. Excel có VBA và Office Scripts với cách viết khác.
Nhiều người cùng nhập đơn vào một file có sao không?
Không sao. Chia quyền chỉnh sửa cho nhân viên, menu Hộp AI hiện với ai có quyền sửa. Lịch gửi cảnh báo chạy theo tài khoản người bật nó và gửi email từ Gmail của người đó.
Hàm =AI() trong Google Sheets là gì?
Đó là hàm Google đưa vào Sheets để gọi Gemini ngay trong ô, ví dụ phân loại phản hồi khách hàng hay tóm tắt ghi chú. Hàm trả về chữ, chạy tối đa vài trăm ô một lần và chưa có trên mọi loại tài khoản. Với số liệu doanh thu, tồn kho thì công thức thường cho kết quả chính xác và ổn định hơn.