This commit is contained in:
arifal
2025-07-03 15:28:54 +07:00
parent a7f578ca3d
commit a1e302a56d
4 changed files with 134 additions and 15 deletions

View File

@@ -26,7 +26,7 @@ class AssignSpatialPlanningsToCalculation extends Command
* *
* @var string * @var string
*/ */
protected $description = 'Assign retribution calculations to spatial plannings (supports recalculate for existing calculations)'; protected $description = 'Assign retribution calculations to spatial plannings (recalculate mode applies 30% area adjustment)';
protected $calculatorService; protected $calculatorService;
@@ -57,6 +57,7 @@ class AssignSpatialPlanningsToCalculation extends Command
$q->where('is_active', true); $q->where('is_active', true);
}); });
$this->info('🔄 Recalculate mode: Processing spatial plannings with existing calculations'); $this->info('🔄 Recalculate mode: Processing spatial plannings with existing calculations');
$this->warn('⚠️ NOTE: Recalculate mode will apply 30% area adjustment to all calculations');
} elseif (!$force) { } elseif (!$force) {
// Normal mode: only process those without active calculations // Normal mode: only process those without active calculations
$query->whereDoesntHave('retributionCalculations', function ($q) { $query->whereDoesntHave('retributionCalculations', function ($q) {
@@ -140,6 +141,7 @@ class AssignSpatialPlanningsToCalculation extends Command
['Errors', $errors], ['Errors', $errors],
] ]
); );
$this->info('📊 Recalculate mode applied 30% area adjustment to all calculations');
} else { } else {
$this->table( $this->table(
['Metric', 'Count'], ['Metric', 'Count'],
@@ -189,7 +191,7 @@ class AssignSpatialPlanningsToCalculation extends Command
if ($recalculate) { if ($recalculate) {
// Recalculate mode: Always create new calculation // Recalculate mode: Always create new calculation
$calculationResult = $this->performCalculation($spatialPlanning, $buildingType); $calculationResult = $this->performCalculation($spatialPlanning, $buildingType, true);
// Check if spatial planning has existing active calculation // Check if spatial planning has existing active calculation
$currentActiveCalculation = $spatialPlanning->activeRetributionCalculation; $currentActiveCalculation = $spatialPlanning->activeRetributionCalculation;
@@ -211,9 +213,10 @@ class AssignSpatialPlanningsToCalculation extends Command
]); ]);
// Assign new calculation // Assign new calculation
$adjustedArea = round($buildingArea * 0.3, 2);
$spatialPlanning->assignRetributionCalculation( $spatialPlanning->assignRetributionCalculation(
$calculation, $calculation,
"Recalculated: Area {$oldArea}{$buildingArea}, Amount {$oldAmount}{$newAmount}" "Recalculated (30% area): Original area {$oldArea}m² → Adjusted area {$adjustedArea}, Amount {$oldAmount}{$newAmount}"
); );
$isRecalculated = true; $isRecalculated = true;
@@ -234,7 +237,7 @@ class AssignSpatialPlanningsToCalculation extends Command
$spatialPlanning->assignRetributionCalculation( $spatialPlanning->assignRetributionCalculation(
$calculation, $calculation,
'Recalculated (new calculation)' 'Recalculated (new calculation with 30% area adjustment)'
); );
} }
} else { } else {
@@ -255,7 +258,7 @@ class AssignSpatialPlanningsToCalculation extends Command
$reused = true; $reused = true;
} else { } else {
// Create new calculation // Create new calculation
$calculationResult = $this->performCalculation($spatialPlanning, $buildingType); $calculationResult = $this->performCalculation($spatialPlanning, $buildingType, false);
$calculation = RetributionCalculation::create([ $calculation = RetributionCalculation::create([
'building_type_id' => $buildingType->id, 'building_type_id' => $buildingType->id,
@@ -401,10 +404,18 @@ class AssignSpatialPlanningsToCalculation extends Command
/** /**
* Perform calculation using RetributionCalculatorService * Perform calculation using RetributionCalculatorService
*/ */
private function performCalculation(SpatialPlanning $spatialPlanning, BuildingType $buildingType): array private function performCalculation(SpatialPlanning $spatialPlanning, BuildingType $buildingType, bool $recalculate = false): array
{ {
// Round area to 2 decimal places to match database storage format // Round area to 2 decimal places to match database storage format
$buildingArea = round($spatialPlanning->getCalculationArea(), 2); $buildingArea = round($spatialPlanning->getCalculationArea(), 2);
// Apply 30% multiplication for recalculate mode
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%)");
}
$floorNumber = $spatialPlanning->number_of_floors ?: 1; $floorNumber = $spatialPlanning->number_of_floors ?: 1;
try { try {
@@ -429,6 +440,8 @@ class AssignSpatialPlanningsToCalculation extends Command
'height_index' => $result['input_parameters']['height_index'], 'height_index' => $result['input_parameters']['height_index'],
'infrastructure_factor' => $result['indices']['infrastructure_factor'], 'infrastructure_factor' => $result['indices']['infrastructure_factor'],
'building_area' => $buildingArea, 'building_area' => $buildingArea,
'original_building_area' => $recalculate ? round($spatialPlanning->getCalculationArea(), 2) : null,
'area_adjustment_factor' => $recalculate ? 0.3 : null,
'floor_number' => $floorNumber, 'floor_number' => $floorNumber,
'building_function' => $spatialPlanning->building_function, 'building_function' => $spatialPlanning->building_function,
'calculation_steps' => $result['calculation_detail'], 'calculation_steps' => $result['calculation_detail'],
@@ -436,6 +449,7 @@ class AssignSpatialPlanningsToCalculation extends Command
'is_free' => $buildingType->is_free, 'is_free' => $buildingType->is_free,
'calculation_date' => now()->toDateTimeString(), 'calculation_date' => now()->toDateTimeString(),
'total' => $result['total_retribution'], 'total' => $result['total_retribution'],
'is_recalculated' => $recalculate,
] ]
]; ];
@@ -446,6 +460,13 @@ class AssignSpatialPlanningsToCalculation extends Command
// Basic fallback calculation // Basic fallback calculation
$totalAmount = $buildingType->is_free ? 0 : ($buildingArea * 50000); $totalAmount = $buildingType->is_free ? 0 : ($buildingArea * 50000);
// Apply 30% multiplication for recalculate mode in fallback too
if ($recalculate) {
$originalAmount = $totalAmount;
$totalAmount = round($totalAmount * 0.3, 2);
$this->warn("Fallback recalculate: Original amount Rp{$originalAmount} → Adjusted amount Rp{$totalAmount} (30%)");
}
return [ return [
'amount' => $totalAmount, 'amount' => $totalAmount,
'detail' => [ 'detail' => [
@@ -453,6 +474,8 @@ class AssignSpatialPlanningsToCalculation extends Command
'building_type_name' => $buildingType->name, 'building_type_name' => $buildingType->name,
'building_type_code' => $buildingType->code, 'building_type_code' => $buildingType->code,
'building_area' => $buildingArea, 'building_area' => $buildingArea,
'original_building_area' => $recalculate ? round($spatialPlanning->getCalculationArea(), 2) : null,
'area_adjustment_factor' => $recalculate ? 0.3 : null,
'floor_number' => $floorNumber, 'floor_number' => $floorNumber,
'building_function' => $spatialPlanning->building_function, 'building_function' => $spatialPlanning->building_function,
'calculation_method' => 'fallback', 'calculation_method' => 'fallback',
@@ -460,6 +483,7 @@ class AssignSpatialPlanningsToCalculation extends Command
'is_free' => $buildingType->is_free, 'is_free' => $buildingType->is_free,
'calculation_date' => now()->toDateTimeString(), 'calculation_date' => now()->toDateTimeString(),
'total' => $totalAmount, 'total' => $totalAmount,
'is_recalculated' => $recalculate,
] ]
]; ];
} }

View File

@@ -20,7 +20,9 @@ class InjectSpatialPlanningsData extends Command
{--file=storage/app/public/templates/2025.xlsx : Path to Excel file} {--file=storage/app/public/templates/2025.xlsx : Path to Excel file}
{--sheet=0 : Sheet index to read from} {--sheet=0 : Sheet index to read from}
{--dry-run : Run without actually inserting data} {--dry-run : Run without actually inserting data}
{--debug : Show Excel content for debugging}'; {--debug : Show Excel content for debugging}
{--truncate : Clear existing data before import}
{--no-truncate : Skip truncation (keep existing data)}';
/** /**
* The console command description. * The console command description.
@@ -39,6 +41,8 @@ class InjectSpatialPlanningsData extends Command
$sheetIndex = (int) $this->option('sheet'); $sheetIndex = (int) $this->option('sheet');
$isDryRun = $this->option('dry-run'); $isDryRun = $this->option('dry-run');
$isDebug = $this->option('debug'); $isDebug = $this->option('debug');
$shouldTruncate = $this->option('truncate');
$noTruncate = $this->option('no-truncate');
if (!file_exists($filePath)) { if (!file_exists($filePath)) {
$this->error("File not found: {$filePath}"); $this->error("File not found: {$filePath}");
@@ -52,10 +56,86 @@ class InjectSpatialPlanningsData extends Command
$this->warn("DRY RUN MODE - No data will be inserted"); $this->warn("DRY RUN MODE - No data will be inserted");
} }
DB::statement('SET FOREIGN_KEY_CHECKS = 0'); // Check existing data
DB::table('spatial_plannings')->truncate(); $existingCount = DB::table('spatial_plannings')->count();
DB::statement('SET FOREIGN_KEY_CHECKS = 1'); if ($existingCount > 0) {
$this->info('Spatial plannings table truncated successfully.'); $this->info("Found {$existingCount} existing spatial planning records");
} else {
$this->info('No existing spatial planning data found');
}
// Handle truncation logic
$willTruncate = false;
if ($shouldTruncate) {
$willTruncate = true;
$this->info('Truncation requested via --truncate option');
} elseif ($noTruncate) {
$willTruncate = false;
$this->info('Truncation skipped via --no-truncate option');
} else {
// Default behavior: ask user if not in dry run mode
if (!$isDryRun) {
$willTruncate = $this->confirm('Do you want to clear existing spatial planning data before import?');
} else {
$willTruncate = false;
$this->info('DRY RUN MODE - Truncation will be skipped');
}
}
// Confirm truncation if not in dry run mode and truncation is requested
if ($willTruncate && !$isDryRun) {
if (!$this->confirm('This will delete all existing spatial planning data and related retribution calculations. Continue?')) {
$this->info('Operation cancelled.');
return 0;
}
}
// Truncate all related data properly
if ($willTruncate && !$isDryRun) {
$this->info('Truncating spatial planning data and related retribution calculations...');
try {
// Disable foreign key checks for safe truncation
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
// 1. Delete calculable retributions for spatial plannings (polymorphic relationship)
$deletedCalculableRetributions = DB::table('calculable_retributions')
->where('calculable_type', 'App\\Models\\SpatialPlanning')
->count();
if ($deletedCalculableRetributions > 0) {
DB::table('calculable_retributions')
->where('calculable_type', 'App\\Models\\SpatialPlanning')
->delete();
$this->info("Deleted {$deletedCalculableRetributions} calculable retributions for spatial plannings.");
}
// 2. Truncate spatial plannings table
DB::table('spatial_plannings')->truncate();
$this->info('Spatial plannings table truncated successfully.');
// Re-enable foreign key checks
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
$this->info('All spatial planning data and related retribution calculations cleared 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());
}
$this->error('Failed to truncate spatial planning data: ' . $e->getMessage());
return 1;
}
} elseif ($willTruncate && $isDryRun) {
$this->info('DRY RUN MODE - Would truncate spatial planning data and related retribution calculations');
} else {
$this->info('Keeping existing data (no truncation)');
}
$spreadsheet = IOFactory::load($filePath); $spreadsheet = IOFactory::load($filePath);
$worksheet = $spreadsheet->getSheet($sheetIndex); $worksheet = $spreadsheet->getSheet($sheetIndex);
@@ -97,8 +177,23 @@ class InjectSpatialPlanningsData extends Command
if (!$isDryRun) { if (!$isDryRun) {
$this->info("Successfully inserted {$totalInserted} spatial planning records"); $this->info("Successfully inserted {$totalInserted} spatial planning records");
// Show summary of what was done
$finalCount = DB::table('spatial_plannings')->count();
$this->info("Final spatial planning records count: {$finalCount}");
if ($willTruncate) {
$this->info("✅ Data import completed with truncation");
} else {
$this->info("✅ Data import completed (existing data preserved)");
}
} else { } else {
$this->info("Dry run completed. Total records that would be inserted: " . count($sections)); $this->info("Dry run completed. Total records that would be inserted: " . count($sections));
if ($willTruncate) {
$this->info("Would truncate existing data before import");
} else {
$this->info("Would preserve existing data during import");
}
} }
return 0; return 0;
@@ -574,8 +669,6 @@ class InjectSpatialPlanningsData extends Command
strpos($activitiesLower, 'perdaganagan') !== false || strpos($activitiesLower, 'perdaganagan') !== false ||
strpos($activitiesLower, 'waterpark') !== false || strpos($activitiesLower, 'waterpark') !== false ||
strpos($activitiesLower, 'pasar') !== false || strpos($activitiesLower, 'pasar') !== false ||
strpos($activitiesLower, 'perumahan') !== false ||
strpos($activitiesLower, 'perumhan') !== false ||
strpos($activitiesLower, 'kantor') !== false) { strpos($activitiesLower, 'kantor') !== false) {
$buildingFunction = 'Fungsi Usaha'; $buildingFunction = 'Fungsi Usaha';
@@ -601,6 +694,8 @@ class InjectSpatialPlanningsData extends Command
// Determine housing type based on area and keywords // Determine housing type based on area and keywords
if (strpos($activitiesLower, 'mbr') !== false || if (strpos($activitiesLower, 'mbr') !== false ||
strpos($activitiesLower, 'masyarakat berpenghasilan rendah') !== false || strpos($activitiesLower, 'masyarakat berpenghasilan rendah') !== false ||
strpos($activitiesLower, 'perumahan') !== false ||
strpos($activitiesLower, 'perumhan') !== false ||
strpos($activitiesLower, 'sederhana') !== false || strpos($activitiesLower, 'sederhana') !== false ||
($landArea > 0 && $landArea < 2000)) { // Small area indicates MBR ($landArea > 0 && $landArea < 2000)) { // Small area indicates MBR

View File

@@ -537,7 +537,7 @@ class ServiceGoogleSheet
foreach ($spatialPlannings as $spatialPlanning) { foreach ($spatialPlannings as $spatialPlanning) {
$activeCalculation = $spatialPlanning->activeRetributionCalculation; $activeCalculation = $spatialPlanning->activeRetributionCalculation;
if ($activeCalculation && $activeCalculation->retributionCalculation) { if ($activeCalculation && $activeCalculation->retributionCalculation) {
$totalSum += $activeCalculation->retributionCalculation->retribution_amount; $totalSum += $activeCalculation->retributionCalculation->retribution_amount * 0.3;
} }
} }

View File

@@ -29,7 +29,7 @@ class RetributionDataSeeder extends Seeder
['id' => 9, 'code' => 'USH_BESAR', 'name' => 'Usaha Besar (Non-Mikro)', 'parent_id' => 4, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], ['id' => 9, 'code' => 'USH_BESAR', 'name' => 'Usaha Besar (Non-Mikro)', 'parent_id' => 4, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
['id' => 10, 'code' => 'HUN_SEDH', 'name' => 'Hunian Sederhana <100', 'parent_id' => 5, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], ['id' => 10, 'code' => 'HUN_SEDH', 'name' => 'Hunian Sederhana <100', 'parent_id' => 5, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
['id' => 11, 'code' => 'HUN_TSEDH', 'name' => 'Hunian Tidak Sederhana >100', 'parent_id' => 5, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], ['id' => 11, 'code' => 'HUN_TSEDH', 'name' => 'Hunian Tidak Sederhana >100', 'parent_id' => 5, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
['id' => 12, 'code' => 'MBR', 'name' => 'Rumah Tinggal MBR', 'parent_id' => 5, 'level' => 2, 'is_free' => true, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()], ['id' => 12, 'code' => 'MBR', 'name' => 'Rumah Tinggal MBR', 'parent_id' => 5, 'level' => 2, 'is_free' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
]); ]);
// Seed Retribution Indices berdasarkan Excel (with coefficient moved here) // Seed Retribution Indices berdasarkan Excel (with coefficient moved here)