@fayz-ai/plugin-financial 0.14.1 → 0.14.2

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.
Files changed (36) hide show
  1. package/dist/components/PaymentModal.d.ts.map +1 -1
  2. package/dist/data/mock.d.ts.map +1 -1
  3. package/dist/data/registries.d.ts.map +1 -1
  4. package/dist/data/supabase.d.ts.map +1 -1
  5. package/dist/data/tables.d.ts +2 -0
  6. package/dist/data/tables.d.ts.map +1 -1
  7. package/dist/data/types.d.ts +2 -2
  8. package/dist/data/types.d.ts.map +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1894 -439
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/idempotency.d.ts +2 -0
  13. package/dist/lib/idempotency.d.ts.map +1 -0
  14. package/dist/lib/setup-guides.d.ts.map +1 -1
  15. package/dist/locales/en.d.ts.map +1 -1
  16. package/dist/locales/pt-BR.d.ts.map +1 -1
  17. package/dist/store.d.ts +2 -2
  18. package/dist/store.d.ts.map +1 -1
  19. package/dist/types.d.ts +10 -0
  20. package/dist/types.d.ts.map +1 -1
  21. package/dist/views/CashRegistersView.d.ts.map +1 -1
  22. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  23. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  24. package/dist/views/InvoiceListView.d.ts.map +1 -1
  25. package/dist/views/NotasFiscaisView.d.ts.map +1 -1
  26. package/dist/views/ReconciliationView.d.ts.map +1 -1
  27. package/dist/views/StatementsView.d.ts.map +1 -1
  28. package/dist/views/document-memory.d.ts +43 -0
  29. package/dist/views/document-memory.d.ts.map +1 -0
  30. package/dist/views/payable-agent.d.ts +58 -0
  31. package/dist/views/payable-agent.d.ts.map +1 -0
  32. package/dist/views/payable-lines.d.ts +32 -0
  33. package/dist/views/payable-lines.d.ts.map +1 -0
  34. package/dist/views/receipt-agent.d.ts +9 -0
  35. package/dist/views/receipt-agent.d.ts.map +1 -1
  36. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import * as React14 from 'react';
2
- import React14__default, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
3
- import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, createSafeDataProvider, registerMessageDefaults, useTranslation, countByTenant, getSupabaseClientOptional, getActiveTenantId, exportCSV } from '@fayz-ai/core';
4
- import { toast, CurrencyInput, DatePicker, Button, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, PageTransition, ModulePage, SubpageHeader, Skeleton, Badge, TooltipProvider, ConfirmDialog, MultiSelect, Tooltip, TooltipTrigger, TooltipContent, Input, Checkbox, ModalFooter, DashboardCanvas, PageHeaderActions, Card, FilterChip, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
5
- import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, TeachMeLink, parseViewId, EntitlementGate, UpgradePrompt, GetStarted, useAuth, PermissionGate, PersonLink, useLimitGuard, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, invalidateLimit, askAI, requestChatUpload, toInlineFile, useChatStore, useAgentSurface, revealPage } from '@fayz-ai/admin';
1
+ import * as React15 from 'react';
2
+ import React15__default, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
3
+ import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, createSafeDataProvider, registerMessageDefaults, useTranslation, createPendingCommand, countByTenant, getSupabaseClientOptional, getActiveTenantId, exportCSV, useDataChanged } from '@fayz-ai/core';
4
+ import { toast, CurrencyInput, DatePicker, Button, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, PageTransition, ModulePage, SubpageHeader, Skeleton, Badge, TooltipProvider, ConfirmDialog, MultiSelect, Tooltip, TooltipTrigger, TooltipContent, Input, Checkbox, ModalFooter, DashboardCanvas, PageHeaderActions, Card, FilterChip, useSaveBar, tourAnchor, SearchSelect, ListView, KpiCard, ChartWidget, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
5
+ import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, TeachMeLink, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, createArchetypeLookup, useChatStore, adminNavigateTo, revealPage, GetStarted, useAuth, resolveEntityRoute, PermissionGate, PersonLink, useLimitGuard, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, invalidateLimit, askAI, requestChatUpload, toInlineFile } from '@fayz-ai/admin';
6
6
  import { CircleDollarSign, FileText, X, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Pencil, Check, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, DollarSign, Trash2, Landmark, ChevronDown, Clock, BookOpen, ChevronRight, Info, Calculator, StickyNote, Play, Square, ChevronUp, ClipboardCheck, FileCheck2, ExternalLink, FileX2, RefreshCw, Send, Ban, CalendarClock, Gift, Loader2, BadgeCheck, Target, Settings, ArrowRight, Receipt, Users, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, Search, Download, Paperclip, CheckCheck, MoreVertical, CircleCheckBig, CircleDashed, Camera, CircleAlert, CircleEllipsis, CalendarDays, LayoutList, Layers, Undo2, Building2, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, ArrowUpRight, ArrowDownRight, EyeOff, Eye } from 'lucide-react';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import { createPortal } from 'react-dom';
@@ -162,6 +162,48 @@ function MobileSummary() {
162
162
  ] });
163
163
  }
164
164
 
165
+ // src/data/tables.ts
166
+ var T = {
167
+ movements: "plg_financial_movements",
168
+ cashMovements: "v_financial_cash_movements",
169
+ accountBalances: "v_financial_account_balances",
170
+ chartOfAccounts: "plg_financial_chart_of_accounts",
171
+ bankAccounts: "plg_financial_bank_accounts",
172
+ cardBrands: "plg_financial_card_brands",
173
+ costCenters: "plg_financial_cost_centers",
174
+ cashRegisterSessions: "plg_financial_cash_register_sessions",
175
+ paymentMethods: "plg_financial_payment_methods",
176
+ paymentMethodTypes: "plg_financial_payment_method_types",
177
+ notas: "plg_financial_nfe",
178
+ // A contabilidade da LINHA. public.items é a linha; o que só o financeiro
179
+ // sabe dela — plano de contas, centro de custo, imposto, CFOP — mora aqui,
180
+ // com item_id de chave. Ver 038_the_invoice_gets_a_line.sql.
181
+ invoiceItems: "plg_financial_invoice_items",
182
+ // A regra de comissão. O registry apontava para `commission_rules`, sem
183
+ // prefixo — nome que a 028 aposentou e que não existe em pool nenhum.
184
+ commissionRules: "plg_financial_commission_rules",
185
+ commissionRuleCriteria: "plg_financial_commission_rule_criteria",
186
+ commissionFunctions: "plg_financial_commission_functions",
187
+ // The legacy `movement_kind='bill'` SHAPE, derived from the instalment
188
+ // balances (financial 040). Reading it instead of the mirror is what let the
189
+ // mirror stop being written: the columns are the same, the source is one.
190
+ // The instalment IS the obligation; the `movement_kind='bill'` shape and
191
+ // its compat view left in financial 044.
192
+ bills: "v_financial_installment_balances",
193
+ /** A fatura com o que já foi pago somado — de onde toda lista de contas lê. */
194
+ invoiceBalances: "v_financial_invoice_balances",
195
+ commissions: "v_financial_commissions",
196
+ commissionGenerations: "plg_financial_commission_generations",
197
+ periods: "plg_financial_periods",
198
+ trialBalance: "v_financial_trial_balance",
199
+ aging: "v_financial_aging",
200
+ agingSummary: "v_financial_aging_summary",
201
+ lineMargin: "v_financial_line_margin",
202
+ marginByMonth: "v_financial_margin_by_month",
203
+ recurringBills: "plg_financial_recurring_bills",
204
+ discountsGiven: "v_financial_discounts_given"
205
+ };
206
+
165
207
  // src/lib/accounts.ts
166
208
  function postableAccounts(accounts) {
167
209
  const parentIds = new Set(accounts.map((a) => a.parentId).filter(Boolean));
@@ -234,8 +276,8 @@ var SCALES = {
234
276
  bilhao: 1e9,
235
277
  bilhoes: 1e9
236
278
  };
237
- function normalize(text) {
238
- return text.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z\s]/g, " ").split(/\s+/).filter(Boolean);
279
+ function normalize(text2) {
280
+ return text2.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z\s]/g, " ").split(/\s+/).filter(Boolean);
239
281
  }
240
282
  function sumGroup(words) {
241
283
  let total = 0;
@@ -280,6 +322,111 @@ function lastCurrencyIndex(words, before) {
280
322
  }
281
323
  return before;
282
324
  }
