@fayz-ai/plugin-financial 0.16.0 → 0.16.1

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