From 85947e43ae02a99a05ecd1828bf09906f0e95f68 Mon Sep 17 00:00:00 2001 From: chen Date: Fri, 18 Sep 2026 11:30:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(partner):=20=E8=B0=83=E6=95=B4=E9=85=92?= =?UTF-8?q?=E9=81=93=E9=A6=86=E4=B8=8E=E5=8C=BA=E5=8E=BF=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=97=A5=E7=BB=93=E5=9D=87=E5=88=86=E6=B1=A0=E4=B8=BA1?= =?UTF-8?q?=E5=88=86=E9=92=9F=E5=A2=9E=E9=87=8F=E7=BB=93=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/partner/DailySettleService.php | 205 +++++++++++++++++- .../shopro/service/partner/PartnerConfig.php | 6 + .../service/partner/PartnerRewardService.php | 46 +++- .../admin/controller/shopro/order/Order.php | 67 ++++-- .../admin/model/shopro/user/WalletLog.php | 33 ++- tests/verify_all_pools_minute_settle_spec.js | 63 ++++++ 6 files changed, 377 insertions(+), 43 deletions(-) create mode 100644 tests/verify_all_pools_minute_settle_spec.js diff --git a/addons/shopro/service/partner/DailySettleService.php b/addons/shopro/service/partner/DailySettleService.php index 18ae224..7edfe2b 100644 --- a/addons/shopro/service/partner/DailySettleService.php +++ b/addons/shopro/service/partner/DailySettleService.php @@ -108,10 +108,8 @@ class DailySettleService // 标记订单已结算马豆 foreach ($unsettledOrders as $o) { - $ext = $o['ext'] ?? []; - if (is_string($ext)) { - $ext = json_decode($ext, true) ?: []; - } + $freshExt = \think\Db::name('shopro_order')->where('id', $o['id'])->value('ext'); + $ext = is_array($freshExt) ? $freshExt : (json_decode($freshExt ?? '', true) ?: []); $ext['madow_settled'] = 1; $ext['madow_settle_time'] = time(); \think\Db::name('shopro_order')->where('id', $o['id'])->update([ @@ -123,7 +121,79 @@ class DailySettleService RateService::clearCache(); } - // 3. 统计今日最新大盘并同步/创建今日 DailyReport + // 3. 酒道馆 1 分钟均分结算(提取未结算酒道馆的合格订单) + $unsettledVenueOrders = []; + $venuePool = 0; + foreach ($allQualifiedOrders as $o) { + $freshExt = \think\Db::name('shopro_order')->where('id', $o['id'])->value('ext'); + $ext = is_array($freshExt) ? $freshExt : (json_decode($freshExt ?? '', true) ?: []); + if (empty($ext['venue_settled'])) { + $unsettledVenueOrders[] = $o; + $rules = PartnerConfig::getOrderEffectiveRules($o); + $vShare = isset($rules['venue_share']) && is_numeric($rules['venue_share']) + ? (float) $rules['venue_share'] + : (float) PartnerConfig::get('partner_venue_share_per_order', 100); + $venuePool = bcadd((string) $venuePool, (string) $vShare, 2); + } + } + + $venueRes = ['pool' => 0, 'count' => 0, 'each' => '0.00']; + $settledVenueOrderIds = []; + if ((float) $venuePool > 0 && !empty($unsettledVenueOrders)) { + $settledVenueOrderIds = array_column($unsettledVenueOrders, 'id'); + $windowStart = time() - (PartnerConfig::activeDays() - 1) * 86400; + $venueIds = $activeService->activeUserIds($windowStart, time(), ['is_venue' => 1]); + + $venueRes = $this->cashShareMinute($venueIds, $venuePool, 'venue_share', 'venue', '酒道馆', $settledVenueOrderIds); + + foreach ($unsettledVenueOrders as $o) { + $freshExt = \think\Db::name('shopro_order')->where('id', $o['id'])->value('ext'); + $ext = is_array($freshExt) ? $freshExt : (json_decode($freshExt ?? '', true) ?: []); + $ext['venue_settled'] = 1; + $ext['venue_settle_time'] = time(); + \think\Db::name('shopro_order')->where('id', $o['id'])->update([ + 'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE), + ]); + } + } + + // 4. 区县代理 1 分钟均分结算(提取未结算区县的合格订单) + $unsettledCountyOrders = []; + $countyPool = 0; + foreach ($allQualifiedOrders as $o) { + $freshExt = \think\Db::name('shopro_order')->where('id', $o['id'])->value('ext'); + $ext = is_array($freshExt) ? $freshExt : (json_decode($freshExt ?? '', true) ?: []); + if (empty($ext['county_settled'])) { + $unsettledCountyOrders[] = $o; + $rules = PartnerConfig::getOrderEffectiveRules($o); + $cShare = isset($rules['county_share']) && is_numeric($rules['county_share']) + ? (float) $rules['county_share'] + : (float) PartnerConfig::get('partner_county_share_per_order', 50); + $countyPool = bcadd((string) $countyPool, (string) $cShare, 2); + } + } + + $countyRes = ['pool' => 0, 'count' => 0, 'each' => '0.00']; + $settledCountyOrderIds = []; + if ((float) $countyPool > 0 && !empty($unsettledCountyOrders)) { + $settledCountyOrderIds = array_column($unsettledCountyOrders, 'id'); + $windowStart = time() - (PartnerConfig::activeDays() - 1) * 86400; + $countyIds = $activeService->activeUserIds($windowStart, time(), ['is_county' => 1]); + + $countyRes = $this->cashShareMinute($countyIds, $countyPool, 'county_share', 'county', '区县', $settledCountyOrderIds); + + foreach ($unsettledCountyOrders as $o) { + $freshExt = \think\Db::name('shopro_order')->where('id', $o['id'])->value('ext'); + $ext = is_array($freshExt) ? $freshExt : (json_decode($freshExt ?? '', true) ?: []); + $ext['county_settled'] = 1; + $ext['county_settle_time'] = time(); + \think\Db::name('shopro_order')->where('id', $o['id'])->update([ + 'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE), + ]); + } + } + + // 5. 统计今日最新大盘并同步/创建今日 DailyReport $todayStart = strtotime($today . ' 00:00:00'); $todayEnd = strtotime($today . ' 23:59:59'); $todayGmv = $rateService->qualifiedGmv($todayStart, $todayEnd); @@ -145,6 +215,24 @@ class DailySettleService ->where('status', 1) ->sum('madow'); + // 当日累计发放酒道馆均分总额(含直接分给酒道馆与尾差/进平台金额) + $todayVenuePool = (float) \think\Db::name('shopro_partner_reward') + ->whereBetween('createtime', [$todayStart, $todayEnd]) + ->where('reward_type', 'venue_share') + ->where('status', 1) + ->sum('amount'); + + // 当日累计发放区县均分总额(含直接分给区县与尾差/进平台金额) + $todayCountyPool = (float) \think\Db::name('shopro_partner_reward') + ->whereBetween('createtime', [$todayStart, $todayEnd]) + ->where('reward_type', 'county_share') + ->where('status', 1) + ->sum('amount'); + + $windowStart = time() - (PartnerConfig::activeDays() - 1) * 86400; + $venueUserCount = count($activeService->activeUserIds($windowStart, time(), ['is_venue' => 1])); + $countyUserCount = count($activeService->activeUserIds($windowStart, time(), ['is_county' => 1])); + $existingTodayReport = DailyReport::where('settle_date', $today)->find(); if ($existingTodayReport) { $existingTodayReport->order_count = $todayOrderCount; @@ -152,6 +240,10 @@ class DailySettleService $existingTodayReport->gmv = $todayGmv; $existingTodayReport->rate = $currentRate; $existingTodayReport->madow_pool = $todayMadowPool; + $existingTodayReport->venue_pool = $todayVenuePool; + $existingTodayReport->venue_user_count = $venueUserCount; + $existingTodayReport->county_pool = $todayCountyPool; + $existingTodayReport->county_user_count = $countyUserCount; $existingTodayReport->voucher_release_total = $todayVoucherReleased; $existingTodayReport->updatetime = time(); $existingTodayReport->save(); @@ -164,10 +256,10 @@ class DailySettleService 'rate' => $currentRate, 'madow_pool' => $todayMadowPool, 'share_user_count' => $madowRes['count'] ?? 0, - 'venue_pool' => 0, - 'venue_user_count' => 0, - 'county_pool' => 0, - 'county_user_count' => 0, + 'venue_pool' => $todayVenuePool, + 'venue_user_count' => $venueUserCount, + 'county_pool' => $todayCountyPool, + 'county_user_count' => $countyUserCount, 'voucher_release_total' => $todayVoucherReleased, 'status' => 1, 'error_msg' => '', @@ -180,6 +272,8 @@ class DailySettleService $rateService->snapshot($today, $todayGmv, [ 'madow_pool' => $todayMadowPool, 'share_user_count' => $madowRes['count'] ?? 0, + 'venue_pool' => $todayVenuePool, + 'county_pool' => $todayCountyPool, ]); return [ @@ -189,7 +283,11 @@ class DailySettleService 'madow_settled_gmv' => (float) $madowGmv, 'madow_pool' => $madowRes['pool'], 'madow_each' => $madowRes['each'], - 'settled_orders' => $settledOrderIds, + 'venue_pool' => $venueRes['pool'], + 'venue_each' => $venueRes['each'], + 'county_pool' => $countyRes['pool'], + 'county_each' => $countyRes['each'], + 'settled_orders' => array_values(array_unique(array_merge($settledOrderIds, $settledVenueOrderIds, $settledCountyOrderIds))), ]; } finally { flock($fp, LOCK_UN); @@ -288,7 +386,8 @@ class DailySettleService 'status' => 1, ]); if ((float) $each > 0) { - Wallet::change($userId, 'commission', $each, $type, [ + $walletType = PartnerConfig::rewardWalletType(); + Wallet::change($userId, $walletType, $each, $type, [ 'settle_date' => $settleDate, ]); AgentModel::where('user_id', $userId)->setInc('total_income', $each); @@ -311,6 +410,82 @@ class DailySettleService } } + /** + * 临时分钟级均分分润(酒道馆 / 区县代理) + */ + public function cashShareMinute($userIds, $pool, $type, $role, $label, $orderIds = []) + { + $count = count($userIds); + $pool = round((float) $pool, 2); + if ($pool <= 0) { + return ['pool' => 0, 'count' => $count, 'each' => '0.00']; + } + + $minId = (int) \think\Db::name('shopro_partner_reward')->where('order_id', '<', 0)->min('order_id'); + $virtualOrderId = min(-100000, $minId - 1); + + if ($count <= 0) { + PartnerRewardService::write([ + 'order_id' => $virtualOrderId, + 'buyer_id' => 0, + 'user_id' => 0, + 'role' => 'system', + 'reward_type' => $type, + 'amount' => $pool, + 'base_amount' => $pool, + 'settle_date' => null, + 'ext' => ['formula' => "{$label}分母为0,奖池 {$pool} 进平台", 'orders' => $orderIds], + 'status' => 1, + ]); + return ['pool' => $pool, 'count' => 0, 'each' => '0.00']; + } + + $each = function_exists('bcdiv') ? bcdiv((string) $pool, (string) $count, 2) : number_format($pool / $count, 2, '.', ''); + $distributed = 0; + + foreach ($userIds as $userId) { + PartnerRewardService::write([ + 'order_id' => $virtualOrderId, + 'buyer_id' => 0, + 'user_id' => $userId, + 'role' => $role, + 'reward_type' => $type, + 'amount' => $each, + 'base_amount' => $pool, + 'settle_date' => null, + 'ext' => ['formula' => "{$label}奖池 {$pool} / {$count} = {$each}", 'share_user_count' => $count, 'pool' => $pool, 'orders' => $orderIds], + 'status' => 1, + ]); + if ((float) $each > 0) { + $walletType = PartnerConfig::rewardWalletType(); + Wallet::change($userId, $walletType, $each, $type, [ + 'minute_settle' => 1, + 'orders' => $orderIds, + ]); + AgentModel::where('user_id', $userId)->setInc('total_income', $each); + } + $distributed = bcadd((string) $distributed, (string) $each, 2); + } + + $tail = bcsub((string) $pool, (string) $distributed, 2); + if ((float) $tail > 0) { + PartnerRewardService::write([ + 'order_id' => $virtualOrderId, + 'buyer_id' => 0, + 'user_id' => 0, + 'role' => 'system', + 'reward_type' => $type, + 'amount' => $tail, + 'base_amount' => $pool, + 'settle_date' => null, + 'ext' => ['formula' => "{$label}尾差 {$tail} 进平台", 'orders' => $orderIds], + 'status' => 1, + ]); + } + + return ['pool' => $pool, 'count' => $count, 'each' => $each]; + } + public function clearDaily($settleDate) { (new VoucherService())->rollbackRelease($settleDate); @@ -323,7 +498,13 @@ class DailySettleService ->select(); foreach ($cashRows as $row) { if ((float) $row->amount > 0) { - Wallet::change($row->user_id, 'commission', -$row->amount, 'reward_back', [ + $walletLog = \app\admin\model\shopro\user\WalletLog::where('user_id', $row->user_id) + ->where('ext', 'like', '%"settle_date":"' . $settleDate . '"%') + ->where('event', $row->reward_type) + ->order('id', 'desc') + ->find(); + $targetWallet = ($walletLog && $walletLog->type === 'commission') ? 'commission' : PartnerConfig::rewardWalletType(); + Wallet::change($row->user_id, $targetWallet, -$row->amount, 'reward_back', [ 'settle_date' => $settleDate, 'rerun' => 1, ], '日结重跑扣回'); diff --git a/addons/shopro/service/partner/PartnerConfig.php b/addons/shopro/service/partner/PartnerConfig.php index d00f125..06f4752 100644 --- a/addons/shopro/service/partner/PartnerConfig.php +++ b/addons/shopro/service/partner/PartnerConfig.php @@ -28,6 +28,12 @@ class PartnerConfig return (bool) self::get('partner_mode', 0); } + public static function rewardWalletType() + { + $type = (string) self::get('partner_reward_wallet', 'money'); + return in_array($type, ['money', 'commission']) ? $type : 'money'; + } + public static function isEnabled() { $enable = self::get('partner_enable', 1); diff --git a/addons/shopro/service/partner/PartnerRewardService.php b/addons/shopro/service/partner/PartnerRewardService.php index 42aad3e..405cad7 100644 --- a/addons/shopro/service/partner/PartnerRewardService.php +++ b/addons/shopro/service/partner/PartnerRewardService.php @@ -78,7 +78,12 @@ class PartnerRewardService foreach ($rewards as $reward) { if (in_array($reward->reward_type, PartnerConfig::instantTypes()) && (float) $reward->amount > 0 && $reward->user_id > 0) { - Wallet::change($reward->user_id, 'commission', -$reward->amount, 'reward_back', [ + $walletLog = \app\admin\model\shopro\user\WalletLog::where('user_id', $reward->user_id) + ->where('ext', 'like', '%"partner_reward_id":' . intval($reward->id) . '%') + ->order('id', 'desc') + ->find(); + $targetWallet = ($walletLog && $walletLog->type === 'commission') ? 'commission' : PartnerConfig::rewardWalletType(); + Wallet::change($reward->user_id, $targetWallet, -$reward->amount, 'reward_back', [ 'partner_reward_id' => $reward->id, 'order_id' => $orderId, ], '收益中心奖励退回'); @@ -200,15 +205,45 @@ class PartnerRewardService return $qualifiedPayFee > 0; } - public function touchSelfOrder($userId, $time = null) + /** + * 刷新有效订单时间(自购及直推订单双向保活) + * 1. 买家本人(自购) + * 2. 买家直属推荐人(直推,严格限制一级上级,二级及以上不触发) + */ + public function touchOrderTimes($buyerId, $time = null) + { + $timestamp = $time ?: time(); + if (!$buyerId) { + return; + } + + // 1. 买家本人自购保活 + $this->updateAgentOrderTime($buyerId, $timestamp); + + // 2. 直推一级上级保活(限定自购以及直推订单都算有效) + $parentUserId = \think\Db::name('user')->where('id', $buyerId)->value('parent_user_id'); + if ($parentUserId && intval($parentUserId) !== intval($buyerId)) { + $this->updateAgentOrderTime($parentUserId, $timestamp); + } + } + + protected function updateAgentOrderTime($userId, $timestamp) { $agent = AgentModel::where('user_id', $userId)->find(); if ($agent) { - $agent->last_self_order_time = $time ?: time(); - $agent->save(); + $current = intval($agent->last_self_order_time ?? 0); + if ($timestamp >= $current) { + $agent->last_self_order_time = $timestamp; + $agent->save(); + } } } + public function touchSelfOrder($userId, $time = null) + { + $this->updateAgentOrderTime($userId, $time ?: time()); + } + public static function dailyOrderId($settleDate) { return -intval(date('Ymd', strtotime($settleDate))); @@ -287,7 +322,8 @@ class PartnerRewardService 'status' => 1, ]); if ($row && self::$lastWriteCreated && (float) $amount > 0 && $userId > 0 && $row->status == 1 && in_array($type, PartnerConfig::instantTypes())) { - Wallet::change($userId, 'commission', $amount, $type, [ + $walletType = PartnerConfig::rewardWalletType(); + Wallet::change($userId, $walletType, $amount, $type, [ 'partner_reward_id' => $row->id, 'order_id' => $orderId, ]); diff --git a/application/admin/controller/shopro/order/Order.php b/application/admin/controller/shopro/order/Order.php index 843c5ab..04e9dbf 100644 --- a/application/admin/controller/shopro/order/Order.php +++ b/application/admin/controller/shopro/order/Order.php @@ -323,25 +323,57 @@ class Order extends Common } if ($settleDate) { + $dayStart = strtotime($settleDate . ' 00:00:00'); + $dayEnd = strtotime($settleDate . ' 23:59:59'); + foreach ($shareTypes as $type) { - $shareRows = \app\admin\model\shopro\partner\Reward::where('settle_date', $settleDate) + $shareRows = \app\admin\model\shopro\partner\Reward::where(function ($q) use ($settleDate, $dayStart, $dayEnd) { + $q->where('settle_date', $settleDate) + ->whereOr(function ($q2) use ($dayStart, $dayEnd) { + $q2->whereNull('settle_date')->whereBetween('createtime', [$dayStart, $dayEnd]); + }); + }) ->where('reward_type', $type) ->where('status', 1) ->where('user_id', '>', 0) ->select(); - $userCount = count($shareRows); + + $distinctUsers = []; + $totalAmount = 0; + $totalMadow = 0; + foreach ($shareRows as $sr) { + $totalAmount = round($totalAmount + (float) ($sr->amount ?? 0), 2); + $totalMadow = function_exists('bcadd') + ? (float) bcadd((string) $totalMadow, (string) ($sr->madow ?? 0), 8) + : ($totalMadow + (float) ($sr->madow ?? 0)); + $distinctUsers[intval($sr->user_id)] = true; + } + $userCount = count($distinctUsers); + if ($userCount <= 0) { // 分母为 0 进平台的记录 - $platform = \app\admin\model\shopro\partner\Reward::where('settle_date', $settleDate) + $platRows = \app\admin\model\shopro\partner\Reward::where(function ($q) use ($settleDate, $dayStart, $dayEnd) { + $q->where('settle_date', $settleDate) + ->whereOr(function ($q2) use ($dayStart, $dayEnd) { + $q2->whereNull('settle_date')->whereBetween('createtime', [$dayStart, $dayEnd]); + }); + }) ->where('reward_type', $type) ->where('status', 1) ->where('user_id', 0) - ->find(); - if (!$platform) { + ->select(); + if (empty($platRows) || count($platRows) <= 0) { continue; } - $platAmount = (float) ($platform->amount ?? 0); - $platMadow = (float) ($platform->madow ?? 0); + $platAmount = 0; + $platMadow = 0; + foreach ($platRows as $pr) { + $platAmount = round($platAmount + (float) ($pr->amount ?? 0), 2); + $platMadow = function_exists('bcadd') + ? (float) bcadd((string) $platMadow, (string) ($pr->madow ?? 0), 8) + : ($platMadow + (float) ($pr->madow ?? 0)); + } + $platform = $platRows[0]; $amountText = $type === 'madow_share' ? \addons\shopro\service\partner\IdentityService::formatMadow($platMadow) . ' 马豆' : '¥' . number_format($platAmount, 2, '.', ''); @@ -367,26 +399,15 @@ class Order extends Common 'unit' => $type === 'madow_share' ? '马豆' : '元', 'formula' => (string) ($platform->formula ?? ''), 'skip_reason_text' => '', - 'tip' => '付款日日结均分:当日无人参与,奖池进平台', + 'tip' => '当日无人参与,奖池进平台', ]; continue; } - $totalAmount = 0; - $totalMadow = 0; - foreach ($shareRows as $sr) { - $totalAmount = round($totalAmount + (float) ($sr->amount ?? 0), 2); - $totalMadow = function_exists('bcadd') - ? (float) bcadd((string) $totalMadow, (string) ($sr->madow ?? 0), 8) - : ($totalMadow + (float) ($sr->madow ?? 0)); - } $first = $shareRows[0]; - $eachAmount = number_format((float) ($first->amount ?? 0), 2, '.', ''); - $eachMadow = \addons\shopro\service\partner\IdentityService::formatMadow($first->madow ?? 0); - $ext = is_array($first->ext) ? $first->ext : []; - if (!empty($ext['share_user_count'])) { - $userCount = intval($ext['share_user_count']); - } + $eachAmount = function_exists('bcdiv') ? bcdiv((string) $totalAmount, (string) $userCount, 2) : number_format($totalAmount / $userCount, 2, '.', ''); + $eachMadowVal = function_exists('bcdiv') ? bcdiv((string) $totalMadow, (string) $userCount, 8) : ($totalMadow / $userCount); + $eachMadow = \addons\shopro\service\partner\IdentityService::formatMadow($eachMadowVal); $items[] = [ 'display' => 'share', @@ -409,7 +430,7 @@ class Order extends Common 'unit' => $type === 'madow_share' ? '马豆' : '元', 'formula' => (string) ($first->formula ?? ''), 'skip_reason_text' => '', - 'tip' => '付款日日结均分(不列出参与用户名单)', + 'tip' => '日结均分(不列出参与用户名单)', ]; } } diff --git a/application/admin/model/shopro/user/WalletLog.php b/application/admin/model/shopro/user/WalletLog.php index 5c3a309..9823a5e 100644 --- a/application/admin/model/shopro/user/WalletLog.php +++ b/application/admin/model/shopro/user/WalletLog.php @@ -42,7 +42,19 @@ class WalletLog extends Common 'recharge_gift' => '线上充值-赠送余额', 'activity_gift' => '活动-赠送余额', 'order_refund' => '订单退款-退还余额', - 'transfer_by_commission' => '服务收益-转入到余额' + 'transfer_by_commission' => '服务收益-转入到余额', + 'reward_income' => '收益分成-入账', + 'reward_back' => '收益分成-退还', + 'push3' => '推荐开拓奖', + 'maker_diff' => '创客进阶奖', + 'maker_peer' => '创客协同奖', + 'maker_overtaken' => '创客卓越奖', + 'team_venue' => '酒道馆运营奖', + 'team_county' => '区县统筹奖', + 'venue_peer' => '酒道馆协同奖', + 'county_peer' => '区县协同奖', + 'venue_share' => '酒道馆加权奖励', + 'county_share' => '区县加权奖励' ], 'commission' => [ 'withdraw' => '提现', @@ -64,7 +76,19 @@ class WalletLog extends Common ]; public function getEventMap() { - return $this->eventMap; + $map = $this->eventMap; + if (class_exists('\\addons\\shopro\\service\\partner\\PartnerConfig')) { + $partnerMap = \addons\shopro\service\partner\PartnerConfig::rewardTypeMap(); + foreach ($partnerMap as $k => $v) { + if (!isset($map['money'][$k])) { + $map['money'][$k] = $v; + } + if (!isset($map['commission'][$k])) { + $map['commission'][$k] = $v; + } + } + } + return $map; } public function scopeMoney($query) @@ -84,6 +108,9 @@ class WalletLog extends Common public function getEventTextAttr($value, $data) { - return $this->eventMap[$data['type']][$data['event']] ?? ''; + $type = $data['type'] ?? ''; + $event = $data['event'] ?? ''; + $eventMap = $this->getEventMap(); + return $eventMap[$type][$event] ?? ($eventMap['commission'][$event] ?? ''); } } diff --git a/tests/verify_all_pools_minute_settle_spec.js b/tests/verify_all_pools_minute_settle_spec.js new file mode 100644 index 0000000..332e40c --- /dev/null +++ b/tests/verify_all_pools_minute_settle_spec.js @@ -0,0 +1,63 @@ +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); + +console.log('========================================================'); +console.log(' 全网奖池(酒道馆/区县代/马豆/兑换券)1分钟结算规范验证'); +console.log('========================================================\n'); + +let passCount = 0; +function test(name, fn) { + try { + fn(); + console.log(` [PASS] ${name}`); + passCount++; + } catch (err) { + console.error(` [FAIL] ${name}: ${err.message}`); + process.exit(1); + } +} + +// 1. DailySettleService.php 验证 +const dailySettlePath = path.resolve(__dirname, '../addons/shopro/service/partner/DailySettleService.php'); +assert(fs.existsSync(dailySettlePath), 'DailySettleService.php 文件存在'); +const settleContent = fs.readFileSync(dailySettlePath, 'utf-8'); + +test('DailySettleService 包含酒道馆与区县增量均分逻辑', () => { + assert(settleContent.includes("empty($ext['venue_settled'])"), '必须检查 venue_settled 增量标记'); + assert(settleContent.includes("empty($ext['county_settled'])"), '必须检查 county_settled 增量标记'); + assert(settleContent.includes("cashShareMinute($venueIds, $venuePool, 'venue_share'"), '调用 cashShareMinute 结算酒道馆均分'); + assert(settleContent.includes("cashShareMinute($countyIds, $countyPool, 'county_share'"), '调用 cashShareMinute 结算区县代理均分'); + assert(settleContent.includes("$ext['venue_settled'] = 1"), '订单必须打上 venue_settled = 1 标记'); + assert(settleContent.includes("$ext['county_settled'] = 1"), '订单必须打上 county_settled = 1 标记'); +}); + +test('DailySettleService 包含 cashShareMinute 方法与资金安全防重', () => { + assert(settleContent.includes('function cashShareMinute('), '必须定义 cashShareMinute 方法'); + assert(settleContent.includes('min(-100000, $minId - 1)'), '使用严格递减负数虚拟 order_id'); + assert(settleContent.includes("'settle_date' => null"), 'settle_date 必须设为 null 避免 uk_daily_user_type 唯一索引冲突'); + assert(settleContent.includes('Wallet::change('), '均分收益实时调用钱包入账'); + assert(settleContent.includes("setInc('total_income'"), '分润金额实时累加至合伙人累计收益'); +}); + +test('DailySettleService runMinute 同步更新 DailyReport 的均分大盘数据', () => { + assert(settleContent.includes('$existingTodayReport->venue_pool = $todayVenuePool'), '报表更新酒道馆池累计'); + assert(settleContent.includes('$existingTodayReport->county_pool = $todayCountyPool'), '报表更新区县池累计'); + assert(settleContent.includes("'venue_pool' => $todayVenuePool"), '报表新建酒道馆池累计'); + assert(settleContent.includes("'county_pool' => $todayCountyPool"), '报表新建区县池累计'); +}); + +// 2. Order.php 订单详情分成展示兼容验证 +const orderControllerPath = path.resolve(__dirname, '../application/admin/controller/shopro/order/Order.php'); +assert(fs.existsSync(orderControllerPath), 'Order.php 控制器存在'); +const orderContent = fs.readFileSync(orderControllerPath, 'utf-8'); + +test('Order.php buildPartnerSplit 支持分钟均分流水时间范围与去重查询', () => { + assert(orderContent.includes("whereNull('settle_date')->whereBetween('createtime'"), '支持通过创建时间查询无结算日的分钟级流水'); + assert(orderContent.includes('$distinctUsers[intval($sr->user_id)] = true'), '参与人数必须按受益人去重'); + assert(orderContent.includes('$userCount = count($distinctUsers)'), '按独立受益人统计均分人数'); +}); + +console.log('\n--------------------------------------------------------'); +console.log(`测试执行结果: 通过 ${passCount} 项, 失败 0 项`); +console.log('========================================================\n');