|
|
@@ -0,0 +1,297 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Module\Farm\Commands;
|
|
|
+
|
|
|
+use App\Module\Farm\Enums\LAND_STATUS;
|
|
|
+use App\Module\Farm\Models\FarmLand;
|
|
|
+use Illuminate\Console\Command;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+use Illuminate\Support\Facades\Log;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 修复已铲除作物的土地状态命令
|
|
|
+ *
|
|
|
+ * 修复作物已经铲除(软删除)但土地状态不是空闲的问题
|
|
|
+ * php artisan farm:fix-removed-crop-land-status --dry-run
|
|
|
+ */
|
|
|
+class FixRemovedCropLandStatusCommand extends Command
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * 命令签名
|
|
|
+ *
|
|
|
+ * @var string
|
|
|
+ */
|
|
|
+ protected $signature = 'farm:fix-removed-crop-land-status
|
|
|
+ {--dry-run : 仅显示需要修复的数据,不执行修复}
|
|
|
+ {--user= : 指定用户ID,只修复该用户的数据}
|
|
|
+ {--limit=100 : 每批处理的数量}';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 命令描述
|
|
|
+ *
|
|
|
+ * @var string
|
|
|
+ */
|
|
|
+ protected $description = '修复作物已经铲除(软删除)但土地状态不是空闲的问题';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行命令
|
|
|
+ *
|
|
|
+ * @return int
|
|
|
+ */
|
|
|
+ public function handle(): int
|
|
|
+ {
|
|
|
+ $dryRun = $this->option('dry-run');
|
|
|
+ $userId = $this->option('user');
|
|
|
+ $limit = (int) $this->option('limit');
|
|
|
+
|
|
|
+ $this->info('开始检查已铲除作物的土地状态...');
|
|
|
+
|
|
|
+ if ($dryRun) {
|
|
|
+ $this->warn('运行在模拟模式,不会实际修改数据');
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取需要修复的数据
|
|
|
+ $problematicLands = $this->getProblematicLands($userId, $limit);
|
|
|
+
|
|
|
+ if ($problematicLands->isEmpty()) {
|
|
|
+ $this->info('没有发现需要修复的数据');
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->info("发现 {$problematicLands->count()} 条需要修复的土地");
|
|
|
+
|
|
|
+ if ($dryRun) {
|
|
|
+ $this->displayProblems($problematicLands);
|
|
|
+ } else {
|
|
|
+ $this->fixProblems($problematicLands);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$dryRun) {
|
|
|
+ $this->info("\n修复完成!");
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $this->error("修复过程中发生错误: {$e->getMessage()}");
|
|
|
+ Log::error('已铲除作物土地状态修复失败', [
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ 'trace' => $e->getTraceAsString()
|
|
|
+ ]);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取有问题的土地数据
|
|
|
+ * 查找作物已软删除但土地状态不是空闲的情况
|
|
|
+ *
|
|
|
+ * @param string|null $userId
|
|
|
+ * @param int $limit
|
|
|
+ * @return \Illuminate\Support\Collection
|
|
|
+ */
|
|
|
+ private function getProblematicLands(?string $userId, int $limit): \Illuminate\Support\Collection
|
|
|
+ {
|
|
|
+ // 先获取有软删除作物的土地ID列表
|
|
|
+ $landsWithDeletedCrops = DB::table('farm_land_users')
|
|
|
+ ->join('farm_crops', 'farm_land_users.id', '=', 'farm_crops.land_id')
|
|
|
+ ->select('farm_land_users.id')
|
|
|
+ ->whereNotNull('farm_crops.deleted_at')
|
|
|
+ ->where('farm_land_users.status', '!=', LAND_STATUS::IDLE->value)
|
|
|
+ ->when($userId, function ($query, $userId) {
|
|
|
+ return $query->where('farm_land_users.user_id', $userId);
|
|
|
+ })
|
|
|
+ ->distinct()
|
|
|
+ ->pluck('farm_land_users.id');
|
|
|
+
|
|
|
+ // 获取没有未删除作物的土地ID列表
|
|
|
+ $landsWithoutActiveCrops = DB::table('farm_land_users')
|
|
|
+ ->leftJoin('farm_crops', function ($join) {
|
|
|
+ $join->on('farm_land_users.id', '=', 'farm_crops.land_id')
|
|
|
+ ->whereNull('farm_crops.deleted_at');
|
|
|
+ })
|
|
|
+ ->select('farm_land_users.id')
|
|
|
+ ->whereNull('farm_crops.id')
|
|
|
+ ->where('farm_land_users.status', '!=', LAND_STATUS::IDLE->value)
|
|
|
+ ->when($userId, function ($query, $userId) {
|
|
|
+ return $query->where('farm_land_users.user_id', $userId);
|
|
|
+ })
|
|
|
+ ->pluck('farm_land_users.id');
|
|
|
+
|
|
|
+ // 合并所有有问题的土地ID
|
|
|
+ $allProblematicLandIds = $landsWithDeletedCrops->concat($landsWithoutActiveCrops)->unique()->take($limit);
|
|
|
+
|
|
|
+ // 获取详细信息
|
|
|
+ $results = collect();
|
|
|
+ foreach ($allProblematicLandIds as $landId) {
|
|
|
+ $land = DB::table('farm_land_users')->where('id', $landId)->first();
|
|
|
+ if (!$land) continue;
|
|
|
+
|
|
|
+ // 获取该土地的软删除作物信息
|
|
|
+ $deletedCrops = DB::table('farm_crops')
|
|
|
+ ->where('land_id', $landId)
|
|
|
+ ->whereNotNull('deleted_at')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ $item = (object) [
|
|
|
+ 'land_id' => $land->id,
|
|
|
+ 'user_id' => $land->user_id,
|
|
|
+ 'land_status' => $land->status,
|
|
|
+ 'has_crop' => $land->has_crop,
|
|
|
+ 'deleted_crops_count' => $deletedCrops->count(),
|
|
|
+ 'crop_ids' => $deletedCrops->pluck('id')->implode(','),
|
|
|
+ 'first_deleted_at' => $deletedCrops->min('deleted_at'),
|
|
|
+ 'last_deleted_at' => $deletedCrops->max('deleted_at'),
|
|
|
+ 'seed_ids' => $deletedCrops->pluck('seed_id')->unique()->implode(',')
|
|
|
+ ];
|
|
|
+
|
|
|
+ $item->problem_type = $this->getProblemType($item);
|
|
|
+ $results->push($item);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $results;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取问题类型
|
|
|
+ *
|
|
|
+ * @param object $item
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ private function getProblemType(object $item): string
|
|
|
+ {
|
|
|
+ if ($item->deleted_crops_count > 0) {
|
|
|
+ return '作物已软删除但土地非空闲';
|
|
|
+ } elseif ($item->deleted_crops_count == 0) {
|
|
|
+ return '土地无作物但状态非空闲';
|
|
|
+ }
|
|
|
+
|
|
|
+ return '其他问题';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示问题数据
|
|
|
+ *
|
|
|
+ * @param \Illuminate\Support\Collection $items
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ private function displayProblems(\Illuminate\Support\Collection $items): void
|
|
|
+ {
|
|
|
+ // 按问题类型分组显示
|
|
|
+ $groupedData = $items->groupBy('problem_type');
|
|
|
+
|
|
|
+ foreach ($groupedData as $problemType => $problemItems) {
|
|
|
+ $this->info("\n=== {$problemType} ({$problemItems->count()} 条) ===");
|
|
|
+
|
|
|
+ $headers = ['土地ID', '用户ID', '当前土地状态', 'has_crop', '软删除作物数', '作物IDs', '最后删除时间', '种子IDs'];
|
|
|
+ $rows = [];
|
|
|
+
|
|
|
+ foreach ($problemItems as $item) {
|
|
|
+ $rows[] = [
|
|
|
+ $item->land_id,
|
|
|
+ $item->user_id,
|
|
|
+ $this->getLandStatusName($item->land_status),
|
|
|
+ $item->has_crop ? '是' : '否',
|
|
|
+ $item->deleted_crops_count,
|
|
|
+ $item->crop_ids ? (strlen($item->crop_ids) > 50 ? substr($item->crop_ids, 0, 47) . '...' : $item->crop_ids) : 'N/A',
|
|
|
+ $item->last_deleted_at ?? 'N/A',
|
|
|
+ $item->seed_ids ?? 'N/A'
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->table($headers, $rows);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 修复问题数据
|
|
|
+ *
|
|
|
+ * @param \Illuminate\Support\Collection $items
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ private function fixProblems(\Illuminate\Support\Collection $items): void
|
|
|
+ {
|
|
|
+ $fixedCount = 0;
|
|
|
+ $failedCount = 0;
|
|
|
+
|
|
|
+ foreach ($items as $item) {
|
|
|
+ try {
|
|
|
+ DB::transaction(function () use ($item) {
|
|
|
+ $this->fixSingleLand($item);
|
|
|
+ });
|
|
|
+
|
|
|
+ $fixedCount++;
|
|
|
+ $this->info("✓ 修复土地 {$item->land_id} (用户 {$item->user_id}) - {$item->problem_type}");
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $failedCount++;
|
|
|
+ $this->error("✗ 修复土地 {$item->land_id} 失败: {$e->getMessage()}");
|
|
|
+
|
|
|
+ Log::error('单个土地状态修复失败', [
|
|
|
+ 'land_id' => $item->land_id,
|
|
|
+ 'user_id' => $item->user_id,
|
|
|
+ 'problem_type' => $item->problem_type,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->info("修复完成: 成功 {$fixedCount} 条,失败 {$failedCount} 条");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 修复单个土地
|
|
|
+ *
|
|
|
+ * @param object $item
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ private function fixSingleLand(object $item): void
|
|
|
+ {
|
|
|
+ $land = FarmLand::lockForUpdate()->find($item->land_id);
|
|
|
+
|
|
|
+ if (!$land) {
|
|
|
+ throw new \Exception('土地不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ $oldStatus = $land->status;
|
|
|
+ $oldHasCrop = $land->has_crop;
|
|
|
+
|
|
|
+ // 设置土地状态为空闲
|
|
|
+ $land->status = LAND_STATUS::IDLE->value;
|
|
|
+ $land->updateHasCrop(); // 这会将has_crop设置为false
|
|
|
+ $land->save();
|
|
|
+
|
|
|
+ Log::info('已铲除作物土地状态修复', [
|
|
|
+ 'land_id' => $land->id,
|
|
|
+ 'user_id' => $land->user_id,
|
|
|
+ 'deleted_crops_count' => $item->deleted_crops_count,
|
|
|
+ 'crop_ids' => $item->crop_ids,
|
|
|
+ 'old_status' => $oldStatus,
|
|
|
+ 'new_status' => $land->status,
|
|
|
+ 'old_has_crop' => $oldHasCrop,
|
|
|
+ 'new_has_crop' => $land->has_crop,
|
|
|
+ 'problem_type' => $item->problem_type,
|
|
|
+ 'first_deleted_at' => $item->first_deleted_at,
|
|
|
+ 'last_deleted_at' => $item->last_deleted_at
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取土地状态名称
|
|
|
+ *
|
|
|
+ * @param int $status
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ private function getLandStatusName(int $status): string
|
|
|
+ {
|
|
|
+ return match ($status) {
|
|
|
+ LAND_STATUS::IDLE->value => '空闲',
|
|
|
+ LAND_STATUS::PLANTING->value => '种植中',
|
|
|
+ LAND_STATUS::DISASTER->value => '灾害',
|
|
|
+ LAND_STATUS::HARVESTABLE->value => '可收获',
|
|
|
+ LAND_STATUS::WITHERED->value => '枯萎',
|
|
|
+ default => '未知'
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|