Commit c2a21afe authored by 刘小敏's avatar 刘小敏

feat(command): 添加补漏分销商分佣脚本

parent 3743f53f
......@@ -27,5 +27,6 @@ return [
'app\command\TestLogDb',
'app\command\AgentOrderCommission',
'app\command\InitCommissionGoods',
'app\command\FixMissedCommission',
];
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Db;
use app\admin\model\shopro\commission\Order as CommissionOrderModel;
use app\admin\model\shopro\commission\Reward as RewardModel;
use addons\shopro\service\commission\Order as OrderService;
use addons\shopro\service\commission\Reward as RewardService;
/**
* 补漏分销商分佣脚本
*
* 遍历已完成的订单,检查分销佣金是否遗漏,补全数据:
* 1. 对于完全没有分佣订单记录的商品,模拟"订单支付成功"流程
* 创建 commission_order + commission_reward
* 2. 对于已有分佣记录但佣金状态为"未结算"的,模拟"订单完成"流程
* 执行奖励拨款(使用 admin 事件,绕过 reward_event 匹配限制)
*
* 命令:php think shopro:fix-missed-commission
*
* 使用场景:
* - 历史订单漏掉分销商分佣时,一次性补填数据
* - 可重复执行,已结算的记录不会重复打款
*/
class FixMissedCommission extends Command
{
/**
* 控制台输出对象
* @var Output
*/
protected $output = null;
/**
* 分页大小
*/
const PAGE_SIZE = 100;
/**
* 配置命令信息
*/
protected function configure()
{
$this->setName('shopro:fix-missed-commission')
->setDescription('补漏分销商分佣:遍历已完成订单,补充遗漏的分销佣金记录并结算');
}
/**
* 自定义日志记录方法
*
* @param string $level 日志级别: info/warning/error
* @param string $message 日志内容
*/
protected function customLog($level, $message)
{
$timestamp = date('Y-m-d H:i:s');
$formattedMessage = "[{$timestamp}] [{$level}] {$message}";
if ($this->output) {
switch ($level) {
case 'error':
$this->output->writeln("<error>{$formattedMessage}</error>");
break;
case 'warning':
$this->output->writeln("<comment>{$formattedMessage}</comment>");
break;
default:
$this->output->writeln("<info>{$formattedMessage}</info>");
break;
}
}
custom_log($message, 0, 'fix_missed_commission', $level);
}
/**
* 命令执行入口
*
* @param Input $input
* @param Output $output
*/
protected function execute(Input $input, Output $output)
{
$this->output = $output;
$this->customLog('info', '========== 开始补漏分销商分佣 ==========');
try {
$page = 1;
$totalOrders = 0;
$totalCreated = 0; // 新创建的分销订单数
$totalSettled = 0; // 新结算的奖励数
$totalSkipped = 0; // 已结算跳过的
while (true) {
// 分页查询已完成的订单(排除积分商品订单)
$orders = Db::name('shopro_order')
->where('status', 'completed')
->where('type', '<>', 'score')
->field('id, user_id, order_sn')
->order('id', 'asc')
->page($page, self::PAGE_SIZE)
->select();
if (empty($orders)) {
$this->customLog('info', "第{$page}页无数据,遍历结束");
break;
}
$pageTotal = count($orders);
$totalOrders += $pageTotal;
$this->customLog('info', "第{$page}页: 获取 {$pageTotal} 个已完成订单");
foreach ($orders as $order) {
$orderId = $order['id'];
$orderUserId = $order['user_id'];
$orderSn = $order['order_sn'] ?? '';
// 获取订单下所有未退款的商品
$items = Db::name('shopro_order_item')
->where('order_id', $orderId)
->where('refund_status', 0) // 未申请退款
->field('id, order_id, goods_id, goods_sku_price_id, pay_fee, goods_price, goods_num, goods_title, ext')
->select();
if (empty($items)) {
continue;
}
// 第一步:为每个缺失分佣记录的商品创建 commission_order + commission_reward(模拟支付成功流程)
foreach ($items as $item) {
$itemId = $item['id'];
// 解析 ext 字段,检查 is_commission 标记
$ext = json_decode($item['ext'], true);
if (is_array($ext) && isset($ext['is_commission']) && !$ext['is_commission']) {
$this->customLog('info', "订单[{$orderId}][{$orderSn}]商品[{$itemId}]标记为不参与分销,跳过");
continue;
}
// 已存在分佣记录则跳过创建
$existingCommissionOrder = CommissionOrderModel::where('order_item_id', $itemId)->find();
if ($existingCommissionOrder) {
continue;
}
// 补充 user_id(OrderService 需要)
$item['user_id'] = $orderUserId;
try {
// 模拟订单支付成功后的分佣流程
$commission = new OrderService($item);
// 检查能否分销
if (!$commission->checkAndSetCommission()) {
$this->customLog('info', "订单[{$orderId}][{$orderSn}]商品[{$itemId}]不满足分销条件,跳过");
continue;
}
// 创建分销订单
$commissionOrder = $commission->createCommissionOrder();
if (!$commissionOrder) {
$this->customLog('warning', "订单[{$orderId}][{$orderSn}]商品[{$itemId}]创建分销订单失败,跳过");
continue;
}
// 执行佣金计划(创建 commission_reward 记录)
$commission->runCommissionPlan($commissionOrder);
$totalCreated++;
$this->customLog('info', "订单[{$orderId}][{$orderSn}]商品[{$itemId}]补建分销订单成功,commission_order_id={$commissionOrder->id}");
} catch (\Exception $e) {
$this->customLog('error', "订单[{$orderId}][{$orderSn}]商品[{$itemId}]创建分佣记录异常: " . $e->getMessage());
if (function_exists('format_log_error_custom_name')) {
format_log_error_custom_name($e, "[FixMissedCommission]创建分佣记录异常", '', 'fix_missed_commission');
}
}
}
// 第二步:对订单下所有未结算的分销奖励执行拨款(模拟订单完成流程)
$commissionOrders = CommissionOrderModel::where('order_id', $orderId)->select();
if (empty($commissionOrders)) {
continue;
}
$rewardService = new RewardService('admin'); // admin 事件不限制 reward_event
foreach ($commissionOrders as $co) {
// 仅处理待结算状态,已结算/已退回/已取消的跳过
if ($co['commission_reward_status'] != RewardModel::COMMISSION_REWARD_STATUS_PENDING) {
$totalSkipped++;
continue;
}
try {
$result = $rewardService->runCommissionRewardByOrder($co);
if ($result) {
$totalSettled++;
$this->customLog('info', "订单[{$orderId}][{$orderSn}]分佣订单[{$co['id']}]拨款成功");
} else {
$this->customLog('warning', "订单[{$orderId}][{$orderSn}]分佣订单[{$co['id']}]拨款失败(可能不满足条件)");
}
} catch (\Exception $e) {
$this->customLog('error', "订单[{$orderId}][{$orderSn}]分佣订单[{$co['id']}]拨款异常: " . $e->getMessage());
if (function_exists('format_log_error_custom_name')) {
format_log_error_custom_name($e, "[FixMissedCommission]拨款异常", '', 'fix_missed_commission');
}
}
}
}
// 如果本页数量少于分页大小,说明已是最后一页
if ($pageTotal < self::PAGE_SIZE) {
$this->customLog('info', "第{$page}页数据不足分页大小,已是最后一页");
break;
}
$page++;
}
$this->customLog('info', "共处理 {$totalOrders} 个已完成订单");
$this->customLog('info', "新建分销订单: {$totalCreated}, 拨款结算: {$totalSettled}, 已结算跳过: {$totalSkipped}");
$this->customLog('info', '========== 补漏分销商分佣完成 ==========');
} catch (\Exception $e) {
$this->customLog('error', '[补漏分销商分佣失败]');
if (function_exists('format_log_error_custom_name')) {
format_log_error_custom_name($e, '[补漏分销商分佣失败]', '', 'fix_missed_commission');
} else {
$this->customLog('error', $e->getMessage() . "\n" . $e->getTraceAsString());
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment