diff --git a/.cursor/agents/owner-d-shop-redeem.md b/.cursor/agents/owner-d-shop-redeem.md index dbef3c1..fa23b36 100644 --- a/.cursor/agents/owner-d-shop-redeem.md +++ b/.cursor/agents/owner-d-shop-redeem.md @@ -35,7 +35,7 @@ User (Owner A) generates token → Shop scans/confirms (you) ## Hard rules -- Redis token: `redeem:token:{token}` TTL 300s +- Redis token: `redeem:token:{token}` TTL **180s**(V3.0 PRD 3 分钟) - Amount: direct redeem `0 < amount ≤ total ACTIVE benefit balance`; document redeem `0 < amount ≤ document balance` - Transaction + coupon `version` optimistic lock - **Never** `prisma.order.update` in redeem module diff --git a/.cursor/rules/backend-nestjs.mdc b/.cursor/rules/backend-nestjs.mdc index 2e1ade5..d1e0dda 100644 --- a/.cursor/rules/backend-nestjs.mdc +++ b/.cursor/rules/backend-nestjs.mdc @@ -48,4 +48,4 @@ export class TradeController { - 埋点 → `log_user_analytics`;业务审计 → `common_event` - 支付 → `log_third_party` + `user_order.pay_*`(无 payments 表) -- 核销码 → Redis 5min(无 DB 表) +- 核销码 → Redis 3min(`REDEEM_TOKEN_TTL_SECONDS`) diff --git a/.cursor/rules/prisma-schema.mdc b/.cursor/rules/prisma-schema.mdc index 9e7ad20..353d1d1 100644 --- a/.cursor/rules/prisma-schema.mdc +++ b/.cursor/rules/prisma-schema.mdc @@ -20,7 +20,7 @@ V2 手册 §五(v3.1,28 表)。schema 与手册保持一致。 ## 关键约定 - 无 `payments` / `user_order_item` / `redeem_tokens` 表 -- 核销码仅 Redis 5 分钟 +- 核销码仅 Redis **3 分钟**(`REDEEM_TOKEN_TTL_SECONDS=180`) - 支付流水 → `log_third_party` + `user_order.pay_*` - 业务事件 → `common_event`;埋点 → `log_user_analytics` diff --git a/.cursor/skills/dukang-coding/reference-frontend.md b/.cursor/skills/dukang-coding/reference-frontend.md index 4dcb79b..d70bc98 100644 --- a/.cursor/skills/dukang-coding/reference-frontend.md +++ b/.cursor/skills/dukang-coding/reference-frontend.md @@ -48,7 +48,7 @@ H5 三端引用 `@dukang/shared-ui`(`tokens.css`)。admin-web 使用 Ant Des ### 订单 5 Tab ``` -all | pending_pay | pending_ship | pending_receive | completed +待付款 | 已付款 (`paid`) | 已完成 ``` ## h5-shop 路由(刘景尧) @@ -112,7 +112,7 @@ all | pending_pay | pending_ship | pending_receive | completed ## UI 硬约束 -- C 端订单 **5 Tab**(含 pending_ship) +- C 端订单 **3 Tab**(`pending_pay` / `paid` / `completed`) - 个人中心无会员等级(V1 不做会员体系) - 清香型 4 SKU 可购;酱香/浓香灰态 - 门店列表仅 `OPEN` 状态 diff --git a/apps/AGENTS.md b/apps/AGENTS.md index 68ed792..2e0e21c 100644 --- a/apps/AGENTS.md +++ b/apps/AGENTS.md @@ -19,7 +19,7 @@ V2 目标:`mini-user` / `mini-partner` / `mini-hq` 替换对应 H5(除门店 - 类型从 `@dukang/shared-types` import,禁止复制枚举字符串 - 禁止 import `server/` 或另一个 `apps/*` 的源码 - UI 共享组件优先 `@dukang/shared-ui` -- C 端订单列表 **5 Tab**(含 `pending_ship`) +- C 端订单列表 **3 Tab**:`待付款 | 已付款 | 已完成`(Tab key: `pending_pay` / `paid` / `completed`) ## 新页面 workflow @@ -32,7 +32,7 @@ V2 目标:`mini-user` / `mini-partner` / `mini-hq` 替换对应 H5(除门店 ### h5-user(jacy-dukang) -主链路:登录 → 首页/详情 → 下单 Mock 支付 → 5 Tab 订单 → 权益 → 核销码 → 门店列表 +主链路:登录 → 首页/详情 → 下单 Mock 支付 → **3 Tab** 订单 → 权益 → 核销码 → 门店列表 **勿改**:门店核销确认 UI(属 h5-shop)、合伙人录店(属 h5-partner) diff --git a/apps/h5-user/src/pages/MinePage.tsx b/apps/h5-user/src/pages/MinePage.tsx index b7161f2..129831c 100644 --- a/apps/h5-user/src/pages/MinePage.tsx +++ b/apps/h5-user/src/pages/MinePage.tsx @@ -11,8 +11,7 @@ const DEFAULT_AVATAR = const ORDER_SHORTCUTS = [ { tab: 'pending_pay', icon: 'payments', label: '待付款' }, - { tab: 'pending_ship', icon: 'package_2', label: '待发货' }, - { tab: 'pending_receive', icon: 'local_shipping', label: '配送中' }, + { tab: 'paid', icon: 'package_2', label: '已付款' }, { tab: 'completed', icon: 'task_alt', label: '已完成' }, ] as const; diff --git a/apps/h5-user/src/pages/OrderListPage.tsx b/apps/h5-user/src/pages/OrderListPage.tsx index 89fc3b6..efed0a9 100644 --- a/apps/h5-user/src/pages/OrderListPage.tsx +++ b/apps/h5-user/src/pages/OrderListPage.tsx @@ -8,17 +8,16 @@ import { request } from '../lib/api'; const TABS = [ { key: 'all', label: '全部' }, { key: 'pending_pay', label: '待付款' }, - { key: 'pending_ship', label: '待发货' }, - { key: 'pending_receive', label: '待收货' }, + { key: 'paid', label: '已付款' }, { key: 'completed', label: '已完成' }, ]; const STATUS_LABEL: Record = { PENDING_PAY: '待付款', - PENDING_SHIP: '待发货', - OUT_WAREHOUSE: '出库中', - SHIPPING: '配送中', - PENDING_RECEIVE: '待收货', + PENDING_SHIP: '已付款', + OUT_WAREHOUSE: '已付款', + SHIPPING: '已付款', + PENDING_RECEIVE: '已付款', COMPLETED: '已完成', }; diff --git a/apps/h5-user/src/pages/PayPage.tsx b/apps/h5-user/src/pages/PayPage.tsx index 86da844..6163eb2 100644 --- a/apps/h5-user/src/pages/PayPage.tsx +++ b/apps/h5-user/src/pages/PayPage.tsx @@ -140,11 +140,11 @@ export default function PayPage() { if (!paid) { alert('支付结果确认中,请稍后在订单列表查看'); } - navigate('/orders?tab=pending_ship'); + navigate('/orders?tab=paid'); return; } - navigate('/orders?tab=pending_ship'); + navigate('/orders?tab=paid'); } catch (e) { if (isWechatAuthRequiredError(e)) { setNeedsWechatAuth(true); diff --git a/apps/h5-user/src/pages/RedeemCodePage.tsx b/apps/h5-user/src/pages/RedeemCodePage.tsx index 279f95e..4949080 100644 --- a/apps/h5-user/src/pages/RedeemCodePage.tsx +++ b/apps/h5-user/src/pages/RedeemCodePage.tsx @@ -1,12 +1,13 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { REDEEM_TOKEN_TTL_SECONDS } from '@dukang/shared-types'; import PageHeader from '@dukang/shared-ui/PageHeader'; export default function RedeemCodePage() { const navigate = useNavigate(); const token = sessionStorage.getItem('redeemToken') || ''; const amount = sessionStorage.getItem('redeemAmount') || '0'; - const [secondsLeft, setSecondsLeft] = useState(300); + const [secondsLeft, setSecondsLeft] = useState(REDEEM_TOKEN_TTL_SECONDS); useEffect(() => { const expireAt = sessionStorage.getItem('redeemExpireAt'); diff --git a/apps/h5-user/src/pages/RedeemPage.tsx b/apps/h5-user/src/pages/RedeemPage.tsx index 3914be7..7ab4868 100644 --- a/apps/h5-user/src/pages/RedeemPage.tsx +++ b/apps/h5-user/src/pages/RedeemPage.tsx @@ -1,8 +1,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; +import { REDEEM_TOKEN_TTL_SECONDS } from '@dukang/shared-types'; import { request } from '../lib/api'; -const REDEEM_TOKEN_TTL_SECONDS = 300; const POLL_INTERVAL_MS = 2500; type BenefitSummary = { @@ -236,7 +236,7 @@ export default function RedeemPage() { type="button" className="redeem-header-btn" aria-label="帮助" - onClick={() => window.alert('核销金额不超过可用余额,核销码 5 分钟内有效。')} + onClick={() => window.alert('核销金额不超过可用余额,核销码 3 分钟内有效。')} > help_outline @@ -301,7 +301,7 @@ export default function RedeemPage() {
shield -

