@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use App\Models\InvoiceLineModel;
|
||||
use App\Models\InvoiceModel;
|
||||
|
||||
class InvoiceIssuanceService
|
||||
{
|
||||
private \CodeIgniter\Database\BaseConnection $db;
|
||||
private InvoiceModel $invoiceModel;
|
||||
private InvoiceLineModel $invoiceLineModel;
|
||||
private InvoiceLedgerService $invoiceLedgerService;
|
||||
|
||||
public function __construct(
|
||||
?\CodeIgniter\Database\BaseConnection $db = null,
|
||||
?InvoiceModel $invoiceModel = null,
|
||||
?InvoiceLineModel $invoiceLineModel = null,
|
||||
?InvoiceLedgerService $invoiceLedgerService = null
|
||||
) {
|
||||
$this->db = $db ?? db_connect();
|
||||
$this->invoiceModel = $invoiceModel ?? new InvoiceModel();
|
||||
$this->invoiceLineModel = $invoiceLineModel ?? new InvoiceLineModel();
|
||||
$this->invoiceLedgerService = $invoiceLedgerService ?? new InvoiceLedgerService();
|
||||
}
|
||||
|
||||
public function issueInvoice(IssueInvoiceCommand $command): InvoiceLedgerResult
|
||||
{
|
||||
$invoiceData = $command->invoiceData;
|
||||
$invoiceData['status'] = FinancialStatus::INVOICE_DRAFT;
|
||||
$invoiceData['total_amount'] = $invoiceData['total_amount'] ?? number_format($command->tuitionAmount + $command->eventAmount, 2, '.', '');
|
||||
$invoiceData['balance'] = $invoiceData['balance'] ?? $invoiceData['total_amount'];
|
||||
|
||||
$this->db->transBegin();
|
||||
|
||||
try {
|
||||
$invoiceId = $this->invoiceModel->insert($invoiceData);
|
||||
$this->requireWrite($invoiceId, 'INVOICE_ISSUE_INSERT_FAILED', $this->invoiceModel);
|
||||
|
||||
$this->db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [(int)$invoiceId]);
|
||||
|
||||
$inserted = $this->invoiceLedgerService->issueInitialInvoiceLines(
|
||||
(int)$invoiceId,
|
||||
$command->tuitionAmount,
|
||||
$command->eventAmount,
|
||||
$command->metadata
|
||||
);
|
||||
if ($inserted <= 0) {
|
||||
throw new FinancialPersistenceException('INVOICE_ISSUE_NO_LINES');
|
||||
}
|
||||
|
||||
$lineTotals = $this->db->table('invoice_lines')
|
||||
->select('COUNT(*) AS line_count, COALESCE(SUM(line_amount_cents),0) AS total_cents')
|
||||
->where('invoice_id', (int)$invoiceId)
|
||||
->where('voided_at IS NULL', null, false)
|
||||
->get()
|
||||
->getRowArray();
|
||||
if ((int)($lineTotals['line_count'] ?? 0) !== $inserted) {
|
||||
throw new FinancialPersistenceException('INVOICE_ISSUE_LINE_COUNT_MISMATCH');
|
||||
}
|
||||
if ((int)($lineTotals['total_cents'] ?? 0) === 0) {
|
||||
throw new FinancialPersistenceException('INVOICE_ISSUE_ZERO_LINE_TOTAL');
|
||||
}
|
||||
|
||||
$this->requireWrite($this->invoiceModel->update((int)$invoiceId, [
|
||||
'status' => FinancialStatus::INVOICE_ISSUED,
|
||||
'updated_at' => utc_now(),
|
||||
]), 'INVOICE_ISSUE_STATUS_UPDATE_FAILED', $this->invoiceModel);
|
||||
|
||||
$ledger = $this->invoiceLedgerService->recalculateInvoice((int)$invoiceId);
|
||||
$this->requireTransactionStatus();
|
||||
$this->db->transCommit();
|
||||
|
||||
return new InvoiceLedgerResult((int)$invoiceId, 0, $ledger);
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function requireWrite($result, string $code, $model = null): void
|
||||
{
|
||||
if ($result === false || $result === null || $result === 0) {
|
||||
$details = is_object($model) && method_exists($model, 'errors') ? (array)$model->errors() : [];
|
||||
throw new FinancialPersistenceException($code, $details);
|
||||
}
|
||||
}
|
||||
|
||||
private function requireTransactionStatus(string $code = 'TRANSACTION_FAILED'): void
|
||||
{
|
||||
if (method_exists($this->db, 'transStatus') && $this->db->transStatus() === false) {
|
||||
throw new FinancialPersistenceException($code);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user