create table if not exists public.commerce_reconciliation_batches ( id uuid primary key default gen_random_uuid(), tenant_id uuid not null references public.tenants(id) on delete cascade, provider text not null, bill_date date not null, bill_type text not null default 'combined' check (bill_type in ('payment', 'refund', 'combined')), source text not null default 'manual_upload' check (source in ('manual_upload', 'provider_download', 'api', 'worker')), source_name text, source_hash text not null, status text not null default 'completed' check (status in ('preview', 'pending', 'processing', 'completed', 'completed_with_issues', 'failed')), total_count integer not null default 0 check (total_count >= 0), matched_count integer not null default 0 check (matched_count >= 0), mismatch_count integer not null default 0 check (mismatch_count >= 0), missing_local_count integer not null default 0 check (missing_local_count >= 0), missing_provider_count integer not null default 0 check (missing_provider_count >= 0), duplicate_count integer not null default 0 check (duplicate_count >= 0), ignored_count integer not null default 0 check (ignored_count >= 0), amount_cents integer not null default 0 check (amount_cents >= 0), refund_amount_cents integer not null default 0 check (refund_amount_cents >= 0), fee_cents integer not null default 0, created_by uuid references public.platform_users(id) on delete set null, completed_at timestamptz, error text, metadata jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table if not exists public.commerce_reconciliation_items ( id uuid primary key default gen_random_uuid(), tenant_id uuid not null references public.tenants(id) on delete cascade, batch_id uuid not null references public.commerce_reconciliation_batches(id) on delete cascade, row_no integer not null check (row_no > 0), provider text not null, transaction_type text not null check (transaction_type in ('payment', 'refund')), provider_trade_no text, provider_refund_no text, order_no text, refund_no text, amount_cents integer not null default 0 check (amount_cents >= 0), refund_amount_cents integer not null default 0 check (refund_amount_cents >= 0), fee_cents integer not null default 0, paid_at timestamptz, refunded_at timestamptz, provider_status text, local_status text, order_id uuid references public.orders(id) on delete set null, payment_id uuid references public.payments(id) on delete set null, refund_request_id uuid references public.commerce_refund_requests(id) on delete set null, match_status text not null check (match_status in ('matched', 'amount_mismatch', 'status_mismatch', 'missing_local', 'missing_provider', 'duplicate', 'ignored')), severity text not null default 'info' check (severity in ('info', 'warning', 'error', 'critical')), issue_code text, details jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), unique (batch_id, row_no) ); create index if not exists idx_commerce_recon_batches_tenant_date on public.commerce_reconciliation_batches(tenant_id, provider, bill_date desc, created_at desc); create index if not exists idx_commerce_recon_batches_status on public.commerce_reconciliation_batches(tenant_id, status, created_at desc); create index if not exists idx_commerce_recon_items_batch_status on public.commerce_reconciliation_items(tenant_id, batch_id, match_status, row_no); create index if not exists idx_commerce_recon_items_order on public.commerce_reconciliation_items(tenant_id, order_no, created_at desc); create index if not exists idx_commerce_recon_items_refund on public.commerce_reconciliation_items(tenant_id, refund_no, provider_refund_no, created_at desc); create index if not exists idx_commerce_recon_items_trade on public.commerce_reconciliation_items(tenant_id, provider, provider_trade_no, created_at desc); alter table public.commerce_reconciliation_batches enable row level security; alter table public.commerce_reconciliation_items enable row level security; drop policy if exists tenant_isolation on public.commerce_reconciliation_batches; create policy tenant_isolation on public.commerce_reconciliation_batches for all using (tenant_id = app.current_tenant_id() or app.is_platform_admin()) with check (tenant_id = app.current_tenant_id() or app.is_platform_admin()); drop policy if exists tenant_isolation on public.commerce_reconciliation_items; create policy tenant_isolation on public.commerce_reconciliation_items for all using (tenant_id = app.current_tenant_id() or app.is_platform_admin()) with check (tenant_id = app.current_tenant_id() or app.is_platform_admin()); drop trigger if exists set_updated_at on public.commerce_reconciliation_batches; create trigger set_updated_at before update on public.commerce_reconciliation_batches for each row execute function app.touch_updated_at();