PT Seva Food Order Backend Script✅
// 🔥 PT SEVA MASTER ENGINE v5.26 (Pro Backend with UPI/COD & Zomato-Level Metrics)
// 🎯 RULES: 7km Order Limit | 4km Driver Allocation | 20km Registration Limit
// 📝 Sheet ID: 1NjqHSgvmcD6Oo7Xd6VUcIRLG-5P57x6WQkoTCveUeqg
const SHEET_ID = "1NjqHSgvmcD6Oo7Xd6VUcIRLG-5P57x6WQkoTCveUeqg";
const MAX_DISTANCE_KM = 7; // 🔥 Customer Order Limit
const DRIVER_ALLOC_KM = 4; // 🔥 Driver Allocation Limit
const REGISTRATION_KM = 20; // 🔥 Driver Registration Limit
const BASE_FEE = 20;
const EXTRA_PER_KM = 5;
function doGet(e) { return handleRequest(e); }
function doPost(e) { return handleRequest(e); }
function handleRequest(e) {
var lock = LockService.getScriptLock();
if (!lock.tryLock(10000)) return response({status: "FAIL", msg: "Server Busy"});
try {
if (!e.parameter.action) return response({status: "FAIL", msg: "No Action"});
var op = e.parameter.action;
var ss = SpreadsheetApp.openById(SHEET_ID);
// =================================================
// 🍔 1. COMMON & FOODIE LOGIC
// =================================================
if (op == "getCities") {
var sheet = ss.getSheetByName("Restaurants");
var data = sheet.getDataRange().getValues();
var cities = [];
for (var i = 1; i < data.length; i++) {
var city = String(data[i][8]).trim();
var status = String(data[i][10]).toUpperCase();
if (city && status == "ACTIVE" && cities.indexOf(city) == -1) cities.push(city);
}
return response({status: "SUCCESS", cities: cities});
}
if (op == "getNearestCity") {
var uLat = Number(e.parameter.lat);
var uLng = Number(e.parameter.lng);
var sheet = ss.getSheetByName("Restaurants");
var data = sheet.getDataRange().getValues();
var closestCity = "";
var minDst = REGISTRATION_KM;
for (var i = 1; i < data.length; i++) {
var status = String(data[i][10]).toUpperCase();
if (status == "ACTIVE") {
var rLat = data[i][12];
var rLng = data[i][13];
var rCity = String(data[i][8]).trim();
if(rLat && rLng) {
var dst = getDist(uLat, uLng, rLat, rLng);
if (dst < minDst) { minDst = dst; closestCity = rCity; }
}
}
}
if (closestCity !== "") return response({status: "SUCCESS", city: closestCity});
else return response({status: "FAIL", msg: "No active city in 20km range"});
}
if (op == "getRestaurants") {
var sheet = ss.getSheetByName("Restaurants");
var data = sheet.getDataRange().getValues();
var reqCity = e.parameter.city;
var list = [];
for (var i = 1; i < data.length; i++) {
var status = String(data[i][10]).toUpperCase();
var rCity = String(data[i][8]).trim();
var storeToggle = String(data[i][14]).toUpperCase();
if (status == "ACTIVE" && storeToggle == "OPEN") {
if (!reqCity || (reqCity && rCity.toLowerCase() == String(reqCity).toLowerCase())) {
var realID = String(data[i][1]);
var shopName = String(data[i][3]).replace(/\s+/g, '');
var maskedID = shopName + "-" + realID.slice(-4);
list.push({
id: realID, display_id: maskedID, name: data[i][3], cuisine: data[i][4],
image: data[i][11], city: rCity, lat: data[i][12], lng: data[i][13], rating: data[i][16] || "4.0"
});
}
}
}
return response({status: "SUCCESS", data: list});
}
if (op == "getMenu") {
var sheet = ss.getSheetByName("Menu");
var data = sheet.getDataRange().getValues();
var list = [];
for(var i=1; i MAX_DISTANCE_KM) return response({status: "FAIL", msg: "Too far ("+dist.toFixed(1)+"km). Max "+MAX_DISTANCE_KM+"km allowed."});
if (dist > 3) deliveryFee = BASE_FEE + Math.round((dist - 3) * EXTRA_PER_KM);
if(deliveryFee > 100) deliveryFee = 100;
}
var grandTotal = itemTotal + deliveryFee;
var payMode = e.parameter.payMode || "COD";
var uData = uSheet.getDataRange().getValues();
var userRow = -1;
for(var i=1; i=1; i--) {
if(String(data[i][2]) == String(phone) || String(data[i][1]) == String(e.parameter.oid)) {
list.push({oid: data[i][1], status: data[i][13], driver: data[i][6] || "Assigning..."});
}
}
return response({status:"SUCCESS", data: list});
}
// =================================================
// 🏪 2. RESTAURANT LOGIC
// =================================================
if (op == "registerRestaurant") {
var sheet = ss.getSheetByName("Restaurants");
var data = sheet.getDataRange().getValues();
var newPhone = String(e.parameter.phone).trim();
for(var i=1; i=1; i--) {
if(String(oData[i][3]) == String(uid) || String(oData[i][4]) == String(uid)) {
var status = oData[i][13];
var amt = Number(oData[i][8]);
var driverID = String(oData[i][6]);
var orderTime = new Date(oData[i][0]);
var isToday = (orderTime.getDate() === now.getDate() && orderTime.getMonth() === now.getMonth() && orderTime.getFullYear() === now.getFullYear());
if(status == "DELIVERED" && isToday) sale += amt;
if(status == "REJECTED" && (now - orderTime) > 30 * 60 * 1000) continue;
if( (status !== "DELIVERED" && status !== "REJECTED") || (status === "REJECTED" && (now - orderTime) <= 30 * 60 * 1000) || (status === "DELIVERED" && isToday) ) {
var msgTxt = "Finding Driver...";
var drvName = "";
if(driverID) {
for(var d=1; d= 1; a--) {
if(String(attData[a][1]) == String(e.parameter.userid) && attData[a][4] == "") {
attSheet.getRange(a+1, 5).setValue(new Date().toLocaleTimeString());
break;
}
}
break;
}
}
return response({status:"SUCCESS"});
}
if (op == "getDeliveryTasks") {
var oSheet = ss.getSheetByName("Orders");
var dSheet = ss.getSheetByName("DeliveryPartners");
var rSheet = ss.getSheetByName("Restaurants");
var oData = oSheet.getDataRange().getValues();
var dData = dSheet.getDataRange().getValues();
var rData = rSheet.getDataRange().getValues();
var restMap = {};
for(var r=1; r=1; i--) {
var orderDate = new Date(oData[i][0]);
var isToday = (orderDate.getDate() === todayDate.getDate() && orderDate.getMonth() === todayDate.getMonth() && orderDate.getFullYear() === todayDate.getFullYear());
var st = oData[i][13], drv = oData[i][6], city = oData[i][11], payMode = String(oData[i][12]).toUpperCase();
var restMobile = oData[i][3], restId = oData[i][4], restName = oData[i][5] || "Restaurant";
var addr = oData[i][10], totalAmt = oData[i][8], deliveryFee = oData[i][9], lat = oData[i][16], lng = oData[i][17];
if(String(drv) == String(drvRealID)) {
if(st != "DELIVERED") {
myOrders.push({ oid: oData[i][1], rest: restName, restPhone: restMobile, addr: addr, amt: totalAmt, fee: deliveryFee, status: st, lat: lat, lng: lng, payMode: payMode });
} else if (st == "DELIVERED" && isToday) {
todayEarn += Number(deliveryFee);
myOrders.push({ oid: oData[i][1], rest: restName, restPhone: restMobile, addr: addr, amt: deliveryFee, status: st, lat: lat, lng: lng, payMode: payMode });
}
}
else if(st == "READY" && drv == "") {
var rLoc = restMap[String(restId)];
if (dLat && dLng && rLoc) {
var distToRest = getDist(dLat, dLng, rLoc.lat, rLoc.lng);
if (distToRest <= DRIVER_ALLOC_KM) {
newOrders.push({ oid: oData[i][1], rest: restName, restPhone: restMobile, addr: addr, amt: totalAmt, fee: deliveryFee, status: st, lat: lat, lng: lng, payMode: payMode });
}
}
else if(String(city).trim().toLowerCase() == String(drvCity).trim().toLowerCase()) {
newOrders.push({ oid: oData[i][1], rest: restName, restPhone: restMobile, addr: addr, amt: totalAmt, fee: deliveryFee, status: st, lat: lat, lng: lng, payMode: payMode });
}
}
}
return response({status:"SUCCESS", new:newOrders, my:myOrders, stats:{wallet:wallet, cash:cash, todayEarn:todayEarn}});
}
if (op == "acceptOrder") {
var s = ss.getSheetByName("Orders"); var d = s.getDataRange().getValues();
for(var i=1; i= BATCH_SIZE) break;
var accStatus = String(data[i][11] || data[i][10]).toUpperCase();
var email = String(data[i][config.emailCol - 1]).trim();
var billStatus = String(data[i][config.statusCol - 1]).trim();
var entityId = String(data[i][config.idCol - 1]).trim();
if (accStatus === "ACTIVE" && email !== "" && billStatus !== sentMark && entityId !== "") {
billTemplate.getRange("B2").setValue(entityId);
billTemplate.getRange("B3").setValue(period.start);
billTemplate.getRange("B4").setValue(period.end);
SpreadsheetApp.flush();
var pdfBlob = generatePDF(ss, billTemplate, config.prefix + "_" + entityId);
var subject = "PT Seva Weekly Payout - " + entityId + " (" + period.label + ")";
var body = "Namaste,\n\nApka weekly payout bill (" + period.start.toLocaleDateString() + " se " + period.end.toLocaleDateString() + ") attach kar diya gaya hai.\n\nTeam PT Seva";
try {
GmailApp.sendEmail(email, subject, body, { from: SENDER_EMAIL, attachments: [pdfBlob], name: "PT Seva Payouts" });
sheet.getRange(i + 1, config.statusCol).setValue(sentMark);
processedCount++;
} catch (e) { sheet.getRange(i + 1, config.statusCol).setValue("ERR: " + e.message); }
}
}
}
function getPayoutPeriod(today) {
var d = today.getDate(); var start = new Date(today); var end = new Date(today); var label = d + "-" + (today.getMonth() + 1);
if (d === 8) { start.setDate(1); end.setDate(7); }
else if (d === 15) { start.setDate(8); end.setDate(14); }
else if (d === 22) { start.setDate(15); end.setDate(21); }
else if (d === 1) { start = new Date(today.getFullYear(), today.getMonth() - 1, 22); end = new Date(today.getFullYear(), today.getMonth(), 0); }
return { start: start, end: end, label: label };
}
function generatePDF(ss, sheet, fileName) {
var url = "https://docs.google.com/spreadsheets/d/" + ss.getId() + "/export?exportFormat=pdf&format=pdf&gid=" + sheet.getSheetId() + "&size=A4&portrait=true&fitw=true&gridlines=false";
var token = ScriptApp.getOAuthToken();
var response = UrlFetchApp.fetch(url, { headers: { 'Authorization': 'Bearer ' + token } });
return response.getBlob().setName(fileName + ".pdf");
}
function setupFinalTriggers() {
var triggers = ScriptApp.getProjectTriggers();
triggers.forEach(t => { if(t.getHandlerFunction() === "processAllPayouts") ScriptApp.deleteTrigger(t); });
ScriptApp.newTrigger("processAllPayouts").timeBased().everyHours(1).create();
}
// 🔥 FIXED: Archive Movement with full 26 Columns + 1 Archive Date
function runArchive() {
var ss = SpreadsheetApp.openById(SHEET_ID);
var oSheet = ss.getSheetByName("Orders");
var aSheet = ss.getSheetByName("Archive");
var oData = oSheet.getDataRange().getValues();
var aData = aSheet.getDataRange().getValues();
var toDeleteOrders = [];
var toDeleteArchive = [];
var now = new Date();
for (var i = 1; i < oData.length; i++) {
var orderDate = new Date(oData[i][0]);
var isToday = (orderDate.getDate() === now.getDate() && orderDate.getMonth() === now.getMonth() && orderDate.getFullYear() === now.getFullYear());
if ((oData[i][13] === "DELIVERED" || oData[i][13] === "REJECTED") && !isToday) {
var archivedRow = [
oData[i][0], oData[i][1], oData[i][2], oData[i][3], oData[i][4], oData[i][5],
oData[i][6], oData[i][7], oData[i][8], oData[i][9], oData[i][10], oData[i][11],
oData[i][12], oData[i][13], oData[i][14], oData[i][15], oData[i][16], oData[i][17], oData[i][18],
oData[i][19] || "", oData[i][20] || 0, oData[i][21] || 0, oData[i][22] || "", oData[i][23] || "", oData[i][24] || "", oData[i][25] || "", now
];
aSheet.appendRow(archivedRow);
toDeleteOrders.push(i+1);
}
}
for (var j = toDeleteOrders.length - 1; j >= 0; j--) oSheet.deleteRow(toDeleteOrders[j]);
for (var k = 1; k < aData.length; k++) {
var archDate = new Date(aData[k][26]);
if ((now - archDate) > 30 * 24 * 60 * 60 * 1000) {
toDeleteArchive.push(k+1);
}
}
for (var m = toDeleteArchive.length - 1; m >= 0; m--) aSheet.deleteRow(toDeleteArchive[m]);
}