mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
d82466f826
- 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>
94 lines
3.4 KiB
PL/PgSQL
94 lines
3.4 KiB
PL/PgSQL
-- Create a paid bill for a vendor payment transaction that has no attached bill.
|
|
-- Such payments post Dr Accounts Payable / Cr Bank (the Banking flow assumes a
|
|
-- bill exists); with no bill, A/P is left negative and the expense is never
|
|
-- recognized. The auto-bill posts Dr Expense / Cr A/P, clearing the A/P and
|
|
-- booking the expense (net: Dr Expense / Cr Bank). Idempotent + safe to re-run.
|
|
--
|
|
-- NOTE: superseded for the Banking flow by 20260603200530 (vendor payments now
|
|
-- book the expense directly). This function is retained for reference/backfill.
|
|
create or replace function accounting.create_bill_for_payment_txn(_txn_id uuid)
|
|
returns uuid
|
|
language plpgsql
|
|
security definer
|
|
set search_path to 'public','accounting'
|
|
as $$
|
|
declare
|
|
t accounting.transactions%rowtype;
|
|
_exp uuid;
|
|
_num text;
|
|
_n int;
|
|
_bill_id uuid;
|
|
begin
|
|
select * into t from accounting.transactions where id = _txn_id;
|
|
if not found then return null; end if;
|
|
|
|
if t.type <> 'debit' or t.vendor_id is null or t.coa_account_id is not null
|
|
or t.transfer_id is not null or t.deposit_id is not null then
|
|
return null;
|
|
end if;
|
|
if coalesce(t.amount, 0) <= 0 then return null; end if;
|
|
if exists (select 1 from accounting.bills b where b.source_payment_id = t.id) then
|
|
return null;
|
|
end if;
|
|
|
|
select a.id into _exp
|
|
from accounting.accounts a
|
|
where a.company_id = t.company_id and a.name = t.category and a.type = 'expense'
|
|
limit 1;
|
|
if _exp is null then _exp := accounting.coa_default_expense(t.company_id); end if;
|
|
|
|
select count(*) into _n from accounting.bills where company_id = t.company_id and auto_created;
|
|
_num := 'AUTO-BILL-' || lpad((_n + 1)::text, 4, '0');
|
|
|
|
insert into accounting.bills
|
|
(company_id, vendor_id, number, issue_date, due_date,
|
|
subtotal, tax, total, paid_amount, status, notes,
|
|
auto_created, source_payment_id, source_payment_kind)
|
|
values
|
|
(t.company_id, t.vendor_id, _num, t.date, t.date,
|
|
t.amount, 0, t.amount, t.amount, 'paid',
|
|
'Auto-created from bank payment with no attached bill',
|
|
true, t.id, 'transaction')
|
|
returning id into _bill_id;
|
|
|
|
insert into accounting.bill_items (bill_id, description, quantity, rate, amount, account_id)
|
|
values (_bill_id, coalesce(nullif(t.category, ''), 'Auto-created payment line'),
|
|
1, t.amount, t.amount, _exp);
|
|
|
|
return _bill_id;
|
|
end;
|
|
$$;
|
|
|
|
-- Backfill existing unattached vendor payments + best-effort link a matching check.
|
|
do $$
|
|
declare r record; _bill uuid;
|
|
begin
|
|
for r in
|
|
select t.id, t.company_id, t.vendor_id, t.amount, t.date
|
|
from accounting.transactions t
|
|
where t.type = 'debit'
|
|
and t.vendor_id is not null
|
|
and t.coa_account_id is null
|
|
and t.transfer_id is null and t.deposit_id is null
|
|
and coalesce(t.description, '') not ilike 'Bill Payment%'
|
|
and not exists (select 1 from accounting.bills b where b.source_payment_id = t.id)
|
|
order by t.date, t.id
|
|
loop
|
|
_bill := accounting.create_bill_for_payment_txn(r.id);
|
|
if _bill is not null then
|
|
update accounting.checks c
|
|
set auto_bill_id = _bill, updated_at = now()
|
|
where c.id = (
|
|
select c2.id from accounting.checks c2
|
|
where c2.company_id = r.company_id
|
|
and c2.payee_vendor_id = r.vendor_id
|
|
and c2.amount = r.amount
|
|
and c2.date = r.date
|
|
and c2.source_bill_id is null
|
|
and c2.auto_bill_id is null
|
|
limit 1
|
|
);
|
|
end if;
|
|
end loop;
|
|
end $$;
|