@fayz-ai/plugin-fiscal-br 0.3.2 → 0.3.5

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.
@@ -0,0 +1,154 @@
1
+ export interface NfeSupplier {
2
+ cnpj: string;
3
+ name: string;
4
+ tradeName?: string;
5
+ ie?: string;
6
+ city?: string;
7
+ state?: string;
8
+ }
9
+ export interface NfeItem {
10
+ itemNumber: number;
11
+ supplierProductCode: string;
12
+ eanBarcode?: string;
13
+ description: string;
14
+ ncm?: string;
15
+ cest?: string;
16
+ cfop?: string;
17
+ unit?: string;
18
+ quantity: number;
19
+ unitPrice: number;
20
+ total: number;
21
+ discount?: number;
22
+ freight?: number;
23
+ other?: number;
24
+ icmsCst?: string;
25
+ icmsCsosn?: string;
26
+ icmsOrigin?: string;
27
+ icmsBase?: number;
28
+ icmsRate?: number;
29
+ icmsValue?: number;
30
+ pisValue?: number;
31
+ cofinsValue?: number;
32
+ ipiValue?: number;
33
+ }
34
+ export interface NfeDuplicate {
35
+ index: number;
36
+ number?: string;
37
+ dueDate?: string;
38
+ value: number;
39
+ }
40
+ export interface NfePayment {
41
+ index: number;
42
+ indicator?: string;
43
+ code?: string;
44
+ label?: string;
45
+ value: number;
46
+ }
47
+ export interface NfeTotals {
48
+ total: number;
49
+ products: number;
50
+ discount: number;
51
+ freight: number;
52
+ insurance: number;
53
+ other: number;
54
+ icmsBase?: number;
55
+ icmsValue?: number;
56
+ pisValue?: number;
57
+ cofinsValue?: number;
58
+ ipiValue?: number;
59
+ }
60
+ export interface ParsedNfe {
61
+ accessKey: string;
62
+ nfeNumber: string;
63
+ nfeSeries: string;
64
+ nfeModel: string;
65
+ issueDate: string;
66
+ natureOperation?: string;
67
+ supplier: NfeSupplier;
68
+ totals: NfeTotals;
69
+ items: NfeItem[];
70
+ duplicates: NfeDuplicate[];
71
+ payments: NfePayment[];
72
+ additionalInfo?: string;
73
+ protocol?: string;
74
+ authorizedAt?: string;
75
+ }
76
+ export declare class NfeParseError extends Error {
77
+ constructor(message: string);
78
+ }
79
+ /** The V1's payment vocabulary for an NF-e `tPag` code. */
80
+ export declare function paymentTypeFromCode(code: string | undefined): string;
81
+ export declare function paymentLabel(type: string): string;
82
+ export declare const onlyDigits: (value: string | undefined) => string;
83
+ /**
84
+ * Read a supplier NF-e. Accepts both the standalone `<NFe>` and the
85
+ * `<nfeProc>` envelope the SEFAZ returns (which is what PlugNotas delivers and
86
+ * what the V1 has stored for all 80 of Artorius' notes).
87
+ */
88
+ export declare function parseNfeXml(xml: string): ParsedNfe;
89
+ export declare function toStagePayload(note: ParsedNfe, options?: {
90
+ unitId?: string | null;
91
+ source?: string;
92
+ }): {
93
+ access_key: string;
94
+ nfe_number: string;
95
+ nfe_series: string;
96
+ nfe_model: string;
97
+ issue_date: string;
98
+ nature_operation: string | null;
99
+ additional_info: string | null;
100
+ unit_id: string | null;
101
+ source: string;
102
+ supplier: {
103
+ cnpj: string;
104
+ name: string;
105
+ ie: string | null;
106
+ };
107
+ totals: {
108
+ total: number;
109
+ products: number;
110
+ discount: number;
111
+ freight: number;
112
+ insurance: number;
113
+ other: number;
114
+ };
115
+ items: {
116
+ item_number: number;
117
+ supplier_product_code: string;
118
+ ean_barcode: string | null;
119
+ description: string;
120
+ ncm: string | null;
121
+ cest: string | null;
122
+ cfop: string | null;
123
+ unit: string | null;
124
+ quantity: number;
125
+ unit_price: number;
126
+ total: number;
127
+ discount: number | null;
128
+ freight: number | null;
129
+ other: number | null;
130
+ icms_cst: string | null;
131
+ icms_csosn: string | null;
132
+ icms_base: number | null;
133
+ icms_rate: number | null;
134
+ icms_value: number | null;
135
+ pis_value: number | null;
136
+ cofins_value: number | null;
137
+ ipi_value: number | null;
138
+ }[];
139
+ duplicates: {
140
+ index: number;
141
+ number: string | null;
142
+ due_date: string | null;
143
+ value: number;
144
+ payment_type: string | null;
145
+ }[];
146
+ payments: {
147
+ index: number;
148
+ indicator: string | null;
149
+ code: string | null;
150
+ label: string | null;
151
+ value: number;
152
+ }[];
153
+ };
154
+ //# sourceMappingURL=nfe-xml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nfe-xml.d.ts","sourceRoot":"","sources":["../../src/lib/nfe-xml.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,12 @@ export declare const MIGRATION_001_PRESERVE_TERMINAL_RESULTS_AND_RECOVER_LEASES
3
3
  export declare const MIGRATION_002_A_NOTA_QUE_CHEGA_TEM_ONDE_ESPERAR = "-- A nota que chega tem onde esperar.\n--\n-- O V1 recebe NF-e de fornecedor pela SEFAZ e guarda cada uma numa caixa de\n-- entrada antes de algu\u00E9m decidir o que fazer com ela: manifestar, importar\n-- para o estoque, ou nada. Essa caixa n\u00E3o tinha destino no V2.\n--\n-- dfe_inbox 703 a nota recebida, com o XML inteiro\n-- dfe_inbox_items 3.215 o item da nota, com todo o tributo\n-- dfe_inbox_payments 739 a forma de pagamento declarada\n-- dfe_inbox_duplicatas 830 o vencimento da duplicata\n-- fiscal_groups 11 o grupo fiscal (CFOP, CST, al\u00EDquotas)\n--\n-- Os n\u00FAmeros somam os dois tenants: Espa\u00E7o Facial e Pertinho do C\u00E9u. \u00C9 o \u00FAnico\n-- dom\u00EDnio fiscal que os dois usam.\n--\n-- \u2500\u2500 O XML \u00E9 arquivo, e arquivo mora em public.documents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `dfe_inbox.xml_content` \u00E9 o XML inteiro numa coluna de texto, e\n-- `pdf_storage_path` aponta para o DANFE no storage do V1. Os dois s\u00E3o ARQUIVO,\n-- e o V2 j\u00E1 tem o arqu\u00E9tipo: `public.documents`, com kind, checksum, storage_*\n-- e a RLS de arquivo que uma coluna de texto nunca teria.\n--\n-- Ent\u00E3o a extens\u00E3o guarda um PONTEIRO \u2014 `document_id` \u2014 e os bytes v\u00E3o para o\n-- arqu\u00E9tipo, pelo est\u00E1gio de assets (`make mig-assets`), que \u00E9 quem alcan\u00E7a\n-- object storage. Enquanto os bytes n\u00E3o chegam, `document_id` fica nulo e o\n-- caminho de origem fica em metadata: a linha \u00E9 verdadeira nos dois momentos.\n--\n-- \u2500\u2500 O tributo vem inteiro, e de prop\u00F3sito \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- 30 colunas de imposto por item (ICMS, ICMS-ST, PIS, COFINS, IPI, com base,\n-- al\u00EDquota e valor de cada). \u00C9 muita coluna, e a tenta\u00E7\u00E3o \u00E9 jogar tudo num\n-- jsonb. N\u00E3o: isto \u00E9 documento fiscal recebido, e a pergunta que se faz dele \u00E9\n-- \"quanto de ICMS entrou neste m\u00EAs\" \u2014 uma soma, sobre coluna, com \u00EDndice. Um\n-- jsonb transformaria toda apura\u00E7\u00E3o numa varredura.\n--\n-- O que VAI para jsonb \u00E9 o `raw_xml_item`, que j\u00E1 \u00E9 jsonb na origem e existe\n-- para confer\u00EAncia, n\u00E3o para conta.\n--\n-- Idempotente.\n\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_inbox_documents (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n -- O ponteiro para o arqu\u00E9tipo. Nulo enquanto os bytes n\u00E3o subiram \u2014 o\n -- caminho de origem fica em metadata at\u00E9 l\u00E1.\n document_id uuid,\n access_key text,\n nfe_number text,\n nfe_series text,\n nfe_model text,\n issue_date timestamp with time zone,\n received_at timestamp with time zone,\n status text DEFAULT 'received'::text NOT NULL,\n -- A manifesta\u00E7\u00E3o do destinat\u00E1rio \u00E9 outro eixo que o status: uma nota pode\n -- estar recebida e n\u00E3o manifestada, e \u00E9 a manifesta\u00E7\u00E3o que a SEFAZ cobra.\n manifestation_status text,\n manifested_at timestamp with time zone,\n manifestation_attempts integer DEFAULT 0 NOT NULL,\n manifestation_response jsonb,\n supplier_party_id uuid,\n supplier_cnpj text,\n supplier_name text,\n supplier_ie text,\n total_value numeric(14,2),\n total_products numeric(14,2),\n total_discount numeric(14,2),\n total_freight numeric(14,2),\n total_insurance numeric(14,2),\n total_other numeric(14,2),\n total_icms_base numeric(14,2),\n total_icms_value numeric(14,2),\n total_icms_st_base numeric(14,2),\n total_icms_st_value numeric(14,2),\n total_pis_value numeric(14,2),\n total_cofins_value numeric(14,2),\n total_ipi_value numeric(14,2),\n nfe_protocol text,\n nfe_authorized_at timestamp with time zone,\n carrier_name text,\n carrier_cnpj text,\n freight_mode_code text,\n additional_info text,\n reviewed_at timestamp with time zone,\n -- O que essa nota virou no estoque, quando virou. Nulo \u00E9 nota recebida e\n -- n\u00E3o importada, que \u00E9 a maioria.\n inventory_operation_id uuid,\n owner_id uuid,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\nCOMMENT ON TABLE public.plg_fiscalbr_inbox_documents IS\n 'A NF-e de fornecedor recebida da SEFAZ, esperando manifesta\u00E7\u00E3o ou importa\u00E7\u00E3o. V1: dfe_inbox.';\nCOMMENT ON COLUMN public.plg_fiscalbr_inbox_documents.document_id IS\n 'V1: xml_content + pdf_storage_path. O XML e o DANFE s\u00E3o arquivo; os bytes moram em public.documents e aqui fica o ponteiro.';\n\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_inbox_items (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n inbox_document_id uuid NOT NULL,\n item_number integer,\n supplier_product_code text,\n ean_barcode text,\n ean_taxable text,\n description text,\n ncm text,\n cest text,\n cfop text,\n unit_commercial text,\n unit_taxable text,\n quantity_commercial numeric(18,4),\n quantity_taxable numeric(18,4),\n unit_price_commercial numeric(18,6),\n unit_price_taxable numeric(18,6),\n total_price numeric(14,2),\n discount numeric(14,2),\n freight numeric(14,2),\n insurance numeric(14,2),\n other_expenses numeric(14,2),\n icms_cst text, icms_csosn text, icms_origin text,\n icms_base numeric(14,2), icms_rate numeric(9,4), icms_value numeric(14,2),\n icms_st_base numeric(14,2), icms_st_value numeric(14,2),\n pis_cst text, pis_base numeric(14,2), pis_rate numeric(9,4), pis_value numeric(14,2),\n cofins_cst text, cofins_base numeric(14,2), cofins_rate numeric(9,4), cofins_value numeric(14,2),\n ipi_cst text, ipi_rate numeric(9,4), ipi_value numeric(14,2),\n -- O produto do V2 que esta linha da nota representa. `reconciled` \u00E9 a\n -- decis\u00E3o de quem conferiu; o id \u00E9 o resultado dela.\n product_id uuid,\n reconciled boolean DEFAULT false NOT NULL,\n raw_xml_item jsonb,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\nCOMMENT ON TABLE public.plg_fiscalbr_inbox_items IS\n 'O item da NF-e recebida, com o tributo em coluna \u2014 a apura\u00E7\u00E3o soma coluna, n\u00E3o varre jsonb. V1: dfe_inbox_items.';\n\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_inbox_payments (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n inbox_document_id uuid NOT NULL,\n payment_index integer,\n payment_indicator text,\n payment_type_code text,\n payment_type_label text,\n value numeric(14,2),\n change_given numeric(14,2),\n card_brand text,\n card_acquirer_cnpj text,\n card_auth_code text,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_inbox_duplicates (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n inbox_document_id uuid NOT NULL,\n duplicate_index integer,\n number text,\n due_date date,\n value numeric(14,2),\n -- O que essa duplicata virou no contas a pagar, se algu\u00E9m a lan\u00E7ou.\n installment_id uuid,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\nCOMMENT ON TABLE public.plg_fiscalbr_inbox_duplicates IS\n 'O vencimento declarado na NF-e recebida. Vira parcela do contas a pagar quando algu\u00E9m lan\u00E7a a nota. V1: dfe_inbox_duplicatas.';\n\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_tax_groups (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n name text NOT NULL,\n cfop text,\n csosn text,\n cst text,\n icms_origin text,\n icms_rate numeric(9,4),\n icms_bc_mode text,\n pis_situation text,\n pis_rate numeric(9,4),\n cofins_situation text,\n cofins_rate numeric(9,4),\n -- A reforma tribut\u00E1ria j\u00E1 est\u00E1 no cadastro do V1 e vem junto: deixar de\n -- fora obrigaria a recadastrar tudo no dia em que IBS/CBS valerem.\n ibs_cbs_situation text,\n ibs_cbs_classification text,\n base_calculation_mode text,\n benefit_code text,\n cest text,\n poverty_fund_rate numeric(9,4),\n is_active boolean DEFAULT true NOT NULL,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\nCOMMENT ON TABLE public.plg_fiscalbr_tax_groups IS\n 'O grupo fiscal: CFOP, CST/CSOSN e al\u00EDquotas que o produto herda na emiss\u00E3o. V1: fiscal_groups. Traz IBS/CBS porque o cadastro do V1 j\u00E1 os tem.';\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-- As chaves, os \u00EDndices 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_fiscalbr_inbox_documents', 'plg_fiscalbr_inbox_items',\n 'plg_fiscalbr_inbox_payments', 'plg_fiscalbr_inbox_duplicates',\n 'plg_fiscalbr_tax_groups'] LOOP\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = ('public.' || t)::regclass AND contype = 'p') THEN\n EXECUTE format('ALTER TABLE ONLY public.%I ADD CONSTRAINT %I PRIMARY KEY (id)', t, t || '_pkey');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = t || '_tenant_id_id_key') THEN\n EXECUTE format('ALTER TABLE ONLY public.%I ADD CONSTRAINT %I UNIQUE (tenant_id, id)', t, t || '_tenant_id_id_key');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = t || '_tenant_fkey') THEN\n EXECUTE format('ALTER TABLE ONLY public.%I ADD CONSTRAINT %I FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE', t, t || '_tenant_fkey');\n END IF;\n END LOOP;\nEND $$;\n\nDO $$ BEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_documents_unit_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_documents ADD CONSTRAINT plg_fiscalbr_inbox_documents_unit_fkey\n FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n END IF;\n -- O arquivo pode ser apagado sem levar a nota junto: a nota fiscal existe\n -- mesmo sem o XML \u00E0 m\u00E3o, e \u00E9 ela que a SEFAZ conhece.\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_documents_document_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_documents ADD CONSTRAINT plg_fiscalbr_inbox_documents_document_fkey\n FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE SET NULL;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_documents_supplier_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_documents ADD CONSTRAINT plg_fiscalbr_inbox_documents_supplier_fkey\n FOREIGN KEY (supplier_party_id) REFERENCES public.people(id) ON DELETE SET NULL;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_documents_operation_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_documents ADD CONSTRAINT plg_fiscalbr_inbox_documents_operation_fkey\n FOREIGN KEY (inventory_operation_id) REFERENCES public.plg_inventory_operations(id) ON DELETE SET NULL;\n END IF;\n\n -- Item, pagamento e duplicata n\u00E3o existem fora da nota: CASCADE.\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_items_document_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_items ADD CONSTRAINT plg_fiscalbr_inbox_items_document_fkey\n FOREIGN KEY (tenant_id, inbox_document_id) REFERENCES public.plg_fiscalbr_inbox_documents(tenant_id, id) ON DELETE CASCADE;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_items_product_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_items ADD CONSTRAINT plg_fiscalbr_inbox_items_product_fkey\n FOREIGN KEY (product_id) REFERENCES public.products(id) ON DELETE SET NULL;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_payments_document_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_payments ADD CONSTRAINT plg_fiscalbr_inbox_payments_document_fkey\n FOREIGN KEY (tenant_id, inbox_document_id) REFERENCES public.plg_fiscalbr_inbox_documents(tenant_id, id) ON DELETE CASCADE;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_duplicates_document_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_duplicates ADD CONSTRAINT plg_fiscalbr_inbox_duplicates_document_fkey\n FOREIGN KEY (tenant_id, inbox_document_id) REFERENCES public.plg_fiscalbr_inbox_documents(tenant_id, id) ON DELETE CASCADE;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_duplicates_installment_fkey') THEN\n ALTER TABLE ONLY public.plg_fiscalbr_inbox_duplicates ADD CONSTRAINT plg_fiscalbr_inbox_duplicates_installment_fkey\n FOREIGN KEY (installment_id) REFERENCES public.plg_financial_invoice_installments(id) ON DELETE SET NULL;\n END IF;\nEND $$;\n\n-- A chave de acesso \u00E9 \u00FAnica por casa: a mesma nota chegando duas vezes da SEFAZ\n-- \u00E9 a mesma nota, e \u00E9 isso que impede a importa\u00E7\u00E3o em dobro.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_fiscalbr_inbox_documents_access_key_uidx\n ON public.plg_fiscalbr_inbox_documents USING btree (tenant_id, access_key)\n WHERE access_key IS NOT NULL;\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_inbox_documents_pending_idx\n ON public.plg_fiscalbr_inbox_documents USING btree (tenant_id, unit_id, issue_date DESC)\n WHERE inventory_operation_id IS NULL;\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_inbox_items_document_idx\n ON public.plg_fiscalbr_inbox_items USING btree (tenant_id, inbox_document_id);\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_inbox_items_product_idx\n ON public.plg_fiscalbr_inbox_items USING btree (tenant_id, product_id) WHERE product_id IS NOT NULL;\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_inbox_payments_document_idx\n ON public.plg_fiscalbr_inbox_payments USING btree (tenant_id, inbox_document_id);\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_inbox_duplicates_document_idx\n ON public.plg_fiscalbr_inbox_duplicates USING btree (tenant_id, inbox_document_id);\nCREATE UNIQUE INDEX IF NOT EXISTS plg_fiscalbr_tax_groups_name_uidx\n ON public.plg_fiscalbr_tax_groups USING btree (tenant_id, lower(name));\n\nDO $$\nDECLARE t text;\nBEGIN\n FOREACH t IN ARRAY ARRAY['plg_fiscalbr_inbox_documents', 'plg_fiscalbr_inbox_items',\n 'plg_fiscalbr_inbox_payments', 'plg_fiscalbr_inbox_duplicates',\n 'plg_fiscalbr_tax_groups'] LOOP\n EXECUTE format('DROP TRIGGER IF EXISTS %I ON public.%I', t || '_updated_at', t);\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 END LOOP;\nEND $$;\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-- Quem enxerga\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-- Nota fiscal \u00E9 do FINANCEIRO, e a permiss\u00E3o \u00E9 a que ele j\u00E1 usa. A caixa de\n-- entrada n\u00E3o tem unidade nos filhos \u2014 eles herdam a da nota \u2014, ent\u00E3o o escopo\n-- de unidade vale s\u00F3 no documento; o resto \u00E9 do tenant, e \u00E9 a CASCADE da FK que\n-- garante que ningu\u00E9m veja filho de nota alheia.\n-- RLS, uma a uma e por extenso. O la\u00E7o adiante j\u00E1 liga tudo isto \u2014 mas ele liga\n-- por `EXECUTE format(...)`, e o port\u00E3o que revisa plugin l\u00EA o ARQUIVO, n\u00E3o o\n-- banco: para ele, tabela com tenant_id e sem um ENABLE literal \u00E9 tabela\n-- aberta. Uma garantia que o revisor n\u00E3o consegue ler \u00E9 uma garantia que\n-- ningu\u00E9m confere.\nALTER TABLE public.plg_fiscalbr_inbox_documents ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_fiscalbr_inbox_items ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_fiscalbr_tax_groups ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_fiscalbr_inbox_payments ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_fiscalbr_inbox_duplicates ENABLE ROW LEVEL SECURITY;\n\nDO $$\nDECLARE\n t text;\n doc boolean;\nBEGIN\n FOREACH t IN ARRAY ARRAY['plg_fiscalbr_inbox_documents', 'plg_fiscalbr_inbox_items',\n 'plg_fiscalbr_inbox_payments', 'plg_fiscalbr_inbox_duplicates',\n 'plg_fiscalbr_tax_groups'] LOOP\n doc := t = 'plg_fiscalbr_inbox_documents';\n EXECUTE format('ALTER TABLE public.%I FORCE ROW LEVEL SECURITY', t);\n EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);\n EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.%I TO authenticated', t);\n EXECUTE format('GRANT ALL ON TABLE public.%I TO service_role', t);\n\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_authz_select', t);\n EXECUTE format($f$CREATE POLICY %I ON public.%I FOR SELECT TO authenticated USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (%s)\n AND (SELECT app.has_permission('financial.read', %s)))$f$,\n t || '_authz_select', t,\n CASE WHEN doc THEN format('unit_id IS NULL OR (SELECT app.has_unit(%I.unit_id))', t) ELSE 'true' END,\n CASE WHEN doc THEN format('%I.unit_id', t) ELSE 'NULL::uuid' END);\n\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_authz_write', t);\n EXECUTE format($f$CREATE POLICY %I ON public.%I FOR ALL TO authenticated USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('financial.edit', %s)))\n WITH CHECK (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('financial.edit', %s)))$f$,\n t || '_authz_write', t,\n CASE WHEN doc THEN format('%I.unit_id', t) ELSE 'NULL::uuid' END,\n CASE WHEN doc THEN format('%I.unit_id', t) ELSE 'NULL::uuid' END);\n END LOOP;\nEND $$;\n\nINSERT INTO public.scoped_resources (resource_table, unit_column, owner_column, shareable, default_visibility, label)\nVALUES ('plg_fiscalbr_inbox_documents', 'unit_id', 'owner_id', false, 'unit', 'Notas recebidas')\nON CONFLICT (resource_table) DO UPDATE\n SET unit_column = EXCLUDED.unit_column, default_visibility = EXCLUDED.default_visibility, label = EXCLUDED.label;\n";
