@fayz-ai/plugin-financial 0.19.0 → 0.20.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 (46) hide show
  1. package/dist/components/InstallmentRow.d.ts +42 -0
  2. package/dist/components/InstallmentRow.d.ts.map +1 -0
  3. package/dist/components/InstallmentSource.d.ts.map +1 -1
  4. package/dist/components/InstallmentStatusBadge.d.ts +6 -0
  5. package/dist/components/InstallmentStatusBadge.d.ts.map +1 -0
  6. package/dist/components/InvoiceAttachmentsModal.d.ts +12 -0
  7. package/dist/components/InvoiceAttachmentsModal.d.ts.map +1 -0
  8. package/dist/components/PaymentFields.d.ts.map +1 -1
  9. package/dist/components/PaymentIcons.d.ts +12 -0
  10. package/dist/components/PaymentIcons.d.ts.map +1 -0
  11. package/dist/components/PaymentReceiptModal.d.ts.map +1 -1
  12. package/dist/components/ReconcileFlow.d.ts +6 -1
  13. package/dist/components/ReconcileFlow.d.ts.map +1 -1
  14. package/dist/components/UnitField.d.ts +22 -0
  15. package/dist/components/UnitField.d.ts.map +1 -0
  16. package/dist/data/match-statement.d.ts +3 -1
  17. package/dist/data/match-statement.d.ts.map +1 -1
  18. package/dist/data/mock.d.ts.map +1 -1
  19. package/dist/data/supabase.d.ts.map +1 -1
  20. package/dist/data/types.d.ts +12 -1
  21. package/dist/data/types.d.ts.map +1 -1
  22. package/dist/index.js +1803 -846
  23. package/dist/index.js.map +1 -1
  24. package/dist/lib/routes.d.ts +5 -0
  25. package/dist/lib/routes.d.ts.map +1 -0
  26. package/dist/lib/settlements.d.ts +6 -0
  27. package/dist/lib/settlements.d.ts.map +1 -1
  28. package/dist/locales/en.d.ts.map +1 -1
  29. package/dist/locales/pt-BR.d.ts.map +1 -1
  30. package/dist/migrations/index.d.ts +3 -0
  31. package/dist/migrations/index.d.ts.map +1 -1
  32. package/dist/types.d.ts +20 -0
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/views/FinancialPage.d.ts.map +1 -1
  35. package/dist/views/GapScreens.d.ts +0 -2
  36. package/dist/views/GapScreens.d.ts.map +1 -1
  37. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  38. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  39. package/dist/views/InvoiceListView.d.ts.map +1 -1
  40. package/dist/views/ReconciliationView.d.ts.map +1 -1
  41. package/dist/views/RecurringBillsView.d.ts.map +1 -1
  42. package/dist/views/cardChip.d.ts +1 -1
  43. package/dist/views/cardChip.d.ts.map +1 -1
  44. package/package.json +4 -4
  45. package/dist/components/AttachmentViewer.d.ts +0 -8
  46. package/dist/components/AttachmentViewer.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import * as React23 from 'react';
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';
1
+ import * as React25 from 'react';
2
+ import React25__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
3
+ import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, getSupabaseClientOptional, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode, useDataChanged, countByTenant, tl, resolveDataProvider, useActiveUnitId, getActiveTenantId, emitDataChanged, exportCSV } from '@fayz-ai/core';
4
+ import { INLINE_LINK_TONE, SearchSelect, ModalTask, toast, Button, useInModalTaskHost, useSaveBar, tourAnchor, DatePicker, SubpageHeader, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, Card, CardContent, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MultiSelect, PageTransition, ModulePage, Badge, TooltipProvider, ConfirmDialog, CurrencyInput, Input, PageHeaderActions, ChoiceSelect, Hint, ModalFooter, PageTabs, Tooltip, TooltipTrigger, TooltipContent, Checkbox, Popover, PopoverTrigger, PopoverContent, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, SegmentedControl, RequiredFilter, FilterMenuRow, ChoiceList, 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, 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';
6
+ import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useUnitFilter, useLimitGuard, usePermissionOptional, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, adminNavigateTo, OrderFormAddItemStep, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, useChatStore, revealPage, useAuth, GetStarted, resolveEntityRoute, PermissionGate, createOneShotAsk, askAI, requestChatUpload, toInlineFile, shrinkForVision } from '@fayz-ai/admin';
7
+ import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Receipt, Gift, Briefcase, Package, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, Ticket, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Undo2, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, ChevronRight, Building2, Image, UploadCloud, Calendar, User, Unlink, ChevronDown, Eye, Inbox, Clock, ClipboardCheck, BookOpen, Info, FolderOpen, Folder, RotateCcw, Settings, ArrowLeft, ChevronLeft, Calculator, Square, Play, ChevronUp, Printer, CalendarClock, BadgeCheck, Target, ArrowRight, Users, RefreshCw, Download, Camera, Sparkles, Link2, FileSearch, CheckCheck, Pause, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, FileDown, ArrowUpRight, ArrowDownRight, EyeOff } from 'lucide-react';
8
8
  import { createPortal } from 'react-dom';
9
9
  import { createStore as createStore$1 } from 'zustand/vanilla';
10
10
  import { useStore } from 'zustand';
@@ -105,22 +105,6 @@ function PettyCashView() {
105
105
  }
106
106
  );
107
107
  }
108
- function DigitizeExpenseView() {
109
- return /* @__PURE__ */ jsx(
110
- ComingSoon,
111
- {
112
- title: "Digitalizar Despesa",
113
- summary: "Fotografar o boleto ou a nota e deixar a m\xE1quina ler valor, vencimento e fornecedor \u2014 em vez de digitar os quatro campos de novo.",
114
- v1Path: "Financeiro \u2192 Contas a Pagar \u2192 Digitalizar Despesa",
115
- measured: "1.436 anexos de conta no V1, 55 da Artorius. A Laissa anexa comprovante 25 vezes por m\xEAs \u2014 mas digita os campos \xE0 m\xE3o todas elas.",
116
- missing: [
117
- "Ler o comprovante e PR\xC9-PREENCHER o formul\xE1rio, deixando o usu\xE1rio confirmar \u2014 n\xE3o gravar sozinho",
118
- "Guardar o arquivo junto do t\xEDtulo, que \xE9 o que a contabilidade pede depois",
119
- "A Entrada de Notas j\xE1 faz isto para XML de NF-e; aqui \xE9 o mesmo trabalho para o que chega em papel"
120
- ]
121
- }
122
- );
123
- }
124
108
  function ImportPayrollView() {
125
109
  return /* @__PURE__ */ jsx(
126
110
  ComingSoon,
@@ -924,9 +908,9 @@ function startReceiptCapture(labels) {
924
908
  });
925
909
  }
926
910
  function ReceiptAgentSurface({ onOpenDraft }) {
927
- const live = React23.useRef(onOpenDraft);
911
+ const live = React25.useRef(onOpenDraft);
928
912
  live.current = onOpenDraft;
929
- const contribution = React23.useMemo(() => ({
913
+ const contribution = React25.useMemo(() => ({
930
914
  id: "financial.payables.receipt",
931
915
  pluginId: "financial",
932
916
  role: "finance",
@@ -1277,22 +1261,22 @@ function searchBrBanks(query) {
1277
1261
  (b) => qd && qd === q && (b.code.startsWith(qd) || b.code.replace(/^0+/, "").startsWith(qd)) || norm(b.name).includes(q) || b.aliases?.some((a) => norm(a).includes(q))
1278
1262
  );
1279
1263
  }
1280
- function BankLogo({ bank, size = 20 }) {
1264
+ function BankLogo({ bank, size: size2 = 20 }) {
1281
1265
  const [broken, setBroken] = useState(false);
1282
1266
  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 });
1267
+ return /* @__PURE__ */ jsx(Landmark, { className: "text-muted-foreground", style: { width: size2 * 0.8, height: size2 * 0.8 }, "aria-hidden": true });
1284
1268
  }
1285
1269
  return /* @__PURE__ */ jsx(
1286
1270
  "img",
1287
1271
  {
1288
1272
  src: bank.logo,
1289
1273
  alt: "",
1290
- width: size,
1291
- height: size,
1274
+ width: size2,
1275
+ height: size2,
1292
1276
  loading: "lazy",
1293
1277
  onError: () => setBroken(true),
1294
1278
  className: "shrink-0 object-contain",
1295
- style: { width: size, height: size }
1279
+ style: { width: size2, height: size2 }
1296
1280
  }
1297
1281
  );
1298
1282
  }
@@ -1420,22 +1404,22 @@ function lineFromInvoice(invoice, payee) {
1420
1404
  var openAmount = (invoice) => Math.max(0, invoice.totalAmount - invoice.paidAmount);
1421
1405
  function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
1422
1406
  const { currency } = useFinancialConfig();
1423
- const [step, setStep] = React23__default.useState(0);
1424
- const [bankAccountId, setBankAccountId] = React23__default.useState("");
1425
- const [draft, setDraft] = React23__default.useState(null);
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("");
1407
+ const [step, setStep] = React25__default.useState(0);
1408
+ const [bankAccountId, setBankAccountId] = React25__default.useState("");
1409
+ const [draft, setDraft] = React25__default.useState(null);
1410
+ const [context, setContext] = React25__default.useState(null);
1411
+ const [companyName, setCompanyName] = React25__default.useState("");
1412
+ const [companyDocument, setCompanyDocument] = React25__default.useState("");
1413
+ const [lines, setLines] = React25__default.useState([]);
1414
+ const [companyUnsaved, setCompanyUnsaved] = React25__default.useState(false);
1415
+ const [loadingContext, setLoadingContext] = React25__default.useState(false);
1416
+ const [busy, setBusy] = React25__default.useState(false);
1417
+ const [error, setError] = React25__default.useState("");
1434
1418
  const eligibleAccounts2 = bankAccounts.filter((account2) => account2.accountType === "bank" && account2.isActive);
1435
1419
  const total = invoices.reduce((sum, invoice) => sum + openAmount(invoice), 0);
1436
1420
  const account = eligibleAccounts2.find((candidate) => candidate.id === bankAccountId);
1437
1421
  const missingBankData = draft ? missingFields(draft, companyDocument) : [];
1438
- const items = React23__default.useMemo(() => lines.map((line) => ({
1422
+ const items = React25__default.useMemo(() => lines.map((line) => ({
1439
1423
  reference: line.invoice.id,
1440
1424
  amount: openAmount(line.invoice),
1441
1425
  paymentDate: line.paymentDate,
@@ -1460,8 +1444,8 @@ function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
1460
1444
  postalCode: line.payee.postalCode
1461
1445
  }
1462
1446
  })), [lines]);
1463
- const gaps = React23__default.useMemo(() => cnabPayeeGaps(items), [items]);
1464
- const batches = React23__default.useMemo(() => {
1447
+ const gaps = React25__default.useMemo(() => cnabPayeeGaps(items), [items]);
1448
+ const batches = React25__default.useMemo(() => {
1465
1449
  const grouped = /* @__PURE__ */ new Map();
1466
1450
  for (const line of lines) {
1467
1451
  const current = grouped.get(line.paymentForm) ?? { count: 0, total: 0 };
@@ -1877,97 +1861,182 @@ function Field({ label, value, onChange, required, placeholder, className = "" }
1877
1861
  /* @__PURE__ */ jsx("input", { value, onChange: (event) => onChange(event.target.value), placeholder, className: "h-9 w-full rounded-input border bg-background px-3 text-sm outline-none focus:border-primary focus:ring-2 focus:ring-primary/15" })
1878
1862
  ] });
1879
1863
  }
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",
1864
+ var ACCEPT = "image/*,application/pdf";
1865
+ var MAX_BYTES = 15 * 1024 * 1024;
1866
+ function size(bytes) {
1867
+ if (!bytes) return "";
1868
+ return bytes < 1024 * 1024 ? `${Math.max(1, Math.round(bytes / 1024))} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
1869
+ }
1870
+ function InvoiceAttachmentsModal({ invoiceId, onClose, onChanged }) {
1871
+ const provider = useFinancialProvider();
1872
+ const inputRef = useRef(null);
1873
+ const [items, setItems] = useState(null);
1874
+ const [uploading, setUploading] = useState(0);
1875
+ const [over, setOver] = useState(false);
1876
+ const [preview, setPreview] = useState(null);
1877
+ async function load() {
1878
+ try {
1879
+ setItems(await (provider.getInvoiceAttachments?.(invoiceId) ?? Promise.resolve([])));
1880
+ } catch (e) {
1881
+ setItems([]);
1882
+ toast.error(e?.message ?? String(e));
1883
+ }
1884
+ }
1885
+ useEffect(() => {
1886
+ void load();
1887
+ }, [invoiceId]);
1888
+ async function upload(files) {
1889
+ const list = Array.from(files);
1890
+ if (!list.length || !provider.addInvoiceAttachment) return;
1891
+ const invalid = list.filter((f) => !(f.type.startsWith("image/") || f.type === "application/pdf") || f.size > MAX_BYTES);
1892
+ if (invalid.length) toast.error("Envie imagem ou PDF de at\xE9 15 MB.");
1893
+ const ok = list.filter((f) => !invalid.includes(f));
1894
+ if (!ok.length) return;
1895
+ setUploading((n) => n + ok.length);
1896
+ for (const file of ok) {
1897
+ try {
1898
+ await provider.addInvoiceAttachment(invoiceId, file);
1899
+ } catch (e) {
1900
+ toast.error(`${file.name}: ${e?.message ?? String(e)}`);
1901
+ } finally {
1902
+ setUploading((n) => n - 1);
1903
+ }
1904
+ }
1905
+ await load();
1906
+ onChanged?.();
1907
+ }
1908
+ return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
1909
+ if (!o) onClose();
1910
+ }, children: /* @__PURE__ */ jsx(ModalContent, { size: preview !== null ? "3xl" : "sm", children: preview !== null && items?.[preview] ? /* @__PURE__ */ jsx(
1911
+ AttachmentPreview,
1912
+ {
1913
+ items,
1914
+ index: preview,
1915
+ onIndex: setPreview,
1916
+ onBack: () => setPreview(null)
1917
+ }
1918
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1919
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs(ModalTitle, { className: "flex items-center gap-2", children: [
1920
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }),
1921
+ "Anexos da conta"
1922
+ ] }) }),
1923
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
1924
+ items === null ? /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", "aria-busy": "true", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-2.5", children: [
1925
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-4 shrink-0 animate-pulse rounded bg-muted" }),
1926
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
1927
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-2/3 animate-pulse rounded bg-muted" }),
1928
+ /* @__PURE__ */ jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-muted" })
1929
+ ] })
1930
+ ] }, i)) }) : items.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-card bg-muted/40 px-4 py-5 text-center", children: [
1931
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhum anexo ainda" }),
1932
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Guarde aqui o boleto, a nota ou o comprovante de pagamento \u2014 fica tudo junto da conta." })
1933
+ ] }) : /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", children: items.map((a, i) => {
1934
+ const Icon = (a.mimeType ?? "").startsWith("image/") ? Image : FileText;
1935
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
1936
+ "button",
1898
1937
  {
1899
- className: "mx-auto flex w-full max-w-4xl flex-1 flex-col gap-3 overflow-hidden",
1900
- onClick: (e) => e.stopPropagation(),
1938
+ type: "button",
1939
+ onClick: () => setPreview(i),
1940
+ className: "flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-muted/40",
1901
1941
  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
- )
1942
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
1943
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1944
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: a.fileName }),
1945
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: a.pending ? "Arquivo ainda n\xE3o dispon\xEDvel" : [size(a.fileSize), a.createdAt?.slice(0, 10).split("-").reverse().join("/")].filter(Boolean).join(" \xB7 ") })
1934
1946
  ] }),
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
- ] })
1947
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
1964
1948
  ]
1965
1949
  }
1966
- )
1967
- }
1968
- ),
1969
- document.body
1970
- );
1950
+ ) }, a.id);
1951
+ }) }),
1952
+ provider.addInvoiceAttachment && /* @__PURE__ */ jsxs(
1953
+ "button",
1954
+ {
1955
+ type: "button",
1956
+ onClick: () => inputRef.current?.click(),
1957
+ onDragOver: (e) => {
1958
+ e.preventDefault();
1959
+ setOver(true);
1960
+ },
1961
+ onDragLeave: () => setOver(false),
1962
+ onDrop: (e) => {
1963
+ e.preventDefault();
1964
+ setOver(false);
1965
+ void upload(e.dataTransfer.files);
1966
+ },
1967
+ disabled: uploading > 0,
1968
+ className: `flex w-full flex-col items-center gap-1 rounded-card border-2 border-dashed px-4 py-5 text-center transition-colors ${over ? "border-primary bg-primary/5" : "border-border hover:bg-muted/40"}`,
1969
+ children: [
1970
+ uploading > 0 ? /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsx(UploadCloud, { className: "h-5 w-5 text-muted-foreground", "aria-hidden": true }),
1971
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: uploading > 0 ? "Enviando\u2026" : "Solte arquivos aqui ou clique para escolher" }),
1972
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Imagem ou PDF, at\xE9 15 MB" })
1973
+ ]
1974
+ }
1975
+ ),
1976
+ /* @__PURE__ */ jsx(
1977
+ "input",
1978
+ {
1979
+ ref: inputRef,
1980
+ type: "file",
1981
+ accept: ACCEPT,
1982
+ multiple: true,
1983
+ className: "hidden",
1984
+ onChange: (e) => {
1985
+ const f = e.target.files;
1986
+ if (f) void upload(f);
1987
+ e.target.value = "";
1988
+ }
1989
+ }
1990
+ )
1991
+ ] })
1992
+ ] }) }) });
1993
+ }
1994
+ function AttachmentPreview({ items, index, onIndex, onBack }) {
1995
+ const doc = items[index];
1996
+ const isImage = (doc.mimeType ?? "").startsWith("image/");
1997
+ const isPdf = doc.mimeType === "application/pdf";
1998
+ const go = (d) => onIndex((index + d + items.length) % items.length);
1999
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2000
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pr-8", children: [
2001
+ /* @__PURE__ */ jsxs(
2002
+ "button",
2003
+ {
2004
+ type: "button",
2005
+ onClick: onBack,
2006
+ className: "inline-flex shrink-0 items-center gap-1 rounded-md px-1.5 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2007
+ children: [
2008
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
2009
+ " Voltar"
2010
+ ]
2011
+ }
2012
+ ),
2013
+ /* @__PURE__ */ jsx(ModalTitle, { className: "min-w-0 flex-1 truncate text-sm font-medium", title: doc.fileName, children: doc.fileName }),
2014
+ items.length > 1 && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
2015
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => go(-1), "aria-label": "Anterior", className: "rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }) }),
2016
+ index + 1,
2017
+ " de ",
2018
+ items.length,
2019
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => go(1), "aria-label": "Pr\xF3ximo", className: "rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
2020
+ ] }),
2021
+ doc.url && /* @__PURE__ */ jsx(
2022
+ "a",
2023
+ {
2024
+ href: doc.url,
2025
+ target: "_blank",
2026
+ rel: "noreferrer",
2027
+ "aria-label": "Abrir em nova aba",
2028
+ className: "shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2029
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" })
2030
+ }
2031
+ )
2032
+ ] }) }),
2033
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx("div", { className: "flex h-[65vh] items-center justify-center overflow-hidden rounded-card bg-muted/40", children: doc.pending || !doc.url ? /* @__PURE__ */ jsxs("p", { className: "max-w-sm p-8 text-center text-sm text-muted-foreground", children: [
2034
+ "O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por",
2035
+ " ",
2036
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
2037
+ "."
2038
+ ] }) : isImage ? /* @__PURE__ */ jsx("img", { src: doc.url, alt: doc.fileName, className: "max-h-full max-w-full object-contain" }) : isPdf ? /* @__PURE__ */ jsx("iframe", { src: doc.url, title: doc.fileName, className: "h-full w-full bg-white" }) : /* @__PURE__ */ jsx("p", { className: "p-8 text-center text-sm text-muted-foreground", children: "Este tipo de arquivo n\xE3o tem pr\xE9-visualiza\xE7\xE3o. Use o bot\xE3o de abrir." }) }) })
2039
+ ] });
1971
2040
  }
1972
2041
  var KIND_LABELS = {
1973
2042
  supplier: "Fornecedor",
@@ -1994,7 +2063,7 @@ function FinancialCounterpartySearchSelect({
1994
2063
  onEdit,
1995
2064
  disabled
1996
2065
  }) {
1997
- const search = React23.useCallback(async (query) => {
2066
+ const search = React25.useCallback(async (query) => {
1998
2067
  const results = await lookup.search(query);
1999
2068
  return results.map((result) => ({
2000
2069
  id: result.id,
@@ -2197,7 +2266,7 @@ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAc
2197
2266
  },
2198
2267
  children: [
2199
2268
  /* @__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 })
2269
+ n > 0 && /* @__PURE__ */ jsx("span", { className: "absolute -right-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-primary-foreground tabular-nums", children: n })
2201
2270
  ]
2202
2271
  }
2203
2272
  ) });
@@ -2345,13 +2414,8 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2345
2414
  }).finally(() => setConsolidating(null));
2346
2415
  }, [provider, t2, fetchInvoices, query, onEdit]);
2347
2416
  const filtered = invoices.filter((inv) => inv.direction === direction);
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]);
2417
+ const [attachmentsOf, setAttachmentsOf] = useState(null);
2418
+ const openAttachments = useCallback((invoiceId) => setAttachmentsOf(invoiceId), []);
2355
2419
  const columns = useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments);
2356
2420
  const contactLookup = useMemo(() => createFinancialCounterpartyLookup(direction), [direction]);
2357
2421
  const tags = useMemo(
@@ -2436,11 +2500,13 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2436
2500
  }, [currency, direction, t2]);
2437
2501
  const bulkActions = useMemo(() => {
2438
2502
  const actions = [
2439
- ...direction === "debit" ? [{ id: "cnab", label: t2("financial.invoice.generateCnab"), icon: FileText, onSelect: (rows) => setCnabTarget(rows.filter((row) => row.status !== "paid" && row.status !== "cancelled")) }] : [],
2440
2503
  { id: "print", label: t2("financial.invoice.printSelected"), icon: Printer, onSelect: printSelected },
2441
2504
  { id: "export", label: t2("financial.invoice.exportSelected"), icon: Download, onSelect: exportSelected }
2442
2505
  ];
2443
2506
  if (!canEdit) return actions;
2507
+ if (direction === "debit") {
2508
+ actions.unshift({ id: "cnab", label: t2("financial.invoice.generateCnab"), icon: FileText, onSelect: (rows) => setCnabTarget(rows.filter((row) => row.status !== "paid" && row.status !== "cancelled")) });
2509
+ }
2444
2510
  actions.push({
2445
2511
  id: "cancel",
2446
2512
  label: t2("financial.invoice.bulkCancel"),
@@ -2496,17 +2562,20 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2496
2562
  value: unitFilter.options.find((u) => u.id === unitFilter.value)?.name ?? unitFilter.allLabel,
2497
2563
  icon: Building2,
2498
2564
  className: "h-9 w-full justify-start",
2499
- align: "start",
2500
- children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
2501
- /* @__PURE__ */ jsx(FilterMenuRow, { active: !unitFilter.value, onClick: () => {
2502
- unitFilter.setValue(void 0);
2503
- close();
2504
- }, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: unitFilter.allLabel }) }),
2505
- unitFilter.options.map((u) => /* @__PURE__ */ jsx(FilterMenuRow, { active: unitFilter.value === u.id, onClick: () => {
2506
- unitFilter.setValue(u.id);
2507
- close();
2508
- }, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: u.name }) }, u.id))
2509
- ] })
2565
+ align: "end",
2566
+ children: (close) => /* @__PURE__ */ jsx(
2567
+ ChoiceList,
2568
+ {
2569
+ options: unitFilter.options.map((u) => ({ value: u.id, label: u.name })),
2570
+ value: unitFilter.value,
2571
+ onSelect: unitFilter.setValue,
2572
+ allLabel: unitFilter.allLabel,
2573
+ searchable: true,
2574
+ searchPlaceholder: unitFilter.def?.searchPlaceholder,
2575
+ manage: unitFilter.def?.manage,
2576
+ onDone: close
2577
+ }
2578
+ )
2510
2579
  }
2511
2580
  ) : locations.length > 1 ? /* @__PURE__ */ jsx(
2512
2581
  RequiredFilter,
@@ -2515,17 +2584,18 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2515
2584
  value: locations.find((u) => u.id === unitId)?.name ?? t2("financial.invoice.filterAllUnits"),
2516
2585
  icon: Building2,
2517
2586
  className: "h-9 w-full justify-start",
2518
- align: "start",
2519
- children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
2520
- /* @__PURE__ */ jsx(FilterMenuRow, { active: !unitId, onClick: () => {
2521
- setUnitId("");
2522
- close();
2523
- }, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: t2("financial.invoice.filterAllUnits") }) }),
2524
- locations.map((u) => /* @__PURE__ */ jsx(FilterMenuRow, { active: unitId === u.id, onClick: () => {
2525
- setUnitId(u.id);
2526
- close();
2527
- }, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: u.name }) }, u.id))
2528
- ] })
2587
+ align: "end",
2588
+ children: (close) => /* @__PURE__ */ jsx(
2589
+ ChoiceList,
2590
+ {
2591
+ options: locations.map((u) => ({ value: u.id, label: u.name })),
2592
+ value: unitId || void 0,
2593
+ onSelect: (v) => setUnitId(v ?? ""),
2594
+ allLabel: t2("financial.invoice.filterAllUnits"),
2595
+ searchable: true,
2596
+ onDone: close
2597
+ }
2598
+ )
2529
2599
  }
2530
2600
  ) : null;
2531
2601
  const statusField = /* @__PURE__ */ jsx(
@@ -2695,7 +2765,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2695
2765
  },
2696
2766
  ...onNewReceiptGated ? [{
2697
2767
  id: "receipt",
2698
- label: t2("financial.quickTx.sendReceipt"),
2768
+ // Same flow for payables and receivables: the photo is the request and
2769
+ // the assistant returns the form filled in.
2770
+ label: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
2699
2771
  description: t2("financial.quickTx.receiptCapture"),
2700
2772
  icon: Camera,
2701
2773
  intent: "secondary",
@@ -2705,10 +2777,20 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2705
2777
  // preenchido. Ver receipt-agent.ts.
2706
2778
  onSelect: () => startReceiptCapture({
2707
2779
  ask: t2("financial.receipt.ask"),
2708
- uploadLabel: t2("financial.quickTx.sendReceipt"),
2780
+ uploadLabel: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
2709
2781
  uploadHint: t2("financial.quickTx.receiptCapture"),
2710
2782
  prompt: t2("financial.receipt.prompt")
2711
2783
  })
2784
+ }] : [],
2785
+ ...direction === "debit" ? [{
2786
+ id: "payroll",
2787
+ label: t2("financial.invoice.importPayroll"),
2788
+ description: t2("financial.invoice.importPayrollSoon"),
2789
+ icon: Users,
2790
+ intent: "secondary",
2791
+ disabled: true,
2792
+ onSelect: () => {
2793
+ }
2712
2794
  }] : []
2713
2795
  ] : [];
2714
2796
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
@@ -2738,13 +2820,14 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2738
2820
  onEmptyAction: onNewGated
2739
2821
  }
2740
2822
  ),
2741
- viewer && /* @__PURE__ */ jsx(
2742
- AttachmentViewer,
2823
+ attachmentsOf && /* @__PURE__ */ jsx(
2824
+ InvoiceAttachmentsModal,
2743
2825
  {
2744
- attachments: viewer.attachments,
2745
- index: viewer.index,
2746
- onIndexChange: (index) => setViewer((v) => v ? { ...v, index } : v),
2747
- onClose: () => setViewer(null)
2826
+ invoiceId: attachmentsOf,
2827
+ onClose: () => setAttachmentsOf(null),
2828
+ onChanged: () => {
2829
+ void fetchInvoices(query);
2830
+ }
2748
2831
  }
2749
2832
  ),
2750
2833
  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: [
@@ -2797,6 +2880,40 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2797
2880
  cnabTarget !== null ? /* @__PURE__ */ jsx(CnabRemittanceModal, { invoices: cnabTarget, bankAccounts, provider, onClose: () => setCnabTarget(null) }) : null
2798
2881
  ] });
2799
2882
  }
2883
+
2884
+ // src/lib/routes.ts
2885
+ var ACCOUNTS_ROUTE = "/registry/accounts";
2886
+ var CHART_OF_ACCOUNTS_ROUTE = "/settings/financial/chart-of-accounts";
2887
+ var COST_CENTERS_ROUTE = "/settings/financial/cost-centers";
2888
+ function useWritableUnits(fallback = []) {
2889
+ const unitMode = useUnitMode();
2890
+ const activeUnitId = useActiveUnitId() ?? void 0;
2891
+ const reachable = useUnitFilter().options;
2892
+ const choices = !unitMode ? fallback : activeUnitId ? reachable.filter((u) => u.id === activeUnitId) : reachable;
2893
+ return { unitMode, activeUnitId, choices };
2894
+ }
2895
+ function UnitField({ choices, value, onChange, label }) {
2896
+ const t2 = useTranslation();
2897
+ const can = usePermissionOptional();
2898
+ return /* @__PURE__ */ jsxs("div", { children: [
2899
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: label ?? t2("financial.invoiceForm.unit") }),
2900
+ /* @__PURE__ */ jsx(
2901
+ ChoiceSelect,
2902
+ {
2903
+ className: "mt-1",
2904
+ icon: Building2,
2905
+ "aria-label": label ?? t2("financial.invoiceForm.unit"),
2906
+ options: choices.map((c) => ({ value: c.id, label: c.name })),
2907
+ value: value || void 0,
2908
+ onChange: (v) => onChange(v ?? ""),
2909
+ allLabel: "Todas / sem unidade",
2910
+ searchable: true,
2911
+ searchPlaceholder: t2("financial.invoiceForm.searchUnit"),
2912
+ manage: can("units", "manage") ? { label: "Gerenciar unidades", onSelect: () => adminNavigateTo("/settings/locations") } : void 0
2913
+ }
2914
+ )
2915
+ ] });
2916
+ }
2800
2917
  var PAYABLE_INSTRUCTIONS = [
2801
2918
  'Uma "conta a pagar" \xE9 uma DESPESA com um FORNECEDOR \u2014 quem a empresa deve. Para registrar uma,',
2802
2919
  "chame `createPayable`. NUNCA use `createRecord` em `financial:payment-methods` para isso: aquele",
@@ -3825,9 +3942,9 @@ var SUBMIT_DRAFT = {
3825
3942
  parameters: { type: "object", properties: {} }
3826
3943
  };
3827
3944
  function PayableDraftSurface({ config, draft }) {
3828
- const live = React23.useRef(draft);
3945
+ const live = React25.useRef(draft);
3829
3946
  live.current = draft;
3830
- const lookup = React23.useMemo(
3947
+ const lookup = React25.useMemo(
3831
3948
  () => config.contactLookup ?? createArchetypeLookup({
3832
3949
  archetype: "person",
3833
3950
  kind: "supplier",
@@ -3836,7 +3953,7 @@ function PayableDraftSurface({ config, draft }) {
3836
3953
  }),
3837
3954
  [config.contactLookup]
3838
3955
  );
3839
- React23.useEffect(() => {
3956
+ React25.useEffect(() => {
3840
3957
  liveDraft = {
3841
3958
  describe: () => live.current.describe(),
3842
3959
  setSupplier: (name, id) => live.current.setSupplier(name, id),
@@ -3847,7 +3964,7 @@ function PayableDraftSurface({ config, draft }) {
3847
3964
  liveDraft = null;
3848
3965
  };
3849
3966
  }, []);
3850
- const contribution = React23.useMemo(() => ({
3967
+ const contribution = React25.useMemo(() => ({
3851
3968
  id: "financial.payables.draft",
3852
3969
  pluginId: "financial",
3853
3970
  role: "finance",
@@ -3922,7 +4039,7 @@ function PayableDraftSurface({ config, draft }) {
3922
4039
  }
3923
4040
  function createFinancialAgentMount(deps) {
3924
4041
  const FinancialAgentMount = () => {
3925
- const contribution = React23.useMemo(() => ({
4042
+ const contribution = React25.useMemo(() => ({
3926
4043
  id: "financial.payables.global",
3927
4044
  pluginId: "financial",
3928
4045
  role: "finance",
@@ -4017,13 +4134,24 @@ function CardVisual({
4017
4134
  lastFour: lastFour2,
4018
4135
  color,
4019
4136
  brandId,
4020
- size = "sm"
4137
+ size: size2 = "sm"
4021
4138
  }) {
4022
4139
  const brand = useBrandName(brandId);
4023
4140
  const last = (lastFour2 ?? "").trim();
4024
4141
  if (!last && !color && !brand) return null;
4025
4142
  const bg = color && /^#[0-9a-fA-F]{6}$/.test(color) ? color : void 0;
4026
- const lg = size === "lg";
4143
+ const lg = size2 === "lg";
4144
+ if (size2 === "xs") {
4145
+ return /* @__PURE__ */ jsx(
4146
+ "span",
4147
+ {
4148
+ className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[3px] border align-middle text-[6px] font-semibold leading-none tabular-nums",
4149
+ style: bg ? { backgroundColor: bg, color: ink(bg), borderColor: "transparent" } : void 0,
4150
+ title: [brand, last && `\u2022\u2022\u2022\u2022 ${last}`].filter(Boolean).join(" "),
4151
+ children: last
4152
+ }
4153
+ );
4154
+ }
4027
4155
  return /* @__PURE__ */ jsxs(
4028
4156
  "span",
4029
4157
  {
@@ -4093,7 +4221,9 @@ function eligibleAccountsFor(bankAccounts, direction, type) {
4093
4221
  const active = bankAccounts.filter((a) => a.isActive !== false);
4094
4222
  if (direction === "debit") {
4095
4223
  if (txType === "credit_card") return active.filter((a) => a.accountType === "card_payable");
4096
- return active.filter((a) => !["card_payable", "card_receivable_credit", "card_receivable_debit"].includes(a.accountType));
4224
+ const own = (type?.allowedAccountTypes?.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType])?.filter((k) => !k.startsWith("card_"));
4225
+ const kinds = own?.length ? own : ["bank", "cash_physical", "cash_session"];
4226
+ return active.filter((a) => kinds.includes(a.accountType));
4097
4227
  }
4098
4228
  const allowed = type?.allowedAccountTypes && type.allowedAccountTypes.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType];
4099
4229
  if (!allowed || !allowed.length) return bankAccounts;
@@ -4415,10 +4545,10 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
4415
4545
  const payMovement = useFinancialStore((s) => s.payMovement);
4416
4546
  const remaining = movement.amount - movement.paidAmount;
4417
4547
  const [saving, setSaving] = useState(false);
4418
- const command = React23__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
4548
+ const command = React25__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
4419
4549
  const [pending2, setPending] = useState(false);
4420
4550
  const [visible, setVisible] = useState(false);
4421
- React23__default.useEffect(() => {
4551
+ React25__default.useEffect(() => {
4422
4552
  requestAnimationFrame(() => setVisible(true));
4423
4553
  }, []);
4424
4554
  const source = movement.metadata ?? {};
@@ -4537,6 +4667,32 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
4537
4667
  document.body
4538
4668
  );
4539
4669
  }
4670
+ var METHOD_ICONS = {
4671
+ cash: Banknote,
4672
+ pix: QrCode,
4673
+ credit_card: CreditCard,
4674
+ debit_card: CreditCard,
4675
+ bank_transfer: ArrowRightLeft,
4676
+ check: FileCheck,
4677
+ boleto: Receipt,
4678
+ voucher: Ticket
4679
+ };
4680
+ function MethodIcon({ type }) {
4681
+ const Icon = METHOD_ICONS[type?.transactionType ?? ""] ?? Wallet;
4682
+ return /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4683
+ }
4684
+ function AccountIcon({ account }) {
4685
+ if (!account) return null;
4686
+ if (account.accountType === "card_payable" && (account.lastFour || account.color || account.cardBrandId)) {
4687
+ return /* @__PURE__ */ jsx(CardVisual, { size: "xs", lastFour: account.lastFour ?? void 0, color: account.color ?? void 0, brandId: account.cardBrandId ?? void 0 });
4688
+ }
4689
+ if (account.accountType.startsWith("card_")) return /* @__PURE__ */ jsx(CreditCard, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4690
+ if (account.accountType === "cash_physical" || account.accountType === "cash_session") {
4691
+ return /* @__PURE__ */ jsx(Banknote, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4692
+ }
4693
+ const bank = findBrBank(account.bankCode, account.bankName ?? account.name);
4694
+ return bank?.logo ? /* @__PURE__ */ jsx(BankLogo, { bank, size: 16 }) : /* @__PURE__ */ jsx(Landmark, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4695
+ }
4540
4696
  var NONE = "__none__";
4541
4697
  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}`;
4542
4698
  function InstallmentSourceFields({
@@ -4548,6 +4704,7 @@ function InstallmentSourceFields({
4548
4704
  const t2 = useTranslation();
4549
4705
  const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
4550
4706
  const bankAccounts = useFinancialStore((s) => s.bankAccounts);
4707
+ const can = usePermissionOptional();
4551
4708
  const selectedType = paymentMethodTypes.find((tp) => tp.id === value.payMethodTypeId);
4552
4709
  const selectedAccount = bankAccounts.find((a) => a.id === value.payAccountId);
4553
4710
  const accounts = eligibleAccountsFor(bankAccounts, direction, selectedType);
@@ -4559,7 +4716,7 @@ function InstallmentSourceFields({
4559
4716
  onChange({ payMethodTypeId: typeId || void 0, payAccountId: typeId ? conta : void 0 });
4560
4717
  }
4561
4718
  const tone = (filled) => filled ? "text-foreground" : "text-destructive border-b border-dashed border-destructive";
4562
- return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
4719
+ return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-start gap-0.5", children: [
4563
4720
  /* @__PURE__ */ jsxs(
4564
4721
  Select,
4565
4722
  {
@@ -4576,37 +4733,219 @@ function InstallmentSourceFields({
4576
4733
  }
4577
4734
  ),
4578
4735
  /* @__PURE__ */ jsxs(SelectContent, { children: [
4579
- paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: tp.name }, tp.id)),
4736
+ paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
4737
+ /* @__PURE__ */ jsx(MethodIcon, { type: tp }),
4738
+ tp.name
4739
+ ] }) }, tp.id)),
4580
4740
  value.payMethodTypeId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
4581
4741
  ] })
4582
4742
  ]
4583
4743
  }
4584
4744
  ),
4585
- /* @__PURE__ */ jsxs(
4586
- Select,
4745
+ /* @__PURE__ */ jsx(
4746
+ AccountPicker,
4587
4747
  {
4588
- value: value.payAccountId ?? "",
4748
+ accounts,
4749
+ value: value.payAccountId,
4589
4750
  disabled: disabled || !value.payMethodTypeId,
4590
- onValueChange: (raw) => onChange({ ...value, payAccountId: raw === NONE ? void 0 : raw }),
4751
+ title: value.payMethodTypeId ? void 0 : t2("financial.invoiceForm.selectMethodFirst"),
4752
+ placeholder: t2("financial.payment.account"),
4753
+ ariaLabel: t2("financial.invoiceForm.paidFrom"),
4754
+ clearLabel: t2("financial.invoiceForm.clear"),
4755
+ className: `${INLINE_LINK_TONE.dashed} ${tone(!!selectedAccount)}`,
4756
+ onChange: (id) => onChange({ ...value, payAccountId: id }),
4757
+ onManage: can("financial", "manage") ? () => adminNavigateTo(ACCOUNTS_ROUTE) : void 0,
4758
+ manageLabel: t2("financial.invoiceForm.manageAccounts")
4759
+ }
4760
+ )
4761
+ ] });
4762
+ }
4763
+ var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
4764
+ function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className, onManage, manageLabel }) {
4765
+ const [open, setOpen] = React25__default.useState(false);
4766
+ const [q, setQ] = React25__default.useState("");
4767
+ const selected = accounts.find((a) => a.id === value);
4768
+ const list = q ? accounts.filter((a) => norm2(a.name).includes(norm2(q))) : accounts;
4769
+ const pick = (id) => {
4770
+ onChange(id);
4771
+ setOpen(false);
4772
+ setQ("");
4773
+ };
4774
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (o) => {
4775
+ setOpen(o);
4776
+ if (!o) setQ("");
4777
+ }, children: [
4778
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
4779
+ "button",
4780
+ {
4781
+ type: "button",
4782
+ disabled,
4783
+ title,
4784
+ "aria-label": ariaLabel,
4785
+ className: `inline-flex max-w-full items-center gap-1 text-xs disabled:cursor-not-allowed disabled:opacity-50 ${className ?? ""}`,
4591
4786
  children: [
4592
- /* @__PURE__ */ jsx(
4593
- SelectTrigger,
4594
- {
4595
- "aria-label": t2("financial.invoiceForm.paidFrom"),
4596
- title: value.payMethodTypeId ? void 0 : t2("financial.invoiceForm.selectMethodFirst"),
4597
- className: `${TRIGGER_CLASS} ${tone(!!selectedAccount)}`,
4598
- children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t2("financial.payment.account") })
4599
- }
4600
- ),
4601
- /* @__PURE__ */ jsxs(SelectContent, { children: [
4602
- accounts.map((a) => /* @__PURE__ */ jsx(SelectItem, { value: a.id, children: a.name }, a.id)),
4603
- value.payAccountId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
4604
- ] })
4787
+ selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
4788
+ /* @__PURE__ */ jsx(AccountIcon, { account: selected }),
4789
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selected.name })
4790
+ ] }) : placeholder,
4791
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true })
4605
4792
  ]
