'date', 'amount' => 'decimal:2', 'recorded_at' => 'datetime', 'confirmed_at' => 'datetime', ]; /** * Boot 方法 - 自動產生收入編號 */ protected static function boot() { parent::boot(); static::creating(function ($income) { if (empty($income->income_number)) { $income->income_number = self::generateIncomeNumber(); } if (empty($income->recorded_at)) { $income->recorded_at = now(); } }); } /** * 產生收入編號 INC-2025-0001 */ public static function generateIncomeNumber(): string { $year = date('Y'); $prefix = "INC-{$year}-"; $lastIncome = self::where('income_number', 'like', "{$prefix}%") ->orderBy('income_number', 'desc') ->first(); if ($lastIncome) { $lastNumber = (int) substr($lastIncome->income_number, -4); $newNumber = $lastNumber + 1; } else { $newNumber = 1; } return $prefix . str_pad($newNumber, 4, '0', STR_PAD_LEFT); } // ========== 關聯 ========== /** * 會計科目 */ public function chartOfAccount(): BelongsTo { return $this->belongsTo(ChartOfAccount::class); } /** * 關聯會員 */ public function member(): BelongsTo { return $this->belongsTo(Member::class); } /** * 記錄的出納人員 */ public function recordedByCashier(): BelongsTo { return $this->belongsTo(User::class, 'recorded_by_cashier_id'); } /** * 確認的會計人員 */ public function confirmedByAccountant(): BelongsTo { return $this->belongsTo(User::class, 'confirmed_by_accountant_id'); } /** * 關聯的出納日記帳 */ public function cashierLedgerEntry(): BelongsTo { return $this->belongsTo(CashierLedgerEntry::class); } /** * 會計分錄 */ public function accountingEntries(): HasMany { return $this->hasMany(AccountingEntry::class); } // ========== 狀態查詢 ========== /** * 是否待確認 */ public function isPending(): bool { return $this->status === self::STATUS_PENDING; } /** * 是否已確認 */ public function isConfirmed(): bool { return $this->status === self::STATUS_CONFIRMED; } /** * 是否已取消 */ public function isCancelled(): bool { return $this->status === self::STATUS_CANCELLED; } /** * 是否可以被會計確認 */ public function canBeConfirmed(): bool { return $this->status === self::STATUS_PENDING; } /** * 是否可以被取消 */ public function canBeCancelled(): bool { return $this->status === self::STATUS_PENDING; } // ========== 業務方法 ========== /** * 會計確認收入 */ public function confirmByAccountant(User $accountant): void { if (!$this->canBeConfirmed()) { throw new \Exception('此收入無法確認'); } DB::transaction(function () use ($accountant) { // 1. 更新收入狀態 $this->update([ 'status' => self::STATUS_CONFIRMED, 'confirmed_by_accountant_id' => $accountant->id, 'confirmed_at' => now(), ]); // 2. 產生出納日記帳記錄 $ledgerEntry = $this->createCashierLedgerEntry(); // 3. 產生會計分錄 $this->generateAccountingEntries(); }); } /** * 取消收入 */ public function cancel(): void { if (!$this->canBeCancelled()) { throw new \Exception('此收入無法取消'); } $this->update([ 'status' => self::STATUS_CANCELLED, ]); } /** * 建立出納日記帳記錄 */ protected function createCashierLedgerEntry(): CashierLedgerEntry { $bankAccount = $this->bank_account ?? 'Main Account'; $balanceBefore = CashierLedgerEntry::getLatestBalance($bankAccount); $ledgerEntry = CashierLedgerEntry::create([ 'entry_date' => $this->income_date, 'entry_type' => CashierLedgerEntry::ENTRY_TYPE_RECEIPT, 'payment_method' => $this->payment_method, 'bank_account' => $bankAccount, 'amount' => $this->amount, 'balance_before' => $balanceBefore, 'balance_after' => $balanceBefore + $this->amount, 'receipt_number' => $this->receipt_number, 'transaction_reference' => $this->transaction_reference, 'recorded_by_cashier_id' => $this->recorded_by_cashier_id, 'recorded_at' => now(), 'notes' => "收入確認:{$this->title} ({$this->income_number})", ]); $this->update(['cashier_ledger_entry_id' => $ledgerEntry->id]); return $ledgerEntry; } /** * 產生會計分錄 */ protected function generateAccountingEntries(): void { // 借方:資產帳戶(現金或銀行存款) $assetAccountId = $this->getAssetAccountId(); AccountingEntry::create([ 'income_id' => $this->id, 'chart_of_account_id' => $assetAccountId, 'entry_type' => AccountingEntry::ENTRY_TYPE_DEBIT, 'amount' => $this->amount, 'entry_date' => $this->income_date, 'description' => "收入:{$this->title} ({$this->income_number})", ]); // 貸方:收入科目 AccountingEntry::create([ 'income_id' => $this->id, 'chart_of_account_id' => $this->chart_of_account_id, 'entry_type' => AccountingEntry::ENTRY_TYPE_CREDIT, 'amount' => $this->amount, 'entry_date' => $this->income_date, 'description' => "收入:{$this->title} ({$this->income_number})", ]); } /** * 根據付款方式取得資產帳戶 ID */ protected function getAssetAccountId(): int { $accountCode = match ($this->payment_method) { self::PAYMENT_METHOD_BANK_TRANSFER => '1201', // 銀行存款 self::PAYMENT_METHOD_CHECK => '1201', // 銀行存款 default => '1101', // 現金 }; return ChartOfAccount::where('account_code', $accountCode)->value('id') ?? 1; } // ========== 文字取得 ========== /** * 取得收入類型文字 */ public function getIncomeTypeText(): string { return match ($this->income_type) { self::TYPE_MEMBERSHIP_FEE => '會費收入', self::TYPE_ENTRANCE_FEE => '入會費收入', self::TYPE_DONATION => '捐款收入', self::TYPE_ACTIVITY => '活動收入', self::TYPE_GRANT => '補助收入', self::TYPE_INTEREST => '利息收入', self::TYPE_OTHER => '其他收入', default => '未知', }; } /** * 取得狀態文字 */ public function getStatusText(): string { return match ($this->status) { self::STATUS_PENDING => '待確認', self::STATUS_CONFIRMED => '已確認', self::STATUS_CANCELLED => '已取消', default => '未知', }; } /** * 取得付款方式文字 */ public function getPaymentMethodText(): string { return match ($this->payment_method) { self::PAYMENT_METHOD_CASH => '現金', self::PAYMENT_METHOD_BANK_TRANSFER => '銀行轉帳', self::PAYMENT_METHOD_CHECK => '支票', default => '未知', }; } /** * 取得狀態標籤屬性 */ public function getStatusLabelAttribute(): string { return $this->getStatusText(); } // ========== 收入類型與科目對應 ========== /** * 取得收入類型對應的預設會計科目代碼 */ public static function getDefaultAccountCode(string $incomeType): string { return match ($incomeType) { self::TYPE_MEMBERSHIP_FEE => '4101', self::TYPE_ENTRANCE_FEE => '4102', self::TYPE_DONATION => '4201', self::TYPE_ACTIVITY => '4402', self::TYPE_GRANT => '4301', self::TYPE_INTEREST => '4401', self::TYPE_OTHER => '4901', default => '4901', }; } /** * 取得收入類型對應的預設會計科目 ID */ public static function getDefaultAccountId(string $incomeType): ?int { $accountCode = self::getDefaultAccountCode($incomeType); return ChartOfAccount::where('account_code', $accountCode)->value('id'); } /** * 靜態方法:取得收入類型文字標籤 */ public static function getIncomeTypeLabel(string $incomeType): string { return match ($incomeType) { self::TYPE_MEMBERSHIP_FEE => '會費收入', self::TYPE_ENTRANCE_FEE => '入會費收入', self::TYPE_DONATION => '捐款收入', self::TYPE_ACTIVITY => '活動收入', self::TYPE_GRANT => '補助收入', self::TYPE_INTEREST => '利息收入', self::TYPE_OTHER => '其他收入', default => '未知', }; } // ========== 查詢範圍 ========== /** * 篩選待確認的收入 */ public function scopePending($query) { return $query->where('status', self::STATUS_PENDING); } /** * 篩選已確認的收入 */ public function scopeConfirmed($query) { return $query->where('status', self::STATUS_CONFIRMED); } /** * 篩選特定收入類型 */ public function scopeOfType($query, string $type) { return $query->where('income_type', $type); } /** * 篩選特定會員 */ public function scopeForMember($query, int $memberId) { return $query->where('member_id', $memberId); } /** * 篩選日期範圍 */ public function scopeDateRange($query, $startDate, $endDate) { return $query->whereBetween('income_date', [$startDate, $endDate]); } }