restructure retribution calculations table

This commit is contained in:
arifal hidayat
2025-06-18 22:53:44 +07:00
parent df70a47bd1
commit 4c3443c2d6
12 changed files with 1548 additions and 0 deletions

View File

@@ -0,0 +1,243 @@
<?php
namespace App\Services;
use App\Models\BuildingType;
use App\Models\HeightIndex;
use App\Models\RetributionConfig;
use App\Models\RetributionCalculation;
class RetributionCalculatorService
{
/**
* Calculate retribution for given parameters
*/
public function calculate(
int $buildingTypeId,
int $floorNumber,
float $buildingArea,
bool $saveResult = true
): array {
// Get building type with indices
$buildingType = BuildingType::with('indices')->findOrFail($buildingTypeId);
// Check if building type is free
if ($buildingType->isFree()) {
return $this->createFreeResult($buildingType, $floorNumber, $buildingArea, $saveResult);
}
// Get height index
$heightIndex = HeightIndex::getHeightIndexByFloor($floorNumber);
// Get configuration values
$baseValue = RetributionConfig::getValue('BASE_VALUE', 70350);
$infrastructureMultiplier = RetributionConfig::getValue('INFRASTRUCTURE_MULTIPLIER', 0.5);
$heightMultiplier = RetributionConfig::getValue('HEIGHT_MULTIPLIER', 0.5);
// Get indices
$indices = $buildingType->indices;
if (!$indices) {
throw new \Exception("Indices not found for building type: {$buildingType->name}");
}
// Calculate using Excel formula
$result = $this->executeCalculation(
$buildingType,
$indices,
$heightIndex,
$baseValue,
$infrastructureMultiplier,
$heightMultiplier,
$floorNumber,
$buildingArea
);
// Save result if requested
if ($saveResult) {
$calculation = RetributionCalculation::createCalculation(
$buildingTypeId,
$floorNumber,
$buildingArea,
$result['total_retribution'],
$result['calculation_detail']
);
$result['calculation_id'] = $calculation->calculation_id;
}
return $result;
}
/**
* Execute the main calculation logic
*/
protected function executeCalculation(
BuildingType $buildingType,
$indices,
float $heightIndex,
float $baseValue,
float $infrastructureMultiplier,
float $heightMultiplier,
int $floorNumber,
float $buildingArea
): array {
// Step 1: Calculate H5 coefficient (Excel formula: RUNDOWN(($E5*($F5+$G5+(0.5*H$3))),4))
// H5 = coefficient * (ip_permanent + ip_complexity + (height_multiplier * height_index))
$h5Raw = $indices->coefficient * (
$indices->ip_permanent +
$indices->ip_complexity +
($heightMultiplier * $heightIndex)
);
// Apply RUNDOWN (floor to 4 decimal places)
$h5 = floor($h5Raw * 10000) / 10000;
// Step 2: Main calculation (Excel: 1*D5*(N5*base_value*H5*1))
// Main = building_area * locality_index * base_value * h5
$mainCalculation = $buildingArea * $indices->locality_index * $baseValue * $h5;
// Step 3: Infrastructure calculation (Excel: O3*(1*D5*(N5*base_value*H5*1)))
// Additional = infrastructure_multiplier * main_calculation
$infrastructureCalculation = $infrastructureMultiplier * $mainCalculation;
// Step 4: Total retribution (Main + Infrastructure)
$totalRetribution = $mainCalculation + $infrastructureCalculation;
return [
'building_type' => [
'id' => $buildingType->id,
'code' => $buildingType->code,
'name' => $buildingType->name,
'is_free' => $buildingType->is_free
],
'input_parameters' => [
'building_area' => $buildingArea,
'floor_number' => $floorNumber,
'height_index' => $heightIndex,
'base_value' => $baseValue,
'infrastructure_multiplier' => $infrastructureMultiplier,
'height_multiplier' => $heightMultiplier
],
'indices' => [
'coefficient' => $indices->coefficient,
'ip_permanent' => $indices->ip_permanent,
'ip_complexity' => $indices->ip_complexity,
'locality_index' => $indices->locality_index,
'infrastructure_factor' => $indices->infrastructure_factor
],
'calculation_steps' => [
'h5_coefficient' => [
'formula' => 'RUNDOWN((coefficient * (ip_permanent + ip_complexity + (height_multiplier * height_index))), 4)',
'calculation' => "RUNDOWN(({$indices->coefficient} * ({$indices->ip_permanent} + {$indices->ip_complexity} + ({$heightMultiplier} * {$heightIndex}))), 4)",
'raw_result' => $h5Raw,
'result' => $h5
],
'main_calculation' => [
'formula' => 'building_area * locality_index * base_value * h5',
'calculation' => "{$buildingArea} * {$indices->locality_index} * {$baseValue} * {$h5}",
'result' => $mainCalculation
],
'infrastructure_calculation' => [
'formula' => 'infrastructure_multiplier * main_calculation',
'calculation' => "{$infrastructureMultiplier} * {$mainCalculation}",
'result' => $infrastructureCalculation
],
'total_calculation' => [
'formula' => 'main_calculation + infrastructure_calculation',
'calculation' => "{$mainCalculation} + {$infrastructureCalculation}",
'result' => $totalRetribution
]
],
'total_retribution' => $totalRetribution,
'formatted_amount' => 'Rp ' . number_format($totalRetribution, 2, ',', '.'),
'calculation_detail' => [
'h5_raw' => $h5Raw,
'h5' => $h5,
'main' => $mainCalculation,
'infrastructure' => $infrastructureCalculation,
'total' => $totalRetribution
]
];
}
/**
* Create result for free building types
*/
protected function createFreeResult(
BuildingType $buildingType,
int $floorNumber,
float $buildingArea,
bool $saveResult
): array {
$result = [
'building_type' => [
'id' => $buildingType->id,
'code' => $buildingType->code,
'name' => $buildingType->name,
'is_free' => true
],
'input_parameters' => [
'building_area' => $buildingArea,
'floor_number' => $floorNumber
],
'total_retribution' => 0.0,
'formatted_amount' => 'Rp 0 (Gratis)',
'calculation_detail' => [
'reason' => 'Building type is free of charge',
'total' => 0.0
]
];
if ($saveResult) {
$calculation = RetributionCalculation::createCalculation(
$buildingType->id,
$floorNumber,
$buildingArea,
0.0,
$result['calculation_detail']
);
$result['calculation_id'] = $calculation->calculation_id;
}
return $result;
}
/**
* Get calculation by ID
*/
public function getCalculationById(string $calculationId): ?RetributionCalculation
{
return RetributionCalculation::with('buildingType')
->where('calculation_id', $calculationId)
->first();
}
/**
* Get all available building types for calculation
*/
public function getAvailableBuildingTypes(): array
{
return BuildingType::with('indices')
->active()
->children() // Only child types can be used for calculation
->get()
->map(function ($type) {
return [
'id' => $type->id,
'code' => $type->code,
'name' => $type->name,
'is_free' => $type->is_free,
'has_indices' => $type->indices !== null,
'coefficient' => $type->indices ? $type->indices->coefficient : null
];
})
->toArray();
}
/**
* Get all available floor numbers
*/
public function getAvailableFloors(): array
{
return HeightIndex::getAvailableFloors();
}
}