@fayz-ai/plugin-financial 0.8.3 → 0.8.4
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/dist/FinancialContext.d.ts +2 -0
- package/dist/FinancialContext.d.ts.map +1 -1
- package/dist/FinancialPage.d.ts.map +1 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +1 -0
- package/dist/data/tables.d.ts.map +1 -1
- package/dist/data/types.d.ts +8 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.cjs +494 -188
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +323 -17
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/NotasFiscaisView.d.ts +3 -0
- package/dist/views/NotasFiscaisView.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/FinancialContext.tsx +2 -0
- package/src/FinancialPage.tsx +14 -0
- package/src/data/mock.ts +69 -0
- package/src/data/supabase.ts +79 -0
- package/src/data/tables.ts +1 -0
- package/src/data/types.ts +13 -0
- package/src/index.ts +6 -0
- package/src/locales/en.ts +1 -0
- package/src/locales/pt-BR.ts +1 -0
- package/src/migrations/011_nfe.sql +58 -0
- package/src/migrations/index.ts +62 -1
- package/src/types.ts +63 -0
- package/src/views/InvoiceDetailView.tsx +3 -1
- package/src/views/NotasFiscaisView.tsx +209 -0
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React18 = require('react');
|
|
4
4
|
var core = require('@fayz-ai/core');
|
|
5
5
|
var ui = require('@fayz-ai/ui');
|
|
6
6
|
var saas = require('@fayz-ai/saas');
|
|
@@ -27,7 +27,7 @@ function _interopNamespace(e) {
|
|
|
27
27
|
return Object.freeze(n);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
var
|
|
30
|
+
var React18__namespace = /*#__PURE__*/_interopNamespace(React18);
|
|
31
31
|
|
|
32
32
|
// src/index.ts
|
|
33
33
|
var ctx = saas.createPluginContext("FinancialPage");
|
|
@@ -68,7 +68,7 @@ function categoryIcon(label) {
|
|
|
68
68
|
function useDateLabel() {
|
|
69
69
|
const t2 = core.useTranslation();
|
|
70
70
|
const { currency } = useFinancialConfig();
|
|
71
|
-
return
|
|
71
|
+
return React18.useMemo(() => {
|
|
72
72
|
const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
73
73
|
const y = /* @__PURE__ */ new Date();
|
|
74
74
|
y.setDate(y.getDate() - 1);
|
|
@@ -98,7 +98,7 @@ function MobileSummary() {
|
|
|
98
98
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
99
99
|
const fetchStatement = useFinancialStore((s) => s.fetchStatement);
|
|
100
100
|
const dateLabel = useDateLabel();
|
|
101
|
-
|
|
101
|
+
React18.useEffect(() => {
|
|
102
102
|
void fetchSummary();
|
|
103
103
|
void fetchBankAccounts();
|
|
104
104
|
const to = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -109,11 +109,11 @@ function MobileSummary() {
|
|
|
109
109
|
const fmt = (n) => saas.formatCurrency(n, currency);
|
|
110
110
|
const inflow = summary?.monthlyInflow ?? 0;
|
|
111
111
|
const outflow = summary?.monthlyOutflow ?? 0;
|
|
112
|
-
const accountName =
|
|
112
|
+
const accountName = React18.useMemo(() => {
|
|
113
113
|
const map = new Map(bankAccounts.map((a) => [a.id, a.name]));
|
|
114
114
|
return (id) => id ? map.get(id) ?? "" : "";
|
|
115
115
|
}, [bankAccounts]);
|
|
116
|
-
const groups =
|
|
116
|
+
const groups = React18.useMemo(() => {
|
|
117
117
|
const entries = (statement?.entries ?? []).filter((e) => e.entryKind === "movement");
|
|
118
118
|
const byDate = /* @__PURE__ */ new Map();
|
|
119
119
|
for (const e of entries) {
|
|
@@ -193,9 +193,9 @@ function typeTheme(type) {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
function ChipSelect({ icon: Icon, label, value, options, onChange, emptyLabel }) {
|
|
196
|
-
const [open, setOpen] =
|
|
197
|
-
const ref =
|
|
198
|
-
|
|
196
|
+
const [open, setOpen] = React18.useState(false);
|
|
197
|
+
const ref = React18.useRef(null);
|
|
198
|
+
React18.useEffect(() => {
|
|
199
199
|
if (!open) return;
|
|
200
200
|
function onDoc(e) {
|
|
201
201
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
@@ -275,22 +275,22 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
|
|
|
275
275
|
const createQuickTransaction = useFinancialStore((s) => s.createQuickTransaction);
|
|
276
276
|
const guardMovements = saas.useLimitGuard("movements_month");
|
|
277
277
|
const can = saas.usePermissionOptional();
|
|
278
|
-
const [type, setType] =
|
|
279
|
-
const [amount, setAmount] =
|
|
280
|
-
const [amountStr, setAmountStr] =
|
|
281
|
-
const [paid, setPaid] =
|
|
282
|
-
const [date, setDate] =
|
|
283
|
-
const [description, setDescription] =
|
|
284
|
-
const [categoryId, setCategoryId] =
|
|
285
|
-
const [accountId, setAccountId] =
|
|
286
|
-
const [toAccountId, setToAccountId] =
|
|
287
|
-
const [recurring, setRecurring] =
|
|
288
|
-
const [saving, setSaving] =
|
|
289
|
-
const [receiptUrl, setReceiptUrl] =
|
|
290
|
-
const fileInputRef =
|
|
291
|
-
const [accounts, setAccounts] =
|
|
292
|
-
const [categories, setCategories] =
|
|
293
|
-
|
|
278
|
+
const [type, setType] = React18.useState(defaultType);
|
|
279
|
+
const [amount, setAmount] = React18.useState(0);
|
|
280
|
+
const [amountStr, setAmountStr] = React18.useState("");
|
|
281
|
+
const [paid, setPaid] = React18.useState(true);
|
|
282
|
+
const [date, setDate] = React18.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
283
|
+
const [description, setDescription] = React18.useState("");
|
|
284
|
+
const [categoryId, setCategoryId] = React18.useState();
|
|
285
|
+
const [accountId, setAccountId] = React18.useState();
|
|
286
|
+
const [toAccountId, setToAccountId] = React18.useState();
|
|
287
|
+
const [recurring, setRecurring] = React18.useState(false);
|
|
288
|
+
const [saving, setSaving] = React18.useState(false);
|
|
289
|
+
const [receiptUrl, setReceiptUrl] = React18.useState();
|
|
290
|
+
const fileInputRef = React18.useRef(null);
|
|
291
|
+
const [accounts, setAccounts] = React18.useState([]);
|
|
292
|
+
const [categories, setCategories] = React18.useState([]);
|
|
293
|
+
React18.useEffect(() => {
|
|
294
294
|
if (!open) return;
|
|
295
295
|
setType(defaultType);
|
|
296
296
|
setAmount(0);
|
|
@@ -304,7 +304,7 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
|
|
|
304
304
|
setRecurring(false);
|
|
305
305
|
setReceiptUrl(void 0);
|
|
306
306
|
}, [open, defaultType, defaultAccountId]);
|
|
307
|
-
|
|
307
|
+
React18.useEffect(() => {
|
|
308
308
|
if (open && focusReceipt) {
|
|
309
309
|
const id = window.setTimeout(() => fileInputRef.current?.click(), 250);
|
|
310
310
|
return () => window.clearTimeout(id);
|
|
@@ -325,7 +325,7 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
|
|
|
325
325
|
reader.onerror = () => ui.toast.error(t2("financial.quickTx.receiptInvalid"));
|
|
326
326
|
reader.readAsDataURL(file);
|
|
327
327
|
}
|
|
328
|
-
|
|
328
|
+
React18.useEffect(() => {
|
|
329
329
|
if (!open) return;
|
|
330
330
|
let cancelled = false;
|
|
331
331
|
void (async () => {
|
|
@@ -344,11 +344,11 @@ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", def
|
|
|
344
344
|
}, [open, provider]);
|
|
345
345
|
const theme = typeTheme(type);
|
|
346
346
|
const HeaderIcon = theme.icon;
|
|
347
|
-
const accountOptions =
|
|
347
|
+
const accountOptions = React18.useMemo(
|
|
348
348
|
() => accounts.map((a) => ({ id: a.id, name: a.name, hint: a.accountType === "credit_card" ? t2("financial.quickTx.card") : void 0 })),
|
|
349
349
|
[accounts, t2]
|
|
350
350
|
);
|
|
351
|
-
const categoryOptions =
|
|
351
|
+
const categoryOptions = React18.useMemo(
|
|
352
352
|
() => categories.filter((c) => c.isActive).map((c) => ({ id: c.id, name: c.name, hint: c.code })),
|
|
353
353
|
[categories]
|
|
354
354
|
);
|
|
@@ -662,7 +662,7 @@ function getSnapshot() {
|
|
|
662
662
|
return pending;
|
|
663
663
|
}
|
|
664
664
|
function usePendingQuickAdd() {
|
|
665
|
-
return
|
|
665
|
+
return React18__namespace.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
666
666
|
}
|
|
667
667
|
var STATUS_OPTIONS = [
|
|
668
668
|
{ value: "open", labelKey: "financial.invoice.statusOpen", color: "bg-info-soft text-info-soft-foreground", icon: lucideReact.CircleDashed },
|
|
@@ -700,7 +700,7 @@ function StatusBadge({ status }) {
|
|
|
700
700
|
}
|
|
701
701
|
function useInvoiceColumns(currency) {
|
|
702
702
|
const t2 = core.useTranslation();
|
|
703
|
-
return
|
|
703
|
+
return React18.useMemo(() => [
|
|
704
704
|
{
|
|
705
705
|
accessorKey: "fiscalNumber",
|
|
706
706
|
header: "#",
|
|
@@ -759,16 +759,16 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
759
759
|
useFinancialStore((s) => s.invoicesTotal);
|
|
760
760
|
const invoicesLoading = useFinancialStore((s) => s.invoicesLoading);
|
|
761
761
|
const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
|
|
762
|
-
const [search, setSearch] =
|
|
763
|
-
const [statusFilter, setStatusFilter] =
|
|
764
|
-
const [accountId, setAccountId] =
|
|
765
|
-
const [costCenterId, setCostCenterId] =
|
|
766
|
-
const [accounts, setAccounts] =
|
|
767
|
-
const [costCenters, setCostCenters] =
|
|
768
|
-
const [datePreset, setDatePreset] =
|
|
769
|
-
const [dateFrom, setDateFrom] =
|
|
770
|
-
const [dateTo, setDateTo] =
|
|
771
|
-
const dateRange =
|
|
762
|
+
const [search, setSearch] = React18.useState("");
|
|
763
|
+
const [statusFilter, setStatusFilter] = React18.useState([]);
|
|
764
|
+
const [accountId, setAccountId] = React18.useState("");
|
|
765
|
+
const [costCenterId, setCostCenterId] = React18.useState("");
|
|
766
|
+
const [accounts, setAccounts] = React18.useState([]);
|
|
767
|
+
const [costCenters, setCostCenters] = React18.useState([]);
|
|
768
|
+
const [datePreset, setDatePreset] = React18.useState("all");
|
|
769
|
+
const [dateFrom, setDateFrom] = React18.useState("");
|
|
770
|
+
const [dateTo, setDateTo] = React18.useState("");
|
|
771
|
+
const dateRange = React18.useMemo(() => {
|
|
772
772
|
if (!dateFrom && !dateTo) return void 0;
|
|
773
773
|
return { from: dateFrom || "0001-01-01", to: dateTo || "9999-12-31" };
|
|
774
774
|
}, [dateFrom, dateTo]);
|
|
@@ -784,7 +784,7 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
784
784
|
setDateFrom(range.from);
|
|
785
785
|
setDateTo(range.to);
|
|
786
786
|
};
|
|
787
|
-
|
|
787
|
+
React18.useEffect(() => {
|
|
788
788
|
let active = true;
|
|
789
789
|
Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
|
|
790
790
|
if (!active) return;
|
|
@@ -799,7 +799,7 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
799
799
|
active = false;
|
|
800
800
|
};
|
|
801
801
|
}, [provider]);
|
|
802
|
-
|
|
802
|
+
React18.useEffect(() => {
|
|
803
803
|
fetchInvoices({
|
|
804
804
|
direction,
|
|
805
805
|
status: statusFilter.length > 0 ? statusFilter : void 0,
|
|
@@ -812,11 +812,11 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
812
812
|
const filtered = invoices.filter((inv) => inv.direction === direction);
|
|
813
813
|
const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
|
|
814
814
|
const columns = useInvoiceColumns(currency);
|
|
815
|
-
const tags =
|
|
815
|
+
const tags = React18.useMemo(
|
|
816
816
|
() => STATUS_OPTIONS.filter((o) => o.value !== "cancelled").map((o) => ({ value: o.value, label: t2(o.labelKey) })),
|
|
817
817
|
[t2]
|
|
818
818
|
);
|
|
819
|
-
const summary =
|
|
819
|
+
const summary = React18.useMemo(() => {
|
|
820
820
|
let totalAmount = 0;
|
|
821
821
|
let paidAmount = 0;
|
|
822
822
|
let openAmount = 0;
|
|
@@ -980,10 +980,10 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
980
980
|
}
|
|
981
981
|
function LocationSelector({ locations, value, onChange }) {
|
|
982
982
|
const t2 = core.useTranslation();
|
|
983
|
-
const [open, setOpen] =
|
|
984
|
-
const [search, setSearch] =
|
|
985
|
-
const ref =
|
|
986
|
-
|
|
983
|
+
const [open, setOpen] = React18.useState(false);
|
|
984
|
+
const [search, setSearch] = React18.useState("");
|
|
985
|
+
const ref = React18.useRef(null);
|
|
986
|
+
React18.useEffect(() => {
|
|
987
987
|
function handler(e) {
|
|
988
988
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
989
989
|
}
|
|
@@ -1063,7 +1063,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
|
|
|
1063
1063
|
const total = item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0);
|
|
1064
1064
|
const kindLabel = itemTypes.find((tp) => tp.value === item.itemKind)?.label ?? item.itemKind;
|
|
1065
1065
|
const hasAdjustments = (item.discount ?? 0) > 0 || (item.surcharge ?? 0) > 0;
|
|
1066
|
-
const [showAdjustments, setShowAdjustments] =
|
|
1066
|
+
const [showAdjustments, setShowAdjustments] = React18.useState(hasAdjustments);
|
|
1067
1067
|
function handleFieldKeyDown(e) {
|
|
1068
1068
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
1069
1069
|
e.preventDefault();
|
|
@@ -1301,25 +1301,25 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1301
1301
|
const can = saas.usePermissionOptional();
|
|
1302
1302
|
const hasLocations = locations.length > 0;
|
|
1303
1303
|
const isEdit = !!editId;
|
|
1304
|
-
const [loaded, setLoaded] =
|
|
1305
|
-
const [loading, setLoading] =
|
|
1306
|
-
const [contactName, setContactName] =
|
|
1307
|
-
const [contactId, setContactId] =
|
|
1308
|
-
const [unitId, setUnitId] =
|
|
1309
|
-
const [invoiceDate, setInvoiceDate] =
|
|
1310
|
-
const [fiscalNumber, setFiscalNumber] =
|
|
1311
|
-
const [observations, setObservations] =
|
|
1312
|
-
const [items, setItems] =
|
|
1313
|
-
const [expandedItemId, setExpandedItemId] =
|
|
1314
|
-
const [addingItem, setAddingItem] =
|
|
1315
|
-
const [installments, setInstallments] =
|
|
1316
|
-
const [saving, setSaving] =
|
|
1317
|
-
const [menuOpen, setMenuOpen] =
|
|
1318
|
-
const [confirmCancel, setConfirmCancel] =
|
|
1319
|
-
const [cancelling, setCancelling] =
|
|
1320
|
-
const menuRef =
|
|
1304
|
+
const [loaded, setLoaded] = React18.useState(!editId);
|
|
1305
|
+
const [loading, setLoading] = React18.useState(!!editId);
|
|
1306
|
+
const [contactName, setContactName] = React18.useState("");
|
|
1307
|
+
const [contactId, setContactId] = React18.useState();
|
|
1308
|
+
const [unitId, setUnitId] = React18.useState(locations[0]?.id ?? "");
|
|
1309
|
+
const [invoiceDate, setInvoiceDate] = React18.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
1310
|
+
const [fiscalNumber, setFiscalNumber] = React18.useState("");
|
|
1311
|
+
const [observations, setObservations] = React18.useState("");
|
|
1312
|
+
const [items, setItems] = React18.useState([]);
|
|
1313
|
+
const [expandedItemId, setExpandedItemId] = React18.useState(null);
|
|
1314
|
+
const [addingItem, setAddingItem] = React18.useState(false);
|
|
1315
|
+
const [installments, setInstallments] = React18.useState([]);
|
|
1316
|
+
const [saving, setSaving] = React18.useState(false);
|
|
1317
|
+
const [menuOpen, setMenuOpen] = React18.useState(false);
|
|
1318
|
+
const [confirmCancel, setConfirmCancel] = React18.useState(false);
|
|
1319
|
+
const [cancelling, setCancelling] = React18.useState(false);
|
|
1320
|
+
const menuRef = React18.useRef(null);
|
|
1321
1321
|
const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
|
|
1322
|
-
|
|
1322
|
+
React18.useEffect(() => {
|
|
1323
1323
|
if (!menuOpen) return;
|
|
1324
1324
|
function handleClick(e) {
|
|
1325
1325
|
if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
|
|
@@ -1338,7 +1338,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1338
1338
|
setCancelling(false);
|
|
1339
1339
|
}
|
|
1340
1340
|
}
|
|
1341
|
-
|
|
1341
|
+
React18.useEffect(() => {
|
|
1342
1342
|
if (!editId || loaded) return;
|
|
1343
1343
|
setLoading(true);
|
|
1344
1344
|
(async () => {
|
|
@@ -1375,8 +1375,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1375
1375
|
const totalAmount = items.reduce((sum, it) => {
|
|
1376
1376
|
return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
|
|
1377
1377
|
}, 0);
|
|
1378
|
-
const prevItemCount =
|
|
1379
|
-
|
|
1378
|
+
const prevItemCount = React18.useRef(items.length);
|
|
1379
|
+
React18.useEffect(() => {
|
|
1380
1380
|
if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
|
|
1381
1381
|
const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
|
|
1382
1382
|
if (total > 0) {
|
|
@@ -1385,7 +1385,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1385
1385
|
}
|
|
1386
1386
|
prevItemCount.current = items.length;
|
|
1387
1387
|
}, [items.length]);
|
|
1388
|
-
|
|
1388
|
+
React18.useEffect(() => {
|
|
1389
1389
|
if (installments.length > 0 && totalAmount > 0 && !isEdit) {
|
|
1390
1390
|
const count = installments.length;
|
|
1391
1391
|
const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
|
|
@@ -1500,8 +1500,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
|
|
|
1500
1500
|
items: items.map(({ _id, ...rest }) => rest),
|
|
1501
1501
|
installments: installments.map(({ _id, ...rest }) => rest)
|
|
1502
1502
|
};
|
|
1503
|
-
const snapshot =
|
|
1504
|
-
|
|
1503
|
+
const snapshot = React18.useRef(null);
|
|
1504
|
+
React18.useEffect(() => {
|
|
1505
1505
|
if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
|
|
1506
1506
|
}, [loaded]);
|
|
1507
1507
|
const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
|
|
@@ -1811,40 +1811,40 @@ function PaymentModal({ movement, onClose, onPaid }) {
|
|
|
1811
1811
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
1812
1812
|
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
1813
1813
|
const remaining = movement.amount - movement.paidAmount;
|
|
1814
|
-
const [amount, setAmount] =
|
|
1815
|
-
const [paymentDate, setPaymentDate] =
|
|
1816
|
-
const [selectedTypeId, setSelectedTypeId] =
|
|
1817
|
-
const [paymentMethodId, setPaymentMethodId] =
|
|
1818
|
-
const [bankAccountId, setBankAccountId] =
|
|
1819
|
-
const [cardBrand, setCardBrand] =
|
|
1820
|
-
const [cardInstallments, setCardInstallments] =
|
|
1821
|
-
const [notes, setNotes] =
|
|
1822
|
-
const [saving, setSaving] =
|
|
1823
|
-
const [visible, setVisible] =
|
|
1824
|
-
const [expandMethodPicker, setExpandMethodPicker] =
|
|
1825
|
-
|
|
1814
|
+
const [amount, setAmount] = React18.useState(remaining);
|
|
1815
|
+
const [paymentDate, setPaymentDate] = React18.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
1816
|
+
const [selectedTypeId, setSelectedTypeId] = React18.useState("");
|
|
1817
|
+
const [paymentMethodId, setPaymentMethodId] = React18.useState("");
|
|
1818
|
+
const [bankAccountId, setBankAccountId] = React18.useState("");
|
|
1819
|
+
const [cardBrand, setCardBrand] = React18.useState("");
|
|
1820
|
+
const [cardInstallments, setCardInstallments] = React18.useState(1);
|
|
1821
|
+
const [notes, setNotes] = React18.useState("");
|
|
1822
|
+
const [saving, setSaving] = React18.useState(false);
|
|
1823
|
+
const [visible, setVisible] = React18.useState(false);
|
|
1824
|
+
const [expandMethodPicker, setExpandMethodPicker] = React18.useState(true);
|
|
1825
|
+
React18.useEffect(() => {
|
|
1826
1826
|
fetchBankAccounts();
|
|
1827
1827
|
fetchPaymentMethods();
|
|
1828
1828
|
requestAnimationFrame(() => setVisible(true));
|
|
1829
1829
|
}, []);
|
|
1830
|
-
const selectedType =
|
|
1830
|
+
const selectedType = React18.useMemo(
|
|
1831
1831
|
() => paymentMethodTypes.find((tp) => tp.id === selectedTypeId),
|
|
1832
1832
|
[paymentMethodTypes, selectedTypeId]
|
|
1833
1833
|
);
|
|
1834
1834
|
const txType = selectedType?.transactionType ?? "";
|
|
1835
|
-
const filteredMethods =
|
|
1835
|
+
const filteredMethods = React18.useMemo(
|
|
1836
1836
|
() => paymentMethods.filter((m) => m.paymentMethodTypeId === selectedTypeId),
|
|
1837
1837
|
[paymentMethods, selectedTypeId]
|
|
1838
1838
|
);
|
|
1839
1839
|
const needsCard = txType === "credit_card" || txType === "debit_card";
|
|
1840
1840
|
const needsCardInstallments = txType === "credit_card";
|
|
1841
|
-
const eligibleAccounts =
|
|
1841
|
+
const eligibleAccounts = React18.useMemo(() => {
|
|
1842
1842
|
const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[txType];
|
|
1843
1843
|
if (!allowed || !allowed.length) return bankAccounts;
|
|
1844
1844
|
const filtered = bankAccounts.filter((a) => allowed.includes(a.accountType));
|
|
1845
1845
|
return filtered.length ? filtered : bankAccounts;
|
|
1846
1846
|
}, [bankAccounts, selectedType, txType]);
|
|
1847
|
-
|
|
1847
|
+
React18.useEffect(() => {
|
|
1848
1848
|
if (selectedTypeId && !bankAccountId && eligibleAccounts.length) {
|
|
1849
1849
|
setBankAccountId(eligibleAccounts[0].id);
|
|
1850
1850
|
}
|
|
@@ -2088,26 +2088,28 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
2088
2088
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
2089
2089
|
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
2090
2090
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
2091
|
-
const [detailsLoaded, setDetailsLoaded] =
|
|
2092
|
-
const [detailsLoading, setDetailsLoading] =
|
|
2093
|
-
const [invoice, setInvoice] =
|
|
2094
|
-
const [items, setItems] =
|
|
2095
|
-
const [movements, setMovements] =
|
|
2096
|
-
const [payments, setPayments] =
|
|
2097
|
-
const [loading, setLoading] =
|
|
2098
|
-
const [payingMovement, setPayingMovement] =
|
|
2099
|
-
const [expandedMovId, setExpandedMovId] =
|
|
2100
|
-
const [menuOpen, setMenuOpen] =
|
|
2101
|
-
const [confirmCancel, setConfirmCancel] =
|
|
2102
|
-
const [cancelling, setCancelling] =
|
|
2103
|
-
const menuRef =
|
|
2091
|
+
const [detailsLoaded, setDetailsLoaded] = React18.useState(false);
|
|
2092
|
+
const [detailsLoading, setDetailsLoading] = React18.useState(false);
|
|
2093
|
+
const [invoice, setInvoice] = React18.useState(null);
|
|
2094
|
+
const [items, setItems] = React18.useState([]);
|
|
2095
|
+
const [movements, setMovements] = React18.useState([]);
|
|
2096
|
+
const [payments, setPayments] = React18.useState([]);
|
|
2097
|
+
const [loading, setLoading] = React18.useState(true);
|
|
2098
|
+
const [payingMovement, setPayingMovement] = React18.useState(null);
|
|
2099
|
+
const [expandedMovId, setExpandedMovId] = React18.useState(null);
|
|
2100
|
+
const [menuOpen, setMenuOpen] = React18.useState(false);
|
|
2101
|
+
const [confirmCancel, setConfirmCancel] = React18.useState(false);
|
|
2102
|
+
const [cancelling, setCancelling] = React18.useState(false);
|
|
2103
|
+
const menuRef = React18.useRef(null);
|
|
2104
2104
|
const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
|
|
2105
2105
|
async function loadData() {
|
|
2106
2106
|
setLoading(true);
|
|
2107
2107
|
const [inv, invItems, movResult] = await Promise.all([
|
|
2108
2108
|
provider.getInvoiceById(invoiceId),
|
|
2109
2109
|
provider.getInvoiceItems(invoiceId),
|
|
2110
|
-
|
|
2110
|
+
// Scope to THIS invoice (not the tenant-wide first-50 page) so every
|
|
2111
|
+
// installment/payment loads regardless of how many movements exist.
|
|
2112
|
+
provider.getMovements({ direction, invoiceId, pageSize: 500 })
|
|
2111
2113
|
]);
|
|
2112
2114
|
setInvoice(inv);
|
|
2113
2115
|
setItems(invItems);
|
|
@@ -2119,10 +2121,10 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
2119
2121
|
);
|
|
2120
2122
|
setLoading(false);
|
|
2121
2123
|
}
|
|
2122
|
-
|
|
2124
|
+
React18.useEffect(() => {
|
|
2123
2125
|
loadData();
|
|
2124
2126
|
}, [invoiceId]);
|
|
2125
|
-
|
|
2127
|
+
React18.useEffect(() => {
|
|
2126
2128
|
if (!menuOpen) return;
|
|
2127
2129
|
function handleClick(e) {
|
|
2128
2130
|
if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
|
|
@@ -2723,16 +2725,16 @@ function CashOpeningPanel({
|
|
|
2723
2725
|
currency,
|
|
2724
2726
|
onOpen
|
|
2725
2727
|
}) {
|
|
2726
|
-
const [selectedAccountId, setSelectedAccountId] =
|
|
2727
|
-
const [mode, setMode] =
|
|
2728
|
-
const [quickBalance, setQuickBalance] =
|
|
2729
|
-
const [counts, setCounts] =
|
|
2730
|
-
const [notes, setNotes] =
|
|
2731
|
-
const [showNotes, setShowNotes] =
|
|
2732
|
-
const [confirming, setConfirming] =
|
|
2733
|
-
const [submitting, setSubmitting] =
|
|
2728
|
+
const [selectedAccountId, setSelectedAccountId] = React18.useState(cashAccounts[0]?.id ?? "");
|
|
2729
|
+
const [mode, setMode] = React18.useState("quick");
|
|
2730
|
+
const [quickBalance, setQuickBalance] = React18.useState(0);
|
|
2731
|
+
const [counts, setCounts] = React18.useState({});
|
|
2732
|
+
const [notes, setNotes] = React18.useState("");
|
|
2733
|
+
const [showNotes, setShowNotes] = React18.useState(false);
|
|
2734
|
+
const [confirming, setConfirming] = React18.useState(false);
|
|
2735
|
+
const [submitting, setSubmitting] = React18.useState(false);
|
|
2734
2736
|
const t2 = core.useTranslation();
|
|
2735
|
-
const countedTotal =
|
|
2737
|
+
const countedTotal = React18.useMemo(() => {
|
|
2736
2738
|
return BRL_DENOMINATIONS.reduce((sum, d) => sum + (counts[d.value] ?? 0) * d.value, 0);
|
|
2737
2739
|
}, [counts]);
|
|
2738
2740
|
const effectiveBalance = mode === "count" ? countedTotal : quickBalance;
|
|
@@ -2908,13 +2910,13 @@ function CashClosingPanel({
|
|
|
2908
2910
|
currency,
|
|
2909
2911
|
onClose
|
|
2910
2912
|
}) {
|
|
2911
|
-
const [mode, setMode] =
|
|
2912
|
-
const [quickBalance, setQuickBalance] =
|
|
2913
|
-
const [counts, setCounts] =
|
|
2914
|
-
const [notes, setNotes] =
|
|
2915
|
-
const [submitting, setSubmitting] =
|
|
2913
|
+
const [mode, setMode] = React18.useState("quick");
|
|
2914
|
+
const [quickBalance, setQuickBalance] = React18.useState(0);
|
|
2915
|
+
const [counts, setCounts] = React18.useState({});
|
|
2916
|
+
const [notes, setNotes] = React18.useState("");
|
|
2917
|
+
const [submitting, setSubmitting] = React18.useState(false);
|
|
2916
2918
|
const t2 = core.useTranslation();
|
|
2917
|
-
const countedTotal =
|
|
2919
|
+
const countedTotal = React18.useMemo(() => {
|
|
2918
2920
|
return BRL_DENOMINATIONS.reduce((sum, d) => sum + (counts[d.value] ?? 0) * d.value, 0);
|
|
2919
2921
|
}, [counts]);
|
|
2920
2922
|
const effectiveBalance = mode === "count" ? countedTotal : quickBalance;
|
|
@@ -3019,8 +3021,8 @@ function CashRegistersView() {
|
|
|
3019
3021
|
const fetchCashSessions = useFinancialStore((s) => s.fetchCashSessions);
|
|
3020
3022
|
const openCashSession = useFinancialStore((s) => s.openCashSession);
|
|
3021
3023
|
const closeCashSession = useFinancialStore((s) => s.closeCashSession);
|
|
3022
|
-
const [expandedClosing, setExpandedClosing] =
|
|
3023
|
-
|
|
3024
|
+
const [expandedClosing, setExpandedClosing] = React18.useState(null);
|
|
3025
|
+
React18.useEffect(() => {
|
|
3024
3026
|
fetchBankAccounts();
|
|
3025
3027
|
fetchCashSessions();
|
|
3026
3028
|
}, []);
|
|
@@ -3124,7 +3126,7 @@ function OpenAnotherCollapsible({
|
|
|
3124
3126
|
onOpen
|
|
3125
3127
|
}) {
|
|
3126
3128
|
const t2 = core.useTranslation();
|
|
3127
|
-
const [expanded, setExpanded] =
|
|
3129
|
+
const [expanded, setExpanded] = React18.useState(false);
|
|
3128
3130
|
const availableAccounts = cashAccounts.filter((a) => !openSessions.some((s) => s.bankAccountId === a.id));
|
|
3129
3131
|
if (availableAccounts.length === 0) return null;
|
|
3130
3132
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
|
|
@@ -3151,14 +3153,14 @@ function TransferModal({ onClose, onDone, defaultFromId }) {
|
|
|
3151
3153
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
3152
3154
|
const createTransfer = useFinancialStore((s) => s.createTransfer);
|
|
3153
3155
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
3154
|
-
const [fromAccountId, setFromAccountId] =
|
|
3155
|
-
const [toAccountId, setToAccountId] =
|
|
3156
|
-
const [amount, setAmount] =
|
|
3157
|
-
const [date, setDate] =
|
|
3158
|
-
const [notes, setNotes] =
|
|
3159
|
-
const [saving, setSaving] =
|
|
3160
|
-
const [visible, setVisible] =
|
|
3161
|
-
|
|
3156
|
+
const [fromAccountId, setFromAccountId] = React18.useState(defaultFromId ?? "");
|
|
3157
|
+
const [toAccountId, setToAccountId] = React18.useState("");
|
|
3158
|
+
const [amount, setAmount] = React18.useState(0);
|
|
3159
|
+
const [date, setDate] = React18.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
3160
|
+
const [notes, setNotes] = React18.useState("");
|
|
3161
|
+
const [saving, setSaving] = React18.useState(false);
|
|
3162
|
+
const [visible, setVisible] = React18.useState(false);
|
|
3163
|
+
React18.useEffect(() => {
|
|
3162
3164
|
fetchBankAccounts();
|
|
3163
3165
|
requestAnimationFrame(() => setVisible(true));
|
|
3164
3166
|
}, []);
|
|
@@ -3301,19 +3303,19 @@ function StatementsView({ onNavigate }) {
|
|
|
3301
3303
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
3302
3304
|
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
3303
3305
|
const fetchStatement = useFinancialStore((s) => s.fetchStatement);
|
|
3304
|
-
const [accountId, setAccountId] =
|
|
3305
|
-
const [dateFrom, setDateFrom] =
|
|
3306
|
+
const [accountId, setAccountId] = React18.useState("");
|
|
3307
|
+
const [dateFrom, setDateFrom] = React18.useState(() => {
|
|
3306
3308
|
const d = /* @__PURE__ */ new Date();
|
|
3307
3309
|
d.setMonth(d.getMonth() - 3);
|
|
3308
3310
|
return d.toISOString().slice(0, 10);
|
|
3309
3311
|
});
|
|
3310
|
-
const [dateTo, setDateTo] =
|
|
3311
|
-
const [showTransfer, setShowTransfer] =
|
|
3312
|
-
|
|
3312
|
+
const [dateTo, setDateTo] = React18.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
3313
|
+
const [showTransfer, setShowTransfer] = React18.useState(false);
|
|
3314
|
+
React18.useEffect(() => {
|
|
3313
3315
|
fetchBankAccounts();
|
|
3314
3316
|
fetchPaymentMethods();
|
|
3315
3317
|
}, []);
|
|
3316
|
-
const methodName =
|
|
3318
|
+
const methodName = React18.useMemo(() => {
|
|
3317
3319
|
const byId = new Map(paymentMethods.map((m) => [m.id, m.name]));
|
|
3318
3320
|
const typeById = new Map(paymentMethodTypes.map((tp) => [tp.id, tp.name]));
|
|
3319
3321
|
return (mov) => {
|
|
@@ -3328,7 +3330,7 @@ function StatementsView({ onNavigate }) {
|
|
|
3328
3330
|
const base = e.movement.direction === "credit" ? "receivables" : "payables";
|
|
3329
3331
|
onNavigate(`${base}-detail:${e.movement.invoiceId}`);
|
|
3330
3332
|
}
|
|
3331
|
-
|
|
3333
|
+
React18.useEffect(() => {
|
|
3332
3334
|
if (accountId) {
|
|
3333
3335
|
fetchStatement({
|
|
3334
3336
|
bankAccountId: accountId === ALL ? void 0 : accountId,
|
|
@@ -3336,11 +3338,11 @@ function StatementsView({ onNavigate }) {
|
|
|
3336
3338
|
});
|
|
3337
3339
|
}
|
|
3338
3340
|
}, [accountId, dateFrom, dateTo]);
|
|
3339
|
-
|
|
3341
|
+
React18.useEffect(() => {
|
|
3340
3342
|
if (!accountId) setAccountId(ALL);
|
|
3341
3343
|
}, []);
|
|
3342
3344
|
const fmt = (n) => saas.formatCurrency(n, currency);
|
|
3343
|
-
const columns =
|
|
3345
|
+
const columns = React18.useMemo(() => [
|
|
3344
3346
|
{
|
|
3345
3347
|
id: "date",
|
|
3346
3348
|
header: t2("financial.statements.columnDate"),
|
|
@@ -3528,20 +3530,20 @@ function CardsView() {
|
|
|
3528
3530
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
3529
3531
|
const bankAccountsLoading = useFinancialStore((s) => s.bankAccountsLoading);
|
|
3530
3532
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
3531
|
-
const [tab, setTab] =
|
|
3532
|
-
const [addOpen, setAddOpen] =
|
|
3533
|
-
const [addCardId, setAddCardId] =
|
|
3534
|
-
|
|
3533
|
+
const [tab, setTab] = React18.useState("open");
|
|
3534
|
+
const [addOpen, setAddOpen] = React18.useState(false);
|
|
3535
|
+
const [addCardId, setAddCardId] = React18.useState();
|
|
3536
|
+
React18.useEffect(() => {
|
|
3535
3537
|
fetchBankAccounts();
|
|
3536
3538
|
}, []);
|
|
3537
|
-
const cards =
|
|
3539
|
+
const cards = React18.useMemo(
|
|
3538
3540
|
() => bankAccounts.filter((a) => a.accountType === "credit_card"),
|
|
3539
3541
|
[bankAccounts]
|
|
3540
3542
|
);
|
|
3541
|
-
const openCards =
|
|
3542
|
-
const closedCards =
|
|
3543
|
+
const openCards = React18.useMemo(() => cards.filter((c) => c.isActive), [cards]);
|
|
3544
|
+
const closedCards = React18.useMemo(() => cards.filter((c) => !c.isActive), [cards]);
|
|
3543
3545
|
const visibleCards = tab === "open" ? openCards : closedCards;
|
|
3544
|
-
const summary =
|
|
3546
|
+
const summary = React18.useMemo(() => {
|
|
3545
3547
|
let totalAvailable = 0;
|
|
3546
3548
|
let totalBills = 0;
|
|
3547
3549
|
for (const c of openCards) {
|
|
@@ -3657,14 +3659,14 @@ function ReconciliationView() {
|
|
|
3657
3659
|
const { currency } = useFinancialConfig();
|
|
3658
3660
|
const provider = useFinancialProvider();
|
|
3659
3661
|
const fmt = (n) => saas.formatCurrency(n, currency);
|
|
3660
|
-
const [lines, setLines] =
|
|
3661
|
-
const [loading, setLoading] =
|
|
3662
|
-
const [selectedId, setSelectedId] =
|
|
3663
|
-
const [candidates, setCandidates] =
|
|
3664
|
-
const [candidatesLoading, setCandidatesLoading] =
|
|
3665
|
-
const [busy, setBusy] =
|
|
3662
|
+
const [lines, setLines] = React18.useState([]);
|
|
3663
|
+
const [loading, setLoading] = React18.useState(true);
|
|
3664
|
+
const [selectedId, setSelectedId] = React18.useState(null);
|
|
3665
|
+
const [candidates, setCandidates] = React18.useState([]);
|
|
3666
|
+
const [candidatesLoading, setCandidatesLoading] = React18.useState(false);
|
|
3667
|
+
const [busy, setBusy] = React18.useState(false);
|
|
3666
3668
|
const supported = typeof provider.getUnreconciled === "function";
|
|
3667
|
-
const load =
|
|
3669
|
+
const load = React18.useCallback(async () => {
|
|
3668
3670
|
if (!provider.getUnreconciled) return;
|
|
3669
3671
|
setLoading(true);
|
|
3670
3672
|
try {
|
|
@@ -3675,10 +3677,10 @@ function ReconciliationView() {
|
|
|
3675
3677
|
setLoading(false);
|
|
3676
3678
|
}
|
|
3677
3679
|
}, [provider]);
|
|
3678
|
-
|
|
3680
|
+
React18.useEffect(() => {
|
|
3679
3681
|
void load();
|
|
3680
3682
|
}, [load]);
|
|
3681
|
-
|
|
3683
|
+
React18.useEffect(() => {
|
|
3682
3684
|
if (!selectedId || !provider.suggestReconciliation) {
|
|
3683
3685
|
setCandidates([]);
|
|
3684
3686
|
return;
|
|
@@ -3797,6 +3799,161 @@ function ReconciliationView() {
|
|
|
3797
3799
|
] })
|
|
3798
3800
|
] });
|
|
3799
3801
|
}
|
|
3802
|
+
var MODEL_LABEL = {
|
|
3803
|
+
nfe: "NF-e",
|
|
3804
|
+
nfce: "NFC-e",
|
|
3805
|
+
nfse: "NFS-e"
|
|
3806
|
+
};
|
|
3807
|
+
var STATUS_META = {
|
|
3808
|
+
draft: { label: "Rascunho", className: "bg-muted text-muted-foreground" },
|
|
3809
|
+
processing: { label: "Processando", className: "bg-blue-100 text-blue-800" },
|
|
3810
|
+
issued: { label: "Emitida", className: "bg-emerald-100 text-emerald-800" },
|
|
3811
|
+
cancelled: { label: "Cancelada", className: "bg-red-100 text-red-700" },
|
|
3812
|
+
error: { label: "Erro", className: "bg-orange-100 text-orange-700" }
|
|
3813
|
+
};
|
|
3814
|
+
function NotasFiscaisView() {
|
|
3815
|
+
const provider = useFinancialProvider();
|
|
3816
|
+
const { currency } = useFinancialConfig();
|
|
3817
|
+
const [notas, setNotas] = React18.useState([]);
|
|
3818
|
+
const [loading, setLoading] = React18.useState(true);
|
|
3819
|
+
const [creating, setCreating] = React18.useState(false);
|
|
3820
|
+
const [busy, setBusy] = React18.useState(null);
|
|
3821
|
+
const supported = typeof provider.getNotas === "function";
|
|
3822
|
+
const reload = React18.useCallback(async () => {
|
|
3823
|
+
if (!provider.getNotas) {
|
|
3824
|
+
setLoading(false);
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
setLoading(true);
|
|
3828
|
+
try {
|
|
3829
|
+
const res = await provider.getNotas({ pageSize: 100 });
|
|
3830
|
+
setNotas(res.data);
|
|
3831
|
+
} finally {
|
|
3832
|
+
setLoading(false);
|
|
3833
|
+
}
|
|
3834
|
+
}, [provider]);
|
|
3835
|
+
React18.useEffect(() => {
|
|
3836
|
+
void reload();
|
|
3837
|
+
}, [reload]);
|
|
3838
|
+
const create = async (input) => {
|
|
3839
|
+
if (!provider.createNota) return;
|
|
3840
|
+
await provider.createNota(input);
|
|
3841
|
+
setCreating(false);
|
|
3842
|
+
await reload();
|
|
3843
|
+
};
|
|
3844
|
+
const issue = async (id) => {
|
|
3845
|
+
if (!provider.issueNota) return;
|
|
3846
|
+
setBusy(id);
|
|
3847
|
+
try {
|
|
3848
|
+
await provider.issueNota(id);
|
|
3849
|
+
await reload();
|
|
3850
|
+
} finally {
|
|
3851
|
+
setBusy(null);
|
|
3852
|
+
}
|
|
3853
|
+
};
|
|
3854
|
+
const cancel = async (id) => {
|
|
3855
|
+
if (!provider.cancelNota) return;
|
|
3856
|
+
setBusy(id);
|
|
3857
|
+
try {
|
|
3858
|
+
await provider.cancelNota(id);
|
|
3859
|
+
await reload();
|
|
3860
|
+
} finally {
|
|
3861
|
+
setBusy(null);
|
|
3862
|
+
}
|
|
3863
|
+
};
|
|
3864
|
+
if (!supported) {
|
|
3865
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-1", children: [
|
|
3866
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title: "Notas Fiscais", subtitle: "Emiss\xE3o e acompanhamento de NF-e / NFC-e / NFS-e", icon: "FileText" }),
|
|
3867
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-4 text-sm text-muted-foreground", children: "Este provedor financeiro ainda n\xE3o exp\xF5e notas fiscais." })
|
|
3868
|
+
] });
|
|
3869
|
+
}
|
|
3870
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-1", children: [
|
|
3871
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3872
|
+
ui.SubpageHeader,
|
|
3873
|
+
{
|
|
3874
|
+
title: "Notas Fiscais",
|
|
3875
|
+
subtitle: "Emiss\xE3o e acompanhamento de NF-e / NFC-e / NFS-e",
|
|
3876
|
+
icon: "FileText",
|
|
3877
|
+
actions: /* @__PURE__ */ jsxRuntime.jsx(saas.PermissionGate, { feature: "financial", action: "create", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "sm", onClick: () => setCreating(true), children: [
|
|
3878
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
3879
|
+
" Nova nota"
|
|
3880
|
+
] }) })
|
|
3881
|
+
}
|
|
3882
|
+
),
|
|
3883
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 rounded-md border border-amber-300/50 bg-amber-50 px-3 py-2 text-xs text-amber-800 dark:bg-amber-950/30 dark:text-amber-300", children: 'Emiss\xE3o fiscal (SEFAZ) ainda n\xE3o integrada \u2014 "Emitir" atribui n\xFAmero/s\xE9rie localmente para acompanhamento. A integra\xE7\xE3o com o emissor entra sem mudar esta tela.' }),
|
|
3884
|
+
creating && /* @__PURE__ */ jsxRuntime.jsx(CreateForm, { onCancel: () => setCreating(false), onCreate: create }),
|
|
3885
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 space-y-2", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 rounded-card border border-border bg-card p-3", children: [
|
|
3886
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-9 w-9 shrink-0 rounded-full" }),
|
|
3887
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [
|
|
3888
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-3.5 w-40 rounded" }),
|
|
3889
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-3 w-56 rounded" })
|
|
3890
|
+
] }),
|
|
3891
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-8 w-20 shrink-0 rounded-md" })
|
|
3892
|
+
] }, i)) }) : notas.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 rounded-card border border-dashed border-border p-8 text-center", children: [
|
|
3893
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "mx-auto h-8 w-8 text-muted-foreground" }),
|
|
3894
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm font-medium text-foreground", children: "Nenhuma nota fiscal ainda" }),
|
|
3895
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Crie uma nota a partir de um pedido ou manualmente." })
|
|
3896
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 space-y-2", children: notas.map((n) => {
|
|
3897
|
+
const meta = STATUS_META[n.status];
|
|
3898
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 rounded-card border border-border bg-card p-3", children: [
|
|
3899
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "h-4 w-4 text-primary" }) }),
|
|
3900
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3901
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3902
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium text-foreground", children: [
|
|
3903
|
+
MODEL_LABEL[n.model],
|
|
3904
|
+
" ",
|
|
3905
|
+
n.number ? `#${n.number}` : "(rascunho)"
|
|
3906
|
+
] }),
|
|
3907
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${meta.className}`, children: meta.label })
|
|
3908
|
+
] }),
|
|
3909
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "truncate text-xs text-muted-foreground", children: [
|
|
3910
|
+
n.contactName || "Sem destinat\xE1rio",
|
|
3911
|
+
" \xB7 ",
|
|
3912
|
+
saas.formatCurrency(n.totalAmount, currency),
|
|
3913
|
+
n.issuedAt ? ` \xB7 ${n.issuedAt.slice(0, 10)}` : ""
|
|
3914
|
+
] })
|
|
3915
|
+
] }),
|
|
3916
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
3917
|
+
n.danfeUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "sm", variant: "ghost", onClick: () => window.open(n.danfeUrl, "_blank", "noopener,noreferrer"), title: "DANFE", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileCheck2, { className: "h-4 w-4" }) }),
|
|
3918
|
+
n.xmlUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "sm", variant: "ghost", onClick: () => window.open(n.xmlUrl, "_blank", "noopener,noreferrer"), title: "XML", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-4 w-4" }) }),
|
|
3919
|
+
n.status === "draft" && /* @__PURE__ */ jsxRuntime.jsx(saas.PermissionGate, { feature: "financial", action: "edit", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "sm", disabled: busy === n.id, onClick: () => issue(n.id), children: [
|
|
3920
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
3921
|
+
" Emitir"
|
|
3922
|
+
] }) }),
|
|
3923
|
+
n.status === "issued" && /* @__PURE__ */ jsxRuntime.jsx(saas.PermissionGate, { feature: "financial", action: "edit", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "sm", variant: "ghost", disabled: busy === n.id, onClick: () => cancel(n.id), title: "Cancelar", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Ban, { className: "h-4 w-4 text-red-500" }) }) })
|
|
3924
|
+
] })
|
|
3925
|
+
] }, n.id);
|
|
3926
|
+
}) })
|
|
3927
|
+
] });
|
|
3928
|
+
}
|
|
3929
|
+
function CreateForm({ onCancel, onCreate }) {
|
|
3930
|
+
const [model, setModel] = React18.useState("nfe");
|
|
3931
|
+
const [contactName, setContactName] = React18.useState("");
|
|
3932
|
+
const [total, setTotal] = React18.useState("");
|
|
3933
|
+
const [observations, setObservations] = React18.useState("");
|
|
3934
|
+
const models = ["nfe", "nfce", "nfse"];
|
|
3935
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 rounded-card border border-border bg-card p-4", children: [
|
|
3936
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-3 text-sm font-semibold text-foreground", children: "Nova nota fiscal" }),
|
|
3937
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3 flex flex-wrap gap-2", children: models.map((m) => /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setModel(m), className: `rounded-full px-3 py-1 text-xs font-medium ${m === model ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: MODEL_LABEL[m] }, m)) }),
|
|
3938
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
|
|
3939
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: contactName, placeholder: "Destinat\xE1rio", onChange: (e) => setContactName(e.target.value), className: "flex-1" }),
|
|
3940
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: total, placeholder: "Valor total", inputMode: "decimal", onChange: (e) => setTotal(e.target.value), className: "sm:w-40" })
|
|
3941
|
+
] }),
|
|
3942
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: observations, placeholder: "Observa\xE7\xF5es (opcional)", onChange: (e) => setObservations(e.target.value), className: "mt-2" }),
|
|
3943
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex gap-2", children: [
|
|
3944
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3945
|
+
ui.Button,
|
|
3946
|
+
{
|
|
3947
|
+
size: "sm",
|
|
3948
|
+
disabled: !total || Number.isNaN(Number(total)),
|
|
3949
|
+
onClick: () => onCreate({ model, contactName: contactName || void 0, totalAmount: Number(total) || 0, observations: observations || void 0 }),
|
|
3950
|
+
children: "Criar rascunho"
|
|
3951
|
+
}
|
|
3952
|
+
),
|
|
3953
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "sm", variant: "outline", onClick: onCancel, children: "Cancelar" })
|
|
3954
|
+
] })
|
|
3955
|
+
] });
|
|
3956
|
+
}
|
|
3800
3957
|
var FINANCIAL_DEFAULTS = {
|
|
3801
3958
|
autoInstallments: true,
|
|
3802
3959
|
requireDoc: false,
|
|
@@ -3833,7 +3990,7 @@ var STEPS = [
|
|
|
3833
3990
|
];
|
|
3834
3991
|
function FinancialOnboarding({ onComplete }) {
|
|
3835
3992
|
const t2 = core.useTranslation();
|
|
3836
|
-
const [step, setStep] =
|
|
3993
|
+
const [step, setStep] = React18.useState(0);
|
|
3837
3994
|
const current = STEPS[step];
|
|
3838
3995
|
const isLast = step === STEPS.length - 1;
|
|
3839
3996
|
const Icon = current.icon;
|
|
@@ -3940,6 +4097,15 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
3940
4097
|
onClick: () => navigate("reconciliation")
|
|
3941
4098
|
});
|
|
3942
4099
|
}
|
|
4100
|
+
if (config.modules.notasFiscais) {
|
|
4101
|
+
items.push({
|
|
4102
|
+
id: "notas-fiscais",
|
|
4103
|
+
label: t2("financial.nav.notasFiscais"),
|
|
4104
|
+
icon: "FileText",
|
|
4105
|
+
active: activeView === "notas-fiscais",
|
|
4106
|
+
onClick: () => navigate("notas-fiscais")
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
3943
4109
|
if (config.modules.commissions) {
|
|
3944
4110
|
items.push({
|
|
3945
4111
|
id: "commissions",
|
|
@@ -3980,16 +4146,17 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
3980
4146
|
reconciliation: 0,
|
|
3981
4147
|
commissions: 0,
|
|
3982
4148
|
cards: 0,
|
|
4149
|
+
"notas-fiscais": 0,
|
|
3983
4150
|
settings: 1
|
|
3984
4151
|
});
|
|
3985
|
-
const [onboardingComplete, setOnboardingComplete] =
|
|
4152
|
+
const [onboardingComplete, setOnboardingComplete] = React18.useState(() => {
|
|
3986
4153
|
try {
|
|
3987
4154
|
return localStorage.getItem("saas-core:financial-onboarded") === "true";
|
|
3988
4155
|
} catch {
|
|
3989
4156
|
return false;
|
|
3990
4157
|
}
|
|
3991
4158
|
});
|
|
3992
|
-
|
|
4159
|
+
React18.useEffect(() => {
|
|
3993
4160
|
if (onboardingComplete) return;
|
|
3994
4161
|
let cancelled = false;
|
|
3995
4162
|
void provider.getMovements({ pageSize: 1 }).then((res) => {
|
|
@@ -4006,16 +4173,16 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
4006
4173
|
const intent = parseIntent(view);
|
|
4007
4174
|
const isSettings = view === "settings";
|
|
4008
4175
|
const nav = saas.useModuleNavAccess(buildNav(config, view, navigate, t2));
|
|
4009
|
-
const [quickAddOpen, setQuickAddOpen] =
|
|
4010
|
-
const [quickAddType, setQuickAddType] =
|
|
4011
|
-
const [quickAddReceipt, setQuickAddReceipt] =
|
|
4012
|
-
const openQuickAdd2 =
|
|
4176
|
+
const [quickAddOpen, setQuickAddOpen] = React18.useState(false);
|
|
4177
|
+
const [quickAddType, setQuickAddType] = React18.useState("expense");
|
|
4178
|
+
const [quickAddReceipt, setQuickAddReceipt] = React18.useState(false);
|
|
4179
|
+
const openQuickAdd2 = React18.useCallback((type = "expense") => {
|
|
4013
4180
|
setQuickAddType(type);
|
|
4014
4181
|
setQuickAddReceipt(false);
|
|
4015
4182
|
setQuickAddOpen(true);
|
|
4016
4183
|
}, []);
|
|
4017
4184
|
const pendingQuickAdd = usePendingQuickAdd();
|
|
4018
|
-
|
|
4185
|
+
React18.useEffect(() => {
|
|
4019
4186
|
if (!pendingQuickAdd) return;
|
|
4020
4187
|
const req = consumeQuickAdd();
|
|
4021
4188
|
if (!req) return;
|
|
@@ -4024,7 +4191,7 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
4024
4191
|
setQuickAddReceipt(req.receipt);
|
|
4025
4192
|
setQuickAddOpen(true);
|
|
4026
4193
|
}, [pendingQuickAdd]);
|
|
4027
|
-
const quickActions =
|
|
4194
|
+
const quickActions = React18.useMemo(() => {
|
|
4028
4195
|
if (config.quickAdd) return [];
|
|
4029
4196
|
if (!canCreate) return [];
|
|
4030
4197
|
const actions = [];
|
|
@@ -4096,6 +4263,8 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
4096
4263
|
return /* @__PURE__ */ jsxRuntime.jsx(CommissionsView, {});
|
|
4097
4264
|
case "cards":
|
|
4098
4265
|
return /* @__PURE__ */ jsxRuntime.jsx(CardsView, {});
|
|
4266
|
+
case "notas-fiscais":
|
|
4267
|
+
return /* @__PURE__ */ jsxRuntime.jsx(NotasFiscaisView, {});
|
|
4099
4268
|
default:
|
|
4100
4269
|
return /* @__PURE__ */ jsxRuntime.jsx(SummaryView, {});
|
|
4101
4270
|
}
|
|
@@ -4158,7 +4327,7 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
4158
4327
|
}
|
|
4159
4328
|
function useEnsureSummary() {
|
|
4160
4329
|
const fetchSummary = useFinancialStore((s) => s.fetchSummary);
|
|
4161
|
-
|
|
4330
|
+
React18.useEffect(() => {
|
|
4162
4331
|
void fetchSummary();
|
|
4163
4332
|
}, []);
|
|
4164
4333
|
}
|
|
@@ -4282,7 +4451,7 @@ function RecentTransactions() {
|
|
|
4282
4451
|
const { currency } = useFinancialConfig();
|
|
4283
4452
|
const invoices = useFinancialStore((s) => s.invoices);
|
|
4284
4453
|
const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
|
|
4285
|
-
|
|
4454
|
+
React18.useEffect(() => {
|
|
4286
4455
|
void fetchInvoices({ pageSize: 5 });
|
|
4287
4456
|
}, []);
|
|
4288
4457
|
const columns = [
|
|
@@ -4318,7 +4487,7 @@ function BalanceHero() {
|
|
|
4318
4487
|
const summary = useFinancialStore((s) => s.summary);
|
|
4319
4488
|
const navigate = ui.useDashboardNavigate();
|
|
4320
4489
|
useEnsureSummary();
|
|
4321
|
-
const [hidden, setHidden] =
|
|
4490
|
+
const [hidden, setHidden] = React18__namespace.default.useState(false);
|
|
4322
4491
|
const money = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : saas.formatCurrency(v ?? 0, currency);
|
|
4323
4492
|
const inflow = summary?.monthlyInflow ?? 0;
|
|
4324
4493
|
const outflow = summary?.monthlyOutflow ?? 0;
|
|
@@ -4391,8 +4560,8 @@ function categorizeSpend(notes) {
|
|
|
4391
4560
|
function SpendByCategoryChart() {
|
|
4392
4561
|
const t2 = core.useTranslation();
|
|
4393
4562
|
const provider = useFinancialProvider();
|
|
4394
|
-
const [rows, setRows] =
|
|
4395
|
-
|
|
4563
|
+
const [rows, setRows] = React18__namespace.default.useState([]);
|
|
4564
|
+
React18.useEffect(() => {
|
|
4396
4565
|
let alive = true;
|
|
4397
4566
|
void (async () => {
|
|
4398
4567
|
const res = await provider.getMovements({ status: "paid", direction: "debit" });
|
|
@@ -4434,8 +4603,8 @@ function UpcomingBills() {
|
|
|
4434
4603
|
const { currency } = useFinancialConfig();
|
|
4435
4604
|
const provider = useFinancialProvider();
|
|
4436
4605
|
const dueLabel = useDueLabel();
|
|
4437
|
-
const [bills, setBills] =
|
|
4438
|
-
|
|
4606
|
+
const [bills, setBills] = React18__namespace.default.useState([]);
|
|
4607
|
+
React18.useEffect(() => {
|
|
4439
4608
|
let alive = true;
|
|
4440
4609
|
void (async () => {
|
|
4441
4610
|
const pending2 = await provider.getMovements({ status: "pending" });
|
|
@@ -4464,7 +4633,7 @@ function CreditCards() {
|
|
|
4464
4633
|
const { currency } = useFinancialConfig();
|
|
4465
4634
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
4466
4635
|
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
4467
|
-
|
|
4636
|
+
React18.useEffect(() => {
|
|
4468
4637
|
void fetchBankAccounts();
|
|
4469
4638
|
}, []);
|
|
4470
4639
|
const cards = bankAccounts.filter((a) => a.accountType === "credit_card");
|
|
@@ -4609,7 +4778,8 @@ function createStore(options) {
|
|
|
4609
4778
|
paymentMethodTypes: defaultPMTypes,
|
|
4610
4779
|
chartOfAccounts: [],
|
|
4611
4780
|
costCenters: [],
|
|
4612
|
-
cardTransactions: []
|
|
4781
|
+
cardTransactions: [],
|
|
4782
|
+
notas: []
|
|
4613
4783
|
};
|
|
4614
4784
|
}
|
|
4615
4785
|
function createMockFinancialProvider(options) {
|
|
@@ -5158,6 +5328,66 @@ function createMockFinancialProvider(options) {
|
|
|
5158
5328
|
internal.updatedAt = now();
|
|
5159
5329
|
}
|
|
5160
5330
|
}
|
|
5331
|
+
},
|
|
5332
|
+
// --- Notas fiscais (NF-e / NFC-e / NFS-e) ---
|
|
5333
|
+
async getNotas(query) {
|
|
5334
|
+
let list = [...store.notas].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
5335
|
+
if (query.status) list = list.filter((n) => matchesStatus(n.status, query.status));
|
|
5336
|
+
if (query.model) list = list.filter((n) => n.model === query.model);
|
|
5337
|
+
if (query.orderId) list = list.filter((n) => n.orderId === query.orderId);
|
|
5338
|
+
if (query.search) {
|
|
5339
|
+
const q = query.search.toLowerCase();
|
|
5340
|
+
list = list.filter((n) => (n.number ?? "").toLowerCase().includes(q) || (n.contactName ?? "").toLowerCase().includes(q));
|
|
5341
|
+
}
|
|
5342
|
+
return paginate(list, query.page, query.pageSize);
|
|
5343
|
+
},
|
|
5344
|
+
async getNotaById(id) {
|
|
5345
|
+
return store.notas.find((n) => n.id === id) ?? null;
|
|
5346
|
+
},
|
|
5347
|
+
async createNota(input) {
|
|
5348
|
+
const nota = {
|
|
5349
|
+
id: uid(),
|
|
5350
|
+
tenantId: "mock-tenant",
|
|
5351
|
+
model: input.model ?? "nfe",
|
|
5352
|
+
status: "draft",
|
|
5353
|
+
number: null,
|
|
5354
|
+
series: input.series ?? null,
|
|
5355
|
+
orderId: input.orderId,
|
|
5356
|
+
contactId: input.contactId,
|
|
5357
|
+
contactName: input.contactName,
|
|
5358
|
+
totalAmount: input.totalAmount,
|
|
5359
|
+
observations: input.observations,
|
|
5360
|
+
createdAt: now(),
|
|
5361
|
+
updatedAt: now()
|
|
5362
|
+
};
|
|
5363
|
+
store.notas.unshift(nota);
|
|
5364
|
+
return nota;
|
|
5365
|
+
},
|
|
5366
|
+
async updateNota(id, patch) {
|
|
5367
|
+
const nota = store.notas.find((n) => n.id === id);
|
|
5368
|
+
if (!nota) throw new Error("Nota fiscal not found");
|
|
5369
|
+
Object.assign(nota, patch, { updatedAt: now() });
|
|
5370
|
+
return nota;
|
|
5371
|
+
},
|
|
5372
|
+
async issueNota(id) {
|
|
5373
|
+
const nota = store.notas.find((n) => n.id === id);
|
|
5374
|
+
if (!nota) throw new Error("Nota fiscal not found");
|
|
5375
|
+
const issuedCount = store.notas.filter((n) => n.status === "issued").length;
|
|
5376
|
+
nota.status = "issued";
|
|
5377
|
+
nota.number = String(issuedCount + 1).padStart(6, "0");
|
|
5378
|
+
nota.series = nota.series ?? "1";
|
|
5379
|
+
nota.issuedAt = now();
|
|
5380
|
+
nota.emissor = "manual";
|
|
5381
|
+
nota.updatedAt = now();
|
|
5382
|
+
return nota;
|
|
5383
|
+
},
|
|
5384
|
+
async cancelNota(id, reason) {
|
|
5385
|
+
const nota = store.notas.find((n) => n.id === id);
|
|
5386
|
+
if (!nota) throw new Error("Nota fiscal not found");
|
|
5387
|
+
nota.status = "cancelled";
|
|
5388
|
+
nota.errorMessage = reason;
|
|
5389
|
+
nota.updatedAt = now();
|
|
5390
|
+
return nota;
|
|
5161
5391
|
}
|
|
5162
5392
|
};
|
|
5163
5393
|
return provider;
|
|
@@ -5172,7 +5402,8 @@ var T = {
|
|
|
5172
5402
|
costCenters: "plg_financial_cost_centers",
|
|
5173
5403
|
cashRegisterSessions: "plg_financial_cash_register_sessions",
|
|
5174
5404
|
paymentMethods: "plg_financial_payment_methods",
|
|
5175
|
-
paymentMethodTypes: "plg_financial_payment_method_types"
|
|
5405
|
+
paymentMethodTypes: "plg_financial_payment_method_types",
|
|
5406
|
+
notas: "plg_financial_nfe"
|
|
5176
5407
|
};
|
|
5177
5408
|
|
|
5178
5409
|
// src/data/supabase.ts
|
|
@@ -5483,6 +5714,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5483
5714
|
}
|
|
5484
5715
|
if (query.bankAccountId) qb = qb.eq("bank_account_id", query.bankAccountId);
|
|
5485
5716
|
if (query.cashSessionId) qb = qb.eq("cash_session_id", query.cashSessionId);
|
|
5717
|
+
if (query.invoiceId) qb = qb.eq("invoice_id", query.invoiceId);
|
|
5486
5718
|
if (query.dateRange) qb = qb.gte("due_date", query.dateRange.from).lte("due_date", query.dateRange.to);
|
|
5487
5719
|
const page = query.page ?? 1;
|
|
5488
5720
|
const pageSize = query.pageSize ?? 50;
|
|
@@ -5906,6 +6138,75 @@ function createSupabaseFinancialProvider() {
|
|
|
5906
6138
|
if (line?.matched_movement_id) {
|
|
5907
6139
|
await pub.from(T.movements).update({ reconciled_at: null }).eq("id", line.matched_movement_id);
|
|
5908
6140
|
}
|
|
6141
|
+
},
|
|
6142
|
+
// --- Notas fiscais (NF-e / NFC-e / NFS-e) ---
|
|
6143
|
+
async getNotas(query) {
|
|
6144
|
+
const { pub } = getClients();
|
|
6145
|
+
let qb = pub.from(T.notas).select("*", { count: "exact" });
|
|
6146
|
+
if (query.status) {
|
|
6147
|
+
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
6148
|
+
qb = qb.in("status", statuses);
|
|
6149
|
+
}
|
|
6150
|
+
if (query.model) qb = qb.eq("model", query.model);
|
|
6151
|
+
if (query.orderId) qb = qb.eq("order_id", query.orderId);
|
|
6152
|
+
if (query.search) qb = qb.or(`number.ilike.%${query.search}%,contact_name.ilike.%${query.search}%`);
|
|
6153
|
+
const page = query.page ?? 1;
|
|
6154
|
+
const pageSize = query.pageSize ?? 50;
|
|
6155
|
+
qb = qb.range((page - 1) * pageSize, page * pageSize - 1).order("created_at", { ascending: false });
|
|
6156
|
+
const { data, count } = await qb;
|
|
6157
|
+
return { data: (data ?? []).map((r) => snakeToCamel(r)), total: count ?? 0 };
|
|
6158
|
+
},
|
|
6159
|
+
async getNotaById(id) {
|
|
6160
|
+
const { pub } = getClients();
|
|
6161
|
+
const { data } = await pub.from(T.notas).select("*").eq("id", id).maybeSingle();
|
|
6162
|
+
return data ? snakeToCamel(data) : null;
|
|
6163
|
+
},
|
|
6164
|
+
async createNota(input) {
|
|
6165
|
+
const { pub } = getClients();
|
|
6166
|
+
const row = {
|
|
6167
|
+
tenant_id: getTenantId(),
|
|
6168
|
+
model: input.model ?? "nfe",
|
|
6169
|
+
status: "draft",
|
|
6170
|
+
series: input.series ?? null,
|
|
6171
|
+
order_id: input.orderId ?? null,
|
|
6172
|
+
contact_id: input.contactId ?? null,
|
|
6173
|
+
contact_name: input.contactName ?? null,
|
|
6174
|
+
total_amount: input.totalAmount,
|
|
6175
|
+
observations: input.observations ?? null
|
|
6176
|
+
};
|
|
6177
|
+
const { data, error } = await pub.from(T.notas).insert(row).select().single();
|
|
6178
|
+
if (error) throw error;
|
|
6179
|
+
return snakeToCamel(data);
|
|
6180
|
+
},
|
|
6181
|
+
async updateNota(id, patch) {
|
|
6182
|
+
const { pub } = getClients();
|
|
6183
|
+
const { data, error } = await pub.from(T.notas).update(camelToSnake(patch)).eq("id", id).select().single();
|
|
6184
|
+
if (error) throw error;
|
|
6185
|
+
return snakeToCamel(data);
|
|
6186
|
+
},
|
|
6187
|
+
// Emission seam: no SEFAZ integration yet, so assign a número/série locally
|
|
6188
|
+
// and mark it issued. An external emissor replaces this body (or overrides
|
|
6189
|
+
// issueNota on a wrapping provider) without any UI change.
|
|
6190
|
+
async issueNota(id) {
|
|
6191
|
+
const { pub } = getClients();
|
|
6192
|
+
const { data: current } = await pub.from(T.notas).select("series").eq("id", id).single();
|
|
6193
|
+
const { count } = await pub.from(T.notas).select("id", { count: "exact", head: true }).eq("status", "issued");
|
|
6194
|
+
const patch = {
|
|
6195
|
+
status: "issued",
|
|
6196
|
+
number: String((count ?? 0) + 1).padStart(6, "0"),
|
|
6197
|
+
series: current?.series ?? "1",
|
|
6198
|
+
issued_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6199
|
+
emissor: "manual"
|
|
6200
|
+
};
|
|
6201
|
+
const { data, error } = await pub.from(T.notas).update(patch).eq("id", id).select().single();
|
|
6202
|
+
if (error) throw error;
|
|
6203
|
+
return snakeToCamel(data);
|
|
6204
|
+
},
|
|
6205
|
+
async cancelNota(id, reason) {
|
|
6206
|
+
const { pub } = getClients();
|
|
6207
|
+
const { data, error } = await pub.from(T.notas).update({ status: "cancelled", error_message: reason ?? null }).eq("id", id).select().single();
|
|
6208
|
+
if (error) throw error;
|
|
6209
|
+
return snakeToCamel(data);
|
|
5909
6210
|
}
|
|
5910
6211
|
};
|
|
5911
6212
|
return provider;
|
|
@@ -6533,6 +6834,7 @@ var en = {
|
|
|
6533
6834
|
"financial.nav.payables": "Accounts Payable",
|
|
6534
6835
|
"financial.nav.receivables": "Accounts Receivable",
|
|
6535
6836
|
"financial.nav.reconciliation": "Reconciliation",
|
|
6837
|
+
"financial.nav.notasFiscais": "Fiscal Invoices",
|
|
6536
6838
|
"financial.nav.recurringExpenses": "Recurring Expenses",
|
|
6537
6839
|
"financial.nav.rules": "Rules",
|
|
6538
6840
|
"financial.nav.statements": "Statements",
|
|
@@ -7026,6 +7328,7 @@ var ptBR = {
|
|
|
7026
7328
|
"financial.nav.payables": "Contas a Pagar",
|
|
7027
7329
|
"financial.nav.receivables": "Contas a Receber",
|
|
7028
7330
|
"financial.nav.reconciliation": "Concilia\xE7\xE3o",
|
|
7331
|
+
"financial.nav.notasFiscais": "Notas Fiscais",
|
|
7029
7332
|
"financial.nav.recurringExpenses": "Despesas Recorrentes",
|
|
7030
7333
|
"financial.nav.rules": "Regras",
|
|
7031
7334
|
"financial.nav.statements": "Extratos",
|
|
@@ -7398,9 +7701,9 @@ var STATUS_STYLE = {
|
|
|
7398
7701
|
function PersonStatementWidget({ person, provider, config }) {
|
|
7399
7702
|
const t2 = core.useTranslation();
|
|
7400
7703
|
const currency = config.currency;
|
|
7401
|
-
const [invoices, setInvoices] =
|
|
7402
|
-
const [loading, setLoading] =
|
|
7403
|
-
|
|
7704
|
+
const [invoices, setInvoices] = React18.useState([]);
|
|
7705
|
+
const [loading, setLoading] = React18.useState(true);
|
|
7706
|
+
React18.useEffect(() => {
|
|
7404
7707
|
let alive = true;
|
|
7405
7708
|
if (!person?.id) {
|
|
7406
7709
|
setLoading(false);
|
|
@@ -7420,7 +7723,7 @@ function PersonStatementWidget({ person, provider, config }) {
|
|
|
7420
7723
|
};
|
|
7421
7724
|
}, [person?.id, provider]);
|
|
7422
7725
|
const fmt = (n) => saas.formatCurrency(n, currency);
|
|
7423
|
-
const totals =
|
|
7726
|
+
const totals = React18.useMemo(() => {
|
|
7424
7727
|
const active = invoices.filter((i) => i.status !== "cancelled");
|
|
7425
7728
|
const billed = active.reduce((s, i) => s + (i.totalAmount ?? 0), 0);
|
|
7426
7729
|
const paid = active.reduce((s, i) => s + (i.paidAmount ?? 0), 0);
|
|
@@ -7430,7 +7733,7 @@ function PersonStatementWidget({ person, provider, config }) {
|
|
|
7430
7733
|
const base = inv.direction === "credit" ? "receivables" : "payables";
|
|
7431
7734
|
window.location.hash = `/financial/${base}/detail/${inv.id}`;
|
|
7432
7735
|
};
|
|
7433
|
-
const columns =
|
|
7736
|
+
const columns = React18.useMemo(() => [
|
|
7434
7737
|
{
|
|
7435
7738
|
id: "date",
|
|
7436
7739
|
header: t2("financial.invoice.columnDate"),
|
|
@@ -7510,7 +7813,8 @@ var DEFAULT_LABELS = {
|
|
|
7510
7813
|
commissionsRules: "Rules",
|
|
7511
7814
|
cards: "Cards",
|
|
7512
7815
|
cardsOverview: "Overview",
|
|
7513
|
-
cardsReconciliation: "Reconciliation"
|
|
7816
|
+
cardsReconciliation: "Reconciliation",
|
|
7817
|
+
notasFiscais: "Fiscal Invoices"
|
|
7514
7818
|
};
|
|
7515
7819
|
var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
|
|
7516
7820
|
var FINANCIAL_NAV_FEATURES = {
|
|
@@ -7537,7 +7841,9 @@ function resolveConfig(options) {
|
|
|
7537
7841
|
commissions: options?.modules?.commissions !== false,
|
|
7538
7842
|
cards: options?.modules?.cards !== false,
|
|
7539
7843
|
// Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
|
|
7540
|
-
reconciliation: options?.modules?.reconciliation === true
|
|
7844
|
+
reconciliation: options?.modules?.reconciliation === true,
|
|
7845
|
+
// Opt-in: only shown when an app explicitly enables fiscal-invoice tracking.
|
|
7846
|
+
notasFiscais: options?.modules?.notasFiscais === true
|
|
7541
7847
|
},
|
|
7542
7848
|
// Opt-in: only render the plugin's own quick-add buttons/FAB when the host asks.
|
|
7543
7849
|
quickAdd: options?.quickAdd === true,
|
|
@@ -7563,9 +7869,9 @@ function createFinancialPlugin(options) {
|
|
|
7563
7869
|
const store = createFinancialStore(provider);
|
|
7564
7870
|
const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
|
|
7565
7871
|
const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
|
|
7566
|
-
const PersonStatementBound = (props) =>
|
|
7872
|
+
const PersonStatementBound = (props) => React18__namespace.default.createElement(PersonStatementWidget, { person: props.item, provider, config });
|
|
7567
7873
|
PersonStatementBound.displayName = "FinancialPersonStatement";
|
|
7568
|
-
const PageComponent = () =>
|
|
7874
|
+
const PageComponent = () => React18__namespace.default.createElement(FinancialPage, { config, provider, store, registries });
|
|
7569
7875
|
return {
|
|
7570
7876
|
id: "financial",
|
|
7571
7877
|
name: config.labels.pageTitle,
|
|
@@ -7769,10 +8075,10 @@ function createFinancialPlugin(options) {
|
|
|
7769
8075
|
label: "Financial",
|
|
7770
8076
|
icon: "DollarSign",
|
|
7771
8077
|
component: (() => {
|
|
7772
|
-
const FinancialSettingsTab = () =>
|
|
8078
|
+
const FinancialSettingsTab = () => React18__namespace.default.createElement(saas.PluginSettingsPanel, {
|
|
7773
8079
|
title: "Financial Settings",
|
|
7774
8080
|
subtitle: "Payment methods, accounts, and configurations",
|
|
7775
|
-
generalSettings:
|
|
8081
|
+
generalSettings: React18__namespace.default.createElement(FinancialGeneralSettings),
|
|
7776
8082
|
registries,
|
|
7777
8083
|
hostPluginId: "financial",
|
|
7778
8084
|
routeBase: "/settings/financial"
|