这个提交包含在:
可乐加冰
2026-09-30 14:01:58 +08:00
当前提交 060590d846
共有 4 个文件被更改,包括 457 次插入 和 3 次删除

查看文件

@@ -486,10 +486,19 @@ class Order extends Base
}
}
// 查询佣金分成明细
$commissions = $this->getOrderCommissions($id, $row);
$commissionTotal = 0;
foreach ($commissions as $comm) {
$commissionTotal += (float)$comm['commission'];
}
return $this->success('获取成功', null, [
'order' => $row,
'item' => $row['item'],
'express' => $row['express']
'order' => $row,
'item' => $row['item'],
'express' => $row['express'],
'commissions' => $commissions,
'commission_total' => number_format($commissionTotal, 2, '.', ''),
]);
}
@@ -497,6 +506,148 @@ class Order extends Base
return $this->view->fetch();
}
/**
* 查询订单关联的佣金分成明细(包括分销商佣金、区域代理分红及通道分账)
*/
protected function getOrderCommissions($orderId, $order = null)
{
$commissions = [];
// 建立商品项名称映射
$itemMap = [];
if (!empty($order['item'])) {
foreach ($order['item'] as $it) {
$sku = !empty($it['goods_sku_text']) ? ' (' . $it['goods_sku_text'] . ')' : '';
$itemMap[$it['id']] = $it['goods_title'] . $sku;
}
}
// 1. 分销佣金明细 (shopro_commission_reward)
try {
$commissionRewards = \app\admin\model\shopro\commission\Reward::with(['agent', 'buyer'])
->where('order_id', $orderId)
->order('id', 'asc')
->select();
foreach ($commissionRewards as $reward) {
$awardTypeName = $reward['award_type_name'] ?? '推广差价';
$levelText = !empty($reward['commission_level']) ? $reward['commission_level'] . '级分销' : '分销奖励';
$commissions[] = [
'id' => $reward['id'],
'category' => 'commission',
'category_text' => '分销佣金',
'award_type_name' => $awardTypeName,
'level_text' => $levelText,
'user' => $reward['agent'] ? [
'id' => $reward['agent']['id'],
'nickname' => $reward['agent']['nickname'],
'avatar' => $reward['agent']['avatar'],
'mobile' => $reward['agent']['mobile'],
] : null,
'commission' => number_format((float)$reward['commission'], 2, '.', ''),
'type_text' => $reward['type_text'] ?: '余额',
'status' => $reward['status'],
'status_text' => $reward['status_text'] ?: '待结算',
'goods_title' => $itemMap[$reward['order_item_id']] ?? '整单',
'order_item_id' => $reward['order_item_id'],
'createtime' => $reward['createtime'],
'createtime_text' => $reward['createtime'] ? date('Y-m-d H:i:s', is_numeric($reward['createtime']) ? $reward['createtime'] : strtotime($reward['createtime'])) : '',
];
}
} catch (\Exception $e) {
}
// 2. 区域代理佣金明细 (shopro_regional_agent_reward)
try {
$regionalRewards = \app\admin\model\shopro\regional_agent\Reward::with(['agentUser', 'agent', 'regionalOrder'])
->where('order_id', $orderId)
->order('id', 'asc')
->select();
foreach ($regionalRewards as $reward) {
$agent = $reward['agent'] ?? null;
$areaText = '';
if ($agent) {
$areaText = $agent['area_name'] ?? '';
if ($agent['area_level'] == 1) {
$areaText .= ' (省代理)';
} elseif ($agent['area_level'] == 2) {
$areaText .= ' (市代理)';
} elseif ($agent['area_level'] == 3) {
$areaText .= ' (区县代理)';
}
}
if (!$areaText && !empty($reward['regionalOrder'])) {
$ro = $reward['regionalOrder'];
$areaText = ($ro['area_name'] ?? '') ?: ($ro['area_id'] ? '区域ID:' . $ro['area_id'] : '区域代理');
}
$commissions[] = [
'id' => $reward['id'],
'category' => 'regional',
'category_text' => '区域代理佣金',
'award_type_name' => '区域分红',
'level_text' => $areaText ?: '区域代理',
'user' => $reward['agentUser'] ? [
'id' => $reward['agentUser']['id'],
'nickname' => $reward['agentUser']['nickname'],
'avatar' => $reward['agentUser']['avatar'],
'mobile' => $reward['agentUser']['mobile'],
] : null,
'commission' => number_format((float)$reward['commission'], 2, '.', ''),
'type_text' => ($reward['type'] == 'money' ? '余额' : ($reward['type'] ?: '余额')),
'status' => $reward['status'],
'status_text' => $reward['status_text'] ?: '待入账',
'goods_title' => $itemMap[$reward['order_item_id']] ?? '整单',
'order_item_id' => $reward['order_item_id'],
'createtime' => $reward['createtime'],
'createtime_text' => $reward['createtime'] ? date('Y-m-d H:i:s', is_numeric($reward['createtime']) ? $reward['createtime'] : strtotime($reward['createtime'])) : '',
];
}
} catch (\Exception $e) {
}
// 3. 通道分账明细 (shopro_split_order,若有)
try {
if (class_exists('\\addons\\shopro\\model\\split\\SplitOrder')) {
$splitOrders = \addons\shopro\model\split\SplitOrder::with(['receivers'])
->where('order_id', $orderId)
->select();
foreach ($splitOrders as $so) {
if (!empty($so['receivers'])) {
foreach ($so['receivers'] as $rec) {
$commissions[] = [
'id' => $rec['id'],
'category' => 'split',
'category_text' => '通道分账',
'award_type_name' => ($so['channel'] ?? '') === 'huifu' ? '汇付分账' : '微信分账',
'level_text' => $rec['description'] ?: ($rec['role_type'] ?? '分账方'),
'user' => [
'id' => $rec['receiver_account'] ?? '',
'nickname' => $rec['receiver_name'] ?: ($rec['receiver_account'] ?? '分账接收方'),
'avatar' => '',
'mobile' => '',
],
'commission' => number_format((float)$rec['amount'], 2, '.', ''),
'type_text' => ($so['channel'] ?? '') === 'huifu' ? '汇付渠道' : '微信渠道',
'status' => $rec['status'],
'status_text' => $rec['status'] == 1 ? '分账成功' : ($rec['status'] == 2 ? '分账失败' : '待分账'),
'goods_title' => '整单分账',
'order_item_id' => 0,
'createtime' => $so['createtime'],
'createtime_text' => $so['createtime'] ? date('Y-m-d H:i:s', is_numeric($so['createtime']) ? $so['createtime'] : strtotime($so['createtime'])) : '',
];
}
}
}
}
} catch (\Exception $e) {
}
return $commissions;
}
/**
* 拆分商品
* @throws PDOException

查看文件

@@ -846,6 +846,65 @@
}
}
.commission-container {
margin-top: 14px;
background: #fff;
padding: 16px 20px 20px;
border-radius: 4px;
}
.commission-header {
justify-content: space-between;
margin-bottom: 14px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
}
.commission-title {
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
}
.commission-badge {
font-size: 12px;
font-weight: normal;
background: #F3EFFB;
color: #7536D0;
padding: 2px 8px;
border-radius: 10px;
margin-left: 10px;
}
.commission-total-info {
font-size: 13px;
}
.commission-total-money {
color: #FF5000;
font-size: 16px;
font-weight: 600;
}
.table-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid #e6e6e6;
}
.table-avatar-placeholder {
width: 32px;
height: 32px;
border-radius: 50%;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
[v-cloak] {
display: none
}
@@ -966,6 +1025,10 @@
<span class="title-span-width">积分:</span><span
style="padding-left: 4px;">+{{orderDetail.score_amount}}</span>
</div>
<div v-if="Number(commissionTotal) > 0" class="order-msg-right-item">
<span class="title-span-width">佣金分成:</span>
<span style="color: #7536D0;font-weight: 500;">¥{{commissionTotal}}</span>
</div>
<div class="order-msg-right-item">
<span class="order-msg-right-title color-444 title-span-width">订单总金额:</span>
<span class="order-msg-right-money">¥{{orderDetail.total_fee}}</span>
@@ -1053,6 +1116,10 @@
<div v-if="orderDetail.type=='score'" class="order-msg-right-item">
<span class="title-span-width">积分:</span><span>+{{orderDetail.score_amount}}</span>
</div>
<div v-if="Number(commissionTotal) > 0" class="order-msg-right-item">
<span class="title-span-width">佣金分成:</span>
<span style="color: #7536D0;font-weight: 500;">¥{{commissionTotal}}</span>
</div>
<div class="order-msg-right-item">
<span class="order-msg-right-title color-444 title-span-width">订单总金额:</span>
<span class="order-msg-right-money">¥{{orderDetail.total_fee}}</span>
@@ -1464,6 +1531,78 @@
</el-table-column>
</el-table>
</div>
<!-- 佣金分成明细 -->
<div class="commission-container" v-if="commissionList && commissionList.length > 0">
<div class="commission-header display-flex">
<div class="commission-title color-333">
<span>佣金分成明细</span>
<span class="commission-badge">共 {{commissionList.length}} 笔分成</span>
</div>
<div class="commission-total-info">
<span class="color-666">分成总额: </span>
<span class="commission-total-money">¥{{commissionTotal}}</span>
</div>
</div>
<el-table :data="commissionList" style="width: 100%" border stripe size="small">
<el-table-column label="分成类型" width="130">
<template slot-scope="scope">
<el-tag size="small" :type="scope.row.category == 'commission' ? '' : (scope.row.category == 'regional' ? 'warning' : 'success')" effect="plain">
{{scope.row.category_text}}
</el-tag>
</template>
</el-table-column>
<el-table-column label="奖励类型" width="130">
<template slot-scope="scope">
<span class="color-333" style="font-weight: 500;">{{scope.row.award_type_name}}</span>
<div class="color-999 font-size-12" v-if="scope.row.level_text">{{scope.row.level_text}}</div>
</template>
</el-table-column>
<el-table-column label="收益人 / 代理商" min-width="190">
<template slot-scope="scope">
<div v-if="scope.row.user" class="display-flex">
<img v-if="scope.row.user.avatar" class="table-avatar" :src="Fast.api.cdnurl(scope.row.user.avatar)">
<div v-else class="table-avatar-placeholder"><i class="el-icon-user"></i></div>
<div style="margin-left: 8px;">
<div class="theme-color cursor-pointer" @click="goUserDetail(scope.row.user.id)">
{{scope.row.user.nickname || '用户ID:' + scope.row.user.id}}
</div>
<div class="color-999 font-size-12" v-if="scope.row.user.mobile">{{scope.row.user.mobile}} (ID:{{scope.row.user.id}})</div>
<div class="color-999 font-size-12" v-else-if="scope.row.user.id">ID: {{scope.row.user.id}}</div>
</div>
</div>
<span v-else class="color-999">-</span>
</template>
</el-table-column>
<el-table-column label="关联商品" min-width="180">
<template slot-scope="scope">
<span class="color-666">{{scope.row.goods_title || '整单'}}</span>
</template>
</el-table-column>
<el-table-column label="分成金额" width="120">
<template slot-scope="scope">
<span class="theme-red" style="font-weight: bold; font-size: 14px;">¥{{scope.row.commission}}</span>
</template>
</el-table-column>
<el-table-column label="结算状态" width="110">
<template slot-scope="scope">
<div class="display-flex" :class="rewardStatusClass(scope.row.status)">
<span class="shopro-status-dot"></span>
<span>{{scope.row.status_text}}</span>
</div>
</template>
</el-table-column>
<el-table-column label="入账方式" width="110">
<template slot-scope="scope">
<span class="color-666">{{scope.row.type_text}}</span>
</template>
</el-table-column>
<el-table-column label="分成时间" width="170">
<template slot-scope="scope">
<span class="color-666">{{scope.row.createtime_text}}</span>
</template>
</el-table-column>
</el-table>
</div>
<!-- 发货 -->
<el-dialog custom-class="order-dialog" title="订单发货" :visible.sync="express_dialog"
:before-close="closeDeliverDialog">

查看文件

@@ -842,6 +842,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
orderId: new URLSearchParams(location.search).get('id'),
orderDetail: [],
orderDetailCopy: [],
commissionList: [],
commissionTotal: '0.00',
goodsList: [], //所有商品
onlyExpressGoodsList: [], //物流快递
noRefundGoodsList: [], //去除退款的
@@ -1017,6 +1019,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
that.noRefundGoodsList = noRefund;
that.packageList = res.data.express;
that.commissionList = res.data.commissions || [];
that.commissionTotal = res.data.commission_total || '0.00';
that.changeSteps();
that.getNoSendGoodsList();
@@ -1156,6 +1160,20 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
goUserDetail(user_id) {
Fast.api.open('shopro/user/user/profile?id=' + user_id, "用户详情");
},
rewardStatusClass(status) {
switch (Number(status)) {
case -2:
return 'shopro-status-info';
case -1:
return 'shopro-status-error';
case 0:
return 'shopro-status-warning';
case 1:
return 'shopro-status-success';
default:
return '';
}
},
addMemo(type) {
if (type == 'cancel') {
this.addMemoFlag = false;

查看文件

@@ -0,0 +1,146 @@
<?php
/**
* 订单详情佣金分成明细查询与展示单元测试 (TDD)
*/
class OrderDetailCommissionTest
{
private $passed = 0;
private $failed = 0;
public function run()
{
echo "=== 开始执行 订单详情佣金分成明细查询与展示单元测试 (TDD) ===\n";
$this->testOrderControllerHasCommissionMethod();
$this->testOrderControllerDetailIncludesCommissions();
$this->testOrderDetailViewHasCommissionSection();
$this->testOrderDetailJsHasCommissionData();
$this->testOrder64CommissionDataQuery();
echo "\n测试结果: {$this->passed} 通过, {$this->failed} 失败\n";
if ($this->failed > 0) {
exit(1);
}
}
private function assert($condition, $message)
{
if ($condition) {
echo " [PASS] {$message}\n";
$this->passed++;
} else {
echo " [FAIL] {$message}\n";
$this->failed++;
}
}
/**
* 1. 验证 Order 控制器中必须包含 getOrderCommissions 辅助方法
*/
public function testOrderControllerHasCommissionMethod()
{
$controllerPath = __DIR__ . '/../application/admin/controller/shopro/order/Order.php';
$this->assert(is_file($controllerPath), "Order.php 必须存在: {$controllerPath}");
$content = file_get_contents($controllerPath);
$hasMethod = strpos($content, 'function getOrderCommissions(') !== false;
$this->assert($hasMethod, "Order 控制器必须包含 getOrderCommissions 方法以获取分销与区域代理等分成明细");
}
/**
* 2. 验证 Order 控制器的 detail 方法返回值中必须包含 commissions 和 commission_total
*/
public function testOrderControllerDetailIncludesCommissions()
{
$controllerPath = __DIR__ . '/../application/admin/controller/shopro/order/Order.php';
$content = file_get_contents($controllerPath);
$startPos = strpos($content, 'public function detail(');
$this->assert($startPos !== false, "Order 控制器必须包含 detail 方法");
$nextFuncPos = strpos($content, 'public function ', $startPos + 20);
$methodBody = substr($content, $startPos, ($nextFuncPos !== false ? $nextFuncPos - $startPos : 1000));
$hasCommissionsKey = strpos($methodBody, "'commissions'") !== false || strpos($methodBody, '"commissions"') !== false;
$hasTotalKey = strpos($methodBody, "'commission_total'") !== false || strpos($methodBody, '"commission_total"') !== false;
$this->assert($hasCommissionsKey, "Order::detail 返回数据必须包含 'commissions' 键");
$this->assert($hasTotalKey, "Order::detail 返回数据必须包含 'commission_total' 键");
}
/**
* 3. 验证 detail.html 视图中必须包含佣金分成明细表格区域
*/
public function testOrderDetailViewHasCommissionSection()
{
$viewPath = __DIR__ . '/../application/admin/view/shopro/order/order/detail.html';
$this->assert(is_file($viewPath), "detail.html 必须存在: {$viewPath}");
$content = file_get_contents($viewPath);
$hasSection = strpos($content, 'commissionList') !== false && strpos($content, '佣金分成明细') !== false;
$this->assert($hasSection, "detail.html 必须包含佣金分成明细 (commissionList) 展示模块");
}
/**
* 4. 验证 order.js 中包含 commissionList 状态与数据接收
*/
public function testOrderDetailJsHasCommissionData()
{
$jsPath = __DIR__ . '/../public/assets/js/backend/shopro/order/order.js';
$this->assert(is_file($jsPath), "order.js 必须存在: {$jsPath}");
$content = file_get_contents($jsPath);
$hasState = strpos($content, 'commissionList') !== false;
$hasReceive = strpos($content, 'res.data.commissions') !== false;
$this->assert($hasState, "order.js 必须声明 commissionList 变量");
$this->assert($hasReceive, "order.js 的 getDetail 必须接收并赋值 res.data.commissions");
}
/**
* 5. 验证数据库中订单 64 的实际分成数据
*/
public function testOrder64CommissionDataQuery()
{
$dbConfigFile = __DIR__ . '/../application/database.php';
if (!is_file($dbConfigFile)) {
$this->assert(false, "数据库配置文件不存在");
return;
}
require_once __DIR__ . '/../thinkphp/base.php';
$dbConfig = require $dbConfigFile;
try {
$pdo = new PDO(
"mysql:host={$dbConfig['hostname']};dbname={$dbConfig['database']};charset=utf8mb4",
$dbConfig['username'],
$dbConfig['password']
);
// 查询分销佣金
$stmt = $pdo->query("SELECT * FROM fa_shopro_commission_reward WHERE order_id = 64");
$commRewards = $stmt->fetchAll(PDO::FETCH_ASSOC);
$this->assert(count($commRewards) >= 1, "订单 64 必须存在至少 1 笔分销佣金记录 (当前: " . count($commRewards) . ")");
// 查询区域代理佣金
$stmt = $pdo->query("SELECT * FROM fa_shopro_regional_agent_reward WHERE order_id = 64");
$regRewards = $stmt->fetchAll(PDO::FETCH_ASSOC);
$this->assert(count($regRewards) >= 1, "订单 64 必须存在至少 1 笔区域代理佣金记录 (当前: " . count($regRewards) . ")");
$total = 0;
foreach ($commRewards as $r) {
$total += floatval($r['commission']);
}
foreach ($regRewards as $r) {
$total += floatval($r['commission']);
}
$this->assert(abs($total - 208.00) < 0.01, "订单 64 的分成总金额必须为 208.00 (计算得: {$total})");
} catch (Exception $e) {
$this->assert(false, "数据库测试失败: " . $e->getMessage());
}
}
}
$test = new OrderDetailCommissionTest();
$test->run();