@fayz-ai/plugin-financial 0.18.3 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/components/AttachmentViewer.d.ts +8 -0
  2. package/dist/components/AttachmentViewer.d.ts.map +1 -0
  3. package/dist/components/BankPicker.d.ts +25 -0
  4. package/dist/components/BankPicker.d.ts.map +1 -0
  5. package/dist/components/CnabRemittanceModal.d.ts.map +1 -1
  6. package/dist/components/InstallmentSource.d.ts.map +1 -1
  7. package/dist/components/ReceivePaymentModal.d.ts +14 -3
  8. package/dist/components/ReceivePaymentModal.d.ts.map +1 -1
  9. package/dist/components/ReconcileFlow.d.ts.map +1 -1
  10. package/dist/config.d.ts +12 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/data/registries.d.ts.map +1 -1
  13. package/dist/data/supabase.d.ts.map +1 -1
  14. package/dist/data/tables.d.ts +7 -0
  15. package/dist/data/tables.d.ts.map +1 -1
  16. package/dist/data/types.d.ts +26 -1
  17. package/dist/data/types.d.ts.map +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1629 -411
  20. package/dist/index.js.map +1 -1
  21. package/dist/lib/br-banks.d.ts +13 -0
  22. package/dist/lib/br-banks.d.ts.map +1 -0
  23. package/dist/lib/cnab240.d.ts +106 -0
  24. package/dist/lib/cnab240.d.ts.map +1 -0
  25. package/dist/lib/invoice-list.d.ts +15 -0
  26. package/dist/lib/invoice-list.d.ts.map +1 -1
  27. package/dist/locales/en.d.ts.map +1 -1
  28. package/dist/locales/pt-BR.d.ts.map +1 -1
  29. package/dist/migrations/index.d.ts +1 -0
  30. package/dist/migrations/index.d.ts.map +1 -1
  31. package/dist/types.d.ts +94 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/views/ClientAccountPanel.d.ts.map +1 -1
  34. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  35. package/dist/views/InvoiceFormView.d.ts +18 -1
  36. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  37. package/dist/views/InvoiceListView.d.ts +3 -2
  38. package/dist/views/InvoiceListView.d.ts.map +1 -1
  39. package/dist/views/PayablesView.d.ts.map +1 -1
  40. package/dist/views/ReceivablesView.d.ts.map +1 -1
  41. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,15 +1,94 @@
1
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';
2
+ import React23__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, DatePicker, tourAnchor, 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, 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, shrinkForVision, toInlineFile, askAI, requestChatUpload } from '@fayz-ai/admin';
7
+ import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban, CircleCheckBig, CircleDashed, Paperclip, Camera, DollarSign, CircleAlert, CircleEllipsis, Pencil, Receipt, Gift, Briefcase, Package, CalendarDays, ExternalLink, LayoutList, Layers, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Undo2, ChevronRight, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, Building2, ChevronDown, Calendar, User, Unlink, Eye, Inbox, 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, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, ArrowLeft, 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,
@@ -388,7 +467,14 @@ var T = {
388
467
  marginByMonth: "v_financial_margin_by_month",
389
468
  recurringBills: "plg_financial_recurring_bills",
390
469
  recurringOccurrences: "plg_financial_recurring_bill_occurrences",
391
- discountsGiven: "v_financial_discounts_given"
470
+ discountsGiven: "v_financial_discounts_given",
471
+ /** O arquivo de remessa que já saiu para o banco, e o pagamento dentro dele (080). */
472
+ cnabBatches: "plg_financial_cnab_batches",
473
+ cnabItems: "plg_financial_cnab_items",
474
+ /** Para onde o dinheiro SAI — a conta de quem a casa paga, nunca conta do razão (149). */
475
+ payeeAccounts: "payee_accounts",
476
+ /** A empresa pagadora da unidade: é o CNPJ dela que vai no cabeçalho do CNAB. */
477
+ unitCompanies: "v_unit_company_legal_entities"
392
478
  };
393
479
 
394
480
  // src/lib/accounts.ts
@@ -865,83 +951,685 @@ function createIdempotencyKey(scope) {
865
951
  }
866
952
  return `${scope}:${suffix}`;
867
953
  }
954
+
955
+ // src/lib/cnab240.ts
956
+ var onlyDigits = (s) => (s || "").replace(/\D/g, "");
957
+ var padL = (v, n, c = "0") => String(v ?? "").slice(0, n).padStart(n, c);
958
+ var padR = (v, n, c = " ") => String(v ?? "").slice(0, n).padEnd(n, c);
959
+ var ascii = (s) => (s || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9 .,&'/()-]/g, " ").toUpperCase();
960
+ var num = (v, n) => padL(onlyDigits(String(v ?? "0")), n);
961
+ var alpha = (v, n) => padR(ascii(String(v ?? "")), n);
962
+ var money = (v, n = 15) => padL(Math.round((Number(v) || 0) * 100).toString(), n);
963
+ var dateYMD = (s) => {
964
+ if (!s) return "00000000";
965
+ const d = /* @__PURE__ */ new Date(s + (s.length === 10 ? "T00:00:00" : ""));
966
+ if (Number.isNaN(d.getTime())) return "00000000";
967
+ return `${padL(d.getDate(), 2)}${padL(d.getMonth() + 1, 2)}${padL(d.getFullYear(), 4)}`;
968
+ };
969
+ var stampDate = (d) => `${padL(d.getDate(), 2)}${padL(d.getMonth() + 1, 2)}${padL(d.getFullYear(), 4)}`;
970
+ var stampTime = (d) => `${padL(d.getHours(), 2)}${padL(d.getMinutes(), 2)}${padL(d.getSeconds(), 2)}`;
971
+ var finalize = (s) => s.length < 240 ? s.padEnd(240, " ") : s.slice(0, 240);
972
+ var inscriptionType = (document2) => onlyDigits(document2).length > 11 ? "2" : "1";
973
+ var serviceTypeFor = (form) => form === "tributo" ? "22" : "20";
974
+ var settlementFor = (form, payeeBank, ourBank) => {
975
+ if (form === "pix") return "45";
976
+ if (form === "boleto") return "30";
977
+ if (form === "tributo") return "11";
978
+ if (form === "ted") {
979
+ return payeeBank && ourBank && onlyDigits(payeeBank) === onlyDigits(ourBank) ? "01" : "41";
980
+ }
981
+ return "01";
982
+ };
983
+ var clearingHouseFor = (form, payeeBank, ourBank) => {
984
+ if (form !== "ted") return "000";
985
+ return payeeBank && ourBank && onlyDigits(payeeBank) === onlyDigits(ourBank) ? "000" : "018";
986
+ };
987
+ var pixInitiationCodeFor = (pixKeyType, pixKey) => {
988
+ const type = (pixKeyType || "").toLowerCase();
989
+ const key = String(pixKey || "").trim();
990
+ const digits2 = onlyDigits(key);
991
+ switch (type) {
992
+ case "phone":
993
+ case "telefone":
994
+ return "01";
995
+ case "email":
996
+ return "02";
997
+ case "cpf":
998
+ case "cnpj":
999
+ return "03";
1000
+ case "random":
1001
+ case "aleatoria":
1002
+ case "evp":
1003
+ return "04";
1004
+ case "bank":
1005
+ case "dados_bancarios":
1006
+ return "05";
1007
+ default:
1008
+ if (digits2.length === 11 || digits2.length === 14) return "03";
1009
+ if (key.includes("@")) return "02";
1010
+ if (key.startsWith("+") || digits2.length >= 10) return "01";
1011
+ if (key) return "04";
1012
+ return "05";
1013
+ }
1014
+ };
1015
+ var initiationFieldFor = (form, payee) => form === "pix" ? `${pixInitiationCodeFor(payee.pixKeyType, payee.pixKey)} ` : "03 ";
1016
+ var pixKeyValueFor = (pixKey, initiationCode) => {
1017
+ const key = String(pixKey || "").trim();
1018
+ if (initiationCode === "01" || initiationCode === "03") return onlyDigits(key);
1019
+ if (initiationCode === "02") return key.toLowerCase();
1020
+ return key;
1021
+ };
1022
+ var pixAlpha = (v, n) => padR(
1023
+ String(v ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\x20-\x7E]/g, " "),
1024
+ n
1025
+ );
1026
+ function linhaDigitavelToBarcode(input) {
1027
+ const d = onlyDigits(input || "");
1028
+ if (d.length === 44) return d;
1029
+ if (d.length === 47) {
1030
+ const c1 = d.substring(0, 9);
1031
+ const c2 = d.substring(10, 20);
1032
+ const c3 = d.substring(21, 31);
1033
+ const dv = d.substring(32, 33);
1034
+ const vv = d.substring(33, 47);
1035
+ return c1.substring(0, 4) + dv + vv + c1.substring(4, 9) + c2 + c3;
1036
+ }
1037
+ if (d.length === 48) {
1038
+ return d.substring(0, 11) + d.substring(12, 23) + d.substring(24, 35) + d.substring(36, 47);
1039
+ }
1040
+ return d;
1041
+ }
1042
+ function headerArquivo(ctx2, sequence, at) {
1043
+ return finalize(
1044
+ 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)
1045
+ );
1046
+ }
1047
+ function trailerArquivo(lotes, records) {
1048
+ return finalize("99999999" + padR("", 9) + num(lotes, 6) + num(records, 6) + num(0, 6) + padR("", 205));
1049
+ }
1050
+ function headerLote(ctx2, lote, form, settlement) {
1051
+ const agreement = form === "pix" ? "" : ctx2.bank.agreement || "";
1052
+ return finalize(
1053
+ 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)
1054
+ );
1055
+ }
1056
+ function trailerLote(lote, records, total) {
1057
+ return finalize(
1058
+ "000" + num(lote, 4) + "5" + padR("", 9) + num(records, 6) + money(total, 18) + num(0, 18) + padR("", 6) + padR("", 165) + padR("", 10)
1059
+ );
1060
+ }
1061
+ function segmentoA(ctx2, lote, index, form, item, docNumber) {
1062
+ const payee = item.payee;
1063
+ const byPixKey = form === "pix" && !!payee.pixKey;
1064
+ const favBank = byPixKey ? "0" : payee.bankCode || "0";
1065
+ const favAgency = byPixKey ? "0" : payee.agency || "0";
1066
+ const favAgencyDigit = byPixKey ? "0" : payee.agencyDigit || "0";
1067
+ const favAccount = byPixKey ? "0" : payee.account || "0";
1068
+ const favAccountDigit = byPixKey ? "0" : payee.accountDigit || "0";
1069
+ return finalize(
1070
+ 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)
1071
+ );
1072
+ }
1073
+ function segmentoB(ctx2, lote, index, form, item) {
1074
+ const payee = item.payee;
1075
+ const byPixKey = form === "pix" && !!payee.pixKey;
1076
+ const initiation = pixInitiationCodeFor(payee.pixKeyType, payee.pixKey);
1077
+ const identifier = byPixKey && initiation === "03" ? payee.pixKey : payee.document;
1078
+ const header = num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "B" + initiationFieldFor(form, payee);
1079
+ if (byPixKey) {
1080
+ return finalize(
1081
+ header + inscriptionType(identifier || "") + num(identifier, 14) + pixAlpha("", 35) + pixAlpha("", 60) + pixAlpha(initiation === "03" ? "" : pixKeyValueFor(payee.pixKey, initiation), 99) + padR("", 6) + num(0, 8)
1082
+ );
1083
+ }
1084
+ return finalize(
1085
+ 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)
1086
+ );
1087
+ }
1088
+ function segmentoJ(ctx2, lote, index, item, docNumber) {
1089
+ return finalize(
1090
+ 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)
1091
+ );
1092
+ }
1093
+ function segmentoJ52(ctx2, lote, index, item, docNumber) {
1094
+ const payerDoc = onlyDigits(ctx2.company.document);
1095
+ const payeeDoc = onlyDigits(item.payee.document);
1096
+ return finalize(
1097
+ 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)
1098
+ );
1099
+ }
1100
+ function segmentoO(ctx2, lote, index, item, docNumber) {
1101
+ return finalize(
1102
+ 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)
1103
+ );
1104
+ }
1105
+ function groupItems(items, ourBankCode) {
1106
+ const groups = /* @__PURE__ */ new Map();
1107
+ for (const item of items) {
1108
+ let settlement = settlementFor(item.paymentForm, item.payee.bankCode, ourBankCode);
1109
+ if (item.paymentForm === "boleto") {
1110
+ const barcode = linhaDigitavelToBarcode(item.barcode || "");
1111
+ settlement = barcode.substring(0, 3) === padL(onlyDigits(ourBankCode), 3) ? "30" : "31";
1112
+ }
1113
+ const key = `${item.paymentForm}:${settlement}`;
1114
+ const group = groups.get(key) ?? { form: item.paymentForm, settlement, list: [] };
1115
+ group.list.push(item);
1116
+ groups.set(key, group);
1117
+ }
1118
+ return [...groups.values()];
1119
+ }
1120
+ var docNumberFor = (sequence, index) => sequence * 1e4 + index + 1;
1121
+ function generateCnab240(input) {
1122
+ if (!input.items.length) throw new Error("Uma remessa sem itens n\xE3o \xE9 um arquivo.");
1123
+ const at = input.generatedAt ?? /* @__PURE__ */ new Date();
1124
+ const ctx2 = { company: input.company, bank: input.bank };
1125
+ const lines = [headerArquivo(ctx2, input.sequence, at)];
1126
+ const groups = groupItems(input.items, input.bank.code);
1127
+ const batches = [];
1128
+ let lote = 0;
1129
+ let totalRecords = 1;
1130
+ let line = 0;
1131
+ for (const group of groups) {
1132
+ lote += 1;
1133
+ lines.push(headerLote(ctx2, lote, group.form, group.settlement));
1134
+ let loteRecords = 1;
1135
+ let loteTotal = 0;
1136
+ let index = 0;
1137
+ for (const item of group.list) {
1138
+ const docNumber = docNumberFor(input.sequence, line);
1139
+ line += 1;
1140
+ loteTotal += Number(item.amount) || 0;
1141
+ if (group.form === "boleto") {
1142
+ lines.push(segmentoJ(ctx2, lote, index += 1, item, docNumber));
1143
+ lines.push(segmentoJ52(ctx2, lote, index += 1, item, docNumber));
1144
+ loteRecords += 2;
1145
+ } else if (group.form === "tributo") {
1146
+ lines.push(segmentoO(ctx2, lote, index += 1, item, docNumber));
1147
+ loteRecords += 1;
1148
+ } else {
1149
+ lines.push(segmentoA(ctx2, lote, index += 1, group.form, item, docNumber));
1150
+ lines.push(segmentoB(ctx2, lote, index += 1, group.form, item));
1151
+ loteRecords += 2;
1152
+ }
1153
+ }
1154
+ loteRecords += 1;
1155
+ lines.push(trailerLote(lote, loteRecords, loteTotal));
1156
+ totalRecords += loteRecords;
1157
+ batches.push({ form: group.form, count: group.list.length, total: loteTotal });
1158
+ }
1159
+ totalRecords += 1;
1160
+ lines.push(trailerArquivo(lote, totalRecords));
1161
+ const totalAmount = input.items.reduce((sum, item) => sum + (Number(item.amount) || 0), 0);
1162
+ const bankLabel = String(parseInt(onlyDigits(input.bank.code) || "0", 10));
1163
+ return {
1164
+ fileName: `CI240_${bankLabel}_${String(input.sequence).padStart(4, "0")}.REM`,
1165
+ content: lines.join("\r\n") + "\r\n",
1166
+ layout: `febraban-${padL(onlyDigits(input.bank.code), 3)}`,
1167
+ totalAmount,
1168
+ itemsCount: input.items.length,
1169
+ batches
1170
+ };
1171
+ }
1172
+ function cnabPayeeGaps(items) {
1173
+ const gaps = [];
1174
+ items.forEach((item, index) => {
1175
+ const payee = item.payee;
1176
+ const name = payee.name || `Favorecido #${index + 1}`;
1177
+ const push = (field, message) => gaps.push({ reference: item.reference, name, field, message });
1178
+ if (!onlyDigits(payee.document)) push("document", "CPF/CNPJ");
1179
+ if (item.paymentForm === "pix") {
1180
+ if (!payee.pixKey?.trim()) push("pixKey", "chave PIX");
1181
+ else if (payee.pixKey.trim().toUpperCase().startsWith("00020")) {
1182
+ push("pixKeyTypeEmv", "o campo tem um PIX Copia-e-Cola (EMV), que o CNAB 240 n\xE3o aceita \u2014 informe a chave real");
1183
+ }
1184
+ }
1185
+ if (item.paymentForm === "ted" && (!payee.bankCode?.trim() || !payee.account?.trim())) {
1186
+ push("bankDetails", "banco, ag\xEAncia e conta");
1187
+ }
1188
+ if (item.paymentForm === "boleto" || item.paymentForm === "tributo") {
1189
+ const barcode = linhaDigitavelToBarcode(item.barcode || "");
1190
+ if (barcode.length !== 44) push("barcode", "c\xF3digo de barras ou linha digit\xE1vel");
1191
+ }
1192
+ });
1193
+ return gaps;
1194
+ }
1195
+ function suggestPaymentForm(barcode) {
1196
+ const raw = (barcode || "").trim();
1197
+ const digits2 = onlyDigits(raw);
1198
+ if (/^\d+$/.test(raw) && digits2.length >= 44 && digits2.length <= 48) return "boleto";
1199
+ return "pix";
1200
+ }
1201
+ function inferPixKeyType(key) {
1202
+ const raw = key.trim();
1203
+ const digits2 = onlyDigits(raw);
1204
+ if (raw.toUpperCase().startsWith("00020")) return "aleatoria";
1205
+ if (digits2.length === 11 && !raw.includes("@")) return "cpf";
1206
+ if (digits2.length === 14) return "cnpj";
1207
+ if (raw.includes("@")) return "email";
1208
+ if (digits2.length >= 10 && digits2.length <= 13) return "telefone";
1209
+ return "aleatoria";
1210
+ }
1211
+ function splitAccountDigit(value, digit) {
1212
+ if (digit.trim()) return [value.trim(), digit.trim()];
1213
+ const match = value.trim().match(/^(\d+)\s*[-/.]\s*([0-9xX])$/);
1214
+ return match ? [match[1], match[2].toUpperCase()] : [value.trim(), ""];
1215
+ }
1216
+
1217
+ // src/lib/br-banks.ts
1218
+ var LOGO_BASE = "https://cdn.jsdelivr.net/gh/Tgentil/Bancos-em-SVG@fe1d43f0cf379135bd01c987bc147a04fdf48c6d";
1219
+ var logo = (path) => `${LOGO_BASE}/${path.split("/").map(encodeURIComponent).join("/")}`;
1220
+ var BR_BANKS = [
1221
+ { code: "001", name: "Banco do Brasil", aliases: ["bb"], logo: logo("Banco do Brasil S.A/banco-do-brasil-sem-fundo.svg") },
1222
+ { code: "341", name: "Ita\xFA", aliases: ["itau", "ita\xFA unibanco"], logo: logo("Ita\xFA Unibanco S.A/itau.svg") },
1223
+ { code: "237", name: "Bradesco", logo: logo("Bradesco S.A/bradesco.svg") },
1224
+ { code: "104", name: "Caixa Econ\xF4mica Federal", aliases: ["caixa", "cef"], logo: logo("Caixa Econ\xF4mica Federal/caixa-economica-federal-1.svg") },
1225
+ { code: "033", name: "Santander", logo: logo("Banco Santander Brasil S.A/banco-santander-logo.svg") },
1226
+ { code: "260", name: "Nubank", aliases: ["nu pagamentos"], logo: logo("Nu Pagamentos S.A/nubank-logo-2021.svg") },
1227
+ { code: "077", name: "Inter", aliases: ["banco inter"], logo: logo("Banco Inter S.A/inter.svg") },
1228
+ { code: "336", name: "C6 Bank", aliases: ["c6"], logo: logo("Banco C6 S.A/c6 bank.svg") },
1229
+ { code: "208", name: "BTG Pactual", aliases: ["btg"], logo: logo("Banco BTG Pacutal/btg-pactual.svg") },
1230
+ { code: "756", name: "Sicoob", logo: logo("Sicoob/sicoob-vector-logo.svg") },
1231
+ { code: "748", name: "Sicredi", logo: logo("Sicredi/logo-svg2.svg") },
1232
+ { code: "290", name: "PagBank", aliases: ["pagseguro"], logo: logo("PagSeguro Internet S.A/logo-pagbank.svg") },
1233
+ { code: "197", name: "Stone", logo: logo("Stone Pagamentos S.A/stone.svg") },
1234
+ { code: "323", name: "Mercado Pago", logo: logo("Mercado Pago/mercado-pago.svg") },
1235
+ { code: "542", name: "InfinitePay", aliases: ["cloudwalk"], logo: logo("InfinitePay/InfitePay.svg") },
1236
+ { code: "403", name: "Cora", logo: logo("Cora Sociedade Credito Direto S.A/icone-cora-rosa-pequeno.svg") },
1237
+ { code: "380", name: "PicPay", logo: logo("PicPay/Logo-PicPay.svg") },
1238
+ { code: "364", name: "Ef\xED", aliases: ["gerencianet", "efi bank"], logo: logo("Ef\xED - Gerencianet/logo-efi-bank-laranja.svg") },
1239
+ { code: "461", name: "Asaas", logo: logo("Asaas IP S.A/header-logo-azul.svg") },
1240
+ { code: "422", name: "Safra", logo: logo("Banco Safra S.A/logo-safra.svg") },
1241
+ { code: "212", name: "Banco Original", aliases: ["original"], logo: logo("Banco Original S.A/banco-original-logo-verde.svg") },
1242
+ { code: "655", name: "Banco BV", aliases: ["votorantim", "bv"], logo: logo("Banco Votorantim/banco-bv-logo.svg") },
1243
+ { code: "041", name: "Banrisul", logo: logo("Banrisul/banrisul-logo-2023.svg") },
1244
+ { code: "070", name: "BRB", aliases: ["banco de bras\xEDlia"], logo: logo("BRB - Banco de Brasilia/brb-logo.svg") },
1245
+ { code: "136", name: "Unicred", logo: logo("Unicred/verde.svg") },
1246
+ { code: "133", name: "Cresol", logo: logo("Cresol/Icone-original.svg") },
1247
+ { code: "085", name: "Ailos", logo: logo("Ailos/logo-ailos.svg") },
1248
+ { code: "004", name: "Banco do Nordeste", aliases: ["bnb"], logo: logo("Banco do Nordeste do Brasil S.A/Logo_BNB.svg") },
1249
+ { code: "021", name: "Banestes", logo: logo("Banco do Estado do Espirito Santo/banestes.svg") },
1250
+ { code: "037", name: "Banpar\xE1", logo: logo("Banco do Estado do Para/Logo_do_Banpar\xE1.svg") },
1251
+ { code: "047", name: "Banese", logo: logo("Banco do Estado do Sergipe/logo banese.svg") },
1252
+ { code: "389", name: "Mercantil do Brasil", aliases: ["mercantil"], logo: logo("Banco Mercantil do Brasil S.A/banco-mercantil-novo-azul.svg") },
1253
+ { code: "318", name: "BMG", logo: logo("Banco BMG/banco-bmg-logo.svg") },
1254
+ { code: "707", name: "Daycoval", logo: logo("Banco Daycoval/logo-Daycoval.svg") },
1255
+ { code: "246", name: "ABC Brasil", logo: logo("ABC Brasil/logoabc.svg") },
1256
+ { code: "218", name: "BS2", logo: logo("Banco BS2 S.A/Banco_BS2.svg") },
1257
+ { code: "102", name: "XP", aliases: ["xp investimentos"], logo: logo("XP Investimentos/xp-investimentos-logo.svg") },
1258
+ { code: "536", name: "Neon", logo: logo("Neon/header-logo-neon.svg") }
1259
+ ];
1260
+ var norm = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
1261
+ var digits = (s) => s.replace(/\D/g, "");
1262
+ function findBrBank(code, name) {
1263
+ const c = digits(code ?? "");
1264
+ if (c) {
1265
+ const byCode = BR_BANKS.find((b) => Number(b.code) === Number(c));
1266
+ if (byCode) return byCode;
1267
+ }
1268
+ const n = norm(name ?? "");
1269
+ if (!n) return void 0;
1270
+ return BR_BANKS.find((b) => norm(b.name) === n || b.aliases?.some((a) => norm(a) === n));
1271
+ }
1272
+ function searchBrBanks(query) {
1273
+ const q = norm(query);
1274
+ if (!q) return BR_BANKS;
1275
+ const qd = digits(q);
1276
+ return BR_BANKS.filter(
1277
+ (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))
1278
+ );
1279
+ }
1280
+ function BankLogo({ bank, size = 20 }) {
1281
+ const [broken, setBroken] = useState(false);
1282
+ if (!bank?.logo || broken) {
1283
+ return /* @__PURE__ */ jsx(Landmark, { className: "text-muted-foreground", style: { width: size * 0.8, height: size * 0.8 }, "aria-hidden": true });
1284
+ }
1285
+ return /* @__PURE__ */ jsx(
1286
+ "img",
1287
+ {
1288
+ src: bank.logo,
1289
+ alt: "",
1290
+ width: size,
1291
+ height: size,
1292
+ loading: "lazy",
1293
+ onError: () => setBroken(true),
1294
+ className: "shrink-0 object-contain",
1295
+ style: { width: size, height: size }
1296
+ }
1297
+ );
1298
+ }
1299
+ var fromCatalog = (entry) => entry.compe ? { code: entry.compe, name: entry.name, logo: entry.logoUrl } : null;
1300
+ function BankPicker({ code, name, search, onChange }) {
1301
+ const known = findBrBank(code, name);
1302
+ const toOption = (b) => ({
1303
+ id: b.code,
1304
+ label: b.name,
1305
+ subtitle: b.code,
1306
+ icon: /* @__PURE__ */ jsx(BankLogo, { bank: b }),
1307
+ data: b
1308
+ });
1309
+ const onSearch = async (query) => {
1310
+ const local = searchBrBanks(query);
1311
+ if (!search) return local.map(toOption);
1312
+ try {
1313
+ const remote = (await search(query)).map(fromCatalog).filter(Boolean);
1314
+ if (!remote.length) return local.map(toOption);
1315
+ const merged = remote.map((bank) => findBrBank(bank.code) ?? bank);
1316
+ const seen = new Set(merged.map((bank) => bank.code));
1317
+ return [...merged, ...local.filter((bank) => !seen.has(bank.code))].map(toOption);
1318
+ } catch {
1319
+ return local.map(toOption);
1320
+ }
1321
+ };
1322
+ return /* @__PURE__ */ jsx(
1323
+ SearchSelect,
1324
+ {
1325
+ value: name ? known?.code ?? `name:${name}` : "",
1326
+ displayValue: name ?? "",
1327
+ leading: name ? /* @__PURE__ */ jsx(BankLogo, { bank: known, size: 18 }) : void 0,
1328
+ placeholder: "Busque pelo nome ou n\xFAmero",
1329
+ minChars: 0,
1330
+ loadOnOpen: true,
1331
+ onSearch,
1332
+ onChange: (_id, option) => {
1333
+ const b = option?.data;
1334
+ onChange(b ? { code: b.code, name: b.name } : { code: "", name: "" });
1335
+ },
1336
+ allowCreate: true,
1337
+ createLabel: "Usar",
1338
+ clearLabel: "Limpar",
1339
+ onCreate: (q) => onChange({ code: code ?? "", name: q.trim() }),
1340
+ noResultsLabel: "Nenhum banco com esse nome",
1341
+ debounce: 0
1342
+ }
1343
+ );
1344
+ }
868
1345
  var STEPS = [
869
1346
  { id: "account", label: "Conta banc\xE1ria" },
870
1347
  { id: "payments", label: "Pagamentos" },
871
1348
  { id: "beneficiaries", label: "Favorecidos" },
872
1349
  { id: "review", label: "Revis\xE3o" }
873
1350
  ];
