@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.js CHANGED
@@ -2,8 +2,8 @@ import * as React17 from 'react';
2
2
  import React17__default, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
3
3
  import { createSafeDataProvider, registerTranslations, useTranslation, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
4
4
  import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, Modal, ModalContent, DatePicker, DashboardCanvas, SubpageHeader, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, Card, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
5
- import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, ModuleActionBar, SettingsGroup, ToggleRow, formatCurrency, parseViewId, PersonLink } from '@fayz-ai/saas';
6
- import { Wallet, Plus, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, X, CalendarDays, Paperclip, Camera, Tag, Repeat, CalendarClock, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Receipt, ChevronDown, Clock, CheckCheck, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, Ban, LayoutList, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, FileText, User, Calendar, Hash, ExternalLink, Building2, ArrowLeftRight, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, Search, Trash2, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, EyeOff, Eye } from 'lucide-react';
5
+ import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, PermissionGate, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, formatCurrency, parseViewId, EntitlementGate, UpgradePrompt, useLimitGuard, invalidateLimit, PersonLink } from '@fayz-ai/saas';
6
+ import { Wallet, Plus, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, X, CalendarDays, Paperclip, Camera, Tag, Repeat, ChevronDown, CalendarClock, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Receipt, Clock, ArrowLeftRight, CheckCheck, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, Ban, LayoutList, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, FileText, User, Calendar, Hash, ExternalLink, Building2, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, Search, Trash2, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, EyeOff, Eye } from 'lucide-react';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import { createPortal } from 'react-dom';
9
9
  import { createStore as createStore$1 } from 'zustand/vanilla';
@@ -252,6 +252,8 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
252
252
  const { currency } = useFinancialConfig();
253
253
  const provider = useFinancialProvider();
254
254
  const createQuickTransaction = useFinancialStore((s) => s.createQuickTransaction);
255
+ const guardMovements = useLimitGuard("movements_month");
256
+ const can = usePermissionOptional();
255
257
  const [type, setType] = useState(defaultType);
256
258
  const [amount, setAmount] = useState(0);
257
259
  const [amountStr, setAmountStr] = useState("");
@@ -337,12 +339,14 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
337
339
  }
338
340
  const canSave = amount > 0 && !saving && (type === "transfer" ? !!accountId && !!toAccountId && accountId !== toAccountId : true);
339
341
  async function handleSave() {
342
+ if (!can("financial", "create")) return;
340
343
  if (!canSave) {
341
344
  if (type === "transfer" && accountId && accountId === toAccountId) {
342
345
  toast.error(t2("financial.transfer.sameAccount"));
343
346
  }
344
347
  return;
345
348
  }
349
+ if (await guardMovements(type === "transfer" ? 2 : 1) === "blocked") return;
346
350
  setSaving(true);
347
351
  try {
348
352
  await createQuickTransaction({
@@ -357,6 +361,7 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
357
361
  recurring: type === "transfer" ? false : recurring,
358
362
  receiptUrl: type === "transfer" ? void 0 : receiptUrl
359
363
  });
364
+ invalidateLimit("movements_month");
360
365
  onOpenChange(false);
361
366
  } catch {
362
367
  } finally {
@@ -645,6 +650,23 @@ var STATUS_OPTIONS = [
645
650
  { value: "overdue", labelKey: "financial.invoice.statusOverdue", color: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert },
646
651
  { value: "cancelled", labelKey: "financial.invoice.statusCancelled", color: "bg-muted text-muted-foreground", icon: Ban }
647
652
  ];
653
+ var isoDate = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
654
+ function presetRange(preset) {
655
+ const now2 = /* @__PURE__ */ new Date();
656
+ switch (preset) {
657
+ case "thisMonth":
658
+ return { from: isoDate(new Date(now2.getFullYear(), now2.getMonth(), 1)), to: isoDate(new Date(now2.getFullYear(), now2.getMonth() + 1, 0)) };
659
+ case "lastMonth":
660
+ return { from: isoDate(new Date(now2.getFullYear(), now2.getMonth() - 1, 1)), to: isoDate(new Date(now2.getFullYear(), now2.getMonth(), 0)) };
661
+ case "last30": {
662
+ const from = new Date(now2);
663
+ from.setDate(from.getDate() - 29);
664
+ return { from: isoDate(from), to: isoDate(now2) };
665
+ }
666
+ case "thisYear":
667
+ return { from: isoDate(new Date(now2.getFullYear(), 0, 1)), to: isoDate(new Date(now2.getFullYear(), 11, 31)) };
668
+ }
669
+ }
648
670
  function StatusBadge({ status }) {
649
671
  const t2 = useTranslation();
650
672
  const opt = STATUS_OPTIONS.find((o) => o.value === status);
@@ -708,6 +730,8 @@ function useInvoiceColumns(currency) {
708
730
  }
709
731
  function InvoiceListView({ direction, onNew, onEdit }) {
710
732
  const t2 = useTranslation();
733
+ const can = usePermissionOptional();
734
+ const onNewGated = can("financial", "create") ? onNew : void 0;
711
735
  const { currency } = useFinancialConfig();
712
736
  const provider = useFinancialProvider();
713
737
  const invoices = useFinancialStore((s) => s.invoices);
@@ -720,6 +744,25 @@ function InvoiceListView({ direction, onNew, onEdit }) {
720
744
  const [costCenterId, setCostCenterId] = useState("");
721
745
  const [accounts, setAccounts] = useState([]);
722
746
  const [costCenters, setCostCenters] = useState([]);
747
+ const [datePreset, setDatePreset] = useState("all");
748
+ const [dateFrom, setDateFrom] = useState("");
749
+ const [dateTo, setDateTo] = useState("");
750
+ const dateRange = useMemo(() => {
751
+ if (!dateFrom && !dateTo) return void 0;
752
+ return { from: dateFrom || "0001-01-01", to: dateTo || "9999-12-31" };
753
+ }, [dateFrom, dateTo]);
754
+ const applyPreset = (value) => {
755
+ setDatePreset(value);
756
+ if (value === "all") {
757
+ setDateFrom("");
758
+ setDateTo("");
759
+ return;
760
+ }
761
+ if (value === "custom") return;
762
+ const range = presetRange(value);
763
+ setDateFrom(range.from);
764
+ setDateTo(range.to);
765
+ };
723
766
  useEffect(() => {
724
767
  let active = true;
725
768
  Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
@@ -741,9 +784,10 @@ function InvoiceListView({ direction, onNew, onEdit }) {
741
784
  status: statusFilter.length > 0 ? statusFilter : void 0,
742
785
  search: search || void 0,
743
786
  accountId: accountId || void 0,
744
- costCenterId: costCenterId || void 0
787
+ costCenterId: costCenterId || void 0,
788
+ dateRange
745
789
  });
746
- }, [direction, statusFilter, search, accountId, costCenterId]);
790
+ }, [direction, statusFilter, search, accountId, costCenterId, dateRange]);
747
791
  const filtered = invoices.filter((inv) => inv.direction === direction);
748
792
  const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
749
793
  const columns = useInvoiceColumns(currency);
@@ -773,7 +817,67 @@ function InvoiceListView({ direction, onNew, onEdit }) {
773
817
  subtitle: t2("financial.invoice.invoices", { count: filtered.length })
774
818
  }
775
819
  ),
776
- (accounts.length > 0 || costCenters.length > 0) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
820
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
821
+ /* @__PURE__ */ jsxs("label", { className: "flex min-w-[180px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
822
+ t2("financial.invoice.filterPeriod"),
823
+ /* @__PURE__ */ jsxs(
824
+ "select",
825
+ {
826
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
827
+ value: datePreset,
828
+ onChange: (event) => applyPreset(event.target.value),
829
+ children: [
830
+ /* @__PURE__ */ jsx("option", { value: "all", children: t2("financial.invoice.filterAllPeriods") }),
831
+ /* @__PURE__ */ jsx("option", { value: "thisMonth", children: t2("financial.invoice.filterThisMonth") }),
832
+ /* @__PURE__ */ jsx("option", { value: "lastMonth", children: t2("financial.invoice.filterLastMonth") }),
833
+ /* @__PURE__ */ jsx("option", { value: "last30", children: t2("financial.invoice.filterLast30") }),
834
+ /* @__PURE__ */ jsx("option", { value: "thisYear", children: t2("financial.invoice.filterThisYear") }),
835
+ /* @__PURE__ */ jsx("option", { value: "custom", children: t2("financial.invoice.filterCustom") })
836
+ ]
837
+ }
838
+ )
839
+ ] }),
840
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
841
+ t2("financial.invoice.filterFrom"),
842
+ /* @__PURE__ */ jsx(
843
+ "input",
844
+ {
845
+ type: "date",
846
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
847
+ value: dateFrom,
848
+ max: dateTo || void 0,
849
+ onChange: (event) => {
850
+ setDateFrom(event.target.value);
851
+ setDatePreset("custom");
852
+ }
853
+ }
854
+ )
855
+ ] }),
856
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
857
+ t2("financial.invoice.filterTo"),
858
+ /* @__PURE__ */ jsx(
859
+ "input",
860
+ {
861
+ type: "date",
862
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
863
+ value: dateTo,
864
+ min: dateFrom || void 0,
865
+ onChange: (event) => {
866
+ setDateTo(event.target.value);
867
+ setDatePreset("custom");
868
+ }
869
+ }
870
+ )
871
+ ] }),
872
+ (dateFrom || dateTo) && /* @__PURE__ */ jsx(
873
+ "button",
874
+ {
875
+ type: "button",
876
+ className: "h-9 rounded-md px-2 text-xs font-medium text-muted-foreground hover:text-foreground",
877
+ onClick: () => applyPreset("all"),
878
+ children: t2("financial.invoice.filterClear")
879
+ }
880
+ ),
777
881
  accounts.length > 0 && /* @__PURE__ */ jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
