From aca0d3c671a60d576f1a0bc00f0daf3189a96566 Mon Sep 17 00:00:00 2001 From: chen Date: Sat, 19 Sep 2026 11:49:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B6=E7=9B=8A=E5=85=A8=E9=A2=9D?= =?UTF-8?q?=E7=9B=B4=E5=85=A5=E5=95=86=E5=9F=8E=E4=BD=99=E9=A2=9D=E3=80=81?= =?UTF-8?q?=E5=AD=98=E9=87=8F=E7=BB=93=E8=BD=AC=E4=B8=8E=E5=A5=96=E9=A1=B9?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E5=85=A8=E5=B1=80=E8=A7=84=E8=8C=83=E5=8C=96?= =?UTF-8?q?=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/shopro/Shopro.php | 1 + addons/shopro/console/PartnerInstall.php | 20 +- .../console/PartnerTransferCommission.php | 84 ++++ addons/shopro/controller/Pay.php | 105 +++- addons/shopro/controller/commission/Agent.php | 19 +- addons/shopro/controller/partner/Index.php | 14 + addons/shopro/controller/user/User.php | 13 + addons/shopro/controller/user/WalletLog.php | 96 +++- addons/shopro/listener/Partner.php | 9 +- addons/shopro/service/commission/Goods.php | 12 +- addons/shopro/service/commission/Order.php | 6 +- .../shopro/service/partner/ActiveService.php | 71 ++- .../service/partner/DailySettleService.php | 35 +- .../service/partner/IdentityService.php | 17 + .../shopro/service/partner/PartnerConfig.php | 31 +- .../service/partner/PartnerRewardService.php | 5 +- .../service/partner/PeerShareReward.php | 6 +- addons/shopro/service/partner/Push3Reward.php | 8 +- .../service/partner/TeamSplitReward.php | 12 +- .../partner/WalletMigrationService.php | 141 ++++++ addons/shopro/upgrade/partner_v2.sql | 1 + .../admin/controller/shopro/Config.php | 19 + .../admin/controller/shopro/Dashboard.php | 6 +- .../controller/shopro/commission/Agent.php | 8 +- .../controller/shopro/commission/Reward.php | 2 +- .../admin/controller/shopro/order/Order.php | 10 +- .../controller/shopro/partner/Reward.php | 41 +- .../admin/controller/shopro/user/User.php | 10 +- .../controller/shopro/user/WalletLog.php | 18 +- .../admin/model/shopro/app/ScoreSkuPrice.php | 6 +- .../admin/model/shopro/partner/Reward.php | 37 ++ .../admin/model/shopro/user/WalletLog.php | 30 +- .../admin/view/shopro/app/score_shop/add.html | 4 +- .../view/shopro/app/score_shop/index.html | 2 +- .../view/shopro/commission/agent/detail.html | 32 +- .../view/shopro/commission/agent/index.html | 2 +- .../view/shopro/commission/goods/edit.html | 26 +- .../view/shopro/commission/reward/index.html | 1 + .../admin/view/shopro/config/index.html | 6 +- .../admin/view/shopro/dashboard/index.html | 8 +- .../admin/view/shopro/order/order/detail.html | 15 +- .../view/shopro/partner/daily/index.html | 10 +- .../view/shopro/partner/reward/index.html | 22 +- .../view/shopro/partner/voucher/index.html | 14 +- .../admin/view/shopro/user/user/detail.html | 48 +- .../admin/view/shopro/user/user/index.html | 14 +- .../admin/view/shopro/user/user/recharge.html | 6 +- public/assets/js/backend/shopro/config.js | 1 + .../js/backend/shopro/partner/reward.js | 14 +- scripts/repair_order_12_county_reward.php | 134 +++++ scripts/transfer_all_commission_to_money.php | 103 ++++ .../cashier/verify_cashier_mixed_pay_spec.js | 297 +++++++++++ .../verify_active_order_source_spec.js | 176 +++++++ .../verify_admin_views_alignment_spec.js | 153 ++++++ .../verify_agent_upgrade_php8_spec.php | 87 ++++ .../verify_api_profile_madow_online.php | 25 + .../verify_income_criteria_alignment_spec.js | 194 ++++++++ tests/partner/verify_madow_switch_spec.js | 163 ++++++ .../verify_partner_reward_to_money_spec.js | 191 ++++++++ .../verify_reward_names_rename_spec.js | 137 ++++++ .../verify_team_county_dual_identity_spec.js | 193 ++++++++ tests/partner/verify_team_self_order_spec.php | 101 ++++ tests/partner/verify_wallet_migration_spec.js | 193 ++++++++ .../verify_wallet_records_alignment_spec.js | 193 ++++++++ tests/verify_custom_role_names_spec.js | 8 +- ...ify_score_shop_voucher_integration_spec.js | 2 +- tests/verify_voucher_unit_ge_spec.js | 132 +++++ uniapp/pages/app/score-shop.vue | 2 +- .../commission/components/account-info.vue | 14 +- uniapp/pages/commission/index.vue | 16 +- uniapp/pages/commission/madow.vue | 9 +- uniapp/pages/commission/rewards.vue | 27 +- uniapp/pages/commission/team.vue | 10 +- uniapp/pages/commission/voucher.vue | 25 +- uniapp/pages/order/confirm.vue | 14 +- uniapp/pages/pay/index.vue | 463 ++++++++++++++++-- uniapp/pages/pay/withdraw.vue | 10 +- uniapp/pages/user/wallet/commission.vue | 56 ++- uniapp/pages/user/wallet/money.vue | 6 +- .../s-wallet-card/s-wallet-card.vue | 5 +- uniapp/sheep/platform/index.js | 4 +- uniapp/sheep/platform/pay.js | 327 ++++++++----- uniapp/sheep/store/user.js | 1 + 83 files changed, 4146 insertions(+), 443 deletions(-) create mode 100644 addons/shopro/console/PartnerTransferCommission.php create mode 100644 addons/shopro/service/partner/WalletMigrationService.php create mode 100644 scripts/repair_order_12_county_reward.php create mode 100644 scripts/transfer_all_commission_to_money.php create mode 100644 tests/cashier/verify_cashier_mixed_pay_spec.js create mode 100644 tests/partner/verify_active_order_source_spec.js create mode 100644 tests/partner/verify_admin_views_alignment_spec.js create mode 100644 tests/partner/verify_agent_upgrade_php8_spec.php create mode 100644 tests/partner/verify_api_profile_madow_online.php create mode 100644 tests/partner/verify_income_criteria_alignment_spec.js create mode 100644 tests/partner/verify_madow_switch_spec.js create mode 100644 tests/partner/verify_partner_reward_to_money_spec.js create mode 100644 tests/partner/verify_reward_names_rename_spec.js create mode 100644 tests/partner/verify_team_county_dual_identity_spec.js create mode 100644 tests/partner/verify_team_self_order_spec.php create mode 100644 tests/partner/verify_wallet_migration_spec.js create mode 100644 tests/partner/verify_wallet_records_alignment_spec.js create mode 100644 tests/verify_voucher_unit_ge_spec.js diff --git a/addons/shopro/Shopro.php b/addons/shopro/Shopro.php index b41e0f0..f1272c6 100755 --- a/addons/shopro/Shopro.php +++ b/addons/shopro/Shopro.php @@ -112,6 +112,7 @@ class Shopro extends Addons 'addons\shopro\console\PartnerDemo', 'addons\shopro\console\PartnerAccept', 'addons\shopro\console\PartnerClean', + 'addons\shopro\console\PartnerTransferCommission', ]); } diff --git a/addons/shopro/console/PartnerInstall.php b/addons/shopro/console/PartnerInstall.php index 080e7f5..ef573b6 100644 --- a/addons/shopro/console/PartnerInstall.php +++ b/addons/shopro/console/PartnerInstall.php @@ -227,24 +227,8 @@ class PartnerInstall extends Command protected function backfillLastSelfOrder(Output $output) { - $rows = Db::name('shopro_order') - ->where('status', 'in', ['paid', 'completed']) - ->where('paid_time', '>', 0) - ->field('user_id, max(paid_time) as last_paid') - ->group('user_id') - ->select(); - $count = 0; - foreach ($rows as $row) { - $agent = Db::name('shopro_commission_agent')->where('user_id', $row['user_id'])->find(); - if (!$agent || !empty($agent['last_self_order_time'])) { - continue; - } - Db::name('shopro_commission_agent')->where('user_id', $row['user_id'])->update([ - 'last_self_order_time' => intval($row['last_paid']), - ]); - $count++; - } - $output->writeln("backfill last_self_order_time: {$count}"); + $count = (new \addons\shopro\service\partner\ActiveService())->syncActiveTimes(); + $output->writeln("backfill last_self_order_time (self + direct): {$count}"); } protected function clearConfigCache(Output $output) diff --git a/addons/shopro/console/PartnerTransferCommission.php b/addons/shopro/console/PartnerTransferCommission.php new file mode 100644 index 0000000..d54316f --- /dev/null +++ b/addons/shopro/console/PartnerTransferCommission.php @@ -0,0 +1,84 @@ +setName('shopro:partner:transfer-commission') + ->addOption('dry-run', 'd', Option::VALUE_NONE, '只读试算预览,不真实扣减与增加资金') + ->addOption('force', 'f', Option::VALUE_NONE, '强制执行,不提示二次确认') + ->setDescription('将所有会员账户已有佣金收益全量结转至商城余额'); + } + + protected function execute(Input $input, Output $output) + { + $dryRun = (bool) $input->getOption('dry-run'); + $force = (bool) $input->getOption('force'); + + $output->writeln("========================================================"); + $output->writeln(" 恒联数通 · 历史收益全量结转至商城余额工具"); + $output->writeln("========================================================"); + + $service = new WalletMigrationService(); + $preview = $service->preview(); + + $output->writeln("待处理会员总数: {$preview['total_users']} 人"); + $output->writeln("待结转收益总金额: ¥{$preview['total_commission']} 元"); + + if ($preview['total_users'] === 0) { + $output->writeln("\n当前无任何待结转佣金余额,无需处理。"); + return 0; + } + + $output->writeln("\n部分待结转会员抽样(前 10 名):"); + foreach (array_slice($preview['samples'], 0, 10) as $i => $s) { + $idx = $i + 1; + $output->writeln(" [{$idx}] 会员ID: {$s['id']} | 手机: {$s['mobile']} | 当前佣金: ¥{$s['current_commission']} -> 商城余额: ¥{$s['current_money']} (结转后预计: ¥{$s['target_money']})"); + } + + if ($dryRun) { + $output->writeln("\n【DRY-RUN 试算模式已结束】未对数据库发生任何资金变动。"); + return 0; + } + + if (!$force) { + $output->writeln("\n【安全警告】即将对全量 {$preview['total_users']} 名会员执行结转,将 ¥{$preview['total_commission']} 元佣金划入商城余额!"); + $output->writeln("若确认执行,请增加参数 --force 或 -f,例如:"); + $output->writeln("php think shopro:partner:transfer-commission --force"); + return 1; + } + + $output->writeln("\n>>> 开始执行资金划转..."); + $startTime = microtime(true); + + $res = $service->transferAll(false, function ($current, $total, $userId, $amount, $err) use ($output) { + if ($err) { + $output->writeln(" [{$current}/{$total}] 会员 {$userId} 结转失败: {$err}"); + } else { + $output->writeln(" [{$current}/{$total}] 会员 {$userId} 成功结转 ¥" . number_format($amount, 2, '.', '') . " 元至商城余额"); + } + }); + + $cost = round(microtime(true) - $startTime, 2); + + $output->writeln("\n========================================================"); + $output->writeln("结转完成!耗时: {$cost} 秒"); + $output->writeln("成功处理: {$res['success_count']} 人"); + $output->writeln("失败人数: {$res['fail_count']} 人"); + $output->writeln("累计结转资金: ¥{$res['transferred_amount']} 元"); + $output->writeln("========================================================"); + + return $res['fail_count'] > 0 ? 1 : 0; + } +} diff --git a/addons/shopro/controller/Pay.php b/addons/shopro/controller/Pay.php index 530ea82..b790370 100644 --- a/addons/shopro/controller/Pay.php +++ b/addons/shopro/controller/Pay.php @@ -36,7 +36,7 @@ class Pay extends Common $payment = $this->request->post('payment'); $openid = $this->request->post('openid', ''); $money = $this->request->post('money', 0); - $money = $money > 0 ? $money : 0; + $money = floatval($money) > 0 ? round(floatval($money), 2) : 0; $platform = $this->request->header('platform'); list($order, $order_type) = $this->getOrderInstance($order_sn); @@ -65,30 +65,62 @@ class Pay extends Common // pay 实例 $payOper = new PayOper(); - if ($money && $order_type == 'order') { - // 余额混合支付 - $order = Db::transaction(function () use ($payOper, $order, $order_type, $money) { + $mixedPayRecord = null; + if ($money > 0 && $order_type == 'order') { + // 余额混合支付(行锁串行化保障并发资金安全) + $order = Db::transaction(function () use ($order, $order_type, $money, $user, &$mixedPayRecord) { // 加锁读订单 $order = $order->lock(true)->find($order->id); + if (in_array($order->status, [$order::STATUS_PAID, $order::STATUS_COMPLETED])) { + $this->error('订单已支付'); + } - // 余额支付 - $order = $payOper->money($order, $money, $order_type); + // 事务内行级排他锁查验用户最新可用余额,杜绝并发快照双花 + $lockedUser = \app\admin\model\shopro\user\User::where('id', $user->id)->lock(true)->find(); + if (bccomp((string)$money, (string)$lockedUser->money, 2) === 1) { + $this->error('账户可用余额不足以进行本次抵扣'); + } + + $deductMoney = min($money, floatval($order->remain_pay_fee)); + if ($deductMoney <= 0) { + return $order; + } + + // 使用锁定的实时用户对象执行余额扣减 + $lockedPayOper = new PayOper($lockedUser); + $order = $lockedPayOper->money($order, $deductMoney, $order_type); return $order; }); + // 获取刚才产生的余额支付记录,用于异常情况冲正 + $mixedPayRecord = PayModel::where('order_id', $order->id) + ->where('order_type', $order_type) + ->where('pay_type', 'money') + ->order('id', 'desc') + ->find(); + if (in_array($order->status, [$order::STATUS_PAID, $order::STATUS_COMPLETED])) { $this->success('订单支付成功', $order); } } if ($payment == 'money' && $order_type == 'order') { - // 余额支付 - $order = Db::transaction(function () use ($payOper, $order, $order_type) { + // 纯余额支付 + $order = Db::transaction(function () use ($order, $order_type, $user) { // 加锁读订单 $order = $order->lock(true)->find($order->id); + if (in_array($order->status, [$order::STATUS_PAID, $order::STATUS_COMPLETED])) { + $this->error('订单已支付'); + } - $order = $payOper->money($order, $order->remain_pay_fee, $order_type); + $lockedUser = \app\admin\model\shopro\user\User::where('id', $user->id)->lock(true)->find(); + if (bccomp((string)$order->remain_pay_fee, (string)$lockedUser->money, 2) === 1) { + $this->error('账户可用余额不足以付清该订单'); + } + + $lockedPayOper = new PayOper($lockedUser); + $order = $lockedPayOper->money($order, $order->remain_pay_fee, $order_type); return $order; }); @@ -146,7 +178,10 @@ class Pay extends Common } if (empty($order_data['payer']['openid'])) { - // 缺少 openid + // 缺少 openid 时,若前面预扣了余额则冲正 + if ($mixedPayRecord) { + $this->rollbackMixedMoney($order, $mixedPayRecord, $user, $order_type); + } $this->error('miss_openid', -1); } } @@ -160,11 +195,18 @@ class Pay extends Common try { $result = $payService->pay($order_data); - } catch (\Yansongda\Pay\Exception\Exception $e) { - $this->error('支付失败' . (config('app_debug') ? ":" . $e->getMessage() : ',请重试')); - } catch (HttpResponseException $e) { - $data = $e->getResponse()->getData(); - $message = $data ? ($data['msg'] ?? '') : $e->getMessage(); + } catch (\Exception $e) { + // 第三方预支付异常,安全冲正本次混合支付预扣款 + if ($mixedPayRecord) { + $this->rollbackMixedMoney($order, $mixedPayRecord, $user, $order_type); + } + + if ($e instanceof HttpResponseException) { + $data = $e->getResponse()->getData(); + $message = $data ? ($data['msg'] ?? '') : $e->getMessage(); + } else { + $message = $e->getMessage(); + } $this->error('支付失败' . (config('app_debug') ? ":" . $message : ',请重试')); } @@ -183,6 +225,39 @@ class Pay extends Common ]); } + /** + * 混合支付预下单异常时,冲正退回预扣余额 + * + * @param \think\Model $order + * @param \think\Model $mixedPayRecord + * @param \think\Model $user + * @param string $order_type + * @return void + */ + protected function rollbackMixedMoney($order, $mixedPayRecord, $user, $order_type) + { + try { + Db::transaction(function () use ($order, $mixedPayRecord, $user, $order_type) { + \addons\shopro\service\Wallet::change($user, 'money', $mixedPayRecord->pay_fee, 'order_refund', [ + 'order_id' => $order->id, + 'order_sn' => $order->order_sn, + 'order_type' => $order_type, + 'remark' => '混合支付预下单未完成,安全冲正退回' + ]); + + $mixedPayRecord->status = PayModel::PAY_STATUS_REFUND; + $mixedPayRecord->refund_fee = $mixedPayRecord->pay_fee; + $mixedPayRecord->save(); + + $order = $order->lock(true)->find($order->id); + $order->remain_pay_fee = bcadd((string)$order->remain_pay_fee, (string)$mixedPayRecord->pay_fee, 2); + $order->save(); + }); + } catch (\Exception $e) { + Log::write('混合支付预扣款冲正异常: ' . $e->getMessage(), 'error'); + } + } + /** diff --git a/addons/shopro/controller/commission/Agent.php b/addons/shopro/controller/commission/Agent.php index 6cb2fdd..be5d8f6 100644 --- a/addons/shopro/controller/commission/Agent.php +++ b/addons/shopro/controller/commission/Agent.php @@ -303,16 +303,18 @@ class Agent extends Commission ->column('id'); if (empty($teamUserIds)) { + $selfAgent = AgentModel::where('user_id', $agentId)->find(); + $selfUser = UserModel::where('id', $agentId)->find(); return [ 'total_count' => 0, 'active_count' => 0, 'expiring_count' => 0, 'inactive_count' => 0, 'partner_count' => 0, - 'team_total_consume' => '0.00', + 'team_total_consume' => \addons\shopro\service\partner\IdentityService::formatMoney($selfUser->total_consume ?? 0), 'team_total_reward' => '0.00', 'team_month_reward' => '0.00', - 'team_order_count' => 0, + 'team_order_count' => intval($selfAgent->child_order_count_0 ?? 0), ]; } @@ -348,8 +350,17 @@ class Agent extends Commission $inactiveCount += $missingAgentCount; } - // 1. 团队累计自购总业绩 - $teamTotalConsume = UserModel::whereIn('id', $teamUserIds)->sum('total_consume'); + // 1. 团队累计自购总业绩与团队订单总数(统一使用包含本人自购的分销团队全口径) + $selfAgent = AgentModel::where('user_id', $agentId)->find(); + $selfUser = UserModel::where('id', $agentId)->find(); + + $teamTotalConsume = isset($selfAgent->child_order_money_all) && floatval($selfAgent->child_order_money_all) > 0 + ? floatval($selfAgent->child_order_money_all) + : (UserModel::whereIn('id', $teamUserIds)->sum('total_consume') + floatval($selfUser->total_consume ?? 0)); + + $teamOrderCount = isset($selfAgent->child_order_count_all) && intval($selfAgent->child_order_count_all) > 0 + ? intval($selfAgent->child_order_count_all) + : ($teamOrderCount + intval($selfAgent->child_order_count_0 ?? 0)); // 2. 团队成员为当前用户贡献的累计收益与本月收益 $monthStart = strtotime(date('Y-m-01 00:00:00')); diff --git a/addons/shopro/controller/partner/Index.php b/addons/shopro/controller/partner/Index.php index 7913112..d075d43 100644 --- a/addons/shopro/controller/partner/Index.php +++ b/addons/shopro/controller/partner/Index.php @@ -28,8 +28,10 @@ class Index extends Commission ->where('createtime', '>=', $monthStart) ->count(); $agent = IdentityService::attachAgent($this->service->agent); + $totalIncome = $agent['total_income'] ?? '0.00'; $this->success('ok', [ + 'total_income' => $totalIncome, 'month_income' => IdentityService::formatMoney($monthIncome), 'skip_count' => intval($skipCount), 'active_30d' => $agent['active_30d'] ?? true, @@ -92,10 +94,21 @@ class Index extends Commission } } + $monthStart = strtotime(date('Y-m-01 00:00:00')); + $monthIncome = RewardModel::where('user_id', $userId) + ->where('status', 1) + ->where('createtime', '>=', $monthStart) + ->whereIn('reward_type', array_merge(PartnerConfig::instantTypes(), ['venue_share', 'county_share'])) + ->sum('amount'); + $agent = IdentityService::attachAgent($this->service->agent); + $totalIncome = $agent['total_income'] ?? '0.00'; + $this->success('ok', [ 'total' => $list->total(), 'per_page' => $list->listRows(), 'current_page' => $list->currentPage(), + 'total_income' => $totalIncome, + 'month_income' => IdentityService::formatMoney($monthIncome), 'data' => $rows, 'buyer_info' => $buyerInfo, ]); @@ -127,6 +140,7 @@ class Index extends Commission 'madow' => $agent['madow'] ?? '0.00000000', 'madow_rate' => $agent['madow_rate'] ?? IdentityService::formatRate(null), 'madow_value' => $agent['madow_value'] ?? '0.00', + 'show_madow_rate' => PartnerConfig::showMadowRate(), 'list' => $list, ]); } diff --git a/addons/shopro/controller/user/User.php b/addons/shopro/controller/user/User.php index ab299fc..8da608f 100644 --- a/addons/shopro/controller/user/User.php +++ b/addons/shopro/controller/user/User.php @@ -84,7 +84,20 @@ class User extends Common $agent = \addons\shopro\service\partner\IdentityService::findAgent($user->id); $partner = \addons\shopro\service\partner\IdentityService::attachProfile($agent); + if (empty($partner['total_income']) || $partner['total_income'] === '0.00') { + $userRewardCash = \app\admin\model\shopro\partner\Reward::where('user_id', $user->id) + ->where('status', 1) + ->where('amount', '>', 0) + ->sum('amount'); + if ($userRewardCash > 0) { + $formatted = \addons\shopro\service\partner\IdentityService::formatMoney($userRewardCash); + $partner['total_income'] = $formatted; + $partner['income_total_cash'] = $formatted; + } + } $data = array_merge($user->toArray(), $partner); + // 对齐用户余额字段:统一映射商城余额,避免前端不同字段读取出现口径偏差 + $data['balance'] = $user->money; $this->success('个人详情', $data); } diff --git a/addons/shopro/controller/user/WalletLog.php b/addons/shopro/controller/user/WalletLog.php index 2be0a09..7ea7d6c 100644 --- a/addons/shopro/controller/user/WalletLog.php +++ b/addons/shopro/controller/user/WalletLog.php @@ -19,20 +19,102 @@ class WalletLog extends Common $list_rows = $this->request->param('list_rows', 10); $date = $this->request->param('date/a'); $user = auth_user(); - $where['user_id'] = $user->id; + + $rewardEvents = [ + 'push3', 'maker_diff', 'maker_peer', 'maker_overtaken', + 'team_venue', 'team_county', 'venue_peer', 'county_peer', + 'venue_share', 'county_share', 'reward_income', 'transfer_by_commission', 'transfer_to_money' + ]; + + $baseQuery = function () use ($user, $type, $rewardEvents) { + $model = UserWalletLogModel::where('user_id', $user->id); + if ($type === 'commission') { + // 收益记录:既包含历史 commission 流水,也对齐直入 money 的收益分成流水 + $model->where(function ($query) use ($rewardEvents) { + $query->where('type', 'commission') + ->whereOr(function ($sub) use ($rewardEvents) { + $sub->where('type', 'money')->whereIn('event', $rewardEvents); + }); + }); + } else { + $model->where('type', $type); + } + return $model; + }; + + $filterQuery = $baseQuery(); + + if (!empty($date) && is_array($date) && count($date) === 2) { + $filterQuery->whereTime('createtime', 'between', $date); + } switch ($tab) { + case 'reward': + $filterQuery->where('amount', '>', 0)->whereIn('event', $rewardEvents); + break; case 'income': - $where['amount'] = ['>', 0]; + $filterQuery->where('amount', '>', 0); break; case 'expense': - $where['amount'] = ['<', 0]; + $filterQuery->where('amount', '<', 0); break; } - $income = UserWalletLogModel::where('user_id', $user->id)->{$type}()->where('amount', '>', 0)->whereTime('createtime', 'between', $date)->sum('amount'); - $expense = UserWalletLogModel::where('user_id', $user->id)->{$type}()->where('amount', '<', 0)->whereTime('createtime', 'between', $date)->sum('amount'); - $logs = UserWalletLogModel::where($where)->{$type}()->whereTime('createtime', 'between', $date)->order('createtime', 'desc')->paginate($list_rows); - $this->success('获取成功', ['list' => $logs, 'income' => $income, 'expense' => $expense]); + // 统计收入与支出(统一统计口径,避免内部互转重叠统计造成误导) + $incomeQuery = $baseQuery(); + $expenseQuery = $baseQuery(); + $rewardQuery = $baseQuery(); + + if (!empty($date) && is_array($date) && count($date) === 2) { + $incomeQuery->whereTime('createtime', 'between', $date); + $expenseQuery->whereTime('createtime', 'between', $date); + $rewardQuery->whereTime('createtime', 'between', $date); + } + + $actualRewardEvents = [ + 'push3', 'maker_diff', 'maker_peer', 'maker_overtaken', + 'team_venue', 'team_county', 'venue_peer', 'county_peer', + 'venue_share', 'county_share', 'reward_income' + ]; + + if ($type === 'commission') { + // 收益流水统计口径对齐: + // 1. 真实收益(总收入):排除内部互转入账事件 transfer_by_commission,避免佣金转入余额时重复计入收益 + // 2. 真实扣减(总支出):排除内部结转转出事件 transfer_to_money 与 commission_transfer,避免误导用户认为产生了消费支出 + $income = (float) $incomeQuery->where('amount', '>', 0) + ->where(function ($q) use ($actualRewardEvents) { + $q->where(function ($sub) use ($actualRewardEvents) { + $sub->where('type', 'money')->whereIn('event', $actualRewardEvents); + })->whereOr(function ($sub) { + $sub->where('type', 'commission')->whereNotIn('event', ['transfer_by_commission']); + }); + })->sum('amount'); + + $expense = (float) $expenseQuery->where('amount', '<', 0) + ->whereNotIn('event', ['transfer_to_money', 'commission_transfer']) + ->sum('amount'); + + $rewardIncome = (float) $rewardQuery->where('amount', '>', 0) + ->where(function ($q) use ($actualRewardEvents) { + $q->where(function ($sub) use ($actualRewardEvents) { + $sub->where('type', 'money')->whereIn('event', $actualRewardEvents); + })->whereOr(function ($sub) { + $sub->where('type', 'commission')->whereNotIn('event', ['transfer_by_commission']); + }); + })->sum('amount'); + } else { + $income = (float) $incomeQuery->where('amount', '>', 0)->sum('amount'); + $expense = (float) $expenseQuery->where('amount', '<', 0)->sum('amount'); + $rewardIncome = (float) $rewardQuery->where('amount', '>', 0)->whereIn('event', $actualRewardEvents)->sum('amount'); + } + + $logs = $filterQuery->order('id', 'desc')->paginate($list_rows); + + $this->success('获取成功', [ + 'list' => $logs, + 'income' => $income, + 'expense' => $expense, + 'reward_income' => $rewardIncome, + ]); } } diff --git a/addons/shopro/listener/Partner.php b/addons/shopro/listener/Partner.php index 16af816..3ce0171 100644 --- a/addons/shopro/listener/Partner.php +++ b/addons/shopro/listener/Partner.php @@ -38,12 +38,9 @@ class Partner \addons\shopro\service\partner\RateService::clearCache(); $userId = $order['user_id'] ?? ($params['user']['id'] ?? 0); if ($userId) { - $agent = new AgentService($userId); - if (method_exists($agent, 'touchLastSelfOrderTime')) { - $agent->touchLastSelfOrderTime(); - } else { - $service->touchSelfOrder($userId); - } + // 自购与直推订单双向保活(自购更新买家,直推更新一级推荐人) + $paidTime = !empty($order['paid_time']) ? intval($order['paid_time']) : time(); + $service->touchOrderTimes($userId, $paidTime); } Queue::push('\addons\shopro\job\PartnerSettle@order', [ 'order_id' => $order['id'], diff --git a/addons/shopro/service/commission/Goods.php b/addons/shopro/service/commission/Goods.php index 46745fb..c8ae903 100755 --- a/addons/shopro/service/commission/Goods.php +++ b/addons/shopro/service/commission/Goods.php @@ -38,7 +38,7 @@ class Goods if ($this->commissionGoods) { $commission_config = $this->commissionGoods->commission_config; - if ($commission_config['status']) { + if (!empty($commission_config) && !empty($commission_config['status'])) { $this->commissionConfig = $commission_config; } switch ($this->commissionGoods->self_rules) { @@ -112,10 +112,12 @@ class Goods $commissionRules = []; foreach ($agentLevelRules as $agentLevel => $rule) { $rule = json_decode($rule, true); - foreach ($rule as $commission_level => $percent) { - $commission_level = explode('_', $commission_level); - $level = intval($commission_level[1]); - $commissionRules[$agentLevel][$level] = ['rate' => $percent]; + if (is_array($rule)) { + foreach ($rule as $commission_level => $percent) { + $commission_level = explode('_', $commission_level); + $level = intval($commission_level[1] ?? 1); + $commissionRules[$agentLevel][$level] = ['rate' => $percent]; + } } } return $commissionRules; diff --git a/addons/shopro/service/commission/Order.php b/addons/shopro/service/commission/Order.php index 34bf86c..c1d903b 100755 --- a/addons/shopro/service/commission/Order.php +++ b/addons/shopro/service/commission/Order.php @@ -71,8 +71,8 @@ class Order return false; } - // 是否自购分销订单 - $this->selfBuy = $this->buyer->isAgentAvaliable() && $this->config->isSelfBuy(); + // 是否自购分销订单(收益中心模式或开启自购时,分销商本人订单均记为自购订单以累积团队业绩) + $this->selfBuy = $this->buyer->isAgentAvaliable() && ($this->config->isSelfBuy() || boolval(sheep_config('shop.commission.partner_mode'))); // 未找到上级分销商且不是自购 if (!$this->buyer->getParentUserId() && !$this->selfBuy) { @@ -202,7 +202,7 @@ class Order 'buyer_id' => $commissionOrder->buyer_id, // 购买人 'commission_order_id' => $commissionOrder->id, // 分销订单ID 'agent_id' => $currentAgent->user->id, // 待分佣分销商ID - 'type' => 'commission', // 奖金类型 + 'type' => \addons\shopro\service\partner\PartnerConfig::rewardWalletType(), // 奖金类型 'commission' => $commission, // 佣金 'status' => 0, // 佣金状态 'original_commission' => $commission, // 原始佣金 diff --git a/addons/shopro/service/partner/ActiveService.php b/addons/shopro/service/partner/ActiveService.php index 5c00342..b29ea3a 100644 --- a/addons/shopro/service/partner/ActiveService.php +++ b/addons/shopro/service/partner/ActiveService.php @@ -36,11 +36,20 @@ class ActiveService return $this->hasQualifiedOrder($userId, $start, $at); } + /** + * 判断指定用户在时间窗口内是否有有效订单(本人自购或直推下级合格订单) + */ public function hasQualifiedOrder($userId, $start, $end) { + if (!$userId) { + return false; + } $rateService = new RateService(); + $directChildIds = \think\Db::name('user')->where('parent_user_id', $userId)->column('id'); + $candidateUserIds = array_merge([$userId], $directChildIds); + $order = $rateService->qualifiedOrderQuery($start, $end) - ->where('o.user_id', $userId) + ->whereIn('o.user_id', $candidateUserIds) ->find(); return (bool) $order; } @@ -70,6 +79,7 @@ class ActiveService if (!empty($nullUserIds)) { $rateService = new RateService(); + // 2.1 自购订单命中 $orderHits = $rateService->qualifiedOrderQuery($start, $end) ->whereIn('o.user_id', $nullUserIds) ->group('o.user_id') @@ -79,8 +89,67 @@ class ActiveService \think\Db::name('shopro_commission_agent')->where('user_id', $uId)->update(['last_self_order_time' => $lastTime]); $hitUserIds[] = intval($uId); } + + // 2.2 直推下级订单命中(针对仍未命中的代理商) + $remainingNullIds = array_diff($nullUserIds, array_keys($orderHits)); + if (!empty($remainingNullIds)) { + $userTable = \think\Db::name('user')->getTable(); + $directHits = $rateService->qualifiedOrderQuery($start, $end) + ->join($userTable . ' u', 'u.id = o.user_id') + ->whereIn('u.parent_user_id', $remainingNullIds) + ->group('u.parent_user_id') + ->column('max(o.paid_time)', 'u.parent_user_id'); + + foreach ($directHits as $pId => $lastTime) { + \think\Db::name('shopro_commission_agent')->where('user_id', $pId)->update(['last_self_order_time' => $lastTime]); + $hitUserIds[] = intval($pId); + } + } } return array_values(array_unique(array_map('intval', $hitUserIds))); } + + /** + * 批量校准所有分销商的有效订单时间(自购及直推合格订单最大值回填) + */ + public function syncActiveTimes($sinceTime = null) + { + $rateService = new RateService(); + $userTable = \think\Db::name('user')->getTable(); + + // 1. 本人自购最新合格订单时间 + $selfQuery = $rateService->qualifiedOrderQuery($sinceTime) + ->group('o.user_id') + ->column('max(o.paid_time)', 'o.user_id'); + + // 2. 直推下级最新合格订单时间 + $directQuery = $rateService->qualifiedOrderQuery($sinceTime) + ->join($userTable . ' u', 'u.id = o.user_id') + ->where('u.parent_user_id', '>', 0) + ->group('u.parent_user_id') + ->column('max(o.paid_time)', 'u.parent_user_id'); + + // 3. 汇总取最大值 + $allUserIds = array_unique(array_merge(array_keys($selfQuery), array_keys($directQuery))); + $updated = 0; + + foreach ($allUserIds as $uId) { + $selfTime = intval($selfQuery[$uId] ?? 0); + $directTime = intval($directQuery[$uId] ?? 0); + $maxTime = max($selfTime, $directTime); + + if ($maxTime > 0) { + $agent = \think\Db::name('shopro_commission_agent')->where('user_id', $uId)->find(); + if ($agent && $maxTime > intval($agent['last_self_order_time'] ?? 0)) { + \think\Db::name('shopro_commission_agent')->where('user_id', $uId)->update([ + 'last_self_order_time' => $maxTime, + ]); + $updated++; + } + } + } + + return $updated; + } } diff --git a/addons/shopro/service/partner/DailySettleService.php b/addons/shopro/service/partner/DailySettleService.php index 7edfe2b..5be3937 100644 --- a/addons/shopro/service/partner/DailySettleService.php +++ b/addons/shopro/service/partner/DailySettleService.php @@ -373,6 +373,7 @@ class DailySettleService } $each = function_exists('bcdiv') ? bcdiv((string) $pool, (string) $count, 2) : number_format($pool / $count, 2, '.', ''); $distributed = 0; + $walletType = PartnerConfig::rewardWalletType(); foreach ($userIds as $userId) { PartnerRewardService::write([ 'order_id' => $orderId, @@ -382,11 +383,14 @@ class DailySettleService 'amount' => $each, 'base_amount' => $pool, 'settle_date' => $settleDate, - 'ext' => ['formula' => "{$label}奖池 {$pool} / {$count} = {$each}", 'share_user_count' => $count], + 'ext' => [ + 'formula' => "{$label}奖池 {$pool} / {$count} = {$each}", + 'share_user_count' => $count, + 'wallet_type' => $walletType, + ], 'status' => 1, ]); if ((float) $each > 0) { - $walletType = PartnerConfig::rewardWalletType(); Wallet::change($userId, $walletType, $each, $type, [ 'settle_date' => $settleDate, ]); @@ -404,7 +408,10 @@ class DailySettleService 'amount' => $tail, 'base_amount' => $pool, 'settle_date' => $settleDate, - 'ext' => ['formula' => "{$label}尾差 {$tail} 进平台"], + 'ext' => [ + 'formula' => "{$label}尾差 {$tail} 进平台", + 'wallet_type' => 'system', + ], 'status' => 1, ]); } @@ -434,7 +441,11 @@ class DailySettleService 'amount' => $pool, 'base_amount' => $pool, 'settle_date' => null, - 'ext' => ['formula' => "{$label}分母为0,奖池 {$pool} 进平台", 'orders' => $orderIds], + 'ext' => [ + 'formula' => "{$label}分母为0,奖池 {$pool} 进平台", + 'orders' => $orderIds, + 'wallet_type' => 'system', + ], 'status' => 1, ]); return ['pool' => $pool, 'count' => 0, 'each' => '0.00']; @@ -442,6 +453,7 @@ class DailySettleService $each = function_exists('bcdiv') ? bcdiv((string) $pool, (string) $count, 2) : number_format($pool / $count, 2, '.', ''); $distributed = 0; + $walletType = PartnerConfig::rewardWalletType(); foreach ($userIds as $userId) { PartnerRewardService::write([ @@ -453,11 +465,16 @@ class DailySettleService 'amount' => $each, 'base_amount' => $pool, 'settle_date' => null, - 'ext' => ['formula' => "{$label}奖池 {$pool} / {$count} = {$each}", 'share_user_count' => $count, 'pool' => $pool, 'orders' => $orderIds], + 'ext' => [ + 'formula' => "{$label}奖池 {$pool} / {$count} = {$each}", + 'share_user_count' => $count, + 'pool' => $pool, + 'orders' => $orderIds, + 'wallet_type' => $walletType, + ], 'status' => 1, ]); if ((float) $each > 0) { - $walletType = PartnerConfig::rewardWalletType(); Wallet::change($userId, $walletType, $each, $type, [ 'minute_settle' => 1, 'orders' => $orderIds, @@ -478,7 +495,11 @@ class DailySettleService 'amount' => $tail, 'base_amount' => $pool, 'settle_date' => null, - 'ext' => ['formula' => "{$label}尾差 {$tail} 进平台", 'orders' => $orderIds], + 'ext' => [ + 'formula' => "{$label}尾差 {$tail} 进平台", + 'orders' => $orderIds, + 'wallet_type' => 'system', + ], 'status' => 1, ]); } diff --git a/addons/shopro/service/partner/IdentityService.php b/addons/shopro/service/partner/IdentityService.php index 0d5d4bf..6752fa8 100644 --- a/addons/shopro/service/partner/IdentityService.php +++ b/addons/shopro/service/partner/IdentityService.php @@ -153,7 +153,17 @@ class IdentityService } $data = is_object($agent) ? $agent->toArray() : $agent; + $userId = intval($data['user_id'] ?? 0); + $agentTotalIncome = floatval($data['total_income'] ?? 0); + $rewardCash = $userId > 0 ? \app\admin\model\shopro\partner\Reward::where('user_id', $userId) + ->where('status', 1) + ->where('amount', '>', 0) + ->sum('amount') : 0; + $unifiedTotalIncome = max($agentTotalIncome, floatval($rewardCash)); + return array_merge($data, $identity, [ + 'total_income' => self::formatMoney($unifiedTotalIncome), + 'income_total_cash' => self::formatMoney($unifiedTotalIncome), 'is_venue' => intval($data['is_venue'] ?? 0), 'is_county' => intval($data['is_county'] ?? 0), 'county_code' => (string) ($data['county_code'] ?? ''), @@ -161,6 +171,7 @@ class IdentityService 'madow' => self::formatMadow($madow), 'madow_rate' => self::formatRate($rate), 'madow_value' => self::formatMoney((float) $madow * (float) $rate), + 'show_madow_rate' => PartnerConfig::showMadowRate(), 'voucher_total' => self::formatMoney($total), 'voucher_released' => self::formatMoney($released), 'voucher_used' => self::formatMoney($used), @@ -190,6 +201,9 @@ class IdentityService 'madow' => '0', 'madow_rate' => self::formatRate($baseRate), 'madow_value' => '0.00', + 'show_madow_rate' => PartnerConfig::showMadowRate(), + 'total_income' => '0.00', + 'income_total_cash' => '0.00', 'voucher_available' => '0.00', 'active_30d' => true, 'active_status' => 'inactive', @@ -211,9 +225,12 @@ class IdentityService 'maker_level_name' => $full['maker_level_name'], 'role_names' => $roleNames, 'custom_role_names' => $roleNames, + 'total_income' => $full['total_income'] ?? '0.00', + 'income_total_cash' => $full['income_total_cash'] ?? '0.00', 'madow' => $full['madow'], 'madow_rate' => $full['madow_rate'], 'madow_value' => $full['madow_value'], + 'show_madow_rate' => $full['show_madow_rate'] ?? PartnerConfig::showMadowRate(), 'voucher_available' => $full['voucher_available'], 'active_30d' => $full['active_30d'], 'active_status' => $full['active_status'] ?? 'inactive', diff --git a/addons/shopro/service/partner/PartnerConfig.php b/addons/shopro/service/partner/PartnerConfig.php index 06f4752..a10c901 100644 --- a/addons/shopro/service/partner/PartnerConfig.php +++ b/addons/shopro/service/partner/PartnerConfig.php @@ -40,6 +40,15 @@ class PartnerConfig return $enable === null ? true : (bool) $enable; } + public static function showMadowRate() + { + $show = self::get('partner_show_madow_rate', 1); + if ($show === null || $show === '') { + return 1; + } + return intval($show) ? 1 : 0; + } + public static function activeDays() { $days = intval(self::get('partner_active_days', 30)); @@ -97,18 +106,20 @@ class PartnerConfig $maker = $roles['maker']; $venue = $roles['venue']; $county = $roles['county']; + $countyShort = in_array($county, ['区县代理', '区县']) ? '区县' : $county; + $countyAgentShort = in_array($county, ['区县代理', '区县']) ? '区县代' : $county; return [ - 'push3' => '推荐开拓奖', + 'push3' => '推荐提成', 'maker_diff' => $maker . '进阶奖', 'maker_peer' => $maker . '协同奖', 'maker_overtaken' => $maker . '卓越奖', - 'team_venue' => $venue . '运营奖', - 'team_county' => $county . '统筹奖', - 'venue_peer' => $venue . '协同奖', - 'county_peer' => $county . '协同奖', - 'venue_share' => $venue . '加权奖励', - 'county_share' => $county . '加权奖励', + 'team_venue' => $venue . '管理奖', + 'team_county' => $countyShort . '管理奖', + 'venue_peer' => $venue . '平级奖', + 'county_peer' => $countyAgentShort . '平级奖', + 'venue_share' => $venue . '加权奖', + 'county_share' => $countyShort . '加权奖', 'madow_share' => '每日贡献马豆', 'admin_madow' => '管理员调整马豆', 'voucher_grant' => '消费补贴发放', @@ -139,13 +150,13 @@ class PartnerConfig 'partner_off' => '结算未开启', ]; $user = [ - 'no_parent' => '暂无推荐人,不计提推荐开拓奖', - 'self_buy' => '本人下单不计提推荐开拓奖', + 'no_parent' => '暂无推荐人,不计提推荐提成', + 'self_buy' => '本人下单不计提推荐提成', 'inactive_30d' => '需 30 日内有本人订单才能领取奖励', 'not_maker' => '', 'not_venue' => '', 'not_county' => '', - 'cap_not_higher' => '同阶不计进阶差额,享受协同奖励', + 'cap_not_higher' => '同阶不计进阶差额,享受平级奖励', 'already_settled' => '', 'partner_goods_off' => '', 'partner_off' => '', diff --git a/addons/shopro/service/partner/PartnerRewardService.php b/addons/shopro/service/partner/PartnerRewardService.php index 405cad7..57aa1d1 100644 --- a/addons/shopro/service/partner/PartnerRewardService.php +++ b/addons/shopro/service/partner/PartnerRewardService.php @@ -310,6 +310,10 @@ class PartnerRewardService public static function account($order, $buyerId, $userId, $type, $role, $amount, $base, $ext = []) { $orderId = is_array($order) || $order instanceof \think\Model ? ($order['id'] ?? 0) : intval($order); + $walletType = PartnerConfig::rewardWalletType(); + if (!isset($ext['wallet_type'])) { + $ext['wallet_type'] = $walletType; + } $row = self::write([ 'order_id' => $orderId, 'buyer_id' => $buyerId, @@ -322,7 +326,6 @@ class PartnerRewardService 'status' => 1, ]); if ($row && self::$lastWriteCreated && (float) $amount > 0 && $userId > 0 && $row->status == 1 && in_array($type, PartnerConfig::instantTypes())) { - $walletType = PartnerConfig::rewardWalletType(); Wallet::change($userId, $walletType, $amount, $type, [ 'partner_reward_id' => $row->id, 'order_id' => $orderId, diff --git a/addons/shopro/service/partner/PeerShareReward.php b/addons/shopro/service/partner/PeerShareReward.php index e4fe64b..34be244 100644 --- a/addons/shopro/service/partner/PeerShareReward.php +++ b/addons/shopro/service/partner/PeerShareReward.php @@ -3,7 +3,7 @@ namespace addons\shopro\service\partner; /** - * 酒道馆 / 区县协同奖 10% 逐代递减级联,最低发放不能少于 0.10 元截断 + * 酒道馆 / 区县代平级奖 10% 逐代递减级联,最低发放不能少于 0.10 元截断 */ class PeerShareReward { @@ -66,7 +66,7 @@ class PeerShareReward $type, $role, 'inactive_30d', - "{$label}协同(第{$gen}代) 30 日无自购" + "{$label}平级(第{$gen}代) 30 日无自购" ); } else { PartnerRewardService::account( @@ -79,7 +79,7 @@ class PeerShareReward $currentBase, [ 'generation' => $gen, - 'formula' => "{$label}协同(第{$gen}代) {$currentBase} × " . ($rate * 100) . "% = {$amount}", + 'formula' => "{$label}平级(第{$gen}代) {$currentBase} × " . ($rate * 100) . "% = {$amount}", 'from_user_id' => $sourceUserId, ] ); diff --git a/addons/shopro/service/partner/Push3Reward.php b/addons/shopro/service/partner/Push3Reward.php index d2f0d3c..7e7b942 100644 --- a/addons/shopro/service/partner/Push3Reward.php +++ b/addons/shopro/service/partner/Push3Reward.php @@ -5,18 +5,18 @@ namespace addons\shopro\service\partner; use app\admin\model\shopro\commission\Agent as AgentModel; /** - * 推荐开拓奖:398/400/600 循环,计到推荐人,自购不发 + * 推荐提成:398/400/600 循环,计到推荐人,自购不发 */ class Push3Reward { public function run($order, $buyerId, $parent, ActiveService $active, $rules = null) { if (!$parent) { - PartnerRewardService::writeSkip($order, $buyerId, 0, 'push3', 'taster', 'no_parent', '暂无推荐人,不计提推荐开拓奖'); + PartnerRewardService::writeSkip($order, $buyerId, 0, 'push3', 'taster', 'no_parent', '暂无推荐人,不计提推荐提成'); return; } if (intval($parent['user_id']) === intval($buyerId)) { - PartnerRewardService::writeSkip($order, $buyerId, $parent['user_id'], 'push3', 'taster', 'self_buy', '本人下单不计提推荐开拓奖'); + PartnerRewardService::writeSkip($order, $buyerId, $parent['user_id'], 'push3', 'taster', 'self_buy', '本人下单不计提推荐提成'); return; } if (intval($parent['level']) < 1) { @@ -42,7 +42,7 @@ class Push3Reward $amount = $amounts[($seq - 1) % 3]; PartnerRewardService::account($order, $buyerId, $agent->user_id, 'push3', 'taster', $amount, $amount, [ - 'formula' => "推荐开拓第{$seq}单,循环第" . ((($seq - 1) % 3) + 1) . "档 +{$amount}", + 'formula' => "推荐提成第{$seq}单,循环第" . ((($seq - 1) % 3) + 1) . "档 +{$amount}", 'seq' => $seq, ]); } diff --git a/addons/shopro/service/partner/TeamSplitReward.php b/addons/shopro/service/partner/TeamSplitReward.php index 368a059..fe3c7c4 100644 --- a/addons/shopro/service/partner/TeamSplitReward.php +++ b/addons/shopro/service/partner/TeamSplitReward.php @@ -3,7 +3,7 @@ namespace addons\shopro\service\partner; /** - * 统筹运营奖励:无馆有县=100;有馆有县=50+50;有馆无县=50 + * 渠道管理奖(馆/县拆分):无馆有县=100;有馆有县=50+50;有馆无县=50 */ class TeamSplitReward { @@ -30,7 +30,7 @@ class TeamSplitReward PartnerRewardService::writeSkip($order, $buyerId, $venue['user_id'], 'team_venue', 'venue', 'inactive_30d', '酒道馆 30 日无自购'); } else { PartnerRewardService::account($order, $buyerId, $venue['user_id'], 'team_venue', 'venue', $venueSplit, $venueSplit, [ - 'formula' => "酒道馆运营奖 {$venueSplit}", + 'formula' => "酒道馆管理奖 {$venueSplit}", ]); $result['venue'] = ['user_id' => $venue['user_id'], 'amount' => $venueSplit, 'index' => $venue['index']]; } @@ -41,8 +41,11 @@ class TeamSplitReward if ($county['inactive']) { PartnerRewardService::writeSkip($order, $buyerId, $county['user_id'], 'team_county', 'county', 'inactive_30d', '区县 30 日无自购'); } else { + $formulaText = $venue + ? (($venue['user_id'] == $county['user_id']) ? "区县管理奖 {$amount}(馆兼县)" : "区县管理奖 {$amount}(馆上)") + : "区县管理奖 {$amount}(直达)"; PartnerRewardService::account($order, $buyerId, $county['user_id'], 'team_county', 'county', $amount, $amount, [ - 'formula' => $venue ? "区县统筹奖 {$amount}(馆上)" : "区县统筹奖 {$amount}(直达)", + 'formula' => $formulaText, ]); $result['county'] = ['user_id' => $county['user_id'], 'amount' => $amount, 'index' => $county['index']]; } @@ -71,7 +74,8 @@ class TeamSplitReward protected function nearestCounty($chain, $venue, ActiveService $active) { - $start = $venue ? ($venue['index'] + 1) : 0; + // 若酒道馆节点本身兼任区县代理,允许从该酒道馆节点开始匹配(兼具双重身份场景);若未兼任,循环会自动检查其上一级 + $start = $venue ? $venue['index'] : 0; for ($i = $start; $i < count($chain); $i++) { if (!intval($chain[$i]['is_county'] ?? 0)) { continue; diff --git a/addons/shopro/service/partner/WalletMigrationService.php b/addons/shopro/service/partner/WalletMigrationService.php new file mode 100644 index 0000000..6ed7f9e --- /dev/null +++ b/addons/shopro/service/partner/WalletMigrationService.php @@ -0,0 +1,141 @@ +', 0); + $totalCount = (int) $query->count(); + $totalCommission = (float) $query->sum('commission'); + + $sampleList = UserModel::where('commission', '>', 0) + ->field('id,nickname,mobile,money,commission') + ->order('commission', 'desc') + ->limit(20) + ->select(); + + $samples = []; + foreach ($sampleList as $u) { + $samples[] = [ + 'id' => (int) $u->id, + 'nickname' => (string) ($u->nickname ?: '用户' . $u->id), + 'mobile' => (string) ($u->mobile ?: ''), + 'current_money' => number_format((float) $u->money, 2, '.', ''), + 'current_commission' => number_format((float) $u->commission, 2, '.', ''), + 'target_money' => number_format((float) bcadd((string) $u->money, (string) $u->commission, 2), 2, '.', ''), + ]; + } + + return [ + 'total_users' => $totalCount, + 'total_commission' => number_format($totalCommission, 2, '.', ''), + 'samples' => $samples, + ]; + } + + /** + * 执行全量结转:将所有会员的 commission 余额转入 money 钱包 + * + * @param bool $isDryRun 是否只读模拟 + * @param callable|null $progressCallback 进度回调 + * @param array $operator 操作人上下文 + * @return array + */ + public function transferAll($isDryRun = false, $progressCallback = null, $operator = []) + { + if ($isDryRun) { + $preview = $this->preview(); + return [ + 'status' => 'dry_run', + 'dry_run' => true, + 'total_users' => $preview['total_users'], + 'transferred_amount' => $preview['total_commission'], + 'success_count' => 0, + 'fail_count' => 0, + 'errors' => [], + 'preview' => $preview, + ]; + } + + $userIds = UserModel::where('commission', '>', 0)->order('id', 'asc')->column('id'); + $totalUsers = count($userIds); + $successCount = 0; + $failCount = 0; + $transferredAmount = '0.00'; + $errors = []; + + foreach ($userIds as $idx => $userId) { + try { + $transferResult = Db::transaction(function () use ($userId) { + $user = UserModel::where('id', $userId)->lock(true)->find(); + if (!$user) { + return 0.0; + } + + $amount = floatval($user->commission); + if ($amount <= 0) { + return 0.0; + } + + // 1. 扣减佣金钱包 + Wallet::change($user, 'commission', -$amount, 'transfer_to_money', [ + 'batch_migration' => 1, + 'reason' => '全量历史收益结转至商城余额', + ], '收益结转至商城余额'); + + // 2. 增加商城余额 + Wallet::change($user, 'money', $amount, 'transfer_by_commission', [ + 'batch_migration' => 1, + 'reason' => '全量历史收益结转至商城余额', + ], '收益结转至商城余额'); + + return $amount; + }); + + if ($transferResult > 0) { + $successCount++; + $transferredAmount = bcadd($transferredAmount, (string) $transferResult, 2); + } + + if (is_callable($progressCallback)) { + $progressCallback($idx + 1, $totalUsers, $userId, $transferResult, null); + } + } catch (\Throwable $e) { + $failCount++; + $errItem = [ + 'user_id' => $userId, + 'error' => $e->getMessage(), + ]; + $errors[] = $errItem; + + if (is_callable($progressCallback)) { + $progressCallback($idx + 1, $totalUsers, $userId, 0.0, $e->getMessage()); + } + } + } + + return [ + 'status' => 'completed', + 'dry_run' => false, + 'total_users' => $totalUsers, + 'success_count' => $successCount, + 'fail_count' => $failCount, + 'transferred_amount' => number_format((float) $transferredAmount, 2, '.', ''), + 'errors' => $errors, + ]; + } +} diff --git a/addons/shopro/upgrade/partner_v2.sql b/addons/shopro/upgrade/partner_v2.sql index e1204cc..e3dbc57 100644 --- a/addons/shopro/upgrade/partner_v2.sql +++ b/addons/shopro/upgrade/partner_v2.sql @@ -246,6 +246,7 @@ INSERT INTO `__PREFIX__shopro_config` (`code`, `parent_code`, `name`, `descripti ('shop.commission.partner_chain_max_depth', 'shop.commission', '关系链最大追溯层级', '向上寻找创客/酒道馆/区县时的最大关系链深度', 'int', '50', NULL, NULL, 84), ('shop.commission.partner_push3_amounts', 'shop.commission', '推三返一循环金额配置', '品酒师推荐直推合格单获得的阶梯奖励金额循环数组', 'array', '[398,400,600]', NULL, NULL, 85), ('shop.commission.partner_madow_mode', 'shop.commission', '每日GMV分润模式', 'madow=以马豆形式发放 cash=直接以佣金现金发放', 'string', 'madow', NULL, NULL, 86), +('shop.commission.partner_show_madow_rate', 'shop.commission', '显示马币市值胶囊', '控制移动端我的马豆页面是否显示马币市值胶囊 1=显示 0=隐藏', 'int', '1', NULL, NULL, 86), ('shop.commission.partner_madow_base_rate', 'shop.commission', '马豆基准汇率', '大盘初始起步汇率 (元/马豆)', 'string', '0.00000075', NULL, NULL, 87), ('shop.commission.partner_madow_step_orders', 'shop.commission', '马豆汇率步进单数', '每达成指定合格订单数,马豆大盘汇率递增1个基数', 'int', '1', NULL, NULL, 88), ('shop.commission.partner_voucher_times', 'shop.commission', '买酒赠送兑换券倍数', '符合条件的订单实付金额倍数,默认1.5倍', 'float', '1.5', NULL, NULL, 89), diff --git a/application/admin/controller/shopro/Config.php b/application/admin/controller/shopro/Config.php index 97351e8..8415e02 100644 --- a/application/admin/controller/shopro/Config.php +++ b/application/admin/controller/shopro/Config.php @@ -194,6 +194,7 @@ class Config extends Common $keys = [ 'partner_mode', 'partner_enable', 'partner_count_type', 'partner_active_days', 'partner_chain_max_depth', 'partner_push3_amounts', 'partner_madow_mode', + 'partner_show_madow_rate', 'partner_madow_base_rate', 'partner_madow_step_orders', 'partner_voucher_times', 'partner_voucher_days', 'partner_maker_bonus', 'partner_maker_peer_rate', 'partner_maker_overtaken_rate', 'partner_team_total', 'partner_venue_split', @@ -216,6 +217,7 @@ class Config extends Common 'partner_chain_max_depth' => 50, 'partner_push3_amounts' => [398, 400, 600], 'partner_madow_mode' => 'madow', + 'partner_show_madow_rate' => 1, 'partner_madow_base_rate' => '0.00000075', 'partner_madow_step_orders' => 1, 'partner_voucher_times' => 1.5, @@ -241,6 +243,7 @@ class Config extends Common $configs[$key] = $value; } } + $configs['partner_show_madow_rate'] = isset($configs['partner_show_madow_rate']) ? intval($configs['partner_show_madow_rate']) : 1; } elseif ('POST' === $this->request->method()) { check_env('commission'); $params = $this->request->param(); @@ -250,6 +253,22 @@ class Config extends Common $save[$key] = $params[$key]; } } + if (isset($save['partner_show_madow_rate'])) { + $save['partner_show_madow_rate'] = intval($save['partner_show_madow_rate']); + } + // 确保数据库存在 partner_show_madow_rate 配置行 + $existConfig = ShoproConfig::where('code', 'shop.commission.partner_show_madow_rate')->find(); + if (!$existConfig) { + ShoproConfig::create([ + 'code' => 'shop.commission.partner_show_madow_rate', + 'parent_code' => 'shop.commission', + 'name' => '显示马币市值', + 'description' => '1=显示 0=隐藏', + 'type' => 'int', + 'value' => '1', + 'weigh' => 86 + ]); + } foreach (['partner_role_name_taster', 'partner_role_name_maker', 'partner_role_name_venue', 'partner_role_name_county', 'partner_role_name_partner'] as $roleKey) { if (isset($save[$roleKey])) { $save[$roleKey] = trim((string) $save[$roleKey]); diff --git a/application/admin/controller/shopro/Dashboard.php b/application/admin/controller/shopro/Dashboard.php index ff7234e..93162cf 100644 --- a/application/admin/controller/shopro/Dashboard.php +++ b/application/admin/controller/shopro/Dashboard.php @@ -749,7 +749,7 @@ class Dashboard extends Common $structureMap = [ 'push3' => [ 'key' => 'push3', - 'name' => '推荐开拓奖励', + 'name' => '推荐提成', 'types' => ['push3'], 'amount' => 0.00, 'count' => 0, @@ -765,7 +765,7 @@ class Dashboard extends Common ], 'channel' => [ 'key' => 'channel', - 'name' => '实体统筹协同奖励', + 'name' => '渠道管理与平级奖', 'types' => ['team_venue', 'team_county', 'venue_peer', 'county_peer'], 'amount' => 0.00, 'count' => 0, @@ -773,7 +773,7 @@ class Dashboard extends Common ], 'share' => [ 'key' => 'share', - 'name' => '全网加权贡献奖励', + 'name' => '全网加权分红奖', 'types' => ['venue_share', 'county_share'], 'amount' => 0.00, 'count' => 0, diff --git a/application/admin/controller/shopro/commission/Agent.php b/application/admin/controller/shopro/commission/Agent.php index a6bbf0d..ca412a7 100644 --- a/application/admin/controller/shopro/commission/Agent.php +++ b/application/admin/controller/shopro/commission/Agent.php @@ -93,10 +93,14 @@ class Agent extends Common ->where('reward_type', '<>', 'push3') ->sum('amount'); - $detail->income_total_cash = number_format((float) $totalCash, 2, '.', ''); + $alignedIncome = number_format(max(floatval($detail->total_income ?? 0), (float) $totalCash), 2, '.', ''); + $detail->income_total_cash = $alignedIncome; + $detail->total_income = $alignedIncome; + $detail->partner_cash_income = $alignedIncome; $detail->push3_cash_income = number_format((float) $push3Cash, 2, '.', ''); $detail->partner_exclusive_cash = number_format((float) $partnerExclusiveCash, 2, '.', ''); - $detail->partner_cash_income = $detail->income_total_cash; + $detail->user_money = number_format(floatval($detail->user ? $detail->user->money : 0), 2, '.', ''); + $detail->user_commission = number_format(floatval($detail->user ? $detail->user->commission : 0), 2, '.', ''); $detail->partner_mode = \addons\shopro\service\partner\PartnerConfig::isPartnerMode() ? 1 : 0; $rate = (new \addons\shopro\service\partner\RateService())->currentRate(); $detail->madow_value = \addons\shopro\service\partner\IdentityService::formatMoney((float) ($detail->madow ?? 0) * (float) $rate); diff --git a/application/admin/controller/shopro/commission/Reward.php b/application/admin/controller/shopro/commission/Reward.php index 4429962..16eebbd 100644 --- a/application/admin/controller/shopro/commission/Reward.php +++ b/application/admin/controller/shopro/commission/Reward.php @@ -58,7 +58,7 @@ class Reward extends Common $export = new \addons\shopro\library\Export(); $params = [ - 'file_name' => '佣金明细列表', + 'file_name' => '历史分销佣金列表', 'cell_titles' => $cellTitles, 'total' => $total, 'is_sub_cell' => false, diff --git a/application/admin/controller/shopro/order/Order.php b/application/admin/controller/shopro/order/Order.php index 04e9dbf..d756a51 100644 --- a/application/admin/controller/shopro/order/Order.php +++ b/application/admin/controller/shopro/order/Order.php @@ -242,7 +242,7 @@ class Order extends Common // 友好展示金额 if ($type === 'voucher_grant') { - $amountText = number_format($baseAmount, 2, '.', '') . ' 兑换券'; + $amountText = number_format($baseAmount, 2, '.', '') . ' 个'; } elseif ($madow > 0) { $amountText = \addons\shopro\service\partner\IdentityService::formatMadow($madow) . ' 马豆'; } elseif ($amount > 0) { @@ -270,6 +270,8 @@ class Order extends Common )), 'formula' => (string) ($row->formula ?? ''), 'skip_reason_text' => (string) ($row->skip_reason_text ?? ''), + 'wallet_type' => (string) ($row->wallet_type ?? ($amount > 0 ? \addons\shopro\service\partner\PartnerConfig::rewardWalletType() : 'system')), + 'wallet_type_text' => (string) ($row->wallet_type_text ?: ($amount > 0 ? (\addons\shopro\service\partner\PartnerConfig::rewardWalletType() === 'money' ? '商城余额' : '佣金钱包') : '平台留存')), ]; } @@ -299,6 +301,8 @@ class Order extends Common 'base_amount' => '0.00', 'amount_text' => '¥' . number_format($cCommission, 2, '.', ''), 'madow' => '0.00000000', + 'wallet_type' => 'commission', + 'wallet_type_text' => '佣金钱包', 'status' => $cStatus, 'status_text' => (string) ($crow->status_text ?: ($cStatus === 1 ? '已入账' : '待入账')), 'formula' => '分销规则佣金', @@ -393,6 +397,8 @@ class Order extends Common 'total_amount' => number_format($platAmount, 2, '.', ''), 'total_madow' => \addons\shopro\service\partner\IdentityService::formatMadow($platMadow), 'amount_text' => $amountText, + 'wallet_type' => 'system', + 'wallet_type_text' => '平台留存', 'settle_date' => $settleDate, 'status' => 1, 'status_text' => '已入账', @@ -424,6 +430,8 @@ class Order extends Common 'total_amount' => number_format($totalAmount, 2, '.', ''), 'total_madow' => \addons\shopro\service\partner\IdentityService::formatMadow($totalMadow), 'amount_text' => $type === 'madow_share' ? ($eachMadow . ' 马豆/人') : ('¥' . $eachAmount . '/人'), + 'wallet_type' => $type === 'madow_share' ? 'madow' : \addons\shopro\service\partner\PartnerConfig::rewardWalletType(), + 'wallet_type_text' => $type === 'madow_share' ? '分红马豆' : (\addons\shopro\service\partner\PartnerConfig::rewardWalletType() === 'money' ? '商城余额' : '佣金钱包'), 'settle_date' => $settleDate, 'status' => 1, 'status_text' => '已入账', diff --git a/application/admin/controller/shopro/partner/Reward.php b/application/admin/controller/shopro/partner/Reward.php index 6fc88b0..ff94e24 100644 --- a/application/admin/controller/shopro/partner/Reward.php +++ b/application/admin/controller/shopro/partner/Reward.php @@ -50,6 +50,7 @@ class Reward extends Common 'reward_type_text' => '奖励类型', 'role_text' => '受益身份', 'amount' => '结算金额', + 'wallet_type_text' => '入账账户', 'madow' => '马豆', 'base_amount' => '基数', 'status_text' => '状态', @@ -83,6 +84,7 @@ class Reward extends Common 'reward_type_text' => $reward['reward_type_text'], 'role_text' => $reward['role_text'] ?? $reward['role'], 'amount' => $reward['amount'], + 'wallet_type_text' => $reward['wallet_type_text'] ?? '商城余额', 'madow' => \addons\shopro\service\partner\IdentityService::formatMadow($reward['madow']), 'base_amount' => $reward['base_amount'], 'status_text' => $reward['status_text'], @@ -102,6 +104,7 @@ class Reward extends Common 'reward_type_text' => '-', 'role_text' => '-', 'amount' => number_format($totalAmount, 2, '.', ''), + 'wallet_type_text' => '现金直入余额', 'madow' => $formattedTotalMadow, 'base_amount' => '-', 'status_text' => '已汇总', @@ -138,7 +141,8 @@ class Reward extends Common 'ext' => ['formula' => '手工补发', 'memo' => $params['memo'], 'manual' => 1], 'status' => 1, ]); - Wallet::change($params['user_id'], 'commission', $amount, 'reward_income', [ + $walletType = PartnerConfig::rewardWalletType(); + Wallet::change($params['user_id'], $walletType, $amount, 'reward_income', [ 'partner_reward_id' => $row->id ?? 0, 'manual' => 1, ], $params['memo']); @@ -162,7 +166,12 @@ class Reward extends Common } Db::transaction(function () use ($row, $memo) { if ((float) $row->amount > 0 && $row->user_id > 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', '%"partner_reward_id":' . intval($row->id) . '%') + ->order('id', 'desc') + ->find(); + $targetWallet = ($walletLog && $walletLog->type === 'commission') ? 'commission' : PartnerConfig::rewardWalletType(); + Wallet::change($row->user_id, $targetWallet, -$row->amount, 'reward_back', [ 'partner_reward_id' => $row->id, ], $memo); } @@ -217,6 +226,8 @@ class Reward extends Common 'user_nickname' => $r->user ? ($r->user->nickname ?: $r->user->mobile) : '平台/买家', 'role_text' => $r->role_text ?: $r->role, 'amount' => $r->amount, + 'wallet_type' => $r->wallet_type, + 'wallet_type_text' => $r->wallet_type_text, 'madow' => $r->madow, 'base_amount' => $r->base_amount, 'status' => $r->status, @@ -259,4 +270,30 @@ class Reward extends Common ] : null, ]); } + + /** + * 全量结转收益预览试算 + */ + public function transferAllCommissionPreview() + { + $service = new \addons\shopro\service\partner\WalletMigrationService(); + $preview = $service->preview(); + $this->success('获取成功', null, $preview); + } + + /** + * 执行全量结转收益至商城余额 + */ + public function transferAllCommission() + { + $isDryRun = (bool) $this->request->param('dry_run', false); + $confirm = (int) $this->request->param('confirm', 0); + if (!$isDryRun && $confirm !== 1) { + $this->error('操作属于高风险批量资金转移,请传递 confirm=1 确认执行'); + } + + $service = new \addons\shopro\service\partner\WalletMigrationService(); + $result = $service->transferAll($isDryRun); + $this->success($isDryRun ? '试算成功' : '结转完成', null, $result); + } } diff --git a/application/admin/controller/shopro/user/User.php b/application/admin/controller/shopro/user/User.php index 2274d7d..6fba0c7 100644 --- a/application/admin/controller/shopro/user/User.php +++ b/application/admin/controller/shopro/user/User.php @@ -142,11 +142,14 @@ class User extends Common : ('已暂停领取' . ($ago !== null ? '(距今 ' . $ago . ' 天)' : '(从未下单)')); } - // 累计现金收益统计 + // 累计现金收益统计(与 IdentityService 严格对齐,兼顾历史分销与收益中心) + $agentTotalIncome = floatval($agent->total_income ?? 0); $totalCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id) ->where('status', 1) ->where('amount', '>', 0) ->sum('amount'); + $alignedTotalIncome = max($agentTotalIncome, (float) $totalCash); + $push3Cash = \app\admin\model\shopro\partner\Reward::where('user_id', $id) ->where('status', 1) ->where('amount', '>', 0) @@ -158,9 +161,12 @@ class User extends Common ->where('reward_type', '<>', 'push3') ->sum('amount'); - $data['income_total_cash'] = number_format((float) $totalCash, 2, '.', ''); + $data['income_total_cash'] = number_format($alignedTotalIncome, 2, '.', ''); + $data['total_income'] = number_format($alignedTotalIncome, 2, '.', ''); $data['push3_cash_income'] = number_format((float) $push3Cash, 2, '.', ''); $data['partner_exclusive_cash'] = number_format((float) $partnerExclusiveCash, 2, '.', ''); + $data['money'] = number_format(floatval($user->money ?? 0), 2, '.', ''); + $data['commission'] = number_format(floatval($user->commission ?? 0), 2, '.', ''); // 兑换券计划资产统计 $voucherTotal = floatval($agent->voucher_total ?? 0); diff --git a/application/admin/controller/shopro/user/WalletLog.php b/application/admin/controller/shopro/user/WalletLog.php index ece320c..932082b 100644 --- a/application/admin/controller/shopro/user/WalletLog.php +++ b/application/admin/controller/shopro/user/WalletLog.php @@ -65,12 +65,26 @@ class WalletLog extends Common } /** - * 佣金记录 + * 佣金与收益记录 */ public function commission($id) { $list_rows = $this->request->param('list_rows', 10); - $walletLogs = WalletLogModel::where('user_id', $id)->commission()->order('id', 'desc')->paginate($list_rows); + $rewardEvents = [ + 'push3', 'maker_diff', 'maker_peer', 'maker_overtaken', + 'team_venue', 'team_county', 'venue_peer', 'county_peer', + 'venue_share', 'county_share', 'reward_income', 'transfer_by_commission', 'transfer_to_money' + ]; + + $walletLogs = WalletLogModel::where('user_id', $id) + ->where(function ($query) use ($rewardEvents) { + $query->where('type', 'commission') + ->whereOr(function ($sub) use ($rewardEvents) { + $sub->where('type', 'money')->whereIn('event', $rewardEvents); + }); + }) + ->order('id', 'desc') + ->paginate($list_rows); // 多态关联 oper $morphs = [ diff --git a/application/admin/model/shopro/app/ScoreSkuPrice.php b/application/admin/model/shopro/app/ScoreSkuPrice.php index 5ab1697..fb7b452 100644 --- a/application/admin/model/shopro/app/ScoreSkuPrice.php +++ b/application/admin/model/shopro/app/ScoreSkuPrice.php @@ -55,11 +55,11 @@ class ScoreSkuPrice extends Common $priceNum = floatval($price); if ($priceNum > 0 && $score > 0) { - return '¥' . $price . '兑换券 + ' . $score . '积分'; + return $price . ' 个兑换券 + ' . $score . ' 积分'; } elseif ($priceNum > 0) { - return '¥' . $price . ' 兑换券'; + return $price . ' 个兑换券'; } elseif ($score > 0) { - return $score . '积分'; + return $score . ' 积分'; } return '免费兑换'; diff --git a/application/admin/model/shopro/partner/Reward.php b/application/admin/model/shopro/partner/Reward.php index ebd4430..4aedc81 100644 --- a/application/admin/model/shopro/partner/Reward.php +++ b/application/admin/model/shopro/partner/Reward.php @@ -22,6 +22,8 @@ class Reward extends Common 'skip_reason', 'skip_reason_text', 'formula', + 'wallet_type', + 'wallet_type_text', ]; public function statusList() @@ -91,6 +93,41 @@ class Reward extends Common return \addons\shopro\service\partner\IdentityService::formatMadow($value); } + public function getWalletTypeAttr($value, $data) + { + $ext = $data['ext'] ?? []; + if (is_string($ext)) { + $ext = json_decode($ext, true) ?: []; + } + if (!empty($ext['wallet_type'])) { + return $ext['wallet_type']; + } + $rewardType = $data['reward_type'] ?? ''; + if (in_array($rewardType, ['voucher_grant', 'voucher_release'], true)) { + return 'voucher'; + } + if ($rewardType === 'madow_share') { + return 'madow'; + } + if (intval($data['user_id'] ?? 0) === 0) { + return 'system'; + } + return PartnerConfig::rewardWalletType(); + } + + public function getWalletTypeTextAttr($value, $data) + { + $type = $this->getWalletTypeAttr(null, $data); + $map = [ + 'money' => '商城余额', + 'commission' => '佣金钱包', + 'voucher' => '消费补贴券', + 'madow' => '分红马豆', + 'system' => '平台留存', + ]; + return $map[$type] ?? '商城余额'; + } + public function buyer() { return $this->belongsTo(UserModel::class, 'buyer_id', 'id')->field('id, nickname, avatar, mobile'); diff --git a/application/admin/model/shopro/user/WalletLog.php b/application/admin/model/shopro/user/WalletLog.php index 9823a5e..b2d58ab 100644 --- a/application/admin/model/shopro/user/WalletLog.php +++ b/application/admin/model/shopro/user/WalletLog.php @@ -45,16 +45,16 @@ class WalletLog extends Common 'transfer_by_commission' => '服务收益-转入到余额', 'reward_income' => '收益分成-入账', 'reward_back' => '收益分成-退还', - 'push3' => '推荐开拓奖', + 'push3' => '推荐提成', 'maker_diff' => '创客进阶奖', 'maker_peer' => '创客协同奖', 'maker_overtaken' => '创客卓越奖', - 'team_venue' => '酒道馆运营奖', - 'team_county' => '区县统筹奖', - 'venue_peer' => '酒道馆协同奖', - 'county_peer' => '区县协同奖', - 'venue_share' => '酒道馆加权奖励', - 'county_share' => '区县加权奖励' + 'team_venue' => '酒道馆管理奖', + 'team_county' => '区县管理奖', + 'venue_peer' => '酒道馆平级奖', + 'county_peer' => '区县代平级奖', + 'venue_share' => '酒道馆加权奖', + 'county_share' => '区县加权奖' ], 'commission' => [ 'withdraw' => '提现', @@ -62,16 +62,16 @@ class WalletLog extends Common 'reward_income' => '服务收益-入账', 'reward_back' => '服务收益-退还', 'transfer_to_money' => '服务收益-转出到余额', - 'push3' => '推荐开拓奖', + 'push3' => '推荐提成', 'maker_diff' => '创客进阶奖', 'maker_peer' => '创客协同奖', 'maker_overtaken' => '创客卓越奖', - 'team_venue' => '酒道馆运营奖', - 'team_county' => '区县统筹奖', - 'venue_peer' => '酒道馆协同奖', - 'county_peer' => '区县协同奖', - 'venue_share' => '酒道馆加权奖励', - 'county_share' => '区县加权奖励' + 'team_venue' => '酒道馆管理奖', + 'team_county' => '区县管理奖', + 'venue_peer' => '酒道馆平级奖', + 'county_peer' => '区县代平级奖', + 'venue_share' => '酒道馆加权奖', + 'county_share' => '区县加权奖' ] ]; @@ -111,6 +111,6 @@ class WalletLog extends Common $type = $data['type'] ?? ''; $event = $data['event'] ?? ''; $eventMap = $this->getEventMap(); - return $eventMap[$type][$event] ?? ($eventMap['commission'][$event] ?? ''); + return $eventMap[$type][$event] ?? ($eventMap['money'][$event] ?? ($eventMap['commission'][$event] ?? ($event ?: ''))); } } diff --git a/application/admin/view/shopro/app/score_shop/add.html b/application/admin/view/shopro/app/score_shop/add.html index 96457c8..0ec3c69 100644 --- a/application/admin/view/shopro/app/score_shop/add.html +++ b/application/admin/view/shopro/app/score_shop/add.html @@ -62,7 +62,7 @@ type="info" :closable="false" class="mb-4" - description="1. 纯兑换券兑换:【所需兑换券】填入所需面额(如 100),【所需积分】填 0。用户在小程序下单时将自动使用可用兑换券 1:1 全额抵扣(实付 0 元)。 2. 兑换券+积分组合:同时填写对应所需兑换券额度与所需积分。 3. 若用户可用兑换券不足,系统自动支持抵扣部分可用券额,剩余差额可使用微信现金支付补齐。" + description="1. 纯兑换券兑换:【所需兑换券】填入所需数量(如 100 个),【所需积分】填 0。用户在小程序下单时将自动使用可用兑换券 1:1 全额抵扣(实付 0 元)。 2. 兑换券+积分组合:同时填写对应所需兑换券数量与所需积分。 3. 若用户可用兑换券不足,系统自动支持抵扣部分可用券额,剩余差额可使用微信现金支付补齐。" show-icon>
@@ -73,7 +73,7 @@ 是否参与 商品原价 可兑换数量 - 所需兑换券(元) + 所需兑换券(个) 所需积分 diff --git a/application/admin/view/shopro/app/score_shop/index.html b/application/admin/view/shopro/app/score_shop/index.html index 72d1e7b..67df600 100644 --- a/application/admin/view/shopro/app/score_shop/index.html +++ b/application/admin/view/shopro/app/score_shop/index.html @@ -125,7 +125,7 @@ @@ -382,9 +382,10 @@
- {{ state.data.income_total_cash || state.data.partner_cash_income || '0.00' }} + {{ state.data.total_income || state.data.income_total_cash || '0.00' }} 元 + 直入商城余额 资产信息 - -
累计佣金:
+ +
累计收益/佣金:
{{ state.data.total_income }} 元
- -
待入账佣金:
-
- {{ state.data.pending_reward }} + +
商城可用余额:
+
+ {{ state.data.user?.money || state.data.user_money || '0.00' }} 元
- + +
历史未转佣金:
+
+ {{ state.data.user?.commission || state.data.user_commission || '0.00' }} + 元 +
+
+
消费金额:
{{ state.data.user?.total_consume || 0 }} @@ -646,7 +654,7 @@ - + diff --git a/application/admin/view/shopro/commission/agent/index.html b/application/admin/view/shopro/commission/agent/index.html index 0558895..4bafbc7 100644 --- a/application/admin/view/shopro/commission/agent/index.html +++ b/application/admin/view/shopro/commission/agent/index.html @@ -87,7 +87,7 @@ - + diff --git a/application/admin/view/shopro/commission/goods/edit.html b/application/admin/view/shopro/commission/goods/edit.html index 4f7729e..2702151 100644 --- a/application/admin/view/shopro/commission/goods/edit.html +++ b/application/admin/view/shopro/commission/goods/edit.html @@ -227,7 +227,7 @@ @@ -239,7 +239,7 @@
- 推荐开拓奖(推三返一): + 推荐提成(推三返一): 循环返现 ¥{{ state.defaultPartnerRules.push3_amounts?.[0] || 398 }} / ¥{{ state.defaultPartnerRules.push3_amounts?.[1] || 400 }} / ¥{{ state.defaultPartnerRules.push3_amounts?.[2] || 600 }} 元
@@ -251,11 +251,11 @@ 同级协同 {{ ((state.defaultPartnerRules.maker_peer_rate || 0.10) * 100).toFixed(0) }}%,超越关怀 {{ ((state.defaultPartnerRules.maker_overtaken_rate || 0.05) * 100).toFixed(0) }}%
- 渠道运营统筹奖: - 酒道馆 ¥{{ state.defaultPartnerRules.venue_split || 50 }},区县统筹 ¥{{ state.defaultPartnerRules.county_full || 100 }}(有馆时各50) + 实体渠道管理奖: + 酒道馆 ¥{{ state.defaultPartnerRules.venue_split || 50 }},区县管理 ¥{{ state.defaultPartnerRules.county_full || 100 }}(有馆时各50)
- 馆/县平级协同奖: + 馆/县平级奖: 酒道馆 {{ ((state.defaultPartnerRules.venue_peer_rate || 0.10) * 100).toFixed(0) }}%,区县代理 {{ ((state.defaultPartnerRules.county_peer_rate || 0.10) * 100).toFixed(0) }}%
@@ -300,10 +300,10 @@ + + + + + + - - + + + + @@ -651,6 +662,13 @@ + + + @@ -172,6 +186,10 @@ name: '全部', value: 'all', }, + { + name: '收益分成', + value: 'reward', + }, { name: '收入', value: 'income', diff --git a/uniapp/pages/user/wallet/money.vue b/uniapp/pages/user/wallet/money.vue index 27e47ca..4f4ff31 100644 --- a/uniapp/pages/user/wallet/money.vue +++ b/uniapp/pages/user/wallet/money.vue @@ -41,7 +41,7 @@ - 线上充值与消费余额 · 下单直接抵扣 + 线上充值与收益分成全额入账 · 下单直接抵扣 即时可用 @@ -185,6 +185,10 @@ name: '全部', value: 'all', }, + { + name: '收益分成', + value: 'reward', + }, { name: '收入', value: 'income', diff --git a/uniapp/sheep/components/s-wallet-card/s-wallet-card.vue b/uniapp/sheep/components/s-wallet-card/s-wallet-card.vue index 29b718a..7f2f183 100644 --- a/uniapp/sheep/components/s-wallet-card/s-wallet-card.vue +++ b/uniapp/sheep/components/s-wallet-card/s-wallet-card.vue @@ -18,8 +18,7 @@ {{ displayMadow }} - 约¥{{ userInfo?.madow_value || '0.00' }} - 马豆 + 马豆 {{ userInfo?.voucher_available || '0.00' }} - 元 + 个 兑换券 diff --git a/uniapp/sheep/platform/index.js b/uniapp/sheep/platform/index.js index 2fa2fb6..366939e 100644 --- a/uniapp/sheep/platform/index.js +++ b/uniapp/sheep/platform/index.js @@ -75,8 +75,8 @@ const useProvider = (_provider = '') => { }; // 支付服务转发 -const pay = (payment, orderType, orderSN) => { - return new Pay(payment, orderType, orderSN); +const pay = (payment, orderType, orderSN, options = {}) => { + return new Pay(payment, orderType, orderSN, options); }; /** diff --git a/uniapp/sheep/platform/pay.js b/uniapp/sheep/platform/pay.js index b97d117..7b82401 100644 --- a/uniapp/sheep/platform/pay.js +++ b/uniapp/sheep/platform/pay.js @@ -15,10 +15,11 @@ import { */ export default class SheepPay { - constructor(payment, orderType, orderSN) { + constructor(payment, orderType, orderSN, options = {}) { this.payment = payment; this.orderSN = orderSN; this.orderType = orderType; + this.options = options || {}; this.payAction(); } @@ -91,22 +92,31 @@ export default class SheepPay { order_sn: this.orderSN, payment: this.payment, }; + if (this.options && Number(this.options.money) > 0) { + data.money = Number(this.options.money); + } if (uni.getStorageSync('openid')) { data.openid = uni.getStorageSync('openid'); } sheep.$api.pay.prepay(data).then((res) => { - res.code === 1 && resolve(res); - if (res.data === -1 && res.msg === 'miss_openid') { - uni.showModal({ - title: '微信支付', - content: '请先绑定微信再使用微信支付', - success: function (res) { - if (res.confirm) { - sheep.$platform.useProvider('wechat').bind(); - } - }, - }); + if (res && res.code === 1) { + resolve(res); + } else { + if (res && res.data === -1 && res.msg === 'miss_openid') { + uni.showModal({ + title: '微信支付', + content: '请先绑定微信再使用微信支付', + success: function (res) { + if (res.confirm) { + sheep.$platform.useProvider('wechat').bind(); + } + }, + }); + } + reject(res); } + }).catch((err) => { + reject(err); }); }); } @@ -114,51 +124,75 @@ export default class SheepPay { // 微信公众号JSSDK支付 async wechatOfficialAccountPay() { let that = this; - let { - code, - data, - msg - } = await this.prepay(); - if (code !== 1) { - console.log('支付错误', msg); - return; - } - $wxsdk.wxpay(data.pay_data, { - success: () => { + try { + let { + code, + data, + msg + } = await this.prepay(); + if (code !== 1) { + console.log('支付错误', msg); + return; + } + if (!data?.pay_data) { that.payResult('success'); - }, - cancel: () => { - sheep.$helper.toast('支付已手动取消'); - }, - fail: () => { - that.payResult('fail'); - }, - }); + return; + } + $wxsdk.wxpay(data.pay_data, { + success: () => { + that.payResult('success'); + }, + cancel: () => { + sheep.$helper.toast('支付已手动取消'); + }, + fail: () => { + that.payResult('fail'); + }, + }); + } catch (e) { + console.log('预支付未完成:', e); + } } //浏览器微信H5支付 async wechatWapPay() { - const { - code, - data - } = await this.prepay(); - if (code === 1) { - const redirect_url = `${getRootUrl()}pages/pay/result?orderSN=${this.orderSN}&payment=${this.payment - }&orderType=${this.orderType}`; - location.href = `${data.pay_data.h5_url}&redirect_url=${encodeURIComponent(redirect_url)}`; + try { + const { + code, + data + } = await this.prepay(); + if (code === 1) { + if (!data?.pay_data) { + this.payResult('success'); + return; + } + const redirect_url = `${getRootUrl()}pages/pay/result?orderSN=${this.orderSN}&payment=${this.payment + }&orderType=${this.orderType}`; + location.href = `${data.pay_data.h5_url}&redirect_url=${encodeURIComponent(redirect_url)}`; + } + } catch (e) { + console.log('预支付未完成:', e); } } // 支付链接 async redirectPay() { - let { - code, - data - } = await this.prepay(); - if (code === 1) { - const redirect_url = `${getRootUrl()}pages/pay/result?orderSN=${this.orderSN}&payment=${this.payment - }&orderType=${this.orderType}`; - location.href = data.pay_data + encodeURIComponent(redirect_url); + try { + let { + code, + data + } = await this.prepay(); + if (code === 1) { + if (!data?.pay_data) { + this.payResult('success'); + return; + } + const redirect_url = `${getRootUrl()}pages/pay/result?orderSN=${this.orderSN}&payment=${this.payment + }&orderType=${this.orderType}`; + location.href = data.pay_data + encodeURIComponent(redirect_url); + } + } catch (e) { + console.log('预支付未完成:', e); } } @@ -167,104 +201,145 @@ export default class SheepPay { // 微信小程序支付 async wechatMiniProgramPay() { let that = this; - let result = await this.prepay(); - uni.requestPayment({ - provider: 'wxpay', - ...result.data.pay_data, - success: (res) => { + try { + let result = await this.prepay(); + if (result.code !== 1) return; + if (!result.data?.pay_data) { that.payResult('success'); - }, - fail: (err) => { - if (err.errMsg === 'requestPayment:fail cancel') { - sheep.$helper.toast('支付已手动取消'); - } else { - that.payResult('fail'); - } - }, - }); - } - - // 余额支付 - async moneyPay() { - const { - code - } = await this.prepay(); - code === 1 && this.payResult('success'); - } - - // 货到付款 - async offlinePay() { - const { - code - } = await this.prepay(); - code === 1 && this.payResult('success'); - } - - // 支付宝复制链接支付 - async copyPayLink() { - let that = this; - let { - code, - data - } = await this.prepay(); - if (code === 1) { - // 引入showModal 点击确认 复制链接; - uni.showModal({ - title: '支付宝支付', - content: '复制链接到外部浏览器', - confirmText: '复制链接', - success: (res) => { - if (res.confirm) { - sheep.$helper.copyText(data.pay_data); - } - }, - }); - } - } - - // 支付宝支付 - async alipay() { - let that = this; - const { - code, - data - } = await this.prepay(); - if (code === 1) { + return; + } uni.requestPayment({ - provider: 'alipay', - orderInfo: data.pay_data, //支付宝订单数据 + provider: 'wxpay', + ...result.data.pay_data, success: (res) => { that.payResult('success'); }, fail: (err) => { - if (err.errMsg === 'requestPayment:fail [paymentAlipay:62001]user cancel') { + if (err.errMsg === 'requestPayment:fail cancel') { sheep.$helper.toast('支付已手动取消'); } else { that.payResult('fail'); } }, }); + } catch (e) { + console.log('预支付未完成:', e); + } + } + + // 余额支付 + async moneyPay() { + try { + const { + code + } = await this.prepay(); + code === 1 && this.payResult('success'); + } catch (e) { + console.log('余额支付未完成:', e); + } + } + + // 货到付款 + async offlinePay() { + try { + const { + code + } = await this.prepay(); + code === 1 && this.payResult('success'); + } catch (e) { + console.log('货到付款未完成:', e); + } + } + + // 支付宝复制链接支付 + async copyPayLink() { + let that = this; + try { + let { + code, + data + } = await this.prepay(); + if (code === 1) { + if (!data?.pay_data) { + that.payResult('success'); + return; + } + // 引入showModal 点击确认 复制链接; + uni.showModal({ + title: '支付宝支付', + content: '复制链接到外部浏览器', + confirmText: '复制链接', + success: (res) => { + if (res.confirm) { + sheep.$helper.copyText(data.pay_data); + } + }, + }); + } + } catch (e) { + console.log('复制支付链接未完成:', e); + } + } + + // 支付宝支付 + async alipay() { + let that = this; + try { + const { + code, + data + } = await this.prepay(); + if (code === 1) { + if (!data?.pay_data) { + that.payResult('success'); + return; + } + uni.requestPayment({ + provider: 'alipay', + orderInfo: data.pay_data, //支付宝订单数据 + success: (res) => { + that.payResult('success'); + }, + fail: (err) => { + if (err.errMsg === 'requestPayment:fail [paymentAlipay:62001]user cancel') { + sheep.$helper.toast('支付已手动取消'); + } else { + that.payResult('fail'); + } + }, + }); + } + } catch (e) { + console.log('支付宝预支付未完成:', e); } } // 微信支付 async wechatAppPay() { let that = this; - let { - code, - data - } = await this.prepay(); - if (code === 1) { - uni.requestPayment({ - provider: 'wxpay', - orderInfo: data.pay_data, //微信订单数据(官方说是string。实测为object) - success: (res) => { + try { + let { + code, + data + } = await this.prepay(); + if (code === 1) { + if (!data?.pay_data) { that.payResult('success'); - }, - fail: (err) => { - err.errMsg !== 'requestPayment:fail cancel' && that.payResult('fail'); - }, - }); + return; + } + uni.requestPayment({ + provider: 'wxpay', + orderInfo: data.pay_data, //微信订单数据(官方说是string。实测为object) + success: (res) => { + that.payResult('success'); + }, + fail: (err) => { + err.errMsg !== 'requestPayment:fail cancel' && that.payResult('fail'); + }, + }); + } + } catch (e) { + console.log('微信App预支付未完成:', e); } } diff --git a/uniapp/sheep/store/user.js b/uniapp/sheep/store/user.js index 3ad5810..08c5f2b 100644 --- a/uniapp/sheep/store/user.js +++ b/uniapp/sheep/store/user.js @@ -24,6 +24,7 @@ const defaultUserInfo = { madow: '', // 马豆数量 madow_rate: '', // 马豆汇率 madow_value: '', // 马豆约合 + show_madow_rate: 1, // 是否显示马豆市值 voucher_available: '', // 可用兑换券 active_30d: true, // 近 30 日是否有本人订单 agent_form: false,