@fayz-ai/plugin-financial 0.18.3 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/components/BankPicker.d.ts +25 -0
  2. package/dist/components/BankPicker.d.ts.map +1 -0
  3. package/dist/components/CnabRemittanceModal.d.ts.map +1 -1
  4. package/dist/components/InstallmentRow.d.ts +42 -0
  5. package/dist/components/InstallmentRow.d.ts.map +1 -0
  6. package/dist/components/InstallmentSource.d.ts.map +1 -1
  7. package/dist/components/InstallmentStatusBadge.d.ts +6 -0
  8. package/dist/components/InstallmentStatusBadge.d.ts.map +1 -0
  9. package/dist/components/InvoiceAttachmentsModal.d.ts +12 -0
  10. package/dist/components/InvoiceAttachmentsModal.d.ts.map +1 -0
  11. package/dist/components/PaymentFields.d.ts.map +1 -1
  12. package/dist/components/PaymentIcons.d.ts +12 -0
  13. package/dist/components/PaymentIcons.d.ts.map +1 -0
  14. package/dist/components/PaymentReceiptModal.d.ts.map +1 -1
  15. package/dist/components/ReceivePaymentModal.d.ts +14 -3
  16. package/dist/components/ReceivePaymentModal.d.ts.map +1 -1
  17. package/dist/components/ReconcileFlow.d.ts.map +1 -1
  18. package/dist/config.d.ts +12 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/data/registries.d.ts.map +1 -1
  21. package/dist/data/supabase.d.ts.map +1 -1
  22. package/dist/data/tables.d.ts +7 -0
  23. package/dist/data/tables.d.ts.map +1 -1
  24. package/dist/data/types.d.ts +28 -1
  25. package/dist/data/types.d.ts.map +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2385 -851
  28. package/dist/index.js.map +1 -1
  29. package/dist/lib/br-banks.d.ts +13 -0
  30. package/dist/lib/br-banks.d.ts.map +1 -0
  31. package/dist/lib/cnab240.d.ts +106 -0
  32. package/dist/lib/cnab240.d.ts.map +1 -0
  33. package/dist/lib/invoice-list.d.ts +15 -0
  34. package/dist/lib/invoice-list.d.ts.map +1 -1
  35. package/dist/lib/settlements.d.ts +6 -0
  36. package/dist/lib/settlements.d.ts.map +1 -1
  37. package/dist/locales/en.d.ts.map +1 -1
  38. package/dist/locales/pt-BR.d.ts.map +1 -1
  39. package/dist/migrations/index.d.ts +2 -0
  40. package/dist/migrations/index.d.ts.map +1 -1
  41. package/dist/types.d.ts +94 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/views/ClientAccountPanel.d.ts.map +1 -1
  44. package/dist/views/FinancialPage.d.ts.map +1 -1
  45. package/dist/views/GapScreens.d.ts +0 -2
  46. package/dist/views/GapScreens.d.ts.map +1 -1
  47. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  48. package/dist/views/InvoiceFormView.d.ts +18 -1
  49. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  50. package/dist/views/InvoiceListView.d.ts +3 -2
  51. package/dist/views/InvoiceListView.d.ts.map +1 -1
  52. package/dist/views/PayablesView.d.ts.map +1 -1
  53. package/dist/views/ReceivablesView.d.ts.map +1 -1
  54. package/dist/views/cardChip.d.ts +1 -1
  55. package/dist/views/cardChip.d.ts.map +1 -1
  56. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,15 +1,94 @@
1
- import * as React23 from 'react';
2
- import React23__default, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
3
- import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, getSupabaseClientOptional, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode, useDataChanged, countByTenant, tl, resolveDataProvider, getActiveTenantId, emitDataChanged, exportCSV, useActiveUnitId } from '@fayz-ai/core';
4
- import { ModalTask, toast, Button, useInModalTaskHost, SubpageHeader, DatePicker, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, Card, CardContent, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MultiSelect, PageTransition, ModulePage, Badge, TooltipProvider, ConfirmDialog, CurrencyInput, Input, PageHeaderActions, Hint, ModalFooter, PageTabs, Tooltip, TooltipTrigger, TooltipContent, Checkbox, SearchSelect, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, SegmentedControl, RequiredFilter, FilterMenuRow, ModalFlow, useSaveBar, Switch, tourAnchor, WizardSteps, KpiCard, ChartWidget, CardHeader, CardTitle, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
1
+ import * as React25 from 'react';
2
+ import React25__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
3
+ import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, getSupabaseClientOptional, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode, useActiveUnitId, useDataChanged, countByTenant, tl, resolveDataProvider, getActiveTenantId, emitDataChanged, exportCSV } from '@fayz-ai/core';
4
+ import { INLINE_LINK_TONE, SearchSelect, ModalTask, toast, Button, useInModalTaskHost, useSaveBar, tourAnchor, DatePicker, SubpageHeader, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, Card, CardContent, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MultiSelect, PageTransition, ModulePage, Badge, TooltipProvider, ConfirmDialog, CurrencyInput, Input, PageHeaderActions, Hint, ModalFooter, PageTabs, Tooltip, TooltipTrigger, TooltipContent, Checkbox, Popover, PopoverTrigger, PopoverContent, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, SegmentedControl, RequiredFilter, FilterMenuRow, ModalFlow, Switch, WizardSteps, KpiCard, ChartWidget, CardHeader, CardTitle, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, usePermissionOptional, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, createArchetypeLookup, useChatStore, adminNavigateTo, revealPage, useAuth, GetStarted, resolveEntityRoute, PermissionGate, useUnitFilter, resolveFayzAgentConnection, getFayzAgentClient, useLookupCreate, OrderFormSkeleton, OrderFormView, OrderFormBlock, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, useLimitGuard, shrinkForVision, toInlineFile, invalidateLimit, askAI, requestChatUpload } from '@fayz-ai/admin';
7
- import { CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Receipt, Gift, Briefcase, Package, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Camera, Undo2, ChevronRight, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, Calendar, Building2, User, Unlink, ChevronDown, Eye, Inbox, Landmark, Clock, ClipboardCheck, BookOpen, Info, FolderOpen, Folder, RotateCcw, Calculator, Square, Play, ChevronUp, Printer, CalendarClock, BadgeCheck, Target, Settings, ArrowRight, Users, RefreshCw, Download, Sparkles, Link2, FileSearch, CheckCheck, Pause, MoreVertical, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, ArrowLeft, ArrowUpRight, ArrowDownRight, EyeOff } from 'lucide-react';
6
+ import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useUnitFilter, useLimitGuard, usePermissionOptional, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, useChatStore, adminNavigateTo, revealPage, useAuth, GetStarted, resolveEntityRoute, PermissionGate, createOneShotAsk, askAI, requestChatUpload, toInlineFile, shrinkForVision } from '@fayz-ai/admin';
7
+ import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Receipt, Gift, Briefcase, Package, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, Ticket, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Undo2, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, ChevronRight, Building2, ChevronDown, Image, UploadCloud, Calendar, User, Unlink, Eye, Inbox, Clock, ClipboardCheck, BookOpen, Info, FolderOpen, Folder, RotateCcw, ArrowLeft, ChevronLeft, Calculator, Square, Play, ChevronUp, Printer, CalendarClock, BadgeCheck, Target, Settings, ArrowRight, Users, RefreshCw, Download, Camera, Sparkles, Link2, FileSearch, CheckCheck, Pause, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, FileDown, ArrowUpRight, ArrowDownRight, EyeOff } from 'lucide-react';
8
8
  import { createPortal } from 'react-dom';
9
9
  import { createStore as createStore$1 } from 'zustand/vanilla';
10
10
  import { useStore } from 'zustand';
11
11
 
12
12
  // src/index.ts
13
+
14
+ // src/config.ts
15
+ var DEFAULT_LABELS = {
16
+ pageTitle: "Financial",
17
+ pageSubtitle: "Financial overview and cash flow management",
18
+ summary: "Summary",
19
+ payables: "Accounts Payable",
20
+ payablesNew: "New",
21
+ payablesList: "List",
22
+ payablesRecurring: "Recurring Expenses",
23
+ receivables: "Accounts Receivable",
24
+ receivablesNew: "New",
25
+ receivablesList: "List",
26
+ cashRegisters: "Cash Registers",
27
+ statements: "Statements",
28
+ commissions: "Commissions",
29
+ commissionsOverview: "Overview",
30
+ commissionsRules: "Rules",
31
+ cards: "Cards",
32
+ goals: "Metas",
33
+ giftCards: "Gift Cards",
34
+ cardsOverview: "Overview",
35
+ cardsReconciliation: "Reconciliation",
36
+ notasFiscais: "Fiscal Invoices"
37
+ };
38
+ var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
39
+ var DEFAULT_ITEM_TYPES = [
40
+ { value: "service", label: "Service", icon: "Briefcase" },
41
+ // O pacote é o terceiro catálogo da casa que atende: um bloco de sessões
42
+ // vendido de uma vez. Só aparece onde há `entityLookups.package` instalado —
43
+ // e, dentro dessas, só onde o catálogo tem linhas (useStockedItemTypes).
44
+ { value: "package", label: "Package", icon: "PackageCheck" },
45
+ { value: "product", label: "Product", icon: "Package" },
46
+ { value: "other", label: "Other", icon: "MoreHorizontal" }
47
+ ];
48
+ function resolveConfig(options) {
49
+ return {
50
+ modules: {
51
+ payables: options?.modules?.payables !== false,
52
+ receivables: options?.modules?.receivables !== false,
53
+ cashRegisters: options?.modules?.cashRegisters !== false,
54
+ statements: options?.modules?.statements !== false,
55
+ // Era opt-in porque a comissão era um cadastro que nada lia: o item de
56
+ // menu só podia levar a uma tela vazia. Desde 009 existe apuração e desde
57
+ // 012 existe borderô, e a tela mostra o que eles produziram — então o
58
+ // motivo do opt-in acabou junto com a tela vazia.
59
+ commissions: options?.modules?.commissions !== false,
60
+ cards: options?.modules?.cards !== false,
61
+ goals: options?.modules?.goals !== false,
62
+ // Ligado como as outras: quando a loja não está instalada a própria tela
63
+ // diz isso, então o item de menu nunca leva a um beco.
64
+ giftCards: options?.modules?.giftCards !== false,
65
+ // Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
66
+ reconciliation: options?.modules?.reconciliation === true,
67
+ // Opt-in: only shown when an app explicitly enables fiscal-invoice tracking.
68
+ // LIGADA como as outras (`!== false`), e não opt-in. Estava em
69
+ // `=== true`: quem não passasse a opção nunca via a tela, e o V1 tem
70
+ // Notas Fiscais no menu do Financeiro desde sempre. A tela se explica
71
+ // sozinha quando o provider não expõe notas, então ligá-la não deixa
72
+ // ninguém diante de um beco.
73
+ notasFiscais: options?.modules?.notasFiscais !== false
74
+ },
75
+ labels: { ...DEFAULT_LABELS, ...options?.labels },
76
+ currency: { ...DEFAULT_CURRENCY, ...options?.currency },
77
+ moduleNav: options?.moduleNav,
78
+ itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
79
+ enableServiceExecution: options?.enableServiceExecution ?? false,
80
+ contactEntity: {
81
+ archetypeKind: options?.contactEntity?.archetypeKind ?? "client",
82
+ label: options?.contactEntity?.label ?? "Contact"
83
+ },
84
+ locations: options?.locations ?? [],
85
+ entityLookups: options?.entityLookups ?? {},
86
+ contactLookup: options?.contactLookup,
87
+ fiscalProvider: options?.fiscalProvider,
88
+ onBookingClick: options?.onBookingClick,
89
+ navFeatureMap: { ...options?.navFeatureMap }
90
+ };
91
+ }
13
92
  function PettyCashView() {
14
93
  return /* @__PURE__ */ jsx(
15
94
  ComingSoon,
@@ -26,22 +105,6 @@ function PettyCashView() {
26
105
  }
27
106
  );
28
107
  }
29
- function DigitizeExpenseView() {
30
- return /* @__PURE__ */ jsx(
31
- ComingSoon,
32
- {
33
- title: "Digitalizar Despesa",
34
- summary: "Fotografar o boleto ou a nota e deixar a m\xE1quina ler valor, vencimento e fornecedor \u2014 em vez de digitar os quatro campos de novo.",
35
- v1Path: "Financeiro \u2192 Contas a Pagar \u2192 Digitalizar Despesa",
36
- measured: "1.436 anexos de conta no V1, 55 da Artorius. A Laissa anexa comprovante 25 vezes por m\xEAs \u2014 mas digita os campos \xE0 m\xE3o todas elas.",
37
- missing: [
38
- "Ler o comprovante e PR\xC9-PREENCHER o formul\xE1rio, deixando o usu\xE1rio confirmar \u2014 n\xE3o gravar sozinho",
39
- "Guardar o arquivo junto do t\xEDtulo, que \xE9 o que a contabilidade pede depois",
40
- "A Entrada de Notas j\xE1 faz isto para XML de NF-e; aqui \xE9 o mesmo trabalho para o que chega em papel"
41
- ]
42
- }
43
- );
44
- }
45
108
  function ImportPayrollView() {
46
109
  return /* @__PURE__ */ jsx(
47
110
  ComingSoon,
@@ -388,7 +451,14 @@ var T = {
388
451
  marginByMonth: "v_financial_margin_by_month",
389
452
  recurringBills: "plg_financial_recurring_bills",
390
453
  recurringOccurrences: "plg_financial_recurring_bill_occurrences",
391
- discountsGiven: "v_financial_discounts_given"
454
+ discountsGiven: "v_financial_discounts_given",
455
+ /** O arquivo de remessa que já saiu para o banco, e o pagamento dentro dele (080). */
456
+ cnabBatches: "plg_financial_cnab_batches",
457
+ cnabItems: "plg_financial_cnab_items",
458
+ /** Para onde o dinheiro SAI — a conta de quem a casa paga, nunca conta do razão (149). */
459
+ payeeAccounts: "payee_accounts",
460
+ /** A empresa pagadora da unidade: é o CNPJ dela que vai no cabeçalho do CNAB. */
461
+ unitCompanies: "v_unit_company_legal_entities"
392
462
  };
393
463
 
394
464
  // src/lib/accounts.ts
@@ -838,9 +908,9 @@ function startReceiptCapture(labels) {
838
908
  });
839
909
  }
