@fayz-ai/plugin-financial 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ChartOfAccountsTree.d.ts +1 -1
- package/dist/components/ChartOfAccountsTree.d.ts.map +1 -1
- package/dist/components/PaymentModal.d.ts.map +1 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/registries.d.ts +4 -0
- package/dist/data/registries.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +1 -0
- package/dist/data/tables.d.ts.map +1 -1
- package/dist/data/types.d.ts +34 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4339 -850
- package/dist/index.js.map +1 -1
- package/dist/lib/invoice-list.d.ts +4 -1
- package/dist/lib/invoice-list.d.ts.map +1 -1
- package/dist/lib/payment-how.d.ts +8 -0
- package/dist/lib/payment-how.d.ts.map +1 -1
- package/dist/lib/useStockedItemTypes.d.ts +6 -0
- package/dist/lib/useStockedItemTypes.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +19 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types.d.ts +38 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/views/InvoiceDetailView.d.ts +2 -1
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/PayablesView.d.ts.map +1 -1
- package/dist/views/ReceivablesView.d.ts.map +1 -1
- package/dist/views/RecurringBillsView.d.ts.map +1 -1
- package/dist/views/cardChip.d.ts +16 -0
- package/dist/views/cardChip.d.ts.map +1 -0
- package/dist/views/focusItem.d.ts +4 -0
- package/dist/views/focusItem.d.ts.map +1 -0
- package/package.json +4 -4
|
@@ -10,7 +10,10 @@ interface InvoiceItemType {
|
|
|
10
10
|
icon?: string;
|
|
11
11
|
}
|
|
12
12
|
/** Product and service are catalog-backed choices; Other is always available. */
|
|
13
|
-
export declare function availableInvoiceItemTypes<T extends InvoiceItemType>(itemTypes: T[], lookupKinds: Iterable<string>, otherLabel: string
|
|
13
|
+
export declare function availableInvoiceItemTypes<T extends InvoiceItemType>(itemTypes: T[], lookupKinds: Iterable<string>, otherLabel: string,
|
|
14
|
+
/** A tela fala `debit`, a recorrência fala `payable` — as duas são conta a
|
|
15
|
+
* PAGAR. Ausente mantém o comportamento antigo. */
|
|
16
|
+
direction?: 'debit' | 'credit' | 'payable' | 'receivable'): T[];
|
|
14
17
|
export interface CounterpartyAccountingPolicy {
|
|
15
18
|
defaultAccountId?: string;
|
|
16
19
|
defaultCostCenterId?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoice-list.d.ts","sourceRoot":"","sources":["../../src/lib/invoice-list.ts"],"names":[],"mappings":"AAAA,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;
|
|
1
|
+
{"version":3,"file":"invoice-list.d.ts","sourceRoot":"","sources":["../../src/lib/invoice-list.ts"],"names":[],"mappings":"AAAA,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAuBD,iFAAiF;AACjF,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,eAAe,EACjE,SAAS,EAAE,CAAC,EAAE,EACd,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC7B,UAAU,EAAE,MAAM;AAClB;oDACoD;AACpD,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,GACxD,CAAC,EAAE,CAWL;AAED,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,aAAa,EAAE,OAAO,CAAA;IACtB,qBAAqB,EAAE,OAAO,CAAA;CAC/B;AAcD,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,OAAO,GAAG,4BAA4B,CAyC9F;AAED,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,4BAA4B,EACpC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,4BAA4B,CAO9B;AAED;gFACgF;AAChF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,wBAAwB,EAAE,EAAE,OAAO,SAAI,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBxG"}
|
|
@@ -4,6 +4,14 @@ export interface PaymentHow {
|
|
|
4
4
|
method?: string;
|
|
5
5
|
/** Where the money left from (payable) or landed (receivable). */
|
|
6
6
|
account?: BankAccount;
|
|
7
|
+
/**
|
|
8
|
+
* A ESPÉCIE da forma — `cash`, `pix`, `credit_card`…
|
|
9
|
+
*
|
|
10
|
+
* O nome já vinha; a espécie é o que permite DESENHAR a forma em vez de
|
|
11
|
+
* escrevê-la. Uma linha de parcela tem espaço para um ícone e não para
|
|
12
|
+
* "Cartão de crédito · Mercado Pago Vini [Brisah]".
|
|
13
|
+
*/
|
|
14
|
+
typeKind?: string;
|
|
7
15
|
}
|
|
8
16
|
/**
|
|
9
17
|
* The account a payment moved through. `bank_account_id` is null whenever the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment-how.d.ts","sourceRoot":"","sources":["../../src/lib/payment-how.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEhG,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"payment-how.d.ts","sourceRoot":"","sources":["../../src/lib/payment-how.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEhG,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAMD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,wBAAgB,eAAe,CAC7B,CAAC,EAAE,iBAAiB,EACpB,GAAG,EAAE;IACH,cAAc,EAAE,aAAa,EAAE,CAAA;IAC/B,kBAAkB,EAAE,iBAAiB,EAAE,CAAA;IACvC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACtC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CAC3B,GACA,UAAU,CAaZ"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EntityLookupMap } from '@fayz-ai/admin';
|
|
2
|
+
/** Os tipos que este documento pode oferecer de fato — os de catálogo vazio saem. */
|
|
3
|
+
export declare function useStockedItemTypes<T extends {
|
|
4
|
+
value: string;
|
|
5
|
+
}>(itemTypes: T[], entityLookups: EntityLookupMap): T[];
|
|
6
|
+
//# sourceMappingURL=useStockedItemTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStockedItemTypes.d.ts","sourceRoot":"","sources":["../../src/lib/useStockedItemTypes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AA+BrD,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAC7D,SAAS,EAAE,CAAC,EAAE,EACd,aAAa,EAAE,eAAe,GAC7B,CAAC,EAAE,CAoCL"}
|
package/dist/locales/en.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA+oBrC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkpBvC,CAAA"}
|
|
@@ -91,6 +91,25 @@ export declare const MIGRATION_089_O_FORNECEDOR_APONTA_PARA_O_PLANO_MIGRADO = "-
|
|
|
91
91
|
export declare const MIGRATION_090_O_DRE_RECORTA_ANTES_DE_SOMAR = "-- Filter the income statement before aggregating it.\n--\n-- The historical view aggregates every fiscal year before PostgREST applies\n-- the requested range. On large tenants that exceeds the authenticated role\n-- timeout. This function accepts the range, resolves tenant/unit/permission\n-- once, and only then joins line items. It is additive and retains the view as\n-- the analytics contract for existing consumers.\n\nCREATE OR REPLACE FUNCTION public.financial_dre(\n p_from date,\n p_to date,\n p_unit_id uuid DEFAULT NULL\n) RETURNS TABLE (\n unit_id uuid,\n competence_month date,\n account_id uuid,\n account_code text,\n account_name text,\n account_type text,\n account_parent_id uuid,\n statement_code text,\n statement_name text,\n unclassified boolean,\n line_count bigint,\n amount numeric,\n discount numeric\n)\nLANGUAGE sql\nSTABLE\nSECURITY DEFINER\nSET search_path TO ''\nAS $function$\n WITH ctx AS MATERIALIZED (\n SELECT app.current_tenant_id() AS tenant,\n app.visible_units() AS units,\n app.has_permission('financial.read', NULL::uuid) AS read_null,\n p_from AS de,\n p_to AS ate,\n p_unit_id AS unidade\n ),\n u_read AS MATERIALIZED (\n SELECT u\n FROM ctx, unnest(ctx.units) AS u\n WHERE app.has_permission('financial.read', u)\n ),\n inv AS MATERIALIZED (\n SELECT f.*\n FROM public.plg_financial_invoices f, ctx\n WHERE f.tenant_id = ctx.tenant\n AND f.cancelled_at IS NULL\n AND f.competence_date >= ctx.de\n AND f.competence_date <= ctx.ate\n AND (CASE WHEN ctx.unidade IS NOT NULL THEN f.unit_id = ctx.unidade\n WHEN f.unit_id IS NULL THEN ctx.read_null\n ELSE f.unit_id IN (SELECT u FROM u_read) END)\n ),\n line AS (\n SELECT f.tenant_id, f.unit_id, f.competence_date, f.direction,\n i.id AS item_id, i.total, i.discount,\n coalesce(x.chart_account_id, i.chart_account_id) AS chart_account_id\n FROM inv f\n JOIN public.items i ON i.invoice_id = f.id\n LEFT JOIN public.plg_financial_invoice_items x ON x.item_id = i.id\n WHERE i.cancelled_at IS NULL\n UNION ALL\n SELECT f.tenant_id, f.unit_id, f.competence_date, f.direction,\n i.id AS item_id, i.total, i.discount,\n coalesce(x.chart_account_id, i.chart_account_id) AS chart_account_id\n FROM inv f\n JOIN public.items i ON i.order_id = f.order_id\n LEFT JOIN public.plg_financial_invoice_items x ON x.item_id = i.id\n WHERE f.order_id IS NOT NULL\n AND i.invoice_id IS NULL\n AND i.cancelled_at IS NULL\n AND NOT EXISTS (SELECT 1 FROM public.items own WHERE own.invoice_id = f.id)\n ),\n structure AS (\n SELECT NULL::uuid AS unit_id, NULL::date AS competence_month,\n c.id AS account_id, c.code AS account_code, c.name AS account_name,\n c.account_type, c.parent_id AS account_parent_id,\n c.code AS statement_code, c.name AS statement_name,\n false AS unclassified, 0::bigint AS line_count,\n 0::numeric AS amount, 0::numeric AS discount\n FROM public.plg_financial_chart_of_accounts c, ctx\n WHERE c.tenant_id = ctx.tenant\n AND c.is_active\n AND c.account_type IN ('revenue', 'expense')\n AND (ctx.read_null OR EXISTS (SELECT 1 FROM u_read))\n ),\n movement AS (\n SELECT l.unit_id,\n date_trunc('month', l.competence_date)::date AS competence_month,\n c.id AS account_id, c.code AS account_code, c.name AS account_name,\n CASE WHEN l.direction = 'receivable' THEN 'revenue' ELSE 'expense' END AS account_type,\n c.parent_id AS account_parent_id,\n CASE WHEN (l.direction = 'receivable' AND c.account_type = 'revenue')\n OR (l.direction = 'payable' AND c.account_type = 'expense')\n THEN c.code END AS statement_code,\n CASE WHEN (l.direction = 'receivable' AND c.account_type = 'revenue')\n OR (l.direction = 'payable' AND c.account_type = 'expense')\n THEN c.name END AS statement_name,\n (c.id IS NULL OR NOT ((l.direction = 'receivable' AND c.account_type = 'revenue')\n OR (l.direction = 'payable' AND c.account_type = 'expense'))) AS unclassified,\n count(*) AS line_count,\n sum(l.total) AS amount,\n sum(l.discount) AS discount\n FROM line l\n LEFT JOIN public.plg_financial_chart_of_accounts c\n ON c.tenant_id = l.tenant_id AND c.id = l.chart_account_id\n GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10\n )\n SELECT * FROM structure\n UNION ALL\n SELECT * FROM movement;\n$function$;\n\nREVOKE ALL ON FUNCTION public.financial_dre(date, date, uuid) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.financial_dre(date, date, uuid) TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.financial_dre(date, date, uuid) IS\n 'Annual/monthly income statement filtered before aggregation with tenant, unit, and financial.read fences.';\n";
|
|
92
92
|
export declare const MIGRATION_091_A_RECORRENCIA_REPETE_OS_ITENS = "-- A recurring rule is a complete invoice template. When an occurrence creates\n-- its invoice, copy every template line with its own accounting dimensions.\n\nCREATE OR REPLACE FUNCTION public.plg_financial_copy_recurring_items()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_bill public.plg_financial_recurring_bills%ROWTYPE;\n v_row jsonb;\n v_item_id uuid;\n v_quantity numeric;\n v_unit_price numeric;\n v_discount numeric;\n v_surcharge numeric;\n v_sort integer := 0;\nBEGIN\n IF coalesce(NEW.metadata ->> 'source', '') <> 'financial_recurring_run'\n OR coalesce(NEW.metadata ->> 'recurring_bill_id', '') = '' THEN\n RETURN NEW;\n END IF;\n\n SELECT * INTO v_bill\n FROM public.plg_financial_recurring_bills\n WHERE id = (NEW.metadata ->> 'recurring_bill_id')::uuid\n AND tenant_id = NEW.tenant_id;\n\n IF NOT FOUND OR jsonb_typeof(v_bill.items) <> 'array' THEN\n RETURN NEW;\n END IF;\n\n FOR v_row IN SELECT value FROM jsonb_array_elements(v_bill.items) LOOP\n v_sort := v_sort + 1;\n v_quantity := greatest(coalesce((v_row ->> 'quantity')::numeric, 1), 0.0001);\n v_unit_price := coalesce((v_row ->> 'unitPrice')::numeric, (v_row ->> 'unit_price')::numeric, 0);\n v_discount := coalesce((v_row ->> 'discount')::numeric, 0);\n v_surcharge := coalesce((v_row ->> 'surcharge')::numeric, (v_row ->> 'addition')::numeric, 0);\n\n INSERT INTO public.items\n (tenant_id, unit_id, invoice_id, product_id, name, description, quantity,\n unit_price, discount, total, sort_order, metadata, created_by)\n VALUES\n (NEW.tenant_id, NEW.unit_id, NEW.id,\n CASE WHEN coalesce(v_row ->> 'referenceId', v_row ->> 'reference_id', v_row ->> 'product_id', '')\n ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'\n THEN coalesce(v_row ->> 'referenceId', v_row ->> 'reference_id', v_row ->> 'product_id')::uuid END,\n nullif(v_row ->> 'description', ''), nullif(v_row ->> 'description', ''),\n v_quantity, v_unit_price, v_discount,\n round(v_quantity * v_unit_price - v_discount + v_surcharge, 2), v_sort,\n jsonb_strip_nulls(jsonb_build_object(\n 'item_kind', coalesce(v_row ->> 'itemKind', v_row ->> 'item_kind', 'other'),\n 'financial', jsonb_build_object('additional', v_surcharge)\n )), NEW.created_by)\n RETURNING id INTO v_item_id;\n\n INSERT INTO public.plg_financial_invoice_items\n (item_id, tenant_id, unit_id, chart_account_id, cost_center_id, owner_id)\n VALUES\n (v_item_id, NEW.tenant_id, NEW.unit_id,\n CASE WHEN coalesce(v_row ->> 'accountId', v_row ->> 'account_id', v_row ->> 'chart_account_id', '')\n ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'\n THEN coalesce(v_row ->> 'accountId', v_row ->> 'account_id', v_row ->> 'chart_account_id')::uuid END,\n CASE WHEN coalesce(v_row ->> 'costCenterId', v_row ->> 'cost_center_id', '')\n ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'\n THEN coalesce(v_row ->> 'costCenterId', v_row ->> 'cost_center_id')::uuid END,\n NEW.created_by);\n END LOOP;\n\n RETURN NEW;\nEND $$;\n\nDROP TRIGGER IF EXISTS plg_financial_recurring_invoice_items ON public.plg_financial_invoices;\nCREATE TRIGGER plg_financial_recurring_invoice_items\n AFTER INSERT ON public.plg_financial_invoices\n FOR EACH ROW EXECUTE FUNCTION public.plg_financial_copy_recurring_items();\n\nREVOKE ALL ON FUNCTION public.plg_financial_copy_recurring_items() FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION public.plg_financial_copy_recurring_items() TO service_role;\n\nCOMMENT ON FUNCTION public.plg_financial_copy_recurring_items() IS\n 'Copies every item of a recurring rule into each generated invoice, preserving per-line accounting.';\n";
|
|
93
93
|
export declare const MIGRATION_092_A_CONTA_PAGA_NO_CARTAO_SAI_DO_CARTAO = "-- A bill paid by credit card is paid from the company's card.\n--\n-- financial_receive_payment settled every credit_card payment in a\n-- card_receivable_credit account \u2014 right for a sale (the acquirer owes us),\n-- wrong for a bill: paying a supplier with the corporate card puts the debt on\n-- that card (card_payable). The payables screen could never pay by card.\n-- Receivables keep the exact rule they had. Replaces the function in place;\n-- idempotent.\n\nCREATE OR REPLACE FUNCTION public.financial_receive_payment(p_installment_id uuid, p_amount numeric, p_account_id uuid DEFAULT NULL::uuid, p_method text DEFAULT NULL::text, p_cash_session_id uuid DEFAULT NULL::uuid, p_card_brand text DEFAULT NULL::text, p_card_installments integer DEFAULT NULL::integer, p_nsu text DEFAULT NULL::text, p_payment_date date DEFAULT NULL::date, p_fee_amount numeric DEFAULT NULL::numeric, p_notes text DEFAULT NULL::text, p_idempotency_key text DEFAULT NULL::text, p_metadata jsonb DEFAULT '{}'::jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_actor uuid := auth.uid();\n v_inst_id uuid := p_installment_id;\n v_inst record;\n v_invoice public.plg_financial_invoices%ROWTYPE;\n v_amount numeric := round(coalesce(p_amount, 0), 2);\n v_open numeric;\n v_method text := lower(coalesce(nullif(btrim(p_method), ''), 'other'));\n v_holder uuid;\n v_holder_k text;\n v_session uuid := p_cash_session_id;\n v_counter uuid;\n v_credit numeric;\n v_mov_id uuid;\n v_alloc_id uuid;\n v_dir text;\nBEGIN\n IF v_amount <= 0 THEN\n RAISE EXCEPTION 'financial_receive_payment: amount must be > 0' USING ERRCODE = '22023';\n END IF;\n IF v_method NOT IN ('cash', 'pix', 'credit_card', 'debit_card', 'transfer', 'boleto', 'check', 'credit', 'other') THEN\n RAISE EXCEPTION 'financial_receive_payment: unknown method %', v_method USING ERRCODE = '22023';\n END IF;\n\n -- accept a legacy per-order 'bill' movement id in place of the installment\n IF NOT EXISTS (SELECT 1 FROM public.plg_financial_invoice_installments i WHERE i.id = v_inst_id) THEN\n SELECT m.financial_invoice_installment_id INTO v_inst_id FROM public.plg_financial_movements m\n WHERE m.id = p_installment_id AND m.movement_kind = 'bill' AND m.tenant_id = v_tenant;\n END IF;\n SELECT i.* INTO v_inst FROM public.plg_financial_invoice_installments i WHERE i.id = v_inst_id AND i.tenant_id = v_tenant;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'financial_receive_payment: installment % not found', p_installment_id USING ERRCODE = 'P0002';\n END IF;\n SELECT * INTO v_invoice FROM public.plg_financial_invoices WHERE id = v_inst.invoice_id;\n PERFORM public.plg_financial_require_permission('financial.create', v_inst.unit_id);\n\n -- idempotency\n IF p_idempotency_key IS NOT NULL THEN\n PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);\n SELECT m.id INTO v_mov_id FROM public.plg_financial_movements m WHERE m.tenant_id = v_tenant AND m.idempotency_key = p_idempotency_key;\n IF v_mov_id IS NOT NULL THEN\n SELECT a.id INTO v_alloc_id FROM public.plg_financial_allocations a WHERE a.payment_movement_id = v_mov_id LIMIT 1;\n RETURN jsonb_build_object('movement_id', v_mov_id, 'allocation_id', v_alloc_id, 'installment_id', v_inst.id,\n 'invoice_id', v_inst.invoice_id, 'amount', v_amount, 'replayed', true);\n END IF;\n END IF;\n\n IF v_inst.cancelled_at IS NOT NULL OR v_invoice.cancelled_at IS NOT NULL THEN\n RAISE EXCEPTION 'financial_receive_payment: installment is cancelled' USING ERRCODE = '55000';\n END IF;\n v_open := round(v_inst.amount - public.plg_financial_installment_paid(v_inst.id), 2);\n IF v_amount > v_open + 0.000001 THEN\n RAISE EXCEPTION 'financial_receive_payment: % exceeds the open balance % of installment %/% of %', v_amount, v_open, v_inst.number, v_invoice.number, v_invoice.number\n USING ERRCODE = '23514';\n END IF;\n\n -- the money holder\n IF v_method = 'credit' THEN\n IF v_invoice.counterparty_person_id IS NULL THEN\n RAISE EXCEPTION 'financial_receive_payment: the invoice has no counterparty to consume credit from' USING ERRCODE = '55000';\n END IF;\n v_holder := public.plg_financial_person_account(v_tenant, v_invoice.counterparty_person_id);\n SELECT balance INTO v_credit FROM public.v_financial_account_balances WHERE account_id = v_holder;\n IF coalesce(v_credit, 0) + 0.000001 < v_amount THEN\n RAISE EXCEPTION 'financial_receive_payment: customer credit % is below %', coalesce(v_credit, 0), v_amount USING ERRCODE = '23514';\n END IF;\n ELSIF v_method = 'cash' THEN\n IF v_session IS NULL THEN\n v_session := public.plg_financial_open_session_of(v_tenant, v_actor, v_inst.unit_id);\n END IF;\n IF v_session IS NULL THEN\n RAISE EXCEPTION 'financial_receive_payment: no open cash session (caixinha) for cash in this unit' USING ERRCODE = '55000',\n HINT = 'Open your cash session first (financial_cash_session_open).';\n END IF;\n SELECT s.account_id INTO v_holder FROM public.plg_financial_cash_register_sessions s\n WHERE s.id = v_session AND s.tenant_id = v_tenant AND s.status = 'open';\n IF v_holder IS NULL THEN\n RAISE EXCEPTION 'financial_receive_payment: cash session % is not open', v_session USING ERRCODE = '55000';\n END IF;\n ELSE\n v_holder := p_account_id;\n IF v_holder IS NULL THEN\n RAISE EXCEPTION 'financial_receive_payment: an account is required for method %', v_method USING ERRCODE = '22023';\n END IF;\n END IF;\n SELECT a.kind INTO v_holder_k FROM public.plg_financial_accounts a WHERE a.id = v_holder AND a.tenant_id = v_tenant AND a.is_active;\n IF v_holder_k IS NULL THEN\n RAISE EXCEPTION 'financial_receive_payment: account % not found or inactive', v_holder USING ERRCODE = 'P0002';\n END IF;\n IF v_invoice.direction = 'payable' THEN\n -- Paying a bill by credit card spends the company's own card (a liability\n -- account), never a card receivable. Debit card leaves the bank directly.\n IF v_method = 'credit_card' AND v_holder_k <> 'card_payable' THEN\n RAISE EXCEPTION 'financial_receive_payment: method credit_card must pay a bill from a card_payable account'\n USING ERRCODE = '23514';\n END IF;\n IF v_method <> 'credit_card' AND v_holder_k = 'card_payable' THEN\n RAISE EXCEPTION 'financial_receive_payment: a card_payable account only pays by credit_card' USING ERRCODE = '23514';\n END IF;\n IF v_holder_k IN ('card_receivable_credit', 'card_receivable_debit') THEN\n RAISE EXCEPTION 'financial_receive_payment: a card receivable account cannot pay a bill' USING ERRCODE = '23514';\n END IF;\n ELSE\n IF (v_method = 'credit_card' AND v_holder_k <> 'card_receivable_credit')\n OR (v_method = 'debit_card' AND v_holder_k <> 'card_receivable_debit') THEN\n RAISE EXCEPTION 'financial_receive_payment: method % must settle in a % account', v_method,\n CASE WHEN v_method = 'credit_card' THEN 'card_receivable_credit' ELSE 'card_receivable_debit' END USING ERRCODE = '23514';\n END IF;\n IF v_method NOT IN ('credit_card', 'debit_card') AND v_holder_k IN ('card_receivable_credit', 'card_receivable_debit') THEN\n RAISE EXCEPTION 'financial_receive_payment: a card receivable account only takes card methods' USING ERRCODE = '23514';\n END IF;\n END IF;\n\n IF v_invoice.direction = 'payable' THEN\n v_dir := 'debit';\n v_counter := public.plg_financial_system_account(v_tenant, 'payables');\n ELSE\n v_dir := 'credit';\n v_counter := public.plg_financial_system_account(v_tenant, 'receivables');\n END IF;\n\n INSERT INTO public.plg_financial_movements\n (tenant_id, unit_id, invoice_id, financial_invoice_id, financial_invoice_installment_id, direction, movement_kind, amount, paid_amount, status,\n due_date, payment_date, installment_number, method, card_brand, card_installments, nsu, cash_session_id,\n bank_account_id, fee_amount, notes, metadata, idempotency_key, created_by, debit_account_id, credit_account_id)\n VALUES\n (v_tenant, v_inst.unit_id, v_invoice.order_id, v_invoice.id, v_inst.id, v_dir, 'payment', v_amount, v_amount, 'paid',\n v_inst.due_date, coalesce(p_payment_date, current_date), v_inst.number, v_method, p_card_brand, p_card_installments, p_nsu, v_session,\n CASE WHEN v_holder_k IN ('person', 'system') THEN NULL ELSE v_holder END,\n round(coalesce(p_fee_amount, 0), 2), p_notes,\n coalesce(p_metadata, '{}'::jsonb) || jsonb_build_object('source', 'financial_receive_payment'),\n p_idempotency_key, v_actor,\n CASE WHEN v_dir = 'credit' THEN v_holder ELSE v_counter END,\n CASE WHEN v_dir = 'credit' THEN v_counter ELSE v_holder END)\n RETURNING id INTO v_mov_id;\n\n INSERT INTO public.plg_financial_allocations (tenant_id, unit_id, payment_movement_id, installment_id, amount, created_by)\n VALUES (v_tenant, v_inst.unit_id, v_mov_id, v_inst.id, v_amount, v_actor)\n RETURNING id INTO v_alloc_id;\n\n PERFORM public.plg_financial_audit(v_tenant, 'financial.payment.receive', 'plg_financial_movements', v_mov_id::text,\n jsonb_build_object('installment_id', v_inst.id, 'invoice_id', v_invoice.id, 'invoice_number', v_invoice.number, 'amount', v_amount,\n 'method', v_method, 'account_id', v_holder, 'cash_session_id', v_session, 'idempotency_key', p_idempotency_key));\n\n RETURN jsonb_build_object('movement_id', v_mov_id, 'allocation_id', v_alloc_id, 'installment_id', v_inst.id,\n 'invoice_id', v_inst.invoice_id, 'amount', v_amount, 'replayed', false);\nEND $$;\n";
|
|
94
|
+
export declare const MIGRATION_093_O_PAGAMENTO_DIZ_QUAL_CONTA_ELE_QUITOU = "-- O pagamento diz qual conta ele quitou.\n--\n-- Na tela de um t\u00EDtulo a pagar, a parcela aparece **paga** e, ao abrir, diz\n-- \"Nenhum detalhe adicional registrado\". Medido em 28/09/2026 no Pertinho do\n-- C\u00E9u, t\u00EDtulo `17663e91` (NF-e 8147427, R$ 12.668,20 em 4 parcelas): as quatro\n-- contas atravessaram, os quatro pagamentos atravessaram, as quatro baixas\n-- atravessaram \u2014 e a parcela 2 mostra-se paga sem nenhum pagamento embaixo.\n--\n-- \u2500\u2500 Por que \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `InvoiceDetailView` pareia pagamento com conta por `settles_movement_id`\n-- (`lib/settlements.ts`). Quando ele \u00E9 nulo, cai na deriva\u00E7\u00E3o por\n-- `installment_number` \u2014 e o pr\u00F3prio arquivo avisa que essa deriva\u00E7\u00E3o erra:\n--\n-- Artorius invoice 176440 carries two payments both stamped installment 1\n-- while settling bills #1 and #2.\n--\n-- \u00C9 exatamente o caso. Os dois pagamentos das parcelas 1 e 2 desse t\u00EDtulo v\u00EAm\n-- do V1 carimbados `installment_number = 1` (o segundo veio da concilia\u00E7\u00E3o\n-- banc\u00E1ria, que n\u00E3o renumera), ent\u00E3o os dois caem na parcela 1 e a parcela 2\n-- fica sem nenhum. O dinheiro est\u00E1 certo, a atribui\u00E7\u00E3o \u00E9 que se perde.\n--\n-- E `settles_movement_id` \u00E9 nulo em TODOS os movimentos portados:\n--\n-- Pertinho do C\u00E9u 2.203 pagamentos 0 com v\u00EDnculo\n-- Espa\u00E7o Facial 138.262 pagamentos 0 com v\u00EDnculo\n--\n-- N\u00E3o \u00E9 dado que faltou na origem. S\u00E3o dois furos:\n--\n-- 1. A `migration.allowlist` mapeia `parent_movement_id` para uma coluna\n-- `parent_movement_id` que N\u00C3O EXISTE em `plg_financial_movements`. O de-para\n-- n\u00E3o erra alto: a chave simplesmente n\u00E3o \u00E9 lida por writer nenhum, e a linha\n-- entra sem o v\u00EDnculo. No V1 do Pertinho do C\u00E9u 2.978 dos 2.986 pagamentos\n-- t\u00EAm `parent_movement_id` \u2014 a resposta estava l\u00E1 o tempo todo.\n--\n-- 2. `upsert_fin_movement` n\u00E3o escreve a coluna, e `upsert_fin_allocation`\n-- tampouco \u2014 mesmo sendo a baixa quem SABE a resposta. No Espa\u00E7o Facial isso\n-- \u00E9 o que importa: l\u00E1 o V1 preenche `parent_movement_id` em 854 dos 138.946\n-- pagamentos, mas a baixa existe para 138.104 deles.\n--\n-- \u2500\u2500 O que muda \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- O de-para passa a apontar para a coluna que existe; os dois writers passam a\n-- preencher o v\u00EDnculo quando ele est\u00E1 VAZIO \u2014 nunca reescrevendo um j\u00E1 gravado,\n-- que \u00E9 o que o append-only protege de verdade (mesma leitura da 047: um\n-- v\u00EDnculo nulo n\u00E3o \u00E9 um fato, \u00E9 uma resposta que ainda n\u00E3o chegou). E o que j\u00E1\n-- est\u00E1 gravado \u00E9 preenchido aqui, a partir das baixas que j\u00E1 atravessaram.\n--\n-- O gatilho `plg_financial_movements_append_only` j\u00E1 admite isto: ele lista\n-- nominalmente o que um evento de dinheiro n\u00E3o pode mudar (valor, forma de\n-- pagamento, conta banc\u00E1ria, status) e `settles_movement_id` n\u00E3o est\u00E1 na lista.\n--\n-- \u2500\u2500 O que este arquivo N\u00C3O conserta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `payment_method_type_id`, `payment_method_id` e `bank_account_id` continuam\n-- nulos em todo movimento portado, e \u00E9 outro problema: o writer n\u00E3o escreve\n-- essas tr\u00EAs colunas, e o append-only pro\u00EDbe preench\u00EA-las depois. Conserto no\n-- writer s\u00F3 vale para linha NOVA; o que j\u00E1 entrou fica. N\u00E3o \u00E9 o que a tela\n-- estava mostrando aqui, e misturar as duas coisas num arquivo s\u00F3 esconderia a\n-- segunda.\n--\n-- Idempotente.\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 1. O de-para aponta para a coluna que existe\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- S\u00F3 esta chave muda. A linha de `financial_movements` \u00E9 `frozen` e tem 30\n-- colunas mapeadas \u2014 reescrev\u00EA-la inteira por causa de uma seria trocar o risco\n-- de errar uma pelo risco de errar as outras 29.\n\nSET LOCAL migration.allowlist_unlock = 'on';\n\nUPDATE migration.allowlist\n SET columns = jsonb_set(\n columns, '{parent_movement_id}',\n '\"settles_movement_id@financial_movements:public.plg_financial_movements\"'::jsonb, true)\n WHERE source_table = 'financial_movements'\n AND columns ->> 'parent_movement_id' IS DISTINCT FROM\n 'settles_movement_id@financial_movements:public.plg_financial_movements';\n\nRESET migration.allowlist_unlock;\n\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 2. O writer do movimento grava o v\u00EDnculo, e o preenche na segunda oferta\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nCREATE OR REPLACE FUNCTION migration.upsert_fin_movement(\n p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $function$\nDECLARE\n v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);\n v_kind_raw text := nullif(btrim(lower(coalesce(p_row ->> 'movement_kind', ''))), '');\n v_kind text := CASE v_kind_raw\n WHEN 'bill' THEN 'bill' WHEN 'payment' THEN 'payment'\n WHEN 'transfer' THEN 'transfer' WHEN 'commission' THEN 'commission'\n WHEN 'conta' THEN 'bill' WHEN 'titulo' THEN 'bill' WHEN 't\u00EDtulo' THEN 'bill'\n WHEN 'pagamento' THEN 'payment' WHEN 'recebimento' THEN 'payment'\n WHEN 'transferencia' THEN 'transfer' WHEN 'transfer\u00EAncia' THEN 'transfer'\n WHEN 'comissao' THEN 'commission' WHEN 'comiss\u00E3o' THEN 'commission'\n END;\n v_status_raw text := nullif(btrim(lower(coalesce(p_row ->> 'status', ''))), '');\n v_status text := CASE v_status_raw\n WHEN 'canceled' THEN 'cancelled'\n WHEN 'cancelado' THEN 'cancelled'\n WHEN 'cancelada' THEN 'cancelled'\n WHEN 'pago' THEN 'paid'\n WHEN 'paga' THEN 'paid'\n WHEN 'pendente' THEN 'pending'\n WHEN 'vencido' THEN 'overdue'\n WHEN 'vencida' THEN 'overdue'\n WHEN 'atrasado' THEN 'overdue'\n WHEN 'parcial' THEN 'partial'\n ELSE coalesce(v_status_raw, 'pending')\n END;\n v_dir text := nullif(btrim(lower(coalesce(p_row ->> 'direction', ''))), '');\n v_amount numeric := round(coalesce((p_row ->> 'amount')::numeric, 0), 2);\n v_invoice uuid := (p_row ->> 'financial_invoice_id')::uuid;\n v_unit uuid := (p_row ->> 'unit_id')::uuid;\n v_due date;\n v_legs record;\n v_debit uuid := (p_row ->> 'debit_account_id')::uuid;\n v_credit uuid := (p_row ->> 'credit_account_id')::uuid;\n -- A conta que ESTE pagamento quitou. Vem de `financial_movements.parent_movement_id`\n -- do V1, j\u00E1 resolvido pelo ledger. Nulo \u00E9 o normal e n\u00E3o \u00E9 erro: o pagamento\n -- avulso n\u00E3o quita conta nenhuma, e no Espa\u00E7o Facial a coluna quase n\u00E3o \u00E9\n -- usada \u2014 l\u00E1 quem responde \u00E9 a baixa, na parte 3 deste arquivo.\n v_settles uuid := (p_row ->> 'settles_movement_id')::uuid;\n v_exists boolean;\n v_standalone boolean := migration._standalone();\nBEGIN\n IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;\n IF p_id IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'a movement needs an id');\n END IF;\n IF v_kind IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value',\n 'reason', format('unknown movement kind %L (expected bill|payment|transfer|commission, or a V1 label of one)', coalesce(v_kind_raw, '<null>')));\n END IF;\n IF v_dir IS NOT NULL AND v_dir NOT IN ('debit', 'credit') THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value', 'reason', format('unknown direction %L (expected debit|credit)', v_dir));\n END IF;\n IF v_dir IS NULL AND v_invoice IS NOT NULL THEN\n SELECT CASE t.direction WHEN 'receivable' THEN 'credit' WHEN 'payable' THEN 'debit' END INTO v_dir\n FROM public.plg_financial_invoices t WHERE t.id = v_invoice;\n END IF;\n IF v_dir IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state',\n 'reason', 'the movement carries no direction and settles no invoice \u2014 which way the money went cannot be derived, and must not be defaulted');\n END IF;\n IF v_amount < 0 THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value', 'reason', 'a money event cannot carry a negative amount \u2014 direction is the direction, not the sign');\n END IF;\n\n IF v_unit IS NULL AND v_invoice IS NOT NULL THEN\n SELECT t.unit_id INTO v_unit FROM public.plg_financial_invoices t WHERE t.id = v_invoice;\n END IF;\n\n v_due := coalesce((p_row ->> 'due_date')::date, (p_row ->> 'paid_at')::date, current_date);\n\n IF v_debit IS NULL OR v_credit IS NULL THEN\n SELECT * INTO v_legs FROM public.plg_financial_legacy_legs(\n p_tenant, v_kind, v_dir, (p_row ->> 'bank_account_id')::uuid, (p_row ->> 'cash_session_id')::uuid,\n v_invoice, v_meta);\n IF v_legs.o_mirror THEN\n v_meta := v_meta || jsonb_build_object('legacy_mirror', true);\n ELSE\n v_debit := coalesce(v_debit, v_legs.o_debit);\n v_credit := coalesce(v_credit, v_legs.o_credit);\n END IF;\n END IF;\n\n -- Um movimento s\u00F3 quita OUTRO movimento, e s\u00F3 se ele j\u00E1 estiver aqui. O\n -- ledger pode ter resolvido um pai que caiu em quarentena \u2014 apontar para ele\n -- estouraria a FK e derrubaria o pagamento inteiro por causa do v\u00EDnculo.\n -- Nulo agora, preenchido na segunda oferta.\n IF v_settles IS NOT NULL AND NOT EXISTS (\n SELECT 1 FROM public.plg_financial_movements m WHERE m.id = v_settles AND m.tenant_id = p_tenant) THEN\n v_settles := NULL;\n END IF;\n\n -- \u2500\u2500 A parcela nasce ANTES do movimento \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n -- Ver 070: `plg_financial_movements_installment_fk` n\u00E3o \u00E9 adi\u00E1vel, e a\n -- sincroniza\u00E7\u00E3o do espelho s\u00F3 acontece na segunda chamada, no fim.\n IF v_kind IN ('bill', 'commission') AND v_invoice IS NOT NULL THEN\n PERFORM migration._fin_installment_from_bill(\n p_tenant, p_id, v_invoice, v_unit,\n (p_row ->> 'installment_number')::integer, v_due, v_amount,\n v_status = 'cancelled', false);\n END IF;\n\n SELECT EXISTS (SELECT 1 FROM public.plg_financial_movements WHERE id = p_id) INTO v_exists;\n IF NOT v_exists THEN\n INSERT INTO public.plg_financial_movements\n (id, tenant_id, unit_id, financial_invoice_id, financial_invoice_installment_id,\n direction, movement_kind, amount, paid_amount, status,\n due_date, payment_date, installment_number, notes, metadata, debit_account_id, credit_account_id,\n settles_movement_id)\n VALUES (p_id, p_tenant, v_unit, v_invoice,\n CASE WHEN v_kind IN ('bill', 'commission') AND v_invoice IS NOT NULL THEN p_id END,\n v_dir, v_kind, v_amount,\n round(coalesce((p_row ->> 'paid_amount')::numeric, 0), 2),\n v_status, v_due, (p_row ->> 'paid_at')::date,\n (p_row ->> 'installment_number')::integer, p_row ->> 'notes', v_meta, v_debit, v_credit,\n CASE WHEN v_kind = 'payment' THEN v_settles END);\n ELSE\n UPDATE public.plg_financial_movements SET\n unit_id = coalesce(unit_id, v_unit),\n debit_account_id = coalesce(debit_account_id, v_debit),\n credit_account_id = coalesce(credit_account_id, v_credit),\n -- Mesma leitura da 047: v\u00EDnculo VAZIO \u00E9 resposta que n\u00E3o chegou, n\u00E3o fato.\n -- Preenchido uma vez, nunca reescrito.\n settles_movement_id = coalesce(settles_movement_id,\n CASE WHEN movement_kind = 'payment' THEN v_settles END),\n financial_invoice_installment_id = coalesce(\n financial_invoice_installment_id,\n CASE WHEN v_kind IN ('bill', 'commission') AND v_invoice IS NOT NULL THEN p_id END)\n WHERE id = p_id;\n END IF;\n\n IF v_kind IN ('bill', 'commission') AND v_invoice IS NOT NULL THEN\n PERFORM migration._fin_installment_from_bill(\n p_tenant, p_id, v_invoice, v_unit,\n (p_row ->> 'installment_number')::integer, v_due, v_amount,\n v_status = 'cancelled', true);\n END IF;\n\n IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_financial_movements', p_id, jsonb_build_object('reason', CASE WHEN v_exists THEN 'updated' ELSE 'created' END)); END IF;\n RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id,\n 'reason', CASE WHEN v_exists THEN 'already in the ledger (append-only; legs, unit, installment, settlement link and cancellation completed)' ELSE 'created' END);\nEND $function$;\n\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 3. A baixa tamb\u00E9m responde \u2014 e no Espa\u00E7o Facial \u00E9 ela quem responde\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- `payment_allocations` \u00E9 a tabela que SABE quanto de qual pagamento quitou qual\n-- conta, e no V2 a parcela tem o id do movimento de conta (070). Ent\u00E3o a baixa\n-- j\u00E1 carrega o v\u00EDnculo: `installment_id` \u00C9 o movimento quitado.\n--\n-- Medido em 28/09/2026: 138.104 dos 138.262 pagamentos do Espa\u00E7o Facial t\u00EAm\n-- baixa, contra 854 dos 138.946 com `parent_movement_id` no V1. Sem esta parte,\n-- a parte 2 conserta o Pertinho do C\u00E9u e n\u00E3o conserta o Espa\u00E7o Facial.\n\nCREATE OR REPLACE FUNCTION migration.upsert_fin_allocation(\n p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $function$\nDECLARE\n v_payment uuid := (p_row ->> 'payment_movement_id')::uuid;\n v_bill uuid := (p_row ->> 'installment_id')::uuid;\n v_amount numeric := round(coalesce((p_row ->> 'amount')::numeric, 0), 2);\n v_unit uuid;\n v_standalone boolean := migration._standalone();\nBEGIN\n IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;\n IF p_id IS NULL OR v_payment IS NULL OR v_bill IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state',\n 'reason', 'uma baixa precisa do pagamento e da conta que ela quita');\n END IF;\n IF v_amount <= 0 THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value',\n 'reason', 'uma baixa de valor zero ou negativo n\u00E3o quita nada');\n END IF;\n\n IF NOT EXISTS (SELECT 1 FROM public.plg_financial_invoice_installments i\n WHERE i.id = v_bill AND i.tenant_id = p_tenant) THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',\n 'reason', format('a conta que esta baixa quita ainda n\u00E3o virou parcela (%s)', v_bill));\n END IF;\n IF NOT EXISTS (SELECT 1 FROM public.plg_financial_movements m\n WHERE m.id = v_payment AND m.tenant_id = p_tenant) THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',\n 'reason', format('o pagamento desta baixa ainda n\u00E3o atravessou (%s)', v_payment));\n END IF;\n\n SELECT m.unit_id INTO v_unit FROM public.plg_financial_movements m WHERE m.id = v_payment;\n\n INSERT INTO public.plg_financial_allocations\n (id, tenant_id, unit_id, payment_movement_id, installment_id, amount, metadata)\n VALUES (p_id, p_tenant, v_unit, v_payment, v_bill, v_amount,\n coalesce(p_row -> 'metadata', '{}'::jsonb))\n ON CONFLICT (payment_movement_id, installment_id) DO UPDATE\n SET amount = EXCLUDED.amount;\n\n -- O v\u00EDnculo, quando ele ainda n\u00E3o existe. S\u00F3 para o pagamento que quita UMA\n -- conta: um pagamento espalhado por duas parcelas n\u00E3o tem \"a\" conta dele, e\n -- escolher uma seria inventar a atribui\u00E7\u00E3o que a tela mostra. S\u00E3o 12 em\n -- 140.328 \u2014 ficam sem v\u00EDnculo, e a tela os trata como n\u00E3o atribu\u00EDdos, que \u00E9 o\n -- que eles s\u00E3o.\n UPDATE public.plg_financial_movements m\n SET settles_movement_id = v_bill\n WHERE m.id = v_payment\n AND m.tenant_id = p_tenant\n AND m.movement_kind = 'payment'\n AND m.settles_movement_id IS NULL\n AND EXISTS (SELECT 1 FROM public.plg_financial_movements b WHERE b.id = v_bill)\n AND NOT EXISTS (SELECT 1 FROM public.plg_financial_allocations a\n WHERE a.payment_movement_id = v_payment AND a.installment_id <> v_bill);\n\n IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_financial_allocations', p_id, jsonb_build_object('reason', 'created')); END IF;\n RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', 'created');\nEND $function$;\n\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 4. O que j\u00E1 est\u00E1 gravado\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- Os writers acima s\u00F3 valem para a pr\u00F3xima oferta, e a pr\u00F3xima oferta de\n-- `financial_movements` devolve `skipped` para tudo: o checksum \u00E9 do dado da\n-- ORIGEM, e mudar o mapa no destino n\u00E3o move uma v\u00EDrgula no V1. O que j\u00E1\n-- atravessou se preenche aqui, pela baixa, que \u00E9 a mesma resposta que a parte 3\n-- passa a dar \u2014 sem depender de re-oferta nenhuma.\n--\n-- Num cluster sem porte isto atualiza zero linhas.\n\nUPDATE public.plg_financial_movements m\n SET settles_movement_id = a.installment_id\n FROM public.plg_financial_allocations a\n WHERE a.payment_movement_id = m.id\n AND m.movement_kind = 'payment'\n AND m.settles_movement_id IS NULL\n AND EXISTS (SELECT 1 FROM public.plg_financial_movements b WHERE b.id = a.installment_id)\n AND NOT EXISTS (SELECT 1 FROM public.plg_financial_allocations o\n WHERE o.payment_movement_id = a.payment_movement_id\n AND o.installment_id <> a.installment_id);\n\n\n-- \u2500\u2500 quem pode executar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nREVOKE ALL ON FUNCTION migration.upsert_fin_movement(uuid, uuid, jsonb, jsonb) FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION migration.upsert_fin_movement(uuid, uuid, jsonb, jsonb) TO service_role;\nREVOKE ALL ON FUNCTION migration.upsert_fin_allocation(uuid, uuid, jsonb, jsonb) FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION migration.upsert_fin_allocation(uuid, uuid, jsonb, jsonb) TO service_role;\n";
|
|
95
|
+
export declare const MIGRATION_094_O_TITULO_DA_NOTA_DE_ENTRADA_SABE_DE_QUEM_ELE_E = "-- O t\u00EDtulo da nota de entrada sabe de quem ele \u00E9.\n--\n-- Contas a Pagar \u2192 detalhe de um t\u00EDtulo vindo de DANFE mostra o valor, mostra o\n-- vencimento, e n\u00E3o mostra fornecedor nenhum. Medido em 28/09/2026:\n--\n-- Pertinho do C\u00E9u 3.042 t\u00EDtulos a pagar 547 sem contraparte\n-- 540 deles nascidos de DANFE\n-- Espa\u00E7o Facial t\u00EDtulos de DANFE 94 sem contraparte\n--\n-- Praticamente todo t\u00EDtulo nascido de nota de entrada est\u00E1 sem fornecedor, nos\n-- dois clientes. N\u00E3o \u00E9 coincid\u00EAncia: nenhum deles jamais teve.\n--\n-- \u2500\u2500 Por que \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `upsert_fin_danfe_installment` (084) monta o t\u00EDtulo a partir do que a parcela\n-- carrega em `metadata.danfe`, e ali o fornecedor \u00E9 `supplier_name` \u2014 TEXTO, o\n-- nome curto que o operador digitou na importa\u00E7\u00E3o. O INSERT n\u00E3o tem\n-- `counterparty_person_id` na lista de colunas, e o extrator nunca ofereceu o\n-- ponteiro: `danfe_imports.supplier_id` existe no V1, est\u00E1 preenchido em 554 das\n-- 558 notas do tenant 8, e n\u00E3o era lido por ningu\u00E9m.\n--\n-- O nome n\u00E3o substitui o ponteiro, e a diferen\u00E7a \u00E9 vis\u00EDvel a olho nu \u2014 o V1\n-- guarda o apelido na nota e a raz\u00E3o social no cadastro:\n--\n-- supplier_name contacts[supplier_id].name\n-- TOP ALTO TOP ALTO PARTICIPACOES E COMERCIO LTDA\n-- BETOFRUIT Hortifrutigranjeiros Betofruti Ltda ME\n-- PRIMEGAS PRIMEGAS DISTRIBUIDORA DE GAS LTDA\n--\n-- Casar por nome acertaria 137 de 554. O ponteiro acerta 554, e \u00E9 o que o V1 usa\n-- para saber a quem ele deve.\n--\n-- \u2500\u2500 O que muda \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- O de-para passa a pedir `note_supplier_id` (o extrator o deriva da pr\u00F3pria\n-- nota, como j\u00E1 faz com os outros oito campos dela) e a resolv\u00EA-lo pelo ledger\n-- de `contacts`, exatamente como `recurring_bills.counterparty_contact_id` j\u00E1\n-- fazia. O writer grava a contraparte, e a preenche quando ela est\u00E1 vazia.\n--\n-- A nota SEM `supplier_id` na origem continua sem contraparte \u2014 4 das 558. Nota\n-- sem fornecedor declarado \u00E9 um dado ausente, e invent\u00E1-lo pelo nome seria pior\n-- que deix\u00E1-lo \u00E0 vista.\n--\n-- \u2500\u2500 O que este arquivo N\u00C3O conserta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- O t\u00EDtulo de DANFE tamb\u00E9m n\u00E3o tem ITENS, e isso \u00E9 outra coisa: as linhas da\n-- nota v\u00E3o para `plg_inventory_stock_movements` (plugin-inventory/010), que \u00E9\n-- onde elas pertencem \u2014 \u00E9 mercadoria entrando, n\u00E3o servi\u00E7o vendido. Um t\u00EDtulo a\n-- pagar de nota de entrada mostrando itens exigiria decidir se a linha fiscal \u00E9\n-- tamb\u00E9m linha financeira, e essa decis\u00E3o n\u00E3o existe ainda. O que existe \u00E9 o\n-- v\u00EDnculo: `plg_inventory_operations.idempotency_key` = a chave de acesso, e o\n-- t\u00EDtulo carrega `danfe:<mesma chave>`. A tela pode seguir esse fio; este\n-- arquivo n\u00E3o o inventa.\n--\n-- Idempotente.\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 1. Um t\u00EDtulo fechado pode GANHAR a contraparte que nunca teve\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- `trg_plg_financial_invoices_immutable` recusa qualquer mudan\u00E7a em\n-- `counterparty_person_id` depois de emitido, e recusa com raz\u00E3o: trocar a quem\n-- se deve \u00E9 trocar o fato. S\u00F3 que NULL n\u00E3o \u00E9 um fato \u2014 \u00E9 uma resposta que n\u00E3o\n-- chegou, e o porte \u00E9 exatamente a situa\u00E7\u00E3o em que ela chega depois.\n--\n-- \u00C9 a mesma leitura que `plg_financial_movements_append_only` j\u00E1 faz com as\n-- pernas cont\u00E1beis, na 047: \"a leg may be FILLED exactly once and never\n-- rewritten\". Aqui vale a mesma frase, com a mesma trava \u2014 preenchido uma vez,\n-- nunca reescrito.\n\nCREATE OR REPLACE FUNCTION public.trg_plg_financial_invoices_immutable()\nRETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $function$\nBEGIN\n -- Not closed yet: a draft is a draft.\n IF OLD.issue_date IS NULL OR OLD.cancelled_at IS NOT NULL THEN RETURN NEW; END IF;\n\n -- The named exception: cancelling, and the bookkeeping that goes with it.\n IF NEW.cancelled_at IS NOT NULL AND OLD.cancelled_at IS NULL THEN RETURN NEW; END IF;\n\n IF NEW.total IS DISTINCT FROM OLD.total\n OR NEW.direction IS DISTINCT FROM OLD.direction\n -- Uma contraparte AUSENTE pode ser preenchida exatamente uma vez. Trocar\n -- uma que existe continua proibido, que \u00E9 o que a imutabilidade protege.\n OR (OLD.counterparty_person_id IS NOT NULL\n AND NEW.counterparty_person_id IS DISTINCT FROM OLD.counterparty_person_id)\n OR NEW.order_id IS DISTINCT FROM OLD.order_id\n OR NEW.number IS DISTINCT FROM OLD.number\n OR NEW.competence_date IS DISTINCT FROM OLD.competence_date\n OR NEW.issue_date IS DISTINCT FROM OLD.issue_date\n OR NEW.currency IS DISTINCT FROM OLD.currency THEN\n RAISE EXCEPTION 'plg_financial_invoices: invoice % is issued and cannot be altered', OLD.number\n USING ERRCODE = '23514',\n HINT = 'A closed invoice is immutable (PRD 04). Correct it with a new invoice, or cancel this one through financial_cancel_invoice.';\n END IF;\n RETURN NEW;\nEND $function$;\n\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 2. O de-para pede o ponteiro do fornecedor\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- Uma chave a mais na linha da 084. `@contacts:public.people` \u00E9 a mesma forma\n-- que `recurring_bills` usa \u2014 o fornecedor do V1 vive em `contacts` e vira\n-- `public.people` no V2.\n\nSET LOCAL migration.allowlist_unlock = 'on';\n\nUPDATE migration.allowlist\n SET columns = jsonb_set(\n columns, '{note_supplier_id}',\n '\"counterparty_person_id@contacts:public.people\"'::jsonb, true)\n WHERE source_table = 'danfe_payments'\n AND columns ->> 'note_supplier_id' IS DISTINCT FROM\n 'counterparty_person_id@contacts:public.people';\n\nRESET migration.allowlist_unlock;\n\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 3. O writer grava a contraparte\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nCREATE OR REPLACE FUNCTION migration.upsert_fin_danfe_installment(\n p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $function$\nDECLARE\n v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);\n v_danfe jsonb := coalesce(v_meta -> 'danfe', '{}'::jsonb);\n v_note uuid;\n v_unit uuid := (p_row ->> 'unit_id')::uuid;\n v_invoice uuid;\n v_issue date;\n v_due date;\n v_amount numeric := round(coalesce((p_row ->> 'amount')::numeric, 0), 2);\n v_number integer := coalesce((p_row ->> 'number')::integer, 1);\n v_total numeric;\n v_desc text;\n -- O fornecedor, j\u00E1 resolvido pelo ledger de `contacts`. Nulo quando a nota do\n -- V1 n\u00E3o declara `supplier_id` (4 das 558 no tenant 8) \u2014 e nulo fica.\n v_party uuid := (p_row ->> 'counterparty_person_id')::uuid;\n v_existing uuid;\n v_created boolean := false;\n v_standalone boolean := migration._standalone();\nBEGIN\n IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;\n\n IF p_id IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state',\n 'reason', 'uma parcela precisa de id');\n END IF;\n\n BEGIN v_note := (v_danfe ->> 'import_uuid')::uuid; EXCEPTION WHEN OTHERS THEN v_note := NULL; END;\n IF v_note IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state',\n 'reason', 'parcela de nota de entrada sem a nota \u2014 o t\u00EDtulo dela n\u00E3o tem de que nascer');\n END IF;\n\n BEGIN v_issue := (v_danfe ->> 'issue_date')::timestamptz::date; EXCEPTION WHEN OTHERS THEN v_issue := NULL; END;\n BEGIN v_due := (p_row ->> 'due_date')::date; EXCEPTION WHEN OTHERS THEN v_due := NULL; END;\n v_due := coalesce(v_due, v_issue, current_date);\n\n IF v_amount < 0 THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value',\n 'reason', format('parcela de valor negativo (%s) \u2014 o destino exige amount >= 0', v_amount));\n END IF;\n\n -- Uma contraparte que n\u00E3o \u00E9 deste tenant \u00E9 pior que nenhuma: ela ligaria o\n -- t\u00EDtulo a algu\u00E9m de outro cliente. Recusa em sil\u00EAncio e o t\u00EDtulo nasce sem.\n IF v_party IS NOT NULL AND NOT EXISTS (\n SELECT 1 FROM public.people p WHERE p.id = v_party AND p.tenant_id = p_tenant) THEN\n v_party := NULL;\n END IF;\n\n v_total := round(coalesce((v_danfe ->> 'payments_total')::numeric, v_amount), 2);\n v_desc := btrim('NF-e ' || coalesce(v_danfe ->> 'nfe_number', 's/n')\n || coalesce('/' || (v_danfe ->> 'nfe_series'), '')\n || coalesce(' \u2014 ' || (v_danfe ->> 'supplier_name'), ''));\n v_invoice := md5('danfe-invoice:' || v_note::text)::uuid;\n\n INSERT INTO public.plg_financial_invoices\n (id, tenant_id, unit_id, direction, number, competence_date, issue_date, total,\n description, idempotency_key, metadata, counterparty_person_id)\n VALUES (v_invoice, p_tenant, v_unit, 'payable', NULL,\n coalesce(v_issue, v_due), coalesce(v_issue, v_due), v_total,\n nullif(v_desc, ''),\n 'danfe:' || coalesce(v_danfe ->> 'access_key', v_note::text),\n jsonb_build_object('danfe', v_danfe - 'payment'), v_party)\n ON CONFLICT (id) DO NOTHING;\n\n -- A segunda parcela da mesma nota encontra o t\u00EDtulo que a primeira criou. Se\n -- aquela veio sem fornecedor e esta tem, o t\u00EDtulo ganha o que lhe faltava \u2014\n -- uma vez, pela mesma regra do gatilho na parte 1.\n IF v_party IS NOT NULL THEN\n UPDATE public.plg_financial_invoices\n SET counterparty_person_id = v_party\n WHERE id = v_invoice AND tenant_id = p_tenant AND counterparty_person_id IS NULL;\n END IF;\n\n SELECT tenant_id INTO v_existing FROM public.plg_financial_invoice_installments WHERE id = p_id;\n IF v_existing IS NOT NULL AND v_existing <> p_tenant THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant',\n 'reason', format('a parcela %s j\u00E1 existe em outro tenant', p_id));\n END IF;\n v_created := v_existing IS NULL;\n\n INSERT INTO public.plg_financial_invoice_installments\n (id, tenant_id, unit_id, invoice_id, number, due_date, amount, metadata, created_at)\n VALUES (p_id, p_tenant, v_unit, v_invoice, v_number, v_due, v_amount, v_meta,\n coalesce((p_row ->> 'created_at')::timestamptz, now()))\n ON CONFLICT (id) DO UPDATE SET\n unit_id = coalesce(EXCLUDED.unit_id, public.plg_financial_invoice_installments.unit_id),\n invoice_id = EXCLUDED.invoice_id,\n number = EXCLUDED.number,\n due_date = EXCLUDED.due_date,\n amount = EXCLUDED.amount,\n metadata = migration._merge_meta(public.plg_financial_invoice_installments.metadata, EXCLUDED.metadata),\n updated_at = now();\n\n IF v_standalone THEN\n PERFORM migration._audit(p_tenant, 'public.plg_financial_invoice_installments', p_id,\n jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END,\n 'invoice_id', v_invoice));\n END IF;\n RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id,\n 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);\nEND $function$;\n\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 4. Os t\u00EDtulos que j\u00E1 est\u00E3o gravados\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- Sem depender de re-oferta e sem ler o V1: a nota j\u00E1 atravessou como opera\u00E7\u00E3o\n-- de invent\u00E1rio, e ela carrega `metadata.danfe.supplier.v1_ref` \u2014 o mesmo\n-- `supplier_id` que o de-para passa a pedir. O fio entre as duas \u00E9 a chave de\n-- acesso, que \u00E9 `idempotency_key` nas duas pontas (084).\n--\n-- Medido em 28/09/2026: 634 t\u00EDtulos de DANFE sem contraparte nos dois tenants,\n-- 634 com a opera\u00E7\u00E3o do lado do invent\u00E1rio, e 630 com o fornecedor j\u00E1 no ledger.\n-- Os 4 restantes s\u00E3o as notas que o V1 importou sem declarar fornecedor.\n--\n-- Num cluster sem porte isto atualiza zero linhas.\n\n-- A GUARDA QUE A 084 J\u00C1 TINHA ESCRITO.\n--\n-- \u00ABeste writer \u00E9 do financeiro, e o financeiro roda antes do invent\u00E1rio \u2014 ler\n-- `plg_inventory_operations` daqui seria depender de uma tabela que ainda n\u00E3o\n-- existe quando este arquivo \u00E9 aplicado.\u00BB \u2014 084, oito arquivos atr\u00E1s\n--\n-- \u00C9 literalmente o que este backfill faz, e \u00E9 por isso que a cadeia de zero\n-- morria aqui: `relation \"public.plg_inventory_operations\" does not exist`.\n-- Num cluster REAL a tabela est\u00E1 l\u00E1, porque o invent\u00E1rio j\u00E1 foi instalado \u2014\n-- ent\u00E3o o defeito s\u00F3 aparece no banco de ensaio, que \u00E9 onde ele deve aparecer.\n--\n-- A guarda \u00E9 a mesma da 015, que resolve o mesmo problema neste mesmo plugin.\n-- Ela n\u00E3o muda nada onde a tabela existe, e onde ela n\u00E3o existe tamb\u00E9m n\u00E3o h\u00E1\n-- o que preencher: o pr\u00F3prio coment\u00E1rio acima diz \u00ABnum cluster sem porte isto\n-- atualiza zero linhas\u00BB.\nDO $backfill$\nBEGIN\n IF to_regclass('public.plg_inventory_operations') IS NULL THEN\n RETURN;\n END IF;\n\n UPDATE public.plg_financial_invoices i\n SET counterparty_person_id = l.target_id\n FROM public.plg_inventory_operations op\n JOIN migration.ledger l\n ON l.source_table = 'contacts'\n AND l.source_numeric_id = (op.metadata -> 'danfe' -> 'supplier' ->> 'v1_ref')::bigint\n AND l.target_id IS NOT NULL\n WHERE op.tenant_id = i.tenant_id\n AND i.idempotency_key = 'danfe:' || op.idempotency_key\n AND i.counterparty_person_id IS NULL\n AND i.cancelled_at IS NULL\n AND i.metadata ? 'danfe'\n AND EXISTS (SELECT 1 FROM public.people p\n WHERE p.id = l.target_id AND p.tenant_id = i.tenant_id);\nEND\n$backfill$;\n\n\n-- \u2500\u2500 quem pode executar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nREVOKE ALL ON FUNCTION migration.upsert_fin_danfe_installment(uuid, uuid, jsonb, jsonb) FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION migration.upsert_fin_danfe_installment(uuid, uuid, jsonb, jsonb) TO service_role;\n";
|
|
96
|
+
export declare const MIGRATION_095_O_CAIXA_DO_V1_E_REGISTRO_NAO_REFERENCIA = "-- O caixa do V1 \u00E9 registro, n\u00E3o refer\u00EAncia.\n--\n-- 32 movimentos financeiros do Pertinho do C\u00E9u est\u00E3o em quarentena de `fk` por\n-- isto, e nenhum deles vai sair de l\u00E1 sozinho:\n--\n-- unresolved reference(s): [{\"ref\": \"95\", \"column\": \"cash_session_id\",\n-- \"ref_table\": \"cash_register_sessions\"}]\n--\n-- `cash_register_sessions` est\u00E1 em `migration.excluded_tables`, por decis\u00E3o\n-- escrita: a sess\u00E3o de caixa do V2 \u00E9 ATRIBU\u00CDDA (`user_id` NOT NULL, uma aberta\n-- por usu\u00E1rio e unidade), e a do V1 n\u00E3o tem a quem atribuir. A tabela nunca vai\n-- existir no destino para este porte \u2014 ent\u00E3o a refer\u00EAncia nunca resolve, e o\n-- movimento que a cita cai para sempre.\n--\n-- \u2500\u2500 A linha j\u00E1 sabia disso \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- A pr\u00F3pria `notes` da allowlist de `financial_movements` diz, desde a 043:\n--\n-- `cash_session_id` \u00E9 REGISTRO (v1_cash_session_ref), n\u00E3o refer\u00EAncia.\n--\n-- e o mapa da mesma linha diz o contr\u00E1rio:\n--\n-- \"cash_session_id\": \"cash_session_id@cash_register_sessions\"\n--\n-- A nota est\u00E1 certa e o mapa est\u00E1 errado. Quem l\u00EA \u00E9 o mapa.\n--\n-- \u2500\u2500 E o writer nunca usou a resposta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `upsert_fin_movement` n\u00E3o grava `cash_session_id`; ele s\u00F3 o entrega a\n-- `plg_financial_legacy_legs`, que o usa para achar a conta que segura o\n-- dinheiro \u2014 em `plg_financial_cash_register_sessions`, que para este porte est\u00E1\n-- vazia. A fun\u00E7\u00E3o j\u00E1 trata a aus\u00EAncia: sem sess\u00E3o e sem banco, a perna cai em\n-- `legacy_holder`. Ou seja, mesmo os movimentos que ATRAVESSARAM j\u00E1 postam em\n-- `legacy_holder`; o que a refer\u00EAncia faz hoje \u00E9 s\u00F3 derrubar os 32 que a citam.\n--\n-- Trocar a refer\u00EAncia por registro n\u00E3o perde informa\u00E7\u00E3o nenhuma \u2014 o id do caixa\n-- do V1 continua leg\u00EDvel em `metadata.v1_cash_session_ref`, que \u00E9 onde\n-- `invoices.cash_session_id` j\u00E1 o guarda \u2014 e devolve os 32 movimentos.\n--\n-- \u2500\u2500 O que este arquivo N\u00C3O conserta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `bank_account_id` tem o mesmo mapa de registro (`jsonb:v1_bank_account_ref`) e\n-- o caso dele \u00E9 o OPOSTO: `bank_accounts` ATRAVESSA, para\n-- `public.plg_financial_accounts`, e o ledger tem os pares (13 no tenant 8, 42 no\n-- tenant 1). Hoje todo pagamento portado posta em `legacy_holder` em vez da conta\n-- que de fato segurou o dinheiro. Corrigir aquilo muda a perna cont\u00E1bil de todo\n-- pagamento NOVO \u2014 e n\u00E3o corrige os que j\u00E1 entraram, porque o append-only s\u00F3\n-- deixa preencher perna NULA e a deles j\u00E1 est\u00E1 escrita. \u00C9 decis\u00E3o de quem \u00E9 dono\n-- do raz\u00E3o, n\u00E3o conserto de transporte, e por isso n\u00E3o vai junto.\n--\n-- Idempotente.\n\nSET LOCAL migration.allowlist_unlock = 'on';\n\nUPDATE migration.allowlist\n SET columns = jsonb_set(\n columns, '{cash_session_id}', '\"jsonb:v1_cash_session_ref\"'::jsonb, true)\n WHERE source_table = 'financial_movements'\n AND columns ->> 'cash_session_id' IS DISTINCT FROM 'jsonb:v1_cash_session_ref';\n\nRESET migration.allowlist_unlock;\n\n-- A quarentena de `fk` \u00E9 reaberta a cada onda (`_reopen_stale` no driver), ent\u00E3o\n-- os 32 s\u00E3o reoferecidos na pr\u00F3xima rodada do espelho e entram. N\u00E3o h\u00E1 o que\n-- desfazer aqui: nenhum deles chegou a ser gravado.\n";
|
|
97
|
+
export declare const MIGRATION_096_O_CAIXA_DO_V1_JA_TEM_DONO_E_PODE_ENTRAR = "-- O caixa do V1 j\u00E1 tem dono, e pode entrar.\n--\n-- `cash_register_sessions` est\u00E1 fora do porte por uma linha em\n-- `migration.excluded_tables` escrita em 03/09/2026:\n--\n-- Deferred: a V2 cash session is ATTRIBUTED \u2014 user_id is NOT NULL and only\n-- one may be open per user and unit... 091 carries V1's opener only as an\n-- opaque `v1_opened_by` reference with no mapping to a V2 auth user, so\n-- every migrated session would have to be attributed to somebody who did\n-- not open it.\n--\n-- A obje\u00E7\u00E3o estava certa e j\u00E1 foi respondida. Quem a respondeu foi o pr\u00F3prio\n-- writer que veio depois: `migration.upsert_fin_cash_session` existe, a linha de\n-- de-para existe com as 29 colunas mapeadas, e a `notes` dela diz a solu\u00E7\u00E3o em\n-- voz alta \u2014\n--\n-- `user_id` recebe o uuid de quem ABRIU no V1 \u2014 a coluna \u00E9 NOT NULL e n\u00E3o\n-- tem FK, ent\u00E3o ela exige identidade, n\u00E3o um login do V2. Assim ningu\u00E9m do\n-- V2 herda a gaveta de ningu\u00E9m, que era a obje\u00E7\u00E3o que mantinha esta tabela\n-- fora.\n--\n-- O que ficou foi a linha de exclus\u00E3o, que ningu\u00E9m apagou. E ela vence: o motor\n-- recusa a tabela com `kind='allowlist'` antes de olhar para o writer. Medido em\n-- 28/09/2026 no Pertinho do C\u00E9u: 13 sess\u00F5es em quarentena por isso.\n--\n-- \u2500\u2500 Por que ela pode entrar agora \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Conferido na origem, tenant 8, 28/09/2026:\n--\n-- 22 sess\u00F5es, 6 operadores distintos, TODAS com `opened_by_user_id`\n-- preenchido e `opened_by_name` leg\u00EDvel\n-- 19 fechadas, 3 abertas \u2014 as 3 de operadores DIFERENTES, na mesma unidade\n-- 22 de 22 com `unit_id` e `account_central_id`\n-- 1 \u00FAnica conta (`bank_accounts` 23), j\u00E1 no ledger\n--\n-- As 3 abertas s\u00E3o o teste que importa: `plg_financial_cash_sessions_one_open_uq`\n-- \u00E9 UNIQUE em `(tenant_id, user_id, unit_id) WHERE status='open'`, e tr\u00EAs\n-- operadores distintos na mesma unidade n\u00E3o colidem. Se um dia colidirem, a\n-- sess\u00E3o cai em quarentena `state` com o 23505 escrito \u2014 que \u00E9 o comportamento\n-- certo, porque duas gavetas abertas pela mesma pessoa no mesmo lugar \u00E9 um\n-- estado que o V2 recusa de prop\u00F3sito.\n--\n-- \u2500\u2500 O que entra junto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- 32 movimentos financeiros do Pertinho do C\u00E9u est\u00E3o em quarentena de `fk`\n-- citando `cash_session_id`. A 091 os resolve pelo outro lado (o caixa vira\n-- REGISTRO no movimento, n\u00E3o refer\u00EAncia) e este arquivo os resolve pela raiz.\n-- Os dois s\u00E3o \u00FAteis: a 091 vale para o movimento cujo caixa N\u00C3O atravessa \u2014\n-- sess\u00E3o de outro tenant, ou de uma janela fora da onda.\n--\n-- `cash_session_lines`, `cash_session_adjustments` e `cash_session_payment_links`\n-- (637 linhas no tenant 8) continuam sem destino no V2. N\u00E3o \u00E9 esquecimento: a\n-- confer\u00EAncia de caixa do V2 tem forma pr\u00F3pria (`v_financial_cash_session_review`\n-- e `plg_financial_cash_session_reviewers`), e o resumo da confer\u00EAncia do V1 j\u00E1\n-- chega em `metadata.review` pela linha de de-para que existe. O detalhe linha a\n-- linha \u00E9 desenho de produto, n\u00E3o transporte.\n--\n-- Idempotente.\n\nSET LOCAL migration.allowlist_unlock = 'on';\n\nDELETE FROM migration.excluded_tables WHERE source_table = 'cash_register_sessions';\n\nRESET migration.allowlist_unlock;\n\n-- A quarentena de `allowlist` n\u00E3o \u00E9 reaberta pelo driver (s\u00F3 a de `fk` \u00E9): ela\n-- descreve uma decis\u00E3o, n\u00E3o uma corrida. Reabrir aqui \u00E9 o que faz a pr\u00F3xima\n-- rodada reoferecer as sess\u00F5es em vez de esperar a marca d'\u00E1gua mexer \u2014 e o\n-- checksum do ledger devolve `skipped` para o que j\u00E1 estiver certo.\nUPDATE migration.ledger l\n SET status = 'pending', reason = 'readmitida: a exclus\u00E3o do caixa saiu (092)', updated_at = now()\n WHERE l.source_table = 'cash_register_sessions'\n AND l.status = 'quarantined'\n AND EXISTS (SELECT 1 FROM migration.quarantine q\n WHERE q.ledger_id = l.id AND q.kind = 'allowlist' AND q.resolved_at IS NULL);\n\nUPDATE migration.quarantine q\n SET resolved_at = now(), resolution = 'a tabela voltou ao de-para (092)'\n FROM migration.ledger l\n WHERE q.ledger_id = l.id AND l.source_table = 'cash_register_sessions'\n AND q.kind = 'allowlist' AND q.resolved_at IS NULL;\n";
|
|
98
|
+
export declare const MIGRATION_097_O_CAIXA_CONTA_LINHA_POR_LINHA = "-- O caixa conta LINHA POR LINHA, e o V2 s\u00F3 guardava o total.\n--\n-- A 096 readmitiu `cash_register_sessions`: as 22 sess\u00F5es do Pertinho do C\u00E9u\n-- atravessam e `plg_financial_cash_register_sessions` guarda abertura,\n-- fechamento, esperado, contado e diferen\u00E7a. Isso responde \"fechou ou n\u00E3o\n-- fechou\". N\u00E3o responde a pergunta que vem em seguida, que \u00E9 a \u00FAnica que o\n-- operador realmente faz: **onde** n\u00E3o fechou.\n--\n-- No V1 essa resposta est\u00E1 em tr\u00EAs tabelas que n\u00E3o t\u00EAm destino nenhum:\n--\n-- cash_session_lines 59 a confer\u00EAncia por forma de pagamento\n-- cash_session_payment_links 568 qual recebimento entrou em qual caixa\n-- cash_session_adjustments 14 o ajuste feito \u00E0 m\u00E3o, com motivo\n--\n-- \u2500\u2500 A linha de confer\u00EAncia tem TR\u00CAS valores, e nenhum \u00E9 redundante \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- system_amount o que o sistema apurou\n-- operator_amount o que o operador contou na gaveta\n-- review_amount o que a revis\u00E3o da retaguarda concluiu depois\n--\n-- Guardar s\u00F3 a diferen\u00E7a jogaria fora a hist\u00F3ria: um caixa em que o operador\n-- errou a contagem e a revis\u00E3o corrigiu \u00E9 um caso; um em que o sistema errou \u00E9\n-- outro; e os dois fecham com a mesma diferen\u00E7a final. `por_fora` \u00E9 a quarta\n-- informa\u00E7\u00E3o \u2014 dinheiro que entrou sem passar pelo sistema \u2014 e \u00E9 justamente a\n-- linha que ningu\u00E9m quer perder.\n--\n-- \u2500\u2500 Por que o v\u00EDnculo do recebimento \u00E9 uma tabela, e n\u00E3o uma coluna \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `cash_session_payment_links` tem `previous_cash_session_id`: o recebimento\n-- MUDA de caixa. Acontece quando algu\u00E9m fecha o turno com um pagamento lan\u00E7ado\n-- no caixa errado e a retaguarda remaneja. Uma coluna `cash_session_id` no\n-- pagamento guardaria s\u00F3 o estado final e apagaria o remanejamento \u2014 que \u00E9\n-- exatamente o que uma auditoria de caixa vai procurar.\n--\n-- O recebimento aqui \u00E9 `plg_tables_order_payments`, a tabela que a\n-- plugin-tables/011 criou: 989 no Pertinho do C\u00E9u, dos quais 568 t\u00EAm v\u00EDnculo\n-- de caixa.\n--\n-- Idempotente.\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 1. A confer\u00EAncia, por forma de pagamento\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nCREATE TABLE IF NOT EXISTS public.plg_financial_cash_session_lines (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n unit_id uuid,\n session_id uuid NOT NULL REFERENCES public.plg_financial_cash_register_sessions(id) ON DELETE CASCADE,\n -- 'cash', 'card', 'pix'\u2026 o V1 n\u00E3o fecha a lista e o V2 tamb\u00E9m n\u00E3o: a forma de\n -- pagamento \u00E9 vocabul\u00E1rio do cliente, e um CHECK aqui derrubaria o caixa\n -- inteiro no dia em que a casa criar uma nova.\n kind text NOT NULL,\n payment_method_type_id uuid REFERENCES public.plg_financial_payment_method_types(id) ON DELETE SET NULL,\n card_brand text,\n settlement_kind text,\n system_amount numeric(14,2),\n system_count integer,\n operator_amount numeric(14,2),\n operator_note text,\n -- Dinheiro que entrou SEM passar pelo sistema. \u00C9 a linha que explica a\n -- diferen\u00E7a quando ela n\u00E3o \u00E9 erro de contagem.\n por_fora boolean NOT NULL DEFAULT false,\n review_amount numeric(14,2),\n review_status text,\n review_note text,\n reviewed_by_name text,\n reviewed_at timestamptz,\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 2. Qual recebimento entrou em qual caixa \u2014 e de onde ele veio\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nCREATE TABLE IF NOT EXISTS public.plg_financial_cash_session_payments (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n unit_id uuid,\n -- SEM `REFERENCES`, e n\u00E3o por pregui\u00E7a: seria um CICLO.\n --\n -- O `plugin-tables` j\u00E1 aponta para o financial \u2014 `plg_tables_order_payments\n -- .movement_id` (011) e `plg_tables_service_charge_snapshots\n -- .cash_session_id` (019) \u2014, ent\u00E3o o financial roda ANTES do tables na\n -- corrente. Uma FK daqui para l\u00E1 referencia tabela que ainda n\u00E3o existe, e \u00E9\n -- exatamente onde o `db-from-zero` parou:\n --\n -- 097_o_caixa_conta_linha_por_linha.sql \u2026 FAILED\n -- ERROR: relation \"public.plg_tables_order_payments\" does not exist\n --\n -- Duas plugins n\u00E3o podem apontar uma para a outra numa cadeia linear. Some o\n -- CASCADE, e o pre\u00E7o est\u00E1 anotado: apagar o pagamento da mesa deixa esta\n -- linha \u00F3rf\u00E3. A sa\u00EDda que o reposit\u00F3rio j\u00E1 nomeou nas outras isen\u00E7\u00F5es de\n -- `cross-prefix-fk` \u00E9 o par polim\u00F3rfico (tipo + id) com views tolerantes \u00E0\n -- aus\u00EAncia do plugin dono \u2014 e \u00E9 ela que fecha isto de verdade.\n order_payment_id uuid NOT NULL,\n session_id uuid NOT NULL REFERENCES public.plg_financial_cash_register_sessions(id) ON DELETE CASCADE,\n -- O caixa de ONDE ele saiu, quando foi remanejado. NULL na esmagadora\n -- maioria: s\u00F3 o remanejamento preenche.\n previous_session_id uuid REFERENCES public.plg_financial_cash_register_sessions(id) ON DELETE SET NULL,\n source text,\n reason text,\n created_by_name text,\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now(),\n -- Um recebimento est\u00E1 num caixa por vez. O remanejamento REESCREVE a linha e\n -- guarda de onde veio; n\u00E3o empilha uma segunda.\n UNIQUE (order_payment_id)\n);\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 3. O ajuste feito \u00E0 m\u00E3o\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nCREATE TABLE IF NOT EXISTS public.plg_financial_cash_session_adjustments (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n unit_id uuid,\n session_id uuid NOT NULL REFERENCES public.plg_financial_cash_register_sessions(id) ON DELETE CASCADE,\n action text NOT NULL,\n payment_method_type_id uuid REFERENCES public.plg_financial_payment_method_types(id) ON DELETE SET NULL,\n card_brand text,\n -- O ajuste que virou dinheiro no raz\u00E3o. NULL quando foi s\u00F3 reclassifica\u00E7\u00E3o.\n movement_id uuid REFERENCES public.plg_financial_movements(id) ON DELETE SET NULL,\n amount numeric(14,2),\n reason text,\n created_by_name text,\n payload jsonb,\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 4. Unidade, \u00EDndices, updated_at e a cerca\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nDO $$\nDECLARE t text;\nBEGIN\n FOREACH t IN ARRAY ARRAY['plg_financial_cash_session_lines','plg_financial_cash_session_payments',\n 'plg_financial_cash_session_adjustments'] LOOP\n BEGIN\n EXECUTE format('ALTER TABLE public.%I ADD CONSTRAINT %I FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL', t, t || '_unit_fkey');\n EXCEPTION WHEN duplicate_object THEN NULL; END;\n END LOOP;\nEND $$;\n\nCREATE INDEX IF NOT EXISTS plg_financial_cash_session_lines_sessao_idx\n ON public.plg_financial_cash_session_lines (session_id);\nCREATE INDEX IF NOT EXISTS plg_financial_cash_session_payments_sessao_idx\n ON public.plg_financial_cash_session_payments (session_id);\nCREATE INDEX IF NOT EXISTS plg_financial_cash_session_adjustments_sessao_idx\n ON public.plg_financial_cash_session_adjustments (session_id, created_at DESC);\n\nDO $$\nDECLARE t text;\nBEGIN\n FOREACH t IN ARRAY ARRAY['plg_financial_cash_session_lines','plg_financial_cash_session_payments'] LOOP\n BEGIN\n EXECUTE format('CREATE TRIGGER %I BEFORE UPDATE ON public.%I FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at()', t || '_updated_at', t);\n EXCEPTION WHEN duplicate_object THEN NULL; END;\n END LOOP;\nEND $$;\n\n-- AUTHZ PELO TEMPLATE DA CASA, n\u00E3o \u00E0 m\u00E3o.\n--\n-- Aqui havia um la\u00E7o que criava quatro pol\u00EDticas por tabela, assim:\n--\n-- CREATE POLICY %I ON public.%I FOR SELECT USING (tenant_id = app.current_tenant_id())\n--\n-- Tr\u00EAs coisas erradas nisso, e as tr\u00EAs o banco de testes cobra:\n--\n-- 1. **Sem `TO authenticated`, a pol\u00EDtica \u00E9 `FOR PUBLIC`** \u2014 e o pgTAP tem uma\n-- linha s\u00F3 para isso: *\u00ABno policy is FOR PUBLIC (every policy is TO\n-- authenticated / a named role)\u00BB*.\n-- 2. **Sem permiss\u00E3o**: a pol\u00EDtica olhava s\u00F3 o inquilino. Quem entra na casa\n-- l\u00EA o caixa inteiro, sem passar por `app.has_permission`.\n-- 3. **Fora do registro**: `app.scaffold_registry` \u00E9 o censo de quem declarou\n-- o pr\u00F3prio acesso, e o teste cobra que NENHUMA `plg_financial_*` fique de\n-- fora em modo `enforce`. Uma tabela financeira sem template \u00E9 uma tabela\n-- cujo acesso ningu\u00E9m declarou.\n--\n-- As tr\u00EAs s\u00E3o partes da SESS\u00C3O de caixa, ent\u00E3o levam o recurso dela \u2014\n-- `financial.cash_session`, o mesmo de `plg_financial_cash_register_sessions`\n-- no baseline. Quem pode ver a sess\u00E3o pode ver as linhas dela.\n--\n-- `unit_optional` e n\u00E3o `unit`: a linha carrega `unit_id` quando sabe, e uma\n-- linha sem unidade n\u00E3o pode sumir da confer\u00EAncia por causa de um campo vazio.\nSELECT app.scaffold_table('public.plg_financial_cash_session_lines'::regclass,\n 'financial.cash_session', 'financial', false, 'enforce');\nSELECT app.scaffold_scope('public.plg_financial_cash_session_lines'::regclass, 'unit_optional', true);\n\nSELECT app.scaffold_table('public.plg_financial_cash_session_payments'::regclass,\n 'financial.cash_session', 'financial', false, 'enforce');\nSELECT app.scaffold_scope('public.plg_financial_cash_session_payments'::regclass, 'unit_optional', true);\n\nSELECT app.scaffold_table('public.plg_financial_cash_session_adjustments'::regclass,\n 'financial.cash_session', 'financial', false, 'enforce');\nSELECT app.scaffold_scope('public.plg_financial_cash_session_adjustments'::regclass, 'unit_optional', true);\n";
|
|
99
|
+
export declare const MIGRATION_098_A_CONTA_QUE_NINGUEM_PAGOU_AINDA_MUDA_DE_DATA_E_DE_PARCELAS = "-- ============================================================================\n-- 098 \u2014 a conta que ningu\u00E9m pagou ainda muda de data e de parcelas\n--\n-- Uma conta a pagar nasce ISSUED (issue_date DEFAULT current_date), e o gatilho\n-- de imutabilidade tratava isso como \"lan\u00E7ada no livro\": a compet\u00EAncia n\u00E3o\n-- mudava mais, e o formul\u00E1rio de edi\u00E7\u00E3o mostrava a data como TEXTO, sem\n-- calend\u00E1rio. O cronograma seguia a mesma sorte \u2014 os atalhos 1x/2x/3x sumiam na\n-- edi\u00E7\u00E3o, e salvar s\u00F3 reescrevia o vencimento de quem j\u00E1 existia.\n--\n-- Mas um t\u00EDtulo que ningu\u00E9m pagou n\u00E3o \u00E9 hist\u00F3ria: \u00E9 um RASCUNHO com n\u00FAmero.\n-- Errar a data ou o n\u00FAmero de parcelas no cadastro e ter de cancelar e refazer\n-- a conta \u2014 queimando um n\u00FAmero de sequ\u00EAncia e enchendo a auditoria de\n-- cancelamentos \u2014 \u00E9 caro para uma corre\u00E7\u00E3o de digita\u00E7\u00E3o.\n--\n-- A regra vira: enquanto NADA foi recebido, a compet\u00EAncia e o cronograma s\u00E3o\n-- edit\u00E1veis. Depois do primeiro centavo, tudo volta a ser imut\u00E1vel, e a\n-- corre\u00E7\u00E3o \u00E9 cancelar e reemitir, como sempre foi.\n--\n-- O per\u00EDodo fechado continua mandando: o gatilho de 006 j\u00E1 dispara em\n-- UPDATE OF competence_date e recusa compet\u00EAncia em m\u00EAs fechado. Este arquivo\n-- n\u00E3o o afrouxa.\n-- ============================================================================\n\n-- \u2500\u2500 1. a compet\u00EAncia de um t\u00EDtulo sem recebimento pode ser corrigida \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.trg_plg_financial_invoices_immutable() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE v_paid numeric;\nBEGIN\n -- Not closed yet: a draft is a draft.\n IF OLD.issue_date IS NULL OR OLD.cancelled_at IS NOT NULL THEN RETURN NEW; END IF;\n\n -- The named exception: cancelling, and the bookkeeping that goes with it.\n IF NEW.cancelled_at IS NOT NULL AND OLD.cancelled_at IS NULL THEN RETURN NEW; END IF;\n\n IF NEW.total IS DISTINCT FROM OLD.total\n OR NEW.direction IS DISTINCT FROM OLD.direction\n OR NEW.counterparty_person_id IS DISTINCT FROM OLD.counterparty_person_id\n OR NEW.order_id IS DISTINCT FROM OLD.order_id\n OR NEW.number IS DISTINCT FROM OLD.number\n OR NEW.issue_date IS DISTINCT FROM OLD.issue_date\n OR NEW.currency IS DISTINCT FROM OLD.currency THEN\n RAISE EXCEPTION 'plg_financial_invoices: invoice % is issued and cannot be altered', OLD.number\n USING ERRCODE = '23514',\n HINT = 'A closed invoice is immutable (PRD 04). Correct it with a new invoice, or cancel this one through financial_cancel_invoice.';\n END IF;\n\n -- A compet\u00EAncia saiu da lista acima e ganhou a sua pr\u00F3pria pergunta: o pre\u00E7o\n -- dela \u00E9 uma varredura das parcelas, e n\u00E3o vale pag\u00E1-lo em toda grava\u00E7\u00E3o de\n -- observa\u00E7\u00E3o.\n IF NEW.competence_date IS DISTINCT FROM OLD.competence_date THEN\n SELECT coalesce(sum(b.paid), 0) INTO v_paid\n FROM public.v_financial_installment_balances b WHERE b.invoice_id = OLD.id;\n IF v_paid > 0 THEN\n RAISE EXCEPTION 'plg_financial_invoices: invoice % has % already received and its competence date cannot move', OLD.number, v_paid\n USING ERRCODE = '23514',\n HINT = 'Reverse the payments first, or correct it with a new invoice.';\n END IF;\n END IF;\n\n RETURN NEW;\nEND $$;\n\nCOMMENT ON FUNCTION public.trg_plg_financial_invoices_immutable() IS\n 'T\u00EDtulo emitido \u00E9 imut\u00E1vel, menos a compet\u00EAncia enquanto nada foi recebido (098).';\n\n-- \u2500\u2500 2. trocar o cronograma inteiro, de uma vez \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Por que RPC e n\u00E3o tr\u00EAs chamadas do cliente: o cronograma tem de somar o\n-- total do t\u00EDtulo, e (invoice_id, number) \u00E9 UNIQUE e n\u00E3o deferrable. Apagar e\n-- reinserir numa transa\u00E7\u00E3o \u00E9 a \u00FAnica forma de ir de 3x para 2x sem passar por\n-- um estado inv\u00E1lido \u2014 e de garantir que uma falha no meio n\u00E3o deixe o t\u00EDtulo\n-- com meia parcela.\nCREATE OR REPLACE FUNCTION public.financial_reschedule_invoice(\n p_invoice_id uuid,\n p_installments jsonb,\n p_idempotency_key text DEFAULT NULL::text\n) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_invoice public.plg_financial_invoices%ROWTYPE;\n v_paid numeric;\n v_mirror int;\n v_sum numeric := 0;\n v_i jsonb;\n v_n int := 0;\n v_out jsonb := '[]'::jsonb;\n v_inst_id uuid;\n v_due date;\n v_amount numeric;\n v_meta jsonb;\n v_old jsonb;\nBEGIN\n SELECT * INTO v_invoice FROM public.plg_financial_invoices WHERE id = p_invoice_id AND tenant_id = v_tenant;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: invoice % not found', p_invoice_id USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission('financial.edit', v_invoice.unit_id);\n PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);\n\n IF v_invoice.cancelled_at IS NOT NULL THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: invoice % is cancelled', v_invoice.number USING ERRCODE = '55000';\n END IF;\n\n SELECT coalesce(sum(b.paid), 0) INTO v_paid\n FROM public.v_financial_installment_balances b WHERE b.invoice_id = p_invoice_id;\n IF v_paid > 0 THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: invoice % has % already received; its schedule is history now', v_invoice.number, v_paid\n USING ERRCODE = '55000',\n HINT = 'Move the due date of an unpaid installment instead, or cancel and reissue.';\n END IF;\n\n -- O espelho legado (`bill`) \u00E9 append-only: nem apaga nem muda de valor. Um\n -- t\u00EDtulo vindo do V1 carrega esses movimentos, e reescrever o cronograma\n -- deixaria o espelho apontando para parcela que n\u00E3o existe mais.\n SELECT count(*) INTO v_mirror FROM public.plg_financial_movements m\n WHERE m.financial_invoice_id = p_invoice_id AND m.movement_kind = 'bill' AND m.status <> 'cancelled';\n IF v_mirror > 0 THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: invoice % carries legacy bill movements and cannot be rescheduled', v_invoice.number\n USING ERRCODE = '55000',\n HINT = 'Cancel and reissue this invoice.';\n END IF;\n\n IF jsonb_typeof(p_installments) <> 'array' OR jsonb_array_length(p_installments) = 0 THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: installments must be a non-empty array' USING ERRCODE = '22023';\n END IF;\n FOR v_i IN SELECT * FROM jsonb_array_elements(p_installments) LOOP\n v_sum := v_sum + round(coalesce((v_i ->> 'amount')::numeric, 0), 2);\n END LOOP;\n IF v_sum <> round(v_invoice.total, 2) THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: installments sum to % but the invoice total is %', v_sum, v_invoice.total\n USING ERRCODE = '23514';\n END IF;\n\n -- O que estava anexado \u00E0 parcela de n\u00FAmero N volta para a parcela de n\u00FAmero\n -- N. Sem isso, trocar 2x por 3x apagaria em sil\u00EAncio o que o cadastro tinha\n -- pendurado ali.\n SELECT coalesce(jsonb_object_agg(i.number::text, i.metadata), '{}'::jsonb) INTO v_old\n FROM public.plg_financial_invoice_installments i WHERE i.invoice_id = p_invoice_id;\n\n DELETE FROM public.plg_financial_invoice_installments WHERE invoice_id = p_invoice_id;\n\n FOR v_i IN SELECT * FROM jsonb_array_elements(p_installments) LOOP\n v_n := v_n + 1;\n v_due := coalesce((v_i ->> 'due_date')::date, v_invoice.competence_date, current_date);\n v_amount := round(coalesce((v_i ->> 'amount')::numeric, 0), 2);\n v_meta := CASE WHEN jsonb_typeof(v_i -> 'metadata') = 'object' THEN v_i -> 'metadata'\n ELSE coalesce(v_old -> v_n::text, '{}'::jsonb) END;\n INSERT INTO public.plg_financial_invoice_installments\n (tenant_id, unit_id, invoice_id, number, due_date, amount, metadata)\n VALUES (v_tenant, v_invoice.unit_id, p_invoice_id, v_n, v_due, v_amount, v_meta)\n RETURNING id INTO v_inst_id;\n v_out := v_out || jsonb_build_object('id', v_inst_id, 'number', v_n, 'due_date', v_due, 'amount', v_amount);\n END LOOP;\n\n PERFORM public.plg_financial_audit(v_tenant, 'financial.invoice.reschedule', 'plg_financial_invoices', p_invoice_id::text,\n jsonb_build_object('number', v_invoice.number, 'installments', v_n, 'idempotency_key', p_idempotency_key));\n\n RETURN jsonb_build_object('invoice_id', p_invoice_id, 'installments', v_out);\nEND $$;\n\nCOMMENT ON FUNCTION public.financial_reschedule_invoice(uuid, jsonb, text) IS\n 'Troca o cronograma inteiro de um t\u00EDtulo sem recebimento; soma tem de bater com o total (098).';\n\nREVOKE ALL ON FUNCTION public.financial_reschedule_invoice(uuid, jsonb, text) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.financial_reschedule_invoice(uuid, jsonb, text) TO authenticated;\nGRANT ALL ON FUNCTION public.financial_reschedule_invoice(uuid, jsonb, text) TO service_role;\n";
|
|
100
|
+
export declare const MIGRATION_099_OS_ITENS_DE_UMA_CONTA_SEM_RECEBIMENTO_AINDA_SE_CORRIGEM = "-- ============================================================================\n-- 099 \u2014 os itens de uma conta sem recebimento ainda se corrigem\n--\n-- A 098 abriu a data e o cronograma enquanto o t\u00EDtulo n\u00E3o recebeu nada. O que\n-- ficou fechado foram os ITENS: `items_invoice_line_immutable` recusa qualquer\n-- UPDATE ou DELETE de linha de fatura emitida, e o total do t\u00EDtulo \u00E9 imut\u00E1vel\n-- no gatilho da 000. Resultado: digitar 140 no lugar de 14 no cadastro s\u00F3 se\n-- conserta cancelando a conta e lan\u00E7ando outra \u2014 queimando um n\u00FAmero de\n-- sequ\u00EAncia e enchendo a auditoria de cancelamentos por causa de um d\u00EDgito.\n--\n-- A regra \u00E9 a mesma da 098, e \u00E9 a regra certa: um t\u00EDtulo que ningu\u00E9m pagou \u00E9\n-- um RASCUNHO COM N\u00DAMERO. Depois do primeiro centavo tudo volta a ser hist\u00F3ria\n-- \u2014 a linha virou raz\u00E3o, e raz\u00E3o n\u00E3o se reescreve.\n--\n-- O per\u00EDodo fechado continua mandando: o gatilho de 006 dispara em\n-- UPDATE OF competence_date, total e recusa compet\u00EAncia em m\u00EAs fechado.\n-- ============================================================================\n\n-- \u2500\u2500 1. a linha de uma fatura sem recebimento \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.trg_invoice_line_immutable() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE v_issued date; v_cancelled timestamptz; v_invoice uuid; v_paid numeric;\nBEGIN\n v_invoice := coalesce(NEW.invoice_id, OLD.invoice_id);\n IF v_invoice IS NULL THEN RETURN coalesce(NEW, OLD); END IF;\n SELECT f.issue_date, f.cancelled_at INTO v_issued, v_cancelled\n FROM public.plg_financial_invoices f WHERE f.id = v_invoice;\n IF v_issued IS NOT NULL AND v_cancelled IS NULL THEN\n -- O que decide n\u00E3o \u00E9 mais \"foi emitida\", \u00E9 \"j\u00E1 entrou dinheiro\".\n SELECT coalesce(sum(b.paid), 0) INTO v_paid\n FROM public.v_financial_installment_balances b WHERE b.invoice_id = v_invoice;\n IF v_paid > 0 THEN\n RAISE EXCEPTION 'items: the lines of invoice % cannot be altered \u2014 % already received', v_invoice, v_paid\n USING ERRCODE = '23514',\n HINT = 'A paid invoice is immutable (PRD 04). Reverse the payments, or raise a new invoice.';\n END IF;\n END IF;\n RETURN coalesce(NEW, OLD);\nEND $$;\n\nCOMMENT ON FUNCTION public.trg_invoice_line_immutable() IS\n 'Linha de fatura \u00E9 imut\u00E1vel depois do primeiro recebimento; antes dele, ainda \u00E9 rascunho (099).';\n\n-- \u2500\u2500 2. e o total que essas linhas somam \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.trg_plg_financial_invoices_immutable() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE v_paid numeric;\nBEGIN\n IF OLD.issue_date IS NULL OR OLD.cancelled_at IS NOT NULL THEN RETURN NEW; END IF;\n IF NEW.cancelled_at IS NOT NULL AND OLD.cancelled_at IS NULL THEN RETURN NEW; END IF;\n\n IF NEW.direction IS DISTINCT FROM OLD.direction\n OR NEW.counterparty_person_id IS DISTINCT FROM OLD.counterparty_person_id\n OR NEW.order_id IS DISTINCT FROM OLD.order_id\n OR NEW.number IS DISTINCT FROM OLD.number\n OR NEW.issue_date IS DISTINCT FROM OLD.issue_date\n OR NEW.currency IS DISTINCT FROM OLD.currency THEN\n RAISE EXCEPTION 'plg_financial_invoices: invoice % is issued and cannot be altered', OLD.number\n USING ERRCODE = '23514',\n HINT = 'A closed invoice is immutable (PRD 04). Correct it with a new invoice, or cancel this one through financial_cancel_invoice.';\n END IF;\n\n -- Compet\u00EAncia (098) e total (099) t\u00EAm a mesma licen\u00E7a e o mesmo limite, e a\n -- pergunta s\u00F3 se faz quando um dos dois mudou: ela varre as parcelas, e n\u00E3o\n -- vale pag\u00E1-la em toda grava\u00E7\u00E3o de observa\u00E7\u00E3o.\n IF NEW.competence_date IS DISTINCT FROM OLD.competence_date\n OR NEW.total IS DISTINCT FROM OLD.total THEN\n SELECT coalesce(sum(b.paid), 0) INTO v_paid\n FROM public.v_financial_installment_balances b WHERE b.invoice_id = OLD.id;\n IF v_paid > 0 THEN\n RAISE EXCEPTION 'plg_financial_invoices: invoice % has % already received and cannot be rewritten', OLD.number, v_paid\n USING ERRCODE = '23514',\n HINT = 'Reverse the payments first, or correct it with a new invoice.';\n END IF;\n END IF;\n\n RETURN NEW;\nEND $$;\n\nCOMMENT ON FUNCTION public.trg_plg_financial_invoices_immutable() IS\n 'T\u00EDtulo emitido \u00E9 imut\u00E1vel; compet\u00EAncia e total ainda se corrigem enquanto nada foi recebido (098, 099).';\n";
|
|
101
|
+
export declare const MIGRATION_100_A_CONTA_BANCARIA_DIZ_DE_QUEM_ELA_E = "-- ---------------------------------------------------------------------------\n-- 100_a_conta_bancaria_diz_de_quem_ela_e.sql \u2014 quatro colunas que faltavam\n-- para uma conta banc\u00E1ria ser uma conta banc\u00E1ria.\n--\n-- `plg_financial_accounts` j\u00E1 sabia o nome do banco, a ag\u00EAncia e o n\u00FAmero da\n-- conta. Faltava o que a tela de cadastro pergunta logo depois, e o que o boleto\n-- e o arquivo CNAB exigem antes de sair:\n--\n-- bank_code o N\u00DAMERO do banco \u2014 077, 104, 290. N\u00E3o \u00E9 decora\u00E7\u00E3o: \u00E9 o\n-- que identifica a institui\u00E7\u00E3o em qualquer integra\u00E7\u00E3o\n-- banc\u00E1ria, e \u00E9 a chave que o CNAB e o PIX usam. \"Inter\" \u00E9\n-- como a pessoa chama; \"077\" \u00E9 como o sistema banc\u00E1rio\n-- chama, e os dois s\u00E3o necess\u00E1rios.\n--\n-- holder_name o TITULAR. A conta de uma rede raramente est\u00E1 no nome da\n-- rede: est\u00E1 no nome do s\u00F3cio, da holding, da empresa de\n-- servi\u00E7os. Quem confere um extrato precisa saber de quem \u00E9\n-- a conta antes de bater o valor.\n--\n-- holder_document CPF ou CNPJ do titular. \u00C9 o par do nome \u2014 e sem ele uma\n-- transfer\u00EAncia sai para o titular errado com o nome certo.\n--\n-- card_brand_id a BANDEIRA, para a conta que \u00E9 de cart\u00E3o. Uma conta\n-- `card_payable` \u00E9 \"o cart\u00E3o Amex do Silvio\", e sem a\n-- bandeira ela \u00E9 s\u00F3 \"um cart\u00E3o\": a fatura n\u00E3o se concilia\n-- com a adquirente, e a taxa de `plg_financial_card_fee_configs`\n-- \u2014 que J\u00C1 \u00E9 por bandeira \u2014 n\u00E3o tem como encontrar a conta.\n--\n-- Por que aqui e n\u00E3o numa tabela \u00E0 parte: `plg_financial_accounts` j\u00E1 carrega\n-- `bank_name`, `agency_number` e `account_number`. P\u00F4r o c\u00F3digo do banco numa\n-- segunda tabela separaria tr\u00EAs campos do mesmo formul\u00E1rio em dois lugares, e a\n-- primeira consulta que algu\u00E9m escrevesse teria de junt\u00E1-los de novo.\n--\n-- Todas NULL: uma conta de pessoa, de caixa f\u00EDsico ou de sess\u00E3o n\u00E3o tem banco\n-- nem titular, e exigir qualquer uma delas transformaria as quatro num campo\n-- obrigat\u00F3rio para quem n\u00E3o tem o que preencher.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_financial_accounts\n ADD COLUMN IF NOT EXISTS bank_code text,\n ADD COLUMN IF NOT EXISTS holder_name text,\n ADD COLUMN IF NOT EXISTS holder_document text,\n ADD COLUMN IF NOT EXISTS card_brand_id uuid;\n\n-- A bandeira \u00E9 uma REFER\u00CANCIA ao cat\u00E1logo, n\u00E3o um texto solto. \"Master\",\n-- \"MASTERCARD\" e \"mastercard\" digitados em tr\u00EAs contas diferentes s\u00E3o a mesma\n-- bandeira, e s\u00F3 o cat\u00E1logo faz com que a taxa as encontre.\n--\n-- ON DELETE SET NULL e n\u00E3o CASCADE: apagar uma bandeira do cat\u00E1logo n\u00E3o pode\n-- levar junto a conta banc\u00E1ria da casa.\nDO $$\nBEGIN\n IF to_regclass('public.plg_financial_card_brands') IS NULL THEN RETURN; END IF;\n IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_accounts_card_brand_fk') THEN RETURN; END IF;\n ALTER TABLE public.plg_financial_accounts\n ADD CONSTRAINT plg_financial_accounts_card_brand_fk\n FOREIGN KEY (card_brand_id) REFERENCES public.plg_financial_card_brands(id) ON DELETE SET NULL;\nEND $$;\n\n-- O c\u00F3digo do banco \u00E9 num\u00E9rico e curto (o COMPE brasileiro tem 3 d\u00EDgitos; a\n-- ISPB, 8). O CHECK recusa o nome do banco digitado no campo do c\u00F3digo, que \u00E9 o\n-- erro que este arquivo existe para tornar imposs\u00EDvel \u2014 e \u00E9 exatamente o erro\n-- que o V1 cometeu: l\u00E1 `bank_name` guarda \"077\" em algumas linhas.\nDO $$\nBEGIN\n IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_accounts_bank_code_ck') THEN RETURN; END IF;\n ALTER TABLE public.plg_financial_accounts\n ADD CONSTRAINT plg_financial_accounts_bank_code_ck\n CHECK (bank_code IS NULL OR bank_code ~ '^[0-9]{3,8}$');\nEND $$;\n\nCREATE INDEX IF NOT EXISTS plg_financial_accounts_card_brand_idx\n ON public.plg_financial_accounts (tenant_id, card_brand_id)\n WHERE card_brand_id IS NOT NULL;\n\nCOMMENT ON COLUMN public.plg_financial_accounts.bank_code IS\n 'N\u00FAmero da institui\u00E7\u00E3o (COMPE, 3 d\u00EDgitos, ou ISPB). \u00C9 o identificador que CNAB, boleto e PIX usam; `bank_name` \u00E9 o nome que a pessoa l\u00EA.';\nCOMMENT ON COLUMN public.plg_financial_accounts.holder_name IS\n 'Titular da conta. Raramente \u00E9 o nome do tenant: \u00E9 o s\u00F3cio, a holding ou a empresa de servi\u00E7os em cujo nome a conta est\u00E1 aberta.';\nCOMMENT ON COLUMN public.plg_financial_accounts.holder_document IS\n 'CPF ou CNPJ do titular, como digitado. Par obrigat\u00F3rio de holder_name para qualquer transfer\u00EAncia.';\nCOMMENT ON COLUMN public.plg_financial_accounts.card_brand_id IS\n 'Bandeira, para conta de cart\u00E3o (kind card_payable / card_receivable_*). \u00C9 o que liga a conta \u00E0s taxas de plg_financial_card_fee_configs, que j\u00E1 s\u00E3o por bandeira.';\n";
|
|
102
|
+
export declare const MIGRATION_101_O_CARTAO_SE_RECONHECE_PELOS_QUATRO_ULTIMOS = "-- ---------------------------------------------------------------------------\n-- 101_o_cartao_se_reconhece_pelos_quatro_ultimos.sql\n--\n-- `plg_financial_accounts.last_four` \u2014 os quatro \u00FAltimos d\u00EDgitos do cart\u00E3o.\n--\n-- \u2500\u2500 por que isto \u00E9 um campo, e n\u00E3o um detalhe \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Uma casa tem \u00ABCart\u00E3o Silvio Mario\u00BB, \u00ABCart\u00E3o Silvio Master 0307\u00BB e \u00ABCart\u00E3o\n-- Silvio Amex 0016\u00BB \u2014 tr\u00EAs nomes que s\u00F3 se distinguem porque ALGU\u00C9M digitou o\n-- final do n\u00FAmero dentro do nome. \u00C9 o mesmo sintoma que a unidade tinha nas\n-- contas hom\u00F4nimas: o operador p\u00F5e no campo errado o dado que a tela n\u00E3o pede.\n--\n-- Com coluna, o final volta a ser o que ele \u00E9 \u2014 a identifica\u00E7\u00E3o do cart\u00E3o na\n-- fatura e no extrato da adquirente, que \u00E9 por onde se concilia \u2014 e o NOME\n-- volta a ser o nome (\u00ABCart\u00E3o do Silvio\u00BB). Quem j\u00E1 digitou no nome n\u00E3o perde\n-- nada: a coluna nasce vazia e o nome continua l\u00E1.\n--\n-- \u2500\u2500 quatro d\u00EDgitos, e s\u00F3 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- O CHECK recusa qualquer coisa que n\u00E3o sejam exatamente quatro d\u00EDgitos. N\u00E3o \u00E9\n-- rigor decorativo: um campo livre aqui recebe \u00AB1234-5678-9012-3456\u00BB mais cedo\n-- ou mais tarde, e guardar o n\u00FAmero inteiro de um cart\u00E3o \u00E9 um problema de outra\n-- natureza \u2014 dado de portador que ningu\u00E9m aqui quer ter, nem para conciliar.\n-- Quatro d\u00EDgitos n\u00E3o identificam um cart\u00E3o para cobrar nele; identificam para\n-- RECONHECER, que \u00E9 tudo o que a tela precisa.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_financial_accounts\n ADD COLUMN IF NOT EXISTS last_four text;\n\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'plg_financial_accounts_last_four_check'\n ) THEN\n ALTER TABLE public.plg_financial_accounts\n ADD CONSTRAINT plg_financial_accounts_last_four_check\n CHECK (last_four IS NULL OR last_four ~ '^[0-9]{4}$');\n END IF;\nEND $$;\n\nCOMMENT ON COLUMN public.plg_financial_accounts.last_four IS\n 'Os quatro \u00FAltimos d\u00EDgitos do cart\u00E3o \u2014 o que a fatura e o extrato da adquirente mostram, e por onde se reconhece qual cart\u00E3o \u00E9. NUNCA o n\u00FAmero completo: o CHECK recusa qualquer coisa que n\u00E3o sejam exatamente quatro d\u00EDgitos.';\n\nCOMMENT ON COLUMN public.plg_financial_accounts.color IS\n 'A cor do cart\u00E3o, como ele \u00E9 na carteira. Serve para reconhec\u00EA-lo de relance numa lista onde tr\u00EAs linhas come\u00E7am com a mesma palavra.';\n";
|
|
103
|
+
export declare const MIGRATION_102_A_BANDEIRA_DO_CARTAO_E_DO_PAIS_NAO_DO_LOJISTA = "-- ---------------------------------------------------------------------------\n-- 102 \u2014 a bandeira do cart\u00E3o \u00E9 do PA\u00CDS, n\u00E3o do lojista.\n--\n-- Mesma hist\u00F3ria da 002, outra tabela. `plg_financial_card_brands` nasce vazia\n-- em todo tenant: o Feira Demo tem ZERO bandeiras e o campo \u00ABBandeira\u00BB do\n-- cadastro de cart\u00E3o abre um seletor sem nenhuma op\u00E7\u00E3o. O lojista n\u00E3o tem como\n-- adivinhar que precisa cadastrar \u00ABVisa\u00BB antes de cadastrar o cart\u00E3o Visa.\n--\n-- E o custo n\u00E3o \u00E9 s\u00F3 de cadastro: as taxas de `plg_financial_card_fee_configs`\n-- J\u00C1 s\u00E3o por bandeira. Sem cat\u00E1logo, a conta de receb\u00EDveis n\u00E3o encontra a taxa,\n-- e a concilia\u00E7\u00E3o do cart\u00E3o n\u00E3o fecha.\n--\n-- POR QUE POR TENANT E N\u00C3O GLOBAL \u2014 a mesma raz\u00E3o que a 002 escreveu:\n-- `tenant_id` \u00E9 NOT NULL, e uma linha global n\u00E3o cabe sem mexer no schema e na\n-- RLS de uma tabela que j\u00E1 tem 57 linhas em produ\u00E7\u00E3o. Semear por tenant d\u00E1 o\n-- mesmo resultado vis\u00EDvel (a lista vem pronta) sem tocar em isolamento, e\n-- `is_active` continua sendo o que \u00C9 do lojista: aceitar ou n\u00E3o cada uma.\n--\n-- QUAIS: as que passam num balc\u00E3o brasileiro. N\u00E3o \u00E9 a lista completa das\n-- bandeiras do mundo \u2014 um seletor com quarenta op\u00E7\u00F5es \u00E9 a mesma polui\u00E7\u00E3o que a\n-- lista vazia, do outro lado. Vale-refei\u00E7\u00E3o entra porque no Brasil ele passa na\n-- mesma maquininha e concilia pelo mesmo caminho.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.financial_seed_card_brands(p_tenant uuid)\nRETURNS void\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = public, pg_catalog\nAS $$\nBEGIN\n -- S\u00F3 quem ainda n\u00E3o tem nenhuma. Um tenant que j\u00E1 montou o cat\u00E1logo dele n\u00E3o\n -- recebe as nossas por cima \u2014 a 002 aprendeu isso do lado das formas.\n IF EXISTS (SELECT 1 FROM public.plg_financial_card_brands WHERE tenant_id = p_tenant) THEN\n RETURN;\n END IF;\n\n INSERT INTO public.plg_financial_card_brands (tenant_id, name, is_active)\n SELECT p_tenant, nome, ativo\n FROM (VALUES\n ('Visa', true),\n ('Mastercard', true),\n ('Elo', true),\n ('American Express', true),\n ('Hipercard', true),\n ('Diners Club', false),\n ('Discover', false),\n -- Vale-refei\u00E7\u00E3o e vale-alimenta\u00E7\u00E3o passam na mesma maquininha e conciliam\n -- pelo mesmo caminho; para o raz\u00E3o, s\u00E3o bandeira como as outras.\n ('Alelo', true),\n ('Sodexo', true),\n ('Ticket', true),\n ('VR', true),\n ('Pluxee', true)\n ) AS t(nome, ativo);\nEND $$;\n\nCOMMENT ON FUNCTION public.financial_seed_card_brands(uuid) IS\n 'Semeia o cat\u00E1logo de bandeiras de cart\u00E3o de um tenant. Idempotente: n\u00E3o faz nada se o tenant j\u00E1 tem alguma. Ver a 102 e a 002 \u2014 bandeira e forma de pagamento s\u00E3o do pa\u00EDs, n\u00E3o do lojista.';\n\nGRANT EXECUTE ON FUNCTION public.financial_seed_card_brands(uuid) TO authenticated;\n\n-- Todo tenant que existe hoje e est\u00E1 sem cat\u00E1logo.\nDO $$\nDECLARE t record;\nBEGIN\n FOR t IN SELECT id FROM public.tenants LOOP\n PERFORM public.financial_seed_card_brands(t.id);\n END LOOP;\nEND $$;\n\n-- E os que vierem. Mesmo gatilho que a 002 pendurou para as formas.\nCREATE OR REPLACE FUNCTION public.trg_seed_card_brands()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = public, pg_catalog\nAS $$\nBEGIN\n PERFORM public.financial_seed_card_brands(NEW.id);\n RETURN NEW;\nEND $$;\n\nDROP TRIGGER IF EXISTS seed_card_brands ON public.tenants;\nCREATE TRIGGER seed_card_brands\n AFTER INSERT ON public.tenants\n FOR EACH ROW EXECUTE FUNCTION public.trg_seed_card_brands();\n";
|
|
104
|
+
export declare const MIGRATION_103_O_RAIL_DA_FICHA_E_IGUAL_EM_TODO_CADASTRO = "-- ---------------------------------------------------------------------------\n-- 103 \u2014 o rail da ficha \u00E9 igual em todo cadastro.\n--\n-- Irm\u00E3 da 150 do n\u00FAcleo, para os cadastros do financeiro. Mesmo argumento: o\n-- rail direito responde sempre \u00E0s mesmas quatro perguntas \u2014 de que unidade \u00E9,\n-- se vale, o que algu\u00E9m anotou, e como a casa recorta este registro \u2014, e um\n-- lugar fixo s\u00F3 \u00E9 fixo se estiver em todos.\n--\n-- Aqui a falta do\u00EDa mais do que no n\u00FAcleo: uma conta banc\u00E1ria \u00E9 exatamente o\n-- tipo de registro sobre o qual algu\u00E9m precisa anotar (\u00ABconta antiga, s\u00F3 para\n-- o aluguel\u00BB), e n\u00E3o tinha onde.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_financial_accounts\n ADD COLUMN IF NOT EXISTS notes text,\n ADD COLUMN IF NOT EXISTS tags text[] DEFAULT '{}'::text[];\n\nALTER TABLE public.plg_financial_chart_of_accounts\n ADD COLUMN IF NOT EXISTS notes text,\n ADD COLUMN IF NOT EXISTS tags text[] DEFAULT '{}'::text[];\n\nALTER TABLE public.plg_financial_cost_centers\n ADD COLUMN IF NOT EXISTS notes text,\n ADD COLUMN IF NOT EXISTS tags text[] DEFAULT '{}'::text[];\n\nALTER TABLE public.plg_financial_payment_method_types\n ADD COLUMN IF NOT EXISTS notes text,\n ADD COLUMN IF NOT EXISTS tags text[] DEFAULT '{}'::text[];\n\nALTER TABLE public.plg_financial_card_brands\n ADD COLUMN IF NOT EXISTS notes text,\n ADD COLUMN IF NOT EXISTS tags text[] DEFAULT '{}'::text[];\n\nCREATE INDEX IF NOT EXISTS plg_financial_accounts_tags_gin\n ON public.plg_financial_accounts USING gin (tags);\n";
|
|
105
|
+
export declare const MIGRATION_104_SERVICO_E_O_QUE_A_CASA_VENDE = "-- Servi\u00E7o \u00E9 o que a casa VENDE, nunca o que ela compra.\n--\n-- O cat\u00E1logo de servi\u00E7os \u00E9 o que a casa FORNECE: dele saem o pre\u00E7o de tabela, a\n-- dura\u00E7\u00E3o, a comiss\u00E3o e a execu\u00E7\u00E3o. Uma conta a PAGAR que aponta para uma linha\n-- desse cat\u00E1logo lan\u00E7a a despesa contra a pr\u00F3pria linha de receita \u2014 e \u00E9 assim\n-- que \"quanto entrou de Botox\" passa a somar o que saiu.\n--\n-- O V1 \u00E9 expl\u00EDcito sobre isso e resolve na porta: o seletor \"O que deseja\n-- adicionar?\" de `PayablesForm` esconde Servi\u00E7o, Pacote e Gift Card com\n-- `!isPayable`. Numa conta a pagar sobram Produto \u2014 que a casa compra de\n-- verdade \u2014 e \"Outra Despesa\". O V2 oferecia os mesmos tipos nos dois sentidos,\n-- e a porta j\u00E1 foi fechada no aplicativo (`availableInvoiceItemTypes`).\n--\n-- Falta o que j\u00E1 entrou por ela. S\u00E3o poucas linhas e elas n\u00E3o somem: viram\n-- \"Outra Despesa\", que \u00E9 o que o V1 teria gravado. O nome digitado fica \u2014\n-- \u00ABProdu\u00E7\u00E3o Musical \u2013 Studio Session\u00BB continua dizendo o que foi pago; o que\n-- sai \u00E9 a AMARRA ao cat\u00E1logo, que \u00E9 a parte errada.\n--\n-- `service_id` tamb\u00E9m \u00E9 limpo: deix\u00E1-lo apontando faria o item continuar\n-- contando como venda daquele servi\u00E7o em qualquer relat\u00F3rio que o siga, com o\n-- tipo dizendo outra coisa. Uma linha n\u00E3o pode ter duas respostas.\n--\n-- \u2500\u2500 Por que o gatilho de imutabilidade sai do caminho \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `items_invoice_line_immutable` recusa qualquer altera\u00E7\u00E3o na linha de uma\n-- conta que j\u00E1 recebeu dinheiro, e ele est\u00E1 certo: o valor de uma conta paga \u00E9\n-- hist\u00F3rico, n\u00E3o rascunho. S\u00F3 que ele recusa a linha INTEIRA, e o que muda aqui\n-- n\u00E3o \u00E9 dinheiro \u2014 quantidade, pre\u00E7o e total ficam byte a byte como est\u00E3o. O\n-- que muda \u00E9 a CLASSIFICA\u00C7\u00C3O, que \u00E9 justamente o que est\u00E1 errado e o que\n-- nenhum extrato reproduz.\n--\n-- Por isso ele \u00E9 desligado para este UPDATE e religado em seguida, inclusive se\n-- o UPDATE falhar. Uma corre\u00E7\u00E3o de classifica\u00E7\u00E3o com o dinheiro intocado \u00E9 o\n-- caso para o qual esta porta existe.\n--\n-- Idempotente: roda de novo sem achar nada.\n\nALTER TABLE public.items DISABLE TRIGGER items_invoice_line_immutable;\n\n-- `items.service_id` S\u00D3 SE A COLUNA EXISTIR.\n--\n-- O espelho `public.services` foi aposentado pela 191 do spine \u2014 um servi\u00E7o \u00E9\n-- um produto de `kind='service'` \u2014 e com ele saiu a coluna que apontava para\n-- l\u00E1. Um pool antigo ainda a tem e precisa da limpeza; um banco levantado do\n-- zero n\u00E3o, e cit\u00E1-la derruba a cadeia em `column i.service_id does not exist`.\n-- Foi o que o db-from-zero pegou.\n--\n-- O `itemKind` \u00E9 corrigido nos dois casos: ele \u00E9 o que a tela l\u00EA.\nDO $servico$\nDECLARE v_tem_coluna boolean := EXISTS (\n SELECT 1 FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = 'items' AND column_name = 'service_id'\n);\nBEGIN\n EXECUTE format($sql$\n UPDATE public.items i\n SET metadata = jsonb_set(\n coalesce(i.metadata, '{}'::jsonb) - 'referenceId',\n '{itemKind}', '\"other\"'::jsonb, true),\n %s\n updated_at = now()\n FROM public.plg_financial_invoices fi\n WHERE fi.id = i.invoice_id\n AND fi.direction = 'payable'\n AND (i.metadata ->> 'itemKind' = 'service' %s)\n $sql$,\n CASE WHEN v_tem_coluna THEN 'service_id = NULL,' ELSE '' END,\n CASE WHEN v_tem_coluna THEN 'OR i.service_id IS NOT NULL' ELSE '' END);\nEND $servico$;\n\nALTER TABLE public.items ENABLE TRIGGER items_invoice_line_immutable;\n";
|
|
106
|
+
export declare const MIGRATION_105_A_OCORRENCIA_PROVISIONADA_VIRA_CONTA_QUANDO_ALGUEM_MANDA = "-- A ocorr\u00EAncia provisionada vira conta quando ALGU\u00C9M manda.\n--\n-- \u2500\u2500 O que o V1 faz \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- A recorr\u00EAncia do V1 tem dois tempos, e \u00E9 isso que a torna confi\u00E1vel:\n--\n-- 1. PROVISIONAR \u2014 `recurring_bill_occurrences` ganha uma linha por\n-- vencimento futuro, com `status = 'provisioned'`. Ela n\u00E3o \u00E9 conta: n\u00E3o\n-- tem fatura, n\u00E3o entra no raz\u00E3o, n\u00E3o cobra ningu\u00E9m. \u00C9 uma PREVIS\u00C3O.\n-- 2. CONSOLIDAR \u2014 algu\u00E9m clica \u00ABConsolidar\u00BB na linha e a RPC\n-- `consolidate_recurring_occurrence` GERA a conta real. A ocorr\u00EAncia\n-- passa a `consolidated` e aponta para o t\u00EDtulo gerado.\n--\n-- A lista de Contas a Pagar mostra as provisionadas como linhas virtuais ao\n-- lado das contas de verdade \u2014 o operador v\u00EA o m\u00EAs inteiro, o previsto e o\n-- lan\u00E7ado, e decide o que vira d\u00EDvida.\n--\n-- \u2500\u2500 O que o V2 tinha \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- As duas tabelas, e 1.169 ocorr\u00EAncias provisionadas migradas do V1 \u2014 todas\n-- invis\u00EDveis. `financial_recurring_run` pula o primeiro tempo: ela cria a\n-- fatura direto da r\u00E9gua, o que \u00E9 auto-consolidar sem o passo de algu\u00E9m\n-- mandar. Faltava exatamente a porta do segundo tempo.\n--\n-- \u2500\u2500 O que esta migration acrescenta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `financial_consolidate_occurrence(uuid)` \u2014 o equivalente do V1, para UMA\n-- ocorr\u00EAncia. Reusa `financial_create_invoice` (nenhum caminho paralelo at\u00E9 o\n-- dinheiro: extrato, saldo e KPI veem o mesmo evento) e copia as linhas da\n-- r\u00E9gua do mesmo jeito que `financial_recurring_run` copia.\n--\n-- Tr\u00EAs decis\u00F5es que valem explica\u00E7\u00E3o:\n--\n-- `overridden` A ocorr\u00EAncia manda no valor e na data, n\u00E3o a r\u00E9gua. O V1\n-- deixa editar o vencimento provisionado, e consolidar com o\n-- valor da r\u00E9gua desfaria a edi\u00E7\u00E3o em sil\u00EAncio.\n--\n-- idempot\u00EAncia A chave \u00E9 a mesma de `financial_recurring_run`\n-- (`recurring:<bill>:<AAAA-MM>`), ent\u00E3o consolidar \u00E0 m\u00E3o o que\n-- a rotina j\u00E1 criou devolve a fatura existente em vez de\n-- duplicar a d\u00EDvida. Consolidar \u00E9 uma a\u00E7\u00E3o de um clique numa\n-- lista; clique duplo n\u00E3o pode custar uma conta a mais.\n--\n-- permiss\u00E3o `financial.create`, a mesma de lan\u00E7ar conta \u00E0 m\u00E3o. Consolidar\n-- CRIA d\u00EDvida \u2014 n\u00E3o \u00E9 um detalhe de exibi\u00E7\u00E3o.\n--\n-- Idempotente: substitui a fun\u00E7\u00E3o.\n\nCREATE OR REPLACE FUNCTION public.financial_consolidate_occurrence(p_occurrence_id uuid)\nRETURNS jsonb\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_occ record;\n v_bill record;\n v_key text;\n v_res jsonb;\n v_invoice uuid;\n v_line jsonb;\n v_i int := 0;\n v_item uuid;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: sem tenant na sessao' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_permission('financial.create', NULL) THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: exige financial.create' USING ERRCODE = '42501';\n END IF;\n\n SELECT * INTO v_occ\n FROM public.plg_financial_recurring_bill_occurrences\n WHERE id = p_occurrence_id AND tenant_id = v_tenant;\n\n IF v_occ IS NULL THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: ocorrencia % nao encontrada', p_occurrence_id\n USING ERRCODE = 'P0002';\n END IF;\n\n -- J\u00E1 consolidada devolve o que existe. Duas pessoas na mesma lista clicam o\n -- mesmo bot\u00E3o, e a segunda n\u00E3o pode criar a segunda d\u00EDvida.\n IF v_occ.status = 'consolidated' THEN\n RETURN jsonb_build_object('occurrence_id', v_occ.id, 'invoice_id', v_occ.invoice_id, 'created', false);\n END IF;\n IF v_occ.status = 'cancelled' THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: ocorrencia cancelada' USING ERRCODE = '23514';\n END IF;\n\n SELECT * INTO v_bill\n FROM public.plg_financial_recurring_bills\n WHERE id = v_occ.recurring_bill_id AND tenant_id = v_tenant;\n\n IF v_bill IS NULL THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: regua da ocorrencia % sumiu', p_occurrence_id\n USING ERRCODE = 'P0002';\n END IF;\n\n v_key := 'recurring:' || v_bill.id::text || ':'\n || to_char(coalesce(v_occ.competence_date, v_occ.due_date), 'YYYY-MM');\n\n SELECT f.id INTO v_invoice\n FROM public.plg_financial_invoices f\n WHERE f.tenant_id = v_tenant\n AND f.metadata ->> 'recurring_key' = v_key\n AND f.cancelled_at IS NULL\n LIMIT 1;\n\n IF v_invoice IS NULL THEN\n v_res := public.financial_create_invoice(\n v_bill.direction,\n v_occ.amount,\n v_bill.counterparty_person_id,\n NULL,\n coalesce(v_occ.unit_id, v_bill.unit_id),\n jsonb_build_array(jsonb_build_object('number', 1, 'due_date', v_occ.due_date, 'amount', v_occ.amount)),\n coalesce(v_occ.competence_date, v_occ.due_date),\n v_bill.description,\n coalesce(v_occ.notes, v_bill.notes),\n jsonb_build_object('source', 'financial_consolidate_occurrence',\n 'recurring_bill_id', v_bill.id,\n 'recurring_occurrence_id', v_occ.id,\n 'recurring_key', v_key),\n v_key\n );\n v_invoice := (v_res ->> 'invoice_id')::uuid;\n\n FOR v_line IN SELECT * FROM jsonb_array_elements(coalesce(v_bill.items, '[]'::jsonb))\n LOOP\n INSERT INTO public.items (\n tenant_id, invoice_id, name, quantity, unit_price, discount, total, sort_order, metadata\n ) VALUES (\n v_tenant, v_invoice,\n v_line ->> 'description',\n coalesce((v_line ->> 'quantity')::numeric, 1),\n coalesce((v_line ->> 'unit_price')::numeric, 0),\n coalesce((v_line ->> 'discount')::numeric, 0),\n round(coalesce((v_line ->> 'quantity')::numeric, 1) * coalesce((v_line ->> 'unit_price')::numeric, 0)\n - coalesce((v_line ->> 'discount')::numeric, 0)\n + coalesce((v_line ->> 'surcharge')::numeric, 0), 2),\n v_i,\n jsonb_build_object('itemKind', v_line ->> 'item_kind', 'referenceId', v_line ->> 'reference_id')\n )\n RETURNING id INTO v_item;\n\n IF (v_line ->> 'account_id') IS NOT NULL OR (v_line ->> 'cost_center_id') IS NOT NULL THEN\n INSERT INTO public.plg_financial_invoice_items (\n item_id, parent_kind, tenant_id, chart_account_id, cost_center_id\n ) VALUES (\n v_item, 'invoice', v_tenant,\n nullif(v_line ->> 'account_id', '')::uuid,\n nullif(v_line ->> 'cost_center_id', '')::uuid\n )\n ON CONFLICT (item_id) DO UPDATE\n SET chart_account_id = excluded.chart_account_id,\n cost_center_id = excluded.cost_center_id;\n END IF;\n\n v_i := v_i + 1;\n END LOOP;\n END IF;\n\n UPDATE public.plg_financial_recurring_bill_occurrences\n SET status = 'consolidated', invoice_id = v_invoice, updated_at = now()\n WHERE id = v_occ.id;\n\n RETURN jsonb_build_object('occurrence_id', v_occ.id, 'invoice_id', v_invoice, 'created', true);\nEND $$;\n\nCOMMENT ON FUNCTION public.financial_consolidate_occurrence(uuid) IS\n 'Transforma UMA ocorr\u00EAncia provisionada na conta real (V1: consolidate_recurring_occurrence). Idempotente pela mesma chave de financial_recurring_run.';\n\nREVOKE ALL ON FUNCTION public.financial_consolidate_occurrence(uuid) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.financial_consolidate_occurrence(uuid) TO authenticated;\nGRANT EXECUTE ON FUNCTION public.financial_consolidate_occurrence(uuid) TO service_role;\n";
|
|
107
|
+
export declare const MIGRATION_106_A_RECORRENCIA_NOVA_TAMBEM_PROVISIONA = "-- A recorr\u00EAncia cadastrada HOJE tamb\u00E9m provisiona.\n--\n-- As 1.169 ocorr\u00EAncias provisionadas que o V2 tem vieram todas da migra\u00E7\u00E3o do\n-- V1. Nada no V2 as CRIA: `financial_recurring_run` pula o primeiro tempo e\n-- gera a fatura direto da r\u00E9gua. O efeito \u00E9 que uma recorr\u00EAncia cadastrada\n-- agora n\u00E3o aparece como previsto em lugar nenhum \u2014 ela some at\u00E9 algu\u00E9m rodar\n-- a rotina, e a\u00ED nasce como d\u00EDvida sem ningu\u00E9m ter conferido.\n--\n-- Esta fun\u00E7\u00E3o \u00E9 o primeiro tempo do V1: escreve os vencimentos FUTUROS como\n-- `provisioned`, sem tocar em fatura nenhuma. Quem os transforma em conta \u00E9\n-- `financial_consolidate_occurrence`, um a um, quando algu\u00E9m manda.\n--\n-- \u2500\u2500 Horizonte \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Doze meses por padr\u00E3o. O suficiente para o ano fechar na tela de quem\n-- planeja, e curto o bastante para que mudar o valor da r\u00E9gua n\u00E3o deixe uma\n-- fila de previs\u00F5es erradas at\u00E9 2030. Quem quiser mais passa `p_through`.\n--\n-- \u2500\u2500 Idempot\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `(recurring_bill_id, seq)` j\u00E1 \u00E9 \u00FAnico, e o `seq` \u00E9 derivado do M\u00CAS \u2014 n\u00E3o de\n-- um contador. Rodar de novo sobre a mesma r\u00E9gua n\u00E3o duplica nem renumera, e\n-- uma ocorr\u00EAncia j\u00E1 consolidada continua onde est\u00E1: o `ON CONFLICT DO NOTHING`\n-- \u00E9 o que garante que reprovisionar nunca desfaz trabalho feito.\n--\n-- Idempotente: substitui a fun\u00E7\u00E3o.\n\nCREATE OR REPLACE FUNCTION public.financial_recurring_provision(\n p_bill_id uuid DEFAULT NULL,\n p_through date DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_through date := coalesce(p_through, (current_date + interval '12 months')::date);\n v_bill record;\n v_month date;\n v_due date;\n v_seq int;\n v_made int := 0;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'financial_recurring_provision: sem tenant na sessao' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_permission('financial.create', NULL) THEN\n RAISE EXCEPTION 'financial_recurring_provision: exige financial.create' USING ERRCODE = '42501';\n END IF;\n\n FOR v_bill IN\n SELECT * FROM public.plg_financial_recurring_bills\n WHERE tenant_id = v_tenant AND is_active\n AND (p_bill_id IS NULL OR id = p_bill_id)\n LOOP\n v_month := date_trunc('month', greatest(v_bill.starts_on, current_date))::date;\n\n WHILE v_month <= v_through LOOP\n v_due := least(\n (v_month + ((v_bill.due_day - 1) || ' days')::interval)::date,\n (v_month + interval '1 month - 1 day')::date);\n\n EXIT WHEN v_bill.ends_on IS NOT NULL AND v_due > v_bill.ends_on;\n\n -- O `seq` sai do M\u00CAS e n\u00E3o de uma contagem: \u00E9 o que faz reprovisionar\n -- cair sempre na mesma linha, em vez de criar uma segunda com outro\n -- n\u00FAmero para o mesmo vencimento.\n v_seq := (extract(year from v_month)::int * 12) + extract(month from v_month)::int;\n\n INSERT INTO public.plg_financial_recurring_bill_occurrences (\n tenant_id, unit_id, recurring_bill_id, seq, competence_date, due_date, amount, status\n ) VALUES (\n v_tenant, v_bill.unit_id, v_bill.id, v_seq, v_month, v_due, v_bill.amount, 'provisioned'\n )\n ON CONFLICT (recurring_bill_id, seq) DO NOTHING;\n\n IF FOUND THEN v_made := v_made + 1; END IF;\n\n v_month := (v_month + CASE v_bill.frequency\n WHEN 'weekly' THEN interval '7 days'\n WHEN 'quarterly' THEN interval '3 months'\n WHEN 'yearly' THEN interval '1 year'\n ELSE interval '1 month'\n END)::date;\n END LOOP;\n END LOOP;\n\n RETURN jsonb_build_object('through', v_through, 'provisioned', v_made);\nEND $$;\n\nCOMMENT ON FUNCTION public.financial_recurring_provision(uuid, date) IS\n 'Escreve os vencimentos futuros de uma r\u00E9gua (ou de todas) como ocorr\u00EAncias provisionadas. \u00C9 o primeiro tempo da recorr\u00EAncia do V1; o segundo \u00E9 financial_consolidate_occurrence.';\n\nREVOKE ALL ON FUNCTION public.financial_recurring_provision(uuid, date) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.financial_recurring_provision(uuid, date) TO authenticated;\nGRANT EXECUTE ON FUNCTION public.financial_recurring_provision(uuid, date) TO service_role;\n";
|
|
108
|
+
export declare const MIGRATION_107_A_PREVISAO_COMECA_ONDE_A_REGUA_COMECA = "-- A previs\u00E3o come\u00E7a onde a R\u00C9GUA come\u00E7a, n\u00E3o onde o rel\u00F3gio est\u00E1.\n--\n-- A 106 provisionava a partir de hoje: `greatest(starts_on, current_date)`.\n-- O efeito \u00E9 que uma recorr\u00EAncia cadastrada com in\u00EDcio em julho \u2014 a conta de\n-- luz que a casa paga desde julho \u2014 nasce sem julho, agosto e setembro. E s\u00E3o\n-- justamente esses que a pessoa procura: os tr\u00EAs vencimentos que j\u00E1 passaram e\n-- ningu\u00E9m lan\u00E7ou. Ela filtra por \u00AB\u00FAltimos 90 dias\u00BB, n\u00E3o v\u00EA nada, e conclui que\n-- a recorr\u00EAncia n\u00E3o funciona.\n--\n-- Previs\u00E3o \u00E9 sobre o CALEND\u00C1RIO da r\u00E9gua, n\u00E3o sobre o instante em que algu\u00E9m\n-- salvou. Uma previs\u00E3o vencida continua sendo uma previs\u00E3o: ela \u00E9 exatamente a\n-- d\u00EDvida que ainda n\u00E3o foi reconhecida, e \u00E9 para isso que a lista serve.\n--\n-- \u2500\u2500 O que N\u00C3O pode virar previs\u00E3o duas vezes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `financial_recurring_run` pode ter criado a fatura daquele m\u00EAs antes desta\n-- porta existir. Provisionar o passado sem olhar para isso poria, lado a lado,\n-- a conta lan\u00E7ada e uma previs\u00E3o dela \u2014 a mesma d\u00EDvida contada duas vezes na\n-- soma do rodap\u00E9. A chave `recurring:<r\u00E9gua>:<AAAA-MM>` \u00E9 a mesma nas duas\n-- fun\u00E7\u00F5es, ent\u00E3o basta perguntar por ela.\n--\n-- Idempotente: substitui a fun\u00E7\u00E3o.\n\nCREATE OR REPLACE FUNCTION public.financial_recurring_provision(\n p_bill_id uuid DEFAULT NULL,\n p_through date DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_through date := coalesce(p_through, (current_date + interval '12 months')::date);\n v_bill record;\n v_month date;\n v_due date;\n v_seq int;\n v_key text;\n v_made int := 0;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'financial_recurring_provision: sem tenant na sessao' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_permission('financial.create', NULL) THEN\n RAISE EXCEPTION 'financial_recurring_provision: exige financial.create' USING ERRCODE = '42501';\n END IF;\n\n FOR v_bill IN\n SELECT * FROM public.plg_financial_recurring_bills\n WHERE tenant_id = v_tenant AND is_active\n AND (p_bill_id IS NULL OR id = p_bill_id)\n LOOP\n -- DESDE O COME\u00C7O DA R\u00C9GUA. O que j\u00E1 venceu \u00E9 o que mais importa ver.\n v_month := date_trunc('month', v_bill.starts_on)::date;\n\n WHILE v_month <= v_through LOOP\n v_due := least(\n (v_month + ((v_bill.due_day - 1) || ' days')::interval)::date,\n (v_month + interval '1 month - 1 day')::date);\n\n EXIT WHEN v_bill.ends_on IS NOT NULL AND v_due > v_bill.ends_on;\n\n v_seq := (extract(year from v_month)::int * 12) + extract(month from v_month)::int;\n v_key := 'recurring:' || v_bill.id::text || ':' || to_char(v_month, 'YYYY-MM');\n\n -- O m\u00EAs que j\u00E1 virou conta n\u00E3o vira previs\u00E3o.\n IF NOT EXISTS (\n SELECT 1 FROM public.plg_financial_invoices f\n WHERE f.tenant_id = v_tenant\n AND f.metadata ->> 'recurring_key' = v_key\n AND f.cancelled_at IS NULL\n ) THEN\n INSERT INTO public.plg_financial_recurring_bill_occurrences (\n tenant_id, unit_id, recurring_bill_id, seq, competence_date, due_date, amount, status\n ) VALUES (\n v_tenant, v_bill.unit_id, v_bill.id, v_seq, v_month, v_due, v_bill.amount, 'provisioned'\n )\n ON CONFLICT (recurring_bill_id, seq) DO NOTHING;\n\n IF FOUND THEN v_made := v_made + 1; END IF;\n END IF;\n\n v_month := (v_month + CASE v_bill.frequency\n WHEN 'weekly' THEN interval '7 days'\n WHEN 'quarterly' THEN interval '3 months'\n WHEN 'yearly' THEN interval '1 year'\n ELSE interval '1 month'\n END)::date;\n END LOOP;\n END LOOP;\n\n RETURN jsonb_build_object('through', v_through, 'provisioned', v_made);\nEND $$;\n\nCOMMENT ON FUNCTION public.financial_recurring_provision(uuid, date) IS\n 'Escreve os vencimentos da r\u00E9gua \u2014 do in\u00EDcio dela at\u00E9 o horizonte \u2014 como ocorr\u00EAncias provisionadas. O m\u00EAs que j\u00E1 virou conta (mesma recurring_key) fica de fora.';\n\n-- \u2500\u2500 O que j\u00E1 estava cadastrado \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- As r\u00E9guas de antes desta porta nasceram sem previs\u00E3o nenhuma. O backfill vai\n-- em SQL puro e n\u00E3o pela fun\u00E7\u00E3o: a fun\u00E7\u00E3o pede `financial.create` do usu\u00E1rio da\n-- sess\u00E3o, e numa migration n\u00E3o h\u00E1 usu\u00E1rio \u2014 forjar a permiss\u00E3o para contornar\n-- a pr\u00F3pria guarda seria pior que n\u00E3o ter guarda.\n--\n-- S\u00F3 MENSAL: \u00E9 o que 100% das r\u00E9guas usam hoje, e escrever o calend\u00E1rio das\n-- outras frequ\u00EAncias em SQL de conjunto custaria mais do que vale \u2014 elas se\n-- provisionam pela fun\u00E7\u00E3o no pr\u00F3ximo salvar.\n\nINSERT INTO public.plg_financial_recurring_bill_occurrences (\n tenant_id, unit_id, recurring_bill_id, seq, competence_date, due_date, amount, status\n)\nSELECT b.tenant_id, b.unit_id, b.id,\n (extract(year from m)::int * 12) + extract(month from m)::int,\n m::date,\n least((m + ((b.due_day - 1) || ' days')::interval)::date,\n (m + interval '1 month - 1 day')::date),\n b.amount,\n 'provisioned'\n FROM public.plg_financial_recurring_bills b\n CROSS JOIN LATERAL generate_series(\n date_trunc('month', b.starts_on),\n date_trunc('month', (current_date + interval '12 months')),\n interval '1 month') AS m\n WHERE b.is_active\n AND b.frequency = 'monthly'\n AND (b.ends_on IS NULL OR m::date <= b.ends_on)\n AND NOT EXISTS (\n SELECT 1 FROM public.plg_financial_invoices f\n WHERE f.tenant_id = b.tenant_id\n AND f.metadata ->> 'recurring_key' = 'recurring:' || b.id::text || ':' || to_char(m, 'YYYY-MM')\n AND f.cancelled_at IS NULL\n )\nON CONFLICT (recurring_bill_id, seq) DO NOTHING;\n";
|
|
109
|
+
export declare const MIGRATION_108_MUDOU_A_REGUA_MUDA_A_PREVISAO = "-- Mudou a r\u00E9gua, muda a previs\u00E3o.\n--\n-- A recorr\u00EAncia do aluguel foi de R$ 2.400 para R$ 800 e a lista continuou\n-- mostrando 2.400 nas previs\u00F5es: `financial_recurring_provision` s\u00F3 INSERE, e\n-- `ON CONFLICT DO NOTHING` deixa intacto o m\u00EAs que j\u00E1 existia. A r\u00E9gua passou a\n-- dizer uma coisa e as previs\u00F5es dela, outra.\n--\n-- Uma previs\u00E3o \u00E9 a r\u00E9gua projetada no calend\u00E1rio. Se a r\u00E9gua muda, o que ainda\n-- n\u00E3o virou conta tem de mudar junto \u2014 do contr\u00E1rio a soma do rodap\u00E9 responde\n-- com um valor que n\u00E3o existe mais em lugar nenhum.\n--\n-- \u2500\u2500 O que N\u00C3O se mexe \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- consolidada J\u00E1 \u00E9 conta. O valor dela \u00E9 hist\u00F3rico, e reescrev\u00EA-lo seria\n-- mudar uma d\u00EDvida reconhecida por um efeito colateral de\n-- editar a r\u00E9gua.\n-- `overridden` Algu\u00E9m editou AQUELE vencimento \u00E0 m\u00E3o \u2014 o V1 deixa, e \u00E9 o\n-- caso do m\u00EAs em que a luz veio diferente. A r\u00E9gua n\u00E3o desfaz\n-- uma decis\u00E3o que uma pessoa tomou sobre um m\u00EAs espec\u00EDfico.\n--\n-- A data acompanha pelo mesmo motivo: mudar o dia de vencimento da r\u00E9gua sem\n-- mover as previs\u00F5es deixaria a lista prometendo o dia 10 para uma r\u00E9gua que\n-- agora diz 20.\n--\n-- Idempotente: substitui a fun\u00E7\u00E3o.\n\nCREATE OR REPLACE FUNCTION public.financial_recurring_provision(\n p_bill_id uuid DEFAULT NULL,\n p_through date DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_through date := coalesce(p_through, (current_date + interval '12 months')::date);\n v_bill record;\n v_month date;\n v_due date;\n v_seq int;\n v_key text;\n v_made int := 0;\n v_sync int := 0;\n v_n int;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'financial_recurring_provision: sem tenant na sessao' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_permission('financial.create', NULL) THEN\n RAISE EXCEPTION 'financial_recurring_provision: exige financial.create' USING ERRCODE = '42501';\n END IF;\n\n FOR v_bill IN\n SELECT * FROM public.plg_financial_recurring_bills\n WHERE tenant_id = v_tenant AND is_active\n AND (p_bill_id IS NULL OR id = p_bill_id)\n LOOP\n -- 1. O que j\u00E1 est\u00E1 previsto passa a dizer o que a r\u00E9gua diz agora.\n UPDATE public.plg_financial_recurring_bill_occurrences o\n SET amount = v_bill.amount,\n unit_id = v_bill.unit_id,\n due_date = least(\n (date_trunc('month', o.competence_date)\n + ((v_bill.due_day - 1) || ' days')::interval)::date,\n (date_trunc('month', o.competence_date)\n + interval '1 month - 1 day')::date),\n updated_at = now()\n WHERE o.recurring_bill_id = v_bill.id\n AND o.tenant_id = v_tenant\n AND o.status = 'provisioned'\n AND NOT coalesce(o.overridden, false)\n AND (o.amount IS DISTINCT FROM v_bill.amount\n OR o.unit_id IS DISTINCT FROM v_bill.unit_id\n OR o.due_date IS DISTINCT FROM least(\n (date_trunc('month', o.competence_date)\n + ((v_bill.due_day - 1) || ' days')::interval)::date,\n (date_trunc('month', o.competence_date)\n + interval '1 month - 1 day')::date));\n GET DIAGNOSTICS v_n = ROW_COUNT;\n v_sync := v_sync + v_n;\n\n -- 2. E os meses que ainda n\u00E3o existem entram.\n v_month := date_trunc('month', v_bill.starts_on)::date;\n\n WHILE v_month <= v_through LOOP\n v_due := least(\n (v_month + ((v_bill.due_day - 1) || ' days')::interval)::date,\n (v_month + interval '1 month - 1 day')::date);\n\n EXIT WHEN v_bill.ends_on IS NOT NULL AND v_due > v_bill.ends_on;\n\n v_seq := (extract(year from v_month)::int * 12) + extract(month from v_month)::int;\n v_key := 'recurring:' || v_bill.id::text || ':' || to_char(v_month, 'YYYY-MM');\n\n IF NOT EXISTS (\n SELECT 1 FROM public.plg_financial_invoices f\n WHERE f.tenant_id = v_tenant\n AND f.metadata ->> 'recurring_key' = v_key\n AND f.cancelled_at IS NULL\n ) THEN\n INSERT INTO public.plg_financial_recurring_bill_occurrences (\n tenant_id, unit_id, recurring_bill_id, seq, competence_date, due_date, amount, status\n ) VALUES (\n v_tenant, v_bill.unit_id, v_bill.id, v_seq, v_month, v_due, v_bill.amount, 'provisioned'\n )\n ON CONFLICT (recurring_bill_id, seq) DO NOTHING;\n\n IF FOUND THEN v_made := v_made + 1; END IF;\n END IF;\n\n v_month := (v_month + CASE v_bill.frequency\n WHEN 'weekly' THEN interval '7 days'\n WHEN 'quarterly' THEN interval '3 months'\n WHEN 'yearly' THEN interval '1 year'\n ELSE interval '1 month'\n END)::date;\n END LOOP;\n END LOOP;\n\n RETURN jsonb_build_object('through', v_through, 'provisioned', v_made, 'synced', v_sync);\nEND $$;\n\nCOMMENT ON FUNCTION public.financial_recurring_provision(uuid, date) IS\n 'Sincroniza as previs\u00F5es com a r\u00E9gua (valor, unidade e vencimento) e escreve os meses que faltam. N\u00E3o toca no que virou conta nem no vencimento editado \u00E0 m\u00E3o (overridden).';\n\n-- As previs\u00F5es que ficaram para tr\u00E1s de uma r\u00E9gua j\u00E1 editada.\nUPDATE public.plg_financial_recurring_bill_occurrences o\n SET amount = b.amount,\n due_date = least(\n (date_trunc('month', o.competence_date) + ((b.due_day - 1) || ' days')::interval)::date,\n (date_trunc('month', o.competence_date) + interval '1 month - 1 day')::date),\n updated_at = now()\n FROM public.plg_financial_recurring_bills b\n WHERE b.id = o.recurring_bill_id\n AND o.status = 'provisioned'\n AND NOT coalesce(o.overridden, false)\n AND o.amount IS DISTINCT FROM b.amount;\n";
|
|
110
|
+
export declare const MIGRATION_109_A_REGUA_GRAVA_CAMELCASE_E_O_SQL_LIA_SNAKE = "-- A r\u00E9gua grava `unitPrice`; o SQL lia `unit_price`.\n--\n-- A conta de luz consolidada nasceu com total R$ 120,00 e a linha dela zerada.\n-- A causa \u00E9 literal: `plg_financial_recurring_bills.items` \u00E9 escrito pela TELA,\n-- em JSON de JavaScript \u2014 `unitPrice`, `itemKind`, `accountId` \u2014, e as duas\n-- fun\u00E7\u00F5es que copiam esses itens leem `unit_price`, `item_kind`, `account_id`.\n-- Nenhuma casa; `->>` devolve NULL; o `coalesce` cai em zero. Sem erro, sem\n-- log: a fatura fica com o total certo (que vem da r\u00E9gua, n\u00E3o das linhas) e com\n-- uma linha que diz zero.\n--\n-- `financial_recurring_run` tem o mesmo defeito desde que foi escrita \u2014 eu o\n-- copiei para `financial_consolidate_occurrence` ao espelh\u00E1-la. As duas s\u00E3o\n-- corrigidas aqui.\n--\n-- \u2500\u2500 Por que ler as DUAS grafias \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Padronizar o JSON numa s\u00F3 seria mais limpo e quebraria toda r\u00E9gua j\u00E1\n-- cadastrada no instante em que rodasse. Ler as duas custa um `coalesce` por\n-- campo e n\u00E3o deixa ningu\u00E9m para tr\u00E1s; quem vier depois escreve na grafia que\n-- quiser e as duas funcionam.\n--\n-- Idempotente: substitui as fun\u00E7\u00F5es e recalcula s\u00F3 o que est\u00E1 zerado.\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 1. As linhas de uma r\u00E9gua, lidas nas duas grafias\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nCREATE OR REPLACE FUNCTION public.plg_financial_line_field(p_line jsonb, p_snake text, p_camel text)\nRETURNS text\nLANGUAGE sql\nIMMUTABLE\nSET search_path TO ''\nAS $$\n SELECT coalesce(nullif(p_line ->> p_snake, ''), nullif(p_line ->> p_camel, ''));\n$$;\n\nCOMMENT ON FUNCTION public.plg_financial_line_field(jsonb, text, text) IS\n 'Um campo da linha de uma r\u00E9gua de recorr\u00EAncia, na grafia do SQL ou na do JavaScript. A tela grava camelCase; as fun\u00E7\u00F5es liam snake_case.';\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 2. Copiar as linhas passa a ser UMA fun\u00E7\u00E3o, usada pelas duas RPCs\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- Estava duplicada \u2014 e foi por isso que o defeito viajou de uma para a outra.\n\nCREATE OR REPLACE FUNCTION public.plg_financial_copy_bill_items(\n p_tenant uuid, p_invoice uuid, p_items jsonb\n)\nRETURNS integer\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_line jsonb;\n v_item uuid;\n v_i int := 0;\n v_qty numeric;\n v_unit numeric;\n v_disc numeric;\n v_surc numeric;\n v_acc text;\n v_cc text;\nBEGIN\n FOR v_line IN SELECT * FROM jsonb_array_elements(coalesce(p_items, '[]'::jsonb))\n LOOP\n v_qty := coalesce(public.plg_financial_line_field(v_line, 'quantity', 'quantity')::numeric, 1);\n v_unit := coalesce(public.plg_financial_line_field(v_line, 'unit_price', 'unitPrice')::numeric, 0);\n v_disc := coalesce(public.plg_financial_line_field(v_line, 'discount', 'discount')::numeric, 0);\n v_surc := coalesce(public.plg_financial_line_field(v_line, 'surcharge', 'surcharge')::numeric, 0);\n v_acc := public.plg_financial_line_field(v_line, 'account_id', 'accountId');\n v_cc := public.plg_financial_line_field(v_line, 'cost_center_id', 'costCenterId');\n\n INSERT INTO public.items (\n tenant_id, invoice_id, name, quantity, unit_price, discount, total, sort_order, metadata\n ) VALUES (\n p_tenant, p_invoice,\n public.plg_financial_line_field(v_line, 'description', 'description'),\n v_qty, v_unit, v_disc,\n round(v_qty * v_unit - v_disc + v_surc, 2),\n v_i,\n jsonb_build_object(\n 'itemKind', coalesce(public.plg_financial_line_field(v_line, 'item_kind', 'itemKind'), 'other'),\n 'referenceId', public.plg_financial_line_field(v_line, 'reference_id', 'referenceId'))\n )\n RETURNING id INTO v_item;\n\n IF v_acc IS NOT NULL OR v_cc IS NOT NULL THEN\n INSERT INTO public.plg_financial_invoice_items (\n item_id, parent_kind, tenant_id, chart_account_id, cost_center_id\n ) VALUES (v_item, 'invoice', p_tenant, v_acc::uuid, v_cc::uuid)\n ON CONFLICT (item_id) DO UPDATE\n SET chart_account_id = excluded.chart_account_id,\n cost_center_id = excluded.cost_center_id;\n END IF;\n\n v_i := v_i + 1;\n END LOOP;\n\n RETURN v_i;\nEND $$;\n\nCOMMENT ON FUNCTION public.plg_financial_copy_bill_items(uuid, uuid, jsonb) IS\n 'Copia as linhas de uma r\u00E9gua de recorr\u00EAncia para a fatura gerada. Uma s\u00F3 c\u00F3pia, usada pelo consolidar e pela rotina \u2014 duplic\u00E1-la foi como o defeito de grafia viajou entre as duas.';\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 3. As linhas zeradas que j\u00E1 foram criadas\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n--\n-- S\u00F3 as de fatura gerada por recorr\u00EAncia, s\u00F3 as que est\u00E3o em zero, e s\u00F3\n-- enquanto nada foi recebido \u2014 o gatilho de imutabilidade continua valendo, e\n-- aqui ele n\u00E3o precisa sair do caminho: uma fatura de recorr\u00EAncia rec\u00E9m-gerada\n-- n\u00E3o tem pagamento.\n\nUPDATE public.items i\n SET unit_price = l.unit_price,\n quantity = l.quantity,\n total = round(l.quantity * l.unit_price, 2),\n name = coalesce(nullif(i.name, ''), l.nome),\n metadata = jsonb_set(coalesce(i.metadata, '{}'::jsonb), '{itemKind}', to_jsonb(l.kind), true),\n updated_at = now()\n FROM (\n SELECT f.id AS invoice_id,\n coalesce(public.plg_financial_line_field(li.value, 'quantity', 'quantity')::numeric, 1) AS quantity,\n coalesce(public.plg_financial_line_field(li.value, 'unit_price', 'unitPrice')::numeric, 0) AS unit_price,\n public.plg_financial_line_field(li.value, 'description', 'description') AS nome,\n coalesce(public.plg_financial_line_field(li.value, 'item_kind', 'itemKind'), 'other') AS kind,\n li.ordinality - 1 AS pos\n FROM public.plg_financial_invoices f\n JOIN public.plg_financial_recurring_bills b\n ON b.id = (f.metadata ->> 'recurring_bill_id')::uuid\n CROSS JOIN LATERAL jsonb_array_elements(coalesce(b.items, '[]'::jsonb)) WITH ORDINALITY AS li(value, ordinality)\n WHERE f.metadata ? 'recurring_bill_id'\n AND f.cancelled_at IS NULL\n ) AS l\n WHERE l.invoice_id = i.invoice_id\n AND l.pos = i.sort_order\n AND coalesce(i.unit_price, 0) = 0\n AND l.unit_price > 0\n AND NOT EXISTS (\n SELECT 1 FROM public.v_financial_installment_balances v\n WHERE v.invoice_id = i.invoice_id AND coalesce(v.paid, 0) > 0\n );\n\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n-- 4. As duas RPCs passam a copiar pela fun\u00E7\u00E3o \u00FAnica\n-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nCREATE OR REPLACE FUNCTION public.financial_consolidate_occurrence(p_occurrence_id uuid)\nRETURNS jsonb\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_occ record;\n v_bill record;\n v_key text;\n v_res jsonb;\n v_invoice uuid;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: sem tenant na sessao' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_permission('financial.create', NULL) THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: exige financial.create' USING ERRCODE = '42501';\n END IF;\n\n SELECT * INTO v_occ FROM public.plg_financial_recurring_bill_occurrences\n WHERE id = p_occurrence_id AND tenant_id = v_tenant;\n IF v_occ IS NULL THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: ocorrencia % nao encontrada', p_occurrence_id\n USING ERRCODE = 'P0002';\n END IF;\n IF v_occ.status = 'consolidated' THEN\n RETURN jsonb_build_object('occurrence_id', v_occ.id, 'invoice_id', v_occ.invoice_id, 'created', false);\n END IF;\n IF v_occ.status = 'cancelled' THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: ocorrencia cancelada' USING ERRCODE = '23514';\n END IF;\n\n SELECT * INTO v_bill FROM public.plg_financial_recurring_bills\n WHERE id = v_occ.recurring_bill_id AND tenant_id = v_tenant;\n IF v_bill IS NULL THEN\n RAISE EXCEPTION 'financial_consolidate_occurrence: regua da ocorrencia % sumiu', p_occurrence_id\n USING ERRCODE = 'P0002';\n END IF;\n\n v_key := 'recurring:' || v_bill.id::text || ':'\n || to_char(coalesce(v_occ.competence_date, v_occ.due_date), 'YYYY-MM');\n\n SELECT f.id INTO v_invoice FROM public.plg_financial_invoices f\n WHERE f.tenant_id = v_tenant AND f.metadata ->> 'recurring_key' = v_key\n AND f.cancelled_at IS NULL LIMIT 1;\n\n IF v_invoice IS NULL THEN\n v_res := public.financial_create_invoice(\n v_bill.direction, v_occ.amount, v_bill.counterparty_person_id, NULL,\n coalesce(v_occ.unit_id, v_bill.unit_id),\n jsonb_build_array(jsonb_build_object('number', 1, 'due_date', v_occ.due_date, 'amount', v_occ.amount)),\n coalesce(v_occ.competence_date, v_occ.due_date),\n v_bill.description, coalesce(v_occ.notes, v_bill.notes),\n jsonb_build_object('source', 'financial_consolidate_occurrence',\n 'recurring_bill_id', v_bill.id,\n 'recurring_occurrence_id', v_occ.id,\n 'recurring_key', v_key),\n v_key);\n v_invoice := (v_res ->> 'invoice_id')::uuid;\n PERFORM public.plg_financial_copy_bill_items(v_tenant, v_invoice, v_bill.items);\n END IF;\n\n UPDATE public.plg_financial_recurring_bill_occurrences\n SET status = 'consolidated', invoice_id = v_invoice, updated_at = now()\n WHERE id = v_occ.id;\n\n RETURN jsonb_build_object('occurrence_id', v_occ.id, 'invoice_id', v_invoice, 'created', true);\nEND $$;\n\nCREATE OR REPLACE FUNCTION public.financial_recurring_run(p_through date DEFAULT NULL::date)\nRETURNS jsonb\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_through date := coalesce(p_through, (date_trunc('month', current_date) + interval '1 month - 1 day')::date);\n v_bill record; v_month date; v_due date; v_key text;\n v_made int := 0; v_total numeric := 0; v_res jsonb; v_invoice uuid;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'financial_recurring_run: sem tenant na sessao' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_permission('financial.create', NULL) THEN\n RAISE EXCEPTION 'financial_recurring_run: exige financial.create' USING ERRCODE = '42501';\n END IF;\n\n FOR v_bill IN\n SELECT * FROM public.plg_financial_recurring_bills\n WHERE tenant_id = v_tenant AND is_active AND starts_on <= v_through\n AND (ends_on IS NULL OR ends_on >= starts_on)\n LOOP\n v_month := date_trunc('month', greatest(v_bill.starts_on, date_trunc('month', v_through)::date))::date;\n WHILE v_month <= v_through LOOP\n v_due := least((v_month + ((v_bill.due_day - 1) || ' days')::interval)::date,\n (v_month + interval '1 month - 1 day')::date);\n EXIT WHEN v_bill.ends_on IS NOT NULL AND v_due > v_bill.ends_on;\n v_key := 'recurring:' || v_bill.id::text || ':' || to_char(v_month, 'YYYY-MM');\n IF NOT EXISTS (SELECT 1 FROM public.plg_financial_invoices f\n WHERE f.tenant_id = v_tenant\n AND f.metadata ->> 'recurring_key' = v_key\n AND f.cancelled_at IS NULL) THEN\n v_res := public.financial_create_invoice(\n v_bill.direction, v_bill.amount, v_bill.counterparty_person_id, NULL, v_bill.unit_id,\n jsonb_build_array(jsonb_build_object('number', 1, 'due_date', v_due, 'amount', v_bill.amount)),\n v_month, v_bill.description, v_bill.notes,\n jsonb_build_object('source', 'financial_recurring_run',\n 'recurring_bill_id', v_bill.id, 'recurring_key', v_key),\n v_key);\n v_invoice := (v_res ->> 'invoice_id')::uuid;\n PERFORM public.plg_financial_copy_bill_items(v_tenant, v_invoice, v_bill.items);\n v_made := v_made + 1;\n v_total := v_total + v_bill.amount;\n END IF;\n v_month := (v_month + CASE v_bill.frequency\n WHEN 'weekly' THEN interval '7 days'\n WHEN 'quarterly' THEN interval '3 months'\n WHEN 'yearly' THEN interval '1 year'\n ELSE interval '1 month' END)::date;\n END LOOP;\n END LOOP;\n\n RETURN jsonb_build_object('through', v_through, 'created', v_made, 'total', round(v_total, 2));\nEND $$;\n";
|
|
111
|
+
export declare const MIGRATION_110_TROCAR_DE_FORNECEDOR_ENQUANTO_NINGUEM_PAGOU = "-- Trocar de fornecedor enquanto ningu\u00E9m pagou.\n--\n-- A trava \u00E9 do BANCO: `trg_plg_financial_invoices_immutable` recusa qualquer\n-- mudan\u00E7a em `counterparty_person_id` a partir do instante em que a conta \u00E9\n-- emitida \u2014 mesmo que ela n\u00E3o tenha recebido um centavo.\n--\n-- Isso j\u00E1 foi revisto duas vezes para os vizinhos dela. A 098 abriu a\n-- compet\u00EAncia e a 099 abriu o total, as duas com o MESMO limite: vale enquanto\n-- `sum(paid) = 0`. A contraparte ficou de fora \u2014 n\u00E3o por decis\u00E3o, ao que tudo\n-- indica, mas porque ningu\u00E9m tinha esbarrado nela.\n--\n-- E ela \u00E9 o campo mais errado de todos na hora de lan\u00E7ar: quem digita seis\n-- contas seguidas escolhe o fornecedor da linha de cima na quarta. Hoje a\n-- corre\u00E7\u00E3o \u00E9 cancelar a conta e lan\u00E7ar outra \u2014 um documento cancelado no\n-- hist\u00F3rico para consertar um clique.\n--\n-- \u2500\u2500 O que continua imut\u00E1vel, e por qu\u00EA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `number` \u00C9 a identidade do documento. Troc\u00E1-lo \u00E9 fazer o hist\u00F3rico\n-- falar de outra conta.\n-- `direction` Pagar e receber s\u00E3o lados opostos do raz\u00E3o; virar o sinal\n-- n\u00E3o \u00E9 corrigir, \u00E9 inverter o fato.\n-- `issue_date` A emiss\u00E3o \u00E9 quando o documento passou a existir.\n-- `currency` O valor gravado s\u00F3 significa alguma coisa com ela.\n-- `order_id` O v\u00EDnculo com a venda que a originou.\n--\n-- A contraparte entra no grupo que a 098 e a 099 j\u00E1 definiram: enquanto nada\n-- foi recebido, \u00E9 rascunho; depois do primeiro centavo, \u00E9 hist\u00F3ria.\n--\n-- Idempotente: substitui a fun\u00E7\u00E3o do gatilho.\n\nCREATE OR REPLACE FUNCTION public.trg_plg_financial_invoices_immutable()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $function$\nDECLARE v_paid numeric;\nBEGIN\n IF OLD.issue_date IS NULL OR OLD.cancelled_at IS NOT NULL THEN RETURN NEW; END IF;\n IF NEW.cancelled_at IS NOT NULL AND OLD.cancelled_at IS NULL THEN RETURN NEW; END IF;\n\n -- O que \u00E9 identidade do documento n\u00E3o muda nunca.\n IF NEW.direction IS DISTINCT FROM OLD.direction\n OR NEW.order_id IS DISTINCT FROM OLD.order_id\n OR NEW.number IS DISTINCT FROM OLD.number\n OR NEW.issue_date IS DISTINCT FROM OLD.issue_date\n OR NEW.currency IS DISTINCT FROM OLD.currency THEN\n RAISE EXCEPTION 'plg_financial_invoices: invoice % is issued and cannot be altered', OLD.number\n USING ERRCODE = '23514',\n HINT = 'A closed invoice is immutable (PRD 04). Correct it with a new invoice, or cancel this one through financial_cancel_invoice.';\n END IF;\n\n -- Compet\u00EAncia (098), total (099) e CONTRAPARTE (110) t\u00EAm a mesma licen\u00E7a e o\n -- mesmo limite. A pergunta s\u00F3 se faz quando um dos tr\u00EAs mudou: ela varre as\n -- parcelas, e n\u00E3o vale pag\u00E1-la em toda grava\u00E7\u00E3o de observa\u00E7\u00E3o.\n IF NEW.competence_date IS DISTINCT FROM OLD.competence_date\n OR NEW.total IS DISTINCT FROM OLD.total\n OR NEW.counterparty_person_id IS DISTINCT FROM OLD.counterparty_person_id THEN\n SELECT coalesce(sum(b.paid), 0) INTO v_paid\n FROM public.v_financial_installment_balances b WHERE b.invoice_id = OLD.id;\n IF v_paid > 0 THEN\n RAISE EXCEPTION 'plg_financial_invoices: invoice % has % already received and cannot be rewritten', OLD.number, v_paid\n USING ERRCODE = '23514',\n HINT = 'Reverse the payments first, or correct it with a new invoice.';\n END IF;\n END IF;\n\n RETURN NEW;\nEND $function$;\n\nCOMMENT ON FUNCTION public.trg_plg_financial_invoices_immutable() IS\n 'Uma conta emitida \u00E9 imut\u00E1vel na IDENTIDADE (n\u00FAmero, dire\u00E7\u00E3o, emiss\u00E3o, moeda, pedido) sempre; e na compet\u00EAncia, no total e na contraparte s\u00F3 depois do primeiro centavo recebido.';\n";
|
|
112
|
+
export declare const MIGRATION_111_REPARCELAR_SEM_APAGAR_A_PARCELA = "-- Reparcelar sem APAGAR a parcela.\n--\n-- Trocar o valor de uma conta cujo pagamento foi estornado falhava com:\n--\n-- plg_financial_movements is append-only: amount, kind, accounts and LINKS\n-- of movement \u2026 cannot change \u2014 post a reversal instead\n--\n-- S\u00E3o duas regras certas se atropelando:\n--\n-- `financial_reschedule_invoice` APAGA as parcelas e as reescreve \u2014 \u00E9 como\n-- ele transforma 2x em 3x sem passar por um estado que `(invoice_id, number)`\n-- recusa.\n--\n-- `plg_financial_movements.financial_invoice_installment_id` \u00E9 FK com ON\n-- DELETE SET NULL, e o movimento \u00E9 append-only justamente nos LINKS.\n--\n-- O pagamento estornado continua existindo \u2014 \u00E9 isso que um estorno significa:\n-- o par fica no livro, l\u00EDquido em zero. Ele aponta para a parcela. Apagar a\n-- parcela manda o banco anular esse ponteiro, e o gatilho recusa \u2014 com raz\u00E3o,\n-- porque um lan\u00E7amento que muda de dono depois de escrito deixa de ser\n-- hist\u00F3rico.\n--\n-- \u2500\u2500 A corre\u00E7\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- A parcela de n\u00FAmero N continua sendo a parcela de n\u00FAmero N: ela \u00E9 ATUALIZADA\n-- no lugar, e o id n\u00E3o muda. S\u00F3 some a que sobrou quando o cronograma encolheu\n-- \u2014 e essa, se tiver movimento pendurado, o banco recusa, que \u00E9 o certo.\n--\n-- De quebra, o que estava anexado \u00E0 parcela deixa de precisar do resgate por\n-- n\u00FAmero: n\u00E3o h\u00E1 mais o que resgatar, porque a linha nunca deixou de existir.\n--\n-- Idempotente: substitui a fun\u00E7\u00E3o.\n\nCREATE OR REPLACE FUNCTION public.financial_reschedule_invoice(\n p_invoice_id uuid,\n p_installments jsonb,\n p_idempotency_key text DEFAULT NULL::text\n)\nRETURNS jsonb\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $function$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_invoice public.plg_financial_invoices%ROWTYPE;\n v_paid numeric;\n v_mirror int;\n v_sum numeric := 0;\n v_i jsonb;\n v_n int := 0;\n v_out jsonb := '[]'::jsonb;\n v_inst_id uuid;\n v_due date;\n v_amount numeric;\n v_meta jsonb;\n v_total int;\nBEGIN\n SELECT * INTO v_invoice FROM public.plg_financial_invoices WHERE id = p_invoice_id AND tenant_id = v_tenant;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: invoice % not found', p_invoice_id USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission('financial.edit', v_invoice.unit_id);\n PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);\n\n IF v_invoice.cancelled_at IS NOT NULL THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: invoice % is cancelled', v_invoice.number USING ERRCODE = '55000';\n END IF;\n\n SELECT coalesce(sum(b.paid), 0) INTO v_paid\n FROM public.v_financial_installment_balances b WHERE b.invoice_id = p_invoice_id;\n IF v_paid > 0 THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: invoice % has % already received; its schedule is history now', v_invoice.number, v_paid\n USING ERRCODE = '55000',\n HINT = 'Move the due date of an unpaid installment instead, or cancel and reissue.';\n END IF;\n\n -- O espelho legado (`bill`) \u00E9 append-only: nem apaga nem muda de valor.\n SELECT count(*) INTO v_mirror FROM public.plg_financial_movements m\n WHERE m.financial_invoice_id = p_invoice_id AND m.movement_kind = 'bill' AND m.status <> 'cancelled';\n IF v_mirror > 0 THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: invoice % carries legacy bill movements and cannot be rescheduled', v_invoice.number\n USING ERRCODE = '55000',\n HINT = 'Cancel and reissue this invoice.';\n END IF;\n\n IF jsonb_typeof(p_installments) <> 'array' OR jsonb_array_length(p_installments) = 0 THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: installments must be a non-empty array' USING ERRCODE = '22023';\n END IF;\n FOR v_i IN SELECT * FROM jsonb_array_elements(p_installments) LOOP\n v_sum := v_sum + round(coalesce((v_i ->> 'amount')::numeric, 0), 2);\n END LOOP;\n IF v_sum <> round(v_invoice.total, 2) THEN\n RAISE EXCEPTION 'financial_reschedule_invoice: installments sum to % but the invoice total is %', v_sum, v_invoice.total\n USING ERRCODE = '23514';\n END IF;\n\n v_total := jsonb_array_length(p_installments);\n\n -- 1. A sobra sai PRIMEIRO. Se ela tiver movimento, o banco recusa aqui \u2014 e \u00E9\n -- a recusa certa: encolher o cronograma apagaria a linha que um\n -- lan\u00E7amento aponta.\n DELETE FROM public.plg_financial_invoice_installments\n WHERE invoice_id = p_invoice_id AND number > v_total;\n\n -- 2. E a parcela de n\u00FAmero N continua sendo a de n\u00FAmero N.\n FOR v_i IN SELECT * FROM jsonb_array_elements(p_installments) LOOP\n v_n := v_n + 1;\n v_due := coalesce((v_i ->> 'due_date')::date, v_invoice.competence_date, current_date);\n v_amount := round(coalesce((v_i ->> 'amount')::numeric, 0), 2);\n v_meta := CASE WHEN jsonb_typeof(v_i -> 'metadata') = 'object' THEN v_i -> 'metadata' ELSE NULL END;\n\n UPDATE public.plg_financial_invoice_installments\n SET due_date = v_due,\n amount = v_amount,\n metadata = coalesce(v_meta, metadata),\n updated_at = now()\n WHERE invoice_id = p_invoice_id AND number = v_n\n RETURNING id INTO v_inst_id;\n\n IF v_inst_id IS NULL THEN\n INSERT INTO public.plg_financial_invoice_installments\n (tenant_id, unit_id, invoice_id, number, due_date, amount, metadata)\n VALUES (v_tenant, v_invoice.unit_id, p_invoice_id, v_n, v_due, v_amount, coalesce(v_meta, '{}'::jsonb))\n RETURNING id INTO v_inst_id;\n END IF;\n\n v_out := v_out || jsonb_build_object('id', v_inst_id, 'number', v_n, 'due_date', v_due, 'amount', v_amount);\n END LOOP;\n\n PERFORM public.plg_financial_audit(v_tenant, 'financial.invoice.reschedule', 'plg_financial_invoices', p_invoice_id::text,\n jsonb_build_object('number', v_invoice.number, 'installments', v_n, 'idempotency_key', p_idempotency_key));\n\n RETURN jsonb_build_object('invoice_id', p_invoice_id, 'installments', v_out);\nEND $function$;\n\nCOMMENT ON FUNCTION public.financial_reschedule_invoice(uuid, jsonb, text) IS\n 'Reescreve o cronograma ATUALIZANDO a parcela de cada n\u00FAmero, sem apag\u00E1-la: um pagamento estornado continua apontando para ela, e o movimento \u00E9 append-only nos links.';\n";
|
|
94
113
|
export declare const MIGRATIONS: Array<{
|
|
95
114
|
id: string;
|
|
96
115
|
sql: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,gu5QAm/HlC,CAAA;AAED,eAAO,MAAM,6DAA6D,4/NA2IzE,CAAA;AAED,eAAO,MAAM,0DAA0D,s8MA6GtE,CAAA;AAED,eAAO,MAAM,8CAA8C,2kSAiJ1D,CAAA;AAED,eAAO,MAAM,wCAAwC,6mRAmLpD,CAAA;AAED,eAAO,MAAM,qDAAqD,0tIAiFjE,CAAA;AAED,eAAO,MAAM,gEAAgE,0xjBAiU5E,CAAA;AAED,eAAO,MAAM,8DAA8D,mmgBA2Q1E,CAAA;AAED,eAAO,MAAM,+DAA+D,shwBAqX3E,CAAA;AAED,eAAO,MAAM,uDAAuD,ik2BAydnE,CAAA;AAED,eAAO,MAAM,+DAA+D,8iXAiR3E,CAAA;AAED,eAAO,MAAM,0DAA0D,qvRA6JtE,CAAA;AAED,eAAO,MAAM,wDAAwD,q3QAuKpE,CAAA;AAED,eAAO,MAAM,gEAAgE,0juBA2Y5E,CAAA;AAED,eAAO,MAAM,kDAAkD,60WAyN9D,CAAA;AAED,eAAO,MAAM,yCAAyC,u6OAgJrD,CAAA;AAED,eAAO,MAAM,4CAA4C,87PAwJxD,CAAA;AAED,eAAO,MAAM,iDAAiD,+3LAwH7D,CAAA;AAED,eAAO,MAAM,iDAAiD,o3GAmE7D,CAAA;AAED,eAAO,MAAM,yCAAyC,k0JAqGrD,CAAA;AAED,eAAO,MAAM,yDAAyD,s1CAsBrE,CAAA;AAED,eAAO,MAAM,yCAAyC,ymOAoIrD,CAAA;AAED,eAAO,MAAM,iDAAiD,g+MA0H7D,CAAA;AAED,eAAO,MAAM,6CAA6C,m1MAgIzD,CAAA;AAED,eAAO,MAAM,2CAA2C,+oMA+HvD,CAAA;AAED,eAAO,MAAM,4CAA4C,8rVA+LxD,CAAA;AAED,eAAO,MAAM,mDAAmD,qpMA4H/D,CAAA;AAED,eAAO,MAAM,0DAA0D,mshBAuStE,CAAA;AAED,eAAO,MAAM,8BAA8B,k4CAqB1C,CAAA;AAED,eAAO,MAAM,0CAA0C,smLA+GtD,CAAA;AAED,eAAO,MAAM,0CAA0C,sqGAiDtD,CAAA;AAED,eAAO,MAAM,4DAA4D,ipHA+ExE,CAAA;AAED,eAAO,MAAM,8CAA8C,+65BAke1D,CAAA;AAED,eAAO,MAAM,+CAA+C,q2XAmN3D,CAAA;AAED,eAAO,MAAM,uDAAuD,67bAsNnE,CAAA;AAED,eAAO,MAAM,oDAAoD,y+mBA8RhE,CAAA;AAED,eAAO,MAAM,sDAAsD,suXAgLlE,CAAA;AAED,eAAO,MAAM,yCAAyC,4jXAuLrD,CAAA;AAED,eAAO,MAAM,6DAA6D,koMA0HzE,CAAA;AAED,eAAO,MAAM,6DAA6D,opMAyFzE,CAAA;AAED,eAAO,MAAM,uCAAuC,s1NA8GnD,CAAA;AAED,eAAO,MAAM,kDAAkD,kzJAwF9D,CAAA;AAED,eAAO,MAAM,mDAAmD,41bA0M/D,CAAA;AAED,eAAO,MAAM,mDAAmD,2mYAgN/D,CAAA;AAED,eAAO,MAAM,6CAA6C,goWAuLzD,CAAA;AAED,eAAO,MAAM,sDAAsD,y+MAsHlE,CAAA;AAED,eAAO,MAAM,oDAAoD,oxPAgJhE,CAAA;AAED,eAAO,MAAM,+DAA+D,++VA8K3E,CAAA;AAED,eAAO,MAAM,8DAA8D,69oBAyU1E,CAAA;AAED,eAAO,MAAM,8CAA8C,8vaAgO1D,CAAA;AAED,eAAO,MAAM,kDAAkD,w/SA+L9D,CAAA;AAED,eAAO,MAAM,8DAA8D,g6SAqL1E,CAAA;AAED,eAAO,MAAM,+CAA+C,snRAwK3D,CAAA;AAED,eAAO,MAAM,oDAAoD,ktRA0KhE,CAAA;AAED,eAAO,MAAM,4CAA4C,y+RA8KxD,CAAA;AAED,eAAO,MAAM,8CAA8C,w8KAiH1D,CAAA;AAED,eAAO,MAAM,qCAAqC,6iOA0IjD,CAAA;AAED,eAAO,MAAM,wCAAwC,+rIAgGpD,CAAA;AAED,eAAO,MAAM,kCAAkC,27IAkG9C,CAAA;AAED,eAAO,MAAM,iCAAiC,o9HA2F7C,CAAA;AAED,eAAO,MAAM,mDAAmD,0iEAsD/D,CAAA;AAED,eAAO,MAAM,8CAA8C,u48BA4e1D,CAAA;AAED,eAAO,MAAM,+CAA+C,+oaAiO3D,CAAA;AAED,eAAO,MAAM,uDAAuD,m/dAgOnE,CAAA;AAED,eAAO,MAAM,oDAAoD,inpBA0ShE,CAAA;AAED,eAAO,MAAM,sDAAsD,gpZA0LlE,CAAA;AAED,eAAO,MAAM,yCAAyC,s+YAiMrD,CAAA;AAED,eAAO,MAAM,yCAAyC,6gaA+LrD,CAAA;AAED,eAAO,MAAM,yDAAyD,ijbA0MrE,CAAA;AAED,eAAO,MAAM,iDAAiD,osUAwK7D,CAAA;AAED,eAAO,MAAM,gDAAgD,6jWAmM5D,CAAA;AAED,eAAO,MAAM,6CAA6C,kviBA0RzD,CAAA;AAED,eAAO,MAAM,+CAA+C,2pjBAuQ3D,CAAA;AAED,eAAO,MAAM,uDAAuD,8iPA8InE,CAAA;AAED,eAAO,MAAM,sDAAsD,y9GA2DlE,CAAA;AAED,eAAO,MAAM,qDAAqD,g9lBA2TjE,CAAA;AAED,eAAO,MAAM,4DAA4D,skHA2DxE,CAAA;AAED,eAAO,MAAM,kDAAkD,uiLA0G9D,CAAA;AAED,eAAO,MAAM,4DAA4D,i9LAuHxE,CAAA;AAED,eAAO,MAAM,yDAAyD,+9BAiBrE,CAAA;AAED,eAAO,MAAM,2DAA2D,47kBAsSvE,CAAA;AAED,eAAO,MAAM,+CAA+C,mxQAgK3D,CAAA;AAED,eAAO,MAAM,oDAAoD,m4IAkFhE,CAAA;AAED,eAAO,MAAM,uEAAuE,yzGA0DnF,CAAA;AAED,eAAO,MAAM,qDAAqD,+mgBAsPjE,CAAA;AAED,eAAO,MAAM,uEAAuE,k/HAqEnF,CAAA;AAED,eAAO,MAAM,iEAAiE,+5VA2M7E,CAAA;AAED,eAAO,MAAM,4DAA4D,40KAgGxE,CAAA;AAED,eAAO,MAAM,4DAA4D,knPAqHxE,CAAA;AAED,eAAO,MAAM,sDAAsD,m4EAsDlE,CAAA;AAED,eAAO,MAAM,0CAA0C,2/JAyHtD,CAAA;AAED,eAAO,MAAM,2CAA2C,k4HAmFvD,CAAA;AAED,eAAO,MAAM,kDAAkD,onTAiK9D,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|