@fayz-ai/plugin-financial 0.16.0 → 0.17.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/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, Input, Checkbox, ModalFooter, DashboardCanvas, ListView, DateRangeControl, PageHeaderActions, SearchSelect, RequiredFilter, Hint, useSaveBar, tourAnchor, FilterMenu, FilterMenuRow, WizardSteps, KpiCard, ChartWidget, CardHeader, CardTitle, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
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, Search, Download, CheckCheck, Pause, MoreVertical, Printer, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, ArrowLeft, ArrowUpRight, ArrowDownRight, EyeOff, Eye } from 'lucide-react';
3
+ import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode, useDataChanged, countByTenant, tl, resolveDataProvider, getSupabaseClientOptional, getActiveTenantId, emitDataChanged, exportCSV, useActiveUnitId } 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
+ 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, useUnitFilter, 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, 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';
@@ -1049,12 +1049,18 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1049
1049
  const [accountId, setAccountId] = useState("");
1050
1050
  const [costCenterId, setCostCenterId] = useState("");
1051
1051
  const [unitId, setUnitId] = useState("");
1052
+ const unitMode = useUnitMode();
1053
+ const unitFilter = useUnitFilter();
1054
+ const effectiveUnitId = unitMode ? unitFilter.value : unitId || void 0;
1052
1055
  const [accounts, setAccounts] = useState([]);
1053
1056
  const postable = useMemo(() => postableAccounts(accounts), [accounts]);
1054
1057
  const [costCenters, setCostCenters] = useState([]);
1055
1058
  const mesCorrente = useMemo(() => {
1056
1059
  const hoje = /* @__PURE__ */ new Date();
1057
- return { from: isoDate(new Date(hoje.getFullYear(), hoje.getMonth(), 1)), to: isoDate(hoje) };
1060
+ return {
1061
+ from: isoDate(new Date(hoje.getFullYear(), hoje.getMonth(), 1)),
1062
+ to: isoDate(new Date(hoje.getFullYear(), hoje.getMonth() + 2, 0))
1063
+ };
1058
1064
  }, []);
1059
1065
  const [datePreset, setDatePreset] = useState("custom");
1060
1066
  const [dateFrom, setDateFrom] = useState(mesCorrente.from);
@@ -1109,9 +1115,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1109
1115
  search: search || void 0,
1110
1116
  accountId: accountId || void 0,
1111
1117
  costCenterId: costCenterId || void 0,
1112
- unitId: unitId || void 0,
1118
+ unitId: effectiveUnitId,
1113
1119
  dateRange
1114
- }), [direction, statusFilter, search, accountId, costCenterId, unitId, dateRange]);
1120
+ }), [direction, statusFilter, search, accountId, costCenterId, effectiveUnitId, dateRange]);
1115
1121
  useEffect(() => {
1116
1122
  fetchInvoices(query);
1117
1123
  }, [query]);
@@ -1257,7 +1263,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1257
1263
  fields: [
1258
1264
  {
1259
1265
  id: "status",
1260
- label: t2("financial.invoice.statusCol"),
1266
+ label: t2("financial.invoice.columnStatus"),
1261
1267
  icon: CircleDashed,
1262
1268
  activeCount: statusFilter.length,
1263
1269
  render: (_close, q) => /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -1314,7 +1320,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1314
1320
  ] }) }, c.id))
1315
1321
  ] })
1316
1322
  }] : [],
1317
- ...locations.length > 1 ? [{
1323
+ ...!unitMode && locations.length > 1 ? [{
1318
1324
  id: "unit",
1319
1325
  label: t2("financial.invoice.filterUnit"),
1320
1326
  icon: Building2,
@@ -1341,6 +1347,25 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1341
1347
  setUnitId("");
1342
1348
  setStatusFilter([]);
1343
1349
  };
1350
+ const unitPill = unitFilter.visible ? /* @__PURE__ */ jsx(
1351
+ RequiredFilter,
1352
+ {
1353
+ label: unitFilter.label,
1354
+ value: unitFilter.options.find((u) => u.id === unitFilter.value)?.name ?? unitFilter.allLabel,
1355
+ icon: Building2,
1356
+ align: "end",
1357
+ children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
1358
+ /* @__PURE__ */ jsx(FilterMenuRow, { active: !unitFilter.value, onClick: () => {
1359
+ unitFilter.setValue(void 0);
1360
+ close();
1361
+ }, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: unitFilter.allLabel }) }),
1362
+ unitFilter.options.map((u) => /* @__PURE__ */ jsx(FilterMenuRow, { active: unitFilter.value === u.id, onClick: () => {
1363
+ unitFilter.setValue(u.id);
1364
+ close();
1365
+ }, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: u.name }) }, u.id))
1366
+ ] })
1367
+ }
1368
+ ) : null;
1344
1369
  const periodFilter = /* @__PURE__ */ jsx(
1345
1370
  RequiredFilter,
1346
1371
  {
@@ -1403,6 +1428,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1403
1428
  onSearchChange: setSearch,
1404
1429
  searchDebounce: 0,
1405
1430
  filtersTrigger: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1431
+ unitPill,
1406
1432
  periodFilter,
1407
1433
  filtersMenu
1408
1434
  ] }),
@@ -2716,13 +2742,20 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
2716
2742
  () => paymentMethods2.filter((m) => m.paymentMethodTypeId === selectedTypeId),
2717
2743
  [paymentMethods2, selectedTypeId]
2718
2744
  );
2719
- const needsCard = txType === "credit_card" || txType === "debit_card";
2745
+ const isPayable = movement.direction === "debit";
2746
+ const payingByCompanyCard = isPayable && txType === "credit_card";
2747
+ const needsCard = !isPayable && (txType === "credit_card" || txType === "debit_card");
2720
2748
  const needsCardInstallments = txType === "credit_card";
2721
2749
  const eligibleAccounts2 = useMemo(() => {
2750
+ const active = bankAccounts.filter((a) => a.isActive !== false);
2751
+ if (isPayable) {
2752
+ if (txType === "credit_card") return active.filter((a) => a.accountType === "card_payable");
2753
+ return active.filter((a) => !["card_payable", "card_receivable_credit", "card_receivable_debit"].includes(a.accountType));
2754
+ }
2722
2755
  const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType];
2723
2756
  if (!allowed || !allowed.length) return bankAccounts;
2724
2757
  return bankAccounts.filter((a) => allowed.includes(a.accountType));
2725
- }, [bankAccounts, selectedType, txType]);
2758
+ }, [bankAccounts, selectedType, txType, isPayable]);
2726
2759
  useEffect(() => {
2727
2760
  if (selectedTypeId && !bankAccountId && eligibleAccounts2.length) {
2728
2761
  setBankAccountId(eligibleAccounts2[0].id);
@@ -2858,15 +2891,15 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
2858
2891
  /* @__PURE__ */ jsx(DatePicker, { value: paymentDate, onChange: setPaymentDate, className: "mt-1" })
2859
2892
  ] })
2860
2893
  ] }),