840
910
  function ReceiptAgentSurface({ onOpenDraft }) {
841
- const live = React23.useRef(onOpenDraft);
911
+ const live = React25.useRef(onOpenDraft);
842
912
  live.current = onOpenDraft;
843
- const contribution = React23.useMemo(() => ({
913
+ const contribution = React25.useMemo(() => ({
844
914
  id: "financial.payables.receipt",
845
915
  pluginId: "financial",
846
916
  role: "finance",
@@ -865,83 +935,685 @@ function createIdempotencyKey(scope) {
865
935
  }
866
936
  return `${scope}:${suffix}`;
867
937
  }
938
+
939
+ // src/lib/cnab240.ts
940
+ var onlyDigits = (s) => (s || "").replace(/\D/g, "");
941
+ var padL = (v, n, c = "0") => String(v ?? "").slice(0, n).padStart(n, c);
942
+ var padR = (v, n, c = " ") => String(v ?? "").slice(0, n).padEnd(n, c);
943
+ var ascii = (s) => (s || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9 .,&'/()-]/g, " ").toUpperCase();
944
+ var num = (v, n) => padL(onlyDigits(String(v ?? "0")), n);
945
+ var alpha = (v, n) => padR(ascii(String(v ?? "")), n);
946
+ var money = (v, n = 15) => padL(Math.round((Number(v) || 0) * 100).toString(), n);
947
+ var dateYMD = (s) => {
948
+ if (!s) return "00000000";
949
+ const d = /* @__PURE__ */ new Date(s + (s.length === 10 ? "T00:00:00" : ""));
950
+ if (Number.isNaN(d.getTime())) return "00000000";
951
+ return `${padL(d.getDate(), 2)}${padL(d.getMonth() + 1, 2)}${padL(d.getFullYear(), 4)}`;
952
+ };
953
+ var stampDate = (d) => `${padL(d.getDate(), 2)}${padL(d.getMonth() + 1, 2)}${padL(d.getFullYear(), 4)}`;
954
+ var stampTime = (d) => `${padL(d.getHours(), 2)}${padL(d.getMinutes(), 2)}${padL(d.getSeconds(), 2)}`;
955
+ var finalize = (s) => s.length < 240 ? s.padEnd(240, " ") : s.slice(0, 240);
956
+ var inscriptionType = (document2) => onlyDigits(document2).length > 11 ? "2" : "1";
957
+ var serviceTypeFor = (form) => form === "tributo" ? "22" : "20";
958
+ var settlementFor = (form, payeeBank, ourBank) => {
959
+ if (form === "pix") return "45";
960
+ if (form === "boleto") return "30";
961
+ if (form === "tributo") return "11";
962
+ if (form === "ted") {
963
+ return payeeBank && ourBank && onlyDigits(payeeBank) === onlyDigits(ourBank) ? "01" : "41";
964
+ }
965
+ return "01";
966
+ };
967
+ var clearingHouseFor = (form, payeeBank, ourBank) => {
968
+ if (form !== "ted") return "000";
969
+ return payeeBank && ourBank && onlyDigits(payeeBank) === onlyDigits(ourBank) ? "000" : "018";
970
+ };
971
+ var pixInitiationCodeFor = (pixKeyType, pixKey) => {
972
+ const type = (pixKeyType || "").toLowerCase();
973
+ const key = String(pixKey || "").trim();
974
+ const digits2 = onlyDigits(key);
975
+ switch (type) {
976
+ case "phone":
977
+ case "telefone":
978
+ return "01";
979
+ case "email":
980
+ return "02";
981
+ case "cpf":
982
+ case "cnpj":
983
+ return "03";
984
+ case "random":
985
+ case "aleatoria":
986
+ case "evp":
987
+ return "04";
988
+ case "bank":
989
+ case "dados_bancarios":
990
+ return "05";
991
+ default:
992
+ if (digits2.length === 11 || digits2.length === 14) return "03";
993
+ if (key.includes("@")) return "02";
994
+ if (key.startsWith("+") || digits2.length >= 10) return "01";
995
+ if (key) return "04";
996
+ return "05";
997
+ }
998
+ };
999
+ var initiationFieldFor = (form, payee) => form === "pix" ? `${pixInitiationCodeFor(payee.pixKeyType, payee.pixKey)} ` : "03 ";
1000
+ var pixKeyValueFor = (pixKey, initiationCode) => {
1001
+ const key = String(pixKey || "").trim();
1002
+ if (initiationCode === "01" || initiationCode === "03") return onlyDigits(key);
1003
+ if (initiationCode === "02") return key.toLowerCase();
1004
+ return key;
1005
+ };
1006
+ var pixAlpha = (v, n) => padR(
1007
+ String(v ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\x20-\x7E]/g, " "),
1008
+ n
1009
+ );
1010
+ function linhaDigitavelToBarcode(input) {
1011
+ const d = onlyDigits(input || "");
1012
+ if (d.length === 44) return d;
1013
+ if (d.length === 47) {
1014
+ const c1 = d.substring(0, 9);
1015
+ const c2 = d.substring(10, 20);
1016
+ const c3 = d.substring(21, 31);
1017
+ const dv = d.substring(32, 33);
1018
+ const vv = d.substring(33, 47);
1019
+ return c1.substring(0, 4) + dv + vv + c1.substring(4, 9) + c2 + c3;
1020
+ }
1021
+ if (d.length === 48) {
1022
+ return d.substring(0, 11) + d.substring(12, 23) + d.substring(24, 35) + d.substring(36, 47);
1023
+ }
1024
+ return d;
1025
+ }
1026
+ function headerArquivo(ctx2, sequence, at) {
1027
+ return finalize(
1028
+ num(ctx2.bank.code, 3) + "00000" + padR("", 9) + inscriptionType(ctx2.company.document) + num(ctx2.company.document, 14) + padR(ctx2.bank.agreement || "", 20) + num(ctx2.bank.agency, 5) + alpha(ctx2.bank.agencyDigit || "", 1) + num(ctx2.bank.account, 12) + alpha(ctx2.bank.accountDigit || "", 1) + alpha("", 1) + alpha(ctx2.company.name, 30) + alpha(ctx2.bank.name, 30) + padR("", 10) + "1" + stampDate(at) + stampTime(at) + num(sequence, 6) + "10300000" + padR("", 20) + padR("", 20) + padR("", 29)
1029
+ );
1030
+ }
1031
+ function trailerArquivo(lotes, records) {
1032
+ return finalize("99999999" + padR("", 9) + num(lotes, 6) + num(records, 6) + num(0, 6) + padR("", 205));
1033
+ }
1034
+ function headerLote(ctx2, lote, form, settlement) {
1035
+ const agreement = form === "pix" ? "" : ctx2.bank.agreement || "";
1036
+ return finalize(
1037
+ num(ctx2.bank.code, 3) + num(lote, 4) + "1C" + serviceTypeFor(form) + settlement + "046 " + inscriptionType(ctx2.company.document) + num(ctx2.company.document, 14) + padR(agreement, 20) + num(ctx2.bank.agency, 5) + alpha(ctx2.bank.agencyDigit || "", 1) + num(ctx2.bank.account, 12) + alpha(ctx2.bank.accountDigit || "", 1) + alpha("", 1) + alpha(ctx2.company.name, 30) + alpha("", 40) + alpha("", 30) + num("", 5) + alpha("", 15) + alpha("", 20) + num("", 8) + alpha("", 2) + " " + padR("", 6) + padR("", 10)
1038
+ );
1039
+ }
1040
+ function trailerLote(lote, records, total) {
1041
+ return finalize(
1042
+ "000" + num(lote, 4) + "5" + padR("", 9) + num(records, 6) + money(total, 18) + num(0, 18) + padR("", 6) + padR("", 165) + padR("", 10)
1043
+ );
1044
+ }
1045
+ function segmentoA(ctx2, lote, index, form, item, docNumber) {
1046
+ const payee = item.payee;
1047
+ const byPixKey = form === "pix" && !!payee.pixKey;
1048
+ const favBank = byPixKey ? "0" : payee.bankCode || "0";
1049
+ const favAgency = byPixKey ? "0" : payee.agency || "0";
1050
+ const favAgencyDigit = byPixKey ? "0" : payee.agencyDigit || "0";
1051
+ const favAccount = byPixKey ? "0" : payee.account || "0";
1052
+ const favAccountDigit = byPixKey ? "0" : payee.accountDigit || "0";
1053
+ return finalize(
1054
+ num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "A000" + clearingHouseFor(form, payee.bankCode, ctx2.bank.code) + num(favBank, 3) + num(favAgency, 5) + num(favAgencyDigit, 1) + num(favAccount, 12) + num(favAccountDigit, 1) + alpha("", 1) + alpha(payee.name, 30) + alpha(`DOC${docNumber}`, 20) + dateYMD(item.paymentDate) + "BRL" + num(0, 15) + money(item.amount, 15) + alpha("", 20) + num(0, 8) + money(0, 15) + num(byPixKey ? "0" : docNumber, 14) + num("0", 8) + num(byPixKey ? "0" : "01", 2) + padR("", 29) + padR("", 10)
1055
+ );
1056
+ }
1057
+ function segmentoB(ctx2, lote, index, form, item) {
1058
+ const payee = item.payee;
1059
+ const byPixKey = form === "pix" && !!payee.pixKey;
1060
+ const initiation = pixInitiationCodeFor(payee.pixKeyType, payee.pixKey);
1061
+ const identifier = byPixKey && initiation === "03" ? payee.pixKey : payee.document;
1062
+ const header = num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "B" + initiationFieldFor(form, payee);
1063
+ if (byPixKey) {
1064
+ return finalize(
1065
+ header + inscriptionType(identifier || "") + num(identifier, 14) + pixAlpha("", 35) + pixAlpha("", 60) + pixAlpha(initiation === "03" ? "" : pixKeyValueFor(payee.pixKey, initiation), 99) + padR("", 6) + num(0, 8)
1066
+ );
1067
+ }
1068
+ return finalize(
1069
+ header + inscriptionType(payee.document) + num(payee.document, 14) + alpha(payee.street || "", 30) + num(payee.number || "0", 5) + alpha(payee.complement || "", 15) + alpha(payee.district || "", 15) + alpha(payee.city || "", 20) + num(payee.postalCode || "0", 8) + alpha(payee.state || "", 2) + dateYMD(item.paymentDate) + money(item.amount, 15) + money(0, 15) + money(0, 15) + money(0, 15) + money(0, 15) + padR("", 99)
1070
+ );
1071
+ }
1072
+ function segmentoJ(ctx2, lote, index, item, docNumber) {
1073
+ return finalize(
1074
+ num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "J000" + padL(linhaDigitavelToBarcode(item.barcode || ""), 44) + alpha(item.payee.name, 30) + dateYMD(item.paymentDate) + money(item.amount, 15) + money(0, 15) + money(0, 15) + dateYMD(item.paymentDate) + money(item.amount, 15) + num("0", 15) + num(docNumber, 20) + num("0", 20) + padR("", 8) + padR("", 10)
1075
+ );
1076
+ }
1077
+ function segmentoJ52(ctx2, lote, index, item, docNumber) {
1078
+ const payerDoc = onlyDigits(ctx2.company.document);
1079
+ const payeeDoc = onlyDigits(item.payee.document);
1080
+ return finalize(
1081
+ num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "J 0052" + inscriptionType(payerDoc) + num(payerDoc, 15) + alpha(ctx2.company.name, 40) + inscriptionType(payeeDoc) + num(payeeDoc, 15) + alpha(item.payee.name, 40) + padR("", 56) + num(docNumber, 53)
1082
+ );
1083
+ }
1084
+ function segmentoO(ctx2, lote, index, item, docNumber) {
1085
+ return finalize(
1086
+ num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "O000" + padL(linhaDigitavelToBarcode(item.barcode || ""), 44) + alpha(item.payee.name, 30) + dateYMD(item.paymentDate) + dateYMD(item.paymentDate) + money(item.amount, 15) + alpha(`DOC${docNumber}`, 20) + padR("", 6) + padR("", 33)
1087
+ );
1088
+ }
1089
+ function groupItems(items, ourBankCode) {
1090
+ const groups = /* @__PURE__ */ new Map();
1091
+ for (const item of items) {
1092
+ let settlement = settlementFor(item.paymentForm, item.payee.bankCode, ourBankCode);
1093
+ if (item.paymentForm === "boleto") {
1094
+ const barcode = linhaDigitavelToBarcode(item.barcode || "");
1095
+ settlement = barcode.substring(0, 3) === padL(onlyDigits(ourBankCode), 3) ? "30" : "31";
1096
+ }
1097
+ const key = `${item.paymentForm}:${settlement}`;
1098
+ const group = groups.get(key) ?? { form: item.paymentForm, settlement, list: [] };
1099
+ group.list.push(item);
1100
+ groups.set(key, group);
1101
+ }
1102
+ return [...groups.values()];
1103
+ }
1104
+ var docNumberFor = (sequence, index) => sequence * 1e4 + index + 1;
1105
+ function generateCnab240(input) {
1106
+ if (!input.items.length) throw new Error("Uma remessa sem itens n\xE3o \xE9 um arquivo.");
1107
+ const at = input.generatedAt ?? /* @__PURE__ */ new Date();
1108
+ const ctx2 = { company: input.company, bank: input.bank };
1109
+ const lines = [headerArquivo(ctx2, input.sequence, at)];
1110
+ const groups = groupItems(input.items, input.bank.code);
1111
+ const batches = [];
1112
+ let lote = 0;
1113
+ let totalRecords = 1;
1114
+ let line = 0;
1115
+ for (const group of groups) {
1116
+ lote += 1;
1117
+ lines.push(headerLote(ctx2, lote, group.form, group.settlement));
1118
+ let loteRecords = 1;
1119
+ let loteTotal = 0;
1120
+ let index = 0;
1121
+ for (const item of group.list) {
1122
+ const docNumber = docNumberFor(input.sequence, line);
1123
+ line += 1;
1124
+ loteTotal += Number(item.amount) || 0;
1125
+ if (group.form === "boleto") {
1126
+ lines.push(segmentoJ(ctx2, lote, index += 1, item, docNumber));
1127
+ lines.push(segmentoJ52(ctx2, lote, index += 1, item, docNumber));
1128
+ loteRecords += 2;
1129
+ } else if (group.form === "tributo") {
1130
+ lines.push(segmentoO(ctx2, lote, index += 1, item, docNumber));
1131
+ loteRecords += 1;
1132
+ } else {
1133
+ lines.push(segmentoA(ctx2, lote, index += 1, group.form, item, docNumber));
1134
+ lines.push(segmentoB(ctx2, lote, index += 1, group.form, item));
1135
+ loteRecords += 2;
1136
+ }
1137
+ }
1138
+ loteRecords += 1;
1139
+ lines.push(trailerLote(lote, loteRecords, loteTotal));
1140
+ totalRecords += loteRecords;
1141
+ batches.push({ form: group.form, count: group.list.length, total: loteTotal });
1142
+ }
1143
+ totalRecords += 1;
1144
+ lines.push(trailerArquivo(lote, totalRecords));
1145
+ const totalAmount = input.items.reduce((sum, item) => sum + (Number(item.amount) || 0), 0);
1146
+ const bankLabel = String(parseInt(onlyDigits(input.bank.code) || "0", 10));
1147
+ return {
1148
+ fileName: `CI240_${bankLabel}_${String(input.sequence).padStart(4, "0")}.REM`,
1149
+ content: lines.join("\r\n") + "\r\n",
1150
+ layout: `febraban-${padL(onlyDigits(input.bank.code), 3)}`,
1151
+ totalAmount,
1152
+ itemsCount: input.items.length,
1153
+ batches
1154
+ };
1155
+ }
1156
+ function cnabPayeeGaps(items) {
1157
+ const gaps = [];
1158
+ items.forEach((item, index) => {
1159
+ const payee = item.payee;
1160
+ const name = payee.name || `Favorecido #${index + 1}`;
1161
+ const push = (field, message) => gaps.push({ reference: item.reference, name, field, message });
1162
+ if (!onlyDigits(payee.document)) push("document", "CPF/CNPJ");
1163
+ if (item.paymentForm === "pix") {
1164
+ if (!payee.pixKey?.trim()) push("pixKey", "chave PIX");
1165
+ else if (payee.pixKey.trim().toUpperCase().startsWith("00020")) {
1166
+ push("pixKeyTypeEmv", "o campo tem um PIX Copia-e-Cola (EMV), que o CNAB 240 n\xE3o aceita \u2014 informe a chave real");
1167
+ }
1168
+ }
1169
+ if (item.paymentForm === "ted" && (!payee.bankCode?.trim() || !payee.account?.trim())) {
1170
+ push("bankDetails", "banco, ag\xEAncia e conta");
1171
+ }
1172
+ if (item.paymentForm === "boleto" || item.paymentForm === "tributo") {
1173
+ const barcode = linhaDigitavelToBarcode(item.barcode || "");
1174
+ if (barcode.length !== 44) push("barcode", "c\xF3digo de barras ou linha digit\xE1vel");
1175
+ }
1176
+ });
1177
+ return gaps;
1178
+ }
1179
+ function suggestPaymentForm(barcode) {
1180
+ const raw = (barcode || "").trim();
1181
+ const digits2 = onlyDigits(raw);
1182
+ if (/^\d+$/.test(raw) && digits2.length >= 44 && digits2.length <= 48) return "boleto";
1183
+ return "pix";
1184
+ }
1185
+ function inferPixKeyType(key) {
1186
+ const raw = key.trim();
1187
+ const digits2 = onlyDigits(raw);
1188
+ if (raw.toUpperCase().startsWith("00020")) return "aleatoria";
1189
+ if (digits2.length === 11 && !raw.includes("@")) return "cpf";
1190
+ if (digits2.length === 14) return "cnpj";
1191
+ if (raw.includes("@")) return "email";
1192
+ if (digits2.length >= 10 && digits2.length <= 13) return "telefone";
1193
+ return "aleatoria";
1194
+ }
1195
+ function splitAccountDigit(value, digit) {
1196
+ if (digit.trim()) return [value.trim(), digit.trim()];
1197
+ const match = value.trim().match(/^(\d+)\s*[-/.]\s*([0-9xX])$/);
1198
+ return match ? [match[1], match[2].toUpperCase()] : [value.trim(), ""];
1199
+ }
1200
+
1201
+ // src/lib/br-banks.ts
1202
+ var LOGO_BASE = "https://cdn.jsdelivr.net/gh/Tgentil/Bancos-em-SVG@fe1d43f0cf379135bd01c987bc147a04fdf48c6d";
1203
+ var logo = (path) => `${LOGO_BASE}/${path.split("/").map(encodeURIComponent).join("/")}`;
1204
+ var BR_BANKS = [
1205
+ { code: "001", name: "Banco do Brasil", aliases: ["bb"], logo: logo("Banco do Brasil S.A/banco-do-brasil-sem-fundo.svg") },
1206
+ { code: "341", name: "Ita\xFA", aliases: ["itau", "ita\xFA unibanco"], logo: logo("Ita\xFA Unibanco S.A/itau.svg") },
1207
+ { code: "237", name: "Bradesco", logo: logo("Bradesco S.A/bradesco.svg") },
1208
+ { code: "104", name: "Caixa Econ\xF4mica Federal", aliases: ["caixa", "cef"], logo: logo("Caixa Econ\xF4mica Federal/caixa-economica-federal-1.svg") },
1209
+ { code: "033", name: "Santander", logo: logo("Banco Santander Brasil S.A/banco-santander-logo.svg") },
1210
+ { code: "260", name: "Nubank", aliases: ["nu pagamentos"], logo: logo("Nu Pagamentos S.A/nubank-logo-2021.svg") },
1211
+ { code: "077", name: "Inter", aliases: ["banco inter"], logo: logo("Banco Inter S.A/inter.svg") },
1212
+ { code: "336", name: "C6 Bank", aliases: ["c6"], logo: logo("Banco C6 S.A/c6 bank.svg") },
1213
+ { code: "208", name: "BTG Pactual", aliases: ["btg"], logo: logo("Banco BTG Pacutal/btg-pactual.svg") },
1214
+ { code: "756", name: "Sicoob", logo: logo("Sicoob/sicoob-vector-logo.svg") },
1215
+ { code: "748", name: "Sicredi", logo: logo("Sicredi/logo-svg2.svg") },
1216
+ { code: "290", name: "PagBank", aliases: ["pagseguro"], logo: logo("PagSeguro Internet S.A/logo-pagbank.svg") },
1217
+ { code: "197", name: "Stone", logo: logo("Stone Pagamentos S.A/stone.svg") },
1218
+ { code: "323", name: "Mercado Pago", logo: logo("Mercado Pago/mercado-pago.svg") },
1219
+ { code: "542", name: "InfinitePay", aliases: ["cloudwalk"], logo: logo("InfinitePay/InfitePay.svg") },
1220
+ { code: "403", name: "Cora", logo: logo("Cora Sociedade Credito Direto S.A/icone-cora-rosa-pequeno.svg") },
1221
+ { code: "380", name: "PicPay", logo: logo("PicPay/Logo-PicPay.svg") },
1222
+ { code: "364", name: "Ef\xED", aliases: ["gerencianet", "efi bank"], logo: logo("Ef\xED - Gerencianet/logo-efi-bank-laranja.svg") },
1223
+ { code: "461", name: "Asaas", logo: logo("Asaas IP S.A/header-logo-azul.svg") },
1224
+ { code: "422", name: "Safra", logo: logo("Banco Safra S.A/logo-safra.svg") },
1225
+ { code: "212", name: "Banco Original", aliases: ["original"], logo: logo("Banco Original S.A/banco-original-logo-verde.svg") },
1226
+ { code: "655", name: "Banco BV", aliases: ["votorantim", "bv"], logo: logo("Banco Votorantim/banco-bv-logo.svg") },
1227
+ { code: "041", name: "Banrisul", logo: logo("Banrisul/banrisul-logo-2023.svg") },
1228
+ { code: "070", name: "BRB", aliases: ["banco de bras\xEDlia"], logo: logo("BRB - Banco de Brasilia/brb-logo.svg") },
1229
+ { code: "136", name: "Unicred", logo: logo("Unicred/verde.svg") },
1230
+ { code: "133", name: "Cresol", logo: logo("Cresol/Icone-original.svg") },
1231
+ { code: "085", name: "Ailos", logo: logo("Ailos/logo-ailos.svg") },
1232
+ { code: "004", name: "Banco do Nordeste", aliases: ["bnb"], logo: logo("Banco do Nordeste do Brasil S.A/Logo_BNB.svg") },
1233
+ { code: "021", name: "Banestes", logo: logo("Banco do Estado do Espirito Santo/banestes.svg") },
1234
+ { code: "037", name: "Banpar\xE1", logo: logo("Banco do Estado do Para/Logo_do_Banpar\xE1.svg") },
1235
+ { code: "047", name: "Banese", logo: logo("Banco do Estado do Sergipe/logo banese.svg") },
1236
+ { code: "389", name: "Mercantil do Brasil", aliases: ["mercantil"], logo: logo("Banco Mercantil do Brasil S.A/banco-mercantil-novo-azul.svg") },
1237
+ { code: "318", name: "BMG", logo: logo("Banco BMG/banco-bmg-logo.svg") },
1238
+ { code: "707", name: "Daycoval", logo: logo("Banco Daycoval/logo-Daycoval.svg") },
1239
+ { code: "246", name: "ABC Brasil", logo: logo("ABC Brasil/logoabc.svg") },
1240
+ { code: "218", name: "BS2", logo: logo("Banco BS2 S.A/Banco_BS2.svg") },
1241
+ { code: "102", name: "XP", aliases: ["xp investimentos"], logo: logo("XP Investimentos/xp-investimentos-logo.svg") },
1242
+ { code: "536", name: "Neon", logo: logo("Neon/header-logo-neon.svg") }
1243
+ ];
1244
+ var norm = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
1245
+ var digits = (s) => s.replace(/\D/g, "");
1246
+ function findBrBank(code, name) {
1247
+ const c = digits(code ?? "");
1248
+ if (c) {
1249
+ const byCode = BR_BANKS.find((b) => Number(b.code) === Number(c));
1250
+ if (byCode) return byCode;
1251
+ }
1252
+ const n = norm(name ?? "");
1253
+ if (!n) return void 0;
1254
+ return BR_BANKS.find((b) => norm(b.name) === n || b.aliases?.some((a) => norm(a) === n));
1255
+ }
1256
+ function searchBrBanks(query) {
1257
+ const q = norm(query);
1258
+ if (!q) return BR_BANKS;
1259
+ const qd = digits(q);
1260
+ return BR_BANKS.filter(
1261
+ (b) => qd && qd === q && (b.code.startsWith(qd) || b.code.replace(/^0+/, "").startsWith(qd)) || norm(b.name).includes(q) || b.aliases?.some((a) => norm(a).includes(q))
1262
+ );
1263
+ }
1264
+ function BankLogo({ bank, size: size2 = 20 }) {
1265
+ const [broken, setBroken] = useState(false);
1266
+ if (!bank?.logo || broken) {
1267
+ return /* @__PURE__ */ jsx(Landmark, { className: "text-muted-foreground", style: { width: size2 * 0.8, height: size2 * 0.8 }, "aria-hidden": true });
1268
+ }
1269
+ return /* @__PURE__ */ jsx(
1270
+ "img",
1271
+ {
1272
+ src: bank.logo,
1273
+ alt: "",
1274
+ width: size2,
1275
+ height: size2,
1276
+ loading: "lazy",
1277
+ onError: () => setBroken(true),
1278
+ className: "shrink-0 object-contain",
1279
+ style: { width: size2, height: size2 }
1280
+ }
1281
+ );
1282
+ }
1283
+ var fromCatalog = (entry) => entry.compe ? { code: entry.compe, name: entry.name, logo: entry.logoUrl } : null;
1284
+ function BankPicker({ code, name, search, onChange }) {
1285
+ const known = findBrBank(code, name);
1286
+ const toOption = (b) => ({
1287
+ id: b.code,
1288
+ label: b.name,
1289
+ subtitle: b.code,
1290
+ icon: /* @__PURE__ */ jsx(BankLogo, { bank: b }),
1291
+ data: b
1292
+ });
1293
+ const onSearch = async (query) => {
1294
+ const local = searchBrBanks(query);
1295
+ if (!search) return local.map(toOption);
1296
+ try {
1297
+ const remote = (await search(query)).map(fromCatalog).filter(Boolean);
1298
+ if (!remote.length) return local.map(toOption);
1299
+ const merged = remote.map((bank) => findBrBank(bank.code) ?? bank);
1300
+ const seen = new Set(merged.map((bank) => bank.code));
1301
+ return [...merged, ...local.filter((bank) => !seen.has(bank.code))].map(toOption);
1302
+ } catch {
1303
+ return local.map(toOption);
1304
+ }
1305
+ };
1306
+ return /* @__PURE__ */ jsx(
1307
+ SearchSelect,
1308
+ {
1309
+ value: name ? known?.code ?? `name:${name}` : "",
1310
+ displayValue: name ?? "",
1311
+ leading: name ? /* @__PURE__ */ jsx(BankLogo, { bank: known, size: 18 }) : void 0,
1312
+ placeholder: "Busque pelo nome ou n\xFAmero",
1313
+ minChars: 0,
1314
+ loadOnOpen: true,
1315
+ onSearch,
1316
+ onChange: (_id, option) => {
1317
+ const b = option?.data;
1318
+ onChange(b ? { code: b.code, name: b.name } : { code: "", name: "" });
1319
+ },
1320
+ allowCreate: true,
1321
+ createLabel: "Usar",
1322
+ clearLabel: "Limpar",
1323
+ onCreate: (q) => onChange({ code: code ?? "", name: q.trim() }),
1324
+ noResultsLabel: "Nenhum banco com esse nome",
1325
+ debounce: 0
1326
+ }
1327
+ );
1328
+ }
868
1329
  var STEPS = [
869
1330
  { id: "account", label: "Conta banc\xE1ria" },
870
1331
  { id: "payments", label: "Pagamentos" },
871
1332
  { id: "beneficiaries", label: "Favorecidos" },
872
1333
  { id: "review", label: "Revis\xE3o" }
873
1334
  ];
1335
+ var FORM_LABEL = {
1336
+ pix: "PIX",
1337
+ ted: "TED/DOC",
1338
+ boleto: "Boleto",
1339
+ tributo: "Tributo"
1340
+ };
1341
+ var PIX_KEY_TYPES = [
1342
+ { value: "cpf", label: "CPF" },
1343
+ { value: "cnpj", label: "CNPJ" },
1344
+ { value: "email", label: "E-mail" },
1345
+ { value: "telefone", label: "Telefone" },
1346
+ { value: "aleatoria", label: "Aleat\xF3ria" }
1347
+ ];
1348
+ var todayISO = () => {
1349
+ const now2 = /* @__PURE__ */ new Date();
1350
+ return `${now2.getFullYear()}-${String(now2.getMonth() + 1).padStart(2, "0")}-${String(now2.getDate()).padStart(2, "0")}`;
1351
+ };
874
1352
  function textMetadata(account, key) {
875
1353
  const value = account.metadata?.[key];
876
1354
  return typeof value === "string" ? value : "";
877
1355
  }
878
1356
  function draftFromAccount(account) {
1357
+ const [agencyNumber, agencyDigit] = splitAccountDigit(account.agencyNumber || "", textMetadata(account, "agencyDigit"));
1358
+ const [accountNumber, accountDigit] = splitAccountDigit(account.accountNumber || "", textMetadata(account, "accountDigit"));
879
1359
  return {
880
- bankName: account.bankName ?? "",
881
- bankCode: textMetadata(account, "bankCode"),
882
- agencyNumber: account.agencyNumber ?? "",
883
- agencyDigit: textMetadata(account, "agencyDigit"),
884
- accountNumber: account.accountNumber ?? "",
885
- accountDigit: textMetadata(account, "accountDigit"),
1360
+ bankName: account.bankName || "",
1361
+ bankCode: account.bankCode || textMetadata(account, "bankCode"),
1362
+ agencyNumber,
1363
+ agencyDigit,
1364
+ accountNumber,
1365
+ accountDigit,
886
1366
  agreementCode: textMetadata(account, "cnabAgreementCode")
887
1367
  };
888
1368
  }
889
- function missingFields(draft) {
1369
+ function missingFields(draft, companyDocument) {
890
1370
  return [
891
1371
  !draft.bankCode.trim() && "c\xF3digo do banco",
892
1372
  !draft.agencyNumber.trim() && "ag\xEAncia",
893
- !draft.accountNumber.trim() && "conta"
1373
+ !draft.accountNumber.trim() && "conta",
1374
+ !companyDocument.replace(/\D/g, "") && "CNPJ da empresa"
894
1375
  ].filter(Boolean);
895
1376
  }
1377
+ var emptyPayee = (invoice) => ({
1378
+ contactId: invoice.contactId ?? "",
1379
+ name: invoice.contactName || invoice.itemsSummary || "Sem favorecido",
1380
+ document: ""
1381
+ });
1382
+ function lineFromInvoice(invoice, payee) {
1383
+ const base = payee ?? emptyPayee(invoice);
1384
+ const barcode = typeof invoice.metadata?.barcode === "string" ? invoice.metadata.barcode : "";
1385
+ const paymentForm = suggestPaymentForm(barcode);
1386
+ const resolved = { ...base, name: base.name || invoice.contactName || "Sem favorecido" };
1387
+ if (!resolved.pixKey?.trim() && resolved.document.replace(/\D/g, "")) {
1388
+ resolved.pixKey = resolved.document;
1389
+ resolved.pixKeyType = inferPixKeyType(resolved.document);
1390
+ } else if (resolved.pixKey?.trim() && !resolved.pixKeyType?.trim()) {
1391
+ resolved.pixKeyType = inferPixKeyType(resolved.pixKey);
1392
+ }
1393
+ const today5 = todayISO();
1394
+ return {
1395
+ invoice,
1396
+ paymentForm,
1397
+ // Paying on a date already past is a rejected line; the due date only wins
1398
+ // while it is still ahead.
1399
+ paymentDate: invoice.dueDate && invoice.dueDate >= today5 ? invoice.dueDate : today5,
1400
+ barcode: paymentForm === "boleto" ? barcode : "",
1401
+ payee: resolved
1402
+ };
1403
+ }
1404
+ var openAmount = (invoice) => Math.max(0, invoice.totalAmount - invoice.paidAmount);
896
1405
  function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
897
1406
  const { currency } = useFinancialConfig();
898
- const [step, setStep] = React23__default.useState(0);
899
- const [bankAccountId, setBankAccountId] = React23__default.useState("");
900
- const [draft, setDraft] = React23__default.useState(null);
901
- const [saving, setSaving] = React23__default.useState(false);
902
- const [saveError, setSaveError] = React23__default.useState("");
1407
+ const [step, setStep] = React25__default.useState(0);
1408
+ const [bankAccountId, setBankAccountId] = React25__default.useState("");
1409
+ const [draft, setDraft] = React25__default.useState(null);
1410
+ const [context, setContext] = React25__default.useState(null);
1411
+ const [companyName, setCompanyName] = React25__default.useState("");
1412
+ const [companyDocument, setCompanyDocument] = React25__default.useState("");
1413
+ const [lines, setLines] = React25__default.useState([]);
1414
+ const [companyUnsaved, setCompanyUnsaved] = React25__default.useState(false);
1415
+ const [loadingContext, setLoadingContext] = React25__default.useState(false);
1416
+ const [busy, setBusy] = React25__default.useState(false);
1417
+ const [error, setError] = React25__default.useState("");
903
1418
  const eligibleAccounts2 = bankAccounts.filter((account2) => account2.accountType === "bank" && account2.isActive);
904
- const total = invoices.reduce((sum, invoice) => sum + Math.max(0, invoice.totalAmount - invoice.paidAmount), 0);
1419
+ const total = invoices.reduce((sum, invoice) => sum + openAmount(invoice), 0);
905
1420
  const account = eligibleAccounts2.find((candidate) => candidate.id === bankAccountId);
906
- const missingBankData = draft ? missingFields(draft) : [];
907
- const selectAccount = (id) => {
1421
+ const missingBankData = draft ? missingFields(draft, companyDocument) : [];
1422
+ const items = React25__default.useMemo(() => lines.map((line) => ({
1423
+ reference: line.invoice.id,
1424
+ amount: openAmount(line.invoice),
1425
+ paymentDate: line.paymentDate,
1426
+ paymentForm: line.paymentForm,
1427
+ barcode: line.barcode,
1428
+ payee: {
1429
+ name: line.payee.name,
1430
+ document: line.payee.document,
1431
+ bankCode: line.payee.bankCode,
1432
+ agency: line.payee.agencyNumber,
1433
+ agencyDigit: line.payee.agencyDigit,
1434
+ account: line.payee.accountNumber,
1435
+ accountDigit: line.payee.accountDigit,
1436
+ pixKey: line.payee.pixKey,
1437
+ pixKeyType: line.payee.pixKeyType,
1438
+ street: line.payee.street,
1439
+ number: line.payee.number,
1440
+ complement: line.payee.complement,
1441
+ district: line.payee.district,
1442
+ city: line.payee.city,
1443
+ state: line.payee.state,
1444
+ postalCode: line.payee.postalCode
1445
+ }
1446
+ })), [lines]);
1447
+ const gaps = React25__default.useMemo(() => cnabPayeeGaps(items), [items]);
1448
+ const batches = React25__default.useMemo(() => {
1449
+ const grouped = /* @__PURE__ */ new Map();
1450
+ for (const line of lines) {
1451
+ const current = grouped.get(line.paymentForm) ?? { count: 0, total: 0 };
1452
+ current.count += 1;
1453
+ current.total += openAmount(line.invoice);
1454
+ grouped.set(line.paymentForm, current);
1455
+ }
1456
+ return [...grouped.entries()];
1457
+ }, [lines]);
1458
+ const selectAccount = async (id) => {
908
1459
  const selected = eligibleAccounts2.find((candidate) => candidate.id === id);
909
1460
  setBankAccountId(id);
910
1461
  setDraft(selected ? draftFromAccount(selected) : null);
911
- setSaveError("");
1462
+ setError("");
1463
+ if (!selected || !provider.getCnabContext) {
1464
+ setLines(invoices.map((invoice) => lineFromInvoice(invoice, void 0)));
1465
+ return;
1466
+ }
1467
+ setLoadingContext(true);
1468
+ try {
1469
+ const loaded = await provider.getCnabContext({
1470
+ contactIds: invoices.map((invoice) => invoice.contactId ?? "").filter(Boolean),
1471
+ bankAccountId: id,
1472
+ unitId: selected.unitId
1473
+ });
1474
+ setContext(loaded);
1475
+ setCompanyName(loaded.company.name);
1476
+ setCompanyDocument(loaded.company.document);
1477
+ const byContact = new Map(loaded.payees.map((payee) => [payee.contactId, payee]));
1478
+ setLines(invoices.map((invoice) => lineFromInvoice(invoice, byContact.get(invoice.contactId ?? ""))));
1479
+ } catch (cause) {
1480
+ setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel carregar os favorecidos.");
1481
+ setLines(invoices.map((invoice) => lineFromInvoice(invoice, void 0)));
1482
+ } finally {
1483
+ setLoadingContext(false);
1484
+ }
912
1485
  };
913
1486
  const updateDraft = (key, value) => {
914
1487
  setDraft((current) => current ? { ...current, [key]: value } : current);
915
- setSaveError("");
1488
+ setError("");
1489
+ };
1490
+ const updateLine = (index, patch) => setLines((current) => current.map((line, i) => i === index ? { ...line, ...patch } : line));
1491
+ const updatePayee = (index, patch) => setLines((current) => current.map((line, i) => i === index ? { ...line, payee: { ...line.payee, ...patch } } : line));
1492
+ const saveBankDraft = async () => {
1493
+ if (!account || !draft) return;
1494
+ await provider.updateBankAccount(account.id, {
1495
+ bankCode: draft.bankCode.trim(),
1496
+ bankName: draft.bankName.trim() || void 0,
1497
+ agencyNumber: draft.agencyNumber.trim(),
1498
+ accountNumber: draft.accountNumber.trim(),
1499
+ metadata: {
1500
+ ...account.metadata,
1501
+ agencyDigit: draft.agencyDigit.trim(),
1502
+ accountDigit: draft.accountDigit.trim(),
1503
+ cnabAgreementCode: draft.agreementCode.trim()
1504
+ }
1505
+ });
1506
+ };
1507
+ const saveCompany = async () => {
1508
+ if (!provider.saveCompany) return;
1509
+ const loaded = context?.company;
1510
+ const unchanged = loaded && loaded.document.replace(/\D/g, "") === companyDocument.replace(/\D/g, "") && loaded.name === companyName;
1511
+ if (unchanged) return;
1512
+ try {
1513
+ const saved = await provider.saveCompany({
1514
+ id: loaded?.id,
1515
+ unitId: account?.unitId,
1516
+ name: companyName,
1517
+ document: companyDocument
1518
+ });
1519
+ setContext((current) => current ? { ...current, company: { ...current.company, id: saved.id, name: companyName, document: companyDocument } } : current);
1520
+ } catch {
1521
+ setCompanyUnsaved(true);
1522
+ }
916
1523
  };
917
1524
  const advance = async () => {
918
- if (step !== 0) {
919
- setStep((value) => Math.min(value + 1, 3));
1525
+ setError("");
1526
+ if (step === 0) {
1527
+ if (!account || !draft || missingBankData.length > 0) return;
1528
+ setBusy(true);
1529
+ try {
1530
+ await saveBankDraft();
1531
+ await saveCompany();
1532
+ setStep(1);
1533
+ } catch (cause) {
1534
+ setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel salvar os dados banc\xE1rios.");
1535
+ } finally {
1536
+ setBusy(false);
1537
+ }
920
1538
  return;
921
1539
  }
922
- if (!account || !draft || missingBankData.length > 0) return;
923
- setSaving(true);
924
- setSaveError("");
1540
+ if (step === 2 && gaps.length > 0) return;
1541
+ setStep((value) => Math.min(value + 1, 3));
1542
+ };
1543
+ const generate = async () => {
1544
+ if (!account || !draft) return;
1545
+ setBusy(true);
1546
+ setError("");
925
1547
  try {
926
- await provider.updateBankAccount(account.id, {
927
- bankName: draft.bankName.trim() || void 0,
928
- agencyNumber: draft.agencyNumber.trim(),
929
- accountNumber: draft.accountNumber.trim(),
930
- metadata: {
931
- ...account.metadata,
932
- bankCode: draft.bankCode.trim(),
933
- agencyDigit: draft.agencyDigit.trim(),
934
- accountDigit: draft.accountDigit.trim(),
935
- cnabAgreementCode: draft.agreementCode.trim()
936
- }
1548
+ const sequence = context?.nextSequence ?? 1;
1549
+ const file = generateCnab240({
1550
+ sequence,
1551
+ company: { name: companyName, document: companyDocument },
1552
+ bank: {
1553
+ code: draft.bankCode,
1554
+ name: draft.bankName,
1555
+ agency: draft.agencyNumber,
1556
+ agencyDigit: draft.agencyDigit,
1557
+ account: draft.accountNumber,
1558
+ accountDigit: draft.accountDigit,
1559
+ agreement: draft.agreementCode
1560
+ },
1561
+ items
937
1562
  });
938
- setStep(1);
939
- } catch (error) {
940
- setSaveError(error instanceof Error ? error.message : "N\xE3o foi poss\xEDvel salvar os dados banc\xE1rios.");
1563
+ if (provider.saveCnabPayees) {
1564
+ const seen = /* @__PURE__ */ new Set();
1565
+ const payload = lines.filter((line) => {
1566
+ if (!line.payee.contactId || seen.has(line.payee.contactId)) return false;
1567
+ seen.add(line.payee.contactId);
1568
+ return true;
1569
+ }).map((line) => ({
1570
+ contactId: line.payee.contactId,
1571
+ payeeAccountId: line.payee.payeeAccountId,
1572
+ document: line.payee.document,
1573
+ bankCode: line.payee.bankCode,
1574
+ bankName: line.payee.bankName,
1575
+ agencyNumber: line.payee.agencyNumber,
1576
+ agencyDigit: line.payee.agencyDigit,
1577
+ accountNumber: line.payee.accountNumber,
1578
+ accountDigit: line.payee.accountDigit,
1579
+ accountType: line.payee.accountType,
1580
+ pixKey: line.payee.pixKey,
1581
+ pixKeyType: line.payee.pixKeyType
1582
+ }));
1583
+ if (payload.length) await provider.saveCnabPayees(payload);
1584
+ }
1585
+ if (provider.recordCnabBatch) {
1586
+ await provider.recordCnabBatch({
1587
+ bankAccountId: account.id,
1588
+ unitId: account.unitId,
1589
+ layout: file.layout,
1590
+ sequenceNumber: sequence,
1591
+ totalAmount: file.totalAmount,
1592
+ fileName: file.fileName,
1593
+ fileContent: file.content,
1594
+ items: lines.map((line) => ({
1595
+ movementId: line.invoice.id,
1596
+ paymentForm: line.paymentForm,
1597
+ paymentDate: line.paymentDate,
1598
+ amount: openAmount(line.invoice)
1599
+ }))
1600
+ });
1601
+ }
1602
+ const blob = new Blob([file.content], { type: "text/plain;charset=us-ascii" });
1603
+ const url = URL.createObjectURL(blob);
1604
+ const anchor = document.createElement("a");
1605
+ anchor.href = url;
1606
+ anchor.download = file.fileName;
1607
+ anchor.click();
1608
+ URL.revokeObjectURL(url);
1609
+ onClose();
1610
+ } catch (cause) {
1611
+ setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel gerar o arquivo.");
941
1612
  } finally {
942
- setSaving(false);
1613
+ setBusy(false);
943
1614
  }
944
1615
  };
1616
+ const canAdvance = step === 0 ? !!account && !!draft && missingBankData.length === 0 && !loadingContext : step === 2 ? gaps.length === 0 : true;
945
1617
  return createPortal(
946
1618
  /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
947
1619
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-[2px]", onClick: onClose }),
@@ -983,82 +1655,203 @@ function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
983
1655
  id: candidate.id,
984
1656
  label: candidate.name,
985
1657
  subtitle: [candidate.bankName, candidate.agencyNumber && `Ag ${candidate.agencyNumber}`, candidate.accountNumber && `Cc ${candidate.accountNumber}`].filter(Boolean).join(" \xB7 "),
986
- icon: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4" })
1658
+ icon: /* @__PURE__ */ jsx(BankLogo, { bank: findBrBank(candidate.bankCode || textMetadata(candidate, "bankCode"), candidate.bankName) })
987
1659
  }));
988
1660
  },
989
- onChange: (id) => selectAccount(id)
1661
+ onChange: (id) => void selectAccount(id)
990
1662
  }
991
1663
  ),
992
- draft ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border bg-muted/20 p-4", children: [
993
- /* @__PURE__ */ jsxs("div", { children: [
994
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados da conta para a remessa" }),
995
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Preencha uma vez; os pr\xF3ximos arquivos reutilizam esta configura\xE7\xE3o." })
1664
+ draft ? /* @__PURE__ */ jsxs(Fragment, { children: [
1665
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border bg-muted/20 p-4", children: [
1666
+ /* @__PURE__ */ jsxs("div", { children: [
1667
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Empresa pagadora" }),
1668
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: companyUnsaved ? "Vale para este arquivo: seu perfil n\xE3o pode gravar o cadastro da empresa. Pe\xE7a a um respons\xE1vel para cadastr\xE1-la." : context?.company.id ? "\xC9 este CNPJ que vai no cabe\xE7alho do arquivo." : "Esta unidade ainda n\xE3o tem empresa cadastrada. O que voc\xEA informar aqui vira o cadastro dela, e a pr\xF3xima remessa j\xE1 abre preenchida." })
1669
+ ] }),
1670
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
1671
+ /* @__PURE__ */ jsx(Field, { label: "Raz\xE3o social / nome", value: companyName, onChange: setCompanyName }),
1672
+ /* @__PURE__ */ jsx(Field, { label: "CNPJ", required: true, value: companyDocument, placeholder: "00.000.000/0000-00", onChange: setCompanyDocument })
1673
+ ] })
996
1674
  ] }),
997
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
998
- /* @__PURE__ */ jsx(Field, { label: "C\xF3digo do banco", required: true, value: draft.bankCode, placeholder: "Ex.: 341", onChange: (value) => updateDraft("bankCode", value) }),
999
- /* @__PURE__ */ jsx(Field, { label: "Nome do banco", value: draft.bankName, placeholder: "Ex.: Ita\xFA", onChange: (value) => updateDraft("bankName", value) }),
1000
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
1001
- /* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: draft.agencyNumber, onChange: (value) => updateDraft("agencyNumber", value), className: "col-span-2" }),
1002
- /* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.agencyDigit, onChange: (value) => updateDraft("agencyDigit", value) })
1675
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border bg-muted/20 p-4", children: [
1676
+ /* @__PURE__ */ jsxs("div", { children: [
1677
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados da conta para a remessa" }),
1678
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Preencha uma vez; os pr\xF3ximos arquivos reutilizam esta configura\xE7\xE3o." })
1003
1679
  ] }),
1004
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
1005
- /* @__PURE__ */ jsx(Field, { label: "Conta", required: true, value: draft.accountNumber, onChange: (value) => updateDraft("accountNumber", value), className: "col-span-2" }),
1006
- /* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.accountDigit, onChange: (value) => updateDraft("accountDigit", value) })
1680
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
1681
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 sm:col-span-2", children: [
1682
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
1683
+ "Banco",
1684
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
1685
+ ] }),
1686
+ /* @__PURE__ */ jsx(
1687
+ BankPicker,
1688
+ {
1689
+ code: draft.bankCode,
1690
+ name: draft.bankName,
1691
+ search: provider.searchBanks ? (query) => provider.searchBanks(query) : void 0,
1692
+ onChange: ({ code, name }) => setDraft((current) => current ? { ...current, bankCode: code || current.bankCode, bankName: name } : current)
1693
+ }
1694
+ ),
1695
+ draft.bankCode ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1696
+ "C\xF3digo ",
1697
+ draft.bankCode
1698
+ ] }) : null
1699
+ ] }),
1700
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
1701
+ /* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: draft.agencyNumber, onChange: (value) => updateDraft("agencyNumber", value), className: "col-span-2" }),
1702
+ /* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.agencyDigit, onChange: (value) => updateDraft("agencyDigit", value) })
1703
+ ] }),
1704
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
1705
+ /* @__PURE__ */ jsx(Field, { label: "Conta", required: true, value: draft.accountNumber, onChange: (value) => updateDraft("accountNumber", value), className: "col-span-2" }),
1706
+ /* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.accountDigit, onChange: (value) => updateDraft("accountDigit", value) })
1707
+ ] }),
1708
+ /* @__PURE__ */ jsx(Field, { label: "Conv\xEAnio / c\xF3digo da empresa", value: draft.agreementCode, placeholder: "Opcional para alguns bancos", onChange: (value) => updateDraft("agreementCode", value), className: "sm:col-span-2" })
1007
1709
  ] }),
1008
- /* @__PURE__ */ jsx(Field, { label: "Conv\xEAnio / c\xF3digo da empresa", value: draft.agreementCode, placeholder: "Opcional para alguns bancos", onChange: (value) => updateDraft("agreementCode", value), className: "sm:col-span-2" })
1009
- ] }),
1010
- missingBankData.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex gap-2 rounded-lg border border-warning/30 bg-warning/10 p-3 text-sm", children: [
1011
- /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-warning" }),
1012
- /* @__PURE__ */ jsxs("p", { children: [
1710
+ missingBankData.length > 0 ? /* @__PURE__ */ jsxs(Warning, { children: [
1013
1711
  "Complete ",
1014
1712
  missingBankData.join(", "),
1015
1713
  " para continuar."
1016
- ] })
1017
- ] }) : null,
1018
- saveError ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: saveError }) : null
1714
+ ] }) : null
1715
+ ] })
1019
1716
  ] }) : null,
1020
1717
  eligibleAccounts2.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Cadastre uma conta banc\xE1ria ativa para iniciar uma remessa." }) : null
1021
1718
  ] }) : null,
1022
1719
  step === 1 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1023
1720
  /* @__PURE__ */ jsxs("div", { children: [
1024
1721
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Pagamentos selecionados" }),
1025
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Confira os t\xEDtulos e defina PIX, TED, boleto ou tributo na pr\xF3xima etapa." })
1722
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Defina a forma de pagamento e a data de pagamento de cada parcela." })
1026
1723
  ] }),
1027
- /* @__PURE__ */ jsx("div", { className: "divide-y rounded-lg border", children: invoices.map((invoice) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 px-3 py-2.5 text-sm", children: [
1028
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1029
- /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: invoice.contactName || invoice.itemsSummary || "Sem descri\xE7\xE3o" }),
1724
+ lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-3 rounded-lg border p-3 sm:grid-cols-12", children: [
1725
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 sm:col-span-4", children: [
1726
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: line.payee.name }),
1030
1727
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1031
- "Vencimento ",
1032
- invoice.dueDate || "\u2014",
1033
- invoice.fiscalNumber ? ` \xB7 #${invoice.fiscalNumber}` : ""
1728
+ "Venc.: ",
1729
+ line.invoice.dueDate || "\u2014",
1730
+ line.invoice.fiscalNumber ? ` \xB7 NF ${line.invoice.fiscalNumber}` : ""
1034
1731
  ] })
1035
1732
  ] }),
