```
feat(split): 优化分账计算诊断功能 - 添加用户信息格式化方法,显示带脱敏手机号的用户简要信息 - 实现详细的汇付接收方缺失诊断逻辑,提供具体的错误原因 - 新增分账明细为空时的详细失败消息格式化功能 - 增强后台订单分账失败原因展示,支持完整和摘要两种显示方式 - 添加规则接收方诊断功能,检测分账规则配置问题 - 优化错误信息的可读性和排查效率 ```
这个提交包含在:
@@ -7,7 +7,9 @@ use addons\shopro\model\split\Receiver;
|
||||
use addons\shopro\model\split\Rule;
|
||||
use addons\shopro\model\commission\Reward as CommissionReward;
|
||||
use addons\shopro\model\regional_agent\Reward as RegionalReward;
|
||||
use addons\shopro\model\User;
|
||||
use addons\shopro\model\UserOauth;
|
||||
use think\Db;
|
||||
|
||||
class SplitCalculator
|
||||
{
|
||||
@@ -98,9 +100,10 @@ class SplitCalculator
|
||||
$receiver = $this->getReceiver('promoter', $userId);
|
||||
if (!$receiver) {
|
||||
$issues[] = sprintf(
|
||||
'推广员#%d 佣金¥%s:未配置汇付接收方(role_id 须等于该 user_id)',
|
||||
$userId,
|
||||
number_format($amount, 2, '.', '')
|
||||
'推广员 %s 佣金¥%s:%s',
|
||||
$this->formatUserBrief($userId),
|
||||
number_format($amount, 2, '.', ''),
|
||||
$this->describeHuifuReceiverGap('promoter', $userId)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -115,7 +118,12 @@ class SplitCalculator
|
||||
continue;
|
||||
}
|
||||
if (!$this->getReceiver('regional_agent', $uid)) {
|
||||
$issues[] = sprintf('区域代理#%d 佣金¥%s 无汇付接收方', $uid, number_format($amount, 2, '.', ''));
|
||||
$issues[] = sprintf(
|
||||
'区域公司 %s 佣金¥%s:%s',
|
||||
$this->formatUserBrief($uid),
|
||||
number_format($amount, 2, '.', ''),
|
||||
$this->describeHuifuReceiverGap('regional_agent', $uid)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$commissionTotal += $amount;
|
||||
@@ -133,6 +141,8 @@ class SplitCalculator
|
||||
if ($amountSource === 'mixed' && !empty($issues)) {
|
||||
$issues[] = '已回退分账规则(如 5%),与佣金金额不一致';
|
||||
}
|
||||
} elseif (empty($ruleItems) && empty($commissionItems) && in_array($amountSource, ['rule', 'mixed'])) {
|
||||
$issues = array_merge($issues, $this->diagnoseRuleReceivers($payFee));
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -144,6 +154,153 @@ class SplitCalculator
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 分账明细为空时的可读失败说明(写入 wx_result / 后台原因)
|
||||
*/
|
||||
public function formatEmptySplitFailureMessage($orderId)
|
||||
{
|
||||
$diag = $this->diagnose($orderId);
|
||||
$issues = array_filter($diag['issues'] ?? []);
|
||||
if ($issues) {
|
||||
return '分账明细为空:' . implode(';', $issues);
|
||||
}
|
||||
return '分账明细为空:请检查分账金额来源、汇付接收方(huifu_id)与佣金/规则是否大于0';
|
||||
}
|
||||
|
||||
protected function diagnoseRuleReceivers($payFee)
|
||||
{
|
||||
$issues = [];
|
||||
if (!$this->config->isHuifuMode()) {
|
||||
return $issues;
|
||||
}
|
||||
$rules = Rule::where('status', 'normal')->order('priority desc')->select();
|
||||
if ($this->isEmptyResult($rules)) {
|
||||
$issues[] = '无生效分账规则,且佣金侧未生成有效接收方';
|
||||
return $issues;
|
||||
}
|
||||
foreach ($this->toResultList($rules) as $rule) {
|
||||
if (in_array($rule['role_type'], ['platform'], true)) {
|
||||
continue;
|
||||
}
|
||||
$receiver = $this->getReceiver($rule['role_type'], 0, true);
|
||||
if ($receiver && trim((string) ($receiver['receiver_account'] ?? '')) !== '') {
|
||||
continue;
|
||||
}
|
||||
$roleLabel = $rule['role_type'] === 'regional_agent' ? '区域公司' : '推广员';
|
||||
$ruleVal = $rule['rule_type'] === 'rate'
|
||||
? (floatval($rule['rule_value']) . '%')
|
||||
: ('¥' . number_format(floatval($rule['rule_value']), 2, '.', ''));
|
||||
$issues[] = sprintf(
|
||||
'规则兜底「%s %s」无汇付 huifu_id:请在分账接收方配置或完成对应角色进件',
|
||||
$roleLabel,
|
||||
$ruleVal
|
||||
);
|
||||
}
|
||||
if (!$issues) {
|
||||
$issues[] = '规则兜底金额为 0 或超过分账上限,未生成有效明细';
|
||||
}
|
||||
return $issues;
|
||||
}
|
||||
|
||||
protected function formatUserBrief($userId)
|
||||
{
|
||||
$userId = intval($userId);
|
||||
if ($userId <= 0) {
|
||||
return '用户#?';
|
||||
}
|
||||
$user = User::where('id', $userId)->field('id,nickname,mobile')->find();
|
||||
if (!$user) {
|
||||
return '用户#' . $userId;
|
||||
}
|
||||
$nickname = trim((string) ($user['nickname'] ?? ''));
|
||||
$mobile = trim((string) ($user['mobile'] ?? ''));
|
||||
if ($nickname !== '' && $mobile !== '') {
|
||||
return $nickname . ' #' . $userId . ' ' . $this->maskMobile($mobile);
|
||||
}
|
||||
if ($nickname !== '') {
|
||||
return $nickname . ' #' . $userId;
|
||||
}
|
||||
if ($mobile !== '') {
|
||||
return '用户#' . $userId . ' ' . $this->maskMobile($mobile);
|
||||
}
|
||||
return '用户#' . $userId;
|
||||
}
|
||||
|
||||
protected function maskMobile($mobile)
|
||||
{
|
||||
$mobile = trim((string) $mobile);
|
||||
if (strlen($mobile) >= 11) {
|
||||
return substr($mobile, 0, 3) . '****' . substr($mobile, -4);
|
||||
}
|
||||
return $mobile;
|
||||
}
|
||||
|
||||
protected function describeHuifuReceiverGap($roleType, $userId)
|
||||
{
|
||||
if (!$this->config->isHuifuMode()) {
|
||||
return '未配置分账接收方';
|
||||
}
|
||||
$userId = intval($userId);
|
||||
$receiver = Receiver::where('role_type', $roleType)
|
||||
->where('role_id', $userId)
|
||||
->where('status', 'normal')
|
||||
->where(function ($q) {
|
||||
$q->where('channel', 'huifu')->whereOr('channel', null)->whereOr('channel', '');
|
||||
})
|
||||
->find();
|
||||
if ($receiver && trim((string) ($receiver['receiver_account'] ?? '')) !== '') {
|
||||
return '接收方已配置,请检查 huifu_id 是否在汇付控台绑定';
|
||||
}
|
||||
|
||||
if (!class_exists('\\addons\\shopro_huifu\\model\\Onboard')) {
|
||||
return '未配置汇付分账接收方(huifu_id),请完成进件或后台添加接收方';
|
||||
}
|
||||
|
||||
$roleRefId = $this->resolveOnboardRoleRefId($roleType, $userId);
|
||||
$onboard = \addons\shopro_huifu\model\Onboard::findByRole($userId, $roleType, $roleRefId);
|
||||
if (!$onboard) {
|
||||
return '未汇付进件,请用户在小程序完成进件绑定或后台代提交';
|
||||
}
|
||||
$status = (string) ($onboard['onboard_status'] ?? '');
|
||||
$huifuId = trim((string) ($onboard['huifu_id'] ?? ''));
|
||||
if ($status === \addons\shopro_huifu\model\Onboard::STATUS_APPROVED) {
|
||||
if ($huifuId === '') {
|
||||
return '进件已通过但未回写 huifu_id,请同步进件状态并配置分账接收方';
|
||||
}
|
||||
return '进件已通过(huifu_id=' . $huifuId . '),但未写入分账接收方 role_id=' . $userId;
|
||||
}
|
||||
return '汇付进件状态:' . $this->onboardStatusLabel($status) . ',暂不能分账';
|
||||
}
|
||||
|
||||
protected function resolveOnboardRoleRefId($roleType, $userId)
|
||||
{
|
||||
if ($roleType === 'regional_agent') {
|
||||
try {
|
||||
$agent = Db::name('shopro_regional_agent')->where('user_id', $userId)->order('id', 'desc')->find();
|
||||
if ($agent) {
|
||||
return intval($agent['id']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return $userId;
|
||||
}
|
||||
|
||||
protected function onboardStatusLabel($status)
|
||||
{
|
||||
$map = [
|
||||
'draft' => '草稿待提交',
|
||||
'submitting' => '提交中',
|
||||
'basic_ok' => '基础进件成功',
|
||||
'busi_pending' => '业务开通审核中',
|
||||
'approved' => '已通过',
|
||||
'rejected' => '已驳回',
|
||||
'failed' => '进件失败',
|
||||
];
|
||||
return $map[$status] ?? ($status !== '' ? $status : '未知');
|
||||
}
|
||||
|
||||
protected function isEmptyResult($result)
|
||||
{
|
||||
if (is_array($result)) {
|
||||
|
||||
@@ -42,7 +42,7 @@ class HuifuSplitService
|
||||
if ($rewardCount + $regionalCount > 0) {
|
||||
\think\Log::warning('[shopro_huifu] split skipped order_id=' . $orderId . ' reason=receiver_missing_or_no_huifu_id');
|
||||
}
|
||||
$msg = '分账明细为空:请检查分账金额来源、汇付接收方(huifu_id)与佣金/规则是否大于0';
|
||||
$msg = $calculator->formatEmptySplitFailureMessage($orderId);
|
||||
$this->markFailed($orderId, 'HFPS' . date('YmdHis') . $orderId, $order, [], $msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -146,10 +146,11 @@ class Order extends Base
|
||||
$data['receiver_details'] = $receiverDetails;
|
||||
$data['receiver_preview'] = $receiverPreview;
|
||||
$data['receiver_summary'] = $summaryLines ? implode(';', $summaryLines) : '—';
|
||||
$data['fail_reason'] = $this->formatFailReason($data['wx_result'] ?? '', $status);
|
||||
$data['fail_reason_full'] = $this->formatFailReasonFull($data['wx_result'] ?? '', $status);
|
||||
|
||||
$diag = (new SplitCalculator())->diagnose($orderId);
|
||||
$data['fail_reason_full'] = $this->formatFailReasonFull($data['wx_result'] ?? '', $status);
|
||||
$data['fail_reason_full'] = $this->enrichFailReasonWithDiagnose($data['fail_reason_full'], $status, $diag);
|
||||
$data['fail_reason'] = $this->summarizeFailReason($data['fail_reason_full'], $diag, $status);
|
||||
|
||||
$data['split_source'] = $diag['final_source'];
|
||||
$data['split_source_text'] = $this->formatSplitSourceText($diag);
|
||||
$data['split_diagnose'] = $diag['issues'];
|
||||
@@ -304,6 +305,57 @@ class Order extends Base
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function enrichFailReasonWithDiagnose($full, $status, array $diag)
|
||||
{
|
||||
if ($status !== 4) {
|
||||
return $full;
|
||||
}
|
||||
$issues = array_values(array_filter($diag['issues'] ?? []));
|
||||
if (!$issues) {
|
||||
return $full;
|
||||
}
|
||||
$raw = is_string($full) ? $full : '';
|
||||
$isEmptySplit = $raw === ''
|
||||
|| strpos($raw, '分账明细为空') !== false
|
||||
|| strpos($raw, '分账未生成') !== false
|
||||
|| ($diag['final_source'] ?? '') === 'none';
|
||||
if (!$isEmptySplit) {
|
||||
return $full;
|
||||
}
|
||||
$block = implode("\n", $issues);
|
||||
if ($raw === '') {
|
||||
return '分账未生成:' . implode(';', $issues);
|
||||
}
|
||||
if (strpos($raw, implode(';', array_slice($issues, 0, 1))) !== false) {
|
||||
return $raw;
|
||||
}
|
||||
return $raw . "\n" . $block;
|
||||
}
|
||||
|
||||
protected function summarizeFailReason($full, array $diag, $status)
|
||||
{
|
||||
if ($status !== 4 || $full === '') {
|
||||
return $this->formatFailReasonFromFull($full, $status);
|
||||
}
|
||||
$issues = array_values(array_filter($diag['issues'] ?? []));
|
||||
if ($issues) {
|
||||
$brief = $issues[0];
|
||||
if (count($issues) > 1) {
|
||||
$brief .= ' 等' . count($issues) . '项';
|
||||
}
|
||||
return mb_strlen($brief) > 72 ? (mb_substr($brief, 0, 72) . '…') : $brief;
|
||||
}
|
||||
return $this->formatFailReasonFromFull($full, $status);
|
||||
}
|
||||
|
||||
protected function formatFailReasonFromFull($full, $status)
|
||||
{
|
||||
if ($status !== 4 || $full === '') {
|
||||
return '';
|
||||
}
|
||||
return mb_strlen($full) > 72 ? (mb_substr($full, 0, 72) . '…') : $full;
|
||||
}
|
||||
|
||||
protected function formatFailReason($wxResult, $status)
|
||||
{
|
||||
if ($status !== 4 || $wxResult === '' || $wxResult === null) {
|
||||
|
||||
在新工单中引用
屏蔽一个用户