@fayz-ai/plugin-financial 0.1.2 → 0.1.4

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/dist/FinancialContext.d.ts +2 -0
  3. package/dist/FinancialContext.d.ts.map +1 -1
  4. package/dist/FinancialPage.d.ts.map +1 -1
  5. package/dist/components/PaymentModal.d.ts.map +1 -1
  6. package/dist/components/PersonStatementWidget.d.ts +18 -0
  7. package/dist/components/PersonStatementWidget.d.ts.map +1 -0
  8. package/dist/components/TransferModal.d.ts +7 -0
  9. package/dist/components/TransferModal.d.ts.map +1 -0
  10. package/dist/contract.test.d.ts +2 -0
  11. package/dist/contract.test.d.ts.map +1 -0
  12. package/dist/data/capability.test.d.ts +2 -0
  13. package/dist/data/capability.test.d.ts.map +1 -0
  14. package/dist/data/mock.d.ts.map +1 -1
  15. package/dist/data/supabase.d.ts.map +1 -1
  16. package/dist/data/types.d.ts +13 -2
  17. package/dist/data/types.d.ts.map +1 -1
  18. package/dist/index.cjs +1529 -263
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1437 -171
  23. package/dist/index.js.map +1 -1
  24. package/dist/locales/en.d.ts.map +1 -1
  25. package/dist/locales/pt-BR.d.ts.map +1 -1
  26. package/dist/registries.d.ts.map +1 -1
  27. package/dist/store.d.ts +4 -2
  28. package/dist/store.d.ts.map +1 -1
  29. package/dist/types.d.ts +111 -1
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/views/InvoiceDetailView.d.ts +3 -1
  32. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  33. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  34. package/dist/views/PayablesView.d.ts +5 -1
  35. package/dist/views/PayablesView.d.ts.map +1 -1
  36. package/dist/views/ReceivablesView.d.ts +5 -1
  37. package/dist/views/ReceivablesView.d.ts.map +1 -1
  38. package/dist/views/ReconciliationView.d.ts +3 -0
  39. package/dist/views/ReconciliationView.d.ts.map +1 -0
  40. package/dist/views/StatementsView.d.ts +3 -1
  41. package/dist/views/StatementsView.d.ts.map +1 -1
  42. package/dist/views/navReferrer.d.ts +5 -0
  43. package/dist/views/navReferrer.d.ts.map +1 -0
  44. package/package.json +8 -5
  45. package/src/FinancialContext.tsx +2 -0
  46. package/src/FinancialPage.tsx +32 -17
  47. package/src/components/PaymentModal.tsx +58 -11
  48. package/src/components/PersonStatementWidget.tsx +133 -0
  49. package/src/components/TransferModal.tsx +150 -0
  50. package/src/contract.test.ts +58 -0
  51. package/src/data/capability.test.ts +90 -0
  52. package/src/data/mock.ts +265 -52
  53. package/src/data/supabase.ts +359 -30
  54. package/src/data/types.ts +22 -3
  55. package/src/index.ts +28 -2
  56. package/src/locales/en.ts +76 -0
  57. package/src/locales/pt-BR.ts +76 -0
  58. package/src/migrations/006_extract_fee_amount.sql +20 -0
  59. package/src/migrations/006_rls_policies.sql +37 -0
  60. package/src/migrations/007_reconciliation.sql +32 -0
  61. package/src/migrations/008_split_payment_movements.sql +29 -0
  62. package/src/registries.ts +3 -1
  63. package/src/store.ts +27 -7
  64. package/src/types.ts +126 -1
  65. package/src/views/InvoiceDetailView.tsx +70 -51
  66. package/src/views/InvoiceFormView.tsx +55 -20
  67. package/src/views/PayablesView.tsx +13 -2
  68. package/src/views/ReceivablesView.tsx +13 -2
  69. package/src/views/ReconciliationView.tsx +211 -0
  70. package/src/views/StatementsView.tsx +241 -29
  71. package/src/views/navReferrer.ts +24 -0
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import React11, { useState, useMemo, useEffect, useRef } from 'react';
1
+ import React14, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
2
2
  import { createSafeDataProvider, registerTranslations, useTranslation, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
3
- import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, PageTransition, ModulePage, Button, DashboardCanvas, SubpageHeader, DatePicker, DataTable, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, Card, CardHeader, CardTitle, CardContent, TableWidget } from '@fayz-ai/ui';
4
- import { createPluginContext, dedup, useModuleNavigation, ModuleActionBar, SettingsGroup, ToggleRow, parseViewId, formatCurrency, PersonLink } from '@fayz-ai/saas';
3
+ import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, DashboardCanvas, SubpageHeader, DatePicker, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, Card, CardHeader, CardTitle, CardContent, TableWidget } from '@fayz-ai/ui';
4
+ import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, ModuleActionBar, SettingsGroup, ToggleRow, formatCurrency, parseViewId, PersonLink } from '@fayz-ai/saas';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, Receipt, ChevronDown, Clock, X, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, Ban, LayoutList, Plus, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, FileText, User, Calendar, Hash, CalendarDays, ExternalLink, Building2, Search, Tag, Trash2, FileCheck, ArrowRightLeft, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle } from 'lucide-react';
6
+ import { Wallet, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Receipt, ChevronDown, Clock, CheckCheck, X, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, Ban, LayoutList, Plus, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, FileText, User, Calendar, Hash, CalendarDays, ExternalLink, Building2, Search, Tag, Trash2, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle } from 'lucide-react';
7
7
  import { createPortal } from 'react-dom';
8
8
  import { createStore as createStore$1 } from 'zustand/vanilla';
9
9
 
@@ -249,12 +249,18 @@ var formId = 1;
249
249
  function fid() {
250
250
  return `f${formId++}`;
251
251
  }
252
- function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdate, onRemove, entityLookups }) {
252
+ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdate, onRemove, entityLookups, showTypeSelector }) {
253
253
  const t2 = useTranslation();
254
254
  const total = item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0);
255
255
  const kindLabel = itemTypes.find((tp) => tp.value === item.itemKind)?.label ?? item.itemKind;
256
256
  const hasAdjustments = (item.discount ?? 0) > 0 || (item.surcharge ?? 0) > 0;
257
257
  const [showAdjustments, setShowAdjustments] = useState(hasAdjustments);
258
+ function handleFieldKeyDown(e) {
259
+ if (e.key === "Enter" && !e.shiftKey) {
260
+ e.preventDefault();
261
+ onToggle();
262
+ }
263
+ }
258
264
  return /* @__PURE__ */ jsxs("div", { className: "border-b last:border-0", children: [
259
265
  /* @__PURE__ */ jsxs(
260
266
  "div",
@@ -291,7 +297,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
291
297
  }
292
298
  ),
293
299
  expanded && /* @__PURE__ */ jsxs("div", { className: "px-4 pb-4 pt-1 ml-8 space-y-3 bg-muted/10", children: [
294
- /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: itemTypes.map((tp) => /* @__PURE__ */ jsx(
300
+ showTypeSelector && /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: itemTypes.map((tp) => /* @__PURE__ */ jsx(
295
301
  "button",
296
302
  {
297
303
  onClick: () => onUpdate({ itemKind: tp.value }),
@@ -340,6 +346,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
340
346
  type: "text",
341
347
  value: item.description,
342
348
  onChange: (e) => onUpdate({ description: e.target.value }),
349
+ onKeyDown: handleFieldKeyDown,
343
350
  placeholder: t2("financial.invoiceForm.itemDescription"),
344
351
  autoFocus: true,
345
352
  className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
@@ -355,11 +362,12 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
355
362
  min: 1,
356
363
  value: item.quantity,
357
364
  onChange: (e) => onUpdate({ quantity: Number(e.target.value) || 1 }),
365
+ onKeyDown: handleFieldKeyDown,
358
366
  className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 py-1.5 text-sm text-center focus:outline-none focus:ring-2 focus:ring-ring"
359
367
  }
360
368
  )
361
369
  ] }),
362
- /* @__PURE__ */ jsx("div", { className: "col-span-4", children: /* @__PURE__ */ jsx(
370
+ /* @__PURE__ */ jsx("div", { className: "col-span-4", onKeyDown: handleFieldKeyDown, children: /* @__PURE__ */ jsx(
363
371
  CurrencyInput,
364
372
  {
365
373
  label: t2("financial.invoiceForm.unitPrice"),
@@ -395,6 +403,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
395
403
  step: 0.01,
396
404
  value: item.discount ?? 0,
397
405
  onChange: (e) => onUpdate({ discount: Number(e.target.value) || 0 }),
406
+ onKeyDown: handleFieldKeyDown,
398
407
  className: "flex-1 bg-transparent px-2 py-1 text-xs outline-none"
399
408
  }
400
409
  )
@@ -412,6 +421,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
412
421
  step: 0.01,
413
422
  value: item.surcharge ?? 0,
414
423
  onChange: (e) => onUpdate({ surcharge: Number(e.target.value) || 0 }),
424
+ onKeyDown: handleFieldKeyDown,
415
425
  className: "flex-1 bg-transparent px-2 py-1 text-xs outline-none"
416
426
  }
417
427
  )
@@ -475,6 +485,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
475
485
  const config = useFinancialConfig();
476
486
  const { currency, itemTypes, locations } = config;
477
487
  const provider = useFinancialProvider();
488
+ const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
489
+ const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
478
490
  const createInvoice = useFinancialStore((s) => s.createInvoice);
479
491
  const hasLocations = locations.length > 0;
480
492
  const isEdit = !!editId;
@@ -585,6 +597,10 @@ function InvoiceFormView({ direction, editId, onSaved }) {
585
597
  setExpandedItemId(newItem._id);
586
598
  setAddingItem(false);
587
599
  }
600
+ function startAddItem() {
601
+ if (hasItemLookups) setAddingItem(true);
602
+ else addItemOfKind(defaultItemKind);
603
+ }
588
604
  function updateItem(id, data) {
589
605
  setItems(items.map((it) => it._id === id ? { ...it, ...data } : it));
590
606
  }
@@ -804,7 +820,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
804
820
  /* @__PURE__ */ jsxs(
805
821
  "button",
806
822
  {
807
- onClick: () => setAddingItem(true),
823
+ onClick: startAddItem,
808
824
  className: "inline-flex items-center gap-1.5 rounded-lg 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",
809
825
  children: [
810
826
  /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
@@ -826,7 +842,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
826
842
  items.length === 0 && !addingItem ? /* @__PURE__ */ jsxs("div", { className: "py-10 text-center", children: [
827
843
  /* @__PURE__ */ jsx(LayoutList, { className: "h-8 w-8 text-muted-foreground/30 mx-auto mb-2" }),
828
844
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.invoiceForm.noItems") }),
829
- /* @__PURE__ */ jsx("button", { onClick: () => setAddingItem(true), className: "text-xs text-primary hover:underline mt-1", children: t2("financial.invoiceForm.addFirstItem") })
845
+ /* @__PURE__ */ jsx("button", { onClick: startAddItem, className: "text-xs text-primary hover:underline mt-1", children: t2("financial.invoiceForm.addFirstItem") })
830
846
  ] }) : items.map((item, i) => /* @__PURE__ */ jsx(
831
847
  ItemRow,
832
848
  {
@@ -841,7 +857,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
841
857
  removeItem(item._id);
842
858
  if (expandedItemId === item._id) setExpandedItemId(null);
843
859
  },
844
- entityLookups: config.entityLookups
860
+ entityLookups: config.entityLookups,
861
+ showTypeSelector: hasItemLookups
845
862
  },
846
863
  item._id
847
864
  ))
@@ -937,6 +954,15 @@ function InvoiceFormView({ direction, editId, onSaved }) {
937
954
  ` })
938
955
  ] });
939
956
  }
957
+ var ACCOUNT_TYPES_FOR_TX = {
958
+ cash: ["cash_register"],
959
+ credit_card: ["credit_card", "bank_account"],
960
+ debit_card: ["bank_account", "cash_register"],
961
+ pix: ["bank_account", "digital_wallet"],
962
+ bank_transfer: ["bank_account"],
963
+ check: ["bank_account"],
964
+ voucher: ["bank_account", "digital_wallet"]
965
+ };
940
966
  var TYPE_ICONS = {
941
967
  cash: Banknote,
942
968
  pix: QrCode,
@@ -996,9 +1022,19 @@ function PaymentModal({ movement, onClose, onPaid }) {
996
1022
  () => paymentMethods.filter((m) => m.paymentMethodTypeId === selectedTypeId),
997
1023
  [paymentMethods, selectedTypeId]
998
1024
  );
999
- const needsBankAccount = txType === "bank_transfer" || txType === "pix" || txType === "check";
1000
1025
  const needsCard = txType === "credit_card" || txType === "debit_card";
1001
1026
  const needsCardInstallments = txType === "credit_card";
1027
+ const eligibleAccounts = useMemo(() => {
1028
+ const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[txType];
1029
+ if (!allowed || !allowed.length) return bankAccounts;
1030
+ const filtered = bankAccounts.filter((a) => allowed.includes(a.accountType));
1031
+ return filtered.length ? filtered : bankAccounts;
1032
+ }, [bankAccounts, selectedType, txType]);
1033
+ useEffect(() => {
1034
+ if (selectedTypeId && !bankAccountId && eligibleAccounts.length) {
1035
+ setBankAccountId(eligibleAccounts[0].id);
1036
+ }
1037
+ }, [selectedTypeId, eligibleAccounts]);
1002
1038
  function handleSelectType(typeId) {
1003
1039
  setSelectedTypeId(typeId);
1004
1040
  setExpandMethodPicker(false);
@@ -1011,8 +1047,14 @@ function PaymentModal({ movement, onClose, onPaid }) {
1011
1047
  setVisible(false);
1012
1048
  setTimeout(onClose, 200);
1013
1049
  }
1050
+ function handleKeyDown(e) {
1051
+ if (e.key !== "Enter" || e.shiftKey) return;
1052
+ if (e.target.tagName === "TEXTAREA") return;
1053
+ e.preventDefault();
1054
+ if (amount > 0 && selectedTypeId && bankAccountId && !saving) void handlePay();
1055
+ }
1014
1056
  async function handlePay() {
1015
- if (amount <= 0 || !selectedTypeId) return;
1057
+ if (amount <= 0 || !selectedTypeId || !bankAccountId) return;
1016
1058
  setSaving(true);
1017
1059
  try {
1018
1060
  await payMovement({
@@ -1023,7 +1065,8 @@ function PaymentModal({ movement, onClose, onPaid }) {
1023
1065
  paymentMethodTypeId: selectedTypeId || void 0,
1024
1066
  bankAccountId: bankAccountId || void 0,
1025
1067
  cardBrand: needsCard ? cardBrand || void 0 : void 0,
1026
- cardInstallments: needsCardInstallments ? cardInstallments : void 0
1068
+ cardInstallments: needsCardInstallments ? cardInstallments : void 0,
1069
+ notes: notes.trim() || void 0
1027
1070
  });
1028
1071
  setVisible(false);
1029
1072
  setTimeout(onPaid, 200);
@@ -1049,6 +1092,7 @@ function PaymentModal({ movement, onClose, onPaid }) {
1049
1092
  transform: visible ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)"
1050
1093
  },
1051
1094
  onClick: (e) => e.stopPropagation(),
1095
+ onKeyDown: handleKeyDown,
1052
1096
  children: [
1053
1097
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b sticky top-0 bg-card z-10", children: [
1054
1098
  /* @__PURE__ */ jsxs("div", { children: [
@@ -1158,11 +1202,17 @@ function PaymentModal({ movement, onClose, onPaid }) {
1158
1202
  ] }, n)) })
1159
1203
  ] })
1160
1204
  ] }),
1161
- needsBankAccount && bankAccounts.length > 0 && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
1205
+ /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
1162
1206
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.bankAccount") }),
1163
- /* @__PURE__ */ jsxs("select", { value: bankAccountId, onChange: (e) => setBankAccountId(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
1207
+ eligibleAccounts.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
1208
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.payment.noAccounts") }),
1209
+ /* @__PURE__ */ jsxs("a", { href: "#/settings/financial/bank-accounts", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
1210
+ t2("financial.payment.configureAccounts"),
1211
+ " \u2192"
1212
+ ] })
1213
+ ] }) : /* @__PURE__ */ jsxs("select", { value: bankAccountId, onChange: (e) => setBankAccountId(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
1164
1214
  /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.selectAccount") }),
1165
- bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
1215
+ eligibleAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
1166
1216
  ] })
1167
1217
  ] }),
1168
1218
  /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "150ms" }, children: [
@@ -1188,7 +1238,7 @@ function PaymentModal({ movement, onClose, onPaid }) {
1188
1238
  variant: "default",
1189
1239
  size: "sm",
1190
1240
  onClick: handlePay,
1191
- disabled: amount <= 0 || !selectedTypeId || saving,
1241
+ disabled: amount <= 0 || !selectedTypeId || !bankAccountId || saving,
1192
1242
  children: [
1193
1243
  /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
1194
1244
  saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: formatCurrency(amount, currency) })
@@ -1213,10 +1263,11 @@ var STATUS_COLORS = {
1213
1263
  draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
1214
1264
  cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
1215
1265
  };
1216
- function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1266
+ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel }) {
1217
1267
  const t2 = useTranslation();
1218
1268
  const config = useFinancialConfig();
1219
1269
  const { currency } = config;
1270
+ const parentLabel = backLabel ?? (direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"));
1220
1271
  const provider = useFinancialProvider();
1221
1272
  const paymentMethods = useFinancialStore((s) => s.paymentMethods);
1222
1273
  const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
@@ -1228,6 +1279,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1228
1279
  const [invoice, setInvoice] = useState(null);
1229
1280
  const [items, setItems] = useState([]);
1230
1281
  const [movements, setMovements] = useState([]);
1282
+ const [payments, setPayments] = useState([]);
1231
1283
  const [loading, setLoading] = useState(true);
1232
1284
  const [payingMovement, setPayingMovement] = useState(null);
1233
1285
  const [expandedMovId, setExpandedMovId] = useState(null);
@@ -1248,6 +1300,9 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1248
1300
  setMovements(
1249
1301
  movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
1250
1302
  );
1303
+ setPayments(
1304
+ movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "payment").sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? ""))
1305
+ );
1251
1306
  setLoading(false);
1252
1307
  }
1253
1308
  useEffect(() => {
@@ -1287,7 +1342,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1287
1342
  }
1288
1343
  if (loading) {
1289
1344
  return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1290
- /* @__PURE__ */ jsx(SubpageHeader, { title: "", onBack, parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables") }),
1345
+ /* @__PURE__ */ jsx(SubpageHeader, { title: "", onBack, parentLabel }),
1291
1346
  /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card shadow-sm overflow-hidden", children: [
1292
1347
  /* @__PURE__ */ jsx("div", { className: "h-1 bg-muted animate-pulse" }),
1293
1348
  /* @__PURE__ */ jsxs("div", { className: "p-5 pb-4", children: [
@@ -1341,7 +1396,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1341
1396
  }
1342
1397
  if (!invoice) {
1343
1398
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
1344
- /* @__PURE__ */ jsx(SubpageHeader, { title: t2("financial.invoice.notFound"), onBack, parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables") }),
1399
+ /* @__PURE__ */ jsx(SubpageHeader, { title: t2("financial.invoice.notFound"), onBack, parentLabel }),
1345
1400
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.invoice.notFoundDesc") })
1346
1401
  ] });
1347
1402
  }
@@ -1388,7 +1443,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1388
1443
  {
1389
1444
  title: invoice.fiscalNumber ? `#${invoice.fiscalNumber}` : invoice.contactName ?? invoice.invoiceDate,
1390
1445
  onBack,
1391
- parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
1446
+ parentLabel,
1392
1447
  actions: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
1393
1448
  invoice.status !== "cancelled" && invoice.status !== "paid" && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onEdit, children: [
1394
1449
  /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
@@ -1530,9 +1585,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1530
1585
  const isExpanded = expandedMovId === mov.id;
1531
1586
  const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
1532
1587
  const MovIcon = movStatus.icon;
1533
- const method = paymentMethods.find((m) => m.id === mov.paymentMethodId);
1534
- const methodType = paymentMethodTypes.find((t3) => t3.id === (method?.paymentMethodTypeId ?? mov.paymentMethodTypeId));
1535
- const account = bankAccounts.find((a) => a.id === mov.bankAccountId);
1588
+ const instPayments = payments.filter((p) => (p.installmentNumber ?? null) === (mov.installmentNumber ?? null));
1536
1589
  return /* @__PURE__ */ jsxs("div", { children: [
1537
1590
  /* @__PURE__ */ jsxs(
1538
1591
  "div",
@@ -1606,43 +1659,53 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1606
1659
  /* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
1607
1660
  /* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
1608
1661
  /* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
1609
- ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
1610
- methodType && /* @__PURE__ */ jsxs(Fragment, { children: [
1611
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: methodType.name }),
1612
- method && /* @__PURE__ */ jsxs("span", { children: [
1613
- "(",
1614
- method.name,
1615
- ")"
1616
- ] })
1617
- ] }),
1618
- mov.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
1619
- methodType && /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1620
- /* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
1621
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: mov.cardBrand }),
1622
- mov.cardInstallments && /* @__PURE__ */ jsxs("span", { children: [
1623
- mov.cardInstallments,
1624
- "x"
1662
+ ] }) : instPayments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
1663
+ const pMethod = paymentMethods.find((m) => m.id === p.paymentMethodId);
1664
+ const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
1665
+ const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
1666
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
1667
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
1668
+ pType && /* @__PURE__ */ jsxs(Fragment, { children: [
1669
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1670
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pType.name }),
1671
+ pMethod && /* @__PURE__ */ jsxs("span", { children: [
1672
+ "(",
1673
+ pMethod.name,
1674
+ ")"
1675
+ ] })
1676
+ ] }),
1677
+ p.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
1678
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1679
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
1680
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: p.cardBrand }),
1681
+ p.cardInstallments && /* @__PURE__ */ jsxs("span", { children: [
1682
+ p.cardInstallments,
1683
+ "x"
1684
+ ] })
1685
+ ] }),
1686
+ pAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
1687
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1688
+ /* @__PURE__ */ jsx(Building2, { className: "h-3 w-3 shrink-0" }),
1689
+ /* @__PURE__ */ jsx("span", { children: pAccount.name })
1690
+ ] }),
1691
+ (p.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1692
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1693
+ /* @__PURE__ */ jsxs("span", { className: "text-warning", children: [
1694
+ t2("financial.statements.columnFee"),
1695
+ " ",
1696
+ formatCurrency(p.feeAmount ?? 0, currency)
1697
+ ] })
1698
+ ] }),
1699
+ p.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
1700
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1701
+ /* @__PURE__ */ jsx("span", { children: p.paymentDate })
1702
+ ] }),
1703
+ p.notes && /* @__PURE__ */ jsxs(Fragment, { children: [
1704
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1705
+ /* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", children: p.notes })
1625
1706
  ] })
1626
- ] }),
1627
- account && /* @__PURE__ */ jsxs(Fragment, { children: [
1628
- (methodType || mov.cardBrand) && /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1629
- /* @__PURE__ */ jsx(Building2, { className: "h-3 w-3 shrink-0" }),
1630
- /* @__PURE__ */ jsx("span", { children: account.name })
1631
- ] }),
1632
- mov.paidAmount > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1633
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1634
- /* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(mov.paidAmount, currency) })
1635
- ] }),
1636
- mov.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
1637
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1638
- /* @__PURE__ */ jsx("span", { children: mov.paymentDate })
1639
- ] }),
1640
- mov.notes && /* @__PURE__ */ jsxs(Fragment, { children: [
1641
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
1642
- /* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", children: mov.notes })
1643
- ] }),
1644
- !methodType && !mov.cardBrand && !account && !mov.notes && /* @__PURE__ */ jsx("span", { children: t2("financial.invoice.noDetails") })
1645
- ] }) }) })
1707
+ ] }, p.id);
1708
+ }) }) }) })
1646
1709
  }
