@fayz-ai/plugin-fiscal-br 0.7.5 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { recordRailFields, RECORD_GROUP, activeFacet, describeError, getSupabaseClientOptional, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus, maskCpfCnpj } from '@fayz-ai/core';
2
- import React7, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
3
- import { ModalHeader, ModalTitle, ModalBody, Skeleton, AiActionButton, Dropdown, DropdownTrigger, Button, DropdownContent, DropdownItem, Hint, Card, SearchSelect, Input, EmBreve, DatePicker, ModalFooter, SubpageHeader, Modal, ModalContent, ModalDescription, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, useFilterDraft, RowActions, PageHeaderActions, FilterPanel, FilterPanelField, SearchField, TooltipProvider, DataTable, ChevronPager, Tooltip, TooltipTrigger, TooltipContent, ListFilterBar, Badge, SegmentedControl, ComingSoon, toast, CardHeader, CardTitle, CardContent, CardDescription, Switch } from '@fayz-ai/ui';
1
+ import { recordRailFields, RECORD_GROUP, activeFacet, describeError, getSupabaseClientOptional, connectionSettings, connectionStatus, definePlugin, createConnectionStore, settingsStatus, maskCpfCnpj, getActiveTenantId as getActiveTenantId$1 } from '@fayz-ai/core';
2
+ import React6, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
3
+ 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';
4
4
  export { ComingSoon, EmBreve } from '@fayz-ai/ui';
5
- import { registerDocumentOpener, PersonLink, getActiveTenantId, useUnitFilter, PageActions, usePermissionOptional, UNIT_DETAIL_ZONES, adminNavigateTo } from '@fayz-ai/admin';
6
- import { Loader2, Download, ChevronDown, FileCode2, FileText, RefreshCw, UserPlus, Gift, AlertTriangle, ChevronRight, Check, Trash2, Plus, KeyRound, Image, UploadCloud, Paperclip, ScanLine, Eye, ListChecks, Inbox, FileDown, ShieldCheck, ExternalLink, CheckCircle2, FileCheck2, XCircle, X, Info } from 'lucide-react';
5
+ import { registerDocumentOpener, PersonLink, getActiveTenantId, useUnitFilter, PageActions, adminNavigateTo, usePermissionOptional, UNIT_DETAIL_ZONES } from '@fayz-ai/admin';
6
+ 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';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import { FINANCIAL_INVOICE_ZONES } from '@fayz-ai/plugin-financial';
9
9
 
@@ -4017,6 +4017,297 @@ $function$;
4017
4017
  REVOKE ALL ON FUNCTION public.fiscalbr_fill_inbox_lines(uuid, jsonb) FROM PUBLIC;
4018
4018
  GRANT EXECUTE ON FUNCTION public.fiscalbr_fill_inbox_lines(uuid, jsonb) TO authenticated;
