/** * The delivery charge shown and sent (as `finalprice`) when ops create orders — * the single order form and bulk upload. The backend stores the figure as * given, so this IS the price the client is billed. * * What it replaces, in both pages: * - A rate card value of 0 was overwritten by a made-up default * (`Number(x) || 40`, `|| 10`, `|| 2`): a card set to "₹15 per km, no base * fee, no free km" was charged ₹40 plus ₹15 for each km after the first 2. * - A client without a rate card was priced off ANOTHER client's card * (`list[0]`), or a hard-coded ₹40 + ₹10/km after 2 km. * - The first card found for the client was used even if Inactive, and the * Pricing page's "priority" was ignored. */ /** * Doormile's standard rate, for a client with no rate card: ₹15 per km, at * least 1 km billed. Without the minimum a trip under 1 km (or a distance the * router rounded to 0) priced at ₹0, and the backend treats a 0 `finalprice` as * "not given" and re-prices the booking from another client's card. */ export const STANDARD_RATE = Object.freeze({ baseprice: 0, basedistance: 0, priceperkm: 15, minimumkm: 1, isStandard: true, }); /** A rate value as a number; an empty or invalid value is 0, never a default. */ const amount = (value) => { const n = Number(value); return Number.isFinite(n) && n >= 0 ? n : 0; }; const isActive = (rule) => !rule?.status || String(rule.status).toLowerCase() === 'active'; /** Inside the card's effectivefrom / effectiveto window (either may be unset). */ const inEffect = (rule, now = Date.now()) => { const from = rule?.effectivefrom ? Date.parse(rule.effectivefrom) : NaN; const to = rule?.effectiveto ? Date.parse(rule.effectiveto) : NaN; return (Number.isNaN(from) || from <= now) && (Number.isNaN(to) || to >= now); }; /** The Pricing page's priority: lower wins; unset is 1, and 0 stays 0. */ const priorityOf = (rule) => { const n = Number(rule?.priority); return rule?.priority == null || rule.priority === '' || !Number.isFinite(n) ? 1 : n; }; /** * The rate card for a client: its Active rules, the lowest priority number * first (the Pricing page's "lower wins"), newest on a tie. A Doormile-wide * card (no client) applies next; failing both, the standard rate. Never * another client's card. */ export function pickRateCard(pricingList, tenantId, now = Date.now()) { const rules = (Array.isArray(pricingList) ? pricingList : []).filter((r) => isActive(r) && inEffect(r, now)); const byPriority = (a, b) => priorityOf(a) - priorityOf(b) || amount(b.pricingid) - amount(a.pricingid); if (tenantId != null && String(tenantId) !== '' && String(tenantId) !== '0') { const own = rules.filter((r) => String(r.tenantid) === String(tenantId)).sort(byPriority); if (own.length) return own[0]; } const doormileWide = rules.filter((r) => r.isdefault || !r.tenantid || String(r.tenantid) === '0').sort(byPriority); return doormileWide[0] || STANDARD_RATE; } /** * The charge for a trip of `distanceKm` on `rate`: the base price covers the * first `basedistance` km, every km after that is `priceperkm`. Rounded to * paise. */ export function chargeForDistance(distanceKm, rate = STANDARD_RATE) { const km = Math.max(amount(distanceKm), amount(rate?.minimumkm)); const base = amount(rate?.baseprice); const freeKm = amount(rate?.basedistance); const perKm = amount(rate?.priceperkm); const charge = km <= freeKm ? base : base + (km - freeKm) * perKm; return Math.round(charge * 100) / 100; } /** "₹15 / km" style labels for the rate card panel. */ export function describeRate(rate) { const base = amount(rate?.baseprice); const freeKm = amount(rate?.basedistance); return { base: base > 0 ? `₹${base}${freeKm > 0 ? ` (first ${freeKm} km)` : ''}` : 'No base fee', perKm: `₹${amount(rate?.priceperkm)} / km${amount(rate?.minimumkm) > 0 ? ` (min ${amount(rate.minimumkm)} km)` : ''}`, source: rate?.isStandard ? 'Standard rate (no client rate card)' : 'Client rate card', }; }