@fayz-ai/plugin-financial 0.1.7 → 0.8.0
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 +2 -0
- package/dist/FinancialContext.d.ts.map +1 -1
- package/dist/FinancialPage.d.ts.map +1 -1
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/mock.d.ts +15 -0
- package/dist/data/mock.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/data/types.d.ts +2 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.cjs +1594 -252
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1456 -131
- 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 +16 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/quick-add.d.ts +13 -0
- package/dist/quick-add.d.ts.map +1 -0
- package/dist/registries.d.ts.map +1 -1
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types.d.ts +42 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/CardsView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/QuickTransactionForm.d.ts +16 -0
- package/dist/views/QuickTransactionForm.d.ts.map +1 -0
- package/dist/views/SummaryView.d.ts +5 -4
- package/dist/views/SummaryView.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/FinancialContext.tsx +2 -0
- package/src/FinancialPage.tsx +86 -7
- package/src/README.md +5 -5
- package/src/contract.test.ts +1 -1
- package/src/data/capability.test.ts +45 -0
- package/src/data/index.ts +1 -0
- package/src/data/mock.ts +50 -10
- package/src/data/supabase.ts +95 -49
- package/src/data/tables.ts +12 -0
- package/src/data/types.ts +2 -1
- package/src/index.ts +24 -4
- package/src/locales/en.ts +75 -0
- package/src/locales/pt-BR.ts +75 -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 +28 -28
- 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/index.ts +544 -0
- package/src/quick-add.ts +58 -0
- package/src/registries.ts +8 -7
- package/src/store.ts +88 -0
- package/src/types.ts +45 -0
- package/src/views/CardsView.tsx +186 -50
- package/src/views/InvoiceListView.tsx +60 -3
- package/src/views/QuickTransactionForm.tsx +537 -0
- package/src/views/SummaryView.tsx +197 -6
- package/src/views/dashboardWidgets.tsx +251 -5
package/dist/index.cjs
CHANGED
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React17 = require('react');
|
|
4
4
|
var core = require('@fayz-ai/core');
|
|
5
5
|
var ui = require('@fayz-ai/ui');
|
|
6
6
|
var saas = require('@fayz-ai/saas');
|
|
7
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
8
7
|
var lucideReact = require('lucide-react');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
9
|
var reactDom = require('react-dom');
|
|
10
10
|
var vanilla = require('zustand/vanilla');
|
|
11
11
|
|
|
12
|
-
function
|
|
12
|
+
function _interopNamespace(e) {
|
|
13
|
+
if (e && e.__esModule) return e;
|
|
14
|
+
var n = Object.create(null);
|
|
15
|
+
if (e) {
|
|
16
|
+
Object.keys(e).forEach(function (k) {
|
|
17
|
+
if (k !== 'default') {
|
|
18
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return e[k]; }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
n.default = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
13
29
|
|
|
14
|
-
var
|
|
30
|
+
var React17__namespace = /*#__PURE__*/_interopNamespace(React17);
|
|
15
31
|
|
|
16
32
|
// src/index.ts
|
|
17
33
|
var ctx = saas.createPluginContext("FinancialPage");
|
|
@@ -20,7 +36,628 @@ var useFinancialConfig = ctx.useConfig;
|
|
|
20
36
|
var useFinancialProvider = ctx.useProvider;
|
|
21
37
|
var useFinancialStore = ctx.useStore;
|
|
22
38
|
function SummaryView() {
|
|
23
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
39
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
40
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsxRuntime.jsx(MobileSummary, {}) }),
|
|
41
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
42
|
+
ui.DashboardCanvas,
|
|
43
|
+
{
|
|
44
|
+
surface: "plugin-home",
|
|
45
|
+
domain: "financial",
|
|
46
|
+
showHeader: false,
|
|
47
|
+
className: "hidden space-y-6 md:block"
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
] });
|
|
51
|
+
}
|
|
52
|
+
function categoryIcon(label) {
|
|
53
|
+
const s = label.toLowerCase();
|
|
54
|
+
if (/(mercado|super|padaria|extra|p[aã]o|hortifr)/.test(s)) return lucideReact.ShoppingCart;
|
|
55
|
+
if (/(uber|99|posto|shell|gasol|combust|carro)/.test(s)) return lucideReact.Car;
|
|
56
|
+
if (/(aluguel|casa|condom|im[oó]vel)/.test(s)) return lucideReact.Home;
|
|
57
|
+
if (/(ifood|restaurante|outback|lanche|food|bar\b)/.test(s)) return lucideReact.Utensils;
|
|
58
|
+
if (/(luz|energia|enel|internet|vivo|conta de|[aá]gua)/.test(s)) return lucideReact.Zap;
|
|
59
|
+
if (/(cinema|netflix|cinemark|filme|disney|prime)/.test(s)) return lucideReact.Film;
|
|
60
|
+
if (/(farm|drogasil|sa[uú]de|plano|hospital|m[eé]dic)/.test(s)) return lucideReact.HeartPulse;
|
|
61
|
+
if (/(academia|smartfit|gym|treino)/.test(s)) return lucideReact.Dumbbell;
|
|
62
|
+
if (/(spotify|music|deezer)/.test(s)) return lucideReact.Music;
|
|
63
|
+
if (/(amazon|shopping|loja|magaz|americanas|mercado livre)/.test(s)) return lucideReact.ShoppingBag;
|
|
64
|
+
if (/(fatura|cart[aã]o|nubank|banco|boleto)/.test(s)) return lucideReact.Landmark;
|
|
65
|
+
if (/(sal[aá]rio|freel|pix|reembolso|receb|renda|d[ií]vidend)/.test(s)) return lucideReact.Wallet;
|
|
66
|
+
return lucideReact.CircleDollarSign;
|
|
67
|
+
}
|
|
68
|
+
function useDateLabel() {
|
|
69
|
+
const t2 = core.useTranslation();
|
|
70
|
+
const { currency } = useFinancialConfig();
|
|
71
|
+
return React17.useMemo(() => {
|
|
72
|
+
const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
73
|
+
const y = /* @__PURE__ */ new Date();
|
|
74
|
+
y.setDate(y.getDate() - 1);
|
|
75
|
+
const yesterdayStr = y.toISOString().slice(0, 10);
|
|
76
|
+
return (dateStr) => {
|
|
77
|
+
if (dateStr === todayStr) return t2("financial.summary.today");
|
|
78
|
+
if (dateStr === yesterdayStr) return t2("financial.summary.yesterday");
|
|
79
|
+
try {
|
|
80
|
+
return (/* @__PURE__ */ new Date(`${dateStr}T00:00:00`)).toLocaleDateString(currency.locale, {
|
|
81
|
+
weekday: "short",
|
|
82
|
+
day: "2-digit",
|
|
83
|
+
month: "short"
|
|
84
|
+
});
|
|
85
|
+
} catch {
|
|
86
|
+
return dateStr;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}, [t2, currency.locale]);
|
|
90
|
+
}
|
|
91
|
+
function MobileSummary() {
|
|
92
|
+
const t2 = core.useTranslation();
|
|
93
|
+
const { currency } = useFinancialConfig();
|
|
94
|
+
const summary = useFinancialStore((s) => s.summary);
|
|
95
|
+
const statement = useFinancialStore((s) => s.statement);
|
|
96
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
97
|
+
const fetchSummary = useFinancialStore((s) => s.fetchSummary);
|
|
98
|
+
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
99
|
+
const fetchStatement = useFinancialStore((s) => s.fetchStatement);
|
|
100
|
+
const dateLabel = useDateLabel();
|
|
101
|
+
React17.useEffect(() => {
|
|
102
|
+
void fetchSummary();
|
|
103
|
+
void fetchBankAccounts();
|
|
104
|
+
const to = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
105
|
+
const from = /* @__PURE__ */ new Date();
|
|
106
|
+
from.setDate(from.getDate() - 90);
|
|
107
|
+
void fetchStatement({ dateRange: { from: from.toISOString().slice(0, 10), to } });
|
|
108
|
+
}, []);
|
|
109
|
+
const fmt = (n) => saas.formatCurrency(n, currency);
|
|
110
|
+
const inflow = summary?.monthlyInflow ?? 0;
|
|
111
|
+
const outflow = summary?.monthlyOutflow ?? 0;
|
|
112
|
+
const accountName = React17.useMemo(() => {
|
|
113
|
+
const map = new Map(bankAccounts.map((a) => [a.id, a.name]));
|
|
114
|
+
return (id) => id ? map.get(id) ?? "" : "";
|
|
115
|
+
}, [bankAccounts]);
|
|
116
|
+
const groups = React17.useMemo(() => {
|
|
117
|
+
const entries = (statement?.entries ?? []).filter((e) => e.entryKind === "movement");
|
|
118
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const e of entries) {
|
|
120
|
+
const d = e.movement.paymentDate ?? e.movement.dueDate;
|
|
121
|
+
const bucket = byDate.get(d);
|
|
122
|
+
if (bucket) bucket.push(e);
|
|
123
|
+
else byDate.set(d, [e]);
|
|
124
|
+
}
|
|
125
|
+
return [...byDate.entries()].sort((a, b) => b[0].localeCompare(a[0]));
|
|
126
|
+
}, [statement]);
|
|
127
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
128
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
129
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-2 rounded-xl border bg-card p-4", children: [
|
|
130
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.summary.totalBalance") }),
|
|
131
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-2xl font-semibold", children: fmt(summary?.totalBalance ?? 0) })
|
|
132
|
+
] }),
|
|
133
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border bg-card p-3", children: [
|
|
134
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.summary.income") }),
|
|
135
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-base font-semibold text-success", children: fmt(inflow) })
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border bg-card p-3", children: [
|
|
138
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.summary.expenses") }),
|
|
139
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-base font-semibold text-destructive", children: fmt(outflow) })
|
|
140
|
+
] })
|
|
141
|
+
] }),
|
|
142
|
+
groups.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border border-dashed py-12 text-center text-sm text-muted-foreground", children: t2("financial.summary.noTransactions") }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: groups.map(([date, rows]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
143
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: dateLabel(date) }),
|
|
144
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y overflow-hidden rounded-xl border bg-card", children: rows.map((e) => {
|
|
145
|
+
const isCredit = e.movement.direction === "credit";
|
|
146
|
+
const title = e.invoice?.contactName || e.movement.notes || t2("financial.summary.transactionsTitle");
|
|
147
|
+
const Icon = categoryIcon(title);
|
|
148
|
+
const category = isCredit ? t2("financial.summary.income") : t2("financial.summary.expenses");
|
|
149
|
+
const acct = accountName(e.movement.bankAccountId);
|
|
150
|
+
const subtitle = acct ? `${category} \xB7 ${acct}` : category;
|
|
151
|
+
const receiptUrl = e.invoice?.metadata?.receiptUrl;
|
|
152
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-3 py-2.5", children: [
|
|
153
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
154
|
+
"span",
|
|
155
|
+
{
|
|
156
|
+
className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full ${isCredit ? "bg-success-soft text-success-soft-foreground" : "bg-destructive-soft text-destructive-soft-foreground"}`,
|
|
157
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" })
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
161
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 truncate text-sm font-medium", children: [
|
|
162
|
+
title,
|
|
163
|
+
receiptUrl && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground", "aria-label": t2("financial.quickTx.receiptAttached") })
|
|
164
|
+
] }),
|
|
165
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-muted-foreground", children: subtitle })
|
|
166
|
+
] }),
|
|
167
|
+
receiptUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
168
|
+
"img",
|
|
169
|
+
{
|
|
170
|
+
src: receiptUrl,
|
|
171
|
+
alt: t2("financial.quickTx.receiptAttached"),
|
|
172
|
+
className: "h-9 w-9 shrink-0 rounded-md border object-cover"
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: `shrink-0 text-sm font-semibold ${isCredit ? "text-success" : "text-destructive"}`, children: [
|
|
176
|
+
isCredit ? "+" : "-",
|
|
177
|
+
fmt(e.net)
|
|
178
|
+
] })
|
|
179
|
+
] }, e.movement.id);
|
|
180
|
+
}) })
|
|
181
|
+
] }, date)) })
|
|
182
|
+
] });
|
|
183
|
+
}
|
|
184
|
+
function typeTheme(type) {
|
|
185
|
+
switch (type) {
|
|
186
|
+
case "income":
|
|
187
|
+
return { header: "bg-success text-success-foreground", ring: "ring-success", icon: lucideReact.ArrowDownCircle };
|
|
188
|
+
case "transfer":
|
|
189
|
+
return { header: "bg-muted text-foreground", ring: "ring-ring", icon: lucideReact.ArrowLeftRight };
|
|
190
|
+
case "expense":
|
|
191
|
+
default:
|
|
192
|
+
return { header: "bg-destructive text-destructive-foreground", ring: "ring-destructive", icon: lucideReact.ArrowUpCircle };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function ChipSelect({ icon: Icon, label, value, options, onChange, emptyLabel }) {
|
|
196
|
+
const [open, setOpen] = React17.useState(false);
|
|
197
|
+
const ref = React17.useRef(null);
|
|
198
|
+
React17.useEffect(() => {
|
|
199
|
+
if (!open) return;
|
|
200
|
+
function onDoc(e) {
|
|
201
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
202
|
+
}
|
|
203
|
+
document.addEventListener("mousedown", onDoc);
|
|
204
|
+
return () => document.removeEventListener("mousedown", onDoc);
|
|
205
|
+
}, [open]);
|
|
206
|
+
const selected = options.find((o) => o.id === value);
|
|
207
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: "relative", children: [
|
|
208
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
|
|
209
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
210
|
+
"button",
|
|
211
|
+
{
|
|
212
|
+
type: "button",
|
|
213
|
+
onClick: () => setOpen((v) => !v),
|
|
214
|
+
className: "mt-1 flex w-full items-center gap-2 rounded-input border border-input bg-card px-3 py-2.5 text-left text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] transition-colors hover:bg-muted/30",
|
|
215
|
+
children: [
|
|
216
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
217
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `flex-1 truncate ${selected ? "" : "text-muted-foreground"}`, children: selected?.name ?? emptyLabel }),
|
|
218
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
open && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fayz-glass-surface absolute bottom-full left-0 right-0 z-50 mb-1 max-h-56 overflow-auto rounded-lg border bg-popover py-1 shadow-lg", children: options.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: emptyLabel }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
223
|
+
value != null && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
224
|
+
"button",
|
|
225
|
+
{
|
|
226
|
+
onClick: () => {
|
|
227
|
+
onChange(void 0);
|
|
228
|
+
setOpen(false);
|
|
229
|
+
},
|
|
230
|
+
className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-xs text-muted-foreground hover:bg-muted/50",
|
|
231
|
+
children: [
|
|
232
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5" }),
|
|
233
|
+
" ",
|
|
234
|
+
emptyLabel
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
),
|
|
238
|
+
options.map((o) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
239
|
+
"button",
|
|
240
|
+
{
|
|
241
|
+
onClick: () => {
|
|
242
|
+
onChange(o.id);
|
|
243
|
+
setOpen(false);
|
|
244
|
+
},
|
|
245
|
+
className: `flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm hover:bg-muted/50 ${o.id === value ? "bg-muted/30" : ""}`,
|
|
246
|
+
children: [
|
|
247
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.name }),
|
|
248
|
+
o.hint && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: o.hint }),
|
|
249
|
+
o.id === value && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5 text-primary" })
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
o.id
|
|
253
|
+
))
|
|
254
|
+
] }) })
|
|
255
|
+
] });
|
|
256
|
+
}
|
|
257
|
+
function TogglePill({ icon: Icon, label, checked, onChange, accent }) {
|
|
258
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
259
|
+
"button",
|
|
260
|
+
{
|
|
261
|
+
type: "button",
|
|
262
|
+
onClick: () => onChange(!checked),
|
|
263
|
+
className: `flex items-center gap-2 rounded-full border px-3 py-2 text-xs font-medium transition-colors ${checked ? `border-transparent ${accent ?? "bg-primary text-primary-foreground"}` : "border-input bg-card text-muted-foreground hover:bg-muted/40"}`,
|
|
264
|
+
children: [
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5" }),
|
|
266
|
+
label
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", defaultAccountId, focusReceipt = false }) {
|
|
272
|
+
const t2 = core.useTranslation();
|
|
273
|
+
const { currency } = useFinancialConfig();
|
|
274
|
+
const provider = useFinancialProvider();
|
|
275
|
+
const createQuickTransaction = useFinancialStore((s) => s.createQuickTransaction);
|
|
276
|
+
const [type, setType] = React17.useState(defaultType);
|
|
277
|
+
const [amount, setAmount] = React17.useState(0);
|
|
278
|
+
const [amountStr, setAmountStr] = React17.useState("");
|
|
279
|
+
const [paid, setPaid] = React17.useState(true);
|
|
280
|
+
const [date, setDate] = React17.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
281
|
+
const [description, setDescription] = React17.useState("");
|
|
282
|
+
const [categoryId, setCategoryId] = React17.useState();
|
|
283
|
+
const [accountId, setAccountId] = React17.useState();
|
|
284
|
+
const [toAccountId, setToAccountId] = React17.useState();
|
|
285
|
+
const [recurring, setRecurring] = React17.useState(false);
|
|
286
|
+
const [saving, setSaving] = React17.useState(false);
|
|
287
|
+
const [receiptUrl, setReceiptUrl] = React17.useState();
|
|
288
|
+
const fileInputRef = React17.useRef(null);
|
|
289
|
+
const [accounts, setAccounts] = React17.useState([]);
|
|
290
|
+
const [categories, setCategories] = React17.useState([]);
|
|
291
|
+
React17.useEffect(() => {
|
|
292
|
+
if (!open) return;
|
|
293
|
+
setType(defaultType);
|
|
294
|
+
setAmount(0);
|
|
295
|
+
setAmountStr("");
|
|
296
|
+
setPaid(true);
|
|
297
|
+
setDate((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
298
|
+
setDescription("");
|
|
299
|
+
setCategoryId(void 0);
|
|
300
|
+
setAccountId(defaultAccountId);
|
|
301
|
+
setToAccountId(void 0);
|
|
302
|
+
setRecurring(false);
|
|
303
|
+
setReceiptUrl(void 0);
|
|
304
|
+
}, [open, defaultType, defaultAccountId]);
|
|
305
|
+
React17.useEffect(() => {
|
|
306
|
+
if (open && focusReceipt) {
|
|
307
|
+
const id = window.setTimeout(() => fileInputRef.current?.click(), 250);
|
|
308
|
+
return () => window.clearTimeout(id);
|
|
309
|
+
}
|
|
310
|
+
}, [open, focusReceipt]);
|
|
311
|
+
function onPickReceipt(e) {
|
|
312
|
+
const file = e.target.files?.[0];
|
|
313
|
+
e.target.value = "";
|
|
314
|
+
if (!file) return;
|
|
315
|
+
if (!file.type.startsWith("image/")) {
|
|
316
|
+
ui.toast.error(t2("financial.quickTx.receiptInvalid"));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const reader = new FileReader();
|
|
320
|
+
reader.onload = () => {
|
|
321
|
+
setReceiptUrl(typeof reader.result === "string" ? reader.result : void 0);
|
|
322
|
+
};
|
|
323
|
+
reader.onerror = () => ui.toast.error(t2("financial.quickTx.receiptInvalid"));
|
|
324
|
+
reader.readAsDataURL(file);
|
|
325
|
+
}
|
|
326
|
+
React17.useEffect(() => {
|
|
327
|
+
if (!open) return;
|
|
328
|
+
let cancelled = false;
|
|
329
|
+
void (async () => {
|
|
330
|
+
const [accs, coa] = await Promise.all([
|
|
331
|
+
provider.getBankAccounts(),
|
|
332
|
+
provider.getChartOfAccounts().catch(() => [])
|
|
333
|
+
]);
|
|
334
|
+
if (cancelled) return;
|
|
335
|
+
setAccounts(accs);
|
|
336
|
+
setCategories(coa);
|
|
337
|
+
setAccountId((prev) => prev ?? accs[0]?.id);
|
|
338
|
+
})();
|
|
339
|
+
return () => {
|
|
340
|
+
cancelled = true;
|
|
341
|
+
};
|
|
342
|
+
}, [open, provider]);
|
|
343
|
+
const theme = typeTheme(type);
|
|
344
|
+
const HeaderIcon = theme.icon;
|
|
345
|
+
const accountOptions = React17.useMemo(
|
|
346
|
+
() => accounts.map((a) => ({ id: a.id, name: a.name, hint: a.accountType === "credit_card" ? t2("financial.quickTx.card") : void 0 })),
|
|
347
|
+
[accounts, t2]
|
|
348
|
+
);
|
|
349
|
+
const categoryOptions = React17.useMemo(
|
|
350
|
+
() => categories.filter((c) => c.isActive).map((c) => ({ id: c.id, name: c.name, hint: c.code })),
|
|
351
|
+
[categories]
|
|
352
|
+
);
|
|
353
|
+
function onAmountChange(raw) {
|
|
354
|
+
const cleaned = raw.replace(/[^\d.,]/g, "").replace(",", ".");
|
|
355
|
+
setAmountStr(raw.replace(/[^\d.,]/g, ""));
|
|
356
|
+
const n = Number.parseFloat(cleaned);
|
|
357
|
+
setAmount(Number.isFinite(n) ? n : 0);
|
|
358
|
+
}
|
|
359
|
+
const canSave = amount > 0 && !saving && (type === "transfer" ? !!accountId && !!toAccountId && accountId !== toAccountId : true);
|
|
360
|
+
async function handleSave() {
|
|
361
|
+
if (!canSave) {
|
|
362
|
+
if (type === "transfer" && accountId && accountId === toAccountId) {
|
|
363
|
+
ui.toast.error(t2("financial.transfer.sameAccount"));
|
|
364
|
+
}
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
setSaving(true);
|
|
368
|
+
try {
|
|
369
|
+
await createQuickTransaction({
|
|
370
|
+
type,
|
|
371
|
+
amount,
|
|
372
|
+
date,
|
|
373
|
+
paid: type === "transfer" ? true : paid,
|
|
374
|
+
description: description || void 0,
|
|
375
|
+
categoryId: type === "transfer" ? void 0 : categoryId,
|
|
376
|
+
bankAccountId: accountId,
|
|
377
|
+
toAccountId: type === "transfer" ? toAccountId : void 0,
|
|
378
|
+
recurring: type === "transfer" ? false : recurring,
|
|
379
|
+
receiptUrl: type === "transfer" ? void 0 : receiptUrl
|
|
380
|
+
});
|
|
381
|
+
onOpenChange(false);
|
|
382
|
+
} catch {
|
|
383
|
+
} finally {
|
|
384
|
+
setSaving(false);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const TYPES = [
|
|
388
|
+
{ id: "expense", label: t2("financial.quickTx.expense") },
|
|
389
|
+
{ id: "income", label: t2("financial.quickTx.income") },
|
|
390
|
+
{ id: "transfer", label: t2("financial.quickTx.transfer") }
|
|
391
|
+
];
|
|
392
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Modal, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.ModalContent, { size: "md", noPadding: true, hideClose: true, className: "overflow-hidden", children: [
|
|
393
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${theme.header} shrink-0 px-5 pb-5 pt-5`, children: [
|
|
394
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
395
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx(HeaderIcon, { className: "h-5 w-5" }),
|
|
397
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold", children: t2("financial.quickTx.title") })
|
|
398
|
+
] }),
|
|
399
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
400
|
+
"button",
|
|
401
|
+
{
|
|
402
|
+
onClick: () => onOpenChange(false),
|
|
403
|
+
className: "flex h-7 w-7 items-center justify-center rounded-full opacity-80 transition-colors hover:bg-black/10 hover:opacity-100",
|
|
404
|
+
"aria-label": t2("financial.quickTx.close"),
|
|
405
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
|
|
406
|
+
}
|
|
407
|
+
)
|
|
408
|
+
] }),
|
|
409
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex gap-1 rounded-full bg-black/15 p-1", children: TYPES.map((ty) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
410
|
+
"button",
|
|
411
|
+
{
|
|
412
|
+
onClick: () => setType(ty.id),
|
|
413
|
+
className: `flex-1 rounded-full px-2 py-1.5 text-xs font-medium transition-colors ${type === ty.id ? "bg-white/90 text-foreground shadow-sm" : "opacity-90 hover:bg-white/10"}`,
|
|
414
|
+
children: ty.label
|
|
415
|
+
},
|
|
416
|
+
ty.id
|
|
417
|
+
)) }),
|
|
418
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 flex items-baseline justify-center gap-2", children: [
|
|
419
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xl font-medium opacity-90", children: currency.symbol }),
|
|
420
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
421
|
+
"input",
|
|
422
|
+
{
|
|
423
|
+
type: "text",
|
|
424
|
+
inputMode: "decimal",
|
|
425
|
+
autoFocus: true,
|
|
426
|
+
value: amountStr,
|
|
427
|
+
onChange: (e) => onAmountChange(e.target.value),
|
|
428
|
+
onKeyDown: (e) => {
|
|
429
|
+
if (e.key === "Enter") void handleSave();
|
|
430
|
+
},
|
|
431
|
+
placeholder: "0,00",
|
|
432
|
+
className: "w-full max-w-[220px] bg-transparent text-center text-4xl font-bold tracking-tight outline-none placeholder:opacity-40"
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
] })
|
|
436
|
+
] }),
|
|
437
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-4 overflow-y-auto px-5 py-5", children: [
|
|
438
|
+
type !== "transfer" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between rounded-input border border-input bg-card px-3 py-2.5 shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)]", children: [
|
|
439
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: type === "income" ? t2("financial.quickTx.received") : t2("financial.quickTx.paid") }),
|
|
440
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
|
|
441
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
442
|
+
"button",
|
|
443
|
+
{
|
|
444
|
+
onClick: () => setPaid(true),
|
|
445
|
+
className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${paid ? "bg-success text-success-foreground" : "bg-muted text-muted-foreground"}`,
|
|
446
|
+
children: t2("financial.quickTx.yes")
|
|
447
|
+
}
|
|
448
|
+
),
|
|
449
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
450
|
+
"button",
|
|
451
|
+
{
|
|
452
|
+
onClick: () => setPaid(false),
|
|
453
|
+
className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${!paid ? "bg-warning text-warning-foreground" : "bg-muted text-muted-foreground"}`,
|
|
454
|
+
children: t2("financial.quickTx.no")
|
|
455
|
+
}
|
|
456
|
+
)
|
|
457
|
+
] })
|
|
458
|
+
] }),
|
|
459
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
460
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
|
|
461
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, { className: "h-3.5 w-3.5" }),
|
|
462
|
+
" ",
|
|
463
|
+
t2("financial.invoiceForm.date")
|
|
464
|
+
] }),
|
|
465
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DatePicker, { value: date, onChange: setDate })
|
|
466
|
+
] }),
|
|
467
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
468
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.description") }),
|
|
469
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
470
|
+
"input",
|
|
471
|
+
{
|
|
472
|
+
type: "text",
|
|
473
|
+
value: description,
|
|
474
|
+
onChange: (e) => setDescription(e.target.value),
|
|
475
|
+
onKeyDown: (e) => {
|
|
476
|
+
if (e.key === "Enter") void handleSave();
|
|
477
|
+
},
|
|
478
|
+
placeholder: t2("financial.quickTx.descriptionPlaceholder"),
|
|
479
|
+
className: "mt-1 w-full rounded-input border border-input bg-card px-3 py-2.5 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] outline-none focus:ring-2 focus:ring-ring"
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
] }),
|
|
483
|
+
type !== "transfer" && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
484
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
|
|
485
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-3.5 w-3.5" }),
|
|
486
|
+
" ",
|
|
487
|
+
t2("financial.quickTx.receipt")
|
|
488
|
+
] }),
|
|
489
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
490
|
+
"input",
|
|
491
|
+
{
|
|
492
|
+
ref: fileInputRef,
|
|
493
|
+
type: "file",
|
|
494
|
+
accept: "image/*",
|
|
495
|
+
capture: "environment",
|
|
496
|
+
className: "hidden",
|
|
497
|
+
onChange: onPickReceipt
|
|
498
|
+
}
|
|
499
|
+
),
|
|
500
|
+
receiptUrl ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 rounded-input border border-input bg-card p-2 shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)]", children: [
|
|
501
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
502
|
+
"img",
|
|
503
|
+
{
|
|
504
|
+
src: receiptUrl,
|
|
505
|
+
alt: t2("financial.quickTx.receipt"),
|
|
506
|
+
className: "h-16 w-16 shrink-0 rounded-md border object-cover"
|
|
507
|
+
}
|
|
508
|
+
),
|
|
509
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
510
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium", children: t2("financial.quickTx.receiptAttached") }),
|
|
511
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
512
|
+
"button",
|
|
513
|
+
{
|
|
514
|
+
type: "button",
|
|
515
|
+
onClick: () => fileInputRef.current?.click(),
|
|
516
|
+
className: "text-xs text-primary hover:underline",
|
|
517
|
+
children: t2("financial.quickTx.receiptReplace")
|
|
518
|
+
}
|
|
519
|
+
)
|
|
520
|
+
] }),
|
|
521
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
522
|
+
"button",
|
|
523
|
+
{
|
|
524
|
+
type: "button",
|
|
525
|
+
onClick: () => setReceiptUrl(void 0),
|
|
526
|
+
"aria-label": t2("financial.quickTx.receiptRemove"),
|
|
527
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted",
|
|
528
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
|
|
529
|
+
}
|
|
530
|
+
)
|
|
531
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
532
|
+
"button",
|
|
533
|
+
{
|
|
534
|
+
type: "button",
|
|
535
|
+
onClick: () => fileInputRef.current?.click(),
|
|
536
|
+
className: `flex w-full items-center justify-center gap-2 rounded-input border border-dashed px-3 py-3 text-sm font-medium transition-colors hover:bg-muted/30 ${focusReceipt ? "border-primary text-primary ring-1 ring-primary/30" : "border-input text-muted-foreground"}`,
|
|
537
|
+
children: [
|
|
538
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Camera, { className: "h-4 w-4" }),
|
|
539
|
+
" ",
|
|
540
|
+
t2("financial.quickTx.receiptCapture")
|
|
541
|
+
]
|
|
542
|
+
}
|
|
543
|
+
)
|
|
544
|
+
] }),
|
|
545
|
+
type === "transfer" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3", children: [
|
|
546
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
547
|
+
ChipSelect,
|
|
548
|
+
{
|
|
549
|
+
icon: lucideReact.Wallet,
|
|
550
|
+
label: t2("financial.quickTx.fromAccount"),
|
|
551
|
+
value: accountId,
|
|
552
|
+
options: accountOptions,
|
|
553
|
+
onChange: setAccountId,
|
|
554
|
+
emptyLabel: t2("financial.quickTx.selectAccount")
|
|
555
|
+
}
|
|
556
|
+
),
|
|
557
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
558
|
+
ChipSelect,
|
|
559
|
+
{
|
|
560
|
+
icon: lucideReact.Wallet,
|
|
561
|
+
label: t2("financial.quickTx.toAccount"),
|
|
562
|
+
value: toAccountId,
|
|
563
|
+
options: accountOptions.filter((o) => o.id !== accountId),
|
|
564
|
+
onChange: setToAccountId,
|
|
565
|
+
emptyLabel: t2("financial.quickTx.selectAccount")
|
|
566
|
+
}
|
|
567
|
+
)
|
|
568
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3", children: [
|
|
569
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
570
|
+
ChipSelect,
|
|
571
|
+
{
|
|
572
|
+
icon: lucideReact.Tag,
|
|
573
|
+
label: t2("financial.quickTx.category"),
|
|
574
|
+
value: categoryId,
|
|
575
|
+
options: categoryOptions,
|
|
576
|
+
onChange: setCategoryId,
|
|
577
|
+
emptyLabel: t2("financial.quickTx.selectCategory")
|
|
578
|
+
}
|
|
579
|
+
),
|
|
580
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
581
|
+
ChipSelect,
|
|
582
|
+
{
|
|
583
|
+
icon: lucideReact.Wallet,
|
|
584
|
+
label: t2("financial.quickTx.account"),
|
|
585
|
+
value: accountId,
|
|
586
|
+
options: accountOptions,
|
|
587
|
+
onChange: setAccountId,
|
|
588
|
+
emptyLabel: t2("financial.quickTx.selectAccount")
|
|
589
|
+
}
|
|
590
|
+
)
|
|
591
|
+
] }),
|
|
592
|
+
type !== "transfer" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
593
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
594
|
+
TogglePill,
|
|
595
|
+
{
|
|
596
|
+
icon: lucideReact.Repeat,
|
|
597
|
+
label: t2("financial.quickTx.recurring"),
|
|
598
|
+
checked: recurring,
|
|
599
|
+
onChange: setRecurring
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
603
|
+
"button",
|
|
604
|
+
{
|
|
605
|
+
type: "button",
|
|
606
|
+
onClick: () => fileInputRef.current?.click(),
|
|
607
|
+
title: t2("financial.quickTx.attachment"),
|
|
608
|
+
className: `flex items-center gap-2 rounded-full border px-3 py-2 text-xs font-medium transition-colors ${receiptUrl ? "border-transparent bg-primary text-primary-foreground" : "border-input bg-card text-muted-foreground hover:bg-muted/40"}`,
|
|
609
|
+
children: [
|
|
610
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-3.5 w-3.5" }),
|
|
611
|
+
" ",
|
|
612
|
+
t2("financial.quickTx.attachment"),
|
|
613
|
+
receiptUrl && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" })
|
|
614
|
+
]
|
|
615
|
+
}
|
|
616
|
+
)
|
|
617
|
+
] })
|
|
618
|
+
] }),
|
|
619
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3 border-t px-5 py-3", children: [
|
|
620
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: amount > 0 ? saas.formatCurrency(amount, currency) : t2("financial.quickTx.enterAmount") }),
|
|
621
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
622
|
+
ui.Button,
|
|
623
|
+
{
|
|
624
|
+
className: "ml-auto",
|
|
625
|
+
onClick: () => void handleSave(),
|
|
626
|
+
disabled: !canSave,
|
|
627
|
+
children: [
|
|
628
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }),
|
|
629
|
+
saving ? t2("financial.quickTx.saving") : t2("financial.quickTx.save")
|
|
630
|
+
]
|
|
631
|
+
}
|
|
632
|
+
)
|
|
633
|
+
] })
|
|
634
|
+
] }) });
|
|
635
|
+
}
|
|
636
|
+
var pending = null;
|
|
637
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
638
|
+
function emit() {
|
|
639
|
+
for (const l of listeners) l();
|
|
640
|
+
}
|
|
641
|
+
function openQuickAdd(type = "expense", receipt = false) {
|
|
642
|
+
pending = { type, receipt };
|
|
643
|
+
emit();
|
|
644
|
+
}
|
|
645
|
+
function consumeQuickAdd() {
|
|
646
|
+
const req = pending;
|
|
647
|
+
pending = null;
|
|
648
|
+
return req;
|
|
649
|
+
}
|
|
650
|
+
function subscribe(cb) {
|
|
651
|
+
listeners.add(cb);
|
|
652
|
+
return () => {
|
|
653
|
+
listeners.delete(cb);
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function getSnapshot() {
|
|
657
|
+
return pending;
|
|
658
|
+
}
|
|
659
|
+
function usePendingQuickAdd() {
|
|
660
|
+
return React17__namespace.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
24
661
|
}
|
|
25
662
|
var STATUS_OPTIONS = [
|
|
26
663
|
{ value: "open", labelKey: "financial.invoice.statusOpen", color: "bg-info-soft text-info-soft-foreground", icon: lucideReact.CircleDashed },
|
|
@@ -41,7 +678,7 @@ function StatusBadge({ status }) {
|
|
|
41
678
|
}
|
|
42
679
|
function useInvoiceColumns(currency) {
|
|
43
680
|
const t2 = core.useTranslation();
|
|
44
|
-
return
|
|
681
|
+
return React17.useMemo(() => [
|
|
45
682
|
{
|
|
46
683
|
accessorKey: "fiscalNumber",
|
|
47
684
|
header: "#",
|
|
@@ -93,27 +730,49 @@ function useInvoiceColumns(currency) {
|
|
|
93
730
|
function InvoiceListView({ direction, onNew, onEdit }) {
|
|
94
731
|
const t2 = core.useTranslation();
|
|
95
732
|
const { currency } = useFinancialConfig();
|
|
733
|
+
const provider = useFinancialProvider();
|
|
96
734
|
const invoices = useFinancialStore((s) => s.invoices);
|
|
97
735
|
useFinancialStore((s) => s.invoicesTotal);
|
|
98
736
|
const invoicesLoading = useFinancialStore((s) => s.invoicesLoading);
|
|
99
737
|
const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
|
|
100
|
-
const [search, setSearch] =
|
|
101
|
-
const [statusFilter, setStatusFilter] =
|
|
102
|
-
|
|
738
|
+
const [search, setSearch] = React17.useState("");
|
|
739
|
+
const [statusFilter, setStatusFilter] = React17.useState([]);
|
|
740
|
+
const [accountId, setAccountId] = React17.useState("");
|
|
741
|
+
const [costCenterId, setCostCenterId] = React17.useState("");
|
|
742
|
+
const [accounts, setAccounts] = React17.useState([]);
|
|
743
|
+
const [costCenters, setCostCenters] = React17.useState([]);
|
|
744
|
+
React17.useEffect(() => {
|
|
745
|
+
let active = true;
|
|
746
|
+
Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
|
|
747
|
+
if (!active) return;
|
|
748
|
+
setAccounts(accountRows.filter((account) => account.nodeType === "leaf"));
|
|
749
|
+
setCostCenters(costCenterRows);
|
|
750
|
+
}).catch(() => {
|
|
751
|
+
if (!active) return;
|
|
752
|
+
setAccounts([]);
|
|
753
|
+
setCostCenters([]);
|
|
754
|
+
});
|
|
755
|
+
return () => {
|
|
756
|
+
active = false;
|
|
757
|
+
};
|
|
758
|
+
}, [provider]);
|
|
759
|
+
React17.useEffect(() => {
|
|
103
760
|
fetchInvoices({
|
|
104
761
|
direction,
|
|
105
762
|
status: statusFilter.length > 0 ? statusFilter : void 0,
|
|
106
|
-
search: search || void 0
|
|
763
|
+
search: search || void 0,
|
|
764
|
+
accountId: accountId || void 0,
|
|
765
|
+
costCenterId: costCenterId || void 0
|
|
107
766
|
});
|
|
108
|
-
}, [direction, statusFilter, search]);
|
|
767
|
+
}, [direction, statusFilter, search, accountId, costCenterId]);
|
|
109
768
|
const filtered = invoices.filter((inv) => inv.direction === direction);
|
|
110
769
|
const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
|
|
111
770
|
const columns = useInvoiceColumns(currency);
|
|
112
|
-
const tags =
|
|
771
|
+
const tags = React17.useMemo(
|
|
113
772
|
() => STATUS_OPTIONS.filter((o) => o.value !== "cancelled").map((o) => ({ value: o.value, label: t2(o.labelKey) })),
|
|
114
773
|
[t2]
|
|
115
774
|
);
|
|
116
|
-
const summary =
|
|
775
|
+
const summary = React17.useMemo(() => {
|
|
117
776
|
let totalAmount = 0;
|
|
118
777
|
let paidAmount = 0;
|
|
119
778
|
let openAmount = 0;
|
|
@@ -135,6 +794,46 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
135
794
|
subtitle: t2("financial.invoice.invoices", { count: filtered.length })
|
|
136
795
|
}
|
|
137
796
|
),
|
|
797
|
+
(accounts.length > 0 || costCenters.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
|
|
798
|
+
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
|
+
t2("financial.invoice.filterAccount"),
|
|
800
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
801
|
+
"select",
|
|
802
|
+
{
|
|
803
|
+
className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
|
|
804
|
+
value: accountId,
|
|
805
|
+
onChange: (event) => setAccountId(event.target.value),
|
|
806
|
+
children: [
|
|
807
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("financial.invoice.filterAllAccounts") }),
|
|
808
|
+
accounts.map((account) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: account.id, children: [
|
|
809
|
+
account.code,
|
|
810
|
+
" - ",
|
|
811
|
+
account.name
|
|
812
|
+
] }, account.id))
|
|
813
|
+
]
|
|
814
|
+
}
|
|
815
|
+
)
|
|
816
|
+
] }),
|
|
817
|
+
costCenters.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
|
|
818
|
+
t2("financial.invoice.filterCostCenter"),
|
|
819
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
820
|
+
"select",
|
|
821
|
+
{
|
|
822
|
+
className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
|
|
823
|
+
value: costCenterId,
|
|
824
|
+
onChange: (event) => setCostCenterId(event.target.value),
|
|
825
|
+
children: [
|
|
826
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("financial.invoice.filterAllCostCenters") }),
|
|
827
|
+
costCenters.map((costCenter) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: costCenter.id, children: [
|
|
828
|
+
costCenter.code,
|
|
829
|
+
" - ",
|
|
830
|
+
costCenter.name
|
|
831
|
+
] }, costCenter.id))
|
|
832
|
+
]
|
|
833
|
+
}
|
|
834
|
+
)
|
|
835
|
+
] })
|
|
836
|
+
] }),
|
|
138
837
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
139
838
|
ui.ListView,
|
|
140
839
|
{
|
|
@@ -177,10 +876,10 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
177
876
|
}
|
|
178
877
|
function LocationSelector({ locations, value, onChange }) {
|
|
179
878
|
const t2 = core.useTranslation();
|
|
180
|
-
const [open, setOpen] =
|
|
181
|
-
const [search, setSearch] =
|
|
182
|
-
const ref =
|
|
183
|
-
|
|
879
|
+
const [open, setOpen] = React17.useState(false);
|
|
880
|
+
const [search, setSearch] = React17.useState("");
|
|
881
|
+
const ref = React17.useRef(null);
|
|
882
|
+
React17.useEffect(() => {
|
|
184
883
|
function handler(e) {
|
|
185
884
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
186
885
|
}
|
|
@@ -260,7 +959,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
|
|
|
260
959
|
const total = item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0);
|
|
261
960
|
const kindLabel = itemTypes.find((tp) => tp.value === item.itemKind)?.label ?? item.itemKind;
|
|
262
961
|
const hasAdjustments = (item.discount ?? 0) > 0 || (item.surcharge ?? 0) > 0;
|
|
263
|
-
const [showAdjustments, setShowAdjustments] =
|
|
962
|
+
const [showAdjustments, setShowAdjustments] = React17.useState(hasAdjustments);
|
|
264
963
|
function handleFieldKeyDown(e) {
|
|
265
964
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
266
965
|
e.preventDefault();
|
|
@@ -496,25 +1195,25 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
496
1195
|
const createInvoice = useFinancialStore((s) => s.createInvoice);
|
|
497
1196
|
const hasLocations = locations.length > 0;
|
|
498
1197
|
const isEdit = !!editId;
|
|
499
|
-
const [loaded, setLoaded] =
|
|
500
|
-
const [loading, setLoading] =
|
|
501
|
-
const [contactName, setContactName] =
|
|
502
|
-
const [contactId, setContactId] =
|
|
503
|
-
const [unitId, setUnitId] =
|
|
504
|
-
const [invoiceDate, setInvoiceDate] =
|
|
505
|
-
const [fiscalNumber, setFiscalNumber] =
|
|
506
|
-
const [observations, setObservations] =
|
|
507
|
-
const [items, setItems] =
|
|
508
|
-
const [expandedItemId, setExpandedItemId] =
|
|
509
|
-
const [addingItem, setAddingItem] =
|
|
510
|
-
const [installments, setInstallments] =
|
|
511
|
-
const [saving, setSaving] =
|
|
512
|
-
const [menuOpen, setMenuOpen] =
|
|
513
|
-
const [confirmCancel, setConfirmCancel] =
|
|
514
|
-
const [cancelling, setCancelling] =
|
|
515
|
-
const menuRef =
|
|
1198
|
+
const [loaded, setLoaded] = React17.useState(!editId);
|
|
1199
|
+
const [loading, setLoading] = React17.useState(!!editId);
|
|
1200
|
+
const [contactName, setContactName] = React17.useState("");
|
|
1201
|
+
const [contactId, setContactId] = React17.useState();
|
|
1202
|
+
const [unitId, setUnitId] = React17.useState(locations[0]?.id ?? "");
|
|
1203
|
+
const [invoiceDate, setInvoiceDate] = React17.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
1204
|
+
const [fiscalNumber, setFiscalNumber] = React17.useState("");
|
|
1205
|
+
const [observations, setObservations] = React17.useState("");
|
|
1206
|
+
const [items, setItems] = React17.useState([]);
|
|
1207
|
+
const [expandedItemId, setExpandedItemId] = React17.useState(null);
|
|
1208
|
+
const [addingItem, setAddingItem] = React17.useState(false);
|
|
1209
|
+
const [installments, setInstallments] = React17.useState([]);
|
|
1210
|
+
const [saving, setSaving] = React17.useState(false);
|
|
1211
|
+
const [menuOpen, setMenuOpen] = React17.useState(false);
|
|
1212
|
+
const [confirmCancel, setConfirmCancel] = React17.useState(false);
|
|
1213
|
+
const [cancelling, setCancelling] = React17.useState(false);
|
|
1214
|
+
const menuRef = React17.useRef(null);
|
|
516
1215
|
const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
|
|
517
|
-
|
|
1216
|
+
React17.useEffect(() => {
|
|
518
1217
|
if (!menuOpen) return;
|
|
519
1218
|
function handleClick(e) {
|
|
520
1219
|
if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
|
|
@@ -533,7 +1232,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
533
1232
|
setCancelling(false);
|
|
534
1233
|
}
|
|
535
1234
|
}
|
|
536
|
-
|
|
1235
|
+
React17.useEffect(() => {
|
|
537
1236
|
if (!editId || loaded) return;
|
|
538
1237
|
setLoading(true);
|
|
539
1238
|
(async () => {
|
|
@@ -570,8 +1269,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
570
1269
|
const totalAmount = items.reduce((sum, it) => {
|
|
571
1270
|
return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
|
|
572
1271
|
}, 0);
|
|
573
|
-
const prevItemCount =
|
|
574
|
-
|
|
1272
|
+
const prevItemCount = React17.useRef(items.length);
|
|
1273
|
+
React17.useEffect(() => {
|
|
575
1274
|
if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
|
|
576
1275
|
const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
|
|
577
1276
|
if (total > 0) {
|
|
@@ -580,7 +1279,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
580
1279
|
}
|
|
581
1280
|
prevItemCount.current = items.length;
|
|
582
1281
|
}, [items.length]);
|
|
583
|
-
|
|
1282
|
+
React17.useEffect(() => {
|
|
584
1283
|
if (installments.length > 0 && totalAmount > 0 && !isEdit) {
|
|
585
1284
|
const count = installments.length;
|
|
586
1285
|
const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
|
|
@@ -692,8 +1391,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
692
1391
|
items: items.map(({ _id, ...rest }) => rest),
|
|
693
1392
|
installments: installments.map(({ _id, ...rest }) => rest)
|
|
694
1393
|
};
|
|
695
|
-
const snapshot =
|
|
696
|
-
|
|
1394
|
+
const snapshot = React17.useRef(null);
|
|
1395
|
+
React17.useEffect(() => {
|
|
697
1396
|
if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
|
|
698
1397
|
}, [loaded]);
|
|
699
1398
|
const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
|
|
@@ -1003,40 +1702,40 @@ function PaymentModal({ movement, onClose, onPaid }) {
|
|
|
1003
1702
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
1004
1703
|
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
1005
1704
|
const remaining = movement.amount - movement.paidAmount;
|
|
1006
|
-
const [amount, setAmount] =
|
|
1007
|
-
const [paymentDate, setPaymentDate] =
|
|
1008
|
-
const [selectedTypeId, setSelectedTypeId] =
|
|
1009
|
-
const [paymentMethodId, setPaymentMethodId] =
|
|
1010
|
-
const [bankAccountId, setBankAccountId] =
|
|
1011
|
-
const [cardBrand, setCardBrand] =
|
|
1012
|
-
const [cardInstallments, setCardInstallments] =
|
|
1013
|
-
const [notes, setNotes] =
|
|
1014
|
-
const [saving, setSaving] =
|
|
1015
|
-
const [visible, setVisible] =
|
|
1016
|
-
const [expandMethodPicker, setExpandMethodPicker] =
|
|
1017
|
-
|
|
1705
|
+
const [amount, setAmount] = React17.useState(remaining);
|
|
1706
|
+
const [paymentDate, setPaymentDate] = React17.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
1707
|
+
const [selectedTypeId, setSelectedTypeId] = React17.useState("");
|
|
1708
|
+
const [paymentMethodId, setPaymentMethodId] = React17.useState("");
|
|
1709
|
+
const [bankAccountId, setBankAccountId] = React17.useState("");
|
|
1710
|
+
const [cardBrand, setCardBrand] = React17.useState("");
|
|
1711
|
+
const [cardInstallments, setCardInstallments] = React17.useState(1);
|
|
1712
|
+
const [notes, setNotes] = React17.useState("");
|
|
1713
|
+
const [saving, setSaving] = React17.useState(false);
|
|
1714
|
+
const [visible, setVisible] = React17.useState(false);
|
|
1715
|
+
const [expandMethodPicker, setExpandMethodPicker] = React17.useState(true);
|
|
1716
|
+
React17.useEffect(() => {
|
|
1018
1717
|
fetchBankAccounts();
|
|
1019
1718
|
fetchPaymentMethods();
|
|
1020
1719
|
requestAnimationFrame(() => setVisible(true));
|
|
1021
1720
|
}, []);
|
|
1022
|
-
const selectedType =
|
|
1721
|
+
const selectedType = React17.useMemo(
|
|
1023
1722
|
() => paymentMethodTypes.find((tp) => tp.id === selectedTypeId),
|
|
1024
1723
|
[paymentMethodTypes, selectedTypeId]
|
|
1025
1724
|
);
|
|
1026
1725
|
const txType = selectedType?.transactionType ?? "";
|
|
1027
|
-
const filteredMethods =
|
|
1726
|
+
const filteredMethods = React17.useMemo(
|
|
1028
1727
|
() => paymentMethods.filter((m) => m.paymentMethodTypeId === selectedTypeId),
|
|
1029
1728
|
[paymentMethods, selectedTypeId]
|
|
1030
1729
|
);
|
|
1031
1730
|
const needsCard = txType === "credit_card" || txType === "debit_card";
|
|
1032
1731
|
const needsCardInstallments = txType === "credit_card";
|
|
1033
|
-
const eligibleAccounts =
|
|
1732
|
+
const eligibleAccounts = React17.useMemo(() => {
|
|
1034
1733
|
const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[txType];
|
|
1035
1734
|
if (!allowed || !allowed.length) return bankAccounts;
|
|
1036
1735
|
const filtered = bankAccounts.filter((a) => allowed.includes(a.accountType));
|
|
1037
1736
|
return filtered.length ? filtered : bankAccounts;
|
|
1038
1737
|
}, [bankAccounts, selectedType, txType]);
|
|
1039
|
-
|
|
1738
|
+
React17.useEffect(() => {
|
|
1040
1739
|
if (selectedTypeId && !bankAccountId && eligibleAccounts.length) {
|
|
1041
1740
|
setBankAccountId(eligibleAccounts[0].id);
|
|
1042
1741
|
}
|
|
@@ -1280,19 +1979,19 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
1280
1979
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
1281
1980
|
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
1282
1981
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
1283
|
-
const [detailsLoaded, setDetailsLoaded] =
|
|
1284
|
-
const [detailsLoading, setDetailsLoading] =
|
|
1285
|
-
const [invoice, setInvoice] =
|
|
1286
|
-
const [items, setItems] =
|
|
1287
|
-
const [movements, setMovements] =
|
|
1288
|
-
const [payments, setPayments] =
|
|
1289
|
-
const [loading, setLoading] =
|
|
1290
|
-
const [payingMovement, setPayingMovement] =
|
|
1291
|
-
const [expandedMovId, setExpandedMovId] =
|
|
1292
|
-
const [menuOpen, setMenuOpen] =
|
|
1293
|
-
const [confirmCancel, setConfirmCancel] =
|
|
1294
|
-
const [cancelling, setCancelling] =
|
|
1295
|
-
const menuRef =
|
|
1982
|
+
const [detailsLoaded, setDetailsLoaded] = React17.useState(false);
|
|
1983
|
+
const [detailsLoading, setDetailsLoading] = React17.useState(false);
|
|
1984
|
+
const [invoice, setInvoice] = React17.useState(null);
|
|
1985
|
+
const [items, setItems] = React17.useState([]);
|
|
1986
|
+
const [movements, setMovements] = React17.useState([]);
|
|
1987
|
+
const [payments, setPayments] = React17.useState([]);
|
|
1988
|
+
const [loading, setLoading] = React17.useState(true);
|
|
1989
|
+
const [payingMovement, setPayingMovement] = React17.useState(null);
|
|
1990
|
+
const [expandedMovId, setExpandedMovId] = React17.useState(null);
|
|
1991
|
+
const [menuOpen, setMenuOpen] = React17.useState(false);
|
|
1992
|
+
const [confirmCancel, setConfirmCancel] = React17.useState(false);
|
|
1993
|
+
const [cancelling, setCancelling] = React17.useState(false);
|
|
1994
|
+
const menuRef = React17.useRef(null);
|
|
1296
1995
|
const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
|
|
1297
1996
|
async function loadData() {
|
|
1298
1997
|
setLoading(true);
|
|
@@ -1311,10 +2010,10 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
1311
2010
|
);
|
|
1312
2011
|
setLoading(false);
|
|
1313
2012
|
}
|
|
1314
|
-
|
|
2013
|
+
React17.useEffect(() => {
|
|
1315
2014
|
loadData();
|
|
1316
2015
|
}, [invoiceId]);
|
|
1317
|
-
|
|
2016
|
+
React17.useEffect(() => {
|
|
1318
2017
|
if (!menuOpen) return;
|
|
1319
2018
|
function handleClick(e) {
|
|
1320
2019
|
if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
|
|
@@ -1915,16 +2614,16 @@ function CashOpeningPanel({
|
|
|
1915
2614
|
currency,
|
|
1916
2615
|
onOpen
|
|
1917
2616
|
}) {
|
|
1918
|
-
const [selectedAccountId, setSelectedAccountId] =
|
|
1919
|
-
const [mode, setMode] =
|
|
1920
|
-
const [quickBalance, setQuickBalance] =
|
|
1921
|
-
const [counts, setCounts] =
|
|
1922
|
-
const [notes, setNotes] =
|
|
1923
|
-
const [showNotes, setShowNotes] =
|
|
1924
|
-
const [confirming, setConfirming] =
|
|
1925
|
-
const [submitting, setSubmitting] =
|
|
2617
|
+
const [selectedAccountId, setSelectedAccountId] = React17.useState(cashAccounts[0]?.id ?? "");
|
|
2618
|
+
const [mode, setMode] = React17.useState("quick");
|
|
2619
|
+
const [quickBalance, setQuickBalance] = React17.useState(0);
|
|
2620
|
+
const [counts, setCounts] = React17.useState({});
|
|
2621
|
+
const [notes, setNotes] = React17.useState("");
|
|
2622
|
+
const [showNotes, setShowNotes] = React17.useState(false);
|
|
2623
|
+
const [confirming, setConfirming] = React17.useState(false);
|
|
2624
|
+
const [submitting, setSubmitting] = React17.useState(false);
|
|
1926
2625
|
const t2 = core.useTranslation();
|
|
1927
|
-
const countedTotal =
|
|
2626
|
+
const countedTotal = React17.useMemo(() => {
|
|
1928
2627
|
return BRL_DENOMINATIONS.reduce((sum, d) => sum + (counts[d.value] ?? 0) * d.value, 0);
|
|
1929
2628
|
}, [counts]);
|
|
1930
2629
|
const effectiveBalance = mode === "count" ? countedTotal : quickBalance;
|
|
@@ -2100,13 +2799,13 @@ function CashClosingPanel({
|
|
|
2100
2799
|
currency,
|
|
2101
2800
|
onClose
|
|
2102
2801
|
}) {
|
|
2103
|
-
const [mode, setMode] =
|
|
2104
|
-
const [quickBalance, setQuickBalance] =
|
|
2105
|
-
const [counts, setCounts] =
|
|
2106
|
-
const [notes, setNotes] =
|
|
2107
|
-
const [submitting, setSubmitting] =
|
|
2802
|
+
const [mode, setMode] = React17.useState("quick");
|
|
2803
|
+
const [quickBalance, setQuickBalance] = React17.useState(0);
|
|
2804
|
+
const [counts, setCounts] = React17.useState({});
|
|
2805
|
+
const [notes, setNotes] = React17.useState("");
|
|
2806
|
+
const [submitting, setSubmitting] = React17.useState(false);
|
|
2108
2807
|
const t2 = core.useTranslation();
|
|
2109
|
-
const countedTotal =
|
|
2808
|
+
const countedTotal = React17.useMemo(() => {
|
|
2110
2809
|
return BRL_DENOMINATIONS.reduce((sum, d) => sum + (counts[d.value] ?? 0) * d.value, 0);
|
|
2111
2810
|
}, [counts]);
|
|
2112
2811
|
const effectiveBalance = mode === "count" ? countedTotal : quickBalance;
|
|
@@ -2211,8 +2910,8 @@ function CashRegistersView() {
|
|
|
2211
2910
|
const fetchCashSessions = useFinancialStore((s) => s.fetchCashSessions);
|
|
2212
2911
|
const openCashSession = useFinancialStore((s) => s.openCashSession);
|
|
2213
2912
|
const closeCashSession = useFinancialStore((s) => s.closeCashSession);
|
|
2214
|
-
const [expandedClosing, setExpandedClosing] =
|
|
2215
|
-
|
|
2913
|
+
const [expandedClosing, setExpandedClosing] = React17.useState(null);
|
|
2914
|
+
React17.useEffect(() => {
|
|
2216
2915
|
fetchBankAccounts();
|
|
2217
2916
|
fetchCashSessions();
|
|
2218
2917
|
}, []);
|
|
@@ -2316,7 +3015,7 @@ function OpenAnotherCollapsible({
|
|
|
2316
3015
|
onOpen
|
|
2317
3016
|
}) {
|
|
2318
3017
|
const t2 = core.useTranslation();
|
|
2319
|
-
const [expanded, setExpanded] =
|
|
3018
|
+
const [expanded, setExpanded] = React17.useState(false);
|
|
2320
3019
|
const availableAccounts = cashAccounts.filter((a) => !openSessions.some((s) => s.bankAccountId === a.id));
|
|
2321
3020
|
if (availableAccounts.length === 0) return null;
|
|
2322
3021
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
|
|
@@ -2343,14 +3042,14 @@ function TransferModal({ onClose, onDone, defaultFromId }) {
|
|
|
2343
3042
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
2344
3043
|
const createTransfer = useFinancialStore((s) => s.createTransfer);
|
|
2345
3044
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
2346
|
-
const [fromAccountId, setFromAccountId] =
|
|
2347
|
-
const [toAccountId, setToAccountId] =
|
|
2348
|
-
const [amount, setAmount] =
|
|
2349
|
-
const [date, setDate] =
|
|
2350
|
-
const [notes, setNotes] =
|
|
2351
|
-
const [saving, setSaving] =
|
|
2352
|
-
const [visible, setVisible] =
|
|
2353
|
-
|
|
3045
|
+
const [fromAccountId, setFromAccountId] = React17.useState(defaultFromId ?? "");
|
|
3046
|
+
const [toAccountId, setToAccountId] = React17.useState("");
|
|
3047
|
+
const [amount, setAmount] = React17.useState(0);
|
|
3048
|
+
const [date, setDate] = React17.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
3049
|
+
const [notes, setNotes] = React17.useState("");
|
|
3050
|
+
const [saving, setSaving] = React17.useState(false);
|
|
3051
|
+
const [visible, setVisible] = React17.useState(false);
|
|
3052
|
+
React17.useEffect(() => {
|
|
2354
3053
|
fetchBankAccounts();
|
|
2355
3054
|
requestAnimationFrame(() => setVisible(true));
|
|
2356
3055
|
}, []);
|
|
@@ -2493,19 +3192,19 @@ function StatementsView({ onNavigate }) {
|
|
|
2493
3192
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
2494
3193
|
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
2495
3194
|
const fetchStatement = useFinancialStore((s) => s.fetchStatement);
|
|
2496
|
-
const [accountId, setAccountId] =
|
|
2497
|
-
const [dateFrom, setDateFrom] =
|
|
3195
|
+
const [accountId, setAccountId] = React17.useState("");
|
|
3196
|
+
const [dateFrom, setDateFrom] = React17.useState(() => {
|
|
2498
3197
|
const d = /* @__PURE__ */ new Date();
|
|
2499
3198
|
d.setMonth(d.getMonth() - 3);
|
|
2500
3199
|
return d.toISOString().slice(0, 10);
|
|
2501
3200
|
});
|
|
2502
|
-
const [dateTo, setDateTo] =
|
|
2503
|
-
const [showTransfer, setShowTransfer] =
|
|
2504
|
-
|
|
3201
|
+
const [dateTo, setDateTo] = React17.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
3202
|
+
const [showTransfer, setShowTransfer] = React17.useState(false);
|
|
3203
|
+
React17.useEffect(() => {
|
|
2505
3204
|
fetchBankAccounts();
|
|
2506
3205
|
fetchPaymentMethods();
|
|
2507
3206
|
}, []);
|
|
2508
|
-
const methodName =
|
|
3207
|
+
const methodName = React17.useMemo(() => {
|
|
2509
3208
|
const byId = new Map(paymentMethods.map((m) => [m.id, m.name]));
|
|
2510
3209
|
const typeById = new Map(paymentMethodTypes.map((tp) => [tp.id, tp.name]));
|
|
2511
3210
|
return (mov) => {
|
|
@@ -2520,7 +3219,7 @@ function StatementsView({ onNavigate }) {
|
|
|
2520
3219
|
const base = e.movement.direction === "credit" ? "receivables" : "payables";
|
|
2521
3220
|
onNavigate(`${base}-detail:${e.movement.invoiceId}`);
|
|
2522
3221
|
}
|
|
2523
|
-
|
|
3222
|
+
React17.useEffect(() => {
|
|
2524
3223
|
if (accountId) {
|
|
2525
3224
|
fetchStatement({
|
|
2526
3225
|
bankAccountId: accountId === ALL ? void 0 : accountId,
|
|
@@ -2528,11 +3227,11 @@ function StatementsView({ onNavigate }) {
|
|
|
2528
3227
|
});
|
|
2529
3228
|
}
|
|
2530
3229
|
}, [accountId, dateFrom, dateTo]);
|
|
2531
|
-
|
|
3230
|
+
React17.useEffect(() => {
|
|
2532
3231
|
if (!accountId) setAccountId(ALL);
|
|
2533
3232
|
}, []);
|
|
2534
3233
|
const fmt = (n) => saas.formatCurrency(n, currency);
|
|
2535
|
-
const columns =
|
|
3234
|
+
const columns = React17.useMemo(() => [
|
|
2536
3235
|
{
|
|
2537
3236
|
id: "date",
|
|
2538
3237
|
header: t2("financial.statements.columnDate"),
|
|
@@ -2707,59 +3406,141 @@ function CommissionsView() {
|
|
|
2707
3406
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center rounded-lg border-2 border-dashed border-muted p-12", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.commissions.comingSoon") }) })
|
|
2708
3407
|
] });
|
|
2709
3408
|
}
|
|
3409
|
+
function cardMath(acc) {
|
|
3410
|
+
const limit = acc.creditLimit ?? 0;
|
|
3411
|
+
const used = Math.max(0, -acc.currentBalance);
|
|
3412
|
+
const available = Math.max(0, limit - used);
|
|
3413
|
+
const pct = limit > 0 ? Math.min(100, used / limit * 100) : 0;
|
|
3414
|
+
return { limit, used, available, pct };
|
|
3415
|
+
}
|
|
2710
3416
|
function CardsView() {
|
|
2711
3417
|
const t2 = core.useTranslation();
|
|
2712
|
-
const { currency
|
|
2713
|
-
const
|
|
2714
|
-
const
|
|
2715
|
-
const
|
|
2716
|
-
|
|
2717
|
-
|
|
3418
|
+
const { currency } = useFinancialConfig();
|
|
3419
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
3420
|
+
const bankAccountsLoading = useFinancialStore((s) => s.bankAccountsLoading);
|
|
3421
|
+
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
3422
|
+
const [tab, setTab] = React17.useState("open");
|
|
3423
|
+
const [addOpen, setAddOpen] = React17.useState(false);
|
|
3424
|
+
const [addCardId, setAddCardId] = React17.useState();
|
|
3425
|
+
React17.useEffect(() => {
|
|
3426
|
+
fetchBankAccounts();
|
|
2718
3427
|
}, []);
|
|
2719
|
-
const
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
3428
|
+
const cards = React17.useMemo(
|
|
3429
|
+
() => bankAccounts.filter((a) => a.accountType === "credit_card"),
|
|
3430
|
+
[bankAccounts]
|
|
3431
|
+
);
|
|
3432
|
+
const openCards = React17.useMemo(() => cards.filter((c) => c.isActive), [cards]);
|
|
3433
|
+
const closedCards = React17.useMemo(() => cards.filter((c) => !c.isActive), [cards]);
|
|
3434
|
+
const visibleCards = tab === "open" ? openCards : closedCards;
|
|
3435
|
+
const summary = React17.useMemo(() => {
|
|
3436
|
+
let totalAvailable = 0;
|
|
3437
|
+
let totalBills = 0;
|
|
3438
|
+
for (const c of openCards) {
|
|
3439
|
+
const { available, used } = cardMath(c);
|
|
3440
|
+
totalAvailable += available;
|
|
3441
|
+
totalBills += used;
|
|
3442
|
+
}
|
|
3443
|
+
return { totalAvailable, totalBills };
|
|
3444
|
+
}, [openCards]);
|
|
3445
|
+
function openAddExpense(cardId) {
|
|
3446
|
+
setAddCardId(cardId);
|
|
3447
|
+
setAddOpen(true);
|
|
2724
3448
|
}
|
|
2725
3449
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
2726
3450
|
/* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title: t2("financial.cards.title"), subtitle: t2("financial.cards.subtitle") }),
|
|
2727
|
-
|
|
2728
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-
|
|
2729
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm
|
|
2730
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: t2("financial.cards.
|
|
2731
|
-
] }) :
|
|
2732
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
2733
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
"
|
|
3451
|
+
bankAccountsLoading && cards.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: "Loading..." }) : cards.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-2xl border-2 border-dashed border-muted", children: [
|
|
3452
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-amber-300 to-yellow-600 mb-3 text-white shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-6 w-6" }) }),
|
|
3453
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: t2("financial.cards.noCards") }),
|
|
3454
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-0.5 max-w-xs", children: t2("financial.cards.noCardsHint") })
|
|
3455
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3456
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-px overflow-hidden rounded-2xl border bg-border shadow-sm", children: [
|
|
3457
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-card px-4 py-3", children: [
|
|
3458
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.cards.availableLimit") }),
|
|
3459
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-lg font-bold tabular-nums text-success", children: saas.formatCurrency(summary.totalAvailable, currency) })
|
|
3460
|
+
] }),
|
|
3461
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-card px-4 py-3", children: [
|
|
3462
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.cards.allBillsValue") }),
|
|
3463
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-lg font-bold tabular-nums", children: saas.formatCurrency(summary.totalBills, currency) })
|
|
2739
3464
|
] })
|
|
2740
3465
|
] }),
|
|
2741
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
3466
|
+
closedCards.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 rounded-full bg-muted p-1 w-full max-w-xs", children: ["open", "closed"].map((k) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3467
|
+
"button",
|
|
3468
|
+
{
|
|
3469
|
+
onClick: () => setTab(k),
|
|
3470
|
+
className: `flex-1 rounded-full px-3 py-1.5 text-xs font-medium transition-colors ${tab === k ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
|
|
3471
|
+
children: k === "open" ? t2("financial.cards.tabOpen") : t2("financial.cards.tabClosed")
|
|
3472
|
+
},
|
|
3473
|
+
k
|
|
3474
|
+
)) }),
|
|
3475
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3", children: visibleCards.map((card) => {
|
|
3476
|
+
const { limit, used, available, pct } = cardMath(card);
|
|
3477
|
+
const over = pct >= 90;
|
|
3478
|
+
const brand = `${card.bankName ?? ""} ${card.name ?? ""}`.toLowerCase();
|
|
3479
|
+
const headerGrad = brand.includes("nubank") || brand.includes("nu ") ? "from-purple-500 via-purple-600 to-indigo-700" : brand.includes("ita") ? "from-orange-400 via-orange-500 to-amber-600" : brand.includes("inter") ? "from-orange-500 via-orange-600 to-orange-700" : "from-indigo-500 via-indigo-600 to-violet-700";
|
|
3480
|
+
const barGrad = brand.includes("nubank") || brand.includes("nu ") ? "from-purple-500 to-indigo-600" : brand.includes("ita") ? "from-orange-400 to-amber-500" : brand.includes("inter") ? "from-orange-500 to-orange-600" : "from-indigo-500 to-violet-600";
|
|
3481
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "overflow-hidden rounded-2xl border bg-card shadow-sm", children: [
|
|
3482
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `bg-gradient-to-br ${headerGrad} px-4 py-4 text-white`, children: [
|
|
3483
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", children: [
|
|
3484
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3485
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-5 w-5" }),
|
|
3486
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: card.name })
|
|
3487
|
+
] }),
|
|
3488
|
+
card.bankName && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium uppercase tracking-wide opacity-80", children: card.bankName })
|
|
3489
|
+
] }),
|
|
3490
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4", children: [
|
|
3491
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] uppercase tracking-wide opacity-80", children: t2("financial.cards.currentBill") }),
|
|
3492
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold tabular-nums leading-tight", children: saas.formatCurrency(used, currency) })
|
|
3493
|
+
] })
|
|
2748
3494
|
] }),
|
|
2749
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
2750
|
-
"
|
|
2751
|
-
|
|
3495
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 px-4 py-3", children: [
|
|
3496
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3497
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-[11px] text-muted-foreground", children: [
|
|
3498
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("financial.cards.usedOfLimit", { percent: String(Math.round(pct)) }) }),
|
|
3499
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: saas.formatCurrency(limit, currency) })
|
|
3500
|
+
] }),
|
|
3501
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 h-2 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3502
|
+
"div",
|
|
3503
|
+
{
|
|
3504
|
+
className: `h-full rounded-full ${over ? "bg-destructive" : `bg-gradient-to-r ${barGrad}`}`,
|
|
3505
|
+
style: { width: `${pct}%` }
|
|
3506
|
+
}
|
|
3507
|
+
) })
|
|
3508
|
+
] }),
|
|
3509
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3510
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3511
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.cards.availableLimit") }),
|
|
3512
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold tabular-nums", children: saas.formatCurrency(available, currency) })
|
|
3513
|
+
] }),
|
|
3514
|
+
card.dueDay != null && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-right text-xs text-muted-foreground", children: [
|
|
3515
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarClock, { className: "h-3.5 w-3.5" }),
|
|
3516
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("financial.cards.dueOn", { day: String(card.dueDay) }) })
|
|
3517
|
+
] })
|
|
3518
|
+
] }),
|
|
3519
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3520
|
+
"button",
|
|
3521
|
+
{
|
|
3522
|
+
type: "button",
|
|
3523
|
+
onClick: () => openAddExpense(card.id),
|
|
3524
|
+
className: "flex w-full items-center justify-center gap-1.5 rounded-input border border-input bg-card px-3 py-2 text-xs font-medium text-foreground transition-colors hover:bg-muted/40",
|
|
3525
|
+
children: [
|
|
3526
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
|
|
3527
|
+
t2("financial.cards.addExpense")
|
|
3528
|
+
]
|
|
3529
|
+
}
|
|
3530
|
+
)
|
|
2752
3531
|
] })
|
|
2753
|
-
] })
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
3532
|
+
] }, card.id);
|
|
3533
|
+
}) })
|
|
3534
|
+
] }),
|
|
3535
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3536
|
+
QuickTransactionForm,
|
|
3537
|
+
{
|
|
3538
|
+
open: addOpen,
|
|
3539
|
+
onOpenChange: setAddOpen,
|
|
3540
|
+
defaultType: "expense",
|
|
3541
|
+
defaultAccountId: addCardId
|
|
3542
|
+
}
|
|
3543
|
+
)
|
|
2763
3544
|
] });
|
|
2764
3545
|
}
|
|
2765
3546
|
function ReconciliationView() {
|
|
@@ -2767,14 +3548,14 @@ function ReconciliationView() {
|
|
|
2767
3548
|
const { currency } = useFinancialConfig();
|
|
2768
3549
|
const provider = useFinancialProvider();
|
|
2769
3550
|
const fmt = (n) => saas.formatCurrency(n, currency);
|
|
2770
|
-
const [lines, setLines] =
|
|
2771
|
-
const [loading, setLoading] =
|
|
2772
|
-
const [selectedId, setSelectedId] =
|
|
2773
|
-
const [candidates, setCandidates] =
|
|
2774
|
-
const [candidatesLoading, setCandidatesLoading] =
|
|
2775
|
-
const [busy, setBusy] =
|
|
3551
|
+
const [lines, setLines] = React17.useState([]);
|
|
3552
|
+
const [loading, setLoading] = React17.useState(true);
|
|
3553
|
+
const [selectedId, setSelectedId] = React17.useState(null);
|
|
3554
|
+
const [candidates, setCandidates] = React17.useState([]);
|
|
3555
|
+
const [candidatesLoading, setCandidatesLoading] = React17.useState(false);
|
|
3556
|
+
const [busy, setBusy] = React17.useState(false);
|
|
2776
3557
|
const supported = typeof provider.getUnreconciled === "function";
|
|
2777
|
-
const load =
|
|
3558
|
+
const load = React17.useCallback(async () => {
|
|
2778
3559
|
if (!provider.getUnreconciled) return;
|
|
2779
3560
|
setLoading(true);
|
|
2780
3561
|
try {
|
|
@@ -2785,10 +3566,10 @@ function ReconciliationView() {
|
|
|
2785
3566
|
setLoading(false);
|
|
2786
3567
|
}
|
|
2787
3568
|
}, [provider]);
|
|
2788
|
-
|
|
3569
|
+
React17.useEffect(() => {
|
|
2789
3570
|
void load();
|
|
2790
3571
|
}, [load]);
|
|
2791
|
-
|
|
3572
|
+
React17.useEffect(() => {
|
|
2792
3573
|
if (!selectedId || !provider.suggestReconciliation) {
|
|
2793
3574
|
setCandidates([]);
|
|
2794
3575
|
return;
|
|
@@ -2940,7 +3721,7 @@ var STEPS = [
|
|
|
2940
3721
|
];
|
|
2941
3722
|
function FinancialOnboarding({ onComplete }) {
|
|
2942
3723
|
const t2 = core.useTranslation();
|
|
2943
|
-
const [step, setStep] =
|
|
3724
|
+
const [step, setStep] = React17.useState(0);
|
|
2944
3725
|
const current = STEPS[step];
|
|
2945
3726
|
const isLast = step === STEPS.length - 1;
|
|
2946
3727
|
const Icon = current.icon;
|
|
@@ -3085,18 +3866,47 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
3085
3866
|
cards: 0,
|
|
3086
3867
|
settings: 1
|
|
3087
3868
|
});
|
|
3088
|
-
const [onboardingComplete, setOnboardingComplete] =
|
|
3869
|
+
const [onboardingComplete, setOnboardingComplete] = React17.useState(() => {
|
|
3089
3870
|
try {
|
|
3090
3871
|
return localStorage.getItem("saas-core:financial-onboarded") === "true";
|
|
3091
3872
|
} catch {
|
|
3092
3873
|
return false;
|
|
3093
3874
|
}
|
|
3094
3875
|
});
|
|
3876
|
+
React17.useEffect(() => {
|
|
3877
|
+
if (onboardingComplete) return;
|
|
3878
|
+
let cancelled = false;
|
|
3879
|
+
void provider.getMovements({ pageSize: 1 }).then((res) => {
|
|
3880
|
+
if (!cancelled && res.total > 0) setOnboardingComplete(true);
|
|
3881
|
+
}).catch(() => {
|
|
3882
|
+
});
|
|
3883
|
+
return () => {
|
|
3884
|
+
cancelled = true;
|
|
3885
|
+
};
|
|
3886
|
+
}, [onboardingComplete, provider]);
|
|
3095
3887
|
const t2 = core.useTranslation();
|
|
3096
3888
|
const intent = parseIntent(view);
|
|
3097
3889
|
const isSettings = view === "settings";
|
|
3098
3890
|
const nav = buildNav(config, view, navigate, t2);
|
|
3099
|
-
const
|
|
3891
|
+
const [quickAddOpen, setQuickAddOpen] = React17.useState(false);
|
|
3892
|
+
const [quickAddType, setQuickAddType] = React17.useState("expense");
|
|
3893
|
+
const [quickAddReceipt, setQuickAddReceipt] = React17.useState(false);
|
|
3894
|
+
const openQuickAdd2 = React17.useCallback((type = "expense") => {
|
|
3895
|
+
setQuickAddType(type);
|
|
3896
|
+
setQuickAddReceipt(false);
|
|
3897
|
+
setQuickAddOpen(true);
|
|
3898
|
+
}, []);
|
|
3899
|
+
const pendingQuickAdd = usePendingQuickAdd();
|
|
3900
|
+
React17.useEffect(() => {
|
|
3901
|
+
if (!pendingQuickAdd) return;
|
|
3902
|
+
const req = consumeQuickAdd();
|
|
3903
|
+
if (!req) return;
|
|
3904
|
+
setQuickAddType(req.type);
|
|
3905
|
+
setQuickAddReceipt(req.receipt);
|
|
3906
|
+
setQuickAddOpen(true);
|
|
3907
|
+
}, [pendingQuickAdd]);
|
|
3908
|
+
const quickActions = React17.useMemo(() => {
|
|
3909
|
+
if (config.quickAdd) return [];
|
|
3100
3910
|
const actions = [];
|
|
3101
3911
|
if (config.modules.payables) {
|
|
3102
3912
|
actions.push({
|
|
@@ -3126,7 +3936,7 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
3126
3936
|
});
|
|
3127
3937
|
}
|
|
3128
3938
|
return actions;
|
|
3129
|
-
}, [config.modules]);
|
|
3939
|
+
}, [config.modules, config.quickAdd]);
|
|
3130
3940
|
function handleOnboardingComplete() {
|
|
3131
3941
|
setOnboardingComplete(true);
|
|
3132
3942
|
try {
|
|
@@ -3170,7 +3980,7 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
3170
3980
|
return /* @__PURE__ */ jsxRuntime.jsx(SummaryView, {});
|
|
3171
3981
|
}
|
|
3172
3982
|
}
|
|
3173
|
-
return /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.
|
|
3983
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3174
3984
|
ui.ModulePage,
|
|
3175
3985
|
{
|
|
3176
3986
|
title: config.labels.pageTitle,
|
|
@@ -3179,21 +3989,50 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
3179
3989
|
showHeader: intent.view === "summary" || view === "summary",
|
|
3180
3990
|
viewKey: view,
|
|
3181
3991
|
direction,
|
|
3182
|
-
headerAction: /* @__PURE__ */ jsxRuntime.
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3992
|
+
headerAction: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3993
|
+
config.quickAdd && // One-primary-action rule (FAY-1247): quick-add is the single primary
|
|
3994
|
+
// add affordance in B2C mode. The receipt entry point lives inside the
|
|
3995
|
+
// sheet's "Fotografar ou anexar recibo" attachment step, so no separate
|
|
3996
|
+
// "Enviar recibo" button is needed here.
|
|
3997
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3998
|
+
ui.Button,
|
|
3999
|
+
{
|
|
4000
|
+
size: "sm",
|
|
4001
|
+
className: "hidden md:inline-flex",
|
|
4002
|
+
onClick: () => openQuickAdd2("expense"),
|
|
4003
|
+
children: [
|
|
4004
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
|
|
4005
|
+
t2("financial.quickTx.newTransaction")
|
|
4006
|
+
]
|
|
4007
|
+
}
|
|
4008
|
+
),
|
|
4009
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4010
|
+
saas.ModuleActionBar,
|
|
4011
|
+
{
|
|
4012
|
+
quickActions,
|
|
4013
|
+
settingsPath: registries && registries.length > 0 ? "/settings/financial" : void 0,
|
|
4014
|
+
settingsLabel: t2("financial.settingsPage.title")
|
|
4015
|
+
}
|
|
4016
|
+
)
|
|
4017
|
+
] }),
|
|
4018
|
+
children: [
|
|
4019
|
+
renderView(),
|
|
4020
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4021
|
+
QuickTransactionForm,
|
|
4022
|
+
{
|
|
4023
|
+
open: quickAddOpen,
|
|
4024
|
+
onOpenChange: setQuickAddOpen,
|
|
4025
|
+
defaultType: quickAddType,
|
|
4026
|
+
focusReceipt: quickAddReceipt
|
|
4027
|
+
}
|
|
4028
|
+
)
|
|
4029
|
+
]
|
|
3191
4030
|
}
|
|
3192
4031
|
) });
|
|
3193
4032
|
}
|
|
3194
4033
|
function useEnsureSummary() {
|
|
3195
4034
|
const fetchSummary = useFinancialStore((s) => s.fetchSummary);
|
|
3196
|
-
|
|
4035
|
+
React17.useEffect(() => {
|
|
3197
4036
|
void fetchSummary();
|
|
3198
4037
|
}, []);
|
|
3199
4038
|
}
|
|
@@ -3317,7 +4156,7 @@ function RecentTransactions() {
|
|
|
3317
4156
|
const { currency } = useFinancialConfig();
|
|
3318
4157
|
const invoices = useFinancialStore((s) => s.invoices);
|
|
3319
4158
|
const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
|
|
3320
|
-
|
|
4159
|
+
React17.useEffect(() => {
|
|
3321
4160
|
void fetchInvoices({ pageSize: 5 });
|
|
3322
4161
|
}, []);
|
|
3323
4162
|
const columns = [
|
|
@@ -3334,6 +4173,202 @@ function RecentTransactions() {
|
|
|
3334
4173
|
];
|
|
3335
4174
|
return /* @__PURE__ */ jsxRuntime.jsx(ui.TableWidget, { title: t2("financial.summary.recentTransactions"), icon: "CircleDollarSign", columns, data: invoices.slice(0, 5), emptyMessage: t2("financial.summary.noTransactions") });
|
|
3336
4175
|
}
|
|
4176
|
+
function useDueLabel() {
|
|
4177
|
+
const t2 = core.useTranslation();
|
|
4178
|
+
return (dueDate) => {
|
|
4179
|
+
const today2 = /* @__PURE__ */ new Date();
|
|
4180
|
+
today2.setHours(0, 0, 0, 0);
|
|
4181
|
+
const due = /* @__PURE__ */ new Date(`${dueDate}T00:00:00`);
|
|
4182
|
+
const days = Math.round((due.getTime() - today2.getTime()) / 864e5);
|
|
4183
|
+
if (days < 0) return t2("financial.home.overdueDays", { count: String(Math.abs(days)) });
|
|
4184
|
+
if (days === 0) return t2("financial.home.dueToday");
|
|
4185
|
+
if (days === 1) return t2("financial.home.dueTomorrow");
|
|
4186
|
+
return t2("financial.home.dueInDays", { count: String(days) });
|
|
4187
|
+
};
|
|
4188
|
+
}
|
|
4189
|
+
function BalanceHero() {
|
|
4190
|
+
const t2 = core.useTranslation();
|
|
4191
|
+
const { currency } = useFinancialConfig();
|
|
4192
|
+
const summary = useFinancialStore((s) => s.summary);
|
|
4193
|
+
const navigate = ui.useDashboardNavigate();
|
|
4194
|
+
useEnsureSummary();
|
|
4195
|
+
const [hidden, setHidden] = React17__namespace.default.useState(false);
|
|
4196
|
+
const money = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : saas.formatCurrency(v ?? 0, currency);
|
|
4197
|
+
const inflow = summary?.monthlyInflow ?? 0;
|
|
4198
|
+
const outflow = summary?.monthlyOutflow ?? 0;
|
|
4199
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.GoldCard, { branded: true, className: "px-5 py-4 text-center md:px-8 md:py-5", children: [
|
|
4200
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium uppercase tracking-wide opacity-75", children: t2("financial.home.balance") }),
|
|
4201
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-0.5 flex items-center justify-center gap-2.5", children: [
|
|
4202
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-3xl font-bold tracking-tight tabular-nums md:text-4xl", children: money(summary?.totalBalance) }),
|
|
4203
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4204
|
+
"button",
|
|
4205
|
+
{
|
|
4206
|
+
type: "button",
|
|
4207
|
+
onClick: () => setHidden((h) => !h),
|
|
4208
|
+
"aria-label": hidden ? t2("financial.home.showBalance") : t2("financial.home.hideBalance"),
|
|
4209
|
+
className: "rounded-full p-1.5 text-primary-foreground/80 transition-colors hover:bg-white/10",
|
|
4210
|
+
children: hidden ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "h-5 w-5" })
|
|
4211
|
+
}
|
|
4212
|
+
)
|
|
4213
|
+
] }),
|
|
4214
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto mt-3 grid max-w-lg grid-cols-2 gap-3", children: [
|
|
4215
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4216
|
+
"button",
|
|
4217
|
+
{
|
|
4218
|
+
type: "button",
|
|
4219
|
+
onClick: () => navigate("/financial/receivables-list"),
|
|
4220
|
+
"aria-label": t2("financial.home.inflow"),
|
|
4221
|
+
className: "flex cursor-pointer items-center gap-2 rounded-2xl bg-white/10 px-3 py-2 text-left transition-colors hover:bg-white/20",
|
|
4222
|
+
children: [
|
|
4223
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-emerald-500/90", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUpRight, { className: "h-4 w-4 text-white" }) }),
|
|
4224
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
|
|
4225
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.inflow") }),
|
|
4226
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money(inflow) })
|
|
4227
|
+
] })
|
|
4228
|
+
]
|
|
4229
|
+
}
|
|
4230
|
+
),
|
|
4231
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4232
|
+
"button",
|
|
4233
|
+
{
|
|
4234
|
+
type: "button",
|
|
4235
|
+
onClick: () => navigate("/financial/payables-list"),
|
|
4236
|
+
"aria-label": t2("financial.home.outflow"),
|
|
4237
|
+
className: "flex cursor-pointer items-center gap-2 rounded-2xl bg-white/10 px-3 py-2 text-left transition-colors hover:bg-white/20",
|
|
4238
|
+
children: [
|
|
4239
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-rose-500/90", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownRight, { className: "h-4 w-4 text-white" }) }),
|
|
4240
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
|
|
4241
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.outflow") }),
|
|
4242
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money(outflow) })
|
|
4243
|
+
] })
|
|
4244
|
+
]
|
|
4245
|
+
}
|
|
4246
|
+
)
|
|
4247
|
+
] })
|
|
4248
|
+
] });
|
|
4249
|
+
}
|
|
4250
|
+
var SPEND_CATEGORIES = [
|
|
4251
|
+
{ key: "subscriptions", keywords: ["netflix", "spotify", "prime video", "disney", "hbo", " max", "youtube", "apple", "assinatura", "subscription"] },
|
|
4252
|
+
{ key: "groceries", keywords: ["supermercado", "mercado", "padaria", "hortifruti", "feira", "a\xE7ougue", "grocery", "market"] },
|
|
4253
|
+
{ key: "transport", keywords: ["uber", "99", "taxi", "posto", "shell", "ipiranga", "combust\xEDvel", "gasolina", "metr\xF4", "\xF4nibus", "transport", "fuel"] },
|
|
4254
|
+
{ key: "dining", keywords: ["ifood", "rappi", "restaurante", "outback", "lanchonete", "pizzaria", "caf\xE9", "cafeteria", "mcdonald", "burger", "food"] },
|
|
4255
|
+
{ key: "home", keywords: ["aluguel", "condom\xEDnio", "luz", "enel", "\xE1gua", "sabesp", "internet", "vivo", "claro", "g\xE1s", "iptu", "rent", "utilit"] },
|
|
4256
|
+
{ key: "health", keywords: ["farm\xE1cia", "farmacia", "drogasil", "drogaria", "academia", "smartfit", "cl\xEDnica", "hospital", "m\xE9dico", "sa\xFAde", "gym", "fitness", "health", "pharmac"] },
|
|
4257
|
+
{ key: "leisure", keywords: ["cinema", "cinemark", "ingresso", "show", "teatro", "viagem", "hotel", "steam", "leisure"] },
|
|
4258
|
+
{ key: "shopping", keywords: ["amazon", "magalu", "shopee", "aliexpress", "loja", "shopping", "zara", "renner", "store"] }
|
|
4259
|
+
];
|
|
4260
|
+
function categorizeSpend(notes) {
|
|
4261
|
+
const s = (notes ?? "").toLowerCase();
|
|
4262
|
+
for (const c of SPEND_CATEGORIES) if (c.keywords.some((k) => s.includes(k))) return c.key;
|
|
4263
|
+
return "other";
|
|
4264
|
+
}
|
|
4265
|
+
function SpendByCategoryChart() {
|
|
4266
|
+
const t2 = core.useTranslation();
|
|
4267
|
+
const provider = useFinancialProvider();
|
|
4268
|
+
const [rows, setRows] = React17__namespace.default.useState([]);
|
|
4269
|
+
React17.useEffect(() => {
|
|
4270
|
+
let alive = true;
|
|
4271
|
+
void (async () => {
|
|
4272
|
+
const res = await provider.getMovements({ status: "paid", direction: "debit" });
|
|
4273
|
+
if (!alive) return;
|
|
4274
|
+
const totals = /* @__PURE__ */ new Map();
|
|
4275
|
+
for (const m of res.data) {
|
|
4276
|
+
if (m.movementKind === "transfer") continue;
|
|
4277
|
+
const key = categorizeSpend(m.notes);
|
|
4278
|
+
totals.set(key, (totals.get(key) ?? 0) + (m.paidAmount || m.amount));
|
|
4279
|
+
}
|
|
4280
|
+
setRows(
|
|
4281
|
+
[...totals.entries()].sort((a, b) => b[1] - a[1]).map(([key, value]) => ({ name: t2(`financial.home.category.${key}`), value: Math.round(value * 100) / 100 }))
|
|
4282
|
+
);
|
|
4283
|
+
})();
|
|
4284
|
+
return () => {
|
|
4285
|
+
alive = false;
|
|
4286
|
+
};
|
|
4287
|
+
}, [provider]);
|
|
4288
|
+
if (rows.length === 0) {
|
|
4289
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { children: [
|
|
4290
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.CardTitle, { children: t2("financial.home.spendByCategory") }) }),
|
|
4291
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.CardContent, { className: "flex h-[200px] items-center justify-center text-sm text-muted-foreground", children: t2("financial.home.noSpend") })
|
|
4292
|
+
] });
|
|
4293
|
+
}
|
|
4294
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4295
|
+
ui.ChartWidget,
|
|
4296
|
+
{
|
|
4297
|
+
type: "pie",
|
|
4298
|
+
title: t2("financial.home.spendByCategory"),
|
|
4299
|
+
icon: "PieChart",
|
|
4300
|
+
categoryKey: "name",
|
|
4301
|
+
data: rows,
|
|
4302
|
+
series: [{ dataKey: "value", label: t2("financial.home.spendByCategory") }]
|
|
4303
|
+
}
|
|
4304
|
+
);
|
|
4305
|
+
}
|
|
4306
|
+
function UpcomingBills() {
|
|
4307
|
+
const t2 = core.useTranslation();
|
|
4308
|
+
const { currency } = useFinancialConfig();
|
|
4309
|
+
const provider = useFinancialProvider();
|
|
4310
|
+
const dueLabel = useDueLabel();
|
|
4311
|
+
const [bills, setBills] = React17__namespace.default.useState([]);
|
|
4312
|
+
React17.useEffect(() => {
|
|
4313
|
+
let alive = true;
|
|
4314
|
+
void (async () => {
|
|
4315
|
+
const pending2 = await provider.getMovements({ status: "pending" });
|
|
4316
|
+
if (!alive) return;
|
|
4317
|
+
setBills(
|
|
4318
|
+
pending2.data.filter((m) => m.direction === "debit" && m.movementKind === "bill").sort((a, b) => a.dueDate.localeCompare(b.dueDate))
|
|
4319
|
+
);
|
|
4320
|
+
})();
|
|
4321
|
+
return () => {
|
|
4322
|
+
alive = false;
|
|
4323
|
+
};
|
|
4324
|
+
}, [provider]);
|
|
4325
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.GoldCard, { className: "p-0 overflow-hidden", children: [
|
|
4326
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-4 pt-4", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold text-foreground", children: t2("financial.home.upcomingBills") }) }),
|
|
4327
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: bills.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-6 text-center text-xs text-muted-foreground", children: t2("financial.home.noBills") }) : bills.map((bill, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3.5" + (i > 0 ? " border-t border-border" : ""), children: [
|
|
4328
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
4329
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: bill.notes ?? t2("financial.home.bill") }),
|
|
4330
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: dueLabel(bill.dueDate) })
|
|
4331
|
+
] }),
|
|
4332
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums text-foreground", children: saas.formatCurrency(bill.amount - bill.paidAmount, currency) })
|
|
4333
|
+
] }, bill.id)) })
|
|
4334
|
+
] });
|
|
4335
|
+
}
|
|
4336
|
+
function CreditCards() {
|
|
4337
|
+
const t2 = core.useTranslation();
|
|
4338
|
+
const { currency } = useFinancialConfig();
|
|
4339
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
4340
|
+
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
4341
|
+
React17.useEffect(() => {
|
|
4342
|
+
void fetchBankAccounts();
|
|
4343
|
+
}, []);
|
|
4344
|
+
const cards = bankAccounts.filter((a) => a.accountType === "credit_card");
|
|
4345
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.GoldCard, { className: "bg-transparent border-0 p-0 shadow-none", children: [
|
|
4346
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mb-3 text-sm font-semibold text-foreground", children: t2("financial.home.cards") }),
|
|
4347
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
4348
|
+
cards.map((card) => {
|
|
4349
|
+
const fatura = Math.abs(Math.min(0, card.currentBalance));
|
|
4350
|
+
const limite = Math.max(0, (card.creditLimit ?? 0) - fatura);
|
|
4351
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl bg-gradient-to-br from-violet-600 to-indigo-700 p-4 text-white shadow-md", children: [
|
|
4352
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
4353
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: card.bankName ?? card.name }),
|
|
4354
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-5 w-5 opacity-80" })
|
|
4355
|
+
] }),
|
|
4356
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-6 text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.currentBill") }),
|
|
4357
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xl font-bold tabular-nums", children: saas.formatCurrency(fatura, currency) }),
|
|
4358
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2 text-[11px] opacity-70", children: [
|
|
4359
|
+
t2("financial.home.availableLimit"),
|
|
4360
|
+
" ",
|
|
4361
|
+
saas.formatCurrency(limite, currency)
|
|
4362
|
+
] })
|
|
4363
|
+
] }, card.id);
|
|
4364
|
+
}),
|
|
4365
|
+
cards.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center rounded-2xl border border-dashed border-border py-6 text-sm font-medium text-muted-foreground", children: [
|
|
4366
|
+
"+ ",
|
|
4367
|
+
t2("financial.home.addCard")
|
|
4368
|
+
] }) : null
|
|
4369
|
+
] })
|
|
4370
|
+
] });
|
|
4371
|
+
}
|
|
3337
4372
|
function createFinancialDashboardWidgets(ctx2) {
|
|
3338
4373
|
const withCtx = (Inner) => {
|
|
3339
4374
|
const Wrapped = () => /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config: ctx2.config, provider: ctx2.provider, store: ctx2.store, children: /* @__PURE__ */ jsxRuntime.jsx(Inner, {}) });
|
|
@@ -3350,7 +4385,19 @@ function createFinancialDashboardWidgets(ctx2) {
|
|
|
3350
4385
|
ui.defineChartWidget({ id: "financial.chart.cash-flow", title: "financial.summary.cashFlow", domain: "financial", span: 2, defaultOrder: 10, surfaces: ["plugin-home"], component: withCtx(CashFlowChart) }),
|
|
3351
4386
|
ui.defineCustomWidget({ id: "financial.panel.breakdown", title: "financial.summary.cashFlowBreakdown", domain: "financial", span: 1, defaultOrder: 11, surfaces: ["plugin-home"], component: withCtx(CashFlowBreakdown) }),
|
|
3352
4387
|
ui.defineCustomWidget({ id: "financial.panel.overdue", title: "financial.summary.overdueTitle", domain: "financial", span: 1, defaultOrder: 12, surfaces: ["plugin-home"], component: withCtx(OverdueAlerts) }),
|
|
3353
|
-
ui.defineTableWidget({ id: "financial.table.recent", title: "financial.summary.recentTransactions", domain: "financial", span: 4, defaultOrder: 20, surfaces: ["plugin-home"], component: withCtx(RecentTransactions) })
|
|
4388
|
+
ui.defineTableWidget({ id: "financial.table.recent", title: "financial.summary.recentTransactions", domain: "financial", span: 4, defaultOrder: 20, surfaces: ["plugin-home"], component: withCtx(RecentTransactions) }),
|
|
4389
|
+
// FINANCE-HOME surface (B2C phone-first dashboard) — a compact analytical
|
|
4390
|
+
// layout: hero band → charts row (spend-by-category + cash-flow) → bills +
|
|
4391
|
+
// cards. This is a dedicated consumer surface (norman-ai's money screen), NOT
|
|
4392
|
+
// the universal B2B 'home': a full-bleed balance hero and credit-card tiles
|
|
4393
|
+
// are personal-finance chrome that would leak into every app that merely
|
|
4394
|
+
// enables plugin-financial (e.g. the beauty-saas Painel). Apps that want them
|
|
4395
|
+
// opt in explicitly by rendering <DashboardCanvas surface="finance-home">.
|
|
4396
|
+
ui.defineCustomWidget({ id: "financial.hero.balance", title: "financial.home.balance", domain: "financial", span: 4, defaultOrder: 0, surfaces: ["finance-home"], component: withCtx(BalanceHero) }),
|
|
4397
|
+
ui.defineChartWidget({ id: "financial.chart.spend-by-category", title: "financial.home.spendByCategory", domain: "financial", span: 2, defaultOrder: 1, surfaces: ["finance-home"], component: withCtx(SpendByCategoryChart) }),
|
|
4398
|
+
ui.defineChartWidget({ id: "financial.chart.cash-flow-home", title: "financial.summary.cashFlow", domain: "financial", span: 2, defaultOrder: 2, surfaces: ["finance-home"], component: withCtx(CashFlowChart) }),
|
|
4399
|
+
ui.defineCustomWidget({ id: "financial.list.upcoming-bills", title: "financial.home.upcomingBills", domain: "financial", span: 2, defaultOrder: 3, surfaces: ["finance-home"], component: withCtx(UpcomingBills) }),
|
|
4400
|
+
ui.defineCustomWidget({ id: "financial.cards", title: "financial.home.cards", domain: "financial", span: 2, defaultOrder: 4, surfaces: ["finance-home"], component: withCtx(CreditCards) })
|
|
3354
4401
|
];
|
|
3355
4402
|
}
|
|
3356
4403
|
|
|
@@ -3383,9 +4430,10 @@ function paginate(items, page, pageSize) {
|
|
|
3383
4430
|
const start = (p - 1) * ps;
|
|
3384
4431
|
return { data: items.slice(start, start + ps), total: items.length };
|
|
3385
4432
|
}
|
|
3386
|
-
function createStore(
|
|
4433
|
+
function createStore(options) {
|
|
3387
4434
|
const tenantId = "mock-tenant";
|
|
3388
|
-
const
|
|
4435
|
+
const seed = options?.seed;
|
|
4436
|
+
const defaultPMTypes = (options?.paymentMethodTypes ?? [
|
|
3389
4437
|
{ name: "Cash", transactionType: "cash" },
|
|
3390
4438
|
{ name: "Credit Card", transactionType: "credit_card" },
|
|
3391
4439
|
{ name: "Debit Card", transactionType: "debit_card" },
|
|
@@ -3423,19 +4471,23 @@ function createStore(seedPaymentMethodTypes) {
|
|
|
3423
4471
|
updatedAt: now()
|
|
3424
4472
|
};
|
|
3425
4473
|
return {
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
4474
|
+
// Seeded data (when provided) makes the module render as if the user already
|
|
4475
|
+
// has accounts + transactions — a populated Summary/Statements instead of the
|
|
4476
|
+
// empty onboarding state. Seed arrays are copied so the caller's data is not mutated.
|
|
4477
|
+
invoices: seed?.invoices ? [...seed.invoices] : [],
|
|
4478
|
+
invoiceItems: seed?.invoiceItems ? [...seed.invoiceItems] : [],
|
|
4479
|
+
movements: seed?.movements ? [...seed.movements] : [],
|
|
4480
|
+
bankAccounts: seed?.bankAccounts ? [...seed.bankAccounts] : [cashAccount, bankAccount],
|
|
3430
4481
|
cashSessions: [],
|
|
3431
|
-
paymentMethods: [],
|
|
4482
|
+
paymentMethods: seed?.paymentMethods ? [...seed.paymentMethods] : [],
|
|
3432
4483
|
paymentMethodTypes: defaultPMTypes,
|
|
3433
4484
|
chartOfAccounts: [],
|
|
4485
|
+
costCenters: [],
|
|
3434
4486
|
cardTransactions: []
|
|
3435
4487
|
};
|
|
3436
4488
|
}
|
|
3437
4489
|
function createMockFinancialProvider(options) {
|
|
3438
|
-
const store = createStore();
|
|
4490
|
+
const store = createStore(options);
|
|
3439
4491
|
const tenantId = "mock-tenant";
|
|
3440
4492
|
function recalcInvoiceStatus(invoice) {
|
|
3441
4493
|
if (invoice.paidAmount >= invoice.totalAmount) {
|
|
@@ -3456,6 +4508,14 @@ function createMockFinancialProvider(options) {
|
|
|
3456
4508
|
if (query.direction) results = results.filter((i) => i.direction === query.direction);
|
|
3457
4509
|
if (query.status) results = results.filter((i) => matchesStatus(i.status, query.status));
|
|
3458
4510
|
if (query.contactId) results = results.filter((i) => i.contactId === query.contactId);
|
|
4511
|
+
if (query.accountId) {
|
|
4512
|
+
const invoiceIds = new Set(store.invoiceItems.filter((item) => item.accountId === query.accountId).map((item) => item.invoiceId));
|
|
4513
|
+
results = results.filter((invoice) => invoiceIds.has(invoice.id));
|
|
4514
|
+
}
|
|
4515
|
+
if (query.costCenterId) {
|
|
4516
|
+
const invoiceIds = new Set(store.invoiceItems.filter((item) => item.costCenterId === query.costCenterId).map((item) => item.invoiceId));
|
|
4517
|
+
results = results.filter((invoice) => invoiceIds.has(invoice.id));
|
|
4518
|
+
}
|
|
3459
4519
|
if (query.dateRange) results = results.filter((i) => matchesDateRange(i.invoiceDate, query.dateRange));
|
|
3460
4520
|
if (query.search) {
|
|
3461
4521
|
const s = query.search.toLowerCase();
|
|
@@ -3778,6 +4838,9 @@ function createMockFinancialProvider(options) {
|
|
|
3778
4838
|
async getChartOfAccounts() {
|
|
3779
4839
|
return store.chartOfAccounts.filter((n) => n.isActive);
|
|
3780
4840
|
},
|
|
4841
|
+
async getCostCenters() {
|
|
4842
|
+
return store.costCenters.filter((c) => c.isActive);
|
|
4843
|
+
},
|
|
3781
4844
|
// --- Card Transactions ---
|
|
3782
4845
|
async getCardTransactions(dateRange) {
|
|
3783
4846
|
let results = [...store.cardTransactions];
|
|
@@ -3865,7 +4928,9 @@ function createMockFinancialProvider(options) {
|
|
|
3865
4928
|
(m) => m.direction === "debit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
|
|
3866
4929
|
);
|
|
3867
4930
|
const todayStr = today();
|
|
3868
|
-
const
|
|
4931
|
+
const monthAgo = /* @__PURE__ */ new Date();
|
|
4932
|
+
monthAgo.setDate(monthAgo.getDate() - 30);
|
|
4933
|
+
const monthStart = monthAgo.toISOString().slice(0, 10);
|
|
3869
4934
|
const monthEnd = todayStr;
|
|
3870
4935
|
const monthMovements = store.movements.filter(
|
|
3871
4936
|
(m) => m.movementKind === "payment" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
|
|
@@ -3972,6 +5037,18 @@ function createMockFinancialProvider(options) {
|
|
|
3972
5037
|
return provider;
|
|
3973
5038
|
}
|
|
3974
5039
|
|
|
5040
|
+
// src/data/tables.ts
|
|
5041
|
+
var T = {
|
|
5042
|
+
movements: "plg_financial_movements",
|
|
5043
|
+
chartOfAccounts: "plg_financial_chart_of_accounts",
|
|
5044
|
+
bankAccounts: "plg_financial_bank_accounts",
|
|
5045
|
+
cardBrands: "plg_financial_card_brands",
|
|
5046
|
+
costCenters: "plg_financial_cost_centers",
|
|
5047
|
+
cashRegisterSessions: "plg_financial_cash_register_sessions",
|
|
5048
|
+
paymentMethods: "plg_financial_payment_methods",
|
|
5049
|
+
paymentMethodTypes: "plg_financial_payment_method_types"
|
|
5050
|
+
};
|
|
5051
|
+
|
|
3975
5052
|
// src/data/supabase.ts
|
|
3976
5053
|
function getTenantId() {
|
|
3977
5054
|
return core.getActiveTenantId();
|
|
@@ -3994,7 +5071,7 @@ function camelToSnake(obj) {
|
|
|
3994
5071
|
function getClients() {
|
|
3995
5072
|
const supabase = core.getSupabaseClientOptional();
|
|
3996
5073
|
if (!supabase) throw new Error("Supabase not initialized");
|
|
3997
|
-
return { core: supabase
|
|
5074
|
+
return { core: supabase, pub: supabase };
|
|
3998
5075
|
}
|
|
3999
5076
|
function mapStageToInvoiceStatus(stage) {
|
|
4000
5077
|
switch (stage) {
|
|
@@ -4021,11 +5098,11 @@ async function sweepOverdue() {
|
|
|
4021
5098
|
try {
|
|
4022
5099
|
const { core, pub } = getClients();
|
|
4023
5100
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4024
|
-
const { data: overdueMovs } = await pub.from(
|
|
5101
|
+
const { data: overdueMovs } = await pub.from(T.movements).update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today2).select("invoice_id");
|
|
4025
5102
|
if (overdueMovs && overdueMovs.length > 0) {
|
|
4026
5103
|
const invoiceIds = [...new Set(overdueMovs.map((m) => m.invoice_id).filter(Boolean))];
|
|
4027
5104
|
for (const invId of invoiceIds) {
|
|
4028
|
-
const { data: allMovs } = await pub.from(
|
|
5105
|
+
const { data: allMovs } = await pub.from(T.movements).select("status").eq("invoice_id", invId).eq("movement_kind", "bill");
|
|
4029
5106
|
const movs = allMovs ?? [];
|
|
4030
5107
|
const hasOverdue = movs.some((m) => m.status === "overdue");
|
|
4031
5108
|
const allPaid = movs.every((m) => m.status === "paid");
|
|
@@ -4049,9 +5126,21 @@ async function fetchInvoiceBalances(pub, ids) {
|
|
|
4049
5126
|
function round22(n) {
|
|
4050
5127
|
return Math.round(n * 100) / 100;
|
|
4051
5128
|
}
|
|
5129
|
+
async function findOrderIdsByItemMetadata(core, filters) {
|
|
5130
|
+
if (!filters.accountId && !filters.costCenterId) return void 0;
|
|
5131
|
+
let qb = core.from("order_items").select("order_id");
|
|
5132
|
+
if (filters.accountId) qb = qb.filter("metadata->>accountId", "eq", filters.accountId);
|
|
5133
|
+
if (filters.costCenterId) qb = qb.filter("metadata->>costCenterId", "eq", filters.costCenterId);
|
|
5134
|
+
const { data } = await qb;
|
|
5135
|
+
const ids = [];
|
|
5136
|
+
for (const row of data ?? []) {
|
|
5137
|
+
if (typeof row.order_id === "string" && row.order_id.length > 0) ids.push(row.order_id);
|
|
5138
|
+
}
|
|
5139
|
+
return Array.from(new Set(ids));
|
|
5140
|
+
}
|
|
4052
5141
|
async function getMethodFeePct(pub, paymentMethodId) {
|
|
4053
5142
|
if (!paymentMethodId) return 0;
|
|
4054
|
-
const { data } = await pub.from(
|
|
5143
|
+
const { data } = await pub.from(T.paymentMethods).select("discount_value").eq("id", paymentMethodId).single();
|
|
4055
5144
|
return Number(data?.discount_value) || 0;
|
|
4056
5145
|
}
|
|
4057
5146
|
function genId() {
|
|
@@ -4076,6 +5165,14 @@ function createSupabaseFinancialProvider() {
|
|
|
4076
5165
|
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
4077
5166
|
qb = qb.in("status", statuses);
|
|
4078
5167
|
}
|
|
5168
|
+
const itemScopedOrderIds = await findOrderIdsByItemMetadata(core, {
|
|
5169
|
+
accountId: query.accountId,
|
|
5170
|
+
costCenterId: query.costCenterId
|
|
5171
|
+
});
|
|
5172
|
+
if (itemScopedOrderIds) {
|
|
5173
|
+
if (itemScopedOrderIds.length === 0) return { data: [], total: 0 };
|
|
5174
|
+
qb = qb.in("id", itemScopedOrderIds);
|
|
5175
|
+
}
|
|
4079
5176
|
if (query.contactId) qb = qb.eq("party_id", query.contactId);
|
|
4080
5177
|
if (query.search) qb = qb.ilike("notes", `%${query.search}%`);
|
|
4081
5178
|
if (query.dateRange) qb = qb.gte("created_at", query.dateRange.from).lte("created_at", query.dateRange.to);
|
|
@@ -4115,7 +5212,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4115
5212
|
const o = snakeToCamel(data);
|
|
4116
5213
|
const meta = data.metadata ?? {};
|
|
4117
5214
|
const balance = (await fetchInvoiceBalances(pub, [id])).get(id);
|
|
4118
|
-
const { data: movs } = await pub.from(
|
|
5215
|
+
const { data: movs } = await pub.from(T.movements).select("paid_amount").eq("invoice_id", id).eq("movement_kind", "bill");
|
|
4119
5216
|
return {
|
|
4120
5217
|
id: o.id,
|
|
4121
5218
|
direction: o.direction ?? (o.kind === "invoice_payable" ? "debit" : "credit"),
|
|
@@ -4151,6 +5248,8 @@ function createSupabaseFinancialProvider() {
|
|
|
4151
5248
|
discount: i.discount ?? 0,
|
|
4152
5249
|
surcharge: 0,
|
|
4153
5250
|
referenceId: i.productId ?? i.serviceId,
|
|
5251
|
+
accountId: i.metadata?.accountId,
|
|
5252
|
+
costCenterId: i.metadata?.costCenterId,
|
|
4154
5253
|
createdAt: i.createdAt
|
|
4155
5254
|
};
|
|
4156
5255
|
});
|
|
@@ -4190,12 +5289,17 @@ function createSupabaseFinancialProvider() {
|
|
|
4190
5289
|
discount: item.discount ?? 0,
|
|
4191
5290
|
total: item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
|
|
4192
5291
|
sort_order: i,
|
|
4193
|
-
metadata: {
|
|
5292
|
+
metadata: {
|
|
5293
|
+
itemKind: item.itemKind,
|
|
5294
|
+
referenceId: item.referenceId,
|
|
5295
|
+
accountId: item.accountId,
|
|
5296
|
+
costCenterId: item.costCenterId
|
|
5297
|
+
}
|
|
4194
5298
|
}))
|
|
4195
5299
|
);
|
|
4196
5300
|
}
|
|
4197
5301
|
if (order && input.installments.length > 0) {
|
|
4198
|
-
await pub.from(
|
|
5302
|
+
await pub.from(T.movements).insert(
|
|
4199
5303
|
input.installments.map((inst, i) => ({
|
|
4200
5304
|
tenant_id: tenantId,
|
|
4201
5305
|
invoice_id: order.id,
|
|
@@ -4237,12 +5341,12 @@ function createSupabaseFinancialProvider() {
|
|
|
4237
5341
|
async cancelInvoice(id) {
|
|
4238
5342
|
const { core, pub } = getClients();
|
|
4239
5343
|
await core.from("orders").update({ status: "cancelled" }).eq("id", id);
|
|
4240
|
-
await pub.from(
|
|
5344
|
+
await pub.from(T.movements).update({ status: "cancelled" }).eq("invoice_id", id).eq("status", "pending");
|
|
4241
5345
|
},
|
|
4242
5346
|
// --- Movements (public.financial_movements) ---
|
|
4243
5347
|
async getMovements(query) {
|
|
4244
5348
|
const { pub } = getClients();
|
|
4245
|
-
let qb = pub.from(
|
|
5349
|
+
let qb = pub.from(T.movements).select("*", { count: "exact" });
|
|
4246
5350
|
if (query.direction) qb = qb.eq("direction", query.direction);
|
|
4247
5351
|
if (query.status) {
|
|
4248
5352
|
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
@@ -4259,7 +5363,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4259
5363
|
},
|
|
4260
5364
|
async payMovement(input) {
|
|
4261
5365
|
const { core, pub } = getClients();
|
|
4262
|
-
const { data: mov } = await pub.from(
|
|
5366
|
+
const { data: mov } = await pub.from(T.movements).select("*").eq("id", input.movementId).single();
|
|
4263
5367
|
if (!mov) throw new Error("Movement not found");
|
|
4264
5368
|
const tenantId = mov.tenant_id ?? getTenantId();
|
|
4265
5369
|
const feePct = mov.direction === "credit" ? await getMethodFeePct(pub, input.paymentMethodId) : 0;
|
|
@@ -4284,17 +5388,17 @@ function createSupabaseFinancialProvider() {
|
|
|
4284
5388
|
card_installments: input.cardInstallments ?? null,
|
|
4285
5389
|
notes: input.notes ?? null
|
|
4286
5390
|
};
|
|
4287
|
-
let { data, error } = await pub.from(
|
|
5391
|
+
let { data, error } = await pub.from(T.movements).insert(paymentRow).select().single();
|
|
4288
5392
|
if (error && /fee_amount/i.test(error.message ?? "")) {
|
|
4289
5393
|
const { fee_amount, ...rest } = paymentRow;
|
|
4290
|
-
({ data, error } = await pub.from(
|
|
5394
|
+
({ data, error } = await pub.from(T.movements).insert(rest).select().single());
|
|
4291
5395
|
}
|
|
4292
5396
|
if (error) throw error;
|
|
4293
5397
|
const newPaid = round22((mov.paid_amount ?? 0) + input.amount);
|
|
4294
5398
|
const billStatus = newPaid >= mov.amount ? "paid" : "partial";
|
|
4295
|
-
await pub.from(
|
|
5399
|
+
await pub.from(T.movements).update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
|
|
4296
5400
|
if (mov.invoice_id) {
|
|
4297
|
-
const { data: allMovs } = await pub.from(
|
|
5401
|
+
const { data: allMovs } = await pub.from(T.movements).select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
|
|
4298
5402
|
const movs = allMovs ?? [];
|
|
4299
5403
|
const allPaid = movs.length > 0 && movs.every((m) => m.status === "paid");
|
|
4300
5404
|
const anyPaid = movs.some((m) => m.status === "paid" || m.status === "partial");
|
|
@@ -4311,7 +5415,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4311
5415
|
},
|
|
4312
5416
|
async cancelMovement(id) {
|
|
4313
5417
|
const { pub } = getClients();
|
|
4314
|
-
await pub.from(
|
|
5418
|
+
await pub.from(T.movements).update({ status: "cancelled" }).eq("id", id);
|
|
4315
5419
|
},
|
|
4316
5420
|
// --- Transfers (between accounts) ---
|
|
4317
5421
|
async createTransfer(input) {
|
|
@@ -4347,10 +5451,10 @@ function createSupabaseFinancialProvider() {
|
|
|
4347
5451
|
metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
|
|
4348
5452
|
}
|
|
4349
5453
|
];
|
|
4350
|
-
let { data, error } = await pub.from(
|
|
5454
|
+
let { data, error } = await pub.from(T.movements).insert(rowsToInsert).select("id, direction");
|
|
4351
5455
|
if (error && /fee_amount/i.test(error.message ?? "")) {
|
|
4352
5456
|
const stripped = rowsToInsert.map(({ fee_amount, ...rest }) => rest);
|
|
4353
|
-
({ data, error } = await pub.from(
|
|
5457
|
+
({ data, error } = await pub.from(T.movements).insert(stripped).select("id, direction"));
|
|
4354
5458
|
}
|
|
4355
5459
|
if (error) throw error;
|
|
4356
5460
|
const rows = data ?? [];
|
|
@@ -4361,13 +5465,13 @@ function createSupabaseFinancialProvider() {
|
|
|
4361
5465
|
// --- Bank Accounts ---
|
|
4362
5466
|
async getBankAccounts() {
|
|
4363
5467
|
const { pub } = getClients();
|
|
4364
|
-
const { data } = await pub.from(
|
|
5468
|
+
const { data } = await pub.from(T.bankAccounts).select("*").eq("is_active", true).order("name");
|
|
4365
5469
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
4366
5470
|
},
|
|
4367
5471
|
async createBankAccount(input) {
|
|
4368
5472
|
const { pub } = getClients();
|
|
4369
5473
|
const tenantId = getTenantId();
|
|
4370
|
-
const { data } = await pub.from(
|
|
5474
|
+
const { data } = await pub.from(T.bankAccounts).insert({
|
|
4371
5475
|
...camelToSnake(input),
|
|
4372
5476
|
tenant_id: tenantId,
|
|
4373
5477
|
current_balance: input.initialBalance ?? 0
|
|
@@ -4379,26 +5483,26 @@ function createSupabaseFinancialProvider() {
|
|
|
4379
5483
|
const row = camelToSnake(partial);
|
|
4380
5484
|
delete row.id;
|
|
4381
5485
|
delete row.tenant_id;
|
|
4382
|
-
const { data } = await pub.from(
|
|
5486
|
+
const { data } = await pub.from(T.bankAccounts).update(row).eq("id", id).select().single();
|
|
4383
5487
|
return snakeToCamel(data);
|
|
4384
5488
|
},
|
|
4385
5489
|
// --- Cash Sessions ---
|
|
4386
5490
|
async getCashSessions(bankAccountId) {
|
|
4387
5491
|
const { pub } = getClients();
|
|
4388
|
-
let qb = pub.from(
|
|
5492
|
+
let qb = pub.from(T.cashRegisterSessions).select("*");
|
|
4389
5493
|
if (bankAccountId) qb = qb.eq("bank_account_id", bankAccountId);
|
|
4390
5494
|
const { data } = await qb.order("opened_at", { ascending: false });
|
|
4391
5495
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
4392
5496
|
},
|
|
4393
5497
|
async getOpenSession(bankAccountId) {
|
|
4394
5498
|
const { pub } = getClients();
|
|
4395
|
-
const { data } = await pub.from(
|
|
5499
|
+
const { data } = await pub.from(T.cashRegisterSessions).select("*").eq("bank_account_id", bankAccountId).eq("status", "open").single();
|
|
4396
5500
|
return data ? snakeToCamel(data) : null;
|
|
4397
5501
|
},
|
|
4398
5502
|
async openCashSession(input) {
|
|
4399
5503
|
const { pub } = getClients();
|
|
4400
5504
|
const tenantId = getTenantId();
|
|
4401
|
-
const { data } = await pub.from(
|
|
5505
|
+
const { data } = await pub.from(T.cashRegisterSessions).insert({
|
|
4402
5506
|
tenant_id: tenantId,
|
|
4403
5507
|
bank_account_id: input.bankAccountId,
|
|
4404
5508
|
status: "open",
|
|
@@ -4411,7 +5515,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4411
5515
|
},
|
|
4412
5516
|
async closeCashSession(input) {
|
|
4413
5517
|
const { pub } = getClients();
|
|
4414
|
-
const { data } = await pub.from(
|
|
5518
|
+
const { data } = await pub.from(T.cashRegisterSessions).update({
|
|
4415
5519
|
status: "closed",
|
|
4416
5520
|
closing_balance: input.closingBalance,
|
|
4417
5521
|
closed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4423,8 +5527,8 @@ function createSupabaseFinancialProvider() {
|
|
|
4423
5527
|
},
|
|
4424
5528
|
async getCashSessionSummary(sessionId) {
|
|
4425
5529
|
const { pub } = getClients();
|
|
4426
|
-
const session = (await pub.from(
|
|
4427
|
-
const { data: movs } = await pub.from(
|
|
5530
|
+
const session = (await pub.from(T.cashRegisterSessions).select("*").eq("id", sessionId).single()).data;
|
|
5531
|
+
const { data: movs } = await pub.from(T.movements).select("direction, paid_amount").eq("cash_session_id", sessionId).eq("status", "paid");
|
|
4428
5532
|
const movements = movs ?? [];
|
|
4429
5533
|
return {
|
|
4430
5534
|
session: snakeToCamel(session),
|
|
@@ -4436,18 +5540,23 @@ function createSupabaseFinancialProvider() {
|
|
|
4436
5540
|
// --- Payment Methods ---
|
|
4437
5541
|
async getPaymentMethods() {
|
|
4438
5542
|
const { pub } = getClients();
|
|
4439
|
-
const { data } = await pub.from(
|
|
5543
|
+
const { data } = await pub.from(T.paymentMethods).select("*").eq("is_active", true).order("name");
|
|
4440
5544
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
4441
5545
|
},
|
|
4442
5546
|
async getPaymentMethodTypes() {
|
|
4443
5547
|
const { pub } = getClients();
|
|
4444
|
-
const { data } = await pub.from(
|
|
5548
|
+
const { data } = await pub.from(T.paymentMethodTypes).select("*").eq("is_active", true).order("name");
|
|
4445
5549
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
4446
5550
|
},
|
|
4447
5551
|
// --- Chart of Accounts ---
|
|
4448
5552
|
async getChartOfAccounts() {
|
|
4449
5553
|
const { pub } = getClients();
|
|
4450
|
-
const { data } = await pub.from(
|
|
5554
|
+
const { data } = await pub.from(T.chartOfAccounts).select("*").eq("is_active", true).order("code");
|
|
5555
|
+
return (data ?? []).map((r) => snakeToCamel(r));
|
|
5556
|
+
},
|
|
5557
|
+
async getCostCenters() {
|
|
5558
|
+
const { pub } = getClients();
|
|
5559
|
+
const { data } = await pub.from(T.costCenters).select("*").eq("is_active", true).order("code");
|
|
4451
5560
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
4452
5561
|
},
|
|
4453
5562
|
// --- Card Transactions ---
|
|
@@ -4459,7 +5568,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4459
5568
|
async getStatement(query) {
|
|
4460
5569
|
const { core, pub } = getClients();
|
|
4461
5570
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4462
|
-
const { data: acctRows } = await pub.from(
|
|
5571
|
+
const { data: acctRows } = await pub.from(T.bankAccounts).select("id, name, initial_balance, is_active");
|
|
4463
5572
|
const allAccounts = acctRows ?? [];
|
|
4464
5573
|
const consolidated = !query.bankAccountId;
|
|
4465
5574
|
const scopeAccounts = consolidated ? allAccounts.filter((a) => a.is_active) : allAccounts.filter((a) => a.id === query.bankAccountId);
|
|
@@ -4471,10 +5580,10 @@ function createSupabaseFinancialProvider() {
|
|
|
4471
5580
|
}
|
|
4472
5581
|
const scopeFilter = (qb) => consolidated ? qb : qb.in("bank_account_id", accountIds);
|
|
4473
5582
|
const netOf = (r) => r.direction === "credit" ? (Number(r.paid_amount) || 0) - (Number(r.fee_amount) || 0) : -(Number(r.paid_amount) || 0);
|
|
4474
|
-
const { data: priorRows } = await scopeFilter(pub.from(
|
|
5583
|
+
const { data: priorRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
|
|
4475
5584
|
const priorNet = (priorRows ?? []).reduce((s, r) => s + netOf(r), 0);
|
|
4476
5585
|
const openingBalance = round22(initialSum + priorNet);
|
|
4477
|
-
const { data: movs } = await scopeFilter(pub.from(
|
|
5586
|
+
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"));
|
|
4478
5587
|
const invoiceIds = [...new Set((movs ?? []).map((r) => r.invoice_id).filter(Boolean))];
|
|
4479
5588
|
const invoiceById = /* @__PURE__ */ new Map();
|
|
4480
5589
|
if (invoiceIds.length) {
|
|
@@ -4516,7 +5625,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4516
5625
|
});
|
|
4517
5626
|
const matchedIds = [...new Set(entries.map((e) => e.movement.matchedMovementId).filter(Boolean))];
|
|
4518
5627
|
if (matchedIds.length) {
|
|
4519
|
-
const { data: matched } = await pub.from(
|
|
5628
|
+
const { data: matched } = await pub.from(T.movements).select("id, invoice_id, notes").in("id", matchedIds);
|
|
4520
5629
|
const matchedById = new Map((matched ?? []).map((m) => [m.id, m]));
|
|
4521
5630
|
const matchedInvoiceIds = [...new Set((matched ?? []).map((m) => m.invoice_id).filter(Boolean))];
|
|
4522
5631
|
const invById = /* @__PURE__ */ new Map();
|
|
@@ -4538,7 +5647,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4538
5647
|
};
|
|
4539
5648
|
}
|
|
4540
5649
|
}
|
|
4541
|
-
const { data: todayRows } = await scopeFilter(pub.from(
|
|
5650
|
+
const { data: todayRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lte("payment_date", today2));
|
|
4542
5651
|
const accountCurrentBalance = round22(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
|
|
4543
5652
|
return {
|
|
4544
5653
|
entries,
|
|
@@ -4556,9 +5665,9 @@ function createSupabaseFinancialProvider() {
|
|
|
4556
5665
|
async getSummary(dateRange) {
|
|
4557
5666
|
sweepOverdue();
|
|
4558
5667
|
const { pub } = getClients();
|
|
4559
|
-
const { data: accounts } = await pub.from(
|
|
5668
|
+
const { data: accounts } = await pub.from(T.bankAccounts).select("current_balance").eq("is_active", true);
|
|
4560
5669
|
const totalBalance = (accounts ?? []).reduce((s, a) => s + (a.current_balance ?? 0), 0);
|
|
4561
|
-
const { data: movs } = await pub.from(
|
|
5670
|
+
const { data: movs } = await pub.from(T.movements).select("direction, movement_kind, amount, paid_amount, status, due_date, payment_date");
|
|
4562
5671
|
const allMovs = movs ?? [];
|
|
4563
5672
|
const unpaid = allMovs.filter((m) => ["pending", "partial", "overdue"].includes(m.status));
|
|
4564
5673
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -4566,7 +5675,9 @@ function createSupabaseFinancialProvider() {
|
|
|
4566
5675
|
const unpaidDebit = unpaid.filter((m) => m.direction === "debit");
|
|
4567
5676
|
const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today2);
|
|
4568
5677
|
const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today2);
|
|
4569
|
-
const
|
|
5678
|
+
const monthAgo = /* @__PURE__ */ new Date();
|
|
5679
|
+
monthAgo.setDate(monthAgo.getDate() - 30);
|
|
5680
|
+
const monthStart = monthAgo.toISOString().slice(0, 10);
|
|
4570
5681
|
const monthPaid = allMovs.filter((m) => m.movement_kind === "payment" && m.payment_date >= monthStart);
|
|
4571
5682
|
return {
|
|
4572
5683
|
totalBalance,
|
|
@@ -4589,7 +5700,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4589
5700
|
const tenantId = getTenantId();
|
|
4590
5701
|
let imported = 0, duplicates = 0;
|
|
4591
5702
|
for (const line of input.lines) {
|
|
4592
|
-
const { error } = await pub.from(
|
|
5703
|
+
const { error } = await pub.from(T.movements).insert({
|
|
4593
5704
|
tenant_id: tenantId,
|
|
4594
5705
|
direction: line.direction,
|
|
4595
5706
|
movement_kind: "payment",
|
|
@@ -4612,7 +5723,7 @@ function createSupabaseFinancialProvider() {
|
|
|
4612
5723
|
},
|
|
4613
5724
|
async getUnreconciled(query) {
|
|
4614
5725
|
const { pub } = getClients();
|
|
4615
|
-
let qb = pub.from(
|
|
5726
|
+
let qb = pub.from(T.movements).select("*").not("external_source", "is", null).is("reconciled_at", null);
|
|
4616
5727
|
if (query?.bankAccountId) qb = qb.eq("bank_account_id", query.bankAccountId);
|
|
4617
5728
|
if (query?.dateRange) qb = qb.gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to);
|
|
4618
5729
|
const { data } = await qb.order("payment_date", { ascending: false });
|
|
@@ -4620,12 +5731,12 @@ function createSupabaseFinancialProvider() {
|
|
|
4620
5731
|
},
|
|
4621
5732
|
async suggestReconciliation(bankMovementId) {
|
|
4622
5733
|
const { core, pub } = getClients();
|
|
4623
|
-
const { data: line } = await pub.from(
|
|
5734
|
+
const { data: line } = await pub.from(T.movements).select("*").eq("id", bankMovementId).single();
|
|
4624
5735
|
if (!line) return [];
|
|
4625
5736
|
const amount = Number(line.amount) || 0;
|
|
4626
5737
|
const lineDate = line.payment_date ?? line.due_date;
|
|
4627
5738
|
const tol = Math.max(0.01, round22(amount * 0.02));
|
|
4628
|
-
const { data } = await pub.from(
|
|
5739
|
+
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);
|
|
4629
5740
|
const candidates = (data ?? []).map((r) => {
|
|
4630
5741
|
const amtDiff = Math.abs((Number(r.amount) || 0) - amount);
|
|
4631
5742
|
const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
|
|
@@ -4651,20 +5762,20 @@ function createSupabaseFinancialProvider() {
|
|
|
4651
5762
|
async reconcileMovement(input) {
|
|
4652
5763
|
const { pub } = getClients();
|
|
4653
5764
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
4654
|
-
await pub.from(
|
|
5765
|
+
await pub.from(T.movements).update({
|
|
4655
5766
|
reconciled_at: now2,
|
|
4656
5767
|
matched_movement_id: input.matchedMovementId ?? null
|
|
4657
5768
|
}).eq("id", input.bankMovementId);
|
|
4658
5769
|
if (input.matchedMovementId) {
|
|
4659
|
-
await pub.from(
|
|
5770
|
+
await pub.from(T.movements).update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
|
|
4660
5771
|
}
|
|
4661
5772
|
},
|
|
4662
5773
|
async unreconcileMovement(bankMovementId) {
|
|
4663
5774
|
const { pub } = getClients();
|
|
4664
|
-
const { data: line } = await pub.from(
|
|
4665
|
-
await pub.from(
|
|
5775
|
+
const { data: line } = await pub.from(T.movements).select("matched_movement_id").eq("id", bankMovementId).single();
|
|
5776
|
+
await pub.from(T.movements).update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
|
|
4666
5777
|
if (line?.matched_movement_id) {
|
|
4667
|
-
await pub.from(
|
|
5778
|
+
await pub.from(T.movements).update({ reconciled_at: null }).eq("id", line.matched_movement_id);
|
|
4668
5779
|
}
|
|
4669
5780
|
}
|
|
4670
5781
|
};
|
|
@@ -4757,6 +5868,82 @@ function createFinancialStore(provider) {
|
|
|
4757
5868
|
throw err;
|
|
4758
5869
|
}
|
|
4759
5870
|
},
|
|
5871
|
+
async createQuickTransaction(input) {
|
|
5872
|
+
try {
|
|
5873
|
+
if (input.type === "transfer") {
|
|
5874
|
+
if (!input.bankAccountId || !input.toAccountId) {
|
|
5875
|
+
throw new Error("Select source and destination accounts");
|
|
5876
|
+
}
|
|
5877
|
+
await provider.createTransfer({
|
|
5878
|
+
fromAccountId: input.bankAccountId,
|
|
5879
|
+
toAccountId: input.toAccountId,
|
|
5880
|
+
amount: input.amount,
|
|
5881
|
+
date: input.date,
|
|
5882
|
+
notes: input.description?.trim() || void 0
|
|
5883
|
+
});
|
|
5884
|
+
} else {
|
|
5885
|
+
const direction = input.type === "income" ? "credit" : "debit";
|
|
5886
|
+
const desc = input.description?.trim() || (input.type === "income" ? "Receita" : "Despesa");
|
|
5887
|
+
const invoiceMeta = {};
|
|
5888
|
+
if (input.recurring) invoiceMeta.recurring = true;
|
|
5889
|
+
if (input.receiptUrl) invoiceMeta.receiptUrl = input.receiptUrl;
|
|
5890
|
+
const invoice = await provider.createInvoice({
|
|
5891
|
+
direction,
|
|
5892
|
+
invoiceDate: input.date,
|
|
5893
|
+
contactName: desc,
|
|
5894
|
+
metadata: Object.keys(invoiceMeta).length > 0 ? invoiceMeta : void 0,
|
|
5895
|
+
items: [{
|
|
5896
|
+
itemKind: "other",
|
|
5897
|
+
description: desc,
|
|
5898
|
+
quantity: 1,
|
|
5899
|
+
unitPrice: input.amount,
|
|
5900
|
+
accountId: input.categoryId
|
|
5901
|
+
}],
|
|
5902
|
+
installments: [{
|
|
5903
|
+
direction,
|
|
5904
|
+
movementKind: "bill",
|
|
5905
|
+
amount: input.amount,
|
|
5906
|
+
dueDate: input.date,
|
|
5907
|
+
installmentNumber: 1,
|
|
5908
|
+
bankAccountId: input.bankAccountId,
|
|
5909
|
+
notes: desc
|
|
5910
|
+
}]
|
|
5911
|
+
});
|
|
5912
|
+
if (input.paid) {
|
|
5913
|
+
const movements = await provider.getMovements({ direction, status: "pending" });
|
|
5914
|
+
const bill = movements.data.find((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
|
|
5915
|
+
if (bill) {
|
|
5916
|
+
await provider.payMovement({
|
|
5917
|
+
movementId: bill.id,
|
|
5918
|
+
amount: input.amount,
|
|
5919
|
+
paymentDate: input.date,
|
|
5920
|
+
bankAccountId: input.bankAccountId,
|
|
5921
|
+
notes: desc
|
|
5922
|
+
});
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
5926
|
+
const invoiceQuery = get().invoiceQuery;
|
|
5927
|
+
const statementQuery = get().statementQuery;
|
|
5928
|
+
const [summary, bankAccounts, invoicesRes, statement] = await Promise.all([
|
|
5929
|
+
provider.getSummary(),
|
|
5930
|
+
provider.getBankAccounts(),
|
|
5931
|
+
provider.getInvoices(invoiceQuery),
|
|
5932
|
+
statementQuery ? provider.getStatement(statementQuery) : Promise.resolve(get().statement)
|
|
5933
|
+
]);
|
|
5934
|
+
set({
|
|
5935
|
+
summary,
|
|
5936
|
+
bankAccounts,
|
|
5937
|
+
invoices: invoicesRes.data,
|
|
5938
|
+
invoicesTotal: invoicesRes.total,
|
|
5939
|
+
statement
|
|
5940
|
+
});
|
|
5941
|
+
ui.toast.success("Transaction saved");
|
|
5942
|
+
} catch (err) {
|
|
5943
|
+
ui.toast.error("Failed to save transaction", { description: err?.message });
|
|
5944
|
+
throw err;
|
|
5945
|
+
}
|
|
5946
|
+
},
|
|
4760
5947
|
async payMovement(input) {
|
|
4761
5948
|
try {
|
|
4762
5949
|
await provider.payMovement(input);
|
|
@@ -4836,7 +6023,7 @@ var paymentMethodTypeEntity = {
|
|
|
4836
6023
|
{ key: "transactionType", label: "Transaction Type", type: "select", options: ["cash", "credit_card", "debit_card", "pix", "bank_transfer", "check", "voucher"], showInTable: true, required: true },
|
|
4837
6024
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
4838
6025
|
],
|
|
4839
|
-
data: { table:
|
|
6026
|
+
data: { table: T.paymentMethodTypes, tenantScoped: true }
|
|
4840
6027
|
};
|
|
4841
6028
|
var paymentMethodEntity = {
|
|
4842
6029
|
name: "Payment Method",
|
|
@@ -4848,14 +6035,14 @@ var paymentMethodEntity = {
|
|
|
4848
6035
|
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
4849
6036
|
// FK to the seeded payment_method_types catalog — load real ids at runtime
|
|
4850
6037
|
// (a static enum select would write the string into the uuid column).
|
|
4851
|
-
{ key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table:
|
|
6038
|
+
{ key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: T.paymentMethodTypes, labelField: "name" }, showInTable: true, required: true },
|
|
4852
6039
|
{ key: "discountValue", label: "Discount %", type: "number", showInTable: true },
|
|
4853
6040
|
{ key: "interestValue", label: "Interest %", type: "number", showInTable: true },
|
|
4854
6041
|
{ key: "minInstallments", label: "Min Installments", type: "number", defaultValue: 1 },
|
|
4855
6042
|
{ key: "maxInstallments", label: "Max Installments", type: "number", defaultValue: 1, showInTable: true },
|
|
4856
6043
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
4857
6044
|
],
|
|
4858
|
-
data: { table:
|
|
6045
|
+
data: { table: T.paymentMethods, tenantScoped: true }
|
|
4859
6046
|
};
|
|
4860
6047
|
var chartOfAccountsEntity = {
|
|
4861
6048
|
name: "Ledger Account",
|
|
@@ -4870,7 +6057,7 @@ var chartOfAccountsEntity = {
|
|
|
4870
6057
|
{ key: "parentId", label: "Parent", type: "text", showInTable: false },
|
|
4871
6058
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
4872
6059
|
],
|
|
4873
|
-
data: { table:
|
|
6060
|
+
data: { table: T.chartOfAccounts, tenantScoped: true }
|
|
4874
6061
|
};
|
|
4875
6062
|
var costCenterEntity = {
|
|
4876
6063
|
name: "Cost Center",
|
|
@@ -4883,7 +6070,7 @@ var costCenterEntity = {
|
|
|
4883
6070
|
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
4884
6071
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
4885
6072
|
],
|
|
4886
|
-
data: { table:
|
|
6073
|
+
data: { table: T.costCenters, tenantScoped: true }
|
|
4887
6074
|
};
|
|
4888
6075
|
var bankAccountEntity = {
|
|
4889
6076
|
name: "Bank Account",
|
|
@@ -4908,7 +6095,7 @@ var bankAccountEntity = {
|
|
|
4908
6095
|
{ key: "dueDay", label: "Due Day", type: "number" },
|
|
4909
6096
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
4910
6097
|
],
|
|
4911
|
-
data: { table:
|
|
6098
|
+
data: { table: T.bankAccounts, tenantScoped: true }
|
|
4912
6099
|
};
|
|
4913
6100
|
var cardBrandEntity = {
|
|
4914
6101
|
name: "Card Brand",
|
|
@@ -4920,7 +6107,7 @@ var cardBrandEntity = {
|
|
|
4920
6107
|
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
4921
6108
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
4922
6109
|
],
|
|
4923
|
-
data: { table:
|
|
6110
|
+
data: { table: T.cardBrands, tenantScoped: true }
|
|
4924
6111
|
};
|
|
4925
6112
|
var commissionRuleEntity = {
|
|
4926
6113
|
name: "Commission Rule",
|
|
@@ -5083,8 +6270,18 @@ var financialRegistries = [
|
|
|
5083
6270
|
|
|
5084
6271
|
// src/locales/en.ts
|
|
5085
6272
|
var en = {
|
|
6273
|
+
"financial.cards.addExpense": "Add expense",
|
|
6274
|
+
"financial.cards.allBillsValue": "All bills value",
|
|
6275
|
+
"financial.cards.availableLimit": "Available limit",
|
|
6276
|
+
"financial.cards.currentBill": "Current bill",
|
|
6277
|
+
"financial.cards.dueOn": "Due on the {{day}}",
|
|
5086
6278
|
"financial.cards.installment": "Installment {{current}}/{{total}}",
|
|
6279
|
+
"financial.cards.noCards": "No cards yet",
|
|
6280
|
+
"financial.cards.noCardsHint": "Add a credit card in settings to track your bills here",
|
|
5087
6281
|
"financial.cards.noTransactions": "No card transactions",
|
|
6282
|
+
"financial.cards.tabClosed": "Closed",
|
|
6283
|
+
"financial.cards.tabOpen": "Open",
|
|
6284
|
+
"financial.cards.usedOfLimit": "{{percent}}% of limit used",
|
|
5088
6285
|
"financial.cards.pending": "Pending",
|
|
5089
6286
|
"financial.cards.received": "Received",
|
|
5090
6287
|
"financial.cards.subtitle": "Credit and debit card transactions",
|
|
@@ -5211,6 +6408,10 @@ var en = {
|
|
|
5211
6408
|
"financial.nav.rules": "Rules",
|
|
5212
6409
|
"financial.nav.statements": "Statements",
|
|
5213
6410
|
"financial.nav.summary": "Summary",
|
|
6411
|
+
"financial.invoice.filterAccount": "Chart account",
|
|
6412
|
+
"financial.invoice.filterAllAccounts": "All accounts",
|
|
6413
|
+
"financial.invoice.filterCostCenter": "Cost center",
|
|
6414
|
+
"financial.invoice.filterAllCostCenters": "All cost centers",
|
|
5214
6415
|
"financial.reconciliation.title": "Reconciliation",
|
|
5215
6416
|
"financial.reconciliation.subtitle": "Match imported bank lines to your movements",
|
|
5216
6417
|
"financial.reconciliation.pending": "{{count}} to reconcile",
|
|
@@ -5325,6 +6526,36 @@ var en = {
|
|
|
5325
6526
|
"financial.summary.totalBalance": "Total Balance",
|
|
5326
6527
|
"financial.summary.transactionsAppear": "Transactions will appear here once recorded",
|
|
5327
6528
|
"financial.summary.viewAll": "View all",
|
|
6529
|
+
"financial.summary.today": "Today",
|
|
6530
|
+
"financial.summary.yesterday": "Yesterday",
|
|
6531
|
+
"financial.summary.transactionsTitle": "Transactions",
|
|
6532
|
+
"financial.home.balance": "Balance",
|
|
6533
|
+
"financial.home.inflow": "Income",
|
|
6534
|
+
"financial.home.outflow": "Expenses",
|
|
6535
|
+
"financial.home.showBalance": "Show balance",
|
|
6536
|
+
"financial.home.hideBalance": "Hide balance",
|
|
6537
|
+
"financial.home.upcomingBills": "Upcoming bills",
|
|
6538
|
+
"financial.home.noBills": "No upcoming bills.",
|
|
6539
|
+
"financial.home.dueToday": "Due today",
|
|
6540
|
+
"financial.home.dueTomorrow": "Due tomorrow",
|
|
6541
|
+
"financial.home.dueInDays": "Due in {{count}} days",
|
|
6542
|
+
"financial.home.overdueDays": "Overdue by {{count}} days",
|
|
6543
|
+
"financial.home.cards": "Cards",
|
|
6544
|
+
"financial.home.currentBill": "Current bill",
|
|
6545
|
+
"financial.home.availableLimit": "Available limit",
|
|
6546
|
+
"financial.home.addCard": "New card",
|
|
6547
|
+
"financial.home.bill": "Bill",
|
|
6548
|
+
"financial.home.spendByCategory": "Spending by category",
|
|
6549
|
+
"financial.home.noSpend": "No spending this month.",
|
|
6550
|
+
"financial.home.category.groceries": "Groceries",
|
|
6551
|
+
"financial.home.category.transport": "Transport",
|
|
6552
|
+
"financial.home.category.subscriptions": "Subscriptions",
|
|
6553
|
+
"financial.home.category.dining": "Dining",
|
|
6554
|
+
"financial.home.category.home": "Home",
|
|
6555
|
+
"financial.home.category.health": "Health",
|
|
6556
|
+
"financial.home.category.leisure": "Leisure",
|
|
6557
|
+
"financial.home.category.shopping": "Shopping",
|
|
6558
|
+
"financial.home.category.other": "Other",
|
|
5328
6559
|
"financial.quickActions.newPayable": "New Payable",
|
|
5329
6560
|
"financial.quickActions.newPayableDesc": "Record an expense or bill to pay",
|
|
5330
6561
|
"financial.quickActions.newReceivable": "New Receivable",
|
|
@@ -5486,13 +6717,54 @@ var en = {
|
|
|
5486
6717
|
"financial.transfer.created": "Transfer recorded",
|
|
5487
6718
|
"financial.transfer.failed": "Failed to record transfer",
|
|
5488
6719
|
"financial.transfer.sameAccount": "Source and destination must differ",
|
|
5489
|
-
"financial.transfer.invalidAmount": "Amount must be greater than zero"
|
|
6720
|
+
"financial.transfer.invalidAmount": "Amount must be greater than zero",
|
|
6721
|
+
"financial.quickTx.title": "New transaction",
|
|
6722
|
+
"financial.quickTx.close": "Close",
|
|
6723
|
+
"financial.quickTx.newTransaction": "New transaction",
|
|
6724
|
+
"financial.quickTx.expense": "Expense",
|
|
6725
|
+
"financial.quickTx.income": "Income",
|
|
6726
|
+
"financial.quickTx.transfer": "Transfer",
|
|
6727
|
+
"financial.quickTx.paid": "Paid",
|
|
6728
|
+
"financial.quickTx.received": "Received",
|
|
6729
|
+
"financial.quickTx.yes": "Yes",
|
|
6730
|
+
"financial.quickTx.no": "No",
|
|
6731
|
+
"financial.quickTx.category": "Category",
|
|
6732
|
+
"financial.quickTx.selectCategory": "No category",
|
|
6733
|
+
"financial.quickTx.account": "Account",
|
|
6734
|
+
"financial.quickTx.selectAccount": "Select account",
|
|
6735
|
+
"financial.quickTx.fromAccount": "From account",
|
|
6736
|
+
"financial.quickTx.toAccount": "To account",
|
|
6737
|
+
"financial.quickTx.card": "Card",
|
|
6738
|
+
"financial.quickTx.recurring": "Recurring",
|
|
6739
|
+
"financial.quickTx.attachment": "Attachment",
|
|
6740
|
+
"financial.quickTx.attachmentSoon": "Attachments coming soon",
|
|
6741
|
+
"financial.quickTx.receipt": "Receipt",
|
|
6742
|
+
"financial.quickTx.receiptCapture": "Snap or attach a receipt",
|
|
6743
|
+
"financial.quickTx.receiptAttached": "Receipt attached",
|
|
6744
|
+
"financial.quickTx.receiptReplace": "Replace",
|
|
6745
|
+
"financial.quickTx.receiptRemove": "Remove receipt",
|
|
6746
|
+
"financial.quickTx.receiptInvalid": "Please choose an image file",
|
|
6747
|
+
"financial.quickTx.sendReceipt": "Send receipt",
|
|
6748
|
+
"financial.quickTx.descriptionPlaceholder": "e.g. Groceries, Salary\u2026",
|
|
6749
|
+
"financial.quickTx.enterAmount": "Enter an amount",
|
|
6750
|
+
"financial.quickTx.save": "Save",
|
|
6751
|
+
"financial.quickTx.saving": "Saving\u2026"
|
|
5490
6752
|
};
|
|
5491
6753
|
|
|
5492
6754
|
// src/locales/pt-BR.ts
|
|
5493
6755
|
var ptBR = {
|
|
6756
|
+
"financial.cards.addExpense": "Adicionar despesa",
|
|
6757
|
+
"financial.cards.allBillsValue": "Total das faturas",
|
|
6758
|
+
"financial.cards.availableLimit": "Limite dispon\xEDvel",
|
|
6759
|
+
"financial.cards.currentBill": "Fatura atual",
|
|
6760
|
+
"financial.cards.dueOn": "Vence dia {{day}}",
|
|
5494
6761
|
"financial.cards.installment": "Parcela {{current}}/{{total}}",
|
|
6762
|
+
"financial.cards.noCards": "Nenhum cart\xE3o cadastrado",
|
|
6763
|
+
"financial.cards.noCardsHint": "Adicione um cart\xE3o de cr\xE9dito nas configura\xE7\xF5es para acompanhar suas faturas aqui",
|
|
5495
6764
|
"financial.cards.noTransactions": "Nenhuma transa\xE7\xE3o com cart\xE3o",
|
|
6765
|
+
"financial.cards.tabClosed": "Fechados",
|
|
6766
|
+
"financial.cards.tabOpen": "Abertos",
|
|
6767
|
+
"financial.cards.usedOfLimit": "{{percent}}% do limite usado",
|
|
5496
6768
|
"financial.cards.pending": "Pendente",
|
|
5497
6769
|
"financial.cards.received": "Recebido",
|
|
5498
6770
|
"financial.cards.subtitle": "Transa\xE7\xF5es de cart\xE3o de cr\xE9dito e d\xE9bito",
|
|
@@ -5619,6 +6891,10 @@ var ptBR = {
|
|
|
5619
6891
|
"financial.nav.rules": "Regras",
|
|
5620
6892
|
"financial.nav.statements": "Extratos",
|
|
5621
6893
|
"financial.nav.summary": "Resumo",
|
|
6894
|
+
"financial.invoice.filterAccount": "Plano de contas",
|
|
6895
|
+
"financial.invoice.filterAllAccounts": "Todos os planos",
|
|
6896
|
+
"financial.invoice.filterCostCenter": "Centro de custo",
|
|
6897
|
+
"financial.invoice.filterAllCostCenters": "Todos os centros",
|
|
5622
6898
|
"financial.reconciliation.title": "Concilia\xE7\xE3o",
|
|
5623
6899
|
"financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
|
|
5624
6900
|
"financial.reconciliation.pending": "{{count}} para conciliar",
|
|
@@ -5733,6 +7009,36 @@ var ptBR = {
|
|
|
5733
7009
|
"financial.summary.totalBalance": "Saldo Total",
|
|
5734
7010
|
"financial.summary.transactionsAppear": "As transa\xE7\xF5es aparecer\xE3o aqui ap\xF3s serem registradas",
|
|
5735
7011
|
"financial.summary.viewAll": "Ver todos",
|
|
7012
|
+
"financial.summary.today": "Hoje",
|
|
7013
|
+
"financial.summary.yesterday": "Ontem",
|
|
7014
|
+
"financial.summary.transactionsTitle": "Transa\xE7\xF5es",
|
|
7015
|
+
"financial.home.balance": "Saldo",
|
|
7016
|
+
"financial.home.inflow": "Entradas",
|
|
7017
|
+
"financial.home.outflow": "Sa\xEDdas",
|
|
7018
|
+
"financial.home.showBalance": "Mostrar saldo",
|
|
7019
|
+
"financial.home.hideBalance": "Ocultar saldo",
|
|
7020
|
+
"financial.home.upcomingBills": "Pr\xF3ximas contas",
|
|
7021
|
+
"financial.home.noBills": "Nenhuma conta a vencer.",
|
|
7022
|
+
"financial.home.dueToday": "Vence hoje",
|
|
7023
|
+
"financial.home.dueTomorrow": "Vence amanh\xE3",
|
|
7024
|
+
"financial.home.dueInDays": "Vence em {{count}} dias",
|
|
7025
|
+
"financial.home.overdueDays": "Venceu h\xE1 {{count}} dias",
|
|
7026
|
+
"financial.home.cards": "Cart\xF5es",
|
|
7027
|
+
"financial.home.currentBill": "Fatura atual",
|
|
7028
|
+
"financial.home.availableLimit": "Limite dispon\xEDvel",
|
|
7029
|
+
"financial.home.addCard": "Novo cart\xE3o",
|
|
7030
|
+
"financial.home.bill": "Conta",
|
|
7031
|
+
"financial.home.spendByCategory": "Gastos por categoria",
|
|
7032
|
+
"financial.home.noSpend": "Sem gastos neste m\xEAs.",
|
|
7033
|
+
"financial.home.category.groceries": "Mercado",
|
|
7034
|
+
"financial.home.category.transport": "Transporte",
|
|
7035
|
+
"financial.home.category.subscriptions": "Assinaturas",
|
|
7036
|
+
"financial.home.category.dining": "Alimenta\xE7\xE3o",
|
|
7037
|
+
"financial.home.category.home": "Casa",
|
|
7038
|
+
"financial.home.category.health": "Sa\xFAde",
|
|
7039
|
+
"financial.home.category.leisure": "Lazer",
|
|
7040
|
+
"financial.home.category.shopping": "Compras",
|
|
7041
|
+
"financial.home.category.other": "Outros",
|
|
5736
7042
|
"financial.quickActions.newPayable": "Nova Conta a Pagar",
|
|
5737
7043
|
"financial.quickActions.newPayableDesc": "Registrar uma despesa ou conta a pagar",
|
|
5738
7044
|
"financial.quickActions.newReceivable": "Nova Conta a Receber",
|
|
@@ -5894,7 +7200,38 @@ var ptBR = {
|
|
|
5894
7200
|
"financial.transfer.created": "Transfer\xEAncia registrada",
|
|
5895
7201
|
"financial.transfer.failed": "Falha ao registrar transfer\xEAncia",
|
|
5896
7202
|
"financial.transfer.sameAccount": "Origem e destino devem ser diferentes",
|
|
5897
|
-
"financial.transfer.invalidAmount": "O valor deve ser maior que zero"
|
|
7203
|
+
"financial.transfer.invalidAmount": "O valor deve ser maior que zero",
|
|
7204
|
+
"financial.quickTx.title": "Nova transa\xE7\xE3o",
|
|
7205
|
+
"financial.quickTx.close": "Fechar",
|
|
7206
|
+
"financial.quickTx.newTransaction": "Nova transa\xE7\xE3o",
|
|
7207
|
+
"financial.quickTx.expense": "Despesa",
|
|
7208
|
+
"financial.quickTx.income": "Receita",
|
|
7209
|
+
"financial.quickTx.transfer": "Transfer\xEAncia",
|
|
7210
|
+
"financial.quickTx.paid": "Pago",
|
|
7211
|
+
"financial.quickTx.received": "Recebido",
|
|
7212
|
+
"financial.quickTx.yes": "Sim",
|
|
7213
|
+
"financial.quickTx.no": "N\xE3o",
|
|
7214
|
+
"financial.quickTx.category": "Categoria",
|
|
7215
|
+
"financial.quickTx.selectCategory": "Sem categoria",
|
|
7216
|
+
"financial.quickTx.account": "Conta",
|
|
7217
|
+
"financial.quickTx.selectAccount": "Selecione a conta",
|
|
7218
|
+
"financial.quickTx.fromAccount": "Conta de origem",
|
|
7219
|
+
"financial.quickTx.toAccount": "Conta de destino",
|
|
7220
|
+
"financial.quickTx.card": "Cart\xE3o",
|
|
7221
|
+
"financial.quickTx.recurring": "Recorrente",
|
|
7222
|
+
"financial.quickTx.attachment": "Anexo",
|
|
7223
|
+
"financial.quickTx.attachmentSoon": "Anexos em breve",
|
|
7224
|
+
"financial.quickTx.receipt": "Recibo",
|
|
7225
|
+
"financial.quickTx.receiptCapture": "Fotografar ou anexar recibo",
|
|
7226
|
+
"financial.quickTx.receiptAttached": "Recibo anexado",
|
|
7227
|
+
"financial.quickTx.receiptReplace": "Substituir",
|
|
7228
|
+
"financial.quickTx.receiptRemove": "Remover recibo",
|
|
7229
|
+
"financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
|
|
7230
|
+
"financial.quickTx.sendReceipt": "Enviar recibo",
|
|
7231
|
+
"financial.quickTx.descriptionPlaceholder": "ex.: Mercado, Sal\xE1rio\u2026",
|
|
7232
|
+
"financial.quickTx.enterAmount": "Informe um valor",
|
|
7233
|
+
"financial.quickTx.save": "Salvar",
|
|
7234
|
+
"financial.quickTx.saving": "Salvando\u2026"
|
|
5898
7235
|
};
|
|
5899
7236
|
|
|
5900
7237
|
// src/locales/index.ts
|
|
@@ -5912,9 +7249,9 @@ var STATUS_STYLE = {
|
|
|
5912
7249
|
function PersonStatementWidget({ person, provider, config }) {
|
|
5913
7250
|
const t2 = core.useTranslation();
|
|
5914
7251
|
const currency = config.currency;
|
|
5915
|
-
const [invoices, setInvoices] =
|
|
5916
|
-
const [loading, setLoading] =
|
|
5917
|
-
|
|
7252
|
+
const [invoices, setInvoices] = React17.useState([]);
|
|
7253
|
+
const [loading, setLoading] = React17.useState(true);
|
|
7254
|
+
React17.useEffect(() => {
|
|
5918
7255
|
let alive = true;
|
|
5919
7256
|
if (!person?.id) {
|
|
5920
7257
|
setLoading(false);
|
|
@@ -5934,7 +7271,7 @@ function PersonStatementWidget({ person, provider, config }) {
|
|
|
5934
7271
|
};
|
|
5935
7272
|
}, [person?.id, provider]);
|
|
5936
7273
|
const fmt = (n) => saas.formatCurrency(n, currency);
|
|
5937
|
-
const totals =
|
|
7274
|
+
const totals = React17.useMemo(() => {
|
|
5938
7275
|
const active = invoices.filter((i) => i.status !== "cancelled");
|
|
5939
7276
|
const billed = active.reduce((s, i) => s + (i.totalAmount ?? 0), 0);
|
|
5940
7277
|
const paid = active.reduce((s, i) => s + (i.paidAmount ?? 0), 0);
|
|
@@ -5944,7 +7281,7 @@ function PersonStatementWidget({ person, provider, config }) {
|
|
|
5944
7281
|
const base = inv.direction === "credit" ? "receivables" : "payables";
|
|
5945
7282
|
window.location.hash = `/financial/${base}/detail/${inv.id}`;
|
|
5946
7283
|
};
|
|
5947
|
-
const columns =
|
|
7284
|
+
const columns = React17.useMemo(() => [
|
|
5948
7285
|
{
|
|
5949
7286
|
id: "date",
|
|
5950
7287
|
header: t2("financial.invoice.columnDate"),
|
|
@@ -6044,6 +7381,8 @@ function resolveConfig(options) {
|
|
|
6044
7381
|
// Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
|
|
6045
7382
|
reconciliation: options?.modules?.reconciliation === true
|
|
6046
7383
|
},
|
|
7384
|
+
// Opt-in: only render the plugin's own quick-add buttons/FAB when the host asks.
|
|
7385
|
+
quickAdd: options?.quickAdd === true,
|
|
6047
7386
|
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
6048
7387
|
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
6049
7388
|
itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
|
|
@@ -6064,9 +7403,10 @@ function createFinancialPlugin(options) {
|
|
|
6064
7403
|
const provider = options?.dataProvider ?? createSafeFinancialProvider();
|
|
6065
7404
|
const store = createFinancialStore(provider);
|
|
6066
7405
|
const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
|
|
6067
|
-
const
|
|
7406
|
+
const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
|
|
7407
|
+
const PersonStatementBound = (props) => React17__namespace.default.createElement(PersonStatementWidget, { person: props.item, provider, config });
|
|
6068
7408
|
PersonStatementBound.displayName = "FinancialPersonStatement";
|
|
6069
|
-
const PageComponent = () =>
|
|
7409
|
+
const PageComponent = () => React17__namespace.default.createElement(FinancialPage, { config, provider, store, registries });
|
|
6070
7410
|
return {
|
|
6071
7411
|
id: "financial",
|
|
6072
7412
|
name: config.labels.pageTitle,
|
|
@@ -6171,18 +7511,18 @@ function createFinancialPlugin(options) {
|
|
|
6171
7511
|
]
|
|
6172
7512
|
}
|
|
6173
7513
|
],
|
|
6174
|
-
registries
|
|
7514
|
+
registries,
|
|
6175
7515
|
settings: [
|
|
6176
7516
|
{
|
|
6177
7517
|
id: "financial",
|
|
6178
7518
|
label: "Financial",
|
|
6179
7519
|
icon: "DollarSign",
|
|
6180
7520
|
component: (() => {
|
|
6181
|
-
const FinancialSettingsTab = () =>
|
|
7521
|
+
const FinancialSettingsTab = () => React17__namespace.default.createElement(saas.PluginSettingsPanel, {
|
|
6182
7522
|
title: "Financial Settings",
|
|
6183
7523
|
subtitle: "Payment methods, accounts, and configurations",
|
|
6184
|
-
generalSettings:
|
|
6185
|
-
registries
|
|
7524
|
+
generalSettings: React17__namespace.default.createElement(FinancialGeneralSettings),
|
|
7525
|
+
registries,
|
|
6186
7526
|
hostPluginId: "financial",
|
|
6187
7527
|
routeBase: "/settings/financial"
|
|
6188
7528
|
});
|
|
@@ -6198,6 +7538,8 @@ function createFinancialPlugin(options) {
|
|
|
6198
7538
|
}
|
|
6199
7539
|
|
|
6200
7540
|
exports.createFinancialPlugin = createFinancialPlugin;
|
|
7541
|
+
exports.createMockFinancialProvider = createMockFinancialProvider;
|
|
6201
7542
|
exports.createSafeFinancialProvider = createSafeFinancialProvider;
|
|
7543
|
+
exports.openQuickAdd = openQuickAdd;
|
|
6202
7544
|
//# sourceMappingURL=index.cjs.map
|
|
6203
7545
|
//# sourceMappingURL=index.cjs.map
|