@fayz-ai/plugin-financial 0.1.1 → 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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/FinancialContext.d.ts +2 -0
  4. package/dist/FinancialContext.d.ts.map +1 -1
  5. package/dist/FinancialPage.d.ts.map +1 -1
  6. package/dist/components/PaymentModal.d.ts.map +1 -1
  7. package/dist/components/PersonStatementWidget.d.ts +18 -0
  8. package/dist/components/PersonStatementWidget.d.ts.map +1 -0
  9. package/dist/components/TransferModal.d.ts +7 -0
  10. package/dist/components/TransferModal.d.ts.map +1 -0
  11. package/dist/contract.test.d.ts +2 -0
  12. package/dist/contract.test.d.ts.map +1 -0
  13. package/dist/data/capability.test.d.ts +2 -0
  14. package/dist/data/capability.test.d.ts.map +1 -0
  15. package/dist/data/mock.d.ts.map +1 -1
  16. package/dist/data/supabase.d.ts.map +1 -1
  17. package/dist/data/types.d.ts +13 -2
  18. package/dist/data/types.d.ts.map +1 -1
  19. package/dist/index.cjs +1529 -263
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1437 -171
  24. package/dist/index.js.map +1 -1
  25. package/dist/locales/en.d.ts.map +1 -1
  26. package/dist/locales/pt-BR.d.ts.map +1 -1
  27. package/dist/registries.d.ts.map +1 -1
  28. package/dist/store.d.ts +4 -2
  29. package/dist/store.d.ts.map +1 -1
  30. package/dist/types.d.ts +111 -1
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/views/InvoiceDetailView.d.ts +3 -1
  33. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  34. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  35. package/dist/views/PayablesView.d.ts +5 -1
  36. package/dist/views/PayablesView.d.ts.map +1 -1
  37. package/dist/views/ReceivablesView.d.ts +5 -1
  38. package/dist/views/ReceivablesView.d.ts.map +1 -1
  39. package/dist/views/ReconciliationView.d.ts +3 -0
  40. package/dist/views/ReconciliationView.d.ts.map +1 -0
  41. package/dist/views/StatementsView.d.ts +3 -1
  42. package/dist/views/StatementsView.d.ts.map +1 -1
  43. package/dist/views/navReferrer.d.ts +5 -0
  44. package/dist/views/navReferrer.d.ts.map +1 -0
  45. package/package.json +8 -5
  46. package/src/FinancialContext.tsx +2 -0
  47. package/src/FinancialPage.tsx +32 -17
  48. package/src/components/PaymentModal.tsx +58 -11
  49. package/src/components/PersonStatementWidget.tsx +133 -0
  50. package/src/components/TransferModal.tsx +150 -0
  51. package/src/contract.test.ts +58 -0
  52. package/src/data/capability.test.ts +90 -0
  53. package/src/data/mock.ts +265 -52
  54. package/src/data/supabase.ts +359 -30
  55. package/src/data/types.ts +22 -3
  56. package/src/index.ts +28 -2
  57. package/src/locales/en.ts +76 -0
  58. package/src/locales/pt-BR.ts +76 -0
  59. package/src/migrations/006_extract_fee_amount.sql +20 -0
  60. package/src/migrations/006_rls_policies.sql +37 -0
  61. package/src/migrations/007_reconciliation.sql +32 -0
  62. package/src/migrations/008_split_payment_movements.sql +29 -0
  63. package/src/registries.ts +3 -1
  64. package/src/store.ts +27 -7
  65. package/src/types.ts +126 -1
  66. package/src/views/InvoiceDetailView.tsx +70 -51
  67. package/src/views/InvoiceFormView.tsx +55 -20
  68. package/src/views/PayablesView.tsx +13 -2
  69. package/src/views/ReceivablesView.tsx +13 -2
  70. package/src/views/ReconciliationView.tsx +211 -0
  71. package/src/views/StatementsView.tsx +241 -29
  72. package/src/views/navReferrer.ts +24 -0
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var React11 = require('react');
3
+ var React14 = require('react');
4
4
  var core = require('@fayz-ai/core');
5
5
  var ui = require('@fayz-ai/ui');
6
6
  var saas = require('@fayz-ai/saas');
@@ -11,7 +11,7 @@ var vanilla = require('zustand/vanilla');
11
11
 
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
13
 
14
- var React11__default = /*#__PURE__*/_interopDefault(React11);
14
+ var React14__default = /*#__PURE__*/_interopDefault(React14);
15
15
 
16
16
  // src/index.ts
17
17
  var ctx = saas.createPluginContext("FinancialPage");
@@ -41,7 +41,7 @@ function StatusBadge({ status }) {
41
41
  }
