93 lines
4.0 KiB
JavaScript
93 lines
4.0 KiB
JavaScript
/**
|
|
* 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',
|
|
};
|
|
}
|