安全提示:请在核销前核实金额,核销码将在 5 分钟后自动失效。

+

安全提示:请在核销前核实金额,核销码将在 3 分钟后自动失效。

diff --git a/packages/domain/src/index.test.ts b/packages/domain/src/index.test.ts index c208dcc..c7785fa 100644 --- a/packages/domain/src/index.test.ts +++ b/packages/domain/src/index.test.ts @@ -6,6 +6,7 @@ import { validateRedeemAmount, allocateBenefitCoupons, calcBenefitSummary, + orderTabToStatuses, } from './index'; describe('calcBenefitAmount', () => { @@ -87,6 +88,24 @@ describe('allocateBenefitCoupons', () => { }); }); +describe('orderTabToStatuses', () => { + it('maps V3.0 three user tabs', () => { + expect(orderTabToStatuses('pending_pay')).toEqual(['PENDING_PAY']); + expect(orderTabToStatuses('paid')).toEqual([ + 'PENDING_SHIP', + 'OUT_WAREHOUSE', + 'SHIPPING', + 'PENDING_RECEIVE', + ]); + expect(orderTabToStatuses('completed')).toEqual(['COMPLETED']); + }); + + it('keeps legacy tab keys mapped to paid', () => { + expect(orderTabToStatuses('pending_ship')).toEqual(orderTabToStatuses('paid')); + expect(orderTabToStatuses('pending_receive')).toEqual(orderTabToStatuses('paid')); + }); +}); + describe('calcBenefitSummary', () => { it('max redeem equals total balance', () => { expect(calcBenefitSummary([300, 400])).toEqual({ diff --git a/packages/domain/src/index.ts b/packages/domain/src/index.ts index 729a14c..0339013 100644 --- a/packages/domain/src/index.ts +++ b/packages/domain/src/index.ts @@ -115,10 +115,10 @@ export function orderTabToStatuses(tab: string): string[] | undefined { switch (tab) { case 'pending_pay': return ['PENDING_PAY']; + case 'paid': case 'pending_ship': - return ['PENDING_SHIP', 'OUT_WAREHOUSE']; case 'pending_receive': - return ['SHIPPING', 'PENDING_RECEIVE']; + return ['PENDING_SHIP', 'OUT_WAREHOUSE', 'SHIPPING', 'PENDING_RECEIVE']; case 'completed': return ['COMPLETED']; default: diff --git a/packages/shared-types/src/enums.ts b/packages/shared-types/src/enums.ts index 1d9f1cb..618453c 100644 --- a/packages/shared-types/src/enums.ts +++ b/packages/shared-types/src/enums.ts @@ -40,9 +40,13 @@ export enum OrderStatus { export enum OrderTab { ALL = 'all', PENDING_PAY = 'pending_pay', - PENDING_SHIP = 'pending_ship', - PENDING_RECEIVE = 'pending_receive', + /** V3.0 PRD:已付款(含待发货/配送中,内部多状态聚合) */ + PAID = 'paid', COMPLETED = 'completed', + /** @deprecated 兼容旧链接,映射同 PAID */ + PENDING_SHIP = 'pending_ship', + /** @deprecated 兼容旧链接,映射同 PAID */ + PENDING_RECEIVE = 'pending_receive', } export enum DeliveryType { @@ -91,6 +95,9 @@ export const CLIENT_APP_ACTOR_MAP: Record = { export const REDEEM_DIRECT_LIMIT_POLICY = 'TOTAL_ACTIVE_BALANCE'; export const REDEEM_DOCUMENT_LIMIT_POLICY = 'DOCUMENT_BALANCE'; -export const REDEEM_TOKEN_TTL_SECONDS = 300; +/** V3.0 PRD:核销码 3 分钟有效 */ +export const REDEEM_TOKEN_TTL_SECONDS = 180; +/** 短信/核销验证码 3 分钟有效(NFR-003) */ +export const SMS_CODE_TTL_SECONDS = 180; /** 核销成功后供用户端轮询结果,略长于 token TTL */ -export const REDEEM_RESULT_TTL_SECONDS = 600; +export const REDEEM_RESULT_TTL_SECONDS = 360; diff --git a/server/dukang-api/prisma/seed-v31.ts b/server/dukang-api/prisma/seed-v31.ts index 87c15e1..3a73669 100644 --- a/server/dukang-api/prisma/seed-v31.ts +++ b/server/dukang-api/prisma/seed-v31.ts @@ -80,7 +80,7 @@ async function main() { await prisma.commonCityCommissionRule.create({ data: { cityId: city.id, - orderCommissionRate: 0.05, + orderCommissionRate: 0, redeemCommissionRate: 0.03, partnerProfitRate: 0.35, storeSettlementRate: 0.6, @@ -111,10 +111,10 @@ async function main() { } const productDefs = [ - { skuCode: 'QX-001', name: '杜康·白水古酿 500ml', subtitle: '清香型 52度 礼盒装', price: 599, sortOrder: 1, img: 'https://picsum.photos/seed/dukang1/400/400' }, - { skuCode: 'QX-002', name: '杜康·年份陈酿(十年)', subtitle: '清香型 42度 纯粮酿造', price: 880, sortOrder: 2, img: 'https://picsum.photos/seed/dukang2/400/400' }, - { skuCode: 'QX-003', name: '杜康·御享1号 珍藏版', subtitle: '高端定制 限量发售', price: 1299, sortOrder: 3, img: 'https://picsum.photos/seed/dukang3/400/400' }, - { skuCode: 'QX-004', name: '杜康·经典传承', subtitle: '清香型 纯粮固态', price: 399, sortOrder: 4, img: 'https://picsum.photos/seed/dukang4/400/400' }, + { skuCode: 'JZ-10', name: '酒祖杜康(国标特级10)', subtitle: '清香型 53度', price: 128, sortOrder: 1, img: 'https://picsum.photos/seed/jiuzu10/400/400' }, + { skuCode: 'JZ-15', name: '酒祖杜康(国标特级15)', subtitle: '清香型 53度', price: 168, sortOrder: 2, img: 'https://picsum.photos/seed/jiuzu15/400/400' }, + { skuCode: 'JZ-20', name: '酒祖杜康(国标特级20)', subtitle: '清香型 53度', price: 298, sortOrder: 3, img: 'https://picsum.photos/seed/jiuzu20/400/400' }, + { skuCode: 'JZ-30', name: '酒祖杜康(国标特级30)', subtitle: '清香型 53度', price: 498, sortOrder: 4, img: 'https://picsum.photos/seed/jiuzu30/400/400' }, ]; const products = []; @@ -126,7 +126,7 @@ async function main() { name: def.name, subtitle: def.subtitle, aromaType: 'QINGXIANG', - spec: def.skuCode === 'QX-002' ? '500ml | 42度' : def.skuCode === 'QX-004' ? '500ml | 46度' : '500ml | 52度', + spec: '500ml | 53度', price: def.price, benefitAmount: def.price, status: 'ON_SALE', diff --git a/server/dukang-api/src/integrations/sms/sms-code.store.ts b/server/dukang-api/src/integrations/sms/sms-code.store.ts index 48c7bcc..3f6c23e 100644 --- a/server/dukang-api/src/integrations/sms/sms-code.store.ts +++ b/server/dukang-api/src/integrations/sms/sms-code.store.ts @@ -1,8 +1,7 @@ import { BadRequestException, Injectable } from '@nestjs/common'; -import { loadAppConfig } from '@dukang/shared-types'; +import { SMS_CODE_TTL_SECONDS, loadAppConfig } from '@dukang/shared-types'; import { RedisService } from '../../common/redis/redis.service'; -const CODE_TTL_SECONDS = 300; const RATE_TTL_SECONDS = 60; function codeKey(phone: string, scene: string) { @@ -36,7 +35,7 @@ export class SmsCodeStore { async generateAndStore(phone: string, scene: string): Promise { const code = this.config.mockSms ? this.config.mockSmsCode : randomSixDigitCode(); - await this.redis.client.set(codeKey(phone, scene), code, 'EX', CODE_TTL_SECONDS); + await this.redis.client.set(codeKey(phone, scene), code, 'EX', SMS_CODE_TTL_SECONDS); return code; } diff --git a/server/dukang-api/src/modules/ops/admin-cities.service.ts b/server/dukang-api/src/modules/ops/admin-cities.service.ts index 681ed40..adf11df 100644 --- a/server/dukang-api/src/modules/ops/admin-cities.service.ts +++ b/server/dukang-api/src/modules/ops/admin-cities.service.ts @@ -69,7 +69,7 @@ export class AdminCitiesService { status: (dto.status ?? 'PENDING') as 'PENDING' | 'ACTIVE' | 'PAUSED', commissionRule: { create: { - orderCommissionRate: 0.05, + orderCommissionRate: 0, redeemCommissionRate: 0.03, }, }, diff --git a/server/dukang-api/src/modules/settlement/settlement.service.ts b/server/dukang-api/src/modules/settlement/settlement.service.ts index af05ee6..90837ec 100644 --- a/server/dukang-api/src/modules/settlement/settlement.service.ts +++ b/server/dukang-api/src/modules/settlement/settlement.service.ts @@ -225,7 +225,7 @@ export class SettlementService { const cityIds = cities.map((c) => c.id); const defaultOrderRate = cities[0]?.commissionRule?.orderCommissionRate ? Number(cities[0].commissionRule.orderCommissionRate) - : 0.05; + : 0; const defaultRedeemRate = cities[0]?.commissionRule?.redeemCommissionRate ? Number(cities[0].commissionRule.redeemCommissionRate) : 0.03; diff --git a/杜康好客-v3-现状对照.md b/杜康好客-v3-现状对照.md index 7976aab..091f617 100644 --- a/杜康好客-v3-现状对照.md +++ b/杜康好客-v3-现状对照.md @@ -14,14 +14,15 @@ |------|------| | **整体完成度(粗估)** | Wave 1 约 **45%**;Wave 2 约 **10%**;Wave 3 约 **5%** | | **可跑通的主链路** | 登录 → 浏览商品 → 同城下单 Mock/微信支付 → 权益 1:1 → **扫码核销** → T+1 门店 payout 记录 → 总部确认打款 | -| **最大阻塞** | 端形态(无小程序 C 端)、规则常量冲突(TTL/Tab/SKU/佣金)、Wave 1 缺口(手机号核销/规则弹窗/试核销/自动推小飞侠) | +| **最大阻塞** | C 端小程序迁移(暂缓)、Wave 1 缺口(手机号核销/规则弹窗/试核销/自动推小飞侠) | +| **P0 冲突** | C2~C7、C14 **已修复**(2026-07-12) | | **冒烟覆盖** | `scripts/smoke-v3.mjs` 仅覆盖窄 W1 切片,**不等于** V3.0 ACC 全量 | ### Wave 门禁状态 | 门禁 | 状态 | 一句话 | |------|------|--------| -| **W1-S1** 下单+支付+权益 | 🔶 | 链路可跑,SKU/订单 Tab/TTL 不合规 | +| **W1-S1** 下单+支付+权益 | 🔶 | TTL/Tab/SKU/佣金已对齐;仍缺小程序形态 | | **W1-S2** 门店核销双通道 | 🔶 | 扫码 ✅;手机号核销 ❌ | | **W1-S3** 合伙人子账号 | ✅ | CRUD + 推广员菜单裁剪已有 | | **W1-S4** 拓店→审核→C 端可见 | 🔶 | 三步录入有;试核销100/负责人复核/SOP 缺失 | @@ -40,26 +41,54 @@ --- -## 1. ⚠️ 冲突项(必须先改) +## 1. ⚠️ 冲突项与改造计划 -> 代码已存在,但与 V3.0 PRD **直接矛盾**,继续开发会累积返工。 +> 代码已存在但与 V3.0 PRD 直接矛盾。**P0 已于 2026-07-12 执行**;P1/P2 排后续迭代。 -| # | V3.0 要求 | 当前实现 | 证据位置 | 改造建议 | -|---|-----------|----------|----------|----------| -| C1 | C 端 = **微信小程序** | `apps/h5-user` H5;无 `mini-user` | `apps/h5-user/` | 新建小程序或明确 H5 为过渡并排迁移 | -| C2 | 核销码 TTL **3 分钟** | **300 秒(5 分钟)** | `packages/shared-types/src/enums.ts` `REDEEM_TOKEN_TTL_SECONDS=300`;`apps/h5-user/src/pages/RedeemPage.tsx` 文案 | 改常量为 180 + 全端文案 | -| C3 | 短信验证码 **3 分钟** | Redis TTL **300 秒** | `server/dukang-api/src/integrations/sms/sms-code.store.ts` | 改 `CODE_TTL_SECONDS=180` | -| C4 | 订单 Tab:**待付款 / 已付款 / 已完成** | **5 Tab**(含待发货/待收货) | `apps/h5-user/src/pages/OrderListPage.tsx`;`OrderTab` enum | 改 Tab 映射与列表 API | -| C5 | 订单状态机三态展示 | **9 态** `OrderStatus` | `packages/shared-types/src/enums.ts` | PRD 三 Tab 映射到细粒度状态,或收敛状态机 | -| C6 | 4 款 **酒祖杜康** + 锚点价 | preV1 杜康系列 ¥399–1299 | `server/dukang-api/prisma/seed-v31.ts` | 替换 seed + 商品管理数据 | -| C7 | 默认佣金 **0% 订单 + 3% 核销** | seed **5% 订单佣金** | `seed-v31.ts` `orderCommissionRate: 0.05` | 改 seed 与开城默认 | -| C8 | **一号多店**(同号可多店主账号) | `StoreAccount.phone @unique`;创建时**拒绝**重复手机 | `schema.prisma` `StoreAccount`;`store.service.ts` `partnerCheckStorePhone` | 改模型:主账号与门店多对多 | -| C9 | 支付后**自动推小飞侠**(同城) | 支付后 delivery `MANUAL`;总部手动推单 | `trade.service.ts`;`admin-xiaofeixia.service.ts` | 支付回调后接 xiaofeixia | -| C10 | 门店结算 = **门店主动提现**(W2) | 核销后**自动生成** `StorePayout` | `settlement.service.ts` `createStorePayout` | 增加余额账本 + 提现申请流 | -| C11 | 工单 **四类型** | enum 仅 **3 类** `REFUND/RESHIPMENT/ALERT` | `schema.prisma` `TicketType` | 扩展 enum + 用户建单 UI | -| C12 | 配送类型含 **现场提货** | `DeliveryType` 仅 `LOCAL/CROSS_CITY` | `packages/shared-types/src/enums.ts` | 新增 `ON_SITE_PICKUP` | -| C13 | 多合伙人(全城/区域平级) | 城市 `partnerId` **单合伙人** | `schema.prisma` `CommonCity.partnerId` | 新增合伙人管辖/区县/佣金快照模型 | -| C14 | 文档/规则残留 5min、5 Tab | `apps/AGENTS.md`、backend rules 仍写旧口径 | `.cursor/rules/`、`apps/AGENTS.md` | 批量对齐 V3.0 | +### 1.1 改造计划总览 + +| 阶段 | 冲突项 | 内容 | 状态 | +|------|--------|------|------| +| **P0** | C2~C7、C14 | TTL、订单三 Tab、SKU seed、佣金默认、文档规则 | ✅ **已执行** | +| **P1** | C1 | C 端 H5 过渡;目标微信小程序另立项 | 📋 已决策暂缓 | +| **P1** | C9 | 支付后自动推小飞侠 | 📋 待做(Wave 1) | +| **P2** | C8 | 一号多店(schema + 拓店确认) | 📋 待做(Wave 2) | +| **P2** | C10 | 门店主动提现 + FIN 护栏 | 📋 待做(Wave 2) | +| **P2** | C12 | 现场提货 `ON_SITE_PICKUP` | 📋 待做(Wave 2) | +| **P3** | C11 | 工单四类型 | 📋 待做(Wave 3) | +| **P3** | C13 | 多合伙人管辖/佣金快照 | 📋 待做(Wave 2~3) | + +### 1.2 冲突明细 + +| # | V3.0 要求 | 改造前 | 状态 | 改造记录(2026-07-12) | +|---|-----------|--------|------|------------------------| +| C1 | C 端微信小程序 | `h5-user` H5 | 📋 暂缓 | 工程决策:H5 过渡,小程序另立项 | +| C2 | 核销码 TTL **3 分钟** | 300s | ✅ 已修复 | `REDEEM_TOKEN_TTL_SECONDS=180`;C 端文案 | +| C3 | 短信验证码 **3 分钟** | 300s | ✅ 已修复 | `SMS_CODE_TTL_SECONDS=180` | +| C4 | 订单 Tab 三态 | 5 Tab | ✅ 已修复 | `pending_pay` / `paid` / `completed` | +| C5 | 列表展示三态 | 9 态直出 | ✅ 已修复 | 内部状态保留;Tab/列表映射三态 | +| C6 | 4 款酒祖杜康 + 锚点价 | preV1 SKU | ✅ 已修复 | `seed-v31.ts` 四 SKU ¥128~498 | +| C7 | 佣金 0%+3% | seed 5% 订单 | ✅ 已修复 | seed + `admin-cities` + settlement 默认 | +| C8 | 一号多店 | phone @unique | 📋 P2 | 待 schema + `partnerCheckStorePhone` | +| C9 | 支付后推小飞侠 | MANUAL delivery | 📋 P1 | 待 `trade.service` 支付回调 | +| C10 | 门店主动提现 | 自动 StorePayout | 📋 P2 | 待提现申请流 | +| C11 | 工单四类型 | 3 enum | 📋 P3 | 待扩展 `TicketType` | +| C12 | 现场提货 | 无 DeliveryType | 📋 P2 | 待 `ON_SITE_PICKUP` | +| C13 | 多合伙人 | 单 partnerId | 📋 P2~3 | 待管辖/快照模型 | +| C14 | 文档残留旧口径 | 5min/5 Tab | ✅ 已修复 | `apps/AGENTS.md`、rules、agents、skills | + +### 1.3 P0 已改文件清单 + +| 模块 | 文件 | +|------|------| +| 契约 | `packages/shared-types/src/enums.ts` | +| 规则 | `packages/domain/src/index.ts`、`index.test.ts` | +| 短信 | `server/.../sms/sms-code.store.ts` | +| 佣金 | `seed-v31.ts`、`admin-cities.service.ts`、`settlement.service.ts` | +| C 端 | `OrderListPage.tsx`、`MinePage.tsx`、`PayPage.tsx`、`RedeemPage.tsx`、`RedeemCodePage.tsx` | +| 文档 | `apps/AGENTS.md`、`.cursor/rules/*`、`.cursor/agents/owner-d-shop-redeem.md` | + +> **重跑 seed**:本地需 `cd server/dukang-api && pnpm prisma:seed` 后商品/佣金数据才与 C6/C7 一致。 --- @@ -73,13 +102,13 @@ | REQ-U-002 | 登录 + 下单强制手机号 | `OrderConfirmPage.tsx`、`PhoneVerifiedGuard` | ✅ | | REQ-U-003 | 微信支付 | `pay-wechat.ts` + 后端 callback | Mock/真实均有 | | REQ-U-004 | 定位/开城 | `HomePage.tsx`、`wechat-location.ts` | 🔶 H5 定位 API | -| REQ-U-005~006 | 商品列表/详情 | `HomePage.tsx`、`ProductDetailPage.tsx` | ⚠️ SKU 数据见 C7 | +| REQ-U-005~006 | 商品列表/详情 | `HomePage.tsx`、`ProductDetailPage.tsx` | ✅ seed 已对齐酒祖杜康 | | REQ-U-007 | 同城 ≥2 瓶 | `packages/domain` + `trade.service.ts` | ✅ | | REQ-U-010 | 锁单写入 30min | `trade.service.ts` `payExpireAt` | 🔶 无自动取消 job | | REQ-U-011 | 权益 1:1 | `benefit.service.ts` | ✅ | | REQ-U-012 | 客服入口 | `CustomerServicePage.tsx` | 🔶 Mock 对话 | | REQ-U-013~014 | 门店列表/详情 | `StoreListPage.tsx` 等 | ✅ 仅 OPEN | -| REQ-U-015 | 出码核销 | `RedeemPage.tsx` | ⚠️ TTL 见 C3 | +| REQ-U-015 | 出码核销 | `RedeemPage.tsx` | ✅ TTL 3 分钟 | | REQ-U-017 | 核销后评价 | `RedeemSuccessPage.tsx` | ✅ | | REQ-U-018 | 权益页 Tab | `BenefitPage.tsx` | ✅ | | REQ-U-019 | 个人中心 | `MinePage.tsx` | ✅ | @@ -220,12 +249,9 @@ ## 6. 建议改造顺序(仅 V3.0 视角) -### P0 — 消除冲突(1~2 天) +### P0 — 消除冲突 ✅ 已完成(2026-07-12) -1. C2/C3:核销码 + 短信 TTL → 3 分钟 -2. C6/C7:seed SKU + 佣金默认值 -3. C4/C5:订单三 Tab 与 API 映射 -4. C14:更新 `apps/AGENTS.md`、`.cursor/rules` 残留旧口径 +C2~C7、C14 见 §1.3。 ### P1 — 补齐 Wave 1 门禁(3~5 天) @@ -264,5 +290,6 @@ | 日期 | 说明 | |------|------| -| 2026-07-12 | 明确总部端保持 WebAdmin,不改为 H5;移除原 C2 端形态冲突 | +| 2026-07-12 | **P0 已执行**:C2~C7、C14 代码与文档对齐;§1 改为计划+状态表 | +| 2026-07-12 | 明确总部端保持 WebAdmin,不改为 H5 | | 2026-07-11 | 首版:V3.0 PRD 全量对照当前 monorepo |