42
42
  function useInvoiceColumns(currency) {
43
43
  const t2 = core.useTranslation();
44
- return React11.useMemo(() => [
44
+ return React14.useMemo(() => [
45
45
  {
46
46
  accessorKey: "fiscalNumber",
47
47
  header: "#",
@@ -97,9 +97,9 @@ function InvoiceListView({ direction, onNew, onEdit }) {
97
97
  useFinancialStore((s) => s.invoicesTotal);
98
98
  const invoicesLoading = useFinancialStore((s) => s.invoicesLoading);
99
99
  const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
100
- const [search, setSearch] = React11.useState("");
101
- const [statusFilter, setStatusFilter] = React11.useState([]);
102
- React11.useEffect(() => {
100
+ const [search, setSearch] = React14.useState("");
101
+ const [statusFilter, setStatusFilter] = React14.useState([]);
102
+ React14.useEffect(() => {
103
103
  fetchInvoices({
104
104
  direction,
105
105
  status: statusFilter.length > 0 ? statusFilter : void 0,
@@ -109,11 +109,11 @@ function InvoiceListView({ direction, onNew, onEdit }) {
109
109
  const filtered = invoices.filter((inv) => inv.direction === direction);
110
110
  const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
111
111
  const columns = useInvoiceColumns(currency);
112
- const tags = React11.useMemo(
112
+ const tags = React14.useMemo(
113
113
  () => STATUS_OPTIONS.filter((o) => o.value !== "cancelled").map((o) => ({ value: o.value, label: t2(o.labelKey) })),
114
114
  [t2]
115
115
  );
116
- const summary = React11.useMemo(() => {
116
+ const summary = React14.useMemo(() => {
117
117
  let totalAmount = 0;
118
118
  let paidAmount = 0;
119
119
  let openAmount = 0;
@@ -177,10 +177,10 @@ function InvoiceListView({ direction, onNew, onEdit }) {
177
177
  }
178
178
  function LocationSelector({ locations, value, onChange }) {
179
179
  const t2 = core.useTranslation();
180
- const [open, setOpen] = React11.useState(false);
181
- const [search, setSearch] = React11.useState("");
182
- const ref = React11.useRef(null);
183
- React11.useEffect(() => {
180
+ const [open, setOpen] = React14.useState(false);
181
+ const [search, setSearch] = React14.useState("");
182
+ const ref = React14.useRef(null);
183
+ React14.useEffect(() => {
184
184
  function handler(e) {
185
185
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
186
186
  }
@@ -255,12 +255,18 @@ var formId = 1;
255
255
  function fid() {
256
256
  return `f${formId++}`;
257
257
  }
258
- function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdate, onRemove, entityLookups }) {
258
+ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdate, onRemove, entityLookups, showTypeSelector }) {
259
259
  const t2 = core.useTranslation();
260
260
  const total = item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0);
261
261
  const kindLabel = itemTypes.find((tp) => tp.value === item.itemKind)?.label ?? item.itemKind;
262
262
  const hasAdjustments = (item.discount ?? 0) > 0 || (item.surcharge ?? 0) > 0;
263
- const [showAdjustments, setShowAdjustments] = React11.useState(hasAdjustments);
263
+ const [showAdjustments, setShowAdjustments] = React14.useState(hasAdjustments);
264
+ function handleFieldKeyDown(e) {
265
+ if (e.key === "Enter" && !e.shiftKey) {
266
+ e.preventDefault();
267
+ onToggle();
268
+ }
269
+ }
264
270
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b last:border-0", children: [
265
271
  /* @__PURE__ */ jsxRuntime.jsxs(
266
272
  "div",
@@ -297,7 +303,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
297
303
  }
298
304
  ),
299
305
  expanded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 pb-4 pt-1 ml-8 space-y-3 bg-muted/10", children: [
300
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1.5", children: itemTypes.map((tp) => /* @__PURE__ */ jsxRuntime.jsx(
306
+ showTypeSelector && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1.5", children: itemTypes.map((tp) => /* @__PURE__ */ jsxRuntime.jsx(
301
307
  "button",
302
308
  {
303
309
  onClick: () => onUpdate({ itemKind: tp.value }),
@@ -346,6 +352,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
346
352
  type: "text",
347
353
  value: item.description,
348
354
  onChange: (e) => onUpdate({ description: e.target.value }),
355
+ onKeyDown: handleFieldKeyDown,
349
356
  placeholder: t2("financial.invoiceForm.itemDescription"),
350
357
  autoFocus: true,
351
358
  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"
@@ -361,11 +368,12 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
361
368
  min: 1,
362
369
  value: item.quantity,
363
370
  onChange: (e) => onUpdate({ quantity: Number(e.target.value) || 1 }),
371
+ onKeyDown: handleFieldKeyDown,
364
372
  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"
365
373
  }
366
374
  )
367
375
  ] }),
368
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-4", children: /* @__PURE__ */ jsxRuntime.jsx(
376
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-4", onKeyDown: handleFieldKeyDown, children: /* @__PURE__ */ jsxRuntime.jsx(
369
377
  ui.CurrencyInput,
370
378
  {
371
379
  label: t2("financial.invoiceForm.unitPrice"),
@@ -401,6 +409,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
401
409
  step: 0.01,
402
410
  value: item.discount ?? 0,
403
411
  onChange: (e) => onUpdate({ discount: Number(e.target.value) || 0 }),
412
+ onKeyDown: handleFieldKeyDown,
404
413
  className: "flex-1 bg-transparent px-2 py-1 text-xs outline-none"
405
414
  }
406
415
  )
@@ -418,6 +427,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
418
427
  step: 0.01,
419
428
  value: item.surcharge ?? 0,
420
429
  onChange: (e) => onUpdate({ surcharge: Number(e.target.value) || 0 }),
430
+ onKeyDown: handleFieldKeyDown,
421
431
  className: "flex-1 bg-transparent px-2 py-1 text-xs outline-none"
422
432
  }
423
433
  )
@@ -481,28 +491,30 @@ function InvoiceFormView({ direction, editId, onSaved }) {
481
491
  const config = useFinancialConfig();
482
492
  const { currency, itemTypes, locations } = config;
483
493
  const provider = useFinancialProvider();
494
+ const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
495
+ const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
484
496
  const createInvoice = useFinancialStore((s) => s.createInvoice);
485
497
  const hasLocations = locations.length > 0;
486
498
  const isEdit = !!editId;
487
- const [loaded, setLoaded] = React11.useState(!editId);
488
- const [loading, setLoading] = React11.useState(!!editId);
489
- const [contactName, setContactName] = React11.useState("");
490
- const [contactId, setContactId] = React11.useState();
491
- const [unitId, setUnitId] = React11.useState(locations[0]?.id ?? "");
492
- const [invoiceDate, setInvoiceDate] = React11.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
493
- const [fiscalNumber, setFiscalNumber] = React11.useState("");
494
- const [observations, setObservations] = React11.useState("");
495
- const [items, setItems] = React11.useState([]);
496
- const [expandedItemId, setExpandedItemId] = React11.useState(null);
497
- const [addingItem, setAddingItem] = React11.useState(false);
498
- const [installments, setInstallments] = React11.useState([]);
499
- const [saving, setSaving] = React11.useState(false);
500
- const [menuOpen, setMenuOpen] = React11.useState(false);
501
- const [confirmCancel, setConfirmCancel] = React11.useState(false);
502
- const [cancelling, setCancelling] = React11.useState(false);
503
- const menuRef = React11.useRef(null);
499
+ const [loaded, setLoaded] = React14.useState(!editId);
500
+ const [loading, setLoading] = React14.useState(!!editId);
501
+ const [contactName, setContactName] = React14.useState("");
502
+ const [contactId, setContactId] = React14.useState();
503
+ const [unitId, setUnitId] = React14.useState(locations[0]?.id ?? "");
504
+ const [invoiceDate, setInvoiceDate] = React14.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
505
+ const [fiscalNumber, setFiscalNumber] = React14.useState("");
506
+ const [observations, setObservations] = React14.useState("");
507
+ const [items, setItems] = React14.useState([]);
508
+ const [expandedItemId, setExpandedItemId] = React14.useState(null);
509
+ const [addingItem, setAddingItem] = React14.useState(false);
510
+ const [installments, setInstallments] = React14.useState([]);
511
+ const [saving, setSaving] = React14.useState(false);
512
+ const [menuOpen, setMenuOpen] = React14.useState(false);
513
+ const [confirmCancel, setConfirmCancel] = React14.useState(false);
514
+ const [cancelling, setCancelling] = React14.useState(false);
515
+ const menuRef = React14.useRef(null);
504
516
  const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
505
- React11.useEffect(() => {
517
+ React14.useEffect(() => {
506
518
  if (!menuOpen) return;
507
519
  function handleClick(e) {
508
520
  if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
@@ -521,7 +533,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
521
533
  setCancelling(false);
522
534
  }
523
535
  }
524
- React11.useEffect(() => {
536
+ React14.useEffect(() => {
525
537
  if (!editId || loaded) return;
526
538
  setLoading(true);
527
539
  (async () => {
@@ -558,8 +570,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
558
570
  const totalAmount = items.reduce((sum, it) => {
559
571
  return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
560
572
  }, 0);
561
- const prevItemCount = React11.useRef(items.length);
562
- React11.useEffect(() => {
573
+ const prevItemCount = React14.useRef(items.length);
574
+ React14.useEffect(() => {
563
575
  if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
564
576
  const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
565
577
  if (total > 0) {
@@ -568,7 +580,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
568
580
  }
569
581
  prevItemCount.current = items.length;
570
582
  }, [items.length]);
571
- React11.useEffect(() => {
583
+ React14.useEffect(() => {
572
584
  if (installments.length > 0 && totalAmount > 0 && !isEdit) {
573
585
  const count = installments.length;
574
586
  const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
@@ -591,6 +603,10 @@ function InvoiceFormView({ direction, editId, onSaved }) {
591
603
  setExpandedItemId(newItem._id);
592
604
  setAddingItem(false);
593
605
  }
606
+ function startAddItem() {
607
+ if (hasItemLookups) setAddingItem(true);
608
+ else addItemOfKind(defaultItemKind);
609
+ }
594
610
  function updateItem(id, data) {
595
611
  setItems(items.map((it) => it._id === id ? { ...it, ...data } : it));
596
612
  }
@@ -676,8 +692,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
676
692
  items: items.map(({ _id, ...rest }) => rest),
677
693
  installments: installments.map(({ _id, ...rest }) => rest)
678
694
  };
679
- const snapshot = React11.useRef(null);
680
- React11.useEffect(() => {
695
+ const snapshot = React14.useRef(null);
696
+ React14.useEffect(() => {
681
697
  if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
682
698
  }, [loaded]);
683
699
  const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
@@ -810,7 +826,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
810
826
  /* @__PURE__ */ jsxRuntime.jsxs(
811
827
  "button",
812
828
  {
813
- onClick: () => setAddingItem(true),
829
+ onClick: startAddItem,
814
830
  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",
815
831
  children: [
816
832
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3 w-3" }),
@@ -832,7 +848,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
832
848
  items.length === 0 && !addingItem ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-10 text-center", children: [
833
849
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LayoutList, { className: "h-8 w-8 text-muted-foreground/30 mx-auto mb-2" }),
834
850
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.invoiceForm.noItems") }),
835
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setAddingItem(true), className: "text-xs text-primary hover:underline mt-1", children: t2("financial.invoiceForm.addFirstItem") })
851
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: startAddItem, className: "text-xs text-primary hover:underline mt-1", children: t2("financial.invoiceForm.addFirstItem") })
836
852
  ] }) : items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
837
853
  ItemRow,
838
854
  {
@@ -847,7 +863,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
847
863
  removeItem(item._id);
848
864
  if (expandedItemId === item._id) setExpandedItemId(null);
849
865
  },
850
- entityLookups: config.entityLookups
866
+ entityLookups: config.entityLookups,
867
+ showTypeSelector: hasItemLookups
851
868
  },
852
869
  item._id
853
870
  ))
@@ -943,6 +960,15 @@ function InvoiceFormView({ direction, editId, onSaved }) {
943
960
  ` })
944
961
  ] });
945
962
  }
963
+ var ACCOUNT_TYPES_FOR_TX = {
964
+ cash: ["cash_register"],
965
+ credit_card: ["credit_card", "bank_account"],
966
+ debit_card: ["bank_account", "cash_register"],
967
+ pix: ["bank_account", "digital_wallet"],
968
+ bank_transfer: ["bank_account"],
969
+ check: ["bank_account"],
970
+ voucher: ["bank_account", "digital_wallet"]
971
+ };
946
972
  var TYPE_ICONS = {
947
973
  cash: lucideReact.Banknote,
948
974
  pix: lucideReact.QrCode,
@@ -977,34 +1003,44 @@ function PaymentModal({ movement, onClose, onPaid }) {
977
1003
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
978
1004
  const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
979
1005
  const remaining = movement.amount - movement.paidAmount;
980
- const [amount, setAmount] = React11.useState(remaining);
981
- const [paymentDate, setPaymentDate] = React11.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
982
- const [selectedTypeId, setSelectedTypeId] = React11.useState("");
983
- const [paymentMethodId, setPaymentMethodId] = React11.useState("");
984
- const [bankAccountId, setBankAccountId] = React11.useState("");
985
- const [cardBrand, setCardBrand] = React11.useState("");
986
- const [cardInstallments, setCardInstallments] = React11.useState(1);
987
- const [notes, setNotes] = React11.useState("");
988
- const [saving, setSaving] = React11.useState(false);
989
- const [visible, setVisible] = React11.useState(false);
990
- const [expandMethodPicker, setExpandMethodPicker] = React11.useState(true);
991
- React11.useEffect(() => {
1006
+ const [amount, setAmount] = React14.useState(remaining);
1007
+ const [paymentDate, setPaymentDate] = React14.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
1008
+ const [selectedTypeId, setSelectedTypeId] = React14.useState("");
1009
+ const [paymentMethodId, setPaymentMethodId] = React14.useState("");
1010
+ const [bankAccountId, setBankAccountId] = React14.useState("");
1011
+ const [cardBrand, setCardBrand] = React14.useState("");
1012
+ const [cardInstallments, setCardInstallments] = React14.useState(1);
1013
+ const [notes, setNotes] = React14.useState("");
1014
+ const [saving, setSaving] = React14.useState(false);
1015
+ const [visible, setVisible] = React14.useState(false);
1016
+ const [expandMethodPicker, setExpandMethodPicker] = React14.useState(true);
1017
+ React14.useEffect(() => {
992
1018
  fetchBankAccounts();
993
1019
  fetchPaymentMethods();
994
1020
  requestAnimationFrame(() => setVisible(true));
995
1021
  }, []);
996
- const selectedType = React11.useMemo(
1022
+ const selectedType = React14.useMemo(
997
1023
  () => paymentMethodTypes.find((tp) => tp.id === selectedTypeId),
998
1024
  [paymentMethodTypes, selectedTypeId]
999
1025
  );
1000
1026
  const txType = selectedType?.transactionType ?? "";
1001
- const filteredMethods = React11.useMemo(
1027
+ const filteredMethods = React14.useMemo(
1002
1028
  () => paymentMethods.filter((m) => m.paymentMethodTypeId === selectedTypeId),
1003
1029
  [paymentMethods, selectedTypeId]
1004
1030
  );
1005
- const needsBankAccount = txType === "bank_transfer" || txType === "pix" || txType === "check";
1006
1031
  const needsCard = txType === "credit_card" || txType === "debit_card";
1007
1032
  const needsCardInstallments = txType === "credit_card";
1033
+ const eligibleAccounts = React14.useMemo(() => {
1034
+ const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[txType];
1035
+ if (!allowed || !allowed.length) return bankAccounts;
1036
+ const filtered = bankAccounts.filter((a) => allowed.includes(a.accountType));
1037
+ return filtered.length ? filtered : bankAccounts;
1038
+ }, [bankAccounts, selectedType, txType]);
1039
+ React14.useEffect(() => {
1040
+ if (selectedTypeId && !bankAccountId && eligibleAccounts.length) {
1041
+ setBankAccountId(eligibleAccounts[0].id);
1042
+ }
1043
+ }, [selectedTypeId, eligibleAccounts]);
1008
1044
  function handleSelectType(typeId) {
1009
1045
  setSelectedTypeId(typeId);
1010
1046
  setExpandMethodPicker(false);
@@ -1017,8 +1053,14 @@ function PaymentModal({ movement, onClose, onPaid }) {
1017
1053
  setVisible(false);
1018
1054
  setTimeout(onClose, 200);
1019
1055
  }
1056
+ function handleKeyDown(e) {
1057
+ if (e.key !== "Enter" || e.shiftKey) return;
1058
+ if (e.target.tagName === "TEXTAREA") return;
1059
+ e.preventDefault();
1060
+ if (amount > 0 && selectedTypeId && bankAccountId && !saving) void handlePay();
1061
+ }
1020
1062
  async function handlePay() {
1021
- if (amount <= 0 || !selectedTypeId) return;
1063
+ if (amount <= 0 || !selectedTypeId || !bankAccountId) return;
1022
1064
  setSaving(true);
1023
1065
  try {
1024
1066
  await payMovement({
@@ -1029,7 +1071,8 @@ function PaymentModal({ movement, onClose, onPaid }) {
1029
1071
  paymentMethodTypeId: selectedTypeId || void 0,
1030
1072
  bankAccountId: bankAccountId || void 0,
1031
1073
  cardBrand: needsCard ? cardBrand || void 0 : void 0,
1032
- cardInstallments: needsCardInstallments ? cardInstallments : void 0
1074
+ cardInstallments: needsCardInstallments ? cardInstallments : void 0,
1075
+ notes: notes.trim() || void 0
1033
1076
  });
1034
1077
  setVisible(false);
1035
1078
  setTimeout(onPaid, 200);
@@ -1055,6 +1098,7 @@ function PaymentModal({ movement, onClose, onPaid }) {
1055
1098
  transform: visible ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)"
1056
1099
  },
1057
1100
  onClick: (e) => e.stopPropagation(),
1101
+ onKeyDown: handleKeyDown,
1058
1102
  children: [
1059
1103
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b sticky top-0 bg-card z-10", children: [
1060
1104
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -1164,11 +1208,17 @@ function PaymentModal({ movement, onClose, onPaid }) {
1164
1208
  ] }, n)) })
1165
1209
  ] })
1166
1210
  ] }),
1167
- needsBankAccount && bankAccounts.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
1211
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
1168
1212
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.bankAccount") }),
1169
- /* @__PURE__ */ jsxRuntime.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: [
1213
+ eligibleAccounts.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
1214
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.payment.noAccounts") }),
1215
+ /* @__PURE__ */ jsxRuntime.jsxs("a", { href: "#/settings/financial/bank-accounts", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
1216
+ t2("financial.payment.configureAccounts"),
1217
+ " \u2192"
1218
+ ] })
1219
+ ] }) : /* @__PURE__ */ jsxRuntime.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: [
1170
1220
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("financial.payment.selectAccount") }),
1171
- bankAccounts.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a.id, children: a.name }, a.id))
1221
+ eligibleAccounts.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a.id, children: a.name }, a.id))
1172
1222
  ] })
1173
1223
  ] }),
1174
1224
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stagger-field", style: { animationDelay: "150ms" }, children: [
@@ -1194,7 +1244,7 @@ function PaymentModal({ movement, onClose, onPaid }) {
1194
1244
  variant: "default",
1195
1245
  size: "sm",
1196
1246
  onClick: handlePay,
1197
- disabled: amount <= 0 || !selectedTypeId || saving,
1247
+ disabled: amount <= 0 || !selectedTypeId || !bankAccountId || saving,
1198
1248
  children: [
1199
1249
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" }),
1200
1250
  saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: saas.formatCurrency(amount, currency) })
@@ -1219,28 +1269,30 @@ var STATUS_COLORS = {
1219
1269
  draft: { bg: "bg-muted text-muted-foreground", icon: lucideReact.CircleDashed, labelKey: "financial.invoice.statusDraft" },
1220
1270
  cancelled: { bg: "bg-muted text-muted-foreground", icon: lucideReact.Ban, labelKey: "financial.invoice.statusCancelled" }
1221
1271
  };
1222
- function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1272
+ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel }) {
1223
1273
  const t2 = core.useTranslation();
1224
1274
  const config = useFinancialConfig();
1225
1275
  const { currency } = config;
1276
+ const parentLabel = backLabel ?? (direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"));
1226
1277
  const provider = useFinancialProvider();
1227
1278
  const paymentMethods = useFinancialStore((s) => s.paymentMethods);
1228
1279
  const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
1229
1280
  const bankAccounts = useFinancialStore((s) => s.bankAccounts);
1230
1281
  const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
1231
1282
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
1232
- const [detailsLoaded, setDetailsLoaded] = React11.useState(false);
1233
- const [detailsLoading, setDetailsLoading] = React11.useState(false);
1234
- const [invoice, setInvoice] = React11.useState(null);
1235
- const [items, setItems] = React11.useState([]);
1236
- const [movements, setMovements] = React11.useState([]);
1237
- const [loading, setLoading] = React11.useState(true);
1238
- const [payingMovement, setPayingMovement] = React11.useState(null);
1239
- const [expandedMovId, setExpandedMovId] = React11.useState(null);
1240
- const [menuOpen, setMenuOpen] = React11.useState(false);
1241
- const [confirmCancel, setConfirmCancel] = React11.useState(false);
1242
- const [cancelling, setCancelling] = React11.useState(false);
1243
- const menuRef = React11.useRef(null);
1283
+ const [detailsLoaded, setDetailsLoaded] = React14.useState(false);
1284
+ const [detailsLoading, setDetailsLoading] = React14.useState(false);
1285
+ const [invoice, setInvoice] = React14.useState(null);
1286
+ const [items, setItems] = React14.useState([]);
1287
+ const [movements, setMovements] = React14.useState([]);
1288
+ const [payments, setPayments] = React14.useState([]);
1289
+ const [loading, setLoading] = React14.useState(true);
1290
+ const [payingMovement, setPayingMovement] = React14.useState(null);
1291
+ const [expandedMovId, setExpandedMovId] = React14.useState(null);
1292
+ const [menuOpen, setMenuOpen] = React14.useState(false);
1293
+ const [confirmCancel, setConfirmCancel] = React14.useState(false);
1294
+ const [cancelling, setCancelling] = React14.useState(false);
1295
+ const menuRef = React14.useRef(null);
1244
1296
  const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
1245
1297
  async function loadData() {
1246
1298
  setLoading(true);
@@ -1254,12 +1306,15 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1254
1306
  setMovements(
1255
1307
  movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
1256
1308
  );
1309
+ setPayments(
1310
+ movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "payment").sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? ""))
1311
+ );
1257
1312
  setLoading(false);
1258
1313
  }
1259
- React11.useEffect(() => {
1314
+ React14.useEffect(() => {
1260
1315
  loadData();
1261
1316
  }, [invoiceId]);
1262
- React11.useEffect(() => {
1317
+ React14.useEffect(() => {
1263
1318
  if (!menuOpen) return;
1264
1319
  function handleClick(e) {
1265
1320
  if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
@@ -1293,7 +1348,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1293
1348
  }
1294
1349
  if (loading) {
1295
1350
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-5", children: [
1296
- /* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title: "", onBack, parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables") }),
1351
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title: "", onBack, parentLabel }),
1297
1352
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border bg-card shadow-sm overflow-hidden", children: [
1298
1353
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1 bg-muted animate-pulse" }),
1299
1354
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-5 pb-4", children: [
@@ -1347,7 +1402,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1347
1402
  }
1348
1403
  if (!invoice) {
1349
1404
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
1350
- /* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title: t2("financial.invoice.notFound"), onBack, parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables") }),
1405
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title: t2("financial.invoice.notFound"), onBack, parentLabel }),
1351
1406
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.invoice.notFoundDesc") })
1352
1407
  ] });
1353
1408
  }
@@ -1394,7 +1449,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1394
1449
  {
1395
1450
  title: invoice.fiscalNumber ? `#${invoice.fiscalNumber}` : invoice.contactName ?? invoice.invoiceDate,
1396
1451
  onBack,
1397
- parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
1452
+ parentLabel,
1398
1453
  actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
1399
1454
  invoice.status !== "cancelled" && invoice.status !== "paid" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: onEdit, children: [
1400
1455
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-3.5 w-3.5" }),
@@ -1536,9 +1591,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1536
1591
  const isExpanded = expandedMovId === mov.id;
1537
1592
  const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
1538
1593
  const MovIcon = movStatus.icon;
1539
- const method = paymentMethods.find((m) => m.id === mov.paymentMethodId);
1540
- const methodType = paymentMethodTypes.find((t3) => t3.id === (method?.paymentMethodTypeId ?? mov.paymentMethodTypeId));
1541
- const account = bankAccounts.find((a) => a.id === mov.bankAccountId);
1594
+ const instPayments = payments.filter((p) => (p.installmentNumber ?? null) === (mov.installmentNumber ?? null));
1542
1595
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1543
1596
  /* @__PURE__ */ jsxRuntime.jsxs(
1544
1597
  "div",
@@ -1612,43 +1665,53 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1612
1665
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
1613
1666
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
1614
1667
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
1615
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
1616
- methodType && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1617
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: methodType.name }),
1618
- method && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1619
- "(",
1620
- method.name,
1621
- ")"
1622
- ] })
1623
- ] }),
1624
- mov.cardBrand && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1625
- methodType && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1626
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-3 w-3 shrink-0" }),
1627
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: mov.cardBrand }),
1628
- mov.cardInstallments && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1629
- mov.cardInstallments,
1630
- "x"
1668
+ ] }) : instPayments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
1669
+ const pMethod = paymentMethods.find((m) => m.id === p.paymentMethodId);
1670
+ const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
1671
+ const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
1672
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
1673
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-success", children: saas.formatCurrency(p.paidAmount, currency) }),
1674
+ pType && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1675
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1676
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: pType.name }),
1677
+ pMethod && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1678
+ "(",
1679
+ pMethod.name,
1680
+ ")"
1681
+ ] })
1682
+ ] }),
1683
+ p.cardBrand && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1684
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1685
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-3 w-3 shrink-0" }),
1686
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: p.cardBrand }),
1687
+ p.cardInstallments && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1688
+ p.cardInstallments,
1689
+ "x"
1690
+ ] })
1691
+ ] }),
1692
+ pAccount && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1693
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1694
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "h-3 w-3 shrink-0" }),
1695
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: pAccount.name })
1696
+ ] }),
1697
+ (p.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1698
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1699
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-warning", children: [
1700
+ t2("financial.statements.columnFee"),
1701
+ " ",
1702
+ saas.formatCurrency(p.feeAmount ?? 0, currency)
1703
+ ] })
1704
+ ] }),
1705
+ p.paymentDate && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1706
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1707
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: p.paymentDate })
1708
+ ] }),
1709
+ p.notes && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1710
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1711
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic truncate max-w-[200px]", children: p.notes })
1631
1712
  ] })
1632
- ] }),
1633
- account && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1634
- (methodType || mov.cardBrand) && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1635
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "h-3 w-3 shrink-0" }),
1636
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: account.name })
1637
- ] }),
1638
- mov.paidAmount > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1639
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1640
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-success", children: saas.formatCurrency(mov.paidAmount, currency) })
1641
- ] }),
1642
- mov.paymentDate && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1643
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1644
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: mov.paymentDate })
1645
- ] }),
1646
- mov.notes && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1647
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
1648
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic truncate max-w-[200px]", children: mov.notes })
1649
- ] }),
1650
- !methodType && !mov.cardBrand && !account && !mov.notes && /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("financial.invoice.noDetails") })
1651
- ] }) }) })
1713
+ ] }, p.id);
1714
+ }) }) }) })
1652
1715
  }
1653
1716
  )
1654
1717
  ] }, mov.id);