778
882
  t2("financial.invoice.filterAccount"),
779
883
  /* @__PURE__ */ jsxs(
@@ -827,12 +931,12 @@ function InvoiceListView({ direction, onNew, onEdit }) {
827
931
  activeTag: statusFilter.length === 1 ? statusFilter[0] : void 0,
828
932
  onTagChange: (v) => setStatusFilter(v ? [v] : []),
829
933
  newLabel: t2("financial.invoice.new"),
830
- onNew,
934
+ onNew: onNewGated,
831
935
  onRowClick: (row) => onEdit?.(row.id),
832
936
  emptyIcon: FileText,
833
937
  emptyMessage: t2("financial.invoice.noInvoices"),
834
- emptyActionLabel: onNew ? t2("financial.invoice.createFirst") : void 0,
835
- onEmptyAction: onNew
938
+ emptyActionLabel: onNewGated ? t2("financial.invoice.createFirst") : void 0,
939
+ onEmptyAction: onNewGated
836
940
  }
837
941
  ),
838
942
  filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
@@ -1172,6 +1276,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
1172
1276
  const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
1173
1277
  const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
1174
1278
  const createInvoice = useFinancialStore((s) => s.createInvoice);
1279
+ const guardMovements = useLimitGuard("movements_month");
1280
+ const can = usePermissionOptional();
1175
1281
  const hasLocations = locations.length > 0;
1176
1282
  const isEdit = !!editId;
1177
1283
  const [loaded, setLoaded] = useState(!editId);
@@ -1309,10 +1415,12 @@ function InvoiceFormView({ direction, editId, onSaved }) {
1309
1415
  setInstallments(inst);
1310
1416
  }
1311
1417
  async function handleSave() {
1418
+ if (!isEdit && !can("financial", "create")) return;
1312
1419
  if (items.length === 0) {
1313
1420
  toast.error(t2("common.formIncomplete"));
1314
1421
  return;
1315
1422
  }
1423
+ if (!isEdit && await guardMovements(Math.max(1, installments.length)) === "blocked") return;
1316
1424
  setSaving(true);
1317
1425
  try {
1318
1426
  const movInputs = installments.map((inst, i) => ({
@@ -1354,6 +1462,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
1354
1462
  items: items.map(({ _id, ...rest }) => rest),
1355
1463
  installments: movInputs
1356
1464
  });
1465
+ invalidateLimit("movements_month");
1357
1466
  onSaved?.(invoice.id);
1358
1467
  }
1359
1468
  } finally {
@@ -3495,7 +3604,7 @@ function CardsView() {
3495
3604
  /* @__PURE__ */ jsx("span", { children: t2("financial.cards.dueOn", { day: String(card.dueDay) }) })
3496
3605
  ] })
3497
3606
  ] }),
3498
- /* @__PURE__ */ jsxs(
3607
+ /* @__PURE__ */ jsx(PermissionGate, { feature: "financial", action: "create", children: /* @__PURE__ */ jsxs(
3499
3608
  "button",
3500
3609
  {
3501
3610
  type: "button",
@@ -3506,7 +3615,7 @@ function CardsView() {
3506
3615
  t2("financial.cards.addExpense")
3507
3616
  ]
3508
3617
  }
3509
- )
3618
+ ) })
3510
3619
  ] })
3511
3620
  ] }, card.id);
3512
3621
  }) })
@@ -3667,28 +3776,31 @@ function ReconciliationView() {
3667
3776
  ] })
3668
3777
  ] });
3669
3778
  }
3779
+ var FINANCIAL_DEFAULTS = {
3780
+ autoInstallments: true,
3781
+ requireDoc: false,
3782
+ allowPartial: true,
3783
+ requireOpening: true,
3784
+ autoClose: false,
3785
+ overdueAlerts: true,
3786
+ dailySummary: false
3787
+ };
3670
3788
  function FinancialGeneralSettings() {
3671
3789
  const t2 = useTranslation();
3790
+ const s = useTenantPluginSettings("financial", FINANCIAL_DEFAULTS);
3672
3791
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3673
3792
  /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("financial.settings.invoices"), description: t2("financial.settings.invoicesDesc"), children: [
3674
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.autoInstallments"), description: t2("financial.settings.autoInstallmentsDesc"), checked: true, onChange: () => {
3675
- } }),
3676
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.requireDoc"), description: t2("financial.settings.requireDocDesc"), checked: false, onChange: () => {
3677
- } }),
3678
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.allowPartial"), description: t2("financial.settings.allowPartialDesc"), checked: true, onChange: () => {
3679
- } })
3793
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.autoInstallments"), description: t2("financial.settings.autoInstallmentsDesc"), checked: s.get("autoInstallments"), onChange: (v) => s.set("autoInstallments", v) }),
3794
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.requireDoc"), description: t2("financial.settings.requireDocDesc"), checked: s.get("requireDoc"), onChange: (v) => s.set("requireDoc", v) }),
3795
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.allowPartial"), description: t2("financial.settings.allowPartialDesc"), checked: s.get("allowPartial"), onChange: (v) => s.set("allowPartial", v) })
3680
3796
  ] }),