325
+
326
+ // src/views/payable-lines.ts
327
+ function round2(n) {
328
+ return Math.round(n * 100) / 100;
329
+ }
330
+ function parseLineItems(raw) {
331
+ if (raw === void 0 || raw === null) return { items: [] };
332
+ if (!Array.isArray(raw)) return { items: [], error: "`items` must be an array of table rows." };
333
+ const items = [];
334
+ for (const entry of raw) {
335
+ if (!entry || typeof entry !== "object") continue;
336
+ const row = entry;
337
+ const description = typeof row.description === "string" ? row.description.trim() : "";
338
+ const unitPrice = parseReceiptAmount(row.unitPrice);
339
+ if (!description || unitPrice === null) {
340
+ return {
341
+ items: [],
342
+ error: 'Every row needs a `description` and a readable `unitPrice` copied from the paper ("47,90"). Re-read the table and resend all rows.'
343
+ };
344
+ }
345
+ const quantityRaw = typeof row.quantity === "number" ? row.quantity : Number(row.quantity);
346
+ const quantity = Number.isFinite(quantityRaw) && quantityRaw > 0 ? quantityRaw : 1;
347
+ items.push({ description, quantity, unitPrice });
348
+ }
349
+ return { items };
350
+ }
351
+ function text(value) {
352
+ const s = typeof value === "string" ? value.trim() : "";
353
+ return s || void 0;
354
+ }
355
+ function today() {
356
+ const now2 = /* @__PURE__ */ new Date();
357
+ const pad = (n) => String(n).padStart(2, "0");
358
+ return `${now2.getFullYear()}-${pad(now2.getMonth() + 1)}-${pad(now2.getDate())}`;
359
+ }
360
+
361
+ // src/views/document-memory.ts
362
+ var lastDocument = null;
363
+ var DOCUMENT_TTL_MS = 15 * 60 * 1e3;
364
+ var STORAGE_KEY = "fayz:financial:last-document-read";
365
+ function readStored() {
366
+ try {
367
+ const raw = globalThis.sessionStorage?.getItem(STORAGE_KEY);
368
+ if (!raw) return null;
369
+ const parsed = JSON.parse(raw);
370
+ return parsed && typeof parsed.at === "number" && parsed.doc ? parsed : null;
371
+ } catch {
372
+ return null;
373
+ }
374
+ }
375
+ function writeStored(entry) {
376
+ try {
377
+ if (entry) globalThis.sessionStorage?.setItem(STORAGE_KEY, JSON.stringify(entry));
378
+ else globalThis.sessionStorage?.removeItem(STORAGE_KEY);
379
+ } catch {
380
+ }
381
+ }
382
+ function rememberedDocument() {
383
+ const entry = lastDocument ?? readStored();
384
+ if (!entry) return null;
385
+ if (Date.now() - entry.at > DOCUMENT_TTL_MS) {
386
+ clearRememberedDocument();
387
+ return null;
388
+ }
389
+ lastDocument = entry;
390
+ return entry.doc;
391
+ }
392
+ function clearRememberedDocument() {
393
+ lastDocument = null;
394
+ writeStored(null);
395
+ }
396
+ function normalizeDocument(args) {
397
+ const parsed = parseLineItems(args.items);
398
+ const items = parsed.error ? [] : parsed.items;
399
+ const shipping = parseReceiptAmount(args.shipping) ?? void 0;
400
+ const subtotal = parseReceiptAmount(args.subtotal) ?? void 0;
401
+ const total = parseReceiptAmount(args.total) ?? void 0;
402
+ const lineSum = items.length ? round2(items.reduce((s, i) => s + i.quantity * i.unitPrice, 0)) : void 0;
403
+ const computed = lineSum === void 0 ? void 0 : round2(lineSum + (shipping ?? 0));
404
+ const reconciles = computed === void 0 || total === void 0 ? false : Math.abs(computed - total) <= 0.02;
405
+ return {
406
+ items,
407
+ ...subtotal === void 0 ? {} : { subtotal },
408
+ ...shipping === void 0 ? {} : { shipping },
409
+ ...total === void 0 ? {} : { total },
410
+ ...lineSum === void 0 ? {} : { lineSum },
411
+ reconciles,
412
+ ...parseReceiptDate(args.documentDate) ? { documentDate: parseReceiptDate(args.documentDate) } : {},
413
+ // `null` é uma RESPOSTA: "o papel não diz". Diferente de "ainda não li".
414
+ dueDate: parseReceiptDate(args.dueDate) ?? null,
415
+ ...parseReceiptDate(args.paymentDate) ? { paymentDate: parseReceiptDate(args.paymentDate) } : {},
416
+ ...text(args.orderRef) ? { orderRef: text(args.orderRef) } : {},
417
+ ...parseReceiptDate(args.deliveryDate) ? { deliveryDate: parseReceiptDate(args.deliveryDate) } : {},
418
+ ...text(args.paymentMethod) ? { method: text(args.paymentMethod) } : {}
419
+ };
420
+ }
421
+ function rememberDocument(doc, meta) {
422
+ lastDocument = {
423
+ at: Date.now(),
424
+ doc,
425
+ ...meta?.source ? { source: meta.source } : {},
426
+ ...meta?.conversationId ? { conversationId: meta.conversationId } : {}
427
+ };
428
+ writeStored(lastDocument);
429
+ }
283
430
  var RECEIPT_INTRO = {
284
431
  role: "finance",
285
432
  persona: "Leitor de recibos",
@@ -356,35 +503,35 @@ var OPEN_DRAFT = {
356
503
  function parseReceiptAmount(raw) {
357
504
  if (typeof raw === "number") return Number.isFinite(raw) && raw > 0 ? raw : null;
358
505
  if (typeof raw !== "string") return null;
359
- const text = raw.replace(/[^\d.,-]/g, "").trim();
360
- if (!text || !/\d/.test(text)) return null;
361
- const lastComma = text.lastIndexOf(",");
362
- const lastDot = text.lastIndexOf(".");
506
+ const text2 = raw.replace(/[^\d.,-]/g, "").trim();
507
+ if (!text2 || !/\d/.test(text2)) return null;
508
+ const lastComma = text2.lastIndexOf(",");
509
+ const lastDot = text2.lastIndexOf(".");
363
510
  const cut = Math.max(lastComma, lastDot);
364
511
  let normalized;
365
512
  if (cut < 0) {
366
- normalized = text;
513
+ normalized = text2;
367
514
  } else {
368
- const decimals = text.length - cut - 1;
369
- normalized = decimals === 3 ? text.replace(/[.,]/g, "") : `${text.slice(0, cut).replace(/[.,]/g, "")}.${text.slice(cut + 1)}`;
515
+ const decimals = text2.length - cut - 1;
516
+ normalized = decimals === 3 ? text2.replace(/[.,]/g, "") : `${text2.slice(0, cut).replace(/[.,]/g, "")}.${text2.slice(cut + 1)}`;
370
517
  }
371
518
  const value = Number(normalized);
372
519
  return Number.isFinite(value) && value > 0 ? value : null;
373
520
  }
374
521
  function parseReceiptDate(raw) {
375
- const text = typeof raw === "string" ? raw.trim() : "";
376
- if (!text) return void 0;
377
- if (/^\d{4}-\d{2}-\d{2}$/.test(text)) return isRealDate(text) ? text : void 0;
378
- const br = text.match(/^(\d{1,2})[/.-](\d{1,2})[/.-](\d{2}|\d{4})$/);
522
+ const text2 = typeof raw === "string" ? raw.trim() : "";
523
+ if (!text2) return void 0;
524
+ if (/^\d{4}-\d{2}-\d{2}$/.test(text2)) return isRealDate(text2) ? text2 : void 0;
525
+ const br = text2.match(/^(\d{1,2})[/.-](\d{1,2})[/.-](\d{2}|\d{4})$/);
379
526
  if (!br) return void 0;
380
527
  const [, d, m, y] = br;
381
528
  const year = y.length === 2 ? `20${y}` : y;
382
529
  const iso = `${year}-${m.padStart(2, "0")}-${d.padStart(2, "0")}`;
383
530
  return isRealDate(iso) ? iso : void 0;
384
531
  }
385
- function findDateIn(text) {
386
- if (typeof text !== "string") return void 0;
387
- return text.match(/\b\d{1,2}[/.-]\d{1,2}[/.-](?:\d{4}|\d{2})\b/)?.[0];
532
+ function findDateIn(text2) {
533
+ if (typeof text2 !== "string") return void 0;
534
+ return text2.match(/\b\d{1,2}[/.-]\d{1,2}[/.-](?:\d{4}|\d{2})\b/)?.[0];
388
535
  }
389
536
  function isRealDate(iso) {
390
537
  const [y, m, d] = iso.split("-").map(Number);
@@ -409,6 +556,33 @@ function createReceiptTools({ onDraft, getReceiptUrl = lastReceiptPhoto }) {
409
556
  {
410
557
  tool: OPEN_DRAFT,
411
558
  execute: async (args) => {
559
+ const read = rememberedDocument();
560
+ if (read && read.total !== void 0) {
561
+ pending = {
562
+ amount: read.total,
563
+ ...read.items.length ? { items: read.items } : {},
564
+ ...read.shipping === void 0 ? {} : { shipping: read.shipping },
565
+ supplier: text(args.supplier) ?? void 0,
566
+ description: text(args.description) ?? void 0,
567
+ notes: [
568
+ text(args.notes),
569
+ read.orderRef ? `Pedido: ${read.orderRef}` : null,
570
+ read.deliveryDate ? `Entrega: ${read.deliveryDate}` : null
571
+ ].filter(Boolean).join(" \xB7 ") || void 0,
572
+ date: read.documentDate ?? parseReceiptDate(args.date),
573
+ receiptUrl: getReceiptUrl()
574
+ };
575
+ onDraft();
576
+ revealPage();
577
+ return {
578
+ ok: true,
579
+ opened: true,
580
+ fromDocument: true,
581
+ items: read.items.length,
582
+ total: read.total,
583
+ message: `O cadastro abriu com o que foi LIDO do documento \u2014 ${read.items.length || 1} linha(s), total ${read.total}. Diga isso em UMA frase e pare. N\xE3o repita os valores campo a campo e N\xC3O os recalcule: a pessoa est\xE1 olhando para eles.`
584
+ };
585
+ }
412
586
  if (typeof args.amount !== "string") {
413
587
  return {
414
588
  ok: false,
@@ -426,16 +600,16 @@ function createReceiptTools({ onDraft, getReceiptUrl = lastReceiptPhoto }) {
426
600
  message: "That is not a readable amount. If the total is illegible on the photo, ask the person."
427
601
  };
428
602
  }
429
- const text = (value) => {
603
+ const text2 = (value) => {
430
604
  const s = typeof value === "string" ? value.trim() : "";
431
605
  return s || void 0;
432
606
  };
433
607
  const isoDate2 = parseReceiptDate(args.date) ?? parseReceiptDate(findDateIn(args.notes));
434
608
  pending = {
435
609
  amount,
436
- supplier: text(args.supplier),
437
- description: text(args.description),
438
- notes: text(args.notes),
610
+ supplier: text2(args.supplier),
611
+ description: text2(args.description),
612
+ notes: text2(args.notes),
439
613
  date: isoDate2,
440
614
  receiptUrl: getReceiptUrl()
441
615
  };
@@ -477,9 +651,9 @@ function startReceiptCapture(labels) {
477
651
  });
478
652
  }
479
653
  function ReceiptAgentSurface({ onOpenDraft }) {
480
- const live = React14.useRef(onOpenDraft);
654
+ const live = React15.useRef(onOpenDraft);
481
655
  live.current = onOpenDraft;
482
- const contribution = React14.useMemo(() => ({
656
+ const contribution = React15.useMemo(() => ({
483
657
  id: "financial.payables.receipt",
484
658
  pluginId: "financial",
485
659
  role: "finance",
@@ -493,6 +667,17 @@ function ReceiptAgentSurface({ onOpenDraft }) {
493
667
  useAgentSurface(contribution);
494
668
  return null;
495
669
  }
670
+
671
+ // src/lib/idempotency.ts
672
+ function createIdempotencyKey(scope) {
673
+ let suffix;
674
+ try {
675
+ suffix = typeof globalThis.crypto?.randomUUID === "function" ? globalThis.crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
676
+ } catch {
677
+ suffix = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
678
+ }
679
+ return `${scope}:${suffix}`;
680
+ }
496
681
  var STATUS_OPTIONS = [
497
682
  { value: "open", labelKey: "financial.invoice.statusOpen", color: "bg-info-soft text-info-soft-foreground", icon: CircleDashed },
498
683
  { value: "partial", labelKey: "financial.invoice.statusPartial", color: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis },
@@ -644,6 +829,10 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
644
829
  useEffect(() => {
645
830
  fetchInvoices(query);
646
831
  }, [query]);
832
+ useDataChanged({ table: T.invoiceBalances }, () => {
833
+ void fetchInvoices(query);
834
+ void fetchSummary();
835
+ }, [query]);
647
836
  const filtered = invoices.filter((inv) => inv.direction === direction);
648
837
  const columns = useInvoiceColumns(currency);
649
838
  const tags = useMemo(
@@ -664,6 +853,13 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
664
853
  }, [filtered]);
665
854
  const [cancelTarget, setCancelTarget] = useState(null);
666
855
  const [bulkBusy, setBulkBusy] = useState(false);
856
+ const [cancelReason, setCancelReason] = useState("");
857
+ const cancelRequestKey = useRef(createIdempotencyKey("financial:invoice-bulk-cancel"));
858
+ const closeBulkCancel = useCallback(() => {
859
+ setCancelTarget(null);
860
+ setCancelReason("");
861
+ cancelRequestKey.current = createIdempotencyKey("financial:invoice-bulk-cancel");
862
+ }, []);
667
863
  const confirmBulkCancel = useCallback(async () => {
668
864
  const targets = (cancelTarget ?? []).filter((inv) => inv.status !== "cancelled");
669
865
  setBulkBusy(true);
@@ -671,7 +867,11 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
671
867
  let failed = 0;
672
868
  for (const invoice of targets) {
673
869
  try {
674
- await provider.cancelInvoice(invoice.id);
870
+ await provider.cancelInvoice({
871
+ invoiceId: invoice.id,
872
+ reason: cancelReason,
873
+ idempotencyKey: `${cancelRequestKey.current}:${invoice.id}`
874
+ });
675
875
  done++;
676
876
  } catch (err) {
677
877
  failed++;
@@ -679,11 +879,11 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
679
879
  }
680
880
  }
681
881
  setBulkBusy(false);
682
- setCancelTarget(null);
882
+ closeBulkCancel();
683
883
  await Promise.all([fetchInvoices(query), fetchSummary()]);
684
884
  if (done > 0) toast.success(t2("financial.invoice.bulkCancelDone", { count: String(done) }));
685
885
  if (failed > 0) toast.error(t2("financial.invoice.bulkCancelFailed", { count: String(failed) }));
686
- }, [cancelTarget, provider, fetchInvoices, fetchSummary, query, t2]);
886
+ }, [cancelTarget, cancelReason, closeBulkCancel, provider, fetchInvoices, fetchSummary, query, t2]);
687
887
  const bulkActions = useMemo(() => {
688
888
  if (!canEdit) return void 0;
689
889
  return [{
@@ -864,21 +1064,1169 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
864
1064
  /* @__PURE__ */ jsx("span", { className: "font-semibold text-warning", children: formatCurrency(summary.openAmount, currency) })
865
1065
  ] })
866
1066
  ] }),
867
- /* @__PURE__ */ jsx(
868
- ConfirmDialog,
1067
+ cancelTarget !== null && createPortal(
1068
+ /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[60] flex items-center justify-center", children: [
1069
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-[2px]", onClick: closeBulkCancel }),
1070
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 w-full max-w-sm rounded-modal border bg-card p-6 shadow-lg mx-4", role: "alertdialog", "aria-modal": "true", children: [
1071
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
1072
+ /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 shrink-0", children: /* @__PURE__ */ jsx(Ban, { className: "h-5 w-5 text-destructive" }) }),
1073
+ /* @__PURE__ */ jsxs("div", { children: [
1074
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoice.bulkCancelTitle", { count: String(cancelTarget.length) }) }),
1075
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoice.cancelDesc") })
1076
+ ] })
1077
+ ] }),
1078
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", htmlFor: "invoice-bulk-cancel-reason", children: t2("financial.invoice.cancelReason") }),
1079
+ /* @__PURE__ */ jsx(
1080
+ "textarea",
1081
+ {
1082
+ id: "invoice-bulk-cancel-reason",
1083
+ value: cancelReason,
1084
+ onChange: (event) => setCancelReason(event.target.value),
1085
+ placeholder: t2("financial.invoice.cancelReasonPlaceholder"),
1086
+ className: "mb-5 min-h-20 w-full resize-y rounded-md border bg-background px-3 py-2 text-sm",
1087
+ autoFocus: true
1088
+ }
1089
+ ),
1090
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
1091
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: closeBulkCancel, disabled: bulkBusy, children: t2("financial.invoice.keepInvoice") }),
1092
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: () => void confirmBulkCancel(), disabled: bulkBusy || !cancelReason.trim(), children: t2("financial.invoice.yesCancel") })
1093
+ ] })
1094
+ ] })
1095
+ ] }),
1096
+ document.body
1097
+ )
1098
+ ] });
1099
+ }
1100
+ var PAYABLE_INSTRUCTIONS = [
1101
+ 'Uma "conta a pagar" \xE9 uma DESPESA com um FORNECEDOR \u2014 quem a empresa deve. Para registrar uma,',
1102
+ "chame `createPayable`. NUNCA use `createRecord` em `financial:payment-methods` para isso: aquele",
1103
+ "registro configura como os CLIENTES pagam a empresa (bandeira, taxa, parcelamento), e um",
1104
+ "fornecedor cadastrado l\xE1 n\xE3o vira conta nenhuma \u2014 vira lixo na configura\xE7\xE3o da loja.",
1105
+ "Se a pessoa mandar a FOTO de um recibo ou de uma nota, leia a imagem e use o que est\xE1 escrito",
1106
+ "nela. Uma foto n\xE3o tem colunas: nunca proponha importa\xE7\xE3o para ela.",
1107
+ "Num pedido ou nota com TABELA, leia a tabela LINHA A LINHA e mande todas em `items` \u2014 c\xF3digo,",
1108
+ 'descri\xE7\xE3o, quantidade e pre\xE7o unit\xE1rio de cada uma. Uma linha gen\xE9rica de "servi\xE7o prestado" com',
1109
+ "o total dentro perde o pedido inteiro. O FRETE \xE9 campo pr\xF3prio (`shipping`), n\xE3o some ao produto.",
1110
+ "`amount` \xE9 sempre o TOTAL do rodap\xE9 (com frete), nunca o subtotal e nunca o total de uma linha.",
1111
+ 'Capture tamb\xE9m "M\xE9todo de pagamento:" quando impresso, o N\xDAMERO DO PEDIDO ("Pedido: #164223") e a',
1112
+ "data de ENTREGA \u2014 pedido e entrega v\xE3o em `notes`, que \xE9 onde a pessoa procura o que o papel dizia.",
1113
+ 'Se o texto trouxer uma linha como "[c\xF3digo Pix copia-e-cola \u2014 valor embutido: X]", aquilo \xE9 o',
1114
+ "c\xF3digo do pagamento: X confirma o total, e os d\xEDgitos em volta N\xC3O s\xE3o pre\xE7os.",
1115
+ '"Expira em" de um c\xF3digo Pix \xE9 a validade do C\xD3DIGO, n\xE3o o vencimento da conta. Um pedido pago \xE0',
1116
+ "vista n\xE3o tem vencimento: deixe `dueDate` de fora e o formul\xE1rio pergunta. Data chutada custa uma",
1117
+ "cobran\xE7a; campo vazio custa um toque.",
1118
+ "LEU UM PAPEL? PASSO 0 \xE9 `resolvePayableContext`, ANTES de escrever qualquer coisa. Extraia TUDO o",
1119
+ "que est\xE1 escrito \u2014 fornecedor, CNPJ, o que foi comprado, forma de pagamento, bandeira e final do",
1120
+ "cart\xE3o, datas, valores \u2014 e mande para conferir contra o que este neg\xF3cio j\xE1 tem. Ele responde o",
1121
+ "que existe e o que falta, sem gravar nada. Diga o resultado em uma linha curta por depend\xEAncia e",
1122
+ "resolva as que faltam NA ORDEM que ele devolve: fornecedor, forma de pagamento, depois a conta,",
1123
+ "depois a baixa. Se voltar um candidato parecido, PERGUNTE se \xE9 o mesmo \u2014 n\xE3o cadastre de novo.",
1124
+ 'Valor vai COPIADO como est\xE1 escrito ("50,00", "1.234,56"), nunca convertido \u2014 a v\xEDrgula \xE9',
1125
+ "decimal em portugu\xEAs, e convert\xEA-la voc\xEA mesmo j\xE1 transformou cinquenta reais em cinco mil.",
1126
+ "N\xE3o entreviste a pessoa campo a campo. Mande o que ela disse e chame a ferramenta: o que abre",
1127
+ "\xE9 um FORMUL\xC1RIO edit\xE1vel, e o que faltar ela preenche ali \u2014 em um toque, olhando os campos.",
1128
+ "Uma conta a pagar tem TR\xCAS passos, cada um confirmado pela pessoa: (1) o fornecedor, (2) o t\xEDtulo,",
1129
+ "(3) o pagamento \u2014 e o terceiro s\xF3 existe se a conta J\xC1 foi paga. Se o recibo ou a pessoa disser",
1130
+ 'que j\xE1 pagou ("pago hoje", "paguei no Pix"), mande `paid: true`; a ferramenta cuida da ordem.',
1131
+ "Quando faltar saber COMO foi pago, a ferramenta PERGUNTA com as op\xE7\xF5es do pr\xF3prio neg\xF3cio na tela.",
1132
+ "N\xE3o repita as op\xE7\xF5es em texto e n\xE3o escolha por ela: espere a resposta e ent\xE3o chame",
1133
+ "`registerPayablePayment` com o id que ela tocou.",
1134
+ "REGRA DE PRECED\xCANCIA: se o cadastro de conta a pagar estiver ABERTO na tela com um rascunho (veja",
1135
+ '"Tela aberta agora"), ele \xE9 a conta em curso. Complete-o com `fillPayableDraft` e salve com',
1136
+ "`submitPayableDraft`. `createPayable` \xE9 s\xF3 para quando N\xC3O h\xE1 rascunho aberto \u2014 cham\xE1-la com o",
1137
+ "formul\xE1rio na tela cria uma segunda conta e deixa a pessoa num rascunho \xF3rf\xE3o.",
1138
+ "Depois de lan\xE7ar, a tela da conta abre sozinha \u2014 \xE9 o normal e n\xE3o precisa ser anunciado. Mande",
1139
+ "`redirect: false` s\xF3 quando levar a pessoa embora atrapalharia: v\xE1rias contas em sequ\xEAncia, ou",
1140
+ "algu\xE9m no meio de outra tarefa que s\xF3 passou a conta de lado.",
1141
+ '"Marca como pago", "paguei", "foi pago" sobre uma conta que VOC\xCA acabou de lan\xE7ar \xE9 sempre',
1142
+ "`registerPayablePayment` com o invoiceId daquela conta \u2014 NUNCA `createPayable` de novo. Lan\xE7ar a",
1143
+ "mesma despesa duas vezes \xE9 dinheiro contado em dobro, e desfazer isso \xE9 trabalho de concilia\xE7\xE3o."
1144
+ ].join(" ");
1145
+ var CREATE_PAYABLE = {
1146
+ id: "financial.create-payable",
1147
+ name: "createPayable",
1148
+ description: 'Records a BILL THE BUSINESS OWES a supplier (conta a pagar, despesa, boleto, invoice from a vendor). This is the tool for "lan\xE7ar/salvar uma conta a pagar", "paguei o fornecedor X", "recibo de despesa". It walks a CHECKLIST, one confirmable step at a time: (1) the supplier \u2014 found by name or created as a supplier contact, (2) the bill itself. Each step opens an EDITABLE FORM pre-filled with what you send and writes nothing until the person confirms it \u2014 so call it as soon as you have an amount and let the forms ask for the rest. If the receipt says it was ALREADY PAID ("pago hoje", "paguei via Pix"), set `paid: true`: step (3) is the payment, and this tool will either ask HOW it was paid or hand you off to `registerPayablePayment`. Send ONLY what you were actually told or could read; omit anything you would be guessing at. When the bill came off a receipt/photo/PDF, call `resolvePayableContext` FIRST and settle the missing dependencies it reports before calling this. A document with a TABLE goes in row by row through `items` (plus `shipping` for frete): the lines are reconciled against `amount`, and a mismatch comes back to be asked about rather than silently trusted. DO NOT use this when a payable form is already open on screen (a draft read from a receipt or PDF): that draft is the bill in progress \u2014 use `fillPayableDraft` and `submitPayableDraft` instead, or this writes a second, duplicate bill and strands the person on a draft that no longer means anything.',
1149
+ icon: "ReceiptText",
1150
+ mode: "persist",
1151
+ execution: { plane: "client" },
1152
+ // O guard é desta ferramenta: ela abre o próprio cartão de confirmação, que é
1153
+ // um formulário. O cartão genérico do loop seria um segundo "tem certeza?"
1154
+ // antes do formulário — duas perguntas para uma decisão.
1155
+ requiresConfirmation: false,
1156
+ category: "Finance",
1157
+ role: "finance",
1158
+ permission: { feature: "financial", action: "create" },
1159
+ limitKey: "movements_month",
1160
+ parameters: {
1161
+ type: "object",
1162
+ properties: {
1163
+ supplier: {
1164
+ type: "string",
1165
+ description: 'Quem RECEBE o dinheiro \u2014 o fornecedor, a empresa, o prestador. \xC9 um NOME de quem foi pago ("Printi", "Padaria do Z\xE9"). N\xE3o \xE9 o que foi comprado nem o valor por extenso.'
1166
+ },
1167
+ amount: {
1168
+ type: "string",
1169
+ description: 'O valor TOTAL da conta \u2014 o que vai ser pago, incluindo frete. Num pedido \xE9 o "Total" do rodap\xE9, nunca o subtotal e nunca o total de uma linha. COPIADO LETRA POR LETRA, com a pontua\xE7\xE3o que estiver escrita: "50,00", "1.234,56", "R$ 89,90". N\xC3O converta para n\xFAmero e n\xE3o tire a v\xEDrgula \u2014 quem l\xEA a pontua\xE7\xE3o \xE9 o aplicativo, que sabe a moeda do pa\xEDs.'
1170
+ },
1171
+ amountInWords: {
1172
+ type: "string",
1173
+ description: 'O valor POR EXTENSO, quando o papel escrever ("CINQUENTA REAIS"). Num recibo manuscrito \xE9 ele que desempata os algarismos. Copie as palavras como est\xE3o.'
1174
+ },
1175
+ dueDate: {
1176
+ type: "string",
1177
+ description: 'O VENCIMENTO, como est\xE1 escrito ("20/01/2026", "2026-01-20"). Omita se ningu\xE9m disse \u2014 o formul\xE1rio pergunta. Uma data de vencimento chutada custa uma confer\xEAncia inteira.'
1178
+ },
1179
+ date: {
1180
+ type: "string",
1181
+ description: 'A data do DOCUMENTO (quando a compra aconteceu), se souber. "hoje" \xE9 hoje.'
1182
+ },
1183
+ description: {
1184
+ type: "string",
1185
+ description: 'O que foi comprado ou pago, em poucas palavras \u2014 \xE9 o nome da linha da conta ("cart\xF5es de visita", "insumos de padaria"). Sem ela a conta nasce sem dizer o que \xE9.'
1186
+ },
1187
+ items: {
1188
+ type: "array",
1189
+ description: 'As LINHAS da nota, uma por linha da tabela \u2014 leia a tabela linha a linha e copie todas. Um pedido de tr\xEAs produtos vira tr\xEAs linhas aqui, n\xE3o uma linha gen\xE9rica de "servi\xE7o prestado": \xE9 assim que a conta diz depois o que foi comprado. Omita quando o papel n\xE3o tem tabela (um recibo de t\xE1xi \xE9 uma linha s\xF3, e a\xED basta `description`).',
1190
+ items: {
1191
+ type: "object",
1192
+ properties: {
1193
+ description: { type: "string", description: "O que \xE9 a linha, como est\xE1 escrito (c\xF3digo + nome)." },
1194
+ quantity: { type: "number", description: "A quantidade da linha. 1 quando a tabela n\xE3o diz." },
1195
+ unitPrice: {
1196
+ type: "string",
1197
+ description: 'O pre\xE7o UNIT\xC1RIO copiado letra por letra ("47,90"), n\xE3o o total da linha e n\xE3o convertido.'
1198
+ }
1199
+ },
1200
+ required: ["description", "unitPrice"]
1201
+ }
1202
+ },
1203
+ shipping: {
1204
+ type: "string",
1205
+ description: 'O FRETE, copiado como est\xE1 escrito ("52,19"), quando o papel o cobra em separado. Vira uma linha pr\xF3pria na conta \u2014 somado a um produto ele desapareceria dentro do pre\xE7o dele.'
1206
+ },
1207
+ notes: {
1208
+ type: "string",
1209
+ description: 'Todo o RESTO que foi dito e n\xE3o coube nos campos acima: N\xDAMERO DO PEDIDO ou do documento ("Pedido: #164223"), data de entrega, condi\xE7\xF5es, para qual projeto. \xC9 onde a pessoa vai procurar o que o papel dizia. N\xC3O repita aqui o que j\xE1 foi para os outros campos.'
1210
+ },
1211
+ paid: {
1212
+ type: "boolean",
1213
+ description: 'true quando a conta J\xC1 FOI PAGA \u2014 "pago hoje", "paguei via Pix", um recibo quitado. Uma conta paga tem um terceiro passo: registrar o pagamento. N\xE3o marque true por um boleto ou uma nota que ainda vai vencer.'
1214
+ },
1215
+ paymentMethod: {
1216
+ type: "string",
1217
+ description: 'COMO foi pago, se a pessoa disse ("Pix", "dinheiro", "cart\xE3o de cr\xE9dito"). N\xE3o chute: sem isto a ferramenta PERGUNTA, oferecendo as formas que este neg\xF3cio aceita.'
1218
+ },
1219
+ paymentDate: {
1220
+ type: "string",
1221
+ description: 'QUANDO foi pago, se disserem. "pago hoje" \xE9 hoje.'
1222
+ },
1223
+ redirect: {
1224
+ type: "boolean",
1225
+ description: "Levar a pessoa at\xE9 a conta rec\xE9m-criada (default: true). Deixe true no caso normal \u2014 quem acabou de lan\xE7ar uma conta quer v\xEA-la. Mande FALSE quando levar embora atrapalharia: ela est\xE1 lan\xE7ando V\xC1RIAS contas em sequ\xEAncia, est\xE1 no meio de outra tarefa (uma tela de cadastro, uma concilia\xE7\xE3o) e s\xF3 passou a conta de lado, ou pediu explicitamente para ficar onde est\xE1. Com false a conta \xE9 criada do mesmo jeito e o link volta na resposta."
1226
+ }
1227
+ },
1228
+ // `amount` deixa de ser obrigatório SÓ porque o passo 0 pode tê-lo lido: sem
1229
+ // leitura guardada a ferramenta recusa do mesmo jeito, com a mesma mensagem.
1230
+ required: []
1231
+ }
1232
+ };
1233
+ var REGISTER_PAYMENT = {
1234
+ id: "financial.register-payable-payment",
1235
+ name: "registerPayablePayment",
1236
+ description: "Step 3 of the payable checklist: registers that a bill WAS PAID \u2014 the money leaving, with its method and date. Call it with the `invoiceId` a previous `createPayable` returned, and the `method` the person chose (send the option id when they tapped one). Opens an editable confirmation before writing. Use it whenever a bill already recorded turns out to be settled; never to create the bill itself.",
1237
+ icon: "BadgeDollarSign",
1238
+ mode: "persist",
1239
+ execution: { plane: "client" },
1240
+ requiresConfirmation: false,
1241
+ category: "Finance",
1242
+ role: "finance",
1243
+ permission: { feature: "financial", action: "update" },
1244
+ parameters: {
1245
+ type: "object",
1246
+ properties: {
1247
+ invoiceId: { type: "string", description: "Id da conta a pagar, devolvido por createPayable." },
1248
+ method: {
1249
+ type: "string",
1250
+ description: 'A forma de pagamento \u2014 o id da op\xE7\xE3o que a pessoa tocou, ou o nome dela ("Pix", "Dinheiro").'
1251
+ },
1252
+ paymentDate: { type: "string", description: "A data do pagamento. Omita para hoje." }
1253
+ },
1254
+ required: ["invoiceId"]
1255
+ }
1256
+ };
1257
+ var OPEN_FULL_FORM = {
1258
+ id: "financial.open-payable-form",
1259
+ name: "openPayableForm",
1260
+ description: 'Opens the full "new payable" screen, empty or pre-filled, for a bill that needs more than a one-line entry: several installments, a receipt to attach, a ledger account or cost centre. Writes nothing. Prefer `createPayable` for an ordinary single bill \u2014 this one is the long form.',
1261
+ icon: "FilePlus",
1262
+ mode: "read",
1263
+ execution: { plane: "client" },
1264
+ category: "Finance",
1265
+ role: "finance",
1266
+ permission: { feature: "financial", action: "create" },
1267
+ parameters: { type: "object", properties: {} }
1268
+ };
1269
+ function supplierRef(id, label) {
1270
+ if (!resolveEntityRoute("person", "supplier")) return {};
1271
+ return { ref: { id, label, archetype: "person:supplier" } };
1272
+ }
1273
+ async function resolveSupplier(lookup, name) {
1274
+ const wanted = name.trim().toLowerCase();
1275
+ const hits = await lookup.search(name).catch(() => []);
1276
+ const exact = hits.find((r) => r.label.trim().toLowerCase() === wanted);
1277
+ if (exact) return { ok: true, id: exact.id, name: exact.label, created: false };
1278
+ if (!lookup.create) return { ok: true, name, created: false };
1279
+ const decision = await useChatStore.getState().requestConfirmation({
1280
+ id: `supplier-${Date.now()}`,
1281
+ toolName: "createPayable",
1282
+ title: "Novo fornecedor",
1283
+ params: { name },
1284
+ plane: "client",
1285
+ scope: "financial:supplier",
1286
+ scopeLabel: "Cadastrar fornecedor",
1287
+ fields: [
1288
+ { key: "name", label: "Nome", type: "text", required: true },
1289
+ { key: "document_number", label: "CNPJ / CPF", type: "text" },
1290
+ { key: "phone", label: "Telefone", type: "text" },
1291
+ { key: "email", label: "E-mail", type: "text" }
1292
+ ]
1293
+ });
1294
+ if (!decision.approved) return { ok: false, cancelled: true };
1295
+ const draft = { ...{ name }, ...decision.values ?? {} };
1296
+ const finalName = text(draft.name) ?? name;
1297
+ const made = await lookup.create({
1298
+ ...draft,
1299
+ name: finalName
1300
+ });
1301
+ return { ok: true, id: made.id, name: made.label, created: true };
1302
+ }
1303
+ async function paymentOptions(provider) {
1304
+ const types = await provider.getPaymentMethodTypes().catch(() => []);
1305
+ return types.filter((t2) => t2.isActive !== false).map((t2) => ({
1306
+ id: t2.id,
1307
+ label: t2.name,
1308
+ transactionType: t2.transactionType,
1309
+ allowedAccountTypes: t2.allowedAccountTypes
1310
+ }));
1311
+ }
1312
+ var ACCOUNT_TYPES_FOR_TX = {
1313
+ cash: ["cash_physical", "cash_session"],
1314
+ credit_card: ["card_receivable_credit"],
1315
+ debit_card: ["card_receivable_debit"],
1316
+ pix: ["bank"],
1317
+ bank_transfer: ["bank"],
1318
+ boleto: ["bank"],
1319
+ check: ["bank"],
1320
+ voucher: ["bank"]
1321
+ };
1322
+ async function eligibleAccounts(provider, method) {
1323
+ const accounts = (await provider.getBankAccounts().catch(() => [])).filter((a) => a.isActive !== false);
1324
+ const allowed = method.allowedAccountTypes?.length ? method.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[method.transactionType ?? ""];
1325
+ if (!allowed) return accounts;
1326
+ return accounts.filter((a) => allowed.includes(a.accountType));
1327
+ }
1328
+ function matchPaymentOption(options, raw) {
1329
+ const wanted = typeof raw === "string" ? raw.trim().toLowerCase() : "";
1330
+ if (!wanted) return null;
1331
+ return options.find((o) => o.id.toLowerCase() === wanted) ?? options.find((o) => o.label.toLowerCase() === wanted) ?? options.find((o) => o.label.toLowerCase().includes(wanted) || wanted.includes(o.label.toLowerCase())) ?? null;
1332
+ }
1333
+ async function settlePayable(provider, input) {
1334
+ const schedule = await provider.getMovements({
1335
+ invoiceId: input.invoiceId,
1336
+ direction: "debit",
1337
+ status: "pending"
1338
+ });
1339
+ const bill = schedule.data.find((m) => m.movementKind === "bill");
1340
+ if (!bill) {
1341
+ return {
1342
+ ok: false,
1343
+ error: "no_open_bill",
1344
+ message: "That bill has no open installment \u2014 it may already be settled. Say so and stop."
1345
+ };
1346
+ }
1347
+ const invoice = await provider.getInvoiceById(input.invoiceId).catch(() => null);
1348
+ const money3 = (n) => n.toLocaleString("pt-BR", { style: "currency", currency: input.currency ?? "BRL" });
1349
+ const invoiceLabel = [
1350
+ invoice?.fiscalNumber ? `#${invoice.fiscalNumber}` : null,
1351
+ invoice?.contactName ?? null,
1352
+ money3(bill.amount)
1353
+ ].filter(Boolean).join(" \u2014 ") || money3(bill.amount);
1354
+ const needsAccount = input.method.transactionType !== "cash";
1355
+ const accounts = needsAccount ? await eligibleAccounts(provider, input.method) : [];
1356
+ if (needsAccount && !accounts.length) {
1357
+ return {
1358
+ ok: false,
1359
+ error: "no_eligible_account",
1360
+ message: `The bill is recorded but NOT settled: this business has no account that can settle a ${input.method.label} payment. Say that, and that it has to be registered from the payables screen.`
1361
+ };
1362
+ }
1363
+ const onlyAccount = accounts.length === 1 ? accounts[0] : void 0;
1364
+ const paymentDate = parseReceiptDate(input.paymentDate) ?? today();
1365
+ const decision = await useChatStore.getState().requestConfirmation({
1366
+ id: `payment-${bill.id}`,
1367
+ toolName: "registerPayablePayment",
1368
+ title: "Registrar pagamento",
1369
+ params: {
1370
+ invoice: invoiceLabel,
1371
+ method: input.method.label,
1372
+ accountId: onlyAccount?.id,
1373
+ paymentDate,
1374
+ amount: bill.amount
1375
+ },
1376
+ plane: "client",
1377
+ scope: "financial:payment",
1378
+ scopeLabel: "Registrar pagamento",
1379
+ fields: [
1380
+ // Qual conta está sendo quitada, por extenso e primeiro. Só de leitura:
1381
+ // é a identidade do que se assina, não um campo a editar.
1382
+ { key: "invoice", label: "Conta a pagar", type: "text", readOnly: true },
1383
+ { key: "method", label: "Forma de pagamento", type: "text", required: true, readOnly: true },
1384
+ ...needsAccount ? [{
1385
+ key: "accountId",
1386
+ label: "Conta de sa\xEDda",
1387
+ type: "select",
1388
+ required: true,
1389
+ options: accounts.map((a) => ({ value: a.id, label: a.name }))
1390
+ }] : [],
1391
+ { key: "paymentDate", label: "Data do pagamento", type: "date", required: true },
1392
+ { key: "amount", label: "Valor pago", type: "number", required: true, min: 0 }
1393
+ ]
1394
+ });
1395
+ if (!decision.approved) {
1396
+ return {
1397
+ ok: false,
1398
+ cancelled: true,
1399
+ reason: "user_declined",
1400
+ message: "The bill is recorded but NOT settled \u2014 the payment step was declined. Say exactly that."
1401
+ };
1402
+ }
1403
+ const values = { paymentDate, amount: bill.amount, accountId: onlyAccount?.id, ...decision.values ?? {} };
1404
+ const accountId = typeof values.accountId === "string" && values.accountId ? values.accountId : void 0;
1405
+ if (needsAccount && !accountId) {
1406
+ return {
1407
+ ok: false,
1408
+ error: "no_account",
1409
+ message: "NOTHING WAS SETTLED. The form came back with no outgoing account \u2014 do not say it was paid."
1410
+ };
1411
+ }
1412
+ await provider.payMovement({
1413
+ movementId: bill.id,
1414
+ amount: parseReceiptAmount(values.amount) ?? bill.amount,
1415
+ paymentDate: parseReceiptDate(values.paymentDate) ?? paymentDate,
1416
+ paymentMethodTypeId: input.method.id,
1417
+ bankAccountId: accountId
1418
+ });
1419
+ return {
1420
+ ok: true,
1421
+ settled: true,
1422
+ invoiceId: input.invoiceId,
1423
+ movementId: bill.id,
1424
+ method: input.method.label,
1425
+ account: accounts.find((a) => a.id === accountId)?.name,
1426
+ paymentDate: parseReceiptDate(values.paymentDate) ?? paymentDate,
1427
+ // O botão que abre a conta quitada. `path` existe para exatamente isto —
1428
+ // uma tela de plugin que o mapa de rotas por arquétipo não sabe resolver.
1429
+ ref: {
1430
+ id: input.invoiceId,
1431
+ label: invoiceLabel,
1432
+ path: `/financial/payables/${input.invoiceId}`
1433
+ },
1434
+ message: `Pago via ${input.method.label}. Diga em UMA frase que a conta foi lan\xE7ada e quitada, e pare.`
1435
+ };
1436
+ }
1437
+ var recentPayables = [];
1438
+ var RECENT_WINDOW_MS = 10 * 60 * 1e3;
1439
+ function rememberPayable(entry) {
1440
+ recentPayables = [...recentPayables.filter((p) => p.invoiceId !== entry.invoiceId), entry].slice(-10);
1441
+ }
1442
+ function findRecentPayable(supplier, amount) {
1443
+ const now2 = Date.now();
1444
+ recentPayables = recentPayables.filter((p) => now2 - p.at < RECENT_WINDOW_MS);
1445
+ const wanted = supplier.trim().toLowerCase();
1446
+ return recentPayables.find(
1447
+ (p) => p.supplier.trim().toLowerCase() === wanted && Math.abs(p.amount - amount) < 5e-3
1448
+ );
1449
+ }
1450
+ function asChoiceCards(choices) {
1451
+ const usable = choices.filter((c) => c.options.length >= 2);
1452
+ return usable.length ? { kind: "record_choice", choices: usable } : {};
1453
+ }
1454
+ function gapChoices(input) {
1455
+ const questions = [];
1456
+ const { supplierName, supplier, method, account } = input;
1457
+ if (supplier.status !== "found" && supplierName) {
1458
+ const candidates = supplier.candidates ?? [];
1459
+ questions.push({
1460
+ id: "supplier",
1461
+ question: candidates.length ? `"${supplierName}" n\xE3o est\xE1 cadastrado. \xC9 algum destes?` : `"${supplierName}" ainda n\xE3o est\xE1 cadastrado. Cadastrar agora?`,
1462
+ options: [
1463
+ // Os parecidos PRIMEIRO: a razão de eles existirem é evitar o
1464
+ // duplicado, então a opção de criar não pode ser a mais fácil de tocar.
1465
+ ...candidates.slice(0, 4).map((c) => ({
1466
+ id: `supplier:${c.id}`,
1467
+ label: c.name,
1468
+ meta: c.why
1469
+ })),
1470
+ { id: "supplier:create", label: `Criar fornecedor "${supplierName}"` },
1471
+ { id: "supplier:cancel", label: "Cancelar" }
1472
+ ]
1473
+ });
1474
+ }
1475
+ if (method.status !== "found") {
1476
+ const available = method.available ?? [];
1477
+ questions.push({
1478
+ id: "method",
1479
+ question: method.spoken ? `O papel diz "${String(method.spoken)}", que n\xE3o est\xE1 cadastrado. Qual forma corresponde?` : "Como esta conta foi (ou vai ser) paga?",
1480
+ options: [
1481
+ ...available.slice(0, 5).map((o) => ({ id: `method:${o.id}`, label: o.label })),
1482
+ { id: "method:none", label: "Deixar em aberto" }
1483
+ ]
1484
+ });
1485
+ }
1486
+ if (account.status === "ambiguous") {
1487
+ const options = account.options ?? [];
1488
+ questions.push({
1489
+ id: "account",
1490
+ question: account.cardLast4 ? `Nenhuma conta cadastrada tem o final ${String(account.cardLast4)}. De qual conta saiu?` : "De qual conta o dinheiro saiu?",
1491
+ options: [
1492
+ ...options.slice(0, 5).map((o) => ({ id: `account:${o.id}`, label: o.name })),
1493
+ { id: "account:skip", label: "Decidir na hora de pagar" }
1494
+ ]
1495
+ });
1496
+ }
1497
+ return questions;
1498
+ }
1499
+ function askHowItWasPaid(invoiceId, options) {
1500
+ return {
1501
+ kind: "record_choice",
1502
+ choices: [{
1503
+ id: `payment-method-${invoiceId}`,
1504
+ question: "Foi pago como?",
1505
+ options
1506
+ }],
1507
+ invoiceId,
1508
+ nextStep: "registerPayablePayment",
1509
+ message: `The bill is recorded but NOT yet settled. The person is being asked HOW it was paid \u2014 the options are on screen. Do not list them again in prose and do not guess one. When they answer, call registerPayablePayment with invoiceId "${invoiceId}" and the option id they chose.`
1510
+ };
1511
+ }
1512
+ function createPayableTools({ config, provider }) {
1513
+ const lookup = config.contactLookup ?? createArchetypeLookup({ archetype: "person", kind: "supplier" });
1514
+ return [
1515
+ {
1516
+ tool: RESOLVE_CONTEXT,
1517
+ execute: async (args) => {
1518
+ consumePayableDraft();
1519
+ const supplierName = text(args.supplier);
1520
+ const supplier = supplierName ? await findSupplier(lookup, supplierName, args.supplierDocument) : { status: "missing", candidates: [] };
1521
+ const method = await findMethod(provider, args);
1522
+ const account = await findAccount(provider, method, args.cardLast4);
1523
+ const gaps = [
1524
+ supplier.status !== "found" ? "supplier" : null,
1525
+ method.status !== "found" ? "method" : null,
1526
+ account.status === "ambiguous" || account.status === "missing" ? "account" : null
1527
+ ].filter(Boolean);
1528
+ const choices = gapChoices({ supplierName, supplier, method, account });
1529
+ const document2 = normalizeDocument(args);
1530
+ rememberDocument(document2, {
1531
+ ...text(args.supplier) ? { source: text(args.supplier) } : {},
1532
+ ...useChatStore.getState().conversationId ? { conversationId: useChatStore.getState().conversationId } : {}
1533
+ });
1534
+ return {
1535
+ ok: true,
1536
+ read: {
1537
+ supplier: supplierName,
1538
+ supplierDocument: onlyDigits(args.supplierDocument) || void 0,
1539
+ paymentMethod: text(args.paymentMethod),
1540
+ cardBrand: text(args.cardBrand),
1541
+ cardLast4: lastFour(args.cardLast4) || void 0
1542
+ },
1543
+ supplier,
1544
+ method,
1545
+ account,
1546
+ gaps,
1547
+ document: document2,
1548
+ ...asChoiceCards(choices),
1549
+ message: 'NOTHING WAS WRITTEN \u2014 this was a check. Tell the person the result as ONE short line per dependency, saying which already exist and which are missing (e.g. "Fornecedor Printi: ja existe - Pix: cadastrado - cartao final 4242: nao encontrei"). FIRST say what you READ, in one line, from `document`: how many items, the total, the method, the order ref, and the dates \u2014 and when `document.dueDate` is null say plainly there is no due date on the paper (\xE0 vista) and that the form will ask. If `document.reconciles` is false and there are items and a total, say the numbers do not add up and ask, instead of choosing one. The extraction is SAVED: when you call createPayable next, you may omit items/shipping/total/dates entirely \u2014 it reuses exactly what was read here. Do NOT re-read the document to fill them in again; re-reading from memory is where wrong numbers come from. ' + (choices.length ? "The open questions are ALREADY ON SCREEN as tappable buttons \u2014 do NOT repeat the options in prose and do NOT ask the person to type an answer. End your reply after the checklist and WAIT for the tap. When they answer, the option id says what to do: `supplier:create` means create the supplier under the name in `read.supplier` (call createPayable and its supplier step will confirm it), `supplier:<uuid>` means use that existing one, `method:<id>` is the payment method, `account:<id>` the outgoing account, and anything `:cancel` means stop. " : "Then continue with createPayable for the bill, and the payment. ") + "Never invent a value the paper did not state."
1550
+ };
1551
+ }
1552
+ },
1553
+ {
1554
+ tool: OPEN_FULL_FORM,
1555
+ execute: async () => {
1556
+ adminNavigateTo("/financial/payables/new");
1557
+ return { ok: true, opened: "/financial/payables/new" };
1558
+ }
1559
+ },
1560
+ {
1561
+ tool: CREATE_PAYABLE,
1562
+ execute: async (args) => {
1563
+ if (hasLivePayableDraft()) {
1564
+ return {
1565
+ ok: false,
1566
+ error: "draft_open",
1567
+ message: "NOTHING WAS WRITTEN, and no second bill was created. The payable form is OPEN ON SCREEN with the draft this conversation has been building \u2014 that draft IS this bill. Continue in it: use `fillPayableDraft` for what is missing and `submitPayableDraft` to save. Do not call createPayable again while that form is open."
1568
+ };
1569
+ }
1570
+ const remembered = rememberedDocument();
1571
+ if (args.amount === void 0 && remembered?.total !== void 0) {
1572
+ args = { ...args, amount: String(remembered.total) };
1573
+ }
1574
+ if (typeof args.amount !== "string") {
1575
+ return {
1576
+ ok: false,
1577
+ error: "amount_must_be_literal",
1578
+ message: 'Send `amount` as the TEXT that was written, punctuation included ("50,00", "1.234,56") \u2014 never as a number. A decimal comma is a thousands separator in English, so converting it yourself turns fifty into five thousand. Resend the literal.'
1579
+ };
1580
+ }
1581
+ const figures = parseReceiptAmount(args.amount);
1582
+ const spelled = parseAmountInWords(args.amountInWords);
1583
+ const amount = spelled ?? figures;
1584
+ if (amount === null) {
1585
+ return {
1586
+ ok: false,
1587
+ error: "invalid_amount",
1588
+ message: "That is not a readable amount. If the total is illegible or was never said, ask the person."
1589
+ };
1590
+ }
1591
+ const lines = args.items === void 0 && remembered?.items.length ? { items: remembered.items, error: void 0 } : parseLineItems(args.items);
1592
+ if (lines.error) return { ok: false, error: "invalid_items", message: lines.error };
1593
+ const shipping = args.shipping === void 0 ? remembered?.shipping ?? null : parseReceiptAmount(args.shipping);
1594
+ const lineSum = round2(lines.items.reduce((s, i) => s + i.quantity * i.unitPrice, 0));
1595
+ const computed = round2(lineSum + (shipping ?? 0));
1596
+ const candidates = [
1597
+ { value: amount, from: "argumento" },
1598
+ ...remembered?.total !== void 0 ? [{ value: remembered.total, from: "leitura" }] : []
1599
+ ];
1600
+ const agreed = lines.items.length ? candidates.find((c) => Math.abs(computed - c.value) <= 0.02) : candidates[0];
1601
+ if (lines.items.length && !agreed) {
1602
+ return {
1603
+ ok: false,
1604
+ error: "total_mismatch",
1605
+ lineSum,
1606
+ shipping: shipping ?? void 0,
1607
+ computed,
1608
+ total: amount,
1609
+ ...remembered?.total !== void 0 ? { totalFromDocument: remembered.total } : {},
1610
+ message: `NOTHING WAS WRITTEN. The lines do not add up to the total: the item(s) sum to ${lineSum}${shipping ? ` plus ${shipping} of shipping` : ""} = ${computed}, but the total given is ${amount}. Do NOT pick one of them and do not adjust a line to make it fit. Re-read the document \u2014 a line may have been missed, a quantity misread, or a discount or shipping line not captured. If the paper genuinely disagrees with itself, ask the person which is right.`
1611
+ };
1612
+ }
1613
+ const reconciledTotal = agreed?.value ?? amount;
1614
+ const correctedFromRead = agreed?.from === "leitura" && Math.abs(agreed.value - amount) > 5e-3;
1615
+ const dueDate = parseReceiptDate(args.dueDate) ?? remembered?.dueDate ?? void 0;
1616
+ const issueDate = parseReceiptDate(args.date) ?? remembered?.documentDate ?? parseReceiptDate(findDateIn(args.notes));
1617
+ const params = { amount };
1618
+ const supplierName = text(args.supplier);
1619
+ if (supplierName) params.supplier = supplierName;
1620
+ const description = text(args.description);
1621
+ if (description) params.description = description;
1622
+ if (dueDate) params.dueDate = dueDate;
1623
+ if (issueDate) params.issueDate = issueDate;
1624
+ const carried = [
1625
+ remembered?.orderRef ? `Pedido: ${remembered.orderRef}` : null,
1626
+ remembered?.deliveryDate ? `Entrega: ${remembered.deliveryDate}` : null
1627
+ ].filter(Boolean).join(" \xB7 ");
1628
+ const notes = text(args.notes) ?? (carried || void 0);
1629
+ if (notes) params.notes = notes;
1630
+ if (lines.items.length) {
1631
+ params.items = lines.items.map((i) => `${i.quantity}\xD7 ${i.description} @ ${i.unitPrice}`).join(" \xB7 ");
1632
+ params.amount = computed;
1633
+ }
1634
+ if (shipping) params.shipping = shipping;
1635
+ if (supplierName) {
1636
+ const already = findRecentPayable(supplierName, amount);
1637
+ if (already) {
1638
+ return {
1639
+ ok: false,
1640
+ error: "already_recorded",
1641
+ invoiceId: already.invoiceId,
1642
+ message: `NOTHING WAS WRITTEN and no second bill was created. The bill for ${supplierName} of ${amount} was ALREADY recorded in this conversation \u2014 its invoiceId is "${already.invoiceId}". If the person is saying it was PAID ("marca como pago", "paguei no Pix"), call registerPayablePayment with invoiceId "${already.invoiceId}". Only call createPayable again if this is genuinely a DIFFERENT bill, with a different amount or supplier.`
1643
+ };
1644
+ }
1645
+ }
1646
+ let supplier = null;
1647
+ if (supplierName) {
1648
+ const step = await resolveSupplier(lookup, supplierName);
1649
+ if (!step.ok) {
1650
+ return {
1651
+ ok: false,
1652
+ cancelled: true,
1653
+ reason: "user_declined_supplier",
1654
+ message: "NOTHING WAS WRITTEN. The person declined creating the supplier, so the bill has nobody to point at. Ask which existing supplier it belongs to \u2014 do not retry the same name."
1655
+ };
1656
+ }
1657
+ supplier = { id: step.id, name: step.name, created: step.created };
1658
+ params.supplier = step.name;
1659
+ }
1660
+ const decision = await useChatStore.getState().requestConfirmation({
1661
+ id: `payable-${Date.now()}`,
1662
+ toolName: "createPayable",
1663
+ // Nomeia o que está sendo criado. Era `config.labels.payablesNew`,
1664
+ // que é o rótulo da AÇÃO na sub-navegação ("New") — no topo de um
1665
+ // cartão de confirmação ele dizia só "New" e não nomeava nada.
1666
+ title: "Nova conta a pagar",
1667
+ params,
1668
+ plane: "client",
1669
+ scope: "financial:payable",
1670
+ scopeLabel: "Lan\xE7ar conta a pagar",
1671
+ fields: [
1672
+ { key: "supplier", label: "Fornecedor", type: "text", required: true },
1673
+ { key: "description", label: "Descri\xE7\xE3o", type: "text", required: true },
1674
+ // As linhas lidas da tabela, à vista e só de leitura: é o que a
1675
+ // pessoa confere para saber se a leitura do papel bateu.
1676
+ ...lines.items.length ? [{ key: "items", label: "Itens", type: "text", readOnly: true }] : [],
1677
+ ...shipping ? [{ key: "shipping", label: "Frete", type: "number", readOnly: true }] : [],
1678
+ // Com tabela lida, o valor É a soma conferida — editá-lo aqui o
1679
+ // separaria das linhas, e o banco recusa uma fatura cujas parcelas
1680
+ // não fecham com os itens. Sem tabela, ele continua editável.
1681
+ {
1682
+ key: "amount",
1683
+ label: "Valor total",
1684
+ type: "number",
1685
+ required: true,
1686
+ min: 0,
1687
+ ...lines.items.length ? { readOnly: true } : {}
1688
+ },
1689
+ { key: "dueDate", label: "Vencimento", type: "date", required: true },
1690
+ { key: "issueDate", label: "Data do documento", type: "date", default: today() },
1691
+ { key: "notes", label: "Observa\xE7\xF5es", type: "text" }
1692
+ ]
1693
+ });
1694
+ if (!decision.approved) return { ok: false, cancelled: true, reason: "user_declined" };
1695
+ const values = { ...params, ...decision.values ?? {} };
1696
+ const finalAmount = lines.items.length ? computed : parseReceiptAmount(values.amount) ?? amount;
1697
+ const finalSupplier = text(values.supplier);
1698
+ const finalDescription = text(values.description) ?? finalSupplier ?? "";
1699
+ const finalDue = parseReceiptDate(values.dueDate);
1700
+ const finalIssue = parseReceiptDate(values.issueDate) ?? today();
1701
+ if (!finalSupplier || !finalDue) {
1702
+ return {
1703
+ ok: false,
1704
+ error: "incomplete",
1705
+ message: "NOTHING WAS WRITTEN. The form came back without a supplier or a due date \u2014 do not tell the user it worked."
1706
+ };
1707
+ }
1708
+ let resolvedSupplier = supplier;
1709
+ if (!resolvedSupplier || resolvedSupplier.name !== finalSupplier) {
1710
+ const again = await resolveSupplier(lookup, finalSupplier);
1711
+ if (!again.ok) {
1712
+ return {
1713
+ ok: false,
1714
+ cancelled: true,
1715
+ reason: "user_declined_supplier",
1716
+ message: "NOTHING WAS WRITTEN. The supplier step was declined \u2014 ask which supplier this bill belongs to."
1717
+ };
1718
+ }
1719
+ resolvedSupplier = { id: again.id, name: again.name, created: again.created };
1720
+ }
1721
+ const invoice = await provider.createInvoice({
1722
+ direction: "debit",
1723
+ invoiceDate: finalIssue,
1724
+ contactId: resolvedSupplier.id,
1725
+ contactName: resolvedSupplier.name,
1726
+ observations: text(values.notes),
1727
+ // A nota que o papel descreve, linha por linha. Sem tabela, a conta
1728
+ // nasce com uma linha só — que é o que um recibo de táxi é. O FRETE é
1729
+ // linha PRÓPRIA: somado ao preço de um produto ele sumiria dentro
1730
+ // dele, e a conciliação com a transportadora não teria onde olhar.
1731
+ items: [
1732
+ ...lines.items.length ? lines.items.map((line) => ({
1733
+ itemKind: "other",
1734
+ description: line.description,
1735
+ quantity: line.quantity,
1736
+ unitPrice: line.unitPrice
1737
+ })) : [{
1738
+ itemKind: "other",
1739
+ description: finalDescription,
1740
+ quantity: 1,
1741
+ unitPrice: round2(finalAmount - (shipping ?? 0))
1742
+ }],
1743
+ ...shipping ? [{ itemKind: "other", description: "Frete", quantity: 1, unitPrice: shipping }] : []
1744
+ ],
1745
+ installments: [{
1746
+ direction: "debit",
1747
+ movementKind: "bill",
1748
+ amount: finalAmount,
1749
+ dueDate: finalDue,
1750
+ installmentNumber: 1
1751
+ }]
1752
+ });
1753
+ const id = invoice?.id;
1754
+ if (id) {
1755
+ rememberPayable({ invoiceId: id, supplier: resolvedSupplier.name, amount: finalAmount, at: Date.now() });
1756
+ }
1757
+ consumePayableDraft();
1758
+ const drift = spelled !== null && figures !== null && Math.abs(spelled - figures) > 5e-3 ? { note: `Os algarismos pareciam ${figures} e o extenso diz ${spelled}. Usei o extenso.` } : {};
1759
+ const invoicePath = id ? `/financial/payables/${id}` : null;
1760
+ const redirected = invoicePath !== null && args.redirect !== false;
1761
+ if (redirected) {
1762
+ adminNavigateTo(invoicePath);
1763
+ revealPage();
1764
+ }
1765
+ const invoiceLink = invoicePath ? { ref: { id, label: `${resolvedSupplier.name} \u2014 ${finalDescription}`, path: invoicePath } } : {};
1766
+ if (args.paid === true && id) {
1767
+ const options = await paymentOptions(provider);
1768
+ const chosen = matchPaymentOption(options, args.paymentMethod);
1769
+ if (!chosen) {
1770
+ if (options.length >= 2) {
1771
+ return {
1772
+ ...askHowItWasPaid(id, options),
1773
+ supplier: resolvedSupplier.name,
1774
+ supplierCreated: resolvedSupplier.created,
1775
+ amount: finalAmount,
1776
+ openedInvoice: redirected,
1777
+ ...drift
1778
+ };
1779
+ }
1780
+ return {
1781
+ ok: true,
1782
+ invoiceId: id,
1783
+ settled: false,
1784
+ supplier: resolvedSupplier.name,
1785
+ amount: finalAmount,
1786
+ dueDate: finalDue,
1787
+ openedInvoice: redirected,
1788
+ ...invoiceLink,
1789
+ message: "A conta foi lan\xE7ada, mas o pagamento N\xC3O foi registrado: este neg\xF3cio n\xE3o tem formas de pagamento cadastradas para escolher. Diga isso e ofere\xE7a registrar a baixa pela tela.",
1790
+ ...drift
1791
+ };
1792
+ }
1793
+ const settled = await settlePayable(provider, {
1794
+ invoiceId: id,
1795
+ method: chosen,
1796
+ paymentDate: typeof args.paymentDate === "string" ? args.paymentDate : void 0,
1797
+ currency: config.currency?.code
1798
+ });
1799
+ return {
1800
+ ...settled,
1801
+ invoiceId: id,
1802
+ supplier: resolvedSupplier.name,
1803
+ supplierCreated: resolvedSupplier.created,
1804
+ amount: finalAmount,
1805
+ openedInvoice: redirected,
1806
+ ...drift
1807
+ };
1808
+ }
1809
+ return {
1810
+ ok: true,
1811
+ invoiceId: id,
1812
+ settled: false,
1813
+ supplier: resolvedSupplier.name,
1814
+ supplierCreated: resolvedSupplier.created,
1815
+ amount: finalAmount,
1816
+ dueDate: finalDue,
1817
+ openedInvoice: redirected,
1818
+ ...lines.items.length ? { itemsWritten: lines.items.length } : {},
1819
+ // Dito, nunca silencioso: se o total que veio na chamada perdeu os
1820
+ // centavos e o da leitura fechou com as linhas, quem escreveu foi a
1821
+ // leitura — e quem confere merece saber disso.
1822
+ ...correctedFromRead ? {
1823
+ correctedTotal: {
1824
+ sent: amount,
1825
+ written: reconciledTotal,
1826
+ why: "o total enviado n\xE3o fechava com as linhas; o lido do documento fechou"
1827
+ }
1828
+ } : {},
1829
+ ...shipping ? { shipping } : {},
1830
+ // A CONTA é o destino principal — é o que a pessoa acabou de criar.
1831
+ ...invoiceLink,
1832
+ // O fornecedor vai como segundo botão, e só onde existe tela para ele
1833
+ // (ver supplierRef): `extractRecordLinks` lê `ref` e `record.ref`.
1834
+ ...resolvedSupplier.id ? { record: supplierRef(resolvedSupplier.id, resolvedSupplier.name) } : {},
1835
+ // O que fazer DEPOIS, dito com o id dentro. Sem esta linha, "marca
1836
+ // como pago no pix hoje" na mensagem seguinte virou uma SEGUNDA conta
1837
+ // a pagar: o modelo tinha a ferramenta de baixa e nenhuma frase
1838
+ // ligando-a a esta fatura.
1839
+ message: `A conta a pagar de ${resolvedSupplier.name} foi lan\xE7ada (invoiceId "${id}"), em aberto. ` + (resolvedSupplier.created ? "O fornecedor foi cadastrado agora. " : "") + (redirected ? "A tela da conta J\xC1 foi aberta \u2014 n\xE3o ofere\xE7a abri-la. " : "") + `Diga isso em UMA frase e pare \u2014 n\xE3o repita os campos, a pessoa acabou de conferi-los. IMPORTANTE: se a pessoa disser depois que esta conta foi paga ("marca como pago", "paguei no Pix", "foi pago hoje"), chame registerPayablePayment com invoiceId "${id}". N\xC3O chame createPayable de novo \u2014 isso criaria uma segunda conta para a mesma despesa.`,
1840
+ ...drift
1841
+ };
1842
+ }
1843
+ },
1844
+ {
1845
+ tool: REGISTER_PAYMENT,
1846
+ execute: async (args) => {
1847
+ const invoiceId = typeof args.invoiceId === "string" ? args.invoiceId : "";
1848
+ if (!invoiceId) {
1849
+ return {
1850
+ ok: false,
1851
+ error: "missing_invoice",
1852
+ message: "Which bill? Pass the `invoiceId` that createPayable returned."
1853
+ };
1854
+ }
1855
+ const options = await paymentOptions(provider);
1856
+ const chosen = matchPaymentOption(options, args.method);
1857
+ if (!chosen) {
1858
+ if (options.length < 2) {
1859
+ return {
1860
+ ok: false,
1861
+ error: "no_payment_methods",
1862
+ message: "This business has no payment methods to choose from. Say so instead of guessing one."
1863
+ };
1864
+ }
1865
+ return askHowItWasPaid(invoiceId, options);
1866
+ }
1867
+ return settlePayable(provider, {
1868
+ invoiceId,
1869
+ method: chosen,
1870
+ paymentDate: typeof args.paymentDate === "string" ? args.paymentDate : void 0,
1871
+ currency: config.currency?.code
1872
+ });
1873
+ }
1874
+ }
1875
+ ];
1876
+ }
1877
+ var liveDraft = null;
1878
+ function hasLivePayableDraft() {
1879
+ return liveDraft !== null;
1880
+ }
1881
+ var COMPANY_NOISE = /* @__PURE__ */ new Set([
1882
+ "ltda",
1883
+ "ltd",
1884
+ "me",
1885
+ "epp",
1886
+ "eireli",
1887
+ "sa",
1888
+ "mei",
1889
+ "cia",
1890
+ "ei",
1891
+ "inc",
1892
+ "comercio",
1893
+ "servicos",
1894
+ "industria",
1895
+ "do",
1896
+ "da",
1897
+ "de",
1898
+ "dos",
1899
+ "das",
1900
+ "e"
1901
+ ]);
1902
+ function normalizeCompanyName(raw) {
1903
+ if (typeof raw !== "string") return "";
1904
+ return raw.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w && !COMPANY_NOISE.has(w)).join(" ").trim();
1905
+ }
1906
+ function onlyDigits(raw) {
1907
+ return typeof raw === "string" ? raw.replace(/\D/g, "") : "";
1908
+ }
1909
+ function lastFour(raw) {
1910
+ const digits = onlyDigits(raw);
1911
+ return digits.length >= 4 ? digits.slice(-4) : "";
1912
+ }
1913
+ async function findSupplier(lookup, rawName, rawDocument) {
1914
+ const wanted = normalizeCompanyName(rawName);
1915
+ const doc = onlyDigits(rawDocument);
1916
+ if (!wanted) return { status: "missing", candidates: [] };
1917
+ const core = wanted.split(" ")[0] ?? wanted;
1918
+ const seen = /* @__PURE__ */ new Map();
1919
+ for (const query of core === wanted ? [core] : [core, wanted]) {
1920
+ for (const hit of await lookup.search(query).catch(() => [])) {
1921
+ const row = hit.data ?? {};
1922
+ seen.set(hit.id, {
1923
+ id: hit.id,
1924
+ name: hit.label,
1925
+ document: onlyDigits(row.document_number) || void 0,
1926
+ why: ""
1927
+ });
1928
+ }
1929
+ }
1930
+ const all = [...seen.values()];
1931
+ if (doc) {
1932
+ const byDoc = all.find((c) => c.document && c.document === doc);
1933
+ if (byDoc) return { status: "found", match: { ...byDoc, why: "mesmo CNPJ/CPF" }, candidates: [] };
1934
+ }
1935
+ const exact = all.filter((c) => normalizeCompanyName(c.name) === wanted);
1936
+ if (exact.length === 1) {
1937
+ const only = exact[0];
1938
+ if (doc && only.document && only.document !== doc) {
1939
+ return {
1940
+ status: "ambiguous",
1941
+ candidates: [{ ...only, why: "mesmo nome, mas o CNPJ do papel \xE9 outro" }]
1942
+ };
1943
+ }
1944
+ return { status: "found", match: { ...only, why: "nome id\xEAntico" }, candidates: [] };
1945
+ }
1946
+ const near = all.map((c) => {
1947
+ const n = normalizeCompanyName(c.name);
1948
+ if (!n) return null;
1949
+ if (n === wanted) return { ...c, why: "nome id\xEAntico" };
1950
+ if (n.startsWith(wanted) || wanted.startsWith(n)) return { ...c, why: "nome contido" };
1951
+ const shared = n.split(" ").filter((w) => wanted.split(" ").includes(w));
1952
+ return shared.length ? { ...c, why: `parte do nome em comum (${shared.join(" ")})` } : null;
1953
+ }).filter((c) => c !== null).slice(0, 5);
1954
+ return { status: near.length ? "ambiguous" : "missing", candidates: near };
1955
+ }
1956
+ async function findMethod(provider, args) {
1957
+ const types = await paymentOptions(provider);
1958
+ const spoken = [args.paymentMethod, args.cardBrand].filter((v) => typeof v === "string" && !!v.trim());
1959
+ const card = lastFour(args.cardLast4);
1960
+ const looksLikeCard = !!(card || args.cardBrand);
1961
+ let match = matchPaymentOption(types, args.paymentMethod);
1962
+ if (!match && looksLikeCard) {
1963
+ match = types.find((t2) => t2.transactionType === "credit_card") ?? null;
1964
+ }
1965
+ const configured = (await provider.getPaymentMethods().catch(() => [])).filter((m) => m.isActive !== false);
1966
+ const namedMethod = configured.find((m) => {
1967
+ const n = normalizeCompanyName(m.name);
1968
+ return !!n && spoken.some((s) => {
1969
+ const w = normalizeCompanyName(s);
1970
+ return !!w && (n === w || n.includes(w) || w.includes(n));
1971
+ });
1972
+ });
1973
+ if (!match) {
1974
+ return {
1975
+ status: "missing",
1976
+ spoken: spoken.join(" ") || void 0,
1977
+ cardLast4: card || void 0,
1978
+ available: types.map((t2) => ({ id: t2.id, label: t2.label })),
1979
+ hint: "This business has no payment method matching what the paper says. ASK which of the available ones it maps to, or offer to add it in Settings (createRecord on financial:payment-methods is the right tool for THAT, and only that). Never silently pick one."
1980
+ };
1981
+ }
1982
+ return {
1983
+ status: "found",
1984
+ id: match.id,
1985
+ label: match.label,
1986
+ transactionType: match.transactionType,
1987
+ ...namedMethod ? { configuredMethod: { id: namedMethod.id, name: namedMethod.name } } : {},
1988
+ ...card ? { cardLast4: card } : {}
1989
+ };
1990
+ }
1991
+ async function findAccount(provider, method, rawLast4) {
1992
+ if (method.status !== "found") {
1993
+ return { status: "blocked", hint: "Resolve the payment method first \u2014 the eligible accounts depend on it." };
1994
+ }
1995
+ if (method.transactionType === "cash") {
1996
+ return {
1997
+ status: "not_applicable",
1998
+ hint: "Cash settles through the open cash session, not an account \u2014 nothing to choose."
1999
+ };
2000
+ }
2001
+ const accounts = await eligibleAccounts(provider, {
2002
+ id: String(method.id),
2003
+ label: String(method.label),
2004
+ transactionType: method.transactionType
2005
+ });
2006
+ const card = lastFour(rawLast4);
2007
+ const byCard = card ? accounts.find((a) => lastFour(a.accountNumber) === card || a.name.includes(card)) : void 0;
2008
+ if (byCard) return { status: "found", id: byCard.id, name: byCard.name, why: `final ${card}` };
2009
+ if (!accounts.length) {
2010
+ return {
2011
+ status: "missing",
2012
+ hint: `No account can settle a ${String(method.label)} payment. Say so \u2014 it has to be created in Settings.`
2013
+ };
2014
+ }
2015
+ if (card) {
2016
+ return {
2017
+ status: "ambiguous",
2018
+ cardLast4: card,
2019
+ note: `o papel diz final ${card} e nenhuma conta cadastrada tem esse final`,
2020
+ options: accounts.map((a) => ({ id: a.id, name: a.name })),
2021
+ hint: `ASK whether the card ending ${card} is one of these accounts, or whether it still has to be registered. Do not assume it is the only eligible one.`
2022
+ };
2023
+ }
2024
+ if (accounts.length === 1) {
2025
+ return { status: "found", id: accounts[0].id, name: accounts[0].name, why: "\xFAnica conta eleg\xEDvel" };
2026
+ }
2027
+ return {
2028
+ status: "ambiguous",
2029
+ ...card ? { cardLast4: card, note: `nenhuma conta bate com o final ${card}` } : {},
2030
+ options: accounts.map((a) => ({ id: a.id, name: a.name })),
2031
+ hint: "Ask which account the money left. Do not pick one."
2032
+ };
2033
+ }
2034
+ var RESOLVE_CONTEXT = {
2035
+ id: "financial.resolve-payable-context",
2036
+ name: "resolvePayableContext",
2037
+ description: "STEP 0, BEFORE writing anything: checks what a receipt names against what this business actually has. Call it as soon as you have read a receipt/photo/PDF, with EVERYTHING the paper says \u2014 supplier, its CNPJ/CPF if printed, the payment method, the card brand and last 4 digits if printed. Returns, for each dependency, whether it already exists here (fuzzy-matched on name and CNPJ, so the same supplier is never created twice), is ambiguous, or is missing \u2014 plus the candidates to choose from. Writes NOTHING. Report the result as a short checklist and then resolve the gaps IN ORDER: supplier first, then the payment method, then the bill (createPayable), then the payment.",
2038
+ icon: "ListChecks",
2039
+ mode: "read",
2040
+ execution: { plane: "client" },
2041
+ category: "Finance",
2042
+ role: "finance",
2043
+ permission: { feature: "financial", action: "read" },
2044
+ parameters: {
2045
+ type: "object",
2046
+ properties: {
2047
+ supplier: { type: "string", description: "O nome do fornecedor EXATAMENTE como est\xE1 escrito no papel." },
2048
+ supplierDocument: { type: "string", description: "O CNPJ ou CPF impresso, se houver. Copie os d\xEDgitos." },
2049
+ paymentMethod: {
2050
+ type: "string",
2051
+ description: 'Como o papel diz que foi pago ("PIX", "cart\xE3o de cr\xE9dito", "dinheiro", "boleto").'
2052
+ },
2053
+ cardBrand: { type: "string", description: 'A bandeira, se impressa ("Visa", "Mastercard", "Nubank").' },
2054
+ cardLast4: { type: "string", description: 'Os quatro \xFAltimos d\xEDgitos do cart\xE3o, se impressos ("4242").' },
2055
+ // O DOCUMENTO INTEIRO, lido UMA VEZ.
2056
+ //
2057
+ // Não é zelo: é onde a fabricação entrava. Extrair no passo 0 e extrair de
2058
+ // novo no passo da conta são duas leituras do mesmo papel, e a segunda não
2059
+ // tem o papel na frente — ela tem a memória da primeira, que é onde os
2060
+ // números começam a escorregar. Lido aqui, normalizado aqui, conferido
2061
+ // aqui, e as ferramentas seguintes CONSOMEM o que ficou guardado.
2062
+ items: {
2063
+ type: "array",
2064
+ description: "As LINHAS da tabela do documento, uma por linha \u2014 leia linha a linha e copie TODAS.",
2065
+ items: {
2066
+ type: "object",
2067
+ properties: {
2068
+ description: { type: "string", description: "O que \xE9 a linha, como est\xE1 escrito." },
2069
+ quantity: { type: "number", description: "A quantidade. 1 quando a tabela n\xE3o diz." },
2070
+ unitPrice: { type: "string", description: 'Pre\xE7o UNIT\xC1RIO copiado ("47,90").' }
2071
+ },
2072
+ required: ["description", "unitPrice"]
2073
+ }
2074
+ },
2075
+ shipping: { type: "string", description: 'O FRETE cobrado \xE0 parte, copiado ("52,19").' },
2076
+ subtotal: { type: "string", description: 'O subtotal impresso, se houver ("1.437,00").' },
2077
+ total: {
2078
+ type: "string",
2079
+ description: 'O TOTAL do rodap\xE9, copiado letra por letra ("1.489,19"). Com frete, sem converter.'
2080
+ },
2081
+ documentDate: { type: "string", description: 'A data do documento ("09/09/2026").' },
2082
+ dueDate: {
2083
+ type: "string",
2084
+ description: 'O VENCIMENTO, s\xF3 se o papel disser. "Expira em" de um c\xF3digo Pix \xE9 a validade do C\xD3DIGO, n\xE3o vencimento: um pedido pago \xE0 vista n\xE3o tem, e a\xED se omite.'
2085
+ },
2086
+ paymentDate: { type: "string", description: "Quando foi pago, se o papel disser." },
2087
+ orderRef: { type: "string", description: 'O n\xFAmero do pedido ou documento ("#164223").' },
2088
+ deliveryDate: { type: "string", description: 'A data de entrega, se impressa ("11/09/2026").' }
2089
+ }
2090
+ }
2091
+ };
2092
+ var FILL_DRAFT = {
2093
+ id: "financial.fill-payable-draft",
2094
+ name: "fillPayableDraft",
2095
+ description: "Fills a field of the payable form ALREADY OPEN on screen (the draft the person is looking at). Use this \u2014 not createPayable \u2014 whenever that form is open: it is the bill in progress, and writing a new one instead would leave the person staring at a draft that means nothing. Looks the supplier up by name and offers to create it when it does not exist yet. Saves nothing.",
2096
+ icon: "PenLine",
2097
+ mode: "read",
2098
+ execution: { plane: "client" },
2099
+ category: "Finance",
2100
+ role: "finance",
2101
+ parameters: {
2102
+ type: "object",
2103
+ properties: {
2104
+ supplier: { type: "string", description: "O nome do fornecedor a colocar no rascunho." },
2105
+ dueDate: { type: "string", description: 'O vencimento, como foi dito ("20/01/2026").' }
2106
+ }
2107
+ }
2108
+ };
2109
+ var SUBMIT_DRAFT = {
2110
+ id: "financial.submit-payable-draft",
2111
+ name: "submitPayableDraft",
2112
+ description: `SAVES the payable form open on screen \u2014 the same thing the screen's save button does. This is how "salva", "pode salvar", "confirma" is answered while that form is open. Never call createPayable for a draft that is already on screen: that writes a second, duplicate bill.`,
2113
+ icon: "Check",
2114
+ mode: "persist",
2115
+ execution: { plane: "client" },
2116
+ requiresConfirmation: false,
2117
+ category: "Finance",
2118
+ role: "finance",
2119
+ permission: { feature: "financial", action: "create" },
2120
+ parameters: { type: "object", properties: {} }
2121
+ };
2122
+ function PayableDraftSurface({ config, draft }) {
2123
+ const live = React15.useRef(draft);
2124
+ live.current = draft;
2125
+ const lookup = React15.useMemo(
2126
+ () => config.contactLookup ?? createArchetypeLookup({ archetype: "person", kind: "supplier" }),
2127
+ [config.contactLookup]
2128
+ );
2129
+ React15.useEffect(() => {
2130
+ liveDraft = {
2131
+ describe: () => live.current.describe(),
2132
+ setSupplier: (name, id) => live.current.setSupplier(name, id),
2133
+ setDueDate: (iso) => live.current.setDueDate(iso),
2134
+ submit: () => live.current.submit()
2135
+ };
2136
+ return () => {
2137
+ liveDraft = null;
2138
+ };
2139
+ }, []);
2140
+ const contribution = React15.useMemo(() => ({
2141
+ id: "financial.payables.draft",
2142
+ pluginId: "financial",
2143
+ role: "finance",
2144
+ instructions: 'O cadastro de uma CONTA A PAGAR est\xE1 aberto na tela, com um rascunho que a pessoa est\xE1 conferindo. Enquanto ele estiver a\xED, ele \xC9 a conta em curso: use `fillPayableDraft` para completar o que falta e `submitPayableDraft` para salvar. N\xC3O chame `createPayable` \u2014 ela criaria uma segunda conta e deixaria a pessoa num rascunho \xF3rf\xE3o. O estado do rascunho est\xE1 em "Tela aberta agora".',
2145
+ describe: () => {
2146
+ const state = live.current.describe();
2147
+ return {
2148
+ title: "Cadastro \u2014 Conta a pagar (rascunho n\xE3o salvo)",
2149
+ state: { screen: "/financial/payables/new", draft: state }
2150
+ };
2151
+ },
2152
+ tools: [
869
2153
  {
870
- open: cancelTarget !== null,
871
- variant: "destructive",
872
- title: t2("financial.invoice.bulkCancelTitle", { count: String(cancelTarget?.length ?? 0) }),
873
- description: t2("financial.invoice.cancelDesc"),
874
- confirmLabel: t2("financial.invoice.yesCancel"),
875
- cancelLabel: t2("financial.invoice.keepInvoice"),
876
- loading: bulkBusy,
877
- onConfirm: () => void confirmBulkCancel(),
878
- onCancel: () => setCancelTarget(null)
2154
+ tool: FILL_DRAFT,
2155
+ execute: async (args) => {
2156
+ const filled = [];
2157
+ const dueDate = parseReceiptDate(args.dueDate);
2158
+ if (dueDate) {
2159
+ live.current.setDueDate(dueDate);
2160
+ filled.push("dueDate");
2161
+ }
2162
+ const name = text(args.supplier);
2163
+ if (name) {
2164
+ const step = await resolveSupplier(lookup, name);
2165
+ if (!step.ok) {
2166
+ return {
2167
+ ok: false,
2168
+ cancelled: true,
2169
+ reason: "user_declined_supplier",
2170
+ message: "The supplier was not created, so the draft still has none. Ask which supplier it is."
2171
+ };
2172
+ }
2173
+ live.current.setSupplier(step.name, step.id);
2174
+ filled.push("supplier");
2175
+ }
2176
+ if (!filled.length) {
2177
+ return { ok: false, error: "nothing_to_fill", message: "Send a supplier or a dueDate to fill in." };
2178
+ }
2179
+ return {
2180
+ ok: true,
2181
+ filled,
2182
+ draft: live.current.describe(),
2183
+ message: "O rascunho na tela foi preenchido \u2014 nada foi salvo ainda. Diga em UMA frase o que entrou e que basta confirmar. Quando a pessoa mandar salvar, chame submitPayableDraft."
2184
+ };
2185
+ }
2186
+ },
2187
+ {
2188
+ tool: SUBMIT_DRAFT,
2189
+ execute: async () => {
2190
+ const before = live.current.describe();
2191
+ if (!before.itemCount) {
2192
+ return {
2193
+ ok: false,
2194
+ error: "empty_draft",
2195
+ message: "NOTHING WAS SAVED. The draft has no line to bill. Say what is missing."
2196
+ };
2197
+ }
2198
+ await live.current.submit();
2199
+ return {
2200
+ ok: true,
2201
+ saved: true,
2202
+ supplier: before.supplier,
2203
+ total: before.total,
2204
+ message: "A conta a pagar foi salva a partir do rascunho que estava na tela, e a ficha dela j\xE1 abriu. Diga isso em UMA frase e pare."
2205
+ };
2206
+ }
879
2207
  }
880
- )
881
- ] });
2208
+ ]
2209
+ }), [lookup]);
2210
+ useAgentSurface(contribution);
2211
+ return null;
2212
+ }
2213
+ function createFinancialAgentMount(deps) {
2214
+ const FinancialAgentMount = () => {
2215
+ const contribution = React15.useMemo(() => ({
2216
+ id: "financial.payables.global",
2217
+ pluginId: "financial",
2218
+ role: "finance",
2219
+ instructions: PAYABLE_INSTRUCTIONS,
2220
+ // Sem `describe`: esta superfície não é uma tela. O snapshot responde "o
2221
+ // que está na tela agora", e um mount de shell que dissesse "Contas a
2222
+ // pagar" mentiria em todas as outras rotas do app.
2223
+ tools: createPayableTools(deps)
2224
+ }), []);
2225
+ useAgentSurface(contribution);
2226
+ return null;
2227
+ };
2228
+ FinancialAgentMount.displayName = "FinancialAgentMount";
2229
+ return FinancialAgentMount;
882
2230
  }
