# Implementation Review — 2026-09-04 **Scope:** Coverage audit of the current implementation against `DS_Delivery_Date_Time_App_Study.docx` (the "DS Pickup Delivery Date & Time" teardown by C2C). **Reviewed:** `app/` (routes, services, lib), `extensions/`, `prisma/schema.prisma`, `widget-src/`, `shopify.app.toml`, `tests/`. **Verdict:** The scheduling core, concurrency safety, multi-surface consistency, zones/auto-assignment, and all-plan checkout enforcement + confirmation — the hard parts and the competitive moat — are solid and tested. The meaningful gaps are inventory-based location exclusion (dead code), payment customization (absent), rate *application* vs. *display*, product-page placement, and live slot re-validation at checkout. Several gaps are consciously documented deferrals; the inventory one looks like an actual wiring bug. --- ## Fully covered | Study requirement | Where | |---|---| | 3 independent fulfillment methods (Shipping / Local Delivery / Pickup) with own rules, calendars | `Method` enum; per-method `SlotTemplate` / `SlotOverride` / `BlackoutDate` | | Date→time slot picker; unavailable dates/slots **hidden**, not rejected | `app/services/scheduling.server.ts:89` filters past-cutoff / under-lead / blacked-out / full | | Cut-off times + preparation/lead time (incl. per-product) | `cutoffMin`, `leadTimeMin` on template; `ProductRule.leadTimeMin` folded in at `app/services/availability-request.server.ts:210` | | Blackout dates; per-weekday / per-method / per-location enable-disable | `BlackoutDate`, `SlotTemplate` per weekday, `SlotOverride.closed` | | Per-slot capacity limit | `capacity` + `remainingCapacity()` in `app/services/capacity.server.ts` | | Multi-location; radius **and** driving-distance zones; postal zones; nearest-location auto-assign; delivery-density threshold | `app/services/zones.server.ts` | | Product / collection / vendor / type / tag rules → allowed methods, lead time, allowed locations | `app/services/product-rules.server.ts` | | Rate resolution by zone / distance per method | `app/services/rates.server.ts` | | Cart + cart-drawer placement; cross-theme auto-inject before checkout button | `widget-src/datetime-widget/datetime-widget.ts:570` | | Thank-you / order-status confirmation on **all plans** | `extensions/datetime-widget/blocks/order-confirmation.liquid` | | POS scheduling against the **same** capacity pool | `app/routes/pos.scheduling.*` reuse `resolveAvailabilityRequest` | | Last-slot race prevention | atomic Lua hold in `app/services/holds.server.ts:22` + `tests/integration/holds.concurrency.test.ts` | | Server-side checkout enforcement on non-Plus | `extensions/validation-slot` Function | | Single availability service across surfaces; multi-tenant `shopDomain` scoping; DST-safe Luxon math | ✅ | | Order write-back (metafield + note attributes) | `app/routes/webhooks.orders.create.tsx` | | Ops dashboard: bookings by day/slot/location, utilization, revenue by method, CSV export | `app/services/dashboard.server.ts` | | DS review complaint (pickup vs "estimated delivery date" mismatch) | explicitly fixed via method-specific labels + `delivery-customization` rename | --- ## Partial / deviations from the study 1. **Inventory-based location exclusion is coded but never called.** `excludeLocationsWithoutStock` (`app/services/zones.server.ts:241`) has **zero callers** — `resolveAvailabilityRequest` never invokes it. Study §3.4 "product-inventory-based location choice" is effectively absent at runtime. *Looks like a wiring bug, not a deliberate deferral.* 2. **Rates are displayed, not applied.** `delivery-customization` only *renames* the delivery option to append a price label (`extensions/delivery-customization/src/evaluate.js:23`). Nothing adjusts the actual charged shipping price or feeds a Shopify rate. Study §3.6 / the DS core value prop ("location-based shipping/delivery/pickup rates") is only half there. 3. **Checkout re-validation checks presence only.** `validation-slot` (`extensions/validation-slot/src/evaluate.js`) verifies the `dd_*` attributes exist; it does **not** re-check that the slot is still open (capacity / blackout / cutoff). Study §5.2 explicitly names "or has since become invalid." Mitigation is the 10-minute hold TTL, which the code comments acknowledge. 4. **Validation Function can't be scoped by product.** Every order on an activated shop is treated as requiring a slot — no ProductRule-scoped enforcement (Functions can't read the app DB). A store mixing schedulable and non-schedulable items can't use it selectively. 5. **"Date range" selection reinterpreted.** The schema/engine implement a SHIPPING **arrival-range display** (`transitMinDays` / `transitMaxDays`), not a shopper-selectable multi-day range. Study §3.2 "support for picking a range rather than a single date." 6. **Product-rule "block a specific slot based on cart contents"** (§3.5, "fragile item that can't go on the earliest slot") is only expressible as a lead-time push. The planned `blockSlotRule Json?` field was dropped from `schema.prisma`. 7. **Pickup map is single-location.** The widget maps the one resolved location (`widget-src/datetime-widget/datetime-widget.ts:427`); there's no multi-pin pickup-location chooser. Study §3.4 "pickup locations shown visually on Google Maps." 8. **Multi-language is structure-only.** Locale keys exist but only `en.default.json` ships — no second translation. 9. **Per-day order cap not modeled** — only per-slot `capacity`. Study §3.3 "in a given slot **or day**." --- ## Not implemented - **Payment customization** (§5.2 — "hide cash-on-delivery for a shipped order"). `write_payment_customizations` scope is requested but no such Function exists. - **Product-page placement** (§3.7). No product-page block — only cart / drawer / thank-you / order-status / POS. - **Checkout UI Extension source** (`extensions/checkout-datetime/`) — only a gitignored `dist/` bundle; no `src/` or `shopify.extension.toml` tracked. The Plus native picker is not a maintained deliverable. (The all-plan Liquid confirmation block does cover thank-you / order-status.) - **Returning-customer recognition** (§3.8) — `read_customers` deliberately dropped; documented as deferred. - **Cart Transform** (deposits / fees) — scope requested, marked v2. - **Named capacity resources** (oven / driver / picker pools) — capacity is a single integer; `CapacityResource` from the plan was never added. - **GDPR compliance webhooks** are commented out in `shopify.app.toml` pending Shopify's Protected Customer Data approval (handlers exist). Blocks a Built-for-Shopify submission. --- ## Implementation follow-up — 2026-09-04 (same day) Actioned in this pass ("Core + new Functions" scope; Cart Transform / deposits, returning-customer recognition, and GDPR re-enable stay deferred). ### Done — code + passing unit tests (`npm test`, 156 green) 1. **Inventory-based location exclusion is now wired.** `resolveAvailabilityRequest` calls `excludeLocationsWithoutStock` when the app-proxy surface supplies an Admin client + cart variant GIDs; the widget now sends `variantIds`. Out-of-stock locations are dropped from both the single-location and delivery-zone paths, with a dedicated "no location stocks every item" message. 2. **Live slot re-validation at checkout.** New `app/services/checkout-snapshot.server.ts` writes a shop-metafield capacity snapshot; the `validation-slot` Function (`evaluateCheckout`) now rejects a complete selection when the snapshot shows the slot is full, blacked out, closed, past the daily cap, or gone from the schedule. Snapshot is refreshed on `orders/create|updated|cancelled` and on slot / blackout / location / enforcement edits. 3. **Scopable checkout enforcement.** `Shop.enforcementMode` (`all` / `tagged` / `off`) + `enforcementTag`, new **Checkout enforcement** admin page (`app.settings.tsx`), honoured by the Function via the snapshot's `enforcement` block and `schedulableProductIds`. 4. **Per-day order cap.** `Location.dailyOrderCap`, threaded through `getAvailability` (`dailyCap` + `consumedPerDate`) — the whole date drops once its bookings reach the cap, and per-slot `remainingCapacity` is clamped to the day's remaining budget. Admin field on the location edit screen. 5. **Product-rule slot blocking (§3.5).** `ProductRule.blockedStartMins`, unioned in `resolveProductRuleConstraints`, applied in `getAvailability` and re-enforced server-side in `resolveHoldRequest`. Admin field on the product rules screen. 6. **Product-page placement.** New `blocks/product-availability.liquid` + widget `data-mode="preview"` — a read-only "earliest available date per method" line that collects nothing. 7. **Second locale.** `extensions/datetime-widget/locales/fr.json` + `fr.schema.json` (French), proving the i18n path end-to-end. New migration: `prisma/migrations/20260904120000_review_gaps` (hand-written; apply with `prisma migrate deploy` — the dev DB was unreachable here). ### Done — source + unit tests, needs `shopify app deploy` / CLI to ship 8. **payment-customization Function** — new `extensions/payment-customization/` (`cart.payment-methods.transform.run`): hides cash-on-delivery / pay-in-store gateways on `SHIPPING` orders. Pure logic unit-tested (`tests/unit/payment-customization.test.ts`). Run `npm --prefix extensions/payment-customization run typegen` then `shopify app deploy`; merchant activates it under Settings → Payments. 9. **checkout-datetime Checkout UI Extension** — `src/` restored (was a gitignored `dist/` only): `Checkout.jsx` (Plus native picker → `checkout.scheduling.*` routes, same capacity pool), `ThankYou.jsx`, `OrderStatus.jsx`. `tsconfig` ships `checkJs:false` until the `` props are reconciled against the live `@shopify/ui-extensions` checkout types (see its README) — the server side it calls is already tested. ### Not done (unchanged from above) - Cart Transform / deposits — v2. - Returning-customer recognition — needs `read_customers` + Protected Customer Data approval. - GDPR compliance webhooks — still blocked on Shopify approval. - Multi-pin pickup-location map picker — still single-location. - Rates remain display-only (delivery-customization renames the option; it doesn't set the charged price). Left as a product decision. - Worker periodic snapshot sweep — event-driven refresh covers the real cases; the 30-day horizon sweep needs offline-session storage first (noted in `jobs/worker.ts`). ## Suggested priority order 1. Wire `excludeLocationsWithoutStock` into `resolveAvailabilityRequest` (or delete it if intentionally cut). — *bug* 2. Decide rate strategy: real `delivery-customization` pricing / Shopify rate integration vs. accept display-only and document it. 3. Live slot re-validation at checkout (metafield capacity snapshot the Validation Function can read) — closes the §5.2 "since become invalid" gap. 4. Product-page placement block. 5. Payment customization Function (COD-on-shipping etc.). 6. Product-scoped enforcement toggle for the Validation Function. 7. Second locale + per-day cap + multi-pin pickup map as smaller follow-ups. 8. Re-enable GDPR webhooks once Protected Customer Data access is granted (launch blocker for BfS).