feat(F-106): completed feature
This commit is contained in:
25
work/artifacts/F-106/implementer.md
Normal file
25
work/artifacts/F-106/implementer.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# F-106 — Admin order editing, status notifications, tracking
|
||||
|
||||
## Implementación
|
||||
|
||||
### Backend (orders module)
|
||||
- `PUT /orders/:id/items` (admin): reemplaza artículos del pedido. Consolida cantidades por variante, resuelve precio neto + IVA vigente del catálogo/pricing, recalcula subtotal/IVA/total conservando descuento (capped) y coste de envío original. Transacción BEGIN/COMMIT con rollback.
|
||||
- `POST /orders/:id/transitions/admin` ahora acepta `trackingNumber` (obligatorio para `SHIPPED`, error 422 `TRACKING_NUMBER_REQUIRED` si falta) y devuelve `{ ...order, notified, notificationError? }`.
|
||||
- Nuevo `order-status-mailer.ts`: envía email al cliente en cada cambio de estado usando SMTP de `store_settings` (Ajustes → SMTP / Email) con fallback a env. HTML escapado, sin raw HTML de usuario. Fallos de envío no bloquean la transición.
|
||||
- Migración `036_order_tracking_number.js`: `orders_orders.tracking_number text NULL`.
|
||||
- `serializeOrder` expone `trackingNumber`.
|
||||
|
||||
### Admin UI (apps/admin)
|
||||
- Detalle de pedido (`/orders/[id]`):
|
||||
- Modal de transición pide nº de seguimiento obligatorio al marcar Enviado.
|
||||
- Banner verde/ámbar con resultado de la notificación al cliente (`notified` / `notificationError`).
|
||||
- Modo "Editar artículos": cantidades editables, quitar artículo, añadir producto (búsqueda por nombre/SKU → selección de variante), guardar con recálculo de totales.
|
||||
- Bloque "Seguimiento" en Resumen mostrando `trackingNumber`.
|
||||
- `api-client.ts`: `ordersApi.transition(id, state, trackingNumber?)` y `ordersApi.editItems`.
|
||||
|
||||
## Evidencia
|
||||
- `npm run typecheck` (backend) OK; `tsc --noEmit` (apps/admin) OK.
|
||||
- `npm run build` backend OK; `next build` admin OK.
|
||||
- Migración 036 aplicada (`Migrations complete!`).
|
||||
- `monolith.sh prod restart` → backend/frontend/admin/storefront 200.
|
||||
- OpenAPI registra `PUT /orders/{id}/items` y trackingNumber en transición admin.
|
||||
12
work/artifacts/F-106/leader-close.json
Normal file
12
work/artifacts/F-106/leader-close.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"feature_id": "F-106",
|
||||
"agent": "leader",
|
||||
"summary": "F-106 delivers admin order editing, customer status notifications and shipment tracking end to end.",
|
||||
"evidence": [
|
||||
"reviewer.json APPROVED",
|
||||
"security.json APPROVED",
|
||||
"qa.json APPROVED"
|
||||
],
|
||||
"verdict": "APPROVED",
|
||||
"timestamp": "2026-08-21T07:27:36Z"
|
||||
}
|
||||
13
work/artifacts/F-106/qa.json
Normal file
13
work/artifacts/F-106/qa.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"feature_id": "F-106",
|
||||
"agent": "qa",
|
||||
"summary": "Backend and admin typecheck and build green; migration 036 applied; verify.sh green; all four services HTTP 200 after prod restart; OpenAPI shows PUT /orders/{id}/items and trackingNumber field.",
|
||||
"evidence": [
|
||||
"npm run typecheck OK; apps/admin tsc --noEmit OK",
|
||||
"next build OK with BUILD_ID",
|
||||
"Migrations complete (036_order_tracking_number)",
|
||||
"./scripts/verify.sh green"
|
||||
],
|
||||
"verdict": "APPROVED",
|
||||
"timestamp": "2026-08-21T07:27:36Z"
|
||||
}
|
||||
16
work/artifacts/F-106/qa.md
Normal file
16
work/artifacts/F-106/qa.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# F-106 — QA gate: APPROVED
|
||||
|
||||
## Ejecutado
|
||||
- `npm run typecheck` (backend): OK.
|
||||
- `npx tsc --noEmit` (apps/admin): OK.
|
||||
- `npm run build` backend: OK.
|
||||
- `npm run build` admin (Next.js): OK.
|
||||
- Migración `036_order_tracking_number`: aplicada (`Migrations complete!`), idempotente (`IF NOT EXISTS`).
|
||||
- `./scripts/verify.sh`: verde.
|
||||
- `monolith.sh prod restart`: backend/frontend/admin/storefront → HTTP 200.
|
||||
- OpenAPI: `PUT /orders/{id}/items` registrado; `trackingNumber` presente en esquema de transición admin.
|
||||
|
||||
## Casos verificados (código + smoke)
|
||||
- Transición a SHIPPED sin tracking → 422 TRACKING_NUMBER_REQUIRED.
|
||||
- Edición de artículos con variante inexistente → 422 VARIANT_NOT_FOUND; sin precio → 422 PRICE_MISSING.
|
||||
- Email sin SMTP configurado → `notified:false` + mensaje, pedido transiciona igualmente.
|
||||
13
work/artifacts/F-106/reviewer.json
Normal file
13
work/artifacts/F-106/reviewer.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"feature_id": "F-106",
|
||||
"agent": "reviewer",
|
||||
"summary": "Admin order item editing recalculates totals server-side in a transaction; SHIPPED requires tracking number; notification failures never block state transitions.",
|
||||
"evidence": [
|
||||
"PUT /orders/:id/items with BEGIN/COMMIT and server-side price/VAT resolution",
|
||||
"422 TRACKING_NUMBER_REQUIRED enforced in backend and UI for SHIPPED",
|
||||
"Response carries notified/notificationError surfaced in admin banner",
|
||||
"Acceptance bullets all covered"
|
||||
],
|
||||
"verdict": "APPROVED",
|
||||
"timestamp": "2026-08-21T07:27:36Z"
|
||||
}
|
||||
16
work/artifacts/F-106/reviewer.md
Normal file
16
work/artifacts/F-106/reviewer.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# F-106 — Reviewer gate: APPROVED
|
||||
|
||||
## Alcance revisado
|
||||
- `project/src/modules/orders/api/orders.routes.ts` (PUT items, tracking en transición, notified/notificationError)
|
||||
- `project/src/modules/orders/application/order-service.ts` (`editItems`, `transitionAdmin` con tracking)
|
||||
- `project/src/modules/orders/domain/order.ts`, `domain/ports.ts`
|
||||
- `project/src/modules/orders/infrastructure/pg-order-repository.ts` (`replaceItems` transaccional)
|
||||
- `project/src/modules/orders/infrastructure/order-status-mailer.ts`
|
||||
- `project/migrations/036_order_tracking_number.js`
|
||||
- Admin: `apps/admin/src/app/(dashboard)/orders/[id]/page.tsx`, `lib/api-client.ts`, `types/index.ts`
|
||||
|
||||
## Veredicto
|
||||
- La edición de artículos usa transición explícita con rollback; totales recalculados server-side (el cliente no puede imponer precios).
|
||||
- Tracking obligatorio para SHIPPED validado en backend (422) y en UI.
|
||||
- Fallo de email no revierte la transición; se reporta `notified:false` + motivo.
|
||||
- Aceptación cubierta: agregar/quitar/ajustar artículos ✅, notificación por email ✅, tracking en Enviado ✅, verify.sh verde ✅.
|
||||
12
work/artifacts/F-106/security.json
Normal file
12
work/artifacts/F-106/security.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"feature_id": "F-106",
|
||||
"agent": "security",
|
||||
"summary": "All new endpoints require admin auth; inputs validated with zod (uuid, quantity 1..999, tracking max 120); parameterized SQL only; SMTP secrets never logged or leaked; email HTML escaped.",
|
||||
"evidence": [
|
||||
"authenticate + requireRole('admin') on items edit and admin transition",
|
||||
"No user-supplied totals trusted; discount capped, totals floored at 0",
|
||||
"escapeHtml applied to all dynamic email content"
|
||||
],
|
||||
"verdict": "APPROVED",
|
||||
"timestamp": "2026-08-21T07:27:36Z"
|
||||
}
|
||||
9
work/artifacts/F-106/security.md
Normal file
9
work/artifacts/F-106/security.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# F-106 — Security gate: APPROVED
|
||||
|
||||
## Controles verificados
|
||||
- **AuthN/AuthZ**: `PUT /orders/:id/items` y transición admin requieren `deps.authenticate` + `requireRole(user,'admin')`.
|
||||
- **Validación de entrada**: `z.uuid()` para variantId, quantity int 1..999, tracking maxLength 120. Sin SQL拼接: todas las queries parametrizadas ($n / ANY($1::uuid[])).
|
||||
- **Secretos**: credenciales SMTP leídas de `store_settings`/env en el momento del envío; no se loguea la contraseña; el error devuelto al admin es genérico del envío, no expone credenciales.
|
||||
- **XSS**: email HTML usa `escapeHtml` para orderId/estado/tracking; sin HTML de usuario sin escapar.
|
||||
- **Integridad de totales**: precios e IVA se resuelven server-side desde catálogo/pricing; `discountCents` capped al nuevo subtotal y `shippingCents` derivado con `Math.max(0,...)` evitando totales negativos.
|
||||
- El motivo de cancelación/reembolso de la UI no se persiste (sin superficie nueva).
|
||||
Reference in New Issue
Block a user