@fayz-ai/plugin-financial 0.2.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +2 -0
  2. package/dist/FinancialContext.d.ts +6 -0
  3. package/dist/FinancialContext.d.ts.map +1 -1
  4. package/dist/FinancialPage.d.ts.map +1 -1
  5. package/dist/components/FinancialGeneralSettings.d.ts.map +1 -1
  6. package/dist/data/supabase.d.ts.map +1 -1
  7. package/dist/data/tables.d.ts +11 -0
  8. package/dist/data/tables.d.ts.map +1 -0
  9. package/dist/index.cjs +345 -81
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +31 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +347 -84
  14. package/dist/index.js.map +1 -1
  15. package/dist/locales/en.d.ts.map +1 -1
  16. package/dist/locales/pt-BR.d.ts.map +1 -1
  17. package/dist/migrations/index.d.ts +18 -0
  18. package/dist/migrations/index.d.ts.map +1 -0
  19. package/dist/registries.d.ts.map +1 -1
  20. package/dist/views/CardsView.d.ts.map +1 -1
  21. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  22. package/dist/views/InvoiceListView.d.ts.map +1 -1
  23. package/dist/views/QuickTransactionForm.d.ts.map +1 -1
  24. package/package.json +10 -4
  25. package/src/FinancialContext.tsx +6 -0
  26. package/src/FinancialPage.tsx +56 -12
  27. package/src/README.md +5 -5
  28. package/src/components/FinancialGeneralSettings.tsx +21 -8
  29. package/src/contract.test.ts +1 -1
  30. package/src/data/supabase.ts +56 -48
  31. package/src/data/tables.ts +12 -0
  32. package/src/index.ts +128 -0
  33. package/src/locales/en.ts +10 -0
  34. package/src/locales/pt-BR.ts +10 -0
  35. package/src/migrations/000_plg_rename.sql +30 -0
  36. package/src/migrations/001_financial_base.sql +30 -30
  37. package/src/migrations/002_chart_of_accounts.sql +10 -10
  38. package/src/migrations/003_card_brands.sql +4 -4
  39. package/src/migrations/004_order_to_cash.sql +43 -29
  40. package/src/migrations/005_seed_defaults.sql +6 -6
  41. package/src/migrations/006_rls_policies.sql +5 -5
  42. package/src/migrations/{006_extract_fee_amount.sql → 006b_extract_fee_amount.sql} +4 -4
  43. package/src/migrations/007_reconciliation.sql +9 -9
  44. package/src/migrations/007b_movement_payment_method_type.sql +6 -0
  45. package/src/migrations/008_split_payment_movements.sql +5 -5
  46. package/src/migrations/009_agent_rpcs.sql +165 -0
  47. package/src/migrations/010_invoice_internal.sql +133 -0
  48. package/src/migrations/index.ts +862 -0
  49. package/src/registries.ts +8 -7
  50. package/src/views/CardsView.tsx +12 -9
  51. package/src/views/InvoiceFormView.tsx +10 -0
  52. package/src/views/InvoiceListView.tsx +98 -9
  53. package/src/views/QuickTransactionForm.tsx +11 -0
package/dist/index.cjs CHANGED
@@ -273,6 +273,8 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
273
273
  const { currency } = useFinancialConfig();
274
274
  const provider = useFinancialProvider();
275
275
  const createQuickTransaction = useFinancialStore((s) => s.createQuickTransaction);
276
+ const guardMovements = saas.useLimitGuard("movements_month");
277
+ const can = saas.usePermissionOptional();
276
278
  const [type, setType] = React17.useState(defaultType);
277
279
  const [amount, setAmount] = React17.useState(0);
278
280
  const [amountStr, setAmountStr] = React17.useState("");
@@ -358,12 +360,14 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
358
360
  }
359
361
  const canSave = amount > 0 && !saving && (type === "transfer" ? !!accountId && !!toAccountId && accountId !== toAccountId : true);