1647
1710
  )
1648
1711
  ] }, mov.id);
@@ -1706,7 +1769,25 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1706
1769
  ` })
1707
1770
  ] });
1708
1771
  }
1709
- function PayablesView({ intent, onNavigate }) {
1772
+
1773
+ // src/views/navReferrer.ts
1774
+ var REFERRER_LABEL_KEYS = {
1775
+ summary: "financial.nav.summary",
1776
+ statements: "financial.nav.statements",
1777
+ "cash-registers": "financial.nav.cashRegisters",
1778
+ reconciliation: "financial.nav.reconciliation",
1779
+ commissions: "financial.nav.commissions",
1780
+ cards: "financial.nav.cards",
1781
+ "receivables-list": "financial.nav.receivables",
1782
+ "payables-list": "financial.nav.payables"
1783
+ };
1784
+ function referrerLabel(view, t2) {
1785
+ if (!view) return null;
1786
+ const key = REFERRER_LABEL_KEYS[view];
1787
+ return key ? t2(key) : null;
1788
+ }
1789
+ function PayablesView({ intent, onNavigate, previousView, back }) {
1790
+ const t2 = useTranslation();
1710
1791
  if (intent.mode === "new") {
1711
1792
  return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "debit", onSaved: (id) => onNavigate(id ? `payables-detail:${id}` : "payables-list") });
1712
1793
  }
@@ -1714,12 +1795,14 @@ function PayablesView({ intent, onNavigate }) {
1714
1795
  return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "debit", editId: intent.editId, onSaved: () => onNavigate(`payables-detail:${intent.editId}`) });
1715
1796
  }
1716
1797
  if (intent.mode === "detail" && intent.editId) {
1798
+ const prevLabel = referrerLabel(previousView, t2);
1717
1799
  return /* @__PURE__ */ jsx(
1718
1800
  InvoiceDetailView,
1719
1801
  {
1720
1802
  invoiceId: intent.editId,
1721
1803
  direction: "debit",
1722
- onBack: () => onNavigate("payables-list"),
1804
+ backLabel: prevLabel ?? void 0,
1805
+ onBack: prevLabel && back ? () => back("payables-list") : () => onNavigate("payables-list"),
1723
1806
  onEdit: () => onNavigate(`payables-edit:${intent.editId}`)
1724
1807
  }
1725
1808
  );
@@ -1733,7 +1816,8 @@ function PayablesView({ intent, onNavigate }) {
1733
1816
  }
1734
1817
  );
1735
1818
  }
1736
- function ReceivablesView({ intent, onNavigate }) {
1819
+ function ReceivablesView({ intent, onNavigate, previousView, back }) {
1820
+ const t2 = useTranslation();
1737
1821
  if (intent.mode === "new") {
1738
1822
  return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "credit", onSaved: (id) => onNavigate(id ? `receivables-detail:${id}` : "receivables-list") });
1739
1823
  }
@@ -1741,12 +1825,14 @@ function ReceivablesView({ intent, onNavigate }) {
1741
1825
  return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "credit", editId: intent.editId, onSaved: () => onNavigate(`receivables-detail:${intent.editId}`) });
1742
1826
  }
1743
1827
  if (intent.mode === "detail" && intent.editId) {
1828
+ const prevLabel = referrerLabel(previousView, t2);
1744
1829
  return /* @__PURE__ */ jsx(
1745
1830
  InvoiceDetailView,
1746
1831
  {
1747
1832
  invoiceId: intent.editId,
1748
1833
  direction: "credit",
1749
- onBack: () => onNavigate("receivables-list"),
1834
+ backLabel: prevLabel ?? void 0,
1835
+ onBack: prevLabel && back ? () => back("receivables-list") : () => onNavigate("receivables-list"),
1750
1836
  onEdit: () => onNavigate(`receivables-edit:${intent.editId}`)
1751
1837
  }
1752
1838
  );
@@ -2245,32 +2331,201 @@ function OpenAnotherCollapsible({
2245
2331
  expanded && /* @__PURE__ */ jsx(CashOpeningPanel, { cashAccounts: availableAccounts, currency, onOpen })
2246
2332
  ] });
2247
2333
  }
2248
- function StatementsView() {
2334
+ function TransferModal({ onClose, onDone, defaultFromId }) {
2335
+ const t2 = useTranslation();
2336
+ const { currency } = useFinancialConfig();
2337
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
2338
+ const createTransfer = useFinancialStore((s) => s.createTransfer);
2339
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
2340
+ const [fromAccountId, setFromAccountId] = useState(defaultFromId ?? "");
2341
+ const [toAccountId, setToAccountId] = useState("");
2342
+ const [amount, setAmount] = useState(0);
2343
+ const [date, setDate] = useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2344
+ const [notes, setNotes] = useState("");
2345
+ const [saving, setSaving] = useState(false);
2346
+ const [visible, setVisible] = useState(false);
2347
+ useEffect(() => {
2348
+ fetchBankAccounts();
2349
+ requestAnimationFrame(() => setVisible(true));
2350
+ }, []);
2351
+ function handleClose() {
2352
+ setVisible(false);
2353
+ setTimeout(onClose, 200);
2354
+ }
2355
+ const sameAccount = !!fromAccountId && fromAccountId === toAccountId;
2356
+ const canSubmit = !!fromAccountId && !!toAccountId && !sameAccount && amount > 0 && !saving;
2357
+ function handleKeyDown(e) {
2358
+ if (e.key !== "Enter" || e.shiftKey) return;
2359
+ if (e.target.tagName === "TEXTAREA") return;
2360
+ e.preventDefault();
2361
+ if (canSubmit) void handleSubmit();
2362
+ }
2363
+ async function handleSubmit() {
2364
+ if (!canSubmit) return;
2365
+ setSaving(true);
2366
+ try {
2367
+ await createTransfer({ fromAccountId, toAccountId, amount, date, notes: notes || void 0 });
2368
+ setVisible(false);
2369
+ setTimeout(onDone, 200);
2370
+ } catch {
2371
+ } finally {
2372
+ setSaving(false);
2373
+ }
2374
+ }
2375
+ const selectClass = "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm";
2376
+ return createPortal(
2377
+ /* @__PURE__ */ jsx(
2378
+ "div",
2379
+ {
2380
+ className: "fixed inset-0 z-50 flex items-center justify-center transition-colors duration-200",
2381
+ style: { backgroundColor: visible ? "rgba(0,0,0,0.5)" : "rgba(0,0,0,0)" },
2382
+ onClick: handleClose,
2383
+ children: /* @__PURE__ */ jsxs(
2384
+ "div",
2385
+ {
2386
+ className: "w-full max-w-md rounded-modal border bg-card shadow-lg mx-4 transition-all duration-200 max-h-[90vh] overflow-y-auto",
2387
+ style: { opacity: visible ? 1 : 0, transform: visible ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)" },
2388
+ onClick: (e) => e.stopPropagation(),
2389
+ onKeyDown: handleKeyDown,
2390
+ children: [
2391
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b", children: [
2392
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2393
+ /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-4 w-4 text-warning" }),
2394
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: t2("financial.transfer.title") })
2395
+ ] }),
2396
+ /* @__PURE__ */ jsx("button", { onClick: handleClose, className: "p-1 text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
2397
+ ] }),
2398
+ /* @__PURE__ */ jsxs("div", { className: "px-5 py-4 space-y-4", children: [
2399
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
2400
+ /* @__PURE__ */ jsxs("div", { children: [
2401
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.transfer.from") }),
2402
+ /* @__PURE__ */ jsxs("select", { value: fromAccountId, onChange: (e) => setFromAccountId(e.target.value), className: selectClass, children: [
2403
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.transfer.selectAccount") }),
2404
+ bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
2405
+ ] })
2406
+ ] }),
2407
+ /* @__PURE__ */ jsxs("div", { children: [
2408
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.transfer.to") }),
2409
+ /* @__PURE__ */ jsxs("select", { value: toAccountId, onChange: (e) => setToAccountId(e.target.value), className: selectClass, children: [
2410
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.transfer.selectAccount") }),
2411
+ bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
2412
+ ] })
2413
+ ] })
2414
+ ] }),
2415
+ sameAccount && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: t2("financial.transfer.sameAccount") }),
2416
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
2417
+ /* @__PURE__ */ jsx(
2418
+ CurrencyInput,
2419
+ {
2420
+ label: t2("financial.transfer.amount"),
2421
+ value: amount,
2422
+ onChange: setAmount,
2423
+ symbol: currency.symbol,
2424
+ locale: currency.locale,
2425
+ currencyCode: currency.code
2426
+ }
2427
+ ),
2428
+ /* @__PURE__ */ jsxs("div", { children: [
2429
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.transfer.date") }),
2430
+ /* @__PURE__ */ jsx(DatePicker, { value: date, onChange: setDate, className: "mt-1" })
2431
+ ] })
2432
+ ] }),
2433
+ /* @__PURE__ */ jsxs("div", { children: [
2434
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.transfer.notes") }),
2435
+ /* @__PURE__ */ jsx(
2436
+ "input",
2437
+ {
2438
+ type: "text",
2439
+ value: notes,
2440
+ onChange: (e) => setNotes(e.target.value),
2441
+ placeholder: t2("financial.transfer.optionalNotes"),
2442
+ className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
2443
+ }
2444
+ )
2445
+ ] })
2446
+ ] }),
2447
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-4 border-t", children: [
2448
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, children: t2("financial.transfer.cancel") }),
2449
+ /* @__PURE__ */ jsxs(Button, { variant: "default", size: "sm", onClick: handleSubmit, disabled: !canSubmit, children: [
2450
+ /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
2451
+ saving ? t2("financial.transfer.processing") : t2("financial.transfer.submit")
2452
+ ] })
2453
+ ] })
2454
+ ]
2455
+ }
2456
+ )
2457
+ }
2458
+ ),
2459
+ document.body
2460
+ );
2461
+ }
2462
+ var ALL = "all";
2463
+ function ReconcileMarker({ kind, label }) {
2464
+ return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 150, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
2465
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0 cursor-default", children: kind === "reconciled" ? /* @__PURE__ */ jsx(CheckCheck, { className: "h-3 w-3 text-success/70" }) : /* @__PURE__ */ jsx(Landmark, { className: "h-3 w-3 text-muted-foreground/40" }) }) }),
2466
+ /* @__PURE__ */ jsx(TooltipContent, { className: "text-xs", children: label })
2467
+ ] }) });
2468
+ }
2469
+ function ReconciledWithRef({ refLabel, tooltip, onOpen }) {
2470
+ const inner = /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-muted-foreground max-w-[160px]", children: [
2471
+ /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-2.5 w-2.5 shrink-0" }),
2472
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: refLabel })
2473
+ ] });
2474
+ return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 150, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
2475
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: onOpen ? /* @__PURE__ */ jsx("button", { onClick: onOpen, className: "hover:text-primary transition-colors", children: inner }) : /* @__PURE__ */ jsx("span", { className: "cursor-default", children: inner }) }),
2476
+ /* @__PURE__ */ jsx(TooltipContent, { className: "text-xs", children: tooltip })
2477
+ ] }) });
2478
+ }
2479
+ function StatementsView({ onNavigate }) {
2249
2480
  const t2 = useTranslation();
2250
2481
  const { currency } = useFinancialConfig();
2251
2482
  const bankAccounts = useFinancialStore((s) => s.bankAccounts);
2252
- const statementEntries = useFinancialStore((s) => s.statementEntries);
2483
+ const statement = useFinancialStore((s) => s.statement);
2253
2484
  const statementLoading = useFinancialStore((s) => s.statementLoading);
2485
+ const paymentMethods = useFinancialStore((s) => s.paymentMethods);
2486
+ const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
2254
2487
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
2488
+ const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
2255
2489
  const fetchStatement = useFinancialStore((s) => s.fetchStatement);
2256
2490
  const [accountId, setAccountId] = useState("");
2257
2491
  const [dateFrom, setDateFrom] = useState(() => {
2258
2492
  const d = /* @__PURE__ */ new Date();
2259
- d.setMonth(d.getMonth() - 1);
2493
+ d.setMonth(d.getMonth() - 3);
2260
2494
  return d.toISOString().slice(0, 10);
2261
2495
  });
2262
2496
  const [dateTo, setDateTo] = useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2497
+ const [showTransfer, setShowTransfer] = useState(false);
2263
2498
  useEffect(() => {
2264
2499
  fetchBankAccounts();
2500
+ fetchPaymentMethods();
2265
2501
  }, []);
2502
+ const methodName = useMemo(() => {
2503
+ const byId = new Map(paymentMethods.map((m) => [m.id, m.name]));
2504
+ const typeById = new Map(paymentMethodTypes.map((tp) => [tp.id, tp.name]));
2505
+ return (mov) => {
2506
+ if (mov.paymentMethodId && byId.has(mov.paymentMethodId)) return byId.get(mov.paymentMethodId);
2507
+ if (mov.paymentMethodTypeId && typeById.has(mov.paymentMethodTypeId)) return typeById.get(mov.paymentMethodTypeId);
2508
+ if (mov.cardBrand) return mov.cardBrand;
2509
+ return "\u2014";
2510
+ };
2511
+ }, [paymentMethods, paymentMethodTypes]);
2512
+ function openInvoice(e) {
2513
+ if (!onNavigate || !e.movement.invoiceId) return;
2514
+ const base = e.movement.direction === "credit" ? "receivables" : "payables";
2515
+ onNavigate(`${base}-detail:${e.movement.invoiceId}`);
2516
+ }
2266
2517
  useEffect(() => {
2267
2518
  if (accountId) {
2268
- fetchStatement({ bankAccountId: accountId, dateRange: { from: dateFrom, to: dateTo } });
2519
+ fetchStatement({
2520
+ bankAccountId: accountId === ALL ? void 0 : accountId,
2521
+ dateRange: { from: dateFrom, to: dateTo }
2522
+ });
2269
2523
  }
2270
2524
  }, [accountId, dateFrom, dateTo]);
2271
2525
  useEffect(() => {
2272
- if (!accountId && bankAccounts.length > 0) setAccountId(bankAccounts[0].id);
2273
- }, [bankAccounts]);
2526
+ if (!accountId) setAccountId(ALL);
2527
+ }, []);
2528
+ const fmt = (n) => formatCurrency(n, currency);
2274
2529
  const columns = useMemo(() => [
2275
2530
  {
2276
2531
  id: "date",
@@ -2280,29 +2535,112 @@ function StatementsView() {
2280
2535
  {
2281
2536
  id: "description",
2282
2537
  header: t2("financial.statements.columnDescription"),
2283
- cell: ({ row }) => /* @__PURE__ */ jsxs("div", { children: [
2284
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: row.original.invoice?.contactName || row.original.movement.notes || "Transaction" }),
2285
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground capitalize", children: row.original.movement.movementKind })
2286
- ] })
2538
+ cell: ({ row }) => {
2539
+ const e = row.original;
2540
+ const isTransfer = e.entryKind !== "movement";
2541
+ const memo = e.movement.notes;
2542
+ const title = e.invoice?.contactName || (isTransfer ? t2("financial.statements.transfer") : memo || "Transaction");
2543
+ const memoLine = memo && memo !== title ? memo : void 0;
2544
+ const doc = e.invoice?.fiscalNumber;
2545
+ const inst = e.movement.installmentNumber;
2546
+ const canOpen = !!onNavigate && !!e.movement.invoiceId;
2547
+ return /* @__PURE__ */ jsxs("div", { children: [
2548
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2549
+ /* @__PURE__ */ jsx(PersonLink, { personId: e.invoice?.contactId, name: title, size: "sm" }),
2550
+ doc && (canOpen ? /* @__PURE__ */ jsx(
2551
+ "button",
2552
+ {
2553
+ onClick: () => openInvoice(e),
2554
+ className: "text-[10px] text-muted-foreground font-mono hover:text-primary hover:underline",
2555
+ title: t2("financial.statements.viewInvoice"),
2556
+ children: doc
2557
+ }
2558
+ ) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: doc })),
2559
+ isTransfer && /* @__PURE__ */ jsx("span", { className: "text-[9px] uppercase tracking-wide px-1 py-px rounded bg-warning/15 text-warning font-semibold", children: t2("financial.statements.transfer") }),
2560
+ e.movement.reconciledAt ? /* @__PURE__ */ jsx(ReconcileMarker, { kind: "reconciled", label: t2("financial.statements.reconciled") }) : e.movement.externalSource ? /* @__PURE__ */ jsx(ReconcileMarker, { kind: "imported", label: t2("financial.statements.imported") }) : null,
2561
+ e.reconciledWith && (() => {
2562
+ const rw = e.reconciledWith;
2563
+ const refLabel = rw.fiscalNumber || rw.contactName || rw.description || rw.movementId.slice(0, 8);
2564
+ const base = e.movement.direction === "credit" ? "receivables" : "payables";
2565
+ const onOpen = onNavigate && rw.invoiceId ? () => onNavigate(`${base}-detail:${rw.invoiceId}`) : void 0;
2566
+ return /* @__PURE__ */ jsx(
2567
+ ReconciledWithRef,
2568
+ {
2569
+ refLabel,
2570
+ tooltip: t2("financial.statements.reconciledWith", { ref: refLabel }),
2571
+ onOpen
2572
+ }
2573
+ );
2574
+ })()
2575
+ ] }),
2576
+ memoLine && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-foreground/80", children: memoLine }),
2577
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2578
+ /* @__PURE__ */ jsx("span", { className: "capitalize", children: isTransfer && e.counterAccountName ? e.entryKind === "transfer-in" ? `\u2190 ${e.counterAccountName}` : `\u2192 ${e.counterAccountName}` : e.movement.movementKind }),
2579
+ inst ? ` \xB7 ${t2("financial.statements.installmentShort", { n: inst })}` : ""
2580
+ ] })
2581
+ ] });
2582
+ }
2583
+ },
2584
+ {
2585
+ id: "method",
2586
+ header: t2("financial.statements.columnMethod"),
2587
+ cell: ({ row }) => {
2588
+ const mov = row.original.movement;
2589
+ const name = methodName(mov);
2590
+ return /* @__PURE__ */ jsxs("div", { children: [
2591
+ /* @__PURE__ */ jsx("p", { className: "text-xs", children: name }),
2592
+ mov.cardInstallments && mov.cardInstallments > 1 && /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2593
+ mov.cardInstallments,
2594
+ "x"
2595
+ ] })
2596
+ ] });
2597
+ }
2287
2598
  },
2288
2599
  {
2289
2600
  id: "debit",
2290
2601
  header: t2("financial.statements.columnDebit"),
2291
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block text-destructive text-xs", children: row.original.movement.direction === "debit" ? formatCurrency(row.original.movement.paidAmount, currency) : "" })
2602
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block text-destructive text-xs", children: row.original.movement.direction === "debit" ? fmt(row.original.net) : "" })
2292
2603
  },
2293
2604
  {
2294
2605
  id: "credit",
2295
2606
  header: t2("financial.statements.columnCredit"),
2296
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block text-success text-xs", children: row.original.movement.direction === "credit" ? formatCurrency(row.original.movement.paidAmount, currency) : "" })
2607
+ cell: ({ row }) => {
2608
+ const e = row.original;
2609
+ if (e.movement.direction !== "credit") return /* @__PURE__ */ jsx("span", { className: "block text-right" });
2610
+ return /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
2611
+ /* @__PURE__ */ jsx("span", { className: "block text-success text-xs", children: fmt(e.net) }),
2612
+ e.fee > 0 && /* @__PURE__ */ jsxs("span", { className: "block text-[10px] text-muted-foreground", children: [
2613
+ fmt(e.gross),
2614
+ " \u2212 ",
2615
+ fmt(e.fee)
2616
+ ] })
2617
+ ] });
2618
+ }
2619
+ },
2620
+ {
2621
+ id: "fee",
2622
+ header: t2("financial.statements.columnFee"),
2623
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block text-warning text-xs", children: row.original.fee > 0 ? fmt(row.original.fee) : "" })
2297
2624
  },
2298
2625
  {
2299
2626
  id: "balance",
2300
2627
  header: t2("financial.statements.columnBalance"),
2301
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block font-medium text-xs", children: formatCurrency(row.original.runningBalance, currency) })
2628
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block font-medium text-xs", children: fmt(row.original.runningBalance) })
2302
2629
  }
2303
- ], [currency, t2]);
2630
+ ], [currency, t2, methodName, onNavigate]);
2631
+ const entries = statement?.entries ?? [];
2304
2632
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2305
- /* @__PURE__ */ jsx(SubpageHeader, { title: t2("financial.statements.title"), subtitle: t2("financial.statements.subtitle") }),
2633
+ /* @__PURE__ */ jsx(
2634
+ SubpageHeader,
2635
+ {
2636
+ title: t2("financial.statements.title"),
2637
+ subtitle: t2("financial.statements.subtitle"),
2638
+ actions: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
2639
+ /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
2640
+ t2("financial.statements.newTransfer")
2641
+ ] })
2642
+ }
2643
+ ),
2306
2644
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-end", children: [
2307
2645
  /* @__PURE__ */ jsxs("div", { className: "flex-1 max-w-xs", children: [
2308
2646
  /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground uppercase", children: t2("financial.statements.account") }),
@@ -2311,9 +2649,9 @@ function StatementsView() {
2311
2649
  {
2312
2650
  value: accountId,
2313
2651
  onChange: (e) => setAccountId(e.target.value),
2314
- className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm",
2652
+ className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm",
2315
2653
  children: [
2316
- /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.statements.selectAccount") }),
2654
+ /* @__PURE__ */ jsx("option", { value: ALL, children: t2("financial.summary.allAccounts") }),
2317
2655
  bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
2318
2656
  ]
2319
2657
  }
@@ -2328,10 +2666,31 @@ function StatementsView() {
2328
2666
  /* @__PURE__ */ jsx(DatePicker, { value: dateTo, onChange: setDateTo, className: "mt-0.5" })
2329
2667
  ] })
2330
2668
  ] }),
2669
+ accountId && statement && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-2", children: [
2670
+ /* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.opening"), value: fmt(statement.openingBalance) }),
2671
+ /* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.totalCredits"), value: fmt(statement.totalCredits), positive: true }),
2672
+ /* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.totalDebits"), value: fmt(statement.totalDebits), negative: true }),
2673
+ /* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.closing"), value: fmt(statement.closingBalance), accent: true })
2674
+ ] }),
2331
2675
  !accountId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border-2 border-dashed border-muted", children: [
2332
2676
  /* @__PURE__ */ jsx(Receipt, { className: "h-8 w-8 text-muted-foreground mb-2" }),
2333
2677
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.statements.selectToView") })
2334
- ] }) : statementLoading ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], loading: true, skeletonRows: 5 }) : statementEntries.length === 0 ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], emptyMessage: t2("financial.statements.noTransactions") }) : /* @__PURE__ */ jsx(DataTable, { columns, data: statementEntries })
2678
+ ] }) : statementLoading ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], loading: true, skeletonRows: 8, compact: true }) : entries.length === 0 ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], emptyMessage: t2("financial.statements.noTransactions"), compact: true }) : /* @__PURE__ */ jsx(DataTable, { columns, data: entries, compact: true }),
2679
+ showTransfer && /* @__PURE__ */ jsx(
2680
+ TransferModal,
2681
+ {
2682
+ defaultFromId: accountId && accountId !== ALL ? accountId : void 0,
2683
+ onClose: () => setShowTransfer(false),
2684
+ onDone: () => setShowTransfer(false)
2685
+ }
2686
+ )
2687
+ ] });
2688
+ }
2689
+ function SummaryStat({ label, value, accent, positive, negative }) {
2690
+ const valueClass = positive ? "text-success" : negative ? "text-destructive" : accent ? "text-foreground" : "text-foreground";
2691
+ return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border px-3 py-2 ${accent ? "bg-muted/40" : "bg-card"}`, children: [
2692
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-muted-foreground uppercase truncate", children: label }),
2693
+ /* @__PURE__ */ jsx("p", { className: `text-sm font-semibold mt-0.5 ${valueClass}`, children: value })
2335
2694
  ] });
