@fayz-ai/plugin-fiscal-br 0.10.3 → 0.12.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,54 +1,439 @@
1
1
  import { packagingCount } from './chunk-5LI2FDOM.js';
2
- import { recordRailFields, RECORD_GROUP, activeFacet, describeError, getSupabaseClientOptional, formatCurrency, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus, maskCpfCnpj, getActiveTenantId as getActiveTenantId$1 } from '@fayz-ai/core';
3
- import React6, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
4
- import { ModalHeader, ModalTitle, ModalBody, Skeleton, AiActionButton, Dropdown, DropdownTrigger, Button, DropdownContent, DropdownItem, Hint, Card, InlineLink, SearchSelect, Input, EmBreve, DatePicker, ModalFooter, SubpageHeader, Modal, ModalContent, ModalDescription, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, useFilterDraft, RowActions, PageHeaderActions, FilterPanel, FilterPanelField, SearchField, TooltipProvider, DataTable, ChevronPager, Tooltip, TooltipTrigger, TooltipContent, Badge, SegmentedControl, ComingSoon, toast, CardHeader, CardTitle, CardContent, CardDescription, Switch } from '@fayz-ai/ui';
2
+ import { createConnectionStore, recordRailFields, RECORD_GROUP, activeFacet, describeError, getSupabaseClientOptional, formatCurrency, maskCpfCnpj, connectionStatus, definePlugin, getActiveTenantId as getActiveTenantId$1 } from '@fayz-ai/core';
3
+ import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
4
+ import { ModalHeader, ModalTitle, ModalBody, Skeleton, AiActionButton, Dropdown, DropdownTrigger, Button, DropdownContent, DropdownItem, Hint, Card, InlineLink, SearchSelect, Input, EmBreve, DatePicker, ModalFooter, SubpageHeader, Modal, ModalContent, ModalDescription, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, CardContent, CardHeader, CardTitle, CardDescription, Badge, Switch, useFilterDraft, RowActions, PageHeaderActions, FilterPanel, FilterPanelField, SearchField, TooltipProvider, DataTable, ChevronPager, Tooltip, TooltipTrigger, TooltipContent, SegmentedControl, toast, Textarea, ComingSoon } from '@fayz-ai/ui';
5
5
  export { ComingSoon, EmBreve } from '@fayz-ai/ui';
6
- import { registerDocumentOpener, PersonLink, getActiveTenantId, useUnitFilter, PageActions, adminNavigateTo, usePermissionOptional, UNIT_DETAIL_ZONES } from '@fayz-ai/admin';
7
- import { Loader2, Download, ChevronDown, FileCode2, FileText, RefreshCw, UserPlus, Gift, AlertTriangle, ChevronRight, Check, Trash2, Plus, KeyRound, Image, UploadCloud, Paperclip, ScanLine, Eye, ListChecks, FileDown, CheckCircle2, FileCheck2, XCircle, Inbox, X, Info, ShieldCheck } from 'lucide-react';
6
+ import { Loader2, Download, ChevronDown, FileCode2, FileText, RefreshCw, UserPlus, Gift, AlertTriangle, ChevronRight, Check, Trash2, Plus, KeyRound, Image, UploadCloud, Paperclip, ScanLine, Eye, X, ShieldCheck, ListChecks, FileDown, CheckCircle2, FileCheck2, XCircle, Inbox, Info } from 'lucide-react';
7
+ import { registerDocumentOpener, PersonLink, getActiveTenantId, useUnitFilter, PageActions, adminNavigateTo, usePermissionOptional, OrderDetailBlock, UNIT_DETAIL_ZONES, ORDER_DETAIL_ZONES, serviceBaseUrl } from '@fayz-ai/admin';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
9
  import { FINANCIAL_INVOICE_ZONES } from '@fayz-ai/financial/views/zones';
10
10
 
