feat(partner): 完善马豆实时动态汇率与精度自适应,支持交易数据清理与汇率重置
这个提交包含在:
@@ -111,6 +111,7 @@ class Shopro extends Addons
|
||||
'addons\shopro\console\PartnerInstall',
|
||||
'addons\shopro\console\PartnerDemo',
|
||||
'addons\shopro\console\PartnerAccept',
|
||||
'addons\shopro\console\PartnerClean',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\console;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\input\Option;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 清理订单数据、分销分润记录、日结快照日志,并将马豆汇率回归初始状态
|
||||
*/
|
||||
class PartnerClean extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('shopro:partner:clean')
|
||||
->addOption('force', 'f', Option::VALUE_NONE, '强制执行,不提示确认')
|
||||
->addOption('with-wallet-log', 'w', Option::VALUE_NONE, '同时清理分佣与订单相关的佣金钱包流水日志')
|
||||
->setDescription('清理所有订单、分销分润、日结日志,重置马豆与汇率回初始值');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$force = (bool) $input->getOption('force');
|
||||
$withWallet = (bool) $input->getOption('with-wallet-log');
|
||||
|
||||
$output->writeln("========================================================");
|
||||
$output->writeln(" 收益中心交易数据清理与马豆汇率重置程序");
|
||||
$output->writeln("========================================================");
|
||||
|
||||
if (!$force) {
|
||||
$output->writeln("【警告】此操作将清空所有商城订单、分销记录、分润流水并重置马豆汇率!");
|
||||
$output->writeln("如需执行,请增加参数 --force 或 -f,例如:");
|
||||
$output->writeln("php think shopro:partner:clean --force");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$prefix = config('database.prefix') ?: 'fa_';
|
||||
$output->writeln("当前数据库前缀: [{$prefix}]");
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 1. 清理商城订单及关联子表
|
||||
$orderTables = [
|
||||
'shopro_order',
|
||||
'shopro_order_item',
|
||||
'shopro_order_action',
|
||||
'shopro_order_aftersale',
|
||||
'shopro_order_aftersale_log',
|
||||
'shopro_order_express',
|
||||
'shopro_order_invoice',
|
||||
'shopro_order_item_stock',
|
||||
];
|
||||
|
||||
$output->writeln("\n>>> 1. 正在清理商城订单及交易明细...");
|
||||
foreach ($orderTables as $tbl) {
|
||||
$fullTable = $prefix . $tbl;
|
||||
if ($this->tableExists($fullTable)) {
|
||||
Db::execute("TRUNCATE TABLE `{$fullTable}`");
|
||||
$output->writeln(" [OK] 清空表 `{$fullTable}`");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 清理分销与合伙人收益流水表
|
||||
$rewardTables = [
|
||||
'shopro_partner_reward', // 二开收益中心全量分润流水
|
||||
'shopro_commission_order', // 分销订单关联表
|
||||
'shopro_commission_reward', // 原分销奖励明细
|
||||
'shopro_commission_log', // 分销变动日志
|
||||
];
|
||||
|
||||
$output->writeln("\n>>> 2. 正在清理分销订单与收益分润记录...");
|
||||
foreach ($rewardTables as $tbl) {
|
||||
$fullTable = $prefix . $tbl;
|
||||
if ($this->tableExists($fullTable)) {
|
||||
Db::execute("TRUNCATE TABLE `{$fullTable}`");
|
||||
$output->writeln(" [OK] 清空表 `{$fullTable}`");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 清理日结对账、马豆大盘汇率快照与兑换券释放计划
|
||||
$dailyTables = [
|
||||
'shopro_partner_rate_log', // 马豆大盘汇率历史快照日志(清空后大盘汇率无历史,自动按 0 单重算)
|
||||
'shopro_partner_daily_report', // 每日日结对账日报表
|
||||
'shopro_partner_voucher_plan', // 兑换券释放计划表
|
||||
'shopro_partner_voucher_log', // 兑换券释放明细流水
|
||||
];
|
||||
|
||||
$output->writeln("\n>>> 3. 正在清理日结对账、汇率快照与兑换券计划...");
|
||||
foreach ($dailyTables as $tbl) {
|
||||
$fullTable = $prefix . $tbl;
|
||||
if ($this->tableExists($fullTable)) {
|
||||
Db::execute("TRUNCATE TABLE `{$fullTable}`");
|
||||
$output->writeln(" [OK] 清空表 `{$fullTable}`");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 重置分销商资产状态(马豆归零、推三返一序号归零、兑换券归零、收益归零)
|
||||
$output->writeln("\n>>> 4. 正在重置分销商(Agent)资产指标...");
|
||||
$agentTable = $prefix . 'shopro_commission_agent';
|
||||
if ($this->tableExists($agentTable)) {
|
||||
Db::execute("UPDATE `{$agentTable}` SET
|
||||
`madow` = 0.00000000,
|
||||
`direct_order_seq` = 0,
|
||||
`voucher_total` = 0.00,
|
||||
`voucher_released` = 0.00,
|
||||
`voucher_used` = 0.00,
|
||||
`total_income` = 0.00,
|
||||
`commission` = 0.00,
|
||||
`total_consume` = 0.00,
|
||||
`last_self_order_time` = NULL");
|
||||
$output->writeln(" [OK] 已将所有分销商持仓马豆、推三返一序号、兑换券及累计收益重置归零");
|
||||
}
|
||||
|
||||
// 5. 重置用户表钱包分销佣金与累计消费额
|
||||
$output->writeln("\n>>> 5. 正在重置会员用户(User)交易与佣金数据...");
|
||||
$userTable = $prefix . 'user';
|
||||
if ($this->tableExists($userTable)) {
|
||||
Db::execute("UPDATE `{$userTable}` SET
|
||||
`commission` = 0.00,
|
||||
`total_consume` = 0.00,
|
||||
`order_count` = 0");
|
||||
$output->writeln(" [OK] 已将所有会员钱包佣金、累计消费、订单计数重置归零");
|
||||
}
|
||||
|
||||
// 6. 可选清理钱包流水表中佣金相关的记录
|
||||
if ($withWallet) {
|
||||
$walletLogTable = $prefix . 'shopro_user_wallet_log';
|
||||
if ($this->tableExists($walletLogTable)) {
|
||||
Db::execute("DELETE FROM `{$walletLogTable}` WHERE `wallet_type` = 'commission' OR `event` IN ('reward_income', 'reward_back', 'order_pay', 'order_refund')");
|
||||
$output->writeln(" [OK] 已清理佣金钱包及订单相关流水日志");
|
||||
}
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
$output->writeln("\n[SUCCESS] 数据库交易与分润记录清理完毕!事务已成功提交。");
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
$output->writeln("\n[FAIL] 清理过程中发生异常,事务已回滚: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 7. 清理缓存中汇率与收益中心配置
|
||||
$output->writeln("\n>>> 6. 正在清除马豆汇率缓存与系统配置缓存...");
|
||||
cache('partner_madow_current_rate', null);
|
||||
cache('partner_rules', null);
|
||||
if (function_exists('rmdirs')) {
|
||||
@rmdirs(RUNTIME_PATH . 'cache');
|
||||
@rmdirs(RUNTIME_PATH . 'temp');
|
||||
}
|
||||
$output->writeln(" [OK] 马豆即时汇率缓存已清理");
|
||||
|
||||
// 8. 校验回归初始汇率
|
||||
$rateService = new \addons\shopro\service\partner\RateService();
|
||||
$resetRate = $rateService->currentRate();
|
||||
$resetN = $rateService->countN();
|
||||
$formattedRate = number_format($resetRate, 8, '.', '');
|
||||
|
||||
$output->writeln("\n========================================================");
|
||||
$output->writeln(" 重置后指标核对:");
|
||||
$output->writeln(" 合格订单总数 N : {$resetN} 单 (应为 0)");
|
||||
$output->writeln(" 当前大盘汇率 : {$formattedRate} 元/豆 (回归初始基准值 0.00000075)");
|
||||
$output->writeln("========================================================\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function tableExists($table)
|
||||
{
|
||||
try {
|
||||
$check = Db::query("SHOW TABLES LIKE '{$table}'");
|
||||
return !empty($check);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,43 +56,47 @@ class Commission
|
||||
*/
|
||||
public function orderPaidAfter($params)
|
||||
{
|
||||
// 订单支付成功
|
||||
$order = $params['order'];
|
||||
try {
|
||||
// 订单支付成功
|
||||
$order = $params['order'];
|
||||
|
||||
// 积分商品订单不参与分销
|
||||
if ($order['type'] === 'score') return;
|
||||
// 积分商品订单不参与分销
|
||||
if ($order['type'] === 'score') return;
|
||||
|
||||
$user = $params['user'];
|
||||
$user = $params['user'];
|
||||
|
||||
$agent = new AgentService($user);
|
||||
$agent = new AgentService($user);
|
||||
|
||||
// 绑定用户关系
|
||||
$agent->bindUserRelation('pay');
|
||||
// 绑定用户关系
|
||||
$agent->bindUserRelation('pay');
|
||||
|
||||
// 先记录分佣 再处理记录业绩、升级等情况
|
||||
$items = $order ? $order['items'] : [];
|
||||
// 先记录分佣 再处理记录业绩、升级等情况
|
||||
$items = $order ? $order['items'] : [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (isset($item['ext']['is_commission']) && !$item['ext']['is_commission']) continue;
|
||||
foreach ($items as $item) {
|
||||
if (isset($item['ext']['is_commission']) && !$item['ext']['is_commission']) continue;
|
||||
|
||||
$commission = new OrderService($item);
|
||||
// 检查能否分销
|
||||
if (!$commission->checkAndSetCommission()) continue;
|
||||
// 添加分销订单
|
||||
$commissionOrder = $commission->createCommissionOrder();
|
||||
$commission = new OrderService($item);
|
||||
// 检查能否分销
|
||||
if (!$commission->checkAndSetCommission()) continue;
|
||||
// 添加分销订单
|
||||
$commissionOrder = $commission->createCommissionOrder();
|
||||
|
||||
if (!$commissionOrder) continue;
|
||||
if (!$commissionOrder) continue;
|
||||
|
||||
// 执行佣金计划
|
||||
$commission->runCommissionPlan($commissionOrder);
|
||||
// 支付后拨款
|
||||
(new RewardService('paid'))->runCommissionRewardByOrder($commissionOrder);
|
||||
// 执行佣金计划
|
||||
$commission->runCommissionPlan($commissionOrder);
|
||||
// 支付后拨款
|
||||
(new RewardService('paid'))->runCommissionRewardByOrder($commissionOrder);
|
||||
}
|
||||
|
||||
// 创建分销商
|
||||
$agent->createNewAgent();
|
||||
// 分销商数据统计&升级(异步执行)
|
||||
$agent->createAsyncAgentUpgrade();
|
||||
} catch (\Throwable $e) {
|
||||
format_log_error($e, 'Commission.orderPaidAfter', '原生分销监听处理异常: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 创建分销商
|
||||
$agent->createNewAgent();
|
||||
// 分销商数据统计&升级(异步执行)
|
||||
$agent->createAsyncAgentUpgrade();
|
||||
}
|
||||
|
||||
// 线下付款后
|
||||
|
||||
@@ -35,6 +35,7 @@ class Partner
|
||||
|
||||
$service = new PartnerRewardService();
|
||||
if ($service->isQualified($order)) {
|
||||
\addons\shopro\service\partner\RateService::clearCache();
|
||||
$userId = $order['user_id'] ?? ($params['user']['id'] ?? 0);
|
||||
if ($userId) {
|
||||
$agent = new AgentService($userId);
|
||||
|
||||
@@ -84,6 +84,11 @@ class IdentityService
|
||||
|
||||
public static function formatMadow($value)
|
||||
{
|
||||
$floatVal = (float) $value;
|
||||
// 当存在小于 1 豆且大于 0 的微量马豆时,自适应保留有效小数(最多4位),避免直接抹零显示为 0 豆
|
||||
if ($floatVal > 0 && $floatVal < 1) {
|
||||
return rtrim(rtrim(number_format($floatVal, 4, '.', ''), '0'), '.');
|
||||
}
|
||||
return number_format((float) $value, 0, '.', '');
|
||||
}
|
||||
|
||||
|
||||
@@ -104,10 +104,12 @@ class PartnerConfig
|
||||
'venue_share' => $venue . '加权奖励',
|
||||
'county_share' => $county . '加权奖励',
|
||||
'madow_share' => '每日贡献马豆',
|
||||
'admin_madow' => '管理员调整马豆',
|
||||
'voucher_grant' => '消费补贴发放',
|
||||
'voucher_release' => '消费补贴到账',
|
||||
'voucher_consume' => '消费券抵扣',
|
||||
'voucher_consume_refund' => '消费券退款退还',
|
||||
'admin_voucher' => '管理员调整兑换券',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -11,21 +11,33 @@ use app\admin\model\shopro\commission\CommissionGoods;
|
||||
*/
|
||||
class RateService
|
||||
{
|
||||
public function currentRate()
|
||||
/**
|
||||
* 获取大盘当前即时生效汇率(基于全网合格订单 N 实时动态计算,短缓存 120 秒保障高并发读性能)
|
||||
*
|
||||
* @param bool $forceRefresh 是否强制穿透缓存实时重算
|
||||
* @return float
|
||||
*/
|
||||
public function currentRate($forceRefresh = false)
|
||||
{
|
||||
$latest = RateLog::order('id', 'desc')->find();
|
||||
if ($latest && (float) $latest->rate > 0) {
|
||||
return (float) $latest->rate;
|
||||
}
|
||||
$cached = cache('partner_madow_current_rate');
|
||||
if ($cached !== null && $cached !== false && (float) $cached > 0) {
|
||||
return (float) $cached;
|
||||
if (!$forceRefresh) {
|
||||
$cached = cache('partner_madow_current_rate');
|
||||
if ($cached !== null && $cached !== false && (float) $cached > 0) {
|
||||
return (float) $cached;
|
||||
}
|
||||
}
|
||||
$rate = $this->calcRate($this->countN());
|
||||
cache('partner_madow_current_rate', $rate, 300);
|
||||
cache('partner_madow_current_rate', $rate, 120);
|
||||
return $rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主动清除当前马豆汇率缓存(订单支付、退款或后台调整时调用)
|
||||
*/
|
||||
public static function clearCache()
|
||||
{
|
||||
cache('partner_madow_current_rate', null);
|
||||
}
|
||||
|
||||
public function calcRate($n = null)
|
||||
{
|
||||
$n = $n === null ? $this->countN() : intval($n);
|
||||
|
||||
在新工单中引用
屏蔽一个用户