For agentic workers: 在现有分支按 tasks.md 的 T074~T078 执行;需要执行工作流时使用 superpowers:executing-plans(如环境提供)。本轮规划由 Codex 完成,代码由 Claude Code 实现,不创建分支或工作树。
Goal: 扩展商家自动满额免运,商品原价达标后由商家承担全额配送费,并优先占用优惠预算。 Architecture: 复用现有促销表和商家页面;商品促销计算接收可信的内部配送上下文;应用层负责地址、规格及运费,订单固化三方金额后沿用支付与结算。 Tech Stack: Java 21、Spring Boot、MyBatis/MyBatis-Plus、MySQL、Vue 2、Element UI、vue-i18n。 Spec: spec.md FR-028~FR-036;contracts/free-delivery.md。
Original Feature: 008-promotion-coupon | Created: 2026-05-29 | Plan updated: 2026-10-19
Current branch: codex/036-multi-store-checkout(仅沿用工作目录,不恢复036实施;执行时不新建/切换分支)。
Input: Feature specification from /specs/008-promotion-coupon/spec.md
为 foodie 商家端新增促销活动管理(满减/折扣商品/第二份半价/新客立减 4 种类型)和优惠券管理(满减券/商品券/免配送费券),商家可在后台创建和管理促销活动与优惠券。旧促销/优惠券代码(SalesPromotion、VipQuanyi)不动,全部新建文件、新表(promotion_ 前缀)、新接口。前端在商家端侧边栏新增独立的「营销管理」菜单。用户端(App/小程序)页面代码不在本范围;后端用户接口、算价和下单金额属于后端范围。2026-10-19 新增满额免运按文末实施约定扩展,商家 PC 必须同步交付。
技术方案:后端跟随现有 CRUD 分层(Entity + MyBatis XML Mapper + Service + Controller),6 张新表 + 2 个商家端 Controller + 2 个用户端 Controller(领券/查券 + 算价计算)。前端新增 2 个 Vue 页面,促销活动创建页面用 el-tabs 切换 4 种活动类型表单,每种类型有不同的表单字段和数据提交结构。折扣类型采用逐商品设折扣方式(参考美团),每个商品独立设置折扣率,不使用折扣区分组。
Language/Version: Java 21 (Spring Boot 3.x, MyBatis-Plus) Primary Dependencies: Spring Boot, MyBatis-Plus, Vue.js 2.6, Element UI 2.15, vue-i18n Storage: MySQL (6 张新表: promotion_activity, promotion_activity_rule, promotion_coupon_batch, promotion_coupon_rule, promotion_user_coupon, pos_orderpromotion) Testing: 手动测试 (Postman + 前端页面验证) Target Platform: 商家 PC 端管理后台 (foodie-store) Project Type: Web 应用 (Java 后端 + Vue 前端) Performance Goals: 无特殊性能要求,标准 CRUD 操作 Constraints: 旧代码不动,新表用 promotion 前缀,SQL 写入 updatesql/sql.md 手动执行,前端文件 CRLF 换行 Scale/Scope: 商家端 2 个新页面 + 后端 6 个实体 + 4 个 Controller (2 商家端 + 2 用户端)
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
本项目 constitution 未配置具体规则(模板状态),跳过此检查。
specs/008-promotion-coupon/
├── spec.md # 功能规格(已完成)
├── plan.md # 本文件 — 实施计划
└── tasks.md # 任务列表(后续生成)
# 后端 (foodie_server)
ruoyi-system/src/main/java/com/ruoyi/system/
├── domain/
│ ├── PromotionActivity.java # 促销活动实体
│ ├── PromotionActivityRule.java # 促销规则实体
│ ├── PromotionCouponBatch.java # 券批次实体
│ ├── PromotionCouponRule.java # 券规则实体
│ └── PromotionUserCoupon.java # 用户券实体
├── mapper/
│ ├── PromotionActivityMapper.java
│ ├── PromotionActivityRuleMapper.java
│ ├── PromotionCouponBatchMapper.java
│ ├── PromotionCouponRuleMapper.java
│ └── PromotionUserCouponMapper.java
└── service/
├── IPromotionActivityService.java
├── IPromotionActivityRuleService.java
├── IPromotionCouponBatchService.java
├── IPromotionCouponRuleService.java
└── impl/
├── PromotionActivityServiceImpl.java
├── PromotionActivityRuleServiceImpl.java
├── PromotionCouponBatchServiceImpl.java
└── PromotionCouponRuleServiceImpl.java
ruoyi-system/src/main/resources/mapper/system/
├── PromotionActivityMapper.xml
├── PromotionActivityRuleMapper.xml
├── PromotionCouponBatchMapper.xml
├── PromotionCouponRuleMapper.xml
└── PromotionUserCouponMapper.xml
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/
├── ShPromotionActivityController.java # 商家促销活动 API
└── ShPromotionCouponController.java # 商家优惠券 API
ruoyi-admin/src/main/java/com/ruoyi/app/user/
├── UserPromotionCouponController.java # 用户端优惠券接口(领券、查券)
└── UserPromotionCalcController.java # 用户端算价接口(计算优惠)
updatesql/
└── sql.md # SQL 迁移脚本(追加建表语句)
# 前端 (foodie-store)
src/
├── api/
│ ├── promotionActivity.js # 促销活动 API
│ └── promotionCoupon.js # 优惠券 API
├── views/
│ ├── PromotionActivity.vue # 促销活动管理页面
│ └── CouponBatch.vue # 优惠券管理页面
├── components/
│ └── Aside.vue # 侧边栏(新增菜单项)
├── router/
│ └── index.js # 路由(新增2条路由)
└── lang/
├── zh.js # 中文 i18n(新增 promoMenu/promoActivity/couponBatch)
├── tw.js # 繁体中文
├── en.js # 英文
└── vi.js # 越南语
Structure Decision: 后端遵循现有分层架构(domain/mapper/service/controller),前端页面放在 views/ 目录,API 放在 api/ 目录。不新建子目录,与现有文件组织方式一致。
折扣商品(type=2)采用逐商品设折扣方式(参考美团/饿了么),不使用「折扣区/折扣档位」分组概念。
商家操作流程:
前端提交的 rules 即为扁平列表,与后端 promotion_activity_rule 表结构一一对应:
rules: [
{productId: 可乐ID, discountRate: 0.5},
{productId: 宫保鸡丁ID, discountRate: 0.7},
{productId: 麻婆豆腐ID, discountRate: 0.8}
]
UI 示意:
活动名称: [超值特惠] 活动时间: [开始] ~ [结束]
商品列表:
┌──────────────┬────────┬──────────────┬──────────┬────────┐
│ 商品名称 │ 原价 │ 折扣率 │ 折后价 │ 操作 │
├──────────────┼────────┼──────────────┼──────────┼────────┤
│ 可乐 │ 6 │ 50% [-][+] │ 3.0 │ 移除 │
│ 宫保鸡丁 │ 20 │ 70% [-][+] │ 14.0 │ 移除 │
│ 麻婆豆腐 │ 18 │ 80% [-][+] │ 14.4 │ 移除 │
└──────────────┴────────┴──────────────┴──────────┴────────┘
[+ 添加商品]
调研记录:docs/research-meituan-discount-product.md
活动状态使用显式字段(0=未开始, 1=进行中, 2=已结束),在创建时根据 startTime 自动设置。不使用定时任务自动更新状态(简化实现),列表查询时按 status 字段过滤即可。
现有的 Quanyi.vue 中已有商品选择弹窗模式(分类下拉 + 搜索 + 商品表格 + 分页)。PromotionActivity.vue 和 CouponBatch.vue 复用相同的交互模式和 API 调用方式,不抽取为公共组件(遵循项目现有风格)。
促销活动创建:前端发送完整的 { storeId, type, name, startTime, endTime, rules: [...] } JSON,后端在一个事务中插入 activity + rules。
优惠券创建:前端发送 { storeId, name, couponType, totalCount, validDays, startTime, endTime, rule: {...} } JSON,后端在一个事务中插入 batch + rule。
| Method | Path | Description | Request | Response |
|---|---|---|---|---|
| GET | /system/shPromotionActivity/list |
分页列表 | params: page, size, storeId, type?, status? | { code:200, data: { records:[], total, current, size } } |
| GET | /system/shPromotionActivity/{id} |
详情(含规则) | path: id | { code:200, data: { id, storeId, type, name, status, startTime, endTime, rules: [...] } } |
| POST | /system/shPromotionActivity |
创建活动 | body: { storeId, type, name, startTime, endTime, rules: [...] } |
{ code:200, msg:"操作成功" } |
| PUT | /system/shPromotionActivity |
修改活动(仅未开始) | body: { id, storeId, type, name, startTime, endTime, rules: [...] } |
{ code:200, msg:"操作成功" } |
| DELETE | /system/shPromotionActivity/{id} |
删除活动(仅未开始) | path: id | { code:200, msg:"操作成功" } |
| PUT | /system/shPromotionActivity/{id}/end |
结束活动 | path: id | { code:200, msg:"操作成功" } |
| Method | Path | Description | Request | Response |
|---|---|---|---|---|
| GET | /system/shPromotionCoupon/list |
分页列表 | params: page, size, storeId, couponType?, status? | { code:200, data: { records:[], total } } |
| GET | /system/shPromotionCoupon/{id} |
详情(含规则) | path: id | { code:200, data: { id, storeId, name, couponType, totalCount, ..., rule: {...} } } |
| POST | /system/shPromotionCoupon |
创建券 | body: { storeId, name, couponType, totalCount, validDays, startTime, endTime, rule: {...} } |
{ code:200, msg:"操作成功" } |
| PUT | /system/shPromotionCoupon |
修改券(未开始/进行中) | body: { id, storeId, name, couponType, totalCount, validDays, startTime, endTime, rule: {...} } |
{ code:200, msg:"操作成功" } |
| DELETE | /system/shPromotionCoupon/{id} |
删除券(仅未开始) | path: id | { code:200, msg:"操作成功" } |
| PUT | /system/shPromotionCoupon/{id}/offShelf |
下架券 | path: id | { code:200, msg:"操作成功" } |
| Method | Path | Description | Request | Response |
|---|---|---|---|---|
| GET | /app/userPromotionCoupon/storeCoupons |
查询门店可领优惠券列表 | params: storeId | { code:200, data: [{ id, name, couponType, threshold, amount, discountRate, isMutex, totalCount, remainCount, hasReceived }] } |
| POST | /app/userPromotionCoupon/receive |
领取优惠券 | body: { batchId, storeId } |
{ code:200, data: { userCouponId }, msg:"领取成功" } |
| GET | /app/userPromotionCoupon/myCoupons |
查询用户优惠券列表 | params: status?(0=未使用/1=已使用/2=已过期), storeId? | { code:200, data: [{ id, batchId, storeId, name, couponType, threshold, amount, discountRate, isMutex, status, expireTime }] } |
| Method | Path | Description | Request | Response |
|---|---|---|---|---|
| POST | /app/userPromotionCalc/calculate |
计算订单优惠 | body: { storeId, items:[{productId, quantity}], couponId?, forcePath? } |
见下方 Response 示例 |
算价接口参数说明:
items 中只传 productId 和 quantity,不传 price。后端根据 productId 从数据库查询商品实际价格,防止前端篡改couponId: 可选,传入用户券ID则计算使用该券后的价格;不传则不使用券forcePath: 可选,"A"=强制走折扣路径,"B"=强制走满减路径,不传或非法值=自动选最优算价接口 Response 示例 1(满减+优惠券+新客立减):
{
"code": 200,
"data": {
"originalAmount": 45.00,
"pathA": {
"label": "折扣",
"items": [{ "productId": 1, "originalPrice": 20.00, "finalPrice": 14.00 }],
"subtotal": 39.00,
"promotionReduce": 6.00
},
"pathB": {
"label": "满减",
"items": [{ "productId": 1, "originalPrice": 20.00, "finalPrice": 20.00 }],
"subtotal": 33.00,
"promotionReduce": 12.00,
"matchedRule": { "threshold": 40, "reduce": 12 }
},
"optimalPath": "B",
"couponReduce": 5.00,
"finalAmount": 28.00,
"details": [
{ "type": "promotion", "subType": 1, "name": "午市满减(满40减12)", "reduce": 12.00 },
{ "type": "coupon", "name": "满30减5(同享券)", "reduce": 5.00 }
],
"availableCoupons": [
{ "id": 10, "name": "满30减5(同享券)", "couponType": 1, "isMutex": 0, "threshold": 30.00, "amount": 5.00 },
{ "id": 11, "name": "满30减5(互斥券)", "couponType": 1, "isMutex": 1, "threshold": 30.00, "amount": 5.00 }
]
}
}
算价接口 Response 示例 2(互斥券场景):
{
"code": 200,
"data": {
"originalAmount": 45.00,
"pathA": { "label": "折扣", "subtotal": 39.00, "promotionReduce": 6.00 },
"pathB": { "label": "满减", "subtotal": 33.00, "promotionReduce": 12.00 },
"optimalPath": "B",
"couponId": 11,
"couponName": "满30减5(互斥券)",
"couponConflict": true,
"conflictNote": "互斥券不可与满减叠加,选择此券将取消满减优惠",
"finalAmount": 40.00,
"details": [
{ "type": "coupon", "name": "满30减5(互斥券)", "reduce": 5.00 }
]
}
}
算价接口 Response 示例 3(第二份半价场景):
{
"code": 200,
"data": {
"originalAmount": 60.00,
"pathA": {
"label": "第二份半价",
"items": [
{ "productId": 3, "originalPrice": 20.00, "finalPrice": 20.00, "discountNote": "第1件原价" },
{ "productId": 3, "originalPrice": 20.00, "finalPrice": 10.00, "discountNote": "第2件半价" }
],
"subtotal": 50.00,
"promotionReduce": 10.00
},
"pathB": { "label": "无满减活动", "subtotal": 60.00, "promotionReduce": 0 },
"optimalPath": "A",
"finalAmount": 50.00,
"details": [
{ "type": "promotion", "subType": 3, "name": "第二份半价(可乐)", "reduce": 10.00 }
]
}
}
创建满减活动:
{
"storeId": 1,
"type": 1,
"name": "午市满减",
"startTime": "2026-06-01 10:00:00",
"endTime": "2026-06-30 22:00:00",
"rules": [
{ "threshold": 20.00, "reduceAmount": 5.00 },
{ "threshold": 40.00, "reduceAmount": 12.00 },
{ "threshold": 60.00, "reduceAmount": 20.00 }
]
}
创建折扣活动:
{
"storeId": 1,
"type": 2,
"name": "夏季折扣",
"startTime": "2026-06-01 00:00:00",
"endTime": "2026-06-30 23:59:59",
"rules": [
{ "discountRate": 0.40, "productId": 101 },
{ "discountRate": 0.40, "productId": 102 },
{ "discountRate": 0.70, "productId": 201 }
]
}
创建满减券:
{
"storeId": 1,
"name": "满30减5",
"couponType": 1,
"totalCount": 100,
"validDays": 7,
"startTime": "2026-06-01 00:00:00",
"endTime": "2026-06-30 23:59:59",
"rule": {
"isMutex": 0,
"threshold": 30.00,
"amount": 5.00
}
}
参见 spec.md 的 Data Model 章节,6 张表完整 DDL 已在 spec 中定义。
表关系:
promotion_activity 1 → N promotion_activity_rule
promotion_coupon_batch 1 → 1 promotion_coupon_rule
promotion_coupon_batch 1 → N promotion_user_coupon
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| 折扣类型前端分组概念(已废弃→改为逐商品设折扣) | 原设计按折扣率分组管理商品,参考美团后改为逐商品设折扣 | 分组概念增加商家认知负担,美团实际不使用分组 |
| 4种活动类型共用一个创建对话框 | 商家从同一个入口创建不同类型的活动 | 每种类型独立页面会导致页面冗余 |
平台要求限制优惠幅度防止恶意竞争:所有促销+优惠券的总优惠不能超过订单的 20%。经多轮口径讨论(见 spec.md Session 2026-09-02),定为"创建侧各自校验 + 算价侧总账预算"双层执行,平台参数开关控制。
状态(2026-10-19 更新):已由 Claude Code 在现有分支实现(T074~T077 完成并过定向测试与回归;T078 自动化验证完成,商家 PC 浏览器手工验收与 SQL 手动执行待开发者)。详细任务、测试和执行顺序见 tasks.md,交接入口见 claude-code-handoff.md。
PosFoodController.java 改动。@RequestBody、token Header;GET 参数显式注解,不使用 Map 请求参数、Bean Validation 或单语言硬编码错误;新文案四语言齐全。C:/Users/99029/.jdks/graalvm-jdk-21.0.12+7.1,仅在当前终端设置环境;前端保留 CRLF。updatesql/sql.md 最后,开发者手动执行;本次规划不生成已可执行迁移、不连接数据库。| 易出错输入/条件 | 必须满足的行为 | 归属 |
|---|---|---|
| 原价100、运费30、预算20;互斥券切换;候选券列表 | 全免30,不能释放免运预留;实减0的券不核销 | T075/T076 |
| 新活动 status=0 已到开始日、已到期但 status=1;并发创建重叠活动 | 按本地日期与未结束状态判断;同店同类型同日仅一个活动 | T074/T075 |
| 跨店商品/规格、别人地址、客户端freight=0、商品价格/amount篡改 | 新契约服务端查价和地址归属;差异拒绝,不只记日志 | T075/T076 |
| 第2份半价预算豁免与普通满减并存,普通满减被预算截为0 | 新配送流程按应用预算后实际应付选路径,保留半价豁免和手动forcePath | T075 |
| 活动结束后的旧订单、支付方式切换、到付交款为负、发票/退款 | 快照不变;交款方向明确;扣客户实际运费;不多退、不重复入账 | T076/T077 |
| 现状 | 实施要求 |
|---|---|
PromotionActivity.type 目前1~4,规则已有threshold |
增加type=5,复用单条threshold规则,无需新优惠券表 |
PromotionActivityMapper.selectActiveByStoreId 与规则查询仅看status=1 |
新type=5使用独立按日期查询,不依赖不存在的状态刷新任务;不顺手重写旧四种活动 |
PromotionCalcServiceImpl 177行起已按促销→新客→券分配预算,半价豁免 |
新可信配送上下文先占F;所有普通/互斥/候选券分支一致 |
UserPromotionCalcController 类注释仍写“促销→券→新客” |
实施时同步修正为实际顺序及新增免运优先规则;本轮规划没有修改生产注释 |
PromotionCalcResponse.finalAmount 实际只有商品应付,没有运费 |
保留旧语义;新增配送和整单应付字段,禁止把免运从商品再减一次 |
OrderPromotionHelper 累加全部promotion,且返回couponId不表示实际用券 |
排除type=5商品汇总;增加appliedCouponId,只核销有真实优惠的券 |
UserOrderController 直接用item.amount/item.freight,优惠校验仅警告 |
版本2分支须权威重算、比较、固化;旧分支保持原契约 |
OrderService.setQishouBilling 以freight计算佣金、税及钱包记录 |
保留原毛配送费和幂等职责,不新增第二笔骑手运费入账 |
OrderInvoiceService 两处使用amount−freight |
版本2改用amount−customerDeliveryFee;历史订单保留原口径 |
type=5 固定仅一条规则 threshold>0,按现有整数元展示;productId、reduceAmount、discountRate、minQuantity不适用且不得携入其他优惠。门槛由商家填写,100不是硬编码常量。PromotionActivitySaveRequest DTO适配既有POST/PUT字段,不破坏原四种类型的请求格式;新增type=5日期按日期范围归一化到本地首日00:00:00和末日23:59:59,响应保持现有yyyy-MM-dd。List<PromotionActivity> selectActiveFreeDeliveryByStoreId(Long storeId, Date now),XML条件为type=5、status in(0,1)、start_time<=now、end_time>=now;随后按activityId读已有规则。返回多条重叠存量活动应报配置冲突,不能随机挑一条。设 O=商品原价(含服务端核实的规格加价),R=原配送费/骑手毛配送费基数,F=本活动免运金额,D=客户应付配送费,G=优惠后商品应付,A=客户应付总额。
F=R、D=0;未命中:F=0、D=R;A=G+D。无配送费时F=0,不产生虚假金额优惠或用券记录。B=roundHalfUp(O×比例)(沿现有整数元规则),供商品优惠使用的初始预算 max(0,B−F)。F允许大于B,绝不部分免运。appliedCouponId只在实际减免>0且校验通过时返回。新订单只按appliedCouponId核销;实减0、预算耗尽、无效/过期券不得被核销。PromotionDeliveryContext(BigDecimal deliveryFee, boolean eligible);IPromotionCalcService 保留 calculate(PromotionCalcRequest, Long),新增重载 calculate(PromotionCalcRequest, Long, PromotionDeliveryContext)。旧调用不自动注入客户端运费;完整报价适配层构造该可信上下文。FoodDeliveryPromotionQuoteService.quote(FoodDeliveryPromotionQuoteRequest request, Long userId): PromotionCalcResponse,由新POST报价方法及版本2下单共用。请求详情见contracts,不能接受客户自行给出的免运额或运费作为权威输入。SysFileInfoController.getZuiyyfei现有跳表规则(含半公里与千元舍入),用小范围方法封装到报价Service,保留旧GET入口。新调用不得使用已停用Mapbox,也不把地图响应/密钥输出到日志。| 位置 | 拟新增/保留字段 | 约束 |
|---|---|---|
| promotion_activity / rule | type增加5、rule.threshold复用 | 不建免运券或新活动表 |
| pos_order.freight | 保留既有Double/原列 | 仍是R,不清零、不改单位 |
| pos_order | promotion_pricing_version TINYINT NULL | 2=本次完整配送金额契约;历史NULL保留旧行为 |
| pos_order | merchant_delivery_subsidy DECIMAL(10,2) NULL | 新流程F,未命中填0;不从餐费再扣 |
| pos_order | customer_delivery_fee DECIMAL(10,2) NULL | 新流程D;旧流程NULL读取时回退freight |
| pos_order_promotion | 已有promo_type=1、promo_sub_type=5 | JSON记录门槛、O/R/F/D/B、活动ID和名称;不改旧枚举值 |
PromotionCalcResponse 新增字段与单位详见contracts;OrderCreatItem 新增promotionPricingVersion及cartItems.specValueIds,保留旧字段。新流程food JSON根据真实商品/规格生成兼容原price/otherPrice/number结构,不信客户端food中的价格。UserOrderController.createOrderChild 对显式version=2单店请求先调用完整报价,校验item.amount、父单actualPayAmount与服务端A一致;原freight字段应提交R,不是0。差异用错误key promotion.freeDelivery.quoteChanged 返回并回滚,不静默采用旧金额。OrderPromotionHelper 增加新流程回填入口,商品减免汇总排除subType=5,写一条type=5快照;按appliedCouponId和实际减免核销,保留事务中的归属/使用状态检查。失败整笔回滚,不留下可支付父单或已核销券。| 场景 | 取餐交付 | 客户端待收 | 配送基数 |
|---|---|---|---|
| 在线已付 | 商家给骑手R | 0 | R |
| 到付A≥R | 骑手给商家A−R | A | R |
| 到付A<R | 商家给骑手R−A | A | R |
@TableField(exist=false)派生属性/getter,沿既有查询路径返回;可选字段为 merchantPayToRider=max(0,到付?R−A:R)、riderPayToMerchant=到付?max(0,A−R):0、customerCollectAmount=到付?A:0;这些为派生值,不另存结算状态。由订单快照与当前payType推导,解决切换支付方式后方向变化。/deliveryQuote 放开 orderType=1/2:跳过地址/自配送/路线运费,固定 PromotionDeliveryContext(0, eligible=false),响应 R/F/D=0、A=G;传入的 addressId 忽略不校验。/calculate 与旧下单契约不变;App 对接文档同步更新为"全场景单一报价入口"。详见tasks的每批Files/Interfaces/Test。核心新增文件为系统内部PromotionDeliveryContext、商家保存DTO、应用层完整报价请求与Service及对应定向测试;其余只在现有促销、订单、Mapper、发票、账单与商家PromotionActivity.vue挂接。不为该活动新建配置开关、券库存、领券记录、菜单、定时任务或批处理迁移。
顺序:T074 商家PC与活动API → T075可信报价与预算 → T076订单/快照/SQL → T077结算/发票/支付回归 → T078统一验证和文档交接。禁止把文档已写、用例已列或SQL已生成标成代码已验证。
| 规格 | 实施任务 |
|---|---|
| FR-028 活动配置自动生效 | T074/T075 |
| FR-029 商品原价门槛 | T075 |
| FR-030 免运优先、预算与豁免 | T075 |
| FR-031 商家承担与金额分离 | T076/T077 |
| FR-032 商家PC、App不动 | T074/T078 |
| FR-033 权威报价与创建校验 | T075/T076 |
| FR-034 旧接口兼容与新契约 | T075/T076/T078 |
| FR-035 快照、交款与支付切换 | T076/T077 |
| FR-036 防重复抵扣/核销、发票与退款 | T075/T076/T077 |
执行命令、用例矩阵和未执行项记录见handoff。必须区分:商家PC配置可用、后端新契约通过、App展示未改、SQL待手动执行。App不改意味着完整用户端展示与新报价接入不由本轮完成;不得让Claude为了“看起来上线”修改App,或在旧接口里更换finalAmount语义。后续App工作应按已固定的contracts接入,无需重新确认原价门槛和免运优先规则。