11
- var PLUGNOTAS_CONNECTOR_ID = "plugnotas";
12
- var PLUGNOTAS_MARK = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%230F766E'/%3E%3Cpath d='M10 7h12v18l-2-1.5-2 1.5-2-1.5-2 1.5-2-1.5-2 1.5V7Z' fill='white'/%3E%3Cpath d='M13 12h6M13 16h6M13 20h4' stroke='%230F766E' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E";
13
- var PLUGNOTAS_FIELDS = [
14
- {
15
- key: "emitterCnpj",
16
- label: "CNPJ do emitente",
17
- type: "text",
18
- placeholder: "00.000.000/0000-00"
11
+ var EmitterError = class extends Error {
12
+ constructor(status, code, message, gates = [], field = null) {
13
+ super(message);
14
+ this.status = status;
15
+ this.code = code;
16
+ this.gates = gates;
17
+ this.field = field;
18
+ this.name = "EmitterError";
19
+ }
20
+ };
21
+ async function send(method, path, body) {
22
+ const base = serviceBaseUrl();
23
+ const supabase = getSupabaseClientOptional();
24
+ if (!base || !supabase) throw new EmitterError(503, "no_client", "the integration service is not configured");
25
+ const { data } = await supabase.auth.getSession();
26
+ const token = data?.session?.access_token;
27
+ if (!token) throw new EmitterError(401, "unauthorized", "sign in again");
28
+ const tenant = getActiveTenantId$1();
29
+ try {
30
+ return await fetch(`${base}${path}`, {
31
+ method,
32
+ headers: {
33
+ authorization: `Bearer ${token}`,
34
+ ...tenant ? { "x-fayz-tenant": tenant } : {},
35
+ ...body === void 0 ? {} : { "content-type": "application/json" }
36
+ },
37
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
38
+ });
39
+ } catch (error) {
40
+ throw new EmitterError(503, "unavailable", error instanceof Error ? error.message : String(error));
41
+ }
42
+ }
43
+ async function refusal(response) {
44
+ const text = await response.text();
45
+ let payload = null;
46
+ try {
47
+ payload = text ? JSON.parse(text) : null;
48
+ } catch {
49
+ }
50
+ return new EmitterError(
51
+ response.status,
52
+ payload?.error ?? "unavailable",
53
+ payload?.message ?? text.slice(0, 200),
54
+ Array.isArray(payload?.gates) ? payload.gates : [],
55
+ typeof payload?.field === "string" ? payload.field : null
56
+ );
57
+ }
58
+ async function request(method, path, body) {
59
+ const response = await send(method, path, body);
60
+ if (!response.ok) throw await refusal(response);
61
+ const text = await response.text();
62
+ return text ? JSON.parse(text) : null;
63
+ }
64
+ async function requestFile(path) {
65
+ const response = await send("GET", path);
66
+ if (!response.ok) throw await refusal(response);
67
+ return response.blob();
68
+ }
69
+
70
+ // src/lib/emitters.ts
71
+ var SENTENCES = {
72
+ no_client: "O servi\xE7o de integra\xE7\xF5es n\xE3o est\xE1 configurado neste app.",
73
+ not_configured: "A emiss\xE3o fiscal ainda n\xE3o est\xE1 ligada no servidor.",
74
+ unauthorized: "Sua sess\xE3o expirou. Entre de novo.",
75
+ not_allowed: "Seu acesso n\xE3o permite alterar a configura\xE7\xE3o fiscal.",
76
+ unavailable: "N\xE3o foi poss\xEDvel falar com o servi\xE7o fiscal. Tente de novo em instantes.",
77
+ unknown_emitter: "Esta empresa n\xE3o foi encontrada nesta conta.",
78
+ cnpj_elsewhere: "Este CNPJ j\xE1 est\xE1 configurado para emiss\xE3o em outra conta.",
79
+ observed: "Esta empresa j\xE1 emite pelo sistema anterior. Ela fica s\xF3 para consulta aqui at\xE9 a migra\xE7\xE3o.",
80
+ not_ready: "Ainda falta preencher o que o cadastro na PlugNotas exige.",
81
+ no_cnpj: "A empresa n\xE3o tem um CNPJ de 14 d\xEDgitos.",
82
+ wrong_password: "A senha n\xE3o abre este certificado. Confira a senha do arquivo .pfx.",
83
+ not_a_certificate: "Este arquivo n\xE3o \xE9 um certificado A1 (.pfx ou .p12).",
84
+ no_certificate: "O arquivo n\xE3o traz nenhum certificado.",
85
+ certificate_of_another_cnpj: "Este certificado \xE9 de outro CNPJ.",
86
+ certificate_expired: "Este certificado est\xE1 vencido.",
87
+ not_base64: "N\xE3o foi poss\xEDvel ler o arquivo do certificado.",
88
+ invalid_setting: "Um dos valores n\xE3o \xE9 aceito.",
89
+ plugnotas_refused: "A PlugNotas recusou o envio.",
90
+ admin_only: "S\xF3 o suporte pode mudar isso."
91
+ };
92
+ var FIELD_SENTENCES = {
93
+ "nfce.cscId": "O ID do CSC tem s\xF3 n\xFAmeros.",
94
+ "nfce.series": "A s\xE9rie vai de 1 a 889.",
95
+ "nfe.series": "A s\xE9rie vai de 1 a 889.",
96
+ "nfce.nextNumber": "O pr\xF3ximo n\xFAmero precisa ser 1 ou maior.",
97
+ "nfe.nextNumber": "O pr\xF3ximo n\xFAmero precisa ser 1 ou maior.",
98
+ taxRegimeCode: "Escolha o regime tribut\xE1rio.",
99
+ specialTaxRegimeCode: "O regime especial vai de 0 a 11."
100
+ };
101
+ function emitterErrorSentence(error) {
102
+ if (error instanceof EmitterError) {
103
+ if (error.field && FIELD_SENTENCES[error.field]) return FIELD_SENTENCES[error.field];
104
+ return SENTENCES[error.code] ?? error.message;
19
105
  }
106
+ return error instanceof Error ? error.message : String(error);
107
+ }
108
+ var GATE_LABELS = {
109
+ cnpj: "CNPJ da empresa",
110
+ legal_name: "Raz\xE3o social",
111
+ tax_regime: "Regime tribut\xE1rio",
112
+ email: "E-mail fiscal",
113
+ state_registration: "Inscri\xE7\xE3o estadual (ou ISENTO)",
114
+ address: "Endere\xE7o fiscal completo (rua, cidade, UF e CEP)",
115
+ ibge_city_code: "C\xF3digo IBGE da cidade",
116
+ certificate: "Certificado A1 enviado",
117
+ certificate_holder: "Certificado do mesmo CNPJ",
118
+ certificate_valid: "Certificado dentro da validade",
119
+ nfce_enabled: "NFC-e ligada",
120
+ nfce_series: "S\xE9rie da NFC-e",
121
+ csc: "CSC (ID e c\xF3digo) da NFC-e",
122
+ registered: "Empresa cadastrada na PlugNotas"
123
+ };
124
+ var one = (legalEntityId, rest = "") => `/v1/fiscal/emitters/${encodeURIComponent(legalEntityId)}${rest}`;
125
+ async function base64Of(file) {
126
+ const bytes = new Uint8Array(await file.arrayBuffer());
127
+ let binary = "";
128
+ for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
129
+ return btoa(binary);
130
+ }
131
+ var emitters = {
132
+ list: async () => (await request("GET", "/v1/fiscal/emitters")).emitters,
133
+ /** Also asks PlugNotas what it holds, and lists what issuing still needs. */
134
+ read: (legalEntityId) => request("GET", one(legalEntityId)),
135
+ save: (legalEntityId, settings) => request("PATCH", one(legalEntityId), settings),
136
+ certificate: async (legalEntityId, file, password) => request("PUT", one(legalEntityId, "/certificate"), { pfxBase64: await base64Of(file), password, fileName: file.name }),
137
+ register: (legalEntityId) => request("PUT", one(legalEntityId, "/registration"))
138
+ };
139
+ var TAX_REGIMES = [
140
+ { value: 1, label: "Simples Nacional" },
141
+ { value: 2, label: "Simples Nacional \u2014 excesso" },
142
+ // Presumed and real profit are both "normal" in PlugNotas' words; the label
143
+ // says so for the store to find its own (V1).
144
+ { value: 3, label: "Regime Normal (Lucro Presumido / Lucro Real)" }
20
145
  ];
21
- var PLUGNOTAS_SETTINGS = [{
22
- key: "environment",
23
- label: "Ambiente fiscal",
24
- description: "Sandbox simula o emissor. Homologa\xE7\xE3o envia para a SEFAZ sem valor fiscal.",
25
- type: "select",
26
- options: [
27
- { value: "sandbox", label: "Sandbox simulado" },
28
- { value: "homologation", label: "Homologa\xE7\xE3o SEFAZ" }
29
- ],
30
- requiredToOperate: true,
31
- defaultValue: "sandbox"
32
- }];
33
- var INTERNAL_SETTINGS = [
34
- "certificateReady",
35
- "cscReady",
36
- "providerEmitterId",
37
- "certificateExpiresAt",
38
- "certificateFingerprint",
39
- // Non-secret operational data written by the host's server-side setup flow.
40
- // Declaring these keys keeps the shared connection store authoritative while
41
- // credentials (API key, A1 bytes/password, CSC) remain outside public JSON.
42
- "stateRegistration",
43
- "corporateName",
44
- "tradeName",
45
- "email",
46
- "taxRegime",
47
- "address",
48
- "series",
49
- "paymentMethod"
146
+ var SPECIAL_TAX_REGIMES = [
147
+ { value: 0, label: "Sem regime especial" },
148
+ { value: 1, label: "Microempresa municipal" },
149
+ { value: 2, label: "Estimativa" },
150
+ { value: 3, label: "Sociedade de profissionais" },
151
+ { value: 4, label: "Cooperativa" },
152
+ { value: 5, label: "MEI" },
153
+ { value: 6, label: "ME ou EPP" },
154
+ { value: 7, label: "Lucro Real" },
155
+ { value: 8, label: "Lucro Presumido" },
156
+ { value: 9, label: "Tributa\xE7\xE3o normal" },
157
+ { value: 10, label: "Simples Nacional com excesso do sublimite" },
158
+ { value: 11, label: "Empresa de responsabilidade limitada" }
50
159
  ];
51
- var DAY_MS = 24 * 60 * 60 * 1e3;
160
+ function EmittersPanel() {
161
+ const [list, setList] = React.useState(null);
162
+ const [error, setError] = React.useState(null);
163
+ const [openId, setOpenId] = React.useState(null);
164
+ const load = React.useCallback(() => {
165
+ setError(null);
166
+ emitters.list().then((rows) => {
167
+ setList(rows);
168
+ if (rows.length === 1) setOpenId(rows[0].legalEntityId);
169
+ }).catch((e) => setError(emitterErrorSentence(e)));
170
+ }, []);
171
+ React.useEffect(load, [load]);
172
+ if (error) {
173
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col items-start gap-3 py-6", children: [
174
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: error }),
175
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: load, children: "Tentar de novo" })
176
+ ] }) });
177
+ }
178
+ if (!list) return /* @__PURE__ */ jsx(Loading, {});
179
+ if (!list.length) {
180
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "py-6 text-sm text-muted-foreground", children: "Nenhuma unidade desta conta tem CNPJ ainda. Preencha a raz\xE3o social e o CNPJ em Configura\xE7\xF5es \u2192 Unidades para configurar a emiss\xE3o." }) });
181
+ }
182
+ return /* @__PURE__ */ jsx("div", { className: "space-y-3", children: list.map((e) => /* @__PURE__ */ jsx(
183
+ EmitterCard,
184
+ {
185
+ summary: e,
186
+ open: openId === e.legalEntityId,
187
+ onToggle: () => setOpenId((id) => id === e.legalEntityId ? null : e.legalEntityId)
188
+ },
189
+ e.legalEntityId
190
+ )) });
191
+ }
192
+ function Loading() {
193
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "space-y-3 py-6", children: [
194
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-48" }),
195
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" })
196
+ ] }) });
197
+ }
198
+ function EmitterCard({ summary, open, onToggle }) {
199
+ const [emitter, setEmitter] = React.useState(null);
200
+ const [error, setError] = React.useState(null);
201
+ const read = React.useCallback(() => {
202
+ setError(null);
203
+ emitters.read(summary.legalEntityId).then(setEmitter).catch((e) => setError(emitterErrorSentence(e)));
204
+ }, [summary.legalEntityId]);
205
+ React.useEffect(() => {
206
+ if (open && !emitter) read();
207
+ }, [open, emitter, read]);
208
+ const shown = emitter ?? summary;
209
+ const observed = shown.mode === "observe";
210
+ return /* @__PURE__ */ jsxs(Card, { children: [
211
+ /* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-start justify-between gap-3 space-y-0", children: [
212
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
213
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: shown.tradeName || shown.legalName || "Empresa sem nome" }),
214
+ /* @__PURE__ */ jsx(CardDescription, { children: [shown.legalName, shown.cnpj ? `CNPJ ${maskCpfCnpj(shown.cnpj)}` : null].filter(Boolean).join(" \xB7 ") })
215
+ ] }),
216
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
217
+ /* @__PURE__ */ jsx(StatusBadge, { emitter: shown }),
218
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onToggle, children: open ? "Fechar" : "Configurar" })
219
+ ] })
220
+ ] }),
221
+ open && /* @__PURE__ */ jsxs(CardContent, { className: "space-y-5", children: [
222
+ error && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 rounded-md border border-destructive/40 p-3 text-sm", children: [
223
+ /* @__PURE__ */ jsx("span", { children: error }),
224
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: read, children: "Tentar de novo" })
225
+ ] }),
226
+ !emitter && !error && /* @__PURE__ */ jsx(Skeleton, { className: "h-24 w-full" }),
227
+ emitter && observed && /* @__PURE__ */ jsx(Observed, { emitter }),
228
+ emitter && !observed && /* @__PURE__ */ jsx(Setup, { emitter, onChange: setEmitter })
229
+ ] })
230
+ ] });
231
+ }
232
+ function StatusBadge({ emitter }) {
233
+ if (emitter.mode === "observe") return /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: "gap-1 text-muted-foreground", children: [
234
+ /* @__PURE__ */ jsx(Eye, { className: "h-3 w-3" }),
235
+ "Emite pelo sistema anterior"
236
+ ] });
237
+ if (emitter.ready === true) return /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400", children: "Pronto para emitir" });
238
+ if (emitter.certificate?.warning === "expired") return /* @__PURE__ */ jsx(Badge, { variant: "destructive", children: "Certificado vencido" });
239
+ if (emitter.ready === false) return /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-muted-foreground", children: "Configura\xE7\xE3o incompleta" });
240
+ return null;
241
+ }
242
+ function Observed({ emitter }) {
243
+ const p = emitter.provider;
244
+ const cert = p?.certificate;
245
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3 text-sm", children: [
246
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Esta empresa j\xE1 est\xE1 cadastrada na PlugNotas e emite pelo sistema anterior. Aqui ela fica s\xF3 para consulta, para que os dois sistemas n\xE3o mudem o mesmo cadastro. A troca \xE9 feita pelo suporte, quando a emiss\xE3o passar para c\xE1." }),
247
+ /* @__PURE__ */ jsxs("dl", { className: "grid gap-x-6 gap-y-1", style: { gridTemplateColumns: "auto 1fr" }, children: [
248
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "NFC-e" }),
249
+ /* @__PURE__ */ jsx("dd", { children: p?.nfce?.active ? `Ligada \xB7 ${p.nfce.production ? "Produ\xE7\xE3o" : "Homologa\xE7\xE3o"}${p.nfce.series.length ? ` \xB7 s\xE9rie ${p.nfce.series.join(", ")}` : ""}` : "Desligada" }),
250
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Notas recebidas (DF-e)" }),
251
+ /* @__PURE__ */ jsx("dd", { children: p?.nfe?.dfe ? "Ligado" : "Desligado" }),
252
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Certificado A1" }),
253
+ /* @__PURE__ */ jsx("dd", { children: cert ? `${cert.expiresAt ? `v\xE1lido at\xE9 ${new Date(cert.expiresAt).toLocaleDateString("pt-BR")}` : "enviado"}` : "Nenhum" })
254
+ ] })
255
+ ] });
256
+ }
257
+ function Setup({ emitter, onChange }) {
258
+ const [draft, setDraft] = React.useState(() => draftOf(emitter));
259
+ const [cscToken, setCscToken] = React.useState("");
260
+ const [saving, setSaving] = React.useState(false);
261
+ const [registering, setRegistering] = React.useState(false);
262
+ const [missing, setMissing] = React.useState(null);
263
+ React.useEffect(() => setDraft(draftOf(emitter)), [emitter]);
264
+ const set = (patch) => setDraft((d) => ({ ...d, ...patch }));
265
+ const gates = missing ?? emitter.gates ?? [];
266
+ const save = async () => {
267
+ const input = {
268
+ taxRegimeCode: draft.taxRegimeCode,
269
+ specialTaxRegimeCode: draft.specialTaxRegimeCode,
270
+ nfce: {
271
+ enabled: draft.nfceEnabled,
272
+ production: draft.nfceProduction,
273
+ series: draft.nfceSeries === "" ? null : Number(draft.nfceSeries),
274
+ nextNumber: draft.nfceNextNumber === "" ? null : Number(draft.nfceNextNumber),
275
+ cscId: draft.cscId.trim() === "" ? null : draft.cscId.trim(),
276
+ ...cscToken ? { cscToken } : {}
277
+ }
278
+ };
279
+ setSaving(true);
280
+ try {
281
+ await emitters.save(emitter.legalEntityId, input);
282
+ setCscToken("");
283
+ const fresh = await emitters.read(emitter.legalEntityId);
284
+ onChange(fresh);
285
+ setMissing(null);
286
+ toast.success("Configura\xE7\xE3o fiscal salva");
287
+ return fresh;
288
+ } catch (e) {
289
+ toast.error("N\xE3o foi poss\xEDvel salvar", { description: emitterErrorSentence(e) });
290
+ return null;
291
+ } finally {
292
+ setSaving(false);
293
+ }
294
+ };
295
+ const register = async () => {
296
+ const saved = await save();
297
+ if (!saved) return;
298
+ setRegistering(true);
299
+ try {
300
+ const after = await emitters.register(emitter.legalEntityId);
301
+ onChange(after);
302
+ setMissing(null);
303
+ toast.success(after.provider?.registered ? "Empresa cadastrada na PlugNotas" : "Cadastro enviado");
304
+ } catch (e) {
305
+ if (e instanceof EmitterError && e.gates.length) setMissing(e.gates);
306
+ toast.error("N\xE3o foi poss\xEDvel cadastrar na PlugNotas", { description: emitterErrorSentence(e) });
307
+ } finally {
308
+ setRegistering(false);
309
+ }
310
+ };
311
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
312
+ /* @__PURE__ */ jsxs("section", { className: "space-y-4 rounded-md border p-4", children: [
313
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Empresa" }),
314
+ /* @__PURE__ */ jsx("div", { className: "fz-container", children: /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
315
+ /* @__PURE__ */ jsx(Field, { label: "Regime tribut\xE1rio", children: /* @__PURE__ */ jsxs(Select, { value: draft.taxRegimeCode ? String(draft.taxRegimeCode) : void 0, onValueChange: (v) => set({ taxRegimeCode: Number(v) }), children: [
316
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Selecione" }) }),
317
+ /* @__PURE__ */ jsx(SelectContent, { children: TAX_REGIMES.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: String(o.value), children: o.label }, o.value)) })
318
+ ] }) }),
319
+ /* @__PURE__ */ jsx(Field, { label: "Regime tribut\xE1rio especial", children: /* @__PURE__ */ jsxs(Select, { value: String(draft.specialTaxRegimeCode), onValueChange: (v) => set({ specialTaxRegimeCode: Number(v) }), children: [
320
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
321
+ /* @__PURE__ */ jsx(SelectContent, { children: SPECIAL_TAX_REGIMES.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: String(o.value), children: o.label }, o.value)) })
322
+ ] }) })
323
+ ] }) }),
324
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Raz\xE3o social, CNPJ, inscri\xE7\xF5es e endere\xE7o v\xEAm de Configura\xE7\xF5es \u2192 Unidades \u2192 Dados da empresa." })
325
+ ] }),
326
+ /* @__PURE__ */ jsx(Certificate, { emitter, onChange }),
327
+ /* @__PURE__ */ jsxs("section", { className: "space-y-4 rounded-md border p-4", children: [
328
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
329
+ /* @__PURE__ */ jsxs("div", { children: [
330
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "NFC-e \u2014 Cupom do Consumidor" }),
331
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Vendas no balc\xE3o e na comanda" })
332
+ ] }),
333
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
334
+ /* @__PURE__ */ jsxs(Select, { value: draft.nfceProduction ? "prod" : "hom", onValueChange: (v) => set({ nfceProduction: v === "prod" }), disabled: !draft.nfceEnabled, children: [
335
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-36", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
336
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
337
+ /* @__PURE__ */ jsx(SelectItem, { value: "hom", children: "Homologa\xE7\xE3o" }),
338
+ /* @__PURE__ */ jsx(SelectItem, { value: "prod", children: "Produ\xE7\xE3o" })
339
+ ] })
340
+ ] }),
341
+ /* @__PURE__ */ jsx(Switch, { checked: draft.nfceEnabled, onCheckedChange: (v) => set({ nfceEnabled: v }) })
342
+ ] })
343
+ ] }),
344
+ draft.nfceEnabled && /* @__PURE__ */ jsx("div", { className: "fz-container space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
345
+ /* @__PURE__ */ jsx(Field, { label: "S\xE9rie", hint: "Use uma s\xE9rie que nenhum outro sistema use para este CNPJ.", children: /* @__PURE__ */ jsx(Input, { inputMode: "numeric", value: draft.nfceSeries, onChange: (e) => set({ nfceSeries: e.target.value.replace(/\D/g, "") }) }) }),
346
+ /* @__PURE__ */ jsx(Field, { label: "Pr\xF3ximo n\xFAmero", hint: "S\xF3 para uma s\xE9rie nova: de onde a numera\xE7\xE3o come\xE7a.", children: /* @__PURE__ */ jsx(Input, { inputMode: "numeric", value: draft.nfceNextNumber, onChange: (e) => set({ nfceNextNumber: e.target.value.replace(/\D/g, "") }) }) }),
347
+ /* @__PURE__ */ jsx(Field, { label: "CSC \u2014 ID", children: /* @__PURE__ */ jsx(Input, { inputMode: "numeric", value: draft.cscId, onChange: (e) => set({ cscId: e.target.value }) }) }),
348
+ /* @__PURE__ */ jsx(Field, { label: "CSC \u2014 C\xF3digo", hint: "O CSC (ID num\xE9rico e c\xF3digo) \xE9 gerado no portal da SEFAZ do estado. N\xE3o \xE9 o login de nenhum sistema.", children: /* @__PURE__ */ jsx(
349
+ Input,
350
+ {
351
+ type: "password",
352
+ autoComplete: "new-password",
353
+ value: cscToken,
354
+ placeholder: emitter.nfce.cscTokenSet ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (configurado, digite para trocar)" : "",
355
+ onChange: (e) => setCscToken(e.target.value)
356
+ }
357
+ ) })
358
+ ] }) })
359
+ ] }),
360
+ gates.length > 0 && /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap gap-2", children: gates.map((g) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: g.ok ? "gap-1 border-emerald-500/40 text-emerald-700 dark:text-emerald-400" : "gap-1 text-muted-foreground", children: [
361
+ g.ok ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(X, { className: "h-3 w-3" }),
362
+ GATE_LABELS[g.id] ?? g.id
363
+ ] }) }, g.id)) }),
364
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-3", children: [
365
+ emitter.providerSyncedAt && /* @__PURE__ */ jsxs("p", { className: "mr-auto text-xs text-muted-foreground", children: [
366
+ "Cadastro na PlugNotas atualizado em ",
367
+ new Date(emitter.providerSyncedAt).toLocaleString("pt-BR")
368
+ ] }),
369
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void save(), disabled: saving || registering, children: saving && !registering ? "Salvando\u2026" : "Salvar" }),
370
+ /* @__PURE__ */ jsx(Button, { onClick: () => void register(), disabled: saving || registering, children: registering ? "Cadastrando\u2026" : emitter.provider?.registered ? "Salvar e atualizar na PlugNotas" : "Salvar e cadastrar na PlugNotas" })
371
+ ] })
372
+ ] });
373
+ }
374
+ function Certificate({ emitter, onChange }) {
375
+ const [file, setFile] = React.useState(null);
376
+ const [password, setPassword] = React.useState("");
377
+ const [sending, setSending] = React.useState(false);
378
+ const c = emitter.certificate;
379
+ const send2 = async () => {
380
+ if (!file) return;
381
+ setSending(true);
382
+ try {
383
+ await emitters.certificate(emitter.legalEntityId, file, password);
384
+ onChange(await emitters.read(emitter.legalEntityId));
385
+ setFile(null);
386
+ setPassword("");
387
+ toast.success("Certificado enviado");
388
+ } catch (e) {
389
+ toast.error("N\xE3o foi poss\xEDvel enviar o certificado", { description: emitterErrorSentence(e) });
390
+ } finally {
391
+ setSending(false);
392
+ }
393
+ };
394
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-4 rounded-md border p-4", children: [
395
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
396
+ /* @__PURE__ */ jsxs("div", { children: [
397
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-sm font-medium", children: [
398
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "h-4 w-4" }),
399
+ " Certificado digital A1"
400
+ ] }),
401
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: c ? [c.holder?.split(",")[0]?.replace(/^CN=/, ""), c.expiresAt ? `v\xE1lido at\xE9 ${new Date(c.expiresAt).toLocaleDateString("pt-BR")}` : null].filter(Boolean).join(" \xB7 ") : "Nenhum certificado enviado" })
402
+ ] }),
403
+ c && /* @__PURE__ */ jsx(Badge, { variant: c.warning === "expired" ? "destructive" : "secondary", children: c.warning === "expired" ? "Vencido" : c.warning === "expiring" ? "Vence em breve" : "Ativo" })
404
+ ] }),
405
+ /* @__PURE__ */ jsx("div", { className: "fz-container", children: /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
406
+ /* @__PURE__ */ jsx(Field, { label: "Arquivo (.pfx ou .p12)", children: /* @__PURE__ */ jsx(Input, { type: "file", accept: ".pfx,.p12,application/x-pkcs12", onChange: (e) => setFile(e.target.files?.[0] ?? null) }) }),
407
+ /* @__PURE__ */ jsx(Field, { label: "Senha do certificado", children: /* @__PURE__ */ jsx(Input, { type: "password", autoComplete: "new-password", value: password, onChange: (e) => setPassword(e.target.value) }) })
408
+ ] }) }),
409
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
410
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "O arquivo vai direto para a PlugNotas e n\xE3o fica guardado no nosso sistema." }),
411
+ /* @__PURE__ */ jsx(Button, { size: "sm", disabled: !file || !password || sending, onClick: () => void send2(), children: sending ? "Enviando\u2026" : c ? "Trocar certificado" : "Enviar certificado" })
412
+ ] })
413
+ ] });
414
+ }
415
+ function Field({ label, hint, children }) {
416
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
417
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: label }),
418
+ children,
419
+ hint && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hint })
420
+ ] });
421
+ }
422
+ function draftOf(e) {
423
+ return {
424
+ taxRegimeCode: e.taxRegimeCode,
425
+ specialTaxRegimeCode: e.specialTaxRegimeCode,
426
+ nfceEnabled: e.nfce.enabled,
427
+ nfceProduction: e.nfce.production,
428
+ nfceSeries: e.nfce.series ? String(e.nfce.series) : "",
429
+ nfceNextNumber: e.nfce.nextNumber ? String(e.nfce.nextNumber) : "",
430
+ cscId: e.nfce.cscId ?? ""
431
+ };
432
+ }
433
+
434
+ // src/connectors/plugnotas.ts
435
+ var PLUGNOTAS_CONNECTOR_ID = "plugnotas";
436
+ var PLUGNOTAS_MARK = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%230F766E'/%3E%3Cpath d='M10 7h12v18l-2-1.5-2 1.5-2-1.5-2 1.5-2-1.5-2 1.5V7Z' fill='white'/%3E%3Cpath d='M13 12h6M13 16h6M13 20h4' stroke='%230F766E' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E";
52
437
  function digits(value) {
53
438
  return value.replace(/\D/g, "");
54
439
  }
