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>
35 lines
1.2 KiB
TypeScript
35 lines
1.2 KiB
TypeScript
// Shared parsing for the ProductRule-scoping params every scheduling route
|
|
// (storefront + POS, availability + hold) accepts identically — kept here so
|
|
// the four call sites don't each re-implement the same defensive JSON parsing.
|
|
|
|
export interface CartLineParam {
|
|
vendor?: string;
|
|
productType?: string;
|
|
}
|
|
|
|
/** `cartLines` is a JSON-encoded array; malformed/missing input degrades to "no cart info" rather than erroring the request. */
|
|
export function parseCartLinesParam(raw: string | null | undefined): CartLineParam[] {
|
|
if (!raw) return [];
|
|
try {
|
|
const parsed = JSON.parse(raw);
|
|
if (!Array.isArray(parsed)) return [];
|
|
return parsed
|
|
.filter((entry): entry is Record<string, unknown> => typeof entry === "object" && entry !== null)
|
|
.map((entry) => ({
|
|
vendor: typeof entry.vendor === "string" ? entry.vendor : undefined,
|
|
productType: typeof entry.productType === "string" ? entry.productType : undefined,
|
|
}));
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/** `productIds` is a comma-separated list of Shopify Product GIDs. */
|
|
export function parseProductIdsParam(raw: string | null | undefined): string[] {
|
|
if (!raw) return [];
|
|
return raw
|
|
.split(",")
|
|
.map((id) => id.trim())
|
|
.filter(Boolean);
|
|
}
|