mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
Accounting: back-sync bill paid status to bill_approvals on INSERT
trg_acct_bill_paid_back was AFTER UPDATE only, so bills mirrored into accounting already-paid never triggered the back-sync that flips public.bills + bill_approvals to 'paid'. Fire the trigger on INSERT too and reconcile existing already-paid mirrored bills. Also backfill invoice-track approvals that uniquely match a bill (bill_id was null). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
-- Fix: bill_approvals not reflecting "paid" when an accounting.bills row is
|
||||||
|
-- created already-paid.
|
||||||
|
--
|
||||||
|
-- Background (see 20260601140000_accounting_sync_bills.sql):
|
||||||
|
-- accounting.sync_accounting_bill_paid() flips public.bills + public.bill_approvals
|
||||||
|
-- to 'paid' once the matching accounting bill is fully paid. It runs from the
|
||||||
|
-- trigger trg_acct_bill_paid_back, which was AFTER UPDATE only.
|
||||||
|
--
|
||||||
|
-- When a public bill is ALREADY paid at the moment it first mirrors into
|
||||||
|
-- accounting, the forward sync INSERTs the accounting row at status='paid'
|
||||||
|
-- with paid_amount set. With no subsequent UPDATE to paid_amount/total, the
|
||||||
|
-- UPDATE-only trigger never fired, so the bill's approvals were left stuck at
|
||||||
|
-- 'approved'/'pending'. This widens the back-sync to also run on INSERT.
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Back-sync trigger: now fires on INSERT as well as UPDATE.
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
create or replace function accounting.tg_accounting_bill_paid_sync()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql security definer set search_path to 'public','accounting'
|
||||||
|
as $$
|
||||||
|
begin
|
||||||
|
begin
|
||||||
|
if tg_op = 'INSERT' then
|
||||||
|
-- a bill mirrored in already-paid never produces an UPDATE; handle it here
|
||||||
|
perform accounting.sync_accounting_bill_paid(new.id);
|
||||||
|
-- on UPDATE only act when the paid position actually changed, to avoid loops
|
||||||
|
elsif old.paid_amount is distinct from new.paid_amount
|
||||||
|
or old.total is distinct from new.total then
|
||||||
|
perform accounting.sync_accounting_bill_paid(new.id);
|
||||||
|
end if;
|
||||||
|
exception when others then
|
||||||
|
raise warning 'accounting: accounting bill paid sync failed for %: %', new.id, sqlerrm;
|
||||||
|
end;
|
||||||
|
return new;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
drop trigger if exists trg_acct_bill_paid_back on accounting.bills;
|
||||||
|
create trigger trg_acct_bill_paid_back
|
||||||
|
after insert or update on accounting.bills
|
||||||
|
for each row execute function accounting.tg_accounting_bill_paid_sync();
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- One-time reconciliation: back-sync any accounting bills that are already
|
||||||
|
-- fully paid but whose linked public bill / approvals were never updated
|
||||||
|
-- (the rows that fell into the INSERT gap above).
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
do $$
|
||||||
|
declare _id uuid;
|
||||||
|
begin
|
||||||
|
for _id in
|
||||||
|
select id from accounting.bills
|
||||||
|
where external_source = 'acmacc_bill' and external_id is not null
|
||||||
|
and coalesce(paid_amount,0) >= coalesce(total,0) and coalesce(total,0) > 0
|
||||||
|
loop
|
||||||
|
perform accounting.sync_accounting_bill_paid(_id);
|
||||||
|
end loop;
|
||||||
|
end $$;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Backfill orphaned, invoice-track approvals (bill_id IS NULL) that were
|
||||||
|
-- created via the invoice flow and never linked to a public.bills row.
|
||||||
|
-- Only link when exactly one matching bill exists for the same association,
|
||||||
|
-- invoice number and amount; adopt that bill's status when it is paid.
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
update public.bill_approvals ba
|
||||||
|
set bill_id = m.bill_id,
|
||||||
|
status = case when m.bill_status = 'paid' then 'paid' else ba.status end,
|
||||||
|
updated_at = now()
|
||||||
|
from (
|
||||||
|
select i.id as invoice_id, b.id as bill_id, b.status as bill_status
|
||||||
|
from public.invoices i
|
||||||
|
join public.bills b
|
||||||
|
on b.association_id = i.association_id
|
||||||
|
and b.amount = i.amount
|
||||||
|
and b.invoice_number is not distinct from i.invoice_number
|
||||||
|
group by i.id, b.id, b.status
|
||||||
|
having (
|
||||||
|
select count(*) from public.bills b2
|
||||||
|
where b2.association_id = i.association_id
|
||||||
|
and b2.amount = i.amount
|
||||||
|
and b2.invoice_number is not distinct from i.invoice_number
|
||||||
|
) = 1
|
||||||
|
) m
|
||||||
|
where ba.bill_id is null
|
||||||
|
and ba.invoice_id = m.invoice_id;
|
||||||
Reference in New Issue
Block a user