feat(split): 添加分账诊断功能用于后台展示 - 新增 SplitCalculator::diagnose() 方法,用于诊断订单分账问题 - 返回分账来源、佣金总额、规则总额及具体问题说明 - 在管理员分账订单控制器中集成诊断信息显示 - 优化前端表格显示分账来源标签和诊断提示信息 - 修复佣金奖励查询中 agent_id 使用错误的问题 ```
303 行
11 KiB
PHP
303 行
11 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro\library\split;
|
||
|
||
use addons\shopro\model\Order;
|
||
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\UserOauth;
|
||
|
||
class SplitCalculator
|
||
{
|
||
protected $config;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->config = new Config();
|
||
}
|
||
|
||
/**
|
||
* 计算订单分账明细
|
||
* @return array [['role_type','role_id','receiver_id','receiver_account','receiver_type','receiver_name','amount','description']]
|
||
*/
|
||
public function calculate($orderId)
|
||
{
|
||
$order = Order::get($orderId);
|
||
if (!$order || !in_array($order['pay_type'], ['wechat', 'huifu_wechat'])) {
|
||
return [];
|
||
}
|
||
|
||
$payFee = floatval($order['pay_fee']);
|
||
if ($payFee <= 0) {
|
||
return [];
|
||
}
|
||
|
||
$source = $this->config->getAmountSource();
|
||
$items = [];
|
||
|
||
if (in_array($source, ['commission', 'mixed'])) {
|
||
$items = $this->calculateFromCommission($orderId);
|
||
}
|
||
|
||
if (empty($items) && in_array($source, ['rule', 'mixed'])) {
|
||
$items = $this->calculateFromRules($payFee);
|
||
}
|
||
|
||
$allocated = array_sum(array_column($items, 'amount'));
|
||
$maxSplit = $payFee * $this->config->getMaxRate() / 100;
|
||
if ($allocated > $maxSplit && $allocated > 0) {
|
||
$ratio = $maxSplit / $allocated;
|
||
foreach ($items as &$item) {
|
||
$item['amount'] = round($item['amount'] * $ratio, 2);
|
||
}
|
||
unset($item);
|
||
}
|
||
|
||
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 {
|
||
return RegionalReward::where('order_id', $orderId)->where('status', '>=', 0)->select();
|
||
} catch (\Exception $e) {
|
||
return RegionalReward::alias('rr')
|
||
->join('shopro_regional_agent_order ro', 'ro.id = rr.regional_order_id')
|
||
->where('ro.order_id', $orderId)
|
||
->where('rr.status', '>=', 0)
|
||
->field('rr.*')
|
||
->select();
|
||
}
|
||
}
|
||
|
||
protected function calculateFromCommission($orderId)
|
||
{
|
||
$items = [];
|
||
|
||
$commissionRewards = CommissionReward::where('order_id', $orderId)->where('status', '>=', 0)->select();
|
||
foreach ($commissionRewards as $reward) {
|
||
// agent_id 即分销商 user_id(shopro_commission_agent 主键)
|
||
$userId = intval($reward['agent_id']);
|
||
if ($userId <= 0) {
|
||
continue;
|
||
}
|
||
$receiver = $this->getReceiver('promoter', $userId);
|
||
if (!$receiver) {
|
||
continue;
|
||
}
|
||
$amount = floatval($reward['commission']);
|
||
if ($amount <= 0) {
|
||
continue;
|
||
}
|
||
$items[] = $this->buildItem('promoter', $userId, $receiver, $amount, '推广员差价');
|
||
}
|
||
|
||
$regionalRewards = $this->getRegionalRewardsByOrderId($orderId);
|
||
foreach ($regionalRewards as $reward) {
|
||
$receiver = $this->getReceiver('regional_agent', $reward['agent_user_id']);
|
||
if (!$receiver) {
|
||
continue;
|
||
}
|
||
$amount = floatval($reward['commission']);
|
||
if ($amount <= 0) {
|
||
continue;
|
||
}
|
||
$items[] = $this->buildItem('regional_agent', $reward['agent_user_id'], $receiver, $amount, '区域公司差价');
|
||
}
|
||
|
||
return $items;
|
||
}
|
||
|
||
protected function calculateFromRules($payFee)
|
||
{
|
||
$items = [];
|
||
$rules = Rule::where('status', 'normal')->order('priority desc')->select();
|
||
foreach ($rules as $rule) {
|
||
if (in_array($rule['role_type'], ['platform'])) {
|
||
continue;
|
||
}
|
||
$receiver = $this->getReceiver($rule['role_type'], 0, true);
|
||
if (!$receiver) {
|
||
continue;
|
||
}
|
||
if ($rule['rule_type'] === 'rate') {
|
||
$amount = round($payFee * floatval($rule['rule_value']) / 100, 2);
|
||
} else {
|
||
$amount = round(floatval($rule['rule_value']), 2);
|
||
}
|
||
if ($amount <= 0) {
|
||
continue;
|
||
}
|
||
$items[] = $this->buildItem($rule['role_type'], 0, $receiver, $amount, '规则分账');
|
||
}
|
||
return $items;
|
||
}
|
||
|
||
protected function buildItem($roleType, $roleId, $receiver, $amount, $desc)
|
||
{
|
||
return [
|
||
'role_type' => $roleType,
|
||
'role_id' => $roleId,
|
||
'receiver_id' => $receiver['id'] ?? 0,
|
||
'receiver_account' => $receiver['receiver_account'],
|
||
'receiver_type' => $receiver['receiver_type'],
|
||
'receiver_name' => $receiver['receiver_name'] ?? '',
|
||
'amount' => round($amount, 2),
|
||
'description' => $desc,
|
||
];
|
||
}
|
||
|
||
protected function getReceiver($roleType, $roleId, $ruleMode = false)
|
||
{
|
||
$channel = $this->config->isHuifuMode() ? 'huifu' : 'wechat';
|
||
$query = Receiver::where('role_type', $roleType)->where('status', 'normal');
|
||
if ($roleId) {
|
||
$query->where('role_id', $roleId);
|
||
}
|
||
$receiver = $query->where(function ($q) use ($channel) {
|
||
$q->where('channel', $channel)->whereOr('channel', null)->whereOr('channel', '');
|
||
})->find();
|
||
|
||
if ($receiver) {
|
||
return $receiver;
|
||
}
|
||
|
||
if ($this->config->isHuifuMode()) {
|
||
if ($roleType === 'platform') {
|
||
$account = $this->config->getPlatformHuifuId();
|
||
if ($account) {
|
||
return [
|
||
'id' => 0,
|
||
'receiver_account' => $account,
|
||
'receiver_type' => 'HUIFU_ID',
|
||
'receiver_name' => $this->config->getPlatformReceiverName(),
|
||
];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
if (!$ruleMode && in_array($roleType, ['promoter', 'regional_agent'])) {
|
||
$oauth = UserOauth::where('user_id', $roleId)->where('provider', 'Wechat')->where('platform', 'wxMiniProgram')->find();
|
||
if ($oauth && $oauth['openid']) {
|
||
return Receiver::create([
|
||
'role_type' => $roleType,
|
||
'role_id' => $roleId,
|
||
'channel' => 'wechat',
|
||
'receiver_type' => 'PERSONAL_OPENID',
|
||
'receiver_account' => $oauth['openid'],
|
||
'receiver_name' => '用户' . $roleId,
|
||
'relation_type' => 'USER',
|
||
'wx_status' => 0,
|
||
'status' => 'normal',
|
||
'createtime' => time(),
|
||
'updatetime' => time(),
|
||
]);
|
||
}
|
||
}
|
||
|
||
if ($roleType === 'platform') {
|
||
$account = $this->config->getPlatformReceiverAccount();
|
||
if ($account) {
|
||
return [
|
||
'id' => 0,
|
||
'receiver_account' => $account,
|
||
'receiver_type' => 'MERCHANT_ID',
|
||
'receiver_name' => $this->config->getPlatformReceiverName(),
|
||
];
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|
||
|