294 lines
12 KiB
PHP
294 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\SpatialPlanning;
|
|
use App\Models\BuildingFunction;
|
|
use App\Models\RetributionProposal;
|
|
use App\Models\RetributionFormula;
|
|
use App\Models\FloorHeightIndex;
|
|
use App\Models\BuildingFunctionParameter;
|
|
use Carbon\Carbon;
|
|
|
|
class RetributionProposalService
|
|
{
|
|
/**
|
|
* Create retribution proposal for spatial planning
|
|
*/
|
|
public function createProposalForSpatialPlanning(
|
|
SpatialPlanning $spatialPlanning,
|
|
int $buildingFunctionId,
|
|
int $floorNumber,
|
|
float $floorArea,
|
|
float $totalBuildingArea = null,
|
|
string $notes = null
|
|
): RetributionProposal {
|
|
// Get building function and its parameters
|
|
$buildingFunction = BuildingFunction::with('parameter')->findOrFail($buildingFunctionId);
|
|
$parameters = $buildingFunction->parameter;
|
|
|
|
if (!$parameters) {
|
|
throw new \Exception("Building function parameters not found for ID: {$buildingFunctionId}");
|
|
}
|
|
|
|
// Get floor height index
|
|
$floorHeightIndex = FloorHeightIndex::where('floor_number', $floorNumber)->first();
|
|
if (!$floorHeightIndex) {
|
|
throw new \Exception("Floor height index not found for floor: {$floorNumber}");
|
|
}
|
|
|
|
// Get retribution formula
|
|
$retributionFormula = RetributionFormula::where('building_function_id', $buildingFunctionId)
|
|
->where('floor_number', $floorNumber)
|
|
->first();
|
|
|
|
if (!$retributionFormula) {
|
|
throw new \Exception("Retribution formula not found for building function ID: {$buildingFunctionId}, floor: {$floorNumber}");
|
|
}
|
|
|
|
// Calculate retribution using Excel formula
|
|
$calculationResult = $this->calculateRetribution(
|
|
$floorArea,
|
|
$parameters,
|
|
$floorHeightIndex->ip_ketinggian
|
|
);
|
|
|
|
// Create retribution proposal
|
|
return RetributionProposal::create([
|
|
'spatial_planning_id' => $spatialPlanning->id,
|
|
'building_function_id' => $buildingFunctionId,
|
|
'retribution_formula_id' => $retributionFormula->id,
|
|
'floor_number' => $floorNumber,
|
|
'floor_area' => $floorArea,
|
|
'total_building_area' => $totalBuildingArea ?? $floorArea,
|
|
'ip_ketinggian' => $floorHeightIndex->ip_ketinggian,
|
|
'floor_retribution_amount' => $calculationResult['total_retribution'],
|
|
'total_retribution_amount' => $calculationResult['total_retribution'],
|
|
'calculation_parameters' => $calculationResult['parameters'],
|
|
'calculation_breakdown' => $calculationResult['breakdown'],
|
|
'notes' => $notes,
|
|
'calculated_at' => Carbon::now()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Create proposal without spatial planning
|
|
*/
|
|
public function createStandaloneProposal(
|
|
int $buildingFunctionId,
|
|
int $floorNumber,
|
|
float $floorArea,
|
|
float $totalBuildingArea = null,
|
|
string $notes = null
|
|
): RetributionProposal {
|
|
// Get building function and its parameters
|
|
$buildingFunction = BuildingFunction::with('parameter')->findOrFail($buildingFunctionId);
|
|
$parameters = $buildingFunction->parameter;
|
|
|
|
if (!$parameters) {
|
|
throw new \Exception("Building function parameters not found for ID: {$buildingFunctionId}");
|
|
}
|
|
|
|
// Get floor height index
|
|
$floorHeightIndex = FloorHeightIndex::where('floor_number', $floorNumber)->first();
|
|
if (!$floorHeightIndex) {
|
|
throw new \Exception("Floor height index not found for floor: {$floorNumber}");
|
|
}
|
|
|
|
// Get retribution formula
|
|
$retributionFormula = RetributionFormula::where('building_function_id', $buildingFunctionId)
|
|
->where('floor_number', $floorNumber)
|
|
->first();
|
|
|
|
if (!$retributionFormula) {
|
|
throw new \Exception("Retribution formula not found for building function ID: {$buildingFunctionId}, floor: {$floorNumber}");
|
|
}
|
|
|
|
// Calculate retribution using Excel formula
|
|
$calculationResult = $this->calculateRetribution(
|
|
$floorArea,
|
|
$parameters,
|
|
$floorHeightIndex->ip_ketinggian
|
|
);
|
|
|
|
// Create retribution proposal
|
|
return RetributionProposal::create([
|
|
'spatial_planning_id' => null,
|
|
'building_function_id' => $buildingFunctionId,
|
|
'retribution_formula_id' => $retributionFormula->id,
|
|
'floor_number' => $floorNumber,
|
|
'floor_area' => $floorArea,
|
|
'total_building_area' => $totalBuildingArea ?? $floorArea,
|
|
'ip_ketinggian' => $floorHeightIndex->ip_ketinggian,
|
|
'floor_retribution_amount' => $calculationResult['total_retribution'],
|
|
'total_retribution_amount' => $calculationResult['total_retribution'],
|
|
'calculation_parameters' => $calculationResult['parameters'],
|
|
'calculation_breakdown' => $calculationResult['breakdown'],
|
|
'notes' => $notes,
|
|
'calculated_at' => Carbon::now()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Calculate retribution using Excel formula
|
|
*/
|
|
protected function calculateRetribution(
|
|
float $floorArea,
|
|
BuildingFunctionParameter $parameters,
|
|
float $ipKetinggian
|
|
): array {
|
|
// Excel formula parameters
|
|
$fungsi_bangunan = $parameters->fungsi_bangunan;
|
|
$ip_permanen = $parameters->ip_permanen;
|
|
$ip_kompleksitas = $parameters->ip_kompleksitas;
|
|
$indeks_lokalitas = $parameters->indeks_lokalitas;
|
|
$base_value = 70350;
|
|
$additional_factor = 0.5;
|
|
|
|
// Step 1: Calculate H13 (floor coefficient)
|
|
$h13 = $fungsi_bangunan * ($ip_permanen + $ip_kompleksitas + (0.5 * $ipKetinggian));
|
|
|
|
// Step 2: Main calculation
|
|
$main_calculation = 1 * $floorArea * ($indeks_lokalitas * $base_value * $h13 * 1);
|
|
|
|
// Step 3: Additional (50%)
|
|
$additional_calculation = $additional_factor * $main_calculation;
|
|
|
|
// Step 4: Total
|
|
$total_retribution = $main_calculation + $additional_calculation;
|
|
|
|
return [
|
|
'total_retribution' => $total_retribution,
|
|
'parameters' => [
|
|
'fungsi_bangunan' => $fungsi_bangunan,
|
|
'ip_permanen' => $ip_permanen,
|
|
'ip_kompleksitas' => $ip_kompleksitas,
|
|
'ip_ketinggian' => $ipKetinggian,
|
|
'indeks_lokalitas' => $indeks_lokalitas,
|
|
'base_value' => $base_value,
|
|
'additional_factor' => $additional_factor,
|
|
'floor_area' => $floorArea
|
|
],
|
|
'breakdown' => [
|
|
'h13_calculation' => [
|
|
'formula' => 'fungsi_bangunan * (ip_permanen + ip_kompleksitas + (0.5 * ip_ketinggian))',
|
|
'calculation' => "{$fungsi_bangunan} * ({$ip_permanen} + {$ip_kompleksitas} + (0.5 * {$ipKetinggian}))",
|
|
'result' => $h13
|
|
],
|
|
'main_calculation' => [
|
|
'formula' => '1 * floor_area * (indeks_lokalitas * base_value * h13 * 1)',
|
|
'calculation' => "1 * {$floorArea} * ({$indeks_lokalitas} * {$base_value} * {$h13} * 1)",
|
|
'result' => $main_calculation
|
|
],
|
|
'additional_calculation' => [
|
|
'formula' => 'additional_factor * main_calculation',
|
|
'calculation' => "{$additional_factor} * {$main_calculation}",
|
|
'result' => $additional_calculation
|
|
],
|
|
'total_calculation' => [
|
|
'formula' => 'main_calculation + additional_calculation',
|
|
'calculation' => "{$main_calculation} + {$additional_calculation}",
|
|
'result' => $total_retribution
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get proposal statistics
|
|
*/
|
|
public function getStatistics(): array
|
|
{
|
|
return [
|
|
'total_proposals' => RetributionProposal::count(),
|
|
'total_amount' => RetributionProposal::sum('total_retribution_amount'),
|
|
'average_amount' => RetributionProposal::avg('total_retribution_amount'),
|
|
'proposals_with_spatial_planning' => RetributionProposal::whereNotNull('spatial_planning_id')->count(),
|
|
'proposals_without_spatial_planning' => RetributionProposal::whereNull('spatial_planning_id')->count(),
|
|
'by_building_function' => RetributionProposal::with('buildingFunction')
|
|
->selectRaw('building_function_id, COUNT(*) as count, SUM(total_retribution_amount) as total_amount')
|
|
->groupBy('building_function_id')
|
|
->orderBy('total_amount', 'desc')
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'building_function_id' => $item->building_function_id,
|
|
'building_function_name' => $item->buildingFunction->name ?? 'Unknown',
|
|
'count' => $item->count,
|
|
'total_amount' => $item->total_amount,
|
|
'formatted_amount' => 'Rp ' . number_format($item->total_amount, 0, ',', '.')
|
|
];
|
|
})->toArray()
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Detect building function from text
|
|
*/
|
|
public function detectBuildingFunction(string $text): ?BuildingFunction
|
|
{
|
|
$text = strtolower($text);
|
|
|
|
// Detection patterns - order matters (more specific first)
|
|
$patterns = [
|
|
'HUNIAN_TIDAK_SEDERHANA' => [
|
|
'hunian mewah', 'villa', 'apartemen', 'kondominium', 'townhouse'
|
|
],
|
|
'HUNIAN_SEDERHANA' => [
|
|
'hunian sederhana', 'hunian', 'rumah', 'perumahan', 'residential', 'fungsi hunian'
|
|
],
|
|
'USAHA_BESAR' => [
|
|
'usaha besar', 'pabrik', 'industri', 'mall', 'hotel', 'restoran', 'supermarket',
|
|
'plaza', 'gedung perkantoran', 'non-mikro', 'non mikro'
|
|
],
|
|
'USAHA_KECIL' => [
|
|
'usaha kecil', 'umkm', 'warung', 'toko', 'mikro', 'kios'
|
|
],
|
|
'CAMPURAN_BESAR' => [
|
|
'campuran besar', 'mixed use besar'
|
|
],
|
|
'CAMPURAN_KECIL' => [
|
|
'campuran kecil', 'ruko', 'mixed use', 'campuran'
|
|
],
|
|
'SOSIAL_BUDAYA' => [
|
|
'sekolah', 'rumah sakit', 'masjid', 'gereja', 'puskesmas', 'klinik',
|
|
'universitas', 'perpustakaan', 'museum'
|
|
],
|
|
'AGAMA' => [
|
|
'tempat ibadah', 'masjid', 'mushola', 'gereja', 'pura', 'vihara', 'klenteng'
|
|
]
|
|
];
|
|
|
|
// Try to find exact matches first
|
|
foreach ($patterns as $functionCode => $keywords) {
|
|
foreach ($keywords as $keyword) {
|
|
if (strpos($text, $keyword) !== false) {
|
|
$buildingFunction = BuildingFunction::where('code', $functionCode)->first();
|
|
if ($buildingFunction) {
|
|
return $buildingFunction;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Debug: Log what we're trying to match
|
|
\Illuminate\Support\Facades\Log::info("Building function detection failed for text: '{$text}'");
|
|
|
|
// If no exact match, try to find by name similarity
|
|
$allFunctions = BuildingFunction::whereNotNull('parent_id')->get(); // Only child functions
|
|
|
|
foreach ($allFunctions as $function) {
|
|
$functionName = strtolower($function->name);
|
|
|
|
// Check if any word from the function name appears in the text
|
|
$words = explode(' ', $functionName);
|
|
foreach ($words as $word) {
|
|
if (strlen($word) > 3 && strpos($text, $word) !== false) {
|
|
return $function;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|