4
4
  export declare const MIGRATION_003_A_CAIXA_DE_ENTRADA_ATRAVESSA = "-- A caixa de entrada atravessa.\n--\n-- A 002 fez as cinco tabelas. Esta escreve o de-para. As cinco usam o writer\n-- gen\u00E9rico `by_shape` (spine 123): as colunas do destino foram desenhadas\n-- olhando a origem, ent\u00E3o o que falta \u00E9 dizer qual nome vira qual \u2014 n\u00E3o uma\n-- fun\u00E7\u00E3o de upsert por tabela.\n--\n-- \u2500\u2500 O arquivo n\u00E3o vem por aqui \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- `xml_content` guarda o XML inteiro e `pdf_storage_path` aponta para o DANFE.\n-- Os dois saem do de-para com `-`: arquivo atravessa pelo est\u00E1gio de assets,\n-- que \u00E9 quem alcan\u00E7a object storage, e vira linha de `public.documents`. O\n-- caminho de origem fica em metadata para o est\u00E1gio achar.\n--\n-- \u2500\u2500 O fornecedor fica para a costura \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- `supplier_party_id` n\u00E3o \u00E9 preenchido aqui. A origem guarda CNPJ e nome, n\u00E3o\n-- um id de contato, e casar CNPJ com `public.people` \u00E9 uma decis\u00E3o de\n-- identidade \u2014 a mesma que j\u00E1 p\u00F4s 438 clientes em quarentena. Fica nulo, com o\n-- CNPJ na coluna ao lado, e uma passada de costura resolve quando algu\u00E9m\n-- decidir a regra.\n--\n-- Idempotente.\n\nSET LOCAL migration.allowlist_unlock = 'on';\n\nSELECT migration._allow(\n 'fiscal_groups', 'public.plg_fiscalbr_tax_groups', 'fiscal_br', 'by_shape',\n jsonb_build_object('target', 'public.plg_fiscalbr_tax_groups'),\n NULL::text[],\n jsonb_build_object(\n 'uuid', 'id',\n 'name', 'name',\n 'cfop', 'cfop',\n 'csosn', 'csosn',\n 'cst', 'cst',\n 'icms_origin', 'icms_origin',\n 'icms_rate', 'icms_rate',\n 'icms_modalidade_bc', 'icms_bc_mode',\n 'pis_situation', 'pis_situation',\n 'pis_rate', 'pis_rate',\n 'cofins_situation', 'cofins_situation',\n 'cofins_rate', 'cofins_rate',\n 'ibs_cbs_situation', 'ibs_cbs_situation',\n 'ibs_cbs_classification', 'ibs_cbs_classification',\n 'base_calculation_mode', 'base_calculation_mode',\n 'cbenef', 'benefit_code',\n 'cest', 'cest',\n 'poverty_fund_rate', 'poverty_fund_rate',\n 'active', 'is_active',\n 'created_at', 'created_at',\n 'updated_at', 'updated_at',\n 'tenant_id', '-',\n 'id', 'jsonb:fiscalbr.v1_ref'),\n 'O grupo fiscal: CFOP, CST/CSOSN e al\u00EDquotas que o produto herda na emiss\u00E3o. '\n 'IBS/CBS v\u00EAm junto porque o cadastro do V1 j\u00E1 os tem \u2014 deixar de fora '\n 'obrigaria a recadastrar tudo no dia em que a reforma valer.'\n);\n\nSELECT migration._allow(\n 'dfe_inbox', 'public.plg_fiscalbr_inbox_documents', 'fiscal_br', 'by_shape',\n jsonb_build_object('target', 'public.plg_fiscalbr_inbox_documents'),\n NULL::text[],\n jsonb_build_object(\n 'uuid', 'id',\n 'company_id', 'unit_id@companies',\n 'access_key', 'access_key',\n 'nfe_number', 'nfe_number',\n 'nfe_series', 'nfe_series',\n 'nfe_model', 'nfe_model',\n 'issue_date', 'issue_date',\n 'received_at', 'received_at',\n 'status', 'status',\n 'manifestation_status', 'manifestation_status',\n 'manifested_at', 'manifested_at',\n 'manifestation_attempts', 'manifestation_attempts',\n 'manifestation_response', 'manifestation_response',\n 'supplier_cnpj', 'supplier_cnpj',\n 'supplier_name', 'supplier_name',\n 'supplier_ie', 'supplier_ie',\n 'total_value', 'total_value',\n 'total_products', 'total_products',\n 'total_discount', 'total_discount',\n 'total_freight', 'total_freight',\n 'total_insurance', 'total_insurance',\n 'total_other', 'total_other',\n 'total_icms_base', 'total_icms_base',\n 'total_icms_value', 'total_icms_value',\n 'total_icms_st_base', 'total_icms_st_base',\n 'total_icms_st_value', 'total_icms_st_value',\n 'total_pis_value', 'total_pis_value',\n 'total_cofins_value', 'total_cofins_value',\n 'total_ipi_value', 'total_ipi_value',\n 'nfe_protocol', 'nfe_protocol') ||\n -- `jsonb_build_object` aceita no m\u00E1ximo 100 argumentos, e este de-para tem\n -- 59 pares. Dois objetos concatenados, e n\u00E3o uma tabela a menos.\n jsonb_build_object(\n 'nfe_authorized_at', 'nfe_authorized_at',\n 'carrier_name', 'carrier_name',\n 'carrier_cnpj', 'carrier_cnpj',\n 'freight_mode_code', 'freight_mode_code',\n 'additional_info', 'additional_info',\n 'reviewed_at', 'reviewed_at',\n 'created_at', 'created_at',\n 'updated_at', 'updated_at',\n 'tenant_id', '-',\n 'reviewed_by', '-',\n 'xml_content', '-',\n 'pdf_storage_path', 'jsonb:fiscalbr.v1_pdf_path',\n 'pdf_fetched_at', 'jsonb:fiscalbr.v1_pdf_fetched_at',\n 'danfe_import_id', 'jsonb:fiscalbr.v1_danfe_import_ref',\n 'plugnotas_payload', 'jsonb:fiscalbr.v1_plugnotas_payload',\n 'plugnotas_received_at', 'jsonb:fiscalbr.v1_plugnotas_received_at',\n 'nfe_cnf', 'jsonb:fiscalbr.nfe.cnf',\n 'nfe_natureza_operacao', 'jsonb:fiscalbr.nfe.natureza_operacao',\n 'nfe_finalidade', 'jsonb:fiscalbr.nfe.finalidade',\n 'nfe_tipo_operacao', 'jsonb:fiscalbr.nfe.tipo_operacao',\n 'nfe_destination', 'jsonb:fiscalbr.nfe.destination',\n 'supplier_address', 'jsonb:fiscalbr.supplier.street',\n 'supplier_number', 'jsonb:fiscalbr.supplier.number',\n 'supplier_complement', 'jsonb:fiscalbr.supplier.complement',\n 'supplier_neighborhood', 'jsonb:fiscalbr.supplier.neighborhood',\n 'supplier_city', 'jsonb:fiscalbr.supplier.city',\n 'supplier_state', 'jsonb:fiscalbr.supplier.state',\n 'supplier_postal_code', 'jsonb:fiscalbr.supplier.postal_code',\n 'supplier_phone', 'jsonb:fiscalbr.supplier.phone',\n 'supplier_email', 'jsonb:fiscalbr.supplier.email',\n 'id', 'jsonb:fiscalbr.v1_ref'),\n 'A NF-e de fornecedor recebida da SEFAZ. `xml_content` sai com `-`: arquivo '\n 'atravessa pelo est\u00E1gio de assets e vira public.documents, e o caminho do '\n 'DANFE fica em metadata para o est\u00E1gio achar. O endere\u00E7o do fornecedor vai '\n 'para metadata porque o fornecedor \u00E9 `public.people`, e duplic\u00E1-lo em coluna '\n 'daria duas respostas para \"onde ele fica\".'\n);\n\nSELECT migration._allow(\n 'dfe_inbox_items', 'public.plg_fiscalbr_inbox_items', 'fiscal_br', 'by_shape',\n jsonb_build_object('target', 'public.plg_fiscalbr_inbox_items'),\n NULL::text[],\n jsonb_build_object(\n 'uuid', 'id',\n 'dfe_inbox_id', 'inbox_document_id@dfe_inbox',\n 'item_number', 'item_number',\n 'product_code_supplier', 'supplier_product_code',\n 'ean_barcode', 'ean_barcode',\n 'ean_trib', 'ean_taxable',\n 'description', 'description',\n 'ncm', 'ncm',\n 'cest', 'cest',\n 'cfop', 'cfop',\n 'unit_commercial', 'unit_commercial',\n 'unit_taxable', 'unit_taxable',\n 'quantity_commercial', 'quantity_commercial',\n 'quantity_taxable', 'quantity_taxable',\n 'unit_price_commercial', 'unit_price_commercial',\n 'unit_price_taxable', 'unit_price_taxable',\n 'total_price', 'total_price',\n 'discount', 'discount',\n 'freight', 'freight',\n 'insurance', 'insurance',\n 'other_expenses', 'other_expenses',\n 'icms_cst', 'icms_cst',\n 'icms_csosn', 'icms_csosn',\n 'icms_origin', 'icms_origin',\n 'icms_base', 'icms_base',\n 'icms_rate', 'icms_rate',\n 'icms_value', 'icms_value',\n 'icms_st_base', 'icms_st_base',\n 'icms_st_value', 'icms_st_value',\n 'pis_cst', 'pis_cst',\n 'pis_base', 'pis_base',\n 'pis_rate', 'pis_rate',\n 'pis_value', 'pis_value',\n 'cofins_cst', 'cofins_cst',\n 'cofins_base', 'cofins_base',\n 'cofins_rate', 'cofins_rate',\n 'cofins_value', 'cofins_value',\n 'ipi_cst', 'ipi_cst',\n 'ipi_rate', 'ipi_rate',\n 'ipi_value', 'ipi_value',\n 'linked_product_id', 'product_id@products',\n 'reconciled', 'reconciled',\n 'raw_xml_item', 'raw_xml_item',\n 'created_at', 'created_at',\n 'updated_at', 'updated_at',\n 'tenant_id', '-',\n 'id', 'jsonb:fiscalbr.v1_ref'),\n 'O item da nota, com o tributo em COLUNA \u2014 a apura\u00E7\u00E3o soma coluna, n\u00E3o varre '\n 'jsonb. `raw_xml_item` \u00E9 a exce\u00E7\u00E3o: j\u00E1 \u00E9 jsonb na origem e serve para '\n 'confer\u00EAncia, n\u00E3o para conta.'\n);\n\nSELECT migration._allow(\n 'dfe_inbox_payments', 'public.plg_fiscalbr_inbox_payments', 'fiscal_br', 'by_shape',\n jsonb_build_object('target', 'public.plg_fiscalbr_inbox_payments'),\n NULL::text[],\n jsonb_build_object(\n 'uuid', 'id',\n 'dfe_inbox_id', 'inbox_document_id@dfe_inbox',\n 'payment_index', 'payment_index',\n 'indicador_pagamento', 'payment_indicator',\n 'tipo_pagamento_code', 'payment_type_code',\n 'tipo_pagamento_label', 'payment_type_label',\n 'value', 'value',\n 'troco', 'change_given',\n 'card_flag', 'card_brand',\n 'card_acquirer_cnpj', 'card_acquirer_cnpj',\n 'card_auth_code', 'card_auth_code',\n 'created_at', 'created_at',\n 'updated_at', 'updated_at',\n 'tenant_id', '-',\n 'id', 'jsonb:fiscalbr.v1_ref'),\n 'A forma de pagamento declarada na nota recebida.'\n);\n\nSELECT migration._allow(\n 'dfe_inbox_duplicatas', 'public.plg_fiscalbr_inbox_duplicates', 'fiscal_br', 'by_shape',\n jsonb_build_object('target', 'public.plg_fiscalbr_inbox_duplicates'),\n NULL::text[],\n jsonb_build_object(\n 'uuid', 'id',\n 'dfe_inbox_id', 'inbox_document_id@dfe_inbox',\n 'duplicata_index', 'duplicate_index',\n 'numero', 'number',\n 'vencimento', 'due_date',\n 'valor', 'value',\n 'created_at', 'created_at',\n 'updated_at', 'updated_at',\n 'tenant_id', '-',\n 'id', 'jsonb:fiscalbr.v1_ref'),\n 'O vencimento declarado na nota. Vira parcela do contas a pagar quando algu\u00E9m '\n 'lan\u00E7a a nota \u2014 `installment_id` fica nulo at\u00E9 l\u00E1, porque a parcela \u00E9 '\n 'decis\u00E3o de quem lan\u00E7a, n\u00E3o da SEFAZ.'\n);\n\nDELETE FROM migration.excluded_tables\n WHERE source_table IN ('dfe_inbox', 'dfe_inbox_items', 'dfe_inbox_payments',\n 'dfe_inbox_duplicatas', 'fiscal_groups');\n\nRESET migration.allowlist_unlock;\n";
