# 12 商城（for AI Agents）

> 关联代码：internal/service/mall/、internal/model/mall.go（Category/Product/SKU/Cart/Review/CouponTemplate/UserCoupon/CouponCode）
> 管理端页面：web/src/views/mall/；小程序页面：pages/mall/*、pages/welfare/*（福利街=积分商品视图）

====================================================================
1. 功能概述
====================================================================

商城承载：自营实物/虚拟商品销售（现金/积分双支付）、抽奖商城奖品的商品底座、CPS 外部商品（京东/淘宝）挂载佣金、优惠券体系（满减/折扣 + 积分兑换）。「福利街」Tab 是商城的积分兑换视图（仅展示 points_enabled 商品）。

====================================================================
2. 底层逻辑
====================================================================

2.1 模型
- MallCategory：分类（名称/编码/图标/排序）。
- MallProduct：商品。价格（分）、库存、轮播图（≤9）、详情 description（富文本 HTML；历史纯文本双路径兼容：前端按是否 HTML 渲染）、上架状态；PointsEnabled/PointsPrice/PerUserExchangeLimit 积分兑换区块；CPS 字段：CPSPlatform(jd/tb)、CPSCommissionRateBps、MonthlySales、RotateDate（每周一轮换约 20% 商品）。
- MallProductSKU：规格组合 SKU（规格组 → 组合生成），独立价格/库存；下单按 SKU 匹配。
- MallCart：购物车；结算走 /mall/cart/checkout 合并单（ProductType=mall_cart）。
- ProductReview：评价（唯一键 order+user+product）；visible/hidden 由后台审核切换；商家可回复。
- CouponTemplate：满减 fixed（减免额，分）/ 折扣 percent（千分位）；门槛金额、适用商品范围、兑换积分、发行总量、每人限兑、有效天数。
- UserCoupon：用户持券（含来源 batch-issue 批量发放/exchange 积分兑换）；下单核销。
- CouponCode：兑换码池（unused/assigned/used，含卡密），抽奖 code 类奖品消费。

2.2 购买链路
- 现金单：POST /mp/mall/order（单品）或 /mp/mall/cart/checkout（购物车合并）→ 选优惠券 → wx.requestPayment → 回调履约（扣 SKU 库存乐观锁 + 核销券 + 销量自增）。
- 积分兑换：POST /mp/mall/product/exchange（扣积分；扣积分与库存同事务；订单类型 mall_product_points）。
- 混合支付不存在：单要么现金（可用券）要么纯积分。
- 购买渠道开关（mall.purchase_enabled，后台「功能开关总览-商城」组，默认开启）：关闭后三个购买写接口（单品下单/加购/购物车结算）返回 403，小程序隐藏现金购买 Tab、加购与结算按钮（详情页强制积分模式，纯现金商品按钮置灰「购买暂未开放」）；积分兑换、优惠券兑换与商品浏览不受影响。开关状态经 GET /api/mp/config/mall 下发。存量待支付订单的重付（/mp/orders/:id/repay）不拦，由超时关单兜底。

2.3 评价
- 用户 POST /mp/mall/reviews（订单完成后）；后台可隐藏/恢复 + 回复。

====================================================================
3. 后台操作（商城管理菜单）
====================================================================

3.1 商品管理 /mall/products（views/mall/products/index.vue）
- 筛选：关键词 + 分类；「导出 CSV」「新增商品」。
- 表格列：名称/主图/分类/价格(元)/积分价/库存/销量/排序/上架/操作。
- 编辑弹窗：名称、主图（ImageUpload）、轮播图（MultiImageUpload ≤9）、详情 RichEditor（编辑区 341px 收窄对齐小程序行宽实时预览）、分类、价格（元）、库存、排序、上架状态 + 积分兑换区块（开关/积分价/每人限兑）+ 编辑态 SKU 管理（规格组 n-dynamic-tags →「生成 SKU 组合」→ 批量保存；已有 SKU 表可单独新增/编辑）。
- 行删除：Popconfirm。

3.2 商品分类 /mall/categories
- 筛选：关键词。
- 表格列：ID/名称/编码/图标/排序/操作。
- CRUD 弹窗：名称/编码/图标(ImageUpload)/排序。
- 行操作：编辑、删除。

3.3 优惠券模板 /mall/coupons（views/mall/coupon-templates/index.vue）
- 筛选：关键词、状态。
- 表格列：ID/名称/类型/减免值/门槛/适用商品/兑换积分/发行量/已领/剩余/有效天数/状态/操作。
- CRUD 弹窗：名称、类型 fixed 满减 / percent 折扣（千分位）、减免值、使用门槛、适用商品多选（ProductPickerModal）、兑换积分、发行总量、每人限兑、有效天数、状态。
- 「批量发放」弹窗：textarea 用户 ID 列表 → batch-issue。
- 行操作：编辑、启用/禁用、删除。

3.4 评价审核 /mall/reviews
- 筛选：关键词、状态（visible/hidden）。
- 表格列：ID/商品/用户/评分/内容/状态/操作。
- 行操作：「回复」（dialog textarea）；「隐藏/显示」切换；「删除」。

3.5 后台操作逻辑备注（隐藏约束，UI 不直接体现）
- 商品删除：物理删除商品记录；已有订单的商品快照不受影响（Order.AddressSnapshot 独立存储），但关联的 SKU 一并删除。
- 分类删除：物理删除分类记录；删除前需确认该分类下无商品（有商品的分类删除会被拒绝或导致商品分类字段悬空）。
- 优惠券折扣单位：percent 类型为千分位（950 = 95 折 / 5% off），fixed 类型减免额单位为分——表单输入时注意换算。
- 评价只可隐藏不可真删：后台「删除」评价实际为 hidden 状态切换，数据仍保留；商家回复后对用户可见。
- SKU 库存独立：商品库存与各 SKU 库存是两级结构，下单按 SKU 扣减；生成 SKU 组合后需逐一检查各 SKU 库存是否为 0。

====================================================================
4. 小程序端操作
====================================================================

- 商城列表 pages/mall/index/index：分类切换、搜索（400ms 防抖）、分页。
- 商品详情 pages/mall/detail/index：轮播、SKU 规格选择匹配、优惠券选择、现金购买（下单→requestPayment）/积分兑换（PointsEnabled 时）、加购物车、评价列表。
- 购物车 pages/mall/cart/index：多选、整单券、选地址、结算支付。
- 订单 pages/mall/orders/index + order-detail/index：重付/取消/确认收货/物流（见 11）。
- 优惠券 pages/mall/coupons/index：可兑模板（积分兑换）+ 我的券两 Tab。
- 福利街 pages/welfare/index：商城积分商品专视图，顶部入口跳完整商城。

====================================================================
5. API 速查
====================================================================

小程序端（/api/mp）：
GET  /api/mp/mall/categories|products|products/:id|skus     商城浏览
GET  /api/mp/config/mall                      购买渠道开关（purchase_enabled）
POST /api/mp/mall/order                        单品现金下单（购买开关关闭时 403）
GET|POST|PUT|DELETE /api/mp/mall/cart(/:id)    购物车（POST 加购受购买开关限制）；POST /api/mp/mall/cart/checkout 合并结算（同受开关限制）
POST /api/mp/mall/product/exchange             积分兑换下单
GET  /api/mp/mall/exchange-orders              兑换订单
POST /api/mp/mall/reviews；GET /api/mp/mall/products/:id/reviews   评价
GET  /api/mp/mall/coupon-templates             可兑券模板
POST /api/mp/mall/coupon/exchange              积分兑券
GET  /api/mp/mall/coupons(/:id)                我的券

管理端（/api/admin，权限 mall:read/write）：
GET|POST|PUT|DELETE /api/admin/mall/categories(:id)
GET|POST|PUT|DELETE /api/admin/mall/products(:id)（+ /export）
GET|POST|PUT|DELETE /api/admin/mall/products/:id/skus(:skuId)
GET  /api/admin/mall/products/:id/specs                       规格组
GET  /api/admin/mall/reviews
GET|POST|PUT|DELETE /api/admin/mall/coupon-templates(:id)；POST /:id/batch-issue 批量发放

====================================================================
6. 注意事项
====================================================================

- 商品详情 description 是 HTML 富文本（wangeditor 产出）；旧数据可能是纯文本，前端双路径渲染，导入/迁移数据时保持该约定。
- SKU 库存与商品库存是两级：下单按 SKU 扣减；配置商品时生成 SKU 组合后仍需检查各 SKU 库存。
- 优惠券折扣 percent 是千分位（例如 950=95 折）；满减 fixed 金额单位分。
- CPS 商品（jd/tb）不参与自营履约：无库存扣减/发货，跳转外部平台（见 07 的 cps-click 上报与 17 的佣金结算）。
- 评价不可删（仅隐藏）；商家回复对用户可见。
- 积分兑换单退款会退积分回库存（见 11 退款白名单）。
- 「商城购买」开关只关现金购买渠道：积分兑换（商品/优惠券）与商品浏览始终可用，福利街不受影响。
