98 lines
3.0 KiB
PHP
98 lines
3.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\InvoiceStatus;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
// InvoiceSettings used for number generation
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
class Invoice extends Model
|
|
{
|
|
use HasFactory, SoftDeletes;
|
|
|
|
protected $fillable = [
|
|
'client_id', 'invoice_number', 'title', 'description',
|
|
'subtotal', 'tax_rate', 'tax_amount', 'total', 'currency',
|
|
'status', 'issue_date', 'due_date', 'paid_date', 'notes',
|
|
'bank_name', 'bank_account', 'bank_holder', 'branch_name',
|
|
'signature_image', 'signer_name', 'signer_title', 'discount_rate',
|
|
];
|
|
|
|
protected $casts = [
|
|
'status' => InvoiceStatus::class,
|
|
'issue_date' => 'date',
|
|
'due_date' => 'date',
|
|
'paid_date' => 'date',
|
|
'subtotal' => 'decimal:2',
|
|
'tax_rate' => 'decimal:2',
|
|
'tax_amount' => 'decimal:2',
|
|
'total' => 'decimal:2',
|
|
];
|
|
|
|
public function client(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Client::class);
|
|
}
|
|
|
|
public function items(): HasMany
|
|
{
|
|
return $this->hasMany(InvoiceItem::class);
|
|
}
|
|
|
|
public function recalculate(): void
|
|
{
|
|
// Total of all items
|
|
$this->subtotal = $this->items->sum('total');
|
|
|
|
$discountAmount = 0;
|
|
if ($this->discount_rate > 0) {
|
|
$discountableSubtotal = $this->items->where('is_discountable', true)->sum('total');
|
|
$discountAmount = round((float) $discountableSubtotal * ($this->discount_rate / 100), 2);
|
|
}
|
|
|
|
$this->subtotal = $this->subtotal - $discountAmount;
|
|
|
|
// Tax is calculated only on taxable items
|
|
$taxableSubtotal = 0;
|
|
foreach ($this->items as $item) {
|
|
if ($item->is_taxable) {
|
|
if ($this->discount_rate > 0 && $item->is_discountable) {
|
|
$taxableSubtotal += $item->total * (1 - $this->discount_rate / 100);
|
|
} else {
|
|
$taxableSubtotal += $item->total;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->tax_amount = round((float) $taxableSubtotal * ($this->tax_rate / 100), 2);
|
|
|
|
// Grand total = subtotal (tax is borne by client separately)
|
|
$this->total = $this->subtotal;
|
|
$this->save();
|
|
}
|
|
|
|
public function getFormattedTotalAttribute(): string
|
|
{
|
|
return 'Rp ' . number_format((float) $this->total, 0, ',', '.');
|
|
}
|
|
|
|
public function getDiscountAmountAttribute(): float
|
|
{
|
|
if ($this->discount_rate <= 0) return 0.0;
|
|
|
|
$discountableSubtotal = $this->items->where('is_discountable', true)->sum('total');
|
|
return round((float) $discountableSubtotal * ($this->discount_rate / 100), 2);
|
|
}
|
|
|
|
public static function generateNumber(): string
|
|
{
|
|
return InvoiceSettings::generateNumber();
|
|
}
|
|
}
|