2336
2695
  }
2337
2696
  function CommissionsView() {
@@ -2397,6 +2756,151 @@ function CardsView() {
2397
2756
  ] }, brand))
2398
2757
  ] });
2399
2758
  }
2759
+ function ReconciliationView() {
2760
+ const t2 = useTranslation();
2761
+ const { currency } = useFinancialConfig();
2762
+ const provider = useFinancialProvider();
2763
+ const fmt = (n) => formatCurrency(n, currency);
2764
+ const [lines, setLines] = useState([]);
2765
+ const [loading, setLoading] = useState(true);
2766
+ const [selectedId, setSelectedId] = useState(null);
2767
+ const [candidates, setCandidates] = useState([]);
2768
+ const [candidatesLoading, setCandidatesLoading] = useState(false);
2769
+ const [busy, setBusy] = useState(false);
2770
+ const supported = typeof provider.getUnreconciled === "function";
2771
+ const load = useCallback(async () => {
2772
+ if (!provider.getUnreconciled) return;
2773
+ setLoading(true);
2774
+ try {
2775
+ const data = await provider.getUnreconciled();
2776
+ setLines(data);
2777
+ setSelectedId((prev) => prev && data.some((l) => l.id === prev) ? prev : null);
2778
+ } finally {
2779
+ setLoading(false);
2780
+ }
2781
+ }, [provider]);
2782
+ useEffect(() => {
2783
+ void load();
2784
+ }, [load]);
2785
+ useEffect(() => {
2786
+ if (!selectedId || !provider.suggestReconciliation) {
2787
+ setCandidates([]);
2788
+ return;
2789
+ }
2790
+ let cancelled = false;
2791
+ setCandidatesLoading(true);
2792
+ provider.suggestReconciliation(selectedId).then((c) => {
2793
+ if (!cancelled) setCandidates(c);
2794
+ }).finally(() => {
2795
+ if (!cancelled) setCandidatesLoading(false);
2796
+ });
2797
+ return () => {
2798
+ cancelled = true;
2799
+ };
2800
+ }, [selectedId, provider]);
2801
+ async function match(bankMovementId, matchedMovementId) {
2802
+ if (!provider.reconcileMovement) return;
2803
+ setBusy(true);
2804
+ try {
2805
+ await provider.reconcileMovement({ bankMovementId, matchedMovementId });
2806
+ toast.success(t2("financial.reconciliation.match"));
2807
+ await load();
2808
+ } catch {
2809
+ toast.error(t2("common.error"));
2810
+ } finally {
2811
+ setBusy(false);
2812
+ }
2813
+ }
2814
+ if (!supported) {
2815
+ return /* @__PURE__ */ jsxs("div", { className: "py-16 text-center", children: [
2816
+ /* @__PURE__ */ jsx(Landmark, { className: "h-8 w-8 text-muted-foreground/30 mx-auto mb-2" }),
2817
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.reconciliation.empty") })
2818
+ ] });
2819
+ }
2820
+ const selected = lines.find((l) => l.id === selectedId) ?? null;
2821
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2822
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
2823
+ /* @__PURE__ */ jsxs("div", { children: [
2824
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold tracking-tight", children: t2("financial.reconciliation.title") }),
2825
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.reconciliation.subtitle") })
2826
+ ] }),
2827
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2828
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: lines.length > 0 ? t2("financial.reconciliation.pending", { count: lines.length }) : t2("financial.reconciliation.allDone") }),
2829
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => void load(), disabled: loading || busy, children: [
2830
+ /* @__PURE__ */ jsx(RefreshCw, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }),
2831
+ t2("financial.reconciliation.refresh")
2832
+ ] })
2833
+ ] })
2834
+ ] }),
2835
+ lines.length === 0 && !loading ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm py-16 text-center", children: [
2836
+ /* @__PURE__ */ jsx(Check, { className: "h-8 w-8 text-success/40 mx-auto mb-2" }),
2837
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t2("financial.reconciliation.empty") }),
2838
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: t2("financial.reconciliation.emptyHint") })
2839
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
2840
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm overflow-hidden", children: [
2841
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
2842
+ /* @__PURE__ */ jsx(Landmark, { className: "h-4 w-4 text-muted-foreground" }),
2843
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.reconciliation.bankLines") })
2844
+ ] }),
2845
+ /* @__PURE__ */ jsx("div", { className: "divide-y max-h-[60vh] overflow-auto", children: lines.map((line) => {
2846
+ const isCredit = line.direction === "credit";
2847
+ return /* @__PURE__ */ jsxs(
2848
+ "button",
2849
+ {
2850
+ onClick: () => setSelectedId(line.id),
2851
+ className: `w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-muted/40 transition-colors ${selectedId === line.id ? "bg-muted/50" : ""}`,
2852
+ children: [
2853
+ isCredit ? /* @__PURE__ */ jsx(ArrowDownCircle, { className: "h-4 w-4 text-success shrink-0" }) : /* @__PURE__ */ jsx(ArrowUpCircle, { className: "h-4 w-4 text-destructive shrink-0" }),
2854
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2855
+ /* @__PURE__ */ jsx("p", { className: "text-sm truncate", children: line.notes || line.externalId }),
2856
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2857
+ line.paymentDate ?? line.dueDate,
2858
+ line.externalSource && /* @__PURE__ */ jsxs("span", { className: "ml-1 uppercase tracking-wide", children: [
2859
+ "\xB7 ",
2860
+ line.externalSource
2861
+ ] })
2862
+ ] })
2863
+ ] }),
2864
+ /* @__PURE__ */ jsx("span", { className: `text-sm font-semibold shrink-0 ${isCredit ? "text-success" : "text-destructive"}`, children: fmt(line.amount) })
2865
+ ]
2866
+ },
2867
+ line.id
2868
+ );
2869
+ }) })
2870
+ ] }),
2871
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm overflow-hidden", children: [
2872
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
2873
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4 text-muted-foreground" }),
2874
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.reconciliation.candidates") })
2875
+ ] }),
2876
+ !selected ? /* @__PURE__ */ jsx("p", { className: "px-4 py-16 text-center text-sm text-muted-foreground", children: t2("financial.reconciliation.selectLine") }) : /* @__PURE__ */ jsx("div", { className: "p-3 space-y-2", children: candidatesLoading ? /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "\u2026" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2877
+ candidates.map((c) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-md border p-2.5", children: [
2878
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2879
+ /* @__PURE__ */ jsx("p", { className: "text-sm truncate", children: c.invoice?.contactName || c.movement.notes || c.invoice?.fiscalNumber || t2("financial.reconciliation.match") }),
2880
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2881
+ c.movement.dueDate,
2882
+ /* @__PURE__ */ jsx("span", { className: "ml-1.5 inline-flex items-center rounded px-1 py-px bg-primary/10 text-primary font-medium", children: t2("financial.reconciliation.matchScore", { score: Math.round(c.score * 100) }) })
2883
+ ] })
2884
+ ] }),
2885
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold shrink-0", children: fmt(c.movement.amount) }),
2886
+ /* @__PURE__ */ jsxs(Button, { size: "sm", disabled: busy, onClick: () => match(selected.id, c.movement.id), children: [
2887
+ /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5" }),
2888
+ t2("financial.reconciliation.match")
2889
+ ] })
2890
+ ] }, c.movement.id)),
2891
+ candidates.length === 0 && /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-xs text-muted-foreground", children: t2("financial.reconciliation.noCandidates") }),
2892
+ /* @__PURE__ */ jsxs("div", { className: "pt-2 mt-1 border-t", children: [
2893
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "w-full", disabled: busy, onClick: () => match(selected.id), children: [
2894
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
2895
+ t2("financial.reconciliation.accept")
2896
+ ] }),
2897
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground text-center mt-1", children: t2("financial.reconciliation.acceptHint") })
2898
+ ] })
2899
+ ] }) })
2900
+ ] })
2901
+ ] })
2902
+ ] });
2903
+ }
2400
2904
  function FinancialGeneralSettings() {
2401
2905
  const t2 = useTranslation();
2402
2906
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
@@ -2521,13 +3025,20 @@ function buildNav(config, activeView, navigate, t2) {
2521
3025
  onClick: () => navigate("cash-registers")
2522
3026
  });
