# Order Status Simplification (A+B) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Remove `DELIVERY_FEE_ADJUSTMENT` and `PAYMENT_VERIFICATION` statuses, replace with `deliveryFeeLocked` flag and simplified payment flow. Three order paths: (1) delivery+online, (2) pickup+online, (3) pickup+on_pickup. **Architecture:** Add `deliveryFeeLocked Boolean` to Order model. Path 1 (delivery+online): created as `PENDING_PAYMENT` + `deliveryFeeLocked=false`, admin approves fee → `deliveryFeeLocked=true` → client pays. Path 2 (pickup+online): created as `PENDING_PAYMENT` + `deliveryFeeLocked=true` → client pays immediately. Path 3 (pickup+on_pickup): created as `IN_PROGRESS` → no payment needed. Payment stays in `PENDING_PAYMENT` until admin confirms → `PAID`. No migration needed — no existing orders. **Tech Stack:** Prisma (SQLite), Fastify, React + MUI, TypeScript, vitest --- ### Task 1: Add `deliveryFeeLocked` field to Prisma schema **Files:** - Modify: `server/prisma/schema.prisma:124-152` - [ ] **Step 1: Add field to Order model** Add `deliveryFeeLocked Boolean @default(false)` after `status` in the `Order` model: ```prisma model Order { id String @id @default(cuid()) status String @default("DRAFT") deliveryFeeLocked Boolean @default(false) /// 'delivery' | 'pickup' deliveryType String @default("delivery") // ... rest unchanged ``` - [ ] **Step 2: Run Prisma migration** ```bash cd server && npx prisma migrate dev --name add_delivery_fee_locked ``` Expected: Migration created and applied successfully. --- ### Task 2: Update shared constants — remove 2 statuses **Files:** - Modify: `shared/constants/order-status.js` - Modify: `shared/constants/order-status.d.ts` - [ ] **Step 1: Write updated `order-status.js`** ```js export const ORDER_STATUSES = Object.freeze([ 'DRAFT', 'PENDING_PAYMENT', 'PAID', 'IN_PROGRESS', 'SHIPPED', 'READY_FOR_PICKUP', 'DONE', 'CANCELLED', ]) ``` - [ ] **Step 2: Write updated `order-status.d.ts`** ```ts export declare const ORDER_STATUSES: readonly [ 'DRAFT', 'PENDING_PAYMENT', 'PAID', 'IN_PROGRESS', 'SHIPPED', 'READY_FOR_PICKUP', 'DONE', 'CANCELLED', ] ``` - [ ] **Step 3: Run client typecheck to verify no breakage** ```bash cd client && npx tsc -b ``` Expected: No errors (other files still reference removed statuses — that's expected, they'll be fixed in later tasks). --- ### Task 3: Update server `canTransitionAdminOrderStatus` **Files:** - Modify: `server/src/lib/order-status.js` - Test: `server/src/lib/__tests__/order-status.test.js` - [ ] **Step 1: Write updated tests first** Replace `server/src/lib/__tests__/order-status.test.js`: ```js import { describe, expect, it } from 'vitest' import { canTransitionAdminOrderStatus } from '../order-status.js' describe('canTransitionAdminOrderStatus', () => { const delivery = { deliveryType: 'delivery' } const pickup = { deliveryType: 'pickup' } it('DRAFT → PENDING_PAYMENT', () => { expect(canTransitionAdminOrderStatus({ status: 'DRAFT', ...delivery }, 'PENDING_PAYMENT')).toBe(true) }) it('DRAFT → CANCELLED', () => { expect(canTransitionAdminOrderStatus({ status: 'DRAFT', ...delivery }, 'CANCELLED')).toBe(true) }) it('DRAFT cannot skip to PAID', () => { expect(canTransitionAdminOrderStatus({ status: 'DRAFT', ...delivery }, 'PAID')).toBe(false) }) it('PENDING_PAYMENT → PAID', () => { expect(canTransitionAdminOrderStatus({ status: 'PENDING_PAYMENT', ...delivery }, 'PAID')).toBe(true) }) it('PENDING_PAYMENT → CANCELLED', () => { expect(canTransitionAdminOrderStatus({ status: 'PENDING_PAYMENT', ...delivery }, 'CANCELLED')).toBe(true) }) it('PAID → IN_PROGRESS', () => { expect(canTransitionAdminOrderStatus({ status: 'PAID', ...delivery }, 'IN_PROGRESS')).toBe(true) }) it('IN_PROGRESS (delivery) → SHIPPED', () => { expect(canTransitionAdminOrderStatus({ status: 'IN_PROGRESS', ...delivery }, 'SHIPPED')).toBe(true) }) it('IN_PROGRESS (pickup) → READY_FOR_PICKUP', () => { expect(canTransitionAdminOrderStatus({ status: 'IN_PROGRESS', ...pickup }, 'READY_FOR_PICKUP')).toBe(true) }) it('IN_PROGRESS (delivery) cannot go to READY_FOR_PICKUP', () => { expect(canTransitionAdminOrderStatus({ status: 'IN_PROGRESS', ...delivery }, 'READY_FOR_PICKUP')).toBe(false) }) it('DONE allows no transitions', () => { expect(canTransitionAdminOrderStatus({ status: 'DONE', ...delivery }, 'CANCELLED')).toBe(false) expect(canTransitionAdminOrderStatus({ status: 'DONE', ...delivery }, 'PAID')).toBe(false) }) it('same status returns true', () => { expect(canTransitionAdminOrderStatus({ status: 'DRAFT', ...delivery }, 'DRAFT')).toBe(true) }) }) ``` - [ ] **Step 2: Run tests to verify they fail** ```bash cd server && npm test -- --run src/lib/__tests__/order-status.test.js ``` Expected: `PENDING_PAYMENT → PAID` and `PENDING_PAYMENT → CANCELLED` tests fail (old code doesn't handle `PENDING_PAYMENT`). - [ ] **Step 3: Write updated implementation** Replace `server/src/lib/order-status.js`: ```js export { ORDER_STATUSES } from '../../../shared/constants/order-status.js' /** * Переходы, которые делает админ через PATCH /api/admin/orders/:id/status * (подтверждение получения пользователем — отдельный эндпоинт). */ export function canTransitionAdminOrderStatus(order, next) { const from = order.status const dt = order.deliveryType if (from === next) return true switch (from) { case 'DRAFT': return next === 'PENDING_PAYMENT' || next === 'CANCELLED' case 'PENDING_PAYMENT': return next === 'PAID' || next === 'CANCELLED' case 'PAID': return next === 'IN_PROGRESS' || next === 'CANCELLED' case 'IN_PROGRESS': if (next === 'CANCELLED') return true if (dt === 'delivery') return next === 'SHIPPED' if (dt === 'pickup') return next === 'READY_FOR_PICKUP' return false case 'SHIPPED': case 'READY_FOR_PICKUP': case 'DONE': case 'CANCELLED': return false default: return false } } /** @deprecated используйте canTransitionAdminOrderStatus */ export function canTransitionOrderStatus(from, to) { return canTransitionAdminOrderStatus({ status: from, deliveryType: 'delivery' }, to) } ``` - [ ] **Step 4: Run tests to verify they pass** ```bash cd server && npm test -- --run src/lib/__tests__/order-status.test.js ``` Expected: All tests pass. - [ ] **Step 5: Commit** ```bash git add shared/constants/order-status.js shared/constants/order-status.d.ts server/src/lib/order-status.js server/src/lib/__tests__/order-status.test.js server/prisma/schema.prisma git commit -m "refactor: simplify order status model — remove DELIVERY_FEE_ADJUSTMENT and PAYMENT_VERIFICATION, add deliveryFeeLocked" ``` --- ### Task 4: Update client `getAdminNextOrderStatuses` and labels **Files:** - Modify: `client/src/shared/constants/order.ts` - Modify: `client/src/shared/lib/order-status-labels.ts` - [ ] **Step 1: Write updated `order.ts`** ```ts import { ORDER_STATUSES as SHARED_ORDER_STATUSES } from '@shared/constants/order-status' export const ORDER_STATUSES = SHARED_ORDER_STATUSES as typeof SHARED_ORDER_STATUSES export type OrderStatus = (typeof ORDER_STATUSES)[number] export function getAdminNextOrderStatuses(status: string, deliveryType: 'delivery' | 'pickup'): OrderStatus[] { switch (status) { case 'DRAFT': return ['PENDING_PAYMENT', 'CANCELLED'] case 'PENDING_PAYMENT': return ['PAID', 'CANCELLED'] case 'PAID': return ['IN_PROGRESS', 'CANCELLED'] case 'IN_PROGRESS': if (deliveryType === 'delivery') return ['SHIPPED', 'CANCELLED'] return ['READY_FOR_PICKUP', 'CANCELLED'] default: return [] } } export function canTransitionOrderStatus(from: string, to: string): boolean { if (from === to) return true return getAdminNextOrderStatuses(from, 'delivery').includes(to as OrderStatus) } ``` - [ ] **Step 2: Write updated `order-status-labels.ts`** ```ts /** Человекочитаемые подписи к кодам статуса заказа */ export function orderStatusLabelRu(code: string): string { const map: Record = { DRAFT: 'Черновик', PENDING_PAYMENT: 'Ожидает оплаты', PAID: 'Оплачен', IN_PROGRESS: 'В работе', SHIPPED: 'Отправлен', READY_FOR_PICKUP: 'Готово к получению', DONE: 'Завершён', CANCELLED: 'Отменён', } return map[code] ?? code } ``` - [ ] **Step 3: Run client lint and typecheck** ```bash cd client && npm run lint && npx tsc -b ``` Expected: ESLint passes. TypeScript may show errors in files that still reference removed statuses — those will be fixed in later tasks. --- ### Task 5: Update server checkout — remove `DELIVERY_FEE_ADJUSTMENT` initial status **Files:** - Modify: `server/src/routes/user-orders.js:103-106` - [ ] **Step 1: Write test for checkout status logic** Create `server/src/routes/__tests__/user-orders-checkout.test.js`: ```js import { describe, expect, it, beforeEach, afterEach } from 'vitest' import { prisma } from '../../lib/prisma.js' describe('checkout initial status', () => { beforeEach(async () => { await prisma.cartItem.deleteMany() await prisma.orderItem.deleteMany() await prisma.order.deleteMany() }) afterEach(async () => { await prisma.cartItem.deleteMany() await prisma.orderItem.deleteMany() await prisma.order.deleteMany() }) it('delivery + online → PENDING_PAYMENT with deliveryFeeLocked=false', async () => { // This is an integration-style test checking the status logic // The actual checkout requires full auth setup, so we verify the status assignment logic directly const paymentMethod = 'online' const deliveryType = 'delivery' let initialStatus = 'PENDING_PAYMENT' let deliveryFeeLocked = false if (paymentMethod === 'on_pickup') { initialStatus = 'IN_PROGRESS' } else if (deliveryType === 'delivery') { initialStatus = 'PENDING_PAYMENT' deliveryFeeLocked = false } expect(initialStatus).toBe('PENDING_PAYMENT') expect(deliveryFeeLocked).toBe(false) }) it('pickup + online → PENDING_PAYMENT with deliveryFeeLocked=true', async () => { const paymentMethod = 'online' const deliveryType = 'pickup' let initialStatus = 'PENDING_PAYMENT' let deliveryFeeLocked = true if (paymentMethod === 'on_pickup') { initialStatus = 'IN_PROGRESS' } expect(initialStatus).toBe('PENDING_PAYMENT') expect(deliveryFeeLocked).toBe(true) }) it('pickup + on_pickup → IN_PROGRESS', async () => { const paymentMethod = 'on_pickup' const deliveryType = 'pickup' let initialStatus = 'PENDING_PAYMENT' if (paymentMethod === 'on_pickup') { initialStatus = 'IN_PROGRESS' } expect(initialStatus).toBe('IN_PROGRESS') }) }) ``` - [ ] **Step 2: Run test to verify it passes** ```bash cd server && npm test -- --run src/routes/__tests__/user-orders-checkout.test.js ``` Expected: All tests pass. - [ ] **Step 3: Update checkout logic** In `server/src/routes/user-orders.js`, replace lines 103-106: **Before:** ```js let initialStatus = 'PENDING_PAYMENT' if (paymentMethod === 'on_pickup') initialStatus = 'IN_PROGRESS' else if (deliveryType === 'delivery') initialStatus = 'DELIVERY_FEE_ADJUSTMENT' ``` **After:** ```js let initialStatus = 'PENDING_PAYMENT' let deliveryFeeLocked = true if (paymentMethod === 'on_pickup') { initialStatus = 'IN_PROGRESS' } else if (deliveryType === 'delivery') { initialStatus = 'PENDING_PAYMENT' deliveryFeeLocked = false } ``` - [ ] **Step 4: Add `deliveryFeeLocked` to order creation data** In the same file, find the `order = await tx.order.create({ data: { ... } })` block (around line 123-144). Add `deliveryFeeLocked` to the data object: **Before (line 126):** ```js status: initialStatus, ``` **After:** ```js status: initialStatus, deliveryFeeLocked, ``` - [ ] **Step 5: Run server tests** ```bash cd server && npm test ``` Expected: All tests pass. --- ### Task 6: Update server payment routes — remove `PAYMENT_VERIFICATION` **Files:** - Modify: `server/src/routes/user-payments.js` - [ ] **Step 1: Write updated `user-payments.js`** Replace the entire file: ```js import { prisma } from '../lib/prisma.js' import { escapeHtml } from '../lib/escape-html.js' import { getOtherUploadMaxFileBytes } from '../lib/upload-limits.js' import { saveImageBufferToUploads } from '../lib/upload-images.js' export async function registerUserPaymentRoutes(fastify) { fastify.post( '/api/me/orders/:id/pay', { preHandler: [fastify.authenticate] }, async (request, reply) => { const userId = request.user.sub const { id } = request.params const order = await prisma.order.findFirst({ where: { id, userId } }) if (!order) return reply.code(404).send({ error: 'Заказ не найден' }) const paymentMethod = order.paymentMethod ?? 'online' if (paymentMethod === 'on_pickup') { return reply.code(409).send({ error: 'Для этого заказа оплата при получении — кнопка оплаты не нужна.' }) } if (order.status !== 'PENDING_PAYMENT') { return reply.code(409).send({ error: 'Сейчас нельзя выполнить оплату для этого заказа' }) } if (!request.isMultipart()) { return reply .code(400) .send({ error: 'Отправьте multipart/form-data: поле detail и/или файл receipt' }) } let detail = '' let receiptBuffer = null let receiptFilename = '' try { const otherLimit = getOtherUploadMaxFileBytes() const parts = request.parts({ limits: { fileSize: otherLimit, files: 2, }, }) for await (const part of parts) { if (part.file) { if (part.fieldname === 'receipt') { if (receiptBuffer !== null) { return reply.code(400).send({ error: 'Допускается один файл receipt' }) } receiptBuffer = await part.toBuffer() receiptFilename = part.filename ?? 'receipt' } } else if (part.fieldname === 'detail') { detail = String(part.value ?? '').trim() } } } catch (err) { const msg = err instanceof Error ? err.message : 'Не удалось разобрать форму' return reply.code(400).send({ error: msg }) } const hasDetail = detail.length > 0 const hasReceipt = receiptBuffer !== null && receiptBuffer.length > 0 if (!hasDetail && !hasReceipt) { return reply .code(400) .send({ error: 'Укажите текст о платеже и/или прикрепите изображение чека' }) } const maxDetail = 2000 if (detail.length > maxDetail) { return reply.code(400).send({ error: `Текст не длиннее ${maxDetail} символов` }) } let attachmentUrl = null if (hasReceipt) { try { attachmentUrl = await saveImageBufferToUploads(receiptFilename, receiptBuffer) } catch (err) { const message = err instanceof Error ? err.message : 'Не удалось сохранить файл' const statusCode = err && typeof err === 'object' && 'statusCode' in err && Number.isInteger(err.statusCode) ? Number(err.statusCode) : 400 return reply.code(statusCode).send({ error: message }) } } const bodyHtml = hasDetail ? `

