e0dfc3ec82
API CI/CD / Validate (composer + pint) (push) Successful in 3m15s
API CI/CD / Test (PHPUnit) (push) Failing after 5m4s
API CI/CD / Build frontend assets (push) Successful in 1m3s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
122 lines
3.6 KiB
PHP
122 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Billing;
|
|
|
|
use App\Models\AdditionalCharge;
|
|
use App\Models\EventCharges;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class BillingTotalsService
|
|
{
|
|
public function eventSubtotal(int $parentId, string $schoolYear): float
|
|
{
|
|
return round(array_sum($this->eventSubtotalsByStudent($parentId, $schoolYear)), 2);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, float> student_id => amount
|
|
*/
|
|
public function eventSubtotalsByStudent(int $parentId, string $schoolYear): array
|
|
{
|
|
$byStudent = [];
|
|
|
|
try {
|
|
$events = EventCharges::getChargesWithEventInfo($parentId, $schoolYear) ?? [];
|
|
foreach ($events as $event) {
|
|
$amount = (float) ($event['charged'] ?? 0);
|
|
if ($amount <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$studentId = (int) ($event['student_id'] ?? 0);
|
|
if ($studentId > 0) {
|
|
$byStudent[$studentId] = ($byStudent[$studentId] ?? 0.0) + $amount;
|
|
} else {
|
|
$byStudent[0] = ($byStudent[0] ?? 0.0) + $amount;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Log::warning('Failed to sum event charges.', ['error' => $e->getMessage()]);
|
|
}
|
|
|
|
foreach ($byStudent as $studentId => $amount) {
|
|
$byStudent[$studentId] = round($amount, 2);
|
|
}
|
|
|
|
return $byStudent;
|
|
}
|
|
|
|
public function additionalSubtotal(int $invoiceId, ?string $schoolYear = null): float
|
|
{
|
|
return $this->sumAdditionalChargeRows(
|
|
$this->fetchAdditionalChargeRows(invoiceId: $invoiceId, schoolYear: $schoolYear)
|
|
);
|
|
}
|
|
|
|
public function additionalSubtotalByParent(int $parentId, string $schoolYear): float
|
|
{
|
|
return $this->sumAdditionalChargeRows(
|
|
$this->fetchAdditionalChargeRows(parentId: $parentId, schoolYear: $schoolYear)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{charge_type: string, amount: float}>
|
|
*/
|
|
private function fetchAdditionalChargeRows(
|
|
?int $parentId = null,
|
|
?int $invoiceId = null,
|
|
?string $schoolYear = null
|
|
): array {
|
|
$rows = [];
|
|
|
|
try {
|
|
$builder = DB::table((new AdditionalCharge)->getTable())
|
|
->select('charge_type', 'amount')
|
|
->where('status', 'applied');
|
|
|
|
if ($parentId !== null) {
|
|
$builder->where('parent_id', $parentId);
|
|
}
|
|
|
|
if ($invoiceId !== null) {
|
|
$builder->where('invoice_id', $invoiceId);
|
|
}
|
|
|
|
if ($schoolYear !== null && $schoolYear !== '') {
|
|
$builder->where('school_year', $schoolYear);
|
|
}
|
|
|
|
$rows = $builder->get()->map(fn ($row) => (array) $row)->all();
|
|
} catch (\Throwable $e) {
|
|
Log::warning('Failed to load additional charges.', ['error' => $e->getMessage()]);
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array{charge_type?: string, amount?: float|int|string}> $rows
|
|
*/
|
|
private function sumAdditionalChargeRows(array $rows): float
|
|
{
|
|
$additionalSubtotal = 0.0;
|
|
|
|
foreach ($rows as $row) {
|
|
$amount = (float) ($row['amount'] ?? 0);
|
|
$type = strtolower((string) ($row['charge_type'] ?? 'add'));
|
|
|
|
if (in_array($type, ['deduct', 'previous'], true)) {
|
|
$amount = -abs($amount);
|
|
} else {
|
|
$amount = abs($amount);
|
|
}
|
|
|
|
$additionalSubtotal += $amount;
|
|
}
|
|
|
|
return round($additionalSubtotal, 2);
|
|
}
|
|
}
|