1351
+ var FORM_LABEL = {
1352
+ pix: "PIX",
1353
+ ted: "TED/DOC",
1354
+ boleto: "Boleto",
1355
+ tributo: "Tributo"
1356
+ };
1357
+ var PIX_KEY_TYPES = [
1358
+ { value: "cpf", label: "CPF" },
1359
+ { value: "cnpj", label: "CNPJ" },
1360
+ { value: "email", label: "E-mail" },
1361
+ { value: "telefone", label: "Telefone" },
1362
+ { value: "aleatoria", label: "Aleat\xF3ria" }
1363
+ ];
1364
+ var todayISO = () => {
1365
+ const now2 = /* @__PURE__ */ new Date();
1366
+ return `${now2.getFullYear()}-${String(now2.getMonth() + 1).padStart(2, "0")}-${String(now2.getDate()).padStart(2, "0")}`;
1367
+ };
874
1368
  function textMetadata(account, key) {
875
1369
  const value = account.metadata?.[key];
876
1370
  return typeof value === "string" ? value : "";
877
1371
  }
878
1372
  function draftFromAccount(account) {
1373
+ const [agencyNumber, agencyDigit] = splitAccountDigit(account.agencyNumber || "", textMetadata(account, "agencyDigit"));
1374
+ const [accountNumber, accountDigit] = splitAccountDigit(account.accountNumber || "", textMetadata(account, "accountDigit"));
879
1375
  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"),
1376
+ bankName: account.bankName || "",
1377
+ bankCode: account.bankCode || textMetadata(account, "bankCode"),
1378
+ agencyNumber,
1379
+ agencyDigit,
1380
+ accountNumber,
1381
+ accountDigit,
886
1382
  agreementCode: textMetadata(account, "cnabAgreementCode")
887
1383
  };
888
1384
  }
889
- function missingFields(draft) {
1385
+ function missingFields(draft, companyDocument) {
890
1386
  return [
891
1387
  !draft.bankCode.trim() && "c\xF3digo do banco",
892
1388
  !draft.agencyNumber.trim() && "ag\xEAncia",
893
- !draft.accountNumber.trim() && "conta"
1389
+ !draft.accountNumber.trim() && "conta",
1390
+ !companyDocument.replace(/\D/g, "") && "CNPJ da empresa"
894
1391
  ].filter(Boolean);
895
1392
  }
1393
+ var emptyPayee = (invoice) => ({
1394
+ contactId: invoice.contactId ?? "",
1395
+ name: invoice.contactName || invoice.itemsSummary || "Sem favorecido",
1396
+ document: ""
1397
+ });
1398
+ function lineFromInvoice(invoice, payee) {
1399
+ const base = payee ?? emptyPayee(invoice);
1400
+ const barcode = typeof invoice.metadata?.barcode === "string" ? invoice.metadata.barcode : "";
1401
+ const paymentForm = suggestPaymentForm(barcode);
1402
+ const resolved = { ...base, name: base.name || invoice.contactName || "Sem favorecido" };
1403
+ if (!resolved.pixKey?.trim() && resolved.document.replace(/\D/g, "")) {
1404
+ resolved.pixKey = resolved.document;
1405
+ resolved.pixKeyType = inferPixKeyType(resolved.document);
1406
+ } else if (resolved.pixKey?.trim() && !resolved.pixKeyType?.trim()) {
1407
+ resolved.pixKeyType = inferPixKeyType(resolved.pixKey);
1408
+ }
1409
+ const today5 = todayISO();
1410
+ return {
1411
+ invoice,
1412
+ paymentForm,
1413
+ // Paying on a date already past is a rejected line; the due date only wins
1414
+ // while it is still ahead.
1415
+ paymentDate: invoice.dueDate && invoice.dueDate >= today5 ? invoice.dueDate : today5,
1416
+ barcode: paymentForm === "boleto" ? barcode : "",
1417
+ payee: resolved
1418
+ };
1419
+ }
1420
+ var openAmount = (invoice) => Math.max(0, invoice.totalAmount - invoice.paidAmount);
896
1421
  function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
897
1422
  const { currency } = useFinancialConfig();
898
1423
  const [step, setStep] = React23__default.useState(0);
899
1424
  const [bankAccountId, setBankAccountId] = React23__default.useState("");
900
1425
  const [draft, setDraft] = React23__default.useState(null);
901
- const [saving, setSaving] = React23__default.useState(false);
902
- const [saveError, setSaveError] = React23__default.useState("");
1426
+ const [context, setContext] = React23__default.useState(null);
1427
+ const [companyName, setCompanyName] = React23__default.useState("");
1428
+ const [companyDocument, setCompanyDocument] = React23__default.useState("");
1429
+ const [lines, setLines] = React23__default.useState([]);
1430
+ const [companyUnsaved, setCompanyUnsaved] = React23__default.useState(false);
1431
+ const [loadingContext, setLoadingContext] = React23__default.useState(false);
1432
+ const [busy, setBusy] = React23__default.useState(false);
1433
+ const [error, setError] = React23__default.useState("");
903
1434
  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);
