fix parent pages and teachers and admin
API CI/CD / Validate (composer + pint) (push) Successful in 3m8s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Test (PHPUnit) (push) Failing after 6m5s
API CI/CD / Security audit (push) Failing after 52s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
API CI/CD / Validate (composer + pint) (push) Successful in 3m8s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Test (PHPUnit) (push) Failing after 6m5s
API CI/CD / Security audit (push) Failing after 52s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Models\ParentAccount;
|
||||
use App\Models\ParentBalanceTransfer;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ParentInvoiceService
|
||||
{
|
||||
@@ -14,6 +19,311 @@ class ParentInvoiceService
|
||||
->orderByDesc('created_at')
|
||||
->get();
|
||||
|
||||
return $rows->toArray();
|
||||
$invoiceIds = $rows
|
||||
->pluck('id')
|
||||
->map(fn ($id) => (int) $id)
|
||||
->filter(fn ($id) => $id > 0)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$paymentsByInvoice = $this->paymentsByInvoice($parentId, $invoiceIds);
|
||||
$adjustments = $this->invoiceAdjustments($invoiceIds);
|
||||
|
||||
return $rows
|
||||
->map(function (Invoice $invoice) use ($paymentsByInvoice, $adjustments): array {
|
||||
$invoiceRow = $invoice->toArray();
|
||||
$invoiceId = (int) $invoice->id;
|
||||
$payments = $paymentsByInvoice[(int) $invoice->id] ?? [];
|
||||
$paidAmount = round(array_sum(array_map(
|
||||
static fn (array $payment): float => (float) ($payment['paid_amount'] ?? 0),
|
||||
$payments
|
||||
)), 2);
|
||||
$discountAmount = round((float) ($adjustments['discounts'][$invoiceId] ?? 0), 2);
|
||||
$refundAmount = round((float) ($adjustments['refunds'][$invoiceId] ?? 0), 2);
|
||||
$totalAmount = round((float) ($invoice->total_amount ?? 0), 2);
|
||||
$balance = max(0.0, round($totalAmount - $paidAmount - $discountAmount - $refundAmount, 2));
|
||||
|
||||
$invoiceRow['payments'] = $payments;
|
||||
$invoiceRow['paid_amount'] = $paidAmount;
|
||||
$invoiceRow['discount'] = $discountAmount;
|
||||
$invoiceRow['refund_amount'] = $refundAmount;
|
||||
$invoiceRow['balance'] = $balance;
|
||||
$invoiceRow['last_paid_amount'] = (float) ($payments[0]['paid_amount'] ?? 0);
|
||||
$invoiceRow['last_payment_date'] = $payments[0]['payment_date'] ?? null;
|
||||
$invoiceRow['status'] = $this->deriveInvoiceStatus($invoice->status, $paidAmount, $balance);
|
||||
|
||||
return $invoiceRow;
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
public function statement(int $parentId, ?string $schoolYear = null): array
|
||||
{
|
||||
$parent = User::query()->find($parentId);
|
||||
$account = $schoolYear
|
||||
? ParentAccount::query()
|
||||
->where('parent_id', $parentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->first()
|
||||
: null;
|
||||
|
||||
$columns = [
|
||||
'id',
|
||||
'invoice_number',
|
||||
'total_amount',
|
||||
'paid_amount',
|
||||
'balance',
|
||||
'status',
|
||||
'description',
|
||||
'school_year',
|
||||
];
|
||||
|
||||
if (Schema::hasColumn('invoices', 'invoice_type')) {
|
||||
$columns[] = 'invoice_type';
|
||||
}
|
||||
if (Schema::hasColumn('invoices', 'balance_transfer_id')) {
|
||||
$columns[] = 'balance_transfer_id';
|
||||
}
|
||||
|
||||
$invoices = Invoice::query()
|
||||
->where('parent_id', $parentId)
|
||||
->when($schoolYear, fn ($q) => $q->where('school_year', $schoolYear))
|
||||
->orderBy('created_at')
|
||||
->get($columns);
|
||||
|
||||
$transferSourceYears = $this->transferSourceYears($invoices->pluck('balance_transfer_id')->filter()->all());
|
||||
$invoiceIds = $invoices
|
||||
->pluck('id')
|
||||
->map(fn ($id) => (int) $id)
|
||||
->filter(fn ($id) => $id > 0)
|
||||
->values()
|
||||
->all();
|
||||
$adjustments = $this->invoiceAdjustments($invoiceIds);
|
||||
|
||||
$lines = $invoices
|
||||
->map(function (Invoice $invoice) use ($transferSourceYears, $adjustments): array {
|
||||
$transferId = (int) ($invoice->balance_transfer_id ?? 0);
|
||||
$invoiceId = (int) $invoice->id;
|
||||
$totalAmount = round((float) ($invoice->total_amount ?? 0), 2);
|
||||
$paidAmount = round((float) ($adjustments['payments'][$invoiceId] ?? $invoice->paid_amount ?? 0), 2);
|
||||
$discountAmount = round((float) ($adjustments['discounts'][$invoiceId] ?? 0), 2);
|
||||
$refundAmount = round((float) ($adjustments['refunds'][$invoiceId] ?? 0), 2);
|
||||
$balance = max(0.0, round($totalAmount - $paidAmount - $discountAmount - $refundAmount, 2));
|
||||
|
||||
return [
|
||||
'label' => $invoice->description ?: ($invoice->invoice_number ?: sprintf('Invoice #%d', $invoice->id)),
|
||||
'amount' => $balance,
|
||||
'source_school_year' => $transferId > 0
|
||||
? ($transferSourceYears[$transferId] ?? $invoice->school_year)
|
||||
: $invoice->school_year,
|
||||
'invoice_id' => $invoice->id,
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$openingBalance = round((float) ($account?->opening_balance ?? 0), 2);
|
||||
$currentBalance = round($openingBalance + array_sum(array_map(
|
||||
static fn (array $line): float => (float) ($line['amount'] ?? 0),
|
||||
$lines
|
||||
)), 2);
|
||||
|
||||
return [
|
||||
'parent_id' => $parentId,
|
||||
'parent_name' => $this->parentName($parent),
|
||||
'school_year' => $schoolYear,
|
||||
'lines' => $lines,
|
||||
'opening_balance' => $openingBalance,
|
||||
'current_balance' => round($currentBalance, 2),
|
||||
];
|
||||
}
|
||||
|
||||
private function parentName(?User $parent): ?string
|
||||
{
|
||||
if (! $parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = array_filter([
|
||||
trim((string) ($parent->firstname ?? '')),
|
||||
trim((string) ($parent->lastname ?? '')),
|
||||
]);
|
||||
|
||||
if ($parts !== []) {
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
return $parent->name ?? $parent->email ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $invoiceIds
|
||||
* @return array<int, list<array<string, mixed>>>
|
||||
*/
|
||||
private function paymentsByInvoice(int $parentId, array $invoiceIds): array
|
||||
{
|
||||
if ($invoiceIds === [] || ! Schema::hasTable('payments')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$columns = [
|
||||
'id',
|
||||
'parent_id',
|
||||
'invoice_id',
|
||||
'paid_amount',
|
||||
Schema::hasColumn('payments', 'balance') ? 'balance' : DB::raw('NULL AS balance'),
|
||||
Schema::hasColumn('payments', 'payment_method') ? 'payment_method' : DB::raw('NULL AS payment_method'),
|
||||
Schema::hasColumn('payments', 'payment_date') ? 'payment_date' : DB::raw('NULL AS payment_date'),
|
||||
Schema::hasColumn('payments', 'transaction_id') ? 'transaction_id' : DB::raw('NULL AS transaction_id'),
|
||||
Schema::hasColumn('payments', 'status') ? 'status' : DB::raw('NULL AS status'),
|
||||
Schema::hasColumn('payments', 'school_year') ? 'school_year' : DB::raw('NULL AS school_year'),
|
||||
Schema::hasColumn('payments', 'semester') ? 'semester' : DB::raw('NULL AS semester'),
|
||||
Schema::hasColumn('payments', 'created_at') ? 'created_at' : DB::raw('NULL AS created_at'),
|
||||
];
|
||||
|
||||
$query = DB::table('payments')
|
||||
->select($columns)
|
||||
->where('parent_id', $parentId)
|
||||
->whereIn('invoice_id', $invoiceIds);
|
||||
|
||||
if (Schema::hasColumn('payments', 'status')) {
|
||||
$query->whereNotIn(DB::raw('LOWER(status)'), ['pending', 'void', 'voided', 'cancelled', 'canceled', 'failed', 'rejected']);
|
||||
}
|
||||
if (Schema::hasColumn('payments', 'payment_date')) {
|
||||
$query->orderByDesc('payment_date');
|
||||
}
|
||||
if (Schema::hasColumn('payments', 'created_at')) {
|
||||
$query->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
$rows = $query
|
||||
->get()
|
||||
->map(static fn ($row): array => [
|
||||
'id' => (int) $row->id,
|
||||
'parent_id' => (int) $row->parent_id,
|
||||
'invoice_id' => (int) $row->invoice_id,
|
||||
'paid_amount' => (float) $row->paid_amount,
|
||||
'balance' => (float) $row->balance,
|
||||
'payment_method' => $row->payment_method,
|
||||
'payment_date' => $row->payment_date,
|
||||
'transaction_id' => $row->transaction_id,
|
||||
'status' => $row->status,
|
||||
'school_year' => $row->school_year,
|
||||
'semester' => $row->semester,
|
||||
'created_at' => $row->created_at,
|
||||
])
|
||||
->all();
|
||||
|
||||
$grouped = [];
|
||||
foreach ($rows as $row) {
|
||||
$grouped[(int) $row['invoice_id']][] = $row;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $invoiceIds
|
||||
* @return array{payments: array<int, float>, discounts: array<int, float>, refunds: array<int, float>}
|
||||
*/
|
||||
private function invoiceAdjustments(array $invoiceIds): array
|
||||
{
|
||||
$invoiceIds = array_values(array_unique(array_filter(array_map('intval', $invoiceIds))));
|
||||
$adjustments = [
|
||||
'payments' => [],
|
||||
'discounts' => [],
|
||||
'refunds' => [],
|
||||
];
|
||||
|
||||
if ($invoiceIds === []) {
|
||||
return $adjustments;
|
||||
}
|
||||
|
||||
if (Schema::hasTable('payments')) {
|
||||
$paymentQuery = DB::table('payments')
|
||||
->selectRaw('invoice_id, COALESCE(SUM(paid_amount),0) AS total')
|
||||
->whereIn('invoice_id', $invoiceIds);
|
||||
|
||||
if (Schema::hasColumn('payments', 'status')) {
|
||||
$paymentQuery->where(function ($query) {
|
||||
$query->whereNotIn(DB::raw('LOWER(status)'), [
|
||||
'pending',
|
||||
'void',
|
||||
'voided',
|
||||
'cancelled',
|
||||
'canceled',
|
||||
'failed',
|
||||
'rejected',
|
||||
'refunded',
|
||||
'chargeback',
|
||||
'declined',
|
||||
'reversed',
|
||||
])->orWhereNull('status');
|
||||
});
|
||||
}
|
||||
|
||||
$adjustments['payments'] = $paymentQuery
|
||||
->groupBy('invoice_id')
|
||||
->pluck('total', 'invoice_id')
|
||||
->map(fn ($value) => (float) $value)
|
||||
->all();
|
||||
}
|
||||
|
||||
if (Schema::hasTable('discount_usages')) {
|
||||
$adjustments['discounts'] = DB::table('discount_usages')
|
||||
->selectRaw('invoice_id, COALESCE(SUM(discount_amount),0) AS total')
|
||||
->whereIn('invoice_id', $invoiceIds)
|
||||
->groupBy('invoice_id')
|
||||
->pluck('total', 'invoice_id')
|
||||
->map(fn ($value) => (float) $value)
|
||||
->all();
|
||||
}
|
||||
|
||||
if (Schema::hasTable('refunds')) {
|
||||
$adjustments['refunds'] = DB::table('refunds')
|
||||
->selectRaw('invoice_id, COALESCE(SUM(refund_paid_amount),0) AS total')
|
||||
->whereIn('invoice_id', $invoiceIds)
|
||||
->whereIn('status', ['Partial', 'Paid'])
|
||||
->groupBy('invoice_id')
|
||||
->pluck('total', 'invoice_id')
|
||||
->map(fn ($value) => (float) $value)
|
||||
->all();
|
||||
}
|
||||
|
||||
return $adjustments;
|
||||
}
|
||||
|
||||
private function deriveInvoiceStatus(?string $storedStatus, float $paidAmount, float $balance): string
|
||||
{
|
||||
if ($paidAmount <= 0 && $balance > 0) {
|
||||
return 'Unpaid';
|
||||
}
|
||||
|
||||
if ($balance <= 0) {
|
||||
return 'Paid';
|
||||
}
|
||||
|
||||
if ($paidAmount > 0) {
|
||||
return 'Partially Paid';
|
||||
}
|
||||
|
||||
return $storedStatus ?: 'Unpaid';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string> $transferIds
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
private function transferSourceYears(array $transferIds): array
|
||||
{
|
||||
$ids = array_values(array_unique(array_map('intval', $transferIds)));
|
||||
if ($ids === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ParentBalanceTransfer::query()
|
||||
->whereIn('id', $ids)
|
||||
->pluck('from_school_year', 'id')
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user