883
2231
  function useReceiptPicker(onPick) {
884
2232
  const t2 = useTranslation();
@@ -910,7 +2258,7 @@ function useReceiptPicker(onPick) {
910
2258
  );
911
2259
  return { open: () => ref.current?.click(), input };
912
2260
  }
913
- var ACCOUNT_TYPES_FOR_TX = {
2261
+ var ACCOUNT_TYPES_FOR_TX2 = {
914
2262
  cash: ["cash_physical", "cash_session"],
915
2263
  credit_card: ["card_receivable_credit"],
916
2264
  debit_card: ["card_receivable_debit"],
@@ -966,6 +2314,8 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
966
2314
  const [cardInstallments, setCardInstallments] = useState(1);
967
2315
  const [notes, setNotes] = useState("");
968
2316
  const [saving, setSaving] = useState(false);
2317
+ const command = React15__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
2318
+ const [pending2, setPending] = useState(false);
969
2319
  const [visible, setVisible] = useState(false);
970
2320
  const [expandMethodPicker, setExpandMethodPicker] = useState(true);
971
2321
  useEffect(() => {
@@ -984,16 +2334,16 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
984
2334
  );
985
2335
  const needsCard = txType === "credit_card" || txType === "debit_card";
986
2336
  const needsCardInstallments = txType === "credit_card";
987
- const eligibleAccounts = useMemo(() => {
988
- const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[txType];
2337
+ const eligibleAccounts2 = useMemo(() => {
2338
+ const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType];
989
2339
  if (!allowed || !allowed.length) return bankAccounts;
990
2340
  return bankAccounts.filter((a) => allowed.includes(a.accountType));
991
2341
  }, [bankAccounts, selectedType, txType]);
992
2342
  useEffect(() => {
993
- if (selectedTypeId && !bankAccountId && eligibleAccounts.length) {
994
- setBankAccountId(eligibleAccounts[0].id);
2343
+ if (selectedTypeId && !bankAccountId && eligibleAccounts2.length) {
2344
+ setBankAccountId(eligibleAccounts2[0].id);
995
2345
  }
996
- }, [selectedTypeId, eligibleAccounts]);
2346
+ }, [selectedTypeId, eligibleAccounts2]);
997
2347
  function handleSelectType(typeId) {
998
2348
  setSelectedTypeId(typeId);
999
2349
  setExpandMethodPicker(false);
@@ -1003,6 +2353,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
1003
2353
  setCardInstallments(1);
1004
2354
  }
1005
2355
  function handleClose() {
2356
+ if (command.current.pending || command.current.running) return;
1006
2357
  setVisible(false);
1007
2358
  setTimeout(onClose, 200);
1008
2359
  }
@@ -1012,15 +2363,17 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
1012
2363
  e.preventDefault();
1013
2364
  if (amount > 0 && selectedTypeId && bankAccountId && !saving) void handlePay();
1014
2365
  }
1015
- React14__default.useEffect(() => {
2366
+ React15__default.useEffect(() => {
1016
2367
  void fetchCashSessions();
1017
2368
  }, [fetchCashSessions]);
1018
2369
  const needsOpenCashSession = txType === "cash" && !openSession;
1019
2370
  async function handlePay() {
1020
- if (amount <= 0 || !selectedTypeId || !bankAccountId || needsOpenCashSession) return;
2371
+ if (command.current.running) return;
2372
+ if (!command.current.pending && (amount <= 0 || !selectedTypeId || !bankAccountId || needsOpenCashSession)) return;
1021
2373
  setSaving(true);
2374
+ setPending(true);
1022
2375
  try {
1023
- await payMovement({
2376
+ await command.current.run({
1024
2377
  // O comprovante viaja com o pagamento — é dele que ele é prova.
1025
2378
  metadata: receiptUrl ? { receiptUrl } : void 0,
1026
2379
  movementId: movement.id,
@@ -1035,12 +2388,13 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
1035
2388
  cardBrand: needsCard ? cardBrand || void 0 : void 0,
1036
2389
  cardInstallments: needsCardInstallments ? cardInstallments : void 0,
1037
2390
  notes: notes.trim() || void 0
1038
- });
2391
+ }, payMovement);
1039
2392
  setVisible(false);
1040
2393
  setTimeout(onPaid, 200);
1041
2394
  } catch {
1042
2395
  } finally {
1043
2396
  setSaving(false);
2397
+ setPending(command.current.pending);
1044
2398
  }
1045
2399
  }
1046
2400
  const SelectedIcon = selectedType ? TYPE_ICONS[txType] ?? Banknote : null;
@@ -1073,7 +2427,8 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
1073
2427
  ] }),
1074
2428
  /* @__PURE__ */ jsx("button", { onClick: handleClose, className: "p-1 text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
1075
2429
  ] }),
