Cấu Hình Liên Kết Google Sheet & Google Drive
function saveScanImageToDrive(base64Data, fileName) {
if (!base64Data || base64Data.indexOf('base64,') === -1) return '';
try {
var folderName = 'TMS_Ho_So_Chung_Tu_GDP';
var folders = DriveApp.getFoldersByName(folderName);
var folder = folders.hasNext() ? folders.next() : DriveApp.createFolder(folderName);
var parts = base64Data.split('base64,');
var contentType = parts[0].replace('data:', '').replace(';', '');
var bytes = Utilities.base64Decode(parts[1]);
var blob = Utilities.newBlob(bytes, contentType, fileName + '.jpg');
var file = folder.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
return file.getUrl();
} catch (err) {
return 'Loi_Drive: ' + err.toString();
}
}
function doGet(e) {
var action = (e && e.parameter && e.parameter.action) || 'getData';
var ss = SpreadsheetApp.getActiveSpreadsheet();
if (action === 'getData') {
var tripSheet = ss.getSheetByName('Nhat_Trinh_Lai_Xe');
var maintSheet = ss.getSheetByName('So_Bao_Duong_Xe');
var gdpSheet = ss.getSheetByName('Ho_So_GDP_5_Quy_Trinh');
var trips = [], maints = [], gdpRecords = [];
if (tripSheet && tripSheet.getLastRow() > 1) {
var data = tripSheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
var r = data[i];
trips.push({
id: r[0],
date: r[1] instanceof Date ? Utilities.formatDate(r[1], "GMT+7", "yyyy-MM-dd") : String(r[1]),
driver: r[2], plate: r[3], route: r[4], time: r[5],
meals: Number(r[6]) || 0, bot: Number(r[7]) || 0, parking: Number(r[8]) || 0,
hotel: Number(r[9]) || 0, loading: Number(r[10]) || 0, wash: Number(r[11]) || 0,
repair: Number(r[12]) || 0, guest: Number(r[13]) || 0, guestNote: r[14],
hasIncident: r[15] === 'CÓ', incidentType: r[16], incidentDesc: r[17], incidentCost: Number(r[18]) || 0,
other: Number(r[19]) || 0,
gdpCleanStatus: r[22] || '', gdpTempStart: r[23] || '', gdpTempEnd: r[24] || '',
gdpInvoiceNo: r[25] || '', gdpCustomer: r[26] || '', gdpGreenSlip: r[27] || '', gdpDriveUrl: r[28] || ''
});
}
}
if (maintSheet && maintSheet.getLastRow() > 1) {
var mData = maintSheet.getDataRange().getValues();
for (var j = 1; j < mData.length; j++) {
var m = mData[j];
maints.push({
id: m[0],
date: m[1] instanceof Date ? Utilities.formatDate(m[1], "GMT+7", "yyyy-MM-dd") : String(m[1]),
plate: m[2], driver: m[3], currentKm: Number(m[4]) || 0, nextKm: Number(m[5]) || 0,
category: m[6], items: m[7], garage: m[8], invoiceNo: m[9], cost: Number(m[10]) || 0, note: m[11],
gdpDriveUrl: m[13] || ''
});
}
}
if (gdpSheet && gdpSheet.getLastRow() > 1) {
var gData = gdpSheet.getDataRange().getValues();
for (var k = 1; k < gData.length; k++) {
var g = gData[k];
try {
if (g[10]) gdpRecords.push(JSON.parse(g[10]));
} catch(err) {}
}
}
return ContentService.createTextOutput(JSON.stringify({ status: 'success', trips: trips, maints: maints, gdpRecords: gdpRecords }))
.setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify({ status: 'ok' })).setMimeType(ContentService.MimeType.JSON);
}
function doPost(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var postData = {};
try { postData = JSON.parse(e.postData.contents); } catch(err) { postData = e.parameter || {}; }
var action = postData.action;
var d = postData.data;
if (action === 'addTrip') {
var tripSheet = ss.getSheetByName('Nhat_Trinh_Lai_Xe');
if (!tripSheet) {
tripSheet = ss.insertSheet('Nhat_Trinh_Lai_Xe');
tripSheet.appendRow([
"Mã Chuyến", "Ngày Chạy", "Lái Xe", "Biển Số", "Lộ Trình", "Khung Giờ",
"Tiền Ăn (₫)", "Vé BOT (₫)", "Bến Bãi (₫)", "Khách Sạn (₫)", "Bốc Xếp (₫)",
"Rửa Xe (₫)", "Vá Lốp/Sửa Xe (₫)", "Tiếp Khách (₫)", "Ghi Chú Tiếp Khách",
"Có Sự Cố", "Loại Sự Cố", "Chi Tiết Sự Cố", "Chi Phí Sự Cố (₫)", "Chi Khác (₫)",
"Tổng Tiền (₫)", "Thời Gian Nhập",
"GDP Vệ Sinh (BM-03)", "Nhiệt Độ Đi (°C)", "Nhiệt Độ Giao (°C)", "Số Hóa Đơn (BM-01)", "Khách Hàng", "Trạng Thái Liên Xanh", "Link Ảnh Chứng Từ (Google Drive)"
]);
tripSheet.getRange(1, 1, 1, 29).setFontWeight("bold").setBackground("#dbeafe");
}
var driveUrl = '';
if (d.gdpDocScanBase64) {
driveUrl = saveScanImageToDrive(d.gdpDocScanBase64, 'ChungTu_' + d.id + '_' + (d.gdpInvoiceNo || 'GDP'));
}
var total = (d.meals||0)+(d.bot||0)+(d.parking||0)+(d.hotel||0)+(d.loading||0)+(d.wash||0)+(d.repair||0)+(d.guest||0)+(d.other||0)+(d.incidentCost||0);
tripSheet.appendRow([
d.id, d.date, d.driver, d.plate, d.route, d.time,
d.meals, d.bot, d.parking, d.hotel, d.loading,
d.wash, d.repair, d.guest, d.guestNote || '',
d.hasIncident ? 'CÓ' : 'KHÔNG', d.incidentType || '', d.incidentDesc || '', d.incidentCost || 0, d.other || 0,
total, new Date(),
d.gdpCleanStatus || 'ĐẠT', d.gdpTempStart || 4.5, d.gdpTempEnd || 5.6, d.gdpInvoiceNo || '', d.gdpCustomer || '', d.gdpGreenSlip || '', driveUrl
]);
return ContentService.createTextOutput(JSON.stringify({ status: 'success', driveUrl: driveUrl })).setMimeType(ContentService.MimeType.JSON);
}
if (action === 'addMaintenance') {
var maintSheet = ss.getSheetByName('So_Bao_Duong_Xe');
if (!maintSheet) {
maintSheet = ss.insertSheet('So_Bao_Duong_Xe');
maintSheet.appendRow([
"Mã Phiếu", "Ngày Bảo Dưỡng", "Biển Số", "Lái Xe", "Số KM Hiện Tại (ODO)",
"Hạn KM Tới", "Phân Loại", "Nội Dung Bảo Dưỡng", "Gara/Xưởng", "Số Hóa Đơn/GCN",
"Chi Phí (₫)", "Ghi Chú", "Thời Gian Nhập", "Link Ảnh Hóa Đơn/GCN (Google Drive)"
]);
maintSheet.getRange(1, 1, 1, 14).setFontWeight("bold").setBackground("#fef3c7");
}
var mDriveUrl = '';
if (d.gdpDocScanBase64) {
mDriveUrl = saveScanImageToDrive(d.gdpDocScanBase64, 'BaoDuong_' + d.id + '_' + d.plate);
}
maintSheet.appendRow([
d.id, d.date, d.plate, d.driver, d.currentKm,
d.nextKm, d.category, d.items, d.garage, d.invoiceNo || '',
d.cost, d.note || '', new Date(), mDriveUrl
]);
return ContentService.createTextOutput(JSON.stringify({ status: 'success', driveUrl: mDriveUrl })).setMimeType(ContentService.MimeType.JSON);
}
if (action === 'saveGdpRecord') {
var gdpSheet = ss.getSheetByName('Ho_So_GDP_5_Quy_Trinh');
if (!gdpSheet) {
gdpSheet = ss.insertSheet('Ho_So_GDP_5_Quy_Trinh');
gdpSheet.appendRow([
"Mã Hồ Sơ GDP", "Mã Quy Trình", "Mã Chuyến/Liên Kết", "Biển Số Xe", "Ngày Thực Hiện",
"Bước Duyệt (1-3)", "1. Lái Xe Thực Hiện", "2. Nhân Viên Điều Vận", "3. TBP Điều Vận",
"Link Ảnh Scan (Google Drive)", "JSON_Payload_Dong_Bo", "Cập Nhật Lúc"
]);
gdpSheet.getRange(1, 1, 1, 12).setFontWeight("bold").setBackground("#d1fae5");
}
var gDriveUrl = d.driveUrl || '';
if (d.newUploadBase64) {
gDriveUrl = saveScanImageToDrive(d.newUploadBase64, 'GDP_' + d.id + '_' + d.tripId);
d.driveUrl = gDriveUrl;
delete d.newUploadBase64;
}
var slimCopy = JSON.parse(JSON.stringify(d));
if (slimCopy.uploadedImg) delete slimCopy.uploadedImg;
gdpSheet.appendRow([
d.id, d.processCode, d.tripId, d.vehicle, d.date,
d.step + '/3',
(d.signatures && d.signatures.doer && d.signatures.doer.name) || '',
(d.signatures && d.signatures.checker && d.signatures.checker.name) || '',
(d.signatures && d.signatures.qa && d.signatures.qa.name) || '',
gDriveUrl, JSON.stringify(slimCopy), new Date()
]);
return ContentService.createTextOutput(JSON.stringify({ status: 'success', driveUrl: gDriveUrl })).setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify({ status: 'ok' })).setMimeType(ContentService.MimeType.JSON);
}
Hệ Thống Điều Vận & Quản Trị Đội Xe Nội Bộ
Theo dõi nhật trình xe chạy hàng ngày, tự động tính định mức tiền ăn theo ca, quản lý lịch bảo dưỡng định kỳ theo số KM (ODO) và đối soát cước vận tải phân phối toàn quốc.
Nhật Trình & Lái Xe
Ghi nhận các chuyến đi trong ngày hoặc nhiều ngày, tiền ăn theo ca, vé cầu đường BOT và chi phí phát sinh dọc tuyến.
Bảo Dưỡng Kỹ Thuật ODO
Theo dõi mốc công tơ mét, nhắc lịch thay dầu nhớt 5.000 km, hạn đăng kiểm và hóa đơn gara sửa chữa xe ở nhà.
Hồ Sơ Chứng Từ GDP
Quản lý 5 biểu mẫu GDP (Giao nhận hóa đơn, nhiệt độ 2-8°C, vệ sinh thùng xe, sự cố), liên xanh và chữ ký số điện tử.
Đơn Hàng SAP & 3PL
Tự động gom đơn SAP theo chuyến xe nội bộ và áp biểu giá hợp đồng đối soát cước các nhà xe thuê ngoài (Phương Trang, Viettel Post...).
Quyết Toán & In A4
Tự động kết xuất bảng tổng hợp thanh toán chi phí cuối tháng chuẩn khổ in A4, kèm đối chiếu tạm ứng để trình ký lãnh đạo.
🚛 Danh Sách Phương Tiện Đội Xe Vinphaco
Trạng thái vận hành, mốc ODO và tài xế phụ trách từng xe