@@ -1712,7 +1775,25 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit }) {
1712
1775
  ` })
1713
1776
  ] });
1714
1777
  }
1715
- function PayablesView({ intent, onNavigate }) {
1778
+
1779
+ // src/views/navReferrer.ts
1780
+ var REFERRER_LABEL_KEYS = {
1781
+ summary: "financial.nav.summary",
1782
+ statements: "financial.nav.statements",
1783
+ "cash-registers": "financial.nav.cashRegisters",
1784
+ reconciliation: "financial.nav.reconciliation",
1785
+ commissions: "financial.nav.commissions",
1786
+ cards: "financial.nav.cards",
1787
+ "receivables-list": "financial.nav.receivables",
1788
+ "payables-list": "financial.nav.payables"
1789
+ };
1790
+ function referrerLabel(view, t2) {
1791
+ if (!view) return null;
1792
+ const key = REFERRER_LABEL_KEYS[view];
1793
+ return key ? t2(key) : null;
1794
+ }
1795
+ function PayablesView({ intent, onNavigate, previousView, back }) {
1796
+ const t2 = core.useTranslation();
1716
1797
  if (intent.mode === "new") {
1717
1798
  return /* @__PURE__ */ jsxRuntime.jsx(InvoiceFormView, { direction: "debit", onSaved: (id) => onNavigate(id ? `payables-detail:${id}` : "payables-list") });
1718
1799
  }
@@ -1720,12 +1801,14 @@ function PayablesView({ intent, onNavigate }) {
1720
1801
  return /* @__PURE__ */ jsxRuntime.jsx(InvoiceFormView, { direction: "debit", editId: intent.editId, onSaved: () => onNavigate(`payables-detail:${intent.editId}`) });
1721
1802
  }
1722
1803
  if (intent.mode === "detail" && intent.editId) {
1804
+ const prevLabel = referrerLabel(previousView, t2);
1723
1805
  return /* @__PURE__ */ jsxRuntime.jsx(
1724
1806
  InvoiceDetailView,
1725
1807
  {
1726
1808
  invoiceId: intent.editId,
1727
1809
  direction: "debit",
1728
- onBack: () => onNavigate("payables-list"),
1810
+ backLabel: prevLabel ?? void 0,
1811
+ onBack: prevLabel && back ? () => back("payables-list") : () => onNavigate("payables-list"),
1729
1812
  onEdit: () => onNavigate(`payables-edit:${intent.editId}`)
1730
1813
  }
1731
1814
  );
@@ -1739,7 +1822,8 @@ function PayablesView({ intent, onNavigate }) {
1739
1822
  }
1740
1823
  );
1741
1824
  }
1742
- function ReceivablesView({ intent, onNavigate }) {
1825
+ function ReceivablesView({ intent, onNavigate, previousView, back }) {
1826
+ const t2 = core.useTranslation();
1743
1827
  if (intent.mode === "new") {
1744
1828
  return /* @__PURE__ */ jsxRuntime.jsx(InvoiceFormView, { direction: "credit", onSaved: (id) => onNavigate(id ? `receivables-detail:${id}` : "receivables-list") });
1745
1829
  }
@@ -1747,12 +1831,14 @@ function ReceivablesView({ intent, onNavigate }) {
1747
1831
  return /* @__PURE__ */ jsxRuntime.jsx(InvoiceFormView, { direction: "credit", editId: intent.editId, onSaved: () => onNavigate(`receivables-detail:${intent.editId}`) });
1748
1832
  }
1749
1833
  if (intent.mode === "detail" && intent.editId) {
1834
+ const prevLabel = referrerLabel(previousView, t2);
1750
1835
  return /* @__PURE__ */ jsxRuntime.jsx(
1751
1836
  InvoiceDetailView,
1752
1837
  {
1753
1838
  invoiceId: intent.editId,
1754
1839
  direction: "credit",
1755
- onBack: () => onNavigate("receivables-list"),
1840
+ backLabel: prevLabel ?? void 0,
1841
+ onBack: prevLabel && back ? () => back("receivables-list") : () => onNavigate("receivables-list"),
1756
1842
  onEdit: () => onNavigate(`receivables-edit:${intent.editId}`)
1757
1843
  }
1758
1844
  );
@@ -1829,16 +1915,16 @@ function CashOpeningPanel({
1829
1915
  currency,
1830
1916
  onOpen
1831
1917
  }) {
1832
- const [selectedAccountId, setSelectedAccountId] = React11.useState(cashAccounts[0]?.id ?? "");
1833
- const [mode, setMode] = React11.useState("quick");
1834
- const [quickBalance, setQuickBalance] = React11.useState(0);
1835
- const [counts, setCounts] = React11.useState({});
1836
- const [notes, setNotes] = React11.useState("");
1837
- const [showNotes, setShowNotes] = React11.useState(false);
1838
- const [confirming, setConfirming] = React11.useState(false);
1839
- const [submitting, setSubmitting] = React11.useState(false);
1918
+ const [selectedAccountId, setSelectedAccountId] = React14.useState(cashAccounts[0]?.id ?? "");
1919
+ const [mode, setMode] = React14.useState("quick");
1920
+ const [quickBalance, setQuickBalance] = React14.useState(0);
1921
+ const [counts, setCounts] = React14.useState({});
1922
+ const [notes, setNotes] = React14.useState("");
1923
+ const [showNotes, setShowNotes] = React14.useState(false);
1924
+ const [confirming, setConfirming] = React14.useState(false);
1925
+ const [submitting, setSubmitting] = React14.useState(false);
1840
1926
  const t2 = core.useTranslation();
1841
- const countedTotal = React11.useMemo(() => {
1927
+ const countedTotal = React14.useMemo(() => {
1842
1928
  return BRL_DENOMINATIONS.reduce((sum, d) => sum + (counts[d.value] ?? 0) * d.value, 0);
1843
1929
  }, [counts]);
1844
1930
  const effectiveBalance = mode === "count" ? countedTotal : quickBalance;
@@ -2014,13 +2100,13 @@ function CashClosingPanel({
2014
2100
  currency,
2015
2101
  onClose
2016
2102
  }) {
2017
- const [mode, setMode] = React11.useState("quick");
2018
- const [quickBalance, setQuickBalance] = React11.useState(0);
2019
- const [counts, setCounts] = React11.useState({});
2020
- const [notes, setNotes] = React11.useState("");
2021
- const [submitting, setSubmitting] = React11.useState(false);
2103
+ const [mode, setMode] = React14.useState("quick");
2104
+ const [quickBalance, setQuickBalance] = React14.useState(0);
2105
+ const [counts, setCounts] = React14.useState({});
2106
+ const [notes, setNotes] = React14.useState("");
2107
+ const [submitting, setSubmitting] = React14.useState(false);
2022
2108
  const t2 = core.useTranslation();
2023
- const countedTotal = React11.useMemo(() => {
2109
+ const countedTotal = React14.useMemo(() => {
2024
2110
  return BRL_DENOMINATIONS.reduce((sum, d) => sum + (counts[d.value] ?? 0) * d.value, 0);
2025
2111
  }, [counts]);
2026
2112
  const effectiveBalance = mode === "count" ? countedTotal : quickBalance;
@@ -2125,8 +2211,8 @@ function CashRegistersView() {
2125
2211
  const fetchCashSessions = useFinancialStore((s) => s.fetchCashSessions);
2126
2212
  const openCashSession = useFinancialStore((s) => s.openCashSession);
2127
2213
  const closeCashSession = useFinancialStore((s) => s.closeCashSession);
2128
- const [expandedClosing, setExpandedClosing] = React11.useState(null);
2129
- React11.useEffect(() => {
2214
+ const [expandedClosing, setExpandedClosing] = React14.useState(null);
2215
+ React14.useEffect(() => {
2130
2216
  fetchBankAccounts();
2131
2217
  fetchCashSessions();
2132
2218
  }, []);
@@ -2230,7 +2316,7 @@ function OpenAnotherCollapsible({
2230
2316
  onOpen
2231
2317
  }) {
2232
2318
  const t2 = core.useTranslation();
2233
- const [expanded, setExpanded] = React11.useState(false);
2319
+ const [expanded, setExpanded] = React14.useState(false);
2234
2320
  const availableAccounts = cashAccounts.filter((a) => !openSessions.some((s) => s.bankAccountId === a.id));
2235
2321
  if (availableAccounts.length === 0) return null;
2236
2322
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
@@ -2251,33 +2337,202 @@ function OpenAnotherCollapsible({
2251
2337
  expanded && /* @__PURE__ */ jsxRuntime.jsx(CashOpeningPanel, { cashAccounts: availableAccounts, currency, onOpen })
2252
2338
  ] });
2253
2339
  }
2254
- function StatementsView() {
2340
+ function TransferModal({ onClose, onDone, defaultFromId }) {
2255
2341
  const t2 = core.useTranslation();
2256
2342
  const { currency } = useFinancialConfig();
2257
2343
  const bankAccounts = useFinancialStore((s) => s.bankAccounts);
2258
- const statementEntries = useFinancialStore((s) => s.statementEntries);
2344
+ const createTransfer = useFinancialStore((s) => s.createTransfer);
2345
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
2346
+ const [fromAccountId, setFromAccountId] = React14.useState(defaultFromId ?? "");
2347
+ const [toAccountId, setToAccountId] = React14.useState("");
2348
+ const [amount, setAmount] = React14.useState(0);
2349
+ const [date, setDate] = React14.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2350
+ const [notes, setNotes] = React14.useState("");
2351
+ const [saving, setSaving] = React14.useState(false);
2352
+ const [visible, setVisible] = React14.useState(false);
2353
+ React14.useEffect(() => {
2354
+ fetchBankAccounts();
2355
+ requestAnimationFrame(() => setVisible(true));
2356
+ }, []);
2357
+ function handleClose() {
2358
+ setVisible(false);
2359
+ setTimeout(onClose, 200);
2360
+ }
2361
+ const sameAccount = !!fromAccountId && fromAccountId === toAccountId;
2362
+ const canSubmit = !!fromAccountId && !!toAccountId && !sameAccount && amount > 0 && !saving;
2363
+ function handleKeyDown(e) {
2364
+ if (e.key !== "Enter" || e.shiftKey) return;
2365
+ if (e.target.tagName === "TEXTAREA") return;
2366
+ e.preventDefault();
2367
+ if (canSubmit) void handleSubmit();
2368
+ }
2369
+ async function handleSubmit() {
2370
+ if (!canSubmit) return;
2371
+ setSaving(true);
2372
+ try {
2373
+ await createTransfer({ fromAccountId, toAccountId, amount, date, notes: notes || void 0 });
2374
+ setVisible(false);
2375
+ setTimeout(onDone, 200);
2376
+ } catch {
2377
+ } finally {
2378
+ setSaving(false);
2379
+ }
2380
+ }
2381
+ 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";
2382
+ return reactDom.createPortal(
2383
+ /* @__PURE__ */ jsxRuntime.jsx(
2384
+ "div",
2385
+ {
2386
+ className: "fixed inset-0 z-50 flex items-center justify-center transition-colors duration-200",
2387
+ style: { backgroundColor: visible ? "rgba(0,0,0,0.5)" : "rgba(0,0,0,0)" },
2388
+ onClick: handleClose,
2389
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2390
+ "div",
2391
+ {
2392
+ 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",
2393
+ style: { opacity: visible ? 1 : 0, transform: visible ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)" },
2394
+ onClick: (e) => e.stopPropagation(),
2395
+ onKeyDown: handleKeyDown,
2396
+ children: [
2397
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b", children: [
2398
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2399
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRightLeft, { className: "h-4 w-4 text-warning" }),
2400
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-semibold", children: t2("financial.transfer.title") })
2401
+ ] }),
2402
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleClose, className: "p-1 text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) })
2403
+ ] }),
2404
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-5 py-4 space-y-4", children: [
2405
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
2406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2407
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.transfer.from") }),
2408
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { value: fromAccountId, onChange: (e) => setFromAccountId(e.target.value), className: selectClass, children: [
2409
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("financial.transfer.selectAccount") }),
2410
+ bankAccounts.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a.id, children: a.name }, a.id))
2411
+ ] })
2412
+ ] }),
2413
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2414
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.transfer.to") }),
2415
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { value: toAccountId, onChange: (e) => setToAccountId(e.target.value), className: selectClass, children: [
2416
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("financial.transfer.selectAccount") }),
2417
+ bankAccounts.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a.id, children: a.name }, a.id))
2418
+ ] })
2419
+ ] })
2420
+ ] }),
2421
+ sameAccount && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: t2("financial.transfer.sameAccount") }),
2422
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
2423
+ /* @__PURE__ */ jsxRuntime.jsx(
2424
+ ui.CurrencyInput,
2425
+ {
2426
+ label: t2("financial.transfer.amount"),
2427
+ value: amount,
2428
+ onChange: setAmount,
2429
+ symbol: currency.symbol,
2430
+ locale: currency.locale,
2431
+ currencyCode: currency.code
2432
+ }
2433
+ ),
2434
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2435
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.transfer.date") }),
2436
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DatePicker, { value: date, onChange: setDate, className: "mt-1" })
2437
+ ] })
2438
+ ] }),
2439
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2440
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.transfer.notes") }),
2441
+ /* @__PURE__ */ jsxRuntime.jsx(
2442
+ "input",
2443
+ {
2444
+ type: "text",
2445
+ value: notes,
2446
+ onChange: (e) => setNotes(e.target.value),
2447
+ placeholder: t2("financial.transfer.optionalNotes"),
2448
+ 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"
2449
+ }
2450
+ )
2451
+ ] })
2452
+ ] }),
2453
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 px-5 py-4 border-t", children: [
2454
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "outline", size: "sm", onClick: handleClose, children: t2("financial.transfer.cancel") }),
2455
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "default", size: "sm", onClick: handleSubmit, disabled: !canSubmit, children: [
2456
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRightLeft, { className: "h-3.5 w-3.5" }),
2457
+ saving ? t2("financial.transfer.processing") : t2("financial.transfer.submit")
2458
+ ] })
2459
+ ] })
2460
+ ]
2461
+ }
2462
+ )
2463
+ }
2464
+ ),
2465
+ document.body
2466
+ );
2467
+ }
2468
+ var ALL = "all";
2469
+ function ReconcileMarker({ kind, label }) {
2470
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipProvider, { delayDuration: 150, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tooltip, { children: [
2471
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0 cursor-default", children: kind === "reconciled" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCheck, { className: "h-3 w-3 text-success/70" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Landmark, { className: "h-3 w-3 text-muted-foreground/40" }) }) }),
2472
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipContent, { className: "text-xs", children: label })
2473
+ ] }) });
2474
+ }
2475
+ function ReconciledWithRef({ refLabel, tooltip, onOpen }) {
2476
+ const inner = /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-muted-foreground max-w-[160px]", children: [
2477
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRightLeft, { className: "h-2.5 w-2.5 shrink-0" }),
2478
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: refLabel })
2479
+ ] });
2480
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipProvider, { delayDuration: 150, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tooltip, { children: [
2481
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipTrigger, { asChild: true, children: onOpen ? /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onOpen, className: "hover:text-primary transition-colors", children: inner }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cursor-default", children: inner }) }),
2482
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipContent, { className: "text-xs", children: tooltip })
2483
+ ] }) });
2484
+ }
2485
+ function StatementsView({ onNavigate }) {
2486
+ const t2 = core.useTranslation();
2487
+ const { currency } = useFinancialConfig();
2488
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
2489
+ const statement = useFinancialStore((s) => s.statement);
2259
2490
  const statementLoading = useFinancialStore((s) => s.statementLoading);
2491
+ const paymentMethods = useFinancialStore((s) => s.paymentMethods);
2492
+ const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
2260
2493
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
2494
+ const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
2261
2495
  const fetchStatement = useFinancialStore((s) => s.fetchStatement);
2262
- const [accountId, setAccountId] = React11.useState("");
2263
- const [dateFrom, setDateFrom] = React11.useState(() => {
2496
+ const [accountId, setAccountId] = React14.useState("");
2497
+ const [dateFrom, setDateFrom] = React14.useState(() => {
2264
2498
  const d = /* @__PURE__ */ new Date();
2265
- d.setMonth(d.getMonth() - 1);
2499
+ d.setMonth(d.getMonth() - 3);
2266
2500
  return d.toISOString().slice(0, 10);
2267
2501
  });
2268
- const [dateTo, setDateTo] = React11.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2269
- React11.useEffect(() => {
2502
+ const [dateTo, setDateTo] = React14.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2503
+ const [showTransfer, setShowTransfer] = React14.useState(false);
2504
+ React14.useEffect(() => {
2270
2505
  fetchBankAccounts();
2506
+ fetchPaymentMethods();
2271
2507
  }, []);
2272
- React11.useEffect(() => {
2508
+ const methodName = React14.useMemo(() => {
2509
+ const byId = new Map(paymentMethods.map((m) => [m.id, m.name]));
2510
+ const typeById = new Map(paymentMethodTypes.map((tp) => [tp.id, tp.name]));
2511
+ return (mov) => {
2512
+ if (mov.paymentMethodId && byId.has(mov.paymentMethodId)) return byId.get(mov.paymentMethodId);
2513
+ if (mov.paymentMethodTypeId && typeById.has(mov.paymentMethodTypeId)) return typeById.get(mov.paymentMethodTypeId);
2514
+ if (mov.cardBrand) return mov.cardBrand;
2515
+ return "\u2014";
2516
+ };
2517
+ }, [paymentMethods, paymentMethodTypes]);
2518
+ function openInvoice(e) {
2519
+ if (!onNavigate || !e.movement.invoiceId) return;
2520
+ const base = e.movement.direction === "credit" ? "receivables" : "payables";
2521
+ onNavigate(`${base}-detail:${e.movement.invoiceId}`);
2522
+ }
2523
+ React14.useEffect(() => {
2273
2524
  if (accountId) {
2274
- fetchStatement({ bankAccountId: accountId, dateRange: { from: dateFrom, to: dateTo } });
2525
+ fetchStatement({
2526
+ bankAccountId: accountId === ALL ? void 0 : accountId,
2527
+ dateRange: { from: dateFrom, to: dateTo }
2528
+ });
2275
2529
  }
2276
2530
  }, [accountId, dateFrom, dateTo]);
2277
- React11.useEffect(() => {
2278
- if (!accountId && bankAccounts.length > 0) setAccountId(bankAccounts[0].id);
2279
- }, [bankAccounts]);
2280
- const columns = React11.useMemo(() => [
2531
+ React14.useEffect(() => {
2532
+ if (!accountId) setAccountId(ALL);
2533
+ }, []);
2534
+ const fmt = (n) => saas.formatCurrency(n, currency);
2535
+ const columns = React14.useMemo(() => [
2281
2536
  {
2282
2537
  id: "date",
2283
2538
  header: t2("financial.statements.columnDate"),
@@ -2286,29 +2541,112 @@ function StatementsView() {
2286
2541
  {
2287
2542
  id: "description",
2288
2543
  header: t2("financial.statements.columnDescription"),
2289
- cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2290
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium", children: row.original.invoice?.contactName || row.original.movement.notes || "Transaction" }),
2291
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground capitalize", children: row.original.movement.movementKind })
2292
- ] })
2544
+ cell: ({ row }) => {
2545
+ const e = row.original;
2546
+ const isTransfer = e.entryKind !== "movement";
2547
+ const memo = e.movement.notes;
2548
+ const title = e.invoice?.contactName || (isTransfer ? t2("financial.statements.transfer") : memo || "Transaction");
2549
+ const memoLine = memo && memo !== title ? memo : void 0;
2550
+ const doc = e.invoice?.fiscalNumber;
2551
+ const inst = e.movement.installmentNumber;
2552
+ const canOpen = !!onNavigate && !!e.movement.invoiceId;
2553
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2554
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
2555
+ /* @__PURE__ */ jsxRuntime.jsx(saas.PersonLink, { personId: e.invoice?.contactId, name: title, size: "sm" }),
2556
+ doc && (canOpen ? /* @__PURE__ */ jsxRuntime.jsx(
2557
+ "button",
2558
+ {
2559
+ onClick: () => openInvoice(e),
2560
+ className: "text-[10px] text-muted-foreground font-mono hover:text-primary hover:underline",
2561
+ title: t2("financial.statements.viewInvoice"),
2562
+ children: doc
2563
+ }
2564
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: doc })),
2565
+ isTransfer && /* @__PURE__ */ jsxRuntime.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") }),
2566
+ e.movement.reconciledAt ? /* @__PURE__ */ jsxRuntime.jsx(ReconcileMarker, { kind: "reconciled", label: t2("financial.statements.reconciled") }) : e.movement.externalSource ? /* @__PURE__ */ jsxRuntime.jsx(ReconcileMarker, { kind: "imported", label: t2("financial.statements.imported") }) : null,
2567
+ e.reconciledWith && (() => {
2568
+ const rw = e.reconciledWith;
2569
+ const refLabel = rw.fiscalNumber || rw.contactName || rw.description || rw.movementId.slice(0, 8);
2570
+ const base = e.movement.direction === "credit" ? "receivables" : "payables";
2571
+ const onOpen = onNavigate && rw.invoiceId ? () => onNavigate(`${base}-detail:${rw.invoiceId}`) : void 0;
2572
+ return /* @__PURE__ */ jsxRuntime.jsx(
2573
+ ReconciledWithRef,
2574
+ {
2575
+ refLabel,
2576
+ tooltip: t2("financial.statements.reconciledWith", { ref: refLabel }),
2577
+ onOpen
2578
+ }
2579
+ );
2580
+ })()
2581
+ ] }),
2582
+ memoLine && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-foreground/80", children: memoLine }),
2583
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2584
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize", children: isTransfer && e.counterAccountName ? e.entryKind === "transfer-in" ? `\u2190 ${e.counterAccountName}` : `\u2192 ${e.counterAccountName}` : e.movement.movementKind }),
2585
+ inst ? ` \xB7 ${t2("financial.statements.installmentShort", { n: inst })}` : ""
2586
+ ] })
2587
+ ] });
2588
+ }
2589
+ },
2590
+ {
2591
+ id: "method",
2592
+ header: t2("financial.statements.columnMethod"),
2593
+ cell: ({ row }) => {
2594
+ const mov = row.original.movement;
2595
+ const name = methodName(mov);
2596
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2597
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs", children: name }),
2598
+ mov.cardInstallments && mov.cardInstallments > 1 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2599
+ mov.cardInstallments,
2600
+ "x"
2601
+ ] })
2602
+ ] });
2603
+ }
2293
2604
  },
2294
2605
  {
2295
2606
  id: "debit",
2296
2607
  header: t2("financial.statements.columnDebit"),
2297
- cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block text-destructive text-xs", children: row.original.movement.direction === "debit" ? saas.formatCurrency(row.original.movement.paidAmount, currency) : "" })
2608
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block text-destructive text-xs", children: row.original.movement.direction === "debit" ? fmt(row.original.net) : "" })
2298
2609
  },
2299
2610
  {
2300
2611
  id: "credit",
2301
2612
  header: t2("financial.statements.columnCredit"),
2302
- cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block text-success text-xs", children: row.original.movement.direction === "credit" ? saas.formatCurrency(row.original.movement.paidAmount, currency) : "" })
2613
+ cell: ({ row }) => {
2614
+ const e = row.original;
2615
+ if (e.movement.direction !== "credit") return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right" });
2616
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right", children: [
2617
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-success text-xs", children: fmt(e.net) }),
2618
+ e.fee > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "block text-[10px] text-muted-foreground", children: [
2619
+ fmt(e.gross),
2620
+ " \u2212 ",
2621
+ fmt(e.fee)
2622
+ ] })
2623
+ ] });
2624
+ }
2625
+ },
2626
+ {
2627
+ id: "fee",
2628
+ header: t2("financial.statements.columnFee"),
2629
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block text-warning text-xs", children: row.original.fee > 0 ? fmt(row.original.fee) : "" })
2303
2630
  },
2304
2631
  {
2305
2632
  id: "balance",
2306
2633
  header: t2("financial.statements.columnBalance"),
2307
- cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block font-medium text-xs", children: saas.formatCurrency(row.original.runningBalance, currency) })
2634
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block font-medium text-xs", children: fmt(row.original.runningBalance) })
2308
2635
  }
2309
- ], [currency, t2]);
2636
+ ], [currency, t2, methodName, onNavigate]);
2637
+ const entries = statement?.entries ?? [];
2310
2638
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
2311
- /* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title: t2("financial.statements.title"), subtitle: t2("financial.statements.subtitle") }),
2639
+ /* @__PURE__ */ jsxRuntime.jsx(
2640
+ ui.SubpageHeader,
2641
+ {
2642
+ title: t2("financial.statements.title"),
2643
+ subtitle: t2("financial.statements.subtitle"),
2644
+ actions: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
2645
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRightLeft, { className: "h-3.5 w-3.5" }),
2646
+ t2("financial.statements.newTransfer")
2647
+ ] })
2648
+ }
2649
+ ),
2312
2650
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-end", children: [
2313
2651
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 max-w-xs", children: [
2314
2652
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[10px] font-medium text-muted-foreground uppercase", children: t2("financial.statements.account") }),
@@ -2317,9 +2655,9 @@ function StatementsView() {
2317
2655
  {
2318
2656
  value: accountId,
2319
2657
  onChange: (e) => setAccountId(e.target.value),
2320
- 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",
2658
+ 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",
2321
2659
  children: [
2322
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("financial.statements.selectAccount") }),
2660
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: ALL, children: t2("financial.summary.allAccounts") }),
2323
2661
  bankAccounts.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a.id, children: a.name }, a.id))
2324
2662
  ]
2325
2663
  }
@@ -2334,10 +2672,31 @@ function StatementsView() {
2334
2672
  /* @__PURE__ */ jsxRuntime.jsx(ui.DatePicker, { value: dateTo, onChange: setDateTo, className: "mt-0.5" })
2335
2673
  ] })
2336
2674
  ] }),
2675
+ accountId && statement && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-2", children: [
2676
+ /* @__PURE__ */ jsxRuntime.jsx(SummaryStat, { label: t2("financial.statements.opening"), value: fmt(statement.openingBalance) }),
2677
+ /* @__PURE__ */ jsxRuntime.jsx(SummaryStat, { label: t2("financial.statements.totalCredits"), value: fmt(statement.totalCredits), positive: true }),
2678
+ /* @__PURE__ */ jsxRuntime.jsx(SummaryStat, { label: t2("financial.statements.totalDebits"), value: fmt(statement.totalDebits), negative: true }),
2679
+ /* @__PURE__ */ jsxRuntime.jsx(SummaryStat, { label: t2("financial.statements.closing"), value: fmt(statement.closingBalance), accent: true })
2680
+ ] }),
2337
2681
  !accountId ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border-2 border-dashed border-muted", children: [
2338
2682
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Receipt, { className: "h-8 w-8 text-muted-foreground mb-2" }),
2339
2683
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.statements.selectToView") })
2340
- ] }) : statementLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns, data: [], loading: true, skeletonRows: 5 }) : statementEntries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns, data: [], emptyMessage: t2("financial.statements.noTransactions") }) : /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns, data: statementEntries })
2684
+ ] }) : statementLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns, data: [], loading: true, skeletonRows: 8, compact: true }) : entries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns, data: [], emptyMessage: t2("financial.statements.noTransactions"), compact: true }) : /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns, data: entries, compact: true }),
2685
+ showTransfer && /* @__PURE__ */ jsxRuntime.jsx(
2686
+ TransferModal,
2687
+ {
2688
+ defaultFromId: accountId && accountId !== ALL ? accountId : void 0,
2689
+ onClose: () => setShowTransfer(false),
2690
+ onDone: () => setShowTransfer(false)
2691
+ }
2692
+ )
2693
+ ] });
2694
+ }
2695
+ function SummaryStat({ label, value, accent, positive, negative }) {
2696
+ const valueClass = positive ? "text-success" : negative ? "text-destructive" : accent ? "text-foreground" : "text-foreground";
2697
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `rounded-lg border px-3 py-2 ${accent ? "bg-muted/40" : "bg-card"}`, children: [
2698
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-medium text-muted-foreground uppercase truncate", children: label }),
2699
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm font-semibold mt-0.5 ${valueClass}`, children: value })
2341
2700
  ] });
