proposalService = $proposalService; } /** * Execute the console command. */ public function handle() { $this->info('🏗️ Processing Spatial Planning Retribution Calculations...'); $this->newLine(); try { // Get options $processAll = $this->option('all'); $newOnly = $this->option('new-only'); $force = $this->option('force'); // Build query $query = SpatialPlanning::query(); if ($newOnly || (!$processAll && !$force)) { $query->whereDoesntHave('retributionProposals'); $this->info('📋 Mode: Processing only spatial plannings WITHOUT retribution proposals'); } elseif ($processAll) { $this->info('📋 Mode: Processing ALL spatial plannings'); } $spatialPlannings = $query->get(); $totalCount = $spatialPlannings->count(); if ($totalCount === 0) { $this->warn('❌ No spatial plannings found to process.'); $this->info('💡 Try running: php artisan spatial:init first'); return 0; } $this->info("📊 Found {$totalCount} spatial plannings to process"); $this->newLine(); // Show preview of what will be processed $this->showProcessingPreview($spatialPlannings); // Confirm processing if (!$this->confirm("Process {$totalCount} spatial plannings and create retribution proposals?")) { $this->info('Operation cancelled.'); return 0; } // Process all spatial plannings $this->processAllSpatialPlannings($spatialPlannings, $force); return 0; } catch (Exception $e) { $this->error('❌ Error during processing: ' . $e->getMessage()); return 1; } } /** * Show processing preview */ private function showProcessingPreview($spatialPlannings) { $this->info('🔍 PREVIEW:'); $canProcess = 0; $cannotProcess = 0; $hasExisting = 0; $sampleData = []; $errorReasons = []; foreach ($spatialPlannings->take(5) as $spatial) { $buildingFunction = $this->proposalService->detectBuildingFunction($spatial->getBuildingFunctionText()); $area = $spatial->getCalculationArea(); $floors = max(1, $spatial->number_of_floors ?? 1); $hasProposals = $spatial->retributionProposals()->exists(); if ($hasProposals) { $hasExisting++; } if ($buildingFunction && $area > 0) { $canProcess++; $sampleData[] = [ 'ID' => $spatial->id, 'Name' => substr($spatial->name ?? 'N/A', 0, 30), 'Function' => $buildingFunction->name ?? 'N/A', 'Area' => number_format($area, 2), 'Floors' => $floors, 'Status' => $hasProposals ? '🔄 Has Proposals' : '✅ Ready' ]; } else { $cannotProcess++; if (!$buildingFunction) { $errorReasons[] = 'Missing building function'; } if ($area <= 0) { $errorReasons[] = 'Missing/zero area'; } } } // Show sample data table if (!empty($sampleData)) { $this->table( ['ID', 'Name', 'Function', 'Area (m²)', 'Floors', 'Status'], $sampleData ); } // Show summary $this->newLine(); $this->info("📈 SUMMARY:"); $this->info(" ✅ Can Process: {$canProcess}"); $this->info(" ❌ Cannot Process: {$cannotProcess}"); $this->info(" 🔄 Has Existing Proposals: {$hasExisting}"); if ($cannotProcess > 0) { $this->warn(" ⚠️ Common Issues: " . implode(', ', array_unique($errorReasons))); } $this->newLine(); } /** * Process all spatial plannings */ private function processAllSpatialPlannings($spatialPlannings, $force) { $this->info('🚀 Starting processing...'); $this->newLine(); $progressBar = $this->output->createProgressBar($spatialPlannings->count()); $progressBar->start(); $stats = [ 'processed' => 0, 'skipped' => 0, 'errors' => 0, 'total_proposals' => 0, 'total_amount' => 0 ]; $errors = []; foreach ($spatialPlannings as $spatial) { try { $result = $this->processSingleSpatialPlanning($spatial, $force); if ($result['success']) { $stats['processed']++; $stats['total_proposals'] += $result['proposals_count']; $stats['total_amount'] += $result['total_amount']; } elseif ($result['skipped']) { $stats['skipped']++; } else { $stats['errors']++; $errors[] = "ID {$spatial->id}: " . $result['error']; } } catch (Exception $e) { $stats['errors']++; $errors[] = "ID {$spatial->id}: " . $e->getMessage(); } $progressBar->advance(); } $progressBar->finish(); $this->newLine(2); // Show final results $this->showFinalResults($stats, $errors); } /** * Process single spatial planning */ private function processSingleSpatialPlanning(SpatialPlanning $spatial, $force) { // Check if already has proposals if (!$force && $spatial->retributionProposals()->exists()) { return ['success' => false, 'skipped' => true]; } // Detect building function $buildingFunction = $this->proposalService->detectBuildingFunction($spatial->getBuildingFunctionText()); if (!$buildingFunction) { return ['success' => false, 'skipped' => false, 'error' => 'Cannot detect building function from: ' . $spatial->getBuildingFunctionText()]; } // Get area $totalArea = $spatial->getCalculationArea(); if ($totalArea <= 0) { return ['success' => false, 'skipped' => false, 'error' => 'Area is zero or missing']; } // Get number of floors $numberOfFloors = max(1, $spatial->number_of_floors ?? 1); // Delete existing proposals if force mode if ($force) { $spatial->retributionProposals()->delete(); } $proposalsCount = 0; $totalAmount = 0; // Create single proposal for the spatial planning (not per floor to prevent duplicates) // Use the highest floor for IP ketinggian calculation (worst case scenario) $highestFloor = $numberOfFloors; $proposal = $this->proposalService->createProposalForSpatialPlanning( $spatial, $buildingFunction->id, $highestFloor, // Use highest floor for calculation $totalArea, // Use total area, not divided by floors $totalArea, "Auto-calculated from spatial planning data (floors: {$numberOfFloors})" ); if ($proposal) { $proposalsCount = 1; $totalAmount = $proposal->total_retribution_amount; } return [ 'success' => true, 'skipped' => false, 'proposals_count' => $proposalsCount, 'total_amount' => $totalAmount ]; } /** * Show final results */ private function showFinalResults($stats, $errors) { $this->info('🎉 PROCESSING COMPLETED!'); $this->newLine(); // Main statistics table $this->table( ['Metric', 'Count'], [ ['✅ Successfully Processed', $stats['processed']], ['⏭️ Skipped (Already Has Proposals)', $stats['skipped']], ['❌ Errors', $stats['errors']], ['📄 Total Proposals Created', $stats['total_proposals']], ['💰 Total Retribution Amount', 'Rp ' . number_format($stats['total_amount'], 2)], ] ); // Show success message if ($stats['processed'] > 0) { $this->newLine(); $this->info("🎊 SUCCESS!"); $this->info(" 📊 Created {$stats['total_proposals']} retribution proposals"); $this->info(" 💵 Total calculated amount: Rp " . number_format($stats['total_amount'], 2)); $this->info(" 📋 Processed {$stats['processed']} spatial plannings"); } // Show errors if any if ($stats['errors'] > 0) { $this->newLine(); $this->warn("⚠️ ERRORS ENCOUNTERED:"); foreach (array_slice($errors, 0, 10) as $error) { $this->warn(" • {$error}"); } if (count($errors) > 10) { $this->warn(" ... and " . (count($errors) - 10) . " more errors"); } } // Show next steps $this->newLine(); $this->info("📋 NEXT STEPS:"); $this->info(" • View proposals: php artisan spatial:check-constraints"); $this->info(" • Check database: SELECT COUNT(*) FROM retribution_proposals;"); $this->info(" • Access via API: GET /api/retribution-proposals"); } }