1435
+ const total = invoices.reduce((sum, invoice) => sum + openAmount(invoice), 0);
905
1436
  const account = eligibleAccounts2.find((candidate) => candidate.id === bankAccountId);
906
- const missingBankData = draft ? missingFields(draft) : [];
907
- const selectAccount = (id) => {
1437
+ const missingBankData = draft ? missingFields(draft, companyDocument) : [];
1438
+ const items = React23__default.useMemo(() => lines.map((line) => ({
1439
+ reference: line.invoice.id,
1440
+ amount: openAmount(line.invoice),
1441
+ paymentDate: line.paymentDate,
1442
+ paymentForm: line.paymentForm,
1443
+ barcode: line.barcode,
1444
+ payee: {
1445
+ name: line.payee.name,
1446
+ document: line.payee.document,
1447
+ bankCode: line.payee.bankCode,
1448
+ agency: line.payee.agencyNumber,
1449
+ agencyDigit: line.payee.agencyDigit,
1450
+ account: line.payee.accountNumber,
1451
+ accountDigit: line.payee.accountDigit,
1452
+ pixKey: line.payee.pixKey,
1453
+ pixKeyType: line.payee.pixKeyType,
1454
+ street: line.payee.street,
1455
+ number: line.payee.number,
1456
+ complement: line.payee.complement,
1457
+ district: line.payee.district,
1458
+ city: line.payee.city,
1459
+ state: line.payee.state,
1460
+ postalCode: line.payee.postalCode
1461
+ }
1462
+ })), [lines]);
1463
+ const gaps = React23__default.useMemo(() => cnabPayeeGaps(items), [items]);
1464
+ const batches = React23__default.useMemo(() => {
1465
+ const grouped = /* @__PURE__ */ new Map();
1466
+ for (const line of lines) {
1467
+ const current = grouped.get(line.paymentForm) ?? { count: 0, total: 0 };
1468
+ current.count += 1;
1469
+ current.total += openAmount(line.invoice);
1470
+ grouped.set(line.paymentForm, current);
1471
+ }
1472
+ return [...grouped.entries()];
1473
+ }, [lines]);
1474
+ const selectAccount = async (id) => {
908
1475
  const selected = eligibleAccounts2.find((candidate) => candidate.id === id);
909
1476
  setBankAccountId(id);
910
1477
  setDraft(selected ? draftFromAccount(selected) : null);
911
- setSaveError("");
1478
+ setError("");
1479
+ if (!selected || !provider.getCnabContext) {
1480
+ setLines(invoices.map((invoice) => lineFromInvoice(invoice, void 0)));
1481
+ return;
1482
+ }
1483
+ setLoadingContext(true);
1484
+ try {
1485
+ const loaded = await provider.getCnabContext({
1486
+ contactIds: invoices.map((invoice) => invoice.contactId ?? "").filter(Boolean),
1487
+ bankAccountId: id,
1488
+ unitId: selected.unitId
1489
+ });
1490
+ setContext(loaded);
1491
+ setCompanyName(loaded.company.name);
1492
+ setCompanyDocument(loaded.company.document);
1493
+ const byContact = new Map(loaded.payees.map((payee) => [payee.contactId, payee]));
1494
+ setLines(invoices.map((invoice) => lineFromInvoice(invoice, byContact.get(invoice.contactId ?? ""))));
1495
+ } catch (cause) {
1496
+ setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel carregar os favorecidos.");
1497
+ setLines(invoices.map((invoice) => lineFromInvoice(invoice, void 0)));
1498
+ } finally {
1499
+ setLoadingContext(false);
1500
+ }
912
1501
  };
913
1502
  const updateDraft = (key, value) => {
914
1503
  setDraft((current) => current ? { ...current, [key]: value } : current);
915
- setSaveError("");
1504
+ setError("");
1505
+ };
1506
+ const updateLine = (index, patch) => setLines((current) => current.map((line, i) => i === index ? { ...line, ...patch } : line));
1507
+ const updatePayee = (index, patch) => setLines((current) => current.map((line, i) => i === index ? { ...line, payee: { ...line.payee, ...patch } } : line));
1508
+ const saveBankDraft = async () => {
1509
+ if (!account || !draft) return;
1510
+ await provider.updateBankAccount(account.id, {
1511
+ bankCode: draft.bankCode.trim(),
1512
+ bankName: draft.bankName.trim() || void 0,
1513
+ agencyNumber: draft.agencyNumber.trim(),
1514
+ accountNumber: draft.accountNumber.trim(),
1515
+ metadata: {
1516
+ ...account.metadata,
1517
+ agencyDigit: draft.agencyDigit.trim(),
1518
+ accountDigit: draft.accountDigit.trim(),
1519
+ cnabAgreementCode: draft.agreementCode.trim()
1520
+ }
1521
+ });
1522
+ };
1523
+ const saveCompany = async () => {
1524
+ if (!provider.saveCompany) return;
1525
+ const loaded = context?.company;
1526
+ const unchanged = loaded && loaded.document.replace(/\D/g, "") === companyDocument.replace(/\D/g, "") && loaded.name === companyName;
1527
+ if (unchanged) return;
1528
+ try {
1529
+ const saved = await provider.saveCompany({
1530
+ id: loaded?.id,
1531
+ unitId: account?.unitId,
1532
+ name: companyName,
1533
+ document: companyDocument
1534
+ });
1535
+ setContext((current) => current ? { ...current, company: { ...current.company, id: saved.id, name: companyName, document: companyDocument } } : current);
1536
+ } catch {
1537
+ setCompanyUnsaved(true);
1538
+ }
916
1539
  };
917
1540
  const advance = async () => {
918
- if (step !== 0) {
919
- setStep((value) => Math.min(value + 1, 3));
1541
+ setError("");
1542
+ if (step === 0) {
1543
+ if (!account || !draft || missingBankData.length > 0) return;
1544
+ setBusy(true);
1545
+ try {
1546
+ await saveBankDraft();
1547
+ await saveCompany();
1548
+ setStep(1);
1549
+ } catch (cause) {
1550
+ setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel salvar os dados banc\xE1rios.");
1551
+ } finally {
1552
+ setBusy(false);
1553
+ }
920
1554
  return;
921
1555
  }
922
- if (!account || !draft || missingBankData.length > 0) return;
923
- setSaving(true);
924
- setSaveError("");
1556
+ if (step === 2 && gaps.length > 0) return;
1557
+ setStep((value) => Math.min(value + 1, 3));
1558
+ };
1559
+ const generate = async () => {
1560
+ if (!account || !draft) return;
1561
+ setBusy(true);
1562
+ setError("");
925
1563
  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
- }
1564
+ const sequence = context?.nextSequence ?? 1;
1565
+ const file = generateCnab240({
1566
+ sequence,
1567
+ company: { name: companyName, document: companyDocument },
1568
+ bank: {
1569
+ code: draft.bankCode,
1570
+ name: draft.bankName,
1571
+ agency: draft.agencyNumber,
1572
+ agencyDigit: draft.agencyDigit,
1573
+ account: draft.accountNumber,
1574
+ accountDigit: draft.accountDigit,
1575
+ agreement: draft.agreementCode
1576
+ },
1577
+ items
937
1578
  });
938
- setStep(1);
939
- } catch (error) {
940
- setSaveError(error instanceof Error ? error.message : "N\xE3o foi poss\xEDvel salvar os dados banc\xE1rios.");
1579
+ if (provider.saveCnabPayees) {
1580
+ const seen = /* @__PURE__ */ new Set();
1581
+ const payload = lines.filter((line) => {
1582
+ if (!line.payee.contactId || seen.has(line.payee.contactId)) return false;
1583
+ seen.add(line.payee.contactId);
1584
+ return true;
1585
+ }).map((line) => ({
1586
+ contactId: line.payee.contactId,
1587
+ payeeAccountId: line.payee.payeeAccountId,
1588
+ document: line.payee.document,
1589
+ bankCode: line.payee.bankCode,
1590
+ bankName: line.payee.bankName,
1591
+ agencyNumber: line.payee.agencyNumber,
1592
+ agencyDigit: line.payee.agencyDigit,
1593
+ accountNumber: line.payee.accountNumber,
1594
+ accountDigit: line.payee.accountDigit,
1595
+ accountType: line.payee.accountType,
1596
+ pixKey: line.payee.pixKey,
1597
+ pixKeyType: line.payee.pixKeyType
1598
+ }));
1599
+ if (payload.length) await provider.saveCnabPayees(payload);
1600
+ }
1601
+ if (provider.recordCnabBatch) {
1602
+ await provider.recordCnabBatch({
1603
+ bankAccountId: account.id,
1604
+ unitId: account.unitId,
1605
+ layout: file.layout,
1606
+ sequenceNumber: sequence,
1607
+ totalAmount: file.totalAmount,
1608
+ fileName: file.fileName,
1609
+ fileContent: file.content,
1610
+ items: lines.map((line) => ({
1611
+ movementId: line.invoice.id,
1612
+ paymentForm: line.paymentForm,
1613
+ paymentDate: line.paymentDate,
1614
+ amount: openAmount(line.invoice)
1615
+ }))
1616
+ });
1617
+ }
1618
+ const blob = new Blob([file.content], { type: "text/plain;charset=us-ascii" });
1619
+ const url = URL.createObjectURL(blob);
1620
+ const anchor = document.createElement("a");
1621
+ anchor.href = url;
1622
+ anchor.download = file.fileName;
1623
+ anchor.click();
1624
+ URL.revokeObjectURL(url);
1625
+ onClose();
1626
+ } catch (cause) {
1627
+ setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel gerar o arquivo.");
941
1628
  } finally {
942
- setSaving(false);
1629
+ setBusy(false);
943
1630
  }
944
1631
  };
1632
+ const canAdvance = step === 0 ? !!account && !!draft && missingBankData.length === 0 && !loadingContext : step === 2 ? gaps.length === 0 : true;
945
1633
  return createPortal(
946
1634
  /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
947
1635
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-[2px]", onClick: onClose }),
@@ -983,82 +1671,203 @@ function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
983
1671
  id: candidate.id,
984
1672
  label: candidate.name,
985
1673
  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" })
1674
+ icon: /* @__PURE__ */ jsx(BankLogo, { bank: findBrBank(candidate.bankCode || textMetadata(candidate, "bankCode"), candidate.bankName) })
987
1675
  }));
988
1676
  },
989
- onChange: (id) => selectAccount(id)
1677
+ onChange: (id) => void selectAccount(id)
990
1678
  }
991
1679
  ),
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." })
1680
+ draft ? /* @__PURE__ */ jsxs(Fragment, { children: [
1681
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border bg-muted/20 p-4", children: [
1682
+ /* @__PURE__ */ jsxs("div", { children: [
1683
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Empresa pagadora" }),
1684
+ /* @__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." })
1685
+ ] }),
1686
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
1687
+ /* @__PURE__ */ jsx(Field, { label: "Raz\xE3o social / nome", value: companyName, onChange: setCompanyName }),
1688
+ /* @__PURE__ */ jsx(Field, { label: "CNPJ", required: true, value: companyDocument, placeholder: "00.000.000/0000-00", onChange: setCompanyDocument })
1689
+ ] })
996
1690
  ] }),
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) })
1691
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border bg-muted/20 p-4", children: [
1692
+ /* @__PURE__ */ jsxs("div", { children: [
1693
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados da conta para a remessa" }),
1694
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Preencha uma vez; os pr\xF3ximos arquivos reutilizam esta configura\xE7\xE3o." })
1003
1695
  ] }),
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) })
1696
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
1697
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 sm:col-span-2", children: [
1698
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
1699
+ "Banco",
1700
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
1701
+ ] }),
1702
+ /* @__PURE__ */ jsx(
1703
+ BankPicker,
1704
+ {
1705
+ code: draft.bankCode,
1706
+ name: draft.bankName,
1707
+ search: provider.searchBanks ? (query) => provider.searchBanks(query) : void 0,
1708
+ onChange: ({ code, name }) => setDraft((current) => current ? { ...current, bankCode: code || current.bankCode, bankName: name } : current)
1709
+ }
1710
+ ),
1711
+ draft.bankCode ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1712
+ "C\xF3digo ",
1713
+ draft.bankCode
1714
+ ] }) : null
1715
+ ] }),
1716
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
1717
+ /* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: draft.agencyNumber, onChange: (value) => updateDraft("agencyNumber", value), className: "col-span-2" }),
1718
+ /* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.agencyDigit, onChange: (value) => updateDraft("agencyDigit", value) })
1719
+ ] }),
1720
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
1721
+ /* @__PURE__ */ jsx(Field, { label: "Conta", required: true, value: draft.accountNumber, onChange: (value) => updateDraft("accountNumber", value), className: "col-span-2" }),
1722
+ /* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.accountDigit, onChange: (value) => updateDraft("accountDigit", value) })
1723
+ ] }),
1724
+ /* @__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
1725
  ] }),
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: [
1726
+ missingBankData.length > 0 ? /* @__PURE__ */ jsxs(Warning, { children: [
1013
1727
  "Complete ",
1014
1728
  missingBankData.join(", "),
1015
1729
  " para continuar."
1016
- ] })
1017
- ] }) : null,
1018
- saveError ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: saveError }) : null
1730
+ ] }) : null
1731
+ ] })
1019
1732
  ] }) : null,
1020
1733
  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
1734
  ] }) : null,
1022
1735
  step === 1 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1023