1036
- /* @__PURE__ */ jsx("span", { className: "shrink-0 font-medium tabular-nums", children: formatCurrency(Math.max(0, invoice.totalAmount - invoice.paidAmount), currency) })
1037
- ] }, invoice.id)) })
1733
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold tabular-nums sm:col-span-2", children: formatCurrency(openAmount(line.invoice), currency) }),
1734
+ /* @__PURE__ */ jsx("div", { className: "sm:col-span-3", children: /* @__PURE__ */ jsxs(Select, { value: line.paymentForm, onValueChange: (value) => updateLine(index, { paymentForm: value }), children: [
1735
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-9", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1736
+ /* @__PURE__ */ jsx(SelectContent, { children: Object.keys(FORM_LABEL).map((form) => /* @__PURE__ */ jsx(SelectItem, { value: form, children: FORM_LABEL[form] }, form)) })
1737
+ ] }) }),
1738
+ /* @__PURE__ */ jsx("div", { className: "sm:col-span-3", children: /* @__PURE__ */ jsx(DatePicker, { value: line.paymentDate, format: "numeric", onChange: (value) => updateLine(index, { paymentDate: value }) }) })
1739
+ ] }, line.invoice.id))
1740
+ ] }) : null,
1741
+ step === 2 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1742
+ /* @__PURE__ */ jsxs("div", { children: [
1743
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados dos favorecidos" }),
1744
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Confirme ou preencha o que falta. O que voc\xEA informar \xE9 salvo no cadastro para o pr\xF3ximo arquivo." })
1745
+ ] }),
1746
+ lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border p-3", children: [
1747
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1748
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
1749
+ line.payee.name,
1750
+ /* @__PURE__ */ jsx("span", { className: "ml-2 rounded-full border px-2 py-0.5 text-xs font-normal text-muted-foreground", children: FORM_LABEL[line.paymentForm] })
1751
+ ] }),
1752
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: formatCurrency(openAmount(line.invoice), currency) })
1753
+ ] }),
1754
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-12", children: [
1755
+ /* @__PURE__ */ jsx(Field, { label: "CPF/CNPJ", required: true, value: line.payee.document, onChange: (value) => updatePayee(index, { document: value }), className: "sm:col-span-4" }),
1756
+ line.paymentForm === "pix" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1757
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 sm:col-span-3", children: [
1758
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Tipo da chave" }),
1759
+ /* @__PURE__ */ jsxs(Select, { value: line.payee.pixKeyType || "", onValueChange: (value) => updatePayee(index, { pixKeyType: value }), children: [
1760
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-9", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Tipo" }) }),
1761
+ /* @__PURE__ */ jsx(SelectContent, { children: PIX_KEY_TYPES.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
1762
+ ] })
1763
+ ] }),
1764
+ /* @__PURE__ */ jsx(Field, { label: "Chave PIX", required: true, value: line.payee.pixKey || "", onChange: (value) => updatePayee(index, { pixKey: value, pixKeyType: line.payee.pixKeyType || inferPixKeyType(value) }), className: "sm:col-span-5" })
1765
+ ] }) : null,
1766
+ line.paymentForm === "ted" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1767
+ /* @__PURE__ */ jsx(Field, { label: "Banco", required: true, value: line.payee.bankCode || "", onChange: (value) => updatePayee(index, { bankCode: value }), className: "sm:col-span-2" }),
1768
+ /* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: line.payee.agencyNumber || "", onChange: (value) => updatePayee(index, { agencyNumber: value }), className: "sm:col-span-2" }),
1769
+ /* @__PURE__ */ jsx(Field, { label: "DV", value: line.payee.agencyDigit || "", onChange: (value) => updatePayee(index, { agencyDigit: value }), className: "sm:col-span-1" }),
1770
+ /* @__PURE__ */ jsx(Field, { label: "Conta", required: true, value: line.payee.accountNumber || "", onChange: (value) => updatePayee(index, { accountNumber: value }), className: "sm:col-span-2" }),
1771
+ /* @__PURE__ */ jsx(Field, { label: "DV", value: line.payee.accountDigit || "", onChange: (value) => updatePayee(index, { accountDigit: value }), className: "sm:col-span-1" })
1772
+ ] }) : null,
1773
+ line.paymentForm === "boleto" || line.paymentForm === "tributo" ? /* @__PURE__ */ jsx(Field, { label: "C\xF3digo de barras / linha digit\xE1vel", required: true, value: line.barcode, onChange: (value) => updateLine(index, { barcode: value }), className: "sm:col-span-8" }) : null
1774
+ ] })
1775
+ ] }, line.invoice.id)),
1776
+ gaps.length > 0 ? /* @__PURE__ */ jsxs(Warning, { children: [
1777
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Falta preencher:" }),
1778
+ /* @__PURE__ */ jsxs("ul", { className: "mt-1 space-y-0.5", children: [
1779
+ gaps.slice(0, 5).map((gap, index) => /* @__PURE__ */ jsxs("li", { children: [
1780
+ gap.name,
1781
+ ": ",
1782
+ gap.message
1783
+ ] }, `${gap.reference}-${gap.field}-${index}`)),
1784
+ gaps.length > 5 ? /* @__PURE__ */ jsxs("li", { className: "text-muted-foreground", children: [
1785
+ "e mais ",
1786
+ gaps.length - 5,
1787
+ "\u2026"
1788
+ ] }) : null
1789
+ ] })
1790
+ ] }) : null
1038
1791
  ] }) : null,
1039
- step >= 2 ? /* @__PURE__ */ jsxs("div", { className: "flex min-h-[260px] flex-col items-center justify-center text-center", children: [
1040
- /* @__PURE__ */ jsx("div", { className: "mb-3 flex h-11 w-11 items-center justify-center rounded-full bg-warning/10", children: /* @__PURE__ */ jsx(AlertTriangle, { className: "h-5 w-5 text-warning" }) }),
1041
- /* @__PURE__ */ jsx("h3", { className: "font-semibold", children: "Dados dos favorecidos ainda n\xE3o est\xE3o completos na V2" }),
1042
- /* @__PURE__ */ jsx("p", { className: "mt-2 max-w-lg text-sm text-muted-foreground", children: "A gera\xE7\xE3o continua bloqueada at\xE9 a migra\xE7\xE3o segura de CPF/CNPJ, chave PIX e dados banc\xE1rios dos favorecidos." })
1043
- ] }) : null
1792
+ step === 3 ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1793
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Resumo da remessa" }),
1794
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 text-sm sm:grid-cols-2", children: [
1795
+ /* @__PURE__ */ jsx(Summary, { label: "Banco pagador", value: `${draft?.bankCode ?? ""} \u2014 ${draft?.bankName || "\u2014"}` }),
1796
+ /* @__PURE__ */ jsx(Summary, { label: "Conta", value: `Ag ${draft?.agencyNumber ?? ""} / Cc ${draft?.accountNumber ?? ""}${draft?.accountDigit ? `-${draft.accountDigit}` : ""}` }),
1797
+ /* @__PURE__ */ jsx(Summary, { label: "Empresa", value: `${companyName || "\u2014"} (${companyDocument || "\u2014"})` }),
1798
+ /* @__PURE__ */ jsx(Summary, { label: "Total", value: formatCurrency(total, currency), strong: true }),
1799
+ /* @__PURE__ */ jsx(Summary, { label: "N\xBA da remessa (NSA)", value: String(context?.nextSequence ?? 1) })
1800
+ ] }),
1801
+ /* @__PURE__ */ jsxs("div", { className: "border-t pt-3", children: [
1802
+ /* @__PURE__ */ jsx("h4", { className: "mb-2 text-sm font-medium", children: "Lotes" }),
1803
+ /* @__PURE__ */ jsx("div", { className: "space-y-1 text-sm", children: batches.map(([form, group]) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
1804
+ /* @__PURE__ */ jsxs("span", { children: [
1805
+ FORM_LABEL[form],
1806
+ " \u2014 ",
1807
+ group.count,
1808
+ " t\xEDtulo(s)"
1809
+ ] }),
1810
+ /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums", children: formatCurrency(group.total, currency) })
1811
+ ] }, form)) })
1812
+ ] }),
1813
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Ao gerar, o arquivo .REM \xE9 baixado, a remessa \xE9 registrada no hist\xF3rico e os dados informados s\xE3o salvos nos cadastros. O status das contas n\xE3o muda." })
1814
+ ] }) : null,
1815
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-4 text-sm text-destructive", children: error }) : null
1044
1816
  ] }),
1045
1817
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-5 py-4", children: [
1046
- /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => step === 0 ? onClose() : setStep((value) => value - 1), disabled: saving, children: [
1818
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => step === 0 ? onClose() : setStep((value) => value - 1), disabled: busy, children: [
1047
1819
  step > 0 ? /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }) : null,
1048
1820
  step === 0 ? "Cancelar" : "Voltar"
1049
1821
  ] }),
1050
- step < 2 ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void advance(), disabled: saving || step === 0 && (!account || !draft || missingBankData.length > 0), children: [
1051
- saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
1052
- step === 0 ? "Salvar e avan\xE7ar" : "Avan\xE7ar",
1053
- " ",
1054
- /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" })
1055
- ] }) : /* @__PURE__ */ jsx(Button, { size: "sm", disabled: true, children: "Gerar arquivo CNAB 240" })
1822
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1823
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
1824
+ invoices.length,
1825
+ " parcela(s) selecionada(s)"
1826
+ ] }),
1827
+ step < 3 ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void advance(), disabled: busy || !canAdvance, children: [
1828
+ busy || loadingContext ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
1829
+ step === 0 ? "Salvar e avan\xE7ar" : "Avan\xE7ar",
1830
+ " ",
1831
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" })
1832
+ ] }) : /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void generate(), disabled: busy || gaps.length > 0, children: [
1833
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(FileDown, { className: "h-4 w-4" }),
1834
+ " Gerar arquivo .REM"
1835
+ ] })
1836
+ ] })
1056
1837
  ] })
1057
1838
  ] })
1058
1839
  ] }),
1059
1840
  document.body
1060
1841
  );
1061
1842
  }
1843
+ function Warning({ children }) {
1844
+ return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 rounded-lg border border-warning/30 bg-warning/10 p-3 text-sm", children: [
1845
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-warning" }),
1846
+ /* @__PURE__ */ jsx("div", { children })
1847
+ ] });
1848
+ }
1849
+ function Summary({ label, value, strong }) {
1850
+ return /* @__PURE__ */ jsxs("div", { children: [
1851
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: label }),
1852
+ /* @__PURE__ */ jsx("div", { className: strong ? "text-lg font-semibold" : "font-medium", children: value })
1853
+ ] });
1854
+ }
1062
1855
  function Field({ label, value, onChange, required, placeholder, className = "" }) {
1063
1856
  return /* @__PURE__ */ jsxs("label", { className: `block space-y-1.5 ${className}`, children: [
1064
1857
  /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
@@ -1068,6 +1861,183 @@ function Field({ label, value, onChange, required, placeholder, className = "" }
1068
1861
  /* @__PURE__ */ jsx("input", { value, onChange: (event) => onChange(event.target.value), placeholder, className: "h-9 w-full rounded-input border bg-background px-3 text-sm outline-none focus:border-primary focus:ring-2 focus:ring-primary/15" })
1069
1862
  ] });
1070
1863
  }
1864
+ var ACCEPT = "image/*,application/pdf";
1865
+ var MAX_BYTES = 15 * 1024 * 1024;
1866
+ function size(bytes) {
1867
+ if (!bytes) return "";
1868
+ return bytes < 1024 * 1024 ? `${Math.max(1, Math.round(bytes / 1024))} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
1869
+ }
1870
+ function InvoiceAttachmentsModal({ invoiceId, onClose, onChanged }) {
1871
+ const provider = useFinancialProvider();
1872
+ const inputRef = useRef(null);
1873
+ const [items, setItems] = useState(null);
1874
+ const [uploading, setUploading] = useState(0);
1875
+ const [over, setOver] = useState(false);
1876
+ const [preview, setPreview] = useState(null);
1877
+ async function load() {
1878
+ try {
1879
+ setItems(await (provider.getInvoiceAttachments?.(invoiceId) ?? Promise.resolve([])));
1880
+ } catch (e) {
1881
+ setItems([]);
1882
+ toast.error(e?.message ?? String(e));
1883
+ }
1884
+ }
1885
+ useEffect(() => {
1886
+ void load();
1887
+ }, [invoiceId]);
1888
+ async function upload(files) {
1889
+ const list = Array.from(files);
1890
+ if (!list.length || !provider.addInvoiceAttachment) return;
1891
+ const invalid = list.filter((f) => !(f.type.startsWith("image/") || f.type === "application/pdf") || f.size > MAX_BYTES);
1892
+ if (invalid.length) toast.error("Envie imagem ou PDF de at\xE9 15 MB.");
1893
+ const ok = list.filter((f) => !invalid.includes(f));
1894
+ if (!ok.length) return;
1895
+ setUploading((n) => n + ok.length);
1896
+ for (const file of ok) {
1897
+ try {
1898
+ await provider.addInvoiceAttachment(invoiceId, file);
1899
+ } catch (e) {
1900
+ toast.error(`${file.name}: ${e?.message ?? String(e)}`);
1901
+ } finally {
1902
+ setUploading((n) => n - 1);
1903
+ }
1904
+ }
1905
+ await load();
1906
+ onChanged?.();
1907
+ }
1908
+ return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
1909
+ if (!o) onClose();
1910
+ }, children: /* @__PURE__ */ jsx(ModalContent, { size: preview !== null ? "3xl" : "sm", children: preview !== null && items?.[preview] ? /* @__PURE__ */ jsx(
1911
+ AttachmentPreview,
1912
+ {
1913
+ items,
1914
+ index: preview,
1915
+ onIndex: setPreview,
1916
+ onBack: () => setPreview(null)
1917
+ }
1918
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1919
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs(ModalTitle, { className: "flex items-center gap-2", children: [
1920
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }),
1921
+ "Anexos da conta"
1922
+ ] }) }),
1923
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
1924
+ items === null ? /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", "aria-busy": "true", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-2.5", children: [
1925
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-4 shrink-0 animate-pulse rounded bg-muted" }),
1926
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
1927
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-2/3 animate-pulse rounded bg-muted" }),
1928
+ /* @__PURE__ */ jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-muted" })
1929
+ ] })
1930
+ ] }, i)) }) : items.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-card bg-muted/40 px-4 py-5 text-center", children: [
1931
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhum anexo ainda" }),
1932
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Guarde aqui o boleto, a nota ou o comprovante de pagamento \u2014 fica tudo junto da conta." })
1933
+ ] }) : /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", children: items.map((a, i) => {
1934
+ const Icon = (a.mimeType ?? "").startsWith("image/") ? Image : FileText;
1935
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
1936
+ "button",
1937
+ {
1938
+ type: "button",
1939
+ onClick: () => setPreview(i),
1940
+ className: "flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-muted/40",
1941
+ children: [
1942
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
1943
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1944
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: a.fileName }),
1945
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: a.pending ? "Arquivo ainda n\xE3o dispon\xEDvel" : [size(a.fileSize), a.createdAt?.slice(0, 10).split("-").reverse().join("/")].filter(Boolean).join(" \xB7 ") })
1946
+ ] }),
1947
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
1948
+ ]
1949
+ }
1950
+ ) }, a.id);
1951
+ }) }),
1952
+ provider.addInvoiceAttachment && /* @__PURE__ */ jsxs(
1953
+ "button",
1954
+ {
1955
+ type: "button",
1956
+ onClick: () => inputRef.current?.click(),
1957
+ onDragOver: (e) => {
1958
+ e.preventDefault();
1959
+ setOver(true);
1960
+ },
1961
+ onDragLeave: () => setOver(false),
1962
+ onDrop: (e) => {
1963
+ e.preventDefault();
1964
+ setOver(false);
1965
+ void upload(e.dataTransfer.files);
1966
+ },
1967
+ disabled: uploading > 0,
1968
+ className: `flex w-full flex-col items-center gap-1 rounded-card border-2 border-dashed px-4 py-5 text-center transition-colors ${over ? "border-primary bg-primary/5" : "border-border hover:bg-muted/40"}`,
1969
+ children: [
1970
+ uploading > 0 ? /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsx(UploadCloud, { className: "h-5 w-5 text-muted-foreground", "aria-hidden": true }),
1971
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: uploading > 0 ? "Enviando\u2026" : "Solte arquivos aqui ou clique para escolher" }),
1972
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Imagem ou PDF, at\xE9 15 MB" })
1973
+ ]
1974
+ }
1975
+ ),
1976
+ /* @__PURE__ */ jsx(
1977
+ "input",
1978
+ {
1979
+ ref: inputRef,
1980
+ type: "file",
1981
+ accept: ACCEPT,
1982
+ multiple: true,
1983
+ className: "hidden",
1984
+ onChange: (e) => {
1985
+ const f = e.target.files;
1986
+ if (f) void upload(f);
1987
+ e.target.value = "";
1988
+ }
1989
+ }
1990
+ )
1991
+ ] })
1992
+ ] }) }) });
1993
+ }
1994
+ function AttachmentPreview({ items, index, onIndex, onBack }) {
1995
+ const doc = items[index];
1996
+ const isImage = (doc.mimeType ?? "").startsWith("image/");
1997
+ const isPdf = doc.mimeType === "application/pdf";
1998
+ const go = (d) => onIndex((index + d + items.length) % items.length);
1999
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2000
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pr-8", children: [
2001
+ /* @__PURE__ */ jsxs(
2002
+ "button",
2003
+ {
2004
+ type: "button",
2005
+ onClick: onBack,
2006
+ className: "inline-flex shrink-0 items-center gap-1 rounded-md px-1.5 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2007
+ children: [
2008
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
2009
+ " Voltar"
2010
+ ]
2011
+ }
2012
+ ),
2013
+ /* @__PURE__ */ jsx(ModalTitle, { className: "min-w-0 flex-1 truncate text-sm font-medium", title: doc.fileName, children: doc.fileName }),
2014
+ items.length > 1 && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
2015
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => go(-1), "aria-label": "Anterior", className: "rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }) }),
2016
+ index + 1,
2017
+ " de ",
2018
+ items.length,
2019
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => go(1), "aria-label": "Pr\xF3ximo", className: "rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
2020
+ ] }),
2021
+ doc.url && /* @__PURE__ */ jsx(
2022
+ "a",
2023
+ {
2024
+ href: doc.url,
2025
+ target: "_blank",
2026
+ rel: "noreferrer",
2027
+ "aria-label": "Abrir em nova aba",
2028
+ className: "shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2029
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" })
2030
+ }
2031
+ )
2032
+ ] }) }),
2033
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx("div", { className: "flex h-[65vh] items-center justify-center overflow-hidden rounded-card bg-muted/40", children: doc.pending || !doc.url ? /* @__PURE__ */ jsxs("p", { className: "max-w-sm p-8 text-center text-sm text-muted-foreground", children: [
2034
+ "O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por",
2035
+ " ",
2036
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
2037
+ "."
2038
+ ] }) : isImage ? /* @__PURE__ */ jsx("img", { src: doc.url, alt: doc.fileName, className: "max-h-full max-w-full object-contain" }) : isPdf ? /* @__PURE__ */ jsx("iframe", { src: doc.url, title: doc.fileName, className: "h-full w-full bg-white" }) : /* @__PURE__ */ jsx("p", { className: "p-8 text-center text-sm text-muted-foreground", children: "Este tipo de arquivo n\xE3o tem pr\xE9-visualiza\xE7\xE3o. Use o bot\xE3o de abrir." }) }) })
2039
+ ] });
2040
+ }
1071
2041
  var KIND_LABELS = {
1072
2042
  supplier: "Fornecedor",
1073
2043
  customer: "Cliente",
@@ -1093,7 +2063,7 @@ function FinancialCounterpartySearchSelect({
1093
2063
  onEdit,
1094
2064
  disabled
1095
2065
  }) {
1096
- const search = React23.useCallback(async (query) => {
2066
+ const search = React25.useCallback(async (query) => {
1097
2067
  const results = await lookup.search(query);
1098
2068
  return results.map((result) => ({
1099
2069
  id: result.id,
@@ -1173,49 +2143,61 @@ function StatusBadge({ status }) {
1173
2143
  opt ? t2(opt.labelKey) : status
1174
2144
  ] });
1175
2145
  }
1176
- function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating) {
2146
+ function formatDueDate(iso) {
2147
+ if (!iso) return "\u2014";
2148
+ const [y, m, d] = iso.slice(0, 10).split("-");
2149
+ return d && m && y ? `${d}/${m}/${y}` : iso;
2150
+ }
2151
+ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments) {
1177
2152
  const t2 = useTranslation();
1178
2153
  return useMemo(() => [
1179
- {
1180
- accessorKey: "fiscalNumber",
1181
- header: "#",
1182
- size: 110,
1183
- cell: ({ getValue }) => {
1184
- const num2 = getValue();
1185
- return num2 ? /* @__PURE__ */ jsxs("span", { className: "text-xs font-mono text-muted-foreground", children: [
1186
- "#",
1187
- num2
1188
- ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
1189
- }
1190
- },
1191
2154
  {
1192
2155
  accessorKey: "dueDate",
1193
2156
  header: t2("financial.invoice.columnDueDate"),
1194
- cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: getValue() })
2157
+ cell: ({ row }) => {
2158
+ const total = row.original.totalInstallments ?? 1;
2159
+ const atual = row.original.installmentNumber ?? 1;
2160
+ return /* @__PURE__ */ jsxs("div", { className: "whitespace-nowrap", children: [
2161
+ /* @__PURE__ */ jsxs("p", { className: "text-sm tabular-nums", children: [
2162
+ formatDueDate(row.original.dueDate),
2163
+ total > 1 && /* @__PURE__ */ jsxs("span", { className: "ml-1.5 text-xs text-muted-foreground", title: t2("financial.invoice.columnInst"), children: [
2164
+ atual,
2165
+ "/",
2166
+ total
2167
+ ] })
2168
+ ] }),
2169
+ row.original.fiscalNumber && /* @__PURE__ */ jsxs("p", { className: "font-mono text-[10px] text-muted-foreground", children: [
2170
+ "#",
2171
+ row.original.fiscalNumber
2172
+ ] })
2173
+ ] });
2174
+ }
1195
2175
  },
1196
2176
  {
1197
2177
  id: "description",
1198
2178
  header: t2("financial.invoice.columnDescription"),
1199
- cell: ({ row }) => /* @__PURE__ */ jsxs("div", { onClick: (e) => e.stopPropagation(), children: [
1200
- row.original.contactName ? /* @__PURE__ */ jsx(PersonLink, { personId: row.original.contactId, name: row.original.contactName, size: "sm", className: "text-sm font-medium" }) : /* @__PURE__ */ jsx("p", { className: "font-medium", children: "\u2014" }),
1201
- row.original.itemsSummary && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground truncate max-w-[250px]", children: row.original.itemsSummary })
1202
- ] })
2179
+ cell: ({ row }) => (
2180
+ // Full cell width with the badge pinned right, so the statuses line up in
2181
+ // one column instead of trailing each name.
2182
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full min-w-[250px] items-center gap-3", onClick: (e) => e.stopPropagation(), children: [
2183
+ /* @__PURE__ */ jsxs("div", { className: "w-0 min-w-0 flex-1", children: [
2184
+ row.original.contactName ? /* @__PURE__ */ jsx(PersonLink, { personId: row.original.contactId, name: row.original.contactName, size: "sm", className: "block max-w-full truncate text-sm font-medium" }) : /* @__PURE__ */ jsx("p", { className: "font-medium", children: "\u2014" }),
2185
+ row.original.itemsSummary && /* @__PURE__ */ jsx("p", { className: "truncate text-[10px] text-muted-foreground", children: row.original.itemsSummary })
2186
+ ] }),
2187
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsx(StatusBadge, { status: row.original.status }) })
2188
+ ] })
2189
+ )
1203
2190
  },
1204
2191
  {
1205
2192
  accessorKey: "totalAmount",
1206
2193
  header: t2("financial.invoice.columnAmount"),
1207
2194
  enableSorting: true,
1208
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `font-medium ${row.original.status === "cancelled" ? "line-through text-muted-foreground" : ""}`, children: formatCurrency(row.original.totalAmount, currency) })
2195
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `whitespace-nowrap font-medium ${row.original.status === "cancelled" ? "line-through text-muted-foreground" : ""}`, children: formatCurrency(row.original.totalAmount, currency) })
1209
2196
  },
1210
2197
  {
1211
2198
  accessorKey: "paidAmount",
1212
2199
  header: t2("financial.invoice.columnPaid"),
1213
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: row.original.status === "cancelled" ? "line-through text-muted-foreground" : "text-muted-foreground", children: formatCurrency(row.original.paidAmount, currency) })
1214
- },
1215
- {
1216
- accessorKey: "status",
1217
- header: t2("financial.invoice.columnStatus"),
1218
- cell: ({ getValue }) => /* @__PURE__ */ jsx(StatusBadge, { status: getValue() })
2200
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `whitespace-nowrap ${row.original.status === "cancelled" ? "line-through text-muted-foreground" : "text-muted-foreground"}`, children: formatCurrency(row.original.paidAmount, currency) })
1219
2201
  },
1220
2202
  {
1221
2203
  id: "paymentMethod",
@@ -1225,7 +2207,7 @@ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAc
1225
2207
  const type = paymentMethodTypes.find((candidate) => candidate.id === (method?.paymentMethodTypeId ?? row.original.paymentMethodTypeId));
1226
2208
  const account = bankAccounts.find((candidate) => candidate.id === row.original.paymentAccountId);
1227
2209
  if (!type && !method && !account) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
1228
- return /* @__PURE__ */ jsxs("div", { className: "max-w-[180px] text-xs", children: [
2210
+ return /* @__PURE__ */ jsxs("div", { className: "max-w-[150px] text-xs", children: [
1229
2211
  /* @__PURE__ */ jsxs("p", { className: "truncate font-medium", children: [
1230
2212
  method?.name ?? type?.name ?? "\u2014",
1231
2213
  row.original.paymentCardInstallments ? ` \xB7 ${row.original.paymentCardInstallments}x` : ""
@@ -1235,69 +2217,62 @@ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAc
1235
2217
  }
1236
2218
  },
1237
2219
  {
1238
- id: "consolidate",
2220
+ id: "actions",
1239
2221
  header: "",
1240
2222
  // O ÚNICO CAMINHO QUE TRANSFORMA PREVISÃO EM DÍVIDA.
1241
2223
  //
1242
2224
  // O V1 troca as ações da linha recorrente por este botão só — não há
1243
2225
  // anexo, nem estorno, nem fatura para abrir: a ocorrência ainda não é
1244
2226
  // nada disso. Consolidar é o clique que a faz existir.
2227
+ //
2228
+ // Fora dela, o clipe, como no V1: diz que a conta tem comprovante sem
2229
+ // obrigar a abri-la. Sem anexo não desenha nada.
1245
2230
  cell: ({ row }) => {
1246
- if (!row.original.isProvisionedOccurrence) return null;
1247
- const id = row.original.occurrenceId;
1248
- return /* @__PURE__ */ jsxs(
2231
+ if (row.original.isProvisionedOccurrence) {
2232
+ const id = row.original.occurrenceId;
2233
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
2234
+ Button,
2235
+ {
2236
+ size: "sm",
2237
+ variant: "outline",
2238
+ className: "h-8 px-2",
2239
+ disabled: !id || consolidating === id,
2240
+ title: t2("financial.invoice.consolidate"),
2241
+ onClick: (e) => {
2242
+ e.stopPropagation();
2243
+ if (id) void consolidar(id);
2244
+ },
2245
+ children: [
2246
+ consolidating === id ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Zap, { className: "h-3.5 w-3.5", "aria-hidden": true }),
2247
+ /* @__PURE__ */ jsx("span", { className: "ml-1 hidden xl:inline", children: t2("financial.invoice.consolidate") })
2248
+ ]
2249
+ }
2250
+ ) });
2251
+ }
2252
+ const n = row.original.attachmentCount ?? 0;
2253
+ if (!n) return null;
2254
+ const label = n === 1 ? "1 anexo" : `${n} anexos`;
2255
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
1249
2256
  Button,
1250
2257
  {
1251
- size: "sm",
1252
- variant: "outline",
1253
- disabled: !id || consolidating === id,
2258
+ size: "icon",
2259
+ variant: "ghost",
2260
+ className: "relative h-8 w-8 text-muted-foreground hover:text-foreground",
2261
+ title: label,
2262
+ "aria-label": label,
1254
2263
  onClick: (e) => {
1255
2264
  e.stopPropagation();
1256
- if (id) void consolidar(id);
2265
+ openAttachments(row.original.invoiceId ?? row.original.id);
1257
2266
  },
1258
2267
  children: [
1259
- consolidating === id ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1 h-3.5 w-3.5 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Zap, { className: "mr-1 h-3.5 w-3.5", "aria-hidden": true }),
1260
- t2("financial.invoice.consolidate")
1261
- ]
1262
- }
1263
- );
1264
- }
1265
- },
1266
- {
1267
- id: "attachments",
1268
- header: "",
1269
- // O clipe, como no V1: diz que a conta tem comprovante sem obrigar a
1270
- // abri-la. Sem anexo não desenha nada — uma coluna de clipes apagados é
1271
- // ruído do tamanho da lista.
1272
- cell: ({ row }) => {
1273
- const n = row.original.attachmentCount ?? 0;
1274
- if (!n) return null;
1275
- return /* @__PURE__ */ jsxs(
1276
- "span",
1277
- {
1278
- className: "inline-flex items-center gap-0.5 text-muted-foreground",
1279
- title: n === 1 ? "1 anexo" : `${n} anexos`,
1280
- children: [
1281
- /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5", "aria-hidden": true }),
1282
- n > 1 ? /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium tabular-nums", children: n }) : null,
1283
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: n === 1 ? "1 anexo" : `${n} anexos` })
2268
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4", "aria-hidden": true }),
2269
+ n > 0 && /* @__PURE__ */ jsx("span", { className: "absolute -right-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-primary-foreground tabular-nums", children: n })
1284
2270
  ]
1285
2271
  }
1286
- );
1287
- }
1288
- },
1289
- {
1290
- accessorKey: "totalInstallments",
1291
- header: t2("financial.invoice.columnInst"),
1292
- // "3/4", como o V1 — a linha é uma parcela, e o número sozinho não diz
1293
- // QUAL delas. Parcela única fica só "1".
1294
- cell: ({ row }) => {
1295
- const total = row.original.totalInstallments ?? 1;
1296
- const atual = row.original.installmentNumber ?? 1;
1297
- return /* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: total > 1 ? `${atual}/${total}` : total });
2272
+ ) });
1298
2273
  }
1299
2274
  }
1300
- ], [currency, t2, paymentMethods2, paymentMethodTypes, bankAccounts]);
2275
+ ], [currency, t2, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments]);
1301
2276
  }
1302
2277
  function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1303
2278
  const t2 = useTranslation();
@@ -1430,7 +2405,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1430
2405
  setConsolidating(occurrenceId);
1431
2406
  void provider.consolidateOccurrence(occurrenceId).then((invoiceId) => {
1432
2407
  toast.success(t2("financial.invoice.consolidated"));
1433
- if (invoiceId && onEdit) onEdit(invoiceId);
2408
+ if (invoiceId && onEdit) onEdit(invoiceId, "open");
1434
2409
  else void fetchInvoices(query);
1435
2410
  }).catch((err) => {
1436
2411
  toast.error(t2("financial.invoice.consolidateFailed"), {
@@ -1439,7 +2414,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1439
2414
  }).finally(() => setConsolidating(null));
1440
2415
  }, [provider, t2, fetchInvoices, query, onEdit]);
1441
2416
  const filtered = invoices.filter((inv) => inv.direction === direction);
1442
- const columns = useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating);
2417
+ const [attachmentsOf, setAttachmentsOf] = useState(null);
2418
+ const openAttachments = useCallback((invoiceId) => setAttachmentsOf(invoiceId), []);
2419
+ const columns = useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments);
1443
2420
  const contactLookup = useMemo(() => createFinancialCounterpartyLookup(direction), [direction]);
1444
2421
  const tags = useMemo(
1445
2422
  // `provisioned` fora da barra: ele não é um recorte do que existe, é o
@@ -1451,14 +2428,14 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1451
2428
  const summary = useMemo(() => {
1452
2429
  let totalAmount = 0;
1453
2430
  let paidAmount = 0;
1454
- let openAmount = 0;
2431
+ let openAmount2 = 0;
1455
2432
  for (const inv of filtered) {
1456
2433
  if (inv.status === "cancelled") continue;
1457
2434
  totalAmount += inv.totalAmount;
1458
2435
  paidAmount += inv.paidAmount;
1459
2436
  }
1460
- openAmount = totalAmount - paidAmount;
1461
- return { totalAmount, paidAmount, openAmount };
2437
+ openAmount2 = totalAmount - paidAmount;
2438
+ return { totalAmount, paidAmount, openAmount: openAmount2 };
1462
2439
  }, [filtered]);
1463
2440
  const [cancelTarget, setCancelTarget] = useState(null);
1464
2441
  const [cnabTarget, setCnabTarget] = useState(null);
@@ -1782,7 +2759,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1782
2759
  },
1783
2760
  ...onNewReceiptGated ? [{
1784
2761
  id: "receipt",
1785
- label: t2("financial.quickTx.sendReceipt"),
2762
+ // Same flow for payables and receivables: the photo is the request and
2763
+ // the assistant returns the form filled in.
2764
+ label: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
1786
2765
  description: t2("financial.quickTx.receiptCapture"),
1787
2766
  icon: Camera,
1788
2767
  intent: "secondary",
@@ -1792,10 +2771,20 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1792
2771
  // preenchido. Ver receipt-agent.ts.
1793
2772
  onSelect: () => startReceiptCapture({
1794
2773
  ask: t2("financial.receipt.ask"),
1795
- uploadLabel: t2("financial.quickTx.sendReceipt"),
2774
+ uploadLabel: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
1796
2775
  uploadHint: t2("financial.quickTx.receiptCapture"),
1797
2776
  prompt: t2("financial.receipt.prompt")
1798
2777
  })
2778
+ }] : [],
2779
+ ...direction === "debit" ? [{
2780
+ id: "payroll",
2781
+ label: t2("financial.invoice.importPayroll"),
2782
+ description: t2("financial.invoice.importPayrollSoon"),
2783
+ icon: Users,
2784
+ intent: "secondary",
2785
+ disabled: true,
2786
+ onSelect: () => {
2787
+ }
1799
2788
  }] : []
1800
2789
  ] : [];
1801
2790
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
@@ -1816,7 +2805,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1816
2805
  actions: newActions,
1817
2806
  bulkActions,
1818
2807
  onRowClick: (row) => {
1819
- if (!row.isProvisionedOccurrence) onEdit?.(row.invoiceId ?? row.id);
2808
+ if (!row.isProvisionedOccurrence) onEdit?.(row.invoiceId ?? row.id, row.status);
1820
2809
  },
1821
2810
  rowClassName: (row) => row.isProvisionedOccurrence ? "border-dashed !cursor-default bg-muted/10 text-muted-foreground" : void 0,
1822
2811
  emptyIcon: FileText,
@@ -1825,6 +2814,16 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1825
2814
  onEmptyAction: onNewGated
1826
2815
  }
1827
2816
  ),
2817
+ attachmentsOf && /* @__PURE__ */ jsx(
2818
+ InvoiceAttachmentsModal,
2819
+ {
2820
+ invoiceId: attachmentsOf,
2821
+ onClose: () => setAttachmentsOf(null),
2822
+ onChanged: () => {
2823
+ void fetchInvoices(query);
2824
+ }
2825
+ }
2826
+ ),
1828
2827
  filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
1829
2828
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1830
2829
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.summaryTotal") }),
@@ -2123,12 +3122,12 @@ async function settlePayable(provider, input) {
2123
3122
  };
2124
3123
  }
2125
3124
  const invoice = await provider.getInvoiceById(input.invoiceId).catch(() => null);
2126
- const money3 = (n) => n.toLocaleString("pt-BR", { style: "currency", currency: input.currency ?? "BRL" });
3125
+ const money4 = (n) => n.toLocaleString("pt-BR", { style: "currency", currency: input.currency ?? "BRL" });
2127
3126
  const invoiceLabel = [
2128
3127
  invoice?.fiscalNumber ? `#${invoice.fiscalNumber}` : null,
2129
3128
  invoice?.contactName ?? null,
2130
- money3(bill.amount)
2131
- ].filter(Boolean).join(" \u2014 ") || money3(bill.amount);
3129
+ money4(bill.amount)
3130
+ ].filter(Boolean).join(" \u2014 ") || money4(bill.amount);
2132
3131
  const needsAccount = input.method.transactionType !== "cash";