2861
- filteredMethods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
2894
+ filteredMethods.length > 0 && !isPayable && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
2862
2895
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: needsCard ? t2("financial.payment.card") : t2("financial.payment.account") }),
2863
2896
  /* @__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
2897
  /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.select") }),
2865
2898
  filteredMethods.map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: m.name }, m.id))
2866
2899
  ] })
2867
2900
  ] }),
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: [
2901
+ (needsCard || payingByCompanyCard) && /* @__PURE__ */ jsxs("div", { className: `grid gap-3 stagger-field ${needsCard && needsCardInstallments ? "grid-cols-2" : "grid-cols-1"}`, style: { animationDelay: "100ms" }, children: [
2902
+ needsCard && /* @__PURE__ */ jsxs("div", { children: [
2870
2903
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.cardBrand") }),
2871
2904
  /* @__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
2905
  /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.selectBrand") }),
@@ -2889,7 +2922,46 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
2889
2922
  " \u2192"
2890
2923
  ] })
2891
2924
  ] }),
2892
- /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
2925
+ payingByCompanyCard && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
2926
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.whichCard") }),
2927
+ 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: [
2928
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.payment.noCompanyCards") }),
2929
+ /* @__PURE__ */ jsxs("a", { href: "#/settings/financial/bank-accounts", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
2930
+ t2("financial.payment.registerCard"),
2931
+ " \u2192"
2932
+ ] })
2933
+ ] }) : /* @__PURE__ */ jsx("div", { className: "mt-1.5 grid grid-cols-2 gap-2", role: "radiogroup", children: eligibleAccounts2.map((card) => {
2934
+ const last4 = (card.accountNumber ?? "").replace(/\D/g, "").slice(-4);
2935
+ const active = bankAccountId === card.id;
2936
+ return /* @__PURE__ */ jsxs(
2937
+ "button",
2938
+ {
2939
+ type: "button",
2940
+ role: "radio",
2941
+ "aria-checked": active,
2942
+ onClick: () => setBankAccountId(card.id),
2943
+ 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"}`,
2944
+ children: [
2945
+ /* @__PURE__ */ jsx(
2946
+ "span",
2947
+ {
2948
+ "aria-hidden": true,
2949
+ className: "flex h-7 w-11 shrink-0 items-end justify-end rounded-md border px-1 pb-0.5",
2950
+ style: { backgroundColor: card.color || "hsl(var(--muted))" },
2951
+ children: /* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 text-white/80 drop-shadow" })
2952
+ }
2953
+ ),
2954
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
2955
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-xs font-medium", children: card.name }),
2956
+ /* @__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 ") })
2957
+ ] })
2958
+ ]
2959
+ },
2960
+ card.id
2961
+ );
2962
+ }) })
2963
+ ] }),
2964
+ !payingByCompanyCard && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
2893
2965
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.bankAccount") }),
2894
2966
  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
2967
  /* @__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 +3081,9 @@ function FinancialCounterpartySearchSelect({
3009
3081
  label,
3010
3082
  required,
3011
3083
  onChange,
3012
- onCreate
3084
+ onCreate,
3085
+ onEdit,
3086
+ disabled
3013
3087
  }) {
3014
3088
  const search = React20.useCallback(async (query) => {
3015
3089
  const results = await lookup.search(query);
@@ -3028,6 +3102,7 @@ function FinancialCounterpartySearchSelect({
3028
3102
  displayValue,
3029
3103
  label,
3030
3104
  required,
3105
+ disabled,
3031
3106
  onChange,
3032
3107
  onSearch: search,
3033
3108
  minChars: 2,
@@ -3039,6 +3114,8 @@ function FinancialCounterpartySearchSelect({
3039
3114
  createLabel: "Cadastrar",
3040
3115
  clearLabel: "Limpar",
3041
3116
  onCreate,
3117
+ onEdit,
3118
+ editLabel: "Editar",
3042
3119
  placeholder: "Digite nome, telefone ou documento..."
3043
3120
  }
3044
3121
  );
@@ -3215,22 +3292,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3215
3292
  const config = useFinancialConfig();
3216
3293
  const provider = useFinancialProvider();
3217
3294
  const { currency, itemTypes: itemTypesRaw, locations } = config;
3218
- const [availableLocations, setAvailableLocations] = useState(locations);
3219
- useEffect(() => {
3220
- if (locations.length > 0) {
3221
- setAvailableLocations(locations);
3222
- return;
3223
- }
3224
- let cancelled = false;
3225
- const lookup = createArchetypeLookup({ archetype: "location", entityDef: null });
3226
- void lookup.list?.().then((rows) => {
3227
- if (!cancelled) setAvailableLocations(rows.map((row) => ({ id: row.id, name: row.label })));
3228
- }).catch(() => {
3229
- });
3230
- return () => {
3231
- cancelled = true;
3232
- };
3233
- }, [locations]);
3295
+ const unitMode = useUnitMode();
3296
+ const activeUnitId = useActiveUnitId();
3297
+ const reachableUnits = useUnitFilter().options;
3298
+ const unitChoices = unitMode ? reachableUnits : locations;
3299
+ const showUnitField = unitChoices.length > 1;
3234
3300
  const [giftCardsEnabled, setGiftCardsEnabled] = useState(false);
3235
3301
  useEffect(() => {
3236
3302
  let cancelled = false;
@@ -3330,7 +3396,17 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3330
3396
  addItemsAutomatically: false
3331
3397
  });
3332
3398
  const accountingPolicyRequest = useRef(0);
3333
- const [unitId, setUnitId] = useState(locations[0]?.id ?? "");
3399
+ const defaultUnitId = () => {
3400
+ if (!unitMode) return void 0;
3401
+ if (activeUnitId && reachableUnits.some((u) => u.id === activeUnitId)) return activeUnitId;
3402
+ return reachableUnits.length === 1 ? reachableUnits[0].id : void 0;
3403
+ };
3404
+ const [unitId, setUnitId] = useState(() => (isEdit ? void 0 : defaultUnitId()) ?? locations[0]?.id ?? "");
3405
+ useEffect(() => {
3406
+ if (isEdit || unitId) return;
3407
+ const next = defaultUnitId();
3408
+ if (next) setUnitId(next);
3409
+ }, [isEdit, unitMode, unitId, activeUnitId, reachableUnits]);
3334
3410
  const [invoiceDate, setInvoiceDate] = useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
3335
3411
  const [fiscalNumber, setFiscalNumber] = useState("");
3336
3412
  const [observations, setObservations] = useState("");
@@ -3425,7 +3501,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3425
3501
  }
3426
3502
  setUnitId(invoice.unitId ?? locations[0]?.id ?? "");
3427
3503
  setInvoiceDate(invoice.invoiceDate);
3428
- setFiscalNumber(invoice.fiscalNumber ?? "");
3504
+ setFiscalNumber(String(invoice.metadata?.fiscalNumber ?? ""));
3429
3505
  setObservations(invoice.observations ?? "");
3430
3506
  const invoiceItems = await provider.getInvoiceItems(editId);
3431
3507
  setItems(invoiceItems.map((it) => ({
@@ -3486,7 +3562,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3486
3562
  accountId: counterpartyAccounting.defaultAccountId,
3487
3563
  costCenterId: counterpartyAccounting.defaultCostCenterId
3488
3564
  };
3489
- setItems([...items, newItem]);
3565
+ setItems((current) => [...current, newItem]);
3490
3566
  setExpandedItemId(newItem._id);
3491
3567
  setAddingItem(false);
3492
3568
  }
@@ -3499,10 +3575,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3499
3575
  else addItemOfKind(defaultItemKind);
3500
3576
  }
3501
3577
  function updateItem(id, data) {
3502
- setItems(items.map((it) => it._id === id ? { ...it, ...data } : it));
3578
+ setItems((current) => current.map((it) => it._id === id ? { ...it, ...data } : it));
3503
3579
  }
3504
3580
  function removeItem(id) {
3505
- setItems(items.filter((it) => it._id !== id));
3581
+ setItems((current) => current.filter((it) => it._id !== id));
3506
3582
  }
3507
3583
  useEffect(() => {
3508
3584
  if (isEdit || totalAmount <= 0 || installments.length > 0) return;
@@ -3585,7 +3661,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3585
3661
  const inst = [];
3586
3662
  for (let i = 0; i < count; i++) {
3587
3663
  const due = new Date(base);
3588
- due.setMonth(due.getMonth() + i + 1);
3664
+ due.setMonth(due.getMonth() + (count === 1 ? 0 : i + 1));
3589
3665
  inst.push({
3590
3666
  _id: fid(),
3591
3667
  amount: i === 0 ? perInstallment + remainder : perInstallment,
@@ -3634,25 +3710,16 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3634
3710
  });
3635
3711
  if (isEdit && editId) {
3636
3712
  await provider.updateInvoice(editId, {
3637
- invoiceDate,
3638
- contactId,
3639
- contactName: contactName || void 0,
3640
3713
  unitId: unitId || void 0,
3641
3714
  fiscalNumber: fiscalNumber || void 0,
3642
- observations: observations || void 0,
3643
- totalAmount
3715
+ observations
3644
3716
  });
3645
3717
  if (provider.updateInstallment) {
3646
3718
  await Promise.all(installments.map((inst, i) => {
3647
3719
  const saved = savedMovements[i];
3648
3720
  if (!saved || saved.status === "paid") return null;
3649
- const dueChanged = inst.dueDate !== saved.dueDate;
3650
- const amountChanged = Number(inst.amount) !== Number(saved.amount);
3651
- if (!dueChanged && !amountChanged) return null;
3652
- return provider.updateInstallment(saved.id, {
3653
- ...dueChanged ? { dueDate: inst.dueDate } : {},
3654
- ...amountChanged ? { amount: inst.amount } : {}
3655
- });
3721
+ if (inst.dueDate === saved.dueDate) return null;
3722
+ return provider.updateInstallment(saved.id, { dueDate: inst.dueDate });
3656
3723
  }).filter(Boolean));
3657
3724
  }
3658
3725
  onSaved?.(editId);
@@ -3750,7 +3817,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3750
3817
  const scheduleSection = (
3751
3818
  // Tour anchor: the guided setup points here for the installment schedule.
3752
3819
  /* @__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") }) : (
3820
+ hasPayments ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.scheduleLocked") }) : isEdit ? null : (
3754
3821
  // Atalhos de parcelamento — 1x, 2x, 3x, 6x, 12x cobrem quase tudo.
3755
3822
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
3756
3823
  "button",
@@ -3877,6 +3944,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3877
3944
  FinancialCounterpartySearchSelect,
3878
3945
  {
3879
3946
  required: true,
3947
+ disabled: isEdit,
3880
3948
  lookup: contactLookup,
3881
3949
  value: contactId ?? "",
3882
3950
  displayValue: contactName,
@@ -3909,7 +3977,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3909
3977
  }
3910
3978
  })();
3911
3979
  },
3912
- onCreate: contactCreate.onCreate
3980
+ onCreate: contactCreate.onCreate,
3981
+ onEdit: contactCreate.onEdit
3913
3982
  }
3914
3983
  ),
3915
3984
  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: [
@@ -3935,10 +4004,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3935
4004
  ] }) });
3936
4005
  const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
3937
4006
  /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3938
- /* @__PURE__ */ jsx(LocationSelector, { locations: availableLocations, value: unitId, onChange: setUnitId }),
4007
+ showUnitField && /* @__PURE__ */ jsx(LocationSelector, { locations: unitChoices, value: unitId, onChange: setUnitId }),
3939
4008
  /* @__PURE__ */ jsxs("div", { children: [
3940
4009
  /* @__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" })
4010
+ 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
4011
  ] }),
3943
4012
  /* @__PURE__ */ jsxs("div", { children: [
3944
4013
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.fiscalNumber") }),
@@ -4017,6 +4086,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4017
4086
  parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
4018
4087
  onBack: () => onSaved?.(),
4019
4088
  actions,
4089
+ 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
4090
  sideSections,
4021
4091
  children: [
4022
4092
  contactSection,
@@ -4026,7 +4096,9 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4026
4096
  "data-tour": "financial.items",
4027
4097
  label: t2("financial.invoiceForm.items"),
4028
4098
  addLabel: t2("financial.invoiceForm.addItem"),
4029
- onAdd: contactId ? startAddItem : void 0,
4099
+ onAdd: isEdit ? void 0 : startAddItem,
4100
+ addDisabled: !contactId,
4101
+ addDisabledHint: t2("financial.invoiceForm.selectContactFirst"),
4030
4102
  count: items.length,
4031
4103
  countLabel: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
4032
4104
  totalLabel: t2("financial.invoiceForm.total"),
@@ -4058,6 +4130,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4058
4130
  currency,
4059
4131
  formatMoney: money3,
4060
4132
  expanded: expandedItemId === item._id,
4133
+ readOnly: isEdit,
4061
4134
  onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
4062
4135
  onChange: (patch) => updateItem(item._id, patch),
4063
4136
  onRemove: () => {
@@ -4130,7 +4203,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4130
4203
  surcharge: t2("financial.invoiceForm.surcharge"),
4131
4204
  adjustments: t2("financial.invoiceForm.discountSurcharge"),
4132
4205
  removeAdjustments: t2("financial.invoiceForm.removeAdjustments"),
4133
- done: t2("financial.invoiceForm.done")
4206
+ done: t2("financial.invoiceForm.done"),
4207
+ edit: t2("financial.invoiceForm.edit")
4134
4208
  }
4135
4209
  },
4136
4210
  item._id
@@ -4225,6 +4299,27 @@ function groupPaymentsByBill(bills, payments) {
4225
4299
  }
4226
4300
  return { byBillId, unattributed };
4227
4301
  }
4302
+
4303
+ // src/lib/payment-how.ts
4304
+ var NOT_A_HOLDER = /* @__PURE__ */ new Set(["person", "system"]);
4305
+ function paymentHolderId(p) {
4306
+ if (p.bankAccountId) return p.bankAccountId;
4307
+ return p.direction === "debit" ? p.creditAccountId : p.debitAccountId;
4308
+ }
4309
+ function describePayment(p, ctx2) {
4310
+ const registry = ctx2.paymentMethods.find((m) => m.id === p.paymentMethodId);
4311
+ const type = ctx2.paymentMethodTypes.find((tp) => tp.id === (registry?.paymentMethodTypeId ?? p.paymentMethodTypeId));
4312
+ let method = [type?.name, registry?.name].filter(Boolean).join(" \xB7 ") || void 0;
4313
+ if (!method && p.method) {
4314
+ const key = `financial.method.${p.method}`;
4315
+ const label = ctx2.t(key);
4316
+ method = label && label !== key ? label : p.method;
4317
+ }
4318
+ const holderId = paymentHolderId(p);
4319
+ const holder = holderId ? ctx2.accountsById.get(holderId) : void 0;
4320
+ const account = holder && !NOT_A_HOLDER.has(holder.accountType) ? holder : void 0;
4321
+ return { method, account };
4322
+ }
4228
4323
  var STATUS_COLORS = {
4229
4324
  open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
4230
4325
  pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
@@ -4254,12 +4349,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
4254
4349
  const [detailsLoaded, setDetailsLoaded] = useState(false);
4255
4350
  const [detailsLoading, setDetailsLoading] = useState(false);
4256
4351
  const [invoice, setInvoice] = useState(null);
4257
- const unitName = useUnitStore((st) => invoice?.unitId ? st.byId[invoice.unitId]?.name : void 0);
4352
+ const unitMode = useUnitMode();
4353
+ const unitName = useUnitStore((st) => unitMode && invoice?.unitId ? st.byId[invoice.unitId]?.name : void 0);
4258
4354
  const [items, setItems] = useState([]);
4259
4355
  const [attachments, setAttachments] = useState([]);
4260
4356
  const [viewing, setViewing] = useState(null);
4261
4357
  const [movements, setMovements] = useState([]);
4262
4358
  const [payments, setPayments] = useState([]);
4359
+ const [holders, setHolders] = useState(/* @__PURE__ */ new Map());
4263
4360
  const [loading, setLoading] = useState(true);
4264
4361
  const [error, setError] = useState(null);
4265
4362
  const [reversing, setReversing] = useState(null);
@@ -4308,9 +4405,9 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
4308
4405
  setMovements(
4309
4406
  movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
4310
4407
  );
4311
- setPayments(
4312
- movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "payment").sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? ""))
4313
- );
4408
+ const pays = movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "payment").sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? ""));
4409
+ setPayments(pays);
4410
+ if (pays.length > 0) void loadPaymentLabels(pays, req);
4314
4411
  } catch (e) {
4315
4412
  if (req !== reqRef.current) return;
4316
4413
  setError(e?.message ?? String(e));
@@ -4332,6 +4429,19 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
4332
4429
  document.addEventListener("mousedown", handleClick);
4333
4430
  return () => document.removeEventListener("mousedown", handleClick);
4334
4431
  }, [menuOpen]);
4432
+ async function loadPaymentLabels(pays, req) {
4433
+ const ids = [...new Set(pays.map(paymentHolderId).filter((id) => Boolean(id)))];
4434
+ try {
4435
+ const [accounts] = await Promise.all([
4436
+ ids.length > 0 && provider.getAccountsByIds ? provider.getAccountsByIds(ids) : Promise.resolve([]),
4437
+ fetchPaymentMethods(),
4438
+ fetchBankAccounts()
4439
+ ]);
4440
+ if (req !== reqRef.current) return;
4441
+ setHolders(new Map(accounts.map((a) => [a.id, a])));
4442
+ } catch {
4443
+ }
4444
+ }
4335
4445
  async function loadPaymentDetails() {
4336
4446
  if (detailsLoaded || detailsLoading) return;
4337
4447
  setDetailsLoading(true);
@@ -4387,6 +4497,9 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
4387
4497
  ] });
4388
4498
  }
4389
4499
  const settlements = groupPaymentsByBill(movements, payments);
4500
+ const accountsById = new Map(bankAccounts.map((a) => [a.id, a]));
4501
+ for (const [id, a] of holders) accountsById.set(id, a);
4502
+ const howCtx = { paymentMethods: paymentMethods2, paymentMethodTypes, accountsById, t: t2 };
4390
4503
  const statusConfig = STATUS_COLORS[invoice.status] ?? STATUS_COLORS.pending;
4391
4504
  statusConfig.icon;
4392
4505
  const totalPaid = movements.reduce((sum, m) => sum + m.paidAmount, 0);
@@ -4488,12 +4601,10 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
4488
4601
  const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
4489
4602
  const MovIcon = movStatus.icon;
4490
4603
  const instPayments = settlements.byBillId.get(mov.id) ?? [];
4491
- const paymentSummary = instPayments.map((payment) => {
4492
- const method = paymentMethods2.find((candidate) => candidate.id === payment.paymentMethodId);
4493
- const type = paymentMethodTypes.find((candidate) => candidate.id === (method?.paymentMethodTypeId ?? payment.paymentMethodTypeId));
4494
- const account = bankAccounts.find((candidate) => candidate.id === payment.bankAccountId);
4495
- return [type?.name, method?.name, account?.name].filter(Boolean).join(" \xB7 ");
4496
- }).filter(Boolean).join(" / ");
4604
+ const paymentSummary = [...new Set(instPayments.map((payment) => {
4605
+ const how = describePayment(payment, howCtx);
4606
+ return [how.method, how.account?.name].filter(Boolean).join(" \xB7 ");
4607
+ }).filter(Boolean))].join(" / ");
4497
4608
  return /* @__PURE__ */ jsxs("div", { children: [
4498
4609
  /* @__PURE__ */ jsxs(
4499
4610
  "div",
@@ -4617,19 +4728,12 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
4617
4728
  /* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
4618
4729
  /* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
4619
4730
  ] }) : instPayments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
4620
- const pMethod = paymentMethods2.find((m) => m.id === p.paymentMethodId);
4621
- const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
4622
- const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
4731
+ const how = describePayment(p, howCtx);
4623
4732
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
4624
4733
  /* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
4625
- pType && /* @__PURE__ */ jsxs(Fragment, { children: [
4734
+ how.method && /* @__PURE__ */ jsxs(Fragment, { children: [
4626
4735
  /* @__PURE__ */ jsx("span", { children: "\xB7" }),
4627
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pType.name }),
4628
- pMethod && /* @__PURE__ */ jsxs("span", { children: [
4629
- "(",
4630
- pMethod.name,
4631
- ")"
4632
- ] })
4736
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: how.method })
4633
4737
  ] }),
4634
4738
  p.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
4635
4739
  /* @__PURE__ */ jsx("span", { children: "\xB7" }),
@@ -4640,10 +4744,10 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
4640
4744
  "x"
4641
4745
  ] })
4642
4746
  ] }),
4643
- pAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
4747
+ how.account && /* @__PURE__ */ jsxs(Fragment, { children: [
4644
4748
  /* @__PURE__ */ jsx("span", { children: "\xB7" }),
4645
4749
  /* @__PURE__ */ jsx(Building2, { className: "h-3 w-3 shrink-0" }),
4646
- /* @__PURE__ */ jsx("span", { children: pAccount.name })
4750
+ /* @__PURE__ */ jsx("span", { children: how.account.name })
4647
4751
  ] }),
4648
4752
  (p.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
4649
4753
  /* @__PURE__ */ jsx("span", { children: "\xB7" }),
@@ -4659,7 +4763,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
4659
4763
  ] }),
4660
4764
  p.notes && /* @__PURE__ */ jsxs(Fragment, { children: [
4661
4765
  /* @__PURE__ */ jsx("span", { children: "\xB7" }),
4662
- /* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", children: p.notes })
4766
+ /* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", title: p.notes, children: p.notes })
4663
4767
  ] })
4664
4768
  ] }, p.id);
4665
4769
  }) }) }) })
@@ -8242,22 +8346,22 @@ function buildNav(config, activeView, navigate, t2) {
8242
8346
  onClick: () => navigate("cash-registers")
8243
8347
  });
8244
8348
  }
8245
- items.push({
8349
+ items.push(config.modules.reconciliation ? {
8350
+ id: "statements",
8351
+ label: t2("financial.nav.statements"),
8352
+ icon: "Receipt",
8353
+ active: activeView === "statements" || activeView === "reconciliation",
8354
+ children: [
8355
+ { id: "statements-list", label: t2("financial.nav.list"), active: activeView === "statements", onClick: () => navigate("statements") },
8356
+ { id: "reconciliation", label: t2("financial.nav.reconciliation"), active: activeView === "reconciliation", onClick: () => navigate("reconciliation") }
8357
+ ]
8358
+ } : {
8246
8359
  id: "statements",
8247
8360
  label: t2("financial.nav.statements"),
8248
8361
  icon: "Receipt",
8249
8362
  active: activeView === "statements",
8250
8363
  onClick: () => navigate("statements")
8251
8364
  });
8252
- if (config.modules.reconciliation) {
8253
- items.push({
8254
- id: "reconciliation",
8255
- label: t2("financial.nav.reconciliation"),
8256
- icon: "ArrowLeftRight",
8257
- active: activeView === "reconciliation",
8258
- onClick: () => navigate("reconciliation")
8259
- });
8260
- }
8261
8365
  if (config.modules.notasFiscais) {
8262
8366
  items.push({
8263
8367
  id: "notas-fiscais",
@@ -8303,10 +8407,14 @@ function buildNav(config, activeView, navigate, t2) {
8303
8407
  onClick: () => navigate("cards")
8304
8408
  });
8305
8409
  }
8306
- for (const item of items) {
8307
- const feature = config.navFeatureMap[item.id];
8308
- if (feature) item.feature = feature;
8309
- }
8410
+ const tag = (list) => {
8411
+ for (const item of list) {
8412
+ const feature = config.navFeatureMap[item.id];
8413
+ if (feature) item.feature = feature;
8414
+ if (item.children) tag(item.children);
8415
+ }
8416
+ };
8417
+ tag(items);
8310
8418
  return items;
8311
8419
  }
8312
8420
  function FinancialPage({ config: appConfig, provider, store: store6, registries }) {
@@ -9201,6 +9309,9 @@ function createMockFinancialProvider(options) {
9201
9309
  async getBankAccounts() {
9202
9310
  return store6.bankAccounts.filter((a) => a.isActive);
9203
9311
  },
9312
+ async getAccountsByIds(ids) {
9313
+ return store6.bankAccounts.filter((a) => ids.includes(a.id));
9314
+ },
9204
9315
  async searchFinancialAccounts(query, limit = 20) {
9205
9316
  const term = query.trim().toLocaleLowerCase("pt-BR");
9206
9317
  if (term.length < 2) return [];
@@ -9883,7 +9994,8 @@ function createSupabaseFinancialProvider() {
9883
9994
  const page = query.page ?? 1;
9884
9995
  const pageSize = query.pageSize ?? 50;
9885
9996
  qb = qb.range((page - 1) * pageSize, page * pageSize - 1).order("due_date", { ascending: true });
9886
- const { data, count } = await qb;
9997
+ const { data, count, error } = await qb;
9998
+ if (error) throw error;
9887
9999
  const linhas = (data ?? []).map(rowToInvoiceLine);
9888
10000
  const titulos = [...new Set(linhas.map((l) => l.invoiceId).filter(Boolean))];
9889
10001
  if (titulos.length) {
@@ -10062,11 +10174,14 @@ function createSupabaseFinancialProvider() {
10062
10174
  if (partial.invoiceDate !== void 0) row.competence_date = partial.invoiceDate;
10063
10175
  if (partial.contactId !== void 0) row.counterparty_person_id = partial.contactId ?? null;
10064
10176
  if (partial.unitId !== void 0) row.unit_id = partial.unitId ?? null;
10065
- if (partial.fiscalNumber !== void 0) row.number = partial.fiscalNumber ?? null;
10066
10177
  if (partial.totalAmount !== void 0) row.total = partial.totalAmount;
10067
- if (partial.contactName !== void 0) {
10178
+ if (partial.contactName !== void 0 || "fiscalNumber" in partial) {
10068
10179
  const { data: cur } = await pub.from("plg_financial_invoices").select("metadata").eq("id", id).maybeSingle();
10069
- row.metadata = { ...cur?.metadata ?? {}, contactName: partial.contactName };
10180
+ row.metadata = {
10181
+ ...cur?.metadata ?? {},
10182
+ ...partial.contactName !== void 0 ? { contactName: partial.contactName } : {},
10183
+ ..."fiscalNumber" in partial ? { fiscalNumber: partial.fiscalNumber ?? null } : {}
10184
+ };
10070
10185
  }
10071
10186
  if (Object.keys(row).length > 0) {
10072
10187
  const { error } = await pub.from("plg_financial_invoices").update(row).eq("id", id);
@@ -10391,6 +10506,16 @@ function createSupabaseFinancialProvider() {
10391
10506
  accountType: r.kind
10392
10507
  }));
10393
10508
  },
10509
+ async getAccountsByIds(ids) {
10510
+ if (ids.length === 0) return [];
10511
+ const { pub } = getClients();
10512
+ const { data, error } = await pub.from("plg_financial_accounts").select("*").in("id", ids);
10513
+ if (error) throw error;
10514
+ return (data ?? []).map((r) => ({
10515
+ ...snakeToCamel(r),
10516
+ accountType: r.kind
10517
+ }));
10518
+ },
10394
10519
  async searchFinancialAccounts(query, limit = 20) {
10395
10520
  const term = query.trim();
10396
10521
  if (term.length < 2) return [];
@@ -11779,6 +11904,482 @@ function createFinancialStore(provider) {
11779
11904
  }));
11780
11905
  }
11781
11906
 
11907
+ // src/lib/chart-tree.ts
11908
+ var ROOT = "";
11909
+ function compareCodes(a, b) {
11910
+ return a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" });
11911
+ }
11912
+ function buildChartTree(nodes) {
11913
+ const byId = new Map(nodes.map((n) => [n.id, n]));
11914
+ const children = /* @__PURE__ */ new Map();
11915
+ for (const n of nodes) {
11916
+ const parent = n.parentId && n.parentId !== n.id && byId.has(n.parentId) ? n.parentId : ROOT;
11917
+ const list = children.get(parent) ?? [];
11918
+ list.push(n.id);
11919
+ children.set(parent, list);
11920
+ }
11921
+ for (const list of children.values()) {
11922
+ list.sort((a, b) => compareCodes(byId.get(a).code, byId.get(b).code));
11923
+ }
11924
+ breakCycles(byId, children);
11925
+ return { byId, children };
11926
+ }
11927
+ function breakCycles(byId, children) {
11928
+ const reached = /* @__PURE__ */ new Set();
11929
+ const walk = (id) => {
11930
+ if (reached.has(id)) return;
11931
+ reached.add(id);
11932
+ for (const c of children.get(id) ?? []) walk(c);
11933
+ };
11934
+ for (const r of children.get(ROOT) ?? []) walk(r);
11935
+ for (const id of byId.keys()) {
11936
+ if (reached.has(id)) continue;
11937
+ const parent = byId.get(id).parentId;
11938
+ children.set(parent, (children.get(parent) ?? []).filter((c) => c !== id));
11939
+ children.set(ROOT, [...children.get(ROOT) ?? [], id].sort((a, b) => compareCodes(byId.get(a).code, byId.get(b).code)));
11940
+ walk(id);
11941
+ }
11942
+ }
11943
+ function rootIds(tree) {
11944
+ return tree.children.get(ROOT) ?? [];
11945
+ }
11946
+ function childIds(tree, id) {
11947
+ return tree.children.get(id) ?? [];
11948
+ }
11949
+ function isGroupNode(tree, node) {
11950
+ return node.nodeType === "group" || childIds(tree, node.id).length > 0;
11951
+ }
11952
+ function descendantIds(tree, id) {
11953
+ const out = /* @__PURE__ */ new Set();
11954
+ const stack = [...childIds(tree, id)];
11955
+ while (stack.length) {
11956
+ const next = stack.pop();
11957
+ if (out.has(next)) continue;
11958
+ out.add(next);
11959
+ stack.push(...childIds(tree, next));
11960
+ }
11961
+ return out;
11962
+ }
11963
+ function normalize2(text2) {
11964
+ return text2.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
11965
+ }
11966
+ function searchVisibleIds(tree, query) {
11967
+ const q = normalize2(query.trim());
11968
+ if (!q) return null;
11969
+ const visible = /* @__PURE__ */ new Set();
11970
+ const matches = /* @__PURE__ */ new Set();
11971
+ for (const n of tree.byId.values()) {
11972
+ if (!normalize2(`${n.code} ${n.name}`).includes(q)) continue;
11973
+ matches.add(n.id);
11974
+ let cur = n;
11975
+ while (cur && !visible.has(cur.id)) {
11976
+ visible.add(cur.id);
11977
+ cur = cur.parentId ? tree.byId.get(cur.parentId) : void 0;
11978
+ }
11979
+ }
11980
+ return { visible, matches };
11981
+ }
11982
+ function suggestNextCode(tree, parentId) {
11983
+ const parent = parentId ? tree.byId.get(parentId) : void 0;
11984
+ const siblings = (parentId ? childIds(tree, parentId) : rootIds(tree)).map((id) => tree.byId.get(id).code).filter((c) => !c.startsWith("("));
11985
+ const prefix = parent ? `${parent.code}.` : "";
11986
+ let best = null;
11987
+ for (const code of siblings) {
11988
+ const m = /^(.*?)(\d+)$/.exec(code);
11989
+ if (!m) continue;
11990
+ const n2 = Number(m[2]);
11991
+ if (!best || n2 > best.n) best = { n: n2, width: m[2].length, head: m[1] };
11992
+ }
11993
+ const taken = new Set([...tree.byId.values()].map((n2) => n2.code));
11994
+ let candidate;
11995
+ if (best) {
11996
+ let n2 = best.n + 1;
11997
+ do {
11998
+ candidate = `${best.head}${String(n2).padStart(best.width, "0")}`;
11999
+ n2++;
12000
+ } while (taken.has(candidate));
12001
+ return candidate;
12002
+ }
12003
+ let n = 1;
12004
+ do {
12005
+ candidate = parent ? `${prefix}${String(n).padStart(2, "0")}` : String(n);
12006
+ n++;
12007
+ } while (taken.has(candidate));
12008
+ return candidate;
12009
+ }
12010
+ var NATURES = ["revenue", "expense", "asset", "liability", "equity"];
12011
+ var NATURE_VARIANT = {
12012
+ revenue: "success",
12013
+ expense: "destructive",
12014
+ asset: "info",
12015
+ liability: "warning",
12016
+ equity: "magic"
12017
+ };
12018
+ var PAGE = 1e3;
12019
+ async function loadAll(provider) {
12020
+ const rows = [];
12021
+ for (let page = 1; ; page++) {
12022
+ const res = await provider.list({ page, pageSize: PAGE, sortBy: "code", sortDir: "asc", countMode: "none" });
12023
+ rows.push(...res.data);
12024
+ if (res.data.length < PAGE) return rows;
12025
+ }
12026
+ }
12027
+ function errorMessage(err) {
12028
+ if (err && typeof err === "object" && "message" in err) return String(err.message);
12029
+ return String(err);
12030
+ }
12031
+ function ChartOfAccountsTree({ entity, mockData, dataProvider }) {
12032
+ const t2 = useTranslation();
12033
+ const can = usePermissionOptional();
12034
+ const canCreate = can("financial", "create");
12035
+ const canEdit = can("financial", "edit");
12036
+ const canDelete = can("financial", "delete");
12037
+ const provider = React20__default.useMemo(
12038
+ () => dataProvider ?? resolveDataProvider(entity, mockData ?? []),
12039
+ [entity, mockData, dataProvider]
12040
+ );
12041
+ const table = entity.data?.table;
12042
+ const [nodes, setNodes] = React20__default.useState(null);
12043
+ const [loadError, setLoadError] = React20__default.useState(null);
12044
+ const [expanded, setExpanded] = React20__default.useState(/* @__PURE__ */ new Set());
12045
+ const [query, setQuery] = React20__default.useState("");
12046
+ const [showInactive, setShowInactive] = React20__default.useState(true);
12047
+ const [editing, setEditing] = React20__default.useState(null);
12048
+ const [confirmDelete, setConfirmDelete] = React20__default.useState(null);
12049
+ const [busy, setBusy] = React20__default.useState(false);
12050
+ const initialized = React20__default.useRef(false);
12051
+ const reload = React20__default.useCallback(async () => {
12052
+ try {
12053
+ const rows = await loadAll(provider);
12054
+ setNodes(rows);
12055
+ setLoadError(null);
12056
+ if (!initialized.current) {
12057
+ initialized.current = true;
12058
+ const tree2 = buildChartTree(rows);
12059
+ setExpanded(new Set(rootIds(tree2)));
12060
+ }
12061
+ } catch (err) {
12062
+ setLoadError(errorMessage(err));
12063
+ setNodes((prev) => prev ?? []);
12064
+ }
12065
+ }, [provider]);
12066
+ React20__default.useEffect(() => {
12067
+ void reload();
12068
+ }, [reload]);
12069
+ const shown = React20__default.useMemo(
12070
+ () => (nodes ?? []).filter((n) => showInactive || n.isActive),
12071
+ [nodes, showInactive]
12072
+ );
12073
+ const tree = React20__default.useMemo(() => buildChartTree(shown), [shown]);
12074
+ const fullTree = React20__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
12075
+ const search = React20__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
12076
+ const groupIds = React20__default.useMemo(
12077
+ () => [...tree.byId.values()].filter((n) => isGroupNode(tree, n)).map((n) => n.id),
12078
+ [tree]
12079
+ );
12080
+ function toggle(id) {
12081
+ setExpanded((prev) => {
12082
+ const next = new Set(prev);
12083
+ if (next.has(id)) next.delete(id);
12084
+ else next.add(id);
12085
+ return next;
12086
+ });
12087
+ }
12088
+ function startCreate(parentId) {
12089
+ const parent = parentId ? fullTree.byId.get(parentId) : void 0;
12090
+ setConfirmDelete(null);
12091
+ setEditing({
12092
+ mode: "create",
12093
+ parentId,
12094
+ draft: {
12095
+ code: suggestNextCode(fullTree, parentId),
12096
+ name: "",
12097
+ nodeType: "leaf",
12098
+ accountType: parent?.accountType ?? "",
12099
+ parentId
12100
+ }
12101
+ });
12102
+ if (parentId) setExpanded((prev) => new Set(prev).add(parentId));
12103
+ }
12104
+ function startEdit(node) {
12105
+ setConfirmDelete(null);
12106
+ setEditing({
12107
+ mode: "edit",
12108
+ id: node.id,
12109
+ draft: {
12110
+ code: node.code,
12111
+ name: node.name,
12112
+ nodeType: node.nodeType === "group" ? "group" : "leaf",
12113
+ accountType: node.accountType ?? "",
12114
+ parentId: node.parentId ?? null
12115
+ }
12116
+ });
12117
+ }
12118
+ function validate(draft, selfId) {
12119
+ const code = draft.code.trim();
12120
+ if (!code) return t2("financial.chartTree.error.codeRequired");
12121
+ if (!draft.name.trim()) return t2("financial.chartTree.error.nameRequired");
12122
+ const clash = (nodes ?? []).find((n) => n.code === code && n.id !== selfId);
12123
+ if (clash) return t2("financial.chartTree.error.codeTaken", { code, name: clash.name });
12124
+ if (selfId && draft.nodeType === "leaf" && childIds(fullTree, selfId).length > 0) {
12125
+ return t2("financial.chartTree.error.leafWithChildren");
12126
+ }
12127
+ return null;
12128
+ }
12129
+ async function run(action, success) {
12130
+ setBusy(true);
12131
+ try {
12132
+ await action();
12133
+ if (table) emitDataChanged({ table });
12134
+ toast.success(success);
12135
+ await reload();
12136
+ return true;
12137
+ } catch (err) {
12138
+ toast.error(t2("financial.chartTree.error.save"), { description: errorMessage(err) });
12139
+ return false;
12140
+ } finally {
12141
+ setBusy(false);
12142
+ }
12143
+ }
12144
+ async function submit() {
12145
+ if (!editing) return;
12146
+ const { draft } = editing;
12147
+ const invalid = validate(draft, editing.mode === "edit" ? editing.id : void 0);
12148
+ if (invalid) {
12149
+ toast.error(invalid);
12150
+ return;
12151
+ }
12152
+ const payload = {
12153
+ code: draft.code.trim(),
12154
+ name: draft.name.trim(),
12155
+ nodeType: draft.nodeType,
12156
+ accountType: draft.accountType || null,
12157
+ parentId: draft.parentId
12158
+ };
12159
+ 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 }));
12160
+ if (ok) setEditing(null);
12161
+ }
12162
+ async function setActive(node, isActive) {
12163
+ await run(
12164
+ () => provider.update(node.id, { isActive }),
12165
+ t2(isActive ? "financial.chartTree.reactivated" : "financial.chartTree.deactivated", { name: node.name })
12166
+ );
12167
+ }
12168
+ async function remove(node) {
12169
+ const ok = await run(() => provider.remove(node.id), t2("financial.chartTree.deleted", { name: node.name }));
12170
+ if (ok) setConfirmDelete(null);
12171
+ }
12172
+ const parentOptions = React20__default.useCallback((q, selfId) => {
12173
+ const blocked = selfId ? descendantIds(fullTree, selfId).add(selfId) : /* @__PURE__ */ new Set();
12174
+ const needle = q.trim().toLowerCase();
12175
+ 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) }));
12176
+ }, [fullTree]);
12177
+ if (nodes === null) {
12178
+ 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)) });
12179
+ }
12180
+ const roots = rootIds(tree).filter((id) => !search || search.visible.has(id));
12181
+ const inactiveCount = (nodes ?? []).filter((n) => !n.isActive).length;
12182
+ function renderForm(draft, level, selfId) {
12183
+ const update = (patch) => setEditing((prev) => prev ? { ...prev, draft: { ...prev.draft, ...patch } } : prev);
12184
+ const parent = draft.parentId ? fullTree.byId.get(draft.parentId) : void 0;
12185
+ return (
12186
+ // Not a <form>: Enter would "click" the first button inside SearchSelect
12187
+ // (its clear action) and wipe the parent instead of saving.
12188
+ /* @__PURE__ */ jsxs(
12189
+ "div",
12190
+ {
12191
+ role: "group",
12192
+ className: "rounded-md border border-border bg-muted/40 p-3 space-y-3",
12193
+ style: { marginLeft: Math.min(level, 8) * 16 },
12194
+ onKeyDown: (e) => {
12195
+ if (e.key === "Escape") setEditing(null);
12196
+ if (e.key === "Enter" && e.target.dataset.submitOnEnter) {
12197
+ e.preventDefault();
12198
+ void submit();
12199
+ }
12200
+ },
12201
+ children: [
12202
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-[8rem_1fr]", children: [
12203
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
12204
+ t2("financial.chartTree.field.code"),
12205
+ /* @__PURE__ */ jsx(Input, { "data-submit-on-enter": "1", value: draft.code, onChange: (e) => update({ code: e.target.value }), required: true })
12206
+ ] }),
12207
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
12208
+ t2("financial.chartTree.field.name"),
12209
+ /* @__PURE__ */ jsx(Input, { "data-submit-on-enter": "1", autoFocus: true, value: draft.name, onChange: (e) => update({ name: e.target.value }), required: true })
12210
+ ] })
12211
+ ] }),
12212
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [
12213
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
12214
+ t2("financial.chartTree.field.structure"),
12215
+ /* @__PURE__ */ jsxs(
12216
+ "select",
12217
+ {
12218
+ className: "flex h-9 w-full rounded-md border border-input bg-background px-2 text-sm text-foreground",
12219
+ value: draft.nodeType,
12220
+ onChange: (e) => update({ nodeType: e.target.value }),
12221
+ children: [
12222
+ /* @__PURE__ */ jsx("option", { value: "leaf", children: t2("financial.chartTree.leaf") }),
12223
+ /* @__PURE__ */ jsx("option", { value: "group", children: t2("financial.chartTree.group") })
12224
+ ]
12225
+ }
12226
+ )
12227
+ ] }),
12228
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
12229
+ t2("financial.chartTree.field.nature"),
12230
+ /* @__PURE__ */ jsxs(
12231
+ "select",
12232
+ {
12233
+ className: "flex h-9 w-full rounded-md border border-input bg-background px-2 text-sm text-foreground",
12234
+ value: draft.accountType,
12235
+ onChange: (e) => update({ accountType: e.target.value }),
12236
+ children: [
12237
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.chartTree.nature.none") }),
12238
+ NATURES.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: t2(`financial.chartTree.nature.${n}`) }, n))
12239
+ ]
12240
+ }
12241
+ )
12242
+ ] }),
12243
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
12244
+ t2("financial.chartTree.field.parent"),
12245
+ /* @__PURE__ */ jsx(
12246
+ SearchSelect,
12247
+ {
12248
+ value: draft.parentId ?? void 0,
12249
+ displayValue: parent ? accountLabel(parent) : void 0,
12250
+ onChange: (id) => update({ parentId: id || null }),
12251
+ onSearch: (q) => parentOptions(q, selfId),
12252
+ minChars: 0,
12253
+ placeholder: t2("financial.chartTree.rootLevel"),
12254
+ noResultsLabel: t2("financial.chartTree.noGroups"),
12255
+ searchingLabel: t2("financial.chartTree.searching"),
12256
+ clearLabel: t2("financial.chartTree.rootLevel")
12257
+ }
12258
+ )
12259
+ ] })
12260
+ ] }),
12261
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [
12262
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setEditing(null), disabled: busy, children: t2("financial.chartTree.cancel") }),
12263
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", disabled: busy, onClick: () => void submit(), children: busy ? t2("financial.chartTree.saving") : t2("financial.chartTree.save") })
12264
+ ] })
12265
+ ]
12266
+ }
12267
+ )
12268
+ );
12269
+ }
12270
+ function renderNode(id, level) {
12271
+ const node = tree.byId.get(id);
12272
+ const kids = childIds(tree, id).filter((c) => !search || search.visible.has(c));
12273
+ const group = isGroupNode(tree, node);
12274
+ const open = search ? true : expanded.has(id);
12275
+ const isEditingThis = editing?.mode === "edit" && editing.id === id;
12276
+ const creatingHere = editing?.mode === "create" && editing.parentId === id;
12277
+ const isMatch = search?.matches.has(id);
12278
+ const hasAnyChildren = childIds(fullTree, id).length > 0;
12279
+ const FolderIcon = open ? FolderOpen : Folder;
12280
+ return /* @__PURE__ */ jsxs("li", { role: "treeitem", "aria-level": level + 1, "aria-expanded": group ? open : void 0, children: [
12281
+ isEditingThis && editing ? renderForm(editing.draft, level, id) : /* @__PURE__ */ jsxs(
12282
+ "div",
12283
+ {
12284
+ 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"),
12285
+ style: { paddingLeft: 8 + Math.min(level, 8) * 16 },
12286
+ children: [
12287
+ /* @__PURE__ */ jsx(
12288
+ "button",
12289
+ {
12290
+ type: "button",
12291
+ className: "flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted " + (kids.length ? "" : "invisible"),
12292
+ onClick: () => toggle(id),
12293
+ "aria-label": open ? t2("financial.chartTree.collapse") : t2("financial.chartTree.expand"),
12294
+ disabled: !!search,
12295
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 transition-transform " + (open ? "rotate-90" : "") })
12296
+ }
12297
+ ),
12298
+ 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" }),
12299
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-xs tabular-nums text-muted-foreground", children: node.code }),
12300
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm " + (group ? "font-semibold" : "") + (isMatch ? " text-primary" : ""), children: node.name }),
12301
+ /* @__PURE__ */ jsxs("span", { className: "hidden shrink-0 items-center gap-1 sm:flex", children: [
12302
+ node.accountType && /* @__PURE__ */ jsx(Badge, { variant: NATURE_VARIANT[node.accountType] ?? "default", dot: false, children: t2(`financial.chartTree.nature.${node.accountType}`) }),
12303
+ !node.isActive && /* @__PURE__ */ jsx(Badge, { variant: "outline", children: t2("financial.chartTree.inactive") })
12304
+ ] }),
12305
+ confirmDelete === id ? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
12306
+ /* @__PURE__ */ jsx("span", { className: "hidden text-xs text-muted-foreground md:inline", children: t2("financial.chartTree.confirmDelete") }),
12307
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "destructive", disabled: busy, onClick: () => void remove(node), children: t2("financial.chartTree.delete") }),
12308
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onClick: () => setConfirmDelete(null), children: t2("financial.chartTree.cancel") })
12309
+ ] }) : /* @__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: [
12310
+ 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" }) }),
12311
+ 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" }) }),
12312
+ 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),
12313
+ 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" }) }),
12314
+ canDelete && !hasAnyChildren && /* @__PURE__ */ jsx(Button, { size: "icon", variant: "ghost", title: t2("financial.chartTree.delete"), "aria-label": t2("financial.chartTree.delete"), onClick: () => {
12315
+ setEditing(null);
12316
+ setConfirmDelete(id);
12317
+ }, disabled: busy, children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }) })
12318
+ ] })
12319
+ ]
12320
+ }
12321
+ ),
12322
+ open && (kids.length > 0 || creatingHere) && /* @__PURE__ */ jsxs("ul", { role: "group", className: "space-y-0.5", children: [
12323
+ creatingHere && editing && /* @__PURE__ */ jsx("li", { children: renderForm(editing.draft, level + 1) }),
12324
+ kids.map((c) => renderNode(c, level + 1))
12325
+ ] })
12326
+ ] }, id);
12327
+ }
12328
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
12329
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
12330
+ /* @__PURE__ */ jsxs("div", { className: "relative min-w-[12rem] flex-1", children: [
12331
+ /* @__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" }),
12332
+ /* @__PURE__ */ jsx(
12333
+ Input,
12334
+ {
12335
+ value: query,
12336
+ onChange: (e) => setQuery(e.target.value),
12337
+ placeholder: t2("financial.chartTree.searchPlaceholder"),
12338
+ className: "pl-8 pr-8",
12339
+ "aria-label": t2("financial.chartTree.searchPlaceholder")
12340
+ }
12341
+ ),
12342
+ query && /* @__PURE__ */ jsx(
12343
+ "button",
12344
+ {
12345
+ type: "button",
12346
+ className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
12347
+ onClick: () => setQuery(""),
12348
+ "aria-label": t2("financial.chartTree.clearSearch"),
12349
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
12350
+ }
12351
+ )
12352
+ ] }),
12353
+ /* @__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: [
12354
+ /* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-4 w-4" }),
12355
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t2("financial.chartTree.expandAll") })
12356
+ ] }),
12357
+ /* @__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: [
12358
+ /* @__PURE__ */ jsx(ChevronsDownUp, { className: "h-4 w-4" }),
12359
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t2("financial.chartTree.collapseAll") })
12360
+ ] }),
12361
+ canCreate && /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => startCreate(null), disabled: busy, children: [
12362
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
12363
+ t2("financial.chartTree.addRoot")
12364
+ ] })
12365
+ ] }),
12366
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground", children: [
12367
+ /* @__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 }) }),
12368
+ inactiveCount > 0 && /* @__PURE__ */ jsxs("label", { className: "inline-flex cursor-pointer items-center gap-1.5", children: [
12369
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: showInactive, onChange: (e) => setShowInactive(e.target.checked) }),
12370
+ t2("financial.chartTree.showInactive", { count: inactiveCount })
12371
+ ] })
12372
+ ] }),
12373
+ loadError && /* @__PURE__ */ jsxs("p", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive", children: [
12374
+ t2("financial.chartTree.error.load"),
12375
+ " ",
12376
+ loadError
12377
+ ] }),
12378
+ editing?.mode === "create" && editing.parentId === null && renderForm(editing.draft, 0),
12379
+ 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)) })
12380
+ ] });
12381
+ }
12382
+
11782
12383
  // src/data/registries.ts
