Document partial returns, the reconstructed-items PATCH, and the new test suite
This commit is contained in:
@@ -678,23 +678,35 @@ transition is still valid (friendlier error than a bare 403 if it's gone
|
||||
stale — two tabs open, order shipped in the meantime) before calling
|
||||
`requestOrderStatusChange()`.
|
||||
|
||||
Requesting a return also asks *why*, via `window.prompt()` — the same
|
||||
plain-browser-dialog pattern already used for cancel's `confirm()`, not a
|
||||
custom form. The reason is required client-side (empty input re-shows an
|
||||
inline error instead of submitting) and stored on `orders.returnReason` —
|
||||
useful for quality/assortment decisions later, and shown back on the order
|
||||
detail page. Cancel doesn't ask for one; it's a lower-stakes action
|
||||
(before shipping, often just a change of mind).
|
||||
Requesting a return opens an inline form (`OrderActionButton.tsx`), not
|
||||
just a confirm dialog — **partial returns are supported**: a quantity
|
||||
input per order line (0 up to that line's ordered quantity) plus a
|
||||
required reason textarea. At least one line must have a nonzero quantity
|
||||
to submit. Cancel stays a plain `window.confirm()` — it's a lower-stakes,
|
||||
whole-order-only action (before shipping, often just a change of mind),
|
||||
no quantity picker or reason needed there.
|
||||
|
||||
The route (`/api/account/orders/[orderNumber]` PATCH) reconstructs the
|
||||
order's *full* `items` array before sending it to Payload — only the
|
||||
requested lines' `returnQuantity` differs from what's already stored,
|
||||
every other field (product/price/tax rate/etc.) is passed through
|
||||
unchanged. This isn't optional: Payload's array field expects every
|
||||
required sub-field present on each row, and the field-lock hook (see
|
||||
below) specifically checks that only `returnQuantity` changed — a sparse
|
||||
`{ returnQuantity: 2 }`-only patch would fail both.
|
||||
|
||||
**The real security boundary is in Payload**, not here: `orders.access.update`
|
||||
already scoped a customer's JWT to their own order, but with no
|
||||
field-level restriction — before this stage, a logged-in customer could in
|
||||
principle PATCH *any* field of their own order (`total`, `items`,
|
||||
anything), just because nothing in the frontend had ever exercised that
|
||||
path yet. `Orders.ts`'s `beforeChange` hook now rejects a
|
||||
customer-authenticated update unless the only changed field is `status`,
|
||||
via an allowed transition. See the Payload README's own writeup for the
|
||||
full detail.
|
||||
path yet. `Orders.ts`'s `beforeChange` hook rejects a customer-authenticated
|
||||
update unless the change is limited to `status` (plus `returnReason` and
|
||||
each item's `returnQuantity`, bounded 0..quantity, alongside a
|
||||
`return_requested` transition), via an allowed transition. See the
|
||||
Payload README's own writeup for the full detail — including
|
||||
`orderUpdateValidation.ts`, where this logic now lives as a unit-tested
|
||||
pure function.
|
||||
|
||||
No hard 14-day return-window check in code (no separately tracked delivery
|
||||
date exists yet) — relies on the existing `/widerruf` legal text plus
|
||||
@@ -705,13 +717,17 @@ Payload admin.
|
||||
|
||||
Reaching `status: 'cancelled'` or `status: 'returned'` also auto-generates
|
||||
a **Stornorechnung**/**Gutschrift** correction-invoice PDF, attached to
|
||||
that status's customer email (Payload-side, see the Payload README's
|
||||
`orders.ts` section — this is not frontend code) — a full reversal of the
|
||||
original invoice, referencing its (immutable) invoice number, drawn from
|
||||
the same invoice number range. This is a document only, not a money
|
||||
movement: an actual refund still has to happen manually, same limitation
|
||||
as above, since no payment provider exists yet to capture or reverse a
|
||||
real charge.
|
||||
that status's customer email (Payload-side, see the Payload README's "How
|
||||
a Stornorechnung/Gutschrift relates to the original invoice" section —
|
||||
this is not frontend code). Stornorechnung is always a full reversal
|
||||
(cancellation is always pre-shipping, whole order, shipping included).
|
||||
Gutschrift reflects only the returned quantities — full or partial —
|
||||
**excludes shipping** (already delivered by the time a return is
|
||||
possible) and **never reprorates the original discount** (confirmed
|
||||
policy, not a default: the discount stays with whatever's kept). Either
|
||||
way, this is a document only, not a money movement: an actual refund
|
||||
still has to happen manually, since no payment provider exists yet to
|
||||
capture or reverse a real charge.
|
||||
|
||||
### Status-change emails
|
||||
|
||||
@@ -754,6 +770,35 @@ sees.
|
||||
monitor in the existing "Content & API" group (`~/dev/README.md`'s
|
||||
documented `sqlite3`-insert method, Kuma 1.x has no REST API for this).
|
||||
|
||||
## Tests
|
||||
|
||||
`npm run test:unit` (Vitest, `node` environment, no jsdom/Next.js runtime
|
||||
needed) — no test infrastructure existed in this repo before; started with
|
||||
the pure logic most likely to silently produce wrong numbers on a live
|
||||
order, not attempted exhaustive coverage:
|
||||
|
||||
- **`app/lib/__tests__/cartTotals.test.ts`** — subtotal/discount/shipping
|
||||
math (`computeSubtotal`, `computeCartTotals`), incl. the fixed-discount
|
||||
clamp and `compareAtPrice`-based savings display being independent of
|
||||
the discount-code math.
|
||||
- **`app/lib/__tests__/invoicePdf.test.ts`** — `isPaidImmediately()` and
|
||||
the original invoice's per-rate `groupByTaxRate()` (both exported via an
|
||||
`__testables` object specifically for this, same pattern the Payload
|
||||
backend uses for its own correction-invoice tests).
|
||||
- **`app/lib/__tests__/bundleContents.test.ts`** — `describeBundleContents()`,
|
||||
extracted out of `app/api/checkout/route.ts` into its own module
|
||||
(`app/lib/bundleContents.ts`) specifically so it's importable from a
|
||||
test — Next.js `route.ts` files only allow HTTP-method (+ a few config)
|
||||
exports, not arbitrary named ones.
|
||||
|
||||
The Gutschrift/Stornorechnung money math itself (`resolveLineItems()`,
|
||||
`groupByTaxRate()` in `correctionInvoicePdf.tsx`) and the Payload-side
|
||||
`orders.ts` field-lock security logic are tested in the **Payload
|
||||
backend's** own `test:unit` instead — see that repo's README's own
|
||||
"Tests" section — since that's where those functions actually live (this
|
||||
repo's `correctionInvoicePdf.tsx` is only a port for the download button,
|
||||
not the source of truth).
|
||||
|
||||
## Deployment
|
||||
|
||||
- **Dockerfile**: 3-stage build (`deps` → `builder` → `runner`) with
|
||||
|
||||
Reference in New Issue
Block a user