@fayz-ai/plugin-fiscal-br 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { recordRailFields, RECORD_GROUP, activeFacet, getSupabaseClientOptional, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus, maskCpfCnpj } from '@fayz-ai/core';
2
- import React5, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
3
- import { ModalHeader, ModalTitle, ModalBody, Skeleton, AiActionButton, Card, Button, EmBreve, SearchSelect, Input, DatePicker, Hint, ModalFooter, SubpageHeader, useFilterDraft, PageHeaderActions, FilterPanel, FilterPanelField, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SearchField, DataTable, ListFilterBar, Modal, ModalContent, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, ModalDescription, Badge, SegmentedControl, ComingSoon, toast, CardHeader, CardTitle, CardContent, CardDescription, Switch } from '@fayz-ai/ui';
1
+ import { recordRailFields, RECORD_GROUP, activeFacet, describeError, getSupabaseClientOptional, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus, maskCpfCnpj } from '@fayz-ai/core';
2
+ import React7, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
3
+ import { ModalHeader, ModalTitle, ModalBody, Skeleton, AiActionButton, Dropdown, DropdownTrigger, Button, DropdownContent, DropdownItem, Hint, Card, SearchSelect, Input, EmBreve, DatePicker, ModalFooter, SubpageHeader, useFilterDraft, RowActions, PageHeaderActions, FilterPanel, FilterPanelField, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SearchField, TooltipProvider, DataTable, Tooltip, TooltipTrigger, TooltipContent, ListFilterBar, Modal, ModalContent, ModalDescription, Badge, SegmentedControl, ComingSoon, toast, CardHeader, CardTitle, CardContent, CardDescription, Switch } from '@fayz-ai/ui';
4
4
  export { ComingSoon, EmBreve } from '@fayz-ai/ui';
5
- import { registerDocumentOpener, getActiveTenantId, useUnitFilter, usePermissionOptional, UNIT_DETAIL_ZONES, adminNavigateTo } from '@fayz-ai/admin';
6
- import { Gift, RefreshCw, Loader2, FileDown, AlertTriangle, ChevronRight, Check, Trash2, Plus, Inbox, UploadCloud, ShieldCheck, FileText, ExternalLink, CheckCircle2, FileCheck2, ChevronDown, XCircle, X, Info } from 'lucide-react';
5
+ import { registerDocumentOpener, PersonLink, getActiveTenantId, useUnitFilter, usePermissionOptional, UNIT_DETAIL_ZONES, adminNavigateTo } from '@fayz-ai/admin';
6
+ import { Loader2, Download, ChevronDown, FileCode2, FileText, RefreshCw, UserPlus, Gift, AlertTriangle, ChevronRight, Check, Trash2, Plus, Image, UploadCloud, Paperclip, ScanLine, Eye, ListChecks, Inbox, KeyRound, FileDown, ShieldCheck, ExternalLink, CheckCircle2, FileCheck2, XCircle, X, Info } from 'lucide-react';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import { FINANCIAL_INVOICE_ZONES } from '@fayz-ai/plugin-financial';
9
9
 
@@ -3771,6 +3771,119 @@ REVOKE ALL ON FUNCTION public.fiscalbr_note_apply(uuid, text, jsonb, jsonb) FROM
3771
3771
  GRANT EXECUTE ON FUNCTION public.fiscalbr_note_for_provider(uuid, text) TO authenticated;
3772
3772
  GRANT EXECUTE ON FUNCTION public.fiscalbr_note_apply(uuid, text, jsonb, jsonb) TO authenticated;