1076
- /* @__PURE__ */ jsxs("div", { className: "px-5 py-4 space-y-4", children: [
2430
+ pending2 && /* @__PURE__ */ jsx("p", { role: "status", className: "px-5 pt-4 text-sm text-warning", children: t2("financial.payment.pendingRetry") }),
2431
+ /* @__PURE__ */ jsxs("fieldset", { disabled: pending2, className: "px-5 py-4 space-y-4", children: [
1077
2432
  /* @__PURE__ */ jsxs("div", { children: [
1078
2433
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.method") }),
1079
2434
  /* @__PURE__ */ jsx(
@@ -1179,7 +2534,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
1179
2534
  ] }),
1180
2535
  /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
1181
2536
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.bankAccount") }),
1182
- eligibleAccounts.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
2537
+ eligibleAccounts2.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
1183
2538
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: bankAccounts.length > 0 && selectedType ? t2("financial.payment.noAccountForMethod", { method: selectedType.name }) : t2("financial.payment.noAccounts") }),
1184
2539
  /* @__PURE__ */ jsxs("a", { href: "#/settings/financial/bank-accounts", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
1185
2540
  t2("financial.payment.configureAccounts"),
@@ -1187,7 +2542,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
1187
2542
  ] })
1188
2543
  ] }) : /* @__PURE__ */ jsxs("select", { value: bankAccountId, onChange: (e) => setBankAccountId(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
1189
2544
  /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.selectAccount") }),
1190
- eligibleAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
2545
+ eligibleAccounts2.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
1191
2546
  ] })
1192
2547
  ] }),
