@fayz-ai/plugin-financial 0.16.0 → 0.16.1
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/ChartOfAccountsTree.d.ts +10 -0
- package/dist/components/ChartOfAccountsTree.d.ts.map +1 -0
- package/dist/components/FinancialCounterpartySearchSelect.d.ts +3 -1
- package/dist/components/FinancialCounterpartySearchSelect.d.ts.map +1 -1
- package/dist/components/PaymentModal.d.ts.map +1 -1
- package/dist/data/registries.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.js +869 -56
- package/dist/index.js.map +1 -1
- package/dist/lib/chart-tree.d.ts +28 -0
- package/dist/lib/chart-tree.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 +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React20 from 'react';
|
|
2
2
|
import React20__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
3
|
-
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useDataChanged, countByTenant, tl, getSupabaseClientOptional, getActiveTenantId, exportCSV } from '@fayz-ai/core';
|
|
4
|
-
import { ModalTask, toast, useInModalTaskHost, CurrencyInput, DatePicker, Button, 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, Tooltip, TooltipTrigger, TooltipContent,
|
|
3
|
+
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useDataChanged, countByTenant, tl, resolveDataProvider, getSupabaseClientOptional, getActiveTenantId, emitDataChanged, exportCSV } from '@fayz-ai/core';
|
|
4
|
+
import { ModalTask, toast, useInModalTaskHost, CurrencyInput, DatePicker, Button, 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, Input, Tooltip, TooltipTrigger, TooltipContent, Checkbox, ModalFooter, SearchSelect, DashboardCanvas, ListView, DateRangeControl, PageHeaderActions, RequiredFilter, Hint, useSaveBar, tourAnchor, FilterMenu, FilterMenuRow, WizardSteps, KpiCard, ChartWidget, CardHeader, CardTitle, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
|
|
5
5
|
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, PluginSettingsPanel, dedup, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, usePermissionOptional, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, createArchetypeLookup, useChatStore, adminNavigateTo, revealPage, GetStarted, useAuth, resolveEntityRoute, PermissionGate, useLookupCreate, OrderFormSkeleton, OrderFormView, OrderFormBlock, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, useLimitGuard, invalidateLimit, askAI, requestChatUpload, toInlineFile } from '@fayz-ai/admin';
|
|
6
|
-
import { CircleDollarSign, FileText, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Pencil, Check, X, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, Camera, Undo2, ChevronDown, Building2, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Landmark, Clock, BookOpen, ChevronRight, Info, Calculator, StickyNote, Play, Square, ChevronUp, ClipboardCheck, FileCheck2, FileX2, RefreshCw, Send, CalendarClock, Loader2, BadgeCheck, Gift, Target, Settings, ArrowRight, Receipt, Users, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2,
|
|
6
|
+
import { CircleDollarSign, FileText, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Pencil, Check, X, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, Camera, Undo2, ChevronDown, Building2, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, Landmark, Clock, BookOpen, ChevronRight, Info, FolderOpen, Folder, RotateCcw, Calculator, StickyNote, Play, Square, ChevronUp, ClipboardCheck, FileCheck2, FileX2, RefreshCw, Send, CalendarClock, Loader2, BadgeCheck, Gift, Target, Settings, ArrowRight, Receipt, Users, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, Download, CheckCheck, Pause, MoreVertical, Printer, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, ArrowLeft, ArrowUpRight, ArrowDownRight, EyeOff, Eye } from 'lucide-react';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
8
|
import { createPortal } from 'react-dom';
|
|
9
9
|
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
@@ -1054,7 +1054,10 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1054
1054
|
const [costCenters, setCostCenters] = useState([]);
|
|
1055
1055
|
const mesCorrente = useMemo(() => {
|
|
1056
1056
|
const hoje = /* @__PURE__ */ new Date();
|
|
1057
|
-
return {
|
|
1057
|
+
return {
|
|
1058
|
+
from: isoDate(new Date(hoje.getFullYear(), hoje.getMonth(), 1)),
|
|
1059
|
+
to: isoDate(new Date(hoje.getFullYear(), hoje.getMonth() + 2, 0))
|
|
1060
|
+
};
|
|
1058
1061
|
}, []);
|
|
1059
1062
|
const [datePreset, setDatePreset] = useState("custom");
|
|
1060
1063
|
const [dateFrom, setDateFrom] = useState(mesCorrente.from);
|
|
@@ -2716,13 +2719,20 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
2716
2719
|
() => paymentMethods2.filter((m) => m.paymentMethodTypeId === selectedTypeId),
|
|
2717
2720
|
[paymentMethods2, selectedTypeId]
|
|
2718
2721
|
);
|
|
2719
|
-
const
|
|
2722
|
+
const isPayable = movement.direction === "debit";
|
|
2723
|
+
const payingByCompanyCard = isPayable && txType === "credit_card";
|
|
2724
|
+
const needsCard = !isPayable && (txType === "credit_card" || txType === "debit_card");
|
|
2720
2725
|
const needsCardInstallments = txType === "credit_card";
|
|
2721
2726
|
const eligibleAccounts2 = useMemo(() => {
|
|
2727
|
+
const active = bankAccounts.filter((a) => a.isActive !== false);
|
|
2728
|
+
if (isPayable) {
|
|
2729
|
+
if (txType === "credit_card") return active.filter((a) => a.accountType === "card_payable");
|
|
2730
|
+
return active.filter((a) => !["card_payable", "card_receivable_credit", "card_receivable_debit"].includes(a.accountType));
|
|
2731
|
+
}
|
|
2722
2732
|
const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType];
|
|
2723
2733
|
if (!allowed || !allowed.length) return bankAccounts;
|
|
2724
2734
|
return bankAccounts.filter((a) => allowed.includes(a.accountType));
|
|
2725
|
-
}, [bankAccounts, selectedType, txType]);
|
|
2735
|
+
}, [bankAccounts, selectedType, txType, isPayable]);
|
|
2726
2736
|
useEffect(() => {
|
|
2727
2737
|
if (selectedTypeId && !bankAccountId && eligibleAccounts2.length) {
|
|
2728
2738
|
setBankAccountId(eligibleAccounts2[0].id);
|
|
@@ -2858,15 +2868,15 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
2858
2868
|
/* @__PURE__ */ jsx(DatePicker, { value: paymentDate, onChange: setPaymentDate, className: "mt-1" })
|
|
2859
2869
|
] })
|
|
2860
2870
|
] }),
|
|
2861
|
-
filteredMethods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
|
|
2871
|
+
filteredMethods.length > 0 && !isPayable && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
|
|
2862
2872
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: needsCard ? t2("financial.payment.card") : t2("financial.payment.account") }),
|
|
2863
2873
|
/* @__PURE__ */ jsxs("select", { value: paymentMethodId, onChange: (e) => setPaymentMethodId(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
|
|
2864
2874
|
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.select") }),
|
|
2865
2875
|
filteredMethods.map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: m.name }, m.id))
|
|
2866
2876
|
] })
|
|
2867
2877
|
] }),
|
|
2868
|
-
needsCard && /* @__PURE__ */ jsxs("div", { className: `grid gap-3 stagger-field ${needsCardInstallments ? "grid-cols-2" : "grid-cols-1"}`, style: { animationDelay: "100ms" }, children: [
|
|
2869
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2878
|
+
(needsCard || payingByCompanyCard) && /* @__PURE__ */ jsxs("div", { className: `grid gap-3 stagger-field ${needsCard && needsCardInstallments ? "grid-cols-2" : "grid-cols-1"}`, style: { animationDelay: "100ms" }, children: [
|
|
2879
|
+
needsCard && /* @__PURE__ */ jsxs("div", { children: [
|
|
2870
2880
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.cardBrand") }),
|
|
2871
2881
|
/* @__PURE__ */ jsxs("select", { value: cardBrand, onChange: (e) => setCardBrand(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
|
|
2872
2882
|
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.selectBrand") }),
|
|
@@ -2889,7 +2899,46 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
2889
2899
|
" \u2192"
|
|
2890
2900
|
] })
|
|
2891
2901
|
] }),
|
|
2892
|
-
/* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "
|
|
2902
|
+
payingByCompanyCard && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
|
|
2903
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.whichCard") }),
|
|
2904
|
+
eligibleAccounts2.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
|
|
2905
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.payment.noCompanyCards") }),
|
|
2906
|
+
/* @__PURE__ */ jsxs("a", { href: "#/settings/financial/bank-accounts", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
2907
|
+
t2("financial.payment.registerCard"),
|
|
2908
|
+
" \u2192"
|
|
2909
|
+
] })
|
|
2910
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "mt-1.5 grid grid-cols-2 gap-2", role: "radiogroup", children: eligibleAccounts2.map((card) => {
|
|
2911
|
+
const last4 = (card.accountNumber ?? "").replace(/\D/g, "").slice(-4);
|
|
2912
|
+
const active = bankAccountId === card.id;
|
|
2913
|
+
return /* @__PURE__ */ jsxs(
|
|
2914
|
+
"button",
|
|
2915
|
+
{
|
|
2916
|
+
type: "button",
|
|
2917
|
+
role: "radio",
|
|
2918
|
+
"aria-checked": active,
|
|
2919
|
+
onClick: () => setBankAccountId(card.id),
|
|
2920
|
+
className: `flex items-center gap-2.5 rounded-button border px-2.5 py-2 text-left transition-colors ${active ? "border-primary bg-primary/5 ring-2 ring-primary/20" : "hover:bg-muted/40"}`,
|
|
2921
|
+
children: [
|
|
2922
|
+
/* @__PURE__ */ jsx(
|
|
2923
|
+
"span",
|
|
2924
|
+
{
|
|
2925
|
+
"aria-hidden": true,
|
|
2926
|
+
className: "flex h-7 w-11 shrink-0 items-end justify-end rounded-md border px-1 pb-0.5",
|
|
2927
|
+
style: { backgroundColor: card.color || "hsl(var(--muted))" },
|
|
2928
|
+
children: /* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 text-white/80 drop-shadow" })
|
|
2929
|
+
}
|
|
2930
|
+
),
|
|
2931
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
2932
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-xs font-medium", children: card.name }),
|
|
2933
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-[10px] tabular-nums text-muted-foreground", children: [card.bankName, last4 ? `\u2022\u2022\u2022\u2022 ${last4}` : null].filter(Boolean).join(" \xB7 ") })
|
|
2934
|
+
] })
|
|
2935
|
+
]
|
|
2936
|
+
},
|
|
2937
|
+
card.id
|
|
2938
|
+
);
|
|
2939
|
+
}) })
|
|
2940
|
+
] }),
|
|
2941
|
+
!payingByCompanyCard && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
|
|
2893
2942
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.bankAccount") }),
|
|
2894
2943
|
eligibleAccounts2.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
|
|
2895
2944
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: bankAccounts.length > 0 && selectedType ? t2("financial.payment.noAccountForMethod", { method: selectedType.name }) : t2("financial.payment.noAccounts") }),
|
|
@@ -3009,7 +3058,9 @@ function FinancialCounterpartySearchSelect({
|
|
|
3009
3058
|
label,
|
|
3010
3059
|
required,
|
|
3011
3060
|
onChange,
|
|
3012
|
-
onCreate
|
|
3061
|
+
onCreate,
|
|
3062
|
+
onEdit,
|
|
3063
|
+
disabled
|
|
3013
3064
|
}) {
|
|
3014
3065
|
const search = React20.useCallback(async (query) => {
|
|
3015
3066
|
const results = await lookup.search(query);
|
|
@@ -3028,6 +3079,7 @@ function FinancialCounterpartySearchSelect({
|
|
|
3028
3079
|
displayValue,
|
|
3029
3080
|
label,
|
|
3030
3081
|
required,
|
|
3082
|
+
disabled,
|
|
3031
3083
|
onChange,
|
|
3032
3084
|
onSearch: search,
|
|
3033
3085
|
minChars: 2,
|
|
@@ -3039,6 +3091,8 @@ function FinancialCounterpartySearchSelect({
|
|
|
3039
3091
|
createLabel: "Cadastrar",
|
|
3040
3092
|
clearLabel: "Limpar",
|
|
3041
3093
|
onCreate,
|
|
3094
|
+
onEdit,
|
|
3095
|
+
editLabel: "Editar",
|
|
3042
3096
|
placeholder: "Digite nome, telefone ou documento..."
|
|
3043
3097
|
}
|
|
3044
3098
|
);
|
|
@@ -3425,7 +3479,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3425
3479
|
}
|
|
3426
3480
|
setUnitId(invoice.unitId ?? locations[0]?.id ?? "");
|
|
3427
3481
|
setInvoiceDate(invoice.invoiceDate);
|
|
3428
|
-
setFiscalNumber(invoice.fiscalNumber ?? "");
|
|
3482
|
+
setFiscalNumber(String(invoice.metadata?.fiscalNumber ?? ""));
|
|
3429
3483
|
setObservations(invoice.observations ?? "");
|
|
3430
3484
|
const invoiceItems = await provider.getInvoiceItems(editId);
|
|
3431
3485
|
setItems(invoiceItems.map((it) => ({
|
|
@@ -3486,7 +3540,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3486
3540
|
accountId: counterpartyAccounting.defaultAccountId,
|
|
3487
3541
|
costCenterId: counterpartyAccounting.defaultCostCenterId
|
|
3488
3542
|
};
|
|
3489
|
-
setItems([...
|
|
3543
|
+
setItems((current) => [...current, newItem]);
|
|
3490
3544
|
setExpandedItemId(newItem._id);
|
|
3491
3545
|
setAddingItem(false);
|
|
3492
3546
|
}
|
|
@@ -3499,10 +3553,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3499
3553
|
else addItemOfKind(defaultItemKind);
|
|
3500
3554
|
}
|
|
3501
3555
|
function updateItem(id, data) {
|
|
3502
|
-
setItems(
|
|
3556
|
+
setItems((current) => current.map((it) => it._id === id ? { ...it, ...data } : it));
|
|
3503
3557
|
}
|
|
3504
3558
|
function removeItem(id) {
|
|
3505
|
-
setItems(
|
|
3559
|
+
setItems((current) => current.filter((it) => it._id !== id));
|
|
3506
3560
|
}
|
|
3507
3561
|
useEffect(() => {
|
|
3508
3562
|
if (isEdit || totalAmount <= 0 || installments.length > 0) return;
|
|
@@ -3585,7 +3639,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3585
3639
|
const inst = [];
|
|
3586
3640
|
for (let i = 0; i < count; i++) {
|
|
3587
3641
|
const due = new Date(base);
|
|
3588
|
-
due.setMonth(due.getMonth() + i + 1);
|
|
3642
|
+
due.setMonth(due.getMonth() + (count === 1 ? 0 : i + 1));
|
|
3589
3643
|
inst.push({
|
|
3590
3644
|
_id: fid(),
|
|
3591
3645
|
amount: i === 0 ? perInstallment + remainder : perInstallment,
|
|
@@ -3634,25 +3688,16 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3634
3688
|
});
|
|
3635
3689
|
if (isEdit && editId) {
|
|
3636
3690
|
await provider.updateInvoice(editId, {
|
|
3637
|
-
invoiceDate,
|
|
3638
|
-
contactId,
|
|
3639
|
-
contactName: contactName || void 0,
|
|
3640
3691
|
unitId: unitId || void 0,
|
|
3641
3692
|
fiscalNumber: fiscalNumber || void 0,
|
|
3642
|
-
observations
|
|
3643
|
-
totalAmount
|
|
3693
|
+
observations
|
|
3644
3694
|
});
|
|
3645
3695
|
if (provider.updateInstallment) {
|
|
3646
3696
|
await Promise.all(installments.map((inst, i) => {
|
|
3647
3697
|
const saved = savedMovements[i];
|
|
3648
3698
|
if (!saved || saved.status === "paid") return null;
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
if (!dueChanged && !amountChanged) return null;
|
|
3652
|
-
return provider.updateInstallment(saved.id, {
|
|
3653
|
-
...dueChanged ? { dueDate: inst.dueDate } : {},
|
|
3654
|
-
...amountChanged ? { amount: inst.amount } : {}
|
|
3655
|
-
});
|
|
3699
|
+
if (inst.dueDate === saved.dueDate) return null;
|
|
3700
|
+
return provider.updateInstallment(saved.id, { dueDate: inst.dueDate });
|
|
3656
3701
|
}).filter(Boolean));
|
|
3657
3702
|
}
|
|
3658
3703
|
onSaved?.(editId);
|
|
@@ -3750,7 +3795,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3750
3795
|
const scheduleSection = (
|
|
3751
3796
|
// Tour anchor: the guided setup points here for the installment schedule.
|
|
3752
3797
|
/* @__PURE__ */ jsx("div", { ...tourAnchor("financial.installments"), children: /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3753
|
-
hasPayments ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.scheduleLocked") }) : (
|
|
3798
|
+
hasPayments ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.scheduleLocked") }) : isEdit ? null : (
|
|
3754
3799
|
// Atalhos de parcelamento — 1x, 2x, 3x, 6x, 12x cobrem quase tudo.
|
|
3755
3800
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
|
|
3756
3801
|
"button",
|
|
@@ -3877,6 +3922,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3877
3922
|
FinancialCounterpartySearchSelect,
|
|
3878
3923
|
{
|
|
3879
3924
|
required: true,
|
|
3925
|
+
disabled: isEdit,
|
|
3880
3926
|
lookup: contactLookup,
|
|
3881
3927
|
value: contactId ?? "",
|
|
3882
3928
|
displayValue: contactName,
|
|
@@ -3909,7 +3955,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3909
3955
|
}
|
|
3910
3956
|
})();
|
|
3911
3957
|
},
|
|
3912
|
-
onCreate: contactCreate.onCreate
|
|
3958
|
+
onCreate: contactCreate.onCreate,
|
|
3959
|
+
onEdit: contactCreate.onEdit
|
|
3913
3960
|
}
|
|
3914
3961
|
),
|
|
3915
3962
|
pendingContact && !contactId && contactCreate.onCreate && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2 rounded-lg border border-dashed border-border bg-muted/30 px-2.5 py-2", children: [
|
|
@@ -3938,7 +3985,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3938
3985
|
/* @__PURE__ */ jsx(LocationSelector, { locations: availableLocations, value: unitId, onChange: setUnitId }),
|
|
3939
3986
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3940
3987
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
|
|
3941
|
-
/* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
|
|
3988
|
+
isEdit ? /* @__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" })
|
|
3942
3989
|
] }),
|
|
3943
3990
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3944
3991
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.fiscalNumber") }),
|
|
@@ -4017,6 +4064,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4017
4064
|
parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
|
|
4018
4065
|
onBack: () => onSaved?.(),
|
|
4019
4066
|
actions,
|
|
4067
|
+
banner: isEdit ? /* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning-soft px-4 py-2.5 text-xs text-warning-soft-foreground", children: t2("financial.invoiceForm.issuedLocked") }) : void 0,
|
|
4020
4068
|
sideSections,
|
|
4021
4069
|
children: [
|
|
4022
4070
|
contactSection,
|
|
@@ -4026,7 +4074,9 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4026
4074
|
"data-tour": "financial.items",
|
|
4027
4075
|
label: t2("financial.invoiceForm.items"),
|
|
4028
4076
|
addLabel: t2("financial.invoiceForm.addItem"),
|
|
4029
|
-
onAdd:
|
|
4077
|
+
onAdd: isEdit ? void 0 : startAddItem,
|
|
4078
|
+
addDisabled: !contactId,
|
|
4079
|
+
addDisabledHint: t2("financial.invoiceForm.selectContactFirst"),
|
|
4030
4080
|
count: items.length,
|
|
4031
4081
|
countLabel: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
|
|
4032
4082
|
totalLabel: t2("financial.invoiceForm.total"),
|
|
@@ -4058,6 +4108,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4058
4108
|
currency,
|
|
4059
4109
|
formatMoney: money3,
|
|
4060
4110
|
expanded: expandedItemId === item._id,
|
|
4111
|
+
readOnly: isEdit,
|
|
4061
4112
|
onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
|
|
4062
4113
|
onChange: (patch) => updateItem(item._id, patch),
|
|
4063
4114
|
onRemove: () => {
|
|
@@ -4130,7 +4181,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4130
4181
|
surcharge: t2("financial.invoiceForm.surcharge"),
|
|
4131
4182
|
adjustments: t2("financial.invoiceForm.discountSurcharge"),
|
|
4132
4183
|
removeAdjustments: t2("financial.invoiceForm.removeAdjustments"),
|
|
4133
|
-
done: t2("financial.invoiceForm.done")
|
|
4184
|
+
done: t2("financial.invoiceForm.done"),
|
|
4185
|
+
edit: t2("financial.invoiceForm.edit")
|
|
4134
4186
|
}
|
|
4135
4187
|
},
|
|
4136
4188
|
item._id
|
|
@@ -9883,7 +9935,8 @@ function createSupabaseFinancialProvider() {
|
|
|
9883
9935
|
const page = query.page ?? 1;
|
|
9884
9936
|
const pageSize = query.pageSize ?? 50;
|
|
9885
9937
|
qb = qb.range((page - 1) * pageSize, page * pageSize - 1).order("due_date", { ascending: true });
|
|
9886
|
-
const { data, count } = await qb;
|
|
9938
|
+
const { data, count, error } = await qb;
|
|
9939
|
+
if (error) throw error;
|
|
9887
9940
|
const linhas = (data ?? []).map(rowToInvoiceLine);
|
|
9888
9941
|
const titulos = [...new Set(linhas.map((l) => l.invoiceId).filter(Boolean))];
|
|
9889
9942
|
if (titulos.length) {
|
|
@@ -10062,11 +10115,14 @@ function createSupabaseFinancialProvider() {
|
|
|
10062
10115
|
if (partial.invoiceDate !== void 0) row.competence_date = partial.invoiceDate;
|
|
10063
10116
|
if (partial.contactId !== void 0) row.counterparty_person_id = partial.contactId ?? null;
|
|
10064
10117
|
if (partial.unitId !== void 0) row.unit_id = partial.unitId ?? null;
|
|
10065
|
-
if (partial.fiscalNumber !== void 0) row.number = partial.fiscalNumber ?? null;
|
|
10066
10118
|
if (partial.totalAmount !== void 0) row.total = partial.totalAmount;
|
|
10067
|
-
if (partial.contactName !== void 0) {
|
|
10119
|
+
if (partial.contactName !== void 0 || "fiscalNumber" in partial) {
|
|
10068
10120
|
const { data: cur } = await pub.from("plg_financial_invoices").select("metadata").eq("id", id).maybeSingle();
|
|
10069
|
-
row.metadata = {
|
|
10121
|
+
row.metadata = {
|
|
10122
|
+
...cur?.metadata ?? {},
|
|
10123
|
+
...partial.contactName !== void 0 ? { contactName: partial.contactName } : {},
|
|
10124
|
+
..."fiscalNumber" in partial ? { fiscalNumber: partial.fiscalNumber ?? null } : {}
|
|
10125
|
+
};
|
|
10070
10126
|
}
|
|
10071
10127
|
if (Object.keys(row).length > 0) {
|
|
10072
10128
|
const { error } = await pub.from("plg_financial_invoices").update(row).eq("id", id);
|
|
@@ -11779,6 +11835,482 @@ function createFinancialStore(provider) {
|
|
|
11779
11835
|
}));
|
|
11780
11836
|
}
|
|
11781
11837
|
|
|
11838
|
+
// src/lib/chart-tree.ts
|
|
11839
|
+
var ROOT = "";
|
|
11840
|
+
function compareCodes(a, b) {
|
|
11841
|
+
return a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" });
|
|
11842
|
+
}
|
|
11843
|
+
function buildChartTree(nodes) {
|
|
11844
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
11845
|
+
const children = /* @__PURE__ */ new Map();
|
|
11846
|
+
for (const n of nodes) {
|
|
11847
|
+
const parent = n.parentId && n.parentId !== n.id && byId.has(n.parentId) ? n.parentId : ROOT;
|
|
11848
|
+
const list = children.get(parent) ?? [];
|
|
11849
|
+
list.push(n.id);
|
|
11850
|
+
children.set(parent, list);
|
|
11851
|
+
}
|
|
11852
|
+
for (const list of children.values()) {
|
|
11853
|
+
list.sort((a, b) => compareCodes(byId.get(a).code, byId.get(b).code));
|
|
11854
|
+
}
|
|
11855
|
+
breakCycles(byId, children);
|
|
11856
|
+
return { byId, children };
|
|
11857
|
+
}
|
|
11858
|
+
function breakCycles(byId, children) {
|
|
11859
|
+
const reached = /* @__PURE__ */ new Set();
|
|
11860
|
+
const walk = (id) => {
|
|
11861
|
+
if (reached.has(id)) return;
|
|
11862
|
+
reached.add(id);
|
|
11863
|
+
for (const c of children.get(id) ?? []) walk(c);
|
|
11864
|
+
};
|
|
11865
|
+
for (const r of children.get(ROOT) ?? []) walk(r);
|
|
11866
|
+
for (const id of byId.keys()) {
|
|
11867
|
+
if (reached.has(id)) continue;
|
|
11868
|
+
const parent = byId.get(id).parentId;
|
|
11869
|
+
children.set(parent, (children.get(parent) ?? []).filter((c) => c !== id));
|
|
11870
|
+
children.set(ROOT, [...children.get(ROOT) ?? [], id].sort((a, b) => compareCodes(byId.get(a).code, byId.get(b).code)));
|
|
11871
|
+
walk(id);
|
|
11872
|
+
}
|
|
11873
|
+
}
|
|
11874
|
+
function rootIds(tree) {
|
|
11875
|
+
return tree.children.get(ROOT) ?? [];
|
|
11876
|
+
}
|
|
11877
|
+
function childIds(tree, id) {
|
|
11878
|
+
return tree.children.get(id) ?? [];
|
|
11879
|
+
}
|
|
11880
|
+
function isGroupNode(tree, node) {
|
|
11881
|
+
return node.nodeType === "group" || childIds(tree, node.id).length > 0;
|
|
11882
|
+
}
|
|
11883
|
+
function descendantIds(tree, id) {
|
|
11884
|
+
const out = /* @__PURE__ */ new Set();
|
|
11885
|
+
const stack = [...childIds(tree, id)];
|
|
11886
|
+
while (stack.length) {
|
|
11887
|
+
const next = stack.pop();
|
|
11888
|
+
if (out.has(next)) continue;
|
|
11889
|
+
out.add(next);
|
|
11890
|
+
stack.push(...childIds(tree, next));
|
|
11891
|
+
}
|
|
11892
|
+
return out;
|
|
11893
|
+
}
|
|
11894
|
+
function normalize2(text2) {
|
|
11895
|
+
return text2.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
11896
|
+
}
|
|
11897
|
+
function searchVisibleIds(tree, query) {
|
|
11898
|
+
const q = normalize2(query.trim());
|
|
11899
|
+
if (!q) return null;
|
|
11900
|
+
const visible = /* @__PURE__ */ new Set();
|
|
11901
|
+
const matches = /* @__PURE__ */ new Set();
|
|
11902
|
+
for (const n of tree.byId.values()) {
|
|
11903
|
+
if (!normalize2(`${n.code} ${n.name}`).includes(q)) continue;
|
|
11904
|
+
matches.add(n.id);
|
|
11905
|
+
let cur = n;
|
|
11906
|
+
while (cur && !visible.has(cur.id)) {
|
|
11907
|
+
visible.add(cur.id);
|
|
11908
|
+
cur = cur.parentId ? tree.byId.get(cur.parentId) : void 0;
|
|
11909
|
+
}
|
|
11910
|
+
}
|
|
11911
|
+
return { visible, matches };
|
|
11912
|
+
}
|
|
11913
|
+
function suggestNextCode(tree, parentId) {
|
|
11914
|
+
const parent = parentId ? tree.byId.get(parentId) : void 0;
|
|
11915
|
+
const siblings = (parentId ? childIds(tree, parentId) : rootIds(tree)).map((id) => tree.byId.get(id).code).filter((c) => !c.startsWith("("));
|
|
11916
|
+
const prefix = parent ? `${parent.code}.` : "";
|
|
11917
|
+
let best = null;
|
|
11918
|
+
for (const code of siblings) {
|
|
11919
|
+
const m = /^(.*?)(\d+)$/.exec(code);
|
|
11920
|
+
if (!m) continue;
|
|
11921
|
+
const n2 = Number(m[2]);
|
|
11922
|
+
if (!best || n2 > best.n) best = { n: n2, width: m[2].length, head: m[1] };
|
|
11923
|
+
}
|
|
11924
|
+
const taken = new Set([...tree.byId.values()].map((n2) => n2.code));
|
|
11925
|
+
let candidate;
|
|
11926
|
+
if (best) {
|
|
11927
|
+
let n2 = best.n + 1;
|
|
11928
|
+
do {
|
|
11929
|
+
candidate = `${best.head}${String(n2).padStart(best.width, "0")}`;
|
|
11930
|
+
n2++;
|
|
11931
|
+
} while (taken.has(candidate));
|
|
11932
|
+
return candidate;
|
|
11933
|
+
}
|
|
11934
|
+
let n = 1;
|
|
11935
|
+
do {
|
|
11936
|
+
candidate = parent ? `${prefix}${String(n).padStart(2, "0")}` : String(n);
|
|
11937
|
+
n++;
|
|
11938
|
+
} while (taken.has(candidate));
|
|
11939
|
+
return candidate;
|
|
11940
|
+
}
|
|
11941
|
+
var NATURES = ["revenue", "expense", "asset", "liability", "equity"];
|
|
11942
|
+
var NATURE_VARIANT = {
|
|
11943
|
+
revenue: "success",
|
|
11944
|
+
expense: "destructive",
|
|
11945
|
+
asset: "info",
|
|
11946
|
+
liability: "warning",
|
|
11947
|
+
equity: "magic"
|
|
11948
|
+
};
|
|
11949
|
+
var PAGE = 1e3;
|
|
11950
|
+
async function loadAll(provider) {
|
|
11951
|
+
const rows = [];
|
|
11952
|
+
for (let page = 1; ; page++) {
|
|
11953
|
+
const res = await provider.list({ page, pageSize: PAGE, sortBy: "code", sortDir: "asc", countMode: "none" });
|
|
11954
|
+
rows.push(...res.data);
|
|
11955
|
+
if (res.data.length < PAGE) return rows;
|
|
11956
|
+
}
|
|
11957
|
+
}
|
|
11958
|
+
function errorMessage(err) {
|
|
11959
|
+
if (err && typeof err === "object" && "message" in err) return String(err.message);
|
|
11960
|
+
return String(err);
|
|
11961
|
+
}
|
|
11962
|
+
function ChartOfAccountsTree({ entity, mockData, dataProvider }) {
|
|
11963
|
+
const t2 = useTranslation();
|
|
11964
|
+
const can = usePermissionOptional();
|
|
11965
|
+
const canCreate = can("financial", "create");
|
|
11966
|
+
const canEdit = can("financial", "edit");
|
|
11967
|
+
const canDelete = can("financial", "delete");
|
|
11968
|
+
const provider = React20__default.useMemo(
|
|
11969
|
+
() => dataProvider ?? resolveDataProvider(entity, mockData ?? []),
|
|
11970
|
+
[entity, mockData, dataProvider]
|
|
11971
|
+
);
|
|
11972
|
+
const table = entity.data?.table;
|
|
11973
|
+
const [nodes, setNodes] = React20__default.useState(null);
|
|
11974
|
+
const [loadError, setLoadError] = React20__default.useState(null);
|
|
11975
|
+
const [expanded, setExpanded] = React20__default.useState(/* @__PURE__ */ new Set());
|
|
11976
|
+
const [query, setQuery] = React20__default.useState("");
|
|
11977
|
+
const [showInactive, setShowInactive] = React20__default.useState(true);
|
|
11978
|
+
const [editing, setEditing] = React20__default.useState(null);
|
|
11979
|
+
const [confirmDelete, setConfirmDelete] = React20__default.useState(null);
|
|
11980
|
+
const [busy, setBusy] = React20__default.useState(false);
|
|
11981
|
+
const initialized = React20__default.useRef(false);
|
|
11982
|
+
const reload = React20__default.useCallback(async () => {
|
|
11983
|
+
try {
|
|
11984
|
+
const rows = await loadAll(provider);
|
|
11985
|
+
setNodes(rows);
|
|
11986
|
+
setLoadError(null);
|
|
11987
|
+
if (!initialized.current) {
|
|
11988
|
+
initialized.current = true;
|
|
11989
|
+
const tree2 = buildChartTree(rows);
|
|
11990
|
+
setExpanded(new Set(rootIds(tree2)));
|
|
11991
|
+
}
|
|
11992
|
+
} catch (err) {
|
|
11993
|
+
setLoadError(errorMessage(err));
|
|
11994
|
+
setNodes((prev) => prev ?? []);
|
|
11995
|
+
}
|
|
11996
|
+
}, [provider]);
|
|
11997
|
+
React20__default.useEffect(() => {
|
|
11998
|
+
void reload();
|
|
11999
|
+
}, [reload]);
|
|
12000
|
+
const shown = React20__default.useMemo(
|
|
12001
|
+
() => (nodes ?? []).filter((n) => showInactive || n.isActive),
|
|
12002
|
+
[nodes, showInactive]
|
|
12003
|
+
);
|
|
12004
|
+
const tree = React20__default.useMemo(() => buildChartTree(shown), [shown]);
|
|
12005
|
+
const fullTree = React20__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
|
|
12006
|
+
const search = React20__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
|
|
12007
|
+
const groupIds = React20__default.useMemo(
|
|
12008
|
+
() => [...tree.byId.values()].filter((n) => isGroupNode(tree, n)).map((n) => n.id),
|
|
12009
|
+
[tree]
|
|
12010
|
+
);
|
|
12011
|
+
function toggle(id) {
|
|
12012
|
+
setExpanded((prev) => {
|
|
12013
|
+
const next = new Set(prev);
|
|
12014
|
+
if (next.has(id)) next.delete(id);
|
|
12015
|
+
else next.add(id);
|
|
12016
|
+
return next;
|
|
12017
|
+
});
|
|
12018
|
+
}
|
|
12019
|
+
function startCreate(parentId) {
|
|
12020
|
+
const parent = parentId ? fullTree.byId.get(parentId) : void 0;
|
|
12021
|
+
setConfirmDelete(null);
|
|
12022
|
+
setEditing({
|
|
12023
|
+
mode: "create",
|
|
12024
|
+
parentId,
|
|
12025
|
+
draft: {
|
|
12026
|
+
code: suggestNextCode(fullTree, parentId),
|
|
12027
|
+
name: "",
|
|
12028
|
+
nodeType: "leaf",
|
|
12029
|
+
accountType: parent?.accountType ?? "",
|
|
12030
|
+
parentId
|
|
12031
|
+
}
|
|
12032
|
+
});
|
|
12033
|
+
if (parentId) setExpanded((prev) => new Set(prev).add(parentId));
|
|
12034
|
+
}
|
|
12035
|
+
function startEdit(node) {
|
|
12036
|
+
setConfirmDelete(null);
|
|
12037
|
+
setEditing({
|
|
12038
|
+
mode: "edit",
|
|
12039
|
+
id: node.id,
|
|
12040
|
+
draft: {
|
|
12041
|
+
code: node.code,
|
|
12042
|
+
name: node.name,
|
|
12043
|
+
nodeType: node.nodeType === "group" ? "group" : "leaf",
|
|
12044
|
+
accountType: node.accountType ?? "",
|
|
12045
|
+
parentId: node.parentId ?? null
|
|
12046
|
+
}
|
|
12047
|
+
});
|
|
12048
|
+
}
|
|
12049
|
+
function validate(draft, selfId) {
|
|
12050
|
+
const code = draft.code.trim();
|
|
12051
|
+
if (!code) return t2("financial.chartTree.error.codeRequired");
|
|
12052
|
+
if (!draft.name.trim()) return t2("financial.chartTree.error.nameRequired");
|
|
12053
|
+
const clash = (nodes ?? []).find((n) => n.code === code && n.id !== selfId);
|
|
12054
|
+
if (clash) return t2("financial.chartTree.error.codeTaken", { code, name: clash.name });
|
|
12055
|
+
if (selfId && draft.nodeType === "leaf" && childIds(fullTree, selfId).length > 0) {
|
|
12056
|
+
return t2("financial.chartTree.error.leafWithChildren");
|
|
12057
|
+
}
|
|
12058
|
+
return null;
|
|
12059
|
+
}
|
|
12060
|
+
async function run(action, success) {
|
|
12061
|
+
setBusy(true);
|
|
12062
|
+
try {
|
|
12063
|
+
await action();
|
|
12064
|
+
if (table) emitDataChanged({ table });
|
|
12065
|
+
toast.success(success);
|
|
12066
|
+
await reload();
|
|
12067
|
+
return true;
|
|
12068
|
+
} catch (err) {
|
|
12069
|
+
toast.error(t2("financial.chartTree.error.save"), { description: errorMessage(err) });
|
|
12070
|
+
return false;
|
|
12071
|
+
} finally {
|
|
12072
|
+
setBusy(false);
|
|
12073
|
+
}
|
|
12074
|
+
}
|
|
12075
|
+
async function submit() {
|
|
12076
|
+
if (!editing) return;
|
|
12077
|
+
const { draft } = editing;
|
|
12078
|
+
const invalid = validate(draft, editing.mode === "edit" ? editing.id : void 0);
|
|
12079
|
+
if (invalid) {
|
|
12080
|
+
toast.error(invalid);
|
|
12081
|
+
return;
|
|
12082
|
+
}
|
|
12083
|
+
const payload = {
|
|
12084
|
+
code: draft.code.trim(),
|
|
12085
|
+
name: draft.name.trim(),
|
|
12086
|
+
nodeType: draft.nodeType,
|
|
12087
|
+
accountType: draft.accountType || null,
|
|
12088
|
+
parentId: draft.parentId
|
|
12089
|
+
};
|
|
12090
|
+
const ok = editing.mode === "create" ? await run(() => provider.create({ ...payload, isActive: true }), t2("financial.chartTree.created", { name: payload.name })) : await run(() => provider.update(editing.id, payload), t2("financial.chartTree.updated", { name: payload.name }));
|
|
12091
|
+
if (ok) setEditing(null);
|
|
12092
|
+
}
|
|
12093
|
+
async function setActive(node, isActive) {
|
|
12094
|
+
await run(
|
|
12095
|
+
() => provider.update(node.id, { isActive }),
|
|
12096
|
+
t2(isActive ? "financial.chartTree.reactivated" : "financial.chartTree.deactivated", { name: node.name })
|
|
12097
|
+
);
|
|
12098
|
+
}
|
|
12099
|
+
async function remove(node) {
|
|
12100
|
+
const ok = await run(() => provider.remove(node.id), t2("financial.chartTree.deleted", { name: node.name }));
|
|
12101
|
+
if (ok) setConfirmDelete(null);
|
|
12102
|
+
}
|
|
12103
|
+
const parentOptions = React20__default.useCallback((q, selfId) => {
|
|
12104
|
+
const blocked = selfId ? descendantIds(fullTree, selfId).add(selfId) : /* @__PURE__ */ new Set();
|
|
12105
|
+
const needle = q.trim().toLowerCase();
|
|
12106
|
+
return [...fullTree.byId.values()].filter((n) => !blocked.has(n.id) && isGroupNode(fullTree, n)).filter((n) => !needle || accountLabel(n).toLowerCase().includes(needle)).sort((a, b) => a.code.localeCompare(b.code, void 0, { numeric: true })).slice(0, 50).map((n) => ({ id: n.id, label: accountLabel(n) }));
|
|
12107
|
+
}, [fullTree]);
|
|
12108
|
+
if (nodes === null) {
|
|
12109
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-2", "aria-busy": "true", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" }, i)) });
|
|
12110
|
+
}
|
|
12111
|
+
const roots = rootIds(tree).filter((id) => !search || search.visible.has(id));
|
|
12112
|
+
const inactiveCount = (nodes ?? []).filter((n) => !n.isActive).length;
|
|
12113
|
+
function renderForm(draft, level, selfId) {
|
|
12114
|
+
const update = (patch) => setEditing((prev) => prev ? { ...prev, draft: { ...prev.draft, ...patch } } : prev);
|
|
12115
|
+
const parent = draft.parentId ? fullTree.byId.get(draft.parentId) : void 0;
|
|
12116
|
+
return (
|
|
12117
|
+
// Not a <form>: Enter would "click" the first button inside SearchSelect
|
|
12118
|
+
// (its clear action) and wipe the parent instead of saving.
|
|
12119
|
+
/* @__PURE__ */ jsxs(
|
|
12120
|
+
"div",
|
|
12121
|
+
{
|
|
12122
|
+
role: "group",
|
|
12123
|
+
className: "rounded-md border border-border bg-muted/40 p-3 space-y-3",
|
|
12124
|
+
style: { marginLeft: Math.min(level, 8) * 16 },
|
|
12125
|
+
onKeyDown: (e) => {
|
|
12126
|
+
if (e.key === "Escape") setEditing(null);
|
|
12127
|
+
if (e.key === "Enter" && e.target.dataset.submitOnEnter) {
|
|
12128
|
+
e.preventDefault();
|
|
12129
|
+
void submit();
|
|
12130
|
+
}
|
|
12131
|
+
},
|
|
12132
|
+
children: [
|
|
12133
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-[8rem_1fr]", children: [
|
|
12134
|
+
/* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
|
|
12135
|
+
t2("financial.chartTree.field.code"),
|
|
12136
|
+
/* @__PURE__ */ jsx(Input, { "data-submit-on-enter": "1", value: draft.code, onChange: (e) => update({ code: e.target.value }), required: true })
|
|
12137
|
+
] }),
|
|
12138
|
+
/* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
|
|
12139
|
+
t2("financial.chartTree.field.name"),
|
|
12140
|
+
/* @__PURE__ */ jsx(Input, { "data-submit-on-enter": "1", autoFocus: true, value: draft.name, onChange: (e) => update({ name: e.target.value }), required: true })
|
|
12141
|
+
] })
|
|
12142
|
+
] }),
|
|
12143
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [
|
|
12144
|
+
/* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
|
|
12145
|
+
t2("financial.chartTree.field.structure"),
|
|
12146
|
+
/* @__PURE__ */ jsxs(
|
|
12147
|
+
"select",
|
|
12148
|
+
{
|
|
12149
|
+
className: "flex h-9 w-full rounded-md border border-input bg-background px-2 text-sm text-foreground",
|
|
12150
|
+
value: draft.nodeType,
|
|
12151
|
+
onChange: (e) => update({ nodeType: e.target.value }),
|
|
12152
|
+
children: [
|
|
12153
|
+
/* @__PURE__ */ jsx("option", { value: "leaf", children: t2("financial.chartTree.leaf") }),
|
|
12154
|
+
/* @__PURE__ */ jsx("option", { value: "group", children: t2("financial.chartTree.group") })
|
|
12155
|
+
]
|
|
12156
|
+
}
|
|
12157
|
+
)
|
|
12158
|
+
] }),
|
|
12159
|
+
/* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
|
|
12160
|
+
t2("financial.chartTree.field.nature"),
|
|
12161
|
+
/* @__PURE__ */ jsxs(
|
|
12162
|
+
"select",
|
|
12163
|
+
{
|
|
12164
|
+
className: "flex h-9 w-full rounded-md border border-input bg-background px-2 text-sm text-foreground",
|
|
12165
|
+
value: draft.accountType,
|
|
12166
|
+
onChange: (e) => update({ accountType: e.target.value }),
|
|
12167
|
+
children: [
|
|
12168
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.chartTree.nature.none") }),
|
|
12169
|
+
NATURES.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: t2(`financial.chartTree.nature.${n}`) }, n))
|
|
12170
|
+
]
|
|
12171
|
+
}
|
|
12172
|
+
)
|
|
12173
|
+
] }),
|
|
12174
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
|
|
12175
|
+
t2("financial.chartTree.field.parent"),
|
|
12176
|
+
/* @__PURE__ */ jsx(
|
|
12177
|
+
SearchSelect,
|
|
12178
|
+
{
|
|
12179
|
+
value: draft.parentId ?? void 0,
|
|
12180
|
+
displayValue: parent ? accountLabel(parent) : void 0,
|
|
12181
|
+
onChange: (id) => update({ parentId: id || null }),
|
|
12182
|
+
onSearch: (q) => parentOptions(q, selfId),
|
|
12183
|
+
minChars: 0,
|
|
12184
|
+
placeholder: t2("financial.chartTree.rootLevel"),
|
|
12185
|
+
noResultsLabel: t2("financial.chartTree.noGroups"),
|
|
12186
|
+
searchingLabel: t2("financial.chartTree.searching"),
|
|
12187
|
+
clearLabel: t2("financial.chartTree.rootLevel")
|
|
12188
|
+
}
|
|
12189
|
+
)
|
|
12190
|
+
] })
|
|
12191
|
+
] }),
|
|
12192
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [
|
|
12193
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setEditing(null), disabled: busy, children: t2("financial.chartTree.cancel") }),
|
|
12194
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", disabled: busy, onClick: () => void submit(), children: busy ? t2("financial.chartTree.saving") : t2("financial.chartTree.save") })
|
|
12195
|
+
] })
|
|
12196
|
+
]
|
|
12197
|
+
}
|
|
12198
|
+
)
|
|
12199
|
+
);
|
|
12200
|
+
}
|
|
12201
|
+
function renderNode(id, level) {
|
|
12202
|
+
const node = tree.byId.get(id);
|
|
12203
|
+
const kids = childIds(tree, id).filter((c) => !search || search.visible.has(c));
|
|
12204
|
+
const group = isGroupNode(tree, node);
|
|
12205
|
+
const open = search ? true : expanded.has(id);
|
|
12206
|
+
const isEditingThis = editing?.mode === "edit" && editing.id === id;
|
|
12207
|
+
const creatingHere = editing?.mode === "create" && editing.parentId === id;
|
|
12208
|
+
const isMatch = search?.matches.has(id);
|
|
12209
|
+
const hasAnyChildren = childIds(fullTree, id).length > 0;
|
|
12210
|
+
const FolderIcon = open ? FolderOpen : Folder;
|
|
12211
|
+
return /* @__PURE__ */ jsxs("li", { role: "treeitem", "aria-level": level + 1, "aria-expanded": group ? open : void 0, children: [
|
|
12212
|
+
isEditingThis && editing ? renderForm(editing.draft, level, id) : /* @__PURE__ */ jsxs(
|
|
12213
|
+
"div",
|
|
12214
|
+
{
|
|
12215
|
+
className: "group/row flex min-h-10 items-center gap-2 rounded-md px-2 py-1 hover:bg-muted/60 " + (node.isActive ? "" : "opacity-60"),
|
|
12216
|
+
style: { paddingLeft: 8 + Math.min(level, 8) * 16 },
|
|
12217
|
+
children: [
|
|
12218
|
+
/* @__PURE__ */ jsx(
|
|
12219
|
+
"button",
|
|
12220
|
+
{
|
|
12221
|
+
type: "button",
|
|
12222
|
+
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted " + (kids.length ? "" : "invisible"),
|
|
12223
|
+
onClick: () => toggle(id),
|
|
12224
|
+
"aria-label": open ? t2("financial.chartTree.collapse") : t2("financial.chartTree.expand"),
|
|
12225
|
+
disabled: !!search,
|
|
12226
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 transition-transform " + (open ? "rotate-90" : "") })
|
|
12227
|
+
}
|
|
12228
|
+
),
|
|
12229
|
+
group ? /* @__PURE__ */ jsx(FolderIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
12230
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-xs tabular-nums text-muted-foreground", children: node.code }),
|
|
12231
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm " + (group ? "font-semibold" : "") + (isMatch ? " text-primary" : ""), children: node.name }),
|
|
12232
|
+
/* @__PURE__ */ jsxs("span", { className: "hidden shrink-0 items-center gap-1 sm:flex", children: [
|
|
12233
|
+
node.accountType && /* @__PURE__ */ jsx(Badge, { variant: NATURE_VARIANT[node.accountType] ?? "default", dot: false, children: t2(`financial.chartTree.nature.${node.accountType}`) }),
|
|
12234
|
+
!node.isActive && /* @__PURE__ */ jsx(Badge, { variant: "outline", children: t2("financial.chartTree.inactive") })
|
|
12235
|
+
] }),
|
|
12236
|
+
confirmDelete === id ? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
|
|
12237
|
+
/* @__PURE__ */ jsx("span", { className: "hidden text-xs text-muted-foreground md:inline", children: t2("financial.chartTree.confirmDelete") }),
|
|
12238
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "destructive", disabled: busy, onClick: () => void remove(node), children: t2("financial.chartTree.delete") }),
|
|
12239
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onClick: () => setConfirmDelete(null), children: t2("financial.chartTree.cancel") })
|
|
12240
|
+
] }) : /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center justify-end gap-0.5 sm:w-32 sm:opacity-0 sm:group-hover/row:opacity-100 sm:focus-within:opacity-100", children: [
|
|
12241
|
+
canCreate && group && node.isActive && /* @__PURE__ */ jsx(Button, { size: "icon", variant: "ghost", title: t2("financial.chartTree.addChild"), "aria-label": t2("financial.chartTree.addChild"), onClick: () => startCreate(id), disabled: busy, children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }) }),
|
|
12242
|
+
canEdit && /* @__PURE__ */ jsx(Button, { size: "icon", variant: "ghost", title: t2("financial.chartTree.edit"), "aria-label": t2("financial.chartTree.edit"), onClick: () => startEdit(node), disabled: busy, children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" }) }),
|
|
12243
|
+
canEdit && !hasAnyChildren && (node.isActive ? /* @__PURE__ */ jsx(Button, { size: "icon", variant: "ghost", title: t2("financial.chartTree.deactivate"), "aria-label": t2("financial.chartTree.deactivate"), onClick: () => void setActive(node, false), disabled: busy, children: /* @__PURE__ */ jsx(Ban, { className: "h-4 w-4" }) }) : null),
|
|
12244
|
+
canEdit && !node.isActive && /* @__PURE__ */ jsx(Button, { size: "icon", variant: "ghost", title: t2("financial.chartTree.reactivate"), "aria-label": t2("financial.chartTree.reactivate"), onClick: () => void setActive(node, true), disabled: busy, children: /* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" }) }),
|
|
12245
|
+
canDelete && !hasAnyChildren && /* @__PURE__ */ jsx(Button, { size: "icon", variant: "ghost", title: t2("financial.chartTree.delete"), "aria-label": t2("financial.chartTree.delete"), onClick: () => {
|
|
12246
|
+
setEditing(null);
|
|
12247
|
+
setConfirmDelete(id);
|
|
12248
|
+
}, disabled: busy, children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }) })
|
|
12249
|
+
] })
|
|
12250
|
+
]
|
|
12251
|
+
}
|
|
12252
|
+
),
|
|
12253
|
+
open && (kids.length > 0 || creatingHere) && /* @__PURE__ */ jsxs("ul", { role: "group", className: "space-y-0.5", children: [
|
|
12254
|
+
creatingHere && editing && /* @__PURE__ */ jsx("li", { children: renderForm(editing.draft, level + 1) }),
|
|
12255
|
+
kids.map((c) => renderNode(c, level + 1))
|
|
12256
|
+
] })
|
|
12257
|
+
] }, id);
|
|
12258
|
+
}
|
|
12259
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
12260
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
12261
|
+
/* @__PURE__ */ jsxs("div", { className: "relative min-w-[12rem] flex-1", children: [
|
|
12262
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
|
|
12263
|
+
/* @__PURE__ */ jsx(
|
|
12264
|
+
Input,
|
|
12265
|
+
{
|
|
12266
|
+
value: query,
|
|
12267
|
+
onChange: (e) => setQuery(e.target.value),
|
|
12268
|
+
placeholder: t2("financial.chartTree.searchPlaceholder"),
|
|
12269
|
+
className: "pl-8 pr-8",
|
|
12270
|
+
"aria-label": t2("financial.chartTree.searchPlaceholder")
|
|
12271
|
+
}
|
|
12272
|
+
),
|
|
12273
|
+
query && /* @__PURE__ */ jsx(
|
|
12274
|
+
"button",
|
|
12275
|
+
{
|
|
12276
|
+
type: "button",
|
|
12277
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
12278
|
+
onClick: () => setQuery(""),
|
|
12279
|
+
"aria-label": t2("financial.chartTree.clearSearch"),
|
|
12280
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
12281
|
+
}
|
|
12282
|
+
)
|
|
12283
|
+
] }),
|
|
12284
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setExpanded(new Set(groupIds)), disabled: !!search, title: t2("financial.chartTree.expandAll"), "aria-label": t2("financial.chartTree.expandAll"), children: [
|
|
12285
|
+
/* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-4 w-4" }),
|
|
12286
|
+
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t2("financial.chartTree.expandAll") })
|
|
12287
|
+
] }),
|
|
12288
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setExpanded(/* @__PURE__ */ new Set()), disabled: !!search, title: t2("financial.chartTree.collapseAll"), "aria-label": t2("financial.chartTree.collapseAll"), children: [
|
|
12289
|
+
/* @__PURE__ */ jsx(ChevronsDownUp, { className: "h-4 w-4" }),
|
|
12290
|
+
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t2("financial.chartTree.collapseAll") })
|
|
12291
|
+
] }),
|
|
12292
|
+
canCreate && /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => startCreate(null), disabled: busy, children: [
|
|
12293
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
12294
|
+
t2("financial.chartTree.addRoot")
|
|
12295
|
+
] })
|
|
12296
|
+
] }),
|
|
12297
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground", children: [
|
|
12298
|
+
/* @__PURE__ */ jsx("span", { children: search ? t2(search.matches.size === 1 ? "financial.chartTree.matchesOne" : "financial.chartTree.matches", { count: search.matches.size }) : t2(tree.byId.size === 1 ? "financial.chartTree.countOne" : "financial.chartTree.count", { count: tree.byId.size }) }),
|
|
12299
|
+
inactiveCount > 0 && /* @__PURE__ */ jsxs("label", { className: "inline-flex cursor-pointer items-center gap-1.5", children: [
|
|
12300
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: showInactive, onChange: (e) => setShowInactive(e.target.checked) }),
|
|
12301
|
+
t2("financial.chartTree.showInactive", { count: inactiveCount })
|
|
12302
|
+
] })
|
|
12303
|
+
] }),
|
|
12304
|
+
loadError && /* @__PURE__ */ jsxs("p", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive", children: [
|
|
12305
|
+
t2("financial.chartTree.error.load"),
|
|
12306
|
+
" ",
|
|
12307
|
+
loadError
|
|
12308
|
+
] }),
|
|
12309
|
+
editing?.mode === "create" && editing.parentId === null && renderForm(editing.draft, 0),
|
|
12310
|
+
roots.length === 0 && !(editing?.mode === "create" && editing.parentId === null) ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border p-8 text-center text-sm text-muted-foreground", children: search ? t2("financial.chartTree.noMatches") : t2("financial.chartTree.empty") }) : /* @__PURE__ */ jsx("ul", { role: "tree", "aria-label": t2("registry.chart-of-accounts"), className: "space-y-0.5", children: roots.map((id) => renderNode(id, 0)) })
|
|
12311
|
+
] });
|
|
12312
|
+
}
|
|
12313
|
+
|
|
11782
12314
|
// src/data/registries.ts
|
|
11783
12315
|
var paymentMethodTypeEntity = {
|
|
11784
12316
|
name: "Payment Type",
|
|
@@ -11846,6 +12378,24 @@ var chartOfAccountsEntity = {
|
|
|
11846
12378
|
],
|
|
11847
12379
|
data: { table: T.chartOfAccounts, tenantScoped: true }
|
|
11848
12380
|
};
|
|
12381
|
+
var chartOfAccountsSeed = [
|
|
12382
|
+
{ id: "coa-1", code: "1", name: "Revenue", nodeType: "group", accountType: "revenue", isActive: true },
|
|
12383
|
+
{ id: "coa-1.1", code: "1.1", name: "Sales Revenue", nodeType: "group", accountType: "revenue", parentId: "coa-1", isActive: true },
|
|
12384
|
+
{ id: "coa-1.1.01", code: "1.1.01", name: "Services", nodeType: "leaf", accountType: "revenue", parentId: "coa-1.1", isActive: true },
|
|
12385
|
+
{ id: "coa-1.1.02", code: "1.1.02", name: "Products", nodeType: "leaf", accountType: "revenue", parentId: "coa-1.1", isActive: true },
|
|
12386
|
+
{ id: "coa-2", code: "2", name: "Other Revenue", nodeType: "group", accountType: "revenue", isActive: true },
|
|
12387
|
+
{ id: "coa-2.1", code: "2.1", name: "Financial Income", nodeType: "leaf", accountType: "revenue", parentId: "coa-2", isActive: true },
|
|
12388
|
+
{ id: "coa-3", code: "3", name: "Operational Costs", nodeType: "group", accountType: "expense", isActive: true },
|
|
12389
|
+
{ id: "coa-3.1", code: "3.1", name: "Materials & Supplies", nodeType: "group", accountType: "expense", parentId: "coa-3", isActive: true },
|
|
12390
|
+
{ id: "coa-3.1.01", code: "3.1.01", name: "Raw Materials", nodeType: "leaf", accountType: "expense", parentId: "coa-3.1", isActive: true },
|
|
12391
|
+
{ id: "coa-3.1.02", code: "3.1.02", name: "Consumables", nodeType: "leaf", accountType: "expense", parentId: "coa-3.1", isActive: true },
|
|
12392
|
+
{ id: "coa-4", code: "4", name: "Expenses", nodeType: "group", accountType: "expense", isActive: true },
|
|
12393
|
+
{ id: "coa-4.1", code: "4.1", name: "Rent", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
|
|
12394
|
+
{ id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
|
|
12395
|
+
{ id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
|
|
12396
|
+
];
|
|
12397
|
+
var ChartOfAccountsRegistryView = (props) => React20__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
|
|
12398
|
+
ChartOfAccountsRegistryView.displayName = "ChartOfAccountsRegistryView";
|
|
11849
12399
|
var costCenterEntity = {
|
|
11850
12400
|
name: "Cost Center",
|
|
11851
12401
|
namePlural: "Cost Centers",
|
|
@@ -11973,22 +12523,8 @@ var financialRegistries = [
|
|
|
11973
12523
|
icon: "BookOpen",
|
|
11974
12524
|
description: "Hierarchical chart of accounts for categorizing transactions",
|
|
11975
12525
|
display: "tree",
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
{ id: "coa-1.1", code: "1.1", name: "Sales Revenue", nodeType: "group", accountType: "revenue", parentId: "coa-1", isActive: true },
|
|
11979
|
-
{ id: "coa-1.1.01", code: "1.1.01", name: "Services", nodeType: "leaf", accountType: "revenue", parentId: "coa-1.1", isActive: true },
|
|
11980
|
-
{ id: "coa-1.1.02", code: "1.1.02", name: "Products", nodeType: "leaf", accountType: "revenue", parentId: "coa-1.1", isActive: true },
|
|
11981
|
-
{ id: "coa-2", code: "2", name: "Other Revenue", nodeType: "group", accountType: "revenue", isActive: true },
|
|
11982
|
-
{ id: "coa-2.1", code: "2.1", name: "Financial Income", nodeType: "leaf", accountType: "revenue", parentId: "coa-2", isActive: true },
|
|
11983
|
-
{ id: "coa-3", code: "3", name: "Operational Costs", nodeType: "group", accountType: "expense", isActive: true },
|
|
11984
|
-
{ id: "coa-3.1", code: "3.1", name: "Materials & Supplies", nodeType: "group", accountType: "expense", parentId: "coa-3", isActive: true },
|
|
11985
|
-
{ id: "coa-3.1.01", code: "3.1.01", name: "Raw Materials", nodeType: "leaf", accountType: "expense", parentId: "coa-3.1", isActive: true },
|
|
11986
|
-
{ id: "coa-3.1.02", code: "3.1.02", name: "Consumables", nodeType: "leaf", accountType: "expense", parentId: "coa-3.1", isActive: true },
|
|
11987
|
-
{ id: "coa-4", code: "4", name: "Expenses", nodeType: "group", accountType: "expense", isActive: true },
|
|
11988
|
-
{ id: "coa-4.1", code: "4.1", name: "Rent", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
|
|
11989
|
-
{ id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
|
|
11990
|
-
{ id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
|
|
11991
|
-
]
|
|
12526
|
+
component: ChartOfAccountsRegistryView,
|
|
12527
|
+
seedData: chartOfAccountsSeed
|
|
11992
12528
|
},
|
|
11993
12529
|
{
|
|
11994
12530
|
id: "cost-centers",
|
|
@@ -12751,6 +13287,8 @@ var en = {
|
|
|
12751
13287
|
"financial.invoice.reverseReason": "Reason for reversal",
|
|
12752
13288
|
"financial.invoice.paymentSchedule": "Payment Schedule",
|
|
12753
13289
|
"financial.invoiceForm.scheduleLocked": "Some installments are paid \u2014 the schedule can no longer be regenerated.",
|
|
13290
|
+
"financial.invoiceForm.issuedLocked": "This bill is issued: amount, items, supplier and date are locked. To change them, cancel it and create a new one. Notes, unit, document number and due dates can still be edited.",
|
|
13291
|
+
"financial.invoiceForm.edit": "Edit",
|
|
12754
13292
|
"financial.invoice.payment": "Payment",
|
|
12755
13293
|
"financial.invoice.balance": "Remaining",
|
|
12756
13294
|
"financial.invoice.details": "Details",
|
|
@@ -12887,6 +13425,9 @@ var en = {
|
|
|
12887
13425
|
"financial.payment.cancel": "Cancel",
|
|
12888
13426
|
"financial.payment.card": "Card",
|
|
12889
13427
|
"financial.payment.cardBrand": "Card Brand",
|
|
13428
|
+
"financial.payment.whichCard": "Which card",
|
|
13429
|
+
"financial.payment.noCompanyCards": "No company card registered. A card payment needs the card on file.",
|
|
13430
|
+
"financial.payment.registerCard": 'Register a card (account type "Card payable")',
|
|
12890
13431
|
"financial.payment.cardInstallments": "Installments",
|
|
12891
13432
|
"financial.payment.date": "Date",
|
|
12892
13433
|
"financial.payment.installment": "Installment #{{number}}",
|
|
@@ -13221,7 +13762,59 @@ var en = {
|
|
|
13221
13762
|
"financial.quickTx.descriptionPlaceholder": "e.g. Groceries, Salary\u2026",
|
|
13222
13763
|
"financial.quickTx.enterAmount": "Enter an amount",
|
|
13223
13764
|
"financial.quickTx.save": "Save",
|
|
13224
|
-
"financial.quickTx.saving": "Saving\u2026"
|
|
13765
|
+
"financial.quickTx.saving": "Saving\u2026",
|
|
13766
|
+
// Chart of accounts tree
|
|
13767
|
+
"financial.chartTree.searchPlaceholder": "Search by code or name",
|
|
13768
|
+
"financial.chartTree.clearSearch": "Clear search",
|
|
13769
|
+
"financial.chartTree.expandAll": "Expand all",
|
|
13770
|
+
"financial.chartTree.collapseAll": "Collapse all",
|
|
13771
|
+
"financial.chartTree.expand": "Expand",
|
|
13772
|
+
"financial.chartTree.collapse": "Collapse",
|
|
13773
|
+
"financial.chartTree.addRoot": "New root account",
|
|
13774
|
+
"financial.chartTree.addChild": "New account inside this one",
|
|
13775
|
+
"financial.chartTree.edit": "Edit",
|
|
13776
|
+
"financial.chartTree.deactivate": "Deactivate",
|
|
13777
|
+
"financial.chartTree.reactivate": "Reactivate",
|
|
13778
|
+
"financial.chartTree.delete": "Delete",
|
|
13779
|
+
"financial.chartTree.confirmDelete": "Delete this account?",
|
|
13780
|
+
"financial.chartTree.cancel": "Cancel",
|
|
13781
|
+
"financial.chartTree.save": "Save",
|
|
13782
|
+
"financial.chartTree.saving": "Saving\u2026",
|
|
13783
|
+
"financial.chartTree.searching": "Searching\u2026",
|
|
13784
|
+
"financial.chartTree.count": "{{count}} accounts",
|
|
13785
|
+
"financial.chartTree.countOne": "1 account",
|
|
13786
|
+
"financial.chartTree.matchesOne": "1 account found",
|
|
13787
|
+
"financial.chartTree.matches": "{{count}} accounts found",
|
|
13788
|
+
"financial.chartTree.showInactive": "Show inactive ({{count}})",
|
|
13789
|
+
"financial.chartTree.inactive": "Inactive",
|
|
13790
|
+
"financial.chartTree.empty": "No accounts yet. Create the first root account.",
|
|
13791
|
+
"financial.chartTree.noMatches": "No account matches the search.",
|
|
13792
|
+
"financial.chartTree.noGroups": "No group found",
|
|
13793
|
+
"financial.chartTree.rootLevel": "Root level (no parent)",
|
|
13794
|
+
"financial.chartTree.group": "Group (holds other accounts)",
|
|
13795
|
+
"financial.chartTree.leaf": "Account (receives postings)",
|
|
13796
|
+
"financial.chartTree.field.code": "Code",
|
|
13797
|
+
"financial.chartTree.field.name": "Name",
|
|
13798
|
+
"financial.chartTree.field.structure": "Structure",
|
|
13799
|
+
"financial.chartTree.field.nature": "Nature",
|
|
13800
|
+
"financial.chartTree.field.parent": "Parent account",
|
|
13801
|
+
"financial.chartTree.nature.none": "No nature",
|
|
13802
|
+
"financial.chartTree.nature.revenue": "Revenue",
|
|
13803
|
+
"financial.chartTree.nature.expense": "Expense",
|
|
13804
|
+
"financial.chartTree.nature.asset": "Asset",
|
|
13805
|
+
"financial.chartTree.nature.liability": "Liability",
|
|
13806
|
+
"financial.chartTree.nature.equity": "Equity",
|
|
13807
|
+
"financial.chartTree.created": 'Account "{{name}}" created',
|
|
13808
|
+
"financial.chartTree.updated": 'Account "{{name}}" updated',
|
|
13809
|
+
"financial.chartTree.deactivated": 'Account "{{name}}" deactivated',
|
|
13810
|
+
"financial.chartTree.reactivated": 'Account "{{name}}" reactivated',
|
|
13811
|
+
"financial.chartTree.deleted": 'Account "{{name}}" deleted',
|
|
13812
|
+
"financial.chartTree.error.save": "Could not save. If the account already has postings, deactivate it instead of deleting.",
|
|
13813
|
+
"financial.chartTree.error.load": "Could not load the chart of accounts.",
|
|
13814
|
+
"financial.chartTree.error.codeRequired": "Enter the account code.",
|
|
13815
|
+
"financial.chartTree.error.nameRequired": "Enter the account name.",
|
|
13816
|
+
"financial.chartTree.error.codeTaken": 'Code {{code}} is already used by "{{name}}".',
|
|
13817
|
+
"financial.chartTree.error.leafWithChildren": "An account with accounts inside it must stay a group."
|
|
13225
13818
|
};
|
|
13226
13819
|
|
|
13227
13820
|
// src/locales/pt-BR.ts
|
|
@@ -13347,6 +13940,8 @@ var ptBR = {
|
|
|
13347
13940
|
"financial.invoice.reverseReason": "Motivo do estorno",
|
|
13348
13941
|
"financial.invoice.paymentSchedule": "Cronograma de Pagamento",
|
|
13349
13942
|
"financial.invoiceForm.scheduleLocked": "Parcelas j\xE1 pagas \u2014 o parcelamento n\xE3o pode mais ser refeito.",
|
|
13943
|
+
"financial.invoiceForm.issuedLocked": "Conta emitida: valor, itens, fornecedor e data ficam travados. Para mudar, cancele e lance uma nova. Observa\xE7\xF5es, unidade, n\xBA do documento e vencimentos continuam edit\xE1veis.",
|
|
13944
|
+
"financial.invoiceForm.edit": "Editar",
|
|
13350
13945
|
"financial.invoice.payment": "Pagamento",
|
|
13351
13946
|
"financial.invoice.balance": "Restante",
|
|
13352
13947
|
"financial.invoice.details": "Detalhes",
|
|
@@ -13486,6 +14081,9 @@ var ptBR = {
|
|
|
13486
14081
|
"financial.payment.cancel": "Cancelar",
|
|
13487
14082
|
"financial.payment.card": "Cart\xE3o",
|
|
13488
14083
|
"financial.payment.cardBrand": "Bandeira do Cart\xE3o",
|
|
14084
|
+
"financial.payment.whichCard": "Qual cart\xE3o",
|
|
14085
|
+
"financial.payment.noCompanyCards": "Nenhum cart\xE3o da empresa cadastrado. Pagamento no cart\xE3o precisa do cart\xE3o cadastrado.",
|
|
14086
|
+
"financial.payment.registerCard": 'Cadastrar cart\xE3o (conta do tipo "Cart\xE3o a pagar")',
|
|
13489
14087
|
"financial.payment.cardInstallments": "Parcelas",
|
|
13490
14088
|
"financial.payment.date": "Data",
|
|
13491
14089
|
"financial.payment.installment": "Parcela #{{number}}",
|
|
@@ -13820,7 +14418,59 @@ var ptBR = {
|
|
|
13820
14418
|
"financial.quickTx.descriptionPlaceholder": "ex.: Mercado, Sal\xE1rio\u2026",
|
|
13821
14419
|
"financial.quickTx.enterAmount": "Informe um valor",
|
|
13822
14420
|
"financial.quickTx.save": "Salvar",
|
|
13823
|
-
"financial.quickTx.saving": "Salvando\u2026"
|
|
14421
|
+
"financial.quickTx.saving": "Salvando\u2026",
|
|
14422
|
+
// Chart of accounts tree
|
|
14423
|
+
"financial.chartTree.searchPlaceholder": "Buscar por c\xF3digo ou nome",
|
|
14424
|
+
"financial.chartTree.clearSearch": "Limpar busca",
|
|
14425
|
+
"financial.chartTree.expandAll": "Expandir tudo",
|
|
14426
|
+
"financial.chartTree.collapseAll": "Recolher tudo",
|
|
14427
|
+
"financial.chartTree.expand": "Expandir",
|
|
14428
|
+
"financial.chartTree.collapse": "Recolher",
|
|
14429
|
+
"financial.chartTree.addRoot": "Nova conta raiz",
|
|
14430
|
+
"financial.chartTree.addChild": "Nova conta dentro desta",
|
|
14431
|
+
"financial.chartTree.edit": "Editar",
|
|
14432
|
+
"financial.chartTree.deactivate": "Desativar",
|
|
14433
|
+
"financial.chartTree.reactivate": "Reativar",
|
|
14434
|
+
"financial.chartTree.delete": "Excluir",
|
|
14435
|
+
"financial.chartTree.confirmDelete": "Excluir esta conta?",
|
|
14436
|
+
"financial.chartTree.cancel": "Cancelar",
|
|
14437
|
+
"financial.chartTree.save": "Salvar",
|
|
14438
|
+
"financial.chartTree.saving": "Salvando\u2026",
|
|
14439
|
+
"financial.chartTree.searching": "Buscando\u2026",
|
|
14440
|
+
"financial.chartTree.count": "{{count}} contas",
|
|
14441
|
+
"financial.chartTree.countOne": "1 conta",
|
|
14442
|
+
"financial.chartTree.matchesOne": "1 conta encontrada",
|
|
14443
|
+
"financial.chartTree.matches": "{{count}} contas encontradas",
|
|
14444
|
+
"financial.chartTree.showInactive": "Mostrar inativas ({{count}})",
|
|
14445
|
+
"financial.chartTree.inactive": "Inativa",
|
|
14446
|
+
"financial.chartTree.empty": "Nenhuma conta no plano ainda. Crie a primeira conta raiz.",
|
|
14447
|
+
"financial.chartTree.noMatches": "Nenhuma conta corresponde \xE0 busca.",
|
|
14448
|
+
"financial.chartTree.noGroups": "Nenhum grupo encontrado",
|
|
14449
|
+
"financial.chartTree.rootLevel": "N\xEDvel raiz (sem pai)",
|
|
14450
|
+
"financial.chartTree.group": "Grupo (agrupa outras contas)",
|
|
14451
|
+
"financial.chartTree.leaf": "Conta (recebe lan\xE7amentos)",
|
|
14452
|
+
"financial.chartTree.field.code": "C\xF3digo",
|
|
14453
|
+
"financial.chartTree.field.name": "Nome",
|
|
14454
|
+
"financial.chartTree.field.structure": "Estrutura",
|
|
14455
|
+
"financial.chartTree.field.nature": "Natureza",
|
|
14456
|
+
"financial.chartTree.field.parent": "Conta pai",
|
|
14457
|
+
"financial.chartTree.nature.none": "Sem natureza",
|
|
14458
|
+
"financial.chartTree.nature.revenue": "Receita",
|
|
14459
|
+
"financial.chartTree.nature.expense": "Despesa",
|
|
14460
|
+
"financial.chartTree.nature.asset": "Ativo",
|
|
14461
|
+
"financial.chartTree.nature.liability": "Passivo",
|
|
14462
|
+
"financial.chartTree.nature.equity": "Patrim\xF4nio",
|
|
14463
|
+
"financial.chartTree.created": 'Conta "{{name}}" criada',
|
|
14464
|
+
"financial.chartTree.updated": 'Conta "{{name}}" atualizada',
|
|
14465
|
+
"financial.chartTree.deactivated": 'Conta "{{name}}" desativada',
|
|
14466
|
+
"financial.chartTree.reactivated": 'Conta "{{name}}" reativada',
|
|
14467
|
+
"financial.chartTree.deleted": 'Conta "{{name}}" exclu\xEDda',
|
|
14468
|
+
"financial.chartTree.error.save": "N\xE3o foi poss\xEDvel salvar. Se a conta j\xE1 tem lan\xE7amentos, desative em vez de excluir.",
|
|
14469
|
+
"financial.chartTree.error.load": "N\xE3o foi poss\xEDvel carregar o plano de contas.",
|
|
14470
|
+
"financial.chartTree.error.codeRequired": "Informe o c\xF3digo da conta.",
|
|
14471
|
+
"financial.chartTree.error.nameRequired": "Informe o nome da conta.",
|
|
14472
|
+
"financial.chartTree.error.codeTaken": 'O c\xF3digo {{code}} j\xE1 \xE9 usado por "{{name}}".',
|
|
14473
|
+
"financial.chartTree.error.leafWithChildren": "Uma conta com contas dentro dela precisa continuar como grupo."
|
|
13824
14474
|
};
|
|
13825
14475
|
|
|
13826
14476
|
// src/locales/index.ts
|
|
@@ -35205,6 +35855,168 @@ GRANT EXECUTE ON FUNCTION public.plg_financial_copy_recurring_items() TO service
|
|
|
35205
35855
|
COMMENT ON FUNCTION public.plg_financial_copy_recurring_items() IS
|
|
35206
35856
|
'Copies every item of a recurring rule into each generated invoice, preserving per-line accounting.';
|
|
35207
35857
|
`;
|
|
35858
|
+
var MIGRATION_092_A_CONTA_PAGA_NO_CARTAO_SAI_DO_CARTAO = `-- A bill paid by credit card is paid from the company's card.
|
|
35859
|
+
--
|
|
35860
|
+
-- financial_receive_payment settled every credit_card payment in a
|
|
35861
|
+
-- card_receivable_credit account \u2014 right for a sale (the acquirer owes us),
|
|
35862
|
+
-- wrong for a bill: paying a supplier with the corporate card puts the debt on
|
|
35863
|
+
-- that card (card_payable). The payables screen could never pay by card.
|
|
35864
|
+
-- Receivables keep the exact rule they had. Replaces the function in place;
|
|
35865
|
+
-- idempotent.
|
|
35866
|
+
|
|
35867
|
+
CREATE OR REPLACE FUNCTION public.financial_receive_payment(p_installment_id uuid, p_amount numeric, p_account_id uuid DEFAULT NULL::uuid, p_method text DEFAULT NULL::text, p_cash_session_id uuid DEFAULT NULL::uuid, p_card_brand text DEFAULT NULL::text, p_card_installments integer DEFAULT NULL::integer, p_nsu text DEFAULT NULL::text, p_payment_date date DEFAULT NULL::date, p_fee_amount numeric DEFAULT NULL::numeric, p_notes text DEFAULT NULL::text, p_idempotency_key text DEFAULT NULL::text, p_metadata jsonb DEFAULT '{}'::jsonb) RETURNS jsonb
|
|
35868
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
35869
|
+
SET search_path TO ''
|
|
35870
|
+
AS $$
|
|
35871
|
+
DECLARE
|
|
35872
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
35873
|
+
v_actor uuid := auth.uid();
|
|
35874
|
+
v_inst_id uuid := p_installment_id;
|
|
35875
|
+
v_inst record;
|
|
35876
|
+
v_invoice public.plg_financial_invoices%ROWTYPE;
|
|
35877
|
+
v_amount numeric := round(coalesce(p_amount, 0), 2);
|
|
35878
|
+
v_open numeric;
|
|
35879
|
+
v_method text := lower(coalesce(nullif(btrim(p_method), ''), 'other'));
|
|
35880
|
+
v_holder uuid;
|
|
35881
|
+
v_holder_k text;
|
|
35882
|
+
v_session uuid := p_cash_session_id;
|
|
35883
|
+
v_counter uuid;
|
|
35884
|
+
v_credit numeric;
|
|
35885
|
+
v_mov_id uuid;
|
|
35886
|
+
v_alloc_id uuid;
|
|
35887
|
+
v_dir text;
|
|
35888
|
+
BEGIN
|
|
35889
|
+
IF v_amount <= 0 THEN
|
|
35890
|
+
RAISE EXCEPTION 'financial_receive_payment: amount must be > 0' USING ERRCODE = '22023';
|
|
35891
|
+
END IF;
|
|
35892
|
+
IF v_method NOT IN ('cash', 'pix', 'credit_card', 'debit_card', 'transfer', 'boleto', 'check', 'credit', 'other') THEN
|
|
35893
|
+
RAISE EXCEPTION 'financial_receive_payment: unknown method %', v_method USING ERRCODE = '22023';
|
|
35894
|
+
END IF;
|
|
35895
|
+
|
|
35896
|
+
-- accept a legacy per-order 'bill' movement id in place of the installment
|
|
35897
|
+
IF NOT EXISTS (SELECT 1 FROM public.plg_financial_invoice_installments i WHERE i.id = v_inst_id) THEN
|
|
35898
|
+
SELECT m.financial_invoice_installment_id INTO v_inst_id FROM public.plg_financial_movements m
|
|
35899
|
+
WHERE m.id = p_installment_id AND m.movement_kind = 'bill' AND m.tenant_id = v_tenant;
|
|
35900
|
+
END IF;
|
|
35901
|
+
SELECT i.* INTO v_inst FROM public.plg_financial_invoice_installments i WHERE i.id = v_inst_id AND i.tenant_id = v_tenant;
|
|
35902
|
+
IF NOT FOUND THEN
|
|
35903
|
+
RAISE EXCEPTION 'financial_receive_payment: installment % not found', p_installment_id USING ERRCODE = 'P0002';
|
|
35904
|
+
END IF;
|
|
35905
|
+
SELECT * INTO v_invoice FROM public.plg_financial_invoices WHERE id = v_inst.invoice_id;
|
|
35906
|
+
PERFORM public.plg_financial_require_permission('financial.create', v_inst.unit_id);
|
|
35907
|
+
|
|
35908
|
+
-- idempotency
|
|
35909
|
+
IF p_idempotency_key IS NOT NULL THEN
|
|
35910
|
+
PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);
|
|
35911
|
+
SELECT m.id INTO v_mov_id FROM public.plg_financial_movements m WHERE m.tenant_id = v_tenant AND m.idempotency_key = p_idempotency_key;
|
|
35912
|
+
IF v_mov_id IS NOT NULL THEN
|
|
35913
|
+
SELECT a.id INTO v_alloc_id FROM public.plg_financial_allocations a WHERE a.payment_movement_id = v_mov_id LIMIT 1;
|
|
35914
|
+
RETURN jsonb_build_object('movement_id', v_mov_id, 'allocation_id', v_alloc_id, 'installment_id', v_inst.id,
|
|
35915
|
+
'invoice_id', v_inst.invoice_id, 'amount', v_amount, 'replayed', true);
|
|
35916
|
+
END IF;
|
|
35917
|
+
END IF;
|
|
35918
|
+
|
|
35919
|
+
IF v_inst.cancelled_at IS NOT NULL OR v_invoice.cancelled_at IS NOT NULL THEN
|
|
35920
|
+
RAISE EXCEPTION 'financial_receive_payment: installment is cancelled' USING ERRCODE = '55000';
|
|
35921
|
+
END IF;
|
|
35922
|
+
v_open := round(v_inst.amount - public.plg_financial_installment_paid(v_inst.id), 2);
|
|
35923
|
+
IF v_amount > v_open + 0.000001 THEN
|
|
35924
|
+
RAISE EXCEPTION 'financial_receive_payment: % exceeds the open balance % of installment %/% of %', v_amount, v_open, v_inst.number, v_invoice.number, v_invoice.number
|
|
35925
|
+
USING ERRCODE = '23514';
|
|
35926
|
+
END IF;
|
|
35927
|
+
|
|
35928
|
+
-- the money holder
|
|
35929
|
+
IF v_method = 'credit' THEN
|
|
35930
|
+
IF v_invoice.counterparty_person_id IS NULL THEN
|
|
35931
|
+
RAISE EXCEPTION 'financial_receive_payment: the invoice has no counterparty to consume credit from' USING ERRCODE = '55000';
|
|
35932
|
+
END IF;
|
|
35933
|
+
v_holder := public.plg_financial_person_account(v_tenant, v_invoice.counterparty_person_id);
|
|
35934
|
+
SELECT balance INTO v_credit FROM public.v_financial_account_balances WHERE account_id = v_holder;
|
|
35935
|
+
IF coalesce(v_credit, 0) + 0.000001 < v_amount THEN
|
|
35936
|
+
RAISE EXCEPTION 'financial_receive_payment: customer credit % is below %', coalesce(v_credit, 0), v_amount USING ERRCODE = '23514';
|
|
35937
|
+
END IF;
|
|
35938
|
+
ELSIF v_method = 'cash' THEN
|
|
35939
|
+
IF v_session IS NULL THEN
|
|
35940
|
+
v_session := public.plg_financial_open_session_of(v_tenant, v_actor, v_inst.unit_id);
|
|
35941
|
+
END IF;
|
|
35942
|
+
IF v_session IS NULL THEN
|
|
35943
|
+
RAISE EXCEPTION 'financial_receive_payment: no open cash session (caixinha) for cash in this unit' USING ERRCODE = '55000',
|
|
35944
|
+
HINT = 'Open your cash session first (financial_cash_session_open).';
|
|
35945
|
+
END IF;
|
|
35946
|
+
SELECT s.account_id INTO v_holder FROM public.plg_financial_cash_register_sessions s
|
|
35947
|
+
WHERE s.id = v_session AND s.tenant_id = v_tenant AND s.status = 'open';
|
|
35948
|
+
IF v_holder IS NULL THEN
|
|
35949
|
+
RAISE EXCEPTION 'financial_receive_payment: cash session % is not open', v_session USING ERRCODE = '55000';
|
|
35950
|
+
END IF;
|
|
35951
|
+
ELSE
|
|
35952
|
+
v_holder := p_account_id;
|
|
35953
|
+
IF v_holder IS NULL THEN
|
|
35954
|
+
RAISE EXCEPTION 'financial_receive_payment: an account is required for method %', v_method USING ERRCODE = '22023';
|
|
35955
|
+
END IF;
|
|
35956
|
+
END IF;
|
|
35957
|
+
SELECT a.kind INTO v_holder_k FROM public.plg_financial_accounts a WHERE a.id = v_holder AND a.tenant_id = v_tenant AND a.is_active;
|
|
35958
|
+
IF v_holder_k IS NULL THEN
|
|
35959
|
+
RAISE EXCEPTION 'financial_receive_payment: account % not found or inactive', v_holder USING ERRCODE = 'P0002';
|
|
35960
|
+
END IF;
|
|
35961
|
+
IF v_invoice.direction = 'payable' THEN
|
|
35962
|
+
-- Paying a bill by credit card spends the company's own card (a liability
|
|
35963
|
+
-- account), never a card receivable. Debit card leaves the bank directly.
|
|
35964
|
+
IF v_method = 'credit_card' AND v_holder_k <> 'card_payable' THEN
|
|
35965
|
+
RAISE EXCEPTION 'financial_receive_payment: method credit_card must pay a bill from a card_payable account'
|
|
35966
|
+
USING ERRCODE = '23514';
|
|
35967
|
+
END IF;
|
|
35968
|
+
IF v_method <> 'credit_card' AND v_holder_k = 'card_payable' THEN
|
|
35969
|
+
RAISE EXCEPTION 'financial_receive_payment: a card_payable account only pays by credit_card' USING ERRCODE = '23514';
|
|
35970
|
+
END IF;
|
|
35971
|
+
IF v_holder_k IN ('card_receivable_credit', 'card_receivable_debit') THEN
|
|
35972
|
+
RAISE EXCEPTION 'financial_receive_payment: a card receivable account cannot pay a bill' USING ERRCODE = '23514';
|
|
35973
|
+
END IF;
|
|
35974
|
+
ELSE
|
|
35975
|
+
IF (v_method = 'credit_card' AND v_holder_k <> 'card_receivable_credit')
|
|
35976
|
+
OR (v_method = 'debit_card' AND v_holder_k <> 'card_receivable_debit') THEN
|
|
35977
|
+
RAISE EXCEPTION 'financial_receive_payment: method % must settle in a % account', v_method,
|
|
35978
|
+
CASE WHEN v_method = 'credit_card' THEN 'card_receivable_credit' ELSE 'card_receivable_debit' END USING ERRCODE = '23514';
|
|
35979
|
+
END IF;
|
|
35980
|
+
IF v_method NOT IN ('credit_card', 'debit_card') AND v_holder_k IN ('card_receivable_credit', 'card_receivable_debit') THEN
|
|
35981
|
+
RAISE EXCEPTION 'financial_receive_payment: a card receivable account only takes card methods' USING ERRCODE = '23514';
|
|
35982
|
+
END IF;
|
|
35983
|
+
END IF;
|
|
35984
|
+
|
|
35985
|
+
IF v_invoice.direction = 'payable' THEN
|
|
35986
|
+
v_dir := 'debit';
|
|
35987
|
+
v_counter := public.plg_financial_system_account(v_tenant, 'payables');
|
|
35988
|
+
ELSE
|
|
35989
|
+
v_dir := 'credit';
|
|
35990
|
+
v_counter := public.plg_financial_system_account(v_tenant, 'receivables');
|
|
35991
|
+
END IF;
|
|
35992
|
+
|
|
35993
|
+
INSERT INTO public.plg_financial_movements
|
|
35994
|
+
(tenant_id, unit_id, invoice_id, financial_invoice_id, financial_invoice_installment_id, direction, movement_kind, amount, paid_amount, status,
|
|
35995
|
+
due_date, payment_date, installment_number, method, card_brand, card_installments, nsu, cash_session_id,
|
|
35996
|
+
bank_account_id, fee_amount, notes, metadata, idempotency_key, created_by, debit_account_id, credit_account_id)
|
|
35997
|
+
VALUES
|
|
35998
|
+
(v_tenant, v_inst.unit_id, v_invoice.order_id, v_invoice.id, v_inst.id, v_dir, 'payment', v_amount, v_amount, 'paid',
|
|
35999
|
+
v_inst.due_date, coalesce(p_payment_date, current_date), v_inst.number, v_method, p_card_brand, p_card_installments, p_nsu, v_session,
|
|
36000
|
+
CASE WHEN v_holder_k IN ('person', 'system') THEN NULL ELSE v_holder END,
|
|
36001
|
+
round(coalesce(p_fee_amount, 0), 2), p_notes,
|
|
36002
|
+
coalesce(p_metadata, '{}'::jsonb) || jsonb_build_object('source', 'financial_receive_payment'),
|
|
36003
|
+
p_idempotency_key, v_actor,
|
|
36004
|
+
CASE WHEN v_dir = 'credit' THEN v_holder ELSE v_counter END,
|
|
36005
|
+
CASE WHEN v_dir = 'credit' THEN v_counter ELSE v_holder END)
|
|
36006
|
+
RETURNING id INTO v_mov_id;
|
|
36007
|
+
|
|
36008
|
+
INSERT INTO public.plg_financial_allocations (tenant_id, unit_id, payment_movement_id, installment_id, amount, created_by)
|
|
36009
|
+
VALUES (v_tenant, v_inst.unit_id, v_mov_id, v_inst.id, v_amount, v_actor)
|
|
36010
|
+
RETURNING id INTO v_alloc_id;
|
|
36011
|
+
|
|
36012
|
+
PERFORM public.plg_financial_audit(v_tenant, 'financial.payment.receive', 'plg_financial_movements', v_mov_id::text,
|
|
36013
|
+
jsonb_build_object('installment_id', v_inst.id, 'invoice_id', v_invoice.id, 'invoice_number', v_invoice.number, 'amount', v_amount,
|
|
36014
|
+
'method', v_method, 'account_id', v_holder, 'cash_session_id', v_session, 'idempotency_key', p_idempotency_key));
|
|
36015
|
+
|
|
36016
|
+
RETURN jsonb_build_object('movement_id', v_mov_id, 'allocation_id', v_alloc_id, 'installment_id', v_inst.id,
|
|
36017
|
+
'invoice_id', v_inst.invoice_id, 'amount', v_amount, 'replayed', false);
|
|
36018
|
+
END $$;
|
|
36019
|
+
`;
|
|
35208
36020
|
var MIGRATIONS = [
|
|
35209
36021
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
35210
36022
|
{ id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
|
|
@@ -35297,7 +36109,8 @@ var MIGRATIONS = [
|
|
|
35297
36109
|
{ id: "088_a_conta_sem_pessoa_e_do_sistema_nao_de_ninguem", sql: MIGRATION_088_A_CONTA_SEM_PESSOA_E_DO_SISTEMA_NAO_DE_NINGUEM },
|
|
35298
36110
|
{ id: "089_o_fornecedor_aponta_para_o_plano_migrado", sql: MIGRATION_089_O_FORNECEDOR_APONTA_PARA_O_PLANO_MIGRADO },
|
|
35299
36111
|
{ id: "090_o_dre_recorta_antes_de_somar", sql: MIGRATION_090_O_DRE_RECORTA_ANTES_DE_SOMAR },
|
|
35300
|
-
{ id: "091_a_recorrencia_repete_os_itens", sql: MIGRATION_091_A_RECORRENCIA_REPETE_OS_ITENS }
|
|
36112
|
+
{ id: "091_a_recorrencia_repete_os_itens", sql: MIGRATION_091_A_RECORRENCIA_REPETE_OS_ITENS },
|
|
36113
|
+
{ id: "092_a_conta_paga_no_cartao_sai_do_cartao", sql: MIGRATION_092_A_CONTA_PAGA_NO_CARTAO_SAI_DO_CARTAO }
|
|
35301
36114
|
];
|
|
35302
36115
|
var STANDALONE_CONFIG = {
|
|
35303
36116
|
currency: { code: "BRL", locale: "pt-BR", symbol: "R$" }
|