@@ -64,43 +449,8 @@ function isValidCnpj(value) {
64
449
  const second = digit(`${normalized.slice(0, 12)}${first}`, [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]);
65
450
  return normalized.endsWith(`${first}${second}`);
66
451
  }
67
- function environment(value) {
68
- if (value === void 0 || value === null || value === "" || value === "sandbox") return "sandbox";
69
- if (value === "homologation") return value;
70
- throw new Error("Produ\xE7\xE3o n\xE3o est\xE1 dispon\xEDvel neste conector preview.");
71
- }
72
- function store() {
73
- return createConnectionStore(PLUGNOTAS_CONNECTOR_ID, {
74
- fields: [...PLUGNOTAS_FIELDS],
75
- settings: [...PLUGNOTAS_SETTINGS],
76
- settingsKeys: INTERNAL_SETTINGS
77
- });
78
- }
79
- function deriveStatus(connection) {
80
- if (!connection?.active) return { connected: false };
81
- const readiness = settingsStatus([...PLUGNOTAS_SETTINGS], connection);
82
- const certificateReady = connection.settings.certificateReady === true;
83
- const cscReady = connection.settings.cscReady === true;
84
- const expiry = typeof connection.settings.certificateExpiresAt === "string" ? Date.parse(connection.settings.certificateExpiresAt) : Number.NaN;
85
- const daysUntilExpiry = Number.isFinite(expiry) ? Math.ceil((expiry - Date.now()) / DAY_MS) : null;
86
- const expired = daysUntilExpiry !== null && daysUntilExpiry <= 0;
87
- const expiringSoon = daysUntilExpiry !== null && daysUntilExpiry > 0 && daysUntilExpiry <= 30;
88
- const missingSettings = [.../* @__PURE__ */ new Set([
89
- ...readiness.missingSettings ?? [],
90
- ...!certificateReady || expired ? ["certificate"] : [],
91
- ...!cscReady ? ["csc"] : []
92
- ])];
93
- return {
94
- connected: true,
95
- needsSetup: missingSettings.length > 0,
96
- ...missingSettings.length ? { missingSettings } : {},
97
- detail: expired ? "Certificado A1 vencido" : !certificateReady ? "Envie o certificado A1 do emitente." : !cscReady ? "Confirme o CSC e o ID Token do emitente." : connection.settings.environment === "homologation" ? "Emitente pronto em homologa\xE7\xE3o" : "Emitente pronto no sandbox",
98
- ...expiringSoon ? { issues: [`O certificado A1 vence em ${daysUntilExpiry} dia${daysUntilExpiry === 1 ? "" : "s"}.`] } : {},
99
- plane: "sync"
100
- };
101
- }
102
- function createPlugNotasConnector(options = {}) {
103
- const connectionStore = store();
452
+ var store = createConnectionStore(PLUGNOTAS_CONNECTOR_ID, {});
453
+ function createPlugNotasConnector() {
104
454
  return {
105
455
  id: PLUGNOTAS_CONNECTOR_ID,
106
456
  // PlugBank and PlugNotas are different products from the same vendor. The
@@ -108,8 +458,8 @@ function createPlugNotasConnector(options = {}) {
108
458
  // other in the generated connector map.
109
459
  catalogSlug: "plugnotas",
110
460
  hostPluginId: "financial",
111
- name: "PlugNotas \xB7 NFC-e",
112
- description: "Emita NFC-e pela TecnoSpeed sem expor certificado, senha ou API key no navegador.",
461
+ name: "PlugNotas \xB7 Notas fiscais",
462
+ description: "Emita NFC-e pela TecnoSpeed. Certificado, CSC e cadastro da empresa ficam no servidor, nunca no navegador.",
113
463
  icon: "ReceiptText",
114
464
  logoUrl: PLUGNOTAS_MARK,
115
465
  authKind: "api-key",
@@ -117,76 +467,33 @@ function createPlugNotasConnector(options = {}) {
117
467
  direction: "outbound",
118
468
  planes: ["sync"],
119
469
  docsUrl: "https://docs.plugnotas.com.br/",
120
- fields: [...PLUGNOTAS_FIELDS],
121
- settings: [...PLUGNOTAS_SETTINGS],
470
+ fields: [],
122
471
  permissions: [{
123
472
  kind: "data",
124
473
  resource: "fiscal-document",
125
474
  actions: ["read", "write"],
126
- reason: "Enviar a NFC-e e reconciliar situa\xE7\xE3o, chave de acesso e artefatos no Financeiro.",
475
+ reason: "Cadastrar a empresa na PlugNotas e emitir as notas das vendas.",
127
476
  sensitive: true
128
477
  }],
129
- invokedAt: [{
130
- key: "financial.nfce.issue",
131
- label: "Quando um operador confirma a emiss\xE3o da NFC-e de um pedido conclu\xEDdo",
132
- plane: "sync",
133
- trigger: "manual",
134
- touches: ["fiscal-document"]
135
- }],
136
478
  howItSyncs: [
137
- "A emiss\xE3o cria primeiro um comando dur\xE1vel e s\xF3 depois chama o provedor.",
138
- "Webhook e recupera\xE7\xE3o consultiva convergem o mesmo comando, sem reenviar no escuro."
479
+ "Cada empresa (CNPJ) \xE9 configurada na aba Emitentes: regime, certificado A1, CSC e s\xE9rie da NFC-e.",
480
+ "O cadastro na PlugNotas \xE9 feito pelo nosso servidor, que confere tudo o que a nota exige antes de enviar.",
481
+ "Uma empresa que j\xE1 emite pelo sistema anterior aparece s\xF3 para consulta, para os dois sistemas n\xE3o mudarem o mesmo cadastro."
139
482
  ],
140
- ...connectionStatus(PLUGNOTAS_CONNECTOR_ID, deriveStatus, { store }),
141
- ...connectionSettings(connectionStore),
142
- async testConnection(values) {
143
- const emitterCnpj = digits(values.emitterCnpj ?? "");
144
- if (!isValidCnpj(emitterCnpj)) return { ok: false, message: "Informe um CNPJ v\xE1lido." };
145
- let selected;
146
- try {
147
- selected = environment(values.environment);
148
- } catch (error) {
149
- return { ok: false, message: error instanceof Error ? error.message : "Ambiente inv\xE1lido." };
150
- }
151
- return options.client?.testEmitter ? options.client.testEmitter({ emitterCnpj, environment: selected }) : { ok: true };
152
- },
153
- async saveConnection(values) {
154
- const emitterCnpj = digits(values.emitterCnpj ?? "");
155
- if (!isValidCnpj(emitterCnpj)) throw new Error("Informe um CNPJ v\xE1lido.");
156
- await connectionStore.save({
157
- active: true,
158
- displayName: `Emitente ${emitterCnpj.slice(-6)}`,
159
- providerAccountKey: emitterCnpj,
160
- settings: {
161
- emitterCnpj,
162
- environment: environment(values.environment),
163
- certificateReady: false,
164
- cscReady: false
165
- }
166
- });
483
+ ...connectionStatus(PLUGNOTAS_CONNECTOR_ID, (connection) => ({
484
+ connected: !!connection?.active,
485
+ detail: "Configure cada empresa na aba Emitentes.",
486
+ plane: "sync"
487
+ }), { store: () => store }),
488
+ saveConnection: async () => {
489
+ await store.save({ active: true });
167
490
  },
168
- ...options.client?.disconnectEmitter ? { disconnect: () => options.client.disconnectEmitter() } : {},
169
- disconnectNote: "As notas existentes permanecem no Financeiro; somente novas emiss\xF5es s\xE3o interrompidas.",
170
- ExtraPanel: options.SetupPanel
491
+ OperatePanel: EmittersPanel,
492
+ operateLabel: "Emitentes",
493
+ disconnect: () => store.disconnect(),
494
+ disconnectNote: "As notas j\xE1 emitidas continuam no Financeiro, e o cadastro das empresas na PlugNotas n\xE3o \xE9 apagado."
171
495
  };
172
496
  }
173
- async function markPlugNotasEmitterReady(input) {
174
- const emitterCnpj = digits(input.emitterCnpj);
175
- if (!isValidCnpj(emitterCnpj)) throw new Error("Informe um CNPJ v\xE1lido.");
176
- await store().save({
177
- active: true,
178
- providerAccountKey: emitterCnpj,
179
- settings: {
180
- emitterCnpj,
181
- environment: environment(input.environment),
182
- certificateReady: input.certificateReady,
183
- cscReady: input.cscReady,
184
- providerEmitterId: input.providerEmitterId,
185
- certificateExpiresAt: input.certificateExpiresAt,
186
- certificateFingerprint: input.certificateFingerprint
187
- }
188
- });
189
- }
190
497
 
191
498
  // src/migrations/index.ts
192
499
  var MIGRATION_000_BASELINE = `-- Fiscal Brasil / PlugNotas baseline.
@@ -4613,6 +4920,315 @@ BEGIN
4613
4920
  END
4614
4921
  $$;
4615
4922
  `;
4923
+ var MIGRATION_031_THE_SERVICE_ISSUES_THE_NOTE = `-- ============================================================================
4924
+ -- 031 \u2014 the integration service issues the order's note
4925
+ --
4926
+ -- control-sdk-backend ADR 0008, step 2: an operator clicks "Emitir NFC-e" on
4927
+ -- an order and the integration service builds the note with the V1's rules,
4928
+ -- sends it to PlugNotas and records what comes back. The note is the record
4929
+ -- 005 already defined (public.documents + plg_fiscalbr_documents, linked to
4930
+ -- the order by plg_fiscalbr_document_orders); these are the service's doors to
4931
+ -- it, one function per action, the tenant explicit, open only to
4932
+ -- \`fayz_integration\`.
4933
+ --
4934
+ -- fiscalbr_order_for_note_for_service what the note is made of
4935
+ -- fiscalbr_note_open_for_service the note, opened before the call
4936
+ -- fiscalbr_note_apply_for_service what PlugNotas answered
4937
+ -- fiscalbr_notes_for_service an order's notes
4938
+ -- fiscalbr_note_for_service one note
4939
+ -- fiscalbr_notes_to_follow_for_service notes still waiting on PlugNotas
4940
+ --
4941
+ -- A note is opened BEFORE PlugNotas is called, with the idIntegracao it will
4942
+ -- carry, so a call whose answer is lost is found again by that id instead of
4943
+ -- being sent twice (the V1's FISCAL_PROVIDER_RESULT_UNCERTAIN rule).
4944
+ -- ============================================================================
4945
+
4946
+ -- What the note of an order is made of: the order, its lines with their tax
4947
+ -- data, what was paid and how, who bought and where it goes. The service
4948
+ -- applies the rules; this only gathers.
4949
+ CREATE OR REPLACE FUNCTION public.fiscalbr_order_for_note_for_service(p_tenant uuid, p_order uuid)
4950
+ RETURNS jsonb
4951
+ LANGUAGE plpgsql STABLE SECURITY DEFINER
4952
+ SET search_path TO ''
4953
+ AS $$
4954
+ DECLARE
4955
+ v_checkout jsonb;
4956
+ BEGIN
4957
+ -- The shop is optional: a site order says how it was paid on its checkout,
4958
+ -- read only where the shop's table exists.
4959
+ IF to_regclass('public.plg_shop_checkout_sessions') IS NOT NULL THEN
4960
+ EXECUTE $q$
4961
+ SELECT jsonb_build_object(
4962
+ 'paymentMethod', s.payment_method, 'provider', s.provider, 'total', s.total, 'status', s.status,
4963
+ 'customerName', s.customer_name, 'customerDocument', s.customer_document,
4964
+ 'destinationState', s.shipping_address ->> 'state', 'destinationCity', s.shipping_address ->> 'city')
4965
+ FROM public.plg_shop_checkout_sessions s
4966
+ WHERE s.order_id = $1 AND s.tenant_id = $2 AND s.status = 'paid'
4967
+ ORDER BY s.updated_at DESC LIMIT 1 $q$
4968
+ INTO v_checkout USING p_order, p_tenant;
4969
+ END IF;
4970
+
4971
+ RETURN (SELECT jsonb_build_object(
4972
+ 'orderId', o.id,
4973
+ 'tenantId', o.tenant_id,
4974
+ 'unitId', o.unit_id,
4975
+ 'kind', o.kind,
4976
+ 'status', o.status,
4977
+ 'channel', o.channel,
4978
+ 'createdAt', o.created_at,
4979
+ 'subtotal', o.subtotal,
4980
+ 'discount', o.discount,
4981
+ 'shipping', o.shipping,
4982
+ 'total', o.total,
4983
+ 'legalEntityId', u.company_legal_entity_id,
4984
+ 'items', coalesce((
4985
+ SELECT jsonb_agg(jsonb_build_object(
4986
+ 'itemId', i.id,
4987
+ 'productId', i.product_id,
4988
+ 'code', coalesce(nullif(p.sku, ''), i.product_id::text, i.id::text),
4989
+ 'name', i.name,
4990
+ 'quantity', i.quantity,
4991
+ 'unitPrice', i.unit_price,
4992
+ 'discount', i.discount,
4993
+ 'total', i.total,
4994
+ 'cancelled', i.cancelled_at IS NOT NULL,
4995
+ -- The product's own value wins, field by field, over its group's (V1).
4996
+ 'tax', CASE WHEN t.product_id IS NULL AND g.id IS NULL THEN NULL ELSE jsonb_build_object(
4997
+ 'ncm', t.ncm,
4998
+ 'cest', coalesce(t.cest, g.cest),
4999
+ 'cfop', coalesce(nullif(t.cfop, ''), g.cfop),
5000
+ 'cst', coalesce(nullif(t.cst, ''), g.cst),
5001
+ 'csosn', g.csosn,
5002
+ 'origin', coalesce(nullif(t.icms_origin, ''), g.icms_origin),
5003
+ 'icmsRate', CASE WHEN coalesce(t.icms_rate, 0) > 0 THEN t.icms_rate ELSE g.icms_rate END,
5004
+ 'icmsBaseMode', g.icms_bc_mode,
5005
+ 'pisSituation', coalesce(nullif(t.pis_situation, ''), g.pis_situation),
5006
+ 'pisRate', CASE WHEN coalesce(t.pis_rate, 0) > 0 THEN t.pis_rate ELSE g.pis_rate END,
5007
+ 'cofinsSituation', coalesce(nullif(t.cofins_situation, ''), g.cofins_situation),
5008
+ 'cofinsRate', CASE WHEN coalesce(t.cofins_rate, 0) > 0 THEN t.cofins_rate ELSE g.cofins_rate END,
5009
+ 'ibsCbsSituation', coalesce(nullif(t.ibs_cbs_situation, ''), g.ibs_cbs_situation),
5010
+ 'ibsCbsClassification', coalesce(nullif(t.ibs_cbs_classification, ''), g.ibs_cbs_classification),
5011
+ 'group', g.name
5012
+ ) END
5013
+ ) ORDER BY i.sort_order, i.created_at)
5014
+ FROM public.items i
5015
+ LEFT JOIN public.products p ON p.id = i.product_id AND p.tenant_id = i.tenant_id
5016
+ LEFT JOIN public.plg_fiscalbr_product_tax_info t ON t.product_id = i.product_id AND t.tenant_id = i.tenant_id
5017
+ LEFT JOIN public.plg_fiscalbr_tax_groups g ON g.id = t.tax_group_id AND g.tenant_id = i.tenant_id
5018
+ WHERE i.order_id = o.id AND i.tenant_id = o.tenant_id
5019
+ ), '[]'::jsonb),
5020
+ -- Anything that is not a site order is paid through the movements the
5021
+ -- order received, net of reversals, by method.
5022
+ 'checkout', v_checkout,
5023
+ 'payments', coalesce((
5024
+ SELECT jsonb_agg(jsonb_build_object('method', x.method, 'methodType', x.method_type, 'amount', x.amount))
5025
+ FROM (
5026
+ SELECT pm.name AS method, pt.name AS method_type,
5027
+ sum(CASE WHEN m.direction = 'credit' THEN m.paid_amount ELSE -m.paid_amount END) AS amount
5028
+ FROM public.plg_financial_movements m
5029
+ LEFT JOIN public.plg_financial_payment_methods pm ON pm.id = m.payment_method_id
5030
+ LEFT JOIN public.plg_financial_payment_method_types pt ON pt.id = pm.payment_method_type_id
5031
+ WHERE m.tenant_id = o.tenant_id AND m.movement_kind = 'payment' AND m.status = 'paid'
5032
+ AND (m.invoice_id = o.id OR m.financial_invoice_id IN (
5033
+ SELECT fi.id FROM public.plg_financial_invoices fi WHERE fi.order_id = o.id AND fi.tenant_id = o.tenant_id))
5034
+ GROUP BY pm.name, pt.name
5035
+ ) x WHERE x.amount <> 0
5036
+ ), '[]'::jsonb),
5037
+ 'notes', coalesce((
5038
+ SELECT jsonb_agg(jsonb_build_object('documentId', d.document_id, 'model', d.model, 'status', d.status))
5039
+ FROM public.plg_fiscalbr_documents d
5040
+ WHERE d.tenant_id = o.tenant_id AND d.order_id = o.id
5041
+ ), '[]'::jsonb)
5042
+ )
5043
+ FROM public.orders o
5044
+ LEFT JOIN app.units u ON u.id = o.unit_id AND u.tenant_id = o.tenant_id
5045
+ WHERE o.id = p_order AND o.tenant_id = p_tenant);
5046
+ END
5047
+ $$;
5048
+
5049
+ -- The note, opened before PlugNotas is called. One live note per order and
5050
+ -- model: a second "Emitir" while one is processing or authorized is refused
5051
+ -- (P0001 note_exists), so a double click never issues twice.
5052
+ CREATE OR REPLACE FUNCTION public.fiscalbr_note_open_for_service(
5053
+ p_tenant uuid, p_order uuid, p_model text, p_input jsonb
5054
+ )
5055
+ RETURNS jsonb
5056
+ LANGUAGE plpgsql SECURITY DEFINER
5057
+ SET search_path TO ''
5058
+ AS $$
5059
+ DECLARE
5060
+ v_order public.orders%ROWTYPE;
5061
+ v_doc uuid := gen_random_uuid();
5062
+ v_integration text;
5063
+ v_existing uuid;
5064
+ BEGIN
5065
+ IF p_model NOT IN ('nfce', 'nfe') THEN
5066
+ RAISE EXCEPTION 'fiscalbr_note_open: model % is not issued here', p_model USING ERRCODE = '22023';
5067
+ END IF;
5068
+ SELECT * INTO v_order FROM public.orders WHERE id = p_order AND tenant_id = p_tenant FOR UPDATE;
5069
+ IF NOT FOUND THEN
5070
+ RAISE EXCEPTION 'fiscalbr_note_open: order % is not of tenant %', p_order, p_tenant USING ERRCODE = 'P0002';
5071
+ END IF;
5072
+ SELECT document_id INTO v_existing FROM public.plg_fiscalbr_documents
5073
+ WHERE tenant_id = p_tenant AND order_id = p_order AND model = p_model AND status IN ('processing', 'authorized')
5074
+ LIMIT 1;
5075
+ IF v_existing IS NOT NULL THEN
5076
+ RAISE EXCEPTION 'note_exists' USING ERRCODE = 'P0001', DETAIL = v_existing::text;
5077
+ END IF;
5078
+
5079
+ -- Ours, and unique per company at PlugNotas: how a lost answer is found again.
5080
+ v_integration := 'csb-' || v_doc::text;
5081
+
5082
+ INSERT INTO public.documents (id, tenant_id, kind, title, status, unit_id, subject_type, subject_id, created_by, metadata)
5083
+ VALUES (v_doc, p_tenant, 'fiscal_document', upper(p_model) || ' do pedido', 'processing', v_order.unit_id,
5084
+ 'order', p_order, nullif(p_input ->> 'userId', '')::uuid, jsonb_build_object('source', 'integration-service'));
5085
+
5086
+ INSERT INTO public.plg_fiscalbr_documents (
5087
+ document_id, tenant_id, unit_id, order_id, model, environment, status, series, provider,
5088
+ provider_integration_id, total_value, recipient_snapshot, request_payload, source_snapshot, emitted_by_user_id, metadata)
5089
+ VALUES (
5090
+ v_doc, p_tenant, v_order.unit_id, p_order, p_model, p_input ->> 'environment', 'processing', p_input ->> 'series', 'plugnotas',
5091
+ v_integration, (p_input ->> 'total')::numeric, p_input -> 'recipient', p_input -> 'request', p_input -> 'source',
5092
+ nullif(p_input ->> 'userId', '')::uuid, jsonb_build_object('emitterCnpj', p_input ->> 'emitterCnpj'));
5093
+
5094
+ INSERT INTO public.plg_fiscalbr_document_orders (tenant_id, document_id, order_id)
5095
+ VALUES (p_tenant, v_doc, p_order);
5096
+
5097
+ INSERT INTO public.plg_fiscalbr_document_events (tenant_id, unit_id, document_id, event_type, status, created_by_user_id)
5098
+ VALUES (p_tenant, v_order.unit_id, v_doc, 'issue_requested', 'processing', nullif(p_input ->> 'userId', '')::uuid);
5099
+
5100
+ RETURN jsonb_build_object('documentId', v_doc, 'integrationId', v_integration);
5101
+ END
5102
+ $$;
5103
+
5104
+ -- What PlugNotas answered, on the note and on its public.documents row (the
5105
+ -- same note seen from two places, 005). Identity (model, unit, order) never moves.
5106
+ CREATE OR REPLACE FUNCTION public.fiscalbr_note_apply_for_service(
5107
+ p_tenant uuid, p_document uuid, p_patch jsonb, p_event jsonb DEFAULT NULL
5108
+ )
5109
+ RETURNS void
5110
+ LANGUAGE plpgsql SECURITY DEFINER
5111
+ SET search_path TO ''
5112
+ AS $$
5113
+ DECLARE
5114
+ v public.plg_fiscalbr_documents%ROWTYPE;
5115
+ v_status text := p_patch ->> 'status';
5116
+ BEGIN
5117
+ SELECT * INTO v FROM public.plg_fiscalbr_documents
5118
+ WHERE document_id = p_document AND tenant_id = p_tenant FOR UPDATE;
5119
+ IF NOT FOUND THEN
5120
+ RAISE EXCEPTION 'fiscalbr_note_apply: note % is not of tenant %', p_document, p_tenant USING ERRCODE = 'P0002';
5121
+ END IF;
5122
+ IF v_status IS NOT NULL AND v_status NOT IN ('processing', 'authorized', 'rejected', 'error', 'cancelled', 'substituted') THEN
5123
+ RAISE EXCEPTION 'fiscalbr_note_apply: unknown status %', v_status USING ERRCODE = '22023';
5124
+ END IF;
5125
+
5126
+ UPDATE public.plg_fiscalbr_documents SET
5127
+ status = coalesce(v_status, status),
5128
+ number = coalesce(p_patch ->> 'number', number),
5129
+ series = coalesce(p_patch ->> 'series', series),
5130
+ access_key = coalesce(p_patch ->> 'access_key', access_key),
5131
+ protocol = coalesce(p_patch ->> 'protocol', protocol),
5132
+ provider_document_id = coalesce(p_patch ->> 'provider_document_id', provider_document_id),
5133
+ response_payload = coalesce(p_patch -> 'response_payload', response_payload),
5134
+ status_payload = coalesce(p_patch -> 'status_payload', status_payload),
5135
+ rejection_payload = CASE WHEN p_patch ? 'rejection_payload' THEN p_patch -> 'rejection_payload' ELSE rejection_payload END,
5136
+ xml = coalesce(p_patch ->> 'xml', xml),
5137
+ authorized_at = coalesce((p_patch ->> 'authorized_at')::timestamptz, authorized_at),
5138
+ last_polled_at = CASE WHEN p_patch ? 'polled' THEN now() ELSE last_polled_at END,
5139
+ cancelled_at = CASE WHEN p_patch ? 'cancelled_at' THEN (p_patch ->> 'cancelled_at')::timestamptz ELSE cancelled_at END,
5140
+ cancel_reason = CASE WHEN p_patch ? 'cancel_reason' THEN p_patch ->> 'cancel_reason' ELSE cancel_reason END,
5141
+ cancelled_by_user_id = CASE WHEN p_patch ? 'cancelled_by_user_id' THEN nullif(p_patch ->> 'cancelled_by_user_id', '')::uuid ELSE cancelled_by_user_id END,
5142
+ metadata = metadata || coalesce(p_patch -> 'metadata', '{}'::jsonb)
5143
+ WHERE document_id = p_document;
5144
+
5145
+ IF v_status IS NOT NULL THEN
5146
+ UPDATE public.documents SET status = v_status WHERE id = p_document AND status IS DISTINCT FROM v_status;
5147
+ END IF;
5148
+
5149
+ IF p_event IS NOT NULL THEN
5150
+ INSERT INTO public.plg_fiscalbr_document_events
5151
+ (tenant_id, unit_id, document_id, event_type, status, justification, response_payload, created_by_user_id)
5152
+ VALUES (v.tenant_id, v.unit_id, p_document, p_event ->> 'event_type', p_event ->> 'status',
5153
+ p_event ->> 'justification', p_event -> 'response_payload', nullif(p_event ->> 'userId', '')::uuid);
5154
+ END IF;
5155
+ END
5156
+ $$;
5157
+
5158
+ CREATE OR REPLACE FUNCTION public.fiscalbr_note_for_service(p_tenant uuid, p_document uuid)
5159
+ RETURNS jsonb
5160
+ LANGUAGE sql STABLE SECURITY DEFINER
5161
+ SET search_path TO ''
5162
+ AS $$
5163
+ SELECT jsonb_build_object(
5164
+ 'documentId', d.document_id, 'tenantId', d.tenant_id, 'unitId', d.unit_id, 'orderId', d.order_id,
5165
+ 'model', d.model, 'environment', d.environment, 'status', d.status, 'series', d.series, 'number', d.number,
5166
+ 'accessKey', d.access_key, 'protocol', d.protocol, 'provider', d.provider,
5167
+ 'providerDocumentId', d.provider_document_id, 'integrationId', d.provider_integration_id,
5168
+ 'total', d.total_value, 'recipient', d.recipient_snapshot, 'rejection', d.rejection_payload,
5169
+ 'hasXml', d.xml IS NOT NULL, 'authorizedAt', d.authorized_at, 'lastPolledAt', d.last_polled_at,
5170
+ 'cancelledAt', d.cancelled_at, 'cancelReason', d.cancel_reason, 'createdAt', d.created_at,
5171
+ 'emitterCnpj', d.metadata ->> 'emitterCnpj', 'metadata', d.metadata)
5172
+ FROM public.plg_fiscalbr_documents d
5173
+ WHERE d.document_id = p_document AND d.tenant_id = p_tenant
5174
+ $$;
5175
+
5176
+ CREATE OR REPLACE FUNCTION public.fiscalbr_notes_for_service(p_tenant uuid, p_order uuid)
5177
+ RETURNS SETOF jsonb
5178
+ LANGUAGE sql STABLE SECURITY DEFINER
5179
+ SET search_path TO ''
5180
+ AS $$
5181
+ SELECT public.fiscalbr_note_for_service(p_tenant, d.document_id)
5182
+ FROM public.plg_fiscalbr_documents d
5183
+ WHERE d.tenant_id = p_tenant AND d.order_id = p_order
5184
+ ORDER BY d.created_at DESC
5185
+ $$;
5186
+
5187
+ -- The XML is large and read only on download.
5188
+ CREATE OR REPLACE FUNCTION public.fiscalbr_note_xml_for_service(p_tenant uuid, p_document uuid)
5189
+ RETURNS text
5190
+ LANGUAGE sql STABLE SECURITY DEFINER
5191
+ SET search_path TO ''
5192
+ AS $$
5193
+ SELECT d.xml FROM public.plg_fiscalbr_documents d WHERE d.document_id = p_document AND d.tenant_id = p_tenant
5194
+ $$;
5195
+
5196
+ -- Notes this service sent that PlugNotas has not settled: the fallback when a
5197
+ -- webhook does not arrive. Oldest poll first.
5198
+ CREATE OR REPLACE FUNCTION public.fiscalbr_notes_to_follow_for_service(p_limit integer DEFAULT 25)
5199
+ RETURNS SETOF jsonb
5200
+ LANGUAGE sql STABLE SECURITY DEFINER
5201
+ SET search_path TO ''
5202
+ AS $$
5203
+ SELECT public.fiscalbr_note_for_service(d.tenant_id, d.document_id)
5204
+ FROM public.plg_fiscalbr_documents d
5205
+ WHERE d.provider = 'plugnotas' AND d.status = 'processing' AND d.provider_integration_id LIKE 'csb-%'
5206
+ AND d.created_at > now() - interval '7 days'
5207
+ ORDER BY coalesce(d.last_polled_at, d.created_at)
5208
+ LIMIT greatest(1, least(p_limit, 100))
5209
+ $$;
5210
+
5211
+ DO $$
5212
+ DECLARE
5213
+ f text;
5214
+ BEGIN
5215
+ FOREACH f IN ARRAY ARRAY[
5216
+ 'public.fiscalbr_order_for_note_for_service(uuid, uuid)',
5217
+ 'public.fiscalbr_note_open_for_service(uuid, uuid, text, jsonb)',
5218
+ 'public.fiscalbr_note_apply_for_service(uuid, uuid, jsonb, jsonb)',
5219
+ 'public.fiscalbr_note_for_service(uuid, uuid)',
5220
+ 'public.fiscalbr_notes_for_service(uuid, uuid)',
5221
+ 'public.fiscalbr_note_xml_for_service(uuid, uuid)',
5222
+ 'public.fiscalbr_notes_to_follow_for_service(integer)'
5223
+ ] LOOP
5224
+ EXECUTE format('REVOKE ALL ON FUNCTION %s FROM PUBLIC, anon, authenticated', f);
5225
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN
5226
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %s TO fayz_integration', f);
5227
+ END IF;
5228
+ END LOOP;
5229
+ END
5230
+ $$;
5231
+ `;
4616
5232
  var MIGRATIONS = [
4617
5233
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
4618
5234
  { id: "001_preserve_terminal_results_and_recover_leases", sql: MIGRATION_001_PRESERVE_TERMINAL_RESULTS_AND_RECOVER_LEASES },
@@ -4644,7 +5260,8 @@ var MIGRATIONS = [
4644
5260
  { id: "027_a_nota_com_conta_e_sem_estoque_termina_no_v2", sql: MIGRATION_027_A_NOTA_COM_CONTA_E_SEM_ESTOQUE_TERMINA_NO_V2 },
4645
5261
  { id: "028_a_linha_de_estoque_tem_unidade", sql: MIGRATION_028_A_LINHA_DE_ESTOQUE_TEM_UNIDADE },
4646
5262
  { id: "029_the_service_writes_the_intake_health", sql: MIGRATION_029_THE_SERVICE_WRITES_THE_INTAKE_HEALTH },
4647
- { id: "030_the_service_onboards_the_emitter", sql: MIGRATION_030_THE_SERVICE_ONBOARDS_THE_EMITTER }
5263
+ { id: "030_the_service_onboards_the_emitter", sql: MIGRATION_030_THE_SERVICE_ONBOARDS_THE_EMITTER },
5264
+ { id: "031_the_service_issues_the_note", sql: MIGRATION_031_THE_SERVICE_ISSUES_THE_NOTE }
4648
5265
  ];
4649
5266
  var taxGroupEntity = {
4650
5267
  name: "Grupo fiscal",
@@ -6455,7 +7072,7 @@ function suggestedStockUnit(item, units, productUnits, productId) {
6455
7072
  const code = NOTE_UNIT_CODES[String(item.unit ?? "").trim().toUpperCase().replace(/\d+$/, "")];
6456
7073
  return code ? units.find((u) => u.abbreviation.toLowerCase() === code)?.id ?? null : null;
6457
7074
  }
6458
- var draftOf = (item) => ({
7075
+ var draftOf2 = (item) => ({
6459
7076
  productId: item.productId,
6460
7077
  productLabel: "",
6461
7078
  entryType: item.entryType ?? null,
@@ -6545,7 +7162,7 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
6545
7162
  setDrafts(Object.fromEntries(detail.items.map((i) => [
6546
7163
  i.id,
6547
7164
  {
6548
- ...draftOf(i),
7165
+ ...draftOf2(i),
6549
7166
  productLabel: i.productId && nomes.get(i.productId) || "",
6550
7167
  // Stocked lines keep what was recorded; open ones get a unit instead of a blank.
6551
7168
  stockUnitId: i.stockUnitId ?? (i.stockMovementId ? null : suggestedStockUnit(i, u, unidadesDoProduto, i.productId))
@@ -6598,7 +7215,7 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
6598
7215
  const buildDecisions = useCallback((rascunhos) => {
6599
7216
  if (!note) return [];
6600
7217
  return note.items.map((item) => {
6601
- const d = rascunhos[item.id] ?? draftOf(item);
7218
+ const d = rascunhos[item.id] ?? draftOf2(item);
6602
7219
  return {
6603
7220
  id: item.id,
6604
7221
  productId: isStockBearing(d.entryType) ? d.productId : null,
@@ -6623,7 +7240,7 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
6623
7240
  }).length;
6624
7241
  const missingType = decisions.filter((d) => !d.entryType).length;
6625
7242
  const suspectFactor = (item, d) => packagingCount(item.unit) !== null && isStockBearing(d.entryType) && ((Number(d.conversionFactor.replace(",", ".")) || 1) === 1 || !d.stockUnitId);
6626
- const unverifiedFactor = note ? note.items.filter((i) => suspectFactor(i, drafts[i.id] ?? draftOf(i))).length : 0;
7243
+ const unverifiedFactor = note ? note.items.filter((i) => suspectFactor(i, drafts[i.id] ?? draftOf2(i))).length : 0;
6627
7244
  const instalmentSum = instalments.reduce((acc, d) => acc + (Number(d.amount) || 0), 0);
6628
7245
  const hasPayable = Boolean(note?.invoiceId);
6629
7246
  const pendingStock = (note?.items ?? []).some((i) => !i.stockMovementId && (!i.entryType || isStockBearing(i.entryType)));
@@ -6652,13 +7269,13 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
6652
7269
  }, [note, buildDecisions, defaultAccount, location, instalments]);
6653
7270
  const pendingItems = useMemo(
6654
7271
  () => (note?.items ?? []).filter((item) => {
6655
- const d = drafts[item.id] ?? draftOf(item);
7272
+ const d = drafts[item.id] ?? draftOf2(item);
6656
7273
  return !d.entryType || isStockBearing(d.entryType) && !d.productId;
6657
7274
  }),
6658
7275
  [note, drafts]
6659
7276
  );
6660
7277
  const machineItems = useMemo(
6661
- () => (note?.items ?? []).filter((item) => (drafts[item.id] ?? draftOf(item)).suggestedBy !== null),
7278
+ () => (note?.items ?? []).filter((item) => (drafts[item.id] ?? draftOf2(item)).suggestedBy !== null),
6662
7279
  [note, drafts]
6663
7280
  );
6664
7281
  const sugerir = useCallback(async (reavaliar2 = false) => {
@@ -6969,17 +7586,17 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
6969
7586
  /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: "Fornecedor" }),
6970
7587
  /* @__PURE__ */ jsx("dd", { className: "text-sm", children: note.supplierPartyId ? /* @__PURE__ */ jsx(PersonLink, { personId: note.supplierPartyId, name: note.supplierName || "\u2014", kind: "supplier" }) : note.supplierName || "\u2014" })
6971
7588
  ] }),
6972
- /* @__PURE__ */ jsx(Field, { label: "CNPJ", value: note.supplierCnpj ?? "\u2014", mono: true }),
6973
- /* @__PURE__ */ jsx(Field, { label: "Natureza da opera\xE7\xE3o", value: note.natureOperation ?? "\u2014" }),
7589
+ /* @__PURE__ */ jsx(Field2, { label: "CNPJ", value: note.supplierCnpj ?? "\u2014", mono: true }),
7590
+ /* @__PURE__ */ jsx(Field2, { label: "Natureza da opera\xE7\xE3o", value: note.natureOperation ?? "\u2014" }),
6974
7591
  /* @__PURE__ */ jsx(
6975
- Field,
7592
+ Field2,
6976
7593
  {
6977
7594
  label: "Manifesta\xE7\xE3o",
6978
7595
  value: `${note.manifestationStatus ?? "n\xE3o manifestada"}${note.manifestationAttempts ? ` \xB7 ${note.manifestationAttempts} tentativa(s)` : ""}`
6979
7596
  }
6980
7597
  ),
6981
- /* @__PURE__ */ jsx(Field, { label: "Manifestada em", value: day3(note.manifestedAt) }),
6982
- /* @__PURE__ */ jsx(Field, { label: "Recebida em", value: day3(note.receivedAt) })
7598
+ /* @__PURE__ */ jsx(Field2, { label: "Manifestada em", value: day3(note.manifestedAt) }),
7599
+ /* @__PURE__ */ jsx(Field2, { label: "Recebida em", value: day3(note.receivedAt) })
6983
7600
  ] }),
6984
7601
  !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: [
6985
7602
  /* @__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: [
@@ -7123,7 +7740,7 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
7123
7740
  )
7124
7741
  ] }),
7125
7742
  note.items.map((item) => {
7126
- const d = drafts[item.id] ?? draftOf(item);
7743
+ const d = drafts[item.id] ?? draftOf2(item);
7127
7744
  const factor = Number(d.conversionFactor.replace(",", ".")) || 1;
7128
7745
  const shortage = d.shortageQuantity === "" ? 0 : Number(d.shortageQuantity.replace(",", ".")) || 0;
7129
7746
  const stockQty = item.quantity * factor - shortage;
@@ -7178,14 +7795,14 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
7178
7795
  item.description
7179
7796
  ] }),
7180
7797
  /* @__PURE__ */ jsxs("dl", { className: "mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs sm:grid-cols-4", children: [
7181
- /* @__PURE__ */ jsx(Field, { small: true, label: "Qtd", value: `${qty(item.quantity)} ${item.unit ?? ""}` }),
7182
- /* @__PURE__ */ jsx(Field, { small: true, label: "Vl. unit.", value: money3(item.unitPrice) }),
7183
- /* @__PURE__ */ jsx(Field, { small: true, label: "Total", value: money3(item.total) }),
7184
- /* @__PURE__ */ jsx(Field, { small: true, label: "C\xF3d", value: item.supplierProductCode || "\u2014", mono: true }),
7185
- /* @__PURE__ */ jsx(Field, { small: true, label: "EAN", value: item.eanBarcode ?? "\u2014", mono: true }),
7186
- /* @__PURE__ */ jsx(Field, { small: true, label: "NCM", value: item.ncm ?? "\u2014", mono: true }),
7187
- /* @__PURE__ */ jsx(Field, { small: true, label: "CFOP", value: item.cfop ?? "\u2014", mono: true }),
7188
- /* @__PURE__ */ jsx(Field, { small: true, label: "CST/CSOSN", value: item.icmsCst ?? item.icmsCsosn ?? "\u2014", mono: true })
7798
+ /* @__PURE__ */ jsx(Field2, { small: true, label: "Qtd", value: `${qty(item.quantity)} ${item.unit ?? ""}` }),
7799
+ /* @__PURE__ */ jsx(Field2, { small: true, label: "Vl. unit.", value: money3(item.unitPrice) }),
7800
+ /* @__PURE__ */ jsx(Field2, { small: true, label: "Total", value: money3(item.total) }),
7801
+ /* @__PURE__ */ jsx(Field2, { small: true, label: "C\xF3d", value: item.supplierProductCode || "\u2014", mono: true }),
7802
+ /* @__PURE__ */ jsx(Field2, { small: true, label: "EAN", value: item.eanBarcode ?? "\u2014", mono: true }),
7803
+ /* @__PURE__ */ jsx(Field2, { small: true, label: "NCM", value: item.ncm ?? "\u2014", mono: true }),
7804
+ /* @__PURE__ */ jsx(Field2, { small: true, label: "CFOP", value: item.cfop ?? "\u2014", mono: true }),
7805
+ /* @__PURE__ */ jsx(Field2, { small: true, label: "CST/CSOSN", value: item.icmsCst ?? item.icmsCsosn ?? "\u2014", mono: true })
7189
7806
  ] })
7190
7807
  ] }),
7191
7808
  /* @__PURE__ */ jsxs("div", { className: "space-y-3 lg:border-l lg:pl-4", children: [
@@ -7529,7 +8146,7 @@ function ItemState({ done, ready }) {
7529
8146
  }
7530
8147
  return /* @__PURE__ */ jsx("span", { className: `inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${ready ? "bg-muted text-muted-foreground" : "bg-warning-soft text-warning-soft-foreground"}`, children: ready ? "A conferir" : "Falta decidir" });
7531
8148
  }
7532
- function Field({ label, value, mono, small }) {
8149
+ function Field2({ label, value, mono, small }) {
7533
8150
  return /* @__PURE__ */ jsxs("div", { children: [
7534
8151
  /* @__PURE__ */ jsx("dt", { className: `text-muted-foreground ${small ? "text-[10px]" : "text-xs"}`, children: label }),
7535
8152
  /* @__PURE__ */ jsx("dd", { className: `${mono ? "tabular-nums" : ""} ${small ? "text-xs" : "text-sm"}`, children: value })
@@ -7645,26 +8262,17 @@ var COLUMNS = [
7645
8262
  ].join(", ");
7646
8263
  var sb2 = () => getSupabaseClientOptional();
7647
8264
  async function uploadCertificate(legalEntityId, file, password) {
7648
- const buf = new Uint8Array(await file.arrayBuffer());
7649
- let bin = "";
7650
- for (let i = 0; i < buf.length; i++) bin += String.fromCharCode(buf[i]);
7651
- const { data, error } = await sb2().functions.invoke("fiscalbr-certificate", {
7652
- body: { legalEntityId, pfxBase64: btoa(bin), password, fileName: file.name, tenantId: getActiveTenantId$1() ?? null }
7653
- });
7654
- if (error) {
7655
- let body = null;
7656
- try {
7657
- body = await error?.context?.json?.();
7658
- } catch {
7659
- }
7660
- throw new Error(body?.error || error.message || "Falha ao falar com o servi\xE7o fiscal.");
8265
+ try {
8266
+ const emitter = await emitters.certificate(legalEntityId, file, password);
8267
+ return { cn: emitter.certificate?.holder ?? "", expiresAt: emitter.certificate?.expiresAt ?? "", warning: null };
8268
+ } catch (error) {
8269
+ throw new Error(emitterErrorSentence(error));
7661
8270
  }
7662
- return data;
7663
8271
  }
7664
8272
  function useEmitterSettings(legalEntityId) {
7665
- const [row, setRow] = React6.useState(null);
7666
- const [loading, setLoading] = React6.useState(true);
7667
- React6.useEffect(() => {
8273
+ const [row, setRow] = React.useState(null);
8274
+ const [loading, setLoading] = React.useState(true);
8275
+ React.useEffect(() => {
7668
8276
  if (!legalEntityId) {
7669
8277
  setLoading(false);
7670
8278
  return;
@@ -7680,7 +8288,7 @@ function useEmitterSettings(legalEntityId) {
7680
8288
  alive = false;
7681
8289
  };
7682
8290
  }, [legalEntityId]);
7683
- const save = React6.useCallback(async (tenantId, patch) => {
8291
+ const save = React.useCallback(async (tenantId, patch) => {
7684
8292
  if (!legalEntityId) return false;
7685
8293
  const payload = { ...patch, tenant_id: tenantId, legal_entity_id: legalEntityId, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
7686
8294
  const { data, error } = await sb2().from("plg_fiscalbr_emitter_settings").upsert(payload, { onConflict: "legal_entity_id" }).select(COLUMNS).single();
@@ -7722,7 +8330,7 @@ function ToggleRow({ title, description, checked, onChange, disabled, children }
7722
8330
  ] })
7723
8331
  ] });
7724
8332
  }
7725
- function Field2({ label, hint, children }) {
8333
+ function Field3({ label, hint, children }) {
7726
8334
  return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
7727
8335
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: label }),
7728
8336
  children,
@@ -7733,7 +8341,7 @@ function SummaryCard({ title, lines, badge, onConfigure }) {
7733
8341
  return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-start justify-between gap-3 space-y-0", children: [
7734
8342
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7735
8343
  /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: title }),
7736
- /* @__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(React6.Fragment, { children: [
8344
+ /* @__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(React.Fragment, { children: [
7737
8345
  /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: l.label }),
7738
8346
  /* @__PURE__ */ jsx("dd", { className: "text-foreground", children: l.value })
7739
8347
  ] }, l.label)) })
@@ -7754,12 +8362,12 @@ function SummaryCard({ title, lines, badge, onConfigure }) {
7754
8362
  var fmtDateTime = (v) => v ? new Date(v).toLocaleString("pt-BR") : null;
7755
8363
  function UnitCertificateWidget({ item }) {
7756
8364
  const { row, loading, save, setRow } = useEmitterSettings(item?.legalEntityId);
7757
- const [open, setOpen] = React6.useState(false);
7758
- const [file, setFile] = React6.useState(null);
7759
- const [password, setPassword] = React6.useState("");
7760
- const [sending, setSending] = React6.useState(false);
8365
+ const [open, setOpen] = React.useState(false);
8366
+ const [file, setFile] = React.useState(null);
8367
+ const [password, setPassword] = React.useState("");
8368
+ const [sending, setSending] = React.useState(false);
7761
8369
  const expiredNow = !!row?.certificate_expires_at && Date.parse(row.certificate_expires_at) < Date.now();
7762
- React6.useEffect(() => {
8370
+ React.useEffect(() => {
7763
8371
  if (expiredNow) setOpen(true);
7764
8372
  }, [expiredNow]);
7765
8373
  if (!item?.id) return null;
@@ -7829,7 +8437,7 @@ function UnitCertificateWidget({ item }) {
7829
8437
  hasCertificate && /* @__PURE__ */ jsx(Badge, { variant: expired ? "destructive" : "secondary", children: expired ? "Vencido" : "Ativo" })
7830
8438
  ] }),
7831
8439
  /* @__PURE__ */ jsx("div", { className: "fz-container", children: /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
7832
- /* @__PURE__ */ jsx(Field2, { label: "Arquivo (.pfx)", children: /* @__PURE__ */ jsx(
8440
+ /* @__PURE__ */ jsx(Field3, { label: "Arquivo (.pfx)", children: /* @__PURE__ */ jsx(
7833
8441
  Input,
7834
8442
  {
7835
8443
  type: "file",
@@ -7838,7 +8446,7 @@ function UnitCertificateWidget({ item }) {
7838
8446
  onChange: (e) => setFile(e.target.files?.[0] ?? null)
7839
8447
  }
7840
8448
  ) }),
7841
- /* @__PURE__ */ jsx(Field2, { label: "Senha do certificado", children: /* @__PURE__ */ jsx(
8449
+ /* @__PURE__ */ jsx(Field3, { label: "Senha do certificado", children: /* @__PURE__ */ jsx(
7842
8450
  Input,
7843
8451
  {
7844
8452
  type: "password",
@@ -7859,15 +8467,15 @@ function UnitCertificateWidget({ item }) {
7859
8467
  onClick: () => {
7860
8468
  if (!file) return;
7861
8469
  setSending(true);
7862
- void uploadCertificate(item.legalEntityId, file, password).then((res) => {
8470
+ void uploadCertificate(item.legalEntityId, file, password).then(async (res) => {
7863
8471
  setRow((r) => ({
7864
8472
  ...r ?? {},
7865
8473
  certificate_cn: res.cn,
7866
8474
  certificate_expires_at: res.expiresAt,
7867
8475
  certificate_uploaded_at: (/* @__PURE__ */ new Date()).toISOString(),
7868
- dfe_sync_enabled: true,
7869
8476
  dfe_last_error: res.warning ?? null
7870
8477
  }));
8478
+ if (!row.dfe_sync_enabled) await save(item.tenantId, { dfe_sync_enabled: true });
7871
8479
  setFile(null);
7872
8480
  setPassword("");
7873
8481
  if (res.warning) toast.warning("Certificado enviado, com um aviso", { description: res.warning });
@@ -7908,12 +8516,14 @@ function EnvSelect({ value, onChange, disabled }) {
7908
8516
  ] });
7909
8517
  }
7910
8518
  function UnitFiscalWidget({ item }) {
7911
- const { row, loading, save } = useEmitterSettings(item?.legalEntityId);
7912
- const [draft, setDraft] = React6.useState({});
7913
- const [cscToken, setCscToken] = React6.useState("");
7914
- const [saving, setSaving] = React6.useState(false);
7915
- const [open, setOpen] = React6.useState(false);
7916
- React6.useEffect(() => {
8519
+ const { row, loading, save, setRow } = useEmitterSettings(item?.legalEntityId);
8520
+ const [draft, setDraft] = React.useState({});
8521
+ const [cscToken, setCscToken] = React.useState("");
8522
+ const [saving, setSaving] = React.useState(false);
8523
+ const [open, setOpen] = React.useState(false);
8524
+ const [registering, setRegistering] = React.useState(false);
8525
+ const [missing, setMissing] = React.useState([]);
8526
+ React.useEffect(() => {
7917
8527
  if (row) setDraft(row);
7918
8528
  }, [row]);
7919
8529
  if (!item?.id) return null;
@@ -7936,8 +8546,29 @@ function UnitFiscalWidget({ item }) {
7936
8546
  { ok: !!row.provider_synced_at, label: "Empresa registrada para emitir" }
7937
8547
  ];
7938
8548
  const ready = (draft.nfse_enabled || draft.nfce_enabled) && checks.every((c) => c.ok);
8549
+ const saveNfce = async () => {
8550
+ try {
8551
+ await emitters.save(item.legalEntityId, {
8552
+ nfce: {
8553
+ enabled: !!draft.nfce_enabled,
8554
+ production: !!draft.nfce_production,
8555
+ series: draft.nfce_series ? Number(draft.nfce_series) : null,
8556
+ cscId: draft.nfce_csc_id ? String(draft.nfce_csc_id).trim() : null,
8557
+ ...cscToken ? { cscToken } : {}
8558
+ }
8559
+ });
8560
+ return true;
8561
+ } catch (error) {
8562
+ toast.error("N\xE3o foi poss\xEDvel salvar a NFC-e", { description: emitterErrorSentence(error) });
8563
+ return false;
8564
+ }
8565
+ };
7939
8566
  const onSave = async () => {
7940
8567
  setSaving(true);
8568
+ if (!await saveNfce()) {
8569
+ setSaving(false);
8570
+ return;
8571
+ }
7941
8572
  const {
7942
8573
  id: _id,
7943
8574
  legal_entity_id: _le,
@@ -7947,6 +8578,10 @@ function UnitFiscalWidget({ item }) {
7947
8578
  certificate_uploaded_at: _u,
7948
8579
  dfe_last_sync_at: _d,
7949
8580
  provider_synced_at: _p,
8581
+ nfce_enabled: _ne,
8582
+ nfce_production: _np,
8583
+ nfce_series: _ns,
8584
+ nfce_csc_id: _nc,
7950
8585
  ...patch
7951
8586
  } = draft;
7952
8587
  const issRate = patch.nfse_iss_rate === "" || patch.nfse_iss_rate == null ? null : Number(String(patch.nfse_iss_rate).replace(",", "."));
@@ -7954,9 +8589,7 @@ function UnitFiscalWidget({ item }) {
7954
8589
  ...patch,
7955
8590
  nfse_iss_rate: Number.isFinite(issRate) ? issRate : null,
7956
8591
  nfse_iss_taxation: patch.nfse_iss_taxation ? Number(patch.nfse_iss_taxation) : null,
7957
- nfse_simples_regime: patch.nfse_simples_regime && patch.nfse_simples_regime !== "auto" ? Number(patch.nfse_simples_regime) : null,
7958
- nfce_series: patch.nfce_series ? Number(patch.nfce_series) : null,
7959
- ...cscToken ? { nfce_csc_token: cscToken } : {}
8592
+ nfse_simples_regime: patch.nfse_simples_regime && patch.nfse_simples_regime !== "auto" ? Number(patch.nfse_simples_regime) : null
7960
8593
  });
7961
8594
  setSaving(false);
7962
8595
  if (ok) {
@@ -7965,6 +8598,21 @@ function UnitFiscalWidget({ item }) {
7965
8598
  toast.success("Configura\xE7\xE3o fiscal salva");
7966
8599
  }
7967
8600
  };
8601
+ const onRegister = async () => {
8602
+ setRegistering(true);
8603
+ setMissing([]);
8604
+ try {
8605
+ if (!await saveNfce()) return;
8606
+ const after = await emitters.register(item.legalEntityId);
8607
+ setRow((r) => ({ ...r ?? {}, provider_synced_at: after.providerSyncedAt }));
8608
+ toast.success("Empresa cadastrada na PlugNotas");
8609
+ } catch (error) {
8610
+ if (error instanceof EmitterError) setMissing(error.gates.filter((g) => !g.ok).map((g) => GATE_LABELS[g.id] ?? g.id));
8611
+ toast.error("N\xE3o foi poss\xEDvel cadastrar na PlugNotas", { description: emitterErrorSentence(error) });
8612
+ } finally {
8613
+ setRegistering(false);
8614
+ }
8615
+ };
7968
8616
  if (!open) {
7969
8617
  const env = (prod) => prod ? "Produ\xE7\xE3o" : "Homologa\xE7\xE3o";
7970
8618
  return /* @__PURE__ */ jsx(
@@ -8012,24 +8660,24 @@ function UnitFiscalWidget({ item }) {
8012
8660
  ),
8013
8661
  draft.nfse_enabled && /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
8014
8662
  /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
8015
- /* @__PURE__ */ jsx(Field2, { label: "C\xF3d. tributa\xE7\xE3o nacional", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_service_code_national ?? "", placeholder: "ex.: 06.02.01", onChange: (e) => set({ nfse_service_code_national: e.target.value }) }) }),
8016
- /* @__PURE__ */ jsx(Field2, { label: "C\xF3d. tributa\xE7\xE3o municipal", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_service_code_municipal ?? "", placeholder: "ex.: 001", onChange: (e) => set({ nfse_service_code_municipal: e.target.value }) }) }),
8017
- /* @__PURE__ */ jsx(Field2, { label: "C\xF3digo NBS", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_nbs_code ?? "", placeholder: "ex.: 1.2602.90.00", onChange: (e) => set({ nfse_nbs_code: e.target.value }) }) }),
8018
- /* @__PURE__ */ jsx(Field2, { label: "CNAE do servi\xE7o", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_cnae ?? "", placeholder: "ex.: 9602-5/02", onChange: (e) => set({ nfse_cnae: e.target.value }) }) }),
8019
- /* @__PURE__ */ jsx(Field2, { label: "Al\xEDquota ISS (%)", children: /* @__PURE__ */ jsx(Input, { inputMode: "decimal", value: draft.nfse_iss_rate ?? "", placeholder: "ex.: 5", onChange: (e) => set({ nfse_iss_rate: e.target.value }) }) }),
8020
- /* @__PURE__ */ jsx(Field2, { label: "Tributa\xE7\xE3o ISS", children: /* @__PURE__ */ jsxs(Select, { value: draft.nfse_iss_taxation != null ? String(draft.nfse_iss_taxation) : void 0, onValueChange: (v) => set({ nfse_iss_taxation: v }), children: [
8663
+ /* @__PURE__ */ jsx(Field3, { label: "C\xF3d. tributa\xE7\xE3o nacional", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_service_code_national ?? "", placeholder: "ex.: 06.02.01", onChange: (e) => set({ nfse_service_code_national: e.target.value }) }) }),
8664
+ /* @__PURE__ */ jsx(Field3, { label: "C\xF3d. tributa\xE7\xE3o municipal", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_service_code_municipal ?? "", placeholder: "ex.: 001", onChange: (e) => set({ nfse_service_code_municipal: e.target.value }) }) }),
8665
+ /* @__PURE__ */ jsx(Field3, { label: "C\xF3digo NBS", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_nbs_code ?? "", placeholder: "ex.: 1.2602.90.00", onChange: (e) => set({ nfse_nbs_code: e.target.value }) }) }),
8666
+ /* @__PURE__ */ jsx(Field3, { label: "CNAE do servi\xE7o", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_cnae ?? "", placeholder: "ex.: 9602-5/02", onChange: (e) => set({ nfse_cnae: e.target.value }) }) }),
8667
+ /* @__PURE__ */ jsx(Field3, { label: "Al\xEDquota ISS (%)", children: /* @__PURE__ */ jsx(Input, { inputMode: "decimal", value: draft.nfse_iss_rate ?? "", placeholder: "ex.: 5", onChange: (e) => set({ nfse_iss_rate: e.target.value }) }) }),
8668
+ /* @__PURE__ */ jsx(Field3, { label: "Tributa\xE7\xE3o ISS", children: /* @__PURE__ */ jsxs(Select, { value: draft.nfse_iss_taxation != null ? String(draft.nfse_iss_taxation) : void 0, onValueChange: (v) => set({ nfse_iss_taxation: v }), children: [
8021
8669
  /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Selecione" }) }),
8022
8670
  /* @__PURE__ */ jsx(SelectContent, { children: ISS_TAXATION.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value)) })
8023
8671
  ] }) }),
8024
- /* @__PURE__ */ jsx(Field2, { label: "IBS/CBS \u2014 CST", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_ibscbs_cst ?? "", placeholder: "ex.: 000", onChange: (e) => set({ nfse_ibscbs_cst: e.target.value }) }) }),
8025
- /* @__PURE__ */ jsx(Field2, { label: "IBS/CBS \u2014 Classifica\xE7\xE3o", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_ibscbs_classification ?? "", placeholder: "ex.: 000001", onChange: (e) => set({ nfse_ibscbs_classification: e.target.value }) }) }),
8026
- isSimples && /* @__PURE__ */ jsx(Field2, { label: "Apura\xE7\xE3o dos tributos no Simples", children: /* @__PURE__ */ jsxs(Select, { value: draft.nfse_simples_regime != null ? String(draft.nfse_simples_regime) : "auto", onValueChange: (v) => set({ nfse_simples_regime: v }), children: [
8672
+ /* @__PURE__ */ jsx(Field3, { label: "IBS/CBS \u2014 CST", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_ibscbs_cst ?? "", placeholder: "ex.: 000", onChange: (e) => set({ nfse_ibscbs_cst: e.target.value }) }) }),
8673
+ /* @__PURE__ */ jsx(Field3, { label: "IBS/CBS \u2014 Classifica\xE7\xE3o", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_ibscbs_classification ?? "", placeholder: "ex.: 000001", onChange: (e) => set({ nfse_ibscbs_classification: e.target.value }) }) }),
8674
+ isSimples && /* @__PURE__ */ jsx(Field3, { label: "Apura\xE7\xE3o dos tributos no Simples", children: /* @__PURE__ */ jsxs(Select, { value: draft.nfse_simples_regime != null ? String(draft.nfse_simples_regime) : "auto", onValueChange: (v) => set({ nfse_simples_regime: v }), children: [
8027
8675
  /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
8028
8676
  /* @__PURE__ */ jsx(SelectContent, { children: SIMPLES_REGIME.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value)) })
8029
8677
  ] }) })
8030
8678
  ] }),
8031
8679
  /* @__PURE__ */ jsx(ToggleRow, { title: "Emitir pelo valor l\xEDquido", checked: !!draft.nfse_net_value, onChange: (v) => set({ nfse_net_value: v }) }),
8032
- /* @__PURE__ */ jsx(Field2, { label: "Informa\xE7\xF5es complementares da nota", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_additional_info ?? "", placeholder: "ex.: Totais Aproximados dos Tributos cfe. Lei n\xBA 12.741/2012\u2026", onChange: (e) => set({ nfse_additional_info: e.target.value }) }) })
8680
+ /* @__PURE__ */ jsx(Field3, { label: "Informa\xE7\xF5es complementares da nota", children: /* @__PURE__ */ jsx(Input, { value: draft.nfse_additional_info ?? "", placeholder: "ex.: Totais Aproximados dos Tributos cfe. Lei n\xBA 12.741/2012\u2026", onChange: (e) => set({ nfse_additional_info: e.target.value }) }) })
8033
8681
  ] })
8034
8682
  ] }),
8035
8683
  /* @__PURE__ */ jsxs("section", { className: "space-y-4 rounded-md border p-4", children: [
@@ -8045,14 +8693,14 @@ function UnitFiscalWidget({ item }) {
8045
8693
  ),
8046
8694
  draft.nfce_enabled && /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
8047
8695
  /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
8048
- /* @__PURE__ */ jsx(Field2, { label: "S\xE9rie", children: /* @__PURE__ */ jsx(Input, { inputMode: "numeric", value: draft.nfce_series ?? "", onChange: (e) => set({ nfce_series: e.target.value.replace(/\D/g, "") }) }) }),
8049
- /* @__PURE__ */ jsx(Field2, { label: "CSC \u2014 ID", children: /* @__PURE__ */ jsx(Input, { value: draft.nfce_csc_id ?? "", onChange: (e) => set({ nfce_csc_id: e.target.value }) }) })
8696
+ /* @__PURE__ */ jsx(Field3, { label: "S\xE9rie", children: /* @__PURE__ */ jsx(Input, { inputMode: "numeric", value: draft.nfce_series ?? "", onChange: (e) => set({ nfce_series: e.target.value.replace(/\D/g, "") }) }) }),
8697
+ /* @__PURE__ */ jsx(Field3, { label: "CSC \u2014 ID", children: /* @__PURE__ */ jsx(Input, { value: draft.nfce_csc_id ?? "", onChange: (e) => set({ nfce_csc_id: e.target.value }) }) })
8050
8698
  ] }),
8051
- /* @__PURE__ */ jsx(Field2, { label: "CSC \u2014 Token", hint: "O CSC (id num\xE9rico + token) \xE9 gerado no portal da SEFAZ do estado \u2014 n\xE3o \xE9 o login de nenhum sistema.", children: /* @__PURE__ */ jsx(Input, { type: "password", value: cscToken, placeholder: row.nfce_csc_token_set ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (configurado \u2014 digite para trocar)" : "", onChange: (e) => setCscToken(e.target.value) }) }),
8699
+ /* @__PURE__ */ jsx(Field3, { label: "CSC \u2014 Token", hint: "O CSC (id num\xE9rico + token) \xE9 gerado no portal da SEFAZ do estado \u2014 n\xE3o \xE9 o login de nenhum sistema.", children: /* @__PURE__ */ jsx(Input, { type: "password", value: cscToken, placeholder: row.nfce_csc_token_set ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (configurado \u2014 digite para trocar)" : "", onChange: (e) => setCscToken(e.target.value) }) }),
8052
8700
  /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
8053
- /* @__PURE__ */ jsx(Field2, { label: "Item gen\xE9rico \u2014 NCM", children: /* @__PURE__ */ jsx(Input, { value: generic.ncm ?? "", onChange: (e) => setGeneric({ ncm: e.target.value }) }) }),
8054
- /* @__PURE__ */ jsx(Field2, { label: "Item gen\xE9rico \u2014 CFOP", children: /* @__PURE__ */ jsx(Input, { value: generic.cfop ?? "", onChange: (e) => setGeneric({ cfop: e.target.value }) }) }),
8055
- /* @__PURE__ */ jsx(Field2, { label: "Item gen\xE9rico \u2014 CSOSN/CST", children: /* @__PURE__ */ jsx(Input, { value: generic.csosn ?? "", onChange: (e) => setGeneric({ csosn: e.target.value }) }) })
8701
+ /* @__PURE__ */ jsx(Field3, { label: "Item gen\xE9rico \u2014 NCM", children: /* @__PURE__ */ jsx(Input, { value: generic.ncm ?? "", onChange: (e) => setGeneric({ ncm: e.target.value }) }) }),
8702
+ /* @__PURE__ */ jsx(Field3, { label: "Item gen\xE9rico \u2014 CFOP", children: /* @__PURE__ */ jsx(Input, { value: generic.cfop ?? "", onChange: (e) => setGeneric({ cfop: e.target.value }) }) }),
8703
+ /* @__PURE__ */ jsx(Field3, { label: "Item gen\xE9rico \u2014 CSOSN/CST", children: /* @__PURE__ */ jsx(Input, { value: generic.csosn ?? "", onChange: (e) => setGeneric({ csosn: e.target.value }) }) })
8056
8704
  ] })
8057
8705
  ] })
8058
8706
  ] }),
@@ -8061,12 +8709,75 @@ function UnitFiscalWidget({ item }) {
8061
8709
  c.label
8062
8710
  ] }) }, c.label)) }),