5
5
  export declare const MIGRATION_004_O_GRUPO_FISCAL_DO_V1_REPETE_NOME = "-- O grupo fiscal do V1 repete nome.\n--\n-- A 002 criou \u00EDndice \u00DANICO em `(tenant_id, lower(name))`. O V1 tem, no tenant\n-- 8, \"Bebidas Alco\u00F3licas\", \"Bebidas N\u00E3o Alco\u00F3licas\" e \"Vinhos e Espumantes\"\n-- DUAS vezes cada \u2014 grupos fiscais diferentes com o mesmo r\u00F3tulo, e o V1\n-- permite. Tr\u00EAs das onze linhas foram para a quarentena por uma regra que eu\n-- inventei, n\u00E3o que a origem tenha.\n--\n-- O \u00EDndice vira comum: serve para buscar, n\u00E3o para proibir. Quem quiser proibir\n-- decide isso na tela, com o dado j\u00E1 dentro \u2014 e sabendo quais tr\u00EAs s\u00E3o.\n--\n-- Esta corre\u00E7\u00E3o nasceu dentro de `financial/083` e mudou de arquivo: o\n-- financeiro roda ANTES do fiscal_br na corrente, e um arquivo do financeiro\n-- que mexe numa tabela do fiscal_br falha do zero com\n-- `relation \"public.plg_fiscalbr_tax_groups\" does not exist`. Cada plugin mexe\n-- na tabela que \u00E9 dele.\n--\n-- Idempotente.\n\nDROP INDEX IF EXISTS public.plg_fiscalbr_tax_groups_name_uidx;\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_tax_groups_name_idx\n ON public.plg_fiscalbr_tax_groups USING btree (tenant_id, lower(name));\n";
