add new ststs feature
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 51s
Tests / PHPUnit (push) Successful in 1m25s

This commit is contained in:
root
2026-09-13 02:20:14 -04:00
parent 1787144f27
commit c3a30989b2
16 changed files with 1395 additions and 84 deletions
+45 -69
View File
@@ -11,6 +11,7 @@ use App\Models\ReimbursementModel;
use App\Models\UserModel;
use App\Libraries\FinancialStatus;
use App\Libraries\InvoiceLedgerService;
use App\Services\FinancialCategorySummaryService;
require_once APPPATH . 'ThirdParty/fpdf/fpdf.php';
use FPDF;
@@ -340,10 +341,16 @@ public function financialReport()
unset($invoiceRow);
// === Expenses ===
$expenses = $expenseModel
$expenseRows = $expenseModel
->select('category, SUM(amount) AS total_amount')
->groupBy('category')
->findAll();
$categorySummary = FinancialCategorySummaryService::summarize($expenseRows);
$expenses = [];
foreach ($categorySummary['expenseCategories'] as $category => $amount) {
$expenses[] = ['category' => $category, 'total_amount' => $amount];
}
$donationToSchool = $categorySummary['donationIncome'];
// === Reimbursements ===
$reimbursements = $reimbursementModel
@@ -370,6 +377,7 @@ public function financialReport()
'paymentTotals' => $paymentTotals,
'refunds' => $refunds,
'expenses' => $expenses,
'donationToSchool' => $donationToSchool,
'reimbursements' => $reimbursements,
'discounts' => $discounts,
'eventFeesTotal' => $eventFeesTotal,
@@ -385,6 +393,7 @@ public function financialReport()
'paymentTotals' => $paymentTotals, // summary totals + grand total
'refunds' => $refunds,
'expenses' => $expenses,
'donationToSchool' => $donationToSchool,
'reimbursements' => $reimbursements,
'discounts' => $discounts,
'eventFeesTotal' => $eventFeesTotal,
@@ -457,7 +466,7 @@ public function financialReport()
['Total Refunds', (float)($data['totalRefunds'] ?? 0)],
['Total Expenses', (float)($data['totalExpenses'] ?? 0)],
['Total Reimbursements', (float)($data['totalReimbursements'] ?? 0)],
['Donation to School (included in expenses)', (float)($data['donationToSchool'] ?? 0)],
['Donation Income to School', (float)($data['donationToSchool'] ?? 0)],
['Net Charges After Discounts/Refunds', (float)($data['netAmount'] ?? 0)],
['Amount Collected (Paid)', (float)($data['amountCollected'] ?? 0)],
['Overpayment Credits', (float)($data['totalOverpaid'] ?? 0)],
@@ -534,7 +543,7 @@ public function financialReport()
['Total Refunds' => (float)($summary['totalRefunds'] ?? 0)],
['Total Expenses' => (float)($summary['totalExpenses'] ?? 0)],
['Total Reimbursements' => (float)($summary['totalReimbursements'] ?? 0)],
['Donation to School (included in expenses)' => (float)($summary['donationToSchool'] ?? 0)],
['Donation Income to School' => (float)($summary['donationToSchool'] ?? 0)],
['Net Charges After Discounts/Refunds' => (float)($summary['netAmount'] ?? 0)],
['Amount Collected (Paid)' => (float)($summary['amountCollected'] ?? 0)],
['Overpayment Credits' => (float)($summary['totalOverpaid'] ?? 0)],
@@ -572,8 +581,8 @@ public function financialReport()
'Parents With Outstanding Balance Details' => $sections['unpaidParents'] ?? [],
'Expense Details' => $sections['expenses'] ?? [],
'Reimbursement Details' => $sections['reimbursements'] ?? [],
'Donation Expense Details' => $sections['donationExpenses'] ?? [],
'Donation Reimbursement Details' => $sections['donationReimbursements'] ?? [],
'Donation Income Details' => $sections['donationExpenses'] ?? [],
'Special Recipient Reimbursement Details' => $sections['donationReimbursements'] ?? [],
'Reimbursement Batch Fallback Details' => $sections['reimbursementBatchFallbacks'] ?? [],
'Overpayment Credit Details' => $summary['overpaymentDetails'] ?? [],
];
@@ -883,6 +892,9 @@ public function financialReport()
$donationExpenses = array_values(array_filter($expenses, static function ($row) {
return strtolower(trim((string)($row['Category'] ?? ''))) === 'donation';
}));
$expenses = array_values(array_filter($expenses, static function ($row) {
return strtolower(trim((string)($row['Category'] ?? ''))) !== 'donation';
}));
$donationReimbursements = [];
if (!empty($specialRecipientIds)) {
@@ -1319,7 +1331,13 @@ public function financialReport()
if (!empty($dateTo)) {
$expBuilder->where('DATE(created_at) <=', $dateTo);
}
$expenses = $expBuilder->groupBy('category')->findAll();
$expenseRows = $expBuilder->groupBy('category')->findAll();
$categorySummary = FinancialCategorySummaryService::summarize($expenseRows);
$expenses = [];
foreach ($categorySummary['expenseCategories'] as $category => $amount) {
$expenses[] = ['category' => $category, 'total_amount' => $amount];
}
$donationIncome = $categorySummary['donationIncome'];
// Reimbursements summary with filters
$reimbBuilder = $reimbursementModel
@@ -1412,6 +1430,10 @@ public function financialReport()
'',
]);
// Donation income is incoming money, not an expense.
fputcsv($out, []);
fputcsv($out, ['Donation Income to School', $donationIncome]);
// Expenses section
fputcsv($out, []); // blank line
fputcsv($out, ['Expenses Summary']);
@@ -1469,7 +1491,7 @@ public function financialReport()
'Total Refunds' => $data['totalRefunds'],
'Total Expenses' => $data['totalExpenses'],
'Total Reimbursements' => $data['totalReimbursements'],
'Donation to School (Masjid/Donation reimbursements)' => $data['donationToSchool'] ?? 0,
'Donation Income to School' => $data['donationToSchool'] ?? 0,
'Net Amount (Earned Income)' => $data['netAmount'],
'Amount Collected (Paid)' => $data['amountCollected'],
'Amount Unpaid (Outstanding)' => $data['totalUnpaid'],
@@ -1673,16 +1695,25 @@ public function financialReport()
$totalCharges += $extraChargesUnapplied;
// === Expenses ===
$expenseBuilder = $expenseModel->where('school_year', $schoolYear);
// Donation rows live in the legacy expenses table but represent incoming
// funds. Classify them separately so they never inflate school expenses.
$expenseCategoryBuilder = $db->table('expenses')
->select('category, COALESCE(SUM(amount), 0) AS amount', false)
->where('school_year', $schoolYear)
->groupBy('category')
->orderBy('amount', 'DESC');
if (!empty($invoiceDateFrom)) {
$expenseBuilder->where('DATE(created_at) >=', $invoiceDateFrom);
$expenseCategoryBuilder->where('DATE(created_at) >=', $invoiceDateFrom);
}
if (!empty($invoiceDateTo)) {
$expenseBuilder->where('DATE(created_at) <=', $invoiceDateTo);
$expenseCategoryBuilder->where('DATE(created_at) <=', $invoiceDateTo);
}
$expenseResult = $expenseBuilder->selectSum('amount')->get()->getRowArray();
$totalExpenses = isset($expenseResult['amount']) ? (float) $expenseResult['amount'] : 0.00;
$categorySummary = FinancialCategorySummaryService::summarize(
$expenseCategoryBuilder->get()->getResultArray()
);
$totalExpenses = $categorySummary['totalExpenses'];
$donationToSchool = $categorySummary['donationIncome'];
$expenseCategories = $categorySummary['expenseCategories'];
// === Reimbursements ===
// Reimbursements: include rows missing school_year by falling back to expense.school_year
@@ -1754,62 +1785,6 @@ public function financialReport()
$fallbackAmount = isset($batchFallbackRow['amount']) ? (float) $batchFallbackRow['amount'] : 0.00;
$totalReimbursements += $fallbackAmount;
// Donations line: donations captured as expenses (category=Donation) plus legacy Masjid/Donation reimbursements.
$donationExpense = 0.0;
$donationExpenseBuilder = (new ExpenseModel())
->where('school_year', $schoolYear)
->where('category', 'Donation');
if (!empty($invoiceDateFrom)) {
$donationExpenseBuilder->where('DATE(created_at) >=', $invoiceDateFrom);
}
if (!empty($invoiceDateTo)) {
$donationExpenseBuilder->where('DATE(created_at) <=', $invoiceDateTo);
}
$donationExpenseRow = $donationExpenseBuilder->selectSum('amount')->get()->getRowArray();
$donationExpense = isset($donationExpenseRow['amount']) ? (float) $donationExpenseRow['amount'] : 0.00;
$donationReimb = 0.0;
$specialRecipientIds = array_map('intval', array_keys(ReimbursementController::SPECIAL_RECIPIENTS));
if (!empty($specialRecipientIds)) {
$donationBuilder = $db->table('reimbursements r')
->select('SUM(r.amount) AS amount')
->join('expenses e', 'e.id = r.expense_id', 'left')
->whereIn('r.reimbursed_to', $specialRecipientIds);
if (!empty($schoolYear)) {
$donationBuilder
->groupStart()
->groupStart()
->where('r.school_year', $schoolYear)
->orWhere(
"REPLACE(REPLACE(REPLACE(REPLACE(r.school_year, ' ', ''), '-', ''), '/', ''), '\\\\', '')",
$normalizedYear
)
->groupEnd()
->orGroupStart()
->where('r.school_year IS NULL', null, false)
->groupStart()
->where('e.school_year', $schoolYear)
->orWhere(
"REPLACE(REPLACE(REPLACE(REPLACE(e.school_year, ' ', ''), '-', ''), '/', ''), '\\\\', '')",
$normalizedYear
)
->groupEnd()
->groupEnd()
->groupEnd();
}
if (!empty($invoiceDateFrom)) {
$donationBuilder->where('DATE(COALESCE(r.created_at, e.created_at)) >=', $invoiceDateFrom);
}
if (!empty($invoiceDateTo)) {
$donationBuilder->where('DATE(COALESCE(r.created_at, e.created_at)) <=', $invoiceDateTo);
}
$donationRow = $donationBuilder->get()->getRowArray();
$donationReimb = isset($donationRow['amount']) ? (float) $donationRow['amount'] : 0.00;
}
$donationToSchool = $donationExpense + $donationReimb;
$totalReimbursements = max(0.0, $totalReimbursements - $donationReimb);
// === Net, Outstanding & Overpayments ===
$overpaymentDetails = [];
$totalUnpaid = 0.0;
@@ -1884,6 +1859,7 @@ public function financialReport()
'totalDiscounts' => $totalDiscounts,
'totalRefunds' => $totalRefunds,
'totalExpenses' => $totalExpenses,
'expenseCategories' => $expenseCategories,
'totalReimbursements' => $totalReimbursements,
'donationToSchool' => $donationToSchool,
'totalPaid' => $totalPaid,