MOHAN 03574a4914 feat: product rules, driving-distance zones, and shipping date ranges
Closes remaining DS-parity gaps from the feature audit:

- ProductRule model (product/collection/vendor/type/tag scoping) with
  real server-side enforcement in hold-request.server.ts, plus shaped
  availability in availability-request.server.ts. Covers per-product
  prep time, cart-content-based slot blocking, and product-restricted
  locations in one mechanism. New /app/rules admin page (Growth+).
- Driving-distance delivery zones via Google's Distance Matrix API,
  cached like existing geocoding results.
- SHIPPING-only estimated arrival range (transitMinDays/transitMaxDays
  on SlotTemplate) — widget shows "Arrives Thu-Sat" instead of a
  meaningless ship-out time slot; carried through to the order
  metafield write-back.

Storefront widget and POS extension now send cart contents (vendor/
type from cart.js, product ids for Admin-API-resolved collection/tag
rules) to both availability and hold endpoints.

checkout-datetime remains excluded from this deploy pending Shopify's
Network Access approval (unrelated to this work) — re-add from
../checkout-datetime-disabled and redeploy once granted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 00:28:45 +05:30

114 lines
3.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { isZoneEligible, type ZoneLike } from "../../app/services/zones.server";
const TORONTO = { lat: 43.6532, lng: -79.3832 };
const KITCHENER = { lat: 43.4516, lng: -80.4925 }; // ~100km from Toronto
function postalZone(overrides: Partial<ZoneLike> = {}): ZoneLike {
return {
id: "zone_1",
locationId: "loc_1",
type: "postal",
postalCodes: ["M5V 3A8", "M4B 1B3"],
radiusKm: null,
minOrders: null,
active: true,
...overrides,
};
}
function radiusZone(overrides: Partial<ZoneLike> = {}): ZoneLike {
return {
id: "zone_2",
locationId: "loc_1",
type: "radius",
postalCodes: [],
radiusKm: 25,
minOrders: null,
active: true,
...overrides,
};
}
describe("isZoneEligible", () => {
it("an inactive zone is never eligible, even with a matching postal code", () => {
const zone = postalZone({ active: false });
expect(isZoneEligible(zone, null, { postalCode: "M5V 3A8" })).toBe(false);
});
describe("postal zones", () => {
it("matches a listed postal code", () => {
expect(isZoneEligible(postalZone(), null, { postalCode: "m5v3a8" })).toBe(true);
});
it("rejects an unlisted postal code", () => {
expect(isZoneEligible(postalZone(), null, { postalCode: "K1A 0A6" })).toBe(false);
});
it("is ineligible when no postal code is provided", () => {
expect(isZoneEligible(postalZone(), null, {})).toBe(false);
});
it("ignores location coordinates entirely (postal zones don't need them)", () => {
expect(isZoneEligible(postalZone(), TORONTO, { postalCode: "M5V 3A8" })).toBe(true);
});
});
describe("radius zones", () => {
it("is eligible within the radius", () => {
expect(isZoneEligible(radiusZone({ radiusKm: 200 }), TORONTO, { coordinates: KITCHENER })).toBe(true);
});
it("is ineligible outside the radius", () => {
expect(isZoneEligible(radiusZone({ radiusKm: 10 }), TORONTO, { coordinates: KITCHENER })).toBe(false);
});
it("is ineligible when the location has no coordinates set", () => {
expect(isZoneEligible(radiusZone(), null, { coordinates: KITCHENER })).toBe(false);
});
it("is ineligible when the customer has no coordinates (couldn't geocode)", () => {
expect(isZoneEligible(radiusZone(), TORONTO, {})).toBe(false);
});
it("is ineligible when radiusKm isn't configured", () => {
expect(isZoneEligible(radiusZone({ radiusKm: null }), TORONTO, { coordinates: KITCHENER })).toBe(false);
});
});
describe("driving-distance zones", () => {
function drivingZone(overrides: Partial<ZoneLike> = {}): ZoneLike {
return {
id: "zone_3",
locationId: "loc_1",
type: "driving",
postalCodes: [],
radiusKm: 30,
minOrders: null,
active: true,
...overrides,
};
}
it("is eligible when the pre-resolved driving distance is within the max", () => {
expect(isZoneEligible(drivingZone({ radiusKm: 30 }), TORONTO, { drivingDistanceKm: 25 })).toBe(true);
});
it("is ineligible when the pre-resolved driving distance exceeds the max", () => {
expect(isZoneEligible(drivingZone({ radiusKm: 30 }), TORONTO, { drivingDistanceKm: 45 })).toBe(false);
});
it("is ineligible when no driving distance could be resolved (no API key, routing failure)", () => {
expect(isZoneEligible(drivingZone(), TORONTO, {})).toBe(false);
});
it("is ineligible when radiusKm isn't configured", () => {
expect(isZoneEligible(drivingZone({ radiusKm: null }), TORONTO, { drivingDistanceKm: 5 })).toBe(false);
});
});
it("an unrecognized zone type is never eligible", () => {
expect(isZoneEligible(postalZone({ type: "driving-time" }), null, { postalCode: "M5V 3A8" })).toBe(false);
});
});