mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Accounting: Sales Receipts, COA sync to dashboard, vendor-expense recognition
- Add Sales Receipts page (dashboard/accounting/sales-receipts): records a cash sale (name, address, income account, price, qty) — deposits and books income in one step via a transaction. New accounting.sales_receipts table. - Sync chart of accounts to the accounting dashboard: mirror accounting.accounts into public.chart_of_accounts for platform associations (one-way, same id) so Bill Approvals and every COA consumer use the dashboard's accounts. Legacy rows hidden; Bill Approvals made system-aware. - Vendor-expense recognition: a vendor payment with no bill now books the expense directly (Dr Expense / Cr Bank) on the payment date instead of going to A/P; payments against open bills still clear A/P (applied FIFO). Backfill reclassifies unbilled payments stuck in A/P. Expense Summary report made GL-driven so it follows the same rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1294,18 +1294,22 @@ function buildFlat(id: ReportId, d: any, cur: string): Flat | null {
|
||||
rows: d.customers.map((c: any) => [c.name, m(Number(c.balance ?? 0))]),
|
||||
};
|
||||
case "expense-summary": {
|
||||
const byCat: Record<string, number> = {};
|
||||
// Direct expenses from expenses table
|
||||
for (const e of d.expenses) byCat[e.category] = (byCat[e.category] ?? 0) + Number(e.amount);
|
||||
// Bill expenses (accrual — total billed, not just paid)
|
||||
for (const b of d.bills) {
|
||||
if (b.status === "void" || b.status === "draft") continue;
|
||||
const cat = b.vendors?.name ?? "Vendor Expenses";
|
||||
byCat[cat] = (byCat[cat] ?? 0) + Number(b.total);
|
||||
// GL-driven so it follows the same recognition rule as the P&L: a bill's
|
||||
// expense counts on the bill date (Dr Expense / Cr A/P), and a vendor payment
|
||||
// with no bill counts on the payment date (Dr Expense / Cr Bank). Reading the
|
||||
// ledger avoids double-counting and never misses direct payments.
|
||||
const byAcct: Record<string, number> = {};
|
||||
for (const l of (d.glLines ?? []) as any[]) {
|
||||
const acc = l.accounts;
|
||||
if (acc?.type !== "expense") continue;
|
||||
const amt = Number(l.debit) - Number(l.credit);
|
||||
if (amt === 0) continue;
|
||||
const name = acc.name ?? "Expense";
|
||||
byAcct[name] = (byAcct[name] ?? 0) + amt;
|
||||
}
|
||||
const rows = Object.entries(byCat).sort((a, b) => b[1] - a[1]).map(([cat, amt]) => [cat, m(amt)]);
|
||||
const total = Object.values(byCat).reduce((s, v) => s + v, 0);
|
||||
return { title: "Expense Summary (Accrual)", columns: ["Category / Vendor", "Amount"], rows: [...rows, ["TOTAL", m(total)]], boldRows: [rows.length] };
|
||||
const rows = Object.entries(byAcct).sort((a, b) => b[1] - a[1]).map(([acct, amt]) => [acct, m(amt)]);
|
||||
const total = Object.values(byAcct).reduce((s, v) => s + v, 0);
|
||||
return { title: "Expense Summary (Accrual)", columns: ["Expense Account", "Amount"], rows: [...rows, ["TOTAL", m(total)]], boldRows: [rows.length] };
|
||||
}
|
||||
case "vendor-balances": {
|
||||
const byVendor: Record<string, number> = {};
|
||||
|
||||
Reference in New Issue
Block a user