101 lines
3.6 KiB
PHP
101 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace Modules\Accounting\Services;
|
|
|
|
use Modules\Accounting\Domain\FinancialPostingRequest;
|
|
use Modules\Accounting\Entities\AccountingCheque;
|
|
|
|
class ChequeService
|
|
{
|
|
public function __construct(protected FinancialPostingService $postingService) {}
|
|
|
|
public function register(array $data, int $businessId, int $userId): AccountingCheque
|
|
{
|
|
$cheque = AccountingCheque::create(array_merge($data, [
|
|
'business_id' => $businessId,
|
|
'created_by' => $userId,
|
|
'status' => 'pending',
|
|
]));
|
|
|
|
if (! empty($data['accounting_account_id']) && ! empty($data['counter_account_id'])) {
|
|
$this->postJournalForCheque($cheque, (int) $data['counter_account_id'], $userId, 'cheque_register');
|
|
}
|
|
|
|
return $cheque;
|
|
}
|
|
|
|
public function allowedTransitions(string $status): array
|
|
{
|
|
$allowed = [
|
|
'pending' => ['deposited', 'cleared', 'cancelled', 'endorsed'],
|
|
'deposited' => ['cleared', 'bounced'],
|
|
'cleared' => [],
|
|
'bounced' => ['pending'],
|
|
'endorsed' => ['cleared', 'cancelled'],
|
|
'cancelled' => [],
|
|
];
|
|
|
|
return $allowed[$status] ?? [];
|
|
}
|
|
|
|
public function transitionRequiresCounterAccount(string $newStatus): bool
|
|
{
|
|
return in_array($newStatus, ['deposited', 'cleared', 'bounced'], true);
|
|
}
|
|
|
|
public function transition(AccountingCheque $cheque, string $newStatus, int $userId, ?int $counterAccountId = null): AccountingCheque
|
|
{
|
|
if (! in_array($newStatus, $this->allowedTransitions($cheque->status), true)) {
|
|
throw new \RuntimeException('Invalid cheque status transition.');
|
|
}
|
|
|
|
if ($this->transitionRequiresCounterAccount($newStatus) && empty($counterAccountId)) {
|
|
throw new \RuntimeException('Counter account is required for this cheque transition.');
|
|
}
|
|
|
|
if (in_array($newStatus, ['deposited', 'cleared', 'bounced'], true) && $counterAccountId && $cheque->accounting_account_id) {
|
|
$this->postJournalForCheque($cheque, $counterAccountId, $userId, 'cheque_'.$newStatus, $newStatus !== 'deposited');
|
|
}
|
|
|
|
$cheque->status = $newStatus;
|
|
$cheque->save();
|
|
|
|
return $cheque;
|
|
}
|
|
|
|
protected function postJournalForCheque(
|
|
AccountingCheque $cheque,
|
|
int $counterAccountId,
|
|
int $userId,
|
|
string $sourceType,
|
|
bool $reverseDirection = false
|
|
): void {
|
|
$amount = (float) $cheque->amount;
|
|
$bankAccountId = (int) $cheque->accounting_account_id;
|
|
|
|
if ($cheque->direction === 'received') {
|
|
$debitAccount = $reverseDirection ? $counterAccountId : $bankAccountId;
|
|
$creditAccount = $reverseDirection ? $bankAccountId : $counterAccountId;
|
|
} else {
|
|
$debitAccount = $reverseDirection ? $bankAccountId : $counterAccountId;
|
|
$creditAccount = $reverseDirection ? $counterAccountId : $bankAccountId;
|
|
}
|
|
|
|
$mapping = $this->postingService->post(new FinancialPostingRequest(
|
|
businessId: $cheque->business_id,
|
|
userId: $userId,
|
|
sourceType: $sourceType,
|
|
sourceId: $cheque->id,
|
|
note: 'Cheque '.$cheque->cheque_number,
|
|
lines: [
|
|
['accounting_account_id' => $debitAccount, 'amount' => $amount, 'type' => 'debit'],
|
|
['accounting_account_id' => $creditAccount, 'amount' => $amount, 'type' => 'credit'],
|
|
],
|
|
operationDate: optional($cheque->due_date)->format('Y-m-d') ?? now()->toDateString(),
|
|
));
|
|
|
|
$cheque->journal_mapping_id = $mapping?->id;
|
|
$cheque->save();
|
|
}
|
|
}
|