360
362
  async function handleSave() {
363
+ if (!can("financial", "create")) return;
361
364
  if (!canSave) {
362
365
  if (type === "transfer" && accountId && accountId === toAccountId) {
363
366
  ui.toast.error(t2("financial.transfer.sameAccount"));
364
367
  }
365
368
  return;
366
369
  }
370
+ if (await guardMovements(type === "transfer" ? 2 : 1) === "blocked") return;
367
371
  setSaving(true);
368
372
  try {
369
373
  await createQuickTransaction({
@@ -378,6 +382,7 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
378
382
  recurring: type === "transfer" ? false : recurring,
379
383
  receiptUrl: type === "transfer" ? void 0 : receiptUrl
380
384
  });
385
+ saas.invalidateLimit("movements_month");
381
386
  onOpenChange(false);
382
387
  } catch {
383
388
  } finally {
@@ -666,6 +671,23 @@ var STATUS_OPTIONS = [
666
671
  { value: "overdue", labelKey: "financial.invoice.statusOverdue", color: "bg-destructive-soft text-destructive-soft-foreground", icon: lucideReact.CircleAlert },
667
672
  { value: "cancelled", labelKey: "financial.invoice.statusCancelled", color: "bg-muted text-muted-foreground", icon: lucideReact.Ban }
668
673
  ];
674
+ var isoDate = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
675
+ function presetRange(preset) {
676
+ const now2 = /* @__PURE__ */ new Date();
677
+ switch (preset) {
678
+ case "thisMonth":
679
+ return { from: isoDate(new Date(now2.getFullYear(), now2.getMonth(), 1)), to: isoDate(new Date(now2.getFullYear(), now2.getMonth() + 1, 0)) };
680
+ case "lastMonth":
681
+ return { from: isoDate(new Date(now2.getFullYear(), now2.getMonth() - 1, 1)), to: isoDate(new Date(now2.getFullYear(), now2.getMonth(), 0)) };
682
+ case "last30": {
683
+ const from = new Date(now2);
684
+ from.setDate(from.getDate() - 29);
685
+ return { from: isoDate(from), to: isoDate(now2) };
686
+ }
687
+ case "thisYear":
688
+ return { from: isoDate(new Date(now2.getFullYear(), 0, 1)), to: isoDate(new Date(now2.getFullYear(), 11, 31)) };
689
+ }
690
+ }
669
691
  function StatusBadge({ status }) {
670
692
  const t2 = core.useTranslation();
671
693
  const opt = STATUS_OPTIONS.find((o) => o.value === status);
@@ -729,6 +751,8 @@ function useInvoiceColumns(currency) {
729
751
  }
730
752
  function InvoiceListView({ direction, onNew, onEdit }) {
731
753
  const t2 = core.useTranslation();
754
+ const can = saas.usePermissionOptional();
755
+ const onNewGated = can("financial", "create") ? onNew : void 0;
732
756
  const { currency } = useFinancialConfig();
733
757
  const provider = useFinancialProvider();
734
758
  const invoices = useFinancialStore((s) => s.invoices);
@@ -741,6 +765,25 @@ function InvoiceListView({ direction, onNew, onEdit }) {
741
765
  const [costCenterId, setCostCenterId] = React17.useState("");
742
766
  const [accounts, setAccounts] = React17.useState([]);
743
767
  const [costCenters, setCostCenters] = React17.useState([]);
768
+ const [datePreset, setDatePreset] = React17.useState("all");
769
+ const [dateFrom, setDateFrom] = React17.useState("");
770
+ const [dateTo, setDateTo] = React17.useState("");
771
+ const dateRange = React17.useMemo(() => {
772
+ if (!dateFrom && !dateTo) return void 0;
773
+ return { from: dateFrom || "0001-01-01", to: dateTo || "9999-12-31" };
774
+ }, [dateFrom, dateTo]);
775
+ const applyPreset = (value) => {
776
+ setDatePreset(value);
777
+ if (value === "all") {
778
+ setDateFrom("");
779
+ setDateTo("");
780
+ return;
781
+ }
782
+ if (value === "custom") return;
783
+ const range = presetRange(value);
784
+ setDateFrom(range.from);
785
+ setDateTo(range.to);
786
+ };
744
787
  React17.useEffect(() => {
745
788
  let active = true;
746
789
  Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
@@ -762,9 +805,10 @@ function InvoiceListView({ direction, onNew, onEdit }) {
762
805
  status: statusFilter.length > 0 ? statusFilter : void 0,
763
806
  search: search || void 0,
764
807
  accountId: accountId || void 0,
765
- costCenterId: costCenterId || void 0
808
+ costCenterId: costCenterId || void 0,
809
+ dateRange
766
810
  });
767
- }, [direction, statusFilter, search, accountId, costCenterId]);
811
+ }, [direction, statusFilter, search, accountId, costCenterId, dateRange]);
768
812
  const filtered = invoices.filter((inv) => inv.direction === direction);
769
813
  const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
770
814
  const columns = useInvoiceColumns(currency);
@@ -794,7 +838,67 @@ function InvoiceListView({ direction, onNew, onEdit }) {
794
838
  subtitle: t2("financial.invoice.invoices", { count: filtered.length })
795
839
  }
796
840
  ),
797
- (accounts.length > 0 || costCenters.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
841
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
842
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex min-w-[180px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
843
+ t2("financial.invoice.filterPeriod"),
844
+ /* @__PURE__ */ jsxRuntime.jsxs(
845
+ "select",
846
+ {
847
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
848
+ value: datePreset,
849
+ onChange: (event) => applyPreset(event.target.value),
850
+ children: [
851
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: t2("financial.invoice.filterAllPeriods") }),
852
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "thisMonth", children: t2("financial.invoice.filterThisMonth") }),
853
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "lastMonth", children: t2("financial.invoice.filterLastMonth") }),
854
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "last30", children: t2("financial.invoice.filterLast30") }),
855
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "thisYear", children: t2("financial.invoice.filterThisYear") }),
856
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "custom", children: t2("financial.invoice.filterCustom") })
857
+ ]
858
+ }
859
+ )
860
+ ] }),
861
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
862
+ t2("financial.invoice.filterFrom"),
863
+ /* @__PURE__ */ jsxRuntime.jsx(
864
+ "input",
865
+ {
866
+ type: "date",
867
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
868
+ value: dateFrom,
869
+ max: dateTo || void 0,
870
+ onChange: (event) => {
871
+ setDateFrom(event.target.value);
872
+ setDatePreset("custom");
873
+ }
874
+ }
875
+ )
876
+ ] }),
877
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
878
+ t2("financial.invoice.filterTo"),
879
+ /* @__PURE__ */ jsxRuntime.jsx(
880
+ "input",
881
+ {
882
+ type: "date",
883
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
884
+ value: dateTo,
885
+ min: dateFrom || void 0,
886
+ onChange: (event) => {
887
+ setDateTo(event.target.value);
888
+ setDatePreset("custom");
889
+ }
890
+ }
891
+ )
892
+ ] }),
893
+ (dateFrom || dateTo) && /* @__PURE__ */ jsxRuntime.jsx(
894
+ "button",
895
+ {
896
+ type: "button",
897
+ className: "h-9 rounded-md px-2 text-xs font-medium text-muted-foreground hover:text-foreground",
898
+ onClick: () => applyPreset("all"),
899
+ children: t2("financial.invoice.filterClear")
900
+ }
901
+ ),
798
902
  accounts.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
799
903
  t2("financial.invoice.filterAccount"),
800
904
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -848,12 +952,12 @@ function InvoiceListView({ direction, onNew, onEdit }) {
848
952
  activeTag: statusFilter.length === 1 ? statusFilter[0] : void 0,
849
953
  onTagChange: (v) => setStatusFilter(v ? [v] : []),
850
954
  newLabel: t2("financial.invoice.new"),
851
- onNew,
955
+ onNew: onNewGated,
852
956
  onRowClick: (row) => onEdit?.(row.id),
853
957
  emptyIcon: lucideReact.FileText,
854
958
  emptyMessage: t2("financial.invoice.noInvoices"),
855
- emptyActionLabel: onNew ? t2("financial.invoice.createFirst") : void 0,
856
- onEmptyAction: onNew
959
+ emptyActionLabel: onNewGated ? t2("financial.invoice.createFirst") : void 0,
960
+ onEmptyAction: onNewGated
857
961
  }
858
962
  ),
859
963
  filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
@@ -1193,6 +1297,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
1193
1297
  const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
1194
1298
  const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
1195
1299
  const createInvoice = useFinancialStore((s) => s.createInvoice);
1300
+ const guardMovements = saas.useLimitGuard("movements_month");
1301
+ const can = saas.usePermissionOptional();
1196
1302
  const hasLocations = locations.length > 0;
1197
1303
  const isEdit = !!editId;
1198
1304
  const [loaded, setLoaded] = React17.useState(!editId);
@@ -1330,10 +1436,12 @@ function InvoiceFormView({ direction, editId, onSaved }) {
1330
1436
  setInstallments(inst);
1331
1437
  }
1332
1438
  async function handleSave() {
1439
+ if (!isEdit && !can("financial", "create")) return;
1333
1440
  if (items.length === 0) {
1334
1441
  ui.toast.error(t2("common.formIncomplete"));
1335
1442
  return;
1336
1443
  }
1444
+ if (!isEdit && await guardMovements(Math.max(1, installments.length)) === "blocked") return;
1337
1445
  setSaving(true);
1338
1446
  try {
1339
1447
  const movInputs = installments.map((inst, i) => ({
@@ -1375,6 +1483,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
1375
1483
  items: items.map(({ _id, ...rest }) => rest),
1376
1484
  installments: movInputs
1377
1485
  });
1486
+ saas.invalidateLimit("movements_month");
1378
1487
  onSaved?.(invoice.id);
1379
1488
  }
1380
1489
  } finally {
@@ -3516,7 +3625,7 @@ function CardsView() {
3516
3625
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("financial.cards.dueOn", { day: String(card.dueDay) }) })
3517
3626
  ] })
3518
3627
  ] }),
3519
- /* @__PURE__ */ jsxRuntime.jsxs(
3628
+ /* @__PURE__ */ jsxRuntime.jsx(saas.PermissionGate, { feature: "financial", action: "create", children: /* @__PURE__ */ jsxRuntime.jsxs(
3520
3629
  "button",
3521
3630
  {
3522
3631
  type: "button",
@@ -3527,7 +3636,7 @@ function CardsView() {
3527
3636
  t2("financial.cards.addExpense")
3528
3637
  ]
3529
3638
  }
3530
- )
3639
+ ) })
3531
3640
  ] })
3532
3641
  ] }, card.id);
3533
3642
  }) })
@@ -3688,28 +3797,31 @@ function ReconciliationView() {
3688
3797
  ] })
3689
3798
  ] });
3690
3799
  }