1736
  /* @__PURE__ */ jsxs("div", { children: [
1024
1737
  /* @__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." })
1738
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Defina a forma de pagamento e a data de pagamento de cada parcela." })
1026
1739
  ] }),
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" }),
1740
+ lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-3 rounded-lg border p-3 sm:grid-cols-12", children: [
1741
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 sm:col-span-4", children: [
1742
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: line.payee.name }),
1030
1743
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1031
- "Vencimento ",
1032
- invoice.dueDate || "\u2014",
1033
- invoice.fiscalNumber ? ` \xB7 #${invoice.fiscalNumber}` : ""
1744
+ "Venc.: ",
1745
+ line.invoice.dueDate || "\u2014",
1746
+ line.invoice.fiscalNumber ? ` \xB7 NF ${line.invoice.fiscalNumber}` : ""
1034
1747
  ] })
1035
1748
  ] }),
1036
- /* @__PURE__ */ jsx("span", { className: "shrink-0 font-medium tabular-nums", children: formatCurrency(Math.max(0, invoice.totalAmount - invoice.paidAmount), currency) })
1037
- ] }, invoice.id)) })
1749
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold tabular-nums sm:col-span-2", children: formatCurrency(openAmount(line.invoice), currency) }),
1750
+ /* @__PURE__ */ jsx("div", { className: "sm:col-span-3", children: /* @__PURE__ */ jsxs(Select, { value: line.paymentForm, onValueChange: (value) => updateLine(index, { paymentForm: value }), children: [
1751
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-9", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1752
+ /* @__PURE__ */ jsx(SelectContent, { children: Object.keys(FORM_LABEL).map((form) => /* @__PURE__ */ jsx(SelectItem, { value: form, children: FORM_LABEL[form] }, form)) })
1753
+ ] }) }),
1754
+ /* @__PURE__ */ jsx("div", { className: "sm:col-span-3", children: /* @__PURE__ */ jsx(DatePicker, { value: line.paymentDate, format: "numeric", onChange: (value) => updateLine(index, { paymentDate: value }) }) })
1755
+ ] }, line.invoice.id))
1756
+ ] }) : null,
1757
+ step === 2 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1758
+ /* @__PURE__ */ jsxs("div", { children: [
1759
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados dos favorecidos" }),
1760
+ /* @__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." })
1761
+ ] }),
1762
+ lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border p-3", children: [
1763
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1764
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
1765
+ line.payee.name,
1766
+ /* @__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] })
1767
+ ] }),
1768
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: formatCurrency(openAmount(line.invoice), currency) })
1769
+ ] }),
1770
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-12", children: [
1771
+ /* @__PURE__ */ jsx(Field, { label: "CPF/CNPJ", required: true, value: line.payee.document, onChange: (value) => updatePayee(index, { document: value }), className: "sm:col-span-4" }),
1772
+ line.paymentForm === "pix" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1773
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 sm:col-span-3", children: [
1774
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Tipo da chave" }),
1775
+ /* @__PURE__ */ jsxs(Select, { value: line.payee.pixKeyType || "", onValueChange: (value) => updatePayee(index, { pixKeyType: value }), children: [
1776
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-9", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Tipo" }) }),
1777
+ /* @__PURE__ */ jsx(SelectContent, { children: PIX_KEY_TYPES.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
1778
+ ] })
1779
+ ] }),
1780
+ /* @__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" })
1781
+ ] }) : null,
1782
+ line.paymentForm === "ted" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1783
+ /* @__PURE__ */ jsx(Field, { label: "Banco", required: true, value: line.payee.bankCode || "", onChange: (value) => updatePayee(index, { bankCode: value }), className: "sm:col-span-2" }),
1784
+ /* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: line.payee.agencyNumber || "", onChange: (value) => updatePayee(index, { agencyNumber: value }), className: "sm:col-span-2" }),
1785
+ /* @__PURE__ */ jsx(Field, { label: "DV", value: line.payee.agencyDigit || "", onChange: (value) => updatePayee(index, { agencyDigit: value }), className: "sm:col-span-1" }),
1786
+ /* @__PURE__ */ jsx(Field, { label: "Conta", required: true, value: line.payee.accountNumber || "", onChange: (value) => updatePayee(index, { accountNumber: value }), className: "sm:col-span-2" }),
1787
+ /* @__PURE__ */ jsx(Field, { label: "DV", value: line.payee.accountDigit || "", onChange: (value) => updatePayee(index, { accountDigit: value }), className: "sm:col-span-1" })
1788
+ ] }) : null,
1789
+ 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
1790
+ ] })
1791
+ ] }, line.invoice.id)),
1792
+ gaps.length > 0 ? /* @__PURE__ */ jsxs(Warning, { children: [
1793
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Falta preencher:" }),
1794
+ /* @__PURE__ */ jsxs("ul", { className: "mt-1 space-y-0.5", children: [
1795
+ gaps.slice(0, 5).map((gap, index) => /* @__PURE__ */ jsxs("li", { children: [
1796
+ gap.name,
1797
+ ": ",
1798
+ gap.message
1799
+ ] }, `${gap.reference}-${gap.field}-${index}`)),
1800
+ gaps.length > 5 ? /* @__PURE__ */ jsxs("li", { className: "text-muted-foreground", children: [
1801
+ "e mais ",
1802
+ gaps.length - 5,
1803
+ "\u2026"
1804
+ ] }) : null
1805
+ ] })
1806
+ ] }) : null
1038
1807
  ] }) : 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
1808
+ step === 3 ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1809
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Resumo da remessa" }),
1810
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 text-sm sm:grid-cols-2", children: [
1811
+ /* @__PURE__ */ jsx(Summary, { label: "Banco pagador", value: `${draft?.bankCode ?? ""} \u2014 ${draft?.bankName || "\u2014"}` }),
1812
+ /* @__PURE__ */ jsx(Summary, { label: "Conta", value: `Ag ${draft?.agencyNumber ?? ""} / Cc ${draft?.accountNumber ?? ""}${draft?.accountDigit ? `-${draft.accountDigit}` : ""}` }),
1813
+ /* @__PURE__ */ jsx(Summary, { label: "Empresa", value: `${companyName || "\u2014"} (${companyDocument || "\u2014"})` }),
1814
+ /* @__PURE__ */ jsx(Summary, { label: "Total", value: formatCurrency(total, currency), strong: true }),
1815
+ /* @__PURE__ */ jsx(Summary, { label: "N\xBA da remessa (NSA)", value: String(context?.nextSequence ?? 1) })
1816
+ ] }),
1817
+ /* @__PURE__ */ jsxs("div", { className: "border-t pt-3", children: [
1818
+ /* @__PURE__ */ jsx("h4", { className: "mb-2 text-sm font-medium", children: "Lotes" }),
1819
+ /* @__PURE__ */ jsx("div", { className: "space-y-1 text-sm", children: batches.map(([form, group]) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
1820
+ /* @__PURE__ */ jsxs("span", { children: [
1821
+ FORM_LABEL[form],
1822
+ " \u2014 ",
1823
+ group.count,
1824
+ " t\xEDtulo(s)"
1825
+ ] }),
1826
+ /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums", children: formatCurrency(group.total, currency) })
1827
+ ] }, form)) })
1828
+ ] }),
1829
+ /* @__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." })
1830
+ ] }) : null,
1831
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-4 text-sm text-destructive", children: error }) : null
1044
1832
  ] }),
1045
1833
  /* @__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: [
1834
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => step === 0 ? onClose() : setStep((value) => value - 1), disabled: busy, children: [
1047
1835
  step > 0 ? /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }) : null,
1048
1836
  step === 0 ? "Cancelar" : "Voltar"
1049
1837
  ] }),
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" })
1838
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1839
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
1840
+ invoices.length,
1841
+ " parcela(s) selecionada(s)"
1842
+ ] }),
1843
+ step < 3 ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void advance(), disabled: busy || !canAdvance, children: [
1844
+ busy || loadingContext ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
1845
+ step === 0 ? "Salvar e avan\xE7ar" : "Avan\xE7ar",
1846
+ " ",
1847
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" })
1848
+ ] }) : /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void generate(), disabled: busy || gaps.length > 0, children: [
1849
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(FileDown, { className: "h-4 w-4" }),
1850
+ " Gerar arquivo .REM"
1851
+ ] })
1852
+ ] })
1056
1853
  ] })
1057
1854
  ] })
1058
1855
  ] }),
1059
1856
  document.body
1060
1857
  );
1061
1858
  }
1859
+ function Warning({ children }) {
1860
+ return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 rounded-lg border border-warning/30 bg-warning/10 p-3 text-sm", children: [
1861
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-warning" }),
1862
+ /* @__PURE__ */ jsx("div", { children })
1863
+ ] });
1864
+ }
1865
+ function Summary({ label, value, strong }) {
1866
+ return /* @__PURE__ */ jsxs("div", { children: [
1867
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: label }),
1868
+ /* @__PURE__ */ jsx("div", { className: strong ? "text-lg font-semibold" : "font-medium", children: value })
1869
+ ] });
1870
+ }
1062
1871
  function Field({ label, value, onChange, required, placeholder, className = "" }) {
1063
1872
  return /* @__PURE__ */ jsxs("label", { className: `block space-y-1.5 ${className}`, children: [
1064
1873
  /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
@@ -1068,6 +1877,98 @@ function Field({ label, value, onChange, required, placeholder, className = "" }
1068
1877
  /* @__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
1878
  ] });
1070
1879
  }
1880
+ function AttachmentViewer({ attachments, index, onIndexChange, onClose }) {
1881
+ if (!attachments[index]) return null;
1882
+ const doc = attachments[index];
1883
+ const ehImagem = (doc.mimeType ?? "").startsWith("image/");
1884
+ const ehPdf = (doc.mimeType ?? "") === "application/pdf";
1885
+ const irPara = (delta) => onIndexChange((index + delta + attachments.length) % attachments.length);
1886
+ return createPortal(
1887
+ /* @__PURE__ */ jsx(
1888
+ "div",
1889
+ {
1890
+ "data-modal-passthrough": true,
1891
+ className: "pointer-events-auto fixed inset-0 z-[200] flex flex-col bg-black/80 p-4",
1892
+ onClick: () => onClose(),
1893
+ role: "dialog",
1894
+ "aria-modal": "true",
1895
+ "aria-label": doc.fileName,
1896
+ children: /* @__PURE__ */ jsxs(
1897
+ "div",
1898
+ {
1899
+ className: "mx-auto flex w-full max-w-4xl flex-1 flex-col gap-3 overflow-hidden",
1900
+ onClick: (e) => e.stopPropagation(),
1901
+ children: [
1902
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-white", children: [
1903
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 shrink-0 opacity-70", "aria-hidden": true }),
1904
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1905
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", title: doc.fileName, children: doc.fileName }),
1906
+ /* @__PURE__ */ jsxs("p", { className: "text-[11px] opacity-60", children: [
1907
+ attachments.length > 1 ? `${index + 1} de ${attachments.length}` : null,
1908
+ doc.fileSize ? `${attachments.length > 1 ? " \xB7 " : ""}${Math.round(doc.fileSize / 1024)} KB` : null
1909
+ ] })
1910
+ ] }),
1911
+ doc.url ? /* @__PURE__ */ jsxs(
1912
+ "a",
1913
+ {
1914
+ href: doc.url,
1915
+ target: "_blank",
1916
+ rel: "noreferrer",
1917
+ 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",
1918
+ children: [
1919
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
1920
+ " Abrir"
1921
+ ]
1922
+ }
1923
+ ) : null,
1924
+ /* @__PURE__ */ jsx(
1925
+ "button",
1926
+ {
1927
+ type: "button",
1928
+ onClick: () => onClose(),
1929
+ "aria-label": "Fechar",
1930
+ 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",
1931
+ children: "Fechar"
1932
+ }
1933
+ )
1934
+ ] }),
1935
+ /* @__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: [
1936
+ "O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por ",
1937
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
1938
+ "."
1939
+ ] }) : 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: [
1940
+ "Este tipo de arquivo n\xE3o tem pr\xE9-visualiza\xE7\xE3o. Use ",
1941
+ /* @__PURE__ */ jsx("strong", { children: "Abrir" }),
1942
+ "."
1943
+ ] }) }),
1944
+ attachments.length > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
1945
+ /* @__PURE__ */ jsx(
1946
+ "button",
1947
+ {
1948
+ type: "button",
1949
+ onClick: () => irPara(-1),
1950
+ className: "rounded-lg border border-white/20 px-3 py-1.5 text-xs font-medium text-white hover:bg-white/10",
1951
+ children: "Anterior"
1952
+ }
1953
+ ),
1954
+ /* @__PURE__ */ jsx(
1955
+ "button",
1956
+ {
1957
+ type: "button",
1958
+ onClick: () => irPara(1),
1959
+ className: "rounded-lg border border-white/20 px-3 py-1.5 text-xs font-medium text-white hover:bg-white/10",
1960
+ children: "Pr\xF3ximo"
1961
+ }
1962
+ )
1963
+ ] })
1964
+ ]
1965
+ }
1966
+ )
1967
+ }
1968
+ ),
1969
+ document.body
1970
+ );
1971
+ }
1071
1972
  var KIND_LABELS = {
1072
1973
  supplier: "Fornecedor",
1073
1974
  customer: "Cliente",
@@ -1173,49 +2074,61 @@ function StatusBadge({ status }) {
1173
2074
  opt ? t2(opt.labelKey) : status
1174
2075
  ] });
1175
2076
  }
1176
- function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating) {
2077
+ function formatDueDate(iso) {
2078
+ if (!iso) return "\u2014";
2079
+ const [y, m, d] = iso.slice(0, 10).split("-");
2080
+ return d && m && y ? `${d}/${m}/${y}` : iso;
2081
+ }
2082
+ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments) {
1177
2083
  const t2 = useTranslation();
1178
2084
  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
2085
  {
1192
2086
  accessorKey: "dueDate",
1193
2087
  header: t2("financial.invoice.columnDueDate"),
1194
- cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: getValue() })
2088
+ cell: ({ row }) => {
2089
+ const total = row.original.totalInstallments ?? 1;
2090
+ const atual = row.original.installmentNumber ?? 1;
2091
+ return /* @__PURE__ */ jsxs("div", { className: "whitespace-nowrap", children: [
2092
+ /* @__PURE__ */ jsxs("p", { className: "text-sm tabular-nums", children: [
2093
+ formatDueDate(row.original.dueDate),
2094
+ total > 1 && /* @__PURE__ */ jsxs("span", { className: "ml-1.5 text-xs text-muted-foreground", title: t2("financial.invoice.columnInst"), children: [
2095
+ atual,
2096
+ "/",
2097
+ total
2098
+ ] })
2099
+ ] }),
2100
+ row.original.fiscalNumber && /* @__PURE__ */ jsxs("p", { className: "font-mono text-[10px] text-muted-foreground", children: [
2101
+ "#",
2102
+ row.original.fiscalNumber
2103
+ ] })
2104
+ ] });
2105
+ }
1195
2106
  },
1196
2107
  {
1197
2108
  id: "description",
1198
2109
  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
- ] })
2110
+ cell: ({ row }) => (
2111
+ // Full cell width with the badge pinned right, so the statuses line up in
2112
+ // one column instead of trailing each name.
2113
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full min-w-[250px] items-center gap-3", onClick: (e) => e.stopPropagation(), children: [
2114
+ /* @__PURE__ */ jsxs("div", { className: "w-0 min-w-0 flex-1", children: [
2115
+ 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" }),
2116
+ row.original.itemsSummary && /* @__PURE__ */ jsx("p", { className: "truncate text-[10px] text-muted-foreground", children: row.original.itemsSummary })
2117
+ ] }),
2118
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsx(StatusBadge, { status: row.original.status }) })
2119
+ ] })
2120
+ )
1203
2121
  },
1204
2122
  {
1205
2123
  accessorKey: "totalAmount",
1206
2124
  header: t2("financial.invoice.columnAmount"),
1207
2125
  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) })
2126
+ 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
2127
  },
1210
2128
  {
1211
2129
  accessorKey: "paidAmount",
1212
2130
  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() })
2131
+ 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
2132
  },