2133
3132
  const accounts = needsAccount ? await eligibleAccounts(provider, input.method) : [];
2134
3133
  if (needsAccount && !accounts.length) {
@@ -2318,7 +3317,7 @@ function createPayableTools({ config, provider }) {
2318
3317
  ok: true,
2319
3318
  read: {
2320
3319
  supplier: supplierName,
2321
- supplierDocument: onlyDigits(args.supplierDocument) || void 0,
3320
+ supplierDocument: onlyDigits2(args.supplierDocument) || void 0,
2322
3321
  paymentMethod: text(args.paymentMethod),
2323
3322
  cardBrand: text(args.cardBrand),
2324
3323
  cardLast4: lastFour(args.cardLast4) || void 0
@@ -2686,16 +3685,16 @@ function normalizeCompanyName(raw) {
2686
3685
  if (typeof raw !== "string") return "";
2687
3686
  return raw.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w && !COMPANY_NOISE.has(w)).join(" ").trim();
2688
3687
  }
2689
- function onlyDigits(raw) {
3688
+ function onlyDigits2(raw) {
2690
3689
  return typeof raw === "string" ? raw.replace(/\D/g, "") : "";
2691
3690
  }
2692
3691
  function lastFour(raw) {
2693
- const digits = onlyDigits(raw);
2694
- return digits.length >= 4 ? digits.slice(-4) : "";
3692
+ const digits2 = onlyDigits2(raw);
3693
+ return digits2.length >= 4 ? digits2.slice(-4) : "";
2695
3694
  }
2696
3695
  async function findSupplier(lookup, rawName, rawDocument) {
2697
3696
  const wanted = normalizeCompanyName(rawName);
2698
- const doc = onlyDigits(rawDocument);
3697
+ const doc = onlyDigits2(rawDocument);
2699
3698
  if (!wanted) return { status: "missing", candidates: [] };
2700
3699
  const core = wanted.split(" ")[0] ?? wanted;
2701
3700
  const seen = /* @__PURE__ */ new Map();
@@ -2705,7 +3704,7 @@ async function findSupplier(lookup, rawName, rawDocument) {
2705
3704
  seen.set(hit.id, {
2706
3705
  id: hit.id,
2707
3706
  name: hit.label,
2708
- document: onlyDigits(row.document_number) || void 0,
3707
+ document: onlyDigits2(row.document_number) || void 0,
2709
3708
  why: ""
2710
3709
  });
2711
3710
  }
@@ -2903,9 +3902,9 @@ var SUBMIT_DRAFT = {
2903
3902
  parameters: { type: "object", properties: {} }
2904
3903
  };
2905
3904
  function PayableDraftSurface({ config, draft }) {
2906
- const live = React23.useRef(draft);
3905
+ const live = React25.useRef(draft);
2907
3906
  live.current = draft;
2908
- const lookup = React23.useMemo(
3907
+ const lookup = React25.useMemo(
2909
3908
  () => config.contactLookup ?? createArchetypeLookup({
2910
3909
  archetype: "person",
2911
3910
  kind: "supplier",
@@ -2914,7 +3913,7 @@ function PayableDraftSurface({ config, draft }) {
2914
3913
  }),
2915
3914
  [config.contactLookup]
2916
3915
  );
2917
- React23.useEffect(() => {
3916
+ React25.useEffect(() => {
2918
3917
  liveDraft = {
2919
3918
  describe: () => live.current.describe(),
2920
3919
  setSupplier: (name, id) => live.current.setSupplier(name, id),
@@ -2925,7 +3924,7 @@ function PayableDraftSurface({ config, draft }) {
2925
3924
  liveDraft = null;
2926
3925
  };
2927
3926
  }, []);
2928
- const contribution = React23.useMemo(() => ({
3927
+ const contribution = React25.useMemo(() => ({
2929
3928
  id: "financial.payables.draft",
2930
3929
  pluginId: "financial",
2931
3930
  role: "finance",
@@ -3000,7 +3999,7 @@ function PayableDraftSurface({ config, draft }) {
3000
3999
  }
3001
4000
  function createFinancialAgentMount(deps) {
3002
4001
  const FinancialAgentMount = () => {
3003
- const contribution = React23.useMemo(() => ({
4002
+ const contribution = React25.useMemo(() => ({
3004
4003
  id: "financial.payables.global",
3005
4004
  pluginId: "financial",
3006
4005
  role: "finance",
@@ -3095,13 +4094,24 @@ function CardVisual({
3095
4094
  lastFour: lastFour2,
3096
4095
  color,
3097
4096
  brandId,
3098
- size = "sm"
4097
+ size: size2 = "sm"
3099
4098
  }) {
3100
4099
  const brand = useBrandName(brandId);
3101
4100
  const last = (lastFour2 ?? "").trim();
3102
4101
  if (!last && !color && !brand) return null;
3103
4102
  const bg = color && /^#[0-9a-fA-F]{6}$/.test(color) ? color : void 0;
3104
- const lg = size === "lg";
4103
+ const lg = size2 === "lg";
4104
+ if (size2 === "xs") {
4105
+ return /* @__PURE__ */ jsx(
4106
+ "span",
4107
+ {
4108
+ className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[3px] border align-middle text-[6px] font-semibold leading-none tabular-nums",
4109
+ style: bg ? { backgroundColor: bg, color: ink(bg), borderColor: "transparent" } : void 0,
4110
+ title: [brand, last && `\u2022\u2022\u2022\u2022 ${last}`].filter(Boolean).join(" "),
4111
+ children: last
4112
+ }
4113
+ );
4114
+ }
3105
4115
  return /* @__PURE__ */ jsxs(
3106
4116
  "span",
3107
4117
  {
@@ -3171,7 +4181,9 @@ function eligibleAccountsFor(bankAccounts, direction, type) {
3171
4181
  const active = bankAccounts.filter((a) => a.isActive !== false);
3172
4182
  if (direction === "debit") {
3173
4183
  if (txType === "credit_card") return active.filter((a) => a.accountType === "card_payable");
3174
- return active.filter((a) => !["card_payable", "card_receivable_credit", "card_receivable_debit"].includes(a.accountType));
4184
+ const own = (type?.allowedAccountTypes?.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType])?.filter((k) => !k.startsWith("card_"));
4185
+ const kinds = own?.length ? own : ["bank", "cash_physical", "cash_session"];
4186
+ return active.filter((a) => kinds.includes(a.accountType));
3175
4187
  }
3176
4188
  const allowed = type?.allowedAccountTypes && type.allowedAccountTypes.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType];
3177
4189
  if (!allowed || !allowed.length) return bankAccounts;
@@ -3493,10 +4505,10 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
3493
4505
  const payMovement = useFinancialStore((s) => s.payMovement);
3494
4506
  const remaining = movement.amount - movement.paidAmount;
3495
4507
  const [saving, setSaving] = useState(false);
3496
- const command = React23__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
4508
+ const command = React25__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
3497
4509
  const [pending2, setPending] = useState(false);
3498
4510
  const [visible, setVisible] = useState(false);
3499
- React23__default.useEffect(() => {
4511
+ React25__default.useEffect(() => {
3500
4512
  requestAnimationFrame(() => setVisible(true));
3501
4513
  }, []);
3502
4514
  const source = movement.metadata ?? {};
@@ -3615,8 +4627,34 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
3615
4627
  document.body
3616
4628
  );
3617
4629
  }
4630
+ var METHOD_ICONS = {
4631
+ cash: Banknote,
4632
+ pix: QrCode,
4633
+ credit_card: CreditCard,
4634
+ debit_card: CreditCard,
4635
+ bank_transfer: ArrowRightLeft,
4636
+ check: FileCheck,
4637
+ boleto: Receipt,
4638
+ voucher: Ticket
4639
+ };
4640
+ function MethodIcon({ type }) {
4641
+ const Icon = METHOD_ICONS[type?.transactionType ?? ""] ?? Wallet;
4642
+ return /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4643
+ }
4644
+ function AccountIcon({ account }) {
4645
+ if (!account) return null;
4646
+ if (account.accountType === "card_payable" && (account.lastFour || account.color || account.cardBrandId)) {
4647
+ return /* @__PURE__ */ jsx(CardVisual, { size: "xs", lastFour: account.lastFour ?? void 0, color: account.color ?? void 0, brandId: account.cardBrandId ?? void 0 });
4648
+ }
4649
+ if (account.accountType.startsWith("card_")) return /* @__PURE__ */ jsx(CreditCard, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4650
+ if (account.accountType === "cash_physical" || account.accountType === "cash_session") {
4651
+ return /* @__PURE__ */ jsx(Banknote, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4652
+ }
4653
+ const bank = findBrBank(account.bankCode, account.bankName ?? account.name);
4654
+ return bank?.logo ? /* @__PURE__ */ jsx(BankLogo, { bank, size: 16 }) : /* @__PURE__ */ jsx(Landmark, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4655
+ }
3618
4656
  var NONE = "__none__";
3619
- var TRIGGER_CLASS = "h-auto w-auto justify-start gap-1 rounded-none border-0 border-b border-dashed border-current bg-transparent p-0 pb-px text-xs font-normal shadow-none ring-offset-0 focus:ring-0 focus:ring-offset-0 [&>svg]:h-3 [&>svg]:w-3";
4657
+ var TRIGGER_CLASS = `h-auto w-auto justify-start gap-1 rounded-none border-0 bg-transparent p-0 text-xs font-normal shadow-none ring-offset-0 focus:ring-0 focus:ring-offset-0 [&>svg]:h-3 [&>svg]:w-3 ${INLINE_LINK_TONE.dashed}`;
3620
4658
  function InstallmentSourceFields({
3621
4659
  direction,
3622
4660
  value,
@@ -3636,8 +4674,8 @@ function InstallmentSourceFields({
3636
4674
  const conta = elegiveis.length === 1 ? elegiveis[0].id : elegiveis.some((a) => a.id === value.payAccountId) ? value.payAccountId : void 0;
3637
4675
  onChange({ payMethodTypeId: typeId || void 0, payAccountId: typeId ? conta : void 0 });
3638
4676
  }
3639
- const tone = (filled) => filled ? "text-foreground" : "text-muted-foreground";
3640
- return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
4677
+ const tone = (filled) => filled ? "text-foreground" : "text-destructive border-b border-dashed border-destructive";
4678
+ return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-start gap-0.5", children: [
3641
4679
  /* @__PURE__ */ jsxs(
3642
4680
  Select,
3643
4681
  {
@@ -3654,37 +4692,202 @@ function InstallmentSourceFields({
3654
4692
  }
3655
4693
  ),
3656
4694
  /* @__PURE__ */ jsxs(SelectContent, { children: [
3657
- paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: tp.name }, tp.id)),
4695
+ paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
4696
+ /* @__PURE__ */ jsx(MethodIcon, { type: tp }),
4697
+ tp.name
4698
+ ] }) }, tp.id)),
3658
4699
  value.payMethodTypeId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
3659
4700
  ] })
3660
4701
  ]
3661
4702
  }
3662
4703
  ),
3663
- /* @__PURE__ */ jsxs(
3664
- Select,
4704
+ /* @__PURE__ */ jsx(
4705
+ AccountPicker,
3665
4706
  {
3666
- value: value.payAccountId ?? "",
4707
+ accounts,
4708
+ value: value.payAccountId,
3667
4709
  disabled: disabled || !value.payMethodTypeId,
3668
- onValueChange: (raw) => onChange({ ...value, payAccountId: raw === NONE ? void 0 : raw }),
4710
+ title: value.payMethodTypeId ? void 0 : t2("financial.invoiceForm.selectMethodFirst"),
4711
+ placeholder: t2("financial.payment.account"),
4712
+ ariaLabel: t2("financial.invoiceForm.paidFrom"),
4713
+ clearLabel: t2("financial.invoiceForm.clear"),
4714
+ className: `${INLINE_LINK_TONE.dashed} ${tone(!!selectedAccount)}`,
4715
+ onChange: (id) => onChange({ ...value, payAccountId: id })
4716
+ }
4717
+ )
4718
+ ] });
4719
+ }
4720
+ var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
4721
+ function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className }) {
4722
+ const [open, setOpen] = React25__default.useState(false);
4723
+ const [q, setQ] = React25__default.useState("");
4724
+ const selected = accounts.find((a) => a.id === value);
4725
+ const list = q ? accounts.filter((a) => norm2(a.name).includes(norm2(q))) : accounts;
4726
+ const pick = (id) => {
4727
+ onChange(id);
4728
+ setOpen(false);
4729
+ setQ("");
4730
+ };
4731
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (o) => {
4732
+ setOpen(o);
4733
+ if (!o) setQ("");
4734
+ }, children: [
4735
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
4736
+ "button",
4737
+ {
4738
+ type: "button",
4739
+ disabled,
4740
+ title,
4741
+ "aria-label": ariaLabel,
4742
+ className: `inline-flex max-w-full items-center gap-1 text-xs disabled:cursor-not-allowed disabled:opacity-50 ${className ?? ""}`,
3669
4743
  children: [
3670
- /* @__PURE__ */ jsx(
3671
- SelectTrigger,
3672
- {
3673
- "aria-label": t2("financial.invoiceForm.paidFrom"),
3674
- title: value.payMethodTypeId ? void 0 : t2("financial.invoiceForm.selectMethodFirst"),
3675
- className: `${TRIGGER_CLASS} ${tone(!!selectedAccount)}`,
3676
- children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t2("financial.payment.account") })
3677
- }
3678
- ),
3679
- /* @__PURE__ */ jsxs(SelectContent, { children: [
3680
- accounts.map((a) => /* @__PURE__ */ jsx(SelectItem, { value: a.id, children: a.name }, a.id)),
3681
- value.payAccountId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
3682
- ] })
4744
+ selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
4745
+ /* @__PURE__ */ jsx(AccountIcon, { account: selected }),
4746
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selected.name })
4747
+ ] }) : placeholder,
4748
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true })
3683
4749
  ]
3684
4750
  }
3685
- )
4751
+ ) }),
4752
+ /* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-64 p-1", children: [
4753
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-1", children: [
4754
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground", "aria-hidden": true }),
4755
+ /* @__PURE__ */ jsx(
4756
+ "input",
4757
+ {
4758
+ autoFocus: true,
4759
+ value: q,
4760
+ onChange: (e) => setQ(e.target.value),
4761
+ onKeyDown: (e) => {
4762
+ if (e.key === "Enter" && list[0]) {
4763
+ e.preventDefault();
4764
+ pick(list[0].id);
4765
+ }
4766
+ },
4767
+ placeholder: "Buscar conta\u2026",
4768
+ className: "h-8 w-full rounded-md border-0 bg-muted/50 pl-7 pr-2 text-xs outline-none focus:ring-0"
4769
+ }
4770
+ )
4771
+ ] }),
4772
+ /* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto", children: list.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: "Nenhuma conta encontrada" }) : list.map((a) => /* @__PURE__ */ jsxs(
4773
+ "button",
4774
+ {
4775
+ type: "button",
4776
+ onClick: () => pick(a.id),
4777
+ className: `flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted ${a.id === value ? "font-medium" : ""}`,
4778
+ children: [
4779
+ /* @__PURE__ */ jsx(AccountIcon, { account: a }),
4780
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: a.name }),
4781
+ a.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
4782
+ ]
4783
+ },
4784
+ a.id
4785
+ )) }),
4786
+ value && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => pick(void 0), className: "mt-1 w-full rounded-md px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted", children: clearLabel })
4787
+ ] })
4788
+ ] });
4789
+ }
4790
+ var STATUS = {
4791
+ open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
4792
+ pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
4793
+ partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial" },
4794
+ paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid" },
4795
+ overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue" },
4796
+ draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
4797
+ cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
4798
+ };
4799
+ function InstallmentStatusBadge({ status }) {
4800
+ const t2 = useTranslation();
4801
+ const s = STATUS[status ?? ""] ?? STATUS.pending;
4802
+ const Icon = s.icon;
4803
+ return /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${s.bg}`, children: [
4804
+ /* @__PURE__ */ jsx(Icon, { className: "h-2 w-2" }),
4805
+ " ",
4806
+ t2(s.labelKey)
4807
+ ] });
4808
+ }
4809
+ function InstallmentRow({
4810
+ number,
4811
+ amount,
4812
+ status,
4813
+ dueDate,
4814
+ onDueDateChange,
4815
+ dueExtra,
4816
+ source,
4817
+ actions,
4818
+ onClick
4819
+ }) {
4820
+ const t2 = useTranslation();
4821
+ const stop = (e) => e.stopPropagation();
4822
+ return /* @__PURE__ */ jsxs(
4823
+ "div",
4824
+ {
4825
+ className: `flex flex-wrap items-center gap-3 px-4 py-3 ${onClick ? "cursor-pointer transition-colors hover:bg-muted/20" : ""}`,
4826
+ onClick,
4827
+ children: [
4828
+ /* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
4829
+ "#",
4830
+ number
4831
+ ] }),
4832
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-start gap-0.5", children: [
4833
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4834
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: amount }),
4835
+ status && /* @__PURE__ */ jsx(InstallmentStatusBadge, { status })
4836
+ ] }),
4837
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground", onClick: stop, children: [
4838
+ t2("financial.payment.due"),
4839
+ onDueDateChange ? /* @__PURE__ */ jsx(DatePicker, { value: dueDate, variant: "inline", format: "numeric", onChange: onDueDateChange }) : dueDate,
4840
+ dueExtra
4841
+ ] })
4842
+ ] }),
4843
+ /* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", onClick: stop, children: source }),
4844
+ (actions || onClick) && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", onClick: stop, children: [
4845
+ actions,
4846
+ onClick && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
4847
+ ] })
4848
+ ]
4849
+ }
4850
+ );
4851
+ }
4852
+ var BTN = "inline-flex shrink-0 items-center gap-1 px-3 py-1.5 text-xs font-medium transition-colors active:shadow-button-inset disabled:pointer-events-none disabled:opacity-40";
4853
+ function InstallmentPayButton({ onClick, disabled, title, children, variant = "primary" }) {
4854
+ return /* @__PURE__ */ jsxs(
4855
+ "button",
4856
+ {
4857
+ type: "button",
4858
+ onClick,
4859
+ disabled,
4860
+ title,
4861
+ className: variant === "primary" ? `${BTN} rounded-button border border-primary bg-primary text-primary-foreground shadow-button-primary hover:bg-primary/90` : `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`,
4862
+ children: [
4863
+ /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
4864
+ children
4865
+ ]
4866
+ }
4867
+ );
4868
+ }
4869
+ function InstallmentActionButton({ onClick, icon: Icon, children }) {
4870
+ return /* @__PURE__ */ jsxs("button", { type: "button", onClick, className: `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`, children: [
4871
+ /* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
4872
+ children
3686
4873
  ] });
3687
4874
  }
4875
+ function InstallmentReceiptButton({ onClick, title, attached, pending: pending2, count }) {
4876
+ return /* @__PURE__ */ jsxs(
4877
+ "button",
4878
+ {
4879
+ type: "button",
4880
+ onClick,
4881
+ title,
4882
+ "aria-label": title,
4883
+ className: `inline-flex shrink-0 items-center gap-1 rounded-lg border bg-card px-2.5 py-1.5 text-xs font-medium shadow-button transition-colors hover:bg-muted active:shadow-button-inset ${attached ? "text-success" : pending2 ? "text-primary" : ""}`,
4884
+ children: [
4885
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3" }),
4886
+ !!count && /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: count })
4887
+ ]
4888
+ }
4889
+ );
4890
+ }
3688
4891
 
3689
4892
  // src/views/focusItem.ts
3690
4893
  var CHAVE = "fayz:financial:focus-item";
@@ -3709,9 +4912,9 @@ function consumirItemParaAbrir(invoiceId) {
3709
4912
  }
3710
4913
  }
3711
4914
  function useStockedItemTypes(itemTypes, entityLookups) {
3712
- const [empty, setEmpty] = React23__default.useState(null);
4915
+ const [empty, setEmpty] = React25__default.useState(null);
3713
4916
  const backed = itemTypes.filter((type) => entityLookups[type.value]?.count).map((type) => type.value).sort().join(",");
3714
- React23__default.useEffect(() => {
4917
+ React25__default.useEffect(() => {
3715
4918
  const kinds = backed ? backed.split(",") : [];
3716
4919
  if (!kinds.length) {
3717
4920
  setEmpty(null);
@@ -3732,17 +4935,17 @@ function useStockedItemTypes(itemTypes, entityLookups) {
3732
4935
  cancelled = true;
3733
4936
  };
3734
4937
  }, [backed]);
3735
- return React23__default.useMemo(
4938
+ return React25__default.useMemo(
3736
4939
  () => empty?.size ? itemTypes.filter((type) => !empty.has(type.value)) : itemTypes,
3737
4940
  [itemTypes, empty]
3738
4941
  );
3739
4942
  }
3740
4943
 
3741
4944
  // src/lib/invoice-list.ts
3742
- var S\u00D3_RECEBIVEL = /* @__PURE__ */ new Set(["service", "gift_card"]);
4945
+ var S\u00D3_RECEBIVEL = /* @__PURE__ */ new Set(["service", "package", "gift_card"]);
3743
4946
  function availableInvoiceItemTypes(itemTypes, lookupKinds, otherLabel, direction) {
3744
4947
  const installed = new Set(lookupKinds);
3745
- const visible = itemTypes.filter((type) => (!(direction === "debit" || direction === "payable") || !S\u00D3_RECEBIVEL.has(type.value)) && (type.value === "other" || !["product", "service"].includes(type.value) || installed.has(type.value)));
4948
+ const visible = itemTypes.filter((type) => (!(direction === "debit" || direction === "payable") || !S\u00D3_RECEBIVEL.has(type.value)) && (type.value === "other" || !["product", "service", "package"].includes(type.value) || installed.has(type.value)));
3746
4949
  if (visible.some((type) => type.value === "other")) return visible;
3747
4950
  return [...visible, { value: "other", label: otherLabel, icon: "MoreHorizontal" }];
3748
4951
  }
@@ -3807,6 +5010,9 @@ function summarizeInvoiceLines(rows, visible = 2) {
3807
5010
  }
3808
5011
  return summaries;
3809
5012
  }
5013
+ function invoiceOpensForEditing(status) {
5014
+ return status !== "paid" && status !== "cancelled";
5015
+ }
3810
5016
  function LocationSelector({ locations, value, onChange }) {
3811
5017
  const t2 = useTranslation();
3812
5018
  const [open, setOpen] = useState(false);
@@ -3925,11 +5131,11 @@ function itemsKey(list) {
3925
5131
  it.costCenterId ?? ""
3926
5132
  ]));
3927
5133
  }
3928
- function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fromCardId }) {
5134
+ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fromCardId, embedded, onChanged }) {
3929
5135
  const t2 = useTranslation();
3930
5136
  const config = useFinancialConfig();
3931
5137
  const provider = useFinancialProvider();
3932
- const { currency, itemTypes: itemTypesRaw, locations } = config;
5138
+ const { currency, itemTypes: itemTypesRaw = [], locations = [] } = config;
3933
5139
  const unitMode = useUnitMode();
3934
5140
  const activeUnitId = useActiveUnitId();
3935
5141
  const reachableUnits = useUnitFilter().options;
@@ -3977,14 +5183,14 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3977
5183
  };
3978
5184
  }, [giftCardsEnabled, provider]);
3979
5185
  const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
3980
- const translatedItemTypes = React23__default.useMemo(
5186
+ const translatedItemTypes = React25__default.useMemo(
3981
5187
  () => itemTypesRaw.map((tp) => {
3982
5188
  const traduzido = t2(`financial.itemType.${tp.value}`);
3983
5189
  return traduzido === `financial.itemType.${tp.value}` ? tp : { ...tp, label: traduzido };
3984
5190
  }),
3985
5191
  [itemTypesRaw, t2]
3986
5192
  );
3987
- const offeredItemTypes = React23__default.useMemo(
5193
+ const offeredItemTypes = React25__default.useMemo(
3988
5194
  () => availableInvoiceItemTypes(
3989
5195
  giftCardsEnabled && !translatedItemTypes.some((type) => type.value === "gift_card") ? [...translatedItemTypes, { value: "gift_card", label: t2("financial.itemType.gift_card"), icon: "Gift" }] : translatedItemTypes,
3990
5196
  Object.keys(entityLookups),
@@ -4005,11 +5211,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4005
5211
  const guardMovements = useLimitGuard("movements_month");
4006
5212
  const can = usePermissionOptional();
4007
5213
  const isEdit = !!editId;
5214
+ const [attachmentsOpen, setAttachmentsOpen] = useState(false);
4008
5215
  useEffect(() => {
4009
- if (isEdit) return;
4010
5216
  void fetchBankAccounts();
4011
5217
  void fetchPaymentMethods();
4012
- }, [isEdit]);
5218
+ }, []);
4013
5219
  useEffect(() => {
4014
5220
  let cancelled = false;
4015
5221
  void Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
@@ -4164,7 +5370,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4164
5370
  discount: it.discount,
4165
5371
  surcharge: it.surcharge,
4166
5372
  accountId: it.accountId,
4167
- costCenterId: it.costCenterId
5373
+ costCenterId: it.costCenterId,
5374
+ metadata: it.metadata
4168
5375
  };
4169
5376
  }));
4170
5377
  const aAbrir = consumirItemParaAbrir(editId);
@@ -4240,6 +5447,39 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4240
5447
  function updateItem(id, data) {
4241
5448
  setItems((current) => current.map((it) => it._id === id ? { ...it, ...data } : it));
4242
5449
  }
5450
+ async function applyItemPatch(item, patch) {
5451
+ const expand = patch.referenceId ? entityLookups[item.itemKind]?.expand : void 0;
5452
+ if (!expand) {
5453
+ updateItem(item._id, patch);
5454
+ return;
5455
+ }
5456
+ let linhas = [];
5457
+ try {
5458
+ linhas = await expand(patch.referenceId);
5459
+ } catch (err) {
5460
+ console.error("[financial] failed to expand package", err);
5461
+ }
5462
+ if (!linhas.length) {
5463
+ updateItem(item._id, patch);
5464
+ return;
5465
+ }
5466
+ const oferecidos = new Set(itemTypes.map((tp) => tp.value));
5467
+ const novas = linhas.map((linha) => ({
5468
+ _id: fid(),
5469
+ itemKind: linha.itemKind && oferecidos.has(linha.itemKind) ? linha.itemKind : item.itemKind,
5470
+ referenceId: linha.referenceId,
5471
+ description: linha.description,
5472
+ quantity: linha.quantity,
5473
+ unitPrice: linha.unitPrice,
5474
+ ...linha.discount ? { discount: linha.discount } : {},
5475
+ ...linha.surcharge ? { surcharge: linha.surcharge } : {},
5476
+ ...linha.metadata ? { metadata: linha.metadata } : {},
5477
+ accountId: item.accountId,
5478
+ costCenterId: item.costCenterId
5479
+ }));
5480
+ setItems((current) => current.flatMap((it) => it._id === item._id ? novas : [it]));
5481
+ setExpandedItemId(null);
5482
+ }
4243
5483
  function removeItem(id) {
4244
5484
  setItems((current) => current.filter((it) => it._id !== id));
4245
5485
  }
@@ -4247,13 +5487,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4247
5487
  if (isEdit || totalAmount <= 0 || installments.length > 0) return;
4248
5488
  generateInstallments(1);
4249
5489
  }, [isEdit, totalAmount, installments.length]);
4250
- const fromCard = React23__default.useMemo(
5490
+ const fromCard = React25__default.useMemo(
4251
5491
  () => fromCardId ? bankAccounts.find((a) => a.id === fromCardId) : void 0,
4252
5492
  [fromCardId, bankAccounts]
4253
5493
  );
4254
5494
  const cardLast4 = (fromCard?.accountNumber ?? "").replace(/\D/g, "").slice(-4);
4255
5495
  const nomeJaTemOsDigitos = !!cardLast4 && (fromCard?.name ?? "").replace(/\D/g, "").endsWith(cardLast4);
4256
- const cardMethodName = React23__default.useMemo(() => {
5496
+ const cardMethodName = React25__default.useMemo(() => {
4257
5497
  const id = installments[0]?.payMethodTypeId;
4258
5498
  const escolhida = id ? paymentMethodTypes.find((tp) => tp.id === id) : void 0;
4259
5499
  return (escolhida ?? paymentMethodTypes.find((tp) => tp.transactionType === "credit_card"))?.name;
@@ -4505,7 +5745,12 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4505
5745
  dirty,
4506
5746
  // Criar ou editar: a barra é o caminho de salvar, e sumir porque nada mudou
4507
5747
  // ainda deixa a tela sem botão. Ela some quando o formulário fecha.
4508
- alwaysVisible: true,
5748
+ //
5749
+ // EMBUTIDO é o contrário: o modal já tem o próprio fechar, então a barra não
5750
+ // é o único caminho de saída — e «Alterações não salvas» sobre um formulário
5751
+ // que ninguém tocou é uma mentira que ensina a ignorar o aviso. Ali ela
5752
+ // aparece quando há o que salvar.
5753
+ alwaysVisible: !embedded,
4509
5754
  saving,
4510
5755
  onSave: () => {
4511
5756
  void handleSave();
@@ -4515,8 +5760,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4515
5760
  });
4516
5761
  const title = isEdit ? t2("financial.invoiceForm.editTitle") : t2("financial.invoiceForm.newTitle");
4517
5762
  const subtitle = isEdit ? t2("financial.invoiceForm.editing") : t2("financial.invoiceForm.creating");
4518
- if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, { parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"), onBack: () => onSaved?.() });
4519
- const money3 = (v) => formatCurrency(v, currency);
5763
+ if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, { parentLabel: embedded ? void 0 : direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"), onBack: embedded ? void 0 : () => onSaved?.() });
5764
+ const money4 = (v) => formatCurrency(v, currency);
4520
5765
  const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
4521
5766
  /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
4522
5767
  menuOpen && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute right-0 top-full z-20 mt-1 w-44 rounded-lg border bg-popover py-1 shadow-md", style: { animation: "field-slide-in 150ms ease-out" }, children: /* @__PURE__ */ jsxs(
@@ -4569,7 +5814,6 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4569
5814
  const saved = savedMovements[i];
4570
5815
  const isPaid2 = saved?.status === "paid";
4571
5816
  const isPartial = saved?.status === "partial";
4572
- const StatusIcon = isPaid2 ? CircleCheckBig : CircleDashed;
4573
5817
  const canAttach = isEdit ? !!saved && !isPaid2 : true;
4574
5818
  const semFonte = Math.abs(inst.amount) < 5e-3 && !isPaid2 && !isPartial;
4575
5819
  const fonteTravada = isPaid2 || isPartial;
@@ -4577,75 +5821,61 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4577
5821
  const contaEscolhida = bankAccounts.find((a) => a.id === inst.payAccountId);
4578
5822
  const faltaParaPagar = !inst.payMethodTypeId ? t2("financial.payment.methodRequired") : !inst.payAccountId ? t2("financial.payment.accountRequired") : null;
4579
5823
  return /* @__PURE__ */ jsxs("div", { children: [
4580
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 px-3 py-2", children: [
4581
- /* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
4582
- "#",
4583
- i + 1
4584
- ] }),
4585
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money3(inst.amount) }),
4586
- 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 ${isPaid2 ? "bg-success-soft text-success-soft-foreground" : isPartial ? "bg-warning-soft text-warning-soft-foreground" : "bg-muted text-muted-foreground"}`, children: [
4587
- /* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5" }),
4588
- t2(isPaid2 ? "financial.invoice.statusPaid" : isPartial ? "financial.invoice.statusPartial" : "financial.invoice.statusPending")
4589
- ] }),
4590
- semFonte ? (
4591
- // Parcela zerada (brinde, 100% de desconto) não sai por forma
4592
- // nenhuma — pedir a forma ali só confunde.
4593
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: "\u2014" })
4594
- ) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
4595
- InstallmentSourceFields,
4596
- {
4597
- direction,
4598
- value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
4599
- onChange: (next) => patch(next)
4600
- }
4601
- ),
4602
- /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [
4603
- !hasPayments ? /* @__PURE__ */ jsx(
4604
- DatePicker,
4605
- {
4606
- value: inst.dueDate,
4607
- variant: "inline",
4608
- format: "numeric",
4609
- onChange: (v) => patch({ dueDate: v })
4610
- }
4611
- ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
4612
- canAttach && /* @__PURE__ */ jsx(
4613
- "button",
5824
+ /* @__PURE__ */ jsx(
5825
+ InstallmentRow,
5826
+ {
5827
+ number: i + 1,
5828
+ amount: money4(inst.amount),
5829
+ status: saved?.status,
5830
+ dueDate: inst.dueDate,
5831
+ onDueDateChange: !hasPayments ? (v) => patch({ dueDate: v }) : void 0,
5832
+ source: semFonte ? (
5833
+ // A zero-value line (gift, 100% discount) has no method to ask for.
5834
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" })
5835
+ ) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
5836
+ InstallmentSourceFields,
4614
5837
  {
4615
- type: "button",
4616
- onClick: () => {
4617
- if (!inst.receiptUrl && pendingReceipt) {
4618
- patch({ receiptUrl: pendingReceipt });
4619
- setPendingReceipt(void 0);
4620
- return;
4621
- }
4622
- setReceiptTarget(i);
4623
- receiptPicker.open();
4624
- },
4625
- title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
4626
- "aria-label": !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
4627
- 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"}`,
4628
- children: inst.receiptUrl ? /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Camera, { className: "h-3.5 w-3.5" })
5838
+ direction,
5839
+ value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
5840
+ onChange: (next) => patch(next)
4629
5841
  }
4630
5842
  ),
4631
- !isPaid2 && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsxs(
4632
- Button,
4633
- {
4634
- size: "sm",
4635
- variant: "outline",
4636
- disabled: saving || !!faltaParaPagar,
4637
- title: faltaParaPagar ?? void 0,
4638
- onClick: () => {
4639
- void pagarParcela(i);
4640
- },
4641
- children: [
4642
- /* @__PURE__ */ jsx(DollarSign, { className: "mr-1 h-3.5 w-3.5" }),
4643
- t2("financial.invoice.pay")
4644
- ]
4645
- }
4646
- )
4647
- ] })
4648
- ] }),
5843
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
5844
+ (canAttach || isEdit) && /* @__PURE__ */ jsx(
5845
+ InstallmentReceiptButton,
5846
+ {
5847
+ attached: !!inst.receiptUrl,
5848
+ pending: !inst.receiptUrl && !!pendingReceipt,
5849
+ title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
5850
+ onClick: () => {
5851
+ if (isEdit && editId) {
5852
+ setAttachmentsOpen(true);
5853
+ return;
5854
+ }
5855
+ if (!inst.receiptUrl && pendingReceipt) {
5856
+ patch({ receiptUrl: pendingReceipt });
5857
+ setPendingReceipt(void 0);
5858
+ return;
5859
+ }
5860
+ setReceiptTarget(i);
5861
+ receiptPicker.open();
5862
+ }
5863
+ }
5864
+ ),
5865
+ !isPaid2 && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsx(
5866
+ InstallmentPayButton,
5867
+ {
5868
+ disabled: saving || !!faltaParaPagar,
5869
+ title: faltaParaPagar ?? void 0,
5870
+ onClick: () => {
5871
+ void pagarParcela(i);
5872
+ },
5873
+ children: t2("financial.invoice.pay")
5874
+ }
5875
+ )
5876
+ ] })
5877
+ }
5878
+ ),
4649
5879
  !isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
