diff --git a/app/Console/Commands/RollbackOrderItemsPaidAt.php b/app/Console/Commands/RollbackOrderItemsPaidAt.php new file mode 100644 index 0000000..6ea7d3f --- /dev/null +++ b/app/Console/Commands/RollbackOrderItemsPaidAt.php @@ -0,0 +1,233 @@ +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; + } +} +