3773
3773
  `;
3774
+ var MIGRATION_020_A_NOTA_ACHA_O_FORNECEDOR_PELO_CNPJ = `-- A nota acha o fornecedor pelo CNPJ \u2014 e, se n\xE3o achar, cadastra.
3775
+ --
3776
+ -- O est\xE1gio (009) s\xF3 casava o CNPJ no momento do import. As notas que vieram
3777
+ -- do V1 atravessaram sem o v\xEDnculo (003 deixou para "a costura"), e um
3778
+ -- fornecedor cadastrado depois nunca era religado: 1029 notas com o fornecedor
3779
+ -- no cadastro e a tela dizendo "n\xE3o est\xE1 no cadastro", e o t\xEDtulo aceito no V2
3780
+ -- nascendo sem contraparte (o tra\xE7o em Contas a Pagar).
3781
+ --
3782
+ -- O V1 nunca pediu cadastro: ao aceitar, \`resolveSupplierId\` procura o
3783
+ -- fornecedor pelo CNPJ e, sem achar, cria com o nome e o CNPJ da nota. Esta
3784
+ -- fun\xE7\xE3o \xE9 esse gesto, com duas pontas:
3785
+ --
3786
+ -- p_create = false s\xF3 casa (ao abrir a nota)
3787
+ -- p_create = true casa ou cria (antes de aceitar)
3788
+ --
3789
+ -- O t\xEDtulo da nota, se j\xE1 existe e est\xE1 SEM contraparte, recebe a mesma pessoa.
3790
+ -- Nunca troca um v\xEDnculo gravado. Idempotente.
3791
+
3792
+ CREATE OR REPLACE FUNCTION public.plg_fiscalbr_supplier_for_cnpj(p_tenant uuid, p_cnpj text)
3793
+ RETURNS uuid
3794
+ LANGUAGE sql
3795
+ STABLE
3796
+ SET search_path TO ''
3797
+ AS $$
3798
+ SELECT p.id FROM public.people p
3799
+ WHERE p.tenant_id = p_tenant
3800
+ AND p_cnpj <> ''
3801
+ AND public.plg_fiscalbr_digits(p.document_number) = p_cnpj
3802
+ ORDER BY (p.kind = 'supplier') DESC, p.created_at
3803
+ LIMIT 1
3804
+ $$;
3805
+
3806
+ CREATE OR REPLACE FUNCTION public.fiscalbr_link_inbox_supplier(p_document_id uuid, p_create boolean DEFAULT false)
3807
+ RETURNS jsonb
3808
+ LANGUAGE plpgsql
3809
+ SECURITY DEFINER
3810
+ SET search_path TO ''
3811
+ AS $$
3812
+ DECLARE
3813
+ v_tenant uuid := public.plg_financial_require_tenant();
3814
+ v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
3815
+ v_cnpj text;
3816
+ v_party uuid;
3817
+ v_created boolean := false;
3818
+ BEGIN
3819
+ SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
3820
+ WHERE id = p_document_id AND tenant_id = v_tenant;
3821
+ IF NOT FOUND THEN
3822
+ RAISE EXCEPTION 'fiscal: inbox note not found' USING ERRCODE = 'P0002';
3823
+ END IF;
3824
+ PERFORM public.plg_financial_require_permission(
3825
+ CASE WHEN p_create THEN 'financial.create' ELSE 'financial.read' END, v_doc.unit_id);
3826
+
3827
+ v_party := v_doc.supplier_party_id;
3828
+ v_cnpj := public.plg_fiscalbr_digits(v_doc.supplier_cnpj);
3829
+
3830
+ IF v_party IS NULL THEN
3831
+ v_party := public.plg_fiscalbr_supplier_for_cnpj(v_tenant, v_cnpj);
3832
+ END IF;
3833
+
3834
+ -- Without a CNPJ there is nothing to match on; a name-only person would be a
3835
+ -- duplicate waiting to happen, so creation needs the document.
3836
+ IF v_party IS NULL AND p_create AND v_cnpj <> '' THEN
3837
+ INSERT INTO public.people (tenant_id, kind, name, document_number)
3838
+ VALUES (v_tenant, 'supplier', coalesce(nullif(trim(v_doc.supplier_name), ''), 'Fornecedor ' || v_cnpj), v_cnpj)
3839
+ RETURNING id INTO v_party;
3840
+ v_created := true;
3841
+ END IF;
3842
+
3843
+ IF v_party IS NOT NULL AND v_doc.supplier_party_id IS NULL THEN
3844
+ UPDATE public.plg_fiscalbr_inbox_documents
3845
+ SET supplier_party_id = v_party
3846
+ WHERE id = v_doc.id AND supplier_party_id IS NULL;
3847
+ END IF;
3848
+
3849
+ IF v_party IS NOT NULL AND v_doc.invoice_id IS NOT NULL THEN
3850
+ UPDATE public.plg_financial_invoices
3851
+ SET counterparty_person_id = v_party
3852
+ WHERE id = v_doc.invoice_id AND tenant_id = v_tenant AND counterparty_person_id IS NULL;
3853
+ END IF;
3854
+
3855
+ RETURN jsonb_build_object('supplier_party_id', v_party, 'created', v_created);
3856
+ END;
3857
+ $$;
3858
+
3859
+ REVOKE ALL ON FUNCTION public.fiscalbr_link_inbox_supplier(uuid, boolean) FROM PUBLIC;
3860
+ GRANT EXECUTE ON FUNCTION public.fiscalbr_link_inbox_supplier(uuid, boolean) TO authenticated;
3861
+
3862
+ -- Backfill: match only, NULLs only. Nothing is created here \u2014 creating a person
3863
+ -- is the operator's gesture (accepting), not a migration's.
3864
+ WITH p AS (
3865
+ SELECT DISTINCT ON (tenant_id, dg) tenant_id, dg, id
3866
+ FROM (SELECT tenant_id, id, kind, created_at, public.plg_fiscalbr_digits(document_number) AS dg
3867
+ FROM public.people
3868
+ WHERE document_number IS NOT NULL) x
3869
+ WHERE length(dg) = 14
3870
+ ORDER BY tenant_id, dg, (kind = 'supplier') DESC, created_at
3871
+ )
3872
+ UPDATE public.plg_fiscalbr_inbox_documents d
3873
+ SET supplier_party_id = p.id
3874
+ FROM p
3875
+ WHERE d.supplier_party_id IS NULL
3876
+ AND p.tenant_id = d.tenant_id
3877
+ AND p.dg = public.plg_fiscalbr_digits(d.supplier_cnpj);
3878
+
3879
+ UPDATE public.plg_financial_invoices i
3880
+ SET counterparty_person_id = d.supplier_party_id
3881
+ FROM public.plg_fiscalbr_inbox_documents d
3882
+ WHERE d.invoice_id = i.id
3883
+ AND d.tenant_id = i.tenant_id
3884
+ AND d.supplier_party_id IS NOT NULL
3885
+ AND i.counterparty_person_id IS NULL;
3886
+ `;
3774
3887
  var MIGRATIONS = [
3775
3888
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
3776
3889
  { id: "001_preserve_terminal_results_and_recover_leases", sql: MIGRATION_001_PRESERVE_TERMINAL_RESULTS_AND_RECOVER_LEASES },
@@ -3791,7 +3904,8 @@ var MIGRATIONS = [
3791
3904
  { id: "016_a_nota_guarda_o_proprio_xml", sql: MIGRATION_016_A_NOTA_GUARDA_O_PROPRIO_XML },
3792
3905
  { id: "017_quem_emite_e_o_cnpj_nao_a_loja", sql: MIGRATION_017_QUEM_EMITE_E_O_CNPJ_NAO_A_LOJA },
3793
3906
  { id: "018_a_nota_do_v1_alcanca_a_conta", sql: MIGRATION_018_A_NOTA_DO_V1_ALCANCA_A_CONTA },
3794
- { id: "019_a_nota_emitida_se_cancela_pelo_v2", sql: MIGRATION_019_A_NOTA_EMITIDA_SE_CANCELA_PELO_V2 }
3907
+ { id: "019_a_nota_emitida_se_cancela_pelo_v2", sql: MIGRATION_019_A_NOTA_EMITIDA_SE_CANCELA_PELO_V2 },
3908
+ { id: "020_a_nota_acha_o_fornecedor_pelo_cnpj", sql: MIGRATION_020_A_NOTA_ACHA_O_FORNECEDOR_PELO_CNPJ }
3795
3909
  ];
3796
3910
  var taxGroupEntity = {
3797
3911
  name: "Grupo fiscal",
@@ -4366,6 +4480,44 @@ async function stageNoteFromXml(xml, options = {}) {
4366
4480
  parsed
4367
4481
  };
4368
4482
  }
4483
+ var PullByKeyError = class extends Error {
4484
+ constructor(message, code, options) {
4485
+ super(message);
4486
+ this.code = code;
4487
+ this.options = options;
4488
+ }
4489
+ };
4490
+ async function pullNoteByKey(key, options = {}) {
4491
+ const { data, error } = await sb().functions.invoke("fiscalbr-dfe-pull", {
4492
+ body: { key: key.replace(/\D/g, ""), legalEntityId: options.legalEntityId, tenantId: getActiveTenantId() ?? null }
4493
+ });
4494
+ if (error) {
4495
+ let body = null;
4496
+ try {
4497
+ body = await error?.context?.json?.();
4498
+ } catch {
4499
+ }
4500
+ throw new PullByKeyError(body?.error || error.message || "Falha ao falar com a SEFAZ.", body?.code, body?.options);
4501
+ }
4502
+ if (data?.pending) return { status: "pending", message: String(data.message ?? "") };
4503
+ if (!data?.xml) throw new PullByKeyError("A SEFAZ n\xE3o devolveu o XML desta nota.");
4504
+ return { status: "staged", ...await stageNoteFromXml(String(data.xml), { unitId: options.unitId }) };
4505
+ }
4506
+ async function getInboxNotePdf(accessKey) {
4507
+ const { data, error } = await sb().functions.invoke("fiscalbr-dfe-pull", {
4508
+ body: { action: "pdf", key: accessKey, tenantId: getActiveTenantId() ?? null }
4509
+ });
4510
+ if (error) {
4511
+ let body = null;
4512
+ try {
4513
+ body = await error?.context?.json?.();
4514
+ } catch {
4515
+ }
4516
+ throw new PullByKeyError(body?.error || error.message || "Falha ao buscar o DANFE.", body?.code);
4517
+ }
4518
+ if (!(data instanceof Blob)) throw new PullByKeyError("O servi\xE7o fiscal n\xE3o devolveu um PDF.");
4519
+ return data;
4520
+ }
4369
4521
  async function saveNoteDecision(input) {
4370
4522
  const { data, error } = await sb().rpc("fiscalbr_save_inbox_decision", {
4371
4523
  p_document_id: input.documentId,
@@ -4404,7 +4556,16 @@ async function saveNoteDecision(input) {
4404
4556
  reconciled: Number(res.reconciled ?? 0)
4405
4557
  };
4406
4558
  }
4559
+ async function linkInboxSupplier(documentId, create = false) {
4560
+ const { data, error } = await sb().rpc("fiscalbr_link_inbox_supplier", { p_document_id: documentId, p_create: create });
4561
+ if (error) throw error;
4562
+ const res = data ?? {};
4563
+ return { supplierPartyId: res.supplier_party_id ?? null, created: Boolean(res.created) };
4564
+ }
4407
4565
  async function acceptNote(documentId, locationId) {
4566
+ await linkInboxSupplier(documentId, true).catch((e) => {
4567
+ if (e?.code !== "PGRST202" && e?.code !== "42883") throw e;
4568
+ });
4408
4569
  const { data, error } = await sb().rpc("fiscalbr_accept_inbox_document", {
4409
4570
  p_document_id: documentId,
4410
4571
  p_location_id: locationId ?? null
@@ -4531,6 +4692,222 @@ async function listSupplierMap(supplierCnpj) {
4531
4692
  lastUsedAt: m.last_used_at ?? null
4532
4693
  }));
4533
4694
  }
4695
+ var NOTE_SUBJECT = "fiscalbr_inbox_document";
4696
+ var INVOICE_SUBJECT = "financial_invoice";
4697
+ async function listNoteAttachments(noteId, invoiceId) {
4698
+ const subjects = [`and(subject_type.eq.${NOTE_SUBJECT},subject_id.eq.${noteId})`];
4699
+ if (invoiceId) subjects.push(`and(subject_type.eq.${INVOICE_SUBJECT},subject_id.eq.${invoiceId})`);
4700
+ 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");
4701
+ if (error) throw error;
4702
+ const rows = data ?? [];
4703
+ const signed = /* @__PURE__ */ new Map();
4704
+ const byBucket = /* @__PURE__ */ new Map();
4705
+ for (const r of rows) {
4706
+ if (!r.storage_bucket || !r.storage_path) continue;
4707
+ byBucket.set(r.storage_bucket, [...byBucket.get(r.storage_bucket) ?? [], r.storage_path]);
4708
+ }
4709
+ for (const [bucket, paths] of byBucket) {
4710
+ const { data: urls } = await sb().storage.from(bucket).createSignedUrls(paths, 60 * 30);
4711
+ for (const u of urls ?? []) if (u.path && u.signedUrl) signed.set(u.path, u.signedUrl);
4712
+ }
4713
+ return rows.map((r) => ({
4714
+ id: r.id,
4715
+ fileName: r.file_name ?? "Anexo",
4716
+ fileSize: r.file_size ?? void 0,
4717
+ mimeType: r.mime_type ?? void 0,
4718
+ url: signed.get(r.storage_path) ?? void 0,
4719
+ createdAt: r.created_at
4720
+ }));
4721
+ }
4722
+ async function addNoteAttachment(noteId, invoiceId, file) {
4723
+ const tenantId = getActiveTenantId();
4724
+ if (!tenantId) throw new Error("Conta sem empresa ativa");
4725
+ const subjectType = invoiceId ? INVOICE_SUBJECT : NOTE_SUBJECT;
4726
+ const subjectId = invoiceId ?? noteId;
4727
+ const path = `${tenantId}/${subjectType}/${subjectId}/${Date.now()}_${file.name.replace(/[^\w.\-]+/g, "_")}`;
4728
+ const { error: upErr } = await sb().storage.from("attachments").upload(path, file, { contentType: file.type || void 0 });
4729
+ if (upErr) throw upErr;
4730
+ const { error } = await sb().from("documents").insert({
4731
+ tenant_id: tenantId,
4732
+ kind: "attachment",
4733
+ status: "active",
4734
+ subject_type: subjectType,
4735
+ subject_id: subjectId,
4736
+ file_name: file.name,
4737
+ file_size: file.size,
4738
+ mime_type: file.type || null,
4739
+ storage_provider: "supabase",
4740
+ storage_bucket: "attachments",
4741
+ storage_path: path
4742
+ });
4743
+ if (error) {
4744
+ await sb().storage.from("attachments").remove([path]).catch(() => {
4745
+ });
4746
+ throw error;
4747
+ }
4748
+ }
4749
+ async function removeNoteAttachment(id) {
4750
+ const { error } = await sb().from("documents").update({ is_active: false }).eq("id", id);
4751
+ if (error) throw error;
4752
+ }
4753
+ async function moveNoteAttachmentsToInvoice(noteId, invoiceId) {
4754
+ const { error } = await sb().from("documents").update({ subject_type: INVOICE_SUBJECT, subject_id: invoiceId }).eq("subject_type", NOTE_SUBJECT).eq("subject_id", noteId);
4755
+ if (error) throw error;
4756
+ }
4757
+ async function extractPaymentCode(file) {
4758
+ const base64 = await new Promise((resolve, reject) => {
4759
+ const reader = new FileReader();
4760
+ reader.onload = () => {
4761
+ const r = String(reader.result);
4762
+ resolve(r.slice(r.indexOf(",") + 1));
4763
+ };
4764
+ reader.onerror = () => reject(reader.error);
4765
+ reader.readAsDataURL(file);
4766
+ });
4767
+ const { data, error } = await sb().functions.invoke("fiscalbr-payment-extract", {
4768
+ body: { name: file.name, mime: file.type, base64 }
4769
+ });
4770
+ if (error) {
4771
+ let body = null;
4772
+ try {
4773
+ body = await error?.context?.json?.();
4774
+ } catch {
4775
+ }
4776
+ throw new Error(body?.error || error.message || "Falha ao ler o boleto.");
4777
+ }
4778
+ if (!data?.ok || data.kind === "none" || !data.code) return null;
4779
+ const code = String(data.code);
4780
+ if (data.kind === "barcode") {
4781
+ const parsed = parseBoleto(code);
4782
+ return {
4783
+ kind: "barcode",
4784
+ code: code.replace(/\D/g, ""),
4785
+ amount: parsed?.amount ?? (typeof data.amount === "number" ? data.amount : null),
4786
+ dueDate: parsed?.dueDate ?? (data.due_date ?? null)
4787
+ };
4788
+ }
4789
+ return { kind: "pix", code, amount: typeof data.amount === "number" ? data.amount : null, dueDate: data.due_date ?? null };
4790
+ }
4791
+ function parseBoleto(raw) {
4792
+ const d = raw.replace(/\D/g, "");
4793
+ if (d.startsWith("8")) return null;
4794
+ let barcode;
4795
+ if (d.length === 44) barcode = d;
4796
+ 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);
4797
+ else return null;
4798
+ const factor = Number(barcode.slice(5, 9));
4799
+ const cents = Number(barcode.slice(9, 19));
4800
+ let dueDate = null;
4801
+ if (factor > 0) {
4802
+ const base = factor >= 1e3 && Date.now() >= Date.UTC(2025, 1, 22) ? Date.UTC(2025, 1, 22) - 1e3 * 864e5 : Date.UTC(1997, 9, 7);
4803
+ dueDate = new Date(base + factor * 864e5).toISOString().slice(0, 10);
4804
+ }
4805
+ return { amount: cents > 0 ? cents / 100 : null, dueDate };
4806
+ }
4807
+ var formatCnpj = (d) => `${d.slice(0, 2)}.${d.slice(2, 5)}.${d.slice(5, 8)}/${d.slice(8, 12)}-${d.slice(12)}`;
4808
+ var groupKey = (digits2) => digits2.replace(/(\d{4})(?=\d)/g, "$1 ");
4809
+ function PullByKeyDialog({ open, onOpenChange, unitId, onPulled }) {
4810
+ const [value, setValue] = useState("");
4811
+ const [busy, setBusy] = useState(false);
4812
+ const [error, setError] = useState(null);
4813
+ const [notice, setNotice] = useState(null);
4814
+ const [cnpjOptions, setCnpjOptions] = useState(null);
4815
+ const [legalEntityId, setLegalEntityId] = useState("");
4816
+ useEffect(() => {
4817
+ if (open) return;
4818
+ setValue("");
4819
+ setError(null);
4820
+ setNotice(null);
4821
+ setCnpjOptions(null);
4822
+ setLegalEntityId("");
4823
+ }, [open]);
4824
+ const digits2 = value.replace(/\D/g, "").slice(0, 44);
4825
+ const needsCnpj = !!cnpjOptions && !legalEntityId;
4826
+ const blocked = digits2.length !== 44 ? `Faltam ${44 - digits2.length} d\xEDgitos` : needsCnpj ? "Escolha o CNPJ" : null;
4827
+ const pull = async () => {
4828
+ setBusy(true);
4829
+ setError(null);
4830
+ setNotice(null);
4831
+ try {
4832
+ const result = await pullNoteByKey(digits2, { legalEntityId: legalEntityId || void 0, unitId });
4833
+ if (result.status === "pending") {
4834
+ setNotice(result.message);
4835
+ return;
4836
+ }
4837
+ onPulled(result);
4838
+ onOpenChange(false);
4839
+ } catch (e) {
4840
+ if (e instanceof PullByKeyError && e.code === "choose_cnpj" && e.options?.length) {
4841
+ setCnpjOptions(e.options);
4842
+ setError(null);
4843
+ return;
4844
+ }
4845
+ setError(e instanceof Error ? e.message : String(e));
4846
+ } finally {
4847
+ setBusy(false);
4848
+ }
4849
+ };
4850
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
4851
+ if (!busy) onOpenChange(o);
4852
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "md", children: [
4853
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
4854
+ /* @__PURE__ */ jsxs(ModalTitle, { className: "flex items-center gap-2", children: [
4855
+ /* @__PURE__ */ jsx(KeyRound, { className: "h-5 w-5", "aria-hidden": true }),
4856
+ "Puxar NF-e por chave de acesso"
4857
+ ] }),
4858
+ /* @__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." })
4859
+ ] }),
4860
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
4861
+ /* @__PURE__ */ jsx(
4862
+ Input,
4863
+ {
4864
+ value: groupKey(digits2),
4865
+ onChange: (e) => {
4866
+ setValue(e.target.value);
4867
+ setError(null);
4868
+ setNotice(null);
4869
+ },
4870
+ onKeyDown: (e) => {
4871
+ if (e.key === "Enter" && !blocked && !busy) void pull();
4872
+ },
4873
+ placeholder: "0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000",
4874
+ inputMode: "numeric",
4875
+ autoFocus: true,
4876
+ className: "font-mono tabular-nums",
4877
+ "aria-label": "Chave de acesso",
4878
+ "data-testid": "pull-by-key-input"
4879
+ }
4880
+ ),
4881
+ /* @__PURE__ */ jsxs("p", { className: `text-xs tabular-nums ${digits2.length === 44 ? "text-success-soft-foreground" : "text-muted-foreground"}`, children: [
4882
+ digits2.length,
4883
+ "/44 d\xEDgitos"
4884
+ ] }),
4885
+ cnpjOptions && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
4886
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "De qual CNPJ \xE9 esta nota?" }),
4887
+ /* @__PURE__ */ jsxs(Select, { value: legalEntityId, onValueChange: setLegalEntityId, children: [
4888
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Escolha o CNPJ destinat\xE1rio" }) }),
4889
+ /* @__PURE__ */ jsx(SelectContent, { children: cnpjOptions.map((o) => /* @__PURE__ */ jsxs(SelectItem, { value: o.id, children: [
4890
+ o.name,
4891
+ " \xB7 ",
4892
+ formatCnpj(o.cnpj)
4893
+ ] }, o.id)) })
4894
+ ] })
4895
+ ] }),
4896
+ error && /* @__PURE__ */ jsx("p", { className: "rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive", role: "alert", children: error }),
4897
+ notice && /* @__PURE__ */ jsx("p", { className: "rounded-md bg-warning-soft px-3 py-2 text-sm text-warning-soft-foreground", role: "status", children: notice }),
4898
+ /* @__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." })
4899
+ ] }),
4900
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
4901
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: busy, children: "Cancelar" }),
4902
+ /* @__PURE__ */ jsxs(Button, { onClick: () => {
4903
+ void pull();
4904
+ }, disabled: busy || !!blocked, "data-testid": "pull-by-key-submit", children: [
4905
+ 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" }),
4906
+ busy ? "Buscando na SEFAZ\u2026" : blocked ?? "Puxar nota"
4907
+ ] })
4908
+ ] })
4909
+ ] }) });
4910
+ }
4534
4911
  var money = (value) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(value || 0);