4606
4793
  }
4607
- )
4794
+ ) }),
4795
+ /* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-64 p-1", children: [
4796
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-1", children: [
4797
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground", "aria-hidden": true }),
4798
+ /* @__PURE__ */ jsx(
4799
+ "input",
4800
+ {
4801
+ autoFocus: true,
4802
+ value: q,
4803
+ onChange: (e) => setQ(e.target.value),
4804
+ onKeyDown: (e) => {
4805
+ if (e.key === "Enter" && list[0]) {
4806
+ e.preventDefault();
4807
+ pick(list[0].id);
4808
+ }
4809
+ },
4810
+ placeholder: "Buscar conta\u2026",
4811
+ className: "h-8 w-full rounded-md border-0 bg-muted/50 pl-7 pr-2 text-xs outline-none focus:ring-0"
4812
+ }
4813
+ )
4814
+ ] }),
4815
+ /* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto", children: list.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: "Nenhuma conta encontrada" }) : list.map((a) => /* @__PURE__ */ jsxs(
4816
+ "button",
4817
+ {
4818
+ type: "button",
4819
+ onClick: () => pick(a.id),
4820
+ className: `flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted ${a.id === value ? "font-medium" : ""}`,
4821
+ children: [
4822
+ /* @__PURE__ */ jsx(AccountIcon, { account: a }),
4823
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: a.name }),
4824
+ a.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
4825
+ ]
4826
+ },
4827
+ a.id
4828
+ )) }),
4829
+ value && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => pick(void 0), className: "mt-1 w-full rounded-md px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted", children: clearLabel }),
4830
+ onManage && /* @__PURE__ */ jsxs(
4831
+ "button",
4832
+ {
4833
+ type: "button",
4834
+ onClick: () => {
4835
+ setOpen(false);
4836
+ onManage();
4837
+ },
4838
+ className: "mt-1 flex w-full items-center gap-2 rounded-md border-t px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
4839
+ children: [
4840
+ /* @__PURE__ */ jsx(Settings, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
4841
+ manageLabel
4842
+ ]
4843
+ }
4844
+ )
4845
+ ] })
4846
+ ] });
4847
+ }
4848
+ var STATUS = {
4849
+ open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
4850
+ pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
4851
+ partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial" },
4852
+ paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid" },
4853
+ overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue" },
4854
+ draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
4855
+ cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
4856
+ };
4857
+ function InstallmentStatusBadge({ status }) {
4858
+ const t2 = useTranslation();
4859
+ const s = STATUS[status ?? ""] ?? STATUS.pending;
4860
+ const Icon = s.icon;
4861
+ return /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${s.bg}`, children: [
4862
+ /* @__PURE__ */ jsx(Icon, { className: "h-2 w-2" }),
4863
+ " ",
4864
+ t2(s.labelKey)
4865
+ ] });
4866
+ }
4867
+ function InstallmentRow({
4868
+ number,
4869
+ amount,
4870
+ status,
4871
+ dueDate,
4872
+ onDueDateChange,
4873
+ dueExtra,
4874
+ source,
4875
+ actions,
4876
+ onClick
4877
+ }) {
4878
+ const t2 = useTranslation();
4879
+ const stop = (e) => e.stopPropagation();
4880
+ return /* @__PURE__ */ jsxs(
4881
+ "div",
4882
+ {
4883
+ className: `flex flex-wrap items-center gap-3 px-4 py-3 ${onClick ? "cursor-pointer transition-colors hover:bg-muted/20" : ""}`,
4884
+ onClick,
4885
+ children: [
4886
+ /* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
4887
+ "#",
4888
+ number
4889
+ ] }),
4890
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-start gap-0.5", children: [
4891
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4892
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: amount }),
4893
+ status && /* @__PURE__ */ jsx(InstallmentStatusBadge, { status })
4894
+ ] }),
4895
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground", onClick: stop, children: [
4896
+ t2("financial.payment.due"),
4897
+ onDueDateChange ? /* @__PURE__ */ jsx(DatePicker, { value: dueDate, variant: "inline", format: "numeric", onChange: onDueDateChange }) : dueDate,
4898
+ dueExtra
4899
+ ] })
4900
+ ] }),
4901
+ /* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", onClick: stop, children: source }),
4902
+ (actions || onClick) && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", onClick: stop, children: [
4903
+ actions,
4904
+ onClick && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
4905
+ ] })
4906
+ ]
4907
+ }
4908
+ );
4909
+ }
4910
+ var BTN = "inline-flex shrink-0 items-center gap-1 px-3 py-1.5 text-xs font-medium transition-colors active:shadow-button-inset disabled:pointer-events-none disabled:opacity-40";
4911
+ function InstallmentPayButton({ onClick, disabled, title, children, variant = "primary" }) {
4912
+ return /* @__PURE__ */ jsxs(
4913
+ "button",
4914
+ {
4915
+ type: "button",
4916
+ onClick,
4917
+ disabled,
4918
+ title,
4919
+ className: variant === "primary" ? `${BTN} rounded-button border border-primary bg-primary text-primary-foreground shadow-button-primary hover:bg-primary/90` : `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`,
4920
+ children: [
4921
+ /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
4922
+ children
4923
+ ]
4924
+ }
4925
+ );
4926
+ }
4927
+ function InstallmentActionButton({ onClick, icon: Icon, children }) {
4928
+ return /* @__PURE__ */ jsxs("button", { type: "button", onClick, className: `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`, children: [
4929
+ /* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
4930
+ children
4608
4931
  ] });
4609
4932
  }
4933
+ function InstallmentReceiptButton({ onClick, title, attached, pending: pending2, count }) {
4934
+ return /* @__PURE__ */ jsxs(
4935
+ "button",
4936
+ {
4937
+ type: "button",
4938
+ onClick,
4939
+ title,
4940
+ "aria-label": title,
4941
+ className: `inline-flex shrink-0 items-center gap-1 rounded-lg border bg-card px-2.5 py-1.5 text-xs font-medium shadow-button transition-colors hover:bg-muted active:shadow-button-inset ${attached ? "text-success" : pending2 ? "text-primary" : ""}`,
4942
+ children: [
4943
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3" }),
4944
+ !!count && /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: count })
4945
+ ]
4946
+ }
4947
+ );
4948
+ }
4610
4949
 
4611
4950
  // src/views/focusItem.ts
4612
4951
  var CHAVE = "fayz:financial:focus-item";
@@ -4631,9 +4970,9 @@ function consumirItemParaAbrir(invoiceId) {
4631
4970
  }
4632
4971
  }
4633
4972
  function useStockedItemTypes(itemTypes, entityLookups) {
4634
- const [empty, setEmpty] = React23__default.useState(null);
4973
+ const [empty, setEmpty] = React25__default.useState(null);
4635
4974
  const backed = itemTypes.filter((type) => entityLookups[type.value]?.count).map((type) => type.value).sort().join(",");
4636
- React23__default.useEffect(() => {
4975
+ React25__default.useEffect(() => {
4637
4976
  const kinds = backed ? backed.split(",") : [];
4638
4977
  if (!kinds.length) {
4639
4978
  setEmpty(null);
@@ -4654,7 +4993,7 @@ function useStockedItemTypes(itemTypes, entityLookups) {
4654
4993
  cancelled = true;
4655
4994
  };
4656
4995
  }, [backed]);
4657
- return React23__default.useMemo(
4996
+ return React25__default.useMemo(
4658
4997
  () => empty?.size ? itemTypes.filter((type) => !empty.has(type.value)) : itemTypes,
4659
4998
  [itemTypes, empty]
4660
4999
  );
@@ -4732,95 +5071,6 @@ function summarizeInvoiceLines(rows, visible = 2) {
4732
5071
  function invoiceOpensForEditing(status) {
4733
5072
  return status !== "paid" && status !== "cancelled";
4734
5073
  }
4735
- function LocationSelector({ locations, value, onChange }) {
4736
- const t2 = useTranslation();
4737
- const [open, setOpen] = useState(false);
4738
- const [search, setSearch] = useState("");
4739
- const ref = useRef(null);
4740
- useEffect(() => {
4741
- function handler(e) {
4742
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
4743
- }
4744
- document.addEventListener("mousedown", handler);
4745
- return () => document.removeEventListener("mousedown", handler);
4746
- }, []);
4747
- const selected = locations.find((l) => l.id === value);
4748
- const filtered = locations.filter(
4749
- (l) => !search.trim() || l.name.toLowerCase().includes(search.toLowerCase())
4750
- );
4751
- return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
4752
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.unit") }),
4753
- /* @__PURE__ */ jsxs(
4754
- "button",
4755
- {
4756
- type: "button",
4757
- onClick: () => setOpen(!open),
4758
- className: "w-full mt-1 flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm text-left hover:bg-muted/30 transition-colors",
4759
- children: [
4760
- /* @__PURE__ */ jsx(Building2, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
4761
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? (value ? t2("financial.invoiceForm.selectUnit") : "Todas / sem unidade") }),
4762
- /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
4763
- ]
4764
- }
4765
- ),
4766
- open && /* @__PURE__ */ jsxs("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden min-w-[260px]", children: [
4767
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [
4768
- /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }),
4769
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.selectUnitTitle") }),
4770
- /* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "ml-auto text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }) })
4771
- ] }),
4772
- /* @__PURE__ */ jsx("div", { className: "p-2 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5", children: [
4773
- /* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground" }),
4774
- /* @__PURE__ */ jsx(
4775
- "input",
4776
- {
4777
- type: "text",
4778
- autoFocus: true,
4779
- value: search,
4780
- onChange: (e) => setSearch(e.target.value),
4781
- placeholder: t2("financial.invoiceForm.searchUnit"),
4782
- className: "flex-1 bg-transparent text-sm outline-none"
4783
- }
4784
- )
4785
- ] }) }),
4786
- /* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-auto py-1", children: [
4787
- /* @__PURE__ */ jsx(
4788
- "button",
4789
- {
4790
- type: "button",
4791
- onClick: () => {
4792
- onChange("");
4793
- setOpen(false);
4794
- setSearch("");
4795
- },
4796
- className: `w-full px-3 py-2.5 text-left text-sm hover:bg-muted/50 ${!value ? "bg-muted/30" : ""}`,
4797
- children: "Todas / sem unidade"
4798
- }
4799
- ),
4800
- filtered.map((loc) => /* @__PURE__ */ jsxs(
4801
- "button",
4802
- {
4803
- onClick: () => {
4804
- onChange(loc.id);
4805
- setOpen(false);
4806
- setSearch("");
4807
- },
4808
- className: `w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-muted/50 transition-colors ${loc.id === value ? "bg-muted/30" : ""}`,
4809
- children: [
4810
- /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-muted shrink-0", children: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }) }),
4811
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
4812
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: loc.name }),
4813
- loc.isHQ && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoiceForm.headquarters") })
4814
- ] })
4815
- ]
4816
- },
4817
- loc.id
4818
- )),
4819
- filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: t2("financial.invoiceForm.noUnitsFound") })
4820
- ] })
4821
- ] })
4822
- ] });
4823
- }
4824
5074
  var formId = 1;
4825
5075
  function fid() {
4826
5076
  return `f${formId++}`;
@@ -4855,11 +5105,9 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4855
5105
  const config = useFinancialConfig();
4856
5106
  const provider = useFinancialProvider();
4857
5107
  const { currency, itemTypes: itemTypesRaw = [], locations = [] } = config;
4858
- const unitMode = useUnitMode();
4859
- const activeUnitId = useActiveUnitId();
5108
+ const { unitMode, activeUnitId, choices: unitChoices } = useWritableUnits(locations);
4860
5109
  const reachableUnits = useUnitFilter().options;
4861
- const unitChoices = unitMode ? reachableUnits : locations;
4862
- const showUnitField = unitChoices.length > 1;
5110
+ const showUnitField = unitMode ? reachableUnits.length > 1 : unitChoices.length > 1;
4863
5111
  const [giftCardsEnabled, setGiftCardsEnabled] = useState(false);
4864
5112
  useEffect(() => {
4865
5113
  let cancelled = false;
@@ -4877,7 +5125,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4877
5125
  };
4878
5126
  }, [direction, provider]);
4879
5127
  const giftCardLookup = useMemo(() => {
4880
- if (!giftCardsEnabled || !provider.getGiftCards) return null;
5128
+ if (!giftCardsEnabled || !provider.findGiftCardByCode) return null;
4881
5129
  const asOption = (card) => ({
4882
5130
  id: card.id,
4883
5131
  label: `Gift Card ${card.code.replace(/(.{4})(?=.)/g, "$1 ")}`,
@@ -4886,30 +5134,43 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4886
5134
  price: card.faceValue,
4887
5135
  data: { price: card.faceValue, code: card.code, state: card.state }
4888
5136
  });
5137
+ const resolve = async (raw) => {
5138
+ const card = await provider.findGiftCardByCode(raw);
5139
+ if (!card) throw new Error(t2("financial.giftCardLine.notFound"));
5140
+ if (card.state === "revoked") throw new Error(t2("financial.giftCardLine.revoked"));
5141
+ if (card.state === "expired") throw new Error(t2("financial.giftCardLine.expired"));
5142
+ if (card.state !== "issued") throw new Error(t2("financial.giftCardLine.alreadySold"));
5143
+ return asOption(card);
5144
+ };
4889
5145
  return {
4890
5146
  async search(query) {
4891
- const cards = await provider.getGiftCards({ search: query.trim() || void 0, state: "issued" });
4892
- return cards.map(asOption);
5147
+ try {
5148
+ return [await resolve(query)];
5149
+ } catch {
5150
+ return [];
5151
+ }
4893
5152
  },
4894
- async getById(id) {
4895
- const cards = await provider.getGiftCards({ state: "issued" });
4896
- const card = cards.find((entry) => entry.id === id);
4897
- return card ? asOption(card) : null;
5153
+ async getById() {
5154
+ return null;
4898
5155
  },
4899
- async list() {
4900
- return (await provider.getGiftCards({ state: "issued" })).map(asOption);
5156
+ byCode: {
5157
+ resolve,
5158
+ label: t2("financial.giftCardLine.label"),
5159
+ placeholder: t2("financial.giftCardLine.placeholder"),
5160
+ hint: t2("financial.giftCardLine.hint"),
5161
+ display: ({ description }) => description.replace(/^Gift Card\s+/, "")
4901
5162
  }
4902
5163
  };
4903
- }, [giftCardsEnabled, provider]);
5164
+ }, [giftCardsEnabled, provider, t2]);
4904
5165
  const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
4905
- const translatedItemTypes = React23__default.useMemo(
5166
+ const translatedItemTypes = React25__default.useMemo(
4906
5167
  () => itemTypesRaw.map((tp) => {
4907
5168
  const traduzido = t2(`financial.itemType.${tp.value}`);
4908
5169
  return traduzido === `financial.itemType.${tp.value}` ? tp : { ...tp, label: traduzido };
4909
5170
  }),
4910
5171
  [itemTypesRaw, t2]
4911
5172
  );
4912
- const offeredItemTypes = React23__default.useMemo(
5173
+ const offeredItemTypes = React25__default.useMemo(
4913
5174
  () => availableInvoiceItemTypes(
4914
5175
  giftCardsEnabled && !translatedItemTypes.some((type) => type.value === "gift_card") ? [...translatedItemTypes, { value: "gift_card", label: t2("financial.itemType.gift_card"), icon: "Gift" }] : translatedItemTypes,
4915
5176
  Object.keys(entityLookups),
@@ -4929,7 +5190,9 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4929
5190
  const createInvoice = useFinancialStore((s) => s.createInvoice);
4930
5191
  const guardMovements = useLimitGuard("movements_month");
4931
5192
  const can = usePermissionOptional();
5193
+ const canManageFinancial = can("financial", "manage");
4932
5194
  const isEdit = !!editId;
5195
+ const [attachmentsOpen, setAttachmentsOpen] = useState(false);
4933
5196
  useEffect(() => {
4934
5197
  void fetchBankAccounts();
4935
5198
  void fetchPaymentMethods();
@@ -5130,7 +5393,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5130
5393
  }, [items.length]);
5131
5394
  const somaParcelas = Math.round(installments.reduce((s, i) => s + i.amount, 0) * 100);
5132
5395
  useEffect(() => {
5133
- if (installments.length === 0 || scheduleTotal <= 0) return;
5396
+ if (installments.length === 0) return;
5397
+ if (scheduleTotal <= 0) {
5398
+ if (!isEdit) setInstallments([]);
5399
+ return;
5400
+ }
5134
5401
  if (somaParcelas === Math.round(scheduleTotal * 100)) return;
5135
5402
  const count = installments.length;
5136
5403
  const perInstallment = Math.floor(scheduleTotal * 100 / count) / 100;
@@ -5139,7 +5406,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5139
5406
  ...inst,
5140
5407
  amount: i === 0 ? perInstallment + remainder : perInstallment
5141
5408
  })));
5142
- }, [scheduleTotal, somaParcelas, installments.length]);
5409
+ }, [scheduleTotal, somaParcelas, installments.length, isEdit]);
5143
5410
  function addItemOfKind(kind) {
5144
5411
  const newItem = {
5145
5412
  _id: fid(),
@@ -5205,13 +5472,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5205
5472
  if (isEdit || totalAmount <= 0 || installments.length > 0) return;
5206
5473
  generateInstallments(1);
5207
5474
  }, [isEdit, totalAmount, installments.length]);
5208
- const fromCard = React23__default.useMemo(
5475
+ const fromCard = React25__default.useMemo(
5209
5476
  () => fromCardId ? bankAccounts.find((a) => a.id === fromCardId) : void 0,
5210
5477
  [fromCardId, bankAccounts]
5211
5478
  );
5212
5479
  const cardLast4 = (fromCard?.accountNumber ?? "").replace(/\D/g, "").slice(-4);
5213
5480
  const nomeJaTemOsDigitos = !!cardLast4 && (fromCard?.name ?? "").replace(/\D/g, "").endsWith(cardLast4);
5214
- const cardMethodName = React23__default.useMemo(() => {
5481
+ const cardMethodName = React25__default.useMemo(() => {
5215
5482
  const id = installments[0]?.payMethodTypeId;
5216
5483
  const escolhida = id ? paymentMethodTypes.find((tp) => tp.id === id) : void 0;
5217
5484
  return (escolhida ?? paymentMethodTypes.find((tp) => tp.transactionType === "credit_card"))?.name;
@@ -5532,7 +5799,6 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5532
5799
  const saved = savedMovements[i];
5533
5800
  const isPaid2 = saved?.status === "paid";
5534
5801
  const isPartial = saved?.status === "partial";
5535
- const StatusIcon = isPaid2 ? CircleCheckBig : CircleDashed;
5536
5802
  const canAttach = isEdit ? !!saved && !isPaid2 : true;
5537
5803
  const semFonte = Math.abs(inst.amount) < 5e-3 && !isPaid2 && !isPartial;
5538
5804
  const fonteTravada = isPaid2 || isPartial;
@@ -5540,75 +5806,61 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5540
5806
  const contaEscolhida = bankAccounts.find((a) => a.id === inst.payAccountId);
5541
5807
  const faltaParaPagar = !inst.payMethodTypeId ? t2("financial.payment.methodRequired") : !inst.payAccountId ? t2("financial.payment.accountRequired") : null;
5542
5808
  return /* @__PURE__ */ jsxs("div", { children: [
5543
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 px-3 py-2", children: [
5544
- /* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
5545
- "#",
5546
- i + 1
5547
- ] }),
5548
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money4(inst.amount) }),
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: [
5550
- /* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5" }),
5551
- t2(isPaid2 ? "financial.invoice.statusPaid" : isPartial ? "financial.invoice.statusPartial" : "financial.invoice.statusPending")
5552
- ] }),
5553
- semFonte ? (
5554
- // Parcela zerada (brinde, 100% de desconto) não sai por forma
5555
- // nenhuma — pedir a forma ali só confunde.
5556
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: "\u2014" })
5557
- ) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
5558
- InstallmentSourceFields,
5559
- {
5560
- direction,
5561
- value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
5562
- onChange: (next) => patch(next)
5563
- }
5564
- ),
5565
- /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [
5566
- !hasPayments ? /* @__PURE__ */ jsx(
5567
- DatePicker,
5809
+ /* @__PURE__ */ jsx(
5810
+ InstallmentRow,
5811
+ {
5812
+ number: i + 1,
5813
+ amount: money4(inst.amount),
5814
+ status: saved?.status,
5815
+ dueDate: inst.dueDate,
5816
+ onDueDateChange: !hasPayments ? (v) => patch({ dueDate: v }) : void 0,
5817
+ source: semFonte ? (
5818
+ // A zero-value line (gift, 100% discount) has no method to ask for.
5819
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" })
5820
+ ) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
5821
+ InstallmentSourceFields,
5568
5822
  {
5569
- value: inst.dueDate,
5570
- variant: "inline",
5571
- format: "numeric",
5572
- onChange: (v) => patch({ dueDate: v })
5573
- }
5574
- ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
5575
- canAttach && /* @__PURE__ */ jsx(
5576
- "button",
5577
- {
5578
- type: "button",
5579
- onClick: () => {
5580
- if (!inst.receiptUrl && pendingReceipt) {
5581
- patch({ receiptUrl: pendingReceipt });
5582
- setPendingReceipt(void 0);
5583
- return;
5584
- }
5585
- setReceiptTarget(i);
5586
- receiptPicker.open();
5587
- },
5588
- title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
5589
- "aria-label": !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
5590
- className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors ${inst.receiptUrl ? "text-success hover:bg-success-soft" : pendingReceipt ? "text-primary hover:bg-primary/10" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
5591
- children: inst.receiptUrl ? /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Camera, { className: "h-3.5 w-3.5" })
5823
+ direction,
5824
+ value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
5825
+ onChange: (next) => patch(next)
5592
5826
  }
5593
5827
  ),
5594
- !isPaid2 && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsxs(
5595
- Button,
5596
- {
5597
- size: "sm",
5598
- variant: "outline",
5599
- disabled: saving || !!faltaParaPagar,
5600
- title: faltaParaPagar ?? void 0,
5601
- onClick: () => {
5602
- void pagarParcela(i);
5603
- },
5604
- children: [
5605
- /* @__PURE__ */ jsx(DollarSign, { className: "mr-1 h-3.5 w-3.5" }),
5606
- t2("financial.invoice.pay")
5607
- ]
5608
- }
5609
- )
5610
- ] })
5611
- ] }),
5828
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
5829
+ (canAttach || isEdit) && /* @__PURE__ */ jsx(
5830
+ InstallmentReceiptButton,
5831
+ {
5832
+ attached: !!inst.receiptUrl,
5833
+ pending: !inst.receiptUrl && !!pendingReceipt,
5834
+ title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
5835
+ onClick: () => {
5836
+ if (isEdit && editId) {
5837
+ setAttachmentsOpen(true);
5838
+ return;
5839
+ }
5840
+ if (!inst.receiptUrl && pendingReceipt) {
5841
+ patch({ receiptUrl: pendingReceipt });
5842
+ setPendingReceipt(void 0);
5843
+ return;
5844
+ }
5845
+ setReceiptTarget(i);
5846
+ receiptPicker.open();
5847
+ }
5848
+ }
5849
+ ),
5850
+ !isPaid2 && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsx(
5851
+ InstallmentPayButton,
5852
+ {
5853
+ disabled: saving || !!faltaParaPagar,
5854
+ title: faltaParaPagar ?? void 0,
5855
+ onClick: () => {
5856
+ void pagarParcela(i);
5857
+ },
5858
+ children: t2("financial.invoice.pay")
5859
+ }
5860
+ )
5861
+ ] })
5862
+ }
5863
+ ),
5612
5864
  !isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
5613
5865
  /* @__PURE__ */ jsx(
5614
5866
  "img",
@@ -5699,7 +5951,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5699
5951
  ] }) });
5700
5952
  const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
5701
5953
  /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
5702
- showUnitField && /* @__PURE__ */ jsx(LocationSelector, { locations: unitChoices, value: unitId, onChange: setUnitId }),
5954
+ showUnitField && /* @__PURE__ */ jsx(UnitField, { choices: unitChoices, value: unitId, onChange: setUnitId }),
5703
5955
  /* @__PURE__ */ jsxs("div", { children: [
5704
5956
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
5705
5957
  isEdit && hasPayments ? /* @__PURE__ */ jsx("p", { className: "mt-1 px-1 py-2 text-sm", children: (/* @__PURE__ */ new Date(`${invoiceDate}T00:00:00`)).toLocaleDateString() }) : /* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
@@ -5864,7 +6116,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5864
6116
  label: accountLabel(a),
5865
6117
  subtitle: a.accountType ?? void 0
5866
6118
  })),
5867
- onChange: (id) => updateItem(item._id, { accountId: id || void 0 })
6119
+ onChange: (id) => updateItem(item._id, { accountId: id || void 0 }),
6120
+ onManage: canManageFinancial ? () => adminNavigateTo(CHART_OF_ACCOUNTS_ROUTE) : void 0,
6121
+ manageLabel: t2("financial.invoiceForm.manageChart"),
6122
+ clearLabel: t2("financial.invoiceForm.clear")
5868
6123
  }
5869
6124
  )
5870
6125
  )
@@ -5887,7 +6142,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5887
6142
  id: c.id,
5888
6143
  label: c.code ? `${c.code} - ${c.name}` : c.name
5889
6144
  })),
5890
- onChange: (id) => updateItem(item._id, { costCenterId: id || void 0 })
6145
+ onChange: (id) => updateItem(item._id, { costCenterId: id || void 0 }),
6146
+ onManage: canManageFinancial ? () => adminNavigateTo(COST_CENTERS_ROUTE) : void 0,
6147
+ manageLabel: t2("financial.invoiceForm.manageCostCenters"),
6148
+ clearLabel: t2("financial.invoiceForm.clear")
5891
6149
  }
5892
6150
  )
5893
6151
  }] : []
@@ -5920,6 +6178,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5920
6178
  }
5921
6179
  ),
5922
6180
  receiptPicker.input,
6181
+ attachmentsOpen && editId && /* @__PURE__ */ jsx(InvoiceAttachmentsModal, { invoiceId: editId, onClose: () => setAttachmentsOpen(false) }),
5923
6182
  payingMovement && /* @__PURE__ */ jsx(
5924
6183
  PaymentModal,
5925
6184
  {
@@ -6015,6 +6274,11 @@ function groupPaymentsByBill(bills, payments) {
6015
6274
  }
6016
6275
  return { byBillId, unattributed };
6017
6276
  }
6277
+ var reversalOf = (m) => m.metadata?.reversal_of;
6278
+ function reversiblePayments(payments) {
6279
+ const reversed = new Set(payments.map(reversalOf).filter(Boolean));
6280
+ return payments.filter((m) => !reversalOf(m) && !reversed.has(m.id));
6281
+ }
6018
6282
 
6019
6283
  // src/lib/payment-how.ts
6020
6284
  var NOT_A_HOLDER = /* @__PURE__ */ new Set(["person", "system"]);
@@ -6062,6 +6326,7 @@ function PaymentReceiptModal({
6062
6326
  const { currency } = useFinancialConfig();
6063
6327
  const fmt = (n) => formatCurrency(n, currency);
6064
6328
  const ordenados = [...payments].sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
6329
+ const estornaveis = new Set(reversiblePayments(ordenados).map((m) => m.id));
6065
6330
  const total = ordenados.reduce((soma, m) => soma + (ehEstorno(m) ? -1 : 1) * (m.paidAmount || m.amount), 0);
6066
6331
  return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
6067
6332
  if (!o) onClose();
@@ -6101,7 +6366,7 @@ function PaymentReceiptModal({
6101
6366
  ] }),
6102
6367
  /* @__PURE__ */ jsx("span", { className: "text-xs", children: payment.notes })
6103
6368
  ] }),
6104
- onReverse && !estorno && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
6369
+ onReverse && estornaveis.has(payment.id) && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
6105
6370
  /* @__PURE__ */ jsx(Hint, { label: t2("financial.paymentReceipt.unlinkUnavailable"), children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "w-full", disabled: true, children: [
6106
6371
  /* @__PURE__ */ jsx(Unlink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
6107
6372
  t2("financial.paymentReceipt.unlink")
@@ -6133,14 +6398,6 @@ var ITEM_ICONS = {
6133
6398
  gift_card: Gift,
6134
6399
  other: Receipt
6135
6400
  };
6136
- var HOW_ICONS = {
6137
- cash: Banknote,
6138
- pix: QrCode,
6139
- credit_card: CreditCard,
6140
- debit_card: CreditCard,
6141
- bank_transfer: ArrowRightLeft,
6142
- check: FileCheck
6143
- };
6144
6401
  var STATUS_COLORS = {
6145
6402
  open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
6146
6403
  pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
@@ -6174,7 +6431,6 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6174
6431
  const unitName = useUnitStore((st) => unitMode && invoice?.unitId ? st.byId[invoice.unitId]?.name : void 0);
6175
6432
  const [items, setItems] = useState([]);
6176
6433
  const [attachments, setAttachments] = useState([]);
6177
- const [viewing, setViewing] = useState(null);
6178
6434
  const [movements, setMovements] = useState([]);
6179
6435
  const [payments, setPayments] = useState([]);
6180
6436
  const [holders, setHolders] = useState(/* @__PURE__ */ new Map());
@@ -6184,13 +6440,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6184
6440
  const [receiptBill, setReceiptBill] = useState(null);
6185
6441
  const [reverseReason, setReverseReason] = useState("");
6186
6442
  const [payingMovement, setPayingMovement] = useState(null);
6187
- const [receiptFor, setReceiptFor] = useState(null);
6443
+ const [attachmentsOpen, setAttachmentsOpen] = useState(false);
6188
6444
  const [pendingReceipt, setPendingReceipt] = useState();
6189
- const receiptPicker = useReceiptPicker((dataUrl) => {
6190
- setPendingReceipt(dataUrl);
6191
- setPayingMovement(receiptFor);
6192
- setReceiptFor(null);
6193
- });
6194
6445
  const [menuOpen, setMenuOpen] = useState(false);
6195
6446
  const [confirmCancel, setConfirmCancel] = useState(false);
6196
6447
  const [cancelling, setCancelling] = useState(false);
@@ -6221,6 +6472,17 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6221
6472
  void loadData();
6222
6473
  }
6223
6474
  }
6475
+ async function patchInvoiceDate(invoiceDate) {
6476
+ if (!invoice || !invoiceDate || invoiceDate === invoice.invoiceDate) return;
6477
+ const antes = invoice;
6478
+ setInvoice({ ...invoice, invoiceDate });
6479
+ try {
6480
+ await provider.updateInvoice(invoice.id, { invoiceDate });
6481
+ } catch (e) {
6482
+ setInvoice(antes);
6483
+ toast.error(e?.message ?? String(e));
6484
+ }
6485
+ }
6224
6486
  const reqRef = useRef(0);
6225
6487
  async function loadData() {
6226
6488
  const req = ++reqRef.current;
@@ -6311,8 +6573,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6311
6573
  setCancelling(false);
6312
6574
  }
6313
6575
  }
6314
- const [chartNames, setChartNames] = React23__default.useState(/* @__PURE__ */ new Map());
6315
- React23__default.useEffect(() => {
6576
+ const [chartNames, setChartNames] = React25__default.useState(/* @__PURE__ */ new Map());
6577
+ React25__default.useEffect(() => {
6316
6578
  let cancelled2 = false;
6317
6579
  void provider.getChartOfAccounts().then((rows) => {
6318
6580
  if (!cancelled2) setChartNames(new Map(rows.map((r) => [r.id, r.name])));
@@ -6432,7 +6694,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6432
6694
  { id: "total", label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
6433
6695
  { id: "items", label: t2("financial.invoice.items"), value: items.length, icon: LayoutList },
6434
6696
  { id: "installments", label: t2("financial.invoice.installments"), value: `${invoice.totalInstallments}x`, icon: Layers },
6435
- { id: "date", label: t2("financial.invoice.date"), value: invoice.invoiceDate, icon: CalendarDays }
6697
+ {
6698
+ id: "date",
6699
+ label: t2("financial.invoice.date"),
6700
+ icon: CalendarDays,
6701
+ value: invoice.status !== "cancelled" ? /* @__PURE__ */ jsx(DatePicker, { value: invoice.invoiceDate, variant: "inline", format: "numeric", onChange: (v) => {
6702
+ void patchInvoiceDate(v);
6703
+ } }) : invoice.invoiceDate
6704
+ }
6436
6705
  ],
6437
6706
  customerLabel: direction === "debit" ? t2("financial.invoice.payTo") : t2("financial.invoice.receiveFrom"),
6438
6707
  unit: unitName ? { label: t2("financial.invoice.unit"), value: unitName } : void 0,
@@ -6450,7 +6719,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6450
6719
  "button",
6451
6720
  {
6452
6721
  type: "button",
6453
- onClick: () => setViewing(0),
6722
+ onClick: () => setAttachmentsOpen(true),
6454
6723
  title: attachments.map((a) => a.fileName).join("\n"),
6455
6724
  className: "mb-2 inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
6456
6725
  children: [
@@ -6465,14 +6734,12 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6465
6734
  const isPaid2 = mov.status === "paid";
6466
6735
  const isPartial = mov.status === "partial";
6467
6736
  const hasPaidDetails = isPaid2 || isPartial;
6468
- const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
6469
- const MovIcon = movStatus.icon;
6470
6737
  const instPayments = settlements.byBillId.get(mov.id) ?? [];
6471
6738
  const hows = instPayments.map((payment) => describePayment(payment, howCtx));
6472
6739
  const paymentSummary = [...new Set(hows.map((how2) => [how2.method, how2.account?.name].filter(Boolean).join(" \xB7 ")).filter(Boolean))].join(" / ");
6473
6740
  const how = hows[0];
6474
6741
  const howCard = how?.account?.accountType === "card_payable" ? how.account : void 0;
6475
- const HowIcon = HOW_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
6742
+ const HowIcon = METHOD_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
6476
6743
  const srcMeta = mov.metadata ?? {};
6477
6744
  const srcStr = (v) => typeof v === "string" && v ? v : void 0;
6478
6745
  const srcTypeId = srcStr(srcMeta.payMethodTypeId);
@@ -6480,66 +6747,41 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6480
6747
  const editavel = !hasPaidDetails && invoice.status !== "cancelled";
6481
6748
  const canChooseSource = editavel && Math.abs(mov.amount) >= 5e-3;
6482
6749
  const faltaParaPagar = !srcTypeId ? t2("financial.payment.methodRequired") : !srcAccountId ? t2("financial.payment.accountRequired") : null;
6483
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
6484
- "div",
6750
+ const ativa = invoice.status !== "cancelled";
6751
+ return /* @__PURE__ */ jsx(
6752
+ InstallmentRow,
6485
6753
  {
6486
- className: `flex items-center gap-3 px-4 py-3 ${hasPaidDetails ? "cursor-pointer hover:bg-muted/20 transition-colors" : ""}`,
6754
+ number: mov.installmentNumber ?? 1,
6755
+ amount: formatCurrency(mov.amount, currency),
6756
+ status: mov.status,
6757
+ dueDate: mov.dueDate,
6758
+ onDueDateChange: editavel ? (v) => {
6759
+ void patchDueDate(mov, v);
6760
+ } : void 0,
6761
+ dueExtra: /* @__PURE__ */ jsxs(Fragment, { children: [
6762
+ mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
6763
+ mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
6764
+ ] }),
6487
6765
  onClick: hasPaidDetails ? () => setReceiptBill(mov) : void 0,
6488
- children: [
6489
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground w-6 shrink-0", children: [
6490
- "#",
6491
- mov.installmentNumber
6492
- ] }),
6493
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-0.5", children: [
6494
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: formatCurrency(mov.amount, currency) }),
6495
- /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${movStatus.bg}`, children: [
6496
- /* @__PURE__ */ jsx(MovIcon, { className: "h-2 w-2" }),
6497
- " ",
6498
- t2(movStatus.labelKey)
6499
- ] }),
6500
- /* @__PURE__ */ jsxs(
6501
- "span",
6502
- {
6503
- className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground",
6504
- onClick: (e) => e.stopPropagation(),
6505
- children: [
6506
- t2("financial.payment.due"),
6507
- editavel ? /* @__PURE__ */ jsx(
6508
- DatePicker,
6509
- {
6510
- value: mov.dueDate,
6511
- variant: "inline",
6512
- format: "numeric",
6513
- onChange: (v) => {
6514
- void patchDueDate(mov, v);
6515
- }
6516
- }
6517
- ) : mov.dueDate,
6518
- mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
6519
- mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
6520
- ]
6521
- }
6522
- )
6523
- ] }),
6524
- canChooseSource && /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
6525
- InstallmentSourceFields,
6526
- {
6527
- direction,
6528
- value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
6529
- onChange: (next) => {
6530
- void patchSource(mov, {
6531
- payMethodTypeId: next.payMethodTypeId ?? null,
6532
- payAccountId: next.payAccountId ?? null
6533
- });
6534
- }
6766
+ source: canChooseSource ? /* @__PURE__ */ jsx(
6767
+ InstallmentSourceFields,
6768
+ {
6769
+ direction,
6770
+ value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
6771
+ onChange: (next) => {
6772
+ void patchSource(mov, {
6773
+ payMethodTypeId: next.payMethodTypeId ?? null,
6774
+ payAccountId: next.payAccountId ?? null
6775
+ });
6535
6776
  }
6536
- ) }),
6777
+ }
6778
+ ) : void 0,
6779
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
6537
6780
  paymentSummary && /* @__PURE__ */ jsx(
6538
6781
  "button",
6539
6782
  {
6540
6783
  type: "button",
6541
- onClick: (e) => {
6542
- e.stopPropagation();
6784
+ onClick: () => {
6543
6785
  if (hasPaidDetails) setReceiptBill(mov);
6544
6786
  },
6545
6787
  title: paymentSummary,
@@ -6548,72 +6790,38 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6548
6790
  children: howCard ? /* @__PURE__ */ jsx(CardVisual, { lastFour: howCard.lastFour ?? void 0, color: howCard.color ?? void 0, brandId: howCard.cardBrandId ?? void 0 }) : HowIcon ? /* @__PURE__ */ jsx(HowIcon, { className: "h-4 w-4", "aria-hidden": true }) : null
6549
6791
  }
6550
6792
  ),
6551
- !isPaid2 && invoice.status !== "cancelled" && /* @__PURE__ */ jsx(
6552
- "button",
6793
+ /* @__PURE__ */ jsx(
6794
+ InstallmentReceiptButton,
6553
6795
  {
6554
- onClick: (e) => {
6555
- e.stopPropagation();
6556
- setReceiptFor(mov);
6557
- receiptPicker.open();
6558
- },
6559
- title: t2("financial.quickTx.receiptCapture"),
6560
- "aria-label": t2("financial.quickTx.receiptCapture"),
6561
- className: "inline-flex shrink-0 items-center gap-1 rounded-lg border bg-card px-2.5 py-1.5 text-xs font-medium shadow-button transition-colors hover:bg-muted active:shadow-button-inset",
6562
- children: /* @__PURE__ */ jsx(Camera, { className: "h-3 w-3" })
6796
+ title: "Anexos",
6797
+ attached: attachments.length > 0,
6798
+ count: attachments.length,
6799
+ onClick: () => setAttachmentsOpen(true)
6563
6800
  }
6564
6801
  ),
6565
- !isPaid2 && !isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
6566
- "button",
6802
+ !isPaid2 && !isPartial && ativa && /* @__PURE__ */ jsxs(
6803
+ InstallmentPayButton,
6567
6804
  {
6568
- onClick: (e) => {
6569
- e.stopPropagation();
6570
- setPayingMovement(mov);
6571
- },
6805
+ onClick: () => setPayingMovement(mov),
6572
6806
  disabled: !!faltaParaPagar,
6573
6807
  title: faltaParaPagar ?? void 0,
6574
- className: "inline-flex items-center gap-1 rounded-button bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0 disabled:opacity-40 disabled:pointer-events-none",
6575
6808
  children: [
6576
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
6577
6809
  t2("financial.invoice.pay"),
6578
6810
  " ",
6579
6811
  remaining < mov.amount ? formatCurrency(remaining, currency) : ""
6580
6812
  ]
6581
6813
  }
6582
6814
  ),
6583
- isPaid2 && instPayments.length > 0 && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
6584
- "button",
6585
- {
6586
- onClick: (e) => {
6587
- e.stopPropagation();
6588
- setReversing(instPayments[instPayments.length - 1]);
6589
- },
6590
- className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
6591
- children: [
6592
- /* @__PURE__ */ jsx(Undo2, { className: "h-3 w-3" }),
6593
- t2("financial.invoice.reverse")
6594
- ]
6595
- }
6596
- ),
6597
- isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
6598
- "button",
6599
- {
6600
- onClick: (e) => {
6601
- e.stopPropagation();
6602
- setPayingMovement(mov);
6603
- },
6604
- className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
6605
- children: [
6606
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
6607
- t2("financial.invoice.pay"),
6608
- " ",
6609
- formatCurrency(remaining, currency)
6610
- ]
6611
- }
6612
- ),
6613
- hasPaidDetails && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
6614
- ]
6615
- }
6616
- ) }, mov.id);
6815
+ isPaid2 && reversiblePayments(instPayments).length > 0 && ativa && /* @__PURE__ */ jsx(InstallmentActionButton, { icon: Undo2, onClick: () => setReversing(reversiblePayments(instPayments).at(-1)), children: t2("financial.invoice.reverse") }),
6816
+ isPartial && ativa && /* @__PURE__ */ jsxs(InstallmentPayButton, { variant: "outline", onClick: () => setPayingMovement(mov), children: [
6817
+ t2("financial.invoice.pay"),
6818
+ " ",
6819
+ formatCurrency(remaining, currency)
6820
+ ] })
6821
+ ] })
6822
+ },
6823
+ mov.id
6824
+ );
6617
6825
  }) }),
