@fayz-ai/plugin-fiscal-br 0.11.0 → 0.12.1
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/data/issued.d.ts +2 -2
- package/dist/data/issued.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +630 -71
- package/dist/index.js.map +1 -1
- package/dist/lib/emitters.d.ts +2 -8
- package/dist/lib/emitters.d.ts.map +1 -1
- package/dist/lib/notes.d.ts +42 -0
- package/dist/lib/notes.d.ts.map +1 -0
- package/dist/lib/service.d.ts +20 -0
- package/dist/lib/service.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/OrderNoteCard.d.ts +5 -0
- package/dist/views/OrderNoteCard.d.ts.map +1 -0
- package/package.json +5 -5
- package/functions/fiscalbr-certificate/index.ts +0 -162
- package/functions/fiscalbr-plugnotas/index.ts +0 -222
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { packagingCount } from './chunk-5LI2FDOM.js';
|
|
2
2
|
import { createConnectionStore, recordRailFields, RECORD_GROUP, activeFacet, describeError, getSupabaseClientOptional, formatCurrency, maskCpfCnpj, connectionStatus, definePlugin, getActiveTenantId as getActiveTenantId$1 } from '@fayz-ai/core';
|
|
3
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,
|
|
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
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, UNIT_DETAIL_ZONES, serviceBaseUrl } from '@fayz-ai/admin';
|
|
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
|
|
|
@@ -18,6 +18,56 @@ var EmitterError = class extends Error {
|
|
|
18
18
|
this.name = "EmitterError";
|
|
19
19
|
}
|
|
20
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
|
|
21
71
|
var SENTENCES = {
|
|
22
72
|
no_client: "O servi\xE7o de integra\xE7\xF5es n\xE3o est\xE1 configurado neste app.",
|
|
23
73
|
not_configured: "A emiss\xE3o fiscal ainda n\xE3o est\xE1 ligada no servidor.",
|
|
@@ -71,43 +121,6 @@ var GATE_LABELS = {
|
|
|
71
121
|
csc: "CSC (ID e c\xF3digo) da NFC-e",
|
|
72
122
|
registered: "Empresa cadastrada na PlugNotas"
|
|
73
123
|
};
|
|
74
|
-
async function request(method, path, body) {
|
|
75
|
-
const base = serviceBaseUrl();
|
|
76
|
-
const supabase = getSupabaseClientOptional();
|
|
77
|
-
if (!base || !supabase) throw new EmitterError(503, "no_client", "the integration service is not configured");
|
|
78
|
-
const { data } = await supabase.auth.getSession();
|
|
79
|
-
const token = data?.session?.access_token;
|
|
80
|
-
if (!token) throw new EmitterError(401, "unauthorized", "sign in again");
|
|
81
|
-
const tenant = getActiveTenantId$1();
|
|
82
|
-
let response;
|
|
83
|
-
try {
|
|
84
|
-
response = await fetch(`${base}${path}`, {
|
|
85
|
-
method,
|
|
86
|
-
headers: {
|
|
87
|
-
authorization: `Bearer ${token}`,
|
|
88
|
-
...tenant ? { "x-fayz-tenant": tenant } : {},
|
|
89
|
-
...body === void 0 ? {} : { "content-type": "application/json" }
|
|
90
|
-
},
|
|
91
|
-
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
92
|
-
});
|
|
93
|
-
} catch (error) {
|
|
94
|
-
throw new EmitterError(503, "unavailable", error instanceof Error ? error.message : String(error));
|
|
95
|
-
}
|
|
96
|
-
const text = await response.text();
|
|
97
|
-
let payload = null;
|
|
98
|
-
try {
|
|
99
|
-
payload = text ? JSON.parse(text) : null;
|
|
100
|
-
} catch {
|
|
101
|
-
}
|
|
102
|
-
if (response.ok) return payload;
|
|
103
|
-
throw new EmitterError(
|
|
104
|
-
response.status,
|
|
105
|
-
payload?.error ?? "unavailable",
|
|
106
|
-
payload?.message ?? text.slice(0, 200),
|
|
107
|
-
Array.isArray(payload?.gates) ? payload.gates : [],
|
|
108
|
-
typeof payload?.field === "string" ? payload.field : null
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
124
|
var one = (legalEntityId, rest = "") => `/v1/fiscal/emitters/${encodeURIComponent(legalEntityId)}${rest}`;
|
|
112
125
|
async function base64Of(file) {
|
|
113
126
|
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
@@ -363,7 +376,7 @@ function Certificate({ emitter, onChange }) {
|
|
|
363
376
|
const [password, setPassword] = React.useState("");
|
|
364
377
|
const [sending, setSending] = React.useState(false);
|
|
365
378
|
const c = emitter.certificate;
|
|
366
|
-
const
|
|
379
|
+
const send2 = async () => {
|
|
367
380
|
if (!file) return;
|
|
368
381
|
setSending(true);
|
|
369
382
|
try {
|
|
@@ -395,7 +408,7 @@ function Certificate({ emitter, onChange }) {
|
|
|
395
408
|
] }) }),
|
|
396
409
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
397
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." }),
|
|
398
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", disabled: !file || !password || sending, onClick: () => void
|
|
411
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", disabled: !file || !password || sending, onClick: () => void send2(), children: sending ? "Enviando\u2026" : c ? "Trocar certificado" : "Enviar certificado" })
|
|
399
412
|
] })
|
|
400
413
|
] });
|
|
401
414
|
}
|
|
@@ -4907,6 +4920,315 @@ BEGIN
|
|
|
4907
4920
|
END
|
|
4908
4921
|
$$;
|
|
4909
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
|
+
`;
|
|
4910
5232
|
var MIGRATIONS = [
|
|
4911
5233
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
4912
5234
|
{ id: "001_preserve_terminal_results_and_recover_leases", sql: MIGRATION_001_PRESERVE_TERMINAL_RESULTS_AND_RECOVER_LEASES },
|
|
@@ -4938,7 +5260,8 @@ var MIGRATIONS = [
|
|
|
4938
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 },
|
|
4939
5261
|
{ id: "028_a_linha_de_estoque_tem_unidade", sql: MIGRATION_028_A_LINHA_DE_ESTOQUE_TEM_UNIDADE },
|
|
4940
5262
|
{ id: "029_the_service_writes_the_intake_health", sql: MIGRATION_029_THE_SERVICE_WRITES_THE_INTAKE_HEALTH },
|
|
4941
|
-
{ 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 }
|
|
4942
5265
|
];
|
|
4943
5266
|
var taxGroupEntity = {
|
|
4944
5267
|
name: "Grupo fiscal",
|
|
@@ -8396,6 +8719,65 @@ function UnitFiscalWidget({ item }) {
|
|
|
8396
8719
|
] })
|
|
8397
8720
|
] });
|
|
8398
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
|
|
8399
8781
|
function sb3() {
|
|
8400
8782
|
const supabase = getSupabaseClientOptional();
|
|
8401
8783
|
if (!supabase) throw new Error("Supabase not initialized");
|
|
@@ -8457,35 +8839,26 @@ async function listNoteEvents(documentId) {
|
|
|
8457
8839
|
createdAt: e.created_at
|
|
8458
8840
|
}));
|
|
8459
8841
|
}
|
|
8460
|
-
async function callIssuer(body) {
|
|
8461
|
-
const { data, error } = await sb3().functions.invoke("fiscalbr-plugnotas", { body: { ...body, tenantId: getActiveTenantId() } });
|
|
8462
|
-
if (error) {
|
|
8463
|
-
const ctx = error?.context;
|
|
8464
|
-
let message = error.message || "Falha ao falar com o emissor fiscal.";
|
|
8465
|
-
if (ctx && typeof ctx.json === "function") {
|
|
8466
|
-
try {
|
|
8467
|
-
const b = await ctx.json();
|
|
8468
|
-
if (b?.error) message = String(b.error);
|
|
8469
|
-
} catch {
|
|
8470
|
-
}
|
|
8471
|
-
}
|
|
8472
|
-
throw new Error(message);
|
|
8473
|
-
}
|
|
8474
|
-
if (data?.error) throw new Error(String(data.error));
|
|
8475
|
-
return data;
|
|
8476
|
-
}
|
|
8477
8842
|
async function refreshNote(documentId) {
|
|
8478
|
-
|
|
8479
|
-
|
|
8843
|
+
try {
|
|
8844
|
+
return (await orderNotes.read(documentId)).status;
|
|
8845
|
+
} catch (error) {
|
|
8846
|
+
throw new Error(noteErrorSentence(error));
|
|
8847
|
+
}
|
|
8480
8848
|
}
|
|
8481
|
-
async function cancelNote(documentId, justification
|
|
8482
|
-
|
|
8483
|
-
|
|
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
|
+
}
|
|
8484
8855
|
}
|
|
8485
8856
|
async function fetchNotePdf(documentId) {
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8857
|
+
try {
|
|
8858
|
+
return await orderNotes.pdf(documentId);
|
|
8859
|
+
} catch (error) {
|
|
8860
|
+
throw new Error(noteErrorSentence(error));
|
|
8861
|
+
}
|
|
8489
8862
|
}
|
|
8490
8863
|
|
|
8491
8864
|
// src/lib/issued-note.ts
|
|
@@ -8664,7 +9037,7 @@ function NoteRow({ note, canCancel, onChanged }) {
|
|
|
8664
9037
|
const [cancelling, setCancelling] = useState(false);
|
|
8665
9038
|
const [busy, setBusy] = useState(null);
|
|
8666
9039
|
const dimmed = isWithoutEffect(note.status);
|
|
8667
|
-
const
|
|
9040
|
+
const tone2 = (STATUS_META[note.status] ?? STATUS_META.error).tone;
|
|
8668
9041
|
const usable = note.status === "authorized" || note.status === "cancelled";
|
|
8669
9042
|
const inFlight = note.status === "processing" || note.status === "draft";
|
|
8670
9043
|
const cancellable = canCancel && note.status === "authorized" && !note.cancelledAt;
|
|
@@ -8712,7 +9085,7 @@ function NoteRow({ note, canCancel, onChanged }) {
|
|
|
8712
9085
|
"n\xBA ",
|
|
8713
9086
|
note.number
|
|
8714
9087
|
] }),
|
|
8715
|
-
/* @__PURE__ */ jsx(Badge, { variant: TONE_VARIANT[
|
|
9088
|
+
/* @__PURE__ */ jsx(Badge, { variant: TONE_VARIANT[tone2], children: statusLabel(note) }),
|
|
8716
9089
|
note.environment === "homologacao" && /* @__PURE__ */ jsx("span", { className: "rounded border border-destructive px-1 text-[10px] font-semibold text-destructive", children: "TESTE" }),
|
|
8717
9090
|
/* @__PURE__ */ jsx("span", { className: "ml-auto text-xs tabular-nums text-muted-foreground", children: dateTime(note.createdAt) })
|
|
8718
9091
|
] }),
|
|
@@ -8765,7 +9138,7 @@ function CancelNotePanel({ note, onClose, onDone }) {
|
|
|
8765
9138
|
setError(null);
|
|
8766
9139
|
setWorking("sending");
|
|
8767
9140
|
try {
|
|
8768
|
-
let status = await cancelNote(note.documentId, text.trim()
|
|
9141
|
+
let status = await cancelNote(note.documentId, text.trim());
|
|
8769
9142
|
setWorking("waiting");
|
|
8770
9143
|
for (const delay of POLL_DELAYS) {
|
|
8771
9144
|
if (status === "cancelled" || status === "authorized") break;
|
|
@@ -8891,6 +9264,184 @@ function NoteDetails({ note }) {
|
|
|
8891
9264
|
] })
|
|
8892
9265
|
] });
|
|
8893
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
|
+
}
|
|
8894
9445
|
|
|
8895
9446
|
// src/lib/financial-provider.ts
|
|
8896
9447
|
function fiscalReader(provider) {
|
|
@@ -9181,6 +9732,14 @@ function createPlugNotasPlugin(options = {}) {
|
|
|
9181
9732
|
order: 0,
|
|
9182
9733
|
permission: { feature: "financial", action: "read" }
|
|
9183
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
|
+
},
|
|
9184
9743
|
{
|
|
9185
9744
|
id: "fiscal-br-unit-fiscal",
|
|
9186
9745
|
zone: UNIT_DETAIL_ZONES.fiscal,
|
|
@@ -9201,6 +9760,6 @@ function createPlugNotasPlugin(options = {}) {
|
|
|
9201
9760
|
});
|
|
9202
9761
|
}
|
|
9203
9762
|
|
|
9204
|
-
export { EVENT_LABEL, EmitterError, EmittersPanel, GATE_LABELS, InboundNoteDocumentModal, InboundNotesPage, InboundNotesView, InvoiceNotesButton, IssuedNotesView, MODEL_LABEL, NfeParseError, NoteReconcileView, 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, 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 };
|
|
9205
9764
|
//# sourceMappingURL=index.js.map
|
|
9206
9765
|
//# sourceMappingURL=index.js.map
|