fix invoice and enrollment fees
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 48s
Tests / PHPUnit (push) Failing after 1m19s

This commit is contained in:
root
2026-08-27 18:34:36 -04:00
parent 0ae9993d82
commit 38644b32ae
29 changed files with 1836 additions and 1555 deletions
+5 -85
View File
@@ -3,24 +3,21 @@
namespace App\Libraries;
use App\Models\AdditionalChargeModel;
use App\Models\InvoiceLineModel;
class InvoiceAdjustmentService
{
private \CodeIgniter\Database\BaseConnection $db;
private AdditionalChargeModel $additionalChargeModel;
private InvoiceLineModel $invoiceLineModel;
private InvoiceLedgerService $invoiceLedgerService;
public function __construct(
?\CodeIgniter\Database\BaseConnection $db = null,
?AdditionalChargeModel $additionalChargeModel = null,
?InvoiceLineModel $invoiceLineModel = null,
$invoiceLineModel = null,
?InvoiceLedgerService $invoiceLedgerService = null
) {
$this->db = $db ?? db_connect();
$this->additionalChargeModel = $additionalChargeModel ?? new AdditionalChargeModel();
$this->invoiceLineModel = $invoiceLineModel ?? new InvoiceLineModel();
$this->invoiceLedgerService = $invoiceLedgerService ?? new InvoiceLedgerService();
}
@@ -43,47 +40,14 @@ class InvoiceAdjustmentService
throw new \RuntimeException('Zero amount additional charges cannot be applied.');
}
$activeSourceKey = $this->activeSourceKey($chargeId);
$existing = $this->db->table('invoice_lines')
->where('active_source_key', $activeSourceKey)
->where('voided_at IS NULL', null, false)
->get()
->getRowArray();
if ($existing) {
throw new \RuntimeException('Additional charge already has an active invoice line.');
}
$now = utc_now();
$lineId = $this->invoiceLineModel->insert([
'invoice_id' => $invoiceId,
'school_year' => (string)$invoice['school_year'],
'line_type' => $amountCents > 0 ? 'additional_charge' : 'additional_deduction',
'source_type' => 'additional_charge',
'source_id' => $chargeId,
'active_source_key' => $activeSourceKey,
'description' => $this->chargeDescription($charge),
'quantity' => '1.00',
'unit_amount_cents' => $amountCents,
'line_amount_cents' => $amountCents,
'discount_eligible' => 0,
'calculation_version' => 'invoice_adjustment_v1',
'metadata_json' => json_encode([
'charge_type' => (string)($charge['charge_type'] ?? ''),
'applied_by' => $actorId,
], JSON_UNESCAPED_SLASHES),
'created_at' => $now,
'updated_at' => $now,
'voided_at' => null,
]);
$this->requireWrite($lineId, 'INVOICE_ADJUSTMENT_LINE_INSERT_FAILED', $this->invoiceLineModel);
$this->requireWrite($this->additionalChargeModel->update($chargeId, [
'status' => FinancialStatus::ADDITIONAL_CHARGE_APPLIED,
'invoice_id' => $invoiceId,
'parent_id' => (int)$invoice['parent_id'],
'school_year' => (string)$invoice['school_year'],
'semester' => (string)($invoice['semester'] ?? ''),
'applied_invoice_line_id' => (int)$lineId,
'applied_invoice_line_id' => null,
'applied_by' => $actorId > 0 ? $actorId : null,
'applied_at' => $now,
]), 'ADDITIONAL_CHARGE_APPLY_UPDATE_FAILED', $this->additionalChargeModel);
@@ -92,7 +56,7 @@ class InvoiceAdjustmentService
$this->requireTransactionStatus();
$this->db->transCommit();
return new InvoiceLedgerResult($invoiceId, (int)$lineId, $ledger);
return new InvoiceLedgerResult($invoiceId, 0, $ledger);
} catch (\Throwable $e) {
$this->db->transRollback();
throw $e;
@@ -118,50 +82,11 @@ class InvoiceAdjustmentService
$invoice = $this->lockInvoice($invoiceId);
$this->assertInvoiceAcceptsAdjustment($invoice);
$originalLineId = (int)($charge['applied_invoice_line_id'] ?? 0);
$originalLine = $originalLineId > 0
? $this->db->query('SELECT * FROM invoice_lines WHERE id = ? FOR UPDATE', [$originalLineId])->getRowArray()
: null;
if (!$originalLine) {
$originalLine = $this->db->query(
'SELECT * FROM invoice_lines WHERE source_type = ? AND source_id = ? AND voided_at IS NULL ORDER BY id ASC LIMIT 1 FOR UPDATE',
['additional_charge', $chargeId]
)->getRowArray();
}
if (!$originalLine) {
throw new \RuntimeException('Original invoice line not found.');
}
$reverseAmountCents = -1 * (int)($originalLine['line_amount_cents'] ?? 0);
if ($reverseAmountCents === 0) {
if ($this->signedChargeAmountCents($charge) === 0) {
throw new \RuntimeException('Zero amount additional charges cannot be reversed.');
}
$now = utc_now();
$lineId = $this->invoiceLineModel->insert([
'invoice_id' => $invoiceId,
'school_year' => (string)$invoice['school_year'],
'line_type' => $reverseAmountCents > 0 ? 'additional_charge_reversal' : 'additional_deduction_reversal',
'source_type' => 'additional_charge_reversal',
'source_id' => $chargeId,
'active_source_key' => null,
'description' => 'Reversal: ' . $this->chargeDescription($charge),
'quantity' => '1.00',
'unit_amount_cents' => $reverseAmountCents,
'line_amount_cents' => $reverseAmountCents,
'discount_eligible' => 0,
'calculation_version' => 'invoice_adjustment_v1',
'metadata_json' => json_encode([
'original_invoice_line_id' => (int)($originalLine['id'] ?? 0),
'reason' => $reason,
'reversed_by' => $actorId,
], JSON_UNESCAPED_SLASHES),
'created_at' => $now,
'updated_at' => $now,
'voided_at' => null,
]);
$this->requireWrite($lineId, 'INVOICE_ADJUSTMENT_REVERSAL_LINE_INSERT_FAILED', $this->invoiceLineModel);
$this->requireWrite($this->additionalChargeModel->update($chargeId, [
'status' => 'reversed',
'voided_by' => $actorId > 0 ? $actorId : null,
@@ -173,7 +98,7 @@ class InvoiceAdjustmentService
$this->requireTransactionStatus();
$this->db->transCommit();
return new InvoiceLedgerResult($invoiceId, (int)$lineId, $ledger);
return new InvoiceLedgerResult($invoiceId, 0, $ledger);
} catch (\Throwable $e) {
$this->db->transRollback();
throw $e;
@@ -226,11 +151,6 @@ class InvoiceAdjustmentService
return (string)($charge['charge_type'] ?? 'add') === 'deduct' ? -1 * $amount : $amount;
}
private function activeSourceKey(int $chargeId): string
{
return 'additional_charge:' . $chargeId;
}
private function chargeDescription(array $charge): string
{
$title = trim((string)($charge['title'] ?? 'Additional charge'));