4535
4912
  var day = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
4536
4913
  var cnpj = (digits2) => {
@@ -4554,6 +4931,7 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4554
4931
  const [busy, setBusy] = useState(false);
4555
4932
  const [report, setReport] = useState(null);
4556
4933
  const [dragging, setDragging] = useState(false);
4934
+ const [pullOpen, setPullOpen] = useState(false);
4557
4935
  const fileInput = useRef(null);
4558
4936
  const unidades = useUnitFilter({ scope: "list" });
4559
4937
  const { draft, applied, patch, setDraft, dirty, apply, reset } = useFilterDraft({
@@ -4614,6 +4992,36 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4614
4992
  setReport(next);
4615
4993
  await reload();
4616
4994
  }, [reload]);
4995
+ const [rowError, setRowError] = useState(null);
4996
+ const downloadXml = useCallback(async (note) => {
4997
+ setRowError(null);
4998
+ try {
4999
+ const xml = await getInboxNoteXml(note.id);
5000
+ if (!xml) {
5001
+ setRowError(`O XML da nota ${note.number} n\xE3o est\xE1 guardado.`);
5002
+ return;
5003
+ }
5004
+ const url = URL.createObjectURL(new Blob([xml], { type: "application/xml" }));
5005
+ const a = document.createElement("a");
5006
+ a.href = url;
5007
+ a.download = `${note.accessKey || note.number || "nfe"}.xml`;
5008
+ a.click();
5009
+ URL.revokeObjectURL(url);
5010
+ } catch (e) {
5011
+ setRowError(e instanceof Error ? e.message : String(e));
5012
+ }
5013
+ }, []);
5014
+ const openPdf = useCallback(async (note) => {
5015
+ setRowError(null);
5016
+ const tab = window.open("", "_blank");
5017
+ try {
5018
+ const pdf = await getInboxNotePdf(note.accessKey ?? "");
5019
+ if (tab) tab.location.href = URL.createObjectURL(pdf);
5020
+ } catch (e) {
5021
+ tab?.close();
5022
+ setRowError(e instanceof Error ? e.message : String(e));
5023
+ }
5024
+ }, []);
4617
5025
  const remove = useCallback(async (note) => {
4618
5026
  await rejectNote(note.id, "Recusada na lista de entrada");
4619
5027
  await reload();
@@ -4653,17 +5061,12 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4653
5061
  id: "supplier",
4654
5062
  header: "Fornecedor",
4655
5063
  accessorFn: (note) => note.supplierName,
5064
+ meta: { truncate: true },
4656
5065
  cell: ({ row }) => /* @__PURE__ */ jsxs("div", { children: [
4657
5066
  /* @__PURE__ */ jsx("p", { className: "font-medium leading-tight", children: row.original.supplierName || "\u2014" }),
4658
5067
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground tabular-nums", children: cnpj(row.original.supplierCnpj) })
4659
5068
  ] })
4660
5069
  },
4661
- {
4662
- id: "items",
4663
- header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Itens" }),
4664
- accessorFn: (note) => note.itemCount,
4665
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums text-muted-foreground", children: row.original.itemCount })
4666
- },
4667
5070
  {
4668
5071
  id: "reconciled",
4669
5072
  header: () => /* @__PURE__ */ jsx("span", { className: "block text-center", children: "Concilia\xE7\xE3o" }),
@@ -4697,24 +5100,44 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4697
5100
  enableSorting: false,
4698
5101
  cell: ({ row }) => {
4699
5102
  const note = row.original;
4700
- return /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", children: [
4701
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: note.stockedAt ? "ghost" : "outline", onClick: () => onOpenNote(note.id), children: note.stockedAt ? "Ver" : "Conciliar" }),
4702
- !note.stockedAt && !note.invoiceId && /* @__PURE__ */ jsx(
4703
- Button,
4704
- {
4705
- size: "sm",
4706
- variant: "ghost",
4707
- "aria-label": `Recusar a nota ${note.number}`,
4708
- onClick: () => {
4709
- void remove(note);
4710
- },
4711
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
4712
- }
4713
- )
4714
- ] });
5103
+ const done = !!note.stockedAt || note.status === "rejected";
5104
+ return (
5105
+ // The row itself opens the note; the buttons must not fire it a second time.
5106
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", onClick: (e) => e.stopPropagation(), children: [
5107
+ /* @__PURE__ */ jsx(ActionIcon, { label: done ? "Ver nota" : "Conciliar", children: /* @__PURE__ */ jsx(
5108
+ Button,
5109
+ {
5110
+ size: "icon",
5111
+ variant: "ghost",
5112
+ "aria-label": done ? `Ver a nota ${note.number}` : `Conciliar a nota ${note.number}`,
5113
+ className: done ? "" : "bg-foreground text-background hover:bg-foreground/85 hover:text-background",
5114
+ onClick: () => onOpenNote(note.id),
5115
+ children: done ? /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ListChecks, { className: "h-4 w-4" })
5116
+ }
5117
+ ) }),
5118
+ /* @__PURE__ */ jsx(
5119
+ RowActions,
5120
+ {
5121
+ inlineLimit: 0,
5122
+ actions: [
5123
+ { id: "xml", label: "Baixar XML", icon: FileCode2, onSelect: () => downloadXml(note) },
5124
+ { id: "pdf", label: "Abrir PDF", icon: FileText, onSelect: () => openPdf(note), hidden: !note.accessKey },
5125
+ {
5126
+ id: "reject",
5127
+ label: "Recusar nota",
5128
+ icon: Trash2,
5129
+ intent: "danger",
5130
+ onSelect: () => remove(note),
5131
+ hidden: done || !!note.invoiceId
5132
+ }
5133
+ ]
5134
+ }
5135
+ )
5136
+ ] })
5137
+ );
4715
5138
  }