1193
2548
  /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "150ms" }, children: [
@@ -1206,14 +2561,14 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
1206
2561
  }
1207
2562
  ` }),
1208
2563
  /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-4 border-t sticky bottom-0 bg-card", children: [
1209
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, children: t2("financial.payment.cancel") }),
2564
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, disabled: pending2 || saving, children: t2("financial.payment.cancel") }),
1210
2565
  /* @__PURE__ */ jsxs(
1211
2566
  Button,
1212
2567
  {
1213
2568
  variant: "default",
1214
2569
  size: "sm",
1215
2570
  onClick: handlePay,
1216
- disabled: amount <= 0 || !selectedTypeId || !bankAccountId || needsOpenCashSession || saving,
2571
+ disabled: saving || !pending2 && (amount <= 0 || !selectedTypeId || !bankAccountId || needsOpenCashSession),
1217
2572
  children: [
1218
2573
  /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
1219
2574
  saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: formatCurrency(amount, currency) })
@@ -1318,7 +2673,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1318
2673
  const t2 = useTranslation();
1319
2674
  const config = useFinancialConfig();
1320
2675
  const { currency, itemTypes: itemTypesRaw, locations } = config;
1321
- const itemTypes = React14__default.useMemo(
2676
+ const itemTypes = React15__default.useMemo(
1322
2677
  () => itemTypesRaw.map((tp) => {
1323
2678
  const traduzido = t2(`financial.itemType.${tp.value}`);
1324
2679
  return traduzido === `financial.itemType.${tp.value}` ? tp : { ...tp, label: traduzido };
@@ -1355,6 +2710,14 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1355
2710
  cancelled = true;
1356
2711
  };
1357
2712
  }, [provider]);
2713
+ const contactLookup = useMemo(
2714
+ () => config.contactLookup ?? createArchetypeLookup({
2715
+ archetype: "person",
2716
+ kind: direction === "debit" ? "supplier" : "customer",
2717
+ kindLabels: { supplier: "Fornecedor", customer: "Cliente" }
2718
+ }),
2719
+ [config.contactLookup, direction]
2720
+ );
1358
2721
  const [loaded, setLoaded] = useState(!editId);
1359
2722
  const [loading, setLoading] = useState(!!editId);
1360
2723
  const [contactName, setContactName] = useState("");
@@ -1373,7 +2736,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1373
2736
  const [savedMovements, setSavedMovements] = useState([]);
1374
2737
  const [payingMovement, setPayingMovement] = useState(null);
1375
2738
  const [pendingReceipt, setPendingReceipt] = useState();
1376
- const contactCreate = useLookupCreate(config.contactLookup, (created) => {
2739
+ const contactCreate = useLookupCreate(contactLookup, (created) => {
1377
2740
  setContactId(created.id);
1378
2741
  setContactName(created.label);
1379
2742
  setPendingContact(null);
@@ -1397,8 +2760,15 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1397
2760
  const [menuOpen, setMenuOpen] = useState(false);
1398
2761
  const [confirmCancel, setConfirmCancel] = useState(false);
1399
2762
  const [cancelling, setCancelling] = useState(false);
2763
+ const [cancelReason, setCancelReason] = useState("");
2764
+ const cancelRequestKey = useRef(createIdempotencyKey("financial:invoice-cancel"));
1400
2765
  const menuRef = useRef(null);
1401
2766
  const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
2767
+ function closeCancelDialog() {
2768
+ setConfirmCancel(false);
2769
+ setCancelReason("");
2770
+ cancelRequestKey.current = createIdempotencyKey("financial:invoice-cancel");
2771
+ }
1402
2772
  useEffect(() => {
1403
2773
  if (!menuOpen) return;
1404
2774
  function handleClick(e) {
@@ -1411,7 +2781,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1411
2781
  if (!editId) return;
1412
2782
  setCancelling(true);
1413
2783
  try {
1414
- await cancelInvoice(editId);
2784
+ await cancelInvoice({
2785
+ invoiceId: editId,
2786
+ reason: cancelReason,
2787
+ idempotencyKey: cancelRequestKey.current
2788
+ });
1415
2789
  onSaved?.();
1416
2790
  } catch {
1417
2791
  } finally {
@@ -1505,13 +2879,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1505
2879
  if (isEdit || totalAmount <= 0 || installments.length > 0) return;
1506
2880
  generateInstallments(1);
1507
2881
  }, [isEdit, totalAmount, installments.length]);
1508
- const fromCard = React14__default.useMemo(
2882
+ const fromCard = React15__default.useMemo(
1509
2883
  () => fromCardId ? bankAccounts.find((a) => a.id === fromCardId) : void 0,
1510
2884
  [fromCardId, bankAccounts]
1511
2885
  );
1512
2886
  const cardLast4 = (fromCard?.accountNumber ?? "").replace(/\D/g, "").slice(-4);
1513
2887
  const nomeJaTemOsDigitos = !!cardLast4 && (fromCard?.name ?? "").replace(/\D/g, "").endsWith(cardLast4);
1514
- const cardMethodName = React14__default.useMemo(() => {
2888
+ const cardMethodName = React15__default.useMemo(() => {
1515
2889
  const id = installments[0]?.payMethodTypeId;
1516
2890
  const escolhida = id ? paymentMethodTypes.find((tp) => tp.id === id) : void 0;
1517
2891
  return (escolhida ?? paymentMethodTypes.find((tp) => tp.transactionType === "credit_card"))?.name;
@@ -1535,21 +2909,31 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1535
2909
  if (draft.supplier) {
1536
2910
  setContactName(draft.supplier);
1537
2911
  void (async () => {
1538
- const hit = (await config.contactLookup?.search(draft.supplier).catch(() => []))?.find((r) => r.label.trim().toLowerCase() === draft.supplier.trim().toLowerCase());
2912
+ const hit = (await contactLookup.search(draft.supplier).catch(() => []))?.find((r) => r.label.trim().toLowerCase() === draft.supplier.trim().toLowerCase());
1539
2913
  if (hit) {
1540
2914
  setContactId(hit.id);
1541
2915
  setContactName(hit.label);
1542
2916
  } else setPendingContact(draft.supplier);
1543
2917
  })();
1544
2918
  }
1545
- const itemId = fid();
1546
- setItems([{
1547
- _id: itemId,
1548
- itemKind: "other",
1549
- description: draft.description ?? draft.supplier ?? "",
1550
- quantity: 1,
1551
- unitPrice: draft.amount
1552
- }]);
2919
+ setItems(
2920
+ draft.items?.length ? [
2921
+ ...draft.items.map((line) => ({
2922
+ _id: fid(),
2923
+ itemKind: "other",
2924
+ description: line.description,
2925
+ quantity: line.quantity,
2926
+ unitPrice: line.unitPrice
2927
+ })),
2928
+ ...draft.shipping ? [{ _id: fid(), itemKind: "other", description: "Frete", quantity: 1, unitPrice: draft.shipping }] : []
2929
+ ] : [{
2930
+ _id: fid(),
2931
+ itemKind: "other",
2932
+ description: draft.description ?? draft.supplier ?? "",
2933
+ quantity: 1,
2934
+ unitPrice: draft.amount - (draft.shipping ?? 0)
2935
+ }]
2936
+ );
1553
2937
  setInstallments([{ _id: fid(), amount: draft.amount, dueDate: draft.date ?? invoiceDate }]);
1554
2938
  if (draft.receiptUrl) setPendingReceipt(draft.receiptUrl);
1555
2939
  }, [isEdit]);
@@ -1651,7 +3035,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1651
3035
  amount: bill.amount,
1652
3036
  paymentDate: inst.dueDate,
1653
3037
  paymentMethodTypeId: inst.payMethodTypeId || void 0,
1654
- bankAccountId: inst.payAccountId || void 0
3038
+ bankAccountId: inst.payAccountId || void 0,
3039
+ idempotencyKey: `financial:invoice:${invoice.id}:installment:${bill.id}:receipt`
1655
3040
  });
1656
3041
  }
1657
3042
  }
@@ -1712,130 +3097,133 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1712
3097
  ] }) : null;
1713
3098
  const paidSoFar = savedMovements.reduce((sum, m) => sum + (m.paidAmount ?? 0), 0);
1714
3099
  const hasPayments = paidSoFar > 0;
1715
- const scheduleSection = /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1716
- hasPayments ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.scheduleLocked") }) : (
1717
- // Atalhos de parcelamento — 1x, 2x, 3x, 6x, 12x cobrem quase tudo.
1718
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
1719
- "button",
1720
- {
1721
- type: "button",
1722
- onClick: () => generateInstallments(n),
1723
- disabled: totalAmount <= 0,
1724
- className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
1725
- children: [
1726
- n,
1727
- "x"
1728
- ]
1729
- },
1730
- n
1731
- )) })
1732
- ),
1733
- installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded border", children: installments.map((inst, i) => {
1734
- const patch = (next) => {
1735
- const updated = [...installments];
1736
- updated[i] = { ...inst, ...next };
1737
- setInstallments(updated);
1738
- };
1739
- const saved = savedMovements[i];
1740
- const isPaid = saved?.status === "paid";
1741
- const isPartial = saved?.status === "partial";
1742
- const StatusIcon = isPaid ? CircleCheckBig : CircleDashed;
1743
- const canAttach = isEdit ? !!saved && !isPaid : true;
1744
- return /* @__PURE__ */ jsxs("div", { children: [
1745
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 px-3 py-2", children: [
1746
- /* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
1747
- "#",
1748
- i + 1
1749
- ] }),
1750
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money3(inst.amount) }),
1751
- saved && /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${isPaid ? "bg-success-soft text-success-soft-foreground" : isPartial ? "bg-warning-soft text-warning-soft-foreground" : "bg-muted text-muted-foreground"}`, children: [
1752
- /* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5" }),
1753
- t2(isPaid ? "financial.invoice.statusPaid" : isPartial ? "financial.invoice.statusPartial" : "financial.invoice.statusPending")
3100
+ const scheduleSection = (
3101
+ // Tour anchor: the guided setup points here for the installment schedule.
3102
+ /* @__PURE__ */ jsx("div", { ...tourAnchor("financial.installments"), children: /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3103
+ hasPayments ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.scheduleLocked") }) : (
3104
+ // Atalhos de parcelamento — 1x, 2x, 3x, 6x, 12x cobrem quase tudo.
3105
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
3106
+ "button",
3107
+ {
3108
+ type: "button",
3109
+ onClick: () => generateInstallments(n),
3110
+ disabled: totalAmount <= 0,
3111
+ className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
3112
+ children: [
3113
+ n,
3114
+ "x"
3115
+ ]
3116
+ },
3117
+ n
3118
+ )) })
3119
+ ),
3120
+ installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded border", children: installments.map((inst, i) => {
3121
+ const patch = (next) => {
3122
+ const updated = [...installments];
3123
+ updated[i] = { ...inst, ...next };
3124
+ setInstallments(updated);
3125
+ };
3126
+ const saved = savedMovements[i];
3127
+ const isPaid = saved?.status === "paid";
3128
+ const isPartial = saved?.status === "partial";
3129
+ const StatusIcon = isPaid ? CircleCheckBig : CircleDashed;
3130
+ const canAttach = isEdit ? !!saved && !isPaid : true;
3131
+ return /* @__PURE__ */ jsxs("div", { children: [
3132
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 px-3 py-2", children: [
3133
+ /* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
3134
+ "#",
3135
+ i + 1
3136
+ ] }),
3137
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money3(inst.amount) }),
3138
+ saved && /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${isPaid ? "bg-success-soft text-success-soft-foreground" : isPartial ? "bg-warning-soft text-warning-soft-foreground" : "bg-muted text-muted-foreground"}`, children: [
3139
+ /* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5" }),
3140
+ t2(isPaid ? "financial.invoice.statusPaid" : isPartial ? "financial.invoice.statusPartial" : "financial.invoice.statusPending")
3141
+ ] }),
3142
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [
3143
+ !hasPayments ? /* @__PURE__ */ jsx(
3144
+ DatePicker,
3145
+ {
3146
+ value: inst.dueDate,
3147
+ variant: "chip",
3148
+ onChange: (v) => patch({ dueDate: v })
3149
+ }
3150
+ ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
3151
+ canAttach && /* @__PURE__ */ jsx(
3152
+ "button",
3153
+ {
3154
+ type: "button",
3155
+ onClick: () => {
3156
+ if (!inst.receiptUrl && pendingReceipt) {
3157
+ patch({ receiptUrl: pendingReceipt });
3158
+ setPendingReceipt(void 0);
3159
+ return;
3160
+ }
3161
+ setReceiptTarget(i);
3162
+ receiptPicker.open();
3163
+ },
3164
+ title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
3165
+ "aria-label": !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
3166
+ className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors ${inst.receiptUrl ? "text-success hover:bg-success-soft" : pendingReceipt ? "text-primary hover:bg-primary/10" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
3167
+ children: inst.receiptUrl ? /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Camera, { className: "h-3.5 w-3.5" })
3168
+ }
3169
+ ),
3170
+ saved && !isPaid && /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: () => setPayingMovement(saved), children: [
3171
+ /* @__PURE__ */ jsx(DollarSign, { className: "mr-1 h-3.5 w-3.5" }),
3172
+ t2("financial.invoice.pay")
3173
+ ] })
3174
+ ] })
1754
3175
  ] }),
1755
- /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [
1756
- !hasPayments ? /* @__PURE__ */ jsx(
1757
- DatePicker,
3176
+ !isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
3177
+ /* @__PURE__ */ jsx(
3178
+ "img",
3179
+ {
3180
+ src: inst.receiptUrl,
3181
+ alt: t2("financial.quickTx.receipt"),
3182
+ className: "h-8 w-8 shrink-0 rounded border object-cover"
3183
+ }
3184
+ ),
3185
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-medium text-success", children: t2("financial.invoiceForm.willBePaid") }),
3186
+ /* @__PURE__ */ jsxs(
3187
+ "select",
3188
+ {
3189
+ value: inst.payMethodTypeId ?? "",
3190
+ onChange: (e) => patch({ payMethodTypeId: e.target.value }),
3191
+ className: "min-w-[8rem] flex-1 rounded-input border border-input bg-card px-2 py-1 text-xs shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring",
3192
+ children: [
3193
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoiceForm.selectMethod") }),
3194
+ paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx("option", { value: tp.id, children: tp.name }, tp.id))
3195
+ ]
3196
+ }
3197
+ ),
3198
+ /* @__PURE__ */ jsxs(
3199
+ "select",
1758
3200
  {
1759
- value: inst.dueDate,
1760
- variant: "chip",
1761
- onChange: (v) => patch({ dueDate: v })
3201
+ value: inst.payAccountId ?? (bankAccounts.length === 1 ? bankAccounts[0].id : ""),
3202
+ onChange: (e) => patch({ payAccountId: e.target.value }),
3203
+ className: "min-w-[8rem] flex-1 rounded-input border border-input bg-card px-2 py-1 text-xs shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring",
3204
+ children: [
3205
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoiceForm.selectAccount") }),
3206
+ bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
3207
+ ]
1762
3208
  }
1763
- ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
1764
- canAttach && /* @__PURE__ */ jsx(
3209
+ ),
3210
+ /* @__PURE__ */ jsx(
1765
3211
  "button",
1766
3212
  {
1767
3213
  type: "button",
1768
- onClick: () => {
1769
- if (!inst.receiptUrl && pendingReceipt) {
1770
- patch({ receiptUrl: pendingReceipt });
1771
- setPendingReceipt(void 0);
1772
- return;
1773
- }
1774
- setReceiptTarget(i);
1775
- receiptPicker.open();
1776
- },
1777
- title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
1778
- "aria-label": !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
1779
- className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors ${inst.receiptUrl ? "text-success hover:bg-success-soft" : pendingReceipt ? "text-primary hover:bg-primary/10" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
1780
- children: inst.receiptUrl ? /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Camera, { className: "h-3.5 w-3.5" })
3214
+ onClick: () => patch({ receiptUrl: void 0, payMethodTypeId: void 0, payAccountId: void 0 }),
3215
+ "aria-label": t2("financial.quickTx.receiptRemove"),
3216
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
3217
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
1781
3218
  }
1782
- ),
1783
- saved && !isPaid && /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: () => setPayingMovement(saved), children: [
1784
- /* @__PURE__ */ jsx(DollarSign, { className: "mr-1 h-3.5 w-3.5" }),
1785
- t2("financial.invoice.pay")
1786
- ] })
3219
+ )
1787
3220
  ] })
1788
- ] }),
1789
- !isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
1790
- /* @__PURE__ */ jsx(
1791
- "img",
1792
- {
1793
- src: inst.receiptUrl,
1794
- alt: t2("financial.quickTx.receipt"),
1795
- className: "h-8 w-8 shrink-0 rounded border object-cover"
1796
- }
1797
- ),
1798
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-medium text-success", children: t2("financial.invoiceForm.willBePaid") }),
1799
- /* @__PURE__ */ jsxs(
1800
- "select",
1801
- {
1802
- value: inst.payMethodTypeId ?? "",
1803
- onChange: (e) => patch({ payMethodTypeId: e.target.value }),
1804
- className: "min-w-[8rem] flex-1 rounded-input border border-input bg-card px-2 py-1 text-xs shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring",
1805
- children: [
1806
- /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoiceForm.selectMethod") }),
1807
- paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx("option", { value: tp.id, children: tp.name }, tp.id))
1808
- ]
1809
- }
1810
- ),
1811
- /* @__PURE__ */ jsxs(
1812
- "select",
1813
- {
1814
- value: inst.payAccountId ?? (bankAccounts.length === 1 ? bankAccounts[0].id : ""),
1815
- onChange: (e) => patch({ payAccountId: e.target.value }),
1816
- className: "min-w-[8rem] flex-1 rounded-input border border-input bg-card px-2 py-1 text-xs shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring",
1817
- children: [
1818
- /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoiceForm.selectAccount") }),
1819
- bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
1820
- ]
1821
- }
1822
- ),
1823
- /* @__PURE__ */ jsx(
1824
- "button",
1825
- {
1826
- type: "button",
1827
- onClick: () => patch({ receiptUrl: void 0, payMethodTypeId: void 0, payAccountId: void 0 }),
1828
- "aria-label": t2("financial.quickTx.receiptRemove"),
1829
- className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
1830
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
1831
- }
1832
- )
1833
- ] })
1834
- ] }, inst._id);
1835
- }) })
1836
- ] }) });
3221
+ ] }, inst._id);
3222
+ }) })
3223
+ ] }) }) })
3224
+ );
1837
3225
  const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
1838
- /* @__PURE__ */ jsxs(OrderFormBlock, { title: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"), children: [
3226
+ /* @__PURE__ */ jsx("div", { ...tourAnchor("financial.contact"), children: /* @__PURE__ */ jsxs(OrderFormBlock, { title: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"), children: [
1839
3227
  /* @__PURE__ */ jsx(
1840
3228
  SearchSelect,
1841
3229
  {
@@ -1848,8 +3236,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1848
3236
  setPendingContact(null);
1849
3237
  },
1850
3238
  onSearch: async (q) => {
1851
- if (!config.contactLookup) return [];
1852
- const results = await config.contactLookup.search(q);
3239
+ const results = await contactLookup.search(q);
1853
3240
  return results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, group: r.group, data: r }));
1854
3241
  },
1855
3242
  createLabel: t2("financial.invoiceForm.createContact"),
@@ -1878,7 +3265,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1878
3265
  )
1879
3266
  ] }),
1880
3267
  contactCreate.modal
1881
- ] }),
3268
+ ] }) }),
1882
3269
  /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1883
3270
  hasLocations && /* @__PURE__ */ jsx(LocationSelector, { locations, value: unitId, onChange: setUnitId }),
1884
3271
  /* @__PURE__ */ jsxs("div", { children: [
@@ -1911,6 +3298,29 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1911
3298
  ) })
1912
3299
  ] });
1913
3300
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3301
+ !isEdit && direction === "debit" && /* @__PURE__ */ jsx(
3302
+ PayableDraftSurface,
3303
+ {
3304
+ config,
3305
+ draft: {
3306
+ describe: () => ({
3307
+ supplier: contactName || void 0,
3308
+ supplierResolved: !!contactId,
3309
+ description: items.map((i) => i.description).filter(Boolean).join(", ") || void 0,
3310
+ total: totalAmount,
3311
+ dueDate: installments[0]?.dueDate,
3312
+ itemCount: items.length
3313
+ }),
3314
+ setSupplier: (name, id) => {
3315
+ setContactName(name);
3316
+ setContactId(id);
3317
+ setPendingContact(id ? null : name);
3318
+ },
3319
+ setDueDate: (iso) => setInstallments((prev) => prev.map((inst, i) => i === 0 ? { ...inst, dueDate: iso } : inst)),
3320
+ submit: handleSave
3321
+ }
3322
+ }
3323
+ ),
1914
3324
  fromCard && /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-3 rounded-card border bg-card px-4 py-3", children: [
1915
3325
  /* @__PURE__ */ jsx(
1916
3326
  "span",
@@ -1944,6 +3354,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
1944
3354
  /* @__PURE__ */ jsx(
1945
3355
  OrderFormItemsCard,
1946
3356
  {
3357
+ "data-tour": "financial.items",
1947
3358
  label: t2("financial.invoiceForm.items"),
1948
3359
  addLabel: t2("financial.invoiceForm.addItem"),
1949
3360
  onAdd: startAddItem,
@@ -2085,7 +3496,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
2085
3496
  }
2086
3497
  ),
2087
3498
  confirmCancel && createPortal(
2088
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setConfirmCancel(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-modal border bg-card shadow-lg mx-4 p-5", onClick: (e) => e.stopPropagation(), style: { animation: "field-slide-in 200ms ease-out" }, children: [
3499
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: closeCancelDialog, children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-modal border bg-card shadow-lg mx-4 p-5", onClick: (e) => e.stopPropagation(), style: { animation: "field-slide-in 200ms ease-out" }, children: [
2089
3500
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
2090
3501
  /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 shrink-0", children: /* @__PURE__ */ jsx(Ban, { className: "h-5 w-5 text-destructive" }) }),
2091
3502
  /* @__PURE__ */ jsxs("div", { children: [
@@ -2094,9 +3505,21 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
2094
3505
  ] })
2095
3506
  ] }),
2096
3507
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-4", children: t2("financial.invoiceForm.cancelDesc") }),
3508
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", htmlFor: "invoice-form-cancel-reason", children: t2("financial.invoice.cancelReason") }),
3509
+ /* @__PURE__ */ jsx(
3510
+ "textarea",
3511
+ {
3512
+ id: "invoice-form-cancel-reason",
3513
+ value: cancelReason,
3514
+ onChange: (event) => setCancelReason(event.target.value),
3515
+ placeholder: t2("financial.invoice.cancelReasonPlaceholder"),
3516
+ className: "mb-4 min-h-20 w-full resize-y rounded-md border bg-background px-3 py-2 text-sm",
3517
+ autoFocus: true
3518
+ }
3519
+ ),
2097
3520
  /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