2342
2701
  }
2343
2702
  function CommissionsView() {
@@ -2354,7 +2713,7 @@ function CardsView() {
2354
2713
  const cardTransactions = useFinancialStore((s) => s.cardTransactions);
2355
2714
  const cardsLoading = useFinancialStore((s) => s.cardsLoading);
2356
2715
  const fetchCardTransactions = useFinancialStore((s) => s.fetchCardTransactions);
2357
- React11.useEffect(() => {
2716
+ React14.useEffect(() => {
2358
2717
  fetchCardTransactions();
2359
2718
  }, []);
2360
2719
  const byBrand = {};
@@ -2403,6 +2762,151 @@ function CardsView() {
2403
2762
  ] }, brand))
2404
2763
  ] });
2405
2764
  }
2765
+ function ReconciliationView() {
2766
+ const t2 = core.useTranslation();
2767
+ const { currency } = useFinancialConfig();
2768
+ const provider = useFinancialProvider();
2769
+ const fmt = (n) => saas.formatCurrency(n, currency);
2770
+ const [lines, setLines] = React14.useState([]);
2771
+ const [loading, setLoading] = React14.useState(true);
2772
+ const [selectedId, setSelectedId] = React14.useState(null);
2773
+ const [candidates, setCandidates] = React14.useState([]);
2774
+ const [candidatesLoading, setCandidatesLoading] = React14.useState(false);
2775
+ const [busy, setBusy] = React14.useState(false);
2776
+ const supported = typeof provider.getUnreconciled === "function";
2777
+ const load = React14.useCallback(async () => {
2778
+ if (!provider.getUnreconciled) return;
2779
+ setLoading(true);
2780
+ try {
2781
+ const data = await provider.getUnreconciled();
2782
+ setLines(data);
2783
+ setSelectedId((prev) => prev && data.some((l) => l.id === prev) ? prev : null);
2784
+ } finally {
2785
+ setLoading(false);
2786
+ }
2787
+ }, [provider]);
2788
+ React14.useEffect(() => {
2789
+ void load();
2790
+ }, [load]);
2791
+ React14.useEffect(() => {
2792
+ if (!selectedId || !provider.suggestReconciliation) {
2793
+ setCandidates([]);
2794
+ return;
2795
+ }
2796
+ let cancelled = false;
2797
+ setCandidatesLoading(true);
2798
+ provider.suggestReconciliation(selectedId).then((c) => {
2799
+ if (!cancelled) setCandidates(c);
2800
+ }).finally(() => {
2801
+ if (!cancelled) setCandidatesLoading(false);
2802
+ });
2803
+ return () => {
2804
+ cancelled = true;
2805
+ };
2806
+ }, [selectedId, provider]);
2807
+ async function match(bankMovementId, matchedMovementId) {
2808
+ if (!provider.reconcileMovement) return;
2809
+ setBusy(true);
2810
+ try {
2811
+ await provider.reconcileMovement({ bankMovementId, matchedMovementId });
2812
+ ui.toast.success(t2("financial.reconciliation.match"));
2813
+ await load();
2814
+ } catch {
2815
+ ui.toast.error(t2("common.error"));
2816
+ } finally {
2817
+ setBusy(false);
2818
+ }
2819
+ }
2820
+ if (!supported) {
2821
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-16 text-center", children: [
2822
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Landmark, { className: "h-8 w-8 text-muted-foreground/30 mx-auto mb-2" }),
2823
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.reconciliation.empty") })
2824
+ ] });
2825
+ }
2826
+ const selected = lines.find((l) => l.id === selectedId) ?? null;
2827
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
2828
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
2829
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2830
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold tracking-tight", children: t2("financial.reconciliation.title") }),
2831
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.reconciliation.subtitle") })
2832
+ ] }),
2833
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2834
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: lines.length > 0 ? t2("financial.reconciliation.pending", { count: lines.length }) : t2("financial.reconciliation.allDone") }),
2835
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: () => void load(), disabled: loading || busy, children: [
2836
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }),
2837
+ t2("financial.reconciliation.refresh")
2838
+ ] })
2839
+ ] })
2840
+ ] }),
2841
+ lines.length === 0 && !loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm py-16 text-center", children: [
2842
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-8 w-8 text-success/40 mx-auto mb-2" }),
2843
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: t2("financial.reconciliation.empty") }),
2844
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1", children: t2("financial.reconciliation.emptyHint") })
2845
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
2846
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm overflow-hidden", children: [
2847
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
2848
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Landmark, { className: "h-4 w-4 text-muted-foreground" }),
2849
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: t2("financial.reconciliation.bankLines") })
2850
+ ] }),
2851
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y max-h-[60vh] overflow-auto", children: lines.map((line) => {
2852
+ const isCredit = line.direction === "credit";
2853
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2854
+ "button",
2855
+ {
2856
+ onClick: () => setSelectedId(line.id),
2857
+ 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" : ""}`,
2858
+ children: [
2859
+ isCredit ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownCircle, { className: "h-4 w-4 text-success shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUpCircle, { className: "h-4 w-4 text-destructive shrink-0" }),
2860
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
2861
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm truncate", children: line.notes || line.externalId }),
2862
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2863
+ line.paymentDate ?? line.dueDate,
2864
+ line.externalSource && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-1 uppercase tracking-wide", children: [
2865
+ "\xB7 ",
2866
+ line.externalSource
2867
+ ] })
2868
+ ] })
2869
+ ] }),
2870
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm font-semibold shrink-0 ${isCredit ? "text-success" : "text-destructive"}`, children: fmt(line.amount) })
2871
+ ]
2872
+ },
2873
+ line.id
2874
+ );
2875
+ }) })
2876
+ ] }),
2877
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm overflow-hidden", children: [
2878
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
2879
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-4 w-4 text-muted-foreground" }),
2880
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: t2("financial.reconciliation.candidates") })
2881
+ ] }),
2882
+ !selected ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-4 py-16 text-center text-sm text-muted-foreground", children: t2("financial.reconciliation.selectLine") }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3 space-y-2", children: candidatesLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "\u2026" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2883
+ candidates.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 rounded-md border p-2.5", children: [
2884
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
2885
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm truncate", children: c.invoice?.contactName || c.movement.notes || c.invoice?.fiscalNumber || t2("financial.reconciliation.match") }),
2886
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2887
+ c.movement.dueDate,
2888
+ /* @__PURE__ */ jsxRuntime.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) }) })
2889
+ ] })
2890
+ ] }),
2891
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold shrink-0", children: fmt(c.movement.amount) }),
2892
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "sm", disabled: busy, onClick: () => match(selected.id, c.movement.id), children: [
2893
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, { className: "h-3.5 w-3.5" }),
2894
+ t2("financial.reconciliation.match")
2895
+ ] })
2896
+ ] }, c.movement.id)),
2897
+ candidates.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-6 text-center text-xs text-muted-foreground", children: t2("financial.reconciliation.noCandidates") }),
2898
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-2 mt-1 border-t", children: [
2899
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", className: "w-full", disabled: busy, onClick: () => match(selected.id), children: [
2900
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" }),
2901
+ t2("financial.reconciliation.accept")
2902
+ ] }),
2903
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground text-center mt-1", children: t2("financial.reconciliation.acceptHint") })
2904
+ ] })
2905
+ ] }) })
2906
+ ] })
2907
+ ] })
2908
+ ] });
2909
+ }
2406
2910
  function FinancialGeneralSettings() {
2407
2911
  const t2 = core.useTranslation();
2408
2912
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
@@ -2436,7 +2940,7 @@ var STEPS = [
2436
2940
  ];
2437
2941
  function FinancialOnboarding({ onComplete }) {
2438
2942
  const t2 = core.useTranslation();
2439
- const [step, setStep] = React11.useState(0);
2943
+ const [step, setStep] = React14.useState(0);
2440
2944
  const current = STEPS[step];
2441
2945
  const isLast = step === STEPS.length - 1;
2442
2946
  const Icon = current.icon;
@@ -2527,13 +3031,20 @@ function buildNav(config, activeView, navigate, t2) {
2527
3031
  onClick: () => navigate("cash-registers")
2528
3032
  });
2529
3033
  }
2530
- if (config.modules.statements) {
3034
+ items.push({
3035
+ id: "statements",
3036
+ label: t2("financial.nav.statements"),
3037
+ icon: "Receipt",
3038
+ active: activeView === "statements",
3039
+ onClick: () => navigate("statements")
3040
+ });
3041
+ if (config.modules.reconciliation) {
2531
3042
  items.push({
2532
- id: "statements",
2533
- label: t2("financial.nav.statements"),
2534
- icon: "Receipt",
2535
- active: activeView === "statements",
2536
- onClick: () => navigate("statements")
3043
+ id: "reconciliation",
3044
+ label: t2("financial.nav.reconciliation"),
3045
+ icon: "ArrowLeftRight",
3046
+ active: activeView === "reconciliation",
3047
+ onClick: () => navigate("reconciliation")
2537
3048
  });
2538
3049
  }
2539
3050
  if (config.modules.commissions) {
@@ -2561,7 +3072,7 @@ function buildNav(config, activeView, navigate, t2) {
2561
3072
  return items;
2562
3073
  }
2563
3074
  function FinancialPage({ config, provider, store, registries }) {
2564
- const { view, direction, navigate } = saas.useModuleNavigation("/financial", {
3075
+ const { view, direction, navigate, previousView, back } = saas.useModuleNavigation("/financial", {
2565
3076
  summary: 0,
2566
3077
  "payables-list": 0,
2567
3078
  "payables-new": 1,
@@ -2569,11 +3080,12 @@ function FinancialPage({ config, provider, store, registries }) {
2569
3080
  "receivables-new": 1,
2570
3081
  "cash-registers": 0,
2571
3082
  statements: 0,
3083
+ reconciliation: 0,
2572
3084
  commissions: 0,
2573
3085
  cards: 0,
2574
3086
  settings: 1
2575
3087
  });
2576
- const [onboardingComplete, setOnboardingComplete] = React11.useState(() => {
3088
+ const [onboardingComplete, setOnboardingComplete] = React14.useState(() => {
2577
3089
  try {
2578
3090
  return localStorage.getItem("saas-core:financial-onboarded") === "true";
2579
3091
  } catch {
@@ -2584,7 +3096,7 @@ function FinancialPage({ config, provider, store, registries }) {
2584
3096
  const intent = parseIntent(view);
2585
3097
  const isSettings = view === "settings";
2586
3098
  const nav = buildNav(config, view, navigate, t2);
2587
- const quickActions = React11.useMemo(() => {
3099
+ const quickActions = React14.useMemo(() => {
2588
3100
  const actions = [];
2589
3101
  if (config.modules.payables) {
2590
3102
  actions.push({
@@ -2626,24 +3138,30 @@ function FinancialPage({ config, provider, store, registries }) {
2626
3138
  return /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsx(FinancialOnboarding, { onComplete: handleOnboardingComplete }) });
2627
3139
  }
2628
3140
  if (isSettings && registries && registries.length > 0) {
2629
- return /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsx(ui.PageTransition, { transitionKey: "settings", direction, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "24px" }, children: [
2630
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "16px" }, children: [
2631
- /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "20px", fontWeight: 600, margin: 0 }, children: t2("financial.settingsPage.title") }),
2632
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--muted-foreground, #6b7280)", margin: "4px 0 0", fontSize: "14px" }, children: t2("financial.settingsPage.subtitle") })
2633
- ] }),
2634
- /* @__PURE__ */ jsxRuntime.jsx(FinancialGeneralSettings, {})
2635
- ] }) }) });
3141
+ return /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsx(ui.PageTransition, { transitionKey: "settings", direction, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
3142
+ saas.PluginSettingsPanel,
3143
+ {
3144
+ title: t2("financial.settingsPage.title"),
3145
+ subtitle: t2("financial.settingsPage.subtitle"),
3146
+ generalSettings: /* @__PURE__ */ jsxRuntime.jsx(FinancialGeneralSettings, {}),
3147
+ registries,
3148
+ hostPluginId: "financial",
3149
+ routeBase: "/settings/financial"
3150
+ }
3151
+ ) }) }) });
2636
3152
  }
2637
3153
  function renderView() {
2638
3154
  switch (intent.view) {
2639
3155
  case "payables":
2640
- return /* @__PURE__ */ jsxRuntime.jsx(PayablesView, { intent, onNavigate: navigate });
3156
+ return /* @__PURE__ */ jsxRuntime.jsx(PayablesView, { intent, onNavigate: navigate, previousView, back });
2641
3157
  case "receivables":
2642
- return /* @__PURE__ */ jsxRuntime.jsx(ReceivablesView, { intent, onNavigate: navigate });
3158
+ return /* @__PURE__ */ jsxRuntime.jsx(ReceivablesView, { intent, onNavigate: navigate, previousView, back });
2643
3159
  case "cash-registers":
2644
3160
  return /* @__PURE__ */ jsxRuntime.jsx(CashRegistersView, {});
2645
3161
  case "statements":
2646
- return /* @__PURE__ */ jsxRuntime.jsx(StatementsView, {});
3162
+ return /* @__PURE__ */ jsxRuntime.jsx(StatementsView, { onNavigate: navigate });
3163
+ case "reconciliation":
3164
+ return /* @__PURE__ */ jsxRuntime.jsx(ReconciliationView, {});
2647
3165
  case "commissions":
2648
3166
  return /* @__PURE__ */ jsxRuntime.jsx(CommissionsView, {});
2649
3167
  case "cards":
@@ -2675,7 +3193,7 @@ function FinancialPage({ config, provider, store, registries }) {
2675
3193
  }
2676
3194
  function useEnsureSummary() {
2677
3195
  const fetchSummary = useFinancialStore((s) => s.fetchSummary);
2678
- React11.useEffect(() => {
3196
+ React14.useEffect(() => {
2679
3197
  void fetchSummary();
2680
3198
  }, []);
2681
3199
  }
@@ -2799,7 +3317,7 @@ function RecentTransactions() {
2799
3317
  const { currency } = useFinancialConfig();
2800
3318
  const invoices = useFinancialStore((s) => s.invoices);
2801
3319
  const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
2802
- React11.useEffect(() => {
3320
+ React14.useEffect(() => {
2803
3321
  void fetchInvoices({ pageSize: 5 });
2804
3322
  }, []);
2805
3323
  const columns = [
@@ -2847,6 +3365,9 @@ function now() {
2847
3365
  function today() {
2848
3366
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2849
3367
  }
3368
+ function round2(n) {
3369
+ return Math.round(n * 100) / 100;
3370
+ }
2850
3371
  function matchesDateRange(dateStr, range) {
2851
3372
  if (!range || !dateStr) return true;
2852
3373
  return dateStr >= range.from && dateStr <= range.to;
@@ -3050,37 +3571,60 @@ function createMockFinancialProvider(options) {
3050
3571
  return paginate(results, query.page, query.pageSize);
3051
3572
  },
3052
3573
  async payMovement(input) {
3053
- const movement = store.movements.find((m) => m.id === input.movementId);
3054
- if (!movement) throw new Error(`Movement ${input.movementId} not found`);
3055
- movement.paidAmount += input.amount;
3056
- movement.paymentDate = input.paymentDate;
3057
- movement.paymentMethodId = input.paymentMethodId ?? movement.paymentMethodId;
3058
- movement.bankAccountId = input.bankAccountId ?? movement.bankAccountId;
3059
- movement.cashSessionId = input.cashSessionId ?? movement.cashSessionId;
3060
- movement.cardBrand = input.cardBrand;
3061
- movement.cardInstallments = input.cardInstallments;
3062
- movement.status = movement.paidAmount >= movement.amount ? "paid" : "partial";
3063
- movement.updatedAt = now();
3064
- if (movement.bankAccountId) {
3065
- const account = store.bankAccounts.find((a) => a.id === movement.bankAccountId);
3574
+ const bill = store.movements.find((m) => m.id === input.movementId);
3575
+ if (!bill) throw new Error(`Movement ${input.movementId} not found`);
3576
+ const method = input.paymentMethodId ? store.paymentMethods.find((pm) => pm.id === input.paymentMethodId) : void 0;
3577
+ const feePct = bill.direction === "credit" ? method?.discountValue ?? 0 : 0;
3578
+ const thisFee = round2(input.amount * (feePct / 100));
3579
+ const payment = {
3580
+ id: uid(),
3581
+ invoiceId: bill.invoiceId,
3582
+ direction: bill.direction,
3583
+ movementKind: "payment",
3584
+ amount: input.amount,
3585
+ paidAmount: input.amount,
3586
+ feeAmount: thisFee,
3587
+ status: "paid",
3588
+ dueDate: bill.dueDate,
3589
+ paymentDate: input.paymentDate,
3590
+ installmentNumber: bill.installmentNumber,
3591
+ paymentMethodId: input.paymentMethodId,
3592
+ paymentMethodTypeId: input.paymentMethodTypeId,
3593
+ bankAccountId: input.bankAccountId,
3594
+ cashSessionId: input.cashSessionId,
3595
+ cardBrand: input.cardBrand,
3596
+ cardInstallments: input.cardInstallments,
3597
+ notes: input.notes,
3598
+ tenantId,
3599
+ createdAt: now(),
3600
+ updatedAt: now()
3601
+ };
3602
+ store.movements.push(payment);
3603
+ bill.paidAmount = round2(bill.paidAmount + input.amount);
3604
+ bill.status = bill.paidAmount >= bill.amount ? "paid" : "partial";
3605
+ bill.paymentDate = void 0;
3606
+ bill.bankAccountId = void 0;
3607
+ bill.updatedAt = now();
3608
+ if (input.bankAccountId) {
3609
+ const account = store.bankAccounts.find((a) => a.id === input.bankAccountId);
3066
3610
  if (account) {
3067
- if (movement.direction === "credit") {
3068
- account.currentBalance += input.amount;
3611
+ if (bill.direction === "credit") {
3612
+ account.currentBalance = round2(account.currentBalance + (input.amount - thisFee));
3069
3613
  } else {
3070
- account.currentBalance -= input.amount;
3614
+ account.currentBalance = round2(account.currentBalance - input.amount);
3071
3615
  }
3072
3616
  account.updatedAt = now();
3073
3617
  }
3074
3618
  }
3075
- if (movement.invoiceId) {
3076
- const invoice = store.invoices.find((i) => i.id === movement.invoiceId);
3619
+ if (bill.invoiceId) {
3620
+ const invoice = store.invoices.find((i) => i.id === bill.invoiceId);
3077
3621
  if (invoice) {
3078
- const invoiceMovements = store.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
3079
- invoice.paidAmount = invoiceMovements.reduce((sum, m) => sum + m.paidAmount, 0);
3622
+ const bills = store.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
3623
+ invoice.paidAmount = bills.reduce((sum, m) => sum + m.paidAmount, 0);
3080
3624
  recalcInvoiceStatus(invoice);
3081
3625
  }
3082
3626
  }
3083
- return movement;
3627
+ return payment;
3084
3628
  },
3085
3629
  async cancelMovement(id) {
3086
3630
  const movement = store.movements.find((m) => m.id === id);
@@ -3088,6 +3632,54 @@ function createMockFinancialProvider(options) {
3088
3632
  movement.status = "cancelled";
3089
3633
  movement.updatedAt = now();
3090
3634
  },
3635
+ // --- Transfers (between accounts) ---
3636
+ async createTransfer(input) {
3637
+ if (!input.fromAccountId || !input.toAccountId || input.fromAccountId === input.toAccountId) {
3638
+ throw new Error("Source and destination accounts must differ");
3639
+ }
3640
+ if (!(input.amount > 0)) throw new Error("Amount must be greater than zero");
3641
+ const transferId = uid();
3642
+ const debitMovementId = uid();
3643
+ const creditMovementId = uid();
3644
+ const base = {
3645
+ movementKind: "transfer",
3646
+ amount: input.amount,
3647
+ paidAmount: input.amount,
3648
+ feeAmount: 0,
3649
+ status: "paid",
3650
+ dueDate: input.date,
3651
+ paymentDate: input.date,
3652
+ notes: input.notes,
3653
+ tenantId,
3654
+ createdAt: now(),
3655
+ updatedAt: now()
3656
+ };
3657
+ store.movements.push({
3658
+ ...base,
3659
+ id: debitMovementId,
3660
+ direction: "debit",
3661
+ bankAccountId: input.fromAccountId,
3662
+ metadata: { transferId, transferRole: "out", counterAccountId: input.toAccountId }
3663
+ });
3664
+ store.movements.push({
3665
+ ...base,
3666
+ id: creditMovementId,
3667
+ direction: "credit",
3668
+ bankAccountId: input.toAccountId,
3669
+ metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
3670
+ });
3671
+ const from = store.bankAccounts.find((a) => a.id === input.fromAccountId);
3672
+ const to = store.bankAccounts.find((a) => a.id === input.toAccountId);
3673
+ if (from) {
3674
+ from.currentBalance = round2(from.currentBalance - input.amount);
3675
+ from.updatedAt = now();
3676
+ }
3677
+ if (to) {
3678
+ to.currentBalance = round2(to.currentBalance + input.amount);
3679
+ to.updatedAt = now();
3680
+ }
3681
+ return { transferId, debitMovementId, creditMovementId };
3682
+ },
3091
3683
  // --- Bank Accounts ---
3092
3684
  async getBankAccounts() {
3093
3685
  return store.bankAccounts.filter((a) => a.isActive);
@@ -3192,25 +3784,76 @@ function createMockFinancialProvider(options) {
3192
3784
  if (dateRange) results = results.filter((t2) => matchesDateRange(t2.expectedDate, dateRange));
3193
3785
  return results.sort((a, b) => a.expectedDate.localeCompare(b.expectedDate));
3194
3786
  },
3195
- // --- Statements ---
3787
+ // --- Statements (ERP extract: opening balance + realized cash + per-row net/fee) ---
3196
3788
  async getStatement(query) {
3197
- const account = store.bankAccounts.find((a) => a.id === query.bankAccountId);
3198
- if (!account) return [];
3199
- const movements = store.movements.filter(
3200
- (m) => m.bankAccountId === query.bankAccountId && m.status === "paid" && matchesDateRange(m.paymentDate, query.dateRange)
3201
- ).sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? ""));
3202
- let runningBalance = account.initialBalance;
3203
- const entries = [];
3204
- for (const movement of movements) {
3205
- if (movement.direction === "credit") {
3206
- runningBalance += movement.paidAmount;
3207
- } else {
3208
- runningBalance -= movement.paidAmount;
3789
+ const todayStr = today();
3790
+ const isRealized = (m) => m.status !== "cancelled" && !!m.paymentDate;
3791
+ const consolidated = !query.bankAccountId;
3792
+ const scopeAccounts = consolidated ? store.bankAccounts.filter((a) => a.isActive) : store.bankAccounts.filter((a) => a.id === query.bankAccountId);
3793
+ const accountIds = new Set(scopeAccounts.map((a) => a.id));
3794
+ const accountNameById = new Map(store.bankAccounts.map((a) => [a.id, a.name]));
3795
+ const initialSum = scopeAccounts.reduce((s, a) => s + a.initialBalance, 0);
3796
+ if (!consolidated && accountIds.size === 0) {
3797
+ return { entries: [], openingBalance: 0, closingBalance: 0, totalCredits: 0, totalDebits: 0, totalFees: 0, net: 0, accountId: query.bankAccountId };
3798
+ }
3799
+ const netOf = (m) => m.direction === "credit" ? m.paidAmount - (m.feeAmount ?? 0) : -m.paidAmount;
3800
+ const inScope = store.movements.filter((m) => isRealized(m) && (consolidated || m.bankAccountId && accountIds.has(m.bankAccountId)));
3801
+ const openingBalance = round2(initialSum + inScope.filter((m) => (m.paymentDate ?? "") < query.dateRange.from).reduce((s, m) => s + netOf(m), 0));
3802
+ const rangeMovs = inScope.filter((m) => matchesDateRange(m.paymentDate, query.dateRange)).sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || a.createdAt.localeCompare(b.createdAt));
3803
+ let balance = openingBalance;
3804
+ let totalCredits = 0, totalDebits = 0, totalFees = 0;
3805
+ const entries = rangeMovs.map((movement) => {
3806
+ const gross = movement.paidAmount;
3807
+ const isCredit = movement.direction === "credit";
3808
+ const fee = isCredit ? movement.feeAmount ?? 0 : 0;
3809
+ const net = isCredit ? round2(gross - fee) : gross;
3810
+ const isTransfer = movement.movementKind === "transfer";
3811
+ const meta = movement.metadata ?? {};
3812
+ const entryKind = isTransfer ? meta.transferRole === "in" ? "transfer-in" : "transfer-out" : "movement";
3813
+ const counterAccountId = isTransfer ? meta.counterAccountId : void 0;
3814
+ balance = round2(isCredit ? balance + net : balance - net);
3815
+ if (!(consolidated && isTransfer)) {
3816
+ if (isCredit) totalCredits = round2(totalCredits + net);
3817
+ else totalDebits = round2(totalDebits + net);
3818
+ totalFees = round2(totalFees + fee);
3209
3819
  }
3210
- const invoice = movement.invoiceId ? store.invoices.find((i) => i.id === movement.invoiceId) : void 0;
3211
- entries.push({ movement, invoice, runningBalance });
3820
+ return {
3821
+ movement,
3822
+ invoice: movement.invoiceId ? store.invoices.find((i) => i.id === movement.invoiceId) : void 0,
3823
+ runningBalance: balance,
3824
+ gross,
3825
+ fee,
3826
+ net,
3827
+ entryKind,
3828
+ counterAccountId,
3829
+ counterAccountName: counterAccountId ? accountNameById.get(counterAccountId) : void 0
3830
+ };
3831
+ });
3832
+ for (const e of entries) {
3833
+ const mid = e.movement.matchedMovementId;
3834
+ const mm = mid ? store.movements.find((m) => m.id === mid) : void 0;
3835
+ if (!mm) continue;
3836
+ const inv = mm.invoiceId ? store.invoices.find((i) => i.id === mm.invoiceId) : void 0;
3837
+ e.reconciledWith = {
3838
+ movementId: mm.id,
3839
+ description: mm.notes ?? void 0,
3840
+ invoiceId: mm.invoiceId,
3841
+ fiscalNumber: inv?.fiscalNumber,
3842
+ contactName: inv?.contactName
3843
+ };
3212
3844
  }
3213
- return entries;
3845
+ const accountCurrentBalance = round2(initialSum + inScope.filter((m) => (m.paymentDate ?? "") <= todayStr).reduce((s, m) => s + netOf(m), 0));
3846
+ return {
3847
+ entries,
3848
+ openingBalance,
3849
+ closingBalance: balance,
3850
+ totalCredits,
3851
+ totalDebits,
3852
+ totalFees,
3853
+ net: round2(totalCredits - totalDebits),
3854
+ accountId: query.bankAccountId,
3855
+ accountCurrentBalance
3856
+ };
3214
3857
  },
3215
3858
  // --- Summary ---
3216
3859
  async getSummary(dateRange) {
@@ -3225,7 +3868,7 @@ function createMockFinancialProvider(options) {
3225
3868
  const monthStart = todayStr.slice(0, 7) + "-01";
3226
3869
  const monthEnd = todayStr;
3227
3870
  const monthMovements = store.movements.filter(
3228
- (m) => m.status === "paid" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
3871
+ (m) => m.movementKind === "payment" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
3229
3872
  );
3230
3873
  const overdueReceivable = pendingReceivable.filter((m) => m.dueDate < todayStr);
3231
3874
  const overduePayable = pendingPayable.filter((m) => m.dueDate < todayStr);
@@ -3240,6 +3883,90 @@ function createMockFinancialProvider(options) {
3240
3883
  overduePayableCount: overduePayable.length,
3241
3884
  overduePayableAmount: overduePayable.reduce((s, m) => s + (m.amount - m.paidAmount), 0)
3242
3885
  };
3886
+ },
3887
+ // --- Reconciliation (conciliação) ---
3888
+ async importBankTransactions(input) {
3889
+ let imported = 0, duplicates = 0;
3890
+ for (const line of input.lines) {
3891
+ const dup = store.movements.some((m) => m.externalSource === line.externalSource && m.externalId === line.externalId);
3892
+ if (dup) {
3893
+ duplicates++;
3894
+ continue;
3895
+ }
3896
+ store.movements.push({
3897
+ id: uid(),
3898
+ direction: line.direction,
3899
+ movementKind: "payment",
3900
+ amount: line.amount,
3901
+ paidAmount: line.amount,
3902
+ status: "paid",
3903
+ dueDate: line.date,
3904
+ paymentDate: line.date,
3905
+ bankAccountId: line.bankAccountId ?? input.bankAccountId,
3906
+ notes: line.description,
3907
+ externalId: line.externalId,
3908
+ externalSource: line.externalSource,
3909
+ tenantId,
3910
+ createdAt: now(),
3911
+ updatedAt: now()
3912
+ });
3913
+ imported++;
3914
+ }
3915
+ return { imported, duplicates };
3916
+ },
3917
+ async getUnreconciled(query) {
3918
+ let results = store.movements.filter((m) => m.externalSource && !m.reconciledAt);
3919
+ if (query?.bankAccountId) results = results.filter((m) => m.bankAccountId === query.bankAccountId);
3920
+ if (query?.dateRange) results = results.filter((m) => matchesDateRange(m.paymentDate, query.dateRange));
3921
+ return results.sort((a, b) => (b.paymentDate ?? "").localeCompare(a.paymentDate ?? ""));
3922
+ },
3923
+ async suggestReconciliation(bankMovementId) {
3924
+ const line = store.movements.find((m) => m.id === bankMovementId);
3925
+ if (!line) return [];
3926
+ const amount = line.amount;
3927
+ const tol = Math.max(0.01, round2(amount * 0.02));
3928
+ const lineDate = line.paymentDate ?? line.dueDate;
3929
+ return store.movements.filter((m) => !m.externalSource && !m.reconciledAt && m.direction === line.direction && m.id !== bankMovementId && Math.abs(m.amount - amount) <= tol).map((m) => {
3930
+ const amtScore = amount > 0 ? Math.max(0, 1 - Math.abs(m.amount - amount) / amount) : 0;
3931
+ const refDate = m.dueDate ?? m.paymentDate;
3932
+ const days = lineDate && refDate ? Math.abs((new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5) : 30;
3933
+ const dateScore = Math.max(0, 1 - days / 30);
3934
+ return {
3935
+ movement: m,
3936
+ invoice: m.invoiceId ? store.invoices.find((i) => i.id === m.invoiceId) : void 0,
3937
+ score: round2(0.7 * amtScore + 0.3 * dateScore)
3938
+ };
3939
+ }).sort((a, b) => b.score - a.score).slice(0, 25);
3940
+ },
3941
+ async reconcileMovement(input) {
3942
+ const line = store.movements.find((m) => m.id === input.bankMovementId);
3943
+ if (line) {
3944
+ line.reconciledAt = now();
3945
+ line.matchedMovementId = input.matchedMovementId;
3946
+ line.updatedAt = now();
3947
+ }
3948
+ if (input.matchedMovementId) {
3949
+ const internal = store.movements.find((m) => m.id === input.matchedMovementId);
3950
+ if (internal) {
3951
+ internal.reconciledAt = now();
3952
+ internal.updatedAt = now();
3953
+ }
3954
+ }
3955
+ },
3956
+ async unreconcileMovement(bankMovementId) {
3957
+ const line = store.movements.find((m) => m.id === bankMovementId);
3958
+ if (!line) return;
3959
+ const matchedId = line.matchedMovementId;
3960
+ line.reconciledAt = void 0;
3961
+ line.matchedMovementId = void 0;
3962
+ line.updatedAt = now();
3963
+ if (matchedId) {
3964
+ const internal = store.movements.find((m) => m.id === matchedId);
3965
+ if (internal) {
3966
+ internal.reconciledAt = void 0;
3967
+ internal.updatedAt = now();
3968
+ }
3969
+ }
3243
3970
  }
3244
3971
  };
3245
3972
  return provider;
@@ -3319,6 +4046,21 @@ async function fetchInvoiceBalances(pub, ids) {
3319
4046
  }
3320
4047
  return map;
3321
4048
  }
4049
+ function round22(n) {
4050
+ return Math.round(n * 100) / 100;
4051
+ }
4052
+ async function getMethodFeePct(pub, paymentMethodId) {
4053
+ if (!paymentMethodId) return 0;
4054
+ const { data } = await pub.from("payment_methods").select("discount_value").eq("id", paymentMethodId).single();
4055
+ return Number(data?.discount_value) || 0;
4056
+ }
4057
+ function genId() {
4058
+ try {
4059
+ if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
4060
+ } catch {
4061
+ }
4062
+ return "tr-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
4063
+ }
3322
4064
  function createSupabaseFinancialProvider() {
3323
4065
  const provider = {
3324
4066
  // --- Invoices (orders at stage=invoiced/paid/partial/overdue/cancelled with direction) ---
@@ -3519,16 +4261,38 @@ function createSupabaseFinancialProvider() {
3519
4261
  const { core, pub } = getClients();
3520
4262
  const { data: mov } = await pub.from("financial_movements").select("*").eq("id", input.movementId).single();
3521
4263
  if (!mov) throw new Error("Movement not found");
3522
- const newPaid = (mov.paid_amount ?? 0) + input.amount;
3523
- const newStatus = newPaid >= mov.amount ? "paid" : "partial";
3524
- const updates = { paid_amount: newPaid, status: newStatus, payment_date: input.paymentDate };
3525
- if (input.paymentMethodId) updates.payment_method_id = input.paymentMethodId;
3526
- if (input.paymentMethodTypeId) updates.payment_method_type_id = input.paymentMethodTypeId;
3527
- if (input.bankAccountId) updates.bank_account_id = input.bankAccountId;
3528
- if (input.cashSessionId) updates.cash_session_id = input.cashSessionId;
3529
- if (input.cardBrand) updates.card_brand = input.cardBrand;
3530
- if (input.cardInstallments) updates.card_installments = input.cardInstallments;
3531
- const { data } = await pub.from("financial_movements").update(updates).eq("id", input.movementId).select().single();
4264
+ const tenantId = mov.tenant_id ?? getTenantId();
4265
+ const feePct = mov.direction === "credit" ? await getMethodFeePct(pub, input.paymentMethodId) : 0;
4266
+ const thisFee = round22(input.amount * (feePct / 100));
4267
+ const paymentRow = {
4268
+ tenant_id: tenantId,
4269
+ invoice_id: mov.invoice_id,
4270
+ direction: mov.direction,
4271
+ movement_kind: "payment",
4272
+ amount: input.amount,
4273
+ paid_amount: input.amount,
4274
+ fee_amount: thisFee,
4275
+ status: "paid",
4276
+ due_date: mov.due_date,
4277
+ payment_date: input.paymentDate,
4278
+ installment_number: mov.installment_number,
4279
+ payment_method_id: input.paymentMethodId ?? null,
4280
+ payment_method_type_id: input.paymentMethodTypeId ?? null,
4281
+ bank_account_id: input.bankAccountId ?? null,
4282
+ cash_session_id: input.cashSessionId ?? null,
4283
+ card_brand: input.cardBrand ?? null,
4284
+ card_installments: input.cardInstallments ?? null,
4285
+ notes: input.notes ?? null
4286
+ };
4287
+ let { data, error } = await pub.from("financial_movements").insert(paymentRow).select().single();
4288
+ if (error && /fee_amount/i.test(error.message ?? "")) {
4289
+ const { fee_amount, ...rest } = paymentRow;
4290
+ ({ data, error } = await pub.from("financial_movements").insert(rest).select().single());
4291
+ }
4292
+ if (error) throw error;
4293
+ const newPaid = round22((mov.paid_amount ?? 0) + input.amount);
4294
+ const billStatus = newPaid >= mov.amount ? "paid" : "partial";
4295
+ await pub.from("financial_movements").update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
3532
4296
  if (mov.invoice_id) {
3533
4297
  const { data: allMovs } = await pub.from("financial_movements").select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
3534
4298
  const movs = allMovs ?? [];
@@ -3549,6 +4313,51 @@ function createSupabaseFinancialProvider() {
3549
4313
  const { pub } = getClients();
3550
4314
  await pub.from("financial_movements").update({ status: "cancelled" }).eq("id", id);
3551
4315
  },
4316
+ // --- Transfers (between accounts) ---
4317
+ async createTransfer(input) {
4318
+ const { pub } = getClients();
4319
+ if (!input.fromAccountId || !input.toAccountId || input.fromAccountId === input.toAccountId) {
4320
+ throw new Error("Source and destination accounts must differ");
4321
+ }
4322
+ if (!(input.amount > 0)) throw new Error("Amount must be greater than zero");
4323
+ const tenantId = getTenantId();
4324
+ const transferId = genId();
4325
+ const base = {
4326
+ tenant_id: tenantId,
4327
+ movement_kind: "transfer",
4328
+ amount: input.amount,
4329
+ paid_amount: input.amount,
4330
+ fee_amount: 0,
4331
+ status: "paid",
4332
+ due_date: input.date,
4333
+ payment_date: input.date,
4334
+ notes: input.notes ?? null
4335
+ };
4336
+ const rowsToInsert = [
4337
+ {
4338
+ ...base,
4339
+ direction: "debit",
4340
+ bank_account_id: input.fromAccountId,
4341
+ metadata: { transferId, transferRole: "out", counterAccountId: input.toAccountId }
4342
+ },
4343
+ {
4344
+ ...base,
4345
+ direction: "credit",
4346
+ bank_account_id: input.toAccountId,
4347
+ metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
4348
+ }
4349
+ ];
4350
+ let { data, error } = await pub.from("financial_movements").insert(rowsToInsert).select("id, direction");
4351
+ if (error && /fee_amount/i.test(error.message ?? "")) {
4352
+ const stripped = rowsToInsert.map(({ fee_amount, ...rest }) => rest);
4353
+ ({ data, error } = await pub.from("financial_movements").insert(stripped).select("id, direction"));
4354
+ }
4355
+ if (error) throw error;
4356
+ const rows = data ?? [];
4357
+ const debitMovementId = rows.find((r) => r.direction === "debit")?.id ?? "";
4358
+ const creditMovementId = rows.find((r) => r.direction === "credit")?.id ?? "";
4359
+ return { transferId, debitMovementId, creditMovementId };
4360
+ },
3552
4361
  // --- Bank Accounts ---
3553
4362
  async getBankAccounts() {
3554
4363
  const { pub } = getClients();
@@ -3646,18 +4455,102 @@ function createSupabaseFinancialProvider() {
3646
4455
  getClients();
3647
4456
  return [];
3648
4457
  },
3649
- // --- Statements ---
4458
+ // --- Statements (ERP extract: opening balance + realized cash + per-row net/fee) ---
3650
4459
  async getStatement(query) {
3651
- const { pub } = getClients();
3652
- 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");
3653
- const { data: account } = await pub.from("bank_accounts").select("initial_balance").eq("id", query.bankAccountId).single();
3654
- let balance = account?.initial_balance ?? 0;
3655
- return (movs ?? []).map((r) => {
3656
- const mov = snakeToCamel(r);
3657
- if (mov.direction === "credit") balance += mov.paidAmount;
3658
- else balance -= mov.paidAmount;
3659
- return { movement: mov, runningBalance: balance };
4460
+ const { core, pub } = getClients();
4461
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4462
+ const { data: acctRows } = await pub.from("bank_accounts").select("id, name, initial_balance, is_active");
4463
+ const allAccounts = acctRows ?? [];
4464
+ const consolidated = !query.bankAccountId;
4465
+ const scopeAccounts = consolidated ? allAccounts.filter((a) => a.is_active) : allAccounts.filter((a) => a.id === query.bankAccountId);
4466
+ const accountIds = scopeAccounts.map((a) => a.id);
4467
+ const accountNameById = new Map(allAccounts.map((a) => [a.id, a.name]));
4468
+ const initialSum = scopeAccounts.reduce((s, a) => s + (Number(a.initial_balance) || 0), 0);
4469
+ if (!consolidated && accountIds.length === 0) {
4470
+ return { entries: [], openingBalance: 0, closingBalance: 0, totalCredits: 0, totalDebits: 0, totalFees: 0, net: 0, accountId: query.bankAccountId };
4471
+ }
4472
+ const scopeFilter = (qb) => consolidated ? qb : qb.in("bank_account_id", accountIds);
4473
+ const netOf = (r) => r.direction === "credit" ? (Number(r.paid_amount) || 0) - (Number(r.fee_amount) || 0) : -(Number(r.paid_amount) || 0);
4474
+ const { data: priorRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
4475
+ const priorNet = (priorRows ?? []).reduce((s, r) => s + netOf(r), 0);
4476
+ const openingBalance = round22(initialSum + priorNet);
4477
+ 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"));
4478
+ const invoiceIds = [...new Set((movs ?? []).map((r) => r.invoice_id).filter(Boolean))];
4479
+ const invoiceById = /* @__PURE__ */ new Map();
4480
+ if (invoiceIds.length) {
4481
+ const { data: orders } = await core.from("orders").select("id, party_id, reference_number, metadata").in("id", invoiceIds);
4482
+ for (const o of orders ?? []) {
4483
+ invoiceById.set(o.id, { id: o.id, contactId: o.party_id, contactName: o.metadata?.contactName, fiscalNumber: o.reference_number });
4484
+ }
4485
+ }
4486
+ let balance = openingBalance;
4487
+ let totalCredits = 0, totalDebits = 0, totalFees = 0;
4488
+ const entries = (movs ?? []).map((r) => {
4489
+ const movement = snakeToCamel(r);
4490
+ const gross = Number(r.paid_amount) || 0;
4491
+ const isCredit = r.direction === "credit";
4492
+ const fee = isCredit ? Number(r.fee_amount) || 0 : 0;
4493
+ const net = isCredit ? round22(gross - fee) : gross;
4494
+ const meta = r.metadata ?? {};
4495
+ const isTransfer = r.movement_kind === "transfer";
4496
+ const entryKind = isTransfer ? meta.transferRole === "in" ? "transfer-in" : "transfer-out" : "movement";
4497
+ const counterAccountId = isTransfer ? meta.counterAccountId : void 0;
4498
+ if (isCredit) balance = round22(balance + net);
4499
+ else balance = round22(balance - net);
4500
+ if (!(consolidated && isTransfer)) {
4501
+ if (isCredit) totalCredits = round22(totalCredits + net);
4502
+ else totalDebits = round22(totalDebits + net);
4503
+ totalFees = round22(totalFees + fee);
4504
+ }
4505
+ return {
4506
+ movement,
4507
+ invoice: r.invoice_id ? invoiceById.get(r.invoice_id) : void 0,
4508
+ runningBalance: balance,
4509
+ gross,
4510
+ fee,
4511
+ net,
4512
+ entryKind,
4513
+ counterAccountId,
4514
+ counterAccountName: counterAccountId ? accountNameById.get(counterAccountId) : void 0
4515
+ };
3660
4516
  });
4517
+ const matchedIds = [...new Set(entries.map((e) => e.movement.matchedMovementId).filter(Boolean))];
4518
+ if (matchedIds.length) {
4519
+ const { data: matched } = await pub.from("financial_movements").select("id, invoice_id, notes").in("id", matchedIds);
4520
+ const matchedById = new Map((matched ?? []).map((m) => [m.id, m]));
4521
+ const matchedInvoiceIds = [...new Set((matched ?? []).map((m) => m.invoice_id).filter(Boolean))];
4522
+ const invById = /* @__PURE__ */ new Map();
4523
+ if (matchedInvoiceIds.length) {
4524
+ const { data: orders } = await core.from("orders").select("id, reference_number, metadata").in("id", matchedInvoiceIds);
4525
+ for (const o of orders ?? []) invById.set(o.id, o);
4526
+ }
4527
+ for (const e of entries) {
4528
+ const mid = e.movement.matchedMovementId;
4529
+ const mm = mid ? matchedById.get(mid) : void 0;
4530
+ if (!mm) continue;
4531
+ const inv = mm.invoice_id ? invById.get(mm.invoice_id) : void 0;
4532
+ e.reconciledWith = {
4533
+ movementId: mm.id,
4534
+ description: mm.notes ?? void 0,
4535
+ invoiceId: mm.invoice_id ?? void 0,
4536
+ fiscalNumber: inv?.reference_number ?? void 0,
4537
+ contactName: inv?.metadata?.contactName ?? void 0
4538
+ };
4539
+ }
4540
+ }
4541
+ const { data: todayRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lte("payment_date", today2));
4542
+ const accountCurrentBalance = round22(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
4543
+ return {
4544
+ entries,
4545
+ openingBalance,
4546
+ closingBalance: balance,
4547
+ totalCredits,
4548
+ totalDebits,
4549
+ totalFees,
4550
+ net: round22(totalCredits - totalDebits),
4551
+ accountId: query.bankAccountId,
4552
+ accountCurrentBalance
4553
+ };
3661
4554
  },
3662
4555
  // --- Summary ---
3663
4556
  async getSummary(dateRange) {
@@ -3674,7 +4567,7 @@ function createSupabaseFinancialProvider() {
3674
4567
  const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today2);
3675
4568
  const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today2);
3676
4569
  const monthStart = today2.slice(0, 7) + "-01";
3677
- const monthPaid = allMovs.filter((m) => m.status === "paid" && m.payment_date >= monthStart);
4570
+ const monthPaid = allMovs.filter((m) => m.movement_kind === "payment" && m.payment_date >= monthStart);
3678
4571
  return {
3679
4572
  totalBalance,
3680
4573
  totalReceivable: unpaidCredit.reduce((s, m) => s + m.amount - m.paid_amount, 0),
@@ -3686,6 +4579,93 @@ function createSupabaseFinancialProvider() {
3686
4579
  overduePayableCount: overdueDebit.length,
3687
4580
  overduePayableAmount: overdueDebit.reduce((s, m) => s + m.amount - m.paid_amount, 0)
3688
4581
  };
4582
+ },
4583
+ // --- Reconciliation (conciliação) ---
4584
+ // Imported bank-statement lines live in financial_movements tagged with
4585
+ // (external_source, external_id), status='paid'. Reconciling links a line
4586
+ // to the internal movement it settles (or accepts it standalone).
4587
+ async importBankTransactions(input) {
4588
+ const { pub } = getClients();
4589
+ const tenantId = getTenantId();
4590
+ let imported = 0, duplicates = 0;
4591
+ for (const line of input.lines) {
4592
+ const { error } = await pub.from("financial_movements").insert({
4593
+ tenant_id: tenantId,
4594
+ direction: line.direction,
4595
+ movement_kind: "payment",
4596
+ amount: line.amount,
4597
+ paid_amount: line.amount,
4598
+ status: "paid",
4599
+ due_date: line.date,
4600
+ payment_date: line.date,
4601
+ bank_account_id: line.bankAccountId ?? input.bankAccountId ?? null,
4602
+ notes: line.description,
4603
+ external_id: line.externalId,
4604
+ external_source: line.externalSource
4605
+ });
4606
+ if (error) {
4607
+ if (error.code === "23505") duplicates++;
4608
+ else throw error;
4609
+ } else imported++;
4610
+ }
4611
+ return { imported, duplicates };
4612
+ },
4613
+ async getUnreconciled(query) {
4614
+ const { pub } = getClients();
4615
+ let qb = pub.from("financial_movements").select("*").not("external_source", "is", null).is("reconciled_at", null);
4616
+ if (query?.bankAccountId) qb = qb.eq("bank_account_id", query.bankAccountId);
4617
+ if (query?.dateRange) qb = qb.gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to);
4618
+ const { data } = await qb.order("payment_date", { ascending: false });
4619
+ return (data ?? []).map((r) => snakeToCamel(r));
4620
+ },
4621
+ async suggestReconciliation(bankMovementId) {
4622
+ const { core, pub } = getClients();
4623
+ const { data: line } = await pub.from("financial_movements").select("*").eq("id", bankMovementId).single();
4624
+ if (!line) return [];
4625
+ const amount = Number(line.amount) || 0;
4626
+ const lineDate = line.payment_date ?? line.due_date;
4627
+ const tol = Math.max(0.01, round22(amount * 0.02));
4628
+ 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);
4629
+ const candidates = (data ?? []).map((r) => {
4630
+ const amtDiff = Math.abs((Number(r.amount) || 0) - amount);
4631
+ const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
4632
+ const refDate = r.due_date ?? r.payment_date;
4633
+ const days = lineDate && refDate ? Math.abs((new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5) : 30;
4634
+ const dateScore = Math.max(0, 1 - days / 30);
4635
+ return {
4636
+ movement: snakeToCamel(r),
4637
+ score: round22(0.7 * amtScore + 0.3 * dateScore)
4638
+ };
4639
+ }).sort((a, b) => b.score - a.score);
4640
+ const invoiceIds = [...new Set(candidates.map((c) => c.movement.invoiceId).filter(Boolean))];
4641
+ if (invoiceIds.length) {
4642
+ const { data: orders } = await core.from("orders").select("id, reference_number, metadata").in("id", invoiceIds);
4643
+ const byId = new Map((orders ?? []).map((o) => [o.id, o]));
4644
+ for (const c of candidates) {
4645
+ const o = c.movement.invoiceId ? byId.get(c.movement.invoiceId) : void 0;
4646
+ if (o) c.invoice = { id: o.id, contactName: o.metadata?.contactName, fiscalNumber: o.reference_number };
4647
+ }
4648
+ }
4649
+ return candidates;
4650
+ },
4651
+ async reconcileMovement(input) {
4652
+ const { pub } = getClients();
4653
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
4654
+ await pub.from("financial_movements").update({
4655
+ reconciled_at: now2,
4656
+ matched_movement_id: input.matchedMovementId ?? null
4657
+ }).eq("id", input.bankMovementId);
4658
+ if (input.matchedMovementId) {
4659
+ await pub.from("financial_movements").update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
4660
+ }
4661
+ },
4662
+ async unreconcileMovement(bankMovementId) {
4663
+ const { pub } = getClients();
4664
+ const { data: line } = await pub.from("financial_movements").select("matched_movement_id").eq("id", bankMovementId).single();
4665
+ await pub.from("financial_movements").update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
4666
+ if (line?.matched_movement_id) {
4667
+ await pub.from("financial_movements").update({ reconciled_at: null }).eq("id", line.matched_movement_id);
4668
+ }
3689
4669
  }
3690
4670
  };
3691
4671
  return provider;
@@ -3707,8 +4687,9 @@ function createFinancialStore(provider) {
3707
4687
  paymentMethodTypes: [],
3708
4688
  summary: null,
3709
4689
  summaryLoading: false,
3710
- statementEntries: [],
4690
+ statement: null,
3711
4691
  statementLoading: false,
4692
+ statementQuery: null,
3712
4693
  cardTransactions: [],
3713
4694
  cardsLoading: false,
3714
4695
  async fetchSummary(dateRange) {
@@ -3750,9 +4731,9 @@ function createFinancialStore(provider) {
3750
4731
  },
3751
4732
  async fetchStatement(query) {
3752
4733
  return saas.dedup("fin:statement:" + JSON.stringify(query), async () => {
3753
- set({ statementLoading: true });
3754
- const statementEntries = await provider.getStatement(query);
3755
- set({ statementEntries, statementLoading: false });
4734
+ set({ statementLoading: true, statementQuery: query });
4735
+ const statement = await provider.getStatement(query);
4736
+ set({ statement, statementLoading: false });
3756
4737
  });
3757
4738
  },
3758
4739
  async fetchCardTransactions(dateRange) {
@@ -3788,6 +4769,22 @@ function createFinancialStore(provider) {
3788
4769
  throw err;
3789
4770
  }
3790
4771
  },
4772
+ async createTransfer(input) {
4773
+ try {
4774
+ await provider.createTransfer(input);
4775
+ const query = get().statementQuery;
4776
+ const [bankAccounts, summary, statement] = await Promise.all([
4777
+ provider.getBankAccounts(),
4778
+ provider.getSummary(),
4779
+ query ? provider.getStatement(query) : Promise.resolve(get().statement)
4780
+ ]);
4781
+ set({ bankAccounts, summary, statement });
4782
+ ui.toast.success("Transfer recorded");
4783
+ } catch (err) {
4784
+ ui.toast.error("Failed to record transfer", { description: err?.message });
4785
+ throw err;
4786
+ }
4787
+ },
3791
4788
  async cancelInvoice(invoiceId) {
3792
4789
  try {
3793
4790
  await provider.cancelInvoice(invoiceId);
@@ -3849,7 +4846,9 @@ var paymentMethodEntity = {
3849
4846
  defaultSort: "name",
3850
4847
  fields: [
3851
4848
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
3852
- { key: "paymentMethodTypeId", label: "Type", type: "select", options: ["cash", "credit_card", "debit_card", "pix", "bank_transfer", "check"], showInTable: true, required: true },
4849
+ // FK to the seeded payment_method_types catalog — load real ids at runtime
4850
+ // (a static enum select would write the string into the uuid column).
4851
+ { key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: "payment_method_types", labelField: "name" }, showInTable: true, required: true },
3853
4852
  { key: "discountValue", label: "Discount %", type: "number", showInTable: true },
3854
4853
  { key: "interestValue", label: "Interest %", type: "number", showInTable: true },
3855
4854
  { key: "minInstallments", label: "Min Installments", type: "number", defaultValue: 1 },
@@ -4212,6 +5211,21 @@ var en = {
4212
5211
  "financial.nav.rules": "Rules",
4213
5212
  "financial.nav.statements": "Statements",
4214
5213
  "financial.nav.summary": "Summary",
5214
+ "financial.reconciliation.title": "Reconciliation",
5215
+ "financial.reconciliation.subtitle": "Match imported bank lines to your movements",
5216
+ "financial.reconciliation.pending": "{{count}} to reconcile",
5217
+ "financial.reconciliation.allDone": "All caught up",
5218
+ "financial.reconciliation.empty": "Nothing to reconcile",
5219
+ "financial.reconciliation.emptyHint": "Imported bank transactions will appear here for matching.",
5220
+ "financial.reconciliation.bankLines": "Bank lines",
5221
+ "financial.reconciliation.candidates": "Suggested matches",
5222
+ "financial.reconciliation.selectLine": "Select a bank line to see suggested matches",
5223
+ "financial.reconciliation.noCandidates": "No suggested matches for this line",
5224
+ "financial.reconciliation.match": "Match",
5225
+ "financial.reconciliation.accept": "Accept as new",
5226
+ "financial.reconciliation.acceptHint": "No internal counterpart \u2014 keep as a standalone entry",
5227
+ "financial.reconciliation.matchScore": "{{score}}% match",
5228
+ "financial.reconciliation.refresh": "Refresh",
4215
5229
  "financial.onboarding.back": "Back",
4216
5230
  "financial.onboarding.configureHint": "You can configure this in detail from the Settings gear icon inside the Financial module.",
4217
5231
  "financial.onboarding.getStarted": "Get Started",
@@ -4276,6 +5290,8 @@ var en = {
4276
5290
  "financial.settingsPage.title": "Financial Settings",
4277
5291
  "financial.payment.noMethods": "No payment methods configured.",
4278
5292
  "financial.payment.configureMethods": "Set up payment methods",
5293
+ "financial.payment.noAccounts": "No compatible account. Create one in Settings.",
5294
+ "financial.payment.configureAccounts": "Set up accounts",
4279
5295
  "financial.statements.account": "Account",
4280
5296
  "financial.statements.from": "From",
4281
5297
  "financial.statements.noTransactions": "No transactions in this period",
@@ -4411,7 +5427,66 @@ var en = {
4411
5427
  "financial.cashRegister.close": "Close Register",
4412
5428
  "financial.movements.title": "Movements",
4413
5429
  "financial.bankAccounts.title": "Bank Accounts",
4414
- "financial.settings.title": "Financial Settings"
5430
+ "financial.settings.title": "Financial Settings",
5431
+ // Settings registry tabs
5432
+ "registry.payment-methods": "Payment Methods",
5433
+ "registry.payment-methods.description": "Configured payment methods with fees and installment rules",
5434
+ "registry.bank-accounts": "Accounts",
5435
+ "registry.bank-accounts.description": "Bank accounts, cash registers, credit cards, and digital wallets",
5436
+ "registry.chart-of-accounts": "Chart of Accounts",
5437
+ "registry.chart-of-accounts.description": "Hierarchical chart of accounts for categorizing transactions",
5438
+ "registry.cost-centers": "Cost Centers",
5439
+ "registry.cost-centers.description": "Cost centers for expense tracking and departmental budgets",
5440
+ "registry.commission-rules": "Commission Rules",
5441
+ "registry.commission-rules.description": "Commission rules for service and product revenue sharing",
5442
+ "registry.price-tables": "Price Tables",
5443
+ "registry.price-tables.description": "Price tables for sale and cost pricing",
5444
+ "registry.price-variations": "Price Variations",
5445
+ "registry.price-variations.description": "Discount and surcharge rules for pricing",
5446
+ "registry.payment-method-types": "Payment Types",
5447
+ "registry.payment-method-types.description": "System payment types (cash, card, PIX, etc.)",
5448
+ "registry.card-brands": "Card Brands",
5449
+ "registry.card-brands.description": "Credit and debit card brands",
5450
+ // Statements / extract
5451
+ "financial.statements.columnDate": "Date",
5452
+ "financial.statements.columnDescription": "Description",
5453
+ "financial.statements.columnDebit": "Debit",
5454
+ "financial.statements.columnCredit": "Credit",
5455
+ "financial.statements.columnFee": "Fee",
5456
+ "financial.statements.columnBalance": "Balance",
5457
+ "financial.statements.columnMethod": "Method",
5458
+ "financial.statements.columnDoc": "Document",
5459
+ "financial.statements.installmentShort": "Inst. {{n}}",
5460
+ "financial.statements.viewInvoice": "View invoice",
5461
+ "financial.person.empty": "No financial activity for this contact yet",
5462
+ "financial.statements.opening": "Opening balance",
5463
+ "financial.statements.closing": "Closing balance",
5464
+ "financial.statements.currentBalance": "Current balance",
5465
+ "financial.statements.totalCredits": "Credits",
5466
+ "financial.statements.totalDebits": "Debits",
5467
+ "financial.statements.totalFees": "Fees",
5468
+ "financial.statements.net": "Net",
5469
+ "financial.statements.transfer": "Transfer",
5470
+ "financial.statements.reconciled": "Reconciled with the bank statement",
5471
+ "financial.statements.imported": "Imported from the bank \xB7 not reconciled",
5472
+ "financial.statements.reconciledWith": "Reconciled with {{ref}}",
5473
+ "financial.statements.newTransfer": "New Transfer",
5474
+ // Transfer dialog
5475
+ "financial.transfer.title": "Transfer Between Accounts",
5476
+ "financial.transfer.from": "From account",
5477
+ "financial.transfer.to": "To account",
5478
+ "financial.transfer.amount": "Amount",
5479
+ "financial.transfer.date": "Date",
5480
+ "financial.transfer.notes": "Notes",
5481
+ "financial.transfer.optionalNotes": "Optional notes...",
5482
+ "financial.transfer.selectAccount": "Select account...",
5483
+ "financial.transfer.submit": "Transfer",
5484
+ "financial.transfer.processing": "Transferring...",
5485
+ "financial.transfer.cancel": "Cancel",
5486
+ "financial.transfer.created": "Transfer recorded",
5487
+ "financial.transfer.failed": "Failed to record transfer",
5488
+ "financial.transfer.sameAccount": "Source and destination must differ",
5489
+ "financial.transfer.invalidAmount": "Amount must be greater than zero"
4415
5490
  };
4416
5491
 
4417
5492
  // src/locales/pt-BR.ts
@@ -4544,6 +5619,21 @@ var ptBR = {
4544
5619
  "financial.nav.rules": "Regras",
4545
5620
  "financial.nav.statements": "Extratos",
4546
5621
  "financial.nav.summary": "Resumo",
5622
+ "financial.reconciliation.title": "Concilia\xE7\xE3o",
5623
+ "financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
5624
+ "financial.reconciliation.pending": "{{count}} para conciliar",
5625
+ "financial.reconciliation.allDone": "Tudo conciliado",
5626
+ "financial.reconciliation.empty": "Nada para conciliar",
5627
+ "financial.reconciliation.emptyHint": "As transa\xE7\xF5es banc\xE1rias importadas aparecem aqui para concilia\xE7\xE3o.",
5628
+ "financial.reconciliation.bankLines": "Linhas do banco",
5629
+ "financial.reconciliation.candidates": "Correspond\xEAncias sugeridas",
5630
+ "financial.reconciliation.selectLine": "Selecione uma linha do banco para ver as sugest\xF5es",
5631
+ "financial.reconciliation.noCandidates": "Nenhuma correspond\xEAncia sugerida para esta linha",
5632
+ "financial.reconciliation.match": "Conciliar",
5633
+ "financial.reconciliation.accept": "Aceitar como novo",
5634
+ "financial.reconciliation.acceptHint": "Sem contrapartida interna \u2014 manter como lan\xE7amento avulso",
5635
+ "financial.reconciliation.matchScore": "{{score}}% compat\xEDvel",
5636
+ "financial.reconciliation.refresh": "Atualizar",
4547
5637
  "financial.onboarding.back": "Voltar",
4548
5638
  "financial.onboarding.configureHint": "Voc\xEA pode configurar isso em detalhes pelo \xEDcone de Configura\xE7\xF5es dentro do m\xF3dulo Financeiro.",
4549
5639
  "financial.onboarding.getStarted": "Come\xE7ar",
@@ -4608,6 +5698,8 @@ var ptBR = {
4608
5698
  "financial.settingsPage.title": "Configura\xE7\xF5es Financeiras",
4609
5699
  "financial.payment.noMethods": "Nenhuma forma de pagamento configurada.",
4610
5700
  "financial.payment.configureMethods": "Configurar formas de pagamento",
5701
+ "financial.payment.noAccounts": "Nenhuma conta compat\xEDvel. Crie uma nas Configura\xE7\xF5es.",
5702
+ "financial.payment.configureAccounts": "Configurar contas",
4611
5703
  "financial.statements.account": "Conta",
4612
5704
  "financial.statements.from": "De",
4613
5705
  "financial.statements.noTransactions": "Sem transa\xE7\xF5es neste per\xEDodo",
@@ -4743,7 +5835,66 @@ var ptBR = {
4743
5835
  "financial.cashRegister.close": "Fechar Caixa",
4744
5836
  "financial.movements.title": "Movimenta\xE7\xF5es",
4745
5837
  "financial.bankAccounts.title": "Contas Banc\xE1rias",
4746
- "financial.settings.title": "Configura\xE7\xF5es Financeiras"
5838
+ "financial.settings.title": "Configura\xE7\xF5es Financeiras",
5839
+ // Abas de registros nas configurações
5840
+ "registry.payment-methods": "Formas de Pagamento",
5841
+ "registry.payment-methods.description": "Formas de pagamento configuradas com taxas e regras de parcelamento",
5842
+ "registry.bank-accounts": "Contas",
5843
+ "registry.bank-accounts.description": "Contas banc\xE1rias, caixas, cart\xF5es de cr\xE9dito e carteiras digitais",
5844
+ "registry.chart-of-accounts": "Plano de Contas",
5845
+ "registry.chart-of-accounts.description": "Plano de contas hier\xE1rquico para categorizar transa\xE7\xF5es",
5846
+ "registry.cost-centers": "Centros de Custo",
5847
+ "registry.cost-centers.description": "Centros de custo para controle de despesas e or\xE7amentos por departamento",
5848
+ "registry.commission-rules": "Regras de Comiss\xE3o",
5849
+ "registry.commission-rules.description": "Regras de comiss\xE3o para servi\xE7os e produtos",
5850
+ "registry.price-tables": "Tabelas de Pre\xE7o",
5851
+ "registry.price-tables.description": "Tabelas de pre\xE7o de venda e custo",
5852
+ "registry.price-variations": "Varia\xE7\xF5es de Pre\xE7o",
5853
+ "registry.price-variations.description": "Regras de desconto e acr\xE9scimo de pre\xE7os",
5854
+ "registry.payment-method-types": "Tipos de Pagamento",
5855
+ "registry.payment-method-types.description": "Tipos de pagamento do sistema (dinheiro, cart\xE3o, PIX, etc.)",
5856
+ "registry.card-brands": "Bandeiras de Cart\xE3o",
5857
+ "registry.card-brands.description": "Bandeiras de cart\xE3o de cr\xE9dito e d\xE9bito",
5858
+ // Extrato
5859
+ "financial.statements.columnDate": "Data",
5860
+ "financial.statements.columnDescription": "Descri\xE7\xE3o",
5861
+ "financial.statements.columnDebit": "D\xE9bito",
5862
+ "financial.statements.columnCredit": "Cr\xE9dito",
5863
+ "financial.statements.columnFee": "Taxa",
5864
+ "financial.statements.columnBalance": "Saldo",
5865
+ "financial.statements.columnMethod": "Forma",
5866
+ "financial.statements.columnDoc": "Documento",
5867
+ "financial.statements.installmentShort": "Parc. {{n}}",
5868
+ "financial.statements.viewInvoice": "Ver fatura",
5869
+ "financial.person.empty": "Nenhuma atividade financeira para este contato ainda",
5870
+ "financial.statements.opening": "Saldo inicial",
5871
+ "financial.statements.closing": "Saldo final",
5872
+ "financial.statements.currentBalance": "Saldo atual",
5873
+ "financial.statements.totalCredits": "Cr\xE9ditos",
5874
+ "financial.statements.totalDebits": "D\xE9bitos",
5875
+ "financial.statements.totalFees": "Taxas",
5876
+ "financial.statements.net": "L\xEDquido",
5877
+ "financial.statements.transfer": "Transfer\xEAncia",
5878
+ "financial.statements.reconciled": "Conciliado com o extrato banc\xE1rio",
5879
+ "financial.statements.imported": "Importado do banco \xB7 n\xE3o conciliado",
5880
+ "financial.statements.reconciledWith": "Conciliado com {{ref}}",
5881
+ "financial.statements.newTransfer": "Nova Transfer\xEAncia",
5882
+ // Diálogo de transferência
5883
+ "financial.transfer.title": "Transfer\xEAncia entre Contas",
5884
+ "financial.transfer.from": "Conta de origem",
5885
+ "financial.transfer.to": "Conta de destino",
5886
+ "financial.transfer.amount": "Valor",
5887
+ "financial.transfer.date": "Data",
5888
+ "financial.transfer.notes": "Observa\xE7\xF5es",
5889
+ "financial.transfer.optionalNotes": "Observa\xE7\xF5es opcionais...",
5890
+ "financial.transfer.selectAccount": "Selecione a conta...",
5891
+ "financial.transfer.submit": "Transferir",
5892
+ "financial.transfer.processing": "Transferindo...",
5893
+ "financial.transfer.cancel": "Cancelar",
5894
+ "financial.transfer.created": "Transfer\xEAncia registrada",
5895
+ "financial.transfer.failed": "Falha ao registrar transfer\xEAncia",
5896
+ "financial.transfer.sameAccount": "Origem e destino devem ser diferentes",
5897
+ "financial.transfer.invalidAmount": "O valor deve ser maior que zero"
4747
5898
  };
4748
5899
 
4749
5900
  // src/locales/index.ts
@@ -4751,8 +5902,104 @@ var financialLocales = {
4751
5902
  en,
4752
5903
  "pt-BR": ptBR
4753
5904
  };
4754
-
4755
- // src/index.ts
5905
+ var STATUS_STYLE = {
5906
+ paid: "bg-success/15 text-success",
5907
+ partial: "bg-warning/15 text-warning",
5908
+ overdue: "bg-destructive/15 text-destructive",
5909
+ open: "bg-muted text-muted-foreground",
5910
+ cancelled: "bg-muted text-muted-foreground line-through"
5911
+ };
5912
+ function PersonStatementWidget({ person, provider, config }) {
5913
+ const t2 = core.useTranslation();
5914
+ const currency = config.currency;
5915
+ const [invoices, setInvoices] = React14.useState([]);
5916
+ const [loading, setLoading] = React14.useState(true);
5917
+ React14.useEffect(() => {
5918
+ let alive = true;
5919
+ if (!person?.id) {
5920
+ setLoading(false);
5921
+ return;
5922
+ }
5923
+ setLoading(true);
5924
+ provider.getInvoices({ contactId: person.id, pageSize: 200 }).then((r) => {
5925
+ if (alive) {
5926
+ setInvoices(r.data);
5927
+ setLoading(false);
5928
+ }
5929
+ }).catch(() => {
5930
+ if (alive) setLoading(false);
5931
+ });
5932
+ return () => {
5933
+ alive = false;
5934
+ };
5935
+ }, [person?.id, provider]);
5936
+ const fmt = (n) => saas.formatCurrency(n, currency);
5937
+ const totals = React14.useMemo(() => {
5938
+ const active = invoices.filter((i) => i.status !== "cancelled");
5939
+ const billed = active.reduce((s, i) => s + (i.totalAmount ?? 0), 0);
5940
+ const paid = active.reduce((s, i) => s + (i.paidAmount ?? 0), 0);
5941
+ return { billed, paid, balance: billed - paid };
5942
+ }, [invoices]);
5943
+ const openInvoice = (inv) => {
5944
+ const base = inv.direction === "credit" ? "receivables" : "payables";
5945
+ window.location.hash = `/financial/${base}/detail/${inv.id}`;
5946
+ };
5947
+ const columns = React14.useMemo(() => [
5948
+ {
5949
+ id: "date",
5950
+ header: t2("financial.invoice.columnDate"),
5951
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: row.original.invoiceDate })
5952
+ },
5953
+ {
5954
+ id: "description",
5955
+ header: t2("financial.invoice.columnDescription"),
5956
+ cell: ({ row }) => {
5957
+ const inv = row.original;
5958
+ return /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => openInvoice(inv), className: "text-left", children: [
5959
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-primary hover:underline", children: inv.itemsSummary || inv.contactName || t2("financial.invoice.invoice") }),
5960
+ inv.fiscalNumber && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1.5 text-[10px] text-muted-foreground font-mono", children: inv.fiscalNumber })
5961
+ ] });
5962
+ }
5963
+ },
5964
+ {
5965
+ id: "amount",
5966
+ header: t2("financial.invoice.columnAmount"),
5967
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-right block text-xs ${row.original.direction === "credit" ? "text-success" : "text-destructive"}`, children: fmt(row.original.totalAmount) })
5968
+ },
5969
+ {
5970
+ id: "paid",
5971
+ header: t2("financial.invoice.columnPaid"),
5972
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block text-xs text-muted-foreground", children: fmt(row.original.paidAmount) })
5973
+ },
5974
+ {
5975
+ id: "balance",
5976
+ header: t2("financial.statements.columnBalance"),
5977
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block text-xs font-medium", children: fmt((row.original.totalAmount ?? 0) - (row.original.paidAmount ?? 0)) })
5978
+ },
5979
+ {
5980
+ id: "status",
5981
+ header: t2("financial.invoice.columnStatus"),
5982
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.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 })
5983
+ }
5984
+ ], [currency, t2]);
5985
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
5986
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
5987
+ /* @__PURE__ */ jsxRuntime.jsx(Stat, { label: t2("financial.invoice.summaryTotal"), value: fmt(totals.billed) }),
5988
+ /* @__PURE__ */ jsxRuntime.jsx(Stat, { label: t2("financial.invoice.summaryPaid"), value: fmt(totals.paid), positive: true }),
5989
+ /* @__PURE__ */ jsxRuntime.jsx(Stat, { label: t2("financial.statements.columnBalance"), value: fmt(totals.balance), accent: true })
5990
+ ] }),
5991
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns, data: [], loading: true, skeletonRows: 5, compact: true }) : invoices.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-10 text-center rounded-lg border-2 border-dashed border-muted", children: [
5992
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Wallet, { className: "h-7 w-7 text-muted-foreground mb-2" }),
5993
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.person.empty") })
5994
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns, data: invoices, compact: true })
5995
+ ] });
5996
+ }
5997
+ function Stat({ label, value, accent, positive }) {
5998
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `rounded-lg border px-3 py-2 ${accent ? "bg-muted/40" : "bg-card"}`, children: [
5999
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-medium text-muted-foreground uppercase truncate", children: label }),
6000
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm font-semibold mt-0.5 ${positive ? "text-success" : "text-foreground"}`, children: value })
6001
+ ] });
6002
+ }
4756
6003
  function createSafeFinancialProvider() {
4757
6004
  return core.createSafeDataProvider(
4758
6005
  () => createSupabaseFinancialProvider(),
@@ -4793,7 +6040,9 @@ function resolveConfig(options) {
4793
6040
  cashRegisters: options?.modules?.cashRegisters !== false,
4794
6041
  statements: options?.modules?.statements !== false,
4795
6042
  commissions: options?.modules?.commissions !== false,
4796
- cards: options?.modules?.cards !== false
6043
+ cards: options?.modules?.cards !== false,
6044
+ // Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
6045
+ reconciliation: options?.modules?.reconciliation === true
4797
6046
  },
4798
6047
  labels: { ...DEFAULT_LABELS, ...options?.labels },
4799
6048
  currency: { ...DEFAULT_CURRENCY, ...options?.currency },
@@ -4815,7 +6064,9 @@ function createFinancialPlugin(options) {
4815
6064
  const provider = options?.dataProvider ?? createSafeFinancialProvider();
4816
6065
  const store = createFinancialStore(provider);
4817
6066
  const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
4818
- const PageComponent = () => React11__default.default.createElement(FinancialPage, { config, provider, store, registries: financialRegistries });
6067
+ const PersonStatementBound = (props) => React14__default.default.createElement(PersonStatementWidget, { person: props.item, provider, config });
6068
+ PersonStatementBound.displayName = "FinancialPersonStatement";
6069
+ const PageComponent = () => React14__default.default.createElement(FinancialPage, { config, provider, store, registries: financialRegistries });
4819
6070
  return {
4820
6071
  id: "financial",
4821
6072
  name: config.labels.pageTitle,
@@ -4848,7 +6099,15 @@ function createFinancialPlugin(options) {
4848
6099
  permission: { feature: "financial", action: "read" }
4849
6100
  }
4850
6101
  ],
4851
- widgets: [],
6102
+ widgets: [
6103
+ {
6104
+ id: "financial-person-statement",
6105
+ zone: "person.detail.financial",
6106
+ component: PersonStatementBound,
6107
+ order: 0,
6108
+ permission: { feature: "financial", action: "read" }
6109
+ }
6110
+ ],
4852
6111
  dashboardWidgets,
4853
6112
  aiTools: [
4854
6113
  {
@@ -4919,7 +6178,14 @@ function createFinancialPlugin(options) {
4919
6178
  label: "Financial",
4920
6179
  icon: "DollarSign",
4921
6180
  component: (() => {
4922
- const FinancialSettingsTab = () => React11__default.default.createElement(FinancialGeneralSettings);
6181
+ const FinancialSettingsTab = () => React14__default.default.createElement(saas.PluginSettingsPanel, {
6182
+ title: "Financial Settings",
6183
+ subtitle: "Payment methods, accounts, and configurations",
6184
+ generalSettings: React14__default.default.createElement(FinancialGeneralSettings),
6185
+ registries: financialRegistries,
6186
+ hostPluginId: "financial",
6187
+ routeBase: "/settings/financial"
6188
+ });
4923
6189
  FinancialSettingsTab.displayName = "FinancialSettingsTab";
4924
6190
  return FinancialSettingsTab;
4925
6191
  })(),