技 术 评 审 版

商品 · 订单 · 退款交易中心优化改造

前端 lianer-backend-ui (Vue 2 / Element UI) · 后端 lianer-back (Spring Boot / Dubbo / Shenyu) · 分支 商品和订单交易中心优化改造V0510
技术范围概述本次改造以退款域为核心,重构主动退款抽屉、退款单审批流、订单列表与详情展示,新增团购平台原路退款渠道(refundType=4),并重构会员卡/私教 ↔ 团购商品 一对多绑定(门店配卡页)。前端改动已提交至 V0510 分支并推送;后端改动遵循「先改不提交」,仅在 lianer-back 工作区修改未 commit。
4退款渠道(含新增 refundType=4)
5退款单状态机节点
1→N商品↔团购 一对多绑定
2后端服务改动(未提交)
① 退款渠道模型与状态机 ② RefundDrawer 主动退款抽屉 ③ 可退金额计算与两个关键实现点 ④ 退款单审批流改造(跳过财务) ⑤ refundType=4 团购平台原路退款 ⑥ 商品 ↔ 团购 一对多映射重构 ⑦ 订单列表 / 详情抽屉重构 ⑧ 后端改动清单(未提交) ⑨ 风险点 / 待接入 / 评审关注

1退款渠道模型与状态机

refundType 渠道枚举

refundType渠道资金动作审核后流向退款上限
1原路退回泰融支付通道原路退审核 → 自动完成(status=3)实收金额
2撤销核销团购通道撤销、恢复券审核 → 自动完成(status=3)整笔 paidAmount
3财务打款线下银行转账审核 → 待打款(status=2)→ 财务回填OA → 完成最终实收 = (实收−已退)×(1−费率)
4 新增团购平台原路退款无资金动作,仅标记+扣权益审核 → 自动完成(status=3)团购实付金额

渠道在 apply() 阶段由原订单 payType 决定,不是用户随意选;权益扣减也在 apply() 阶段(refundMemCardOrder/refundPrivate)完成,因此 audit/pay 仅做状态流转——这正是 refundType=4 只需「审核即完成」的根本原因。

status 状态机

status=1待审核 type 1/2/4 → status=3 type 3 → status=2 驳回 → status=-1 待打款 pay()财务回填OA status=3 打款完成终态 status=-2 用户取消
1 待审核2 审核待打款3 打款完成(终态)-1 运营驳回-2 用户取消

2RefundDrawer 主动退款抽屉前端

src/views/order/components/RefundDrawer.vue(约 800 行,新建/重构)— 从 order/member.vue & course.vue 的「退款」按钮唤起,渠道优先流。

组件协作RefundDrawer(发起)/ refundList.vue 内嵌处理抽屉(审批+打款,真实链路)/ RefundDetailDrawer(只读详情,订单列表「详情」入口)三者共用 refund-drawer.scss 视觉语言与渠道 pill(含 ch-4 绿色)。

3可退金额计算 + 两个关键实现点前端

计算链路

// 残值:课时卡按比例;无单位数据兜底为剩余可退(要点②)
residualValue   = !totalUnits ? remainingPayable
                              : round2(paidAmount * remainUnits / totalUnits)
recommendedRefundable = max(0, residualValue - penaltyFee - discountSupplement)
maxRefund       = mode==='tuangou' ? paidAmount : min(channelMax, remainingPayable)
cappedRecommended     = min(recommendedRefundable, maxRefund)
// 财务打款上限 = 最终实收(要点①)
finalReceived   = round2(max(0, paidAmount - alreadyRefunded) * (1 - feeRate))
                  feeRate = hasTuangou ? 0.08 : 0.006

要点 ① 财务打款上限 = 最终实收

channelMax() 的 offline 分支取 finalReceived(最终实收),label 为「最终实收(通道/平台扣费后)」,渠道卡副标题与上限提示按费率口径说明。与订单列表 computeFinalReceived 口径一致(团购 8% / 通道 0.6%)。

