create service count floor level and retributions

This commit is contained in:
arifal
2025-06-18 18:44:30 +07:00
parent df70a47bd1
commit 59cc102c5a
13 changed files with 1415 additions and 180 deletions

View File

@@ -2,208 +2,124 @@
namespace App\Console\Commands;
use App\Models\BuildingFunction;
use App\Models\FloorHeightIndex;
use App\Models\RetributionProposal;
use Illuminate\Console\Command;
use App\Services\DynamicRetributionCalculationService;
class TestExcelFormulaCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'test:excel-formula {--building-function=10} {--floor-area=100} {--floor-number=2}';
protected $signature = 'test:excel-formula';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Test Excel formula calculation with sample data';
protected $description = 'Test Excel formula implementation for PBG retribution calculation';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('🧮 Testing Excel Formula Calculation');
$service = new DynamicRetributionCalculationService();
$this->info('=== TESTING RUNDOWN 4 IMPLEMENTATION ===');
$this->info('Formula: =ROUNDDOWN(($E13*($F13+$G13+(0.5*I$3))),4)');
$this->newLine();
// Get parameters
$buildingFunctionId = $this->option('building-function');
$floorArea = (float) $this->option('floor-area');
$floorNumber = (int) $this->option('floor-number');
// Get building function and parameters
$buildingFunction = BuildingFunction::with('parameter')->find($buildingFunctionId);
if (!$buildingFunction || !$buildingFunction->parameter) {
$this->error("Building function with ID {$buildingFunctionId} not found or has no parameters.");
return 1;
}
// Get IP ketinggian for floor
$floorHeightIndex = FloorHeightIndex::where('floor_number', $floorNumber)->first();
$ipKetinggian = $floorHeightIndex ? $floorHeightIndex->ip_ketinggian : 1.0;
// Get parameters
$parameters = $buildingFunction->parameter->getParametersArray();
$this->info("Test Parameters:");
$this->table(
['Parameter', 'Excel Cell', 'Value'],
[
['Building Function', 'Building Function', $buildingFunction->name],
['Floor Area (D13)', 'D13', $floorArea . ' m²'],
['Fungsi Bangunan (E13)', 'E13', $parameters['fungsi_bangunan']],
['IP Permanen (F13)', 'F13', $parameters['ip_permanen']],
['IP Kompleksitas (G13)', 'G13', $parameters['ip_kompleksitas']],
['IP Ketinggian (H$3)', 'H$3', $ipKetinggian],
['Indeks Lokalitas (N13)', 'N13', $parameters['indeks_lokalitas']],
['Base Value', '70350', '70,350'],
['Additional Factor ($O$3)', '$O$3', '0.5 (50%)']
]
);
$this->newLine();
// Calculate manually using the Excel formula
$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;
// Create breakdown array
$breakdown = [
'calculation_steps' => [
'step_1_h13' => [
'formula' => 'fungsi_bangunan * (ip_permanen + ip_kompleksitas + (0.5 * ip_ketinggian))',
'calculation' => "{$fungsi_bangunan} * ({$ip_permanen} + {$ip_kompleksitas} + (0.5 * {$ipKetinggian}))",
'result' => $h13
],
'step_2_main' => [
'formula' => '(1*D13*(N13*70350*H13*1))',
'calculation' => "1 * {$floorArea} * ({$indeks_lokalitas} * {$base_value} * {$h13} * 1)",
'result' => $main_calculation
],
'step_3_additional' => [
'formula' => '($O$3*(1*D13*(N13*70350*H13*1)))',
'calculation' => "{$additional_factor} * {$main_calculation}",
'result' => $additional_calculation
],
'step_4_total' => [
'formula' => 'main + additional',
'calculation' => "{$main_calculation} + {$additional_calculation}",
'result' => $total_retribution
]
],
'formatted_results' => [
'H13' => number_format($h13, 6),
'main_calculation' => 'Rp ' . number_format($main_calculation, 2),
'additional_calculation' => 'Rp ' . number_format($additional_calculation, 2),
'total_result' => 'Rp ' . number_format($total_retribution, 2)
]
];
$this->info("📊 Calculation Breakdown:");
$this->newLine();
// Show each step
foreach ($breakdown['calculation_steps'] as $stepName => $step) {
$this->info("🔸 " . strtoupper(str_replace('_', ' ', $stepName)));
$this->line(" Formula: " . $step['formula']);
$this->line(" Calculation: " . $step['calculation']);
$this->line(" Result: " . (is_numeric($step['result']) ? number_format($step['result'], 6) : $step['result']));
$this->newLine();
}
$this->info("💰 Final Results:");
$this->table(
['Component', 'Value'],
[
['H13 (Floor Coefficient)', $breakdown['formatted_results']['H13']],
['Main Calculation', $breakdown['formatted_results']['main_calculation']],
['Additional (50%)', $breakdown['formatted_results']['additional_calculation']],
['Total Retribution', $breakdown['formatted_results']['total_result']]
]
);
$this->newLine();
$this->info("🔍 Excel Formula Verification:");
$this->line("Main Formula: =(1*D13*(N13*70350*H13*1))+(\$O\$3*(1*D13*(N13*70350*H13*1)))");
$this->line("H13 Formula: =(\$E13*(\$F13+\$G13+(0.5*H\$3)))");
// Test with different floor numbers
if ($this->confirm('Test with different floor numbers?')) {
$this->testMultipleFloors($buildingFunction, $floorArea, $parameters);
}
return 0;
}
/**
* Test calculation with multiple floor numbers
*/
protected function testMultipleFloors(BuildingFunction $buildingFunction, float $floorArea, array $parameters)
{
$this->newLine();
$this->info("🏢 Testing Multiple Floors:");
// Test 1: Hunian Sederhana
$this->info('1. HUNIAN SEDERHANA (1 Lantai, 100 m²)');
$tableData = [];
$totalRetribution = 0;
for ($floor = 1; $floor <= 5; $floor++) {
$floorHeightIndex = FloorHeightIndex::where('floor_number', $floor)->first();
$ipKetinggian = $floorHeightIndex ? $floorHeightIndex->ip_ketinggian : 1.0;
// Calculate using Excel formula
$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;
// Calculate H13 and result
$H13 = $fungsi_bangunan * ($ip_permanen + $ip_kompleksitas + (0.5 * $ipKetinggian));
$main_calc = 1 * $floorArea * ($indeks_lokalitas * $base_value * $H13 * 1);
$additional_calc = $additional_factor * $main_calc;
$result = $main_calc + $additional_calc;
$totalRetribution += $result;
$tableData[] = [
"L{$floor}",
$ipKetinggian,
number_format($H13, 6),
'Rp ' . number_format($result, 2)
];
}
$this->table(
['Floor', 'IP Ketinggian', 'H13 Value', 'Retribution Amount'],
$tableData
$result1 = $service->calculateRetribution(
buildingFunctionId: 10, // HUNIAN_SEDERHANA
floorLevel: 1,
luasBangunan: 100,
indeksLokasi: 'sedang',
includeInfrastructure: true
);
if ($result1['success']) {
$rundown4 = $result1['calculation_breakdown']['rundown_4_calculation'];
$retribution = $result1['calculation_breakdown']['retribution_calculation'];
$this->info(' ✓ Before ROUNDDOWN: ' . $rundown4['before_rounddown']);
$this->info(' ✓ After ROUNDDOWN(4): ' . $rundown4['after_rounddown']);
$this->info(' ✓ Basic Retribution: Rp ' . number_format($retribution['basic_amount']));
$this->info(' ✓ Infrastructure: Rp ' . number_format($retribution['infrastructure_amount']));
$this->info(' 💰 TOTAL: Rp ' . number_format($retribution['total_amount']));
} else {
$this->error(' ❌ Error: ' . $result1['error']);
}
$this->newLine();
$this->info("🏗️ Total Building Retribution (5 floors): Rp " . number_format($totalRetribution, 2));
$this->info("📏 Total Building Area: " . number_format($floorArea * 5, 2) . "");
$this->info("💡 Average per m²: Rp " . number_format($totalRetribution / ($floorArea * 5), 2));
// Test 2: Usaha Besar
$this->info('2. USAHA BESAR (3 Lantai, 200 m²)');
$result2 = $service->calculateRetribution(
buildingFunctionId: 9, // USAHA_BESAR
floorLevel: 3,
luasBangunan: 200,
indeksLokasi: 'tinggi',
includeInfrastructure: true
);
if ($result2['success']) {
$rundown4 = $result2['calculation_breakdown']['rundown_4_calculation'];
$retribution = $result2['calculation_breakdown']['retribution_calculation'];
$this->info(' ✓ Rundown 4 Result: ' . $rundown4['after_rounddown']);
$this->info(' ✓ Basic: Rp ' . number_format($retribution['basic_amount']));
$this->info(' ✓ Infrastructure: Rp ' . number_format($retribution['infrastructure_amount']));
$this->info(' 💰 TOTAL: Rp ' . number_format($retribution['total_amount']));
}
$this->newLine();
// Test 3: Keagamaan (Free)
$this->info('3. KEAGAMAAN (2 Lantai, 150 m²) - BEBAS RETRIBUSI');
$result3 = $service->calculateRetribution(
buildingFunctionId: 1, // KEAGAMAAN
floorLevel: 2,
luasBangunan: 150,
indeksLokasi: 'sedang',
includeInfrastructure: true
);
if ($result3['success']) {
$this->info(' ✓ Rundown 4 Result: ' . $result3['results']['h5_rundown4']);
$this->info(' 💸 RETRIBUSI BEBAS: Rp ' . number_format($result3['results']['total_retribution']));
}
$this->newLine();
// Test 4: Multi-Floor Calculation
$this->info('4. CAMPURAN BESAR - Multi Lantai (1-4 Lantai, 300 m²)');
$result4 = $service->calculateMultiFloorRetribution(
buildingFunctionId: 7, // CAMPURAN_BESAR
floors: [1, 2, 3, 4],
luasBangunan: 300,
indeksLokasi: 'tinggi'
);
if ($result4['success']) {
$this->info(' ✓ Per Floor Calculations:');
foreach ($result4['floor_details'] as $floor => $detail) {
$this->info(" Lantai {$floor}: H5={$detail['h5_rundown4']}, Total=Rp " . number_format($detail['total_retribution']));
}
$this->info(' 💰 TOTAL SEMUA LANTAI: Rp ' . number_format($result4['total_retribution']));
}
$this->newLine();
$this->info('=== RUNDOWN 4 VERIFICATION ===');
$this->info('✅ Formula: =ROUNDDOWN(($E13*($F13+$G13+(0.5*I$3))),4)');
$this->info('✅ ROUNDDOWN function with 4 decimal precision');
$this->info('✅ Tarif Dasar: Rp 7.035.000');
$this->info('✅ Infrastructure calculation: O3 * basic');
$this->info('✅ Code simplified - unused formulas removed');
$this->newLine();
$this->info('🎉 RUNDOWN 4 Implementation is CLEAN and VERIFIED!');
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class BuildingFunctionFormulaConfig extends Model
{
use HasFactory;
protected $fillable = [
'building_function_id',
'formula_id',
'floor_level',
'min_floor',
'max_floor',
'multiplier',
'is_active',
'priority',
'notes',
];
protected $casts = [
'multiplier' => 'decimal:4',
'is_active' => 'boolean',
'min_floor' => 'integer',
'max_floor' => 'integer',
'floor_level' => 'integer',
'priority' => 'integer',
];
/**
* Relationship to BuildingFunction
*/
public function buildingFunction()
{
return $this->belongsTo(BuildingFunction::class, 'building_function_id');
}
/**
* Relationship to MasterFormula
*/
public function formula()
{
return $this->belongsTo(MasterFormula::class, 'formula_id');
}
/**
* Scope untuk konfigurasi aktif
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* Scope untuk mencari berdasarkan building function dan floor level
*/
public function scopeForBuildingAndFloor($query, $buildingFunctionId, $floorLevel)
{
return $query->where('building_function_id', $buildingFunctionId)
->where(function($q) use ($floorLevel) {
$q->where('floor_level', $floorLevel)
->orWhere('floor_level', 0) // 0 = berlaku untuk semua lantai
->orWhere(function($qq) use ($floorLevel) {
$qq->whereNotNull('min_floor')
->whereNotNull('max_floor')
->where('min_floor', '<=', $floorLevel)
->where('max_floor', '>=', $floorLevel);
});
})
->orderBy('priority', 'desc')
->orderBy('floor_level', 'desc'); // Prioritas: spesifik floor > range > semua lantai
}
/**
* Method untuk mendapatkan formula yang tepat untuk building function dan floor tertentu
*/
public static function getFormulaForBuildingAndFloor($buildingFunctionId, $floorLevel)
{
return self::active()
->forBuildingAndFloor($buildingFunctionId, $floorLevel)
->with('formula')
->first();
}
/**
* Method untuk check apakah floor level masuk dalam range
*/
public function isFloorInRange($floorLevel)
{
// Jika floor_level = 0, berlaku untuk semua lantai
if ($this->floor_level == 0) {
return true;
}
// Jika ada specific floor level
if ($this->floor_level == $floorLevel) {
return true;
}
// Jika ada range min-max
if (!is_null($this->min_floor) && !is_null($this->max_floor)) {
return $floorLevel >= $this->min_floor && $floorLevel <= $this->max_floor;
}
return false;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class FormulaParameter extends Model
{
use HasFactory;
protected $fillable = [
'formula_id',
'parameter_id',
];
/**
* Relationship to MasterFormula
*/
public function formula()
{
return $this->belongsTo(MasterFormula::class, 'formula_id');
}
/**
* Relationship to MasterParameter
*/
public function parameter()
{
return $this->belongsTo(MasterParameter::class, 'parameter_id');
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class MasterFormula extends Model
{
use HasFactory;
protected $fillable = [
'formula_code',
'formula_name',
'formula_expression',
'formula_category',
'description',
'usage_notes',
];
/**
* Relationship to FormulaParameter (Many-to-Many dengan MasterParameter)
*/
public function formulaParameters()
{
return $this->hasMany(FormulaParameter::class, 'formula_id');
}
/**
* Relationship to MasterParameter through FormulaParameter
*/
public function parameters()
{
return $this->belongsToMany(MasterParameter::class, 'formula_parameters', 'formula_id', 'parameter_id');
}
/**
* Relationship to BuildingFunctionFormulaConfig
*/
public function buildingConfigs()
{
return $this->hasMany(BuildingFunctionFormulaConfig::class, 'formula_id');
}
/**
* Scope untuk mencari berdasarkan kategori
*/
public function scopeByCategory($query, $category)
{
return $query->where('formula_category', $category);
}
/**
* Method untuk mendapatkan parameter yang diperlukan formula ini
*/
public function getRequiredParameters()
{
return $this->parameters()->get();
}
/**
* Method untuk evaluate formula dengan parameter values
*/
public function evaluateFormula($parameterValues = [])
{
$expression = $this->formula_expression;
// Replace parameter codes dengan nilai actual
foreach ($parameterValues as $parameterCode => $value) {
$expression = str_replace('{' . $parameterCode . '}', $value, $expression);
}
// Evaluasi mathematical expression (hati-hati dengan security!)
// Untuk production, gunakan library yang aman seperti SymfonyExpressionLanguage
try {
return eval("return $expression;");
} catch (Exception $e) {
throw new \Exception("Error evaluating formula: " . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class MasterParameter extends Model
{
use HasFactory;
protected $fillable = [
'parameter_code',
'parameter_name',
'default_value',
'unit',
'description',
];
protected $casts = [
'default_value' => 'decimal:6',
];
/**
* Relationship to FormulaParameter
*/
public function formulaParameters()
{
return $this->hasMany(FormulaParameter::class, 'parameter_id');
}
/**
* Relationship to MasterFormula through FormulaParameter
*/
public function formulas()
{
return $this->belongsToMany(MasterFormula::class, 'formula_parameters', 'parameter_id', 'formula_id');
}
/**
* Scope untuk mencari berdasarkan parameter code
*/
public function scopeByCode($query, $code)
{
return $query->where('parameter_code', $code);
}
/**
* Method untuk mendapatkan nilai parameter dengan fallback ke default
*/
public function getValue($customValue = null)
{
return $customValue !== null ? $customValue : $this->default_value;
}
}

View File

@@ -0,0 +1,180 @@
<?php
namespace App\Services;
use App\Models\BuildingFunction;
use App\Models\MasterParameter;
/**
* Retribution Calculation Service - Rundown 4 Implementation
*
* Implementasi rumus Excel Rundown 4:
* =ROUNDDOWN(($E13*($F13+$G13+(0.5*I$3))),4)
* Retribusi = (1*D5*(N5*7035000*H5*1))+($O$3*(1*D5*(N5*7035000*H5*1)))
*/
class DynamicRetributionCalculationService
{
/**
* Calculate retribution using Rundown 4 formula
*/
public function calculateRetribution($buildingFunctionId, $floorLevel, $luasBangunan, $indeksLokasi = 'sedang', $includeInfrastructure = true)
{
try {
// Get parameters for this building function
$parameters = $this->getParameterValues($buildingFunctionId, $floorLevel, $indeksLokasi);
// Step 1: Calculate Rundown 4 per floor value
$h5 = $this->calculateRundown4($parameters);
// Step 2: Calculate basic retribution
$basicRetribution = $parameters['koefisien_dasar'] * $luasBangunan *
($parameters['indeks_lokalitas'] * $parameters['tarif_dasar'] * $h5 * $parameters['koefisien_dasar']);
// Step 3: Calculate infrastructure (optional)
$infrastructureAmount = 0;
$totalRetribution = $basicRetribution;
if ($includeInfrastructure) {
$infrastructureAmount = $parameters['asumsi_prasarana'] * $basicRetribution;
$totalRetribution = $basicRetribution + $infrastructureAmount;
}
return [
'success' => true,
'building_function_id' => $buildingFunctionId,
'floor_level' => $floorLevel,
'luas_bangunan' => $luasBangunan,
'indeks_lokalitas_type' => $indeksLokasi,
'calculation_breakdown' => [
'rundown_4_calculation' => [
'excel_formula' => '=ROUNDDOWN(($E13*($F13+$G13+(0.5*I$3))),4)',
'fungsi_bangunan' => $parameters['fungsi'],
'ip_permanen' => $parameters['ip_permanen'],
'ip_kompleksitas' => $parameters['ip_kompleksitas'],
'multiplier_ketinggian' => $parameters['multiplier_ketinggian'],
'ip_ketinggian' => $parameters['ip_ketinggian'],
'before_rounddown' => $parameters['fungsi'] * ($parameters['ip_permanen'] + $parameters['ip_kompleksitas'] + ($parameters['multiplier_ketinggian'] * $parameters['ip_ketinggian'])),
'after_rounddown' => $h5,
'precision' => 4
],
'retribution_calculation' => [
'basic_formula' => '1 * D5 * (N5 * 7035000 * H5 * 1)',
'infrastructure_formula' => 'O3 * basic_retribution',
'basic_amount' => $basicRetribution,
'infrastructure_amount' => $infrastructureAmount,
'total_amount' => $totalRetribution
]
],
'results' => [
'h5_rundown4' => $h5,
'basic_retribution' => $basicRetribution,
'infrastructure_amount' => $infrastructureAmount,
'total_retribution' => $totalRetribution
]
];
} catch (\Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'building_function_id' => $buildingFunctionId,
'floor_level' => $floorLevel
];
}
}
/**
* Calculate multiple floors retribution
*/
public function calculateMultiFloorRetribution($buildingFunctionId, array $floors, $luasBangunan, $indeksLokasi = 'sedang')
{
$floorDetails = [];
$totalRetribution = 0;
foreach ($floors as $floor) {
$result = $this->calculateRetribution($buildingFunctionId, $floor, $luasBangunan, $indeksLokasi, true);
if ($result['success']) {
$floorDetails[$floor] = $result['results'];
$totalRetribution += $result['results']['total_retribution'];
}
}
return [
'success' => true,
'building_function_id' => $buildingFunctionId,
'floors' => $floors,
'luas_bangunan' => $luasBangunan,
'indeks_lokalitas_type' => $indeksLokasi,
'floor_details' => $floorDetails,
'total_retribution' => $totalRetribution,
'average_per_floor' => count($floors) > 0 ? $totalRetribution / count($floors) : 0
];
}
/**
* Calculate Rundown 4 per-floor value with ROUNDDOWN
* Formula: =ROUNDDOWN(($E13*($F13+$G13+(0.5*I$3))),4)
*/
private function calculateRundown4($parameters)
{
$calculation = $parameters['fungsi'] *
($parameters['ip_permanen'] + $parameters['ip_kompleksitas'] +
($parameters['multiplier_ketinggian'] * $parameters['ip_ketinggian']));
// Apply ROUNDDOWN with 4 decimal places
return floor($calculation * 10000) / 10000;
}
/**
* Get parameter values for calculation
*/
private function getParameterValues($buildingFunctionId, $floorLevel, $indeksLokasi)
{
$buildingFunction = BuildingFunction::find($buildingFunctionId);
if (!$buildingFunction) {
throw new \Exception("Building function not found: {$buildingFunctionId}");
}
$functionCode = strtolower($buildingFunction->code);
$lokalitasMapping = [
'rendah' => 'indeks_lokalitas_rendah',
'sedang' => 'indeks_lokalitas_sedang',
'tinggi' => 'indeks_lokalitas_tinggi'
];
$lokalitasParamCode = $lokalitasMapping[$indeksLokasi] ?? 'indeks_lokalitas_sedang';
$parameterCodes = [
'tarif_dasar',
'koefisien_dasar',
'multiplier_ketinggian',
'asumsi_prasarana',
$lokalitasParamCode,
"ip_ketinggian_{$floorLevel}",
"fungsi_{$functionCode}",
"ip_permanen_{$functionCode}",
"ip_kompleksitas_{$functionCode}"
];
$parameters = MasterParameter::whereIn('parameter_code', $parameterCodes)->get();
$values = [];
foreach ($parameters as $param) {
$values[$param->parameter_code] = $param->default_value;
}
return [
'tarif_dasar' => $values['tarif_dasar'] ?? 7035000,
'koefisien_dasar' => $values['koefisien_dasar'] ?? 1,
'multiplier_ketinggian' => $values['multiplier_ketinggian'] ?? 0.5,
'asumsi_prasarana' => $values['asumsi_prasarana'] ?? 0.5,
'indeks_lokalitas' => $values[$lokalitasParamCode] ?? 0.004,
'ip_ketinggian' => $values["ip_ketinggian_{$floorLevel}"] ?? 1.0,
'fungsi' => $values["fungsi_{$functionCode}"] ?? 0,
'ip_permanen' => $values["ip_permanen_{$functionCode}"] ?? 0,
'ip_kompleksitas' => $values["ip_kompleksitas_{$functionCode}"] ?? 0
];
}
}