4650
5880
  /* @__PURE__ */ jsx(
4651
5881
  "img",
@@ -4815,10 +6045,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4815
6045
  /* @__PURE__ */ jsxs(
4816
6046
  OrderFormView,
4817
6047
  {
6048
+ embedded,
4818
6049
  title,
4819
6050
  subtitle,
4820
- parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
4821
- onBack: () => onSaved?.(),
6051
+ parentLabel: embedded ? void 0 : direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
6052
+ onBack: embedded ? void 0 : () => onSaved?.(),
4822
6053
  actions,
4823
6054
  banner: isEdit && hasPayments ? /* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning-soft px-4 py-2.5 text-xs text-warning-soft-foreground", children: t2("financial.invoiceForm.issuedLocked") }) : void 0,
4824
6055
  sideSections,
@@ -4836,7 +6067,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4836
6067
  count: items.length,
4837
6068
  countLabel: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
4838
6069
  totalLabel: t2("financial.invoiceForm.total"),
4839
- total: money3(totalAmount),
6070
+ total: money4(totalAmount),
4840
6071
  emptyLabel: contactId ? t2("financial.invoiceForm.noItems") : t2("financial.invoiceForm.selectContactFirst"),
4841
6072
  emptyActionLabel: t2("financial.invoiceForm.addFirstItem"),
4842
6073
  header: addingItem && /* @__PURE__ */ jsx(
@@ -4862,11 +6093,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4862
6093
  referenceId: item.referenceId,
4863
6094
  itemTypes,
4864
6095
  currency,
4865
- formatMoney: money3,
6096
+ formatMoney: money4,
4866
6097
  expanded: expandedItemId === item._id,
4867
6098
  readOnly: !canEditItems,
4868
6099
  onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
4869
- onChange: (patch) => updateItem(item._id, patch),
6100
+ onChange: (patch) => {
6101
+ void applyItemPatch(item, patch);
6102
+ },
4870
6103
  onRemove: () => {
4871
6104
  removeItem(item._id);
4872
6105
  if (expandedItemId === item._id) setExpandedItemId(null);
@@ -4927,6 +6160,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4927
6160
  }] : []
4928
6161
  ],
4929
6162
  entityLookups,
6163
+ pickOnly: Boolean(entityLookups[item.itemKind]?.expand) && !item.referenceId,
4930
6164
  showTypeSelector: hasItemLookups,
4931
6165
  allowSurcharge: true,
4932
6166
  labels: {
@@ -4953,6 +6187,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4953
6187
  }
4954
6188
  ),
4955
6189
  receiptPicker.input,
6190
+ attachmentsOpen && editId && /* @__PURE__ */ jsx(InvoiceAttachmentsModal, { invoiceId: editId, onClose: () => setAttachmentsOpen(false) }),
4956
6191
  payingMovement && /* @__PURE__ */ jsx(
4957
6192
  PaymentModal,
4958
6193
  {
@@ -4982,6 +6217,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4982
6217
  setSavedMovements(
4983
6218
  movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
4984
6219
  );
6220
+ onChanged?.();
4985
6221
  })();
4986
6222
  }
4987
6223
  }
@@ -5047,6 +6283,11 @@ function groupPaymentsByBill(bills, payments) {
5047
6283
  }
5048
6284
  return { byBillId, unattributed };
5049
6285
  }
6286
+ var reversalOf = (m) => m.metadata?.reversal_of;
6287
+ function reversiblePayments(payments) {
6288
+ const reversed = new Set(payments.map(reversalOf).filter(Boolean));
6289
+ return payments.filter((m) => !reversalOf(m) && !reversed.has(m.id));
6290
+ }
5050
6291
 
5051
6292
  // src/lib/payment-how.ts
5052
6293
  var NOT_A_HOLDER = /* @__PURE__ */ new Set(["person", "system"]);
@@ -5094,6 +6335,7 @@ function PaymentReceiptModal({
5094
6335
  const { currency } = useFinancialConfig();
5095
6336
  const fmt = (n) => formatCurrency(n, currency);
5096
6337
  const ordenados = [...payments].sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
6338
+ const estornaveis = new Set(reversiblePayments(ordenados).map((m) => m.id));
5097
6339
  const total = ordenados.reduce((soma, m) => soma + (ehEstorno(m) ? -1 : 1) * (m.paidAmount || m.amount), 0);
5098
6340
  return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
5099
6341
  if (!o) onClose();
@@ -5133,7 +6375,7 @@ function PaymentReceiptModal({
5133
6375
  ] }),
5134
6376
  /* @__PURE__ */ jsx("span", { className: "text-xs", children: payment.notes })
5135
6377
  ] }),