4019
4019
  `;
4020
+ var MIGRATION_024_A_NOTA_NAO_APONTA_PARA_CONTA_QUE_SUMIU = `-- ---------------------------------------------------------------------------
4021
+ -- A note never points at a payable (or stock movement) that no longer exists.
4022
+ -- ---------------------------------------------------------------------------
4023
+ -- invoice_id and stock_movement_id cross into other plugins, so they carry no
4024
+ -- foreign key. When a re-copy rebuilt a tenant's payables under new ids, the
4025
+ -- notes kept the old ones: the screen read "already accepted" and hid every
4026
+ -- action, while Contas a Pagar had nothing. This heals a note: a dead payable
4027
+ -- link is re-pointed to the live payable of the same NF-e when exactly one
4028
+ -- exists, otherwise cleared; a dead movement link is cleared, and the note
4029
+ -- stops counting as stocked only when that dead movement was all it had. A
4030
+ -- cleared note can be accepted again, which writes the payable and the stock.
4031
+ -- ---------------------------------------------------------------------------
4032
+
4033
+ CREATE OR REPLACE FUNCTION public.plg_fiscalbr_heal_note_links(p_tenant uuid, p_document_id uuid)
4034
+ RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
4035
+ DECLARE
4036
+ v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
4037
+ v_invoice uuid;
4038
+ v_matches integer;
4039
+ v_dead_mv integer := 0;
4040
+ v_relinked boolean := false;
4041
+ v_released boolean := false;
4042
+ v_unstocked boolean := false;
4043
+ BEGIN
4044
+ SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
4045
+ WHERE tenant_id = p_tenant AND id = p_document_id
4046
+ FOR UPDATE;
4047
+ IF NOT FOUND THEN
4048
+ RETURN jsonb_build_object('document_id', p_document_id, 'changed', false);
4049
+ END IF;
4050
+
4051
+ IF v_doc.invoice_id IS NOT NULL AND NOT EXISTS (
4052
+ SELECT 1 FROM public.plg_financial_invoices f WHERE f.id = v_doc.invoice_id) THEN
4053
+ -- Same NF-e: the key this plugin writes, or the V1 conciliation's text for the same supplier.
4054
+ SELECT count(*), min(f.id::text)::uuid INTO v_matches, v_invoice
4055
+ FROM public.plg_financial_invoices f
4056
+ WHERE f.tenant_id = p_tenant AND f.direction = 'payable' AND f.cancelled_at IS NULL
4057
+ AND (f.idempotency_key = 'danfe:' || v_doc.access_key
4058
+ OR (v_doc.supplier_party_id IS NOT NULL
4059
+ AND f.counterparty_person_id = v_doc.supplier_party_id
4060
+ AND f.description ~ ('NF-e 0*' || ltrim(v_doc.nfe_number, '0') || '(\\D|$)')))
4061
+ AND NOT EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_documents o
4062
+ WHERE o.tenant_id = p_tenant AND o.invoice_id = f.id AND o.id <> v_doc.id);
4063
+ IF v_matches = 1 THEN
4064
+ v_relinked := true;
4065
+ ELSE
4066
+ v_invoice := NULL;
4067
+ v_released := true;
4068
+ END IF;
4069
+ UPDATE public.plg_fiscalbr_inbox_documents SET invoice_id = v_invoice, updated_at = now()
4070
+ WHERE id = v_doc.id;
4071
+ END IF;
4072
+
4073
+ UPDATE public.plg_fiscalbr_inbox_items i SET stock_movement_id = NULL, updated_at = now()
4074
+ WHERE i.tenant_id = p_tenant AND i.inbox_document_id = v_doc.id
4075
+ AND i.stock_movement_id IS NOT NULL
4076
+ AND NOT EXISTS (SELECT 1 FROM public.plg_inventory_stock_movements m WHERE m.id = i.stock_movement_id);
4077
+ GET DIAGNOSTICS v_dead_mv = ROW_COUNT;
4078
+
4079
+ IF v_dead_mv > 0 AND v_doc.stocked_at IS NOT NULL
4080
+ AND v_doc.inventory_operation_id IS NULL
4081
+ AND NOT EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_items i
4082
+ WHERE i.tenant_id = p_tenant AND i.inbox_document_id = v_doc.id
4083
+ AND i.stock_movement_id IS NOT NULL) THEN
4084
+ UPDATE public.plg_fiscalbr_inbox_documents SET stocked_at = NULL, updated_at = now()
4085
+ WHERE id = v_doc.id;
4086
+ v_unstocked := true;
4087
+ END IF;
4088
+
4089
+ RETURN jsonb_build_object('document_id', v_doc.id,
4090
+ 'changed', v_relinked OR v_released OR v_dead_mv > 0,
4091
+ 'relinked', v_relinked, 'released', v_released,
4092
+ 'dead_movements', v_dead_mv, 'unstocked', v_unstocked);
4093
+ END;
4094
+ $function$;
4095
+
4096
+ REVOKE ALL ON FUNCTION public.plg_fiscalbr_heal_note_links(uuid, uuid) FROM PUBLIC, anon, authenticated;
4097
+
4098
+ -- The screen heals the note it opens, so a future re-copy cannot strand one again.
4099
+ CREATE OR REPLACE FUNCTION public.fiscalbr_heal_inbox_note(p_document_id uuid)
4100
+ RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
4101
+ DECLARE
4102
+ v_tenant uuid := public.plg_financial_require_tenant();
4103
+ v_unit uuid;
4104
+ BEGIN
4105
+ SELECT unit_id INTO v_unit FROM public.plg_fiscalbr_inbox_documents
4106
+ WHERE tenant_id = v_tenant AND id = p_document_id;
4107
+ IF NOT FOUND THEN
4108
+ RAISE EXCEPTION 'fiscalbr: nota n\xE3o encontrada' USING ERRCODE = 'P0002';
4109
+ END IF;
4110
+ PERFORM public.plg_financial_require_permission('financial.create', v_unit);
4111
+ RETURN public.plg_fiscalbr_heal_note_links(v_tenant, p_document_id);
4112
+ END;
4113
+ $function$;
4114
+
4115
+ REVOKE ALL ON FUNCTION public.fiscalbr_heal_inbox_note(uuid) FROM PUBLIC;
4116
+ GRANT EXECUTE ON FUNCTION public.fiscalbr_heal_inbox_note(uuid) TO authenticated;
4117
+
4118
+ -- Heal what is already stranded.
4119
+ DO $backfill$
4120
+ DECLARE r record;
4121
+ BEGIN
4122
+ FOR r IN
4123
+ SELECT DISTINCT d.tenant_id, d.id
4124
+ FROM public.plg_fiscalbr_inbox_documents d
4125
+ LEFT JOIN public.plg_fiscalbr_inbox_items i
4126
+ ON i.tenant_id = d.tenant_id AND i.inbox_document_id = d.id
4127
+ WHERE (d.invoice_id IS NOT NULL
4128
+ AND NOT EXISTS (SELECT 1 FROM public.plg_financial_invoices f WHERE f.id = d.invoice_id))
4129
+ OR (i.stock_movement_id IS NOT NULL
4130
+ AND NOT EXISTS (SELECT 1 FROM public.plg_inventory_stock_movements m WHERE m.id = i.stock_movement_id))
4131
+ LOOP
4132
+ PERFORM public.plg_fiscalbr_heal_note_links(r.tenant_id, r.id);
4133
+ END LOOP;
4134
+ END
4135
+ $backfill$;
4136
+ `;
4137
+ var MIGRATION_025_A_CONTA_DA_NOTA_FECHA_COM_O_TOTAL = `-- ---------------------------------------------------------------------------
4138
+ -- The payable a note opens lists products as products, and adds up to its total.
4139
+ -- ---------------------------------------------------------------------------
4140
+ -- Two defects in the lines fiscalbr_accept_inbox_document wrote:
4141
+ -- 1. \`itemKind\` carried the fiscal entry type ('insumo', 'revenda'\u2026), which the
4142
+ -- financial screens do not know, so a linked product showed as "Insumo"
4143
+ -- with no product. The kind is 'product' when the line has one; the entry
4144
+ -- type moves to \`entryType\`, and product/chart account go to their columns.
4145
+ -- 2. Only the product lines were written, so freight, insurance, other
4146
+ -- expenses, IPI, ICMS-ST and discount were in the total but in no line
4147
+ -- (NF-e 1430116: R$ 480,47 owed, R$ 478,47 listed). Each becomes a line,
4148
+ -- and whatever still differs from the instalments becomes one more.
4149
+ -- ---------------------------------------------------------------------------
4150
+
4151
+ CREATE OR REPLACE FUNCTION public.plg_fiscalbr_note_charge_lines(
4152
+ p_tenant uuid, p_document_id uuid, p_invoice_id uuid, p_total numeric)
4153
+ RETURNS integer LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$
4154
+ DECLARE
4155
+ v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;
4156
+ v_sum numeric;
4157
+ v_rest numeric;
4158
+ v_n integer := 0;
4159
+ v_c record;
4160
+ BEGIN
4161
+ SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents
4162
+ WHERE tenant_id = p_tenant AND id = p_document_id;
4163
+ IF NOT FOUND OR p_invoice_id IS NULL THEN RETURN 0; END IF;
4164
+ IF EXISTS (SELECT 1 FROM public.items
4165
+ WHERE tenant_id = p_tenant AND invoice_id = p_invoice_id
4166
+ AND metadata ? 'noteCharge') THEN
4167
+ RETURN 0;
4168
+ END IF;
4169
+ -- Only a payable that already lists the note's products: a V1 copy with no lines
4170
+ -- would get its whole total as one "difference" line.
4171
+ IF NOT EXISTS (SELECT 1 FROM public.items
4172
+ WHERE tenant_id = p_tenant AND invoice_id = p_invoice_id
4173
+ AND metadata ? 'cfop') THEN
4174
+ RETURN 0;
4175
+ END IF;
4176
+
4177
+ FOR v_c IN
4178
+ SELECT * FROM (VALUES
4179
+ (1, 'freight', 'Frete', round(coalesce(v_doc.total_freight, 0), 2)),
4180
+ (2, 'insurance', 'Seguro', round(coalesce(v_doc.total_insurance, 0), 2)),
4181
+ (3, 'other', 'Outras despesas da nota', round(coalesce(v_doc.total_other, 0), 2)),
4182
+ (4, 'ipi', 'IPI', round(coalesce(v_doc.total_ipi_value, 0), 2)),
4183
+ (5, 'icms_st', 'ICMS-ST', round(coalesce(v_doc.total_icms_st_value, 0), 2)),
4184
+ (6, 'discount', 'Desconto da nota', -round(coalesce(v_doc.total_discount, 0), 2))
4185
+ ) AS c(ord, code, label, amount)
4186
+ WHERE amount <> 0
4187
+ ORDER BY ord
4188
+ LOOP
4189
+ INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)
4190
+ VALUES (p_tenant, p_invoice_id, v_c.label, 1, v_c.amount, v_c.amount, 1000 + v_c.ord,
4191
+ jsonb_build_object('itemKind', 'other', 'noteCharge', v_c.code));
4192
+ v_n := v_n + 1;
4193
+ END LOOP;
4194
+
4195
+ SELECT coalesce(sum(total), 0) INTO v_sum FROM public.items
4196
+ WHERE tenant_id = p_tenant AND invoice_id = p_invoice_id AND cancelled_at IS NULL;
4197
+ v_rest := round(coalesce(p_total, 0) - v_sum, 2);
4198
+ IF v_rest <> 0 THEN
4199
+ INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)
4200
+ VALUES (p_tenant, p_invoice_id, 'Diferen\xE7a entre a nota e as parcelas', 1, v_rest, v_rest, 1099,
4201
+ jsonb_build_object('itemKind', 'other', 'noteCharge', 'instalment_difference'));
4202
+ v_n := v_n + 1;
4203
+ END IF;
4204
+ RETURN v_n;
4205
+ END;
4206
+ $function$;
4207
+
4208
+ REVOKE ALL ON FUNCTION public.plg_fiscalbr_note_charge_lines(uuid, uuid, uuid, numeric) FROM PUBLIC, anon, authenticated;
4209
+
4210
+ DO $m$
4211
+ DECLARE
4212
+ v_def text;
4213
+ v_start integer;
4214
+ v_end integer;
4215
+ v_marker text := 'INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)';
4216
+ v_close text := 'ON CONFLICT DO NOTHING;';
4217
+ v_new text := $q$INSERT INTO public.items (tenant_id, invoice_id, product_id, chart_account_id, name, quantity, unit_price, total, sort_order, metadata)
4218
+ SELECT v_tenant, v_invoice, i.product_id, coalesce(i.chart_account_id, v_doc.default_chart_account_id),
4219
+ i.description, i.quantity_commercial, i.unit_price_commercial,
4220
+ round(coalesce(i.total_price, 0), 2), i.item_number,
4221
+ jsonb_strip_nulls(jsonb_build_object(
4222
+ 'itemKind', CASE WHEN i.product_id IS NOT NULL THEN 'product' ELSE 'other' END,
4223
+ 'referenceId', i.product_id, 'entryType', i.entry_type,
4224
+ 'chartAccountId', coalesce(i.chart_account_id, v_doc.default_chart_account_id),
4225
+ 'ncm', i.ncm, 'cfop', i.cfop,
4226
+ 'supplierProductCode', i.supplier_product_code))
4227
+ FROM public.plg_fiscalbr_inbox_items i
4228
+ WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id
4229
+ ORDER BY i.item_number
4230
+ ON CONFLICT DO NOTHING;
4231
+ PERFORM public.plg_fiscalbr_note_charge_lines(v_tenant, v_doc.id, v_invoice, v_total);$q$;
4232
+ BEGIN
4233
+ SELECT pg_get_functiondef(p.oid) INTO v_def
4234
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
4235
+ WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_accept_inbox_document';
4236
+ IF v_def IS NULL THEN
4237
+ RAISE EXCEPTION 'fiscalbr/025: fiscalbr_accept_inbox_document does not exist';
4238
+ END IF;
4239
+ IF position('plg_fiscalbr_note_charge_lines' in v_def) > 0 THEN RETURN; END IF;
4240
+
4241
+ v_start := position(v_marker in v_def);
4242
+ IF v_start = 0 THEN
4243
+ RAISE EXCEPTION 'fiscalbr/025: unexpected body of fiscalbr_accept_inbox_document \u2014 check by hand';
4244
+ END IF;
4245
+ v_end := v_start + position(v_close in substr(v_def, v_start)) - 1 + length(v_close);
4246
+ EXECUTE substr(v_def, 1, v_start - 1) || v_new || substr(v_def, v_end);
4247
+ END
4248
+ $m$;
4249
+
4250
+ -- The payables already opened by notes: same kind/product fix, and the missing charge lines.
4251
+ ALTER TABLE public.items DISABLE TRIGGER items_invoice_line_immutable;
4252
+
4253
+ UPDATE public.items it
4254
+ SET product_id = coalesce(it.product_id, (it.metadata->>'referenceId')::uuid),
4255
+ metadata = jsonb_set(it.metadata || jsonb_build_object('entryType', it.metadata->>'itemKind'),
4256
+ '{itemKind}', '"product"'::jsonb, true),
4257
+ updated_at = now()
4258
+ FROM public.plg_financial_invoices f
4259
+ WHERE f.id = it.invoice_id
4260
+ AND f.metadata ? 'danfe'
4261
+ AND it.metadata->>'itemKind' IN ('insumo','revenda','patrimonio','uso_consumo','despesa','servico')
4262
+ AND it.metadata->>'referenceId' IS NOT NULL
4263
+ AND EXISTS (SELECT 1 FROM public.products p WHERE p.id = (it.metadata->>'referenceId')::uuid);
4264
+
4265
+ UPDATE public.items it
4266
+ SET metadata = jsonb_set(it.metadata || jsonb_build_object('entryType', it.metadata->>'itemKind'),
4267
+ '{itemKind}', '"other"'::jsonb, true),
4268
+ updated_at = now()
4269
+ FROM public.plg_financial_invoices f
4270
+ WHERE f.id = it.invoice_id
4271
+ AND f.metadata ? 'danfe'
4272
+ AND it.metadata->>'itemKind' IN ('insumo','revenda','patrimonio','uso_consumo','despesa','servico');
4273
+
4274
+ ALTER TABLE public.items ENABLE TRIGGER items_invoice_line_immutable;
4275
+
4276
+ DO $backfill$
4277
+ DECLARE r record;
4278
+ BEGIN
4279
+ FOR r IN
4280
+ SELECT d.tenant_id, d.id, f.id AS invoice_id, f.total
4281
+ FROM public.plg_fiscalbr_inbox_documents d
4282
+ JOIN public.plg_financial_invoices f ON f.id = d.invoice_id AND f.tenant_id = d.tenant_id
4283
+ WHERE f.metadata ? 'danfe' AND f.cancelled_at IS NULL
4284
+ LOOP
4285
+ PERFORM public.plg_fiscalbr_note_charge_lines(r.tenant_id, r.id, r.invoice_id, r.total);
4286
+ END LOOP;
4287
+ END
4288
+ $backfill$;
4289
+ `;
4290
+ var MIGRATION_026_O_RECEBIMENTO_PARADO_DIZ_POR_QUE = `-- ---------------------------------------------------------------------------
4291
+ -- When notes stop arriving, the emitter row says why.
4292
+ -- ---------------------------------------------------------------------------
4293
+ -- The SEFAZ pull fails per CNPJ (expired A1 certificate, provider refusal) and
4294
+ -- the V1 records only the last failure (integration_sync_state.last_error). The
4295
+ -- V2 kept nothing, so a tenant could go two weeks without a note and no screen
4296
+ -- would say so. These two columns hold that last failure; the notes list warns
4297
+ -- from them and from a stale dfe_last_sync_at.
4298
+ -- ---------------------------------------------------------------------------
4299
+
4300
+ ALTER TABLE public.plg_fiscalbr_emitter_settings
4301
+ ADD COLUMN IF NOT EXISTS dfe_last_error text,
4302
+ ADD COLUMN IF NOT EXISTS dfe_last_error_at timestamptz;
4303
+
4304
+ COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.dfe_last_error IS
4305
+ 'V1: integration_sync_state.last_error (source = dfe_inbox) \u2014 last failure pulling notes from SEFAZ; null when the last pull worked.';
4306
+ COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.dfe_last_error_at IS
4307
+ 'V1: integration_sync_state.last_synced_at when last_status = error.';
4308
+
4309
+ GRANT SELECT (dfe_last_error, dfe_last_error_at) ON public.plg_fiscalbr_emitter_settings TO authenticated;
4310
+ `;
4020
4311
  var MIGRATIONS = [
4021
4312
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
4022
4313
  { id: "001_preserve_terminal_results_and_recover_leases", sql: MIGRATION_001_PRESERVE_TERMINAL_RESULTS_AND_RECOVER_LEASES },
@@ -4041,7 +4332,10 @@ var MIGRATIONS = [
4041
4332
  { id: "020_a_nota_acha_o_fornecedor_pelo_cnpj", sql: MIGRATION_020_A_NOTA_ACHA_O_FORNECEDOR_PELO_CNPJ },
4042
4333
  { id: "021_uso_e_consumo_entra_no_estoque", sql: MIGRATION_021_USO_E_CONSUMO_ENTRA_NO_ESTOQUE },
4043
4334
  { id: "022_a_nota_do_v1_traz_o_xml", sql: MIGRATION_022_A_NOTA_DO_V1_TRAZ_O_XML },
4044
- { id: "023_a_nota_aceita_sem_linhas_recebe_as_linhas", sql: MIGRATION_023_A_NOTA_ACEITA_SEM_LINHAS_RECEBE_AS_LINHAS }
4335
+ { id: "023_a_nota_aceita_sem_linhas_recebe_as_linhas", sql: MIGRATION_023_A_NOTA_ACEITA_SEM_LINHAS_RECEBE_AS_LINHAS },
4336
+ { id: "024_a_nota_nao_aponta_para_conta_que_sumiu", sql: MIGRATION_024_A_NOTA_NAO_APONTA_PARA_CONTA_QUE_SUMIU },
4337
+ { id: "025_a_conta_da_nota_fecha_com_o_total", sql: MIGRATION_025_A_CONTA_DA_NOTA_FECHA_COM_O_TOTAL },
4338
+ { id: "026_o_recebimento_parado_diz_por_que", sql: MIGRATION_026_O_RECEBIMENTO_PARADO_DIZ_POR_QUE }
4045
4339
  ];
4046
4340
  var taxGroupEntity = {
4047
4341
  name: "Grupo fiscal",
@@ -4501,7 +4795,7 @@ async function listInboxNotes(options = {}) {
4501
4795
  if (filter === "stocked") query = query.not("stocked_at", "is", null);
4502
4796
  if (filter === "pending") query = query.is("stocked_at", null);
4503
4797
  if (filter === "rejected") query = query.eq("status", "rejected");
4504
- if (filter === "to_manifest") query = query.not("manifestation_status", "eq", "confirmada");
4798
+ if (filter === "manifest_error") query = query.eq("manifestation_status", "erro");
4505
4799
  if (search?.trim()) {
4506
4800
  const term = search.trim();
4507
4801
  query = query.or(
@@ -4625,6 +4919,18 @@ async function fillInboxLines(documentId, xml) {
4625
4919
  if (error) throw error;
4626
4920
  return Number(data?.items ?? 0);
4627
4921
  }
4922
+ async function healInboxNote(documentId) {
4923
+ const { data, error } = await sb().rpc("fiscalbr_heal_inbox_note", { p_document_id: documentId });
4924
+ if (error) throw error;
4925
+ const r = data ?? {};
4926
+ return { changed: Boolean(r.changed), released: Boolean(r.released) };
4927
+ }
4928
+ async function getNotePayable(invoiceId) {
4929
+ const { data, error } = await sb().from("plg_financial_invoices").select("id, number, total, cancelled_at").eq("id", invoiceId).maybeSingle();
4930
+ if (error) throw error;
4931
+ if (!data) return null;
4932
+ return { id: data.id, number: data.number ?? null, total: Number(data.total ?? 0), cancelledAt: data.cancelled_at ?? null };
4933
+ }
4628
4934
  var PullByKeyError = class extends Error {
4629
4935
  constructor(message, code, options) {
4630
4936
  super(message);
@@ -4949,6 +5255,61 @@ function parseBoleto(raw) {
4949
5255
  }
4950
5256
  return { amount: cents > 0 ? cents / 100 : null, dueDate };
4951
5257
  }
5258
+ var STALE_AFTER_MS = 24 * 60 * 60 * 1e3;
5259
+ function expiryFromError(error) {
5260
+ const m = error?.match(/vencido em:?\s*(\d{2})\/(\d{2})\/(\d{4})/i);
5261
+ return m ? `${m[3]}-${m[2]}-${m[1]}` : null;
5262
+ }
5263
+ async function getInboxIntakeHealth() {
5264
+ const { data, error } = await sb().from("plg_fiscalbr_emitter_settings").select("legal_entity_id, dfe_sync_enabled, dfe_last_sync_at, dfe_last_error, certificate_expires_at").eq("dfe_sync_enabled", true);
5265
+ if (error) throw error;
5266
+ const now = Date.now();
5267
+ const stalled = (data ?? []).filter((r) => {
5268
+ const expired = r.certificate_expires_at && Date.parse(r.certificate_expires_at) < now;
5269
+ const stale = !r.dfe_last_sync_at || now - Date.parse(r.dfe_last_sync_at) > STALE_AFTER_MS;
5270
+ return expired || stale || Boolean(r.dfe_last_error);
5271
+ });
5272
+ if (stalled.length === 0) return null;
5273
+ const since = stalled.map((r) => r.dfe_last_sync_at).filter(Boolean).sort()[0] ?? null;
5274
+ const withError = stalled.find((r) => r.dfe_last_error);
5275
+ const certificateExpiredAt = stalled.map((r) => r.certificate_expires_at && Date.parse(r.certificate_expires_at) < now ? r.certificate_expires_at : expiryFromError(r.dfe_last_error)).find(Boolean) ?? null;
5276
+ const target = stalled.find((r) => r.certificate_expires_at && Date.parse(r.certificate_expires_at) < now) ?? withError ?? stalled[0];
5277
+ const { data: unit } = await sb().from("v_unit_company_legal_entities").select("unit_id").eq("company_legal_entity_id", target.legal_entity_id).limit(1).maybeSingle();
5278
+ return { stalled: stalled.length, since, certificateExpiredAt, error: withError?.dfe_last_error ?? null, unitId: unit?.unit_id ?? null };
5279
+ }
5280
+ async function syncNewNotes(max = 25) {
5281
+ const { data: newest } = await sb().from("plg_fiscalbr_inbox_documents").select("issue_date").order("issue_date", { ascending: false }).limit(1).maybeSingle();
5282
+ const since = newest?.issue_date ? new Date(Date.parse(newest.issue_date) - 2 * 864e5).toISOString().slice(0, 10) : void 0;
5283
+ const { data, error } = await sb().functions.invoke("fiscalbr-dfe-pull", {
5284
+ body: { action: "list", since, tenantId: getActiveTenantId() ?? null }
5285
+ });
5286
+ if (error) {
5287
+ let body = null;
5288
+ try {
5289
+ body = await error?.context?.json?.();
5290
+ } catch {
5291
+ }
5292
+ throw new PullByKeyError(body?.error || error.message || "Falha ao falar com a SEFAZ.", body?.code);
5293
+ }
5294
+ const listed = data?.notes ?? [];
5295
+ if (listed.length === 0) return { added: 0, waiting: 0, failed: (data?.failures ?? []).length };
5296
+ const keys = [...new Set(listed.map((n) => n.key))];
5297
+ const { data: known } = await sb().from("plg_fiscalbr_inbox_documents").select("access_key").in("access_key", keys);
5298
+ const have = new Set((known ?? []).map((r) => r.access_key));
5299
+ const missing = listed.filter((n, i) => !have.has(n.key) && listed.findIndex((m) => m.key === n.key) === i).slice(0, max);
5300
+ const result = { added: 0, waiting: 0, failed: (data?.failures ?? []).length };
5301
+ for (const n of missing) {
5302
+ try {
5303
+ const r = await pullNoteByKey(n.key, { legalEntityId: n.legalEntityId });
5304
+ if (r.status === "staged") result.added++;
5305
+ else result.waiting++;
5306
+ } catch (e) {
5307
+ if (e instanceof PullByKeyError && (e.code === "summary_only" || e.code === "throttled")) result.waiting++;
5308
+ else result.failed++;
5309
+ }
5310
+ }
5311
+ return result;
5312
+ }
4952
5313
  var formatCnpj = (d) => `${d.slice(0, 2)}.${d.slice(2, 5)}.${d.slice(5, 8)}/${d.slice(8, 12)}-${d.slice(12)}`;
4953
5314
  var groupKey = (digits2) => digits2.replace(/(\d{4})(?=\d)/g, "$1 ");
4954
5315
  function PullByKeyDialog({ open, onOpenChange, unitId, initialKey, onPulled }) {
@@ -5063,16 +5424,17 @@ var cnpj = (digits2) => {
5063
5424
  if (d.length !== 14) return digits2 ?? "\u2014";
5064
5425
  return `${d.slice(0, 2)}.${d.slice(2, 5)}.${d.slice(5, 8)}/${d.slice(8, 12)}-${d.slice(12)}`;
5065
5426
  };
5066
- var CAMPO = "h-9 w-full rounded-md border border-input bg-background px-2 text-sm";
5427
+ var CAMPO = "h-9 w-full rounded-input border border-input bg-background px-3 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2";
5067
5428
  var STATE_OPTIONS = [
5068
5429
  { value: "all", label: "Todos" },
5069
5430
  { value: "pending", label: "Estoque pendente" },
5070
5431
  { value: "stocked", label: "Estocado" },
5071
- { value: "to_manifest", label: "A manifestar" },
5432
+ { value: "manifest_error", label: "Manifesta\xE7\xE3o com erro" },
5072
5433
  { value: "rejected", label: "Recusada" }
5073
5434
  ];
5074
5435
  var isoDay = (d) => d.toISOString().slice(0, 10);
5075
- function InboundNotesView({ onOpenNote, onOpenManifest }) {
5436
+ var SYNC_KEY = "fiscalbr.inbox.lastSefazSync";
5437
+ function InboundNotesView({ onOpenNote }) {
5076
5438
  const [notes, setNotes] = useState([]);
5077
5439
  const [loading, setLoading] = useState(true);
5078
5440
  const [search, setSearch] = useState("");
@@ -5116,6 +5478,44 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
5116
5478
  useEffect(() => {
5117
5479
  void reload();
5118
5480
  }, [reload]);
5481
+ const [intake, setIntake] = useState(null);
5482
+ const [syncing, setSyncing] = useState(false);
5483
+ const [syncNote, setSyncNote] = useState(null);
5484
+ const refreshIntake = useCallback(() => {
5485
+ getInboxIntakeHealth().then(setIntake).catch(() => setIntake(null));
5486
+ }, []);
5487
+ const syncNow = useCallback(async (quiet) => {
5488
+ setSyncing(true);
5489
+ if (!quiet) setSyncNote(null);
5490
+ try {
5491
+ const r = await syncNewNotes();
5492
+ try {
5493
+ localStorage.setItem(SYNC_KEY, String(Date.now()));
5494
+ } catch {
5495
+ }
5496
+ if (r.added) void reload();
5497
+ const parts = [
5498
+ r.added ? `${r.added} ${r.added === 1 ? "nota nova" : "notas novas"}` : null,
5499
+ r.waiting ? `${r.waiting} ainda sem o XML completo na SEFAZ` : null,
5500
+ r.failed ? `${r.failed} com erro` : null
5501
+ ].filter(Boolean);
5502
+ if (!quiet || parts.length) setSyncNote(parts.length ? `SEFAZ: ${parts.join(" \xB7 ")}.` : "SEFAZ: nenhuma nota nova.");
5503
+ } catch (e) {
5504
+ if (!quiet) setSyncNote(e instanceof Error ? e.message : String(e));
5505
+ } finally {
5506
+ setSyncing(false);
5507
+ refreshIntake();
5508
+ }
5509
+ }, [reload, refreshIntake]);
5510
+ useEffect(() => {
5511
+ refreshIntake();
5512
+ let last = 0;
5513
+ try {
5514
+ last = Number(localStorage.getItem(SYNC_KEY) ?? 0);
5515
+ } catch {
5516
+ }
5517
+ if (Date.now() - last > 30 * 6e4) void syncNow(true);
5518
+ }, []);
5119
5519
  const ingest = useCallback(async (files) => {
5120
5520
  const xmls = files.filter((f) => /\.xml$/i.test(f.name));
5121
5521
  if (xmls.length === 0) {
@@ -5300,7 +5700,7 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
5300
5700
  ] }) : /* @__PURE__ */ jsxs("div", { children: [
5301
5701
  /* @__PURE__ */ jsx(Inbox, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
5302
5702
  /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota de entrada" }),
5303
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "As notas chegam da SEFAZ pela fila de manifesta\xE7\xE3o, ou voc\xEA arrasta o XML aqui." })
5703
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "As notas chegam da SEFAZ sozinhas, ou voc\xEA arrasta o XML aqui." })
5304
5704
  ] });
5305
5705
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
5306
5706
  /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(
@@ -5318,6 +5718,16 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
5318
5718
  disabled: busy,
5319
5719
  onSelect: () => fileInput.current?.click()
5320
5720
  },
5721
+ {
5722
+ id: "sync-sefaz",
5723
+ label: syncing ? "Buscando na SEFAZ\u2026" : "Buscar notas novas",
5724
+ icon: syncing ? Loader2 : RefreshCw,
5725
+ disabled: syncing,
5726
+ testId: "sync-sefaz",
5727
+ onSelect: () => {
5728
+ void syncNow(false);
5729
+ }
5730
+ },
5321
5731
  {
5322
5732
  id: "pull-by-key",
5323
5733
  label: "Puxar por chave",
@@ -5325,11 +5735,29 @@ function InboundNotesView({ onOpenNote, onOpenManifest }) {
5325
5735
  creates: true,
5326
5736
  testId: "pull-by-key",
5327
5737
  onSelect: () => setPullOpen(true)
5328
- },
5329
- ...onOpenManifest ? [{ id: "manifest", label: "Fila da SEFAZ", icon: Inbox, onSelect: onOpenManifest }] : []
5738
+ }
5330
5739
  ]
5331
5740
  }
5332
5741
  ) }),
5742
+ syncNote && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", "data-testid": "sync-note", children: syncNote }),
5743
+ intake && /* @__PURE__ */ jsxs(Card, { className: "border-warning bg-warning-soft p-4", "data-testid": "intake-stalled", children: [
5744
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-warning-soft-foreground", children: intake.certificateExpiredAt ? `As notas da SEFAZ n\xE3o est\xE3o chegando: o certificado digital venceu em ${day(intake.certificateExpiredAt)}.` : `As notas da SEFAZ n\xE3o est\xE3o chegando${intake.since ? ` desde ${day(intake.since)}` : ""}.` }),
5745
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-warning-soft-foreground", children: [
5746
+ intake.certificateExpiredAt ? "Renove o certificado A1 do CNPJ e envie o arquivo novo. As notas que ficaram para tr\xE1s chegam sozinhas depois disso." : intake.error ?? "A busca autom\xE1tica parou. Enquanto isso, d\xE1 para importar o XML ou puxar a nota pela chave.",
5747
+ intake.stalled > 1 ? ` (${intake.stalled} CNPJs afetados)` : ""
5748
+ ] }),
5749
+ intake.unitId && (intake.certificateExpiredAt || intake.error) && /* @__PURE__ */ jsx(
5750
+ Button,
5751
+ {
5752
+ size: "sm",
5753
+ variant: "outline",
5754
+ className: "mt-3",
5755
+ "data-testid": "intake-update-certificate",
5756
+ onClick: () => adminNavigateTo(`/settings/locations/${intake.unitId}/fields:tax`),
5757
+ children: "Atualizar certificado"
5758
+ }
5759
+ )
5760
+ ] }),
5333
5761
  /* @__PURE__ */ jsxs(
5334
5762
  Card,
5335
5763
  {
@@ -5741,6 +6169,8 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5741
6169
  const [suggesting, setSuggesting] = useState(false);
5742
6170
  const [suggestionNote, setSuggestionNote] = useState(null);
5743
6171
  const [pullOpen, setPullOpen] = useState(false);
6172
+ const [payable, setPayable] = useState(void 0);
6173
+ const [payableLost, setPayableLost] = useState(false);
5744
6174
  const load = useCallback(async () => {
5745
6175
  setLoading(true);
5746
6176
  try {
@@ -5749,6 +6179,12 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
5749
6179
  const linked = await linkInboxSupplier(noteId).catch(() => null);
5750
6180
  if (linked?.supplierPartyId) detail = { ...detail, supplierPartyId: linked.supplierPartyId };
5751
6181
  }
6182
+ if (detail && (detail.invoiceId || detail.stockedAt)) {
6183
+ const healed = await healInboxNote(noteId).catch(() => null);
6184
+ if (healed?.released) setPayableLost(true);
6185
+ if (healed?.changed) detail = await getInboxNote(noteId) ?? detail;
6186
+ }
6187
+ setPayable(detail?.invoiceId ? await getNotePayable(detail.invoiceId).catch(() => void 0) : void 0);
5752
6188
  if (detail && detail.items.length === 0 && detail.hasXml && detail.status !== "rejected") {
5753
6189
  const xml = await getInboxNoteXml(noteId).catch(() => null);
5754
6190
  if (xml) {
@@ -6161,6 +6597,32 @@ function NoteReconcileView({ noteId, onBack, onOpenInvoice, chrome = "page" }) {
6161
6597
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-success-soft-foreground", children: accepted.alreadyAccepted ? "Esta nota j\xE1 havia sido aceita \u2014 nada foi lan\xE7ado de novo." : `Aceita: ${accepted.stockLines} ${accepted.stockLines === 1 ? "linha" : "linhas"} de estoque e um t\xEDtulo de ${money3(accepted.invoiceTotal)} em ${accepted.instalments} ${accepted.instalments === 1 ? "parcela" : "parcelas"}.` }),
6162
6598
  accepted.invoiceId && onOpenInvoice && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => onOpenInvoice(accepted.invoiceId), children: "Abrir a conta a pagar" })
6163
6599
  ] }),
6600
+ payableLost && !readOnly && /* @__PURE__ */ jsxs(Card, { className: "border-warning bg-warning-soft p-4", "data-testid": "note-payable-lost", children: [
6601
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-warning-soft-foreground", children: "A conta a pagar e a entrada no estoque desta nota foram apagadas fora daqui." }),
6602
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-warning-soft-foreground", children: "Confira a concilia\xE7\xE3o e clique em Aceitar para lan\xE7ar as duas de novo." })
6603
+ ] }),
6604
+ !accepted && /* @__PURE__ */ jsxs(Card, { className: "p-4", "data-testid": "note-postings", children: [
6605
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Lan\xE7amentos desta nota" }),
6606
+ /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid gap-x-6 gap-y-3 text-sm sm:grid-cols-2", children: [
6607
+ /* @__PURE__ */ jsxs("div", { children: [
6608
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: "Conta a pagar" }),
6609
+ /* @__PURE__ */ jsxs("dd", { className: "mt-0.5 flex flex-wrap items-center gap-2", children: [
6610
+ !note.invoiceId && /* @__PURE__ */ jsx("span", { className: payableLost ? "text-destructive" : "text-muted-foreground", children: payableLost ? "N\xE3o existe mais \u2014 foi apagada; aceite a nota para lan\xE7ar de novo" : readOnly ? "Nenhuma \u2014 esta nota n\xE3o abriu conta a pagar" : "Ainda n\xE3o lan\xE7ada \u2014 nasce quando a nota for aceita" }),
6611
+ note.invoiceId && payable === null && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "N\xE3o encontrada \u2014 a conta foi apagada" }),
6612
+ payable && /* @__PURE__ */ jsxs("span", { "data-testid": "note-payable-link", children: [
6613
+ onOpenInvoice ? /* @__PURE__ */ jsx(InlineLink, { onClick: () => onOpenInvoice(payable.id), children: payable.number ?? "Conta a pagar" }) : payable.number ?? "Conta a pagar",
6614
+ " \xB7 ",
6615
+ money3(payable.total),
6616
+ payable.cancelledAt && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " \xB7 cancelada" })
6617
+ ] })
6618
+ ] })
6619
+ ] }),
6620
+ /* @__PURE__ */ jsxs("div", { children: [
6621
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: "Estoque" }),
6622
+ /* @__PURE__ */ jsx("dd", { className: "mt-0.5", children: note.stockedAt ? `Entrada em ${day3(note.stockedAt)}` : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: readOnly ? "Sem entrada no estoque" : "Ainda sem entrada \u2014 entra quando a nota for aceita" }) })
6623
+ ] })
6624
+ ] })
6625
+ ] }),
6164
6626
  /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
