124 lines
4.4 KiB
PHP
124 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Libraries;
|
|
|
|
use App\Models\InvoiceModel;
|
|
|
|
class InvoiceIssuanceService
|
|
{
|
|
private \CodeIgniter\Database\BaseConnection $db;
|
|
private InvoiceModel $invoiceModel;
|
|
private InvoiceLedgerService $invoiceLedgerService;
|
|
|
|
public function __construct(
|
|
?\CodeIgniter\Database\BaseConnection $db = null,
|
|
?InvoiceModel $invoiceModel = null,
|
|
$invoiceLineModel = null,
|
|
?InvoiceLedgerService $invoiceLedgerService = null
|
|
) {
|
|
$this->db = $db ?? db_connect();
|
|
$this->invoiceModel = $invoiceModel ?? new InvoiceModel();
|
|
$this->invoiceLedgerService = $invoiceLedgerService ?? new InvoiceLedgerService();
|
|
}
|
|
|
|
public function issueInvoice(IssueInvoiceCommand $command): InvoiceLedgerResult
|
|
{
|
|
$invoiceData = $command->invoiceData;
|
|
if (trim((string)($invoiceData['invoice_number'] ?? '')) === '') {
|
|
$invoiceData['invoice_number'] = $this->generateInvoiceNumber(
|
|
(string)($invoiceData['school_year'] ?? ''),
|
|
(int)($invoiceData['parent_id'] ?? 0)
|
|
);
|
|
}
|
|
$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]);
|
|
|
|
$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;
|
|
}
|
|
}
|
|
|
|
public function generateInvoiceNumber(string $schoolYear, ?int $parentId = null): string
|
|
{
|
|
$prefix = $this->invoiceNumberPrefix($schoolYear, $parentId);
|
|
do {
|
|
$invoiceNumber = $prefix . '-' . uniqid();
|
|
$exists = $this->db->table('invoices')
|
|
->where('invoice_number', $invoiceNumber)
|
|
->countAllResults() > 0;
|
|
} while ($exists);
|
|
|
|
return $invoiceNumber;
|
|
}
|
|
|
|
private function invoiceNumberPrefix(string $schoolYear, ?int $parentId = null): string
|
|
{
|
|
$year = date('y');
|
|
if (preg_match('/^(\d{4})-\d{4}$/', $schoolYear, $matches) === 1) {
|
|
$year = substr($matches[1], -2);
|
|
}
|
|
|
|
$parentSuffix = $this->parentSchoolIdSuffix((int)($parentId ?? 0));
|
|
if ($parentSuffix !== '') {
|
|
return 'INV-' . $year . $parentSuffix;
|
|
}
|
|
|
|
return 'INV-' . $year . str_pad((string) max(0, (int)($parentId ?? 0)), 5, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
private function parentSchoolIdSuffix(int $parentId): string
|
|
{
|
|
if ($parentId <= 0) {
|
|
return '';
|
|
}
|
|
|
|
$row = $this->db->table('users')
|
|
->select('school_id')
|
|
->where('id', $parentId)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
$schoolId = trim((string)($row['school_id'] ?? ''));
|
|
if (preg_match('/^\d{2}(\d{5})$/', $schoolId, $matches) === 1) {
|
|
return $matches[1];
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|