11783
12384
  var paymentMethodTypeEntity = {
11784
12385
  name: "Payment Type",
@@ -11846,6 +12447,24 @@ var chartOfAccountsEntity = {
11846
12447
  ],
11847
12448
  data: { table: T.chartOfAccounts, tenantScoped: true }
11848
12449
  };
12450
+ var chartOfAccountsSeed = [
12451
+ { id: "coa-1", code: "1", name: "Revenue", nodeType: "group", accountType: "revenue", isActive: true },
12452
+ { id: "coa-1.1", code: "1.1", name: "Sales Revenue", nodeType: "group", accountType: "revenue", parentId: "coa-1", isActive: true },
12453
+ { id: "coa-1.1.01", code: "1.1.01", name: "Services", nodeType: "leaf", accountType: "revenue", parentId: "coa-1.1", isActive: true },
12454
+ { id: "coa-1.1.02", code: "1.1.02", name: "Products", nodeType: "leaf", accountType: "revenue", parentId: "coa-1.1", isActive: true },
12455
+ { id: "coa-2", code: "2", name: "Other Revenue", nodeType: "group", accountType: "revenue", isActive: true },
12456
+ { id: "coa-2.1", code: "2.1", name: "Financial Income", nodeType: "leaf", accountType: "revenue", parentId: "coa-2", isActive: true },
12457
+ { id: "coa-3", code: "3", name: "Operational Costs", nodeType: "group", accountType: "expense", isActive: true },
12458
+ { id: "coa-3.1", code: "3.1", name: "Materials & Supplies", nodeType: "group", accountType: "expense", parentId: "coa-3", isActive: true },
12459
+ { id: "coa-3.1.01", code: "3.1.01", name: "Raw Materials", nodeType: "leaf", accountType: "expense", parentId: "coa-3.1", isActive: true },
12460
+ { id: "coa-3.1.02", code: "3.1.02", name: "Consumables", nodeType: "leaf", accountType: "expense", parentId: "coa-3.1", isActive: true },
12461
+ { id: "coa-4", code: "4", name: "Expenses", nodeType: "group", accountType: "expense", isActive: true },
12462
+ { id: "coa-4.1", code: "4.1", name: "Rent", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
12463
+ { id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
12464
+ { id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
12465
+ ];
12466
+ var ChartOfAccountsRegistryView = (props) => React20__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
12467
+ ChartOfAccountsRegistryView.displayName = "ChartOfAccountsRegistryView";
11849
12468
  var costCenterEntity = {
11850
12469
  name: "Cost Center",
11851
12470
  namePlural: "Cost Centers",
@@ -11973,22 +12592,8 @@ var financialRegistries = [
11973
12592
  icon: "BookOpen",
11974
12593
  description: "Hierarchical chart of accounts for categorizing transactions",
11975
12594
  display: "tree",
11976
- seedData: [
11977
- { id: "coa-1", code: "1", name: "Revenue", nodeType: "group", accountType: "revenue", isActive: true },
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
- ]
12595
+ component: ChartOfAccountsRegistryView,
12596
+ seedData: chartOfAccountsSeed
11992
12597
  },
11993
12598
  {
11994
12599
  id: "cost-centers",
@@ -12751,6 +13356,8 @@ var en = {
12751
13356
  "financial.invoice.reverseReason": "Reason for reversal",
12752
13357
  "financial.invoice.paymentSchedule": "Payment Schedule",
12753
13358
  "financial.invoiceForm.scheduleLocked": "Some installments are paid \u2014 the schedule can no longer be regenerated.",
13359
+ "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.",
13360
+ "financial.invoiceForm.edit": "Edit",
12754
13361
  "financial.invoice.payment": "Payment",
12755
13362
  "financial.invoice.balance": "Remaining",
12756
13363
  "financial.invoice.details": "Details",
@@ -12887,6 +13494,9 @@ var en = {
12887
13494
  "financial.payment.cancel": "Cancel",
12888
13495
  "financial.payment.card": "Card",
12889
13496
  "financial.payment.cardBrand": "Card Brand",
13497
+ "financial.payment.whichCard": "Which card",
13498
+ "financial.payment.noCompanyCards": "No company card registered. A card payment needs the card on file.",
13499
+ "financial.payment.registerCard": 'Register a card (account type "Card payable")',
12890
13500
  "financial.payment.cardInstallments": "Installments",
12891
13501
  "financial.payment.date": "Date",
12892
13502
  "financial.payment.installment": "Installment #{{number}}",
@@ -13221,7 +13831,59 @@ var en = {
13221
13831
  "financial.quickTx.descriptionPlaceholder": "e.g. Groceries, Salary\u2026",
13222
13832
  "financial.quickTx.enterAmount": "Enter an amount",
13223
13833
  "financial.quickTx.save": "Save",
13224
- "financial.quickTx.saving": "Saving\u2026"
13834
+ "financial.quickTx.saving": "Saving\u2026",
13835
+ // Chart of accounts tree
13836
+ "financial.chartTree.searchPlaceholder": "Search by code or name",
13837
+ "financial.chartTree.clearSearch": "Clear search",
13838
+ "financial.chartTree.expandAll": "Expand all",
13839
+ "financial.chartTree.collapseAll": "Collapse all",
13840
+ "financial.chartTree.expand": "Expand",
13841
+ "financial.chartTree.collapse": "Collapse",
13842
+ "financial.chartTree.addRoot": "New root account",
13843
+ "financial.chartTree.addChild": "New account inside this one",
13844
+ "financial.chartTree.edit": "Edit",
13845
+ "financial.chartTree.deactivate": "Deactivate",
13846
+ "financial.chartTree.reactivate": "Reactivate",
13847
+ "financial.chartTree.delete": "Delete",
13848
+ "financial.chartTree.confirmDelete": "Delete this account?",
13849
+ "financial.chartTree.cancel": "Cancel",
13850
+ "financial.chartTree.save": "Save",
13851
+ "financial.chartTree.saving": "Saving\u2026",
13852
+ "financial.chartTree.searching": "Searching\u2026",
13853
+ "financial.chartTree.count": "{{count}} accounts",
13854
+ "financial.chartTree.countOne": "1 account",
13855
+ "financial.chartTree.matchesOne": "1 account found",
13856
+ "financial.chartTree.matches": "{{count}} accounts found",
13857
+ "financial.chartTree.showInactive": "Show inactive ({{count}})",
13858
+ "financial.chartTree.inactive": "Inactive",
13859
+ "financial.chartTree.empty": "No accounts yet. Create the first root account.",
13860
+ "financial.chartTree.noMatches": "No account matches the search.",
13861
+ "financial.chartTree.noGroups": "No group found",
13862
+ "financial.chartTree.rootLevel": "Root level (no parent)",
13863
+ "financial.chartTree.group": "Group (holds other accounts)",
13864
+ "financial.chartTree.leaf": "Account (receives postings)",
13865
+ "financial.chartTree.field.code": "Code",
13866
+ "financial.chartTree.field.name": "Name",
13867
+ "financial.chartTree.field.structure": "Structure",
13868
+ "financial.chartTree.field.nature": "Nature",
13869
+ "financial.chartTree.field.parent": "Parent account",
13870
+ "financial.chartTree.nature.none": "No nature",
13871
+ "financial.chartTree.nature.revenue": "Revenue",
13872
+ "financial.chartTree.nature.expense": "Expense",
13873
+ "financial.chartTree.nature.asset": "Asset",
13874
+ "financial.chartTree.nature.liability": "Liability",
13875
+ "financial.chartTree.nature.equity": "Equity",
13876
+ "financial.chartTree.created": 'Account "{{name}}" created',
13877
+ "financial.chartTree.updated": 'Account "{{name}}" updated',
13878
+ "financial.chartTree.deactivated": 'Account "{{name}}" deactivated',
13879
+ "financial.chartTree.reactivated": 'Account "{{name}}" reactivated',
13880
+ "financial.chartTree.deleted": 'Account "{{name}}" deleted',
13881
+ "financial.chartTree.error.save": "Could not save. If the account already has postings, deactivate it instead of deleting.",
13882
+ "financial.chartTree.error.load": "Could not load the chart of accounts.",
13883
+ "financial.chartTree.error.codeRequired": "Enter the account code.",
13884
+ "financial.chartTree.error.nameRequired": "Enter the account name.",
13885
+ "financial.chartTree.error.codeTaken": 'Code {{code}} is already used by "{{name}}".',
13886
+ "financial.chartTree.error.leafWithChildren": "An account with accounts inside it must stay a group."
13225
13887
  };
13226
13888
 
13227
13889
  // src/locales/pt-BR.ts
@@ -13347,6 +14009,8 @@ var ptBR = {
13347
14009
  "financial.invoice.reverseReason": "Motivo do estorno",
13348
14010
  "financial.invoice.paymentSchedule": "Cronograma de Pagamento",
13349
14011
  "financial.invoiceForm.scheduleLocked": "Parcelas j\xE1 pagas \u2014 o parcelamento n\xE3o pode mais ser refeito.",
14012
+ "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.",
14013
+ "financial.invoiceForm.edit": "Editar",
13350
14014
  "financial.invoice.payment": "Pagamento",
13351
14015
  "financial.invoice.balance": "Restante",
13352
14016
  "financial.invoice.details": "Detalhes",
@@ -13486,6 +14150,9 @@ var ptBR = {
13486
14150
  "financial.payment.cancel": "Cancelar",
13487
14151
  "financial.payment.card": "Cart\xE3o",
13488
14152
  "financial.payment.cardBrand": "Bandeira do Cart\xE3o",
14153
+ "financial.payment.whichCard": "Qual cart\xE3o",
14154
+ "financial.payment.noCompanyCards": "Nenhum cart\xE3o da empresa cadastrado. Pagamento no cart\xE3o precisa do cart\xE3o cadastrado.",
14155
+ "financial.payment.registerCard": 'Cadastrar cart\xE3o (conta do tipo "Cart\xE3o a pagar")',
13489
14156
  "financial.payment.cardInstallments": "Parcelas",
13490
14157
  "financial.payment.date": "Data",
13491
14158
  "financial.payment.installment": "Parcela #{{number}}",
@@ -13820,7 +14487,59 @@ var ptBR = {
13820
14487
  "financial.quickTx.descriptionPlaceholder": "ex.: Mercado, Sal\xE1rio\u2026",
13821
14488
  "financial.quickTx.enterAmount": "Informe um valor",
13822
14489
  "financial.quickTx.save": "Salvar",
13823
- "financial.quickTx.saving": "Salvando\u2026"
14490
+ "financial.quickTx.saving": "Salvando\u2026",
14491
+ // Chart of accounts tree
14492
+ "financial.chartTree.searchPlaceholder": "Buscar por c\xF3digo ou nome",
14493
+ "financial.chartTree.clearSearch": "Limpar busca",
14494
+ "financial.chartTree.expandAll": "Expandir tudo",
14495
+ "financial.chartTree.collapseAll": "Recolher tudo",
14496
+ "financial.chartTree.expand": "Expandir",
14497
+ "financial.chartTree.collapse": "Recolher",
14498
+ "financial.chartTree.addRoot": "Nova conta raiz",
14499
+ "financial.chartTree.addChild": "Nova conta dentro desta",
14500
+ "financial.chartTree.edit": "Editar",
14501
+ "financial.chartTree.deactivate": "Desativar",
14502
+ "financial.chartTree.reactivate": "Reativar",
14503
+ "financial.chartTree.delete": "Excluir",
14504
+ "financial.chartTree.confirmDelete": "Excluir esta conta?",
14505
+ "financial.chartTree.cancel": "Cancelar",
14506
+ "financial.chartTree.save": "Salvar",
14507
+ "financial.chartTree.saving": "Salvando\u2026",
14508
+ "financial.chartTree.searching": "Buscando\u2026",
14509
+ "financial.chartTree.count": "{{count}} contas",
14510
+ "financial.chartTree.countOne": "1 conta",
14511
+ "financial.chartTree.matchesOne": "1 conta encontrada",
14512
+ "financial.chartTree.matches": "{{count}} contas encontradas",
14513
+ "financial.chartTree.showInactive": "Mostrar inativas ({{count}})",
14514
+ "financial.chartTree.inactive": "Inativa",
14515
+ "financial.chartTree.empty": "Nenhuma conta no plano ainda. Crie a primeira conta raiz.",
14516
+ "financial.chartTree.noMatches": "Nenhuma conta corresponde \xE0 busca.",
14517
+ "financial.chartTree.noGroups": "Nenhum grupo encontrado",
14518
+ "financial.chartTree.rootLevel": "N\xEDvel raiz (sem pai)",
14519
+ "financial.chartTree.group": "Grupo (agrupa outras contas)",
14520
+ "financial.chartTree.leaf": "Conta (recebe lan\xE7amentos)",
14521
+ "financial.chartTree.field.code": "C\xF3digo",
14522
+ "financial.chartTree.field.name": "Nome",
14523
+ "financial.chartTree.field.structure": "Estrutura",
14524
+ "financial.chartTree.field.nature": "Natureza",
14525
+ "financial.chartTree.field.parent": "Conta pai",
14526
+ "financial.chartTree.nature.none": "Sem natureza",
14527
+ "financial.chartTree.nature.revenue": "Receita",
14528
+ "financial.chartTree.nature.expense": "Despesa",
14529
+ "financial.chartTree.nature.asset": "Ativo",
14530
+ "financial.chartTree.nature.liability": "Passivo",
14531
+ "financial.chartTree.nature.equity": "Patrim\xF4nio",
14532
+ "financial.chartTree.created": 'Conta "{{name}}" criada',
14533
+ "financial.chartTree.updated": 'Conta "{{name}}" atualizada',
14534
+ "financial.chartTree.deactivated": 'Conta "{{name}}" desativada',
14535
+ "financial.chartTree.reactivated": 'Conta "{{name}}" reativada',
14536
+ "financial.chartTree.deleted": 'Conta "{{name}}" exclu\xEDda',
14537
+ "financial.chartTree.error.save": "N\xE3o foi poss\xEDvel salvar. Se a conta j\xE1 tem lan\xE7amentos, desative em vez de excluir.",
14538
+ "financial.chartTree.error.load": "N\xE3o foi poss\xEDvel carregar o plano de contas.",
14539
+ "financial.chartTree.error.codeRequired": "Informe o c\xF3digo da conta.",
14540
+ "financial.chartTree.error.nameRequired": "Informe o nome da conta.",
14541
+ "financial.chartTree.error.codeTaken": 'O c\xF3digo {{code}} j\xE1 \xE9 usado por "{{name}}".',
14542
+ "financial.chartTree.error.leafWithChildren": "Uma conta com contas dentro dela precisa continuar como grupo."
13824
14543
  };
13825
14544
 
13826
14545
  // src/locales/index.ts
@@ -35205,6 +35924,168 @@ GRANT EXECUTE ON FUNCTION public.plg_financial_copy_recurring_items() TO service
35205
35924
  COMMENT ON FUNCTION public.plg_financial_copy_recurring_items() IS
35206
35925
  'Copies every item of a recurring rule into each generated invoice, preserving per-line accounting.';
35207
35926
  `;
35927
+ var MIGRATION_092_A_CONTA_PAGA_NO_CARTAO_SAI_DO_CARTAO = `-- A bill paid by credit card is paid from the company's card.
35928
+ --
35929
+ -- financial_receive_payment settled every credit_card payment in a
35930
+ -- card_receivable_credit account \u2014 right for a sale (the acquirer owes us),
35931
+ -- wrong for a bill: paying a supplier with the corporate card puts the debt on
35932
+ -- that card (card_payable). The payables screen could never pay by card.
35933
+ -- Receivables keep the exact rule they had. Replaces the function in place;
35934
+ -- idempotent.
35935
+
35936
+ 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
35937
+ LANGUAGE plpgsql SECURITY DEFINER
35938
+ SET search_path TO ''
35939
+ AS $$
35940
+ DECLARE
35941
+ v_tenant uuid := public.plg_financial_require_tenant();
35942
+ v_actor uuid := auth.uid();
35943
+ v_inst_id uuid := p_installment_id;
35944
+ v_inst record;
35945
+ v_invoice public.plg_financial_invoices%ROWTYPE;
35946
+ v_amount numeric := round(coalesce(p_amount, 0), 2);
35947
+ v_open numeric;
35948
+ v_method text := lower(coalesce(nullif(btrim(p_method), ''), 'other'));
35949
+ v_holder uuid;
35950
+ v_holder_k text;
35951
+ v_session uuid := p_cash_session_id;
35952
+ v_counter uuid;
35953
+ v_credit numeric;
35954
+ v_mov_id uuid;
35955
+ v_alloc_id uuid;
35956
+ v_dir text;
35957
+ BEGIN
35958
+ IF v_amount <= 0 THEN
35959
+ RAISE EXCEPTION 'financial_receive_payment: amount must be > 0' USING ERRCODE = '22023';
35960
+ END IF;
35961
+ IF v_method NOT IN ('cash', 'pix', 'credit_card', 'debit_card', 'transfer', 'boleto', 'check', 'credit', 'other') THEN
35962
+ RAISE EXCEPTION 'financial_receive_payment: unknown method %', v_method USING ERRCODE = '22023';
35963
+ END IF;
35964
+
35965
+ -- accept a legacy per-order 'bill' movement id in place of the installment
35966
+ IF NOT EXISTS (SELECT 1 FROM public.plg_financial_invoice_installments i WHERE i.id = v_inst_id) THEN
35967
+ SELECT m.financial_invoice_installment_id INTO v_inst_id FROM public.plg_financial_movements m
35968
+ WHERE m.id = p_installment_id AND m.movement_kind = 'bill' AND m.tenant_id = v_tenant;
35969
+ END IF;
35970
+ SELECT i.* INTO v_inst FROM public.plg_financial_invoice_installments i WHERE i.id = v_inst_id AND i.tenant_id = v_tenant;
35971
+ IF NOT FOUND THEN
35972
+ RAISE EXCEPTION 'financial_receive_payment: installment % not found', p_installment_id USING ERRCODE = 'P0002';
35973
+ END IF;
35974
+ SELECT * INTO v_invoice FROM public.plg_financial_invoices WHERE id = v_inst.invoice_id;
35975
+ PERFORM public.plg_financial_require_permission('financial.create', v_inst.unit_id);
35976
+
35977
+ -- idempotency
35978
+ IF p_idempotency_key IS NOT NULL THEN
35979
+ PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);
35980
+ 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;
35981
+ IF v_mov_id IS NOT NULL THEN
35982
+ SELECT a.id INTO v_alloc_id FROM public.plg_financial_allocations a WHERE a.payment_movement_id = v_mov_id LIMIT 1;
35983
+ RETURN jsonb_build_object('movement_id', v_mov_id, 'allocation_id', v_alloc_id, 'installment_id', v_inst.id,
35984
+ 'invoice_id', v_inst.invoice_id, 'amount', v_amount, 'replayed', true);
35985
+ END IF;
35986
+ END IF;
35987
+
35988
+ IF v_inst.cancelled_at IS NOT NULL OR v_invoice.cancelled_at IS NOT NULL THEN
35989
+ RAISE EXCEPTION 'financial_receive_payment: installment is cancelled' USING ERRCODE = '55000';
35990
+ END IF;
35991
+ v_open := round(v_inst.amount - public.plg_financial_installment_paid(v_inst.id), 2);
35992
+ IF v_amount > v_open + 0.000001 THEN
35993
+ 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
35994
+ USING ERRCODE = '23514';
35995
+ END IF;
35996
+
35997
+ -- the money holder
35998
+ IF v_method = 'credit' THEN
35999
+ IF v_invoice.counterparty_person_id IS NULL THEN
36000
+ RAISE EXCEPTION 'financial_receive_payment: the invoice has no counterparty to consume credit from' USING ERRCODE = '55000';
36001
+ END IF;
36002
+ v_holder := public.plg_financial_person_account(v_tenant, v_invoice.counterparty_person_id);
36003
+ SELECT balance INTO v_credit FROM public.v_financial_account_balances WHERE account_id = v_holder;
36004
+ IF coalesce(v_credit, 0) + 0.000001 < v_amount THEN
36005
+ RAISE EXCEPTION 'financial_receive_payment: customer credit % is below %', coalesce(v_credit, 0), v_amount USING ERRCODE = '23514';
36006
+ END IF;
36007
+ ELSIF v_method = 'cash' THEN
36008
+ IF v_session IS NULL THEN
36009
+ v_session := public.plg_financial_open_session_of(v_tenant, v_actor, v_inst.unit_id);
36010
+ END IF;
36011
+ IF v_session IS NULL THEN
36012
+ RAISE EXCEPTION 'financial_receive_payment: no open cash session (caixinha) for cash in this unit' USING ERRCODE = '55000',
36013
+ HINT = 'Open your cash session first (financial_cash_session_open).';
36014
+ END IF;
36015
+ SELECT s.account_id INTO v_holder FROM public.plg_financial_cash_register_sessions s
36016
+ WHERE s.id = v_session AND s.tenant_id = v_tenant AND s.status = 'open';
36017
+ IF v_holder IS NULL THEN
36018
+ RAISE EXCEPTION 'financial_receive_payment: cash session % is not open', v_session USING ERRCODE = '55000';
36019
+ END IF;
36020
+ ELSE
36021
+ v_holder := p_account_id;
36022
+ IF v_holder IS NULL THEN
36023
+ RAISE EXCEPTION 'financial_receive_payment: an account is required for method %', v_method USING ERRCODE = '22023';
36024
+ END IF;
36025
+ END IF;
36026
+ 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;
36027
+ IF v_holder_k IS NULL THEN
36028
+ RAISE EXCEPTION 'financial_receive_payment: account % not found or inactive', v_holder USING ERRCODE = 'P0002';
36029
+ END IF;
36030
+ IF v_invoice.direction = 'payable' THEN
36031
+ -- Paying a bill by credit card spends the company's own card (a liability
36032
+ -- account), never a card receivable. Debit card leaves the bank directly.
36033
+ IF v_method = 'credit_card' AND v_holder_k <> 'card_payable' THEN
36034
+ RAISE EXCEPTION 'financial_receive_payment: method credit_card must pay a bill from a card_payable account'
36035
+ USING ERRCODE = '23514';
36036
+ END IF;
36037
+ IF v_method <> 'credit_card' AND v_holder_k = 'card_payable' THEN
36038
+ RAISE EXCEPTION 'financial_receive_payment: a card_payable account only pays by credit_card' USING ERRCODE = '23514';
36039
+ END IF;
36040
+ IF v_holder_k IN ('card_receivable_credit', 'card_receivable_debit') THEN
36041
+ RAISE EXCEPTION 'financial_receive_payment: a card receivable account cannot pay a bill' USING ERRCODE = '23514';
36042
+ END IF;
36043
+ ELSE
36044
+ IF (v_method = 'credit_card' AND v_holder_k <> 'card_receivable_credit')
36045
+ OR (v_method = 'debit_card' AND v_holder_k <> 'card_receivable_debit') THEN
36046
+ RAISE EXCEPTION 'financial_receive_payment: method % must settle in a % account', v_method,
36047
+ CASE WHEN v_method = 'credit_card' THEN 'card_receivable_credit' ELSE 'card_receivable_debit' END USING ERRCODE = '23514';
36048
+ END IF;
36049
+ IF v_method NOT IN ('credit_card', 'debit_card') AND v_holder_k IN ('card_receivable_credit', 'card_receivable_debit') THEN
36050
+ RAISE EXCEPTION 'financial_receive_payment: a card receivable account only takes card methods' USING ERRCODE = '23514';
36051
+ END IF;
36052
+ END IF;
36053
+
36054
+ IF v_invoice.direction = 'payable' THEN
36055
+ v_dir := 'debit';
36056
+ v_counter := public.plg_financial_system_account(v_tenant, 'payables');
36057
+ ELSE
36058
+ v_dir := 'credit';
36059
+ v_counter := public.plg_financial_system_account(v_tenant, 'receivables');
36060
+ END IF;
36061
+
36062
+ INSERT INTO public.plg_financial_movements
36063
+ (tenant_id, unit_id, invoice_id, financial_invoice_id, financial_invoice_installment_id, direction, movement_kind, amount, paid_amount, status,
36064
+ due_date, payment_date, installment_number, method, card_brand, card_installments, nsu, cash_session_id,
36065
+ bank_account_id, fee_amount, notes, metadata, idempotency_key, created_by, debit_account_id, credit_account_id)
36066
+ VALUES
36067
+ (v_tenant, v_inst.unit_id, v_invoice.order_id, v_invoice.id, v_inst.id, v_dir, 'payment', v_amount, v_amount, 'paid',
36068
+ 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,
36069
+ CASE WHEN v_holder_k IN ('person', 'system') THEN NULL ELSE v_holder END,
36070
+ round(coalesce(p_fee_amount, 0), 2), p_notes,
36071
+ coalesce(p_metadata, '{}'::jsonb) || jsonb_build_object('source', 'financial_receive_payment'),
36072
+ p_idempotency_key, v_actor,
36073
+ CASE WHEN v_dir = 'credit' THEN v_holder ELSE v_counter END,
36074
+ CASE WHEN v_dir = 'credit' THEN v_counter ELSE v_holder END)
36075
+ RETURNING id INTO v_mov_id;
36076
+
36077
+ INSERT INTO public.plg_financial_allocations (tenant_id, unit_id, payment_movement_id, installment_id, amount, created_by)
36078
+ VALUES (v_tenant, v_inst.unit_id, v_mov_id, v_inst.id, v_amount, v_actor)
36079
+ RETURNING id INTO v_alloc_id;
36080
+
36081
+ PERFORM public.plg_financial_audit(v_tenant, 'financial.payment.receive', 'plg_financial_movements', v_mov_id::text,
36082
+ jsonb_build_object('installment_id', v_inst.id, 'invoice_id', v_invoice.id, 'invoice_number', v_invoice.number, 'amount', v_amount,
36083
+ 'method', v_method, 'account_id', v_holder, 'cash_session_id', v_session, 'idempotency_key', p_idempotency_key));
36084
+
36085
+ RETURN jsonb_build_object('movement_id', v_mov_id, 'allocation_id', v_alloc_id, 'installment_id', v_inst.id,
36086
+ 'invoice_id', v_inst.invoice_id, 'amount', v_amount, 'replayed', false);
36087
+ END $$;
36088
+ `;
35208
36089
  var MIGRATIONS = [
35209
36090
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
35210
36091
  { id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
@@ -35297,7 +36178,8 @@ var MIGRATIONS = [
35297
36178
  { 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
36179
  { id: "089_o_fornecedor_aponta_para_o_plano_migrado", sql: MIGRATION_089_O_FORNECEDOR_APONTA_PARA_O_PLANO_MIGRADO },
35299
36180
  { 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 }
36181
+ { id: "091_a_recorrencia_repete_os_itens", sql: MIGRATION_091_A_RECORRENCIA_REPETE_OS_ITENS },
36182
+ { id: "092_a_conta_paga_no_cartao_sai_do_cartao", sql: MIGRATION_092_A_CONTA_PAGA_NO_CARTAO_SAI_DO_CARTAO }
35301
36183
  ];
35302
36184
  var STANDALONE_CONFIG = {
35303
36185
  currency: { code: "BRL", locale: "pt-BR", symbol: "R$" }