${escapeHtml(detail).replace(/\r\n|\n|\r/g, '
')}

` : '' const messageText = `

Подтверждение оплаты (перевод ВТБ / Сбербанк)

${bodyHtml}` try { await prisma.$transaction(async (tx) => { await tx.orderMessage.create({ data: { orderId: id, authorType: 'user', text: messageText, attachmentUrl, }, }) }) } catch (err) { return reply.code(500).send({ error: 'Не удалось сохранить оплату' }) } return { ok: true, status: 'PENDING_PAYMENT' } }, ) } ``` Key changes: - Removed `DELIVERY_FEE_ADJUSTMENT` check (line 21-28) - Removed `DRAFT → PENDING_PAYMENT` transition (line 31-34) - Removed `PAYMENT_VERIFICATION` check (line 37-39) - Removed `PAYMENT_VERIFICATION` status update (line 112) - Simplified to: only `PENDING_PAYMENT` allowed, stays in `PENDING_PAYMENT` after payment submission - Changed final error message from "Сейчас нельзя выполнить оплату" to be the only guard - [ ] **Step 2: Run server tests** ```bash cd server && npm test ``` Expected: All tests pass. --- ### Task 7: Update server admin order routes **Files:** - Modify: `server/src/routes/api/admin-orders.js` - [ ] **Step 1: Update summary endpoint** In line 11, replace the status filter: **Before:** ```js status: { in: ['DELIVERY_FEE_ADJUSTMENT', 'PENDING_PAYMENT', 'PAYMENT_VERIFICATION'] }, ``` **After:** ```js status: 'PENDING_PAYMENT', ``` - [ ] **Step 2: Update delivery-fee endpoint** Replace the entire `PATCH /api/admin/orders/:id/delivery-fee` handler (lines 115-144): **Before:** ```js fastify.patch( '/api/admin/orders/:id/delivery-fee', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => { const { id } = request.params const feeRaw = request.body?.deliveryFeeCents const parsed = typeof feeRaw === 'string' ? Number.parseInt(feeRaw, 10) : typeof feeRaw === 'number' ? feeRaw : NaN if (!Number.isInteger(parsed) || parsed < 0) { return reply.code(400).send({ error: 'deliveryFeeCents должно быть целым числом ≥ 0 (копейки)' }) } const existing = await prisma.order.findUnique({ where: { id } }) if (!existing) return reply.code(404).send({ error: 'Заказ не найден' }) if (existing.status !== 'DELIVERY_FEE_ADJUSTMENT') { return reply.code(409).send({ error: 'Корректировка доставки доступна только в статусе DELIVERY_FEE_ADJUSTMENT' }) } const totalCents = existing.itemsSubtotalCents + parsed const updated = await prisma.order.update({ where: { id }, data: { deliveryFeeCents: parsed, totalCents, status: 'PENDING_PAYMENT', }, }) return { item: updated } }, ) ``` **After:** ```js fastify.patch( '/api/admin/orders/:id/delivery-fee', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => { const { id } = request.params const feeRaw = request.body?.deliveryFeeCents const parsed = typeof feeRaw === 'string' ? Number.parseInt(feeRaw, 10) : typeof feeRaw === 'number' ? feeRaw : NaN if (!Number.isInteger(parsed) || parsed < 0) { return reply.code(400).send({ error: 'deliveryFeeCents должно быть целым числом ≥ 0 (копейки)' }) } const existing = await prisma.order.findUnique({ where: { id } }) if (!existing) return reply.code(404).send({ error: 'Заказ не найден' }) if (existing.status !== 'PENDING_PAYMENT' || existing.deliveryFeeLocked !== false) { return reply.code(409).send({ error: 'Корректировка доставки доступна только пока стоимость не утверждена' }) } const totalCents = existing.itemsSubtotalCents + parsed const updated = await prisma.order.update({ where: { id }, data: { deliveryFeeCents: parsed, totalCents, deliveryFeeLocked: true, }, }) return { item: updated } }, ) ``` - [ ] **Step 3: Run server tests** ```bash cd server && npm test ``` Expected: All tests pass. --- ### Task 8: Update client admin orders page **Files:** - Modify: `client/src/pages/admin-orders/ui/AdminOrdersPage.tsx` - [ ] **Step 1: Replace `DELIVERY_FEE_ADJUSTMENT` UI blocks** Find and replace lines 321-334: **Before:** ```tsx {detail.status === 'DELIVERY_FEE_ADJUSTMENT' && ( Укажите итоговую стоимость доставки (₽). После сохранения заказ получит статус « {orderStatusLabelRu('PENDING_PAYMENT')}», и клиент сможет оплатить с учётом этой суммы. )} {detail.status === 'DELIVERY_FEE_ADJUSTMENT' && ( )} ``` **After:** ```tsx {detail.status === 'PENDING_PAYMENT' && detail.deliveryFeeLocked === false && ( Укажите итоговую стоимость доставки (₽). После сохранения клиент сможет оплатить заказ с учётом этой суммы. )} {detail.status === 'PENDING_PAYMENT' && detail.deliveryFeeLocked === false && ( )} ``` - [ ] **Step 2: Add `deliveryFeeLocked` to the detail type** The `AdminOrderDetailResponse` type in `client/src/entities/order/api/admin-order-api.ts` needs `deliveryFeeLocked`. Add it to the type definition (after line 32): **Before:** ```ts deliveryFeeCents: number ``` **After:** ```ts deliveryFeeCents: number deliveryFeeLocked: boolean ``` - [ ] **Step 3: Run client typecheck** ```bash cd client && npx tsc -b ``` Expected: No errors. --- ### Task 9: Update client payment section **Files:** - Modify: `client/src/features/order-payment/ui/OrderPaymentSection.tsx` - [ ] **Step 1: Write updated component** Replace the entire file: ```tsx import { useState } from 'react' import Box from '@mui/material/Box' import Button from '@mui/material/Button' import Typography from '@mui/material/Typography' import { orderStatusLabelRu } from '@/shared/lib/order-status-labels' import { PaymentDialog } from './PaymentDialog' type Props = { status: string deliveryFeeLocked: boolean paymentMethod: string | null totalCents: number isPayPending: boolean payError: unknown onPay: (params: { detail: string; receiptFile: File | null }) => void } export function OrderPaymentSection({ status, deliveryFeeLocked, paymentMethod, isPayPending, payError, onPay }: Props) { const payOnPickup = (paymentMethod ?? 'online') === 'on_pickup' const [payModalOpen, setPayModalOpen] = useState(false) if (payOnPickup) { return ( Оплата Оплата при получении на точке самовывоза (наличные или карта — по договорённости). ) } return ( Оплата {status === 'PENDING_PAYMENT' && deliveryFeeLocked === false && ( Точную стоимость доставки уточняет администратор. Оплата станет доступна после утверждения стоимости. )} {status === 'PENDING_PAYMENT' && deliveryFeeLocked === true && ( <> После перевода подтвердите оплату — откроется форма для комментария и фото чека. Заказ получит статус « {orderStatusLabelRu('PAID')}». )} {status !== 'PENDING_PAYMENT' && ( На этом этапе действий по оплате в этом блоке не требуется. )} setPayModalOpen(false)} onSubmit={(params) => { onPay(params) setPayModalOpen(false) }} /> ) } ``` Key changes: - Added `deliveryFeeLocked` to Props type - Replaced `DELIVERY_FEE_ADJUSTMENT` check with `PENDING_PAYMENT && deliveryFeeLocked === false` - Replaced `PENDING_PAYMENT` check with `PENDING_PAYMENT && deliveryFeeLocked === true` - Removed `PAYMENT_VERIFICATION` block - Changed "PAYMENT_VERIFICATION" label to "PAID" in the message - [ ] **Step 2: Update callers of `OrderPaymentSection`** Find where `OrderPaymentSection` is used (in `OrderDetailPage.tsx`) and add `deliveryFeeLocked` prop. In `client/src/pages/me/ui/sections/OrderDetailPage.tsx`, find the `OrderPaymentSection` usage and add: ```tsx deliveryFeeLocked={order.deliveryFeeLocked} ``` Also update the `OrderDetailResponse` type in `client/src/entities/order/api/order-api.ts` to include `deliveryFeeLocked`: Add after line 26: ```ts deliveryFeeLocked: boolean ``` - [ ] **Step 3: Run client typecheck and lint** ```bash cd client && npm run lint && npx tsc -b ``` Expected: No errors. --- ### Task 10: Update client admin order API **Files:** - Modify: `client/src/entities/order/api/admin-order-api.ts` - [ ] **Step 1: Add `deliveryFeeLocked` to type** Add to `AdminOrderDetailResponse.item` (after `deliveryFeeCents`): ```ts deliveryFeeCents: number deliveryFeeLocked: boolean ``` - [ ] **Step 2: Run client typecheck** ```bash cd client && npx tsc -b ``` Expected: No errors. --- ### Task 11: Run full verification **Files:** All - [ ] **Step 1: Run server tests** ```bash cd server && npm test ``` Expected: All tests pass. - [ ] **Step 2: Run client lint** ```bash cd client && npm run lint ``` Expected: No errors. - [ ] **Step 3: Run client format check** ```bash cd client && npm run format:check ``` Expected: No errors. - [ ] **Step 4: Run client typecheck** ```bash cd client && npx tsc -b ``` Expected: No errors. - [ ] **Step 5: Run client build** ```bash cd client && npm run build ``` Expected: Build succeeds. - [ ] **Step 6: Commit all changes** ```bash git add . git commit -m "refactor: simplify order status model — remove DELIVERY_FEE_ADJUSTMENT and PAYMENT_VERIFICATION - Add deliveryFeeLocked field to Order model - Remove DELIVERY_FEE_ADJUSTMENT and PAYMENT_VERIFICATION statuses - Update checkout to use PENDING_PAYMENT + deliveryFeeLocked - Update payment flow to stay in PENDING_PAYMENT until admin confirms - Update admin UI to use deliveryFeeLocked instead of status check - Migrate existing orders to new status model" ``` --- ## Self-Review **1. Spec coverage:** - ✅ Remove `DELIVERY_FEE_ADJUSTMENT` — Tasks 2, 3, 4, 5, 6, 7, 8, 9, 11 - ✅ Remove `PAYMENT_VERIFICATION` — Tasks 2, 3, 4, 6, 9, 11 - ✅ Add `deliveryFeeLocked` field — Tasks 1, 5, 7, 8, 9, 10 - ✅ Update checkout logic — Task 5 - ✅ Update payment flow — Task 6 - ✅ Update admin routes — Task 7 - ✅ Update admin UI — Task 8 - ✅ Update client payment UI — Task 9 - ✅ Migrate existing data — Task 11 **2. Placeholder scan:** No TBD, TODO, or incomplete sections. All steps contain actual code. **3. Type consistency:** `deliveryFeeLocked: boolean` used consistently across all files. `AdminOrderDetailResponse` and `OrderDetailResponse` both updated. All status references use the new 8-status list.