@fayz-ai/plugin-financial 0.19.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) 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/data/supabase.d.ts.map +1 -1
  13. package/dist/data/types.d.ts +2 -0
  14. package/dist/data/types.d.ts.map +1 -1
  15. package/dist/index.js +887 -571
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/settlements.d.ts +6 -0
  18. package/dist/lib/settlements.d.ts.map +1 -1
  19. package/dist/locales/en.d.ts.map +1 -1
  20. package/dist/locales/pt-BR.d.ts.map +1 -1
  21. package/dist/migrations/index.d.ts +1 -0
  22. package/dist/migrations/index.d.ts.map +1 -1
  23. package/dist/views/FinancialPage.d.ts.map +1 -1
  24. package/dist/views/GapScreens.d.ts +0 -2
  25. package/dist/views/GapScreens.d.ts.map +1 -1
  26. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  27. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  28. package/dist/views/InvoiceListView.d.ts.map +1 -1
  29. package/dist/views/cardChip.d.ts +1 -1
  30. package/dist/views/cardChip.d.ts.map +1 -1
  31. package/package.json +4 -4
  32. package/dist/components/AttachmentViewer.d.ts +0 -8
  33. 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';
1
+ import * as React25 from 'react';
2
+ import React25__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
3
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';
4
+ import { INLINE_LINK_TONE, SearchSelect, ModalTask, toast, Button, useInModalTaskHost, useSaveBar, tourAnchor, DatePicker, SubpageHeader, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, Card, CardContent, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MultiSelect, PageTransition, ModulePage, Badge, TooltipProvider, ConfirmDialog, CurrencyInput, Input, PageHeaderActions, Hint, ModalFooter, PageTabs, Tooltip, TooltipTrigger, TooltipContent, Checkbox, Popover, PopoverTrigger, PopoverContent, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, SegmentedControl, RequiredFilter, FilterMenuRow, ModalFlow, Switch, WizardSteps, KpiCard, ChartWidget, CardHeader, CardTitle, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, 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, OrderFormAddItemStep, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, useChatStore, adminNavigateTo, revealPage, useAuth, GetStarted, resolveEntityRoute, PermissionGate, createOneShotAsk, askAI, requestChatUpload, toInlineFile, shrinkForVision } from '@fayz-ai/admin';
7
+ import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Receipt, Gift, Briefcase, Package, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, Ticket, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Undo2, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, ChevronRight, Building2, ChevronDown, Image, UploadCloud, Calendar, User, Unlink, Eye, Inbox, Clock, ClipboardCheck, BookOpen, Info, FolderOpen, Folder, RotateCcw, ArrowLeft, ChevronLeft, Calculator, Square, Play, ChevronUp, Printer, CalendarClock, BadgeCheck, Target, Settings, ArrowRight, Users, RefreshCw, Download, Camera, Sparkles, Link2, FileSearch, CheckCheck, Pause, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, FileDown, ArrowUpRight, ArrowDownRight, EyeOff } from 'lucide-react';
8
8
  import { createPortal } from 'react-dom';
9
9
  import { createStore as createStore$1 } from 'zustand/vanilla';
10
10
  import { useStore } from 'zustand';
@@ -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(
@@ -2695,7 +2759,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2695
2759
  },
2696
2760
  ...onNewReceiptGated ? [{
2697
2761
  id: "receipt",
2698
- label: t2("financial.quickTx.sendReceipt"),
2762
+ // Same flow for payables and receivables: the photo is the request and
2763
+ // the assistant returns the form filled in.
2764
+ label: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
2699
2765
  description: t2("financial.quickTx.receiptCapture"),
2700
2766
  icon: Camera,
2701
2767
  intent: "secondary",
@@ -2705,10 +2771,20 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2705
2771
  // preenchido. Ver receipt-agent.ts.
2706
2772
  onSelect: () => startReceiptCapture({
2707
2773
  ask: t2("financial.receipt.ask"),
2708
- uploadLabel: t2("financial.quickTx.sendReceipt"),
2774
+ uploadLabel: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
2709
2775
  uploadHint: t2("financial.quickTx.receiptCapture"),
2710
2776
  prompt: t2("financial.receipt.prompt")
2711
2777
  })
2778
+ }] : [],
2779
+ ...direction === "debit" ? [{
2780
+ id: "payroll",
2781
+ label: t2("financial.invoice.importPayroll"),
2782
+ description: t2("financial.invoice.importPayrollSoon"),
2783
+ icon: Users,
2784
+ intent: "secondary",
2785
+ disabled: true,
2786
+ onSelect: () => {
2787
+ }
2712
2788
  }] : []
2713
2789
  ] : [];
2714
2790
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
@@ -2738,13 +2814,14 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
2738
2814
  onEmptyAction: onNewGated
2739
2815
  }
2740
2816
  ),
2741
- viewer && /* @__PURE__ */ jsx(
2742
- AttachmentViewer,
2817
+ attachmentsOf && /* @__PURE__ */ jsx(
2818
+ InvoiceAttachmentsModal,
2743
2819
  {
2744
- attachments: viewer.attachments,
2745
- index: viewer.index,
2746
- onIndexChange: (index) => setViewer((v) => v ? { ...v, index } : v),
2747
- onClose: () => setViewer(null)
2820
+ invoiceId: attachmentsOf,
2821
+ onClose: () => setAttachmentsOf(null),
2822
+ onChanged: () => {
2823
+ void fetchInvoices(query);
2824
+ }
2748
2825
  }
2749
2826
  ),
2750
2827
  filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
@@ -3825,9 +3902,9 @@ var SUBMIT_DRAFT = {
3825
3902
  parameters: { type: "object", properties: {} }
3826
3903
  };
3827
3904
  function PayableDraftSurface({ config, draft }) {
3828
- const live = React23.useRef(draft);
3905
+ const live = React25.useRef(draft);
3829
3906
  live.current = draft;
3830
- const lookup = React23.useMemo(
3907
+ const lookup = React25.useMemo(
3831
3908
  () => config.contactLookup ?? createArchetypeLookup({
3832
3909
  archetype: "person",
3833
3910
  kind: "supplier",
@@ -3836,7 +3913,7 @@ function PayableDraftSurface({ config, draft }) {
3836
3913
  }),
3837
3914
  [config.contactLookup]
3838
3915
  );
3839
- React23.useEffect(() => {
3916
+ React25.useEffect(() => {
3840
3917
  liveDraft = {
3841
3918
  describe: () => live.current.describe(),
3842
3919
  setSupplier: (name, id) => live.current.setSupplier(name, id),
@@ -3847,7 +3924,7 @@ function PayableDraftSurface({ config, draft }) {
3847
3924
  liveDraft = null;
3848
3925
  };
3849
3926
  }, []);
3850
- const contribution = React23.useMemo(() => ({
3927
+ const contribution = React25.useMemo(() => ({
3851
3928
  id: "financial.payables.draft",
3852
3929
  pluginId: "financial",
3853
3930
  role: "finance",
@@ -3922,7 +3999,7 @@ function PayableDraftSurface({ config, draft }) {
3922
3999
  }
3923
4000
  function createFinancialAgentMount(deps) {
3924
4001
  const FinancialAgentMount = () => {
3925
- const contribution = React23.useMemo(() => ({
4002
+ const contribution = React25.useMemo(() => ({
3926
4003
  id: "financial.payables.global",
3927
4004
  pluginId: "financial",
3928
4005
  role: "finance",
@@ -4017,13 +4094,24 @@ function CardVisual({
4017
4094
  lastFour: lastFour2,
4018
4095
  color,
4019
4096
  brandId,
4020
- size = "sm"
4097
+ size: size2 = "sm"
4021
4098
  }) {
4022
4099
  const brand = useBrandName(brandId);
4023
4100
  const last = (lastFour2 ?? "").trim();
4024
4101
  if (!last && !color && !brand) return null;
4025
4102
  const bg = color && /^#[0-9a-fA-F]{6}$/.test(color) ? color : void 0;
4026
- const lg = size === "lg";
4103
+ const lg = size2 === "lg";
4104
+ if (size2 === "xs") {
4105
+ return /* @__PURE__ */ jsx(
4106
+ "span",
4107
+ {
4108
+ className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[3px] border align-middle text-[6px] font-semibold leading-none tabular-nums",
4109
+ style: bg ? { backgroundColor: bg, color: ink(bg), borderColor: "transparent" } : void 0,
4110
+ title: [brand, last && `\u2022\u2022\u2022\u2022 ${last}`].filter(Boolean).join(" "),
4111
+ children: last
4112
+ }
4113
+ );
4114
+ }
4027
4115
  return /* @__PURE__ */ jsxs(
4028
4116
  "span",
4029
4117
  {
@@ -4093,7 +4181,9 @@ function eligibleAccountsFor(bankAccounts, direction, type) {
4093
4181
  const active = bankAccounts.filter((a) => a.isActive !== false);
4094
4182
  if (direction === "debit") {
4095
4183
  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));
4184
+ const own = (type?.allowedAccountTypes?.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType])?.filter((k) => !k.startsWith("card_"));
4185
+ const kinds = own?.length ? own : ["bank", "cash_physical", "cash_session"];
4186
+ return active.filter((a) => kinds.includes(a.accountType));
4097
4187
  }
4098
4188
  const allowed = type?.allowedAccountTypes && type.allowedAccountTypes.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType];
4099
4189
  if (!allowed || !allowed.length) return bankAccounts;
@@ -4415,10 +4505,10 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
4415
4505
  const payMovement = useFinancialStore((s) => s.payMovement);
4416
4506
  const remaining = movement.amount - movement.paidAmount;
4417
4507
  const [saving, setSaving] = useState(false);
4418
- const command = React23__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
4508
+ const command = React25__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
4419
4509
  const [pending2, setPending] = useState(false);
4420
4510
  const [visible, setVisible] = useState(false);
4421
- React23__default.useEffect(() => {
4511
+ React25__default.useEffect(() => {
4422
4512
  requestAnimationFrame(() => setVisible(true));
4423
4513
  }, []);
4424
4514
  const source = movement.metadata ?? {};
@@ -4537,6 +4627,32 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
4537
4627
  document.body
4538
4628
  );
4539
4629
  }
4630
+ var METHOD_ICONS = {
4631
+ cash: Banknote,
4632
+ pix: QrCode,
4633
+ credit_card: CreditCard,
4634
+ debit_card: CreditCard,
4635
+ bank_transfer: ArrowRightLeft,
4636
+ check: FileCheck,
4637
+ boleto: Receipt,
4638
+ voucher: Ticket
4639
+ };
4640
+ function MethodIcon({ type }) {
4641
+ const Icon = METHOD_ICONS[type?.transactionType ?? ""] ?? Wallet;
4642
+ return /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4643
+ }
4644
+ function AccountIcon({ account }) {
4645
+ if (!account) return null;
4646
+ if (account.accountType === "card_payable" && (account.lastFour || account.color || account.cardBrandId)) {
4647
+ return /* @__PURE__ */ jsx(CardVisual, { size: "xs", lastFour: account.lastFour ?? void 0, color: account.color ?? void 0, brandId: account.cardBrandId ?? void 0 });
4648
+ }
4649
+ if (account.accountType.startsWith("card_")) return /* @__PURE__ */ jsx(CreditCard, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4650
+ if (account.accountType === "cash_physical" || account.accountType === "cash_session") {
4651
+ return /* @__PURE__ */ jsx(Banknote, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4652
+ }
4653
+ const bank = findBrBank(account.bankCode, account.bankName ?? account.name);
4654
+ return bank?.logo ? /* @__PURE__ */ jsx(BankLogo, { bank, size: 16 }) : /* @__PURE__ */ jsx(Landmark, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
4655
+ }
4540
4656
  var NONE = "__none__";
4541
4657
  var TRIGGER_CLASS = `h-auto w-auto justify-start gap-1 rounded-none border-0 bg-transparent p-0 text-xs font-normal shadow-none ring-offset-0 focus:ring-0 focus:ring-offset-0 [&>svg]:h-3 [&>svg]:w-3 ${INLINE_LINK_TONE.dashed}`;
4542
4658
  function InstallmentSourceFields({
@@ -4559,7 +4675,7 @@ function InstallmentSourceFields({
4559
4675
  onChange({ payMethodTypeId: typeId || void 0, payAccountId: typeId ? conta : void 0 });
4560
4676
  }
4561
4677
  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: [
4678
+ return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-start gap-0.5", children: [
4563
4679
  /* @__PURE__ */ jsxs(
4564
4680
  Select,
4565
4681
  {
@@ -4576,37 +4692,202 @@ function InstallmentSourceFields({
4576
4692
  }
4577
4693
  ),
4578
4694
  /* @__PURE__ */ jsxs(SelectContent, { children: [
4579
- paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: tp.name }, tp.id)),
4695
+ paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
4696
+ /* @__PURE__ */ jsx(MethodIcon, { type: tp }),
4697
+ tp.name
4698
+ ] }) }, tp.id)),
4580
4699
  value.payMethodTypeId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
4581
4700
  ] })
4582
4701
  ]
4583
4702
  }
4584
4703
  ),
4585
- /* @__PURE__ */ jsxs(
4586
- Select,
4704
+ /* @__PURE__ */ jsx(
4705
+ AccountPicker,
4587
4706
  {
4588
- value: value.payAccountId ?? "",
4707
+ accounts,
4708
+ value: value.payAccountId,
4589
4709
  disabled: disabled || !value.payMethodTypeId,
4590
- onValueChange: (raw) => onChange({ ...value, payAccountId: raw === NONE ? void 0 : raw }),
4710
+ title: value.payMethodTypeId ? void 0 : t2("financial.invoiceForm.selectMethodFirst"),
4711
+ placeholder: t2("financial.payment.account"),
4712
+ ariaLabel: t2("financial.invoiceForm.paidFrom"),
4713
+ clearLabel: t2("financial.invoiceForm.clear"),
4714
+ className: `${INLINE_LINK_TONE.dashed} ${tone(!!selectedAccount)}`,
4715
+ onChange: (id) => onChange({ ...value, payAccountId: id })
4716
+ }
4717
+ )
4718
+ ] });
4719
+ }
4720
+ var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
4721
+ function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className }) {
4722
+ const [open, setOpen] = React25__default.useState(false);
4723
+ const [q, setQ] = React25__default.useState("");
4724
+ const selected = accounts.find((a) => a.id === value);
4725
+ const list = q ? accounts.filter((a) => norm2(a.name).includes(norm2(q))) : accounts;
4726
+ const pick = (id) => {
4727
+ onChange(id);
4728
+ setOpen(false);
4729
+ setQ("");
4730
+ };
4731
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (o) => {
4732
+ setOpen(o);
4733
+ if (!o) setQ("");
4734
+ }, children: [
4735
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
4736
+ "button",
4737
+ {
4738
+ type: "button",
4739
+ disabled,
4740
+ title,
4741
+ "aria-label": ariaLabel,
4742
+ className: `inline-flex max-w-full items-center gap-1 text-xs disabled:cursor-not-allowed disabled:opacity-50 ${className ?? ""}`,
4591
4743
  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
- ] })
4744
+ selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
4745
+ /* @__PURE__ */ jsx(AccountIcon, { account: selected }),
4746
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selected.name })
4747
+ ] }) : placeholder,
4748
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true })
4605
4749
  ]
4606
4750
  }
4607
- )
4751
+ ) }),
4752
+ /* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-64 p-1", children: [
4753
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-1", children: [
4754
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground", "aria-hidden": true }),
4755
+ /* @__PURE__ */ jsx(
4756
+ "input",
4757
+ {
4758
+ autoFocus: true,
4759
+ value: q,
4760
+ onChange: (e) => setQ(e.target.value),
4761
+ onKeyDown: (e) => {
4762
+ if (e.key === "Enter" && list[0]) {
4763
+ e.preventDefault();
4764
+ pick(list[0].id);
4765
+ }
4766
+ },
4767
+ placeholder: "Buscar conta\u2026",
4768
+ className: "h-8 w-full rounded-md border-0 bg-muted/50 pl-7 pr-2 text-xs outline-none focus:ring-0"
4769
+ }
4770
+ )
4771
+ ] }),
4772
+ /* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto", children: list.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: "Nenhuma conta encontrada" }) : list.map((a) => /* @__PURE__ */ jsxs(
4773
+ "button",
4774
+ {
4775
+ type: "button",
4776
+ onClick: () => pick(a.id),
4777
+ className: `flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted ${a.id === value ? "font-medium" : ""}`,
4778
+ children: [
4779
+ /* @__PURE__ */ jsx(AccountIcon, { account: a }),
4780
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: a.name }),
4781
+ a.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
4782
+ ]
4783
+ },
4784
+ a.id
4785
+ )) }),
4786
+ value && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => pick(void 0), className: "mt-1 w-full rounded-md px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted", children: clearLabel })
4787
+ ] })
4608
4788
  ] });
