@fayz-ai/plugin-fiscal-br 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -89,6 +89,7 @@ export declare function parseNfeXml(xml: string): ParsedNfe;
89
89
  export declare function toStagePayload(note: ParsedNfe, options?: {
90
90
  unitId?: string | null;
91
91
  source?: string;
92
+ xml?: string;
92
93
  }): {
93
94
  access_key: string;
94
95
  nfe_number: string;
@@ -150,5 +151,6 @@ export declare function toStagePayload(note: ParsedNfe, options?: {
150
151
  label: string | null;
151
152
  value: number;
152
153
  }[];
154
+ xml?: string | undefined;
153
155
  };
154
156
  //# sourceMappingURL=nfe-xml.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nfe-xml.d.ts","sourceRoot":"","sources":["../../src/lib/nfe-xml.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,WAAW,CAAA;IACrB,MAAM,EAAE,SAAS,CAAA;IACjB,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,UAAU,EAAE,YAAY,EAAE,CAAA;IAC1B,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AA8ID,2DAA2D;AAC3D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGpE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,MAA2C,CAAA;AAIlG;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CA+IlD;AAQD,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiExG"}
1
+ {"version":3,"file":"nfe-xml.d.ts","sourceRoot":"","sources":["../../src/lib/nfe-xml.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,WAAW,CAAA;IACrB,MAAM,EAAE,SAAS,CAAA;IACjB,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,UAAU,EAAE,YAAY,EAAE,CAAA;IAC1B,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AA8ID,2DAA2D;AAC3D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGpE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,MAA2C,CAAA;AAIlG;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CA+IlD;AAQD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,EACf,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsExE"}
@@ -0,0 +1,196 @@
1
+ import type { ChartAccountOption, InboxEntryType, InboxItem, MeasurementUnitOption } from '../types';
2
+ /** Quantas peças a unidade comercial declara, quando ela declara. */
3
+ export declare function packagingCount(commercialUnit: string | null | undefined): number | null;
4
+ export declare const isCountingUnit: (abbreviation: string | null | undefined) => boolean;
5
+ /**
6
+ * O FATOR QUE O XML JÁ DIZ — a evidência mais forte, e é uma divisão.
7
+ *
8
+ * `uTrib`/`qTrib` são a unidade e a quantidade TRIBUTÁVEIS da NF-e: o fornecedor
9
+ * vende em CAIXA mas declara ao fisco em KG. Quando a unidade tributável é a
10
+ * unidade em que a casa guarda, o fator não é palpite nenhum:
11
+ *
12
+ * 4 CX com qTrib 88,224 KG → fator = 88,224 ÷ 4 = 22,056
13
+ *
14
+ * É a regra nº 1 do prompt do V1, e era a que faltava virar conta: o caso
15
+ * Artorius (carne a R$ 397/kg em vez de ~R$ 18) foi esta divisão não ter sido
16
+ * feita. Devolve `null` quando a unidade tributável é outra coisa — aí só a
17
+ * descrição responde, e isso é julgamento, não aritmética.
18
+ */
19
+ export declare function taxableFactor(item: Pick<InboxItem, 'unit' | 'quantity' | 'unitTaxable' | 'quantityTaxable'>, stockUnitAbbreviation: string | null | undefined): number | null;
20
+ /**
21
+ * O FATOR QUE OS DADOS JÁ DIZEM, aplicado depois da resposta do modelo.
22
+ *
23
+ * Prompt é pedido, não garantia. Duas evidências aqui são aritmética e por isso
24
+ * não se delegam:
25
+ *
26
+ * ① `uTrib`/`qTrib` batendo com a unidade de estoque → uma divisão (ver
27
+ * `taxableFactor`). Ganha de tudo, inclusive do que o modelo respondeu.
28
+ * ② embalagem que CONTA peça (MI, DZ, CENTO) → o número está na sigla. Só
29
+ * entra quando o modelo não calculou nada (fator 1).
30
+ *
31
+ * ENTRE 1 E 1.000, O PADRÃO É 1.000. «Milheiro» significa mil peças: é o que a
32
+ * palavra diz, e discordar dela exige evidência. A primeira versão disto era
33
+ * conservadora no NÚMERO — mantinha 1 quando não dava para confirmar a unidade de
34
+ * estoque — e conservadorismo no número é o pior lugar para ele:
35
+ *
36
+ * 1 MI a R$ 616,00 fator 1 → 1 saco a R$ 616,00 cada (absurdo)
37
+ * fator 1000 → 1.000 sacos a R$ 0,616 (certo)
38
+ *
39
+ * Errar para 1 não é cauteloso, é 1.000x errado em silêncio. O cuidado mora no
40
+ * PORTÃO: a tela não deixa aceitar enquanto alguém não confirmar a unidade de
41
+ * estoque, e a confiança baixa marca a linha como «confira».
42
+ *
43
+ * A exceção continua sendo o estoque em PESO: ninguém sabe quanto pesa o
44
+ * milheiro, e ali 1.000 seria um chute de outra natureza. Aí o fator fica como
45
+ * está e sai aviso.
46
+ */
47
+ export declare function enforceFactorEvidence(result: NoteSuggestionResult, items: Array<Pick<InboxItem, 'id' | 'unit' | 'description' | 'quantity' | 'unitTaxable' | 'quantityTaxable'>>, units: Array<Pick<MeasurementUnitOption, 'id' | 'abbreviation'>>): NoteSuggestionResult;
48
+ export interface NoteItemSuggestion {
49
+ itemId: string;
50
+ entryType: InboxEntryType | null;
51
+ productId: string | null;
52
+ /** O nome que o modelo propõe quando nada casa. A tela ainda não cria produto. */
53
+ newProductName: string | null;
54
+ stockUnitId: string | null;
55
+ conversionFactor: number;
56
+ chartAccountId: string | null;
57
+ /** 0..1. Abaixo de 0,6 a tela marca a linha como «confira». */
58
+ confidence: number;
59
+ }
60
+ export interface NoteSuggestionResult {
61
+ items: NoteItemSuggestion[];
62
+ warnings: string[];
63
+ }
64
+ /** Um produto que o índice ofereceu para UMA linha da nota (migration 012). */
65
+ export interface SuggestionCandidate {
66
+ id: string;
67
+ name: string;
68
+ sku?: string | null;
69
+ /** A unidade do PRODUTO. É ela que vira unidade de estoque — ver `resolveStockUnit`. */
70
+ unit?: string | null;
71
+ }
72
+ /** A tradução de volta: o que cada número daquele lote significa. */
73
+ export interface SuggestionCodebook {
74
+ items: InboxItem[];
75
+ /** Candidatos na ordem em que foram numerados, por item (mesmo índice de `items`). */
76
+ candidates: SuggestionCandidate[][];
77
+ accounts: ChartAccountOption[];
78
+ units: MeasurementUnitOption[];
79
+ }
80
+ export interface SuggestionNoteHeader {
81
+ number: string;
82
+ supplierName: string;
83
+ natureOperation: string | null;
84
+ }
85
+ /**
86
+ * Teto da mensagem por lote, em caracteres.
87
+ *
88
+ * Folgado porque agora o teto é o do MODELO e não o de um broker de chat: a
89
+ * function fala direto com o provedor. 24 KB de pergunta cabem com sobra na
90
+ * janela e ainda deixam a resposta inteira voltar.
91
+ */
92
+ export declare const MESSAGE_BUDGET = 24000;
93
+ export declare const SUGGESTION_PROMPT: string;
94
+ /**
95
+ * O schema da tool. O V1 forçava `tool_choice`, e é isso que mantém a resposta
96
+ * sendo dado em vez de prosa — texto livre voltava «Claro! Vou analisar os
97
+ * itens…» e o parser ficava sem nada para ler.
98
+ */
99
+ export declare const SUGGESTION_TOOL: {
100
+ readonly type: "function";
101
+ readonly function: {
102
+ readonly name: "aplicar_sugestoes";
103
+ readonly description: "Associações sugeridas para cada item numerado da NF-e";
104
+ readonly parameters: {
105
+ readonly type: "object";
106
+ readonly properties: {
107
+ readonly r: {
108
+ readonly type: "array";
109
+ readonly items: {
110
+ readonly type: "object";
111
+ readonly properties: {
112
+ readonly it: {
113
+ readonly type: "integer";
114
+ readonly description: "número do item";
115
+ };
116
+ readonly tipo: {
117
+ readonly type: "string";
118
+ readonly enum: readonly ["insumo", "revenda", "patrimonio", "despesa", "servico"];
119
+ };
120
+ readonly c: {
121
+ readonly type: readonly ["integer", "null"];
122
+ readonly description: "número do candidato daquele item";
123
+ };
124
+ readonly novo: {
125
+ readonly type: readonly ["string", "null"];
126
+ readonly description: "nome do produto a criar";
127
+ };
128
+ readonly a: {
129
+ readonly type: readonly ["integer", "null"];
130
+ readonly description: "número da conta";
131
+ };
132
+ readonly f: {
133
+ readonly type: "number";
134
+ readonly description: "fator de conversão";
135
+ };
136
+ readonly conf: {
137
+ readonly type: "number";
138
+ readonly description: "0 a 1";
139
+ };
140
+ };
141
+ readonly required: readonly ["it", "tipo", "f", "conf"];
142
+ };
143
+ };
144
+ readonly avisos: {
145
+ readonly type: "array";
146
+ readonly items: {
147
+ readonly type: "string";
148
+ };
149
+ };
150
+ };
151
+ readonly required: readonly ["r"];
152
+ };
153
+ };
154
+ };
155
+ /**
156
+ * A UNIDADE DE ESTOQUE É A DO PRODUTO, resolvida aqui e não pelo modelo.
157
+ *
158
+ * Quando o produto não declara unidade, fica nulo — e nulo é honesto, porque o
159
+ * campo continua na tela esperando alguém.
160
+ */
161
+ export declare function resolveStockUnit(candidate: SuggestionCandidate | undefined, units: MeasurementUnitOption[]): string | null;
162
+ /** O corpo da pergunta de UM lote, com tudo numerado. */
163
+ export declare function buildSuggestionPayload(book: SuggestionCodebook, note: SuggestionNoteHeader): string;
164
+ /** Quantos caracteres a pergunta deste lote tem. */
165
+ export declare const suggestionSize: (book: SuggestionCodebook, note: SuggestionNoteHeader) => number;
166
+ /**
167
+ * Divide os itens em lotes que CABEM, medindo cada um.
168
+ *
169
+ * Divisão binária e não «de N em N»: o custo de um item varia de 60 a 700
170
+ * caracteres conforme quantos candidatos o índice achou, então qualquer número
171
+ * fixo erra numa das duas direções — manda mensagem grande demais, ou multiplica
172
+ * as voltas sem precisar.
173
+ *
174
+ * Um item que sozinho não cabe volta como lote de um: cortá-lo em silêncio faria
175
+ * a resposta dizer «preenchi» sobre uma linha intocada.
176
+ */
177
+ export declare function splitToFit(input: {
178
+ items: InboxItem[];
179
+ candidates: Record<string, SuggestionCandidate[]>;
180
+ accounts: ChartAccountOption[];
181
+ units: MeasurementUnitOption[];
182
+ note: SuggestionNoteHeader;
183
+ }, budget?: number): SuggestionCodebook[];
184
+ /**
185
+ * O que o modelo respondeu, virado em sugestão com que a tela sabe lidar.
186
+ *
187
+ * Aceita o objeto já pronto (argumentos da tool) ou o texto cru: a tool é
188
+ * forçada, mas um modelo trocado amanhã pode responder em prosa, e perder a
189
+ * sugestão inteira por uma cerca de código seria barato de evitar.
190
+ *
191
+ * NÃO HÁ ALLOW-LIST DE ID porque não há id: número fora da faixa cai por
192
+ * aritmética. Exportado porque é onde os defeitos aparecem, e isso se prova sem
193
+ * rede.
194
+ */
195
+ export declare function parseNoteSuggestionReply(reply: unknown, book: SuggestionCodebook): NoteSuggestionResult;
196
+ //# sourceMappingURL=note-suggestion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"note-suggestion.d.ts","sourceRoot":"","sources":["../../src/lib/note-suggestion.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,13 @@ export declare const MIGRATION_007_A_RLS_DO_DOCUMENTO_PASSA_A_SER_LEGIVEL = "--
9
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
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
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";
12
+ export declare const MIGRATION_011_O_IMPOSTO_DO_PRODUTO_E_DO_FISCAL = "-- O imposto do PRODUTO mora no fiscal, n\u00E3o no estoque.\n--\n-- No V1 as dezessete colunas de tributo (NCM, CFOP, CST, PIS, COFINS, CEST, FCP\n-- e os dois campos novos da reforma) ficam numa tabela do m\u00F3dulo de estoque,\n-- ao lado de quantidade m\u00EDnima e temperatura de armazenamento. O sal\u00E3o que\n-- nunca emitiu uma nota carrega as dezessete do mesmo jeito.\n--\n-- Aqui elas ficam onde o assunto \u00E9: `plg_fiscalbr_tax_groups` j\u00E1 guarda a\n-- mesma lista para o GRUPO fiscal, e esta tabela \u00E9 o que um produto tem de\n-- diferente do grupo dele. Produto sem nada de fiscal n\u00E3o tem linha nenhuma, e\n-- o estoque de quem n\u00E3o emite nota n\u00E3o muda de forma.\n--\n-- `tax_group_id` \u00E9 a heran\u00E7a e as outras colunas s\u00E3o a exce\u00E7\u00E3o: NULL aqui quer\n-- dizer \u00ABvale o que o grupo diz\u00BB, n\u00E3o \u00ABzero\u00BB. Por isso nenhuma tem DEFAULT 0 \u2014\n-- um zero gravado afirmaria al\u00EDquota zero e sobrescreveria o grupo em sil\u00EAncio.\n\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_product_tax_info (\n product_id uuid PRIMARY KEY REFERENCES public.products(id) ON DELETE CASCADE,\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n tax_group_id uuid REFERENCES public.plg_fiscalbr_tax_groups(id) ON DELETE SET NULL,\n\n -- Nomenclatura Comum do Mercosul: \u00E9 do PRODUTO, nunca do grupo. \u00C9 o campo que\n -- o V1 preenche mesmo quando nenhum outro est\u00E1 preenchido.\n ncm text,\n cest text,\n icms_origin text,\n icms_rate numeric(9,4),\n cfop text,\n cst text,\n pis_situation text,\n pis_rate numeric(9,4),\n cofins_situation text,\n cofins_rate numeric(9,4),\n base_calculation_mode text,\n benefit_code text,\n poverty_fund_rate numeric(9,4),\n\n -- Reforma tribut\u00E1ria. Entram agora pelo mesmo motivo que entraram no grupo:\n -- deixar de fora obrigaria a recadastrar o cat\u00E1logo inteiro no dia em que\n -- IBS e CBS valerem.\n ibs_cbs_situation text,\n ibs_cbs_classification text,\n\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\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_product_tax_info_tenant_idx\n ON public.plg_fiscalbr_product_tax_info (tenant_id);\nCREATE INDEX IF NOT EXISTS plg_fiscalbr_product_tax_info_group_idx\n ON public.plg_fiscalbr_product_tax_info (tax_group_id);\n\nALTER TABLE public.plg_fiscalbr_product_tax_info ENABLE ROW LEVEL SECURITY;\n\n-- Quem l\u00EA e escreve \u00E9 quem cuida do CADASTRO do produto, n\u00E3o quem emite a\n-- nota: a aba Fiscal do V1 vive dentro do formul\u00E1rio do produto, e exigir\n-- permiss\u00E3o de financeiro ali esconderia a se\u00E7\u00E3o do estoquista que digita o\n-- NCM da mercadoria que acabou de chegar.\nDROP POLICY IF EXISTS plg_fiscalbr_product_tax_info_authz_select ON public.plg_fiscalbr_product_tax_info;\nCREATE POLICY plg_fiscalbr_product_tax_info_authz_select\n ON public.plg_fiscalbr_product_tax_info FOR SELECT TO authenticated\n USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('inventory.read', NULL::uuid)));\n\nDROP POLICY IF EXISTS plg_fiscalbr_product_tax_info_authz_write ON public.plg_fiscalbr_product_tax_info;\nCREATE POLICY plg_fiscalbr_product_tax_info_authz_write\n ON public.plg_fiscalbr_product_tax_info FOR ALL TO authenticated\n USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('inventory.edit', NULL::uuid)))\n WITH CHECK (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('inventory.edit', NULL::uuid)));\n\nGRANT ALL ON TABLE public.plg_fiscalbr_product_tax_info TO authenticated;\nGRANT ALL ON TABLE public.plg_fiscalbr_product_tax_info TO service_role;\n\nCOMMENT ON TABLE public.plg_fiscalbr_product_tax_info IS\n 'Tributos do produto \u2014 a exce\u00E7\u00E3o ao grupo fiscal. Espelha a aba Fiscal do cadastro de produto do V1 (product_fiscal_info). NULL herda do grupo.';\n";
13
+ export declare const MIGRATION_012_QUEM_E_O_PRODUTO_SE_PERGUNTA_AO_INDICE = "-- ---------------------------------------------------------------------------\n-- Quem \u00E9 o produto desta linha da nota: pergunta-se ao \u00EDndice, n\u00E3o ao modelo.\n-- ---------------------------------------------------------------------------\n-- A sugest\u00E3o de IA da entrada de notas mandava o CAT\u00C1LOGO para o modelo. O V1\n-- mandava 400 produtos em todo turno (`.slice(0, 400)` sobre um `.limit(800)`\n-- sem `order by` \u2014 o corte era arbitr\u00E1rio, e acima de 400 produtos a function\n-- respondia \u00ABcrie este produto\u00BB para produto cadastrado, sem aviso).\n--\n-- O custo disso \u00E9 `cat\u00E1logo \u00D7 notas`. Num tenant de 171 produtos s\u00E3o ~3,5k\n-- tokens por nota; num distribuidor de 8.000 s\u00E3o ~160k e n\u00E3o cabe no turno. E a\n-- varredura de cat\u00E1logo n\u00E3o \u00E9 trabalho de modelo: \u00E9 trabalho de \u00EDndice.\n--\n-- Esta fun\u00E7\u00E3o \u00E9 a pr\u00E9-sele\u00E7\u00E3o, em TR\u00CAS fases, e a primeira que acerta encerra a\n-- linha:\n--\n-- supplier_map o de-para do fornecedor. O que aquele CNPJ j\u00E1 ensinou n\u00E3o se\n-- pergunta de novo \u2014 e numa nota repetida isso resolve a nota\n-- inteira, ent\u00E3o a IA nem roda.\n-- ean c\u00F3digo de barras contra o cadastro. Igualdade, n\u00E3o palpite.\n-- text semelhan\u00E7a de texto, com os candidatos que sobraram.\n--\n-- `reason` \u00E9 o que a tela usa para decidir: `supplier_map` e `ean` s\u00E3o V\u00CDNCULO\n-- (liga sozinho), `text` \u00E9 CANDIDATO (vai para o modelo desambiguar). Sem essa\n-- coluna a tela trataria um EAN exato como um chute de trigrama.\n--\n-- A fase de texto N\u00C3O reimplementa busca: ela chama `public.global_search`, que\n-- j\u00E1 tem \u00EDndice GIN de trigrama, `fayz_norm()` (tira acento, min\u00FAscula, colapsa\n-- pontua\u00E7\u00E3o) e a escada de pontua\u00E7\u00E3o da casa. Reescrever isso pior era o erro\n-- que o `ILIKE` da descri\u00E7\u00E3o fatiada em 24 caracteres j\u00E1 estava cometendo do\n-- lado do cliente \u2014 `name.ilike.%SACO PLASTICO 10X15 TRAN%` n\u00E3o casa acento,\n-- ordem de palavra nem abrevia\u00E7\u00E3o, e o resultado mais comum era zero candidatos.\n--\n-- O RPC foi renomeado na 137 (`fayz_global_search` \u2192 `global_search`) e h\u00E1 pool\n-- em cada lado. Aqui se descobre o nome em tempo de execu\u00E7\u00E3o, como o cliente j\u00E1\n-- faz em `@fayz-ai/core/search`.\n--\n-- N\u00C3O \u00E9 SECURITY DEFINER. Roda como quem chamou, a RLS de `products` vale, e o\n-- tenant vem de `app.current_tenant_id()` \u2014 o cliente n\u00E3o passa tenant e por\n-- isso n\u00E3o pode passar o de outra conta.\n--\n-- `SET search_path` \u00E9 expl\u00EDcito porque `similarity`/`word_similarity` moram em\n-- `extensions`: corpo com search_path vazio passa no DDL e estoura na primeira\n-- chamada.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_match_product_candidates(\n -- [{ \"ref\": \"<id do item>\", \"text\": \"SACO PLASTICO 10X15\", \"ean\": \"789\u2026\", \"code\": \"ABC1\" }]\n p_queries jsonb,\n p_supplier_cnpj text DEFAULT NULL,\n p_per_query integer DEFAULT 8\n) RETURNS TABLE (\n ref text,\n product_id uuid,\n name text,\n sku text,\n unit text,\n cost numeric,\n score real,\n reason text\n)\nLANGUAGE plpgsql\nSTABLE\nSET search_path TO 'public', 'extensions', 'pg_temp'\nAS $fn$\nDECLARE\n v_tenant uuid := (SELECT app.current_tenant_id());\n v_cnpj text := public.fayz_digits(coalesce(p_supplier_cnpj, ''));\n v_barcodes boolean := to_regclass('public.plg_inventory_product_barcodes') IS NOT NULL;\n v_map boolean := to_regclass('public.plg_fiscalbr_supplier_product_map') IS NOT NULL;\n v_search text;\n v_q record;\n v_found uuid;\nBEGIN\n IF v_tenant IS NULL THEN RETURN; END IF;\n IF p_queries IS NULL OR jsonb_typeof(p_queries) <> 'array' THEN RETURN; END IF;\n p_per_query := least(greatest(coalesce(p_per_query, 8), 1), 50);\n\n v_search := CASE\n WHEN to_regprocedure('public.global_search(text,uuid,text[],integer,integer)') IS NOT NULL\n THEN 'public.global_search'\n WHEN to_regprocedure('public.fayz_global_search(text,uuid,text[],integer,integer)') IS NOT NULL\n THEN 'public.fayz_global_search'\n ELSE NULL\n END;\n\n FOR v_q IN\n SELECT e->>'ref' AS ref,\n coalesce(e->>'text', '') AS text,\n public.fayz_digits(coalesce(e->>'ean', '')) AS ean,\n nullif(btrim(coalesce(e->>'code', '')), '') AS code\n FROM jsonb_array_elements(p_queries) AS e\n WHERE e->>'ref' IS NOT NULL\n LOOP\n v_found := NULL;\n\n -- \u2460 O de-para do fornecedor. Aprendizado da casa, e ele ganha de tudo.\n IF v_map AND v_cnpj <> '' AND v_q.code IS NOT NULL THEN\n SELECT p.id INTO v_found\n FROM public.plg_fiscalbr_supplier_product_map m\n JOIN public.products p ON p.id = m.product_id\n WHERE m.supplier_cnpj = v_cnpj\n AND m.supplier_product_code = v_q.code\n AND p.is_active\n ORDER BY m.hit_count DESC NULLS LAST, m.last_used_at DESC NULLS LAST\n LIMIT 1;\n\n IF v_found IS NOT NULL THEN\n RETURN QUERY\n SELECT v_q.ref, p.id, p.name, p.sku, p.unit, p.cost, 1.0::real, 'supplier_map'::text\n FROM public.products p WHERE p.id = v_found;\n CONTINUE;\n END IF;\n END IF;\n\n -- \u2461 O c\u00F3digo de barras. O produto guarda o principal em `metadata->>barcode`\n -- (no V1 era coluna) e os demais na tabela de c\u00F3digos do estoque.\n IF length(v_q.ean) >= 8 THEN\n SELECT p.id INTO v_found\n FROM public.products p\n WHERE p.is_active\n AND (\n public.fayz_digits(coalesce(p.metadata->>'barcode', '')) = v_q.ean\n OR (v_barcodes AND EXISTS (\n SELECT 1 FROM public.plg_inventory_product_barcodes b\n WHERE b.product_id = p.id\n AND public.fayz_digits(coalesce(b.barcode, '')) = v_q.ean))\n )\n LIMIT 1;\n\n IF v_found IS NOT NULL THEN\n RETURN QUERY\n SELECT v_q.ref, p.id, p.name, p.sku, p.unit, p.cost, 1.0::real, 'ean'::text\n FROM public.products p WHERE p.id = v_found;\n CONTINUE;\n END IF;\n END IF;\n\n -- \u2462 Semelhan\u00E7a de texto, pela busca indexada que j\u00E1 existe.\n CONTINUE WHEN v_search IS NULL OR length(public.fayz_norm(v_q.text)) < 3;\n\n RETURN QUERY EXECUTE format($q$\n SELECT $4::text, p.id, p.name, p.sku, p.unit, p.cost, g.score, 'text'::text\n FROM %s($1, $2, ARRAY['product']::text[], $3, $3) g\n JOIN public.products p ON p.id = g.record_id::uuid\n WHERE p.is_active\n ORDER BY g.score DESC\n $q$, v_search)\n USING v_q.text, v_tenant, p_per_query, v_q.ref;\n END LOOP;\nEND\n$fn$;\n\nGRANT EXECUTE ON FUNCTION public.fiscalbr_match_product_candidates(jsonb, text, integer) TO authenticated;\n\nCOMMENT ON FUNCTION public.fiscalbr_match_product_candidates(jsonb, text, integer) IS\n 'Pr\u00E9-sele\u00E7\u00E3o de produtos para a sugest\u00E3o de IA da entrada de notas: de-para do fornecedor e EAN resolvem sozinhos (reason supplier_map/ean, v\u00EDnculo direto); o resto volta como candidatos de texto (reason text) para o modelo desambiguar. Uma viagem para a nota inteira \u2014 substitui o despejo de 400 produtos do V1 e o ILIKE da descri\u00E7\u00E3o fatiada.';\n";
14
+ export declare const MIGRATION_013_A_SUGESTAO_SOBREVIVE_A_FECHAR_A_JANELA = "-- ---------------------------------------------------------------------------\n-- A sugest\u00E3o sobrevive a fechar a janela \u2014 e diz QUEM escreveu no campo.\n-- ---------------------------------------------------------------------------\n-- A concilia\u00E7\u00E3o virou modal para a lista n\u00E3o ser desmontada. O efeito colateral\n-- \u00E9 que o modal fecha com clique fora e com Esc, e a sugest\u00E3o s\u00F3 existia em\n-- mem\u00F3ria at\u00E9 algu\u00E9m clicar em \u00ABSalvar concilia\u00E7\u00E3o\u00BB:\n--\n-- sugerir (30s de IA) \u2192 revisar 7 de 11 itens \u2192 clique fora \u2192 perdeu os 11\n--\n-- No V1 fechar n\u00E3o custava nada, porque a edge function j\u00E1 tinha gravado. Ent\u00E3o\n-- a V2 grava tamb\u00E9m, logo depois da sugest\u00E3o, pelo caminho que j\u00E1 existia\n-- (`fiscalbr_save_inbox_decision`, que n\u00E3o marca `reconciled`) \u2014 nenhum segundo\n-- caminho de escrita, nenhuma coluna `ai_*` paralela aos campos de verdade.\n--\n-- O que faltava era a MEM\u00D3RIA DE AUTORIA. Depois de recarregar, a tela via um\n-- fator 22,056 e n\u00E3o sabia se foi a Laissa que digitou ou a m\u00E1quina que chutou \u2014\n-- e as duas coisas se conferem de maneiras diferentes.\n--\n-- O V1 guardava um booleano, `ai_suggested`. Aqui \u00E9 texto, porque a V2 tem TR\u00CAS\n-- autores de m\u00E1quina e eles n\u00E3o merecem o mesmo selo:\n--\n-- supplier_map o de-para deste fornecedor. Igualdade, e aprendizado da casa.\n-- ean c\u00F3digo de barras. Igualdade.\n-- ai semelhan\u00E7a julgada por modelo. \u00C9 o \u00FAnico que se confere item\n-- a item, e `suggestion_confidence` diz o quanto.\n--\n-- NULL \u00E9 a resposta mais importante: significa \u00ABuma pessoa escolheu isto\u00BB. O\n-- cliente limpa a coluna em qualquer edi\u00E7\u00E3o do campo, ent\u00E3o o selo nunca mente\n-- sobre um valor que algu\u00E9m corrigiu \u00E0 m\u00E3o.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_fiscalbr_inbox_items\n ADD COLUMN IF NOT EXISTS suggested_by text,\n ADD COLUMN IF NOT EXISTS suggestion_confidence numeric;\n\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'plg_fiscalbr_inbox_items_suggested_by_check'\n AND conrelid = 'public.plg_fiscalbr_inbox_items'::regclass\n ) THEN\n ALTER TABLE public.plg_fiscalbr_inbox_items\n ADD CONSTRAINT plg_fiscalbr_inbox_items_suggested_by_check\n CHECK (suggested_by IS NULL OR suggested_by IN ('ai', 'ean', 'supplier_map'));\n END IF;\nEND $$;\n\nCOMMENT ON COLUMN public.plg_fiscalbr_inbox_items.suggested_by IS\n 'Quem escreveu a decis\u00E3o desta linha: ai (modelo, confira), ean / supplier_map (igualdade), NULL (uma pessoa). O cliente limpa em qualquer edi\u00E7\u00E3o manual.';\nCOMMENT ON COLUMN public.plg_fiscalbr_inbox_items.suggestion_confidence IS\n '0..1, s\u00F3 para suggested_by = ai. Abaixo de 0,6 a tela marca a linha como \u00ABconfira\u00BB.';\n\n-- \u2500\u2500 A grava\u00E7\u00E3o da decis\u00E3o passa a carregar a autoria \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Mesma assinatura, ent\u00E3o \u00E9 `CREATE OR REPLACE` e nenhum chamador muda. O corpo\n-- \u00E9 o da 009 com duas linhas a mais: o resto fica palavra por palavra, porque\n-- esta fun\u00E7\u00E3o \u00E9 o \u00FAnico caminho de escrita da decis\u00E3o e reescrev\u00EA-la de mem\u00F3ria\n-- \u00E9 como se perde uma regra (a de `reconciled`, por exemplo).\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 -- Quem escreveu. Ausente na chave \u00E9 NULL, que \u00E9 \u00ABuma pessoa escolheu\u00BB \u2014 e \u00E9\n -- o que acontece em todo salvamento comum, sem a IA ter passado por aqui.\n suggested_by = nullif(v_row->>'suggested_by', ''),\n suggestion_confidence = nullif(v_row->>'suggestion_confidence', '')::numeric,\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";
15
+ export declare const MIGRATION_014_A_DESCRICAO_DA_NOTA_TEM_LIXO_NO_MEIO = "-- ---------------------------------------------------------------------------\n-- A descri\u00E7\u00E3o da nota tem lixo no meio, e um token de lixo matava a busca.\n-- ---------------------------------------------------------------------------\n-- Caso real, primeira nota de verdade: o item \u00AB20X35 AR VACUO\u00BB voltou SEM\n-- candidato, e o produto `EMBALAGEM VACUO 20X35` est\u00E1 cadastrado.\n--\n-- `public.global_search` exige que TODOS os tokens apare\u00E7am no haystack \u2014 \u00E9 a\n-- regra certa para gente digitando (\u00ABjo\u00E3o silva\u00BB n\u00E3o deve trazer todos os jo\u00E3os)\n-- e \u00E9 a regra errada para descri\u00E7\u00E3o de NF-e. Os tokens de `20X35 AR VACUO` s\u00E3o\n-- `20x35`, `ar`, `vacuo`; o produto \u00E9 `embalagem vacuo 20x35`. Dois casam. `ar`\n-- n\u00E3o aparece em lugar nenhum, e o AND derruba a linha inteira.\n--\n-- A segunda fase (`word_similarity`) tamb\u00E9m n\u00E3o salva: ela compara a frase\n-- inteira contra o haystack, e com um ter\u00E7o da frase sendo ru\u00EDdo a semelhan\u00E7a\n-- fica abaixo do limiar.\n--\n-- Descri\u00E7\u00E3o de nota fiscal \u00E9 assim por natureza: abreviada, em ordem pr\u00F3pria, com\n-- sigla de unidade, micragem e palavra solta no meio. Ent\u00E3o o que entra na busca\n-- deixa de ser a frase e passa a ser O QUE NELA DISTINGUE:\n--\n-- \u2460 tokens de 3 letras ou mais. \u00ABar\u00BB, \u00ABkg\u00BB, \u00ABun\u00BB, \u00ABc/\u00BB, \u00ABp/\u00BB saem. \u00AB20x35\u00BB\n-- e \u00ABvacuo\u00BB ficam \u2014 e s\u00E3o eles que identificam o produto.\n-- \u2461 se mesmo assim n\u00E3o vier nada, UMA \u00FAltima tentativa com o token mais longo\n-- sozinho. \u00C9 o recuo que acha \u00ABembalagem a v\u00E1cuo 15 x 25\u00BB a partir de\n-- \u00ABVACUO\u00BB, para a pessoa escolher entre poucos em vez de nenhum.\n--\n-- N\u00E3o se mexe em `global_search`: l\u00E1 a regra est\u00E1 certa para quem ela serve.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_match_product_candidates(\n p_queries jsonb,\n p_supplier_cnpj text DEFAULT NULL,\n p_per_query integer DEFAULT 8\n) RETURNS TABLE (\n ref text,\n product_id uuid,\n name text,\n sku text,\n unit text,\n cost numeric,\n score real,\n reason text\n)\nLANGUAGE plpgsql\nSTABLE\nSET search_path TO 'public', 'extensions', 'pg_temp'\nAS $fn$\nDECLARE\n v_tenant uuid := (SELECT app.current_tenant_id());\n v_cnpj text := public.fayz_digits(coalesce(p_supplier_cnpj, ''));\n v_barcodes boolean := to_regclass('public.plg_inventory_product_barcodes') IS NOT NULL;\n v_map boolean := to_regclass('public.plg_fiscalbr_supplier_product_map') IS NOT NULL;\n v_search text;\n v_q record;\n v_found uuid;\n v_tokens text[];\n v_strong text[];\n v_busca text;\n v_recuo text;\nBEGIN\n IF v_tenant IS NULL THEN RETURN; END IF;\n IF p_queries IS NULL OR jsonb_typeof(p_queries) <> 'array' THEN RETURN; END IF;\n p_per_query := least(greatest(coalesce(p_per_query, 8), 1), 50);\n\n v_search := CASE\n WHEN to_regprocedure('public.global_search(text,uuid,text[],integer,integer)') IS NOT NULL\n THEN 'public.global_search'\n WHEN to_regprocedure('public.fayz_global_search(text,uuid,text[],integer,integer)') IS NOT NULL\n THEN 'public.fayz_global_search'\n ELSE NULL\n END;\n\n FOR v_q IN\n SELECT e->>'ref' AS ref,\n coalesce(e->>'text', '') AS text,\n public.fayz_digits(coalesce(e->>'ean', '')) AS ean,\n nullif(btrim(coalesce(e->>'code', '')), '') AS code\n FROM jsonb_array_elements(p_queries) AS e\n WHERE e->>'ref' IS NOT NULL\n LOOP\n v_found := NULL;\n\n -- \u2460 O de-para do fornecedor. Aprendizado da casa, e ele ganha de tudo.\n IF v_map AND v_cnpj <> '' AND v_q.code IS NOT NULL THEN\n SELECT p.id INTO v_found\n FROM public.plg_fiscalbr_supplier_product_map m\n JOIN public.products p ON p.id = m.product_id\n WHERE m.supplier_cnpj = v_cnpj\n AND m.supplier_product_code = v_q.code\n AND p.is_active\n ORDER BY m.hit_count DESC NULLS LAST, m.last_used_at DESC NULLS LAST\n LIMIT 1;\n\n IF v_found IS NOT NULL THEN\n RETURN QUERY\n SELECT v_q.ref, p.id, p.name, p.sku, p.unit, p.cost, 1.0::real, 'supplier_map'::text\n FROM public.products p WHERE p.id = v_found;\n CONTINUE;\n END IF;\n END IF;\n\n -- \u2461 O c\u00F3digo de barras. \u00ABSEM GTIN\u00BB \u00E9 o que a NF-e escreve quando n\u00E3o h\u00E1 \u2014 e\n -- `fayz_digits` o reduz a vazio, ent\u00E3o o teste de comprimento j\u00E1 o barra.\n IF length(v_q.ean) >= 8 THEN\n SELECT p.id INTO v_found\n FROM public.products p\n WHERE p.is_active\n AND (\n public.fayz_digits(coalesce(p.metadata->>'barcode', '')) = v_q.ean\n OR (v_barcodes AND EXISTS (\n SELECT 1 FROM public.plg_inventory_product_barcodes b\n WHERE b.product_id = p.id\n AND public.fayz_digits(coalesce(b.barcode, '')) = v_q.ean))\n )\n LIMIT 1;\n\n IF v_found IS NOT NULL THEN\n RETURN QUERY\n SELECT v_q.ref, p.id, p.name, p.sku, p.unit, p.cost, 1.0::real, 'ean'::text\n FROM public.products p WHERE p.id = v_found;\n CONTINUE;\n END IF;\n END IF;\n\n -- \u2462 Semelhan\u00E7a de texto, com o que DISTINGUE em vez da frase inteira.\n CONTINUE WHEN v_search IS NULL;\n\n v_tokens := array_remove(string_to_array(public.fayz_norm(v_q.text), ' '), '');\n SELECT array_agg(t ORDER BY length(t) DESC) INTO v_strong\n FROM unnest(v_tokens) t WHERE length(t) >= 3;\n\n v_busca := array_to_string(coalesce(v_strong, v_tokens), ' ');\n -- O recuo \u00E9 o token mais longo sozinho: numa descri\u00E7\u00E3o de nota ele \u00E9 quase\n -- sempre o nome da coisa, e os outros s\u00E3o medida, micragem ou conjun\u00E7\u00E3o.\n v_recuo := coalesce(v_strong[1], '');\n\n CONTINUE WHEN length(v_busca) < 3;\n\n RETURN QUERY EXECUTE format($q$\n WITH forte AS (\n SELECT g.record_id, g.score FROM %1$s($1, $2, ARRAY['product']::text[], $3, $3) g\n ),\n -- S\u00F3 quando a busca forte n\u00E3o achou nada: um recuo que rodasse sempre\n -- encheria a lista de parecidos e empurraria o certo para fora do teto.\n fraco AS (\n SELECT g.record_id, g.score * 0.7 AS score\n FROM %1$s($4, $2, ARRAY['product']::text[], $3, $3) g\n WHERE $4 <> '' AND $4 <> $1 AND NOT EXISTS (SELECT 1 FROM forte)\n )\n SELECT $5::text, p.id, p.name, p.sku, p.unit, p.cost, g.score::real, 'text'::text\n FROM (SELECT * FROM forte UNION ALL SELECT * FROM fraco) g\n JOIN public.products p ON p.id = g.record_id::uuid\n WHERE p.is_active\n ORDER BY g.score DESC\n $q$, v_search)\n USING v_busca, v_tenant, p_per_query, v_recuo, v_q.ref;\n END LOOP;\nEND\n$fn$;\n\nGRANT EXECUTE ON FUNCTION public.fiscalbr_match_product_candidates(jsonb, text, integer) TO authenticated;\n\nCOMMENT ON FUNCTION public.fiscalbr_match_product_candidates(jsonb, text, integer) IS\n 'Pr\u00E9-sele\u00E7\u00E3o de produtos para a sugest\u00E3o de IA da entrada de notas: de-para do fornecedor e EAN resolvem sozinhos (reason supplier_map/ean); o resto volta como candidatos de texto. A busca usa os tokens de 3+ letras da descri\u00E7\u00E3o, n\u00E3o a frase \u2014 \u00AB20X35 AR VACUO\u00BB achava nada porque o AND de global_search exige o token \u00ABar\u00BB, que n\u00E3o existe em \u00ABembalagem vacuo 20x35\u00BB.';\n";
16
+ export declare const MIGRATION_015_O_MOVIMENTO_DIZ_QUE_VEIO_DA_NOTA = "-- ---------------------------------------------------------------------------\n-- O movimento de estoque diz, por escrito, que veio desta nota.\n-- ---------------------------------------------------------------------------\n-- A aceita\u00E7\u00E3o j\u00E1 mandava a frase para `inventory_receive`, na chave `notes` do\n-- cabe\u00E7alho \u2014 e ela nunca chegava: l\u00E1 dentro o `coalesce(v_ln.notes, \u2026)` d\u00E1 a vez\n-- \u00E0 anota\u00E7\u00E3o DA LINHA, que aqui \u00E9 sempre a descri\u00E7\u00E3o do item. A tela de\n-- Movimenta\u00E7\u00F5es ent\u00E3o escrevia \u00ABSem motivo registrado\u00BB sobre a entrada mais\n-- rastre\u00E1vel que existe.\n--\n-- `inventory/021` abriu a chave `reason` no cabe\u00E7alho. Esta migration passa a\n-- us\u00E1-la, com a mesma frase que as linhas migradas do V1 carregam \u2014 para que a\n-- entrada de ontem e a de hoje se leiam igual no mesmo raz\u00E3o.\n--\n-- S\u00F3 a chamada muda. O resto do corpo \u00E9 o da 009, palavra por palavra: esta\n-- fun\u00E7\u00E3o escreve estoque e t\u00EDtulo na mesma transa\u00E7\u00E3o, e reescrev\u00EA-la de mem\u00F3ria\n-- \u00E9 como se perde uma regra.\n-- ---------------------------------------------------------------------------\n\nDO $m$\nDECLARE\n v_def text;\n v_antes text := E' ''ref'', ''NF-e '' || coalesce(v_doc.nfe_number, ''?''),\\n ''notes'', ''Entrada via DANFE NF-e '' || coalesce(v_doc.nfe_number, ''?'')),';\n v_depois text := E' ''ref'', ''NF-e '' || coalesce(v_doc.nfe_number, ''?''),\\n ''notes'', ''Entrada via DANFE NF-e '' || coalesce(v_doc.nfe_number, ''?''),\\n ''reason'', ''Entrada via DANFE NF-e '' || coalesce(v_doc.nfe_number, ''?'')),';\nBEGIN\n SELECT pg_get_functiondef(p.oid) INTO v_def\n FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_accept_inbox_document';\n\n IF v_def IS NULL THEN\n RAISE NOTICE 'fiscalbr: accept ainda n\u00E3o existe neste pool \u2014 009 roda antes e j\u00E1 traz o reason';\n RETURN;\n END IF;\n\n -- J\u00E1 aplicada: sair sem tocar em nada \u00E9 o que torna esta migration repet\u00EDvel.\n IF position('''reason'', ''Entrada via DANFE' in v_def) > 0 THEN RETURN; END IF;\n\n IF position(v_antes in v_def) = 0 THEN\n RAISE EXCEPTION 'fiscalbr/015: o corpo de fiscalbr_accept_inbox_document n\u00E3o \u00E9 o esperado \u2014 confira \u00E0 m\u00E3o em vez de deixar passar calado';\n END IF;\n\n EXECUTE replace(v_def, v_antes, v_depois);\nEND $m$;\n";
17
+ export declare const MIGRATION_016_A_NOTA_GUARDA_O_PROPRIO_XML = "-- ---------------------------------------------------------------------------\n-- A nota guarda o pr\u00F3prio XML, em vez de l\u00EA-lo e jogar fora.\n-- ---------------------------------------------------------------------------\n-- O V1 abre o XML e o DANFE direto da lista de notas. A V2 mostrava os dois\n-- bot\u00F5es desligados porque n\u00E3o tinha o arquivo: das 889 notas deste cluster,\n-- ZERO t\u00EAm `document_id`, zero t\u00EAm bytes em `documents`, zero t\u00EAm nada no\n-- metadata.\n--\n-- E n\u00E3o \u00E9 que o arquivo nunca tenha passado por aqui \u2014 ele passa INTEIRO pela\n-- m\u00E3o do est\u00E1gio: o navegador l\u00EA o `.xml` que a pessoa arrastou, o parser extrai\n-- itens, parcelas e totais, e os bytes s\u00E3o descartados no fim da fun\u00E7\u00E3o. A nota\n-- fiscal \u00E9 documento com guarda legal de cinco anos, e a \u00FAnica c\u00F3pia ficava na\n-- pasta de downloads de quem importou.\n--\n-- `xml_content text` \u00E9 o mesmo nome e o mesmo tipo do `dfe_inbox.xml_content` do\n-- V1 \u2014 a ponte da 003 j\u00E1 citava essa coluna, e repetir o nome \u00E9 o que faz uma\n-- futura migra\u00E7\u00E3o de dados ser um `INSERT ... SELECT` em vez de um de-para.\n--\n-- Guardar no banco e n\u00E3o no bucket, de prop\u00F3sito: s\u00E3o 10\u201350 KB de texto por nota\n-- e s\u00E3o o \u00DANICO conte\u00FAdo que torna a linha audit\u00E1vel. Storage \u00E9 outro sistema,\n-- com outra vida \u00FAtil e outra pol\u00EDtica de acesso, e um XML fiscal que vive em\n-- bucket \u00E9 um XML que algu\u00E9m apaga limpando bucket.\n--\n-- Isto N\u00C3O preenche as 889 notas antigas: o que n\u00E3o foi guardado n\u00E3o se inventa.\n-- A tela desliga o bot\u00E3o por nota, n\u00E3o por feature.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_fiscalbr_inbox_documents\n ADD COLUMN IF NOT EXISTS xml_content text;\n\nCOMMENT ON COLUMN public.plg_fiscalbr_inbox_documents.xml_content IS\n 'O XML autorizado da NF-e, como chegou. Nulo nas notas importadas antes desta coluna existir \u2014 o que n\u00E3o foi guardado n\u00E3o se inventa.';\n\n-- \u2500\u2500 O est\u00E1gio passa a guardar o que j\u00E1 tinha em m\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Patch sobre o corpo que est\u00E1 no cluster, e n\u00E3o reescrita: esta fun\u00E7\u00E3o insere\n-- documento, itens, duplicatas e pagamentos numa transa\u00E7\u00E3o s\u00F3, e reescrev\u00EA-la de\n-- mem\u00F3ria \u00E9 como se perde uma regra.\n\nDO $m$\nDECLARE\n v_def text;\n v_cols_antes text := E' total_freight, total_insurance, total_other, additional_info, metadata)';\n v_cols_depois text := E' total_freight, total_insurance, total_other, additional_info, metadata, xml_content)';\n v_vals_antes text := E' jsonb_build_object(''source'', coalesce(p_note->>''source'', ''xml_upload'')))';\n v_vals_depois text := E' jsonb_build_object(''source'', coalesce(p_note->>''source'', ''xml_upload'')),' || E'\\n' ||\n E' nullif(p_note->>''xml'', ''''))';\n v_upd_antes text := E' total_value = EXCLUDED.total_value,';\n v_upd_depois text := E' total_value = EXCLUDED.total_value,' || E'\\n' ||\n E' -- Reimportar com o arquivo preenche o que faltava; reimportar sem ele' || E'\\n' ||\n E' -- n\u00E3o apaga o que j\u00E1 estava guardado.' || E'\\n' ||\n E' xml_content = coalesce(EXCLUDED.xml_content, public.plg_fiscalbr_inbox_documents.xml_content),';\nBEGIN\n SELECT pg_get_functiondef(p.oid) INTO v_def\n FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_stage_inbox_document';\n\n IF v_def IS NULL THEN\n RAISE NOTICE 'fiscalbr/016: o est\u00E1gio ainda n\u00E3o existe neste pool \u2014 a 009 roda antes';\n RETURN;\n END IF;\n\n -- J\u00E1 aplicada: sair sem tocar em nada \u00E9 o que torna esta migration repet\u00EDvel.\n IF position('xml_content' in v_def) > 0 THEN RETURN; END IF;\n\n IF position(v_cols_antes in v_def) = 0\n OR position(v_vals_antes in v_def) = 0\n OR position(v_upd_antes in v_def) = 0 THEN\n RAISE EXCEPTION 'fiscalbr/016: o corpo de fiscalbr_stage_inbox_document n\u00E3o \u00E9 o esperado \u2014 confira \u00E0 m\u00E3o em vez de deixar passar calado';\n END IF;\n\n v_def := replace(v_def, v_cols_antes, v_cols_depois);\n v_def := replace(v_def, v_vals_antes, v_vals_depois);\n v_def := replace(v_def, v_upd_antes, v_upd_depois);\n EXECUTE v_def;\nEND $m$;\n";
18
+ export declare const MIGRATION_017_QUEM_EMITE_E_O_CNPJ_NAO_A_LOJA = "-- Quem emite \u00E9 o CNPJ, n\u00E3o a loja.\n--\n-- As abas \"Certificado\" e \"Emiss\u00E3o Fiscal\" do cadastro de empresa do V1\n-- guardam, em companies, como aquela pessoa jur\u00EDdica emite NFS-e e NFC-e e se\n-- recebe DANFes da SEFAZ. No V2 a pessoa jur\u00EDdica \u00E9\n-- public.company_legal_entities (base), e a filial chega nela por\n-- app.units.company_legal_entity_id (168). Esta tabela \u00E9 uma linha por pessoa\n-- jur\u00EDdica:\n--\n-- unidade \u2192 app.units.company_legal_entity_id \u2192 company_legal_entities.id\n-- \u2192 plg_fiscalbr_emitter_settings.legal_entity_id (UNIQUE)\n--\n-- Duas filiais com o mesmo CNPJ dividem a mesma configura\u00E7\u00E3o, como no V1.\n--\n-- O certificado A1 N\u00C3O mora aqui. O .pfx e a senha v\u00E3o direto ao provedor\n-- (PlugNotas) e o que fica \u00E9 o recibo: id no provedor, titular e validade. O\n-- CSC da NFC-e \u00E9 segredo \u2014 o navegador grava, mas n\u00E3o l\u00EA de volta: a coluna\n-- fica fora do GRANT SELECT e a tela v\u00EA s\u00F3 `nfce_csc_token_set`.\n--\n-- Plano aprovado em docs/port/empresa.schema.json. Idempotente.\n\nCREATE TABLE IF NOT EXISTS public.plg_fiscalbr_emitter_settings (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n legal_entity_id uuid NOT NULL,\n\n nfse_enabled boolean NOT NULL DEFAULT false,\n nfse_production boolean NOT NULL DEFAULT false,\n nfse_national boolean NOT NULL DEFAULT false,\n nfse_service_code_national text,\n nfse_service_code_municipal text,\n nfse_nbs_code text,\n nfse_cnae text,\n nfse_iss_rate numeric(5,2) CHECK (nfse_iss_rate IS NULL OR (nfse_iss_rate >= 0 AND nfse_iss_rate <= 100)),\n nfse_iss_taxation smallint,\n nfse_iss_exigibility smallint,\n nfse_ibscbs_cst text,\n nfse_ibscbs_classification text,\n nfse_simples_regime smallint,\n nfse_net_value boolean NOT NULL DEFAULT false,\n nfse_additional_info text,\n nfse_city_features jsonb,\n\n nfce_enabled boolean NOT NULL DEFAULT false,\n nfce_production boolean NOT NULL DEFAULT false,\n nfce_series integer CHECK (nfce_series IS NULL OR nfce_series > 0),\n nfce_csc_id text,\n nfce_csc_token text,\n nfce_csc_token_set boolean GENERATED ALWAYS AS (nfce_csc_token IS NOT NULL AND nfce_csc_token <> '') STORED,\n nfce_generic_item jsonb,\n\n nfe_enabled boolean NOT NULL DEFAULT false,\n\n certificate_provider_id text,\n certificate_cn text,\n certificate_expires_at timestamptz,\n certificate_uploaded_at timestamptz,\n\n dfe_sync_enabled boolean NOT NULL DEFAULT false,\n dfe_sync_cursor text,\n dfe_last_sync_at timestamptz,\n provider_synced_at timestamptz,\n\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n UNIQUE (tenant_id, id)\n);\n\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_fiscalbr_emitter_settings_legal_entity_fkey') THEN\n -- company_legal_entities \u00E9 da base, n\u00E3o de outro plugin: a FK f\u00EDsica \u00E9\n -- permitida (a regra do 000 pro\u00EDbe FK entre PLUGINS).\n ALTER TABLE ONLY public.plg_fiscalbr_emitter_settings\n ADD CONSTRAINT plg_fiscalbr_emitter_settings_legal_entity_fkey\n FOREIGN KEY (legal_entity_id) REFERENCES public.company_legal_entities(id) ON DELETE CASCADE;\n END IF;\nEND $$;\n\nCREATE UNIQUE INDEX IF NOT EXISTS plg_fiscalbr_emitter_settings_legal_entity_uidx\n ON public.plg_fiscalbr_emitter_settings USING btree (legal_entity_id);\n\nCOMMENT ON TABLE public.plg_fiscalbr_emitter_settings IS\n 'V1: companies (abas Certificado + Emiss\u00E3o Fiscal). Como uma pessoa jur\u00EDdica emite NFS-e/NFC-e e recebe DANFes. Uma linha por CNPJ; a filial chega por app.units.company_legal_entity_id.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.legal_entity_id IS 'A chave que conecta: company_legal_entities.id. UNIQUE.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_production IS 'V1: companies.nfse_producao \u2014 false = homologa\u00E7\u00E3o.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_national IS 'V1: companies.nfse_nacional \u2014 padr\u00E3o nacional \u00D7 municipal.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_service_code_national IS 'V1: companies.nfse_codigo_tributacao_nacional';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_service_code_municipal IS 'V1: companies.nfse_codigo_tributacao_municipio';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_nbs_code IS 'V1: companies.nfse_codigo_nbs';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_cnae IS 'V1: companies.nfse_cnae \u2014 CNAE do servi\u00E7o, pode diferir do principal.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_iss_rate IS 'V1: companies.nfse_iss_aliquota (%)';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_iss_taxation IS 'V1: companies.nfse_iss_tipo_tributacao';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_iss_exigibility IS 'V1: companies.nfse_iss_exigibilidade';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_simples_regime IS 'V1: companies.nfse_regime_apuracao_sn \u2014 s\u00F3 para optante do Simples.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_net_value IS 'V1: companies.nfse_valor_liquido \u2014 emitir pelo valor l\u00EDquido.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfse_city_features IS 'V1: companies.nfse_city_features \u2014 o que a prefeitura aceita, devolvido pelo provedor.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfce_csc_token IS 'V1: companies.nfce_csc_token \u2014 SEGREDO. Fora do GRANT SELECT de authenticated.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfce_generic_item IS 'V1: companies.nfce_item_generico \u2014 {ncm, cfop, csosn}.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.certificate_provider_id IS 'V1: companies.certificate_plugnotas_id \u2014 recibo; o .pfx n\u00E3o fica no banco.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.dfe_sync_enabled IS 'V1: companies.dfe_sync_enabled \u2014 receber DANFes automaticamente.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.dfe_sync_cursor IS 'V1: companies.dfe_sync_cursor \u2014 NSU onde a busca na SEFAZ parou.';\n\nALTER TABLE public.plg_fiscalbr_emitter_settings ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_fiscalbr_emitter_settings FORCE ROW LEVEL SECURITY;\n\n-- O segredo fica fora do SELECT: grava-se, n\u00E3o se l\u00EA.\nREVOKE ALL ON TABLE public.plg_fiscalbr_emitter_settings FROM anon, authenticated;\nGRANT INSERT, UPDATE, DELETE ON TABLE public.plg_fiscalbr_emitter_settings TO authenticated;\nGRANT SELECT (\n id, tenant_id, legal_entity_id,\n nfse_enabled, nfse_production, nfse_national, nfse_service_code_national, nfse_service_code_municipal,\n nfse_nbs_code, nfse_cnae, nfse_iss_rate, nfse_iss_taxation, nfse_iss_exigibility, nfse_ibscbs_cst,\n nfse_ibscbs_classification, nfse_simples_regime, nfse_net_value, nfse_additional_info, nfse_city_features,\n nfce_enabled, nfce_production, nfce_series, nfce_csc_id, nfce_csc_token_set, nfce_generic_item,\n nfe_enabled, certificate_provider_id, certificate_cn, certificate_expires_at, certificate_uploaded_at,\n dfe_sync_enabled, dfe_sync_cursor, dfe_last_sync_at, provider_synced_at, created_at, updated_at\n) ON public.plg_fiscalbr_emitter_settings TO authenticated;\nGRANT ALL ON TABLE public.plg_fiscalbr_emitter_settings TO service_role;\n\nDROP POLICY IF EXISTS plg_fiscalbr_emitter_settings_authz_select ON public.plg_fiscalbr_emitter_settings;\nCREATE POLICY plg_fiscalbr_emitter_settings_authz_select ON public.plg_fiscalbr_emitter_settings\n FOR SELECT TO authenticated 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_emitter_settings_authz_write ON public.plg_fiscalbr_emitter_settings;\nCREATE POLICY plg_fiscalbr_emitter_settings_authz_write ON public.plg_fiscalbr_emitter_settings\n FOR ALL TO authenticated\n USING (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('settings.manage', NULL::uuid)))\n WITH CHECK (\n tenant_id = (SELECT app.current_tenant_id())\n AND (SELECT app.has_permission('settings.manage', NULL::uuid))\n AND EXISTS (SELECT 1 FROM public.company_legal_entities le\n WHERE le.id = plg_fiscalbr_emitter_settings.legal_entity_id\n AND le.tenant_id = plg_fiscalbr_emitter_settings.tenant_id));\n";
12
19
  export declare const MIGRATIONS: Array<{
13
20
  id: string;
14
21
  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,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"}
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,8CAA8C,k/HAiF1D,CAAA;AAED,eAAO,MAAM,oDAAoD,w2OA4JhE,CAAA;AAED,eAAO,MAAM,oDAAoD,gvRAwJhE,CAAA;AAED,eAAO,MAAM,kDAAkD,shPAoK9D,CAAA;AAED,eAAO,MAAM,8CAA8C,8/EA0C1D,CAAA;AAED,eAAO,MAAM,yCAAyC,2mJA2ErD,CAAA;AAED,eAAO,MAAM,4CAA4C,68QA0IxD,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAmBzD,CAAA"}
package/dist/types.d.ts CHANGED
@@ -85,6 +85,8 @@ export interface InboxNote {
85
85
  operationId: string | null;
86
86
  stockedAt: string | null;
87
87
  documentId: string | null;
88
+ /** O XML está guardado nesta nota. Falso nas importadas antes da migration 016. */
89
+ hasXml: boolean;
88
90
  unitId: string | null;
89
91
  }
90
92
  export interface InboxItem {
@@ -100,6 +102,17 @@ export interface InboxItem {
100
102
  quantity: number;
101
103
  unitPrice: number;
102
104
  total: number;
105
+ /**
106
+ * `uTrib`/`qTrib` do XML — a unidade e a quantidade TRIBUTÁVEIS.
107
+ *
108
+ * É a evidência mais forte que existe para o fator de conversão, e ela já
109
+ * estava no banco: 558 dos 3.691 itens têm unidade tributável diferente da
110
+ * comercial. «4 CX» com `qTrib` 88,224 KG diz que a caixa tem 22,056 kg — e o
111
+ * caso Artorius (carne a R$ 397/kg) foi exatamente esta conta não ter sido
112
+ * feita. Ver `taxableFactor`.
113
+ */
114
+ unitTaxable: string | null;
115
+ quantityTaxable: number | null;
103
116
  discount: number | null;
104
117
  icmsCst: string | null;
105
118
  icmsCsosn: string | null;
@@ -114,6 +127,14 @@ export interface InboxItem {
114
127
  costMode: string;
115
128
  unitCostNew: number | null;
116
129
  reconciled: boolean;
130
+ /**
131
+ * Quem escreveu a decisão desta linha. `null` é «uma pessoa escolheu», e é o
132
+ * caso que importa: igualdade (`ean`, `supplier_map`) e semelhança (`ai`) não
133
+ * se conferem do mesmo jeito que uma escolha humana. Ver a migration 013.
134
+ */
135
+ suggestedBy: 'ai' | 'ean' | 'supplier_map' | null;
136
+ /** 0..1, só quando `suggestedBy === 'ai'`. Abaixo de 0,6 a tela pede conferência. */
137
+ suggestionConfidence: number | null;
117
138
  stockMovementId: string | null;
118
139
  shortageQuantity: number | null;
119
140
  shortageReason: string | null;
@@ -170,6 +191,27 @@ export interface ProductOption {
170
191
  cost: number | null;
171
192
  kind: string | null;
172
193
  }
194
+ /**
195
+ * Um candidato a produto para uma linha da nota, com o motivo de ter aparecido.
196
+ *
197
+ * Devolvido pela RPC `fiscalbr_match_product_candidates` (migration 012) e lido
198
+ * DENTRO da edge function de sugestão — o cliente não chama essa RPC, porque quem
199
+ * decide o que fazer com `reason` é quem monta a pergunta ao modelo.
200
+ *
201
+ * O motivo é a parte que importa: `ean` e `supplier_map` são igualdade — liga e
202
+ * pronto. `text` é semelhança, e semelhança se confere.
203
+ */
204
+ export interface ProductMatch {
205
+ /** O id do item da nota que originou a busca. */
206
+ ref: string;
207
+ productId: string;
208
+ name: string;
209
+ sku: string | null;
210
+ unit: string | null;
211
+ cost: number | null;
212
+ score: number;
213
+ reason: 'supplier_map' | 'ean' | 'text';
214
+ }
173
215
  export interface MeasurementUnitOption {
174
216
  id: string;
175
217
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"InboundNotesPage.d.ts","sourceRoot":"","sources":["../../src/views/InboundNotesPage.tsx"],"names":[],"mappings":"AAaA,OAAO,KAA2C,MAAM,OAAO,CAAA;AA0B/D,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;CAC7C;AAKD,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,GAAE,qBAA0B,qBA0D5E;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,KAAK,CAAC,YAAY,CAInG"}
1
+ {"version":3,"file":"InboundNotesPage.d.ts","sourceRoot":"","sources":["../../src/views/InboundNotesPage.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAA2C,MAAM,OAAO,CAAA;AA4B/D,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;CAC7C;AAKD,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,GAAE,qBAA0B,qBA0F5E;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,KAAK,CAAC,YAAY,CAInG"}
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
  export interface ManifestViewProps {
3
3
  onOpenNote: (noteId: string) => void;
4
+ /** Volta para o Manifesto de Notas. Sem isto só o «voltar» do navegador saía daqui. */
5
+ onBack?: () => void;
4
6
  }
5
- export declare function ManifestView({ onOpenNote }: ManifestViewProps): React.JSX.Element;
7
+ export declare function ManifestView({ onOpenNote, onBack }: ManifestViewProps): React.JSX.Element;
6
8
  //# sourceMappingURL=ManifestView.d.ts.map