216 lines
9.3 KiB
JavaScript
216 lines
9.3 KiB
JavaScript
/**
|
|
* Build the logistics bulk-upload test sheet.
|
|
*
|
|
* node scripts/makeLogisticsTestSheet.cjs
|
|
*
|
|
* The tenant's own export (tests/fixtures/jayanthi-kitchen-route.xls) is 13
|
|
* lunch drops inside Coimbatore. Every one of them is hyperlocal, so uploading
|
|
* it exercises none of the base-handover path — no parcel in that file is ever
|
|
* routed through a base, and `next_action` never leaves `deliver`.
|
|
*
|
|
* This sheet keeps that file's exact column titles, so it travels through the
|
|
* same header map and the same page code, and changes only the one thing that
|
|
* decides routing: WHERE THE PARCEL IS GOING.
|
|
*
|
|
* The rule it is built against (backend, isHyperlocalBooking):
|
|
*
|
|
* • both pincodes present → same first 3 digits is hyperlocal, different is
|
|
* routed through a base. The prefix decides
|
|
* outright; distance does not get a vote.
|
|
* • a pincode missing → fall back to straight-line distance, ≤30 km is
|
|
* hyperlocal.
|
|
* • neither resolvable → routed through a base, which is the safe answer.
|
|
*
|
|
* Two columns are added that the tenant's file does not carry — Receiver
|
|
* Pincode and Receiver City. Both are already in SHEET_HEADER_MAP
|
|
* (receiverpincode → postcode, receivercity → city), so this is not a new
|
|
* format: it is the same format with columns the map already knows filled in.
|
|
* Without a pincode every row would land on the distance fallback, and the
|
|
* prefix rule — the one that actually decides intercity — would never run.
|
|
*/
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const XLSX = require('xlsx');
|
|
|
|
const SENDER = {
|
|
name: 'Jayanthi',
|
|
phone: 7708733769,
|
|
address: 'Edayarpalayam, Coimbatore, Tamil Nadu 641025'
|
|
};
|
|
|
|
const PICKUP_DATE = 'Sep 02, 2026';
|
|
|
|
/**
|
|
* expect: what the backend should decide for the row, and why. Not a column —
|
|
* it is carried here so the fixture and the test assertions cannot drift, and
|
|
* it is written into a "Notes" column on the sheet so an operator running this
|
|
* by hand can see what they are meant to be looking at.
|
|
*/
|
|
const ROWS = [
|
|
// ---- Customer → Base. Different postal area, so the rider collects and
|
|
// carries it to a base instead of to the receiver. This is the whole
|
|
// point of the file.
|
|
{
|
|
name: 'Suresh Kumar', phone: 9840112233, alt: 9840112234,
|
|
address: 'Flat 3B, 21 NSC Bose Road, Parrys, Chennai', city: 'Chennai', pincode: '600001',
|
|
lat: 13.091, lng: 80.285, item: 'Documents envelope', qty: 1, cash: 0,
|
|
expect: 'base handover — Coimbatore 641 to Chennai 600'
|
|
},
|
|
{
|
|
name: 'Priya Raghavan', phone: 9840223344, alt: 9840223345,
|
|
address: '14 Cheran Street, Raja Annamalai Puram, Chennai', city: 'Chennai', pincode: '600028',
|
|
lat: 13.018, lng: 80.256, item: 'Spare parts carton', qty: 2, cash: 0,
|
|
expect: 'base handover — Coimbatore 641 to Chennai 600'
|
|
},
|
|
{
|
|
name: 'Anil Reddy', phone: 9848334455, alt: 9848334456,
|
|
address: 'Plot 42, Gachibowli, Hyderabad', city: 'Hyderabad', pincode: '500081',
|
|
lat: 17.44, lng: 78.3489, item: 'Textile samples', qty: 3, cash: 0,
|
|
expect: 'base handover — interstate, Coimbatore to Hyderabad'
|
|
},
|
|
{
|
|
name: 'Meera Krishnan', phone: 9848445566, alt: 9848445567,
|
|
address: 'Tower B, Financial District, Nanakramguda, Hyderabad', city: 'Hyderabad', pincode: '500032',
|
|
lat: 17.4156, lng: 78.3378, item: 'Legal documents', qty: 1, cash: 450,
|
|
expect: 'base handover with COD — cash must survive the base leg'
|
|
},
|
|
{
|
|
name: 'Rahul Menon', phone: 9880556677, alt: 9880556678,
|
|
address: '9 Brigade Road, MG Road, Bengaluru', city: 'Bengaluru', pincode: '560001',
|
|
lat: 12.975, lng: 77.606, item: 'Electronics accessories', qty: 2, cash: 0,
|
|
expect: 'base handover — interstate, Coimbatore to Bengaluru'
|
|
},
|
|
{
|
|
name: 'Divya Nair', phone: 9880667788, alt: 9880667789,
|
|
address: 'Prestige Shantiniketan, Whitefield, Bengaluru', city: 'Bengaluru', pincode: '560066',
|
|
lat: 12.9698, lng: 77.75, item: 'Gift hamper', qty: 1, cash: 1200,
|
|
expect: 'base handover with COD'
|
|
},
|
|
{
|
|
name: 'Karthik Subramani', phone: 9843778899, alt: 9843778890,
|
|
address: '5 East Veli Street, Madurai', city: 'Madurai', pincode: '625001',
|
|
lat: 9.9195, lng: 78.119, item: 'Temple prasadam box', qty: 1, cash: 0,
|
|
expect: 'base handover — same state, different postal area still routes via a base'
|
|
},
|
|
{
|
|
name: 'Lakshmi Devi', phone: 9842889900, alt: 9842889901,
|
|
address: '18 Cherry Road, Salem', city: 'Salem', pincode: '636001',
|
|
lat: 11.664, lng: 78.146, item: 'Handloom bundle', qty: 2, cash: 0,
|
|
expect: 'base handover — same state, different postal area'
|
|
},
|
|
|
|
// ---- Customer → Customer. Same postal area, so the collecting rider carries
|
|
// it straight to the receiver and no base is involved. These rows are
|
|
// the control group: if one of them routes to a base, the prefix rule
|
|
// has broken.
|
|
{
|
|
name: 'Ganesh Iyer', phone: 9994001122, alt: 9994001123,
|
|
address: '22 Avinashi Road, Peelamedu, Coimbatore', city: 'Coimbatore', pincode: '641004',
|
|
lat: 11.029, lng: 76.993, item: 'Lunch box', qty: 1, cash: 0,
|
|
expect: 'direct to customer — same 641 area'
|
|
},
|
|
{
|
|
name: 'Revathi Balaji', phone: 9994112233, alt: 9994112234,
|
|
address: '7 Cross Cut Road, Gandhipuram, Coimbatore', city: 'Coimbatore', pincode: '641012',
|
|
lat: 11.018, lng: 76.966, item: 'Lunch box', qty: 2, cash: 260,
|
|
expect: 'direct to customer with COD — same 641 area'
|
|
},
|
|
{
|
|
name: 'Vignesh Murugan', phone: 9994223344, alt: 9994223345,
|
|
address: '3/74 Nethaji Street, Edayarpalayam, Coimbatore', city: 'Coimbatore', pincode: '641025',
|
|
lat: 11.008, lng: 76.928, item: 'Lunch box', qty: 1, cash: 0,
|
|
expect: 'direct to customer — receiver shares the sender pincode'
|
|
},
|
|
{
|
|
name: 'Anitha Selvam', phone: 9994334455, alt: 9994334456,
|
|
address: '11 DB Road, Saibaba Colony, Coimbatore', city: 'Coimbatore', pincode: '641038',
|
|
lat: 11.023, lng: 76.945, item: 'Bakery order', qty: 3, cash: 0,
|
|
expect: 'direct to customer — same 641 area'
|
|
},
|
|
|
|
// ---- The fallback rule. A pincode is missing, so the decision falls to the
|
|
// straight-line distance instead. Both outcomes are represented, because
|
|
// a fallback that only ever answers one way is not being tested.
|
|
{
|
|
name: 'Mohan Das', phone: 9995445566, alt: 9995445567,
|
|
address: 'Near Hope College signal, Coimbatore', city: 'Coimbatore', pincode: '',
|
|
lat: 11.05, lng: 77.01, item: 'Stationery pack', qty: 1, cash: 0,
|
|
expect: 'direct to customer — no pincode, but ~8 km falls inside the 30 km fallback'
|
|
},
|
|
{
|
|
name: 'Sridhar Venkat', phone: 9995556677, alt: 9995556678,
|
|
address: 'Opposite Central Station, Chennai', city: 'Chennai', pincode: '',
|
|
lat: 13.0827, lng: 80.2707, item: 'Machine component', qty: 1, cash: 0,
|
|
expect: 'base handover — no pincode, but ~430 km is far outside the fallback'
|
|
},
|
|
{
|
|
name: 'Bhavani Shankar', phone: 9995667788, alt: 9995667789,
|
|
address: 'T Nagar, Chennai', city: 'Chennai', pincode: '64',
|
|
lat: 13.06, lng: 80.24, item: 'Fabric roll', qty: 1, cash: 0,
|
|
expect: 'base handover — a 2-digit pincode is unusable, so distance decides'
|
|
},
|
|
|
|
// ---- The case that proves the prefix rule outranks distance. Pollachi is
|
|
// ~40 km from Coimbatore — closer than plenty of runs the console treats
|
|
// as local — but 642 is a different postal area, so it goes via a base.
|
|
{
|
|
name: 'Ramesh Palanisamy', phone: 9995778899, alt: 9995778890,
|
|
address: '2 Mahalingapuram Road, Pollachi', city: 'Pollachi', pincode: '642001',
|
|
lat: 10.658, lng: 77.008, item: 'Agricultural samples', qty: 2, cash: 0,
|
|
expect: 'base handover — nearby, but 642 is a different area and the prefix decides'
|
|
}
|
|
];
|
|
|
|
const HEADERS = [
|
|
'Pickup Date (YYYY-MMM-DD )',
|
|
'Sender Name*',
|
|
'Sender Phone*',
|
|
'Sender Address*',
|
|
'Receiver Name*',
|
|
'Receiver Phone',
|
|
'Receiver Alternate Phone*',
|
|
'Receiver Full Address',
|
|
'Receiver City',
|
|
'Receiver Pincode',
|
|
'Receiver Latitude',
|
|
'Receiver Longitude',
|
|
'Item Description*',
|
|
'Quantity',
|
|
'Collect Cash',
|
|
'Expected Routing (not read by the app)'
|
|
];
|
|
|
|
const sheetRows = ROWS.map((r) => ({
|
|
'Pickup Date (YYYY-MMM-DD )': PICKUP_DATE,
|
|
'Sender Name*': SENDER.name,
|
|
'Sender Phone*': SENDER.phone,
|
|
'Sender Address*': SENDER.address,
|
|
'Receiver Name*': r.name,
|
|
'Receiver Phone': r.phone,
|
|
'Receiver Alternate Phone*': r.alt,
|
|
'Receiver Full Address': r.address,
|
|
'Receiver City': r.city,
|
|
'Receiver Pincode': r.pincode,
|
|
'Receiver Latitude': r.lat,
|
|
'Receiver Longitude': r.lng,
|
|
'Item Description*': r.item,
|
|
Quantity: r.qty,
|
|
'Collect Cash': r.cash,
|
|
'Expected Routing (not read by the app)': r.expect
|
|
}));
|
|
|
|
const ws = XLSX.utils.json_to_sheet(sheetRows, { header: HEADERS });
|
|
ws['!cols'] = HEADERS.map((h) => ({ wch: Math.min(46, Math.max(14, h.length + 2)) }));
|
|
|
|
const wb = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(wb, ws, 'Doormile_Logistics_Test');
|
|
|
|
const targets = process.argv.slice(2);
|
|
if (targets.length === 0) targets.push(path.join(__dirname, '..', 'tests', 'fixtures', 'doormile-logistics-test.xlsx'));
|
|
|
|
targets.forEach((out) => {
|
|
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
XLSX.writeFile(wb, out);
|
|
console.log(`wrote ${sheetRows.length} rows → ${out}`);
|
|
});
|