const TG_TOKEN = '
YOUR_BOT_TOKEN';
const CHAT_ID = '
YOUR_CHAT_ID';
function doPost(e) {
try {
// Nhận từ FormData (payload field) hoặc JSON body
const raw = (e.parameter && e.parameter.payload) ? e.parameter.payload : e.postData.contents;
const data = JSON.parse(raw);
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = data.sheet;
if (sheet === 'HocVien' && data.rows) {
const sh = ss.getSheetByName('HocVien') || ss.insertSheet('HocVien');
sh.clearContents();
const headers = ['ID','Họ tên','SĐT','Loại','Khóa học','Học phí','Ngày ĐK','Trạng thái','Email','Ngày học','Đợt 1','Thời gian Đ1','Đợt 2','Thời gian Đ2','Ghi chú','Cập nhật'];
sh.getRange(1, 1, 1, headers.length).setValues([headers]).setFontWeight('bold');
if (data.rows.length) {
const rows = data.rows.map(s => [
s.id||'', s.name||'', s.phone||'', s.type||'',
s.course||'', s.fee||0, s.start||'', s.status||'',
s.email||'', s.class_date||'',
s.paid1||'Chưa thu', s.paid1_at||'',
s.paid2||'Chưa thu', s.paid2_at||'',
s.note||'', data.time||''
]);
sh.getRange(2, 1, rows.length, headers.length).setValues(rows);
}
}
else if (sheet === 'TonKho' && data.rows) {
const sh = ss.getSheetByName('TonKho') || ss.insertSheet('TonKho');
sh.clearContents();
const headers = ['ID','Tên nguyên liệu','Tồn kho','Đơn vị','Tối thiểu','Giá nhập','Cập nhật'];
sh.getRange(1,1,1,headers.length).setValues([headers]).setFontWeight('bold');
if (data.rows.length) {
const rows = data.rows.map(r => [r.id||'',r.name||'',r.qty||0,r.unit||'',r.min||0,r.cost||0,data.time||'']);
sh.getRange(2,1,rows.length,headers.length).setValues(rows);
}
}
else if (sheet === 'DonHang') {
const sh = ss.getSheetByName('DonHang') || ss.insertSheet('DonHang');
if (sh.getLastRow() === 0)
sh.getRange(1,1,1,13).setValues([['Mã ĐH','Khách','Sản phẩm','Tạm tính','CK(%)','VAT(%)','Tổng TT','Thanh toán','Giao hàng','Địa chỉ','Trạng thái','Thời gian','Ghi chú']]).setFontWeight('bold');
// Dedup by ID
const existIds=sh.getLastRow()>1?sh.getRange(2,1,sh.getLastRow()-1,1).getValues().map(r=>r[0]):[];
if(existIds.indexOf(data.id)===-1){
sh.appendRow([data.id||'',data.customer||'',data.items||'',
data.subtotal||data.total||0,data.discount||0,data.vat||0,data.total||0,
data.payment||'',data.shipping||'',data.address||'',
data.status||'',data.time||'',data.note||'']);
} else {
// Update status only
const allR=sh.getDataRange().getValues();
for(var ki=1;ki
0) {
const r5 = data.rows.map(t => [
t.id||'', t.type||'', t.category||'', t.amount||0,
t.note||'', t.time||'', t.auto||''
]);
sh5.getRange(2,1,r5.length,h5.length).setValues(r5);
r5.forEach((r,i) => {
sh5.getRange(i+2,1,1,h5.length).setBackground(r[1]==='Thu'?'#f0fff4':'#fff0f0');
});
// Dòng tổng kết
const thuTotal = r5.filter(r=>r[1]==='Thu').reduce((a,r)=>a+(+r[3]||0),0);
const chiTotal = r5.filter(r=>r[1]==='Chi').reduce((a,r)=>a+(+r[3]||0),0);
const lastRow = r5.length + 2;
sh5.getRange(lastRow,1,1,h5.length).setBackground('#fff8e1');
sh5.getRange(lastRow,1).setValue('TỔNG KẾT').setFontWeight('bold');
sh5.getRange(lastRow,2).setValue('Thu: '+thuTotal.toLocaleString()+'đ').setFontWeight('bold').setFontColor('#1a7a1a');
sh5.getRange(lastRow,3).setValue('Chi: '+chiTotal.toLocaleString()+'đ').setFontWeight('bold').setFontColor('#cc0000');
sh5.getRange(lastRow,4).setValue(thuTotal-chiTotal).setFontWeight('bold').setFontColor(thuTotal>=chiTotal?'#1a7a1a':'#cc0000');
sh5.getRange(lastRow,5).setValue('Lợi nhuận').setFontWeight('bold');
}
}
return ContentService.createTextOutput(JSON.stringify({ok:true})).setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService.createTextOutput(JSON.stringify({ok:false,error:err.toString()})).setMimeType(ContentService.MimeType.JSON);
}
}
// ═══════════════════════════════════════════════════
// BÁO CÁO TỰ ĐỘNG 8H SÁNG
// Set Trigger: Apps Script → Triggers → sendDailyReport8am → Time-driven → Day timer → 8-9am
// ═══════════════════════════════════════════════════
function sendDailyReport8am() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const shSt = ss.getSheetByName('HocVien');
const shOrd = ss.getSheetByName('DonHang');
const shStock = ss.getSheetByName('TonKho');
const today = new Date(); today.setHours(0, 0, 0, 0);
const fmtMoney = n => (+n||0).toLocaleString('vi-VN') + 'đ';
const todayStr = Utilities.formatDate(today, 'Asia/Ho_Chi_Minh', 'dd/MM/yyyy');
// ── Học viên ──
let activeCount = 0, activeOnline = 0, activeOffline = 0, unpaid1 = [], unpaid2 = [], endingSoon = [];
if (shSt && shSt.getLastRow() > 1) {
const rows = shSt.getDataRange().getValues();
const heads = rows[0];
const col = h => heads.indexOf(h);
rows.slice(1).forEach(r => {
const name = r[col('Họ tên')] || '';
const status = r[col('Trạng thái')] || '';
const course = r[col('Khóa học')] || '';
const fee = +r[col('Học phí')] || 0;
const type = r[col('Loại')] || '';
const paid1 = r[col('Đợt 1')] || '';
const paid2 = r[col('Đợt 2')] || '';
const endDate = r[col('Ngày KT')] || '';
if (status === 'Đang học') { activeCount++; if(type==='Online') activeOnline++; else activeOffline++; }
if (fee > 0 && paid1 !== 'Đã thu')
unpaid1.push(name + ' — ' + fmtMoney(+r[col('paid1_amount')]||Math.round(fee * 0.3)));
if (fee > 0 && paid1 === 'Đã thu' && paid2 !== 'Đã thu' && type === 'Offline') {
const rest = +r[col('paid2_amount')] || (fee - Math.round(fee * 0.3));
let suffix = '';
if (endDate) {
const ed = new Date(endDate); ed.setHours(0,0,0,0);
const d = Math.round((ed - today) / 86400000);
suffix = d === 0 ? ' ⚠️ KT HÔM NAY' : ' (còn ' + d + ' ngày)';
if (d >= 0 && d <= 3) endingSoon.push(name + ' — ' + (d===0?'HÔM NAY':'còn '+d+' ngày') + (paid2==='Đã thu'?' ✅':' ❌ chưa thu đợt 2'));
}
unpaid2.push(name + ' — ' + fmtMoney(rest) + suffix);
}
});
}
// ── Đơn hàng hôm nay ──
let todayRev = 0, todayCount = 0;
if (shOrd && shOrd.getLastRow() > 1) {
shOrd.getDataRange().getValues().slice(1).forEach(r => {
if ((r[5]||'').toString().includes(todayStr) && r[4]==='Đã thanh toán' && !(r[0]||'').toString().startsWith('HP')) {
todayRev += +r[3]||0; todayCount++;
}
});
}
// ── Kho sắp hết ──
let lowStock = [];
if (shStock && shStock.getLastRow() > 1) {
const sRows = shStock.getDataRange().getValues();
const sHeads = sRows[0];
const sc = h => sHeads.indexOf(h);
sRows.slice(1).forEach(r => {
const qty = +r[sc('Tồn kho')]||0, min = +r[sc('Tối thiểu')]||0;
if (qty <= min) lowStock.push(r[sc('Tên nguyên liệu')] + ': còn ' + qty + ' ' + (r[sc('Đơn vị')]||''));
});
}
// ── Build message ──
const d = Utilities.formatDate(today, 'Asia/Ho_Chi_Minh', 'dd/MM/yyyy');
let msg = '🌅 *BÁO CÁO SÁNG — ' + d + '*
━━━━━━━━━━━━━━━━
';
msg += '🎓 *HỌC VIÊN ĐANG HỌC: ' + activeCount + ' người*
';
if(activeOffline>0) msg += '• Offline: ' + activeOffline + ' người
';
if(activeOnline>0) msg += '• Online: ' + activeOnline + ' người
';
if (unpaid1.length) { msg += '
💳 *CHƯA THU ĐỢT 1 (' + unpaid1.length + ')*
'; unpaid1.forEach(s => msg += '• ' + s + '
'); }
if (unpaid2.length) { msg += '
💸 *CHƯA THU ĐỢT 2 (' + unpaid2.length + ')*
'; unpaid2.forEach(s => msg += '• ' + s + '
'); }
if (endingSoon.length) { msg += '
🏁 *KẾT THÚC KHÓA SẮP TỚI*
'; endingSoon.forEach(s => msg += '• ' + s + '
'); }
if (todayRev > 0) { msg += '
🛍️ *HÔM NAY*
• ' + todayCount + ' đơn — ' + fmtMoney(todayRev) + '
'; }
if (lowStock.length) { msg += '
⚠️ *KHO SẮP HẾT*
'; lowStock.forEach(s => msg += '• ' + s + '
'); }
msg += '
━━━━━━━━━━━━━━━━
_Trương Dáng Hương Academy_';
UrlFetchApp.fetch('https://api.telegram.org/bot' + TG_TOKEN + '/sendMessage', {
method: 'post', payload: { chat_id: CHAT_ID, text: msg, parse_mode: 'Markdown' }
});
}
// Giữ hàm cũ để tương thích trigger cũ
function sendDailyReminders() { sendDailyReport8am(); }