@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.
- package/README.md +2 -0
- package/dist/FinancialContext.d.ts +6 -0
- package/dist/FinancialContext.d.ts.map +1 -1
- package/dist/FinancialPage.d.ts.map +1 -1
- package/dist/components/FinancialGeneralSettings.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +11 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/index.cjs +345 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +347 -84
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +18 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/registries.d.ts.map +1 -1
- package/dist/views/CardsView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/QuickTransactionForm.d.ts.map +1 -1
- package/package.json +10 -4
- package/src/FinancialContext.tsx +6 -0
- package/src/FinancialPage.tsx +56 -12
- package/src/README.md +5 -5
- package/src/components/FinancialGeneralSettings.tsx +21 -8
- package/src/contract.test.ts +1 -1
- package/src/data/supabase.ts +56 -48
- package/src/data/tables.ts +12 -0
- package/src/index.ts +128 -0
- package/src/locales/en.ts +10 -0
- package/src/locales/pt-BR.ts +10 -0
- package/src/migrations/000_plg_rename.sql +30 -0
- package/src/migrations/001_financial_base.sql +30 -30
- package/src/migrations/002_chart_of_accounts.sql +10 -10
- package/src/migrations/003_card_brands.sql +4 -4
- package/src/migrations/004_order_to_cash.sql +43 -29
- package/src/migrations/005_seed_defaults.sql +6 -6
- package/src/migrations/006_rls_policies.sql +5 -5
- package/src/migrations/{006_extract_fee_amount.sql → 006b_extract_fee_amount.sql} +4 -4
- package/src/migrations/007_reconciliation.sql +9 -9
- package/src/migrations/007b_movement_payment_method_type.sql +6 -0
- package/src/migrations/008_split_payment_movements.sql +5 -5
- package/src/migrations/009_agent_rpcs.sql +165 -0
- package/src/migrations/010_invoice_internal.sql +133 -0
- package/src/migrations/index.ts +862 -0
- package/src/registries.ts +8 -7
- package/src/views/CardsView.tsx +12 -9
- package/src/views/InvoiceFormView.tsx +10 -0
- package/src/views/InvoiceListView.tsx +98 -9
- 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,
|
|
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
|
-
|
|
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:
|
|
835
|
-
onEmptyAction:
|
|
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:
|
|
3675
|
-
|
|
3676
|
-
/* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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)
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
5498
|
-
const { data: movs } = await pub.from(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
5743
|
-
await pub.from(
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|