4716
5139
  }
4717
- ], [onOpenNote, remove]);
5140
+ ], [onOpenNote, remove, downloadXml, openPdf]);
4718
5141
  const emptyMessage = query || activeCount > 0 ? /* @__PURE__ */ jsxs("div", { children: [
4719
5142
  /* @__PURE__ */ jsx(Inbox, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
4720
5143
  /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota encontrada" }),
@@ -4730,6 +5153,10 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4730
5153
  /* @__PURE__ */ jsx(Inbox, { className: "h-4 w-4 mr-1.5" }),
4731
5154
  "Fila da SEFAZ"
4732
5155
  ] }),
5156
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setPullOpen(true), "data-testid": "pull-by-key", children: [
5157
+ /* @__PURE__ */ jsx(KeyRound, { className: "h-4 w-4 mr-1.5" }),
5158
+ "Puxar por chave"
5159
+ ] }),
4733
5160
  /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => fileInput.current?.click(), disabled: busy, children: [
4734
5161
  busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 mr-1.5 animate-spin" }) : /* @__PURE__ */ jsx(UploadCloud, { className: "h-4 w-4 mr-1.5" }),
4735
5162
  "Importar XML"
@@ -4770,6 +5197,21 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4770
5197
  ]
4771
5198
  }
4772
5199
  ),
5200
+ /* @__PURE__ */ jsx(
5201
+ PullByKeyDialog,
5202
+ {
5203
+ open: pullOpen,
5204
+ onOpenChange: setPullOpen,
5205
+ unitId: applied.unitId || null,
5206
+ onPulled: (r) => {
5207
+ setReport({
5208
+ ok: [{ file: "chave", number: r.parsed.nfeNumber, items: r.itemCount, matched: r.matchedCount, repeated: r.alreadyAccepted }],
5209
+ failed: []
5210
+ });
5211
+ void reload();
5212
+ }
5213
+ }
5214
+ ),
4773
5215
  report && /* @__PURE__ */ jsxs(Card, { className: "p-4 space-y-2", children: [
4774
5216
  report.ok.map((r) => /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
4775
5217
  /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
@@ -4822,6 +5264,7 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4822
5264
  ]
4823
5265
  }
4824
5266
  ),
5267
+ rowError && /* @__PURE__ */ jsx("p", { className: "rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive", role: "alert", children: rowError }),
4825
5268
  /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
4826
5269
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: /* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: /* @__PURE__ */ jsx(
4827
5270
  SearchField,
@@ -4832,7 +5275,7 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4832
5275
  "data-testid": "inbound-notes-search"
4833
5276
  }
4834
5277
  ) }) }),
4835
- /* @__PURE__ */ jsx(
5278
+ /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsx(
4836
5279
  DataTable,
4837
5280
  {
4838
5281
  variant: "bare",
@@ -4840,12 +5283,19 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
4840
5283
  data: loading ? [] : shown,
4841
5284
  loading,
4842
5285
  getRowId: (note) => note.id,
5286
+ onRowClick: (note) => onOpenNote(note.id),
4843
5287
  emptyMessage
4844
5288
  }
4845
- )
5289
+ ) })
4846
5290
  ] })
4847
5291
  ] });
4848
5292
  }
5293
+ function ActionIcon({ label, children }) {
5294
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
5295
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children }),
5296
+ /* @__PURE__ */ jsx(TooltipContent, { children: label })
5297
+ ] });
5298
+ }
4849
5299
  function ReconcileBadge({ done, total }) {
4850
5300
  const full = total > 0 && done === total;
4851
5301
  return /* @__PURE__ */ jsxs(
@@ -4869,6 +5319,182 @@ function StockBadge({ stockedAt, status }) {
4869
5319
  "Estocado"
4870
5320
  ] }) : /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-warning-soft px-2 py-0.5 text-[11px] font-medium text-warning-soft-foreground", children: "Pendente" });
4871
5321
  }