8063
8711
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-3", children: [
8064
- /* @__PURE__ */ jsx("p", { className: "mr-auto text-xs text-muted-foreground", children: "Registrar a empresa no emissor ainda n\xE3o est\xE1 dispon\xEDvel nesta vers\xE3o \u2014 a configura\xE7\xE3o fica salva para quando estiver." }),
8065
- /* @__PURE__ */ jsx(Button, { onClick: () => void onSave(), disabled: saving, children: saving ? "Salvando\u2026" : "Salvar configura\xE7\xE3o" })
8712
+ missing.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mr-auto text-xs text-destructive", children: [
8713
+ "Falta: ",
8714
+ missing.join(", ")
8715
+ ] }),
8716
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void onRegister(), disabled: saving || registering, children: registering ? "Cadastrando\u2026" : row.provider_synced_at ? "Atualizar na PlugNotas" : "Cadastrar na PlugNotas" }),
8717
+ /* @__PURE__ */ jsx(Button, { onClick: () => void onSave(), disabled: saving || registering, children: saving ? "Salvando\u2026" : "Salvar configura\xE7\xE3o" })
8066
8718
  ] })
8067
8719
  ] })
8068
8720
  ] });
8069
8721
  }
8722
+
8723
+ // src/lib/notes.ts
8724
+ var CANCEL_MIN_JUSTIFICATION = 15;
8725
+ var SENTENCES2 = {
8726
+ no_client: "O servi\xE7o de integra\xE7\xF5es n\xE3o est\xE1 configurado neste app.",
8727
+ not_configured: "A emiss\xE3o fiscal ainda n\xE3o est\xE1 ligada no servidor.",
8728
+ unauthorized: "Sua sess\xE3o expirou. Entre de novo.",
8729
+ not_allowed: "Seu acesso n\xE3o permite emitir ou cancelar notas.",
8730
+ unavailable: "N\xE3o foi poss\xEDvel falar com o servi\xE7o fiscal. Tente de novo em instantes.",
8731
+ unknown_order: "Este pedido n\xE3o foi encontrado.",
8732
+ unknown_note: "Esta nota n\xE3o foi encontrada.",
8733
+ not_a_sale: "S\xF3 uma venda recebe NFC-e.",
8734
+ order_not_closed: "Este pedido ainda n\xE3o pode receber nota.",
8735
+ no_emitter: "A unidade deste pedido n\xE3o tem empresa (CNPJ) cadastrada para emitir.",
8736
+ emitter_not_owned: "Esta empresa ainda emite pelo sistema anterior. Configure em Integra\xE7\xF5es \u2192 PlugNotas.",
8737
+ emitter_not_ready: "Falta configurar a empresa para emitir NFC-e (Integra\xE7\xF5es \u2192 PlugNotas).",
8738
+ interstate: "Este pedido vai para outro estado: precisa de NF-e, n\xE3o de NFC-e.",
8739
+ no_series: "A empresa n\xE3o tem s\xE9rie de NFC-e configurada.",
8740
+ no_items: "O pedido n\xE3o tem produtos para a nota.",
8741
+ products_without_ncm: "H\xE1 produtos sem cadastro fiscal (NCM).",
8742
+ icms_code_mismatch: "H\xE1 produtos com c\xF3digo de ICMS que n\xE3o combina com o regime da empresa.",
8743
+ not_paid: "O pedido n\xE3o tem pagamento recebido para a nota.",
8744
+ totals_differ: "Os itens e os pagamentos do pedido n\xE3o fecham no mesmo total.",
8745
+ invalid_text: "Um produto tem um nome que a SEFAZ n\xE3o aceita (s\xF3 emojis ou caracteres especiais).",
8746
+ note_exists: "Este pedido j\xE1 tem uma nota emitida ou em processamento.",
8747
+ not_authorized: "S\xF3 uma nota autorizada pode ser cancelada ou baixada.",
8748
+ justification_too_short: `A justificativa precisa ter pelo menos ${CANCEL_MIN_JUSTIFICATION} caracteres.`,
8749
+ cancel_deadline_passed: "O prazo de cancelamento desta nota j\xE1 passou.",
8750
+ cancel_refused: "A SEFAZ recusou o cancelamento.",
8751
+ plugnotas_refused: "A PlugNotas recusou o envio."
8752
+ };
8753
+ function noteErrorSentence(error) {
8754
+ if (error instanceof EmitterError) {
8755
+ const base = SENTENCES2[error.code] ?? error.message;
8756
+ return error.code === "products_without_ncm" || error.code === "icms_code_mismatch" ? `${base} ${error.message.split(": ").slice(1).join(": ")}`.trim() : base;
8757
+ }
8758
+ return error instanceof Error ? error.message : String(error);
8759
+ }
8760
+ var order = (orderId) => `/v1/fiscal/orders/${encodeURIComponent(orderId)}/notes`;
8761
+ var one2 = (documentId, rest = "") => `/v1/fiscal/notes/${encodeURIComponent(documentId)}${rest}`;
8762
+ var orderNotes = {
8763
+ list: async (orderId) => (await request("GET", order(orderId))).notes,
8764
+ issueNfce: (orderId) => request("POST", order(orderId), { model: "nfce" }),
8765
+ /** Reads PlugNotas again while the note is processing. */
8766
+ read: (documentId) => request("GET", one2(documentId)),
8767
+ cancel: (documentId, justification) => request("PUT", one2(documentId, "/cancellation"), { justification }),
8768
+ pdf: (documentId) => requestFile(one2(documentId, "/pdf")),
8769
+ xml: (documentId) => requestFile(one2(documentId, "/xml"))
8770
+ };
8771
+ var NOTE_STATUS_LABEL = {
8772
+ processing: "Processando",
8773
+ authorized: "Autorizada",
8774
+ rejected: "Rejeitada",
8775
+ error: "Erro",
8776
+ cancelled: "Cancelada",
8777
+ substituted: "Substitu\xEDda"
8778
+ };
8779
+
8780
+ // src/data/issued.ts
8070
8781
  function sb3() {
8071
8782
  const supabase = getSupabaseClientOptional();
8072
8783
  if (!supabase) throw new Error("Supabase not initialized");
@@ -8128,35 +8839,26 @@ async function listNoteEvents(documentId) {
8128
8839
  createdAt: e.created_at
8129
8840
  }));
