feat(split): 新增分账订单线下结案功能 - 添加 SplitCloseService 类用于处理订单结案逻辑 - 实现订单结案状态检查和标记功能 - 在汇付分账重试逻辑中加入结案状态验证,防止已结案订单继续重试 - 更新分账手动服务,添加结案操作类型 - 扩展钱包结算类,增加结案相关诊断方法 - 修改测试脚本,支持批量标记订单为已结案状态 - 添加数据库升级脚本,增加结案相关字段和菜单权限 ```
736 行
26 KiB
PHP
736 行
26 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro\library\split;
|
||
|
||
use addons\shopro\model\Order;
|
||
use addons\shopro\model\User;
|
||
use addons\shopro\model\commission\Reward as CommissionReward;
|
||
use addons\shopro\model\regional_agent\Reward as RegionalReward;
|
||
use addons\shopro\model\split\Receiver;
|
||
use addons\shopro\model\split\SplitManual;
|
||
use addons\shopro\model\split\SplitManualLog;
|
||
use addons\shopro\model\split\SplitOrder;
|
||
use think\Db;
|
||
|
||
class SplitManualService
|
||
{
|
||
protected $config;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->config = new Config();
|
||
}
|
||
|
||
/**
|
||
* 是否存在手动覆盖
|
||
*/
|
||
public function hasOverride($orderId)
|
||
{
|
||
return (bool) SplitManual::where('order_id', intval($orderId))->find();
|
||
}
|
||
|
||
/**
|
||
* 批量:存在手动覆盖的 order_id
|
||
* @param int[] $orderIds
|
||
* @return int[]
|
||
*/
|
||
public function orderIdsWithOverride(array $orderIds)
|
||
{
|
||
$orderIds = array_values(array_unique(array_filter(array_map('intval', $orderIds))));
|
||
if (!$orderIds) {
|
||
return [];
|
||
}
|
||
return array_map('intval', SplitManual::where('order_id', 'in', $orderIds)->column('order_id'));
|
||
}
|
||
|
||
/**
|
||
* 批量:存在变更历史的 order_id
|
||
* @param int[] $orderIds
|
||
* @return int[]
|
||
*/
|
||
public function orderIdsWithLog(array $orderIds)
|
||
{
|
||
$orderIds = array_values(array_unique(array_filter(array_map('intval', $orderIds))));
|
||
if (!$orderIds) {
|
||
return [];
|
||
}
|
||
return array_map('intval', SplitManualLog::where('order_id', 'in', $orderIds)->group('order_id')->column('order_id'));
|
||
}
|
||
|
||
/**
|
||
* 供 SplitCalculator 使用的明细
|
||
* @return array
|
||
*/
|
||
public function getCalculateItems($orderId)
|
||
{
|
||
$manual = SplitManual::where('order_id', intval($orderId))->find();
|
||
if (!$manual) {
|
||
return [];
|
||
}
|
||
$items = $manual['items'] ?? [];
|
||
if (!is_array($items) || empty($items)) {
|
||
return [];
|
||
}
|
||
return $this->normalizeCalculateItems($items);
|
||
}
|
||
|
||
/**
|
||
* 保存手动覆盖
|
||
* @param int $orderId
|
||
* @param array $items [{receiver_id, role_type, amount, description?}]
|
||
* @param int $adminId
|
||
* @param string $adminName
|
||
* @param string $remark
|
||
* @return SplitManual
|
||
*/
|
||
public function save($orderId, array $items, $adminId = 0, $adminName = '', $remark = '')
|
||
{
|
||
$orderId = intval($orderId);
|
||
$this->assertEditableSplitOrder($orderId);
|
||
$normalized = $this->validateAndNormalizeItems($orderId, $items);
|
||
|
||
$existing = SplitManual::where('order_id', $orderId)->find();
|
||
$beforeItems = $existing ? ($existing['items'] ?? []) : [];
|
||
$action = $existing ? 'update' : 'create';
|
||
|
||
$originalItems = [];
|
||
if (!$existing) {
|
||
$originalItems = $this->snapshotPlannedAutoItems($orderId);
|
||
} else {
|
||
$originalItems = $existing['original_items'] ?? [];
|
||
}
|
||
|
||
$storageItems = $this->enrichItemsForStorage($normalized);
|
||
$itemsChanged = !$existing || !$this->itemsEqual($beforeItems, $storageItems);
|
||
$logBefore = $existing ? $beforeItems : $originalItems;
|
||
|
||
Db::startTrans();
|
||
try {
|
||
if ($existing) {
|
||
$existing->items = $storageItems;
|
||
$existing->admin_id = intval($adminId);
|
||
$existing->remark = $remark;
|
||
$existing->save();
|
||
$manual = $existing;
|
||
} else {
|
||
$manual = SplitManual::create([
|
||
'order_id' => $orderId,
|
||
'items' => $storageItems,
|
||
'original_items' => $originalItems,
|
||
'admin_id' => intval($adminId),
|
||
'remark' => $remark,
|
||
]);
|
||
}
|
||
|
||
if ($itemsChanged) {
|
||
$this->appendLog($orderId, $action, $logBefore, $storageItems, $adminId, $adminName, $remark);
|
||
}
|
||
Db::commit();
|
||
return $manual;
|
||
} catch (\Exception $e) {
|
||
Db::rollback();
|
||
throw $e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清除手动覆盖
|
||
*/
|
||
public function clear($orderId, $adminId = 0, $adminName = '', $remark = '')
|
||
{
|
||
$orderId = intval($orderId);
|
||
$this->assertEditableSplitOrder($orderId);
|
||
|
||
$existing = SplitManual::where('order_id', $orderId)->find();
|
||
if (!$existing) {
|
||
return true;
|
||
}
|
||
|
||
$beforeItems = $existing['items'] ?? [];
|
||
|
||
Db::startTrans();
|
||
try {
|
||
$existing->delete();
|
||
$this->appendLog($orderId, 'clear', $beforeItems, [], $adminId, $adminName, $remark);
|
||
Db::commit();
|
||
return true;
|
||
} catch (\Exception $e) {
|
||
Db::rollback();
|
||
throw $e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存并重试前标记 pending 日志
|
||
* @return int log id
|
||
*/
|
||
public function markPendingRetryLog($orderId, $adminId = 0, $adminName = '', $remark = '')
|
||
{
|
||
$orderId = intval($orderId);
|
||
$manual = SplitManual::where('order_id', $orderId)->find();
|
||
if (!$manual) {
|
||
throw new \RuntimeException('请先保存手动指定接收方');
|
||
}
|
||
|
||
$order = Order::get($orderId);
|
||
$orderSn = $order ? (string) $order['order_sn'] : '';
|
||
|
||
$log = SplitManualLog::create([
|
||
'order_id' => $orderId,
|
||
'order_sn' => $orderSn,
|
||
'action' => 'retry',
|
||
'before_items' => [],
|
||
'after_items' => $manual['items'] ?? [],
|
||
'admin_id' => intval($adminId),
|
||
'admin_name' => $adminName,
|
||
'remark' => $remark ?: '保存并重试',
|
||
'retry_result' => 'pending',
|
||
'createtime' => time(),
|
||
]);
|
||
|
||
return intval($log->id);
|
||
}
|
||
|
||
/**
|
||
* 重试完成后更新日志
|
||
*/
|
||
public function finishPendingRetryLog($orderId, $success, $message = '', $splitOrderId = null)
|
||
{
|
||
$orderId = intval($orderId);
|
||
$log = SplitManualLog::where('order_id', $orderId)
|
||
->where('action', 'retry')
|
||
->where('retry_result', 'pending')
|
||
->order('id', 'desc')
|
||
->find();
|
||
|
||
if (!$log) {
|
||
return;
|
||
}
|
||
|
||
$log->retry_result = $success ? 'success' : 'fail';
|
||
$log->retry_message = $message;
|
||
if ($splitOrderId) {
|
||
$log->retry_split_order_id = intval($splitOrderId);
|
||
} else {
|
||
$latest = SplitOrder::where('order_id', $orderId)->where('channel', 'huifu')->order('id', 'desc')->find();
|
||
if ($latest) {
|
||
$log->retry_split_order_id = intval($latest['id']);
|
||
}
|
||
}
|
||
$log->save();
|
||
}
|
||
|
||
/**
|
||
* 变更历史
|
||
*/
|
||
public function getHistory($orderId, $offset = 0, $limit = 20)
|
||
{
|
||
$orderId = intval($orderId);
|
||
$query = SplitManualLog::where('order_id', $orderId);
|
||
$total = $query->count();
|
||
$rows = SplitManualLog::where('order_id', $orderId)
|
||
->order('id', 'desc')
|
||
->limit($offset, $limit)
|
||
->select();
|
||
|
||
$list = [];
|
||
foreach ($rows as $row) {
|
||
$data = is_array($row) ? $row : $row->toArray();
|
||
$data['action_text'] = $this->actionLabel($data['action'] ?? '');
|
||
$data['retry_result_text'] = $this->retryResultLabel($data['retry_result'] ?? '');
|
||
$data['createtime_text'] = !empty($data['createtime'])
|
||
? date('Y-m-d H:i:s', $data['createtime'])
|
||
: '-';
|
||
$before = is_array($data['before_items'] ?? null) ? $data['before_items'] : [];
|
||
$after = is_array($data['after_items'] ?? null) ? $data['after_items'] : [];
|
||
$data['before_summary'] = $this->formatItemsBrief($before);
|
||
$data['after_summary'] = $this->formatItemsBrief($after);
|
||
$data['change_summary'] = $this->buildChangeSummary($data['action'] ?? '', $before, $after);
|
||
$list[] = $data;
|
||
}
|
||
|
||
return ['total' => $total, 'rows' => $list];
|
||
}
|
||
|
||
/**
|
||
* 汇付接收方下拉选项
|
||
*/
|
||
public function listHuifuReceiverOptions($keyword = '')
|
||
{
|
||
$query = Receiver::where('status', 'normal')
|
||
->where(function ($q) {
|
||
$q->where('channel', 'huifu')->whereOr('channel', null)->whereOr('channel', '');
|
||
})
|
||
->where('receiver_account', '<>', '');
|
||
|
||
$keyword = trim((string) $keyword);
|
||
if ($keyword !== '') {
|
||
$query->where(function ($q) use ($keyword) {
|
||
$like = '%' . $keyword . '%';
|
||
$q->where('receiver_name', 'like', $like)
|
||
->whereOr('receiver_account', 'like', $like);
|
||
});
|
||
}
|
||
|
||
$rows = $query->order('id', 'desc')->limit(200)->select();
|
||
$options = [];
|
||
foreach ($rows as $row) {
|
||
$data = is_array($row) ? $row : $row->toArray();
|
||
$account = trim((string) ($data['receiver_account'] ?? ''));
|
||
if ($account === '') {
|
||
continue;
|
||
}
|
||
$options[] = [
|
||
'id' => intval($data['id']),
|
||
'role_type' => $data['role_type'] ?? '',
|
||
'role_id' => intval($data['role_id'] ?? 0),
|
||
'receiver_account' => $account,
|
||
'receiver_name' => trim((string) ($data['receiver_name'] ?? '')),
|
||
'receiver_type' => $data['receiver_type'] ?? 'HUIFU_ID',
|
||
'label' => $this->formatReceiverOptionLabel($data),
|
||
];
|
||
}
|
||
return $options;
|
||
}
|
||
|
||
/**
|
||
* 加载指定失败单的编辑上下文
|
||
*/
|
||
public function getManualContext($splitOrderId)
|
||
{
|
||
$splitOrder = SplitOrder::get(intval($splitOrderId));
|
||
if (!$splitOrder) {
|
||
throw new \RuntimeException('分账记录不存在');
|
||
}
|
||
$this->assertEditableSplitRow($splitOrder);
|
||
|
||
$orderId = intval($splitOrder['order_id']);
|
||
$order = Order::get($orderId);
|
||
$manual = SplitManual::where('order_id', $orderId)->find();
|
||
|
||
$calculator = new SplitCalculator();
|
||
$autoItems = $calculator->calculateAuto($orderId);
|
||
$autoItems = $this->enrichItemsForStorage($autoItems);
|
||
|
||
$currentItems = $manual ? ($manual['items'] ?? []) : $autoItems;
|
||
$originalItems = $manual ? ($manual['original_items'] ?? []) : $autoItems;
|
||
|
||
$confirmBase = null;
|
||
if (class_exists('\\addons\\shopro_huifu\\library\\HuifuSplitService') && $order) {
|
||
try {
|
||
$svc = new \addons\shopro_huifu\library\HuifuSplitService();
|
||
$confirmBase = $svc->previewConfirmPayFee($order['order_sn'], $order['pay_fee']);
|
||
} catch (\Throwable $e) {
|
||
$confirmBase = floatval($order['pay_fee'] ?? 0);
|
||
}
|
||
}
|
||
|
||
return [
|
||
'split_order_id' => intval($splitOrder['id']),
|
||
'order_id' => $orderId,
|
||
'order_sn' => $splitOrder['order_sn'] ?? ($order['order_sn'] ?? ''),
|
||
'pay_fee' => floatval($order['pay_fee'] ?? 0),
|
||
'confirm_base' => floatval($confirmBase ?? ($order['pay_fee'] ?? 0)),
|
||
'max_rate' => $this->config->getMaxRate(),
|
||
'max_split_amount'=> round(floatval($order['pay_fee'] ?? 0) * $this->config->getMaxRate() / 100, 2),
|
||
'has_manual' => (bool) $manual,
|
||
'current_items' => $currentItems,
|
||
'original_items' => $originalItems,
|
||
'auto_items' => $autoItems,
|
||
'receiver_options'=> $this->listHuifuReceiverOptions(),
|
||
];
|
||
}
|
||
|
||
protected function appendLog($orderId, $action, $beforeItems, $afterItems, $adminId, $adminName, $remark)
|
||
{
|
||
$order = Order::get($orderId);
|
||
SplitManualLog::create([
|
||
'order_id' => $orderId,
|
||
'order_sn' => $order ? (string) $order['order_sn'] : '',
|
||
'action' => $action,
|
||
'before_items' => is_array($beforeItems) ? $beforeItems : [],
|
||
'after_items' => is_array($afterItems) ? $afterItems : [],
|
||
'admin_id' => intval($adminId),
|
||
'admin_name' => $adminName,
|
||
'remark' => $remark,
|
||
'createtime' => time(),
|
||
]);
|
||
}
|
||
|
||
protected function assertEditableSplitOrder($orderId)
|
||
{
|
||
$row = SplitOrder::where('order_id', intval($orderId))
|
||
->where('channel', 'huifu')
|
||
->where('status', 4)
|
||
->order('id', 'desc')
|
||
->find();
|
||
if (!$row) {
|
||
throw new \RuntimeException('仅失败状态的汇付分账记录可指定接收方');
|
||
}
|
||
}
|
||
|
||
protected function assertEditableSplitRow($splitOrder)
|
||
{
|
||
$data = is_array($splitOrder) ? $splitOrder : $splitOrder->toArray();
|
||
if (($data['channel'] ?? '') !== 'huifu') {
|
||
throw new \RuntimeException('仅汇付通道分账记录可指定接收方');
|
||
}
|
||
if (intval($data['status'] ?? 0) !== 4) {
|
||
throw new \RuntimeException('仅失败状态的分账记录可指定接收方');
|
||
}
|
||
}
|
||
|
||
protected function validateAndNormalizeItems($orderId, array $items)
|
||
{
|
||
if (empty($items)) {
|
||
throw new \RuntimeException('请至少添加一行分账明细');
|
||
}
|
||
|
||
$order = Order::get($orderId);
|
||
if (!$order) {
|
||
throw new \RuntimeException('订单不存在');
|
||
}
|
||
|
||
$payFee = floatval($order['pay_fee']);
|
||
$maxSplit = round($payFee * $this->config->getMaxRate() / 100, 2);
|
||
$total = 0.0;
|
||
$normalized = [];
|
||
$paymentHuifuId = $this->getPaymentHuifuId();
|
||
|
||
foreach ($items as $item) {
|
||
$receiverId = intval($item['receiver_id'] ?? 0);
|
||
$amount = round(floatval($item['amount'] ?? 0), 2);
|
||
if ($receiverId <= 0) {
|
||
throw new \RuntimeException('请选择有效的分账接收方');
|
||
}
|
||
if ($amount <= 0) {
|
||
throw new \RuntimeException('分账金额须大于 0');
|
||
}
|
||
|
||
$receiver = Receiver::where('id', $receiverId)->where('status', 'normal')->find();
|
||
if (!$receiver) {
|
||
throw new \RuntimeException('接收方不存在或已禁用');
|
||
}
|
||
$account = trim((string) ($receiver['receiver_account'] ?? ''));
|
||
if ($account === '') {
|
||
throw new \RuntimeException('接收方未配置 huifu_id');
|
||
}
|
||
if ($paymentHuifuId !== '' && $account === $paymentHuifuId) {
|
||
throw new \RuntimeException('不得仅分给收款主商户(' . $paymentHuifuId . ')');
|
||
}
|
||
|
||
$total += $amount;
|
||
$normalized[] = [
|
||
'receiver_id' => $receiverId,
|
||
'role_type' => $item['role_type'] ?? ($receiver['role_type'] ?? 'promoter'),
|
||
'role_id' => intval($receiver['role_id'] ?? 0),
|
||
'receiver_account' => $account,
|
||
'receiver_type' => $receiver['receiver_type'] ?? 'HUIFU_ID',
|
||
'receiver_name' => trim((string) ($receiver['receiver_name'] ?? '')),
|
||
'amount' => $amount,
|
||
'description' => trim((string) ($item['description'] ?? '手动指定')),
|
||
];
|
||
}
|
||
|
||
if ($total > $maxSplit + 0.001) {
|
||
throw new \RuntimeException(sprintf(
|
||
'分账合计 ¥%s 超过上限 ¥%s(订单金额 × %s%%)',
|
||
number_format($total, 2, '.', ''),
|
||
number_format($maxSplit, 2, '.', ''),
|
||
$this->config->getMaxRate()
|
||
));
|
||
}
|
||
|
||
$confirmBase = $payFee;
|
||
if (class_exists('\\addons\\shopro_huifu\\library\\HuifuSplitService')) {
|
||
try {
|
||
$svc = new \addons\shopro_huifu\library\HuifuSplitService();
|
||
$confirmBase = floatval($svc->previewConfirmPayFee($order['order_sn'], $payFee));
|
||
} catch (\Throwable $e) {
|
||
// use pay_fee
|
||
}
|
||
}
|
||
if ($total > $confirmBase + 0.001) {
|
||
throw new \RuntimeException(sprintf(
|
||
'分账合计 ¥%s 超过汇付确认基数 ¥%s',
|
||
number_format($total, 2, '.', ''),
|
||
number_format($confirmBase, 2, '.', '')
|
||
));
|
||
}
|
||
|
||
return $normalized;
|
||
}
|
||
|
||
protected function normalizeCalculateItems(array $items)
|
||
{
|
||
$result = [];
|
||
foreach ($items as $item) {
|
||
$amount = round(floatval($item['amount'] ?? 0), 2);
|
||
if ($amount <= 0) {
|
||
continue;
|
||
}
|
||
$account = trim((string) ($item['receiver_account'] ?? ''));
|
||
if ($account === '') {
|
||
$receiverId = intval($item['receiver_id'] ?? 0);
|
||
if ($receiverId > 0) {
|
||
$receiver = Receiver::get($receiverId);
|
||
if ($receiver) {
|
||
$account = trim((string) ($receiver['receiver_account'] ?? ''));
|
||
}
|
||
}
|
||
}
|
||
if ($account === '') {
|
||
continue;
|
||
}
|
||
$result[] = [
|
||
'role_type' => $item['role_type'] ?? 'promoter',
|
||
'role_id' => intval($item['role_id'] ?? 0),
|
||
'receiver_id' => intval($item['receiver_id'] ?? 0),
|
||
'receiver_account' => $account,
|
||
'receiver_type' => $item['receiver_type'] ?? 'HUIFU_ID',
|
||
'receiver_name' => trim((string) ($item['receiver_name'] ?? '')),
|
||
'amount' => $amount,
|
||
'description' => trim((string) ($item['description'] ?? '手动指定')),
|
||
];
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
protected function enrichItemsForStorage(array $items)
|
||
{
|
||
$result = [];
|
||
foreach ($items as $item) {
|
||
$receiverId = intval($item['receiver_id'] ?? 0);
|
||
$account = trim((string) ($item['receiver_account'] ?? ''));
|
||
$name = trim((string) ($item['receiver_name'] ?? ''));
|
||
$roleType = $item['role_type'] ?? 'promoter';
|
||
$roleId = intval($item['role_id'] ?? 0);
|
||
|
||
if ($receiverId > 0) {
|
||
$receiver = Receiver::get($receiverId);
|
||
if ($receiver) {
|
||
if ($account === '') {
|
||
$account = trim((string) ($receiver['receiver_account'] ?? ''));
|
||
}
|
||
if ($name === '') {
|
||
$name = trim((string) ($receiver['receiver_name'] ?? ''));
|
||
}
|
||
if (!$roleType) {
|
||
$roleType = $receiver['role_type'] ?? 'promoter';
|
||
}
|
||
if (!$roleId) {
|
||
$roleId = intval($receiver['role_id'] ?? 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
$result[] = [
|
||
'receiver_id' => $receiverId,
|
||
'role_type' => $roleType,
|
||
'role_id' => $roleId,
|
||
'receiver_account' => $account,
|
||
'receiver_type' => $item['receiver_type'] ?? 'HUIFU_ID',
|
||
'receiver_name' => $name,
|
||
'amount' => round(floatval($item['amount'] ?? 0), 2),
|
||
'description' => trim((string) ($item['description'] ?? '')),
|
||
];
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
protected function getPaymentHuifuId()
|
||
{
|
||
if (!class_exists('\\addons\\shopro_huifu\\library\\HuifuConfig')) {
|
||
return '';
|
||
}
|
||
try {
|
||
return trim((string) \addons\shopro_huifu\library\HuifuConfig::getInstance()->get('huifu_id'));
|
||
} catch (\Throwable $e) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
protected function formatReceiverOptionLabel(array $data)
|
||
{
|
||
$name = trim((string) ($data['receiver_name'] ?? ''));
|
||
$account = trim((string) ($data['receiver_account'] ?? ''));
|
||
$roleType = $data['role_type'] ?? '';
|
||
$roleMap = [
|
||
'platform' => '平台',
|
||
'promoter' => '推广员',
|
||
'regional_agent' => '区域公司',
|
||
];
|
||
$roleLabel = $roleMap[$roleType] ?? $roleType;
|
||
$parts = array_filter([$roleLabel, $name ?: ('#' . ($data['role_id'] ?? 0)), $account]);
|
||
return implode(' · ', $parts);
|
||
}
|
||
|
||
protected function actionLabel($action)
|
||
{
|
||
$map = [
|
||
'create' => '首次指定',
|
||
'update' => '修改',
|
||
'clear' => '清除',
|
||
'retry' => '保存并重试',
|
||
'close' => '线下结案',
|
||
];
|
||
return $map[$action] ?? $action;
|
||
}
|
||
|
||
protected function retryResultLabel($result)
|
||
{
|
||
if ($result === null || $result === '') {
|
||
return '';
|
||
}
|
||
$map = [
|
||
'pending' => '重试中',
|
||
'success' => '成功',
|
||
'fail' => '失败',
|
||
];
|
||
return $map[$result] ?? $result;
|
||
}
|
||
|
||
/**
|
||
* 首次改人时的「原系统计划」快照(含未进件推广员)
|
||
*/
|
||
protected function snapshotPlannedAutoItems($orderId)
|
||
{
|
||
$calculator = new SplitCalculator();
|
||
$auto = $calculator->calculateAuto($orderId);
|
||
if (!empty($auto)) {
|
||
return $this->enrichItemsForStorage($auto);
|
||
}
|
||
|
||
$items = [];
|
||
$commissionRewards = CommissionReward::where('order_id', $orderId)->where('status', '>=', 0)->select();
|
||
foreach ($commissionRewards as $reward) {
|
||
$userId = intval($reward['agent_id']);
|
||
$amount = round(floatval($reward['commission']), 2);
|
||
if ($userId <= 0 || $amount <= 0) {
|
||
continue;
|
||
}
|
||
$items[] = $this->buildPlannedItem('promoter', $userId, $amount, '原推广员佣金');
|
||
}
|
||
|
||
foreach ($this->getRegionalRewardsByOrderId($orderId) as $reward) {
|
||
$userId = intval($reward['agent_user_id']);
|
||
$amount = round(floatval($reward['commission']), 2);
|
||
if ($userId <= 0 || $amount <= 0) {
|
||
continue;
|
||
}
|
||
$items[] = $this->buildPlannedItem('regional_agent', $userId, $amount, '原区域公司佣金');
|
||
}
|
||
|
||
return $items;
|
||
}
|
||
|
||
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 buildPlannedItem($roleType, $userId, $amount, $desc)
|
||
{
|
||
$receiver = Receiver::where('role_type', $roleType)
|
||
->where('role_id', $userId)
|
||
->where('status', 'normal')
|
||
->find();
|
||
$user = User::get($userId);
|
||
$name = $receiver ? trim((string) ($receiver['receiver_name'] ?? '')) : '';
|
||
if ($name === '' && $user) {
|
||
$name = trim((string) ($user['nickname'] ?? '')) ?: ('用户#' . $userId);
|
||
}
|
||
return [
|
||
'receiver_id' => $receiver ? intval($receiver['id']) : 0,
|
||
'role_type' => $roleType,
|
||
'role_id' => $userId,
|
||
'receiver_account' => $receiver ? trim((string) ($receiver['receiver_account'] ?? '')) : '',
|
||
'receiver_name' => $name,
|
||
'amount' => $amount,
|
||
'description' => $desc,
|
||
];
|
||
}
|
||
|
||
protected function buildChangeSummary($action, array $before, array $after)
|
||
{
|
||
if ($action === 'retry') {
|
||
$afterText = $this->formatItemsBrief($after);
|
||
return $afterText ? ('按以下接收方重新发起分账:' . $afterText) : '重新发起分账重试';
|
||
}
|
||
if ($action === 'clear') {
|
||
$beforeText = $this->formatItemsBrief($before);
|
||
return $beforeText
|
||
? ('清除手动指定,恢复自动计算(原手动指定:' . $beforeText . ')')
|
||
: '清除手动指定,恢复自动计算';
|
||
}
|
||
|
||
$beforeText = $this->formatItemsBrief($before);
|
||
$afterText = $this->formatItemsBrief($after);
|
||
if ($beforeText === '' && $afterText !== '') {
|
||
return '指定分账接收方为:' . $afterText;
|
||
}
|
||
if ($beforeText === $afterText) {
|
||
return '分账接收方未变化:' . $afterText;
|
||
}
|
||
return '分账接收方:' . ($beforeText ?: '(无有效自动接收方)') . ' → ' . $afterText;
|
||
}
|
||
|
||
protected function formatItemsBrief(array $items)
|
||
{
|
||
if (empty($items)) {
|
||
return '';
|
||
}
|
||
$parts = [];
|
||
foreach ($items as $item) {
|
||
$parts[] = $this->formatOneItemBrief($item);
|
||
}
|
||
return implode(';', $parts);
|
||
}
|
||
|
||
protected function formatOneItemBrief(array $item)
|
||
{
|
||
$roleMap = [
|
||
'platform' => '平台',
|
||
'promoter' => '推广员',
|
||
'regional_agent' => '区域公司',
|
||
];
|
||
$role = $roleMap[$item['role_type'] ?? ''] ?? ($item['role_type'] ?? '接收方');
|
||
$name = trim((string) ($item['receiver_name'] ?? ''));
|
||
if ($name === '' && !empty($item['role_id'])) {
|
||
$name = '用户#' . intval($item['role_id']);
|
||
}
|
||
$account = trim((string) ($item['receiver_account'] ?? ''));
|
||
$accountPart = $account !== '' ? $account : '未进件';
|
||
$amt = number_format(floatval($item['amount'] ?? 0), 2, '.', '');
|
||
return $role . ' ' . $name . '(' . $accountPart . ')¥' . $amt;
|
||
}
|
||
|
||
protected function itemsEqual(array $before, array $after)
|
||
{
|
||
$norm = function (array $items) {
|
||
$rows = [];
|
||
foreach ($items as $item) {
|
||
$rows[] = [
|
||
intval($item['receiver_id'] ?? 0),
|
||
number_format(floatval($item['amount'] ?? 0), 2, '.', ''),
|
||
trim((string) ($item['receiver_account'] ?? '')),
|
||
];
|
||
}
|
||
usort($rows, function ($a, $b) {
|
||
return ($a[0] <=> $b[0]) ?: strcmp($a[1], $b[1]);
|
||
});
|
||
return $rows;
|
||
};
|
||
return $norm($before) === $norm($after);
|
||
}
|
||
}
|