要点 ② 可退金额自动联动(computed 驱动)

要满足的联动

切换渠道 / 改违约金后可退金额需实时联动;订单无单位数据时也要给出合理默认值(非 0)。

为何用 computed 而非 watch

① watch + manualOverride 标志 + $nextTick 的多重异步联动时序难控,故用 computed 取代。
queryMemberOrderDetail 返回的 MemberOrderInfoResusedTimes/surplusTimes,时长卡/年卡 times 可空 → totalUnits=0,若残值硬返回 0 推荐值会塌成 0,故无单位数据时残值兜底为剩余可退。

// 重构:computed 驱动取代 watch/nextTick
isManualRefundable() { return this.manualRefundable !== null }
effectiveRefundable: {
  get() { return manualRefundable != null ? manualRefundable : cappedRecommended },
  set(v){ const n=num(v); manualRefundable = |n-cappedRecommended|<0.001 ? null : n }
}
// 兜底:无单位数据 → 残值 = 剩余可退(实付−已退),而非 0
residualValue(){ if(!totalUnits) return this.remainingPayable; ... }

同步删除 syncRefundable/onRefundableChange/resetRefundable 方法、form.penaltyFee/discountSupplement 两个 watch、未用的 hasSettleAmountapplyModeDefaults/reset 改为只置 manualRefundable=null

注意 Element UI 2.15.14 坑el-input-numbervalue watcher 是 immediate:true 且在 handler 内 $emit('input')。computed setter 已用「与 cappedRecommended 近似即视为回到自动态」消化该回灌,避免误标手动覆盖。

4退款单审批流改造(跳过财务)前端后端

src/views/finance/refundList.vue(约 1448 行,合并自 origin/Release260309 后重构)+ 后端 RefundBusiness.java

后端 RefundBusiness.java(未提交)

audit(): autoPayout = refundType==1 || refundType==2 || refundType==4
        → 命中则 audit 内串接 pay(),status 直推 3
        → refundType==4 成功文案「审核通过,已标记团购平台原路退款」
pay() : 新增 else-if payType==4 分支 —— 纯标记:
        setRefundType(4)/setStatus(3)/setStatusName("打款完成")/setPayTime
        不走通道、不动 tuangou_order

前端 refundList.vue

5refundType=4 团购平台原路退款本次新增

业务语义:用户已在美团/抖音 App 自助退款且到账,本系统仅做登记标记 + 权益扣减,不发起任何实际退款。仅对团购订单(hasTuangou)开放。