2523
3027
  }
2524
- if (config.modules.statements) {
3028
+ items.push({
3029
+ id: "statements",
3030
+ label: t2("financial.nav.statements"),
3031
+ icon: "Receipt",
3032
+ active: activeView === "statements",
3033
+ onClick: () => navigate("statements")
3034
+ });
3035
+ if (config.modules.reconciliation) {
2525
3036
  items.push({
2526
- id: "statements",
2527
- label: t2("financial.nav.statements"),
2528
- icon: "Receipt",
2529
- active: activeView === "statements",
2530
- onClick: () => navigate("statements")
3037
+ id: "reconciliation",
3038
+ label: t2("financial.nav.reconciliation"),
3039
+ icon: "ArrowLeftRight",
3040
+ active: activeView === "reconciliation",
3041
+ onClick: () => navigate("reconciliation")
2531
3042
  });
2532
3043
  }
2533
3044
  if (config.modules.commissions) {
@@ -2555,7 +3066,7 @@ function buildNav(config, activeView, navigate, t2) {
2555
3066
  return items;
2556
3067
  }
2557
3068
  function FinancialPage({ config, provider, store, registries }) {
2558
- const { view, direction, navigate } = useModuleNavigation("/financial", {
3069
+ const { view, direction, navigate, previousView, back } = useModuleNavigation("/financial", {
2559
3070
  summary: 0,
2560
3071
  "payables-list": 0,
2561
3072
  "payables-new": 1,
@@ -2563,6 +3074,7 @@ function FinancialPage({ config, provider, store, registries }) {
2563
3074
  "receivables-new": 1,
2564
3075
  "cash-registers": 0,
2565
3076
  statements: 0,
3077
+ reconciliation: 0,
2566
3078
  commissions: 0,
2567
3079
  cards: 0,
2568
3080
  settings: 1
@@ -2620,24 +3132,30 @@ function FinancialPage({ config, provider, store, registries }) {
2620
3132
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(FinancialOnboarding, { onComplete: handleOnboardingComplete }) });
2621
3133
  }
2622
3134
  if (isSettings && registries && registries.length > 0) {
2623
- return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(PageTransition, { transitionKey: "settings", direction, children: /* @__PURE__ */ jsxs("div", { style: { padding: "24px" }, children: [
2624
- /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
2625
- /* @__PURE__ */ jsx("h1", { style: { fontSize: "20px", fontWeight: 600, margin: 0 }, children: t2("financial.settingsPage.title") }),
2626
- /* @__PURE__ */ jsx("p", { style: { color: "var(--muted-foreground, #6b7280)", margin: "4px 0 0", fontSize: "14px" }, children: t2("financial.settingsPage.subtitle") })
2627
- ] }),
2628
- /* @__PURE__ */ jsx(FinancialGeneralSettings, {})
2629
- ] }) }) });
3135
+ return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(PageTransition, { transitionKey: "settings", direction, children: /* @__PURE__ */ jsx("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsx(
3136
+ PluginSettingsPanel,
3137
+ {
3138
+ title: t2("financial.settingsPage.title"),
3139
+ subtitle: t2("financial.settingsPage.subtitle"),
3140
+ generalSettings: /* @__PURE__ */ jsx(FinancialGeneralSettings, {}),
3141
+ registries,
3142
+ hostPluginId: "financial",
3143
+ routeBase: "/settings/financial"
3144
+ }
3145
+ ) }) }) });
2630
3146
  }
