• Mahmoud Aglan's avatar
    fix(accounting): opening balances were double-counted in three reports · 2f77d3e1
    Mahmoud Aglan authored
    The opening figures live in TWO places in this ledger: the
    chart_of_accounts.opening_balance column AND 24 posted journal entries dated
    2024-07-01 with reference_type='opening', totalling 90,601,962.36.
    
    Three reports read the column and then also summed the ledger movement that
    already contained those same entries, counting the opening twice:
    
    - Trial balance (LedgerService::getTrialBalance)
    - General ledger  (LedgerService::getAccountLedger)
    - Balance sheet   (FinancialReportService::getBalanceSheet, and the
                       consolidated sheet which delegates to it)
    
    Measured on live data, trial balance over FY 2024/2025:
      1103 مشروعات تحت التنفيذ   reported 85,627,410.75  actual 43,923,543.75
      210201 أرباح مرحلة         reported -146,645,270   actual -73,322,635
    i.e. exactly double on every account carrying an opening balance. The report
    still footed, because opening balances net to zero across debit and credit —
    so it looked right and every line was wrong. Only periods containing
    2024-07-01 were affected; a 2026 trial balance was already correct.
    
    The opening column is now derived as cumulative posted movement BEFORE the
    period start, which is the standard definition, removes the double count
    structurally, and works for any period rather than only a year boundary. The
    trial balance query is also restructured into two independent aggregates so
    no row multiplication is possible and an account whose only movement predates
    the period still appears.
    
    Income statement was already correct and is unchanged.
    
    Also in this commit:
    
    - LedgerService::rebuildBalances() + a seed that runs it. The opening import
      wrote journal rows without going through JournalService, so 24 accounts had
      a cached current_balance disagreeing with the ledger — retained earnings
      cached 0.00 against an actual 73,322,635.00. The reports read the ledger and
      were fine, but the Chart of Accounts screen and the bank-reconciliation
      opening figure read the cache, which is precisely where an accountant would
      find a number contradicting the trial balance.
    
    - Carnet guest entry never posted. Accounting listened on
      'carnet.guest_entry_recorded'; GuestEntryService dispatches
      'carnet_guest.entry_recorded' (underscore, not dot). Notifications listens on
      the correct name, which is why notifications worked and the ledger entry
      never appeared. Fees were recorded in carnet_guest_entries.amount_paid and
      posted nowhere.
    
    - 'tournament.fee_collected' has no dispatcher anywhere. Documented as dead
      rather than left looking wired.
    
    - Two fiscal years were flagged is_current; the seed leaves exactly the one
      containing today. FiscalYear::findByDate now resolves overlapping years
      deterministically (open first, then narrowest range) instead of taking
      whatever the database returned — this chart has calendar years overlapping a
      July-June year, so Jul-Dec 2024 matches two. No entry is reassigned; all 795
      are already inside their assigned year.
    
    - PostingRouter and postViaRule now probe App::db() with try/catch. It is typed
      `: Database` and throws when unbound rather than returning null, so the
      previous null guards could never fire.
    Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    2f77d3e1
Name
Last commit
Last update
..
Academies Loading commit data...
AcademyContracts Loading commit data...
AccessMatrix Loading commit data...
Accounting Loading commit data...
Achievements Loading commit data...
ActivitySubscriptions Loading commit data...
Alerts Loading commit data...
Archive Loading commit data...
Audit Loading commit data...
Auth Loading commit data...
Branches Loading commit data...
Carnets Loading commit data...
Cashier Loading commit data...
Children Loading commit data...
Coaches Loading commit data...
Dashboard Loading commit data...
Death Loading commit data...
Disciplines Loading commit data...
Divorce Loading commit data...
Documents Loading commit data...
Facilities Loading commit data...
FacilityDashboards Loading commit data...
FacilityGrids Loading commit data...
Fines Loading commit data...
Foreign Loading commit data...
Forms Loading commit data...
HR Loading commit data...
Honorary Loading commit data...
Installments Loading commit data...
Interviews Loading commit data...
Inventory Loading commit data...
MatchCenter Loading commit data...
MedicalBoard Loading commit data...
Members Loading commit data...
News Loading commit data...
Notifications Loading commit data...
ParentPortal Loading commit data...
Payments Loading commit data...
PlayerAffairs Loading commit data...
PlayerApi Loading commit data...
PlayerAuth Loading commit data...
PlaygroundAdmin Loading commit data...
PoolManagement Loading commit data...
Pricing Loading commit data...
Procurement Loading commit data...
Receipts Loading commit data...
Rentals Loading commit data...
Reports Loading commit data...
Reservations Loading commit data...
Roles Loading commit data...
Rules Loading commit data...
Sales Loading commit data...
Scheduling Loading commit data...
Seasonal Loading commit data...
ServiceCatalog Loading commit data...
Sessions Loading commit data...
Settings Loading commit data...
Sports Loading commit data...
SportsActivity Loading commit data...
SportsDashboard Loading commit data...
Spouses Loading commit data...
Subscriptions Loading commit data...
Support Loading commit data...
Temporary Loading commit data...
Tournaments Loading commit data...
TrainerPortal Loading commit data...
Training Loading commit data...
TrainingGroups Loading commit data...
Transfers Loading commit data...
Treasury Loading commit data...
Tutorials Loading commit data...
Users Loading commit data...
Waiver Loading commit data...
Workflow Loading commit data...