restructure retribution calculations table
This commit is contained in:
263
app/Console/Commands/TestRetributionCalculation.php
Normal file
263
app/Console/Commands/TestRetributionCalculation.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\RetributionCalculatorService;
|
||||
use App\Models\BuildingType;
|
||||
|
||||
class TestRetributionCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'retribution:test
|
||||
{--area= : Luas bangunan dalam m2}
|
||||
{--floor= : Jumlah lantai (1-6)}
|
||||
{--type= : ID atau kode building type}
|
||||
{--all : Test semua building types}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Test perhitungan retribusi PBG dengan input luas bangunan dan tinggi lantai';
|
||||
|
||||
protected $calculatorService;
|
||||
|
||||
public function __construct(RetributionCalculatorService $calculatorService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->calculatorService = $calculatorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🏢 SISTEM TEST PERHITUNGAN RETRIBUSI PBG');
|
||||
$this->info('=' . str_repeat('=', 50));
|
||||
|
||||
// Test all building types if --all flag is used
|
||||
if ($this->option('all')) {
|
||||
return $this->testAllBuildingTypes();
|
||||
}
|
||||
|
||||
// Get input parameters
|
||||
$area = $this->getArea();
|
||||
$floor = $this->getFloor();
|
||||
$buildingTypeId = $this->getBuildingType();
|
||||
|
||||
if (!$area || !$floor || !$buildingTypeId) {
|
||||
$this->error('❌ Parameter tidak lengkap!');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Perform calculation
|
||||
$this->performCalculation($buildingTypeId, $floor, $area);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function getArea()
|
||||
{
|
||||
$area = $this->option('area');
|
||||
|
||||
if (!$area) {
|
||||
$area = $this->ask('📐 Masukkan luas bangunan (m²)');
|
||||
}
|
||||
|
||||
if (!is_numeric($area) || $area <= 0) {
|
||||
$this->error('❌ Luas bangunan harus berupa angka positif!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (float) $area;
|
||||
}
|
||||
|
||||
protected function getFloor()
|
||||
{
|
||||
$floor = $this->option('floor');
|
||||
|
||||
if (!$floor) {
|
||||
$floor = $this->ask('🏗️ Masukkan jumlah lantai (1-6)');
|
||||
}
|
||||
|
||||
if (!is_numeric($floor) || $floor < 1 || $floor > 6) {
|
||||
$this->error('❌ Jumlah lantai harus antara 1-6!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $floor;
|
||||
}
|
||||
|
||||
protected function getBuildingType()
|
||||
{
|
||||
$type = $this->option('type');
|
||||
|
||||
if (!$type) {
|
||||
$this->showBuildingTypes();
|
||||
$type = $this->ask('🏢 Masukkan ID atau kode building type');
|
||||
}
|
||||
|
||||
// Try to find by ID first, then by code
|
||||
$buildingType = null;
|
||||
|
||||
if (is_numeric($type)) {
|
||||
$buildingType = BuildingType::find($type);
|
||||
} else {
|
||||
$buildingType = BuildingType::where('code', strtoupper($type))->first();
|
||||
}
|
||||
|
||||
if (!$buildingType) {
|
||||
$this->error('❌ Building type tidak ditemukan!');
|
||||
return null;
|
||||
}
|
||||
|
||||
return $buildingType->id;
|
||||
}
|
||||
|
||||
protected function showBuildingTypes()
|
||||
{
|
||||
$this->info('📋 DAFTAR BUILDING TYPES:');
|
||||
$this->line('');
|
||||
|
||||
$buildingTypes = BuildingType::with('indices')
|
||||
->whereHas('indices') // Only types that have indices
|
||||
->get();
|
||||
|
||||
$headers = ['ID', 'Kode', 'Nama', 'Coefficient', 'Free'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($buildingTypes as $type) {
|
||||
$rows[] = [
|
||||
$type->id,
|
||||
$type->code,
|
||||
$type->name,
|
||||
$type->indices ? number_format($type->indices->coefficient, 4) : 'N/A',
|
||||
$type->is_free ? '✅' : '❌'
|
||||
];
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
protected function performCalculation($buildingTypeId, $floor, $area)
|
||||
{
|
||||
try {
|
||||
$result = $this->calculatorService->calculate($buildingTypeId, $floor, $area, false);
|
||||
|
||||
$this->displayResults($result, $area, $floor);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Error: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayResults($result, $area, $floor)
|
||||
{
|
||||
$this->info('');
|
||||
$this->info('📊 HASIL PERHITUNGAN RETRIBUSI');
|
||||
$this->info('=' . str_repeat('=', 40));
|
||||
|
||||
// Building info
|
||||
$this->line('🏢 <fg=cyan>Building Type:</> ' . $result['building_type']['name']);
|
||||
$this->line('📐 <fg=cyan>Luas Bangunan:</> ' . number_format($area, 0) . ' m²');
|
||||
$this->line('🏗️ <fg=cyan>Jumlah Lantai:</> ' . $floor);
|
||||
|
||||
if (isset($result['building_type']['is_free']) && $result['building_type']['is_free']) {
|
||||
$this->line('');
|
||||
$this->info('🎉 GRATIS - Building type ini tidak dikenakan retribusi');
|
||||
$this->line('💰 <fg=green>Total Retribusi: Rp 0</fg=green>');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Parameters
|
||||
$this->info('📋 PARAMETER PERHITUNGAN:');
|
||||
$indices = $result['indices'];
|
||||
$this->line('• Coefficient: ' . number_format($indices['coefficient'], 4));
|
||||
$this->line('• IP Permanent: ' . number_format($indices['ip_permanent'], 4));
|
||||
$this->line('• IP Complexity: ' . number_format($indices['ip_complexity'], 4));
|
||||
$this->line('• Locality Index: ' . number_format($indices['locality_index'], 4));
|
||||
$this->line('• Height Index: ' . number_format($result['input_parameters']['height_index'], 4));
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Calculation steps
|
||||
$this->info('🔢 LANGKAH PERHITUNGAN:');
|
||||
$detail = $result['calculation_detail'];
|
||||
$this->line('1. H5 Raw: ' . number_format($detail['h5_raw'], 6));
|
||||
$this->line('2. H5 Rounded: ' . number_format($detail['h5'], 4));
|
||||
$this->line('3. Main Calculation: Rp ' . number_format($detail['main'], 2));
|
||||
$this->line('4. Infrastructure (50%): Rp ' . number_format($detail['infrastructure'], 2));
|
||||
|
||||
$this->line('');
|
||||
|
||||
// Final result
|
||||
$this->info('💰 <fg=green>TOTAL RETRIBUSI: ' . $result['formatted_amount'] . '</fg=green>');
|
||||
$this->line('📈 <fg=yellow>Per m²: Rp ' . number_format($result['total_retribution'] / $area, 2) . '</fg=yellow>');
|
||||
}
|
||||
|
||||
protected function testAllBuildingTypes()
|
||||
{
|
||||
$area = $this->option('area') ?: 100;
|
||||
$floor = $this->option('floor') ?: 2;
|
||||
|
||||
$this->info("🧪 TESTING SEMUA BUILDING TYPES");
|
||||
$this->info("📐 Luas: {$area} m² | 🏗️ Lantai: {$floor}");
|
||||
$this->info('=' . str_repeat('=', 60));
|
||||
|
||||
$buildingTypes = BuildingType::with('indices')
|
||||
->whereHas('indices') // Only types that have indices
|
||||
->orderBy('level')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$headers = ['Kode', 'Nama', 'Coefficient', 'Total Retribusi', 'Per m²'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($buildingTypes as $type) {
|
||||
try {
|
||||
$result = $this->calculatorService->calculate($type->id, $floor, $area, false);
|
||||
|
||||
if ($type->is_free) {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
'FREE',
|
||||
'Rp 0',
|
||||
'Rp 0'
|
||||
];
|
||||
} else {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
number_format($result['indices']['coefficient'], 4),
|
||||
'Rp ' . number_format($result['total_retribution'], 0),
|
||||
'Rp ' . number_format($result['total_retribution'] / $area, 0)
|
||||
];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$rows[] = [
|
||||
$type->code,
|
||||
$type->name,
|
||||
'ERROR',
|
||||
$e->getMessage(),
|
||||
'-'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
291
app/Console/Commands/TestRetributionData.php
Normal file
291
app/Console/Commands/TestRetributionData.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\BuildingType;
|
||||
use App\Models\RetributionIndex;
|
||||
use App\Models\HeightIndex;
|
||||
use App\Models\RetributionConfig;
|
||||
use App\Models\RetributionCalculation;
|
||||
use App\Services\RetributionCalculatorService;
|
||||
|
||||
class TestRetributionData extends Command
|
||||
{
|
||||
protected $signature = 'retribution:data
|
||||
{--save : Save calculation results to database}
|
||||
{--show : Show existing data and relations}
|
||||
{--clear : Clear calculation history}';
|
||||
|
||||
protected $description = 'Test retribution data storage and display database relations';
|
||||
|
||||
protected $calculatorService;
|
||||
|
||||
public function __construct(RetributionCalculatorService $calculatorService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->calculatorService = $calculatorService;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🗄️ SISTEM TEST DATA & RELASI RETRIBUSI PBG');
|
||||
$this->info('=' . str_repeat('=', 55));
|
||||
|
||||
if ($this->option('clear')) {
|
||||
$this->clearCalculationHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->option('show')) {
|
||||
$this->showExistingData();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->option('save')) {
|
||||
$this->saveTestCalculations();
|
||||
}
|
||||
|
||||
$this->showDatabaseStructure();
|
||||
$this->showSampleData();
|
||||
$this->showRelations();
|
||||
}
|
||||
|
||||
protected function saveTestCalculations()
|
||||
{
|
||||
$this->info('💾 MENYIMPAN SAMPLE CALCULATIONS...');
|
||||
$this->line('');
|
||||
|
||||
$testCases = [
|
||||
['type_code' => 'KEAGAMAAN', 'area' => 200, 'floor' => 1],
|
||||
['type_code' => 'SOSBUDAYA', 'area' => 150, 'floor' => 2],
|
||||
['type_code' => 'CAMP_KECIL', 'area' => 1, 'floor' => 1],
|
||||
['type_code' => 'UMKM', 'area' => 100, 'floor' => 2],
|
||||
['type_code' => 'HUN_SEDH', 'area' => 80, 'floor' => 1],
|
||||
['type_code' => 'USH_BESAR', 'area' => 500, 'floor' => 3],
|
||||
];
|
||||
|
||||
foreach ($testCases as $case) {
|
||||
$buildingType = BuildingType::where('code', $case['type_code'])->first();
|
||||
|
||||
if (!$buildingType) {
|
||||
$this->warn("⚠️ Building type {$case['type_code']} not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $this->calculatorService->calculate(
|
||||
$buildingType->id,
|
||||
$case['floor'],
|
||||
$case['area']
|
||||
);
|
||||
|
||||
// Save to database
|
||||
RetributionCalculation::create([
|
||||
'calculation_id' => 'TST' . now()->format('ymdHis') . rand(10, 99),
|
||||
'building_type_id' => $buildingType->id,
|
||||
'floor_number' => $case['floor'],
|
||||
'building_area' => $case['area'],
|
||||
'retribution_amount' => $result['total_retribution'],
|
||||
'calculation_detail' => json_encode($result),
|
||||
'calculated_at' => now()
|
||||
]);
|
||||
|
||||
$this->info("✅ Saved: {$buildingType->name} - {$case['area']}m² - {$case['floor']} lantai - Rp " . number_format($result['total_retribution']));
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$this->info('💾 Sample calculations saved successfully!');
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
protected function showExistingData()
|
||||
{
|
||||
$this->info('📊 DATA YANG TERSIMPAN DI DATABASE');
|
||||
$this->info('=' . str_repeat('=', 40));
|
||||
|
||||
$calculations = RetributionCalculation::with('buildingType')
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
if ($calculations->isEmpty()) {
|
||||
$this->warn('❌ Tidak ada data calculation yang tersimpan');
|
||||
$this->info('💡 Gunakan --save untuk menyimpan sample data');
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = ['ID', 'Building Type', 'Area', 'Floor', 'Amount', 'Created'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($calculations as $calc) {
|
||||
$rows[] = [
|
||||
substr($calc->calculation_id, -8),
|
||||
$calc->buildingType->name ?? 'N/A',
|
||||
$calc->building_area . ' m²',
|
||||
$calc->floor_number,
|
||||
'Rp ' . number_format($calc->retribution_amount),
|
||||
$calc->created_at->format('d/m H:i')
|
||||
];
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
}
|
||||
|
||||
protected function clearCalculationHistory()
|
||||
{
|
||||
$count = RetributionCalculation::count();
|
||||
|
||||
if ($count === 0) {
|
||||
$this->info('ℹ️ Tidak ada data calculation untuk dihapus');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->confirm("🗑️ Hapus {$count} calculation records?")) {
|
||||
RetributionCalculation::truncate();
|
||||
$this->info("✅ {$count} calculation records berhasil dihapus");
|
||||
}
|
||||
}
|
||||
|
||||
protected function showDatabaseStructure()
|
||||
{
|
||||
$this->info('🏗️ STRUKTUR DATABASE RETRIBUSI');
|
||||
$this->info('=' . str_repeat('=', 35));
|
||||
|
||||
$tables = [
|
||||
'building_types' => 'Hierarki dan metadata building types',
|
||||
'retribution_indices' => 'Parameter perhitungan (coefficient, IP, dll)',
|
||||
'height_indices' => 'Koefisien tinggi berdasarkan lantai',
|
||||
'retribution_configs' => 'Konfigurasi global (base value, dll)',
|
||||
'retribution_calculations' => 'History perhitungan dan hasil'
|
||||
];
|
||||
|
||||
foreach ($tables as $table => $description) {
|
||||
$this->line("📋 <info>{$table}</info>: {$description}");
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
protected function showSampleData()
|
||||
{
|
||||
$this->info('📋 SAMPLE DATA DARI SETIAP TABEL');
|
||||
$this->info('=' . str_repeat('=', 35));
|
||||
|
||||
// Building Types
|
||||
$this->line('<comment>🏢 BUILDING TYPES:</comment>');
|
||||
$buildingTypes = BuildingType::select('id', 'code', 'name', 'level', 'is_free')
|
||||
->orderBy('level')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$headers = ['ID', 'Code', 'Name', 'Level', 'Free'];
|
||||
$rows = [];
|
||||
foreach ($buildingTypes->take(5) as $type) {
|
||||
$rows[] = [
|
||||
$type->id,
|
||||
$type->code,
|
||||
substr($type->name, 0, 25) . '...',
|
||||
$type->level,
|
||||
$type->is_free ? '✅' : '❌'
|
||||
];
|
||||
}
|
||||
$this->table($headers, $rows);
|
||||
|
||||
// Retribution Indices
|
||||
$this->line('<comment>📊 RETRIBUTION INDICES:</comment>');
|
||||
$indices = RetributionIndex::with('buildingType')
|
||||
->select('building_type_id', 'coefficient', 'ip_permanent', 'ip_complexity', 'locality_index')
|
||||
->get();
|
||||
|
||||
$headers = ['Building Type', 'Coefficient', 'IP Permanent', 'IP Complexity', 'Locality'];
|
||||
$rows = [];
|
||||
foreach ($indices->take(5) as $index) {
|
||||
$rows[] = [
|
||||
$index->buildingType->code ?? 'N/A',
|
||||
number_format($index->coefficient, 4),
|
||||
number_format($index->ip_permanent, 4),
|
||||
number_format($index->ip_complexity, 4),
|
||||
number_format($index->locality_index, 4)
|
||||
];
|
||||
}
|
||||
$this->table($headers, $rows);
|
||||
|
||||
// Height Indices
|
||||
$this->line('<comment>🏗️ HEIGHT INDICES:</comment>');
|
||||
$heights = HeightIndex::orderBy('floor_number')->get();
|
||||
|
||||
$headers = ['Floor', 'Height Index'];
|
||||
$rows = [];
|
||||
foreach ($heights as $height) {
|
||||
$rows[] = [
|
||||
$height->floor_number,
|
||||
number_format($height->height_index, 4)
|
||||
];
|
||||
}
|
||||
$this->table($headers, $rows);
|
||||
|
||||
// Retribution Configs
|
||||
$this->line('<comment>⚙️ RETRIBUTION CONFIGS:</comment>');
|
||||
$configs = RetributionConfig::all();
|
||||
|
||||
$headers = ['Key', 'Value', 'Description'];
|
||||
$rows = [];
|
||||
foreach ($configs as $config) {
|
||||
$rows[] = [
|
||||
$config->key,
|
||||
$config->value,
|
||||
substr($config->description ?? '', 0, 30) . '...'
|
||||
];
|
||||
}
|
||||
$this->table($headers, $rows);
|
||||
}
|
||||
|
||||
protected function showRelations()
|
||||
{
|
||||
$this->info('🔗 RELASI ANTAR TABEL');
|
||||
$this->info('=' . str_repeat('=', 25));
|
||||
|
||||
// Test relations dengan sample data
|
||||
$buildingType = BuildingType::with(['indices', 'calculations'])
|
||||
->where('code', 'UMKM')
|
||||
->first();
|
||||
|
||||
if (!$buildingType) {
|
||||
$this->warn('⚠️ Sample building type tidak ditemukan');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line("<comment>🏢 Building Type: {$buildingType->name}</comment>");
|
||||
$this->line(" 📋 Code: {$buildingType->code}");
|
||||
$this->line(" 📊 Level: {$buildingType->level}");
|
||||
$this->line(" 🆓 Free: " . ($buildingType->is_free ? 'Ya' : 'Tidak'));
|
||||
|
||||
// Show indices relation
|
||||
if ($buildingType->indices) {
|
||||
$index = $buildingType->indices;
|
||||
$this->line(" <comment>📊 Retribution Index:</comment>");
|
||||
$this->line(" 💰 Coefficient: " . number_format($index->coefficient, 4));
|
||||
$this->line(" 🏗️ IP Permanent: " . number_format($index->ip_permanent, 4));
|
||||
$this->line(" 🔧 IP Complexity: " . number_format($index->ip_complexity, 4));
|
||||
$this->line(" 📍 Locality Index: " . number_format($index->locality_index, 4));
|
||||
}
|
||||
|
||||
// Show calculations relation
|
||||
$calculationsCount = $buildingType->calculations()->count();
|
||||
$this->line(" <comment>📈 Calculations: {$calculationsCount} records</comment>");
|
||||
|
||||
if ($calculationsCount > 0) {
|
||||
$latestCalc = $buildingType->calculations()->latest()->first();
|
||||
$this->line(" 📅 Latest: " . $latestCalc->created_at->format('d/m/Y H:i'));
|
||||
$this->line(" 💰 Amount: Rp " . number_format($latestCalc->retribution_amount));
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$this->info('🎯 KESIMPULAN RELASI:');
|
||||
$this->line(' • BuildingType hasOne RetributionIndex');
|
||||
$this->line(' • BuildingType hasMany RetributionCalculations');
|
||||
$this->line(' • RetributionCalculation belongsTo BuildingType');
|
||||
$this->line(' • HeightIndex independent (digunakan berdasarkan floor_number)');
|
||||
$this->line(' • RetributionConfig global settings');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user