proposalService = $proposalService; } /** * Execute the console command. */ public function handle() { $this->info('🧮 Starting Retribution Proposal Calculation...'); $this->newLine(); try { // Get processing options $force = $this->option('force'); $limit = $this->option('limit') ? (int) $this->option('limit') : null; $skipExisting = $this->option('skip-existing'); $dryRun = $this->option('dry-run'); // Build query for spatial plannings $query = SpatialPlanning::query(); if ($skipExisting && !$force) { $query->whereDoesntHave('retributionProposals'); } if ($limit) { $query->limit($limit); } $spatialPlannings = $query->get(); $totalCount = $spatialPlannings->count(); if ($totalCount === 0) { $this->warn('No spatial plannings found to process.'); return 0; } // Show processing summary $this->info("📊 PROCESSING SUMMARY:"); $this->info(" Total Spatial Plannings: {$totalCount}"); $this->info(" Force Recalculate: " . ($force ? 'Yes' : 'No')); $this->info(" Skip Existing: " . ($skipExisting ? 'Yes' : 'No')); $this->info(" Dry Run: " . ($dryRun ? 'Yes' : 'No')); $this->newLine(); if ($dryRun) { $this->showDryRunPreview($spatialPlannings); return 0; } // Confirm processing if (!$this->confirm("Process {$totalCount} spatial plannings?")) { $this->info('Operation cancelled.'); return 0; } // Process spatial plannings $this->processRetributionCalculations($spatialPlannings, $force); return 0; } catch (Exception $e) { $this->error('Error during retribution calculation: ' . $e->getMessage()); return 1; } } /** * Show dry run preview */ private function showDryRunPreview($spatialPlannings) { $this->info('🔍 DRY RUN PREVIEW:'); $this->newLine(); $processable = 0; $withProposals = 0; $withoutFunction = 0; $withoutArea = 0; foreach ($spatialPlannings as $spatial) { $hasProposals = $spatial->retributionProposals()->exists(); $buildingFunction = $this->proposalService->detectBuildingFunction($spatial->getBuildingFunctionText()); $area = $spatial->getCalculationArea(); if ($hasProposals) { $withProposals++; } if (!$buildingFunction) { $withoutFunction++; } if ($area <= 0) { $withoutArea++; } if ($buildingFunction && $area > 0) { $processable++; } } $this->table( ['Status', 'Count', 'Description'], [ ['✅ Processable', $processable, 'Can create retribution proposals'], ['🔄 With Existing Proposals', $withProposals, 'Already have proposals (will skip unless --force)'], ['❌ Missing Building Function', $withoutFunction, 'Cannot detect building function'], ['❌ Missing Area', $withoutArea, 'Area is zero or missing'], ] ); $this->newLine(); $this->info("💡 To process: php artisan retribution:calculate-proposals"); $this->info("💡 To force recalculate: php artisan retribution:calculate-proposals --force"); } /** * Process retribution calculations */ private function processRetributionCalculations($spatialPlannings, $force) { $progressBar = $this->output->createProgressBar($spatialPlannings->count()); $progressBar->start(); $stats = [ 'processed' => 0, 'skipped' => 0, 'errors' => 0, 'created_proposals' => 0 ]; foreach ($spatialPlannings as $spatial) { try { $result = $this->processSingleSpatialPlanning($spatial, $force); if ($result['status'] === 'processed') { $stats['processed']++; $stats['created_proposals'] += $result['proposals_created']; } elseif ($result['status'] === 'skipped') { $stats['skipped']++; } else { $stats['errors']++; } } catch (Exception $e) { $stats['errors']++; $this->newLine(); $this->error("Error processing spatial planning ID {$spatial->id}: " . $e->getMessage()); } $progressBar->advance(); } $progressBar->finish(); $this->newLine(2); // Show final statistics $this->showFinalStatistics($stats); } /** * Process single spatial planning */ private function processSingleSpatialPlanning(SpatialPlanning $spatial, $force) { // Check if already has proposals if (!$force && $spatial->retributionProposals()->exists()) { return ['status' => 'skipped', 'reason' => 'already_has_proposals']; } // Detect building function $buildingFunction = $this->proposalService->detectBuildingFunction($spatial->getBuildingFunctionText()); if (!$buildingFunction) { return ['status' => 'error', 'reason' => 'no_building_function']; } // Get area $totalArea = $spatial->getCalculationArea(); if ($totalArea <= 0) { return ['status' => 'error', 'reason' => 'no_area']; } // Get number of floors $numberOfFloors = max(1, $spatial->number_of_floors ?? 1); // Delete existing proposals if force mode if ($force) { $spatial->retributionProposals()->delete(); } $proposalsCreated = 0; // Create proposals for each floor for ($floor = 1; $floor <= $numberOfFloors; $floor++) { // Calculate floor area (distribute total area across floors) $floorArea = $totalArea / $numberOfFloors; // Create retribution proposal $proposal = $this->proposalService->createProposalForSpatialPlanning( $spatial, $buildingFunction->id, $floor, $floorArea, $totalArea, "Auto-generated from spatial planning calculation" ); if ($proposal) { $proposalsCreated++; } } return [ 'status' => 'processed', 'proposals_created' => $proposalsCreated ]; } /** * Show final statistics */ private function showFinalStatistics($stats) { $this->info('✅ CALCULATION COMPLETED!'); $this->newLine(); $this->table( ['Metric', 'Count'], [ ['Processed Successfully', $stats['processed']], ['Skipped (Already Exists)', $stats['skipped']], ['Errors', $stats['errors']], ['Total Proposals Created', $stats['created_proposals']], ] ); if ($stats['errors'] > 0) { $this->newLine(); $this->warn("⚠️ {$stats['errors']} spatial plannings had errors:"); $this->warn(" • Missing building function detection"); $this->warn(" • Missing or zero area"); $this->warn(" • Other calculation errors"); } if ($stats['processed'] > 0) { $this->newLine(); $this->info("🎉 Successfully created {$stats['created_proposals']} retribution proposals!"); $this->info("💡 You can view them using: php artisan retribution:list-proposals"); } } }