6618
6826
  settlements.unattributed.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t bg-warning-soft/40 px-4 py-2.5", children: [
6619
6827
  /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground", children: t2("financial.invoice.unlinkedPayments") }),
@@ -6632,14 +6840,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6632
6840
  extraSections: sideBlocks
6633
6841
  }
6634
6842
  ),
6635
- receiptPicker.input,
6636
- viewing !== null && /* @__PURE__ */ jsx(
6637
- AttachmentViewer,
6843
+ attachmentsOpen && /* @__PURE__ */ jsx(
6844
+ InvoiceAttachmentsModal,
6638
6845
  {
6639
- attachments,
6640
- index: viewing,
6641
- onIndexChange: setViewing,
6642
- onClose: () => setViewing(null)
6846
+ invoiceId: invoice.id,
6847
+ onClose: () => setAttachmentsOpen(false),
6848
+ onChanged: () => {
6849
+ void loadData();
6850
+ }
6643
6851
  }
6644
6852
  ),
6645
6853
  receiptBill && /* @__PURE__ */ jsx(
@@ -7362,7 +7570,7 @@ function CashRegistersView({ variant = "page" } = {}) {
7362
7570
  const [tab, setTab] = useState("open");
7363
7571
  const [reviewRows, setReviewRows] = useState(null);
7364
7572
  const [reviewError, setReviewError] = useState(null);
7365
- const loadReviews = React23__default.useCallback(async () => {
7573
+ const loadReviews = React25__default.useCallback(async () => {
7366
7574
  try {
7367
7575
  setReviewError(null);
7368
7576
  setReviewRows(await provider.getCashSessionReviews?.() ?? []);
@@ -7962,7 +8170,7 @@ function StatementsView({ onNavigate }) {
7962
8170
  "extrato"
7963
8171
  );
7964
8172
  };
7965
- const headerActions = React23__default.useMemo(() => [{
8173
+ const headerActions = React25__default.useMemo(() => [{
7966
8174
  id: "new-transfer",
7967
8175
  label: t2("financial.statements.newTransfer"),
7968
8176
  icon: ArrowRightLeft,
@@ -8158,15 +8366,15 @@ function CommissionsView() {
8158
8366
  const fetchCommissions = useFinancialStore((s) => s.fetchCommissions);
8159
8367
  const generateCommissions = useFinancialStore((s) => s.generateCommissions);
8160
8368
  const provider = useFinancialProvider();
8161
- const [from, setFrom] = React23__default.useState(firstOfMonth());
8162
- const [to, setTo] = React23__default.useState(today2());
8163
- const [preset, setPreset] = React23__default.useState("this_month");
8164
- const [running, setRunning] = React23__default.useState(false);
8165
- const [lastRun, setLastRun] = React23__default.useState(null);
8166
- const [generation, setGeneration] = React23__default.useState(null);
8167
- const [busy, setBusy] = React23__default.useState(null);
8168
- const [error, setError] = React23__default.useState(null);
8169
- const loadGeneration = React23__default.useCallback(async () => {
8369
+ const [from, setFrom] = React25__default.useState(firstOfMonth());
8370
+ const [to, setTo] = React25__default.useState(today2());
8371
+ const [preset, setPreset] = React25__default.useState("this_month");
8372
+ const [running, setRunning] = React25__default.useState(false);
8373
+ const [lastRun, setLastRun] = React25__default.useState(null);
8374
+ const [generation, setGeneration] = React25__default.useState(null);
8375
+ const [busy, setBusy] = React25__default.useState(null);
8376
+ const [error, setError] = React25__default.useState(null);
8377
+ const loadGeneration = React25__default.useCallback(async () => {
8170
8378
  try {
8171
8379
  const gens = await provider.getCommissionGenerations?.() ?? [];
8172
8380
  const doPeriodo = gens.filter((g) => g.periodStart === from && g.periodEnd === to);
@@ -8174,11 +8382,11 @@ function CommissionsView() {
8174
8382
  } catch {
8175
8383
  }
8176
8384
  }, [provider, from, to]);
8177
- React23__default.useEffect(() => {
8385
+ React25__default.useEffect(() => {
8178
8386
  void fetchCommissions({ periodStart: from, periodEnd: to });
8179
8387
  void loadGeneration();
8180
8388
  }, [fetchCommissions, loadGeneration, from, to]);
8181
- const byPerson = React23__default.useMemo(() => {
8389
+ const byPerson = React25__default.useMemo(() => {
8182
8390
  const map = /* @__PURE__ */ new Map();
8183
8391
  for (const c of commissions2) {
8184
8392
  const key = c.personId;
@@ -8231,7 +8439,7 @@ function CommissionsView() {
8231
8439
  setBusy(null);
8232
8440
  }
8233
8441
  }
8234
- const headerActions = React23__default.useMemo(() => [{
8442
+ const headerActions = React25__default.useMemo(() => [{
8235
8443
  id: "commission-rules",
8236
8444
  label: "Configurar regras de comiss\xE3o",
8237
8445
  icon: Settings,
@@ -8679,13 +8887,13 @@ function money3(v) {
8679
8887
  function GoalsView() {
8680
8888
  const { labels } = useFinancialConfig();
8681
8889
  const provider = useFinancialProvider();
8682
- const [units, setUnits] = React23__default.useState([]);
8683
- const [unitId, setUnitId] = React23__default.useState("");
8684
- const [year, setYear] = React23__default.useState((/* @__PURE__ */ new Date()).getFullYear());
8685
- const [rows, setRows] = React23__default.useState(null);
8686
- const [editing, setEditing] = React23__default.useState(null);
8687
- const [error, setError] = React23__default.useState(null);
8688
- React23__default.useEffect(() => {
8890
+ const [units, setUnits] = React25__default.useState([]);
8891
+ const [unitId, setUnitId] = React25__default.useState("");
8892
+ const [year, setYear] = React25__default.useState((/* @__PURE__ */ new Date()).getFullYear());
8893
+ const [rows, setRows] = React25__default.useState(null);
8894
+ const [editing, setEditing] = React25__default.useState(null);
8895
+ const [error, setError] = React25__default.useState(null);
8896
+ React25__default.useEffect(() => {
8689
8897
  void (async () => {
8690
8898
  const cat = await provider.getCommissionCatalogs?.();
8691
8899
  const us = cat?.units ?? [];
@@ -8693,7 +8901,7 @@ function GoalsView() {
8693
8901
  setUnitId((cur) => cur || us[0]?.id || "");
8694
8902
  })();
8695
8903
  }, [provider]);
8696
- const load = React23__default.useCallback(async () => {
8904
+ const load = React25__default.useCallback(async () => {
8697
8905
  if (!unitId) return;
8698
8906
  setRows(null);
8699
8907
  try {
@@ -8704,7 +8912,7 @@ function GoalsView() {
8704
8912
  setRows([]);
8705
8913
  }
8706
8914
  }, [provider, unitId, year]);
8707
- React23__default.useEffect(() => {
8915
+ React25__default.useEffect(() => {
8708
8916
  void load();
8709
8917
  }, [load]);
8710
8918
  const totalRealizado = (rows ?? []).reduce((s, r) => s + r.achievedAmount, 0);
@@ -8835,11 +9043,11 @@ function GoalDialog({
8835
9043
  onSaved
8836
9044
  }) {
8837
9045
  const provider = useFinancialProvider();
8838
- const [target, setTarget] = React23__default.useState(String(goal.targetAmount || ""));
8839
- const [sup, setSup] = React23__default.useState(String(goal.superAmount || ""));
8840
- const [hyper, setHyper] = React23__default.useState(String(goal.hyperAmount || ""));
8841
- const [saving, setSaving] = React23__default.useState(false);
8842
- const [error, setError] = React23__default.useState(null);
9046
+ const [target, setTarget] = React25__default.useState(String(goal.targetAmount || ""));
9047
+ const [sup, setSup] = React25__default.useState(String(goal.superAmount || ""));
9048
+ const [hyper, setHyper] = React25__default.useState(String(goal.hyperAmount || ""));
9049
+ const [saving, setSaving] = React25__default.useState(false);
9050
+ const [error, setError] = React25__default.useState(null);
8843
9051
  const t2 = Number(target) || 0;
8844
9052
  const s = Number(sup) || 0;
8845
9053
  const h = Number(hyper) || 0;
@@ -8930,21 +9138,21 @@ function GiftCardsView() {
8930
9138
  const provider = useFinancialProvider();
8931
9139
  const { currency } = useFinancialConfig();
8932
9140
  const can = usePermissionOptional();
8933
- const [enabled, setEnabled] = React23__default.useState(null);
8934
- const [cards, setCards] = React23__default.useState(null);
8935
- const [error, setError] = React23__default.useState(null);
8936
- const [issuing, setIssuing] = React23__default.useState(false);
8937
- const [issued, setIssued] = React23__default.useState(null);
8938
- const [activating, setActivating] = React23__default.useState(null);
8939
- const [search, setSearch] = React23__default.useState("");
8940
- const [state, setState] = React23__default.useState("");
8941
- const [minValue, setMinValue] = React23__default.useState("");
8942
- const [maxValue, setMaxValue] = React23__default.useState("");
8943
- const [from, setFrom] = React23__default.useState("");
8944
- const [to, setTo] = React23__default.useState("");
8945
- const money4 = React23__default.useCallback((v) => formatCurrency(v, currency), [currency]);
8946
- const seq = React23__default.useRef(0);
8947
- const load = React23__default.useCallback(async () => {
9141
+ const [enabled, setEnabled] = React25__default.useState(null);
9142
+ const [cards, setCards] = React25__default.useState(null);
9143
+ const [error, setError] = React25__default.useState(null);
9144
+ const [issuing, setIssuing] = React25__default.useState(false);
9145
+ const [issued, setIssued] = React25__default.useState(null);
9146
+ const [activating, setActivating] = React25__default.useState(null);
9147
+ const [search, setSearch] = React25__default.useState("");
9148
+ const [state, setState] = React25__default.useState("");
9149
+ const [minValue, setMinValue] = React25__default.useState("");
9150
+ const [maxValue, setMaxValue] = React25__default.useState("");
9151
+ const [from, setFrom] = React25__default.useState("");
9152
+ const [to, setTo] = React25__default.useState("");
9153
+ const money4 = React25__default.useCallback((v) => formatCurrency(v, currency), [currency]);
9154
+ const seq = React25__default.useRef(0);
9155
+ const load = React25__default.useCallback(async () => {
8948
9156
  const mine = ++seq.current;
8949
9157
  setCards(null);
8950
9158
  setError(null);
@@ -8965,7 +9173,7 @@ function GiftCardsView() {
8965
9173
  setCards([]);
8966
9174
  }
8967
9175
  }, [provider, search, state, minValue, maxValue, from, to]);
8968
- React23__default.useEffect(() => {
9176
+ React25__default.useEffect(() => {
8969
9177
  void (async () => {
8970
9178
  try {
8971
9179
  setEnabled(await provider.giftCardsEnabled?.() === true);
@@ -8974,10 +9182,10 @@ function GiftCardsView() {
8974
9182
  }
8975
9183
  })();
8976
9184
  }, [provider]);
8977
- React23__default.useEffect(() => {
9185
+ React25__default.useEffect(() => {
8978
9186
  if (enabled) void load();
8979
9187
  }, [enabled, load]);
8980
- const columns = React23__default.useMemo(() => [
9188
+ const columns = React25__default.useMemo(() => [
8981
9189
  {
8982
9190
  accessorKey: "code",
8983
9191
  header: "C\xF3digo",
@@ -9158,12 +9366,12 @@ function IssueDialog({
9158
9366
  onIssued
9159
9367
  }) {
9160
9368
  const provider = useFinancialProvider();
9161
- const [quantity, setQuantity] = React23__default.useState("10");
9162
- const [amount, setAmount] = React23__default.useState("100");
9163
- const [expiresAt, setExpiresAt] = React23__default.useState("");
9164
- const [note, setNote] = React23__default.useState("");
9165
- const [saving, setSaving] = React23__default.useState(false);
9166
- const [error, setError] = React23__default.useState(null);
9369
+ const [quantity, setQuantity] = React25__default.useState("10");
9370
+ const [amount, setAmount] = React25__default.useState("100");
9371
+ const [expiresAt, setExpiresAt] = React25__default.useState("");
9372
+ const [note, setNote] = React25__default.useState("");
9373
+ const [saving, setSaving] = React25__default.useState(false);
9374
+ const [error, setError] = React25__default.useState(null);
9167
9375
  const q = Math.trunc(Number(quantity)) || 0;
9168
9376
  const a = Number(amount) || 0;
9169
9377
  const total = q * a;
@@ -9572,6 +9780,21 @@ var shortDate2 = (value) => {
9572
9780
  const [year, month, day] = value.split("-");
9573
9781
  return year && month && day ? `${day}/${month}/${year}` : value;
9574
9782
  };
9783
+ var shiftDate = (value, days) => {
9784
+ const d = /* @__PURE__ */ new Date(`${value}T00:00:00`);
9785
+ if (Number.isNaN(d.getTime())) return value;
9786
+ d.setDate(d.getDate() + days);
9787
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
9788
+ };
9789
+ var toAmountInput = (n) => round23(Math.max(0, n)).toFixed(2).replace(".", ",");
9790
+ var CAMPO_FILTRO = "h-[30px] w-full rounded-input border border-input bg-card px-2 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-1 focus:ring-ring";
9791
+ var LINK_DAYS = 3;
9792
+ var LINK_AMOUNT_TOLERANCE = 0.1;
9793
+ var WIDEN_DAYS = 15;
9794
+ var parseAmount = (value) => {
9795
+ const n = Number.parseFloat(value.replace(",", "."));
9796
+ return Number.isFinite(n) ? n : void 0;
9797
+ };
9575
9798
  var round23 = (n) => Math.round((Number(n) || 0) * 100) / 100;
9576
9799
  var emptyItem = () => ({ description: "", quantity: 1, unitPrice: 0 });
9577
9800
  function LinhaDoExtrato2({ line, fmt }) {
@@ -9596,6 +9819,35 @@ function LinhaDoExtrato2({ line, fmt }) {
9596
9819
  ] })
9597
9820
  ] });
9598
9821
  }
9822
+ function CandidatoBotao({ item, selected, onToggle, fmt, t: t2 }) {
9823
+ const { candidate: c, mode, openAmount: openAmount2 } = item;
9824
+ const parcela = c.movement.installmentNumber;
9825
+ return /* @__PURE__ */ jsxs(
9826
+ "button",
9827
+ {
9828
+ type: "button",
9829
+ onClick: onToggle,
9830
+ "aria-pressed": selected,
9831
+ className: `flex w-full items-center gap-3 rounded-md border p-2.5 text-left transition-colors ${selected ? "border-primary bg-primary/5 ring-2 ring-primary/20" : "hover:bg-muted/40"}`,
9832
+ children: [
9833
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
9834
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: c.invoice?.contactName || c.movement.notes || c.invoice?.fiscalNumber || t2("financial.reconciliation.match") }),
9835
+ /* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-[10px] text-muted-foreground", children: [
9836
+ /* @__PURE__ */ jsx("span", { children: shortDate2(c.movement.dueDate) }),
9837
+ c.invoice?.fiscalNumber && /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
9838
+ "#",
9839
+ c.invoice.fiscalNumber,
9840
+ parcela ? ` \xB7 ${parcela}` : ""
9841
+ ] }),
9842
+ c.score > 0 && /* @__PURE__ */ jsx("span", { className: "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) }) }),
9843
+ /* @__PURE__ */ jsx("span", { className: `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") })
9844
+ ] })
9845
+ ] }),
9846
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold", children: fmt(c.movement.amount) })
9847
+ ]
9848
+ }
9849
+ );
9850
+ }
9599
9851
  function EscolhaCard({ icon: Icon, title, hint, active, onClick }) {
9600
9852
  return /* @__PURE__ */ jsxs(
9601
9853
  "button",
@@ -9614,17 +9866,18 @@ function EscolhaCard({ icon: Icon, title, hint, active, onClick }) {
9614
9866
  }
9615
9867
  );
9616
9868
  }
9617
- function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
9869
+ function ReconcileFlow({ line, initialIntent, initialTarget, initialStep = 0, onClose, onDone }) {
9618
9870
  const t2 = useTranslation();
9619
9871
  const { currency } = useFinancialConfig();
9620
9872
  const provider = useFinancialProvider();
9621
9873
  const fmt = useCallback((n) => formatCurrency(n, currency), [currency]);
9622
- const [step, setStep] = useState(0);
9874
+ const [step, setStep] = useState(initialStep);
9623
9875
  const [intent, setIntent] = useState(initialIntent ?? null);
9624
9876
  const [saving, setSaving] = useState(false);
9625
9877
  const [candidates, setCandidates] = useState([]);
9626
9878
  const [candidatesLoading, setCandidatesLoading] = useState(false);
9627
- const [targetId, setTargetId] = useState(null);
9879
+ const [target, setTarget] = useState(initialTarget ?? null);
9880
+ const targetId = target?.candidate.movement.id ?? null;
9628
9881
  const [adjustments, setAdjustments] = useState([]);
9629
9882
  const [contactId, setContactId] = useState("");
9630
9883
  const [contactName, setContactName] = useState(line.counterpartyName ?? "");
@@ -9647,7 +9900,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
9647
9900
  void provider.getBankAccounts().then(setContas).catch(() => setContas([]));
9648
9901
  }, [provider, line.bankAccountId]);
9649
9902
  useEffect(() => {
9650
- if (intent !== "link" || !provider.suggestReconciliation) return;
9903
+ if (intent !== "link" || !provider.suggestReconciliation || provider.searchReconciliationTargets) return;
9651
9904
  let cancelled = false;
9652
9905
  setCandidatesLoading(true);
9653
9906
  provider.suggestReconciliation(line.id).then((raw) => {
@@ -9661,10 +9914,72 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
9661
9914
  cancelled = true;
9662
9915
  };
9663
9916
  }, [intent, provider, line.id]);
9664
- const target = useMemo(
9665
- () => candidates.find((c) => c.candidate.movement.id === targetId) ?? null,
9666
- [candidates, targetId]
9667
- );
9917
+ const podeBuscar = !!provider.searchReconciliationTargets;
9918
+ const filtrosIniciais = () => ({
9919
+ dueFrom: shiftDate(line.date, -LINK_DAYS),
9920
+ dueTo: shiftDate(line.date, LINK_DAYS),
9921
+ status: "all",
9922
+ texto: "",
9923
+ min: toAmountInput(line.amount * (1 - LINK_AMOUNT_TOLERANCE)),
9924
+ max: toAmountInput(line.amount * (1 + LINK_AMOUNT_TOLERANCE))
9925
+ });
9926
+ const [rascunho, setRascunho] = useState(filtrosIniciais);
9927
+ const [aplicado, setAplicado] = useState(filtrosIniciais);
9928
+ const mudarFiltro = (patch) => setRascunho((prev) => ({ ...prev, ...patch }));
9929
+ const filtrosPendentes = JSON.stringify(rascunho) !== JSON.stringify(aplicado);
9930
+ const [resultados, setResultados] = useState([]);
9931
+ const [paginaBusca, setPaginaBusca] = useState(1);
9932
+ const [temMais, setTemMais] = useState(false);
9933
+ const [buscando, setBuscando] = useState(false);
9934
+ const buscaSeq = useRef(0);
9935
+ const buscar = useCallback(async (pagina) => {
9936
+ if (!provider.searchReconciliationTargets) return;
9937
+ const seq = ++buscaSeq.current;
9938
+ setBuscando(true);
9939
+ try {
9940
+ const res = await provider.searchReconciliationTargets(line.id, {
9941
+ dueFrom: aplicado.dueFrom || void 0,
9942
+ dueTo: aplicado.dueTo || void 0,
9943
+ status: aplicado.status,
9944
+ search: aplicado.texto.trim() || void 0,
9945
+ amountMin: parseAmount(aplicado.min),
9946
+ amountMax: parseAmount(aplicado.max),
9947
+ page: pagina
9948
+ });
9949
+ if (seq !== buscaSeq.current) return;
9950
+ const novos = resolveCandidates(res.data);
9951
+ setResultados((prev) => {
9952
+ if (pagina === 1) return novos;
9953
+ const vistos = new Set(prev.map((r) => r.candidate.movement.id));
9954
+ return [...prev, ...novos.filter((r) => !vistos.has(r.candidate.movement.id))];
9955
+ });
9956
+ setPaginaBusca(pagina);
9957
+ setTemMais(res.hasMore);
9958
+ } catch (e) {
9959
+ if (seq !== buscaSeq.current) return;
9960
+ toast.error(e instanceof Error ? e.message : t2("common.error"));
9961
+ } finally {
9962
+ if (seq === buscaSeq.current) setBuscando(false);
9963
+ }
9964
+ }, [provider, line.id, aplicado, t2]);
9965
+ const ultimaBusca = useRef(null);
9966
+ const naSelecao = intent === "link" && step === 1;
9967
+ useEffect(() => {
9968
+ if (!naSelecao || !podeBuscar) return;
9969
+ const chave = JSON.stringify(aplicado);
9970
+ if (ultimaBusca.current === chave) return;
9971
+ ultimaBusca.current = chave;
9972
+ void buscar(1);
9973
+ }, [naSelecao, podeBuscar, aplicado, buscar]);
9974
+ const contasEncontradas = useMemo(() => {
9975
+ const lista = podeBuscar ? resultados : candidates;
9976
+ return initialTarget && !lista.some((c) => c.candidate.movement.id === initialTarget.candidate.movement.id) ? [initialTarget, ...lista] : lista;
9977
+ }, [podeBuscar, resultados, candidates, initialTarget]);
9978
+ const ampliarBusca = () => {
9979
+ const amplo = { ...rascunho, dueFrom: shiftDate(line.date, -WIDEN_DAYS), dueTo: shiftDate(line.date, WIDEN_DAYS), min: "", max: "" };
9980
+ setRascunho(amplo);
9981
+ setAplicado(amplo);
9982
+ };
9668
9983
  const contaDoExtrato = useMemo(
9669
9984
  () => contas.find((a) => a.id === line.bankAccountId) ?? null,
9670
9985
  [contas, line.bankAccountId]
@@ -9689,6 +10004,15 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
9689
10004
  const rows = q ? planoDoTipo.filter((a) => `${a.code ?? ""} ${a.name}`.toLocaleLowerCase("pt-BR").includes(q)) : planoDoTipo;
9690
10005
  return rows.slice(0, 50).map((a) => ({ id: a.id, label: accountLabel(a) }));
9691
10006
  }, [planoDoTipo]);
10007
+ const descricaoPadrao = (type) => type === "interest" ? t2("financial.reconciliation.adjustmentDefaultInterest") : type === "fine" ? t2("financial.reconciliation.adjustmentDefaultFine") : isOut ? t2("financial.reconciliation.adjustmentDefaultDiscountOut") : t2("financial.reconciliation.adjustmentDefaultDiscountIn");
10008
+ const buscaPlanoDoAjuste = useCallback((adj, query) => {
10009
+ const natureza = adjustmentDirection(line.direction, adj.type) === "debit" ? "expense" : "revenue";
10010
+ const folhas = postableAccounts(chart);
10011
+ const doTipo = folhas.filter((a) => a.accountType === natureza);
10012
+ const base = doTipo.length > 0 ? doTipo : folhas;
10013
+ const q = query.trim().toLocaleLowerCase("pt-BR");
10014
+ return (q ? base.filter((a) => `${a.code ?? ""} ${a.name}`.toLocaleLowerCase("pt-BR").includes(q)) : base).slice(0, 50).map((a) => ({ id: a.id, label: accountLabel(a) }));
10015
+ }, [chart, line.direction]);
9692
10016
  const valorAQuitar = intent === "link" && target ? target.openAmount : round23(line.amount);
9693
10017
  const quita = intent === "bill" || intent === "link" && target?.mode === "settle";
9694
10018
  const leitor = useMemo(() => {
@@ -9725,8 +10049,9 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
9725
10049
  const somaItens = sumItems(items.filter((i) => i.description.trim()));
9726
10050
  const erroItens = itemsMismatch(items, line.amount, fmt);
9727
10051
  const gap = target ? adjustmentGap(line.amount, target.openAmount) : 0;
9728
- const erroAjuste = intent === "link" && target?.mode === "settle" && Math.abs(gap) >= 0.01 ? adjustmentsMismatch(adjustments, gap, fmt) : null;
9729
- const bloqueioPasso1 = !intent ? t2("financial.reconciliation.pickIntent") : intent === "link" && !target ? t2("financial.reconciliation.pickCandidate") : null;
10052
+ const erroAjuste = intent === "link" && target?.mode === "settle" && Math.abs(gap) >= 0.01 ? adjustmentsMismatch(adjustments, gap, fmt) ?? (adjustments.some((a) => round23(a.amount) > 0 && !a.accountId) ? t2("financial.reconciliation.adjustmentNeedsAccount") : null) : null;
10053
+ const bloqueioPasso1 = !intent ? t2("financial.reconciliation.pickIntent") : null;
10054
+ const bloqueioSelecao = intent === "link" && !target ? t2("financial.reconciliation.pickCandidate") : null;
9730
10055
  const bloqueioPasso2 = intent === "transfer" ? counterAccountId ? null : t2("financial.reconciliation.pickAccount") : intent === "bill" ? description.trim() || items.some((i) => i.description.trim()) ? erroItens : t2("financial.reconciliation.needDescription") : erroAjuste;
9731
10056
  const bloqueioPasso3 = saving ? t2("common.saving") : null;
9732
10057
  async function confirmar() {
@@ -9853,37 +10178,148 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
9853
10178
  onClick: () => setIntent("transfer")
9854
10179
  }
9855
10180
  )
10181
+ ] })
10182
+ ] });
10183
+ const statusDaConta = (item) => item.mode === "link_only" ? t2("financial.reconciliation.searchStatusPaid") : item.candidate.movement.status === "overdue" ? t2("financial.reconciliation.statusOverdue") : t2("financial.reconciliation.searchStatusOpen");
10184
+ const valorAConciliar = (item) => item.mode === "link_only" ? item.candidate.movement.amount : item.openAmount;
10185
+ const parcelaDe = (item) => {
10186
+ const n = item.candidate.movement.installmentNumber ?? 1;
10187
+ return item.candidate.installmentCount ? `${n}/${item.candidate.installmentCount}` : String(n);
10188
+ };
10189
+ const filtrar = () => {
10190
+ if (filtrosPendentes) setAplicado(rascunho);
10191
+ else void buscar(1);
10192
+ };
10193
+ const passoSelecionar = /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-3", children: [
10194
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
10195
+ t2("financial.reconciliation.selectBillFor", {
10196
+ date: shortDate2(line.date),
10197
+ description: line.description || line.externalId
10198
+ }),
10199
+ " ",
10200
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: fmt(line.amount) })
9856
10201
  ] }),
9857
- intent === "link" && /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-card border p-2.5", children: [
9858
- /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: t2("financial.reconciliation.candidates") }),
9859
- candidatesLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
9860
- /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full rounded-md" }),
9861
- /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full rounded-md" })
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 }) => {
9863
- const escolhido = targetId === c.movement.id;
9864
- return /* @__PURE__ */ jsxs(
9865
- "button",
10202
+ podeBuscar && /* @__PURE__ */ jsxs(
10203
+ FilterPanel,
10204
+ {
10205
+ title: t2("financial.reconciliation.filtersTitle"),
10206
+ dirty: filtrosPendentes,
10207
+ onApply: filtrar,
10208
+ applyLabel: t2("financial.reconciliation.filter"),
10209
+ pendingTitle: t2("financial.reconciliation.filterPending"),
10210
+ pendingHint: t2("financial.reconciliation.searchPendingHint"),
10211
+ busy: buscando,
10212
+ moreLabel: t2("financial.reconciliation.filterMore"),
10213
+ lessLabel: t2("financial.reconciliation.filterLess"),
10214
+ advancedCount: rascunho.status !== "all" ? 1 : 0,
10215
+ advanced: /* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchStatus"), children: /* @__PURE__ */ jsx(
10216
+ SegmentedControl,
9866
10217
  {
9867
- type: "button",
9868
- onClick: () => setTargetId(escolhido ? null : c.movement.id),
9869
- "aria-pressed": escolhido,
9870
- className: `flex w-full items-center gap-3 rounded-md border p-2.5 text-left transition-colors ${escolhido ? "border-primary bg-primary/5 ring-2 ring-primary/20" : "hover:bg-muted/40"}`,
9871
- children: [
9872
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
9873
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: c.invoice?.contactName || c.movement.notes || c.invoice?.fiscalNumber || t2("financial.reconciliation.match") }),
9874
- /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
9875
- shortDate2(c.movement.dueDate),
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) }) }),
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") })
9878
- ] })
9879
- ] }),
9880
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold", children: fmt(c.movement.amount) })
10218
+ "aria-label": t2("financial.reconciliation.searchStatus"),
10219
+ value: rascunho.status,
10220
+ onChange: (v) => mudarFiltro({ status: v }),
10221
+ options: [
10222
+ { value: "all", label: t2("financial.reconciliation.searchStatusAllShort") },
10223
+ { value: "open", label: t2("financial.reconciliation.searchStatusOpen") },
10224
+ { value: "paid", label: t2("financial.reconciliation.searchStatusPaid") }
9881
10225
  ]
9882
- },
9883
- c.movement.id
9884
- );
9885
- })
9886
- ] })
10226
+ }
10227
+ ) }),
10228
+ footer: /* @__PURE__ */ jsx(
10229
+ Input,
10230
+ {
10231
+ value: rascunho.texto,
10232
+ placeholder: t2("financial.reconciliation.searchText"),
10233
+ "aria-label": t2("financial.reconciliation.searchText"),
10234
+ onChange: (e) => mudarFiltro({ texto: e.target.value }),
10235
+ onKeyDown: (e) => {
10236
+ if (e.key !== "Enter") return;
10237
+ e.preventDefault();
10238
+ filtrar();
10239
+ }
10240
+ }
10241
+ ),
10242
+ children: [
10243
+ /* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchDueFrom"), children: /* @__PURE__ */ jsx("input", { type: "date", value: rascunho.dueFrom, onChange: (e) => mudarFiltro({ dueFrom: e.target.value }), className: CAMPO_FILTRO }) }),
10244
+ /* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchDueTo"), children: /* @__PURE__ */ jsx("input", { type: "date", value: rascunho.dueTo, onChange: (e) => mudarFiltro({ dueTo: e.target.value }), className: CAMPO_FILTRO }) }),
10245
+ /* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchAmountMin"), children: /* @__PURE__ */ jsx("input", { inputMode: "decimal", placeholder: "0,00", value: rascunho.min, onChange: (e) => mudarFiltro({ min: e.target.value }), className: CAMPO_FILTRO }) }),
10246
+ /* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchAmountMax"), children: /* @__PURE__ */ jsx("input", { inputMode: "decimal", placeholder: "0,00", value: rascunho.max, onChange: (e) => mudarFiltro({ max: e.target.value }), className: CAMPO_FILTRO }) })
10247
+ ]
10248
+ }
10249
+ ),
10250
+ (buscando || candidatesLoading) && contasEncontradas.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
10251
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full rounded-md" }),
10252
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full rounded-md" })
10253
+ ] }) : contasEncontradas.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-card border py-6 text-center", children: [
10254
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.reconciliation.searchEmpty") }),
10255
+ podeBuscar && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: ampliarBusca, children: t2("financial.reconciliation.searchWiden") })
10256
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
10257
+ /* @__PURE__ */ jsx("div", { className: "space-y-2 sm:hidden", children: contasEncontradas.map((item) => /* @__PURE__ */ jsx(
10258
+ CandidatoBotao,
10259
+ {
10260
+ item,
10261
+ selected: targetId === item.candidate.movement.id,
10262
+ onToggle: () => setTarget(targetId === item.candidate.movement.id ? null : item),
10263
+ fmt,
10264
+ t: t2
10265
+ },
10266
+ item.candidate.movement.id
10267
+ )) }),
10268
+ /* @__PURE__ */ jsx("div", { className: "hidden overflow-hidden rounded-card border sm:block", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
10269
+ /* @__PURE__ */ jsx("thead", { className: "bg-muted/50 text-left text-[11px] text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
10270
+ /* @__PURE__ */ jsx("th", { className: "w-8 p-2" }),
10271
+ /* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.colDue") }),
10272
+ /* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.colCounterparty") }),
10273
+ /* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.colDescription") }),
10274
+ /* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.colInstallment") }),
10275
+ /* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.searchStatus") }),
10276
+ /* @__PURE__ */ jsx("th", { className: "p-2 text-right font-medium", children: t2("financial.reconciliation.colToReconcile") }),
10277
+ /* @__PURE__ */ jsx("th", { className: "p-2 text-right font-medium", children: t2("financial.reconciliation.colDelta") })
10278
+ ] }) }),
10279
+ /* @__PURE__ */ jsx("tbody", { children: contasEncontradas.map((item) => {
10280
+ const c = item.candidate;
10281
+ const escolhido = targetId === c.movement.id;
10282
+ const valor = valorAConciliar(item);
10283
+ const delta = round23(line.amount - valor);
10284
+ return /* @__PURE__ */ jsxs(
10285
+ "tr",
10286
+ {
10287
+ onClick: () => setTarget(escolhido ? null : item),
10288
+ className: `cursor-pointer border-t transition-colors ${escolhido ? "bg-primary/10" : "hover:bg-muted/40"}`,
10289
+ children: [
10290
+ /* @__PURE__ */ jsx("td", { className: "p-2", children: /* @__PURE__ */ jsx(
10291
+ "input",
10292
+ {
10293
+ type: "radio",
10294
+ readOnly: true,
10295
+ checked: escolhido,
10296
+ "aria-label": c.invoice?.contactName ?? c.movement.notes ?? c.movement.id
10297
+ }
10298
+ ) }),
10299
+ /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap p-2", children: shortDate2(c.movement.dueDate) }),
10300
+ /* @__PURE__ */ jsx("td", { className: "max-w-[12rem] truncate p-2 font-medium", title: c.invoice?.contactName, children: c.invoice?.contactName || "\u2014" }),
10301
+ /* @__PURE__ */ jsxs("td", { className: "max-w-[14rem] truncate p-2", title: c.movement.notes, children: [
10302
+ c.invoice?.fiscalNumber && /* @__PURE__ */ jsxs("span", { className: "mr-1 text-muted-foreground", children: [
10303
+ "#",
10304
+ c.invoice.fiscalNumber
10305
+ ] }),
10306
+ c.movement.notes
10307
+ ] }),
10308
+ /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap p-2", children: parcelaDe(item) }),
10309
+ /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap p-2", children: /* @__PURE__ */ jsx("span", { className: `rounded px-1.5 py-0.5 text-[10px] font-medium ${item.mode === "link_only" ? "bg-success/10 text-success" : c.movement.status === "overdue" ? "bg-destructive/10 text-destructive" : "bg-muted text-foreground"}`, children: statusDaConta(item) }) }),
10310
+ /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap p-2 text-right font-semibold", children: fmt(valor) }),
10311
+ /* @__PURE__ */ jsx("td", { className: `whitespace-nowrap p-2 text-right text-[11px] ${Math.abs(delta) < 0.01 ? "text-success" : "text-destructive"}`, children: Math.abs(delta) < 0.01 ? "OK" : `${delta > 0 ? "+" : "\u2212"}${fmt(Math.abs(delta))}` })
10312
+ ]
10313
+ },
10314
+ c.movement.id
10315
+ );
10316
+ }) })
10317
+ ] }) })
10318
+ ] }),
10319
+ temMais && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "ghost", disabled: buscando, onClick: () => void buscar(paginaBusca + 1), children: [
10320
+ buscando && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin", "aria-hidden": true }),
10321
+ t2("financial.reconciliation.searchMore")
10322
+ ] }) })
9887
10323
  ] });
9888
10324
  const passoDetalhes = /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-3", children: [
9889
10325
  intent === "bill" && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -9935,30 +10371,28 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
9935
10371
  onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, description: e.target.value } : x))
9936
10372
  }
9937
10373
  ) }),
9938
- /* @__PURE__ */ jsx(
10374
+ /* @__PURE__ */ jsx("div", { className: "w-16 shrink-0", children: /* @__PURE__ */ jsx(
9939
10375
  Input,
9940
10376
  {
9941
10377
  "aria-label": t2("financial.reconciliation.itemQuantity"),
9942
- className: "w-16 shrink-0",
9943
10378
  type: "number",
9944
10379
  min: "0",
9945
10380
  step: "0.001",
9946
10381
  value: item.quantity,
9947
10382
  onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, quantity: Number(e.target.value) } : x))
9948
10383
  }
9949
- ),
9950
- /* @__PURE__ */ jsx(
10384
+ ) }),
10385
+ /* @__PURE__ */ jsx("div", { className: "w-24 shrink-0", children: /* @__PURE__ */ jsx(
9951
10386
  Input,
9952
10387
  {
9953
10388
  "aria-label": t2("financial.reconciliation.itemUnitPrice"),
9954
- className: "w-24 shrink-0",
9955
10389
  type: "number",
9956
10390
  min: "0",
9957
10391
  step: "0.01",
9958
10392
  value: item.unitPrice,
9959
10393
  onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, unitPrice: Number(e.target.value) } : x))
9960
10394
  }
9961
- ),
10395
+ ) }),
9962
10396
  /* @__PURE__ */ jsx(Hint, { label: t2("common.delete"), children: /* @__PURE__ */ jsx(
9963
10397
  Button,
9964
10398
  {
@@ -10059,58 +10493,86 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
10059
10493
  ] })
10060
10494
  ] }),
10061
10495
  target.mode === "link_only" ? /* @__PURE__ */ jsx("p", { className: "rounded-card border border-success/40 bg-success/5 px-3 py-2.5 text-[11px] leading-snug", children: t2("financial.reconciliation.linkOnlyExplain") }) : Math.abs(gap) < 0.01 ? /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.reconciliation.noAdjustmentNeeded") }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
10062
- /* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning/5 px-3 py-2 text-[11px] leading-snug", children: t2("financial.reconciliation.adjustmentNeeded", {
10496
+ /* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning/5 px-3 py-2 text-[11px] leading-snug", children: t2(isOut ? "financial.reconciliation.adjustmentNeeded" : "financial.reconciliation.adjustmentNeededIn", {
10063
10497
  gap: fmt(Math.abs(gap)),
10064
10498
  side: gap > 0 ? t2("financial.reconciliation.adjustmentMore") : t2("financial.reconciliation.adjustmentLess")
10065
10499
  }) }),
10066
- adjustments.map((adj, idx) => /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1.5", children: [
10067
- /* @__PURE__ */ jsx("div", { className: "w-28 shrink-0", children: /* @__PURE__ */ jsxs(
10068
- Select,
10069
- {
10070
- value: adj.type,
10071
- onValueChange: (v) => setAdjustments((prev) => prev.map((x, i) => i === idx ? { ...x, type: v } : x)),
10072
- children: [
10073
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
10074
- /* @__PURE__ */ jsxs(SelectContent, { children: [
10075
- /* @__PURE__ */ jsx(SelectItem, { value: "interest", children: t2("financial.reconciliation.interest") }),
10076
- /* @__PURE__ */ jsx(SelectItem, { value: "fine", children: t2("financial.reconciliation.fine") }),
10077
- /* @__PURE__ */ jsx(SelectItem, { value: "discount", children: t2("financial.reconciliation.discount") })
10078
- ] })
10079
- ]
10080
- }
10081
- ) }),
10082
- /* @__PURE__ */ jsx(
10083
- Input,
10084
- {
10085
- "aria-label": t2("financial.reconciliation.adjustmentAmount"),
10086
- className: "w-24 shrink-0",
10087
- type: "number",
10088
- min: "0",
10089
- step: "0.01",
10090
- value: adj.amount,
10091
- onChange: (e) => setAdjustments((prev) => prev.map((x, i) => i === idx ? { ...x, amount: Number(e.target.value) } : x))
10092
- }
10093
- ),
10094
- /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
10095
- Input,
10096
- {
10097
- "aria-label": t2("financial.reconciliation.adjustmentDescription"),
10098
- placeholder: t2("financial.reconciliation.adjustmentDescription"),
10099
- value: adj.description ?? "",
10100
- onChange: (e) => setAdjustments((prev) => prev.map((x, i) => i === idx ? { ...x, description: e.target.value } : x))
10101
- }
10102
- ) }),
10103
- /* @__PURE__ */ jsx(Hint, { label: t2("common.delete"), children: /* @__PURE__ */ jsx(
10104
- Button,
10105
- {
10106
- size: "sm",
10107
- variant: "ghost",
10108
- "aria-label": t2("common.delete"),
10109
- onClick: () => setAdjustments((prev) => prev.filter((_, i) => i !== idx)),
10110
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": true })
10111
- }
10112
- ) })
10113
- ] }, idx)),
10500
+ adjustments.map((adj, idx) => {
10501
+ const mudar = (patch) => setAdjustments((prev) => prev.map((x, i) => i === idx ? { ...x, ...patch } : x));
10502
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-card border p-2.5", children: [
10503
+ /* @__PURE__ */ jsxs("div", { className: "grid min-w-0 flex-1 items-end gap-2 sm:grid-cols-2", children: [
10504
+ /* @__PURE__ */ jsxs("div", { children: [
10505
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: t2("financial.reconciliation.adjustmentType") }),
10506
+ /* @__PURE__ */ jsxs(
10507
+ Select,
10508
+ {
10509
+ value: adj.type,
10510
+ onValueChange: (v) => {
10511
+ const type = v;
10512
+ const escrita = adj.description?.trim() && adj.description !== descricaoPadrao(adj.type);
10513
+ mudar(escrita ? { type } : { type, description: descricaoPadrao(type) });
10514
+ },
10515
+ children: [
10516
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
10517
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
10518
+ /* @__PURE__ */ jsx(SelectItem, { value: "interest", children: t2("financial.reconciliation.interest") }),
10519
+ /* @__PURE__ */ jsx(SelectItem, { value: "fine", children: t2("financial.reconciliation.fine") }),
10520
+ /* @__PURE__ */ jsx(SelectItem, { value: "discount", children: t2("financial.reconciliation.discount") })
10521
+ ] })
10522
+ ]
10523
+ }
10524
+ )
10525
+ ] }),
10526
+ /* @__PURE__ */ jsx(
10527
+ CurrencyInput,
10528
+ {
10529
+ label: t2("financial.reconciliation.adjustmentAmount"),
10530
+ value: adj.amount,
10531
+ onChange: (v) => mudar({ amount: v }),
10532
+ symbol: currency.symbol,
10533
+ locale: currency.locale,
10534
+ currencyCode: currency.code
10535
+ }
10536
+ ),
10537
+ /* @__PURE__ */ jsx(
10538
+ SearchSelect,
10539
+ {
10540
+ label: `${t2("financial.reconciliation.chartAccount")} *`,
10541
+ value: adj.accountId ?? "",
10542
+ displayValue: chart.find((a) => a.id === adj.accountId)?.name,
10543
+ placeholder: t2("financial.reconciliation.chartAccountPlaceholder"),
10544
+ minChars: 0,
10545
+ loadOnOpen: true,
10546
+ noResultsLabel: t2("financial.reconciliation.chartAccountNone"),
10547
+ clearLabel: "Limpar",
10548
+ onSearch: (q) => buscaPlanoDoAjuste(adj, q),
10549
+ onChange: (id) => mudar({ accountId: id || void 0 })
10550
+ }
10551
+ ),
10552
+ /* @__PURE__ */ jsxs("div", { children: [
10553
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: t2("financial.reconciliation.adjustmentDescription") }),
10554
+ /* @__PURE__ */ jsx(
10555
+ Input,
10556
+ {
10557
+ placeholder: t2("financial.reconciliation.adjustmentDescriptionPlaceholder"),
10558
+ value: adj.description ?? "",
10559
+ onChange: (e) => mudar({ description: e.target.value })
10560
+ }
10561
+ )
10562
+ ] })
10563
+ ] }),
10564
+ /* @__PURE__ */ jsx("div", { className: "pt-6", children: /* @__PURE__ */ jsx(Hint, { label: t2("common.delete"), children: /* @__PURE__ */ jsx(
10565
+ Button,
10566
+ {
10567
+ size: "sm",
10568
+ variant: "ghost",
10569
+ "aria-label": t2("common.delete"),
10570
+ onClick: () => setAdjustments((prev) => prev.filter((_, i) => i !== idx)),
10571
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": true })
10572
+ }
10573
+ ) }) })
10574
+ ] }, idx);
10575
+ }),
10114
10576
  /* @__PURE__ */ jsxs(
10115
10577
  Button,
10116
10578
  {
@@ -10118,6 +10580,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
10118
10580
  variant: "outline",
10119
10581
  onClick: () => setAdjustments((prev) => [...prev, {
10120
10582
  type: gap > 0 ? "interest" : "discount",
10583
+ description: descricaoPadrao(gap > 0 ? "interest" : "discount"),
10121
10584
  // A diferença que ainda não foi explicada já vem no campo:
10122
10585
  // digitar de novo um número que a tela acabou de calcular é
10123
10586
  // trabalho que o produto podia poupar.
@@ -10216,6 +10679,13 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
10216
10679
  ) : /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: t2("financial.reconciliation.noPaymentNeeded") })
10217
10680
  ] });
10218
10681
  const motivo = (texto) => texto ? /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: texto }) : void 0;
10682
+ const paginaDetalhes = {
10683
+ id: "details",
10684
+ title: intent === "bill" ? t2("financial.reconciliation.stepItems") : intent === "link" ? t2("financial.reconciliation.stepAdjust") : t2("financial.reconciliation.stepDetails"),
10685
+ render: () => passoDetalhes,
10686
+ footer: motivo(bloqueioPasso2),
10687
+ blockedReason: bloqueioPasso2 ?? void 0
10688
+ };
10219
10689
  const pages = [
10220
10690
  {
10221
10691
  id: "classify",
@@ -10224,13 +10694,15 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
10224
10694
  footer: motivo(bloqueioPasso1),
10225
10695
  blockedReason: bloqueioPasso1 ?? void 0
10226
10696
  },
10227
- {
10228
- id: "details",
10229
- title: intent === "bill" ? t2("financial.reconciliation.stepItems") : t2("financial.reconciliation.stepDetails"),
10230
- render: () => passoDetalhes,
10231
- footer: motivo(bloqueioPasso2),
10232
- blockedReason: bloqueioPasso2 ?? void 0
10233
- },
10697
+ ...intent === "link" ? [{
10698
+ id: "select",
10699
+ title: t2("financial.reconciliation.stepSelectBill"),
10700
+ render: () => passoSelecionar,
10701
+ footer: motivo(bloqueioSelecao),
10702
+ blockedReason: bloqueioSelecao ?? void 0
10703
+ }] : [],
10704
+ // Like V1: linking asks for an adjustment only when there is a gap to explain.
10705
+ ...intent !== "link" || !target || target.mode === "settle" && Math.abs(gap) >= 0.01 ? [paginaDetalhes] : [],
10234
10706
  {
10235
10707
  id: "confirm",
10236
10708
  title: t2("financial.reconciliation.stepConfirm"),
@@ -10247,7 +10719,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
10247
10719
  if (!o && !saving) onClose();
10248
10720
  },
10249
10721
  mode: "steps",
10250
- size: "lg",
10722
+ size: intent === "link" && pages[step]?.id === "select" ? "4xl" : "lg",
10251
10723
  title: t2("financial.reconciliation.flowTitle"),
10252
10724
  pages,
10253
10725
  current: step,
@@ -10257,8 +10729,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
10257
10729
  setStep(Math.max(0, alvo));
10258
10730
  return;
10259
10731
  }
10260
- if (step === 0 && bloqueioPasso1) return;
10261
- if (step === 1 && bloqueioPasso2) return;
10732
+ if (pages.slice(step, alvo).some((p) => p.blockedReason)) return;
10262
10733
  setStep(Math.min(pages.length - 1, alvo));
10263
10734
  },
10264
10735
  doneLabel: saving ? t2("common.saving") : t2("financial.reconciliation.confirmAction"),
@@ -10438,11 +10909,11 @@ function ReconciliationView() {
10438
10909
  setBusy(false);
10439
10910
  }
10440
10911
  }
10441
- const RefreshIcon = React23__default.useMemo(
10912
+ const RefreshIcon = React25__default.useMemo(
10442
10913
  () => ({ className }) => /* @__PURE__ */ jsx(RefreshCw, { className: `${className ?? ""} ${loading ? "animate-spin" : ""}` }),
10443
10914
  [loading]
10444
10915
  );
10445
- const headerActions = React23__default.useMemo(() => [{
10916
+ const headerActions = React25__default.useMemo(() => [{
10446
10917
  id: "refresh",
10447
10918
  label: t2("financial.reconciliation.refresh"),
10448
10919
  icon: RefreshIcon,
@@ -10593,9 +11064,16 @@ function ReconciliationView() {
10593
11064
  t: t2,
10594
11065
  onSugerir: () => void sugerir(line),
10595
11066
  onTransferir: () => setFlow({ line, intent: "transfer" }),
10596
- onVincular: () => setFlow({ line, intent: "link" }),
11067
+ onVincular: () => setFlow({ line, intent: "link", step: 1 }),
10597
11068
  onLancarConta: () => setFlow({ line, intent: "bill" }),
10598
- onAceitarCandidato: (movId) => void vincular(line.id, movId),
11069
+ onAceitarCandidato: (cand) => {
11070
+ const [alvo] = resolveCandidates([cand]);
11071
+ if (alvo.mode === "link_only" && Math.abs(cand.movement.amount - line.amount) < 0.01) {
11072
+ void vincular(line.id, cand.movement.id);
11073
+ } else {
11074
+ setFlow({ line, intent: "link", target: alvo, step: 1 });
11075
+ }
11076
+ },
10599
11077
  onDesfazer: () => void desfazer(line.id),
10600
11078
  onVerConta: () => {
10601
11079
  const modulo = line.direction === "debit" ? "payables" : "receivables";
@@ -10625,6 +11103,8 @@ function ReconciliationView() {
10625
11103
  {
10626
11104
  line: flow.line,
10627
11105
  initialIntent: flow.intent,
11106
+ initialTarget: flow.target,
11107
+ initialStep: flow.step,
10628
11108
  onClose: () => setFlow(null),
10629
11109
  onDone: () => {
10630
11110
  setFlow(null);
@@ -10754,7 +11234,7 @@ function StatementCard({
10754
11234
  }
10755
11235
  )
10756
11236
  ] }),
10757
- /* @__PURE__ */ jsxs(Button, { size: "sm", className: "shrink-0", disabled: busy, onClick: () => onAceitarCandidato(sugestao.movement.id), children: [
11237
+ /* @__PURE__ */ jsxs(Button, { size: "sm", className: "shrink-0", disabled: busy, onClick: () => onAceitarCandidato(sugestao), children: [
10758
11238
  /* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" }),
10759
11239
  t2("financial.reconciliation.match")
10760
11240
  ] })
@@ -10867,13 +11347,13 @@ function RecurringBillsList({ direction, onNavigate }) {
10867
11347
  const provider = useFinancialProvider();
10868
11348
  const { currency } = useFinancialConfig();
10869
11349
  const can = usePermissionOptional();
10870
- const [rows, setRows] = React23__default.useState(null);
10871
- const [search, setSearch] = React23__default.useState("");
10872
- const [status, setStatus] = React23__default.useState("all");
10873
- const [changing, setChanging] = React23__default.useState(null);
11350
+ const [rows, setRows] = React25__default.useState(null);
11351
+ const [search, setSearch] = React25__default.useState("");
11352
+ const [status, setStatus] = React25__default.useState("all");
11353
+ const [changing, setChanging] = React25__default.useState(null);
10874
11354
  const isPayable = direction === "payable";
10875
11355
  const prefix = isPayable ? "payables" : "receivables";
10876
- const load = React23__default.useCallback(async () => {
11356
+ const load = React25__default.useCallback(async () => {
10877
11357
  setRows(null);
10878
11358
  try {
10879
11359
  setRows(await provider.getRecurringBills?.(direction) ?? []);
@@ -10882,10 +11362,10 @@ function RecurringBillsList({ direction, onNavigate }) {
10882
11362
  toast.error(error?.message ?? "N\xE3o foi poss\xEDvel carregar as recorr\xEAncias.");
10883
11363
  }
10884
11364
  }, [provider, direction]);
10885
- React23__default.useEffect(() => {
11365
+ React25__default.useEffect(() => {
10886
11366
  void load();
10887
11367
  }, [load]);
10888
- const filtered = React23__default.useMemo(() => {
11368
+ const filtered = React25__default.useMemo(() => {
10889
11369
  const term = search.trim().toLowerCase();
10890
11370
  const visiveis = (rows ?? []).filter((row) => {
10891
11371
  if (status === "active" && !row.isActive) return false;
@@ -10895,7 +11375,7 @@ function RecurringBillsList({ direction, onNavigate }) {
10895
11375
  });
10896
11376
  return [...visiveis].sort((a, b) => Number(b.isActive) - Number(a.isActive) || (a.dueDay ?? 0) - (b.dueDay ?? 0) || a.description.localeCompare(b.description));
10897
11377
  }, [rows, search, status]);
10898
- const porMes = React23__default.useMemo(() => {
11378
+ const porMes = React25__default.useMemo(() => {
10899
11379
  const fator = { weekly: 52 / 12, monthly: 1, quarterly: 1 / 3, yearly: 1 / 12 };
10900
11380
  const ativas = filtered.filter((row) => row.isActive);
10901
11381
  return {
@@ -10916,7 +11396,7 @@ function RecurringBillsList({ direction, onNavigate }) {
10916
11396
  setChanging(null);
10917
11397
  }
10918
11398
  }
10919
- const columns = React23__default.useMemo(() => [
11399
+ const columns = React25__default.useMemo(() => [
10920
11400
  {
10921
11401
  accessorKey: "description",
10922
11402
  header: "Descri\xE7\xE3o",
@@ -11012,41 +11492,43 @@ function RecurringBillsList({ direction, onNavigate }) {
11012
11492
  function RecurringBillFormView({ direction, valueId, onBack }) {
11013
11493
  const provider = useFinancialProvider();
11014
11494
  const { locations, currency, itemTypes: configuredItemTypes, entityLookups } = useFinancialConfig();
11495
+ const t2 = useTranslation();
11015
11496
  const isPayable = direction === "payable";
11016
- const [value, setValue] = React23__default.useState(null);
11017
- const [loaded, setLoaded] = React23__default.useState(!valueId);
11018
- const [contact, setContact] = React23__default.useState(null);
11019
- const [counterpartyAccounting, setCounterpartyAccounting] = React23__default.useState({
11497
+ const [value, setValue] = React25__default.useState(null);
11498
+ const [loaded, setLoaded] = React25__default.useState(!valueId);
11499
+ const [contact, setContact] = React25__default.useState(null);
11500
+ const [counterpartyAccounting, setCounterpartyAccounting] = React25__default.useState({
11020
11501
  allowedAccountIds: [],
11021
11502
  limitAccounts: false,
11022
11503
  addItemsAutomatically: false
11023
11504
  });
11024
- const accountingPolicyRequest = React23__default.useRef(0);
11025
- const counterpartyLookup = React23__default.useMemo(
11505
+ const accountingPolicyRequest = React25__default.useRef(0);
11506
+ const counterpartyLookup = React25__default.useMemo(
11026
11507
  () => createFinancialCounterpartyLookup(isPayable ? "debit" : "credit"),
11027
11508
  [isPayable]
11028
11509
  );
11029
11510
  const contactCreate = useLookupCreate(counterpartyLookup, (created) => {
11030
11511
  setContact({ id: created.id, name: created.label });
11031
11512
  });
11032
- const initialItem = React23__default.useRef(newRecurringItem());
11033
- const [items, setItems] = React23__default.useState([initialItem.current]);
11034
- const [expandedItemId, setExpandedItemId] = React23__default.useState(initialItem.current._id);
11035
- const [addingItem, setAddingItem] = React23__default.useState(false);
11036
- const [frequency, setFrequency] = React23__default.useState("monthly");
11037
- const [dueDay, setDueDay] = React23__default.useState("1");
11038
- const [startsOn, setStartsOn] = React23__default.useState(today3());
11039
- const [endsOn, setEndsOn] = React23__default.useState("");
11040
- const [unitId, setUnitId] = React23__default.useState(locations[0]?.id ?? "");
11041
- const [availableLocations, setAvailableLocations] = React23__default.useState(locations);
11042
- const [notes, setNotes] = React23__default.useState("");
11043
- const [isActive, setIsActive] = React23__default.useState(true);
11044
- const [accounts, setAccounts] = React23__default.useState([]);
11045
- const [costCenters, setCostCenters] = React23__default.useState([]);
11046
- const [saving, setSaving] = React23__default.useState(false);
11047
- const [ending, setEnding] = React23__default.useState(false);
11048
- const [error, setError] = React23__default.useState(null);
11049
- React23__default.useEffect(() => {
11513
+ const initialItem = React25__default.useRef(newRecurringItem());
11514
+ const [items, setItems] = React25__default.useState([initialItem.current]);
11515
+ const [expandedItemId, setExpandedItemId] = React25__default.useState(initialItem.current._id);
11516
+ const [addingItem, setAddingItem] = React25__default.useState(false);
11517
+ const [frequency, setFrequency] = React25__default.useState("monthly");
11518
+ const [dueDay, setDueDay] = React25__default.useState("1");
11519
+ const [startsOn, setStartsOn] = React25__default.useState(today3());
11520
+ const [endsOn, setEndsOn] = React25__default.useState("");
11521
+ const [unitId, setUnitId] = React25__default.useState(locations[0]?.id ?? "");
11522
+ const [availableLocations, setAvailableLocations] = React25__default.useState(locations);
11523
+ const [notes, setNotes] = React25__default.useState("");
11524
+ const [isActive, setIsActive] = React25__default.useState(true);
11525
+ const [accounts, setAccounts] = React25__default.useState([]);
11526
+ const [costCenters, setCostCenters] = React25__default.useState([]);
11527
+ const { choices: unitChoices } = useWritableUnits(availableLocations);
11528
+ const [saving, setSaving] = React25__default.useState(false);
11529
+ const [ending, setEnding] = React25__default.useState(false);
11530
+ const [error, setError] = React25__default.useState(null);
11531
+ React25__default.useEffect(() => {
11050
11532
  if (locations.length > 0) {
11051
11533
  setAvailableLocations(locations);
11052
11534
  return;
@@ -11061,7 +11543,7 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
11061
11543
  cancelled = true;
11062
11544
  };
11063
11545
  }, [locations]);
11064
- React23__default.useEffect(() => {
11546
+ React25__default.useEffect(() => {
11065
11547
  let cancelled = false;
11066
11548
  void Promise.all([
11067
11549
  provider.getChartOfAccounts(),
@@ -11108,21 +11590,25 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
11108
11590
  cancelled = true;
11109
11591
  };
11110
11592
  }, [provider, direction, valueId]);
11111
- const offeredItemTypes = React23__default.useMemo(() => availableInvoiceItemTypes(
11112
- configuredItemTypes,
11593
+ const translatedItemTypes = React25__default.useMemo(() => configuredItemTypes.map((tp) => {
11594
+ const label = t2(`financial.itemType.${tp.value}`);
11595
+ return label === `financial.itemType.${tp.value}` ? tp : { ...tp, label };
11596
+ }), [configuredItemTypes, t2]);
11597
+ const offeredItemTypes = React25__default.useMemo(() => availableInvoiceItemTypes(
11598
+ translatedItemTypes,
11113
11599
  Object.keys(entityLookups),
11114
- configuredItemTypes.find((item) => item.value === "other")?.label ?? "Outro",
11600
+ t2("financial.itemType.other"),
11115
11601
  direction
11116
- ), [configuredItemTypes, entityLookups, direction]);
11602
+ ), [translatedItemTypes, entityLookups, direction, t2]);
11117
11603
  const itemTypes = useStockedItemTypes(offeredItemTypes, entityLookups);
11118
- const selectableAccounts = React23__default.useMemo(() => {
11604
+ const selectableAccounts = React25__default.useMemo(() => {
11119
11605
  const postable = postableAccounts(accounts);
11120
11606
  if (!counterpartyAccounting.limitAccounts) return postable;
11121
11607
  const allowed = new Set(counterpartyAccounting.allowedAccountIds);
11122
11608
  return postable.filter((account) => allowed.has(account.id));
11123
11609
  }, [accounts, counterpartyAccounting]);
11124
11610
  const hasItemLookups = itemTypes.some((item) => entityLookups[item.value]);
11125
- const amount = React23__default.useMemo(() => items.reduce(
11611
+ const amount = React25__default.useMemo(() => items.reduce(
11126
11612
  (sum, item) => sum + item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
11127
11613
  0
11128
11614
  ), [items]);
@@ -11130,8 +11616,8 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
11130
11616
  const numericDueDay = Math.trunc(Number(dueDay));
11131
11617
  const invalid = items.length === 0 ? "Adicione pelo menos um item." : items.some((item) => !item.description.trim()) ? "Informe a descri\xE7\xE3o de todos os itens." : !contact?.id ? `Selecione ${isPayable ? "quem recebe" : "quem paga"}.` : !(amount > 0) ? "Informe um valor maior que zero." : numericDueDay < 1 || numericDueDay > 31 ? "O dia do vencimento vai de 1 a 31." : !startsOn ? "Informe o in\xEDcio da recorr\xEAncia." : endsOn && endsOn < startsOn ? "O t\xE9rmino n\xE3o pode vir antes do in\xEDcio." : null;
11132
11618
  const fields = { items, contactId: contact?.id ?? "", frequency, dueDay, startsOn, endsOn, unitId, notes, isActive };
11133
- const snapshot = React23__default.useRef(null);
11134
- React23__default.useEffect(() => {
11619
+ const snapshot = React25__default.useRef(null);
11620
+ React25__default.useEffect(() => {
11135
11621
  if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
11136
11622
  }, [loaded]);
11137
11623
  const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
@@ -11239,13 +11725,10 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
11239
11725
  ] }) : void 0,
11240
11726
  sideSections: /* @__PURE__ */ jsxs(Fragment, { children: [
11241
11727
  /* @__PURE__ */ jsx(OrderFormBlock, { title: "Recorr\xEAncia", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
11242
- /* @__PURE__ */ jsx(Field2, { label: "Unidade", children: /* @__PURE__ */ jsxs("select", { value: unitId, onChange: (event) => setUnitId(event.target.value), className: "h-9 w-full rounded-input border border-input bg-card px-3 text-sm", children: [
11243
- /* @__PURE__ */ jsx("option", { value: "", children: "Todas / sem unidade" }),
11244
- availableLocations.map((location) => /* @__PURE__ */ jsx("option", { value: location.id, children: location.name }, location.id))
11245
- ] }) }),
11728
+ /* @__PURE__ */ jsx(UnitField, { label: "Unidade", choices: unitChoices, value: unitId, onChange: setUnitId }),
11246
11729
  /* @__PURE__ */ jsx(Field2, { label: "Periodicidade *", children: /* @__PURE__ */ jsx("select", { value: frequency, onChange: (event) => setFrequency(event.target.value), className: "h-9 w-full rounded-input border border-input bg-card px-3 text-sm", children: FREQUENCIES.map((item) => /* @__PURE__ */ jsx("option", { value: item.value, children: item.label }, item.value)) }) }),
11247
11730
  /* @__PURE__ */ jsx(Field2, { label: "Dia do vencimento *", children: /* @__PURE__ */ jsx(Input, { type: "number", min: 1, max: 31, value: dueDay, onChange: (event) => setDueDay(event.target.value) }) }),
11248
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
11731
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
11249
11732
  /* @__PURE__ */ jsx(Field2, { label: "In\xEDcio *", children: /* @__PURE__ */ jsx(DatePicker, { value: startsOn, onChange: setStartsOn, className: "w-full" }) }),
11250
11733
  /* @__PURE__ */ jsx(Field2, { label: "T\xE9rmino", children: /* @__PURE__ */ jsx(DatePicker, { value: endsOn, onChange: setEndsOn, className: "w-full" }) })
11251
11734
  ] }),
@@ -11521,16 +12004,8 @@ function buildNav(config, activeView, navigate, t2) {
11521
12004
  children: [
11522
12005
  { id: "payables-new", kind: "action", label: t2("financial.nav.new"), active: activeView.startsWith("payables-new"), onClick: () => navigate("payables-new") },
11523
12006
  { id: "payables-list", label: t2("financial.nav.list"), active: activeView === "payables-list", onClick: () => navigate("payables-list") },
11524
- { id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") },
11525
- // As do V1 que ainda não existem aqui — ver ./GapScreens.
11526
- //
11527
- // «Da minha caixinha» SAIU do menu. No V1 ela é `{caixaAberto && …}`:
11528
- // aparece enquanto a sessão daquela pessoa está aberta e some quando
11529
- // ela fecha. Como aba fixa ela prometia uma tela que quase ninguém vê
11530
- // — e pagar do dinheiro da gaveta é uma FORMA de pagamento do mesmo
11531
- // lançamento, não um cadastro à parte. A rota segue de pé.
11532
- { id: "payables-digitize", label: "Digitalizar Despesa", active: activeView === "payables-digitize", onClick: () => navigate("payables-digitize") },
11533
- { id: "payables-payroll", label: "Importar Folha", active: activeView === "payables-payroll", onClick: () => navigate("payables-payroll") }
12007
+ { id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") }
12008
+ // Digitize expense and import payroll live in the list's create menu, not here.
11534
12009
  ]
11535
12010
  });
11536
12011
  }
@@ -11780,8 +12255,9 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
11780
12255
  // tamanho do buraco, medido no banco do V1.
11781
12256
  case "payables-petty-cash":
11782
12257
  return /* @__PURE__ */ jsx(PettyCashView, {});
12258
+ // Digitizing is the list's receipt capture now — the old link lands on the list.
11783
12259
  case "payables-digitize":
11784
- return /* @__PURE__ */ jsx(DigitizeExpenseView, {});
12260
+ return /* @__PURE__ */ jsx(PayablesView, { intent: { view: "payables", mode: "list" }, onNavigate: navigate, previousView, back });
11785
12261
  case "payables-payroll":
11786
12262
  return /* @__PURE__ */ jsx(ImportPayrollView, {});
11787
12263
  case "cash-reviewers":
@@ -11988,7 +12464,7 @@ function BalanceHero() {
11988
12464
  const summary = useFinancialStore((s) => s.summary);
11989
12465
  const navigate = useDashboardNavigate();
11990
12466
  useEnsureSummary();
11991
- const [hidden, setHidden] = React23__default.useState(false);
12467
+ const [hidden, setHidden] = React25__default.useState(false);
11992
12468
  const money4 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
11993
12469
  const inflow = summary?.monthlyInflow ?? 0;
11994
12470
  const outflow = summary?.monthlyOutflow ?? 0;
@@ -12061,7 +12537,7 @@ function categorizeSpend(notes) {
12061
12537
  function SpendByCategoryChart() {
12062
12538
  const t2 = useTranslation();
12063
12539
  const provider = useFinancialProvider();
12064
- const [rows, setRows] = React23__default.useState([]);
12540
+ const [rows, setRows] = React25__default.useState([]);
12065
12541
  useEffect(() => {
12066
12542
  let alive = true;
12067
12543
  void (async () => {
@@ -12104,7 +12580,7 @@ function UpcomingBills() {
12104
12580
  const { currency } = useFinancialConfig();
12105
12581
  const provider = useFinancialProvider();
12106
12582
  const dueLabel = useDueLabel();
12107
- const [bills, setBills] = React23__default.useState([]);
12583
+ const [bills, setBills] = React25__default.useState([]);
12108
12584
  useEffect(() => {
12109
12585
  let alive = true;
12110
12586
  void (async () => {
@@ -12214,6 +12690,10 @@ var STOPWORDS = /* @__PURE__ */ new Set([
12214
12690
  "pagamento",
12215
12691
  "pagto",
12216
12692
  "pag",
12693
+ "pago",
12694
+ "paga",
12695
+ "pgto",
12696
+ "recebimento",
12217
12697
  "boleto",
12218
12698
  "debito",
12219
12699
  "credito",
@@ -12258,15 +12738,20 @@ function nameScore(descricao, nome) {
12258
12738
  if (texto.size === 0) return 0;
12259
12739
  const achados = alvo.filter((w) => texto.has(w)).length;
12260
12740
  const razao = achados / alvo.length;
12261
- if (achados === 1 && alvo.length > 2) return razao * 0.4;
12262
- return razao;
12741
+ const nomeSet = new Set(alvo);
12742
+ const reverso = texto.size >= 2 && [...texto].every((w) => nomeSet.has(w)) ? 1 : 0;
12743
+ if (achados === 1 && alvo.length > 2) return Math.max(reverso, razao * 0.4);
12744
+ return Math.max(reverso, razao);
12263
12745
  }
12264
- function amountScore(lineAmount, movementAmount) {
12746
+ function amountScore(lineAmount, movementAmount, tolerance = 0.02) {
12265
12747
  if (lineAmount <= 0) return 0;
12266
12748
  const diff = Math.abs(movementAmount - lineAmount);
12267
- if (diff > lineAmount * 0.02 + 0.01) return 0;
12268
- return Math.max(0, 1 - diff / (lineAmount * 0.02 + 0.01));
12749
+ const limite = lineAmount * tolerance + 0.01;
12750
+ if (diff > limite) return 0;
12751
+ return Math.max(0, 1 - diff / limite);
12269
12752
  }
12753
+ var NAME_AMOUNT_TOLERANCE = 0.1;
12754
+ var STRONG_NAME = 0.5;
12270
12755
  function dateScore(lineDate, refDate) {
12271
12756
  if (!lineDate || !refDate) return 0;
12272
12757
  const days = Math.abs(new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5;
@@ -12274,13 +12759,16 @@ function dateScore(lineDate, refDate) {
12274
12759
  }
12275
12760
  function scorePair(line, movement, context = {}) {
12276
12761
  if (movement.direction !== line.direction) return 0;
12277
- const valor = amountScore(line.amount, movement.amount);
12278
- if (valor === 0) return 0;
12279
12762
  const texto = `${line.description ?? ""} ${line.counterpartyName ?? ""}`;
12280
12763
  const nome = Math.max(
12281
12764
  nameScore(texto, context.contactName),
12282
12765
  nameScore(texto, movement.notes)
12283
12766
  );
12767
+ let valor = amountScore(line.amount, movement.amount);
12768
+ if (valor === 0 && nome >= STRONG_NAME) {
12769
+ valor = 0.7 * amountScore(line.amount, movement.amount, NAME_AMOUNT_TOLERANCE);
12770
+ }
12771
+ if (valor === 0) return 0;
12284
12772
  const data = dateScore(line.date, movement.paymentDate ?? movement.dueDate);
12285
12773
  return Math.round((0.45 * valor + 0.35 * nome + 0.2 * data) * 100) / 100;
12286
12774
  }
@@ -13168,6 +13656,22 @@ function createMockFinancialProvider(options) {
13168
13656
  };
13169
13657
  }).sort((a, b) => b.score - a.score).slice(0, 25);
13170
13658
  },
13659
+ async searchReconciliationTargets(statementLineId, query) {
13660
+ const line = store6.statementLines.find((l) => l.id === statementLineId);
13661
+ if (!line) return { data: [], hasMore: false };
13662
+ const status = query.status ?? "all";
13663
+ const termo = query.search?.trim().toLocaleLowerCase("pt-BR");
13664
+ const rows = store6.movements.filter((m) => !m.externalSource && !m.reconciledAt && m.direction === line.direction).filter((m) => m.status !== "cancelled").filter((m) => {
13665
+ const paga = m.movementKind === "payment" || m.status === "paid";
13666
+ return status === "all" || (status === "paid" ? paga : !paga);
13667
+ }).filter((m) => !query.dueFrom || (m.dueDate ?? "") >= query.dueFrom).filter((m) => !query.dueTo || (m.dueDate ?? "") <= query.dueTo).filter((m) => query.amountMin == null || m.amount >= query.amountMin).filter((m) => query.amountMax == null || m.amount <= query.amountMax).map((m) => ({ movement: m, invoice: m.invoiceId ? store6.invoices.find((i) => i.id === m.invoiceId) : void 0 })).filter(({ movement, invoice }) => !termo || `${invoice?.contactName ?? ""} ${invoice?.fiscalNumber ?? ""} ${movement.notes ?? ""}`.toLocaleLowerCase("pt-BR").includes(termo)).sort((a, b) => (a.movement.dueDate ?? "").localeCompare(b.movement.dueDate ?? ""));
13668
+ const pageSize = query.pageSize ?? 50;
13669
+ const start = ((query.page ?? 1) - 1) * pageSize;
13670
+ return {
13671
+ data: rows.slice(start, start + pageSize).map((c) => ({ ...c, score: 0 })),
13672
+ hasMore: rows.length > start + pageSize
13673
+ };
13674
+ },
13171
13675
  // A mesma cabeça da conciliação de verdade (match-statement), sobre a
13172
13676
  // memória: valor, nome e data, e um lançamento por linha.
13173
13677
  async suggestReconciliationBatch(lines) {
@@ -13475,6 +13979,19 @@ async function writeLineAccounting(pub, tenantId, insertedItems, inputItems) {
13475
13979
  const { error } = await pub.from(T.invoiceItems).upsert(rows, { onConflict: "item_id" });
13476
13980
  if (error) console.warn("[financial] line accounting mirror skipped:", error.message);
13477
13981
  }
13982
+ function shiftIsoDate(value, days) {
13983
+ const d = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
13984
+ d.setUTCDate(d.getUTCDate() + days);
13985
+ return d.toISOString().slice(0, 10);
13986
+ }
13987
+ function matchScore(line, m) {
13988
+ const amount = Number(line.amount) || 0;
13989
+ const amtDiff = Math.abs((Number(m.amount) || 0) - amount);
13990
+ const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
13991
+ const refDate = m.dueDate ?? m.paymentDate;
13992
+ const days = line.date && refDate ? Math.abs((new Date(line.date).getTime() - new Date(refDate).getTime()) / 864e5) : 30;
13993
+ return round26(0.7 * amtScore + 0.3 * Math.max(0, 1 - days / 30));
13994
+ }
13478
13995
  function billRowToMovement(r) {
13479
13996
  return {
13480
13997
  id: r.installment_id,
@@ -13571,6 +14088,85 @@ function createSupabaseFinancialProvider() {
13571
14088
  externalSource: data.external_source
13572
14089
  };
13573
14090
  }
14091
+ async function searchBillTargets(line, query) {
14092
+ const { pub } = getClients();
14093
+ const pageSize = query.pageSize ?? 50;
14094
+ const page = query.page ?? 1;
14095
+ let qb = pub.from("v_financial_invoice_lines_due").select("installment_id,invoice_id,direction,number,due_date,description,counterparty_name,installment_number,installment_count,amount,paid,status").eq("direction", line.direction === "debit" ? "payable" : "receivable");
14096
+ const status = query.status ?? "all";
14097
+ qb = status === "open" ? qb.in("status", ["pending", "partial", "overdue"]) : status === "paid" ? qb.eq("status", "paid") : qb.neq("status", "cancelled");
14098
+ if (query.dueFrom) qb = qb.gte("due_date", query.dueFrom);
14099
+ if (query.dueTo) qb = qb.lte("due_date", query.dueTo);
14100
+ if (query.amountMin != null) qb = qb.gte("amount", query.amountMin);
14101
+ if (query.amountMax != null) qb = qb.lte("amount", query.amountMax);
14102
+ const termo = query.search?.trim().replace(/[,()*%]/g, " ").replace(/\s+/g, " ");
14103
+ if (termo) {
14104
+ qb = qb.or(["description", "number", "counterparty_name"].map((c) => `${c}.ilike.%${termo}%`).join(","));
14105
+ }
14106
+ const { data, error } = await qb.order("due_date", { ascending: true }).range((page - 1) * pageSize, page * pageSize);
14107
+ if (error) throw error;
14108
+ const rows = data ?? [];
14109
+ const hasMore = rows.length > pageSize;
14110
+ const visiveis = rows.slice(0, pageSize);
14111
+ const pagas = visiveis.filter((r) => r.status === "paid").map((r) => r.installment_id);
14112
+ const pagamentoDe = /* @__PURE__ */ new Map();
14113
+ if (pagas.length) {
14114
+ const { data: allocs } = await pub.from(T.allocations).select("installment_id,payment_movement_id").in("installment_id", pagas);
14115
+ const ids = [...new Set((allocs ?? []).map((a) => a.payment_movement_id).filter(Boolean))];
14116
+ const { data: pays } = ids.length ? await pub.from(T.movements).select("*").in("id", ids).neq("status", "cancelled").is("reversed_by_id", null) : { data: [] };
14117
+ const byId = new Map((pays ?? []).map((p) => [p.id, p]));
14118
+ for (const a of allocs ?? []) {
14119
+ const p = byId.get(a.payment_movement_id);
14120
+ if (p) pagamentoDe.set(a.installment_id, p);
14121
+ }
14122
+ }
14123
+ const candidates = [];
14124
+ for (const r of visiveis) {
14125
+ const invoice = {
14126
+ id: r.invoice_id,
14127
+ contactName: r.counterparty_name ?? void 0,
14128
+ fiscalNumber: r.number ?? void 0
14129
+ };
14130
+ let movement;
14131
+ if (r.status === "paid") {
14132
+ const p = pagamentoDe.get(r.installment_id);
14133
+ if (!p || p.reconciled_at) continue;
14134
+ movement = {
14135
+ ...snakeToCamel(p),
14136
+ invoiceId: r.invoice_id,
14137
+ settlesMovementId: r.installment_id,
14138
+ dueDate: r.due_date,
14139
+ notes: p.notes ?? r.description ?? void 0
14140
+ };
14141
+ } else {
14142
+ movement = {
14143
+ id: r.installment_id,
14144
+ invoiceId: r.invoice_id,
14145
+ direction: line.direction,
14146
+ movementKind: "bill",
14147
+ amount: Number(r.amount) || 0,
14148
+ paidAmount: Number(r.paid) || 0,
14149
+ status: r.status,
14150
+ dueDate: r.due_date,
14151
+ installmentNumber: r.installment_number ?? void 0,
14152
+ notes: r.description ?? void 0
14153
+ };
14154
+ }
14155
+ const pareado = scorePair(
14156
+ { amount: line.amount, date: line.date, direction: line.direction, description: line.description ?? "" },
14157
+ movement,
14158
+ { contactName: r.counterparty_name ?? void 0 }
14159
+ );
14160
+ candidates.push({
14161
+ movement,
14162
+ invoice,
14163
+ score: pareado > 0 ? pareado : round26(0.6 * matchScore(line, movement)),
14164
+ installmentCount: r.installment_count ?? void 0
14165
+ });
14166
+ }
14167
+ candidates.sort((a, b) => b.score - a.score);
14168
+ return { data: candidates, hasMore };
14169
+ }
13574
14170
  async function markReconciled(statementLineId, matchedMovementId) {
13575
14171
  const { pub } = getClients();
13576
14172
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
@@ -13778,6 +14374,33 @@ function createSupabaseFinancialProvider() {
13778
14374
  };
13779
14375
  });
13780
14376
  },
14377
+ async addInvoiceAttachment(invoiceId, file) {
14378
+ const { pub } = getClients();
14379
+ const tenantId = getTenantId();
14380
+ if (!tenantId) throw new Error("Conta sem empresa ativa");
14381
+ const safe = file.name.replace(/[^\w.\-]+/g, "_");
14382
+ const path = `${tenantId}/financial_invoice/${invoiceId}/${Date.now()}_${safe}`;
14383
+ const { error: upErr } = await pub.storage.from("attachments").upload(path, file, { contentType: file.type || void 0 });
14384
+ if (upErr) throw upErr;
14385
+ const { error } = await pub.from("documents").insert({
14386
+ tenant_id: tenantId,
14387
+ kind: "attachment",
14388
+ status: "active",
14389
+ subject_type: "financial_invoice",
14390
+ subject_id: invoiceId,
14391
+ file_name: file.name,
14392
+ file_size: file.size,
14393
+ mime_type: file.type || null,
14394
+ storage_provider: "supabase",
14395
+ storage_bucket: "attachments",
14396
+ storage_path: path
14397
+ });
14398
+ if (error) {
14399
+ await pub.storage.from("attachments").remove([path]).catch(() => {
14400
+ });
14401
+ throw error;
14402
+ }
14403
+ },
13781
14404
  async createInvoice(input) {
13782
14405
  const { core, pub } = getClients();
13783
14406
  const tenantId = getTenantId();
@@ -15184,6 +15807,31 @@ function createSupabaseFinancialProvider() {
15184
15807
  customerEmail: r.customer_email
15185
15808
  }));
15186
15809
  },
15810
+ async findGiftCardByCode(raw) {
15811
+ const code = raw.replace(/[\s.-]/g, "").toUpperCase();
15812
+ if (!code) return null;
15813
+ const { pub } = getClients();
15814
+ let { data, error } = await pub.rpc("financial_gift_card_by_code", { p_code: code });
15815
+ if (error?.code === "PGRST202") {
15816
+ ({ data, error } = await pub.rpc("financial_gift_cards", { p_search: code, p_limit: 5 }));
15817
+ }
15818
+ if (error) throw error;
15819
+ const row = (data ?? []).find((r) => String(r.code).toUpperCase() === code);
15820
+ if (!row) return null;
15821
+ return {
15822
+ id: row.id,
15823
+ code: row.code,
15824
+ faceValue: Number(row.face_value) || 0,
15825
+ balance: row.balance == null ? null : Number(row.balance),
15826
+ state: row.state,
15827
+ currency: row.currency,
15828
+ issuedAt: row.issued_at,
15829
+ expiresAt: row.expires_at,
15830
+ activatedAt: row.activated_at,
15831
+ note: row.note,
15832
+ customerEmail: row.customer_email
15833
+ };
15834
+ },
15187
15835
  async issueGiftCards(input) {
15188
15836
  const { pub } = getClients();
15189
15837
  const { data, error } = await pub.rpc("financial_issue_gift_cards", {
@@ -15343,7 +15991,17 @@ function createSupabaseFinancialProvider() {
15343
15991
  if (o) c.invoice = { id: o.id, contactName: o.metadata?.contactName, fiscalNumber: o.reference_number };
15344
15992
  }
15345
15993
  }
15346
- return candidates;
15994
+ const linha = { id: line.id, amount, date: lineDate, direction: line.direction, externalSource: line.external_source };
15995
+ const abertas = await searchBillTargets(linha, {
15996
+ status: "open",
15997
+ amountMin: round26(amount - tol),
15998
+ amountMax: round26(amount + tol),
15999
+ pageSize: 25
16000
+ }).catch(() => ({ data: [], hasMore: false }));
16001
+ return [...candidates, ...abertas.data].sort((a, b) => b.score - a.score);
16002
+ },
16003
+ async searchReconciliationTargets(statementLineId, query) {
16004
+ return searchBillTargets(await readStatementLine(statementLineId), query);
15347
16005
  },
15348
16006
  // ------------------------------------------------------------------
15349
16007
  // A SUGESTÃO QUE CHEGA SOZINHA, para a página inteira.
@@ -15363,16 +16021,38 @@ function createSupabaseFinancialProvider() {
15363
16021
  const abertas = lines.filter((line) => !line.reconciledAt && (Number(line.amount) || 0) > 0);
15364
16022
  if (abertas.length === 0) return {};
15365
16023
  const faixas = [...new Set(abertas.map((line) => round26(line.amount)))].map((valor) => {
15366
- const tol = Math.max(0.01, round26(valor * 0.02));
16024
+ const tol = Math.max(0.01, round26(valor * NAME_AMOUNT_TOLERANCE));
15367
16025
  return `and(amount.gte.${round26(valor - tol)},amount.lte.${round26(valor + tol)})`;
15368
16026
  });
15369
16027
  const direcoes = [...new Set(abertas.map((line) => line.direction))];
15370
16028
  const { data, error } = await pub.from(T.movements).select("*").is("external_source", null).is("reconciled_at", null).in("direction", direcoes).or(faixas.join(",")).limit(500);
15371
16029
  if (error) throw error;
15372
16030
  const movimentos = (data ?? []).map((r) => snakeToCamel(r));
15373
- if (movimentos.length === 0) return {};
16031
+ const datas = abertas.map((line) => line.date).filter(Boolean).sort();
15374
16032
  const contexto = /* @__PURE__ */ new Map();
15375
- const invoiceIds = [...new Set(movimentos.map((m) => m.invoiceId).filter(Boolean))];
16033
+ if (datas.length) {
16034
+ const { data: billRows } = await pub.from("v_financial_invoice_lines_due").select("installment_id,invoice_id,direction,number,due_date,description,counterparty_name,installment_number,amount,paid,status").in("direction", direcoes.map((d) => d === "debit" ? "payable" : "receivable")).in("status", ["pending", "partial", "overdue"]).gte("due_date", shiftIsoDate(datas[0], -30)).lte("due_date", shiftIsoDate(datas[datas.length - 1], 30)).or(faixas.join(",")).limit(500);
16035
+ for (const r of billRows ?? []) {
16036
+ movimentos.push({
16037
+ id: r.installment_id,
16038
+ invoiceId: r.invoice_id,
16039
+ direction: r.direction === "payable" ? "debit" : "credit",
16040
+ movementKind: "bill",
16041
+ amount: Number(r.amount) || 0,
16042
+ paidAmount: Number(r.paid) || 0,
16043
+ status: r.status,
16044
+ dueDate: r.due_date,
16045
+ installmentNumber: r.installment_number ?? void 0,
16046
+ notes: r.description ?? void 0
16047
+ });
16048
+ contexto.set(r.installment_id, {
16049
+ contactName: r.counterparty_name ?? void 0,
16050
+ invoice: { id: r.invoice_id, contactName: r.counterparty_name ?? void 0, fiscalNumber: r.number ?? void 0 }
16051
+ });
16052
+ }
16053
+ }
16054
+ if (movimentos.length === 0) return {};
16055
+ const invoiceIds = [...new Set(movimentos.filter((m) => !contexto.has(m.id)).map((m) => m.invoiceId).filter(Boolean))];
15376
16056
  if (invoiceIds.length > 0) {
15377
16057
  const { data: orders } = await core.from("orders").select("id, reference_number, metadata").in("id", invoiceIds);
15378
16058
  const byId = new Map((orders ?? []).map((o) => [o.id, o]));
@@ -15391,7 +16071,7 @@ function createSupabaseFinancialProvider() {
15391
16071
  async reconcileMovement(input) {
15392
16072
  if (input.mode === "settle" && input.matchedMovementId) {
15393
16073
  const line = await readStatementLine(input.bankMovementId);
15394
- const { data: alvo } = await getClients().pub.from(T.movements).select("amount, paid_amount").eq("id", input.matchedMovementId).maybeSingle();
16074
+ const { data: alvo } = await getClients().pub.from(T.bills).select("amount, paid").eq("installment_id", input.matchedMovementId).maybeSingle();
15395
16075
  if (!alvo) throw new Error("Lan\xE7amento n\xE3o encontrado");
15396
16076
  await runReconcileLink(
15397
16077
  reconcileFacade(),
@@ -15399,7 +16079,7 @@ function createSupabaseFinancialProvider() {
15399
16079
  {
15400
16080
  movementId: input.matchedMovementId,
15401
16081
  mode: "settle",
15402
- openAmount: round26((Number(alvo.amount) || 0) - (Number(alvo.paid_amount) || 0))
16082
+ openAmount: round26((Number(alvo.amount) || 0) - (Number(alvo.paid) || 0))
15403
16083
  },
15404
16084
  { payment: input.payment, adjustments: input.adjustments }
15405
16085
  );
@@ -15674,7 +16354,8 @@ function createSupabaseFinancialProvider() {
15674
16354
  };
15675
16355
  for (const payee of payees) {
15676
16356
  if (text2(payee.document)) {
15677
- await pub.from("people").update({ document_number: text2(payee.document) }).eq("id", payee.contactId);
16357
+ const { error: error2 } = await pub.from("people").update({ document_number: text2(payee.document) }).eq("id", payee.contactId);
16358
+ if (error2) throw error2;
15678
16359
  }
15679
16360
  const row = {
15680
16361
  bank_code: text2(payee.bankCode),
@@ -15693,18 +16374,16 @@ function createSupabaseFinancialProvider() {
15693
16374
  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
16375
  targetId = existing?.id;
15695
16376
  }
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
- }
16377
+ const { data: written, error } = targetId ? await pub.from(T.payeeAccounts).update(row).eq("id", targetId).select("id") : await pub.from(T.payeeAccounts).insert({
16378
+ ...row,
16379
+ tenant_id: tenantId,
16380
+ owner_type: "person",
16381
+ owner_id: payee.contactId,
16382
+ label: "Principal",
16383
+ is_default: true
16384
+ }).select("id");
16385
+ if (error) throw error;
16386
+ if (!written?.length) throw new Error("payee account was not saved (no financial edit permission?)");
15708
16387
  }
15709
16388
  },
15710
16389
  /**
@@ -16252,21 +16931,21 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
16252
16931
  const canCreate = can("financial", "create");
16253
16932
  const canEdit = can("financial", "edit");
16254
16933
  const canDelete = can("financial", "delete");
16255
- const provider = React23__default.useMemo(
16934
+ const provider = React25__default.useMemo(
16256
16935
  () => dataProvider ?? resolveDataProvider(entity, mockData ?? []),
16257
16936
  [entity, mockData, dataProvider]
16258
16937
  );
16259
16938
  const table = entity.data?.table;
16260
- const [nodes, setNodes] = React23__default.useState(null);
16261
- const [loadError, setLoadError] = React23__default.useState(null);
16262
- const [expanded, setExpanded] = React23__default.useState(/* @__PURE__ */ new Set());
16263
- const [query, setQuery] = React23__default.useState("");
16264
- const [showInactive, setShowInactive] = React23__default.useState(true);
16265
- const [editing, setEditing] = React23__default.useState(null);
16266
- const [confirmDelete, setConfirmDelete] = React23__default.useState(null);
16267
- const [busy, setBusy] = React23__default.useState(false);
16268
- const initialized = React23__default.useRef(false);
16269
- const reload = React23__default.useCallback(async () => {
16939
+ const [nodes, setNodes] = React25__default.useState(null);
16940
+ const [loadError, setLoadError] = React25__default.useState(null);
16941
+ const [expanded, setExpanded] = React25__default.useState(/* @__PURE__ */ new Set());
16942
+ const [query, setQuery] = React25__default.useState("");
16943
+ const [showInactive, setShowInactive] = React25__default.useState(true);
16944
+ const [editing, setEditing] = React25__default.useState(null);
16945
+ const [confirmDelete, setConfirmDelete] = React25__default.useState(null);
16946
+ const [busy, setBusy] = React25__default.useState(false);
16947
+ const initialized = React25__default.useRef(false);
16948
+ const reload = React25__default.useCallback(async () => {
16270
16949
  try {
16271
16950
  const rows = await loadAll(provider);
16272
16951
  setNodes(rows);
@@ -16281,17 +16960,17 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
16281
16960
  setNodes((prev) => prev ?? []);
16282
16961
  }
16283
16962
  }, [provider]);
16284
- React23__default.useEffect(() => {
16963
+ React25__default.useEffect(() => {
16285
16964
  void reload();
16286
16965
  }, [reload]);
16287
- const shown = React23__default.useMemo(
16966
+ const shown = React25__default.useMemo(
16288
16967
  () => (nodes ?? []).filter((n) => showInactive || n.isActive),
16289
16968
  [nodes, showInactive]
16290
16969
  );
16291
- const tree = React23__default.useMemo(() => buildChartTree(shown), [shown]);
16292
- const fullTree = React23__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
16293
- const search = React23__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
16294
- const groupIds = React23__default.useMemo(
16970
+ const tree = React25__default.useMemo(() => buildChartTree(shown), [shown]);
16971
+ const fullTree = React25__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
16972
+ const search = React25__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
16973
+ const groupIds = React25__default.useMemo(
16295
16974
  () => [...tree.byId.values()].filter((n) => isGroupNode(tree, n)).map((n) => n.id),
16296
16975
  [tree]
16297
16976
  );
@@ -16387,7 +17066,7 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
16387
17066
  const ok = await run(() => provider.remove(node.id), t2("financial.chartTree.deleted", { name: node.name }));
16388
17067
  if (ok) setConfirmDelete(null);
16389
17068
  }
16390
- const parentOptions = React23__default.useCallback((q, selfId) => {
17069
+ const parentOptions = React25__default.useCallback((q, selfId) => {
16391
17070
  const blocked = selfId ? descendantIds(fullTree, selfId).add(selfId) : /* @__PURE__ */ new Set();
16392
17071
  const needle = q.trim().toLowerCase();
16393
17072
  return [...fullTree.byId.values()].filter((n) => !blocked.has(n.id) && isGroupNode(fullTree, n)).filter((n) => !needle || accountLabel(n).toLowerCase().includes(needle)).sort((a, b) => a.code.localeCompare(b.code, void 0, { numeric: true })).slice(0, 50).map((n) => ({ id: n.id, label: accountLabel(n) }));
@@ -16690,7 +17369,7 @@ var chartOfAccountsSeed = [
16690
17369
  { id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
16691
17370
  { id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
16692
17371
  ];
16693
- var ChartOfAccountsRegistryView = (props) => React23__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
17372
+ var ChartOfAccountsRegistryView = (props) => React25__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
16694
17373
  ChartOfAccountsRegistryView.displayName = "ChartOfAccountsRegistryView";
16695
17374
  var costCenterEntity = {
16696
17375
  name: "Cost Center",
@@ -16788,7 +17467,7 @@ var bankAccountEntity = {
16788
17467
  labelWhen: (v) => v.kind === "card_payable" ? "Banco emissor" : void 0,
16789
17468
  visibleWhen: (v) => hasIssuer(v.kind),
16790
17469
  group: "bank",
16791
- renderInput: ({ value, values, onChange, setValue }) => React23__default.createElement(BankPicker, {
17470
+ renderInput: ({ value, values, onChange, setValue }) => React25__default.createElement(BankPicker, {
16792
17471
  code: values.bankCode ?? "",
16793
17472
  name: value ?? "",
16794
17473
  onChange: (next) => {
@@ -17657,10 +18336,10 @@ function CashSessionHeaderControl({ config, provider, store: store6 }) {
17657
18336
  const fetchCashSessions = useStore(store6, (s) => s.fetchCashSessions);
17658
18337
  const open = useStore(store6, (s) => s.cashSessionModalOpen);
17659
18338
  const setOpen = useStore(store6, (s) => s.setCashSessionModalOpen);
17660
- React23__default.useEffect(() => {
18339
+ React25__default.useEffect(() => {
17661
18340
  void fetchCashSessions?.();
17662
18341
  }, [fetchCashSessions]);
17663
- React23__default.useEffect(() => {
18342
+ React25__default.useEffect(() => {
17664
18343
  if (!open) return;
17665
18344
  const close = () => setOpen(false);
17666
18345
  window.addEventListener("hashchange", close);
@@ -17780,7 +18459,7 @@ var en = {
17780
18459
  "financial.invoice.items": "Items",
17781
18460
  "financial.invoice.keepInvoice": "Keep Invoice",
17782
18461
  "financial.invoice.new": "New",
17783
- "financial.invoice.newManual": "Fill it in by hand",
18462
+ "financial.invoice.newManual": "Create",
17784
18463
  "financial.invoiceForm.createContact": "Create",
17785
18464
  "financial.invoiceForm.alreadyPaid": "Already paid",
17786
18465
  "financial.invoiceForm.paidWith": "Payment method",
@@ -17792,6 +18471,9 @@ var en = {
17792
18471
  "financial.invoiceForm.paidNeedsMethod": "Tell us the payment method and the account",
17793
18472
  "financial.invoiceForm.willBePaid": "This installment will be settled",
17794
18473
  "financial.invoiceForm.clear": "Clear",
18474
+ "financial.invoiceForm.manageAccounts": "Manage accounts",
18475
+ "financial.invoiceForm.manageChart": "Manage chart of accounts",
18476
+ "financial.invoiceForm.manageCostCenters": "Manage cost centers",
17795
18477
  "financial.invoice.newManualHint": "Open the full invoice form",
17796
18478
  "financial.invoice.noDetails": "No additional details recorded",
17797
18479
  "financial.invoice.unlinkedPayments": "Payments not linked to an installment",
@@ -17840,6 +18522,13 @@ var en = {
17840
18522
  "financial.itemType.product": "Product",
17841
18523
  "financial.itemType.other": "Other",
17842
18524
  "financial.itemType.gift_card": "Gift Card",
18525
+ "financial.giftCardLine.label": "Gift card code",
18526
+ "financial.giftCardLine.placeholder": "0000 0000 0000 0000",
18527
+ "financial.giftCardLine.hint": "Scan or type the full card code.",
18528
+ "financial.giftCardLine.notFound": "Gift card not found",
18529
+ "financial.giftCardLine.revoked": "Gift card cancelled \u2014 cannot be sold",
18530
+ "financial.giftCardLine.expired": "Gift card expired",
18531
+ "financial.giftCardLine.alreadySold": "This gift card has already been sold",
17843
18532
  "financial.invoiceForm.cancel": "Cancel",
17844
18533
  "financial.invoiceForm.close": "Close",
17845
18534
  "financial.invoiceForm.creating": "Creating new invoice",
@@ -18043,13 +18732,41 @@ var en = {
18043
18732
  "financial.reconciliation.reconciled": "Reconciled",
18044
18733
  "financial.reconciliation.undo": "Undo",
18045
18734
  "financial.reconciliation.flowTitle": "Reconcile transaction",
18046
- "financial.reconciliation.stepClassify": "How to classify",
18735
+ "financial.reconciliation.stepClassify": "What to do",
18047
18736
  "financial.reconciliation.stepItems": "The items",
18048
18737
  "financial.reconciliation.stepDetails": "The details",
18049
18738
  "financial.reconciliation.stepConfirm": "Confirm",
18050
18739
  "financial.reconciliation.confirmAction": "Confirm",
18051
18740
  "financial.reconciliation.howToClassify": "How do you want to classify this transaction?",
18052
18741
  "financial.reconciliation.pickIntent": "Pick what this transaction is",
18742
+ "financial.reconciliation.foundTitle": "Bills found",
18743
+ "financial.reconciliation.searchWiden": "Widen search (\xB115 days, any amount)",
18744
+ "financial.reconciliation.stepSelectBill": "Select the bill",
18745
+ "financial.reconciliation.stepAdjust": "Adjustment",
18746
+ "financial.reconciliation.selectBillFor": "Statement of {{date}} \xB7 {{description}} \xB7",
18747
+ "financial.reconciliation.statusOverdue": "Overdue",
18748
+ "financial.reconciliation.colDue": "Due",
18749
+ "financial.reconciliation.colCounterparty": "Counterparty",
18750
+ "financial.reconciliation.colDescription": "Number / Description",
18751
+ "financial.reconciliation.colInstallment": "Installment",
18752
+ "financial.reconciliation.colToReconcile": "To reconcile",
18753
+ "financial.reconciliation.colDelta": "\u0394 vs statement",
18754
+ "financial.reconciliation.searchStatusAllShort": "All",
18755
+ "financial.reconciliation.searchPendingHint": "The list still shows the previous filters. Click Filter to apply.",
18756
+ "financial.reconciliation.searchTitle": "Find a bill",
18757
+ "financial.reconciliation.searchDueFrom": "Due from",
18758
+ "financial.reconciliation.searchDueTo": "to",
18759
+ "financial.reconciliation.searchStatus": "Status",
18760
+ "financial.reconciliation.searchStatusAll": "All (not reconciled)",
18761
+ "financial.reconciliation.searchStatusOpen": "Open",
18762
+ "financial.reconciliation.searchStatusPaid": "Paid",
18763
+ "financial.reconciliation.searchText": "Search (counterparty, number, description)",
18764
+ "financial.reconciliation.searchTextPlaceholder": "Type and press Enter",
18765
+ "financial.reconciliation.searchAmountMin": "Min amount",
18766
+ "financial.reconciliation.searchAmountMax": "Max amount",
18767
+ "financial.reconciliation.searchAction": "Search",
18768
+ "financial.reconciliation.searchEmpty": "No bills match these filters.",
18769
+ "financial.reconciliation.searchMore": "Load more",
18053
18770
  "financial.reconciliation.pickCandidate": "Pick the entry to link",
18054
18771
  "financial.reconciliation.needDescription": "Describe the entry",
18055
18772
  "financial.reconciliation.intentLink": "Link to an existing entry",
@@ -18085,6 +18802,14 @@ var en = {
18085
18802
  "financial.reconciliation.linkOnlyExplain": "This bill was already paid in the system. Confirming only ties the statement line to it \u2014 no second payment, no change to the amount.",
18086
18803
  "financial.reconciliation.noAdjustmentNeeded": "The amount matches the bill. Nothing to adjust.",
18087
18804
  "financial.reconciliation.adjustmentNeeded": "The bank took {{gap}} {{side}} than the bill asked for. Say what that difference is.",
18805
+ "financial.reconciliation.adjustmentNeededIn": "{{gap}} {{side}} came in than the bill charged. Say what that difference is.",
18806
+ "financial.reconciliation.adjustmentNeedsAccount": "Pick the chart account for each adjustment.",
18807
+ "financial.reconciliation.adjustmentType": "Type",
18808
+ "financial.reconciliation.adjustmentDescriptionPlaceholder": "E.g. late interest",
18809
+ "financial.reconciliation.adjustmentDefaultInterest": "Late interest",
18810
+ "financial.reconciliation.adjustmentDefaultFine": "Late fine",
18811
+ "financial.reconciliation.adjustmentDefaultDiscountOut": "Discount obtained",
18812
+ "financial.reconciliation.adjustmentDefaultDiscountIn": "Discount granted",
18088
18813
  "financial.reconciliation.adjustmentMore": "more",
18089
18814
  "financial.reconciliation.adjustmentLess": "less",
18090
18815
  "financial.reconciliation.interest": "Interest",
@@ -18519,6 +19244,10 @@ var en = {
18519
19244
  "financial.quickTx.receiptRemove": "Remove receipt",
18520
19245
  "financial.quickTx.receiptInvalid": "Please choose an image file",
18521
19246
  "financial.quickTx.sendReceipt": "Send receipt",
19247
+ "financial.quickTx.digitizeExpense": "Digitize expense",
19248
+ "financial.quickTx.digitizeReceipt": "Digitize receipt",
19249
+ "financial.invoice.importPayroll": "Import payroll",
19250
+ "financial.invoice.importPayrollSoon": "Coming soon",
18522
19251
  "financial.receipt.ask": "Send the receipt photo\u2026",
18523
19252
  "financial.receipt.attachRead": "Attach the receipt the assistant read",
18524
19253
  "financial.receipt.fromReceipt": "From the receipt",
@@ -18670,7 +19399,7 @@ var ptBR = {
18670
19399
  "financial.invoice.items": "Itens",
18671
19400
  "financial.invoice.keepInvoice": "Manter Fatura",
18672
19401
  "financial.invoice.new": "Nova",
18673
- "financial.invoice.newManual": "Cadastrar manualmente",
19402
+ "financial.invoice.newManual": "Cadastrar",
18674
19403
  "financial.invoiceForm.createContact": "Cadastrar",
18675
19404
  "financial.invoiceForm.alreadyPaid": "J\xE1 foi pago",
18676
19405
  "financial.invoiceForm.paidWith": "Forma de pagamento",
@@ -18682,6 +19411,9 @@ var ptBR = {
18682
19411
  "financial.invoiceForm.paidNeedsMethod": "Diga a forma de pagamento e a conta",
18683
19412
  "financial.invoiceForm.willBePaid": "Esta parcela entra como paga",
18684
19413
  "financial.invoiceForm.clear": "Limpar",
19414
+ "financial.invoiceForm.manageAccounts": "Gerenciar contas",
19415
+ "financial.invoiceForm.manageChart": "Gerenciar plano de contas",
19416
+ "financial.invoiceForm.manageCostCenters": "Gerenciar centros de custo",
18685
19417
  "financial.invoice.newManualHint": "Preencher os campos da conta",
18686
19418
  "financial.invoice.noDetails": "Nenhum detalhe adicional registrado",
18687
19419
  "financial.invoice.unlinkedPayments": "Pagamentos sem parcela vinculada",
@@ -18730,6 +19462,13 @@ var ptBR = {
18730
19462
  "financial.itemType.product": "Produto",
18731
19463
  "financial.itemType.other": "Outro",
18732
19464
  "financial.itemType.gift_card": "Gift Card",
19465
+ "financial.giftCardLine.label": "C\xF3digo do gift card",
19466
+ "financial.giftCardLine.placeholder": "0000 0000 0000 0000",
19467
+ "financial.giftCardLine.hint": "Escaneie ou digite o c\xF3digo completo do cart\xE3o.",
19468
+ "financial.giftCardLine.notFound": "Gift card n\xE3o encontrado",
19469
+ "financial.giftCardLine.revoked": "Gift card cancelado \u2014 n\xE3o pode ser vendido",
19470
+ "financial.giftCardLine.expired": "Gift card expirado",
19471
+ "financial.giftCardLine.alreadySold": "Este gift card j\xE1 foi vendido",
18733
19472
  "financial.invoiceForm.cancel": "Cancelar",
18734
19473
  "financial.invoiceForm.close": "Fechar",
18735
19474
  "financial.invoiceForm.creating": "Criando nova fatura",
@@ -18936,13 +19675,41 @@ var ptBR = {
18936
19675
  "financial.reconciliation.reconciled": "Conciliado",
18937
19676
  "financial.reconciliation.undo": "Desfazer",
18938
19677
  "financial.reconciliation.flowTitle": "Conciliar movimenta\xE7\xE3o",
18939
- "financial.reconciliation.stepClassify": "Como classificar",
19678
+ "financial.reconciliation.stepClassify": "O que deseja fazer",
18940
19679
  "financial.reconciliation.stepItems": "Os itens",
18941
19680
  "financial.reconciliation.stepDetails": "Os detalhes",
18942
19681
  "financial.reconciliation.stepConfirm": "Confirmar",
18943
19682
  "financial.reconciliation.confirmAction": "Confirmar",
18944
19683
  "financial.reconciliation.howToClassify": "Como voc\xEA quer classificar esta transa\xE7\xE3o?",
18945
19684
  "financial.reconciliation.pickIntent": "Escolha o que esta transa\xE7\xE3o \xE9",
19685
+ "financial.reconciliation.foundTitle": "Contas encontradas",
19686
+ "financial.reconciliation.searchWiden": "Ampliar busca (\xB115 dias, qualquer valor)",
19687
+ "financial.reconciliation.stepSelectBill": "Selecione a conta",
19688
+ "financial.reconciliation.stepAdjust": "Ajuste",
19689
+ "financial.reconciliation.selectBillFor": "Extrato de {{date}} \xB7 {{description}} \xB7",
19690
+ "financial.reconciliation.statusOverdue": "Vencido",
19691
+ "financial.reconciliation.colDue": "Venc.",
19692
+ "financial.reconciliation.colCounterparty": "Contraparte",
19693
+ "financial.reconciliation.colDescription": "N\xFAmero / Descri\xE7\xE3o",
19694
+ "financial.reconciliation.colInstallment": "Parcela",
19695
+ "financial.reconciliation.colToReconcile": "Valor a conciliar",
19696
+ "financial.reconciliation.colDelta": "\u0394 vs extrato",
19697
+ "financial.reconciliation.searchStatusAllShort": "Todos",
19698
+ "financial.reconciliation.searchPendingHint": "A lista ainda mostra o recorte anterior. Clique em Filtrar para aplicar.",
19699
+ "financial.reconciliation.searchTitle": "Buscar conta",
19700
+ "financial.reconciliation.searchDueFrom": "Vencimento de",
19701
+ "financial.reconciliation.searchDueTo": "at\xE9",
19702
+ "financial.reconciliation.searchStatus": "Situa\xE7\xE3o",
19703
+ "financial.reconciliation.searchStatusAll": "Todos (n\xE3o conciliados)",
19704
+ "financial.reconciliation.searchStatusOpen": "Em aberto",
19705
+ "financial.reconciliation.searchStatusPaid": "Pagos",
19706
+ "financial.reconciliation.searchText": "Buscar (contraparte, n\xFAmero, descri\xE7\xE3o)",
19707
+ "financial.reconciliation.searchTextPlaceholder": "Digite e pressione Enter",
19708
+ "financial.reconciliation.searchAmountMin": "Valor m\xEDn.",
19709
+ "financial.reconciliation.searchAmountMax": "Valor m\xE1x.",
19710
+ "financial.reconciliation.searchAction": "Buscar",
19711
+ "financial.reconciliation.searchEmpty": "Nenhuma conta com esses filtros.",
19712
+ "financial.reconciliation.searchMore": "Carregar mais",
18946
19713
  "financial.reconciliation.pickCandidate": "Escolha o lan\xE7amento a vincular",
18947
19714
  "financial.reconciliation.needDescription": "Descreva o lan\xE7amento",
18948
19715
  "financial.reconciliation.intentLink": "Vincular a um lan\xE7amento",
@@ -18978,6 +19745,14 @@ var ptBR = {
18978
19745
  "financial.reconciliation.linkOnlyExplain": "Esta conta j\xE1 foi paga no sistema. Confirmar apenas amarra a linha do extrato a ela \u2014 sem criar um segundo pagamento e sem mexer no valor.",
18979
19746
  "financial.reconciliation.noAdjustmentNeeded": "O valor bate com a conta. Nada a ajustar.",
18980
19747
  "financial.reconciliation.adjustmentNeeded": "O banco tirou {{gap}} {{side}} do que a conta pedia. Diga o que \xE9 essa diferen\xE7a.",
19748
+ "financial.reconciliation.adjustmentNeededIn": "Entrou {{gap}} {{side}} do que a conta cobrava. Diga o que \xE9 essa diferen\xE7a.",
19749
+ "financial.reconciliation.adjustmentNeedsAccount": "Escolha o plano de contas de cada ajuste.",
19750
+ "financial.reconciliation.adjustmentType": "Tipo",
19751
+ "financial.reconciliation.adjustmentDescriptionPlaceholder": "Ex.: juros por atraso",
19752
+ "financial.reconciliation.adjustmentDefaultInterest": "Juros por atraso",
19753
+ "financial.reconciliation.adjustmentDefaultFine": "Multa por atraso",
19754
+ "financial.reconciliation.adjustmentDefaultDiscountOut": "Desconto obtido",
19755
+ "financial.reconciliation.adjustmentDefaultDiscountIn": "Desconto concedido",
18981
19756
  "financial.reconciliation.adjustmentMore": "a mais",
18982
19757
  "financial.reconciliation.adjustmentLess": "a menos",
18983
19758
  "financial.reconciliation.interest": "Juros",
@@ -19412,6 +20187,10 @@ var ptBR = {
19412
20187
  "financial.quickTx.receiptRemove": "Remover recibo",
19413
20188
  "financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
19414
20189
  "financial.quickTx.sendReceipt": "Enviar recibo",
20190
+ "financial.quickTx.digitizeExpense": "Digitalizar despesa",
20191
+ "financial.quickTx.digitizeReceipt": "Digitalizar comprovante",
20192
+ "financial.invoice.importPayroll": "Importar folha",
20193
+ "financial.invoice.importPayrollSoon": "Em breve",
19415
20194
  "financial.receipt.ask": "Mande a foto do recibo\u2026",
19416
20195
  "financial.receipt.attachRead": "Anexar o recibo que o assistente leu",
19417
20196
  "financial.receipt.fromReceipt": "Do recibo",
@@ -19577,14 +20356,14 @@ function CommissionRuleDialog({
19577
20356
  onClose,
19578
20357
  onSaved
19579
20358
  }) {
19580
- const [name, setName] = React23__default.useState(rule?.name ?? "");
19581
- const [description, setDescription] = React23__default.useState(rule?.description ?? "");
19582
- const [priority, setPriority] = React23__default.useState(String(rule?.priority ?? 1));
19583
- const [isActive, setIsActive] = React23__default.useState(rule?.isActive ?? true);
19584
- const [isDefault, setIsDefault] = React23__default.useState(rule?.isDefault ?? !hasDefault);
19585
- const [saving, setSaving] = React23__default.useState(false);
19586
- const [error, setError] = React23__default.useState(null);
19587
- const initial = React23__default.useMemo(() => {
20359
+ const [name, setName] = React25__default.useState(rule?.name ?? "");
20360
+ const [description, setDescription] = React25__default.useState(rule?.description ?? "");
20361
+ const [priority, setPriority] = React25__default.useState(String(rule?.priority ?? 1));
20362
+ const [isActive, setIsActive] = React25__default.useState(rule?.isActive ?? true);
20363
+ const [isDefault, setIsDefault] = React25__default.useState(rule?.isDefault ?? !hasDefault);
20364
+ const [saving, setSaving] = React25__default.useState(false);
20365
+ const [error, setError] = React25__default.useState(null);
20366
+ const initial = React25__default.useMemo(() => {
19588
20367
  const get = (t2, key = "ids") => rule?.criteria.find((c) => c.criteriaType === t2)?.criteriaValue?.[key] ?? [];
19589
20368
  return {
19590
20369
  services: get("service"),
@@ -19595,12 +20374,12 @@ function CommissionRuleDialog({
19595
20374
  ranges: get("time_range", "ranges")
19596
20375
  };
19597
20376
  }, [rule]);
19598
- const [services, setServices] = React23__default.useState(initial.services);
19599
- const [categories, setCategories] = React23__default.useState(initial.categories);
19600
- const [professionals, setProfessionals] = React23__default.useState(initial.professionals);
19601
- const [units, setUnits] = React23__default.useState(initial.units);
19602
- const [weekdays, setWeekdays] = React23__default.useState(initial.weekdays);
19603
- const [ranges, setRanges] = React23__default.useState(initial.ranges);
20377
+ const [services, setServices] = React25__default.useState(initial.services);
20378
+ const [categories, setCategories] = React25__default.useState(initial.categories);
20379
+ const [professionals, setProfessionals] = React25__default.useState(initial.professionals);
20380
+ const [units, setUnits] = React25__default.useState(initial.units);
20381
+ const [weekdays, setWeekdays] = React25__default.useState(initial.weekdays);
20382
+ const [ranges, setRanges] = React25__default.useState(initial.ranges);
19604
20383
  const criteriaCount = [services, categories, professionals, units].filter((a) => a.length > 0).length + (weekdays.length ? 1 : 0) + (ranges.length ? 1 : 0);
19605
20384
  async function handleSave() {
19606
20385
  if (!name.trim()) {
@@ -19843,11 +20622,11 @@ function blankFunction(order) {
19843
20622
  };
19844
20623
  }
19845
20624
  function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
19846
- const [functions, setFunctions] = React23__default.useState(
20625
+ const [functions, setFunctions] = React25__default.useState(
19847
20626
  rule.functions.length ? rule.functions : [blankFunction(0)]
19848
20627
  );
19849
- const [saving, setSaving] = React23__default.useState(false);
19850
- const [error, setError] = React23__default.useState(null);
20628
+ const [saving, setSaving] = React25__default.useState(false);
20629
+ const [error, setError] = React25__default.useState(null);
19851
20630
  function patch(i, changes) {
19852
20631
  setFunctions((fs) => fs.map((f, j) => j === i ? { ...f, ...changes } : f));
19853
20632
  }
@@ -20131,19 +20910,19 @@ function functionChip(fn) {
20131
20910
  return fn.valueType === "percentage" ? `${who}: ${fn.valueAmount}%` : `${who}: R$ ${fn.valueAmount.toFixed(2)}`;
20132
20911
  }
20133
20912
  function CommissionRulesView({ provider }) {
20134
- const [rules, setRules] = React23__default.useState(null);
20135
- const [catalogs, setCatalogs] = React23__default.useState({
20913
+ const [rules, setRules] = React25__default.useState(null);
20914
+ const [catalogs, setCatalogs] = React25__default.useState({
20136
20915
  services: [],
20137
20916
  categories: [],
20138
20917
  professionals: [],
20139
20918
  units: []
20140
20919
  });
20141
- const [error, setError] = React23__default.useState(null);
20142
- const [funnel, setFunnel] = React23__default.useState({});
20143
- const [editing, setEditing] = React23__default.useState(null);
20144
- const [functionsFor, setFunctionsFor] = React23__default.useState(null);
20145
- const [deleting, setDeleting] = React23__default.useState(null);
20146
- const load = React23__default.useCallback(async () => {
20920
+ const [error, setError] = React25__default.useState(null);
20921
+ const [funnel, setFunnel] = React25__default.useState({});
20922
+ const [editing, setEditing] = React25__default.useState(null);
20923
+ const [functionsFor, setFunctionsFor] = React25__default.useState(null);
20924
+ const [deleting, setDeleting] = React25__default.useState(null);
20925
+ const load = React25__default.useCallback(async () => {
20147
20926
  try {
20148
20927
  setError(null);
20149
20928
  const [r, c] = await Promise.all([
@@ -20157,17 +20936,17 @@ function CommissionRulesView({ provider }) {
20157
20936
  setRules([]);
20158
20937
  }
20159
20938
  }, [provider]);
20160
- React23__default.useEffect(() => {
20939
+ React25__default.useEffect(() => {
20161
20940
  void load();
20162
20941
  }, [load]);
20163
20942
  const funnelActive = Object.values(funnel).some(Boolean);
20164
- const visible = React23__default.useMemo(() => {
20943
+ const visible = React25__default.useMemo(() => {
20165
20944
  if (!rules) return [];
20166
20945
  const ranked = rankRules(rules);
20167
20946
  if (!funnelActive) return ranked;
20168
20947
  return ranked.filter((r) => r.isDefault || ruleMatches(r, funnel, catalogs));
20169
20948
  }, [rules, funnel, funnelActive, catalogs]);
20170
- const winner = React23__default.useMemo(() => {
20949
+ const winner = React25__default.useMemo(() => {
20171
20950
  if (!funnelActive) return null;
20172
20951
  return visible.find((r) => r.isActive && !r.isDefault) ?? visible.find((r) => r.isActive && r.isDefault) ?? null;
20173
20952
  }, [visible, funnelActive]);
@@ -20182,7 +20961,7 @@ function CommissionRulesView({ provider }) {
20182
20961
  await provider.duplicateCommissionRule?.(rule.id);
20183
20962
  await load();
20184
20963
  }
20185
- const headerActions = React23__default.useMemo(() => [{
20964
+ const headerActions = React25__default.useMemo(() => [{
20186
20965
  id: "new-rule",
20187
20966
  label: "Adicionar regra",
20188
20967
  icon: Plus,
@@ -20964,7 +21743,7 @@ function PersonStatementWidget({ person, provider, config }) {
20964
21743
  ] });
20965
21744
  }
20966
21745
  function SupplierFinancialSettings({ person, provider }) {
20967
- const initial = React23__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
21746
+ const initial = React25__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
20968
21747
  const [accounts, setAccounts] = useState([]);
20969
21748
  const [costCenters, setCostCenters] = useState([]);
20970
21749
  const [defaultAccountId, setDefaultAccountId] = useState(initial.defaultAccountId ?? "");
@@ -44176,6 +44955,182 @@ CREATE TRIGGER payee_accounts_one_default
44176
44955
  BEFORE INSERT OR UPDATE OF is_default ON public.payee_accounts
44177
44956
  FOR EACH ROW EXECUTE FUNCTION public.trg_payee_accounts_one_default();
44178
44957
  `;
44958
+ var MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA = `-- ---------------------------------------------------------------------------
44959
+ -- 116 \u2014 um estorno n\xE3o se estorna.
44960
+ --
44961
+ -- \`financial_reverse_movement\` aceitava qualquer \`payment\`, inclusive a linha
44962
+ -- que ela mesma lan\xE7a como contrapartida. A tela mirava \xABo \xFAltimo pagamento da
44963
+ -- parcela\xBB, que depois do primeiro estorno \xC9 o estorno: cada clique estornava o
44964
+ -- estorno anterior, o dinheiro ia e voltava, e o pagamento vivo continuava l\xE1 \u2014
44965
+ -- a conta seguia \xABpaga\xBB para sempre. Agora o banco recusa com uma mensagem que
44966
+ -- a tela mostra.
44967
+ --
44968
+ -- Idempotente: s\xF3 rep\xF5e a fun\xE7\xE3o.
44969
+ -- ---------------------------------------------------------------------------
44970
+
44971
+ CREATE OR REPLACE FUNCTION public.financial_reverse_movement(p_movement_id uuid, p_reason text, p_idempotency_key text DEFAULT NULL::text) RETURNS jsonb
44972
+ LANGUAGE plpgsql SECURITY DEFINER
44973
+ SET search_path TO ''
44974
+ AS $$
44975
+ DECLARE
44976
+ v_tenant uuid := public.plg_financial_require_tenant();
44977
+ v_m public.plg_financial_movements%ROWTYPE;
44978
+ v_rev uuid;
44979
+ BEGIN
44980
+ IF nullif(btrim(coalesce(p_reason, '')), '') IS NULL THEN
44981
+ RAISE EXCEPTION 'financial_reverse_movement: a reason is required' USING ERRCODE = '22023';
44982
+ END IF;
44983
+ SELECT * INTO v_m FROM public.plg_financial_movements WHERE id = p_movement_id AND tenant_id = v_tenant;
44984
+ IF NOT FOUND THEN
44985
+ RAISE EXCEPTION 'financial_reverse_movement: movement % not found', p_movement_id USING ERRCODE = 'P0002';
44986
+ END IF;
44987
+ PERFORM public.plg_financial_require_permission('financial.edit', v_m.unit_id);
44988
+ IF v_m.movement_kind NOT IN ('payment', 'transfer') OR v_m.debit_account_id IS NULL THEN
44989
+ RAISE EXCEPTION 'financial_reverse_movement: % is not a money event', p_movement_id USING ERRCODE = '55000';
44990
+ END IF;
44991
+ IF v_m.reverses_id IS NOT NULL THEN
44992
+ RAISE EXCEPTION 'Este lan\xE7amento j\xE1 \xE9 um estorno e n\xE3o pode ser estornado.' USING ERRCODE = '55000';
44993
+ END IF;
44994
+ IF v_m.reversed_by_id IS NOT NULL THEN
44995
+ RETURN jsonb_build_object('reversal_id', v_m.reversed_by_id, 'movement_id', v_m.id, 'replayed', true);
44996
+ END IF;
44997
+ IF p_idempotency_key IS NOT NULL THEN
44998
+ PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);
44999
+ SELECT m.id INTO v_rev FROM public.plg_financial_movements m WHERE m.tenant_id = v_tenant AND m.idempotency_key = p_idempotency_key;
45000
+ IF v_rev IS NOT NULL THEN
45001
+ RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', true);
45002
+ END IF;
45003
+ END IF;
45004
+
45005
+ INSERT INTO public.plg_financial_movements
45006
+ (tenant_id, unit_id, invoice_id, financial_invoice_id, financial_invoice_installment_id, direction, movement_kind, amount, paid_amount, status,
45007
+ due_date, payment_date, installment_number, method, cash_session_id, bank_account_id, reason, notes, metadata,
45008
+ idempotency_key, created_by, reverses_id, debit_account_id, credit_account_id)
45009
+ VALUES
45010
+ (v_m.tenant_id, v_m.unit_id, v_m.invoice_id, v_m.financial_invoice_id, v_m.financial_invoice_installment_id,
45011
+ CASE WHEN v_m.direction = 'credit' THEN 'debit' ELSE 'credit' END, v_m.movement_kind, v_m.amount, v_m.amount, 'paid',
45012
+ current_date, current_date, v_m.installment_number, v_m.method, v_m.cash_session_id, v_m.bank_account_id, btrim(p_reason),
45013
+ 'Estorno de ' || v_m.id::text,
45014
+ jsonb_build_object('source', 'financial_reverse_movement', 'reversal_of', v_m.id, 'reason', btrim(p_reason)),
45015
+ p_idempotency_key, auth.uid(), v_m.id, v_m.credit_account_id, v_m.debit_account_id)
45016
+ RETURNING id INTO v_rev;
45017
+
45018
+ UPDATE public.plg_financial_movements SET reversed_by_id = v_rev, reason = coalesce(reason, btrim(p_reason)), updated_at = now()
45019
+ WHERE id = v_m.id;
45020
+ IF v_m.financial_invoice_installment_id IS NOT NULL THEN PERFORM public.plg_financial_sync_mirror(v_m.financial_invoice_installment_id); END IF;
45021
+
45022
+ PERFORM public.plg_financial_audit(v_tenant, 'financial.movement.reverse', 'plg_financial_movements', v_m.id::text,
45023
+ jsonb_build_object('reversal_id', v_rev, 'amount', v_m.amount, 'reason', btrim(p_reason), 'idempotency_key', p_idempotency_key));
45024
+ RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', false);
45025
+ END $$;
45026
+
45027
+ `;
45028
+ var MIGRATION_117_COPYING_LINES_IS_AN_INTERNAL_STEP = `-- ---------------------------------------------------------------------------
45029
+ -- 117 \u2014 copying lines onto an invoice is an internal step, not a door.
45030
+ --
45031
+ -- Both copiers are SECURITY DEFINER with no caller check and were executable by
45032
+ -- PUBLIC (so anon too): anyone holding an invoice id could write public.items
45033
+ -- into it. Every real caller is another SECURITY DEFINER function owned by
45034
+ -- postgres (fn_invoice_from_order_internal, order_raise_receivable,
45035
+ -- financial_recurring_run, financial_consolidate_occurrence), which keeps its
45036
+ -- right to execute as owner. So the fix is the grant, not the body.
45037
+ --
45038
+ -- Idempotent: grants only.
45039
+ -- ---------------------------------------------------------------------------
45040
+
45041
+ REVOKE ALL ON FUNCTION public.plg_financial_copy_bill_items(uuid, uuid, jsonb) FROM PUBLIC, anon, authenticated;
45042
+ GRANT EXECUTE ON FUNCTION public.plg_financial_copy_bill_items(uuid, uuid, jsonb) TO service_role;
45043
+
45044
+ REVOKE ALL ON FUNCTION public.plg_financial_copy_order_lines_to_invoice(uuid, uuid) FROM PUBLIC, anon, authenticated;
45045
+ GRANT EXECUTE ON FUNCTION public.plg_financial_copy_order_lines_to_invoice(uuid, uuid) TO service_role;
45046
+ `;
45047
+ var MIGRATION_118_GIFT_CARD_FOUND_BY_EXACT_CODE = `-- 118_gift_card_found_by_exact_code.sql
45048
+ --
45049
+ -- Selling a gift card on a receivable takes the code typed at the counter and
45050
+ -- must match exactly one card. financial_gift_cards (027) matches by substring,
45051
+ -- so a partial code answered with real codes of other cards. This lookup
45052
+ -- returns the one card whose code equals the input (spaces, dots, dashes and
45053
+ -- case ignored), or nothing.
45054
+ --
45055
+ -- The shop table is optional and applied after this chain, so it is resolved
45056
+ -- at run time, as in 027.
45057
+
45058
+ CREATE OR REPLACE FUNCTION public.financial_gift_card_by_code(p_code text)
45059
+ RETURNS TABLE (
45060
+ id uuid,
45061
+ code text,
45062
+ face_value numeric,
45063
+ balance numeric,
45064
+ balance_known boolean,
45065
+ state text,
45066
+ currency text,
45067
+ issued_at timestamptz,
45068
+ expires_at timestamptz,
45069
+ activated_at timestamptz,
45070
+ note text,
45071
+ customer_email text
45072
+ )
45073
+ LANGUAGE plpgsql
45074
+ STABLE
45075
+ SECURITY DEFINER
45076
+ SET search_path TO ''
45077
+ AS $$
45078
+ DECLARE
45079
+ v_tenant uuid := app.current_tenant_id();
45080
+ v_code text := upper(regexp_replace(coalesce(p_code, ''), '[\\s.\\-]', '', 'g'));
45081
+ v_activated text;
45082
+ BEGIN
45083
+ IF v_tenant IS NULL THEN
45084
+ RAISE EXCEPTION 'financial_gift_card_by_code: sem tenant na sess\xE3o' USING ERRCODE = '42501';
45085
+ END IF;
45086
+ -- Whoever can raise a receivable can sell a card on it.
45087
+ IF NOT app.has_permission('financial.create', NULL) THEN
45088
+ RAISE EXCEPTION 'financial_gift_card_by_code: exige financial.create' USING ERRCODE = '42501';
45089
+ END IF;
45090
+ IF v_code = '' OR to_regclass('public.plg_shop_gift_cards') IS NULL THEN
45091
+ RETURN;
45092
+ END IF;
45093
+
45094
+ IF EXISTS (SELECT 1 FROM information_schema.columns c
45095
+ WHERE c.table_schema = 'public' AND c.table_name = 'plg_shop_gift_cards'
45096
+ AND c.column_name = 'activated_at') THEN
45097
+ v_activated := 'g.activated_at';
45098
+ ELSE
45099
+ v_activated :=
45100
+ 'CASE WHEN g.status = ''disabled'' AND g.balance = 0 THEN NULL'
45101
+ || ' WHEN g.updated_at > g.created_at THEN g.updated_at'
45102
+ || ' ELSE g.created_at END';
45103
+ END IF;
45104
+
45105
+ -- Same state vocabulary as financial_gift_cards (027).
45106
+ RETURN QUERY EXECUTE
45107
+ 'SELECT r.id, r.code, r.initial_balance,'
45108
+ || 'CASE WHEN r.never THEN NULL ELSE r.balance END,'
45109
+ || '(NOT r.never),'
45110
+ || 'CASE WHEN r.never THEN ''issued'''
45111
+ || ' WHEN r.balance = 0 OR r.status = ''redeemed'' THEN ''redeemed'''
45112
+ || ' WHEN r.expires_at IS NOT NULL AND r.expires_at < now() THEN ''expired'''
45113
+ || ' WHEN r.status = ''disabled'' THEN ''revoked'''
45114
+ || ' ELSE ''active'' END,'
45115
+ || 'r.currency, r.created_at, r.expires_at, r.activated_at, r.note, r.customer_email '
45116
+ || 'FROM (SELECT g.id, g.code, g.initial_balance, g.balance, g.currency, g.status,'
45117
+ || 'g.created_at, g.expires_at, g.note, g.customer_email,'
45118
+ || '(g.status = ''disabled'' AND g.balance = 0) AS never,'
45119
+ || v_activated || ' AS activated_at '
45120
+ || 'FROM public.plg_shop_gift_cards g '
45121
+ || 'WHERE g.tenant_id = $1 AND upper(g.code) = $2) r '
45122
+ || 'LIMIT 1'
45123
+ USING v_tenant, v_code;
45124
+ END $$;
45125
+
45126
+ COMMENT ON FUNCTION public.financial_gift_card_by_code(text) IS
45127
+ 'The one gift card whose code equals the input (spaces/case ignored), for selling it on a receivable. Never matches part of a code (118).';
45128
+
45129
+ REVOKE ALL ON FUNCTION public.financial_gift_card_by_code(text) FROM PUBLIC;
45130
+ REVOKE ALL ON FUNCTION public.financial_gift_card_by_code(text) FROM anon;
45131
+ GRANT EXECUTE ON FUNCTION public.financial_gift_card_by_code(text) TO authenticated;
45132
+ GRANT EXECUTE ON FUNCTION public.financial_gift_card_by_code(text) TO service_role;
45133
+ `;
44179
45134
  var MIGRATIONS = [
44180
45135
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
44181
45136
  { id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
@@ -44292,7 +45247,10 @@ var MIGRATIONS = [
44292
45247
  { id: "112_a_linha_do_extrato_nao_e_um_lancamento_ainda", sql: MIGRATION_112_A_LINHA_DO_EXTRATO_NAO_E_UM_LANCAMENTO_AINDA },
44293
45248
  { id: "113_so_a_conta_integrada_aparece_na_conciliacao", sql: MIGRATION_113_SO_A_CONTA_INTEGRADA_APARECE_NA_CONCILIACAO },
44294
45249
  { 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 }
45250
+ { id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE },
45251
+ { id: "116_um_estorno_nao_se_estorna", sql: MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA },
45252
+ { id: "117_copying_lines_is_an_internal_step", sql: MIGRATION_117_COPYING_LINES_IS_AN_INTERNAL_STEP },
45253
+ { id: "118_gift_card_found_by_exact_code", sql: MIGRATION_118_GIFT_CARD_FOUND_BY_EXACT_CODE }
44296
45254
  ];
44297
45255
  var STANDALONE_CONFIG = resolveConfig();
44298
45256
  registerTranslations(financialLocales);
@@ -44302,8 +45260,8 @@ function ReceivePaymentModal({
44302
45260
  onClose,
44303
45261
  onPaid
44304
45262
  }) {
44305
- const provider = React23__default.useMemo(() => createSafeFinancialProvider(), []);
44306
- const store6 = React23__default.useMemo(() => createFinancialStore(provider), [provider]);
45263
+ const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
45264
+ const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
44307
45265
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
44308
45266
  PaymentModal,
44309
45267
  {
@@ -44321,7 +45279,7 @@ function createFinancialSettingsPlugin(options) {
44321
45279
  ...financialRegistries.filter((r) => RECEIVING_REGISTRIES.includes(r.id)),
44322
45280
  ...options?.extraRegistries ?? []
44323
45281
  ];
44324
- const SettingsTab = () => React23__default.createElement(PluginSettingsPanel, {
45282
+ const SettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
44325
45283
  title: "Pagamentos",
44326
45284
  registries,
44327
45285
  hostPluginId: "financial",
@@ -44378,27 +45336,27 @@ function ClientAccountPanel({
44378
45336
  currency = BRL
44379
45337
  }) {
44380
45338
  const provider = useFinancialProvider();
44381
- const [account, setAccount] = React23__default.useState(null);
44382
- const [busy, setBusy] = React23__default.useState(false);
44383
- const [viewInvoiceId, setViewInvoiceId] = React23__default.useState(null);
44384
- const [comprando, setComprando] = React23__default.useState(false);
44385
- const [buscaServico, setBuscaServico] = React23__default.useState("");
44386
- const [catalogo, setCatalogo] = React23__default.useState([]);
44387
- const [escolhidos, setEscolhidos] = React23__default.useState([]);
44388
- const [comprandoBusy, setComprandoBusy] = React23__default.useState(false);
44389
- const [erroCompra, setErroCompra] = React23__default.useState(null);
44390
- const load = React23__default.useCallback(async () => {
45339
+ const [account, setAccount] = React25__default.useState(null);
45340
+ const [busy, setBusy] = React25__default.useState(false);
45341
+ const [viewInvoiceId, setViewInvoiceId] = React25__default.useState(null);
45342
+ const [comprando, setComprando] = React25__default.useState(false);
45343
+ const [buscaServico, setBuscaServico] = React25__default.useState("");
45344
+ const [catalogo, setCatalogo] = React25__default.useState([]);
45345
+ const [escolhidos, setEscolhidos] = React25__default.useState([]);
45346
+ const [comprandoBusy, setComprandoBusy] = React25__default.useState(false);
45347
+ const [erroCompra, setErroCompra] = React25__default.useState(null);
45348
+ const load = React25__default.useCallback(async () => {
44391
45349
  if (!personId) return;
44392
45350
  setAccount(await provider.getClientAccount(personId));
44393
45351
  }, [personId, provider]);
44394
- React23__default.useEffect(() => {
45352
+ React25__default.useEffect(() => {
44395
45353
  void load();
44396
45354
  }, [load]);
44397
45355
  const viewInvoice = account?.invoices.find((i) => i.id === viewInvoiceId) ?? null;
44398
45356
  const viewEditable = invoiceOpensForEditing(viewInvoice?.status);
44399
45357
  const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
44400
45358
  const hasForThis = forThis.length > 0;
44401
- React23__default.useEffect(() => {
45359
+ React25__default.useEffect(() => {
44402
45360
  if (!comprando) return;
44403
45361
  let vivo = true;
44404
45362
  const t2 = setTimeout(async () => {
@@ -44673,8 +45631,8 @@ function ClientAccountPanel({
44673
45631
  ] });
44674
45632
  }
44675
45633
  function ClientAccountInline(props) {
44676
- const provider = React23__default.useMemo(() => createSafeFinancialProvider(), []);
44677
- const store6 = React23__default.useMemo(() => createFinancialStore(provider), [provider]);
45634
+ const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
45635
+ const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
44678
45636
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(ClientAccountPanel, { ...props }) });
44679
45637
  }
44680
45638
  function ClientAccountModal({
@@ -44702,7 +45660,6 @@ function createSafeFinancialProvider() {
44702
45660
  () => createMockFinancialProvider()
44703
45661
  );
44704
45662
  }
44705
- var ACCOUNTS_ROUTE = "/registry/accounts";
44706
45663
  var FINANCIAL_NAV_FEATURES = {
44707
45664
  reconciliation: "fin_reconciliation",
44708
45665
  cards: "fin_cards",
@@ -44723,11 +45680,11 @@ function createFinancialPlugin(options) {
44723
45680
  const settingsRegistries = registries.filter((r) => r.id !== "bank-accounts");
44724
45681
  const AccountsRegistryPage = createCrudPage(financialAccountEntity, { feature: "financial" });
44725
45682
  AccountsRegistryPage.displayName = "FinancialAccountsRegistryPage";
44726
- const PersonStatementBound = (props) => React23__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
45683
+ const PersonStatementBound = (props) => React25__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
44727
45684
  PersonStatementBound.displayName = "FinancialPersonStatement";
44728
- const SupplierFinancialSettingsBound = (props) => React23__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
45685
+ const SupplierFinancialSettingsBound = (props) => React25__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
44729
45686
  SupplierFinancialSettingsBound.displayName = "FinancialSupplierSettings";
44730
- const PageComponent = () => React23__default.createElement(FinancialPage, { config, provider, store: store6, registries });
45687
+ const PageComponent = () => React25__default.createElement(FinancialPage, { config, provider, store: store6, registries });
44731
45688
  const AgentMount = createFinancialAgentMount({ config, provider });
44732
45689
  return {
44733
45690
  id: "financial",
@@ -44785,7 +45742,7 @@ function createFinancialPlugin(options) {
44785
45742
  // tem — lá é um ícone no cabeçalho, de qualquer tela.
44786
45743
  headerControls: config.modules.cashRegisters ? [{
44787
45744
  id: "financial.cash-session",
44788
- component: (() => React23__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
45745
+ component: (() => React25__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
44789
45746
  order: 90,
44790
45747
  permission: { feature: "financial", action: "read" }
44791
45748
  }] : [],
@@ -45016,9 +45973,9 @@ function createFinancialPlugin(options) {
45016
45973
  // lado, e a corrida de "Plugins" não é partida ao meio.
45017
45974
  group: "Financeiro",
45018
45975
  component: (() => {
45019
- const FinancialSettingsTab = () => React23__default.createElement(PluginSettingsPanel, {
45976
+ const FinancialSettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
45020
45977
  title: "Financial Settings",
45021
- generalSettings: React23__default.createElement(FinancialGeneralSettings, { config }),
45978
+ generalSettings: React25__default.createElement(FinancialGeneralSettings, { config }),
45022
45979
  registries: settingsRegistries,
45023
45980
  hostPluginId: "financial",
45024
45981
  routeBase: "/settings/financial"
@@ -45043,7 +46000,7 @@ function createFinancialPlugin(options) {
45043
46000
  icon: "HandCoins",
45044
46001
  group: "Financeiro",
45045
46002
  component: (() => {
45046
- const CommissionRulesTab = () => React23__default.createElement(CommissionRulesView, { provider });
46003
+ const CommissionRulesTab = () => React25__default.createElement(CommissionRulesView, { provider });
45047
46004
  CommissionRulesTab.displayName = "CommissionRulesTab";
45048
46005
  return CommissionRulesTab;
45049
46006
  })(),