diff --git a/app/Console/Commands/AssignSpatialPlanningsToCalculation.php b/app/Console/Commands/AssignSpatialPlanningsToCalculation.php index 3214a63..4fafc83 100644 --- a/app/Console/Commands/AssignSpatialPlanningsToCalculation.php +++ b/app/Console/Commands/AssignSpatialPlanningsToCalculation.php @@ -26,7 +26,7 @@ class AssignSpatialPlanningsToCalculation extends Command * * @var string */ - protected $description = 'Assign retribution calculations to spatial plannings (recalculate mode applies 30% area adjustment)'; + protected $description = 'Assign retribution calculations to spatial plannings (recalculate mode recalculates with current values)'; protected $calculatorService; @@ -57,7 +57,7 @@ class AssignSpatialPlanningsToCalculation extends Command $q->where('is_active', true); }); $this->info('🔄 Recalculate mode: Processing spatial plannings with existing calculations'); - $this->warn('⚠️ NOTE: Recalculate mode will apply 30% area adjustment to all calculations'); + $this->warn('⚠️ NOTE: Recalculate mode will recalculate all existing calculations with current values'); } elseif (!$force) { // Normal mode: only process those without active calculations $query->whereDoesntHave('retributionCalculations', function ($q) { @@ -141,7 +141,7 @@ class AssignSpatialPlanningsToCalculation extends Command ['Errors', $errors], ] ); - $this->info('📊 Recalculate mode applied 30% area adjustment to all calculations'); + $this->info('📊 Recalculate mode recalculated all existing calculations with current values'); } else { $this->table( ['Metric', 'Count'], @@ -213,10 +213,9 @@ class AssignSpatialPlanningsToCalculation extends Command ]); // Assign new calculation - $adjustedArea = round($buildingArea * 0.3, 2); $spatialPlanning->assignRetributionCalculation( $calculation, - "Recalculated (30% area): Original area {$oldArea}m² → Adjusted area {$adjustedArea}m², Amount {$oldAmount}→{$newAmount}" + "Recalculated: Original area {$oldArea}m² → New area {$buildingArea}m², Amount {$oldAmount}→{$newAmount}" ); $isRecalculated = true; @@ -237,7 +236,7 @@ class AssignSpatialPlanningsToCalculation extends Command $spatialPlanning->assignRetributionCalculation( $calculation, - 'Recalculated (new calculation with 30% area adjustment)' + 'Recalculated (new calculation with current values)' ); } } else { @@ -409,11 +408,9 @@ class AssignSpatialPlanningsToCalculation extends Command // Round area to 2 decimal places to match database storage format $buildingArea = round($spatialPlanning->getCalculationArea(), 2); - // Apply 30% multiplication for recalculate mode + // For recalculate mode, use the current area without any adjustment if ($recalculate) { - $originalArea = $buildingArea; - $buildingArea = round($buildingArea * 0.3, 2); // 30% of original area - $this->info("Recalculate mode: Original area {$originalArea}m² → Adjusted area {$buildingArea}m² (30%)"); + $this->info("Recalculate mode: Using current area {$buildingArea}m²"); } $floorNumber = $spatialPlanning->number_of_floors ?: 1; @@ -440,8 +437,6 @@ class AssignSpatialPlanningsToCalculation extends Command 'height_index' => $result['input_parameters']['height_index'], 'infrastructure_factor' => $result['indices']['infrastructure_factor'], 'building_area' => $buildingArea, - 'original_building_area' => $recalculate ? round($spatialPlanning->getCalculationArea(), 2) : null, - 'area_adjustment_factor' => $recalculate ? 0.3 : null, 'floor_number' => $floorNumber, 'building_function' => $spatialPlanning->building_function, 'calculation_steps' => $result['calculation_detail'], @@ -460,11 +455,9 @@ class AssignSpatialPlanningsToCalculation extends Command // Basic fallback calculation $totalAmount = $buildingType->is_free ? 0 : ($buildingArea * 50000); - // Apply 30% multiplication for recalculate mode in fallback too + // For recalculate mode in fallback, use current amount without adjustment if ($recalculate) { - $originalAmount = $totalAmount; - $totalAmount = round($totalAmount * 0.3, 2); - $this->warn("Fallback recalculate: Original amount Rp{$originalAmount} → Adjusted amount Rp{$totalAmount} (30%)"); + $this->warn("Fallback recalculate: Using current amount Rp{$totalAmount}"); } return [ @@ -474,8 +467,6 @@ class AssignSpatialPlanningsToCalculation extends Command 'building_type_name' => $buildingType->name, 'building_type_code' => $buildingType->code, 'building_area' => $buildingArea, - 'original_building_area' => $recalculate ? round($spatialPlanning->getCalculationArea(), 2) : null, - 'area_adjustment_factor' => $recalculate ? 0.3 : null, 'floor_number' => $floorNumber, 'building_function' => $spatialPlanning->building_function, 'calculation_method' => 'fallback', diff --git a/app/Console/Commands/InjectSpatialPlanningsData.php b/app/Console/Commands/InjectSpatialPlanningsData.php index d0c73c6..04e2c5e 100644 --- a/app/Console/Commands/InjectSpatialPlanningsData.php +++ b/app/Console/Commands/InjectSpatialPlanningsData.php @@ -453,9 +453,9 @@ class InjectSpatialPlanningsData extends Command $location = $columnD; } - // Calculate area: total luas lahan dibagi persentase BCR + // Calculate area: total luas lahan dikali persentase BCR $calculatedArea = $landArea > 0 && $bcrPercentage > 0 ? - ($landArea / ($bcrPercentage / 100)) : 0; + round($landArea * ($bcrPercentage / 100), 2) : 0; return [ 'data_type' => $columnD, @@ -586,9 +586,9 @@ class InjectSpatialPlanningsData extends Command $noSKKL = !empty($noSKKLValues) ? implode('|', $noSKKLValues) : null; $noUKL = !empty($noUKLValues) ? implode('|', $noUKLValues) : null; - // Calculate area using BCR formula: land_area / (bcr_percentage / 100) + // Calculate area using BCR formula: land_area * (bcr_percentage / 100) $calculatedArea = $landArea > 0 && $bcrPercentage > 0 ? - ($landArea / ($bcrPercentage / 100)) : 0; + round($landArea * ($bcrPercentage / 100), 2) : 0; // Determine building_function and sub_building_function based on activities and applicant name $buildingFunction = 'Mixed Development'; // Default @@ -598,21 +598,8 @@ class InjectSpatialPlanningsData extends Command $applicantName = $section['applicant_name'] ?? ''; $isCompany = (strpos($applicantName, 'PT ') === 0 || strpos($applicantName, 'PT.') === 0); - // PRIORITY: PT Company validation - PT/PT. automatically classified as Fungsi Usaha - if ($isCompany) { - $buildingFunction = 'Fungsi Usaha'; - - // For PT companies: area-based classification - if ($landArea > 0 && $landArea < 500) { // < 500 m² for PT = Non-Mikro (since PT is already established business) - $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; - } elseif ($landArea >= 500) { // >= 500 m² for PT = Large Business - $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; - } else { - $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; // Default for PT - } - } - // For non-PT applicants, use activity-based classification - elseif (!empty($activities)) { + // Activity-based classification (priority over PT validation for specific activities) + if (!empty($activities)) { $activitiesLower = strtolower($activities); // 1. FUNGSI KEAGAMAAN @@ -629,7 +616,39 @@ class InjectSpatialPlanningsData extends Command $subBuildingFunction = 'Fungsi Keagamaan'; } - // 2. FUNGSI SOSIAL BUDAYA + // 2. FUNGSI HUNIAN (PERUMAHAN) - PRIORITY HIGHER THAN PT VALIDATION + elseif (strpos($activitiesLower, 'perumahan') !== false || + strpos($activitiesLower, 'perumhan') !== false || + strpos($activitiesLower, 'perum') !== false || + strpos($activitiesLower, 'rumah') !== false || + strpos($activitiesLower, 'hunian') !== false || + strpos($activitiesLower, 'residence') !== false || + strpos($activitiesLower, 'residential') !== false || + strpos($activitiesLower, 'housing') !== false || + strpos($activitiesLower, 'town') !== false) { + + $buildingFunction = 'Fungsi Hunian'; + + // Determine housing type based on area and keywords + if (strpos($activitiesLower, 'mbr') !== false || + strpos($activitiesLower, 'masyarakat berpenghasilan rendah') !== false || + strpos($activitiesLower, 'sederhana') !== false || + ($landArea > 0 && $landArea < 2000)) { // Small area indicates MBR + + $subBuildingFunction = 'Rumah Tinggal Deret (MBR) dan Rumah Tinggal Tunggal (MBR)'; + } + elseif ($landArea > 0 && $landArea < 100) { + $subBuildingFunction = 'Sederhana <100'; + } + elseif ($landArea > 0 && $landArea > 100) { + $subBuildingFunction = 'Tidak Sederhana >100'; + } + else { + $subBuildingFunction = 'Tidak Sederhana >100'; // Default for housing + } + } + + // 3. FUNGSI SOSIAL BUDAYA elseif (strpos($activitiesLower, 'sekolah') !== false || strpos($activitiesLower, 'rumah sakit') !== false || strpos($activitiesLower, 'puskesmas') !== false || @@ -648,7 +667,7 @@ class InjectSpatialPlanningsData extends Command $subBuildingFunction = 'Fungsi Sosial Budaya'; } - // 3. FUNGSI USAHA + // 4. FUNGSI USAHA elseif (strpos($activitiesLower, 'perdagangan') !== false || strpos($activitiesLower, 'dagang') !== false || strpos($activitiesLower, 'toko') !== false || @@ -681,37 +700,6 @@ class InjectSpatialPlanningsData extends Command } } - // 4. FUNGSI HUNIAN (PERUMAHAN) - elseif (strpos($activitiesLower, 'rumah') !== false || - strpos($activitiesLower, 'hunian') !== false || - strpos($activitiesLower, 'residence') !== false || - strpos($activitiesLower, 'residential') !== false || - strpos($activitiesLower, 'housing') !== false || - strpos($activitiesLower, 'town') !== false) { - - $buildingFunction = 'Fungsi Hunian'; - - // Determine housing type based on area and keywords - if (strpos($activitiesLower, 'mbr') !== false || - strpos($activitiesLower, 'masyarakat berpenghasilan rendah') !== false || - strpos($activitiesLower, 'perumahan') !== false || - strpos($activitiesLower, 'perumhan') !== false || - strpos($activitiesLower, 'sederhana') !== false || - ($landArea > 0 && $landArea < 2000)) { // Small area indicates MBR - - $subBuildingFunction = 'Rumah Tinggal Deret (MBR) dan Rumah Tinggal Tunggal (MBR)'; - } - elseif ($landArea > 0 && $landArea < 100) { - $subBuildingFunction = 'Sederhana <100'; - } - elseif ($landArea > 0 && $landArea > 100) { - $subBuildingFunction = 'Tidak Sederhana >100'; - } - else { - $subBuildingFunction = 'Fungsi Hunian'; // Default for housing - } - } - // 5. FUNGSI CAMPURAN elseif (strpos($activitiesLower, 'campuran') !== false || strpos($activitiesLower, 'mixed') !== false || @@ -727,6 +715,38 @@ class InjectSpatialPlanningsData extends Command $subBuildingFunction = 'Campuran Kecil'; } } + // If no specific activity detected, fall back to PT validation + else { + // PT Company validation - PT/PT. automatically classified as Fungsi Usaha + if ($isCompany) { + $buildingFunction = 'Fungsi Usaha'; + + // For PT companies: area-based classification + if ($landArea > 0 && $landArea < 500) { // < 500 m² for PT = Non-Mikro (since PT is already established business) + $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; + } elseif ($landArea >= 500) { // >= 500 m² for PT = Large Business + $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; + } else { + $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; // Default for PT + } + } + } + } + // If no activities, fall back to PT validation + else { + // PT Company validation - PT/PT. automatically classified as Fungsi Usaha + if ($isCompany) { + $buildingFunction = 'Fungsi Usaha'; + + // For PT companies: area-based classification + if ($landArea > 0 && $landArea < 500) { // < 500 m² for PT = Non-Mikro (since PT is already established business) + $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; + } elseif ($landArea >= 500) { // >= 500 m² for PT = Large Business + $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; + } else { + $subBuildingFunction = 'Usaha Besar (Non-Mikro)'; // Default for PT + } + } } return [ diff --git a/app/Console/Commands/TruncateSpatialPlanningData.php b/app/Console/Commands/TruncateSpatialPlanningData.php new file mode 100644 index 0000000..775297d --- /dev/null +++ b/app/Console/Commands/TruncateSpatialPlanningData.php @@ -0,0 +1,266 @@ +option('force'); + $backup = $this->option('backup'); + $dryRun = $this->option('dry-run'); + $onlyActive = $this->option('only-active'); + + if ($dryRun) { + $this->warn('DRY RUN MODE - No data will be truncated'); + } + + // Check existing data + $this->showDataStatistics(); + + // Confirm truncation if not in force mode + if (!$force && !$dryRun) { + if (!$this->confirm('This will permanently delete all spatial planning data and related calculations. Continue?')) { + $this->info('Operation cancelled.'); + return 0; + } + } + + // Create backup if requested + if ($backup && !$dryRun) { + $this->createBackup(); + } + + // Perform truncation + if ($dryRun) { + $this->performDryRun(); + } else { + $this->performTruncation($onlyActive); + } + + // Show final statistics + if (!$dryRun) { + $this->showDataStatistics('AFTER'); + } + + return 0; + } catch (Exception $e) { + $this->error("Error: " . $e->getMessage()); + Log::error("TruncateSpatialPlanningData failed", ['error' => $e->getMessage()]); + return 1; + } + } + + /** + * Show data statistics + */ + private function showDataStatistics(string $prefix = 'BEFORE'): void + { + $this->info("=== {$prefix} TRUNCATION ==="); + + $spatialCount = DB::table('spatial_plannings')->count(); + $calculableCount = DB::table('calculable_retributions')->count(); + $activeCalculableCount = DB::table('calculable_retributions')->where('is_active', true)->count(); + $calculationCount = DB::table('retribution_calculations')->count(); + + $this->table( + ['Table', 'Total Records', 'Active Records'], + [ + ['spatial_plannings', $spatialCount, '-'], + ['calculable_retributions', $calculableCount, $activeCalculableCount], + ['retribution_calculations', $calculationCount, '-'], + ] + ); + + // Show breakdown by building function + $buildingFunctionStats = DB::table('spatial_plannings') + ->select('building_function', DB::raw('count(*) as total')) + ->groupBy('building_function') + ->get(); + + if ($buildingFunctionStats->isNotEmpty()) { + $this->info('Building Function Breakdown:'); + foreach ($buildingFunctionStats as $stat) { + $this->line(" - {$stat->building_function}: {$stat->total} records"); + } + } + + $this->newLine(); + } + + /** + * Create backup of data + */ + private function createBackup(): void + { + $this->info('Creating backup...'); + + $timestamp = date('Y-m-d_H-i-s'); + $backupPath = storage_path("backups/spatial_planning_backup_{$timestamp}"); + + // Create backup directory + if (!file_exists($backupPath)) { + mkdir($backupPath, 0755, true); + } + + // Backup spatial plannings + $spatialData = DB::table('spatial_plannings')->get(); + file_put_contents( + "{$backupPath}/spatial_plannings.json", + $spatialData->toJson(JSON_PRETTY_PRINT) + ); + + // Backup calculable retributions + $calculableData = DB::table('calculable_retributions')->get(); + file_put_contents( + "{$backupPath}/calculable_retributions.json", + $calculableData->toJson(JSON_PRETTY_PRINT) + ); + + // Backup retribution calculations + $calculationData = DB::table('retribution_calculations')->get(); + file_put_contents( + "{$backupPath}/retribution_calculations.json", + $calculationData->toJson(JSON_PRETTY_PRINT) + ); + + $this->info("Backup created at: {$backupPath}"); + } + + /** + * Perform dry run + */ + private function performDryRun(): void + { + $this->info('DRY RUN - Would truncate the following:'); + + $spatialCount = DB::table('spatial_plannings')->count(); + $calculableCount = DB::table('calculable_retributions')->count(); + $calculationCount = DB::table('retribution_calculations')->count(); + + $this->line(" - spatial_plannings: {$spatialCount} records"); + $this->line(" - calculable_retributions: {$calculableCount} records"); + $this->line(" - retribution_calculations: {$calculationCount} records"); + + $this->warn('No actual data was truncated (dry run mode)'); + } + + /** + * Perform actual truncation + */ + private function performTruncation(bool $onlyActive = false): void + { + $this->info('Starting truncation...'); + + try { + // Disable foreign key checks for safe truncation + DB::statement('SET FOREIGN_KEY_CHECKS = 0'); + + if ($onlyActive) { + // Only truncate active calculations + $this->truncateActiveOnly(); + } else { + // Truncate all data + $this->truncateAllData(); + } + + // Re-enable foreign key checks + DB::statement('SET FOREIGN_KEY_CHECKS = 1'); + + $this->info('✅ Truncation completed successfully!'); + + } catch (Exception $e) { + // Make sure to re-enable foreign key checks even on error + try { + DB::statement('SET FOREIGN_KEY_CHECKS = 1'); + } catch (Exception $fkError) { + $this->error('Failed to re-enable foreign key checks: ' . $fkError->getMessage()); + } + + throw $e; + } + } + + /** + * Truncate only active calculations + */ + private function truncateActiveOnly(): void + { + $this->info('Truncating only active calculations...'); + + // Delete active calculable retributions + $deletedActive = DB::table('calculable_retributions') + ->where('is_active', true) + ->delete(); + $this->info("Deleted {$deletedActive} active calculable retributions"); + + // Delete orphaned retribution calculations + $deletedOrphaned = DB::table('retribution_calculations') + ->whereNotExists(function ($query) { + $query->select(DB::raw(1)) + ->from('calculable_retributions') + ->whereColumn('calculable_retributions.retribution_calculation_id', 'retribution_calculations.id'); + }) + ->delete(); + $this->info("Deleted {$deletedOrphaned} orphaned retribution calculations"); + + // Keep spatial plannings but remove their calculation relationships + $this->info('Spatial plannings data preserved (only calculations removed)'); + } + + /** + * Truncate all data + */ + private function truncateAllData(): void + { + $this->info('Truncating all data...'); + + // Get counts before truncation + $spatialCount = DB::table('spatial_plannings')->count(); + $calculableCount = DB::table('calculable_retributions')->count(); + $calculationCount = DB::table('retribution_calculations')->count(); + + // Truncate tables in correct order + DB::table('calculable_retributions')->truncate(); + $this->info("Truncated calculable_retributions table ({$calculableCount} records)"); + + DB::table('retribution_calculations')->truncate(); + $this->info("Truncated retribution_calculations table ({$calculationCount} records)"); + + DB::table('spatial_plannings')->truncate(); + $this->info("Truncated spatial_plannings table ({$spatialCount} records)"); + + // Reset auto increment + DB::statement('ALTER TABLE calculable_retributions AUTO_INCREMENT = 1'); + DB::statement('ALTER TABLE retribution_calculations AUTO_INCREMENT = 1'); + DB::statement('ALTER TABLE spatial_plannings AUTO_INCREMENT = 1'); + $this->info('Reset auto increment counters'); + } +} \ No newline at end of file diff --git a/app/Services/ServiceGoogleSheet.php b/app/Services/ServiceGoogleSheet.php index cf7b37d..12a8a38 100644 --- a/app/Services/ServiceGoogleSheet.php +++ b/app/Services/ServiceGoogleSheet.php @@ -537,7 +537,7 @@ class ServiceGoogleSheet foreach ($spatialPlannings as $spatialPlanning) { $activeCalculation = $spatialPlanning->activeRetributionCalculation; if ($activeCalculation && $activeCalculation->retributionCalculation) { - $totalSum += $activeCalculation->retributionCalculation->retribution_amount * 0.1; + $totalSum += $activeCalculation->retributionCalculation->retribution_amount; } } diff --git a/routes/console.php b/routes/console.php index f5bd364..331bd31 100644 --- a/routes/console.php +++ b/routes/console.php @@ -9,4 +9,5 @@ Artisan::command('inspire', function () { })->purpose('Display an inspiring quote')->hourly(); Schedule::command("app:scraping-leader-data")->dailyAt("00:00"); -Schedule::command("app:scraping-data")->dailyAt("00:30"); \ No newline at end of file +Schedule::command("app:scraping-data")->dailyAt("00:30"); +