5322
+ var MAX_BYTES = 15 * 1024 * 1024;
5323
+ var money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
5324
+ var day2 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
5325
+ var size = (b) => !b ? "" : b < 1024 * 1024 ? `${Math.max(1, Math.round(b / 1024))} KB` : `${(b / 1024 / 1024).toFixed(1)} MB`;
5326
+ function applyPaymentCode(list, code) {
5327
+ const field = code.kind === "pix" ? "pixKey" : "barcode";
5328
+ const free = list.map((p, i) => ({ p, i })).filter(({ p }) => !p[field]);
5329
+ const sameAmount = code.amount != null ? free.find(({ p }) => Math.abs((Number(p.amount) || 0) - code.amount) < 0.01) : void 0;
5330
+ const target = sameAmount ?? free[0];
5331
+ const patch = (p) => ({
5332
+ ...p,
5333
+ [field]: code.code,
5334
+ paymentType: code.kind === "pix" ? "pix" : "boleto",
5335
+ dueDate: p.dueDate || code.dueDate,
5336
+ amount: Number(p.amount) > 0 ? p.amount : code.amount ?? 0
5337
+ });
5338
+ if (target) return { next: list.map((p, i) => i === target.i ? patch(p) : p), index: target.i };
5339
+ const fresh = patch({ index: list.length, amount: 0 });
5340
+ return { next: [...list, fresh], index: list.length };
5341
+ }
5342
+ function NoteAttachmentsCard({ noteId, invoiceId, readOnly, instalments, onInstalments }) {
5343
+ const input = useRef(null);
5344
+ const [items, setItems] = useState(null);
5345
+ const [busy, setBusy] = useState(null);
5346
+ const [over, setOver] = useState(false);
5347
+ const [confirming, setConfirming] = useState(null);
5348
+ const [messages, setMessages] = useState([]);
5349
+ const current = useRef(instalments);
5350
+ current.current = instalments;
5351
+ const load = useCallback(async () => {
5352
+ try {
5353
+ setItems(await listNoteAttachments(noteId, invoiceId));
5354
+ } catch {
5355
+ setItems([]);
5356
+ }
5357
+ }, [noteId, invoiceId]);
5358
+ useEffect(() => {
5359
+ void load();
5360
+ }, [load]);
5361
+ const upload = async (files) => {
5362
+ const out = [];
5363
+ for (const file of files) {
5364
+ if (!(file.type.startsWith("image/") || file.type === "application/pdf") || file.size > MAX_BYTES) {
5365
+ out.push({ tone: "error", text: `${file.name}: envie imagem ou PDF de at\xE9 15 MB.` });
5366
+ continue;
5367
+ }
5368
+ setBusy(`Anexando ${file.name}\u2026`);
5369
+ try {
5370
+ await addNoteAttachment(noteId, invoiceId, file);
5371
+ } catch (e) {
5372
+ out.push({ tone: "error", text: `${file.name}: ${e instanceof Error ? e.message : String(e)}` });
5373
+ continue;
5374
+ }
5375
+ if (readOnly) continue;
5376
+ setBusy(`Lendo ${file.name}\u2026`);
5377
+ try {
5378
+ const code = await extractPaymentCode(file);
5379
+ if (!code) {
5380
+ out.push({ tone: "warn", text: `${file.name}: anexado. N\xE3o achei c\xF3digo de boleto nem Pix nele.` });
5381
+ } else {
5382
+ const { next, index } = applyPaymentCode(current.current, code);
5383
+ current.current = next;
5384
+ onInstalments(next);
5385
+ const p = next[index];
5386
+ out.push({
5387
+ tone: "ok",
5388
+ text: `${code.kind === "pix" ? "Pix" : "Boleto"} lido \u2192 parcela ${index + 1}: ${money2(Number(p.amount) || 0)}, vence ${day2(p.dueDate ?? null)}. Confira e salve.`
5389
+ });
5390
+ }
5391
+ } catch (e) {
5392
+ out.push({ tone: "warn", text: `${file.name}: anexado, mas n\xE3o consegui ler o c\xF3digo (${e instanceof Error ? e.message : String(e)}).` });
5393
+ }
5394
+ }
5395
+ setBusy(null);
5396
+ setMessages(out);
5397
+ await load();
5398
+ };
5399
+ const remove = async (a) => {
5400
+ setBusy(`Removendo ${a.fileName}\u2026`);
5401
+ try {
5402
+ await removeNoteAttachment(a.id);
5403
+ setMessages([{ tone: "ok", text: `${a.fileName} removido.` }]);
5404
+ } catch (e) {
5405
+ setMessages([{ tone: "error", text: `${a.fileName}: ${e instanceof Error ? e.message : String(e)}` }]);
5406
+ } finally {
5407
+ setBusy(null);
5408
+ await load();
5409
+ }
5410
+ };
5411
+ return /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
5412
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
5413
+ /* @__PURE__ */ jsxs("div", { children: [
5414
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Anexos da conta" }),
5415
+ /* @__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." })
5416
+ ] }),
5417
+ items && items.length > 0 && /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs text-muted-foreground", children: [
5418
+ items.length,
5419
+ " ",
5420
+ items.length === 1 ? "anexo" : "anexos"
5421
+ ] })
5422
+ ] }),
5423
+ 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: [
5424
+ 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 }),
5425
+ 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 }),
5426
+ confirming === a.id ? (
5427
+ // Inline, not a dialog: this card already lives inside a modal.
5428
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
5429
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Remover?" }),
5430
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: () => {
5431
+ setConfirming(null);
5432
+ void remove(a);
5433
+ }, children: "Remover" }),
5434
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setConfirming(null), children: "Cancelar" })
5435
+ ] })
5436
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
5437
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: size(a.fileSize) }),
5438
+ /* @__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" }) }) })
5439
+ ] })
5440
+ ] }, a.id)) }),
5441
+ /* @__PURE__ */ jsxs(
5442
+ "div",
5443
+ {
5444
+ 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"}`,
5445
+ onDragOver: (e) => {
5446
+ e.preventDefault();
5447
+ setOver(true);
5448
+ },
5449
+ onDragLeave: () => setOver(false),
5450
+ onDrop: (e) => {
5451
+ e.preventDefault();
5452
+ setOver(false);
5453
+ void upload(Array.from(e.dataTransfer.files));
5454
+ },
5455
+ children: [
5456
+ busy ? /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
5457
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
5458
+ busy
5459
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5460
+ /* @__PURE__ */ jsx(UploadCloud, { className: "h-6 w-6 text-muted-foreground", "aria-hidden": true }),
5461
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Arraste o boleto ou o comprovante aqui" }),
5462
+ /* @__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: [
5463
+ /* @__PURE__ */ jsx(Paperclip, { className: "mr-1.5 h-4 w-4" }),
5464
+ "Anexar arquivo"
5465
+ ] }) }),
5466
+ !readOnly && /* @__PURE__ */ jsxs("p", { className: "mt-1 flex items-center gap-1 text-[11px] text-muted-foreground", children: [
5467
+ /* @__PURE__ */ jsx(ScanLine, { className: "h-3 w-3", "aria-hidden": true }),
5468
+ " o c\xF3digo de barras e o Pix s\xE3o lidos sozinhos"
5469
+ ] })
5470
+ ] }),
5471
+ /* @__PURE__ */ jsx(
5472
+ "input",
5473
+ {
5474
+ ref: input,
5475
+ type: "file",
5476
+ multiple: true,
5477
+ accept: "image/*,application/pdf",
5478
+ className: "hidden",
5479
+ onChange: (e) => {
5480
+ void upload(Array.from(e.target.files ?? []));
5481
+ e.target.value = "";
5482
+ }
5483
+ }
5484
+ )
5485
+ ]
5486
+ }
5487
+ ),
5488
+ messages.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mt-3 space-y-1", children: messages.map((m, i) => /* @__PURE__ */ jsx(
5489
+ "li",
5490
+ {
5491
+ className: `text-xs ${m.tone === "ok" ? "text-success-soft-foreground" : m.tone === "warn" ? "text-warning-soft-foreground" : "text-destructive"}`,
5492
+ children: m.text
5493
+ },
5494
+ i
5495
+ )) })
5496
+ ] });
5497
+ }
4872
5498
 
4873
5499
  // src/types.ts
4874
5500
  var STOCK_BEARING_ENTRY_TYPES = ["insumo", "revenda", "patrimonio"];
@@ -4891,9 +5517,9 @@ var norm = (u) => (u ?? "").trim().toUpperCase().replace(/\.$/, "");
4891
5517
  function packagingCount(commercialUnit) {
4892
5518
  return PACKAGING_COUNT[norm(commercialUnit)] ?? null;
4893
5519
  }
4894
- var money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
5520
+ var money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
4895
5521
  var qty = (v) => new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 4 }).format(v || 0);
4896
- var day2 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
5522
+ var day3 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
4897
5523
  var ENTRY_TYPES = [
4898
5524
  { value: "insumo", label: "Insumo" },
4899
5525
  { value: "revenda", label: "Produto Revenda" },
@@ -4945,7 +5571,11 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
4945
5571
  const load = useCallback(async () => {
4946
5572
  setLoading(true);
4947
5573
  try {
4948
- const detail = await getInboxNote(noteId);
5574
+ let detail = await getInboxNote(noteId);
5575
+ if (detail && !detail.supplierPartyId && detail.supplierCnpj) {
5576
+ const linked = await linkInboxSupplier(noteId).catch(() => null);
5577
+ if (linked?.supplierPartyId) detail = { ...detail, supplierPartyId: linked.supplierPartyId };
5578
+ }
4949
5579
  setNote(detail);
4950
5580
  const failed = [];
4951
5581
  const [u, a, l] = await Promise.all([
@@ -5121,7 +5751,10 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5121
5751
  const moldura = (node, titulo, rodape2) => emModal ? /* @__PURE__ */ jsxs(Fragment, { children: [
5122
5752
  /* @__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: [
5123
5753
  /* @__PURE__ */ jsx(ModalTitle, { className: "min-w-0 truncate", children: titulo }),
5124
- botaoIA
5754
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
5755
+ botaoIA,
5756
+ ferramentas
5757
+ ] })
5125
5758
  ] }),
5126
5759
  /* @__PURE__ */ jsx(ModalBody, { className: "px-6 py-4", children: node }),
5127
5760
  rodape2 && /* @__PURE__ */ jsx(ModalFooter, { className: "shrink-0 flex-row items-center justify-between gap-3 space-x-0 border-t px-6 py-3 pr-16", children: rodape2 })
@@ -5133,6 +5766,36 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5133
5766
  rodape2 && /* @__PURE__ */ jsx("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: rodape2 })
5134
5767
  ] })
5135
5768
  );
5769
+ const [cadastrando, setCadastrando] = useState(false);
5770
+ const cadastrarFornecedor = useCallback(async () => {
5771
+ if (!note) return;
5772
+ setCadastrando(true);
5773
+ setError(null);
5774
+ try {
5775
+ const { supplierPartyId } = await linkInboxSupplier(note.id, true);
5776
+ if (supplierPartyId) setNote({ ...note, supplierPartyId });
5777
+ } catch (e) {
5778
+ setError(e instanceof Error ? e.message : String(e));
5779
+ } finally {
5780
+ setCadastrando(false);
5781
+ }
5782
+ }, [note]);
5783
+ const [abrindoPdf, setAbrindoPdf] = useState(false);
5784
+ const abrirPdf = useCallback(async () => {
5785
+ if (!note?.accessKey) return;
5786
+ const tab = window.open("", "_blank");
5787
+ setAbrindoPdf(true);
5788
+ setError(null);
5789
+ try {
5790
+ const pdf = await getInboxNotePdf(note.accessKey);
5791
+ if (tab) tab.location.href = URL.createObjectURL(pdf);
5792
+ } catch (e) {
5793
+ tab?.close();
5794
+ setError(e instanceof Error ? e.message : String(e));
5795
+ } finally {
5796
+ setAbrindoPdf(false);
5797
+ }
5798
+ }, [note]);
5136
5799
  const baixarXml = useCallback(async () => {
5137
5800
  if (!note) return;
5138
5801
  setBaixandoXml(true);
@@ -5155,6 +5818,32 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5155
5818
  setBaixandoXml(false);
5156
5819
  }
5157
5820
  }, [note]);
5821
+ const ferramentas = note ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
5822
+ /* @__PURE__ */ jsxs(Dropdown, { children: [
5823
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", disabled: baixandoXml || abrindoPdf, children: [
5824
+ 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" }),
5825
+ "Baixar",
5826
+ /* @__PURE__ */ jsx(ChevronDown, { className: "ml-1 h-3.5 w-3.5 opacity-60" })
5827
+ ] }) }),
5828
+ /* @__PURE__ */ jsxs(DropdownContent, { align: "end", children: [
5829
+ /* @__PURE__ */ jsxs(DropdownItem, { className: "gap-2", disabled: !note.hasXml, onSelect: () => {
5830
+ void baixarXml();
5831
+ }, children: [
5832
+ /* @__PURE__ */ jsx(FileCode2, { className: "h-4 w-4" }),
5833
+ note.hasXml ? "XML da nota" : "XML n\xE3o guardado \u2014 reimporte o .xml"
5834
+ ] }),
5835
+ /* @__PURE__ */ jsxs(DropdownItem, { className: "gap-2", disabled: !note.accessKey, onSelect: () => {
5836
+ void abrirPdf();
5837
+ }, children: [
5838
+ /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4" }),
5839
+ "DANFE (PDF)"
5840
+ ] })
5841
+ ] })
5842
+ ] }),
5843
+ /* @__PURE__ */ jsx(Hint, { label: "Atualizar", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Atualizar a nota", onClick: () => {
5844
+ void load();
5845
+ }, children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" }) }) })
5846
+ ] }) : null;
5158
5847
  const save = async () => {
5159
5848
  if (!note) return;
5160
5849
  setSaving(true);
@@ -5186,7 +5875,10 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5186
5875
  defaultLocationId: location,
5187
5876
  instalments
5188
5877
  });
