```
feat(split): 添加分账诊断功能用于后台展示 - 新增 SplitCalculator::diagnose() 方法,用于诊断订单分账问题 - 返回分账来源、佣金总额、规则总额及具体问题说明 - 在管理员分账订单控制器中集成诊断信息显示 - 优化前端表格显示分账来源标签和诊断提示信息 - 修复佣金奖励查询中 agent_id 使用错误的问题 ```
这个提交包含在:
@@ -58,6 +58,92 @@ class SplitCalculator
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊断为何未走佣金分账(供后台展示)
|
||||
* @return array{amount_source:string,final_source:string,issues:array,commission_total:float,rule_total:float}
|
||||
*/
|
||||
public function diagnose($orderId)
|
||||
{
|
||||
$order = Order::get($orderId);
|
||||
$amountSource = $this->config->getAmountSource();
|
||||
$issues = [];
|
||||
$commissionTotal = 0.0;
|
||||
|
||||
if (!$order) {
|
||||
return [
|
||||
'amount_source' => $amountSource,
|
||||
'final_source' => 'none',
|
||||
'issues' => ['订单不存在'],
|
||||
'commission_total'=> 0,
|
||||
'rule_total' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$payFee = floatval($order['pay_fee']);
|
||||
$commissionRewards = CommissionReward::where('order_id', $orderId)->where('status', '>=', 0)->select();
|
||||
if ($commissionRewards->isEmpty()) {
|
||||
$issues[] = '该订单无分销佣金记录(未产生推广关系或未参与分销)';
|
||||
}
|
||||
|
||||
foreach ($commissionRewards as $reward) {
|
||||
$userId = intval($reward['agent_id']);
|
||||
$amount = floatval($reward['commission']);
|
||||
if ($userId <= 0) {
|
||||
$issues[] = '佣金记录 agent_id 无效';
|
||||
continue;
|
||||
}
|
||||
if ($amount <= 0) {
|
||||
continue;
|
||||
}
|
||||
$receiver = $this->getReceiver('promoter', $userId);
|
||||
if (!$receiver) {
|
||||
$issues[] = sprintf(
|
||||
'推广员#%d 佣金¥%s 无汇付接收方(分账接收方 role_id 须等于该 user_id)',
|
||||
$userId,
|
||||
number_format($amount, 2, '.', '')
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$commissionTotal += $amount;
|
||||
}
|
||||
|
||||
$regionalRewards = $this->getRegionalRewardsByOrderId($orderId);
|
||||
foreach ($regionalRewards as $reward) {
|
||||
$uid = intval($reward['agent_user_id']);
|
||||
$amount = floatval($reward['commission']);
|
||||
if ($uid <= 0 || $amount <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!$this->getReceiver('regional_agent', $uid)) {
|
||||
$issues[] = sprintf('区域代理#%d 佣金¥%s 无汇付接收方', $uid, number_format($amount, 2, '.', ''));
|
||||
continue;
|
||||
}
|
||||
$commissionTotal += $amount;
|
||||
}
|
||||
|
||||
$commissionItems = $this->calculateFromCommission($orderId);
|
||||
$ruleItems = $this->calculateFromRules($payFee);
|
||||
$ruleTotal = array_sum(array_column($ruleItems, 'amount'));
|
||||
|
||||
$finalSource = 'none';
|
||||
if (!empty($commissionItems) && in_array($amountSource, ['commission', 'mixed'])) {
|
||||
$finalSource = 'commission';
|
||||
} elseif (!empty($ruleItems) && ($amountSource === 'rule' || ($amountSource === 'mixed' && empty($commissionItems)))) {
|
||||
$finalSource = 'rule';
|
||||
if ($amountSource === 'mixed' && !empty($issues)) {
|
||||
$issues[] = '已回退到分账规则(如推广员 5%),金额与佣金不一致';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'amount_source' => $amountSource,
|
||||
'final_source' => $finalSource,
|
||||
'issues' => $issues,
|
||||
'commission_total' => round($commissionTotal, 2),
|
||||
'rule_total' => round($ruleTotal, 2),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getRegionalRewardsByOrderId($orderId)
|
||||
{
|
||||
try {
|
||||
@@ -78,9 +164,9 @@ class SplitCalculator
|
||||
|
||||
$commissionRewards = CommissionReward::where('order_id', $orderId)->where('status', '>=', 0)->select();
|
||||
foreach ($commissionRewards as $reward) {
|
||||
$agent = \addons\shopro\model\commission\Agent::get($reward['agent_id']);
|
||||
$userId = $agent ? $agent['user_id'] : 0;
|
||||
if (!$userId) {
|
||||
// agent_id 即分销商 user_id(shopro_commission_agent 主键)
|
||||
$userId = intval($reward['agent_id']);
|
||||
if ($userId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$receiver = $this->getReceiver('promoter', $userId);
|
||||
|
||||
@@ -148,6 +148,13 @@ class Order extends Base
|
||||
$data['receiver_summary'] = $summaryLines ? implode(';', $summaryLines) : '—';
|
||||
$data['fail_reason'] = $this->formatFailReason($data['wx_result'] ?? '', $status);
|
||||
|
||||
$diag = (new SplitCalculator())->diagnose($orderId);
|
||||
$data['split_source'] = $diag['final_source'];
|
||||
$data['split_source_text'] = $this->formatSplitSourceText($diag);
|
||||
$data['split_diagnose'] = $diag['issues'];
|
||||
$data['commission_total'] = $diag['commission_total'];
|
||||
$data['rule_total'] = $diag['rule_total'];
|
||||
|
||||
unset($data['order'], $data['receivers']);
|
||||
$rows[] = $data;
|
||||
}
|
||||
@@ -280,6 +287,22 @@ class Order extends Base
|
||||
return $mobile;
|
||||
}
|
||||
|
||||
protected function formatSplitSourceText(array $diag)
|
||||
{
|
||||
$map = [
|
||||
'commission' => '按佣金分账',
|
||||
'rule' => '按规则分账(兜底)',
|
||||
'none' => '未算出明细',
|
||||
];
|
||||
$text = $map[$diag['final_source'] ?? ''] ?? '-';
|
||||
if (($diag['final_source'] ?? '') === 'commission' && ($diag['commission_total'] ?? 0) > 0) {
|
||||
$text .= ' ¥' . number_format(floatval($diag['commission_total']), 2, '.', '');
|
||||
} elseif (($diag['final_source'] ?? '') === 'rule' && ($diag['rule_total'] ?? 0) > 0) {
|
||||
$text .= ' ¥' . number_format(floatval($diag['rule_total']), 2, '.', '');
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function formatFailReason($wxResult, $status)
|
||||
{
|
||||
if ($status !== 4 || $wxResult === '' || $wxResult === null) {
|
||||
|
||||
@@ -164,6 +164,24 @@
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.source-tag {
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.source-tag.commission { background: #ecf5ff; color: #409eff; }
|
||||
.source-tag.rule { background: #fdf6ec; color: #e6a23c; }
|
||||
.diagnose-hint {
|
||||
font-size: 11px;
|
||||
color: #e6a23c;
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
white-space: normal;
|
||||
}
|
||||
.op-hint {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
@@ -230,11 +248,14 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="128" align="right">
|
||||
<el-table-column label="金额" width="138" align="right">
|
||||
<template slot-scope="s">
|
||||
<div class="amount-cell">
|
||||
<div class="money-line">订单 <span class="money-text">¥{{ formatMoney(s.row.total_amount) }}</span></div>
|
||||
<div class="money-line split-line">分账 <span class="money-text">¥{{ formatMoney(s.row.split_amount) }}</span></div>
|
||||
<span v-if="s.row.split_source_text" :class="'source-tag ' + (s.row.split_source === 'commission' ? 'commission' : 'rule')">
|
||||
{{ s.row.split_source_text }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -252,6 +273,9 @@
|
||||
<span class="recv-amt">¥{{ formatMoney(r.amount) }}</span>
|
||||
</div>
|
||||
<div v-if="s.row.receiver_preview" class="recv-preview-hint">按当前规则预估(历史失败单未落库明细)</div>
|
||||
<div v-if="s.row.split_diagnose && s.row.split_diagnose.length && s.row.split_source === 'rule'" class="diagnose-hint">
|
||||
{{ s.row.split_diagnose.join(';') }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="cell-sub-plain">—</span>
|
||||
</template>
|
||||
|
||||
在新工单中引用
屏蔽一个用户