Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
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>
196 lines
11 KiB
Markdown
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).
|