8130
8841
  }
8131
- async function callIssuer(body) {
8132
- const { data, error } = await sb3().functions.invoke("fiscalbr-plugnotas", { body: { ...body, tenantId: getActiveTenantId() } });
8133
- if (error) {
8134
- const ctx = error?.context;
8135
- let message = error.message || "Falha ao falar com o emissor fiscal.";
8136
- if (ctx && typeof ctx.json === "function") {
8137
- try {
8138
- const b = await ctx.json();
8139
- if (b?.error) message = String(b.error);
8140
- } catch {
8141
- }
8142
- }
8143
- throw new Error(message);
8144
- }
8145
- if (data?.error) throw new Error(String(data.error));
8146
- return data;
8147
- }
8148
8842
  async function refreshNote(documentId) {
8149
- const r = await callIssuer({ action: "poll_status", document_id: documentId });
8150
- return String(r?.status ?? "");
8843
+ try {
8844
+ return (await orderNotes.read(documentId)).status;
8845
+ } catch (error) {
8846
+ throw new Error(noteErrorSentence(error));
8847
+ }
8151
8848
  }
8152
- async function cancelNote(documentId, justification, code) {
8153
- const r = await callIssuer({ action: "cancel", document_id: documentId, justification, code });
8154
- return String(r?.status ?? "");
8849
+ async function cancelNote(documentId, justification) {
8850
+ try {
8851
+ return (await orderNotes.cancel(documentId, justification)).status;
8852
+ } catch (error) {
8853
+ throw new Error(noteErrorSentence(error));
8854
+ }
8155
8855
  }
8156
8856
  async function fetchNotePdf(documentId) {
8157
- const data = await callIssuer({ action: "get_pdf", document_id: documentId });
8158
- if (!(data instanceof Blob)) throw new Error("O emissor n\xE3o devolveu um PDF.");
8159
- return new Blob([data], { type: "application/pdf" });
8857
+ try {
8858
+ return await orderNotes.pdf(documentId);
8859
+ } catch (error) {
8860
+ throw new Error(noteErrorSentence(error));
8861
+ }
8160
8862
  }
8161
8863
 
8162
8864
  // src/lib/issued-note.ts
@@ -8335,7 +9037,7 @@ function NoteRow({ note, canCancel, onChanged }) {
8335
9037
  const [cancelling, setCancelling] = useState(false);
8336
9038
  const [busy, setBusy] = useState(null);
8337
9039
  const dimmed = isWithoutEffect(note.status);
8338
- const tone = (STATUS_META[note.status] ?? STATUS_META.error).tone;
9040
+ const tone2 = (STATUS_META[note.status] ?? STATUS_META.error).tone;
8339
9041
  const usable = note.status === "authorized" || note.status === "cancelled";
8340
9042
  const inFlight = note.status === "processing" || note.status === "draft";
8341
9043
  const cancellable = canCancel && note.status === "authorized" && !note.cancelledAt;
@@ -8383,7 +9085,7 @@ function NoteRow({ note, canCancel, onChanged }) {
8383
9085
  "n\xBA ",
8384
9086
  note.number
8385
9087
  ] }),