3681
3797
  /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("financial.settings.cashRegister"), description: t2("financial.settings.cashRegisterDesc"), children: [
3682
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.requireOpening"), description: t2("financial.settings.requireOpeningDesc"), checked: true, onChange: () => {
3683
- } }),
3684
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.autoClose"), description: t2("financial.settings.autoCloseDesc"), checked: false, onChange: () => {
3685
- } })
3798
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.requireOpening"), description: t2("financial.settings.requireOpeningDesc"), checked: s.get("requireOpening"), onChange: (v) => s.set("requireOpening", v) }),
3799
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.autoClose"), description: t2("financial.settings.autoCloseDesc"), checked: s.get("autoClose"), onChange: (v) => s.set("autoClose", v) })
3686
3800
  ] }),
3687
3801
  /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("financial.settings.notifications"), description: t2("financial.settings.notificationsDesc"), children: [
3688
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.overdueAlerts"), description: t2("financial.settings.overdueAlertsDesc"), checked: true, onChange: () => {
3689
- } }),
3690
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.dailySummary"), description: t2("financial.settings.dailySummaryDesc"), checked: false, onChange: () => {
3691
- } })
3802
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.overdueAlerts"), description: t2("financial.settings.overdueAlertsDesc"), checked: s.get("overdueAlerts"), onChange: (v) => s.set("overdueAlerts", v) }),
3803
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.dailySummary"), description: t2("financial.settings.dailySummaryDesc"), checked: s.get("dailySummary"), onChange: (v) => s.set("dailySummary", v) })
3692
3804
  ] })
3693
3805
  ] });
3694
3806
  }
@@ -3829,6 +3941,10 @@ function buildNav(config, activeView, navigate, t2) {
3829
3941
  ]
3830
3942
  });
3831
3943
  }
3944
+ for (const item of items) {
3945
+ const feature = config.navFeatureMap[item.id];
3946
+ if (feature) item.feature = feature;
3947
+ }
3832
3948
  return items;
3833
3949
  }
3834
3950
  function FinancialPage({ config, provider, store, registries }) {
@@ -3864,9 +3980,11 @@ function FinancialPage({ config, provider, store, registries }) {
3864
3980
  };
3865
3981
  }, [onboardingComplete, provider]);
3866
3982
  const t2 = useTranslation();
3983
+ const can = usePermissionOptional();
3984
+ const canCreate = can("financial", "create");
3867
3985
  const intent = parseIntent(view);
3868
3986
  const isSettings = view === "settings";
3869
- const nav = buildNav(config, view, navigate, t2);
3987
+ const nav = useModuleNavAccess(buildNav(config, view, navigate, t2));
3870
3988
  const [quickAddOpen, setQuickAddOpen] = useState(false);
3871
3989
  const [quickAddType, setQuickAddType] = useState("expense");
3872
3990
  const [quickAddReceipt, setQuickAddReceipt] = useState(false);
@@ -3880,12 +3998,14 @@ function FinancialPage({ config, provider, store, registries }) {
3880
3998
  if (!pendingQuickAdd) return;
3881
3999
  const req = consumeQuickAdd();
3882
4000
  if (!req) return;
4001
+ if (!canCreate) return;
3883
4002
  setQuickAddType(req.type);
3884
4003
  setQuickAddReceipt(req.receipt);
3885
4004
  setQuickAddOpen(true);
3886
4005
  }, [pendingQuickAdd]);
