212 lines
7.2 KiB
JavaScript
212 lines
7.2 KiB
JavaScript
import {
|
|
assignmentSummary,
|
|
editNeedsReconcile,
|
|
extractRiders,
|
|
extractUnassigned,
|
|
flattenRiders,
|
|
moveOrderInPreviewData,
|
|
withUnassignedZones
|
|
} from '@/lib/dispatchPreview';
|
|
|
|
/**
|
|
* One real failed run, start to finish.
|
|
*
|
|
* LIVE_RUN is the verbatim reply from
|
|
* `routes.workolik.com/api/v1/optimization/riderassign?hypertuning_params=balanced`
|
|
* on 2026-09-22 for booking DM-107871 — the run in the screenshots. It is
|
|
* kept whole rather than trimmed: the defect these guard against was an
|
|
* assumption about the response shape, and a fixture written from the same
|
|
* assumption proves nothing.
|
|
*
|
|
* What it records: the solver processed the order correctly (zone resolved,
|
|
* profit computed) and had no rider to give it to — `active_riders_pool: 0`,
|
|
* before any filtering, with nothing excluded.
|
|
*/
|
|
const LIVE_RUN = {
|
|
code: 200,
|
|
zone_summary: [
|
|
{
|
|
zone_name: 'North West',
|
|
total_orders: 1,
|
|
assigned_orders: 0,
|
|
unassigned_orders_count: 1,
|
|
active_riders_count: 0,
|
|
total_delivery_kms: 0.0,
|
|
total_profit: 56.0
|
|
}
|
|
],
|
|
zones: [
|
|
{
|
|
zone_name: 'North West',
|
|
total_orders: 1,
|
|
active_riders_count: 0,
|
|
assigned_orders: 0,
|
|
unassigned_orders_count: 1,
|
|
total_delivery_kms: 0.0,
|
|
total_profit: 56.0,
|
|
riders: [],
|
|
unassigned_orders: [
|
|
{
|
|
bookingid: 1737,
|
|
bookingno: 'DM-107871',
|
|
tenantid: 1,
|
|
pickupaddress: '124 Gandhipuram Main Rd',
|
|
pickuplatitude: 11.0168,
|
|
pickuplongitude: 76.9558,
|
|
deliveryaddress: 'NO . 26, JMV GARDEN, Subramaniapalayam, Coimbatore, Tamil Nadu 641029',
|
|
deliverylatitude: 11.0590674,
|
|
deliverylongitude: 76.9298476,
|
|
status: 'Pending_Pickup',
|
|
assignedmileruserid: null,
|
|
consignmentid: null,
|
|
deliveryid: 0,
|
|
deliverydate: '2026-09-23 17:56:04',
|
|
assigntime: '2026-09-22 13:54:39',
|
|
orderstatus: 'pending',
|
|
orderamount: 56,
|
|
droplat: '11.0590674',
|
|
droplon: '76.9298476',
|
|
pickuplat: 11.0168,
|
|
pickuplon: 76.9558,
|
|
deliverycharges: 56,
|
|
pickuplocation: '124 Gandhipuram Main Rd',
|
|
deliverylocation: 'NO . 26, JMV GARDEN, Subramaniapalayam, Coimbatore, Tamil Nadu 641029',
|
|
hubid: 2,
|
|
deliverylat: '11.0590674',
|
|
deliverylong: '76.9298476',
|
|
unassigned_reason: 'No riders found (check partner online status).',
|
|
zone_name: 'North West',
|
|
rider_charge: 0.0,
|
|
profit: 56.0,
|
|
profit_class: 'HighMargin'
|
|
}
|
|
]
|
|
}
|
|
],
|
|
details: [],
|
|
message: 'Success',
|
|
status: true,
|
|
meta: {
|
|
total_orders: 1,
|
|
utilized_riders: 0,
|
|
active_riders_pool: 0,
|
|
assigned_orders: 0,
|
|
unassigned_orders: 1,
|
|
total_profit: 56.0,
|
|
unassigned_details: [{ orderid: null, reason: 'No riders found (check partner online status).' }],
|
|
absent_riders_excluded: [],
|
|
absent_riders_unknown: [],
|
|
solver_mode: '2phase_heuristic'
|
|
}
|
|
};
|
|
|
|
// The order id every surface keys this booking by. The refusal list carries
|
|
// no `orderid` of its own — `bookingno` is what it has.
|
|
const ORDER_ID = 'DM-107871';
|
|
|
|
describe('the run as the page reads it', () => {
|
|
it('reports the refusal instead of the 200', () => {
|
|
expect(assignmentSummary(LIVE_RUN)).toMatchObject({
|
|
assigned: 0,
|
|
unassigned: 1,
|
|
total: 1,
|
|
riders: 0,
|
|
failed: true
|
|
});
|
|
});
|
|
|
|
it('has nothing to commit before a miler is picked', () => {
|
|
// `details: []` — so the Assign Orders fallback is empty too, and the
|
|
// page must not offer to dispatch anything.
|
|
expect(flattenRiders(extractRiders(LIVE_RUN))).toEqual([]);
|
|
expect(LIVE_RUN.details).toEqual([]);
|
|
});
|
|
|
|
it('puts the order on the board with its coordinates, so it can be pinned', () => {
|
|
const order = withUnassignedZones(LIVE_RUN).zones[0].riders[0].orders[0];
|
|
expect(order.orderid).toBe(ORDER_ID);
|
|
expect(Number(order.droplat)).toBeCloseTo(11.0590674);
|
|
expect(Number(order.droplon)).toBeCloseTo(76.9298476);
|
|
});
|
|
});
|
|
|
|
describe('recovering the run by hand', () => {
|
|
// The operator opens the zone, presses "change rider" on DM-107871 and
|
|
// picks a real Doormile miler. Everything below has to follow from that.
|
|
const recovered = () =>
|
|
moveOrderInPreviewData(LIVE_RUN, { orderId: ORDER_ID, newRiderId: 12, newRiderName: 'Vijay Bangalore Rider' });
|
|
|
|
it('finds the order by its bookingno, which is the only id it has', () => {
|
|
const rider = extractRiders(recovered())[0];
|
|
expect(rider).toMatchObject({ rider_id: 12, rider_name: 'Vijay Bangalore Rider' });
|
|
expect(rider.orders).toHaveLength(1);
|
|
});
|
|
|
|
it('clears the refusal, so the banner and the Reconcile list empty out', () => {
|
|
expect(extractUnassigned(recovered())).toEqual([]);
|
|
expect(assignmentSummary(recovered())).toMatchObject({ assigned: 1, unassigned: 0, failed: false });
|
|
});
|
|
|
|
it('produces a commit payload carrying the real miler id', () => {
|
|
const [delivery] = flattenRiders(extractRiders(recovered()));
|
|
expect(delivery).toMatchObject({ orderid: ORDER_ID, bookingid: 1737, rider_id: 12, userid: 12 });
|
|
});
|
|
|
|
it('keeps the booking fields the commit needs', () => {
|
|
const [delivery] = flattenRiders(extractRiders(recovered()));
|
|
// `POST /admin/bookings/:id/assign-miler` is keyed on the booking, so
|
|
// losing bookingid here would make the order uncommittable.
|
|
expect(delivery.bookingid).toBe(1737);
|
|
expect(delivery.deliverycharges).toBe(56);
|
|
});
|
|
});
|
|
|
|
describe('whether the recovery still has to go through Reconcile', () => {
|
|
const assignTo = (preview, newRiderId) => ({
|
|
before: preview,
|
|
after: moveOrderInPreviewData(preview, { orderId: ORDER_ID, newRiderId, newRiderName: 'Vijay' })
|
|
});
|
|
|
|
it('does not, for a refused order given a miler with nothing else on', () => {
|
|
// The dead end this exists to break: reconcile-steps is the same service
|
|
// that had no riders, so gating Assign Orders on it would strand the
|
|
// order permanently.
|
|
const { before, after } = assignTo(LIVE_RUN, 12);
|
|
expect(editNeedsReconcile(before, after, { orderId: ORDER_ID, newRiderId: 12 })).toBe(false);
|
|
});
|
|
|
|
it('does, when the refused order is dropped into an existing route', () => {
|
|
const withPlan = {
|
|
...LIVE_RUN,
|
|
zones: [
|
|
{
|
|
...LIVE_RUN.zones[0],
|
|
riders: [{ rider_id: 12, rider_name: 'Vijay', orders: [{ orderid: 'DM-100001' }, { orderid: 'DM-100002' }] }]
|
|
}
|
|
]
|
|
};
|
|
const { before, after } = assignTo(withPlan, 12);
|
|
expect(editNeedsReconcile(before, after, { orderId: ORDER_ID, newRiderId: 12 })).toBe(true);
|
|
});
|
|
|
|
it('does, for an ordinary rider-to-rider move inside the plan', () => {
|
|
const planned = {
|
|
zones: [
|
|
{
|
|
zone_name: 'North West',
|
|
riders: [
|
|
{ rider_id: 7, rider_name: 'Rajan', orders: [{ orderid: 'DM-100001' }] },
|
|
{ rider_id: 12, rider_name: 'Vijay', orders: [{ orderid: 'DM-100002' }] }
|
|
]
|
|
}
|
|
]
|
|
};
|
|
const after = moveOrderInPreviewData(planned, {
|
|
orderId: 'DM-100001',
|
|
newRiderId: 12,
|
|
newRiderName: 'Vijay'
|
|
});
|
|
expect(editNeedsReconcile(planned, after, { orderId: 'DM-100001', newRiderId: 12 })).toBe(true);
|
|
});
|
|
});
|