@fayz-ai/plugin-fiscal-br 0.13.4 → 0.13.6
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/GapScreens-RDPPFZMG.js +3 -0
- package/dist/GapScreens-RDPPFZMG.js.map +1 -0
- package/dist/InboundNotesPage-R72EC5E4.js +5 -0
- package/dist/InboundNotesPage-R72EC5E4.js.map +1 -0
- package/dist/InvoiceNotesButton-3SMNHWMQ.js +5 -0
- package/dist/InvoiceNotesButton-3SMNHWMQ.js.map +1 -0
- package/dist/NoteReconcileView-3KVXZLCG.js +4 -0
- package/dist/NoteReconcileView-3KVXZLCG.js.map +1 -0
- package/dist/OrderNoteCard-H6P66R4J.js +5 -0
- package/dist/OrderNoteCard-H6P66R4J.js.map +1 -0
- package/dist/UnitFiscalWidgets-KCHSISBS.js +503 -0
- package/dist/UnitFiscalWidgets-KCHSISBS.js.map +1 -0
- package/dist/chunk-2GHTB3Z2.js +66 -0
- package/dist/chunk-2GHTB3Z2.js.map +1 -0
- package/dist/chunk-7NW3EBOQ.js +101 -0
- package/dist/chunk-7NW3EBOQ.js.map +1 -0
- package/dist/chunk-G7T5YMNY.js +241 -0
- package/dist/chunk-G7T5YMNY.js.map +1 -0
- package/dist/chunk-JRP5AZFJ.js +64 -0
- package/dist/chunk-JRP5AZFJ.js.map +1 -0
- package/dist/chunk-OGXYHL3Z.js +590 -0
- package/dist/chunk-OGXYHL3Z.js.map +1 -0
- package/dist/chunk-RZT2BO6E.js +64 -0
- package/dist/chunk-RZT2BO6E.js.map +1 -0
- package/dist/chunk-UYGGBJCR.js +2314 -0
- package/dist/chunk-UYGGBJCR.js.map +1 -0
- package/dist/chunk-WID54RUV.js +498 -0
- package/dist/chunk-WID54RUV.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -9391
- package/dist/index.js.map +1 -1
- package/dist/migrations/main.browser.js +39 -0
- package/dist/migrations/main.browser.js.map +1 -0
- package/dist/migrations/main.js +4912 -0
- package/dist/migrations/main.js.map +1 -0
- package/dist/views/InboundNotesPage.d.ts.map +1 -1
- package/dist/views/inventoryView.d.ts.map +1 -1
- package/package.json +11 -5
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import { listInboxNotes, getInboxIntakeHealth, syncNewNotes, stageNoteFromXml, getInboxNoteXml, getInboxNotePdf, rejectNote, PullByKeyDialog, findInboxNoteByOperation, NoteReconcileView } from './chunk-UYGGBJCR.js';
|
|
2
|
+
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
3
|
+
import { useFilterDraft, Button, RowActions, PageHeaderActions, Card, FilterPanel, FilterPanelField, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SearchField, TooltipProvider, DataTable, ChevronPager, Tooltip, TooltipTrigger, TooltipContent, Modal, ModalContent } from '@fayz-ai/ui';
|
|
4
|
+
import { Eye, ListChecks, FileCode2, FileText, Trash2, Loader2, UploadCloud, RefreshCw, KeyRound, FileDown, Inbox } from 'lucide-react';
|
|
5
|
+
import { useUnitFilter, PageActions, adminNavigateTo } from '@fayz-ai/admin';
|
|
6
|
+
import { currentAppPath, appHref, onAppPathChange, navigateApp, formatCurrency } from '@fayz-ai/core';
|
|
7
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
var money = (value) => formatCurrency(value || 0, "BRL");
|
|
10
|
+
var day = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
|
|
11
|
+
var cnpj = (digits) => {
|
|
12
|
+
const d = (digits ?? "").replace(/\D/g, "");
|
|
13
|
+
if (d.length !== 14) return digits ?? "\u2014";
|
|
14
|
+
return `${d.slice(0, 2)}.${d.slice(2, 5)}.${d.slice(5, 8)}/${d.slice(8, 12)}-${d.slice(12)}`;
|
|
15
|
+
};
|
|
16
|
+
var CAMPO = "h-9 w-full rounded-input border border-input bg-background px-3 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2";
|
|
17
|
+
var STATE_OPTIONS = [
|
|
18
|
+
{ value: "all", label: "Todos" },
|
|
19
|
+
{ value: "pending", label: "Estoque pendente" },
|
|
20
|
+
{ value: "stocked", label: "Estocado" },
|
|
21
|
+
{ value: "manifest_error", label: "Manifesta\xE7\xE3o com erro" },
|
|
22
|
+
{ value: "rejected", label: "Recusada" }
|
|
23
|
+
];
|
|
24
|
+
var isoDay = (d) => d.toISOString().slice(0, 10);
|
|
25
|
+
var SYNC_KEY = "fiscalbr.inbox.lastSefazSync";
|
|
26
|
+
function InboundNotesView({ onOpenNote }) {
|
|
27
|
+
const [notes, setNotes] = useState([]);
|
|
28
|
+
const [loading, setLoading] = useState(true);
|
|
29
|
+
const [search, setSearch] = useState("");
|
|
30
|
+
const [busy, setBusy] = useState(false);
|
|
31
|
+
const [report, setReport] = useState(null);
|
|
32
|
+
const [dragging, setDragging] = useState(false);
|
|
33
|
+
const [pullOpen, setPullOpen] = useState(false);
|
|
34
|
+
const fileInput = useRef(null);
|
|
35
|
+
const unidades = useUnitFilter({ scope: "list" });
|
|
36
|
+
const { draft, applied, patch, setDraft, dirty, apply, reset } = useFilterDraft({
|
|
37
|
+
filter: "all",
|
|
38
|
+
unitId: unidades.value ?? "",
|
|
39
|
+
// Sem período por padrão: o V1 abre em «Todos os registros», e uma janela
|
|
40
|
+
// de 30 dias esconderia justamente a nota velha que nunca foi estocada.
|
|
41
|
+
from: "",
|
|
42
|
+
to: ""
|
|
43
|
+
});
|
|
44
|
+
const ancora = unidades.value ?? "";
|
|
45
|
+
const ancoraAnterior = useRef(ancora);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (ancoraAnterior.current === ancora) return;
|
|
48
|
+
ancoraAnterior.current = ancora;
|
|
49
|
+
setDraft((atual) => ({ ...atual, unitId: ancora }));
|
|
50
|
+
apply();
|
|
51
|
+
}, [ancora, setDraft, apply]);
|
|
52
|
+
const reload = useCallback(async () => {
|
|
53
|
+
setLoading(true);
|
|
54
|
+
try {
|
|
55
|
+
setNotes(await listInboxNotes({
|
|
56
|
+
filter: applied.filter,
|
|
57
|
+
unitId: applied.unitId || void 0,
|
|
58
|
+
from: applied.from || void 0,
|
|
59
|
+
to: applied.to || void 0,
|
|
60
|
+
// The list pages on screen; the default 200 would silently drop the rest.
|
|
61
|
+
limit: 5e3
|
|
62
|
+
}));
|
|
63
|
+
} finally {
|
|
64
|
+
setLoading(false);
|
|
65
|
+
}
|
|
66
|
+
}, [applied]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
void reload();
|
|
69
|
+
}, [reload]);
|
|
70
|
+
const [intake, setIntake] = useState(null);
|
|
71
|
+
const [syncing, setSyncing] = useState(false);
|
|
72
|
+
const [syncNote, setSyncNote] = useState(null);
|
|
73
|
+
const refreshIntake = useCallback(() => {
|
|
74
|
+
getInboxIntakeHealth().then(setIntake).catch(() => setIntake(null));
|
|
75
|
+
}, []);
|
|
76
|
+
const syncNow = useCallback(async (quiet) => {
|
|
77
|
+
setSyncing(true);
|
|
78
|
+
if (!quiet) setSyncNote(null);
|
|
79
|
+
try {
|
|
80
|
+
const r = await syncNewNotes();
|
|
81
|
+
try {
|
|
82
|
+
localStorage.setItem(SYNC_KEY, String(Date.now()));
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
if (r.added) void reload();
|
|
86
|
+
const parts = [
|
|
87
|
+
r.added ? `${r.added} ${r.added === 1 ? "nota nova" : "notas novas"}` : null,
|
|
88
|
+
r.waiting ? `${r.waiting} ainda sem o XML completo na SEFAZ` : null,
|
|
89
|
+
r.failed ? `${r.failed} com erro` : null
|
|
90
|
+
].filter(Boolean);
|
|
91
|
+
if (!quiet || parts.length) setSyncNote(parts.length ? `SEFAZ: ${parts.join(" \xB7 ")}.` : "SEFAZ: nenhuma nota nova.");
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (!quiet) setSyncNote(e instanceof Error ? e.message : String(e));
|
|
94
|
+
} finally {
|
|
95
|
+
setSyncing(false);
|
|
96
|
+
refreshIntake();
|
|
97
|
+
}
|
|
98
|
+
}, [reload, refreshIntake]);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
refreshIntake();
|
|
101
|
+
let last = 0;
|
|
102
|
+
try {
|
|
103
|
+
last = Number(localStorage.getItem(SYNC_KEY) ?? 0);
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
if (Date.now() - last > 30 * 6e4) void syncNow(true);
|
|
107
|
+
}, []);
|
|
108
|
+
const ingest = useCallback(async (files) => {
|
|
109
|
+
const xmls = files.filter((f) => /\.xml$/i.test(f.name));
|
|
110
|
+
if (xmls.length === 0) {
|
|
111
|
+
setReport({ ok: [], failed: [{ file: "\u2014", message: "Nenhum arquivo .xml no que foi solto." }] });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
setBusy(true);
|
|
115
|
+
const next = { ok: [], failed: [] };
|
|
116
|
+
for (const file of xmls) {
|
|
117
|
+
try {
|
|
118
|
+
const res = await stageNoteFromXml(await file.text());
|
|
119
|
+
next.ok.push({
|
|
120
|
+
file: file.name,
|
|
121
|
+
number: res.parsed.nfeNumber,
|
|
122
|
+
items: res.itemCount,
|
|
123
|
+
matched: res.matchedCount,
|
|
124
|
+
repeated: res.alreadyAccepted
|
|
125
|
+
});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
next.failed.push({ file: file.name, message: error instanceof Error ? error.message : String(error) });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
setBusy(false);
|
|
131
|
+
setReport(next);
|
|
132
|
+
await reload();
|
|
133
|
+
}, [reload]);
|
|
134
|
+
const [rowError, setRowError] = useState(null);
|
|
135
|
+
const downloadXml = useCallback(async (note) => {
|
|
136
|
+
setRowError(null);
|
|
137
|
+
try {
|
|
138
|
+
const xml = await getInboxNoteXml(note.id);
|
|
139
|
+
if (!xml) {
|
|
140
|
+
setRowError(`O XML da nota ${note.number} n\xE3o est\xE1 guardado.`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const url = URL.createObjectURL(new Blob([xml], { type: "application/xml" }));
|
|
144
|
+
const a = document.createElement("a");
|
|
145
|
+
a.href = url;
|
|
146
|
+
a.download = `${note.accessKey || note.number || "nfe"}.xml`;
|
|
147
|
+
a.click();
|
|
148
|
+
URL.revokeObjectURL(url);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
setRowError(e instanceof Error ? e.message : String(e));
|
|
151
|
+
}
|
|
152
|
+
}, []);
|
|
153
|
+
const openPdf = useCallback(async (note) => {
|
|
154
|
+
setRowError(null);
|
|
155
|
+
const tab = window.open("", "_blank");
|
|
156
|
+
try {
|
|
157
|
+
const pdf = await getInboxNotePdf(note.accessKey ?? "");
|
|
158
|
+
if (tab) tab.location.href = URL.createObjectURL(pdf);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
tab?.close();
|
|
161
|
+
setRowError(e instanceof Error ? e.message : String(e));
|
|
162
|
+
}
|
|
163
|
+
}, []);
|
|
164
|
+
const remove = useCallback(async (note) => {
|
|
165
|
+
await rejectNote(note.id, "Recusada na lista de entrada");
|
|
166
|
+
await reload();
|
|
167
|
+
}, [reload]);
|
|
168
|
+
const activeCount = (applied.filter === "all" ? 0 : 1) + (applied.unitId ? 1 : 0) + (applied.from || applied.to ? 1 : 0);
|
|
169
|
+
const limpar = useCallback(() => {
|
|
170
|
+
reset({ filter: "all", unitId: ancora, from: "", to: "" });
|
|
171
|
+
}, [reset, ancora]);
|
|
172
|
+
const setFrom = (from) => patch({ from, to: draft.to && from > draft.to ? from : draft.to });
|
|
173
|
+
const setTo = (to) => patch({ to, from: draft.from && to < draft.from ? to : draft.from });
|
|
174
|
+
const query = search.trim().toLowerCase();
|
|
175
|
+
const shown = useMemo(() => {
|
|
176
|
+
if (!query) return notes;
|
|
177
|
+
const digits = query.replace(/\D/g, "");
|
|
178
|
+
return notes.filter((note) => {
|
|
179
|
+
if (`${note.number} ${note.series} ${note.supplierName}`.toLowerCase().includes(query)) return true;
|
|
180
|
+
if (digits.length < 2) return false;
|
|
181
|
+
if ((note.supplierCnpj ?? "").replace(/\D/g, "").includes(digits)) return true;
|
|
182
|
+
if ((note.accessKey ?? "").includes(digits)) return true;
|
|
183
|
+
return day(note.issueDate).replace(/\D/g, "").includes(digits);
|
|
184
|
+
});
|
|
185
|
+
}, [notes, query]);
|
|
186
|
+
const [page, setPage] = useState(1);
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
setPage(1);
|
|
189
|
+
}, [notes, query]);
|
|
190
|
+
const pageRows = useMemo(() => shown.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [shown, page]);
|
|
191
|
+
const columns = useMemo(() => [
|
|
192
|
+
{
|
|
193
|
+
id: "issueDate",
|
|
194
|
+
header: "Emiss\xE3o",
|
|
195
|
+
accessorFn: (note) => note.issueDate ?? "",
|
|
196
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: day(row.original.issueDate) })
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: "number",
|
|
200
|
+
header: "N\xBA",
|
|
201
|
+
accessorFn: (note) => Number(note.number) || note.number,
|
|
202
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "tabular-nums font-medium", children: row.original.number })
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "supplier",
|
|
206
|
+
header: "Fornecedor",
|
|
207
|
+
accessorFn: (note) => note.supplierName,
|
|
208
|
+
meta: { truncate: true, mobile: "title" },
|
|
209
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
210
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium leading-tight", children: row.original.supplierName || "\u2014" }),
|
|
211
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground tabular-nums", children: cnpj(row.original.supplierCnpj) })
|
|
212
|
+
] })
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: "reconciled",
|
|
216
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-center", children: "Concilia\xE7\xE3o" }),
|
|
217
|
+
accessorFn: (note) => note.itemCount > 0 ? note.reconciledCount / note.itemCount : -1,
|
|
218
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(ReconcileBadge, { done: row.original.reconciledCount, total: row.original.itemCount }) })
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "total",
|
|
222
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Valor" }),
|
|
223
|
+
accessorFn: (note) => note.total,
|
|
224
|
+
cell: ({ row }) => {
|
|
225
|
+
const note = row.original;
|
|
226
|
+
return /* @__PURE__ */ jsxs("div", { className: "text-right tabular-nums", children: [
|
|
227
|
+
money(note.total),
|
|
228
|
+
note.instalmentCount > 0 && Math.abs(note.instalmentTotal - note.total) > 0.01 && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-warning-soft-foreground", children: [
|
|
229
|
+
"parcelas: ",
|
|
230
|
+
money(note.instalmentTotal)
|
|
231
|
+
] })
|
|
232
|
+
] });
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "stock",
|
|
237
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-center", children: "Estoque" }),
|
|
238
|
+
accessorFn: (note) => note.stockedAt ? 2 : note.status === "rejected" ? 0 : 1,
|
|
239
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(StockBadge, { stockedAt: row.original.stockedAt, status: row.original.status }) })
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "actions",
|
|
243
|
+
header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "A\xE7\xF5es" }),
|
|
244
|
+
enableSorting: false,
|
|
245
|
+
cell: ({ row }) => {
|
|
246
|
+
const note = row.original;
|
|
247
|
+
const done = !!note.stockedAt || note.status === "rejected";
|
|
248
|
+
return (
|
|
249
|
+
// The row itself opens the note; the buttons must not fire it a second time.
|
|
250
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", onClick: (e) => e.stopPropagation(), children: [
|
|
251
|
+
/* @__PURE__ */ jsx(ActionIcon, { label: done ? "Ver nota" : "Conciliar", children: /* @__PURE__ */ jsx(
|
|
252
|
+
Button,
|
|
253
|
+
{
|
|
254
|
+
size: "icon",
|
|
255
|
+
variant: "ghost",
|
|
256
|
+
"aria-label": done ? `Ver a nota ${note.number}` : `Conciliar a nota ${note.number}`,
|
|
257
|
+
className: done ? "" : "bg-foreground text-background hover:bg-foreground/85 hover:text-background",
|
|
258
|
+
onClick: () => onOpenNote(note.id),
|
|
259
|
+
children: done ? /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ListChecks, { className: "h-4 w-4" })
|
|
260
|
+
}
|
|
261
|
+
) }),
|
|
262
|
+
/* @__PURE__ */ jsx(
|
|
263
|
+
RowActions,
|
|
264
|
+
{
|
|
265
|
+
inlineLimit: 0,
|
|
266
|
+
actions: [
|
|
267
|
+
{ id: "xml", label: "Baixar XML", icon: FileCode2, onSelect: () => downloadXml(note) },
|
|
268
|
+
{ id: "pdf", label: "Abrir PDF", icon: FileText, onSelect: () => openPdf(note), hidden: !note.accessKey },
|
|
269
|
+
{
|
|
270
|
+
id: "reject",
|
|
271
|
+
label: "Recusar nota",
|
|
272
|
+
icon: Trash2,
|
|
273
|
+
intent: "danger",
|
|
274
|
+
onSelect: () => remove(note),
|
|
275
|
+
hidden: done || !!note.invoiceId
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
] })
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
], [onOpenNote, remove, downloadXml, openPdf]);
|
|
285
|
+
const emptyMessage = query || activeCount > 0 ? /* @__PURE__ */ jsxs("div", { children: [
|
|
286
|
+
/* @__PURE__ */ jsx(Inbox, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
|
|
287
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota encontrada" }),
|
|
288
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Tente outro n\xFAmero, fornecedor ou CNPJ." })
|
|
289
|
+
] }) : /* @__PURE__ */ jsxs("div", { children: [
|
|
290
|
+
/* @__PURE__ */ jsx(Inbox, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
|
|
291
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota de entrada" }),
|
|
292
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "As notas chegam da SEFAZ sozinhas, ou voc\xEA arrasta o XML aqui." })
|
|
293
|
+
] });
|
|
294
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
295
|
+
/* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(
|
|
296
|
+
PageActions,
|
|
297
|
+
{
|
|
298
|
+
actions: [
|
|
299
|
+
{
|
|
300
|
+
// Picking an XML off a filesystem is a desk job.
|
|
301
|
+
id: "import-xml",
|
|
302
|
+
label: "Importar XML",
|
|
303
|
+
icon: busy ? Loader2 : UploadCloud,
|
|
304
|
+
intent: "primary",
|
|
305
|
+
creates: true,
|
|
306
|
+
desktopOnly: true,
|
|
307
|
+
disabled: busy,
|
|
308
|
+
onSelect: () => fileInput.current?.click()
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "sync-sefaz",
|
|
312
|
+
label: syncing ? "Buscando na SEFAZ\u2026" : "Buscar notas novas",
|
|
313
|
+
icon: syncing ? Loader2 : RefreshCw,
|
|
314
|
+
disabled: syncing,
|
|
315
|
+
testId: "sync-sefaz",
|
|
316
|
+
onSelect: () => {
|
|
317
|
+
void syncNow(false);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: "pull-by-key",
|
|
322
|
+
label: "Puxar por chave",
|
|
323
|
+
icon: KeyRound,
|
|
324
|
+
creates: true,
|
|
325
|
+
testId: "pull-by-key",
|
|
326
|
+
onSelect: () => setPullOpen(true)
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
) }),
|
|
331
|
+
syncNote && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", "data-testid": "sync-note", children: syncNote }),
|
|
332
|
+
intake && /* @__PURE__ */ jsxs(Card, { className: "border-warning bg-warning-soft p-4", "data-testid": "intake-stalled", children: [
|
|
333
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-warning-soft-foreground", children: intake.certificateExpiredAt ? `As notas da SEFAZ n\xE3o est\xE3o chegando: o certificado digital venceu em ${day(intake.certificateExpiredAt)}.` : `As notas da SEFAZ n\xE3o est\xE3o chegando${intake.since ? ` desde ${day(intake.since)}` : ""}.` }),
|
|
334
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-warning-soft-foreground", children: [
|
|
335
|
+
intake.certificateExpiredAt ? "Renove o certificado A1 do CNPJ e envie o arquivo novo. As notas que ficaram para tr\xE1s chegam sozinhas depois disso." : intake.error ?? "A busca autom\xE1tica parou. Enquanto isso, d\xE1 para importar o XML ou puxar a nota pela chave.",
|
|
336
|
+
intake.stalled > 1 ? ` (${intake.stalled} CNPJs afetados)` : ""
|
|
337
|
+
] }),
|
|
338
|
+
intake.unitId && (intake.certificateExpiredAt || intake.error) && /* @__PURE__ */ jsx(
|
|
339
|
+
Button,
|
|
340
|
+
{
|
|
341
|
+
size: "sm",
|
|
342
|
+
variant: "outline",
|
|
343
|
+
className: "mt-3",
|
|
344
|
+
"data-testid": "intake-update-certificate",
|
|
345
|
+
onClick: () => adminNavigateTo(`/settings/locations/${intake.unitId}/fields:tax`),
|
|
346
|
+
children: "Atualizar certificado"
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
] }),
|
|
350
|
+
/* @__PURE__ */ jsxs(
|
|
351
|
+
Card,
|
|
352
|
+
{
|
|
353
|
+
className: `hidden border-dashed p-6 text-center transition-colors md:block ${dragging ? "border-primary bg-primary/5" : ""}`,
|
|
354
|
+
onDragOver: (e) => {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
setDragging(true);
|
|
357
|
+
},
|
|
358
|
+
onDragLeave: () => setDragging(false),
|
|
359
|
+
onDrop: (e) => {
|
|
360
|
+
e.preventDefault();
|
|
361
|
+
setDragging(false);
|
|
362
|
+
void ingest(Array.from(e.dataTransfer.files));
|
|
363
|
+
},
|
|
364
|
+
children: [
|
|
365
|
+
/* @__PURE__ */ jsx(UploadCloud, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
|
|
366
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Arraste arquivos XML aqui ou clique para selecionar" }),
|
|
367
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Suporta m\xFAltiplos arquivos XML de NF-e. O XML do fornecedor d\xE1 entrada no estoque e cria a conta a pagar." }),
|
|
368
|
+
/* @__PURE__ */ jsx(
|
|
369
|
+
"input",
|
|
370
|
+
{
|
|
371
|
+
ref: fileInput,
|
|
372
|
+
type: "file",
|
|
373
|
+
accept: ".xml,text/xml,application/xml",
|
|
374
|
+
multiple: true,
|
|
375
|
+
className: "hidden",
|
|
376
|
+
onChange: (e) => {
|
|
377
|
+
void ingest(Array.from(e.target.files ?? []));
|
|
378
|
+
e.target.value = "";
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
)
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
/* @__PURE__ */ jsx(
|
|
386
|
+
PullByKeyDialog,
|
|
387
|
+
{
|
|
388
|
+
open: pullOpen,
|
|
389
|
+
onOpenChange: setPullOpen,
|
|
390
|
+
unitId: applied.unitId || null,
|
|
391
|
+
onPulled: (r) => {
|
|
392
|
+
setReport({
|
|
393
|
+
ok: [{ file: "chave", number: r.parsed.nfeNumber, items: r.itemCount, matched: r.matchedCount, repeated: r.alreadyAccepted }],
|
|
394
|
+
failed: []
|
|
395
|
+
});
|
|
396
|
+
void reload();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
),
|
|
400
|
+
report && /* @__PURE__ */ jsxs(Card, { className: "p-4 space-y-2", children: [
|
|
401
|
+
report.ok.map((r) => /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
|
|
402
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
403
|
+
"NF-e ",
|
|
404
|
+
r.number
|
|
405
|
+
] }),
|
|
406
|
+
" ",
|
|
407
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: r.repeated ? "\u2014 j\xE1 tinha sido aceita, nada mudou" : `\u2014 ${r.items} ${r.items === 1 ? "item" : "itens"}, ${r.matched} j\xE1 ${r.matched === 1 ? "vinculado" : "vinculados"}` })
|
|
408
|
+
] }, r.file)),
|
|
409
|
+
report.failed.map((r) => /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
|
|
410
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: r.file }),
|
|
411
|
+
" \u2014 ",
|
|
412
|
+
r.message
|
|
413
|
+
] }, r.file)),
|
|
414
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setReport(null), children: "Fechar" })
|
|
415
|
+
] }),
|
|
416
|
+
/* @__PURE__ */ jsxs(
|
|
417
|
+
FilterPanel,
|
|
418
|
+
{
|
|
419
|
+
title: "Filtros",
|
|
420
|
+
dirty,
|
|
421
|
+
onApply: apply,
|
|
422
|
+
applyLabel: "Buscar",
|
|
423
|
+
pendingTitle: "Filtros n\xE3o aplicados",
|
|
424
|
+
pendingHint: "A lista ainda mostra o recorte anterior.",
|
|
425
|
+
onClear: activeCount > 0 ? limpar : void 0,
|
|
426
|
+
clearLabel: "Limpar",
|
|
427
|
+
busy: loading,
|
|
428
|
+
children: [
|
|
429
|
+
unidades.visible && /* @__PURE__ */ jsx(FilterPanelField, { label: unidades.label, children: /* @__PURE__ */ jsxs(
|
|
430
|
+
Select,
|
|
431
|
+
{
|
|
432
|
+
value: draft.unitId || "__all__",
|
|
433
|
+
onValueChange: (v) => patch({ unitId: v === "__all__" ? "" : v }),
|
|
434
|
+
children: [
|
|
435
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
436
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
437
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "__all__", children: unidades.allLabel }),
|
|
438
|
+
unidades.options.map((u) => /* @__PURE__ */ jsx(SelectItem, { value: u.id, children: u.name }, u.id))
|
|
439
|
+
] })
|
|
440
|
+
]
|
|
441
|
+
}
|
|
442
|
+
) }),
|
|
443
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: "Estado", children: /* @__PURE__ */ jsxs(Select, { value: draft.filter, onValueChange: (v) => patch({ filter: v }), children: [
|
|
444
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
445
|
+
/* @__PURE__ */ jsx(SelectContent, { children: STATE_OPTIONS.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value)) })
|
|
446
|
+
] }) }),
|
|
447
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: "Emiss\xE3o de", children: /* @__PURE__ */ jsx("input", { type: "date", value: draft.from, max: isoDay(/* @__PURE__ */ new Date()), onChange: (e) => setFrom(e.target.value), className: CAMPO }) }),
|
|
448
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: "Emiss\xE3o at\xE9", children: /* @__PURE__ */ jsx("input", { type: "date", value: draft.to, max: isoDay(/* @__PURE__ */ new Date()), onChange: (e) => setTo(e.target.value), className: CAMPO }) })
|
|
449
|
+
]
|
|
450
|
+
}
|
|
451
|
+
),
|
|
452
|
+
rowError && /* @__PURE__ */ jsx("p", { className: "rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive", role: "alert", children: rowError }),
|
|
453
|
+
/* @__PURE__ */ jsxs(Card, { style: { overflow: "clip" }, children: [
|
|
454
|
+
/* @__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(
|
|
455
|
+
SearchField,
|
|
456
|
+
{
|
|
457
|
+
value: search,
|
|
458
|
+
onChange: setSearch,
|
|
459
|
+
placeholder: "Buscar por n\xFAmero, fornecedor, CNPJ\u2026",
|
|
460
|
+
"data-testid": "inbound-notes-search"
|
|
461
|
+
}
|
|
462
|
+
) }) }),
|
|
463
|
+
/* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsx(
|
|
464
|
+
DataTable,
|
|
465
|
+
{
|
|
466
|
+
variant: "bare",
|
|
467
|
+
columns,
|
|
468
|
+
data: loading ? [] : pageRows,
|
|
469
|
+
loading,
|
|
470
|
+
getRowId: (note) => note.id,
|
|
471
|
+
onRowClick: (note) => onOpenNote(note.id),
|
|
472
|
+
emptyMessage,
|
|
473
|
+
mobileLayout: "cards"
|
|
474
|
+
}
|
|
475
|
+
) }),
|
|
476
|
+
!loading && shown.length > 0 && /* @__PURE__ */ jsx(ChevronPager, { page, pageSize: PAGE_SIZE, total: shown.length, onPageChange: setPage, noun: "notas" })
|
|
477
|
+
] })
|
|
478
|
+
] });
|
|
479
|
+
}
|
|
480
|
+
var PAGE_SIZE = 50;
|
|
481
|
+
function ActionIcon({ label, children }) {
|
|
482
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
483
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children }),
|
|
484
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: label })
|
|
485
|
+
] });
|
|
486
|
+
}
|
|
487
|
+
function ReconcileBadge({ done, total }) {
|
|
488
|
+
const full = total > 0 && done === total;
|
|
489
|
+
return /* @__PURE__ */ jsxs(
|
|
490
|
+
"span",
|
|
491
|
+
{
|
|
492
|
+
className: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium tabular-nums ${full ? "bg-success-soft text-success-soft-foreground" : "bg-muted text-muted-foreground"}`,
|
|
493
|
+
children: [
|
|
494
|
+
done,
|
|
495
|
+
"/",
|
|
496
|
+
total
|
|
497
|
+
]
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
function StockBadge({ stockedAt, status }) {
|
|
502
|
+
if (status === "rejected") {
|
|
503
|
+
return /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground line-through", children: "Recusada" });
|
|
504
|
+
}
|
|
505
|
+
return stockedAt ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-full bg-success-soft px-2 py-0.5 text-[11px] font-medium text-success-soft-foreground", children: [
|
|
506
|
+
/* @__PURE__ */ jsx(FileDown, { className: "mr-1 h-3 w-3", "aria-hidden": true }),
|
|
507
|
+
"Estocado"
|
|
508
|
+
] }) : /* @__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" });
|
|
509
|
+
}
|
|
510
|
+
var ROUTE_PREFIX = "/inbound-notes";
|
|
511
|
+
function viewFromLocation() {
|
|
512
|
+
if (typeof window === "undefined") return { kind: "list" };
|
|
513
|
+
const hash = currentAppPath();
|
|
514
|
+
const note = hash.match(/\/inbound-notes\/note\/([0-9a-f-]{36})/i);
|
|
515
|
+
if (note) return { kind: "note", id: note[1] };
|
|
516
|
+
return { kind: "list" };
|
|
517
|
+
}
|
|
518
|
+
var pathOf = (view) => view.kind === "note" ? `${ROUTE_PREFIX}/note/${view.id}` : ROUTE_PREFIX;
|
|
519
|
+
var STACK_MARK = { inboundNotes: true };
|
|
520
|
+
function InboundNotesPage({ payableRoute = (id) => `/financial/payables/${id}` } = {}) {
|
|
521
|
+
const [view, setView] = useState(viewFromLocation);
|
|
522
|
+
const go = useCallback((next) => {
|
|
523
|
+
setView(next);
|
|
524
|
+
if (typeof window === "undefined") return;
|
|
525
|
+
const path = pathOf(next);
|
|
526
|
+
if (currentAppPath() === path) return;
|
|
527
|
+
const empilhado = window.history.state?.inboundNotes;
|
|
528
|
+
if (next.kind !== "note" && empilhado) {
|
|
529
|
+
window.history.back();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (next.kind === "list") window.history.replaceState(null, "", appHref(path));
|
|
533
|
+
else window.history.pushState(STACK_MARK, "", appHref(path));
|
|
534
|
+
}, []);
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
if (typeof window === "undefined") return;
|
|
537
|
+
const onPop = () => {
|
|
538
|
+
const next = viewFromLocation();
|
|
539
|
+
setView(next);
|
|
540
|
+
};
|
|
541
|
+
window.addEventListener("popstate", onPop);
|
|
542
|
+
const off = onAppPathChange(onPop);
|
|
543
|
+
return () => {
|
|
544
|
+
window.removeEventListener("popstate", onPop);
|
|
545
|
+
off();
|
|
546
|
+
};
|
|
547
|
+
}, []);
|
|
548
|
+
const fecharNota = useCallback(() => go({ kind: "list" }), [go]);
|
|
549
|
+
useEffect(() => {
|
|
550
|
+
if (typeof window === "undefined") return;
|
|
551
|
+
const op = currentAppPath().match(/[?&]operation=([0-9a-f-]{36})/i)?.[1];
|
|
552
|
+
if (!op) return;
|
|
553
|
+
let vivo = true;
|
|
554
|
+
findInboxNoteByOperation(op).then((id) => {
|
|
555
|
+
if (vivo && id) go({ kind: "note", id });
|
|
556
|
+
}).catch(() => {
|
|
557
|
+
});
|
|
558
|
+
return () => {
|
|
559
|
+
vivo = false;
|
|
560
|
+
};
|
|
561
|
+
}, [go]);
|
|
562
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
563
|
+
/* @__PURE__ */ jsx(InboundNotesView, { onOpenNote: (id) => go({ kind: "note", id }) }),
|
|
564
|
+
/* @__PURE__ */ jsx(Modal, { open: view.kind === "note", onOpenChange: (open) => {
|
|
565
|
+
if (!open) fecharNota();
|
|
566
|
+
}, children: view.kind === "note" && // `noPadding`: a conciliação desenha o próprio cabeçalho e o próprio
|
|
567
|
+
// corpo — o título e o «Sugerir com IA» ficam fora da área que rola, e o
|
|
568
|
+
// rodapé «Aceitar» precisa encostar nas bordas do modal.
|
|
569
|
+
/* @__PURE__ */ jsx(ModalContent, { size: "5xl", noPadding: true, className: "sm:max-h-[90vh]", children: /* @__PURE__ */ jsx(
|
|
570
|
+
NoteReconcileView,
|
|
571
|
+
{
|
|
572
|
+
chrome: "modal",
|
|
573
|
+
noteId: view.id,
|
|
574
|
+
onBack: fecharNota,
|
|
575
|
+
onOpenInvoice: payableRoute ? (invoiceId) => {
|
|
576
|
+
navigateApp(payableRoute(invoiceId));
|
|
577
|
+
} : void 0
|
|
578
|
+
}
|
|
579
|
+
) }) })
|
|
580
|
+
] });
|
|
581
|
+
}
|
|
582
|
+
function makeInboundNotesRoute(options = {}) {
|
|
583
|
+
return function InboundNotesRoute() {
|
|
584
|
+
return /* @__PURE__ */ jsx(InboundNotesPage, { ...options });
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export { InboundNotesPage, InboundNotesView, makeInboundNotesRoute };
|
|
589
|
+
//# sourceMappingURL=chunk-OGXYHL3Z.js.map
|
|
590
|
+
//# sourceMappingURL=chunk-OGXYHL3Z.js.map
|