235 lines
6.3 KiB
PHP
235 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
class BankReconciliation extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
protected static function booted()
|
|
{
|
|
static::creating(function (BankReconciliation $model) {
|
|
$model->adjusted_balance = $model->adjusted_balance ?? (float) $model->calculateAdjustedBalance();
|
|
$model->discrepancy_amount = $model->discrepancy_amount ?? (float) $model->calculateDiscrepancy();
|
|
$model->reconciliation_status = $model->reconciliation_status ?? self::STATUS_PENDING;
|
|
});
|
|
}
|
|
|
|
protected $fillable = [
|
|
'reconciliation_month',
|
|
'bank_statement_balance',
|
|
'bank_statement_date',
|
|
'bank_statement_file_path',
|
|
'system_book_balance',
|
|
'outstanding_checks',
|
|
'deposits_in_transit',
|
|
'bank_charges',
|
|
'adjusted_balance',
|
|
'discrepancy_amount',
|
|
'reconciliation_status',
|
|
'prepared_by_cashier_id',
|
|
'reviewed_by_accountant_id',
|
|
'approved_by_manager_id',
|
|
'prepared_at',
|
|
'reviewed_at',
|
|
'approved_at',
|
|
'notes',
|
|
];
|
|
|
|
protected $casts = [
|
|
'reconciliation_month' => 'date',
|
|
'bank_statement_balance' => 'decimal:2',
|
|
'bank_statement_date' => 'date',
|
|
'system_book_balance' => 'decimal:2',
|
|
'outstanding_checks' => 'array',
|
|
'deposits_in_transit' => 'array',
|
|
'bank_charges' => 'array',
|
|
'adjusted_balance' => 'decimal:2',
|
|
'discrepancy_amount' => 'decimal:2',
|
|
'prepared_at' => 'datetime',
|
|
'reviewed_at' => 'datetime',
|
|
'approved_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* 狀態常數
|
|
*/
|
|
const STATUS_PENDING = 'pending';
|
|
const STATUS_COMPLETED = 'completed';
|
|
const STATUS_DISCREPANCY = 'discrepancy';
|
|
|
|
/**
|
|
* 製作調節表的出納人員
|
|
*/
|
|
public function preparedByCashier(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'prepared_by_cashier_id');
|
|
}
|
|
|
|
/**
|
|
* 覆核的會計人員
|
|
*/
|
|
public function reviewedByAccountant(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'reviewed_by_accountant_id');
|
|
}
|
|
|
|
/**
|
|
* 核准的主管
|
|
*/
|
|
public function approvedByManager(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'approved_by_manager_id');
|
|
}
|
|
|
|
/**
|
|
* 計算調整後餘額
|
|
*/
|
|
public function calculateAdjustedBalance(): float
|
|
{
|
|
$adjusted = $this->system_book_balance;
|
|
|
|
// 加上在途存款
|
|
if ($this->deposits_in_transit) {
|
|
foreach ($this->deposits_in_transit as $deposit) {
|
|
$adjusted += floatval($deposit['amount'] ?? 0);
|
|
}
|
|
}
|
|
|
|
// 減去未兌現支票
|
|
if ($this->outstanding_checks) {
|
|
foreach ($this->outstanding_checks as $check) {
|
|
$adjusted -= floatval($check['amount'] ?? 0);
|
|
}
|
|
}
|
|
|
|
// 減去銀行手續費
|
|
if ($this->bank_charges) {
|
|
foreach ($this->bank_charges as $charge) {
|
|
$adjusted -= floatval($charge['amount'] ?? 0);
|
|
}
|
|
}
|
|
|
|
return $adjusted;
|
|
}
|
|
|
|
/**
|
|
* 計算差異金額
|
|
*/
|
|
public function calculateDiscrepancy(): float
|
|
{
|
|
$adjusted = $this->adjusted_balance ?? $this->calculateAdjustedBalance();
|
|
|
|
return abs($adjusted - floatval($this->bank_statement_balance));
|
|
}
|
|
|
|
/**
|
|
* 檢查是否有差異
|
|
*/
|
|
public function hasDiscrepancy(float $tolerance = 0.01): bool
|
|
{
|
|
return $this->calculateDiscrepancy() > $tolerance;
|
|
}
|
|
|
|
/**
|
|
* 是否待覆核
|
|
*/
|
|
public function isPending(): bool
|
|
{
|
|
return $this->reconciliation_status === self::STATUS_PENDING;
|
|
}
|
|
|
|
/**
|
|
* 是否已完成
|
|
*/
|
|
public function isCompleted(): bool
|
|
{
|
|
return $this->reconciliation_status === self::STATUS_COMPLETED;
|
|
}
|
|
|
|
/**
|
|
* 是否有差異待處理
|
|
*/
|
|
public function hasUnresolvedDiscrepancy(): bool
|
|
{
|
|
return $this->reconciliation_status === self::STATUS_DISCREPANCY
|
|
|| $this->discrepancy_amount > 0.01;
|
|
}
|
|
|
|
/**
|
|
* 是否可以被會計覆核
|
|
*/
|
|
public function canBeReviewed(): bool
|
|
{
|
|
return $this->isPending() && $this->reviewed_at === null;
|
|
}
|
|
|
|
/**
|
|
* 是否可以被主管核准
|
|
*/
|
|
public function canBeApproved(): bool
|
|
{
|
|
return $this->reviewed_at !== null && $this->approved_at === null;
|
|
}
|
|
|
|
/**
|
|
* 取得狀態文字
|
|
*/
|
|
public function getStatusText(): string
|
|
{
|
|
return match ($this->reconciliation_status) {
|
|
self::STATUS_PENDING => '待覆核',
|
|
self::STATUS_COMPLETED => '已完成',
|
|
self::STATUS_DISCREPANCY => '有差異',
|
|
default => '未知',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 取得未達帳項總計
|
|
*/
|
|
public function getOutstandingItemsSummary(): array
|
|
{
|
|
$checksTotal = 0;
|
|
$checksCount = 0;
|
|
if ($this->outstanding_checks) {
|
|
foreach ($this->outstanding_checks as $check) {
|
|
$checksTotal += floatval($check['amount'] ?? 0);
|
|
$checksCount++;
|
|
}
|
|
}
|
|
|
|
$depositsTotal = 0;
|
|
$depositsCount = 0;
|
|
if ($this->deposits_in_transit) {
|
|
foreach ($this->deposits_in_transit as $deposit) {
|
|
$depositsTotal += floatval($deposit['amount'] ?? 0);
|
|
$depositsCount++;
|
|
}
|
|
}
|
|
|
|
$chargesTotal = 0;
|
|
$chargesCount = 0;
|
|
if ($this->bank_charges) {
|
|
foreach ($this->bank_charges as $charge) {
|
|
$chargesTotal += floatval($charge['amount'] ?? 0);
|
|
$chargesCount++;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'total_outstanding_checks' => $checksTotal,
|
|
'outstanding_checks_count' => $checksCount,
|
|
'total_deposits_in_transit' => $depositsTotal,
|
|
'deposits_in_transit_count' => $depositsCount,
|
|
'total_bank_charges' => $chargesTotal,
|
|
'bank_charges_count' => $chargesCount,
|
|
'net_adjustment' => $depositsTotal - $checksTotal - $chargesTotal,
|
|
];
|
|
}
|
|
}
|