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