-- Auto-create record-only bills for no-bill vendor payments in IMPORT-MODE companies. -- -- Buildium often records a vendor payment as a direct check (Dr Expense / Cr Bank) -- with no Bill behind it. For import-mode companies (gl_auto_post=false) the GL is -- the Buildium pull, and post_bill_gl no-ops, so a bill is a RECORD ONLY — creating -- one for such a payment surfaces it on the Bills page WITHOUT touching the GL (the -- expense is already booked once via the check). This makes A/P / vendor history -- complete without any double-count. -- -- A "no-bill vendor payment" = a buildium_gl journal entry that: looks like a check -- /payment (description), debits >=1 expense account, credits >=1 asset account (the -- bank), and has NO accounts-payable line. Idempotent: skips entries that already -- have an acmacc_autobill_nobill bill. Vendor is resolved from the payee (text after -- the last "·" in the description) by exact name match; unmatched => bill with no -- vendor (still correct on the GL, fixable later). Safe to call after every GL pull. create or replace function accounting.autocreate_nobill_vendor_bills(_company_id uuid) returns integer language plpgsql security definer set search_path to 'public','accounting' as $function$ declare _n int := 0; je record; _bill uuid; _vendor uuid; _payee text; begin -- Import-mode only: in gl_managed companies bills POST (post_bill_gl) and would -- double-count; those use the direct-expense rule instead. if accounting.gl_managed(_company_id) then return 0; end if; for je in select j.id, j.date, j.description, (select sum(l.debit) from accounting.journal_entry_lines l join accounting.accounts a on a.id=l.account_id where l.journal_entry_id=j.id and a.type='expense') as total from accounting.journal_entries j where j.company_id = _company_id and j.external_source = 'buildium_gl' and (j.description ilike 'Check%' or j.description ilike '%payment%') and exists (select 1 from accounting.journal_entry_lines l join accounting.accounts a on a.id=l.account_id where l.journal_entry_id=j.id and a.type='expense' and l.debit>0) and exists (select 1 from accounting.journal_entry_lines l2 join accounting.accounts a2 on a2.id=l2.account_id where l2.journal_entry_id=j.id and a2.type='asset' and l2.credit>0) and not exists (select 1 from accounting.journal_entry_lines l3 join accounting.accounts a3 on a3.id=l3.account_id where l3.journal_entry_id=j.id and a3.type='liability' and a3.name ilike '%payable%') and not exists (select 1 from accounting.bills b where b.company_id=_company_id and b.external_source='acmacc_autobill_nobill' and b.external_id=j.id::text) loop _payee := nullif(trim(regexp_replace(je.description, '^.*·\s*', '')), ''); select id into _vendor from accounting.vendors where company_id=_company_id and _payee is not null and lower(name)=lower(_payee) limit 1; insert into accounting.bills (company_id, vendor_id, number, issue_date, due_date, status, subtotal, tax, total, paid_amount, auto_created, external_source, external_id, notes) values (_company_id, _vendor, 'AUTOBILL-'||left(replace(je.id::text,'-',''),8), je.date, je.date, 'paid', coalesce(je.total,0), 0, coalesce(je.total,0), coalesce(je.total,0), true, 'acmacc_autobill_nobill', je.id::text, je.description) returning id into _bill; insert into accounting.bill_items (bill_id, description, quantity, rate, amount, account_id) select _bill, a.name, 1, l.debit, l.debit, l.account_id from accounting.journal_entry_lines l join accounting.accounts a on a.id=l.account_id where l.journal_entry_id=je.id and a.type='expense' and l.debit>0; _n := _n + 1; end loop; return _n; end$function$;