diff --git a/addons/shopro/library/split/SplitCalculator.php b/addons/shopro/library/split/SplitCalculator.php index 35acbee..66f818c 100644 --- a/addons/shopro/library/split/SplitCalculator.php +++ b/addons/shopro/library/split/SplitCalculator.php @@ -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)) { diff --git a/addons/shopro_huifu/library/HuifuSplitService.php b/addons/shopro_huifu/library/HuifuSplitService.php index b3ab01a..e2ed388 100644 --- a/addons/shopro_huifu/library/HuifuSplitService.php +++ b/addons/shopro_huifu/library/HuifuSplitService.php @@ -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; } diff --git a/application/admin/controller/shopro/split/Order.php b/application/admin/controller/shopro/split/Order.php index b494854..1fb3337 100644 --- a/application/admin/controller/shopro/split/Order.php +++ b/application/admin/controller/shopro/split/Order.php @@ -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) {