5189
- setAccepted(await acceptNote(note.id, location));
5878
+ const result = await acceptNote(note.id, location);
5879
+ if (result.invoiceId) await moveNoteAttachmentsToInvoice(note.id, result.invoiceId).catch(() => {
5880
+ });
5881
+ setAccepted(result);
5190
5882
  await load();
5191
5883
  } catch (e) {
5192
5884
  setError(e instanceof Error ? e.message : String(e));
@@ -5279,13 +5971,16 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5279
5971
  return moldura(/* @__PURE__ */ jsxs("div", { className: `space-y-4 ${emModal ? "" : "pb-24"}`, children: [
5280
5972
  cabecalho(`Concilia\xE7\xE3o \xB7 NF-e ${note.number}`),
5281
5973
  accepted && /* @__PURE__ */ jsxs(Card, { className: "border-success bg-success-soft p-4", children: [
5282
- /* @__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." : `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"}.` }),
5974
+ /* @__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." : `Aceita: ${accepted.stockLines} ${accepted.stockLines === 1 ? "linha" : "linhas"} de estoque e um t\xEDtulo de ${money3(accepted.invoiceTotal)} em ${accepted.instalments} ${accepted.instalments === 1 ? "parcela" : "parcelas"}.` }),
5283
5975
  accepted.invoiceId && onOpenInvoice && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => onOpenInvoice(accepted.invoiceId), children: "Abrir a conta a pagar" })
5284
5976
  ] }),
5285
5977
  /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
5286
5978
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Situa\xE7\xE3o fiscal" }),
5287
5979
  /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid gap-x-6 gap-y-2 text-sm sm:grid-cols-2 lg:grid-cols-3", children: [
5288
- /* @__PURE__ */ jsx(Field, { label: "Fornecedor", value: note.supplierName || "\u2014" }),
5980
+ /* @__PURE__ */ jsxs("div", { children: [
5981
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: "Fornecedor" }),
5982
+ /* @__PURE__ */ jsx("dd", { className: "text-sm", children: note.supplierPartyId ? /* @__PURE__ */ jsx(PersonLink, { personId: note.supplierPartyId, name: note.supplierName || "\u2014", kind: "supplier" }) : note.supplierName || "\u2014" })
5983
+ ] }),
5289
5984
  /* @__PURE__ */ jsx(Field, { label: "CNPJ", value: note.supplierCnpj ?? "\u2014", mono: true }),
5290
5985
  /* @__PURE__ */ jsx(Field, { label: "Natureza da opera\xE7\xE3o", value: note.natureOperation ?? "\u2014" }),
5291
5986
  /* @__PURE__ */ jsx(
@@ -5295,15 +5990,22 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5295
5990
  value: `${note.manifestationStatus ?? "n\xE3o manifestada"}${note.manifestationAttempts ? ` \xB7 ${note.manifestationAttempts} tentativa(s)` : ""}`
5296
5991
  }
5297
5992
  ),
5298
- /* @__PURE__ */ jsx(Field, { label: "Manifestada em", value: day2(note.manifestedAt) }),
5299
- /* @__PURE__ */ jsx(Field, { label: "Recebida em", value: day2(note.receivedAt) })
5993
+ /* @__PURE__ */ jsx(Field, { label: "Manifestada em", value: day3(note.manifestedAt) }),
5994
+ /* @__PURE__ */ jsx(Field, { label: "Recebida em", value: day3(note.receivedAt) })
5995
+ ] }),
5996
+ !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: [
5997
+ /* @__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: [
5998
+ "A nota n\xE3o traz o CNPJ do fornecedor, ent\xE3o a conta a pagar vai nascer ",
5999
+ /* @__PURE__ */ jsx("strong", { children: "sem fornecedor" }),
6000
+ "."
6001
+ ] }) }),
6002
+ note.supplierCnpj && !readOnly && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", disabled: cadastrando, onClick: () => {
6003
+ void cadastrarFornecedor();
6004
+ }, children: [
6005
+ 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" }),
6006
+ "Cadastrar agora"
6007
+ ] })
5300
6008
  ] }),
5301
- !note.supplierPartyId && /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-md border border-warning bg-warning-soft px-3 py-2", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-warning-soft-foreground", children: [
5302
- "Este fornecedor n\xE3o est\xE1 no cadastro de pessoas, ent\xE3o o t\xEDtulo a pagar vai nascer",
5303
- " ",
5304
- /* @__PURE__ */ jsx("strong", { children: "sem contraparte" }),
5305
- ". Cadastre o CNPJ antes de aceitar para a conta saber a quem se deve."
5306
- ] }) }),
5307
6009
  /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: [
5308
6010
  /* @__PURE__ */ jsx(Gift, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
5309
6011
  "A natureza desta nota \xE9 ",
@@ -5315,41 +6017,7 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5315
6017
  " ",
5316
6018
  /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 py-0.5 text-[10px]", children: "Em breve" })
5317
6019
  ] }) }),
5318
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [
5319
- /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => {
5320
- void load();
5321
- }, children: [
5322
- /* @__PURE__ */ jsx(RefreshCw, { className: "mr-1.5 h-4 w-4" }),
5323
- "Atualizar status"
5324
- ] }),
5325
- note.hasXml ? /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", disabled: baixandoXml, onClick: () => {
5326
- void baixarXml();
5327
- }, children: [
5328
- baixandoXml ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(FileDown, { className: "mr-1.5 h-4 w-4" }),
5329
- "Baixar XML"
5330
- ] }) : /* @__PURE__ */ jsx(
5331
- EmBreve,
5332
- {
5333
- label: "Baixar XML",
5334
- o_que: "O arquivo desta nota n\xE3o foi guardado \u2014 ela entrou antes de o est\xE1gio passar a gravar o XML. Reimportar o .xml preenche"
5335
- }
5336
- ),
5337
- /* @__PURE__ */ jsx(
5338
- EmBreve,
5339
- {
5340
- label: "DANFE (PDF)",
5341
- o_que: "O espelho impresso da nota. Tem layout fixado pela SEFAZ e vinha renderizado da PlugNotas no V1 \u2014 n\xE3o sai do XML sozinho"
5342
- }
5343
- ),
5344
- /* @__PURE__ */ jsx(
5345
- EmBreve,
5346
- {
5347
- label: "Cadastrar fornecedor",
5348
- o_que: "Criar a pessoa do fornecedor aqui, sem sair da nota \u2014 \xE9 o que preenche a contraparte do t\xEDtulo",
5349
- quantas: "a Laissa cadastrou 37 fornecedores no V1 enquanto importava"
5350
- }
5351
- )
5352
- ] })
6020
+ !emModal && /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: ferramentas })
5353
6021
  ] }),
5354
6022
  /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
5355
6023
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Padr\xF5es desta nota" }),
@@ -5479,11 +6147,11 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5479
6147
  " ",
5480
6148
  item.unit ?? "",
5481
6149
  " \xB7 ",
5482
- money2(item.unitPrice),
6150
+ money3(item.unitPrice),
5483
6151
  typeLabel ? ` \xB7 ${typeLabel}` : " \xB7 sem tipo"
5484
6152
  ] })
5485
6153
  ] }),
5486
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm tabular-nums", children: money2(item.total) }),
6154
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm tabular-nums", children: money3(item.total) }),
5487
6155
  d.suggestedBy && !done && /* @__PURE__ */ jsx(AuthorBadge, { draft: d }),
5488
6156
  /* @__PURE__ */ jsx(ItemState, { done: done && ready, ready })
5489
6157
  ]
@@ -5499,8 +6167,8 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5499
6167
  ] }),
5500
6168
  /* @__PURE__ */ jsxs("dl", { className: "mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs sm:grid-cols-4", children: [
5501
6169
  /* @__PURE__ */ jsx(Field, { small: true, label: "Qtd", value: `${qty(item.quantity)} ${item.unit ?? ""}` }),
5502
- /* @__PURE__ */ jsx(Field, { small: true, label: "Vl. unit.", value: money2(item.unitPrice) }),
5503
- /* @__PURE__ */ jsx(Field, { small: true, label: "Total", value: money2(item.total) }),
6170
+ /* @__PURE__ */ jsx(Field, { small: true, label: "Vl. unit.", value: money3(item.unitPrice) }),
6171
+ /* @__PURE__ */ jsx(Field, { small: true, label: "Total", value: money3(item.total) }),
5504
6172
  /* @__PURE__ */ jsx(Field, { small: true, label: "C\xF3d", value: item.supplierProductCode || "\u2014", mono: true }),
5505
6173
  /* @__PURE__ */ jsx(Field, { small: true, label: "EAN", value: item.eanBarcode ?? "\u2014", mono: true }),
5506
6174
  /* @__PURE__ */ jsx(Field, { small: true, label: "NCM", value: item.ncm ?? "\u2014", mono: true }),
@@ -5617,7 +6285,7 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5617
6285
  ] }),
5618
6286
  /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [
5619
6287
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Novo custo unit\xE1rio" }),
5620
- /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money2(unitCost) })
6288
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money3(unitCost) })
5621
6289
  ] })
5622
6290
  ] }),
5623
6291
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
@@ -5720,18 +6388,18 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5720
6388
  instalments.length === 1 ? "parcela" : "parcelas",
5721
6389
  " \xB7",
5722
6390
  " ",
5723
- /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money2(instalmentSum) })
6391
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money3(instalmentSum) })
5724
6392
  ] })
5725
6393
  ] }),
5726
6394
  note.declaredPayments.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
5727
6395
  "A nota declarou: ",
5728
- note.declaredPayments.map((p) => `${p.label ?? p.code} ${money2(p.value)}`).join(" \xB7 ")
6396
+ note.declaredPayments.map((p) => `${p.label ?? p.code} ${money3(p.value)}`).join(" \xB7 ")
5729
6397
  ] }),
5730
6398
  /* @__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: [
5731
6399
  /* @__PURE__ */ jsx("span", { className: "pb-2 text-xs tabular-nums text-muted-foreground", children: inst.index }),
