diff --git a/application/admin/controller/shopro/order/Order.php b/application/admin/controller/shopro/order/Order.php index 4341a13..abdd5d5 100644 --- a/application/admin/controller/shopro/order/Order.php +++ b/application/admin/controller/shopro/order/Order.php @@ -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 diff --git a/application/admin/view/shopro/order/order/detail.html b/application/admin/view/shopro/order/order/detail.html index f85de63..6c68a83 100644 --- a/application/admin/view/shopro/order/order/detail.html +++ b/application/admin/view/shopro/order/order/detail.html @@ -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 @@ 积分:+{{orderDetail.score_amount}} +
+ 佣金分成: + ¥{{commissionTotal}} +
订单总金额: ¥{{orderDetail.total_fee}} @@ -1053,6 +1116,10 @@
积分:+{{orderDetail.score_amount}}
+
+ 佣金分成: + ¥{{commissionTotal}} +
订单总金额: ¥{{orderDetail.total_fee}} @@ -1464,6 +1531,78 @@
+ +
+
+
+ 佣金分成明细 + 共 {{commissionList.length}} 笔分成 +
+
+ 分成总额: + ¥{{commissionTotal}} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/public/assets/js/backend/shopro/order/order.js b/public/assets/js/backend/shopro/order/order.js index 1f8f527..17f064e 100644 --- a/public/assets/js/backend/shopro/order/order.js +++ b/public/assets/js/backend/shopro/order/order.js @@ -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; diff --git a/tests/OrderDetailCommissionTest.php b/tests/OrderDetailCommissionTest.php new file mode 100644 index 0000000..6929e61 --- /dev/null +++ b/tests/OrderDetailCommissionTest.php @@ -0,0 +1,146 @@ +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();