-
DevPilot authored
Auditing the basics against the live books turned up four things that could never have worked, plus a fifth that worked too well. CLOSING A FISCAL YEAR WAS IMPOSSIBLE — three separate faults stacked: 1. `Database::execute()` did not exist. Six call sites used it — closing a period, setting the current fiscal year, setting the default bank account, rebuilding an account balance, deleting a facility blackout — and every one was a fatal the moment it ran. Added to Database rather than patched at the call sites. This alone is why period_closings was empty. 2. Closing a year requires every month closed first, then dates its entry the last day of the year — inside a month it just required to be closed. The two rules deadlocked. The closing entry, and only it, may now post there. 3. The same entry has to zero accounts retired during the year, which the inactive-account guard refused. Closing an account is exactly when you must be able to clear it. 4. And `period_closings.period` is varchar(7) while the year-end key is `YYYY-MM-YE` — ten characters — so the entry posted and the record failed, leaving the year half-closed. It now runs end to end: 19 lines, revenue and expenses zeroed to retained earnings, year marked closed, balance sheet still balanced. PAYROLL POSTED TWICE. Salaries are the largest single expense the club books and nothing guarded the reference, so a retried event or a second click doubled the expense, the withheld tax and the insurance liability. Every other posting in that service guards; this one did not. FIXED ASSETS COULD NOT BE CREATED AT ALL. The club carries 4,705,686 of machinery and 41,703,867 of construction in progress, and `asset_register` is empty — so nothing has ever depreciated. Not because depreciation is unwired, it posts correctly, but because there was no create route, no create method, and nothing anywhere that inserted an asset. `item_id` was NOT NULL against inventory_items, so registering a building meant inventing a stock row for it. Now: item_id and warehouse_id are nullable, the eight asset categories the chart already defines are seeded with their three GL accounts each, and there is a form. It distinguishes a purchase (posts Dr Asset / Cr Cash-or-Payable) from an opening-balance asset (posts nothing — the cost is already in the ledger, and posting it again would double the balance sheet). Asset disposal never worked either: `AccountCodes` was not imported, so the handler threw a fatal that the bootstrap's catch swallowed. Assets came off the register and stayed on the balance sheet. The register's own queries inner-joined inventory_items and warehouses, which would have hidden every real fixed asset behind an empty list and a "not found" page. LEFT joins, and the category name where there is no stock item. The depreciation month picker posted `month` while the controller read `period_month`, so whichever month the accountant chose was dropped and the current one ran instead. Overlapping fiscal years are now shown on the fiscal-years screen. The club carries calendar years alongside a July–June year, so entries in the overlap belong to both and closing one leaves the other open over the same transactions. Only the club can say which convention is real, so it is surfaced, not guessed. Verified on a clone of production with all 409 foreign keys: full asset cycle posts (purchase, depreciation by category, disposal with its 5,000 loss), all 1,489 routes resolve, 252 controllers instantiate, 241 services load, trial balance diff 0.00, balance sheet balanced. Co-Authored-By:Claude Opus 5 <noreply@anthropic.com>
33c76389