8386
- /* @__PURE__ */ jsx(Badge, { variant: TONE_VARIANT[tone], children: statusLabel(note) }),
9088
+ /* @__PURE__ */ jsx(Badge, { variant: TONE_VARIANT[tone2], children: statusLabel(note) }),
8387
9089
  note.environment === "homologacao" && /* @__PURE__ */ jsx("span", { className: "rounded border border-destructive px-1 text-[10px] font-semibold text-destructive", children: "TESTE" }),
8388
9090
  /* @__PURE__ */ jsx("span", { className: "ml-auto text-xs tabular-nums text-muted-foreground", children: dateTime(note.createdAt) })
8389
9091
  ] }),
@@ -8436,7 +9138,7 @@ function CancelNotePanel({ note, onClose, onDone }) {
8436
9138
  setError(null);
8437
9139
  setWorking("sending");
8438
9140
  try {
8439
- let status = await cancelNote(note.documentId, text.trim(), isNfse ? code : void 0);
9141
+ let status = await cancelNote(note.documentId, text.trim());
8440
9142
  setWorking("waiting");
8441
9143
  for (const delay of POLL_DELAYS) {
8442
9144
  if (status === "cancelled" || status === "authorized") break;
@@ -8562,6 +9264,184 @@ function NoteDetails({ note }) {
8562
9264
  ] })
8563
9265
  ] });