5732
6400
  /* @__PURE__ */ jsxs("label", { className: "block", children: [
5733
6401
  /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Vencimento" }),
5734
- readOnly ? /* @__PURE__ */ jsx("p", { className: "py-2 text-sm tabular-nums", children: day2(inst.dueDate) }) : /* @__PURE__ */ jsx(
6402
+ readOnly ? /* @__PURE__ */ jsx("p", { className: "py-2 text-sm tabular-nums", children: day3(inst.dueDate) }) : /* @__PURE__ */ jsx(
5735
6403
  DatePicker,
5736
6404
  {
5737
6405
  value: inst.dueDate ?? "",
@@ -5795,41 +6463,24 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5795
6463
  Math.abs(instalmentSum - note.total) > 0.01 && /* @__PURE__ */ jsx(Hint, { className: "mt-3", children: /* @__PURE__ */ jsxs("span", { children: [
5796
6464
  /* @__PURE__ */ jsx(AlertTriangle, { className: "mr-1 inline h-3.5 w-3.5", "aria-hidden": true }),
5797
6465
  "A nota diz ",
5798
- money2(note.total),
6466
+ money3(note.total),
5799
6467
  " e as parcelas somam ",
5800
- money2(instalmentSum),
6468
+ money3(instalmentSum),
5801
6469
  ". O t\xEDtulo vai nascer com ",
5802
- /* @__PURE__ */ jsx("strong", { children: money2(instalmentSum) }),
6470
+ /* @__PURE__ */ jsx("strong", { children: money3(instalmentSum) }),
5803
6471
  " \u2014 \xE9 a soma das parcelas que define o saldo a pagar."
5804
6472
  ] }) })
5805
6473
  ] }),
5806
- /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
5807
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Anexos da conta" }),
5808
- /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
5809
- "Boleto, comprovante, foto da nota. No sistema antigo, anexar o boleto tamb\xE9m",
5810
- " ",
5811
- /* @__PURE__ */ jsx("strong", { children: "preenchia as parcelas sozinho" }),
5812
- " \u2014 \xE9 a automa\xE7\xE3o mais \xFAtil e a mais invis\xEDvel de l\xE1."
5813
- ] }),
5814
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
5815
- /* @__PURE__ */ jsx(
5816
- EmBreve,
5817
- {
5818
- label: "Anexar boleto",
5819
- o_que: "Guardar o arquivo no t\xEDtulo que esta nota cria",
5820
- quantas: "25 anexos no V1, o \xFAltimo hoje"
5821
- }
5822
- ),
5823
- /* @__PURE__ */ jsx(
5824
- EmBreve,
5825
- {
5826
- label: "Ler o c\xF3digo de barras do boleto",
5827
- o_que: "Preencher vencimento e valor da parcela a partir do boleto anexado",
5828
- quantas: "automa\xE7\xE3o do V1 que ningu\xE9m documentou"
5829
- }
5830
- )
5831
- ] })
5832
- ] }),
6474
+ /* @__PURE__ */ jsx(
6475
+ NoteAttachmentsCard,
6476
+ {
6477
+ noteId: note.id,
6478
+ invoiceId: note.invoiceId,
6479
+ readOnly,
6480
+ instalments,
6481
+ onInstalments: setInstalments
6482
+ }
6483
+ ),
5833
6484
  error && /* @__PURE__ */ jsx(Card, { className: "border-destructive bg-destructive/5 p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }) })
5834
6485
  ] }), `Concilia\xE7\xE3o \xB7 NF-e ${note.number}`, rodape);
5835
6486
  }
@@ -5858,8 +6509,8 @@ function Field({ label, value, mono, small }) {
5858
6509
  /* @__PURE__ */ jsx("dd", { className: `${mono ? "tabular-nums" : ""} ${small ? "text-xs" : "text-sm"}`, children: value })
5859
6510
  ] });
5860
6511
  }
5861
- var money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
5862
- var day3 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
6512
+ var money4 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
6513
+ var day4 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
5863
6514
  var MANIFEST_TONE = {
5864
6515
  confirmada: "bg-success-soft text-success-soft-foreground",
5865
6516
  processando: "bg-info-soft text-info-soft-foreground",
@@ -5869,6 +6520,7 @@ function ManifestView({ onOpenNote, onBack }) {
5869
6520
  const [notes, setNotes] = useState([]);
5870
6521
  const [loading, setLoading] = useState(true);
5871
6522
  const [tab, setTab] = useState("pendentes");
6523
+ const [pullOpen, setPullOpen] = useState(false);
5872
6524
  const reload = useCallback(async () => {
5873
6525
  setLoading(true);
5874
6526
  try {
@@ -5918,12 +6570,19 @@ function ManifestView({ onOpenNote, onBack }) {
5918
6570
  /* @__PURE__ */ jsx(Hint, { className: "mt-3", children: /* @__PURE__ */ jsx("span", { children: "A manifesta\xE7\xE3o continua sendo feita no sistema antigo por enquanto. Aqui voc\xEA l\xEA a fila e o que a SEFAZ respondeu; a entrada no estoque e a conta a pagar j\xE1 s\xE3o feitas nesta tela nova." }) }),
5919
6571
  /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
5920
6572
  /* @__PURE__ */ jsx(EmBreve, { label: "Aceitar / Recusar", o_que: "Responder \xE0 SEFAZ \u2014 \xE9 obriga\xE7\xE3o com prazo. Hoje a manifesta\xE7\xE3o \xE9 feita no sistema antigo" }),
5921
- /* @__PURE__ */ jsx(EmBreve, { label: "Puxar por chave", o_que: "Buscar uma NF-e espec\xEDfica pela chave de acesso de 44 d\xEDgitos" }),
6573
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setPullOpen(true), children: [
6574
+ /* @__PURE__ */ jsx(KeyRound, { className: "mr-1.5 h-4 w-4" }),
6575
+ "Puxar por chave"
6576
+ ] }),
5922
6577
  /* @__PURE__ */ jsx(EmBreve, { label: "Sincronizar agora", o_que: "Pedir \xE0 SEFAZ o que chegou desde a \xFAltima varredura" }),
5923
6578
  /* @__PURE__ */ jsx(EmBreve, { label: "Resincronizar tudo", o_que: "Refazer a varredura inteira do per\xEDodo" }),
5924
6579
  /* @__PURE__ */ jsx(EmBreve, { label: "Diagnosticar", o_que: "Por que uma nota n\xE3o chegou: certificado, credencial ou cursor parado" })
5925
6580
  ] })
5926
6581
  ] }),
6582
+ /* @__PURE__ */ jsx(PullByKeyDialog, { open: pullOpen, onOpenChange: setPullOpen, onPulled: (r) => {
6583
+ void reload();
6584
+ onOpenNote(r.documentId);
6585
+ } }),
5927
6586
  /* @__PURE__ */ jsx(ListFilterBar, { filters, onClear: () => setTab("todas"), activeCount: tab === "todas" ? 0 : 1 }),
5928
6587
  loading ? /* @__PURE__ */ jsx("div", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full" }, i)) }) : shown.length === 0 ? /* @__PURE__ */ jsxs(Card, { className: "p-10 text-center", children: [
5929
6588
  /* @__PURE__ */ jsx(FileText, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
@@ -5939,10 +6598,10 @@ function ManifestView({ onOpenNote, onBack }) {
5939
6598
  /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "A\xE7\xF5es" })
5940
6599
  ] }) }),
