```
feat(split): 新增分账接收方明细功能 新增 SplitOrderHelper 类用于同步分账接收方明细数据,包括接收方角色、 姓名、账户和金额等信息。优化管理后台分账订单列表页面,显示详细的 接收方信息和失败原因,提升分账流程的可视化和可追溯性。 ```
这个提交包含在:
@@ -4,6 +4,7 @@ namespace app\admin\controller\shopro\split;
|
||||
|
||||
use app\admin\controller\shopro\Base;
|
||||
use addons\shopro\library\split\Config as SplitConfig;
|
||||
use addons\shopro\library\split\SplitCalculator;
|
||||
use addons\shopro\library\split\SplitExecutor;
|
||||
use addons\shopro\library\split\ProfitSharingService;
|
||||
use app\admin\model\shopro\order\Order as ShopOrder;
|
||||
@@ -38,7 +39,9 @@ class Order extends Base
|
||||
$total = $query->where($where)->count();
|
||||
$list = $this->buildSearch()
|
||||
->with([
|
||||
'receivers',
|
||||
'receivers' => function ($q) {
|
||||
$q->with('receiverConfig');
|
||||
},
|
||||
'order' => function ($q) {
|
||||
$q->field('id,order_sn,user_id,pay_fee,paytime,status')
|
||||
->with(['user' => function ($uq) {
|
||||
@@ -110,14 +113,18 @@ class Order extends Base
|
||||
$buyerNickname = $user['nickname'] ?? '';
|
||||
$buyerMobile = $user['mobile'] ?? '';
|
||||
|
||||
$orderId = intval($data['order_id'] ?? 0);
|
||||
$receivers = $data['receivers'] ?? [];
|
||||
$lines = [];
|
||||
foreach ($receivers as $recv) {
|
||||
$r = is_array($recv) ? $recv : (method_exists($recv, 'toArray') ? $recv->toArray() : []);
|
||||
$lines[] = sprintf(
|
||||
'%s ¥%s',
|
||||
$this->roleTypeLabel($r['role_type'] ?? ''),
|
||||
number_format(floatval($r['amount'] ?? 0), 2, '.', '')
|
||||
$receiverDetails = $this->buildReceiverDetails($orderId, $receivers);
|
||||
$receiverPreview = empty($receivers) && !empty($receiverDetails);
|
||||
|
||||
$summaryLines = [];
|
||||
foreach ($receiverDetails as $detail) {
|
||||
$summaryLines[] = sprintf(
|
||||
'%s %s ¥%s',
|
||||
$detail['role_label'],
|
||||
$detail['display_name'],
|
||||
number_format(floatval($detail['amount']), 2, '.', '')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,15 +143,169 @@ class Order extends Base
|
||||
$data['order_paytime_text'] = (is_array($order) && !empty($order['paytime']))
|
||||
? date('Y-m-d H:i:s', $order['paytime'])
|
||||
: '-';
|
||||
$data['receiver_summary'] = $lines ? implode(';', $lines) : '—';
|
||||
$data['receiver_details'] = $receiverDetails;
|
||||
$data['receiver_preview'] = $receiverPreview;
|
||||
$data['receiver_summary'] = $summaryLines ? implode(';', $summaryLines) : '—';
|
||||
$data['fail_reason'] = $this->formatFailReason($data['wx_result'] ?? '', $status);
|
||||
|
||||
unset($data['order']);
|
||||
unset($data['order'], $data['receivers']);
|
||||
$rows[] = $data;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
protected function buildReceiverDetails($orderId, $receivers)
|
||||
{
|
||||
$details = [];
|
||||
if (!empty($receivers)) {
|
||||
$userIds = [];
|
||||
foreach ($receivers as $recv) {
|
||||
$r = is_array($recv) ? $recv : (method_exists($recv, 'toArray') ? $recv->toArray() : []);
|
||||
$cfg = $r['receiver_config'] ?? null;
|
||||
if (is_object($cfg) && method_exists($cfg, 'toArray')) {
|
||||
$cfg = $cfg->toArray();
|
||||
}
|
||||
$roleId = intval(is_array($cfg) ? ($cfg['role_id'] ?? 0) : 0);
|
||||
if ($roleId > 0) {
|
||||
$userIds[] = $roleId;
|
||||
}
|
||||
}
|
||||
$users = $userIds ? $this->loadUsersByIds($userIds) : [];
|
||||
|
||||
foreach ($receivers as $recv) {
|
||||
$r = is_array($recv) ? $recv : (method_exists($recv, 'toArray') ? $recv->toArray() : []);
|
||||
$cfg = $r['receiver_config'] ?? null;
|
||||
if (is_object($cfg) && method_exists($cfg, 'toArray')) {
|
||||
$cfg = $cfg->toArray();
|
||||
}
|
||||
$cfg = is_array($cfg) ? $cfg : [];
|
||||
$roleId = intval($cfg['role_id'] ?? 0);
|
||||
$account = trim((string) ($cfg['receiver_account'] ?? ''));
|
||||
$recvName = trim((string) ($cfg['receiver_name'] ?? ''));
|
||||
$user = $roleId && isset($users[$roleId]) ? $users[$roleId] : null;
|
||||
$details[] = [
|
||||
'role_label' => $this->roleTypeLabel($r['role_type'] ?? ($cfg['role_type'] ?? '')),
|
||||
'display_name' => $this->formatReceiverDisplayName($roleId, $recvName, $user),
|
||||
'account' => $account,
|
||||
'amount' => floatval($r['amount'] ?? 0),
|
||||
'status' => intval($r['status'] ?? 0),
|
||||
];
|
||||
}
|
||||
return $details;
|
||||
}
|
||||
|
||||
if ($orderId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$items = (new SplitCalculator())->calculate($orderId);
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
if (empty($items)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$roleIds = [];
|
||||
foreach ($items as $item) {
|
||||
$rid = intval($item['role_id'] ?? 0);
|
||||
if ($rid > 0) {
|
||||
$roleIds[] = $rid;
|
||||
}
|
||||
}
|
||||
$users = $roleIds ? $this->loadUsersByIds($roleIds) : [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$roleId = intval($item['role_id'] ?? 0);
|
||||
$user = $roleId && isset($users[$roleId]) ? $users[$roleId] : null;
|
||||
$details[] = [
|
||||
'role_label' => $this->roleTypeLabel($item['role_type'] ?? ''),
|
||||
'display_name' => $this->formatReceiverDisplayName(
|
||||
$roleId,
|
||||
trim((string) ($item['receiver_name'] ?? '')),
|
||||
$user
|
||||
),
|
||||
'account' => trim((string) ($item['receiver_account'] ?? '')),
|
||||
'amount' => floatval($item['amount'] ?? 0),
|
||||
'status' => -1,
|
||||
];
|
||||
}
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
protected function loadUsersByIds(array $userIds)
|
||||
{
|
||||
$userIds = array_values(array_unique(array_filter(array_map('intval', $userIds))));
|
||||
if (!$userIds) {
|
||||
return [];
|
||||
}
|
||||
$rows = ShopUser::where('id', 'in', $userIds)->field('id,nickname,mobile')->select();
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$item = is_array($row) ? $row : $row->toArray();
|
||||
$map[intval($item['id'])] = $item;
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
protected function formatReceiverDisplayName($roleId, $receiverName, $user)
|
||||
{
|
||||
$nickname = is_array($user) ? trim((string) ($user['nickname'] ?? '')) : '';
|
||||
$mobile = is_array($user) ? trim((string) ($user['mobile'] ?? '')) : '';
|
||||
if ($nickname !== '') {
|
||||
$label = $nickname;
|
||||
if ($roleId > 0) {
|
||||
$label .= '#' . $roleId;
|
||||
}
|
||||
if ($mobile !== '') {
|
||||
$label .= ' ' . $this->maskMobile($mobile);
|
||||
}
|
||||
return $label;
|
||||
}
|
||||
if ($receiverName !== '') {
|
||||
return $roleId > 0 ? ($receiverName . '#' . $roleId) : $receiverName;
|
||||
}
|
||||
return $roleId > 0 ? ('用户#' . $roleId) : '通用接收方';
|
||||
}
|
||||
|
||||
protected function maskMobile($mobile)
|
||||
{
|
||||
$mobile = trim((string) $mobile);
|
||||
if (strlen($mobile) >= 11) {
|
||||
return substr($mobile, 0, 3) . '****' . substr($mobile, -4);
|
||||
}
|
||||
return $mobile;
|
||||
}
|
||||
|
||||
protected function formatFailReason($wxResult, $status)
|
||||
{
|
||||
if ($status !== 4 || $wxResult === '' || $wxResult === null) {
|
||||
return '';
|
||||
}
|
||||
$raw = is_string($wxResult) ? $wxResult : json_encode($wxResult, JSON_UNESCAPED_UNICODE);
|
||||
if ($raw === '') {
|
||||
return '';
|
||||
}
|
||||
if (strpos($raw, 'fund_freeze_stat=FREEZE') !== false) {
|
||||
return '微信资金冻结,需发货/确认收货后再分账';
|
||||
}
|
||||
$decoded = json_decode($raw, true);
|
||||
if (is_array($decoded)) {
|
||||
$parts = array_filter([
|
||||
$decoded['resp_desc'] ?? '',
|
||||
$decoded['resp_code'] ?? '',
|
||||
$decoded['bank_message'] ?? '',
|
||||
]);
|
||||
if ($parts) {
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
}
|
||||
return mb_strlen($raw) > 120 ? (mb_substr($raw, 0, 120) . '…') : $raw;
|
||||
}
|
||||
|
||||
protected function formatBuyerDisplay($nickname, $mobile, $userId)
|
||||
{
|
||||
$nickname = trim((string) $nickname);
|
||||
|
||||
在新工单中引用
屏蔽一个用户