6165
6627
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Situa\xE7\xE3o fiscal" }),
6166
6628
  /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid gap-x-6 gap-y-2 text-sm sm:grid-cols-2 lg:grid-cols-3", children: [
@@ -6716,132 +7178,20 @@ function Field({ label, value, mono, small }) {
6716
7178
  /* @__PURE__ */ jsx("dd", { className: `${mono ? "tabular-nums" : ""} ${small ? "text-xs" : "text-sm"}`, children: value })
6717
7179
  ] });
6718
7180
  }
6719
- var money4 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(v || 0);
6720
- var day4 = (iso) => iso ? iso.slice(0, 10).split("-").reverse().join("/") : "\u2014";
6721
- var MANIFEST_TONE = {
6722
- confirmada: "bg-success-soft text-success-soft-foreground",
6723
- processando: "bg-info-soft text-info-soft-foreground",
6724
- erro: "bg-destructive/10 text-destructive"
6725
- };
6726
- function ManifestView({ onOpenNote, onBack }) {
6727
- const [notes, setNotes] = useState([]);
6728
- const [loading, setLoading] = useState(true);
6729
- const [tab, setTab] = useState("pendentes");
6730
- const [pullOpen, setPullOpen] = useState(false);
6731
- const reload = useCallback(async () => {
6732
- setLoading(true);
6733
- try {
6734
- setNotes(await listInboxNotes({ filter: "all", limit: 300 }));
6735
- } finally {
6736
- setLoading(false);
6737
- }
6738
- }, []);
6739
- useEffect(() => {
6740
- void reload();
6741
- }, [reload]);
6742
- const shown = useMemo(() => notes.filter((n) => {
6743
- const status = n.manifestationStatus ?? "";
6744
- if (tab === "todas") return true;
6745
- if (tab === "confirmadas") return status === "confirmada";
6746
- if (tab === "erro") return status === "erro";
6747
- return status !== "confirmada";
6748
- }), [notes, tab]);
6749
- const pending = notes.filter((n) => (n.manifestationStatus ?? "") !== "confirmada").length;
6750
- const filters = useMemo(() => [{
6751
- id: "manifestacao",
6752
- label: "Manifesta\xE7\xE3o",
6753
- allLabel: "Todas",
6754
- value: tab === "todas" ? void 0 : tab,
6755
- onChange: (v) => setTab(v ?? "todas"),
6756
- options: [
6757
- { value: "pendentes", label: "Pendentes" },
6758
- { value: "confirmadas", label: "Confirmadas" },
6759
- { value: "erro", label: "Com erro" }
6760
- ]
6761
- }], [tab]);
6762
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
6763
- onBack && /* @__PURE__ */ jsx(SubpageHeader, { title: "Fila da SEFAZ", onBack, parentLabel: "Manifesto de Notas" }),
6764
- /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
6765
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
6766
- /* @__PURE__ */ jsx(ShieldCheck, { className: "mt-0.5 h-5 w-5 text-muted-foreground", "aria-hidden": true }),
6767
- /* @__PURE__ */ jsxs("div", { children: [
6768
- /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
6769
- pending,
6770
- " ",
6771
- pending === 1 ? "nota aguardando" : "notas aguardando",
6772
- " manifesta\xE7\xE3o \xE0 SEFAZ"
6773
- ] }),
6774
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Manifestar responde \xE0 SEFAZ e tem prazo. Dar entrada responde ao seu estoque e ao financeiro \u2014 s\xE3o duas filas." })
6775
- ] })
6776
- ] }),
6777
- /* @__PURE__ */ jsx(Hint, { className: "mt-3", children: /* @__PURE__ */ jsx("span", { children: "A manifesta\xE7\xE3o continua sendo feita no sistema antigo por enquanto. Aqui voc\xEA l\xEA a fila e o que a SEFAZ respondeu; a entrada no estoque e a conta a pagar j\xE1 s\xE3o feitas nesta tela nova." }) }),
6778
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
6779
- /* @__PURE__ */ jsx(EmBreve, { label: "Aceitar / Recusar", o_que: "Responder \xE0 SEFAZ \u2014 \xE9 obriga\xE7\xE3o com prazo. Hoje a manifesta\xE7\xE3o \xE9 feita no sistema antigo" }),
6780
- /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setPullOpen(true), children: [
6781
- /* @__PURE__ */ jsx(KeyRound, { className: "mr-1.5 h-4 w-4" }),
6782
- "Puxar por chave"
6783
- ] }),
6784
- /* @__PURE__ */ jsx(EmBreve, { label: "Sincronizar agora", o_que: "Pedir \xE0 SEFAZ o que chegou desde a \xFAltima varredura" }),
6785
- /* @__PURE__ */ jsx(EmBreve, { label: "Resincronizar tudo", o_que: "Refazer a varredura inteira do per\xEDodo" }),
6786
- /* @__PURE__ */ jsx(EmBreve, { label: "Diagnosticar", o_que: "Por que uma nota n\xE3o chegou: certificado, credencial ou cursor parado" })
6787
- ] })
6788
- ] }),
6789
- /* @__PURE__ */ jsx(PullByKeyDialog, { open: pullOpen, onOpenChange: setPullOpen, onPulled: (r) => {
6790
- void reload();
6791
- onOpenNote(r.documentId);
6792
- } }),
6793
- /* @__PURE__ */ jsx(ListFilterBar, { filters, onClear: () => setTab("todas"), activeCount: tab === "todas" ? 0 : 1 }),
6794
- loading ? /* @__PURE__ */ jsx("div", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full" }, i)) }) : shown.length === 0 ? /* @__PURE__ */ jsxs(Card, { className: "p-10 text-center", children: [
6795
- /* @__PURE__ */ jsx(FileText, { className: "mx-auto h-8 w-8 text-muted-foreground", "aria-hidden": true }),
6796
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium", children: "Nenhuma nota nesta fila" })
6797
- ] }) : /* @__PURE__ */ jsx(Card, { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
6798
- /* @__PURE__ */ jsx("thead", { className: "border-b bg-muted/40 text-xs text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
6799
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Emiss\xE3o" }),
6800
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "N\xBA" }),
6801
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium", children: "Fornecedor" }),
6802
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Valor" }),
6803
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "Parcelas" }),
6804
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center font-medium", children: "Manifesta\xE7\xE3o" }),
6805
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: "A\xE7\xF5es" })
6806
- ] }) }),
6807
- /* @__PURE__ */ jsx("tbody", { children: shown.map((note) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 hover:bg-muted/30", children: [
6808
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums text-muted-foreground", children: day4(note.issueDate) }),
6809
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 tabular-nums font-medium", children: note.number }),
6810
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: note.supplierName || "\u2014" }),
6811
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums", children: money4(note.total) }),
6812
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums text-muted-foreground", children: note.instalmentCount }),
6813
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-center", children: /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${MANIFEST_TONE[note.manifestationStatus ?? ""] ?? "bg-muted text-muted-foreground"}`, children: note.manifestationStatus ?? "n\xE3o manifestada" }) }),
6814
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
6815
- /* @__PURE__ */ jsx(EmBreve, { label: "XML", o_que: "Ver o XML bruto autorizado desta nota" }),
6816
- /* @__PURE__ */ jsx(EmBreve, { label: "PDF", o_que: "Abrir o DANFE desta nota" }),
6817
- /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: () => onOpenNote(note.id), children: [
6818
- "Abrir concilia\xE7\xE3o",
6819
- /* @__PURE__ */ jsx(ExternalLink, { className: "ml-1.5 h-3.5 w-3.5" })
6820
- ] })
6821
- ] }) })
6822
- ] }, note.id)) })
6823
- ] }) }) })
6824
- ] });
6825
- }
6826
7181
  var HASH_PREFIX = "#/inbound-notes";
