@fayz-ai/plugin-financial 0.19.0 → 0.20.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/dist/components/InstallmentRow.d.ts +42 -0
- package/dist/components/InstallmentRow.d.ts.map +1 -0
- package/dist/components/InstallmentSource.d.ts.map +1 -1
- package/dist/components/InstallmentStatusBadge.d.ts +6 -0
- package/dist/components/InstallmentStatusBadge.d.ts.map +1 -0
- package/dist/components/InvoiceAttachmentsModal.d.ts +12 -0
- package/dist/components/InvoiceAttachmentsModal.d.ts.map +1 -0
- package/dist/components/PaymentFields.d.ts.map +1 -1
- package/dist/components/PaymentIcons.d.ts +12 -0
- package/dist/components/PaymentIcons.d.ts.map +1 -0
- package/dist/components/PaymentReceiptModal.d.ts.map +1 -1
- package/dist/components/ReconcileFlow.d.ts +6 -1
- package/dist/components/ReconcileFlow.d.ts.map +1 -1
- package/dist/components/UnitField.d.ts +22 -0
- package/dist/components/UnitField.d.ts.map +1 -0
- package/dist/data/match-statement.d.ts +3 -1
- package/dist/data/match-statement.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/types.d.ts +12 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.js +1803 -846
- package/dist/index.js.map +1 -1
- package/dist/lib/routes.d.ts +5 -0
- package/dist/lib/routes.d.ts.map +1 -0
- package/dist/lib/settlements.d.ts +6 -0
- package/dist/lib/settlements.d.ts.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 +3 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/FinancialPage.d.ts.map +1 -1
- package/dist/views/GapScreens.d.ts +0 -2
- package/dist/views/GapScreens.d.ts.map +1 -1
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/ReconciliationView.d.ts.map +1 -1
- package/dist/views/RecurringBillsView.d.ts.map +1 -1
- package/dist/views/cardChip.d.ts +1 -1
- package/dist/views/cardChip.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/components/AttachmentViewer.d.ts +0 -8
- package/dist/components/AttachmentViewer.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import
|
|
3
|
-
import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, getSupabaseClientOptional, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode,
|
|
4
|
-
import { INLINE_LINK_TONE, SearchSelect, ModalTask, toast, Button, useInModalTaskHost, useSaveBar,
|
|
1
|
+
import * as React25 from 'react';
|
|
2
|
+
import React25__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
3
|
+
import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, getSupabaseClientOptional, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode, useDataChanged, countByTenant, tl, resolveDataProvider, useActiveUnitId, getActiveTenantId, emitDataChanged, exportCSV } from '@fayz-ai/core';
|
|
4
|
+
import { INLINE_LINK_TONE, SearchSelect, ModalTask, toast, Button, useInModalTaskHost, useSaveBar, tourAnchor, DatePicker, SubpageHeader, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, Card, CardContent, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MultiSelect, PageTransition, ModulePage, Badge, TooltipProvider, ConfirmDialog, CurrencyInput, Input, PageHeaderActions, ChoiceSelect, Hint, ModalFooter, PageTabs, Tooltip, TooltipTrigger, TooltipContent, Checkbox, Popover, PopoverTrigger, PopoverContent, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, SegmentedControl, RequiredFilter, FilterMenuRow, ChoiceList, ModalFlow, Switch, WizardSteps, KpiCard, ChartWidget, CardHeader, CardTitle, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
|
|
5
5
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
-
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useUnitFilter, useLimitGuard, usePermissionOptional, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, useChatStore,
|
|
7
|
-
import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban,
|
|
6
|
+
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useUnitFilter, useLimitGuard, usePermissionOptional, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, adminNavigateTo, OrderFormAddItemStep, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, useChatStore, revealPage, useAuth, GetStarted, resolveEntityRoute, PermissionGate, createOneShotAsk, askAI, requestChatUpload, toInlineFile, shrinkForVision } from '@fayz-ai/admin';
|
|
7
|
+
import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Receipt, Gift, Briefcase, Package, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, Ticket, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Undo2, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, ChevronRight, Building2, Image, UploadCloud, Calendar, User, Unlink, ChevronDown, Eye, Inbox, Clock, ClipboardCheck, BookOpen, Info, FolderOpen, Folder, RotateCcw, Settings, ArrowLeft, ChevronLeft, Calculator, Square, Play, ChevronUp, Printer, CalendarClock, BadgeCheck, Target, ArrowRight, Users, RefreshCw, Download, Camera, Sparkles, Link2, FileSearch, CheckCheck, Pause, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, FileDown, ArrowUpRight, ArrowDownRight, EyeOff } from 'lucide-react';
|
|
8
8
|
import { createPortal } from 'react-dom';
|
|
9
9
|
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
10
10
|
import { useStore } from 'zustand';
|
|
@@ -105,22 +105,6 @@ function PettyCashView() {
|
|
|
105
105
|
}
|
|
106
106
|
);
|
|
107
107
|
}
|
|
108
|
-
function DigitizeExpenseView() {
|
|
109
|
-
return /* @__PURE__ */ jsx(
|
|
110
|
-
ComingSoon,
|
|
111
|
-
{
|
|
112
|
-
title: "Digitalizar Despesa",
|
|
113
|
-
summary: "Fotografar o boleto ou a nota e deixar a m\xE1quina ler valor, vencimento e fornecedor \u2014 em vez de digitar os quatro campos de novo.",
|
|
114
|
-
v1Path: "Financeiro \u2192 Contas a Pagar \u2192 Digitalizar Despesa",
|
|
115
|
-
measured: "1.436 anexos de conta no V1, 55 da Artorius. A Laissa anexa comprovante 25 vezes por m\xEAs \u2014 mas digita os campos \xE0 m\xE3o todas elas.",
|
|
116
|
-
missing: [
|
|
117
|
-
"Ler o comprovante e PR\xC9-PREENCHER o formul\xE1rio, deixando o usu\xE1rio confirmar \u2014 n\xE3o gravar sozinho",
|
|
118
|
-
"Guardar o arquivo junto do t\xEDtulo, que \xE9 o que a contabilidade pede depois",
|
|
119
|
-
"A Entrada de Notas j\xE1 faz isto para XML de NF-e; aqui \xE9 o mesmo trabalho para o que chega em papel"
|
|
120
|
-
]
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
108
|
function ImportPayrollView() {
|
|
125
109
|
return /* @__PURE__ */ jsx(
|
|
126
110
|
ComingSoon,
|
|
@@ -924,9 +908,9 @@ function startReceiptCapture(labels) {
|
|
|
924
908
|
});
|
|
925
909
|
}
|
|
926
910
|
function ReceiptAgentSurface({ onOpenDraft }) {
|
|
927
|
-
const live =
|
|
911
|
+
const live = React25.useRef(onOpenDraft);
|
|
928
912
|
live.current = onOpenDraft;
|
|
929
|
-
const contribution =
|
|
913
|
+
const contribution = React25.useMemo(() => ({
|
|
930
914
|
id: "financial.payables.receipt",
|
|
931
915
|
pluginId: "financial",
|
|
932
916
|
role: "finance",
|
|
@@ -1277,22 +1261,22 @@ function searchBrBanks(query) {
|
|
|
1277
1261
|
(b) => qd && qd === q && (b.code.startsWith(qd) || b.code.replace(/^0+/, "").startsWith(qd)) || norm(b.name).includes(q) || b.aliases?.some((a) => norm(a).includes(q))
|
|
1278
1262
|
);
|
|
1279
1263
|
}
|
|
1280
|
-
function BankLogo({ bank, size = 20 }) {
|
|
1264
|
+
function BankLogo({ bank, size: size2 = 20 }) {
|
|
1281
1265
|
const [broken, setBroken] = useState(false);
|
|
1282
1266
|
if (!bank?.logo || broken) {
|
|
1283
|
-
return /* @__PURE__ */ jsx(Landmark, { className: "text-muted-foreground", style: { width:
|
|
1267
|
+
return /* @__PURE__ */ jsx(Landmark, { className: "text-muted-foreground", style: { width: size2 * 0.8, height: size2 * 0.8 }, "aria-hidden": true });
|
|
1284
1268
|
}
|
|
1285
1269
|
return /* @__PURE__ */ jsx(
|
|
1286
1270
|
"img",
|
|
1287
1271
|
{
|
|
1288
1272
|
src: bank.logo,
|
|
1289
1273
|
alt: "",
|
|
1290
|
-
width:
|
|
1291
|
-
height:
|
|
1274
|
+
width: size2,
|
|
1275
|
+
height: size2,
|
|
1292
1276
|
loading: "lazy",
|
|
1293
1277
|
onError: () => setBroken(true),
|
|
1294
1278
|
className: "shrink-0 object-contain",
|
|
1295
|
-
style: { width:
|
|
1279
|
+
style: { width: size2, height: size2 }
|
|
1296
1280
|
}
|
|
1297
1281
|
);
|
|
1298
1282
|
}
|
|
@@ -1420,22 +1404,22 @@ function lineFromInvoice(invoice, payee) {
|
|
|
1420
1404
|
var openAmount = (invoice) => Math.max(0, invoice.totalAmount - invoice.paidAmount);
|
|
1421
1405
|
function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
|
|
1422
1406
|
const { currency } = useFinancialConfig();
|
|
1423
|
-
const [step, setStep] =
|
|
1424
|
-
const [bankAccountId, setBankAccountId] =
|
|
1425
|
-
const [draft, setDraft] =
|
|
1426
|
-
const [context, setContext] =
|
|
1427
|
-
const [companyName, setCompanyName] =
|
|
1428
|
-
const [companyDocument, setCompanyDocument] =
|
|
1429
|
-
const [lines, setLines] =
|
|
1430
|
-
const [companyUnsaved, setCompanyUnsaved] =
|
|
1431
|
-
const [loadingContext, setLoadingContext] =
|
|
1432
|
-
const [busy, setBusy] =
|
|
1433
|
-
const [error, setError] =
|
|
1407
|
+
const [step, setStep] = React25__default.useState(0);
|
|
1408
|
+
const [bankAccountId, setBankAccountId] = React25__default.useState("");
|
|
1409
|
+
const [draft, setDraft] = React25__default.useState(null);
|
|
1410
|
+
const [context, setContext] = React25__default.useState(null);
|
|
1411
|
+
const [companyName, setCompanyName] = React25__default.useState("");
|
|
1412
|
+
const [companyDocument, setCompanyDocument] = React25__default.useState("");
|
|
1413
|
+
const [lines, setLines] = React25__default.useState([]);
|
|
1414
|
+
const [companyUnsaved, setCompanyUnsaved] = React25__default.useState(false);
|
|
1415
|
+
const [loadingContext, setLoadingContext] = React25__default.useState(false);
|
|
1416
|
+
const [busy, setBusy] = React25__default.useState(false);
|
|
1417
|
+
const [error, setError] = React25__default.useState("");
|
|
1434
1418
|
const eligibleAccounts2 = bankAccounts.filter((account2) => account2.accountType === "bank" && account2.isActive);
|
|
1435
1419
|
const total = invoices.reduce((sum, invoice) => sum + openAmount(invoice), 0);
|
|
1436
1420
|
const account = eligibleAccounts2.find((candidate) => candidate.id === bankAccountId);
|
|
1437
1421
|
const missingBankData = draft ? missingFields(draft, companyDocument) : [];
|
|
1438
|
-
const items =
|
|
1422
|
+
const items = React25__default.useMemo(() => lines.map((line) => ({
|
|
1439
1423
|
reference: line.invoice.id,
|
|
1440
1424
|
amount: openAmount(line.invoice),
|
|
1441
1425
|
paymentDate: line.paymentDate,
|
|
@@ -1460,8 +1444,8 @@ function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
|
|
|
1460
1444
|
postalCode: line.payee.postalCode
|
|
1461
1445
|
}
|
|
1462
1446
|
})), [lines]);
|
|
1463
|
-
const gaps =
|
|
1464
|
-
const batches =
|
|
1447
|
+
const gaps = React25__default.useMemo(() => cnabPayeeGaps(items), [items]);
|
|
1448
|
+
const batches = React25__default.useMemo(() => {
|
|
1465
1449
|
const grouped = /* @__PURE__ */ new Map();
|
|
1466
1450
|
for (const line of lines) {
|
|
1467
1451
|
const current = grouped.get(line.paymentForm) ?? { count: 0, total: 0 };
|
|
@@ -1877,97 +1861,182 @@ function Field({ label, value, onChange, required, placeholder, className = "" }
|
|
|
1877
1861
|
/* @__PURE__ */ jsx("input", { value, onChange: (event) => onChange(event.target.value), placeholder, className: "h-9 w-full rounded-input border bg-background px-3 text-sm outline-none focus:border-primary focus:ring-2 focus:ring-primary/15" })
|
|
1878
1862
|
] });
|
|
1879
1863
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1864
|
+
var ACCEPT = "image/*,application/pdf";
|
|
1865
|
+
var MAX_BYTES = 15 * 1024 * 1024;
|
|
1866
|
+
function size(bytes) {
|
|
1867
|
+
if (!bytes) return "";
|
|
1868
|
+
return bytes < 1024 * 1024 ? `${Math.max(1, Math.round(bytes / 1024))} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
1869
|
+
}
|
|
1870
|
+
function InvoiceAttachmentsModal({ invoiceId, onClose, onChanged }) {
|
|
1871
|
+
const provider = useFinancialProvider();
|
|
1872
|
+
const inputRef = useRef(null);
|
|
1873
|
+
const [items, setItems] = useState(null);
|
|
1874
|
+
const [uploading, setUploading] = useState(0);
|
|
1875
|
+
const [over, setOver] = useState(false);
|
|
1876
|
+
const [preview, setPreview] = useState(null);
|
|
1877
|
+
async function load() {
|
|
1878
|
+
try {
|
|
1879
|
+
setItems(await (provider.getInvoiceAttachments?.(invoiceId) ?? Promise.resolve([])));
|
|
1880
|
+
} catch (e) {
|
|
1881
|
+
setItems([]);
|
|
1882
|
+
toast.error(e?.message ?? String(e));
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
useEffect(() => {
|
|
1886
|
+
void load();
|
|
1887
|
+
}, [invoiceId]);
|
|
1888
|
+
async function upload(files) {
|
|
1889
|
+
const list = Array.from(files);
|
|
1890
|
+
if (!list.length || !provider.addInvoiceAttachment) return;
|
|
1891
|
+
const invalid = list.filter((f) => !(f.type.startsWith("image/") || f.type === "application/pdf") || f.size > MAX_BYTES);
|
|
1892
|
+
if (invalid.length) toast.error("Envie imagem ou PDF de at\xE9 15 MB.");
|
|
1893
|
+
const ok = list.filter((f) => !invalid.includes(f));
|
|
1894
|
+
if (!ok.length) return;
|
|
1895
|
+
setUploading((n) => n + ok.length);
|
|
1896
|
+
for (const file of ok) {
|
|
1897
|
+
try {
|
|
1898
|
+
await provider.addInvoiceAttachment(invoiceId, file);
|
|
1899
|
+
} catch (e) {
|
|
1900
|
+
toast.error(`${file.name}: ${e?.message ?? String(e)}`);
|
|
1901
|
+
} finally {
|
|
1902
|
+
setUploading((n) => n - 1);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
await load();
|
|
1906
|
+
onChanged?.();
|
|
1907
|
+
}
|
|
1908
|
+
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
1909
|
+
if (!o) onClose();
|
|
1910
|
+
}, children: /* @__PURE__ */ jsx(ModalContent, { size: preview !== null ? "3xl" : "sm", children: preview !== null && items?.[preview] ? /* @__PURE__ */ jsx(
|
|
1911
|
+
AttachmentPreview,
|
|
1912
|
+
{
|
|
1913
|
+
items,
|
|
1914
|
+
index: preview,
|
|
1915
|
+
onIndex: setPreview,
|
|
1916
|
+
onBack: () => setPreview(null)
|
|
1917
|
+
}
|
|
1918
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1919
|
+
/* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs(ModalTitle, { className: "flex items-center gap-2", children: [
|
|
1920
|
+
/* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }),
|
|
1921
|
+
"Anexos da conta"
|
|
1922
|
+
] }) }),
|
|
1923
|
+
/* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
|
|
1924
|
+
items === null ? /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", "aria-busy": "true", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-2.5", children: [
|
|
1925
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 shrink-0 animate-pulse rounded bg-muted" }),
|
|
1926
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
|
|
1927
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-2/3 animate-pulse rounded bg-muted" }),
|
|
1928
|
+
/* @__PURE__ */ jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-muted" })
|
|
1929
|
+
] })
|
|
1930
|
+
] }, i)) }) : items.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-card bg-muted/40 px-4 py-5 text-center", children: [
|
|
1931
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhum anexo ainda" }),
|
|
1932
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Guarde aqui o boleto, a nota ou o comprovante de pagamento \u2014 fica tudo junto da conta." })
|
|
1933
|
+
] }) : /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", children: items.map((a, i) => {
|
|
1934
|
+
const Icon = (a.mimeType ?? "").startsWith("image/") ? Image : FileText;
|
|
1935
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
1936
|
+
"button",
|
|
1898
1937
|
{
|
|
1899
|
-
|
|
1900
|
-
onClick: (
|
|
1938
|
+
type: "button",
|
|
1939
|
+
onClick: () => setPreview(i),
|
|
1940
|
+
className: "flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-muted/40",
|
|
1901
1941
|
children: [
|
|
1902
|
-
/* @__PURE__ */
|
|
1903
|
-
|
|
1904
|
-
/* @__PURE__ */
|
|
1905
|
-
|
|
1906
|
-
/* @__PURE__ */ jsxs("p", { className: "text-[11px] opacity-60", children: [
|
|
1907
|
-
attachments.length > 1 ? `${index + 1} de ${attachments.length}` : null,
|
|
1908
|
-
doc.fileSize ? `${attachments.length > 1 ? " \xB7 " : ""}${Math.round(doc.fileSize / 1024)} KB` : null
|
|
1909
|
-
] })
|
|
1910
|
-
] }),
|
|
1911
|
-
doc.url ? /* @__PURE__ */ jsxs(
|
|
1912
|
-
"a",
|
|
1913
|
-
{
|
|
1914
|
-
href: doc.url,
|
|
1915
|
-
target: "_blank",
|
|
1916
|
-
rel: "noreferrer",
|
|
1917
|
-
className: "inline-flex shrink-0 items-center gap-1 rounded-lg border border-white/20 px-2.5 py-1.5 text-xs font-medium text-white hover:bg-white/10",
|
|
1918
|
-
children: [
|
|
1919
|
-
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
1920
|
-
" Abrir"
|
|
1921
|
-
]
|
|
1922
|
-
}
|
|
1923
|
-
) : null,
|
|
1924
|
-
/* @__PURE__ */ jsx(
|
|
1925
|
-
"button",
|
|
1926
|
-
{
|
|
1927
|
-
type: "button",
|
|
1928
|
-
onClick: () => onClose(),
|
|
1929
|
-
"aria-label": "Fechar",
|
|
1930
|
-
className: "shrink-0 rounded-lg border border-white/20 px-2.5 py-1.5 text-xs font-medium text-white hover:bg-white/10",
|
|
1931
|
-
children: "Fechar"
|
|
1932
|
-
}
|
|
1933
|
-
)
|
|
1942
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
1943
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1944
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: a.fileName }),
|
|
1945
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: a.pending ? "Arquivo ainda n\xE3o dispon\xEDvel" : [size(a.fileSize), a.createdAt?.slice(0, 10).split("-").reverse().join("/")].filter(Boolean).join(" \xB7 ") })
|
|
1934
1946
|
] }),
|
|
1935
|
-
/* @__PURE__ */ jsx(
|
|
1936
|
-
"O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por ",
|
|
1937
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
|
|
1938
|
-
"."
|
|
1939
|
-
] }) : ehImagem ? /* @__PURE__ */ jsx("img", { src: doc.url, alt: doc.fileName, className: "max-h-full max-w-full object-contain" }) : ehPdf ? /* @__PURE__ */ jsx("iframe", { src: doc.url, title: doc.fileName, className: "h-full w-full rounded-xl bg-white" }) : /* @__PURE__ */ jsxs("p", { className: "p-8 text-center text-sm text-white/70", children: [
|
|
1940
|
-
"Este tipo de arquivo n\xE3o tem pr\xE9-visualiza\xE7\xE3o. Use ",
|
|
1941
|
-
/* @__PURE__ */ jsx("strong", { children: "Abrir" }),
|
|
1942
|
-
"."
|
|
1943
|
-
] }) }),
|
|
1944
|
-
attachments.length > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
|
|
1945
|
-
/* @__PURE__ */ jsx(
|
|
1946
|
-
"button",
|
|
1947
|
-
{
|
|
1948
|
-
type: "button",
|
|
1949
|
-
onClick: () => irPara(-1),
|
|
1950
|
-
className: "rounded-lg border border-white/20 px-3 py-1.5 text-xs font-medium text-white hover:bg-white/10",
|
|
1951
|
-
children: "Anterior"
|
|
1952
|
-
}
|
|
1953
|
-
),
|
|
1954
|
-
/* @__PURE__ */ jsx(
|
|
1955
|
-
"button",
|
|
1956
|
-
{
|
|
1957
|
-
type: "button",
|
|
1958
|
-
onClick: () => irPara(1),
|
|
1959
|
-
className: "rounded-lg border border-white/20 px-3 py-1.5 text-xs font-medium text-white hover:bg-white/10",
|
|
1960
|
-
children: "Pr\xF3ximo"
|
|
1961
|
-
}
|
|
1962
|
-
)
|
|
1963
|
-
] })
|
|
1947
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
1964
1948
|
]
|
|
1965
1949
|
}
|
|
1966
|
-
)
|
|
1967
|
-
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1950
|
+
) }, a.id);
|
|
1951
|
+
}) }),
|
|
1952
|
+
provider.addInvoiceAttachment && /* @__PURE__ */ jsxs(
|
|
1953
|
+
"button",
|
|
1954
|
+
{
|
|
1955
|
+
type: "button",
|
|
1956
|
+
onClick: () => inputRef.current?.click(),
|
|
1957
|
+
onDragOver: (e) => {
|
|
1958
|
+
e.preventDefault();
|
|
1959
|
+
setOver(true);
|
|
1960
|
+
},
|
|
1961
|
+
onDragLeave: () => setOver(false),
|
|
1962
|
+
onDrop: (e) => {
|
|
1963
|
+
e.preventDefault();
|
|
1964
|
+
setOver(false);
|
|
1965
|
+
void upload(e.dataTransfer.files);
|
|
1966
|
+
},
|
|
1967
|
+
disabled: uploading > 0,
|
|
1968
|
+
className: `flex w-full flex-col items-center gap-1 rounded-card border-2 border-dashed px-4 py-5 text-center transition-colors ${over ? "border-primary bg-primary/5" : "border-border hover:bg-muted/40"}`,
|
|
1969
|
+
children: [
|
|
1970
|
+
uploading > 0 ? /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsx(UploadCloud, { className: "h-5 w-5 text-muted-foreground", "aria-hidden": true }),
|
|
1971
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: uploading > 0 ? "Enviando\u2026" : "Solte arquivos aqui ou clique para escolher" }),
|
|
1972
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Imagem ou PDF, at\xE9 15 MB" })
|
|
1973
|
+
]
|
|
1974
|
+
}
|
|
1975
|
+
),
|
|
1976
|
+
/* @__PURE__ */ jsx(
|
|
1977
|
+
"input",
|
|
1978
|
+
{
|
|
1979
|
+
ref: inputRef,
|
|
1980
|
+
type: "file",
|
|
1981
|
+
accept: ACCEPT,
|
|
1982
|
+
multiple: true,
|
|
1983
|
+
className: "hidden",
|
|
1984
|
+
onChange: (e) => {
|
|
1985
|
+
const f = e.target.files;
|
|
1986
|
+
if (f) void upload(f);
|
|
1987
|
+
e.target.value = "";
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
)
|
|
1991
|
+
] })
|
|
1992
|
+
] }) }) });
|
|
1993
|
+
}
|
|
1994
|
+
function AttachmentPreview({ items, index, onIndex, onBack }) {
|
|
1995
|
+
const doc = items[index];
|
|
1996
|
+
const isImage = (doc.mimeType ?? "").startsWith("image/");
|
|
1997
|
+
const isPdf = doc.mimeType === "application/pdf";
|
|
1998
|
+
const go = (d) => onIndex((index + d + items.length) % items.length);
|
|
1999
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2000
|
+
/* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pr-8", children: [
|
|
2001
|
+
/* @__PURE__ */ jsxs(
|
|
2002
|
+
"button",
|
|
2003
|
+
{
|
|
2004
|
+
type: "button",
|
|
2005
|
+
onClick: onBack,
|
|
2006
|
+
className: "inline-flex shrink-0 items-center gap-1 rounded-md px-1.5 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
2007
|
+
children: [
|
|
2008
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
|
|
2009
|
+
" Voltar"
|
|
2010
|
+
]
|
|
2011
|
+
}
|
|
2012
|
+
),
|
|
2013
|
+
/* @__PURE__ */ jsx(ModalTitle, { className: "min-w-0 flex-1 truncate text-sm font-medium", title: doc.fileName, children: doc.fileName }),
|
|
2014
|
+
items.length > 1 && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
|
|
2015
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => go(-1), "aria-label": "Anterior", className: "rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }) }),
|
|
2016
|
+
index + 1,
|
|
2017
|
+
" de ",
|
|
2018
|
+
items.length,
|
|
2019
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => go(1), "aria-label": "Pr\xF3ximo", className: "rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
|
|
2020
|
+
] }),
|
|
2021
|
+
doc.url && /* @__PURE__ */ jsx(
|
|
2022
|
+
"a",
|
|
2023
|
+
{
|
|
2024
|
+
href: doc.url,
|
|
2025
|
+
target: "_blank",
|
|
2026
|
+
rel: "noreferrer",
|
|
2027
|
+
"aria-label": "Abrir em nova aba",
|
|
2028
|
+
className: "shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
2029
|
+
children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" })
|
|
2030
|
+
}
|
|
2031
|
+
)
|
|
2032
|
+
] }) }),
|
|
2033
|
+
/* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx("div", { className: "flex h-[65vh] items-center justify-center overflow-hidden rounded-card bg-muted/40", children: doc.pending || !doc.url ? /* @__PURE__ */ jsxs("p", { className: "max-w-sm p-8 text-center text-sm text-muted-foreground", children: [
|
|
2034
|
+
"O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por",
|
|
2035
|
+
" ",
|
|
2036
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
|
|
2037
|
+
"."
|
|
2038
|
+
] }) : isImage ? /* @__PURE__ */ jsx("img", { src: doc.url, alt: doc.fileName, className: "max-h-full max-w-full object-contain" }) : isPdf ? /* @__PURE__ */ jsx("iframe", { src: doc.url, title: doc.fileName, className: "h-full w-full bg-white" }) : /* @__PURE__ */ jsx("p", { className: "p-8 text-center text-sm text-muted-foreground", children: "Este tipo de arquivo n\xE3o tem pr\xE9-visualiza\xE7\xE3o. Use o bot\xE3o de abrir." }) }) })
|
|
2039
|
+
] });
|
|
1971
2040
|
}
|
|
1972
2041
|
var KIND_LABELS = {
|
|
1973
2042
|
supplier: "Fornecedor",
|
|
@@ -1994,7 +2063,7 @@ function FinancialCounterpartySearchSelect({
|
|
|
1994
2063
|
onEdit,
|
|
1995
2064
|
disabled
|
|
1996
2065
|
}) {
|
|
1997
|
-
const search =
|
|
2066
|
+
const search = React25.useCallback(async (query) => {
|
|
1998
2067
|
const results = await lookup.search(query);
|
|
1999
2068
|
return results.map((result) => ({
|
|
2000
2069
|
id: result.id,
|
|
@@ -2197,7 +2266,7 @@ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAc
|
|
|
2197
2266
|
},
|
|
2198
2267
|
children: [
|
|
2199
2268
|
/* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4", "aria-hidden": true }),
|
|
2200
|
-
n >
|
|
2269
|
+
n > 0 && /* @__PURE__ */ jsx("span", { className: "absolute -right-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-primary-foreground tabular-nums", children: n })
|
|
2201
2270
|
]
|
|
2202
2271
|
}
|
|
2203
2272
|
) });
|
|
@@ -2345,13 +2414,8 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2345
2414
|
}).finally(() => setConsolidating(null));
|
|
2346
2415
|
}, [provider, t2, fetchInvoices, query, onEdit]);
|
|
2347
2416
|
const filtered = invoices.filter((inv) => inv.direction === direction);
|
|
2348
|
-
const [
|
|
2349
|
-
const openAttachments = useCallback((invoiceId) =>
|
|
2350
|
-
if (!provider.getInvoiceAttachments) return;
|
|
2351
|
-
provider.getInvoiceAttachments(invoiceId).then((attachments) => {
|
|
2352
|
-
if (attachments.length) setViewer({ attachments, index: 0 });
|
|
2353
|
-
}).catch(() => toast.error("N\xE3o foi poss\xEDvel abrir os anexos"));
|
|
2354
|
-
}, [provider]);
|
|
2417
|
+
const [attachmentsOf, setAttachmentsOf] = useState(null);
|
|
2418
|
+
const openAttachments = useCallback((invoiceId) => setAttachmentsOf(invoiceId), []);
|
|
2355
2419
|
const columns = useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments);
|
|
2356
2420
|
const contactLookup = useMemo(() => createFinancialCounterpartyLookup(direction), [direction]);
|
|
2357
2421
|
const tags = useMemo(
|
|
@@ -2436,11 +2500,13 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2436
2500
|
}, [currency, direction, t2]);
|
|
2437
2501
|
const bulkActions = useMemo(() => {
|
|
2438
2502
|
const actions = [
|
|
2439
|
-
...direction === "debit" ? [{ id: "cnab", label: t2("financial.invoice.generateCnab"), icon: FileText, onSelect: (rows) => setCnabTarget(rows.filter((row) => row.status !== "paid" && row.status !== "cancelled")) }] : [],
|
|
2440
2503
|
{ id: "print", label: t2("financial.invoice.printSelected"), icon: Printer, onSelect: printSelected },
|
|
2441
2504
|
{ id: "export", label: t2("financial.invoice.exportSelected"), icon: Download, onSelect: exportSelected }
|
|
2442
2505
|
];
|
|
2443
2506
|
if (!canEdit) return actions;
|
|
2507
|
+
if (direction === "debit") {
|
|
2508
|
+
actions.unshift({ id: "cnab", label: t2("financial.invoice.generateCnab"), icon: FileText, onSelect: (rows) => setCnabTarget(rows.filter((row) => row.status !== "paid" && row.status !== "cancelled")) });
|
|
2509
|
+
}
|
|
2444
2510
|
actions.push({
|
|
2445
2511
|
id: "cancel",
|
|
2446
2512
|
label: t2("financial.invoice.bulkCancel"),
|
|
@@ -2496,17 +2562,20 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2496
2562
|
value: unitFilter.options.find((u) => u.id === unitFilter.value)?.name ?? unitFilter.allLabel,
|
|
2497
2563
|
icon: Building2,
|
|
2498
2564
|
className: "h-9 w-full justify-start",
|
|
2499
|
-
align: "
|
|
2500
|
-
children: (close) => /* @__PURE__ */
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
unitFilter.
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2565
|
+
align: "end",
|
|
2566
|
+
children: (close) => /* @__PURE__ */ jsx(
|
|
2567
|
+
ChoiceList,
|
|
2568
|
+
{
|
|
2569
|
+
options: unitFilter.options.map((u) => ({ value: u.id, label: u.name })),
|
|
2570
|
+
value: unitFilter.value,
|
|
2571
|
+
onSelect: unitFilter.setValue,
|
|
2572
|
+
allLabel: unitFilter.allLabel,
|
|
2573
|
+
searchable: true,
|
|
2574
|
+
searchPlaceholder: unitFilter.def?.searchPlaceholder,
|
|
2575
|
+
manage: unitFilter.def?.manage,
|
|
2576
|
+
onDone: close
|
|
2577
|
+
}
|
|
2578
|
+
)
|
|
2510
2579
|
}
|
|
2511
2580
|
) : locations.length > 1 ? /* @__PURE__ */ jsx(
|
|
2512
2581
|
RequiredFilter,
|
|
@@ -2515,17 +2584,18 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2515
2584
|
value: locations.find((u) => u.id === unitId)?.name ?? t2("financial.invoice.filterAllUnits"),
|
|
2516
2585
|
icon: Building2,
|
|
2517
2586
|
className: "h-9 w-full justify-start",
|
|
2518
|
-
align: "
|
|
2519
|
-
children: (close) => /* @__PURE__ */
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2587
|
+
align: "end",
|
|
2588
|
+
children: (close) => /* @__PURE__ */ jsx(
|
|
2589
|
+
ChoiceList,
|
|
2590
|
+
{
|
|
2591
|
+
options: locations.map((u) => ({ value: u.id, label: u.name })),
|
|
2592
|
+
value: unitId || void 0,
|
|
2593
|
+
onSelect: (v) => setUnitId(v ?? ""),
|
|
2594
|
+
allLabel: t2("financial.invoice.filterAllUnits"),
|
|
2595
|
+
searchable: true,
|
|
2596
|
+
onDone: close
|
|
2597
|
+
}
|
|
2598
|
+
)
|
|
2529
2599
|
}
|
|
2530
2600
|
) : null;
|
|
2531
2601
|
const statusField = /* @__PURE__ */ jsx(
|
|
@@ -2695,7 +2765,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2695
2765
|
},
|
|
2696
2766
|
...onNewReceiptGated ? [{
|
|
2697
2767
|
id: "receipt",
|
|
2698
|
-
|
|
2768
|
+
// Same flow for payables and receivables: the photo is the request and
|
|
2769
|
+
// the assistant returns the form filled in.
|
|
2770
|
+
label: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
|
|
2699
2771
|
description: t2("financial.quickTx.receiptCapture"),
|
|
2700
2772
|
icon: Camera,
|
|
2701
2773
|
intent: "secondary",
|
|
@@ -2705,10 +2777,20 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2705
2777
|
// preenchido. Ver receipt-agent.ts.
|
|
2706
2778
|
onSelect: () => startReceiptCapture({
|
|
2707
2779
|
ask: t2("financial.receipt.ask"),
|
|
2708
|
-
uploadLabel: t2("financial.quickTx.
|
|
2780
|
+
uploadLabel: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
|
|
2709
2781
|
uploadHint: t2("financial.quickTx.receiptCapture"),
|
|
2710
2782
|
prompt: t2("financial.receipt.prompt")
|
|
2711
2783
|
})
|
|
2784
|
+
}] : [],
|
|
2785
|
+
...direction === "debit" ? [{
|
|
2786
|
+
id: "payroll",
|
|
2787
|
+
label: t2("financial.invoice.importPayroll"),
|
|
2788
|
+
description: t2("financial.invoice.importPayrollSoon"),
|
|
2789
|
+
icon: Users,
|
|
2790
|
+
intent: "secondary",
|
|
2791
|
+
disabled: true,
|
|
2792
|
+
onSelect: () => {
|
|
2793
|
+
}
|
|
2712
2794
|
}] : []
|
|
2713
2795
|
] : [];
|
|
2714
2796
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
@@ -2738,13 +2820,14 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2738
2820
|
onEmptyAction: onNewGated
|
|
2739
2821
|
}
|
|
2740
2822
|
),
|
|
2741
|
-
|
|
2742
|
-
|
|
2823
|
+
attachmentsOf && /* @__PURE__ */ jsx(
|
|
2824
|
+
InvoiceAttachmentsModal,
|
|
2743
2825
|
{
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2826
|
+
invoiceId: attachmentsOf,
|
|
2827
|
+
onClose: () => setAttachmentsOf(null),
|
|
2828
|
+
onChanged: () => {
|
|
2829
|
+
void fetchInvoices(query);
|
|
2830
|
+
}
|
|
2748
2831
|
}
|
|
2749
2832
|
),
|
|
2750
2833
|
filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
|
|
@@ -2797,6 +2880,40 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2797
2880
|
cnabTarget !== null ? /* @__PURE__ */ jsx(CnabRemittanceModal, { invoices: cnabTarget, bankAccounts, provider, onClose: () => setCnabTarget(null) }) : null
|
|
2798
2881
|
] });
|
|
2799
2882
|
}
|
|
2883
|
+
|
|
2884
|
+
// src/lib/routes.ts
|
|
2885
|
+
var ACCOUNTS_ROUTE = "/registry/accounts";
|
|
2886
|
+
var CHART_OF_ACCOUNTS_ROUTE = "/settings/financial/chart-of-accounts";
|
|
2887
|
+
var COST_CENTERS_ROUTE = "/settings/financial/cost-centers";
|
|
2888
|
+
function useWritableUnits(fallback = []) {
|
|
2889
|
+
const unitMode = useUnitMode();
|
|
2890
|
+
const activeUnitId = useActiveUnitId() ?? void 0;
|
|
2891
|
+
const reachable = useUnitFilter().options;
|
|
2892
|
+
const choices = !unitMode ? fallback : activeUnitId ? reachable.filter((u) => u.id === activeUnitId) : reachable;
|
|
2893
|
+
return { unitMode, activeUnitId, choices };
|
|
2894
|
+
}
|
|
2895
|
+
function UnitField({ choices, value, onChange, label }) {
|
|
2896
|
+
const t2 = useTranslation();
|
|
2897
|
+
const can = usePermissionOptional();
|
|
2898
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
2899
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: label ?? t2("financial.invoiceForm.unit") }),
|
|
2900
|
+
/* @__PURE__ */ jsx(
|
|
2901
|
+
ChoiceSelect,
|
|
2902
|
+
{
|
|
2903
|
+
className: "mt-1",
|
|
2904
|
+
icon: Building2,
|
|
2905
|
+
"aria-label": label ?? t2("financial.invoiceForm.unit"),
|
|
2906
|
+
options: choices.map((c) => ({ value: c.id, label: c.name })),
|
|
2907
|
+
value: value || void 0,
|
|
2908
|
+
onChange: (v) => onChange(v ?? ""),
|
|
2909
|
+
allLabel: "Todas / sem unidade",
|
|
2910
|
+
searchable: true,
|
|
2911
|
+
searchPlaceholder: t2("financial.invoiceForm.searchUnit"),
|
|
2912
|
+
manage: can("units", "manage") ? { label: "Gerenciar unidades", onSelect: () => adminNavigateTo("/settings/locations") } : void 0
|
|
2913
|
+
}
|
|
2914
|
+
)
|
|
2915
|
+
] });
|
|
2916
|
+
}
|
|
2800
2917
|
var PAYABLE_INSTRUCTIONS = [
|
|
2801
2918
|
'Uma "conta a pagar" \xE9 uma DESPESA com um FORNECEDOR \u2014 quem a empresa deve. Para registrar uma,',
|
|
2802
2919
|
"chame `createPayable`. NUNCA use `createRecord` em `financial:payment-methods` para isso: aquele",
|
|
@@ -3825,9 +3942,9 @@ var SUBMIT_DRAFT = {
|
|
|
3825
3942
|
parameters: { type: "object", properties: {} }
|
|
3826
3943
|
};
|
|
3827
3944
|
function PayableDraftSurface({ config, draft }) {
|
|
3828
|
-
const live =
|
|
3945
|
+
const live = React25.useRef(draft);
|
|
3829
3946
|
live.current = draft;
|
|
3830
|
-
const lookup =
|
|
3947
|
+
const lookup = React25.useMemo(
|
|
3831
3948
|
() => config.contactLookup ?? createArchetypeLookup({
|
|
3832
3949
|
archetype: "person",
|
|
3833
3950
|
kind: "supplier",
|
|
@@ -3836,7 +3953,7 @@ function PayableDraftSurface({ config, draft }) {
|
|
|
3836
3953
|
}),
|
|
3837
3954
|
[config.contactLookup]
|
|
3838
3955
|
);
|
|
3839
|
-
|
|
3956
|
+
React25.useEffect(() => {
|
|
3840
3957
|
liveDraft = {
|
|
3841
3958
|
describe: () => live.current.describe(),
|
|
3842
3959
|
setSupplier: (name, id) => live.current.setSupplier(name, id),
|
|
@@ -3847,7 +3964,7 @@ function PayableDraftSurface({ config, draft }) {
|
|
|
3847
3964
|
liveDraft = null;
|
|
3848
3965
|
};
|
|
3849
3966
|
}, []);
|
|
3850
|
-
const contribution =
|
|
3967
|
+
const contribution = React25.useMemo(() => ({
|
|
3851
3968
|
id: "financial.payables.draft",
|
|
3852
3969
|
pluginId: "financial",
|
|
3853
3970
|
role: "finance",
|
|
@@ -3922,7 +4039,7 @@ function PayableDraftSurface({ config, draft }) {
|
|
|
3922
4039
|
}
|
|
3923
4040
|
function createFinancialAgentMount(deps) {
|
|
3924
4041
|
const FinancialAgentMount = () => {
|
|
3925
|
-
const contribution =
|
|
4042
|
+
const contribution = React25.useMemo(() => ({
|
|
3926
4043
|
id: "financial.payables.global",
|
|
3927
4044
|
pluginId: "financial",
|
|
3928
4045
|
role: "finance",
|
|
@@ -4017,13 +4134,24 @@ function CardVisual({
|
|
|
4017
4134
|
lastFour: lastFour2,
|
|
4018
4135
|
color,
|
|
4019
4136
|
brandId,
|
|
4020
|
-
size = "sm"
|
|
4137
|
+
size: size2 = "sm"
|
|
4021
4138
|
}) {
|
|
4022
4139
|
const brand = useBrandName(brandId);
|
|
4023
4140
|
const last = (lastFour2 ?? "").trim();
|
|
4024
4141
|
if (!last && !color && !brand) return null;
|
|
4025
4142
|
const bg = color && /^#[0-9a-fA-F]{6}$/.test(color) ? color : void 0;
|
|
4026
|
-
const lg =
|
|
4143
|
+
const lg = size2 === "lg";
|
|
4144
|
+
if (size2 === "xs") {
|
|
4145
|
+
return /* @__PURE__ */ jsx(
|
|
4146
|
+
"span",
|
|
4147
|
+
{
|
|
4148
|
+
className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[3px] border align-middle text-[6px] font-semibold leading-none tabular-nums",
|
|
4149
|
+
style: bg ? { backgroundColor: bg, color: ink(bg), borderColor: "transparent" } : void 0,
|
|
4150
|
+
title: [brand, last && `\u2022\u2022\u2022\u2022 ${last}`].filter(Boolean).join(" "),
|
|
4151
|
+
children: last
|
|
4152
|
+
}
|
|
4153
|
+
);
|
|
4154
|
+
}
|
|
4027
4155
|
return /* @__PURE__ */ jsxs(
|
|
4028
4156
|
"span",
|
|
4029
4157
|
{
|
|
@@ -4093,7 +4221,9 @@ function eligibleAccountsFor(bankAccounts, direction, type) {
|
|
|
4093
4221
|
const active = bankAccounts.filter((a) => a.isActive !== false);
|
|
4094
4222
|
if (direction === "debit") {
|
|
4095
4223
|
if (txType === "credit_card") return active.filter((a) => a.accountType === "card_payable");
|
|
4096
|
-
|
|
4224
|
+
const own = (type?.allowedAccountTypes?.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType])?.filter((k) => !k.startsWith("card_"));
|
|
4225
|
+
const kinds = own?.length ? own : ["bank", "cash_physical", "cash_session"];
|
|
4226
|
+
return active.filter((a) => kinds.includes(a.accountType));
|
|
4097
4227
|
}
|
|
4098
4228
|
const allowed = type?.allowedAccountTypes && type.allowedAccountTypes.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType];
|
|
4099
4229
|
if (!allowed || !allowed.length) return bankAccounts;
|
|
@@ -4415,10 +4545,10 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
4415
4545
|
const payMovement = useFinancialStore((s) => s.payMovement);
|
|
4416
4546
|
const remaining = movement.amount - movement.paidAmount;
|
|
4417
4547
|
const [saving, setSaving] = useState(false);
|
|
4418
|
-
const command =
|
|
4548
|
+
const command = React25__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
|
|
4419
4549
|
const [pending2, setPending] = useState(false);
|
|
4420
4550
|
const [visible, setVisible] = useState(false);
|
|
4421
|
-
|
|
4551
|
+
React25__default.useEffect(() => {
|
|
4422
4552
|
requestAnimationFrame(() => setVisible(true));
|
|
4423
4553
|
}, []);
|
|
4424
4554
|
const source = movement.metadata ?? {};
|
|
@@ -4537,6 +4667,32 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
4537
4667
|
document.body
|
|
4538
4668
|
);
|
|
4539
4669
|
}
|
|
4670
|
+
var METHOD_ICONS = {
|
|
4671
|
+
cash: Banknote,
|
|
4672
|
+
pix: QrCode,
|
|
4673
|
+
credit_card: CreditCard,
|
|
4674
|
+
debit_card: CreditCard,
|
|
4675
|
+
bank_transfer: ArrowRightLeft,
|
|
4676
|
+
check: FileCheck,
|
|
4677
|
+
boleto: Receipt,
|
|
4678
|
+
voucher: Ticket
|
|
4679
|
+
};
|
|
4680
|
+
function MethodIcon({ type }) {
|
|
4681
|
+
const Icon = METHOD_ICONS[type?.transactionType ?? ""] ?? Wallet;
|
|
4682
|
+
return /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4683
|
+
}
|
|
4684
|
+
function AccountIcon({ account }) {
|
|
4685
|
+
if (!account) return null;
|
|
4686
|
+
if (account.accountType === "card_payable" && (account.lastFour || account.color || account.cardBrandId)) {
|
|
4687
|
+
return /* @__PURE__ */ jsx(CardVisual, { size: "xs", lastFour: account.lastFour ?? void 0, color: account.color ?? void 0, brandId: account.cardBrandId ?? void 0 });
|
|
4688
|
+
}
|
|
4689
|
+
if (account.accountType.startsWith("card_")) return /* @__PURE__ */ jsx(CreditCard, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4690
|
+
if (account.accountType === "cash_physical" || account.accountType === "cash_session") {
|
|
4691
|
+
return /* @__PURE__ */ jsx(Banknote, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4692
|
+
}
|
|
4693
|
+
const bank = findBrBank(account.bankCode, account.bankName ?? account.name);
|
|
4694
|
+
return bank?.logo ? /* @__PURE__ */ jsx(BankLogo, { bank, size: 16 }) : /* @__PURE__ */ jsx(Landmark, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4695
|
+
}
|
|
4540
4696
|
var NONE = "__none__";
|
|
4541
4697
|
var TRIGGER_CLASS = `h-auto w-auto justify-start gap-1 rounded-none border-0 bg-transparent p-0 text-xs font-normal shadow-none ring-offset-0 focus:ring-0 focus:ring-offset-0 [&>svg]:h-3 [&>svg]:w-3 ${INLINE_LINK_TONE.dashed}`;
|
|
4542
4698
|
function InstallmentSourceFields({
|
|
@@ -4548,6 +4704,7 @@ function InstallmentSourceFields({
|
|
|
4548
4704
|
const t2 = useTranslation();
|
|
4549
4705
|
const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
|
|
4550
4706
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
4707
|
+
const can = usePermissionOptional();
|
|
4551
4708
|
const selectedType = paymentMethodTypes.find((tp) => tp.id === value.payMethodTypeId);
|
|
4552
4709
|
const selectedAccount = bankAccounts.find((a) => a.id === value.payAccountId);
|
|
4553
4710
|
const accounts = eligibleAccountsFor(bankAccounts, direction, selectedType);
|
|
@@ -4559,7 +4716,7 @@ function InstallmentSourceFields({
|
|
|
4559
4716
|
onChange({ payMethodTypeId: typeId || void 0, payAccountId: typeId ? conta : void 0 });
|
|
4560
4717
|
}
|
|
4561
4718
|
const tone = (filled) => filled ? "text-foreground" : "text-destructive border-b border-dashed border-destructive";
|
|
4562
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-
|
|
4719
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-start gap-0.5", children: [
|
|
4563
4720
|
/* @__PURE__ */ jsxs(
|
|
4564
4721
|
Select,
|
|
4565
4722
|
{
|
|
@@ -4576,37 +4733,219 @@ function InstallmentSourceFields({
|
|
|
4576
4733
|
}
|
|
4577
4734
|
),
|
|
4578
4735
|
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
4579
|
-
paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children:
|
|
4736
|
+
paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
4737
|
+
/* @__PURE__ */ jsx(MethodIcon, { type: tp }),
|
|
4738
|
+
tp.name
|
|
4739
|
+
] }) }, tp.id)),
|
|
4580
4740
|
value.payMethodTypeId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
|
|
4581
4741
|
] })
|
|
4582
4742
|
]
|
|
4583
4743
|
}
|
|
4584
4744
|
),
|
|
4585
|
-
/* @__PURE__ */
|
|
4586
|
-
|
|
4745
|
+
/* @__PURE__ */ jsx(
|
|
4746
|
+
AccountPicker,
|
|
4587
4747
|
{
|
|
4588
|
-
|
|
4748
|
+
accounts,
|
|
4749
|
+
value: value.payAccountId,
|
|
4589
4750
|
disabled: disabled || !value.payMethodTypeId,
|
|
4590
|
-
|
|
4751
|
+
title: value.payMethodTypeId ? void 0 : t2("financial.invoiceForm.selectMethodFirst"),
|
|
4752
|
+
placeholder: t2("financial.payment.account"),
|
|
4753
|
+
ariaLabel: t2("financial.invoiceForm.paidFrom"),
|
|
4754
|
+
clearLabel: t2("financial.invoiceForm.clear"),
|
|
4755
|
+
className: `${INLINE_LINK_TONE.dashed} ${tone(!!selectedAccount)}`,
|
|
4756
|
+
onChange: (id) => onChange({ ...value, payAccountId: id }),
|
|
4757
|
+
onManage: can("financial", "manage") ? () => adminNavigateTo(ACCOUNTS_ROUTE) : void 0,
|
|
4758
|
+
manageLabel: t2("financial.invoiceForm.manageAccounts")
|
|
4759
|
+
}
|
|
4760
|
+
)
|
|
4761
|
+
] });
|
|
4762
|
+
}
|
|
4763
|
+
var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
4764
|
+
function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className, onManage, manageLabel }) {
|
|
4765
|
+
const [open, setOpen] = React25__default.useState(false);
|
|
4766
|
+
const [q, setQ] = React25__default.useState("");
|
|
4767
|
+
const selected = accounts.find((a) => a.id === value);
|
|
4768
|
+
const list = q ? accounts.filter((a) => norm2(a.name).includes(norm2(q))) : accounts;
|
|
4769
|
+
const pick = (id) => {
|
|
4770
|
+
onChange(id);
|
|
4771
|
+
setOpen(false);
|
|
4772
|
+
setQ("");
|
|
4773
|
+
};
|
|
4774
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (o) => {
|
|
4775
|
+
setOpen(o);
|
|
4776
|
+
if (!o) setQ("");
|
|
4777
|
+
}, children: [
|
|
4778
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
4779
|
+
"button",
|
|
4780
|
+
{
|
|
4781
|
+
type: "button",
|
|
4782
|
+
disabled,
|
|
4783
|
+
title,
|
|
4784
|
+
"aria-label": ariaLabel,
|
|
4785
|
+
className: `inline-flex max-w-full items-center gap-1 text-xs disabled:cursor-not-allowed disabled:opacity-50 ${className ?? ""}`,
|
|
4591
4786
|
children: [
|
|
4592
|
-
/* @__PURE__ */
|
|
4593
|
-
|
|
4594
|
-
{
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
className: `${TRIGGER_CLASS} ${tone(!!selectedAccount)}`,
|
|
4598
|
-
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t2("financial.payment.account") })
|
|
4599
|
-
}
|
|
4600
|
-
),
|
|
4601
|
-
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
4602
|
-
accounts.map((a) => /* @__PURE__ */ jsx(SelectItem, { value: a.id, children: a.name }, a.id)),
|
|
4603
|
-
value.payAccountId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
|
|
4604
|
-
] })
|
|
4787
|
+
selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4788
|
+
/* @__PURE__ */ jsx(AccountIcon, { account: selected }),
|
|
4789
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: selected.name })
|
|
4790
|
+
] }) : placeholder,
|
|
4791
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true })
|
|
4605
4792
|
]
|
|
4606
4793
|
}
|
|
4607
|
-
)
|
|
4794
|
+
) }),
|
|
4795
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-64 p-1", children: [
|
|
4796
|
+
/* @__PURE__ */ jsxs("div", { className: "relative mb-1", children: [
|
|
4797
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground", "aria-hidden": true }),
|
|
4798
|
+
/* @__PURE__ */ jsx(
|
|
4799
|
+
"input",
|
|
4800
|
+
{
|
|
4801
|
+
autoFocus: true,
|
|
4802
|
+
value: q,
|
|
4803
|
+
onChange: (e) => setQ(e.target.value),
|
|
4804
|
+
onKeyDown: (e) => {
|
|
4805
|
+
if (e.key === "Enter" && list[0]) {
|
|
4806
|
+
e.preventDefault();
|
|
4807
|
+
pick(list[0].id);
|
|
4808
|
+
}
|
|
4809
|
+
},
|
|
4810
|
+
placeholder: "Buscar conta\u2026",
|
|
4811
|
+
className: "h-8 w-full rounded-md border-0 bg-muted/50 pl-7 pr-2 text-xs outline-none focus:ring-0"
|
|
4812
|
+
}
|
|
4813
|
+
)
|
|
4814
|
+
] }),
|
|
4815
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto", children: list.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: "Nenhuma conta encontrada" }) : list.map((a) => /* @__PURE__ */ jsxs(
|
|
4816
|
+
"button",
|
|
4817
|
+
{
|
|
4818
|
+
type: "button",
|
|
4819
|
+
onClick: () => pick(a.id),
|
|
4820
|
+
className: `flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted ${a.id === value ? "font-medium" : ""}`,
|
|
4821
|
+
children: [
|
|
4822
|
+
/* @__PURE__ */ jsx(AccountIcon, { account: a }),
|
|
4823
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: a.name }),
|
|
4824
|
+
a.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
|
|
4825
|
+
]
|
|
4826
|
+
},
|
|
4827
|
+
a.id
|
|
4828
|
+
)) }),
|
|
4829
|
+
value && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => pick(void 0), className: "mt-1 w-full rounded-md px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted", children: clearLabel }),
|
|
4830
|
+
onManage && /* @__PURE__ */ jsxs(
|
|
4831
|
+
"button",
|
|
4832
|
+
{
|
|
4833
|
+
type: "button",
|
|
4834
|
+
onClick: () => {
|
|
4835
|
+
setOpen(false);
|
|
4836
|
+
onManage();
|
|
4837
|
+
},
|
|
4838
|
+
className: "mt-1 flex w-full items-center gap-2 rounded-md border-t px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
4839
|
+
children: [
|
|
4840
|
+
/* @__PURE__ */ jsx(Settings, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
|
|
4841
|
+
manageLabel
|
|
4842
|
+
]
|
|
4843
|
+
}
|
|
4844
|
+
)
|
|
4845
|
+
] })
|
|
4846
|
+
] });
|
|
4847
|
+
}
|
|
4848
|
+
var STATUS = {
|
|
4849
|
+
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
|
|
4850
|
+
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
|
|
4851
|
+
partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial" },
|
|
4852
|
+
paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid" },
|
|
4853
|
+
overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue" },
|
|
4854
|
+
draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
|
|
4855
|
+
cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
|
|
4856
|
+
};
|
|
4857
|
+
function InstallmentStatusBadge({ status }) {
|
|
4858
|
+
const t2 = useTranslation();
|
|
4859
|
+
const s = STATUS[status ?? ""] ?? STATUS.pending;
|
|
4860
|
+
const Icon = s.icon;
|
|
4861
|
+
return /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${s.bg}`, children: [
|
|
4862
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-2 w-2" }),
|
|
4863
|
+
" ",
|
|
4864
|
+
t2(s.labelKey)
|
|
4865
|
+
] });
|
|
4866
|
+
}
|
|
4867
|
+
function InstallmentRow({
|
|
4868
|
+
number,
|
|
4869
|
+
amount,
|
|
4870
|
+
status,
|
|
4871
|
+
dueDate,
|
|
4872
|
+
onDueDateChange,
|
|
4873
|
+
dueExtra,
|
|
4874
|
+
source,
|
|
4875
|
+
actions,
|
|
4876
|
+
onClick
|
|
4877
|
+
}) {
|
|
4878
|
+
const t2 = useTranslation();
|
|
4879
|
+
const stop = (e) => e.stopPropagation();
|
|
4880
|
+
return /* @__PURE__ */ jsxs(
|
|
4881
|
+
"div",
|
|
4882
|
+
{
|
|
4883
|
+
className: `flex flex-wrap items-center gap-3 px-4 py-3 ${onClick ? "cursor-pointer transition-colors hover:bg-muted/20" : ""}`,
|
|
4884
|
+
onClick,
|
|
4885
|
+
children: [
|
|
4886
|
+
/* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
|
|
4887
|
+
"#",
|
|
4888
|
+
number
|
|
4889
|
+
] }),
|
|
4890
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-start gap-0.5", children: [
|
|
4891
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4892
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: amount }),
|
|
4893
|
+
status && /* @__PURE__ */ jsx(InstallmentStatusBadge, { status })
|
|
4894
|
+
] }),
|
|
4895
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground", onClick: stop, children: [
|
|
4896
|
+
t2("financial.payment.due"),
|
|
4897
|
+
onDueDateChange ? /* @__PURE__ */ jsx(DatePicker, { value: dueDate, variant: "inline", format: "numeric", onChange: onDueDateChange }) : dueDate,
|
|
4898
|
+
dueExtra
|
|
4899
|
+
] })
|
|
4900
|
+
] }),
|
|
4901
|
+
/* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", onClick: stop, children: source }),
|
|
4902
|
+
(actions || onClick) && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", onClick: stop, children: [
|
|
4903
|
+
actions,
|
|
4904
|
+
onClick && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
4905
|
+
] })
|
|
4906
|
+
]
|
|
4907
|
+
}
|
|
4908
|
+
);
|
|
4909
|
+
}
|
|
4910
|
+
var BTN = "inline-flex shrink-0 items-center gap-1 px-3 py-1.5 text-xs font-medium transition-colors active:shadow-button-inset disabled:pointer-events-none disabled:opacity-40";
|
|
4911
|
+
function InstallmentPayButton({ onClick, disabled, title, children, variant = "primary" }) {
|
|
4912
|
+
return /* @__PURE__ */ jsxs(
|
|
4913
|
+
"button",
|
|
4914
|
+
{
|
|
4915
|
+
type: "button",
|
|
4916
|
+
onClick,
|
|
4917
|
+
disabled,
|
|
4918
|
+
title,
|
|
4919
|
+
className: variant === "primary" ? `${BTN} rounded-button border border-primary bg-primary text-primary-foreground shadow-button-primary hover:bg-primary/90` : `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`,
|
|
4920
|
+
children: [
|
|
4921
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
4922
|
+
children
|
|
4923
|
+
]
|
|
4924
|
+
}
|
|
4925
|
+
);
|
|
4926
|
+
}
|
|
4927
|
+
function InstallmentActionButton({ onClick, icon: Icon, children }) {
|
|
4928
|
+
return /* @__PURE__ */ jsxs("button", { type: "button", onClick, className: `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`, children: [
|
|
4929
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
|
|
4930
|
+
children
|
|
4608
4931
|
] });
|
|
4609
4932
|
}
|
|
4933
|
+
function InstallmentReceiptButton({ onClick, title, attached, pending: pending2, count }) {
|
|
4934
|
+
return /* @__PURE__ */ jsxs(
|
|
4935
|
+
"button",
|
|
4936
|
+
{
|
|
4937
|
+
type: "button",
|
|
4938
|
+
onClick,
|
|
4939
|
+
title,
|
|
4940
|
+
"aria-label": title,
|
|
4941
|
+
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 ${attached ? "text-success" : pending2 ? "text-primary" : ""}`,
|
|
4942
|
+
children: [
|
|
4943
|
+
/* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3" }),
|
|
4944
|
+
!!count && /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: count })
|
|
4945
|
+
]
|
|
4946
|
+
}
|
|
4947
|
+
);
|
|
4948
|
+
}
|
|
4610
4949
|
|
|
4611
4950
|
// src/views/focusItem.ts
|
|
4612
4951
|
var CHAVE = "fayz:financial:focus-item";
|
|
@@ -4631,9 +4970,9 @@ function consumirItemParaAbrir(invoiceId) {
|
|
|
4631
4970
|
}
|
|
4632
4971
|
}
|
|
4633
4972
|
function useStockedItemTypes(itemTypes, entityLookups) {
|
|
4634
|
-
const [empty, setEmpty] =
|
|
4973
|
+
const [empty, setEmpty] = React25__default.useState(null);
|
|
4635
4974
|
const backed = itemTypes.filter((type) => entityLookups[type.value]?.count).map((type) => type.value).sort().join(",");
|
|
4636
|
-
|
|
4975
|
+
React25__default.useEffect(() => {
|
|
4637
4976
|
const kinds = backed ? backed.split(",") : [];
|
|
4638
4977
|
if (!kinds.length) {
|
|
4639
4978
|
setEmpty(null);
|
|
@@ -4654,7 +4993,7 @@ function useStockedItemTypes(itemTypes, entityLookups) {
|
|
|
4654
4993
|
cancelled = true;
|
|
4655
4994
|
};
|
|
4656
4995
|
}, [backed]);
|
|
4657
|
-
return
|
|
4996
|
+
return React25__default.useMemo(
|
|
4658
4997
|
() => empty?.size ? itemTypes.filter((type) => !empty.has(type.value)) : itemTypes,
|
|
4659
4998
|
[itemTypes, empty]
|
|
4660
4999
|
);
|
|
@@ -4732,95 +5071,6 @@ function summarizeInvoiceLines(rows, visible = 2) {
|
|
|
4732
5071
|
function invoiceOpensForEditing(status) {
|
|
4733
5072
|
return status !== "paid" && status !== "cancelled";
|
|
4734
5073
|
}
|
|
4735
|
-
function LocationSelector({ locations, value, onChange }) {
|
|
4736
|
-
const t2 = useTranslation();
|
|
4737
|
-
const [open, setOpen] = useState(false);
|
|
4738
|
-
const [search, setSearch] = useState("");
|
|
4739
|
-
const ref = useRef(null);
|
|
4740
|
-
useEffect(() => {
|
|
4741
|
-
function handler(e) {
|
|
4742
|
-
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
4743
|
-
}
|
|
4744
|
-
document.addEventListener("mousedown", handler);
|
|
4745
|
-
return () => document.removeEventListener("mousedown", handler);
|
|
4746
|
-
}, []);
|
|
4747
|
-
const selected = locations.find((l) => l.id === value);
|
|
4748
|
-
const filtered = locations.filter(
|
|
4749
|
-
(l) => !search.trim() || l.name.toLowerCase().includes(search.toLowerCase())
|
|
4750
|
-
);
|
|
4751
|
-
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
4752
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.unit") }),
|
|
4753
|
-
/* @__PURE__ */ jsxs(
|
|
4754
|
-
"button",
|
|
4755
|
-
{
|
|
4756
|
-
type: "button",
|
|
4757
|
-
onClick: () => setOpen(!open),
|
|
4758
|
-
className: "w-full mt-1 flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm text-left hover:bg-muted/30 transition-colors",
|
|
4759
|
-
children: [
|
|
4760
|
-
/* @__PURE__ */ jsx(Building2, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
4761
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? (value ? t2("financial.invoiceForm.selectUnit") : "Todas / sem unidade") }),
|
|
4762
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
|
|
4763
|
-
]
|
|
4764
|
-
}
|
|
4765
|
-
),
|
|
4766
|
-
open && /* @__PURE__ */ jsxs("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden min-w-[260px]", children: [
|
|
4767
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [
|
|
4768
|
-
/* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
4769
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.selectUnitTitle") }),
|
|
4770
|
-
/* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "ml-auto text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }) })
|
|
4771
|
-
] }),
|
|
4772
|
-
/* @__PURE__ */ jsx("div", { className: "p-2 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5", children: [
|
|
4773
|
-
/* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
4774
|
-
/* @__PURE__ */ jsx(
|
|
4775
|
-
"input",
|
|
4776
|
-
{
|
|
4777
|
-
type: "text",
|
|
4778
|
-
autoFocus: true,
|
|
4779
|
-
value: search,
|
|
4780
|
-
onChange: (e) => setSearch(e.target.value),
|
|
4781
|
-
placeholder: t2("financial.invoiceForm.searchUnit"),
|
|
4782
|
-
className: "flex-1 bg-transparent text-sm outline-none"
|
|
4783
|
-
}
|
|
4784
|
-
)
|
|
4785
|
-
] }) }),
|
|
4786
|
-
/* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-auto py-1", children: [
|
|
4787
|
-
/* @__PURE__ */ jsx(
|
|
4788
|
-
"button",
|
|
4789
|
-
{
|
|
4790
|
-
type: "button",
|
|
4791
|
-
onClick: () => {
|
|
4792
|
-
onChange("");
|
|
4793
|
-
setOpen(false);
|
|
4794
|
-
setSearch("");
|
|
4795
|
-
},
|
|
4796
|
-
className: `w-full px-3 py-2.5 text-left text-sm hover:bg-muted/50 ${!value ? "bg-muted/30" : ""}`,
|
|
4797
|
-
children: "Todas / sem unidade"
|
|
4798
|
-
}
|
|
4799
|
-
),
|
|
4800
|
-
filtered.map((loc) => /* @__PURE__ */ jsxs(
|
|
4801
|
-
"button",
|
|
4802
|
-
{
|
|
4803
|
-
onClick: () => {
|
|
4804
|
-
onChange(loc.id);
|
|
4805
|
-
setOpen(false);
|
|
4806
|
-
setSearch("");
|
|
4807
|
-
},
|
|
4808
|
-
className: `w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-muted/50 transition-colors ${loc.id === value ? "bg-muted/30" : ""}`,
|
|
4809
|
-
children: [
|
|
4810
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-muted shrink-0", children: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }) }),
|
|
4811
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
4812
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: loc.name }),
|
|
4813
|
-
loc.isHQ && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoiceForm.headquarters") })
|
|
4814
|
-
] })
|
|
4815
|
-
]
|
|
4816
|
-
},
|
|
4817
|
-
loc.id
|
|
4818
|
-
)),
|
|
4819
|
-
filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: t2("financial.invoiceForm.noUnitsFound") })
|
|
4820
|
-
] })
|
|
4821
|
-
] })
|
|
4822
|
-
] });
|
|
4823
|
-
}
|
|
4824
5074
|
var formId = 1;
|
|
4825
5075
|
function fid() {
|
|
4826
5076
|
return `f${formId++}`;
|
|
@@ -4855,11 +5105,9 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4855
5105
|
const config = useFinancialConfig();
|
|
4856
5106
|
const provider = useFinancialProvider();
|
|
4857
5107
|
const { currency, itemTypes: itemTypesRaw = [], locations = [] } = config;
|
|
4858
|
-
const unitMode =
|
|
4859
|
-
const activeUnitId = useActiveUnitId();
|
|
5108
|
+
const { unitMode, activeUnitId, choices: unitChoices } = useWritableUnits(locations);
|
|
4860
5109
|
const reachableUnits = useUnitFilter().options;
|
|
4861
|
-
const
|
|
4862
|
-
const showUnitField = unitChoices.length > 1;
|
|
5110
|
+
const showUnitField = unitMode ? reachableUnits.length > 1 : unitChoices.length > 1;
|
|
4863
5111
|
const [giftCardsEnabled, setGiftCardsEnabled] = useState(false);
|
|
4864
5112
|
useEffect(() => {
|
|
4865
5113
|
let cancelled = false;
|
|
@@ -4877,7 +5125,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4877
5125
|
};
|
|
4878
5126
|
}, [direction, provider]);
|
|
4879
5127
|
const giftCardLookup = useMemo(() => {
|
|
4880
|
-
if (!giftCardsEnabled || !provider.
|
|
5128
|
+
if (!giftCardsEnabled || !provider.findGiftCardByCode) return null;
|
|
4881
5129
|
const asOption = (card) => ({
|
|
4882
5130
|
id: card.id,
|
|
4883
5131
|
label: `Gift Card ${card.code.replace(/(.{4})(?=.)/g, "$1 ")}`,
|
|
@@ -4886,30 +5134,43 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4886
5134
|
price: card.faceValue,
|
|
4887
5135
|
data: { price: card.faceValue, code: card.code, state: card.state }
|
|
4888
5136
|
});
|
|
5137
|
+
const resolve = async (raw) => {
|
|
5138
|
+
const card = await provider.findGiftCardByCode(raw);
|
|
5139
|
+
if (!card) throw new Error(t2("financial.giftCardLine.notFound"));
|
|
5140
|
+
if (card.state === "revoked") throw new Error(t2("financial.giftCardLine.revoked"));
|
|
5141
|
+
if (card.state === "expired") throw new Error(t2("financial.giftCardLine.expired"));
|
|
5142
|
+
if (card.state !== "issued") throw new Error(t2("financial.giftCardLine.alreadySold"));
|
|
5143
|
+
return asOption(card);
|
|
5144
|
+
};
|
|
4889
5145
|
return {
|
|
4890
5146
|
async search(query) {
|
|
4891
|
-
|
|
4892
|
-
|
|
5147
|
+
try {
|
|
5148
|
+
return [await resolve(query)];
|
|
5149
|
+
} catch {
|
|
5150
|
+
return [];
|
|
5151
|
+
}
|
|
4893
5152
|
},
|
|
4894
|
-
async getById(
|
|
4895
|
-
|
|
4896
|
-
const card = cards.find((entry) => entry.id === id);
|
|
4897
|
-
return card ? asOption(card) : null;
|
|
5153
|
+
async getById() {
|
|
5154
|
+
return null;
|
|
4898
5155
|
},
|
|
4899
|
-
|
|
4900
|
-
|
|
5156
|
+
byCode: {
|
|
5157
|
+
resolve,
|
|
5158
|
+
label: t2("financial.giftCardLine.label"),
|
|
5159
|
+
placeholder: t2("financial.giftCardLine.placeholder"),
|
|
5160
|
+
hint: t2("financial.giftCardLine.hint"),
|
|
5161
|
+
display: ({ description }) => description.replace(/^Gift Card\s+/, "")
|
|
4901
5162
|
}
|
|
4902
5163
|
};
|
|
4903
|
-
}, [giftCardsEnabled, provider]);
|
|
5164
|
+
}, [giftCardsEnabled, provider, t2]);
|
|
4904
5165
|
const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
|
|
4905
|
-
const translatedItemTypes =
|
|
5166
|
+
const translatedItemTypes = React25__default.useMemo(
|
|
4906
5167
|
() => itemTypesRaw.map((tp) => {
|
|
4907
5168
|
const traduzido = t2(`financial.itemType.${tp.value}`);
|
|
4908
5169
|
return traduzido === `financial.itemType.${tp.value}` ? tp : { ...tp, label: traduzido };
|
|
4909
5170
|
}),
|
|
4910
5171
|
[itemTypesRaw, t2]
|
|
4911
5172
|
);
|
|
4912
|
-
const offeredItemTypes =
|
|
5173
|
+
const offeredItemTypes = React25__default.useMemo(
|
|
4913
5174
|
() => availableInvoiceItemTypes(
|
|
4914
5175
|
giftCardsEnabled && !translatedItemTypes.some((type) => type.value === "gift_card") ? [...translatedItemTypes, { value: "gift_card", label: t2("financial.itemType.gift_card"), icon: "Gift" }] : translatedItemTypes,
|
|
4915
5176
|
Object.keys(entityLookups),
|
|
@@ -4929,7 +5190,9 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4929
5190
|
const createInvoice = useFinancialStore((s) => s.createInvoice);
|
|
4930
5191
|
const guardMovements = useLimitGuard("movements_month");
|
|
4931
5192
|
const can = usePermissionOptional();
|
|
5193
|
+
const canManageFinancial = can("financial", "manage");
|
|
4932
5194
|
const isEdit = !!editId;
|
|
5195
|
+
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
|
|
4933
5196
|
useEffect(() => {
|
|
4934
5197
|
void fetchBankAccounts();
|
|
4935
5198
|
void fetchPaymentMethods();
|
|
@@ -5130,7 +5393,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5130
5393
|
}, [items.length]);
|
|
5131
5394
|
const somaParcelas = Math.round(installments.reduce((s, i) => s + i.amount, 0) * 100);
|
|
5132
5395
|
useEffect(() => {
|
|
5133
|
-
if (installments.length === 0
|
|
5396
|
+
if (installments.length === 0) return;
|
|
5397
|
+
if (scheduleTotal <= 0) {
|
|
5398
|
+
if (!isEdit) setInstallments([]);
|
|
5399
|
+
return;
|
|
5400
|
+
}
|
|
5134
5401
|
if (somaParcelas === Math.round(scheduleTotal * 100)) return;
|
|
5135
5402
|
const count = installments.length;
|
|
5136
5403
|
const perInstallment = Math.floor(scheduleTotal * 100 / count) / 100;
|
|
@@ -5139,7 +5406,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5139
5406
|
...inst,
|
|
5140
5407
|
amount: i === 0 ? perInstallment + remainder : perInstallment
|
|
5141
5408
|
})));
|
|
5142
|
-
}, [scheduleTotal, somaParcelas, installments.length]);
|
|
5409
|
+
}, [scheduleTotal, somaParcelas, installments.length, isEdit]);
|
|
5143
5410
|
function addItemOfKind(kind) {
|
|
5144
5411
|
const newItem = {
|
|
5145
5412
|
_id: fid(),
|
|
@@ -5205,13 +5472,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5205
5472
|
if (isEdit || totalAmount <= 0 || installments.length > 0) return;
|
|
5206
5473
|
generateInstallments(1);
|
|
5207
5474
|
}, [isEdit, totalAmount, installments.length]);
|
|
5208
|
-
const fromCard =
|
|
5475
|
+
const fromCard = React25__default.useMemo(
|
|
5209
5476
|
() => fromCardId ? bankAccounts.find((a) => a.id === fromCardId) : void 0,
|
|
5210
5477
|
[fromCardId, bankAccounts]
|
|
5211
5478
|
);
|
|
5212
5479
|
const cardLast4 = (fromCard?.accountNumber ?? "").replace(/\D/g, "").slice(-4);
|
|
5213
5480
|
const nomeJaTemOsDigitos = !!cardLast4 && (fromCard?.name ?? "").replace(/\D/g, "").endsWith(cardLast4);
|
|
5214
|
-
const cardMethodName =
|
|
5481
|
+
const cardMethodName = React25__default.useMemo(() => {
|
|
5215
5482
|
const id = installments[0]?.payMethodTypeId;
|
|
5216
5483
|
const escolhida = id ? paymentMethodTypes.find((tp) => tp.id === id) : void 0;
|
|
5217
5484
|
return (escolhida ?? paymentMethodTypes.find((tp) => tp.transactionType === "credit_card"))?.name;
|
|
@@ -5532,7 +5799,6 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5532
5799
|
const saved = savedMovements[i];
|
|
5533
5800
|
const isPaid2 = saved?.status === "paid";
|
|
5534
5801
|
const isPartial = saved?.status === "partial";
|
|
5535
|
-
const StatusIcon = isPaid2 ? CircleCheckBig : CircleDashed;
|
|
5536
5802
|
const canAttach = isEdit ? !!saved && !isPaid2 : true;
|
|
5537
5803
|
const semFonte = Math.abs(inst.amount) < 5e-3 && !isPaid2 && !isPartial;
|
|
5538
5804
|
const fonteTravada = isPaid2 || isPartial;
|
|
@@ -5540,75 +5806,61 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5540
5806
|
const contaEscolhida = bankAccounts.find((a) => a.id === inst.payAccountId);
|
|
5541
5807
|
const faltaParaPagar = !inst.payMethodTypeId ? t2("financial.payment.methodRequired") : !inst.payAccountId ? t2("financial.payment.accountRequired") : null;
|
|
5542
5808
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
5543
|
-
/* @__PURE__ */
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
i + 1
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: "\u2014" })
|
|
5557
|
-
) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
|
|
5558
|
-
InstallmentSourceFields,
|
|
5559
|
-
{
|
|
5560
|
-
direction,
|
|
5561
|
-
value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
|
|
5562
|
-
onChange: (next) => patch(next)
|
|
5563
|
-
}
|
|
5564
|
-
),
|
|
5565
|
-
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [
|
|
5566
|
-
!hasPayments ? /* @__PURE__ */ jsx(
|
|
5567
|
-
DatePicker,
|
|
5809
|
+
/* @__PURE__ */ jsx(
|
|
5810
|
+
InstallmentRow,
|
|
5811
|
+
{
|
|
5812
|
+
number: i + 1,
|
|
5813
|
+
amount: money4(inst.amount),
|
|
5814
|
+
status: saved?.status,
|
|
5815
|
+
dueDate: inst.dueDate,
|
|
5816
|
+
onDueDateChange: !hasPayments ? (v) => patch({ dueDate: v }) : void 0,
|
|
5817
|
+
source: semFonte ? (
|
|
5818
|
+
// A zero-value line (gift, 100% discount) has no method to ask for.
|
|
5819
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" })
|
|
5820
|
+
) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
|
|
5821
|
+
InstallmentSourceFields,
|
|
5568
5822
|
{
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
onChange: (v) => patch({ dueDate: v })
|
|
5573
|
-
}
|
|
5574
|
-
) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
|
|
5575
|
-
canAttach && /* @__PURE__ */ jsx(
|
|
5576
|
-
"button",
|
|
5577
|
-
{
|
|
5578
|
-
type: "button",
|
|
5579
|
-
onClick: () => {
|
|
5580
|
-
if (!inst.receiptUrl && pendingReceipt) {
|
|
5581
|
-
patch({ receiptUrl: pendingReceipt });
|
|
5582
|
-
setPendingReceipt(void 0);
|
|
5583
|
-
return;
|
|
5584
|
-
}
|
|
5585
|
-
setReceiptTarget(i);
|
|
5586
|
-
receiptPicker.open();
|
|
5587
|
-
},
|
|
5588
|
-
title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
|
|
5589
|
-
"aria-label": !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
|
|
5590
|
-
className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors ${inst.receiptUrl ? "text-success hover:bg-success-soft" : pendingReceipt ? "text-primary hover:bg-primary/10" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
5591
|
-
children: inst.receiptUrl ? /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Camera, { className: "h-3.5 w-3.5" })
|
|
5823
|
+
direction,
|
|
5824
|
+
value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
|
|
5825
|
+
onChange: (next) => patch(next)
|
|
5592
5826
|
}
|
|
5593
5827
|
),
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5828
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5829
|
+
(canAttach || isEdit) && /* @__PURE__ */ jsx(
|
|
5830
|
+
InstallmentReceiptButton,
|
|
5831
|
+
{
|
|
5832
|
+
attached: !!inst.receiptUrl,
|
|
5833
|
+
pending: !inst.receiptUrl && !!pendingReceipt,
|
|
5834
|
+
title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
|
|
5835
|
+
onClick: () => {
|
|
5836
|
+
if (isEdit && editId) {
|
|
5837
|
+
setAttachmentsOpen(true);
|
|
5838
|
+
return;
|
|
5839
|
+
}
|
|
5840
|
+
if (!inst.receiptUrl && pendingReceipt) {
|
|
5841
|
+
patch({ receiptUrl: pendingReceipt });
|
|
5842
|
+
setPendingReceipt(void 0);
|
|
5843
|
+
return;
|
|
5844
|
+
}
|
|
5845
|
+
setReceiptTarget(i);
|
|
5846
|
+
receiptPicker.open();
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
),
|
|
5850
|
+
!isPaid2 && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsx(
|
|
5851
|
+
InstallmentPayButton,
|
|
5852
|
+
{
|
|
5853
|
+
disabled: saving || !!faltaParaPagar,
|
|
5854
|
+
title: faltaParaPagar ?? void 0,
|
|
5855
|
+
onClick: () => {
|
|
5856
|
+
void pagarParcela(i);
|
|
5857
|
+
},
|
|
5858
|
+
children: t2("financial.invoice.pay")
|
|
5859
|
+
}
|
|
5860
|
+
)
|
|
5861
|
+
] })
|
|
5862
|
+
}
|
|
5863
|
+
),
|
|
5612
5864
|
!isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
|
|
5613
5865
|
/* @__PURE__ */ jsx(
|
|
5614
5866
|
"img",
|
|
@@ -5699,7 +5951,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5699
5951
|
] }) });
|
|
5700
5952
|
const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5701
5953
|
/* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
5702
|
-
showUnitField && /* @__PURE__ */ jsx(
|
|
5954
|
+
showUnitField && /* @__PURE__ */ jsx(UnitField, { choices: unitChoices, value: unitId, onChange: setUnitId }),
|
|
5703
5955
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
5704
5956
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
|
|
5705
5957
|
isEdit && hasPayments ? /* @__PURE__ */ jsx("p", { className: "mt-1 px-1 py-2 text-sm", children: (/* @__PURE__ */ new Date(`${invoiceDate}T00:00:00`)).toLocaleDateString() }) : /* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
|
|
@@ -5864,7 +6116,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5864
6116
|
label: accountLabel(a),
|
|
5865
6117
|
subtitle: a.accountType ?? void 0
|
|
5866
6118
|
})),
|
|
5867
|
-
onChange: (id) => updateItem(item._id, { accountId: id || void 0 })
|
|
6119
|
+
onChange: (id) => updateItem(item._id, { accountId: id || void 0 }),
|
|
6120
|
+
onManage: canManageFinancial ? () => adminNavigateTo(CHART_OF_ACCOUNTS_ROUTE) : void 0,
|
|
6121
|
+
manageLabel: t2("financial.invoiceForm.manageChart"),
|
|
6122
|
+
clearLabel: t2("financial.invoiceForm.clear")
|
|
5868
6123
|
}
|
|
5869
6124
|
)
|
|
5870
6125
|
)
|
|
@@ -5887,7 +6142,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5887
6142
|
id: c.id,
|
|
5888
6143
|
label: c.code ? `${c.code} - ${c.name}` : c.name
|
|
5889
6144
|
})),
|
|
5890
|
-
onChange: (id) => updateItem(item._id, { costCenterId: id || void 0 })
|
|
6145
|
+
onChange: (id) => updateItem(item._id, { costCenterId: id || void 0 }),
|
|
6146
|
+
onManage: canManageFinancial ? () => adminNavigateTo(COST_CENTERS_ROUTE) : void 0,
|
|
6147
|
+
manageLabel: t2("financial.invoiceForm.manageCostCenters"),
|
|
6148
|
+
clearLabel: t2("financial.invoiceForm.clear")
|
|
5891
6149
|
}
|
|
5892
6150
|
)
|
|
5893
6151
|
}] : []
|
|
@@ -5920,6 +6178,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5920
6178
|
}
|
|
5921
6179
|
),
|
|
5922
6180
|
receiptPicker.input,
|
|
6181
|
+
attachmentsOpen && editId && /* @__PURE__ */ jsx(InvoiceAttachmentsModal, { invoiceId: editId, onClose: () => setAttachmentsOpen(false) }),
|
|
5923
6182
|
payingMovement && /* @__PURE__ */ jsx(
|
|
5924
6183
|
PaymentModal,
|
|
5925
6184
|
{
|
|
@@ -6015,6 +6274,11 @@ function groupPaymentsByBill(bills, payments) {
|
|
|
6015
6274
|
}
|
|
6016
6275
|
return { byBillId, unattributed };
|
|
6017
6276
|
}
|
|
6277
|
+
var reversalOf = (m) => m.metadata?.reversal_of;
|
|
6278
|
+
function reversiblePayments(payments) {
|
|
6279
|
+
const reversed = new Set(payments.map(reversalOf).filter(Boolean));
|
|
6280
|
+
return payments.filter((m) => !reversalOf(m) && !reversed.has(m.id));
|
|
6281
|
+
}
|
|
6018
6282
|
|
|
6019
6283
|
// src/lib/payment-how.ts
|
|
6020
6284
|
var NOT_A_HOLDER = /* @__PURE__ */ new Set(["person", "system"]);
|
|
@@ -6062,6 +6326,7 @@ function PaymentReceiptModal({
|
|
|
6062
6326
|
const { currency } = useFinancialConfig();
|
|
6063
6327
|
const fmt = (n) => formatCurrency(n, currency);
|
|
6064
6328
|
const ordenados = [...payments].sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
|
|
6329
|
+
const estornaveis = new Set(reversiblePayments(ordenados).map((m) => m.id));
|
|
6065
6330
|
const total = ordenados.reduce((soma, m) => soma + (ehEstorno(m) ? -1 : 1) * (m.paidAmount || m.amount), 0);
|
|
6066
6331
|
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
6067
6332
|
if (!o) onClose();
|
|
@@ -6101,7 +6366,7 @@ function PaymentReceiptModal({
|
|
|
6101
6366
|
] }),
|
|
6102
6367
|
/* @__PURE__ */ jsx("span", { className: "text-xs", children: payment.notes })
|
|
6103
6368
|
] }),
|
|
6104
|
-
onReverse &&
|
|
6369
|
+
onReverse && estornaveis.has(payment.id) && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
|
|
6105
6370
|
/* @__PURE__ */ jsx(Hint, { label: t2("financial.paymentReceipt.unlinkUnavailable"), children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "w-full", disabled: true, children: [
|
|
6106
6371
|
/* @__PURE__ */ jsx(Unlink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
6107
6372
|
t2("financial.paymentReceipt.unlink")
|
|
@@ -6133,14 +6398,6 @@ var ITEM_ICONS = {
|
|
|
6133
6398
|
gift_card: Gift,
|
|
6134
6399
|
other: Receipt
|
|
6135
6400
|
};
|
|
6136
|
-
var HOW_ICONS = {
|
|
6137
|
-
cash: Banknote,
|
|
6138
|
-
pix: QrCode,
|
|
6139
|
-
credit_card: CreditCard,
|
|
6140
|
-
debit_card: CreditCard,
|
|
6141
|
-
bank_transfer: ArrowRightLeft,
|
|
6142
|
-
check: FileCheck
|
|
6143
|
-
};
|
|
6144
6401
|
var STATUS_COLORS = {
|
|
6145
6402
|
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
|
|
6146
6403
|
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
|
|
@@ -6174,7 +6431,6 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6174
6431
|
const unitName = useUnitStore((st) => unitMode && invoice?.unitId ? st.byId[invoice.unitId]?.name : void 0);
|
|
6175
6432
|
const [items, setItems] = useState([]);
|
|
6176
6433
|
const [attachments, setAttachments] = useState([]);
|
|
6177
|
-
const [viewing, setViewing] = useState(null);
|
|
6178
6434
|
const [movements, setMovements] = useState([]);
|
|
6179
6435
|
const [payments, setPayments] = useState([]);
|
|
6180
6436
|
const [holders, setHolders] = useState(/* @__PURE__ */ new Map());
|
|
@@ -6184,13 +6440,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6184
6440
|
const [receiptBill, setReceiptBill] = useState(null);
|
|
6185
6441
|
const [reverseReason, setReverseReason] = useState("");
|
|
6186
6442
|
const [payingMovement, setPayingMovement] = useState(null);
|
|
6187
|
-
const [
|
|
6443
|
+
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
|
|
6188
6444
|
const [pendingReceipt, setPendingReceipt] = useState();
|
|
6189
|
-
const receiptPicker = useReceiptPicker((dataUrl) => {
|
|
6190
|
-
setPendingReceipt(dataUrl);
|
|
6191
|
-
setPayingMovement(receiptFor);
|
|
6192
|
-
setReceiptFor(null);
|
|
6193
|
-
});
|
|
6194
6445
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
6195
6446
|
const [confirmCancel, setConfirmCancel] = useState(false);
|
|
6196
6447
|
const [cancelling, setCancelling] = useState(false);
|
|
@@ -6221,6 +6472,17 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6221
6472
|
void loadData();
|
|
6222
6473
|
}
|
|
6223
6474
|
}
|
|
6475
|
+
async function patchInvoiceDate(invoiceDate) {
|
|
6476
|
+
if (!invoice || !invoiceDate || invoiceDate === invoice.invoiceDate) return;
|
|
6477
|
+
const antes = invoice;
|
|
6478
|
+
setInvoice({ ...invoice, invoiceDate });
|
|
6479
|
+
try {
|
|
6480
|
+
await provider.updateInvoice(invoice.id, { invoiceDate });
|
|
6481
|
+
} catch (e) {
|
|
6482
|
+
setInvoice(antes);
|
|
6483
|
+
toast.error(e?.message ?? String(e));
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6224
6486
|
const reqRef = useRef(0);
|
|
6225
6487
|
async function loadData() {
|
|
6226
6488
|
const req = ++reqRef.current;
|
|
@@ -6311,8 +6573,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6311
6573
|
setCancelling(false);
|
|
6312
6574
|
}
|
|
6313
6575
|
}
|
|
6314
|
-
const [chartNames, setChartNames] =
|
|
6315
|
-
|
|
6576
|
+
const [chartNames, setChartNames] = React25__default.useState(/* @__PURE__ */ new Map());
|
|
6577
|
+
React25__default.useEffect(() => {
|
|
6316
6578
|
let cancelled2 = false;
|
|
6317
6579
|
void provider.getChartOfAccounts().then((rows) => {
|
|
6318
6580
|
if (!cancelled2) setChartNames(new Map(rows.map((r) => [r.id, r.name])));
|
|
@@ -6432,7 +6694,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6432
6694
|
{ id: "total", label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
|
|
6433
6695
|
{ id: "items", label: t2("financial.invoice.items"), value: items.length, icon: LayoutList },
|
|
6434
6696
|
{ id: "installments", label: t2("financial.invoice.installments"), value: `${invoice.totalInstallments}x`, icon: Layers },
|
|
6435
|
-
{
|
|
6697
|
+
{
|
|
6698
|
+
id: "date",
|
|
6699
|
+
label: t2("financial.invoice.date"),
|
|
6700
|
+
icon: CalendarDays,
|
|
6701
|
+
value: invoice.status !== "cancelled" ? /* @__PURE__ */ jsx(DatePicker, { value: invoice.invoiceDate, variant: "inline", format: "numeric", onChange: (v) => {
|
|
6702
|
+
void patchInvoiceDate(v);
|
|
6703
|
+
} }) : invoice.invoiceDate
|
|
6704
|
+
}
|
|
6436
6705
|
],
|
|
6437
6706
|
customerLabel: direction === "debit" ? t2("financial.invoice.payTo") : t2("financial.invoice.receiveFrom"),
|
|
6438
6707
|
unit: unitName ? { label: t2("financial.invoice.unit"), value: unitName } : void 0,
|
|
@@ -6450,7 +6719,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6450
6719
|
"button",
|
|
6451
6720
|
{
|
|
6452
6721
|
type: "button",
|
|
6453
|
-
onClick: () =>
|
|
6722
|
+
onClick: () => setAttachmentsOpen(true),
|
|
6454
6723
|
title: attachments.map((a) => a.fileName).join("\n"),
|
|
6455
6724
|
className: "mb-2 inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
6456
6725
|
children: [
|
|
@@ -6465,14 +6734,12 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6465
6734
|
const isPaid2 = mov.status === "paid";
|
|
6466
6735
|
const isPartial = mov.status === "partial";
|
|
6467
6736
|
const hasPaidDetails = isPaid2 || isPartial;
|
|
6468
|
-
const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
|
|
6469
|
-
const MovIcon = movStatus.icon;
|
|
6470
6737
|
const instPayments = settlements.byBillId.get(mov.id) ?? [];
|
|
6471
6738
|
const hows = instPayments.map((payment) => describePayment(payment, howCtx));
|
|
6472
6739
|
const paymentSummary = [...new Set(hows.map((how2) => [how2.method, how2.account?.name].filter(Boolean).join(" \xB7 ")).filter(Boolean))].join(" / ");
|
|
6473
6740
|
const how = hows[0];
|
|
6474
6741
|
const howCard = how?.account?.accountType === "card_payable" ? how.account : void 0;
|
|
6475
|
-
const HowIcon =
|
|
6742
|
+
const HowIcon = METHOD_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
|
|
6476
6743
|
const srcMeta = mov.metadata ?? {};
|
|
6477
6744
|
const srcStr = (v) => typeof v === "string" && v ? v : void 0;
|
|
6478
6745
|
const srcTypeId = srcStr(srcMeta.payMethodTypeId);
|
|
@@ -6480,66 +6747,41 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6480
6747
|
const editavel = !hasPaidDetails && invoice.status !== "cancelled";
|
|
6481
6748
|
const canChooseSource = editavel && Math.abs(mov.amount) >= 5e-3;
|
|
6482
6749
|
const faltaParaPagar = !srcTypeId ? t2("financial.payment.methodRequired") : !srcAccountId ? t2("financial.payment.accountRequired") : null;
|
|
6483
|
-
|
|
6484
|
-
|
|
6750
|
+
const ativa = invoice.status !== "cancelled";
|
|
6751
|
+
return /* @__PURE__ */ jsx(
|
|
6752
|
+
InstallmentRow,
|
|
6485
6753
|
{
|
|
6486
|
-
|
|
6754
|
+
number: mov.installmentNumber ?? 1,
|
|
6755
|
+
amount: formatCurrency(mov.amount, currency),
|
|
6756
|
+
status: mov.status,
|
|
6757
|
+
dueDate: mov.dueDate,
|
|
6758
|
+
onDueDateChange: editavel ? (v) => {
|
|
6759
|
+
void patchDueDate(mov, v);
|
|
6760
|
+
} : void 0,
|
|
6761
|
+
dueExtra: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6762
|
+
mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
|
|
6763
|
+
mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
|
|
6764
|
+
] }),
|
|
6487
6765
|
onClick: hasPaidDetails ? () => setReceiptBill(mov) : void 0,
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
t2(movStatus.labelKey)
|
|
6499
|
-
] }),
|
|
6500
|
-
/* @__PURE__ */ jsxs(
|
|
6501
|
-
"span",
|
|
6502
|
-
{
|
|
6503
|
-
className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground",
|
|
6504
|
-
onClick: (e) => e.stopPropagation(),
|
|
6505
|
-
children: [
|
|
6506
|
-
t2("financial.payment.due"),
|
|
6507
|
-
editavel ? /* @__PURE__ */ jsx(
|
|
6508
|
-
DatePicker,
|
|
6509
|
-
{
|
|
6510
|
-
value: mov.dueDate,
|
|
6511
|
-
variant: "inline",
|
|
6512
|
-
format: "numeric",
|
|
6513
|
-
onChange: (v) => {
|
|
6514
|
-
void patchDueDate(mov, v);
|
|
6515
|
-
}
|
|
6516
|
-
}
|
|
6517
|
-
) : mov.dueDate,
|
|
6518
|
-
mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
|
|
6519
|
-
mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
|
|
6520
|
-
]
|
|
6521
|
-
}
|
|
6522
|
-
)
|
|
6523
|
-
] }),
|
|
6524
|
-
canChooseSource && /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
|
|
6525
|
-
InstallmentSourceFields,
|
|
6526
|
-
{
|
|
6527
|
-
direction,
|
|
6528
|
-
value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
|
|
6529
|
-
onChange: (next) => {
|
|
6530
|
-
void patchSource(mov, {
|
|
6531
|
-
payMethodTypeId: next.payMethodTypeId ?? null,
|
|
6532
|
-
payAccountId: next.payAccountId ?? null
|
|
6533
|
-
});
|
|
6534
|
-
}
|
|
6766
|
+
source: canChooseSource ? /* @__PURE__ */ jsx(
|
|
6767
|
+
InstallmentSourceFields,
|
|
6768
|
+
{
|
|
6769
|
+
direction,
|
|
6770
|
+
value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
|
|
6771
|
+
onChange: (next) => {
|
|
6772
|
+
void patchSource(mov, {
|
|
6773
|
+
payMethodTypeId: next.payMethodTypeId ?? null,
|
|
6774
|
+
payAccountId: next.payAccountId ?? null
|
|
6775
|
+
});
|
|
6535
6776
|
}
|
|
6536
|
-
|
|
6777
|
+
}
|
|
6778
|
+
) : void 0,
|
|
6779
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6537
6780
|
paymentSummary && /* @__PURE__ */ jsx(
|
|
6538
6781
|
"button",
|
|
6539
6782
|
{
|
|
6540
6783
|
type: "button",
|
|
6541
|
-
onClick: (
|
|
6542
|
-
e.stopPropagation();
|
|
6784
|
+
onClick: () => {
|
|
6543
6785
|
if (hasPaidDetails) setReceiptBill(mov);
|
|
6544
6786
|
},
|
|
6545
6787
|
title: paymentSummary,
|
|
@@ -6548,72 +6790,38 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6548
6790
|
children: howCard ? /* @__PURE__ */ jsx(CardVisual, { lastFour: howCard.lastFour ?? void 0, color: howCard.color ?? void 0, brandId: howCard.cardBrandId ?? void 0 }) : HowIcon ? /* @__PURE__ */ jsx(HowIcon, { className: "h-4 w-4", "aria-hidden": true }) : null
|
|
6549
6791
|
}
|
|
6550
6792
|
),
|
|
6551
|
-
|
|
6552
|
-
|
|
6793
|
+
/* @__PURE__ */ jsx(
|
|
6794
|
+
InstallmentReceiptButton,
|
|
6553
6795
|
{
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
},
|
|
6559
|
-
title: t2("financial.quickTx.receiptCapture"),
|
|
6560
|
-
"aria-label": t2("financial.quickTx.receiptCapture"),
|
|
6561
|
-
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",
|
|
6562
|
-
children: /* @__PURE__ */ jsx(Camera, { className: "h-3 w-3" })
|
|
6796
|
+
title: "Anexos",
|
|
6797
|
+
attached: attachments.length > 0,
|
|
6798
|
+
count: attachments.length,
|
|
6799
|
+
onClick: () => setAttachmentsOpen(true)
|
|
6563
6800
|
}
|
|
6564
6801
|
),
|
|
6565
|
-
!isPaid2 && !isPartial &&
|
|
6566
|
-
|
|
6802
|
+
!isPaid2 && !isPartial && ativa && /* @__PURE__ */ jsxs(
|
|
6803
|
+
InstallmentPayButton,
|
|
6567
6804
|
{
|
|
6568
|
-
onClick: (
|
|
6569
|
-
e.stopPropagation();
|
|
6570
|
-
setPayingMovement(mov);
|
|
6571
|
-
},
|
|
6805
|
+
onClick: () => setPayingMovement(mov),
|
|
6572
6806
|
disabled: !!faltaParaPagar,
|
|
6573
6807
|
title: faltaParaPagar ?? void 0,
|
|
6574
|
-
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 disabled:opacity-40 disabled:pointer-events-none",
|
|
6575
6808
|
children: [
|
|
6576
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
6577
6809
|
t2("financial.invoice.pay"),
|
|
6578
6810
|
" ",
|
|
6579
6811
|
remaining < mov.amount ? formatCurrency(remaining, currency) : ""
|
|
6580
6812
|
]
|
|
6581
6813
|
}
|
|
6582
6814
|
),
|
|
6583
|
-
isPaid2 && instPayments.length > 0 &&
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
t2("financial.invoice.reverse")
|
|
6594
|
-
]
|
|
6595
|
-
}
|
|
6596
|
-
),
|
|
6597
|
-
isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
6598
|
-
"button",
|
|
6599
|
-
{
|
|
6600
|
-
onClick: (e) => {
|
|
6601
|
-
e.stopPropagation();
|
|
6602
|
-
setPayingMovement(mov);
|
|
6603
|
-
},
|
|
6604
|
-
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",
|
|
6605
|
-
children: [
|
|
6606
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
6607
|
-
t2("financial.invoice.pay"),
|
|
6608
|
-
" ",
|
|
6609
|
-
formatCurrency(remaining, currency)
|
|
6610
|
-
]
|
|
6611
|
-
}
|
|
6612
|
-
),
|
|
6613
|
-
hasPaidDetails && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
6614
|
-
]
|
|
6615
|
-
}
|
|
6616
|
-
) }, mov.id);
|
|
6815
|
+
isPaid2 && reversiblePayments(instPayments).length > 0 && ativa && /* @__PURE__ */ jsx(InstallmentActionButton, { icon: Undo2, onClick: () => setReversing(reversiblePayments(instPayments).at(-1)), children: t2("financial.invoice.reverse") }),
|
|
6816
|
+
isPartial && ativa && /* @__PURE__ */ jsxs(InstallmentPayButton, { variant: "outline", onClick: () => setPayingMovement(mov), children: [
|
|
6817
|
+
t2("financial.invoice.pay"),
|
|
6818
|
+
" ",
|
|
6819
|
+
formatCurrency(remaining, currency)
|
|
6820
|
+
] })
|
|
6821
|
+
] })
|
|
6822
|
+
},
|
|
6823
|
+
mov.id
|
|
6824
|
+
);
|
|
6617
6825
|
}) }),
|
|
6618
6826
|
settlements.unattributed.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t bg-warning-soft/40 px-4 py-2.5", children: [
|
|
6619
6827
|
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground", children: t2("financial.invoice.unlinkedPayments") }),
|
|
@@ -6632,14 +6840,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6632
6840
|
extraSections: sideBlocks
|
|
6633
6841
|
}
|
|
6634
6842
|
),
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
AttachmentViewer,
|
|
6843
|
+
attachmentsOpen && /* @__PURE__ */ jsx(
|
|
6844
|
+
InvoiceAttachmentsModal,
|
|
6638
6845
|
{
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6846
|
+
invoiceId: invoice.id,
|
|
6847
|
+
onClose: () => setAttachmentsOpen(false),
|
|
6848
|
+
onChanged: () => {
|
|
6849
|
+
void loadData();
|
|
6850
|
+
}
|
|
6643
6851
|
}
|
|
6644
6852
|
),
|
|
6645
6853
|
receiptBill && /* @__PURE__ */ jsx(
|
|
@@ -7362,7 +7570,7 @@ function CashRegistersView({ variant = "page" } = {}) {
|
|
|
7362
7570
|
const [tab, setTab] = useState("open");
|
|
7363
7571
|
const [reviewRows, setReviewRows] = useState(null);
|
|
7364
7572
|
const [reviewError, setReviewError] = useState(null);
|
|
7365
|
-
const loadReviews =
|
|
7573
|
+
const loadReviews = React25__default.useCallback(async () => {
|
|
7366
7574
|
try {
|
|
7367
7575
|
setReviewError(null);
|
|
7368
7576
|
setReviewRows(await provider.getCashSessionReviews?.() ?? []);
|
|
@@ -7962,7 +8170,7 @@ function StatementsView({ onNavigate }) {
|
|
|
7962
8170
|
"extrato"
|
|
7963
8171
|
);
|
|
7964
8172
|
};
|
|
7965
|
-
const headerActions =
|
|
8173
|
+
const headerActions = React25__default.useMemo(() => [{
|
|
7966
8174
|
id: "new-transfer",
|
|
7967
8175
|
label: t2("financial.statements.newTransfer"),
|
|
7968
8176
|
icon: ArrowRightLeft,
|
|
@@ -8158,15 +8366,15 @@ function CommissionsView() {
|
|
|
8158
8366
|
const fetchCommissions = useFinancialStore((s) => s.fetchCommissions);
|
|
8159
8367
|
const generateCommissions = useFinancialStore((s) => s.generateCommissions);
|
|
8160
8368
|
const provider = useFinancialProvider();
|
|
8161
|
-
const [from, setFrom] =
|
|
8162
|
-
const [to, setTo] =
|
|
8163
|
-
const [preset, setPreset] =
|
|
8164
|
-
const [running, setRunning] =
|
|
8165
|
-
const [lastRun, setLastRun] =
|
|
8166
|
-
const [generation, setGeneration] =
|
|
8167
|
-
const [busy, setBusy] =
|
|
8168
|
-
const [error, setError] =
|
|
8169
|
-
const loadGeneration =
|
|
8369
|
+
const [from, setFrom] = React25__default.useState(firstOfMonth());
|
|
8370
|
+
const [to, setTo] = React25__default.useState(today2());
|
|
8371
|
+
const [preset, setPreset] = React25__default.useState("this_month");
|
|
8372
|
+
const [running, setRunning] = React25__default.useState(false);
|
|
8373
|
+
const [lastRun, setLastRun] = React25__default.useState(null);
|
|
8374
|
+
const [generation, setGeneration] = React25__default.useState(null);
|
|
8375
|
+
const [busy, setBusy] = React25__default.useState(null);
|
|
8376
|
+
const [error, setError] = React25__default.useState(null);
|
|
8377
|
+
const loadGeneration = React25__default.useCallback(async () => {
|
|
8170
8378
|
try {
|
|
8171
8379
|
const gens = await provider.getCommissionGenerations?.() ?? [];
|
|
8172
8380
|
const doPeriodo = gens.filter((g) => g.periodStart === from && g.periodEnd === to);
|
|
@@ -8174,11 +8382,11 @@ function CommissionsView() {
|
|
|
8174
8382
|
} catch {
|
|
8175
8383
|
}
|
|
8176
8384
|
}, [provider, from, to]);
|
|
8177
|
-
|
|
8385
|
+
React25__default.useEffect(() => {
|
|
8178
8386
|
void fetchCommissions({ periodStart: from, periodEnd: to });
|
|
8179
8387
|
void loadGeneration();
|
|
8180
8388
|
}, [fetchCommissions, loadGeneration, from, to]);
|
|
8181
|
-
const byPerson =
|
|
8389
|
+
const byPerson = React25__default.useMemo(() => {
|
|
8182
8390
|
const map = /* @__PURE__ */ new Map();
|
|
8183
8391
|
for (const c of commissions2) {
|
|
8184
8392
|
const key = c.personId;
|
|
@@ -8231,7 +8439,7 @@ function CommissionsView() {
|
|
|
8231
8439
|
setBusy(null);
|
|
8232
8440
|
}
|
|
8233
8441
|
}
|
|
8234
|
-
const headerActions =
|
|
8442
|
+
const headerActions = React25__default.useMemo(() => [{
|
|
8235
8443
|
id: "commission-rules",
|
|
8236
8444
|
label: "Configurar regras de comiss\xE3o",
|
|
8237
8445
|
icon: Settings,
|
|
@@ -8679,13 +8887,13 @@ function money3(v) {
|
|
|
8679
8887
|
function GoalsView() {
|
|
8680
8888
|
const { labels } = useFinancialConfig();
|
|
8681
8889
|
const provider = useFinancialProvider();
|
|
8682
|
-
const [units, setUnits] =
|
|
8683
|
-
const [unitId, setUnitId] =
|
|
8684
|
-
const [year, setYear] =
|
|
8685
|
-
const [rows, setRows] =
|
|
8686
|
-
const [editing, setEditing] =
|
|
8687
|
-
const [error, setError] =
|
|
8688
|
-
|
|
8890
|
+
const [units, setUnits] = React25__default.useState([]);
|
|
8891
|
+
const [unitId, setUnitId] = React25__default.useState("");
|
|
8892
|
+
const [year, setYear] = React25__default.useState((/* @__PURE__ */ new Date()).getFullYear());
|
|
8893
|
+
const [rows, setRows] = React25__default.useState(null);
|
|
8894
|
+
const [editing, setEditing] = React25__default.useState(null);
|
|
8895
|
+
const [error, setError] = React25__default.useState(null);
|
|
8896
|
+
React25__default.useEffect(() => {
|
|
8689
8897
|
void (async () => {
|
|
8690
8898
|
const cat = await provider.getCommissionCatalogs?.();
|
|
8691
8899
|
const us = cat?.units ?? [];
|
|
@@ -8693,7 +8901,7 @@ function GoalsView() {
|
|
|
8693
8901
|
setUnitId((cur) => cur || us[0]?.id || "");
|
|
8694
8902
|
})();
|
|
8695
8903
|
}, [provider]);
|
|
8696
|
-
const load =
|
|
8904
|
+
const load = React25__default.useCallback(async () => {
|
|
8697
8905
|
if (!unitId) return;
|
|
8698
8906
|
setRows(null);
|
|
8699
8907
|
try {
|
|
@@ -8704,7 +8912,7 @@ function GoalsView() {
|
|
|
8704
8912
|
setRows([]);
|
|
8705
8913
|
}
|
|
8706
8914
|
}, [provider, unitId, year]);
|
|
8707
|
-
|
|
8915
|
+
React25__default.useEffect(() => {
|
|
8708
8916
|
void load();
|
|
8709
8917
|
}, [load]);
|
|
8710
8918
|
const totalRealizado = (rows ?? []).reduce((s, r) => s + r.achievedAmount, 0);
|
|
@@ -8835,11 +9043,11 @@ function GoalDialog({
|
|
|
8835
9043
|
onSaved
|
|
8836
9044
|
}) {
|
|
8837
9045
|
const provider = useFinancialProvider();
|
|
8838
|
-
const [target, setTarget] =
|
|
8839
|
-
const [sup, setSup] =
|
|
8840
|
-
const [hyper, setHyper] =
|
|
8841
|
-
const [saving, setSaving] =
|
|
8842
|
-
const [error, setError] =
|
|
9046
|
+
const [target, setTarget] = React25__default.useState(String(goal.targetAmount || ""));
|
|
9047
|
+
const [sup, setSup] = React25__default.useState(String(goal.superAmount || ""));
|
|
9048
|
+
const [hyper, setHyper] = React25__default.useState(String(goal.hyperAmount || ""));
|
|
9049
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
9050
|
+
const [error, setError] = React25__default.useState(null);
|
|
8843
9051
|
const t2 = Number(target) || 0;
|
|
8844
9052
|
const s = Number(sup) || 0;
|
|
8845
9053
|
const h = Number(hyper) || 0;
|
|
@@ -8930,21 +9138,21 @@ function GiftCardsView() {
|
|
|
8930
9138
|
const provider = useFinancialProvider();
|
|
8931
9139
|
const { currency } = useFinancialConfig();
|
|
8932
9140
|
const can = usePermissionOptional();
|
|
8933
|
-
const [enabled, setEnabled] =
|
|
8934
|
-
const [cards, setCards] =
|
|
8935
|
-
const [error, setError] =
|
|
8936
|
-
const [issuing, setIssuing] =
|
|
8937
|
-
const [issued, setIssued] =
|
|
8938
|
-
const [activating, setActivating] =
|
|
8939
|
-
const [search, setSearch] =
|
|
8940
|
-
const [state, setState] =
|
|
8941
|
-
const [minValue, setMinValue] =
|
|
8942
|
-
const [maxValue, setMaxValue] =
|
|
8943
|
-
const [from, setFrom] =
|
|
8944
|
-
const [to, setTo] =
|
|
8945
|
-
const money4 =
|
|
8946
|
-
const seq =
|
|
8947
|
-
const load =
|
|
9141
|
+
const [enabled, setEnabled] = React25__default.useState(null);
|
|
9142
|
+
const [cards, setCards] = React25__default.useState(null);
|
|
9143
|
+
const [error, setError] = React25__default.useState(null);
|
|
9144
|
+
const [issuing, setIssuing] = React25__default.useState(false);
|
|
9145
|
+
const [issued, setIssued] = React25__default.useState(null);
|
|
9146
|
+
const [activating, setActivating] = React25__default.useState(null);
|
|
9147
|
+
const [search, setSearch] = React25__default.useState("");
|
|
9148
|
+
const [state, setState] = React25__default.useState("");
|
|
9149
|
+
const [minValue, setMinValue] = React25__default.useState("");
|
|
9150
|
+
const [maxValue, setMaxValue] = React25__default.useState("");
|
|
9151
|
+
const [from, setFrom] = React25__default.useState("");
|
|
9152
|
+
const [to, setTo] = React25__default.useState("");
|
|
9153
|
+
const money4 = React25__default.useCallback((v) => formatCurrency(v, currency), [currency]);
|
|
9154
|
+
const seq = React25__default.useRef(0);
|
|
9155
|
+
const load = React25__default.useCallback(async () => {
|
|
8948
9156
|
const mine = ++seq.current;
|
|
8949
9157
|
setCards(null);
|
|
8950
9158
|
setError(null);
|
|
@@ -8965,7 +9173,7 @@ function GiftCardsView() {
|
|
|
8965
9173
|
setCards([]);
|
|
8966
9174
|
}
|
|
8967
9175
|
}, [provider, search, state, minValue, maxValue, from, to]);
|
|
8968
|
-
|
|
9176
|
+
React25__default.useEffect(() => {
|
|
8969
9177
|
void (async () => {
|
|
8970
9178
|
try {
|
|
8971
9179
|
setEnabled(await provider.giftCardsEnabled?.() === true);
|
|
@@ -8974,10 +9182,10 @@ function GiftCardsView() {
|
|
|
8974
9182
|
}
|
|
8975
9183
|
})();
|
|
8976
9184
|
}, [provider]);
|
|
8977
|
-
|
|
9185
|
+
React25__default.useEffect(() => {
|
|
8978
9186
|
if (enabled) void load();
|
|
8979
9187
|
}, [enabled, load]);
|
|
8980
|
-
const columns =
|
|
9188
|
+
const columns = React25__default.useMemo(() => [
|
|
8981
9189
|
{
|
|
8982
9190
|
accessorKey: "code",
|
|
8983
9191
|
header: "C\xF3digo",
|
|
@@ -9158,12 +9366,12 @@ function IssueDialog({
|
|
|
9158
9366
|
onIssued
|
|
9159
9367
|
}) {
|
|
9160
9368
|
const provider = useFinancialProvider();
|
|
9161
|
-
const [quantity, setQuantity] =
|
|
9162
|
-
const [amount, setAmount] =
|
|
9163
|
-
const [expiresAt, setExpiresAt] =
|
|
9164
|
-
const [note, setNote] =
|
|
9165
|
-
const [saving, setSaving] =
|
|
9166
|
-
const [error, setError] =
|
|
9369
|
+
const [quantity, setQuantity] = React25__default.useState("10");
|
|
9370
|
+
const [amount, setAmount] = React25__default.useState("100");
|
|
9371
|
+
const [expiresAt, setExpiresAt] = React25__default.useState("");
|
|
9372
|
+
const [note, setNote] = React25__default.useState("");
|
|
9373
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
9374
|
+
const [error, setError] = React25__default.useState(null);
|
|
9167
9375
|
const q = Math.trunc(Number(quantity)) || 0;
|
|
9168
9376
|
const a = Number(amount) || 0;
|
|
9169
9377
|
const total = q * a;
|
|
@@ -9572,6 +9780,21 @@ var shortDate2 = (value) => {
|
|
|
9572
9780
|
const [year, month, day] = value.split("-");
|
|
9573
9781
|
return year && month && day ? `${day}/${month}/${year}` : value;
|
|
9574
9782
|
};
|
|
9783
|
+
var shiftDate = (value, days) => {
|
|
9784
|
+
const d = /* @__PURE__ */ new Date(`${value}T00:00:00`);
|
|
9785
|
+
if (Number.isNaN(d.getTime())) return value;
|
|
9786
|
+
d.setDate(d.getDate() + days);
|
|
9787
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
9788
|
+
};
|
|
9789
|
+
var toAmountInput = (n) => round23(Math.max(0, n)).toFixed(2).replace(".", ",");
|
|
9790
|
+
var CAMPO_FILTRO = "h-[30px] w-full rounded-input border border-input bg-card px-2 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-1 focus:ring-ring";
|
|
9791
|
+
var LINK_DAYS = 3;
|
|
9792
|
+
var LINK_AMOUNT_TOLERANCE = 0.1;
|
|
9793
|
+
var WIDEN_DAYS = 15;
|
|
9794
|
+
var parseAmount = (value) => {
|
|
9795
|
+
const n = Number.parseFloat(value.replace(",", "."));
|
|
9796
|
+
return Number.isFinite(n) ? n : void 0;
|
|
9797
|
+
};
|
|
9575
9798
|
var round23 = (n) => Math.round((Number(n) || 0) * 100) / 100;
|
|
9576
9799
|
var emptyItem = () => ({ description: "", quantity: 1, unitPrice: 0 });
|
|
9577
9800
|
function LinhaDoExtrato2({ line, fmt }) {
|
|
@@ -9596,6 +9819,35 @@ function LinhaDoExtrato2({ line, fmt }) {
|
|
|
9596
9819
|
] })
|
|
9597
9820
|
] });
|
|
9598
9821
|
}
|
|
9822
|
+
function CandidatoBotao({ item, selected, onToggle, fmt, t: t2 }) {
|
|
9823
|
+
const { candidate: c, mode, openAmount: openAmount2 } = item;
|
|
9824
|
+
const parcela = c.movement.installmentNumber;
|
|
9825
|
+
return /* @__PURE__ */ jsxs(
|
|
9826
|
+
"button",
|
|
9827
|
+
{
|
|
9828
|
+
type: "button",
|
|
9829
|
+
onClick: onToggle,
|
|
9830
|
+
"aria-pressed": selected,
|
|
9831
|
+
className: `flex w-full items-center gap-3 rounded-md border p-2.5 text-left transition-colors ${selected ? "border-primary bg-primary/5 ring-2 ring-primary/20" : "hover:bg-muted/40"}`,
|
|
9832
|
+
children: [
|
|
9833
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
9834
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: c.invoice?.contactName || c.movement.notes || c.invoice?.fiscalNumber || t2("financial.reconciliation.match") }),
|
|
9835
|
+
/* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-[10px] text-muted-foreground", children: [
|
|
9836
|
+
/* @__PURE__ */ jsx("span", { children: shortDate2(c.movement.dueDate) }),
|
|
9837
|
+
c.invoice?.fiscalNumber && /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
9838
|
+
"#",
|
|
9839
|
+
c.invoice.fiscalNumber,
|
|
9840
|
+
parcela ? ` \xB7 ${parcela}` : ""
|
|
9841
|
+
] }),
|
|
9842
|
+
c.score > 0 && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded bg-magic/10 px-1 py-px font-medium text-magic-soft-foreground", children: t2("financial.reconciliation.matchScore", { score: Math.round(c.score * 100) }) }),
|
|
9843
|
+
/* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded px-1 py-px font-medium ${mode === "settle" ? "bg-warning/10 text-warning" : "bg-success/10 text-success"}`, children: mode === "settle" ? t2("financial.reconciliation.candidateOpen", { amount: fmt(openAmount2) }) : t2("financial.reconciliation.candidatePaid") })
|
|
9844
|
+
] })
|
|
9845
|
+
] }),
|
|
9846
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold", children: fmt(c.movement.amount) })
|
|
9847
|
+
]
|
|
9848
|
+
}
|
|
9849
|
+
);
|
|
9850
|
+
}
|
|
9599
9851
|
function EscolhaCard({ icon: Icon, title, hint, active, onClick }) {
|
|
9600
9852
|
return /* @__PURE__ */ jsxs(
|
|
9601
9853
|
"button",
|
|
@@ -9614,17 +9866,18 @@ function EscolhaCard({ icon: Icon, title, hint, active, onClick }) {
|
|
|
9614
9866
|
}
|
|
9615
9867
|
);
|
|
9616
9868
|
}
|
|
9617
|
-
function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
9869
|
+
function ReconcileFlow({ line, initialIntent, initialTarget, initialStep = 0, onClose, onDone }) {
|
|
9618
9870
|
const t2 = useTranslation();
|
|
9619
9871
|
const { currency } = useFinancialConfig();
|
|
9620
9872
|
const provider = useFinancialProvider();
|
|
9621
9873
|
const fmt = useCallback((n) => formatCurrency(n, currency), [currency]);
|
|
9622
|
-
const [step, setStep] = useState(
|
|
9874
|
+
const [step, setStep] = useState(initialStep);
|
|
9623
9875
|
const [intent, setIntent] = useState(initialIntent ?? null);
|
|
9624
9876
|
const [saving, setSaving] = useState(false);
|
|
9625
9877
|
const [candidates, setCandidates] = useState([]);
|
|
9626
9878
|
const [candidatesLoading, setCandidatesLoading] = useState(false);
|
|
9627
|
-
const [
|
|
9879
|
+
const [target, setTarget] = useState(initialTarget ?? null);
|
|
9880
|
+
const targetId = target?.candidate.movement.id ?? null;
|
|
9628
9881
|
const [adjustments, setAdjustments] = useState([]);
|
|
9629
9882
|
const [contactId, setContactId] = useState("");
|
|
9630
9883
|
const [contactName, setContactName] = useState(line.counterpartyName ?? "");
|
|
@@ -9647,7 +9900,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9647
9900
|
void provider.getBankAccounts().then(setContas).catch(() => setContas([]));
|
|
9648
9901
|
}, [provider, line.bankAccountId]);
|
|
9649
9902
|
useEffect(() => {
|
|
9650
|
-
if (intent !== "link" || !provider.suggestReconciliation) return;
|
|
9903
|
+
if (intent !== "link" || !provider.suggestReconciliation || provider.searchReconciliationTargets) return;
|
|
9651
9904
|
let cancelled = false;
|
|
9652
9905
|
setCandidatesLoading(true);
|
|
9653
9906
|
provider.suggestReconciliation(line.id).then((raw) => {
|
|
@@ -9661,10 +9914,72 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9661
9914
|
cancelled = true;
|
|
9662
9915
|
};
|
|
9663
9916
|
}, [intent, provider, line.id]);
|
|
9664
|
-
const
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9917
|
+
const podeBuscar = !!provider.searchReconciliationTargets;
|
|
9918
|
+
const filtrosIniciais = () => ({
|
|
9919
|
+
dueFrom: shiftDate(line.date, -LINK_DAYS),
|
|
9920
|
+
dueTo: shiftDate(line.date, LINK_DAYS),
|
|
9921
|
+
status: "all",
|
|
9922
|
+
texto: "",
|
|
9923
|
+
min: toAmountInput(line.amount * (1 - LINK_AMOUNT_TOLERANCE)),
|
|
9924
|
+
max: toAmountInput(line.amount * (1 + LINK_AMOUNT_TOLERANCE))
|
|
9925
|
+
});
|
|
9926
|
+
const [rascunho, setRascunho] = useState(filtrosIniciais);
|
|
9927
|
+
const [aplicado, setAplicado] = useState(filtrosIniciais);
|
|
9928
|
+
const mudarFiltro = (patch) => setRascunho((prev) => ({ ...prev, ...patch }));
|
|
9929
|
+
const filtrosPendentes = JSON.stringify(rascunho) !== JSON.stringify(aplicado);
|
|
9930
|
+
const [resultados, setResultados] = useState([]);
|
|
9931
|
+
const [paginaBusca, setPaginaBusca] = useState(1);
|
|
9932
|
+
const [temMais, setTemMais] = useState(false);
|
|
9933
|
+
const [buscando, setBuscando] = useState(false);
|
|
9934
|
+
const buscaSeq = useRef(0);
|
|
9935
|
+
const buscar = useCallback(async (pagina) => {
|
|
9936
|
+
if (!provider.searchReconciliationTargets) return;
|
|
9937
|
+
const seq = ++buscaSeq.current;
|
|
9938
|
+
setBuscando(true);
|
|
9939
|
+
try {
|
|
9940
|
+
const res = await provider.searchReconciliationTargets(line.id, {
|
|
9941
|
+
dueFrom: aplicado.dueFrom || void 0,
|
|
9942
|
+
dueTo: aplicado.dueTo || void 0,
|
|
9943
|
+
status: aplicado.status,
|
|
9944
|
+
search: aplicado.texto.trim() || void 0,
|
|
9945
|
+
amountMin: parseAmount(aplicado.min),
|
|
9946
|
+
amountMax: parseAmount(aplicado.max),
|
|
9947
|
+
page: pagina
|
|
9948
|
+
});
|
|
9949
|
+
if (seq !== buscaSeq.current) return;
|
|
9950
|
+
const novos = resolveCandidates(res.data);
|
|
9951
|
+
setResultados((prev) => {
|
|
9952
|
+
if (pagina === 1) return novos;
|
|
9953
|
+
const vistos = new Set(prev.map((r) => r.candidate.movement.id));
|
|
9954
|
+
return [...prev, ...novos.filter((r) => !vistos.has(r.candidate.movement.id))];
|
|
9955
|
+
});
|
|
9956
|
+
setPaginaBusca(pagina);
|
|
9957
|
+
setTemMais(res.hasMore);
|
|
9958
|
+
} catch (e) {
|
|
9959
|
+
if (seq !== buscaSeq.current) return;
|
|
9960
|
+
toast.error(e instanceof Error ? e.message : t2("common.error"));
|
|
9961
|
+
} finally {
|
|
9962
|
+
if (seq === buscaSeq.current) setBuscando(false);
|
|
9963
|
+
}
|
|
9964
|
+
}, [provider, line.id, aplicado, t2]);
|
|
9965
|
+
const ultimaBusca = useRef(null);
|
|
9966
|
+
const naSelecao = intent === "link" && step === 1;
|
|
9967
|
+
useEffect(() => {
|
|
9968
|
+
if (!naSelecao || !podeBuscar) return;
|
|
9969
|
+
const chave = JSON.stringify(aplicado);
|
|
9970
|
+
if (ultimaBusca.current === chave) return;
|
|
9971
|
+
ultimaBusca.current = chave;
|
|
9972
|
+
void buscar(1);
|
|
9973
|
+
}, [naSelecao, podeBuscar, aplicado, buscar]);
|
|
9974
|
+
const contasEncontradas = useMemo(() => {
|
|
9975
|
+
const lista = podeBuscar ? resultados : candidates;
|
|
9976
|
+
return initialTarget && !lista.some((c) => c.candidate.movement.id === initialTarget.candidate.movement.id) ? [initialTarget, ...lista] : lista;
|
|
9977
|
+
}, [podeBuscar, resultados, candidates, initialTarget]);
|
|
9978
|
+
const ampliarBusca = () => {
|
|
9979
|
+
const amplo = { ...rascunho, dueFrom: shiftDate(line.date, -WIDEN_DAYS), dueTo: shiftDate(line.date, WIDEN_DAYS), min: "", max: "" };
|
|
9980
|
+
setRascunho(amplo);
|
|
9981
|
+
setAplicado(amplo);
|
|
9982
|
+
};
|
|
9668
9983
|
const contaDoExtrato = useMemo(
|
|
9669
9984
|
() => contas.find((a) => a.id === line.bankAccountId) ?? null,
|
|
9670
9985
|
[contas, line.bankAccountId]
|
|
@@ -9689,6 +10004,15 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9689
10004
|
const rows = q ? planoDoTipo.filter((a) => `${a.code ?? ""} ${a.name}`.toLocaleLowerCase("pt-BR").includes(q)) : planoDoTipo;
|
|
9690
10005
|
return rows.slice(0, 50).map((a) => ({ id: a.id, label: accountLabel(a) }));
|
|
9691
10006
|
}, [planoDoTipo]);
|
|
10007
|
+
const descricaoPadrao = (type) => type === "interest" ? t2("financial.reconciliation.adjustmentDefaultInterest") : type === "fine" ? t2("financial.reconciliation.adjustmentDefaultFine") : isOut ? t2("financial.reconciliation.adjustmentDefaultDiscountOut") : t2("financial.reconciliation.adjustmentDefaultDiscountIn");
|
|
10008
|
+
const buscaPlanoDoAjuste = useCallback((adj, query) => {
|
|
10009
|
+
const natureza = adjustmentDirection(line.direction, adj.type) === "debit" ? "expense" : "revenue";
|
|
10010
|
+
const folhas = postableAccounts(chart);
|
|
10011
|
+
const doTipo = folhas.filter((a) => a.accountType === natureza);
|
|
10012
|
+
const base = doTipo.length > 0 ? doTipo : folhas;
|
|
10013
|
+
const q = query.trim().toLocaleLowerCase("pt-BR");
|
|
10014
|
+
return (q ? base.filter((a) => `${a.code ?? ""} ${a.name}`.toLocaleLowerCase("pt-BR").includes(q)) : base).slice(0, 50).map((a) => ({ id: a.id, label: accountLabel(a) }));
|
|
10015
|
+
}, [chart, line.direction]);
|
|
9692
10016
|
const valorAQuitar = intent === "link" && target ? target.openAmount : round23(line.amount);
|
|
9693
10017
|
const quita = intent === "bill" || intent === "link" && target?.mode === "settle";
|
|
9694
10018
|
const leitor = useMemo(() => {
|
|
@@ -9725,8 +10049,9 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9725
10049
|
const somaItens = sumItems(items.filter((i) => i.description.trim()));
|
|
9726
10050
|
const erroItens = itemsMismatch(items, line.amount, fmt);
|
|
9727
10051
|
const gap = target ? adjustmentGap(line.amount, target.openAmount) : 0;
|
|
9728
|
-
const erroAjuste = intent === "link" && target?.mode === "settle" && Math.abs(gap) >= 0.01 ? adjustmentsMismatch(adjustments, gap, fmt) : null;
|
|
9729
|
-
const bloqueioPasso1 = !intent ? t2("financial.reconciliation.pickIntent") :
|
|
10052
|
+
const erroAjuste = intent === "link" && target?.mode === "settle" && Math.abs(gap) >= 0.01 ? adjustmentsMismatch(adjustments, gap, fmt) ?? (adjustments.some((a) => round23(a.amount) > 0 && !a.accountId) ? t2("financial.reconciliation.adjustmentNeedsAccount") : null) : null;
|
|
10053
|
+
const bloqueioPasso1 = !intent ? t2("financial.reconciliation.pickIntent") : null;
|
|
10054
|
+
const bloqueioSelecao = intent === "link" && !target ? t2("financial.reconciliation.pickCandidate") : null;
|
|
9730
10055
|
const bloqueioPasso2 = intent === "transfer" ? counterAccountId ? null : t2("financial.reconciliation.pickAccount") : intent === "bill" ? description.trim() || items.some((i) => i.description.trim()) ? erroItens : t2("financial.reconciliation.needDescription") : erroAjuste;
|
|
9731
10056
|
const bloqueioPasso3 = saving ? t2("common.saving") : null;
|
|
9732
10057
|
async function confirmar() {
|
|
@@ -9853,37 +10178,148 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9853
10178
|
onClick: () => setIntent("transfer")
|
|
9854
10179
|
}
|
|
9855
10180
|
)
|
|
10181
|
+
] })
|
|
10182
|
+
] });
|
|
10183
|
+
const statusDaConta = (item) => item.mode === "link_only" ? t2("financial.reconciliation.searchStatusPaid") : item.candidate.movement.status === "overdue" ? t2("financial.reconciliation.statusOverdue") : t2("financial.reconciliation.searchStatusOpen");
|
|
10184
|
+
const valorAConciliar = (item) => item.mode === "link_only" ? item.candidate.movement.amount : item.openAmount;
|
|
10185
|
+
const parcelaDe = (item) => {
|
|
10186
|
+
const n = item.candidate.movement.installmentNumber ?? 1;
|
|
10187
|
+
return item.candidate.installmentCount ? `${n}/${item.candidate.installmentCount}` : String(n);
|
|
10188
|
+
};
|
|
10189
|
+
const filtrar = () => {
|
|
10190
|
+
if (filtrosPendentes) setAplicado(rascunho);
|
|
10191
|
+
else void buscar(1);
|
|
10192
|
+
};
|
|
10193
|
+
const passoSelecionar = /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-3", children: [
|
|
10194
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
10195
|
+
t2("financial.reconciliation.selectBillFor", {
|
|
10196
|
+
date: shortDate2(line.date),
|
|
10197
|
+
description: line.description || line.externalId
|
|
10198
|
+
}),
|
|
10199
|
+
" ",
|
|
10200
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: fmt(line.amount) })
|
|
9856
10201
|
] }),
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
10202
|
+
podeBuscar && /* @__PURE__ */ jsxs(
|
|
10203
|
+
FilterPanel,
|
|
10204
|
+
{
|
|
10205
|
+
title: t2("financial.reconciliation.filtersTitle"),
|
|
10206
|
+
dirty: filtrosPendentes,
|
|
10207
|
+
onApply: filtrar,
|
|
10208
|
+
applyLabel: t2("financial.reconciliation.filter"),
|
|
10209
|
+
pendingTitle: t2("financial.reconciliation.filterPending"),
|
|
10210
|
+
pendingHint: t2("financial.reconciliation.searchPendingHint"),
|
|
10211
|
+
busy: buscando,
|
|
10212
|
+
moreLabel: t2("financial.reconciliation.filterMore"),
|
|
10213
|
+
lessLabel: t2("financial.reconciliation.filterLess"),
|
|
10214
|
+
advancedCount: rascunho.status !== "all" ? 1 : 0,
|
|
10215
|
+
advanced: /* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchStatus"), children: /* @__PURE__ */ jsx(
|
|
10216
|
+
SegmentedControl,
|
|
9866
10217
|
{
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
9875
|
-
shortDate2(c.movement.dueDate),
|
|
9876
|
-
/* @__PURE__ */ jsx("span", { className: "ml-1.5 inline-flex items-center rounded bg-magic/10 px-1 py-px font-medium text-magic-soft-foreground", children: t2("financial.reconciliation.matchScore", { score: Math.round(c.score * 100) }) }),
|
|
9877
|
-
/* @__PURE__ */ jsx("span", { className: `ml-1.5 inline-flex items-center rounded px-1 py-px font-medium ${mode === "settle" ? "bg-warning/10 text-warning" : "bg-success/10 text-success"}`, children: mode === "settle" ? t2("financial.reconciliation.candidateOpen", { amount: fmt(openAmount2) }) : t2("financial.reconciliation.candidatePaid") })
|
|
9878
|
-
] })
|
|
9879
|
-
] }),
|
|
9880
|
-
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold", children: fmt(c.movement.amount) })
|
|
10218
|
+
"aria-label": t2("financial.reconciliation.searchStatus"),
|
|
10219
|
+
value: rascunho.status,
|
|
10220
|
+
onChange: (v) => mudarFiltro({ status: v }),
|
|
10221
|
+
options: [
|
|
10222
|
+
{ value: "all", label: t2("financial.reconciliation.searchStatusAllShort") },
|
|
10223
|
+
{ value: "open", label: t2("financial.reconciliation.searchStatusOpen") },
|
|
10224
|
+
{ value: "paid", label: t2("financial.reconciliation.searchStatusPaid") }
|
|
9881
10225
|
]
|
|
9882
|
-
}
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
10226
|
+
}
|
|
10227
|
+
) }),
|
|
10228
|
+
footer: /* @__PURE__ */ jsx(
|
|
10229
|
+
Input,
|
|
10230
|
+
{
|
|
10231
|
+
value: rascunho.texto,
|
|
10232
|
+
placeholder: t2("financial.reconciliation.searchText"),
|
|
10233
|
+
"aria-label": t2("financial.reconciliation.searchText"),
|
|
10234
|
+
onChange: (e) => mudarFiltro({ texto: e.target.value }),
|
|
10235
|
+
onKeyDown: (e) => {
|
|
10236
|
+
if (e.key !== "Enter") return;
|
|
10237
|
+
e.preventDefault();
|
|
10238
|
+
filtrar();
|
|
10239
|
+
}
|
|
10240
|
+
}
|
|
10241
|
+
),
|
|
10242
|
+
children: [
|
|
10243
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchDueFrom"), children: /* @__PURE__ */ jsx("input", { type: "date", value: rascunho.dueFrom, onChange: (e) => mudarFiltro({ dueFrom: e.target.value }), className: CAMPO_FILTRO }) }),
|
|
10244
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchDueTo"), children: /* @__PURE__ */ jsx("input", { type: "date", value: rascunho.dueTo, onChange: (e) => mudarFiltro({ dueTo: e.target.value }), className: CAMPO_FILTRO }) }),
|
|
10245
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchAmountMin"), children: /* @__PURE__ */ jsx("input", { inputMode: "decimal", placeholder: "0,00", value: rascunho.min, onChange: (e) => mudarFiltro({ min: e.target.value }), className: CAMPO_FILTRO }) }),
|
|
10246
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("financial.reconciliation.searchAmountMax"), children: /* @__PURE__ */ jsx("input", { inputMode: "decimal", placeholder: "0,00", value: rascunho.max, onChange: (e) => mudarFiltro({ max: e.target.value }), className: CAMPO_FILTRO }) })
|
|
10247
|
+
]
|
|
10248
|
+
}
|
|
10249
|
+
),
|
|
10250
|
+
(buscando || candidatesLoading) && contasEncontradas.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10251
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full rounded-md" }),
|
|
10252
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full rounded-md" })
|
|
10253
|
+
] }) : contasEncontradas.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-card border py-6 text-center", children: [
|
|
10254
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.reconciliation.searchEmpty") }),
|
|
10255
|
+
podeBuscar && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: ampliarBusca, children: t2("financial.reconciliation.searchWiden") })
|
|
10256
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10257
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2 sm:hidden", children: contasEncontradas.map((item) => /* @__PURE__ */ jsx(
|
|
10258
|
+
CandidatoBotao,
|
|
10259
|
+
{
|
|
10260
|
+
item,
|
|
10261
|
+
selected: targetId === item.candidate.movement.id,
|
|
10262
|
+
onToggle: () => setTarget(targetId === item.candidate.movement.id ? null : item),
|
|
10263
|
+
fmt,
|
|
10264
|
+
t: t2
|
|
10265
|
+
},
|
|
10266
|
+
item.candidate.movement.id
|
|
10267
|
+
)) }),
|
|
10268
|
+
/* @__PURE__ */ jsx("div", { className: "hidden overflow-hidden rounded-card border sm:block", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
10269
|
+
/* @__PURE__ */ jsx("thead", { className: "bg-muted/50 text-left text-[11px] text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
10270
|
+
/* @__PURE__ */ jsx("th", { className: "w-8 p-2" }),
|
|
10271
|
+
/* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.colDue") }),
|
|
10272
|
+
/* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.colCounterparty") }),
|
|
10273
|
+
/* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.colDescription") }),
|
|
10274
|
+
/* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.colInstallment") }),
|
|
10275
|
+
/* @__PURE__ */ jsx("th", { className: "p-2 font-medium", children: t2("financial.reconciliation.searchStatus") }),
|
|
10276
|
+
/* @__PURE__ */ jsx("th", { className: "p-2 text-right font-medium", children: t2("financial.reconciliation.colToReconcile") }),
|
|
10277
|
+
/* @__PURE__ */ jsx("th", { className: "p-2 text-right font-medium", children: t2("financial.reconciliation.colDelta") })
|
|
10278
|
+
] }) }),
|
|
10279
|
+
/* @__PURE__ */ jsx("tbody", { children: contasEncontradas.map((item) => {
|
|
10280
|
+
const c = item.candidate;
|
|
10281
|
+
const escolhido = targetId === c.movement.id;
|
|
10282
|
+
const valor = valorAConciliar(item);
|
|
10283
|
+
const delta = round23(line.amount - valor);
|
|
10284
|
+
return /* @__PURE__ */ jsxs(
|
|
10285
|
+
"tr",
|
|
10286
|
+
{
|
|
10287
|
+
onClick: () => setTarget(escolhido ? null : item),
|
|
10288
|
+
className: `cursor-pointer border-t transition-colors ${escolhido ? "bg-primary/10" : "hover:bg-muted/40"}`,
|
|
10289
|
+
children: [
|
|
10290
|
+
/* @__PURE__ */ jsx("td", { className: "p-2", children: /* @__PURE__ */ jsx(
|
|
10291
|
+
"input",
|
|
10292
|
+
{
|
|
10293
|
+
type: "radio",
|
|
10294
|
+
readOnly: true,
|
|
10295
|
+
checked: escolhido,
|
|
10296
|
+
"aria-label": c.invoice?.contactName ?? c.movement.notes ?? c.movement.id
|
|
10297
|
+
}
|
|
10298
|
+
) }),
|
|
10299
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-nowrap p-2", children: shortDate2(c.movement.dueDate) }),
|
|
10300
|
+
/* @__PURE__ */ jsx("td", { className: "max-w-[12rem] truncate p-2 font-medium", title: c.invoice?.contactName, children: c.invoice?.contactName || "\u2014" }),
|
|
10301
|
+
/* @__PURE__ */ jsxs("td", { className: "max-w-[14rem] truncate p-2", title: c.movement.notes, children: [
|
|
10302
|
+
c.invoice?.fiscalNumber && /* @__PURE__ */ jsxs("span", { className: "mr-1 text-muted-foreground", children: [
|
|
10303
|
+
"#",
|
|
10304
|
+
c.invoice.fiscalNumber
|
|
10305
|
+
] }),
|
|
10306
|
+
c.movement.notes
|
|
10307
|
+
] }),
|
|
10308
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-nowrap p-2", children: parcelaDe(item) }),
|
|
10309
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-nowrap p-2", children: /* @__PURE__ */ jsx("span", { className: `rounded px-1.5 py-0.5 text-[10px] font-medium ${item.mode === "link_only" ? "bg-success/10 text-success" : c.movement.status === "overdue" ? "bg-destructive/10 text-destructive" : "bg-muted text-foreground"}`, children: statusDaConta(item) }) }),
|
|
10310
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-nowrap p-2 text-right font-semibold", children: fmt(valor) }),
|
|
10311
|
+
/* @__PURE__ */ jsx("td", { className: `whitespace-nowrap p-2 text-right text-[11px] ${Math.abs(delta) < 0.01 ? "text-success" : "text-destructive"}`, children: Math.abs(delta) < 0.01 ? "OK" : `${delta > 0 ? "+" : "\u2212"}${fmt(Math.abs(delta))}` })
|
|
10312
|
+
]
|
|
10313
|
+
},
|
|
10314
|
+
c.movement.id
|
|
10315
|
+
);
|
|
10316
|
+
}) })
|
|
10317
|
+
] }) })
|
|
10318
|
+
] }),
|
|
10319
|
+
temMais && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "ghost", disabled: buscando, onClick: () => void buscar(paginaBusca + 1), children: [
|
|
10320
|
+
buscando && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin", "aria-hidden": true }),
|
|
10321
|
+
t2("financial.reconciliation.searchMore")
|
|
10322
|
+
] }) })
|
|
9887
10323
|
] });
|
|
9888
10324
|
const passoDetalhes = /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-3", children: [
|
|
9889
10325
|
intent === "bill" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -9935,30 +10371,28 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9935
10371
|
onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, description: e.target.value } : x))
|
|
9936
10372
|
}
|
|
9937
10373
|
) }),
|
|
9938
|
-
/* @__PURE__ */ jsx(
|
|
10374
|
+
/* @__PURE__ */ jsx("div", { className: "w-16 shrink-0", children: /* @__PURE__ */ jsx(
|
|
9939
10375
|
Input,
|
|
9940
10376
|
{
|
|
9941
10377
|
"aria-label": t2("financial.reconciliation.itemQuantity"),
|
|
9942
|
-
className: "w-16 shrink-0",
|
|
9943
10378
|
type: "number",
|
|
9944
10379
|
min: "0",
|
|
9945
10380
|
step: "0.001",
|
|
9946
10381
|
value: item.quantity,
|
|
9947
10382
|
onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, quantity: Number(e.target.value) } : x))
|
|
9948
10383
|
}
|
|
9949
|
-
),
|
|
9950
|
-
/* @__PURE__ */ jsx(
|
|
10384
|
+
) }),
|
|
10385
|
+
/* @__PURE__ */ jsx("div", { className: "w-24 shrink-0", children: /* @__PURE__ */ jsx(
|
|
9951
10386
|
Input,
|
|
9952
10387
|
{
|
|
9953
10388
|
"aria-label": t2("financial.reconciliation.itemUnitPrice"),
|
|
9954
|
-
className: "w-24 shrink-0",
|
|
9955
10389
|
type: "number",
|
|
9956
10390
|
min: "0",
|
|
9957
10391
|
step: "0.01",
|
|
9958
10392
|
value: item.unitPrice,
|
|
9959
10393
|
onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, unitPrice: Number(e.target.value) } : x))
|
|
9960
10394
|
}
|
|
9961
|
-
),
|
|
10395
|
+
) }),
|
|
9962
10396
|
/* @__PURE__ */ jsx(Hint, { label: t2("common.delete"), children: /* @__PURE__ */ jsx(
|
|
9963
10397
|
Button,
|
|
9964
10398
|
{
|
|
@@ -10059,58 +10493,86 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10059
10493
|
] })
|
|
10060
10494
|
] }),
|
|
10061
10495
|
target.mode === "link_only" ? /* @__PURE__ */ jsx("p", { className: "rounded-card border border-success/40 bg-success/5 px-3 py-2.5 text-[11px] leading-snug", children: t2("financial.reconciliation.linkOnlyExplain") }) : Math.abs(gap) < 0.01 ? /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.reconciliation.noAdjustmentNeeded") }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
10062
|
-
/* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning/5 px-3 py-2 text-[11px] leading-snug", children: t2("financial.reconciliation.adjustmentNeeded", {
|
|
10496
|
+
/* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning/5 px-3 py-2 text-[11px] leading-snug", children: t2(isOut ? "financial.reconciliation.adjustmentNeeded" : "financial.reconciliation.adjustmentNeededIn", {
|
|
10063
10497
|
gap: fmt(Math.abs(gap)),
|
|
10064
10498
|
side: gap > 0 ? t2("financial.reconciliation.adjustmentMore") : t2("financial.reconciliation.adjustmentLess")
|
|
10065
10499
|
}) }),
|
|
10066
|
-
adjustments.map((adj, idx) =>
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
{
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10500
|
+
adjustments.map((adj, idx) => {
|
|
10501
|
+
const mudar = (patch) => setAdjustments((prev) => prev.map((x, i) => i === idx ? { ...x, ...patch } : x));
|
|
10502
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-card border p-2.5", children: [
|
|
10503
|
+
/* @__PURE__ */ jsxs("div", { className: "grid min-w-0 flex-1 items-end gap-2 sm:grid-cols-2", children: [
|
|
10504
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
10505
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: t2("financial.reconciliation.adjustmentType") }),
|
|
10506
|
+
/* @__PURE__ */ jsxs(
|
|
10507
|
+
Select,
|
|
10508
|
+
{
|
|
10509
|
+
value: adj.type,
|
|
10510
|
+
onValueChange: (v) => {
|
|
10511
|
+
const type = v;
|
|
10512
|
+
const escrita = adj.description?.trim() && adj.description !== descricaoPadrao(adj.type);
|
|
10513
|
+
mudar(escrita ? { type } : { type, description: descricaoPadrao(type) });
|
|
10514
|
+
},
|
|
10515
|
+
children: [
|
|
10516
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
10517
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
10518
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "interest", children: t2("financial.reconciliation.interest") }),
|
|
10519
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "fine", children: t2("financial.reconciliation.fine") }),
|
|
10520
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "discount", children: t2("financial.reconciliation.discount") })
|
|
10521
|
+
] })
|
|
10522
|
+
]
|
|
10523
|
+
}
|
|
10524
|
+
)
|
|
10525
|
+
] }),
|
|
10526
|
+
/* @__PURE__ */ jsx(
|
|
10527
|
+
CurrencyInput,
|
|
10528
|
+
{
|
|
10529
|
+
label: t2("financial.reconciliation.adjustmentAmount"),
|
|
10530
|
+
value: adj.amount,
|
|
10531
|
+
onChange: (v) => mudar({ amount: v }),
|
|
10532
|
+
symbol: currency.symbol,
|
|
10533
|
+
locale: currency.locale,
|
|
10534
|
+
currencyCode: currency.code
|
|
10535
|
+
}
|
|
10536
|
+
),
|
|
10537
|
+
/* @__PURE__ */ jsx(
|
|
10538
|
+
SearchSelect,
|
|
10539
|
+
{
|
|
10540
|
+
label: `${t2("financial.reconciliation.chartAccount")} *`,
|
|
10541
|
+
value: adj.accountId ?? "",
|
|
10542
|
+
displayValue: chart.find((a) => a.id === adj.accountId)?.name,
|
|
10543
|
+
placeholder: t2("financial.reconciliation.chartAccountPlaceholder"),
|
|
10544
|
+
minChars: 0,
|
|
10545
|
+
loadOnOpen: true,
|
|
10546
|
+
noResultsLabel: t2("financial.reconciliation.chartAccountNone"),
|
|
10547
|
+
clearLabel: "Limpar",
|
|
10548
|
+
onSearch: (q) => buscaPlanoDoAjuste(adj, q),
|
|
10549
|
+
onChange: (id) => mudar({ accountId: id || void 0 })
|
|
10550
|
+
}
|
|
10551
|
+
),
|
|
10552
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
10553
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: t2("financial.reconciliation.adjustmentDescription") }),
|
|
10554
|
+
/* @__PURE__ */ jsx(
|
|
10555
|
+
Input,
|
|
10556
|
+
{
|
|
10557
|
+
placeholder: t2("financial.reconciliation.adjustmentDescriptionPlaceholder"),
|
|
10558
|
+
value: adj.description ?? "",
|
|
10559
|
+
onChange: (e) => mudar({ description: e.target.value })
|
|
10560
|
+
}
|
|
10561
|
+
)
|
|
10562
|
+
] })
|
|
10563
|
+
] }),
|
|
10564
|
+
/* @__PURE__ */ jsx("div", { className: "pt-6", children: /* @__PURE__ */ jsx(Hint, { label: t2("common.delete"), children: /* @__PURE__ */ jsx(
|
|
10565
|
+
Button,
|
|
10566
|
+
{
|
|
10567
|
+
size: "sm",
|
|
10568
|
+
variant: "ghost",
|
|
10569
|
+
"aria-label": t2("common.delete"),
|
|
10570
|
+
onClick: () => setAdjustments((prev) => prev.filter((_, i) => i !== idx)),
|
|
10571
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": true })
|
|
10572
|
+
}
|
|
10573
|
+
) }) })
|
|
10574
|
+
] }, idx);
|
|
10575
|
+
}),
|
|
10114
10576
|
/* @__PURE__ */ jsxs(
|
|
10115
10577
|
Button,
|
|
10116
10578
|
{
|
|
@@ -10118,6 +10580,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10118
10580
|
variant: "outline",
|
|
10119
10581
|
onClick: () => setAdjustments((prev) => [...prev, {
|
|
10120
10582
|
type: gap > 0 ? "interest" : "discount",
|
|
10583
|
+
description: descricaoPadrao(gap > 0 ? "interest" : "discount"),
|
|
10121
10584
|
// A diferença que ainda não foi explicada já vem no campo:
|
|
10122
10585
|
// digitar de novo um número que a tela acabou de calcular é
|
|
10123
10586
|
// trabalho que o produto podia poupar.
|
|
@@ -10216,6 +10679,13 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10216
10679
|
) : /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: t2("financial.reconciliation.noPaymentNeeded") })
|
|
10217
10680
|
] });
|
|
10218
10681
|
const motivo = (texto) => texto ? /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: texto }) : void 0;
|
|
10682
|
+
const paginaDetalhes = {
|
|
10683
|
+
id: "details",
|
|
10684
|
+
title: intent === "bill" ? t2("financial.reconciliation.stepItems") : intent === "link" ? t2("financial.reconciliation.stepAdjust") : t2("financial.reconciliation.stepDetails"),
|
|
10685
|
+
render: () => passoDetalhes,
|
|
10686
|
+
footer: motivo(bloqueioPasso2),
|
|
10687
|
+
blockedReason: bloqueioPasso2 ?? void 0
|
|
10688
|
+
};
|
|
10219
10689
|
const pages = [
|
|
10220
10690
|
{
|
|
10221
10691
|
id: "classify",
|
|
@@ -10224,13 +10694,15 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10224
10694
|
footer: motivo(bloqueioPasso1),
|
|
10225
10695
|
blockedReason: bloqueioPasso1 ?? void 0
|
|
10226
10696
|
},
|
|
10227
|
-
{
|
|
10228
|
-
id: "
|
|
10229
|
-
title:
|
|
10230
|
-
render: () =>
|
|
10231
|
-
footer: motivo(
|
|
10232
|
-
blockedReason:
|
|
10233
|
-
},
|
|
10697
|
+
...intent === "link" ? [{
|
|
10698
|
+
id: "select",
|
|
10699
|
+
title: t2("financial.reconciliation.stepSelectBill"),
|
|
10700
|
+
render: () => passoSelecionar,
|
|
10701
|
+
footer: motivo(bloqueioSelecao),
|
|
10702
|
+
blockedReason: bloqueioSelecao ?? void 0
|
|
10703
|
+
}] : [],
|
|
10704
|
+
// Like V1: linking asks for an adjustment only when there is a gap to explain.
|
|
10705
|
+
...intent !== "link" || !target || target.mode === "settle" && Math.abs(gap) >= 0.01 ? [paginaDetalhes] : [],
|
|
10234
10706
|
{
|
|
10235
10707
|
id: "confirm",
|
|
10236
10708
|
title: t2("financial.reconciliation.stepConfirm"),
|
|
@@ -10247,7 +10719,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10247
10719
|
if (!o && !saving) onClose();
|
|
10248
10720
|
},
|
|
10249
10721
|
mode: "steps",
|
|
10250
|
-
size: "lg",
|
|
10722
|
+
size: intent === "link" && pages[step]?.id === "select" ? "4xl" : "lg",
|
|
10251
10723
|
title: t2("financial.reconciliation.flowTitle"),
|
|
10252
10724
|
pages,
|
|
10253
10725
|
current: step,
|
|
@@ -10257,8 +10729,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10257
10729
|
setStep(Math.max(0, alvo));
|
|
10258
10730
|
return;
|
|
10259
10731
|
}
|
|
10260
|
-
if (step
|
|
10261
|
-
if (step === 1 && bloqueioPasso2) return;
|
|
10732
|
+
if (pages.slice(step, alvo).some((p) => p.blockedReason)) return;
|
|
10262
10733
|
setStep(Math.min(pages.length - 1, alvo));
|
|
10263
10734
|
},
|
|
10264
10735
|
doneLabel: saving ? t2("common.saving") : t2("financial.reconciliation.confirmAction"),
|
|
@@ -10438,11 +10909,11 @@ function ReconciliationView() {
|
|
|
10438
10909
|
setBusy(false);
|
|
10439
10910
|
}
|
|
10440
10911
|
}
|
|
10441
|
-
const RefreshIcon =
|
|
10912
|
+
const RefreshIcon = React25__default.useMemo(
|
|
10442
10913
|
() => ({ className }) => /* @__PURE__ */ jsx(RefreshCw, { className: `${className ?? ""} ${loading ? "animate-spin" : ""}` }),
|
|
10443
10914
|
[loading]
|
|
10444
10915
|
);
|
|
10445
|
-
const headerActions =
|
|
10916
|
+
const headerActions = React25__default.useMemo(() => [{
|
|
10446
10917
|
id: "refresh",
|
|
10447
10918
|
label: t2("financial.reconciliation.refresh"),
|
|
10448
10919
|
icon: RefreshIcon,
|
|
@@ -10593,9 +11064,16 @@ function ReconciliationView() {
|
|
|
10593
11064
|
t: t2,
|
|
10594
11065
|
onSugerir: () => void sugerir(line),
|
|
10595
11066
|
onTransferir: () => setFlow({ line, intent: "transfer" }),
|
|
10596
|
-
onVincular: () => setFlow({ line, intent: "link" }),
|
|
11067
|
+
onVincular: () => setFlow({ line, intent: "link", step: 1 }),
|
|
10597
11068
|
onLancarConta: () => setFlow({ line, intent: "bill" }),
|
|
10598
|
-
onAceitarCandidato: (
|
|
11069
|
+
onAceitarCandidato: (cand) => {
|
|
11070
|
+
const [alvo] = resolveCandidates([cand]);
|
|
11071
|
+
if (alvo.mode === "link_only" && Math.abs(cand.movement.amount - line.amount) < 0.01) {
|
|
11072
|
+
void vincular(line.id, cand.movement.id);
|
|
11073
|
+
} else {
|
|
11074
|
+
setFlow({ line, intent: "link", target: alvo, step: 1 });
|
|
11075
|
+
}
|
|
11076
|
+
},
|
|
10599
11077
|
onDesfazer: () => void desfazer(line.id),
|
|
10600
11078
|
onVerConta: () => {
|
|
10601
11079
|
const modulo = line.direction === "debit" ? "payables" : "receivables";
|
|
@@ -10625,6 +11103,8 @@ function ReconciliationView() {
|
|
|
10625
11103
|
{
|
|
10626
11104
|
line: flow.line,
|
|
10627
11105
|
initialIntent: flow.intent,
|
|
11106
|
+
initialTarget: flow.target,
|
|
11107
|
+
initialStep: flow.step,
|
|
10628
11108
|
onClose: () => setFlow(null),
|
|
10629
11109
|
onDone: () => {
|
|
10630
11110
|
setFlow(null);
|
|
@@ -10754,7 +11234,7 @@ function StatementCard({
|
|
|
10754
11234
|
}
|
|
10755
11235
|
)
|
|
10756
11236
|
] }),
|
|
10757
|
-
/* @__PURE__ */ jsxs(Button, { size: "sm", className: "shrink-0", disabled: busy, onClick: () => onAceitarCandidato(sugestao
|
|
11237
|
+
/* @__PURE__ */ jsxs(Button, { size: "sm", className: "shrink-0", disabled: busy, onClick: () => onAceitarCandidato(sugestao), children: [
|
|
10758
11238
|
/* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" }),
|
|
10759
11239
|
t2("financial.reconciliation.match")
|
|
10760
11240
|
] })
|
|
@@ -10867,13 +11347,13 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
10867
11347
|
const provider = useFinancialProvider();
|
|
10868
11348
|
const { currency } = useFinancialConfig();
|
|
10869
11349
|
const can = usePermissionOptional();
|
|
10870
|
-
const [rows, setRows] =
|
|
10871
|
-
const [search, setSearch] =
|
|
10872
|
-
const [status, setStatus] =
|
|
10873
|
-
const [changing, setChanging] =
|
|
11350
|
+
const [rows, setRows] = React25__default.useState(null);
|
|
11351
|
+
const [search, setSearch] = React25__default.useState("");
|
|
11352
|
+
const [status, setStatus] = React25__default.useState("all");
|
|
11353
|
+
const [changing, setChanging] = React25__default.useState(null);
|
|
10874
11354
|
const isPayable = direction === "payable";
|
|
10875
11355
|
const prefix = isPayable ? "payables" : "receivables";
|
|
10876
|
-
const load =
|
|
11356
|
+
const load = React25__default.useCallback(async () => {
|
|
10877
11357
|
setRows(null);
|
|
10878
11358
|
try {
|
|
10879
11359
|
setRows(await provider.getRecurringBills?.(direction) ?? []);
|
|
@@ -10882,10 +11362,10 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
10882
11362
|
toast.error(error?.message ?? "N\xE3o foi poss\xEDvel carregar as recorr\xEAncias.");
|
|
10883
11363
|
}
|
|
10884
11364
|
}, [provider, direction]);
|
|
10885
|
-
|
|
11365
|
+
React25__default.useEffect(() => {
|
|
10886
11366
|
void load();
|
|
10887
11367
|
}, [load]);
|
|
10888
|
-
const filtered =
|
|
11368
|
+
const filtered = React25__default.useMemo(() => {
|
|
10889
11369
|
const term = search.trim().toLowerCase();
|
|
10890
11370
|
const visiveis = (rows ?? []).filter((row) => {
|
|
10891
11371
|
if (status === "active" && !row.isActive) return false;
|
|
@@ -10895,7 +11375,7 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
10895
11375
|
});
|
|
10896
11376
|
return [...visiveis].sort((a, b) => Number(b.isActive) - Number(a.isActive) || (a.dueDay ?? 0) - (b.dueDay ?? 0) || a.description.localeCompare(b.description));
|
|
10897
11377
|
}, [rows, search, status]);
|
|
10898
|
-
const porMes =
|
|
11378
|
+
const porMes = React25__default.useMemo(() => {
|
|
10899
11379
|
const fator = { weekly: 52 / 12, monthly: 1, quarterly: 1 / 3, yearly: 1 / 12 };
|
|
10900
11380
|
const ativas = filtered.filter((row) => row.isActive);
|
|
10901
11381
|
return {
|
|
@@ -10916,7 +11396,7 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
10916
11396
|
setChanging(null);
|
|
10917
11397
|
}
|
|
10918
11398
|
}
|
|
10919
|
-
const columns =
|
|
11399
|
+
const columns = React25__default.useMemo(() => [
|
|
10920
11400
|
{
|
|
10921
11401
|
accessorKey: "description",
|
|
10922
11402
|
header: "Descri\xE7\xE3o",
|
|
@@ -11012,41 +11492,43 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
11012
11492
|
function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
11013
11493
|
const provider = useFinancialProvider();
|
|
11014
11494
|
const { locations, currency, itemTypes: configuredItemTypes, entityLookups } = useFinancialConfig();
|
|
11495
|
+
const t2 = useTranslation();
|
|
11015
11496
|
const isPayable = direction === "payable";
|
|
11016
|
-
const [value, setValue] =
|
|
11017
|
-
const [loaded, setLoaded] =
|
|
11018
|
-
const [contact, setContact] =
|
|
11019
|
-
const [counterpartyAccounting, setCounterpartyAccounting] =
|
|
11497
|
+
const [value, setValue] = React25__default.useState(null);
|
|
11498
|
+
const [loaded, setLoaded] = React25__default.useState(!valueId);
|
|
11499
|
+
const [contact, setContact] = React25__default.useState(null);
|
|
11500
|
+
const [counterpartyAccounting, setCounterpartyAccounting] = React25__default.useState({
|
|
11020
11501
|
allowedAccountIds: [],
|
|
11021
11502
|
limitAccounts: false,
|
|
11022
11503
|
addItemsAutomatically: false
|
|
11023
11504
|
});
|
|
11024
|
-
const accountingPolicyRequest =
|
|
11025
|
-
const counterpartyLookup =
|
|
11505
|
+
const accountingPolicyRequest = React25__default.useRef(0);
|
|
11506
|
+
const counterpartyLookup = React25__default.useMemo(
|
|
11026
11507
|
() => createFinancialCounterpartyLookup(isPayable ? "debit" : "credit"),
|
|
11027
11508
|
[isPayable]
|
|
11028
11509
|
);
|
|
11029
11510
|
const contactCreate = useLookupCreate(counterpartyLookup, (created) => {
|
|
11030
11511
|
setContact({ id: created.id, name: created.label });
|
|
11031
11512
|
});
|
|
11032
|
-
const initialItem =
|
|
11033
|
-
const [items, setItems] =
|
|
11034
|
-
const [expandedItemId, setExpandedItemId] =
|
|
11035
|
-
const [addingItem, setAddingItem] =
|
|
11036
|
-
const [frequency, setFrequency] =
|
|
11037
|
-
const [dueDay, setDueDay] =
|
|
11038
|
-
const [startsOn, setStartsOn] =
|
|
11039
|
-
const [endsOn, setEndsOn] =
|
|
11040
|
-
const [unitId, setUnitId] =
|
|
11041
|
-
const [availableLocations, setAvailableLocations] =
|
|
11042
|
-
const [notes, setNotes] =
|
|
11043
|
-
const [isActive, setIsActive] =
|
|
11044
|
-
const [accounts, setAccounts] =
|
|
11045
|
-
const [costCenters, setCostCenters] =
|
|
11046
|
-
const
|
|
11047
|
-
const [
|
|
11048
|
-
const [
|
|
11049
|
-
|
|
11513
|
+
const initialItem = React25__default.useRef(newRecurringItem());
|
|
11514
|
+
const [items, setItems] = React25__default.useState([initialItem.current]);
|
|
11515
|
+
const [expandedItemId, setExpandedItemId] = React25__default.useState(initialItem.current._id);
|
|
11516
|
+
const [addingItem, setAddingItem] = React25__default.useState(false);
|
|
11517
|
+
const [frequency, setFrequency] = React25__default.useState("monthly");
|
|
11518
|
+
const [dueDay, setDueDay] = React25__default.useState("1");
|
|
11519
|
+
const [startsOn, setStartsOn] = React25__default.useState(today3());
|
|
11520
|
+
const [endsOn, setEndsOn] = React25__default.useState("");
|
|
11521
|
+
const [unitId, setUnitId] = React25__default.useState(locations[0]?.id ?? "");
|
|
11522
|
+
const [availableLocations, setAvailableLocations] = React25__default.useState(locations);
|
|
11523
|
+
const [notes, setNotes] = React25__default.useState("");
|
|
11524
|
+
const [isActive, setIsActive] = React25__default.useState(true);
|
|
11525
|
+
const [accounts, setAccounts] = React25__default.useState([]);
|
|
11526
|
+
const [costCenters, setCostCenters] = React25__default.useState([]);
|
|
11527
|
+
const { choices: unitChoices } = useWritableUnits(availableLocations);
|
|
11528
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
11529
|
+
const [ending, setEnding] = React25__default.useState(false);
|
|
11530
|
+
const [error, setError] = React25__default.useState(null);
|
|
11531
|
+
React25__default.useEffect(() => {
|
|
11050
11532
|
if (locations.length > 0) {
|
|
11051
11533
|
setAvailableLocations(locations);
|
|
11052
11534
|
return;
|
|
@@ -11061,7 +11543,7 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
11061
11543
|
cancelled = true;
|
|
11062
11544
|
};
|
|
11063
11545
|
}, [locations]);
|
|
11064
|
-
|
|
11546
|
+
React25__default.useEffect(() => {
|
|
11065
11547
|
let cancelled = false;
|
|
11066
11548
|
void Promise.all([
|
|
11067
11549
|
provider.getChartOfAccounts(),
|
|
@@ -11108,21 +11590,25 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
11108
11590
|
cancelled = true;
|
|
11109
11591
|
};
|
|
11110
11592
|
}, [provider, direction, valueId]);
|
|
11111
|
-
const
|
|
11112
|
-
|
|
11593
|
+
const translatedItemTypes = React25__default.useMemo(() => configuredItemTypes.map((tp) => {
|
|
11594
|
+
const label = t2(`financial.itemType.${tp.value}`);
|
|
11595
|
+
return label === `financial.itemType.${tp.value}` ? tp : { ...tp, label };
|
|
11596
|
+
}), [configuredItemTypes, t2]);
|
|
11597
|
+
const offeredItemTypes = React25__default.useMemo(() => availableInvoiceItemTypes(
|
|
11598
|
+
translatedItemTypes,
|
|
11113
11599
|
Object.keys(entityLookups),
|
|
11114
|
-
|
|
11600
|
+
t2("financial.itemType.other"),
|
|
11115
11601
|
direction
|
|
11116
|
-
), [
|
|
11602
|
+
), [translatedItemTypes, entityLookups, direction, t2]);
|
|
11117
11603
|
const itemTypes = useStockedItemTypes(offeredItemTypes, entityLookups);
|
|
11118
|
-
const selectableAccounts =
|
|
11604
|
+
const selectableAccounts = React25__default.useMemo(() => {
|
|
11119
11605
|
const postable = postableAccounts(accounts);
|
|
11120
11606
|
if (!counterpartyAccounting.limitAccounts) return postable;
|
|
11121
11607
|
const allowed = new Set(counterpartyAccounting.allowedAccountIds);
|
|
11122
11608
|
return postable.filter((account) => allowed.has(account.id));
|
|
11123
11609
|
}, [accounts, counterpartyAccounting]);
|
|
11124
11610
|
const hasItemLookups = itemTypes.some((item) => entityLookups[item.value]);
|
|
11125
|
-
const amount =
|
|
11611
|
+
const amount = React25__default.useMemo(() => items.reduce(
|
|
11126
11612
|
(sum, item) => sum + item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
|
|
11127
11613
|
0
|
|
11128
11614
|
), [items]);
|
|
@@ -11130,8 +11616,8 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
11130
11616
|
const numericDueDay = Math.trunc(Number(dueDay));
|
|
11131
11617
|
const invalid = items.length === 0 ? "Adicione pelo menos um item." : items.some((item) => !item.description.trim()) ? "Informe a descri\xE7\xE3o de todos os itens." : !contact?.id ? `Selecione ${isPayable ? "quem recebe" : "quem paga"}.` : !(amount > 0) ? "Informe um valor maior que zero." : numericDueDay < 1 || numericDueDay > 31 ? "O dia do vencimento vai de 1 a 31." : !startsOn ? "Informe o in\xEDcio da recorr\xEAncia." : endsOn && endsOn < startsOn ? "O t\xE9rmino n\xE3o pode vir antes do in\xEDcio." : null;
|
|
11132
11618
|
const fields = { items, contactId: contact?.id ?? "", frequency, dueDay, startsOn, endsOn, unitId, notes, isActive };
|
|
11133
|
-
const snapshot =
|
|
11134
|
-
|
|
11619
|
+
const snapshot = React25__default.useRef(null);
|
|
11620
|
+
React25__default.useEffect(() => {
|
|
11135
11621
|
if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
|
|
11136
11622
|
}, [loaded]);
|
|
11137
11623
|
const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
|
|
@@ -11239,13 +11725,10 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
11239
11725
|
] }) : void 0,
|
|
11240
11726
|
sideSections: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11241
11727
|
/* @__PURE__ */ jsx(OrderFormBlock, { title: "Recorr\xEAncia", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
11242
|
-
/* @__PURE__ */ jsx(
|
|
11243
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "Todas / sem unidade" }),
|
|
11244
|
-
availableLocations.map((location) => /* @__PURE__ */ jsx("option", { value: location.id, children: location.name }, location.id))
|
|
11245
|
-
] }) }),
|
|
11728
|
+
/* @__PURE__ */ jsx(UnitField, { label: "Unidade", choices: unitChoices, value: unitId, onChange: setUnitId }),
|
|
11246
11729
|
/* @__PURE__ */ jsx(Field2, { label: "Periodicidade *", children: /* @__PURE__ */ jsx("select", { value: frequency, onChange: (event) => setFrequency(event.target.value), className: "h-9 w-full rounded-input border border-input bg-card px-3 text-sm", children: FREQUENCIES.map((item) => /* @__PURE__ */ jsx("option", { value: item.value, children: item.label }, item.value)) }) }),
|
|
11247
11730
|
/* @__PURE__ */ jsx(Field2, { label: "Dia do vencimento *", children: /* @__PURE__ */ jsx(Input, { type: "number", min: 1, max: 31, value: dueDay, onChange: (event) => setDueDay(event.target.value) }) }),
|
|
11248
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
11731
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
11249
11732
|
/* @__PURE__ */ jsx(Field2, { label: "In\xEDcio *", children: /* @__PURE__ */ jsx(DatePicker, { value: startsOn, onChange: setStartsOn, className: "w-full" }) }),
|
|
11250
11733
|
/* @__PURE__ */ jsx(Field2, { label: "T\xE9rmino", children: /* @__PURE__ */ jsx(DatePicker, { value: endsOn, onChange: setEndsOn, className: "w-full" }) })
|
|
11251
11734
|
] }),
|
|
@@ -11521,16 +12004,8 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
11521
12004
|
children: [
|
|
11522
12005
|
{ id: "payables-new", kind: "action", label: t2("financial.nav.new"), active: activeView.startsWith("payables-new"), onClick: () => navigate("payables-new") },
|
|
11523
12006
|
{ id: "payables-list", label: t2("financial.nav.list"), active: activeView === "payables-list", onClick: () => navigate("payables-list") },
|
|
11524
|
-
{ id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") }
|
|
11525
|
-
//
|
|
11526
|
-
//
|
|
11527
|
-
// «Da minha caixinha» SAIU do menu. No V1 ela é `{caixaAberto && …}`:
|
|
11528
|
-
// aparece enquanto a sessão daquela pessoa está aberta e some quando
|
|
11529
|
-
// ela fecha. Como aba fixa ela prometia uma tela que quase ninguém vê
|
|
11530
|
-
// — e pagar do dinheiro da gaveta é uma FORMA de pagamento do mesmo
|
|
11531
|
-
// lançamento, não um cadastro à parte. A rota segue de pé.
|
|
11532
|
-
{ id: "payables-digitize", label: "Digitalizar Despesa", active: activeView === "payables-digitize", onClick: () => navigate("payables-digitize") },
|
|
11533
|
-
{ id: "payables-payroll", label: "Importar Folha", active: activeView === "payables-payroll", onClick: () => navigate("payables-payroll") }
|
|
12007
|
+
{ id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") }
|
|
12008
|
+
// Digitize expense and import payroll live in the list's create menu, not here.
|
|
11534
12009
|
]
|
|
11535
12010
|
});
|
|
11536
12011
|
}
|
|
@@ -11780,8 +12255,9 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
|
|
|
11780
12255
|
// tamanho do buraco, medido no banco do V1.
|
|
11781
12256
|
case "payables-petty-cash":
|
|
11782
12257
|
return /* @__PURE__ */ jsx(PettyCashView, {});
|
|
12258
|
+
// Digitizing is the list's receipt capture now — the old link lands on the list.
|
|
11783
12259
|
case "payables-digitize":
|
|
11784
|
-
return /* @__PURE__ */ jsx(
|
|
12260
|
+
return /* @__PURE__ */ jsx(PayablesView, { intent: { view: "payables", mode: "list" }, onNavigate: navigate, previousView, back });
|
|
11785
12261
|
case "payables-payroll":
|
|
11786
12262
|
return /* @__PURE__ */ jsx(ImportPayrollView, {});
|
|
11787
12263
|
case "cash-reviewers":
|
|
@@ -11988,7 +12464,7 @@ function BalanceHero() {
|
|
|
11988
12464
|
const summary = useFinancialStore((s) => s.summary);
|
|
11989
12465
|
const navigate = useDashboardNavigate();
|
|
11990
12466
|
useEnsureSummary();
|
|
11991
|
-
const [hidden, setHidden] =
|
|
12467
|
+
const [hidden, setHidden] = React25__default.useState(false);
|
|
11992
12468
|
const money4 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
|
|
11993
12469
|
const inflow = summary?.monthlyInflow ?? 0;
|
|
11994
12470
|
const outflow = summary?.monthlyOutflow ?? 0;
|
|
@@ -12061,7 +12537,7 @@ function categorizeSpend(notes) {
|
|
|
12061
12537
|
function SpendByCategoryChart() {
|
|
12062
12538
|
const t2 = useTranslation();
|
|
12063
12539
|
const provider = useFinancialProvider();
|
|
12064
|
-
const [rows, setRows] =
|
|
12540
|
+
const [rows, setRows] = React25__default.useState([]);
|
|
12065
12541
|
useEffect(() => {
|
|
12066
12542
|
let alive = true;
|
|
12067
12543
|
void (async () => {
|
|
@@ -12104,7 +12580,7 @@ function UpcomingBills() {
|
|
|
12104
12580
|
const { currency } = useFinancialConfig();
|
|
12105
12581
|
const provider = useFinancialProvider();
|
|
12106
12582
|
const dueLabel = useDueLabel();
|
|
12107
|
-
const [bills, setBills] =
|
|
12583
|
+
const [bills, setBills] = React25__default.useState([]);
|
|
12108
12584
|
useEffect(() => {
|
|
12109
12585
|
let alive = true;
|
|
12110
12586
|
void (async () => {
|
|
@@ -12214,6 +12690,10 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
12214
12690
|
"pagamento",
|
|
12215
12691
|
"pagto",
|
|
12216
12692
|
"pag",
|
|
12693
|
+
"pago",
|
|
12694
|
+
"paga",
|
|
12695
|
+
"pgto",
|
|
12696
|
+
"recebimento",
|
|
12217
12697
|
"boleto",
|
|
12218
12698
|
"debito",
|
|
12219
12699
|
"credito",
|
|
@@ -12258,15 +12738,20 @@ function nameScore(descricao, nome) {
|
|
|
12258
12738
|
if (texto.size === 0) return 0;
|
|
12259
12739
|
const achados = alvo.filter((w) => texto.has(w)).length;
|
|
12260
12740
|
const razao = achados / alvo.length;
|
|
12261
|
-
|
|
12262
|
-
|
|
12741
|
+
const nomeSet = new Set(alvo);
|
|
12742
|
+
const reverso = texto.size >= 2 && [...texto].every((w) => nomeSet.has(w)) ? 1 : 0;
|
|
12743
|
+
if (achados === 1 && alvo.length > 2) return Math.max(reverso, razao * 0.4);
|
|
12744
|
+
return Math.max(reverso, razao);
|
|
12263
12745
|
}
|
|
12264
|
-
function amountScore(lineAmount, movementAmount) {
|
|
12746
|
+
function amountScore(lineAmount, movementAmount, tolerance = 0.02) {
|
|
12265
12747
|
if (lineAmount <= 0) return 0;
|
|
12266
12748
|
const diff = Math.abs(movementAmount - lineAmount);
|
|
12267
|
-
|
|
12268
|
-
|
|
12749
|
+
const limite = lineAmount * tolerance + 0.01;
|
|
12750
|
+
if (diff > limite) return 0;
|
|
12751
|
+
return Math.max(0, 1 - diff / limite);
|
|
12269
12752
|
}
|
|
12753
|
+
var NAME_AMOUNT_TOLERANCE = 0.1;
|
|
12754
|
+
var STRONG_NAME = 0.5;
|
|
12270
12755
|
function dateScore(lineDate, refDate) {
|
|
12271
12756
|
if (!lineDate || !refDate) return 0;
|
|
12272
12757
|
const days = Math.abs(new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5;
|
|
@@ -12274,13 +12759,16 @@ function dateScore(lineDate, refDate) {
|
|
|
12274
12759
|
}
|
|
12275
12760
|
function scorePair(line, movement, context = {}) {
|
|
12276
12761
|
if (movement.direction !== line.direction) return 0;
|
|
12277
|
-
const valor = amountScore(line.amount, movement.amount);
|
|
12278
|
-
if (valor === 0) return 0;
|
|
12279
12762
|
const texto = `${line.description ?? ""} ${line.counterpartyName ?? ""}`;
|
|
12280
12763
|
const nome = Math.max(
|
|
12281
12764
|
nameScore(texto, context.contactName),
|
|
12282
12765
|
nameScore(texto, movement.notes)
|
|
12283
12766
|
);
|
|
12767
|
+
let valor = amountScore(line.amount, movement.amount);
|
|
12768
|
+
if (valor === 0 && nome >= STRONG_NAME) {
|
|
12769
|
+
valor = 0.7 * amountScore(line.amount, movement.amount, NAME_AMOUNT_TOLERANCE);
|
|
12770
|
+
}
|
|
12771
|
+
if (valor === 0) return 0;
|
|
12284
12772
|
const data = dateScore(line.date, movement.paymentDate ?? movement.dueDate);
|
|
12285
12773
|
return Math.round((0.45 * valor + 0.35 * nome + 0.2 * data) * 100) / 100;
|
|
12286
12774
|
}
|
|
@@ -13168,6 +13656,22 @@ function createMockFinancialProvider(options) {
|
|
|
13168
13656
|
};
|
|
13169
13657
|
}).sort((a, b) => b.score - a.score).slice(0, 25);
|
|
13170
13658
|
},
|
|
13659
|
+
async searchReconciliationTargets(statementLineId, query) {
|
|
13660
|
+
const line = store6.statementLines.find((l) => l.id === statementLineId);
|
|
13661
|
+
if (!line) return { data: [], hasMore: false };
|
|
13662
|
+
const status = query.status ?? "all";
|
|
13663
|
+
const termo = query.search?.trim().toLocaleLowerCase("pt-BR");
|
|
13664
|
+
const rows = store6.movements.filter((m) => !m.externalSource && !m.reconciledAt && m.direction === line.direction).filter((m) => m.status !== "cancelled").filter((m) => {
|
|
13665
|
+
const paga = m.movementKind === "payment" || m.status === "paid";
|
|
13666
|
+
return status === "all" || (status === "paid" ? paga : !paga);
|
|
13667
|
+
}).filter((m) => !query.dueFrom || (m.dueDate ?? "") >= query.dueFrom).filter((m) => !query.dueTo || (m.dueDate ?? "") <= query.dueTo).filter((m) => query.amountMin == null || m.amount >= query.amountMin).filter((m) => query.amountMax == null || m.amount <= query.amountMax).map((m) => ({ movement: m, invoice: m.invoiceId ? store6.invoices.find((i) => i.id === m.invoiceId) : void 0 })).filter(({ movement, invoice }) => !termo || `${invoice?.contactName ?? ""} ${invoice?.fiscalNumber ?? ""} ${movement.notes ?? ""}`.toLocaleLowerCase("pt-BR").includes(termo)).sort((a, b) => (a.movement.dueDate ?? "").localeCompare(b.movement.dueDate ?? ""));
|
|
13668
|
+
const pageSize = query.pageSize ?? 50;
|
|
13669
|
+
const start = ((query.page ?? 1) - 1) * pageSize;
|
|
13670
|
+
return {
|
|
13671
|
+
data: rows.slice(start, start + pageSize).map((c) => ({ ...c, score: 0 })),
|
|
13672
|
+
hasMore: rows.length > start + pageSize
|
|
13673
|
+
};
|
|
13674
|
+
},
|
|
13171
13675
|
// A mesma cabeça da conciliação de verdade (match-statement), sobre a
|
|
13172
13676
|
// memória: valor, nome e data, e um lançamento por linha.
|
|
13173
13677
|
async suggestReconciliationBatch(lines) {
|
|
@@ -13475,6 +13979,19 @@ async function writeLineAccounting(pub, tenantId, insertedItems, inputItems) {
|
|
|
13475
13979
|
const { error } = await pub.from(T.invoiceItems).upsert(rows, { onConflict: "item_id" });
|
|
13476
13980
|
if (error) console.warn("[financial] line accounting mirror skipped:", error.message);
|
|
13477
13981
|
}
|
|
13982
|
+
function shiftIsoDate(value, days) {
|
|
13983
|
+
const d = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
13984
|
+
d.setUTCDate(d.getUTCDate() + days);
|
|
13985
|
+
return d.toISOString().slice(0, 10);
|
|
13986
|
+
}
|
|
13987
|
+
function matchScore(line, m) {
|
|
13988
|
+
const amount = Number(line.amount) || 0;
|
|
13989
|
+
const amtDiff = Math.abs((Number(m.amount) || 0) - amount);
|
|
13990
|
+
const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
|
|
13991
|
+
const refDate = m.dueDate ?? m.paymentDate;
|
|
13992
|
+
const days = line.date && refDate ? Math.abs((new Date(line.date).getTime() - new Date(refDate).getTime()) / 864e5) : 30;
|
|
13993
|
+
return round26(0.7 * amtScore + 0.3 * Math.max(0, 1 - days / 30));
|
|
13994
|
+
}
|
|
13478
13995
|
function billRowToMovement(r) {
|
|
13479
13996
|
return {
|
|
13480
13997
|
id: r.installment_id,
|
|
@@ -13571,6 +14088,85 @@ function createSupabaseFinancialProvider() {
|
|
|
13571
14088
|
externalSource: data.external_source
|
|
13572
14089
|
};
|
|
13573
14090
|
}
|
|
14091
|
+
async function searchBillTargets(line, query) {
|
|
14092
|
+
const { pub } = getClients();
|
|
14093
|
+
const pageSize = query.pageSize ?? 50;
|
|
14094
|
+
const page = query.page ?? 1;
|
|
14095
|
+
let qb = pub.from("v_financial_invoice_lines_due").select("installment_id,invoice_id,direction,number,due_date,description,counterparty_name,installment_number,installment_count,amount,paid,status").eq("direction", line.direction === "debit" ? "payable" : "receivable");
|
|
14096
|
+
const status = query.status ?? "all";
|
|
14097
|
+
qb = status === "open" ? qb.in("status", ["pending", "partial", "overdue"]) : status === "paid" ? qb.eq("status", "paid") : qb.neq("status", "cancelled");
|
|
14098
|
+
if (query.dueFrom) qb = qb.gte("due_date", query.dueFrom);
|
|
14099
|
+
if (query.dueTo) qb = qb.lte("due_date", query.dueTo);
|
|
14100
|
+
if (query.amountMin != null) qb = qb.gte("amount", query.amountMin);
|
|
14101
|
+
if (query.amountMax != null) qb = qb.lte("amount", query.amountMax);
|
|
14102
|
+
const termo = query.search?.trim().replace(/[,()*%]/g, " ").replace(/\s+/g, " ");
|
|
14103
|
+
if (termo) {
|
|
14104
|
+
qb = qb.or(["description", "number", "counterparty_name"].map((c) => `${c}.ilike.%${termo}%`).join(","));
|
|
14105
|
+
}
|
|
14106
|
+
const { data, error } = await qb.order("due_date", { ascending: true }).range((page - 1) * pageSize, page * pageSize);
|
|
14107
|
+
if (error) throw error;
|
|
14108
|
+
const rows = data ?? [];
|
|
14109
|
+
const hasMore = rows.length > pageSize;
|
|
14110
|
+
const visiveis = rows.slice(0, pageSize);
|
|
14111
|
+
const pagas = visiveis.filter((r) => r.status === "paid").map((r) => r.installment_id);
|
|
14112
|
+
const pagamentoDe = /* @__PURE__ */ new Map();
|
|
14113
|
+
if (pagas.length) {
|
|
14114
|
+
const { data: allocs } = await pub.from(T.allocations).select("installment_id,payment_movement_id").in("installment_id", pagas);
|
|
14115
|
+
const ids = [...new Set((allocs ?? []).map((a) => a.payment_movement_id).filter(Boolean))];
|
|
14116
|
+
const { data: pays } = ids.length ? await pub.from(T.movements).select("*").in("id", ids).neq("status", "cancelled").is("reversed_by_id", null) : { data: [] };
|
|
14117
|
+
const byId = new Map((pays ?? []).map((p) => [p.id, p]));
|
|
14118
|
+
for (const a of allocs ?? []) {
|
|
14119
|
+
const p = byId.get(a.payment_movement_id);
|
|
14120
|
+
if (p) pagamentoDe.set(a.installment_id, p);
|
|
14121
|
+
}
|
|
14122
|
+
}
|
|
14123
|
+
const candidates = [];
|
|
14124
|
+
for (const r of visiveis) {
|
|
14125
|
+
const invoice = {
|
|
14126
|
+
id: r.invoice_id,
|
|
14127
|
+
contactName: r.counterparty_name ?? void 0,
|
|
14128
|
+
fiscalNumber: r.number ?? void 0
|
|
14129
|
+
};
|
|
14130
|
+
let movement;
|
|
14131
|
+
if (r.status === "paid") {
|
|
14132
|
+
const p = pagamentoDe.get(r.installment_id);
|
|
14133
|
+
if (!p || p.reconciled_at) continue;
|
|
14134
|
+
movement = {
|
|
14135
|
+
...snakeToCamel(p),
|
|
14136
|
+
invoiceId: r.invoice_id,
|
|
14137
|
+
settlesMovementId: r.installment_id,
|
|
14138
|
+
dueDate: r.due_date,
|
|
14139
|
+
notes: p.notes ?? r.description ?? void 0
|
|
14140
|
+
};
|
|
14141
|
+
} else {
|
|
14142
|
+
movement = {
|
|
14143
|
+
id: r.installment_id,
|
|
14144
|
+
invoiceId: r.invoice_id,
|
|
14145
|
+
direction: line.direction,
|
|
14146
|
+
movementKind: "bill",
|
|
14147
|
+
amount: Number(r.amount) || 0,
|
|
14148
|
+
paidAmount: Number(r.paid) || 0,
|
|
14149
|
+
status: r.status,
|
|
14150
|
+
dueDate: r.due_date,
|
|
14151
|
+
installmentNumber: r.installment_number ?? void 0,
|
|
14152
|
+
notes: r.description ?? void 0
|
|
14153
|
+
};
|
|
14154
|
+
}
|
|
14155
|
+
const pareado = scorePair(
|
|
14156
|
+
{ amount: line.amount, date: line.date, direction: line.direction, description: line.description ?? "" },
|
|
14157
|
+
movement,
|
|
14158
|
+
{ contactName: r.counterparty_name ?? void 0 }
|
|
14159
|
+
);
|
|
14160
|
+
candidates.push({
|
|
14161
|
+
movement,
|
|
14162
|
+
invoice,
|
|
14163
|
+
score: pareado > 0 ? pareado : round26(0.6 * matchScore(line, movement)),
|
|
14164
|
+
installmentCount: r.installment_count ?? void 0
|
|
14165
|
+
});
|
|
14166
|
+
}
|
|
14167
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
14168
|
+
return { data: candidates, hasMore };
|
|
14169
|
+
}
|
|
13574
14170
|
async function markReconciled(statementLineId, matchedMovementId) {
|
|
13575
14171
|
const { pub } = getClients();
|
|
13576
14172
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -13778,6 +14374,33 @@ function createSupabaseFinancialProvider() {
|
|
|
13778
14374
|
};
|
|
13779
14375
|
});
|
|
13780
14376
|
},
|
|
14377
|
+
async addInvoiceAttachment(invoiceId, file) {
|
|
14378
|
+
const { pub } = getClients();
|
|
14379
|
+
const tenantId = getTenantId();
|
|
14380
|
+
if (!tenantId) throw new Error("Conta sem empresa ativa");
|
|
14381
|
+
const safe = file.name.replace(/[^\w.\-]+/g, "_");
|
|
14382
|
+
const path = `${tenantId}/financial_invoice/${invoiceId}/${Date.now()}_${safe}`;
|
|
14383
|
+
const { error: upErr } = await pub.storage.from("attachments").upload(path, file, { contentType: file.type || void 0 });
|
|
14384
|
+
if (upErr) throw upErr;
|
|
14385
|
+
const { error } = await pub.from("documents").insert({
|
|
14386
|
+
tenant_id: tenantId,
|
|
14387
|
+
kind: "attachment",
|
|
14388
|
+
status: "active",
|
|
14389
|
+
subject_type: "financial_invoice",
|
|
14390
|
+
subject_id: invoiceId,
|
|
14391
|
+
file_name: file.name,
|
|
14392
|
+
file_size: file.size,
|
|
14393
|
+
mime_type: file.type || null,
|
|
14394
|
+
storage_provider: "supabase",
|
|
14395
|
+
storage_bucket: "attachments",
|
|
14396
|
+
storage_path: path
|
|
14397
|
+
});
|
|
14398
|
+
if (error) {
|
|
14399
|
+
await pub.storage.from("attachments").remove([path]).catch(() => {
|
|
14400
|
+
});
|
|
14401
|
+
throw error;
|
|
14402
|
+
}
|
|
14403
|
+
},
|
|
13781
14404
|
async createInvoice(input) {
|
|
13782
14405
|
const { core, pub } = getClients();
|
|
13783
14406
|
const tenantId = getTenantId();
|
|
@@ -15184,6 +15807,31 @@ function createSupabaseFinancialProvider() {
|
|
|
15184
15807
|
customerEmail: r.customer_email
|
|
15185
15808
|
}));
|
|
15186
15809
|
},
|
|
15810
|
+
async findGiftCardByCode(raw) {
|
|
15811
|
+
const code = raw.replace(/[\s.-]/g, "").toUpperCase();
|
|
15812
|
+
if (!code) return null;
|
|
15813
|
+
const { pub } = getClients();
|
|
15814
|
+
let { data, error } = await pub.rpc("financial_gift_card_by_code", { p_code: code });
|
|
15815
|
+
if (error?.code === "PGRST202") {
|
|
15816
|
+
({ data, error } = await pub.rpc("financial_gift_cards", { p_search: code, p_limit: 5 }));
|
|
15817
|
+
}
|
|
15818
|
+
if (error) throw error;
|
|
15819
|
+
const row = (data ?? []).find((r) => String(r.code).toUpperCase() === code);
|
|
15820
|
+
if (!row) return null;
|
|
15821
|
+
return {
|
|
15822
|
+
id: row.id,
|
|
15823
|
+
code: row.code,
|
|
15824
|
+
faceValue: Number(row.face_value) || 0,
|
|
15825
|
+
balance: row.balance == null ? null : Number(row.balance),
|
|
15826
|
+
state: row.state,
|
|
15827
|
+
currency: row.currency,
|
|
15828
|
+
issuedAt: row.issued_at,
|
|
15829
|
+
expiresAt: row.expires_at,
|
|
15830
|
+
activatedAt: row.activated_at,
|
|
15831
|
+
note: row.note,
|
|
15832
|
+
customerEmail: row.customer_email
|
|
15833
|
+
};
|
|
15834
|
+
},
|
|
15187
15835
|
async issueGiftCards(input) {
|
|
15188
15836
|
const { pub } = getClients();
|
|
15189
15837
|
const { data, error } = await pub.rpc("financial_issue_gift_cards", {
|
|
@@ -15343,7 +15991,17 @@ function createSupabaseFinancialProvider() {
|
|
|
15343
15991
|
if (o) c.invoice = { id: o.id, contactName: o.metadata?.contactName, fiscalNumber: o.reference_number };
|
|
15344
15992
|
}
|
|
15345
15993
|
}
|
|
15346
|
-
|
|
15994
|
+
const linha = { id: line.id, amount, date: lineDate, direction: line.direction, externalSource: line.external_source };
|
|
15995
|
+
const abertas = await searchBillTargets(linha, {
|
|
15996
|
+
status: "open",
|
|
15997
|
+
amountMin: round26(amount - tol),
|
|
15998
|
+
amountMax: round26(amount + tol),
|
|
15999
|
+
pageSize: 25
|
|
16000
|
+
}).catch(() => ({ data: [], hasMore: false }));
|
|
16001
|
+
return [...candidates, ...abertas.data].sort((a, b) => b.score - a.score);
|
|
16002
|
+
},
|
|
16003
|
+
async searchReconciliationTargets(statementLineId, query) {
|
|
16004
|
+
return searchBillTargets(await readStatementLine(statementLineId), query);
|
|
15347
16005
|
},
|
|
15348
16006
|
// ------------------------------------------------------------------
|
|
15349
16007
|
// A SUGESTÃO QUE CHEGA SOZINHA, para a página inteira.
|
|
@@ -15363,16 +16021,38 @@ function createSupabaseFinancialProvider() {
|
|
|
15363
16021
|
const abertas = lines.filter((line) => !line.reconciledAt && (Number(line.amount) || 0) > 0);
|
|
15364
16022
|
if (abertas.length === 0) return {};
|
|
15365
16023
|
const faixas = [...new Set(abertas.map((line) => round26(line.amount)))].map((valor) => {
|
|
15366
|
-
const tol = Math.max(0.01, round26(valor *
|
|
16024
|
+
const tol = Math.max(0.01, round26(valor * NAME_AMOUNT_TOLERANCE));
|
|
15367
16025
|
return `and(amount.gte.${round26(valor - tol)},amount.lte.${round26(valor + tol)})`;
|
|
15368
16026
|
});
|
|
15369
16027
|
const direcoes = [...new Set(abertas.map((line) => line.direction))];
|
|
15370
16028
|
const { data, error } = await pub.from(T.movements).select("*").is("external_source", null).is("reconciled_at", null).in("direction", direcoes).or(faixas.join(",")).limit(500);
|
|
15371
16029
|
if (error) throw error;
|
|
15372
16030
|
const movimentos = (data ?? []).map((r) => snakeToCamel(r));
|
|
15373
|
-
|
|
16031
|
+
const datas = abertas.map((line) => line.date).filter(Boolean).sort();
|
|
15374
16032
|
const contexto = /* @__PURE__ */ new Map();
|
|
15375
|
-
|
|
16033
|
+
if (datas.length) {
|
|
16034
|
+
const { data: billRows } = await pub.from("v_financial_invoice_lines_due").select("installment_id,invoice_id,direction,number,due_date,description,counterparty_name,installment_number,amount,paid,status").in("direction", direcoes.map((d) => d === "debit" ? "payable" : "receivable")).in("status", ["pending", "partial", "overdue"]).gte("due_date", shiftIsoDate(datas[0], -30)).lte("due_date", shiftIsoDate(datas[datas.length - 1], 30)).or(faixas.join(",")).limit(500);
|
|
16035
|
+
for (const r of billRows ?? []) {
|
|
16036
|
+
movimentos.push({
|
|
16037
|
+
id: r.installment_id,
|
|
16038
|
+
invoiceId: r.invoice_id,
|
|
16039
|
+
direction: r.direction === "payable" ? "debit" : "credit",
|
|
16040
|
+
movementKind: "bill",
|
|
16041
|
+
amount: Number(r.amount) || 0,
|
|
16042
|
+
paidAmount: Number(r.paid) || 0,
|
|
16043
|
+
status: r.status,
|
|
16044
|
+
dueDate: r.due_date,
|
|
16045
|
+
installmentNumber: r.installment_number ?? void 0,
|
|
16046
|
+
notes: r.description ?? void 0
|
|
16047
|
+
});
|
|
16048
|
+
contexto.set(r.installment_id, {
|
|
16049
|
+
contactName: r.counterparty_name ?? void 0,
|
|
16050
|
+
invoice: { id: r.invoice_id, contactName: r.counterparty_name ?? void 0, fiscalNumber: r.number ?? void 0 }
|
|
16051
|
+
});
|
|
16052
|
+
}
|
|
16053
|
+
}
|
|
16054
|
+
if (movimentos.length === 0) return {};
|
|
16055
|
+
const invoiceIds = [...new Set(movimentos.filter((m) => !contexto.has(m.id)).map((m) => m.invoiceId).filter(Boolean))];
|
|
15376
16056
|
if (invoiceIds.length > 0) {
|
|
15377
16057
|
const { data: orders } = await core.from("orders").select("id, reference_number, metadata").in("id", invoiceIds);
|
|
15378
16058
|
const byId = new Map((orders ?? []).map((o) => [o.id, o]));
|
|
@@ -15391,7 +16071,7 @@ function createSupabaseFinancialProvider() {
|
|
|
15391
16071
|
async reconcileMovement(input) {
|
|
15392
16072
|
if (input.mode === "settle" && input.matchedMovementId) {
|
|
15393
16073
|
const line = await readStatementLine(input.bankMovementId);
|
|
15394
|
-
const { data: alvo } = await getClients().pub.from(T.
|
|
16074
|
+
const { data: alvo } = await getClients().pub.from(T.bills).select("amount, paid").eq("installment_id", input.matchedMovementId).maybeSingle();
|
|
15395
16075
|
if (!alvo) throw new Error("Lan\xE7amento n\xE3o encontrado");
|
|
15396
16076
|
await runReconcileLink(
|
|
15397
16077
|
reconcileFacade(),
|
|
@@ -15399,7 +16079,7 @@ function createSupabaseFinancialProvider() {
|
|
|
15399
16079
|
{
|
|
15400
16080
|
movementId: input.matchedMovementId,
|
|
15401
16081
|
mode: "settle",
|
|
15402
|
-
openAmount: round26((Number(alvo.amount) || 0) - (Number(alvo.
|
|
16082
|
+
openAmount: round26((Number(alvo.amount) || 0) - (Number(alvo.paid) || 0))
|
|
15403
16083
|
},
|
|
15404
16084
|
{ payment: input.payment, adjustments: input.adjustments }
|
|
15405
16085
|
);
|
|
@@ -15674,7 +16354,8 @@ function createSupabaseFinancialProvider() {
|
|
|
15674
16354
|
};
|
|
15675
16355
|
for (const payee of payees) {
|
|
15676
16356
|
if (text2(payee.document)) {
|
|
15677
|
-
await pub.from("people").update({ document_number: text2(payee.document) }).eq("id", payee.contactId);
|
|
16357
|
+
const { error: error2 } = await pub.from("people").update({ document_number: text2(payee.document) }).eq("id", payee.contactId);
|
|
16358
|
+
if (error2) throw error2;
|
|
15678
16359
|
}
|
|
15679
16360
|
const row = {
|
|
15680
16361
|
bank_code: text2(payee.bankCode),
|
|
@@ -15693,18 +16374,16 @@ function createSupabaseFinancialProvider() {
|
|
|
15693
16374
|
const { data: existing } = await pub.from(T.payeeAccounts).select("id").eq("owner_type", "person").eq("owner_id", payee.contactId).order("is_default", { ascending: false }).order("created_at").limit(1).maybeSingle();
|
|
15694
16375
|
targetId = existing?.id;
|
|
15695
16376
|
}
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
});
|
|
15707
|
-
}
|
|
16377
|
+
const { data: written, error } = targetId ? await pub.from(T.payeeAccounts).update(row).eq("id", targetId).select("id") : await pub.from(T.payeeAccounts).insert({
|
|
16378
|
+
...row,
|
|
16379
|
+
tenant_id: tenantId,
|
|
16380
|
+
owner_type: "person",
|
|
16381
|
+
owner_id: payee.contactId,
|
|
16382
|
+
label: "Principal",
|
|
16383
|
+
is_default: true
|
|
16384
|
+
}).select("id");
|
|
16385
|
+
if (error) throw error;
|
|
16386
|
+
if (!written?.length) throw new Error("payee account was not saved (no financial edit permission?)");
|
|
15708
16387
|
}
|
|
15709
16388
|
},
|
|
15710
16389
|
/**
|
|
@@ -16252,21 +16931,21 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
|
|
|
16252
16931
|
const canCreate = can("financial", "create");
|
|
16253
16932
|
const canEdit = can("financial", "edit");
|
|
16254
16933
|
const canDelete = can("financial", "delete");
|
|
16255
|
-
const provider =
|
|
16934
|
+
const provider = React25__default.useMemo(
|
|
16256
16935
|
() => dataProvider ?? resolveDataProvider(entity, mockData ?? []),
|
|
16257
16936
|
[entity, mockData, dataProvider]
|
|
16258
16937
|
);
|
|
16259
16938
|
const table = entity.data?.table;
|
|
16260
|
-
const [nodes, setNodes] =
|
|
16261
|
-
const [loadError, setLoadError] =
|
|
16262
|
-
const [expanded, setExpanded] =
|
|
16263
|
-
const [query, setQuery] =
|
|
16264
|
-
const [showInactive, setShowInactive] =
|
|
16265
|
-
const [editing, setEditing] =
|
|
16266
|
-
const [confirmDelete, setConfirmDelete] =
|
|
16267
|
-
const [busy, setBusy] =
|
|
16268
|
-
const initialized =
|
|
16269
|
-
const reload =
|
|
16939
|
+
const [nodes, setNodes] = React25__default.useState(null);
|
|
16940
|
+
const [loadError, setLoadError] = React25__default.useState(null);
|
|
16941
|
+
const [expanded, setExpanded] = React25__default.useState(/* @__PURE__ */ new Set());
|
|
16942
|
+
const [query, setQuery] = React25__default.useState("");
|
|
16943
|
+
const [showInactive, setShowInactive] = React25__default.useState(true);
|
|
16944
|
+
const [editing, setEditing] = React25__default.useState(null);
|
|
16945
|
+
const [confirmDelete, setConfirmDelete] = React25__default.useState(null);
|
|
16946
|
+
const [busy, setBusy] = React25__default.useState(false);
|
|
16947
|
+
const initialized = React25__default.useRef(false);
|
|
16948
|
+
const reload = React25__default.useCallback(async () => {
|
|
16270
16949
|
try {
|
|
16271
16950
|
const rows = await loadAll(provider);
|
|
16272
16951
|
setNodes(rows);
|
|
@@ -16281,17 +16960,17 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
|
|
|
16281
16960
|
setNodes((prev) => prev ?? []);
|
|
16282
16961
|
}
|
|
16283
16962
|
}, [provider]);
|
|
16284
|
-
|
|
16963
|
+
React25__default.useEffect(() => {
|
|
16285
16964
|
void reload();
|
|
16286
16965
|
}, [reload]);
|
|
16287
|
-
const shown =
|
|
16966
|
+
const shown = React25__default.useMemo(
|
|
16288
16967
|
() => (nodes ?? []).filter((n) => showInactive || n.isActive),
|
|
16289
16968
|
[nodes, showInactive]
|
|
16290
16969
|
);
|
|
16291
|
-
const tree =
|
|
16292
|
-
const fullTree =
|
|
16293
|
-
const search =
|
|
16294
|
-
const groupIds =
|
|
16970
|
+
const tree = React25__default.useMemo(() => buildChartTree(shown), [shown]);
|
|
16971
|
+
const fullTree = React25__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
|
|
16972
|
+
const search = React25__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
|
|
16973
|
+
const groupIds = React25__default.useMemo(
|
|
16295
16974
|
() => [...tree.byId.values()].filter((n) => isGroupNode(tree, n)).map((n) => n.id),
|
|
16296
16975
|
[tree]
|
|
16297
16976
|
);
|
|
@@ -16387,7 +17066,7 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
|
|
|
16387
17066
|
const ok = await run(() => provider.remove(node.id), t2("financial.chartTree.deleted", { name: node.name }));
|
|
16388
17067
|
if (ok) setConfirmDelete(null);
|
|
16389
17068
|
}
|
|
16390
|
-
const parentOptions =
|
|
17069
|
+
const parentOptions = React25__default.useCallback((q, selfId) => {
|
|
16391
17070
|
const blocked = selfId ? descendantIds(fullTree, selfId).add(selfId) : /* @__PURE__ */ new Set();
|
|
16392
17071
|
const needle = q.trim().toLowerCase();
|
|
16393
17072
|
return [...fullTree.byId.values()].filter((n) => !blocked.has(n.id) && isGroupNode(fullTree, n)).filter((n) => !needle || accountLabel(n).toLowerCase().includes(needle)).sort((a, b) => a.code.localeCompare(b.code, void 0, { numeric: true })).slice(0, 50).map((n) => ({ id: n.id, label: accountLabel(n) }));
|
|
@@ -16690,7 +17369,7 @@ var chartOfAccountsSeed = [
|
|
|
16690
17369
|
{ id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
|
|
16691
17370
|
{ id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
|
|
16692
17371
|
];
|
|
16693
|
-
var ChartOfAccountsRegistryView = (props) =>
|
|
17372
|
+
var ChartOfAccountsRegistryView = (props) => React25__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
|
|
16694
17373
|
ChartOfAccountsRegistryView.displayName = "ChartOfAccountsRegistryView";
|
|
16695
17374
|
var costCenterEntity = {
|
|
16696
17375
|
name: "Cost Center",
|
|
@@ -16788,7 +17467,7 @@ var bankAccountEntity = {
|
|
|
16788
17467
|
labelWhen: (v) => v.kind === "card_payable" ? "Banco emissor" : void 0,
|
|
16789
17468
|
visibleWhen: (v) => hasIssuer(v.kind),
|
|
16790
17469
|
group: "bank",
|
|
16791
|
-
renderInput: ({ value, values, onChange, setValue }) =>
|
|
17470
|
+
renderInput: ({ value, values, onChange, setValue }) => React25__default.createElement(BankPicker, {
|
|
16792
17471
|
code: values.bankCode ?? "",
|
|
16793
17472
|
name: value ?? "",
|
|
16794
17473
|
onChange: (next) => {
|
|
@@ -17657,10 +18336,10 @@ function CashSessionHeaderControl({ config, provider, store: store6 }) {
|
|
|
17657
18336
|
const fetchCashSessions = useStore(store6, (s) => s.fetchCashSessions);
|
|
17658
18337
|
const open = useStore(store6, (s) => s.cashSessionModalOpen);
|
|
17659
18338
|
const setOpen = useStore(store6, (s) => s.setCashSessionModalOpen);
|
|
17660
|
-
|
|
18339
|
+
React25__default.useEffect(() => {
|
|
17661
18340
|
void fetchCashSessions?.();
|
|
17662
18341
|
}, [fetchCashSessions]);
|
|
17663
|
-
|
|
18342
|
+
React25__default.useEffect(() => {
|
|
17664
18343
|
if (!open) return;
|
|
17665
18344
|
const close = () => setOpen(false);
|
|
17666
18345
|
window.addEventListener("hashchange", close);
|
|
@@ -17780,7 +18459,7 @@ var en = {
|
|
|
17780
18459
|
"financial.invoice.items": "Items",
|
|
17781
18460
|
"financial.invoice.keepInvoice": "Keep Invoice",
|
|
17782
18461
|
"financial.invoice.new": "New",
|
|
17783
|
-
"financial.invoice.newManual": "
|
|
18462
|
+
"financial.invoice.newManual": "Create",
|
|
17784
18463
|
"financial.invoiceForm.createContact": "Create",
|
|
17785
18464
|
"financial.invoiceForm.alreadyPaid": "Already paid",
|
|
17786
18465
|
"financial.invoiceForm.paidWith": "Payment method",
|
|
@@ -17792,6 +18471,9 @@ var en = {
|
|
|
17792
18471
|
"financial.invoiceForm.paidNeedsMethod": "Tell us the payment method and the account",
|
|
17793
18472
|
"financial.invoiceForm.willBePaid": "This installment will be settled",
|
|
17794
18473
|
"financial.invoiceForm.clear": "Clear",
|
|
18474
|
+
"financial.invoiceForm.manageAccounts": "Manage accounts",
|
|
18475
|
+
"financial.invoiceForm.manageChart": "Manage chart of accounts",
|
|
18476
|
+
"financial.invoiceForm.manageCostCenters": "Manage cost centers",
|
|
17795
18477
|
"financial.invoice.newManualHint": "Open the full invoice form",
|
|
17796
18478
|
"financial.invoice.noDetails": "No additional details recorded",
|
|
17797
18479
|
"financial.invoice.unlinkedPayments": "Payments not linked to an installment",
|
|
@@ -17840,6 +18522,13 @@ var en = {
|
|
|
17840
18522
|
"financial.itemType.product": "Product",
|
|
17841
18523
|
"financial.itemType.other": "Other",
|
|
17842
18524
|
"financial.itemType.gift_card": "Gift Card",
|
|
18525
|
+
"financial.giftCardLine.label": "Gift card code",
|
|
18526
|
+
"financial.giftCardLine.placeholder": "0000 0000 0000 0000",
|
|
18527
|
+
"financial.giftCardLine.hint": "Scan or type the full card code.",
|
|
18528
|
+
"financial.giftCardLine.notFound": "Gift card not found",
|
|
18529
|
+
"financial.giftCardLine.revoked": "Gift card cancelled \u2014 cannot be sold",
|
|
18530
|
+
"financial.giftCardLine.expired": "Gift card expired",
|
|
18531
|
+
"financial.giftCardLine.alreadySold": "This gift card has already been sold",
|
|
17843
18532
|
"financial.invoiceForm.cancel": "Cancel",
|
|
17844
18533
|
"financial.invoiceForm.close": "Close",
|
|
17845
18534
|
"financial.invoiceForm.creating": "Creating new invoice",
|
|
@@ -18043,13 +18732,41 @@ var en = {
|
|
|
18043
18732
|
"financial.reconciliation.reconciled": "Reconciled",
|
|
18044
18733
|
"financial.reconciliation.undo": "Undo",
|
|
18045
18734
|
"financial.reconciliation.flowTitle": "Reconcile transaction",
|
|
18046
|
-
"financial.reconciliation.stepClassify": "
|
|
18735
|
+
"financial.reconciliation.stepClassify": "What to do",
|
|
18047
18736
|
"financial.reconciliation.stepItems": "The items",
|
|
18048
18737
|
"financial.reconciliation.stepDetails": "The details",
|
|
18049
18738
|
"financial.reconciliation.stepConfirm": "Confirm",
|
|
18050
18739
|
"financial.reconciliation.confirmAction": "Confirm",
|
|
18051
18740
|
"financial.reconciliation.howToClassify": "How do you want to classify this transaction?",
|
|
18052
18741
|
"financial.reconciliation.pickIntent": "Pick what this transaction is",
|
|
18742
|
+
"financial.reconciliation.foundTitle": "Bills found",
|
|
18743
|
+
"financial.reconciliation.searchWiden": "Widen search (\xB115 days, any amount)",
|
|
18744
|
+
"financial.reconciliation.stepSelectBill": "Select the bill",
|
|
18745
|
+
"financial.reconciliation.stepAdjust": "Adjustment",
|
|
18746
|
+
"financial.reconciliation.selectBillFor": "Statement of {{date}} \xB7 {{description}} \xB7",
|
|
18747
|
+
"financial.reconciliation.statusOverdue": "Overdue",
|
|
18748
|
+
"financial.reconciliation.colDue": "Due",
|
|
18749
|
+
"financial.reconciliation.colCounterparty": "Counterparty",
|
|
18750
|
+
"financial.reconciliation.colDescription": "Number / Description",
|
|
18751
|
+
"financial.reconciliation.colInstallment": "Installment",
|
|
18752
|
+
"financial.reconciliation.colToReconcile": "To reconcile",
|
|
18753
|
+
"financial.reconciliation.colDelta": "\u0394 vs statement",
|
|
18754
|
+
"financial.reconciliation.searchStatusAllShort": "All",
|
|
18755
|
+
"financial.reconciliation.searchPendingHint": "The list still shows the previous filters. Click Filter to apply.",
|
|
18756
|
+
"financial.reconciliation.searchTitle": "Find a bill",
|
|
18757
|
+
"financial.reconciliation.searchDueFrom": "Due from",
|
|
18758
|
+
"financial.reconciliation.searchDueTo": "to",
|
|
18759
|
+
"financial.reconciliation.searchStatus": "Status",
|
|
18760
|
+
"financial.reconciliation.searchStatusAll": "All (not reconciled)",
|
|
18761
|
+
"financial.reconciliation.searchStatusOpen": "Open",
|
|
18762
|
+
"financial.reconciliation.searchStatusPaid": "Paid",
|
|
18763
|
+
"financial.reconciliation.searchText": "Search (counterparty, number, description)",
|
|
18764
|
+
"financial.reconciliation.searchTextPlaceholder": "Type and press Enter",
|
|
18765
|
+
"financial.reconciliation.searchAmountMin": "Min amount",
|
|
18766
|
+
"financial.reconciliation.searchAmountMax": "Max amount",
|
|
18767
|
+
"financial.reconciliation.searchAction": "Search",
|
|
18768
|
+
"financial.reconciliation.searchEmpty": "No bills match these filters.",
|
|
18769
|
+
"financial.reconciliation.searchMore": "Load more",
|
|
18053
18770
|
"financial.reconciliation.pickCandidate": "Pick the entry to link",
|
|
18054
18771
|
"financial.reconciliation.needDescription": "Describe the entry",
|
|
18055
18772
|
"financial.reconciliation.intentLink": "Link to an existing entry",
|
|
@@ -18085,6 +18802,14 @@ var en = {
|
|
|
18085
18802
|
"financial.reconciliation.linkOnlyExplain": "This bill was already paid in the system. Confirming only ties the statement line to it \u2014 no second payment, no change to the amount.",
|
|
18086
18803
|
"financial.reconciliation.noAdjustmentNeeded": "The amount matches the bill. Nothing to adjust.",
|
|
18087
18804
|
"financial.reconciliation.adjustmentNeeded": "The bank took {{gap}} {{side}} than the bill asked for. Say what that difference is.",
|
|
18805
|
+
"financial.reconciliation.adjustmentNeededIn": "{{gap}} {{side}} came in than the bill charged. Say what that difference is.",
|
|
18806
|
+
"financial.reconciliation.adjustmentNeedsAccount": "Pick the chart account for each adjustment.",
|
|
18807
|
+
"financial.reconciliation.adjustmentType": "Type",
|
|
18808
|
+
"financial.reconciliation.adjustmentDescriptionPlaceholder": "E.g. late interest",
|
|
18809
|
+
"financial.reconciliation.adjustmentDefaultInterest": "Late interest",
|
|
18810
|
+
"financial.reconciliation.adjustmentDefaultFine": "Late fine",
|
|
18811
|
+
"financial.reconciliation.adjustmentDefaultDiscountOut": "Discount obtained",
|
|
18812
|
+
"financial.reconciliation.adjustmentDefaultDiscountIn": "Discount granted",
|
|
18088
18813
|
"financial.reconciliation.adjustmentMore": "more",
|
|
18089
18814
|
"financial.reconciliation.adjustmentLess": "less",
|
|
18090
18815
|
"financial.reconciliation.interest": "Interest",
|
|
@@ -18519,6 +19244,10 @@ var en = {
|
|
|
18519
19244
|
"financial.quickTx.receiptRemove": "Remove receipt",
|
|
18520
19245
|
"financial.quickTx.receiptInvalid": "Please choose an image file",
|
|
18521
19246
|
"financial.quickTx.sendReceipt": "Send receipt",
|
|
19247
|
+
"financial.quickTx.digitizeExpense": "Digitize expense",
|
|
19248
|
+
"financial.quickTx.digitizeReceipt": "Digitize receipt",
|
|
19249
|
+
"financial.invoice.importPayroll": "Import payroll",
|
|
19250
|
+
"financial.invoice.importPayrollSoon": "Coming soon",
|
|
18522
19251
|
"financial.receipt.ask": "Send the receipt photo\u2026",
|
|
18523
19252
|
"financial.receipt.attachRead": "Attach the receipt the assistant read",
|
|
18524
19253
|
"financial.receipt.fromReceipt": "From the receipt",
|
|
@@ -18670,7 +19399,7 @@ var ptBR = {
|
|
|
18670
19399
|
"financial.invoice.items": "Itens",
|
|
18671
19400
|
"financial.invoice.keepInvoice": "Manter Fatura",
|
|
18672
19401
|
"financial.invoice.new": "Nova",
|
|
18673
|
-
"financial.invoice.newManual": "Cadastrar
|
|
19402
|
+
"financial.invoice.newManual": "Cadastrar",
|
|
18674
19403
|
"financial.invoiceForm.createContact": "Cadastrar",
|
|
18675
19404
|
"financial.invoiceForm.alreadyPaid": "J\xE1 foi pago",
|
|
18676
19405
|
"financial.invoiceForm.paidWith": "Forma de pagamento",
|
|
@@ -18682,6 +19411,9 @@ var ptBR = {
|
|
|
18682
19411
|
"financial.invoiceForm.paidNeedsMethod": "Diga a forma de pagamento e a conta",
|
|
18683
19412
|
"financial.invoiceForm.willBePaid": "Esta parcela entra como paga",
|
|
18684
19413
|
"financial.invoiceForm.clear": "Limpar",
|
|
19414
|
+
"financial.invoiceForm.manageAccounts": "Gerenciar contas",
|
|
19415
|
+
"financial.invoiceForm.manageChart": "Gerenciar plano de contas",
|
|
19416
|
+
"financial.invoiceForm.manageCostCenters": "Gerenciar centros de custo",
|
|
18685
19417
|
"financial.invoice.newManualHint": "Preencher os campos da conta",
|
|
18686
19418
|
"financial.invoice.noDetails": "Nenhum detalhe adicional registrado",
|
|
18687
19419
|
"financial.invoice.unlinkedPayments": "Pagamentos sem parcela vinculada",
|
|
@@ -18730,6 +19462,13 @@ var ptBR = {
|
|
|
18730
19462
|
"financial.itemType.product": "Produto",
|
|
18731
19463
|
"financial.itemType.other": "Outro",
|
|
18732
19464
|
"financial.itemType.gift_card": "Gift Card",
|
|
19465
|
+
"financial.giftCardLine.label": "C\xF3digo do gift card",
|
|
19466
|
+
"financial.giftCardLine.placeholder": "0000 0000 0000 0000",
|
|
19467
|
+
"financial.giftCardLine.hint": "Escaneie ou digite o c\xF3digo completo do cart\xE3o.",
|
|
19468
|
+
"financial.giftCardLine.notFound": "Gift card n\xE3o encontrado",
|
|
19469
|
+
"financial.giftCardLine.revoked": "Gift card cancelado \u2014 n\xE3o pode ser vendido",
|
|
19470
|
+
"financial.giftCardLine.expired": "Gift card expirado",
|
|
19471
|
+
"financial.giftCardLine.alreadySold": "Este gift card j\xE1 foi vendido",
|
|
18733
19472
|
"financial.invoiceForm.cancel": "Cancelar",
|
|
18734
19473
|
"financial.invoiceForm.close": "Fechar",
|
|
18735
19474
|
"financial.invoiceForm.creating": "Criando nova fatura",
|
|
@@ -18936,13 +19675,41 @@ var ptBR = {
|
|
|
18936
19675
|
"financial.reconciliation.reconciled": "Conciliado",
|
|
18937
19676
|
"financial.reconciliation.undo": "Desfazer",
|
|
18938
19677
|
"financial.reconciliation.flowTitle": "Conciliar movimenta\xE7\xE3o",
|
|
18939
|
-
"financial.reconciliation.stepClassify": "
|
|
19678
|
+
"financial.reconciliation.stepClassify": "O que deseja fazer",
|
|
18940
19679
|
"financial.reconciliation.stepItems": "Os itens",
|
|
18941
19680
|
"financial.reconciliation.stepDetails": "Os detalhes",
|
|
18942
19681
|
"financial.reconciliation.stepConfirm": "Confirmar",
|
|
18943
19682
|
"financial.reconciliation.confirmAction": "Confirmar",
|
|
18944
19683
|
"financial.reconciliation.howToClassify": "Como voc\xEA quer classificar esta transa\xE7\xE3o?",
|
|
18945
19684
|
"financial.reconciliation.pickIntent": "Escolha o que esta transa\xE7\xE3o \xE9",
|
|
19685
|
+
"financial.reconciliation.foundTitle": "Contas encontradas",
|
|
19686
|
+
"financial.reconciliation.searchWiden": "Ampliar busca (\xB115 dias, qualquer valor)",
|
|
19687
|
+
"financial.reconciliation.stepSelectBill": "Selecione a conta",
|
|
19688
|
+
"financial.reconciliation.stepAdjust": "Ajuste",
|
|
19689
|
+
"financial.reconciliation.selectBillFor": "Extrato de {{date}} \xB7 {{description}} \xB7",
|
|
19690
|
+
"financial.reconciliation.statusOverdue": "Vencido",
|
|
19691
|
+
"financial.reconciliation.colDue": "Venc.",
|
|
19692
|
+
"financial.reconciliation.colCounterparty": "Contraparte",
|
|
19693
|
+
"financial.reconciliation.colDescription": "N\xFAmero / Descri\xE7\xE3o",
|
|
19694
|
+
"financial.reconciliation.colInstallment": "Parcela",
|
|
19695
|
+
"financial.reconciliation.colToReconcile": "Valor a conciliar",
|
|
19696
|
+
"financial.reconciliation.colDelta": "\u0394 vs extrato",
|
|
19697
|
+
"financial.reconciliation.searchStatusAllShort": "Todos",
|
|
19698
|
+
"financial.reconciliation.searchPendingHint": "A lista ainda mostra o recorte anterior. Clique em Filtrar para aplicar.",
|
|
19699
|
+
"financial.reconciliation.searchTitle": "Buscar conta",
|
|
19700
|
+
"financial.reconciliation.searchDueFrom": "Vencimento de",
|
|
19701
|
+
"financial.reconciliation.searchDueTo": "at\xE9",
|
|
19702
|
+
"financial.reconciliation.searchStatus": "Situa\xE7\xE3o",
|
|
19703
|
+
"financial.reconciliation.searchStatusAll": "Todos (n\xE3o conciliados)",
|
|
19704
|
+
"financial.reconciliation.searchStatusOpen": "Em aberto",
|
|
19705
|
+
"financial.reconciliation.searchStatusPaid": "Pagos",
|
|
19706
|
+
"financial.reconciliation.searchText": "Buscar (contraparte, n\xFAmero, descri\xE7\xE3o)",
|
|
19707
|
+
"financial.reconciliation.searchTextPlaceholder": "Digite e pressione Enter",
|
|
19708
|
+
"financial.reconciliation.searchAmountMin": "Valor m\xEDn.",
|
|
19709
|
+
"financial.reconciliation.searchAmountMax": "Valor m\xE1x.",
|
|
19710
|
+
"financial.reconciliation.searchAction": "Buscar",
|
|
19711
|
+
"financial.reconciliation.searchEmpty": "Nenhuma conta com esses filtros.",
|
|
19712
|
+
"financial.reconciliation.searchMore": "Carregar mais",
|
|
18946
19713
|
"financial.reconciliation.pickCandidate": "Escolha o lan\xE7amento a vincular",
|
|
18947
19714
|
"financial.reconciliation.needDescription": "Descreva o lan\xE7amento",
|
|
18948
19715
|
"financial.reconciliation.intentLink": "Vincular a um lan\xE7amento",
|
|
@@ -18978,6 +19745,14 @@ var ptBR = {
|
|
|
18978
19745
|
"financial.reconciliation.linkOnlyExplain": "Esta conta j\xE1 foi paga no sistema. Confirmar apenas amarra a linha do extrato a ela \u2014 sem criar um segundo pagamento e sem mexer no valor.",
|
|
18979
19746
|
"financial.reconciliation.noAdjustmentNeeded": "O valor bate com a conta. Nada a ajustar.",
|
|
18980
19747
|
"financial.reconciliation.adjustmentNeeded": "O banco tirou {{gap}} {{side}} do que a conta pedia. Diga o que \xE9 essa diferen\xE7a.",
|
|
19748
|
+
"financial.reconciliation.adjustmentNeededIn": "Entrou {{gap}} {{side}} do que a conta cobrava. Diga o que \xE9 essa diferen\xE7a.",
|
|
19749
|
+
"financial.reconciliation.adjustmentNeedsAccount": "Escolha o plano de contas de cada ajuste.",
|
|
19750
|
+
"financial.reconciliation.adjustmentType": "Tipo",
|
|
19751
|
+
"financial.reconciliation.adjustmentDescriptionPlaceholder": "Ex.: juros por atraso",
|
|
19752
|
+
"financial.reconciliation.adjustmentDefaultInterest": "Juros por atraso",
|
|
19753
|
+
"financial.reconciliation.adjustmentDefaultFine": "Multa por atraso",
|
|
19754
|
+
"financial.reconciliation.adjustmentDefaultDiscountOut": "Desconto obtido",
|
|
19755
|
+
"financial.reconciliation.adjustmentDefaultDiscountIn": "Desconto concedido",
|
|
18981
19756
|
"financial.reconciliation.adjustmentMore": "a mais",
|
|
18982
19757
|
"financial.reconciliation.adjustmentLess": "a menos",
|
|
18983
19758
|
"financial.reconciliation.interest": "Juros",
|
|
@@ -19412,6 +20187,10 @@ var ptBR = {
|
|
|
19412
20187
|
"financial.quickTx.receiptRemove": "Remover recibo",
|
|
19413
20188
|
"financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
|
|
19414
20189
|
"financial.quickTx.sendReceipt": "Enviar recibo",
|
|
20190
|
+
"financial.quickTx.digitizeExpense": "Digitalizar despesa",
|
|
20191
|
+
"financial.quickTx.digitizeReceipt": "Digitalizar comprovante",
|
|
20192
|
+
"financial.invoice.importPayroll": "Importar folha",
|
|
20193
|
+
"financial.invoice.importPayrollSoon": "Em breve",
|
|
19415
20194
|
"financial.receipt.ask": "Mande a foto do recibo\u2026",
|
|
19416
20195
|
"financial.receipt.attachRead": "Anexar o recibo que o assistente leu",
|
|
19417
20196
|
"financial.receipt.fromReceipt": "Do recibo",
|
|
@@ -19577,14 +20356,14 @@ function CommissionRuleDialog({
|
|
|
19577
20356
|
onClose,
|
|
19578
20357
|
onSaved
|
|
19579
20358
|
}) {
|
|
19580
|
-
const [name, setName] =
|
|
19581
|
-
const [description, setDescription] =
|
|
19582
|
-
const [priority, setPriority] =
|
|
19583
|
-
const [isActive, setIsActive] =
|
|
19584
|
-
const [isDefault, setIsDefault] =
|
|
19585
|
-
const [saving, setSaving] =
|
|
19586
|
-
const [error, setError] =
|
|
19587
|
-
const initial =
|
|
20359
|
+
const [name, setName] = React25__default.useState(rule?.name ?? "");
|
|
20360
|
+
const [description, setDescription] = React25__default.useState(rule?.description ?? "");
|
|
20361
|
+
const [priority, setPriority] = React25__default.useState(String(rule?.priority ?? 1));
|
|
20362
|
+
const [isActive, setIsActive] = React25__default.useState(rule?.isActive ?? true);
|
|
20363
|
+
const [isDefault, setIsDefault] = React25__default.useState(rule?.isDefault ?? !hasDefault);
|
|
20364
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
20365
|
+
const [error, setError] = React25__default.useState(null);
|
|
20366
|
+
const initial = React25__default.useMemo(() => {
|
|
19588
20367
|
const get = (t2, key = "ids") => rule?.criteria.find((c) => c.criteriaType === t2)?.criteriaValue?.[key] ?? [];
|
|
19589
20368
|
return {
|
|
19590
20369
|
services: get("service"),
|
|
@@ -19595,12 +20374,12 @@ function CommissionRuleDialog({
|
|
|
19595
20374
|
ranges: get("time_range", "ranges")
|
|
19596
20375
|
};
|
|
19597
20376
|
}, [rule]);
|
|
19598
|
-
const [services, setServices] =
|
|
19599
|
-
const [categories, setCategories] =
|
|
19600
|
-
const [professionals, setProfessionals] =
|
|
19601
|
-
const [units, setUnits] =
|
|
19602
|
-
const [weekdays, setWeekdays] =
|
|
19603
|
-
const [ranges, setRanges] =
|
|
20377
|
+
const [services, setServices] = React25__default.useState(initial.services);
|
|
20378
|
+
const [categories, setCategories] = React25__default.useState(initial.categories);
|
|
20379
|
+
const [professionals, setProfessionals] = React25__default.useState(initial.professionals);
|
|
20380
|
+
const [units, setUnits] = React25__default.useState(initial.units);
|
|
20381
|
+
const [weekdays, setWeekdays] = React25__default.useState(initial.weekdays);
|
|
20382
|
+
const [ranges, setRanges] = React25__default.useState(initial.ranges);
|
|
19604
20383
|
const criteriaCount = [services, categories, professionals, units].filter((a) => a.length > 0).length + (weekdays.length ? 1 : 0) + (ranges.length ? 1 : 0);
|
|
19605
20384
|
async function handleSave() {
|
|
19606
20385
|
if (!name.trim()) {
|
|
@@ -19843,11 +20622,11 @@ function blankFunction(order) {
|
|
|
19843
20622
|
};
|
|
19844
20623
|
}
|
|
19845
20624
|
function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
|
|
19846
|
-
const [functions, setFunctions] =
|
|
20625
|
+
const [functions, setFunctions] = React25__default.useState(
|
|
19847
20626
|
rule.functions.length ? rule.functions : [blankFunction(0)]
|
|
19848
20627
|
);
|
|
19849
|
-
const [saving, setSaving] =
|
|
19850
|
-
const [error, setError] =
|
|
20628
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
20629
|
+
const [error, setError] = React25__default.useState(null);
|
|
19851
20630
|
function patch(i, changes) {
|
|
19852
20631
|
setFunctions((fs) => fs.map((f, j) => j === i ? { ...f, ...changes } : f));
|
|
19853
20632
|
}
|
|
@@ -20131,19 +20910,19 @@ function functionChip(fn) {
|
|
|
20131
20910
|
return fn.valueType === "percentage" ? `${who}: ${fn.valueAmount}%` : `${who}: R$ ${fn.valueAmount.toFixed(2)}`;
|
|
20132
20911
|
}
|
|
20133
20912
|
function CommissionRulesView({ provider }) {
|
|
20134
|
-
const [rules, setRules] =
|
|
20135
|
-
const [catalogs, setCatalogs] =
|
|
20913
|
+
const [rules, setRules] = React25__default.useState(null);
|
|
20914
|
+
const [catalogs, setCatalogs] = React25__default.useState({
|
|
20136
20915
|
services: [],
|
|
20137
20916
|
categories: [],
|
|
20138
20917
|
professionals: [],
|
|
20139
20918
|
units: []
|
|
20140
20919
|
});
|
|
20141
|
-
const [error, setError] =
|
|
20142
|
-
const [funnel, setFunnel] =
|
|
20143
|
-
const [editing, setEditing] =
|
|
20144
|
-
const [functionsFor, setFunctionsFor] =
|
|
20145
|
-
const [deleting, setDeleting] =
|
|
20146
|
-
const load =
|
|
20920
|
+
const [error, setError] = React25__default.useState(null);
|
|
20921
|
+
const [funnel, setFunnel] = React25__default.useState({});
|
|
20922
|
+
const [editing, setEditing] = React25__default.useState(null);
|
|
20923
|
+
const [functionsFor, setFunctionsFor] = React25__default.useState(null);
|
|
20924
|
+
const [deleting, setDeleting] = React25__default.useState(null);
|
|
20925
|
+
const load = React25__default.useCallback(async () => {
|
|
20147
20926
|
try {
|
|
20148
20927
|
setError(null);
|
|
20149
20928
|
const [r, c] = await Promise.all([
|
|
@@ -20157,17 +20936,17 @@ function CommissionRulesView({ provider }) {
|
|
|
20157
20936
|
setRules([]);
|
|
20158
20937
|
}
|
|
20159
20938
|
}, [provider]);
|
|
20160
|
-
|
|
20939
|
+
React25__default.useEffect(() => {
|
|
20161
20940
|
void load();
|
|
20162
20941
|
}, [load]);
|
|
20163
20942
|
const funnelActive = Object.values(funnel).some(Boolean);
|
|
20164
|
-
const visible =
|
|
20943
|
+
const visible = React25__default.useMemo(() => {
|
|
20165
20944
|
if (!rules) return [];
|
|
20166
20945
|
const ranked = rankRules(rules);
|
|
20167
20946
|
if (!funnelActive) return ranked;
|
|
20168
20947
|
return ranked.filter((r) => r.isDefault || ruleMatches(r, funnel, catalogs));
|
|
20169
20948
|
}, [rules, funnel, funnelActive, catalogs]);
|
|
20170
|
-
const winner =
|
|
20949
|
+
const winner = React25__default.useMemo(() => {
|
|
20171
20950
|
if (!funnelActive) return null;
|
|
20172
20951
|
return visible.find((r) => r.isActive && !r.isDefault) ?? visible.find((r) => r.isActive && r.isDefault) ?? null;
|
|
20173
20952
|
}, [visible, funnelActive]);
|
|
@@ -20182,7 +20961,7 @@ function CommissionRulesView({ provider }) {
|
|
|
20182
20961
|
await provider.duplicateCommissionRule?.(rule.id);
|
|
20183
20962
|
await load();
|
|
20184
20963
|
}
|
|
20185
|
-
const headerActions =
|
|
20964
|
+
const headerActions = React25__default.useMemo(() => [{
|
|
20186
20965
|
id: "new-rule",
|
|
20187
20966
|
label: "Adicionar regra",
|
|
20188
20967
|
icon: Plus,
|
|
@@ -20964,7 +21743,7 @@ function PersonStatementWidget({ person, provider, config }) {
|
|
|
20964
21743
|
] });
|
|
20965
21744
|
}
|
|
20966
21745
|
function SupplierFinancialSettings({ person, provider }) {
|
|
20967
|
-
const initial =
|
|
21746
|
+
const initial = React25__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
|
|
20968
21747
|
const [accounts, setAccounts] = useState([]);
|
|
20969
21748
|
const [costCenters, setCostCenters] = useState([]);
|
|
20970
21749
|
const [defaultAccountId, setDefaultAccountId] = useState(initial.defaultAccountId ?? "");
|
|
@@ -44176,6 +44955,182 @@ CREATE TRIGGER payee_accounts_one_default
|
|
|
44176
44955
|
BEFORE INSERT OR UPDATE OF is_default ON public.payee_accounts
|
|
44177
44956
|
FOR EACH ROW EXECUTE FUNCTION public.trg_payee_accounts_one_default();
|
|
44178
44957
|
`;
|
|
44958
|
+
var MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA = `-- ---------------------------------------------------------------------------
|
|
44959
|
+
-- 116 \u2014 um estorno n\xE3o se estorna.
|
|
44960
|
+
--
|
|
44961
|
+
-- \`financial_reverse_movement\` aceitava qualquer \`payment\`, inclusive a linha
|
|
44962
|
+
-- que ela mesma lan\xE7a como contrapartida. A tela mirava \xABo \xFAltimo pagamento da
|
|
44963
|
+
-- parcela\xBB, que depois do primeiro estorno \xC9 o estorno: cada clique estornava o
|
|
44964
|
+
-- estorno anterior, o dinheiro ia e voltava, e o pagamento vivo continuava l\xE1 \u2014
|
|
44965
|
+
-- a conta seguia \xABpaga\xBB para sempre. Agora o banco recusa com uma mensagem que
|
|
44966
|
+
-- a tela mostra.
|
|
44967
|
+
--
|
|
44968
|
+
-- Idempotente: s\xF3 rep\xF5e a fun\xE7\xE3o.
|
|
44969
|
+
-- ---------------------------------------------------------------------------
|
|
44970
|
+
|
|
44971
|
+
CREATE OR REPLACE FUNCTION public.financial_reverse_movement(p_movement_id uuid, p_reason text, p_idempotency_key text DEFAULT NULL::text) RETURNS jsonb
|
|
44972
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
44973
|
+
SET search_path TO ''
|
|
44974
|
+
AS $$
|
|
44975
|
+
DECLARE
|
|
44976
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
44977
|
+
v_m public.plg_financial_movements%ROWTYPE;
|
|
44978
|
+
v_rev uuid;
|
|
44979
|
+
BEGIN
|
|
44980
|
+
IF nullif(btrim(coalesce(p_reason, '')), '') IS NULL THEN
|
|
44981
|
+
RAISE EXCEPTION 'financial_reverse_movement: a reason is required' USING ERRCODE = '22023';
|
|
44982
|
+
END IF;
|
|
44983
|
+
SELECT * INTO v_m FROM public.plg_financial_movements WHERE id = p_movement_id AND tenant_id = v_tenant;
|
|
44984
|
+
IF NOT FOUND THEN
|
|
44985
|
+
RAISE EXCEPTION 'financial_reverse_movement: movement % not found', p_movement_id USING ERRCODE = 'P0002';
|
|
44986
|
+
END IF;
|
|
44987
|
+
PERFORM public.plg_financial_require_permission('financial.edit', v_m.unit_id);
|
|
44988
|
+
IF v_m.movement_kind NOT IN ('payment', 'transfer') OR v_m.debit_account_id IS NULL THEN
|
|
44989
|
+
RAISE EXCEPTION 'financial_reverse_movement: % is not a money event', p_movement_id USING ERRCODE = '55000';
|
|
44990
|
+
END IF;
|
|
44991
|
+
IF v_m.reverses_id IS NOT NULL THEN
|
|
44992
|
+
RAISE EXCEPTION 'Este lan\xE7amento j\xE1 \xE9 um estorno e n\xE3o pode ser estornado.' USING ERRCODE = '55000';
|
|
44993
|
+
END IF;
|
|
44994
|
+
IF v_m.reversed_by_id IS NOT NULL THEN
|
|
44995
|
+
RETURN jsonb_build_object('reversal_id', v_m.reversed_by_id, 'movement_id', v_m.id, 'replayed', true);
|
|
44996
|
+
END IF;
|
|
44997
|
+
IF p_idempotency_key IS NOT NULL THEN
|
|
44998
|
+
PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);
|
|
44999
|
+
SELECT m.id INTO v_rev FROM public.plg_financial_movements m WHERE m.tenant_id = v_tenant AND m.idempotency_key = p_idempotency_key;
|
|
45000
|
+
IF v_rev IS NOT NULL THEN
|
|
45001
|
+
RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', true);
|
|
45002
|
+
END IF;
|
|
45003
|
+
END IF;
|
|
45004
|
+
|
|
45005
|
+
INSERT INTO public.plg_financial_movements
|
|
45006
|
+
(tenant_id, unit_id, invoice_id, financial_invoice_id, financial_invoice_installment_id, direction, movement_kind, amount, paid_amount, status,
|
|
45007
|
+
due_date, payment_date, installment_number, method, cash_session_id, bank_account_id, reason, notes, metadata,
|
|
45008
|
+
idempotency_key, created_by, reverses_id, debit_account_id, credit_account_id)
|
|
45009
|
+
VALUES
|
|
45010
|
+
(v_m.tenant_id, v_m.unit_id, v_m.invoice_id, v_m.financial_invoice_id, v_m.financial_invoice_installment_id,
|
|
45011
|
+
CASE WHEN v_m.direction = 'credit' THEN 'debit' ELSE 'credit' END, v_m.movement_kind, v_m.amount, v_m.amount, 'paid',
|
|
45012
|
+
current_date, current_date, v_m.installment_number, v_m.method, v_m.cash_session_id, v_m.bank_account_id, btrim(p_reason),
|
|
45013
|
+
'Estorno de ' || v_m.id::text,
|
|
45014
|
+
jsonb_build_object('source', 'financial_reverse_movement', 'reversal_of', v_m.id, 'reason', btrim(p_reason)),
|
|
45015
|
+
p_idempotency_key, auth.uid(), v_m.id, v_m.credit_account_id, v_m.debit_account_id)
|
|
45016
|
+
RETURNING id INTO v_rev;
|
|
45017
|
+
|
|
45018
|
+
UPDATE public.plg_financial_movements SET reversed_by_id = v_rev, reason = coalesce(reason, btrim(p_reason)), updated_at = now()
|
|
45019
|
+
WHERE id = v_m.id;
|
|
45020
|
+
IF v_m.financial_invoice_installment_id IS NOT NULL THEN PERFORM public.plg_financial_sync_mirror(v_m.financial_invoice_installment_id); END IF;
|
|
45021
|
+
|
|
45022
|
+
PERFORM public.plg_financial_audit(v_tenant, 'financial.movement.reverse', 'plg_financial_movements', v_m.id::text,
|
|
45023
|
+
jsonb_build_object('reversal_id', v_rev, 'amount', v_m.amount, 'reason', btrim(p_reason), 'idempotency_key', p_idempotency_key));
|
|
45024
|
+
RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', false);
|
|
45025
|
+
END $$;
|
|
45026
|
+
|
|
45027
|
+
`;
|
|
45028
|
+
var MIGRATION_117_COPYING_LINES_IS_AN_INTERNAL_STEP = `-- ---------------------------------------------------------------------------
|
|
45029
|
+
-- 117 \u2014 copying lines onto an invoice is an internal step, not a door.
|
|
45030
|
+
--
|
|
45031
|
+
-- Both copiers are SECURITY DEFINER with no caller check and were executable by
|
|
45032
|
+
-- PUBLIC (so anon too): anyone holding an invoice id could write public.items
|
|
45033
|
+
-- into it. Every real caller is another SECURITY DEFINER function owned by
|
|
45034
|
+
-- postgres (fn_invoice_from_order_internal, order_raise_receivable,
|
|
45035
|
+
-- financial_recurring_run, financial_consolidate_occurrence), which keeps its
|
|
45036
|
+
-- right to execute as owner. So the fix is the grant, not the body.
|
|
45037
|
+
--
|
|
45038
|
+
-- Idempotent: grants only.
|
|
45039
|
+
-- ---------------------------------------------------------------------------
|
|
45040
|
+
|
|
45041
|
+
REVOKE ALL ON FUNCTION public.plg_financial_copy_bill_items(uuid, uuid, jsonb) FROM PUBLIC, anon, authenticated;
|
|
45042
|
+
GRANT EXECUTE ON FUNCTION public.plg_financial_copy_bill_items(uuid, uuid, jsonb) TO service_role;
|
|
45043
|
+
|
|
45044
|
+
REVOKE ALL ON FUNCTION public.plg_financial_copy_order_lines_to_invoice(uuid, uuid) FROM PUBLIC, anon, authenticated;
|
|
45045
|
+
GRANT EXECUTE ON FUNCTION public.plg_financial_copy_order_lines_to_invoice(uuid, uuid) TO service_role;
|
|
45046
|
+
`;
|
|
45047
|
+
var MIGRATION_118_GIFT_CARD_FOUND_BY_EXACT_CODE = `-- 118_gift_card_found_by_exact_code.sql
|
|
45048
|
+
--
|
|
45049
|
+
-- Selling a gift card on a receivable takes the code typed at the counter and
|
|
45050
|
+
-- must match exactly one card. financial_gift_cards (027) matches by substring,
|
|
45051
|
+
-- so a partial code answered with real codes of other cards. This lookup
|
|
45052
|
+
-- returns the one card whose code equals the input (spaces, dots, dashes and
|
|
45053
|
+
-- case ignored), or nothing.
|
|
45054
|
+
--
|
|
45055
|
+
-- The shop table is optional and applied after this chain, so it is resolved
|
|
45056
|
+
-- at run time, as in 027.
|
|
45057
|
+
|
|
45058
|
+
CREATE OR REPLACE FUNCTION public.financial_gift_card_by_code(p_code text)
|
|
45059
|
+
RETURNS TABLE (
|
|
45060
|
+
id uuid,
|
|
45061
|
+
code text,
|
|
45062
|
+
face_value numeric,
|
|
45063
|
+
balance numeric,
|
|
45064
|
+
balance_known boolean,
|
|
45065
|
+
state text,
|
|
45066
|
+
currency text,
|
|
45067
|
+
issued_at timestamptz,
|
|
45068
|
+
expires_at timestamptz,
|
|
45069
|
+
activated_at timestamptz,
|
|
45070
|
+
note text,
|
|
45071
|
+
customer_email text
|
|
45072
|
+
)
|
|
45073
|
+
LANGUAGE plpgsql
|
|
45074
|
+
STABLE
|
|
45075
|
+
SECURITY DEFINER
|
|
45076
|
+
SET search_path TO ''
|
|
45077
|
+
AS $$
|
|
45078
|
+
DECLARE
|
|
45079
|
+
v_tenant uuid := app.current_tenant_id();
|
|
45080
|
+
v_code text := upper(regexp_replace(coalesce(p_code, ''), '[\\s.\\-]', '', 'g'));
|
|
45081
|
+
v_activated text;
|
|
45082
|
+
BEGIN
|
|
45083
|
+
IF v_tenant IS NULL THEN
|
|
45084
|
+
RAISE EXCEPTION 'financial_gift_card_by_code: sem tenant na sess\xE3o' USING ERRCODE = '42501';
|
|
45085
|
+
END IF;
|
|
45086
|
+
-- Whoever can raise a receivable can sell a card on it.
|
|
45087
|
+
IF NOT app.has_permission('financial.create', NULL) THEN
|
|
45088
|
+
RAISE EXCEPTION 'financial_gift_card_by_code: exige financial.create' USING ERRCODE = '42501';
|
|
45089
|
+
END IF;
|
|
45090
|
+
IF v_code = '' OR to_regclass('public.plg_shop_gift_cards') IS NULL THEN
|
|
45091
|
+
RETURN;
|
|
45092
|
+
END IF;
|
|
45093
|
+
|
|
45094
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns c
|
|
45095
|
+
WHERE c.table_schema = 'public' AND c.table_name = 'plg_shop_gift_cards'
|
|
45096
|
+
AND c.column_name = 'activated_at') THEN
|
|
45097
|
+
v_activated := 'g.activated_at';
|
|
45098
|
+
ELSE
|
|
45099
|
+
v_activated :=
|
|
45100
|
+
'CASE WHEN g.status = ''disabled'' AND g.balance = 0 THEN NULL'
|
|
45101
|
+
|| ' WHEN g.updated_at > g.created_at THEN g.updated_at'
|
|
45102
|
+
|| ' ELSE g.created_at END';
|
|
45103
|
+
END IF;
|
|
45104
|
+
|
|
45105
|
+
-- Same state vocabulary as financial_gift_cards (027).
|
|
45106
|
+
RETURN QUERY EXECUTE
|
|
45107
|
+
'SELECT r.id, r.code, r.initial_balance,'
|
|
45108
|
+
|| 'CASE WHEN r.never THEN NULL ELSE r.balance END,'
|
|
45109
|
+
|| '(NOT r.never),'
|
|
45110
|
+
|| 'CASE WHEN r.never THEN ''issued'''
|
|
45111
|
+
|| ' WHEN r.balance = 0 OR r.status = ''redeemed'' THEN ''redeemed'''
|
|
45112
|
+
|| ' WHEN r.expires_at IS NOT NULL AND r.expires_at < now() THEN ''expired'''
|
|
45113
|
+
|| ' WHEN r.status = ''disabled'' THEN ''revoked'''
|
|
45114
|
+
|| ' ELSE ''active'' END,'
|
|
45115
|
+
|| 'r.currency, r.created_at, r.expires_at, r.activated_at, r.note, r.customer_email '
|
|
45116
|
+
|| 'FROM (SELECT g.id, g.code, g.initial_balance, g.balance, g.currency, g.status,'
|
|
45117
|
+
|| 'g.created_at, g.expires_at, g.note, g.customer_email,'
|
|
45118
|
+
|| '(g.status = ''disabled'' AND g.balance = 0) AS never,'
|
|
45119
|
+
|| v_activated || ' AS activated_at '
|
|
45120
|
+
|| 'FROM public.plg_shop_gift_cards g '
|
|
45121
|
+
|| 'WHERE g.tenant_id = $1 AND upper(g.code) = $2) r '
|
|
45122
|
+
|| 'LIMIT 1'
|
|
45123
|
+
USING v_tenant, v_code;
|
|
45124
|
+
END $$;
|
|
45125
|
+
|
|
45126
|
+
COMMENT ON FUNCTION public.financial_gift_card_by_code(text) IS
|
|
45127
|
+
'The one gift card whose code equals the input (spaces/case ignored), for selling it on a receivable. Never matches part of a code (118).';
|
|
45128
|
+
|
|
45129
|
+
REVOKE ALL ON FUNCTION public.financial_gift_card_by_code(text) FROM PUBLIC;
|
|
45130
|
+
REVOKE ALL ON FUNCTION public.financial_gift_card_by_code(text) FROM anon;
|
|
45131
|
+
GRANT EXECUTE ON FUNCTION public.financial_gift_card_by_code(text) TO authenticated;
|
|
45132
|
+
GRANT EXECUTE ON FUNCTION public.financial_gift_card_by_code(text) TO service_role;
|
|
45133
|
+
`;
|
|
44179
45134
|
var MIGRATIONS = [
|
|
44180
45135
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
44181
45136
|
{ id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
|
|
@@ -44292,7 +45247,10 @@ var MIGRATIONS = [
|
|
|
44292
45247
|
{ id: "112_a_linha_do_extrato_nao_e_um_lancamento_ainda", sql: MIGRATION_112_A_LINHA_DO_EXTRATO_NAO_E_UM_LANCAMENTO_AINDA },
|
|
44293
45248
|
{ id: "113_so_a_conta_integrada_aparece_na_conciliacao", sql: MIGRATION_113_SO_A_CONTA_INTEGRADA_APARECE_NA_CONCILIACAO },
|
|
44294
45249
|
{ id: "114_a_parcela_diz_por_qual_conta_ela_foi_paga", sql: MIGRATION_114_A_PARCELA_DIZ_POR_QUAL_CONTA_ELA_FOI_PAGA },
|
|
44295
|
-
{ id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE }
|
|
45250
|
+
{ id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE },
|
|
45251
|
+
{ id: "116_um_estorno_nao_se_estorna", sql: MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA },
|
|
45252
|
+
{ id: "117_copying_lines_is_an_internal_step", sql: MIGRATION_117_COPYING_LINES_IS_AN_INTERNAL_STEP },
|
|
45253
|
+
{ id: "118_gift_card_found_by_exact_code", sql: MIGRATION_118_GIFT_CARD_FOUND_BY_EXACT_CODE }
|
|
44296
45254
|
];
|
|
44297
45255
|
var STANDALONE_CONFIG = resolveConfig();
|
|
44298
45256
|
registerTranslations(financialLocales);
|
|
@@ -44302,8 +45260,8 @@ function ReceivePaymentModal({
|
|
|
44302
45260
|
onClose,
|
|
44303
45261
|
onPaid
|
|
44304
45262
|
}) {
|
|
44305
|
-
const provider =
|
|
44306
|
-
const store6 =
|
|
45263
|
+
const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
|
|
45264
|
+
const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
|
|
44307
45265
|
return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
|
|
44308
45266
|
PaymentModal,
|
|
44309
45267
|
{
|
|
@@ -44321,7 +45279,7 @@ function createFinancialSettingsPlugin(options) {
|
|
|
44321
45279
|
...financialRegistries.filter((r) => RECEIVING_REGISTRIES.includes(r.id)),
|
|
44322
45280
|
...options?.extraRegistries ?? []
|
|
44323
45281
|
];
|
|
44324
|
-
const SettingsTab = () =>
|
|
45282
|
+
const SettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
|
|
44325
45283
|
title: "Pagamentos",
|
|
44326
45284
|
registries,
|
|
44327
45285
|
hostPluginId: "financial",
|
|
@@ -44378,27 +45336,27 @@ function ClientAccountPanel({
|
|
|
44378
45336
|
currency = BRL
|
|
44379
45337
|
}) {
|
|
44380
45338
|
const provider = useFinancialProvider();
|
|
44381
|
-
const [account, setAccount] =
|
|
44382
|
-
const [busy, setBusy] =
|
|
44383
|
-
const [viewInvoiceId, setViewInvoiceId] =
|
|
44384
|
-
const [comprando, setComprando] =
|
|
44385
|
-
const [buscaServico, setBuscaServico] =
|
|
44386
|
-
const [catalogo, setCatalogo] =
|
|
44387
|
-
const [escolhidos, setEscolhidos] =
|
|
44388
|
-
const [comprandoBusy, setComprandoBusy] =
|
|
44389
|
-
const [erroCompra, setErroCompra] =
|
|
44390
|
-
const load =
|
|
45339
|
+
const [account, setAccount] = React25__default.useState(null);
|
|
45340
|
+
const [busy, setBusy] = React25__default.useState(false);
|
|
45341
|
+
const [viewInvoiceId, setViewInvoiceId] = React25__default.useState(null);
|
|
45342
|
+
const [comprando, setComprando] = React25__default.useState(false);
|
|
45343
|
+
const [buscaServico, setBuscaServico] = React25__default.useState("");
|
|
45344
|
+
const [catalogo, setCatalogo] = React25__default.useState([]);
|
|
45345
|
+
const [escolhidos, setEscolhidos] = React25__default.useState([]);
|
|
45346
|
+
const [comprandoBusy, setComprandoBusy] = React25__default.useState(false);
|
|
45347
|
+
const [erroCompra, setErroCompra] = React25__default.useState(null);
|
|
45348
|
+
const load = React25__default.useCallback(async () => {
|
|
44391
45349
|
if (!personId) return;
|
|
44392
45350
|
setAccount(await provider.getClientAccount(personId));
|
|
44393
45351
|
}, [personId, provider]);
|
|
44394
|
-
|
|
45352
|
+
React25__default.useEffect(() => {
|
|
44395
45353
|
void load();
|
|
44396
45354
|
}, [load]);
|
|
44397
45355
|
const viewInvoice = account?.invoices.find((i) => i.id === viewInvoiceId) ?? null;
|
|
44398
45356
|
const viewEditable = invoiceOpensForEditing(viewInvoice?.status);
|
|
44399
45357
|
const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
|
|
44400
45358
|
const hasForThis = forThis.length > 0;
|
|
44401
|
-
|
|
45359
|
+
React25__default.useEffect(() => {
|
|
44402
45360
|
if (!comprando) return;
|
|
44403
45361
|
let vivo = true;
|
|
44404
45362
|
const t2 = setTimeout(async () => {
|
|
@@ -44673,8 +45631,8 @@ function ClientAccountPanel({
|
|
|
44673
45631
|
] });
|
|
44674
45632
|
}
|
|
44675
45633
|
function ClientAccountInline(props) {
|
|
44676
|
-
const provider =
|
|
44677
|
-
const store6 =
|
|
45634
|
+
const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
|
|
45635
|
+
const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
|
|
44678
45636
|
return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(ClientAccountPanel, { ...props }) });
|
|
44679
45637
|
}
|
|
44680
45638
|
function ClientAccountModal({
|
|
@@ -44702,7 +45660,6 @@ function createSafeFinancialProvider() {
|
|
|
44702
45660
|
() => createMockFinancialProvider()
|
|
44703
45661
|
);
|
|
44704
45662
|
}
|
|
44705
|
-
var ACCOUNTS_ROUTE = "/registry/accounts";
|
|
44706
45663
|
var FINANCIAL_NAV_FEATURES = {
|
|
44707
45664
|
reconciliation: "fin_reconciliation",
|
|
44708
45665
|
cards: "fin_cards",
|
|
@@ -44723,11 +45680,11 @@ function createFinancialPlugin(options) {
|
|
|
44723
45680
|
const settingsRegistries = registries.filter((r) => r.id !== "bank-accounts");
|
|
44724
45681
|
const AccountsRegistryPage = createCrudPage(financialAccountEntity, { feature: "financial" });
|
|
44725
45682
|
AccountsRegistryPage.displayName = "FinancialAccountsRegistryPage";
|
|
44726
|
-
const PersonStatementBound = (props) =>
|
|
45683
|
+
const PersonStatementBound = (props) => React25__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
|
|
44727
45684
|
PersonStatementBound.displayName = "FinancialPersonStatement";
|
|
44728
|
-
const SupplierFinancialSettingsBound = (props) =>
|
|
45685
|
+
const SupplierFinancialSettingsBound = (props) => React25__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
|
|
44729
45686
|
SupplierFinancialSettingsBound.displayName = "FinancialSupplierSettings";
|
|
44730
|
-
const PageComponent = () =>
|
|
45687
|
+
const PageComponent = () => React25__default.createElement(FinancialPage, { config, provider, store: store6, registries });
|
|
44731
45688
|
const AgentMount = createFinancialAgentMount({ config, provider });
|
|
44732
45689
|
return {
|
|
44733
45690
|
id: "financial",
|
|
@@ -44785,7 +45742,7 @@ function createFinancialPlugin(options) {
|
|
|
44785
45742
|
// tem — lá é um ícone no cabeçalho, de qualquer tela.
|
|
44786
45743
|
headerControls: config.modules.cashRegisters ? [{
|
|
44787
45744
|
id: "financial.cash-session",
|
|
44788
|
-
component: (() =>
|
|
45745
|
+
component: (() => React25__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
|
|
44789
45746
|
order: 90,
|
|
44790
45747
|
permission: { feature: "financial", action: "read" }
|
|
44791
45748
|
}] : [],
|
|
@@ -45016,9 +45973,9 @@ function createFinancialPlugin(options) {
|
|
|
45016
45973
|
// lado, e a corrida de "Plugins" não é partida ao meio.
|
|
45017
45974
|
group: "Financeiro",
|
|
45018
45975
|
component: (() => {
|
|
45019
|
-
const FinancialSettingsTab = () =>
|
|
45976
|
+
const FinancialSettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
|
|
45020
45977
|
title: "Financial Settings",
|
|
45021
|
-
generalSettings:
|
|
45978
|
+
generalSettings: React25__default.createElement(FinancialGeneralSettings, { config }),
|
|
45022
45979
|
registries: settingsRegistries,
|
|
45023
45980
|
hostPluginId: "financial",
|
|
45024
45981
|
routeBase: "/settings/financial"
|
|
@@ -45043,7 +46000,7 @@ function createFinancialPlugin(options) {
|
|
|
45043
46000
|
icon: "HandCoins",
|
|
45044
46001
|
group: "Financeiro",
|
|
45045
46002
|
component: (() => {
|
|
45046
|
-
const CommissionRulesTab = () =>
|
|
46003
|
+
const CommissionRulesTab = () => React25__default.createElement(CommissionRulesView, { provider });
|
|
45047
46004
|
CommissionRulesTab.displayName = "CommissionRulesTab";
|
|
45048
46005
|
return CommissionRulesTab;
|
|
45049
46006
|
})(),
|