1220
2133
  {
1221
2134
  id: "paymentMethod",
@@ -1225,7 +2138,7 @@ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAc
1225
2138
  const type = paymentMethodTypes.find((candidate) => candidate.id === (method?.paymentMethodTypeId ?? row.original.paymentMethodTypeId));
1226
2139
  const account = bankAccounts.find((candidate) => candidate.id === row.original.paymentAccountId);
1227
2140
  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: [
2141
+ return /* @__PURE__ */ jsxs("div", { className: "max-w-[150px] text-xs", children: [
1229
2142
  /* @__PURE__ */ jsxs("p", { className: "truncate font-medium", children: [
1230
2143
  method?.name ?? type?.name ?? "\u2014",
1231
2144
  row.original.paymentCardInstallments ? ` \xB7 ${row.original.paymentCardInstallments}x` : ""
@@ -1235,69 +2148,62 @@ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAc
1235
2148
  }
1236
2149
  },
1237
2150
  {
1238
- id: "consolidate",
2151
+ id: "actions",
1239
2152
  header: "",
1240
2153
  // O ÚNICO CAMINHO QUE TRANSFORMA PREVISÃO EM DÍVIDA.
1241
2154
  //
1242
2155
  // O V1 troca as ações da linha recorrente por este botão só — não há
1243
2156
  // anexo, nem estorno, nem fatura para abrir: a ocorrência ainda não é
1244
2157
  // nada disso. Consolidar é o clique que a faz existir.
2158
+ //
2159
+ // Fora dela, o clipe, como no V1: diz que a conta tem comprovante sem
2160
+ // obrigar a abri-la. Sem anexo não desenha nada.
1245
2161
  cell: ({ row }) => {
1246
- if (!row.original.isProvisionedOccurrence) return null;
1247
- const id = row.original.occurrenceId;
1248
- return /* @__PURE__ */ jsxs(
2162
+ if (row.original.isProvisionedOccurrence) {
2163
+ const id = row.original.occurrenceId;
2164
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
2165
+ Button,
2166
+ {
2167
+ size: "sm",
2168
+ variant: "outline",
2169
+ className: "h-8 px-2",
2170
+ disabled: !id || consolidating === id,
2171
+ title: t2("financial.invoice.consolidate"),
2172
+ onClick: (e) => {
2173
+ e.stopPropagation();
2174
+ if (id) void consolidar(id);
2175
+ },
2176
+ children: [
2177
+ 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 }),
2178
+ /* @__PURE__ */ jsx("span", { className: "ml-1 hidden xl:inline", children: t2("financial.invoice.consolidate") })
2179
+ ]
2180
+ }
2181
+ ) });
2182
+ }
2183
+ const n = row.original.attachmentCount ?? 0;
2184
+ if (!n) return null;
2185
+ const label = n === 1 ? "1 anexo" : `${n} anexos`;
2186
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
1249
2187
  Button,
1250
2188
  {
1251
- size: "sm",
1252
- variant: "outline",
1253
- disabled: !id || consolidating === id,
2189
+ size: "icon",
2190
+ variant: "ghost",
2191
+ className: "relative h-8 w-8 text-muted-foreground hover:text-foreground",
2192
+ title: label,
2193
+ "aria-label": label,
1254
2194
  onClick: (e) => {
1255
2195
  e.stopPropagation();
1256
- if (id) void consolidar(id);
2196
+ openAttachments(row.original.invoiceId ?? row.original.id);
1257
2197
  },
1258
2198
  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")
2199
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4", "aria-hidden": true }),
2200
+ n > 1 && /* @__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 })
1261
2201
  ]
1262
2202
  }
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` })
1284
- ]
1285
- }
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 });
2203
+ ) });
1298
2204
  }
1299
2205
  }
1300
- ], [currency, t2, paymentMethods2, paymentMethodTypes, bankAccounts]);
2206
+ ], [currency, t2, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments]);
1301
2207
  }
1302
2208
  function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1303
2209
  const t2 = useTranslation();
@@ -1430,7 +2336,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1430
2336
  setConsolidating(occurrenceId);
1431
2337
  void provider.consolidateOccurrence(occurrenceId).then((invoiceId) => {
1432
2338
  toast.success(t2("financial.invoice.consolidated"));
1433
- if (invoiceId && onEdit) onEdit(invoiceId);
2339
+ if (invoiceId && onEdit) onEdit(invoiceId, "open");
1434
2340
  else void fetchInvoices(query);
1435
2341
  }).catch((err) => {
1436
2342
  toast.error(t2("financial.invoice.consolidateFailed"), {
@@ -1439,7 +2345,14 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1439
2345
  }).finally(() => setConsolidating(null));
1440
2346
  }, [provider, t2, fetchInvoices, query, onEdit]);
1441
2347
  const filtered = invoices.filter((inv) => inv.direction === direction);
1442
- const columns = useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating);
2348
+ const [viewer, setViewer] = useState(null);
2349
+ const openAttachments = useCallback((invoiceId) => {
2350
+ if (!provider.getInvoiceAttachments) return;
2351
+ provider.getInvoiceAttachments(invoiceId).then((attachments) => {
2352
+ if (attachments.length) setViewer({ attachments, index: 0 });
2353
+ }).catch(() => toast.error("N\xE3o foi poss\xEDvel abrir os anexos"));
2354
+ }, [provider]);
2355
+ const columns = useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments);
1443
2356
  const contactLookup = useMemo(() => createFinancialCounterpartyLookup(direction), [direction]);
1444
2357
  const tags = useMemo(
1445
2358
  // `provisioned` fora da barra: ele não é um recorte do que existe, é o
@@ -1451,14 +2364,14 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1451
2364
  const summary = useMemo(() => {
1452
2365
  let totalAmount = 0;
1453
2366
  let paidAmount = 0;
1454
- let openAmount = 0;
2367
+ let openAmount2 = 0;
1455
2368
  for (const inv of filtered) {
1456
2369
  if (inv.status === "cancelled") continue;
1457
2370
  totalAmount += inv.totalAmount;
1458
2371
  paidAmount += inv.paidAmount;
1459
2372
  }
1460
- openAmount = totalAmount - paidAmount;
1461
- return { totalAmount, paidAmount, openAmount };
2373
+ openAmount2 = totalAmount - paidAmount;
2374
+ return { totalAmount, paidAmount, openAmount: openAmount2 };
1462
2375
  }, [filtered]);
1463
2376
  const [cancelTarget, setCancelTarget] = useState(null);
1464
2377
  const [cnabTarget, setCnabTarget] = useState(null);
@@ -1816,7 +2729,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1816
2729
  actions: newActions,
1817
2730
  bulkActions,
1818
2731
  onRowClick: (row) => {
1819
- if (!row.isProvisionedOccurrence) onEdit?.(row.invoiceId ?? row.id);
2732
+ if (!row.isProvisionedOccurrence) onEdit?.(row.invoiceId ?? row.id, row.status);
1820
2733
  },
1821
2734
  rowClassName: (row) => row.isProvisionedOccurrence ? "border-dashed !cursor-default bg-muted/10 text-muted-foreground" : void 0,
1822
2735
  emptyIcon: FileText,
@@ -1825,6 +2738,15 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1825
2738
  onEmptyAction: onNewGated
1826
2739
  }
1827
2740
  ),
2741
+ viewer && /* @__PURE__ */ jsx(
2742
+ AttachmentViewer,
2743
+ {
2744
+ attachments: viewer.attachments,
2745
+ index: viewer.index,
2746
+ onIndexChange: (index) => setViewer((v) => v ? { ...v, index } : v),
2747
+ onClose: () => setViewer(null)
2748
+ }
2749
+ ),
1828
2750
  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
2751
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1830
2752
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.summaryTotal") }),
@@ -2123,12 +3045,12 @@ async function settlePayable(provider, input) {
2123
3045
  };
2124
3046
  }
2125
3047
  const invoice = await provider.getInvoiceById(input.invoiceId).catch(() => null);
2126
- const money3 = (n) => n.toLocaleString("pt-BR", { style: "currency", currency: input.currency ?? "BRL" });
3048
+ const money4 = (n) => n.toLocaleString("pt-BR", { style: "currency", currency: input.currency ?? "BRL" });
2127
3049
  const invoiceLabel = [
2128
3050
  invoice?.fiscalNumber ? `#${invoice.fiscalNumber}` : null,
2129
3051
  invoice?.contactName ?? null,
2130
- money3(bill.amount)
2131
- ].filter(Boolean).join(" \u2014 ") || money3(bill.amount);
3052
+ money4(bill.amount)
3053
+ ].filter(Boolean).join(" \u2014 ") || money4(bill.amount);
2132
3054
  const needsAccount = input.method.transactionType !== "cash";
2133
3055
  const accounts = needsAccount ? await eligibleAccounts(provider, input.method) : [];
2134
3056
  if (needsAccount && !accounts.length) {
@@ -2318,7 +3240,7 @@ function createPayableTools({ config, provider }) {
2318
3240
  ok: true,
2319
3241
  read: {
2320
3242
  supplier: supplierName,
2321
- supplierDocument: onlyDigits(args.supplierDocument) || void 0,
3243
+ supplierDocument: onlyDigits2(args.supplierDocument) || void 0,
2322
3244
  paymentMethod: text(args.paymentMethod),
2323
3245
  cardBrand: text(args.cardBrand),
2324
3246
  cardLast4: lastFour(args.cardLast4) || void 0
@@ -2686,16 +3608,16 @@ function normalizeCompanyName(raw) {
2686
3608
  if (typeof raw !== "string") return "";
2687
3609
  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
3610
  }
2689
- function onlyDigits(raw) {
3611
+ function onlyDigits2(raw) {
2690
3612
  return typeof raw === "string" ? raw.replace(/\D/g, "") : "";
2691
3613
  }
2692
3614
  function lastFour(raw) {
2693
- const digits = onlyDigits(raw);
2694
- return digits.length >= 4 ? digits.slice(-4) : "";
3615
+ const digits2 = onlyDigits2(raw);
3616
+ return digits2.length >= 4 ? digits2.slice(-4) : "";
2695
3617
  }
2696
3618
  async function findSupplier(lookup, rawName, rawDocument) {
2697
3619
  const wanted = normalizeCompanyName(rawName);
2698
- const doc = onlyDigits(rawDocument);
3620
+ const doc = onlyDigits2(rawDocument);
2699
3621
  if (!wanted) return { status: "missing", candidates: [] };
2700
3622
  const core = wanted.split(" ")[0] ?? wanted;
2701
3623
  const seen = /* @__PURE__ */ new Map();
@@ -2705,7 +3627,7 @@ async function findSupplier(lookup, rawName, rawDocument) {
2705
3627
  seen.set(hit.id, {
2706
3628
  id: hit.id,
2707
3629
  name: hit.label,
2708
- document: onlyDigits(row.document_number) || void 0,
3630
+ document: onlyDigits2(row.document_number) || void 0,
2709
3631
  why: ""
2710
3632
  });
2711
3633
  }
@@ -3616,7 +4538,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
3616
4538
  );
3617
4539
  }
3618
4540
  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";
4541
+ 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
4542
  function InstallmentSourceFields({
3621
4543
  direction,
3622
4544
  value,
@@ -3636,7 +4558,7 @@ function InstallmentSourceFields({
3636
4558
  const conta = elegiveis.length === 1 ? elegiveis[0].id : elegiveis.some((a) => a.id === value.payAccountId) ? value.payAccountId : void 0;
3637
4559
  onChange({ payMethodTypeId: typeId || void 0, payAccountId: typeId ? conta : void 0 });
3638
4560
  }
3639
- const tone = (filled) => filled ? "text-foreground" : "text-muted-foreground";
4561
+ const tone = (filled) => filled ? "text-foreground" : "text-destructive border-b border-dashed border-destructive";
3640
4562
  return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
3641
4563
  /* @__PURE__ */ jsxs(
3642
4564
  Select,
@@ -3739,10 +4661,10 @@ function useStockedItemTypes(itemTypes, entityLookups) {
3739
4661
  }
3740
4662
 
3741
4663
  // src/lib/invoice-list.ts
3742
- var S\u00D3_RECEBIVEL = /* @__PURE__ */ new Set(["service", "gift_card"]);
4664
+ var S\u00D3_RECEBIVEL = /* @__PURE__ */ new Set(["service", "package", "gift_card"]);
3743
4665
  function availableInvoiceItemTypes(itemTypes, lookupKinds, otherLabel, direction) {
3744
4666
  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)));
4667
+ 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
4668
  if (visible.some((type) => type.value === "other")) return visible;
3747
4669
  return [...visible, { value: "other", label: otherLabel, icon: "MoreHorizontal" }];
3748
4670
  }
@@ -3807,6 +4729,9 @@ function summarizeInvoiceLines(rows, visible = 2) {
3807
4729
  }
3808
4730
  return summaries;
3809
4731
  }
4732
+ function invoiceOpensForEditing(status) {
4733
+ return status !== "paid" && status !== "cancelled";
4734
+ }
3810
4735
  function LocationSelector({ locations, value, onChange }) {
3811
4736
  const t2 = useTranslation();
3812
4737
  const [open, setOpen] = useState(false);
@@ -3925,11 +4850,11 @@ function itemsKey(list) {
3925
4850
  it.costCenterId ?? ""
3926
4851
  ]));
3927
4852
  }
3928
- function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fromCardId }) {
4853
+ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fromCardId, embedded, onChanged }) {
3929
4854
  const t2 = useTranslation();
3930
4855
  const config = useFinancialConfig();
3931
4856
  const provider = useFinancialProvider();
3932
- const { currency, itemTypes: itemTypesRaw, locations } = config;
4857
+ const { currency, itemTypes: itemTypesRaw = [], locations = [] } = config;
3933
4858
  const unitMode = useUnitMode();
3934
4859
  const activeUnitId = useActiveUnitId();
3935
4860
  const reachableUnits = useUnitFilter().options;
@@ -4006,10 +4931,9 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4006
4931
  const can = usePermissionOptional();
4007
4932
  const isEdit = !!editId;
4008
4933
  useEffect(() => {
4009
- if (isEdit) return;
4010
4934
  void fetchBankAccounts();
4011
4935
  void fetchPaymentMethods();
4012
- }, [isEdit]);
4936
+ }, []);
4013
4937
  useEffect(() => {
4014
4938
  let cancelled = false;
4015
4939
  void Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
@@ -4164,7 +5088,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4164
5088
  discount: it.discount,
4165
5089
  surcharge: it.surcharge,
4166
5090
  accountId: it.accountId,
4167
- costCenterId: it.costCenterId
5091
+ costCenterId: it.costCenterId,
5092
+ metadata: it.metadata
4168
5093
  };
4169
5094
  }));
4170
5095
  const aAbrir = consumirItemParaAbrir(editId);
@@ -4240,6 +5165,39 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4240
5165
  function updateItem(id, data) {
4241
5166
  setItems((current) => current.map((it) => it._id === id ? { ...it, ...data } : it));
4242
5167
  }
5168
+ async function applyItemPatch(item, patch) {
5169
+ const expand = patch.referenceId ? entityLookups[item.itemKind]?.expand : void 0;
5170
+ if (!expand) {
5171
+ updateItem(item._id, patch);
5172
+ return;
5173
+ }
5174
+ let linhas = [];
5175
+ try {
5176
+ linhas = await expand(patch.referenceId);
5177
+ } catch (err) {
5178
+ console.error("[financial] failed to expand package", err);
5179
+ }
5180
+ if (!linhas.length) {
5181
+ updateItem(item._id, patch);
5182
+ return;
5183
+ }
5184
+ const oferecidos = new Set(itemTypes.map((tp) => tp.value));
5185
+ const novas = linhas.map((linha) => ({
5186
+ _id: fid(),
5187
+ itemKind: linha.itemKind && oferecidos.has(linha.itemKind) ? linha.itemKind : item.itemKind,
5188
+ referenceId: linha.referenceId,
5189
+ description: linha.description,
5190
+ quantity: linha.quantity,
5191
+ unitPrice: linha.unitPrice,
5192
+ ...linha.discount ? { discount: linha.discount } : {},
5193
+ ...linha.surcharge ? { surcharge: linha.surcharge } : {},
5194
+ ...linha.metadata ? { metadata: linha.metadata } : {},
5195
+ accountId: item.accountId,
5196
+ costCenterId: item.costCenterId
5197
+ }));
5198
+ setItems((current) => current.flatMap((it) => it._id === item._id ? novas : [it]));
5199
+ setExpandedItemId(null);
5200
+ }
4243
5201
  function removeItem(id) {
4244
5202
  setItems((current) => current.filter((it) => it._id !== id));
4245
5203
  }
@@ -4505,7 +5463,12 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4505
5463
  dirty,
4506
5464
  // Criar ou editar: a barra é o caminho de salvar, e sumir porque nada mudou
4507
5465
  // ainda deixa a tela sem botão. Ela some quando o formulário fecha.
4508
- alwaysVisible: true,
5466
+ //
5467
+ // EMBUTIDO é o contrário: o modal já tem o próprio fechar, então a barra não
5468
+ // é o único caminho de saída — e «Alterações não salvas» sobre um formulário
5469
+ // que ninguém tocou é uma mentira que ensina a ignorar o aviso. Ali ela
5470
+ // aparece quando há o que salvar.
5471
+ alwaysVisible: !embedded,
4509
5472
  saving,
4510
5473
  onSave: () => {
4511
5474
  void handleSave();
@@ -4515,8 +5478,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4515
5478
  });
4516
5479
  const title = isEdit ? t2("financial.invoiceForm.editTitle") : t2("financial.invoiceForm.newTitle");
4517
5480
  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);
5481
+ 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?.() });
5482
+ const money4 = (v) => formatCurrency(v, currency);
4520
5483
  const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
4521
5484
  /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
4522
5485
  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(
@@ -4582,7 +5545,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4582
5545
  "#",
4583
5546
  i + 1
4584
5547
  ] }),
4585
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money3(inst.amount) }),
5548
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money4(inst.amount) }),
4586
5549
  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