2631
3147
  function renderView() {
2632
3148
  switch (intent.view) {
2633
3149
  case "payables":
2634
- return /* @__PURE__ */ jsx(PayablesView, { intent, onNavigate: navigate });
3150
+ return /* @__PURE__ */ jsx(PayablesView, { intent, onNavigate: navigate, previousView, back });
2635
3151
  case "receivables":
2636
- return /* @__PURE__ */ jsx(ReceivablesView, { intent, onNavigate: navigate });
3152
+ return /* @__PURE__ */ jsx(ReceivablesView, { intent, onNavigate: navigate, previousView, back });
2637
3153
  case "cash-registers":
2638
3154
  return /* @__PURE__ */ jsx(CashRegistersView, {});
2639
3155
  case "statements":
2640
- return /* @__PURE__ */ jsx(StatementsView, {});
3156
+ return /* @__PURE__ */ jsx(StatementsView, { onNavigate: navigate });
3157
+ case "reconciliation":
3158
+ return /* @__PURE__ */ jsx(ReconciliationView, {});
2641
3159
  case "commissions":
2642
3160
  return /* @__PURE__ */ jsx(CommissionsView, {});
2643
3161
  case "cards":
@@ -2841,6 +3359,9 @@ function now() {
2841
3359
  function today() {
2842
3360
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2843
3361
  }
3362
+ function round2(n) {
3363
+ return Math.round(n * 100) / 100;
3364
+ }
2844
3365
  function matchesDateRange(dateStr, range) {
2845
3366
  if (!range || !dateStr) return true;
2846
3367
  return dateStr >= range.from && dateStr <= range.to;
@@ -3044,37 +3565,60 @@ function createMockFinancialProvider(options) {
3044
3565
  return paginate(results, query.page, query.pageSize);
3045
3566
  },
3046
3567
  async payMovement(input) {
3047
- const movement = store.movements.find((m) => m.id === input.movementId);
3048
- if (!movement) throw new Error(`Movement ${input.movementId} not found`);
3049
- movement.paidAmount += input.amount;
3050
- movement.paymentDate = input.paymentDate;
3051
- movement.paymentMethodId = input.paymentMethodId ?? movement.paymentMethodId;
3052
- movement.bankAccountId = input.bankAccountId ?? movement.bankAccountId;
3053
- movement.cashSessionId = input.cashSessionId ?? movement.cashSessionId;
3054
- movement.cardBrand = input.cardBrand;
3055
- movement.cardInstallments = input.cardInstallments;
3056
- movement.status = movement.paidAmount >= movement.amount ? "paid" : "partial";
3057
- movement.updatedAt = now();
3058
- if (movement.bankAccountId) {
3059
- const account = store.bankAccounts.find((a) => a.id === movement.bankAccountId);
3568
+ const bill = store.movements.find((m) => m.id === input.movementId);
3569
+ if (!bill) throw new Error(`Movement ${input.movementId} not found`);
3570
+ const method = input.paymentMethodId ? store.paymentMethods.find((pm) => pm.id === input.paymentMethodId) : void 0;
3571
+ const feePct = bill.direction === "credit" ? method?.discountValue ?? 0 : 0;
3572
+ const thisFee = round2(input.amount * (feePct / 100));
3573
+ const payment = {
3574
+ id: uid(),
3575
+ invoiceId: bill.invoiceId,
3576
+ direction: bill.direction,
3577
+ movementKind: "payment",
3578
+ amount: input.amount,
3579
+ paidAmount: input.amount,
3580
+ feeAmount: thisFee,
3581
+ status: "paid",
3582
+ dueDate: bill.dueDate,
3583
+ paymentDate: input.paymentDate,
3584
+ installmentNumber: bill.installmentNumber,
3585
+ paymentMethodId: input.paymentMethodId,
3586
+ paymentMethodTypeId: input.paymentMethodTypeId,
3587
+ bankAccountId: input.bankAccountId,
3588
+ cashSessionId: input.cashSessionId,
3589
+ cardBrand: input.cardBrand,
3590
+ cardInstallments: input.cardInstallments,
3591
+ notes: input.notes,
3592
+ tenantId,
3593
+ createdAt: now(),
3594
+ updatedAt: now()
3595
+ };
3596
+ store.movements.push(payment);
3597
+ bill.paidAmount = round2(bill.paidAmount + input.amount);
3598
+ bill.status = bill.paidAmount >= bill.amount ? "paid" : "partial";
3599
+ bill.paymentDate = void 0;
3600
+ bill.bankAccountId = void 0;
3601
+ bill.updatedAt = now();
3602
+ if (input.bankAccountId) {
3603
+ const account = store.bankAccounts.find((a) => a.id === input.bankAccountId);
3060
3604
  if (account) {
3061
- if (movement.direction === "credit") {
3062
- account.currentBalance += input.amount;
3605
+ if (bill.direction === "credit") {
3606
+ account.currentBalance = round2(account.currentBalance + (input.amount - thisFee));
3063
3607
  } else {
3064
- account.currentBalance -= input.amount;
3608
+ account.currentBalance = round2(account.currentBalance - input.amount);
3065
3609
  }
3066
3610
  account.updatedAt = now();
3067
3611
  }
3068
3612
  }
3069
- if (movement.invoiceId) {
3070
- const invoice = store.invoices.find((i) => i.id === movement.invoiceId);
3613
+ if (bill.invoiceId) {
3614
+ const invoice = store.invoices.find((i) => i.id === bill.invoiceId);
3071
3615
  if (invoice) {
3072
- const invoiceMovements = store.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
3073
- invoice.paidAmount = invoiceMovements.reduce((sum, m) => sum + m.paidAmount, 0);
3616
+ const bills = store.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
3617
+ invoice.paidAmount = bills.reduce((sum, m) => sum + m.paidAmount, 0);
3074
3618
  recalcInvoiceStatus(invoice);
3075
3619
  }
3076
3620
  }
3077
- return movement;
3621
+ return payment;
3078
3622
  },
3079
3623
  async cancelMovement(id) {
3080
3624
  const movement = store.movements.find((m) => m.id === id);
@@ -3082,6 +3626,54 @@ function createMockFinancialProvider(options) {
3082
3626
  movement.status = "cancelled";
3083
3627
  movement.updatedAt = now();
3084
3628
  },
3629
+ // --- Transfers (between accounts) ---
3630
+ async createTransfer(input) {
3631
+ if (!input.fromAccountId || !input.toAccountId || input.fromAccountId === input.toAccountId) {
3632
+ throw new Error("Source and destination accounts must differ");
3633
+ }
3634
+ if (!(input.amount > 0)) throw new Error("Amount must be greater than zero");
3635
+ const transferId = uid();
3636
+ const debitMovementId = uid();
3637
+ const creditMovementId = uid();
3638
+ const base = {
3639
+ movementKind: "transfer",
3640
+ amount: input.amount,
3641
+ paidAmount: input.amount,
3642
+ feeAmount: 0,
3643
+ status: "paid",
3644
+ dueDate: input.date,
3645
+ paymentDate: input.date,
3646
+ notes: input.notes,
3647
+ tenantId,
3648
+ createdAt: now(),
3649
+ updatedAt: now()
3650
+ };
3651
+ store.movements.push({
3652
+ ...base,
3653
+ id: debitMovementId,
3654
+ direction: "debit",
3655
+ bankAccountId: input.fromAccountId,
3656
+ metadata: { transferId, transferRole: "out", counterAccountId: input.toAccountId }
3657
+ });
3658
+ store.movements.push({
3659
+ ...base,
3660
+ id: creditMovementId,
3661
+ direction: "credit",
3662
+ bankAccountId: input.toAccountId,
3663
+ metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
3664
+ });
3665
+ const from = store.bankAccounts.find((a) => a.id === input.fromAccountId);
3666
+ const to = store.bankAccounts.find((a) => a.id === input.toAccountId);
3667
+ if (from) {
3668
+ from.currentBalance = round2(from.currentBalance - input.amount);
3669
+ from.updatedAt = now();
3670
+ }
3671
+ if (to) {
3672
+ to.currentBalance = round2(to.currentBalance + input.amount);
3673
+ to.updatedAt = now();
3674
+ }
3675
+ return { transferId, debitMovementId, creditMovementId };
3676
+ },
3085
3677
  // --- Bank Accounts ---
3086
3678
  async getBankAccounts() {
3087
3679
  return store.bankAccounts.filter((a) => a.isActive);
@@ -3186,25 +3778,76 @@ function createMockFinancialProvider(options) {
3186
3778
  if (dateRange) results = results.filter((t2) => matchesDateRange(t2.expectedDate, dateRange));
3187
3779
  return results.sort((a, b) => a.expectedDate.localeCompare(b.expectedDate));
3188
3780
  },
3189
- // --- Statements ---
3781
+ // --- Statements (ERP extract: opening balance + realized cash + per-row net/fee) ---
3190
3782
  async getStatement(query) {
3191
- const account = store.bankAccounts.find((a) => a.id === query.bankAccountId);
3192
- if (!account) return [];
3193
- const movements = store.movements.filter(
3194
- (m) => m.bankAccountId === query.bankAccountId && m.status === "paid" && matchesDateRange(m.paymentDate, query.dateRange)
3195
- ).sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? ""));
3196
- let runningBalance = account.initialBalance;
3197
- const entries = [];
3198
- for (const movement of movements) {
3199
- if (movement.direction === "credit") {
3200
- runningBalance += movement.paidAmount;
3201
- } else {
3202
- runningBalance -= movement.paidAmount;
3783
+ const todayStr = today();
3784
+ const isRealized = (m) => m.status !== "cancelled" && !!m.paymentDate;
3785
+ const consolidated = !query.bankAccountId;
3786
+ const scopeAccounts = consolidated ? store.bankAccounts.filter((a) => a.isActive) : store.bankAccounts.filter((a) => a.id === query.bankAccountId);
3787
+ const accountIds = new Set(scopeAccounts.map((a) => a.id));
3788
+ const accountNameById = new Map(store.bankAccounts.map((a) => [a.id, a.name]));
3789
+ const initialSum = scopeAccounts.reduce((s, a) => s + a.initialBalance, 0);
3790
+ if (!consolidated && accountIds.size === 0) {
3791
+ return { entries: [], openingBalance: 0, closingBalance: 0, totalCredits: 0, totalDebits: 0, totalFees: 0, net: 0, accountId: query.bankAccountId };
3792
+ }
3793
+ const netOf = (m) => m.direction === "credit" ? m.paidAmount - (m.feeAmount ?? 0) : -m.paidAmount;
3794
+ const inScope = store.movements.filter((m) => isRealized(m) && (consolidated || m.bankAccountId && accountIds.has(m.bankAccountId)));
3795
+ const openingBalance = round2(initialSum + inScope.filter((m) => (m.paymentDate ?? "") < query.dateRange.from).reduce((s, m) => s + netOf(m), 0));
3796
+ const rangeMovs = inScope.filter((m) => matchesDateRange(m.paymentDate, query.dateRange)).sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || a.createdAt.localeCompare(b.createdAt));
3797
+ let balance = openingBalance;
3798
+ let totalCredits = 0, totalDebits = 0, totalFees = 0;
3799
+ const entries = rangeMovs.map((movement) => {
3800
+ const gross = movement.paidAmount;
3801
+ const isCredit = movement.direction === "credit";
3802
+ const fee = isCredit ? movement.feeAmount ?? 0 : 0;
3803
+ const net = isCredit ? round2(gross - fee) : gross;
3804
+ const isTransfer = movement.movementKind === "transfer";
3805
+ const meta = movement.metadata ?? {};
3806
+ const entryKind = isTransfer ? meta.transferRole === "in" ? "transfer-in" : "transfer-out" : "movement";
3807
+ const counterAccountId = isTransfer ? meta.counterAccountId : void 0;
3808
+ balance = round2(isCredit ? balance + net : balance - net);
3809
+ if (!(consolidated && isTransfer)) {
3810
+ if (isCredit) totalCredits = round2(totalCredits + net);
3811
+ else totalDebits = round2(totalDebits + net);
3812
+ totalFees = round2(totalFees + fee);
3203
3813
  }
3204
- const invoice = movement.invoiceId ? store.invoices.find((i) => i.id === movement.invoiceId) : void 0;
3205
- entries.push({ movement, invoice, runningBalance });
3814
+ return {
3815
+ movement,
3816
+ invoice: movement.invoiceId ? store.invoices.find((i) => i.id === movement.invoiceId) : void 0,
3817
+ runningBalance: balance,
3818
+ gross,
3819
+ fee,
3820
+ net,
3821
+ entryKind,
3822
+ counterAccountId,
3823
+ counterAccountName: counterAccountId ? accountNameById.get(counterAccountId) : void 0
3824
+ };
3825
+ });
3826
+ for (const e of entries) {
3827
+ const mid = e.movement.matchedMovementId;
3828
+ const mm = mid ? store.movements.find((m) => m.id === mid) : void 0;
3829
+ if (!mm) continue;
3830
+ const inv = mm.invoiceId ? store.invoices.find((i) => i.id === mm.invoiceId) : void 0;
3831
+ e.reconciledWith = {
3832
+ movementId: mm.id,
3833
+ description: mm.notes ?? void 0,
3834
+ invoiceId: mm.invoiceId,
3835
+ fiscalNumber: inv?.fiscalNumber,
3836
+ contactName: inv?.contactName
3837
+ };
3206
3838
  }
3207
- return entries;
3839
+ const accountCurrentBalance = round2(initialSum + inScope.filter((m) => (m.paymentDate ?? "") <= todayStr).reduce((s, m) => s + netOf(m), 0));
3840
+ return {
3841
+ entries,
3842
+ openingBalance,
3843
+ closingBalance: balance,
3844
+ totalCredits,
3845
+ totalDebits,
3846
+ totalFees,
3847
+ net: round2(totalCredits - totalDebits),
3848
+ accountId: query.bankAccountId,
3849
+ accountCurrentBalance
3850
+ };
3208
3851
  },
3209
3852
  // --- Summary ---
3210
3853
  async getSummary(dateRange) {
@@ -3219,7 +3862,7 @@ function createMockFinancialProvider(options) {
3219
3862
  const monthStart = todayStr.slice(0, 7) + "-01";
3220
3863
  const monthEnd = todayStr;
3221
3864
  const monthMovements = store.movements.filter(
3222
- (m) => m.status === "paid" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
3865
+ (m) => m.movementKind === "payment" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
3223
3866
  );
3224
3867
  const overdueReceivable = pendingReceivable.filter((m) => m.dueDate < todayStr);
3225
3868
  const overduePayable = pendingPayable.filter((m) => m.dueDate < todayStr);
@@ -3234,6 +3877,90 @@ function createMockFinancialProvider(options) {
3234
3877
  overduePayableCount: overduePayable.length,
3235
3878
  overduePayableAmount: overduePayable.reduce((s, m) => s + (m.amount - m.paidAmount), 0)
3236
3879
  };
3880
+ },
3881
+ // --- Reconciliation (conciliação) ---
3882
+ async importBankTransactions(input) {
3883
+ let imported = 0, duplicates = 0;
3884
+ for (const line of input.lines) {
3885
+ const dup = store.movements.some((m) => m.externalSource === line.externalSource && m.externalId === line.externalId);
3886
+ if (dup) {
3887
+ duplicates++;
3888
+ continue;
3889
+ }
3890
+ store.movements.push({
3891
+ id: uid(),
3892
+ direction: line.direction,
3893
+ movementKind: "payment",
3894
+ amount: line.amount,
3895
+ paidAmount: line.amount,
3896
+ status: "paid",
3897
+ dueDate: line.date,
3898
+ paymentDate: line.date,
3899
+ bankAccountId: line.bankAccountId ?? input.bankAccountId,
3900
+ notes: line.description,
3901
+ externalId: line.externalId,
3902
+ externalSource: line.externalSource,
3903
+ tenantId,
3904
+ createdAt: now(),
3905
+ updatedAt: now()
3906
+ });
3907
+ imported++;
3908
+ }
3909
+ return { imported, duplicates };
3910
+ },
3911
+ async getUnreconciled(query) {
3912
+ let results = store.movements.filter((m) => m.externalSource && !m.reconciledAt);
3913
+ if (query?.bankAccountId) results = results.filter((m) => m.bankAccountId === query.bankAccountId);
3914
+ if (query?.dateRange) results = results.filter((m) => matchesDateRange(m.paymentDate, query.dateRange));
3915
+ return results.sort((a, b) => (b.paymentDate ?? "").localeCompare(a.paymentDate ?? ""));
3916
+ },
3917
+ async suggestReconciliation(bankMovementId) {
3918
+ const line = store.movements.find((m) => m.id === bankMovementId);
3919
+ if (!line) return [];
3920
+ const amount = line.amount;
3921
+ const tol = Math.max(0.01, round2(amount * 0.02));
3922
+ const lineDate = line.paymentDate ?? line.dueDate;
3923
+ return store.movements.filter((m) => !m.externalSource && !m.reconciledAt && m.direction === line.direction && m.id !== bankMovementId && Math.abs(m.amount - amount) <= tol).map((m) => {
3924
+ const amtScore = amount > 0 ? Math.max(0, 1 - Math.abs(m.amount - amount) / amount) : 0;
3925
+ const refDate = m.dueDate ?? m.paymentDate;
3926
+ const days = lineDate && refDate ? Math.abs((new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5) : 30;
3927
+ const dateScore = Math.max(0, 1 - days / 30);
3928
+ return {
3929
+ movement: m,
3930
+ invoice: m.invoiceId ? store.invoices.find((i) => i.id === m.invoiceId) : void 0,
3931
+ score: round2(0.7 * amtScore + 0.3 * dateScore)
3932
+ };
3933
+ }).sort((a, b) => b.score - a.score).slice(0, 25);
3934
+ },
3935
+ async reconcileMovement(input) {
3936
+ const line = store.movements.find((m) => m.id === input.bankMovementId);
3937
+ if (line) {
3938
+ line.reconciledAt = now();
3939
+ line.matchedMovementId = input.matchedMovementId;
3940
+ line.updatedAt = now();
3941
+ }
3942
+ if (input.matchedMovementId) {
3943
+ const internal = store.movements.find((m) => m.id === input.matchedMovementId);
3944
+ if (internal) {
3945
+ internal.reconciledAt = now();
3946
+ internal.updatedAt = now();
3947
+ }
3948
+ }
3949
+ },
3950
+ async unreconcileMovement(bankMovementId) {
3951
+ const line = store.movements.find((m) => m.id === bankMovementId);
3952
+ if (!line) return;
3953
+ const matchedId = line.matchedMovementId;
3954
+ line.reconciledAt = void 0;
3955
+ line.matchedMovementId = void 0;
3956
+ line.updatedAt = now();
3957
+ if (matchedId) {
3958
+ const internal = store.movements.find((m) => m.id === matchedId);
3959
+ if (internal) {
3960
+ internal.reconciledAt = void 0;
3961
+ internal.updatedAt = now();
3962
+ }
3963
+ }
3237
3964
  }
3238
3965
  };
3239
3966
  return provider;
@@ -3313,6 +4040,21 @@ async function fetchInvoiceBalances(pub, ids) {
3313
4040
  }
3314
4041
  return map;
3315
4042
  }
4043
+ function round22(n) {
4044
+ return Math.round(n * 100) / 100;
4045
+ }
4046
+ async function getMethodFeePct(pub, paymentMethodId) {
4047
+ if (!paymentMethodId) return 0;
4048
+ const { data } = await pub.from("payment_methods").select("discount_value").eq("id", paymentMethodId).single();
4049
+ return Number(data?.discount_value) || 0;
4050
+ }
4051
+ function genId() {
4052
+ try {
4053
+ if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
4054
+ } catch {
4055
+ }
4056
+ return "tr-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
4057
+ }
3316
4058
  function createSupabaseFinancialProvider() {
3317
4059
  const provider = {
3318
4060
  // --- Invoices (orders at stage=invoiced/paid/partial/overdue/cancelled with direction) ---
@@ -3513,16 +4255,38 @@ function createSupabaseFinancialProvider() {
3513
4255
  const { core, pub } = getClients();
3514
4256
  const { data: mov } = await pub.from("financial_movements").select("*").eq("id", input.movementId).single();
3515
4257
  if (!mov) throw new Error("Movement not found");
3516
- const newPaid = (mov.paid_amount ?? 0) + input.amount;
3517
- const newStatus = newPaid >= mov.amount ? "paid" : "partial";
3518
- const updates = { paid_amount: newPaid, status: newStatus, payment_date: input.paymentDate };
3519
- if (input.paymentMethodId) updates.payment_method_id = input.paymentMethodId;
3520
- if (input.paymentMethodTypeId) updates.payment_method_type_id = input.paymentMethodTypeId;
3521
- if (input.bankAccountId) updates.bank_account_id = input.bankAccountId;
3522
- if (input.cashSessionId) updates.cash_session_id = input.cashSessionId;
3523
- if (input.cardBrand) updates.card_brand = input.cardBrand;
3524
- if (input.cardInstallments) updates.card_installments = input.cardInstallments;
3525
- const { data } = await pub.from("financial_movements").update(updates).eq("id", input.movementId).select().single();
4258
+ const tenantId = mov.tenant_id ?? getTenantId();
4259
+ const feePct = mov.direction === "credit" ? await getMethodFeePct(pub, input.paymentMethodId) : 0;
4260
+ const thisFee = round22(input.amount * (feePct / 100));
4261
+ const paymentRow = {
4262
+ tenant_id: tenantId,
4263
+ invoice_id: mov.invoice_id,
4264
+ direction: mov.direction,
4265
+ movement_kind: "payment",
4266
+ amount: input.amount,
4267
+ paid_amount: input.amount,
4268
+ fee_amount: thisFee,
4269
+ status: "paid",
4270
+ due_date: mov.due_date,
4271
+ payment_date: input.paymentDate,
4272
+ installment_number: mov.installment_number,
4273
+ payment_method_id: input.paymentMethodId ?? null,
4274
+ payment_method_type_id: input.paymentMethodTypeId ?? null,
4275
+ bank_account_id: input.bankAccountId ?? null,
4276
+ cash_session_id: input.cashSessionId ?? null,
4277
+ card_brand: input.cardBrand ?? null,
4278
+ card_installments: input.cardInstallments ?? null,
4279
+ notes: input.notes ?? null
4280
+ };
4281
+ let { data, error } = await pub.from("financial_movements").insert(paymentRow).select().single();
4282
+ if (error && /fee_amount/i.test(error.message ?? "")) {
4283
+ const { fee_amount, ...rest } = paymentRow;
4284
+ ({ data, error } = await pub.from("financial_movements").insert(rest).select().single());
4285
+ }
4286
+ if (error) throw error;
4287
+ const newPaid = round22((mov.paid_amount ?? 0) + input.amount);
4288
+ const billStatus = newPaid >= mov.amount ? "paid" : "partial";
4289
+ await pub.from("financial_movements").update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
3526
4290
  if (mov.invoice_id) {
3527
4291
  const { data: allMovs } = await pub.from("financial_movements").select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
3528
4292
  const movs = allMovs ?? [];
@@ -3543,6 +4307,51 @@ function createSupabaseFinancialProvider() {
3543
4307
  const { pub } = getClients();
3544
4308
  await pub.from("financial_movements").update({ status: "cancelled" }).eq("id", id);
3545
4309
  },
4310
+ // --- Transfers (between accounts) ---
4311
+ async createTransfer(input) {
4312
+ const { pub } = getClients();
4313
+ if (!input.fromAccountId || !input.toAccountId || input.fromAccountId === input.toAccountId) {
4314
+ throw new Error("Source and destination accounts must differ");
4315
+ }
4316
+ if (!(input.amount > 0)) throw new Error("Amount must be greater than zero");
4317
+ const tenantId = getTenantId();
4318
+ const transferId = genId();
4319
+ const base = {
4320
+ tenant_id: tenantId,
4321
+ movement_kind: "transfer",
4322
+ amount: input.amount,
4323
+ paid_amount: input.amount,
4324
+ fee_amount: 0,
4325
+ status: "paid",
4326
+ due_date: input.date,
4327
+ payment_date: input.date,
4328
+ notes: input.notes ?? null
4329
+ };
4330
+ const rowsToInsert = [
4331
+ {
4332
+ ...base,
4333
+ direction: "debit",
4334
+ bank_account_id: input.fromAccountId,
4335
+ metadata: { transferId, transferRole: "out", counterAccountId: input.toAccountId }
4336
+ },
4337
+ {
4338
+ ...base,
4339
+ direction: "credit",
4340
+ bank_account_id: input.toAccountId,
4341
+ metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
4342
+ }
4343
+ ];
4344
+ let { data, error } = await pub.from("financial_movements").insert(rowsToInsert).select("id, direction");
4345
+ if (error && /fee_amount/i.test(error.message ?? "")) {
4346
+ const stripped = rowsToInsert.map(({ fee_amount, ...rest }) => rest);
4347
+ ({ data, error } = await pub.from("financial_movements").insert(stripped).select("id, direction"));
4348
+ }
4349
+ if (error) throw error;
4350
+ const rows = data ?? [];
4351
+ const debitMovementId = rows.find((r) => r.direction === "debit")?.id ?? "";
4352
+ const creditMovementId = rows.find((r) => r.direction === "credit")?.id ?? "";
4353
+ return { transferId, debitMovementId, creditMovementId };
4354
+ },
3546
4355
  // --- Bank Accounts ---
3547
4356
  async getBankAccounts() {
3548
4357
  const { pub } = getClients();
@@ -3640,18 +4449,102 @@ function createSupabaseFinancialProvider() {
3640
4449
  getClients();
3641
4450
  return [];
3642
4451
  },
3643
- // --- Statements ---
4452
+ // --- Statements (ERP extract: opening balance + realized cash + per-row net/fee) ---
3644
4453
  async getStatement(query) {
3645
- const { pub } = getClients();
3646
- const { data: movs } = await pub.from("financial_movements").select("*").eq("bank_account_id", query.bankAccountId).eq("status", "paid").gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to).order("payment_date");
3647
- const { data: account } = await pub.from("bank_accounts").select("initial_balance").eq("id", query.bankAccountId).single();
3648
- let balance = account?.initial_balance ?? 0;
3649
- return (movs ?? []).map((r) => {
3650
- const mov = snakeToCamel(r);
3651
- if (mov.direction === "credit") balance += mov.paidAmount;
3652
- else balance -= mov.paidAmount;
3653
- return { movement: mov, runningBalance: balance };
4454
+ const { core, pub } = getClients();
4455
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4456
+ const { data: acctRows } = await pub.from("bank_accounts").select("id, name, initial_balance, is_active");
4457
+ const allAccounts = acctRows ?? [];
4458
+ const consolidated = !query.bankAccountId;
4459
+ const scopeAccounts = consolidated ? allAccounts.filter((a) => a.is_active) : allAccounts.filter((a) => a.id === query.bankAccountId);
4460
+ const accountIds = scopeAccounts.map((a) => a.id);
4461
+ const accountNameById = new Map(allAccounts.map((a) => [a.id, a.name]));
4462
+ const initialSum = scopeAccounts.reduce((s, a) => s + (Number(a.initial_balance) || 0), 0);
4463
+ if (!consolidated && accountIds.length === 0) {
4464
+ return { entries: [], openingBalance: 0, closingBalance: 0, totalCredits: 0, totalDebits: 0, totalFees: 0, net: 0, accountId: query.bankAccountId };
4465
+ }
4466
+ const scopeFilter = (qb) => consolidated ? qb : qb.in("bank_account_id", accountIds);
4467
+ const netOf = (r) => r.direction === "credit" ? (Number(r.paid_amount) || 0) - (Number(r.fee_amount) || 0) : -(Number(r.paid_amount) || 0);
4468
+ const { data: priorRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
4469
+ const priorNet = (priorRows ?? []).reduce((s, r) => s + netOf(r), 0);
4470
+ const openingBalance = round22(initialSum + priorNet);
4471
+ const { data: movs } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to).order("payment_date").order("created_at"));
4472
+ const invoiceIds = [...new Set((movs ?? []).map((r) => r.invoice_id).filter(Boolean))];
4473
+ const invoiceById = /* @__PURE__ */ new Map();
4474
+ if (invoiceIds.length) {
4475
+ const { data: orders } = await core.from("orders").select("id, party_id, reference_number, metadata").in("id", invoiceIds);
4476
+ for (const o of orders ?? []) {
4477
+ invoiceById.set(o.id, { id: o.id, contactId: o.party_id, contactName: o.metadata?.contactName, fiscalNumber: o.reference_number });
4478
+ }
4479
+ }
4480
+ let balance = openingBalance;
4481
+ let totalCredits = 0, totalDebits = 0, totalFees = 0;
4482
+ const entries = (movs ?? []).map((r) => {
4483
+ const movement = snakeToCamel(r);
4484
+ const gross = Number(r.paid_amount) || 0;
4485
+ const isCredit = r.direction === "credit";
4486
+ const fee = isCredit ? Number(r.fee_amount) || 0 : 0;
4487
+ const net = isCredit ? round22(gross - fee) : gross;
4488
+ const meta = r.metadata ?? {};
4489
+ const isTransfer = r.movement_kind === "transfer";
4490
+ const entryKind = isTransfer ? meta.transferRole === "in" ? "transfer-in" : "transfer-out" : "movement";
4491
+ const counterAccountId = isTransfer ? meta.counterAccountId : void 0;
4492
+ if (isCredit) balance = round22(balance + net);
4493
+ else balance = round22(balance - net);
4494
+ if (!(consolidated && isTransfer)) {
4495
+ if (isCredit) totalCredits = round22(totalCredits + net);
4496
+ else totalDebits = round22(totalDebits + net);
4497
+ totalFees = round22(totalFees + fee);
4498
+ }
4499
+ return {
4500
+ movement,
4501
+ invoice: r.invoice_id ? invoiceById.get(r.invoice_id) : void 0,
4502
+ runningBalance: balance,
4503
+ gross,
4504
+ fee,
4505
+ net,
4506
+ entryKind,
4507
+ counterAccountId,
4508
+ counterAccountName: counterAccountId ? accountNameById.get(counterAccountId) : void 0
4509
+ };
3654
4510
  });
4511
+ const matchedIds = [...new Set(entries.map((e) => e.movement.matchedMovementId).filter(Boolean))];
4512
+ if (matchedIds.length) {
4513
+ const { data: matched } = await pub.from("financial_movements").select("id, invoice_id, notes").in("id", matchedIds);
4514
+ const matchedById = new Map((matched ?? []).map((m) => [m.id, m]));
4515
+ const matchedInvoiceIds = [...new Set((matched ?? []).map((m) => m.invoice_id).filter(Boolean))];
4516
+ const invById = /* @__PURE__ */ new Map();
4517
+ if (matchedInvoiceIds.length) {
4518
+ const { data: orders } = await core.from("orders").select("id, reference_number, metadata").in("id", matchedInvoiceIds);
4519
+ for (const o of orders ?? []) invById.set(o.id, o);
4520
+ }
4521
+ for (const e of entries) {
4522
+ const mid = e.movement.matchedMovementId;
4523
+ const mm = mid ? matchedById.get(mid) : void 0;
4524
+ if (!mm) continue;
4525
+ const inv = mm.invoice_id ? invById.get(mm.invoice_id) : void 0;
4526
+ e.reconciledWith = {
4527
+ movementId: mm.id,
4528
+ description: mm.notes ?? void 0,
4529
+ invoiceId: mm.invoice_id ?? void 0,
4530
+ fiscalNumber: inv?.reference_number ?? void 0,
4531
+ contactName: inv?.metadata?.contactName ?? void 0
4532
+ };
4533
+ }
4534
+ }
4535
+ const { data: todayRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lte("payment_date", today2));
4536
+ const accountCurrentBalance = round22(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
4537
+ return {
4538
+ entries,
4539
+ openingBalance,
4540
+ closingBalance: balance,
4541
+ totalCredits,
4542
+ totalDebits,
4543
+ totalFees,
4544
+ net: round22(totalCredits - totalDebits),
4545
+ accountId: query.bankAccountId,
4546
+ accountCurrentBalance
4547
+ };
3655
4548
  },
3656
4549
  // --- Summary ---
3657
4550
  async getSummary(dateRange) {
@@ -3668,7 +4561,7 @@ function createSupabaseFinancialProvider() {
3668
4561
  const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today2);
3669
4562
  const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today2);
3670
4563
  const monthStart = today2.slice(0, 7) + "-01";
3671
- const monthPaid = allMovs.filter((m) => m.status === "paid" && m.payment_date >= monthStart);
4564
+ const monthPaid = allMovs.filter((m) => m.movement_kind === "payment" && m.payment_date >= monthStart);
3672
4565
  return {
3673
4566
  totalBalance,
3674
4567
  totalReceivable: unpaidCredit.reduce((s, m) => s + m.amount - m.paid_amount, 0),
@@ -3680,6 +4573,93 @@ function createSupabaseFinancialProvider() {
3680
4573
  overduePayableCount: overdueDebit.length,
3681
4574
  overduePayableAmount: overdueDebit.reduce((s, m) => s + m.amount - m.paid_amount, 0)
3682
4575
  };
4576
+ },
4577
+ // --- Reconciliation (conciliação) ---
4578
+ // Imported bank-statement lines live in financial_movements tagged with
4579
+ // (external_source, external_id), status='paid'. Reconciling links a line
4580
+ // to the internal movement it settles (or accepts it standalone).
4581
+ async importBankTransactions(input) {
4582
+ const { pub } = getClients();
4583
+ const tenantId = getTenantId();
4584
+ let imported = 0, duplicates = 0;
4585
+ for (const line of input.lines) {
4586
+ const { error } = await pub.from("financial_movements").insert({
4587
+ tenant_id: tenantId,
4588
+ direction: line.direction,
4589
+ movement_kind: "payment",
4590
+ amount: line.amount,
4591
+ paid_amount: line.amount,
4592
+ status: "paid",
4593
+ due_date: line.date,
4594
+ payment_date: line.date,
4595
+ bank_account_id: line.bankAccountId ?? input.bankAccountId ?? null,
4596
+ notes: line.description,
4597
+ external_id: line.externalId,
4598
+ external_source: line.externalSource
4599
+ });
4600
+ if (error) {
4601
+ if (error.code === "23505") duplicates++;
4602
+ else throw error;
4603
+ } else imported++;
4604
+ }
4605
+ return { imported, duplicates };
4606
+ },
4607
+ async getUnreconciled(query) {
4608
+ const { pub } = getClients();
4609
+ let qb = pub.from("financial_movements").select("*").not("external_source", "is", null).is("reconciled_at", null);
4610
+ if (query?.bankAccountId) qb = qb.eq("bank_account_id", query.bankAccountId);
4611
+ if (query?.dateRange) qb = qb.gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to);
4612
+ const { data } = await qb.order("payment_date", { ascending: false });
4613
+ return (data ?? []).map((r) => snakeToCamel(r));
4614
+ },
4615
+ async suggestReconciliation(bankMovementId) {
4616
+ const { core, pub } = getClients();
4617
+ const { data: line } = await pub.from("financial_movements").select("*").eq("id", bankMovementId).single();
4618
+ if (!line) return [];
4619
+ const amount = Number(line.amount) || 0;
4620
+ const lineDate = line.payment_date ?? line.due_date;
4621
+ const tol = Math.max(0.01, round22(amount * 0.02));
4622
+ const { data } = await pub.from("financial_movements").select("*").is("external_source", null).is("reconciled_at", null).eq("direction", line.direction).gte("amount", round22(amount - tol)).lte("amount", round22(amount + tol)).neq("id", bankMovementId).limit(25);
4623
+ const candidates = (data ?? []).map((r) => {
4624
+ const amtDiff = Math.abs((Number(r.amount) || 0) - amount);
4625
+ const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
4626
+ const refDate = r.due_date ?? r.payment_date;
4627
+ const days = lineDate && refDate ? Math.abs((new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5) : 30;
4628
+ const dateScore = Math.max(0, 1 - days / 30);
4629
+ return {
4630
+ movement: snakeToCamel(r),
4631
+ score: round22(0.7 * amtScore + 0.3 * dateScore)
4632
+ };
4633
+ }).sort((a, b) => b.score - a.score);
4634
+ const invoiceIds = [...new Set(candidates.map((c) => c.movement.invoiceId).filter(Boolean))];
4635
+ if (invoiceIds.length) {
4636
+ const { data: orders } = await core.from("orders").select("id, reference_number, metadata").in("id", invoiceIds);
4637
+ const byId = new Map((orders ?? []).map((o) => [o.id, o]));
4638
+ for (const c of candidates) {
4639
+ const o = c.movement.invoiceId ? byId.get(c.movement.invoiceId) : void 0;
4640
+ if (o) c.invoice = { id: o.id, contactName: o.metadata?.contactName, fiscalNumber: o.reference_number };
4641
+ }
4642
+ }
4643
+ return candidates;
4644
+ },
4645
+ async reconcileMovement(input) {
4646
+ const { pub } = getClients();
4647
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
4648
+ await pub.from("financial_movements").update({
4649
+ reconciled_at: now2,
4650
+ matched_movement_id: input.matchedMovementId ?? null
4651
+ }).eq("id", input.bankMovementId);
4652
+ if (input.matchedMovementId) {
4653
+ await pub.from("financial_movements").update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
4654
+ }
4655
+ },
4656
+ async unreconcileMovement(bankMovementId) {
4657
+ const { pub } = getClients();
4658
+ const { data: line } = await pub.from("financial_movements").select("matched_movement_id").eq("id", bankMovementId).single();
4659
+ await pub.from("financial_movements").update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
4660
+ if (line?.matched_movement_id) {
4661
+ await pub.from("financial_movements").update({ reconciled_at: null }).eq("id", line.matched_movement_id);
4662
+ }
3683
4663
  }
3684
4664
  };
3685
4665
  return provider;
@@ -3701,8 +4681,9 @@ function createFinancialStore(provider) {
3701
4681
  paymentMethodTypes: [],
3702
4682
  summary: null,
3703
4683
  summaryLoading: false,
3704
- statementEntries: [],
4684
+ statement: null,
3705
4685
  statementLoading: false,
4686
+ statementQuery: null,
3706
4687
  cardTransactions: [],
3707
4688
  cardsLoading: false,
3708
4689
  async fetchSummary(dateRange) {
@@ -3744,9 +4725,9 @@ function createFinancialStore(provider) {
3744
4725
  },
3745
4726
  async fetchStatement(query) {
3746
4727
  return dedup("fin:statement:" + JSON.stringify(query), async () => {
3747
- set({ statementLoading: true });
3748
- const statementEntries = await provider.getStatement(query);
3749
- set({ statementEntries, statementLoading: false });
4728
+ set({ statementLoading: true, statementQuery: query });
4729
+ const statement = await provider.getStatement(query);
4730
+ set({ statement, statementLoading: false });
3750
4731
  });
3751
4732
  },
3752
4733
  async fetchCardTransactions(dateRange) {
@@ -3782,6 +4763,22 @@ function createFinancialStore(provider) {
3782
4763
  throw err;
3783
4764
  }
3784
4765
  },
4766
+ async createTransfer(input) {
4767
+ try {
4768
+ await provider.createTransfer(input);
4769
+ const query = get().statementQuery;
4770
+ const [bankAccounts, summary, statement] = await Promise.all([
4771
+ provider.getBankAccounts(),
4772
+ provider.getSummary(),
4773
+ query ? provider.getStatement(query) : Promise.resolve(get().statement)
4774
+ ]);
4775
+ set({ bankAccounts, summary, statement });
4776
+ toast.success("Transfer recorded");
4777
+ } catch (err) {
4778
+ toast.error("Failed to record transfer", { description: err?.message });
4779
+ throw err;
4780
+ }
4781
+ },
3785
4782
  async cancelInvoice(invoiceId) {
3786
4783
  try {
3787
4784
  await provider.cancelInvoice(invoiceId);
@@ -3843,7 +4840,9 @@ var paymentMethodEntity = {
3843
4840
  defaultSort: "name",
3844
4841
  fields: [
3845
4842
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
3846
- { key: "paymentMethodTypeId", label: "Type", type: "select", options: ["cash", "credit_card", "debit_card", "pix", "bank_transfer", "check"], showInTable: true, required: true },
4843
+ // FK to the seeded payment_method_types catalog — load real ids at runtime
4844
+ // (a static enum select would write the string into the uuid column).
4845
+ { key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: "payment_method_types", labelField: "name" }, showInTable: true, required: true },
3847
4846
  { key: "discountValue", label: "Discount %", type: "number", showInTable: true },
3848
4847
  { key: "interestValue", label: "Interest %", type: "number", showInTable: true },
3849
4848
  { key: "minInstallments", label: "Min Installments", type: "number", defaultValue: 1 },
@@ -4206,6 +5205,21 @@ var en = {
4206
5205
  "financial.nav.rules": "Rules",
4207
5206
  "financial.nav.statements": "Statements",
4208
5207
  "financial.nav.summary": "Summary",
5208
+ "financial.reconciliation.title": "Reconciliation",
5209
+ "financial.reconciliation.subtitle": "Match imported bank lines to your movements",
5210
+ "financial.reconciliation.pending": "{{count}} to reconcile",
5211
+ "financial.reconciliation.allDone": "All caught up",
5212
+ "financial.reconciliation.empty": "Nothing to reconcile",
5213
+ "financial.reconciliation.emptyHint": "Imported bank transactions will appear here for matching.",
5214
+ "financial.reconciliation.bankLines": "Bank lines",
5215
+ "financial.reconciliation.candidates": "Suggested matches",
5216
+ "financial.reconciliation.selectLine": "Select a bank line to see suggested matches",
5217
+ "financial.reconciliation.noCandidates": "No suggested matches for this line",
5218
+ "financial.reconciliation.match": "Match",
5219
+ "financial.reconciliation.accept": "Accept as new",
5220
+ "financial.reconciliation.acceptHint": "No internal counterpart \u2014 keep as a standalone entry",
5221
+ "financial.reconciliation.matchScore": "{{score}}% match",
5222
+ "financial.reconciliation.refresh": "Refresh",
4209
5223
  "financial.onboarding.back": "Back",
4210
5224
  "financial.onboarding.configureHint": "You can configure this in detail from the Settings gear icon inside the Financial module.",
4211
5225
  "financial.onboarding.getStarted": "Get Started",
@@ -4270,6 +5284,8 @@ var en = {
4270
5284
  "financial.settingsPage.title": "Financial Settings",
4271
5285
  "financial.payment.noMethods": "No payment methods configured.",
4272
5286
  "financial.payment.configureMethods": "Set up payment methods",
5287
+ "financial.payment.noAccounts": "No compatible account. Create one in Settings.",
5288
+ "financial.payment.configureAccounts": "Set up accounts",
4273
5289
  "financial.statements.account": "Account",
4274
5290
  "financial.statements.from": "From",
4275
5291
  "financial.statements.noTransactions": "No transactions in this period",
@@ -4405,7 +5421,66 @@ var en = {
4405
5421
  "financial.cashRegister.close": "Close Register",
4406
5422
  "financial.movements.title": "Movements",
4407
5423
  "financial.bankAccounts.title": "Bank Accounts",
4408
- "financial.settings.title": "Financial Settings"
5424
+ "financial.settings.title": "Financial Settings",
5425
+ // Settings registry tabs
5426
+ "registry.payment-methods": "Payment Methods",
5427
+ "registry.payment-methods.description": "Configured payment methods with fees and installment rules",
5428
+ "registry.bank-accounts": "Accounts",
5429
+ "registry.bank-accounts.description": "Bank accounts, cash registers, credit cards, and digital wallets",
5430
+ "registry.chart-of-accounts": "Chart of Accounts",
5431
+ "registry.chart-of-accounts.description": "Hierarchical chart of accounts for categorizing transactions",
5432
+ "registry.cost-centers": "Cost Centers",
5433
+ "registry.cost-centers.description": "Cost centers for expense tracking and departmental budgets",
5434
+ "registry.commission-rules": "Commission Rules",
5435
+ "registry.commission-rules.description": "Commission rules for service and product revenue sharing",
5436
+ "registry.price-tables": "Price Tables",
5437
+ "registry.price-tables.description": "Price tables for sale and cost pricing",
5438
+ "registry.price-variations": "Price Variations",
5439
+ "registry.price-variations.description": "Discount and surcharge rules for pricing",
5440
+ "registry.payment-method-types": "Payment Types",
5441
+ "registry.payment-method-types.description": "System payment types (cash, card, PIX, etc.)",
5442
+ "registry.card-brands": "Card Brands",
5443
+ "registry.card-brands.description": "Credit and debit card brands",
5444
+ // Statements / extract
5445
+ "financial.statements.columnDate": "Date",
5446
+ "financial.statements.columnDescription": "Description",
5447
+ "financial.statements.columnDebit": "Debit",
5448
+ "financial.statements.columnCredit": "Credit",
5449
+ "financial.statements.columnFee": "Fee",
5450
+ "financial.statements.columnBalance": "Balance",
5451
+ "financial.statements.columnMethod": "Method",
5452
+ "financial.statements.columnDoc": "Document",
5453
+ "financial.statements.installmentShort": "Inst. {{n}}",
5454
+ "financial.statements.viewInvoice": "View invoice",
5455
+ "financial.person.empty": "No financial activity for this contact yet",
5456
+ "financial.statements.opening": "Opening balance",
5457
+ "financial.statements.closing": "Closing balance",
5458
+ "financial.statements.currentBalance": "Current balance",
5459
+ "financial.statements.totalCredits": "Credits",
5460
+ "financial.statements.totalDebits": "Debits",
5461
+ "financial.statements.totalFees": "Fees",
5462
+ "financial.statements.net": "Net",
5463
+ "financial.statements.transfer": "Transfer",
5464
+ "financial.statements.reconciled": "Reconciled with the bank statement",
5465
+ "financial.statements.imported": "Imported from the bank \xB7 not reconciled",
5466
+ "financial.statements.reconciledWith": "Reconciled with {{ref}}",
5467
+ "financial.statements.newTransfer": "New Transfer",
5468
+ // Transfer dialog
5469
+ "financial.transfer.title": "Transfer Between Accounts",
5470
+ "financial.transfer.from": "From account",
5471
+ "financial.transfer.to": "To account",
5472
+ "financial.transfer.amount": "Amount",
5473
+ "financial.transfer.date": "Date",
5474
+ "financial.transfer.notes": "Notes",
5475
+ "financial.transfer.optionalNotes": "Optional notes...",
5476
+ "financial.transfer.selectAccount": "Select account...",
5477
+ "financial.transfer.submit": "Transfer",
5478
+ "financial.transfer.processing": "Transferring...",
5479
+ "financial.transfer.cancel": "Cancel",
5480
+ "financial.transfer.created": "Transfer recorded",
5481
+ "financial.transfer.failed": "Failed to record transfer",
5482
+ "financial.transfer.sameAccount": "Source and destination must differ",
5483
+ "financial.transfer.invalidAmount": "Amount must be greater than zero"
4409
5484
  };
4410
5485
 
4411
5486
  // src/locales/pt-BR.ts
@@ -4538,6 +5613,21 @@ var ptBR = {
4538
5613
  "financial.nav.rules": "Regras",
4539
5614
  "financial.nav.statements": "Extratos",
4540
5615
  "financial.nav.summary": "Resumo",
5616
+ "financial.reconciliation.title": "Concilia\xE7\xE3o",
5617
+ "financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
5618
+ "financial.reconciliation.pending": "{{count}} para conciliar",
5619
+ "financial.reconciliation.allDone": "Tudo conciliado",
5620
+ "financial.reconciliation.empty": "Nada para conciliar",
5621
+ "financial.reconciliation.emptyHint": "As transa\xE7\xF5es banc\xE1rias importadas aparecem aqui para concilia\xE7\xE3o.",
5622
+ "financial.reconciliation.bankLines": "Linhas do banco",
5623
+ "financial.reconciliation.candidates": "Correspond\xEAncias sugeridas",
5624
+ "financial.reconciliation.selectLine": "Selecione uma linha do banco para ver as sugest\xF5es",
5625
+ "financial.reconciliation.noCandidates": "Nenhuma correspond\xEAncia sugerida para esta linha",
5626
+ "financial.reconciliation.match": "Conciliar",
5627
+ "financial.reconciliation.accept": "Aceitar como novo",
5628
+ "financial.reconciliation.acceptHint": "Sem contrapartida interna \u2014 manter como lan\xE7amento avulso",
5629
+ "financial.reconciliation.matchScore": "{{score}}% compat\xEDvel",
5630
+ "financial.reconciliation.refresh": "Atualizar",
4541
5631
  "financial.onboarding.back": "Voltar",
4542
5632
  "financial.onboarding.configureHint": "Voc\xEA pode configurar isso em detalhes pelo \xEDcone de Configura\xE7\xF5es dentro do m\xF3dulo Financeiro.",
4543
5633
  "financial.onboarding.getStarted": "Come\xE7ar",
@@ -4602,6 +5692,8 @@ var ptBR = {
4602
5692
  "financial.settingsPage.title": "Configura\xE7\xF5es Financeiras",
4603
5693
  "financial.payment.noMethods": "Nenhuma forma de pagamento configurada.",
4604
5694
  "financial.payment.configureMethods": "Configurar formas de pagamento",
5695
+ "financial.payment.noAccounts": "Nenhuma conta compat\xEDvel. Crie uma nas Configura\xE7\xF5es.",
5696
+ "financial.payment.configureAccounts": "Configurar contas",
4605
5697
  "financial.statements.account": "Conta",
4606
5698
  "financial.statements.from": "De",
4607
5699
  "financial.statements.noTransactions": "Sem transa\xE7\xF5es neste per\xEDodo",
@@ -4737,7 +5829,66 @@ var ptBR = {
4737
5829
  "financial.cashRegister.close": "Fechar Caixa",
4738
5830
  "financial.movements.title": "Movimenta\xE7\xF5es",
4739
5831
  "financial.bankAccounts.title": "Contas Banc\xE1rias",
4740
- "financial.settings.title": "Configura\xE7\xF5es Financeiras"
5832
+ "financial.settings.title": "Configura\xE7\xF5es Financeiras",
5833
+ // Abas de registros nas configurações
5834
+ "registry.payment-methods": "Formas de Pagamento",
5835
+ "registry.payment-methods.description": "Formas de pagamento configuradas com taxas e regras de parcelamento",
5836
+ "registry.bank-accounts": "Contas",
5837
+ "registry.bank-accounts.description": "Contas banc\xE1rias, caixas, cart\xF5es de cr\xE9dito e carteiras digitais",
5838
+ "registry.chart-of-accounts": "Plano de Contas",
5839
+ "registry.chart-of-accounts.description": "Plano de contas hier\xE1rquico para categorizar transa\xE7\xF5es",
5840
+ "registry.cost-centers": "Centros de Custo",
5841
+ "registry.cost-centers.description": "Centros de custo para controle de despesas e or\xE7amentos por departamento",
5842
+ "registry.commission-rules": "Regras de Comiss\xE3o",
5843
+ "registry.commission-rules.description": "Regras de comiss\xE3o para servi\xE7os e produtos",
5844
+ "registry.price-tables": "Tabelas de Pre\xE7o",
5845
+ "registry.price-tables.description": "Tabelas de pre\xE7o de venda e custo",
5846
+ "registry.price-variations": "Varia\xE7\xF5es de Pre\xE7o",
5847
+ "registry.price-variations.description": "Regras de desconto e acr\xE9scimo de pre\xE7os",
5848
+ "registry.payment-method-types": "Tipos de Pagamento",
5849
+ "registry.payment-method-types.description": "Tipos de pagamento do sistema (dinheiro, cart\xE3o, PIX, etc.)",
5850
+ "registry.card-brands": "Bandeiras de Cart\xE3o",
5851
+ "registry.card-brands.description": "Bandeiras de cart\xE3o de cr\xE9dito e d\xE9bito",
5852
+ // Extrato
5853
+ "financial.statements.columnDate": "Data",
5854
+ "financial.statements.columnDescription": "Descri\xE7\xE3o",
5855
+ "financial.statements.columnDebit": "D\xE9bito",
5856
+ "financial.statements.columnCredit": "Cr\xE9dito",
5857
+ "financial.statements.columnFee": "Taxa",
5858
+ "financial.statements.columnBalance": "Saldo",
5859
+ "financial.statements.columnMethod": "Forma",
5860
+ "financial.statements.columnDoc": "Documento",
5861
+ "financial.statements.installmentShort": "Parc. {{n}}",
5862
+ "financial.statements.viewInvoice": "Ver fatura",
5863
+ "financial.person.empty": "Nenhuma atividade financeira para este contato ainda",
5864
+ "financial.statements.opening": "Saldo inicial",
5865
+ "financial.statements.closing": "Saldo final",
5866
+ "financial.statements.currentBalance": "Saldo atual",
5867
+ "financial.statements.totalCredits": "Cr\xE9ditos",
5868
+ "financial.statements.totalDebits": "D\xE9bitos",
5869
+ "financial.statements.totalFees": "Taxas",
5870
+ "financial.statements.net": "L\xEDquido",
5871
+ "financial.statements.transfer": "Transfer\xEAncia",
5872
+ "financial.statements.reconciled": "Conciliado com o extrato banc\xE1rio",
5873
+ "financial.statements.imported": "Importado do banco \xB7 n\xE3o conciliado",
5874
+ "financial.statements.reconciledWith": "Conciliado com {{ref}}",
5875
+ "financial.statements.newTransfer": "Nova Transfer\xEAncia",
5876
+ // Diálogo de transferência
5877
+ "financial.transfer.title": "Transfer\xEAncia entre Contas",
5878
+ "financial.transfer.from": "Conta de origem",
5879
+ "financial.transfer.to": "Conta de destino",
5880
+ "financial.transfer.amount": "Valor",
5881
+ "financial.transfer.date": "Data",
5882
+ "financial.transfer.notes": "Observa\xE7\xF5es",
5883
+ "financial.transfer.optionalNotes": "Observa\xE7\xF5es opcionais...",
5884
+ "financial.transfer.selectAccount": "Selecione a conta...",
5885
+ "financial.transfer.submit": "Transferir",
5886
+ "financial.transfer.processing": "Transferindo...",
5887
+ "financial.transfer.cancel": "Cancelar",
5888
+ "financial.transfer.created": "Transfer\xEAncia registrada",
5889
+ "financial.transfer.failed": "Falha ao registrar transfer\xEAncia",
5890
+ "financial.transfer.sameAccount": "Origem e destino devem ser diferentes",
5891
+ "financial.transfer.invalidAmount": "O valor deve ser maior que zero"
4741
5892
  };
4742
5893
 
4743
5894
  // src/locales/index.ts
@@ -4745,8 +5896,104 @@ var financialLocales = {
4745
5896
  en,
4746
5897
  "pt-BR": ptBR
4747
5898
  };
4748
-
4749
- // src/index.ts
5899
+ var STATUS_STYLE = {
5900
+ paid: "bg-success/15 text-success",
5901
+ partial: "bg-warning/15 text-warning",
5902
+ overdue: "bg-destructive/15 text-destructive",
5903
+ open: "bg-muted text-muted-foreground",
5904
+ cancelled: "bg-muted text-muted-foreground line-through"
5905
+ };
5906
+ function PersonStatementWidget({ person, provider, config }) {
5907
+ const t2 = useTranslation();
5908
+ const currency = config.currency;
5909
+ const [invoices, setInvoices] = useState([]);
5910
+ const [loading, setLoading] = useState(true);
5911
+ useEffect(() => {
5912
+ let alive = true;
5913
+ if (!person?.id) {
5914
+ setLoading(false);
5915
+ return;
5916
+ }
5917
+ setLoading(true);
5918
+ provider.getInvoices({ contactId: person.id, pageSize: 200 }).then((r) => {
5919
+ if (alive) {
5920
+ setInvoices(r.data);
5921
+ setLoading(false);
5922
+ }
5923
+ }).catch(() => {
5924
+ if (alive) setLoading(false);
5925
+ });
5926
+ return () => {
5927
+ alive = false;
5928
+ };
5929
+ }, [person?.id, provider]);
5930
+ const fmt = (n) => formatCurrency(n, currency);
5931
+ const totals = useMemo(() => {
5932
+ const active = invoices.filter((i) => i.status !== "cancelled");
5933
+ const billed = active.reduce((s, i) => s + (i.totalAmount ?? 0), 0);
5934
+ const paid = active.reduce((s, i) => s + (i.paidAmount ?? 0), 0);
5935
+ return { billed, paid, balance: billed - paid };
5936
+ }, [invoices]);
5937
+ const openInvoice = (inv) => {
5938
+ const base = inv.direction === "credit" ? "receivables" : "payables";
5939
+ window.location.hash = `/financial/${base}/detail/${inv.id}`;
5940
+ };
5941
+ const columns = useMemo(() => [
5942
+ {
5943
+ id: "date",
5944
+ header: t2("financial.invoice.columnDate"),
5945
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: row.original.invoiceDate })
5946
+ },
5947
+ {
5948
+ id: "description",
5949
+ header: t2("financial.invoice.columnDescription"),
5950
+ cell: ({ row }) => {
5951
+ const inv = row.original;
5952
+ return /* @__PURE__ */ jsxs("button", { onClick: () => openInvoice(inv), className: "text-left", children: [
5953
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-primary hover:underline", children: inv.itemsSummary || inv.contactName || t2("financial.invoice.invoice") }),
5954
+ inv.fiscalNumber && /* @__PURE__ */ jsx("span", { className: "ml-1.5 text-[10px] text-muted-foreground font-mono", children: inv.fiscalNumber })
5955
+ ] });
5956
+ }
5957
+ },
5958
+ {
5959
+ id: "amount",
5960
+ header: t2("financial.invoice.columnAmount"),
5961
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `text-right block text-xs ${row.original.direction === "credit" ? "text-success" : "text-destructive"}`, children: fmt(row.original.totalAmount) })
5962
+ },
5963
+ {
5964
+ id: "paid",
5965
+ header: t2("financial.invoice.columnPaid"),
5966
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block text-xs text-muted-foreground", children: fmt(row.original.paidAmount) })
5967
+ },
5968
+ {
5969
+ id: "balance",
5970
+ header: t2("financial.statements.columnBalance"),
5971
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block text-xs font-medium", children: fmt((row.original.totalAmount ?? 0) - (row.original.paidAmount ?? 0)) })
5972
+ },
5973
+ {
5974
+ id: "status",
5975
+ header: t2("financial.invoice.columnStatus"),
5976
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${STATUS_STYLE[row.original.status] ?? STATUS_STYLE.open}`, children: row.original.status })
5977
+ }
5978
+ ], [currency, t2]);
5979
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
5980
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
5981
+ /* @__PURE__ */ jsx(Stat, { label: t2("financial.invoice.summaryTotal"), value: fmt(totals.billed) }),
5982
+ /* @__PURE__ */ jsx(Stat, { label: t2("financial.invoice.summaryPaid"), value: fmt(totals.paid), positive: true }),
5983
+ /* @__PURE__ */ jsx(Stat, { label: t2("financial.statements.columnBalance"), value: fmt(totals.balance), accent: true })
5984
+ ] }),
5985
+ loading ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], loading: true, skeletonRows: 5, compact: true }) : invoices.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-10 text-center rounded-lg border-2 border-dashed border-muted", children: [
5986
+ /* @__PURE__ */ jsx(Wallet, { className: "h-7 w-7 text-muted-foreground mb-2" }),
5987
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.person.empty") })
5988
+ ] }) : /* @__PURE__ */ jsx(DataTable, { columns, data: invoices, compact: true })
5989
+ ] });
5990
+ }
5991
+ function Stat({ label, value, accent, positive }) {
5992
+ return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border px-3 py-2 ${accent ? "bg-muted/40" : "bg-card"}`, children: [
5993
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-muted-foreground uppercase truncate", children: label }),
5994
+ /* @__PURE__ */ jsx("p", { className: `text-sm font-semibold mt-0.5 ${positive ? "text-success" : "text-foreground"}`, children: value })
5995
+ ] });
5996
+ }
4750
5997
  function createSafeFinancialProvider() {
4751
5998
  return createSafeDataProvider(
4752
5999
  () => createSupabaseFinancialProvider(),
@@ -4787,7 +6034,9 @@ function resolveConfig(options) {
4787
6034
  cashRegisters: options?.modules?.cashRegisters !== false,
4788
6035
  statements: options?.modules?.statements !== false,
4789
6036
  commissions: options?.modules?.commissions !== false,
4790
- cards: options?.modules?.cards !== false
6037
+ cards: options?.modules?.cards !== false,
6038
+ // Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
6039
+ reconciliation: options?.modules?.reconciliation === true
4791
6040
  },
4792
6041
  labels: { ...DEFAULT_LABELS, ...options?.labels },
4793
6042
  currency: { ...DEFAULT_CURRENCY, ...options?.currency },
@@ -4809,7 +6058,9 @@ function createFinancialPlugin(options) {
4809
6058
  const provider = options?.dataProvider ?? createSafeFinancialProvider();
4810
6059
  const store = createFinancialStore(provider);
4811
6060
  const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
4812
- const PageComponent = () => React11.createElement(FinancialPage, { config, provider, store, registries: financialRegistries });
6061
+ const PersonStatementBound = (props) => React14.createElement(PersonStatementWidget, { person: props.item, provider, config });
6062
+ PersonStatementBound.displayName = "FinancialPersonStatement";
6063
+ const PageComponent = () => React14.createElement(FinancialPage, { config, provider, store, registries: financialRegistries });
4813
6064
  return {
4814
6065
  id: "financial",
4815
6066
  name: config.labels.pageTitle,
@@ -4842,7 +6093,15 @@ function createFinancialPlugin(options) {
4842
6093
  permission: { feature: "financial", action: "read" }
4843
6094
  }
4844
6095
  ],
4845
- widgets: [],
6096
+ widgets: [
6097
+ {
6098
+ id: "financial-person-statement",
6099
+ zone: "person.detail.financial",
6100
+ component: PersonStatementBound,
6101
+ order: 0,
6102
+ permission: { feature: "financial", action: "read" }
6103
+ }
6104
+ ],
4846
6105
  dashboardWidgets,
4847
6106
  aiTools: [
4848
6107
  {
@@ -4913,7 +6172,14 @@ function createFinancialPlugin(options) {
4913
6172
  label: "Financial",
4914
6173
  icon: "DollarSign",
4915
6174
  component: (() => {
4916
- const FinancialSettingsTab = () => React11.createElement(FinancialGeneralSettings);
6175
+ const FinancialSettingsTab = () => React14.createElement(PluginSettingsPanel, {
6176
+ title: "Financial Settings",
6177
+ subtitle: "Payment methods, accounts, and configurations",
6178
+ generalSettings: React14.createElement(FinancialGeneralSettings),
6179
+ registries: financialRegistries,
6180
+ hostPluginId: "financial",
6181
+ routeBase: "/settings/financial"
6182
+ });
4917
6183
  FinancialSettingsTab.displayName = "FinancialSettingsTab";
4918
6184
  return FinancialSettingsTab;
4919
6185
  })(),