2098
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmCancel(false), children: t2("financial.invoiceForm.keepInvoice") }),
2099
- /* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancelInvoice, disabled: cancelling, children: [
3521
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: closeCancelDialog, children: t2("financial.invoiceForm.keepInvoice") }),
3522
+ /* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancelInvoice, disabled: cancelling || !cancelReason.trim(), children: [
2100
3523
  /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
2101
3524
  cancelling ? t2("financial.invoiceForm.cancelling") : t2("financial.invoiceForm.yesCancel")
2102
3525
  ] })
@@ -2180,8 +3603,15 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
2180
3603
  const [menuOpen, setMenuOpen] = useState(false);
2181
3604
  const [confirmCancel, setConfirmCancel] = useState(false);
2182
3605
  const [cancelling, setCancelling] = useState(false);
3606
+ const [cancelReason, setCancelReason] = useState("");
3607
+ const cancelRequestKey = useRef(createIdempotencyKey("financial:invoice-cancel"));
2183
3608
  const menuRef = useRef(null);
2184
3609
  const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
3610
+ function closeCancelDialog() {
3611
+ setConfirmCancel(false);
3612
+ setCancelReason("");
3613
+ cancelRequestKey.current = createIdempotencyKey("financial:invoice-cancel");
3614
+ }
2185
3615
  async function loadData() {
2186
3616
  setLoading(true);
2187
3617
  const [inv, invItems, movResult] = await Promise.all([
@@ -2204,6 +3634,9 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
2204
3634
  useEffect(() => {
2205
3635
  loadData();
2206
3636
  }, [invoiceId]);
3637
+ useDataChanged({ table: T.movements }, () => {
3638
+ void loadData();
3639
+ }, [invoiceId, direction]);
2207
3640
  useEffect(() => {
2208
3641
  if (!menuOpen) return;
2209
3642
  function handleClick(e) {
@@ -2227,9 +3660,13 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
2227
3660
  async function handleCancel() {
2228
3661
  setCancelling(true);
2229
3662
  try {
2230
- await cancelInvoice(invoiceId);
3663
+ await cancelInvoice({
3664
+ invoiceId,
3665
+ reason: cancelReason,
3666
+ idempotencyKey: cancelRequestKey.current
3667
+ });
2231
3668
  await loadData();
2232
- setConfirmCancel(false);
3669
+ closeCancelDialog();
2233
3670
  setMenuOpen(false);
2234
3671
  } catch {
2235
3672
  } finally {
@@ -2317,6 +3754,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
2317
3754
  badges: [{ label: t2(statusConfig.labelKey), tone: statusConfig.tone }],
2318
3755
  onBack,
2319
3756
  backLabel: parentLabel,
3757
+ meta: invoice,
2320
3758
  actions,
2321
3759
  stats: [
2322
3760
  { id: "total", label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
@@ -2577,7 +4015,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
2577
4015
  }
2578
4016
  ),
2579
4017
  confirmCancel && createPortal(
2580
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setConfirmCancel(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-modal border bg-card shadow-lg mx-4 p-5", onClick: (e) => e.stopPropagation(), style: { animation: "field-slide-in 200ms ease-out" }, children: [
4018
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: closeCancelDialog, children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-modal border bg-card shadow-lg mx-4 p-5", onClick: (e) => e.stopPropagation(), style: { animation: "field-slide-in 200ms ease-out" }, children: [
2581
4019
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
2582
4020
  /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 shrink-0", children: /* @__PURE__ */ jsx(Ban, { className: "h-5 w-5 text-destructive" }) }),
2583
4021
  /* @__PURE__ */ jsxs("div", { children: [
@@ -2586,9 +4024,21 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
2586
4024
  ] })
2587
4025
  ] }),
2588
4026
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-4", children: t2("financial.invoice.cancelDesc") }),
4027
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", htmlFor: "invoice-cancel-reason", children: t2("financial.invoice.cancelReason") }),
4028
+ /* @__PURE__ */ jsx(
4029
+ "textarea",
4030
+ {
4031
+ id: "invoice-cancel-reason",
4032
+ value: cancelReason,
4033
+ onChange: (event) => setCancelReason(event.target.value),
4034
+ placeholder: t2("financial.invoice.cancelReasonPlaceholder"),
4035
+ className: "mb-4 min-h-20 w-full resize-y rounded-md border bg-background px-3 py-2 text-sm",
4036
+ autoFocus: true
4037
+ }
4038
+ ),
2589
4039
  /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