5550
  /* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5" }),
4588
5551
  t2(isPaid2 ? "financial.invoice.statusPaid" : isPartial ? "financial.invoice.statusPartial" : "financial.invoice.statusPending")
@@ -4815,10 +5778,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4815
5778
  /* @__PURE__ */ jsxs(
4816
5779
  OrderFormView,
4817
5780
  {
5781
+ embedded,
4818
5782
  title,
4819
5783
  subtitle,
4820
- parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
4821
- onBack: () => onSaved?.(),
5784
+ parentLabel: embedded ? void 0 : direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
5785
+ onBack: embedded ? void 0 : () => onSaved?.(),
4822
5786
  actions,
4823
5787
  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
5788
  sideSections,
@@ -4836,7 +5800,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4836
5800
  count: items.length,
4837
5801
  countLabel: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
4838
5802
  totalLabel: t2("financial.invoiceForm.total"),
4839
- total: money3(totalAmount),
5803
+ total: money4(totalAmount),
4840
5804
  emptyLabel: contactId ? t2("financial.invoiceForm.noItems") : t2("financial.invoiceForm.selectContactFirst"),
4841
5805
  emptyActionLabel: t2("financial.invoiceForm.addFirstItem"),
4842
5806
  header: addingItem && /* @__PURE__ */ jsx(
@@ -4862,11 +5826,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4862
5826
  referenceId: item.referenceId,
4863
5827
  itemTypes,
4864
5828
  currency,
4865
- formatMoney: money3,
5829
+ formatMoney: money4,
4866
5830
  expanded: expandedItemId === item._id,
4867
5831
  readOnly: !canEditItems,
4868
5832
  onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
4869
- onChange: (patch) => updateItem(item._id, patch),
5833
+ onChange: (patch) => {
5834
+ void applyItemPatch(item, patch);
5835
+ },
4870
5836
  onRemove: () => {
4871
5837
  removeItem(item._id);
4872
5838
  if (expandedItemId === item._id) setExpandedItemId(null);
@@ -4927,6 +5893,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4927
5893
  }] : []
4928
5894
  ],
4929
5895
  entityLookups,
5896
+ pickOnly: Boolean(entityLookups[item.itemKind]?.expand) && !item.referenceId,
4930
5897
  showTypeSelector: hasItemLookups,
4931
5898
  allowSurcharge: true,
4932
5899
  labels: {
@@ -4982,6 +5949,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4982
5949
  setSavedMovements(
4983
5950
  movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
4984
5951
  );
5952
+ onChanged?.();
4985
5953
  })();
4986
5954
  }
4987
5955
  }
@@ -5665,96 +6633,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
5665
6633
  }
5666
6634
  ),
5667
6635
  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
6636
+ viewing !== null && /* @__PURE__ */ jsx(
6637
+ AttachmentViewer,
6638
+ {
6639
+ attachments,
6640
+ index: viewing,
6641
+ onIndexChange: setViewing,
6642
+ onClose: () => setViewing(null)
6643
+ }
5758
6644
  ),
5759
6645
  receiptBill && /* @__PURE__ */ jsx(
5760
6646
  PaymentReceiptModal,
@@ -5924,7 +6810,7 @@ function PayablesView({ intent, onNavigate, previousView, back }) {
5924
6810
  direction: "debit",
5925
6811
  onNew: () => onNavigate("payables-new"),
5926
6812
  onNewReceipt: () => onNavigate("payables-new-receipt"),
5927
- onEdit: (id) => onNavigate(`payables-detail:${id}`)
6813
+ onEdit: (id, status) => onNavigate(`payables-${invoiceOpensForEditing(status) ? "edit" : "detail"}:${id}`)
5928
6814
  }
5929
6815
  );
5930
6816
  }
@@ -5958,7 +6844,7 @@ function ReceivablesView({ intent, onNavigate, previousView, back }) {
5958
6844
  direction: "credit",
5959
6845
  onNew: () => onNavigate("receivables-new"),
5960
6846
  onNewReceipt: () => onNavigate("receivables-new-receipt"),
5961
- onEdit: (id) => onNavigate(`receivables-detail:${id}`)
6847
+ onEdit: (id, status) => onNavigate(`receivables-${invoiceOpensForEditing(status) ? "edit" : "detail"}:${id}`)
5962
6848
  }
5963
6849
  );
5964
6850
  }
@@ -7255,7 +8141,7 @@ function firstOfMonth() {
7255
8141
  function today2() {
7256
8142
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7257
8143
  }
7258
- function money(v) {
8144
+ function money2(v) {
7259
8145
  return v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
7260
8146
  }
7261
8147
  function Step({ n, label, done, state }) {
@@ -7309,7 +8195,7 @@ function CommissionsView() {
7309
8195
  setError(null);
7310
8196
  try {
7311
8197
  const r = await generateCommissions({ periodStart: from, periodEnd: to });
7312
- setLastRun(r ? `${r.entries} lan\xE7amento(s) \xB7 ${money(r.total)}` : null);
8198
+ setLastRun(r ? `${r.entries} lan\xE7amento(s) \xB7 ${money2(r.total)}` : null);
7313
8199
  await loadGeneration();
7314
8200
  } catch (e) {
7315
8201
  setError(e?.message ?? String(e));
@@ -7337,7 +8223,7 @@ function CommissionsView() {
7337
8223
  setError(null);
7338
8224
  try {
7339
8225
  const r = await provider.payRunCommissions?.(generation.id);
7340
- setLastRun(`${r?.payables ?? 0} conta(s) a pagar \xB7 ${money(r?.total ?? 0)}`);
8226
+ setLastRun(`${r?.payables ?? 0} conta(s) a pagar \xB7 ${money2(r?.total ?? 0)}`);
7341
8227
  await Promise.all([loadGeneration(), fetchCommissions({ periodStart: from, periodEnd: to })]);
7342
8228
  } catch (e) {
7343
8229
  setError(e?.message ?? String(e));
@@ -7383,7 +8269,7 @@ function CommissionsView() {
7383
8269
  lastRun && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: lastRun }),
7384
8270
  /* @__PURE__ */ jsxs("div", { className: "ml-auto text-right", children: [
7385
8271
  /* @__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) })
8272
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money2(total) })
7387
8273
  ] })
7388
8274
  ] }),
7389
8275
  generation && /* @__PURE__ */ jsxs("div", { className: "rounded-2xl border border-border bg-card px-4 py-3", children: [
@@ -7449,7 +8335,7 @@ function CommissionsView() {
7449
8335
  p.count,
7450
8336
  " lan\xE7amento(s)"
7451
8337
  ] }),
7452
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children: money(p.total) })
8338
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children: money2(p.total) })
7453
8339
  ] })
7454
8340
  ] }, id)) })
7455
8341
  ] }),
@@ -7468,9 +8354,9 @@ function CommissionsView() {
7468
8354
  /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: c.itemName ?? "\u2014" }),
7469
8355
  /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: c.invoiceNumber ?? "\u2014" }),
7470
8356
  /* @__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) }),
8357
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums text-muted-foreground", children: money2(c.baseAmount) }),
7472
8358
  /* @__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) })
8359
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right font-semibold tabular-nums", children: money2(c.amount) })
7474
8360
  ] }, c.id)) })
7475
8361
  ] }) })
7476
8362
  ] })
@@ -7787,7 +8673,7 @@ var MESES = [
7787
8673
  "Novembro",
7788
8674
  "Dezembro"
7789
8675
  ];
7790
- function money2(v) {
8676
+ function money3(v) {
7791
8677
  return v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
7792
8678
  }
7793
8679
  function GoalsView() {
@@ -7867,11 +8753,11 @@ function GoalsView() {
7867
8753
  ] }),
7868
8754
  /* @__PURE__ */ jsxs("div", { className: "ml-auto text-right", children: [
7869
8755
  /* @__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) })
8756
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money3(totalRealizado) })
7871
8757
  ] }),
7872
8758
  totalMeta > 0 && /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
7873
8759
  /* @__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) })
8760
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money3(totalMeta) })
7875
8761
  ] })
7876
8762
  ] }),
7877
8763
  semMeta > 0 && /* @__PURE__ */ jsxs("p", { className: "rounded-card border border-warning/30 bg-warning/5 px-4 py-2 text-xs", children: [
@@ -7900,7 +8786,7 @@ function GoalsView() {
7900
8786
  /* @__PURE__ */ jsx("td", {})
7901
8787
  ] }, i)) : rows.map((r) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 hover:bg-muted/40", children: [
7902
8788
  /* @__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) }),
8789
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums", children: money3(r.achievedAmount) }),
7904
8790
  /* @__PURE__ */ jsx(Degrau, { valor: r.targetAmount, percentual: r.targetPercent }),
7905
8791
  /* @__PURE__ */ jsx(Degrau, { valor: r.superAmount, percentual: r.superPercent }),
7906
8792
  /* @__PURE__ */ jsx(Degrau, { valor: r.hyperAmount, percentual: r.hyperPercent }),
@@ -7936,7 +8822,7 @@ function Degrau({ valor, percentual }) {
7936
8822
  }
7937
8823
  const bateu = percentual >= 100;
7938
8824
  return /* @__PURE__ */ jsxs("td", { className: "px-4 py-2 text-right", children: [
7939
- /* @__PURE__ */ jsx("div", { className: "tabular-nums", children: money2(valor) }),
8825
+ /* @__PURE__ */ jsx("div", { className: "tabular-nums", children: money3(valor) }),
7940
8826
  /* @__PURE__ */ jsxs("div", { className: `text-[10px] tabular-nums ${bateu ? "font-semibold text-success" : "text-muted-foreground"}`, children: [
7941
8827
  percentual.toFixed(2),
7942
8828
  "%"
@@ -7991,7 +8877,7 @@ function GoalDialog({
7991
8877
  ] }),
7992
8878
  /* @__PURE__ */ jsxs(ModalDescription, { className: "text-xs", children: [
7993
8879
  "Realizado at\xE9 agora: ",
7994
- /* @__PURE__ */ jsx("strong", { children: money2(goal.achievedAmount) })
8880
+ /* @__PURE__ */ jsx("strong", { children: money3(goal.achievedAmount) })
7995
8881
  ] })
7996
8882
  ] }),
7997
8883
  /* @__PURE__ */ jsx(ModalBody, { className: "fz-container px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
@@ -8056,7 +8942,7 @@ function GiftCardsView() {
8056
8942
  const [maxValue, setMaxValue] = React23__default.useState("");
8057
8943
  const [from, setFrom] = React23__default.useState("");
8058
8944
  const [to, setTo] = React23__default.useState("");
8059
- const money3 = React23__default.useCallback((v) => formatCurrency(v, currency), [currency]);
8945
+ const money4 = React23__default.useCallback((v) => formatCurrency(v, currency), [currency]);
8060
8946
  const seq = React23__default.useRef(0);
8061
8947
  const load = React23__default.useCallback(async () => {
8062
8948
  const mine = ++seq.current;
@@ -8100,12 +8986,12 @@ function GiftCardsView() {
8100
8986
  {
8101
8987
  accessorKey: "faceValue",
8102
8988
  header: "Valor",
8103
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: money3(row.original.faceValue) })
8989
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: money4(row.original.faceValue) })
8104
8990
  },
8105
8991
  {
8106
8992
  accessorKey: "balance",
8107
8993
  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) })
8994
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: row.original.balance === null ? /* @__PURE__ */ jsx(Dash, {}) : money4(row.original.balance) })
8109
8995
  },
8110
8996
  {
8111
8997
  accessorKey: "state",
@@ -8145,7 +9031,7 @@ function GiftCardsView() {
8145
9031
  /* @__PURE__ */ jsx(TooltipContent, { className: "text-xs", children: "Ativar na venda \u2014 o saldo passa a valer o valor de face" })
8146
9032
  ] }) }) : null
8147
9033
  }
8148
- ], [activating, load, money3, provider]);
9034
+ ], [activating, load, money4, provider]);
8149
9035
  const outstanding = (cards ?? []).filter((c) => c.state === "active").reduce((s, c) => s + (c.balance ?? 0), 0);
8150
9036
  const faceIssued = (cards ?? []).reduce((s, c) => s + c.faceValue, 0);
8151
9037
  if (enabled === false) {
@@ -8180,11 +9066,11 @@ function GiftCardsView() {
8180
9066
  /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-6 rounded-card border bg-card px-4 py-3", children: [
8181
9067
  /* @__PURE__ */ jsxs("div", { children: [
8182
9068
  /* @__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) })
9069
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money4(faceIssued) })
8184
9070
  ] }),
8185
9071
  /* @__PURE__ */ jsxs("div", { children: [
8186
9072
  /* @__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) }),
9073
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money4(outstanding) }),
8188
9074
  /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "s\xF3 os cart\xF5es j\xE1 vendidos" })
8189
9075
  ] })
8190
9076
  ] }),
@@ -8196,7 +9082,7 @@ function GiftCardsView() {
8196
9082
  ] }),
8197
9083
  " ",
8198
9084
  "\u2014 ",
8199
- money3(issued.total),
9085
+ money4(issued.total),
8200
9086
  " em valor de face. Eles nascem sem saldo: ative cada um na venda."
8201
9087
  ] }),
8202
9088
  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 +9141,7 @@ function GiftCardsView() {
8255
9141
  issuing && /* @__PURE__ */ jsx(
8256
9142
  IssueDialog,
8257
9143
  {
8258
- money: money3,
9144
+ money: money4,
8259
9145
  onClose: () => setIssuing(false),
8260
9146
  onIssued: async (count, total) => {
8261
9147
  setIssuing(false);
@@ -8267,7 +9153,7 @@ function GiftCardsView() {
8267
9153
  ] }) });
8268
9154
  }
8269
9155
  function IssueDialog({
8270
- money: money3,
9156
+ money: money4,
8271
9157
  onClose,
8272
9158
  onIssued
8273
9159
  }) {
@@ -8360,7 +9246,7 @@ function IssueDialog({
8360
9246
  ] }),
8361
9247
  /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-muted/40 px-4 py-2 text-sm", children: [
8362
9248
  "Total do lote: ",
8363
- /* @__PURE__ */ jsx("strong", { className: "tabular-nums", children: money3(total) })
9249
+ /* @__PURE__ */ jsx("strong", { className: "tabular-nums", children: money4(total) })
8364
9250
  ] }),
8365
9251
  invalid && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: invalid }),
8366
9252
  error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
@@ -8642,7 +9528,7 @@ function stripFence(text2) {
8642
9528
  const end = trimmed.lastIndexOf("}");
8643
9529
  return start >= 0 && end > start ? trimmed.slice(start, end + 1) : trimmed;
8644
9530
  }
8645
- var num = (v, fallback = 0) => {
9531
+ var num2 = (v, fallback = 0) => {
8646
9532
  const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
8647
9533
  return Number.isFinite(n) ? n : fallback;
8648
9534
  };
@@ -8661,8 +9547,8 @@ function parseReceiptReply(content) {
8661
9547
  const item = row ?? {};
8662
9548
  const description = str(item.description);
8663
9549
  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;
9550
+ const quantity = num2(item.quantity, 1) || 1;
9551
+ const unitPrice = item.unitPrice != null ? num2(item.unitPrice) : num2(item.total) / quantity;
8666
9552
  return { description, quantity, unitPrice: Math.round(unitPrice * 100) / 100 };
8667
9553
  }).filter((i) => i !== null) : [];
8668
9554
  const warnings = Array.isArray(raw.warnings) ? raw.warnings.map((w) => str(w)).filter((w) => !!w) : [];
@@ -8805,16 +9691,10 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
8805
9691
  }, [planoDoTipo]);
8806
9692
  const valorAQuitar = intent === "link" && target ? target.openAmount : round23(line.amount);
8807
9693
  const quita = intent === "bill" || intent === "link" && target?.mode === "settle";
8808
- const agentConnection = useMemo(() => resolveFayzAgentConnection(), []);
8809
9694
  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]);