8564
9266
  }
9267
+ var POLL_DELAYS2 = [3, 5, 8, 12, 20, 20];
9268
+ var tone = (status) => status === "authorized" ? "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400" : status === "rejected" || status === "error" ? "border-destructive/40 bg-destructive/10 text-destructive" : "text-muted-foreground";
9269
+ async function openFile(blob, fileName, download) {
9270
+ const url = URL.createObjectURL(blob);
9271
+ if (download) {
9272
+ const a = document.createElement("a");
9273
+ a.href = url;
9274
+ a.download = fileName;
9275
+ a.click();
9276
+ } else {
9277
+ window.open(url, "_blank", "noopener");
9278
+ }
9279
+ setTimeout(() => URL.revokeObjectURL(url), 6e4);
9280
+ }
9281
+ function OrderNoteCard({ orderId }) {
9282
+ const [notes, setNotes] = React.useState(null);
9283
+ const [error, setError] = React.useState(null);
9284
+ const [issuing, setIssuing] = React.useState(false);
9285
+ const [cancelling, setCancelling] = React.useState(null);
9286
+ const load = React.useCallback(async () => {
9287
+ if (!orderId) return;
9288
+ try {
9289
+ setNotes(await orderNotes.list(orderId));
9290
+ setError(null);
9291
+ } catch (e) {
9292
+ setError(noteErrorSentence(e));
9293
+ }
9294
+ }, [orderId]);
9295
+ React.useEffect(() => {
9296
+ void load();
9297
+ }, [load]);
9298
+ const processing = notes?.find((n) => n.status === "processing" || n.cancellation === "processing");
9299
+ React.useEffect(() => {
9300
+ if (!processing) return;
9301
+ let alive = true;
9302
+ let step = 0;
9303
+ let timer;
9304
+ const tick = async () => {
9305
+ if (!alive || step >= POLL_DELAYS2.length) return;
9306
+ timer = setTimeout(async () => {
9307
+ step++;
9308
+ try {
9309
+ const fresh = await orderNotes.read(processing.documentId);
9310
+ if (!alive) return;
9311
+ setNotes((list) => list?.map((n) => n.documentId === fresh.documentId ? fresh : n) ?? null);
9312
+ if (fresh.status === "processing" || fresh.cancellation === "processing") void tick();
9313
+ else if (fresh.status === "authorized" && !fresh.cancellation) toast.success(`NFC-e n\xBA ${fresh.number ?? ""} autorizada`);
9314
+ else if (fresh.status === "rejected") toast.error("A SEFAZ rejeitou a NFC-e", { description: fresh.rejection?.message ?? void 0 });
9315
+ } catch {
9316
+ void tick();
9317
+ }
9318
+ }, POLL_DELAYS2[step] * 1e3);
9319
+ };
9320
+ void tick();
9321
+ return () => {
9322
+ alive = false;
9323
+ clearTimeout(timer);
9324
+ };
9325
+ }, [processing?.documentId, processing?.status, processing?.cancellation]);
9326
+ if (!orderId) return null;
9327
+ const live = notes?.find((n) => n.status === "processing" || n.status === "authorized");
9328
+ const issue = async () => {
9329
+ setIssuing(true);
9330
+ try {
9331
+ const note = await orderNotes.issueNfce(orderId);
9332
+ setNotes((list) => [note, ...list ?? []]);
9333
+ if (note.status === "rejected") toast.error("A NFC-e foi recusada", { description: note.rejection?.message ?? void 0 });
9334
+ else toast.success("NFC-e enviada. Aguardando a SEFAZ\u2026");
9335
+ } catch (e) {
9336
+ toast.error("N\xE3o foi poss\xEDvel emitir a NFC-e", { description: noteErrorSentence(e) });
9337
+ } finally {
9338
+ setIssuing(false);
9339
+ }
9340
+ };
9341
+ return /* @__PURE__ */ jsxs(
9342
+ OrderDetailBlock,
9343
+ {
9344
+ title: "Nota fiscal",
9345
+ actions: !live && notes !== null && !error ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void issue(), disabled: issuing, children: [
9346
+ /* @__PURE__ */ jsx(FileText, { className: "mr-1.5 h-4 w-4" }),
9347
+ issuing ? "Emitindo\u2026" : "Emitir NFC-e"
9348
+ ] }) : void 0,
9349
+ children: [
9350
+ error && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 text-sm", children: [
9351
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: error }),
9352
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => void load(), children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" }) })
9353
+ ] }),
9354
+ !error && notes === null && /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" }),
9355
+ !error && notes?.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nenhuma nota emitida para este pedido." }),
9356
+ !error && notes && notes.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: notes.map((n) => /* @__PURE__ */ jsxs("li", { className: "space-y-1.5 text-sm", children: [
9357
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
9358
+ /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
9359
+ n.model === "nfce" ? "NFC-e" : "NF-e",
9360
+ n.number ? ` n\xBA ${n.number}` : "",
9361
+ n.series ? ` \xB7 s\xE9rie ${n.series}` : ""
9362
+ ] }),
9363
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", className: tone(n.status), children: n.cancellation === "processing" ? "Cancelando\u2026" : n.uncertain ? "Aguardando a SEFAZ" : NOTE_STATUS_LABEL[n.status] }),
9364
+ n.environment === "homologation" && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-muted-foreground", children: "Homologa\xE7\xE3o" })
9365
+ ] }),
9366
+ n.accessKey && /* @__PURE__ */ jsx("p", { className: "break-all font-mono text-xs text-muted-foreground", children: n.accessKey.replace(/(\d{4})(?=\d)/g, "$1 ") }),
9367
+ (n.status === "rejected" || n.status === "error") && n.rejection?.message && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: n.rejection.message }),
9368
+ n.status === "cancelled" && n.cancelReason && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
9369
+ "Cancelada: ",
9370
+ n.cancelReason
9371
+ ] }),
9372
+ (n.status === "authorized" || n.status === "cancelled") && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
9373
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => void orderNotes.pdf(n.documentId).then((b) => openFile(b, `nfce-${n.number ?? n.documentId}.pdf`, false)).catch((e) => toast.error("N\xE3o foi poss\xEDvel abrir o DANFE", { description: noteErrorSentence(e) })), children: [
9374
+ /* @__PURE__ */ jsx(FileText, { className: "mr-1.5 h-4 w-4" }),
9375
+ "DANFE"
9376
+ ] }),
9377
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => void orderNotes.xml(n.documentId).then((b) => openFile(b, `nfce-${n.number ?? n.documentId}.xml`, true)).catch((e) => toast.error("N\xE3o foi poss\xEDvel baixar o XML", { description: noteErrorSentence(e) })), children: [
9378
+ /* @__PURE__ */ jsx(Download, { className: "mr-1.5 h-4 w-4" }),
9379
+ "XML"
9380
+ ] }),
9381
+ n.status === "authorized" && !n.cancellation && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "text-destructive", onClick: () => setCancelling(n), children: "Cancelar nota" })
9382
+ ] })
9383
+ ] }, n.documentId)) }),
9384
+ cancelling && /* @__PURE__ */ jsx(
9385
+ CancelDialog,
9386
+ {
9387
+ note: cancelling,
9388
+ onClose: () => setCancelling(null),
9389
+ onDone: (fresh) => {
9390
+ setNotes((list) => list?.map((x) => x.documentId === fresh.documentId ? fresh : x) ?? null);
9391
+ setCancelling(null);
9392
+ }
9393
+ }
9394
+ )
9395
+ ]
9396
+ }
9397
+ );
9398
+ }
9399
+ function CancelDialog({ note, onClose, onDone }) {
9400
+ const [text, setText] = React.useState("");
9401
+ const [sending, setSending] = React.useState(false);
9402
+ const ok = text.trim().length >= CANCEL_MIN_JUSTIFICATION;
9403
+ const send2 = async () => {
9404
+ setSending(true);
9405
+ try {
9406
+ const fresh = await orderNotes.cancel(note.documentId, text.trim());
9407
+ toast.success("Cancelamento enviado. Aguardando a SEFAZ\u2026");
9408
+ onDone(fresh);
9409
+ } catch (e) {
9410
+ toast.error("N\xE3o foi poss\xEDvel cancelar a nota", { description: noteErrorSentence(e) });
9411
+ } finally {
9412
+ setSending(false);
9413
+ }
9414
+ };
9415
+ return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (open) => {
9416
+ if (!open) onClose();
9417
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { children: [
9418
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
9419
+ /* @__PURE__ */ jsxs(ModalTitle, { children: [
9420
+ "Cancelar NFC-e",
9421
+ note.number ? ` n\xBA ${note.number}` : ""
9422
+ ] }),
9423
+ /* @__PURE__ */ jsx(ModalDescription, { children: "A SEFAZ s\xF3 aceita o cancelamento de uma NFC-e logo depois da emiss\xE3o (em geral, 30 minutos). Para emitir uma NF-e no lugar, cancele primeiro esta nota." })
9424
+ ] }),
9425
+ /* @__PURE__ */ jsx(
9426
+ Textarea,
9427
+ {
9428
+ value: text,
9429
+ onChange: (e) => setText(e.target.value),
9430
+ placeholder: "Ex.: Emiss\xE3o em duplicidade por erro operacional",
9431
+ rows: 3
9432
+ }
9433
+ ),
9434
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
9435
+ "Pelo menos ",
9436
+ CANCEL_MIN_JUSTIFICATION,
9437
+ " caracteres."
9438
+ ] }),
9439
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
9440
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: sending, children: "Voltar" }),
9441
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => void send2(), disabled: !ok || sending, children: sending ? "Cancelando\u2026" : "Cancelar nota" })
9442
+ ] })
9443
+ ] }) });
9444
+ }
8565
9445
 
