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
*/
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;
@@ -57,6 +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');
} elseif (!$force) {
// Normal mode: only process those without active calculations
$query->whereDoesntHave('retributionCalculations', function ($q) {
@@ -140,6 +141,7 @@ class AssignSpatialPlanningsToCalculation extends Command
['Errors', $errors],
]
);
$this->info('📊 Recalculate mode applied 30% area adjustment to all calculations');
} else {
$this->table(
['Metric', 'Count'],
@@ -189,7 +191,7 @@ class AssignSpatialPlanningsToCalculation extends Command
if ($recalculate) {
// 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
$currentActiveCalculation = $spatialPlanning->activeRetributionCalculation;
@@ -211,9 +213,10 @@ class AssignSpatialPlanningsToCalculation extends Command
]);
// Assign new calculation
$adjustedArea = round($buildingArea * 0.3, 2);
$spatialPlanning->assignRetributionCalculation(
$calculation,
"Recalculated: Area {$oldArea}{$buildingArea}, Amount {$oldAmount}{$newAmount}"
"Recalculated (30% area): Original area {$oldArea}m² → Adjusted area {$adjustedArea}, Amount {$oldAmount}{$newAmount}"
);
$isRecalculated = true;
@@ -234,7 +237,7 @@ class AssignSpatialPlanningsToCalculation extends Command
$spatialPlanning->assignRetributionCalculation(
$calculation,
'Recalculated (new calculation)'
'Recalculated (new calculation with 30% area adjustment)'
);
}
} else {
@@ -255,7 +258,7 @@ class AssignSpatialPlanningsToCalculation extends Command
$reused = true;
} else {
// Create new calculation
$calculationResult = $this->performCalculation($spatialPlanning, $buildingType);
$calculationResult = $this->performCalculation($spatialPlanning, $buildingType, false);
$calculation = RetributionCalculation::create([
'building_type_id' => $buildingType->id,
@@ -401,10 +404,18 @@ class AssignSpatialPlanningsToCalculation extends Command
/**
* 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
$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;
try {
@@ -429,6 +440,8 @@ 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'],
@@ -436,6 +449,7 @@ class AssignSpatialPlanningsToCalculation extends Command
'is_free' => $buildingType->is_free,
'calculation_date' => now()->toDateTimeString(),
'total' => $result['total_retribution'],
'is_recalculated' => $recalculate,
]
];
@@ -446,6 +460,13 @@ class AssignSpatialPlanningsToCalculation extends Command
// Basic fallback calculation
$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 [
'amount' => $totalAmount,
'detail' => [
@@ -453,6 +474,8 @@ 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',
@@ -460,6 +483,7 @@ class AssignSpatialPlanningsToCalculation extends Command
'is_free' => $buildingType->is_free,
'calculation_date' => now()->toDateTimeString(),
'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}
{--sheet=0 : Sheet index to read from}
{--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.
@@ -39,6 +41,8 @@ class InjectSpatialPlanningsData extends Command
$sheetIndex = (int) $this->option('sheet');
$isDryRun = $this->option('dry-run');
$isDebug = $this->option('debug');
$shouldTruncate = $this->option('truncate');
$noTruncate = $this->option('no-truncate');
if (!file_exists($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");
}
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
DB::table('spatial_plannings')->truncate();
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
$this->info('Spatial plannings table truncated successfully.');
// Check existing data
$existingCount = DB::table('spatial_plannings')->count();
if ($existingCount > 0) {
$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);
$worksheet = $spreadsheet->getSheet($sheetIndex);
@@ -97,8 +177,23 @@ class InjectSpatialPlanningsData extends Command
if (!$isDryRun) {
$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 {
$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;
@@ -574,8 +669,6 @@ class InjectSpatialPlanningsData extends Command
strpos($activitiesLower, 'perdaganagan') !== false ||
strpos($activitiesLower, 'waterpark') !== false ||
strpos($activitiesLower, 'pasar') !== false ||
strpos($activitiesLower, 'perumahan') !== false ||
strpos($activitiesLower, 'perumhan') !== false ||
strpos($activitiesLower, 'kantor') !== false) {
$buildingFunction = 'Fungsi Usaha';
@@ -601,6 +694,8 @@ class InjectSpatialPlanningsData extends Command
// 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

View File

@@ -537,7 +537,7 @@ class ServiceGoogleSheet
foreach ($spatialPlannings as $spatialPlanning) {
$activeCalculation = $spatialPlanning->activeRetributionCalculation;
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' => 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' => 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)