9695
+ const ask = createOneShotAsk();
9696
+ return ask ? createAgentReceiptReader({ chat: ask, toInlineFile: toInlineFile, shrinkForVision }) : null;
9697
+ }, []);
8818
9698
  async function lerComprovante(file) {
8819
9699
  if (!leitor) return;
8820
9700
  if (file.size > 10 * 1024 * 1024) {
@@ -8979,7 +9859,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
8979
9859
  candidatesLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
8980
9860
  /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full rounded-md" }),
8981
9861
  /* @__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 }) => {
9862
+ ] }) : 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
9863
  const escolhido = targetId === c.movement.id;
8984
9864
  return /* @__PURE__ */ jsxs(
8985
9865
  "button",
@@ -8994,7 +9874,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
8994
9874
  /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
8995
9875
  shortDate2(c.movement.dueDate),
8996
9876
  /* @__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") })
9877
+ /* @__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
9878
  ] })
8999
9879
  ] }),
9000
9880
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold", children: fmt(c.movement.amount) })
@@ -11109,13 +11989,13 @@ function BalanceHero() {
11109
11989
  const navigate = useDashboardNavigate();
11110
11990
  useEnsureSummary();
11111
11991
  const [hidden, setHidden] = React23__default.useState(false);
11112
- const money3 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
11992
+ const money4 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
11113
11993
  const inflow = summary?.monthlyInflow ?? 0;
11114
11994
  const outflow = summary?.monthlyOutflow ?? 0;
11115
11995
  return /* @__PURE__ */ jsxs(GoldCard, { branded: true, className: "px-5 py-4 text-center md:px-8 md:py-5", children: [
11116
11996
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide opacity-75", children: t2("financial.home.balance") }),
11117
11997
  /* @__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) }),
11998
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-bold tracking-tight tabular-nums md:text-4xl", children: money4(summary?.totalBalance) }),
11119
11999
  /* @__PURE__ */ jsx(
11120
12000
  "button",
11121
12001
  {
@@ -11139,7 +12019,7 @@ function BalanceHero() {
11139
12019
  /* @__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
12020
  /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
11141
12021
  /* @__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) })
12022
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money4(inflow) })
11143
12023
  ] })
11144
12024
  ]
11145
12025
  }
@@ -11155,7 +12035,7 @@ function BalanceHero() {
11155
12035
  /* @__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
12036
  /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
11157
12037
  /* @__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) })
12038
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money4(outflow) })
11159
12039
  ] })
11160
12040
  ]
11161
12041
  }
@@ -12846,9 +13726,17 @@ function createSupabaseFinancialProvider() {
12846
13726
  totalAmount: i.total ?? 0,
12847
13727
  discount: i.discount ?? 0,
12848
13728
  surcharge: 0,
12849
- referenceId: i.productId,
13729
+ // `product_id` quando a linha veio de uma porta que o preenche (a
13730
+ // RPC da agenda); do metadata quando veio DESTA tela, que é onde a
13731
+ // escolha de catálogo é gravada. Lendo só a coluna, reabrir uma
13732
+ // fatura apagava o item escolhido de toda linha e deixava o texto
13733
+ // solto — e salvar de novo confirmava a perda.
13734
+ referenceId: i.productId ?? i.metadata?.referenceId,
12850
13735
  accountId: i.chartAccountId,
12851
13736
  costCenterId: i.costCenterId,
13737
+ // De onde a linha veio — o pacote que a gerou, por exemplo. Sem
13738
+ // devolvê-lo, editar a fatura desfazia o vínculo em silêncio.
13739
+ metadata: i.metadata ?? void 0,
12852
13740
  createdAt: i.createdAt
12853
13741
  };
12854
13742
  });
@@ -14631,6 +15519,237 @@ function createSupabaseFinancialProvider() {
14631
15519
  const { data, error } = await pub.from(T.notas).update({ status: "cancelled", error_message: reason ?? null }).eq("id", id).select().single();
14632
15520
  if (error) throw error;
14633
15521
  return snakeToCamel(data);
15522
+ },
15523
+ // --- Remessa CNAB 240 (080) ---
15524
+ /**
15525
+ * The Bacen registry, by COMPE code or by name.
15526
+ *
15527
+ * A bank is the country's, not the tenant's, so `public.banks` is global and
15528
+ * read-only. A pool where migration 167 has not run yet answers with an
15529
+ * empty list, and the caller falls back to the static catalogue — a missing
15530
+ * table costs a longer list, never the screen.
15531
+ */
15532
+ async searchBanks(query, limit = 12) {
15533
+ const term = query.trim();
15534
+ const { pub } = getClients();
15535
+ let qb = pub.from("banks").select("ispb,compe,name,legal_name,brand_color,logo_url").eq("is_active", true);
15536
+ if (term) {
15537
+ const digits2 = term.replace(/\D/g, "");
15538
+ qb = digits2.length >= 2 && digits2.length <= 3 ? qb.like("compe", `${digits2}%`) : qb.or(`name.ilike.%${term}%,legal_name.ilike.%${term}%`);
15539
+ }
15540
+ const { data, error } = await qb.order("compe", { nullsFirst: false }).limit(limit);
15541
+ if (error) return [];
15542
+ return (data ?? []).map((row) => ({
15543
+ ispb: row.ispb,
15544
+ compe: row.compe ?? void 0,
15545
+ name: row.name,
15546
+ legalName: row.legal_name ?? void 0,
15547
+ brandColor: row.brand_color ?? void 0,
15548
+ logoUrl: row.logo_url ?? void 0
15549
+ }));
15550
+ },
15551
+ /**
15552
+ * Who gets paid, who pays, and which file number is next.
15553
+ *
15554
+ * The payee's document lives on `people`, the account on `payee_accounts`
15555
+ * and the address on `addresses` — three tables because each answers a
15556
+ * different question, and the CNPJ of the paying company is a fourth. The
15557
+ * wizard asks once and gets all of it, so step 3 can show a form with the
15558
+ * blanks already filled instead of a sentence saying data is missing.
15559
+ */
15560
+ async getCnabContext({ contactIds, bankAccountId, unitId }) {
15561
+ const { pub } = getClients();
15562
+ const ids = [...new Set(contactIds.filter(Boolean))];
15563
+ const [peopleRes, accountsRes, addressRes, companyRes, unitRes, seqRes] = await Promise.all([
15564
+ ids.length ? pub.from("people").select("id,name,document_number").in("id", ids) : Promise.resolve({ data: [], error: null }),
15565
+ 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 }),
15566
+ ids.length ? pub.from("addresses").select("*").eq("owner_type", "person").in("owner_id", ids) : Promise.resolve({ data: [], error: null }),
15567
+ // As empresas do tenant. São unidades de linhas, então vêm todas e a
15568
+ // escolha é feita aqui — a da unidade da conta, senão a sem unidade,
15569
+ // senão a única que existe.
15570
+ pub.from("company_legal_entities").select("id,unit_id,kind,legal_name,trade_name,document_number").eq("is_active", true).limit(20),
15571
+ // O nome da unidade é o que a tela escreve enquanto não há empresa
15572
+ // cadastrada — melhor que um campo em branco pedindo razão social.
15573
+ unitId ? pub.from(T.unitCompanies).select("unit_id,unit_name").eq("unit_id", unitId).maybeSingle() : Promise.resolve({ data: null, error: null }),
15574
+ pub.from(T.cnabBatches).select("sequence_number").eq("bank_account_id", bankAccountId).order("sequence_number", { ascending: false }).limit(1).maybeSingle()
15575
+ ]);
15576
+ if (peopleRes.error) throw peopleRes.error;
15577
+ if (accountsRes.error) throw accountsRes.error;
15578
+ const accountOf = /* @__PURE__ */ new Map();
15579
+ for (const row of accountsRes.data ?? []) {
15580
+ if (!accountOf.has(row.owner_id)) accountOf.set(row.owner_id, row);
15581
+ }
15582
+ const addressOf = /* @__PURE__ */ new Map();
15583
+ for (const row of addressRes.data ?? []) {
15584
+ if (!addressOf.has(row.owner_id)) addressOf.set(row.owner_id, row);
15585
+ }
15586
+ const payees = (peopleRes.data ?? []).map((person) => {
15587
+ const account = accountOf.get(person.id);
15588
+ const address = addressOf.get(person.id);
15589
+ return {
15590
+ contactId: person.id,
15591
+ name: person.name ?? "",
15592
+ document: person.document_number ?? "",
15593
+ payeeAccountId: account?.id,
15594
+ bankCode: account?.bank_code ?? "",
15595
+ bankName: account?.bank_name ?? "",
15596
+ agencyNumber: account?.agency_number ?? "",
15597
+ agencyDigit: account?.agency_digit ?? "",
15598
+ accountNumber: account?.account_number ?? "",
15599
+ accountDigit: account?.account_digit ?? "",
15600
+ accountType: account?.account_type ?? "",
15601
+ pixKey: account?.pix_key ?? "",
15602
+ pixKeyType: account?.pix_key_type ?? "",
15603
+ street: address?.street ?? "",
15604
+ number: address?.number ?? "",
15605
+ complement: address?.complement ?? "",
15606
+ district: address?.district ?? "",
15607
+ city: address?.city ?? "",
15608
+ state: address?.state ?? "",
15609
+ postalCode: address?.postal_code ?? ""
15610
+ };
15611
+ });
15612
+ const companies = companyRes.data ?? [];
15613
+ const companyRow = companies.find((row) => unitId && row.unit_id === unitId) ?? companies.find((row) => !row.unit_id) ?? companies[0];
15614
+ const unitName = unitRes.data?.unit_name ?? "";
15615
+ const company = {
15616
+ id: companyRow?.id ?? void 0,
15617
+ unitId,
15618
+ name: companyRow?.trade_name || companyRow?.legal_name || unitName,
15619
+ document: companyRow?.document_number || ""
15620
+ };
15621
+ return {
15622
+ company,
15623
+ nextSequence: Number(seqRes.data?.sequence_number ?? 0) + 1,
15624
+ payees
15625
+ };
15626
+ },
15627
+ /**
15628
+ * A empresa pagadora, cadastrada de dentro da remessa.
15629
+ *
15630
+ * `company_legal_entities` nasceu sem tela: nenhuma superfície do SDK
15631
+ * escreve nela, e o resultado é um pool com treze unidades e zero empresas.
15632
+ * Como o CNPJ é obrigatório no cabeçalho do CNAB, é aqui que a pessoa o
15633
+ * digita — e gravá-lo é o que faz a PRÓXIMA remessa já vir preenchida.
15634
+ *
15635
+ * `unit_id` na linha é o que recorta a empresa por unidade. O elo inverso
15636
+ * (`app.units.company_legal_entity_id`) fica para quem tiver a RPC: o schema
15637
+ * `app` não é alcançável por aqui, e a leitura já resolve pelos dois lados.
15638
+ */
15639
+ async saveCompany(input) {
15640
+ const { pub } = getClients();
15641
+ const tenantId = getTenantId();
15642
+ const digits2 = (input.document || "").replace(/\D/g, "");
15643
+ const name = input.name.trim();
15644
+ if (!name) throw new Error("A empresa precisa de um nome.");
15645
+ if (digits2.length !== 11 && digits2.length !== 14) {
15646
+ throw new Error("CNPJ com 14 d\xEDgitos ou CPF com 11 \u2014 o cabe\xE7alho do CNAB n\xE3o aceita outro.");
15647
+ }
15648
+ const row = {
15649
+ kind: digits2.length === 14 ? "company" : "individual",
15650
+ legal_name: name,
15651
+ trade_name: name,
15652
+ document_number: digits2
15653
+ };
15654
+ if (input.id) {
15655
+ const { error: error2 } = await pub.from("company_legal_entities").update(row).eq("id", input.id);
15656
+ if (error2) throw error2;
15657
+ return { id: input.id };
15658
+ }
15659
+ 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();
15660
+ if (error) throw error;
15661
+ return { id: data.id };
15662
+ },
15663
+ /**
15664
+ * What the wizard collected goes back to the cadastro, not to the file only.
15665
+ * Otherwise the same CPF is typed again on the next remittance, and the two
15666
+ * typings disagree.
15667
+ */
15668
+ async saveCnabPayees(payees) {
15669
+ const { pub } = getClients();
15670
+ const tenantId = getTenantId();
15671
+ const text2 = (v) => {
15672
+ const s = (v ?? "").trim();
15673
+ return s === "" ? null : s;
15674
+ };
15675
+ for (const payee of payees) {
15676
+ if (text2(payee.document)) {
15677
+ await pub.from("people").update({ document_number: text2(payee.document) }).eq("id", payee.contactId);
15678
+ }
15679
+ const row = {
15680
+ bank_code: text2(payee.bankCode),
15681
+ bank_name: text2(payee.bankName),
15682
+ agency_number: text2(payee.agencyNumber),
15683
+ agency_digit: text2(payee.agencyDigit),
15684
+ account_number: text2(payee.accountNumber),
15685
+ account_digit: text2(payee.accountDigit),
15686
+ account_type: text2(payee.accountType),
15687
+ pix_key: text2(payee.pixKey),
15688
+ pix_key_type: text2(payee.pixKeyType)
15689
+ };
15690
+ if (!row.account_number && !row.pix_key) continue;
15691
+ let targetId = payee.payeeAccountId;
15692
+ if (!targetId) {
15693
+ 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();
15694
+ targetId = existing?.id;
15695
+ }
15696
+ if (targetId) {
15697
+ await pub.from(T.payeeAccounts).update(row).eq("id", targetId);
15698
+ } else {
15699
+ await pub.from(T.payeeAccounts).insert({
15700
+ ...row,
15701
+ tenant_id: tenantId,
15702
+ owner_type: "person",
15703
+ owner_id: payee.contactId,
15704
+ label: "Principal",
15705
+ is_default: true
15706
+ });
15707
+ }
15708
+ }
15709
+ },
15710
+ /**
15711
+ * The file that already left for the bank. `sequence_number` is the bank's
15712
+ * count, not the file's — two remittances carrying the same one is a
15713
+ * rejected remittance, so it is written with the batch and never derived
15714
+ * again.
15715
+ */
15716
+ async recordCnabBatch(input) {
15717
+ const { pub } = getClients();
15718
+ const tenantId = getTenantId();
15719
+ const { data, error } = await pub.from(T.cnabBatches).insert({
15720
+ tenant_id: tenantId,
15721
+ unit_id: input.unitId ?? null,
15722
+ bank_account_id: input.bankAccountId,
15723
+ layout: input.layout,
15724
+ sequence_number: input.sequenceNumber,
15725
+ total_amount: input.totalAmount,
15726
+ items_count: input.items.length,
15727
+ file_name: input.fileName,
15728
+ // The file's home is `public.documents` through `document_id`; until the
15729
+ // upload path exists it rides in metadata, which keeps a generated
15730
+ // remittance downloadable instead of gone.
15731
+ metadata: { file_content: input.fileContent }
15732
+ }).select("id").single();
15733
+ if (error) throw error;
15734
+ const batchId = data.id;
15735
+ if (input.items.length) {
15736
+ const ids = [...new Set(input.items.map((item) => item.movementId))];
15737
+ const { data: movementRows } = await pub.from(T.movements).select("id").in("id", ids);
15738
+ const isMovement = new Set((movementRows ?? []).map((row) => row.id));
15739
+ const { error: itemsError } = await pub.from(T.cnabItems).insert(
15740
+ input.items.map((item) => ({
15741
+ tenant_id: tenantId,
15742
+ batch_id: batchId,
15743
+ installment_id: item.movementId,
15744
+ movement_id: isMovement.has(item.movementId) ? item.movementId : null,
15745
+ payment_form: item.paymentForm,
15746
+ payment_date: item.paymentDate,
15747
+ amount: item.amount
15748
+ }))
15749
+ );
15750
+ if (itemsError) throw itemsError;
15751
+ }
15752
+ return { id: batchId };
14634
15753
  }
14635
15754
  };
14636
15755
  return provider;
@@ -15591,6 +16710,7 @@ var costCenterEntity = {
15591
16710
  };
15592
16711
  var isCard = (t2) => t2 === "card_receivable_credit" || t2 === "card_receivable_debit" || t2 === "card_payable";
15593
16712
  var isBank = (t2) => t2 === "bank";
16713
+ var hasIssuer = (t2) => isBank(t2) || t2 === "card_payable";
15594
16714
  var ACCOUNT_KINDS = ["cash_physical", "bank", "card_receivable_credit", "card_receivable_debit", "card_payable"];
15595
16715
  var bankAccountEntity = {
15596
16716
  name: "Conta",
@@ -15611,7 +16731,12 @@ var bankAccountEntity = {
15611
16731
  // sozinho quando nenhum dos seus campos se aplica.
15612
16732
  fieldGroups: [
15613
16733
  { id: "identity", label: "Identifica\xE7\xE3o", columns: 2 },
15614
- { id: "bank", label: "Onde o dinheiro est\xE1", columns: 2 },
16734
+ {
16735
+ id: "bank",
16736
+ label: "Onde o dinheiro est\xE1",
16737
+ columns: 2,
16738
+ labelWhen: (v) => v.kind === "card_payable" ? "Quem emitiu o cart\xE3o" : void 0
16739
+ },
15615
16740
  { id: "holder", label: "Titular", columns: 2, description: "A conta de uma rede raramente est\xE1 no nome da rede." },
15616
16741
  { id: "card", label: "Cart\xE3o", columns: 2 },
15617
16742
  { ...RECORD_GROUP }
@@ -15651,16 +16776,26 @@ var bankAccountEntity = {
15651
16776
  label: "N\xFAmero do banco",
15652
16777
  type: "text",
15653
16778
  showInTable: false,
15654
- visibleWhen: (v) => isBank(v.kind),
16779
+ visibleWhen: (v) => hasIssuer(v.kind),
15655
16780
  group: "bank"
15656
16781
  },
16782
+ // Picking the bank fills its number too, so the two cannot disagree.
15657
16783
  {
15658
16784
  key: "bankName",
15659
16785
  label: "Banco",
15660
- type: "text",
16786
+ type: "custom",
15661
16787
  showInTable: false,
15662
- visibleWhen: (v) => isBank(v.kind),
15663
- group: "bank"
16788
+ labelWhen: (v) => v.kind === "card_payable" ? "Banco emissor" : void 0,
16789
+ visibleWhen: (v) => hasIssuer(v.kind),
16790
+ group: "bank",
16791
+ renderInput: ({ value, values, onChange, setValue }) => React23__default.createElement(BankPicker, {
16792
+ code: values.bankCode ?? "",
16793
+ name: value ?? "",
16794
+ onChange: (next) => {
16795
+ onChange(next.name);
16796
+ if (next.code || !next.name) setValue?.("bankCode", next.code);
16797
+ }
16798
+ })
15664
16799
  },
15665
16800
  {
15666
16801
  key: "agencyNumber",
@@ -16701,6 +17836,7 @@ var en = {
16701
17836
  "financial.invoiceForm.contactRequired": "Select who will pay or receive before adding items.",
16702
17837
  "financial.invoiceForm.additionalNotes": "Additional notes...",
16703
17838
  "financial.itemType.service": "Service",
17839
+ "financial.itemType.package": "Package",
16704
17840
  "financial.itemType.product": "Product",
16705
17841
  "financial.itemType.other": "Other",
16706
17842
  "financial.itemType.gift_card": "Gift Card",
@@ -17590,6 +18726,7 @@ var ptBR = {
17590
18726
  "financial.invoiceForm.contactRequired": "Selecione quem vai pagar ou receber antes de adicionar itens.",
17591
18727
  "financial.invoiceForm.additionalNotes": "Observa\xE7\xF5es adicionais...",
17592
18728
  "financial.itemType.service": "Servi\xE7o",
18729
+ "financial.itemType.package": "Pacote",
17593
18730
  "financial.itemType.product": "Produto",
17594
18731
  "financial.itemType.other": "Outro",
17595
18732
  "financial.itemType.gift_card": "Gift Card",
@@ -42900,6 +44037,145 @@ COMMENT ON VIEW public.v_financial_invoice_lines_due IS
42900
44037
  ALTER VIEW public.v_financial_invoice_lines_due SET (security_invoker = true);
42901
44038
  GRANT SELECT ON public.v_financial_invoice_lines_due TO authenticated, service_role;
42902
44039
  `;
44040
+ var MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE = `-- ---------------------------------------------------------------------------
44041
+ -- 115 \u2014 a remessa aponta para a conta que EXISTE.
44042
+ --
44043
+ -- \`plg_financial_cnab_batches.bank_account_id\` carrega DUAS chaves estrangeiras
44044
+ -- para a mesma coluna:
44045
+ --
44046
+ -- plg_financial_cnab_batches_account_fkey \u2192 plg_financial_bank_accounts
44047
+ -- plg_financial_cnab_batches_ledger_account_fkey \u2192 plg_financial_accounts
44048
+ --
44049
+ -- A segunda \xE9 a certa. A primeira aponta para a tabela LEGADA \u2014 aquela que a
44050
+ -- 021 aposentou e que hoje \xE9 s\xF3 o encaminhamento por gatilho. Neste cluster ela
44051
+ -- tem 5 linhas contra 63 contas banc\xE1rias no raz\xE3o, e o efeito \xE9 exato: gerar
44052
+ -- uma remessa por qualquer conta criada depois do porte morre com
44053
+ --
44054
+ -- violates foreign key constraint "plg_financial_cnab_batches_account_fkey"
44055
+ -- Key is not present in table "plg_financial_bank_accounts"
44056
+ --
44057
+ -- Uma coluna com duas FKs exige que o id esteja nas DUAS tabelas, e \xE9 por isso
44058
+ -- que a tela nunca conseguiu gravar: ela escreve onde \`getBankAccounts\` l\xEA, que
44059
+ -- \xE9 o raz\xE3o, e o legado nunca soube daquela conta.
44060
+ --
44061
+ -- A 080 trouxe a tabela do V1 com a FK que o V1 tinha. O que faltou foi
44062
+ -- aposentar a chave junto com a tabela a que ela se referia.
44063
+ --
44064
+ -- Derrubar a FK legada n\xE3o perde nada: a do raz\xE3o continua guardando a coluna, e
44065
+ -- \xE9 ela que diz a verdade. As 60 remessas j\xE1 migradas apontam para contas que
44066
+ -- est\xE3o nas duas tabelas e seguem v\xE1lidas.
44067
+ --
44068
+ -- Idempotente.
44069
+ -- ---------------------------------------------------------------------------
44070
+
44071
+ ALTER TABLE public.plg_financial_cnab_batches
44072
+ DROP CONSTRAINT IF EXISTS plg_financial_cnab_batches_account_fkey;
44073
+
44074
+ -- A do raz\xE3o pode n\xE3o existir num pool que recebeu a 080 e n\xE3o a corre\xE7\xE3o; \xE9
44075
+ -- ela que fica, ent\xE3o se faltar, nasce aqui.
44076
+ DO $$
44077
+ BEGIN
44078
+ IF NOT EXISTS (
44079
+ SELECT 1 FROM pg_constraint
44080
+ WHERE conrelid = 'public.plg_financial_cnab_batches'::regclass
44081
+ AND conname = 'plg_financial_cnab_batches_ledger_account_fkey'
44082
+ ) THEN
44083
+ ALTER TABLE public.plg_financial_cnab_batches
44084
+ ADD CONSTRAINT plg_financial_cnab_batches_ledger_account_fkey
44085
+ FOREIGN KEY (bank_account_id)
44086
+ REFERENCES public.plg_financial_accounts(id) ON DELETE SET NULL;
44087
+ END IF;
44088
+ END $$;
44089
+
44090
+ COMMENT ON COLUMN public.plg_financial_cnab_batches.bank_account_id IS
44091
+ '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.';
44092
+
44093
+ -- \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
44094
+ --
44095
+ -- \`plg_financial_cnab_items.movement_id\` aponta para \`plg_financial_movements\`.
44096
+ -- A tela manda a PARCELA, e o porte deixou parcela e movimento com o mesmo id
44097
+ -- na maioria das linhas \u2014 mas n\xE3o em todas: aqui s\xE3o 171.978 parcelas contra
44098
+ -- 143.175 com movimento de mesmo id. As outras 28.803 fariam a remessa morrer
44099
+ -- na hora de arquivar um arquivo que J\xC1 foi baixado e j\xE1 foi para o banco.
44100
+ --
44101
+ -- O item passa a aceitar a parcela em coluna pr\xF3pria. \`movement_id\` continua
44102
+ -- sendo o elo com o raz\xE3o onde ele existe; \`installment_id\` \xE9 o que a tela
44103
+ -- sempre teve na m\xE3o, e \xE9 por ele que \xABesta conta j\xE1 foi numa remessa\xBB tem
44104
+ -- resposta.
44105
+ ALTER TABLE public.plg_financial_cnab_items
44106
+ ADD COLUMN IF NOT EXISTS installment_id uuid;
44107
+
44108
+ DO $$
44109
+ BEGIN
44110
+ IF NOT EXISTS (
44111
+ SELECT 1 FROM pg_constraint
44112
+ WHERE conrelid = 'public.plg_financial_cnab_items'::regclass
44113
+ AND conname = 'plg_financial_cnab_items_installment_fkey'
44114
+ ) THEN
44115
+ ALTER TABLE public.plg_financial_cnab_items
44116
+ ADD CONSTRAINT plg_financial_cnab_items_installment_fkey
44117
+ FOREIGN KEY (installment_id)
44118
+ REFERENCES public.plg_financial_invoice_installments(id) ON DELETE SET NULL;
44119
+ END IF;
44120
+ END $$;
44121
+
44122
+ -- As linhas j\xE1 migradas: onde o movimento tem parcela de mesmo id, o elo novo
44123
+ -- se preenche sozinho. O que n\xE3o casar fica nulo, que \xE9 a verdade.
44124
+ UPDATE public.plg_financial_cnab_items ci
44125
+ SET installment_id = ci.movement_id
44126
+ WHERE ci.installment_id IS NULL
44127
+ AND ci.movement_id IS NOT NULL
44128
+ AND EXISTS (
44129
+ SELECT 1 FROM public.plg_financial_invoice_installments i WHERE i.id = ci.movement_id
44130
+ );
44131
+
44132
+ CREATE INDEX IF NOT EXISTS plg_financial_cnab_items_installment_idx
44133
+ ON public.plg_financial_cnab_items (tenant_id, installment_id)
44134
+ WHERE installment_id IS NOT NULL;
44135
+
44136
+ COMMENT ON COLUMN public.plg_financial_cnab_items.installment_id IS
44137
+ '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.';
44138
+
44139
+ -- \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
44140
+ --
44141
+ -- \`payee_accounts_one_default_uidx\` \xE9 \xFAnico em (tenant_id, owner_type, owner_id)
44142
+ -- WHERE is_default, e est\xE1 certo: duas contas padr\xE3o fazem a tela mostrar uma e
44143
+ -- a remessa pagar a outra.
44144
+ --
44145
+ -- O que estava errado era quem escrevia: a remessa inseria sempre com
44146
+ -- \`is_default = true\`, ent\xE3o a SEGUNDA tentativa de gerar o mesmo arquivo \u2014
44147
+ -- depois de a primeira morrer na FK acima \u2014 batia em
44148
+ --
44149
+ -- duplicate key value violates unique constraint
44150
+ --
44151
+ -- O conserto \xE9 do lado de quem escreve (\`saveCnabPayees\` procura antes de
44152
+ -- inserir), e esta fun\xE7\xE3o \xE9 a rede: um \`insert\` que j\xE1 tem padr\xE3o vira o padr\xE3o
44153
+ -- da pessoa sem precisar que o chamador saiba disso.
44154
+ CREATE OR REPLACE FUNCTION public.trg_payee_accounts_one_default()
44155
+ RETURNS trigger
44156
+ LANGUAGE plpgsql
44157
+ SECURITY INVOKER
44158
+ SET search_path = public, pg_catalog
44159
+ AS $$
44160
+ BEGIN
44161
+ IF NEW.is_default THEN
44162
+ UPDATE public.payee_accounts
44163
+ SET is_default = false
44164
+ WHERE tenant_id = NEW.tenant_id
44165
+ AND owner_type = NEW.owner_type
44166
+ AND owner_id = NEW.owner_id
44167
+ AND is_default
44168
+ AND id <> NEW.id;
44169
+ END IF;
44170
+ RETURN NEW;
44171
+ END
44172
+ $$;
44173
+
44174
+ DROP TRIGGER IF EXISTS payee_accounts_one_default ON public.payee_accounts;
44175
+ CREATE TRIGGER payee_accounts_one_default
44176
+ BEFORE INSERT OR UPDATE OF is_default ON public.payee_accounts
44177
+ FOR EACH ROW EXECUTE FUNCTION public.trg_payee_accounts_one_default();
44178
+ `;
42903
44179
  var MIGRATIONS = [
42904
44180
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
42905
44181
  { id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
@@ -43015,11 +44291,10 @@ var MIGRATIONS = [
43015
44291
  { id: "111_reparcelar_sem_apagar_a_parcela", sql: MIGRATION_111_REPARCELAR_SEM_APAGAR_A_PARCELA },
43016
44292
  { id: "112_a_linha_do_extrato_nao_e_um_lancamento_ainda", sql: MIGRATION_112_A_LINHA_DO_EXTRATO_NAO_E_UM_LANCAMENTO_AINDA },
43017
44293
  { 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 }
44294
+ { id: "114_a_parcela_diz_por_qual_conta_ela_foi_paga", sql: MIGRATION_114_A_PARCELA_DIZ_POR_QUAL_CONTA_ELA_FOI_PAGA },
44295
+ { id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE }
43019
44296
  ];
43020
- var STANDALONE_CONFIG = {
43021
- currency: { code: "BRL", locale: "pt-BR", symbol: "R$" }
43022
- };
44297
+ var STANDALONE_CONFIG = resolveConfig();
43023
44298
  registerTranslations(financialLocales);
43024
44299
  function ReceivePaymentModal({
43025
44300
  installment,
@@ -43105,7 +44380,7 @@ function ClientAccountPanel({
43105
44380
  const provider = useFinancialProvider();
43106
44381
  const [account, setAccount] = React23__default.useState(null);
43107
44382
  const [busy, setBusy] = React23__default.useState(false);
43108
- const [viewInvoice, setViewInvoice] = React23__default.useState(null);
44383
+ const [viewInvoiceId, setViewInvoiceId] = React23__default.useState(null);
43109
44384
  const [comprando, setComprando] = React23__default.useState(false);
43110
44385
  const [buscaServico, setBuscaServico] = React23__default.useState("");
43111
44386
  const [catalogo, setCatalogo] = React23__default.useState([]);
@@ -43119,6 +44394,8 @@ function ClientAccountPanel({
43119
44394
  React23__default.useEffect(() => {
43120
44395
  void load();
43121
44396
  }, [load]);
44397
+ const viewInvoice = account?.invoices.find((i) => i.id === viewInvoiceId) ?? null;
44398
+ const viewEditable = invoiceOpensForEditing(viewInvoice?.status);
43122
44399
  const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
43123
44400
  const hasForThis = forThis.length > 0;
43124
44401
  React23__default.useEffect(() => {
@@ -43331,7 +44608,7 @@ function ClientAccountPanel({
43331
44608
  "button",
43332
44609
  {
43333
44610
  type: "button",
43334
- onClick: () => setViewInvoice(i),
44611
+ onClick: () => setViewInvoiceId(i.id),
43335
44612
  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
44613
  children: i.balance > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
43337
44614
  /* @__PURE__ */ jsx(CircleDollarSign, { className: "h-3.5 w-3.5" }),
@@ -43362,17 +44639,31 @@ function ClientAccountPanel({
43362
44639
  hint: clientName ?? void 0,
43363
44640
  width: "max-w-3xl",
43364
44641
  onClose: () => {
43365
- setViewInvoice(null);
44642
+ setViewInvoiceId(null);
43366
44643
  void load();
43367
44644
  },
43368
- children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-3", children: /* @__PURE__ */ jsx(
44645
+ children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-3", children: viewEditable ? /* @__PURE__ */ jsx(
44646
+ InvoiceFormView,
44647
+ {
44648
+ embedded: true,
44649
+ direction: "credit",
44650
+ editId: viewInvoice.id,
44651
+ onSaved: () => {
44652
+ void load();
44653
+ },
44654
+ onChanged: () => {
44655
+ setViewInvoiceId(null);
44656
+ void load();
44657
+ }
44658
+ }
44659
+ ) : /* @__PURE__ */ jsx(
43369
44660
  InvoiceDetailView,
43370
44661
  {
43371
44662
  embedded: true,
43372
44663
  invoiceId: viewInvoice.id,
43373
44664
  direction: "credit",
43374
44665
  onBack: () => {
43375
- setViewInvoice(null);
44666
+ setViewInvoiceId(null);
43376
44667
  void load();
43377
44668
  }
43378
44669
  }
@@ -43412,30 +44703,6 @@ function createSafeFinancialProvider() {
43412
44703
  );
43413
44704
  }
43414
44705
  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
44706
  var FINANCIAL_NAV_FEATURES = {
43440
44707
  reconciliation: "fin_reconciliation",
43441
44708
  cards: "fin_cards",
@@ -43445,55 +44712,6 @@ var FINANCIAL_NAV_FEATURES = {
43445
44712
  payables: "fin_payables",
43446
44713
  "cash-registers": "fin_cash_registers"
43447
44714
  };
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
44715
  function createFinancialPlugin(options) {
43498
44716
  const config = resolveConfig(options);
43499
44717
  registerTranslations(financialLocales);