-- Manual Deposits: let a deposit's source (credit) account be selectable instead of -- always Undeposited Funds, and support multi-line deposits. This removes the forced -- routing through Undeposited Funds (the structural cause of negative Undeposited -- balances) and lets a deposit book interest income, refunds, reimbursements, etc. -- Single-source fallback: a deposit with no lines credits this account (default -- Undeposited Funds when null), keeping the existing "deposit received payments" flow. alter table accounting.deposits add column if not exists source_account_id uuid references accounting.accounts(id); -- Multi-line credits: one deposit = Dr Bank (total) and a set of credit lines, each -- with its own account and amount. The deposit's amount equals the sum of its lines. create table if not exists accounting.deposit_lines ( id uuid primary key default gen_random_uuid(), deposit_id uuid not null references accounting.deposits(id) on delete cascade, company_id uuid not null references accounting.companies(id) on delete cascade, account_id uuid not null references accounting.accounts(id), amount numeric not null default 0, memo text, created_at timestamptz not null default now() ); create index if not exists idx_deposit_lines_deposit on accounting.deposit_lines(deposit_id); alter table accounting.deposit_lines enable row level security; drop policy if exists "Accounting staff full access" on accounting.deposit_lines; create policy "Accounting staff full access" on accounting.deposit_lines for all to authenticated using (accounting.is_accounting_staff()) with check (accounting.is_accounting_staff()); drop policy if exists "Members CRUD deposit_lines" on accounting.deposit_lines; create policy "Members CRUD deposit_lines" on accounting.deposit_lines for all to authenticated using (accounting.is_company_member(company_id, auth.uid())) with check (accounting.is_company_member(company_id, auth.uid())); grant select, insert, update, delete on accounting.deposit_lines to authenticated, service_role; -- The deposit header trigger posts GL on insert (before any lines exist), so re-post -- whenever the lines change too. post_deposit_gl clears + reposts, so this is idempotent. create or replace function accounting.tg_deposit_line_gl() returns trigger language plpgsql security definer set search_path to 'public', 'accounting' as $$ begin begin perform accounting.post_deposit_gl(coalesce(new.deposit_id, old.deposit_id)); exception when others then raise warning 'accounting: deposit line GL post failed: %', sqlerrm; end; return coalesce(new, old); end$$; drop trigger if exists trg_acct_deposit_line_gl on accounting.deposit_lines; create trigger trg_acct_deposit_line_gl after insert or update or delete on accounting.deposit_lines for each row execute function accounting.tg_deposit_line_gl();