8566
9446
  // src/lib/financial-provider.ts
8567
9447
  function fiscalReader(provider) {
@@ -8737,9 +9617,9 @@ function inboundNotesInventoryView(options = {}) {
8737
9617
  };
8738
9618
  }
8739
9619
  function InboundNoteDocumentModal({ movement, onClose }) {
8740
- const [noteId, setNoteId] = React6.useState(null);
8741
- const [erro, setErro] = React6.useState(null);
8742
- React6.useEffect(() => {
9620
+ const [noteId, setNoteId] = React.useState(null);
9621
+ const [erro, setErro] = React.useState(null);
9622
+ React.useEffect(() => {
8743
9623
  let vivo = true;
8744
9624
  const numero = nfeNumberOf(movement);
8745
9625
  const busca = movement.operationId ? findInboxNoteByOperation(movement.operationId).then((id) => id ?? (numero ? findInboxNoteByNumber(numero) : null)) : numero ? findInboxNoteByNumber(numero) : Promise.resolve(null);
@@ -8852,6 +9732,14 @@ function createPlugNotasPlugin(options = {}) {
8852
9732
  order: 0,
8853
9733
  permission: { feature: "financial", action: "read" }
8854
9734
  },
9735
+ {
9736
+ // Issue, follow and cancel the order's NFC-e (control-sdk-backend ADR 0008).
9737
+ id: "fiscal-br-order-note",
9738
+ zone: ORDER_DETAIL_ZONES.fiscal,
9739
+ component: OrderNoteCard,
9740
+ order: 0,
9741
+ permission: { feature: "financial", action: "read" }
9742
+ },
8855
9743
  {
8856
9744
  id: "fiscal-br-unit-fiscal",
8857
9745
  zone: UNIT_DETAIL_ZONES.fiscal,
@@ -8860,77 +9748,18 @@ function createPlugNotasPlugin(options = {}) {
8860
9748
  permission: { feature: "financial", action: "read" }
8861
9749
  }
8862
9750
  ],
8863
- connectors: [createPlugNotasConnector(options)],
8864
- functions: [{
8865
- slug: "plugnotas-bridge",
8866
- ingress: "none",
8867
- verifyJwt: false,
8868
- secrets: [
8869
- "PLUGNOTAS_API_KEY",
8870
- "PLUGNOTAS_BASE_URL",
8871
- "PLUGNOTAS_ENVIRONMENT",
8872
- "PLUGNOTAS_WEBHOOK_SECRET",
8873
- "PLUGNOTAS_ARTIFACT_SIGNING_KEY",
8874
- "PLUGNOTAS_ALLOWED_ORIGINS",
8875
- "PLUGNOTAS_WORKER_SECRET",
8876
- "SUPABASE_URL",
8877
- "SUPABASE_ANON_KEY",
8878
- "SUPABASE_SERVICE_ROLE_KEY"
8879
- ],
8880
- grants: [
8881
- "plugnotas_register_emitter",
8882
- "plugnotas_disconnect_emitter",
8883
- "plugnotas_create_nfce_draft_for_order",
8884
- "plugnotas_prepare_nfce",
8885
- "plugnotas_prepare_nfce_cancel",
8886
- "plugnotas_apply_nfce_result",
8887
- "plugnotas_apply_nfce_webhook",
8888
- "plugnotas_get_artifact_locator",
8889
- "plugnotas_claim_nfce_recovery",
8890
- "plugnotas_record_nfce_attempt"
8891
- ]
8892
- }],
9751
+ // PlugNotas is reached by the integration service (control-sdk-backend
9752
+ // ADR 0008), never by an edge function of this plugin.
9753
+ connectors: [createPlugNotasConnector()],
8893
9754
  migrations: MIGRATIONS.map((entry) => ({
8894
9755
  id: `fiscal-br-${entry.id}`,
8895
9756
  version: "1.0.0",
8896
9757
  sql: entry.sql,
8897
9758
  description: "Durable PlugNotas delivery, recovery, webhook and emitter-registration boundary."
8898
- })),
8899
- diagnostics: [{
8900
- id: "plugnotas-nfce-ready",
8901
- description: "NFC-e requires the shared connection, order, item and financial fiscal-document contracts.",
8902
- requires: {
8903
- tables: [
8904
- "connections",
8905
- "orders",
8906
- "items",
8907
- "plg_financial_nfe",
8908
- "plg_fiscalbr_nfce_jobs",
8909
- "plg_fiscalbr_nfce_events",
8910
- "plg_fiscalbr_webhook_receipts"
8911
- ],
8912
- rpcs: [
8913
- "plugnotas_create_nfce_draft_for_order",
8914
- "plugnotas_prepare_nfce",
8915
- "plugnotas_apply_nfce_result",
8916
- "plugnotas_apply_nfce_webhook",
8917
- "plugnotas_claim_nfce_recovery"
8918
- ],
8919
- env: [
8920
- "PLUGNOTAS_API_KEY",
8921
- "PLUGNOTAS_BASE_URL",
8922
- "PLUGNOTAS_ENVIRONMENT",
8923
- "PLUGNOTAS_WEBHOOK_SECRET",
8924
- "PLUGNOTAS_ARTIFACT_SIGNING_KEY",
8925
- "PLUGNOTAS_ALLOWED_ORIGINS",
8926
- "PLUGNOTAS_WORKER_SECRET"
8927
- ]
8928
- },
8929
- level: "error"
8930
- }]
9759
+ }))
8931
9760
  });
8932
9761
  }
8933
9762
 
8934
- export { EVENT_LABEL, InboundNoteDocumentModal, InboundNotesPage, InboundNotesView, InvoiceNotesButton, IssuedNotesView, MODEL_LABEL, 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, fillInboxLines, findInboxNoteByNumber, findInboxNoteByOperation, getInboxIntakeHealth, getInboxNote, getInboxNotePdf, getInboxNoteXml, getNotePayable, healInboxNote, inboundNoteDocumentModal, inboundNotesInventoryView, isStockBearing, isValidCnpj, isWithoutEffect, linkInboxSupplier, listChartAccounts, listInboxNotes, listInvoiceNotes, listMeasurementUnits, listNoteAttachments, listNoteEvents, listProductStockUnits, listProductsByIds, listStockLocations, listSupplierMap, markPlugNotasEmitterReady, moveNoteAttachmentsToInvoice, notesButtonState, onlyDigits, parseBoleto, parseNfeXml, paymentLabel, paymentTypeFromCode, pullNoteByKey, recipientLabel, refreshNote, rejectNote, removeNoteAttachment, saveNoteDecision, searchProducts, stageNoteFromXml, statusLabel, suggestNoteItems, summarizeNotes, syncNewNotes, toStagePayload };
9763
+ export { CANCEL_MIN_JUSTIFICATION, EVENT_LABEL, EmitterError, EmittersPanel, GATE_LABELS, InboundNoteDocumentModal, InboundNotesPage, InboundNotesView, InvoiceNotesButton, IssuedNotesView, MODEL_LABEL, NOTE_STATUS_LABEL, NfeParseError, NoteReconcileView, OrderNoteCard, PLUGNOTAS_CONNECTOR_ID, PullByKeyError, ReturnedNotesView, SPECIAL_TAX_REGIMES, STATUS_META, STOCK_BEARING_ENTRY_TYPES, TAX_REGIMES, TaxGroupsView, acceptNote, addNoteAttachment, cancelNote, createPlugNotasConnector, createPlugNotasFinancialProvider, createPlugNotasPlugin, emitterErrorSentence, emitters, eventStatusLabel, explainRejection, extractPaymentCode, fetchNotePdf, fillInboxLines, findInboxNoteByNumber, findInboxNoteByOperation, getInboxIntakeHealth, getInboxNote, getInboxNotePdf, getInboxNoteXml, getNotePayable, healInboxNote, inboundNoteDocumentModal, inboundNotesInventoryView, isStockBearing, isValidCnpj, isWithoutEffect, linkInboxSupplier, listChartAccounts, listInboxNotes, listInvoiceNotes, listMeasurementUnits, listNoteAttachments, listNoteEvents, listProductStockUnits, listProductsByIds, listStockLocations, listSupplierMap, moveNoteAttachmentsToInvoice, noteErrorSentence, notesButtonState, onlyDigits, orderNotes, parseBoleto, parseNfeXml, paymentLabel, paymentTypeFromCode, pullNoteByKey, recipientLabel, refreshNote, rejectNote, removeNoteAttachment, saveNoteDecision, searchProducts, stageNoteFromXml, statusLabel, suggestNoteItems, summarizeNotes, syncNewNotes, toStagePayload };
8935
9764
  //# sourceMappingURL=index.js.map
8936
9765
  //# sourceMappingURL=index.js.map