@fayz-ai/plugin-fiscal-br 0.13.4 → 0.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/GapScreens-RDPPFZMG.js +3 -0
  2. package/dist/GapScreens-RDPPFZMG.js.map +1 -0
  3. package/dist/InboundNotesPage-R72EC5E4.js +5 -0
  4. package/dist/InboundNotesPage-R72EC5E4.js.map +1 -0
  5. package/dist/InvoiceNotesButton-3SMNHWMQ.js +5 -0
  6. package/dist/InvoiceNotesButton-3SMNHWMQ.js.map +1 -0
  7. package/dist/NoteReconcileView-3KVXZLCG.js +4 -0
  8. package/dist/NoteReconcileView-3KVXZLCG.js.map +1 -0
  9. package/dist/OrderNoteCard-H6P66R4J.js +5 -0
  10. package/dist/OrderNoteCard-H6P66R4J.js.map +1 -0
  11. package/dist/UnitFiscalWidgets-KCHSISBS.js +503 -0
  12. package/dist/UnitFiscalWidgets-KCHSISBS.js.map +1 -0
  13. package/dist/chunk-2GHTB3Z2.js +66 -0
  14. package/dist/chunk-2GHTB3Z2.js.map +1 -0
  15. package/dist/chunk-7NW3EBOQ.js +101 -0
  16. package/dist/chunk-7NW3EBOQ.js.map +1 -0
  17. package/dist/chunk-G7T5YMNY.js +241 -0
  18. package/dist/chunk-G7T5YMNY.js.map +1 -0
  19. package/dist/chunk-JRP5AZFJ.js +64 -0
  20. package/dist/chunk-JRP5AZFJ.js.map +1 -0
  21. package/dist/chunk-OGXYHL3Z.js +590 -0
  22. package/dist/chunk-OGXYHL3Z.js.map +1 -0
  23. package/dist/chunk-RZT2BO6E.js +64 -0
  24. package/dist/chunk-RZT2BO6E.js.map +1 -0
  25. package/dist/chunk-UYGGBJCR.js +2314 -0
  26. package/dist/chunk-UYGGBJCR.js.map +1 -0
  27. package/dist/chunk-WID54RUV.js +498 -0
  28. package/dist/chunk-WID54RUV.js.map +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +146 -9391
  31. package/dist/index.js.map +1 -1
  32. package/dist/migrations/main.browser.js +39 -0
  33. package/dist/migrations/main.browser.js.map +1 -0
  34. package/dist/migrations/main.js +4912 -0
  35. package/dist/migrations/main.js.map +1 -0
  36. package/dist/views/InboundNotesPage.d.ts.map +1 -1
  37. package/dist/views/inventoryView.d.ts.map +1 -1
  38. package/package.json +11 -5
