You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

234 lines
8.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Console\Commands;
use App\Models\Balance;
use App\Models\OrderItems;
use App\Models\Orders;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class RollbackOrderItemsPaidAt extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'order-items:rollback-paid-at {date} {--dry-run : 试运行模式,只显示预览信息,不执行实际回滚}';
/**
* The console command description.
*
* @var string
*/
protected $description = '回滚指定日期 service_date 的 order_items 表中被修复的 paid_at 字段,恢复到修复前的状态';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$date = $this->argument('date');
// 验证日期格式
try {
$serviceDate = Carbon::createFromFormat('Y-m-d', $date);
} catch (\Exception $e) {
$this->error("日期格式错误,请使用 Y-m-d 格式例如2025-12-01");
return 1;
}
$serviceDateStr = $serviceDate->format('Y-m-d');
$serviceDateEnd = $serviceDateStr . ' 23:59:59';
$isDryRun = $this->option('dry-run');
if ($isDryRun) {
$this->warn("=== 试运行模式:将只显示预览信息,不会执行实际回滚 ===");
$this->line('');
}
$this->info("开始" . ($isDryRun ? "预览" : "回滚") . " {$serviceDateStr} 的订单项修复...");
// 查询需要回滚的记录
// 条件:有 paid_at_original说明被修复过且 paid_at 等于修复后的值
$items = OrderItems::where('service_date', $serviceDateStr)
->whereNotNull('paid_at_original')
->where('paid_at', $serviceDateEnd)
->with(['order' => function ($query) {
$query->select('id', 'customer_id');
}])
->get();
$totalCount = $items->count();
$this->info("找到 {$totalCount} 条需要回滚的记录");
if ($totalCount == 0) {
$this->info("没有需要回滚的记录");
return 0;
}
$rolledBackCount = 0;
$errorCount = 0;
$previewData = [];
if (!$isDryRun) {
$bar = $this->output->createProgressBar($totalCount);
$bar->start();
} else {
$this->line('');
$this->info("预览将要回滚的记录:");
$this->line('');
}
foreach ($items as $item) {
try {
// 获取客户ID
$customerId = $item->order ? $item->order->customer_id : null;
if (!$customerId) {
if (!$isDryRun) {
$this->line('');
$this->warn("订单项 {$item->id} 没有关联的客户,跳过");
$bar->advance();
}
$errorCount++;
continue;
}
$originalPaidAt = $item->paid_at_original;
$currentPaidAt = $item->paid_at;
// 检查是否有关联的 balance 记录
$balanceRecord = Balance::where('belongs_type', OrderItems::class)
->where('belongs_id', $item->id)
->where('customer_id', $customerId)
->first();
if ($isDryRun) {
// 试运行模式:只收集预览信息
$previewData[] = [
'order_item_id' => $item->id,
'order_id' => $item->order_id,
'customer_id' => $customerId,
'service_date' => $item->service_date,
'current_paid_at' => $currentPaidAt,
'original_paid_at' => $originalPaidAt,
'has_balance_record' => $balanceRecord ? '是' : '否',
'balance_record_id' => $balanceRecord ? $balanceRecord->id : null,
'balance_current_created_at' => $balanceRecord ? $balanceRecord->created_at : null,
];
$rolledBackCount++;
} else {
// 实际执行回滚
DB::beginTransaction();
try {
// 恢复 paid_at
$item->update([
'paid_at' => $originalPaidAt,
'paid_at_original' => null // 清空,表示已回滚
]);
// 恢复关联的 balance 记录的 created_at
if ($balanceRecord) {
$balanceRecord->update([
'created_at' => $originalPaidAt
]);
} else {
$this->line('');
$this->warn("订单项 {$item->id} 没有找到关联的 balance 记录");
}
DB::commit();
$rolledBackCount++;
Log::info("回滚订单项 {$item->id}paid_at {$currentPaidAt} -> {$originalPaidAt}");
} catch (\Exception $e) {
DB::rollBack();
$errorCount++;
$this->line('');
$this->error("回滚订单项 {$item->id} 失败:" . $e->getMessage());
Log::error("回滚订单项 {$item->id} 失败:" . $e->getMessage());
}
}
} catch (\Exception $e) {
$errorCount++;
if (!$isDryRun) {
$this->line('');
$this->error("处理订单项 {$item->id} 时出错:" . $e->getMessage());
}
Log::error("处理订单项 {$item->id} 时出错:" . $e->getMessage());
}
if (!$isDryRun) {
$bar->advance();
}
}
if (!$isDryRun) {
$bar->finish();
}
$this->line('');
$this->line('');
// 试运行模式:显示详细预览信息
if ($isDryRun && !empty($previewData)) {
$this->info("=== 预览详情 ===");
$this->line('');
$this->info("将要回滚的记录(" . count($previewData) . " 条):");
$this->line('');
$this->table(
['订单项ID', '订单ID', '客户ID', '服务日期', '当前paid_at', '回滚后paid_at', '有balance记录', 'balance当前created_at'],
array_map(function($item) {
return [
$item['order_item_id'],
$item['order_id'],
$item['customer_id'],
$item['service_date'],
$item['current_paid_at'],
$item['original_paid_at'],
$item['has_balance_record'],
$item['balance_current_created_at'] ?: '-',
];
}, $previewData)
);
$this->line('');
}
// 输出统计信息
$this->info(($isDryRun ? "预览" : "回滚") . "完成!");
$this->table(
['统计项', '数量'],
[
['总记录数', $totalCount],
['成功' . ($isDryRun ? '(将回滚)' : '回滚'), $rolledBackCount],
['处理失败', $errorCount],
]
);
if ($isDryRun) {
$this->line('');
$this->comment("提示:这是试运行模式,没有执行实际回滚。");
$this->comment("要执行实际回滚,请运行命令时不加 --dry-run 参数。");
}
return 0;
}
}