3800
+ var FINANCIAL_DEFAULTS = {
3801
+ autoInstallments: true,
3802
+ requireDoc: false,
3803
+ allowPartial: true,
3804
+ requireOpening: true,
3805
+ autoClose: false,
3806
+ overdueAlerts: true,
3807
+ dailySummary: false
3808
+ };
3691
3809
  function FinancialGeneralSettings() {
3692
3810
  const t2 = core.useTranslation();
3811
+ const s = saas.useTenantPluginSettings("financial", FINANCIAL_DEFAULTS);
3693
3812
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
3694
3813
  /* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: t2("financial.settings.invoices"), description: t2("financial.settings.invoicesDesc"), children: [
3695
- /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.autoInstallments"), description: t2("financial.settings.autoInstallmentsDesc"), checked: true, onChange: () => {
3696
- } }),
3697
- /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.requireDoc"), description: t2("financial.settings.requireDocDesc"), checked: false, onChange: () => {
3698
- } }),
3699
- /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.allowPartial"), description: t2("financial.settings.allowPartialDesc"), checked: true, onChange: () => {
3700
- } })
3814
+ /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.autoInstallments"), description: t2("financial.settings.autoInstallmentsDesc"), checked: s.get("autoInstallments"), onChange: (v) => s.set("autoInstallments", v) }),
3815
+ /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.requireDoc"), description: t2("financial.settings.requireDocDesc"), checked: s.get("requireDoc"), onChange: (v) => s.set("requireDoc", v) }),
3816
+ /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.allowPartial"), description: t2("financial.settings.allowPartialDesc"), checked: s.get("allowPartial"), onChange: (v) => s.set("allowPartial", v) })
3701
3817
  ] }),
3702
3818
  /* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: t2("financial.settings.cashRegister"), description: t2("financial.settings.cashRegisterDesc"), children: [
3703
- /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.requireOpening"), description: t2("financial.settings.requireOpeningDesc"), checked: true, onChange: () => {
3704
- } }),
3705
- /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.autoClose"), description: t2("financial.settings.autoCloseDesc"), checked: false, onChange: () => {
3706
- } })
3819
+ /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.requireOpening"), description: t2("financial.settings.requireOpeningDesc"), checked: s.get("requireOpening"), onChange: (v) => s.set("requireOpening", v) }),
3820
+ /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.autoClose"), description: t2("financial.settings.autoCloseDesc"), checked: s.get("autoClose"), onChange: (v) => s.set("autoClose", v) })
3707
3821
  ] }),
3708
3822
  /* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: t2("financial.settings.notifications"), description: t2("financial.settings.notificationsDesc"), children: [
3709
- /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.overdueAlerts"), description: t2("financial.settings.overdueAlertsDesc"), checked: true, onChange: () => {
3710
- } }),
3711
- /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.dailySummary"), description: t2("financial.settings.dailySummaryDesc"), checked: false, onChange: () => {
3712
- } })
3823
+ /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.overdueAlerts"), description: t2("financial.settings.overdueAlertsDesc"), checked: s.get("overdueAlerts"), onChange: (v) => s.set("overdueAlerts", v) }),
3824
+ /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("financial.settings.dailySummary"), description: t2("financial.settings.dailySummaryDesc"), checked: s.get("dailySummary"), onChange: (v) => s.set("dailySummary", v) })
3713
3825
  ] })
3714
3826
  ] });
3715
3827
  }
@@ -3850,6 +3962,10 @@ function buildNav(config, activeView, navigate, t2) {
3850
3962
  ]
3851
3963
  });
3852
3964
  }
3965
+ for (const item of items) {
3966
+ const feature = config.navFeatureMap[item.id];
3967
+ if (feature) item.feature = feature;
3968
+ }
3853
3969
  return items;
3854
3970
  }
3855
3971
  function FinancialPage({ config, provider, store, registries }) {
@@ -3885,9 +4001,11 @@ function FinancialPage({ config, provider, store, registries }) {
3885
4001
  };
3886
4002
  }, [onboardingComplete, provider]);
3887
4003
  const t2 = core.useTranslation();
4004
+ const can = saas.usePermissionOptional();
4005
+ const canCreate = can("financial", "create");
3888
4006
  const intent = parseIntent(view);
3889
4007
  const isSettings = view === "settings";
3890
- const nav = buildNav(config, view, navigate, t2);
4008
+ const nav = saas.useModuleNavAccess(buildNav(config, view, navigate, t2));
3891
4009
  const [quickAddOpen, setQuickAddOpen] = React17.useState(false);
3892
4010
  const [quickAddType, setQuickAddType] = React17.useState("expense");
3893
4011
  const [quickAddReceipt, setQuickAddReceipt] = React17.useState(false);
@@ -3901,12 +4019,14 @@ function FinancialPage({ config, provider, store, registries }) {
3901
4019
  if (!pendingQuickAdd) return;
3902
4020
  const req = consumeQuickAdd();
3903
4021
  if (!req) return;
4022
+ if (!canCreate) return;
3904
4023
  setQuickAddType(req.type);
3905
4024
  setQuickAddReceipt(req.receipt);
3906
4025
  setQuickAddOpen(true);
3907
4026
  }, [pendingQuickAdd]);
3908
4027
  const quickActions = React17.useMemo(() => {
3909
4028
  if (config.quickAdd) return [];
4029
+ if (!canCreate) return [];
3910
4030
  const actions = [];
3911
4031
  if (config.modules.payables) {
3912
4032
  actions.push({
@@ -3936,7 +4056,7 @@ function FinancialPage({ config, provider, store, registries }) {
3936
4056
  });
3937
4057
  }
3938
4058
  return actions;
3939
- }, [config.modules, config.quickAdd]);
4059
+ }, [config.modules, config.quickAdd, canCreate, navigate, t2]);
3940
4060
  function handleOnboardingComplete() {
3941
4061
  setOnboardingComplete(true);
3942
4062
  try {
@@ -3980,6 +4100,11 @@ function FinancialPage({ config, provider, store, registries }) {
3980
4100
  return /* @__PURE__ */ jsxRuntime.jsx(SummaryView, {});
3981
4101
  }
3982
4102
  }
