@fayz-ai/plugin-financial 0.9.3 → 0.11.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 -2
- package/dist/components/PaymentModal.d.ts +3 -1
- package/dist/components/PaymentModal.d.ts.map +1 -1
- package/dist/components/ReceiptPicker.d.ts +10 -0
- package/dist/components/ReceiptPicker.d.ts.map +1 -0
- package/dist/context.d.ts +0 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/registries.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1135 -1053
- package/dist/index.js.map +1 -1
- package/dist/integrations/acquirers/shared.d.ts +15 -0
- package/dist/integrations/acquirers/shared.d.ts.map +1 -0
- package/dist/integrations/cielo/connectorDef.d.ts +6 -0
- package/dist/integrations/cielo/connectorDef.d.ts.map +1 -0
- package/dist/integrations/mercadopago/connectorDef.d.ts +5 -0
- package/dist/integrations/mercadopago/connectorDef.d.ts.map +1 -0
- package/dist/integrations/pagbank/connectorDef.d.ts +5 -0
- package/dist/integrations/pagbank/connectorDef.d.ts.map +1 -0
- package/dist/integrations/stone/connectorDef.d.ts +6 -0
- package/dist/integrations/stone/connectorDef.d.ts.map +1 -0
- package/dist/lib/accounts.d.ts +16 -0
- package/dist/lib/accounts.d.ts.map +1 -0
- package/dist/lib/settlements.d.ts +22 -0
- package/dist/lib/settlements.d.ts.map +1 -0
- 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 +4 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/store.d.ts +1 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/types.d.ts +12 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/views/CardsView.d.ts +3 -1
- package/dist/views/CardsView.d.ts.map +1 -1
- package/dist/views/CommissionsView.d.ts +7 -0
- package/dist/views/CommissionsView.d.ts.map +1 -1
- package/dist/views/FinancialPage.d.ts +2 -0
- package/dist/views/FinancialPage.d.ts.map +1 -1
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts +3 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts +3 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/PayablesView.d.ts.map +1 -1
- package/dist/views/ReceivablesView.d.ts.map +1 -1
- package/dist/views/StatementsView.d.ts.map +1 -1
- package/package.json +8 -7
- package/dist/data/capability.test.d.ts +0 -2
- package/dist/data/capability.test.d.ts.map +0 -1
- package/dist/lib/quick-add.d.ts +0 -13
- package/dist/lib/quick-add.d.ts.map +0 -1
- package/dist/views/QuickTransactionForm.d.ts +0 -16
- package/dist/views/QuickTransactionForm.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { Wallet, Plus, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, X, CalendarDays, Paperclip, Camera, Tag, Repeat, ChevronDown, FileText, FileCheck2, ExternalLink, Send, Ban, CalendarClock, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Search, Download, Receipt, Clock, ArrowLeftRight, CheckCheck, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, CircleCheckBig, CircleDashed, CircleAlert, CircleEllipsis, Pencil, LayoutList, Layers, Building2, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, EyeOff, Eye } from 'lucide-react';
|
|
1
|
+
import React17, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
2
|
+
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, createSafeDataProvider, registerTranslations, registerMessageDefaults, useTranslation, getSupabaseClientOptional, getActiveTenantId, exportCSV } from '@fayz-ai/core';
|
|
3
|
+
import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, DashboardCanvas, SubpageHeader, Skeleton, PageHeaderActions, Card, FilterChip, DatePicker, Input, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, ConfirmDialog, KpiCard, ChartWidget, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
|
|
4
|
+
import { createPluginContext, fayzConnectorOAuth, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, formatCurrency, parseViewId, EntitlementGate, UpgradePrompt, PermissionGate, PersonLink, useLimitGuard, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, OrderDetailSkeleton, OrderDetailBlock, OrderDetailView, QuickActionsButton, invalidateLimit } from '@fayz-ai/admin';
|
|
5
|
+
import { Wallet, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, Plus, FileText, FileCheck2, ExternalLink, Send, Ban, CalendarClock, Users, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Search, Download, Receipt, ChevronDown, Clock, Paperclip, CheckCheck, X, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, CircleCheckBig, CircleDashed, Camera, CircleAlert, CircleEllipsis, Pencil, CalendarDays, LayoutList, Layers, Building2, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, EyeOff, Eye } from 'lucide-react';
|
|
7
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
7
|
import { createPortal } from 'react-dom';
|
|
9
8
|
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
@@ -105,22 +104,22 @@ function MobileSummary() {
|
|
|
105
104
|
}, [statement]);
|
|
106
105
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
107
106
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
108
|
-
/* @__PURE__ */ jsxs("div", { className: "col-span-2 rounded-
|
|
107
|
+
/* @__PURE__ */ jsxs("div", { className: "col-span-2 rounded-card border bg-card p-4", children: [
|
|
109
108
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.summary.totalBalance") }),
|
|
110
109
|
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-2xl font-semibold", children: fmt(summary?.totalBalance ?? 0) })
|
|
111
110
|
] }),
|
|
112
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
111
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card p-3", children: [
|
|
113
112
|
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.summary.income") }),
|
|
114
113
|
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-base font-semibold text-success", children: fmt(inflow) })
|
|
115
114
|
] }),
|
|
116
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
115
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card p-3", children: [
|
|
117
116
|
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.summary.expenses") }),
|
|
118
117
|
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-base font-semibold text-destructive", children: fmt(outflow) })
|
|
119
118
|
] })
|
|
120
119
|
] }),
|
|
121
120
|
groups.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-dashed py-12 text-center text-sm text-muted-foreground", children: t2("financial.summary.noTransactions") }) : /* @__PURE__ */ jsx("div", { className: "space-y-4", children: groups.map(([date, rows]) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
122
121
|
/* @__PURE__ */ jsx("p", { className: "px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: dateLabel(date) }),
|
|
123
|
-
/* @__PURE__ */ jsx("div", { className: "divide-y overflow-hidden rounded-
|
|
122
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y overflow-hidden rounded-card border bg-card", children: rows.map((e) => {
|
|
124
123
|
const isCredit = e.movement.direction === "credit";
|
|
125
124
|
const title = e.invoice?.contactName || e.movement.notes || t2("financial.summary.transactionsTitle");
|
|
126
125
|
const Icon = categoryIcon(title);
|
|
@@ -160,488 +159,14 @@ function MobileSummary() {
|
|
|
160
159
|
] }, date)) })
|
|
161
160
|
] });
|
|
162
161
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return { header: "bg-muted text-foreground", ring: "ring-ring", icon: ArrowLeftRight };
|
|
169
|
-
case "expense":
|
|
170
|
-
default:
|
|
171
|
-
return { header: "bg-destructive text-destructive-foreground", ring: "ring-destructive", icon: ArrowUpCircle };
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function ChipSelect({ icon: Icon, label, value, options, onChange, emptyLabel }) {
|
|
175
|
-
const [open, setOpen] = useState(false);
|
|
176
|
-
const ref = useRef(null);
|
|
177
|
-
useEffect(() => {
|
|
178
|
-
if (!open) return;
|
|
179
|
-
function onDoc(e) {
|
|
180
|
-
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
181
|
-
}
|
|
182
|
-
document.addEventListener("mousedown", onDoc);
|
|
183
|
-
return () => document.removeEventListener("mousedown", onDoc);
|
|
184
|
-
}, [open]);
|
|
185
|
-
const selected = options.find((o) => o.id === value);
|
|
186
|
-
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
187
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
|
|
188
|
-
/* @__PURE__ */ jsxs(
|
|
189
|
-
"button",
|
|
190
|
-
{
|
|
191
|
-
type: "button",
|
|
192
|
-
onClick: () => setOpen((v) => !v),
|
|
193
|
-
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",
|
|
194
|
-
children: [
|
|
195
|
-
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
196
|
-
/* @__PURE__ */ jsx("span", { className: `flex-1 truncate ${selected ? "" : "text-muted-foreground"}`, children: selected?.name ?? emptyLabel }),
|
|
197
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
|
|
198
|
-
]
|
|
199
|
-
}
|
|
200
|
-
),
|
|
201
|
-
open && /* @__PURE__ */ 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__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: emptyLabel }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
202
|
-
value != null && /* @__PURE__ */ jsxs(
|
|
203
|
-
"button",
|
|
204
|
-
{
|
|
205
|
-
onClick: () => {
|
|
206
|
-
onChange(void 0);
|
|
207
|
-
setOpen(false);
|
|
208
|
-
},
|
|
209
|
-
className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-xs text-muted-foreground hover:bg-muted/50",
|
|
210
|
-
children: [
|
|
211
|
-
/* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }),
|
|
212
|
-
" ",
|
|
213
|
-
emptyLabel
|
|
214
|
-
]
|
|
215
|
-
}
|
|
216
|
-
),
|
|
217
|
-
options.map((o) => /* @__PURE__ */ jsxs(
|
|
218
|
-
"button",
|
|
219
|
-
{
|
|
220
|
-
onClick: () => {
|
|
221
|
-
onChange(o.id);
|
|
222
|
-
setOpen(false);
|
|
223
|
-
},
|
|
224
|
-
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" : ""}`,
|
|
225
|
-
children: [
|
|
226
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.name }),
|
|
227
|
-
o.hint && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: o.hint }),
|
|
228
|
-
o.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-primary" })
|
|
229
|
-
]
|
|
230
|
-
},
|
|
231
|
-
o.id
|
|
232
|
-
))
|
|
233
|
-
] }) })
|
|
234
|
-
] });
|
|
235
|
-
}
|
|
236
|
-
function TogglePill({ icon: Icon, label, checked, onChange, accent }) {
|
|
237
|
-
return /* @__PURE__ */ jsxs(
|
|
238
|
-
"button",
|
|
239
|
-
{
|
|
240
|
-
type: "button",
|
|
241
|
-
onClick: () => onChange(!checked),
|
|
242
|
-
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"}`,
|
|
243
|
-
children: [
|
|
244
|
-
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
|
|
245
|
-
label
|
|
246
|
-
]
|
|
247
|
-
}
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", defaultAccountId, focusReceipt = false }) {
|
|
251
|
-
const t2 = useTranslation();
|
|
252
|
-
const { currency } = useFinancialConfig();
|
|
253
|
-
const provider = useFinancialProvider();
|
|
254
|
-
const createQuickTransaction = useFinancialStore((s) => s.createQuickTransaction);
|
|
255
|
-
const guardMovements = useLimitGuard("movements_month");
|
|
256
|
-
const can = usePermissionOptional();
|
|
257
|
-
const [type, setType] = useState(defaultType);
|
|
258
|
-
const [amount, setAmount] = useState(0);
|
|
259
|
-
const [amountStr, setAmountStr] = useState("");
|
|
260
|
-
const [paid, setPaid] = useState(true);
|
|
261
|
-
const [date, setDate] = useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
262
|
-
const [description, setDescription] = useState("");
|
|
263
|
-
const [categoryId, setCategoryId] = useState();
|
|
264
|
-
const [accountId, setAccountId] = useState();
|
|
265
|
-
const [toAccountId, setToAccountId] = useState();
|
|
266
|
-
const [recurring, setRecurring] = useState(false);
|
|
267
|
-
const [saving, setSaving] = useState(false);
|
|
268
|
-
const [receiptUrl, setReceiptUrl] = useState();
|
|
269
|
-
const fileInputRef = useRef(null);
|
|
270
|
-
const [accounts, setAccounts] = useState([]);
|
|
271
|
-
const [categories, setCategories] = useState([]);
|
|
272
|
-
useEffect(() => {
|
|
273
|
-
if (!open) return;
|
|
274
|
-
setType(defaultType);
|
|
275
|
-
setAmount(0);
|
|
276
|
-
setAmountStr("");
|
|
277
|
-
setPaid(true);
|
|
278
|
-
setDate((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
279
|
-
setDescription("");
|
|
280
|
-
setCategoryId(void 0);
|
|
281
|
-
setAccountId(defaultAccountId);
|
|
282
|
-
setToAccountId(void 0);
|
|
283
|
-
setRecurring(false);
|
|
284
|
-
setReceiptUrl(void 0);
|
|
285
|
-
}, [open, defaultType, defaultAccountId]);
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
if (open && focusReceipt) {
|
|
288
|
-
const id = window.setTimeout(() => fileInputRef.current?.click(), 250);
|
|
289
|
-
return () => window.clearTimeout(id);
|
|
290
|
-
}
|
|
291
|
-
}, [open, focusReceipt]);
|
|
292
|
-
function onPickReceipt(e) {
|
|
293
|
-
const file = e.target.files?.[0];
|
|
294
|
-
e.target.value = "";
|
|
295
|
-
if (!file) return;
|
|
296
|
-
if (!file.type.startsWith("image/")) {
|
|
297
|
-
toast.error(t2("financial.quickTx.receiptInvalid"));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const reader = new FileReader();
|
|
301
|
-
reader.onload = () => {
|
|
302
|
-
setReceiptUrl(typeof reader.result === "string" ? reader.result : void 0);
|
|
303
|
-
};
|
|
304
|
-
reader.onerror = () => toast.error(t2("financial.quickTx.receiptInvalid"));
|
|
305
|
-
reader.readAsDataURL(file);
|
|
306
|
-
}
|
|
307
|
-
useEffect(() => {
|
|
308
|
-
if (!open) return;
|
|
309
|
-
let cancelled = false;
|
|
310
|
-
void (async () => {
|
|
311
|
-
const [accs, coa] = await Promise.all([
|
|
312
|
-
provider.getBankAccounts(),
|
|
313
|
-
provider.getChartOfAccounts().catch(() => [])
|
|
314
|
-
]);
|
|
315
|
-
if (cancelled) return;
|
|
316
|
-
setAccounts(accs);
|
|
317
|
-
setCategories(coa);
|
|
318
|
-
setAccountId((prev) => prev ?? accs[0]?.id);
|
|
319
|
-
})();
|
|
320
|
-
return () => {
|
|
321
|
-
cancelled = true;
|
|
322
|
-
};
|
|
323
|
-
}, [open, provider]);
|
|
324
|
-
const theme = typeTheme(type);
|
|
325
|
-
const HeaderIcon = theme.icon;
|
|
326
|
-
const accountOptions = useMemo(
|
|
327
|
-
() => accounts.map((a) => ({ id: a.id, name: a.name, hint: a.accountType === "credit_card" ? t2("financial.quickTx.card") : void 0 })),
|
|
328
|
-
[accounts, t2]
|
|
329
|
-
);
|
|
330
|
-
const categoryOptions = useMemo(
|
|
331
|
-
() => categories.filter((c) => c.isActive).map((c) => ({ id: c.id, name: c.name, hint: c.code })),
|
|
332
|
-
[categories]
|
|
333
|
-
);
|
|
334
|
-
function onAmountChange(raw) {
|
|
335
|
-
const cleaned = raw.replace(/[^\d.,]/g, "").replace(",", ".");
|
|
336
|
-
setAmountStr(raw.replace(/[^\d.,]/g, ""));
|
|
337
|
-
const n = Number.parseFloat(cleaned);
|
|
338
|
-
setAmount(Number.isFinite(n) ? n : 0);
|
|
339
|
-
}
|
|
340
|
-
const canSave = amount > 0 && !saving && (type === "transfer" ? !!accountId && !!toAccountId && accountId !== toAccountId : true);
|
|
341
|
-
async function handleSave() {
|
|
342
|
-
if (!can("financial", "create")) return;
|
|
343
|
-
if (!canSave) {
|
|
344
|
-
if (type === "transfer" && accountId && accountId === toAccountId) {
|
|
345
|
-
toast.error(t2("financial.transfer.sameAccount"));
|
|
346
|
-
}
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (await guardMovements(type === "transfer" ? 2 : 1) === "blocked") return;
|
|
350
|
-
setSaving(true);
|
|
351
|
-
try {
|
|
352
|
-
await createQuickTransaction({
|
|
353
|
-
type,
|
|
354
|
-
amount,
|
|
355
|
-
date,
|
|
356
|
-
paid: type === "transfer" ? true : paid,
|
|
357
|
-
description: description || void 0,
|
|
358
|
-
categoryId: type === "transfer" ? void 0 : categoryId,
|
|
359
|
-
bankAccountId: accountId,
|
|
360
|
-
toAccountId: type === "transfer" ? toAccountId : void 0,
|
|
361
|
-
recurring: type === "transfer" ? false : recurring,
|
|
362
|
-
receiptUrl: type === "transfer" ? void 0 : receiptUrl
|
|
363
|
-
});
|
|
364
|
-
invalidateLimit("movements_month");
|
|
365
|
-
onOpenChange(false);
|
|
366
|
-
} catch {
|
|
367
|
-
} finally {
|
|
368
|
-
setSaving(false);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
const TYPES = [
|
|
372
|
-
{ id: "expense", label: t2("financial.quickTx.expense") },
|
|
373
|
-
{ id: "income", label: t2("financial.quickTx.income") },
|
|
374
|
-
{ id: "transfer", label: t2("financial.quickTx.transfer") }
|
|
375
|
-
];
|
|
376
|
-
return /* @__PURE__ */ jsx(Modal, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ModalContent, { size: "md", noPadding: true, hideClose: true, className: "overflow-hidden", children: [
|
|
377
|
-
/* @__PURE__ */ jsxs("div", { className: `${theme.header} shrink-0 px-5 pb-5 pt-5`, children: [
|
|
378
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
379
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
380
|
-
/* @__PURE__ */ jsx(HeaderIcon, { className: "h-5 w-5" }),
|
|
381
|
-
/* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: t2("financial.quickTx.title") })
|
|
382
|
-
] }),
|
|
383
|
-
/* @__PURE__ */ jsx(
|
|
384
|
-
"button",
|
|
385
|
-
{
|
|
386
|
-
onClick: () => onOpenChange(false),
|
|
387
|
-
className: "flex h-7 w-7 items-center justify-center rounded-full opacity-80 transition-colors hover:bg-black/10 hover:opacity-100",
|
|
388
|
-
"aria-label": t2("financial.quickTx.close"),
|
|
389
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
390
|
-
}
|
|
391
|
-
)
|
|
392
|
-
] }),
|
|
393
|
-
/* @__PURE__ */ jsx("div", { className: "mt-4 flex gap-1 rounded-full bg-black/15 p-1", children: TYPES.map((ty) => /* @__PURE__ */ jsx(
|
|
394
|
-
"button",
|
|
395
|
-
{
|
|
396
|
-
onClick: () => setType(ty.id),
|
|
397
|
-
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"}`,
|
|
398
|
-
children: ty.label
|
|
399
|
-
},
|
|
400
|
-
ty.id
|
|
401
|
-
)) }),
|
|
402
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-5 flex items-baseline justify-center gap-2", children: [
|
|
403
|
-
/* @__PURE__ */ jsx("span", { className: "text-xl font-medium opacity-90", children: currency.symbol }),
|
|
404
|
-
/* @__PURE__ */ jsx(
|
|
405
|
-
"input",
|
|
406
|
-
{
|
|
407
|
-
type: "text",
|
|
408
|
-
inputMode: "decimal",
|
|
409
|
-
autoFocus: true,
|
|
410
|
-
value: amountStr,
|
|
411
|
-
onChange: (e) => onAmountChange(e.target.value),
|
|
412
|
-
onKeyDown: (e) => {
|
|
413
|
-
if (e.key === "Enter") void handleSave();
|
|
414
|
-
},
|
|
415
|
-
placeholder: "0,00",
|
|
416
|
-
className: "w-full max-w-[220px] bg-transparent text-center text-4xl font-bold tracking-tight outline-none placeholder:opacity-40"
|
|
417
|
-
}
|
|
418
|
-
)
|
|
419
|
-
] })
|
|
420
|
-
] }),
|
|
421
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-4 overflow-y-auto px-5 py-5", children: [
|
|
422
|
-
type !== "transfer" && /* @__PURE__ */ 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: [
|
|
423
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: type === "income" ? t2("financial.quickTx.received") : t2("financial.quickTx.paid") }),
|
|
424
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
|
|
425
|
-
/* @__PURE__ */ jsx(
|
|
426
|
-
"button",
|
|
427
|
-
{
|
|
428
|
-
onClick: () => setPaid(true),
|
|
429
|
-
className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${paid ? "bg-success text-success-foreground" : "bg-muted text-muted-foreground"}`,
|
|
430
|
-
children: t2("financial.quickTx.yes")
|
|
431
|
-
}
|
|
432
|
-
),
|
|
433
|
-
/* @__PURE__ */ jsx(
|
|
434
|
-
"button",
|
|
435
|
-
{
|
|
436
|
-
onClick: () => setPaid(false),
|
|
437
|
-
className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${!paid ? "bg-warning text-warning-foreground" : "bg-muted text-muted-foreground"}`,
|
|
438
|
-
children: t2("financial.quickTx.no")
|
|
439
|
-
}
|
|
440
|
-
)
|
|
441
|
-
] })
|
|
442
|
-
] }),
|
|
443
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
444
|
-
/* @__PURE__ */ jsxs("span", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
|
|
445
|
-
/* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5" }),
|
|
446
|
-
" ",
|
|
447
|
-
t2("financial.invoiceForm.date")
|
|
448
|
-
] }),
|
|
449
|
-
/* @__PURE__ */ jsx(DatePicker, { value: date, onChange: setDate })
|
|
450
|
-
] }),
|
|
451
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
452
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.description") }),
|
|
453
|
-
/* @__PURE__ */ jsx(
|
|
454
|
-
"input",
|
|
455
|
-
{
|
|
456
|
-
type: "text",
|
|
457
|
-
value: description,
|
|
458
|
-
onChange: (e) => setDescription(e.target.value),
|
|
459
|
-
onKeyDown: (e) => {
|
|
460
|
-
if (e.key === "Enter") void handleSave();
|
|
461
|
-
},
|
|
462
|
-
placeholder: t2("financial.quickTx.descriptionPlaceholder"),
|
|
463
|
-
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"
|
|
464
|
-
}
|
|
465
|
-
)
|
|
466
|
-
] }),
|
|
467
|
-
type !== "transfer" && /* @__PURE__ */ jsxs("div", { children: [
|
|
468
|
-
/* @__PURE__ */ jsxs("span", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
|
|
469
|
-
/* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }),
|
|
470
|
-
" ",
|
|
471
|
-
t2("financial.quickTx.receipt")
|
|
472
|
-
] }),
|
|
473
|
-
/* @__PURE__ */ jsx(
|
|
474
|
-
"input",
|
|
475
|
-
{
|
|
476
|
-
ref: fileInputRef,
|
|
477
|
-
type: "file",
|
|
478
|
-
accept: "image/*",
|
|
479
|
-
capture: "environment",
|
|
480
|
-
className: "hidden",
|
|
481
|
-
onChange: onPickReceipt
|
|
482
|
-
}
|
|
483
|
-
),
|
|
484
|
-
receiptUrl ? /* @__PURE__ */ 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: [
|
|
485
|
-
/* @__PURE__ */ jsx(
|
|
486
|
-
"img",
|
|
487
|
-
{
|
|
488
|
-
src: receiptUrl,
|
|
489
|
-
alt: t2("financial.quickTx.receipt"),
|
|
490
|
-
className: "h-16 w-16 shrink-0 rounded-md border object-cover"
|
|
491
|
-
}
|
|
492
|
-
),
|
|
493
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
494
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: t2("financial.quickTx.receiptAttached") }),
|
|
495
|
-
/* @__PURE__ */ jsx(
|
|
496
|
-
"button",
|
|
497
|
-
{
|
|
498
|
-
type: "button",
|
|
499
|
-
onClick: () => fileInputRef.current?.click(),
|
|
500
|
-
className: "text-xs text-primary hover:underline",
|
|
501
|
-
children: t2("financial.quickTx.receiptReplace")
|
|
502
|
-
}
|
|
503
|
-
)
|
|
504
|
-
] }),
|
|
505
|
-
/* @__PURE__ */ jsx(
|
|
506
|
-
"button",
|
|
507
|
-
{
|
|
508
|
-
type: "button",
|
|
509
|
-
onClick: () => setReceiptUrl(void 0),
|
|
510
|
-
"aria-label": t2("financial.quickTx.receiptRemove"),
|
|
511
|
-
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted",
|
|
512
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
513
|
-
}
|
|
514
|
-
)
|
|
515
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
516
|
-
"button",
|
|
517
|
-
{
|
|
518
|
-
type: "button",
|
|
519
|
-
onClick: () => fileInputRef.current?.click(),
|
|
520
|
-
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"}`,
|
|
521
|
-
children: [
|
|
522
|
-
/* @__PURE__ */ jsx(Camera, { className: "h-4 w-4" }),
|
|
523
|
-
" ",
|
|
524
|
-
t2("financial.quickTx.receiptCapture")
|
|
525
|
-
]
|
|
526
|
-
}
|
|
527
|
-
)
|
|
528
|
-
] }),
|
|
529
|
-
type === "transfer" ? /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3", children: [
|
|
530
|
-
/* @__PURE__ */ jsx(
|
|
531
|
-
ChipSelect,
|
|
532
|
-
{
|
|
533
|
-
icon: Wallet,
|
|
534
|
-
label: t2("financial.quickTx.fromAccount"),
|
|
535
|
-
value: accountId,
|
|
536
|
-
options: accountOptions,
|
|
537
|
-
onChange: setAccountId,
|
|
538
|
-
emptyLabel: t2("financial.quickTx.selectAccount")
|
|
539
|
-
}
|
|
540
|
-
),
|
|
541
|
-
/* @__PURE__ */ jsx(
|
|
542
|
-
ChipSelect,
|
|
543
|
-
{
|
|
544
|
-
icon: Wallet,
|
|
545
|
-
label: t2("financial.quickTx.toAccount"),
|
|
546
|
-
value: toAccountId,
|
|
547
|
-
options: accountOptions.filter((o) => o.id !== accountId),
|
|
548
|
-
onChange: setToAccountId,
|
|
549
|
-
emptyLabel: t2("financial.quickTx.selectAccount")
|
|
550
|
-
}
|
|
551
|
-
)
|
|
552
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3", children: [
|
|
553
|
-
/* @__PURE__ */ jsx(
|
|
554
|
-
ChipSelect,
|
|
555
|
-
{
|
|
556
|
-
icon: Tag,
|
|
557
|
-
label: t2("financial.quickTx.category"),
|
|
558
|
-
value: categoryId,
|
|
559
|
-
options: categoryOptions,
|
|
560
|
-
onChange: setCategoryId,
|
|
561
|
-
emptyLabel: t2("financial.quickTx.selectCategory")
|
|
562
|
-
}
|
|
563
|
-
),
|
|
564
|
-
/* @__PURE__ */ jsx(
|
|
565
|
-
ChipSelect,
|
|
566
|
-
{
|
|
567
|
-
icon: Wallet,
|
|
568
|
-
label: t2("financial.quickTx.account"),
|
|
569
|
-
value: accountId,
|
|
570
|
-
options: accountOptions,
|
|
571
|
-
onChange: setAccountId,
|
|
572
|
-
emptyLabel: t2("financial.quickTx.selectAccount")
|
|
573
|
-
}
|
|
574
|
-
)
|
|
575
|
-
] }),
|
|
576
|
-
type !== "transfer" && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
577
|
-
/* @__PURE__ */ jsx(
|
|
578
|
-
TogglePill,
|
|
579
|
-
{
|
|
580
|
-
icon: Repeat,
|
|
581
|
-
label: t2("financial.quickTx.recurring"),
|
|
582
|
-
checked: recurring,
|
|
583
|
-
onChange: setRecurring
|
|
584
|
-
}
|
|
585
|
-
),
|
|
586
|
-
/* @__PURE__ */ jsxs(
|
|
587
|
-
"button",
|
|
588
|
-
{
|
|
589
|
-
type: "button",
|
|
590
|
-
onClick: () => fileInputRef.current?.click(),
|
|
591
|
-
title: t2("financial.quickTx.attachment"),
|
|
592
|
-
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"}`,
|
|
593
|
-
children: [
|
|
594
|
-
/* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }),
|
|
595
|
-
" ",
|
|
596
|
-
t2("financial.quickTx.attachment"),
|
|
597
|
-
receiptUrl && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
|
|
598
|
-
]
|
|
599
|
-
}
|
|
600
|
-
)
|
|
601
|
-
] })
|
|
602
|
-
] }),
|
|
603
|
-
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3 border-t px-5 py-3", children: [
|
|
604
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: amount > 0 ? formatCurrency(amount, currency) : t2("financial.quickTx.enterAmount") }),
|
|
605
|
-
/* @__PURE__ */ jsxs(
|
|
606
|
-
Button,
|
|
607
|
-
{
|
|
608
|
-
className: "ml-auto",
|
|
609
|
-
onClick: () => void handleSave(),
|
|
610
|
-
disabled: !canSave,
|
|
611
|
-
children: [
|
|
612
|
-
/* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
|
|
613
|
-
saving ? t2("financial.quickTx.saving") : t2("financial.quickTx.save")
|
|
614
|
-
]
|
|
615
|
-
}
|
|
616
|
-
)
|
|
617
|
-
] })
|
|
618
|
-
] }) });
|
|
619
|
-
}
|
|
620
|
-
var pending = null;
|
|
621
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
622
|
-
function emit() {
|
|
623
|
-
for (const l of listeners) l();
|
|
624
|
-
}
|
|
625
|
-
function openQuickAdd(type = "expense", receipt = false) {
|
|
626
|
-
pending = { type, receipt };
|
|
627
|
-
emit();
|
|
628
|
-
}
|
|
629
|
-
function consumeQuickAdd() {
|
|
630
|
-
const req = pending;
|
|
631
|
-
pending = null;
|
|
632
|
-
return req;
|
|
633
|
-
}
|
|
634
|
-
function subscribe(cb) {
|
|
635
|
-
listeners.add(cb);
|
|
636
|
-
return () => {
|
|
637
|
-
listeners.delete(cb);
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
function getSnapshot() {
|
|
641
|
-
return pending;
|
|
162
|
+
|
|
163
|
+
// src/lib/accounts.ts
|
|
164
|
+
function postableAccounts(accounts) {
|
|
165
|
+
const parentIds = new Set(accounts.map((a) => a.parentId).filter(Boolean));
|
|
166
|
+
return accounts.filter((a) => a.nodeType !== "group" && !parentIds.has(a.id));
|
|
642
167
|
}
|
|
643
|
-
function
|
|
644
|
-
return
|
|
168
|
+
function accountLabel(account) {
|
|
169
|
+
return `${account.code} - ${account.name}`;
|
|
645
170
|
}
|
|
646
171
|
var STATUS_OPTIONS = [
|
|
647
172
|
{ value: "open", labelKey: "financial.invoice.statusOpen", color: "bg-info-soft text-info-soft-foreground", icon: CircleDashed },
|
|
@@ -728,21 +253,26 @@ function useInvoiceColumns(currency) {
|
|
|
728
253
|
}
|
|
729
254
|
], [currency, t2]);
|
|
730
255
|
}
|
|
731
|
-
function InvoiceListView({ direction, onNew, onEdit }) {
|
|
256
|
+
function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
732
257
|
const t2 = useTranslation();
|
|
733
258
|
const can = usePermissionOptional();
|
|
734
|
-
const
|
|
259
|
+
const canCreate = can("financial", "create");
|
|
260
|
+
const canEdit = can("financial", "edit");
|
|
261
|
+
const onNewGated = canCreate ? onNew : void 0;
|
|
262
|
+
const onNewReceiptGated = canCreate ? onNewReceipt : void 0;
|
|
735
263
|
const { currency } = useFinancialConfig();
|
|
736
264
|
const provider = useFinancialProvider();
|
|
737
265
|
const invoices = useFinancialStore((s) => s.invoices);
|
|
738
266
|
useFinancialStore((s) => s.invoicesTotal);
|
|
739
267
|
const invoicesLoading = useFinancialStore((s) => s.invoicesLoading);
|
|
740
268
|
const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
|
|
269
|
+
const fetchSummary = useFinancialStore((s) => s.fetchSummary);
|
|
741
270
|
const [search, setSearch] = useState("");
|
|
742
271
|
const [statusFilter, setStatusFilter] = useState([]);
|
|
743
272
|
const [accountId, setAccountId] = useState("");
|
|
744
273
|
const [costCenterId, setCostCenterId] = useState("");
|
|
745
274
|
const [accounts, setAccounts] = useState([]);
|
|
275
|
+
const postable = useMemo(() => postableAccounts(accounts), [accounts]);
|
|
746
276
|
const [costCenters, setCostCenters] = useState([]);
|
|
747
277
|
const [datePreset, setDatePreset] = useState("all");
|
|
748
278
|
const [dateFrom, setDateFrom] = useState("");
|
|
@@ -778,16 +308,17 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
778
308
|
active = false;
|
|
779
309
|
};
|
|
780
310
|
}, [provider]);
|
|
311
|
+
const query = useMemo(() => ({
|
|
312
|
+
direction,
|
|
313
|
+
status: statusFilter.length > 0 ? statusFilter : void 0,
|
|
314
|
+
search: search || void 0,
|
|
315
|
+
accountId: accountId || void 0,
|
|
316
|
+
costCenterId: costCenterId || void 0,
|
|
317
|
+
dateRange
|
|
318
|
+
}), [direction, statusFilter, search, accountId, costCenterId, dateRange]);
|
|
781
319
|
useEffect(() => {
|
|
782
|
-
fetchInvoices(
|
|
783
|
-
|
|
784
|
-
status: statusFilter.length > 0 ? statusFilter : void 0,
|
|
785
|
-
search: search || void 0,
|
|
786
|
-
accountId: accountId || void 0,
|
|
787
|
-
costCenterId: costCenterId || void 0,
|
|
788
|
-
dateRange
|
|
789
|
-
});
|
|
790
|
-
}, [direction, statusFilter, search, accountId, costCenterId, dateRange]);
|
|
320
|
+
fetchInvoices(query);
|
|
321
|
+
}, [query]);
|
|
791
322
|
const filtered = invoices.filter((inv) => inv.direction === direction);
|
|
792
323
|
const columns = useInvoiceColumns(currency);
|
|
793
324
|
const tags = useMemo(
|
|
@@ -806,6 +337,46 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
806
337
|
openAmount = totalAmount - paidAmount;
|
|
807
338
|
return { totalAmount, paidAmount, openAmount };
|
|
808
339
|
}, [filtered]);
|
|
340
|
+
const [cancelTarget, setCancelTarget] = useState(null);
|
|
341
|
+
const [bulkBusy, setBulkBusy] = useState(false);
|
|
342
|
+
const confirmBulkCancel = useCallback(async () => {
|
|
343
|
+
const targets = (cancelTarget ?? []).filter((inv) => inv.status !== "cancelled");
|
|
344
|
+
setBulkBusy(true);
|
|
345
|
+
let done = 0;
|
|
346
|
+
let failed = 0;
|
|
347
|
+
for (const invoice of targets) {
|
|
348
|
+
try {
|
|
349
|
+
await provider.cancelInvoice(invoice.id);
|
|
350
|
+
done++;
|
|
351
|
+
} catch (err) {
|
|
352
|
+
failed++;
|
|
353
|
+
console.warn("[financial] cancelInvoice failed", invoice.id, err);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
setBulkBusy(false);
|
|
357
|
+
setCancelTarget(null);
|
|
358
|
+
await Promise.all([fetchInvoices(query), fetchSummary()]);
|
|
359
|
+
if (done > 0) toast.success(t2("financial.invoice.bulkCancelDone", { count: String(done) }));
|
|
360
|
+
if (failed > 0) toast.error(t2("financial.invoice.bulkCancelFailed", { count: String(failed) }));
|
|
361
|
+
}, [cancelTarget, provider, fetchInvoices, fetchSummary, query, t2]);
|
|
362
|
+
const bulkActions = useMemo(() => {
|
|
363
|
+
if (!canEdit) return void 0;
|
|
364
|
+
return [{
|
|
365
|
+
id: "cancel",
|
|
366
|
+
label: t2("financial.invoice.bulkCancel"),
|
|
367
|
+
icon: Ban,
|
|
368
|
+
destructive: true,
|
|
369
|
+
disabled: bulkBusy,
|
|
370
|
+
onSelect: (rows) => {
|
|
371
|
+
const targets = rows.filter((inv) => inv.status !== "cancelled");
|
|
372
|
+
if (targets.length === 0) {
|
|
373
|
+
toast.error(t2("financial.invoice.bulkCancelNone"));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
setCancelTarget(targets);
|
|
377
|
+
}
|
|
378
|
+
}];
|
|
379
|
+
}, [canEdit, bulkBusy, t2]);
|
|
809
380
|
const receivedLabel = direction === "debit" ? t2("financial.invoice.summaryPaid") : t2("financial.invoice.summaryReceived");
|
|
810
381
|
const pendingLabel = direction === "debit" ? t2("financial.invoice.summaryPayable") : t2("financial.invoice.summaryReceivable");
|
|
811
382
|
const exportRows = () => {
|
|
@@ -857,7 +428,7 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
857
428
|
children: t2("financial.invoice.filterClear")
|
|
858
429
|
}
|
|
859
430
|
),
|
|
860
|
-
|
|
431
|
+
postable.length > 0 && /* @__PURE__ */ jsx(
|
|
861
432
|
FilterChip,
|
|
862
433
|
{
|
|
863
434
|
icon: Landmark,
|
|
@@ -865,9 +436,9 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
865
436
|
allLabel: t2("financial.invoice.filterAllAccounts"),
|
|
866
437
|
value: accountId || void 0,
|
|
867
438
|
onChange: (next) => setAccountId(next ?? ""),
|
|
868
|
-
options:
|
|
439
|
+
options: postable.map((account) => ({
|
|
869
440
|
value: account.id,
|
|
870
|
-
label:
|
|
441
|
+
label: accountLabel(account)
|
|
871
442
|
}))
|
|
872
443
|
}
|
|
873
444
|
),
|
|
@@ -886,6 +457,29 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
886
457
|
}
|
|
887
458
|
)
|
|
888
459
|
] });
|
|
460
|
+
const newActions = onNewGated ? /* @__PURE__ */ jsx(
|
|
461
|
+
QuickActionsButton,
|
|
462
|
+
{
|
|
463
|
+
variant: "default",
|
|
464
|
+
label: t2("financial.invoice.new"),
|
|
465
|
+
actions: [
|
|
466
|
+
{
|
|
467
|
+
id: "manual",
|
|
468
|
+
label: t2("financial.invoice.newManual"),
|
|
469
|
+
description: t2("financial.invoice.newManualHint"),
|
|
470
|
+
icon: "FileText",
|
|
471
|
+
action: onNewGated
|
|
472
|
+
},
|
|
473
|
+
...onNewReceiptGated ? [{
|
|
474
|
+
id: "receipt",
|
|
475
|
+
label: t2("financial.quickTx.sendReceipt"),
|
|
476
|
+
description: t2("financial.quickTx.receiptCapture"),
|
|
477
|
+
icon: "Camera",
|
|
478
|
+
action: onNewReceiptGated
|
|
479
|
+
}] : []
|
|
480
|
+
]
|
|
481
|
+
}
|
|
482
|
+
) : void 0;
|
|
889
483
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
890
484
|
/* @__PURE__ */ jsx(
|
|
891
485
|
ListView,
|
|
@@ -904,8 +498,8 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
904
498
|
filters: listFilters,
|
|
905
499
|
onExport: filtered.length > 0 ? exportRows : void 0,
|
|
906
500
|
exportLabel: t2("crud.export.action"),
|
|
907
|
-
|
|
908
|
-
|
|
501
|
+
headerActions: newActions,
|
|
502
|
+
bulkActions,
|
|
909
503
|
onRowClick: (row) => onEdit?.(row.id),
|
|
910
504
|
emptyIcon: FileText,
|
|
911
505
|
emptyMessage: t2("financial.invoice.noInvoices"),
|
|
@@ -928,9 +522,53 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
928
522
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: pendingLabel }),
|
|
929
523
|
/* @__PURE__ */ jsx("span", { className: "font-semibold text-warning", children: formatCurrency(summary.openAmount, currency) })
|
|
930
524
|
] })
|
|
931
|
-
] })
|
|
525
|
+
] }),
|
|
526
|
+
/* @__PURE__ */ jsx(
|
|
527
|
+
ConfirmDialog,
|
|
528
|
+
{
|
|
529
|
+
open: cancelTarget !== null,
|
|
530
|
+
variant: "destructive",
|
|
531
|
+
title: t2("financial.invoice.bulkCancelTitle", { count: String(cancelTarget?.length ?? 0) }),
|
|
532
|
+
description: t2("financial.invoice.cancelDesc"),
|
|
533
|
+
confirmLabel: t2("financial.invoice.yesCancel"),
|
|
534
|
+
cancelLabel: t2("financial.invoice.keepInvoice"),
|
|
535
|
+
loading: bulkBusy,
|
|
536
|
+
onConfirm: () => void confirmBulkCancel(),
|
|
537
|
+
onCancel: () => setCancelTarget(null)
|
|
538
|
+
}
|
|
539
|
+
)
|
|
932
540
|
] });
|
|
933
541
|
}
|
|
542
|
+
function useReceiptPicker(onPick) {
|
|
543
|
+
const t2 = useTranslation();
|
|
544
|
+
const ref = useRef(null);
|
|
545
|
+
const input = /* @__PURE__ */ jsx(
|
|
546
|
+
"input",
|
|
547
|
+
{
|
|
548
|
+
ref,
|
|
549
|
+
type: "file",
|
|
550
|
+
accept: "image/*",
|
|
551
|
+
capture: "environment",
|
|
552
|
+
className: "hidden",
|
|
553
|
+
onChange: (e) => {
|
|
554
|
+
const file = e.target.files?.[0];
|
|
555
|
+
e.target.value = "";
|
|
556
|
+
if (!file) return;
|
|
557
|
+
if (!file.type.startsWith("image/")) {
|
|
558
|
+
toast.error(t2("financial.quickTx.receiptInvalid"));
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const reader = new FileReader();
|
|
562
|
+
reader.onload = () => {
|
|
563
|
+
if (typeof reader.result === "string") onPick(reader.result);
|
|
564
|
+
};
|
|
565
|
+
reader.onerror = () => toast.error(t2("financial.quickTx.receiptInvalid"));
|
|
566
|
+
reader.readAsDataURL(file);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
return { open: () => ref.current?.click(), input };
|
|
571
|
+
}
|
|
934
572
|
var ACCOUNT_TYPES_FOR_TX = {
|
|
935
573
|
cash: ["cash_register"],
|
|
936
574
|
credit_card: ["credit_card", "bank_account"],
|
|
@@ -964,7 +602,7 @@ var ICON_COLORS = {
|
|
|
964
602
|
bank_transfer: "text-warning",
|
|
965
603
|
check: "text-muted-foreground"
|
|
966
604
|
};
|
|
967
|
-
function PaymentModal({ movement, onClose, onPaid }) {
|
|
605
|
+
function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
968
606
|
const t2 = useTranslation();
|
|
969
607
|
const { currency } = useFinancialConfig();
|
|
970
608
|
const payMovement = useFinancialStore((s) => s.payMovement);
|
|
@@ -1035,6 +673,8 @@ function PaymentModal({ movement, onClose, onPaid }) {
|
|
|
1035
673
|
setSaving(true);
|
|
1036
674
|
try {
|
|
1037
675
|
await payMovement({
|
|
676
|
+
// O comprovante viaja com o pagamento — é dele que ele é prova.
|
|
677
|
+
metadata: receiptUrl ? { receiptUrl } : void 0,
|
|
1038
678
|
movementId: movement.id,
|
|
1039
679
|
amount,
|
|
1040
680
|
paymentDate,
|
|
@@ -1094,7 +734,7 @@ function PaymentModal({ movement, onClose, onPaid }) {
|
|
|
1094
734
|
"button",
|
|
1095
735
|
{
|
|
1096
736
|
onClick: () => setExpandMethodPicker(true),
|
|
1097
|
-
className: `flex items-center gap-2 mt-1.5 w-full rounded-
|
|
737
|
+
className: `flex items-center gap-2 mt-1.5 w-full rounded-button border px-3 py-2.5 text-left transition-colors hover:bg-muted/30 ${TYPE_COLORS[txType] ?? ""}`,
|
|
1098
738
|
"data-active": "true",
|
|
1099
739
|
children: [
|
|
1100
740
|
SelectedIcon && /* @__PURE__ */ jsx(SelectedIcon, { className: `h-4 w-4 ${ICON_COLORS[txType] ?? ""}` }),
|
|
@@ -1125,7 +765,7 @@ function PaymentModal({ movement, onClose, onPaid }) {
|
|
|
1125
765
|
{
|
|
1126
766
|
"data-active": isActive,
|
|
1127
767
|
onClick: () => handleSelectType(type.id),
|
|
1128
|
-
className: `flex flex-col items-center gap-1.5 rounded-
|
|
768
|
+
className: `flex flex-col items-center gap-1.5 rounded-button border px-2 py-3 text-center transition-all ${TYPE_COLORS[tt] ?? TYPE_COLORS.cash}`,
|
|
1129
769
|
children: [
|
|
1130
770
|
/* @__PURE__ */ jsx(Icon, { className: `h-5 w-5 ${isActive ? ICON_COLORS[tt] ?? "" : "text-muted-foreground"}` }),
|
|
1131
771
|
/* @__PURE__ */ jsx("span", { className: `text-[11px] font-medium leading-tight ${isActive ? "text-foreground" : "text-muted-foreground"}`, children: type.name })
|
|
@@ -1311,18 +951,38 @@ var formId = 1;
|
|
|
1311
951
|
function fid() {
|
|
1312
952
|
return `f${formId++}`;
|
|
1313
953
|
}
|
|
1314
|
-
function InvoiceFormView({ direction, editId, onSaved }) {
|
|
954
|
+
function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false }) {
|
|
1315
955
|
const t2 = useTranslation();
|
|
1316
956
|
const config = useFinancialConfig();
|
|
1317
957
|
const { currency, itemTypes, locations } = config;
|
|
1318
958
|
const provider = useFinancialProvider();
|
|
1319
959
|
const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
|
|
1320
960
|
const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
|
|
961
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
962
|
+
const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
|
|
963
|
+
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
964
|
+
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
965
|
+
const payMovement = useFinancialStore((s) => s.payMovement);
|
|
1321
966
|
const createInvoice = useFinancialStore((s) => s.createInvoice);
|
|
1322
967
|
const guardMovements = useLimitGuard("movements_month");
|
|
1323
968
|
const can = usePermissionOptional();
|
|
1324
969
|
const hasLocations = locations.length > 0;
|
|
1325
970
|
const isEdit = !!editId;
|
|
971
|
+
useEffect(() => {
|
|
972
|
+
if (isEdit) return;
|
|
973
|
+
void fetchBankAccounts();
|
|
974
|
+
void fetchPaymentMethods();
|
|
975
|
+
}, [isEdit]);
|
|
976
|
+
useEffect(() => {
|
|
977
|
+
let cancelled = false;
|
|
978
|
+
void provider.getChartOfAccounts().then((rows) => {
|
|
979
|
+
if (!cancelled) setAccounts(rows);
|
|
980
|
+
}).catch(() => {
|
|
981
|
+
});
|
|
982
|
+
return () => {
|
|
983
|
+
cancelled = true;
|
|
984
|
+
};
|
|
985
|
+
}, [provider]);
|
|
1326
986
|
const [loaded, setLoaded] = useState(!editId);
|
|
1327
987
|
const [loading, setLoading] = useState(!!editId);
|
|
1328
988
|
const [contactName, setContactName] = useState("");
|
|
@@ -1332,11 +992,32 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1332
992
|
const [fiscalNumber, setFiscalNumber] = useState("");
|
|
1333
993
|
const [observations, setObservations] = useState("");
|
|
1334
994
|
const [items, setItems] = useState([]);
|
|
995
|
+
const [accounts, setAccounts] = useState([]);
|
|
996
|
+
const postable = useMemo(() => postableAccounts(accounts), [accounts]);
|
|
1335
997
|
const [expandedItemId, setExpandedItemId] = useState(null);
|
|
1336
998
|
const [addingItem, setAddingItem] = useState(false);
|
|
1337
999
|
const [installments, setInstallments] = useState([]);
|
|
1338
1000
|
const [savedMovements, setSavedMovements] = useState([]);
|
|
1339
1001
|
const [payingMovement, setPayingMovement] = useState(null);
|
|
1002
|
+
const [pendingReceipt, setPendingReceipt] = useState();
|
|
1003
|
+
const contactCreate = useLookupCreate(config.contactLookup, (created) => {
|
|
1004
|
+
setContactId(created.id);
|
|
1005
|
+
setContactName(created.label);
|
|
1006
|
+
});
|
|
1007
|
+
const [receiptTarget, setReceiptTarget] = useState(null);
|
|
1008
|
+
const receiptPicker = useReceiptPicker((dataUrl) => {
|
|
1009
|
+
const i = receiptTarget;
|
|
1010
|
+
setReceiptTarget(null);
|
|
1011
|
+
if (i == null) return;
|
|
1012
|
+
if (isEdit) {
|
|
1013
|
+
const saved = savedMovements[i];
|
|
1014
|
+
if (!saved) return;
|
|
1015
|
+
setPendingReceipt(dataUrl);
|
|
1016
|
+
setPayingMovement(saved);
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
setInstallments((prev) => prev.map((inst, idx) => idx === i ? { ...inst, receiptUrl: dataUrl } : inst));
|
|
1020
|
+
});
|
|
1340
1021
|
const [saving, setSaving] = useState(false);
|
|
1341
1022
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
1342
1023
|
const [confirmCancel, setConfirmCancel] = useState(false);
|
|
@@ -1383,7 +1064,9 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1383
1064
|
quantity: it.quantity,
|
|
1384
1065
|
unitPrice: it.unitPrice,
|
|
1385
1066
|
discount: it.discount,
|
|
1386
|
-
surcharge: it.surcharge
|
|
1067
|
+
surcharge: it.surcharge,
|
|
1068
|
+
accountId: it.accountId,
|
|
1069
|
+
costCenterId: it.costCenterId
|
|
1387
1070
|
})));
|
|
1388
1071
|
const movements = await provider.getMovements({ direction: invoice.direction });
|
|
1389
1072
|
const invoiceMovements = movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0));
|
|
@@ -1443,6 +1126,21 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1443
1126
|
function removeItem(id) {
|
|
1444
1127
|
setItems(items.filter((it) => it._id !== id));
|
|
1445
1128
|
}
|
|
1129
|
+
useEffect(() => {
|
|
1130
|
+
if (isEdit || totalAmount <= 0 || installments.length > 0) return;
|
|
1131
|
+
generateInstallments(1);
|
|
1132
|
+
}, [isEdit, totalAmount, installments.length]);
|
|
1133
|
+
const receiptAutoOpened = useRef(false);
|
|
1134
|
+
useEffect(() => {
|
|
1135
|
+
if (!focusReceipt || isEdit || receiptAutoOpened.current) return;
|
|
1136
|
+
if (installments.length === 0 || installments[0]?.receiptUrl) return;
|
|
1137
|
+
receiptAutoOpened.current = true;
|
|
1138
|
+
const id = window.setTimeout(() => {
|
|
1139
|
+
setReceiptTarget(0);
|
|
1140
|
+
receiptPicker.open();
|
|
1141
|
+
}, 250);
|
|
1142
|
+
return () => window.clearTimeout(id);
|
|
1143
|
+
}, [focusReceipt, isEdit, installments.length]);
|
|
1446
1144
|
function generateInstallments(count) {
|
|
1447
1145
|
if (count < 1 || totalAmount <= 0) return;
|
|
1448
1146
|
const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
|
|
@@ -1466,6 +1164,10 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1466
1164
|
toast.error(t2("common.formIncomplete"));
|
|
1467
1165
|
return;
|
|
1468
1166
|
}
|
|
1167
|
+
if (!isEdit && installments.some((i) => i.receiptUrl && (!i.payMethodTypeId || !i.payAccountId))) {
|
|
1168
|
+
toast.error(t2("financial.invoiceForm.paidNeedsMethod"));
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1469
1171
|
if (!isEdit && await guardMovements(Math.max(1, installments.length)) === "blocked") return;
|
|
1470
1172
|
setSaving(true);
|
|
1471
1173
|
try {
|
|
@@ -1485,6 +1187,11 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1485
1187
|
installmentNumber: 1
|
|
1486
1188
|
});
|
|
1487
1189
|
}
|
|
1190
|
+
installments.forEach((inst, i) => {
|
|
1191
|
+
if (inst.receiptUrl && movInputs[i]) {
|
|
1192
|
+
movInputs[i] = { ...movInputs[i], metadata: { ...movInputs[i].metadata ?? {}, receiptUrl: inst.receiptUrl } };
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1488
1195
|
if (isEdit && editId) {
|
|
1489
1196
|
await provider.updateInvoice(editId, {
|
|
1490
1197
|
invoiceDate,
|
|
@@ -1509,6 +1216,22 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1509
1216
|
installments: movInputs
|
|
1510
1217
|
});
|
|
1511
1218
|
invalidateLimit("movements_month");
|
|
1219
|
+
const paidOnes = installments.map((inst, i) => ({ inst, number: i + 1 })).filter(({ inst }) => inst.receiptUrl);
|
|
1220
|
+
if (paidOnes.length > 0) {
|
|
1221
|
+
const movements = await provider.getMovements({ direction, status: "pending" });
|
|
1222
|
+
const bills = movements.data.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
|
|
1223
|
+
for (const { inst, number } of paidOnes) {
|
|
1224
|
+
const bill = bills.find((m) => (m.installmentNumber ?? 1) === number);
|
|
1225
|
+
if (!bill) continue;
|
|
1226
|
+
await payMovement({
|
|
1227
|
+
movementId: bill.id,
|
|
1228
|
+
amount: bill.amount,
|
|
1229
|
+
paymentDate: inst.dueDate,
|
|
1230
|
+
paymentMethodTypeId: inst.payMethodTypeId || void 0,
|
|
1231
|
+
bankAccountId: inst.payAccountId || void 0
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1512
1235
|
onSaved?.(invoice.id);
|
|
1513
1236
|
}
|
|
1514
1237
|
} finally {
|
|
@@ -1541,7 +1264,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1541
1264
|
});
|
|
1542
1265
|
const title = isEdit ? t2("financial.invoiceForm.editTitle") : t2("financial.invoiceForm.newTitle");
|
|
1543
1266
|
const subtitle = isEdit ? t2("financial.invoiceForm.editing") : t2("financial.invoiceForm.creating");
|
|
1544
|
-
if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, {});
|
|
1267
|
+
if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, { parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"), onBack: onSaved });
|
|
1545
1268
|
const money = (v) => formatCurrency(v, currency);
|
|
1546
1269
|
const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
|
|
1547
1270
|
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
|
|
@@ -1563,113 +1286,148 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1563
1286
|
] }) : null;
|
|
1564
1287
|
const paidSoFar = savedMovements.reduce((sum, m) => sum + (m.paidAmount ?? 0), 0);
|
|
1565
1288
|
const hasPayments = paidSoFar > 0;
|
|
1566
|
-
const
|
|
1567
|
-
/* @__PURE__ */ jsx("
|
|
1568
|
-
|
|
1569
|
-
{
|
|
1570
|
-
|
|
1571
|
-
onClick: () => generateInstallments(n),
|
|
1572
|
-
disabled: totalAmount <= 0,
|
|
1573
|
-
className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
|
|
1574
|
-
children: [
|
|
1575
|
-
n,
|
|
1576
|
-
"x"
|
|
1577
|
-
]
|
|
1578
|
-
},
|
|
1579
|
-
n
|
|
1580
|
-
)) }),
|
|
1581
|
-
installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: installments.map((inst, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded border p-2.5", children: [
|
|
1582
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1583
|
-
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-muted-foreground", children: [
|
|
1584
|
-
"#",
|
|
1585
|
-
i + 1
|
|
1586
|
-
] }),
|
|
1587
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children: money(inst.amount) })
|
|
1588
|
-
] }),
|
|
1589
|
-
/* @__PURE__ */ jsx(
|
|
1590
|
-
DatePicker,
|
|
1289
|
+
const scheduleSection = /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1290
|
+
hasPayments ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.scheduleLocked") }) : (
|
|
1291
|
+
// Atalhos de parcelamento — 1x, 2x, 3x, 6x, 12x cobrem quase tudo.
|
|
1292
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
|
|
1293
|
+
"button",
|
|
1591
1294
|
{
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
!hasPayments && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
|
|
1605
|
-
"button",
|
|
1606
|
-
{
|
|
1607
|
-
type: "button",
|
|
1608
|
-
onClick: () => generateInstallments(n),
|
|
1609
|
-
disabled: totalAmount <= 0,
|
|
1610
|
-
className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
|
|
1611
|
-
children: [
|
|
1612
|
-
n,
|
|
1613
|
-
"x"
|
|
1614
|
-
]
|
|
1615
|
-
},
|
|
1616
|
-
n
|
|
1617
|
-
)) }),
|
|
1295
|
+
type: "button",
|
|
1296
|
+
onClick: () => generateInstallments(n),
|
|
1297
|
+
disabled: totalAmount <= 0,
|
|
1298
|
+
className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
|
|
1299
|
+
children: [
|
|
1300
|
+
n,
|
|
1301
|
+
"x"
|
|
1302
|
+
]
|
|
1303
|
+
},
|
|
1304
|
+
n
|
|
1305
|
+
)) })
|
|
1306
|
+
),
|
|
1618
1307
|
installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded border", children: installments.map((inst, i) => {
|
|
1308
|
+
const patch = (next) => {
|
|
1309
|
+
const updated = [...installments];
|
|
1310
|
+
updated[i] = { ...inst, ...next };
|
|
1311
|
+
setInstallments(updated);
|
|
1312
|
+
};
|
|
1619
1313
|
const saved = savedMovements[i];
|
|
1620
1314
|
const isPaid = saved?.status === "paid";
|
|
1621
1315
|
const isPartial = saved?.status === "partial";
|
|
1622
1316
|
const StatusIcon = isPaid ? CircleCheckBig : CircleDashed;
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
/* @__PURE__ */ jsx(
|
|
1631
|
-
|
|
1317
|
+
const canAttach = isEdit ? !!saved && !isPaid : true;
|
|
1318
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
1319
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 px-3 py-2", children: [
|
|
1320
|
+
/* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
|
|
1321
|
+
"#",
|
|
1322
|
+
i + 1
|
|
1323
|
+
] }),
|
|
1324
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money(inst.amount) }),
|
|
1325
|
+
saved && /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${isPaid ? "bg-success-soft text-success-soft-foreground" : isPartial ? "bg-warning-soft text-warning-soft-foreground" : "bg-muted text-muted-foreground"}`, children: [
|
|
1326
|
+
/* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5" }),
|
|
1327
|
+
t2(isPaid ? "financial.invoice.statusPaid" : isPartial ? "financial.invoice.statusPartial" : "financial.invoice.statusPending")
|
|
1328
|
+
] }),
|
|
1329
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [
|
|
1330
|
+
!hasPayments ? /* @__PURE__ */ jsx(
|
|
1331
|
+
DatePicker,
|
|
1332
|
+
{
|
|
1333
|
+
value: inst.dueDate,
|
|
1334
|
+
variant: "chip",
|
|
1335
|
+
onChange: (v) => patch({ dueDate: v })
|
|
1336
|
+
}
|
|
1337
|
+
) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
|
|
1338
|
+
canAttach && /* @__PURE__ */ jsx(
|
|
1339
|
+
"button",
|
|
1340
|
+
{
|
|
1341
|
+
type: "button",
|
|
1342
|
+
onClick: () => {
|
|
1343
|
+
setReceiptTarget(i);
|
|
1344
|
+
receiptPicker.open();
|
|
1345
|
+
},
|
|
1346
|
+
title: t2("financial.quickTx.receiptCapture"),
|
|
1347
|
+
"aria-label": t2("financial.quickTx.receiptCapture"),
|
|
1348
|
+
className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors ${inst.receiptUrl ? "text-success hover:bg-success-soft" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
1349
|
+
children: inst.receiptUrl ? /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Camera, { className: "h-3.5 w-3.5" })
|
|
1350
|
+
}
|
|
1351
|
+
),
|
|
1352
|
+
saved && !isPaid && /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: () => setPayingMovement(saved), children: [
|
|
1353
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "mr-1 h-3.5 w-3.5" }),
|
|
1354
|
+
t2("financial.invoice.pay")
|
|
1355
|
+
] })
|
|
1356
|
+
] })
|
|
1632
1357
|
] }),
|
|
1633
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
1634
|
-
|
|
1635
|
-
|
|
1358
|
+
!isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
|
|
1359
|
+
/* @__PURE__ */ jsx(
|
|
1360
|
+
"img",
|
|
1361
|
+
{
|
|
1362
|
+
src: inst.receiptUrl,
|
|
1363
|
+
alt: t2("financial.quickTx.receipt"),
|
|
1364
|
+
className: "h-8 w-8 shrink-0 rounded border object-cover"
|
|
1365
|
+
}
|
|
1366
|
+
),
|
|
1367
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-medium text-success", children: t2("financial.invoiceForm.willBePaid") }),
|
|
1368
|
+
/* @__PURE__ */ jsxs(
|
|
1369
|
+
"select",
|
|
1370
|
+
{
|
|
1371
|
+
value: inst.payMethodTypeId ?? "",
|
|
1372
|
+
onChange: (e) => patch({ payMethodTypeId: e.target.value }),
|
|
1373
|
+
className: "min-w-[8rem] flex-1 rounded-input border border-input bg-card px-2 py-1 text-xs shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring",
|
|
1374
|
+
children: [
|
|
1375
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoiceForm.selectMethod") }),
|
|
1376
|
+
paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx("option", { value: tp.id, children: tp.name }, tp.id))
|
|
1377
|
+
]
|
|
1378
|
+
}
|
|
1379
|
+
),
|
|
1380
|
+
/* @__PURE__ */ jsxs(
|
|
1381
|
+
"select",
|
|
1636
1382
|
{
|
|
1637
|
-
value: inst.
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1383
|
+
value: inst.payAccountId ?? (bankAccounts.length === 1 ? bankAccounts[0].id : ""),
|
|
1384
|
+
onChange: (e) => patch({ payAccountId: e.target.value }),
|
|
1385
|
+
className: "min-w-[8rem] flex-1 rounded-input border border-input bg-card px-2 py-1 text-xs shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring",
|
|
1386
|
+
children: [
|
|
1387
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoiceForm.selectAccount") }),
|
|
1388
|
+
bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
|
|
1389
|
+
]
|
|
1644
1390
|
}
|
|
1645
|
-
)
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1391
|
+
),
|
|
1392
|
+
/* @__PURE__ */ jsx(
|
|
1393
|
+
"button",
|
|
1394
|
+
{
|
|
1395
|
+
type: "button",
|
|
1396
|
+
onClick: () => patch({ receiptUrl: void 0, payMethodTypeId: void 0, payAccountId: void 0 }),
|
|
1397
|
+
"aria-label": t2("financial.quickTx.receiptRemove"),
|
|
1398
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
1399
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
1400
|
+
}
|
|
1401
|
+
)
|
|
1650
1402
|
] })
|
|
1651
1403
|
] }, inst._id);
|
|
1652
1404
|
}) })
|
|
1653
|
-
] }) })
|
|
1405
|
+
] }) });
|
|
1654
1406
|
const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1655
|
-
/* @__PURE__ */
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1407
|
+
/* @__PURE__ */ jsxs(OrderFormBlock, { title: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"), children: [
|
|
1408
|
+
/* @__PURE__ */ jsx(
|
|
1409
|
+
SearchSelect,
|
|
1410
|
+
{
|
|
1411
|
+
required: true,
|
|
1412
|
+
value: contactId ?? "",
|
|
1413
|
+
displayValue: contactName,
|
|
1414
|
+
onChange: (id, opt) => {
|
|
1415
|
+
setContactId(id || void 0);
|
|
1416
|
+
setContactName(opt?.label ?? "");
|
|
1417
|
+
},
|
|
1418
|
+
onSearch: async (q) => {
|
|
1419
|
+
if (!config.contactLookup) return [];
|
|
1420
|
+
const results = await config.contactLookup.search(q);
|
|
1421
|
+
return results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, group: r.group, data: r }));
|
|
1422
|
+
},
|
|
1423
|
+
createLabel: t2("financial.invoiceForm.createContact"),
|
|
1424
|
+
clearLabel: t2("financial.invoiceForm.clear"),
|
|
1425
|
+
onCreate: contactCreate.onCreate,
|
|
1426
|
+
placeholder: t2("financial.invoiceForm.searchType")
|
|
1427
|
+
}
|
|
1428
|
+
),
|
|
1429
|
+
contactCreate.modal
|
|
1430
|
+
] }),
|
|
1673
1431
|
/* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1674
1432
|
hasLocations && /* @__PURE__ */ jsx(LocationSelector, { locations, value: unitId, onChange: setUnitId }),
|
|
1675
1433
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -1690,7 +1448,6 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1690
1448
|
)
|
|
1691
1449
|
] })
|
|
1692
1450
|
] }) }),
|
|
1693
|
-
!isEdit && installmentsEditor,
|
|
1694
1451
|
/* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoiceForm.observations"), children: /* @__PURE__ */ jsx(
|
|
1695
1452
|
"textarea",
|
|
1696
1453
|
{
|
|
@@ -1756,6 +1513,21 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1756
1513
|
removeItem(item._id);
|
|
1757
1514
|
if (expandedItemId === item._id) setExpandedItemId(null);
|
|
1758
1515
|
},
|
|
1516
|
+
extraFields: postable.length > 0 && /* @__PURE__ */ jsx("div", { className: "fz-fields fz-fields-2", children: /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1517
|
+
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.account") }),
|
|
1518
|
+
/* @__PURE__ */ jsxs(
|
|
1519
|
+
"select",
|
|
1520
|
+
{
|
|
1521
|
+
value: item.accountId ?? "",
|
|
1522
|
+
onChange: (e) => updateItem(item._id, { accountId: e.target.value || void 0 }),
|
|
1523
|
+
className: "mt-0.5 w-full rounded-input border border-input bg-card px-2.5 py-1.5 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring",
|
|
1524
|
+
children: [
|
|
1525
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoiceForm.accountNone") }),
|
|
1526
|
+
postable.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: accountLabel(a) }, a.id))
|
|
1527
|
+
]
|
|
1528
|
+
}
|
|
1529
|
+
)
|
|
1530
|
+
] }) }),
|
|
1759
1531
|
entityLookups: config.entityLookups,
|
|
1760
1532
|
showTypeSelector: hasItemLookups,
|
|
1761
1533
|
allowSurcharge: true,
|
|
@@ -1781,13 +1553,19 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1781
1553
|
]
|
|
1782
1554
|
}
|
|
1783
1555
|
),
|
|
1556
|
+
receiptPicker.input,
|
|
1784
1557
|
payingMovement && /* @__PURE__ */ jsx(
|
|
1785
1558
|
PaymentModal,
|
|
1786
1559
|
{
|
|
1787
1560
|
movement: payingMovement,
|
|
1788
|
-
|
|
1561
|
+
receiptUrl: pendingReceipt,
|
|
1562
|
+
onClose: () => {
|
|
1563
|
+
setPayingMovement(null);
|
|
1564
|
+
setPendingReceipt(void 0);
|
|
1565
|
+
},
|
|
1789
1566
|
onPaid: () => {
|
|
1790
1567
|
setPayingMovement(null);
|
|
1568
|
+
setPendingReceipt(void 0);
|
|
1791
1569
|
void (async () => {
|
|
1792
1570
|
if (!editId) return;
|
|
1793
1571
|
const movements = await provider.getMovements({ direction });
|
|
@@ -1826,6 +1604,27 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1826
1604
|
` })
|
|
1827
1605
|
] });
|
|
1828
1606
|
}
|
|
1607
|
+
|
|
1608
|
+
// src/lib/settlements.ts
|
|
1609
|
+
function groupPaymentsByBill(bills, payments) {
|
|
1610
|
+
const byBillId = /* @__PURE__ */ new Map();
|
|
1611
|
+
for (const bill of bills) byBillId.set(bill.id, []);
|
|
1612
|
+
const unattributed = [];
|
|
1613
|
+
for (const payment of payments) {
|
|
1614
|
+
if (payment.settlesMovementId) {
|
|
1615
|
+
const bucket = byBillId.get(payment.settlesMovementId);
|
|
1616
|
+
if (bucket) bucket.push(payment);
|
|
1617
|
+
else unattributed.push(payment);
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
const derived = bills.find(
|
|
1621
|
+
(bill) => (bill.installmentNumber ?? null) === (payment.installmentNumber ?? null)
|
|
1622
|
+
);
|
|
1623
|
+
if (derived) byBillId.get(derived.id).push(payment);
|
|
1624
|
+
else unattributed.push(payment);
|
|
1625
|
+
}
|
|
1626
|
+
return { byBillId, unattributed };
|
|
1627
|
+
}
|
|
1829
1628
|
var STATUS_COLORS = {
|
|
1830
1629
|
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
|
|
1831
1630
|
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
|
|
@@ -1854,6 +1653,13 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
1854
1653
|
const [payments, setPayments] = useState([]);
|
|
1855
1654
|
const [loading, setLoading] = useState(true);
|
|
1856
1655
|
const [payingMovement, setPayingMovement] = useState(null);
|
|
1656
|
+
const [receiptFor, setReceiptFor] = useState(null);
|
|
1657
|
+
const [pendingReceipt, setPendingReceipt] = useState();
|
|
1658
|
+
const receiptPicker = useReceiptPicker((dataUrl) => {
|
|
1659
|
+
setPendingReceipt(dataUrl);
|
|
1660
|
+
setPayingMovement(receiptFor);
|
|
1661
|
+
setReceiptFor(null);
|
|
1662
|
+
});
|
|
1857
1663
|
const [expandedMovId, setExpandedMovId] = useState(null);
|
|
1858
1664
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
1859
1665
|
const [confirmCancel, setConfirmCancel] = useState(false);
|
|
@@ -1915,7 +1721,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
1915
1721
|
}
|
|
1916
1722
|
}
|
|
1917
1723
|
if (loading) {
|
|
1918
|
-
return /* @__PURE__ */ jsx(OrderDetailSkeleton, {});
|
|
1724
|
+
return /* @__PURE__ */ jsx(OrderDetailSkeleton, { parentLabel, onBack });
|
|
1919
1725
|
}
|
|
1920
1726
|
if (!invoice) {
|
|
1921
1727
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
@@ -1923,6 +1729,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
1923
1729
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.invoice.notFoundDesc") })
|
|
1924
1730
|
] });
|
|
1925
1731
|
}
|
|
1732
|
+
const settlements = groupPaymentsByBill(movements, payments);
|
|
1926
1733
|
const statusConfig = STATUS_COLORS[invoice.status] ?? STATUS_COLORS.pending;
|
|
1927
1734
|
statusConfig.icon;
|
|
1928
1735
|
const totalPaid = movements.reduce((sum, m) => sum + m.paidAmount, 0);
|
|
@@ -1997,7 +1804,6 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
1997
1804
|
{
|
|
1998
1805
|
title: invoice.fiscalNumber ? `#${invoice.fiscalNumber}` : invoice.contactName ?? invoice.invoiceDate,
|
|
1999
1806
|
badges: [{ label: t2(statusConfig.labelKey), tone: statusConfig.tone }],
|
|
2000
|
-
subtitle: invoice.invoiceDate,
|
|
2001
1807
|
onBack,
|
|
2002
1808
|
backLabel: parentLabel,
|
|
2003
1809
|
actions,
|
|
@@ -2016,150 +1822,183 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
2016
1822
|
mainSections: (
|
|
2017
1823
|
// O cronograma no MESMO bloco que os itens e o pagamento — antes o título
|
|
2018
1824
|
// ficava fora do cartão, e a seção lia como se fosse de outra tela.
|
|
2019
|
-
/* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
/* @__PURE__ */ jsxs(
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
"
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2040
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2041
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: formatCurrency(mov.amount, currency) }),
|
|
2042
|
-
/* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${movStatus.bg}`, children: [
|
|
2043
|
-
/* @__PURE__ */ jsx(MovIcon, { className: "h-2 w-2" }),
|
|
2044
|
-
" ",
|
|
2045
|
-
t2(movStatus.labelKey)
|
|
2046
|
-
] })
|
|
1825
|
+
/* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsxs("div", { className: "-mx-4 -mb-4", children: [
|
|
1826
|
+
movements.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-4 text-center text-sm text-muted-foreground", children: t2("financial.invoice.noInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y border-t", children: movements.map((mov) => {
|
|
1827
|
+
const remaining = mov.amount - mov.paidAmount;
|
|
1828
|
+
const isPaid = mov.status === "paid";
|
|
1829
|
+
const isPartial = mov.status === "partial";
|
|
1830
|
+
const hasPaidDetails = isPaid || isPartial;
|
|
1831
|
+
const isExpanded = expandedMovId === mov.id;
|
|
1832
|
+
const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
|
|
1833
|
+
const MovIcon = movStatus.icon;
|
|
1834
|
+
const instPayments = settlements.byBillId.get(mov.id) ?? [];
|
|
1835
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
1836
|
+
/* @__PURE__ */ jsxs(
|
|
1837
|
+
"div",
|
|
1838
|
+
{
|
|
1839
|
+
className: `flex items-center gap-3 px-4 py-3 ${hasPaidDetails ? "cursor-pointer hover:bg-muted/20 transition-colors" : ""}`,
|
|
1840
|
+
onClick: hasPaidDetails ? () => handleExpandMov(mov.id) : void 0,
|
|
1841
|
+
children: [
|
|
1842
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground w-6 shrink-0", children: [
|
|
1843
|
+
"#",
|
|
1844
|
+
mov.installmentNumber
|
|
2047
1845
|
] }),
|
|
2048
|
-
/* @__PURE__ */ jsxs("
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
onClick: (e) => {
|
|
2060
|
-
e.stopPropagation();
|
|
2061
|
-
setPayingMovement(mov);
|
|
2062
|
-
},
|
|
2063
|
-
className: "inline-flex items-center gap-1 rounded-lg bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0",
|
|
2064
|
-
children: [
|
|
2065
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
2066
|
-
t2("financial.invoice.pay"),
|
|
2067
|
-
" ",
|
|
2068
|
-
remaining < mov.amount ? formatCurrency(remaining, currency) : ""
|
|
2069
|
-
]
|
|
2070
|
-
}
|
|
2071
|
-
),
|
|
2072
|
-
isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
2073
|
-
"button",
|
|
2074
|
-
{
|
|
2075
|
-
onClick: (e) => {
|
|
2076
|
-
e.stopPropagation();
|
|
2077
|
-
setPayingMovement(mov);
|
|
2078
|
-
},
|
|
2079
|
-
className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
|
|
2080
|
-
children: [
|
|
2081
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
2082
|
-
t2("financial.invoice.pay"),
|
|
1846
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
1847
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1848
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: formatCurrency(mov.amount, currency) }),
|
|
1849
|
+
/* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${movStatus.bg}`, children: [
|
|
1850
|
+
/* @__PURE__ */ jsx(MovIcon, { className: "h-2 w-2" }),
|
|
1851
|
+
" ",
|
|
1852
|
+
t2(movStatus.labelKey)
|
|
1853
|
+
] })
|
|
1854
|
+
] }),
|
|
1855
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
1856
|
+
t2("financial.invoice.due"),
|
|
2083
1857
|
" ",
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
),
|
|
2088
|
-
hasPaidDetails && /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 text-muted-foreground shrink-0 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })
|
|
2089
|
-
]
|
|
2090
|
-
}
|
|
2091
|
-
),
|
|
2092
|
-
/* @__PURE__ */ jsx(
|
|
2093
|
-
"div",
|
|
2094
|
-
{
|
|
2095
|
-
className: "grid transition-all duration-200 ease-out",
|
|
2096
|
-
style: { gridTemplateRows: isExpanded ? "1fr" : "0fr" },
|
|
2097
|
-
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-2.5 pl-14", children: detailsLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2098
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
|
|
2099
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
|
|
2100
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
|
|
2101
|
-
] }) : instPayments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
|
|
2102
|
-
const pMethod = paymentMethods.find((m) => m.id === p.paymentMethodId);
|
|
2103
|
-
const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
|
|
2104
|
-
const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
|
|
2105
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
|
|
2106
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
|
|
2107
|
-
pType && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2108
|
-
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2109
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pType.name }),
|
|
2110
|
-
pMethod && /* @__PURE__ */ jsxs("span", { children: [
|
|
2111
|
-
"(",
|
|
2112
|
-
pMethod.name,
|
|
2113
|
-
")"
|
|
2114
|
-
] })
|
|
2115
|
-
] }),
|
|
2116
|
-
p.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2117
|
-
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2118
|
-
/* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
|
|
2119
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: p.cardBrand }),
|
|
2120
|
-
p.cardInstallments && /* @__PURE__ */ jsxs("span", { children: [
|
|
2121
|
-
p.cardInstallments,
|
|
2122
|
-
"x"
|
|
1858
|
+
mov.dueDate,
|
|
1859
|
+
mov.paidAmount > 0 && !isPaid && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
|
|
1860
|
+
mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
|
|
2123
1861
|
] })
|
|
2124
1862
|
] }),
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
t2("financial.
|
|
2134
|
-
" ",
|
|
2135
|
-
|
|
1863
|
+
!isPaid && invoice.status !== "cancelled" && /* @__PURE__ */ jsx(
|
|
1864
|
+
"button",
|
|
1865
|
+
{
|
|
1866
|
+
onClick: (e) => {
|
|
1867
|
+
e.stopPropagation();
|
|
1868
|
+
setReceiptFor(mov);
|
|
1869
|
+
receiptPicker.open();
|
|
1870
|
+
},
|
|
1871
|
+
title: t2("financial.quickTx.receiptCapture"),
|
|
1872
|
+
"aria-label": t2("financial.quickTx.receiptCapture"),
|
|
1873
|
+
className: "inline-flex shrink-0 items-center gap-1 rounded-lg border bg-card px-2.5 py-1.5 text-xs font-medium shadow-button transition-colors hover:bg-muted active:shadow-button-inset",
|
|
1874
|
+
children: /* @__PURE__ */ jsx(Camera, { className: "h-3 w-3" })
|
|
1875
|
+
}
|
|
1876
|
+
),
|
|
1877
|
+
!isPaid && !isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
1878
|
+
"button",
|
|
1879
|
+
{
|
|
1880
|
+
onClick: (e) => {
|
|
1881
|
+
e.stopPropagation();
|
|
1882
|
+
setPayingMovement(mov);
|
|
1883
|
+
},
|
|
1884
|
+
className: "inline-flex items-center gap-1 rounded-button bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0",
|
|
1885
|
+
children: [
|
|
1886
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
1887
|
+
t2("financial.invoice.pay"),
|
|
1888
|
+
" ",
|
|
1889
|
+
remaining < mov.amount ? formatCurrency(remaining, currency) : ""
|
|
1890
|
+
]
|
|
1891
|
+
}
|
|
1892
|
+
),
|
|
1893
|
+
isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
1894
|
+
"button",
|
|
1895
|
+
{
|
|
1896
|
+
onClick: (e) => {
|
|
1897
|
+
e.stopPropagation();
|
|
1898
|
+
setPayingMovement(mov);
|
|
1899
|
+
},
|
|
1900
|
+
className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
|
|
1901
|
+
children: [
|
|
1902
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
1903
|
+
t2("financial.invoice.pay"),
|
|
1904
|
+
" ",
|
|
1905
|
+
formatCurrency(remaining, currency)
|
|
1906
|
+
]
|
|
1907
|
+
}
|
|
1908
|
+
),
|
|
1909
|
+
hasPaidDetails && /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 text-muted-foreground shrink-0 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })
|
|
1910
|
+
]
|
|
1911
|
+
}
|
|
1912
|
+
),
|
|
1913
|
+
/* @__PURE__ */ jsx(
|
|
1914
|
+
"div",
|
|
1915
|
+
{
|
|
1916
|
+
className: "grid transition-all duration-200 ease-out",
|
|
1917
|
+
style: { gridTemplateRows: isExpanded ? "1fr" : "0fr" },
|
|
1918
|
+
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-2.5 pl-14", children: detailsLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1919
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
|
|
1920
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
|
|
1921
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
|
|
1922
|
+
] }) : instPayments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
|
|
1923
|
+
const pMethod = paymentMethods.find((m) => m.id === p.paymentMethodId);
|
|
1924
|
+
const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
|
|
1925
|
+
const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
|
|
1926
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
|
|
1927
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
|
|
1928
|
+
pType && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1929
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
1930
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pType.name }),
|
|
1931
|
+
pMethod && /* @__PURE__ */ jsxs("span", { children: [
|
|
1932
|
+
"(",
|
|
1933
|
+
pMethod.name,
|
|
1934
|
+
")"
|
|
1935
|
+
] })
|
|
1936
|
+
] }),
|
|
1937
|
+
p.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1938
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
1939
|
+
/* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
|
|
1940
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: p.cardBrand }),
|
|
1941
|
+
p.cardInstallments && /* @__PURE__ */ jsxs("span", { children: [
|
|
1942
|
+
p.cardInstallments,
|
|
1943
|
+
"x"
|
|
1944
|
+
] })
|
|
1945
|
+
] }),
|
|
1946
|
+
pAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1947
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
1948
|
+
/* @__PURE__ */ jsx(Building2, { className: "h-3 w-3 shrink-0" }),
|
|
1949
|
+
/* @__PURE__ */ jsx("span", { children: pAccount.name })
|
|
1950
|
+
] }),
|
|
1951
|
+
(p.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1952
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
1953
|
+
/* @__PURE__ */ jsxs("span", { className: "text-warning", children: [
|
|
1954
|
+
t2("financial.statements.columnFee"),
|
|
1955
|
+
" ",
|
|
1956
|
+
formatCurrency(p.feeAmount ?? 0, currency)
|
|
1957
|
+
] })
|
|
1958
|
+
] }),
|
|
1959
|
+
p.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1960
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
1961
|
+
/* @__PURE__ */ jsx("span", { children: p.paymentDate })
|
|
1962
|
+
] }),
|
|
1963
|
+
p.notes && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1964
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
1965
|
+
/* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", children: p.notes })
|
|
2136
1966
|
] })
|
|
2137
|
-
] })
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
1967
|
+
] }, p.id);
|
|
1968
|
+
}) }) }) })
|
|
1969
|
+
}
|
|
1970
|
+
)
|
|
1971
|
+
] }, mov.id);
|
|
1972
|
+
}) }),
|
|
1973
|
+
settlements.unattributed.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t bg-warning-soft/40 px-4 py-2.5", children: [
|
|
1974
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground", children: t2("financial.invoice.unlinkedPayments") }),
|
|
1975
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-[10px] text-muted-foreground", children: t2("financial.invoice.unlinkedPaymentsDesc") }),
|
|
1976
|
+
settlements.unattributed.map((p) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
|
|
1977
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
|
|
1978
|
+
p.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1979
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
1980
|
+
/* @__PURE__ */ jsx("span", { children: p.paymentDate })
|
|
1981
|
+
] })
|
|
1982
|
+
] }, p.id))
|
|
1983
|
+
] })
|
|
1984
|
+
] }) })
|
|
2152
1985
|
),
|
|
2153
1986
|
extraSections: sideBlocks
|
|
2154
1987
|
}
|
|
2155
1988
|
),
|
|
1989
|
+
receiptPicker.input,
|
|
2156
1990
|
payingMovement && /* @__PURE__ */ jsx(
|
|
2157
1991
|
PaymentModal,
|
|
2158
1992
|
{
|
|
2159
1993
|
movement: payingMovement,
|
|
2160
|
-
|
|
1994
|
+
receiptUrl: pendingReceipt,
|
|
1995
|
+
onClose: () => {
|
|
1996
|
+
setPayingMovement(null);
|
|
1997
|
+
setPendingReceipt(void 0);
|
|
1998
|
+
},
|
|
2161
1999
|
onPaid: () => {
|
|
2162
2000
|
setPayingMovement(null);
|
|
2001
|
+
setPendingReceipt(void 0);
|
|
2163
2002
|
loadData();
|
|
2164
2003
|
}
|
|
2165
2004
|
}
|
|
@@ -2212,7 +2051,7 @@ function referrerLabel(view, t2) {
|
|
|
2212
2051
|
function PayablesView({ intent, onNavigate, previousView, back }) {
|
|
2213
2052
|
const t2 = useTranslation();
|
|
2214
2053
|
if (intent.mode === "new") {
|
|
2215
|
-
return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "debit", onSaved: (id) => onNavigate(id ? `payables-detail:${id}` : "payables-list") });
|
|
2054
|
+
return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "debit", focusReceipt: intent.receipt, onSaved: (id) => onNavigate(id ? `payables-detail:${id}` : "payables-list") });
|
|
2216
2055
|
}
|
|
2217
2056
|
if (intent.mode === "edit" && intent.editId) {
|
|
2218
2057
|
return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "debit", editId: intent.editId, onSaved: () => onNavigate(`payables-detail:${intent.editId}`) });
|
|
@@ -2235,6 +2074,7 @@ function PayablesView({ intent, onNavigate, previousView, back }) {
|
|
|
2235
2074
|
{
|
|
2236
2075
|
direction: "debit",
|
|
2237
2076
|
onNew: () => onNavigate("payables-new"),
|
|
2077
|
+
onNewReceipt: () => onNavigate("payables-new-receipt"),
|
|
2238
2078
|
onEdit: (id) => onNavigate(`payables-detail:${id}`)
|
|
2239
2079
|
}
|
|
2240
2080
|
);
|
|
@@ -2242,7 +2082,7 @@ function PayablesView({ intent, onNavigate, previousView, back }) {
|
|
|
2242
2082
|
function ReceivablesView({ intent, onNavigate, previousView, back }) {
|
|
2243
2083
|
const t2 = useTranslation();
|
|
2244
2084
|
if (intent.mode === "new") {
|
|
2245
|
-
return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "credit", onSaved: (id) => onNavigate(id ? `receivables-detail:${id}` : "receivables-list") });
|
|
2085
|
+
return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "credit", focusReceipt: intent.receipt, onSaved: (id) => onNavigate(id ? `receivables-detail:${id}` : "receivables-list") });
|
|
2246
2086
|
}
|
|
2247
2087
|
if (intent.mode === "edit" && intent.editId) {
|
|
2248
2088
|
return /* @__PURE__ */ jsx(InvoiceFormView, { direction: "credit", editId: intent.editId, onSaved: () => onNavigate(`receivables-detail:${intent.editId}`) });
|
|
@@ -2265,6 +2105,7 @@ function ReceivablesView({ intent, onNavigate, previousView, back }) {
|
|
|
2265
2105
|
{
|
|
2266
2106
|
direction: "credit",
|
|
2267
2107
|
onNew: () => onNavigate("receivables-new"),
|
|
2108
|
+
onNewReceipt: () => onNavigate("receivables-new-receipt"),
|
|
2268
2109
|
onEdit: (id) => onNavigate(`receivables-detail:${id}`)
|
|
2269
2110
|
}
|
|
2270
2111
|
);
|
|
@@ -2401,7 +2242,7 @@ function CashOpeningPanel({
|
|
|
2401
2242
|
{
|
|
2402
2243
|
onClick: handleConfirm,
|
|
2403
2244
|
disabled: submitting,
|
|
2404
|
-
className: "inline-flex items-center gap-1.5 rounded-
|
|
2245
|
+
className: "inline-flex items-center gap-1.5 rounded-button bg-primary border border-primary px-4 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors disabled:opacity-50",
|
|
2405
2246
|
children: [
|
|
2406
2247
|
/* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
|
|
2407
2248
|
" ",
|
|
@@ -2502,7 +2343,7 @@ function CashOpeningPanel({
|
|
|
2502
2343
|
{
|
|
2503
2344
|
onClick: () => setConfirming(true),
|
|
2504
2345
|
disabled: effectiveBalance <= 0,
|
|
2505
|
-
className: "inline-flex items-center gap-1.5 rounded-
|
|
2346
|
+
className: "inline-flex items-center gap-1.5 rounded-button bg-primary border border-primary px-4 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors disabled:opacity-50",
|
|
2506
2347
|
children: [
|
|
2507
2348
|
/* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
|
|
2508
2349
|
" ",
|
|
@@ -2645,7 +2486,7 @@ function CashRegistersView() {
|
|
|
2645
2486
|
}
|
|
2646
2487
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
2647
2488
|
/* @__PURE__ */ jsx(SubpageHeader, { title: labels.cashRegisters ?? t2("financial.cash.title"), subtitle: t2("financial.cash.subtitle") }),
|
|
2648
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
2489
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm", children: [
|
|
2649
2490
|
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-4 py-3 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2650
2491
|
/* @__PURE__ */ jsx(Landmark, { className: "h-4 w-4 text-info" }),
|
|
2651
2492
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.cash.openSessions") }),
|
|
@@ -2682,7 +2523,7 @@ function CashRegistersView() {
|
|
|
2682
2523
|
] }, session.id)) })
|
|
2683
2524
|
] }),
|
|
2684
2525
|
openSessions.length > 0 && cashAccounts.length > openSessions.length && /* @__PURE__ */ jsx(OpenAnotherCollapsible, { cashAccounts, openSessions, currency, onOpen: handleOpen }),
|
|
2685
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
2526
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm", children: [
|
|
2686
2527
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
|
|
2687
2528
|
/* @__PURE__ */ jsx(Clock, { className: "h-4 w-4 text-muted-foreground" }),
|
|
2688
2529
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.cash.sessionHistory") })
|
|
@@ -2736,7 +2577,7 @@ function OpenAnotherCollapsible({
|
|
|
2736
2577
|
const [expanded, setExpanded] = useState(false);
|
|
2737
2578
|
const availableAccounts = cashAccounts.filter((a) => !openSessions.some((s) => s.bankAccountId === a.id));
|
|
2738
2579
|
if (availableAccounts.length === 0) return null;
|
|
2739
|
-
return /* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
2580
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm", children: [
|
|
2740
2581
|
/* @__PURE__ */ jsxs(
|
|
2741
2582
|
"button",
|
|
2742
2583
|
{
|
|
@@ -2963,7 +2804,7 @@ function StatementsView({ onNavigate }) {
|
|
|
2963
2804
|
const e = row.original;
|
|
2964
2805
|
const isTransfer = e.entryKind !== "movement";
|
|
2965
2806
|
const memo = e.movement.notes;
|
|
2966
|
-
const title = e.invoice?.contactName || (isTransfer ? t2("financial.statements.transfer") : memo || "
|
|
2807
|
+
const title = e.invoice?.contactName || (isTransfer ? t2("financial.statements.transfer") : memo || t2("financial.statements.untitledEntry"));
|
|
2967
2808
|
const memoLine = memo && memo !== title ? memo : void 0;
|
|
2968
2809
|
const doc = e.invoice?.fiscalNumber;
|
|
2969
2810
|
const inst = e.movement.installmentNumber;
|
|
@@ -3083,7 +2924,7 @@ function StatementsView({ onNavigate }) {
|
|
|
3083
2924
|
);
|
|
3084
2925
|
};
|
|
3085
2926
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3086
|
-
/* @__PURE__ */ jsx(
|
|
2927
|
+
/* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
|
|
3087
2928
|
/* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
|
|
3088
2929
|
t2("financial.statements.newTransfer")
|
|
3089
2930
|
] }) }),
|
|
@@ -3173,7 +3014,12 @@ function CommissionsView() {
|
|
|
3173
3014
|
const { labels } = useFinancialConfig();
|
|
3174
3015
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3175
3016
|
/* @__PURE__ */ jsx(SubpageHeader, { title: labels.commissions, subtitle: t2("financial.commissions.subtitle") }),
|
|
3176
|
-
/* @__PURE__ */
|
|
3017
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-2xl border-2 border-dashed border-muted px-6 py-12 text-center", children: [
|
|
3018
|
+
/* @__PURE__ */ jsx("div", { className: "mb-3 flex h-12 w-12 items-center justify-center rounded-xl bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(Users, { className: "h-6 w-6" }) }),
|
|
3019
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t2("financial.commissions.emptyTitle") }),
|
|
3020
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-xs text-muted-foreground", children: t2("financial.commissions.emptyDesc") }),
|
|
3021
|
+
/* @__PURE__ */ jsx("p", { className: "mt-3 max-w-sm text-xs text-muted-foreground", children: t2("financial.commissions.emptyRulesHint") })
|
|
3022
|
+
] })
|
|
3177
3023
|
] });
|
|
3178
3024
|
}
|
|
3179
3025
|
function cardMath(acc) {
|
|
@@ -3183,15 +3029,13 @@ function cardMath(acc) {
|
|
|
3183
3029
|
const pct = limit > 0 ? Math.min(100, used / limit * 100) : 0;
|
|
3184
3030
|
return { limit, used, available, pct };
|
|
3185
3031
|
}
|
|
3186
|
-
function CardsView() {
|
|
3032
|
+
function CardsView({ onNavigate }) {
|
|
3187
3033
|
const t2 = useTranslation();
|
|
3188
3034
|
const { currency } = useFinancialConfig();
|
|
3189
3035
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
3190
3036
|
const bankAccountsLoading = useFinancialStore((s) => s.bankAccountsLoading);
|
|
3191
3037
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
3192
3038
|
const [tab, setTab] = useState("open");
|
|
3193
|
-
const [addOpen, setAddOpen] = useState(false);
|
|
3194
|
-
const [addCardId, setAddCardId] = useState();
|
|
3195
3039
|
useEffect(() => {
|
|
3196
3040
|
fetchBankAccounts();
|
|
3197
3041
|
}, []);
|
|
@@ -3212,13 +3056,12 @@ function CardsView() {
|
|
|
3212
3056
|
}
|
|
3213
3057
|
return { totalAvailable, totalBills };
|
|
3214
3058
|
}, [openCards]);
|
|
3215
|
-
function openAddExpense(
|
|
3216
|
-
|
|
3217
|
-
setAddOpen(true);
|
|
3059
|
+
function openAddExpense(_cardId) {
|
|
3060
|
+
onNavigate?.("payables-new");
|
|
3218
3061
|
}
|
|
3219
3062
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3220
3063
|
/* @__PURE__ */ jsx(SubpageHeader, { title: t2("financial.cards.title"), subtitle: t2("financial.cards.subtitle") }),
|
|
3221
|
-
bankAccountsLoading && cards.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: "
|
|
3064
|
+
bankAccountsLoading && cards.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: t2("financial.cards.loading") }) : cards.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-2xl border-2 border-dashed border-muted", children: [
|
|
3222
3065
|
/* @__PURE__ */ 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__ */ jsx(CreditCard, { className: "h-6 w-6" }) }),
|
|
3223
3066
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t2("financial.cards.noCards") }),
|
|
3224
3067
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 max-w-xs", children: t2("financial.cards.noCardsHint") })
|
|
@@ -3301,16 +3144,7 @@ function CardsView() {
|
|
|
3301
3144
|
] })
|
|
3302
3145
|
] }, card.id);
|
|
3303
3146
|
}) })
|
|
3304
|
-
] })
|
|
3305
|
-
/* @__PURE__ */ jsx(
|
|
3306
|
-
QuickTransactionForm,
|
|
3307
|
-
{
|
|
3308
|
-
open: addOpen,
|
|
3309
|
-
onOpenChange: setAddOpen,
|
|
3310
|
-
defaultType: "expense",
|
|
3311
|
-
defaultAccountId: addCardId
|
|
3312
|
-
}
|
|
3313
|
-
)
|
|
3147
|
+
] })
|
|
3314
3148
|
] });
|
|
3315
3149
|
}
|
|
3316
3150
|
function ReconciliationView() {
|
|
@@ -3389,12 +3223,12 @@ function ReconciliationView() {
|
|
|
3389
3223
|
] })
|
|
3390
3224
|
] })
|
|
3391
3225
|
] }),
|
|
3392
|
-
lines.length === 0 && !loading ? /* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
3226
|
+
lines.length === 0 && !loading ? /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm py-16 text-center", children: [
|
|
3393
3227
|
/* @__PURE__ */ jsx(Check, { className: "h-8 w-8 text-success/40 mx-auto mb-2" }),
|
|
3394
3228
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t2("financial.reconciliation.empty") }),
|
|
3395
3229
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: t2("financial.reconciliation.emptyHint") })
|
|
3396
3230
|
] }) : /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
|
|
3397
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
3231
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm overflow-hidden", children: [
|
|
3398
3232
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
|
|
3399
3233
|
/* @__PURE__ */ jsx(Landmark, { className: "h-4 w-4 text-muted-foreground" }),
|
|
3400
3234
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.reconciliation.bankLines") })
|
|
@@ -3425,7 +3259,7 @@ function ReconciliationView() {
|
|
|
3425
3259
|
);
|
|
3426
3260
|
}) })
|
|
3427
3261
|
] }),
|
|
3428
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
3262
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm overflow-hidden", children: [
|
|
3429
3263
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
|
|
3430
3264
|
/* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4 text-muted-foreground" }),
|
|
3431
3265
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.reconciliation.candidates") })
|
|
@@ -3692,8 +3526,12 @@ function FinancialOnboarding({ onComplete }) {
|
|
|
3692
3526
|
}
|
|
3693
3527
|
function parseIntent(activeView) {
|
|
3694
3528
|
const { base, id } = parseViewId(activeView);
|
|
3695
|
-
const m = base.match(/^(payables|receivables)-(new|list|detail|edit)$/);
|
|
3696
|
-
if (m)
|
|
3529
|
+
const m = base.match(/^(payables|receivables)-(new-receipt|new|list|detail|edit)$/);
|
|
3530
|
+
if (m) {
|
|
3531
|
+
const raw = m[2];
|
|
3532
|
+
const mode = raw === "new-receipt" ? "new" : raw;
|
|
3533
|
+
return { view: m[1], mode, editId: id, receipt: raw === "new-receipt" };
|
|
3534
|
+
}
|
|
3697
3535
|
return { view: activeView };
|
|
3698
3536
|
}
|
|
3699
3537
|
function buildNav(config, activeView, navigate, t2) {
|
|
@@ -3713,7 +3551,7 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
3713
3551
|
icon: "ArrowUpCircle",
|
|
3714
3552
|
active: activeView.startsWith("payables"),
|
|
3715
3553
|
children: [
|
|
3716
|
-
{ id: "payables-new", label: t2("financial.nav.new"), active: activeView
|
|
3554
|
+
{ id: "payables-new", label: t2("financial.nav.new"), active: activeView.startsWith("payables-new"), onClick: () => navigate("payables-new") },
|
|
3717
3555
|
{ id: "payables-list", label: t2("financial.nav.list"), active: activeView === "payables-list", onClick: () => navigate("payables-list") },
|
|
3718
3556
|
{ id: "payables-recurring", label: t2("financial.nav.recurringExpenses"), active: activeView === "payables-recurring", onClick: () => navigate("payables-recurring") }
|
|
3719
3557
|
]
|
|
@@ -3726,7 +3564,7 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
3726
3564
|
icon: "ArrowDownCircle",
|
|
3727
3565
|
active: activeView.startsWith("receivables"),
|
|
3728
3566
|
children: [
|
|
3729
|
-
{ id: "receivables-new", label: t2("financial.nav.new"), active: activeView
|
|
3567
|
+
{ id: "receivables-new", label: t2("financial.nav.new"), active: activeView.startsWith("receivables-new"), onClick: () => navigate("receivables-new") },
|
|
3730
3568
|
{ id: "receivables-list", label: t2("financial.nav.list"), active: activeView === "receivables-list", onClick: () => navigate("receivables-list") }
|
|
3731
3569
|
]
|
|
3732
3570
|
});
|
|
@@ -3770,10 +3608,8 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
3770
3608
|
id: "commissions",
|
|
3771
3609
|
label: t2("financial.nav.commissions"),
|
|
3772
3610
|
icon: "Users",
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
{ id: "commissions-rules", label: t2("financial.nav.rules"), active: activeView === "commissions", onClick: () => navigate("commissions") }
|
|
3776
|
-
]
|
|
3611
|
+
active: activeView === "commissions",
|
|
3612
|
+
onClick: () => navigate("commissions")
|
|
3777
3613
|
});
|
|
3778
3614
|
}
|
|
3779
3615
|
if (config.modules.cards) {
|
|
@@ -3781,10 +3617,8 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
3781
3617
|
id: "cards",
|
|
3782
3618
|
label: t2("financial.nav.cards"),
|
|
3783
3619
|
icon: "CreditCard",
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
{ id: "cards-reconciliation", label: t2("financial.nav.reconciliation"), active: activeView === "cards", onClick: () => navigate("cards") }
|
|
3787
|
-
]
|
|
3620
|
+
active: activeView === "cards",
|
|
3621
|
+
onClick: () => navigate("cards")
|
|
3788
3622
|
});
|
|
3789
3623
|
}
|
|
3790
3624
|
for (const item of items) {
|
|
@@ -3793,7 +3627,7 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
3793
3627
|
}
|
|
3794
3628
|
return items;
|
|
3795
3629
|
}
|
|
3796
|
-
function FinancialPage({ config, provider, store:
|
|
3630
|
+
function FinancialPage({ config, provider, store: store6, registries }) {
|
|
3797
3631
|
const { view, direction, navigate, previousView, back } = useModuleNavigation("/financial", {
|
|
3798
3632
|
summary: 0,
|
|
3799
3633
|
"payables-list": 0,
|
|
@@ -3832,26 +3666,7 @@ function FinancialPage({ config, provider, store: store2, registries }) {
|
|
|
3832
3666
|
const intent = parseIntent(view);
|
|
3833
3667
|
const isSettings = view === "settings";
|
|
3834
3668
|
const nav = useModuleNavAccess(buildNav(config, view, navigate, t2));
|
|
3835
|
-
const [quickAddOpen, setQuickAddOpen] = useState(false);
|
|
3836
|
-
const [quickAddType, setQuickAddType] = useState("expense");
|
|
3837
|
-
const [quickAddReceipt, setQuickAddReceipt] = useState(false);
|
|
3838
|
-
const openQuickAdd2 = useCallback((type = "expense") => {
|
|
3839
|
-
setQuickAddType(type);
|
|
3840
|
-
setQuickAddReceipt(false);
|
|
3841
|
-
setQuickAddOpen(true);
|
|
3842
|
-
}, []);
|
|
3843
|
-
const pendingQuickAdd = usePendingQuickAdd();
|
|
3844
|
-
useEffect(() => {
|
|
3845
|
-
if (!pendingQuickAdd) return;
|
|
3846
|
-
const req = consumeQuickAdd();
|
|
3847
|
-
if (!req) return;
|
|
3848
|
-
if (!canCreate) return;
|
|
3849
|
-
setQuickAddType(req.type);
|
|
3850
|
-
setQuickAddReceipt(req.receipt);
|
|
3851
|
-
setQuickAddOpen(true);
|
|
3852
|
-
}, [pendingQuickAdd]);
|
|
3853
3669
|
const quickActions = useMemo(() => {
|
|
3854
|
-
if (config.quickAdd) return [];
|
|
3855
3670
|
if (!canCreate) return [];
|
|
3856
3671
|
const actions = [];
|
|
3857
3672
|
if (config.modules.payables) {
|
|
@@ -3882,7 +3697,7 @@ function FinancialPage({ config, provider, store: store2, registries }) {
|
|
|
3882
3697
|
});
|
|
3883
3698
|
}
|
|
3884
3699
|
return actions;
|
|
3885
|
-
}, [config.modules,
|
|
3700
|
+
}, [config.modules, canCreate, navigate, t2]);
|
|
3886
3701
|
function handleOnboardingComplete() {
|
|
3887
3702
|
setOnboardingComplete(true);
|
|
3888
3703
|
try {
|
|
@@ -3891,14 +3706,13 @@ function FinancialPage({ config, provider, store: store2, registries }) {
|
|
|
3891
3706
|
}
|
|
3892
3707
|
}
|
|
3893
3708
|
if (!onboardingComplete) {
|
|
3894
|
-
return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store:
|
|
3709
|
+
return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store6, children: /* @__PURE__ */ jsx(FinancialOnboarding, { onComplete: handleOnboardingComplete }) });
|
|
3895
3710
|
}
|
|
3896
3711
|
if (isSettings && registries && registries.length > 0) {
|
|
3897
|
-
return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store:
|
|
3712
|
+
return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store6, children: /* @__PURE__ */ jsx(PageTransition, { transitionKey: "settings", direction, children: /* @__PURE__ */ jsx("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsx(
|
|
3898
3713
|
PluginSettingsPanel,
|
|
3899
3714
|
{
|
|
3900
3715
|
title: t2("financial.settingsPage.title"),
|
|
3901
|
-
subtitle: t2("financial.settingsPage.subtitle"),
|
|
3902
3716
|
generalSettings: /* @__PURE__ */ jsx(FinancialGeneralSettings, {}),
|
|
3903
3717
|
registries,
|
|
3904
3718
|
hostPluginId: "financial",
|
|
@@ -3923,7 +3737,7 @@ function FinancialPage({ config, provider, store: store2, registries }) {
|
|
|
3923
3737
|
case "commissions":
|
|
3924
3738
|
return /* @__PURE__ */ jsx(CommissionsView, {});
|
|
3925
3739
|
case "cards":
|
|
3926
|
-
return /* @__PURE__ */ jsx(CardsView, {});
|
|
3740
|
+
return /* @__PURE__ */ jsx(CardsView, { onNavigate: navigate });
|
|
3927
3741
|
case "notas-fiscais":
|
|
3928
3742
|
return /* @__PURE__ */ jsx(NotasFiscaisView, {});
|
|
3929
3743
|
default:
|
|
@@ -3935,7 +3749,7 @@ function FinancialPage({ config, provider, store: store2, registries }) {
|
|
|
3935
3749
|
if (!feature) return renderView();
|
|
3936
3750
|
return /* @__PURE__ */ jsx(EntitlementGate, { feature, fallback: /* @__PURE__ */ jsx(UpgradePrompt, { feature }), children: renderView() });
|
|
3937
3751
|
}
|
|
3938
|
-
return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store:
|
|
3752
|
+
return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store6, children: /* @__PURE__ */ jsx(
|
|
3939
3753
|
ModulePage,
|
|
3940
3754
|
{
|
|
3941
3755
|
title: config.labels.pageTitle,
|
|
@@ -3945,45 +3759,15 @@ function FinancialPage({ config, provider, store: store2, registries }) {
|
|
|
3945
3759
|
showHeader: intent.view === "summary" || view === "summary",
|
|
3946
3760
|
viewKey: view,
|
|
3947
3761
|
direction,
|
|
3948
|
-
headerAction: /* @__PURE__ */
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
size: "sm",
|
|
3958
|
-
className: "hidden md:inline-flex",
|
|
3959
|
-
onClick: () => openQuickAdd2("expense"),
|
|
3960
|
-
children: [
|
|
3961
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
3962
|
-
t2("financial.quickTx.newTransaction")
|
|
3963
|
-
]
|
|
3964
|
-
}
|
|
3965
|
-
) }),
|
|
3966
|
-
/* @__PURE__ */ jsx(
|
|
3967
|
-
ModuleActionBar,
|
|
3968
|
-
{
|
|
3969
|
-
quickActions,
|
|
3970
|
-
settingsPath: registries && registries.length > 0 ? "/settings/financial" : void 0,
|
|
3971
|
-
settingsLabel: t2("financial.settingsPage.title")
|
|
3972
|
-
}
|
|
3973
|
-
)
|
|
3974
|
-
] }),
|
|
3975
|
-
children: [
|
|
3976
|
-
renderGatedView(),
|
|
3977
|
-
/* @__PURE__ */ jsx(
|
|
3978
|
-
QuickTransactionForm,
|
|
3979
|
-
{
|
|
3980
|
-
open: quickAddOpen,
|
|
3981
|
-
onOpenChange: setQuickAddOpen,
|
|
3982
|
-
defaultType: quickAddType,
|
|
3983
|
-
focusReceipt: quickAddReceipt
|
|
3984
|
-
}
|
|
3985
|
-
)
|
|
3986
|
-
]
|
|
3762
|
+
headerAction: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(
|
|
3763
|
+
ModuleActionBar,
|
|
3764
|
+
{
|
|
3765
|
+
quickActions,
|
|
3766
|
+
settingsPath: registries && registries.length > 0 ? "/settings/financial" : void 0,
|
|
3767
|
+
settingsLabel: t2("financial.settingsPage.title")
|
|
3768
|
+
}
|
|
3769
|
+
) }),
|
|
3770
|
+
children: renderGatedView()
|
|
3987
3771
|
}
|
|
3988
3772
|
) });
|
|
3989
3773
|
}
|
|
@@ -4149,7 +3933,7 @@ function BalanceHero() {
|
|
|
4149
3933
|
const summary = useFinancialStore((s) => s.summary);
|
|
4150
3934
|
const navigate = useDashboardNavigate();
|
|
4151
3935
|
useEnsureSummary();
|
|
4152
|
-
const [hidden, setHidden] =
|
|
3936
|
+
const [hidden, setHidden] = React17.useState(false);
|
|
4153
3937
|
const money = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
|
|
4154
3938
|
const inflow = summary?.monthlyInflow ?? 0;
|
|
4155
3939
|
const outflow = summary?.monthlyOutflow ?? 0;
|
|
@@ -4222,7 +4006,7 @@ function categorizeSpend(notes) {
|
|
|
4222
4006
|
function SpendByCategoryChart() {
|
|
4223
4007
|
const t2 = useTranslation();
|
|
4224
4008
|
const provider = useFinancialProvider();
|
|
4225
|
-
const [rows, setRows] =
|
|
4009
|
+
const [rows, setRows] = React17.useState([]);
|
|
4226
4010
|
useEffect(() => {
|
|
4227
4011
|
let alive = true;
|
|
4228
4012
|
void (async () => {
|
|
@@ -4265,14 +4049,14 @@ function UpcomingBills() {
|
|
|
4265
4049
|
const { currency } = useFinancialConfig();
|
|
4266
4050
|
const provider = useFinancialProvider();
|
|
4267
4051
|
const dueLabel = useDueLabel();
|
|
4268
|
-
const [bills, setBills] =
|
|
4052
|
+
const [bills, setBills] = React17.useState([]);
|
|
4269
4053
|
useEffect(() => {
|
|
4270
4054
|
let alive = true;
|
|
4271
4055
|
void (async () => {
|
|
4272
|
-
const
|
|
4056
|
+
const pending = await provider.getMovements({ status: "pending" });
|
|
4273
4057
|
if (!alive) return;
|
|
4274
4058
|
setBills(
|
|
4275
|
-
|
|
4059
|
+
pending.data.filter((m) => m.direction === "debit" && m.movementKind === "bill").sort((a, b) => a.dueDate.localeCompare(b.dueDate))
|
|
4276
4060
|
);
|
|
4277
4061
|
})();
|
|
4278
4062
|
return () => {
|
|
@@ -4445,7 +4229,7 @@ function createStore(options) {
|
|
|
4445
4229
|
};
|
|
4446
4230
|
}
|
|
4447
4231
|
function createMockFinancialProvider(options) {
|
|
4448
|
-
const
|
|
4232
|
+
const store6 = createStore(options);
|
|
4449
4233
|
const tenantId = "mock-tenant";
|
|
4450
4234
|
function recalcInvoiceStatus(invoice) {
|
|
4451
4235
|
if (invoice.paidAmount >= invoice.totalAmount) {
|
|
@@ -4453,7 +4237,7 @@ function createMockFinancialProvider(options) {
|
|
|
4453
4237
|
} else if (invoice.paidAmount > 0) {
|
|
4454
4238
|
invoice.status = "partial";
|
|
4455
4239
|
} else {
|
|
4456
|
-
const movements =
|
|
4240
|
+
const movements = store6.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
|
|
4457
4241
|
const hasOverdue = movements.some((m) => m.status === "overdue" || m.status === "pending" && m.dueDate < today());
|
|
4458
4242
|
invoice.status = hasOverdue ? "overdue" : "open";
|
|
4459
4243
|
}
|
|
@@ -4462,16 +4246,16 @@ function createMockFinancialProvider(options) {
|
|
|
4462
4246
|
const provider = {
|
|
4463
4247
|
// --- Invoices ---
|
|
4464
4248
|
async getInvoices(query) {
|
|
4465
|
-
let results = [...
|
|
4249
|
+
let results = [...store6.invoices];
|
|
4466
4250
|
if (query.direction) results = results.filter((i) => i.direction === query.direction);
|
|
4467
4251
|
if (query.status) results = results.filter((i) => matchesStatus(i.status, query.status));
|
|
4468
4252
|
if (query.contactId) results = results.filter((i) => i.contactId === query.contactId);
|
|
4469
4253
|
if (query.accountId) {
|
|
4470
|
-
const invoiceIds = new Set(
|
|
4254
|
+
const invoiceIds = new Set(store6.invoiceItems.filter((item) => item.accountId === query.accountId).map((item) => item.invoiceId));
|
|
4471
4255
|
results = results.filter((invoice) => invoiceIds.has(invoice.id));
|
|
4472
4256
|
}
|
|
4473
4257
|
if (query.costCenterId) {
|
|
4474
|
-
const invoiceIds = new Set(
|
|
4258
|
+
const invoiceIds = new Set(store6.invoiceItems.filter((item) => item.costCenterId === query.costCenterId).map((item) => item.invoiceId));
|
|
4475
4259
|
results = results.filter((invoice) => invoiceIds.has(invoice.id));
|
|
4476
4260
|
}
|
|
4477
4261
|
if (query.dateRange) results = results.filter((i) => matchesDateRange(i.invoiceDate, query.dateRange));
|
|
@@ -4485,10 +4269,10 @@ function createMockFinancialProvider(options) {
|
|
|
4485
4269
|
return paginate(results, query.page, query.pageSize);
|
|
4486
4270
|
},
|
|
4487
4271
|
async getInvoiceById(id) {
|
|
4488
|
-
return
|
|
4272
|
+
return store6.invoices.find((i) => i.id === id) ?? null;
|
|
4489
4273
|
},
|
|
4490
4274
|
async getInvoiceItems(invoiceId) {
|
|
4491
|
-
return
|
|
4275
|
+
return store6.invoiceItems.filter((i) => i.invoiceId === invoiceId);
|
|
4492
4276
|
},
|
|
4493
4277
|
async createInvoice(input) {
|
|
4494
4278
|
const invoiceId = uid();
|
|
@@ -4517,7 +4301,7 @@ function createMockFinancialProvider(options) {
|
|
|
4517
4301
|
createdAt: now(),
|
|
4518
4302
|
updatedAt: now()
|
|
4519
4303
|
};
|
|
4520
|
-
|
|
4304
|
+
store6.invoices.push(invoice);
|
|
4521
4305
|
for (const itemInput of input.items) {
|
|
4522
4306
|
const itemTotal = itemInput.quantity * itemInput.unitPrice - (itemInput.discount ?? 0) + (itemInput.surcharge ?? 0);
|
|
4523
4307
|
const item = {
|
|
@@ -4536,7 +4320,7 @@ function createMockFinancialProvider(options) {
|
|
|
4536
4320
|
metadata: itemInput.metadata,
|
|
4537
4321
|
createdAt: now()
|
|
4538
4322
|
};
|
|
4539
|
-
|
|
4323
|
+
store6.invoiceItems.push(item);
|
|
4540
4324
|
}
|
|
4541
4325
|
for (const movInput of input.installments) {
|
|
4542
4326
|
const movement = {
|
|
@@ -4557,29 +4341,29 @@ function createMockFinancialProvider(options) {
|
|
|
4557
4341
|
createdAt: now(),
|
|
4558
4342
|
updatedAt: now()
|
|
4559
4343
|
};
|
|
4560
|
-
|
|
4344
|
+
store6.movements.push(movement);
|
|
4561
4345
|
}
|
|
4562
4346
|
return invoice;
|
|
4563
4347
|
},
|
|
4564
4348
|
async updateInvoice(id, data) {
|
|
4565
|
-
const invoice =
|
|
4349
|
+
const invoice = store6.invoices.find((i) => i.id === id);
|
|
4566
4350
|
if (!invoice) throw new Error(`Invoice ${id} not found`);
|
|
4567
4351
|
Object.assign(invoice, data, { updatedAt: now() });
|
|
4568
4352
|
return invoice;
|
|
4569
4353
|
},
|
|
4570
4354
|
async cancelInvoice(id) {
|
|
4571
|
-
const invoice =
|
|
4355
|
+
const invoice = store6.invoices.find((i) => i.id === id);
|
|
4572
4356
|
if (!invoice) throw new Error(`Invoice ${id} not found`);
|
|
4573
4357
|
invoice.status = "cancelled";
|
|
4574
4358
|
invoice.updatedAt = now();
|
|
4575
|
-
|
|
4359
|
+
store6.movements.filter((m) => m.invoiceId === id && m.status === "pending").forEach((m) => {
|
|
4576
4360
|
m.status = "cancelled";
|
|
4577
4361
|
m.updatedAt = now();
|
|
4578
4362
|
});
|
|
4579
4363
|
},
|
|
4580
4364
|
// --- Movements ---
|
|
4581
4365
|
async getMovements(query) {
|
|
4582
|
-
let results = [...
|
|
4366
|
+
let results = [...store6.movements];
|
|
4583
4367
|
if (query.direction) results = results.filter((m) => m.direction === query.direction);
|
|
4584
4368
|
if (query.status) results = results.filter((m) => matchesStatus(m.status, query.status));
|
|
4585
4369
|
if (query.bankAccountId) results = results.filter((m) => m.bankAccountId === query.bankAccountId);
|
|
@@ -4589,9 +4373,9 @@ function createMockFinancialProvider(options) {
|
|
|
4589
4373
|
return paginate(results, query.page, query.pageSize);
|
|
4590
4374
|
},
|
|
4591
4375
|
async payMovement(input) {
|
|
4592
|
-
const bill =
|
|
4376
|
+
const bill = store6.movements.find((m) => m.id === input.movementId);
|
|
4593
4377
|
if (!bill) throw new Error(`Movement ${input.movementId} not found`);
|
|
4594
|
-
const method = input.paymentMethodId ?
|
|
4378
|
+
const method = input.paymentMethodId ? store6.paymentMethods.find((pm) => pm.id === input.paymentMethodId) : void 0;
|
|
4595
4379
|
const feePct = bill.direction === "credit" ? method?.discountValue ?? 0 : 0;
|
|
4596
4380
|
const thisFee = round2(input.amount * (feePct / 100));
|
|
4597
4381
|
const payment = {
|
|
@@ -4603,6 +4387,7 @@ function createMockFinancialProvider(options) {
|
|
|
4603
4387
|
paidAmount: input.amount,
|
|
4604
4388
|
feeAmount: thisFee,
|
|
4605
4389
|
status: "paid",
|
|
4390
|
+
settlesMovementId: bill.id,
|
|
4606
4391
|
dueDate: bill.dueDate,
|
|
4607
4392
|
paymentDate: input.paymentDate,
|
|
4608
4393
|
installmentNumber: bill.installmentNumber,
|
|
@@ -4613,18 +4398,19 @@ function createMockFinancialProvider(options) {
|
|
|
4613
4398
|
cardBrand: input.cardBrand,
|
|
4614
4399
|
cardInstallments: input.cardInstallments,
|
|
4615
4400
|
notes: input.notes,
|
|
4401
|
+
metadata: input.metadata,
|
|
4616
4402
|
tenantId,
|
|
4617
4403
|
createdAt: now(),
|
|
4618
4404
|
updatedAt: now()
|
|
4619
4405
|
};
|
|
4620
|
-
|
|
4406
|
+
store6.movements.push(payment);
|
|
4621
4407
|
bill.paidAmount = round2(bill.paidAmount + input.amount);
|
|
4622
4408
|
bill.status = bill.paidAmount >= bill.amount ? "paid" : "partial";
|
|
4623
4409
|
bill.paymentDate = void 0;
|
|
4624
4410
|
bill.bankAccountId = void 0;
|
|
4625
4411
|
bill.updatedAt = now();
|
|
4626
4412
|
if (input.bankAccountId) {
|
|
4627
|
-
const account =
|
|
4413
|
+
const account = store6.bankAccounts.find((a) => a.id === input.bankAccountId);
|
|
4628
4414
|
if (account) {
|
|
4629
4415
|
if (bill.direction === "credit") {
|
|
4630
4416
|
account.currentBalance = round2(account.currentBalance + (input.amount - thisFee));
|
|
@@ -4635,9 +4421,9 @@ function createMockFinancialProvider(options) {
|
|
|
4635
4421
|
}
|
|
4636
4422
|
}
|
|
4637
4423
|
if (bill.invoiceId) {
|
|
4638
|
-
const invoice =
|
|
4424
|
+
const invoice = store6.invoices.find((i) => i.id === bill.invoiceId);
|
|
4639
4425
|
if (invoice) {
|
|
4640
|
-
const bills =
|
|
4426
|
+
const bills = store6.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
|
|
4641
4427
|
invoice.paidAmount = bills.reduce((sum, m) => sum + m.paidAmount, 0);
|
|
4642
4428
|
recalcInvoiceStatus(invoice);
|
|
4643
4429
|
}
|
|
@@ -4645,7 +4431,7 @@ function createMockFinancialProvider(options) {
|
|
|
4645
4431
|
return payment;
|
|
4646
4432
|
},
|
|
4647
4433
|
async cancelMovement(id) {
|
|
4648
|
-
const movement =
|
|
4434
|
+
const movement = store6.movements.find((m) => m.id === id);
|
|
4649
4435
|
if (!movement) throw new Error(`Movement ${id} not found`);
|
|
4650
4436
|
movement.status = "cancelled";
|
|
4651
4437
|
movement.updatedAt = now();
|
|
@@ -4672,22 +4458,22 @@ function createMockFinancialProvider(options) {
|
|
|
4672
4458
|
createdAt: now(),
|
|
4673
4459
|
updatedAt: now()
|
|
4674
4460
|
};
|
|
4675
|
-
|
|
4461
|
+
store6.movements.push({
|
|
4676
4462
|
...base,
|
|
4677
4463
|
id: debitMovementId,
|
|
4678
4464
|
direction: "debit",
|
|
4679
4465
|
bankAccountId: input.fromAccountId,
|
|
4680
4466
|
metadata: { transferId, transferRole: "out", counterAccountId: input.toAccountId }
|
|
4681
4467
|
});
|
|
4682
|
-
|
|
4468
|
+
store6.movements.push({
|
|
4683
4469
|
...base,
|
|
4684
4470
|
id: creditMovementId,
|
|
4685
4471
|
direction: "credit",
|
|
4686
4472
|
bankAccountId: input.toAccountId,
|
|
4687
4473
|
metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
|
|
4688
4474
|
});
|
|
4689
|
-
const from =
|
|
4690
|
-
const to =
|
|
4475
|
+
const from = store6.bankAccounts.find((a) => a.id === input.fromAccountId);
|
|
4476
|
+
const to = store6.bankAccounts.find((a) => a.id === input.toAccountId);
|
|
4691
4477
|
if (from) {
|
|
4692
4478
|
from.currentBalance = round2(from.currentBalance - input.amount);
|
|
4693
4479
|
from.updatedAt = now();
|
|
@@ -4700,7 +4486,7 @@ function createMockFinancialProvider(options) {
|
|
|
4700
4486
|
},
|
|
4701
4487
|
// --- Bank Accounts ---
|
|
4702
4488
|
async getBankAccounts() {
|
|
4703
|
-
return
|
|
4489
|
+
return store6.bankAccounts.filter((a) => a.isActive);
|
|
4704
4490
|
},
|
|
4705
4491
|
async createBankAccount(data) {
|
|
4706
4492
|
const account = {
|
|
@@ -4721,26 +4507,26 @@ function createMockFinancialProvider(options) {
|
|
|
4721
4507
|
createdAt: now(),
|
|
4722
4508
|
updatedAt: now()
|
|
4723
4509
|
};
|
|
4724
|
-
|
|
4510
|
+
store6.bankAccounts.push(account);
|
|
4725
4511
|
return account;
|
|
4726
4512
|
},
|
|
4727
4513
|
async updateBankAccount(id, data) {
|
|
4728
|
-
const account =
|
|
4514
|
+
const account = store6.bankAccounts.find((a) => a.id === id);
|
|
4729
4515
|
if (!account) throw new Error(`Bank account ${id} not found`);
|
|
4730
4516
|
Object.assign(account, data, { updatedAt: now() });
|
|
4731
4517
|
return account;
|
|
4732
4518
|
},
|
|
4733
4519
|
// --- Cash Sessions ---
|
|
4734
4520
|
async getCashSessions(bankAccountId) {
|
|
4735
|
-
let results = [...
|
|
4521
|
+
let results = [...store6.cashSessions];
|
|
4736
4522
|
if (bankAccountId) results = results.filter((s) => s.bankAccountId === bankAccountId);
|
|
4737
4523
|
return results.sort((a, b) => b.openedAt.localeCompare(a.openedAt));
|
|
4738
4524
|
},
|
|
4739
4525
|
async getOpenSession(bankAccountId) {
|
|
4740
|
-
return
|
|
4526
|
+
return store6.cashSessions.find((s) => s.bankAccountId === bankAccountId && s.status === "open") ?? null;
|
|
4741
4527
|
},
|
|
4742
4528
|
async openCashSession(input) {
|
|
4743
|
-
const account =
|
|
4529
|
+
const account = store6.bankAccounts.find((a) => a.id === input.bankAccountId);
|
|
4744
4530
|
const session = {
|
|
4745
4531
|
id: uid(),
|
|
4746
4532
|
bankAccountId: input.bankAccountId,
|
|
@@ -4754,13 +4540,13 @@ function createMockFinancialProvider(options) {
|
|
|
4754
4540
|
tenantId,
|
|
4755
4541
|
createdAt: now()
|
|
4756
4542
|
};
|
|
4757
|
-
|
|
4543
|
+
store6.cashSessions.push(session);
|
|
4758
4544
|
return session;
|
|
4759
4545
|
},
|
|
4760
4546
|
async closeCashSession(input) {
|
|
4761
|
-
const session =
|
|
4547
|
+
const session = store6.cashSessions.find((s) => s.id === input.sessionId);
|
|
4762
4548
|
if (!session) throw new Error(`Cash session ${input.sessionId} not found`);
|
|
4763
|
-
const sessionMovements =
|
|
4549
|
+
const sessionMovements = store6.movements.filter((m) => m.cashSessionId === session.id && m.status === "paid");
|
|
4764
4550
|
const inflow = sessionMovements.filter((m) => m.direction === "credit").reduce((s, m) => s + m.paidAmount, 0);
|
|
4765
4551
|
const outflow = sessionMovements.filter((m) => m.direction === "debit").reduce((s, m) => s + m.paidAmount, 0);
|
|
4766
4552
|
const expectedBalance = session.openingBalance + inflow - outflow;
|
|
@@ -4775,9 +4561,9 @@ function createMockFinancialProvider(options) {
|
|
|
4775
4561
|
return session;
|
|
4776
4562
|
},
|
|
4777
4563
|
async getCashSessionSummary(sessionId) {
|
|
4778
|
-
const session =
|
|
4564
|
+
const session = store6.cashSessions.find((s) => s.id === sessionId);
|
|
4779
4565
|
if (!session) throw new Error(`Cash session ${sessionId} not found`);
|
|
4780
|
-
const sessionMovements =
|
|
4566
|
+
const sessionMovements = store6.movements.filter((m) => m.cashSessionId === sessionId && m.status === "paid");
|
|
4781
4567
|
return {
|
|
4782
4568
|
session,
|
|
4783
4569
|
movementCount: sessionMovements.length,
|
|
@@ -4787,21 +4573,21 @@ function createMockFinancialProvider(options) {
|
|
|
4787
4573
|
},
|
|
4788
4574
|
// --- Payment Methods ---
|
|
4789
4575
|
async getPaymentMethods() {
|
|
4790
|
-
return
|
|
4576
|
+
return store6.paymentMethods.filter((pm) => pm.isActive);
|
|
4791
4577
|
},
|
|
4792
4578
|
async getPaymentMethodTypes() {
|
|
4793
|
-
return
|
|
4579
|
+
return store6.paymentMethodTypes.filter((t2) => t2.isActive);
|
|
4794
4580
|
},
|
|
4795
4581
|
// --- Chart of Accounts ---
|
|
4796
4582
|
async getChartOfAccounts() {
|
|
4797
|
-
return
|
|
4583
|
+
return store6.chartOfAccounts.filter((n) => n.isActive);
|
|
4798
4584
|
},
|
|
4799
4585
|
async getCostCenters() {
|
|
4800
|
-
return
|
|
4586
|
+
return store6.costCenters.filter((c) => c.isActive);
|
|
4801
4587
|
},
|
|
4802
4588
|
// --- Card Transactions ---
|
|
4803
4589
|
async getCardTransactions(dateRange) {
|
|
4804
|
-
let results = [...
|
|
4590
|
+
let results = [...store6.cardTransactions];
|
|
4805
4591
|
if (dateRange) results = results.filter((t2) => matchesDateRange(t2.expectedDate, dateRange));
|
|
4806
4592
|
return results.sort((a, b) => a.expectedDate.localeCompare(b.expectedDate));
|
|
4807
4593
|
},
|
|
@@ -4810,15 +4596,15 @@ function createMockFinancialProvider(options) {
|
|
|
4810
4596
|
const todayStr = today();
|
|
4811
4597
|
const isRealized = (m) => m.status !== "cancelled" && !!m.paymentDate;
|
|
4812
4598
|
const consolidated = !query.bankAccountId;
|
|
4813
|
-
const scopeAccounts = consolidated ?
|
|
4599
|
+
const scopeAccounts = consolidated ? store6.bankAccounts.filter((a) => a.isActive) : store6.bankAccounts.filter((a) => a.id === query.bankAccountId);
|
|
4814
4600
|
const accountIds = new Set(scopeAccounts.map((a) => a.id));
|
|
4815
|
-
const accountNameById = new Map(
|
|
4601
|
+
const accountNameById = new Map(store6.bankAccounts.map((a) => [a.id, a.name]));
|
|
4816
4602
|
const initialSum = scopeAccounts.reduce((s, a) => s + a.initialBalance, 0);
|
|
4817
4603
|
if (!consolidated && accountIds.size === 0) {
|
|
4818
4604
|
return { entries: [], openingBalance: 0, closingBalance: 0, totalCredits: 0, totalDebits: 0, totalFees: 0, net: 0, accountId: query.bankAccountId };
|
|
4819
4605
|
}
|
|
4820
4606
|
const netOf = (m) => m.direction === "credit" ? m.paidAmount - (m.feeAmount ?? 0) : -m.paidAmount;
|
|
4821
|
-
const inScope =
|
|
4607
|
+
const inScope = store6.movements.filter((m) => isRealized(m) && (consolidated || m.bankAccountId && accountIds.has(m.bankAccountId)));
|
|
4822
4608
|
const openingBalance = round2(initialSum + inScope.filter((m) => (m.paymentDate ?? "") < query.dateRange.from).reduce((s, m) => s + netOf(m), 0));
|
|
4823
4609
|
const rangeMovs = inScope.filter((m) => matchesDateRange(m.paymentDate, query.dateRange)).sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || a.createdAt.localeCompare(b.createdAt));
|
|
4824
4610
|
let balance = openingBalance;
|
|
@@ -4840,7 +4626,7 @@ function createMockFinancialProvider(options) {
|
|
|
4840
4626
|
}
|
|
4841
4627
|
return {
|
|
4842
4628
|
movement,
|
|
4843
|
-
invoice: movement.invoiceId ?
|
|
4629
|
+
invoice: movement.invoiceId ? store6.invoices.find((i) => i.id === movement.invoiceId) : void 0,
|
|
4844
4630
|
runningBalance: balance,
|
|
4845
4631
|
gross,
|
|
4846
4632
|
fee,
|
|
@@ -4852,9 +4638,9 @@ function createMockFinancialProvider(options) {
|
|
|
4852
4638
|
});
|
|
4853
4639
|
for (const e of entries) {
|
|
4854
4640
|
const mid = e.movement.matchedMovementId;
|
|
4855
|
-
const mm = mid ?
|
|
4641
|
+
const mm = mid ? store6.movements.find((m) => m.id === mid) : void 0;
|
|
4856
4642
|
if (!mm) continue;
|
|
4857
|
-
const inv = mm.invoiceId ?
|
|
4643
|
+
const inv = mm.invoiceId ? store6.invoices.find((i) => i.id === mm.invoiceId) : void 0;
|
|
4858
4644
|
e.reconciledWith = {
|
|
4859
4645
|
movementId: mm.id,
|
|
4860
4646
|
description: mm.notes ?? void 0,
|
|
@@ -4878,11 +4664,11 @@ function createMockFinancialProvider(options) {
|
|
|
4878
4664
|
},
|
|
4879
4665
|
// --- Summary ---
|
|
4880
4666
|
async getSummary(dateRange) {
|
|
4881
|
-
const totalBalance =
|
|
4882
|
-
const pendingReceivable =
|
|
4667
|
+
const totalBalance = store6.bankAccounts.filter((a) => a.isActive).reduce((sum, a) => sum + a.currentBalance, 0);
|
|
4668
|
+
const pendingReceivable = store6.movements.filter(
|
|
4883
4669
|
(m) => m.direction === "credit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
|
|
4884
4670
|
);
|
|
4885
|
-
const pendingPayable =
|
|
4671
|
+
const pendingPayable = store6.movements.filter(
|
|
4886
4672
|
(m) => m.direction === "debit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
|
|
4887
4673
|
);
|
|
4888
4674
|
const todayStr = today();
|
|
@@ -4890,7 +4676,7 @@ function createMockFinancialProvider(options) {
|
|
|
4890
4676
|
monthAgo.setDate(monthAgo.getDate() - 30);
|
|
4891
4677
|
const monthStart = monthAgo.toISOString().slice(0, 10);
|
|
4892
4678
|
const monthEnd = todayStr;
|
|
4893
|
-
const monthMovements =
|
|
4679
|
+
const monthMovements = store6.movements.filter(
|
|
4894
4680
|
(m) => m.movementKind === "payment" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
|
|
4895
4681
|
);
|
|
4896
4682
|
const overdueReceivable = pendingReceivable.filter((m) => m.dueDate < todayStr);
|
|
@@ -4911,12 +4697,12 @@ function createMockFinancialProvider(options) {
|
|
|
4911
4697
|
async importBankTransactions(input) {
|
|
4912
4698
|
let imported = 0, duplicates = 0;
|
|
4913
4699
|
for (const line of input.lines) {
|
|
4914
|
-
const dup =
|
|
4700
|
+
const dup = store6.movements.some((m) => m.externalSource === line.externalSource && m.externalId === line.externalId);
|
|
4915
4701
|
if (dup) {
|
|
4916
4702
|
duplicates++;
|
|
4917
4703
|
continue;
|
|
4918
4704
|
}
|
|
4919
|
-
|
|
4705
|
+
store6.movements.push({
|
|
4920
4706
|
id: uid(),
|
|
4921
4707
|
direction: line.direction,
|
|
4922
4708
|
movementKind: "payment",
|
|
@@ -4938,38 +4724,38 @@ function createMockFinancialProvider(options) {
|
|
|
4938
4724
|
return { imported, duplicates };
|
|
4939
4725
|
},
|
|
4940
4726
|
async getUnreconciled(query) {
|
|
4941
|
-
let results =
|
|
4727
|
+
let results = store6.movements.filter((m) => m.externalSource && !m.reconciledAt);
|
|
4942
4728
|
if (query?.bankAccountId) results = results.filter((m) => m.bankAccountId === query.bankAccountId);
|
|
4943
4729
|
if (query?.dateRange) results = results.filter((m) => matchesDateRange(m.paymentDate, query.dateRange));
|
|
4944
4730
|
return results.sort((a, b) => (b.paymentDate ?? "").localeCompare(a.paymentDate ?? ""));
|
|
4945
4731
|
},
|
|
4946
4732
|
async suggestReconciliation(bankMovementId) {
|
|
4947
|
-
const line =
|
|
4733
|
+
const line = store6.movements.find((m) => m.id === bankMovementId);
|
|
4948
4734
|
if (!line) return [];
|
|
4949
4735
|
const amount = line.amount;
|
|
4950
4736
|
const tol = Math.max(0.01, round2(amount * 0.02));
|
|
4951
4737
|
const lineDate = line.paymentDate ?? line.dueDate;
|
|
4952
|
-
return
|
|
4738
|
+
return store6.movements.filter((m) => !m.externalSource && !m.reconciledAt && m.direction === line.direction && m.id !== bankMovementId && Math.abs(m.amount - amount) <= tol).map((m) => {
|
|
4953
4739
|
const amtScore = amount > 0 ? Math.max(0, 1 - Math.abs(m.amount - amount) / amount) : 0;
|
|
4954
4740
|
const refDate = m.dueDate ?? m.paymentDate;
|
|
4955
4741
|
const days = lineDate && refDate ? Math.abs((new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5) : 30;
|
|
4956
4742
|
const dateScore = Math.max(0, 1 - days / 30);
|
|
4957
4743
|
return {
|
|
4958
4744
|
movement: m,
|
|
4959
|
-
invoice: m.invoiceId ?
|
|
4745
|
+
invoice: m.invoiceId ? store6.invoices.find((i) => i.id === m.invoiceId) : void 0,
|
|
4960
4746
|
score: round2(0.7 * amtScore + 0.3 * dateScore)
|
|
4961
4747
|
};
|
|
4962
4748
|
}).sort((a, b) => b.score - a.score).slice(0, 25);
|
|
4963
4749
|
},
|
|
4964
4750
|
async reconcileMovement(input) {
|
|
4965
|
-
const line =
|
|
4751
|
+
const line = store6.movements.find((m) => m.id === input.bankMovementId);
|
|
4966
4752
|
if (line) {
|
|
4967
4753
|
line.reconciledAt = now();
|
|
4968
4754
|
line.matchedMovementId = input.matchedMovementId;
|
|
4969
4755
|
line.updatedAt = now();
|
|
4970
4756
|
}
|
|
4971
4757
|
if (input.matchedMovementId) {
|
|
4972
|
-
const internal =
|
|
4758
|
+
const internal = store6.movements.find((m) => m.id === input.matchedMovementId);
|
|
4973
4759
|
if (internal) {
|
|
4974
4760
|
internal.reconciledAt = now();
|
|
4975
4761
|
internal.updatedAt = now();
|
|
@@ -4977,14 +4763,14 @@ function createMockFinancialProvider(options) {
|
|
|
4977
4763
|
}
|
|
4978
4764
|
},
|
|
4979
4765
|
async unreconcileMovement(bankMovementId) {
|
|
4980
|
-
const line =
|
|
4766
|
+
const line = store6.movements.find((m) => m.id === bankMovementId);
|
|
4981
4767
|
if (!line) return;
|
|
4982
4768
|
const matchedId = line.matchedMovementId;
|
|
4983
4769
|
line.reconciledAt = void 0;
|
|
4984
4770
|
line.matchedMovementId = void 0;
|
|
4985
4771
|
line.updatedAt = now();
|
|
4986
4772
|
if (matchedId) {
|
|
4987
|
-
const internal =
|
|
4773
|
+
const internal = store6.movements.find((m) => m.id === matchedId);
|
|
4988
4774
|
if (internal) {
|
|
4989
4775
|
internal.reconciledAt = void 0;
|
|
4990
4776
|
internal.updatedAt = now();
|
|
@@ -4993,7 +4779,7 @@ function createMockFinancialProvider(options) {
|
|
|
4993
4779
|
},
|
|
4994
4780
|
// --- Notas fiscais (NF-e / NFC-e / NFS-e) ---
|
|
4995
4781
|
async getNotas(query) {
|
|
4996
|
-
let list = [...
|
|
4782
|
+
let list = [...store6.notas].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
4997
4783
|
if (query.status) list = list.filter((n) => matchesStatus(n.status, query.status));
|
|
4998
4784
|
if (query.model) list = list.filter((n) => n.model === query.model);
|
|
4999
4785
|
if (query.orderId) list = list.filter((n) => n.orderId === query.orderId);
|
|
@@ -5004,7 +4790,7 @@ function createMockFinancialProvider(options) {
|
|
|
5004
4790
|
return paginate(list, query.page, query.pageSize);
|
|
5005
4791
|
},
|
|
5006
4792
|
async getNotaById(id) {
|
|
5007
|
-
return
|
|
4793
|
+
return store6.notas.find((n) => n.id === id) ?? null;
|
|
5008
4794
|
},
|
|
5009
4795
|
async createNota(input) {
|
|
5010
4796
|
const nota = {
|
|
@@ -5022,19 +4808,19 @@ function createMockFinancialProvider(options) {
|
|
|
5022
4808
|
createdAt: now(),
|
|
5023
4809
|
updatedAt: now()
|
|
5024
4810
|
};
|
|
5025
|
-
|
|
4811
|
+
store6.notas.unshift(nota);
|
|
5026
4812
|
return nota;
|
|
5027
4813
|
},
|
|
5028
4814
|
async updateNota(id, patch) {
|
|
5029
|
-
const nota =
|
|
4815
|
+
const nota = store6.notas.find((n) => n.id === id);
|
|
5030
4816
|
if (!nota) throw new Error("Nota fiscal not found");
|
|
5031
4817
|
Object.assign(nota, patch, { updatedAt: now() });
|
|
5032
4818
|
return nota;
|
|
5033
4819
|
},
|
|
5034
4820
|
async issueNota(id) {
|
|
5035
|
-
const nota =
|
|
4821
|
+
const nota = store6.notas.find((n) => n.id === id);
|
|
5036
4822
|
if (!nota) throw new Error("Nota fiscal not found");
|
|
5037
|
-
const issuedCount =
|
|
4823
|
+
const issuedCount = store6.notas.filter((n) => n.status === "issued").length;
|
|
5038
4824
|
nota.status = "issued";
|
|
5039
4825
|
nota.number = String(issuedCount + 1).padStart(6, "0");
|
|
5040
4826
|
nota.series = nota.series ?? "1";
|
|
@@ -5044,7 +4830,7 @@ function createMockFinancialProvider(options) {
|
|
|
5044
4830
|
return nota;
|
|
5045
4831
|
},
|
|
5046
4832
|
async cancelNota(id, reason) {
|
|
5047
|
-
const nota =
|
|
4833
|
+
const nota = store6.notas.find((n) => n.id === id);
|
|
5048
4834
|
if (!nota) throw new Error("Nota fiscal not found");
|
|
5049
4835
|
nota.status = "cancelled";
|
|
5050
4836
|
nota.errorMessage = reason;
|
|
@@ -5299,6 +5085,11 @@ function createSupabaseFinancialProvider() {
|
|
|
5299
5085
|
party_id: input.contactId,
|
|
5300
5086
|
notes: input.observations,
|
|
5301
5087
|
currency: "BRL",
|
|
5088
|
+
// The form has an invoice-date picker and this used to ignore it, so
|
|
5089
|
+
// every invoice was dated the day it was typed. `invoiceDate` is read
|
|
5090
|
+
// back off created_at (see mapping below) and is what the date filters
|
|
5091
|
+
// and the payables history sort by, so it has to be written here.
|
|
5092
|
+
...input.invoiceDate ? { created_at: (/* @__PURE__ */ new Date(`${input.invoiceDate}T12:00:00Z`)).toISOString() } : {},
|
|
5302
5093
|
metadata: { itemsSummary, contactName: input.contactName, installmentCount: input.installments.length || 1 }
|
|
5303
5094
|
}).select().single();
|
|
5304
5095
|
if (order && input.items.length > 0) {
|
|
@@ -5331,14 +5122,17 @@ function createSupabaseFinancialProvider() {
|
|
|
5331
5122
|
paid_amount: 0,
|
|
5332
5123
|
status: "pending",
|
|
5333
5124
|
due_date: inst.dueDate,
|
|
5334
|
-
installment_number: inst.installmentNumber ?? i + 1
|
|
5125
|
+
installment_number: inst.installmentNumber ?? i + 1,
|
|
5126
|
+
// O metadata da parcela vinha sendo descartado aqui: o comprovante
|
|
5127
|
+
// anexado no cadastro sobrevivia no mock e sumia no banco.
|
|
5128
|
+
metadata: inst.metadata ?? null
|
|
5335
5129
|
}))
|
|
5336
5130
|
);
|
|
5337
5131
|
}
|
|
5338
5132
|
return {
|
|
5339
5133
|
id: order.id,
|
|
5340
5134
|
direction: input.direction,
|
|
5341
|
-
invoiceDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
5135
|
+
invoiceDate: input.invoiceDate ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
5342
5136
|
fiscalNumber: referenceNumber,
|
|
5343
5137
|
totalAmount,
|
|
5344
5138
|
paidAmount: 0,
|
|
@@ -5409,11 +5203,18 @@ function createSupabaseFinancialProvider() {
|
|
|
5409
5203
|
cash_session_id: input.cashSessionId ?? null,
|
|
5410
5204
|
card_brand: input.cardBrand ?? null,
|
|
5411
5205
|
card_installments: input.cardInstallments ?? null,
|
|
5412
|
-
notes: input.notes ?? null
|
|
5206
|
+
notes: input.notes ?? null,
|
|
5207
|
+
metadata: input.metadata ?? null,
|
|
5208
|
+
// Which bill this settles. Without it the pairing has to be re-derived
|
|
5209
|
+
// from (invoice_id, installment_number), which is ambiguous the moment
|
|
5210
|
+
// one installment takes two payments. See migration 014.
|
|
5211
|
+
settles_movement_id: mov.id
|
|
5413
5212
|
};
|
|
5414
5213
|
let { data, error } = await pub.from(T.movements).insert(paymentRow).select().single();
|
|
5415
|
-
if (error && /fee_amount/i.test(error.message ?? "")) {
|
|
5416
|
-
const {
|
|
5214
|
+
if (error && /fee_amount|settles_movement_id/i.test(error.message ?? "")) {
|
|
5215
|
+
const rest = { ...paymentRow };
|
|
5216
|
+
if (/fee_amount/i.test(error.message ?? "")) delete rest.fee_amount;
|
|
5217
|
+
if (/settles_movement_id/i.test(error.message ?? "")) delete rest.settles_movement_id;
|
|
5417
5218
|
({ data, error } = await pub.from(T.movements).insert(rest).select().single());
|
|
5418
5219
|
}
|
|
5419
5220
|
if (error) throw error;
|
|
@@ -6097,82 +5898,6 @@ function createFinancialStore(provider) {
|
|
|
6097
5898
|
throw err;
|
|
6098
5899
|
}
|
|
6099
5900
|
},
|
|
6100
|
-
async createQuickTransaction(input) {
|
|
6101
|
-
try {
|
|
6102
|
-
if (input.type === "transfer") {
|
|
6103
|
-
if (!input.bankAccountId || !input.toAccountId) {
|
|
6104
|
-
throw new Error("Select source and destination accounts");
|
|
6105
|
-
}
|
|
6106
|
-
await provider.createTransfer({
|
|
6107
|
-
fromAccountId: input.bankAccountId,
|
|
6108
|
-
toAccountId: input.toAccountId,
|
|
6109
|
-
amount: input.amount,
|
|
6110
|
-
date: input.date,
|
|
6111
|
-
notes: input.description?.trim() || void 0
|
|
6112
|
-
});
|
|
6113
|
-
} else {
|
|
6114
|
-
const direction = input.type === "income" ? "credit" : "debit";
|
|
6115
|
-
const desc = input.description?.trim() || (input.type === "income" ? "Receita" : "Despesa");
|
|
6116
|
-
const invoiceMeta = {};
|
|
6117
|
-
if (input.recurring) invoiceMeta.recurring = true;
|
|
6118
|
-
if (input.receiptUrl) invoiceMeta.receiptUrl = input.receiptUrl;
|
|
6119
|
-
const invoice = await provider.createInvoice({
|
|
6120
|
-
direction,
|
|
6121
|
-
invoiceDate: input.date,
|
|
6122
|
-
contactName: desc,
|
|
6123
|
-
metadata: Object.keys(invoiceMeta).length > 0 ? invoiceMeta : void 0,
|
|
6124
|
-
items: [{
|
|
6125
|
-
itemKind: "other",
|
|
6126
|
-
description: desc,
|
|
6127
|
-
quantity: 1,
|
|
6128
|
-
unitPrice: input.amount,
|
|
6129
|
-
accountId: input.categoryId
|
|
6130
|
-
}],
|
|
6131
|
-
installments: [{
|
|
6132
|
-
direction,
|
|
6133
|
-
movementKind: "bill",
|
|
6134
|
-
amount: input.amount,
|
|
6135
|
-
dueDate: input.date,
|
|
6136
|
-
installmentNumber: 1,
|
|
6137
|
-
bankAccountId: input.bankAccountId,
|
|
6138
|
-
notes: desc
|
|
6139
|
-
}]
|
|
6140
|
-
});
|
|
6141
|
-
if (input.paid) {
|
|
6142
|
-
const movements = await provider.getMovements({ direction, status: "pending" });
|
|
6143
|
-
const bill = movements.data.find((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
|
|
6144
|
-
if (bill) {
|
|
6145
|
-
await provider.payMovement({
|
|
6146
|
-
movementId: bill.id,
|
|
6147
|
-
amount: input.amount,
|
|
6148
|
-
paymentDate: input.date,
|
|
6149
|
-
bankAccountId: input.bankAccountId,
|
|
6150
|
-
notes: desc
|
|
6151
|
-
});
|
|
6152
|
-
}
|
|
6153
|
-
}
|
|
6154
|
-
}
|
|
6155
|
-
const invoiceQuery = get().invoiceQuery;
|
|
6156
|
-
const statementQuery = get().statementQuery;
|
|
6157
|
-
const [summary, bankAccounts, invoicesRes, statement] = await Promise.all([
|
|
6158
|
-
provider.getSummary(),
|
|
6159
|
-
provider.getBankAccounts(),
|
|
6160
|
-
provider.getInvoices(invoiceQuery),
|
|
6161
|
-
statementQuery ? provider.getStatement(statementQuery) : Promise.resolve(get().statement)
|
|
6162
|
-
]);
|
|
6163
|
-
set({
|
|
6164
|
-
summary,
|
|
6165
|
-
bankAccounts,
|
|
6166
|
-
invoices: invoicesRes.data,
|
|
6167
|
-
invoicesTotal: invoicesRes.total,
|
|
6168
|
-
statement
|
|
6169
|
-
});
|
|
6170
|
-
toast.success("Transaction saved");
|
|
6171
|
-
} catch (err) {
|
|
6172
|
-
toast.error("Failed to save transaction", { description: err?.message });
|
|
6173
|
-
throw err;
|
|
6174
|
-
}
|
|
6175
|
-
},
|
|
6176
5901
|
async payMovement(input) {
|
|
6177
5902
|
try {
|
|
6178
5903
|
await provider.payMovement(input);
|
|
@@ -6282,7 +6007,17 @@ var chartOfAccountsEntity = {
|
|
|
6282
6007
|
fields: [
|
|
6283
6008
|
{ key: "code", label: "Code", type: "text", required: true, showInTable: true },
|
|
6284
6009
|
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
6285
|
-
{ key: "nodeType", label: "
|
|
6010
|
+
{ key: "nodeType", label: "Structure", type: "select", options: ["group", "leaf"], showInTable: true, required: true, defaultValue: "leaf" },
|
|
6011
|
+
// What the account IS, as opposed to where it sits in the tree (nodeType).
|
|
6012
|
+
// Migration 013 leaves it unconstrained; the picker offers the five the
|
|
6013
|
+
// module reasons about.
|
|
6014
|
+
{ key: "accountType", label: "Nature", type: "select", options: [
|
|
6015
|
+
{ label: "Revenue", value: "revenue" },
|
|
6016
|
+
{ label: "Expense", value: "expense" },
|
|
6017
|
+
{ label: "Asset", value: "asset" },
|
|
6018
|
+
{ label: "Liability", value: "liability" },
|
|
6019
|
+
{ label: "Equity", value: "equity" }
|
|
6020
|
+
], showInTable: true },
|
|
6286
6021
|
{ key: "parentId", label: "Parent", type: "text", showInTable: false },
|
|
6287
6022
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
6288
6023
|
],
|
|
@@ -6404,20 +6139,20 @@ var financialRegistries = [
|
|
|
6404
6139
|
description: "Hierarchical chart of accounts for categorizing transactions",
|
|
6405
6140
|
display: "tree",
|
|
6406
6141
|
seedData: [
|
|
6407
|
-
{ id: "coa-1", code: "1", name: "Revenue", nodeType: "group", isActive: true },
|
|
6408
|
-
{ id: "coa-1.1", code: "1.1", name: "Sales Revenue", nodeType: "group", parentId: "coa-1", isActive: true },
|
|
6409
|
-
{ id: "coa-1.1.01", code: "1.1.01", name: "Services", nodeType: "leaf", parentId: "coa-1.1", isActive: true },
|
|
6410
|
-
{ id: "coa-1.1.02", code: "1.1.02", name: "Products", nodeType: "leaf", parentId: "coa-1.1", isActive: true },
|
|
6411
|
-
{ id: "coa-2", code: "2", name: "Other Revenue", nodeType: "group", isActive: true },
|
|
6412
|
-
{ id: "coa-2.1", code: "2.1", name: "Financial Income", nodeType: "leaf", parentId: "coa-2", isActive: true },
|
|
6413
|
-
{ id: "coa-3", code: "3", name: "Operational Costs", nodeType: "group", isActive: true },
|
|
6414
|
-
{ id: "coa-3.1", code: "3.1", name: "Materials & Supplies", nodeType: "group", parentId: "coa-3", isActive: true },
|
|
6415
|
-
{ id: "coa-3.1.01", code: "3.1.01", name: "Raw Materials", nodeType: "leaf", parentId: "coa-3.1", isActive: true },
|
|
6416
|
-
{ id: "coa-3.1.02", code: "3.1.02", name: "Consumables", nodeType: "leaf", parentId: "coa-3.1", isActive: true },
|
|
6417
|
-
{ id: "coa-4", code: "4", name: "Expenses", nodeType: "group", isActive: true },
|
|
6418
|
-
{ id: "coa-4.1", code: "4.1", name: "Rent", nodeType: "leaf", parentId: "coa-4", isActive: true },
|
|
6419
|
-
{ id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", parentId: "coa-4", isActive: true },
|
|
6420
|
-
{ id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", parentId: "coa-4", isActive: true }
|
|
6142
|
+
{ id: "coa-1", code: "1", name: "Revenue", nodeType: "group", accountType: "revenue", isActive: true },
|
|
6143
|
+
{ id: "coa-1.1", code: "1.1", name: "Sales Revenue", nodeType: "group", accountType: "revenue", parentId: "coa-1", isActive: true },
|
|
6144
|
+
{ id: "coa-1.1.01", code: "1.1.01", name: "Services", nodeType: "leaf", accountType: "revenue", parentId: "coa-1.1", isActive: true },
|
|
6145
|
+
{ id: "coa-1.1.02", code: "1.1.02", name: "Products", nodeType: "leaf", accountType: "revenue", parentId: "coa-1.1", isActive: true },
|
|
6146
|
+
{ id: "coa-2", code: "2", name: "Other Revenue", nodeType: "group", accountType: "revenue", isActive: true },
|
|
6147
|
+
{ id: "coa-2.1", code: "2.1", name: "Financial Income", nodeType: "leaf", accountType: "revenue", parentId: "coa-2", isActive: true },
|
|
6148
|
+
{ id: "coa-3", code: "3", name: "Operational Costs", nodeType: "group", accountType: "expense", isActive: true },
|
|
6149
|
+
{ id: "coa-3.1", code: "3.1", name: "Materials & Supplies", nodeType: "group", accountType: "expense", parentId: "coa-3", isActive: true },
|
|
6150
|
+
{ id: "coa-3.1.01", code: "3.1.01", name: "Raw Materials", nodeType: "leaf", accountType: "expense", parentId: "coa-3.1", isActive: true },
|
|
6151
|
+
{ id: "coa-3.1.02", code: "3.1.02", name: "Consumables", nodeType: "leaf", accountType: "expense", parentId: "coa-3.1", isActive: true },
|
|
6152
|
+
{ id: "coa-4", code: "4", name: "Expenses", nodeType: "group", accountType: "expense", isActive: true },
|
|
6153
|
+
{ id: "coa-4.1", code: "4.1", name: "Rent", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
|
|
6154
|
+
{ id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
|
|
6155
|
+
{ id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
|
|
6421
6156
|
]
|
|
6422
6157
|
},
|
|
6423
6158
|
{
|
|
@@ -6505,6 +6240,7 @@ var en = {
|
|
|
6505
6240
|
"financial.cards.currentBill": "Current bill",
|
|
6506
6241
|
"financial.cards.dueOn": "Due on the {{day}}",
|
|
6507
6242
|
"financial.cards.installment": "Installment {{current}}/{{total}}",
|
|
6243
|
+
"financial.cards.loading": "Loading...",
|
|
6508
6244
|
"financial.cards.noCards": "No cards yet",
|
|
6509
6245
|
"financial.cards.noCardsHint": "Add a credit card in settings to track your bills here",
|
|
6510
6246
|
"financial.cards.noTransactions": "No card transactions",
|
|
@@ -6546,10 +6282,17 @@ var en = {
|
|
|
6546
6282
|
"financial.cashRegisters.register": "Register",
|
|
6547
6283
|
"financial.cashRegisters.sessionHistory": "Session History",
|
|
6548
6284
|
"financial.cashRegisters.subtitle": "Open, count and close cash sessions",
|
|
6549
|
-
"financial.commissions.
|
|
6285
|
+
"financial.commissions.emptyTitle": "No commissions calculated",
|
|
6286
|
+
"financial.commissions.emptyDesc": "This module tracks commission rules, but nothing here calculates a commission from your sales yet.",
|
|
6287
|
+
"financial.commissions.emptyRulesHint": "Commission rules are managed under Settings \u203A Financial \u203A Properties.",
|
|
6550
6288
|
"financial.commissions.subtitle": "Team commissions",
|
|
6551
6289
|
"financial.invoice.accountsPayable": "Accounts Payable",
|
|
6552
6290
|
"financial.invoice.accountsReceivable": "Accounts Receivable",
|
|
6291
|
+
"financial.invoice.bulkCancel": "Cancel",
|
|
6292
|
+
"financial.invoice.bulkCancelDone": "{{count}} invoice(s) cancelled",
|
|
6293
|
+
"financial.invoice.bulkCancelFailed": "{{count}} invoice(s) could not be cancelled",
|
|
6294
|
+
"financial.invoice.bulkCancelNone": "None of the selected invoices can be cancelled.",
|
|
6295
|
+
"financial.invoice.bulkCancelTitle": "Cancel {{count}} invoice(s)?",
|
|
6553
6296
|
"financial.invoice.cancelConfirm": "This action cannot be undone.",
|
|
6554
6297
|
"financial.invoice.cancelDesc": "The invoice will be marked as cancelled. All pending installments will also be cancelled. This invoice will no longer count towards totals.",
|
|
6555
6298
|
"financial.invoice.cancelInvoice": "Cancel Invoice",
|
|
@@ -6567,7 +6310,20 @@ var en = {
|
|
|
6567
6310
|
"financial.invoice.items": "Items",
|
|
6568
6311
|
"financial.invoice.keepInvoice": "Keep Invoice",
|
|
6569
6312
|
"financial.invoice.new": "New",
|
|
6313
|
+
"financial.invoice.newManual": "Fill it in by hand",
|
|
6314
|
+
"financial.invoiceForm.createContact": "Create",
|
|
6315
|
+
"financial.invoiceForm.alreadyPaid": "Already paid",
|
|
6316
|
+
"financial.invoiceForm.paidWith": "Payment method",
|
|
6317
|
+
"financial.invoiceForm.paidFrom": "Paid from",
|
|
6318
|
+
"financial.invoiceForm.selectMethod": "Select a method",
|
|
6319
|
+
"financial.invoiceForm.selectAccount": "Select an account",
|
|
6320
|
+
"financial.invoiceForm.paidNeedsMethod": "Tell us the payment method and the account",
|
|
6321
|
+
"financial.invoiceForm.willBePaid": "This installment will be settled",
|
|
6322
|
+
"financial.invoiceForm.clear": "Clear",
|
|
6323
|
+
"financial.invoice.newManualHint": "Open the full invoice form",
|
|
6570
6324
|
"financial.invoice.noDetails": "No additional details recorded",
|
|
6325
|
+
"financial.invoice.unlinkedPayments": "Payments not linked to an installment",
|
|
6326
|
+
"financial.invoice.unlinkedPaymentsDesc": "Money recorded against this invoice that does not name one of the installments above.",
|
|
6571
6327
|
"financial.invoice.noInstallments": "No installments",
|
|
6572
6328
|
"financial.invoice.noInvoices": "No invoices yet",
|
|
6573
6329
|
"financial.invoice.summaryTotal": "Total",
|
|
@@ -6872,6 +6628,8 @@ var en = {
|
|
|
6872
6628
|
"financial.invoiceForm.cancelling": "Cancelling...",
|
|
6873
6629
|
"financial.invoiceForm.removeAdjustments": "Remove adjustments",
|
|
6874
6630
|
"financial.invoiceForm.discount": "Discount",
|
|
6631
|
+
"financial.invoiceForm.account": "Chart account",
|
|
6632
|
+
"financial.invoiceForm.accountNone": "Not assigned",
|
|
6875
6633
|
"financial.invoiceForm.surcharge": "Surcharge",
|
|
6876
6634
|
"financial.invoiceForm.total": "Total:",
|
|
6877
6635
|
"financial.invoiceForm.itemCount": "{{count}} item{{plural}}",
|
|
@@ -6945,6 +6703,7 @@ var en = {
|
|
|
6945
6703
|
"financial.statements.totalFees": "Fees",
|
|
6946
6704
|
"financial.statements.net": "Net",
|
|
6947
6705
|
"financial.statements.transfer": "Transfer",
|
|
6706
|
+
"financial.statements.untitledEntry": "Transaction",
|
|
6948
6707
|
"financial.statements.reconciled": "Reconciled with the bank statement",
|
|
6949
6708
|
"financial.statements.imported": "Imported from the bank \xB7 not reconciled",
|
|
6950
6709
|
"financial.statements.reconciledWith": "Reconciled with {{ref}}",
|
|
@@ -7006,6 +6765,7 @@ var ptBR = {
|
|
|
7006
6765
|
"financial.cards.currentBill": "Fatura atual",
|
|
7007
6766
|
"financial.cards.dueOn": "Vence dia {{day}}",
|
|
7008
6767
|
"financial.cards.installment": "Parcela {{current}}/{{total}}",
|
|
6768
|
+
"financial.cards.loading": "Carregando...",
|
|
7009
6769
|
"financial.cards.noCards": "Nenhum cart\xE3o cadastrado",
|
|
7010
6770
|
"financial.cards.noCardsHint": "Adicione um cart\xE3o de cr\xE9dito nas configura\xE7\xF5es para acompanhar suas faturas aqui",
|
|
7011
6771
|
"financial.cards.noTransactions": "Nenhuma transa\xE7\xE3o com cart\xE3o",
|
|
@@ -7047,10 +6807,17 @@ var ptBR = {
|
|
|
7047
6807
|
"financial.cashRegisters.register": "Caixa",
|
|
7048
6808
|
"financial.cashRegisters.sessionHistory": "Hist\xF3rico de Sess\xF5es",
|
|
7049
6809
|
"financial.cashRegisters.subtitle": "Abrir, contar e fechar sess\xF5es de caixa",
|
|
7050
|
-
"financial.commissions.
|
|
6810
|
+
"financial.commissions.emptyTitle": "Nenhuma comiss\xE3o calculada",
|
|
6811
|
+
"financial.commissions.emptyDesc": "Este m\xF3dulo guarda as regras de comiss\xE3o, mas ainda n\xE3o h\xE1 nada que calcule comiss\xE3o a partir das suas vendas.",
|
|
6812
|
+
"financial.commissions.emptyRulesHint": "As regras de comiss\xE3o ficam em Configura\xE7\xF5es \u203A Financeiro \u203A Propriedades.",
|
|
7051
6813
|
"financial.commissions.subtitle": "Comiss\xF5es da equipe",
|
|
7052
6814
|
"financial.invoice.accountsPayable": "Contas a Pagar",
|
|
7053
6815
|
"financial.invoice.accountsReceivable": "Contas a Receber",
|
|
6816
|
+
"financial.invoice.bulkCancel": "Cancelar",
|
|
6817
|
+
"financial.invoice.bulkCancelDone": "{{count}} fatura(s) cancelada(s)",
|
|
6818
|
+
"financial.invoice.bulkCancelFailed": "{{count}} fatura(s) n\xE3o puderam ser canceladas",
|
|
6819
|
+
"financial.invoice.bulkCancelNone": "Nenhuma das faturas selecionadas pode ser cancelada.",
|
|
6820
|
+
"financial.invoice.bulkCancelTitle": "Cancelar {{count}} fatura(s)?",
|
|
7054
6821
|
"financial.invoice.cancelConfirm": "Esta a\xE7\xE3o n\xE3o pode ser desfeita.",
|
|
7055
6822
|
"financial.invoice.cancelDesc": "A fatura ser\xE1 marcada como cancelada. Todas as parcelas pendentes tamb\xE9m ser\xE3o canceladas. Esta fatura n\xE3o contar\xE1 mais nos totais.",
|
|
7056
6823
|
"financial.invoice.cancelInvoice": "Cancelar Fatura",
|
|
@@ -7068,7 +6835,20 @@ var ptBR = {
|
|
|
7068
6835
|
"financial.invoice.items": "Itens",
|
|
7069
6836
|
"financial.invoice.keepInvoice": "Manter Fatura",
|
|
7070
6837
|
"financial.invoice.new": "Nova",
|
|
6838
|
+
"financial.invoice.newManual": "Cadastrar manualmente",
|
|
6839
|
+
"financial.invoiceForm.createContact": "Cadastrar",
|
|
6840
|
+
"financial.invoiceForm.alreadyPaid": "J\xE1 foi pago",
|
|
6841
|
+
"financial.invoiceForm.paidWith": "Forma de pagamento",
|
|
6842
|
+
"financial.invoiceForm.paidFrom": "Saiu de",
|
|
6843
|
+
"financial.invoiceForm.selectMethod": "Selecione a forma",
|
|
6844
|
+
"financial.invoiceForm.selectAccount": "Selecione a conta",
|
|
6845
|
+
"financial.invoiceForm.paidNeedsMethod": "Diga a forma de pagamento e a conta",
|
|
6846
|
+
"financial.invoiceForm.willBePaid": "Esta parcela entra como paga",
|
|
6847
|
+
"financial.invoiceForm.clear": "Limpar",
|
|
6848
|
+
"financial.invoice.newManualHint": "Preencher os campos da conta",
|
|
7071
6849
|
"financial.invoice.noDetails": "Nenhum detalhe adicional registrado",
|
|
6850
|
+
"financial.invoice.unlinkedPayments": "Pagamentos sem parcela vinculada",
|
|
6851
|
+
"financial.invoice.unlinkedPaymentsDesc": "Dinheiro registrado nesta fatura que n\xE3o aponta para nenhuma das parcelas acima.",
|
|
7072
6852
|
"financial.invoice.noInstallments": "Sem parcelas",
|
|
7073
6853
|
"financial.invoice.noInvoices": "Nenhuma fatura ainda",
|
|
7074
6854
|
"financial.invoice.summaryTotal": "Total",
|
|
@@ -7376,6 +7156,8 @@ var ptBR = {
|
|
|
7376
7156
|
"financial.invoiceForm.cancelling": "Cancelando...",
|
|
7377
7157
|
"financial.invoiceForm.removeAdjustments": "Remover ajustes",
|
|
7378
7158
|
"financial.invoiceForm.discount": "Desconto",
|
|
7159
|
+
"financial.invoiceForm.account": "Conta cont\xE1bil",
|
|
7160
|
+
"financial.invoiceForm.accountNone": "N\xE3o atribu\xEDda",
|
|
7379
7161
|
"financial.invoiceForm.surcharge": "Acr\xE9scimo",
|
|
7380
7162
|
"financial.invoiceForm.total": "Total:",
|
|
7381
7163
|
"financial.invoiceForm.itemCount": "{{count}} item{{plural}}",
|
|
@@ -7449,6 +7231,7 @@ var ptBR = {
|
|
|
7449
7231
|
"financial.statements.totalFees": "Taxas",
|
|
7450
7232
|
"financial.statements.net": "L\xEDquido",
|
|
7451
7233
|
"financial.statements.transfer": "Transfer\xEAncia",
|
|
7234
|
+
"financial.statements.untitledEntry": "Lan\xE7amento",
|
|
7452
7235
|
"financial.statements.reconciled": "Conciliado com o extrato banc\xE1rio",
|
|
7453
7236
|
"financial.statements.imported": "Importado do banco \xB7 n\xE3o conciliado",
|
|
7454
7237
|
"financial.statements.reconciledWith": "Conciliado com {{ref}}",
|
|
@@ -7657,6 +7440,306 @@ var stripeConnectorDef = {
|
|
|
7657
7440
|
await store.disconnect();
|
|
7658
7441
|
}
|
|
7659
7442
|
};
|
|
7443
|
+
|
|
7444
|
+
// src/integrations/acquirers/shared.ts
|
|
7445
|
+
function receivablePermissions(provider) {
|
|
7446
|
+
return [
|
|
7447
|
+
{
|
|
7448
|
+
kind: "data",
|
|
7449
|
+
resource: "payment",
|
|
7450
|
+
actions: ["write"],
|
|
7451
|
+
reason: `Lan\xE7ar no Financeiro cada venda que o ${provider} recebeu: valor, forma de pagamento, parcelas, taxa cobrada e a data em que o dinheiro cai na conta.`,
|
|
7452
|
+
sensitive: true
|
|
7453
|
+
},
|
|
7454
|
+
{
|
|
7455
|
+
kind: "data",
|
|
7456
|
+
resource: "invoice",
|
|
7457
|
+
actions: ["read", "write"],
|
|
7458
|
+
reason: `Encontrar o receb\xEDvel que corresponde \xE0quela venda e dar a baixa quando o ${provider} confirmar o repasse. O valor do receb\xEDvel n\xE3o \xE9 alterado.`,
|
|
7459
|
+
sensitive: true
|
|
7460
|
+
}
|
|
7461
|
+
];
|
|
7462
|
+
}
|
|
7463
|
+
var SETTLE_ON_SETTING = {
|
|
7464
|
+
key: "settleOn",
|
|
7465
|
+
label: "Quando dar a baixa",
|
|
7466
|
+
description: "A venda no cart\xE3o entra hoje e o dinheiro cai depois. Escolha o que conta como recebido.",
|
|
7467
|
+
type: "select",
|
|
7468
|
+
options: [
|
|
7469
|
+
{ value: "payout", label: "Quando o dinheiro cai na conta (repasse)" },
|
|
7470
|
+
{ value: "sale", label: "Na hora da venda" }
|
|
7471
|
+
],
|
|
7472
|
+
defaultValue: "payout"
|
|
7473
|
+
};
|
|
7474
|
+
function methodsSetting(options) {
|
|
7475
|
+
return {
|
|
7476
|
+
key: "methods",
|
|
7477
|
+
label: "Quais vendas trazer",
|
|
7478
|
+
description: "S\xF3 o que estiver marcado aqui vira lan\xE7amento no Financeiro.",
|
|
7479
|
+
type: "multi-select",
|
|
7480
|
+
options,
|
|
7481
|
+
// Nothing marked is a feed with nothing to bring: the connection is real and
|
|
7482
|
+
// reads as "conectado, falta configurar".
|
|
7483
|
+
requiredToOperate: true
|
|
7484
|
+
};
|
|
7485
|
+
}
|
|
7486
|
+
function methodsDetail2(labels, value) {
|
|
7487
|
+
const list = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
7488
|
+
const named = list.map((v) => labels[String(v).trim()] ?? String(v).trim()).filter(Boolean);
|
|
7489
|
+
return named.length > 0 ? named.join(", ") : void 0;
|
|
7490
|
+
}
|
|
7491
|
+
|
|
7492
|
+
// src/integrations/mercadopago/connectorDef.tsx
|
|
7493
|
+
var MERCADOPAGO_CONNECTOR_ID = "mercadopago";
|
|
7494
|
+
var METHOD_LABELS2 = {
|
|
7495
|
+
credit_card: "Cr\xE9dito",
|
|
7496
|
+
debit_card: "D\xE9bito",
|
|
7497
|
+
pix: "Pix",
|
|
7498
|
+
ticket: "Boleto",
|
|
7499
|
+
account_money: "Saldo em conta"
|
|
7500
|
+
};
|
|
7501
|
+
var MERCADOPAGO_SETTINGS = [
|
|
7502
|
+
methodsSetting([
|
|
7503
|
+
{ value: "credit_card", label: METHOD_LABELS2.credit_card },
|
|
7504
|
+
{ value: "debit_card", label: METHOD_LABELS2.debit_card },
|
|
7505
|
+
{ value: "pix", label: METHOD_LABELS2.pix },
|
|
7506
|
+
{ value: "ticket", label: METHOD_LABELS2.ticket },
|
|
7507
|
+
{ value: "account_money", label: METHOD_LABELS2.account_money }
|
|
7508
|
+
]),
|
|
7509
|
+
SETTLE_ON_SETTING
|
|
7510
|
+
];
|
|
7511
|
+
var store2 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID, {
|
|
7512
|
+
settings: MERCADOPAGO_SETTINGS,
|
|
7513
|
+
settingsKeys: ["collectorId"]
|
|
7514
|
+
});
|
|
7515
|
+
var mercadoPagoConnectorDef = {
|
|
7516
|
+
id: MERCADOPAGO_CONNECTOR_ID,
|
|
7517
|
+
hostPluginId: "financial",
|
|
7518
|
+
name: "Mercado Pago",
|
|
7519
|
+
description: "Traga para o Financeiro tudo que entrou no Mercado Pago \u2014 Pix, cart\xE3o e maquininha \u2014 com a taxa e a data do repasse.",
|
|
7520
|
+
icon: "CreditCard",
|
|
7521
|
+
category: "payments",
|
|
7522
|
+
authKind: "oauth",
|
|
7523
|
+
// We reach Mercado Pago for what it received; nothing here charges anyone.
|
|
7524
|
+
direction: "inbound",
|
|
7525
|
+
planes: ["sync"],
|
|
7526
|
+
docsUrl: "https://www.mercadopago.com.br/developers/pt/docs/checkout-api/additional-content/security/oauth/creation",
|
|
7527
|
+
permissions: receivablePermissions("Mercado Pago"),
|
|
7528
|
+
fields: [],
|
|
7529
|
+
settings: MERCADOPAGO_SETTINGS,
|
|
7530
|
+
howItSyncs: [
|
|
7531
|
+
"De tempos em tempos, o que o Mercado Pago recebeu vira lan\xE7amento no Financeiro.",
|
|
7532
|
+
"Uma venda j\xE1 lan\xE7ada n\xE3o entra duas vezes: ela guarda o n\xFAmero que tem no Mercado Pago.",
|
|
7533
|
+
"A taxa vem junto, ent\xE3o o valor l\xEDquido \xE9 o que aparece no caixa."
|
|
7534
|
+
],
|
|
7535
|
+
...connectionStatus(
|
|
7536
|
+
MERCADOPAGO_CONNECTOR_ID,
|
|
7537
|
+
(connection) => ({
|
|
7538
|
+
connected: !!connection?.active,
|
|
7539
|
+
detail: methodsDetail2(METHOD_LABELS2, connection?.settings?.methods),
|
|
7540
|
+
plane: "sync",
|
|
7541
|
+
...settingsStatus(MERCADOPAGO_SETTINGS, connection)
|
|
7542
|
+
}),
|
|
7543
|
+
{ store: () => store2 }
|
|
7544
|
+
),
|
|
7545
|
+
...connectionSettings(store2),
|
|
7546
|
+
...connectionRuns(store2),
|
|
7547
|
+
startOAuth: fayzConnectorOAuth(MERCADOPAGO_CONNECTOR_ID),
|
|
7548
|
+
saveConnection: async () => {
|
|
7549
|
+
await store2.save({ active: true });
|
|
7550
|
+
},
|
|
7551
|
+
disconnect: () => store2.disconnect(),
|
|
7552
|
+
disconnectNote: "Desconectar para de trazer vendas novas. O que j\xE1 foi lan\xE7ado no Financeiro continua l\xE1."
|
|
7553
|
+
};
|
|
7554
|
+
var PAGBANK_CONNECTOR_ID = "pagbank";
|
|
7555
|
+
var METHOD_LABELS3 = {
|
|
7556
|
+
CREDIT_CARD: "Cr\xE9dito",
|
|
7557
|
+
DEBIT_CARD: "D\xE9bito",
|
|
7558
|
+
PIX: "Pix",
|
|
7559
|
+
BOLETO: "Boleto"
|
|
7560
|
+
};
|
|
7561
|
+
var PAGBANK_SETTINGS = [
|
|
7562
|
+
methodsSetting([
|
|
7563
|
+
{ value: "CREDIT_CARD", label: METHOD_LABELS3.CREDIT_CARD },
|
|
7564
|
+
{ value: "DEBIT_CARD", label: METHOD_LABELS3.DEBIT_CARD },
|
|
7565
|
+
{ value: "PIX", label: METHOD_LABELS3.PIX },
|
|
7566
|
+
{ value: "BOLETO", label: METHOD_LABELS3.BOLETO }
|
|
7567
|
+
]),
|
|
7568
|
+
SETTLE_ON_SETTING
|
|
7569
|
+
];
|
|
7570
|
+
var store3 = createConnectionStore(PAGBANK_CONNECTOR_ID, {
|
|
7571
|
+
settings: PAGBANK_SETTINGS,
|
|
7572
|
+
settingsKeys: ["accountId"]
|
|
7573
|
+
});
|
|
7574
|
+
var pagBankConnectorDef = {
|
|
7575
|
+
id: PAGBANK_CONNECTOR_ID,
|
|
7576
|
+
hostPluginId: "financial",
|
|
7577
|
+
name: "PagBank",
|
|
7578
|
+
description: "Traga para o Financeiro tudo que entrou no PagBank \u2014 Pix, cart\xE3o e maquininha \u2014 com a taxa e a data do repasse.",
|
|
7579
|
+
icon: "CreditCard",
|
|
7580
|
+
// The catalogue carries no `pagbank` row yet, so the mark is declared here
|
|
7581
|
+
// rather than borrowed. Without it the row renders as a grey plug.
|
|
7582
|
+
logoUrl: "https://assets.fayz.ai/connectors/pagbank_pagseguro.png",
|
|
7583
|
+
category: "payments",
|
|
7584
|
+
authKind: "oauth",
|
|
7585
|
+
direction: "inbound",
|
|
7586
|
+
planes: ["sync"],
|
|
7587
|
+
docsUrl: "https://developer.pagbank.com.br/docs/connect-authorization",
|
|
7588
|
+
permissions: receivablePermissions("PagBank"),
|
|
7589
|
+
fields: [],
|
|
7590
|
+
settings: PAGBANK_SETTINGS,
|
|
7591
|
+
howItSyncs: [
|
|
7592
|
+
"De tempos em tempos, o que o PagBank recebeu vira lan\xE7amento no Financeiro.",
|
|
7593
|
+
"Uma venda j\xE1 lan\xE7ada n\xE3o entra duas vezes: ela guarda o n\xFAmero que tem no PagBank.",
|
|
7594
|
+
"A taxa vem junto, ent\xE3o o valor l\xEDquido \xE9 o que aparece no caixa."
|
|
7595
|
+
],
|
|
7596
|
+
...connectionStatus(
|
|
7597
|
+
PAGBANK_CONNECTOR_ID,
|
|
7598
|
+
(connection) => ({
|
|
7599
|
+
connected: !!connection?.active,
|
|
7600
|
+
detail: methodsDetail2(METHOD_LABELS3, connection?.settings?.methods),
|
|
7601
|
+
plane: "sync",
|
|
7602
|
+
...settingsStatus(PAGBANK_SETTINGS, connection)
|
|
7603
|
+
}),
|
|
7604
|
+
{ store: () => store3 }
|
|
7605
|
+
),
|
|
7606
|
+
...connectionSettings(store3),
|
|
7607
|
+
...connectionRuns(store3),
|
|
7608
|
+
startOAuth: fayzConnectorOAuth(PAGBANK_CONNECTOR_ID),
|
|
7609
|
+
saveConnection: async () => {
|
|
7610
|
+
await store3.save({ active: true });
|
|
7611
|
+
},
|
|
7612
|
+
disconnect: () => store3.disconnect(),
|
|
7613
|
+
disconnectNote: "Desconectar para de trazer vendas novas. O que j\xE1 foi lan\xE7ado no Financeiro continua l\xE1."
|
|
7614
|
+
};
|
|
7615
|
+
var CIELO_CONNECTOR_ID = "cielo";
|
|
7616
|
+
var CIELO_FIELDS = [
|
|
7617
|
+
{ key: "merchantId", label: "Merchant ID", type: "text", secret: true, placeholder: "Cielo \u2192 Gest\xE3o API \u2192 Credencial API" },
|
|
7618
|
+
{ key: "merchantKey", label: "Merchant Key", type: "password", placeholder: "Mostrada uma \xFAnica vez, ao gerar" }
|
|
7619
|
+
];
|
|
7620
|
+
var METHOD_LABELS4 = {
|
|
7621
|
+
CreditCard: "Cr\xE9dito",
|
|
7622
|
+
DebitCard: "D\xE9bito",
|
|
7623
|
+
Pix: "Pix"
|
|
7624
|
+
};
|
|
7625
|
+
var CIELO_SETTINGS = [
|
|
7626
|
+
methodsSetting([
|
|
7627
|
+
{ value: "CreditCard", label: METHOD_LABELS4.CreditCard },
|
|
7628
|
+
{ value: "DebitCard", label: METHOD_LABELS4.DebitCard },
|
|
7629
|
+
{ value: "Pix", label: METHOD_LABELS4.Pix }
|
|
7630
|
+
]),
|
|
7631
|
+
SETTLE_ON_SETTING
|
|
7632
|
+
];
|
|
7633
|
+
var store4 = createConnectionStore(CIELO_CONNECTOR_ID, {
|
|
7634
|
+
fields: CIELO_FIELDS,
|
|
7635
|
+
settings: CIELO_SETTINGS
|
|
7636
|
+
});
|
|
7637
|
+
var cieloConnectorDef = {
|
|
7638
|
+
id: CIELO_CONNECTOR_ID,
|
|
7639
|
+
hostPluginId: "financial",
|
|
7640
|
+
name: "Cielo",
|
|
7641
|
+
description: "Traga para o Financeiro tudo que entrou na Cielo \u2014 cr\xE9dito, d\xE9bito e Pix \u2014 com a taxa e a data do repasse.",
|
|
7642
|
+
icon: "CreditCard",
|
|
7643
|
+
// The catalogue carries no `cielo` row yet, so the mark is declared here.
|
|
7644
|
+
logoUrl: "https://assets.fayz.ai/connectors/cielo.jpg",
|
|
7645
|
+
category: "payments",
|
|
7646
|
+
authKind: "api-key",
|
|
7647
|
+
direction: "inbound",
|
|
7648
|
+
planes: ["sync"],
|
|
7649
|
+
docsUrl: "https://developercielo.github.io/manual/cielo-ecommerce",
|
|
7650
|
+
providerInstall: { url: "https://www.cielo.com.br/" },
|
|
7651
|
+
permissions: receivablePermissions("Cielo"),
|
|
7652
|
+
fields: CIELO_FIELDS,
|
|
7653
|
+
settings: CIELO_SETTINGS,
|
|
7654
|
+
howItSyncs: [
|
|
7655
|
+
"De tempos em tempos, o que a Cielo recebeu vira lan\xE7amento no Financeiro.",
|
|
7656
|
+
"Uma venda j\xE1 lan\xE7ada n\xE3o entra duas vezes: ela guarda o n\xFAmero que tem na Cielo.",
|
|
7657
|
+
"A taxa vem junto, ent\xE3o o valor l\xEDquido \xE9 o que aparece no caixa."
|
|
7658
|
+
],
|
|
7659
|
+
...connectionStatus(
|
|
7660
|
+
CIELO_CONNECTOR_ID,
|
|
7661
|
+
(connection) => ({
|
|
7662
|
+
connected: !!connection?.active,
|
|
7663
|
+
detail: methodsDetail2(METHOD_LABELS4, connection?.settings?.methods),
|
|
7664
|
+
plane: "sync",
|
|
7665
|
+
...settingsStatus(CIELO_SETTINGS, connection)
|
|
7666
|
+
}),
|
|
7667
|
+
{ store: () => store4 }
|
|
7668
|
+
),
|
|
7669
|
+
...connectionSettings(store4),
|
|
7670
|
+
...connectionRuns(store4),
|
|
7671
|
+
// Reached only after the vault accepted the pair; neither half arrives here.
|
|
7672
|
+
saveConnection: async () => {
|
|
7673
|
+
await store4.save({ active: true });
|
|
7674
|
+
},
|
|
7675
|
+
disconnect: () => store4.disconnect(),
|
|
7676
|
+
disconnectNote: "Desconectar para de trazer vendas novas. O que j\xE1 foi lan\xE7ado no Financeiro continua l\xE1."
|
|
7677
|
+
};
|
|
7678
|
+
var STONE_CONNECTOR_ID = "stone";
|
|
7679
|
+
var STONE_FIELDS = [
|
|
7680
|
+
{ key: "stoneCode", label: "Stone Code", type: "text", placeholder: "N\xFAmero de afilia\xE7\xE3o da sua loja" },
|
|
7681
|
+
{ key: "secretKey", label: "Secret Key", type: "password", placeholder: "Stone \u2192 painel \u2192 chaves de API" }
|
|
7682
|
+
];
|
|
7683
|
+
var METHOD_LABELS5 = {
|
|
7684
|
+
credit: "Cr\xE9dito",
|
|
7685
|
+
debit: "D\xE9bito",
|
|
7686
|
+
pix: "Pix",
|
|
7687
|
+
voucher: "Vale-refei\xE7\xE3o"
|
|
7688
|
+
};
|
|
7689
|
+
var STONE_SETTINGS = [
|
|
7690
|
+
methodsSetting([
|
|
7691
|
+
{ value: "credit", label: METHOD_LABELS5.credit },
|
|
7692
|
+
{ value: "debit", label: METHOD_LABELS5.debit },
|
|
7693
|
+
{ value: "pix", label: METHOD_LABELS5.pix },
|
|
7694
|
+
{ value: "voucher", label: METHOD_LABELS5.voucher }
|
|
7695
|
+
]),
|
|
7696
|
+
SETTLE_ON_SETTING
|
|
7697
|
+
];
|
|
7698
|
+
var store5 = createConnectionStore(STONE_CONNECTOR_ID, {
|
|
7699
|
+
fields: STONE_FIELDS,
|
|
7700
|
+
settings: STONE_SETTINGS
|
|
7701
|
+
});
|
|
7702
|
+
var stoneConnectorDef = {
|
|
7703
|
+
id: STONE_CONNECTOR_ID,
|
|
7704
|
+
hostPluginId: "financial",
|
|
7705
|
+
name: "Stone",
|
|
7706
|
+
description: "Traga para o Financeiro tudo que entrou na Stone \u2014 cr\xE9dito, d\xE9bito, Pix e vale \u2014 com a taxa e a data do repasse.",
|
|
7707
|
+
icon: "CreditCard",
|
|
7708
|
+
category: "payments",
|
|
7709
|
+
authKind: "api-key",
|
|
7710
|
+
direction: "inbound",
|
|
7711
|
+
planes: ["sync"],
|
|
7712
|
+
docsUrl: "https://docs.stone.com.br",
|
|
7713
|
+
permissions: receivablePermissions("Stone"),
|
|
7714
|
+
fields: STONE_FIELDS,
|
|
7715
|
+
settings: STONE_SETTINGS,
|
|
7716
|
+
howItSyncs: [
|
|
7717
|
+
"De tempos em tempos, o que a Stone recebeu vira lan\xE7amento no Financeiro.",
|
|
7718
|
+
"Uma venda j\xE1 lan\xE7ada n\xE3o entra duas vezes: ela guarda o n\xFAmero que tem na Stone.",
|
|
7719
|
+
"A taxa vem junto, ent\xE3o o valor l\xEDquido \xE9 o que aparece no caixa."
|
|
7720
|
+
],
|
|
7721
|
+
...connectionStatus(
|
|
7722
|
+
STONE_CONNECTOR_ID,
|
|
7723
|
+
(connection) => ({
|
|
7724
|
+
connected: !!connection?.active,
|
|
7725
|
+
detail: methodsDetail2(METHOD_LABELS5, connection?.settings?.methods),
|
|
7726
|
+
plane: "sync",
|
|
7727
|
+
...settingsStatus(STONE_SETTINGS, connection)
|
|
7728
|
+
}),
|
|
7729
|
+
{ store: () => store5 }
|
|
7730
|
+
),
|
|
7731
|
+
...connectionSettings(store5),
|
|
7732
|
+
...connectionRuns(store5),
|
|
7733
|
+
saveConnection: async (values) => {
|
|
7734
|
+
await store5.save({
|
|
7735
|
+
active: true,
|
|
7736
|
+
providerAccountKey: values.stoneCode ?? null,
|
|
7737
|
+
settings: { stoneCode: values.stoneCode }
|
|
7738
|
+
});
|
|
7739
|
+
},
|
|
7740
|
+
disconnect: () => store5.disconnect(),
|
|
7741
|
+
disconnectNote: "Desconectar para de trazer vendas novas. O que j\xE1 foi lan\xE7ado no Financeiro continua l\xE1."
|
|
7742
|
+
};
|
|
7660
7743
|
var STATUS_STYLE = {
|
|
7661
7744
|
paid: "bg-success/15 text-success",
|
|
7662
7745
|
partial: "bg-warning/15 text-warning",
|
|
@@ -7804,15 +7887,16 @@ function resolveConfig(options) {
|
|
|
7804
7887
|
receivables: options?.modules?.receivables !== false,
|
|
7805
7888
|
cashRegisters: options?.modules?.cashRegisters !== false,
|
|
7806
7889
|
statements: options?.modules?.statements !== false,
|
|
7807
|
-
|
|
7890
|
+
// Opt-in: commission RULES are a settings registry, but nothing computes a
|
|
7891
|
+
// commission. Defaulting it on advertised a nav item that could only ever
|
|
7892
|
+
// land on an empty screen.
|
|
7893
|
+
commissions: options?.modules?.commissions === true,
|
|
7808
7894
|
cards: options?.modules?.cards !== false,
|
|
7809
7895
|
// Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
|
|
7810
7896
|
reconciliation: options?.modules?.reconciliation === true,
|
|
7811
7897
|
// Opt-in: only shown when an app explicitly enables fiscal-invoice tracking.
|
|
7812
7898
|
notasFiscais: options?.modules?.notasFiscais === true
|
|
7813
7899
|
},
|
|
7814
|
-
// Opt-in: only render the plugin's own quick-add buttons/FAB when the host asks.
|
|
7815
|
-
quickAdd: options?.quickAdd === true,
|
|
7816
7900
|
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
7817
7901
|
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
7818
7902
|
moduleNav: options?.moduleNav,
|
|
@@ -7834,14 +7918,15 @@ function createFinancialPlugin(options) {
|
|
|
7834
7918
|
registerTranslations(financialLocales);
|
|
7835
7919
|
registerMessageDefaults(buildFinancialMessageDefaults());
|
|
7836
7920
|
const provider = options?.dataProvider ?? createSafeFinancialProvider();
|
|
7837
|
-
const
|
|
7838
|
-
const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store:
|
|
7921
|
+
const store6 = createFinancialStore(provider);
|
|
7922
|
+
const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store: store6 });
|
|
7839
7923
|
const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
|
|
7840
|
-
const PersonStatementBound = (props) =>
|
|
7924
|
+
const PersonStatementBound = (props) => React17.createElement(PersonStatementWidget, { person: props.item, provider, config });
|
|
7841
7925
|
PersonStatementBound.displayName = "FinancialPersonStatement";
|
|
7842
|
-
const PageComponent = () =>
|
|
7926
|
+
const PageComponent = () => React17.createElement(FinancialPage, { config, provider, store: store6, registries });
|
|
7843
7927
|
return {
|
|
7844
7928
|
id: "financial",
|
|
7929
|
+
defaultAgentRole: "finance",
|
|
7845
7930
|
name: config.labels.pageTitle,
|
|
7846
7931
|
icon: "DollarSign",
|
|
7847
7932
|
version: "1.0.0",
|
|
@@ -7968,28 +8053,15 @@ function createFinancialPlugin(options) {
|
|
|
7968
8053
|
{ label: "Compare this week's revenue to last week" }
|
|
7969
8054
|
]
|
|
7970
8055
|
},
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
type: "object",
|
|
7981
|
-
properties: {
|
|
7982
|
-
contact: { type: "string", description: "Contact/customer name" },
|
|
7983
|
-
amount: { type: "number", description: "Invoice amount" },
|
|
7984
|
-
description: { type: "string", description: "Invoice description" }
|
|
7985
|
-
},
|
|
7986
|
-
required: ["contact", "amount"]
|
|
7987
|
-
},
|
|
7988
|
-
suggestions: [
|
|
7989
|
-
{ label: "Create an invoice for a new client" }
|
|
7990
|
-
],
|
|
7991
|
-
permission: { feature: "financial", action: "create" }
|
|
7992
|
-
},
|
|
8056
|
+
// (removed) `createInvoice`. It declared mode:'persist' with no handler, no
|
|
8057
|
+
// dataTools entry and no RPC, so buildAgentToolset filtered it out — a
|
|
8058
|
+
// capability advertised to nobody. It was not given an RPC because its
|
|
8059
|
+
// three parameters (contact, amount, description) cannot express what
|
|
8060
|
+
// CreateInvoiceInput requires: `direction` (payable vs receivable),
|
|
8061
|
+
// `invoiceDate`, and `installments[]` with their due dates. An RPC would
|
|
8062
|
+
// have to invent all three, and a bill with a guessed due date is worse
|
|
8063
|
+
// than no bill. Creating an invoice stays the form's job — /financial
|
|
8064
|
+
// #payables-new, which the agent can navigate to.
|
|
7993
8065
|
{
|
|
7994
8066
|
id: "financial.mark-payment-received",
|
|
7995
8067
|
name: "markPaymentReceived",
|
|
@@ -8045,10 +8117,9 @@ function createFinancialPlugin(options) {
|
|
|
8045
8117
|
label: "Financial",
|
|
8046
8118
|
icon: "DollarSign",
|
|
8047
8119
|
component: (() => {
|
|
8048
|
-
const FinancialSettingsTab = () =>
|
|
8120
|
+
const FinancialSettingsTab = () => React17.createElement(PluginSettingsPanel, {
|
|
8049
8121
|
title: "Financial Settings",
|
|
8050
|
-
|
|
8051
|
-
generalSettings: React18__default.createElement(FinancialGeneralSettings),
|
|
8122
|
+
generalSettings: React17.createElement(FinancialGeneralSettings),
|
|
8052
8123
|
registries,
|
|
8053
8124
|
hostPluginId: "financial",
|
|
8054
8125
|
routeBase: "/settings/financial"
|
|
@@ -8063,7 +8134,18 @@ function createFinancialPlugin(options) {
|
|
|
8063
8134
|
// Stripe is the first connector this module implements rather than merely
|
|
8064
8135
|
// shelves — it stays in `integrations` too, because the union is what the
|
|
8065
8136
|
// catalogue row joins on.
|
|
8066
|
-
|
|
8137
|
+
// Stripe emits the receivable; the four below bring back what was actually
|
|
8138
|
+
// received, which is the other half of a conciliação. Mercado Pago and
|
|
8139
|
+
// PagBank broker the merchant's consent, so they ask for nothing; Cielo and
|
|
8140
|
+
// Stone issue their credential TO the merchant and have no consent flow to
|
|
8141
|
+
// broker — see src/integrations/acquirers/ACQUIRERS.md.
|
|
8142
|
+
connectors: [
|
|
8143
|
+
stripeConnectorDef,
|
|
8144
|
+
mercadoPagoConnectorDef,
|
|
8145
|
+
pagBankConnectorDef,
|
|
8146
|
+
cieloConnectorDef,
|
|
8147
|
+
stoneConnectorDef
|
|
8148
|
+
],
|
|
8067
8149
|
// TecnoSpeed is live already — the openbanking addon's `plugbank`
|
|
8068
8150
|
// connector IS this slug. AbacatePay and Pagar.me were named as what
|
|
8069
8151
|
// having Financeiro should put on the shelf; Mercado Pago, Bling and Stone
|
|
@@ -8073,6 +8155,6 @@ function createFinancialPlugin(options) {
|
|
|
8073
8155
|
};
|
|
8074
8156
|
}
|
|
8075
8157
|
|
|
8076
|
-
export { FINANCIAL_NAV_FEATURES, buildFinancialDomainEvents, buildFinancialMessageDefaults, createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider
|
|
8158
|
+
export { FINANCIAL_NAV_FEATURES, buildFinancialDomainEvents, buildFinancialMessageDefaults, createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider };
|
|
8077
8159
|
//# sourceMappingURL=index.js.map
|
|
8078
8160
|
//# sourceMappingURL=index.js.map
|