'array', 'discount_details' => 'array', 'calculation_log' => 'array', 'total_value' => 'decimal:2', 'land_value' => 'decimal:2', 'foundation_value' => 'decimal:2', 'total_value_with_foundation' => 'decimal:2', 'paid_amount' => 'decimal:2', 'remaining_amount' => 'decimal:2', 'excess_amount' => 'decimal:2', 'signing_date' => 'date', 'sale_date' => 'date', 'hql_confirmation_date' => 'date', ]; public function product() { return $this->belongsTo(Product::class); } public function paymentTemplate() { return $this->belongsTo(PaymentTemplate::class); } public function customers() { return $this->belongsToMany(Customer::class, 'contract_customers') ->using(ContractCustomer::class) ->withPivot('id', 'role', 'transfer_order') ->withTimestamps(); } public function appendices() { return $this->hasMany(Appendix::class); } public function paymentSchedule() { return $this->hasOne(PaymentSchedule::class); } public function scheduleItems(): HasManyThrough { return $this->hasManyThrough( PaymentScheduleItem::class, PaymentSchedule::class, 'contract_id', 'schedule_id', 'id', 'id' ); } public function payments() { return $this->hasMany(Payment::class); } public function paymentFines() { return $this->hasMany(PaymentFine::class); } /** * Giá trị sau chiết khấu (qua PriceCalculationService). */ public function getFinalValueAttribute(): float { if ($this->calculation_log) { return (float) ($this->calculation_log['final_values']['total_payment'] ?? 0); } // Fallback: tính nhanh nếu chưa có calculation_log $result = \App\Services\Calculation\PriceCalculationService::calculateForContract($this); return (float) ($result->get('total_payment') ?? 0); } /** * Lấy phiếu tính giá chi tiết. */ public function getPriceSheetAttribute(): ?array { if ($this->calculation_log) { return $this->calculation_log['price_sheet'] ?? null; } return null; } protected static function booted() { static::saving(function ($contract) { // Bảo vệ tính toán tài chính: total_value luôn bằng land_value + foundation_value $landValue = (float) ($contract->land_value ?? 0); $foundationValue = (float) ($contract->foundation_value ?? 0); if ($landValue > 0 || $foundationValue > 0) { $contract->total_value = $landValue + $foundationValue; } elseif ($contract->exists === false && empty($contract->total_value) && !empty($contract->product_id)) { // Fallback khi tạo mới và chưa có giá trị tài chính chi tiết $product = Product::find($contract->product_id); if ($product) { $contract->total_value = $product->total_price; } } $contract->remaining_amount = (float) ($contract->total_value ?? 0) - (float) ($contract->paid_amount ?? 0); }); static::saved(function ($contract) { // Guard: tránh infinite loop khi lưu calculation_log if (self::$calculating) return; // Tự động tính toán và lưu snapshot sau khi lưu if ($contract->land_value || $contract->foundation_value) { self::$calculating = true; try { $result = \App\Services\Calculation\PriceCalculationService::calculateForContract($contract); $contract->updateQuietly([ 'calculation_log' => [ 'steps' => $result->getSteps(), 'final_values' => $result->getValues(), 'price_sheet' => $result->toPriceSheet(), 'calculated_at' => now()->toDateTimeString(), ], ]); } finally { self::$calculating = false; } } }); } }