diff --git a/.crmeb/skills/crmeb-order-dev/references/order-aftersale.md b/.crmeb/skills/crmeb-order-dev/references/order-aftersale.md index 359bc98fe..8af2c818b 100644 --- a/.crmeb/skills/crmeb-order-dev/references/order-aftersale.md +++ b/.crmeb/skills/crmeb-order-dev/references/order-aftersale.md @@ -72,7 +72,7 @@ AfterSaleServices::refund - Handler 中的资金/资源副作用一律经 `RefundEffectContextInterface::once()` 登记去重,禁止直接裸写。 - 中台 `refund_status`(0 无 / 1 申请中 / 2 已退)由 Adapter `resolveRefundStatus` 推导,退款完成时 `syncStatus` 同步。 -**资金流水(`eb_capital_flow`)只记真实资金进出**:判定统一入口为 `PayServices::isCashChannel()`(第三方在线渠道 + 线下支付;余额 `yue`、后台代付、0 元单一律 false)。`CapitalFlowServices::setFlow()` 内部已做拦截,退款链路在调用前还要排除 `refund_type=balance` / `refund_method=balance` 的站内结转,即「余额支付订单退款」「原路退款但改退余额」都不产生现金流出,不记流水。 +**资金流水(`eb_capital_flow`)只记真实资金进出**:判定统一入口为 `PayServices::isCashChannel()`(第三方在线渠道 + 线下支付;余额 `yue`、后台代付、0 元单一律 false)。`CapitalFlowServices::setFlow()` 内部已做拦截,退款链路在调用前还要排除 `refund_type=balance` / `refund_method=balance` 的站内结转,即「余额支付订单退款」「原路退款但改退余额」都不产生现金流出,不记流水。读取侧口径一致:`CapitalFlow::searchCashOnlyAttr` 在列表、导出、账单统计中统一排除 `pay_type` 为 `yue` / `admin` / 空的历史流水,保证后台资金流水页面只出现现金渠道记录。 > **现状说明**:`OrderRefundCreateAfter / OrderRefundRefuse / OrderRefundCancelAfter` 的模块监听器已注册但服务层触发点待接线(见 [订单生命周期·事件地图](order-lifecycle.md#三订单事件地图)),相关后置逻辑目前挂在 `AfterSaleServices` 内部回调(`lifecycle->afterApply/afterAudit`)。 diff --git a/backend/app/model/system/statistics/CapitalFlow.php b/backend/app/model/system/statistics/CapitalFlow.php index d8da50795..98c691dbc 100644 --- a/backend/app/model/system/statistics/CapitalFlow.php +++ b/backend/app/model/system/statistics/CapitalFlow.php @@ -59,4 +59,18 @@ class CapitalFlow extends BaseModel { if (!empty($value)) $query->whereIn('id', $value); } + + /** + * 仅现金渠道搜索器 + * 资金流水只统计真实资金进出:余额支付(yue)、后台代付(admin)、0 元单(空) 属于站内结算, + * 即使历史数据中存在也不在资金流水、账单中展示。 + * @param $query + * @param $value + */ + public function searchCashOnlyAttr($query, $value) + { + if ($value) { + $query->whereNotIn('pay_type', ['', 'yue', 'admin']); + } + } } diff --git a/backend/app/services/statistic/CapitalFlowServices.php b/backend/app/services/statistic/CapitalFlowServices.php index 047edb371..77cd5df1d 100644 --- a/backend/app/services/statistic/CapitalFlowServices.php +++ b/backend/app/services/statistic/CapitalFlowServices.php @@ -128,12 +128,25 @@ class CapitalFlowServices extends BaseService { $export = $where['export'] ?? 0; unset($where['export']); + // 资金流水只展示现金渠道:余额支付、后台代付、0 元单等站内结算即使有历史数据也不展示 + $where['cash_only'] = 1; [$page, $limit] = $this->getPageValue(); - $status = ['全部', '支付订单', '订单退款', '充值订单', '充值退款', '抽奖红包', '佣金提现', '购买会员', '线下收银']; + // 全量类型文案(用于列表/导出展示,历史数据可能包含已下线的类型) + $typeLabels = [0 => '全部', 1 => '订单支付', 2 => '订单退款', 3 => '充值订单', 4 => '充值退款', 5 => '抽奖红包', 6 => '佣金提现', 7 => '购买会员', 8 => '线下收银']; + // 筛选下拉只保留资金流水页面实际使用的现金交易类型;value 与 trading_type 一一对应,不依赖数组下标 + $type_options = [ + ['label' => '全部', 'value' => 0], + ['label' => '订单支付', 'value' => 1], + ['label' => '订单退款', 'value' => 2], + ['label' => '购买会员', 'value' => 7], + ['label' => '线下收银', 'value' => 8], + ]; + // 兼容旧前端:status 为下标即 trading_type 的全量类型列表;新前端改用 type_options + $status = ['全部', '订单支付', '订单退款', '充值订单', '充值退款', '抽奖红包', '佣金提现', '购买会员', '线下收银']; $list = $this->dao->getList($where, $page, $limit); foreach ($list as &$item) { $item['add_time'] = date('Y-m-d H:i:s', $item['add_time']); - $item['trading_type'] = $status[$item['trading_type']]; + $item['trading_type'] = $typeLabels[$item['trading_type']] ?? '其他'; $item['pay_type_name'] = PayServices::getPayType()[$item['pay_type'] != 'routine' ? $item['pay_type'] : 'weixin'] ?? '其他方式'; } $count = $this->dao->count($where); @@ -143,7 +156,7 @@ class CapitalFlowServices extends BaseService $fileName = '账单导出' . date('YmdHis') . rand(1000, 9999); return compact('list', 'fileKey', 'header', 'fileName'); } else { - return compact('list', 'count', 'status'); + return compact('list', 'count', 'status', 'type_options'); } } @@ -170,6 +183,8 @@ class CapitalFlowServices extends BaseService */ public function getFlowRecord($where) { + // 账单统计与流水列表口径保持一致,只统计现金渠道 + $where['cash_only'] = 1; [$page, $limit] = $this->getPageValue(); $data = $this->dao->getRecordList($where, $page, $limit); $i = 1; diff --git a/frontend/admin/src/pages/finance/capitalFlow/index.vue b/frontend/admin/src/pages/finance/capitalFlow/index.vue index 44691974a..9cdfcf88a 100644 --- a/frontend/admin/src/pages/finance/capitalFlow/index.vue +++ b/frontend/admin/src/pages/finance/capitalFlow/index.vue @@ -23,8 +23,8 @@ > - - + + @@ -73,9 +73,7 @@ @@ -129,29 +127,8 @@ const modals = ref(false); const total = ref(0); const loading = ref(false); const tabList = ref([]); -const withdrawal = ref([]); -const payment = [ - { - title: '全部', - value: '', - }, - { - title: '微信', - value: 'weixin', - }, - { - title: '支付宝', - value: 'alipay', - }, - { - title: '银行卡', - value: 'bank', - }, - { - title: '线下支付', - value: 'offline', - }, -]; +// 交易类型筛选:后端返回 [{label, value}],value 直接对应 trading_type +const typeOptions = ref([]); const formValidate = ref({ trading_type: 0, time: '', @@ -214,7 +191,10 @@ function getList() { let data = res.data; tabList.value = data.list; total.value = data.count; - withdrawal.value = data.status; + typeOptions.value = + data.type_options && data.type_options.length + ? data.type_options + : (data.status || []).map((label, value) => ({ label, value })); loading.value = false; }) .catch((res) => {