优惠券可在消费时抵扣金额
flowchart TD
%% ============ 颜色定义 ============
classDef page fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
classDef api fill:#fff3e0,stroke:#f57c00,stroke-width:1px
classDef service fill:#e8f5e9,stroke:#388e3c,stroke-width:1px
classDef data fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1px
classDef external fill:#fce4ec,stroke:#d32f2f,stroke-width:1px,stroke-dasharray:3 2
classDef actor fill:#e1f5fe,stroke:#0288d1,stroke-width:2px,stroke-dasharray:5 3
%% ============ 角色 ============
A0(("👤 家长")):::actor
B0(("👶 孩子")):::actor
C0(("🖥 管理员")):::actor
%% ================================================================
%% 阶段一:管理员创建可兑换优惠券
%% ================================================================
subgraph 阶段一[阶段一:管理员创建可兑换优惠券]
direction TB
P1["🖥 管理员:创建优惠券页"]:::page
P1 -->|"填写优惠券信息"| A1["🔗 POST /api/admin/coupon/create"]:::api
A1 -->|"名称, 类型, 面值, 最低消费,<br>有效期, 总量, 适用范围,<br>**wealthEnergyCost(财富能量兑换价)**"| S1["⚙️ AdminCouponController.create()"]:::service
S1 -->|"写入优惠券记录"| D1["📦 coupon 表<br>包含 wealth_energy_cost 字段"]:::data
end
%% ================================================================
%% 阶段二:查看可兑换优惠券
%% ================================================================
subgraph 阶段二[阶段二:查看可兑换优惠券]
D1 --> P2["🏠 富页面:我的优惠券"]:::page
P2 -->|"查看可兑换列表"| A2["🔗 POST /api/coupon/exchange/list"]:::api
A2 -->|"用户ID, 孩子ID"| S2["⚙️ 查询 wealth_energy_cost > 0 的优惠券"]:::service
S2 -->|"返回可兑换优惠券列表<br>含 wealthEnergyCost, 面值, 有效期"| D2["📦 可兑换优惠券列表"]:::data
D2 --> P2a["🏠 展示每张优惠券:<br>面值 10元 | 需 100 财富能量<br>面值 20元 | 需 200 财富能量"]:::page
end
%% ================================================================
%% 阶段三:兑换流程(两种入口)
%% ================================================================
subgraph 阶段三[阶段三:兑换流程]
direction TB
P3_entry{"兑换入口"}:::page
P3_entry -->|"入口A: 富页面→我的优惠券"| P3a["🏠 富页面:优惠券管理处"]:::page
P3_entry -->|"入口B: 消费时直接兑换"| P3b["🏠 商品确认页:选择优惠券"]:::page
P3a -->|"点击兑换"| P3_confirm["🏠 兑换确认弹窗:<br>消耗 100 财富能量<br>获得 10元 优惠券<br>获得 100 行动能量"]:::page
P3b -->|"可选优惠券为空<br>点击'去兑换'"| P3_confirm
P3_confirm -->|"确认兑换"| A3["🔗 POST /api/coupon/exchange"]:::api
A3 -->|"用户ID, 孩子ID, 优惠券ID"| S3["⚙️ CouponService.exchangeWithEnergy()"]:::service
end
%% ================================================================
%% 阶段四:能量扣除+发放+优惠券发放
%% ================================================================
subgraph 阶段四[阶段四:能量转换与优惠券发放]
S3 --> S4a{"⚙️ 校验财富能量余额<br>childId + wealth 维度"}:::service
S4a -->|"余额不足"| D4a["📦 返回错误: 财富能量不足"]:::data
S4a -->|"余额充足"| S4b["⚙️ ① 扣除财富能量<br>EnergyService.deductEnergyByCode()<br>dimensionCode=wealth"]:::service
S4b -->|"扣除N点, 写流水"| D4b["📦 energy_log 表<br>财富能量 -N"]:::data
S4b --> S4c["⚙️ ② 发放行动能量<br>EnergyService.awardEnergy()<br>sourceType=coupon_exchange<br>数量=N"]:::service
S4c -->|"发放N点, 写流水"| D4c["📦 energy_log 表<br>行动能量 +N<br>+ energy_balance 更新"]:::data
S4c --> S4d["⚙️ ③ 发放优惠券到用户账户<br>CouponService.issueToUser()"]:::service
S4d -->|"写入用户-优惠券关联"| D4d["📦 user_coupon 表<br>userId, couponId,<br>status=AVAILABLE"]:::data
S4d -->|"更新优惠券已领数量"| D4e["📦 coupon 表<br>usedCount++"]:::data
end
%% ================================================================
%% 阶段五:使用优惠券消费
%% ================================================================
subgraph 阶段五[阶段五:使用优惠券消费]
D4d --> P5["🏠 商品确认页:选择优惠券"]:::page
P5 -->|"查看可用优惠券"| A5["🔗 POST /api/coupon/list"]:::api
A5 -->|"用户ID"| S5["⚙️ CouponService.listAvailable()"]:::service
S5 -->|"返回可用优惠券列表"| D5["📦 用户可用优惠券列表"]:::data
P5 -->|"选择优惠券"| A5a["🔗 POST /api/coupon/apply"]:::api
A5a -->|"用户优惠券ID, 订单类型, 订单金额"| S5a["⚙️ CouponService.apply()"]:::service
S5a -->|"校验有效期/适用范围/最低消费"| D5a["📦 返回折扣金额"]:::data
P5 -->|"提交订单"| A5b["🔗 POST /api/product/order/create"]:::api
A5b -->|"商品ID, 数量, 优惠券ID"| S5b["⚙️ ProductOrderService.create()"]:::service
S5b -->|"创建订单, 标记优惠券"| D5b["📦 product_orders 表<br>coupon_id 关联"]:::data
D5b -->|"支付成功"| S5c["⚙️ CouponService.markUsed()"]:::service
S5c -->|"标记优惠券已使用"| D5c["📦 user_coupon 表<br>status=USED, orderId"]:::data
end
%% ================================================================
%% 角色关联
%% ================================================================
A0 -.- P2
A0 -.- P3a
A0 -.- P3b
A0 -.- P5
B0 -.- P3a
C0 -.- P1