6827
7182
  function viewFromLocation() {
6828
7183
  if (typeof window === "undefined") return { kind: "list" };
6829
7184
  const hash = window.location.hash;
6830
7185
  const note = hash.match(/\/inbound-notes\/note\/([0-9a-f-]{36})/i);
6831
7186
  if (note) return { kind: "note", id: note[1] };
6832
- if (/\/inbound-notes\/manifest/.test(hash)) return { kind: "manifest" };
6833
7187
  return { kind: "list" };
6834
7188
  }
6835
- var pathOf = (view) => view.kind === "note" ? `${HASH_PREFIX}/note/${view.id}` : view.kind === "manifest" ? `${HASH_PREFIX}/manifest` : HASH_PREFIX;
7189
+ var pathOf = (view) => view.kind === "note" ? `${HASH_PREFIX}/note/${view.id}` : HASH_PREFIX;
6836
7190
  var STACK_MARK = { inboundNotes: true };
6837
- function InboundNotesPage({ payableRoute } = {}) {
7191
+ function InboundNotesPage({ payableRoute = (id) => `/financial/payables/${id}` } = {}) {
6838
7192
  const [view, setView] = useState(viewFromLocation);
6839
- const [background, setBackground] = useState(
6840
- () => viewFromLocation().kind === "manifest" ? "manifest" : "list"
6841
- );
6842
7193
  const go = useCallback((next) => {
6843
7194
  setView(next);
6844
- if (next.kind !== "note") setBackground(next.kind);
6845
7195
  if (typeof window === "undefined") return;
6846
7196
  const path = pathOf(next);
6847
7197
  if (window.location.hash === path) return;
@@ -6858,7 +7208,6 @@ function InboundNotesPage({ payableRoute } = {}) {
6858
7208
  const onPop = () => {
6859
7209
  const next = viewFromLocation();
6860
7210
  setView(next);
6861
- if (next.kind !== "note") setBackground(next.kind);
6862
7211
  };
6863
7212
  window.addEventListener("popstate", onPop);
6864
7213
  window.addEventListener("hashchange", onPop);
@@ -6867,7 +7216,7 @@ function InboundNotesPage({ payableRoute } = {}) {
6867
7216
  window.removeEventListener("hashchange", onPop);
6868
7217
  };
6869
7218
  }, []);
6870
- const fecharNota = useCallback(() => go({ kind: background }), [go, background]);
7219
+ const fecharNota = useCallback(() => go({ kind: "list" }), [go]);
6871
7220
  useEffect(() => {
6872
7221
  if (typeof window === "undefined") return;
6873
7222
  const op = window.location.hash.match(/[?&]operation=([0-9a-f-]{36})/i)?.[1];
@@ -6882,13 +7231,7 @@ function InboundNotesPage({ payableRoute } = {}) {
6882
7231
  };
6883
7232
  }, [go]);
6884
7233
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6885
- background === "manifest" ? /* @__PURE__ */ jsx(ManifestView, { onOpenNote: (id) => go({ kind: "note", id }), onBack: () => go({ kind: "list" }) }) : /* @__PURE__ */ jsx(
6886
- InboundNotesView,
6887
- {
6888
- onOpenNote: (id) => go({ kind: "note", id }),
6889
- onOpenManifest: () => go({ kind: "manifest" })
6890
- }
6891
- ),
7234
+ /* @__PURE__ */ jsx(InboundNotesView, { onOpenNote: (id) => go({ kind: "note", id }) }),
6892
7235
  /* @__PURE__ */ jsx(Modal, { open: view.kind === "note", onOpenChange: (open) => {
6893
7236
  if (!open) fecharNota();
6894
7237
  }, children: view.kind === "note" && // `noPadding`: a conciliação desenha o próprio cabeçalho e o próprio
@@ -6940,13 +7283,31 @@ var COLUMNS = [
6940
7283
  "certificate_uploaded_at",
6941
7284
  "dfe_sync_enabled",
6942
7285
  "dfe_last_sync_at",
7286
+ "dfe_last_error",
6943
7287
  "provider_synced_at"
6944
7288
  ].join(", ");
6945
7289
  var sb2 = () => getSupabaseClientOptional();
7290
+ async function uploadCertificate(legalEntityId, file, password) {
7291
+ const buf = new Uint8Array(await file.arrayBuffer());
7292
+ let bin = "";
7293
+ for (let i = 0; i < buf.length; i++) bin += String.fromCharCode(buf[i]);
7294
+ const { data, error } = await sb2().functions.invoke("fiscalbr-certificate", {
7295
+ body: { legalEntityId, pfxBase64: btoa(bin), password, fileName: file.name, tenantId: getActiveTenantId$1() ?? null }
7296
+ });
7297
+ if (error) {
7298
+ let body = null;
7299
+ try {
7300
+ body = await error?.context?.json?.();
7301
+ } catch {
7302
+ }
7303
+ throw new Error(body?.error || error.message || "Falha ao falar com o servi\xE7o fiscal.");
7304
+ }
7305
+ return data;
7306
+ }
6946
7307
  function useEmitterSettings(legalEntityId) {
6947
- const [row, setRow] = React7.useState(null);
6948
- const [loading, setLoading] = React7.useState(true);
6949
- React7.useEffect(() => {
7308
+ const [row, setRow] = React6.useState(null);
7309
+ const [loading, setLoading] = React6.useState(true);
7310
+ React6.useEffect(() => {
6950
7311
  if (!legalEntityId) {
6951
7312
  setLoading(false);
6952
7313
  return;
@@ -6962,7 +7323,7 @@ function useEmitterSettings(legalEntityId) {
6962
7323
  alive = false;
6963
7324
  };
6964
7325
  }, [legalEntityId]);
6965
- const save = React7.useCallback(async (tenantId, patch) => {
7326
+ const save = React6.useCallback(async (tenantId, patch) => {
6966
7327
  if (!legalEntityId) return false;
6967
7328
  const payload = { ...patch, tenant_id: tenantId, legal_entity_id: legalEntityId, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
6968
7329
  const { data, error } = await sb2().from("plg_fiscalbr_emitter_settings").upsert(payload, { onConflict: "legal_entity_id" }).select(COLUMNS).single();
@@ -6973,7 +7334,7 @@ function useEmitterSettings(legalEntityId) {
6973
7334
  setRow(data);
6974
7335
  return true;
6975
7336
  }, [legalEntityId]);
6976
- return { row, loading, save };
7337
+ return { row, loading, save, setRow };
6977
7338
  }
6978
7339
  function NeedsLegalEntity({ item, what }) {
6979
7340
  return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col items-start gap-3 py-6", children: [
@@ -7015,7 +7376,7 @@ function SummaryCard({ title, lines, badge, onConfigure }) {
7015
7376
  return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-start justify-between gap-3 space-y-0", children: [
7016
7377
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7017
7378
  /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: title }),
7018
- /* @__PURE__ */ jsx("dl", { className: "mt-2 grid gap-x-6 gap-y-1 text-sm", style: { gridTemplateColumns: "auto 1fr" }, children: lines.map((l) => /* @__PURE__ */ jsxs(React7.Fragment, { children: [
7379
+ /* @__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: [
7019
7380
  /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: l.label }),
7020
7381
  /* @__PURE__ */ jsx("dd", { className: "text-foreground", children: l.value })
7021
7382
  ] }, l.label)) })
@@ -7035,8 +7396,15 @@ function SummaryCard({ title, lines, badge, onConfigure }) {
7035
7396
  }
7036
7397
  var fmtDateTime = (v) => v ? new Date(v).toLocaleString("pt-BR") : null;
7037
7398
  function UnitCertificateWidget({ item }) {
7038
- const { row, loading, save } = useEmitterSettings(item?.legalEntityId);
7039
- const [open, setOpen] = React7.useState(false);
7399
+ const { row, loading, save, setRow } = useEmitterSettings(item?.legalEntityId);
7400
+ const [open, setOpen] = React6.useState(false);
7401
+ const [file, setFile] = React6.useState(null);
7402
+ const [password, setPassword] = React6.useState("");
7403
+ const [sending, setSending] = React6.useState(false);
7404
+ const expiredNow = !!row?.certificate_expires_at && Date.parse(row.certificate_expires_at) < Date.now();
7405
+ React6.useEffect(() => {
7406
+ if (expiredNow) setOpen(true);
7407
+ }, [expiredNow]);
7040
7408
  if (!item?.id) return null;
7041
7409
  if (!item.legalEntityId) return /* @__PURE__ */ jsx(NeedsLegalEntity, { item, what: "O certificado digital" });
7042
7410
  if (loading || !row) return /* @__PURE__ */ jsx(LoadingCard, {});
@@ -7104,10 +7472,55 @@ function UnitCertificateWidget({ item }) {
7104
7472
  hasCertificate && /* @__PURE__ */ jsx(Badge, { variant: expired ? "destructive" : "secondary", children: expired ? "Vencido" : "Ativo" })
7105
7473
  ] }),
7106
7474
  /* @__PURE__ */ jsx("div", { className: "fz-container", children: /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-4", children: [
7107
- /* @__PURE__ */ jsx(Field2, { label: "Arquivo (.pfx)", children: /* @__PURE__ */ jsx(Input, { type: "file", accept: ".pfx,.p12", disabled: true }) }),
7108
- /* @__PURE__ */ jsx(Field2, { label: "Senha do certificado", children: /* @__PURE__ */ jsx(Input, { type: "password", disabled: true }) })
7475
+ /* @__PURE__ */ jsx(Field2, { label: "Arquivo (.pfx)", children: /* @__PURE__ */ jsx(
7476
+ Input,
7477
+ {
7478
+ type: "file",
7479
+ accept: ".pfx,.p12,application/x-pkcs12",
7480
+ "data-testid": "certificate-file",
7481
+ onChange: (e) => setFile(e.target.files?.[0] ?? null)
7482
+ }
7483
+ ) }),
7484
+ /* @__PURE__ */ jsx(Field2, { label: "Senha do certificado", children: /* @__PURE__ */ jsx(
7485
+ Input,
7486
+ {
7487
+ type: "password",
7488
+ autoComplete: "new-password",
7489
+ name: "pfx-password",
7490
+ value: password,
7491
+ "data-testid": "certificate-password",
7492
+ onChange: (e) => setPassword(e.target.value)
7493
+ }
7494
+ ) })
7109
7495
  ] }) }),
7110
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "O envio do certificado ainda n\xE3o est\xE1 dispon\xEDvel nesta vers\xE3o: falta a fun\xE7\xE3o no servidor que repassa o arquivo ao PlugNotas sem guard\xE1-lo." })
7496
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
7497
+ Button,
7498
+ {
7499
+ size: "sm",
7500
+ "data-testid": "certificate-send",
7501
+ disabled: !file || !password || sending,
7502
+ onClick: () => {
7503
+ if (!file) return;
7504
+ setSending(true);
7505
+ void uploadCertificate(item.legalEntityId, file, password).then((res) => {
7506
+ setRow((r) => ({
7507
+ ...r ?? {},
7508
+ certificate_cn: res.cn,
7509
+ certificate_expires_at: res.expiresAt,
7510
+ certificate_uploaded_at: (/* @__PURE__ */ new Date()).toISOString(),
7511
+ dfe_sync_enabled: true,
7512
+ dfe_last_error: res.warning ?? null
7513
+ }));
7514
+ setFile(null);
7515
+ setPassword("");
7516
+ if (res.warning) toast.warning("Certificado enviado, com um aviso", { description: res.warning });
7517
+ else toast.success(`Certificado enviado \xB7 v\xE1lido at\xE9 ${new Date(res.expiresAt).toLocaleDateString("pt-BR")}`);
7518
+ }).catch((e) => toast.error("N\xE3o foi poss\xEDvel enviar o certificado", { description: e.message })).finally(() => setSending(false));
7519
+ },
7520
+ children: sending ? "Enviando\u2026" : hasCertificate ? "Atualizar certificado" : "Enviar certificado"
7521
+ }
7522
+ ) }),
7523
+ row.dfe_last_error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: row.dfe_last_error })
7111
7524
  ] }),