改动
前端 RefundDrawer新增 mode='tgPlatform' 第 4 卡片、limit/提示文案、buildPayload 分支、确认弹窗文案
前端 refundListrefundTypeList 增 {4,团购平台原路退款}、modeKey、ch-4 pill、各处文案分支
前端 RefundDetailDrawer4 渠道卡(ch-4)
样式refund-drawer.scss 增 icon-tgp / pill-tgPlatform(#f0f9eb / #67c23a 绿)
后端RefundBusiness audit/pay 的 type==4 分支(见 §4,未提交)

6商品 ↔ 团购 一对多映射重构前端

团购订单核销/发权益、退款撤销核销恢复券、refundType=4 扣权益,全部依赖「内部商品 ↔ 团购套餐」绑定关系。本次将该能力从旧 groupCoupon/bingCoupon.vue 等分散入口,重构进 member/config.vue(门店配卡页)。

数据模型(一对多约束)

// 内部商品(会员卡 cardId / 私教课 courseId) 1 ── N 团购绑定
binding = { channel:1美团点评|2抖音, dealId, dealGroupId,
            procuctType:1会员卡|2私教课, procuctId, procuctName }
// 反向唯一:一个 dealId 只能绑一个内部商品 → boundDealInfo(dealId) 命中即提示「已绑定到 XX」
层 / 文件改动
member/config.vue去 el-table 改门店纵向面板;每店左右分列会员卡/私教,商品卡片化;卡片下 getCardBindings/getCourseBindings 渲染绑定 chip(binding-mt/binding-dy);openAddBinding 选商户 key→storeTuangouList 拉 deals→单选→tunagouBindProductdeleteBindingdeleteTuangouProduct(二次确认);boundDealInfo 做反向唯一校验提示;loading 覆盖 stores+cards+courses+bindings 全量
member/components/BatchBindDrawer.vue 新建从 groupCoupon/bingCoupon.vue 移植的批量绑定抽屉,顶部「批量绑定团购」入口
member/components/CardEditDialog.vue 新建从 member/index.vue 抽出会员卡编辑弹窗,配卡页点卡片直接复用
course/components/CourseEditDialog.vue 新建从 course/index.vue 抽出私教课编辑弹窗,同上复用
APItunagouBindList / tunagouBindProduct / deleteTuangouProduct / storeTuangouList(@/api/mt)、tuangouKeyList(@/api/tk)
路由新增 router/modules/goods.js(+54);member.js/course.js 相应迁移
内部商品 cardId/ courseId deal A · 美团点评 deal B · 美团点评 deal C · 抖音 核销时 dealId → 内部商品发对应权益 / 退款扣对应权益
▲ 商品 1 ─ N 团购绑定 + 反向唯一 示意图(非真机界面)
门店配卡页(绑定主界面)+ 新增绑定弹窗 + 批量绑定抽屉
会员卡管理 → 门店配卡  |  路由 member/config

7订单列表 / 详情抽屉重构前端

列表 order/member.vue · course.vue

  • 8→少列:退款并入「金额」列;团购信息并入「订单状态」列
  • order_price 统一「应付」语义;新增 computeFinalPaid/computeFinalReceived,最终实付/实收 gated orderStatus≥2
  • 用户列:displayUserName + ID + 全新/复购/体验 tag 聚合;移除重复体验课 tag
  • chlType=1 文案「正常下单」→「小程序下单」
  • 补录审核挪到「订单」列(语义聚合方案2);补录订单 dialog→drawer
  • course.vue 分配教练抽屉重构:open-first + async load + 搜索 + 卡片,避免加载「卡死」体感

新建/重构组件

  • OrderPaymentDrawer.vue 支付/团购核销详情抽屉(按 chlType 4/5 分两种形态)
  • RefundDetailDrawer.vue 只读退款详情抽屉
  • TuangouVerifyDialog.vue 团购核销弹窗重构(门店 storeListRich 下拉、券结果卡)
  • CancelVerifyButton.vue / tuangouPending.vue 团购待核销页
  • 共享 adjust-order-drawer.scss(补录抽屉)
  • 全局团购渠道名「美团」→「美团点评」
订单列表新版 + 详情抽屉群
订单管理 → 会员卡订单 / 私教订单  |  member.vue · course.vue

8后端改动清单(lianer-back,未提交)后端

文件改动
RefundBusiness.javaaudit() autoPayout 含 1/2/4 并串接 pay();pay() 新增 payType==4 纯标记分支;type=4 成功文案
PrivateOrderBackImpl.java / MemberOrderBackImpl.javalist 接口 join 真实会员昵称(userBaseApi.getUsers(userIds)
MemberOrderListBackRes.java新增 private String userName;
提交策略后端按指示「先改不提交」——lianer-back 工作区已改但未 commit;前端每步 commit + push 到 V0510 分支。
退款模块来源完整退款模块(entities/mappers/api/service 共 57 文件)原存在于 dev_gkbn/test2 分支但本地 master 缺失,已选择性 checkout + 补 cross-cutting 依赖(PaymentCodeAndMsg/UserRightsBusiness/OrderBusiness 等)至编译通过;财务退款单 UI 合并自 origin/Release260309 @72f0e60。线上 refund 表实际在用(约 1.9w 行)。

9风险点 / 待接入 / 评审关注