3887
4006
  const quickActions = useMemo(() => {
3888
4007
  if (config.quickAdd) return [];
4008
+ if (!canCreate) return [];
3889
4009
  const actions = [];
3890
4010
  if (config.modules.payables) {
3891
4011
  actions.push({
@@ -3915,7 +4035,7 @@ function FinancialPage({ config, provider, store, registries }) {
3915
4035
  });
3916
4036
  }
3917
4037
  return actions;
3918
- }, [config.modules, config.quickAdd]);
4038
+ }, [config.modules, config.quickAdd, canCreate, navigate, t2]);
3919
4039
  function handleOnboardingComplete() {
3920
4040
  setOnboardingComplete(true);
3921
4041
  try {
@@ -3959,6 +4079,11 @@ function FinancialPage({ config, provider, store, registries }) {
3959
4079
  return /* @__PURE__ */ jsx(SummaryView, {});
3960
4080
  }
3961
4081
  }
4082
+ function renderGatedView() {
4083
+ const feature = config.navFeatureMap[intent.view];
4084
+ if (!feature) return renderView();
4085
+ return /* @__PURE__ */ jsx(EntitlementGate, { feature, fallback: /* @__PURE__ */ jsx(UpgradePrompt, { feature }), children: renderView() });
4086
+ }
3962
4087
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxs(
3963
4088
  ModulePage,
3964
4089
  {
@@ -3973,7 +4098,8 @@ function FinancialPage({ config, provider, store, registries }) {
3973
4098
  // add affordance in B2C mode. The receipt entry point lives inside the
3974
4099
  // sheet's "Fotografar ou anexar recibo" attachment step, so no separate
3975
4100
  // "Enviar recibo" button is needed here.
3976
- /* @__PURE__ */ jsxs(
4101
+ // Gated on the role's `create` action so read-only members don't see it.
4102
+ /* @__PURE__ */ jsx(PermissionGate, { feature: "financial", action: "create", children: /* @__PURE__ */ jsxs(
3977
4103
  Button,
3978
4104
  {
3979
4105
  size: "sm",
@@ -3984,7 +4110,7 @@ function FinancialPage({ config, provider, store, registries }) {
3984
4110
  t2("financial.quickTx.newTransaction")
3985
4111
  ]
3986
4112
  }
3987
- ),
4113
+ ) }),
3988
4114
  /* @__PURE__ */ jsx(
3989
4115
  ModuleActionBar,
3990
4116
  {
@@ -3995,7 +4121,7 @@ function FinancialPage({ config, provider, store, registries }) {
3995
4121
  )
3996
4122
  ] }),
3997
4123
  children: [
3998
- renderView(),
4124
+ renderGatedView(),
3999
4125
  /* @__PURE__ */ jsx(
4000
4126
  QuickTransactionForm,
4001
4127
  {
@@ -5016,6 +5142,18 @@ function createMockFinancialProvider(options) {
5016
5142
  return provider;
5017
5143
  }
5018
5144
 
5145
+ // src/data/tables.ts
5146
+ var T = {
5147
+ movements: "plg_financial_movements",
5148
+ chartOfAccounts: "plg_financial_chart_of_accounts",
5149
+ bankAccounts: "plg_financial_bank_accounts",
5150
+ cardBrands: "plg_financial_card_brands",
5151
+ costCenters: "plg_financial_cost_centers",
5152
+ cashRegisterSessions: "plg_financial_cash_register_sessions",
5153
+ paymentMethods: "plg_financial_payment_methods",
5154
+ paymentMethodTypes: "plg_financial_payment_method_types"
5155
+ };
5156
+
5019
5157
  // src/data/supabase.ts
5020
5158
  function getTenantId() {
5021
5159
  return getActiveTenantId();
@@ -5038,7 +5176,7 @@ function camelToSnake(obj) {
5038
5176
  function getClients() {
5039
5177
  const supabase = getSupabaseClientOptional();
5040
5178
  if (!supabase) throw new Error("Supabase not initialized");
5041
- return { core: supabase.schema("saas_core"), pub: supabase };
5179
+ return { core: supabase, pub: supabase };
5042
5180
  }
5043
5181
  function mapStageToInvoiceStatus(stage) {
5044
5182
  switch (stage) {
@@ -5065,11 +5203,11 @@ async function sweepOverdue() {
5065
5203
  try {
5066
5204
  const { core, pub } = getClients();
5067
5205
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5068
- const { data: overdueMovs } = await pub.from("financial_movements").update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today2).select("invoice_id");
5206
+ const { data: overdueMovs } = await pub.from(T.movements).update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today2).select("invoice_id");
5069
5207
  if (overdueMovs && overdueMovs.length > 0) {
5070
5208
  const invoiceIds = [...new Set(overdueMovs.map((m) => m.invoice_id).filter(Boolean))];
5071
5209
  for (const invId of invoiceIds) {
5072
- const { data: allMovs } = await pub.from("financial_movements").select("status").eq("invoice_id", invId).eq("movement_kind", "bill");
5210
+ const { data: allMovs } = await pub.from(T.movements).select("status").eq("invoice_id", invId).eq("movement_kind", "bill");
5073
5211
  const movs = allMovs ?? [];
5074
5212
  const hasOverdue = movs.some((m) => m.status === "overdue");
5075
5213
  const allPaid = movs.every((m) => m.status === "paid");
@@ -5107,7 +5245,7 @@ async function findOrderIdsByItemMetadata(core, filters) {
5107
5245
  }
5108
5246
  async function getMethodFeePct(pub, paymentMethodId) {
5109
5247
  if (!paymentMethodId) return 0;
5110
- const { data } = await pub.from("payment_methods").select("discount_value").eq("id", paymentMethodId).single();
5248
+ const { data } = await pub.from(T.paymentMethods).select("discount_value").eq("id", paymentMethodId).single();
5111
5249
  return Number(data?.discount_value) || 0;
5112
5250
  }
5113
5251
  function genId() {
@@ -5142,7 +5280,10 @@ function createSupabaseFinancialProvider() {
5142
5280
  }
5143
5281
  if (query.contactId) qb = qb.eq("party_id", query.contactId);
5144
5282
  if (query.search) qb = qb.ilike("notes", `%${query.search}%`);
5145
- if (query.dateRange) qb = qb.gte("created_at", query.dateRange.from).lte("created_at", query.dateRange.to);
5283
+ if (query.dateRange) {
5284
+ const to = query.dateRange.to.length === 10 ? `${query.dateRange.to}T23:59:59.999` : query.dateRange.to;
5285
+ qb = qb.gte("created_at", query.dateRange.from).lte("created_at", to);
5286
+ }
5146
5287
  const page = query.page ?? 1;
5147
5288
  const pageSize = query.pageSize ?? 50;
5148
5289
  qb = qb.range((page - 1) * pageSize, page * pageSize - 1).order("created_at", { ascending: false });
@@ -5179,7 +5320,7 @@ function createSupabaseFinancialProvider() {
5179
5320
  const o = snakeToCamel(data);
5180
5321
  const meta = data.metadata ?? {};
5181
5322
  const balance = (await fetchInvoiceBalances(pub, [id])).get(id);
5182
- const { data: movs } = await pub.from("financial_movements").select("paid_amount").eq("invoice_id", id).eq("movement_kind", "bill");
5323
+ const { data: movs } = await pub.from(T.movements).select("paid_amount").eq("invoice_id", id).eq("movement_kind", "bill");
5183
5324
  return {
5184
5325
  id: o.id,
5185
5326
  direction: o.direction ?? (o.kind === "invoice_payable" ? "debit" : "credit"),
@@ -5266,7 +5407,7 @@ function createSupabaseFinancialProvider() {
5266
5407
  );
5267
5408
  }
5268
5409
  if (order && input.installments.length > 0) {
5269
- await pub.from("financial_movements").insert(
5410
+ await pub.from(T.movements).insert(
5270
5411
  input.installments.map((inst, i) => ({
5271
5412
  tenant_id: tenantId,
5272
5413
  invoice_id: order.id,
@@ -5308,12 +5449,12 @@ function createSupabaseFinancialProvider() {
5308
5449
  async cancelInvoice(id) {
5309
5450
  const { core, pub } = getClients();
5310
5451
  await core.from("orders").update({ status: "cancelled" }).eq("id", id);
5311
- await pub.from("financial_movements").update({ status: "cancelled" }).eq("invoice_id", id).eq("status", "pending");
5452
+ await pub.from(T.movements).update({ status: "cancelled" }).eq("invoice_id", id).eq("status", "pending");
5312
5453
  },
5313
5454
  // --- Movements (public.financial_movements) ---
5314
5455
  async getMovements(query) {
5315
5456
  const { pub } = getClients();
5316
- let qb = pub.from("financial_movements").select("*", { count: "exact" });
5457
+ let qb = pub.from(T.movements).select("*", { count: "exact" });
5317
5458
  if (query.direction) qb = qb.eq("direction", query.direction);
5318
5459
  if (query.status) {
5319
5460
  const statuses = Array.isArray(query.status) ? query.status : [query.status];
@@ -5330,7 +5471,7 @@ function createSupabaseFinancialProvider() {
5330
5471
  },
5331
5472
  async payMovement(input) {
5332
5473
  const { core, pub } = getClients();
5333
- const { data: mov } = await pub.from("financial_movements").select("*").eq("id", input.movementId).single();
5474
+ const { data: mov } = await pub.from(T.movements).select("*").eq("id", input.movementId).single();
5334
5475
  if (!mov) throw new Error("Movement not found");
5335
5476
  const tenantId = mov.tenant_id ?? getTenantId();
5336
5477
  const feePct = mov.direction === "credit" ? await getMethodFeePct(pub, input.paymentMethodId) : 0;
@@ -5355,17 +5496,17 @@ function createSupabaseFinancialProvider() {
5355
5496
  card_installments: input.cardInstallments ?? null,
5356
5497
  notes: input.notes ?? null
5357
5498
  };
5358
- let { data, error } = await pub.from("financial_movements").insert(paymentRow).select().single();
5499
+ let { data, error } = await pub.from(T.movements).insert(paymentRow).select().single();
5359
5500
  if (error && /fee_amount/i.test(error.message ?? "")) {
5360
5501
  const { fee_amount, ...rest } = paymentRow;
5361
- ({ data, error } = await pub.from("financial_movements").insert(rest).select().single());
5502
+ ({ data, error } = await pub.from(T.movements).insert(rest).select().single());
5362
5503
  }
5363
5504
  if (error) throw error;
5364
5505
  const newPaid = round22((mov.paid_amount ?? 0) + input.amount);
5365
5506
  const billStatus = newPaid >= mov.amount ? "paid" : "partial";
5366
- await pub.from("financial_movements").update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
5507
+ await pub.from(T.movements).update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
5367
5508
  if (mov.invoice_id) {
5368
- const { data: allMovs } = await pub.from("financial_movements").select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
5509
+ const { data: allMovs } = await pub.from(T.movements).select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
5369
5510
  const movs = allMovs ?? [];
5370
5511
  const allPaid = movs.length > 0 && movs.every((m) => m.status === "paid");
5371
5512
  const anyPaid = movs.some((m) => m.status === "paid" || m.status === "partial");
@@ -5382,7 +5523,7 @@ function createSupabaseFinancialProvider() {
5382
5523
  },
5383
5524
  async cancelMovement(id) {
5384
5525
  const { pub } = getClients();
5385
- await pub.from("financial_movements").update({ status: "cancelled" }).eq("id", id);
5526
+ await pub.from(T.movements).update({ status: "cancelled" }).eq("id", id);
5386
5527
  },
5387
5528
  // --- Transfers (between accounts) ---
5388
5529
  async createTransfer(input) {
@@ -5418,10 +5559,10 @@ function createSupabaseFinancialProvider() {
5418
5559
  metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
5419
5560
  }
5420
5561
  ];
5421
- let { data, error } = await pub.from("financial_movements").insert(rowsToInsert).select("id, direction");
5562
+ let { data, error } = await pub.from(T.movements).insert(rowsToInsert).select("id, direction");
5422
5563
  if (error && /fee_amount/i.test(error.message ?? "")) {
5423
5564
  const stripped = rowsToInsert.map(({ fee_amount, ...rest }) => rest);
5424
- ({ data, error } = await pub.from("financial_movements").insert(stripped).select("id, direction"));
5565
+ ({ data, error } = await pub.from(T.movements).insert(stripped).select("id, direction"));
5425
5566
  }
5426
5567
  if (error) throw error;
5427
5568
  const rows = data ?? [];
@@ -5432,13 +5573,13 @@ function createSupabaseFinancialProvider() {
5432
5573
  // --- Bank Accounts ---
5433
5574
  async getBankAccounts() {
5434
5575
  const { pub } = getClients();
5435
- const { data } = await pub.from("bank_accounts").select("*").eq("is_active", true).order("name");
5576
+ const { data } = await pub.from(T.bankAccounts).select("*").eq("is_active", true).order("name");
5436
5577
  return (data ?? []).map((r) => snakeToCamel(r));
5437
5578
  },
5438
5579
  async createBankAccount(input) {
5439
5580
  const { pub } = getClients();
5440
5581
  const tenantId = getTenantId();
5441
- const { data } = await pub.from("bank_accounts").insert({
5582
+ const { data } = await pub.from(T.bankAccounts).insert({
5442
5583
  ...camelToSnake(input),
5443
5584
  tenant_id: tenantId,
5444
5585
  current_balance: input.initialBalance ?? 0
@@ -5450,26 +5591,26 @@ function createSupabaseFinancialProvider() {
5450
5591
  const row = camelToSnake(partial);
5451
5592
  delete row.id;
5452
5593
  delete row.tenant_id;
5453
- const { data } = await pub.from("bank_accounts").update(row).eq("id", id).select().single();
5594
+ const { data } = await pub.from(T.bankAccounts).update(row).eq("id", id).select().single();
5454
5595
  return snakeToCamel(data);
5455
5596
  },
5456
5597
  // --- Cash Sessions ---
5457
5598
  async getCashSessions(bankAccountId) {
5458
5599
  const { pub } = getClients();
5459
- let qb = pub.from("cash_register_sessions").select("*");
5600
+ let qb = pub.from(T.cashRegisterSessions).select("*");
5460
5601
  if (bankAccountId) qb = qb.eq("bank_account_id", bankAccountId);
5461
5602
  const { data } = await qb.order("opened_at", { ascending: false });
5462
5603
  return (data ?? []).map((r) => snakeToCamel(r));
5463
5604
  },
5464
5605
  async getOpenSession(bankAccountId) {
5465
5606
  const { pub } = getClients();
5466
- const { data } = await pub.from("cash_register_sessions").select("*").eq("bank_account_id", bankAccountId).eq("status", "open").single();
5607
+ const { data } = await pub.from(T.cashRegisterSessions).select("*").eq("bank_account_id", bankAccountId).eq("status", "open").single();
5467
5608
  return data ? snakeToCamel(data) : null;
5468
5609
  },
5469
5610
  async openCashSession(input) {
5470
5611
  const { pub } = getClients();
5471
5612
  const tenantId = getTenantId();
5472
- const { data } = await pub.from("cash_register_sessions").insert({
5613
+ const { data } = await pub.from(T.cashRegisterSessions).insert({
5473
5614
  tenant_id: tenantId,
5474
5615
  bank_account_id: input.bankAccountId,
5475
5616
  status: "open",
@@ -5482,7 +5623,7 @@ function createSupabaseFinancialProvider() {
5482
5623
  },
5483
5624
  async closeCashSession(input) {
5484
5625
  const { pub } = getClients();
5485
- const { data } = await pub.from("cash_register_sessions").update({
5626
+ const { data } = await pub.from(T.cashRegisterSessions).update({
5486
5627
  status: "closed",
5487
5628
  closing_balance: input.closingBalance,
5488
5629
  closed_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5494,8 +5635,8 @@ function createSupabaseFinancialProvider() {
5494
5635
  },
5495
5636
  async getCashSessionSummary(sessionId) {
5496
5637
  const { pub } = getClients();
5497
- const session = (await pub.from("cash_register_sessions").select("*").eq("id", sessionId).single()).data;
5498
- const { data: movs } = await pub.from("financial_movements").select("direction, paid_amount").eq("cash_session_id", sessionId).eq("status", "paid");
5638
+ const session = (await pub.from(T.cashRegisterSessions).select("*").eq("id", sessionId).single()).data;
5639
+ const { data: movs } = await pub.from(T.movements).select("direction, paid_amount").eq("cash_session_id", sessionId).eq("status", "paid");
5499
5640
  const movements = movs ?? [];
5500
5641
  return {
5501
5642
  session: snakeToCamel(session),
@@ -5507,23 +5648,23 @@ function createSupabaseFinancialProvider() {
5507
5648
  // --- Payment Methods ---
5508
5649
  async getPaymentMethods() {
5509
5650
  const { pub } = getClients();
5510
- const { data } = await pub.from("payment_methods").select("*").eq("is_active", true).order("name");
5651
+ const { data } = await pub.from(T.paymentMethods).select("*").eq("is_active", true).order("name");
5511
5652
  return (data ?? []).map((r) => snakeToCamel(r));
5512
5653
  },
5513
5654
  async getPaymentMethodTypes() {
5514
5655
  const { pub } = getClients();
5515
- const { data } = await pub.from("payment_method_types").select("*").eq("is_active", true).order("name");
5656
+ const { data } = await pub.from(T.paymentMethodTypes).select("*").eq("is_active", true).order("name");
5516
5657
  return (data ?? []).map((r) => snakeToCamel(r));
5517
5658
  },
5518
5659
  // --- Chart of Accounts ---
5519
5660
  async getChartOfAccounts() {
5520
5661
  const { pub } = getClients();
5521
- const { data } = await pub.from("chart_of_accounts").select("*").eq("is_active", true).order("code");
5662
+ const { data } = await pub.from(T.chartOfAccounts).select("*").eq("is_active", true).order("code");
5522
5663
  return (data ?? []).map((r) => snakeToCamel(r));
5523
5664
  },
5524
5665
  async getCostCenters() {
5525
5666
  const { pub } = getClients();
5526
- const { data } = await pub.from("cost_centers").select("*").eq("is_active", true).order("code");
5667
+ const { data } = await pub.from(T.costCenters).select("*").eq("is_active", true).order("code");
5527
5668
  return (data ?? []).map((r) => snakeToCamel(r));
5528
5669
  },
5529
5670
  // --- Card Transactions ---
@@ -5535,7 +5676,7 @@ function createSupabaseFinancialProvider() {
5535
5676
  async getStatement(query) {
5536
5677
  const { core, pub } = getClients();
5537
5678
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5538
- const { data: acctRows } = await pub.from("bank_accounts").select("id, name, initial_balance, is_active");
5679
+ const { data: acctRows } = await pub.from(T.bankAccounts).select("id, name, initial_balance, is_active");
5539
5680
  const allAccounts = acctRows ?? [];
5540
5681
  const consolidated = !query.bankAccountId;
5541
5682
  const scopeAccounts = consolidated ? allAccounts.filter((a) => a.is_active) : allAccounts.filter((a) => a.id === query.bankAccountId);
@@ -5547,10 +5688,10 @@ function createSupabaseFinancialProvider() {
5547
5688
  }
5548
5689
  const scopeFilter = (qb) => consolidated ? qb : qb.in("bank_account_id", accountIds);
5549
5690
  const netOf = (r) => r.direction === "credit" ? (Number(r.paid_amount) || 0) - (Number(r.fee_amount) || 0) : -(Number(r.paid_amount) || 0);
5550
- const { data: priorRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
5691
+ const { data: priorRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
5551
5692
  const priorNet = (priorRows ?? []).reduce((s, r) => s + netOf(r), 0);
5552
5693
  const openingBalance = round22(initialSum + priorNet);
5553
- 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"));
5694
+ 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"));
5554
5695
  const invoiceIds = [...new Set((movs ?? []).map((r) => r.invoice_id).filter(Boolean))];
5555
5696
  const invoiceById = /* @__PURE__ */ new Map();
5556
5697
  if (invoiceIds.length) {
@@ -5592,7 +5733,7 @@ function createSupabaseFinancialProvider() {
5592
5733
  });
5593
5734
  const matchedIds = [...new Set(entries.map((e) => e.movement.matchedMovementId).filter(Boolean))];
5594
5735
  if (matchedIds.length) {
5595
- const { data: matched } = await pub.from("financial_movements").select("id, invoice_id, notes").in("id", matchedIds);
5736
+ const { data: matched } = await pub.from(T.movements).select("id, invoice_id, notes").in("id", matchedIds);
5596
5737
  const matchedById = new Map((matched ?? []).map((m) => [m.id, m]));
5597
5738
  const matchedInvoiceIds = [...new Set((matched ?? []).map((m) => m.invoice_id).filter(Boolean))];
5598
5739
  const invById = /* @__PURE__ */ new Map();
@@ -5614,7 +5755,7 @@ function createSupabaseFinancialProvider() {
5614
5755
  };
5615
5756
  }
5616
5757
  }
5617
- const { data: todayRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lte("payment_date", today2));
5758
+ const { data: todayRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lte("payment_date", today2));
5618
5759
  const accountCurrentBalance = round22(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
5619
5760
  return {
5620
5761
  entries,
@@ -5632,9 +5773,9 @@ function createSupabaseFinancialProvider() {
5632
5773
  async getSummary(dateRange) {
5633
5774
  sweepOverdue();
5634
5775
  const { pub } = getClients();
5635
- const { data: accounts } = await pub.from("bank_accounts").select("current_balance").eq("is_active", true);
5776
+ const { data: accounts } = await pub.from(T.bankAccounts).select("current_balance").eq("is_active", true);
5636
5777
  const totalBalance = (accounts ?? []).reduce((s, a) => s + (a.current_balance ?? 0), 0);
5637
- const { data: movs } = await pub.from("financial_movements").select("direction, movement_kind, amount, paid_amount, status, due_date, payment_date");
5778
+ const { data: movs } = await pub.from(T.movements).select("direction, movement_kind, amount, paid_amount, status, due_date, payment_date");
5638
5779
  const allMovs = movs ?? [];
5639
5780
  const unpaid = allMovs.filter((m) => ["pending", "partial", "overdue"].includes(m.status));
5640
5781
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -5667,7 +5808,7 @@ function createSupabaseFinancialProvider() {
5667
5808
  const tenantId = getTenantId();
5668
5809
  let imported = 0, duplicates = 0;
5669
5810
  for (const line of input.lines) {
5670
- const { error } = await pub.from("financial_movements").insert({
5811
+ const { error } = await pub.from(T.movements).insert({
5671
5812
  tenant_id: tenantId,
5672
5813
  direction: line.direction,
5673
5814
  movement_kind: "payment",
@@ -5690,7 +5831,7 @@ function createSupabaseFinancialProvider() {
5690
5831
  },
5691
5832
  async getUnreconciled(query) {
5692
5833
  const { pub } = getClients();
5693
- let qb = pub.from("financial_movements").select("*").not("external_source", "is", null).is("reconciled_at", null);
5834
+ let qb = pub.from(T.movements).select("*").not("external_source", "is", null).is("reconciled_at", null);
5694
5835
  if (query?.bankAccountId) qb = qb.eq("bank_account_id", query.bankAccountId);
5695
5836
  if (query?.dateRange) qb = qb.gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to);
5696
5837
  const { data } = await qb.order("payment_date", { ascending: false });
@@ -5698,12 +5839,12 @@ function createSupabaseFinancialProvider() {
5698
5839
  },
5699
5840
  async suggestReconciliation(bankMovementId) {
5700
5841
  const { core, pub } = getClients();
5701
- const { data: line } = await pub.from("financial_movements").select("*").eq("id", bankMovementId).single();
5842
+ const { data: line } = await pub.from(T.movements).select("*").eq("id", bankMovementId).single();
5702
5843
  if (!line) return [];
5703
5844
  const amount = Number(line.amount) || 0;
5704
5845
  const lineDate = line.payment_date ?? line.due_date;
5705
5846
  const tol = Math.max(0.01, round22(amount * 0.02));
5706
- 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);
5847
+ 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);
5707
5848
  const candidates = (data ?? []).map((r) => {
5708
5849
  const amtDiff = Math.abs((Number(r.amount) || 0) - amount);
5709
5850
  const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
@@ -5729,20 +5870,20 @@ function createSupabaseFinancialProvider() {
5729
5870
  async reconcileMovement(input) {
5730
5871
  const { pub } = getClients();
5731
5872
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
5732
- await pub.from("financial_movements").update({
5873
+ await pub.from(T.movements).update({
5733
5874
  reconciled_at: now2,
5734
5875
  matched_movement_id: input.matchedMovementId ?? null
5735
5876
  }).eq("id", input.bankMovementId);
5736
5877
  if (input.matchedMovementId) {
5737
- await pub.from("financial_movements").update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
5878
+ await pub.from(T.movements).update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
5738
5879
  }
5739
5880
  },
5740
5881
  async unreconcileMovement(bankMovementId) {
5741
5882
  const { pub } = getClients();
5742
- const { data: line } = await pub.from("financial_movements").select("matched_movement_id").eq("id", bankMovementId).single();
5743
- await pub.from("financial_movements").update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
5883
+ const { data: line } = await pub.from(T.movements).select("matched_movement_id").eq("id", bankMovementId).single();
5884
+ await pub.from(T.movements).update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
5744
5885
  if (line?.matched_movement_id) {
5745
- await pub.from("financial_movements").update({ reconciled_at: null }).eq("id", line.matched_movement_id);
5886
+ await pub.from(T.movements).update({ reconciled_at: null }).eq("id", line.matched_movement_id);
5746
5887
  }
5747
5888
  }
5748
5889
  };
@@ -5990,7 +6131,7 @@ var paymentMethodTypeEntity = {
5990
6131
  { key: "transactionType", label: "Transaction Type", type: "select", options: ["cash", "credit_card", "debit_card", "pix", "bank_transfer", "check", "voucher"], showInTable: true, required: true },
5991
6132
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
5992
6133
  ],
5993
- data: { table: "payment_method_types", tenantScoped: true }
6134
+ data: { table: T.paymentMethodTypes, tenantScoped: true }
5994
6135
  };
5995
6136
  var paymentMethodEntity = {
5996
6137
  name: "Payment Method",
@@ -6002,14 +6143,14 @@ var paymentMethodEntity = {
6002
6143
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
6003
6144
  // FK to the seeded payment_method_types catalog — load real ids at runtime
6004
6145
  // (a static enum select would write the string into the uuid column).
6005
- { key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: "payment_method_types", labelField: "name" }, showInTable: true, required: true },
6146
+ { key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: T.paymentMethodTypes, labelField: "name" }, showInTable: true, required: true },
6006
6147
  { key: "discountValue", label: "Discount %", type: "number", showInTable: true },
6007
6148
  { key: "interestValue", label: "Interest %", type: "number", showInTable: true },
6008
6149
  { key: "minInstallments", label: "Min Installments", type: "number", defaultValue: 1 },
6009
6150
  { key: "maxInstallments", label: "Max Installments", type: "number", defaultValue: 1, showInTable: true },
6010
6151
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6011
6152
  ],
6012
- data: { table: "payment_methods", tenantScoped: true }
6153
+ data: { table: T.paymentMethods, tenantScoped: true }
6013
6154
  };
6014
6155
  var chartOfAccountsEntity = {
6015
6156
  name: "Ledger Account",
@@ -6024,7 +6165,7 @@ var chartOfAccountsEntity = {
6024
6165
  { key: "parentId", label: "Parent", type: "text", showInTable: false },
6025
6166
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6026
6167
  ],
6027
- data: { table: "chart_of_accounts", tenantScoped: true }
6168
+ data: { table: T.chartOfAccounts, tenantScoped: true }
6028
6169
  };
6029
6170
  var costCenterEntity = {
6030
6171
  name: "Cost Center",
@@ -6037,7 +6178,7 @@ var costCenterEntity = {
6037
6178
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
6038
6179
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6039
6180
  ],
6040
- data: { table: "cost_centers", tenantScoped: true }
6181
+ data: { table: T.costCenters, tenantScoped: true }
6041
6182
  };
6042
6183
  var bankAccountEntity = {
6043
6184
  name: "Bank Account",
@@ -6062,7 +6203,7 @@ var bankAccountEntity = {
6062
6203
  { key: "dueDay", label: "Due Day", type: "number" },
6063
6204
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6064
6205
  ],
6065
- data: { table: "bank_accounts", tenantScoped: true }
6206
+ data: { table: T.bankAccounts, tenantScoped: true }
6066
6207
  };
6067
6208
  var cardBrandEntity = {
6068
6209
  name: "Card Brand",
@@ -6074,7 +6215,7 @@ var cardBrandEntity = {
6074
6215
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
6075
6216
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
6076
6217
  ],
6077
- data: { table: "card_brands", tenantScoped: true }
6218
+ data: { table: T.cardBrands, tenantScoped: true }
6078
6219
  };
6079
6220
  var commissionRuleEntity = {
6080
6221
  name: "Commission Rule",
@@ -6379,6 +6520,16 @@ var en = {
6379
6520
  "financial.invoice.filterAllAccounts": "All accounts",
6380
6521
  "financial.invoice.filterCostCenter": "Cost center",
6381
6522
  "financial.invoice.filterAllCostCenters": "All cost centers",
6523
+ "financial.invoice.filterPeriod": "Period",
6524
+ "financial.invoice.filterAllPeriods": "All time",
6525
+ "financial.invoice.filterThisMonth": "This month",
6526
+ "financial.invoice.filterLastMonth": "Last month",
6527
+ "financial.invoice.filterLast30": "Last 30 days",
6528
+ "financial.invoice.filterThisYear": "This year",
6529
+ "financial.invoice.filterCustom": "Custom",
6530
+ "financial.invoice.filterFrom": "From",
6531
+ "financial.invoice.filterTo": "To",
6532
+ "financial.invoice.filterClear": "Clear",
6382
6533
  "financial.reconciliation.title": "Reconciliation",
6383
6534
  "financial.reconciliation.subtitle": "Match imported bank lines to your movements",
6384
6535
  "financial.reconciliation.pending": "{{count}} to reconcile",
@@ -6862,6 +7013,16 @@ var ptBR = {
6862
7013
  "financial.invoice.filterAllAccounts": "Todos os planos",
6863
7014
  "financial.invoice.filterCostCenter": "Centro de custo",
6864
7015
  "financial.invoice.filterAllCostCenters": "Todos os centros",
7016
+ "financial.invoice.filterPeriod": "Per\xEDodo",
7017
+ "financial.invoice.filterAllPeriods": "Todo o per\xEDodo",
7018
+ "financial.invoice.filterThisMonth": "Este m\xEAs",
7019
+ "financial.invoice.filterLastMonth": "M\xEAs passado",
7020
+ "financial.invoice.filterLast30": "\xDAltimos 30 dias",
7021
+ "financial.invoice.filterThisYear": "Este ano",
7022
+ "financial.invoice.filterCustom": "Personalizado",
7023
+ "financial.invoice.filterFrom": "De",
7024
+ "financial.invoice.filterTo": "At\xE9",
7025
+ "financial.invoice.filterClear": "Limpar",
6865
7026
  "financial.reconciliation.title": "Concilia\xE7\xE3o",
6866
7027
  "financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
6867
7028
  "financial.reconciliation.pending": "{{count}} para conciliar",
@@ -7331,6 +7492,15 @@ var DEFAULT_LABELS = {
7331
7492
  cardsReconciliation: "Reconciliation"
7332
7493
  };
7333
7494
  var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
7495
+ var FINANCIAL_NAV_FEATURES = {
7496
+ reconciliation: "fin_reconciliation",
7497
+ cards: "fin_cards",
7498
+ commissions: "fin_commissions",
7499
+ statements: "fin_statements",
7500
+ receivables: "fin_receivables",
7501
+ payables: "fin_payables",
7502
+ "cash-registers": "fin_cash_registers"
7503
+ };
7334
7504
  var DEFAULT_ITEM_TYPES = [
7335
7505
  { value: "service", label: "Service", icon: "Briefcase" },
7336
7506
  { value: "product", label: "Product", icon: "Package" },
@@ -7361,7 +7531,8 @@ function resolveConfig(options) {
7361
7531
  locations: options?.locations ?? [],
7362
7532
  entityLookups: options?.entityLookups ?? {},
7363
7533
  contactLookup: options?.contactLookup,
7364
- onBookingClick: options?.onBookingClick
7534
+ onBookingClick: options?.onBookingClick,
7535
+ navFeatureMap: { ...options?.navFeatureMap }
7365
7536
  };
7366
7537
  }
7367
7538
  function createFinancialPlugin(options) {
@@ -7389,6 +7560,70 @@ function createFinancialPlugin(options) {
7389
7560
  ...config.modules.payables ? [{ id: "financial.payables", label: config.labels.payables, group: config.labels.pageTitle }] : [],
7390
7561
  ...config.modules.commissions ? [{ id: "financial.commissions", label: config.labels.commissions, group: config.labels.pageTitle }] : []
7391
7562
  ],
7563
+ // Recurring monthly quota — counts financial movement rows created this
7564
+ // month (invoices write one per installment; quick transactions one, or two
7565
+ // for a transfer).
7566
+ declaredLimits: [
7567
+ { key: "movements_month", label: "Financial movements this month", table: "plg_financial_movements", period: "month" }
7568
+ ],
7569
+ queryEntities: [
7570
+ {
7571
+ key: "financial:invoices",
7572
+ entity: {
7573
+ name: "Invoice",
7574
+ namePlural: "Invoices (receipts REC- / payables PAG-)",
7575
+ icon: "Receipt",
7576
+ permission: { feature: "financial", action: "read" },
7577
+ fields: [
7578
+ { key: "referenceNumber", label: "Reference (REC-/PAG-)", type: "text", searchable: true },
7579
+ { key: "flow", label: "Flow (receivable/payable)", type: "text" },
7580
+ { key: "amount", label: "Amount", type: "number" },
7581
+ { key: "paid", label: "Paid", type: "number" },
7582
+ { key: "balance", label: "Balance", type: "number" },
7583
+ { key: "status", label: "Status", type: "text" },
7584
+ { key: "createdAt", label: "Created at", type: "text" }
7585
+ ],
7586
+ data: {
7587
+ table: "v_invoice_balances",
7588
+ tenantScoped: true,
7589
+ searchColumns: ["reference_number"]
7590
+ }
7591
+ }
7592
+ },
7593
+ {
7594
+ key: "financial:movements",
7595
+ entity: {
7596
+ name: "Financial movement",
7597
+ namePlural: "Financial movements (installment ledger)",
7598
+ icon: "Banknote",
7599
+ permission: { feature: "financial", action: "read" },
7600
+ fields: [
7601
+ { key: "direction", label: "Direction", type: "text" },
7602
+ { key: "movementKind", label: "Kind (bill/payment)", type: "text" },
7603
+ { key: "amount", label: "Amount", type: "number" },
7604
+ { key: "paidAmount", label: "Paid amount", type: "number" },
7605
+ { key: "status", label: "Status", type: "text" },
7606
+ { key: "dueDate", label: "Due date", type: "text" },
7607
+ { key: "paymentDate", label: "Payment date", type: "text" },
7608
+ { key: "installmentNumber", label: "Installment", type: "number" },
7609
+ { key: "cardInstallments", label: "Card installments", type: "number" },
7610
+ { key: "createdAt", label: "Created at", type: "text" }
7611
+ ],
7612
+ data: {
7613
+ table: "plg_financial_movements",
7614
+ tenantScoped: true
7615
+ }
7616
+ }
7617
+ }
7618
+ ],
7619
+ declaredRpcs: [
7620
+ {
7621
+ name: "agent_financial_mark_payment_received",
7622
+ kind: "write",
7623
+ 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.",
7624
+ audits: true
7625
+ }
7626
+ ],
7392
7627
  navigation: [
7393
7628
  {
7394
7629
  section: options?.navSection ?? "main",
@@ -7442,6 +7677,7 @@ function createFinancialPlugin(options) {
7442
7677
  description: "Creates a new invoice/payable for a contact.",
7443
7678
  icon: "FileText",
7444
7679
  mode: "persist",
7680
+ limitKey: "movements_month",
7445
7681
  category: "Finance",
7446
7682
  parameters: {
7447
7683
  type: "object",
@@ -7457,6 +7693,33 @@ function createFinancialPlugin(options) {
7457
7693
  ],
7458
7694
  permission: { feature: "financial", action: "create" }
7459
7695
  },
7696
+ {
7697
+ id: "financial.mark-payment-received",
7698
+ name: "markPaymentReceived",
7699
+ 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.',
7700
+ icon: "BadgeDollarSign",
7701
+ mode: "persist",
7702
+ execution: { plane: "server", kind: "rpc", rpc: "agent_financial_mark_payment_received" },
7703
+ category: "Finance",
7704
+ parameters: {
7705
+ type: "object",
7706
+ properties: {
7707
+ booking_id: { type: "string", description: "Appointment id (alternative to order_id)" },
7708
+ order_id: { type: "string", description: "Order id" },
7709
+ method: {
7710
+ type: "string",
7711
+ description: "Payment method",
7712
+ enum: ["cash", "pix", "credit_card", "debit_card", "transfer"]
7713
+ },
7714
+ installments: { type: "number", description: 'Credit-card installments (1-12), e.g. 2 for "cart\xE3o 2x"' }
7715
+ },
7716
+ required: ["method"]
7717
+ },
7718
+ suggestions: [
7719
+ { label: "Marcar o atendimento como pago no cart\xE3o em 2x" }
7720
+ ],
7721
+ permission: { feature: "financial.receivables", action: "update" }
7722
+ },
7460
7723
  {
7461
7724
  id: "financial.list-payables",
7462
7725
  name: "listPayables",
@@ -7504,6 +7767,6 @@ function createFinancialPlugin(options) {
7504
7767
  };
7505
7768
  }
7506
7769
 
7507
- export { createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider, openQuickAdd };
7770
+ export { FINANCIAL_NAV_FEATURES, createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider, openQuickAdd };
7508
7771
  //# sourceMappingURL=index.js.map
7509
7772
  //# sourceMappingURL=index.js.map