6
+ export declare const MIGRATION_005_A_NOTA_EMITIDA_E_UM_DOCUMENTO = "-- A nota emitida \u00E9 um DOCUMENTO, e o documento j\u00E1 tem arqu\u00E9tipo.\n--\n-- O V1 do Pertinho do C\u00E9u emitiu 496 notas (todas NFC-e: 440 conclu\u00EDdas, 51\n-- rejeitadas, 5 com erro) e nenhuma delas tem onde morar no V2. O que existe\n-- hoje aqui \u00E9 a caixa de ENTRADA (`plg_fiscalbr_inbox_*`, a nota que chega do\n-- fornecedor) e o JOB de sa\u00EDda (`plg_fiscalbr_nfce_jobs`, a tentativa de\n-- emitir). Falta o resultado: a nota que a casa emitiu, com chave, protocolo e\n-- XML.\n--\n-- \u2500\u2500 Por que pendurar em `public.documents` e n\u00E3o numa tabela solta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Porque a pergunta que o operador faz n\u00E3o \u00E9 \"quais notas fiscais existem\",\n-- \u00E9 \"o que foi emitido sobre ESTE pedido\". Hoje a resposta est\u00E1 espalhada:\n-- o anexo do t\u00EDtulo est\u00E1 em `documents`, o formul\u00E1rio est\u00E1 em `documents`, a\n-- foto do ponto est\u00E1 em `documents` \u2014 e a nota fiscal ficaria de fora, sendo\n-- o documento mais documento de todos.\n--\n-- `documents` \u00E9 o arqu\u00E9tipo: `kind` diz a esp\u00E9cie, `subject_type`/`subject_id`\n-- dizem sobre o qu\u00EA, e o plugin pendura o que s\u00F3 ele entende numa extens\u00E3o 1:1\n-- pela chave \u2014 exatamente como `plg_forms_documents` j\u00E1 faz. Esta migra\u00E7\u00E3o\n-- segue esse padr\u00E3o, n\u00E3o inventa outro.\n--\n-- \u2500\u2500 O que entra e o que N\u00C3O entra \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- ENTRA o artefato legal e a trilha:\n-- xml_content 113 de 496 preenchidos, 618 kB no total (5,6 kB cada).\n-- \u00C9 a prova da emiss\u00E3o e n\u00E3o se regenera. Fica.\n-- os quatro payloads 108 kB para as 496 notas inteiras. \u00C9 a conversa com a\n-- SEFAZ, e \u00E9 ela que explica as 51 rejei\u00E7\u00F5es.\n-- pdf_storage_path 84 de 496. Vira `documents.storage_path` com\n-- `storage_provider = 'v1-pending'`, a mesma conven\u00E7\u00E3o\n-- que `upsert_document` usa: o REGISTRO chegou, os bytes\n-- ainda n\u00E3o.\n--\n-- N\u00C3O entra nada que o V2 j\u00E1 saiba responder por conta pr\u00F3pria, e a emiss\u00E3o\n-- nova do V2 continua nascendo por `plg_fiscalbr_nfce_jobs` \u2014 este arquivo \u00E9 o\n-- destino do resultado, n\u00E3o um segundo caminho de emiss\u00E3o.\n--\n-- \u2500\u2500 Os dois ponteiros da nota do V1, que n\u00E3o s\u00E3o o mesmo \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- fiscal_documents.order_id 496 de 496 \u2014 a COMANDA (`table_orders`)\n-- fiscal_documents.invoice_id 118 de 496 \u2014 a VENDA (`invoices`)\n--\n-- Os dois chegam em `public.orders` no V2, e \u00E0s vezes na MESMA linha (a\n-- comanda que virou nota adota a venda). Guardar os dois na mesma coluna\n-- perderia justamente a distin\u00E7\u00E3o: `order_id` na extens\u00E3o \u00E9 a comanda; a venda\n-- vai para `plg_fiscalbr_document_orders`, que \u00E9 N:N porque uma nota pode\n-- juntar v\u00E1rias (511 v\u00EDnculos para 496 notas).\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 esp\u00E9cie entra no vocabul\u00E1rio\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-- `migration.upsert_document` RECUSA esp\u00E9cie que n\u00E3o esteja aqui, e faz certo:\n-- \u00E9 o registro que impede `kind` de virar campo livre.\n\nINSERT INTO public.document_kinds (kind, plugin, description, statuses, status)\nVALUES ('fiscal_document', 'fiscal_br',\n 'A nota fiscal que a casa EMITIU (NFC-e, NF-e, NFS-e), com chave, protocolo e XML',\n ARRAY['draft','processing','authorized','rejected','error','cancelled','substituted'],\n 'declared')\nON CONFLICT (kind) DO UPDATE\n SET plugin = EXCLUDED.plugin,\n description = EXCLUDED.description,\n statuses = EXCLUDED.statuses,\n updated_at = now();\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. A extens\u00E3o: o que s\u00F3 o fiscal entende\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_fiscalbr_documents (\n -- A chave \u00C9 o documento. N\u00E3o h\u00E1 id pr\u00F3prio: a nota e o documento s\u00E3o a mesma\n -- coisa vista de dois lugares, e um segundo id s\u00F3 criaria a chance de eles\n -- discordarem.\n document_id uuid PRIMARY KEY REFERENCES public.documents(id) ON DELETE CASCADE,\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n unit_id uuid,\n -- A comanda/pedido que originou a emiss\u00E3o.\n order_id uuid REFERENCES public.orders(id) ON DELETE SET NULL,\n model text NOT NULL,\n environment text,\n status text NOT NULL,\n series text,\n number text,\n -- 44 d\u00EDgitos. Sem CHECK de formato: uma nota REJEITADA n\u00E3o tem chave, e uma\n -- chave que a SEFAZ devolveu torta \u00E9 informa\u00E7\u00E3o, n\u00E3o erro de digita\u00E7\u00E3o.\n access_key text,\n verification_code text,\n protocol text,\n provider text,\n provider_document_id text,\n provider_integration_id text,\n total_value numeric(14,2),\n iss_value numeric(14,2),\n recipient_snapshot jsonb,\n request_payload jsonb,\n response_payload jsonb,\n status_payload jsonb,\n rejection_payload jsonb,\n source_snapshot jsonb,\n -- O artefato legal. 5,6 kB por nota nas 113 que o t\u00EAm; n\u00E3o se regenera.\n xml text,\n authorized_at timestamptz,\n last_polled_at timestamptz,\n emitted_by_user_id uuid,\n cancelled_at timestamptz,\n cancel_reason text,\n cancelled_by_user_id uuid,\n -- A cadeia de substitui\u00E7\u00E3o. Auto-refer\u00EAncia, ent\u00E3o SET NULL: apagar a nota\n -- substitu\u00EDda n\u00E3o pode arrastar a que a substituiu.\n substitutes_document_id uuid REFERENCES public.plg_fiscalbr_documents(document_id) ON DELETE SET NULL,\n substituted_by_document_id uuid REFERENCES public.plg_fiscalbr_documents(document_id) ON DELETE SET NULL,\n observations text,\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\nDO $$ BEGIN\n ALTER TABLE public.plg_fiscalbr_documents\n ADD CONSTRAINT plg_fiscalbr_documents_unit_fkey\n FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\nEXCEPTION WHEN duplicate_object THEN NULL; END $$;\n\n-- A chave de acesso \u00E9 \u00FAnica POR INQUILINO e s\u00F3 quando existe: 56 das 496 notas\n-- do Pertinho n\u00E3o t\u00EAm chave nenhuma (rejeitadas e com erro), e um UNIQUE\n-- comum as empilharia todas num conflito s\u00F3.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_fiscalbr_documents_chave_idx\n ON public.plg_fiscalbr_documents (tenant_id, access_key) WHERE access_key IS NOT NULL;\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_documents_order_idx\n ON public.plg_fiscalbr_documents (tenant_id, order_id);\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_documents_status_idx\n ON public.plg_fiscalbr_documents (tenant_id, unit_id, status, authorized_at DESC);\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 trilha da emiss\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-- 1.886 eventos para 496 notas \u2014 quase quatro por nota. \u00C9 aqui que a rejei\u00E7\u00E3o\n-- diz o motivo, e \u00E9 o \u00FAnico lugar em que ela diz.\n\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_document_events (\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 document_id uuid NOT NULL REFERENCES public.plg_fiscalbr_documents(document_id) ON DELETE CASCADE,\n event_type text NOT NULL,\n status text,\n justification text,\n request_payload jsonb,\n response_payload jsonb,\n created_by_user_id uuid,\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_document_events_doc_idx\n ON public.plg_fiscalbr_document_events (document_id, created_at DESC);\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. Quais vendas a nota cobre\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_fiscalbr_document_orders (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n document_id uuid NOT NULL REFERENCES public.plg_fiscalbr_documents(document_id) ON DELETE CASCADE,\n order_id uuid NOT NULL REFERENCES public.orders(id) ON DELETE CASCADE,\n created_at timestamptz NOT NULL DEFAULT now(),\n UNIQUE (document_id, order_id)\n);\n\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_document_orders_order_idx\n ON public.plg_fiscalbr_document_orders (tenant_id, order_id);\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-- 5. 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 $$ BEGIN\n CREATE TRIGGER plg_fiscalbr_documents_updated_at BEFORE UPDATE\n ON public.plg_fiscalbr_documents FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\nEXCEPTION WHEN duplicate_object THEN NULL; END $$;\n\n-- `app.current_tenant_id()` e n\u00E3o `user_tenant_ids()`, pela raz\u00E3o que a 013 do\n-- plugin-tables teve de consertar em produ\u00E7\u00E3o: a segunda devolve a UNI\u00C3O das\n-- contas do chamador e mostraria a nota fiscal de um cliente para outro.\nDO $$\nDECLARE t text;\nBEGIN\n FOREACH t IN ARRAY ARRAY['plg_fiscalbr_documents','plg_fiscalbr_document_events',\n 'plg_fiscalbr_document_orders'] LOOP\n EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_select', t);\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_insert', t);\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_update', t);\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_delete', t);\n EXECUTE format('CREATE POLICY %I ON public.%I FOR SELECT USING (tenant_id = (select app.current_tenant_id()))', t || '_select', t);\n EXECUTE format('CREATE POLICY %I ON public.%I FOR INSERT WITH CHECK (tenant_id = (select app.current_tenant_id()))', t || '_insert', t);\n EXECUTE format('CREATE POLICY %I ON public.%I FOR UPDATE USING (tenant_id = (select app.current_tenant_id()))', t || '_update', t);\n EXECUTE format('CREATE POLICY %I ON public.%I FOR DELETE USING (tenant_id = (select app.current_tenant_id()))', t || '_delete', t);\n EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO authenticated', t);\n END LOOP;\nEND $$;\n";
7
+ export declare const MIGRATION_006_O_GRUPO_FISCAL_GANHA_O_RAIL = "-- O grupo fiscal ganha observa\u00E7\u00E3o e etiqueta.\n--\n-- A tabela existe desde a 002 e nunca teve TELA: o grupo fiscal de toda conta\n-- migrada estava no banco sem porta. Estas duas colunas s\u00E3o o que falta para\n-- ele ter o mesmo rail de todo cadastro do produto \u2014 e a etiqueta tem uso\n-- imediato aqui, porque \u00ABSimples\u00BB, \u00ABmonof\u00E1sico\u00BB e \u00ABsubstitui\u00E7\u00E3o tribut\u00E1ria\u00BB\n-- s\u00E3o recortes que nenhum campo do grupo responde e que decidem qual grupo\n-- algu\u00E9m procura.\n--\n-- Idempotente.\n\nALTER TABLE public.plg_fiscalbr_tax_groups\n ADD COLUMN IF NOT EXISTS notes text,\n ADD COLUMN IF NOT EXISTS tags text[] DEFAULT '{}'::text[],\n ADD COLUMN IF NOT EXISTS unit_id uuid;\n\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_tax_groups_tags_gin\n ON public.plg_fiscalbr_tax_groups USING gin (tags);\n";
8
+ export declare const MIGRATION_007_A_RLS_DO_DOCUMENTO_PASSA_A_SER_LEGIVEL = "-- ---------------------------------------------------------------------------\n-- 007_a_rls_do_documento_passa_a_ser_legivel.sql\n--\n-- MESMO caso da 036 do plugin-tables, e a mesma medi\u00E7\u00E3o antes de escrever.\n--\n-- `check-plugin-review` reprovou estas tabelas com *\u00ABcria public.X com\n-- tenant_id e nunca liga row security\u00BB*. Fui ao banco: **todas t\u00EAm RLS ligada,\n-- com pol\u00EDticas** \u2014 medido no cluster do Pertinho em 29-09:\n--\n-- plg_fiscalbr_documents ........ rls=t, 4 pol\u00EDticas\n-- plg_fiscalbr_document_events .. rls=t, 4\n-- plg_fiscalbr_document_orders .. rls=t, 4\n--\n-- N\u00E3o h\u00E1 vazamento. O `ENABLE` dessas tabelas mora dentro de um\n-- `DO $$ \u2026 EXECUTE format(...)`, e o port\u00E3o l\u00EA o ARQUIVO, n\u00E3o o banco \u2014 uma\n-- garantia que o revisor n\u00E3o consegue ler \u00E9 uma garantia que ningu\u00E9m confere.\n--\n-- As migrations que as criam J\u00C1 FORAM APLICADAS; reescrev\u00EA-las trocaria o\n-- checksum, que \u00E9 a deriva de ledger pela qual este reposit\u00F3rio para o mundo.\n-- Ent\u00E3o a legibilidade entra por arquivo NOVO \u2014 o port\u00E3o pergunta ao SQL\n-- INTEIRO do plugin.\n--\n-- Ligar RLS j\u00E1 ligada \u00E9 no-op: isto n\u00E3o muda comportamento, torna confer\u00EDvel o\n-- que j\u00E1 era verdade.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_fiscalbr_documents ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_fiscalbr_document_events ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_fiscalbr_document_orders ENABLE ROW LEVEL SECURITY;\n";
9
+ export declare const MIGRATION_008_A_NOTA_RECEBIDA_GUARDA_A_DECISAO_DE_ENTRADA = "-- ---------------------------------------------------------------------------\n-- 008_a_nota_recebida_guarda_a_decisao_de_entrada.sql\n--\n-- A 002 trouxe a nota; esta guarda o que se DECIDE sobre ela.\n--\n-- O inbox do V2 j\u00E1 sabe tudo que o XML diz: fornecedor, item, NCM, CFOP, os 30\n-- campos de tributo. O que n\u00E3o tem onde morar \u00E9 a decis\u00E3o que o operador toma\n-- em cima da nota, e \u00E9 ela que transforma documento em estoque e em dinheiro:\n--\n-- a qual PRODUTO esta linha corresponde (product_id, j\u00E1 existia)\n-- que TIPO de entrada ela \u00E9 (entry_type)\n-- em que UNIDADE de estoque, com que FATOR (stock_unit_id, conversion_factor)\n-- em que PLANO DE CONTAS ela cai (chart_account_id)\n-- com que CUSTO o produto fica (cost_mode, unit_cost_new)\n--\n-- Sem essas colunas a tela de concilia\u00E7\u00E3o n\u00E3o tem onde salvar, e \"importar nota\n-- fiscal\" viraria um leitor de XML \u2014 que \u00E9 exatamente o que o V2 tinha.\n--\n-- \u2500\u2500 O vocabul\u00E1rio vem do V1 medido, n\u00E3o de um enum inventado \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `danfe_import_items.entry_type` no V1 de produ\u00E7\u00E3o (29-09-2026), 3.402 linhas:\n--\n-- insumo 2.303 revenda 546 patrimonio 280\n-- despesa 262 servico 11 (null) 5\n--\n-- S\u00E3o cinco, e duas delas \u2014 `despesa` e `servico` \u2014 N\u00C3O viram estoque. \u00C9 por\n-- isso que o tipo \u00E9 coluna da linha e n\u00E3o da nota: a mesma NF-e traz o insumo\n-- que entra no estoque e a taxa de entrega que s\u00F3 vira despesa.\n--\n-- `cost_mode` tem UM valor em 3.407 linhas: `novo`. A m\u00E9dia ponderada existe na\n-- tela do V1 e ningu\u00E9m nunca escolheu. Fica no CHECK por paridade \u2014 tirar a\n-- op\u00E7\u00E3o \u00E9 decis\u00E3o de produto, e o dado n\u00E3o autoriza ningu\u00E9m a tom\u00E1-la por aqui.\n--\n-- \u2500\u2500 Por que `stock_movement_id` na linha, e n\u00E3o s\u00F3 na nota \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Uma nota pode virar estoque PARCIALMENTE: o V1 tem os status `partial` e\n-- `partial_stocked` justamente porque o operador concilia 1 de 6 itens hoje e o\n-- resto amanh\u00E3. Guardar o movimento s\u00F3 no cabe\u00E7alho responderia \"esta nota\n-- entrou?\" e nunca \"esta LINHA entrou?\" \u2014 e \u00E9 a segunda pergunta que decide se\n-- aceitar de novo duplica saldo.\n--\n-- \u2500\u2500 O de-para \u00E9 o que faz a segunda nota ser r\u00E1pida \u2500\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-- `supplier_product_mappings` no V1: (CNPJ do fornecedor, c\u00F3digo do produto no\n-- fornecedor) \u2192 produto, tipo, plano, unidade, fator. 66 linhas na Artorius, e \u00E9\n-- o que faz a nota seguinte do mesmo fornecedor chegar j\u00E1 vinculada. Sem ele,\n-- toda importa\u00E7\u00E3o recome\u00E7a do zero e o ganho de importar XML desaparece.\n--\n-- A chave \u00E9 (tenant, CNPJ, c\u00F3digo) e N\u00C3O inclui unidade: o de-para \u00E9\n-- conhecimento sobre o cat\u00E1logo do fornecedor, n\u00E3o sobre uma loja.\n--\n-- Aditivo e idempotente. Nada aqui apaga, renomeia ou reescreve dado existente.\n-- ---------------------------------------------------------------------------\n\n-- \u2500\u2500 A decis\u00E3o, linha a linha \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nALTER TABLE public.plg_fiscalbr_inbox_items\n ADD COLUMN IF NOT EXISTS entry_type text,\n ADD COLUMN IF NOT EXISTS chart_account_id uuid,\n ADD COLUMN IF NOT EXISTS stock_unit_id uuid,\n -- A quantidade que entra no estoque, j\u00E1 convertida. Derivada\n -- (quantity_commercial \u00D7 conversion_factor), mas GRAVADA: \u00E9 o n\u00FAmero que o\n -- operador viu na tela antes de aceitar, e recalcular depois com um fator que\n -- algu\u00E9m editou daria outra resposta para a mesma entrada.\n ADD COLUMN IF NOT EXISTS stock_quantity numeric(18,4),\n ADD COLUMN IF NOT EXISTS conversion_factor numeric(14,4) NOT NULL DEFAULT 1,\n ADD COLUMN IF NOT EXISTS cost_mode text,\n ADD COLUMN IF NOT EXISTS unit_cost_new numeric(18,6),\n -- O movimento que ESTA linha produziu. Nulo = n\u00E3o entrou.\n ADD COLUMN IF NOT EXISTS stock_movement_id uuid,\n -- Diverg\u00EAncia: chegou menos do que a nota diz. O V1 guarda quantidade, motivo\n -- e observa\u00E7\u00E3o, e a nota continua v\u00E1lida \u2014 quem responde pela diferen\u00E7a \u00E9 o\n -- fornecedor, n\u00E3o o estoque.\n ADD COLUMN IF NOT EXISTS shortage_quantity numeric(18,4),\n ADD COLUMN IF NOT EXISTS shortage_reason text,\n ADD COLUMN IF NOT EXISTS shortage_note text;\n\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_items_entry_type_chk') THEN\n ALTER TABLE public.plg_fiscalbr_inbox_items\n ADD CONSTRAINT plg_fiscalbr_inbox_items_entry_type_chk\n CHECK (entry_type IS NULL OR entry_type IN ('insumo','revenda','patrimonio','despesa','servico'));\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_items_cost_mode_chk') THEN\n ALTER TABLE public.plg_fiscalbr_inbox_items\n ADD CONSTRAINT plg_fiscalbr_inbox_items_cost_mode_chk\n CHECK (cost_mode IS NULL OR cost_mode IN ('novo','media'));\n END IF;\n -- Um fator zero ou negativo n\u00E3o converte nada: transformaria a quantidade da\n -- nota em zero estoque sem ningu\u00E9m acusar. \u00C9 o defeito silencioso que o\n -- FLOW.md do V1 nomeia (\u00ABfator de convers\u00E3o errado \u00E9 silencioso\u00BB), e aqui ele\n -- deixa de ser poss\u00EDvel.\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_items_factor_chk') THEN\n ALTER TABLE public.plg_fiscalbr_inbox_items\n ADD CONSTRAINT plg_fiscalbr_inbox_items_factor_chk CHECK (conversion_factor > 0);\n END IF;\nEND $$;\n\n-- FK l\u00F3gica para o plano de contas e a unidade: as duas s\u00E3o de OUTROS plugins\n-- (financial e inventory), e a 000 deste plugin j\u00E1 estabelece que FK f\u00EDsica\n-- entre plugins \u00E9 proibida \u2014 instalar ou remover um addon n\u00E3o reescreve o\n-- schema do vizinho. A integridade \u00E9 verificada na RPC, que \u00E9 quem escreve.\nCOMMENT ON COLUMN public.plg_fiscalbr_inbox_items.chart_account_id IS\n 'Logical reference to plg_financial_chart_of_accounts.id \u2014 validated by the accept RPC, not by a cross-plugin FK.';\nCOMMENT ON COLUMN public.plg_fiscalbr_inbox_items.stock_unit_id IS\n 'Logical reference to plg_inventory_measurement_units.id \u2014 validated by the accept RPC.';\nCOMMENT ON COLUMN public.plg_fiscalbr_inbox_items.stock_quantity IS\n 'quantity_commercial x conversion_factor, stored as shown on screen before accepting.';\n\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_inbox_items_pending_idx\n ON public.plg_fiscalbr_inbox_items (tenant_id, inbox_document_id)\n WHERE reconciled IS NOT TRUE;\n\n-- \u2500\u2500 A decis\u00E3o no cabe\u00E7alho da nota \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nALTER TABLE public.plg_fiscalbr_inbox_documents\n -- \u00ABitens sem plano herdar\u00E3o este\u00BB \u2014 o plano da nota \u00E9 padr\u00E3o com HERAN\u00C7A, n\u00E3o\n -- obriga\u00E7\u00E3o por item. A frase \u00E9 do V1 e a coluna existe para ela.\n ADD COLUMN IF NOT EXISTS default_chart_account_id uuid,\n -- O t\u00EDtulo a pagar que esta nota gerou. Mesma ideia do inventory_operation_id\n -- que a 002 j\u00E1 trouxe para o estoque: a nota aponta para as duas travessias.\n ADD COLUMN IF NOT EXISTS invoice_id uuid,\n ADD COLUMN IF NOT EXISTS stocked_at timestamptz,\n -- A natureza da opera\u00E7\u00E3o declarada na NF-e (\u00ABREM BONIFICACAO NAO\n -- CONTRIBUINTE\u00BB). O bloco de situa\u00E7\u00E3o fiscal da concilia\u00E7\u00E3o mostra isso, e\n -- sem a coluna a tela mostraria um campo vazio.\n ADD COLUMN IF NOT EXISTS nature_operation text,\n ADD COLUMN IF NOT EXISTS default_location_id uuid;\n\nCOMMENT ON COLUMN public.plg_fiscalbr_inbox_documents.invoice_id IS\n 'Logical reference to plg_financial_invoices.id created by fiscalbr_accept_inbox_document.';\n\n-- \u2500\u2500 A parcela sabe como se paga \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 duplicata do XML traz n\u00FAmero, vencimento e valor. O que o operador acrescenta\n-- na tela \u00E9 COMO ela se paga \u2014 e o boleto que ele anexa carrega o c\u00F3digo de\n-- barras, que \u00E9 o que a automa\u00E7\u00E3o do V1 l\u00EA para preencher a parcela.\n\nALTER TABLE public.plg_fiscalbr_inbox_duplicates\n ADD COLUMN IF NOT EXISTS payment_type text,\n ADD COLUMN IF NOT EXISTS barcode text,\n ADD COLUMN IF NOT EXISTS pix_key text,\n ADD COLUMN IF NOT EXISTS payment_method_id uuid;\n\n-- O vocabul\u00E1rio \u00E9 o do V1, medido em `danfe_payments.payment_type` (1.069 linhas):\n-- boleto 660 \u00B7 sem_pagamento 108 \u00B7 outro 107 \u00B7 dinheiro 65 \u00B7\n-- duplicata_mercantil 45 \u00B7 pix 35 \u00B7 cartao_credito 35 \u00B7 cartao_debito 9 \u00B7\n-- credito_loja 4 \u00B7 vale_presente 1\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_inbox_duplicates_payment_type_chk') THEN\n ALTER TABLE public.plg_fiscalbr_inbox_duplicates\n ADD CONSTRAINT plg_fiscalbr_inbox_duplicates_payment_type_chk\n CHECK (payment_type IS NULL OR payment_type IN (\n 'boleto','pix','dinheiro','cartao_credito','cartao_debito',\n 'duplicata_mercantil','credito_loja','vale_presente','sem_pagamento','outro'));\n END IF;\nEND $$;\n\n-- \u2500\u2500 O de-para fornecedor \u00D7 produto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_supplier_product_map (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n -- S\u00F3 d\u00EDgitos, normalizado na escrita: o XML traz o CNPJ cru e a tela do V1\n -- aceitava pontua\u00E7\u00E3o, ent\u00E3o duas grafias do mesmo fornecedor viravam dois\n -- de-para e a segunda nota n\u00E3o achava o primeiro.\n supplier_cnpj text NOT NULL,\n supplier_product_code text NOT NULL,\n product_id uuid NOT NULL REFERENCES public.products(id) ON DELETE CASCADE,\n entry_type text,\n chart_account_id uuid,\n stock_unit_id uuid,\n conversion_factor numeric(14,4) NOT NULL DEFAULT 1,\n cost_mode text,\n -- Quantas vezes este de-para acertou. Serve para a tela mostrar de onde veio o\n -- v\u00EDnculo (\u00ABj\u00E1 usado 12 vezes\u00BB) em vez de um produto que apareceu sozinho.\n hit_count integer NOT NULL DEFAULT 0,\n last_used_at timestamptz,\n owner_id uuid,\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT plg_fiscalbr_supplier_product_map_factor_chk CHECK (conversion_factor > 0),\n CONSTRAINT plg_fiscalbr_supplier_product_map_entry_type_chk\n CHECK (entry_type IS NULL OR entry_type IN ('insumo','revenda','patrimonio','despesa','servico')),\n CONSTRAINT plg_fiscalbr_supplier_product_map_cost_mode_chk\n CHECK (cost_mode IS NULL OR cost_mode IN ('novo','media'))\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS plg_fiscalbr_supplier_product_map_uq\n ON public.plg_fiscalbr_supplier_product_map (tenant_id, supplier_cnpj, supplier_product_code);\n\nALTER TABLE public.plg_fiscalbr_supplier_product_map ENABLE ROW LEVEL SECURITY;\n\n-- Mesmas pol\u00EDticas das tabelas do inbox (002): o de-para \u00E9 conhecimento do\n-- financeiro sobre o fornecedor, ent\u00E3o l\u00EA com `financial.read` e escreve com\n-- `financial.edit`. N\u00E3o tem unit_id \u2014 \u00E9 cat\u00E1logo do fornecedor, n\u00E3o da loja.\nDROP POLICY IF EXISTS plg_fiscalbr_supplier_product_map_authz_select ON public.plg_fiscalbr_supplier_product_map;\nCREATE POLICY plg_fiscalbr_supplier_product_map_authz_select\n ON public.plg_fiscalbr_supplier_product_map FOR SELECT TO authenticated\n USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('financial.read', NULL::uuid)));\n\nDROP POLICY IF EXISTS plg_fiscalbr_supplier_product_map_authz_write ON public.plg_fiscalbr_supplier_product_map;\nCREATE POLICY plg_fiscalbr_supplier_product_map_authz_write\n ON public.plg_fiscalbr_supplier_product_map FOR ALL TO authenticated\n USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('financial.edit', NULL::uuid)))\n WITH CHECK (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('financial.edit', NULL::uuid)));\n\nCOMMENT ON TABLE public.plg_fiscalbr_supplier_product_map IS\n 'Supplier catalogue memory: (CNPJ, supplier product code) -> the entry decision, so the next note from the same supplier arrives already bound. Mirrors V1 supplier_product_mappings.';\n";
10
+ export declare const MIGRATION_009_ACEITAR_A_NOTA_ESCREVE_ESTOQUE_E_TITULO_DE_UMA_VEZ = "-- ---------------------------------------------------------------------------\n-- 009_aceitar_a_nota_escreve_estoque_e_titulo_de_uma_vez.sql\n--\n-- \u00C9 o arquivo que fecha o porte: \u00ABAceitar\u00BB na concilia\u00E7\u00E3o d\u00E1 entrada no estoque\n-- E cria a conta a pagar, numa transa\u00E7\u00E3o s\u00F3.\n--\n-- Medido no V1 de produ\u00E7\u00E3o (tenant 12 da Artorius, 29-09-2026): **72 de 72**\n-- movimentos de estoque nasceram com `danfe_import_item_id`. Nenhum foi digitado.\n-- Dar ao V2 uma tela de movimenta\u00E7\u00E3o manual e chamar isso de paridade seria\n-- entregar a tela que eles nunca abriram.\n--\n-- \u2500\u2500 N\u00E3o reimplementa estoque nem t\u00EDtulo. CHAMA quem j\u00E1 faz \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- O V2 j\u00E1 tem as duas primitivas, e as duas j\u00E1 s\u00E3o idempotentes:\n--\n-- public.inventory_receive(location, lines, document, idempotency_key)\n-- public.financial_create_invoice(direction, total, counterparty, \u2026, key)\n--\n-- Reescrever INSERT em `plg_inventory_stock_movements` aqui daria uma segunda\n-- resposta para \u00ABcomo entra estoque\u00BB \u2014 e a posi\u00E7\u00E3o \u00E9 derivada por gatilho, ent\u00E3o\n-- a segunda resposta sairia com saldo certo e sem ningu\u00E9m acusar. A entrada de\n-- nota \u00E9 mais um CHAMADOR dessas portas, como a requisi\u00E7\u00E3o e a contagem.\n--\n-- Idempot\u00EAncia vem de gra\u00E7a da\u00ED: a chave \u00E9 derivada da chave de acesso da NF-e,\n-- que \u00E9 \u00FAnica por documento fiscal no Brasil. Aceitar duas vezes devolve o mesmo\n-- resultado sem escrever de novo \u2014 o AC7 do plano.\n--\n-- \u2500\u2500 O total do t\u00EDtulo \u00E9 a soma das PARCELAS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- Regra herdada da `plugin-financial/084`, que mediu: em 6,3% das notas a soma\n-- das duplicatas n\u00E3o bate com o total da nota. `plg_financial_invoices` \u00E9\n-- imut\u00E1vel depois de emitido, ent\u00E3o um t\u00EDtulo aberto com o total errado fica\n-- com saldo errado para sempre em toda tela que faz \u00ABtotal \u2212 pago\u00BB.\n-- O total da nota continua leg\u00EDvel no `metadata` e na caixa de entrada.\n--\n-- E a MESMA chave de idempot\u00EAncia da 084 (`danfe:<chave>`): uma nota que o porte\n-- hist\u00F3rico j\u00E1 transformou em t\u00EDtulo n\u00E3o pode ganhar um segundo aqui.\n--\n-- \u2500\u2500 Despesa e servi\u00E7o n\u00E3o viram estoque \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- `entry_type` decide por LINHA. A mesma NF-e traz o insumo que entra no estoque\n-- e a taxa de entrega que s\u00F3 vira despesa \u2014 e as duas precisam ir para o mesmo\n-- t\u00EDtulo a pagar. Por isso a lista de linhas do estoque \u00E9 um FILTRO sobre os\n-- itens, n\u00E3o os itens.\n--\n-- Idempotente. S\u00F3 cria fun\u00E7\u00E3o.\n-- ---------------------------------------------------------------------------\n\n-- \u2500\u2500 Ajudantes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 XML traz o CNPJ cru e a tela aceita pontua\u00E7\u00E3o. Duas grafias do mesmo\n-- fornecedor viravam dois de-para no V1, e a segunda nota n\u00E3o achava o primeiro.\nCREATE OR REPLACE FUNCTION public.plg_fiscalbr_digits(p_value text)\nRETURNS text LANGUAGE sql IMMUTABLE SET search_path TO '' AS $$\n SELECT regexp_replace(coalesce(p_value, ''), '[^0-9]', '', 'g')\n$$;\n\n-- \u2500\u2500 Receber a nota: o XML j\u00E1 lido vira linhas \u2500\u2500\u2500\u2500\u2500\u2500\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 XML \u00E9 decomposto no NAVEGADOR e chega aqui como jsonb. Parsear NF-e em\n-- PL/pgSQL seria escrever um leitor de XML dentro do banco, sem teste unit\u00E1rio\n-- poss\u00EDvel e sem a mensagem de erro que a tela precisa dar para um arquivo que\n-- n\u00E3o \u00E9 uma nota.\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_stage_inbox_document(p_note jsonb)\nRETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_key text := btrim(coalesce(p_note->>'access_key', ''));\n v_cnpj text := public.plg_fiscalbr_digits(p_note->'supplier'->>'cnpj');\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\n v_unit uuid := nullif(p_note->>'unit_id', '')::uuid;\n v_party uuid;\n v_item jsonb;\n v_map public.plg_fiscalbr_supplier_product_map%ROWTYPE;\n v_n integer := 0;\n v_matched integer := 0;\nBEGIN\n PERFORM public.plg_financial_require_permission('financial.create', v_unit);\n\n IF length(v_key) <> 44 THEN\n RAISE EXCEPTION 'fiscalbr: chave de acesso inv\u00E1lida (esperado 44 d\u00EDgitos, veio %)', length(v_key)\n USING ERRCODE = '22023';\n END IF;\n\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = v_tenant AND access_key = v_key;\n\n -- Nota j\u00E1 aceita n\u00E3o \u00E9 reimportada: o XML \u00E9 o mesmo documento fiscal, e\n -- reescrever os itens apagaria a decis\u00E3o que virou estoque e t\u00EDtulo.\n IF FOUND AND (v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL) THEN\n RETURN jsonb_build_object(\n 'document_id', v_doc.id, 'access_key', v_key, 'already_accepted', true,\n 'invoice_id', v_doc.invoice_id, 'items', 0, 'matched', 0);\n END IF;\n\n -- O fornecedor \u00E9 uma PESSOA, n\u00E3o um nome. A 094 do financial documenta o custo\n -- de n\u00E3o ter o ponteiro: praticamente todo t\u00EDtulo nascido de DANFE ficou sem\n -- contraparte. Casar por CNPJ acerta; casar por nome acertaria 137 de 554.\n IF v_cnpj <> '' THEN\n SELECT id INTO v_party FROM public.people\n WHERE tenant_id = v_tenant AND public.plg_fiscalbr_digits(document_number) = v_cnpj\n LIMIT 1;\n END IF;\n\n INSERT INTO public.plg_fiscalbr_inbox_documents (\n tenant_id, unit_id, access_key, nfe_number, nfe_series, nfe_model, issue_date,\n received_at, status, nature_operation, supplier_party_id, supplier_cnpj,\n supplier_name, supplier_ie, total_value, total_products, total_discount,\n total_freight, total_insurance, total_other, additional_info, metadata)\n VALUES (\n v_tenant, v_unit, v_key, p_note->>'nfe_number', p_note->>'nfe_series',\n coalesce(p_note->>'nfe_model', '55'), nullif(p_note->>'issue_date', '')::date,\n now(), 'pending', p_note->>'nature_operation', v_party, nullif(v_cnpj, ''),\n p_note->'supplier'->>'name', p_note->'supplier'->>'ie',\n (p_note->'totals'->>'total')::numeric, (p_note->'totals'->>'products')::numeric,\n (p_note->'totals'->>'discount')::numeric, (p_note->'totals'->>'freight')::numeric,\n (p_note->'totals'->>'insurance')::numeric, (p_note->'totals'->>'other')::numeric,\n p_note->>'additional_info',\n jsonb_build_object('source', coalesce(p_note->>'source', 'xml_upload')))\n -- O \u00EDndice \u00FAnico da 002 \u00E9 PARCIAL (`WHERE access_key IS NOT NULL`), e a\n -- infer\u00EAncia do ON CONFLICT s\u00F3 o alcan\u00E7a repetindo o predicado. Sem ele o\n -- INSERT levanta \u00ABno unique or exclusion constraint matching\u00BB \u2014 e levantaria\n -- na primeira reimporta\u00E7\u00E3o, n\u00E3o na aplica\u00E7\u00E3o.\n ON CONFLICT (tenant_id, access_key) WHERE access_key IS NOT NULL DO UPDATE SET\n -- Reimportar o mesmo XML corrige o cabe\u00E7alho; a contraparte s\u00F3 \u00E9 preenchida\n -- quando est\u00E1 vazia, para n\u00E3o desfazer uma costura feita \u00E0 m\u00E3o.\n nfe_number = EXCLUDED.nfe_number,\n nature_operation = coalesce(EXCLUDED.nature_operation, public.plg_fiscalbr_inbox_documents.nature_operation),\n supplier_party_id = coalesce(public.plg_fiscalbr_inbox_documents.supplier_party_id, EXCLUDED.supplier_party_id),\n supplier_name = EXCLUDED.supplier_name,\n total_value = EXCLUDED.total_value,\n updated_at = now()\n RETURNING * INTO v_doc;\n\n -- Os itens e as duplicatas s\u00E3o do DOCUMENTO, n\u00E3o do operador: v\u00EAm do XML e\n -- podem ser reescritos enquanto a nota n\u00E3o foi aceita.\n DELETE FROM public.plg_fiscalbr_inbox_items WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;\n DELETE FROM public.plg_fiscalbr_inbox_duplicates WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;\n DELETE FROM public.plg_fiscalbr_inbox_payments WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;\n\n FOR v_item IN SELECT * FROM jsonb_array_elements(coalesce(p_note->'items', '[]'::jsonb)) LOOP\n v_n := v_n + 1;\n\n -- O de-para: a segunda nota do mesmo fornecedor chega j\u00E1 vinculada. \u00C9 o que\n -- faz importar XML valer a pena \u2014 sem ele toda nota recome\u00E7a do zero.\n SELECT * INTO v_map FROM public.plg_fiscalbr_supplier_product_map\n WHERE tenant_id = v_tenant AND supplier_cnpj = v_cnpj\n AND supplier_product_code = coalesce(v_item->>'supplier_product_code', '');\n IF FOUND THEN v_matched := v_matched + 1; END IF;\n\n INSERT INTO public.plg_fiscalbr_inbox_items (\n tenant_id, inbox_document_id, item_number, supplier_product_code, ean_barcode,\n description, ncm, cest, cfop, unit_commercial, quantity_commercial,\n unit_price_commercial, total_price, discount, freight, other_expenses,\n icms_cst, icms_csosn, icms_base, icms_rate, icms_value,\n pis_value, cofins_value, ipi_value, raw_xml_item,\n product_id, entry_type, chart_account_id, stock_unit_id, conversion_factor,\n cost_mode, reconciled, stock_quantity, unit_cost_new)\n VALUES (\n v_tenant, v_doc.id, coalesce((v_item->>'item_number')::int, v_n),\n v_item->>'supplier_product_code', v_item->>'ean_barcode',\n v_item->>'description', v_item->>'ncm', v_item->>'cest', v_item->>'cfop',\n v_item->>'unit', (v_item->>'quantity')::numeric,\n (v_item->>'unit_price')::numeric, (v_item->>'total')::numeric,\n (v_item->>'discount')::numeric, (v_item->>'freight')::numeric,\n (v_item->>'other')::numeric,\n v_item->>'icms_cst', v_item->>'icms_csosn', (v_item->>'icms_base')::numeric,\n (v_item->>'icms_rate')::numeric, (v_item->>'icms_value')::numeric,\n (v_item->>'pis_value')::numeric, (v_item->>'cofins_value')::numeric,\n (v_item->>'ipi_value')::numeric, v_item,\n v_map.product_id, v_map.entry_type, v_map.chart_account_id, v_map.stock_unit_id,\n coalesce(v_map.conversion_factor, 1), coalesce(v_map.cost_mode, 'novo'),\n v_map.product_id IS NOT NULL,\n -- A quantidade de estoque j\u00E1 sai calculada quando o de-para deu o fator: \u00E9\n -- o n\u00FAmero que a tela mostra antes de aceitar.\n CASE WHEN v_map.product_id IS NOT NULL\n THEN (v_item->>'quantity')::numeric * coalesce(v_map.conversion_factor, 1) END,\n CASE WHEN v_map.product_id IS NOT NULL AND coalesce(v_map.conversion_factor, 1) > 0\n THEN (v_item->>'unit_price')::numeric / coalesce(v_map.conversion_factor, 1) END);\n END LOOP;\n\n INSERT INTO public.plg_fiscalbr_inbox_duplicates (\n tenant_id, inbox_document_id, duplicate_index, number, due_date, value, payment_type)\n SELECT v_tenant, v_doc.id, coalesce((d->>'index')::int, ord), d->>'number',\n nullif(d->>'due_date', '')::date, (d->>'value')::numeric,\n nullif(d->>'payment_type', '')\n FROM jsonb_array_elements(coalesce(p_note->'duplicates', '[]'::jsonb)) WITH ORDINALITY AS t(d, ord);\n\n INSERT INTO public.plg_fiscalbr_inbox_payments (\n tenant_id, inbox_document_id, payment_index, payment_indicator,\n payment_type_code, payment_type_label, value)\n SELECT v_tenant, v_doc.id, coalesce((p->>'index')::int, ord), p->>'indicator',\n p->>'code', p->>'label', (p->>'value')::numeric\n FROM jsonb_array_elements(coalesce(p_note->'payments', '[]'::jsonb)) WITH ORDINALITY AS t(p, ord);\n\n RETURN jsonb_build_object(\n 'document_id', v_doc.id, 'access_key', v_key, 'already_accepted', false,\n 'supplier_party_id', v_party, 'items', v_n, 'matched', v_matched);\nEND $function$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_stage_inbox_document(jsonb) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_stage_inbox_document(jsonb) TO authenticated;\n\n-- \u2500\u2500 Salvar a concilia\u00E7\u00E3o sem aceitar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- Conciliar e estocar s\u00E3o duas coisas no V1 \u2014 da\u00ED as duas colunas na lista, e\n-- da\u00ED a fila de 55 notas conciliadas e n\u00E3o estocadas. Quem salva o v\u00EDnculo n\u00E3o\n-- est\u00E1 mandando dar entrada.\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_save_inbox_decision(\n p_document_id uuid,\n p_items jsonb,\n p_default_chart_account_id uuid DEFAULT NULL,\n p_default_location_id uuid DEFAULT NULL,\n p_duplicates jsonb DEFAULT NULL)\nRETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\n v_row jsonb;\n v_done integer := 0;\n v_total integer;\nBEGIN\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = v_tenant AND id = p_document_id;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr: nota % n\u00E3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission('financial.edit', v_doc.unit_id);\n\n IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN\n RAISE EXCEPTION 'fiscalbr: nota % j\u00E1 foi aceita \u2014 a decis\u00E3o n\u00E3o muda depois do estoque', p_document_id\n USING ERRCODE = '23514';\n END IF;\n\n UPDATE public.plg_fiscalbr_inbox_documents SET\n default_chart_account_id = coalesce(p_default_chart_account_id, default_chart_account_id),\n default_location_id = coalesce(p_default_location_id, default_location_id),\n reviewed_at = now(), updated_at = now()\n WHERE id = v_doc.id;\n\n FOR v_row IN SELECT * FROM jsonb_array_elements(coalesce(p_items, '[]'::jsonb)) LOOP\n UPDATE public.plg_fiscalbr_inbox_items i SET\n product_id = nullif(v_row->>'product_id', '')::uuid,\n entry_type = nullif(v_row->>'entry_type', ''),\n chart_account_id = nullif(v_row->>'chart_account_id', '')::uuid,\n stock_unit_id = nullif(v_row->>'stock_unit_id', '')::uuid,\n conversion_factor = coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1),\n cost_mode = coalesce(nullif(v_row->>'cost_mode', ''), 'novo'),\n shortage_quantity = nullif(v_row->>'shortage_quantity', '')::numeric,\n shortage_reason = nullif(v_row->>'shortage_reason', ''),\n shortage_note = nullif(v_row->>'shortage_note', ''),\n -- Derivadas, gravadas: \u00E9 o que o operador viu.\n stock_quantity = i.quantity_commercial * coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1)\n - coalesce(nullif(v_row->>'shortage_quantity', '')::numeric, 0),\n unit_cost_new = CASE WHEN coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1) > 0\n THEN i.unit_price_commercial / coalesce(nullif(v_row->>'conversion_factor', '')::numeric, 1) END,\n -- Uma linha est\u00E1 conciliada quando sabe o que \u00E9. Despesa e servi\u00E7o n\u00E3o\n -- pedem produto: elas nunca v\u00E3o virar estoque.\n reconciled = CASE\n WHEN nullif(v_row->>'entry_type', '') IN ('despesa','servico') THEN true\n ELSE nullif(v_row->>'product_id', '') IS NOT NULL\n END,\n updated_at = now()\n WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id\n AND i.id = (v_row->>'id')::uuid;\n v_done := v_done + 1;\n END LOOP;\n\n IF p_duplicates IS NOT NULL THEN\n DELETE FROM public.plg_fiscalbr_inbox_duplicates\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;\n INSERT INTO public.plg_fiscalbr_inbox_duplicates (\n tenant_id, inbox_document_id, duplicate_index, number, due_date, value,\n payment_type, barcode, pix_key, payment_method_id)\n SELECT v_tenant, v_doc.id, coalesce((d->>'index')::int, ord), d->>'number',\n nullif(d->>'due_date', '')::date, (d->>'value')::numeric,\n nullif(d->>'payment_type', ''), nullif(d->>'barcode', ''),\n nullif(d->>'pix_key', ''), nullif(d->>'payment_method_id', '')::uuid\n FROM jsonb_array_elements(p_duplicates) WITH ORDINALITY AS t(d, ord);\n END IF;\n\n SELECT count(*) INTO v_total FROM public.plg_fiscalbr_inbox_items\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;\n\n UPDATE public.plg_fiscalbr_inbox_documents SET status = CASE\n WHEN (SELECT count(*) FROM public.plg_fiscalbr_inbox_items\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id AND reconciled) = v_total\n THEN 'accepted' ELSE 'partial' END\n WHERE id = v_doc.id;\n\n RETURN jsonb_build_object('document_id', v_doc.id, 'saved', v_done, 'items', v_total,\n 'reconciled', (SELECT count(*) FROM public.plg_fiscalbr_inbox_items\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id AND reconciled));\nEND $function$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_save_inbox_decision(uuid, jsonb, uuid, uuid, jsonb) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_save_inbox_decision(uuid, jsonb, uuid, uuid, jsonb) TO authenticated;\n\n-- \u2500\u2500 Aceitar: estoque e t\u00EDtulo, 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\u2500\u2500\u2500\u2500\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_accept_inbox_document(\n p_document_id uuid,\n p_location_id uuid DEFAULT NULL)\nRETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\n v_loc uuid;\n v_lines jsonb := '[]'::jsonb;\n v_insts jsonb := '[]'::jsonb;\n v_recv jsonb;\n v_inv jsonb;\n v_it record;\n v_no integer := 0;\n v_total numeric := 0;\n v_stock_n integer := 0;\n v_key text;\n v_invoice uuid;\n -- Posi\u00E7\u00E3o na lista de linhas \u2192 id do item da nota. `line_no` do movimento\n -- indexa este array direto.\n v_item_ids uuid[] := '{}';\nBEGIN\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = v_tenant AND id = p_document_id;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr: nota % n\u00E3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission('financial.create', v_doc.unit_id);\n\n -- J\u00E1 aceita devolve o que aceitou. N\u00E3o \u00E9 erro: \u00E9 a segunda vez que o operador\n -- clicou, e o AC7 diz que isso n\u00E3o pode dobrar nada.\n IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN\n RETURN jsonb_build_object('document_id', v_doc.id, 'already_accepted', true,\n 'invoice_id', v_doc.invoice_id, 'operation_id', v_doc.inventory_operation_id);\n END IF;\n\n v_loc := coalesce(p_location_id, v_doc.default_location_id);\n\n -- Toda linha tem de saber o que \u00E9. Aceitar uma nota com item sem tipo gravaria\n -- um t\u00EDtulo cujo rateio ningu\u00E9m consegue explicar depois.\n IF EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_items\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id\n AND (entry_type IS NULL OR entry_type = '')) THEN\n RAISE EXCEPTION 'fiscalbr: h\u00E1 item sem tipo de registro \u2014 conciliar antes de aceitar'\n USING ERRCODE = '23514';\n END IF;\n\n -- As linhas que viram estoque. Despesa e servi\u00E7o ficam fora daqui e entram s\u00F3\n -- no t\u00EDtulo.\n FOR v_it IN\n SELECT * FROM public.plg_fiscalbr_inbox_items\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id\n AND entry_type IN ('insumo','revenda','patrimonio')\n ORDER BY item_number\n LOOP\n IF v_it.product_id IS NULL THEN\n RAISE EXCEPTION 'fiscalbr: item % \u00E9 % e n\u00E3o tem produto vinculado', v_it.item_number, v_it.entry_type\n USING ERRCODE = '23514';\n END IF;\n v_no := v_no + 1;\n -- A ordem desta lista \u00C9 o `line_no` que o movimento vai carregar. Guardo o\n -- id da linha na mesma posi\u00E7\u00E3o para devolver o movimento ao item sem\n -- reconsultar \u2014 e sem depender de casar por produto, que repete quando a\n -- mesma mercadoria vem em duas linhas da nota.\n v_item_ids := v_item_ids || v_it.id;\n v_lines := v_lines || jsonb_build_object(\n 'product_id', v_it.product_id,\n 'quantity', coalesce(v_it.stock_quantity, v_it.quantity_commercial * v_it.conversion_factor),\n 'unit_cost', coalesce(v_it.unit_cost_new, v_it.unit_price_commercial / v_it.conversion_factor),\n 'measurement_unit_id', v_it.stock_unit_id,\n 'notes', v_it.description);\n END LOOP;\n v_stock_n := v_no;\n\n IF v_stock_n > 0 THEN\n IF v_loc IS NULL THEN\n RAISE EXCEPTION 'fiscalbr: a nota tem % linha(s) de estoque e nenhum local de estoque escolhido', v_stock_n\n USING ERRCODE = '22023';\n END IF;\n -- A chave de acesso \u00E9 \u00FAnica por documento fiscal no Brasil: \u00E9 a chave de\n -- idempot\u00EAncia natural, e \u00E9 ela que faz o segundo clique n\u00E3o dobrar saldo.\n v_recv := public.inventory_receive(\n v_loc, v_lines,\n jsonb_build_object(\n 'supplier_id', v_doc.supplier_party_id,\n 'date', v_doc.issue_date,\n 'type', 'nfe',\n 'ref', 'NF-e ' || coalesce(v_doc.nfe_number, '?'),\n 'notes', 'Entrada via DANFE NF-e ' || coalesce(v_doc.nfe_number, '?')),\n 'nfe:' || v_doc.access_key);\n\n -- Cada movimento volta para a SUA linha. Guardar s\u00F3 no cabe\u00E7alho responderia\n -- \u00ABesta nota entrou?\u00BB e nunca \u00ABesta linha entrou?\u00BB \u2014 e \u00E9 a segunda pergunta\n -- que a concilia\u00E7\u00E3o parcial faz.\n --\n -- `line_no` do movimento \u00E9 a posi\u00E7\u00E3o na lista que montei acima, e\n -- `v_item_ids` est\u00E1 na mesma ordem: um join, n\u00E3o uma varredura por posi\u00E7\u00E3o.\n UPDATE public.plg_fiscalbr_inbox_items i\n SET stock_movement_id = m.movement_id, updated_at = now()\n FROM (\n SELECT v_item_ids[(mov->>'line_no')::int] AS item_id, (mov->>'id')::uuid AS movement_id\n FROM jsonb_array_elements(coalesce(v_recv->'movements', '[]'::jsonb)) AS t(mov)\n WHERE (mov->>'line_no') IS NOT NULL\n ) m\n WHERE i.tenant_id = v_tenant AND i.id = m.item_id;\n END IF;\n\n -- \u2500\u2500 O t\u00EDtulo \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 -- Total = soma das PARCELAS (regra da financial/084). Nota sem duplicata vira\n -- uma parcela s\u00F3, no vencimento que a nota declarar ou na emiss\u00E3o.\n SELECT coalesce(sum(value), 0),\n coalesce(jsonb_agg(jsonb_build_object(\n 'number', coalesce(duplicate_index, 1),\n 'due_date', coalesce(due_date, v_doc.issue_date, current_date),\n 'amount', round(value, 2),\n 'metadata', jsonb_strip_nulls(jsonb_build_object(\n 'barcode', barcode, 'pixKey', pix_key, 'paymentType', payment_type,\n 'danfeNumber', number))) ORDER BY duplicate_index), '[]'::jsonb)\n INTO v_total, v_insts\n FROM public.plg_fiscalbr_inbox_duplicates\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;\n\n IF jsonb_array_length(v_insts) = 0 THEN\n v_total := round(coalesce(v_doc.total_value, 0), 2);\n v_insts := jsonb_build_array(jsonb_build_object(\n 'number', 1, 'due_date', coalesce(v_doc.issue_date, current_date), 'amount', v_total));\n ELSE\n v_total := round(v_total, 2);\n END IF;\n\n -- A MESMA chave da 084: nota j\u00E1 transformada em t\u00EDtulo pelo porte hist\u00F3rico\n -- n\u00E3o ganha um segundo aqui.\n v_key := 'danfe:' || v_doc.access_key;\n\n v_inv := public.financial_create_invoice(\n 'payable', v_total, v_doc.supplier_party_id, NULL, v_doc.unit_id, v_insts,\n v_doc.issue_date,\n 'NF-e ' || coalesce(v_doc.nfe_number, '?') || ' \u00B7 ' || coalesce(v_doc.supplier_name, 'fornecedor'),\n NULL,\n jsonb_build_object(\n 'source', 'danfe',\n 'danfe', jsonb_strip_nulls(jsonb_build_object(\n 'accessKey', v_doc.access_key, 'number', v_doc.nfe_number,\n 'series', v_doc.nfe_series, 'supplierName', v_doc.supplier_name,\n 'supplierCnpj', v_doc.supplier_cnpj,\n -- O total da NOTA continua leg\u00EDvel, mesmo quando difere do total do\n -- t\u00EDtulo. \u00C9 a diferen\u00E7a que a 084 mediu em 6,3% das notas.\n 'noteTotal', v_doc.total_value,\n 'inboxDocumentId', v_doc.id))),\n v_key);\n v_invoice := (v_inv->>'invoice_id')::uuid;\n\n -- As linhas do t\u00EDtulo: TODOS os itens, inclusive despesa e servi\u00E7o. \u00C9 o que\n -- responde \u00ABde que essa conta \u00E9 feita\u00BB.\n INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)\n SELECT v_tenant, v_invoice, i.description, i.quantity_commercial, i.unit_price_commercial,\n round(coalesce(i.total_price, 0), 2), i.item_number,\n jsonb_strip_nulls(jsonb_build_object(\n 'itemKind', i.entry_type, 'referenceId', i.product_id,\n 'chartAccountId', i.chart_account_id, 'ncm', i.ncm, 'cfop', i.cfop,\n 'supplierProductCode', i.supplier_product_code))\n FROM public.plg_fiscalbr_inbox_items i\n WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id\n ORDER BY i.item_number\n ON CONFLICT DO NOTHING;\n\n UPDATE public.plg_fiscalbr_inbox_documents SET\n invoice_id = v_invoice,\n inventory_operation_id = coalesce((v_recv->>'operation_id')::uuid, inventory_operation_id),\n stocked_at = CASE WHEN v_stock_n > 0 THEN now() ELSE stocked_at END,\n status = 'accepted', updated_at = now()\n WHERE id = v_doc.id;\n\n -- \u2500\u2500 O de-para aprende \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 -- A decis\u00E3o desta nota \u00E9 o que faz a pr\u00F3xima do mesmo fornecedor chegar\n -- pronta. Guardar isto \u00E9 o que separa \u00ABimportei XML\u00BB de \u00ABo sistema sabe o meu\n -- fornecedor\u00BB.\n INSERT INTO public.plg_fiscalbr_supplier_product_map (\n tenant_id, supplier_cnpj, supplier_product_code, product_id, entry_type,\n chart_account_id, stock_unit_id, conversion_factor, cost_mode, hit_count, last_used_at)\n SELECT v_tenant, v_doc.supplier_cnpj, i.supplier_product_code, i.product_id,\n i.entry_type, coalesce(i.chart_account_id, v_doc.default_chart_account_id),\n i.stock_unit_id, i.conversion_factor, i.cost_mode, 1, now()\n FROM public.plg_fiscalbr_inbox_items i\n WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id\n AND i.product_id IS NOT NULL\n AND coalesce(v_doc.supplier_cnpj, '') <> ''\n AND coalesce(i.supplier_product_code, '') <> ''\n ON CONFLICT (tenant_id, supplier_cnpj, supplier_product_code) DO UPDATE SET\n product_id = EXCLUDED.product_id,\n entry_type = EXCLUDED.entry_type,\n chart_account_id = coalesce(EXCLUDED.chart_account_id, public.plg_fiscalbr_supplier_product_map.chart_account_id),\n stock_unit_id = coalesce(EXCLUDED.stock_unit_id, public.plg_fiscalbr_supplier_product_map.stock_unit_id),\n conversion_factor = EXCLUDED.conversion_factor,\n cost_mode = EXCLUDED.cost_mode,\n hit_count = public.plg_fiscalbr_supplier_product_map.hit_count + 1,\n last_used_at = now(),\n updated_at = now();\n\n RETURN jsonb_build_object(\n 'document_id', v_doc.id, 'already_accepted', false,\n 'invoice_id', v_invoice, 'invoice_total', v_total,\n 'installments', jsonb_array_length(v_insts),\n 'operation_id', v_recv->>'operation_id',\n 'stock_lines', v_stock_n,\n 'movements', coalesce(v_recv->'movements', '[]'::jsonb));\nEND $function$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) TO authenticated;\n\n-- \u2500\u2500 Rejeitar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- O rodap\u00E9 do di\u00E1logo do V1 tem dois bot\u00F5es, e o outro tamb\u00E9m precisa existir:\n-- recusar a nota \u00E9 dizer que ela n\u00E3o vira nada aqui \u2014 sem apagar o documento,\n-- que \u00E9 fiscal e tem de continuar leg\u00EDvel.\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_reject_inbox_document(\n p_document_id uuid, p_reason text DEFAULT NULL)\nRETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\nBEGIN\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = v_tenant AND id = p_document_id;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr: nota % n\u00E3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission('financial.edit', v_doc.unit_id);\n\n IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN\n RAISE EXCEPTION 'fiscalbr: nota % j\u00E1 virou estoque ou t\u00EDtulo \u2014 recusar n\u00E3o desfaz', p_document_id\n USING ERRCODE = '23514';\n END IF;\n\n UPDATE public.plg_fiscalbr_inbox_documents SET\n status = 'rejected',\n metadata = metadata || jsonb_strip_nulls(jsonb_build_object('rejectReason', p_reason)),\n reviewed_at = now(), updated_at = now()\n WHERE id = v_doc.id;\n\n RETURN jsonb_build_object('document_id', v_doc.id, 'status', 'rejected');\nEND $function$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_reject_inbox_document(uuid, text) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_reject_inbox_document(uuid, text) TO authenticated;\n";
11
+ export declare const MIGRATION_010_A_DATA_DE_EMISSAO_E_INSTANTE_E_A_COMPETENCIA_E_DIA = "-- ---------------------------------------------------------------------------\n-- 010_a_data_de_emissao_e_instante_e_a_competencia_e_dia.sql\n--\n-- A 009 n\u00E3o conseguia aceitar nota nenhuma. Descoberto no primeiro teste com\n-- usu\u00E1rio logado de verdade, que \u00E9 onde ela ia ser usada:\n--\n-- POST rpc/fiscalbr_accept_inbox_document \u2192 404\n-- function public.financial_create_invoice(unknown, numeric, uuid, unknown,\n-- uuid, jsonb, timestamp with time zone, text, unknown, jsonb, text)\n-- does not exist\n--\n-- `plg_fiscalbr_inbox_documents.issue_date` \u00E9 **timestamptz** \u2014 a NF-e declara um\n-- INSTANTE (`dhEmi`, com fuso), e a 002 preservou isso. Eu escrevi a chamada\n-- supondo `date`, e `financial_create_invoice` pede `p_competence_date date`.\n--\n-- Nenhum teste pegou porque nenhum podia: o SQL aplica, a fun\u00E7\u00E3o compila (o corpo\n-- de PL/pgSQL n\u00E3o resolve assinatura em CREATE), e o erro s\u00F3 existe na chamada.\n-- Foi preciso um usu\u00E1rio logado percorrendo o caminho.\n--\n-- \u2500\u2500 Duas corre\u00E7\u00F5es, n\u00E3o uma \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-- 1. `issue_date::date` onde a compet\u00EAncia \u00E9 pedida. Instante \u00E9 quando a nota foi\n-- emitida; compet\u00EAncia \u00E9 o DIA a que o t\u00EDtulo pertence, e o m\u00EAs do t\u00EDtulo vem\n-- dela \u2014 um `timestamptz` viajando para l\u00E1 levaria fuso para dentro de uma\n-- conta cont\u00E1bil.\n--\n-- 2. **Todo argumento tipado na chamada.** A chamada posicional passava `'payable'`\n-- e `NULL` sem tipo, e o Postgres tem de adivinhar entre as sobrecargas. Adivinhar\n-- certo hoje e errado quando algu\u00E9m acrescentar uma sobrecarga \u00E9 o defeito que\n-- volta meses depois sem ningu\u00E9m mexer neste arquivo. Agora vai `::text`,\n-- `::uuid`, `::date` em tudo.\n--\n-- Idempotente: s\u00F3 substitui o corpo de uma fun\u00E7\u00E3o.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_accept_inbox_document(\n p_document_id uuid,\n p_location_id uuid DEFAULT NULL)\nRETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\n v_loc uuid;\n v_lines jsonb := '[]'::jsonb;\n v_insts jsonb := '[]'::jsonb;\n v_recv jsonb;\n v_inv jsonb;\n v_it record;\n v_no integer := 0;\n v_total numeric := 0;\n v_stock_n integer := 0;\n v_key text;\n v_invoice uuid;\n v_item_ids uuid[] := '{}';\n -- A compet\u00EAncia: o DIA da emiss\u00E3o, n\u00E3o o instante.\n v_competence date;\nBEGIN\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = v_tenant AND id = p_document_id;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr: nota % n\u00E3o encontrada neste tenant', p_document_id USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission('financial.create', v_doc.unit_id);\n\n IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN\n RETURN jsonb_build_object('document_id', v_doc.id, 'already_accepted', true,\n 'invoice_id', v_doc.invoice_id, 'operation_id', v_doc.inventory_operation_id);\n END IF;\n\n v_loc := coalesce(p_location_id, v_doc.default_location_id);\n v_competence := (v_doc.issue_date)::date;\n\n IF EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_items\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id\n AND (entry_type IS NULL OR entry_type = '')) THEN\n RAISE EXCEPTION 'fiscalbr: h\u00E1 item sem tipo de registro \u2014 conciliar antes de aceitar'\n USING ERRCODE = '23514';\n END IF;\n\n FOR v_it IN\n SELECT * FROM public.plg_fiscalbr_inbox_items\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id\n AND entry_type IN ('insumo','revenda','patrimonio')\n ORDER BY item_number\n LOOP\n IF v_it.product_id IS NULL THEN\n RAISE EXCEPTION 'fiscalbr: item % \u00E9 % e n\u00E3o tem produto vinculado', v_it.item_number, v_it.entry_type\n USING ERRCODE = '23514';\n END IF;\n v_no := v_no + 1;\n v_item_ids := v_item_ids || v_it.id;\n v_lines := v_lines || jsonb_build_object(\n 'product_id', v_it.product_id,\n 'quantity', coalesce(v_it.stock_quantity, v_it.quantity_commercial * v_it.conversion_factor),\n 'unit_cost', coalesce(v_it.unit_cost_new, v_it.unit_price_commercial / v_it.conversion_factor),\n 'measurement_unit_id', v_it.stock_unit_id,\n 'notes', v_it.description);\n END LOOP;\n v_stock_n := v_no;\n\n IF v_stock_n > 0 THEN\n IF v_loc IS NULL THEN\n RAISE EXCEPTION 'fiscalbr: a nota tem % linha(s) de estoque e nenhum local de estoque escolhido', v_stock_n\n USING ERRCODE = '22023';\n END IF;\n v_recv := public.inventory_receive(\n v_loc::uuid,\n v_lines::jsonb,\n jsonb_build_object(\n 'supplier_id', v_doc.supplier_party_id,\n -- O documento do estoque tamb\u00E9m quer o DIA.\n 'date', v_competence,\n 'type', 'nfe',\n 'ref', 'NF-e ' || coalesce(v_doc.nfe_number, '?'),\n 'notes', 'Entrada via DANFE NF-e ' || coalesce(v_doc.nfe_number, '?')),\n ('nfe:' || v_doc.access_key)::text);\n\n UPDATE public.plg_fiscalbr_inbox_items i\n SET stock_movement_id = m.movement_id, updated_at = now()\n FROM (\n SELECT v_item_ids[(mov->>'line_no')::int] AS item_id, (mov->>'id')::uuid AS movement_id\n FROM jsonb_array_elements(coalesce(v_recv->'movements', '[]'::jsonb)) AS t(mov)\n WHERE (mov->>'line_no') IS NOT NULL\n ) m\n WHERE i.tenant_id = v_tenant AND i.id = m.item_id;\n END IF;\n\n SELECT coalesce(sum(value), 0),\n coalesce(jsonb_agg(jsonb_build_object(\n 'number', coalesce(duplicate_index, 1),\n 'due_date', coalesce(due_date, v_competence, current_date),\n 'amount', round(value, 2),\n 'metadata', jsonb_strip_nulls(jsonb_build_object(\n 'barcode', barcode, 'pixKey', pix_key, 'paymentType', payment_type,\n 'danfeNumber', number))) ORDER BY duplicate_index), '[]'::jsonb)\n INTO v_total, v_insts\n FROM public.plg_fiscalbr_inbox_duplicates\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id;\n\n IF jsonb_array_length(v_insts) = 0 THEN\n v_total := round(coalesce(v_doc.total_value, 0), 2);\n v_insts := jsonb_build_array(jsonb_build_object(\n 'number', 1, 'due_date', coalesce(v_competence, current_date), 'amount', v_total));\n ELSE\n v_total := round(v_total, 2);\n END IF;\n\n v_key := 'danfe:' || v_doc.access_key;\n\n -- TODO argumento tipado. A chamada posicional sem tipo obrigava o Postgres a\n -- adivinhar entre sobrecargas, e foi assim que esta fun\u00E7\u00E3o nasceu quebrada.\n v_inv := public.financial_create_invoice(\n 'payable'::text,\n v_total::numeric,\n v_doc.supplier_party_id::uuid,\n NULL::uuid,\n v_doc.unit_id::uuid,\n v_insts::jsonb,\n v_competence::date,\n ('NF-e ' || coalesce(v_doc.nfe_number, '?') || ' \u00B7 ' || coalesce(v_doc.supplier_name, 'fornecedor'))::text,\n NULL::text,\n jsonb_build_object(\n 'source', 'danfe',\n 'danfe', jsonb_strip_nulls(jsonb_build_object(\n 'accessKey', v_doc.access_key, 'number', v_doc.nfe_number,\n 'series', v_doc.nfe_series, 'supplierName', v_doc.supplier_name,\n 'supplierCnpj', v_doc.supplier_cnpj,\n 'noteTotal', v_doc.total_value,\n 'inboxDocumentId', v_doc.id)))::jsonb,\n v_key::text);\n v_invoice := (v_inv->>'invoice_id')::uuid;\n\n INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)\n SELECT v_tenant, v_invoice, i.description, i.quantity_commercial, i.unit_price_commercial,\n round(coalesce(i.total_price, 0), 2), i.item_number,\n jsonb_strip_nulls(jsonb_build_object(\n 'itemKind', i.entry_type, 'referenceId', i.product_id,\n 'chartAccountId', i.chart_account_id, 'ncm', i.ncm, 'cfop', i.cfop,\n 'supplierProductCode', i.supplier_product_code))\n FROM public.plg_fiscalbr_inbox_items i\n WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id\n ORDER BY i.item_number\n ON CONFLICT DO NOTHING;\n\n UPDATE public.plg_fiscalbr_inbox_documents SET\n invoice_id = v_invoice,\n inventory_operation_id = coalesce((v_recv->>'operation_id')::uuid, inventory_operation_id),\n stocked_at = CASE WHEN v_stock_n > 0 THEN now() ELSE stocked_at END,\n status = 'accepted', updated_at = now()\n WHERE id = v_doc.id;\n\n INSERT INTO public.plg_fiscalbr_supplier_product_map (\n tenant_id, supplier_cnpj, supplier_product_code, product_id, entry_type,\n chart_account_id, stock_unit_id, conversion_factor, cost_mode, hit_count, last_used_at)\n SELECT v_tenant, v_doc.supplier_cnpj, i.supplier_product_code, i.product_id,\n i.entry_type, coalesce(i.chart_account_id, v_doc.default_chart_account_id),\n i.stock_unit_id, i.conversion_factor, i.cost_mode, 1, now()\n FROM public.plg_fiscalbr_inbox_items i\n WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id\n AND i.product_id IS NOT NULL\n AND coalesce(v_doc.supplier_cnpj, '') <> ''\n AND coalesce(i.supplier_product_code, '') <> ''\n ON CONFLICT (tenant_id, supplier_cnpj, supplier_product_code) DO UPDATE SET\n product_id = EXCLUDED.product_id,\n entry_type = EXCLUDED.entry_type,\n chart_account_id = coalesce(EXCLUDED.chart_account_id, public.plg_fiscalbr_supplier_product_map.chart_account_id),\n stock_unit_id = coalesce(EXCLUDED.stock_unit_id, public.plg_fiscalbr_supplier_product_map.stock_unit_id),\n conversion_factor = EXCLUDED.conversion_factor,\n cost_mode = EXCLUDED.cost_mode,\n hit_count = public.plg_fiscalbr_supplier_product_map.hit_count + 1,\n last_used_at = now(),\n updated_at = now();\n\n RETURN jsonb_build_object(\n 'document_id', v_doc.id, 'already_accepted', false,\n 'invoice_id', v_invoice, 'invoice_total', v_total,\n 'installments', jsonb_array_length(v_insts),\n 'operation_id', v_recv->>'operation_id',\n 'stock_lines', v_stock_n,\n 'movements', coalesce(v_recv->'movements', '[]'::jsonb));\nEND $function$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_accept_inbox_document(uuid, uuid) TO authenticated;\n";
6
12
  export declare const MIGRATIONS: Array<{
7
13
  id: string;
8
14
  sql: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,2g0BA2jBlC,CAAA;AAED,eAAO,MAAM,0DAA0D,uzRAmMtE,CAAA;AAED,eAAO,MAAM,+CAA+C,m5oBAyW3D,CAAA;AAED,eAAO,MAAM,0CAA0C,o1XAgPtD,CAAA;AAED,eAAO,MAAM,8CAA8C,6qCAsB1D,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAMzD,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,2g0BA2jBlC,CAAA;AAED,eAAO,MAAM,0DAA0D,uzRAmMtE,CAAA;AAED,eAAO,MAAM,+CAA+C,m5oBAyW3D,CAAA;AAED,eAAO,MAAM,0CAA0C,o1XAgPtD,CAAA;AAED,eAAO,MAAM,8CAA8C,6qCAsB1D,CAAA;AAED,eAAO,MAAM,2CAA2C,w8dA6MvD,CAAA;AAED,eAAO,MAAM,yCAAyC,42BAkBrD,CAAA;AAED,eAAO,MAAM,oDAAoD,4oDA+BhE,CAAA;AAED,eAAO,MAAM,yDAAyD,otbA0NrE,CAAA;AAED,eAAO,MAAM,gEAAgE,q1+BAoiB5E,CAAA;AAED,eAAO,MAAM,gEAAgE,mqVAgO5E,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAYzD,CAAA"}
package/dist/types.d.ts CHANGED
@@ -52,4 +52,139 @@ export interface PlugNotasFinancialProviderOptions {
52
52
  base: import('@fayz-ai/plugin-financial').FinancialDataProvider;
53
53
  gateway: PlugNotasFiscalGateway;
54
54
  }
55
+ /** Os cinco tipos de registro do V1, medidos em 3.402 linhas de produção. */
56
+ export type InboxEntryType = 'insumo' | 'revenda' | 'patrimonio' | 'despesa' | 'servico';
57
+ /** Despesa e serviço nunca viram estoque: viram só título a pagar. */
58
+ export declare const STOCK_BEARING_ENTRY_TYPES: readonly InboxEntryType[];
59
+ export declare const isStockBearing: (type: string | null | undefined) => boolean;
60
+ export type InboxCostMode = 'novo' | 'media';
61
+ export type InboxStatusFilter = 'all' | 'pending' | 'stocked' | 'rejected' | 'to_manifest';
62
+ export interface InboxNote {
63
+ id: string;
64
+ accessKey: string;
65
+ number: string;
66
+ series: string;
67
+ issueDate: string | null;
68
+ receivedAt: string | null;
69
+ status: string;
70
+ manifestationStatus: string | null;
71
+ manifestedAt: string | null;
72
+ manifestationAttempts: number;
73
+ natureOperation: string | null;
74
+ supplierName: string;
75
+ supplierCnpj: string | null;
76
+ supplierPartyId: string | null;
77
+ /** O total declarado na NOTA. */
78
+ total: number;
79
+ itemCount: number;
80
+ reconciledCount: number;
81
+ instalmentCount: number;
82
+ /** A soma das PARCELAS — é com este total que o título nasce (financial/084). */
83
+ instalmentTotal: number;
84
+ invoiceId: string | null;
85
+ operationId: string | null;
86
+ stockedAt: string | null;
87
+ documentId: string | null;
88
+ unitId: string | null;
89
+ }
90
+ export interface InboxItem {
91
+ id: string;
92
+ itemNumber: number;
93
+ supplierProductCode: string;
94
+ eanBarcode: string | null;
95
+ description: string;
96
+ ncm: string | null;
97
+ cest: string | null;
98
+ cfop: string | null;
99
+ unit: string | null;
100
+ quantity: number;
101
+ unitPrice: number;
102
+ total: number;
103
+ discount: number | null;
104
+ icmsCst: string | null;
105
+ icmsCsosn: string | null;
106
+ icmsValue: number | null;
107
+ productId: string | null;
108
+ entryType: string | null;
109
+ chartAccountId: string | null;
110
+ stockUnitId: string | null;
111
+ conversionFactor: number;
112
+ /** Quantidade × fator, menos a falta. É o número que a tela mostra ANTES de aceitar. */
113
+ stockQuantity: number;
114
+ costMode: string;
115
+ unitCostNew: number | null;
116
+ reconciled: boolean;
117
+ stockMovementId: string | null;
118
+ shortageQuantity: number | null;
119
+ shortageReason: string | null;
120
+ shortageNote: string | null;
121
+ }
122
+ export interface InboxInstalment {
123
+ id: string;
124
+ index: number;
125
+ number: string | null;
126
+ dueDate: string | null;
127
+ amount: number;
128
+ paymentType: string | null;
129
+ barcode: string | null;
130
+ pixKey: string | null;
131
+ paymentMethodId: string | null;
132
+ }
133
+ export interface InboxNoteDetail extends InboxNote {
134
+ defaultChartAccountId: string | null;
135
+ defaultLocationId: string | null;
136
+ items: InboxItem[];
137
+ instalments: InboxInstalment[];
138
+ /** `pag > detPag` do XML: o que o fornecedor DECLAROU, não o que se decidiu. */
139
+ declaredPayments: Array<{
140
+ index: number;
141
+ code: string | null;
142
+ label: string | null;
143
+ value: number;
144
+ }>;
145
+ }
146
+ export interface StageResult {
147
+ documentId: string;
148
+ accessKey: string;
149
+ alreadyAccepted: boolean;
150
+ supplierPartyId: string | null;
151
+ itemCount: number;
152
+ /** Quantos itens o de-para do fornecedor já vinculou sozinho. */
153
+ matchedCount: number;
154
+ }
155
+ export interface AcceptResult {
156
+ documentId: string;
157
+ /** `true` é o segundo clique, não um erro: a RPC é idempotente na chave de acesso. */
158
+ alreadyAccepted: boolean;
159
+ invoiceId: string | null;
160
+ invoiceTotal: number;
161
+ instalments: number;
162
+ operationId: string | null;
163
+ stockLines: number;
164
+ }
165
+ export interface ProductOption {
166
+ id: string;
167
+ name: string;
168
+ sku: string | null;
169
+ unit: string | null;
170
+ cost: number | null;
171
+ kind: string | null;
172
+ }
173
+ export interface MeasurementUnitOption {
174
+ id: string;
175
+ name: string;
176
+ abbreviation: string;
177
+ isBase: boolean;
178
+ }
179
+ export interface ChartAccountOption {
180
+ id: string;
181
+ code: string;
182
+ name: string;
183
+ kind: string | null;
184
+ }
185
+ export interface StockLocationOption {
186
+ id: string;
187
+ name: string;
188
+ unitId: string | null;
189
+ }
55
190
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,cAAc,CAAA;AAC7D,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAA;AAE/F,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,oBAAoB,CAAA;IACjC,gBAAgB,EAAE,OAAO,CAAA;IACzB,QAAQ,EAAE,OAAO,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,uBAAuB,CAAA;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAA;IAC1F,OAAO,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAA;IACpE,MAAM,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAA;IACnF,QAAQ,CAAC,KAAK,EAAE;QACd,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,kBAAkB,CAAA;KACzC,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,CAAC,KAAK,EAAE;QAClB,WAAW,EAAE,MAAM,CAAA;QACnB,WAAW,EAAE,oBAAoB,CAAA;KAClC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9C,iBAAiB,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,iCAAiC;IAChD,IAAI,EAAE,OAAO,2BAA2B,EAAE,qBAAqB,CAAA;IAC/D,OAAO,EAAE,sBAAsB,CAAA;CAChC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { ComingSoon, EmBreve, type ComingSoonProps } from '@fayz-ai/ui';
2
+ //# sourceMappingURL=ComingSoon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComingSoon.d.ts","sourceRoot":"","sources":["../../src/views/ComingSoon.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ /**
3
+ * Grupos Fiscais — a 5ª tela do grupo no V1. Na V2 o grupo fiscal já existe como
4
+ * CADASTRO (`fiscalRegistries`), só não tem porta aqui dentro.
5
+ */
6
+ export declare function TaxGroupsView({ onBack }?: {
7
+ onBack?: () => void;
8
+ }): React.JSX.Element;
9
+ /** Notas Emitidas — no V1 FUNCIONA, e é o gap maior dos dois. */
10
+ export declare function IssuedNotesView({ onBack }?: {
11
+ onBack?: () => void;
12
+ }): React.JSX.Element;
13
+ /** Notas Devolvidas — no V1 é STUB, e a tela diz isso. */
14
+ export declare function ReturnedNotesView({ onBack }?: {
15
+ onBack?: () => void;
16
+ }): React.JSX.Element;
17
+ //# sourceMappingURL=GapScreens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GapScreens.d.ts","sourceRoot":"","sources":["../../src/views/GapScreens.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,qBAgBrE;AAED,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,qBAiBvE;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,qBAiBzE"}