7112
7525
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
7113
7526
  "\xDAltima sincroniza\xE7\xE3o: ",
@@ -7139,11 +7552,11 @@ function EnvSelect({ value, onChange, disabled }) {
7139
7552
  }
7140
7553
  function UnitFiscalWidget({ item }) {
7141
7554
  const { row, loading, save } = useEmitterSettings(item?.legalEntityId);
7142
- const [draft, setDraft] = React7.useState({});
7143
- const [cscToken, setCscToken] = React7.useState("");
7144
- const [saving, setSaving] = React7.useState(false);
7145
- const [open, setOpen] = React7.useState(false);
7146
- React7.useEffect(() => {
7555
+ const [draft, setDraft] = React6.useState({});
7556
+ const [cscToken, setCscToken] = React6.useState("");
7557
+ const [saving, setSaving] = React6.useState(false);
7558
+ const [open, setOpen] = React6.useState(false);
7559
+ React6.useEffect(() => {
7147
7560
  if (row) setDraft(row);
7148
7561
  }, [row]);
7149
7562
  if (!item?.id) return null;
@@ -7511,7 +7924,7 @@ var dayOrDateTime = (v) => {
7511
7924
  const d = new Date(v);
7512
7925
  return d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0 ? d.toLocaleDateString("pt-BR") : dateTime(v);
7513
7926
  };
7514
- var money5 = (v) => v == null ? "\u2014" : v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
7927
+ var money4 = (v) => v == null ? "\u2014" : v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
7515
7928
  function buttonHint(s) {
7516
7929
  const parts = [s.hasValid ? "Notas fiscais desta conta \u2014 h\xE1 uma nota v\xE1lida" : "Notas fiscais desta conta"];
7517
7930
  if (s.withoutEffect > 0) parts.push(`${s.withoutEffect} cancelada(s), rejeitada(s) ou com erro`);
@@ -7750,7 +8163,7 @@ function NoteDetails({ note }) {
7750
8163
  ["Refer\xEAncia", note.integrationId && /* @__PURE__ */ jsx("span", { className: "break-all font-mono text-xs", children: note.integrationId })],
7751
8164
  ["Chave de acesso", note.accessKey && /* @__PURE__ */ jsx("span", { className: "break-all font-mono text-xs", children: note.accessKey })],
7752
8165
  ["C\xF3digo de verifica\xE7\xE3o", note.verificationCode],
7753
- ["Valor", money5(note.totalValue)],
8166
+ ["Valor", money4(note.totalValue)],
7754
8167
  ["Tomador", recipientLabel(note.recipient)],
7755
8168
  ["Criada em", dateTime(note.createdAt)],
7756
8169
  ["Autorizada em", note.authorizedAt && dayOrDateTime(note.authorizedAt)],
@@ -7965,9 +8378,9 @@ function inboundNotesInventoryView(options = {}) {
7965
8378
  };
7966
8379
  }
7967
8380
  function InboundNoteDocumentModal({ movement, onClose }) {
7968
- const [noteId, setNoteId] = React7.useState(null);
7969
- const [erro, setErro] = React7.useState(null);
7970
- React7.useEffect(() => {
8381
+ const [noteId, setNoteId] = React6.useState(null);
8382
+ const [erro, setErro] = React6.useState(null);
8383
+ React6.useEffect(() => {
7971
8384
  let vivo = true;
7972
8385
  const numero = nfeNumberOf(movement);
7973
8386
  const busca = movement.operationId ? findInboxNoteByOperation(movement.operationId).then((id) => id ?? (numero ? findInboxNoteByNumber(numero) : null)) : numero ? findInboxNoteByNumber(numero) : Promise.resolve(null);
@@ -7998,7 +8411,17 @@ function InboundNoteDocumentModal({ movement, onClose }) {
7998
8411
  ] })
7999
8412
  ] });
8000
8413
  }
8001
- return /* @__PURE__ */ jsx(NoteReconcileView, { chrome: "modal", noteId, onBack: onClose });
8414
+ return /* @__PURE__ */ jsx(
8415
+ NoteReconcileView,
8416
+ {
8417
+ chrome: "modal",
8418
+ noteId,
8419
+ onBack: onClose,
8420
+ onOpenInvoice: (id) => {
8421
+ window.location.hash = `/financial/payables/${id}`;
8422
+ }
8423
+ }
8424
+ );
8002
8425
  }
8003
8426
  function inboundNoteDocumentModal(movement, close) {
8004
8427
  if (movement.documentType !== "nfe" || !movement.operationId) return void 0;
@@ -8149,6 +8572,6 @@ function createPlugNotasPlugin(options = {}) {
8149
8572
  });
8150
8573
  }
8151
8574
 
8152
- export { EVENT_LABEL, InboundNoteDocumentModal, InboundNotesPage, InboundNotesView, InvoiceNotesButton, IssuedNotesView, MODEL_LABEL, ManifestView, NfeParseError, NoteReconcileView, PLUGNOTAS_CONNECTOR_ID, PLUGNOTAS_FIELDS, PLUGNOTAS_SETTINGS, PullByKeyError, ReturnedNotesView, STATUS_META, STOCK_BEARING_ENTRY_TYPES, TaxGroupsView, acceptNote, addNoteAttachment, cancelNote, createPlugNotasConnector, createPlugNotasFinancialProvider, createPlugNotasPlugin, eventStatusLabel, explainRejection, extractPaymentCode, fetchNotePdf, fillInboxLines, findInboxNoteByNumber, findInboxNoteByOperation, getInboxNote, getInboxNotePdf, getInboxNoteXml, inboundNoteDocumentModal, inboundNotesInventoryView, isStockBearing, isValidCnpj, isWithoutEffect, linkInboxSupplier, listChartAccounts, listInboxNotes, listInvoiceNotes, listMeasurementUnits, listNoteAttachments, listNoteEvents, listProductsByIds, listStockLocations, listSupplierMap, markPlugNotasEmitterReady, moveNoteAttachmentsToInvoice, notesButtonState, onlyDigits, parseBoleto, parseNfeXml, paymentLabel, paymentTypeFromCode, pullNoteByKey, recipientLabel, refreshNote, rejectNote, removeNoteAttachment, saveNoteDecision, searchProducts, stageNoteFromXml, statusLabel, suggestNoteItems, summarizeNotes, toStagePayload };
8575
+ 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, listProductsByIds, listStockLocations, listSupplierMap, markPlugNotasEmitterReady, moveNoteAttachmentsToInvoice, notesButtonState, onlyDigits, parseBoleto, parseNfeXml, paymentLabel, paymentTypeFromCode, pullNoteByKey, recipientLabel, refreshNote, rejectNote, removeNoteAttachment, saveNoteDecision, searchProducts, stageNoteFromXml, statusLabel, suggestNoteItems, summarizeNotes, syncNewNotes, toStagePayload };
8153
8576
  //# sourceMappingURL=index.js.map
8154
8577
  //# sourceMappingURL=index.js.map