'decimal:6', 'total_building_area' => 'decimal:6', 'ip_ketinggian' => 'decimal:6', 'floor_retribution_amount' => 'decimal:2', 'total_retribution_amount' => 'decimal:2', 'calculation_parameters' => 'array', 'calculation_breakdown' => 'array', 'calculated_at' => 'datetime' ]; /** * Relationship with SpatialPlanning */ public function spatialPlanning(): BelongsTo { return $this->belongsTo(SpatialPlanning::class); } /** * Relationship with BuildingFunction */ public function buildingFunction(): BelongsTo { return $this->belongsTo(BuildingFunction::class); } /** * Relationship with RetributionFormula */ public function retributionFormula(): BelongsTo { return $this->belongsTo(RetributionFormula::class); } /** * Generate proposal number */ public static function generateProposalNumber(): string { $year = now()->format('Y'); $month = now()->format('m'); // Use max ID + 1 to avoid duplicates when records are deleted $maxId = static::whereYear('created_at', now()->year) ->whereMonth('created_at', now()->month) ->max('id') ?? 0; $nextNumber = $maxId + 1; // Fallback: if still duplicate, use timestamp $proposalNumber = sprintf('RP-%s%s-%04d', $year, $month, $nextNumber); // Check if exists and increment until unique $counter = $nextNumber; while (static::where('proposal_number', $proposalNumber)->exists()) { $counter++; $proposalNumber = sprintf('RP-%s%s-%04d', $year, $month, $counter); } return $proposalNumber; } /** * Get formatted floor retribution amount */ public function getFormattedFloorAmountAttribute(): string { return 'Rp ' . number_format($this->floor_retribution_amount, 0, ',', '.'); } /** * Get formatted total retribution amount */ public function getFormattedTotalAmountAttribute(): string { return 'Rp ' . number_format($this->total_retribution_amount, 0, ',', '.'); } /** * Get calculation breakdown for specific parameter */ public function getCalculationBreakdownFor(string $parameter) { return $this->calculation_breakdown[$parameter] ?? null; } /** * Get parameter value */ public function getParameterValue(string $parameter) { return $this->calculation_parameters[$parameter] ?? null; } /** * Scope for filtering by building function */ public function scopeByBuildingFunction($query, int $buildingFunctionId) { return $query->where('building_function_id', $buildingFunctionId); } /** * Scope for filtering by spatial planning */ public function scopeBySpatialPlanning($query, int $spatialPlanningId) { return $query->where('spatial_planning_id', $spatialPlanningId); } /** * Get total retribution amount for multiple proposals */ public static function getTotalAmount($proposals = null): float { if ($proposals) { return $proposals->sum('total_retribution_amount'); } return static::sum('total_retribution_amount'); } /** * Get basic statistics */ public static function getBasicStats(): array { return [ 'total_count' => static::count(), 'total_amount' => static::sum('total_retribution_amount'), 'average_amount' => static::avg('total_retribution_amount'), ]; } /** * Boot method to generate proposal number */ protected static function boot() { parent::boot(); static::creating(function ($model) { if (empty($model->proposal_number)) { $model->proposal_number = static::generateProposalNumber(); } if (empty($model->calculated_at)) { $model->calculated_at = now(); } }); } }