4609
4789
  }
4790
+ var STATUS = {
4791
+ open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
4792
+ pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
4793
+ partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial" },
4794
+ paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid" },
4795
+ overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue" },
4796
+ draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
4797
+ cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
4798
+ };
4799
+ function InstallmentStatusBadge({ status }) {
4800
+ const t2 = useTranslation();
4801
+ const s = STATUS[status ?? ""] ?? STATUS.pending;
4802
+ const Icon = s.icon;
4803
+ return /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${s.bg}`, children: [
4804
+ /* @__PURE__ */ jsx(Icon, { className: "h-2 w-2" }),
4805
+ " ",
4806
+ t2(s.labelKey)
4807
+ ] });
4808
+ }
4809
+ function InstallmentRow({
4810
+ number,
4811
+ amount,
4812
+ status,
4813
+ dueDate,
4814
+ onDueDateChange,
4815
+ dueExtra,
4816
+ source,
4817
+ actions,
4818
+ onClick
4819
+ }) {
4820
+ const t2 = useTranslation();
4821
+ const stop = (e) => e.stopPropagation();
4822
+ return /* @__PURE__ */ jsxs(
4823
+ "div",
4824
+ {
4825
+ className: `flex flex-wrap items-center gap-3 px-4 py-3 ${onClick ? "cursor-pointer transition-colors hover:bg-muted/20" : ""}`,
4826
+ onClick,
4827
+ children: [
4828
+ /* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
4829
+ "#",
4830
+ number
4831
+ ] }),
4832
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-start gap-0.5", children: [
4833
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4834
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: amount }),
4835
+ status && /* @__PURE__ */ jsx(InstallmentStatusBadge, { status })
4836
+ ] }),
4837
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground", onClick: stop, children: [
4838
+ t2("financial.payment.due"),
4839
+ onDueDateChange ? /* @__PURE__ */ jsx(DatePicker, { value: dueDate, variant: "inline", format: "numeric", onChange: onDueDateChange }) : dueDate,
4840
+ dueExtra
4841
+ ] })
4842
+ ] }),
4843
+ /* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", onClick: stop, children: source }),
4844
+ (actions || onClick) && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", onClick: stop, children: [
4845
+ actions,
4846
+ onClick && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
4847
+ ] })
4848
+ ]
4849
+ }
4850
+ );
4851
+ }
4852
+ var BTN = "inline-flex shrink-0 items-center gap-1 px-3 py-1.5 text-xs font-medium transition-colors active:shadow-button-inset disabled:pointer-events-none disabled:opacity-40";
4853
+ function InstallmentPayButton({ onClick, disabled, title, children, variant = "primary" }) {
4854
+ return /* @__PURE__ */ jsxs(
4855
+ "button",
4856
+ {
4857
+ type: "button",
4858
+ onClick,
4859
+ disabled,
4860
+ title,
4861
+ className: variant === "primary" ? `${BTN} rounded-button border border-primary bg-primary text-primary-foreground shadow-button-primary hover:bg-primary/90` : `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`,
4862
+ children: [
4863
+ /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
4864
+ children
4865
+ ]
4866
+ }
4867
+ );
4868
+ }
4869
+ function InstallmentActionButton({ onClick, icon: Icon, children }) {
4870
+ return /* @__PURE__ */ jsxs("button", { type: "button", onClick, className: `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`, children: [
4871
+ /* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
4872
+ children
4873
+ ] });
4874
+ }
4875
+ function InstallmentReceiptButton({ onClick, title, attached, pending: pending2, count }) {
4876
+ return /* @__PURE__ */ jsxs(
4877
+ "button",
4878
+ {
4879
+ type: "button",
4880
+ onClick,
4881
+ title,
4882
+ "aria-label": title,
4883
+ className: `inline-flex shrink-0 items-center gap-1 rounded-lg border bg-card px-2.5 py-1.5 text-xs font-medium shadow-button transition-colors hover:bg-muted active:shadow-button-inset ${attached ? "text-success" : pending2 ? "text-primary" : ""}`,
4884
+ children: [
4885
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3" }),
4886
+ !!count && /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: count })
4887
+ ]
4888
+ }
4889
+ );
4890
+ }
4610
4891
 
4611
4892
  // src/views/focusItem.ts
4612
4893
  var CHAVE = "fayz:financial:focus-item";
@@ -4631,9 +4912,9 @@ function consumirItemParaAbrir(invoiceId) {
4631
4912
  }
4632
4913
  }
4633
4914
  function useStockedItemTypes(itemTypes, entityLookups) {
4634
- const [empty, setEmpty] = React23__default.useState(null);
4915
+ const [empty, setEmpty] = React25__default.useState(null);
4635
4916
  const backed = itemTypes.filter((type) => entityLookups[type.value]?.count).map((type) => type.value).sort().join(",");
4636
- React23__default.useEffect(() => {
4917
+ React25__default.useEffect(() => {
4637
4918
  const kinds = backed ? backed.split(",") : [];
4638
4919
  if (!kinds.length) {
4639
4920
  setEmpty(null);
@@ -4654,7 +4935,7 @@ function useStockedItemTypes(itemTypes, entityLookups) {
4654
4935
  cancelled = true;
4655
4936
  };
4656
4937
  }, [backed]);
4657
- return React23__default.useMemo(
4938
+ return React25__default.useMemo(
4658
4939
  () => empty?.size ? itemTypes.filter((type) => !empty.has(type.value)) : itemTypes,
4659
4940
  [itemTypes, empty]
4660
4941
  );
@@ -4902,14 +5183,14 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4902
5183
  };
4903
5184
  }, [giftCardsEnabled, provider]);
4904
5185
  const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
4905
- const translatedItemTypes = React23__default.useMemo(
5186
+ const translatedItemTypes = React25__default.useMemo(
4906
5187
  () => itemTypesRaw.map((tp) => {
4907
5188
  const traduzido = t2(`financial.itemType.${tp.value}`);
4908
5189
  return traduzido === `financial.itemType.${tp.value}` ? tp : { ...tp, label: traduzido };
4909
5190
  }),
4910
5191
  [itemTypesRaw, t2]
4911
5192
  );
4912
- const offeredItemTypes = React23__default.useMemo(
5193
+ const offeredItemTypes = React25__default.useMemo(
4913
5194
  () => availableInvoiceItemTypes(
4914
5195
  giftCardsEnabled && !translatedItemTypes.some((type) => type.value === "gift_card") ? [...translatedItemTypes, { value: "gift_card", label: t2("financial.itemType.gift_card"), icon: "Gift" }] : translatedItemTypes,
4915
5196
  Object.keys(entityLookups),
@@ -4930,6 +5211,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4930
5211
  const guardMovements = useLimitGuard("movements_month");
4931
5212
  const can = usePermissionOptional();
4932
5213
  const isEdit = !!editId;
5214
+ const [attachmentsOpen, setAttachmentsOpen] = useState(false);
4933
5215
  useEffect(() => {
4934
5216
  void fetchBankAccounts();
4935
5217
  void fetchPaymentMethods();
@@ -5205,13 +5487,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5205
5487
  if (isEdit || totalAmount <= 0 || installments.length > 0) return;
5206
5488
  generateInstallments(1);
5207
5489
  }, [isEdit, totalAmount, installments.length]);
5208
- const fromCard = React23__default.useMemo(
5490
+ const fromCard = React25__default.useMemo(
5209
5491
  () => fromCardId ? bankAccounts.find((a) => a.id === fromCardId) : void 0,
5210
5492
  [fromCardId, bankAccounts]
5211
5493
  );
5212
5494
  const cardLast4 = (fromCard?.accountNumber ?? "").replace(/\D/g, "").slice(-4);
5213
5495
  const nomeJaTemOsDigitos = !!cardLast4 && (fromCard?.name ?? "").replace(/\D/g, "").endsWith(cardLast4);
5214
- const cardMethodName = React23__default.useMemo(() => {
5496
+ const cardMethodName = React25__default.useMemo(() => {
5215
5497
  const id = installments[0]?.payMethodTypeId;
5216
5498
  const escolhida = id ? paymentMethodTypes.find((tp) => tp.id === id) : void 0;
5217
5499
  return (escolhida ?? paymentMethodTypes.find((tp) => tp.transactionType === "credit_card"))?.name;
@@ -5532,7 +5814,6 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5532
5814
  const saved = savedMovements[i];
5533
5815
  const isPaid2 = saved?.status === "paid";
5534
5816
  const isPartial = saved?.status === "partial";
5535
- const StatusIcon = isPaid2 ? CircleCheckBig : CircleDashed;
5536
5817
  const canAttach = isEdit ? !!saved && !isPaid2 : true;
5537
5818
  const semFonte = Math.abs(inst.amount) < 5e-3 && !isPaid2 && !isPartial;
5538
5819
  const fonteTravada = isPaid2 || isPartial;
@@ -5540,75 +5821,61 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5540
5821
  const contaEscolhida = bankAccounts.find((a) => a.id === inst.payAccountId);
5541
5822
  const faltaParaPagar = !inst.payMethodTypeId ? t2("financial.payment.methodRequired") : !inst.payAccountId ? t2("financial.payment.accountRequired") : null;
5542
5823
  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,
5568
- {
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",
5824
+ /* @__PURE__ */ jsx(
5825
+ InstallmentRow,
5826
+ {
5827
+ number: i + 1,
5828
+ amount: money4(inst.amount),
5829
+ status: saved?.status,
5830
+ dueDate: inst.dueDate,
5831
+ onDueDateChange: !hasPayments ? (v) => patch({ dueDate: v }) : void 0,
5832
+ source: semFonte ? (
5833
+ // A zero-value line (gift, 100% discount) has no method to ask for.
5834
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" })
5835
+ ) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
5836
+ InstallmentSourceFields,
5577
5837
  {
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" })
5838
+ direction,
5839
+ value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
5840
+ onChange: (next) => patch(next)
5592
5841
  }
5593
5842
  ),
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
- ] }),
5843
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
5844
+ (canAttach || isEdit) && /* @__PURE__ */ jsx(
5845
+ InstallmentReceiptButton,
5846
+ {
5847
+ attached: !!inst.receiptUrl,
5848
+ pending: !inst.receiptUrl && !!pendingReceipt,
5849
+ title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
5850
+ onClick: () => {
5851
+ if (isEdit && editId) {
5852
+ setAttachmentsOpen(true);
5853
+ return;
5854
+ }
5855
+ if (!inst.receiptUrl && pendingReceipt) {
5856
+ patch({ receiptUrl: pendingReceipt });
5857
+ setPendingReceipt(void 0);
5858
+ return;
5859
+ }
5860
+ setReceiptTarget(i);
5861
+ receiptPicker.open();
5862
+ }
5863
+ }
5864
+ ),
5865
+ !isPaid2 && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsx(
5866
+ InstallmentPayButton,
5867
+ {
5868
+ disabled: saving || !!faltaParaPagar,
5869
+ title: faltaParaPagar ?? void 0,
5870
+ onClick: () => {
5871
+ void pagarParcela(i);
5872
+ },
5873
+ children: t2("financial.invoice.pay")
5874
+ }
5875
+ )
5876
+ ] })
5877
+ }
5878
+ ),
5612
5879
  !isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
5613
5880
  /* @__PURE__ */ jsx(
5614
5881
  "img",
@@ -5920,6 +6187,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
5920
6187
  }
5921
6188
  ),
5922
6189
  receiptPicker.input,
6190
+ attachmentsOpen && editId && /* @__PURE__ */ jsx(InvoiceAttachmentsModal, { invoiceId: editId, onClose: () => setAttachmentsOpen(false) }),
5923
6191
  payingMovement && /* @__PURE__ */ jsx(
5924
6192
  PaymentModal,
5925
6193
  {
@@ -6015,6 +6283,11 @@ function groupPaymentsByBill(bills, payments) {
6015
6283
  }
6016
6284
  return { byBillId, unattributed };
6017
6285
  }
6286
+ var reversalOf = (m) => m.metadata?.reversal_of;
6287
+ function reversiblePayments(payments) {
6288
+ const reversed = new Set(payments.map(reversalOf).filter(Boolean));
6289
+ return payments.filter((m) => !reversalOf(m) && !reversed.has(m.id));
6290
+ }
6018
6291
 
6019
6292
  // src/lib/payment-how.ts
6020
6293
  var NOT_A_HOLDER = /* @__PURE__ */ new Set(["person", "system"]);
@@ -6062,6 +6335,7 @@ function PaymentReceiptModal({
6062
6335
  const { currency } = useFinancialConfig();
6063
6336
  const fmt = (n) => formatCurrency(n, currency);
6064
6337
  const ordenados = [...payments].sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
6338
+ const estornaveis = new Set(reversiblePayments(ordenados).map((m) => m.id));
6065
6339
  const total = ordenados.reduce((soma, m) => soma + (ehEstorno(m) ? -1 : 1) * (m.paidAmount || m.amount), 0);
6066
6340
  return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
6067
6341
  if (!o) onClose();
@@ -6101,7 +6375,7 @@ function PaymentReceiptModal({
6101
6375
  ] }),
6102
6376
  /* @__PURE__ */ jsx("span", { className: "text-xs", children: payment.notes })
6103
6377
  ] }),
6104
- onReverse && !estorno && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
6378
+ onReverse && estornaveis.has(payment.id) && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
6105
6379
  /* @__PURE__ */ jsx(Hint, { label: t2("financial.paymentReceipt.unlinkUnavailable"), children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "w-full", disabled: true, children: [
6106
6380
  /* @__PURE__ */ jsx(Unlink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
6107
6381
  t2("financial.paymentReceipt.unlink")
@@ -6133,14 +6407,6 @@ var ITEM_ICONS = {
6133
6407
  gift_card: Gift,
6134
6408
  other: Receipt
6135
6409
  };
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
6410
  var STATUS_COLORS = {
6145
6411
  open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
6146
6412
  pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
@@ -6174,7 +6440,6 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6174
6440
  const unitName = useUnitStore((st) => unitMode && invoice?.unitId ? st.byId[invoice.unitId]?.name : void 0);
6175
6441
  const [items, setItems] = useState([]);
6176
6442
  const [attachments, setAttachments] = useState([]);
6177
- const [viewing, setViewing] = useState(null);
6178
6443
  const [movements, setMovements] = useState([]);
6179
6444
  const [payments, setPayments] = useState([]);
6180
6445
  const [holders, setHolders] = useState(/* @__PURE__ */ new Map());
@@ -6184,13 +6449,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6184
6449
  const [receiptBill, setReceiptBill] = useState(null);
6185
6450
  const [reverseReason, setReverseReason] = useState("");
6186
6451
  const [payingMovement, setPayingMovement] = useState(null);
6187
- const [receiptFor, setReceiptFor] = useState(null);
6452
+ const [attachmentsOpen, setAttachmentsOpen] = useState(false);
6188
6453
  const [pendingReceipt, setPendingReceipt] = useState();
6189
- const receiptPicker = useReceiptPicker((dataUrl) => {
6190
- setPendingReceipt(dataUrl);
6191
- setPayingMovement(receiptFor);
6192
- setReceiptFor(null);
6193
- });
6194
6454
  const [menuOpen, setMenuOpen] = useState(false);
6195
6455
  const [confirmCancel, setConfirmCancel] = useState(false);
6196
6456
  const [cancelling, setCancelling] = useState(false);
@@ -6221,6 +6481,17 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6221
6481
  void loadData();
6222
6482
  }
6223
6483
  }
6484
+ async function patchInvoiceDate(invoiceDate) {
6485
+ if (!invoice || !invoiceDate || invoiceDate === invoice.invoiceDate) return;
6486
+ const antes = invoice;
6487
+ setInvoice({ ...invoice, invoiceDate });
6488
+ try {
6489
+ await provider.updateInvoice(invoice.id, { invoiceDate });
6490
+ } catch (e) {
6491
+ setInvoice(antes);
6492
+ toast.error(e?.message ?? String(e));
6493
+ }
6494
+ }
6224
6495
  const reqRef = useRef(0);
6225
6496
  async function loadData() {
6226
6497
  const req = ++reqRef.current;
@@ -6311,8 +6582,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6311
6582
  setCancelling(false);
6312
6583
  }
6313
6584
  }
6314
- const [chartNames, setChartNames] = React23__default.useState(/* @__PURE__ */ new Map());
6315
- React23__default.useEffect(() => {
6585
+ const [chartNames, setChartNames] = React25__default.useState(/* @__PURE__ */ new Map());
6586
+ React25__default.useEffect(() => {
6316
6587
  let cancelled2 = false;
6317
6588
  void provider.getChartOfAccounts().then((rows) => {
6318
6589
  if (!cancelled2) setChartNames(new Map(rows.map((r) => [r.id, r.name])));
@@ -6432,7 +6703,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6432
6703
  { id: "total", label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
6433
6704
  { id: "items", label: t2("financial.invoice.items"), value: items.length, icon: LayoutList },
6434
6705
  { 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 }
6706
+ {
6707
+ id: "date",
6708
+ label: t2("financial.invoice.date"),
6709
+ icon: CalendarDays,
6710
+ value: invoice.status !== "cancelled" ? /* @__PURE__ */ jsx(DatePicker, { value: invoice.invoiceDate, variant: "inline", format: "numeric", onChange: (v) => {
6711
+ void patchInvoiceDate(v);
6712
+ } }) : invoice.invoiceDate
6713
+ }
6436
6714
  ],
6437
6715
  customerLabel: direction === "debit" ? t2("financial.invoice.payTo") : t2("financial.invoice.receiveFrom"),
6438
6716
  unit: unitName ? { label: t2("financial.invoice.unit"), value: unitName } : void 0,
@@ -6450,7 +6728,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6450
6728
  "button",
6451
6729
  {
6452
6730
  type: "button",
6453
- onClick: () => setViewing(0),
6731
+ onClick: () => setAttachmentsOpen(true),
6454
6732
  title: attachments.map((a) => a.fileName).join("\n"),
6455
6733
  className: "mb-2 inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
6456
6734
  children: [
@@ -6465,14 +6743,12 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6465
6743
  const isPaid2 = mov.status === "paid";
6466
6744
  const isPartial = mov.status === "partial";
6467
6745
  const hasPaidDetails = isPaid2 || isPartial;
6468
- const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
6469
- const MovIcon = movStatus.icon;
6470
6746
  const instPayments = settlements.byBillId.get(mov.id) ?? [];
6471
6747
  const hows = instPayments.map((payment) => describePayment(payment, howCtx));
6472
6748
  const paymentSummary = [...new Set(hows.map((how2) => [how2.method, how2.account?.name].filter(Boolean).join(" \xB7 ")).filter(Boolean))].join(" / ");
6473
6749
  const how = hows[0];
6474
6750
  const howCard = how?.account?.accountType === "card_payable" ? how.account : void 0;
6475
- const HowIcon = HOW_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
6751
+ const HowIcon = METHOD_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
6476
6752
  const srcMeta = mov.metadata ?? {};
6477
6753
  const srcStr = (v) => typeof v === "string" && v ? v : void 0;
6478
6754
  const srcTypeId = srcStr(srcMeta.payMethodTypeId);
@@ -6480,66 +6756,41 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6480
6756
  const editavel = !hasPaidDetails && invoice.status !== "cancelled";
6481
6757
  const canChooseSource = editavel && Math.abs(mov.amount) >= 5e-3;
6482
6758
  const faltaParaPagar = !srcTypeId ? t2("financial.payment.methodRequired") : !srcAccountId ? t2("financial.payment.accountRequired") : null;
6483
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
6484
- "div",
6759
+ const ativa = invoice.status !== "cancelled";
6760
+ return /* @__PURE__ */ jsx(
6761
+ InstallmentRow,
6485
6762
  {
6486
- className: `flex items-center gap-3 px-4 py-3 ${hasPaidDetails ? "cursor-pointer hover:bg-muted/20 transition-colors" : ""}`,
6763
+ number: mov.installmentNumber ?? 1,
6764
+ amount: formatCurrency(mov.amount, currency),
6765
+ status: mov.status,
6766
+ dueDate: mov.dueDate,
6767
+ onDueDateChange: editavel ? (v) => {
6768
+ void patchDueDate(mov, v);
6769
+ } : void 0,
6770
+ dueExtra: /* @__PURE__ */ jsxs(Fragment, { children: [
6771
+ mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
6772
+ mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
6773
+ ] }),
6487
6774
  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
- }
6775
+ source: canChooseSource ? /* @__PURE__ */ jsx(
6776
+ InstallmentSourceFields,
6777
+ {
6778
+ direction,
6779
+ value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
6780
+ onChange: (next) => {
6781
+ void patchSource(mov, {
6782
+ payMethodTypeId: next.payMethodTypeId ?? null,
6783
+ payAccountId: next.payAccountId ?? null
6784
+ });
6535
6785
  }
6536
- ) }),
6786
+ }
6787
+ ) : void 0,
6788
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
6537
6789
  paymentSummary && /* @__PURE__ */ jsx(
6538
6790
  "button",
6539
6791
  {
6540
6792
  type: "button",
6541
- onClick: (e) => {
6542
- e.stopPropagation();
6793
+ onClick: () => {
6543
6794
  if (hasPaidDetails) setReceiptBill(mov);
6544
6795
  },
6545
6796
  title: paymentSummary,
@@ -6548,72 +6799,38 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6548
6799
  children: howCard ? /* @__PURE__ */ jsx(CardVisual, { lastFour: howCard.lastFour ?? void 0, color: howCard.color ?? void 0, brandId: howCard.cardBrandId ?? void 0 }) : HowIcon ? /* @__PURE__ */ jsx(HowIcon, { className: "h-4 w-4", "aria-hidden": true }) : null
6549
6800
  }
6550
6801
  ),
6551
- !isPaid2 && invoice.status !== "cancelled" && /* @__PURE__ */ jsx(
6552
- "button",
6802
+ /* @__PURE__ */ jsx(
6803
+ InstallmentReceiptButton,
6553
6804
  {
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" })
6805
+ title: "Anexos",
6806
+ attached: attachments.length > 0,
6807
+ count: attachments.length,
6808
+ onClick: () => setAttachmentsOpen(true)
6563
6809
  }
6564
6810
  ),
6565
- !isPaid2 && !isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
6566
- "button",
6811
+ !isPaid2 && !isPartial && ativa && /* @__PURE__ */ jsxs(
6812
+ InstallmentPayButton,
6567
6813
  {
6568
- onClick: (e) => {
6569
- e.stopPropagation();
6570
- setPayingMovement(mov);
6571
- },
6814
+ onClick: () => setPayingMovement(mov),
6572
6815
  disabled: !!faltaParaPagar,
6573
6816
  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
6817
  children: [
6576
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
6577
6818
  t2("financial.invoice.pay"),
6578
6819
  " ",
6579
6820
  remaining < mov.amount ? formatCurrency(remaining, currency) : ""
6580
6821
  ]
6581
6822
  }
6582
6823
  ),
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);
6824
+ isPaid2 && reversiblePayments(instPayments).length > 0 && ativa && /* @__PURE__ */ jsx(InstallmentActionButton, { icon: Undo2, onClick: () => setReversing(reversiblePayments(instPayments).at(-1)), children: t2("financial.invoice.reverse") }),
6825
+ isPartial && ativa && /* @__PURE__ */ jsxs(InstallmentPayButton, { variant: "outline", onClick: () => setPayingMovement(mov), children: [
6826
+ t2("financial.invoice.pay"),
6827
+ " ",
6828
+ formatCurrency(remaining, currency)
6829
+ ] })
6830
+ ] })
6831
+ },
6832
+ mov.id
6833
+ );
6617
6834
  }) }),
6618
6835
  settlements.unattributed.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t bg-warning-soft/40 px-4 py-2.5", children: [
6619
6836
  /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground", children: t2("financial.invoice.unlinkedPayments") }),
@@ -6632,14 +6849,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
6632
6849
  extraSections: sideBlocks
6633
6850
  }
6634
6851
  ),
6635
- receiptPicker.input,
6636
- viewing !== null && /* @__PURE__ */ jsx(
6637
- AttachmentViewer,
6852
+ attachmentsOpen && /* @__PURE__ */ jsx(
6853
+ InvoiceAttachmentsModal,
6638
6854
  {
6639
- attachments,
6640
- index: viewing,
6641
- onIndexChange: setViewing,
6642
- onClose: () => setViewing(null)
6855
+ invoiceId: invoice.id,
6856
+ onClose: () => setAttachmentsOpen(false),
6857
+ onChanged: () => {
6858
+ void loadData();
6859
+ }
6643
6860
  }
6644
6861
  ),
6645
6862
  receiptBill && /* @__PURE__ */ jsx(
@@ -7362,7 +7579,7 @@ function CashRegistersView({ variant = "page" } = {}) {
7362
7579
  const [tab, setTab] = useState("open");
7363
7580
  const [reviewRows, setReviewRows] = useState(null);
7364
7581
  const [reviewError, setReviewError] = useState(null);
7365
- const loadReviews = React23__default.useCallback(async () => {
7582
+ const loadReviews = React25__default.useCallback(async () => {
7366
7583
  try {
7367
7584
  setReviewError(null);
7368
7585
  setReviewRows(await provider.getCashSessionReviews?.() ?? []);
@@ -7962,7 +8179,7 @@ function StatementsView({ onNavigate }) {
7962
8179
  "extrato"
7963
8180
  );
7964
8181
  };
7965
- const headerActions = React23__default.useMemo(() => [{
8182
+ const headerActions = React25__default.useMemo(() => [{
7966
8183
  id: "new-transfer",
7967
8184
  label: t2("financial.statements.newTransfer"),
7968
8185
  icon: ArrowRightLeft,
@@ -8158,15 +8375,15 @@ function CommissionsView() {
8158
8375
  const fetchCommissions = useFinancialStore((s) => s.fetchCommissions);
8159
8376
  const generateCommissions = useFinancialStore((s) => s.generateCommissions);
8160
8377
  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 () => {
8378
+ const [from, setFrom] = React25__default.useState(firstOfMonth());
8379
+ const [to, setTo] = React25__default.useState(today2());
8380
+ const [preset, setPreset] = React25__default.useState("this_month");
8381
+ const [running, setRunning] = React25__default.useState(false);
8382
+ const [lastRun, setLastRun] = React25__default.useState(null);
8383
+ const [generation, setGeneration] = React25__default.useState(null);
8384
+ const [busy, setBusy] = React25__default.useState(null);
8385
+ const [error, setError] = React25__default.useState(null);
8386
+ const loadGeneration = React25__default.useCallback(async () => {
8170
8387
  try {
8171
8388
  const gens = await provider.getCommissionGenerations?.() ?? [];
8172
8389
  const doPeriodo = gens.filter((g) => g.periodStart === from && g.periodEnd === to);
@@ -8174,11 +8391,11 @@ function CommissionsView() {
8174
8391
  } catch {
8175
8392
  }
8176
8393
  }, [provider, from, to]);
8177
- React23__default.useEffect(() => {
8394
+ React25__default.useEffect(() => {
8178
8395
  void fetchCommissions({ periodStart: from, periodEnd: to });
8179
8396
  void loadGeneration();
8180
8397
  }, [fetchCommissions, loadGeneration, from, to]);
8181
- const byPerson = React23__default.useMemo(() => {
8398
+ const byPerson = React25__default.useMemo(() => {
8182
8399
  const map = /* @__PURE__ */ new Map();
8183
8400
  for (const c of commissions2) {
8184
8401
  const key = c.personId;
@@ -8231,7 +8448,7 @@ function CommissionsView() {
8231
8448
  setBusy(null);
8232
8449
  }
8233
8450
  }
8234
- const headerActions = React23__default.useMemo(() => [{
8451
+ const headerActions = React25__default.useMemo(() => [{
8235
8452
  id: "commission-rules",
8236
8453
  label: "Configurar regras de comiss\xE3o",
8237
8454
  icon: Settings,
@@ -8679,13 +8896,13 @@ function money3(v) {
8679
8896
  function GoalsView() {
8680
8897
  const { labels } = useFinancialConfig();
8681
8898
  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(() => {
8899
+ const [units, setUnits] = React25__default.useState([]);
8900
+ const [unitId, setUnitId] = React25__default.useState("");
8901
+ const [year, setYear] = React25__default.useState((/* @__PURE__ */ new Date()).getFullYear());
8902
+ const [rows, setRows] = React25__default.useState(null);
8903
+ const [editing, setEditing] = React25__default.useState(null);
8904
+ const [error, setError] = React25__default.useState(null);
8905
+ React25__default.useEffect(() => {
8689
8906
  void (async () => {
8690
8907
  const cat = await provider.getCommissionCatalogs?.();
8691
8908
  const us = cat?.units ?? [];
@@ -8693,7 +8910,7 @@ function GoalsView() {
8693
8910
  setUnitId((cur) => cur || us[0]?.id || "");
8694
8911
  })();
8695
8912
  }, [provider]);
8696
- const load = React23__default.useCallback(async () => {
8913
+ const load = React25__default.useCallback(async () => {
8697
8914
  if (!unitId) return;
8698
8915
  setRows(null);
8699
8916
  try {
@@ -8704,7 +8921,7 @@ function GoalsView() {
8704
8921
  setRows([]);
8705
8922
  }
8706
8923
  }, [provider, unitId, year]);
8707
- React23__default.useEffect(() => {
8924
+ React25__default.useEffect(() => {
8708
8925
  void load();
8709
8926
  }, [load]);
8710
8927
  const totalRealizado = (rows ?? []).reduce((s, r) => s + r.achievedAmount, 0);
@@ -8835,11 +9052,11 @@ function GoalDialog({
8835
9052
  onSaved
8836
9053
  }) {
8837
9054
  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);
9055
+ const [target, setTarget] = React25__default.useState(String(goal.targetAmount || ""));
9056
+ const [sup, setSup] = React25__default.useState(String(goal.superAmount || ""));
9057
+ const [hyper, setHyper] = React25__default.useState(String(goal.hyperAmount || ""));
9058
+ const [saving, setSaving] = React25__default.useState(false);
9059
+ const [error, setError] = React25__default.useState(null);
8843
9060
  const t2 = Number(target) || 0;
8844
9061
  const s = Number(sup) || 0;
8845
9062
  const h = Number(hyper) || 0;
@@ -8930,21 +9147,21 @@ function GiftCardsView() {
8930
9147
  const provider = useFinancialProvider();
8931
9148
  const { currency } = useFinancialConfig();
8932
9149
  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 () => {
9150
+ const [enabled, setEnabled] = React25__default.useState(null);
9151
+ const [cards, setCards] = React25__default.useState(null);
9152
+ const [error, setError] = React25__default.useState(null);
9153
+ const [issuing, setIssuing] = React25__default.useState(false);
9154
+ const [issued, setIssued] = React25__default.useState(null);
9155
+ const [activating, setActivating] = React25__default.useState(null);
9156
+ const [search, setSearch] = React25__default.useState("");
9157
+ const [state, setState] = React25__default.useState("");
9158
+ const [minValue, setMinValue] = React25__default.useState("");
9159
+ const [maxValue, setMaxValue] = React25__default.useState("");
9160
+ const [from, setFrom] = React25__default.useState("");
9161
+ const [to, setTo] = React25__default.useState("");
9162
+ const money4 = React25__default.useCallback((v) => formatCurrency(v, currency), [currency]);
9163
+ const seq = React25__default.useRef(0);
9164
+ const load = React25__default.useCallback(async () => {
8948
9165
  const mine = ++seq.current;
8949
9166
  setCards(null);
8950
9167
  setError(null);
@@ -8965,7 +9182,7 @@ function GiftCardsView() {
8965
9182
  setCards([]);
8966
9183
  }
8967
9184
  }, [provider, search, state, minValue, maxValue, from, to]);
8968
- React23__default.useEffect(() => {
9185
+ React25__default.useEffect(() => {
8969
9186
  void (async () => {
8970
9187
  try {
8971
9188
  setEnabled(await provider.giftCardsEnabled?.() === true);
@@ -8974,10 +9191,10 @@ function GiftCardsView() {
8974
9191
  }
8975
9192
  })();
8976
9193
  }, [provider]);
8977
- React23__default.useEffect(() => {
9194
+ React25__default.useEffect(() => {
8978
9195
  if (enabled) void load();
8979
9196
  }, [enabled, load]);
8980
- const columns = React23__default.useMemo(() => [
9197
+ const columns = React25__default.useMemo(() => [
8981
9198
  {
8982
9199
  accessorKey: "code",
8983
9200
  header: "C\xF3digo",
@@ -9158,12 +9375,12 @@ function IssueDialog({
9158
9375
  onIssued
9159
9376
  }) {
9160
9377
  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);
9378
+ const [quantity, setQuantity] = React25__default.useState("10");
9379
+ const [amount, setAmount] = React25__default.useState("100");
9380
+ const [expiresAt, setExpiresAt] = React25__default.useState("");
9381
+ const [note, setNote] = React25__default.useState("");
9382
+ const [saving, setSaving] = React25__default.useState(false);
9383
+ const [error, setError] = React25__default.useState(null);
9167
9384
  const q = Math.trunc(Number(quantity)) || 0;
9168
9385
  const a = Number(amount) || 0;
9169
9386
  const total = q * a;
@@ -10438,11 +10655,11 @@ function ReconciliationView() {
10438
10655
  setBusy(false);
10439
10656
  }
10440
10657
  }
10441
- const RefreshIcon = React23__default.useMemo(
10658
+ const RefreshIcon = React25__default.useMemo(
10442
10659
  () => ({ className }) => /* @__PURE__ */ jsx(RefreshCw, { className: `${className ?? ""} ${loading ? "animate-spin" : ""}` }),
10443
10660
  [loading]
10444
10661
  );
10445
- const headerActions = React23__default.useMemo(() => [{
10662
+ const headerActions = React25__default.useMemo(() => [{
10446
10663
  id: "refresh",
10447
10664
  label: t2("financial.reconciliation.refresh"),
10448
10665
  icon: RefreshIcon,
@@ -10867,13 +11084,13 @@ function RecurringBillsList({ direction, onNavigate }) {
10867
11084
  const provider = useFinancialProvider();
10868
11085
  const { currency } = useFinancialConfig();
10869
11086
  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);
11087
+ const [rows, setRows] = React25__default.useState(null);
11088
+ const [search, setSearch] = React25__default.useState("");
11089
+ const [status, setStatus] = React25__default.useState("all");
11090
+ const [changing, setChanging] = React25__default.useState(null);
10874
11091
  const isPayable = direction === "payable";
10875
11092
  const prefix = isPayable ? "payables" : "receivables";
10876
- const load = React23__default.useCallback(async () => {
11093
+ const load = React25__default.useCallback(async () => {
10877
11094
  setRows(null);
10878
11095
  try {
10879
11096
  setRows(await provider.getRecurringBills?.(direction) ?? []);
@@ -10882,10 +11099,10 @@ function RecurringBillsList({ direction, onNavigate }) {
10882
11099
  toast.error(error?.message ?? "N\xE3o foi poss\xEDvel carregar as recorr\xEAncias.");
10883
11100
  }
10884
11101
  }, [provider, direction]);
10885
- React23__default.useEffect(() => {
11102
+ React25__default.useEffect(() => {
10886
11103
  void load();
10887
11104
  }, [load]);
10888
- const filtered = React23__default.useMemo(() => {
11105
+ const filtered = React25__default.useMemo(() => {
10889
11106
  const term = search.trim().toLowerCase();
10890
11107
  const visiveis = (rows ?? []).filter((row) => {
10891
11108
  if (status === "active" && !row.isActive) return false;
@@ -10895,7 +11112,7 @@ function RecurringBillsList({ direction, onNavigate }) {
10895
11112
  });
10896
11113
  return [...visiveis].sort((a, b) => Number(b.isActive) - Number(a.isActive) || (a.dueDay ?? 0) - (b.dueDay ?? 0) || a.description.localeCompare(b.description));
10897
11114
  }, [rows, search, status]);
10898
- const porMes = React23__default.useMemo(() => {
11115
+ const porMes = React25__default.useMemo(() => {
10899
11116
  const fator = { weekly: 52 / 12, monthly: 1, quarterly: 1 / 3, yearly: 1 / 12 };
10900
11117
  const ativas = filtered.filter((row) => row.isActive);
10901
11118
  return {
@@ -10916,7 +11133,7 @@ function RecurringBillsList({ direction, onNavigate }) {
10916
11133
  setChanging(null);
10917
11134
  }
10918
11135
  }
10919
- const columns = React23__default.useMemo(() => [
11136
+ const columns = React25__default.useMemo(() => [
10920
11137
  {
10921
11138
  accessorKey: "description",
10922
11139
  header: "Descri\xE7\xE3o",
@@ -11013,40 +11230,40 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
11013
11230
  const provider = useFinancialProvider();
11014
11231
  const { locations, currency, itemTypes: configuredItemTypes, entityLookups } = useFinancialConfig();
11015
11232
  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({
11233
+ const [value, setValue] = React25__default.useState(null);
11234
+ const [loaded, setLoaded] = React25__default.useState(!valueId);
11235
+ const [contact, setContact] = React25__default.useState(null);
11236
+ const [counterpartyAccounting, setCounterpartyAccounting] = React25__default.useState({
11020
11237
  allowedAccountIds: [],
11021
11238
  limitAccounts: false,
11022
11239
  addItemsAutomatically: false
11023
11240
  });
11024
- const accountingPolicyRequest = React23__default.useRef(0);
11025
- const counterpartyLookup = React23__default.useMemo(
11241
+ const accountingPolicyRequest = React25__default.useRef(0);
11242
+ const counterpartyLookup = React25__default.useMemo(
11026
11243
  () => createFinancialCounterpartyLookup(isPayable ? "debit" : "credit"),
11027
11244
  [isPayable]
11028
11245
  );
11029
11246
  const contactCreate = useLookupCreate(counterpartyLookup, (created) => {
11030
11247
  setContact({ id: created.id, name: created.label });
11031
11248
  });
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(() => {
11249
+ const initialItem = React25__default.useRef(newRecurringItem());
11250
+ const [items, setItems] = React25__default.useState([initialItem.current]);
11251
+ const [expandedItemId, setExpandedItemId] = React25__default.useState(initialItem.current._id);
11252
+ const [addingItem, setAddingItem] = React25__default.useState(false);
11253
+ const [frequency, setFrequency] = React25__default.useState("monthly");
11254
+ const [dueDay, setDueDay] = React25__default.useState("1");
11255
+ const [startsOn, setStartsOn] = React25__default.useState(today3());
11256
+ const [endsOn, setEndsOn] = React25__default.useState("");
11257
+ const [unitId, setUnitId] = React25__default.useState(locations[0]?.id ?? "");
11258
+ const [availableLocations, setAvailableLocations] = React25__default.useState(locations);
11259
+ const [notes, setNotes] = React25__default.useState("");
11260
+ const [isActive, setIsActive] = React25__default.useState(true);
11261
+ const [accounts, setAccounts] = React25__default.useState([]);
11262
+ const [costCenters, setCostCenters] = React25__default.useState([]);
11263
+ const [saving, setSaving] = React25__default.useState(false);
11264
+ const [ending, setEnding] = React25__default.useState(false);
11265
+ const [error, setError] = React25__default.useState(null);
11266
+ React25__default.useEffect(() => {
11050
11267
  if (locations.length > 0) {
11051
11268
  setAvailableLocations(locations);
11052
11269
  return;
@@ -11061,7 +11278,7 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
11061
11278
  cancelled = true;
11062
11279
  };
11063
11280
  }, [locations]);
11064
- React23__default.useEffect(() => {
11281
+ React25__default.useEffect(() => {
11065
11282
  let cancelled = false;
11066
11283
  void Promise.all([
11067
11284
  provider.getChartOfAccounts(),
@@ -11108,21 +11325,21 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
11108
11325
  cancelled = true;
11109
11326
  };
11110
11327
  }, [provider, direction, valueId]);
11111
- const offeredItemTypes = React23__default.useMemo(() => availableInvoiceItemTypes(
11328
+ const offeredItemTypes = React25__default.useMemo(() => availableInvoiceItemTypes(
11112
11329
  configuredItemTypes,
11113
11330
  Object.keys(entityLookups),
11114
11331
  configuredItemTypes.find((item) => item.value === "other")?.label ?? "Outro",
11115
11332
  direction
11116
11333
  ), [configuredItemTypes, entityLookups, direction]);
11117
11334
  const itemTypes = useStockedItemTypes(offeredItemTypes, entityLookups);
11118
- const selectableAccounts = React23__default.useMemo(() => {
11335
+ const selectableAccounts = React25__default.useMemo(() => {
11119
11336
  const postable = postableAccounts(accounts);
11120
11337
  if (!counterpartyAccounting.limitAccounts) return postable;
11121
11338
  const allowed = new Set(counterpartyAccounting.allowedAccountIds);
11122
11339
  return postable.filter((account) => allowed.has(account.id));
11123
11340
  }, [accounts, counterpartyAccounting]);
11124
11341
  const hasItemLookups = itemTypes.some((item) => entityLookups[item.value]);
11125
- const amount = React23__default.useMemo(() => items.reduce(
11342
+ const amount = React25__default.useMemo(() => items.reduce(
11126
11343
  (sum, item) => sum + item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
11127
11344
  0
11128
11345
  ), [items]);
@@ -11130,8 +11347,8 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
11130
11347
  const numericDueDay = Math.trunc(Number(dueDay));
11131
11348
  const invalid = items.length === 0 ? "Adicione pelo menos um item." : items.some((item) => !item.description.trim()) ? "Informe a descri\xE7\xE3o de todos os itens." : !contact?.id ? `Selecione ${isPayable ? "quem recebe" : "quem paga"}.` : !(amount > 0) ? "Informe um valor maior que zero." : numericDueDay < 1 || numericDueDay > 31 ? "O dia do vencimento vai de 1 a 31." : !startsOn ? "Informe o in\xEDcio da recorr\xEAncia." : endsOn && endsOn < startsOn ? "O t\xE9rmino n\xE3o pode vir antes do in\xEDcio." : null;
11132
11349
  const fields = { items, contactId: contact?.id ?? "", frequency, dueDay, startsOn, endsOn, unitId, notes, isActive };
11133
- const snapshot = React23__default.useRef(null);
11134
- React23__default.useEffect(() => {
11350
+ const snapshot = React25__default.useRef(null);
11351
+ React25__default.useEffect(() => {
11135
11352
  if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
11136
11353
  }, [loaded]);
11137
11354
  const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
@@ -11521,16 +11738,8 @@ function buildNav(config, activeView, navigate, t2) {
11521
11738
  children: [
11522
11739
  { id: "payables-new", kind: "action", label: t2("financial.nav.new"), active: activeView.startsWith("payables-new"), onClick: () => navigate("payables-new") },
11523
11740
  { 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") }
11741
+ { id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") }
11742
+ // Digitize expense and import payroll live in the list's create menu, not here.
11534
11743
  ]
11535
11744
  });
11536
11745
  }
@@ -11780,8 +11989,9 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
11780
11989
  // tamanho do buraco, medido no banco do V1.
11781
11990
  case "payables-petty-cash":
11782
11991
  return /* @__PURE__ */ jsx(PettyCashView, {});
11992
+ // Digitizing is the list's receipt capture now — the old link lands on the list.
11783
11993
  case "payables-digitize":
11784
- return /* @__PURE__ */ jsx(DigitizeExpenseView, {});
11994
+ return /* @__PURE__ */ jsx(PayablesView, { intent: { view: "payables", mode: "list" }, onNavigate: navigate, previousView, back });
11785
11995
  case "payables-payroll":
11786
11996
  return /* @__PURE__ */ jsx(ImportPayrollView, {});
11787
11997
  case "cash-reviewers":
@@ -11988,7 +12198,7 @@ function BalanceHero() {
11988
12198
  const summary = useFinancialStore((s) => s.summary);
11989
12199
  const navigate = useDashboardNavigate();
11990
12200
  useEnsureSummary();
11991
- const [hidden, setHidden] = React23__default.useState(false);
12201
+ const [hidden, setHidden] = React25__default.useState(false);
11992
12202
  const money4 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
11993
12203
  const inflow = summary?.monthlyInflow ?? 0;
11994
12204
  const outflow = summary?.monthlyOutflow ?? 0;
@@ -12061,7 +12271,7 @@ function categorizeSpend(notes) {
12061
12271
  function SpendByCategoryChart() {
12062
12272
  const t2 = useTranslation();
12063
12273
  const provider = useFinancialProvider();
12064
- const [rows, setRows] = React23__default.useState([]);
12274
+ const [rows, setRows] = React25__default.useState([]);
12065
12275
  useEffect(() => {
12066
12276
  let alive = true;
12067
12277
  void (async () => {
@@ -12104,7 +12314,7 @@ function UpcomingBills() {
12104
12314
  const { currency } = useFinancialConfig();
12105
12315
  const provider = useFinancialProvider();
12106
12316
  const dueLabel = useDueLabel();
12107
- const [bills, setBills] = React23__default.useState([]);
12317
+ const [bills, setBills] = React25__default.useState([]);
12108
12318
  useEffect(() => {
12109
12319
  let alive = true;
12110
12320
  void (async () => {
@@ -13778,6 +13988,33 @@ function createSupabaseFinancialProvider() {
13778
13988
  };
13779
13989
  });
13780
13990
  },
13991
+ async addInvoiceAttachment(invoiceId, file) {
13992
+ const { pub } = getClients();
13993
+ const tenantId = getTenantId();
13994
+ if (!tenantId) throw new Error("Conta sem empresa ativa");
13995
+ const safe = file.name.replace(/[^\w.\-]+/g, "_");
13996
+ const path = `${tenantId}/financial_invoice/${invoiceId}/${Date.now()}_${safe}`;
13997
+ const { error: upErr } = await pub.storage.from("attachments").upload(path, file, { contentType: file.type || void 0 });
13998
+ if (upErr) throw upErr;
13999
+ const { error } = await pub.from("documents").insert({
14000
+ tenant_id: tenantId,
14001
+ kind: "attachment",
14002
+ status: "active",
14003
+ subject_type: "financial_invoice",
14004
+ subject_id: invoiceId,
14005
+ file_name: file.name,
14006
+ file_size: file.size,
14007
+ mime_type: file.type || null,
14008
+ storage_provider: "supabase",
14009
+ storage_bucket: "attachments",
14010
+ storage_path: path
14011
+ });
14012
+ if (error) {
14013
+ await pub.storage.from("attachments").remove([path]).catch(() => {
14014
+ });
14015
+ throw error;
14016
+ }
14017
+ },
13781
14018
  async createInvoice(input) {
13782
14019
  const { core, pub } = getClients();
13783
14020
  const tenantId = getTenantId();
@@ -16252,21 +16489,21 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
16252
16489
  const canCreate = can("financial", "create");
16253
16490
  const canEdit = can("financial", "edit");
16254
16491
  const canDelete = can("financial", "delete");
16255
- const provider = React23__default.useMemo(
16492
+ const provider = React25__default.useMemo(
16256
16493
  () => dataProvider ?? resolveDataProvider(entity, mockData ?? []),
16257
16494
  [entity, mockData, dataProvider]
16258
16495
  );
16259
16496
  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 () => {
16497
+ const [nodes, setNodes] = React25__default.useState(null);
16498
+ const [loadError, setLoadError] = React25__default.useState(null);
16499
+ const [expanded, setExpanded] = React25__default.useState(/* @__PURE__ */ new Set());
16500
+ const [query, setQuery] = React25__default.useState("");
16501
+ const [showInactive, setShowInactive] = React25__default.useState(true);
16502
+ const [editing, setEditing] = React25__default.useState(null);
16503
+ const [confirmDelete, setConfirmDelete] = React25__default.useState(null);
16504
+ const [busy, setBusy] = React25__default.useState(false);
16505
+ const initialized = React25__default.useRef(false);
16506
+ const reload = React25__default.useCallback(async () => {
16270
16507
  try {
16271
16508
  const rows = await loadAll(provider);
16272
16509
  setNodes(rows);
@@ -16281,17 +16518,17 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
16281
16518
  setNodes((prev) => prev ?? []);
16282
16519
  }
16283
16520
  }, [provider]);
16284
- React23__default.useEffect(() => {
16521
+ React25__default.useEffect(() => {
16285
16522
  void reload();
16286
16523
  }, [reload]);
16287
- const shown = React23__default.useMemo(
16524
+ const shown = React25__default.useMemo(
16288
16525
  () => (nodes ?? []).filter((n) => showInactive || n.isActive),
16289
16526
  [nodes, showInactive]
16290
16527
  );
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(
16528
+ const tree = React25__default.useMemo(() => buildChartTree(shown), [shown]);
16529
+ const fullTree = React25__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
16530
+ const search = React25__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
16531
+ const groupIds = React25__default.useMemo(
16295
16532
  () => [...tree.byId.values()].filter((n) => isGroupNode(tree, n)).map((n) => n.id),
16296
16533
  [tree]
16297
16534
  );
@@ -16387,7 +16624,7 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
16387
16624
  const ok = await run(() => provider.remove(node.id), t2("financial.chartTree.deleted", { name: node.name }));
16388
16625
  if (ok) setConfirmDelete(null);
16389
16626
  }
16390
- const parentOptions = React23__default.useCallback((q, selfId) => {
16627
+ const parentOptions = React25__default.useCallback((q, selfId) => {
16391
16628
  const blocked = selfId ? descendantIds(fullTree, selfId).add(selfId) : /* @__PURE__ */ new Set();
16392
16629
  const needle = q.trim().toLowerCase();
16393
16630
  return [...fullTree.byId.values()].filter((n) => !blocked.has(n.id) && isGroupNode(fullTree, n)).filter((n) => !needle || accountLabel(n).toLowerCase().includes(needle)).sort((a, b) => a.code.localeCompare(b.code, void 0, { numeric: true })).slice(0, 50).map((n) => ({ id: n.id, label: accountLabel(n) }));
@@ -16690,7 +16927,7 @@ var chartOfAccountsSeed = [
16690
16927
  { id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
16691
16928
  { id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
16692
16929
  ];
16693
- var ChartOfAccountsRegistryView = (props) => React23__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
16930
+ var ChartOfAccountsRegistryView = (props) => React25__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
16694
16931
  ChartOfAccountsRegistryView.displayName = "ChartOfAccountsRegistryView";
16695
16932
  var costCenterEntity = {
16696
16933
  name: "Cost Center",
@@ -16788,7 +17025,7 @@ var bankAccountEntity = {
16788
17025
  labelWhen: (v) => v.kind === "card_payable" ? "Banco emissor" : void 0,
16789
17026
  visibleWhen: (v) => hasIssuer(v.kind),
16790
17027
  group: "bank",
16791
- renderInput: ({ value, values, onChange, setValue }) => React23__default.createElement(BankPicker, {
17028
+ renderInput: ({ value, values, onChange, setValue }) => React25__default.createElement(BankPicker, {
16792
17029
  code: values.bankCode ?? "",
16793
17030
  name: value ?? "",
16794
17031
  onChange: (next) => {
@@ -17657,10 +17894,10 @@ function CashSessionHeaderControl({ config, provider, store: store6 }) {
17657
17894
  const fetchCashSessions = useStore(store6, (s) => s.fetchCashSessions);
17658
17895
  const open = useStore(store6, (s) => s.cashSessionModalOpen);
17659
17896
  const setOpen = useStore(store6, (s) => s.setCashSessionModalOpen);
17660
- React23__default.useEffect(() => {
17897
+ React25__default.useEffect(() => {
17661
17898
  void fetchCashSessions?.();
17662
17899
  }, [fetchCashSessions]);
17663
- React23__default.useEffect(() => {
17900
+ React25__default.useEffect(() => {
17664
17901
  if (!open) return;
17665
17902
  const close = () => setOpen(false);
17666
17903
  window.addEventListener("hashchange", close);
@@ -17780,7 +18017,7 @@ var en = {
17780
18017
  "financial.invoice.items": "Items",
17781
18018
  "financial.invoice.keepInvoice": "Keep Invoice",
17782
18019
  "financial.invoice.new": "New",
17783
- "financial.invoice.newManual": "Fill it in by hand",
18020
+ "financial.invoice.newManual": "Create",
17784
18021
  "financial.invoiceForm.createContact": "Create",
17785
18022
  "financial.invoiceForm.alreadyPaid": "Already paid",
17786
18023
  "financial.invoiceForm.paidWith": "Payment method",
@@ -18519,6 +18756,10 @@ var en = {
18519
18756
  "financial.quickTx.receiptRemove": "Remove receipt",
18520
18757
  "financial.quickTx.receiptInvalid": "Please choose an image file",
18521
18758
  "financial.quickTx.sendReceipt": "Send receipt",
18759
+ "financial.quickTx.digitizeExpense": "Digitize expense",
18760
+ "financial.quickTx.digitizeReceipt": "Digitize receipt",
18761
+ "financial.invoice.importPayroll": "Import payroll",
18762
+ "financial.invoice.importPayrollSoon": "Coming soon",
18522
18763
  "financial.receipt.ask": "Send the receipt photo\u2026",
18523
18764
  "financial.receipt.attachRead": "Attach the receipt the assistant read",
18524
18765
  "financial.receipt.fromReceipt": "From the receipt",
@@ -18670,7 +18911,7 @@ var ptBR = {
18670
18911
  "financial.invoice.items": "Itens",
18671
18912
  "financial.invoice.keepInvoice": "Manter Fatura",
18672
18913
  "financial.invoice.new": "Nova",
18673
- "financial.invoice.newManual": "Cadastrar manualmente",
18914
+ "financial.invoice.newManual": "Cadastrar",
18674
18915
  "financial.invoiceForm.createContact": "Cadastrar",
18675
18916
  "financial.invoiceForm.alreadyPaid": "J\xE1 foi pago",
18676
18917
  "financial.invoiceForm.paidWith": "Forma de pagamento",
@@ -19412,6 +19653,10 @@ var ptBR = {
19412
19653
  "financial.quickTx.receiptRemove": "Remover recibo",
19413
19654
  "financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
19414
19655
  "financial.quickTx.sendReceipt": "Enviar recibo",
19656
+ "financial.quickTx.digitizeExpense": "Digitalizar despesa",
19657
+ "financial.quickTx.digitizeReceipt": "Digitalizar comprovante",
19658
+ "financial.invoice.importPayroll": "Importar folha",
19659
+ "financial.invoice.importPayrollSoon": "Em breve",
19415
19660
  "financial.receipt.ask": "Mande a foto do recibo\u2026",
19416
19661
  "financial.receipt.attachRead": "Anexar o recibo que o assistente leu",
19417
19662
  "financial.receipt.fromReceipt": "Do recibo",
@@ -19577,14 +19822,14 @@ function CommissionRuleDialog({
19577
19822
  onClose,
19578
19823
  onSaved
19579
19824
  }) {
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(() => {
19825
+ const [name, setName] = React25__default.useState(rule?.name ?? "");
19826
+ const [description, setDescription] = React25__default.useState(rule?.description ?? "");
19827
+ const [priority, setPriority] = React25__default.useState(String(rule?.priority ?? 1));
19828
+ const [isActive, setIsActive] = React25__default.useState(rule?.isActive ?? true);
19829
+ const [isDefault, setIsDefault] = React25__default.useState(rule?.isDefault ?? !hasDefault);
19830
+ const [saving, setSaving] = React25__default.useState(false);
19831
+ const [error, setError] = React25__default.useState(null);
19832
+ const initial = React25__default.useMemo(() => {
19588
19833
  const get = (t2, key = "ids") => rule?.criteria.find((c) => c.criteriaType === t2)?.criteriaValue?.[key] ?? [];
19589
19834
  return {
19590
19835
  services: get("service"),
@@ -19595,12 +19840,12 @@ function CommissionRuleDialog({
19595
19840
  ranges: get("time_range", "ranges")
19596
19841
  };
19597
19842
  }, [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);
19843
+ const [services, setServices] = React25__default.useState(initial.services);
19844
+ const [categories, setCategories] = React25__default.useState(initial.categories);
19845
+ const [professionals, setProfessionals] = React25__default.useState(initial.professionals);
19846
+ const [units, setUnits] = React25__default.useState(initial.units);
19847
+ const [weekdays, setWeekdays] = React25__default.useState(initial.weekdays);
19848
+ const [ranges, setRanges] = React25__default.useState(initial.ranges);
19604
19849
  const criteriaCount = [services, categories, professionals, units].filter((a) => a.length > 0).length + (weekdays.length ? 1 : 0) + (ranges.length ? 1 : 0);
19605
19850
  async function handleSave() {
19606
19851
  if (!name.trim()) {
@@ -19843,11 +20088,11 @@ function blankFunction(order) {
19843
20088
  };
19844
20089
  }
19845
20090
  function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
19846
- const [functions, setFunctions] = React23__default.useState(
20091
+ const [functions, setFunctions] = React25__default.useState(
19847
20092
  rule.functions.length ? rule.functions : [blankFunction(0)]
19848
20093
  );
19849
- const [saving, setSaving] = React23__default.useState(false);
19850
- const [error, setError] = React23__default.useState(null);
20094
+ const [saving, setSaving] = React25__default.useState(false);
20095
+ const [error, setError] = React25__default.useState(null);
19851
20096
  function patch(i, changes) {
19852
20097
  setFunctions((fs) => fs.map((f, j) => j === i ? { ...f, ...changes } : f));
19853
20098
  }
@@ -20131,19 +20376,19 @@ function functionChip(fn) {
20131
20376
  return fn.valueType === "percentage" ? `${who}: ${fn.valueAmount}%` : `${who}: R$ ${fn.valueAmount.toFixed(2)}`;
20132
20377
  }
20133
20378
  function CommissionRulesView({ provider }) {
20134
- const [rules, setRules] = React23__default.useState(null);
20135
- const [catalogs, setCatalogs] = React23__default.useState({
20379
+ const [rules, setRules] = React25__default.useState(null);
20380
+ const [catalogs, setCatalogs] = React25__default.useState({
20136
20381
  services: [],
20137
20382
  categories: [],
20138
20383
  professionals: [],
20139
20384
  units: []
20140
20385
  });
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 () => {
20386
+ const [error, setError] = React25__default.useState(null);
20387
+ const [funnel, setFunnel] = React25__default.useState({});
20388
+ const [editing, setEditing] = React25__default.useState(null);
20389
+ const [functionsFor, setFunctionsFor] = React25__default.useState(null);
20390
+ const [deleting, setDeleting] = React25__default.useState(null);
20391
+ const load = React25__default.useCallback(async () => {
20147
20392
  try {
20148
20393
  setError(null);
20149
20394
  const [r, c] = await Promise.all([
@@ -20157,17 +20402,17 @@ function CommissionRulesView({ provider }) {
20157
20402
  setRules([]);
20158
20403
  }
20159
20404
  }, [provider]);
20160
- React23__default.useEffect(() => {
20405
+ React25__default.useEffect(() => {
20161
20406
  void load();
20162
20407
  }, [load]);
20163
20408
  const funnelActive = Object.values(funnel).some(Boolean);
20164
- const visible = React23__default.useMemo(() => {
20409
+ const visible = React25__default.useMemo(() => {
20165
20410
  if (!rules) return [];
20166
20411
  const ranked = rankRules(rules);
20167
20412
  if (!funnelActive) return ranked;
20168
20413
  return ranked.filter((r) => r.isDefault || ruleMatches(r, funnel, catalogs));
20169
20414
  }, [rules, funnel, funnelActive, catalogs]);
20170
- const winner = React23__default.useMemo(() => {
20415
+ const winner = React25__default.useMemo(() => {
20171
20416
  if (!funnelActive) return null;
20172
20417
  return visible.find((r) => r.isActive && !r.isDefault) ?? visible.find((r) => r.isActive && r.isDefault) ?? null;
20173
20418
  }, [visible, funnelActive]);
@@ -20182,7 +20427,7 @@ function CommissionRulesView({ provider }) {
20182
20427
  await provider.duplicateCommissionRule?.(rule.id);
20183
20428
  await load();
20184
20429
  }
20185
- const headerActions = React23__default.useMemo(() => [{
20430
+ const headerActions = React25__default.useMemo(() => [{
20186
20431
  id: "new-rule",
20187
20432
  label: "Adicionar regra",
20188
20433
  icon: Plus,
@@ -20964,7 +21209,7 @@ function PersonStatementWidget({ person, provider, config }) {
20964
21209
  ] });
20965
21210
  }
20966
21211
  function SupplierFinancialSettings({ person, provider }) {
20967
- const initial = React23__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
21212
+ const initial = React25__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
20968
21213
  const [accounts, setAccounts] = useState([]);
20969
21214
  const [costCenters, setCostCenters] = useState([]);
20970
21215
  const [defaultAccountId, setDefaultAccountId] = useState(initial.defaultAccountId ?? "");
@@ -44175,6 +44420,76 @@ DROP TRIGGER IF EXISTS payee_accounts_one_default ON public.payee_accounts;
44175
44420
  CREATE TRIGGER payee_accounts_one_default
44176
44421
  BEFORE INSERT OR UPDATE OF is_default ON public.payee_accounts
44177
44422
  FOR EACH ROW EXECUTE FUNCTION public.trg_payee_accounts_one_default();
44423
+ `;
44424
+ var MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA = `-- ---------------------------------------------------------------------------
44425
+ -- 116 \u2014 um estorno n\xE3o se estorna.
44426
+ --
44427
+ -- \`financial_reverse_movement\` aceitava qualquer \`payment\`, inclusive a linha
44428
+ -- que ela mesma lan\xE7a como contrapartida. A tela mirava \xABo \xFAltimo pagamento da
44429
+ -- parcela\xBB, que depois do primeiro estorno \xC9 o estorno: cada clique estornava o
44430
+ -- estorno anterior, o dinheiro ia e voltava, e o pagamento vivo continuava l\xE1 \u2014
44431
+ -- a conta seguia \xABpaga\xBB para sempre. Agora o banco recusa com uma mensagem que
44432
+ -- a tela mostra.
44433
+ --
44434
+ -- Idempotente: s\xF3 rep\xF5e a fun\xE7\xE3o.
44435
+ -- ---------------------------------------------------------------------------
44436
+
44437
+ CREATE OR REPLACE FUNCTION public.financial_reverse_movement(p_movement_id uuid, p_reason text, p_idempotency_key text DEFAULT NULL::text) RETURNS jsonb
44438
+ LANGUAGE plpgsql SECURITY DEFINER
44439
+ SET search_path TO ''
44440
+ AS $$
44441
+ DECLARE
44442
+ v_tenant uuid := public.plg_financial_require_tenant();
44443
+ v_m public.plg_financial_movements%ROWTYPE;
44444
+ v_rev uuid;
44445
+ BEGIN
44446
+ IF nullif(btrim(coalesce(p_reason, '')), '') IS NULL THEN
44447
+ RAISE EXCEPTION 'financial_reverse_movement: a reason is required' USING ERRCODE = '22023';
44448
+ END IF;
44449
+ SELECT * INTO v_m FROM public.plg_financial_movements WHERE id = p_movement_id AND tenant_id = v_tenant;
44450
+ IF NOT FOUND THEN
44451
+ RAISE EXCEPTION 'financial_reverse_movement: movement % not found', p_movement_id USING ERRCODE = 'P0002';
44452
+ END IF;
44453
+ PERFORM public.plg_financial_require_permission('financial.edit', v_m.unit_id);
44454
+ IF v_m.movement_kind NOT IN ('payment', 'transfer') OR v_m.debit_account_id IS NULL THEN
44455
+ RAISE EXCEPTION 'financial_reverse_movement: % is not a money event', p_movement_id USING ERRCODE = '55000';
44456
+ END IF;
44457
+ IF v_m.reverses_id IS NOT NULL THEN
44458
+ RAISE EXCEPTION 'Este lan\xE7amento j\xE1 \xE9 um estorno e n\xE3o pode ser estornado.' USING ERRCODE = '55000';
44459
+ END IF;
44460
+ IF v_m.reversed_by_id IS NOT NULL THEN
44461
+ RETURN jsonb_build_object('reversal_id', v_m.reversed_by_id, 'movement_id', v_m.id, 'replayed', true);
44462
+ END IF;
44463
+ IF p_idempotency_key IS NOT NULL THEN
44464
+ PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);
44465
+ SELECT m.id INTO v_rev FROM public.plg_financial_movements m WHERE m.tenant_id = v_tenant AND m.idempotency_key = p_idempotency_key;
44466
+ IF v_rev IS NOT NULL THEN
44467
+ RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', true);
44468
+ END IF;
44469
+ END IF;
44470
+
44471
+ INSERT INTO public.plg_financial_movements
44472
+ (tenant_id, unit_id, invoice_id, financial_invoice_id, financial_invoice_installment_id, direction, movement_kind, amount, paid_amount, status,
44473
+ due_date, payment_date, installment_number, method, cash_session_id, bank_account_id, reason, notes, metadata,
44474
+ idempotency_key, created_by, reverses_id, debit_account_id, credit_account_id)
44475
+ VALUES
44476
+ (v_m.tenant_id, v_m.unit_id, v_m.invoice_id, v_m.financial_invoice_id, v_m.financial_invoice_installment_id,
44477
+ CASE WHEN v_m.direction = 'credit' THEN 'debit' ELSE 'credit' END, v_m.movement_kind, v_m.amount, v_m.amount, 'paid',
44478
+ current_date, current_date, v_m.installment_number, v_m.method, v_m.cash_session_id, v_m.bank_account_id, btrim(p_reason),
44479
+ 'Estorno de ' || v_m.id::text,
44480
+ jsonb_build_object('source', 'financial_reverse_movement', 'reversal_of', v_m.id, 'reason', btrim(p_reason)),
44481
+ p_idempotency_key, auth.uid(), v_m.id, v_m.credit_account_id, v_m.debit_account_id)
44482
+ RETURNING id INTO v_rev;
44483
+
44484
+ UPDATE public.plg_financial_movements SET reversed_by_id = v_rev, reason = coalesce(reason, btrim(p_reason)), updated_at = now()
44485
+ WHERE id = v_m.id;
44486
+ IF v_m.financial_invoice_installment_id IS NOT NULL THEN PERFORM public.plg_financial_sync_mirror(v_m.financial_invoice_installment_id); END IF;
44487
+
44488
+ PERFORM public.plg_financial_audit(v_tenant, 'financial.movement.reverse', 'plg_financial_movements', v_m.id::text,
44489
+ jsonb_build_object('reversal_id', v_rev, 'amount', v_m.amount, 'reason', btrim(p_reason), 'idempotency_key', p_idempotency_key));
44490
+ RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', false);
44491
+ END $$;
44492
+
44178
44493
  `;
44179
44494
  var MIGRATIONS = [
44180
44495
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
@@ -44292,7 +44607,8 @@ var MIGRATIONS = [
44292
44607
  { id: "112_a_linha_do_extrato_nao_e_um_lancamento_ainda", sql: MIGRATION_112_A_LINHA_DO_EXTRATO_NAO_E_UM_LANCAMENTO_AINDA },
44293
44608
  { id: "113_so_a_conta_integrada_aparece_na_conciliacao", sql: MIGRATION_113_SO_A_CONTA_INTEGRADA_APARECE_NA_CONCILIACAO },
44294
44609
  { 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 }
44610
+ { id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE },
44611
+ { id: "116_um_estorno_nao_se_estorna", sql: MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA }
44296
44612
  ];
44297
44613
  var STANDALONE_CONFIG = resolveConfig();
44298
44614
  registerTranslations(financialLocales);
@@ -44302,8 +44618,8 @@ function ReceivePaymentModal({
44302
44618
  onClose,
44303
44619
  onPaid
44304
44620
  }) {
44305
- const provider = React23__default.useMemo(() => createSafeFinancialProvider(), []);
44306
- const store6 = React23__default.useMemo(() => createFinancialStore(provider), [provider]);
44621
+ const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
44622
+ const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
44307
44623
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
44308
44624
  PaymentModal,
44309
44625
  {
@@ -44321,7 +44637,7 @@ function createFinancialSettingsPlugin(options) {
44321
44637
  ...financialRegistries.filter((r) => RECEIVING_REGISTRIES.includes(r.id)),
44322
44638
  ...options?.extraRegistries ?? []
44323
44639
  ];
44324
- const SettingsTab = () => React23__default.createElement(PluginSettingsPanel, {
44640
+ const SettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
44325
44641
  title: "Pagamentos",
44326
44642
  registries,
44327
44643
  hostPluginId: "financial",
@@ -44378,27 +44694,27 @@ function ClientAccountPanel({
44378
44694
  currency = BRL
44379
44695
  }) {
44380
44696
  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 () => {
44697
+ const [account, setAccount] = React25__default.useState(null);
44698
+ const [busy, setBusy] = React25__default.useState(false);
44699
+ const [viewInvoiceId, setViewInvoiceId] = React25__default.useState(null);
44700
+ const [comprando, setComprando] = React25__default.useState(false);
44701
+ const [buscaServico, setBuscaServico] = React25__default.useState("");
44702
+ const [catalogo, setCatalogo] = React25__default.useState([]);
44703
+ const [escolhidos, setEscolhidos] = React25__default.useState([]);
44704
+ const [comprandoBusy, setComprandoBusy] = React25__default.useState(false);
44705
+ const [erroCompra, setErroCompra] = React25__default.useState(null);
44706
+ const load = React25__default.useCallback(async () => {
44391
44707
  if (!personId) return;
44392
44708
  setAccount(await provider.getClientAccount(personId));
44393
44709
  }, [personId, provider]);
44394
- React23__default.useEffect(() => {
44710
+ React25__default.useEffect(() => {
44395
44711
  void load();
44396
44712
  }, [load]);
44397
44713
  const viewInvoice = account?.invoices.find((i) => i.id === viewInvoiceId) ?? null;
44398
44714
  const viewEditable = invoiceOpensForEditing(viewInvoice?.status);
44399
44715
  const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
44400
44716
  const hasForThis = forThis.length > 0;
44401
- React23__default.useEffect(() => {
44717
+ React25__default.useEffect(() => {
44402
44718
  if (!comprando) return;
44403
44719
  let vivo = true;
44404
44720
  const t2 = setTimeout(async () => {
@@ -44673,8 +44989,8 @@ function ClientAccountPanel({
44673
44989
  ] });
44674
44990
  }
44675
44991
  function ClientAccountInline(props) {
44676
- const provider = React23__default.useMemo(() => createSafeFinancialProvider(), []);
44677
- const store6 = React23__default.useMemo(() => createFinancialStore(provider), [provider]);
44992
+ const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
44993
+ const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
44678
44994
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(ClientAccountPanel, { ...props }) });
44679
44995
  }
44680
44996
  function ClientAccountModal({
@@ -44723,11 +45039,11 @@ function createFinancialPlugin(options) {
44723
45039
  const settingsRegistries = registries.filter((r) => r.id !== "bank-accounts");
44724
45040
  const AccountsRegistryPage = createCrudPage(financialAccountEntity, { feature: "financial" });
44725
45041
  AccountsRegistryPage.displayName = "FinancialAccountsRegistryPage";
44726
- const PersonStatementBound = (props) => React23__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
45042
+ const PersonStatementBound = (props) => React25__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
44727
45043
  PersonStatementBound.displayName = "FinancialPersonStatement";
44728
- const SupplierFinancialSettingsBound = (props) => React23__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
45044
+ const SupplierFinancialSettingsBound = (props) => React25__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
44729
45045
  SupplierFinancialSettingsBound.displayName = "FinancialSupplierSettings";
44730
- const PageComponent = () => React23__default.createElement(FinancialPage, { config, provider, store: store6, registries });
45046
+ const PageComponent = () => React25__default.createElement(FinancialPage, { config, provider, store: store6, registries });
44731
45047
  const AgentMount = createFinancialAgentMount({ config, provider });
44732
45048
  return {
44733
45049
  id: "financial",
@@ -44785,7 +45101,7 @@ function createFinancialPlugin(options) {
44785
45101
  // tem — lá é um ícone no cabeçalho, de qualquer tela.
44786
45102
  headerControls: config.modules.cashRegisters ? [{
44787
45103
  id: "financial.cash-session",
44788
- component: (() => React23__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
45104
+ component: (() => React25__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
44789
45105
  order: 90,
44790
45106
  permission: { feature: "financial", action: "read" }
44791
45107
  }] : [],
@@ -45016,9 +45332,9 @@ function createFinancialPlugin(options) {
45016
45332
  // lado, e a corrida de "Plugins" não é partida ao meio.
45017
45333
  group: "Financeiro",
45018
45334
  component: (() => {
45019
- const FinancialSettingsTab = () => React23__default.createElement(PluginSettingsPanel, {
45335
+ const FinancialSettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
45020
45336
  title: "Financial Settings",
45021
- generalSettings: React23__default.createElement(FinancialGeneralSettings, { config }),
45337
+ generalSettings: React25__default.createElement(FinancialGeneralSettings, { config }),
45022
45338
  registries: settingsRegistries,
45023
45339
  hostPluginId: "financial",
45024
45340
  routeBase: "/settings/financial"
@@ -45043,7 +45359,7 @@ function createFinancialPlugin(options) {
45043
45359
  icon: "HandCoins",
45044
45360
  group: "Financeiro",
45045
45361
  component: (() => {
45046
- const CommissionRulesTab = () => React23__default.createElement(CommissionRulesView, { provider });
45362
+ const CommissionRulesTab = () => React25__default.createElement(CommissionRulesView, { provider });
45047
45363
  CommissionRulesTab.displayName = "CommissionRulesTab";
45048
45364
  return CommissionRulesTab;
45049
45365
  })(),