4103
+ function renderGatedView() {
4104
+ const feature = config.navFeatureMap[intent.view];
4105
+ if (!feature) return renderView();
4106
+ return /* @__PURE__ */ jsxRuntime.jsx(saas.EntitlementGate, { feature, fallback: /* @__PURE__ */ jsxRuntime.jsx(saas.UpgradePrompt, { feature }), children: renderView() });
4107
+ }
3983
4108
  return /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsxs(
3984
4109
  ui.ModulePage,
3985
4110
  {
@@ -3994,7 +4119,8 @@ function FinancialPage({ config, provider, store, registries }) {
3994
4119
  // add affordance in B2C mode. The receipt entry point lives inside the
3995
4120
  // sheet's "Fotografar ou anexar recibo" attachment step, so no separate
3996
4121
  // "Enviar recibo" button is needed here.
3997
- /* @__PURE__ */ jsxRuntime.jsxs(
4122
+ // Gated on the role's `create` action so read-only members don't see it.
4123
+ /* @__PURE__ */ jsxRuntime.jsx(saas.PermissionGate, { feature: "financial", action: "create", children: /* @__PURE__ */ jsxRuntime.jsxs(
3998
4124
  ui.Button,
3999
4125
  {
4000
4126
  size: "sm",
@@ -4005,7 +4131,7 @@ function FinancialPage({ config, provider, store, registries }) {
4005
4131
  t2("financial.quickTx.newTransaction")
4006
4132
  ]
4007
4133
  }
4008
- ),
4134
+ ) }),
4009
4135
  /* @__PURE__ */ jsxRuntime.jsx(
4010
4136
  saas.ModuleActionBar,
4011
4137
  {
@@ -4016,7 +4142,7 @@ function FinancialPage({ config, provider, store, registries }) {
4016
4142
  )
4017
4143
  ] }),
4018
4144
  children: [
4019
- renderView(),
4145
+ renderGatedView(),
4020
4146
  /* @__PURE__ */ jsxRuntime.jsx(
4021
4147
  QuickTransactionForm,
4022
4148
  {
@@ -5037,6 +5163,18 @@ function createMockFinancialProvider(options) {
5037
5163
  return provider;
5038
5164
  }
5039
5165
 
5166
+ // src/data/tables.ts
5167
+ var T = {
5168
+ movements: "plg_financial_movements",
5169
+ chartOfAccounts: "plg_financial_chart_of_accounts",
5170
+ bankAccounts: "plg_financial_bank_accounts",
5171
+ cardBrands: "plg_financial_card_brands",
5172
+ costCenters: "plg_financial_cost_centers",
5173
+ cashRegisterSessions: "plg_financial_cash_register_sessions",
5174
+ paymentMethods: "plg_financial_payment_methods",
5175
+ paymentMethodTypes: "plg_financial_payment_method_types"
5176
+ };
5177
+
5040
5178
  // src/data/supabase.ts
5041
5179
  function getTenantId() {
5042
5180
  return core.getActiveTenantId();
@@ -5059,7 +5197,7 @@ function camelToSnake(obj) {
5059
5197
  function getClients() {
5060
5198
  const supabase = core.getSupabaseClientOptional();
5061
5199
  if (!supabase) throw new Error("Supabase not initialized");
5062
- return { core: supabase.schema("saas_core"), pub: supabase };
5200
+ return { core: supabase, pub: supabase };
5063
5201
  }
5064
5202
  function mapStageToInvoiceStatus(stage) {
5065
5203
  switch (stage) {
@@ -5086,11 +5224,11 @@ async function sweepOverdue() {
5086
5224
  try {
5087
5225
  const { core, pub } = getClients();
5088
5226
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5089
- const { data: overdueMovs } = await pub.from("financial_movements").update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today2).select("invoice_id");
5227
+ const { data: overdueMovs } = await pub.from(T.movements).update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today2).select("invoice_id");
5090
5228
  if (overdueMovs && overdueMovs.length > 0) {
5091
5229
  const invoiceIds = [...new Set(overdueMovs.map((m) => m.invoice_id).filter(Boolean))];
5092
5230
  for (const invId of invoiceIds) {
5093
- const { data: allMovs } = await pub.from("financial_movements").select("status").eq("invoice_id", invId).eq("movement_kind", "bill");
5231
+ const { data: allMovs } = await pub.from(T.movements).select("status").eq("invoice_id", invId).eq("movement_kind", "bill");
5094
5232
  const movs = allMovs ?? [];
5095
5233
  const hasOverdue = movs.some((m) => m.status === "overdue");
5096
5234
  const allPaid = movs.every((m) => m.status === "paid");
@@ -5128,7 +5266,7 @@ async function findOrderIdsByItemMetadata(core, filters) {
5128
5266
  }
5129
5267
  async function getMethodFeePct(pub, paymentMethodId) {
5130
5268
  if (!paymentMethodId) return 0;
5131
- const { data } = await pub.from("payment_methods").select("discount_value").eq("id", paymentMethodId).single();
5269
+ const { data } = await pub.from(T.paymentMethods).select("discount_value").eq("id", paymentMethodId).single();
5132
5270
  return Number(data?.discount_value) || 0;
5133
5271
  }
5134
5272
  function genId() {
@@ -5163,7 +5301,10 @@ function createSupabaseFinancialProvider() {
5163
5301
  }
5164
5302
  if (query.contactId) qb = qb.eq("party_id", query.contactId);
5165
5303
  if (query.search) qb = qb.ilike("notes", `%${query.search}%`);
5166
- if (query.dateRange) qb = qb.gte("created_at", query.dateRange.from).lte("created_at", query.dateRange.to);
5304
+ if (query.dateRange) {
5305
+ const to = query.dateRange.to.length === 10 ? `${query.dateRange.to}T23:59:59.999` : query.dateRange.to;
5306
+ qb = qb.gte("created_at", query.dateRange.from).lte("created_at", to);
5307
+ }
5167
5308
  const page = query.page ?? 1;
5168
5309
  const pageSize = query.pageSize ?? 50;
5169
5310
  qb = qb.range((page - 1) * pageSize, page * pageSize - 1).order("created_at", { ascending: false });
@@ -5200,7 +5341,7 @@ function createSupabaseFinancialProvider() {
5200
5341
  const o = snakeToCamel(data);
5201
5342
  const meta = data.metadata ?? {};
5202
5343
  const balance = (await fetchInvoiceBalances(pub, [id])).get(id);
5203
- const { data: movs } = await pub.from("financial_movements").select("paid_amount").eq("invoice_id", id).eq("movement_kind", "bill");
5344
+ const { data: movs } = await pub.from(T.movements).select("paid_amount").eq("invoice_id", id).eq("movement_kind", "bill");
5204
5345
  return {
5205
5346
  id: o.id,
5206
5347
  direction: o.direction ?? (o.kind === "invoice_payable" ? "debit" : "credit"),
@@ -5287,7 +5428,7 @@ function createSupabaseFinancialProvider() {
5287
5428
  );
5288
5429
  }
5289
5430
  if (order && input.installments.length > 0) {
5290
- await pub.from("financial_movements").insert(
5431
+ await pub.from(T.movements).insert(
5291
5432
  input.installments.map((inst, i) => ({
5292
5433
  tenant_id: tenantId,
5293
5434
  invoice_id: order.id,
@@ -5329,12 +5470,12 @@ function createSupabaseFinancialProvider() {
5329
5470
  async cancelInvoice(id) {
5330
5471
  const { core, pub } = getClients();
5331
5472
  await core.from("orders").update({ status: "cancelled" }).eq("id", id);
5332
- await pub.from("financial_movements").update({ status: "cancelled" }).eq("invoice_id", id).eq("status", "pending");
5473
+ await pub.from(T.movements).update({ status: "cancelled" }).eq("invoice_id", id).eq("status", "pending");
5333
5474
  },
5334
5475
  // --- Movements (public.financial_movements) ---
5335
5476
  async getMovements(query) {
5336
5477
  const { pub } = getClients();
5337
- let qb = pub.from("financial_movements").select("*", { count: "exact" });
5478
+ let qb = pub.from(T.movements).select("*", { count: "exact" });
5338
5479
  if (query.direction) qb = qb.eq("direction", query.direction);
5339
5480
  if (query.status) {
5340
5481
  const statuses = Array.isArray(query.status) ? query.status : [query.status];
@@ -5351,7 +5492,7 @@ function createSupabaseFinancialProvider() {
5351
5492
  },
5352
5493
  async payMovement(input) {
5353
5494
  const { core, pub } = getClients();
5354
- const { data: mov } = await pub.from("financial_movements").select("*").eq("id", input.movementId).single();
5495
+ const { data: mov } = await pub.from(T.movements).select("*").eq("id", input.movementId).single();
5355
5496
  if (!mov) throw new Error("Movement not found");
5356
5497
  const tenantId = mov.tenant_id ?? getTenantId();
5357
5498
  const feePct = mov.direction === "credit" ? await getMethodFeePct(pub, input.paymentMethodId) : 0;
@@ -5376,17 +5517,17 @@ function createSupabaseFinancialProvider() {
5376
5517
  card_installments: input.cardInstallments ?? null,
5377
5518
  notes: input.notes ?? null
5378
5519
  };
5379
- let { data, error } = await pub.from("financial_movements").insert(paymentRow).select().single();
5520
+ let { data, error } = await pub.from(T.movements).insert(paymentRow).select().single();
5380
5521
  if (error && /fee_amount/i.test(error.message ?? "")) {
5381
5522
  const { fee_amount, ...rest } = paymentRow;
5382
- ({ data, error } = await pub.from("financial_movements").insert(rest).select().single());
5523
+ ({ data, error } = await pub.from(T.movements).insert(rest).select().single());
5383
5524
  }
5384
5525
  if (error) throw error;
5385
5526
  const newPaid = round22((mov.paid_amount ?? 0) + input.amount);
5386
5527
  const billStatus = newPaid >= mov.amount ? "paid" : "partial";
5387
- await pub.from("financial_movements").update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
5528
+ await pub.from(T.movements).update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
5388
5529
  if (mov.invoice_id) {
5389
- const { data: allMovs } = await pub.from("financial_movements").select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
5530
+ const { data: allMovs } = await pub.from(T.movements).select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
5390
5531
  const movs = allMovs ?? [];
5391
5532
  const allPaid = movs.length > 0 && movs.every((m) => m.status === "paid");
5392
5533
  const anyPaid = movs.some((m) => m.status === "paid" || m.status === "partial");
@@ -5403,7 +5544,7 @@ function createSupabaseFinancialProvider() {
5403
5544
  },
5404
5545
  async cancelMovement(id) {
5405
5546
  const { pub } = getClients();
5406
- await pub.from("financial_movements").update({ status: "cancelled" }).eq("id", id);
5547
+ await pub.from(T.movements).update({ status: "cancelled" }).eq("id", id);
5407
5548
  },
5408
5549
  // --- Transfers (between accounts) ---
5409
5550
  async createTransfer(input) {
@@ -5439,10 +5580,10 @@ function createSupabaseFinancialProvider() {
5439
5580
  metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
5440
5581
  }
5441
5582
  ];
5442
- let { data, error } = await pub.from("financial_movements").insert(rowsToInsert).select("id, direction");
5583
+ let { data, error } = await pub.from(T.movements).insert(rowsToInsert).select("id, direction");
5443
5584
  if (error && /fee_amount/i.test(error.message ?? "")) {
5444
5585
  const stripped = rowsToInsert.map(({ fee_amount, ...rest }) => rest);
5445
- ({ data, error } = await pub.from("financial_movements").insert(stripped).select("id, direction"));
5586
+ ({ data, error } = await pub.from(T.movements).insert(stripped).select("id, direction"));
5446
5587
  }
5447
5588
  if (error) throw error;
5448
5589
  const rows = data ?? [];
@@ -5453,13 +5594,13 @@ function createSupabaseFinancialProvider() {
5453
5594
  // --- Bank Accounts ---
5454
5595
  async getBankAccounts() {
5455
5596
  const { pub } = getClients();
5456
- const { data } = await pub.from("bank_accounts").select("*").eq("is_active", true).order("name");
5597
+ const { data } = await pub.from(T.bankAccounts).select("*").eq("is_active", true).order("name");
5457
5598
  return (data ?? []).map((r) => snakeToCamel(r));
5458
5599
  },
5459
5600
  async createBankAccount(input) {
5460
5601
  const { pub } = getClients();
5461
5602
  const tenantId = getTenantId();
5462
- const { data } = await pub.from("bank_accounts").insert({
5603
+ const { data } = await pub.from(T.bankAccounts).insert({
5463
5604
  ...camelToSnake(input),
5464
5605
  tenant_id: tenantId,
5465
5606
  current_balance: input.initialBalance ?? 0
@@ -5471,26 +5612,26 @@ function createSupabaseFinancialProvider() {
5471
5612
  const row = camelToSnake(partial);
5472
5613
  delete row.id;
5473
5614
  delete row.tenant_id;
5474
- const { data } = await pub.from("bank_accounts").update(row).eq("id", id).select().single();
5615
+ const { data } = await pub.from(T.bankAccounts).update(row).eq("id", id).select().single();
5475
5616
  return snakeToCamel(data);
5476
5617
  },
5477
5618
  // --- Cash Sessions ---
5478
5619
  async getCashSessions(bankAccountId) {
5479
5620
  const { pub } = getClients();
5480
- let qb = pub.from("cash_register_sessions").select("*");
5621
+ let qb = pub.from(T.cashRegisterSessions).select("*");
5481
5622
  if (bankAccountId) qb = qb.eq("bank_account_id", bankAccountId);
5482
5623
  const { data } = await qb.order("opened_at", { ascending: false });
5483
5624
  return (data ?? []).map((r) => snakeToCamel(r));
5484
5625
  },
5485
5626
  async getOpenSession(bankAccountId) {
5486
5627
  const { pub } = getClients();
5487
- const { data } = await pub.from("cash_register_sessions").select("*").eq("bank_account_id", bankAccountId).eq("status", "open").single();
5628
+ const { data } = await pub.from(T.cashRegisterSessions).select("*").eq("bank_account_id", bankAccountId).eq("status", "open").single();
5488
5629
  return data ? snakeToCamel(data) : null;
5489
5630
  },
5490
5631
  async openCashSession(input) {
5491
5632
  const { pub } = getClients();
5492
5633
  const tenantId = getTenantId();
5493
- const { data } = await pub.from("cash_register_sessions").insert({
5634
+ const { data } = await pub.from(T.cashRegisterSessions).insert({
5494
5635
  tenant_id: tenantId,
5495
5636
  bank_account_id: input.bankAccountId,
5496
5637
  status: "open",
@@ -5503,7 +5644,7 @@ function createSupabaseFinancialProvider() {
5503
5644
  },
5504
5645
  async closeCashSession(input) {
5505
5646
  const { pub } = getClients();
5506
- const { data } = await pub.from("cash_register_sessions").update({
5647
+ const { data } = await pub.from(T.cashRegisterSessions).update({
5507
5648
  status: "closed",
5508
5649
  closing_balance: input.closingBalance,
5509
5650
  closed_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5515,8 +5656,8 @@ function createSupabaseFinancialProvider() {
5515
5656
  },
5516
5657
  async getCashSessionSummary(sessionId) {
5517
5658
  const { pub } = getClients();
5518
- const session = (await pub.from("cash_register_sessions").select("*").eq("id", sessionId).single()).data;
5519
- const { data: movs } = await pub.from("financial_movements").select("direction, paid_amount").eq("cash_session_id", sessionId).eq("status", "paid");
5659
+ const session = (await pub.from(T.cashRegisterSessions).select("*").eq("id", sessionId).single()).data;
5660
+ const { data: movs } = await pub.from(T.movements).select("direction, paid_amount").eq("cash_session_id", sessionId).eq("status", "paid");
5520
5661
  const movements = movs ?? [];
5521
5662
  return {
5522
5663
  session: snakeToCamel(session),
@@ -5528,23 +5669,23 @@ function createSupabaseFinancialProvider() {
5528
5669
  // --- Payment Methods ---
5529
5670
  async getPaymentMethods() {
5530
5671
  const { pub } = getClients();
5531
- const { data } = await pub.from("payment_methods").select("*").eq("is_active", true).order("name");
5672
+ const { data } = await pub.from(T.paymentMethods).select("*").eq("is_active", true).order("name");
5532
5673
  return (data ?? []).map((r) => snakeToCamel(r));
5533
5674
  },
5534
5675
  async getPaymentMethodTypes() {
5535
5676
  const { pub } = getClients();
5536
- const { data } = await pub.from("payment_method_types").select("*").eq("is_active", true).order("name");
5677
+ const { data } = await pub.from(T.paymentMethodTypes).select("*").eq("is_active", true).order("name");
5537
5678
  return (data ?? []).map((r) => snakeToCamel(r));
5538
5679
  },
5539
5680
  // --- Chart of Accounts ---
5540
5681
  async getChartOfAccounts() {
5541
5682
  const { pub } = getClients();
5542
- const { data } = await pub.from("chart_of_accounts").select("*").eq("is_active", true).order("code");
5683
+ const { data } = await pub.from(T.chartOfAccounts).select("*").eq("is_active", true).order("code");
5543
5684
  return (data ?? []).map((r) => snakeToCamel(r));
5544
5685
  },
5545
5686
  async getCostCenters() {
5546
5687
  const { pub } = getClients();
5547
- const { data } = await pub.from("cost_centers").select("*").eq("is_active", true).order("code");
5688
+ const { data } = await pub.from(T.costCenters).select("*").eq("is_active", true).order("code");
5548
5689
  return (data ?? []).map((r) => snakeToCamel(r));
5549
5690
  },
5550
5691
  // --- Card Transactions ---
@@ -5556,7 +5697,7 @@ function createSupabaseFinancialProvider() {
5556
5697
  async getStatement(query) {
5557
5698
  const { core, pub } = getClients();
5558
5699
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5559
- const { data: acctRows } = await pub.from("bank_accounts").select("id, name, initial_balance, is_active");
5700
+ const { data: acctRows } = await pub.from(T.bankAccounts).select("id, name, initial_balance, is_active");
5560
5701
  const allAccounts = acctRows ?? [];
5561
5702
  const consolidated = !query.bankAccountId;
5562
5703
  const scopeAccounts = consolidated ? allAccounts.filter((a) => a.is_active) : allAccounts.filter((a) => a.id === query.bankAccountId);
@@ -5568,10 +5709,10 @@ function createSupabaseFinancialProvider() {
5568
5709
  }
5569
5710
  const scopeFilter = (qb) => consolidated ? qb : qb.in("bank_account_id", accountIds);
5570
5711
  const netOf = (r) => r.direction === "credit" ? (Number(r.paid_amount) || 0) - (Number(r.fee_amount) || 0) : -(Number(r.paid_amount) || 0);
5571
- const { data: priorRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
5712
+ const { data: priorRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
5572
5713
  const priorNet = (priorRows ?? []).reduce((s, r) => s + netOf(r), 0);
5573
5714
  const openingBalance = round22(initialSum + priorNet);
5574
- 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"));
5715
+ const { data: movs } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to).order("payment_date").order("created_at"));
5575
5716
  const invoiceIds = [...new Set((movs ?? []).map((r) => r.invoice_id).filter(Boolean))];
5576
5717
  const invoiceById = /* @__PURE__ */ new Map();
5577
5718
  if (invoiceIds.length) {
@@ -5613,7 +5754,7 @@ function createSupabaseFinancialProvider() {
5613
5754
  });
5614
5755
  const matchedIds = [...new Set(entries.map((e) => e.movement.matchedMovementId).filter(Boolean))];
5615
5756
  if (matchedIds.length) {
5616
- const { data: matched } = await pub.from("financial_movements").select("id, invoice_id, notes").in("id", matchedIds);
5757
+ const { data: matched } = await pub.from(T.movements).select("id, invoice_id, notes").in("id", matchedIds);
5617
5758
  const matchedById = new Map((matched ?? []).map((m) => [m.id, m]));
5618
5759
  const matchedInvoiceIds = [...new Set((matched ?? []).map((m) => m.invoice_id).filter(Boolean))];
5619
5760
  const invById = /* @__PURE__ */ new Map();
@@ -5635,7 +5776,7 @@ function createSupabaseFinancialProvider() {
5635
5776
  };
5636
5777
  }
5637
5778
  }
5638
- const { data: todayRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lte("payment_date", today2));
5779
+ const { data: todayRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lte("payment_date", today2));
5639
5780
  const accountCurrentBalance = round22(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
5640
5781
  return {
5641
5782
  entries,
@@ -5653,9 +5794,9 @@ function createSupabaseFinancialProvider() {
5653
5794
  async getSummary(dateRange) {
5654
5795
  sweepOverdue();
5655
5796
  const { pub } = getClients();
5656
- const { data: accounts } = await pub.from("bank_accounts").select("current_balance").eq("is_active", true);
5797
+ const { data: accounts } = await pub.from(T.bankAccounts).select("current_balance").eq("is_active", true);
5657
5798
  const totalBalance = (accounts ?? []).reduce((s, a) => s + (a.current_balance ?? 0), 0);
5658
- const { data: movs } = await pub.from("financial_movements").select("direction, movement_kind, amount, paid_amount, status, due_date, payment_date");
5799
+ const { data: movs } = await pub.from(T.movements).select("direction, movement_kind, amount, paid_amount, status, due_date, payment_date");
5659
5800
  const allMovs = movs ?? [];
5660
5801
  const unpaid = allMovs.filter((m) => ["pending", "partial", "overdue"].includes(m.status));
5661
5802
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -5688,7 +5829,7 @@ function createSupabaseFinancialProvider() {
5688
5829
  const tenantId = getTenantId();
5689
5830
  let imported = 0, duplicates = 0;
5690
5831
  for (const line of input.lines) {
5691
- const { error } = await pub.from("financial_movements").insert({
5832
+ const { error } = await pub.from(T.movements).insert({
5692
5833
  tenant_id: tenantId,
5693
5834
  direction: line.direction,
5694
5835
  movement_kind: "payment",
@@ -5711,7 +5852,7 @@ function createSupabaseFinancialProvider() {
5711
5852
  },
5712
5853
  async getUnreconciled(query) {
5713
5854
  const { pub } = getClients();
5714
- let qb = pub.from("financial_movements").select("*").not("external_source", "is", null).is("reconciled_at", null);
5855
+ let qb = pub.from(T.movements).select("*").not("external_source", "is", null).is("reconciled_at", null);
5715
5856
  if (query?.bankAccountId) qb = qb.eq("bank_account_id", query.bankAccountId);
5716
5857
  if (query?.dateRange) qb = qb.gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to);
5717
5858
  const { data } = await qb.order("payment_date", { ascending: false });
@@ -5719,12 +5860,12 @@ function createSupabaseFinancialProvider() {
5719
5860
  },
5720
5861
  async suggestReconciliation(bankMovementId) {
5721
5862
  const { core, pub } = getClients();
5722
- const { data: line } = await pub.from("financial_movements").select("*").eq("id", bankMovementId).single();
5863
+ const { data: line } = await pub.from(T.movements).select("*").eq("id", bankMovementId).single();
5723
5864
  if (!line) return [];
5724
5865
  const amount = Number(line.amount) || 0;
5725
5866
  const lineDate = line.payment_date ?? line.due_date;
5726
5867
  const tol = Math.max(0.01, round22(amount * 0.02));
5727
- 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);
5868
+ const { data } = await pub.from(T.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);
5728
5869
  const candidates = (data ?? []).map((r) => {
5729
5870
  const amtDiff = Math.abs((Number(r.amount) || 0) - amount);
5730
5871
  const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
@@ -5750,20 +5891,20 @@ function createSupabaseFinancialProvider() {
5750
5891
  async reconcileMovement(input) {
5751
5892
  const { pub } = getClients();
5752
5893
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
5753
- await pub.from("financial_movements").update({
5894
+ await pub.from(T.movements).update({
5754
5895
  reconciled_at: now2,
5755
5896
  matched_movement_id: input.matchedMovementId ?? null
5756
5897
  }).eq("id", input.bankMovementId);
5757
5898
  if (input.matchedMovementId) {
5758
- await pub.from("financial_movements").update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
5899
+ await pub.from(T.movements).update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
5759
5900
  }
5760
5901
  },
5761
5902
  async unreconcileMovement(bankMovementId) {
5762
5903
  const { pub } = getClients();
5763
- const { data: line } = await pub.from("financial_movements").select("matched_movement_id").eq("id", bankMovementId).single();
5764
- await pub.from("financial_movements").update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
5904
+ const { data: line } = await pub.from(T.movements).select("matched_movement_id").eq("id", bankMovementId).single();
5905
+ await pub.from(T.movements).update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
5765
5906
  if (line?.matched_movement_id) {
5766
- await pub.from("financial_movements").update({ reconciled_at: null }).eq("id", line.matched_movement_id);
5907
+ await pub.from(T.movements).update({ reconciled_at: null }).eq("id", line.matched_movement_id);
5767
5908
  }
5768
5909
  }
5769
5910
  };
@@ -6011,7 +6152,7 @@ var paymentMethodTypeEntity = {
6011
6152
  { key: "transactionType", label: "Transaction Type", type: "select", options: ["cash", "credit_card", "debit_card", "pix", "bank_transfer", "check", "voucher"], showInTable: true, required: true },
6012
6153
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6013
6154
  ],
6014
- data: { table: "payment_method_types", tenantScoped: true }
6155
+ data: { table: T.paymentMethodTypes, tenantScoped: true }
6015
6156
  };
6016
6157
  var paymentMethodEntity = {
6017
6158
  name: "Payment Method",
@@ -6023,14 +6164,14 @@ var paymentMethodEntity = {
6023
6164
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
6024
6165
  // FK to the seeded payment_method_types catalog — load real ids at runtime
6025
6166
  // (a static enum select would write the string into the uuid column).
6026
- { key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: "payment_method_types", labelField: "name" }, showInTable: true, required: true },
6167
+ { key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: T.paymentMethodTypes, labelField: "name" }, showInTable: true, required: true },
6027
6168
  { key: "discountValue", label: "Discount %", type: "number", showInTable: true },
6028
6169
  { key: "interestValue", label: "Interest %", type: "number", showInTable: true },
6029
6170
  { key: "minInstallments", label: "Min Installments", type: "number", defaultValue: 1 },
6030
6171
  { key: "maxInstallments", label: "Max Installments", type: "number", defaultValue: 1, showInTable: true },
6031
6172
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6032
6173
  ],
6033
- data: { table: "payment_methods", tenantScoped: true }
6174
+ data: { table: T.paymentMethods, tenantScoped: true }
6034
6175
  };
6035
6176
  var chartOfAccountsEntity = {
6036
6177
  name: "Ledger Account",
@@ -6045,7 +6186,7 @@ var chartOfAccountsEntity = {
6045
6186
  { key: "parentId", label: "Parent", type: "text", showInTable: false },
6046
6187
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6047
6188
  ],
6048
- data: { table: "chart_of_accounts", tenantScoped: true }
6189
+ data: { table: T.chartOfAccounts, tenantScoped: true }
6049
6190
  };
6050
6191
  var costCenterEntity = {
6051
6192
  name: "Cost Center",
@@ -6058,7 +6199,7 @@ var costCenterEntity = {
6058
6199
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
6059
6200
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6060
6201
  ],
6061
- data: { table: "cost_centers", tenantScoped: true }
6202
+ data: { table: T.costCenters, tenantScoped: true }
6062
6203
  };
6063
6204
  var bankAccountEntity = {
6064
6205
  name: "Bank Account",
@@ -6083,7 +6224,7 @@ var bankAccountEntity = {
6083
6224
  { key: "dueDay", label: "Due Day", type: "number" },
6084
6225
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6085
6226
  ],
6086
- data: { table: "bank_accounts", tenantScoped: true }
6227
+ data: { table: T.bankAccounts, tenantScoped: true }
6087
6228
  };
6088
6229
  var cardBrandEntity = {
6089
6230
  name: "Card Brand",
@@ -6095,7 +6236,7 @@ var cardBrandEntity = {
6095
6236
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
6096
6237
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6097
6238
  ],
6098
- data: { table: "card_brands", tenantScoped: true }
6239
+ data: { table: T.cardBrands, tenantScoped: true }
6099
6240
  };
6100
6241
  var commissionRuleEntity = {
6101
6242
  name: "Commission Rule",
@@ -6400,6 +6541,16 @@ var en = {
6400
6541
  "financial.invoice.filterAllAccounts": "All accounts",
6401
6542
  "financial.invoice.filterCostCenter": "Cost center",
6402
6543
  "financial.invoice.filterAllCostCenters": "All cost centers",
6544
+ "financial.invoice.filterPeriod": "Period",
6545
+ "financial.invoice.filterAllPeriods": "All time",
6546
+ "financial.invoice.filterThisMonth": "This month",
6547
+ "financial.invoice.filterLastMonth": "Last month",
6548
+ "financial.invoice.filterLast30": "Last 30 days",
6549
+ "financial.invoice.filterThisYear": "This year",
6550
+ "financial.invoice.filterCustom": "Custom",
6551
+ "financial.invoice.filterFrom": "From",
6552
+ "financial.invoice.filterTo": "To",
6553
+ "financial.invoice.filterClear": "Clear",
6403
6554
  "financial.reconciliation.title": "Reconciliation",
6404
6555
  "financial.reconciliation.subtitle": "Match imported bank lines to your movements",
6405
6556
  "financial.reconciliation.pending": "{{count}} to reconcile",
@@ -6883,6 +7034,16 @@ var ptBR = {
6883
7034
  "financial.invoice.filterAllAccounts": "Todos os planos",
6884
7035
  "financial.invoice.filterCostCenter": "Centro de custo",
6885
7036
  "financial.invoice.filterAllCostCenters": "Todos os centros",
7037
+ "financial.invoice.filterPeriod": "Per\xEDodo",
7038
+ "financial.invoice.filterAllPeriods": "Todo o per\xEDodo",
7039
+ "financial.invoice.filterThisMonth": "Este m\xEAs",
7040
+ "financial.invoice.filterLastMonth": "M\xEAs passado",
7041
+ "financial.invoice.filterLast30": "\xDAltimos 30 dias",
7042
+ "financial.invoice.filterThisYear": "Este ano",
7043
+ "financial.invoice.filterCustom": "Personalizado",
7044
+ "financial.invoice.filterFrom": "De",
7045
+ "financial.invoice.filterTo": "At\xE9",
7046
+ "financial.invoice.filterClear": "Limpar",
6886
7047
  "financial.reconciliation.title": "Concilia\xE7\xE3o",
6887
7048
  "financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
6888
7049
  "financial.reconciliation.pending": "{{count}} para conciliar",
@@ -7352,6 +7513,15 @@ var DEFAULT_LABELS = {
7352
7513
  cardsReconciliation: "Reconciliation"
7353
7514
  };
7354
7515
  var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
7516
+ var FINANCIAL_NAV_FEATURES = {
7517
+ reconciliation: "fin_reconciliation",
7518
+ cards: "fin_cards",
7519
+ commissions: "fin_commissions",
7520
+ statements: "fin_statements",
7521
+ receivables: "fin_receivables",
7522
+ payables: "fin_payables",
7523
+ "cash-registers": "fin_cash_registers"
7524
+ };
7355
7525
  var DEFAULT_ITEM_TYPES = [
7356
7526
  { value: "service", label: "Service", icon: "Briefcase" },
7357
7527
  { value: "product", label: "Product", icon: "Package" },
@@ -7382,7 +7552,8 @@ function resolveConfig(options) {
7382
7552
  locations: options?.locations ?? [],
7383
7553
  entityLookups: options?.entityLookups ?? {},
7384
7554
  contactLookup: options?.contactLookup,
7385
- onBookingClick: options?.onBookingClick
7555
+ onBookingClick: options?.onBookingClick,
7556
+ navFeatureMap: { ...options?.navFeatureMap }
7386
7557
  };
7387
7558
  }
7388
7559
  function createFinancialPlugin(options) {
@@ -7410,6 +7581,70 @@ function createFinancialPlugin(options) {
7410
7581
  ...config.modules.payables ? [{ id: "financial.payables", label: config.labels.payables, group: config.labels.pageTitle }] : [],
7411
7582
  ...config.modules.commissions ? [{ id: "financial.commissions", label: config.labels.commissions, group: config.labels.pageTitle }] : []
7412
7583
  ],
7584
+ // Recurring monthly quota — counts financial movement rows created this
7585
+ // month (invoices write one per installment; quick transactions one, or two
7586
+ // for a transfer).
7587
+ declaredLimits: [
7588
+ { key: "movements_month", label: "Financial movements this month", table: "plg_financial_movements", period: "month" }
7589
+ ],
7590
+ queryEntities: [
7591
+ {
7592
+ key: "financial:invoices",
7593
+ entity: {
7594
+ name: "Invoice",
7595
+ namePlural: "Invoices (receipts REC- / payables PAG-)",
7596
+ icon: "Receipt",
7597
+ permission: { feature: "financial", action: "read" },
7598
+ fields: [
7599
+ { key: "referenceNumber", label: "Reference (REC-/PAG-)", type: "text", searchable: true },
7600
+ { key: "flow", label: "Flow (receivable/payable)", type: "text" },
7601
+ { key: "amount", label: "Amount", type: "number" },
7602
+ { key: "paid", label: "Paid", type: "number" },
7603
+ { key: "balance", label: "Balance", type: "number" },
7604
+ { key: "status", label: "Status", type: "text" },
7605
+ { key: "createdAt", label: "Created at", type: "text" }
7606
+ ],
7607
+ data: {
7608
+ table: "v_invoice_balances",
7609
+ tenantScoped: true,
7610
+ searchColumns: ["reference_number"]
7611
+ }
7612
+ }
7613
+ },
7614
+ {
7615
+ key: "financial:movements",
7616
+ entity: {
7617
+ name: "Financial movement",
7618
+ namePlural: "Financial movements (installment ledger)",
7619
+ icon: "Banknote",
7620
+ permission: { feature: "financial", action: "read" },
7621
+ fields: [
7622
+ { key: "direction", label: "Direction", type: "text" },
7623
+ { key: "movementKind", label: "Kind (bill/payment)", type: "text" },
7624
+ { key: "amount", label: "Amount", type: "number" },
7625
+ { key: "paidAmount", label: "Paid amount", type: "number" },
7626
+ { key: "status", label: "Status", type: "text" },
7627
+ { key: "dueDate", label: "Due date", type: "text" },
7628
+ { key: "paymentDate", label: "Payment date", type: "text" },
7629
+ { key: "installmentNumber", label: "Installment", type: "number" },
7630
+ { key: "cardInstallments", label: "Card installments", type: "number" },
7631
+ { key: "createdAt", label: "Created at", type: "text" }
7632
+ ],
7633
+ data: {
7634
+ table: "plg_financial_movements",
7635
+ tenantScoped: true
7636
+ }
7637
+ }
7638
+ }
7639
+ ],
7640
+ declaredRpcs: [
7641
+ {
7642
+ name: "agent_financial_mark_payment_received",
7643
+ kind: "write",
7644
+ description: "Guarded settle: invoices the order via fn_invoice_from_order (installments for credit card) and marks the receivable paid, audited with the acting user.",
7645
+ audits: true
7646
+ }
7647
+ ],
7413
7648
  navigation: [
7414
7649
  {
7415
7650
  section: options?.navSection ?? "main",
@@ -7463,6 +7698,7 @@ function createFinancialPlugin(options) {
7463
7698
  description: "Creates a new invoice/payable for a contact.",
7464
7699
  icon: "FileText",
7465
7700
  mode: "persist",
7701
+ limitKey: "movements_month",
7466
7702
  category: "Finance",
7467
7703
  parameters: {
7468
7704
  type: "object",
@@ -7478,6 +7714,33 @@ function createFinancialPlugin(options) {
7478
7714
  ],
7479
7715
  permission: { feature: "financial", action: "create" }
7480
7716
  },
7717
+ {
7718
+ id: "financial.mark-payment-received",
7719
+ name: "markPaymentReceived",
7720
+ description: 'Marks an order/appointment as paid: invoices it (with installments for credit card, e.g. "cart\xE3o 2x") and settles the receivable. Resolve the booking or order id via search tools first.',
7721
+ icon: "BadgeDollarSign",
7722
+ mode: "persist",
7723
+ execution: { plane: "server", kind: "rpc", rpc: "agent_financial_mark_payment_received" },
7724
+ category: "Finance",
7725
+ parameters: {
7726
+ type: "object",
7727
+ properties: {
7728
+ booking_id: { type: "string", description: "Appointment id (alternative to order_id)" },
7729
+ order_id: { type: "string", description: "Order id" },
7730
+ method: {
7731
+ type: "string",
7732
+ description: "Payment method",
7733
+ enum: ["cash", "pix", "credit_card", "debit_card", "transfer"]
7734
+ },
7735
+ installments: { type: "number", description: 'Credit-card installments (1-12), e.g. 2 for "cart\xE3o 2x"' }
7736
+ },
7737
+ required: ["method"]
7738
+ },
7739
+ suggestions: [
7740
+ { label: "Marcar o atendimento como pago no cart\xE3o em 2x" }
7741
+ ],
7742
+ permission: { feature: "financial.receivables", action: "update" }
7743
+ },
7481
7744
  {
7482
7745
  id: "financial.list-payables",
7483
7746
  name: "listPayables",
@@ -7525,6 +7788,7 @@ function createFinancialPlugin(options) {
7525
7788
  };
7526
7789
  }
7527
7790
 
7791
+ exports.FINANCIAL_NAV_FEATURES = FINANCIAL_NAV_FEATURES;
7528
7792
  exports.createFinancialPlugin = createFinancialPlugin;
7529
7793
  exports.createMockFinancialProvider = createMockFinancialProvider;
7530
7794
  exports.createSafeFinancialProvider = createSafeFinancialProvider;