@@ -0,0 +1,2314 @@
1
+ import { packagingCount } from './chunk-5LI2FDOM.js';
2
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
3
+ import { KeyRound, Loader2, Download, ChevronDown, FileCode2, FileText, RefreshCw, UserPlus, Gift, AlertTriangle, ChevronRight, Check, Trash2, Plus, Image, UploadCloud, Paperclip, ScanLine, X } from 'lucide-react';
4
+ import { getSupabaseClientOptional, formatCurrency } from '@fayz-ai/core';
5
+ import { getActiveTenantId, PersonLink } from '@fayz-ai/admin';
6
+ import { Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, ModalFooter, Button, AiActionButton, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, Hint, Card, InlineLink, SearchSelect, EmBreve, DatePicker, SubpageHeader, Skeleton } from '@fayz-ai/ui';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+
9
+ // src/lib/nfe-xml.ts
10
+ var NfeParseError = class extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "NfeParseError";
14
+ }
15
+ };
16
+ var ENTITIES = {
17
+ amp: "&",
18
+ lt: "<",
19
+ gt: ">",
20
+ quot: '"',
21
+ apos: "'"
22
+ };
23
+ function decode(raw) {
24
+ return raw.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body) => {
25
+ if (body.startsWith("#x") || body.startsWith("#X")) return String.fromCodePoint(parseInt(body.slice(2), 16));
26
+ if (body.startsWith("#")) return String.fromCodePoint(parseInt(body.slice(1), 10));
27
+ return ENTITIES[body] ?? whole;
28
+ });
29
+ }
30
+ var bare = (name) => {
31
+ const i = name.indexOf(":");
32
+ return i < 0 ? name : name.slice(i + 1);
33
+ };
34
+ function parseXml(xml) {
35
+ const src = xml.replace(/<\?[\s\S]*?\?>/g, "").replace(/<!--[\s\S]*?-->/g, "").replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, (_m, inner) => inner).replace(/<!DOCTYPE[^>]*>/gi, "");
36
+ const root = { name: "#root", attrs: {}, children: [], text: "" };
37
+ const stack = [root];
38
+ const tag = /<\s*(\/?)\s*([A-Za-z_][\w.:-]*)((?:\s+[\w.:-]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*(\/?)\s*>/g;
39
+ let cursor = 0;
40
+ let m;
41
+ while ((m = tag.exec(src)) !== null) {
42
+ const [whole, closing, rawName, rawAttrs, selfClosing] = m;
43
+ const top = stack[stack.length - 1];
44
+ const between = src.slice(cursor, m.index);
45
+ if (between.trim()) top.text += decode(between);
46
+ cursor = m.index + whole.length;
47
+ if (closing) {
48
+ if (stack.length > 1) stack.pop();
49
+ continue;
50
+ }
51
+ const attrs = {};
52
+ const attrRe = /([\w.:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
53
+ let a;
54
+ while ((a = attrRe.exec(rawAttrs ?? "")) !== null) {
55
+ attrs[bare(a[1])] = decode(a[2] ?? a[3] ?? "");
56
+ }
57
+ const node = { name: bare(rawName), attrs, children: [], text: "" };
58
+ top.children.push(node);
59
+ if (!selfClosing) stack.push(node);
60
+ }
61
+ return root;
62
+ }
63
+ var kids = (node, name) => node ? node.children.filter((c) => c.name === name) : [];
64
+ var kid = (node, name) => kids(node, name)[0];
65
+ function at(node, path) {
66
+ let cur = node;
67
+ for (const step of path.split(".")) {
68
+ cur = kid(cur, step);
69
+ if (!cur) return void 0;
70
+ }
71
+ return cur;
72
+ }
73
+ var str = (node, path) => {
74
+ const target = path ? at(node, path) : node;
75
+ const value = target?.text.trim();
76
+ return value ? value : void 0;
77
+ };
78
+ var num = (node, path) => {
79
+ const value = str(node, path);
80
+ if (value === void 0) return void 0;
81
+ const parsed = Number(value);
82
+ return Number.isFinite(parsed) ? parsed : void 0;
83
+ };
84
+ var zero = (node, path) => num(node, path) ?? 0;
85
+ var PAYMENT_BY_CODE = {
86
+ "01": "dinheiro",
87
+ "02": "outro",
88
+ // cheque
89
+ "03": "cartao_credito",
90
+ "04": "cartao_debito",
91
+ "05": "credito_loja",
92
+ "10": "outro",
93
+ // vale alimentação
94
+ "11": "outro",
95
+ // vale refeição
96
+ "12": "vale_presente",
97
+ "13": "outro",
98
+ // vale combustível
99
+ "14": "duplicata_mercantil",
100
+ "15": "boleto",
101
+ "16": "outro",
102
+ // depósito bancário
103
+ "17": "pix",
104
+ "18": "outro",
105
+ // transferência bancária
106
+ "19": "outro",
107
+ // programa de fidelidade
108
+ "90": "sem_pagamento",
109
+ "99": "outro"
110
+ };
111
+ var PAYMENT_LABEL = {
112
+ dinheiro: "Dinheiro",
113
+ cartao_credito: "Cart\xE3o de cr\xE9dito",
114
+ cartao_debito: "Cart\xE3o de d\xE9bito",
115
+ credito_loja: "Cr\xE9dito na loja",
116
+ vale_presente: "Vale presente",
117
+ duplicata_mercantil: "Duplicata mercantil",
118
+ boleto: "Boleto",
119
+ pix: "PIX",
120
+ sem_pagamento: "Sem pagamento",
121
+ outro: "Outro"
122
+ };
123
+ function paymentTypeFromCode(code) {
124
+ if (!code) return "outro";
125
+ return PAYMENT_BY_CODE[code.padStart(2, "0")] ?? "outro";
126
+ }
127
+ function paymentLabel(type) {
128
+ return PAYMENT_LABEL[type] ?? "Outro";
129
+ }
130
+ var onlyDigits = (value) => (value ?? "").replace(/\D+/g, "");
131
+ function parseNfeXml(xml) {
132
+ if (!xml || !xml.trim()) throw new NfeParseError("O arquivo est\xE1 vazio.");
133
+ const root = parseXml(xml);
134
+ const proc = kid(root, "nfeProc");
135
+ const nfe = kid(proc ?? root, "NFe") ?? kid(root, "NFe");
136
+ const inf = kid(nfe, "infNFe");
137
+ if (!inf) {
138
+ throw new NfeParseError(
139
+ "Este arquivo n\xE3o \xE9 uma NF-e: n\xE3o encontrei <infNFe>. Se veio do fornecedor, pe\xE7a o XML da nota, n\xE3o o DANFE em PDF."
140
+ );
141
+ }
142
+ const accessKey = onlyDigits(inf.attrs.Id ?? inf.attrs.id ?? "");
143
+ if (accessKey.length !== 44) {
144
+ throw new NfeParseError(
145
+ `A chave de acesso tem ${accessKey.length} d\xEDgitos e deveria ter 44. O arquivo pode estar truncado.`
146
+ );
147
+ }
148
+ const ide = kid(inf, "ide");
149
+ const model = str(ide, "mod") ?? "55";
150
+ if (model !== "55") {
151
+ throw new NfeParseError(
152
+ `Este XML \xE9 modelo ${model}${model === "65" ? " (NFC-e, nota de venda no balc\xE3o)" : ""}, e a entrada de notas recebe NF-e modelo 55 de fornecedor.`
153
+ );
154
+ }
155
+ const emit = kid(inf, "emit");
156
+ const ender = kid(emit, "enderEmit");
157
+ const total = at(inf, "total.ICMSTot");
158
+ const dhEmi = str(ide, "dhEmi") ?? str(ide, "dEmi") ?? "";
159
+ const items = kids(inf, "det").map((det, i) => {
160
+ const prod = kid(det, "prod");
161
+ const imposto = kid(det, "imposto");
162
+ const icms = kid(imposto, "ICMS")?.children[0];
163
+ const pis = kid(imposto, "PIS")?.children[0];
164
+ const cofins = kid(imposto, "COFINS")?.children[0];
165
+ const ipi = kid(kid(imposto, "IPI"), "IPITrib");
166
+ return {
167
+ itemNumber: Number(det.attrs.nItem ?? i + 1) || i + 1,
168
+ supplierProductCode: str(prod, "cProd") ?? "",
169
+ eanBarcode: str(prod, "cEAN"),
170
+ description: str(prod, "xProd") ?? "(sem descri\xE7\xE3o)",
171
+ ncm: str(prod, "NCM"),
172
+ cest: str(prod, "CEST"),
173
+ cfop: str(prod, "CFOP"),
174
+ unit: str(prod, "uCom"),
175
+ quantity: zero(prod, "qCom"),
176
+ unitPrice: zero(prod, "vUnCom"),
177
+ total: zero(prod, "vProd"),
178
+ discount: num(prod, "vDesc"),
179
+ freight: num(prod, "vFrete"),
180
+ other: num(prod, "vOutro"),
181
+ icmsCst: str(icms, "CST"),
182
+ icmsCsosn: str(icms, "CSOSN"),
183
+ icmsOrigin: str(icms, "orig"),
184
+ icmsBase: num(icms, "vBC"),
185
+ icmsRate: num(icms, "pICMS"),
186
+ icmsValue: num(icms, "vICMS"),
187
+ pisValue: num(pis, "vPIS"),
188
+ cofinsValue: num(cofins, "vCOFINS"),
189
+ ipiValue: num(ipi, "vIPI")
190
+ };
191
+ });
192
+ if (items.length === 0) {
193
+ throw new NfeParseError("A nota n\xE3o tem nenhum item \u2014 n\xE3o h\xE1 o que dar entrada.");
194
+ }
195
+ const cobr = kid(inf, "cobr");
196
+ const duplicates = kids(cobr, "dup").map((dup, i) => ({
197
+ index: i + 1,
198
+ number: str(dup, "nDup"),
199
+ dueDate: str(dup, "dVenc"),
200
+ value: zero(dup, "vDup")
201
+ }));
202
+ const payments = kids(kid(inf, "pag"), "detPag").map((det, i) => {
203
+ const code = str(det, "tPag");
204
+ const type = paymentTypeFromCode(code);
205
+ return {
206
+ index: i + 1,
207
+ indicator: str(det, "indPag"),
208
+ code,
209
+ label: paymentLabel(type),
210
+ value: zero(det, "vPag")
211
+ };
212
+ });
213
+ const prot = at(proc, "protNFe.infProt");
214
+ return {
215
+ accessKey,
216
+ nfeNumber: str(ide, "nNF") ?? "",
217
+ nfeSeries: str(ide, "serie") ?? "",
218
+ nfeModel: model,
219
+ // The layout writes an ISO instant with offset; the inbox stores a date.
220
+ issueDate: dhEmi.slice(0, 10),
221
+ natureOperation: str(ide, "natOp"),
222
+ supplier: {
223
+ cnpj: onlyDigits(str(emit, "CNPJ") ?? str(emit, "CPF")),
224
+ name: str(emit, "xNome") ?? "",
225
+ tradeName: str(emit, "xFant"),
226
+ ie: str(emit, "IE"),
227
+ city: str(ender, "xMun"),
228
+ state: str(ender, "UF")
229
+ },
230
+ totals: {
231
+ total: zero(total, "vNF"),
232
+ products: zero(total, "vProd"),
233
+ discount: zero(total, "vDesc"),
234
+ freight: zero(total, "vFrete"),
235
+ insurance: zero(total, "vSeg"),
236
+ other: zero(total, "vOutro"),
237
+ icmsBase: num(total, "vBC"),
238
+ icmsValue: num(total, "vICMS"),
239
+ pisValue: num(total, "vPIS"),
240
+ cofinsValue: num(total, "vCOFINS"),
241
+ ipiValue: num(total, "vIPI")
242
+ },
243
+ items,
244
+ duplicates,
245
+ payments,
246
+ additionalInfo: str(at(inf, "infAdic"), "infCpl"),
247
+ protocol: str(prot, "nProt"),
248
+ authorizedAt: str(prot, "dhRecbto")
249
+ };
250
+ }
251
+ function toStagePayload(note, options = {}) {
252
+ return {
253
+ // O ARQUIVO VIAJA JUNTO. Ele já está na mão aqui — o parser acabou de lê-lo —
254
+ // e sem isto os bytes morriam no fim da função: nota fiscal tem guarda legal
255
+ // de cinco anos e a única cópia ficava na pasta de downloads de quem importou.
256
+ ...options.xml ? { xml: options.xml } : {},
257
+ access_key: note.accessKey,
258
+ nfe_number: note.nfeNumber,
259
+ nfe_series: note.nfeSeries,
260
+ nfe_model: note.nfeModel,
261
+ issue_date: note.issueDate,
262
+ nature_operation: note.natureOperation ?? null,
263
+ additional_info: note.additionalInfo ?? null,
264
+ unit_id: options.unitId ?? null,
265
+ source: options.source ?? "xml_upload",
266
+ supplier: {
267
+ cnpj: note.supplier.cnpj,
268
+ name: note.supplier.name,
269
+ ie: note.supplier.ie ?? null
270
+ },
271
+ totals: {
272
+ total: note.totals.total,
273
+ products: note.totals.products,
274
+ discount: note.totals.discount,
275
+ freight: note.totals.freight,
276
+ insurance: note.totals.insurance,
277
+ other: note.totals.other
278
+ },
279
+ items: note.items.map((i) => ({
280
+ item_number: i.itemNumber,
281
+ supplier_product_code: i.supplierProductCode,
282
+ ean_barcode: i.eanBarcode ?? null,
283
+ description: i.description,
284
+ ncm: i.ncm ?? null,
285
+ cest: i.cest ?? null,
286
+ cfop: i.cfop ?? null,
287
+ unit: i.unit ?? null,
288
+ quantity: i.quantity,
289
+ unit_price: i.unitPrice,
290
+ total: i.total,
291
+ discount: i.discount ?? null,
292
+ freight: i.freight ?? null,
293
+ other: i.other ?? null,
294
+ icms_cst: i.icmsCst ?? null,
295
+ icms_csosn: i.icmsCsosn ?? null,
296
+ icms_base: i.icmsBase ?? null,
297
+ icms_rate: i.icmsRate ?? null,
298
+ icms_value: i.icmsValue ?? null,
299
+ pis_value: i.pisValue ?? null,
300
+ cofins_value: i.cofinsValue ?? null,
301
+ ipi_value: i.ipiValue ?? null
302
+ })),
303
+ // The instalment carries the payment form the note declared, so the
304
+ // reconciliation dialog opens with `Boleto` already chosen instead of blank.
305
+ duplicates: note.duplicates.map((d) => ({
306
+ index: d.index,
307
+ number: d.number ?? null,
308
+ due_date: d.dueDate ?? null,
309
+ value: d.value,
310
+ payment_type: note.payments.length === 1 ? paymentTypeFromCode(note.payments[0].code) : null
311
+ })),
312
+ payments: note.payments.map((p) => ({
313
+ index: p.index,
314
+ indicator: p.indicator ?? null,
315
+ code: p.code ?? null,
316
+ label: p.label ?? null,
317
+ value: p.value
318
+ }))
319
+ };
320
+ }
321
+
322
+ // src/data/inbox.ts
323
+ function sb() {
324
+ const supabase = getSupabaseClientOptional();
325
+ if (!supabase) throw new Error("Supabase not initialized");
326
+ return supabase;
327
+ }
328
+ var NOTE_COLUMNS = `
329
+ id, access_key, nfe_number, nfe_series, issue_date, received_at, status,
330
+ manifestation_status, manifested_at, manifestation_attempts,
331
+ nature_operation, supplier_name, supplier_cnpj, supplier_party_id, xml_content,
332
+ total_value, total_products, total_discount, total_freight,
333
+ invoice_id, inventory_operation_id, stocked_at, reviewed_at,
334
+ default_chart_account_id, default_location_id, unit_id, document_id, metadata,
335
+ items:plg_fiscalbr_inbox_items(id, reconciled, entry_type),
336
+ duplicates:plg_fiscalbr_inbox_duplicates(id, value)
337
+ `;
338
+ function rowToNote(row) {
339
+ const items = row.items ?? [];
340
+ const duplicates = row.duplicates ?? [];
341
+ return {
342
+ id: String(row.id),
343
+ accessKey: String(row.access_key ?? ""),
344
+ number: String(row.nfe_number ?? ""),
345
+ series: String(row.nfe_series ?? ""),
346
+ issueDate: row.issue_date ?? null,
347
+ receivedAt: row.received_at ?? null,
348
+ status: row.status ?? "pending",
349
+ manifestationStatus: row.manifestation_status ?? null,
350
+ manifestedAt: row.manifested_at ?? null,
351
+ manifestationAttempts: Number(row.manifestation_attempts ?? 0),
352
+ natureOperation: row.nature_operation ?? null,
353
+ supplierName: row.supplier_name ?? "",
354
+ supplierCnpj: row.supplier_cnpj ?? null,
355
+ supplierPartyId: row.supplier_party_id ?? null,
356
+ total: Number(row.total_value ?? 0),
357
+ itemCount: items.length,
358
+ reconciledCount: items.filter((i) => i.reconciled).length,
359
+ instalmentCount: duplicates.length,
360
+ // The sum of instalments, because that is the total the payable is opened
361
+ // with (financial/084) — showing the note total here would make the list and
362
+ // Contas a Pagar disagree on 6,3% of notes.
363
+ instalmentTotal: duplicates.reduce((acc, d) => acc + Number(d.value ?? 0), 0),
364
+ invoiceId: row.invoice_id ?? null,
365
+ operationId: row.inventory_operation_id ?? null,
366
+ stockedAt: row.stocked_at ?? null,
367
+ documentId: row.document_id ?? null,
368
+ hasXml: Boolean(row.xml_content),
369
+ unitId: row.unit_id ?? null
370
+ };
371
+ }
372
+ async function listInboxNotes(options = {}) {
373
+ const { filter = "all", search, unitId, from, to, limit = 200 } = options;
374
+ let query = sb().from("plg_fiscalbr_inbox_documents").select(NOTE_COLUMNS);
375
+ if (unitId) query = query.eq("unit_id", unitId);
376
+ if (from) query = query.or(`issue_date.gte.${from},issue_date.is.null`);
377
+ if (to) query = query.or(`issue_date.lte.${to},issue_date.is.null`);
378
+ if (filter === "stocked") query = query.not("stocked_at", "is", null);
379
+ if (filter === "pending") query = query.is("stocked_at", null);
380
+ if (filter === "rejected") query = query.eq("status", "rejected");
381
+ if (filter === "manifest_error") query = query.eq("manifestation_status", "erro");
382
+ if (search?.trim()) {
383
+ const term = search.trim();
384
+ query = query.or(
385
+ `supplier_name.ilike.%${term}%,nfe_number.ilike.%${term}%,access_key.ilike.%${term}%`
386
+ );
387
+ }
388
+ const { data, error } = await query.order("issue_date", { ascending: false, nullsFirst: false }).order("received_at", { ascending: false }).limit(limit);
389
+ if (error) throw error;
390
+ return (data ?? []).map(rowToNote);
391
+ }
392
+ var ITEM_COLUMNS = `
393
+ id, item_number, supplier_product_code, ean_barcode, description, ncm, cest, cfop,
394
+ unit_commercial, quantity_commercial, unit_price_commercial, total_price, discount,
395
+ unit_taxable, quantity_taxable,
396
+ icms_cst, icms_csosn, icms_base, icms_rate, icms_value, pis_value, cofins_value, ipi_value,
397
+ product_id, entry_type, chart_account_id, stock_unit_id, stock_quantity,
398
+ conversion_factor, cost_mode, unit_cost_new, reconciled, stock_movement_id,
399
+ shortage_quantity, shortage_reason, shortage_note, suggested_by, suggestion_confidence
400
+ `;
401
+ function rowToItem(row) {
402
+ const quantity = Number(row.quantity_commercial ?? 0);
403
+ const factor = Number(row.conversion_factor ?? 1) || 1;
404
+ return {
405
+ id: String(row.id),
406
+ itemNumber: Number(row.item_number ?? 0),
407
+ supplierProductCode: row.supplier_product_code ?? "",
408
+ eanBarcode: row.ean_barcode ?? null,
409
+ description: row.description ?? "",
410
+ ncm: row.ncm ?? null,
411
+ cest: row.cest ?? null,
412
+ cfop: row.cfop ?? null,
413
+ unit: row.unit_commercial ?? null,
414
+ quantity,
415
+ unitPrice: Number(row.unit_price_commercial ?? 0),
416
+ total: Number(row.total_price ?? 0),
417
+ unitTaxable: row.unit_taxable ?? null,
418
+ quantityTaxable: row.quantity_taxable == null ? null : Number(row.quantity_taxable),
419
+ discount: row.discount == null ? null : Number(row.discount),
420
+ icmsCst: row.icms_cst ?? null,
421
+ icmsCsosn: row.icms_csosn ?? null,
422
+ icmsValue: row.icms_value == null ? null : Number(row.icms_value),
423
+ productId: row.product_id ?? null,
424
+ entryType: row.entry_type ?? null,
425
+ chartAccountId: row.chart_account_id ?? null,
426
+ stockUnitId: row.stock_unit_id ?? null,
427
+ conversionFactor: factor,
428
+ // Derived AND stored by the RPC. Reading the stored value keeps the screen
429
+ // showing the number the operator accepted, and falls back to the product of
430
+ // the two only while the decision has not been saved yet.
431
+ stockQuantity: row.stock_quantity == null ? quantity * factor : Number(row.stock_quantity),
432
+ costMode: row.cost_mode ?? "novo",
433
+ unitCostNew: row.unit_cost_new == null ? null : Number(row.unit_cost_new),
434
+ reconciled: Boolean(row.reconciled),
435
+ suggestedBy: row.suggested_by === "ai" || row.suggested_by === "ean" || row.suggested_by === "supplier_map" ? row.suggested_by : null,
436
+ suggestionConfidence: row.suggestion_confidence == null ? null : Number(row.suggestion_confidence),
437
+ stockMovementId: row.stock_movement_id ?? null,
438
+ shortageQuantity: row.shortage_quantity == null ? null : Number(row.shortage_quantity),
439
+ shortageReason: row.shortage_reason ?? null,
440
+ shortageNote: row.shortage_note ?? null
441
+ };
442
+ }
443
+ async function getInboxNote(id) {
444
+ const { data, error } = await sb().from("plg_fiscalbr_inbox_documents").select(NOTE_COLUMNS).eq("id", id).maybeSingle();
445
+ if (error) throw error;
446
+ if (!data) return null;
447
+ const [items, instalments, payments] = await Promise.all([
448
+ sb().from("plg_fiscalbr_inbox_items").select(ITEM_COLUMNS).eq("inbox_document_id", id).order("item_number"),
449
+ sb().from("plg_fiscalbr_inbox_duplicates").select("id, duplicate_index, number, due_date, value, payment_type, barcode, pix_key, payment_method_id").eq("inbox_document_id", id).order("duplicate_index"),
450
+ sb().from("plg_fiscalbr_inbox_payments").select("id, payment_index, payment_type_code, payment_type_label, value").eq("inbox_document_id", id).order("payment_index")
451
+ ]);
452
+ if (items.error) throw items.error;
453
+ if (instalments.error) throw instalments.error;
454
+ return {
455
+ ...rowToNote(data),
456
+ natureOperation: data.nature_operation ?? null,
457
+ defaultChartAccountId: data.default_chart_account_id ?? null,
458
+ defaultLocationId: data.default_location_id ?? null,
459
+ items: (items.data ?? []).map(rowToItem),
460
+ instalments: (instalments.data ?? []).map((d) => ({
461
+ id: String(d.id),
462
+ index: Number(d.duplicate_index ?? 0),
463
+ number: d.number ?? null,
464
+ dueDate: d.due_date ?? null,
465
+ amount: Number(d.value ?? 0),
466
+ paymentType: d.payment_type ?? null,
467
+ barcode: d.barcode ?? null,
468
+ pixKey: d.pix_key ?? null,
469
+ paymentMethodId: d.payment_method_id ?? null
470
+ })),
471
+ declaredPayments: (payments.data ?? []).map((p) => ({
472
+ index: Number(p.payment_index ?? 0),
473
+ code: p.payment_type_code ?? null,
474
+ label: p.payment_type_label ?? null,
475
+ value: Number(p.value ?? 0)
476
+ }))
477
+ };
478
+ }
479
+ async function stageNoteFromXml(xml, options = {}) {
480
+ const parsed = parseNfeXml(xml);
481
+ const { data, error } = await sb().rpc("fiscalbr_stage_inbox_document", {
482
+ p_note: toStagePayload(parsed, { unitId: options.unitId ?? null, xml })
483
+ });
484
+ if (error) throw error;
485
+ const res = data ?? {};
486
+ return {
487
+ documentId: String(res.document_id ?? ""),
488
+ accessKey: String(res.access_key ?? parsed.accessKey),
489
+ alreadyAccepted: Boolean(res.already_accepted),
490
+ supplierPartyId: res.supplier_party_id ?? null,
491
+ itemCount: Number(res.items ?? 0),
492
+ matchedCount: Number(res.matched ?? 0),
493
+ parsed
494
+ };
495
+ }
496
+ async function fillInboxLines(documentId, xml) {
497
+ const payload = toStagePayload(parseNfeXml(xml));
498
+ const { data, error } = await sb().rpc("fiscalbr_fill_inbox_lines", {
499
+ p_document_id: documentId,
500
+ p_items: payload.items
501
+ });
502
+ if (error) throw error;
503
+ return Number(data?.items ?? 0);
504
+ }
505
+ async function healInboxNote(documentId) {
506
+ const { data, error } = await sb().rpc("fiscalbr_heal_inbox_note", { p_document_id: documentId });
507
+ if (error) throw error;
508
+ const r = data ?? {};
509
+ return { changed: Boolean(r.changed), released: Boolean(r.released) };
510
+ }
511
+ async function getNotePayable(invoiceId) {
512
+ const { data, error } = await sb().from("plg_financial_invoices").select("id, number, total, cancelled_at").eq("id", invoiceId).maybeSingle();
513
+ if (error) throw error;
514
+ if (!data) return null;
515
+ return { id: data.id, number: data.number ?? null, total: Number(data.total ?? 0), cancelledAt: data.cancelled_at ?? null };
516
+ }
517
+ var PullByKeyError = class extends Error {
518
+ constructor(message, code, options) {
519
+ super(message);
520
+ this.code = code;
521
+ this.options = options;
522
+ }
523
+ };
524
+ async function pullNoteByKey(key, options = {}) {
525
+ const { data, error } = await sb().functions.invoke("fiscalbr-dfe-pull", {
526
+ body: { key: key.replace(/\D/g, ""), legalEntityId: options.legalEntityId, tenantId: getActiveTenantId() ?? null }
527
+ });
528
+ if (error) {
529
+ let body = null;
530
+ try {
531
+ body = await error?.context?.json?.();
532
+ } catch {
533
+ }
534
+ throw new PullByKeyError(body?.error || error.message || "Falha ao falar com a SEFAZ.", body?.code, body?.options);
535
+ }
536
+ if (data?.pending) return { status: "pending", message: String(data.message ?? "") };
537
+ if (!data?.xml) throw new PullByKeyError("A SEFAZ n\xE3o devolveu o XML desta nota.");
538
+ return { status: "staged", ...await stageNoteFromXml(String(data.xml), { unitId: options.unitId }) };
539
+ }
540
+ async function getInboxNotePdf(accessKey) {
541
+ const { data, error } = await sb().functions.invoke("fiscalbr-dfe-pull", {
542
+ body: { action: "pdf", key: accessKey, tenantId: getActiveTenantId() ?? null }
543
+ });
544
+ if (error) {
545
+ let body = null;
546
+ try {
547
+ body = await error?.context?.json?.();
548
+ } catch {
549
+ }
550
+ throw new PullByKeyError(body?.error || error.message || "Falha ao buscar o DANFE.", body?.code);
551
+ }
552
+ if (!(data instanceof Blob)) throw new PullByKeyError("O servi\xE7o fiscal n\xE3o devolveu um PDF.");
553
+ return data;
554
+ }
555
+ async function saveNoteDecision(input) {
556
+ const { data, error } = await sb().rpc("fiscalbr_save_inbox_decision", {
557
+ p_document_id: input.documentId,
558
+ p_items: input.items.map((i) => ({
559
+ id: i.id,
560
+ product_id: i.productId ?? null,
561
+ entry_type: i.entryType ?? null,
562
+ chart_account_id: i.chartAccountId ?? null,
563
+ stock_unit_id: i.stockUnitId ?? null,
564
+ conversion_factor: i.conversionFactor ?? 1,
565
+ cost_mode: i.costMode ?? "novo",
566
+ suggested_by: i.suggestedBy ?? null,
567
+ suggestion_confidence: i.suggestionConfidence ?? null,
568
+ shortage_quantity: i.shortageQuantity ?? null,
569
+ shortage_reason: i.shortageReason ?? null,
570
+ shortage_note: i.shortageNote ?? null
571
+ })),
572
+ p_default_chart_account_id: input.defaultChartAccountId ?? null,
573
+ p_default_location_id: input.defaultLocationId ?? null,
574
+ p_duplicates: input.instalments ? input.instalments.map((d) => ({
575
+ index: d.index,
576
+ number: d.number ?? null,
577
+ due_date: d.dueDate ?? null,
578
+ value: d.amount,
579
+ payment_type: d.paymentType ?? null,
580
+ barcode: d.barcode ?? null,
581
+ pix_key: d.pixKey ?? null,
582
+ payment_method_id: d.paymentMethodId ?? null
583
+ })) : null
584
+ });
585
+ if (error) throw error;
586
+ const res = data ?? {};
587
+ return {
588
+ saved: Number(res.saved ?? 0),
589
+ items: Number(res.items ?? 0),
590
+ reconciled: Number(res.reconciled ?? 0)
591
+ };
592
+ }
593
+ async function linkInboxSupplier(documentId, create = false) {
594
+ const { data, error } = await sb().rpc("fiscalbr_link_inbox_supplier", { p_document_id: documentId, p_create: create });
595
+ if (error) throw error;
596
+ const res = data ?? {};
597
+ return { supplierPartyId: res.supplier_party_id ?? null, created: Boolean(res.created) };
598
+ }
599
+ async function acceptNote(documentId, locationId) {
600
+ await linkInboxSupplier(documentId, true).catch((e) => {
601
+ if (e?.code !== "PGRST202" && e?.code !== "42883") throw e;
602
+ });
603
+ const { data, error } = await sb().rpc("fiscalbr_accept_inbox_document", {
604
+ p_document_id: documentId,
605
+ p_location_id: locationId ?? null
606
+ });
607
+ if (error) throw error;
608
+ const res = data ?? {};
609
+ return {
610
+ documentId: String(res.document_id ?? documentId),
611
+ alreadyAccepted: Boolean(res.already_accepted),
612
+ invoiceId: res.invoice_id ?? null,
613
+ invoiceTotal: Number(res.invoice_total ?? 0),
614
+ instalments: Number(res.installments ?? 0),
615
+ operationId: res.operation_id ?? null,
616
+ stockLines: Number(res.stock_lines ?? 0)
617
+ };
618
+ }
619
+ async function rejectNote(documentId, reason) {
620
+ const { error } = await sb().rpc("fiscalbr_reject_inbox_document", {
621
+ p_document_id: documentId,
622
+ p_reason: reason ?? null
623
+ });
624
+ if (error) throw error;
625
+ }
626
+ async function searchProducts(term, limit = 20) {
627
+ let query = sb().from("products").select("id, name, sku, unit, cost, kind").eq("is_active", true).limit(limit);
628
+ if (term.trim()) query = query.or(`name.ilike.%${term.trim()}%,sku.ilike.%${term.trim()}%`);
629
+ const { data, error } = await query.order("name");
630
+ if (error) throw error;
631
+ return (data ?? []).map((p) => ({
632
+ id: String(p.id),
633
+ name: p.name ?? "",
634
+ sku: p.sku ?? null,
635
+ unit: p.unit ?? null,
636
+ cost: p.cost == null ? null : Number(p.cost),
637
+ kind: p.kind ?? null
638
+ }));
639
+ }
640
+ async function getInboxNoteXml(id) {
641
+ const { data, error } = await sb().from("plg_fiscalbr_inbox_documents").select("xml_content").eq("id", id).maybeSingle();
642
+ if (error) throw error;
643
+ return data?.xml_content ?? null;
644
+ }
645
+ async function findInboxNoteByNumber(nfeNumber) {
646
+ const n = (nfeNumber ?? "").replace(/\D/g, "");
647
+ if (!n) return null;
648
+ const { data, error } = await sb().from("plg_fiscalbr_inbox_documents").select("id").eq("nfe_number", n).limit(2);
649
+ if (error) throw error;
650
+ const linhas = data ?? [];
651
+ return linhas.length === 1 ? linhas[0].id : null;
652
+ }
653
+ async function findInboxNoteByOperation(operationId) {
654
+ if (!operationId) return null;
655
+ const { data, error } = await sb().from("plg_fiscalbr_inbox_documents").select("id").eq("inventory_operation_id", operationId).limit(1).maybeSingle();
656
+ if (error) throw error;
657
+ return data?.id ?? null;
658
+ }
659
+ async function suggestNoteItems(documentId, reevaluate = false) {
660
+ const { data, error } = await sb().functions.invoke("fiscalbr-suggest-note-items", {
661
+ body: { documentId, reevaluate, tenantId: getActiveTenantId() ?? null }
662
+ });
663
+ if (error) {
664
+ const corpo = await error.context?.json?.().catch(() => null);
665
+ throw new Error(corpo?.error ?? error.message);
666
+ }
667
+ const res = data ?? {};
668
+ if (res.ok === false) throw new Error(String(res.error ?? "A sugest\xE3o falhou."));
669
+ return {
670
+ applied: Number(res.applied ?? 0),
671
+ typedOnly: Number(res.typedOnly ?? 0),
672
+ bound: Number(res.bound ?? 0),
673
+ boundByEan: Number(res.boundByEan ?? 0),
674
+ newProductNames: Array.isArray(res.newProductNames) ? res.newProductNames.filter((n) => typeof n === "string") : [],
675
+ warnings: Array.isArray(res.warnings) ? res.warnings.filter((w) => typeof w === "string") : []
676
+ };
677
+ }
678
+ async function listProductsByIds(ids) {
679
+ if (ids.length === 0) return [];
680
+ const { data, error } = await sb().from("products").select("id, name, sku, unit, cost, kind").in("id", ids);
681
+ if (error) throw error;
682
+ return (data ?? []).map((p) => ({
683
+ id: String(p.id),
684
+ name: p.name ?? "",
685
+ sku: p.sku ?? null,
686
+ unit: p.unit ?? null,
687
+ cost: p.cost == null ? null : Number(p.cost),
688
+ kind: p.kind ?? null
689
+ }));
690
+ }
691
+ async function listMeasurementUnits() {
692
+ const { data, error } = await sb().from("plg_inventory_measurement_units").select("id, name, abbreviation, is_base").eq("is_active", true).order("name");
693
+ if (error) throw error;
694
+ return (data ?? []).map((u) => ({
695
+ id: String(u.id),
696
+ name: u.name ?? "",
697
+ abbreviation: u.abbreviation ?? "",
698
+ isBase: Boolean(u.is_base)
699
+ }));
700
+ }
701
+ async function listChartAccounts() {
702
+ const { data, error } = await sb().from("plg_financial_chart_of_accounts").select("id, code, name, account_type, node_type, parent_id, is_active").order("code");
703
+ if (error) throw error;
704
+ const rows = (data ?? []).filter((a) => a.is_active !== false);
705
+ const parents = new Set(rows.map((a) => a.parent_id).filter(Boolean).map(String));
706
+ return rows.filter((a) => a.node_type !== "group" && !parents.has(String(a.id))).map((a) => ({
707
+ id: String(a.id),
708
+ code: a.code ?? "",
709
+ name: a.name ?? "",
710
+ kind: a.account_type ?? null
711
+ }));
712
+ }
713
+ async function listStockLocations() {
714
+ const { data, error } = await sb().from("plg_inventory_stock_locations").select("id, name, unit_id, is_active").order("name");
715
+ if (error) throw error;
716
+ return (data ?? []).filter((l) => l.is_active !== false).map((l) => ({ id: String(l.id), name: l.name ?? "", unitId: l.unit_id ?? null }));
717
+ }
718
+ async function listSupplierMap(supplierCnpj) {
719
+ if (!supplierCnpj) return [];
720
+ const { data, error } = await sb().from("plg_fiscalbr_supplier_product_map").select("supplier_product_code, product_id, hit_count, last_used_at").eq("supplier_cnpj", supplierCnpj);
721
+ if (error) throw error;
722
+ return (data ?? []).map((m) => ({
723
+ supplierProductCode: m.supplier_product_code ?? "",
724
+ productId: String(m.product_id),
725
+ hitCount: Number(m.hit_count ?? 0),
726
+ lastUsedAt: m.last_used_at ?? null
727
+ }));
728
+ }
729
+ var NOTE_SUBJECT = "fiscalbr_inbox_document";
730
+ var INVOICE_SUBJECT = "financial_invoice";
731
+ async function listNoteAttachments(noteId, invoiceId) {
732
+ const subjects = [`and(subject_type.eq.${NOTE_SUBJECT},subject_id.eq.${noteId})`];
733
+ if (invoiceId) subjects.push(`and(subject_type.eq.${INVOICE_SUBJECT},subject_id.eq.${invoiceId})`);
734
+ const { data, error } = await sb().from("documents").select("id, file_name, file_size, mime_type, storage_bucket, storage_path, created_at").or(subjects.join(",")).eq("is_active", true).order("created_at");
735
+ if (error) throw error;
736
+ const rows = data ?? [];
737
+ const signed = /* @__PURE__ */ new Map();
738
+ const byBucket = /* @__PURE__ */ new Map();
739
+ for (const r of rows) {
740
+ if (!r.storage_bucket || !r.storage_path) continue;
741
+ byBucket.set(r.storage_bucket, [...byBucket.get(r.storage_bucket) ?? [], r.storage_path]);
742
+ }
743
+ for (const [bucket, paths] of byBucket) {
744
+ const { data: urls } = await sb().storage.from(bucket).createSignedUrls(paths, 60 * 30);
745
+ for (const u of urls ?? []) if (u.path && u.signedUrl) signed.set(u.path, u.signedUrl);
746
+ }
747
+ return rows.map((r) => ({
748
+ id: r.id,
749
+ fileName: r.file_name ?? "Anexo",
750
+ fileSize: r.file_size ?? void 0,
751
+ mimeType: r.mime_type ?? void 0,
752
+ url: signed.get(r.storage_path) ?? void 0,
753
+ createdAt: r.created_at
754
+ }));
755
+ }
756
+ async function addNoteAttachment(noteId, invoiceId, file) {
757
+ const tenantId = getActiveTenantId();
758
+ if (!tenantId) throw new Error("Conta sem empresa ativa");
759
+ const subjectType = invoiceId ? INVOICE_SUBJECT : NOTE_SUBJECT;
760
+ const subjectId = invoiceId ?? noteId;
761
+ const path = `${tenantId}/${subjectType}/${subjectId}/${Date.now()}_${file.name.replace(/[^\w.\-]+/g, "_")}`;
762
+ const { error: upErr } = await sb().storage.from("attachments").upload(path, file, { contentType: file.type || void 0 });
763
+ if (upErr) throw upErr;
764
+ const { error } = await sb().from("documents").insert({
765
+ tenant_id: tenantId,
766
+ kind: "attachment",
767
+ status: "active",
768
+ subject_type: subjectType,
769
+ subject_id: subjectId,
770
+ file_name: file.name,
771
+ file_size: file.size,
772
+ mime_type: file.type || null,
773
+ storage_provider: "supabase",
774
+ storage_bucket: "attachments",
775
+ storage_path: path
776
+ });
777
+ if (error) {
778
+ await sb().storage.from("attachments").remove([path]).catch(() => {
779
+ });
780
+ throw error;
781
+ }
782
+ }
783
+ async function removeNoteAttachment(id) {
784
+ const { error } = await sb().from("documents").update({ is_active: false }).eq("id", id);
785
+ if (error) throw error;
786
+ }
787
+ async function moveNoteAttachmentsToInvoice(noteId, invoiceId) {
788
+ const { error } = await sb().from("documents").update({ subject_type: INVOICE_SUBJECT, subject_id: invoiceId }).eq("subject_type", NOTE_SUBJECT).eq("subject_id", noteId);
789
+ if (error) throw error;
790
+ }
791
+ async function extractPaymentCode(file) {
792
+ const base64 = await new Promise((resolve, reject) => {
793
+ const reader = new FileReader();
794
+ reader.onload = () => {
795
+ const r = String(reader.result);
796
+ resolve(r.slice(r.indexOf(",") + 1));
797
+ };
798
+ reader.onerror = () => reject(reader.error);
799
+ reader.readAsDataURL(file);
800
+ });
801
+ const { data, error } = await sb().functions.invoke("fiscalbr-payment-extract", {
802
+ body: { name: file.name, mime: file.type, base64 }
803
+ });
804
+ if (error) {
805
+ let body = null;
806
+ try {
807
+ body = await error?.context?.json?.();
808
+ } catch {
809
+ }
810
+ throw new Error(body?.error || error.message || "Falha ao ler o boleto.");
811
+ }
812
+ if (!data?.ok || data.kind === "none" || !data.code) return null;
813
+ const code = String(data.code);
814
+ if (data.kind === "barcode") {
815
+ const parsed = parseBoleto(code);
816
+ return {
817
+ kind: "barcode",
818
+ code: code.replace(/\D/g, ""),
819
+ amount: parsed?.amount ?? (typeof data.amount === "number" ? data.amount : null),
820
+ dueDate: parsed?.dueDate ?? (data.due_date ?? null)
821
+ };
822
+ }
823
+ return { kind: "pix", code, amount: typeof data.amount === "number" ? data.amount : null, dueDate: data.due_date ?? null };
824
+ }
825
+ function parseBoleto(raw) {
826
+ const d = raw.replace(/\D/g, "");
827
+ if (d.startsWith("8")) return null;
828
+ let barcode;
829
+ if (d.length === 44) barcode = d;
830
+ else if (d.length === 47) barcode = d.slice(0, 4) + d[32] + d.slice(33, 47) + d.slice(4, 9) + d.slice(10, 20) + d.slice(21, 31);
831
+ else return null;
832
+ const factor = Number(barcode.slice(5, 9));
833
+ const cents = Number(barcode.slice(9, 19));
834
+ let dueDate = null;
835
+ if (factor > 0) {
836
+ const base = factor >= 1e3 && Date.now() >= Date.UTC(2025, 1, 22) ? Date.UTC(2025, 1, 22) - 1e3 * 864e5 : Date.UTC(1997, 9, 7);
837
+ dueDate = new Date(base + factor * 864e5).toISOString().slice(0, 10);
838
+ }
839
+ return { amount: cents > 0 ? cents / 100 : null, dueDate };
840
+ }
841
+ var STALE_AFTER_MS = 24 * 60 * 60 * 1e3;
842
+ function expiryFromError(error) {
843
+ const m = error?.match(/vencido em:?\s*(\d{2})\/(\d{2})\/(\d{4})/i);
844
+ return m ? `${m[3]}-${m[2]}-${m[1]}` : null;
845
+ }
846
+ async function getInboxIntakeHealth() {
847
+ const { data, error } = await sb().from("plg_fiscalbr_emitter_settings").select("legal_entity_id, dfe_sync_enabled, dfe_last_sync_at, dfe_last_error, certificate_expires_at").eq("dfe_sync_enabled", true);
848
+ if (error) throw error;
849
+ const now = Date.now();
850
+ const stalled = (data ?? []).filter((r) => {
851
+ const expired = r.certificate_expires_at && Date.parse(r.certificate_expires_at) < now;
852
+ const stale = !r.dfe_last_sync_at || now - Date.parse(r.dfe_last_sync_at) > STALE_AFTER_MS;
853
+ return expired || stale || Boolean(r.dfe_last_error);
854
+ });
855
+ if (stalled.length === 0) return null;
856
+ const since = stalled.map((r) => r.dfe_last_sync_at).filter(Boolean).sort()[0] ?? null;
857
+ const withError = stalled.find((r) => r.dfe_last_error);
858
+ const certificateExpiredAt = stalled.map((r) => r.certificate_expires_at && Date.parse(r.certificate_expires_at) < now ? r.certificate_expires_at : expiryFromError(r.dfe_last_error)).find(Boolean) ?? null;
859
+ const target = stalled.find((r) => r.certificate_expires_at && Date.parse(r.certificate_expires_at) < now) ?? withError ?? stalled[0];
860
+ const { data: unit } = await sb().from("v_unit_company_legal_entities").select("unit_id").eq("company_legal_entity_id", target.legal_entity_id).limit(1).maybeSingle();
861
+ return { stalled: stalled.length, since, certificateExpiredAt, error: withError?.dfe_last_error ?? null, unitId: unit?.unit_id ?? null };
862
+ }
863
+ async function syncNewNotes(max = 25) {
864
+ const { data: newest } = await sb().from("plg_fiscalbr_inbox_documents").select("issue_date").order("issue_date", { ascending: false }).limit(1).maybeSingle();
865
+ const since = newest?.issue_date ? new Date(Date.parse(newest.issue_date) - 2 * 864e5).toISOString().slice(0, 10) : void 0;
866
+ const { data, error } = await sb().functions.invoke("fiscalbr-dfe-pull", {
867
+ body: { action: "list", since, tenantId: getActiveTenantId() ?? null }
868
+ });
869
+ if (error) {
870
+ let body = null;
871
+ try {
872
+ body = await error?.context?.json?.();
873
+ } catch {
874
+ }
875
+ throw new PullByKeyError(body?.error || error.message || "Falha ao falar com a SEFAZ.", body?.code);
876
+ }
877
+ const listed = data?.notes ?? [];
878
+ if (listed.length === 0) return { added: 0, waiting: 0, failed: (data?.failures ?? []).length };
879
+ const keys = [...new Set(listed.map((n) => n.key))];
880
+ const { data: known } = await sb().from("plg_fiscalbr_inbox_documents").select("access_key").in("access_key", keys);
881
+ const have = new Set((known ?? []).map((r) => r.access_key));
882
+ const missing = listed.filter((n, i) => !have.has(n.key) && listed.findIndex((m) => m.key === n.key) === i).slice(0, max);
883
+ const result = { added: 0, waiting: 0, failed: (data?.failures ?? []).length };
884
+ for (const n of missing) {
885
+ try {
886
+ const r = await pullNoteByKey(n.key, { legalEntityId: n.legalEntityId });
887
+ if (r.status === "staged") result.added++;
888
+ else result.waiting++;
889
+ } catch (e) {
890
+ if (e instanceof PullByKeyError && (e.code === "summary_only" || e.code === "throttled")) result.waiting++;
891
+ else result.failed++;
892
+ }
893
+ }
894
+ return result;
895
+ }
896
+ async function listProductStockUnits(productIds) {
897
+ if (productIds.length === 0) return /* @__PURE__ */ new Map();
898
+ const { data, error } = await sb().from("plg_inventory_product_details").select("product_id, measurement_unit_id").in("product_id", productIds).not("measurement_unit_id", "is", null);
899
+ if (error) throw error;
900
+ return new Map((data ?? []).map((r) => [String(r.product_id), String(r.measurement_unit_id)]));
901
+ }
902
+ var MAX_BYTES = 15 * 1024 * 1024;
903
+ var money = (v) => formatCurrency(v || 0);
904
+ var day = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
905
+ var size = (b) => !b ? "" : b < 1024 * 1024 ? `${Math.max(1, Math.round(b / 1024))} KB` : `${(b / 1024 / 1024).toFixed(1)} MB`;
906
+ function applyPaymentCode(list, code) {
907
+ const field = code.kind === "pix" ? "pixKey" : "barcode";
908
+ const free = list.map((p, i) => ({ p, i })).filter(({ p }) => !p[field]);
909
+ const sameAmount = code.amount != null ? free.find(({ p }) => Math.abs((Number(p.amount) || 0) - code.amount) < 0.01) : void 0;
910
+ const target = sameAmount ?? free[0];
911
+ const patch = (p) => ({
912
+ ...p,
913
+ [field]: code.code,
914
+ paymentType: code.kind === "pix" ? "pix" : "boleto",
915
+ dueDate: p.dueDate || code.dueDate,
916
+ amount: Number(p.amount) > 0 ? p.amount : code.amount ?? 0
917
+ });
918
+ if (target) return { next: list.map((p, i) => i === target.i ? patch(p) : p), index: target.i };
919
+ const fresh = patch({ index: list.length, amount: 0 });
920
+ return { next: [...list, fresh], index: list.length };
921
+ }
922
+ function NoteAttachmentsCard({ noteId, invoiceId, readOnly, instalments, onInstalments }) {
923
+ const input = useRef(null);
924
+ const [items, setItems] = useState(null);
925
+ const [busy, setBusy] = useState(null);
926
+ const [over, setOver] = useState(false);
927
+ const [confirming, setConfirming] = useState(null);
928
+ const [messages, setMessages] = useState([]);
929
+ const current = useRef(instalments);
930
+ current.current = instalments;
931
+ const load = useCallback(async () => {
932
+ try {
933
+ setItems(await listNoteAttachments(noteId, invoiceId));
934
+ } catch {
935
+ setItems([]);
936
+ }
937
+ }, [noteId, invoiceId]);
938
+ useEffect(() => {
939
+ void load();
940
+ }, [load]);
941
+ const upload = async (files) => {
942
+ const out = [];
943
+ for (const file of files) {
944
+ if (!(file.type.startsWith("image/") || file.type === "application/pdf") || file.size > MAX_BYTES) {
945
+ out.push({ tone: "error", text: `${file.name}: envie imagem ou PDF de at\xE9 15 MB.` });
946
+ continue;
947
+ }
948
+ setBusy(`Anexando ${file.name}\u2026`);
949
+ try {
950
+ await addNoteAttachment(noteId, invoiceId, file);
951
+ } catch (e) {
952
+ out.push({ tone: "error", text: `${file.name}: ${e instanceof Error ? e.message : String(e)}` });
953
+ continue;
954
+ }
955
+ if (readOnly) continue;
956
+ setBusy(`Lendo ${file.name}\u2026`);
957
+ try {
958
+ const code = await extractPaymentCode(file);
959
+ if (!code) {
960
+ out.push({ tone: "warn", text: `${file.name}: anexado. N\xE3o achei c\xF3digo de boleto nem Pix nele.` });
961
+ } else {
962
+ const { next, index } = applyPaymentCode(current.current, code);
963
+ current.current = next;
964
+ onInstalments(next);
965
+ const p = next[index];
966
+ out.push({
967
+ tone: "ok",
968
+ text: `${code.kind === "pix" ? "Pix" : "Boleto"} lido \u2192 parcela ${index + 1}: ${money(Number(p.amount) || 0)}, vence ${day(p.dueDate ?? null)}. Confira e salve.`
969
+ });
970
+ }
971
+ } catch (e) {
972
+ out.push({ tone: "warn", text: `${file.name}: anexado, mas n\xE3o consegui ler o c\xF3digo (${e instanceof Error ? e.message : String(e)}).` });
973
+ }
974
+ }
975
+ setBusy(null);
976
+ setMessages(out);
977
+ await load();
978
+ };
979
+ const remove = async (a) => {
980
+ setBusy(`Removendo ${a.fileName}\u2026`);
981
+ try {
982
+ await removeNoteAttachment(a.id);
983
+ setMessages([{ tone: "ok", text: `${a.fileName} removido.` }]);
984
+ } catch (e) {
985
+ setMessages([{ tone: "error", text: `${a.fileName}: ${e instanceof Error ? e.message : String(e)}` }]);
986
+ } finally {
987
+ setBusy(null);
988
+ await load();
989
+ }
990
+ };
991
+ return /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
992
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
993
+ /* @__PURE__ */ jsxs("div", { children: [
994
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Anexos da conta" }),
995
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Boleto, comprovante, foto da nota. Boleto e Pix anexados s\xE3o lidos e preenchem a parcela." })
996
+ ] }),
997
+ items && items.length > 0 && /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs text-muted-foreground", children: [
998
+ items.length,
999
+ " ",
1000
+ items.length === 1 ? "anexo" : "anexos"
1001
+ ] })
1002
+ ] }),
1003
+ items === null ? /* @__PURE__ */ jsx("div", { className: "mt-3 h-10 animate-pulse rounded-md bg-muted" }) : items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mt-3 divide-y rounded-md border", children: items.map((a) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 px-3 py-2 text-sm", children: [
1004
+ a.mimeType?.startsWith("image/") ? /* @__PURE__ */ jsx(Image, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }) : /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
1005
+ a.url ? /* @__PURE__ */ jsx("a", { href: a.url, target: "_blank", rel: "noreferrer", className: "min-w-0 flex-1 truncate hover:underline", children: a.fileName }) : /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: a.fileName }),
1006
+ confirming === a.id ? (
1007
+ // Inline, not a dialog: this card already lives inside a modal.
1008
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
1009
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Remover?" }),
1010
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: () => {
1011
+ setConfirming(null);
1012
+ void remove(a);
1013
+ }, children: "Remover" }),
1014
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setConfirming(null), children: "Cancelar" })
1015
+ ] })
1016
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1017
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: size(a.fileSize) }),
1018
+ /* @__PURE__ */ jsx(Hint, { label: "Remover anexo", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", "aria-label": `Remover ${a.fileName}`, disabled: !!busy, onClick: () => setConfirming(a.id), children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }) }) })
1019
+ ] })
1020
+ ] }, a.id)) }),
1021
+ /* @__PURE__ */ jsxs(
1022
+ "div",
1023
+ {
1024
+ className: `mt-3 flex flex-col items-center gap-1 rounded-md border border-dashed px-4 py-5 text-center transition-colors ${over ? "border-primary bg-primary/5" : "border-input"}`,
1025
+ onDragOver: (e) => {
1026
+ e.preventDefault();
1027
+ setOver(true);
1028
+ },
1029
+ onDragLeave: () => setOver(false),
1030
+ onDrop: (e) => {
1031
+ e.preventDefault();
1032
+ setOver(false);
1033
+ void upload(Array.from(e.dataTransfer.files));
1034
+ },
1035
+ children: [
1036
+ busy ? /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
1037
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
1038
+ busy
1039
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1040
+ /* @__PURE__ */ jsx(UploadCloud, { className: "h-6 w-6 text-muted-foreground", "aria-hidden": true }),
1041
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Arraste o boleto ou o comprovante aqui" }),
1042
+ /* @__PURE__ */ jsx("div", { className: "mt-1 flex gap-2", children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => input.current?.click(), "data-testid": "note-attach", children: [
1043
+ /* @__PURE__ */ jsx(Paperclip, { className: "mr-1.5 h-4 w-4" }),
1044
+ "Anexar arquivo"
1045
+ ] }) }),
1046
+ !readOnly && /* @__PURE__ */ jsxs("p", { className: "mt-1 flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1047
+ /* @__PURE__ */ jsx(ScanLine, { className: "h-3 w-3", "aria-hidden": true }),
1048
+ " o c\xF3digo de barras e o Pix s\xE3o lidos sozinhos"
1049
+ ] })
1050
+ ] }),
1051
+ /* @__PURE__ */ jsx(
1052
+ "input",
1053
+ {
1054
+ ref: input,
1055
+ type: "file",
1056
+ multiple: true,
1057
+ accept: "image/*,application/pdf",
1058
+ className: "hidden",
1059
+ onChange: (e) => {
1060
+ void upload(Array.from(e.target.files ?? []));
1061
+ e.target.value = "";
1062
+ }
1063
+ }
1064
+ )
1065
+ ]
1066
+ }
1067
+ ),
1068
+ messages.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mt-3 space-y-1", children: messages.map((m, i) => /* @__PURE__ */ jsx(
1069
+ "li",
1070
+ {
1071
+ className: `text-xs ${m.tone === "ok" ? "text-success-soft-foreground" : m.tone === "warn" ? "text-warning-soft-foreground" : "text-destructive"}`,
1072
+ children: m.text
1073
+ },
1074
+ i
1075
+ )) })
1076
+ ] });
1077
+ }
1078
+ var formatCnpj = (d) => `${d.slice(0, 2)}.${d.slice(2, 5)}.${d.slice(5, 8)}/${d.slice(8, 12)}-${d.slice(12)}`;
1079
+ var groupKey = (digits) => digits.replace(/(\d{4})(?=\d)/g, "$1 ");
1080
+ function PullByKeyDialog({ open, onOpenChange, unitId, initialKey, onPulled }) {
1081
+ const [value, setValue] = useState("");
1082
+ const [busy, setBusy] = useState(false);
1083
+ const [error, setError] = useState(null);
1084
+ const [notice, setNotice] = useState(null);
1085
+ const [cnpjOptions, setCnpjOptions] = useState(null);
1086
+ const [legalEntityId, setLegalEntityId] = useState("");
1087
+ useEffect(() => {
1088
+ if (open) {
1089
+ if (initialKey) setValue(initialKey);
1090
+ return;
1091
+ }
1092
+ setValue("");
1093
+ setError(null);
1094
+ setNotice(null);
1095
+ setCnpjOptions(null);
1096
+ setLegalEntityId("");
1097
+ }, [open, initialKey]);
1098
+ const digits = value.replace(/\D/g, "").slice(0, 44);
1099
+ const needsCnpj = !!cnpjOptions && !legalEntityId;
1100
+ const blocked = digits.length !== 44 ? `Faltam ${44 - digits.length} d\xEDgitos` : needsCnpj ? "Escolha o CNPJ" : null;
1101
+ const pull = async () => {
1102
+ setBusy(true);
1103
+ setError(null);
1104
+ setNotice(null);
1105
+ try {
1106
+ const result = await pullNoteByKey(digits, { legalEntityId: legalEntityId || void 0, unitId });
1107
+ if (result.status === "pending") {
1108
+ setNotice(result.message);
1109
+ return;
1110
+ }
1111
+ onPulled(result);
1112
+ onOpenChange(false);
1113
+ } catch (e) {
1114
+ if (e instanceof PullByKeyError && e.code === "choose_cnpj" && e.options?.length) {
1115
+ setCnpjOptions(e.options);
1116
+ setError(null);
1117
+ return;
1118
+ }
1119
+ setError(e instanceof Error ? e.message : String(e));
1120
+ } finally {
1121
+ setBusy(false);
1122
+ }
1123
+ };
1124
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
1125
+ if (!busy) onOpenChange(o);
1126
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "md", children: [
1127
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
1128
+ /* @__PURE__ */ jsxs(ModalTitle, { className: "flex items-center gap-2", children: [
1129
+ /* @__PURE__ */ jsx(KeyRound, { className: "h-5 w-5", "aria-hidden": true }),
1130
+ "Puxar NF-e por chave de acesso"
1131
+ ] }),
1132
+ /* @__PURE__ */ jsx(ModalDescription, { children: "Para baixar uma nota que n\xE3o veio na coleta autom\xE1tica (por exemplo, emitida antes de ligar o recebimento de DANFes). Cole a chave de acesso de 44 d\xEDgitos." })
1133
+ ] }),
1134
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
1135
+ /* @__PURE__ */ jsx(
1136
+ Input,
1137
+ {
1138
+ value: groupKey(digits),
1139
+ onChange: (e) => {
1140
+ setValue(e.target.value);
1141
+ setError(null);
1142
+ setNotice(null);
1143
+ },
1144
+ onKeyDown: (e) => {
1145
+ if (e.key === "Enter" && !blocked && !busy) void pull();
1146
+ },
1147
+ placeholder: "0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000",
1148
+ inputMode: "numeric",
1149
+ autoFocus: true,
1150
+ className: "font-mono tabular-nums",
1151
+ "aria-label": "Chave de acesso",
1152
+ "data-testid": "pull-by-key-input"
1153
+ }
1154
+ ),
1155
+ /* @__PURE__ */ jsxs("p", { className: `text-xs tabular-nums ${digits.length === 44 ? "text-success-soft-foreground" : "text-muted-foreground"}`, children: [
1156
+ digits.length,
1157
+ "/44 d\xEDgitos"
1158
+ ] }),
1159
+ cnpjOptions && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1160
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "De qual CNPJ \xE9 esta nota?" }),
1161
+ /* @__PURE__ */ jsxs(Select, { value: legalEntityId, onValueChange: setLegalEntityId, children: [
1162
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Escolha o CNPJ destinat\xE1rio" }) }),
1163
+ /* @__PURE__ */ jsx(SelectContent, { children: cnpjOptions.map((o) => /* @__PURE__ */ jsxs(SelectItem, { value: o.id, children: [
1164
+ o.name,
1165
+ " \xB7 ",
1166
+ formatCnpj(o.cnpj)
1167
+ ] }, o.id)) })
1168
+ ] })
1169
+ ] }),
1170
+ error && /* @__PURE__ */ jsx("p", { className: "rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive", role: "alert", children: error }),
1171
+ notice && /* @__PURE__ */ jsx("p", { className: "rounded-md bg-warning-soft px-3 py-2 text-sm text-warning-soft-foreground", role: "status", children: notice }),
1172
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Puxar n\xE3o manifesta a nota na SEFAZ \u2014 a manifesta\xE7\xE3o continua sendo feita no sistema antigo." })
1173
+ ] }),
1174
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
1175
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: busy, children: "Cancelar" }),
1176
+ /* @__PURE__ */ jsxs(Button, { onClick: () => {
1177
+ void pull();
1178
+ }, disabled: busy || !!blocked, "data-testid": "pull-by-key-submit", children: [
1179
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(KeyRound, { className: "mr-1.5 h-4 w-4" }),
1180
+ busy ? "Buscando na SEFAZ\u2026" : blocked ?? "Puxar nota"
1181
+ ] })
1182
+ ] })
1183
+ ] }) });
1184
+ }
1185
+
1186
+ // src/types.ts
1187
+ var STOCK_BEARING_ENTRY_TYPES = ["insumo", "revenda", "patrimonio", "uso_consumo"];
1188
+ var isStockBearing = (type) => STOCK_BEARING_ENTRY_TYPES.includes(type);
1189
+ var money2 = (v) => formatCurrency(v || 0, "BRL");
1190
+ var qty = (v) => new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 4 }).format(v || 0);
1191
+ var day2 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
1192
+ var ENTRY_TYPES = [
1193
+ { value: "insumo", label: "Insumo" },
1194
+ { value: "revenda", label: "Produto Revenda" },
1195
+ { value: "patrimonio", label: "Patrim\xF4nio" },
1196
+ { value: "uso_consumo", label: "Uso e Consumo", hint: "com estoque" },
1197
+ { value: "despesa", label: "Despesa", hint: "n\xE3o vira estoque" },
1198
+ { value: "servico", label: "Servi\xE7o", hint: "n\xE3o vira estoque" }
1199
+ ];
1200
+ var localSearch = (options) => (query) => {
1201
+ const q = query.trim().toLowerCase();
1202
+ return (q ? options.filter((o) => o.label.toLowerCase().includes(q)) : options).slice(0, 40);
1203
+ };
1204
+ var labelOf = (options, id) => options.find((o) => o.id === id)?.label;
1205
+ var NOTE_UNIT_CODES = {
1206
+ KG: "kg",
1207
+ KGS: "kg",
1208
+ KILO: "kg",
1209
+ QUILO: "kg",
1210
+ G: "g",
1211
+ GR: "g",
1212
+ GRS: "g",
1213
+ L: "l",
1214
+ LT: "l",
1215
+ LTS: "l",
1216
+ LITRO: "l",
1217
+ ML: "ml",
1218
+ UN: "un",
1219
+ UND: "un",
1220
+ UNID: "un",
1221
+ UNI: "un",
1222
+ UNIDADE: "un"
1223
+ };
1224
+ function suggestedStockUnit(item, units, productUnits, productId) {
1225
+ const fromProduct = productId ? productUnits.get(productId) : void 0;
1226
+ if (fromProduct && units.some((u) => u.id === fromProduct)) return fromProduct;
1227
+ if (packagingCount(item.unit) !== null) return null;
1228
+ const code = NOTE_UNIT_CODES[String(item.unit ?? "").trim().toUpperCase().replace(/\d+$/, "")];
1229
+ return code ? units.find((u) => u.abbreviation.toLowerCase() === code)?.id ?? null : null;
1230
+ }
1231
+ var draftOf = (item) => ({
1232
+ productId: item.productId,
1233
+ productLabel: "",
1234
+ entryType: item.entryType ?? null,
1235
+ chartAccountId: item.chartAccountId,
1236
+ stockUnitId: item.stockUnitId,
1237
+ conversionFactor: String(item.conversionFactor ?? 1),
1238
+ costMode: item.costMode === "media" ? "media" : "novo",
1239
+ shortageQuantity: item.shortageQuantity == null ? "" : String(item.shortageQuantity),
1240
+ // Vem do SERVIDOR: é o que faz o selo sobreviver a recarregar a página.
1241
+ suggestedBy: item.suggestedBy,
1242
+ suggestionConfidence: item.suggestionConfidence
1243
+ });
1244
+ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
1245
+ const emModal = chrome === "modal";
1246
+ const sangria = emModal ? "-mx-6 px-6" : "-mx-4 px-4";
1247
+ const cabecalho = (titulo) => emModal ? null : /* @__PURE__ */ jsx(SubpageHeader, { title: titulo, onBack, parentLabel: "Manifesto de Notas" });
1248
+ const [note, setNote] = useState(null);
1249
+ const [loading, setLoading] = useState(true);
1250
+ const [drafts, setDrafts] = useState({});
1251
+ const [instalments, setInstalments] = useState([]);
1252
+ const [defaultAccount, setDefaultAccount] = useState(null);
1253
+ const [location, setLocation] = useState(null);
1254
+ const [units, setUnits] = useState([]);
1255
+ const [productUnits, setProductUnits] = useState(() => /* @__PURE__ */ new Map());
1256
+ const [accounts, setAccounts] = useState([]);
1257
+ const [locations, setLocations] = useState([]);
1258
+ const [saving, setSaving] = useState(false);
1259
+ const [accepting, setAccepting] = useState(false);
1260
+ const [openIds, setOpenIds] = useState([]);
1261
+ const [checked, setChecked] = useState({});
1262
+ const [registryWarning, setRegistryWarning] = useState(null);
1263
+ const [error, setError] = useState(null);
1264
+ const [accepted, setAccepted] = useState(null);
1265
+ const [baixandoXml, setBaixandoXml] = useState(false);
1266
+ const [suggesting, setSuggesting] = useState(false);
1267
+ const [suggestionNote, setSuggestionNote] = useState(null);
1268
+ const [pullOpen, setPullOpen] = useState(false);
1269
+ const [payable, setPayable] = useState(void 0);
1270
+ const [payableLost, setPayableLost] = useState(false);
1271
+ const load = useCallback(async () => {
1272
+ setLoading(true);
1273
+ try {
1274
+ let detail = await getInboxNote(noteId);
1275
+ if (detail && !detail.supplierPartyId && detail.supplierCnpj) {
1276
+ const linked = await linkInboxSupplier(noteId).catch(() => null);
1277
+ if (linked?.supplierPartyId) detail = { ...detail, supplierPartyId: linked.supplierPartyId };
1278
+ }
1279
+ if (detail && (detail.invoiceId || detail.stockedAt)) {
1280
+ const healed = await healInboxNote(noteId).catch(() => null);
1281
+ if (healed?.released) setPayableLost(true);
1282
+ if (healed?.changed) detail = await getInboxNote(noteId) ?? detail;
1283
+ }
1284
+ setPayable(detail?.invoiceId ? await getNotePayable(detail.invoiceId).catch(() => void 0) : void 0);
1285
+ if (detail && detail.items.length === 0 && detail.hasXml && detail.status !== "rejected") {
1286
+ const xml = await getInboxNoteXml(noteId).catch(() => null);
1287
+ if (xml) {
1288
+ const accepted2 = Boolean(detail.stockedAt || detail.invoiceId);
1289
+ const filled = accepted2 ? await fillInboxLines(noteId, xml).catch(() => 0) : (await stageNoteFromXml(xml, { unitId: detail.unitId }).catch(() => null))?.itemCount ?? 0;
1290
+ if (filled) detail = await getInboxNote(noteId) ?? detail;
1291
+ }
1292
+ }
1293
+ setNote(detail);
1294
+ const failed = [];
1295
+ const [u, a, l] = await Promise.all([
1296
+ listMeasurementUnits().catch(() => {
1297
+ failed.push("unidades");
1298
+ return [];
1299
+ }),
1300
+ listChartAccounts().catch(() => {
1301
+ failed.push("plano de contas");
1302
+ return [];
1303
+ }),
1304
+ listStockLocations().catch(() => {
1305
+ failed.push("locais de estoque");
1306
+ return [];
1307
+ })
1308
+ ]);
1309
+ setUnits(u);
1310
+ setAccounts(a);
1311
+ setLocations(l);
1312
+ setRegistryWarning(failed.length ? `N\xE3o consegui carregar ${failed.join(", ")}. Estes seletores v\xE3o aparecer vazios.` : null);
1313
+ if (detail) {
1314
+ const vinculados = [...new Set(detail.items.map((i) => i.productId).filter((id) => !!id))];
1315
+ const nomes = vinculados.length ? new Map((await listProductsByIds(vinculados).catch(() => [])).map((p) => [p.id, p.name])) : /* @__PURE__ */ new Map();
1316
+ const unidadesDoProduto = await listProductStockUnits(vinculados).catch(() => /* @__PURE__ */ new Map());
1317
+ setProductUnits(unidadesDoProduto);
1318
+ setDrafts(Object.fromEntries(detail.items.map((i) => [
1319
+ i.id,
1320
+ {
1321
+ ...draftOf(i),
1322
+ productLabel: i.productId && nomes.get(i.productId) || "",
1323
+ // Stocked lines keep what was recorded; open ones get a unit instead of a blank.
1324
+ stockUnitId: i.stockUnitId ?? (i.stockMovementId ? null : suggestedStockUnit(i, u, unidadesDoProduto, i.productId))
1325
+ }
1326
+ ])));
1327
+ setChecked(Object.fromEntries(detail.items.filter((i) => i.reconciled).map((i) => [i.id, true])));
1328
+ const pending = detail.items.find((i) => !i.reconciled);
1329
+ setOpenIds(pending ? [pending.id] : []);
1330
+ setInstalments(detail.instalments.map((d) => ({
1331
+ index: d.index,
1332
+ number: d.number,
1333
+ dueDate: d.dueDate,
1334
+ amount: d.amount,
1335
+ paymentType: d.paymentType,
1336
+ barcode: d.barcode,
1337
+ pixKey: d.pixKey
1338
+ })));
1339
+ setDefaultAccount(detail.defaultChartAccountId);
1340
+ setLocation(detail.defaultLocationId ?? l[0]?.id ?? null);
1341
+ }
1342
+ } catch (e) {
1343
+ setError(e instanceof Error ? e.message : String(e));
1344
+ } finally {
1345
+ setLoading(false);
1346
+ }
1347
+ }, [noteId]);
1348
+ useEffect(() => {
1349
+ void load();
1350
+ }, [load]);
1351
+ const patch = (id, change) => setDrafts((prev) => ({
1352
+ ...prev,
1353
+ [id]: {
1354
+ ...prev[id],
1355
+ ..."suggestedBy" in change ? {} : { suggestedBy: null, suggestionConfidence: null },
1356
+ ...change
1357
+ }
1358
+ }));
1359
+ const accountOptions = useMemo(
1360
+ () => accounts.map((a) => ({ id: a.id, label: `${a.code} \xB7 ${a.name}` })),
1361
+ [accounts]
1362
+ );
1363
+ const unitOptions = useMemo(
1364
+ () => units.map((u) => ({ id: u.id, label: u.abbreviation, subtitle: u.name })),
1365
+ [units]
1366
+ );
1367
+ const locationOptions = useMemo(
1368
+ () => locations.map((l) => ({ id: l.id, label: l.name })),
1369
+ [locations]
1370
+ );
1371
+ const buildDecisions = useCallback((rascunhos) => {
1372
+ if (!note) return [];
1373
+ return note.items.map((item) => {
1374
+ const d = rascunhos[item.id] ?? draftOf(item);
1375
+ return {
1376
+ id: item.id,
1377
+ productId: isStockBearing(d.entryType) ? d.productId : null,
1378
+ entryType: d.entryType,
1379
+ // A herança que a legenda do V1 promete: «itens sem plano herdarão este».
1380
+ chartAccountId: d.chartAccountId ?? defaultAccount,
1381
+ stockUnitId: d.stockUnitId,
1382
+ conversionFactor: Number(d.conversionFactor.replace(",", ".")) || 1,
1383
+ costMode: d.costMode,
1384
+ suggestedBy: d.suggestedBy,
1385
+ suggestionConfidence: d.suggestionConfidence,
1386
+ shortageQuantity: d.shortageQuantity === "" ? null : Number(d.shortageQuantity.replace(",", "."))
1387
+ };
1388
+ });
1389
+ }, [note, defaultAccount]);
1390
+ const decisions = useMemo(() => buildDecisions(drafts), [buildDecisions, drafts]);
1391
+ const stockLines = decisions.filter((d) => isStockBearing(d.entryType));
1392
+ const missingProduct = stockLines.filter((d) => !d.productId).length;
1393
+ const missingUnit = stockLines.filter((d) => {
1394
+ const item = note?.items.find((i) => i.id === d.id);
1395
+ return !d.stockUnitId && !item?.stockMovementId;
1396
+ }).length;
1397
+ const missingType = decisions.filter((d) => !d.entryType).length;
1398
+ const suspectFactor = (item, d) => packagingCount(item.unit) !== null && isStockBearing(d.entryType) && ((Number(d.conversionFactor.replace(",", ".")) || 1) === 1 || !d.stockUnitId);
1399
+ const unverifiedFactor = note ? note.items.filter((i) => suspectFactor(i, drafts[i.id] ?? draftOf(i))).length : 0;
1400
+ const instalmentSum = instalments.reduce((acc, d) => acc + (Number(d.amount) || 0), 0);
1401
+ const hasPayable = Boolean(note?.invoiceId);
1402
+ const pendingStock = (note?.items ?? []).some((i) => !i.stockMovementId && (!i.entryType || isStockBearing(i.entryType)));
1403
+ const readOnly = Boolean(note?.stockedAt || hasPayable && !pendingStock);
1404
+ const paymentLocked = readOnly || hasPayable;
1405
+ const isReady = (item, d) => Boolean(d.entryType) && (!isStockBearing(d.entryType) || Boolean(d.productId)) && !suspectFactor(item, d);
1406
+ const doneCount = note ? note.items.filter((i) => checked[i.id]).length : 0;
1407
+ const toggleItem = (id) => setOpenIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]);
1408
+ const confirmItem = (id) => {
1409
+ setChecked((prev) => ({ ...prev, [id]: true }));
1410
+ setOpenIds((prev) => {
1411
+ const rest = prev.filter((x) => x !== id);
1412
+ const next = note?.items.find((i) => i.id !== id && !checked[i.id]);
1413
+ return next && !rest.includes(next.id) ? [...rest, next.id] : rest;
1414
+ });
1415
+ };
1416
+ const persistir = useCallback(async (rascunhos) => {
1417
+ if (!note) return;
1418
+ await saveNoteDecision({
1419
+ documentId: note.id,
1420
+ items: buildDecisions(rascunhos),
1421
+ defaultChartAccountId: defaultAccount,
1422
+ defaultLocationId: location,
1423
+ instalments
1424
+ });
1425
+ }, [note, buildDecisions, defaultAccount, location, instalments]);
1426
+ const pendingItems = useMemo(
1427
+ () => (note?.items ?? []).filter((item) => {
1428
+ const d = drafts[item.id] ?? draftOf(item);
1429
+ return !d.entryType || isStockBearing(d.entryType) && !d.productId;
1430
+ }),
1431
+ [note, drafts]
1432
+ );
1433
+ const machineItems = useMemo(
1434
+ () => (note?.items ?? []).filter((item) => (drafts[item.id] ?? draftOf(item)).suggestedBy !== null),
1435
+ [note, drafts]
1436
+ );
1437
+ const sugerir = useCallback(async (reavaliar2 = false) => {
1438
+ if (!note) return;
1439
+ setSuggesting(true);
1440
+ setSuggestionNote(null);
1441
+ setError(null);
1442
+ try {
1443
+ await persistir(drafts);
1444
+ const res = await suggestNoteItems(note.id, reavaliar2);
1445
+ await load();
1446
+ if (res.applied === 0 && res.bound === 0 && res.typedOnly === 0) {
1447
+ setSuggestionNote(res.warnings[0] ?? "N\xE3o achei nada para preencher nestes itens.");
1448
+ return;
1449
+ }
1450
+ const semIA = res.bound ? `Sem IA: ${res.boundByEan ? `${res.boundByEan} pelo c\xF3digo de barras` : ""}` + (res.boundByEan && res.bound - res.boundByEan ? " e " : "") + (res.bound - res.boundByEan ? `${res.bound - res.boundByEan} pelo cat\xE1logo deste fornecedor` : "") + "." : "";
1451
+ setSuggestionNote([
1452
+ semIA,
1453
+ res.applied ? `${res.applied} ${res.applied === 1 ? "item preenchido" : "itens preenchidos"} pela IA.` : "",
1454
+ res.typedOnly ? `${res.typedOnly} ${res.typedOnly === 1 ? "item ficou" : "itens ficaram"} sem produto no cat\xE1logo` + (res.newProductNames.length ? ` (${res.newProductNames.slice(0, 3).join(", ")})` : "") + " \u2014 escolha o produto ou cadastre antes de aceitar." : "",
1455
+ res.applied || res.bound ? "Salvo." : "",
1456
+ ...res.warnings
1457
+ ].filter(Boolean).join(" "));
1458
+ } catch (e) {
1459
+ setError(e instanceof Error ? e.message : String(e));
1460
+ } finally {
1461
+ setSuggesting(false);
1462
+ }
1463
+ }, [note, drafts, persistir, load]);
1464
+ const reavaliar = pendingItems.length === 0 && machineItems.length > 0;
1465
+ const botaoIA = readOnly || pendingItems.length === 0 && !reavaliar ? null : /* @__PURE__ */ jsx(
1466
+ AiActionButton,
1467
+ {
1468
+ size: "sm",
1469
+ className: "shrink-0",
1470
+ busy: suggesting,
1471
+ busyLabel: "Pensando\u2026",
1472
+ onClick: () => {
1473
+ void sugerir(reavaliar);
1474
+ },
1475
+ children: reavaliar ? "Rodar IA novamente" : "Sugerir com IA"
1476
+ }
1477
+ );
1478
+ const moldura = (node, titulo, rodape2) => emModal ? /* @__PURE__ */ jsxs(Fragment, { children: [
1479
+ /* @__PURE__ */ jsxs(ModalHeader, { className: "shrink-0 flex-row items-center justify-between gap-3 space-y-0 border-b px-6 py-3 pr-14 text-left sm:flex-row", children: [
1480
+ /* @__PURE__ */ jsx(ModalTitle, { className: "min-w-0 truncate", children: titulo }),
1481
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
1482
+ botaoIA,
1483
+ ferramentas
1484
+ ] })
1485
+ ] }),
1486
+ /* @__PURE__ */ jsx(ModalBody, { className: "px-6 py-4", children: node }),
1487
+ rodape2 && /* @__PURE__ */ jsx(ModalFooter, { variant: "bar", start: rodape2.start, children: rodape2.end })
1488
+ ] }) : (
1489
+ // Em tela cheia não há moldura onde pendurar o rodapé: ele volta para dentro
1490
+ // do conteúdo, grudado na base da janela enquanto a página rola.
1491
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1492
+ node,
1493
+ rodape2 && /* @__PURE__ */ jsxs("div", { className: `sticky bottom-0 ${sangria} flex items-center justify-between gap-3 border-t bg-card/95 py-3 pr-16 backdrop-blur`, children: [
1494
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 items-center gap-3", children: rodape2.start }),
1495
+ /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: rodape2.end })
1496
+ ] })
1497
+ ] })
1498
+ );
1499
+ const [cadastrando, setCadastrando] = useState(false);
1500
+ const cadastrarFornecedor = useCallback(async () => {
1501
+ if (!note) return;
1502
+ setCadastrando(true);
1503
+ setError(null);
1504
+ try {
1505
+ const { supplierPartyId } = await linkInboxSupplier(note.id, true);
1506
+ if (supplierPartyId) setNote({ ...note, supplierPartyId });
1507
+ } catch (e) {
1508
+ setError(e instanceof Error ? e.message : String(e));
1509
+ } finally {
1510
+ setCadastrando(false);
1511
+ }
1512
+ }, [note]);
1513
+ const [abrindoPdf, setAbrindoPdf] = useState(false);
1514
+ const abrirPdf = useCallback(async () => {
1515
+ if (!note?.accessKey) return;
1516
+ const tab = window.open("", "_blank");
1517
+ setAbrindoPdf(true);
1518
+ setError(null);
1519
+ try {
1520
+ const pdf = await getInboxNotePdf(note.accessKey);
1521
+ if (tab) tab.location.href = URL.createObjectURL(pdf);
1522
+ } catch (e) {
1523
+ tab?.close();
1524
+ setError(e instanceof Error ? e.message : String(e));
1525
+ } finally {
1526
+ setAbrindoPdf(false);
1527
+ }
1528
+ }, [note]);
1529
+ const baixarXml = useCallback(async () => {
1530
+ if (!note) return;
1531
+ setBaixandoXml(true);
1532
+ setError(null);
1533
+ try {
1534
+ const xml = await getInboxNoteXml(note.id);
1535
+ if (!xml) {
1536
+ setError("O arquivo desta nota n\xE3o est\xE1 guardado.");
1537
+ return;
1538
+ }
1539
+ const url = URL.createObjectURL(new Blob([xml], { type: "application/xml" }));
1540
+ const a = document.createElement("a");
1541
+ a.href = url;
1542
+ a.download = `${note.accessKey || note.number || "nfe"}.xml`;
1543
+ a.click();
1544
+ URL.revokeObjectURL(url);
1545
+ } catch (e) {
1546
+ setError(e instanceof Error ? e.message : String(e));
1547
+ } finally {
1548
+ setBaixandoXml(false);
1549
+ }
1550
+ }, [note]);
1551
+ const ferramentas = note ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1552
+ /* @__PURE__ */ jsxs(Dropdown, { children: [
1553
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", disabled: baixandoXml || abrindoPdf, children: [
1554
+ baixandoXml || abrindoPdf ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Download, { className: "mr-1.5 h-4 w-4" }),
1555
+ "Baixar",
1556
+ /* @__PURE__ */ jsx(ChevronDown, { className: "ml-1 h-3.5 w-3.5 opacity-60" })
1557
+ ] }) }),
1558
+ /* @__PURE__ */ jsxs(DropdownContent, { align: "end", children: [
1559
+ /* @__PURE__ */ jsxs(DropdownItem, { className: "gap-2", disabled: !note.hasXml, onSelect: () => {
1560
+ void baixarXml();
1561
+ }, children: [
1562
+ /* @__PURE__ */ jsx(FileCode2, { className: "h-4 w-4" }),
1563
+ note.hasXml ? "XML da nota" : "XML n\xE3o guardado \u2014 reimporte o .xml"
1564
+ ] }),
1565
+ /* @__PURE__ */ jsxs(DropdownItem, { className: "gap-2", disabled: !note.accessKey, onSelect: () => {
1566
+ void abrirPdf();
1567
+ }, children: [
1568
+ /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4" }),
1569
+ "DANFE (PDF)"
1570
+ ] })
1571
+ ] })
1572
+ ] }),
1573
+ /* @__PURE__ */ jsx(Hint, { label: "Atualizar", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Atualizar a nota", onClick: () => {
1574
+ void load();
1575
+ }, children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" }) }) })
1576
+ ] }) : null;
1577
+ const save = async () => {
1578
+ if (!note) return;
1579
+ setSaving(true);
1580
+ setError(null);
1581
+ try {
1582
+ await saveNoteDecision({
1583
+ documentId: note.id,
1584
+ items: decisions,
1585
+ defaultChartAccountId: defaultAccount,
1586
+ defaultLocationId: location,
1587
+ instalments
1588
+ });
1589
+ await load();
1590
+ } catch (e) {
1591
+ setError(e instanceof Error ? e.message : String(e));
1592
+ } finally {
1593
+ setSaving(false);
1594
+ }
1595
+ };
1596
+ const accept = async () => {
1597
+ if (!note) return;
1598
+ setAccepting(true);
1599
+ setError(null);
1600
+ try {
1601
+ await saveNoteDecision({
1602
+ documentId: note.id,
1603
+ items: decisions,
1604
+ defaultChartAccountId: defaultAccount,
1605
+ defaultLocationId: location,
1606
+ instalments
1607
+ });
1608
+ const result = await acceptNote(note.id, location);
1609
+ if (result.invoiceId) await moveNoteAttachmentsToInvoice(note.id, result.invoiceId).catch(() => {
1610
+ });
1611
+ setAccepted(result);
1612
+ await load();
1613
+ } catch (e) {
1614
+ setError(e instanceof Error ? e.message : String(e));
1615
+ } finally {
1616
+ setAccepting(false);
1617
+ }
1618
+ };
1619
+ if (loading) {
1620
+ return moldura(/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1621
+ cabecalho("Concilia\xE7\xE3o"),
1622
+ /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
1623
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" }),
1624
+ /* @__PURE__ */ jsx("div", { className: "mt-3 grid gap-x-6 gap-y-3 sm:grid-cols-2 lg:grid-cols-3", children: [0, 1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsxs("div", { children: [
1625
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" }),
1626
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-4 w-36" })
1627
+ ] }, i)) })
1628
+ ] }),
1629
+ /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
1630
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-36" }),
1631
+ /* @__PURE__ */ jsx("div", { className: "mt-3 grid gap-4 sm:grid-cols-2", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("div", { children: [
1632
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-28" }),
1633
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-9 w-full" })
1634
+ ] }, i)) })
1635
+ ] }),
1636
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }),
1637
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs(Card, { className: "flex items-center gap-3 px-4 py-3", children: [
1638
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded-full" }),
1639
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1640
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-2/3" }),
1641
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-1/3" })
1642
+ ] }),
1643
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
1644
+ ] }, i)) })
1645
+ ] }), "Concilia\xE7\xE3o");
1646
+ }
1647
+ if (!note) {
1648
+ return moldura(/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1649
+ cabecalho("Concilia\xE7\xE3o"),
1650
+ /* @__PURE__ */ jsx(Card, { className: "p-8 text-center text-sm", children: error ? /* @__PURE__ */ jsxs("span", { className: "text-destructive", children: [
1651
+ "N\xE3o consegui carregar a nota: ",
1652
+ error
1653
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Nota n\xE3o encontrada." }) })
1654
+ ] }), "Concilia\xE7\xE3o");
1655
+ }
1656
+ const pendencias = [
1657
+ unverifiedFactor > 0 && `${unverifiedFactor} com fator por conferir`,
1658
+ missingType > 0 && `${missingType} sem tipo`,
1659
+ missingProduct > 0 && `${missingProduct} sem produto`,
1660
+ missingUnit > 0 && `${missingUnit} sem unidade de estoque`
1661
+ ].filter(Boolean).join(" \xB7 ");
1662
+ const rodape = readOnly ? null : {
1663
+ start: /* @__PURE__ */ jsxs(Fragment, { children: [
1664
+ !hasPayable && /* @__PURE__ */ jsxs(
1665
+ Button,
1666
+ {
1667
+ variant: "ghost",
1668
+ size: "sm",
1669
+ className: "text-destructive hover:bg-destructive/10 hover:text-destructive",
1670
+ onClick: async () => {
1671
+ await rejectNote(note.id);
1672
+ onBack();
1673
+ },
1674
+ disabled: saving || accepting,
1675
+ children: [
1676
+ /* @__PURE__ */ jsx(X, { className: "mr-1.5 h-3.5 w-3.5" }),
1677
+ "Rejeitar"
1678
+ ]
1679
+ }
1680
+ ),
1681
+ pendencias && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: pendencias })
1682
+ ] }),
1683
+ end: /* @__PURE__ */ jsxs(Fragment, { children: [
1684
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
1685
+ void save();
1686
+ }, disabled: saving || accepting, children: [
1687
+ saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }),
1688
+ "Salvar concilia\xE7\xE3o"
1689
+ ] }),
1690
+ /* @__PURE__ */ jsxs(
1691
+ Button,
1692
+ {
1693
+ size: "sm",
1694
+ onClick: () => {
1695
+ void accept();
1696
+ },
1697
+ disabled: accepting || saving || missingType > 0 || missingProduct > 0 || missingUnit > 0 || unverifiedFactor > 0,
1698
+ children: [
1699
+ accepting ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "mr-1.5 h-3.5 w-3.5" }),
1700
+ hasPayable ? "Dar entrada no estoque" : "Aceitar"
1701
+ ]
1702
+ }
1703
+ )
1704
+ ] })
1705
+ };
1706
+ return moldura(/* @__PURE__ */ jsxs("div", { className: `space-y-4 ${emModal ? "" : "pb-24"}`, children: [
1707
+ cabecalho(`Concilia\xE7\xE3o \xB7 NF-e ${note.number}`),
1708
+ accepted && /* @__PURE__ */ jsxs(Card, { className: "border-success bg-success-soft p-4", children: [
1709
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-success-soft-foreground", children: accepted.alreadyAccepted ? "Esta nota j\xE1 havia sido aceita \u2014 nada foi lan\xE7ado de novo." : accepted.instalments === 0 ? `Entrada no estoque: ${accepted.stockLines} ${accepted.stockLines === 1 ? "linha" : "linhas"}, ligadas \xE0 conta a pagar que a nota j\xE1 tinha (${money2(accepted.invoiceTotal)}).` : `Aceita: ${accepted.stockLines} ${accepted.stockLines === 1 ? "linha" : "linhas"} de estoque e um t\xEDtulo de ${money2(accepted.invoiceTotal)} em ${accepted.instalments} ${accepted.instalments === 1 ? "parcela" : "parcelas"}.` }),
1710
+ accepted.invoiceId && onOpenInvoice && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => onOpenInvoice(accepted.invoiceId), children: "Abrir a conta a pagar" })
1711
+ ] }),
1712
+ payableLost && !readOnly && /* @__PURE__ */ jsxs(Card, { className: "border-warning bg-warning-soft p-4", "data-testid": "note-payable-lost", children: [
1713
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-warning-soft-foreground", children: "A conta a pagar e a entrada no estoque desta nota foram apagadas fora daqui." }),
1714
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-warning-soft-foreground", children: "Confira a concilia\xE7\xE3o e clique em Aceitar para lan\xE7ar as duas de novo." })
1715
+ ] }),
1716
+ !accepted && /* @__PURE__ */ jsxs(Card, { className: "p-4", "data-testid": "note-postings", children: [
1717
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Lan\xE7amentos desta nota" }),
1718
+ /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid gap-x-6 gap-y-3 text-sm sm:grid-cols-2", children: [
1719
+ /* @__PURE__ */ jsxs("div", { children: [
1720
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: "Conta a pagar" }),
1721
+ /* @__PURE__ */ jsxs("dd", { className: "mt-0.5 flex flex-wrap items-center gap-2", children: [
1722
+ !note.invoiceId && /* @__PURE__ */ jsx("span", { className: payableLost ? "text-destructive" : "text-muted-foreground", children: payableLost ? "N\xE3o existe mais \u2014 foi apagada; aceite a nota para lan\xE7ar de novo" : readOnly ? "Nenhuma \u2014 esta nota n\xE3o abriu conta a pagar" : "Ainda n\xE3o lan\xE7ada \u2014 nasce quando a nota for aceita" }),
1723
+ note.invoiceId && payable === null && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "N\xE3o encontrada \u2014 a conta foi apagada" }),
1724
+ payable && /* @__PURE__ */ jsxs("span", { "data-testid": "note-payable-link", children: [
1725
+ onOpenInvoice ? /* @__PURE__ */ jsx(InlineLink, { onClick: () => onOpenInvoice(payable.id), children: payable.number ?? "Conta a pagar" }) : payable.number ?? "Conta a pagar",
1726
+ " \xB7 ",
1727
+ money2(payable.total),
1728
+ payable.cancelledAt && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " \xB7 cancelada" })
1729
+ ] })
1730
+ ] })
1731
+ ] }),
1732
+ /* @__PURE__ */ jsxs("div", { children: [
1733
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: "Estoque" }),
1734
+ /* @__PURE__ */ jsx("dd", { className: "mt-0.5", children: note.stockedAt ? `Entrada em ${day2(note.stockedAt)}` : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: readOnly ? "Sem entrada no estoque" : hasPayable ? "Ainda sem entrada \u2014 conclua a concilia\xE7\xE3o e d\xEA entrada" : "Ainda sem entrada \u2014 entra quando a nota for aceita" }) })
1735
+ ] })
1736
+ ] })
1737
+ ] }),
1738
+ /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
1739
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Situa\xE7\xE3o fiscal" }),
1740
+ /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid gap-x-6 gap-y-2 text-sm sm:grid-cols-2 lg:grid-cols-3", children: [
1741
+ /* @__PURE__ */ jsxs("div", { children: [
1742
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: "Fornecedor" }),
1743
+ /* @__PURE__ */ jsx("dd", { className: "text-sm", children: note.supplierPartyId ? /* @__PURE__ */ jsx(PersonLink, { personId: note.supplierPartyId, name: note.supplierName || "\u2014", kind: "supplier" }) : note.supplierName || "\u2014" })
1744
+ ] }),
1745
+ /* @__PURE__ */ jsx(Field, { label: "CNPJ", value: note.supplierCnpj ?? "\u2014", mono: true }),
1746
+ /* @__PURE__ */ jsx(Field, { label: "Natureza da opera\xE7\xE3o", value: note.natureOperation ?? "\u2014" }),
1747
+ /* @__PURE__ */ jsx(
1748
+ Field,
1749
+ {
1750
+ label: "Manifesta\xE7\xE3o",
1751
+ value: `${note.manifestationStatus ?? "n\xE3o manifestada"}${note.manifestationAttempts ? ` \xB7 ${note.manifestationAttempts} tentativa(s)` : ""}`
1752
+ }
1753
+ ),
1754
+ /* @__PURE__ */ jsx(Field, { label: "Manifestada em", value: day2(note.manifestedAt) }),
1755
+ /* @__PURE__ */ jsx(Field, { label: "Recebida em", value: day2(note.receivedAt) })
1756
+ ] }),
1757
+ !note.supplierPartyId && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-between gap-2 rounded-md border border-input bg-muted/40 px-3 py-2", children: [
1758
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: note.supplierCnpj ? /* @__PURE__ */ jsx(Fragment, { children: "Este fornecedor ainda n\xE3o est\xE1 no cadastro. Ao aceitar, ele \xE9 cadastrado com o nome e o CNPJ da nota." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1759
+ "A nota n\xE3o traz o CNPJ do fornecedor, ent\xE3o a conta a pagar vai nascer ",
1760
+ /* @__PURE__ */ jsx("strong", { children: "sem fornecedor" }),
1761
+ "."
1762
+ ] }) }),
1763
+ note.supplierCnpj && !readOnly && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", disabled: cadastrando, onClick: () => {
1764
+ void cadastrarFornecedor();
1765
+ }, children: [
1766
+ cadastrando ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { className: "mr-1.5 h-4 w-4" }),
1767
+ "Cadastrar agora"
1768
+ ] })
1769
+ ] }),
1770
+ /bonific|brinde|doacao|doação/i.test(note.natureOperation ?? "") && /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-md border border-dashed border-input px-3 py-2", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1771
+ /* @__PURE__ */ jsx(Gift, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
1772
+ "A natureza desta nota \xE9 ",
1773
+ /* @__PURE__ */ jsx("strong", { children: "bonifica\xE7\xE3o" }),
1774
+ ". Ela deveria entrar no estoque",
1775
+ " ",
1776
+ /* @__PURE__ */ jsx("strong", { children: "sem" }),
1777
+ " abrir conta a pagar \u2014 hoje ainda abre.",
1778
+ " ",
1779
+ /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 py-0.5 text-[10px]", children: "Em breve" })
1780
+ ] }) }),
1781
+ !emModal && /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: ferramentas })
1782
+ ] }),
1783
+ /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
1784
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Padr\xF5es desta nota" }),
1785
+ registryWarning && /* @__PURE__ */ jsx("div", { className: "mt-2 rounded-md border border-warning bg-warning-soft px-3 py-2", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-warning-soft-foreground", children: [
1786
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
1787
+ registryWarning
1788
+ ] }) }),
1789
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 grid gap-4 sm:grid-cols-2", children: [
1790
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1791
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Plano de contas padr\xE3o" }),
1792
+ /* @__PURE__ */ jsx(
1793
+ SearchSelect,
1794
+ {
1795
+ onSearch: localSearch(accountOptions),
1796
+ value: defaultAccount ?? void 0,
1797
+ displayValue: labelOf(accountOptions, defaultAccount),
1798
+ onChange: (id) => setDefaultAccount(id || null),
1799
+ minChars: 0,
1800
+ placeholder: "Escolher plano de contas",
1801
+ noResultsLabel: "Nenhum plano de contas",
1802
+ searchingLabel: "Buscando\u2026",
1803
+ clearLabel: "Limpar",
1804
+ disabled: readOnly
1805
+ }
1806
+ ),
1807
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-[11px] text-muted-foreground", children: "Itens sem plano herdar\xE3o este." })
1808
+ ] }),
1809
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1810
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Local de estoque" }),
1811
+ /* @__PURE__ */ jsx(
1812
+ SearchSelect,
1813
+ {
1814
+ onSearch: localSearch(locationOptions),
1815
+ value: location ?? void 0,
1816
+ displayValue: labelOf(locationOptions, location),
1817
+ onChange: (id) => setLocation(id || null),
1818
+ minChars: 0,
1819
+ placeholder: "Escolher local",
1820
+ noResultsLabel: "Nenhum local de estoque",
1821
+ searchingLabel: "Buscando\u2026",
1822
+ clearLabel: "Limpar",
1823
+ disabled: readOnly
1824
+ }
1825
+ ),
1826
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-[11px] text-muted-foreground", children: "Onde as linhas de estoque desta nota entram." })
1827
+ ] })
1828
+ ] })
1829
+ ] }),
1830
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1831
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
1832
+ /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold", children: [
1833
+ "Itens ",
1834
+ /* @__PURE__ */ jsxs("span", { className: "font-normal text-muted-foreground", children: [
1835
+ "(",
1836
+ note.items.length,
1837
+ ")"
1838
+ ] })
1839
+ ] }),
1840
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 min-w-[6rem] flex-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
1841
+ "div",
1842
+ {
1843
+ className: "h-full rounded-full bg-primary transition-all",
1844
+ style: { width: `${note.items.length ? doneCount / note.items.length * 100 : 0}%` }
1845
+ }
1846
+ ) }),
1847
+ /* @__PURE__ */ jsxs("span", { className: "text-xs tabular-nums text-muted-foreground", children: [
1848
+ doneCount,
1849
+ "/",
1850
+ note.items.length,
1851
+ " conferidos"
1852
+ ] }),
1853
+ /* @__PURE__ */ jsx(
1854
+ Button,
1855
+ {
1856
+ variant: "ghost",
1857
+ size: "sm",
1858
+ onClick: () => setOpenIds(openIds.length ? [] : note.items.map((i) => i.id)),
1859
+ children: openIds.length ? "Recolher todos" : "Abrir todos"
1860
+ }
1861
+ ),
1862
+ !emModal && botaoIA
1863
+ ] }),
1864
+ suggestionNote && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-md border border-magic/30 bg-magic-soft px-3 py-2", children: [
1865
+ /* @__PURE__ */ jsx("p", { className: "flex-1 text-xs text-magic-soft-foreground", children: suggestionNote }),
1866
+ /* @__PURE__ */ jsx(
1867
+ Button,
1868
+ {
1869
+ variant: "bare",
1870
+ size: "bare",
1871
+ type: "button",
1872
+ className: "shrink-0 text-xs text-magic-soft-foreground/70 hover:underline font-normal",
1873
+ onClick: () => setSuggestionNote(null),
1874
+ children: "Fechar"
1875
+ }
1876
+ )
1877
+ ] }),
1878
+ note.items.length === 0 && /* @__PURE__ */ jsxs(Card, { className: "p-6 text-center", "data-testid": "note-without-items", children: [
1879
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Esta nota chegou sem os itens" }),
1880
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: readOnly || !note.accessKey ? "N\xE3o h\xE1 o XML dela guardado para ler os itens." : note.hasXml ? "O XML guardado \xE9 s\xF3 o resumo da SEFAZ, sem os itens. Busque a nota completa pela chave de acesso." : "O XML dela n\xE3o veio junto. Busque na SEFAZ pela chave de acesso para conciliar." }),
1881
+ !readOnly && note.accessKey && note.status !== "rejected" && /* @__PURE__ */ jsxs(Button, { className: "mt-3", size: "sm", onClick: () => setPullOpen(true), children: [
1882
+ /* @__PURE__ */ jsx(Download, { className: "mr-1.5 h-4 w-4" }),
1883
+ " Buscar itens na SEFAZ"
1884
+ ] }),
1885
+ /* @__PURE__ */ jsx(
1886
+ PullByKeyDialog,
1887
+ {
1888
+ open: pullOpen,
1889
+ onOpenChange: setPullOpen,
1890
+ unitId: note.unitId,
1891
+ initialKey: note.accessKey ?? void 0,
1892
+ onPulled: () => {
1893
+ void load();
1894
+ }
1895
+ }
1896
+ )
1897
+ ] }),
1898
+ note.items.map((item) => {
1899
+ const d = drafts[item.id] ?? draftOf(item);
1900
+ const factor = Number(d.conversionFactor.replace(",", ".")) || 1;
1901
+ const shortage = d.shortageQuantity === "" ? 0 : Number(d.shortageQuantity.replace(",", ".")) || 0;
1902
+ const stockQty = item.quantity * factor - shortage;
1903
+ const unitCost = factor > 0 ? item.unitPrice / factor : item.unitPrice;
1904
+ const bearing = isStockBearing(d.entryType);
1905
+ const open = openIds.includes(item.id);
1906
+ const done = Boolean(checked[item.id]);
1907
+ const ready = isReady(item, d);
1908
+ const typeLabel = ENTRY_TYPES.find((t) => t.value === d.entryType)?.label;
1909
+ return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
1910
+ /* @__PURE__ */ jsxs(
1911
+ Button,
1912
+ {
1913
+ variant: "bare",
1914
+ size: "bare",
1915
+ type: "button",
1916
+ onClick: () => toggleItem(item.id),
1917
+ "aria-expanded": open,
1918
+ className: "flex w-full items-center justify-start gap-3 rounded-none px-4 py-3 text-left font-normal transition-colors hover:bg-muted/30",
1919
+ children: [
1920
+ /* @__PURE__ */ jsx(
1921
+ ChevronRight,
1922
+ {
1923
+ className: `h-4 w-4 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-90" : ""}`,
1924
+ "aria-hidden": true
1925
+ }
1926
+ ),
1927
+ /* @__PURE__ */ jsx("span", { className: "w-5 shrink-0 text-xs tabular-nums text-muted-foreground", children: item.itemNumber }),
1928
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
1929
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium leading-tight", children: item.description }),
1930
+ /* @__PURE__ */ jsxs("span", { className: "block truncate text-xs text-muted-foreground", children: [
1931
+ qty(item.quantity),
1932
+ " ",
1933
+ item.unit ?? "",
1934
+ " \xB7 ",
1935
+ money2(item.unitPrice),
1936
+ typeLabel ? ` \xB7 ${typeLabel}` : " \xB7 sem tipo"
1937
+ ] })
1938
+ ] }),
1939
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm tabular-nums", children: money2(item.total) }),
1940
+ d.suggestedBy && !done && /* @__PURE__ */ jsx(AuthorBadge, { draft: d }),
1941
+ /* @__PURE__ */ jsx(ItemState, { done: done && ready, ready })
1942
+ ]
1943
+ }
1944
+ ),
1945
+ open && /* @__PURE__ */ jsxs("div", { className: "grid gap-4 border-t p-4 lg:grid-cols-2", children: [
1946
+ /* @__PURE__ */ jsxs("div", { children: [
1947
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Item na nota" }),
1948
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 font-medium leading-tight", children: [
1949
+ item.itemNumber,
1950
+ ". ",
1951
+ item.description
1952
+ ] }),
1953
+ /* @__PURE__ */ jsxs("dl", { className: "mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs sm:grid-cols-4", children: [
1954
+ /* @__PURE__ */ jsx(Field, { small: true, label: "Qtd", value: `${qty(item.quantity)} ${item.unit ?? ""}` }),
1955
+ /* @__PURE__ */ jsx(Field, { small: true, label: "Vl. unit.", value: money2(item.unitPrice) }),
1956
+ /* @__PURE__ */ jsx(Field, { small: true, label: "Total", value: money2(item.total) }),
1957
+ /* @__PURE__ */ jsx(Field, { small: true, label: "C\xF3d", value: item.supplierProductCode || "\u2014", mono: true }),
1958
+ /* @__PURE__ */ jsx(Field, { small: true, label: "EAN", value: item.eanBarcode ?? "\u2014", mono: true }),
1959
+ /* @__PURE__ */ jsx(Field, { small: true, label: "NCM", value: item.ncm ?? "\u2014", mono: true }),
1960
+ /* @__PURE__ */ jsx(Field, { small: true, label: "CFOP", value: item.cfop ?? "\u2014", mono: true }),
1961
+ /* @__PURE__ */ jsx(Field, { small: true, label: "CST/CSOSN", value: item.icmsCst ?? item.icmsCsosn ?? "\u2014", mono: true })
1962
+ ] })
1963
+ ] }),
1964
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 lg:border-l lg:pl-4", children: [
1965
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Item no estoque" }),
1966
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1967
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Tipo do registro" }),
1968
+ /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: ENTRY_TYPES.map((t) => /* @__PURE__ */ jsxs(
1969
+ Button,
1970
+ {
1971
+ variant: "bare",
1972
+ size: "bare",
1973
+ type: "button",
1974
+ disabled: readOnly,
1975
+ onClick: () => patch(item.id, { entryType: t.value }),
1976
+ className: `gap-0 font-normal rounded-full border px-2.5 py-1 text-[11px] transition-colors disabled:opacity-60 ${d.entryType === t.value ? "border-primary bg-primary text-primary-foreground" : "border-input hover:bg-muted"}`,
1977
+ children: [
1978
+ t.label,
1979
+ t.hint && /* @__PURE__ */ jsxs("span", { className: "ml-1 opacity-70", children: [
1980
+ "\xB7 ",
1981
+ t.hint
1982
+ ] })
1983
+ ]
1984
+ },
1985
+ t.value
1986
+ )) })
1987
+ ] }),
1988
+ bearing && /* @__PURE__ */ jsxs(Fragment, { children: [
1989
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1990
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Produto vinculado" }),
1991
+ /* @__PURE__ */ jsx(
1992
+ SearchSelect,
1993
+ {
1994
+ value: d.productId ?? void 0,
1995
+ displayValue: d.productLabel || void 0,
1996
+ onChange: (id, option) => {
1997
+ patch(item.id, { productId: id || null, productLabel: option?.label ?? "" });
1998
+ if (id && !d.stockUnitId) {
1999
+ void listProductStockUnits([id]).then((m) => {
2000
+ setProductUnits((prev) => new Map([...prev, ...m]));
2001
+ const unit = suggestedStockUnit(item, units, m, id);
2002
+ if (unit) patch(item.id, { stockUnitId: unit });
2003
+ }).catch(() => {
2004
+ });
2005
+ }
2006
+ },
2007
+ onSearch: async (term) => {
2008
+ const found = await searchProducts(term || item.description.slice(0, 24));
2009
+ return found.map((prod) => ({
2010
+ id: prod.id,
2011
+ label: prod.name,
2012
+ subtitle: prod.sku ?? void 0
2013
+ }));
2014
+ },
2015
+ minChars: 0,
2016
+ placeholder: "Buscar produto do cat\xE1logo",
2017
+ noResultsLabel: "Nenhum produto encontrado",
2018
+ searchingLabel: "Buscando\u2026",
2019
+ clearLabel: "Limpar",
2020
+ disabled: readOnly
2021
+ }
2022
+ )
2023
+ ] }),
2024
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
2025
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2026
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Fator de convers\xE3o" }),
2027
+ /* @__PURE__ */ jsx(
2028
+ Input,
2029
+ {
2030
+ value: d.conversionFactor,
2031
+ onChange: (e) => patch(item.id, { conversionFactor: e.target.value }),
2032
+ inputMode: "decimal",
2033
+ disabled: readOnly
2034
+ }
2035
+ )
2036
+ ] }),
2037
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2038
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Unidade de estoque" }),
2039
+ /* @__PURE__ */ jsx(
2040
+ SearchSelect,
2041
+ {
2042
+ onSearch: localSearch(unitOptions),
2043
+ value: d.stockUnitId ?? void 0,
2044
+ displayValue: labelOf(unitOptions, d.stockUnitId),
2045
+ onChange: (id) => patch(item.id, { stockUnitId: id || null }),
2046
+ minChars: 0,
2047
+ placeholder: "Escolha a unidade",
2048
+ noResultsLabel: "Nenhuma unidade",
2049
+ searchingLabel: "Buscando\u2026",
2050
+ clearLabel: "Limpar",
2051
+ disabled: readOnly
2052
+ }
2053
+ )
2054
+ ] })
2055
+ ] }),
2056
+ suspectFactor(item, d) && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-warning bg-warning-soft px-3 py-2", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-warning-soft-foreground", children: [
2057
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
2058
+ "Este item vem em ",
2059
+ /* @__PURE__ */ jsx("strong", { children: item.unit }),
2060
+ ", que s\xE3o",
2061
+ " ",
2062
+ qty(packagingCount(item.unit) ?? 0),
2063
+ " por embalagem.",
2064
+ " ",
2065
+ !d.stockUnitId ? /* @__PURE__ */ jsxs(Fragment, { children: [
2066
+ "Escolha a ",
2067
+ /* @__PURE__ */ jsx("strong", { children: "unidade de estoque" }),
2068
+ " para confirmar o fator \u2014 o produto est\xE1 cadastrado sem unidade."
2069
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2070
+ "O fator ainda \xE9 1: do jeito que est\xE1 entrariam ",
2071
+ qty(item.quantity),
2072
+ " ",
2073
+ "em vez de ",
2074
+ qty(item.quantity * (packagingCount(item.unit) ?? 1)),
2075
+ "."
2076
+ ] })
2077
+ ] }) }),
2078
+ /* @__PURE__ */ jsxs("div", { className: "rounded-md bg-muted/50 px-3 py-2", children: [
2079
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [
2080
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Qtd estoque (calc.)" }),
2081
+ /* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", children: qty(stockQty) })
2082
+ ] }),
2083
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [
2084
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Novo custo unit\xE1rio" }),
2085
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money2(unitCost) })
2086
+ ] })
2087
+ ] }),
2088
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2089
+ /* @__PURE__ */ jsx(
2090
+ EmBreve,
2091
+ {
2092
+ label: "Criar produto daqui",
2093
+ o_que: "Cadastrar o produto a partir desta linha da nota, com nome, c\xF3digo e unidade j\xE1 preenchidos",
2094
+ quantas: "ela criou 33 produtos no V1 no meio de importa\xE7\xF5es"
2095
+ }
2096
+ ),
2097
+ /* @__PURE__ */ jsx(
2098
+ EmBreve,
2099
+ {
2100
+ label: "Lote e validade",
2101
+ o_que: "Informar lote e vencimento desta entrada \u2014 a coluna j\xE1 existe no movimento de estoque",
2102
+ quantas: "usado 5 vezes no V1; numa churrascaria \xE9 validade de carne"
2103
+ }
2104
+ )
2105
+ ] }),
2106
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
2107
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2108
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Modo de custo" }),
2109
+ /* @__PURE__ */ jsx("div", { className: "mt-1 flex gap-1", children: ["novo", "media"].map((mode) => /* @__PURE__ */ jsx(
2110
+ Button,
2111
+ {
2112
+ variant: "bare",
2113
+ size: "bare",
2114
+ type: "button",
2115
+ disabled: readOnly,
2116
+ onClick: () => patch(item.id, { costMode: mode }),
2117
+ className: `font-normal flex-1 rounded-md border px-2 py-1 text-[11px] disabled:opacity-60 ${d.costMode === mode ? "border-primary bg-primary/10" : "border-input hover:bg-muted"}`,
2118
+ children: mode === "novo" ? "Novo" : "M\xE9dia"
2119
+ },
2120
+ mode
2121
+ )) })
2122
+ ] }),
2123
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2124
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Falta (qtd)" }),
2125
+ /* @__PURE__ */ jsx(
2126
+ Input,
2127
+ {
2128
+ value: d.shortageQuantity,
2129
+ onChange: (e) => patch(item.id, { shortageQuantity: e.target.value }),
2130
+ inputMode: "decimal",
2131
+ placeholder: "0",
2132
+ disabled: readOnly
2133
+ }
2134
+ )
2135
+ ] })
2136
+ ] })
2137
+ ] }),
2138
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2139
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Plano de contas" }),
2140
+ /* @__PURE__ */ jsx(
2141
+ SearchSelect,
2142
+ {
2143
+ onSearch: localSearch(accountOptions),
2144
+ value: d.chartAccountId ?? defaultAccount ?? void 0,
2145
+ displayValue: labelOf(accountOptions, d.chartAccountId ?? defaultAccount),
2146
+ onChange: (id) => patch(item.id, { chartAccountId: id || null }),
2147
+ minChars: 0,
2148
+ placeholder: "Herda o padr\xE3o da nota",
2149
+ noResultsLabel: "Nenhum plano de contas",
2150
+ searchingLabel: "Buscando\u2026",
2151
+ clearLabel: "Limpar",
2152
+ disabled: readOnly
2153
+ }
2154
+ )
2155
+ ] }),
2156
+ item.stockMovementId && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-success-soft-foreground", children: [
2157
+ /* @__PURE__ */ jsx(Check, { className: "mr-1 inline h-3 w-3", "aria-hidden": true }),
2158
+ "Esta linha j\xE1 deu entrada no estoque."
2159
+ ] }),
2160
+ !readOnly && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 border-t pt-3", children: [
2161
+ !ready && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: !d.entryType ? "Escolha o tipo do registro" : "Vincule o produto" }),
2162
+ /* @__PURE__ */ jsxs(
2163
+ Button,
2164
+ {
2165
+ size: "sm",
2166
+ variant: done ? "ghost" : "outline",
2167
+ disabled: !ready,
2168
+ onClick: () => done ? setChecked((prev) => ({ ...prev, [item.id]: false })) : confirmItem(item.id),
2169
+ children: [
2170
+ /* @__PURE__ */ jsx(Check, { className: "mr-1.5 h-4 w-4" }),
2171
+ done ? "Conferido" : "Conferir item"
2172
+ ]
2173
+ }
2174
+ )
2175
+ ] })
2176
+ ] })
2177
+ ] })
2178
+ ] }, item.id);
2179
+ })
2180
+ ] }),
2181
+ /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
2182
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
2183
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados de pagamento" }),
2184
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
2185
+ instalments.length,
2186
+ " ",
2187
+ instalments.length === 1 ? "parcela" : "parcelas",
2188
+ " \xB7",
2189
+ " ",
2190
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money2(instalmentSum) })
2191
+ ] })
2192
+ ] }),
2193
+ note.declaredPayments.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
2194
+ "A nota declarou: ",
2195
+ note.declaredPayments.map((p) => `${p.label ?? p.code} ${money2(p.value)}`).join(" \xB7 ")
2196
+ ] }),
2197
+ /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-2", children: instalments.map((inst, i) => /* @__PURE__ */ jsxs("div", { className: "grid items-end gap-2 sm:grid-cols-[2rem_1fr_1fr_1fr_2rem]", children: [
2198
+ /* @__PURE__ */ jsx("span", { className: "pb-2 text-xs tabular-nums text-muted-foreground", children: inst.index }),
2199
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2200
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Vencimento" }),
2201
+ paymentLocked ? /* @__PURE__ */ jsx("p", { className: "py-2 text-sm tabular-nums", children: day2(inst.dueDate) }) : /* @__PURE__ */ jsx(
2202
+ DatePicker,
2203
+ {
2204
+ value: inst.dueDate ?? "",
2205
+ onChange: (v) => setInstalments((prev) => prev.map((p, j) => j === i ? { ...p, dueDate: v || null } : p))
2206
+ }
2207
+ )
2208
+ ] }),
2209
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2210
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Valor" }),
2211
+ /* @__PURE__ */ jsx(
2212
+ Input,
2213
+ {
2214
+ value: String(inst.amount),
2215
+ inputMode: "decimal",
2216
+ disabled: paymentLocked,
2217
+ onChange: (e) => setInstalments((prev) => prev.map((p, j) => j === i ? { ...p, amount: Number(e.target.value.replace(",", ".")) || 0 } : p))
2218
+ }
2219
+ )
2220
+ ] }),
2221
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2222
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "C\xF3d. barras / PIX" }),
2223
+ /* @__PURE__ */ jsx(
2224
+ Input,
2225
+ {
2226
+ value: inst.barcode ?? inst.pixKey ?? "",
2227
+ disabled: paymentLocked,
2228
+ onChange: (e) => setInstalments((prev) => prev.map((p, j) => j === i ? { ...p, barcode: e.target.value } : p))
2229
+ }
2230
+ )
2231
+ ] }),
2232
+ !paymentLocked && /* @__PURE__ */ jsx(
2233
+ Button,
2234
+ {
2235
+ variant: "ghost",
2236
+ size: "sm",
2237
+ "aria-label": `Remover a parcela ${inst.index}`,
2238
+ onClick: () => setInstalments((prev) => prev.filter((_, j) => j !== i)),
2239
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
2240
+ }
2241
+ )
2242
+ ] }, i)) }),
2243
+ !paymentLocked && /* @__PURE__ */ jsxs(
2244
+ Button,
2245
+ {
2246
+ variant: "outline",
2247
+ size: "sm",
2248
+ className: "mt-3",
2249
+ onClick: () => setInstalments((prev) => [...prev, {
2250
+ index: prev.length + 1,
2251
+ number: null,
2252
+ dueDate: note.issueDate,
2253
+ amount: 0,
2254
+ paymentType: "boleto"
2255
+ }]),
2256
+ children: [
2257
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
2258
+ "Adicionar parcela"
2259
+ ]
2260
+ }
2261
+ ),
2262
+ Math.abs(instalmentSum - note.total) > 0.01 && /* @__PURE__ */ jsx(Hint, { className: "mt-3", children: /* @__PURE__ */ jsxs("span", { children: [
2263
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
2264
+ "A nota diz ",
2265
+ money2(note.total),
2266
+ " e as parcelas somam ",
2267
+ money2(instalmentSum),
2268
+ ". O t\xEDtulo vai nascer com ",
2269
+ /* @__PURE__ */ jsx("strong", { children: money2(instalmentSum) }),
2270
+ " \u2014 \xE9 a soma das parcelas que define o saldo a pagar."
2271
+ ] }) })
2272
+ ] }),
2273
+ /* @__PURE__ */ jsx(
2274
+ NoteAttachmentsCard,
2275
+ {
2276
+ noteId: note.id,
2277
+ invoiceId: note.invoiceId,
2278
+ readOnly,
2279
+ instalments,
2280
+ onInstalments: setInstalments
2281
+ }
2282
+ ),
2283
+ error && /* @__PURE__ */ jsx(Card, { className: "border-destructive bg-destructive/5 p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }) })
2284
+ ] }), `Concilia\xE7\xE3o \xB7 NF-e ${note.number}`, rodape);
2285
+ }
2286
+ function AuthorBadge({ draft }) {
2287
+ if (draft.suggestedBy === "ai") {
2288
+ const confira = draft.suggestionConfidence != null && draft.suggestionConfidence < 0.6;
2289
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center rounded-full bg-magic-soft px-2 py-0.5 text-[11px] font-medium text-magic-soft-foreground", children: [
2290
+ "IA",
2291
+ confira ? " \xB7 confira" : ""
2292
+ ] });
2293
+ }
2294
+ return /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0 items-center rounded-full bg-info-soft px-2 py-0.5 text-[11px] font-medium text-info-soft-foreground", children: draft.suggestedBy === "ean" ? "EAN" : "fornecedor" });
2295
+ }
2296
+ function ItemState({ done, ready }) {
2297
+ if (done) {
2298
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center rounded-full bg-success-soft px-2 py-0.5 text-[11px] font-medium text-success-soft-foreground", children: [
2299
+ /* @__PURE__ */ jsx(Check, { className: "mr-1 h-3 w-3", "aria-hidden": true }),
2300
+ "Conferido"
2301
+ ] });
2302
+ }
2303
+ return /* @__PURE__ */ jsx("span", { className: `inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${ready ? "bg-muted text-muted-foreground" : "bg-warning-soft text-warning-soft-foreground"}`, children: ready ? "A conferir" : "Falta decidir" });
2304
+ }
2305
+ function Field({ label, value, mono, small }) {
2306
+ return /* @__PURE__ */ jsxs("div", { children: [
2307
+ /* @__PURE__ */ jsx("dt", { className: `text-muted-foreground ${small ? "text-[10px]" : "text-xs"}`, children: label }),
2308
+ /* @__PURE__ */ jsx("dd", { className: `${mono ? "tabular-nums" : ""} ${small ? "text-xs" : "text-sm"}`, children: value })
2309
+ ] });
2310
+ }
2311
+
2312
+ export { NfeParseError, NoteReconcileView, PullByKeyDialog, PullByKeyError, STOCK_BEARING_ENTRY_TYPES, acceptNote, addNoteAttachment, extractPaymentCode, fillInboxLines, findInboxNoteByNumber, findInboxNoteByOperation, getInboxIntakeHealth, getInboxNote, getInboxNotePdf, getInboxNoteXml, getNotePayable, healInboxNote, isStockBearing, linkInboxSupplier, listChartAccounts, listInboxNotes, listMeasurementUnits, listNoteAttachments, listProductStockUnits, listProductsByIds, listStockLocations, listSupplierMap, moveNoteAttachmentsToInvoice, onlyDigits, parseBoleto, parseNfeXml, paymentLabel, paymentTypeFromCode, pullNoteByKey, rejectNote, removeNoteAttachment, saveNoteDecision, searchProducts, stageNoteFromXml, suggestNoteItems, syncNewNotes, toStagePayload };
2313
+ //# sourceMappingURL=chunk-UYGGBJCR.js.map
2314
+ //# sourceMappingURL=chunk-UYGGBJCR.js.map