@fayz-ai/plugin-financial 0.19.1 → 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/InstallmentSource.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 +10 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.js +923 -282
- 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/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +2 -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/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/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as React25 from 'react';
|
|
2
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,
|
|
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, Hint, ModalFooter, PageTabs, Tooltip, TooltipTrigger, TooltipContent, Checkbox, Popover, PopoverTrigger, PopoverContent, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, SegmentedControl, RequiredFilter, FilterMenuRow, ModalFlow, Switch, WizardSteps, KpiCard, ChartWidget, CardHeader, CardTitle, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
|
|
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, 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,
|
|
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';
|
|
@@ -2500,11 +2500,13 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2500
2500
|
}, [currency, direction, t2]);
|
|
2501
2501
|
const bulkActions = useMemo(() => {
|
|
2502
2502
|
const actions = [
|
|
2503
|
-
...direction === "debit" ? [{ id: "cnab", label: t2("financial.invoice.generateCnab"), icon: FileText, onSelect: (rows) => setCnabTarget(rows.filter((row) => row.status !== "paid" && row.status !== "cancelled")) }] : [],
|
|
2504
2503
|
{ id: "print", label: t2("financial.invoice.printSelected"), icon: Printer, onSelect: printSelected },
|
|
2505
2504
|
{ id: "export", label: t2("financial.invoice.exportSelected"), icon: Download, onSelect: exportSelected }
|
|
2506
2505
|
];
|
|
2507
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
|
+
}
|
|
2508
2510
|
actions.push({
|
|
2509
2511
|
id: "cancel",
|
|
2510
2512
|
label: t2("financial.invoice.bulkCancel"),
|
|
@@ -2560,17 +2562,20 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2560
2562
|
value: unitFilter.options.find((u) => u.id === unitFilter.value)?.name ?? unitFilter.allLabel,
|
|
2561
2563
|
icon: Building2,
|
|
2562
2564
|
className: "h-9 w-full justify-start",
|
|
2563
|
-
align: "
|
|
2564
|
-
children: (close) => /* @__PURE__ */
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
unitFilter.
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
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
|
+
)
|
|
2574
2579
|
}
|
|
2575
2580
|
) : locations.length > 1 ? /* @__PURE__ */ jsx(
|
|
2576
2581
|
RequiredFilter,
|
|
@@ -2579,17 +2584,18 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2579
2584
|
value: locations.find((u) => u.id === unitId)?.name ?? t2("financial.invoice.filterAllUnits"),
|
|
2580
2585
|
icon: Building2,
|
|
2581
2586
|
className: "h-9 w-full justify-start",
|
|
2582
|
-
align: "
|
|
2583
|
-
children: (close) => /* @__PURE__ */
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
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
|
+
)
|
|
2593
2599
|
}
|
|
2594
2600
|
) : null;
|
|
2595
2601
|
const statusField = /* @__PURE__ */ jsx(
|
|
@@ -2874,6 +2880,40 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2874
2880
|
cnabTarget !== null ? /* @__PURE__ */ jsx(CnabRemittanceModal, { invoices: cnabTarget, bankAccounts, provider, onClose: () => setCnabTarget(null) }) : null
|
|
2875
2881
|
] });
|
|
2876
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
|
+
}
|
|
2877
2917
|
var PAYABLE_INSTRUCTIONS = [
|
|
2878
2918
|
'Uma "conta a pagar" \xE9 uma DESPESA com um FORNECEDOR \u2014 quem a empresa deve. Para registrar uma,',
|
|
2879
2919
|
"chame `createPayable`. NUNCA use `createRecord` em `financial:payment-methods` para isso: aquele",
|
|
@@ -4664,6 +4704,7 @@ function InstallmentSourceFields({
|
|
|
4664
4704
|
const t2 = useTranslation();
|
|
4665
4705
|
const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
|
|
4666
4706
|
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
4707
|
+
const can = usePermissionOptional();
|
|
4667
4708
|
const selectedType = paymentMethodTypes.find((tp) => tp.id === value.payMethodTypeId);
|
|
4668
4709
|
const selectedAccount = bankAccounts.find((a) => a.id === value.payAccountId);
|
|
4669
4710
|
const accounts = eligibleAccountsFor(bankAccounts, direction, selectedType);
|
|
@@ -4712,13 +4753,15 @@ function InstallmentSourceFields({
|
|
|
4712
4753
|
ariaLabel: t2("financial.invoiceForm.paidFrom"),
|
|
4713
4754
|
clearLabel: t2("financial.invoiceForm.clear"),
|
|
4714
4755
|
className: `${INLINE_LINK_TONE.dashed} ${tone(!!selectedAccount)}`,
|
|
4715
|
-
onChange: (id) => onChange({ ...value, payAccountId: id })
|
|
4756
|
+
onChange: (id) => onChange({ ...value, payAccountId: id }),
|
|
4757
|
+
onManage: can("financial", "manage") ? () => adminNavigateTo(ACCOUNTS_ROUTE) : void 0,
|
|
4758
|
+
manageLabel: t2("financial.invoiceForm.manageAccounts")
|
|
4716
4759
|
}
|
|
4717
4760
|
)
|
|
4718
4761
|
] });
|
|
4719
4762
|
}
|
|
4720
4763
|
var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
4721
|
-
function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className }) {
|
|
4764
|
+
function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className, onManage, manageLabel }) {
|
|
4722
4765
|
const [open, setOpen] = React25__default.useState(false);
|
|
4723
4766
|
const [q, setQ] = React25__default.useState("");
|
|
4724
4767
|
const selected = accounts.find((a) => a.id === value);
|
|
@@ -4783,7 +4826,22 @@ function AccountPicker({ accounts, value, onChange, disabled, title, placeholder
|
|
|
4783
4826
|
},
|
|
4784
4827
|
a.id
|
|
4785
4828
|
)) }),
|
|
4786
|
-
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 })
|
|
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
|
+
)
|
|
4787
4845
|
] })
|
|
4788
4846
|
] });
|
|
4789
4847
|
}
|
|
@@ -5013,95 +5071,6 @@ function summarizeInvoiceLines(rows, visible = 2) {
|
|
|
5013
5071
|
function invoiceOpensForEditing(status) {
|
|
5014
5072
|
return status !== "paid" && status !== "cancelled";
|
|
5015
5073
|
}
|
|
5016
|
-
function LocationSelector({ locations, value, onChange }) {
|
|
5017
|
-
const t2 = useTranslation();
|
|
5018
|
-
const [open, setOpen] = useState(false);
|
|
5019
|
-
const [search, setSearch] = useState("");
|
|
5020
|
-
const ref = useRef(null);
|
|
5021
|
-
useEffect(() => {
|
|
5022
|
-
function handler(e) {
|
|
5023
|
-
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
5024
|
-
}
|
|
5025
|
-
document.addEventListener("mousedown", handler);
|
|
5026
|
-
return () => document.removeEventListener("mousedown", handler);
|
|
5027
|
-
}, []);
|
|
5028
|
-
const selected = locations.find((l) => l.id === value);
|
|
5029
|
-
const filtered = locations.filter(
|
|
5030
|
-
(l) => !search.trim() || l.name.toLowerCase().includes(search.toLowerCase())
|
|
5031
|
-
);
|
|
5032
|
-
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
5033
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.unit") }),
|
|
5034
|
-
/* @__PURE__ */ jsxs(
|
|
5035
|
-
"button",
|
|
5036
|
-
{
|
|
5037
|
-
type: "button",
|
|
5038
|
-
onClick: () => setOpen(!open),
|
|
5039
|
-
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",
|
|
5040
|
-
children: [
|
|
5041
|
-
/* @__PURE__ */ jsx(Building2, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
5042
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? (value ? t2("financial.invoiceForm.selectUnit") : "Todas / sem unidade") }),
|
|
5043
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
|
|
5044
|
-
]
|
|
5045
|
-
}
|
|
5046
|
-
),
|
|
5047
|
-
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: [
|
|
5048
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [
|
|
5049
|
-
/* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
5050
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.selectUnitTitle") }),
|
|
5051
|
-
/* @__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" }) })
|
|
5052
|
-
] }),
|
|
5053
|
-
/* @__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: [
|
|
5054
|
-
/* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
5055
|
-
/* @__PURE__ */ jsx(
|
|
5056
|
-
"input",
|
|
5057
|
-
{
|
|
5058
|
-
type: "text",
|
|
5059
|
-
autoFocus: true,
|
|
5060
|
-
value: search,
|
|
5061
|
-
onChange: (e) => setSearch(e.target.value),
|
|
5062
|
-
placeholder: t2("financial.invoiceForm.searchUnit"),
|
|
5063
|
-
className: "flex-1 bg-transparent text-sm outline-none"
|
|
5064
|
-
}
|
|
5065
|
-
)
|
|
5066
|
-
] }) }),
|
|
5067
|
-
/* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-auto py-1", children: [
|
|
5068
|
-
/* @__PURE__ */ jsx(
|
|
5069
|
-
"button",
|
|
5070
|
-
{
|
|
5071
|
-
type: "button",
|
|
5072
|
-
onClick: () => {
|
|
5073
|
-
onChange("");
|
|
5074
|
-
setOpen(false);
|
|
5075
|
-
setSearch("");
|
|
5076
|
-
},
|
|
5077
|
-
className: `w-full px-3 py-2.5 text-left text-sm hover:bg-muted/50 ${!value ? "bg-muted/30" : ""}`,
|
|
5078
|
-
children: "Todas / sem unidade"
|
|
5079
|
-
}
|
|
5080
|
-
),
|
|
5081
|
-
filtered.map((loc) => /* @__PURE__ */ jsxs(
|
|
5082
|
-
"button",
|
|
5083
|
-
{
|
|
5084
|
-
onClick: () => {
|
|
5085
|
-
onChange(loc.id);
|
|
5086
|
-
setOpen(false);
|
|
5087
|
-
setSearch("");
|
|
5088
|
-
},
|
|
5089
|
-
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" : ""}`,
|
|
5090
|
-
children: [
|
|
5091
|
-
/* @__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" }) }),
|
|
5092
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
5093
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: loc.name }),
|
|
5094
|
-
loc.isHQ && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoiceForm.headquarters") })
|
|
5095
|
-
] })
|
|
5096
|
-
]
|
|
5097
|
-
},
|
|
5098
|
-
loc.id
|
|
5099
|
-
)),
|
|
5100
|
-
filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: t2("financial.invoiceForm.noUnitsFound") })
|
|
5101
|
-
] })
|
|
5102
|
-
] })
|
|
5103
|
-
] });
|
|
5104
|
-
}
|
|
5105
5074
|
var formId = 1;
|
|
5106
5075
|
function fid() {
|
|
5107
5076
|
return `f${formId++}`;
|
|
@@ -5136,11 +5105,9 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5136
5105
|
const config = useFinancialConfig();
|
|
5137
5106
|
const provider = useFinancialProvider();
|
|
5138
5107
|
const { currency, itemTypes: itemTypesRaw = [], locations = [] } = config;
|
|
5139
|
-
const unitMode =
|
|
5140
|
-
const activeUnitId = useActiveUnitId();
|
|
5108
|
+
const { unitMode, activeUnitId, choices: unitChoices } = useWritableUnits(locations);
|
|
5141
5109
|
const reachableUnits = useUnitFilter().options;
|
|
5142
|
-
const
|
|
5143
|
-
const showUnitField = unitChoices.length > 1;
|
|
5110
|
+
const showUnitField = unitMode ? reachableUnits.length > 1 : unitChoices.length > 1;
|
|
5144
5111
|
const [giftCardsEnabled, setGiftCardsEnabled] = useState(false);
|
|
5145
5112
|
useEffect(() => {
|
|
5146
5113
|
let cancelled = false;
|
|
@@ -5158,7 +5125,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5158
5125
|
};
|
|
5159
5126
|
}, [direction, provider]);
|
|
5160
5127
|
const giftCardLookup = useMemo(() => {
|
|
5161
|
-
if (!giftCardsEnabled || !provider.
|
|
5128
|
+
if (!giftCardsEnabled || !provider.findGiftCardByCode) return null;
|
|
5162
5129
|
const asOption = (card) => ({
|
|
5163
5130
|
id: card.id,
|
|
5164
5131
|
label: `Gift Card ${card.code.replace(/(.{4})(?=.)/g, "$1 ")}`,
|
|
@@ -5167,21 +5134,34 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5167
5134
|
price: card.faceValue,
|
|
5168
5135
|
data: { price: card.faceValue, code: card.code, state: card.state }
|
|
5169
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
|
+
};
|
|
5170
5145
|
return {
|
|
5171
5146
|
async search(query) {
|
|
5172
|
-
|
|
5173
|
-
|
|
5147
|
+
try {
|
|
5148
|
+
return [await resolve(query)];
|
|
5149
|
+
} catch {
|
|
5150
|
+
return [];
|
|
5151
|
+
}
|
|
5174
5152
|
},
|
|
5175
|
-
async getById(
|
|
5176
|
-
|
|
5177
|
-
const card = cards.find((entry) => entry.id === id);
|
|
5178
|
-
return card ? asOption(card) : null;
|
|
5153
|
+
async getById() {
|
|
5154
|
+
return null;
|
|
5179
5155
|
},
|
|
5180
|
-
|
|
5181
|
-
|
|
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+/, "")
|
|
5182
5162
|
}
|
|
5183
5163
|
};
|
|
5184
|
-
}, [giftCardsEnabled, provider]);
|
|
5164
|
+
}, [giftCardsEnabled, provider, t2]);
|
|
5185
5165
|
const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
|
|
5186
5166
|
const translatedItemTypes = React25__default.useMemo(
|
|
5187
5167
|
() => itemTypesRaw.map((tp) => {
|
|
@@ -5210,6 +5190,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5210
5190
|
const createInvoice = useFinancialStore((s) => s.createInvoice);
|
|
5211
5191
|
const guardMovements = useLimitGuard("movements_month");
|
|
5212
5192
|
const can = usePermissionOptional();
|
|
5193
|
+
const canManageFinancial = can("financial", "manage");
|
|
5213
5194
|
const isEdit = !!editId;
|
|
5214
5195
|
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
|
|
5215
5196
|
useEffect(() => {
|
|
@@ -5412,7 +5393,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5412
5393
|
}, [items.length]);
|
|
5413
5394
|
const somaParcelas = Math.round(installments.reduce((s, i) => s + i.amount, 0) * 100);
|
|
5414
5395
|
useEffect(() => {
|
|
5415
|
-
if (installments.length === 0
|
|
5396
|
+
if (installments.length === 0) return;
|
|
5397
|
+
if (scheduleTotal <= 0) {
|
|
5398
|
+
if (!isEdit) setInstallments([]);
|
|
5399
|
+
return;
|
|
5400
|
+
}
|
|
5416
5401
|
if (somaParcelas === Math.round(scheduleTotal * 100)) return;
|
|
5417
5402
|
const count = installments.length;
|
|
5418
5403
|
const perInstallment = Math.floor(scheduleTotal * 100 / count) / 100;
|
|
@@ -5421,7 +5406,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5421
5406
|
...inst,
|
|
5422
5407
|
amount: i === 0 ? perInstallment + remainder : perInstallment
|
|
5423
5408
|
})));
|
|
5424
|
-
}, [scheduleTotal, somaParcelas, installments.length]);
|
|
5409
|
+
}, [scheduleTotal, somaParcelas, installments.length, isEdit]);
|
|
5425
5410
|
function addItemOfKind(kind) {
|
|
5426
5411
|
const newItem = {
|
|
5427
5412
|
_id: fid(),
|
|
@@ -5966,7 +5951,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5966
5951
|
] }) });
|
|
5967
5952
|
const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5968
5953
|
/* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
5969
|
-
showUnitField && /* @__PURE__ */ jsx(
|
|
5954
|
+
showUnitField && /* @__PURE__ */ jsx(UnitField, { choices: unitChoices, value: unitId, onChange: setUnitId }),
|
|
5970
5955
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
5971
5956
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
|
|
5972
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" })
|
|
@@ -6131,7 +6116,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
6131
6116
|
label: accountLabel(a),
|
|
6132
6117
|
subtitle: a.accountType ?? void 0
|
|
6133
6118
|
})),
|
|
6134
|
-
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")
|
|
6135
6123
|
}
|
|
6136
6124
|
)
|
|
6137
6125
|
)
|
|
@@ -6154,7 +6142,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
6154
6142
|
id: c.id,
|
|
6155
6143
|
label: c.code ? `${c.code} - ${c.name}` : c.name
|
|
6156
6144
|
})),
|
|
6157
|
-
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")
|
|
6158
6149
|
}
|
|
6159
6150
|
)
|
|
6160
6151
|
}] : []
|
|
@@ -9789,6 +9780,21 @@ var shortDate2 = (value) => {
|
|
|
9789
9780
|
const [year, month, day] = value.split("-");
|
|
9790
9781
|
return year && month && day ? `${day}/${month}/${year}` : value;
|
|
9791
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
|
+
};
|
|
9792
9798
|
var round23 = (n) => Math.round((Number(n) || 0) * 100) / 100;
|
|
9793
9799
|
var emptyItem = () => ({ description: "", quantity: 1, unitPrice: 0 });
|
|
9794
9800
|
function LinhaDoExtrato2({ line, fmt }) {
|
|
@@ -9813,6 +9819,35 @@ function LinhaDoExtrato2({ line, fmt }) {
|
|
|
9813
9819
|
] })
|
|
9814
9820
|
] });
|
|
9815
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
|
+
}
|
|
9816
9851
|
function EscolhaCard({ icon: Icon, title, hint, active, onClick }) {
|
|
9817
9852
|
return /* @__PURE__ */ jsxs(
|
|
9818
9853
|
"button",
|
|
@@ -9831,17 +9866,18 @@ function EscolhaCard({ icon: Icon, title, hint, active, onClick }) {
|
|
|
9831
9866
|
}
|
|
9832
9867
|
);
|
|
9833
9868
|
}
|
|
9834
|
-
function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
9869
|
+
function ReconcileFlow({ line, initialIntent, initialTarget, initialStep = 0, onClose, onDone }) {
|
|
9835
9870
|
const t2 = useTranslation();
|
|
9836
9871
|
const { currency } = useFinancialConfig();
|
|
9837
9872
|
const provider = useFinancialProvider();
|
|
9838
9873
|
const fmt = useCallback((n) => formatCurrency(n, currency), [currency]);
|
|
9839
|
-
const [step, setStep] = useState(
|
|
9874
|
+
const [step, setStep] = useState(initialStep);
|
|
9840
9875
|
const [intent, setIntent] = useState(initialIntent ?? null);
|
|
9841
9876
|
const [saving, setSaving] = useState(false);
|
|
9842
9877
|
const [candidates, setCandidates] = useState([]);
|
|
9843
9878
|
const [candidatesLoading, setCandidatesLoading] = useState(false);
|
|
9844
|
-
const [
|
|
9879
|
+
const [target, setTarget] = useState(initialTarget ?? null);
|
|
9880
|
+
const targetId = target?.candidate.movement.id ?? null;
|
|
9845
9881
|
const [adjustments, setAdjustments] = useState([]);
|
|
9846
9882
|
const [contactId, setContactId] = useState("");
|
|
9847
9883
|
const [contactName, setContactName] = useState(line.counterpartyName ?? "");
|
|
@@ -9864,7 +9900,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9864
9900
|
void provider.getBankAccounts().then(setContas).catch(() => setContas([]));
|
|
9865
9901
|
}, [provider, line.bankAccountId]);
|
|
9866
9902
|
useEffect(() => {
|
|
9867
|
-
if (intent !== "link" || !provider.suggestReconciliation) return;
|
|
9903
|
+
if (intent !== "link" || !provider.suggestReconciliation || provider.searchReconciliationTargets) return;
|
|
9868
9904
|
let cancelled = false;
|
|
9869
9905
|
setCandidatesLoading(true);
|
|
9870
9906
|
provider.suggestReconciliation(line.id).then((raw) => {
|
|
@@ -9878,10 +9914,72 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9878
9914
|
cancelled = true;
|
|
9879
9915
|
};
|
|
9880
9916
|
}, [intent, provider, line.id]);
|
|
9881
|
-
const
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
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
|
+
};
|
|
9885
9983
|
const contaDoExtrato = useMemo(
|
|
9886
9984
|
() => contas.find((a) => a.id === line.bankAccountId) ?? null,
|
|
9887
9985
|
[contas, line.bankAccountId]
|
|
@@ -9906,6 +10004,15 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9906
10004
|
const rows = q ? planoDoTipo.filter((a) => `${a.code ?? ""} ${a.name}`.toLocaleLowerCase("pt-BR").includes(q)) : planoDoTipo;
|
|
9907
10005
|
return rows.slice(0, 50).map((a) => ({ id: a.id, label: accountLabel(a) }));
|
|
9908
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]);
|
|
9909
10016
|
const valorAQuitar = intent === "link" && target ? target.openAmount : round23(line.amount);
|
|
9910
10017
|
const quita = intent === "bill" || intent === "link" && target?.mode === "settle";
|
|
9911
10018
|
const leitor = useMemo(() => {
|
|
@@ -9942,8 +10049,9 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
9942
10049
|
const somaItens = sumItems(items.filter((i) => i.description.trim()));
|
|
9943
10050
|
const erroItens = itemsMismatch(items, line.amount, fmt);
|
|
9944
10051
|
const gap = target ? adjustmentGap(line.amount, target.openAmount) : 0;
|
|
9945
|
-
const erroAjuste = intent === "link" && target?.mode === "settle" && Math.abs(gap) >= 0.01 ? adjustmentsMismatch(adjustments, gap, fmt) : null;
|
|
9946
|
-
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;
|
|
9947
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;
|
|
9948
10056
|
const bloqueioPasso3 = saving ? t2("common.saving") : null;
|
|
9949
10057
|
async function confirmar() {
|
|
@@ -10070,37 +10178,148 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10070
10178
|
onClick: () => setIntent("transfer")
|
|
10071
10179
|
}
|
|
10072
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) })
|
|
10073
10201
|
] }),
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
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,
|
|
10083
10217
|
{
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
10092
|
-
shortDate2(c.movement.dueDate),
|
|
10093
|
-
/* @__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) }) }),
|
|
10094
|
-
/* @__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") })
|
|
10095
|
-
] })
|
|
10096
|
-
] }),
|
|
10097
|
-
/* @__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") }
|
|
10098
10225
|
]
|
|
10099
|
-
}
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
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
|
+
] }) })
|
|
10104
10323
|
] });
|
|
10105
10324
|
const passoDetalhes = /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-3", children: [
|
|
10106
10325
|
intent === "bill" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -10152,30 +10371,28 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10152
10371
|
onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, description: e.target.value } : x))
|
|
10153
10372
|
}
|
|
10154
10373
|
) }),
|
|
10155
|
-
/* @__PURE__ */ jsx(
|
|
10374
|
+
/* @__PURE__ */ jsx("div", { className: "w-16 shrink-0", children: /* @__PURE__ */ jsx(
|
|
10156
10375
|
Input,
|
|
10157
10376
|
{
|
|
10158
10377
|
"aria-label": t2("financial.reconciliation.itemQuantity"),
|
|
10159
|
-
className: "w-16 shrink-0",
|
|
10160
10378
|
type: "number",
|
|
10161
10379
|
min: "0",
|
|
10162
10380
|
step: "0.001",
|
|
10163
10381
|
value: item.quantity,
|
|
10164
10382
|
onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, quantity: Number(e.target.value) } : x))
|
|
10165
10383
|
}
|
|
10166
|
-
),
|
|
10167
|
-
/* @__PURE__ */ jsx(
|
|
10384
|
+
) }),
|
|
10385
|
+
/* @__PURE__ */ jsx("div", { className: "w-24 shrink-0", children: /* @__PURE__ */ jsx(
|
|
10168
10386
|
Input,
|
|
10169
10387
|
{
|
|
10170
10388
|
"aria-label": t2("financial.reconciliation.itemUnitPrice"),
|
|
10171
|
-
className: "w-24 shrink-0",
|
|
10172
10389
|
type: "number",
|
|
10173
10390
|
min: "0",
|
|
10174
10391
|
step: "0.01",
|
|
10175
10392
|
value: item.unitPrice,
|
|
10176
10393
|
onChange: (e) => setItems((prev) => prev.map((x, i) => i === idx ? { ...x, unitPrice: Number(e.target.value) } : x))
|
|
10177
10394
|
}
|
|
10178
|
-
),
|
|
10395
|
+
) }),
|
|
10179
10396
|
/* @__PURE__ */ jsx(Hint, { label: t2("common.delete"), children: /* @__PURE__ */ jsx(
|
|
10180
10397
|
Button,
|
|
10181
10398
|
{
|
|
@@ -10276,58 +10493,86 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10276
10493
|
] })
|
|
10277
10494
|
] }),
|
|
10278
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: [
|
|
10279
|
-
/* @__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", {
|
|
10280
10497
|
gap: fmt(Math.abs(gap)),
|
|
10281
10498
|
side: gap > 0 ? t2("financial.reconciliation.adjustmentMore") : t2("financial.reconciliation.adjustmentLess")
|
|
10282
10499
|
}) }),
|
|
10283
|
-
adjustments.map((adj, idx) =>
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
{
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
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
|
+
}),
|
|
10331
10576
|
/* @__PURE__ */ jsxs(
|
|
10332
10577
|
Button,
|
|
10333
10578
|
{
|
|
@@ -10335,6 +10580,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10335
10580
|
variant: "outline",
|
|
10336
10581
|
onClick: () => setAdjustments((prev) => [...prev, {
|
|
10337
10582
|
type: gap > 0 ? "interest" : "discount",
|
|
10583
|
+
description: descricaoPadrao(gap > 0 ? "interest" : "discount"),
|
|
10338
10584
|
// A diferença que ainda não foi explicada já vem no campo:
|
|
10339
10585
|
// digitar de novo um número que a tela acabou de calcular é
|
|
10340
10586
|
// trabalho que o produto podia poupar.
|
|
@@ -10433,6 +10679,13 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10433
10679
|
) : /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: t2("financial.reconciliation.noPaymentNeeded") })
|
|
10434
10680
|
] });
|
|
10435
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
|
+
};
|
|
10436
10689
|
const pages = [
|
|
10437
10690
|
{
|
|
10438
10691
|
id: "classify",
|
|
@@ -10441,13 +10694,15 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10441
10694
|
footer: motivo(bloqueioPasso1),
|
|
10442
10695
|
blockedReason: bloqueioPasso1 ?? void 0
|
|
10443
10696
|
},
|
|
10444
|
-
{
|
|
10445
|
-
id: "
|
|
10446
|
-
title:
|
|
10447
|
-
render: () =>
|
|
10448
|
-
footer: motivo(
|
|
10449
|
-
blockedReason:
|
|
10450
|
-
},
|
|
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] : [],
|
|
10451
10706
|
{
|
|
10452
10707
|
id: "confirm",
|
|
10453
10708
|
title: t2("financial.reconciliation.stepConfirm"),
|
|
@@ -10464,7 +10719,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10464
10719
|
if (!o && !saving) onClose();
|
|
10465
10720
|
},
|
|
10466
10721
|
mode: "steps",
|
|
10467
|
-
size: "lg",
|
|
10722
|
+
size: intent === "link" && pages[step]?.id === "select" ? "4xl" : "lg",
|
|
10468
10723
|
title: t2("financial.reconciliation.flowTitle"),
|
|
10469
10724
|
pages,
|
|
10470
10725
|
current: step,
|
|
@@ -10474,8 +10729,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
10474
10729
|
setStep(Math.max(0, alvo));
|
|
10475
10730
|
return;
|
|
10476
10731
|
}
|
|
10477
|
-
if (step
|
|
10478
|
-
if (step === 1 && bloqueioPasso2) return;
|
|
10732
|
+
if (pages.slice(step, alvo).some((p) => p.blockedReason)) return;
|
|
10479
10733
|
setStep(Math.min(pages.length - 1, alvo));
|
|
10480
10734
|
},
|
|
10481
10735
|
doneLabel: saving ? t2("common.saving") : t2("financial.reconciliation.confirmAction"),
|
|
@@ -10810,9 +11064,16 @@ function ReconciliationView() {
|
|
|
10810
11064
|
t: t2,
|
|
10811
11065
|
onSugerir: () => void sugerir(line),
|
|
10812
11066
|
onTransferir: () => setFlow({ line, intent: "transfer" }),
|
|
10813
|
-
onVincular: () => setFlow({ line, intent: "link" }),
|
|
11067
|
+
onVincular: () => setFlow({ line, intent: "link", step: 1 }),
|
|
10814
11068
|
onLancarConta: () => setFlow({ line, intent: "bill" }),
|
|
10815
|
-
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
|
+
},
|
|
10816
11077
|
onDesfazer: () => void desfazer(line.id),
|
|
10817
11078
|
onVerConta: () => {
|
|
10818
11079
|
const modulo = line.direction === "debit" ? "payables" : "receivables";
|
|
@@ -10842,6 +11103,8 @@ function ReconciliationView() {
|
|
|
10842
11103
|
{
|
|
10843
11104
|
line: flow.line,
|
|
10844
11105
|
initialIntent: flow.intent,
|
|
11106
|
+
initialTarget: flow.target,
|
|
11107
|
+
initialStep: flow.step,
|
|
10845
11108
|
onClose: () => setFlow(null),
|
|
10846
11109
|
onDone: () => {
|
|
10847
11110
|
setFlow(null);
|
|
@@ -10971,7 +11234,7 @@ function StatementCard({
|
|
|
10971
11234
|
}
|
|
10972
11235
|
)
|
|
10973
11236
|
] }),
|
|
10974
|
-
/* @__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: [
|
|
10975
11238
|
/* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" }),
|
|
10976
11239
|
t2("financial.reconciliation.match")
|
|
10977
11240
|
] })
|
|
@@ -11229,6 +11492,7 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
11229
11492
|
function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
11230
11493
|
const provider = useFinancialProvider();
|
|
11231
11494
|
const { locations, currency, itemTypes: configuredItemTypes, entityLookups } = useFinancialConfig();
|
|
11495
|
+
const t2 = useTranslation();
|
|
11232
11496
|
const isPayable = direction === "payable";
|
|
11233
11497
|
const [value, setValue] = React25__default.useState(null);
|
|
11234
11498
|
const [loaded, setLoaded] = React25__default.useState(!valueId);
|
|
@@ -11260,6 +11524,7 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
11260
11524
|
const [isActive, setIsActive] = React25__default.useState(true);
|
|
11261
11525
|
const [accounts, setAccounts] = React25__default.useState([]);
|
|
11262
11526
|
const [costCenters, setCostCenters] = React25__default.useState([]);
|
|
11527
|
+
const { choices: unitChoices } = useWritableUnits(availableLocations);
|
|
11263
11528
|
const [saving, setSaving] = React25__default.useState(false);
|
|
11264
11529
|
const [ending, setEnding] = React25__default.useState(false);
|
|
11265
11530
|
const [error, setError] = React25__default.useState(null);
|
|
@@ -11325,12 +11590,16 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
11325
11590
|
cancelled = true;
|
|
11326
11591
|
};
|
|
11327
11592
|
}, [provider, direction, valueId]);
|
|
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]);
|
|
11328
11597
|
const offeredItemTypes = React25__default.useMemo(() => availableInvoiceItemTypes(
|
|
11329
|
-
|
|
11598
|
+
translatedItemTypes,
|
|
11330
11599
|
Object.keys(entityLookups),
|
|
11331
|
-
|
|
11600
|
+
t2("financial.itemType.other"),
|
|
11332
11601
|
direction
|
|
11333
|
-
), [
|
|
11602
|
+
), [translatedItemTypes, entityLookups, direction, t2]);
|
|
11334
11603
|
const itemTypes = useStockedItemTypes(offeredItemTypes, entityLookups);
|
|
11335
11604
|
const selectableAccounts = React25__default.useMemo(() => {
|
|
11336
11605
|
const postable = postableAccounts(accounts);
|
|
@@ -11456,13 +11725,10 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
11456
11725
|
] }) : void 0,
|
|
11457
11726
|
sideSections: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11458
11727
|
/* @__PURE__ */ jsx(OrderFormBlock, { title: "Recorr\xEAncia", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
11459
|
-
/* @__PURE__ */ jsx(
|
|
11460
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "Todas / sem unidade" }),
|
|
11461
|
-
availableLocations.map((location) => /* @__PURE__ */ jsx("option", { value: location.id, children: location.name }, location.id))
|
|
11462
|
-
] }) }),
|
|
11728
|
+
/* @__PURE__ */ jsx(UnitField, { label: "Unidade", choices: unitChoices, value: unitId, onChange: setUnitId }),
|
|
11463
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)) }) }),
|
|
11464
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) }) }),
|
|
11465
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
11731
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
11466
11732
|
/* @__PURE__ */ jsx(Field2, { label: "In\xEDcio *", children: /* @__PURE__ */ jsx(DatePicker, { value: startsOn, onChange: setStartsOn, className: "w-full" }) }),
|
|
11467
11733
|
/* @__PURE__ */ jsx(Field2, { label: "T\xE9rmino", children: /* @__PURE__ */ jsx(DatePicker, { value: endsOn, onChange: setEndsOn, className: "w-full" }) })
|
|
11468
11734
|
] }),
|
|
@@ -12424,6 +12690,10 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
12424
12690
|
"pagamento",
|
|
12425
12691
|
"pagto",
|
|
12426
12692
|
"pag",
|
|
12693
|
+
"pago",
|
|
12694
|
+
"paga",
|
|
12695
|
+
"pgto",
|
|
12696
|
+
"recebimento",
|
|
12427
12697
|
"boleto",
|
|
12428
12698
|
"debito",
|
|
12429
12699
|
"credito",
|
|
@@ -12468,15 +12738,20 @@ function nameScore(descricao, nome) {
|
|
|
12468
12738
|
if (texto.size === 0) return 0;
|
|
12469
12739
|
const achados = alvo.filter((w) => texto.has(w)).length;
|
|
12470
12740
|
const razao = achados / alvo.length;
|
|
12471
|
-
|
|
12472
|
-
|
|
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);
|
|
12473
12745
|
}
|
|
12474
|
-
function amountScore(lineAmount, movementAmount) {
|
|
12746
|
+
function amountScore(lineAmount, movementAmount, tolerance = 0.02) {
|
|
12475
12747
|
if (lineAmount <= 0) return 0;
|
|
12476
12748
|
const diff = Math.abs(movementAmount - lineAmount);
|
|
12477
|
-
|
|
12478
|
-
|
|
12749
|
+
const limite = lineAmount * tolerance + 0.01;
|
|
12750
|
+
if (diff > limite) return 0;
|
|
12751
|
+
return Math.max(0, 1 - diff / limite);
|
|
12479
12752
|
}
|
|
12753
|
+
var NAME_AMOUNT_TOLERANCE = 0.1;
|
|
12754
|
+
var STRONG_NAME = 0.5;
|
|
12480
12755
|
function dateScore(lineDate, refDate) {
|
|
12481
12756
|
if (!lineDate || !refDate) return 0;
|
|
12482
12757
|
const days = Math.abs(new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5;
|
|
@@ -12484,13 +12759,16 @@ function dateScore(lineDate, refDate) {
|
|
|
12484
12759
|
}
|
|
12485
12760
|
function scorePair(line, movement, context = {}) {
|
|
12486
12761
|
if (movement.direction !== line.direction) return 0;
|
|
12487
|
-
const valor = amountScore(line.amount, movement.amount);
|
|
12488
|
-
if (valor === 0) return 0;
|
|
12489
12762
|
const texto = `${line.description ?? ""} ${line.counterpartyName ?? ""}`;
|
|
12490
12763
|
const nome = Math.max(
|
|
12491
12764
|
nameScore(texto, context.contactName),
|
|
12492
12765
|
nameScore(texto, movement.notes)
|
|
12493
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;
|
|
12494
12772
|
const data = dateScore(line.date, movement.paymentDate ?? movement.dueDate);
|
|
12495
12773
|
return Math.round((0.45 * valor + 0.35 * nome + 0.2 * data) * 100) / 100;
|
|
12496
12774
|
}
|
|
@@ -13378,6 +13656,22 @@ function createMockFinancialProvider(options) {
|
|
|
13378
13656
|
};
|
|
13379
13657
|
}).sort((a, b) => b.score - a.score).slice(0, 25);
|
|
13380
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
|
+
},
|
|
13381
13675
|
// A mesma cabeça da conciliação de verdade (match-statement), sobre a
|
|
13382
13676
|
// memória: valor, nome e data, e um lançamento por linha.
|
|
13383
13677
|
async suggestReconciliationBatch(lines) {
|
|
@@ -13685,6 +13979,19 @@ async function writeLineAccounting(pub, tenantId, insertedItems, inputItems) {
|
|
|
13685
13979
|
const { error } = await pub.from(T.invoiceItems).upsert(rows, { onConflict: "item_id" });
|
|
13686
13980
|
if (error) console.warn("[financial] line accounting mirror skipped:", error.message);
|
|
13687
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
|
+
}
|
|
13688
13995
|
function billRowToMovement(r) {
|
|
13689
13996
|
return {
|
|
13690
13997
|
id: r.installment_id,
|
|
@@ -13781,6 +14088,85 @@ function createSupabaseFinancialProvider() {
|
|
|
13781
14088
|
externalSource: data.external_source
|
|
13782
14089
|
};
|
|
13783
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
|
+
}
|
|
13784
14170
|
async function markReconciled(statementLineId, matchedMovementId) {
|
|
13785
14171
|
const { pub } = getClients();
|
|
13786
14172
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -15421,6 +15807,31 @@ function createSupabaseFinancialProvider() {
|
|
|
15421
15807
|
customerEmail: r.customer_email
|
|
15422
15808
|
}));
|
|
15423
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
|
+
},
|
|
15424
15835
|
async issueGiftCards(input) {
|
|
15425
15836
|
const { pub } = getClients();
|
|
15426
15837
|
const { data, error } = await pub.rpc("financial_issue_gift_cards", {
|
|
@@ -15580,7 +15991,17 @@ function createSupabaseFinancialProvider() {
|
|
|
15580
15991
|
if (o) c.invoice = { id: o.id, contactName: o.metadata?.contactName, fiscalNumber: o.reference_number };
|
|
15581
15992
|
}
|
|
15582
15993
|
}
|
|
15583
|
-
|
|
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);
|
|
15584
16005
|
},
|
|
15585
16006
|
// ------------------------------------------------------------------
|
|
15586
16007
|
// A SUGESTÃO QUE CHEGA SOZINHA, para a página inteira.
|
|
@@ -15600,16 +16021,38 @@ function createSupabaseFinancialProvider() {
|
|
|
15600
16021
|
const abertas = lines.filter((line) => !line.reconciledAt && (Number(line.amount) || 0) > 0);
|
|
15601
16022
|
if (abertas.length === 0) return {};
|
|
15602
16023
|
const faixas = [...new Set(abertas.map((line) => round26(line.amount)))].map((valor) => {
|
|
15603
|
-
const tol = Math.max(0.01, round26(valor *
|
|
16024
|
+
const tol = Math.max(0.01, round26(valor * NAME_AMOUNT_TOLERANCE));
|
|
15604
16025
|
return `and(amount.gte.${round26(valor - tol)},amount.lte.${round26(valor + tol)})`;
|
|
15605
16026
|
});
|
|
15606
16027
|
const direcoes = [...new Set(abertas.map((line) => line.direction))];
|
|
15607
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);
|
|
15608
16029
|
if (error) throw error;
|
|
15609
16030
|
const movimentos = (data ?? []).map((r) => snakeToCamel(r));
|
|
15610
|
-
|
|
16031
|
+
const datas = abertas.map((line) => line.date).filter(Boolean).sort();
|
|
15611
16032
|
const contexto = /* @__PURE__ */ new Map();
|
|
15612
|
-
|
|
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))];
|
|
15613
16056
|
if (invoiceIds.length > 0) {
|
|
15614
16057
|
const { data: orders } = await core.from("orders").select("id, reference_number, metadata").in("id", invoiceIds);
|
|
15615
16058
|
const byId = new Map((orders ?? []).map((o) => [o.id, o]));
|
|
@@ -15628,7 +16071,7 @@ function createSupabaseFinancialProvider() {
|
|
|
15628
16071
|
async reconcileMovement(input) {
|
|
15629
16072
|
if (input.mode === "settle" && input.matchedMovementId) {
|
|
15630
16073
|
const line = await readStatementLine(input.bankMovementId);
|
|
15631
|
-
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();
|
|
15632
16075
|
if (!alvo) throw new Error("Lan\xE7amento n\xE3o encontrado");
|
|
15633
16076
|
await runReconcileLink(
|
|
15634
16077
|
reconcileFacade(),
|
|
@@ -15636,7 +16079,7 @@ function createSupabaseFinancialProvider() {
|
|
|
15636
16079
|
{
|
|
15637
16080
|
movementId: input.matchedMovementId,
|
|
15638
16081
|
mode: "settle",
|
|
15639
|
-
openAmount: round26((Number(alvo.amount) || 0) - (Number(alvo.
|
|
16082
|
+
openAmount: round26((Number(alvo.amount) || 0) - (Number(alvo.paid) || 0))
|
|
15640
16083
|
},
|
|
15641
16084
|
{ payment: input.payment, adjustments: input.adjustments }
|
|
15642
16085
|
);
|
|
@@ -15911,7 +16354,8 @@ function createSupabaseFinancialProvider() {
|
|
|
15911
16354
|
};
|
|
15912
16355
|
for (const payee of payees) {
|
|
15913
16356
|
if (text2(payee.document)) {
|
|
15914
|
-
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;
|
|
15915
16359
|
}
|
|
15916
16360
|
const row = {
|
|
15917
16361
|
bank_code: text2(payee.bankCode),
|
|
@@ -15930,18 +16374,16 @@ function createSupabaseFinancialProvider() {
|
|
|
15930
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();
|
|
15931
16375
|
targetId = existing?.id;
|
|
15932
16376
|
}
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
});
|
|
15944
|
-
}
|
|
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?)");
|
|
15945
16387
|
}
|
|
15946
16388
|
},
|
|
15947
16389
|
/**
|
|
@@ -18029,6 +18471,9 @@ var en = {
|
|
|
18029
18471
|
"financial.invoiceForm.paidNeedsMethod": "Tell us the payment method and the account",
|
|
18030
18472
|
"financial.invoiceForm.willBePaid": "This installment will be settled",
|
|
18031
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",
|
|
18032
18477
|
"financial.invoice.newManualHint": "Open the full invoice form",
|
|
18033
18478
|
"financial.invoice.noDetails": "No additional details recorded",
|
|
18034
18479
|
"financial.invoice.unlinkedPayments": "Payments not linked to an installment",
|
|
@@ -18077,6 +18522,13 @@ var en = {
|
|
|
18077
18522
|
"financial.itemType.product": "Product",
|
|
18078
18523
|
"financial.itemType.other": "Other",
|
|
18079
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",
|
|
18080
18532
|
"financial.invoiceForm.cancel": "Cancel",
|
|
18081
18533
|
"financial.invoiceForm.close": "Close",
|
|
18082
18534
|
"financial.invoiceForm.creating": "Creating new invoice",
|
|
@@ -18280,13 +18732,41 @@ var en = {
|
|
|
18280
18732
|
"financial.reconciliation.reconciled": "Reconciled",
|
|
18281
18733
|
"financial.reconciliation.undo": "Undo",
|
|
18282
18734
|
"financial.reconciliation.flowTitle": "Reconcile transaction",
|
|
18283
|
-
"financial.reconciliation.stepClassify": "
|
|
18735
|
+
"financial.reconciliation.stepClassify": "What to do",
|
|
18284
18736
|
"financial.reconciliation.stepItems": "The items",
|
|
18285
18737
|
"financial.reconciliation.stepDetails": "The details",
|
|
18286
18738
|
"financial.reconciliation.stepConfirm": "Confirm",
|
|
18287
18739
|
"financial.reconciliation.confirmAction": "Confirm",
|
|
18288
18740
|
"financial.reconciliation.howToClassify": "How do you want to classify this transaction?",
|
|
18289
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",
|
|
18290
18770
|
"financial.reconciliation.pickCandidate": "Pick the entry to link",
|
|
18291
18771
|
"financial.reconciliation.needDescription": "Describe the entry",
|
|
18292
18772
|
"financial.reconciliation.intentLink": "Link to an existing entry",
|
|
@@ -18322,6 +18802,14 @@ var en = {
|
|
|
18322
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.",
|
|
18323
18803
|
"financial.reconciliation.noAdjustmentNeeded": "The amount matches the bill. Nothing to adjust.",
|
|
18324
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",
|
|
18325
18813
|
"financial.reconciliation.adjustmentMore": "more",
|
|
18326
18814
|
"financial.reconciliation.adjustmentLess": "less",
|
|
18327
18815
|
"financial.reconciliation.interest": "Interest",
|
|
@@ -18923,6 +19411,9 @@ var ptBR = {
|
|
|
18923
19411
|
"financial.invoiceForm.paidNeedsMethod": "Diga a forma de pagamento e a conta",
|
|
18924
19412
|
"financial.invoiceForm.willBePaid": "Esta parcela entra como paga",
|
|
18925
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",
|
|
18926
19417
|
"financial.invoice.newManualHint": "Preencher os campos da conta",
|
|
18927
19418
|
"financial.invoice.noDetails": "Nenhum detalhe adicional registrado",
|
|
18928
19419
|
"financial.invoice.unlinkedPayments": "Pagamentos sem parcela vinculada",
|
|
@@ -18971,6 +19462,13 @@ var ptBR = {
|
|
|
18971
19462
|
"financial.itemType.product": "Produto",
|
|
18972
19463
|
"financial.itemType.other": "Outro",
|
|
18973
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",
|
|
18974
19472
|
"financial.invoiceForm.cancel": "Cancelar",
|
|
18975
19473
|
"financial.invoiceForm.close": "Fechar",
|
|
18976
19474
|
"financial.invoiceForm.creating": "Criando nova fatura",
|
|
@@ -19177,13 +19675,41 @@ var ptBR = {
|
|
|
19177
19675
|
"financial.reconciliation.reconciled": "Conciliado",
|
|
19178
19676
|
"financial.reconciliation.undo": "Desfazer",
|
|
19179
19677
|
"financial.reconciliation.flowTitle": "Conciliar movimenta\xE7\xE3o",
|
|
19180
|
-
"financial.reconciliation.stepClassify": "
|
|
19678
|
+
"financial.reconciliation.stepClassify": "O que deseja fazer",
|
|
19181
19679
|
"financial.reconciliation.stepItems": "Os itens",
|
|
19182
19680
|
"financial.reconciliation.stepDetails": "Os detalhes",
|
|
19183
19681
|
"financial.reconciliation.stepConfirm": "Confirmar",
|
|
19184
19682
|
"financial.reconciliation.confirmAction": "Confirmar",
|
|
19185
19683
|
"financial.reconciliation.howToClassify": "Como voc\xEA quer classificar esta transa\xE7\xE3o?",
|
|
19186
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",
|
|
19187
19713
|
"financial.reconciliation.pickCandidate": "Escolha o lan\xE7amento a vincular",
|
|
19188
19714
|
"financial.reconciliation.needDescription": "Descreva o lan\xE7amento",
|
|
19189
19715
|
"financial.reconciliation.intentLink": "Vincular a um lan\xE7amento",
|
|
@@ -19219,6 +19745,14 @@ var ptBR = {
|
|
|
19219
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.",
|
|
19220
19746
|
"financial.reconciliation.noAdjustmentNeeded": "O valor bate com a conta. Nada a ajustar.",
|
|
19221
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",
|
|
19222
19756
|
"financial.reconciliation.adjustmentMore": "a mais",
|
|
19223
19757
|
"financial.reconciliation.adjustmentLess": "a menos",
|
|
19224
19758
|
"financial.reconciliation.interest": "Juros",
|
|
@@ -44490,6 +45024,112 @@ BEGIN
|
|
|
44490
45024
|
RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', false);
|
|
44491
45025
|
END $$;
|
|
44492
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;
|
|
44493
45133
|
`;
|
|
44494
45134
|
var MIGRATIONS = [
|
|
44495
45135
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
@@ -44608,7 +45248,9 @@ var MIGRATIONS = [
|
|
|
44608
45248
|
{ id: "113_so_a_conta_integrada_aparece_na_conciliacao", sql: MIGRATION_113_SO_A_CONTA_INTEGRADA_APARECE_NA_CONCILIACAO },
|
|
44609
45249
|
{ id: "114_a_parcela_diz_por_qual_conta_ela_foi_paga", sql: MIGRATION_114_A_PARCELA_DIZ_POR_QUAL_CONTA_ELA_FOI_PAGA },
|
|
44610
45250
|
{ id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE },
|
|
44611
|
-
{ id: "116_um_estorno_nao_se_estorna", sql: MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA }
|
|
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 }
|
|
44612
45254
|
];
|
|
44613
45255
|
var STANDALONE_CONFIG = resolveConfig();
|
|
44614
45256
|
registerTranslations(financialLocales);
|
|
@@ -45018,7 +45660,6 @@ function createSafeFinancialProvider() {
|
|
|
45018
45660
|
() => createMockFinancialProvider()
|
|
45019
45661
|
);
|
|
45020
45662
|
}
|
|
45021
|
-
var ACCOUNTS_ROUTE = "/registry/accounts";
|
|
45022
45663
|
var FINANCIAL_NAV_FEATURES = {
|
|
45023
45664
|
reconciliation: "fin_reconciliation",
|
|
45024
45665
|
cards: "fin_cards",
|