自动修改

This commit is contained in:
2026-07-12 07:50:27 +08:00
parent b74e4e4fe5
commit d0f0fa09af
18 changed files with 117 additions and 66 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ User (Owner A) generates token → Shop scans/confirms (you)
## Hard rules ## 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` - Amount: direct redeem `0 < amount ≤ total ACTIVE benefit balance`; document redeem `0 < amount ≤ document balance`
- Transaction + coupon `version` optimistic lock - Transaction + coupon `version` optimistic lock
- **Never** `prisma.order.update` in redeem module - **Never** `prisma.order.update` in redeem module
+1 -1
View File
@@ -48,4 +48,4 @@ export class TradeController {
- 埋点 → `log_user_analytics`;业务审计 → `common_event` - 埋点 → `log_user_analytics`;业务审计 → `common_event`
- 支付 → `log_third_party` + `user_order.pay_*`(无 payments 表) - 支付 → `log_third_party` + `user_order.pay_*`(无 payments 表)
- 核销码 → Redis 5min无 DB 表 - 核销码 → Redis 3min`REDEEM_TOKEN_TTL_SECONDS`
+1 -1
View File
@@ -20,7 +20,7 @@ V2 手册 §五(v3.128 表)。schema 与手册保持一致。
## 关键约定 ## 关键约定
- 无 `payments` / `user_order_item` / `redeem_tokens` 表 - 无 `payments` / `user_order_item` / `redeem_tokens` 表
- 核销码仅 Redis 5 分钟 - 核销码仅 Redis **3 分钟**`REDEEM_TOKEN_TTL_SECONDS=180`
- 支付流水 → `log_third_party` + `user_order.pay_*` - 支付流水 → `log_third_party` + `user_order.pay_*`
- 业务事件 → `common_event`;埋点 → `log_user_analytics` - 业务事件 → `common_event`;埋点 → `log_user_analytics`
@@ -48,7 +48,7 @@ H5 三端引用 `@dukang/shared-ui``tokens.css`)。admin-web 使用 Ant Des
### 订单 5 Tab ### 订单 5 Tab
``` ```
all | pending_pay | pending_ship | pending_receive | completed 待付款 | 已付款 (`paid`) | 已完成
``` ```
## h5-shop 路由(刘景尧) ## h5-shop 路由(刘景尧)
@@ -112,7 +112,7 @@ all | pending_pay | pending_ship | pending_receive | completed
## UI 硬约束 ## UI 硬约束
- C 端订单 **5 Tab**pending_ship - C 端订单 **3 Tab**`pending_pay` / `paid` / `completed`
- 个人中心无会员等级(V1 不做会员体系) - 个人中心无会员等级(V1 不做会员体系)
- 清香型 4 SKU 可购;酱香/浓香灰态 - 清香型 4 SKU 可购;酱香/浓香灰态
- 门店列表仅 `OPEN` 状态 - 门店列表仅 `OPEN` 状态
+2 -2
View File
@@ -19,7 +19,7 @@ V2 目标:`mini-user` / `mini-partner` / `mini-hq` 替换对应 H5(除门店
- 类型从 `@dukang/shared-types` import,禁止复制枚举字符串 - 类型从 `@dukang/shared-types` import,禁止复制枚举字符串
- 禁止 import `server/` 或另一个 `apps/*` 的源码 - 禁止 import `server/` 或另一个 `apps/*` 的源码
- UI 共享组件优先 `@dukang/shared-ui` - UI 共享组件优先 `@dukang/shared-ui`
- C 端订单列表 **5 Tab**(含 `pending_ship` - C 端订单列表 **3 Tab**`待付款 | 已付款 | 已完成`Tab key: `pending_pay` / `paid` / `completed`
## 新页面 workflow ## 新页面 workflow
@@ -32,7 +32,7 @@ V2 目标:`mini-user` / `mini-partner` / `mini-hq` 替换对应 H5(除门店
### h5-userjacy-dukang ### h5-userjacy-dukang
主链路:登录 → 首页/详情 → 下单 Mock 支付 → 5 Tab 订单 → 权益 → 核销码 → 门店列表 主链路:登录 → 首页/详情 → 下单 Mock 支付 → **3 Tab** 订单 → 权益 → 核销码 → 门店列表
**勿改**:门店核销确认 UI(属 h5-shop)、合伙人录店(属 h5-partner **勿改**:门店核销确认 UI(属 h5-shop)、合伙人录店(属 h5-partner
+1 -2
View File
@@ -11,8 +11,7 @@ const DEFAULT_AVATAR =
const ORDER_SHORTCUTS = [ const ORDER_SHORTCUTS = [
{ tab: 'pending_pay', icon: 'payments', label: '待付款' }, { tab: 'pending_pay', icon: 'payments', label: '待付款' },
{ tab: 'pending_ship', icon: 'package_2', label: '待发货' }, { tab: 'paid', icon: 'package_2', label: '已付款' },
{ tab: 'pending_receive', icon: 'local_shipping', label: '配送中' },
{ tab: 'completed', icon: 'task_alt', label: '已完成' }, { tab: 'completed', icon: 'task_alt', label: '已完成' },
] as const; ] as const;
+5 -6
View File
@@ -8,17 +8,16 @@ import { request } from '../lib/api';
const TABS = [ const TABS = [
{ key: 'all', label: '全部' }, { key: 'all', label: '全部' },
{ key: 'pending_pay', label: '待付款' }, { key: 'pending_pay', label: '待付款' },
{ key: 'pending_ship', label: '待发货' }, { key: 'paid', label: '已付款' },
{ key: 'pending_receive', label: '待收货' },
{ key: 'completed', label: '已完成' }, { key: 'completed', label: '已完成' },
]; ];
const STATUS_LABEL: Record<string, string> = { const STATUS_LABEL: Record<string, string> = {
PENDING_PAY: '待付款', PENDING_PAY: '待付款',
PENDING_SHIP: '待发货', PENDING_SHIP: '已付款',
OUT_WAREHOUSE: '出库中', OUT_WAREHOUSE: '已付款',
SHIPPING: '配送中', SHIPPING: '已付款',
PENDING_RECEIVE: '待收货', PENDING_RECEIVE: '已付款',
COMPLETED: '已完成', COMPLETED: '已完成',
}; };
+2 -2
View File
@@ -140,11 +140,11 @@ export default function PayPage() {
if (!paid) { if (!paid) {
alert('支付结果确认中,请稍后在订单列表查看'); alert('支付结果确认中,请稍后在订单列表查看');
} }
navigate('/orders?tab=pending_ship'); navigate('/orders?tab=paid');
return; return;
} }
navigate('/orders?tab=pending_ship'); navigate('/orders?tab=paid');
} catch (e) { } catch (e) {
if (isWechatAuthRequiredError(e)) { if (isWechatAuthRequiredError(e)) {
setNeedsWechatAuth(true); setNeedsWechatAuth(true);
+2 -1
View File
@@ -1,12 +1,13 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { REDEEM_TOKEN_TTL_SECONDS } from '@dukang/shared-types';
import PageHeader from '@dukang/shared-ui/PageHeader'; import PageHeader from '@dukang/shared-ui/PageHeader';
export default function RedeemCodePage() { export default function RedeemCodePage() {
const navigate = useNavigate(); const navigate = useNavigate();
const token = sessionStorage.getItem('redeemToken') || ''; const token = sessionStorage.getItem('redeemToken') || '';
const amount = sessionStorage.getItem('redeemAmount') || '0'; const amount = sessionStorage.getItem('redeemAmount') || '0';
const [secondsLeft, setSecondsLeft] = useState(300); const [secondsLeft, setSecondsLeft] = useState(REDEEM_TOKEN_TTL_SECONDS);
useEffect(() => { useEffect(() => {
const expireAt = sessionStorage.getItem('redeemExpireAt'); const expireAt = sessionStorage.getItem('redeemExpireAt');
+3 -3
View File
@@ -1,8 +1,8 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom'; import { useNavigate, useSearchParams } from 'react-router-dom';
import { REDEEM_TOKEN_TTL_SECONDS } from '@dukang/shared-types';
import { request } from '../lib/api'; import { request } from '../lib/api';
const REDEEM_TOKEN_TTL_SECONDS = 300;
const POLL_INTERVAL_MS = 2500; const POLL_INTERVAL_MS = 2500;
type BenefitSummary = { type BenefitSummary = {
@@ -236,7 +236,7 @@ export default function RedeemPage() {
type="button" type="button"
className="redeem-header-btn" className="redeem-header-btn"
aria-label="帮助" aria-label="帮助"
onClick={() => window.alert('核销金额不超过可用余额,核销码 5 分钟内有效。')} onClick={() => window.alert('核销金额不超过可用余额,核销码 3 分钟内有效。')}
> >
<span className="material-symbols-outlined">help_outline</span> <span className="material-symbols-outlined">help_outline</span>
</button> </button>
@@ -301,7 +301,7 @@ export default function RedeemPage() {
<div className="redeem-security"> <div className="redeem-security">
<span className="material-symbols-outlined">shield</span> <span className="material-symbols-outlined">shield</span>
<p> 5 </p> <p> 3 </p>
</div> </div>
</main> </main>
+19
View File
@@ -6,6 +6,7 @@ import {
validateRedeemAmount, validateRedeemAmount,
allocateBenefitCoupons, allocateBenefitCoupons,
calcBenefitSummary, calcBenefitSummary,
orderTabToStatuses,
} from './index'; } from './index';
describe('calcBenefitAmount', () => { 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', () => { describe('calcBenefitSummary', () => {
it('max redeem equals total balance', () => { it('max redeem equals total balance', () => {
expect(calcBenefitSummary([300, 400])).toEqual({ expect(calcBenefitSummary([300, 400])).toEqual({
+2 -2
View File
@@ -115,10 +115,10 @@ export function orderTabToStatuses(tab: string): string[] | undefined {
switch (tab) { switch (tab) {
case 'pending_pay': case 'pending_pay':
return ['PENDING_PAY']; return ['PENDING_PAY'];
case 'paid':
case 'pending_ship': case 'pending_ship':
return ['PENDING_SHIP', 'OUT_WAREHOUSE'];
case 'pending_receive': case 'pending_receive':
return ['SHIPPING', 'PENDING_RECEIVE']; return ['PENDING_SHIP', 'OUT_WAREHOUSE', 'SHIPPING', 'PENDING_RECEIVE'];
case 'completed': case 'completed':
return ['COMPLETED']; return ['COMPLETED'];
default: default:
+11 -4
View File
@@ -40,9 +40,13 @@ export enum OrderStatus {
export enum OrderTab { export enum OrderTab {
ALL = 'all', ALL = 'all',
PENDING_PAY = 'pending_pay', PENDING_PAY = 'pending_pay',
PENDING_SHIP = 'pending_ship', /** V3.0 PRD:已付款(含待发货/配送中,内部多状态聚合) */
PENDING_RECEIVE = 'pending_receive', PAID = 'paid',
COMPLETED = 'completed', COMPLETED = 'completed',
/** @deprecated 兼容旧链接,映射同 PAID */
PENDING_SHIP = 'pending_ship',
/** @deprecated 兼容旧链接,映射同 PAID */
PENDING_RECEIVE = 'pending_receive',
} }
export enum DeliveryType { export enum DeliveryType {
@@ -91,6 +95,9 @@ export const CLIENT_APP_ACTOR_MAP: Record<ClientApp, ActorType> = {
export const REDEEM_DIRECT_LIMIT_POLICY = 'TOTAL_ACTIVE_BALANCE'; export const REDEEM_DIRECT_LIMIT_POLICY = 'TOTAL_ACTIVE_BALANCE';
export const REDEEM_DOCUMENT_LIMIT_POLICY = 'DOCUMENT_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 */ /** 核销成功后供用户端轮询结果,略长于 token TTL */
export const REDEEM_RESULT_TTL_SECONDS = 600; export const REDEEM_RESULT_TTL_SECONDS = 360;
+6 -6
View File
@@ -80,7 +80,7 @@ async function main() {
await prisma.commonCityCommissionRule.create({ await prisma.commonCityCommissionRule.create({
data: { data: {
cityId: city.id, cityId: city.id,
orderCommissionRate: 0.05, orderCommissionRate: 0,
redeemCommissionRate: 0.03, redeemCommissionRate: 0.03,
partnerProfitRate: 0.35, partnerProfitRate: 0.35,
storeSettlementRate: 0.6, storeSettlementRate: 0.6,
@@ -111,10 +111,10 @@ async function main() {
} }
const productDefs = [ const productDefs = [
{ skuCode: 'QX-001', name: '杜康·白水古酿 500ml', subtitle: '清香型 52度 礼盒装', price: 599, sortOrder: 1, img: 'https://picsum.photos/seed/dukang1/400/400' }, { skuCode: 'JZ-10', name: '酒祖杜康(国标特级10', subtitle: '清香型 53度', price: 128, sortOrder: 1, img: 'https://picsum.photos/seed/jiuzu10/400/400' },
{ skuCode: 'QX-002', name: '杜康·年份陈酿(十年', subtitle: '清香型 42度 纯粮酿造', price: 880, sortOrder: 2, img: 'https://picsum.photos/seed/dukang2/400/400' }, { skuCode: 'JZ-15', name: '酒祖杜康(国标特级15', subtitle: '清香型 53度', price: 168, sortOrder: 2, img: 'https://picsum.photos/seed/jiuzu15/400/400' },
{ skuCode: 'QX-003', name: '杜康·御享1号 珍藏版', subtitle: '高端定制 限量发售', price: 1299, sortOrder: 3, img: 'https://picsum.photos/seed/dukang3/400/400' }, { skuCode: 'JZ-20', name: '酒祖杜康(国标特级20', subtitle: '清香型 53度', price: 298, sortOrder: 3, img: 'https://picsum.photos/seed/jiuzu20/400/400' },
{ skuCode: 'QX-004', name: '杜康·经典传承', subtitle: '清香型 纯粮固态', price: 399, sortOrder: 4, img: 'https://picsum.photos/seed/dukang4/400/400' }, { skuCode: 'JZ-30', name: '酒祖杜康(国标特级30', subtitle: '清香型 53度', price: 498, sortOrder: 4, img: 'https://picsum.photos/seed/jiuzu30/400/400' },
]; ];
const products = []; const products = [];
@@ -126,7 +126,7 @@ async function main() {
name: def.name, name: def.name,
subtitle: def.subtitle, subtitle: def.subtitle,
aromaType: 'QINGXIANG', aromaType: 'QINGXIANG',
spec: def.skuCode === 'QX-002' ? '500ml | 42度' : def.skuCode === 'QX-004' ? '500ml | 46度' : '500ml | 52度', spec: '500ml | 53度',
price: def.price, price: def.price,
benefitAmount: def.price, benefitAmount: def.price,
status: 'ON_SALE', status: 'ON_SALE',
@@ -1,8 +1,7 @@
import { BadRequestException, Injectable } from '@nestjs/common'; 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'; import { RedisService } from '../../common/redis/redis.service';
const CODE_TTL_SECONDS = 300;
const RATE_TTL_SECONDS = 60; const RATE_TTL_SECONDS = 60;
function codeKey(phone: string, scene: string) { function codeKey(phone: string, scene: string) {
@@ -36,7 +35,7 @@ export class SmsCodeStore {
async generateAndStore(phone: string, scene: string): Promise<string> { async generateAndStore(phone: string, scene: string): Promise<string> {
const code = this.config.mockSms ? this.config.mockSmsCode : randomSixDigitCode(); 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; return code;
} }
@@ -69,7 +69,7 @@ export class AdminCitiesService {
status: (dto.status ?? 'PENDING') as 'PENDING' | 'ACTIVE' | 'PAUSED', status: (dto.status ?? 'PENDING') as 'PENDING' | 'ACTIVE' | 'PAUSED',
commissionRule: { commissionRule: {
create: { create: {
orderCommissionRate: 0.05, orderCommissionRate: 0,
redeemCommissionRate: 0.03, redeemCommissionRate: 0.03,
}, },
}, },
@@ -225,7 +225,7 @@ export class SettlementService {
const cityIds = cities.map((c) => c.id); const cityIds = cities.map((c) => c.id);
const defaultOrderRate = cities[0]?.commissionRule?.orderCommissionRate const defaultOrderRate = cities[0]?.commissionRule?.orderCommissionRate
? Number(cities[0].commissionRule.orderCommissionRate) ? Number(cities[0].commissionRule.orderCommissionRate)
: 0.05; : 0;
const defaultRedeemRate = cities[0]?.commissionRule?.redeemCommissionRate const defaultRedeemRate = cities[0]?.commissionRule?.redeemCommissionRate
? Number(cities[0].commissionRule.redeemCommissionRate) ? Number(cities[0].commissionRule.redeemCommissionRate)
: 0.03; : 0.03;
+55 -28
View File
@@ -14,14 +14,15 @@
|------|------| |------|------|
| **整体完成度(粗估)** | Wave 1 约 **45%**Wave 2 约 **10%**Wave 3 约 **5%** | | **整体完成度(粗估)** | Wave 1 约 **45%**Wave 2 约 **10%**Wave 3 约 **5%** |
| **可跑通的主链路** | 登录 → 浏览商品 → 同城下单 Mock/微信支付 → 权益 1:1 → **扫码核销** → T+1 门店 payout 记录 → 总部确认打款 | | **可跑通的主链路** | 登录 → 浏览商品 → 同城下单 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 全量 | | **冒烟覆盖** | `scripts/smoke-v3.mjs` 仅覆盖窄 W1 切片,**不等于** V3.0 ACC 全量 |
### Wave 门禁状态 ### Wave 门禁状态
| 门禁 | 状态 | 一句话 | | 门禁 | 状态 | 一句话 |
|------|------|--------| |------|------|--------|
| **W1-S1** 下单+支付+权益 | 🔶 | 链路可跑,SKU/订单 Tab/TTL 不合规 | | **W1-S1** 下单+支付+权益 | 🔶 | TTL/Tab/SKU/佣金已对齐;仍缺小程序形态 |
| **W1-S2** 门店核销双通道 | 🔶 | 扫码 ✅;手机号核销 ❌ | | **W1-S2** 门店核销双通道 | 🔶 | 扫码 ✅;手机号核销 ❌ |
| **W1-S3** 合伙人子账号 | ✅ | CRUD + 推广员菜单裁剪已有 | | **W1-S3** 合伙人子账号 | ✅ | CRUD + 推广员菜单裁剪已有 |
| **W1-S4** 拓店→审核→C 端可见 | 🔶 | 三步录入有;试核销100/负责人复核/SOP 缺失 | | **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 要求 | 当前实现 | 证据位置 | 改造建议 | ### 1.1 改造计划总览
|---|-----------|----------|----------|----------|
| 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` | | **P0** | C2~C7、C14 | TTL、订单三 Tab、SKU seed、佣金默认、文档规则 | ✅ **已执行** |
| C4 | 订单 Tab:**待付款 / 已付款 / 已完成** | **5 Tab**(含待发货/待收货) | `apps/h5-user/src/pages/OrderListPage.tsx``OrderTab` enum | 改 Tab 映射与列表 API | | **P1** | C1 | C 端 H5 过渡;目标微信小程序另立项 | 📋 已决策暂缓 |
| C5 | 订单状态机三态展示 | **9 态** `OrderStatus` | `packages/shared-types/src/enums.ts` | PRD 三 Tab 映射到细粒度状态,或收敛状态机 | | **P1** | C9 | 支付后自动推小飞侠 | 📋 待做(Wave 1) |
| C6 | 4 款 **酒祖杜康** + 锚点价 | preV1 杜康系列 ¥3991299 | `server/dukang-api/prisma/seed-v31.ts` | 替换 seed + 商品管理数据 | | **P2** | C8 | 一号多店(schema + 拓店确认) | 📋 待做(Wave 2) |
| C7 | 默认佣金 **0% 订单 + 3% 核销** | seed **5% 订单佣金** | `seed-v31.ts` `orderCommissionRate: 0.05` | 改 seed 与开城默认 | | **P2** | C10 | 门店主动提现 + FIN 护栏 | 📋 待做(Wave 2 |
| C8 | **一号多店**(同号可多店主账号) | `StoreAccount.phone @unique`;创建时**拒绝**重复手机 | `schema.prisma` `StoreAccount``store.service.ts` `partnerCheckStorePhone` | 改模型:主账号与门店多对多 | | **P2** | C12 | 现场提货 `ON_SITE_PICKUP` | 📋 待做(Wave 2 |
| C9 | 支付后**自动推小飞侠**(同城) | 支付后 delivery `MANUAL`;总部手动推单 | `trade.service.ts``admin-xiaofeixia.service.ts` | 支付回调后接 xiaofeixia | | **P3** | C11 | 工单四类型 | 📋 待做(Wave 3) |
| C10 | 门店结算 = **门店主动提现**W2 | 核销后**自动生成** `StorePayout` | `settlement.service.ts` `createStorePayout` | 增加余额账本 + 提现申请流 | | **P3** | C13 | 多合伙人管辖/佣金快照 | 📋 待做(Wave 2~3) |
| 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` | ### 1.2 冲突明细
| C13 | 多合伙人(全城/区域平级) | 城市 `partnerId` **单合伙人** | `schema.prisma` `CommonCity.partnerId` | 新增合伙人管辖/区县/佣金快照模型 |
| C14 | 文档/规则残留 5min、5 Tab | `apps/AGENTS.md`、backend rules 仍写旧口径 | `.cursor/rules/``apps/AGENTS.md` | 批量对齐 V3.0 | | # | 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-002 | 登录 + 下单强制手机号 | `OrderConfirmPage.tsx``PhoneVerifiedGuard` | ✅ |
| REQ-U-003 | 微信支付 | `pay-wechat.ts` + 后端 callback | Mock/真实均有 | | REQ-U-003 | 微信支付 | `pay-wechat.ts` + 后端 callback | Mock/真实均有 |
| REQ-U-004 | 定位/开城 | `HomePage.tsx``wechat-location.ts` | 🔶 H5 定位 API | | 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-007 | 同城 ≥2 瓶 | `packages/domain` + `trade.service.ts` | ✅ |
| REQ-U-010 | 锁单写入 30min | `trade.service.ts` `payExpireAt` | 🔶 无自动取消 job | | REQ-U-010 | 锁单写入 30min | `trade.service.ts` `payExpireAt` | 🔶 无自动取消 job |
| REQ-U-011 | 权益 1:1 | `benefit.service.ts` | ✅ | | REQ-U-011 | 权益 1:1 | `benefit.service.ts` | ✅ |
| REQ-U-012 | 客服入口 | `CustomerServicePage.tsx` | 🔶 Mock 对话 | | REQ-U-012 | 客服入口 | `CustomerServicePage.tsx` | 🔶 Mock 对话 |
| REQ-U-013~014 | 门店列表/详情 | `StoreListPage.tsx` 等 | ✅ 仅 OPEN | | 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-017 | 核销后评价 | `RedeemSuccessPage.tsx` | ✅ |
| REQ-U-018 | 权益页 Tab | `BenefitPage.tsx` | ✅ | | REQ-U-018 | 权益页 Tab | `BenefitPage.tsx` | ✅ |
| REQ-U-019 | 个人中心 | `MinePage.tsx` | ✅ | | REQ-U-019 | 个人中心 | `MinePage.tsx` | ✅ |
@@ -220,12 +249,9 @@
## 6. 建议改造顺序(仅 V3.0 视角) ## 6. 建议改造顺序(仅 V3.0 视角)
### P0 — 消除冲突1~2 天 ### P0 — 消除冲突 ✅ 已完成(2026-07-12
1. C2/C3:核销码 + 短信 TTL → 3 分钟 C2~C7、C14 见 §1.3。
2. C6/C7seed SKU + 佣金默认值
3. C4/C5:订单三 Tab 与 API 映射
4. C14:更新 `apps/AGENTS.md``.cursor/rules` 残留旧口径
### P1 — 补齐 Wave 1 门禁(3~5 天) ### 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 | | 2026-07-11 | 首版:V3.0 PRD 全量对照当前 monorepo |