metatrondelivery/IMPLEMENTATION_REVIEW_2026-09-04.md
metatroncubeswdev a2c78d703f
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
feat: close DS study coverage gaps (inventory exclusion, live checkout re-validation, per-day cap, payment fn, checkout ext)
Audited the implementation against DS_Delivery_Date_Time_App_Study.docx and
closed the actionable gaps (see IMPLEMENTATION_REVIEW_2026-09-04.md).

Core (code + unit tests, 156 green):
- Wire excludeLocationsWithoutStock into resolveAvailabilityRequest; widget
  now sends variantIds so inventory-based location exclusion actually runs.
- Live slot re-validation at checkout: new checkout-snapshot.server.ts writes
  a shop-metafield capacity snapshot; validation-slot's evaluateCheckout
  rejects a complete selection that has since filled / blacked out / closed /
  hit the daily cap / left the schedule. Refreshed on order webhooks and
  slot/blackout/location/enforcement edits.
- Scopable checkout enforcement: Shop.enforcementMode (all|tagged|off) +
  enforcementTag, new app.settings.tsx admin page, honoured via the snapshot.
- Per-day order cap: Location.dailyOrderCap threaded through getAvailability
  (dailyCap + consumedPerDate); admin field on the location screen.
- Product-rule slot blocking: ProductRule.blockedStartMins, unioned in
  resolveProductRuleConstraints, enforced in the engine and resolveHoldRequest;
  admin field on the product rules screen.
- Product-page placement: product-availability.liquid block + widget
  data-mode="preview" (read-only earliest-date line).
- Second locale: datetime-widget fr.json / fr.schema.json.
- Migration 20260904120000_review_gaps (apply with prisma migrate deploy).

New Functions (source + unit tests; need `shopify app deploy` to ship):
- extensions/payment-customization: cart.payment-methods.transform.run — hides
  cash-on-delivery / pay-in-store gateways on SHIPPING orders.
- extensions/checkout-datetime/src: restored from a gitignored dist-only state
  — Plus native picker + Thank you / Order status confirmation blocks, all
  calling the existing checkout.scheduling.* routes (one capacity pool).
  tsconfig ships checkJs:false pending reconciliation with live checkout types.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 01:31:02 -04:00

196 lines
11 KiB
Markdown

# 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 `<s-*>` 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).