5941
6600
  /* @__PURE__ */ jsx("tbody", { children: shown.map((note) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 hover:bg-muted/30", children: [
5942
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums text-muted-foreground", children: day3(note.issueDate) }),
6601
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums text-muted-foreground", children: day4(note.issueDate) }),
5943
6602
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums font-medium", children: note.number }),
5944
6603
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: note.supplierName || "\u2014" }),
5945
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums", children: money3(note.total) }),
6604
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums", children: money4(note.total) }),
5946
6605
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums text-muted-foreground", children: note.instalmentCount }),
5947
6606
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-center", children: /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${MANIFEST_TONE[note.manifestationStatus ?? ""] ?? "bg-muted text-muted-foreground"}`, children: note.manifestationStatus ?? "n\xE3o manifestada" }) }),
5948
6607
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
@@ -6078,9 +6737,9 @@ var COLUMNS = [
6078
6737
  ].join(", ");
6079
6738
  var sb2 = () => getSupabaseClientOptional();
6080
6739
  function useEmitterSettings(legalEntityId) {
6081
- const [row, setRow] = React5.useState(null);
6082
- const [loading, setLoading] = React5.useState(true);
6083
- React5.useEffect(() => {
6740
+ const [row, setRow] = React7.useState(null);
6741
+ const [loading, setLoading] = React7.useState(true);
6742
+ React7.useEffect(() => {
6084
6743
  if (!legalEntityId) {
6085
6744
  setLoading(false);
6086
6745
  return;
@@ -6096,7 +6755,7 @@ function useEmitterSettings(legalEntityId) {
6096
6755
  alive = false;
6097
6756
  };
6098
6757
  }, [legalEntityId]);
6099
- const save = React5.useCallback(async (tenantId, patch) => {
6758
+ const save = React7.useCallback(async (tenantId, patch) => {
6100
6759
  if (!legalEntityId) return false;
6101
6760
  const payload = { ...patch, tenant_id: tenantId, legal_entity_id: legalEntityId, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
6102
6761
  const { data, error } = await sb2().from("plg_fiscalbr_emitter_settings").upsert(payload, { onConflict: "legal_entity_id" }).select(COLUMNS).single();
@@ -6149,7 +6808,7 @@ function SummaryCard({ title, lines, badge, onConfigure }) {
6149
6808
  return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-start justify-between gap-3 space-y-0", children: [
6150
6809
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
6151
6810
  /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: title }),
6152
- /* @__PURE__ */ jsx("dl", { className: "mt-2 grid gap-x-6 gap-y-1 text-sm", style: { gridTemplateColumns: "auto 1fr" }, children: lines.map((l) => /* @__PURE__ */ jsxs(React5.Fragment, { children: [
6811
+ /* @__PURE__ */ jsx("dl", { className: "mt-2 grid gap-x-6 gap-y-1 text-sm", style: { gridTemplateColumns: "auto 1fr" }, children: lines.map((l) => /* @__PURE__ */ jsxs(React7.Fragment, { children: [
6153
6812
  /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: l.label }),
6154
6813
  /* @__PURE__ */ jsx("dd", { className: "text-foreground", children: l.value })
6155
6814
  ] }, l.label)) })
@@ -6170,7 +6829,7 @@ function SummaryCard({ title, lines, badge, onConfigure }) {
6170
6829
  var fmtDateTime = (v) => v ? new Date(v).toLocaleString("pt-BR") : null;
6171
6830
  function UnitCertificateWidget({ item }) {
6172
6831
  const { row, loading, save } = useEmitterSettings(item?.legalEntityId);
6173
- const [open, setOpen] = React5.useState(false);
6832
+ const [open, setOpen] = React7.useState(false);
6174
6833
  if (!item?.id) return null;
6175
6834
  if (!item.legalEntityId) return /* @__PURE__ */ jsx(NeedsLegalEntity, { item, what: "O certificado digital" });
6176
6835
  if (loading || !row) return /* @__PURE__ */ jsx(LoadingCard, {});
@@ -6273,11 +6932,11 @@ function EnvSelect({ value, onChange, disabled }) {
6273
6932
  }
6274
6933
  function UnitFiscalWidget({ item }) {
6275
6934
  const { row, loading, save } = useEmitterSettings(item?.legalEntityId);
6276
- const [draft, setDraft] = React5.useState({});
6277
- const [cscToken, setCscToken] = React5.useState("");
6278
- const [saving, setSaving] = React5.useState(false);
6279
- const [open, setOpen] = React5.useState(false);
6280
- React5.useEffect(() => {
6935
+ const [draft, setDraft] = React7.useState({});
6936
+ const [cscToken, setCscToken] = React7.useState("");
6937
+ const [saving, setSaving] = React7.useState(false);
6938
+ const [open, setOpen] = React7.useState(false);
6939
+ React7.useEffect(() => {
6281
6940
  if (row) setDraft(row);
6282
6941
  }, [row]);
6283
6942
  if (!item?.id) return null;
@@ -6645,7 +7304,7 @@ var dayOrDateTime = (v) => {
6645
7304
  const d = new Date(v);
6646
7305
  return d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0 ? d.toLocaleDateString("pt-BR") : dateTime(v);
6647
7306
  };
6648
- var money4 = (v) => v == null ? "\u2014" : v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
7307
+ var money5 = (v) => v == null ? "\u2014" : v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
6649
7308
  function buttonHint(s) {
6650
7309
  const parts = [s.hasValid ? "Notas fiscais desta conta \u2014 h\xE1 uma nota v\xE1lida" : "Notas fiscais desta conta"];
6651
7310
  if (s.withoutEffect > 0) parts.push(`${s.withoutEffect} cancelada(s), rejeitada(s) ou com erro`);
@@ -6713,7 +7372,7 @@ function NoteRow({ note, canCancel, onChanged }) {
6713
7372
  else window.location.assign(url);
6714
7373
  } catch (e) {
6715
7374
  w?.close();
6716
- toast.error("PDF indispon\xEDvel", { description: e instanceof Error ? e.message : String(e) });
7375
+ toast.error("PDF indispon\xEDvel", { description: describeError(e) });
6717
7376
  } finally {
6718
7377
  setBusy(null);
6719
7378
  }
@@ -6725,7 +7384,7 @@ function NoteRow({ note, canCancel, onChanged }) {
6725
7384
  await onChanged();
6726
7385
  toast(status === note.status ? "Ainda em processamento na SEFAZ/prefeitura" : `Nota ${statusLabel({ status, cancelledAt: null }).toLowerCase()}`);
6727
7386
  } catch (e) {
6728
- toast.error("N\xE3o foi poss\xEDvel consultar", { description: e instanceof Error ? e.message : String(e) });
7387
+ toast.error("N\xE3o foi poss\xEDvel consultar", { description: describeError(e) });
6729
7388
  } finally {
6730
7389
  setBusy(null);
6731
7390
  }
@@ -6813,7 +7472,7 @@ function CancelNotePanel({ note, onClose, onDone }) {
6813
7472
  else toast("Cancelamento solicitado", { description: "A SEFAZ/prefeitura ainda est\xE1 processando. Use \xABAtualizar\xBB para conferir." });
6814
7473
  await onDone();
6815
7474
  } catch (e) {
6816
- setError(e instanceof Error ? e.message : String(e));
7475
+ setError(describeError(e));
6817
7476
  setWorking(null);
6818
7477
  }
6819
7478
  };
@@ -6884,7 +7543,7 @@ function NoteDetails({ note }) {
6884
7543
  ["Refer\xEAncia", note.integrationId && /* @__PURE__ */ jsx("span", { className: "break-all font-mono text-xs", children: note.integrationId })],
6885
7544
  ["Chave de acesso", note.accessKey && /* @__PURE__ */ jsx("span", { className: "break-all font-mono text-xs", children: note.accessKey })],
6886
7545
  ["C\xF3digo de verifica\xE7\xE3o", note.verificationCode],
6887
- ["Valor", money4(note.totalValue)],
7546
+ ["Valor", money5(note.totalValue)],
6888
7547
  ["Tomador", recipientLabel(note.recipient)],
6889
7548
  ["Criada em", dateTime(note.createdAt)],
6890
7549
  ["Autorizada em", note.authorizedAt && dayOrDateTime(note.authorizedAt)],
@@ -7099,9 +7758,9 @@ function inboundNotesInventoryView(options = {}) {
7099
7758
  };
7100
7759
  }
7101
7760
  function InboundNoteDocumentModal({ movement, onClose }) {
7102
- const [noteId, setNoteId] = React5.useState(null);
7103
- const [erro, setErro] = React5.useState(null);
7104
- React5.useEffect(() => {
7761
+ const [noteId, setNoteId] = React7.useState(null);
7762
+ const [erro, setErro] = React7.useState(null);
7763
+ React7.useEffect(() => {
7105
7764
  let vivo = true;
7106
7765
  const numero = nfeNumberOf(movement);
7107
7766
  const busca = movement.operationId ? findInboxNoteByOperation(movement.operationId).then((id) => id ?? (numero ? findInboxNoteByNumber(numero) : null)) : numero ? findInboxNoteByNumber(numero) : Promise.resolve(null);
@@ -7110,7 +7769,7 @@ function InboundNoteDocumentModal({ movement, onClose }) {
7110
7769
  if (id) setNoteId(id);
7111
7770
  else setErro("N\xE3o achei a nota desta entrada nesta conta.");
7112
7771
  }).catch((e) => {
7113
- if (vivo) setErro(e instanceof Error ? e.message : String(e));
7772
+ if (vivo) setErro(describeError(e));
7114
7773
  });
7115
7774
  return () => {
7116
7775
  vivo = false;
@@ -7283,6 +7942,6 @@ function createPlugNotasPlugin(options = {}) {
7283
7942
  });
7284
7943
  }
7285
7944
 
7286
- export { EVENT_LABEL, InboundNoteDocumentModal, InboundNotesPage, InboundNotesView, InvoiceNotesButton, IssuedNotesView, MODEL_LABEL, ManifestView, NfeParseError, NoteReconcileView, PLUGNOTAS_CONNECTOR_ID, PLUGNOTAS_FIELDS, PLUGNOTAS_SETTINGS, ReturnedNotesView, STATUS_META, STOCK_BEARING_ENTRY_TYPES, TaxGroupsView, acceptNote, cancelNote, createPlugNotasConnector, createPlugNotasFinancialProvider, createPlugNotasPlugin, eventStatusLabel, explainRejection, fetchNotePdf, findInboxNoteByNumber, findInboxNoteByOperation, getInboxNote, getInboxNoteXml, inboundNoteDocumentModal, inboundNotesInventoryView, isStockBearing, isValidCnpj, isWithoutEffect, listChartAccounts, listInboxNotes, listInvoiceNotes, listMeasurementUnits, listNoteEvents, listProductsByIds, listStockLocations, listSupplierMap, markPlugNotasEmitterReady, notesButtonState, onlyDigits, parseNfeXml, paymentLabel, paymentTypeFromCode, recipientLabel, refreshNote, rejectNote, saveNoteDecision, searchProducts, stageNoteFromXml, statusLabel, suggestNoteItems, summarizeNotes, toStagePayload };
7945
+ export { EVENT_LABEL, InboundNoteDocumentModal, InboundNotesPage, InboundNotesView, InvoiceNotesButton, IssuedNotesView, MODEL_LABEL, ManifestView, NfeParseError, NoteReconcileView, PLUGNOTAS_CONNECTOR_ID, PLUGNOTAS_FIELDS, PLUGNOTAS_SETTINGS, PullByKeyError, ReturnedNotesView, STATUS_META, STOCK_BEARING_ENTRY_TYPES, TaxGroupsView, acceptNote, addNoteAttachment, cancelNote, createPlugNotasConnector, createPlugNotasFinancialProvider, createPlugNotasPlugin, eventStatusLabel, explainRejection, extractPaymentCode, fetchNotePdf, findInboxNoteByNumber, findInboxNoteByOperation, getInboxNote, getInboxNotePdf, getInboxNoteXml, inboundNoteDocumentModal, inboundNotesInventoryView, isStockBearing, isValidCnpj, isWithoutEffect, linkInboxSupplier, listChartAccounts, listInboxNotes, listInvoiceNotes, listMeasurementUnits, listNoteAttachments, listNoteEvents, listProductsByIds, listStockLocations, listSupplierMap, markPlugNotasEmitterReady, moveNoteAttachmentsToInvoice, notesButtonState, onlyDigits, parseBoleto, parseNfeXml, paymentLabel, paymentTypeFromCode, pullNoteByKey, recipientLabel, refreshNote, rejectNote, removeNoteAttachment, saveNoteDecision, searchProducts, stageNoteFromXml, statusLabel, suggestNoteItems, summarizeNotes, toStagePayload };
7287
7946
  //# sourceMappingURL=index.js.map
7288
7947
  //# sourceMappingURL=index.js.map