5136
- onReverse && !estorno && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
6378
+ onReverse && estornaveis.has(payment.id) && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
5137
6379
  /* @__PURE__ */ jsx(Hint, { label: t2("financial.paymentReceipt.unlinkUnavailable"), children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "w-full", disabled: true, children: [
5138
6380
  /* @__PURE__ */ jsx(Unlink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
5139
6381
  t2("financial.paymentReceipt.unlink")
@@ -5165,14 +6407,6 @@ var ITEM_ICONS = {
5165
6407
  gift_card: Gift,
5166
6408
  other: Receipt
5167
6409
  };
5168
- var HOW_ICONS = {
5169
- cash: Banknote,
5170
- pix: QrCode,
5171
- credit_card: CreditCard,
5172
- debit_card: CreditCard,
5173
- bank_transfer: ArrowRightLeft,
5174
- check: FileCheck
5175
- };
5176
6410
  var STATUS_COLORS = {
5177
6411
  open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
5178
6412
  pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
@@ -5206,7 +6440,6 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5206
6440
  const unitName = useUnitStore((st) => unitMode && invoice?.unitId ? st.byId[invoice.unitId]?.name : void 0);
5207
6441
  const [items, setItems] = useState([]);
5208
6442
  const [attachments, setAttachments] = useState([]);
5209
- const [viewing, setViewing] = useState(null);
5210
6443
  const [movements, setMovements] = useState([]);
5211
6444
  const [payments, setPayments] = useState([]);
5212
6445
  const [holders, setHolders] = useState(/* @__PURE__ */ new Map());
@@ -5216,13 +6449,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5216
6449
  const [receiptBill, setReceiptBill] = useState(null);
5217
6450
  const [reverseReason, setReverseReason] = useState("");
5218
6451
  const [payingMovement, setPayingMovement] = useState(null);
5219
- const [receiptFor, setReceiptFor] = useState(null);
6452
+ const [attachmentsOpen, setAttachmentsOpen] = useState(false);
5220
6453
  const [pendingReceipt, setPendingReceipt] = useState();
5221
- const receiptPicker = useReceiptPicker((dataUrl) => {
5222
- setPendingReceipt(dataUrl);
5223
- setPayingMovement(receiptFor);
5224
- setReceiptFor(null);
5225
- });
5226
6454
  const [menuOpen, setMenuOpen] = useState(false);
5227
6455
  const [confirmCancel, setConfirmCancel] = useState(false);
5228
6456
  const [cancelling, setCancelling] = useState(false);
@@ -5253,6 +6481,17 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5253
6481
  void loadData();
5254
6482
  }
5255
6483
  }
6484
+ async function patchInvoiceDate(invoiceDate) {
6485
+ if (!invoice || !invoiceDate || invoiceDate === invoice.invoiceDate) return;
6486
+ const antes = invoice;
6487
+ setInvoice({ ...invoice, invoiceDate });
6488
+ try {
6489
+ await provider.updateInvoice(invoice.id, { invoiceDate });
6490
+ } catch (e) {
6491
+ setInvoice(antes);
6492
+ toast.error(e?.message ?? String(e));
6493
+ }
6494
+ }
5256
6495
  const reqRef = useRef(0);
5257
6496
  async function loadData() {
5258
6497
  const req = ++reqRef.current;
@@ -5343,8 +6582,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5343
6582
  setCancelling(false);
5344
6583
  }
5345
6584
  }
5346
- const [chartNames, setChartNames] = React23__default.useState(/* @__PURE__ */ new Map());
5347
- React23__default.useEffect(() => {
6585
+ const [chartNames, setChartNames] = React25__default.useState(/* @__PURE__ */ new Map());
6586
+ React25__default.useEffect(() => {
5348
6587
  let cancelled2 = false;
5349
6588
  void provider.getChartOfAccounts().then((rows) => {
5350
6589
  if (!cancelled2) setChartNames(new Map(rows.map((r) => [r.id, r.name])));
@@ -5464,7 +6703,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5464
6703
  { id: "total", label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
5465
6704
  { id: "items", label: t2("financial.invoice.items"), value: items.length, icon: LayoutList },
5466
6705
  { id: "installments", label: t2("financial.invoice.installments"), value: `${invoice.totalInstallments}x`, icon: Layers },
5467
- { id: "date", label: t2("financial.invoice.date"), value: invoice.invoiceDate, icon: CalendarDays }
6706
+ {
6707
+ id: "date",
6708
+ label: t2("financial.invoice.date"),
6709
+ icon: CalendarDays,
6710
+ value: invoice.status !== "cancelled" ? /* @__PURE__ */ jsx(DatePicker, { value: invoice.invoiceDate, variant: "inline", format: "numeric", onChange: (v) => {
6711
+ void patchInvoiceDate(v);
6712
+ } }) : invoice.invoiceDate
6713
+ }
5468
6714
  ],
5469
6715
  customerLabel: direction === "debit" ? t2("financial.invoice.payTo") : t2("financial.invoice.receiveFrom"),
5470
6716
  unit: unitName ? { label: t2("financial.invoice.unit"), value: unitName } : void 0,
@@ -5482,7 +6728,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5482
6728
  "button",
5483
6729
  {
5484
6730
  type: "button",
5485
- onClick: () => setViewing(0),
6731
+ onClick: () => setAttachmentsOpen(true),
5486
6732
  title: attachments.map((a) => a.fileName).join("\n"),
5487
6733
  className: "mb-2 inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
5488
6734
  children: [
@@ -5497,14 +6743,12 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5497
6743
  const isPaid2 = mov.status === "paid";
5498
6744
  const isPartial = mov.status === "partial";
5499
6745
  const hasPaidDetails = isPaid2 || isPartial;
5500
- const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
5501
- const MovIcon = movStatus.icon;
5502
6746
  const instPayments = settlements.byBillId.get(mov.id) ?? [];
5503
6747
  const hows = instPayments.map((payment) => describePayment(payment, howCtx));
5504
6748
  const paymentSummary = [...new Set(hows.map((how2) => [how2.method, how2.account?.name].filter(Boolean).join(" \xB7 ")).filter(Boolean))].join(" / ");
5505
6749
  const how = hows[0];
5506
6750
  const howCard = how?.account?.accountType === "card_payable" ? how.account : void 0;
5507
- const HowIcon = HOW_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
6751
+ const HowIcon = METHOD_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
5508
6752
  const srcMeta = mov.metadata ?? {};
5509
6753
  const srcStr = (v) => typeof v === "string" && v ? v : void 0;
5510
6754
  const srcTypeId = srcStr(srcMeta.payMethodTypeId);
@@ -5512,66 +6756,41 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5512
6756
  const editavel = !hasPaidDetails && invoice.status !== "cancelled";
5513
6757
  const canChooseSource = editavel && Math.abs(mov.amount) >= 5e-3;
5514
6758
  const faltaParaPagar = !srcTypeId ? t2("financial.payment.methodRequired") : !srcAccountId ? t2("financial.payment.accountRequired") : null;
5515
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
5516
- "div",
6759
+ const ativa = invoice.status !== "cancelled";
6760
+ return /* @__PURE__ */ jsx(
6761
+ InstallmentRow,
5517
6762
  {
5518
- className: `flex items-center gap-3 px-4 py-3 ${hasPaidDetails ? "cursor-pointer hover:bg-muted/20 transition-colors" : ""}`,
6763
+ number: mov.installmentNumber ?? 1,
6764
+ amount: formatCurrency(mov.amount, currency),
6765
+ status: mov.status,
6766
+ dueDate: mov.dueDate,
6767
+ onDueDateChange: editavel ? (v) => {
6768
+ void patchDueDate(mov, v);
6769
+ } : void 0,
6770
+ dueExtra: /* @__PURE__ */ jsxs(Fragment, { children: [
6771
+ mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
6772
+ mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
6773
+ ] }),
5519
6774
  onClick: hasPaidDetails ? () => setReceiptBill(mov) : void 0,
5520
- children: [
5521
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground w-6 shrink-0", children: [
5522
- "#",
5523
- mov.installmentNumber
5524
- ] }),
5525
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-0.5", children: [
5526
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: formatCurrency(mov.amount, currency) }),
5527
- /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${movStatus.bg}`, children: [
5528
- /* @__PURE__ */ jsx(MovIcon, { className: "h-2 w-2" }),
5529
- " ",
5530
- t2(movStatus.labelKey)
5531
- ] }),
5532
- /* @__PURE__ */ jsxs(
5533
- "span",
5534
- {
5535
- className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground",
5536
- onClick: (e) => e.stopPropagation(),
5537
- children: [
5538
- t2("financial.payment.due"),
5539
- editavel ? /* @__PURE__ */ jsx(
5540
- DatePicker,
5541
- {
5542
- value: mov.dueDate,
5543
- variant: "inline",
5544
- format: "numeric",
5545
- onChange: (v) => {
5546
- void patchDueDate(mov, v);
5547
- }
5548
- }
5549
- ) : mov.dueDate,
5550
- mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
5551
- mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
5552
- ]
5553
- }
5554
- )
5555
- ] }),
5556
- canChooseSource && /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
5557
- InstallmentSourceFields,
5558
- {
5559
- direction,
5560
- value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
5561
- onChange: (next) => {
5562
- void patchSource(mov, {
5563
- payMethodTypeId: next.payMethodTypeId ?? null,
5564
- payAccountId: next.payAccountId ?? null
5565
- });
5566
- }
6775
+ source: canChooseSource ? /* @__PURE__ */ jsx(
6776
+ InstallmentSourceFields,
6777
+ {
6778
+ direction,
6779
+ value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
6780
+ onChange: (next) => {
6781
+ void patchSource(mov, {
6782
+ payMethodTypeId: next.payMethodTypeId ?? null,
6783
+ payAccountId: next.payAccountId ?? null
6784
+ });
5567
6785
  }
5568
- ) }),
6786
+ }
6787
+ ) : void 0,
6788
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
5569
6789
  paymentSummary && /* @__PURE__ */ jsx(
5570
6790
  "button",
5571
6791
  {
5572
6792
  type: "button",
5573
- onClick: (e) => {
5574
- e.stopPropagation();
6793
+ onClick: () => {
5575
6794
  if (hasPaidDetails) setReceiptBill(mov);
5576
6795
  },
5577
6796
  title: paymentSummary,
@@ -5580,72 +6799,38 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5580
6799
  children: howCard ? /* @__PURE__ */ jsx(CardVisual, { lastFour: howCard.lastFour ?? void 0, color: howCard.color ?? void 0, brandId: howCard.cardBrandId ?? void 0 }) : HowIcon ? /* @__PURE__ */ jsx(HowIcon, { className: "h-4 w-4", "aria-hidden": true }) : null
5581
6800
  }
5582
6801
  ),
5583
- !isPaid2 && invoice.status !== "cancelled" && /* @__PURE__ */ jsx(
5584
- "button",
6802
+ /* @__PURE__ */ jsx(
6803
+ InstallmentReceiptButton,
5585
6804
  {
5586
- onClick: (e) => {
5587
- e.stopPropagation();
5588
- setReceiptFor(mov);
5589
- receiptPicker.open();
5590
- },
5591
- title: t2("financial.quickTx.receiptCapture"),
5592
- "aria-label": t2("financial.quickTx.receiptCapture"),
5593
- className: "inline-flex shrink-0 items-center gap-1 rounded-lg border bg-card px-2.5 py-1.5 text-xs font-medium shadow-button transition-colors hover:bg-muted active:shadow-button-inset",
5594
- children: /* @__PURE__ */ jsx(Camera, { className: "h-3 w-3" })
6805
+ title: "Anexos",
6806
+ attached: attachments.length > 0,
6807
+ count: attachments.length,
6808
+ onClick: () => setAttachmentsOpen(true)
5595
6809
  }
5596
6810
  ),
5597
- !isPaid2 && !isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
5598
- "button",
6811
+ !isPaid2 && !isPartial && ativa && /* @__PURE__ */ jsxs(
6812
+ InstallmentPayButton,
5599
6813
  {
5600
- onClick: (e) => {
5601
- e.stopPropagation();
5602
- setPayingMovement(mov);
5603
- },
6814
+ onClick: () => setPayingMovement(mov),
5604
6815
  disabled: !!faltaParaPagar,
5605
6816
  title: faltaParaPagar ?? void 0,
5606
- className: "inline-flex items-center gap-1 rounded-button bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0 disabled:opacity-40 disabled:pointer-events-none",
5607
6817
  children: [
5608
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
5609
6818
  t2("financial.invoice.pay"),
5610
6819
  " ",
5611
6820
  remaining < mov.amount ? formatCurrency(remaining, currency) : ""
5612
6821
  ]
5613
6822
  }
5614
6823
  ),
5615
- isPaid2 && instPayments.length > 0 && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
5616
- "button",
5617
- {
5618
- onClick: (e) => {
5619
- e.stopPropagation();
5620
- setReversing(instPayments[instPayments.length - 1]);
5621
- },
5622
- className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
5623
- children: [
5624
- /* @__PURE__ */ jsx(Undo2, { className: "h-3 w-3" }),
5625
- t2("financial.invoice.reverse")
5626
- ]
5627
- }
5628
- ),
5629
- isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
5630
- "button",
5631
- {
5632
- onClick: (e) => {
5633
- e.stopPropagation();
5634
- setPayingMovement(mov);
5635
- },
5636
- className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
5637
- children: [
5638
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
5639
- t2("financial.invoice.pay"),
5640
- " ",
5641
- formatCurrency(remaining, currency)
5642
- ]
5643
- }
5644
- ),
5645
- hasPaidDetails && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
5646
- ]
5647
- }
5648
- ) }, mov.id);
6824
+ isPaid2 && reversiblePayments(instPayments).length > 0 && ativa && /* @__PURE__ */ jsx(InstallmentActionButton, { icon: Undo2, onClick: () => setReversing(reversiblePayments(instPayments).at(-1)), children: t2("financial.invoice.reverse") }),
6825
+ isPartial && ativa && /* @__PURE__ */ jsxs(InstallmentPayButton, { variant: "outline", onClick: () => setPayingMovement(mov), children: [
6826
+ t2("financial.invoice.pay"),
6827
+ " ",
6828
+ formatCurrency(remaining, currency)
6829
+ ] })
6830
+ ] })
6831
+ },
6832
+ mov.id
6833
+ );
5649
6834
  }) }),
5650
6835
  settlements.unattributed.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t bg-warning-soft/40 px-4 py-2.5", children: [
5651
6836
  /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground", children: t2("financial.invoice.unlinkedPayments") }),
@@ -5664,97 +6849,15 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5664
6849
  extraSections: sideBlocks
5665
6850
  }
5666
6851
  ),
5667
- receiptPicker.input,
5668
- viewing !== null && attachments[viewing] && createPortal(
5669
- (() => {
5670
- const doc = attachments[viewing];
5671
- const ehImagem = (doc.mimeType ?? "").startsWith("image/");
5672
- const ehPdf = (doc.mimeType ?? "") === "application/pdf";
5673
- const irPara = (delta) => setViewing((i) => i === null ? null : (i + delta + attachments.length) % attachments.length);
5674
- return /* @__PURE__ */ jsx(
5675
- "div",
5676
- {
5677
- "data-modal-passthrough": true,
5678
- className: "pointer-events-auto fixed inset-0 z-[200] flex flex-col bg-black/80 p-4",
5679
- onClick: () => setViewing(null),
5680
- role: "dialog",
5681
- "aria-modal": "true",
5682
- "aria-label": doc.fileName,
5683
- children: /* @__PURE__ */ jsxs(
5684
- "div",
5685
- {
5686
- className: "mx-auto flex w-full max-w-4xl flex-1 flex-col gap-3 overflow-hidden",
5687
- onClick: (e) => e.stopPropagation(),
5688
- children: [
5689
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-white", children: [
5690
- /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 shrink-0 opacity-70", "aria-hidden": true }),
5691
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
5692
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", title: doc.fileName, children: doc.fileName }),
5693
- /* @__PURE__ */ jsxs("p", { className: "text-[11px] opacity-60", children: [
5694
- attachments.length > 1 ? `${viewing + 1} de ${attachments.length}` : null,
5695
- doc.fileSize ? `${attachments.length > 1 ? " \xB7 " : ""}${Math.round(doc.fileSize / 1024)} KB` : null
5696
- ] })
5697
- ] }),
5698
- doc.url ? /* @__PURE__ */ jsxs(
5699
- "a",
5700
- {
5701
- href: doc.url,
5702
- target: "_blank",
5703
- rel: "noreferrer",
5704
- className: "inline-flex shrink-0 items-center gap-1 rounded-lg border border-white/20 px-2.5 py-1.5 text-xs font-medium text-white hover:bg-white/10",
5705
- children: [
5706
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
5707
- " Abrir"
5708
- ]
5709
- }
5710
- ) : null,
5711
- /* @__PURE__ */ jsx(
5712
- "button",
5713
- {
5714
- type: "button",
5715
- onClick: () => setViewing(null),
5716
- "aria-label": "Fechar",
5717
- className: "shrink-0 rounded-lg border border-white/20 px-2.5 py-1.5 text-xs font-medium text-white hover:bg-white/10",
5718
- children: "Fechar"
5719
- }
5720
- )
5721
- ] }),
5722
- /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center rounded-xl bg-white/5", children: doc.pending || !doc.url ? /* @__PURE__ */ jsxs("p", { className: "max-w-sm p-8 text-center text-sm text-white/70", children: [
5723
- "O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por ",
5724
- /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
5725
- "."
5726
- ] }) : ehImagem ? /* @__PURE__ */ jsx("img", { src: doc.url, alt: doc.fileName, className: "max-h-full max-w-full object-contain" }) : ehPdf ? /* @__PURE__ */ jsx("iframe", { src: doc.url, title: doc.fileName, className: "h-full w-full rounded-xl bg-white" }) : /* @__PURE__ */ jsxs("p", { className: "p-8 text-center text-sm text-white/70", children: [
5727
- "Este tipo de arquivo n\xE3o tem pr\xE9-visualiza\xE7\xE3o. Use ",
5728
- /* @__PURE__ */ jsx("strong", { children: "Abrir" }),
5729
- "."
5730
- ] }) }),
5731
- attachments.length > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
5732
- /* @__PURE__ */ jsx(
5733
- "button",
5734
- {
5735
- type: "button",
5736
- onClick: () => irPara(-1),
5737
- className: "rounded-lg border border-white/20 px-3 py-1.5 text-xs font-medium text-white hover:bg-white/10",
5738
- children: "Anterior"
5739
- }
5740
- ),
5741
- /* @__PURE__ */ jsx(
5742
- "button",
5743
- {
5744
- type: "button",
5745
- onClick: () => irPara(1),
5746
- className: "rounded-lg border border-white/20 px-3 py-1.5 text-xs font-medium text-white hover:bg-white/10",
5747
- children: "Pr\xF3ximo"
5748
- }
5749
- )
5750
- ] })
5751
- ]
5752
- }
5753
- )
5754
- }
5755
- );
5756
- })(),
5757
- document.body
6852
+ attachmentsOpen && /* @__PURE__ */ jsx(
6853
+ InvoiceAttachmentsModal,
6854
+ {
6855
+ invoiceId: invoice.id,
6856
+ onClose: () => setAttachmentsOpen(false),
6857
+ onChanged: () => {
6858
+ void loadData();
6859
+ }
6860
+ }
5758
6861
  ),
5759
6862
  receiptBill && /* @__PURE__ */ jsx(
5760
6863
  PaymentReceiptModal,
@@ -5924,7 +7027,7 @@ function PayablesView({ intent, onNavigate, previousView, back }) {
5924
7027
  direction: "debit",
5925
7028
  onNew: () => onNavigate("payables-new"),
5926
7029
  onNewReceipt: () => onNavigate("payables-new-receipt"),
5927
- onEdit: (id) => onNavigate(`payables-detail:${id}`)
7030
+ onEdit: (id, status) => onNavigate(`payables-${invoiceOpensForEditing(status) ? "edit" : "detail"}:${id}`)
5928
7031
  }
5929
7032
  );
5930
7033
  }
@@ -5958,7 +7061,7 @@ function ReceivablesView({ intent, onNavigate, previousView, back }) {
5958
7061
  direction: "credit",
5959
7062
  onNew: () => onNavigate("receivables-new"),
5960
7063
  onNewReceipt: () => onNavigate("receivables-new-receipt"),
5961
- onEdit: (id) => onNavigate(`receivables-detail:${id}`)
7064
+ onEdit: (id, status) => onNavigate(`receivables-${invoiceOpensForEditing(status) ? "edit" : "detail"}:${id}`)
5962
7065
  }
5963
7066
  );
5964
7067
  }
@@ -6476,7 +7579,7 @@ function CashRegistersView({ variant = "page" } = {}) {
6476
7579
  const [tab, setTab] = useState("open");
6477
7580
  const [reviewRows, setReviewRows] = useState(null);
6478
7581
  const [reviewError, setReviewError] = useState(null);
6479
- const loadReviews = React23__default.useCallback(async () => {
7582
+ const loadReviews = React25__default.useCallback(async () => {
6480
7583
  try {
6481
7584
  setReviewError(null);
6482
7585
  setReviewRows(await provider.getCashSessionReviews?.() ?? []);
@@ -7076,7 +8179,7 @@ function StatementsView({ onNavigate }) {
7076
8179
  "extrato"
7077
8180
  );
7078
8181
  };
7079
- const headerActions = React23__default.useMemo(() => [{
8182
+ const headerActions = React25__default.useMemo(() => [{
7080
8183
  id: "new-transfer",
7081
8184
  label: t2("financial.statements.newTransfer"),
7082
8185
  icon: ArrowRightLeft,
@@ -7255,7 +8358,7 @@ function firstOfMonth() {
7255
8358
  function today2() {
7256
8359
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7257
8360
  }
7258
- function money(v) {
8361
+ function money2(v) {
7259
8362
  return v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
7260
8363
  }
7261
8364
  function Step({ n, label, done, state }) {
@@ -7272,15 +8375,15 @@ function CommissionsView() {
7272
8375
  const fetchCommissions = useFinancialStore((s) => s.fetchCommissions);
7273
8376
  const generateCommissions = useFinancialStore((s) => s.generateCommissions);
7274
8377
  const provider = useFinancialProvider();
7275
- const [from, setFrom] = React23__default.useState(firstOfMonth());
7276
- const [to, setTo] = React23__default.useState(today2());
7277
- const [preset, setPreset] = React23__default.useState("this_month");
7278
- const [running, setRunning] = React23__default.useState(false);
7279
- const [lastRun, setLastRun] = React23__default.useState(null);
7280
- const [generation, setGeneration] = React23__default.useState(null);
7281
- const [busy, setBusy] = React23__default.useState(null);
7282
- const [error, setError] = React23__default.useState(null);
7283
- const loadGeneration = React23__default.useCallback(async () => {
8378
+ const [from, setFrom] = React25__default.useState(firstOfMonth());
8379
+ const [to, setTo] = React25__default.useState(today2());
8380
+ const [preset, setPreset] = React25__default.useState("this_month");
8381
+ const [running, setRunning] = React25__default.useState(false);
8382
+ const [lastRun, setLastRun] = React25__default.useState(null);
8383
+ const [generation, setGeneration] = React25__default.useState(null);
8384
+ const [busy, setBusy] = React25__default.useState(null);
8385
+ const [error, setError] = React25__default.useState(null);
8386
+ const loadGeneration = React25__default.useCallback(async () => {
7284
8387
  try {
7285
8388
  const gens = await provider.getCommissionGenerations?.() ?? [];
7286
8389
  const doPeriodo = gens.filter((g) => g.periodStart === from && g.periodEnd === to);
@@ -7288,11 +8391,11 @@ function CommissionsView() {
7288
8391
  } catch {
7289
8392
  }
7290
8393
  }, [provider, from, to]);
7291
- React23__default.useEffect(() => {
8394
+ React25__default.useEffect(() => {
7292
8395
  void fetchCommissions({ periodStart: from, periodEnd: to });
7293
8396
  void loadGeneration();
7294
8397
  }, [fetchCommissions, loadGeneration, from, to]);
7295
- const byPerson = React23__default.useMemo(() => {
8398
+ const byPerson = React25__default.useMemo(() => {
7296
8399
  const map = /* @__PURE__ */ new Map();
7297
8400
  for (const c of commissions2) {
7298
8401
  const key = c.personId;
@@ -7309,7 +8412,7 @@ function CommissionsView() {
7309
8412
  setError(null);
7310
8413
  try {
7311
8414
  const r = await generateCommissions({ periodStart: from, periodEnd: to });
7312
- setLastRun(r ? `${r.entries} lan\xE7amento(s) \xB7 ${money(r.total)}` : null);
8415
+ setLastRun(r ? `${r.entries} lan\xE7amento(s) \xB7 ${money2(r.total)}` : null);
7313
8416
  await loadGeneration();
7314
8417
  } catch (e) {
7315
8418
  setError(e?.message ?? String(e));
@@ -7337,7 +8440,7 @@ function CommissionsView() {
7337
8440
  setError(null);
7338
8441
  try {
7339
8442
  const r = await provider.payRunCommissions?.(generation.id);
7340
- setLastRun(`${r?.payables ?? 0} conta(s) a pagar \xB7 ${money(r?.total ?? 0)}`);
8443
+ setLastRun(`${r?.payables ?? 0} conta(s) a pagar \xB7 ${money2(r?.total ?? 0)}`);
7341
8444
  await Promise.all([loadGeneration(), fetchCommissions({ periodStart: from, periodEnd: to })]);
7342
8445
  } catch (e) {
7343
8446
  setError(e?.message ?? String(e));
@@ -7345,7 +8448,7 @@ function CommissionsView() {
7345
8448
  setBusy(null);
7346
8449
  }
7347
8450
  }
7348
- const headerActions = React23__default.useMemo(() => [{
8451
+ const headerActions = React25__default.useMemo(() => [{
7349
8452
  id: "commission-rules",
7350
8453
  label: "Configurar regras de comiss\xE3o",
7351
8454
  icon: Settings,
@@ -7383,7 +8486,7 @@ function CommissionsView() {
7383
8486
  lastRun && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: lastRun }),
7384
8487
  /* @__PURE__ */ jsxs("div", { className: "ml-auto text-right", children: [
7385
8488
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Total do per\xEDodo" }),
7386
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money(total) })
8489
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money2(total) })
7387
8490
  ] })
7388
8491
  ] }),
7389
8492
  generation && /* @__PURE__ */ jsxs("div", { className: "rounded-2xl border border-border bg-card px-4 py-3", children: [
@@ -7449,7 +8552,7 @@ function CommissionsView() {
7449
8552
  p.count,
7450
8553
  " lan\xE7amento(s)"
7451
8554
  ] }),
7452
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children: money(p.total) })
8555
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children: money2(p.total) })
7453
8556
  ] })
7454
8557
  ] }, id)) })
7455
8558
  ] }),
@@ -7468,9 +8571,9 @@ function CommissionsView() {
7468
8571
  /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: c.itemName ?? "\u2014" }),
7469
8572
  /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: c.invoiceNumber ?? "\u2014" }),
7470
8573
  /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: c.ruleName ?? "\u2014" }),
7471
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums text-muted-foreground", children: money(c.baseAmount) }),
8574
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums text-muted-foreground", children: money2(c.baseAmount) }),
7472
8575
  /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums text-muted-foreground", children: c.rate }),
7473
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right font-semibold tabular-nums", children: money(c.amount) })
8576
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right font-semibold tabular-nums", children: money2(c.amount) })
7474
8577
  ] }, c.id)) })
7475
8578
  ] }) })
7476
8579
  ] })
@@ -7787,19 +8890,19 @@ var MESES = [
7787
8890
  "Novembro",
7788
8891
  "Dezembro"
7789
8892
  ];
7790
- function money2(v) {
8893
+ function money3(v) {
7791
8894
  return v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
7792
8895
  }
7793
8896
  function GoalsView() {
7794
8897
  const { labels } = useFinancialConfig();
7795
8898
  const provider = useFinancialProvider();
7796
- const [units, setUnits] = React23__default.useState([]);
7797
- const [unitId, setUnitId] = React23__default.useState("");
7798
- const [year, setYear] = React23__default.useState((/* @__PURE__ */ new Date()).getFullYear());
7799
- const [rows, setRows] = React23__default.useState(null);
7800
- const [editing, setEditing] = React23__default.useState(null);
7801
- const [error, setError] = React23__default.useState(null);
7802
- React23__default.useEffect(() => {
8899
+ const [units, setUnits] = React25__default.useState([]);
8900
+ const [unitId, setUnitId] = React25__default.useState("");
8901
+ const [year, setYear] = React25__default.useState((/* @__PURE__ */ new Date()).getFullYear());
8902
+ const [rows, setRows] = React25__default.useState(null);
8903
+ const [editing, setEditing] = React25__default.useState(null);
8904
+ const [error, setError] = React25__default.useState(null);
8905
+ React25__default.useEffect(() => {
7803
8906
  void (async () => {
7804
8907
  const cat = await provider.getCommissionCatalogs?.();
7805
8908
  const us = cat?.units ?? [];
@@ -7807,7 +8910,7 @@ function GoalsView() {
7807
8910
  setUnitId((cur) => cur || us[0]?.id || "");
7808
8911
  })();
7809
8912
  }, [provider]);
7810
- const load = React23__default.useCallback(async () => {
8913
+ const load = React25__default.useCallback(async () => {
7811
8914
  if (!unitId) return;
7812
8915
  setRows(null);
7813
8916
  try {
@@ -7818,7 +8921,7 @@ function GoalsView() {
7818
8921
  setRows([]);
7819
8922
  }
7820
8923
  }, [provider, unitId, year]);
7821
- React23__default.useEffect(() => {
8924
+ React25__default.useEffect(() => {
7822
8925
  void load();
7823
8926
  }, [load]);
7824
8927
  const totalRealizado = (rows ?? []).reduce((s, r) => s + r.achievedAmount, 0);
@@ -7867,11 +8970,11 @@ function GoalsView() {
7867
8970
  ] }),
7868
8971
  /* @__PURE__ */ jsxs("div", { className: "ml-auto text-right", children: [
7869
8972
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Realizado no ano" }),
7870
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money2(totalRealizado) })
8973
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money3(totalRealizado) })
7871
8974
  ] }),
7872
8975
  totalMeta > 0 && /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
7873
8976
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Meta do ano" }),
7874
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money2(totalMeta) })
8977
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money3(totalMeta) })
7875
8978
  ] })
7876
8979
  ] }),
7877
8980
  semMeta > 0 && /* @__PURE__ */ jsxs("p", { className: "rounded-card border border-warning/30 bg-warning/5 px-4 py-2 text-xs", children: [
@@ -7900,7 +9003,7 @@ function GoalsView() {
7900
9003
  /* @__PURE__ */ jsx("td", {})
7901
9004
  ] }, i)) : rows.map((r) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 hover:bg-muted/40", children: [
7902
9005
  /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-medium", children: MESES[r.month - 1] }),
7903
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums", children: money2(r.achievedAmount) }),
9006
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums", children: money3(r.achievedAmount) }),
7904
9007
  /* @__PURE__ */ jsx(Degrau, { valor: r.targetAmount, percentual: r.targetPercent }),
7905
9008
  /* @__PURE__ */ jsx(Degrau, { valor: r.superAmount, percentual: r.superPercent }),
7906
9009
  /* @__PURE__ */ jsx(Degrau, { valor: r.hyperAmount, percentual: r.hyperPercent }),
@@ -7936,7 +9039,7 @@ function Degrau({ valor, percentual }) {
7936
9039
  }
7937
9040
  const bateu = percentual >= 100;
7938
9041
  return /* @__PURE__ */ jsxs("td", { className: "px-4 py-2 text-right", children: [
7939
- /* @__PURE__ */ jsx("div", { className: "tabular-nums", children: money2(valor) }),
9042
+ /* @__PURE__ */ jsx("div", { className: "tabular-nums", children: money3(valor) }),
7940
9043
  /* @__PURE__ */ jsxs("div", { className: `text-[10px] tabular-nums ${bateu ? "font-semibold text-success" : "text-muted-foreground"}`, children: [
7941
9044
  percentual.toFixed(2),
7942
9045
  "%"
@@ -7949,11 +9052,11 @@ function GoalDialog({
7949
9052
  onSaved
7950
9053
  }) {
7951
9054
  const provider = useFinancialProvider();
7952
- const [target, setTarget] = React23__default.useState(String(goal.targetAmount || ""));
7953
- const [sup, setSup] = React23__default.useState(String(goal.superAmount || ""));
7954
- const [hyper, setHyper] = React23__default.useState(String(goal.hyperAmount || ""));
7955
- const [saving, setSaving] = React23__default.useState(false);
7956
- const [error, setError] = React23__default.useState(null);
9055
+ const [target, setTarget] = React25__default.useState(String(goal.targetAmount || ""));
9056
+ const [sup, setSup] = React25__default.useState(String(goal.superAmount || ""));
9057
+ const [hyper, setHyper] = React25__default.useState(String(goal.hyperAmount || ""));
9058
+ const [saving, setSaving] = React25__default.useState(false);
9059
+ const [error, setError] = React25__default.useState(null);
7957
9060
  const t2 = Number(target) || 0;
7958
9061
  const s = Number(sup) || 0;
7959
9062
  const h = Number(hyper) || 0;
@@ -7991,7 +9094,7 @@ function GoalDialog({
7991
9094
  ] }),
7992
9095
  /* @__PURE__ */ jsxs(ModalDescription, { className: "text-xs", children: [
7993
9096
  "Realizado at\xE9 agora: ",
7994
- /* @__PURE__ */ jsx("strong", { children: money2(goal.achievedAmount) })
9097
+ /* @__PURE__ */ jsx("strong", { children: money3(goal.achievedAmount) })
7995
9098
  ] })
7996
9099
  ] }),
7997
9100
  /* @__PURE__ */ jsx(ModalBody, { className: "fz-container px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
@@ -8044,21 +9147,21 @@ function GiftCardsView() {
8044
9147
  const provider = useFinancialProvider();
8045
9148
  const { currency } = useFinancialConfig();
8046
9149
  const can = usePermissionOptional();
8047
- const [enabled, setEnabled] = React23__default.useState(null);
8048
- const [cards, setCards] = React23__default.useState(null);
8049
- const [error, setError] = React23__default.useState(null);
8050
- const [issuing, setIssuing] = React23__default.useState(false);
8051
- const [issued, setIssued] = React23__default.useState(null);
8052
- const [activating, setActivating] = React23__default.useState(null);
8053
- const [search, setSearch] = React23__default.useState("");
8054
- const [state, setState] = React23__default.useState("");
8055
- const [minValue, setMinValue] = React23__default.useState("");
8056
- const [maxValue, setMaxValue] = React23__default.useState("");
8057
- const [from, setFrom] = React23__default.useState("");
8058
- const [to, setTo] = React23__default.useState("");
8059
- const money3 = React23__default.useCallback((v) => formatCurrency(v, currency), [currency]);
8060
- const seq = React23__default.useRef(0);
8061
- const load = React23__default.useCallback(async () => {
9150
+ const [enabled, setEnabled] = React25__default.useState(null);
9151
+ const [cards, setCards] = React25__default.useState(null);
9152
+ const [error, setError] = React25__default.useState(null);
9153
+ const [issuing, setIssuing] = React25__default.useState(false);
9154
+ const [issued, setIssued] = React25__default.useState(null);
9155
+ const [activating, setActivating] = React25__default.useState(null);
9156
+ const [search, setSearch] = React25__default.useState("");
9157
+ const [state, setState] = React25__default.useState("");
9158
+ const [minValue, setMinValue] = React25__default.useState("");
9159
+ const [maxValue, setMaxValue] = React25__default.useState("");
9160
+ const [from, setFrom] = React25__default.useState("");
9161
+ const [to, setTo] = React25__default.useState("");
9162
+ const money4 = React25__default.useCallback((v) => formatCurrency(v, currency), [currency]);
9163
+ const seq = React25__default.useRef(0);
9164
+ const load = React25__default.useCallback(async () => {
8062
9165
  const mine = ++seq.current;
8063
9166
  setCards(null);
8064
9167
  setError(null);
@@ -8079,7 +9182,7 @@ function GiftCardsView() {
8079
9182
  setCards([]);
8080
9183
  }
8081
9184
  }, [provider, search, state, minValue, maxValue, from, to]);
8082
- React23__default.useEffect(() => {
9185
+ React25__default.useEffect(() => {
8083
9186
  void (async () => {
8084
9187
  try {
8085
9188
  setEnabled(await provider.giftCardsEnabled?.() === true);
@@ -8088,10 +9191,10 @@ function GiftCardsView() {
8088
9191
  }
8089
9192
  })();
8090
9193
  }, [provider]);
8091
- React23__default.useEffect(() => {
9194
+ React25__default.useEffect(() => {
8092
9195
  if (enabled) void load();
8093
9196
  }, [enabled, load]);
8094
- const columns = React23__default.useMemo(() => [
9197
+ const columns = React25__default.useMemo(() => [
8095
9198
  {
8096
9199
  accessorKey: "code",
8097
9200
  header: "C\xF3digo",
@@ -8100,12 +9203,12 @@ function GiftCardsView() {
8100
9203
  {
8101
9204
  accessorKey: "faceValue",
8102
9205
  header: "Valor",
8103
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: money3(row.original.faceValue) })
9206
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: money4(row.original.faceValue) })
8104
9207
  },
8105
9208
  {
8106
9209
  accessorKey: "balance",
8107
9210
  header: "Saldo",
8108
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: row.original.balance === null ? /* @__PURE__ */ jsx(Dash, {}) : money3(row.original.balance) })
9211
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: row.original.balance === null ? /* @__PURE__ */ jsx(Dash, {}) : money4(row.original.balance) })
8109
9212
  },
8110
9213
  {
8111
9214
  accessorKey: "state",
@@ -8145,7 +9248,7 @@ function GiftCardsView() {
8145
9248
  /* @__PURE__ */ jsx(TooltipContent, { className: "text-xs", children: "Ativar na venda \u2014 o saldo passa a valer o valor de face" })
8146
9249
  ] }) }) : null
8147
9250
  }
8148
- ], [activating, load, money3, provider]);
9251
+ ], [activating, load, money4, provider]);
8149
9252
  const outstanding = (cards ?? []).filter((c) => c.state === "active").reduce((s, c) => s + (c.balance ?? 0), 0);
8150
9253
  const faceIssued = (cards ?? []).reduce((s, c) => s + c.faceValue, 0);
8151
9254
  if (enabled === false) {
@@ -8180,11 +9283,11 @@ function GiftCardsView() {
8180
9283
  /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-6 rounded-card border bg-card px-4 py-3", children: [
8181
9284
  /* @__PURE__ */ jsxs("div", { children: [
8182
9285
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Valor de face emitido" }),
8183
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money3(faceIssued) })
9286
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money4(faceIssued) })
8184
9287
  ] }),
8185
9288
  /* @__PURE__ */ jsxs("div", { children: [
8186
9289
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Passivo em aberto" }),
8187
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money3(outstanding) }),
9290
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money4(outstanding) }),
8188
9291
  /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "s\xF3 os cart\xF5es j\xE1 vendidos" })
8189
9292
  ] })
8190
9293
  ] }),
@@ -8196,7 +9299,7 @@ function GiftCardsView() {
8196
9299
  ] }),
8197
9300
  " ",
8198
9301
  "\u2014 ",
8199
- money3(issued.total),
9302
+ money4(issued.total),
8200
9303
  " em valor de face. Eles nascem sem saldo: ative cada um na venda."
8201
9304
  ] }),
8202
9305
  error && /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-card border border-destructive/30 bg-destructive/5 px-4 py-3 text-xs text-destructive", children: error }),
@@ -8255,7 +9358,7 @@ function GiftCardsView() {
8255
9358
  issuing && /* @__PURE__ */ jsx(
8256
9359
  IssueDialog,
8257
9360
  {
8258
- money: money3,
9361
+ money: money4,
8259
9362
  onClose: () => setIssuing(false),
8260
9363
  onIssued: async (count, total) => {
8261
9364
  setIssuing(false);
@@ -8267,17 +9370,17 @@ function GiftCardsView() {
8267
9370
  ] }) });
8268
9371
  }
8269
9372
  function IssueDialog({
8270
- money: money3,
9373
+ money: money4,
8271
9374
  onClose,
8272
9375
  onIssued
8273
9376
  }) {
8274
9377
  const provider = useFinancialProvider();
8275
- const [quantity, setQuantity] = React23__default.useState("10");
8276
- const [amount, setAmount] = React23__default.useState("100");
8277
- const [expiresAt, setExpiresAt] = React23__default.useState("");
8278
- const [note, setNote] = React23__default.useState("");
8279
- const [saving, setSaving] = React23__default.useState(false);
8280
- const [error, setError] = React23__default.useState(null);
9378
+ const [quantity, setQuantity] = React25__default.useState("10");
9379
+ const [amount, setAmount] = React25__default.useState("100");
9380
+ const [expiresAt, setExpiresAt] = React25__default.useState("");
9381
+ const [note, setNote] = React25__default.useState("");
9382
+ const [saving, setSaving] = React25__default.useState(false);
9383
+ const [error, setError] = React25__default.useState(null);
8281
9384
  const q = Math.trunc(Number(quantity)) || 0;
8282
9385
  const a = Number(amount) || 0;
8283
9386
  const total = q * a;
@@ -8360,7 +9463,7 @@ function IssueDialog({
8360
9463
  ] }),
8361
9464
  /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-muted/40 px-4 py-2 text-sm", children: [
8362
9465
  "Total do lote: ",
8363
- /* @__PURE__ */ jsx("strong", { className: "tabular-nums", children: money3(total) })
9466
+ /* @__PURE__ */ jsx("strong", { className: "tabular-nums", children: money4(total) })
8364
9467
  ] }),
8365
9468
  invalid && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: invalid }),
8366
9469
  error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
@@ -8642,7 +9745,7 @@ function stripFence(text2) {
8642
9745
  const end = trimmed.lastIndexOf("}");
8643
9746
  return start >= 0 && end > start ? trimmed.slice(start, end + 1) : trimmed;
8644
9747
  }
8645
- var num = (v, fallback = 0) => {
9748
+ var num2 = (v, fallback = 0) => {
8646
9749
  const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
8647
9750
  return Number.isFinite(n) ? n : fallback;
8648
9751
  };
@@ -8661,8 +9764,8 @@ function parseReceiptReply(content) {
8661
9764
  const item = row ?? {};
8662
9765
  const description = str(item.description);
8663
9766
  if (!description) return null;
8664
- const quantity = num(item.quantity, 1) || 1;
8665
- const unitPrice = item.unitPrice != null ? num(item.unitPrice) : num(item.total) / quantity;
9767
+ const quantity = num2(item.quantity, 1) || 1;
9768
+ const unitPrice = item.unitPrice != null ? num2(item.unitPrice) : num2(item.total) / quantity;
8666
9769
  return { description, quantity, unitPrice: Math.round(unitPrice * 100) / 100 };
8667
9770
  }).filter((i) => i !== null) : [];
8668
9771
  const warnings = Array.isArray(raw.warnings) ? raw.warnings.map((w) => str(w)).filter((w) => !!w) : [];
@@ -8805,16 +9908,10 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
8805
9908
  }, [planoDoTipo]);
8806
9909
  const valorAQuitar = intent === "link" && target ? target.openAmount : round23(line.amount);
8807
9910
  const quita = intent === "bill" || intent === "link" && target?.mode === "settle";
8808
- const agentConnection = useMemo(() => resolveFayzAgentConnection(), []);
8809
9911
  const leitor = useMemo(() => {
8810
- if (!agentConnection) return null;
8811
- const client = getFayzAgentClient(agentConnection);
8812
- return createAgentReceiptReader({
8813
- chat: (input) => client.chat(input),
8814
- toInlineFile: toInlineFile,
8815
- shrinkForVision
8816
- });
8817
- }, [agentConnection]);
9912
+ const ask = createOneShotAsk();
9913
+ return ask ? createAgentReceiptReader({ chat: ask, toInlineFile: toInlineFile, shrinkForVision }) : null;
9914
+ }, []);
8818
9915
  async function lerComprovante(file) {
8819
9916
  if (!leitor) return;
8820
9917
  if (file.size > 10 * 1024 * 1024) {
@@ -8979,7 +10076,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
8979
10076
  candidatesLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
8980
10077
  /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full rounded-md" }),
8981
10078
  /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full rounded-md" })
8982
- ] }) : candidates.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: t2("financial.reconciliation.noCandidates") }) : candidates.map(({ candidate: c, mode, openAmount }) => {
10079
+ ] }) : candidates.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: t2("financial.reconciliation.noCandidates") }) : candidates.map(({ candidate: c, mode, openAmount: openAmount2 }) => {
8983
10080
  const escolhido = targetId === c.movement.id;
8984
10081
  return /* @__PURE__ */ jsxs(
8985
10082
  "button",
@@ -8994,7 +10091,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
8994
10091
  /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
8995
10092
  shortDate2(c.movement.dueDate),
8996
10093
  /* @__PURE__ */ jsx("span", { className: "ml-1.5 inline-flex items-center rounded bg-magic/10 px-1 py-px font-medium text-magic-soft-foreground", children: t2("financial.reconciliation.matchScore", { score: Math.round(c.score * 100) }) }),
8997
- /* @__PURE__ */ jsx("span", { className: `ml-1.5 inline-flex items-center rounded px-1 py-px font-medium ${mode === "settle" ? "bg-warning/10 text-warning" : "bg-success/10 text-success"}`, children: mode === "settle" ? t2("financial.reconciliation.candidateOpen", { amount: fmt(openAmount) }) : t2("financial.reconciliation.candidatePaid") })
10094
+ /* @__PURE__ */ jsx("span", { className: `ml-1.5 inline-flex items-center rounded px-1 py-px font-medium ${mode === "settle" ? "bg-warning/10 text-warning" : "bg-success/10 text-success"}`, children: mode === "settle" ? t2("financial.reconciliation.candidateOpen", { amount: fmt(openAmount2) }) : t2("financial.reconciliation.candidatePaid") })
8998
10095
  ] })
8999
10096
  ] }),
9000
10097
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold", children: fmt(c.movement.amount) })
@@ -9558,11 +10655,11 @@ function ReconciliationView() {
9558
10655
  setBusy(false);
9559
10656
  }
9560
10657
  }
9561
- const RefreshIcon = React23__default.useMemo(
10658
+ const RefreshIcon = React25__default.useMemo(
9562
10659
  () => ({ className }) => /* @__PURE__ */ jsx(RefreshCw, { className: `${className ?? ""} ${loading ? "animate-spin" : ""}` }),
9563
10660
  [loading]
9564
10661
  );
9565
- const headerActions = React23__default.useMemo(() => [{
10662
+ const headerActions = React25__default.useMemo(() => [{
9566
10663
  id: "refresh",
9567
10664
  label: t2("financial.reconciliation.refresh"),
9568
10665
  icon: RefreshIcon,
@@ -9987,13 +11084,13 @@ function RecurringBillsList({ direction, onNavigate }) {
9987
11084
  const provider = useFinancialProvider();
9988
11085
  const { currency } = useFinancialConfig();
9989
11086
  const can = usePermissionOptional();
9990
- const [rows, setRows] = React23__default.useState(null);
9991
- const [search, setSearch] = React23__default.useState("");
9992
- const [status, setStatus] = React23__default.useState("all");
9993
- const [changing, setChanging] = React23__default.useState(null);
11087
+ const [rows, setRows] = React25__default.useState(null);
11088
+ const [search, setSearch] = React25__default.useState("");
11089
+ const [status, setStatus] = React25__default.useState("all");
11090
+ const [changing, setChanging] = React25__default.useState(null);
9994
11091
  const isPayable = direction === "payable";
9995
11092
  const prefix = isPayable ? "payables" : "receivables";
9996
- const load = React23__default.useCallback(async () => {
11093
+ const load = React25__default.useCallback(async () => {
9997
11094
  setRows(null);
9998
11095
  try {
9999
11096
  setRows(await provider.getRecurringBills?.(direction) ?? []);
@@ -10002,10 +11099,10 @@ function RecurringBillsList({ direction, onNavigate }) {
10002
11099
  toast.error(error?.message ?? "N\xE3o foi poss\xEDvel carregar as recorr\xEAncias.");
10003
11100
  }
10004
11101
  }, [provider, direction]);
10005
- React23__default.useEffect(() => {
11102
+ React25__default.useEffect(() => {
10006
11103
  void load();
10007
11104
  }, [load]);
10008
- const filtered = React23__default.useMemo(() => {
11105
+ const filtered = React25__default.useMemo(() => {
10009
11106
  const term = search.trim().toLowerCase();
10010
11107
  const visiveis = (rows ?? []).filter((row) => {
10011
11108
  if (status === "active" && !row.isActive) return false;
@@ -10015,7 +11112,7 @@ function RecurringBillsList({ direction, onNavigate }) {
10015
11112
  });
10016
11113
  return [...visiveis].sort((a, b) => Number(b.isActive) - Number(a.isActive) || (a.dueDay ?? 0) - (b.dueDay ?? 0) || a.description.localeCompare(b.description));
10017
11114
  }, [rows, search, status]);
10018
- const porMes = React23__default.useMemo(() => {
11115
+ const porMes = React25__default.useMemo(() => {
10019
11116
  const fator = { weekly: 52 / 12, monthly: 1, quarterly: 1 / 3, yearly: 1 / 12 };
10020
11117
  const ativas = filtered.filter((row) => row.isActive);
10021
11118
  return {
@@ -10036,7 +11133,7 @@ function RecurringBillsList({ direction, onNavigate }) {
10036
11133
  setChanging(null);
10037
11134
  }
10038
11135
  }
10039
- const columns = React23__default.useMemo(() => [
11136
+ const columns = React25__default.useMemo(() => [
10040
11137
  {
10041
11138
  accessorKey: "description",
10042
11139
  header: "Descri\xE7\xE3o",
@@ -10133,40 +11230,40 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
10133
11230
  const provider = useFinancialProvider();
10134
11231
  const { locations, currency, itemTypes: configuredItemTypes, entityLookups } = useFinancialConfig();
10135
11232
  const isPayable = direction === "payable";
10136
- const [value, setValue] = React23__default.useState(null);
10137
- const [loaded, setLoaded] = React23__default.useState(!valueId);
10138
- const [contact, setContact] = React23__default.useState(null);
10139
- const [counterpartyAccounting, setCounterpartyAccounting] = React23__default.useState({
11233
+ const [value, setValue] = React25__default.useState(null);
11234
+ const [loaded, setLoaded] = React25__default.useState(!valueId);
11235
+ const [contact, setContact] = React25__default.useState(null);
11236
+ const [counterpartyAccounting, setCounterpartyAccounting] = React25__default.useState({
10140
11237
  allowedAccountIds: [],
10141
11238
  limitAccounts: false,
10142
11239
  addItemsAutomatically: false
10143
11240
  });
10144
- const accountingPolicyRequest = React23__default.useRef(0);
10145
- const counterpartyLookup = React23__default.useMemo(
11241
+ const accountingPolicyRequest = React25__default.useRef(0);
11242
+ const counterpartyLookup = React25__default.useMemo(
10146
11243
  () => createFinancialCounterpartyLookup(isPayable ? "debit" : "credit"),
10147
11244
  [isPayable]
10148
11245
  );
10149
11246
  const contactCreate = useLookupCreate(counterpartyLookup, (created) => {
10150
11247
  setContact({ id: created.id, name: created.label });
10151
11248
  });
10152
- const initialItem = React23__default.useRef(newRecurringItem());
10153
- const [items, setItems] = React23__default.useState([initialItem.current]);
10154
- const [expandedItemId, setExpandedItemId] = React23__default.useState(initialItem.current._id);
10155
- const [addingItem, setAddingItem] = React23__default.useState(false);
10156
- const [frequency, setFrequency] = React23__default.useState("monthly");
10157
- const [dueDay, setDueDay] = React23__default.useState("1");
10158
- const [startsOn, setStartsOn] = React23__default.useState(today3());
10159
- const [endsOn, setEndsOn] = React23__default.useState("");
10160
- const [unitId, setUnitId] = React23__default.useState(locations[0]?.id ?? "");
10161
- const [availableLocations, setAvailableLocations] = React23__default.useState(locations);
10162
- const [notes, setNotes] = React23__default.useState("");
10163
- const [isActive, setIsActive] = React23__default.useState(true);
10164
- const [accounts, setAccounts] = React23__default.useState([]);
10165
- const [costCenters, setCostCenters] = React23__default.useState([]);
10166
- const [saving, setSaving] = React23__default.useState(false);
10167
- const [ending, setEnding] = React23__default.useState(false);
10168
- const [error, setError] = React23__default.useState(null);
10169
- React23__default.useEffect(() => {
11249
+ const initialItem = React25__default.useRef(newRecurringItem());
11250
+ const [items, setItems] = React25__default.useState([initialItem.current]);
11251
+ const [expandedItemId, setExpandedItemId] = React25__default.useState(initialItem.current._id);
11252
+ const [addingItem, setAddingItem] = React25__default.useState(false);
11253
+ const [frequency, setFrequency] = React25__default.useState("monthly");
11254
+ const [dueDay, setDueDay] = React25__default.useState("1");
11255
+ const [startsOn, setStartsOn] = React25__default.useState(today3());
11256
+ const [endsOn, setEndsOn] = React25__default.useState("");
11257
+ const [unitId, setUnitId] = React25__default.useState(locations[0]?.id ?? "");
11258
+ const [availableLocations, setAvailableLocations] = React25__default.useState(locations);
11259
+ const [notes, setNotes] = React25__default.useState("");
11260
+ const [isActive, setIsActive] = React25__default.useState(true);
11261
+ const [accounts, setAccounts] = React25__default.useState([]);
11262
+ const [costCenters, setCostCenters] = React25__default.useState([]);
11263
+ const [saving, setSaving] = React25__default.useState(false);
11264
+ const [ending, setEnding] = React25__default.useState(false);
11265
+ const [error, setError] = React25__default.useState(null);
11266
+ React25__default.useEffect(() => {
10170
11267
  if (locations.length > 0) {
10171
11268
  setAvailableLocations(locations);
10172
11269
  return;
@@ -10181,7 +11278,7 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
10181
11278
  cancelled = true;
10182
11279
  };
10183
11280
  }, [locations]);
10184
- React23__default.useEffect(() => {
11281
+ React25__default.useEffect(() => {
10185
11282
  let cancelled = false;
10186
11283
  void Promise.all([
10187
11284
  provider.getChartOfAccounts(),
@@ -10228,21 +11325,21 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
10228
11325
  cancelled = true;
10229
11326
  };
10230
11327
  }, [provider, direction, valueId]);
10231
- const offeredItemTypes = React23__default.useMemo(() => availableInvoiceItemTypes(
11328
+ const offeredItemTypes = React25__default.useMemo(() => availableInvoiceItemTypes(
10232
11329
  configuredItemTypes,
10233
11330
  Object.keys(entityLookups),
10234
11331
  configuredItemTypes.find((item) => item.value === "other")?.label ?? "Outro",
10235
11332
  direction
10236
11333
  ), [configuredItemTypes, entityLookups, direction]);
10237
11334
  const itemTypes = useStockedItemTypes(offeredItemTypes, entityLookups);
10238
- const selectableAccounts = React23__default.useMemo(() => {
11335
+ const selectableAccounts = React25__default.useMemo(() => {
10239
11336
  const postable = postableAccounts(accounts);
10240
11337
  if (!counterpartyAccounting.limitAccounts) return postable;
10241
11338
  const allowed = new Set(counterpartyAccounting.allowedAccountIds);
10242
11339
  return postable.filter((account) => allowed.has(account.id));
10243
11340
  }, [accounts, counterpartyAccounting]);
10244
11341
  const hasItemLookups = itemTypes.some((item) => entityLookups[item.value]);
10245
- const amount = React23__default.useMemo(() => items.reduce(
11342
+ const amount = React25__default.useMemo(() => items.reduce(
10246
11343
  (sum, item) => sum + item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
10247
11344
  0
10248
11345
  ), [items]);
@@ -10250,8 +11347,8 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
10250
11347
  const numericDueDay = Math.trunc(Number(dueDay));
10251
11348
  const invalid = items.length === 0 ? "Adicione pelo menos um item." : items.some((item) => !item.description.trim()) ? "Informe a descri\xE7\xE3o de todos os itens." : !contact?.id ? `Selecione ${isPayable ? "quem recebe" : "quem paga"}.` : !(amount > 0) ? "Informe um valor maior que zero." : numericDueDay < 1 || numericDueDay > 31 ? "O dia do vencimento vai de 1 a 31." : !startsOn ? "Informe o in\xEDcio da recorr\xEAncia." : endsOn && endsOn < startsOn ? "O t\xE9rmino n\xE3o pode vir antes do in\xEDcio." : null;
10252
11349
  const fields = { items, contactId: contact?.id ?? "", frequency, dueDay, startsOn, endsOn, unitId, notes, isActive };
10253
- const snapshot = React23__default.useRef(null);
10254
- React23__default.useEffect(() => {
11350
+ const snapshot = React25__default.useRef(null);
11351
+ React25__default.useEffect(() => {
10255
11352
  if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
10256
11353
  }, [loaded]);
10257
11354
  const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
@@ -10641,16 +11738,8 @@ function buildNav(config, activeView, navigate, t2) {
10641
11738
  children: [
10642
11739
  { id: "payables-new", kind: "action", label: t2("financial.nav.new"), active: activeView.startsWith("payables-new"), onClick: () => navigate("payables-new") },
10643
11740
  { id: "payables-list", label: t2("financial.nav.list"), active: activeView === "payables-list", onClick: () => navigate("payables-list") },
10644
- { id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") },
10645
- // As do V1 que ainda não existem aqui — ver ./GapScreens.
10646
- //
10647
- // «Da minha caixinha» SAIU do menu. No V1 ela é `{caixaAberto && …}`:
10648
- // aparece enquanto a sessão daquela pessoa está aberta e some quando
10649
- // ela fecha. Como aba fixa ela prometia uma tela que quase ninguém vê
10650
- // — e pagar do dinheiro da gaveta é uma FORMA de pagamento do mesmo
10651
- // lançamento, não um cadastro à parte. A rota segue de pé.
10652
- { id: "payables-digitize", label: "Digitalizar Despesa", active: activeView === "payables-digitize", onClick: () => navigate("payables-digitize") },
10653
- { id: "payables-payroll", label: "Importar Folha", active: activeView === "payables-payroll", onClick: () => navigate("payables-payroll") }
11741
+ { id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") }
11742
+ // Digitize expense and import payroll live in the list's create menu, not here.
10654
11743
  ]
10655
11744
  });
10656
11745
  }
@@ -10900,8 +11989,9 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
10900
11989
  // tamanho do buraco, medido no banco do V1.
10901
11990
  case "payables-petty-cash":
10902
11991
  return /* @__PURE__ */ jsx(PettyCashView, {});
11992
+ // Digitizing is the list's receipt capture now — the old link lands on the list.
10903
11993
  case "payables-digitize":
10904
- return /* @__PURE__ */ jsx(DigitizeExpenseView, {});
11994
+ return /* @__PURE__ */ jsx(PayablesView, { intent: { view: "payables", mode: "list" }, onNavigate: navigate, previousView, back });
10905
11995
  case "payables-payroll":
10906
11996
  return /* @__PURE__ */ jsx(ImportPayrollView, {});
10907
11997
  case "cash-reviewers":
@@ -11108,14 +12198,14 @@ function BalanceHero() {
11108
12198
  const summary = useFinancialStore((s) => s.summary);
11109
12199
  const navigate = useDashboardNavigate();
11110
12200
  useEnsureSummary();
11111
- const [hidden, setHidden] = React23__default.useState(false);
11112
- const money3 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
12201
+ const [hidden, setHidden] = React25__default.useState(false);
12202
+ const money4 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
11113
12203
  const inflow = summary?.monthlyInflow ?? 0;
11114
12204
  const outflow = summary?.monthlyOutflow ?? 0;
11115
12205
  return /* @__PURE__ */ jsxs(GoldCard, { branded: true, className: "px-5 py-4 text-center md:px-8 md:py-5", children: [
11116
12206
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide opacity-75", children: t2("financial.home.balance") }),
11117
12207
  /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center justify-center gap-2.5", children: [
11118
- /* @__PURE__ */ jsx("span", { className: "text-3xl font-bold tracking-tight tabular-nums md:text-4xl", children: money3(summary?.totalBalance) }),
12208
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-bold tracking-tight tabular-nums md:text-4xl", children: money4(summary?.totalBalance) }),
11119
12209
  /* @__PURE__ */ jsx(
11120
12210
  "button",
11121
12211
  {
@@ -11139,7 +12229,7 @@ function BalanceHero() {
11139
12229
  /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-emerald-500/90", children: /* @__PURE__ */ jsx(ArrowUpRight, { className: "h-4 w-4 text-white" }) }),
11140
12230
  /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
11141
12231
  /* @__PURE__ */ jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.inflow") }),
11142
- /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money3(inflow) })
12232
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money4(inflow) })
11143
12233
  ] })
11144
12234
  ]
11145
12235
  }
@@ -11155,7 +12245,7 @@ function BalanceHero() {
11155
12245
  /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-rose-500/90", children: /* @__PURE__ */ jsx(ArrowDownRight, { className: "h-4 w-4 text-white" }) }),
11156
12246
  /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
11157
12247
  /* @__PURE__ */ jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.outflow") }),
11158
- /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money3(outflow) })
12248
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money4(outflow) })
11159
12249
  ] })
11160
12250
  ]
11161
12251
  }
@@ -11181,7 +12271,7 @@ function categorizeSpend(notes) {
11181
12271
  function SpendByCategoryChart() {
11182
12272
  const t2 = useTranslation();
11183
12273
  const provider = useFinancialProvider();
11184
- const [rows, setRows] = React23__default.useState([]);
12274
+ const [rows, setRows] = React25__default.useState([]);
11185
12275
  useEffect(() => {
11186
12276
  let alive = true;
11187
12277
  void (async () => {
@@ -11224,7 +12314,7 @@ function UpcomingBills() {
11224
12314
  const { currency } = useFinancialConfig();
11225
12315
  const provider = useFinancialProvider();
11226
12316
  const dueLabel = useDueLabel();
11227
- const [bills, setBills] = React23__default.useState([]);
12317
+ const [bills, setBills] = React25__default.useState([]);
11228
12318
  useEffect(() => {
11229
12319
  let alive = true;
11230
12320
  void (async () => {
@@ -12846,9 +13936,17 @@ function createSupabaseFinancialProvider() {
12846
13936
  totalAmount: i.total ?? 0,
12847
13937
  discount: i.discount ?? 0,
12848
13938
  surcharge: 0,
12849
- referenceId: i.productId,
13939
+ // `product_id` quando a linha veio de uma porta que o preenche (a
13940
+ // RPC da agenda); do metadata quando veio DESTA tela, que é onde a
13941
+ // escolha de catálogo é gravada. Lendo só a coluna, reabrir uma
13942
+ // fatura apagava o item escolhido de toda linha e deixava o texto
13943
+ // solto — e salvar de novo confirmava a perda.
13944
+ referenceId: i.productId ?? i.metadata?.referenceId,
12850
13945
  accountId: i.chartAccountId,
12851
13946
  costCenterId: i.costCenterId,
13947
+ // De onde a linha veio — o pacote que a gerou, por exemplo. Sem
13948
+ // devolvê-lo, editar a fatura desfazia o vínculo em silêncio.
13949
+ metadata: i.metadata ?? void 0,
12852
13950
  createdAt: i.createdAt
12853
13951
  };
12854
13952
  });
@@ -12890,6 +13988,33 @@ function createSupabaseFinancialProvider() {
12890
13988
  };
12891
13989
  });
12892
13990
  },
13991
+ async addInvoiceAttachment(invoiceId, file) {
13992
+ const { pub } = getClients();
13993
+ const tenantId = getTenantId();
13994
+ if (!tenantId) throw new Error("Conta sem empresa ativa");
13995
+ const safe = file.name.replace(/[^\w.\-]+/g, "_");
13996
+ const path = `${tenantId}/financial_invoice/${invoiceId}/${Date.now()}_${safe}`;
13997
+ const { error: upErr } = await pub.storage.from("attachments").upload(path, file, { contentType: file.type || void 0 });
13998
+ if (upErr) throw upErr;
13999
+ const { error } = await pub.from("documents").insert({
14000
+ tenant_id: tenantId,
14001
+ kind: "attachment",
14002
+ status: "active",
14003
+ subject_type: "financial_invoice",
14004
+ subject_id: invoiceId,
14005
+ file_name: file.name,
14006
+ file_size: file.size,
14007
+ mime_type: file.type || null,
14008
+ storage_provider: "supabase",
14009
+ storage_bucket: "attachments",
14010
+ storage_path: path
14011
+ });
14012
+ if (error) {
14013
+ await pub.storage.from("attachments").remove([path]).catch(() => {
14014
+ });
14015
+ throw error;
14016
+ }
14017
+ },
12893
14018
  async createInvoice(input) {
12894
14019
  const { core, pub } = getClients();
12895
14020
  const tenantId = getTenantId();
@@ -14631,6 +15756,237 @@ function createSupabaseFinancialProvider() {
14631
15756
  const { data, error } = await pub.from(T.notas).update({ status: "cancelled", error_message: reason ?? null }).eq("id", id).select().single();
14632
15757
  if (error) throw error;
14633
15758
  return snakeToCamel(data);
15759
+ },
15760
+ // --- Remessa CNAB 240 (080) ---
15761
+ /**
15762
+ * The Bacen registry, by COMPE code or by name.
15763
+ *
15764
+ * A bank is the country's, not the tenant's, so `public.banks` is global and
15765
+ * read-only. A pool where migration 167 has not run yet answers with an
15766
+ * empty list, and the caller falls back to the static catalogue — a missing
15767
+ * table costs a longer list, never the screen.
15768
+ */
15769
+ async searchBanks(query, limit = 12) {
15770
+ const term = query.trim();
15771
+ const { pub } = getClients();
15772
+ let qb = pub.from("banks").select("ispb,compe,name,legal_name,brand_color,logo_url").eq("is_active", true);
15773
+ if (term) {
15774
+ const digits2 = term.replace(/\D/g, "");
15775
+ qb = digits2.length >= 2 && digits2.length <= 3 ? qb.like("compe", `${digits2}%`) : qb.or(`name.ilike.%${term}%,legal_name.ilike.%${term}%`);
15776
+ }
15777
+ const { data, error } = await qb.order("compe", { nullsFirst: false }).limit(limit);
15778
+ if (error) return [];
15779
+ return (data ?? []).map((row) => ({
15780
+ ispb: row.ispb,
15781
+ compe: row.compe ?? void 0,
15782
+ name: row.name,
15783
+ legalName: row.legal_name ?? void 0,
15784
+ brandColor: row.brand_color ?? void 0,
15785
+ logoUrl: row.logo_url ?? void 0
15786
+ }));
15787
+ },
15788
+ /**
15789
+ * Who gets paid, who pays, and which file number is next.
15790
+ *
15791
+ * The payee's document lives on `people`, the account on `payee_accounts`
15792
+ * and the address on `addresses` — three tables because each answers a
15793
+ * different question, and the CNPJ of the paying company is a fourth. The
15794
+ * wizard asks once and gets all of it, so step 3 can show a form with the
15795
+ * blanks already filled instead of a sentence saying data is missing.
15796
+ */
15797
+ async getCnabContext({ contactIds, bankAccountId, unitId }) {
15798
+ const { pub } = getClients();
15799
+ const ids = [...new Set(contactIds.filter(Boolean))];
15800
+ const [peopleRes, accountsRes, addressRes, companyRes, unitRes, seqRes] = await Promise.all([
15801
+ ids.length ? pub.from("people").select("id,name,document_number").in("id", ids) : Promise.resolve({ data: [], error: null }),
15802
+ ids.length ? pub.from(T.payeeAccounts).select("*").eq("owner_type", "person").in("owner_id", ids).order("is_default", { ascending: false }).order("created_at") : Promise.resolve({ data: [], error: null }),
15803
+ ids.length ? pub.from("addresses").select("*").eq("owner_type", "person").in("owner_id", ids) : Promise.resolve({ data: [], error: null }),
15804
+ // As empresas do tenant. São unidades de linhas, então vêm todas e a
15805
+ // escolha é feita aqui — a da unidade da conta, senão a sem unidade,
15806
+ // senão a única que existe.
15807
+ pub.from("company_legal_entities").select("id,unit_id,kind,legal_name,trade_name,document_number").eq("is_active", true).limit(20),
15808
+ // O nome da unidade é o que a tela escreve enquanto não há empresa
15809
+ // cadastrada — melhor que um campo em branco pedindo razão social.
15810
+ unitId ? pub.from(T.unitCompanies).select("unit_id,unit_name").eq("unit_id", unitId).maybeSingle() : Promise.resolve({ data: null, error: null }),
15811
+ pub.from(T.cnabBatches).select("sequence_number").eq("bank_account_id", bankAccountId).order("sequence_number", { ascending: false }).limit(1).maybeSingle()
15812
+ ]);
15813
+ if (peopleRes.error) throw peopleRes.error;
15814
+ if (accountsRes.error) throw accountsRes.error;
15815
+ const accountOf = /* @__PURE__ */ new Map();
15816
+ for (const row of accountsRes.data ?? []) {
15817
+ if (!accountOf.has(row.owner_id)) accountOf.set(row.owner_id, row);
15818
+ }
15819
+ const addressOf = /* @__PURE__ */ new Map();
15820
+ for (const row of addressRes.data ?? []) {
15821
+ if (!addressOf.has(row.owner_id)) addressOf.set(row.owner_id, row);
15822
+ }
15823
+ const payees = (peopleRes.data ?? []).map((person) => {
15824
+ const account = accountOf.get(person.id);
15825
+ const address = addressOf.get(person.id);
15826
+ return {
15827
+ contactId: person.id,
15828
+ name: person.name ?? "",
15829
+ document: person.document_number ?? "",
15830
+ payeeAccountId: account?.id,
15831
+ bankCode: account?.bank_code ?? "",
15832
+ bankName: account?.bank_name ?? "",
15833
+ agencyNumber: account?.agency_number ?? "",
15834
+ agencyDigit: account?.agency_digit ?? "",
15835
+ accountNumber: account?.account_number ?? "",
15836
+ accountDigit: account?.account_digit ?? "",
15837
+ accountType: account?.account_type ?? "",
15838
+ pixKey: account?.pix_key ?? "",
15839
+ pixKeyType: account?.pix_key_type ?? "",
15840
+ street: address?.street ?? "",
15841
+ number: address?.number ?? "",
15842
+ complement: address?.complement ?? "",
15843
+ district: address?.district ?? "",
15844
+ city: address?.city ?? "",
15845
+ state: address?.state ?? "",
15846
+ postalCode: address?.postal_code ?? ""
15847
+ };
15848
+ });
15849
+ const companies = companyRes.data ?? [];
15850
+ const companyRow = companies.find((row) => unitId && row.unit_id === unitId) ?? companies.find((row) => !row.unit_id) ?? companies[0];
15851
+ const unitName = unitRes.data?.unit_name ?? "";
15852
+ const company = {
15853
+ id: companyRow?.id ?? void 0,
15854
+ unitId,
15855
+ name: companyRow?.trade_name || companyRow?.legal_name || unitName,
15856
+ document: companyRow?.document_number || ""
15857
+ };
15858
+ return {
15859
+ company,
15860
+ nextSequence: Number(seqRes.data?.sequence_number ?? 0) + 1,
15861
+ payees
15862
+ };
15863
+ },
15864
+ /**
15865
+ * A empresa pagadora, cadastrada de dentro da remessa.
15866
+ *
15867
+ * `company_legal_entities` nasceu sem tela: nenhuma superfície do SDK
15868
+ * escreve nela, e o resultado é um pool com treze unidades e zero empresas.
15869
+ * Como o CNPJ é obrigatório no cabeçalho do CNAB, é aqui que a pessoa o
15870
+ * digita — e gravá-lo é o que faz a PRÓXIMA remessa já vir preenchida.
15871
+ *
15872
+ * `unit_id` na linha é o que recorta a empresa por unidade. O elo inverso
15873
+ * (`app.units.company_legal_entity_id`) fica para quem tiver a RPC: o schema
15874
+ * `app` não é alcançável por aqui, e a leitura já resolve pelos dois lados.
15875
+ */
15876
+ async saveCompany(input) {
15877
+ const { pub } = getClients();
15878
+ const tenantId = getTenantId();
15879
+ const digits2 = (input.document || "").replace(/\D/g, "");
15880
+ const name = input.name.trim();
15881
+ if (!name) throw new Error("A empresa precisa de um nome.");
15882
+ if (digits2.length !== 11 && digits2.length !== 14) {
15883
+ throw new Error("CNPJ com 14 d\xEDgitos ou CPF com 11 \u2014 o cabe\xE7alho do CNAB n\xE3o aceita outro.");
15884
+ }
15885
+ const row = {
15886
+ kind: digits2.length === 14 ? "company" : "individual",
15887
+ legal_name: name,
15888
+ trade_name: name,
15889
+ document_number: digits2
15890
+ };
15891
+ if (input.id) {
15892
+ const { error: error2 } = await pub.from("company_legal_entities").update(row).eq("id", input.id);
15893
+ if (error2) throw error2;
15894
+ return { id: input.id };
15895
+ }
15896
+ const { data, error } = await pub.from("company_legal_entities").insert({ ...row, tenant_id: tenantId, unit_id: input.unitId ?? null, is_active: true }).select("id").single();
15897
+ if (error) throw error;
15898
+ return { id: data.id };
15899
+ },
15900
+ /**
15901
+ * What the wizard collected goes back to the cadastro, not to the file only.
15902
+ * Otherwise the same CPF is typed again on the next remittance, and the two
15903
+ * typings disagree.
15904
+ */
15905
+ async saveCnabPayees(payees) {
15906
+ const { pub } = getClients();
15907
+ const tenantId = getTenantId();
15908
+ const text2 = (v) => {
15909
+ const s = (v ?? "").trim();
15910
+ return s === "" ? null : s;
15911
+ };
15912
+ for (const payee of payees) {
15913
+ if (text2(payee.document)) {
15914
+ await pub.from("people").update({ document_number: text2(payee.document) }).eq("id", payee.contactId);
15915
+ }
15916
+ const row = {
15917
+ bank_code: text2(payee.bankCode),
15918
+ bank_name: text2(payee.bankName),
15919
+ agency_number: text2(payee.agencyNumber),
15920
+ agency_digit: text2(payee.agencyDigit),
15921
+ account_number: text2(payee.accountNumber),
15922
+ account_digit: text2(payee.accountDigit),
15923
+ account_type: text2(payee.accountType),
15924
+ pix_key: text2(payee.pixKey),
15925
+ pix_key_type: text2(payee.pixKeyType)
15926
+ };
15927
+ if (!row.account_number && !row.pix_key) continue;
15928
+ let targetId = payee.payeeAccountId;
15929
+ if (!targetId) {
15930
+ const { data: existing } = await pub.from(T.payeeAccounts).select("id").eq("owner_type", "person").eq("owner_id", payee.contactId).order("is_default", { ascending: false }).order("created_at").limit(1).maybeSingle();
15931
+ targetId = existing?.id;
15932
+ }
15933
+ if (targetId) {
15934
+ await pub.from(T.payeeAccounts).update(row).eq("id", targetId);
15935
+ } else {
15936
+ await pub.from(T.payeeAccounts).insert({
15937
+ ...row,
15938
+ tenant_id: tenantId,
15939
+ owner_type: "person",
15940
+ owner_id: payee.contactId,
15941
+ label: "Principal",
15942
+ is_default: true
15943
+ });
15944
+ }
15945
+ }
15946
+ },
15947
+ /**
15948
+ * The file that already left for the bank. `sequence_number` is the bank's
15949
+ * count, not the file's — two remittances carrying the same one is a
15950
+ * rejected remittance, so it is written with the batch and never derived
15951
+ * again.
15952
+ */
15953
+ async recordCnabBatch(input) {
15954
+ const { pub } = getClients();
15955
+ const tenantId = getTenantId();
15956
+ const { data, error } = await pub.from(T.cnabBatches).insert({
15957
+ tenant_id: tenantId,
15958
+ unit_id: input.unitId ?? null,
15959
+ bank_account_id: input.bankAccountId,
15960
+ layout: input.layout,
15961
+ sequence_number: input.sequenceNumber,
15962
+ total_amount: input.totalAmount,
15963
+ items_count: input.items.length,
15964
+ file_name: input.fileName,
15965
+ // The file's home is `public.documents` through `document_id`; until the
15966
+ // upload path exists it rides in metadata, which keeps a generated
15967
+ // remittance downloadable instead of gone.
15968
+ metadata: { file_content: input.fileContent }
15969
+ }).select("id").single();
15970
+ if (error) throw error;
15971
+ const batchId = data.id;
15972
+ if (input.items.length) {
15973
+ const ids = [...new Set(input.items.map((item) => item.movementId))];
15974
+ const { data: movementRows } = await pub.from(T.movements).select("id").in("id", ids);
15975
+ const isMovement = new Set((movementRows ?? []).map((row) => row.id));
15976
+ const { error: itemsError } = await pub.from(T.cnabItems).insert(
15977
+ input.items.map((item) => ({
15978
+ tenant_id: tenantId,
15979
+ batch_id: batchId,
15980
+ installment_id: item.movementId,
15981
+ movement_id: isMovement.has(item.movementId) ? item.movementId : null,
15982
+ payment_form: item.paymentForm,
15983
+ payment_date: item.paymentDate,
15984
+ amount: item.amount
15985
+ }))
15986
+ );
15987
+ if (itemsError) throw itemsError;
15988
+ }
15989
+ return { id: batchId };
14634
15990
  }
14635
15991
  };
14636
15992
  return provider;
@@ -15133,21 +16489,21 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
15133
16489
  const canCreate = can("financial", "create");
15134
16490
  const canEdit = can("financial", "edit");
15135
16491
  const canDelete = can("financial", "delete");
15136
- const provider = React23__default.useMemo(
16492
+ const provider = React25__default.useMemo(
15137
16493
  () => dataProvider ?? resolveDataProvider(entity, mockData ?? []),
15138
16494
  [entity, mockData, dataProvider]
15139
16495
  );
15140
16496
  const table = entity.data?.table;
15141
- const [nodes, setNodes] = React23__default.useState(null);
15142
- const [loadError, setLoadError] = React23__default.useState(null);
15143
- const [expanded, setExpanded] = React23__default.useState(/* @__PURE__ */ new Set());
15144
- const [query, setQuery] = React23__default.useState("");
15145
- const [showInactive, setShowInactive] = React23__default.useState(true);
15146
- const [editing, setEditing] = React23__default.useState(null);
15147
- const [confirmDelete, setConfirmDelete] = React23__default.useState(null);
15148
- const [busy, setBusy] = React23__default.useState(false);
15149
- const initialized = React23__default.useRef(false);
15150
- const reload = React23__default.useCallback(async () => {
16497
+ const [nodes, setNodes] = React25__default.useState(null);
16498
+ const [loadError, setLoadError] = React25__default.useState(null);
16499
+ const [expanded, setExpanded] = React25__default.useState(/* @__PURE__ */ new Set());
16500
+ const [query, setQuery] = React25__default.useState("");
16501
+ const [showInactive, setShowInactive] = React25__default.useState(true);
16502
+ const [editing, setEditing] = React25__default.useState(null);
16503
+ const [confirmDelete, setConfirmDelete] = React25__default.useState(null);
16504
+ const [busy, setBusy] = React25__default.useState(false);
16505
+ const initialized = React25__default.useRef(false);
16506
+ const reload = React25__default.useCallback(async () => {
15151
16507
  try {
15152
16508
  const rows = await loadAll(provider);
15153
16509
  setNodes(rows);
@@ -15162,17 +16518,17 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
15162
16518
  setNodes((prev) => prev ?? []);
15163
16519
  }
15164
16520
  }, [provider]);
15165
- React23__default.useEffect(() => {
16521
+ React25__default.useEffect(() => {
15166
16522
  void reload();
15167
16523
  }, [reload]);
15168
- const shown = React23__default.useMemo(
16524
+ const shown = React25__default.useMemo(
15169
16525
  () => (nodes ?? []).filter((n) => showInactive || n.isActive),
15170
16526
  [nodes, showInactive]
15171
16527
  );
15172
- const tree = React23__default.useMemo(() => buildChartTree(shown), [shown]);
15173
- const fullTree = React23__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
15174
- const search = React23__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
15175
- const groupIds = React23__default.useMemo(
16528
+ const tree = React25__default.useMemo(() => buildChartTree(shown), [shown]);
16529
+ const fullTree = React25__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
16530
+ const search = React25__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
16531
+ const groupIds = React25__default.useMemo(
15176
16532
  () => [...tree.byId.values()].filter((n) => isGroupNode(tree, n)).map((n) => n.id),
15177
16533
  [tree]
15178
16534
  );
@@ -15268,7 +16624,7 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
15268
16624
  const ok = await run(() => provider.remove(node.id), t2("financial.chartTree.deleted", { name: node.name }));
15269
16625
  if (ok) setConfirmDelete(null);
15270
16626
  }
15271
- const parentOptions = React23__default.useCallback((q, selfId) => {
16627
+ const parentOptions = React25__default.useCallback((q, selfId) => {
15272
16628
  const blocked = selfId ? descendantIds(fullTree, selfId).add(selfId) : /* @__PURE__ */ new Set();
15273
16629
  const needle = q.trim().toLowerCase();
15274
16630
  return [...fullTree.byId.values()].filter((n) => !blocked.has(n.id) && isGroupNode(fullTree, n)).filter((n) => !needle || accountLabel(n).toLowerCase().includes(needle)).sort((a, b) => a.code.localeCompare(b.code, void 0, { numeric: true })).slice(0, 50).map((n) => ({ id: n.id, label: accountLabel(n) }));
@@ -15571,7 +16927,7 @@ var chartOfAccountsSeed = [
15571
16927
  { id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
15572
16928
  { id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
15573
16929
  ];
15574
- var ChartOfAccountsRegistryView = (props) => React23__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
16930
+ var ChartOfAccountsRegistryView = (props) => React25__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
15575
16931
  ChartOfAccountsRegistryView.displayName = "ChartOfAccountsRegistryView";
15576
16932
  var costCenterEntity = {
15577
16933
  name: "Cost Center",
@@ -15591,6 +16947,7 @@ var costCenterEntity = {
15591
16947
  };
15592
16948
  var isCard = (t2) => t2 === "card_receivable_credit" || t2 === "card_receivable_debit" || t2 === "card_payable";
15593
16949
  var isBank = (t2) => t2 === "bank";
16950
+ var hasIssuer = (t2) => isBank(t2) || t2 === "card_payable";
15594
16951
  var ACCOUNT_KINDS = ["cash_physical", "bank", "card_receivable_credit", "card_receivable_debit", "card_payable"];
15595
16952
  var bankAccountEntity = {
15596
16953
  name: "Conta",
@@ -15611,7 +16968,12 @@ var bankAccountEntity = {
15611
16968
  // sozinho quando nenhum dos seus campos se aplica.
15612
16969
  fieldGroups: [
15613
16970
  { id: "identity", label: "Identifica\xE7\xE3o", columns: 2 },
15614
- { id: "bank", label: "Onde o dinheiro est\xE1", columns: 2 },
16971
+ {
16972
+ id: "bank",
16973
+ label: "Onde o dinheiro est\xE1",
16974
+ columns: 2,
16975
+ labelWhen: (v) => v.kind === "card_payable" ? "Quem emitiu o cart\xE3o" : void 0
16976
+ },
15615
16977
  { id: "holder", label: "Titular", columns: 2, description: "A conta de uma rede raramente est\xE1 no nome da rede." },
15616
16978
  { id: "card", label: "Cart\xE3o", columns: 2 },
15617
16979
  { ...RECORD_GROUP }
@@ -15651,16 +17013,26 @@ var bankAccountEntity = {
15651
17013
  label: "N\xFAmero do banco",
15652
17014
  type: "text",
15653
17015
  showInTable: false,
15654
- visibleWhen: (v) => isBank(v.kind),
17016
+ visibleWhen: (v) => hasIssuer(v.kind),
15655
17017
  group: "bank"
15656
17018
  },
17019
+ // Picking the bank fills its number too, so the two cannot disagree.
15657
17020
  {
15658
17021
  key: "bankName",
15659
17022
  label: "Banco",
15660
- type: "text",
17023
+ type: "custom",
15661
17024
  showInTable: false,
15662
- visibleWhen: (v) => isBank(v.kind),
15663
- group: "bank"
17025
+ labelWhen: (v) => v.kind === "card_payable" ? "Banco emissor" : void 0,
17026
+ visibleWhen: (v) => hasIssuer(v.kind),
17027
+ group: "bank",
17028
+ renderInput: ({ value, values, onChange, setValue }) => React25__default.createElement(BankPicker, {
17029
+ code: values.bankCode ?? "",
17030
+ name: value ?? "",
17031
+ onChange: (next) => {
17032
+ onChange(next.name);
17033
+ if (next.code || !next.name) setValue?.("bankCode", next.code);
17034
+ }
17035
+ })
15664
17036
  },
15665
17037
  {
15666
17038
  key: "agencyNumber",
@@ -16522,10 +17894,10 @@ function CashSessionHeaderControl({ config, provider, store: store6 }) {
16522
17894
  const fetchCashSessions = useStore(store6, (s) => s.fetchCashSessions);
16523
17895
  const open = useStore(store6, (s) => s.cashSessionModalOpen);
16524
17896
  const setOpen = useStore(store6, (s) => s.setCashSessionModalOpen);
16525
- React23__default.useEffect(() => {
17897
+ React25__default.useEffect(() => {
16526
17898
  void fetchCashSessions?.();
16527
17899
  }, [fetchCashSessions]);
16528
- React23__default.useEffect(() => {
17900
+ React25__default.useEffect(() => {
16529
17901
  if (!open) return;
16530
17902
  const close = () => setOpen(false);
16531
17903
  window.addEventListener("hashchange", close);
@@ -16645,7 +18017,7 @@ var en = {
16645
18017
  "financial.invoice.items": "Items",
16646
18018
  "financial.invoice.keepInvoice": "Keep Invoice",
16647
18019
  "financial.invoice.new": "New",
16648
- "financial.invoice.newManual": "Fill it in by hand",
18020
+ "financial.invoice.newManual": "Create",
16649
18021
  "financial.invoiceForm.createContact": "Create",
16650
18022
  "financial.invoiceForm.alreadyPaid": "Already paid",
16651
18023
  "financial.invoiceForm.paidWith": "Payment method",
@@ -16701,6 +18073,7 @@ var en = {
16701
18073
  "financial.invoiceForm.contactRequired": "Select who will pay or receive before adding items.",
16702
18074
  "financial.invoiceForm.additionalNotes": "Additional notes...",
16703
18075
  "financial.itemType.service": "Service",
18076
+ "financial.itemType.package": "Package",
16704
18077
  "financial.itemType.product": "Product",
16705
18078
  "financial.itemType.other": "Other",
16706
18079
  "financial.itemType.gift_card": "Gift Card",
@@ -17383,6 +18756,10 @@ var en = {
17383
18756
  "financial.quickTx.receiptRemove": "Remove receipt",
17384
18757
  "financial.quickTx.receiptInvalid": "Please choose an image file",
17385
18758
  "financial.quickTx.sendReceipt": "Send receipt",
18759
+ "financial.quickTx.digitizeExpense": "Digitize expense",
18760
+ "financial.quickTx.digitizeReceipt": "Digitize receipt",
18761
+ "financial.invoice.importPayroll": "Import payroll",
18762
+ "financial.invoice.importPayrollSoon": "Coming soon",
17386
18763
  "financial.receipt.ask": "Send the receipt photo\u2026",
17387
18764
  "financial.receipt.attachRead": "Attach the receipt the assistant read",
17388
18765
  "financial.receipt.fromReceipt": "From the receipt",
@@ -17534,7 +18911,7 @@ var ptBR = {
17534
18911
  "financial.invoice.items": "Itens",
17535
18912
  "financial.invoice.keepInvoice": "Manter Fatura",
17536
18913
  "financial.invoice.new": "Nova",
17537
- "financial.invoice.newManual": "Cadastrar manualmente",
18914
+ "financial.invoice.newManual": "Cadastrar",
17538
18915
  "financial.invoiceForm.createContact": "Cadastrar",
17539
18916
  "financial.invoiceForm.alreadyPaid": "J\xE1 foi pago",
17540
18917
  "financial.invoiceForm.paidWith": "Forma de pagamento",
@@ -17590,6 +18967,7 @@ var ptBR = {
17590
18967
  "financial.invoiceForm.contactRequired": "Selecione quem vai pagar ou receber antes de adicionar itens.",
17591
18968
  "financial.invoiceForm.additionalNotes": "Observa\xE7\xF5es adicionais...",
17592
18969
  "financial.itemType.service": "Servi\xE7o",
18970
+ "financial.itemType.package": "Pacote",
17593
18971
  "financial.itemType.product": "Produto",
17594
18972
  "financial.itemType.other": "Outro",
17595
18973
  "financial.itemType.gift_card": "Gift Card",
@@ -18275,6 +19653,10 @@ var ptBR = {
18275
19653
  "financial.quickTx.receiptRemove": "Remover recibo",
18276
19654
  "financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
18277
19655
  "financial.quickTx.sendReceipt": "Enviar recibo",
19656
+ "financial.quickTx.digitizeExpense": "Digitalizar despesa",
19657
+ "financial.quickTx.digitizeReceipt": "Digitalizar comprovante",
19658
+ "financial.invoice.importPayroll": "Importar folha",
19659
+ "financial.invoice.importPayrollSoon": "Em breve",
18278
19660
  "financial.receipt.ask": "Mande a foto do recibo\u2026",
18279
19661
  "financial.receipt.attachRead": "Anexar o recibo que o assistente leu",
18280
19662
  "financial.receipt.fromReceipt": "Do recibo",
@@ -18440,14 +19822,14 @@ function CommissionRuleDialog({
18440
19822
  onClose,
18441
19823
  onSaved
18442
19824
  }) {
18443
- const [name, setName] = React23__default.useState(rule?.name ?? "");
18444
- const [description, setDescription] = React23__default.useState(rule?.description ?? "");
18445
- const [priority, setPriority] = React23__default.useState(String(rule?.priority ?? 1));
18446
- const [isActive, setIsActive] = React23__default.useState(rule?.isActive ?? true);
18447
- const [isDefault, setIsDefault] = React23__default.useState(rule?.isDefault ?? !hasDefault);
18448
- const [saving, setSaving] = React23__default.useState(false);
18449
- const [error, setError] = React23__default.useState(null);
18450
- const initial = React23__default.useMemo(() => {
19825
+ const [name, setName] = React25__default.useState(rule?.name ?? "");
19826
+ const [description, setDescription] = React25__default.useState(rule?.description ?? "");
19827
+ const [priority, setPriority] = React25__default.useState(String(rule?.priority ?? 1));
19828
+ const [isActive, setIsActive] = React25__default.useState(rule?.isActive ?? true);
19829
+ const [isDefault, setIsDefault] = React25__default.useState(rule?.isDefault ?? !hasDefault);
19830
+ const [saving, setSaving] = React25__default.useState(false);
19831
+ const [error, setError] = React25__default.useState(null);
19832
+ const initial = React25__default.useMemo(() => {
18451
19833
  const get = (t2, key = "ids") => rule?.criteria.find((c) => c.criteriaType === t2)?.criteriaValue?.[key] ?? [];
18452
19834
  return {
18453
19835
  services: get("service"),
@@ -18458,12 +19840,12 @@ function CommissionRuleDialog({
18458
19840
  ranges: get("time_range", "ranges")
18459
19841
  };
18460
19842
  }, [rule]);
18461
- const [services, setServices] = React23__default.useState(initial.services);
18462
- const [categories, setCategories] = React23__default.useState(initial.categories);
18463
- const [professionals, setProfessionals] = React23__default.useState(initial.professionals);
18464
- const [units, setUnits] = React23__default.useState(initial.units);
18465
- const [weekdays, setWeekdays] = React23__default.useState(initial.weekdays);
18466
- const [ranges, setRanges] = React23__default.useState(initial.ranges);
19843
+ const [services, setServices] = React25__default.useState(initial.services);
19844
+ const [categories, setCategories] = React25__default.useState(initial.categories);
19845
+ const [professionals, setProfessionals] = React25__default.useState(initial.professionals);
19846
+ const [units, setUnits] = React25__default.useState(initial.units);
19847
+ const [weekdays, setWeekdays] = React25__default.useState(initial.weekdays);
19848
+ const [ranges, setRanges] = React25__default.useState(initial.ranges);
18467
19849
  const criteriaCount = [services, categories, professionals, units].filter((a) => a.length > 0).length + (weekdays.length ? 1 : 0) + (ranges.length ? 1 : 0);
18468
19850
  async function handleSave() {
18469
19851
  if (!name.trim()) {
@@ -18706,11 +20088,11 @@ function blankFunction(order) {
18706
20088
  };
18707
20089
  }
18708
20090
  function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
18709
- const [functions, setFunctions] = React23__default.useState(
20091
+ const [functions, setFunctions] = React25__default.useState(
18710
20092
  rule.functions.length ? rule.functions : [blankFunction(0)]
18711
20093
  );
18712
- const [saving, setSaving] = React23__default.useState(false);
18713
- const [error, setError] = React23__default.useState(null);
20094
+ const [saving, setSaving] = React25__default.useState(false);
20095
+ const [error, setError] = React25__default.useState(null);
18714
20096
  function patch(i, changes) {
18715
20097
  setFunctions((fs) => fs.map((f, j) => j === i ? { ...f, ...changes } : f));
18716
20098
  }
@@ -18994,19 +20376,19 @@ function functionChip(fn) {
18994
20376
  return fn.valueType === "percentage" ? `${who}: ${fn.valueAmount}%` : `${who}: R$ ${fn.valueAmount.toFixed(2)}`;
18995
20377
  }
18996
20378
  function CommissionRulesView({ provider }) {
18997
- const [rules, setRules] = React23__default.useState(null);
18998
- const [catalogs, setCatalogs] = React23__default.useState({
20379
+ const [rules, setRules] = React25__default.useState(null);
20380
+ const [catalogs, setCatalogs] = React25__default.useState({
18999
20381
  services: [],
19000
20382
  categories: [],
19001
20383
  professionals: [],
19002
20384
  units: []
19003
20385
  });
19004
- const [error, setError] = React23__default.useState(null);
19005
- const [funnel, setFunnel] = React23__default.useState({});
19006
- const [editing, setEditing] = React23__default.useState(null);
19007
- const [functionsFor, setFunctionsFor] = React23__default.useState(null);
19008
- const [deleting, setDeleting] = React23__default.useState(null);
19009
- const load = React23__default.useCallback(async () => {
20386
+ const [error, setError] = React25__default.useState(null);
20387
+ const [funnel, setFunnel] = React25__default.useState({});
20388
+ const [editing, setEditing] = React25__default.useState(null);
20389
+ const [functionsFor, setFunctionsFor] = React25__default.useState(null);
20390
+ const [deleting, setDeleting] = React25__default.useState(null);
20391
+ const load = React25__default.useCallback(async () => {
19010
20392
  try {
19011
20393
  setError(null);
19012
20394
  const [r, c] = await Promise.all([
@@ -19020,17 +20402,17 @@ function CommissionRulesView({ provider }) {
19020
20402
  setRules([]);
19021
20403
  }
19022
20404
  }, [provider]);
19023
- React23__default.useEffect(() => {
20405
+ React25__default.useEffect(() => {
19024
20406
  void load();
19025
20407
  }, [load]);
19026
20408
  const funnelActive = Object.values(funnel).some(Boolean);
19027
- const visible = React23__default.useMemo(() => {
20409
+ const visible = React25__default.useMemo(() => {
19028
20410
  if (!rules) return [];
19029
20411
  const ranked = rankRules(rules);
19030
20412
  if (!funnelActive) return ranked;
19031
20413
  return ranked.filter((r) => r.isDefault || ruleMatches(r, funnel, catalogs));
19032
20414
  }, [rules, funnel, funnelActive, catalogs]);
19033
- const winner = React23__default.useMemo(() => {
20415
+ const winner = React25__default.useMemo(() => {
19034
20416
  if (!funnelActive) return null;
19035
20417
  return visible.find((r) => r.isActive && !r.isDefault) ?? visible.find((r) => r.isActive && r.isDefault) ?? null;
19036
20418
  }, [visible, funnelActive]);
@@ -19045,7 +20427,7 @@ function CommissionRulesView({ provider }) {
19045
20427
  await provider.duplicateCommissionRule?.(rule.id);
19046
20428
  await load();
19047
20429
  }
19048
- const headerActions = React23__default.useMemo(() => [{
20430
+ const headerActions = React25__default.useMemo(() => [{
19049
20431
  id: "new-rule",
19050
20432
  label: "Adicionar regra",
19051
20433
  icon: Plus,
@@ -19827,7 +21209,7 @@ function PersonStatementWidget({ person, provider, config }) {
19827
21209
  ] });
19828
21210
  }
19829
21211
  function SupplierFinancialSettings({ person, provider }) {
19830
- const initial = React23__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
21212
+ const initial = React25__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
19831
21213
  const [accounts, setAccounts] = useState([]);
19832
21214
  const [costCenters, setCostCenters] = useState([]);
19833
21215
  const [defaultAccountId, setDefaultAccountId] = useState(initial.defaultAccountId ?? "");
@@ -42899,6 +44281,215 @@ COMMENT ON VIEW public.v_financial_invoice_lines_due IS
42899
44281
 
42900
44282
  ALTER VIEW public.v_financial_invoice_lines_due SET (security_invoker = true);
42901
44283
  GRANT SELECT ON public.v_financial_invoice_lines_due TO authenticated, service_role;
44284
+ `;
44285
+ var MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE = `-- ---------------------------------------------------------------------------
44286
+ -- 115 \u2014 a remessa aponta para a conta que EXISTE.
44287
+ --
44288
+ -- \`plg_financial_cnab_batches.bank_account_id\` carrega DUAS chaves estrangeiras
44289
+ -- para a mesma coluna:
44290
+ --
44291
+ -- plg_financial_cnab_batches_account_fkey \u2192 plg_financial_bank_accounts
44292
+ -- plg_financial_cnab_batches_ledger_account_fkey \u2192 plg_financial_accounts
44293
+ --
44294
+ -- A segunda \xE9 a certa. A primeira aponta para a tabela LEGADA \u2014 aquela que a
44295
+ -- 021 aposentou e que hoje \xE9 s\xF3 o encaminhamento por gatilho. Neste cluster ela
44296
+ -- tem 5 linhas contra 63 contas banc\xE1rias no raz\xE3o, e o efeito \xE9 exato: gerar
44297
+ -- uma remessa por qualquer conta criada depois do porte morre com
44298
+ --
44299
+ -- violates foreign key constraint "plg_financial_cnab_batches_account_fkey"
44300
+ -- Key is not present in table "plg_financial_bank_accounts"
44301
+ --
44302
+ -- Uma coluna com duas FKs exige que o id esteja nas DUAS tabelas, e \xE9 por isso
44303
+ -- que a tela nunca conseguiu gravar: ela escreve onde \`getBankAccounts\` l\xEA, que
44304
+ -- \xE9 o raz\xE3o, e o legado nunca soube daquela conta.
44305
+ --
44306
+ -- A 080 trouxe a tabela do V1 com a FK que o V1 tinha. O que faltou foi
44307
+ -- aposentar a chave junto com a tabela a que ela se referia.
44308
+ --
44309
+ -- Derrubar a FK legada n\xE3o perde nada: a do raz\xE3o continua guardando a coluna, e
44310
+ -- \xE9 ela que diz a verdade. As 60 remessas j\xE1 migradas apontam para contas que
44311
+ -- est\xE3o nas duas tabelas e seguem v\xE1lidas.
44312
+ --
44313
+ -- Idempotente.
44314
+ -- ---------------------------------------------------------------------------
44315
+
44316
+ ALTER TABLE public.plg_financial_cnab_batches
44317
+ DROP CONSTRAINT IF EXISTS plg_financial_cnab_batches_account_fkey;
44318
+
44319
+ -- A do raz\xE3o pode n\xE3o existir num pool que recebeu a 080 e n\xE3o a corre\xE7\xE3o; \xE9
44320
+ -- ela que fica, ent\xE3o se faltar, nasce aqui.
44321
+ DO $$
44322
+ BEGIN
44323
+ IF NOT EXISTS (
44324
+ SELECT 1 FROM pg_constraint
44325
+ WHERE conrelid = 'public.plg_financial_cnab_batches'::regclass
44326
+ AND conname = 'plg_financial_cnab_batches_ledger_account_fkey'
44327
+ ) THEN
44328
+ ALTER TABLE public.plg_financial_cnab_batches
44329
+ ADD CONSTRAINT plg_financial_cnab_batches_ledger_account_fkey
44330
+ FOREIGN KEY (bank_account_id)
44331
+ REFERENCES public.plg_financial_accounts(id) ON DELETE SET NULL;
44332
+ END IF;
44333
+ END $$;
44334
+
44335
+ COMMENT ON COLUMN public.plg_financial_cnab_batches.bank_account_id IS
44336
+ 'A conta do RAZ\xC3O que o arquivo debita \u2014 plg_financial_accounts, a mesma que getBankAccounts l\xEA. N\xE3o \xE9 a tabela legada plg_financial_bank_accounts: uma conta criada depois do porte s\xF3 existe no raz\xE3o.';
44337
+
44338
+ -- \u2500\u2500 a parcela que n\xE3o tem movimento g\xEAmeo \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
44339
+ --
44340
+ -- \`plg_financial_cnab_items.movement_id\` aponta para \`plg_financial_movements\`.
44341
+ -- A tela manda a PARCELA, e o porte deixou parcela e movimento com o mesmo id
44342
+ -- na maioria das linhas \u2014 mas n\xE3o em todas: aqui s\xE3o 171.978 parcelas contra
44343
+ -- 143.175 com movimento de mesmo id. As outras 28.803 fariam a remessa morrer
44344
+ -- na hora de arquivar um arquivo que J\xC1 foi baixado e j\xE1 foi para o banco.
44345
+ --
44346
+ -- O item passa a aceitar a parcela em coluna pr\xF3pria. \`movement_id\` continua
44347
+ -- sendo o elo com o raz\xE3o onde ele existe; \`installment_id\` \xE9 o que a tela
44348
+ -- sempre teve na m\xE3o, e \xE9 por ele que \xABesta conta j\xE1 foi numa remessa\xBB tem
44349
+ -- resposta.
44350
+ ALTER TABLE public.plg_financial_cnab_items
44351
+ ADD COLUMN IF NOT EXISTS installment_id uuid;
44352
+
44353
+ DO $$
44354
+ BEGIN
44355
+ IF NOT EXISTS (
44356
+ SELECT 1 FROM pg_constraint
44357
+ WHERE conrelid = 'public.plg_financial_cnab_items'::regclass
44358
+ AND conname = 'plg_financial_cnab_items_installment_fkey'
44359
+ ) THEN
44360
+ ALTER TABLE public.plg_financial_cnab_items
44361
+ ADD CONSTRAINT plg_financial_cnab_items_installment_fkey
44362
+ FOREIGN KEY (installment_id)
44363
+ REFERENCES public.plg_financial_invoice_installments(id) ON DELETE SET NULL;
44364
+ END IF;
44365
+ END $$;
44366
+
44367
+ -- As linhas j\xE1 migradas: onde o movimento tem parcela de mesmo id, o elo novo
44368
+ -- se preenche sozinho. O que n\xE3o casar fica nulo, que \xE9 a verdade.
44369
+ UPDATE public.plg_financial_cnab_items ci
44370
+ SET installment_id = ci.movement_id
44371
+ WHERE ci.installment_id IS NULL
44372
+ AND ci.movement_id IS NOT NULL
44373
+ AND EXISTS (
44374
+ SELECT 1 FROM public.plg_financial_invoice_installments i WHERE i.id = ci.movement_id
44375
+ );
44376
+
44377
+ CREATE INDEX IF NOT EXISTS plg_financial_cnab_items_installment_idx
44378
+ ON public.plg_financial_cnab_items (tenant_id, installment_id)
44379
+ WHERE installment_id IS NOT NULL;
44380
+
44381
+ COMMENT ON COLUMN public.plg_financial_cnab_items.installment_id IS
44382
+ 'A PARCELA que esta linha da remessa paga \u2014 \xE9 o que a tela de contas tem na m\xE3o. movement_id continua apontando para o raz\xE3o onde o movimento g\xEAmeo existe.';
44383
+
44384
+ -- \u2500\u2500 uma conta padr\xE3o por favorecido, sem derrubar a remessa \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
44385
+ --
44386
+ -- \`payee_accounts_one_default_uidx\` \xE9 \xFAnico em (tenant_id, owner_type, owner_id)
44387
+ -- WHERE is_default, e est\xE1 certo: duas contas padr\xE3o fazem a tela mostrar uma e
44388
+ -- a remessa pagar a outra.
44389
+ --
44390
+ -- O que estava errado era quem escrevia: a remessa inseria sempre com
44391
+ -- \`is_default = true\`, ent\xE3o a SEGUNDA tentativa de gerar o mesmo arquivo \u2014
44392
+ -- depois de a primeira morrer na FK acima \u2014 batia em
44393
+ --
44394
+ -- duplicate key value violates unique constraint
44395
+ --
44396
+ -- O conserto \xE9 do lado de quem escreve (\`saveCnabPayees\` procura antes de
44397
+ -- inserir), e esta fun\xE7\xE3o \xE9 a rede: um \`insert\` que j\xE1 tem padr\xE3o vira o padr\xE3o
44398
+ -- da pessoa sem precisar que o chamador saiba disso.
44399
+ CREATE OR REPLACE FUNCTION public.trg_payee_accounts_one_default()
44400
+ RETURNS trigger
44401
+ LANGUAGE plpgsql
44402
+ SECURITY INVOKER
44403
+ SET search_path = public, pg_catalog
44404
+ AS $$
44405
+ BEGIN
44406
+ IF NEW.is_default THEN
44407
+ UPDATE public.payee_accounts
44408
+ SET is_default = false
44409
+ WHERE tenant_id = NEW.tenant_id
44410
+ AND owner_type = NEW.owner_type
44411
+ AND owner_id = NEW.owner_id
44412
+ AND is_default
44413
+ AND id <> NEW.id;
44414
+ END IF;
44415
+ RETURN NEW;
44416
+ END
44417
+ $$;
44418
+
44419
+ DROP TRIGGER IF EXISTS payee_accounts_one_default ON public.payee_accounts;
44420
+ CREATE TRIGGER payee_accounts_one_default
44421
+ BEFORE INSERT OR UPDATE OF is_default ON public.payee_accounts
44422
+ FOR EACH ROW EXECUTE FUNCTION public.trg_payee_accounts_one_default();
44423
+ `;
44424
+ var MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA = `-- ---------------------------------------------------------------------------
44425
+ -- 116 \u2014 um estorno n\xE3o se estorna.
44426
+ --
44427
+ -- \`financial_reverse_movement\` aceitava qualquer \`payment\`, inclusive a linha
44428
+ -- que ela mesma lan\xE7a como contrapartida. A tela mirava \xABo \xFAltimo pagamento da
44429
+ -- parcela\xBB, que depois do primeiro estorno \xC9 o estorno: cada clique estornava o
44430
+ -- estorno anterior, o dinheiro ia e voltava, e o pagamento vivo continuava l\xE1 \u2014
44431
+ -- a conta seguia \xABpaga\xBB para sempre. Agora o banco recusa com uma mensagem que
44432
+ -- a tela mostra.
44433
+ --
44434
+ -- Idempotente: s\xF3 rep\xF5e a fun\xE7\xE3o.
44435
+ -- ---------------------------------------------------------------------------
44436
+
44437
+ CREATE OR REPLACE FUNCTION public.financial_reverse_movement(p_movement_id uuid, p_reason text, p_idempotency_key text DEFAULT NULL::text) RETURNS jsonb
44438
+ LANGUAGE plpgsql SECURITY DEFINER
44439
+ SET search_path TO ''
44440
+ AS $$
44441
+ DECLARE
44442
+ v_tenant uuid := public.plg_financial_require_tenant();
44443
+ v_m public.plg_financial_movements%ROWTYPE;
44444
+ v_rev uuid;
44445
+ BEGIN
44446
+ IF nullif(btrim(coalesce(p_reason, '')), '') IS NULL THEN
44447
+ RAISE EXCEPTION 'financial_reverse_movement: a reason is required' USING ERRCODE = '22023';
44448
+ END IF;
44449
+ SELECT * INTO v_m FROM public.plg_financial_movements WHERE id = p_movement_id AND tenant_id = v_tenant;
44450
+ IF NOT FOUND THEN
44451
+ RAISE EXCEPTION 'financial_reverse_movement: movement % not found', p_movement_id USING ERRCODE = 'P0002';
44452
+ END IF;
44453
+ PERFORM public.plg_financial_require_permission('financial.edit', v_m.unit_id);
44454
+ IF v_m.movement_kind NOT IN ('payment', 'transfer') OR v_m.debit_account_id IS NULL THEN
44455
+ RAISE EXCEPTION 'financial_reverse_movement: % is not a money event', p_movement_id USING ERRCODE = '55000';
44456
+ END IF;
44457
+ IF v_m.reverses_id IS NOT NULL THEN
44458
+ RAISE EXCEPTION 'Este lan\xE7amento j\xE1 \xE9 um estorno e n\xE3o pode ser estornado.' USING ERRCODE = '55000';
44459
+ END IF;
44460
+ IF v_m.reversed_by_id IS NOT NULL THEN
44461
+ RETURN jsonb_build_object('reversal_id', v_m.reversed_by_id, 'movement_id', v_m.id, 'replayed', true);
44462
+ END IF;
44463
+ IF p_idempotency_key IS NOT NULL THEN
44464
+ PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);
44465
+ SELECT m.id INTO v_rev FROM public.plg_financial_movements m WHERE m.tenant_id = v_tenant AND m.idempotency_key = p_idempotency_key;
44466
+ IF v_rev IS NOT NULL THEN
44467
+ RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', true);
44468
+ END IF;
44469
+ END IF;
44470
+
44471
+ INSERT INTO public.plg_financial_movements
44472
+ (tenant_id, unit_id, invoice_id, financial_invoice_id, financial_invoice_installment_id, direction, movement_kind, amount, paid_amount, status,
44473
+ due_date, payment_date, installment_number, method, cash_session_id, bank_account_id, reason, notes, metadata,
44474
+ idempotency_key, created_by, reverses_id, debit_account_id, credit_account_id)
44475
+ VALUES
44476
+ (v_m.tenant_id, v_m.unit_id, v_m.invoice_id, v_m.financial_invoice_id, v_m.financial_invoice_installment_id,
44477
+ CASE WHEN v_m.direction = 'credit' THEN 'debit' ELSE 'credit' END, v_m.movement_kind, v_m.amount, v_m.amount, 'paid',
44478
+ current_date, current_date, v_m.installment_number, v_m.method, v_m.cash_session_id, v_m.bank_account_id, btrim(p_reason),
44479
+ 'Estorno de ' || v_m.id::text,
44480
+ jsonb_build_object('source', 'financial_reverse_movement', 'reversal_of', v_m.id, 'reason', btrim(p_reason)),
44481
+ p_idempotency_key, auth.uid(), v_m.id, v_m.credit_account_id, v_m.debit_account_id)
44482
+ RETURNING id INTO v_rev;
44483
+
44484
+ UPDATE public.plg_financial_movements SET reversed_by_id = v_rev, reason = coalesce(reason, btrim(p_reason)), updated_at = now()
44485
+ WHERE id = v_m.id;
44486
+ IF v_m.financial_invoice_installment_id IS NOT NULL THEN PERFORM public.plg_financial_sync_mirror(v_m.financial_invoice_installment_id); END IF;
44487
+
44488
+ PERFORM public.plg_financial_audit(v_tenant, 'financial.movement.reverse', 'plg_financial_movements', v_m.id::text,
44489
+ jsonb_build_object('reversal_id', v_rev, 'amount', v_m.amount, 'reason', btrim(p_reason), 'idempotency_key', p_idempotency_key));
44490
+ RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', false);
44491
+ END $$;
44492
+
42902
44493
  `;
42903
44494
  var MIGRATIONS = [
42904
44495
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
@@ -43015,11 +44606,11 @@ var MIGRATIONS = [
43015
44606
  { id: "111_reparcelar_sem_apagar_a_parcela", sql: MIGRATION_111_REPARCELAR_SEM_APAGAR_A_PARCELA },
43016
44607
  { id: "112_a_linha_do_extrato_nao_e_um_lancamento_ainda", sql: MIGRATION_112_A_LINHA_DO_EXTRATO_NAO_E_UM_LANCAMENTO_AINDA },
43017
44608
  { id: "113_so_a_conta_integrada_aparece_na_conciliacao", sql: MIGRATION_113_SO_A_CONTA_INTEGRADA_APARECE_NA_CONCILIACAO },
43018
- { id: "114_a_parcela_diz_por_qual_conta_ela_foi_paga", sql: MIGRATION_114_A_PARCELA_DIZ_POR_QUAL_CONTA_ELA_FOI_PAGA }
44609
+ { id: "114_a_parcela_diz_por_qual_conta_ela_foi_paga", sql: MIGRATION_114_A_PARCELA_DIZ_POR_QUAL_CONTA_ELA_FOI_PAGA },
44610
+ { id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE },
44611
+ { id: "116_um_estorno_nao_se_estorna", sql: MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA }
43019
44612
  ];
43020
- var STANDALONE_CONFIG = {
43021
- currency: { code: "BRL", locale: "pt-BR", symbol: "R$" }
43022
- };
44613
+ var STANDALONE_CONFIG = resolveConfig();
43023
44614
  registerTranslations(financialLocales);
43024
44615
  function ReceivePaymentModal({
43025
44616
  installment,
@@ -43027,8 +44618,8 @@ function ReceivePaymentModal({
43027
44618
  onClose,
43028
44619
  onPaid
43029
44620
  }) {
43030
- const provider = React23__default.useMemo(() => createSafeFinancialProvider(), []);
43031
- const store6 = React23__default.useMemo(() => createFinancialStore(provider), [provider]);
44621
+ const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
44622
+ const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
43032
44623
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
43033
44624
  PaymentModal,
43034
44625
  {
@@ -43046,7 +44637,7 @@ function createFinancialSettingsPlugin(options) {
43046
44637
  ...financialRegistries.filter((r) => RECEIVING_REGISTRIES.includes(r.id)),
43047
44638
  ...options?.extraRegistries ?? []
43048
44639
  ];
43049
- const SettingsTab = () => React23__default.createElement(PluginSettingsPanel, {
44640
+ const SettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
43050
44641
  title: "Pagamentos",
43051
44642
  registries,
43052
44643
  hostPluginId: "financial",
@@ -43103,25 +44694,27 @@ function ClientAccountPanel({
43103
44694
  currency = BRL
43104
44695
  }) {
43105
44696
  const provider = useFinancialProvider();
43106
- const [account, setAccount] = React23__default.useState(null);
43107
- const [busy, setBusy] = React23__default.useState(false);
43108
- const [viewInvoice, setViewInvoice] = React23__default.useState(null);
43109
- const [comprando, setComprando] = React23__default.useState(false);
43110
- const [buscaServico, setBuscaServico] = React23__default.useState("");
43111
- const [catalogo, setCatalogo] = React23__default.useState([]);
43112
- const [escolhidos, setEscolhidos] = React23__default.useState([]);
43113
- const [comprandoBusy, setComprandoBusy] = React23__default.useState(false);
43114
- const [erroCompra, setErroCompra] = React23__default.useState(null);
43115
- const load = React23__default.useCallback(async () => {
44697
+ const [account, setAccount] = React25__default.useState(null);
44698
+ const [busy, setBusy] = React25__default.useState(false);
44699
+ const [viewInvoiceId, setViewInvoiceId] = React25__default.useState(null);
44700
+ const [comprando, setComprando] = React25__default.useState(false);
44701
+ const [buscaServico, setBuscaServico] = React25__default.useState("");
44702
+ const [catalogo, setCatalogo] = React25__default.useState([]);
44703
+ const [escolhidos, setEscolhidos] = React25__default.useState([]);
44704
+ const [comprandoBusy, setComprandoBusy] = React25__default.useState(false);
44705
+ const [erroCompra, setErroCompra] = React25__default.useState(null);
44706
+ const load = React25__default.useCallback(async () => {
43116
44707
  if (!personId) return;
43117
44708
  setAccount(await provider.getClientAccount(personId));
43118
44709
  }, [personId, provider]);
43119
- React23__default.useEffect(() => {
44710
+ React25__default.useEffect(() => {
43120
44711
  void load();
43121
44712
  }, [load]);
44713
+ const viewInvoice = account?.invoices.find((i) => i.id === viewInvoiceId) ?? null;
44714
+ const viewEditable = invoiceOpensForEditing(viewInvoice?.status);
43122
44715
  const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
43123
44716
  const hasForThis = forThis.length > 0;
43124
- React23__default.useEffect(() => {
44717
+ React25__default.useEffect(() => {
43125
44718
  if (!comprando) return;
43126
44719
  let vivo = true;
43127
44720
  const t2 = setTimeout(async () => {
@@ -43331,7 +44924,7 @@ function ClientAccountPanel({
43331
44924
  "button",
43332
44925
  {
43333
44926
  type: "button",
43334
- onClick: () => setViewInvoice(i),
44927
+ onClick: () => setViewInvoiceId(i.id),
43335
44928
  className: "inline-flex shrink-0 items-center gap-1 rounded-button border border-input px-2.5 py-1.5 text-xs font-medium hover:bg-muted",
43336
44929
  children: i.balance > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
43337
44930
  /* @__PURE__ */ jsx(CircleDollarSign, { className: "h-3.5 w-3.5" }),
@@ -43362,17 +44955,31 @@ function ClientAccountPanel({
43362
44955
  hint: clientName ?? void 0,
43363
44956
  width: "max-w-3xl",
43364
44957
  onClose: () => {
43365
- setViewInvoice(null);
44958
+ setViewInvoiceId(null);
43366
44959
  void load();
43367
44960
  },
43368
- children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-3", children: /* @__PURE__ */ jsx(
44961
+ children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-3", children: viewEditable ? /* @__PURE__ */ jsx(
44962
+ InvoiceFormView,
44963
+ {
44964
+ embedded: true,
44965
+ direction: "credit",
44966
+ editId: viewInvoice.id,
44967
+ onSaved: () => {
44968
+ void load();
44969
+ },
44970
+ onChanged: () => {
44971
+ setViewInvoiceId(null);
44972
+ void load();
44973
+ }
44974
+ }
44975
+ ) : /* @__PURE__ */ jsx(
43369
44976
  InvoiceDetailView,
43370
44977
  {
43371
44978
  embedded: true,
43372
44979
  invoiceId: viewInvoice.id,
43373
44980
  direction: "credit",
43374
44981
  onBack: () => {
43375
- setViewInvoice(null);
44982
+ setViewInvoiceId(null);
43376
44983
  void load();
43377
44984
  }
43378
44985
  }
@@ -43382,8 +44989,8 @@ function ClientAccountPanel({
43382
44989
  ] });
43383
44990
  }
43384
44991
  function ClientAccountInline(props) {
43385
- const provider = React23__default.useMemo(() => createSafeFinancialProvider(), []);
43386
- const store6 = React23__default.useMemo(() => createFinancialStore(provider), [provider]);
44992
+ const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
44993
+ const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
43387
44994
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(ClientAccountPanel, { ...props }) });
43388
44995
  }
43389
44996
  function ClientAccountModal({
@@ -43412,30 +45019,6 @@ function createSafeFinancialProvider() {
43412
45019
  );
43413
45020
  }
43414
45021
  var ACCOUNTS_ROUTE = "/registry/accounts";
43415
- var DEFAULT_LABELS = {
43416
- pageTitle: "Financial",
43417
- pageSubtitle: "Financial overview and cash flow management",
43418
- summary: "Summary",
43419
- payables: "Accounts Payable",
43420
- payablesNew: "New",
43421
- payablesList: "List",
43422
- payablesRecurring: "Recurring Expenses",
43423
- receivables: "Accounts Receivable",
43424
- receivablesNew: "New",
43425
- receivablesList: "List",
43426
- cashRegisters: "Cash Registers",
43427
- statements: "Statements",
43428
- commissions: "Commissions",
43429
- commissionsOverview: "Overview",
43430
- commissionsRules: "Rules",
43431
- cards: "Cards",
43432
- goals: "Metas",
43433
- giftCards: "Gift Cards",
43434
- cardsOverview: "Overview",
43435
- cardsReconciliation: "Reconciliation",
43436
- notasFiscais: "Fiscal Invoices"
43437
- };
43438
- var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
43439
45022
  var FINANCIAL_NAV_FEATURES = {
43440
45023
  reconciliation: "fin_reconciliation",
43441
45024
  cards: "fin_cards",
@@ -43445,55 +45028,6 @@ var FINANCIAL_NAV_FEATURES = {
43445
45028
  payables: "fin_payables",
43446
45029
  "cash-registers": "fin_cash_registers"
43447
45030
  };
43448
- var DEFAULT_ITEM_TYPES = [
43449
- { value: "service", label: "Service", icon: "Briefcase" },
43450
- { value: "product", label: "Product", icon: "Package" },
43451
- { value: "other", label: "Other", icon: "MoreHorizontal" }
43452
- ];
43453
- function resolveConfig(options) {
43454
- return {
43455
- modules: {
43456
- payables: options?.modules?.payables !== false,
43457
- receivables: options?.modules?.receivables !== false,
43458
- cashRegisters: options?.modules?.cashRegisters !== false,
43459
- statements: options?.modules?.statements !== false,
43460
- // Era opt-in porque a comissão era um cadastro que nada lia: o item de
43461
- // menu só podia levar a uma tela vazia. Desde 009 existe apuração e desde
43462
- // 012 existe borderô, e a tela mostra o que eles produziram — então o
43463
- // motivo do opt-in acabou junto com a tela vazia.
43464
- commissions: options?.modules?.commissions !== false,
43465
- cards: options?.modules?.cards !== false,
43466
- goals: options?.modules?.goals !== false,
43467
- // Ligado como as outras: quando a loja não está instalada a própria tela
43468
- // diz isso, então o item de menu nunca leva a um beco.
43469
- giftCards: options?.modules?.giftCards !== false,
43470
- // Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
43471
- reconciliation: options?.modules?.reconciliation === true,
43472
- // Opt-in: only shown when an app explicitly enables fiscal-invoice tracking.
43473
- // LIGADA como as outras (`!== false`), e não opt-in. Estava em
43474
- // `=== true`: quem não passasse a opção nunca via a tela, e o V1 tem
43475
- // Notas Fiscais no menu do Financeiro desde sempre. A tela se explica
43476
- // sozinha quando o provider não expõe notas, então ligá-la não deixa
43477
- // ninguém diante de um beco.
43478
- notasFiscais: options?.modules?.notasFiscais !== false
43479
- },
43480
- labels: { ...DEFAULT_LABELS, ...options?.labels },
43481
- currency: { ...DEFAULT_CURRENCY, ...options?.currency },
43482
- moduleNav: options?.moduleNav,
43483
- itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
43484
- enableServiceExecution: options?.enableServiceExecution ?? false,
43485
- contactEntity: {
43486
- archetypeKind: options?.contactEntity?.archetypeKind ?? "client",
43487
- label: options?.contactEntity?.label ?? "Contact"
43488
- },
43489
- locations: options?.locations ?? [],
43490
- entityLookups: options?.entityLookups ?? {},
43491
- contactLookup: options?.contactLookup,
43492
- fiscalProvider: options?.fiscalProvider,
43493
- onBookingClick: options?.onBookingClick,
43494
- navFeatureMap: { ...options?.navFeatureMap }
43495
- };
43496
- }
43497
45031
  function createFinancialPlugin(options) {
43498
45032
  const config = resolveConfig(options);
43499
45033
  registerTranslations(financialLocales);
@@ -43505,11 +45039,11 @@ function createFinancialPlugin(options) {
43505
45039
  const settingsRegistries = registries.filter((r) => r.id !== "bank-accounts");
43506
45040
  const AccountsRegistryPage = createCrudPage(financialAccountEntity, { feature: "financial" });
43507
45041
  AccountsRegistryPage.displayName = "FinancialAccountsRegistryPage";
43508
- const PersonStatementBound = (props) => React23__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
45042
+ const PersonStatementBound = (props) => React25__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
43509
45043
  PersonStatementBound.displayName = "FinancialPersonStatement";
43510
- const SupplierFinancialSettingsBound = (props) => React23__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
45044
+ const SupplierFinancialSettingsBound = (props) => React25__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
43511
45045
  SupplierFinancialSettingsBound.displayName = "FinancialSupplierSettings";
43512
- const PageComponent = () => React23__default.createElement(FinancialPage, { config, provider, store: store6, registries });
45046
+ const PageComponent = () => React25__default.createElement(FinancialPage, { config, provider, store: store6, registries });
43513
45047
  const AgentMount = createFinancialAgentMount({ config, provider });
43514
45048
  return {
43515
45049
  id: "financial",
@@ -43567,7 +45101,7 @@ function createFinancialPlugin(options) {
43567
45101
  // tem — lá é um ícone no cabeçalho, de qualquer tela.
43568
45102
  headerControls: config.modules.cashRegisters ? [{
43569
45103
  id: "financial.cash-session",
43570
- component: (() => React23__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
45104
+ component: (() => React25__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
43571
45105
  order: 90,
43572
45106
  permission: { feature: "financial", action: "read" }
43573
45107
  }] : [],
@@ -43798,9 +45332,9 @@ function createFinancialPlugin(options) {
43798
45332
  // lado, e a corrida de "Plugins" não é partida ao meio.
43799
45333
  group: "Financeiro",
43800
45334
  component: (() => {
43801
- const FinancialSettingsTab = () => React23__default.createElement(PluginSettingsPanel, {
45335
+ const FinancialSettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
43802
45336
  title: "Financial Settings",
43803
- generalSettings: React23__default.createElement(FinancialGeneralSettings, { config }),
45337
+ generalSettings: React25__default.createElement(FinancialGeneralSettings, { config }),
43804
45338
  registries: settingsRegistries,
43805
45339
  hostPluginId: "financial",
43806
45340
  routeBase: "/settings/financial"
@@ -43825,7 +45359,7 @@ function createFinancialPlugin(options) {
43825
45359
  icon: "HandCoins",
43826
45360
  group: "Financeiro",
43827
45361
  component: (() => {
43828
- const CommissionRulesTab = () => React23__default.createElement(CommissionRulesView, { provider });
45362
+ const CommissionRulesTab = () => React25__default.createElement(CommissionRulesView, { provider });
43829
45363
  CommissionRulesTab.displayName = "CommissionRulesTab";
43830
45364
  return CommissionRulesTab;
43831
45365
  })(),