| 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 只需「审核即完成」的根本原因。
src/views/order/components/RefundDrawer.vue(约 800 行,新建/重构)— 从 order/member.vue & course.vue 的「退款」按钮唤起,渠道优先流。
setInterval + tickerSubs 多实例订阅,美团 10min / 抖音 60min 窗口,过期 disabledresidualValue → recommendedRefundable → cappedRecommended 全 computed 链路$emit('submit') mock,待接后端// 残值:课时卡按比例;无单位数据兜底为剩余可退(要点②) 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%)。
切换渠道 / 改违约金后可退金额需实时联动;订单无单位数据时也要给出合理默认值(非 0)。
① watch + manualOverride 标志 + $nextTick 的多重异步联动时序难控,故用 computed 取代。
② queryMemberOrderDetail 返回的 MemberOrderInfoRes 无 usedTimes/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、未用的 hasSettleAmount;applyModeDefaults/reset 改为只置 manualRefundable=null。
el-input-number 的 value watcher 是 immediate:true 且在 handler 内 $emit('input')。computed setter 已用「与 cappedRecommended 近似即视为回到自动态」消化该回灌,避免误标手动覆盖。src/views/finance/refundList.vue(约 1448 行,合并自 origin/Release260309 后重构)+ 后端 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
4:'tgPlatform';channel-pill ch-4 绿色?refnum= / ?orderNumber= query 直达业务语义:用户已在美团/抖音 App 自助退款且到账,本系统仅做登记标记 + 权益扣减,不发起任何实际退款。仅对团购订单(hasTuangou)开放。
| 层 | 改动 |
|---|---|
| 前端 RefundDrawer | 新增 mode='tgPlatform' 第 4 卡片、limit/提示文案、buildPayload 分支、确认弹窗文案 |
| 前端 refundList | refundTypeList 增 {4,团购平台原路退款}、modeKey、ch-4 pill、各处文案分支 |
| 前端 RefundDetailDrawer | 4 渠道卡(ch-4) |
| 样式 | refund-drawer.scss 增 icon-tgp / pill-tgPlatform(#f0f9eb / #67c23a 绿) |
| 后端 | RefundBusiness audit/pay 的 type==4 分支(见 §4,未提交) |
团购订单核销/发权益、退款撤销核销恢复券、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→单选→tunagouBindProduct;deleteBinding→deleteTuangouProduct(二次确认);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 抽出私教课编辑弹窗,同上复用 |
| API | tunagouBindList / tunagouBindProduct / deleteTuangouProduct / storeTuangouList(@/api/mt)、tuangouKeyList(@/api/tk) |
| 路由 | 新增 router/modules/goods.js(+54);member.js/course.js 相应迁移 |
computeFinalPaid/computeFinalReceived,最终实付/实收 gated orderStatus≥2| 文件 | 改动 |
|---|---|
RefundBusiness.java | audit() autoPayout 含 1/2/4 并串接 pay();pay() 新增 payType==4 纯标记分支;type=4 成功文案 |
PrivateOrderBackImpl.java / MemberOrderBackImpl.java | list 接口 join 真实会员昵称(userBaseApi.getUsers(userIds)) |
MemberOrderListBackRes.java | 新增 private String userName; |
dev_gkbn/test2 分支但本地 master 缺失,已选择性 checkout + 补 cross-cutting 依赖(PaymentCodeAndMsg/UserRightsBusiness/OrderBusiness 等)至编译通过;财务退款单 UI 合并自 origin/Release260309 @72f0e60。线上 refund 表实际在用(约 1.9w 行)。$emit('submit') + message),主动退款发起接口待接入,需后端确认入参契约(mode/refundAmount/penaltyFee/discountSupplement/manualOverride…)。boundDealInfo 提示,需后端在 tunagouBindProduct 做唯一约束兜底,否则并发/绕过前端会产生一 deal 多商品脏数据,核销发错权益。