Stage M5 and Stage M6

This commit is contained in:
Nông Đức Huy
2026-08-13 23:20:23 +07:00
parent 7688657d3c
commit 624a6402bf
77 changed files with 4879 additions and 176 deletions
+29 -12
View File
@@ -141,7 +141,7 @@ sport-store/
│ ├── common/ decorators · filters · guards · interceptors
│ │ middleware · pipes · errors
│ ├── infrastructure/ prisma · redis · storage · events · logging
│ └── modules/ 20 bounded contexts
│ └── modules/ 21 bounded contexts
│
├── packages/
│ ├── types/ Framework-free domain + API contracts (zero deps)
@@ -158,7 +158,7 @@ sport-store/
│
├── docs/
│ ├── architecture.md Boundaries, conventions, risks — read this first
│ └── adr/ 17 decision records
│ └── adr/ 18 decision records
│
├── docker-compose.yml Backing services; `--profile full` runs everything
├── turbo.json pnpm-workspace.yaml package.json
@@ -251,17 +251,16 @@ locale-in-path would buy nothing.
| **M2** ✅ | Auth: login, refresh rotation with reuse detection, RBAC admin, user & role management |
| **M3** ✅ | Admin catalog: write API, variant matrix, media uploads, inventory ledger, product editor (option builder + per-locale tabs) |
| **M4** ✅ | Storefront catalog — listings, PDP, variant selector, filters, sort control, load-more pagination and a mobile filter sheet |
| **M5** | Cart, checkout, orders |
| **M6** | Search + faceting |
| **M5** ✅ | Cart (Redis), guest checkout, orders with stock reservation and an admin order lifecycle |
| **M6** ✅ | Search: PostgreSQL full-text + trigram, diacritic-folded, ranked, with type-ahead and refinable results |
| **M7** | Promotions, coupons, reviews, CMS |
| **M8** | Customer account |
| **M9** | Payments (VNPay, MoMo, ZaloPay, COD), shipping, notifications |
**Recommended next step: M5 (cart & checkout).** M3 now closes the loop end to end: an operator
creates a product with per-locale content, defines the option axes, gets a generated variant matrix,
prices it, attaches imagery per colourway, receives stock through the ledger and publishes — and the
result renders on the storefront in both languages. Cart and checkout are the first flows that put
the variant model under real concurrency.
**Recommended next step: M7 (promotions, coupons, reviews, CMS) or M9 (payments).** The store can
now be browsed, searched, filled into a bag and checked out, and every order moves stock through a
ledger. What it still cannot do is take money — which is the one gap between this and a shop that
trades.
---
@@ -271,11 +270,12 @@ Everything below was run, not assumed:
- `pnpm lint` · `pnpm typecheck` · `pnpm test` · `pnpm build` — 27/27 Turborepo tasks pass;
`pnpm format:check` clean
- 6 migrations, 32 tables; seed loads 36 permissions, 6 roles, 3 brands, 8 categories,
- 7 migrations, 35 tables; seed loads 36 permissions, 6 roles, 3 brands, 8 categories,
3 collections, 12 products, **155 variants**, 64 uploaded images and 3 dev accounts
- **48 tests** — RBAC guards, password hashing, translation fallback, `Accept-Language`, the
- **57 tests** — RBAC guards, password hashing, translation fallback, `Accept-Language`, the
variant matrix planner, the HTTP client's fetch receiver and retry recursion, the inventory
list's variant-driven projection, and the two admin-schema defects that caused silent data loss
list's variant-driven projection, the two admin-schema defects that caused silent data loss, and
order-number round-tripping
- Catalog: listings with filters/facets/cursor paging, PDP, navigation — correctly localised in
both `vi` and `en`; money formats per locale from one integer (`690.000 ₫` / `₫690,000`)
- Storefront: every route 200 in both locales; `/en/products/<vi-slug>` → 307 →
@@ -303,6 +303,23 @@ through Chrome with the console and network panel open:
media library upload driven from the browser (presign → PUT to MinIO → register, 400×500 PNG
landed at 10,962 bytes with a date-partitioned UUID key); inventory adjustment from the table
wrote a ledger entry and the storefront went `OUT_OF_STOCK` → `IN_STOCK` on the next request
- **Search (M6):** `nocturne` and `running jacket` match exactly; `ao chay bo` finds _Áo Chạy Bộ
Aero_ without diacritics; `nocturn`, `jaket` and `runing` survive their typos; `velocity`
matches by brand, `crimson` by colourway, `VEL-NOC` by SKU; `zzzzqqq` correctly finds nothing.
Type-ahead suggests from the product name only, and results stay refinable by the same facets
as any listing with an honest sort control
- **Concurrency:** three simultaneous checkouts for a single unit produce exactly one order and
two clean rejections, with `reserved` landing on 1 — the read-then-write version created two
orders and lost a reservation
- **The purchase flow, end to end in a browser (M5):** added to bag from the PDP (badge updates),
changed quantity in the bag, checked out as a guest and placed order **SP-000003**; the
confirmation page is reachable from its bookmarkable URL; the admin confirmed then fulfilled it,
which moved `onHand` 15→12, released `reserved` 3→0 and wrote a `SALE -3` ledger entry alongside
`order.confirmed` / `order.fulfilled` audit records
- **Commerce guard rails:** reserving does not touch `onHand`; cancelling returns the reservation;
`PENDING→COMPLETED` is refused; cancelling without a reason is refused; a 20-unit request caps to
available stock with a `QUANTITY_REDUCED` notice; guest order lookup needs number _and_ email and
answers a wrong email with the same `NOT_FOUND` as a wrong number
- **Catalog write correctness**, each reproduced before the fix and re-run after: a product
authored in one language is accepted; renaming a product no longer clears its gender/sport
targeting, collections or attributes; adding a size inherits the sibling price and the stored