Google Sheets Nâng Cao Bài 10: Apps Script Nâng Cao - API Calls, JSON và Web App

Google Sheets Nâng Cao Bài 10: Apps Script Nâng Cao - API Calls, JSON và Web App
📚 Series Google Sheets Nâng Cao (Bài 10/10 — Bài Kết)
Bài 1: Data Validation nâng cao | Bài 2: Conditional Formatting | Bài 3: IMPORTRANGE & kết nối sheet | Bài 4: Array Formulas | Bài 5: Apps Script cơ bản | Bài 6: QUERY function | Bài 7: Dashboard & Charts | Bài 8: Pivot Table nâng cao | Bài 9: Automation với Triggers | Bài 10: Apps Script Nâng Cao (Bài này)
Chào mừng bạn đến với bài học cuối cùng trong series Google Sheets Nâng Cao 10 bài! Sau khi đã nắm vững Apps Script cơ bản ở Bài 5, hôm nay chúng ta sẽ tiến thêm một bước — học cách gọi external API, xử lý JSON, và thậm chí biến Google Sheets thành một Web App thực sự.
Đây là kiến thức giúp bạn xây dựng những hệ thống tự động hóa mạnh mẽ, tích hợp với hàng trăm dịch vụ bên ngoài như Telegram, Slack, CRM, hay bất kỳ REST API nào.
1. Ôn Lại Bài 5: Apps Script Cơ Bản
Trước khi đi vào nội dung nâng cao, hãy nhanh chóng ôn lại những gì đã học ở Bài 5:
- Apps Script Editor: Truy cập qua Extensions → Apps Script
- SpreadsheetApp: API chính để thao tác với Google Sheets
- getActiveSheet(), getRange(), getValue()/setValue()
- Simple triggers: onOpen, onEdit, onFormSubmit
- Time-driven triggers: chạy script theo lịch
Nếu bạn chưa đọc Bài 5, hãy quay lại xem trước. Bài 10 này yêu cầu kiến thức nền tảng từ Bài 5.
2. UrlFetchApp: Gọi External API từ Apps Script
UrlFetchApp là service quan trọng nhất trong Apps Script cho phép bạn thực hiện HTTP requests — GET, POST, PUT, DELETE — đến bất kỳ URL nào trên internet.
2.1. GET Request Cơ Bản
function fetchWeatherData() {
// Gọi API thời tiết miễn phí (ví dụ)
const city = 'Ho Chi Minh';
const apiKey = 'YOUR_API_KEY'; // Lưu trong PropertiesService
const url = 'https://api.openweathermap.org/data/2.5/weather?q='
+ encodeURIComponent(city)
+ '&appid=' + apiKey
+ '&units=metric&lang=vi';
try {
const response = UrlFetchApp.fetch(url);
const statusCode = response.getResponseCode();
if (statusCode === 200) {
const jsonText = response.getContentText();
const data = JSON.parse(jsonText);
// Lấy thông tin từ JSON
const temp = data.main.temp;
const description = data.weather[0].description;
const humidity = data.main.humidity;
// Ghi vào sheet
const sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange('A1').setValue('Nhiệt độ: ' + temp + '°C');
sheet.getRange('A2').setValue('Thời tiết: ' + description);
sheet.getRange('A3').setValue('Độ ẩm: ' + humidity + '%');
sheet.getRange('A4').setValue('Cập nhật: ' + new Date().toLocaleString('vi-VN'));
Logger.log('Lấy dữ liệu thành công!');
} else {
Logger.log('Lỗi HTTP: ' + statusCode);
}
} catch (e) {
Logger.log('Lỗi: ' + e.toString());
}
}
2.2. POST Request với JSON Body
function postDataToAPI() {
const url = 'https://api.example.com/orders';
// Dữ liệu cần gửi
const payload = {
orderId: 'ORD-2027-001',
customer: 'Nguyễn Văn A',
total: 1500000,
items: [
{ name: 'Sản phẩm A', qty: 2, price: 500000 },
{ name: 'Sản phẩm B', qty: 1, price: 500000 }
]
};
const options = {
method: 'POST',
contentType: 'application/json',
headers: {
'Authorization': 'Bearer ' + getApiKey(), // Lấy từ PropertiesService
'X-Custom-Header': 'GoogleSheets-Integration'
},
payload: JSON.stringify(payload),
muteHttpExceptions: true // Không throw exception khi lỗi HTTP
};
const response = UrlFetchApp.fetch(url, options);
const result = JSON.parse(response.getContentText());
if (result.success) {
Logger.log('Order created: ' + result.orderId);
return result;
} else {
Logger.log('API Error: ' + result.message);
throw new Error(result.message);
}
}
// Helper: Lấy API key từ PropertiesService (bảo mật hơn hardcode)
function getApiKey() {
return PropertiesService.getScriptProperties().getProperty('API_KEY');
}
2.3. Lưu API Keys An Toàn với PropertiesService
// Chạy một lần để lưu API key
function setupApiKeys() {
const props = PropertiesService.getScriptProperties();
props.setProperty('OPENWEATHER_API_KEY', 'abc123xyz');
props.setProperty('TELEGRAM_BOT_TOKEN', '12345:ABCDEF');
props.setProperty('TELEGRAM_CHAT_ID', '-100123456789');
Logger.log('API keys đã được lưu an toàn!');
}
// Đọc API key
function getProperty(key) {
return PropertiesService.getScriptProperties().getProperty(key);
}
Quan trọng: Không bao giờ hardcode API keys trực tiếp trong code. Dùng PropertiesService để lưu trữ an toàn. Keys này không bị expose trong script editor và không được sync lên Git.
3. Xử Lý JSON Response Nâng Cao
3.1. Parse JSON Phức Tạp
function processComplexJSON() {
// Giả sử API trả về danh sách đơn hàng
const sampleResponse = {
"status": "success",
"data": {
"orders": [
{
"id": "ORD001",
"date": "2027-01-15",
"customer": { "name": "Nguyễn Văn A", "phone": "0901234567" },
"items": [
{ "product": "Áo polo", "qty": 2, "price": 350000 }
],
"total": 700000,
"status": "delivered"
},
{
"id": "ORD002",
"date": "2027-01-16",
"customer": { "name": "Trần Thị B", "phone": "0987654321" },
"items": [
{ "product": "Quần jean", "qty": 1, "price": 650000 },
{ "product": "Áo thun", "qty": 3, "price": 200000 }
],
"total": 1250000,
"status": "processing"
}
],
"totalOrders": 2,
"totalRevenue": 1950000
}
};
const orders = sampleResponse.data.orders;
const sheet = SpreadsheetApp.getActiveSheet();
// Headers
sheet.getRange(1, 1, 1, 6).setValues([
['Order ID', 'Ngày', 'Khách hàng', 'SĐT', 'Tổng tiền', 'Trạng thái']
]);
// Data rows
orders.forEach((order, index) => {
const row = index + 2;
sheet.getRange(row, 1, 1, 6).setValues([[
order.id,
order.date,
order.customer.name,
order.customer.phone,
order.total,
order.status
]]);
});
// Tóm tắt
Logger.log('Tổng đơn hàng: ' + sampleResponse.data.totalOrders);
Logger.log('Tổng doanh thu: ' + sampleResponse.data.totalRevenue.toLocaleString() + ' VND');
}
3.2. Chuyển Sheet Data thành JSON để gửi API
function sheetToJSON() {
const sheet = SpreadsheetApp.getActiveSheet();
const data = sheet.getDataRange().getValues();
const headers = data[0]; // Hàng đầu tiên là headers
const rows = data.slice(1); // Các hàng còn lại
const jsonArray = rows
.filter(row => row[0] !== '') // Bỏ hàng trống
.map(row => {
const obj = {};
headers.forEach((header, i) => {
obj[header] = row[i];
});
return obj;
});
Logger.log(JSON.stringify(jsonArray, null, 2));
return jsonArray;
}
4. Tạo Web App từ Apps Script
Đây là một trong những tính năng powerful nhất — bạn có thể deploy Apps Script như một web server thực sự!
4.1. Hiểu doGet và doPost
// doGet: Xử lý HTTP GET request
function doGet(e) {
const action = e.parameter.action || 'default';
if (action === 'getOrders') {
const orders = getOrdersFromSheet();
return ContentService
.createTextOutput(JSON.stringify({ success: true, data: orders }))
.setMimeType(ContentService.MimeType.JSON);
}
if (action === 'getStats') {
const stats = getStatsFromSheet();
return ContentService
.createTextOutput(JSON.stringify(stats))
.setMimeType(ContentService.MimeType.JSON);
}
// Default: trả về HTML page
return HtmlService.createHtmlOutputFromFile('index');
}
// doPost: Xử lý HTTP POST request (webhook, form submission)
function doPost(e) {
try {
const data = JSON.parse(e.postData.contents);
// Lưu data vào sheet
const sheet = SpreadsheetApp.openById('SPREADSHEET_ID')
.getSheetByName('Data');
sheet.appendRow([
new Date(),
data.name,
data.email,
data.message
]);
return ContentService
.createTextOutput(JSON.stringify({ success: true, message: 'Đã nhận dữ liệu!' }))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService
.createTextOutput(JSON.stringify({ success: false, error: error.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
4.2. HTMLService — Tạo UI Nhúng Vào Google Sheets
Tạo file index.html trong Apps Script Editor:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #4285f4; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
.success { color: green; margin-top: 10px; }
.error { color: red; margin-top: 10px; }
</style>
</head>
<body>
<h2>Thêm Khách Hàng Mới</h2>
<div class="form-group">
<label>Tên khách hàng:</label>
<input type="text" id="name" placeholder="Nguyễn Văn A">
</div>
<div class="form-group">
<label>Email:</label>
<input type="email" id="email" placeholder="email@example.com">
</div>
<div class="form-group">
<label>Số điện thoại:</label>
<input type="text" id="phone" placeholder="0901234567">
</div>
<div class="form-group">
<label>Ghi chú:</label>
<textarea id="note" rows="3" placeholder="Ghi chú..."></textarea>
</div>
<button onclick="submitForm()">Thêm Khách Hàng</button>
<div id="message"></div>
<script>
function submitForm() {
const data = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
phone: document.getElementById('phone').value,
note: document.getElementById('note').value
};
if (!data.name || !data.email) {
document.getElementById('message').innerHTML = '<p class="error">Vui lòng điền đầy đủ thông tin!</p>';
return;
}
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.addCustomer(data);
}
function onSuccess(result) {
document.getElementById('message').innerHTML = '<p class="success">✅ Thêm thành công! ID: ' + result.id + '</p>';
// Clear form
['name', 'email', 'phone', 'note'].forEach(id => document.getElementById(id).value = '');
}
function onFailure(error) {
document.getElementById('message').innerHTML = '<p class="error">❌ Lỗi: ' + error.message + '</p>';
}
</script>
</body>
</html>
Code Apps Script xử lý form:
// Code.gs
function addCustomer(data) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Customers');
const id = 'KH-' + Date.now();
sheet.appendRow([
id,
data.name,
data.email,
data.phone,
data.note,
new Date().toLocaleDateString('vi-VN')
]);
return { success: true, id: id };
}
// Mở sidebar trong Sheets
function openCustomerForm() {
const html = HtmlService.createHtmlOutputFromFile('index')
.setTitle('Thêm Khách Hàng')
.setWidth(400);
SpreadsheetApp.getUi().showSidebar(html);
}
// Thêm menu tùy chỉnh
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('CRM Tools')
.addItem('Thêm Khách Hàng', 'openCustomerForm')
.addItem('Gửi Báo Cáo Telegram', 'sendDailyReportToTelegram')
.addToUi();
}
5. Tích Hợp Telegram API: Gửi Thông Báo từ Google Sheets
Đây là use case cực kỳ phổ biến trong doanh nghiệp Việt Nam — gửi thông báo đơn hàng mới, báo cáo doanh thu hàng ngày, cảnh báo tồn kho thấp,... trực tiếp lên Telegram.
5.1. Setup Telegram Bot
- Mở Telegram, tìm @BotFather
- Gõ
/newbot, đặt tên và username cho bot - BotFather sẽ cấp Bot Token dạng
123456:ABCDEFabcdef - Add bot vào group/channel, lấy Chat ID bằng cách gọi API:
https://api.telegram.org/botTOKEN/getUpdates
5.2. Gửi Tin Nhắn Text
function sendTelegramMessage(message) {
const token = getProperty('TELEGRAM_BOT_TOKEN');
const chatId = getProperty('TELEGRAM_CHAT_ID');
const url = 'https://api.telegram.org/bot' + token + '/sendMessage';
const payload = {
chat_id: chatId,
text: message,
parse_mode: 'HTML' // Hỗ trợ HTML formatting
};
const options = {
method: 'POST',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const result = JSON.parse(response.getContentText());
if (result.ok) {
Logger.log('Telegram message sent successfully!');
} else {
Logger.log('Telegram error: ' + result.description);
}
return result;
}
5.3. Báo Cáo Doanh Thu Hàng Ngày Tự Động
function sendDailyReportToTelegram() {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('DonHang');
const data = sheet.getDataRange().getValues();
const today = new Date().toLocaleDateString('vi-VN');
let todayRevenue = 0;
let todayOrders = 0;
// Tính doanh thu hôm nay (cột A: ngày, cột E: tổng tiền)
for (let i = 1; i < data.length; i++) {
const orderDate = new Date(data[i][0]).toLocaleDateString('vi-VN');
if (orderDate === today) {
todayRevenue += data[i][4] || 0;
todayOrders++;
}
}
const message = [
'📊 Báo Cáo Doanh Thu Hôm Nay',
'📅 Ngày: ' + today,
'🛒 Số đơn hàng: ' + todayOrders + '',
'💰 Doanh thu: ' + todayRevenue.toLocaleString('vi-VN') + ' ₫',
'',
'✅ Dữ liệu từ SheetStore Dashboard'
].join('
');
sendTelegramMessage(message);
}
// Setup trigger tự động gửi lúc 20:00 mỗi ngày
function setupDailyTrigger() {
// Xóa triggers cũ nếu có
ScriptApp.getProjectTriggers().forEach(trigger => {
if (trigger.getHandlerFunction() === 'sendDailyReportToTelegram') {
ScriptApp.deleteTrigger(trigger);
}
});
// Tạo trigger mới
ScriptApp.newTrigger('sendDailyReportToTelegram')
.timeBased()
.everyDays(1)
.atHour(20)
.create();
Logger.log('Đã setup trigger gửi báo cáo lúc 20:00 mỗi ngày!');
}
5.4. Cảnh Báo Tồn Kho Thấp
function checkLowStock() {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('TonKho');
const data = sheet.getDataRange().getValues();
const lowStockItems = [];
const THRESHOLD = 10; // Cảnh báo khi tồn kho < 10
for (let i = 1; i < data.length; i++) {
const productName = data[i][0];
const currentStock = data[i][2];
const minStock = data[i][3] || THRESHOLD;
if (productName && currentStock < minStock) {
lowStockItems.push({
name: productName,
stock: currentStock,
min: minStock
});
}
}
if (lowStockItems.length > 0) {
let message = '⚠️ CẢNH BÁO: Tồn Kho Thấp!
';
lowStockItems.forEach(item => {
message += '🔴 ' + item.name + ': còn ' + item.stock + ' (min: ' + item.min + ')
';
});
message += '
📱 Vui lòng bổ sung hàng ngay!';
sendTelegramMessage(message);
Logger.log('Đã gửi cảnh báo cho ' + lowStockItems.length + ' sản phẩm');
} else {
Logger.log('Tồn kho ổn định, không cần cảnh báo');
}
}
6. Tạo Webhook Receiver
Web App (doPost) có thể hoạt động như một webhook endpoint — nhận dữ liệu từ các dịch vụ bên ngoài và lưu vào Google Sheets tự động.
// Webhook nhận đơn hàng từ website/app
function doPost(e) {
try {
// Verify webhook signature (bảo mật)
const signature = e.parameter.sig || '';
if (!verifySignature(e.postData.contents, signature)) {
return ContentService
.createTextOutput(JSON.stringify({ error: 'Invalid signature' }))
.setMimeType(ContentService.MimeType.JSON);
}
const order = JSON.parse(e.postData.contents);
// Lưu vào Sheets
const sheet = SpreadsheetApp.openById('SPREADSHEET_ID')
.getSheetByName('Orders');
sheet.appendRow([
order.orderId,
order.customerName,
order.customerEmail,
order.totalAmount,
order.paymentMethod,
order.status,
new Date()
]);
// Gửi Telegram notification
const msg = '🛒 Đơn Hàng Mới!
' +
'📦 Mã: ' + order.orderId + '
' +
'👤 KH: ' + order.customerName + '
' +
'💰 Tổng: ' + parseInt(order.totalAmount).toLocaleString('vi-VN') + ' ₫';
sendTelegramMessage(msg);
return ContentService
.createTextOutput(JSON.stringify({ success: true, received: order.orderId }))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
Logger.log('Webhook error: ' + error.toString());
return ContentService
.createTextOutput(JSON.stringify({ success: false, error: error.message }))
.setMimeType(ContentService.MimeType.JSON);
}
}
function verifySignature(body, sig) {
const secret = getProperty('WEBHOOK_SECRET');
const expected = Utilities.computeHmacSha256Signature(body, secret);
const expectedHex = expected.map(b => ('0' + (b & 0xFF).toString(16)).slice(-2)).join('');
return sig === expectedHex;
}
7. Mini CRM Nhúng Trong Google Sheets
Kết hợp tất cả kỹ năng trên để xây dựng một mini CRM hoàn chỉnh:
// === MINI CRM SYSTEM ===
// 1. Menu setup
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('🏪 CRM Tools')
.addItem('➕ Thêm khách hàng', 'openAddCustomerDialog')
.addItem('📧 Gửi email hàng loạt', 'openBulkEmailDialog')
.addSeparator()
.addItem('📊 Báo cáo Telegram', 'sendDailyReportToTelegram')
.addItem('⚠️ Check tồn kho', 'checkLowStock')
.addSeparator()
.addItem('🔄 Sync từ API', 'syncFromExternalAPI')
.addToUi();
}
// 2. Sync data từ external CRM API
function syncFromExternalAPI() {
const apiUrl = 'https://api.yourcrm.com/customers';
const apiKey = getProperty('CRM_API_KEY');
const options = {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + apiKey,
'Content-Type': 'application/json'
},
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(apiUrl, options);
if (response.getResponseCode() !== 200) {
SpreadsheetApp.getUi().alert('Lỗi kết nối API: ' + response.getResponseCode());
return;
}
const customers = JSON.parse(response.getContentText()).data;
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
// Clear và rewrite (hoặc dùng incremental sync phức tạp hơn)
sheet.clearContents();
sheet.appendRow(['ID', 'Tên', 'Email', 'SĐT', 'Tổng mua', 'Lần cuối mua', 'Tag']);
customers.forEach(cust => {
sheet.appendRow([
cust.id, cust.name, cust.email, cust.phone,
cust.totalPurchase, cust.lastOrderDate, (cust.tags || []).join(', ')
]);
});
SpreadsheetApp.getUi().alert('Đã sync ' + customers.length + ' khách hàng!');
}
8. Best Practices Error Handling Trong Apps Script
// Pattern 1: Try-catch với logging chi tiết
function robustApiCall(url, options) {
const MAX_RETRIES = 3;
let lastError;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const response = UrlFetchApp.fetch(url, { ...options, muteHttpExceptions: true });
const statusCode = response.getResponseCode();
if (statusCode >= 200 && statusCode < 300) {
return JSON.parse(response.getContentText());
}
// Rate limit: chờ và retry
if (statusCode === 429) {
Logger.log('Rate limited, chờ ' + (attempt * 2) + ' giây...');
Utilities.sleep(attempt * 2000);
continue;
}
// Server error: retry
if (statusCode >= 500) {
Logger.log('Server error ' + statusCode + ', retry ' + attempt + '/' + MAX_RETRIES);
Utilities.sleep(1000);
continue;
}
// Client error: không retry
throw new Error('HTTP ' + statusCode + ': ' + response.getContentText());
} catch (e) {
lastError = e;
Logger.log('Attempt ' + attempt + ' failed: ' + e.toString());
if (attempt < MAX_RETRIES) {
Utilities.sleep(1000 * attempt);
}
}
}
// Gửi cảnh báo lỗi qua Telegram
sendTelegramMessage('🚨 API Error Alert
URL: ' + url + '
Lỗi: ' + lastError.message);
throw lastError;
}
// Pattern 2: Centralized error handling
function withErrorHandling(fn, context) {
try {
return fn();
} catch (e) {
const errorMsg = [
'❌ Lỗi trong function: ' + (fn.name || 'anonymous'),
'Context: ' + (context || 'N/A'),
'Error: ' + e.toString(),
'Stack: ' + e.stack
].join('
');
Logger.log(errorMsg);
// Log vào sheet "Errors"
const errorSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Errors');
if (errorSheet) {
errorSheet.appendRow([new Date(), fn.name || 'unknown', e.toString(), context]);
}
return null; // Hoặc throw tùy use case
}
}
9. Deployment Modes: Test vs Production
Khi deploy Web App từ Apps Script, có 2 lựa chọn quan trọng:
| Setting | Test Deployment | Production Deployment |
|---|---|---|
| Mục đích | Dev & testing | Live users |
| Phiên bản code | Latest (real-time) | Locked version |
| URL ổn định | Không (thay đổi) | Có (cố định) |
| Execute as | Me (owner) | Me / User accessing |
| Who has access | Only me | Anyone / Org |
| Quota | 6 phút/execution | 6 phút/execution |
Quy trình Deploy Production:
- Click Deploy → New deployment
- Type: Web app
- Execute as: Me (nếu cần access Sheet của owner)
- Who has access: Anyone (cho public webhook) hoặc Anyone within [domain]
- Click Deploy → Copy URL
- Khi update code: Deploy → Manage deployments → Edit → tạo version mới
10. Tổng Kết Series Google Sheets Nâng Cao 10 Bài
Chúc mừng! Bạn đã hoàn thành toàn bộ series Google Sheets Nâng Cao. Hãy nhìn lại hành trình đã đi:
📚 Bạn Đã Học Được Gì?
| Bài 1 | Data Validation nâng cao — kiểm soát dữ liệu đầu vào chính xác |
| Bài 2 | Conditional Formatting — highlight dữ liệu theo điều kiện phức tạp |
| Bài 3 | IMPORTRANGE & kết nối nhiều Sheet — chia sẻ dữ liệu liên sheet |
| Bài 4 | Array Formulas — xử lý hàng nghìn dòng bằng 1 công thức |
| Bài 5 | Apps Script cơ bản — tự động hóa với JavaScript |
| Bài 6 | QUERY function — SQL-like queries trong Google Sheets |
| Bài 7 | Dashboard & Charts — visualize data chuyên nghiệp |
| Bài 8 | Pivot Table nâng cao — phân tích đa chiều nhanh chóng |
| Bài 9 | Automation với Triggers — chạy script tự động không cần can thiệp |
| Bài 10 | Apps Script nâng cao — API calls, JSON, Web App, Telegram integration |
Bước Tiếp Theo
- Áp dụng ngay: Chọn 1 quy trình thủ công trong doanh nghiệp và tự động hóa bằng Apps Script
- Template SheetStore: Dùng các template có sẵn của SheetStore làm nền tảng, customize theo nhu cầu
- Cộng đồng: Tham gia nhóm Facebook/Zalo người dùng SheetStore để chia sẻ kinh nghiệm
- Nâng cấp lên Custom Code: Nếu Google Sheets không đủ — SheetStore cung cấp dịch vụ code phần mềm riêng theo yêu cầu
💡 Lời Khuyên Từ SheetStore
Google Sheets + Apps Script có thể handle 80% nhu cầu quản lý của SME Việt Nam với chi phí gần như bằng 0. Nhưng khi bạn cần multi-user real-time, mobile app riêng, hay tích hợp phức tạp — đó là lúc nên cân nhắc phần mềm chuyên dụng. Xem dịch vụ code app riêng của SheetStore.
FAQ
Apps Script có thể gọi bao nhiêu API mỗi ngày?
Google giới hạn: 20,000 URL fetch calls/ngày (free), 6 phút execution time/script. Đủ cho hầu hết SME.
Webhook URL của Web App có thay đổi khi update code không?
Production deployment URL cố định. Chỉ cần tạo version mới khi update, URL không đổi.
Có thể dùng Apps Script mà không biết JavaScript không?
Khó. Apps Script dùng JavaScript. Nhưng với AI (ChatGPT/Gemini), bạn có thể prompt viết script và chỉ cần hiểu logic để customize.
Chi phí sử dụng Apps Script là bao nhiêu?
Miễn phí với Google account thông thường. Google Workspace trả phí có quota cao hơn.
📚 Bài Viết Liên Quan
- Template Google Sheets Báo Cáo Bán Hàng Theo Vùng và Đại Lý 2027: Phân Tích Đa Chiều
- Google Sheets Nâng Cao Bài 9: Bảo Mật, Phân Quyền và Chia Sẻ Chuyên Nghiệp
- Google Sheets Nâng Cao Bài 4: Hàm QUERY - Lọc và Phân Tích Dữ Liệu Chuyên Nghiệp
- Template Google Sheets Quản Lý Phòng Khám và Bệnh Viện Nhỏ 2027
Chia sẻ bài viết:
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.