2590
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmCancel(false), children: t2("financial.invoice.keepInvoice") }),
2591
- /* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancel, disabled: cancelling, children: [
4040
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: closeCancelDialog, children: t2("financial.invoice.keepInvoice") }),
4041
+ /* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancel, disabled: cancelling || !cancelReason.trim(), children: [
2592
4042
  /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
2593
4043
  cancelling ? t2("financial.invoice.cancelling") : t2("financial.invoice.yesCancel")
2594
4044
  ] })
@@ -2948,6 +4398,7 @@ function CashOpeningPanel({
2948
4398
  /* @__PURE__ */ jsxs(
2949
4399
  "button",
2950
4400
  {
4401
+ "data-tour": "financial.cash-open",
2951
4402
  onClick: () => setConfirming(true),
2952
4403
  disabled: effectiveBalance < 0,
2953
4404
  className: "inline-flex items-center gap-1.5 rounded-button bg-primary border border-primary px-4 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors disabled:opacity-50",
@@ -3094,7 +4545,7 @@ function ReviewQueue({ variant }) {
3094
4545
  const me = user?.id ?? null;
3095
4546
  const [busy, setBusy] = useState(null);
3096
4547
  const [error, setError] = useState(null);
3097
- const load = React14__default.useCallback(async () => {
4548
+ const load = React15__default.useCallback(async () => {
3098
4549
  try {
3099
4550
  setError(null);
3100
4551
  setRows(await provider.getCashSessionReviews?.() ?? []);
@@ -3252,6 +4703,7 @@ function CashRegistersView({ variant = "page" } = {}) {
3252
4703
  expandedClosing === session.id ? /* @__PURE__ */ jsx(CashClosingPanel, { session, currency, onClose: handleClose }) : /* @__PURE__ */ jsxs(
3253
4704
  "button",
3254
4705
  {
4706
+ "data-tour": "financial.cash-close",
3255
4707
  onClick: () => setExpandedClosing(session.id),
3256
4708
  className: "inline-flex items-center gap-1.5 text-xs text-destructive hover:text-destructive font-medium transition-colors",
3257
4709
  children: [
@@ -3706,7 +5158,7 @@ function StatementsView({ onNavigate }) {
3706
5158
  about: "A movimenta\xE7\xE3o de uma conta por vez: cr\xE9ditos, d\xE9bitos e transfer\xEAncias no per\xEDodo. \xC9 tamb\xE9m de onde sai a transfer\xEAncia entre duas contas."
3707
5159
  }
3708
5160
  ),
3709
- /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
5161
+ /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
3710
5162
  /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
3711
5163
  t2("financial.statements.newTransfer")
3712
5164
  ] })
@@ -3797,7 +5249,7 @@ function firstOfMonth() {
3797
5249
  d.setDate(1);
3798
5250
  return d.toISOString().slice(0, 10);
3799
5251
  }
3800
- function today() {
5252
+ function today2() {
3801
5253
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3802
5254
  }
3803
5255
  function money(v) {
@@ -3817,14 +5269,14 @@ function CommissionsView() {
3817
5269
  const fetchCommissions = useFinancialStore((s) => s.fetchCommissions);
3818
5270
  const generateCommissions = useFinancialStore((s) => s.generateCommissions);
3819
5271
  const provider = useFinancialProvider();
3820
- const [from, setFrom] = React14__default.useState(firstOfMonth());
3821
- const [to, setTo] = React14__default.useState(today());
3822
- const [running, setRunning] = React14__default.useState(false);
3823
- const [lastRun, setLastRun] = React14__default.useState(null);
3824
- const [generation, setGeneration] = React14__default.useState(null);
3825
- const [busy, setBusy] = React14__default.useState(null);
3826
- const [error, setError] = React14__default.useState(null);
3827
- const loadGeneration = React14__default.useCallback(async () => {
5272
+ const [from, setFrom] = React15__default.useState(firstOfMonth());
5273
+ const [to, setTo] = React15__default.useState(today2());
5274
+ const [running, setRunning] = React15__default.useState(false);
5275
+ const [lastRun, setLastRun] = React15__default.useState(null);
5276
+ const [generation, setGeneration] = React15__default.useState(null);
5277
+ const [busy, setBusy] = React15__default.useState(null);
5278
+ const [error, setError] = React15__default.useState(null);
5279
+ const loadGeneration = React15__default.useCallback(async () => {
3828
5280
  try {
3829
5281
  const gens = await provider.getCommissionGenerations?.() ?? [];
3830
5282
  const doPeriodo = gens.filter((g) => g.periodStart === from && g.periodEnd === to);
@@ -3832,11 +5284,11 @@ function CommissionsView() {
3832
5284
  } catch {
3833
5285
  }
3834
5286
  }, [provider, from, to]);
3835
- React14__default.useEffect(() => {
5287
+ React15__default.useEffect(() => {
3836
5288
  void fetchCommissions({ periodStart: from, periodEnd: to });
3837
5289
  void loadGeneration();
3838
5290
  }, [fetchCommissions, loadGeneration, from, to]);
3839
- const byPerson = React14__default.useMemo(() => {
5291
+ const byPerson = React15__default.useMemo(() => {
3840
5292
  const map = /* @__PURE__ */ new Map();
3841
5293
  for (const c of commissions2) {
3842
5294
  const key = c.personId;
@@ -4365,13 +5817,13 @@ function money2(v) {
4365
5817
  function GoalsView() {
4366
5818
  const { labels } = useFinancialConfig();
4367
5819
  const provider = useFinancialProvider();
4368
- const [units, setUnits] = React14__default.useState([]);
4369
- const [unitId, setUnitId] = React14__default.useState("");
4370
- const [year, setYear] = React14__default.useState((/* @__PURE__ */ new Date()).getFullYear());
4371
- const [rows, setRows] = React14__default.useState(null);
4372
- const [editing, setEditing] = React14__default.useState(null);
4373
- const [error, setError] = React14__default.useState(null);
4374
- React14__default.useEffect(() => {
5820
+ const [units, setUnits] = React15__default.useState([]);
5821
+ const [unitId, setUnitId] = React15__default.useState("");
5822
+ const [year, setYear] = React15__default.useState((/* @__PURE__ */ new Date()).getFullYear());
5823
+ const [rows, setRows] = React15__default.useState(null);
5824
+ const [editing, setEditing] = React15__default.useState(null);
5825
+ const [error, setError] = React15__default.useState(null);
5826
+ React15__default.useEffect(() => {
4375
5827
  void (async () => {
4376
5828
  const cat = await provider.getCommissionCatalogs?.();
4377
5829
  const us = cat?.units ?? [];
@@ -4379,7 +5831,7 @@ function GoalsView() {
4379
5831
  setUnitId((cur) => cur || us[0]?.id || "");
4380
5832
  })();
4381
5833
  }, [provider]);
4382
- const load = React14__default.useCallback(async () => {
5834
+ const load = React15__default.useCallback(async () => {
4383
5835
  if (!unitId) return;
4384
5836
  setRows(null);
4385
5837
  try {
@@ -4390,7 +5842,7 @@ function GoalsView() {
4390
5842
  setRows([]);
4391
5843
  }
4392
5844
  }, [provider, unitId, year]);
4393
- React14__default.useEffect(() => {
5845
+ React15__default.useEffect(() => {
4394
5846
  void load();
4395
5847
  }, [load]);
4396
5848
  const totalRealizado = (rows ?? []).reduce((s, r) => s + r.achievedAmount, 0);
@@ -4531,11 +5983,11 @@ function GoalDialog({
4531
5983
  onSaved
4532
5984
  }) {
4533
5985
  const provider = useFinancialProvider();
4534
- const [target, setTarget] = React14__default.useState(String(goal.targetAmount || ""));
4535
- const [sup, setSup] = React14__default.useState(String(goal.superAmount || ""));
4536
- const [hyper, setHyper] = React14__default.useState(String(goal.hyperAmount || ""));
4537
- const [saving, setSaving] = React14__default.useState(false);
4538
- const [error, setError] = React14__default.useState(null);
5986
+ const [target, setTarget] = React15__default.useState(String(goal.targetAmount || ""));
5987
+ const [sup, setSup] = React15__default.useState(String(goal.superAmount || ""));
5988
+ const [hyper, setHyper] = React15__default.useState(String(goal.hyperAmount || ""));
5989
+ const [saving, setSaving] = React15__default.useState(false);
5990
+ const [error, setError] = React15__default.useState(null);
4539
5991
  const t2 = Number(target) || 0;
4540
5992
  const s = Number(sup) || 0;
4541
5993
  const h = Number(hyper) || 0;
@@ -4625,21 +6077,21 @@ function Dash() {
4625
6077
  function GiftCardsView() {
4626
6078
  const provider = useFinancialProvider();
4627
6079
  const { currency } = useFinancialConfig();
4628
- const [enabled, setEnabled] = React14__default.useState(null);
4629
- const [cards, setCards] = React14__default.useState(null);
4630
- const [error, setError] = React14__default.useState(null);
4631
- const [issuing, setIssuing] = React14__default.useState(false);
4632
- const [issued, setIssued] = React14__default.useState(null);
4633
- const [activating, setActivating] = React14__default.useState(null);
4634
- const [search, setSearch] = React14__default.useState("");
4635
- const [state, setState] = React14__default.useState("");
4636
- const [minValue, setMinValue] = React14__default.useState("");
4637
- const [maxValue, setMaxValue] = React14__default.useState("");
4638
- const [from, setFrom] = React14__default.useState("");
4639
- const [to, setTo] = React14__default.useState("");
4640
- const money3 = React14__default.useCallback((v) => formatCurrency(v, currency), [currency]);
4641
- const seq = React14__default.useRef(0);
4642
- const load = React14__default.useCallback(async () => {
6080
+ const [enabled, setEnabled] = React15__default.useState(null);
6081
+ const [cards, setCards] = React15__default.useState(null);
6082
+ const [error, setError] = React15__default.useState(null);
6083
+ const [issuing, setIssuing] = React15__default.useState(false);
6084
+ const [issued, setIssued] = React15__default.useState(null);
6085
+ const [activating, setActivating] = React15__default.useState(null);
6086
+ const [search, setSearch] = React15__default.useState("");
6087
+ const [state, setState] = React15__default.useState("");
6088
+ const [minValue, setMinValue] = React15__default.useState("");
6089
+ const [maxValue, setMaxValue] = React15__default.useState("");
6090
+ const [from, setFrom] = React15__default.useState("");
6091
+ const [to, setTo] = React15__default.useState("");
6092
+ const money3 = React15__default.useCallback((v) => formatCurrency(v, currency), [currency]);
6093
+ const seq = React15__default.useRef(0);
6094
+ const load = React15__default.useCallback(async () => {
4643
6095
  const mine = ++seq.current;
4644
6096
  setCards(null);
4645
6097
  setError(null);
@@ -4660,7 +6112,7 @@ function GiftCardsView() {
4660
6112
  setCards([]);
4661
6113
  }
4662
6114
  }, [provider, search, state, minValue, maxValue, from, to]);
4663
- React14__default.useEffect(() => {
6115
+ React15__default.useEffect(() => {
4664
6116
  void (async () => {
4665
6117
  try {
4666
6118
  setEnabled(await provider.giftCardsEnabled?.() === true);
@@ -4669,7 +6121,7 @@ function GiftCardsView() {
4669
6121
  }
4670
6122
  })();
4671
6123
  }, [provider]);
4672
- React14__default.useEffect(() => {
6124
+ React15__default.useEffect(() => {
4673
6125
  if (enabled) void load();
4674
6126
  }, [enabled, load]);
4675
6127
  const outstanding = (cards ?? []).filter((c) => c.state === "active").reduce((s, c) => s + (c.balance ?? 0), 0);
@@ -4884,12 +6336,12 @@ function IssueDialog({
4884
6336
  onIssued
4885
6337
  }) {
4886
6338
  const provider = useFinancialProvider();
4887
- const [quantity, setQuantity] = React14__default.useState("10");
4888
- const [amount, setAmount] = React14__default.useState("100");
4889
- const [expiresAt, setExpiresAt] = React14__default.useState("");
4890
- const [note, setNote] = React14__default.useState("");
4891
- const [saving, setSaving] = React14__default.useState(false);
4892
- const [error, setError] = React14__default.useState(null);
6339
+ const [quantity, setQuantity] = React15__default.useState("10");
6340
+ const [amount, setAmount] = React15__default.useState("100");
6341
+ const [expiresAt, setExpiresAt] = React15__default.useState("");
6342
+ const [note, setNote] = React15__default.useState("");
6343
+ const [saving, setSaving] = React15__default.useState(false);
6344
+ const [error, setError] = React15__default.useState(null);
4893
6345
  const q = Math.trunc(Number(quantity)) || 0;
4894
6346
  const a = Number(amount) || 0;
4895
6347
  const total = q * a;
@@ -5058,25 +6510,19 @@ function ReconciliationView() {
5058
6510
  }
5059
6511
  const selected = lines.find((l) => l.id === selectedId) ?? null;
5060
6512
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
5061
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
5062
- /* @__PURE__ */ jsxs("div", { children: [
5063
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold tracking-tight", children: t2("financial.reconciliation.title") }),
5064
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.reconciliation.subtitle") })
5065
- ] }),
5066
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5067
- /* @__PURE__ */ jsx(
5068
- TeachMeLink,
5069
- {
5070
- feature: "financial.conciliacao",
5071
- title: "Concilia\xE7\xE3o Banc\xE1ria",
5072
- about: "O extrato que vem do BANCO, n\xE3o do sistema, e a compara\xE7\xE3o entre os dois. Pendente aqui n\xE3o \xE9 conta a pagar: \xE9 transa\xE7\xE3o que o banco tem e ainda n\xE3o foi casada com um lan\xE7amento."
5073
- }
5074
- ),
5075
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: lines.length > 0 ? t2("financial.reconciliation.pending", { count: lines.length }) : t2("financial.reconciliation.allDone") }),
5076
- /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => void load(), disabled: loading || busy, children: [
5077
- /* @__PURE__ */ jsx(RefreshCw, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }),
5078
- t2("financial.reconciliation.refresh")
5079
- ] })
6513
+ /* @__PURE__ */ jsxs(PageHeaderActions, { children: [
6514
+ /* @__PURE__ */ jsx(
6515
+ TeachMeLink,
6516
+ {
6517
+ feature: "financial.conciliacao",
6518
+ title: "Concilia\xE7\xE3o Banc\xE1ria",
6519
+ about: "O extrato que vem do BANCO, n\xE3o do sistema, e a compara\xE7\xE3o entre os dois. Pendente aqui n\xE3o \xE9 conta a pagar: \xE9 transa\xE7\xE3o que o banco tem e ainda n\xE3o foi casada com um lan\xE7amento."
6520
+ }
6521
+ ),
6522
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: lines.length > 0 ? t2("financial.reconciliation.pending", { count: lines.length }) : t2("financial.reconciliation.allDone") }),
6523
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "sm", onClick: () => void load(), disabled: loading || busy, children: [
6524
+ /* @__PURE__ */ jsx(RefreshCw, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }),
6525
+ t2("financial.reconciliation.refresh")
5080
6526
  ] })
5081
6527
  ] }),
5082
6528
  lines.length === 0 && !loading ? /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm py-16 text-center", children: [
@@ -5241,7 +6687,7 @@ function NotasFiscaisView() {
5241
6687
  };
5242
6688
  if (!supported) {
5243
6689
  return /* @__PURE__ */ jsxs("div", { className: "p-1", children: [
5244
- /* @__PURE__ */ jsx(SubpageHeader, { title: "Notas Fiscais", subtitle: "Emiss\xE3o e acompanhamento de NF-e / NFC-e / NFS-e", icon: "FileText" }),
6690
+ /* @__PURE__ */ jsx(SubpageHeader, { title: "Notas Fiscais", subtitle: "Emiss\xE3o e acompanhamento de NF-e / NFC-e / NFS-e" }),
5245
6691
  /* @__PURE__ */ jsx("p", { className: "mt-4 text-sm text-muted-foreground", children: "Este provedor financeiro ainda n\xE3o exp\xF5e notas fiscais." })
5246
6692
  ] });
5247
6693
  }
@@ -5251,7 +6697,6 @@ function NotasFiscaisView() {
5251
6697
  {
5252
6698
  title: "Notas Fiscais",
5253
6699
  subtitle: "Emiss\xE3o e acompanhamento de NF-e / NFC-e / NFS-e",
5254
- icon: "FileText",
5255
6700
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [
5256
6701
  /* @__PURE__ */ jsx(
5257
6702
  TeachMeLink,
@@ -5262,8 +6707,8 @@ function NotasFiscaisView() {
5262
6707
  }
5263
6708
  ),
5264
6709
  canCreateManually && /* @__PURE__ */ jsx(PermissionGate, { feature: "financial", action: "create", children: /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => setCreating(true), children: [
5265
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
5266
- " Nova nota"
6710
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
6711
+ "Nova nota"
5267
6712
  ] }) })
5268
6713
  ] })
5269
6714
  }
@@ -5841,10 +7286,10 @@ function RecentTransactions() {
5841
7286
  function useDueLabel() {
5842
7287
  const t2 = useTranslation();
5843
7288
  return (dueDate) => {
5844
- const today3 = /* @__PURE__ */ new Date();
5845
- today3.setHours(0, 0, 0, 0);
7289
+ const today4 = /* @__PURE__ */ new Date();
7290
+ today4.setHours(0, 0, 0, 0);
5846
7291
  const due = /* @__PURE__ */ new Date(`${dueDate}T00:00:00`);
5847
- const days = Math.round((due.getTime() - today3.getTime()) / 864e5);
7292
+ const days = Math.round((due.getTime() - today4.getTime()) / 864e5);
5848
7293
  if (days < 0) return t2("financial.home.overdueDays", { count: String(Math.abs(days)) });
5849
7294
  if (days === 0) return t2("financial.home.dueToday");
5850
7295
  if (days === 1) return t2("financial.home.dueTomorrow");
@@ -5857,7 +7302,7 @@ function BalanceHero() {
5857
7302
  const summary = useFinancialStore((s) => s.summary);
5858
7303
  const navigate = useDashboardNavigate();
5859
7304
  useEnsureSummary();
5860
- const [hidden, setHidden] = React14__default.useState(false);
7305
+ const [hidden, setHidden] = React15__default.useState(false);
5861
7306
  const money3 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
5862
7307
  const inflow = summary?.monthlyInflow ?? 0;
5863
7308
  const outflow = summary?.monthlyOutflow ?? 0;
@@ -5930,7 +7375,7 @@ function categorizeSpend(notes) {
5930
7375
  function SpendByCategoryChart() {
5931
7376
  const t2 = useTranslation();
5932
7377
  const provider = useFinancialProvider();
5933
- const [rows, setRows] = React14__default.useState([]);
7378
+ const [rows, setRows] = React15__default.useState([]);
5934
7379
  useEffect(() => {
5935
7380
  let alive = true;
5936
7381
  void (async () => {
@@ -5973,7 +7418,7 @@ function UpcomingBills() {
5973
7418
  const { currency } = useFinancialConfig();
5974
7419
  const provider = useFinancialProvider();
5975
7420
  const dueLabel = useDueLabel();
5976
- const [bills, setBills] = React14__default.useState([]);
7421
+ const [bills, setBills] = React15__default.useState([]);
5977
7422
  useEffect(() => {
5978
7423
  let alive = true;
5979
7424
  void (async () => {
@@ -6074,10 +7519,10 @@ function uid() {
6074
7519
  function now() {
6075
7520
  return (/* @__PURE__ */ new Date()).toISOString();
6076
7521
  }
6077
- function today2() {
7522
+ function today3() {
6078
7523
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6079
7524
  }
6080
- function round2(n) {
7525
+ function round22(n) {
6081
7526
  return Math.round(n * 100) / 100;
6082
7527
  }
6083
7528
  function matchesDateRange(dateStr, range) {
@@ -6165,7 +7610,7 @@ function createMockFinancialProvider(options) {
6165
7610
  invoice.status = "partial";
6166
7611
  } else {
6167
7612
  const movements = store6.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
6168
- const hasOverdue = movements.some((m) => m.status === "overdue" || m.status === "pending" && m.dueDate < today2());
7613
+ const hasOverdue = movements.some((m) => m.status === "overdue" || m.status === "pending" && m.dueDate < today3());
6169
7614
  invoice.status = hasOverdue ? "overdue" : "open";
6170
7615
  }
6171
7616
  invoice.updatedAt = now();
@@ -6278,12 +7723,13 @@ function createMockFinancialProvider(options) {
6278
7723
  Object.assign(invoice, data, { updatedAt: now() });
6279
7724
  return invoice;
6280
7725
  },
6281
- async cancelInvoice(id) {
6282
- const invoice = store6.invoices.find((i) => i.id === id);
6283
- if (!invoice) throw new Error(`Invoice ${id} not found`);
7726
+ async cancelInvoice(input) {
7727
+ if (!input.reason.trim()) throw new Error("A cancellation reason is required");
7728
+ const invoice = store6.invoices.find((i) => i.id === input.invoiceId);
7729
+ if (!invoice) throw new Error(`Invoice ${input.invoiceId} not found`);
6284
7730
  invoice.status = "cancelled";
6285
7731
  invoice.updatedAt = now();
6286
- store6.movements.filter((m) => m.invoiceId === id && m.status === "pending").forEach((m) => {
7732
+ store6.movements.filter((m) => m.invoiceId === input.invoiceId && m.status === "pending").forEach((m) => {
6287
7733
  m.status = "cancelled";
6288
7734
  m.updatedAt = now();
6289
7735
  });
@@ -6319,7 +7765,7 @@ function createMockFinancialProvider(options) {
6319
7765
  if (!bill) throw new Error(`Movement ${input.movementId} not found`);
6320
7766
  const method = input.paymentMethodId ? store6.paymentMethods.find((pm) => pm.id === input.paymentMethodId) : void 0;
6321
7767
  const feePct = bill.direction === "credit" ? method?.discountValue ?? 0 : 0;
6322
- const thisFee = round2(input.amount * (feePct / 100));
7768
+ const thisFee = round22(input.amount * (feePct / 100));
6323
7769
  const payment = {
6324
7770
  id: uid(),
6325
7771
  invoiceId: bill.invoiceId,
@@ -6346,7 +7792,7 @@ function createMockFinancialProvider(options) {
6346
7792
  updatedAt: now()
6347
7793
  };
6348
7794
  store6.movements.push(payment);
6349
- bill.paidAmount = round2(bill.paidAmount + input.amount);
7795
+ bill.paidAmount = round22(bill.paidAmount + input.amount);
6350
7796
  bill.status = bill.paidAmount >= bill.amount ? "paid" : "partial";
6351
7797
  bill.paymentDate = void 0;
6352
7798
  bill.bankAccountId = void 0;
@@ -6355,9 +7801,9 @@ function createMockFinancialProvider(options) {
6355
7801
  const account = store6.bankAccounts.find((a) => a.id === input.bankAccountId);
6356
7802
  if (account) {
6357
7803
  if (bill.direction === "credit") {
6358
- account.currentBalance = round2(account.currentBalance + (input.amount - thisFee));
7804
+ account.currentBalance = round22(account.currentBalance + (input.amount - thisFee));
6359
7805
  } else {
6360
- account.currentBalance = round2(account.currentBalance - input.amount);
7806
+ account.currentBalance = round22(account.currentBalance - input.amount);
6361
7807
  }
6362
7808
  account.updatedAt = now();
6363
7809
  }
@@ -6417,11 +7863,11 @@ function createMockFinancialProvider(options) {
6417
7863
  const from = store6.bankAccounts.find((a) => a.id === input.fromAccountId);
6418
7864
  const to = store6.bankAccounts.find((a) => a.id === input.toAccountId);
6419
7865
  if (from) {
6420
- from.currentBalance = round2(from.currentBalance - input.amount);
7866
+ from.currentBalance = round22(from.currentBalance - input.amount);
6421
7867
  from.updatedAt = now();
6422
7868
  }
6423
7869
  if (to) {
6424
- to.currentBalance = round2(to.currentBalance + input.amount);
7870
+ to.currentBalance = round22(to.currentBalance + input.amount);
6425
7871
  to.updatedAt = now();
6426
7872
  }
6427
7873
  return { transferId, debitMovementId, creditMovementId };
@@ -6535,7 +7981,7 @@ function createMockFinancialProvider(options) {
6535
7981
  },
6536
7982
  // --- Statements (ERP extract: opening balance + realized cash + per-row net/fee) ---
6537
7983
  async getStatement(query) {
6538
- const todayStr = today2();
7984
+ const todayStr = today3();
6539
7985
  const isRealized = (m) => m.status !== "cancelled" && !!m.paymentDate && affectsCash(m);
6540
7986
  const consolidated = !query.bankAccountId;
6541
7987
  const scopeAccounts = consolidated ? store6.bankAccounts.filter((a) => a.isActive) : store6.bankAccounts.filter((a) => a.id === query.bankAccountId);
@@ -6547,7 +7993,7 @@ function createMockFinancialProvider(options) {
6547
7993
  }
6548
7994
  const netOf = (m) => m.direction === "credit" ? m.paidAmount - (m.feeAmount ?? 0) : -m.paidAmount;
6549
7995
  const inScope = store6.movements.filter((m) => isRealized(m) && (consolidated || m.bankAccountId && accountIds.has(m.bankAccountId)));
6550
- const openingBalance = round2(initialSum + inScope.filter((m) => (m.paymentDate ?? "") < query.dateRange.from).reduce((s, m) => s + netOf(m), 0));
7996
+ const openingBalance = round22(initialSum + inScope.filter((m) => (m.paymentDate ?? "") < query.dateRange.from).reduce((s, m) => s + netOf(m), 0));
6551
7997
  const rangeMovs = inScope.filter((m) => matchesDateRange(m.paymentDate, query.dateRange)).sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || a.createdAt.localeCompare(b.createdAt));
6552
7998
  let balance = openingBalance;
6553
7999
  let totalCredits = 0, totalDebits = 0, totalFees = 0;
@@ -6555,16 +8001,16 @@ function createMockFinancialProvider(options) {
6555
8001
  const gross = movement.paidAmount;
6556
8002
  const isCredit = movement.direction === "credit";
6557
8003
  const fee = isCredit ? movement.feeAmount ?? 0 : 0;
6558
- const net = isCredit ? round2(gross - fee) : gross;
8004
+ const net = isCredit ? round22(gross - fee) : gross;
6559
8005
  const isTransfer = movement.movementKind === "transfer";
6560
8006
  const meta = movement.metadata ?? {};
6561
8007
  const entryKind = isTransfer ? meta.transferRole === "in" ? "transfer-in" : "transfer-out" : "movement";
6562
8008
  const counterAccountId = isTransfer ? meta.counterAccountId : void 0;
6563
- balance = round2(isCredit ? balance + net : balance - net);
8009
+ balance = round22(isCredit ? balance + net : balance - net);
6564
8010
  if (!(consolidated && isTransfer)) {
6565
- if (isCredit) totalCredits = round2(totalCredits + net);
6566
- else totalDebits = round2(totalDebits + net);
6567
- totalFees = round2(totalFees + fee);
8011
+ if (isCredit) totalCredits = round22(totalCredits + net);
8012
+ else totalDebits = round22(totalDebits + net);
8013
+ totalFees = round22(totalFees + fee);
6568
8014
  }
6569
8015
  return {
6570
8016
  movement,
@@ -6591,7 +8037,7 @@ function createMockFinancialProvider(options) {
6591
8037
  contactName: inv?.contactName
6592
8038
  };
6593
8039
  }
6594
- const accountCurrentBalance = round2(initialSum + inScope.filter((m) => (m.paymentDate ?? "") <= todayStr).reduce((s, m) => s + netOf(m), 0));
8040
+ const accountCurrentBalance = round22(initialSum + inScope.filter((m) => (m.paymentDate ?? "") <= todayStr).reduce((s, m) => s + netOf(m), 0));
6595
8041
  return {
6596
8042
  entries,
6597
8043
  openingBalance,
@@ -6599,7 +8045,7 @@ function createMockFinancialProvider(options) {
6599
8045
  totalCredits,
6600
8046
  totalDebits,
6601
8047
  totalFees,
6602
- net: round2(totalCredits - totalDebits),
8048
+ net: round22(totalCredits - totalDebits),
6603
8049
  accountId: query.bankAccountId,
6604
8050
  accountCurrentBalance
6605
8051
  };
@@ -6613,7 +8059,7 @@ function createMockFinancialProvider(options) {
6613
8059
  const pendingPayable = store6.movements.filter(
6614
8060
  (m) => m.direction === "debit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
6615
8061
  );
6616
- const todayStr = today2();
8062
+ const todayStr = today3();
6617
8063
  const monthAgo = /* @__PURE__ */ new Date();
6618
8064
  monthAgo.setDate(monthAgo.getDate() - 30);
6619
8065
  const monthStart = monthAgo.toISOString().slice(0, 10);
@@ -6675,7 +8121,7 @@ function createMockFinancialProvider(options) {
6675
8121
  const line = store6.movements.find((m) => m.id === bankMovementId);
6676
8122
  if (!line) return [];
6677
8123
  const amount = line.amount;
6678
- const tol = Math.max(0.01, round2(amount * 0.02));
8124
+ const tol = Math.max(0.01, round22(amount * 0.02));
6679
8125
  const lineDate = line.paymentDate ?? line.dueDate;
6680
8126
  return store6.movements.filter((m) => !m.externalSource && !m.reconciledAt && m.direction === line.direction && m.id !== bankMovementId && Math.abs(m.amount - amount) <= tol).map((m) => {
6681
8127
  const amtScore = amount > 0 ? Math.max(0, 1 - Math.abs(m.amount - amount) / amount) : 0;
@@ -6685,7 +8131,7 @@ function createMockFinancialProvider(options) {
6685
8131
  return {
6686
8132
  movement: m,
6687
8133
  invoice: m.invoiceId ? store6.invoices.find((i) => i.id === m.invoiceId) : void 0,
6688
- score: round2(0.7 * amtScore + 0.3 * dateScore)
8134
+ score: round22(0.7 * amtScore + 0.3 * dateScore)
6689
8135
  };
6690
8136
  }).sort((a, b) => b.score - a.score).slice(0, 25);
6691
8137
  },
@@ -6789,46 +8235,6 @@ function createMockFinancialProvider(options) {
6789
8235
  return provider;
6790
8236
  }
6791
8237
 
6792
- // src/data/tables.ts
6793
- var T = {
6794
- movements: "plg_financial_movements",
6795
- cashMovements: "v_financial_cash_movements",
6796
- accountBalances: "v_financial_account_balances",
6797
- chartOfAccounts: "plg_financial_chart_of_accounts",
6798
- bankAccounts: "plg_financial_bank_accounts",
6799
- cardBrands: "plg_financial_card_brands",
6800
- costCenters: "plg_financial_cost_centers",
6801
- cashRegisterSessions: "plg_financial_cash_register_sessions",
6802
- paymentMethods: "plg_financial_payment_methods",
6803
- paymentMethodTypes: "plg_financial_payment_method_types",
6804
- notas: "plg_financial_nfe",
6805
- // A contabilidade da LINHA. public.items é a linha; o que só o financeiro
6806
- // sabe dela — plano de contas, centro de custo, imposto, CFOP — mora aqui,
6807
- // com item_id de chave. Ver 038_the_invoice_gets_a_line.sql.
6808
- invoiceItems: "plg_financial_invoice_items",
6809
- // A regra de comissão. O registry apontava para `commission_rules`, sem
6810
- // prefixo — nome que a 028 aposentou e que não existe em pool nenhum.
6811
- commissionRules: "plg_financial_commission_rules",
6812
- commissionRuleCriteria: "plg_financial_commission_rule_criteria",
6813
- commissionFunctions: "plg_financial_commission_functions",
6814
- // The legacy `movement_kind='bill'` SHAPE, derived from the instalment
6815
- // balances (financial 040). Reading it instead of the mirror is what let the
6816
- // mirror stop being written: the columns are the same, the source is one.
6817
- // The instalment IS the obligation; the `movement_kind='bill'` shape and
6818
- // its compat view left in financial 044.
6819
- bills: "v_financial_installment_balances",
6820
- commissions: "v_financial_commissions",
6821
- commissionGenerations: "plg_financial_commission_generations",
6822
- periods: "plg_financial_periods",
6823
- trialBalance: "v_financial_trial_balance",
6824
- aging: "v_financial_aging",
6825
- agingSummary: "v_financial_aging_summary",
6826
- lineMargin: "v_financial_line_margin",
6827
- marginByMonth: "v_financial_margin_by_month",
6828
- recurringBills: "plg_financial_recurring_bills",
6829
- discountsGiven: "v_financial_discounts_given"
6830
- };
6831
-
6832
8238
  // src/data/supabase.ts
6833
8239
  function hasCriteriaValue(v) {
6834
8240
  if (!v) return false;
@@ -6863,8 +8269,8 @@ async function sweepOverdue() {
6863
8269
  _overdueSwept = true;
6864
8270
  try {
6865
8271
  const { core, pub } = getClients();
6866
- const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6867
- const { data: overdueMovs } = await pub.from(T.movements).update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today3).select("invoice_id");
8272
+ const today4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8273
+ const { data: overdueMovs } = await pub.from(T.movements).update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today4).select("invoice_id");
6868
8274
  if (overdueMovs && overdueMovs.length > 0) {
6869
8275
  const invoiceIds = [...new Set(overdueMovs.map((m) => m.invoice_id).filter(Boolean))];
6870
8276
  for (const invId of invoiceIds) {
@@ -6880,7 +8286,7 @@ async function sweepOverdue() {
6880
8286
  } catch {
6881
8287
  }
6882
8288
  }
6883
- function round22(n) {
8289
+ function round23(n) {
6884
8290
  return Math.round(n * 100) / 100;
6885
8291
  }
6886
8292
  async function findInvoiceIdsByLineAccounting(core, pub, filters) {
@@ -6954,7 +8360,8 @@ function rowToInvoice(r) {
6954
8360
  metadata: r.metadata ?? void 0,
6955
8361
  tenantId: r.tenant_id,
6956
8362
  createdAt: r.created_at,
6957
- updatedAt: r.updated_at
8363
+ updatedAt: r.updated_at,
8364
+ createdBy: r.created_by ?? void 0
6958
8365
  };
6959
8366
  }
6960
8367
  async function getMethodFeePct(pub, paymentMethodId) {
@@ -7051,15 +8458,15 @@ function createSupabaseFinancialProvider() {
7051
8458
  async createInvoice(input) {
7052
8459
  const { core, pub } = getClients();
7053
8460
  const tenantId = getTenantId();
7054
- const lineTotal = (i) => round22(i.quantity * i.unitPrice - (i.discount ?? 0) + (i.surcharge ?? 0));
7055
- const totalAmount = round22(input.items.reduce((sum, i) => sum + lineTotal(i), 0));
8461
+ const lineTotal = (i) => round23(i.quantity * i.unitPrice - (i.discount ?? 0) + (i.surcharge ?? 0));
8462
+ const totalAmount = round23(input.items.reduce((sum, i) => sum + lineTotal(i), 0));
7056
8463
  const itemsSummary = input.items.map((i) => i.description).filter(Boolean).join(", ");
7057
8464
  const installments = (input.installments.length > 0 ? input.installments : [
7058
8465
  { amount: totalAmount, dueDate: input.invoiceDate ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) }
7059
8466
  ]).map((inst, n) => ({
7060
8467
  number: inst.installmentNumber ?? n + 1,
7061
8468
  due_date: inst.dueDate,
7062
- amount: round22(inst.amount),
8469
+ amount: round23(inst.amount),
7063
8470
  ...inst.metadata ? { metadata: inst.metadata } : {}
7064
8471
  }));
7065
8472
  const { data: res, error } = await pub.rpc("financial_create_invoice", {
@@ -7102,12 +8509,14 @@ function createSupabaseFinancialProvider() {
7102
8509
  if (Object.keys(row).length > 0) await pub.from("plg_financial_invoices").update(row).eq("id", id);
7103
8510
  return await provider.getInvoiceById(id);
7104
8511
  },
7105
- async cancelInvoice(id) {
8512
+ async cancelInvoice(input) {
7106
8513
  const { pub } = getClients();
8514
+ const reason = input.reason.trim();
8515
+ if (!reason) throw new Error("A cancellation reason is required");
7107
8516
  const { error } = await pub.rpc("financial_cancel_invoice", {
7108
- p_invoice_id: id,
7109
- p_reason: null,
7110
- p_idempotency_key: genId()
8517
+ p_invoice_id: input.invoiceId,
8518
+ p_reason: reason,
8519
+ p_idempotency_key: input.idempotencyKey ?? createIdempotencyKey("financial:invoice-cancel")
7111
8520
  });
7112
8521
  if (error) throw error;
7113
8522
  },
@@ -7264,7 +8673,7 @@ function createSupabaseFinancialProvider() {
7264
8673
  method = type?.transaction_type ?? null;
7265
8674
  }
7266
8675
  const feePct = before.direction === "receivable" ? await getMethodFeePct(pub, input.paymentMethodId) : 0;
7267
- const fee = round22(input.amount * (feePct / 100));
8676
+ const fee = round23(input.amount * (feePct / 100));
7268
8677
  const { error } = await pub.rpc("financial_receive_payment", {
7269
8678
  p_installment_id: input.movementId,
7270
8679
  p_amount: input.amount,
@@ -7279,11 +8688,16 @@ function createSupabaseFinancialProvider() {
7279
8688
  p_payment_date: input.paymentDate,
7280
8689
  p_fee_amount: fee || null,
7281
8690
  p_notes: input.notes ?? null,
8691
+ p_idempotency_key: input.idempotencyKey ?? createIdempotencyKey("financial:payment"),
7282
8692
  p_metadata: input.metadata ?? {}
7283
8693
  });
7284
8694
  if (error) throw error;
7285
- const { data: after } = await pub.from(T.bills).select("*").eq("installment_id", input.movementId).maybeSingle();
7286
- return billRowToMovement(after ?? before);
8695
+ try {
8696
+ const { data: after } = await pub.from(T.bills).select("*").eq("installment_id", input.movementId).maybeSingle();
8697
+ return billRowToMovement(after ?? before);
8698
+ } catch {
8699
+ return billRowToMovement(before);
8700
+ }
7287
8701
  },
7288
8702
  async cancelMovement(id) {
7289
8703
  const { pub } = getClients();
@@ -7532,7 +8946,7 @@ function createSupabaseFinancialProvider() {
7532
8946
  // --- Statements (ERP extract: opening balance + realized cash + per-row net/fee) ---
7533
8947
  async getStatement(query) {
7534
8948
  const { core, pub } = getClients();
7535
- const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8949
+ const today4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7536
8950
  const { data: acctRows } = await pub.from("plg_financial_accounts").select("id, name, is_active");
7537
8951
  const allAccounts = acctRows ?? [];
7538
8952
  const consolidated = !query.bankAccountId;
@@ -7547,7 +8961,7 @@ function createSupabaseFinancialProvider() {
7547
8961
  const netOf = (r) => r.direction === "credit" ? (Number(r.paid_amount) || 0) - (Number(r.fee_amount) || 0) : -(Number(r.paid_amount) || 0);
7548
8962
  const { data: priorRows } = await scopeFilter(pub.from(T.cashMovements).select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
7549
8963
  const priorNet = (priorRows ?? []).reduce((s, r) => s + netOf(r), 0);
7550
- const openingBalance = round22(initialSum + priorNet);
8964
+ const openingBalance = round23(initialSum + priorNet);
7551
8965
  const { data: movs } = await scopeFilter(pub.from(T.cashMovements).select("*").neq("status", "cancelled").gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to).order("payment_date").order("created_at"));
7552
8966
  const invoiceIds = [...new Set((movs ?? []).map((r) => r.invoice_id).filter(Boolean))];
7553
8967
  const invoiceById = /* @__PURE__ */ new Map();
@@ -7564,17 +8978,17 @@ function createSupabaseFinancialProvider() {
7564
8978
  const gross = Number(r.paid_amount) || 0;
7565
8979
  const isCredit = r.direction === "credit";
7566
8980
  const fee = isCredit ? Number(r.fee_amount) || 0 : 0;
7567
- const net = isCredit ? round22(gross - fee) : gross;
8981
+ const net = isCredit ? round23(gross - fee) : gross;
7568
8982
  const meta = r.metadata ?? {};
7569
8983
  const isTransfer = r.movement_kind === "transfer";
7570
8984
  const entryKind = isTransfer ? meta.transferRole === "in" ? "transfer-in" : "transfer-out" : "movement";
7571
8985
  const counterAccountId = isTransfer ? meta.counterAccountId : void 0;
7572
- if (isCredit) balance = round22(balance + net);
7573
- else balance = round22(balance - net);
8986
+ if (isCredit) balance = round23(balance + net);
8987
+ else balance = round23(balance - net);
7574
8988
  if (!(consolidated && isTransfer)) {
7575
- if (isCredit) totalCredits = round22(totalCredits + net);
7576
- else totalDebits = round22(totalDebits + net);
7577
- totalFees = round22(totalFees + fee);
8989
+ if (isCredit) totalCredits = round23(totalCredits + net);
8990
+ else totalDebits = round23(totalDebits + net);
8991
+ totalFees = round23(totalFees + fee);
7578
8992
  }
7579
8993
  return {
7580
8994
  movement,
@@ -7612,8 +9026,8 @@ function createSupabaseFinancialProvider() {
7612
9026
  };
7613
9027
  }
7614
9028
  }
7615
- const { data: todayRows } = await scopeFilter(pub.from(T.cashMovements).select("*").neq("status", "cancelled").lte("payment_date", today3));
7616
- const accountCurrentBalance = round22(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
9029
+ const { data: todayRows } = await scopeFilter(pub.from(T.cashMovements).select("*").neq("status", "cancelled").lte("payment_date", today4));
9030
+ const accountCurrentBalance = round23(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
7617
9031
  return {
7618
9032
  entries,
7619
9033
  openingBalance,
@@ -7621,7 +9035,7 @@ function createSupabaseFinancialProvider() {
7621
9035
  totalCredits,
7622
9036
  totalDebits,
7623
9037
  totalFees,
7624
- net: round22(totalCredits - totalDebits),
9038
+ net: round23(totalCredits - totalDebits),
7625
9039
  accountId: query.bankAccountId,
7626
9040
  accountCurrentBalance
7627
9041
  };
@@ -8103,15 +9517,15 @@ function createSupabaseFinancialProvider() {
8103
9517
  const totalBalance = (accounts ?? []).reduce((s, a) => s + (Number(a.balance) || 0), 0);
8104
9518
  const allMovs = movs ?? [];
8105
9519
  const unpaid = allMovs.filter((m) => ["pending", "partial", "overdue"].includes(m.status));
8106
- const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9520
+ const today4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8107
9521
  const unpaidCredit = unpaid.filter((m) => m.direction === "credit");
8108
9522
  const unpaidDebit = unpaid.filter((m) => m.direction === "debit");
8109
- const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today3);
8110
- const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today3);
9523
+ const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today4);
9524
+ const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today4);
8111
9525
  const monthAgo = /* @__PURE__ */ new Date();
8112
9526
  monthAgo.setDate(monthAgo.getDate() - 30);
8113
9527
  const monthStart = monthAgo.toISOString().slice(0, 10);
8114
- const period = dateRange ?? { from: monthStart, to: today3 };
9528
+ const period = dateRange ?? { from: monthStart, to: today4 };
8115
9529
  const monthPaid = (cashMovs ?? []).filter((m) => m.movement_kind === "payment" && m.payment_date >= period.from && m.payment_date <= period.to);
8116
9530
  return {
8117
9531
  totalBalance,
@@ -8170,8 +9584,8 @@ function createSupabaseFinancialProvider() {
8170
9584
  if (!line) return [];
8171
9585
  const amount = Number(line.amount) || 0;
8172
9586
  const lineDate = line.payment_date ?? line.due_date;
8173
- const tol = Math.max(0.01, round22(amount * 0.02));
8174
- const { data, error } = await pub.from(T.movements).select("*").is("external_source", null).is("reconciled_at", null).eq("direction", line.direction).gte("amount", round22(amount - tol)).lte("amount", round22(amount + tol)).neq("id", bankMovementId).limit(25);
9587
+ const tol = Math.max(0.01, round23(amount * 0.02));
9588
+ const { data, error } = await pub.from(T.movements).select("*").is("external_source", null).is("reconciled_at", null).eq("direction", line.direction).gte("amount", round23(amount - tol)).lte("amount", round23(amount + tol)).neq("id", bankMovementId).limit(25);
8175
9589
  if (error) throw error;
8176
9590
  const candidates = (data ?? []).map((r) => {
8177
9591
  const amtDiff = Math.abs((Number(r.amount) || 0) - amount);
@@ -8181,7 +9595,7 @@ function createSupabaseFinancialProvider() {
8181
9595
  const dateScore = Math.max(0, 1 - days / 30);
8182
9596
  return {
8183
9597
  movement: snakeToCamel(r),
8184
- score: round22(0.7 * amtScore + 0.3 * dateScore)
9598
+ score: round23(0.7 * amtScore + 0.3 * dateScore)
8185
9599
  };
8186
9600
  }).sort((a, b) => b.score - a.score);
8187
9601
  const invoiceIds = [...new Set(candidates.map((c) => c.movement.invoiceId).filter(Boolean))];
@@ -8536,13 +9950,17 @@ function createFinancialStore(provider) {
8536
9950
  async payMovement(input) {
8537
9951
  try {
8538
9952
  await provider.payMovement(input);
9953
+ } catch (err) {
9954
+ toast.error("Failed to record payment", { description: err?.message });
9955
+ throw err;
9956
+ }
9957
+ try {
8539
9958
  const query = get().invoiceQuery;
8540
9959
  const [result, summary] = await Promise.all([provider.getInvoices(query), provider.getSummary()]);
8541
9960
  set({ invoices: result.data, invoicesTotal: result.total, summary });
8542
9961
  toast.success("Payment recorded");
8543
- } catch (err) {
8544
- toast.error("Failed to record payment", { description: err?.message });
8545
- throw err;
9962
+ } catch {
9963
+ toast.warning("Payment recorded; refresh the list to see the updated balance");
8546
9964
  }
8547
9965
  },
8548
9966
  async reverseMovement(movementId, reason) {
@@ -8573,9 +9991,9 @@ function createFinancialStore(provider) {
8573
9991
  throw err;
8574
9992
  }
8575
9993
  },
8576
- async cancelInvoice(invoiceId) {
9994
+ async cancelInvoice(input) {
8577
9995
  try {
8578
- await provider.cancelInvoice(invoiceId);
9996
+ await provider.cancelInvoice(input);
8579
9997
  const query = get().invoiceQuery;
8580
9998
  const [result, summary] = await Promise.all([provider.getInvoices(query), provider.getSummary()]);
8581
9999
  set({ invoices: result.data, invoicesTotal: result.total, summary });
@@ -8794,6 +10212,10 @@ var financialRegistries = [
8794
10212
  entity: paymentMethodEntity,
8795
10213
  icon: "Wallet",
8796
10214
  description: "Configured payment methods with fees and installment rules",
10215
+ // O nome convida ao erro: pedido "salva essa conta a pagar do fornecedor X,
10216
+ // pago hoje", o assistente cadastrou o FORNECEDOR aqui como forma de
10217
+ // pagamento. Isto é configuração de como o CLIENTE paga a empresa.
10218
+ agentHint: "CONFIGURATION of how customers pay this business (card brand, fee, installment rules). NEVER for a supplier, a bill or an expense \u2014 those are payables, see the createPayable tool",
8797
10219
  display: "table"
8798
10220
  },
8799
10221
  {
@@ -8997,15 +10419,25 @@ var payables = {
8997
10419
  check: "existe pelo menos uma conta cadastrada"
8998
10420
  }
8999
10421
  ],
10422
+ // The anchored steps ARE the tour: startFeatureSetup turns each one into a
10423
+ // spotlight stop, in this order, navigating per stop. The unanchored ones
10424
+ // stay prose — briefing for the assistant to say in its own words.
10425
+ // O tour NAVEGA até a tela de cadastro (3 paradas, cada uma com rota, então
10426
+ // reiniciar renavega). Chegando, a `practice` assume: a pessoa preenche os
10427
+ // campos sozinha com os marcadores numerados, para FIXAR o cadastro.
9000
10428
  steps: [
9001
- { do: "Financeiro \u203A Contas a pagar \u203A Nova.", route: "/financial/payables/new" },
9002
- { do: "Diga a quem se deve, o valor total e a classifica\xE7\xE3o (plano de contas)." },
9003
- {
9004
- do: "Se for parcelado, informe o n\xFAmero de parcelas e confira as datas antes de salvar.",
9005
- why: "O cronograma nasce aqui. Editar depois recria o parcelamento inteiro."
9006
- },
10429
+ { do: "O Financeiro mora aqui.", anchor: "nav./financial", route: "/financial" },
10430
+ { do: "Abra Contas a Pagar neste menu.", anchor: "modnav.payables", route: "/financial/payables/list" },
10431
+ { do: "Clique em Cadastrar manualmente para abrir uma conta nova.", anchor: "action.manual", route: "/financial/payables/list" },
10432
+ { do: "Diga a quem se deve, adicione um item com o valor, confira as parcelas e salve." },
9007
10433
  { do: "Ao pagar, d\xEA baixa NA PARCELA, escolhendo a conta de onde o dinheiro saiu." }
9008
10434
  ],
10435
+ practice: [
10436
+ { anchor: "financial.contact", label: "Selecione o fornecedor" },
10437
+ { anchor: "financial.items", label: "Adicione um item com o valor" },
10438
+ { anchor: "financial.installments", label: "Confira as parcelas" },
10439
+ { anchor: "savebar.save", label: "Salve a conta", final: true }
10440
+ ],
9009
10441
  rules: [
9010
10442
  "Uma conta a pagar \xE9 um cabe\xE7alho com parcelas: o que vence, o que se paga e o que aparece no extrato \xE9 sempre a PARCELA.",
9011
10443
  "Baixa exige dizer de qual conta saiu \u2014 \xE9 o que faz o extrato bater."
@@ -9038,15 +10470,18 @@ var receivables = {
9038
10470
  check: "a tela de receber oferece pelo menos uma forma"
9039
10471
  }
9040
10472
  ],
10473
+ // Só as abas de TOPO do módulo viram `modnav.<id>`; os filhos (list/new) são
10474
+ // um SegmentedControl sem âncora, então o tour navega pela aba e pela rota.
9041
10475
  steps: [
9042
- { do: "Financeiro \u203A Contas a receber \u203A Lista para ver o que est\xE1 em aberto.", route: "/financial/receivables/list" },
10476
+ { do: "O Financeiro mora aqui.", anchor: "nav./financial", route: "/financial" },
10477
+ { do: "Abra Contas a Receber neste menu \u2014 a lista mostra o que est\xE1 em aberto.", anchor: "modnav.receivables", route: "/financial/receivables/list" },
10478
+ { do: "Cadastre uma cobran\xE7a \xE0 m\xE3o s\xF3 para o que n\xE3o nasceu de uma venda.", anchor: "action.manual", route: "/financial/receivables/list" },
9043
10479
  {
9044
- do: "Receba pela pr\xF3pria linha, escolhendo a forma e \u2014 no cart\xE3o \u2014 o n\xFAmero de parcelas.",
10480
+ do: "O normal \xE9 receber pela pr\xF3pria linha, escolhendo a forma e \u2014 no cart\xE3o \u2014 o n\xFAmero de parcelas.",
9045
10481
  byAssistant: true,
9046
10482
  tool: "markPaymentReceived",
9047
10483
  why: "Eu consigo dar baixa direto quando voc\xEA me disser qual atendimento ou pedido foi pago."
9048
- },
9049
- { do: "S\xF3 lance uma cobran\xE7a \xE0 m\xE3o (Nova) para o que n\xE3o nasceu de uma venda." }
10484
+ }
9050
10485
  ],
9051
10486
  rules: [
9052
10487
  "Venda conclu\xEDda vira fatura por gatilho: NENHUMA tela escreve fatura por fora. Se voc\xEA est\xE1 cadastrando \xE0 m\xE3o o que foi vendido, alguma coisa est\xE1 errada antes daqui.",
@@ -9080,9 +10515,11 @@ var cashRegisters = {
9080
10515
  }
9081
10516
  ],
9082
10517
  steps: [
9083
- { do: "Financeiro \u203A Caixas: abra o caixa dizendo o valor que est\xE1 na gaveta.", route: "/financial/cash/registers" },
10518
+ { do: "O Financeiro mora aqui.", anchor: "nav./financial", route: "/financial" },
10519
+ { do: "Abra Caixas neste menu.", anchor: "modnav.cash-registers", route: "/financial/cash/registers" },
10520
+ { do: "Abra o caixa dizendo o valor que est\xE1 na gaveta agora.", anchor: "financial.cash-open", route: "/financial/cash/registers" },
9084
10521
  { do: "Durante o dia, o movimento entra sozinho pelo que for recebido." },
9085
- { do: "No fim, feche informando o que foi contado de verdade \u2014 inclusive quando n\xE3o bate." },
10522
+ { do: "No fim, feche informando o que foi CONTADO de verdade \u2014 inclusive quando n\xE3o bate.", anchor: "financial.cash-close", route: "/financial/cash/registers" },
9086
10523
  { do: "Quem tem al\xE7ada confere e aprova a sess\xE3o fechada.", why: "\xC9 a aprova\xE7\xE3o que congela o dia." }
9087
10524
  ],
9088
10525
  rules: [
@@ -9406,11 +10843,11 @@ function buildFinancialOnboarding() {
9406
10843
  function CashSessionHeaderControl({ config, provider, store: store6 }) {
9407
10844
  const sessions = useStore(store6, (s) => s.cashSessions);
9408
10845
  const fetchCashSessions = useStore(store6, (s) => s.fetchCashSessions);
9409
- const [open, setOpen] = React14__default.useState(false);
9410
- React14__default.useEffect(() => {
10846
+ const [open, setOpen] = React15__default.useState(false);
10847
+ React15__default.useEffect(() => {
9411
10848
  void fetchCashSessions?.();
9412
10849
  }, [fetchCashSessions]);
9413
- React14__default.useEffect(() => {
10850
+ React15__default.useEffect(() => {
9414
10851
  if (!open) return;
9415
10852
  const close = () => setOpen(false);
9416
10853
  window.addEventListener("hashchange", close);
@@ -9444,6 +10881,7 @@ function CashSessionHeaderControl({ config, provider, store: store6 }) {
9444
10881
 
9445
10882
  // src/locales/en.ts
9446
10883
  var en = {
10884
+ "financial.payment.pendingRetry": "Confirmation is pending. Do not start another payment. Retry the same details before leaving this screen.",
9447
10885
  "financial.cards.addExpense": "Add expense",
9448
10886
  "financial.cards.expenseOnCard": "Expense on this card",
9449
10887
  "financial.cards.allBillsValue": "All bills value",
@@ -9508,6 +10946,8 @@ var en = {
9508
10946
  "financial.invoice.bulkCancelTitle": "Cancel {{count}} invoice(s)?",
9509
10947
  "financial.invoice.cancelConfirm": "This action cannot be undone.",
9510
10948
  "financial.invoice.cancelDesc": "The invoice will be marked as cancelled. All pending installments will also be cancelled. This invoice will no longer count towards totals.",
10949
+ "financial.invoice.cancelReason": "Reason for cancellation",
10950
+ "financial.invoice.cancelReasonPlaceholder": "Explain why this invoice is being cancelled",
9511
10951
  "financial.invoice.cancelInvoice": "Cancel Invoice",
9512
10952
  "financial.invoice.cancelling": "Cancelling...",
9513
10953
  "financial.invoice.contact": "Contact",
@@ -10011,6 +11451,7 @@ var en = {
10011
11451
 
10012
11452
  // src/locales/pt-BR.ts
10013
11453
  var ptBR = {
11454
+ "financial.payment.pendingRetry": "A confirma\xE7\xE3o ainda est\xE1 pendente. N\xE3o abra outro pagamento. Tente novamente com os mesmos dados antes de sair desta tela.",
10014
11455
  "financial.cards.addExpense": "Adicionar despesa",
10015
11456
  "financial.cards.expenseOnCard": "Despesa neste cart\xE3o",
10016
11457
  "financial.cards.allBillsValue": "Total das faturas",
@@ -10075,6 +11516,8 @@ var ptBR = {
10075
11516
  "financial.invoice.bulkCancelTitle": "Cancelar {{count}} fatura(s)?",
10076
11517
  "financial.invoice.cancelConfirm": "Esta a\xE7\xE3o n\xE3o pode ser desfeita.",
10077
11518
  "financial.invoice.cancelDesc": "A fatura ser\xE1 marcada como cancelada. Todas as parcelas pendentes tamb\xE9m ser\xE3o canceladas. Esta fatura n\xE3o contar\xE1 mais nos totais.",
11519
+ "financial.invoice.cancelReason": "Motivo do cancelamento",
11520
+ "financial.invoice.cancelReasonPlaceholder": "Explique por que esta fatura est\xE1 sendo cancelada",
10078
11521
  "financial.invoice.cancelInvoice": "Cancelar Fatura",
10079
11522
  "financial.invoice.cancelling": "Cancelando...",
10080
11523
  "financial.invoice.contact": "Contato",
@@ -10604,14 +12047,14 @@ function CommissionRuleDialog({
10604
12047
  onClose,
10605
12048
  onSaved
10606
12049
  }) {
10607
- const [name, setName] = React14__default.useState(rule?.name ?? "");
10608
- const [description, setDescription] = React14__default.useState(rule?.description ?? "");
10609
- const [priority, setPriority] = React14__default.useState(String(rule?.priority ?? 1));
10610
- const [isActive, setIsActive] = React14__default.useState(rule?.isActive ?? true);
10611
- const [isDefault, setIsDefault] = React14__default.useState(rule?.isDefault ?? !hasDefault);
10612
- const [saving, setSaving] = React14__default.useState(false);
10613
- const [error, setError] = React14__default.useState(null);
10614
- const initial = React14__default.useMemo(() => {
12050
+ const [name, setName] = React15__default.useState(rule?.name ?? "");
12051
+ const [description, setDescription] = React15__default.useState(rule?.description ?? "");
12052
+ const [priority, setPriority] = React15__default.useState(String(rule?.priority ?? 1));
12053
+ const [isActive, setIsActive] = React15__default.useState(rule?.isActive ?? true);
12054
+ const [isDefault, setIsDefault] = React15__default.useState(rule?.isDefault ?? !hasDefault);
12055
+ const [saving, setSaving] = React15__default.useState(false);
12056
+ const [error, setError] = React15__default.useState(null);
12057
+ const initial = React15__default.useMemo(() => {
10615
12058
  const get = (t2, key = "ids") => rule?.criteria.find((c) => c.criteriaType === t2)?.criteriaValue?.[key] ?? [];
10616
12059
  return {
10617
12060
  services: get("service"),
@@ -10622,12 +12065,12 @@ function CommissionRuleDialog({
10622
12065
  ranges: get("time_range", "ranges")
10623
12066
  };
10624
12067
  }, [rule]);
10625
- const [services, setServices] = React14__default.useState(initial.services);
10626
- const [categories, setCategories] = React14__default.useState(initial.categories);
10627
- const [professionals, setProfessionals] = React14__default.useState(initial.professionals);
10628
- const [units, setUnits] = React14__default.useState(initial.units);
10629
- const [weekdays, setWeekdays] = React14__default.useState(initial.weekdays);
10630
- const [ranges, setRanges] = React14__default.useState(initial.ranges);
12068
+ const [services, setServices] = React15__default.useState(initial.services);
12069
+ const [categories, setCategories] = React15__default.useState(initial.categories);
12070
+ const [professionals, setProfessionals] = React15__default.useState(initial.professionals);
12071
+ const [units, setUnits] = React15__default.useState(initial.units);
12072
+ const [weekdays, setWeekdays] = React15__default.useState(initial.weekdays);
12073
+ const [ranges, setRanges] = React15__default.useState(initial.ranges);
10631
12074
  const criteriaCount = [services, categories, professionals, units].filter((a) => a.length > 0).length + (weekdays.length ? 1 : 0) + (ranges.length ? 1 : 0);
10632
12075
  async function handleSave() {
10633
12076
  if (!name.trim()) {
@@ -10870,11 +12313,11 @@ function blankFunction(order) {
10870
12313
  };
10871
12314
  }
10872
12315
  function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
10873
- const [functions, setFunctions] = React14__default.useState(
12316
+ const [functions, setFunctions] = React15__default.useState(
10874
12317
  rule.functions.length ? rule.functions : [blankFunction(0)]
10875
12318
  );
10876
- const [saving, setSaving] = React14__default.useState(false);
10877
- const [error, setError] = React14__default.useState(null);
12319
+ const [saving, setSaving] = React15__default.useState(false);
12320
+ const [error, setError] = React15__default.useState(null);
10878
12321
  function patch(i, changes) {
10879
12322
  setFunctions((fs) => fs.map((f, j) => j === i ? { ...f, ...changes } : f));
10880
12323
  }
@@ -11158,19 +12601,19 @@ function functionChip(fn) {
11158
12601
  return fn.valueType === "percentage" ? `${who}: ${fn.valueAmount}%` : `${who}: R$ ${fn.valueAmount.toFixed(2)}`;
11159
12602
  }
11160
12603
  function CommissionRulesView({ provider }) {
11161
- const [rules, setRules] = React14__default.useState(null);
11162
- const [catalogs, setCatalogs] = React14__default.useState({
12604
+ const [rules, setRules] = React15__default.useState(null);
12605
+ const [catalogs, setCatalogs] = React15__default.useState({
11163
12606
  services: [],
11164
12607
  categories: [],
11165
12608
  professionals: [],
11166
12609
  units: []
11167
12610
  });
11168
- const [error, setError] = React14__default.useState(null);
11169
- const [funnel, setFunnel] = React14__default.useState({});
11170
- const [editing, setEditing] = React14__default.useState(null);
11171
- const [functionsFor, setFunctionsFor] = React14__default.useState(null);
11172
- const [deleting, setDeleting] = React14__default.useState(null);
11173
- const load = React14__default.useCallback(async () => {
12611
+ const [error, setError] = React15__default.useState(null);
12612
+ const [funnel, setFunnel] = React15__default.useState({});
12613
+ const [editing, setEditing] = React15__default.useState(null);
12614
+ const [functionsFor, setFunctionsFor] = React15__default.useState(null);
12615
+ const [deleting, setDeleting] = React15__default.useState(null);
12616
+ const load = React15__default.useCallback(async () => {
11174
12617
  try {
11175
12618
  setError(null);
11176
12619
  const [r, c] = await Promise.all([
@@ -11184,17 +12627,17 @@ function CommissionRulesView({ provider }) {
11184
12627
  setRules([]);
11185
12628
  }
11186
12629
  }, [provider]);
11187
- React14__default.useEffect(() => {
12630
+ React15__default.useEffect(() => {
11188
12631
  void load();
11189
12632
  }, [load]);
11190
12633
  const funnelActive = Object.values(funnel).some(Boolean);
11191
- const visible = React14__default.useMemo(() => {
12634
+ const visible = React15__default.useMemo(() => {
11192
12635
  if (!rules) return [];
11193
12636
  const ranked = rankRules(rules);
11194
12637
  if (!funnelActive) return ranked;
11195
12638
  return ranked.filter((r) => r.isDefault || ruleMatches(r, funnel, catalogs));
11196
12639
  }, [rules, funnel, funnelActive, catalogs]);
11197
- const winner = React14__default.useMemo(() => {
12640
+ const winner = React15__default.useMemo(() => {
11198
12641
  if (!funnelActive) return null;
11199
12642
  return visible.find((r) => r.isActive && !r.isDefault) ?? visible.find((r) => r.isActive && r.isDefault) ?? null;
11200
12643
  }, [visible, funnelActive]);
@@ -21443,8 +22886,8 @@ function ReceivePaymentModal({
21443
22886
  onClose,
21444
22887
  onPaid
21445
22888
  }) {
21446
- const provider = React14__default.useMemo(() => createSafeFinancialProvider(), []);
21447
- const store6 = React14__default.useMemo(() => createFinancialStore(provider), [provider]);
22889
+ const provider = React15__default.useMemo(() => createSafeFinancialProvider(), []);
22890
+ const store6 = React15__default.useMemo(() => createFinancialStore(provider), [provider]);
21448
22891
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
21449
22892
  PaymentModal,
21450
22893
  {
@@ -21462,7 +22905,7 @@ function createFinancialSettingsPlugin(options) {
21462
22905
  ...financialRegistries.filter((r) => RECEIVING_REGISTRIES.includes(r.id)),
21463
22906
  ...options?.extraRegistries ?? []
21464
22907
  ];
21465
- const SettingsTab = () => React14__default.createElement(PluginSettingsPanel, {
22908
+ const SettingsTab = () => React15__default.createElement(PluginSettingsPanel, {
21466
22909
  title: "Pagamentos",
21467
22910
  registries,
21468
22911
  hostPluginId: "financial",
@@ -21505,19 +22948,19 @@ function ClientAccountPanel({
21505
22948
  currency = BRL
21506
22949
  }) {
21507
22950
  const provider = useFinancialProvider();
21508
- const [account, setAccount] = React14__default.useState(null);
21509
- const [busy, setBusy] = React14__default.useState(false);
21510
- const [paying, setPaying] = React14__default.useState(null);
22951
+ const [account, setAccount] = React15__default.useState(null);
22952
+ const [busy, setBusy] = React15__default.useState(false);
22953
+ const [paying, setPaying] = React15__default.useState(null);
21511
22954
  async function openPayment(invoiceId) {
21512
22955
  const res = await provider.getMovements({ invoiceId, pageSize: 200 });
21513
22956
  const open = (res.data ?? []).find((m) => m.movementKind === "bill" && m.amount - (m.paidAmount ?? 0) > 5e-3);
21514
22957
  if (open) setPaying(open);
21515
22958
  }
21516
- const load = React14__default.useCallback(async () => {
22959
+ const load = React15__default.useCallback(async () => {
21517
22960
  if (!personId) return;
21518
22961
  setAccount(await provider.getClientAccount(personId));
21519
22962
  }, [personId, provider]);
21520
- React14__default.useEffect(() => {
22963
+ React15__default.useEffect(() => {
21521
22964
  void load();
21522
22965
  }, [load]);
21523
22966
  const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
@@ -21619,8 +23062,8 @@ function ClientAccountModal({
21619
23062
  onGenerate,
21620
23063
  onClose
21621
23064
  }) {
21622
- const provider = React14__default.useMemo(() => createSafeFinancialProvider(), []);
21623
- const store6 = React14__default.useMemo(() => createFinancialStore(provider), [provider]);
23065
+ const provider = React15__default.useMemo(() => createSafeFinancialProvider(), []);
23066
+ const store6 = React15__default.useMemo(() => createFinancialStore(provider), [provider]);
21624
23067
  return /* @__PURE__ */ jsx(RightRailPage, { title: "Conta do cliente", open: true, onClose, width: "max-w-md", children: /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
21625
23068
  ClientAccountPanel,
21626
23069
  {
@@ -21729,9 +23172,10 @@ function createFinancialPlugin(options) {
21729
23172
  const store6 = createFinancialStore(provider);
21730
23173
  const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store: store6 });
21731
23174
  const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
21732
- const PersonStatementBound = (props) => React14__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
23175
+ const PersonStatementBound = (props) => React15__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
21733
23176
  PersonStatementBound.displayName = "FinancialPersonStatement";
21734
- const PageComponent = () => React14__default.createElement(FinancialPage, { config, provider, store: store6, registries });
23177
+ const PageComponent = () => React15__default.createElement(FinancialPage, { config, provider, store: store6, registries });
23178
+ const AgentMount = createFinancialAgentMount({ config, provider });
21735
23179
  return {
21736
23180
  id: "financial",
21737
23181
  defaultAgentRole: "finance",
@@ -21788,7 +23232,7 @@ function createFinancialPlugin(options) {
21788
23232
  // tem — lá é um ícone no cabeçalho, de qualquer tela.
21789
23233
  headerControls: config.modules.cashRegisters ? [{
21790
23234
  id: "financial.cash-session",
21791
- component: (() => React14__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
23235
+ component: (() => React15__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
21792
23236
  order: 90,
21793
23237
  permission: { feature: "financial", action: "read" }
21794
23238
  }] : [],
@@ -21890,6 +23334,17 @@ function createFinancialPlugin(options) {
21890
23334
  component: PersonStatementBound,
21891
23335
  order: 0,
21892
23336
  permission: { feature: "financial", action: "read" }
23337
+ },
23338
+ {
23339
+ // Headless. Publica ao assistente a porta de lançar uma conta a pagar
23340
+ // em QUALQUER rota — ver payable-agent.tsx. `shell.topbar.end` é a
23341
+ // única zona global do shell que de fato é renderizada e que fica FORA
23342
+ // do switch de rota (mesma leitura do pill do plugin-scribe).
23343
+ id: "financial-agent-mount",
23344
+ zone: "shell.topbar.end",
23345
+ component: AgentMount,
23346
+ order: -20,
23347
+ permission: { feature: "financial", action: "read" }
21893
23348
  }
21894
23349
  ],
21895
23350
  dashboardWidgets,
@@ -21980,9 +23435,9 @@ function createFinancialPlugin(options) {
21980
23435
  // lado, e a corrida de "Plugins" não é partida ao meio.
21981
23436
  group: "Financeiro",
21982
23437
  component: (() => {
21983
- const FinancialSettingsTab = () => React14__default.createElement(PluginSettingsPanel, {
23438
+ const FinancialSettingsTab = () => React15__default.createElement(PluginSettingsPanel, {
21984
23439
  title: "Financial Settings",
21985
- generalSettings: React14__default.createElement(FinancialGeneralSettings),
23440
+ generalSettings: React15__default.createElement(FinancialGeneralSettings),
21986
23441
  registries,
21987
23442
  hostPluginId: "financial",
21988
23443
  routeBase: "/settings/financial"
@@ -22007,7 +23462,7 @@ function createFinancialPlugin(options) {
22007
23462
  icon: "HandCoins",
22008
23463
  group: "Financeiro",
22009
23464
  component: (() => {
22010
- const CommissionRulesTab = () => React14__default.createElement(CommissionRulesView, { provider });
23465
+ const CommissionRulesTab = () => React15__default.createElement(CommissionRulesView, { provider });
22011
23466
  CommissionRulesTab.displayName = "CommissionRulesTab";
22012
23467
  return CommissionRulesTab;
22013
23468
  })(),