@fayz-ai/plugin-financial 0.15.0 → 0.16.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.
Files changed (35) hide show
  1. package/dist/components/CnabRemittanceModal.d.ts +10 -0
  2. package/dist/components/CnabRemittanceModal.d.ts.map +1 -0
  3. package/dist/components/FinancialCounterpartySearchSelect.d.ts +16 -0
  4. package/dist/components/FinancialCounterpartySearchSelect.d.ts.map +1 -0
  5. package/dist/components/PersonStatementWidget.d.ts +9 -0
  6. package/dist/components/PersonStatementWidget.d.ts.map +1 -1
  7. package/dist/data/mock.d.ts +2 -1
  8. package/dist/data/mock.d.ts.map +1 -1
  9. package/dist/data/supabase.d.ts.map +1 -1
  10. package/dist/data/tables.d.ts +1 -0
  11. package/dist/data/tables.d.ts.map +1 -1
  12. package/dist/data/types.d.ts +10 -1
  13. package/dist/data/types.d.ts.map +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2291 -505
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/analytics-cards.d.ts +5 -0
  18. package/dist/lib/analytics-cards.d.ts.map +1 -0
  19. package/dist/lib/invoice-list.d.ts +28 -0
  20. package/dist/lib/invoice-list.d.ts.map +1 -0
  21. package/dist/locales/en.d.ts.map +1 -1
  22. package/dist/locales/pt-BR.d.ts.map +1 -1
  23. package/dist/migrations/index.d.ts +3 -0
  24. package/dist/migrations/index.d.ts.map +1 -1
  25. package/dist/types.d.ts +68 -1
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/views/FinancialPage.d.ts.map +1 -1
  28. package/dist/views/GiftCardsView.d.ts.map +1 -1
  29. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  30. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  31. package/dist/views/InvoiceListView.d.ts.map +1 -1
  32. package/dist/views/RecurringBillsView.d.ts +9 -0
  33. package/dist/views/RecurringBillsView.d.ts.map +1 -0
  34. package/dist/views/StatementsView.d.ts.map +1 -1
  35. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import * as React15 from 'react';
2
- import React15__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
3
- import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useDataChanged, countByTenant, 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, PageTransition, ModulePage, Skeleton, Badge, TooltipProvider, ConfirmDialog, MultiSelect, Tooltip, TooltipTrigger, TooltipContent, Input, Checkbox, ModalFooter, DashboardCanvas, ListFilterBar, DateRangeControl, PageHeaderActions, Card, FilterMenu, FilterMenuRow, Hint, useSaveBar, tourAnchor, SearchSelect, ListView, KpiCard, ChartWidget, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
5
- import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, PluginSettingsPanel, dedup, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePermissionOptional, useModuleNavAccess, ModuleActionBar, SettingsGroup, ToggleRow, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, createArchetypeLookup, useChatStore, adminNavigateTo, revealPage, GetStarted, useAuth, resolveEntityRoute, PermissionGate, useLimitGuard, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, 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, Gift, Loader2, BadgeCheck, Target, Settings, ArrowRight, Receipt, Users, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, Search, Download, CheckCheck, MoreVertical, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, ArrowUpRight, ArrowDownRight, EyeOff, Eye } from 'lucide-react';
1
+ import * as React20 from 'react';
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';
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';
@@ -77,7 +77,7 @@ function MobileSummary() {
77
77
  const fetchSummary = useFinancialStore((s) => s.fetchSummary);
78
78
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
79
79
  const fetchStatement = useFinancialStore((s) => s.fetchStatement);
80
- const dateLabel = useDateLabel();
80
+ const dateLabel2 = useDateLabel();
81
81
  useEffect(() => {
82
82
  void fetchSummary();
83
83
  void fetchBankAccounts();
@@ -120,7 +120,7 @@ function MobileSummary() {
120
120
  ] })
121
121
  ] }),
122
122
  groups.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-dashed py-12 text-center text-sm text-muted-foreground", children: t2("financial.summary.noTransactions") }) : /* @__PURE__ */ jsx("div", { className: "space-y-4", children: groups.map(([date, rows]) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
123
- /* @__PURE__ */ jsx("p", { className: "px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: dateLabel(date) }),
123
+ /* @__PURE__ */ jsx("p", { className: "px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: dateLabel2(date) }),
124
124
  /* @__PURE__ */ jsx("div", { className: "divide-y overflow-hidden rounded-card border bg-card", children: rows.map((e) => {
125
125
  const isCredit = e.movement.direction === "credit";
126
126
  const title = e.invoice?.contactName || e.movement.notes || t2("financial.summary.transactionsTitle");
@@ -165,6 +165,7 @@ function MobileSummary() {
165
165
  // src/data/tables.ts
166
166
  var T = {
167
167
  movements: "plg_financial_movements",
168
+ allocations: "plg_financial_allocations",
168
169
  cashMovements: "v_financial_cash_movements",
169
170
  accountBalances: "v_financial_account_balances",
170
171
  chartOfAccounts: "plg_financial_chart_of_accounts",
@@ -651,9 +652,9 @@ function startReceiptCapture(labels) {
651
652
  });
652
653
  }
653
654
  function ReceiptAgentSurface({ onOpenDraft }) {
654
- const live = React15.useRef(onOpenDraft);
655
+ const live = React20.useRef(onOpenDraft);
655
656
  live.current = onOpenDraft;
656
- const contribution = React15.useMemo(() => ({
657
+ const contribution = React20.useMemo(() => ({
657
658
  id: "financial.payables.receipt",
658
659
  pluginId: "financial",
659
660
  role: "finance",
@@ -678,6 +679,209 @@ function createIdempotencyKey(scope) {
678
679
  }
679
680
  return `${scope}:${suffix}`;
680
681
  }
682
+ var STEPS = [
683
+ { id: "account", label: "Conta banc\xE1ria" },
684
+ { id: "payments", label: "Pagamentos" },
685
+ { id: "beneficiaries", label: "Favorecidos" },
686
+ { id: "review", label: "Revis\xE3o" }
687
+ ];
688
+ function textMetadata(account, key) {
689
+ const value = account.metadata?.[key];
690
+ return typeof value === "string" ? value : "";
691
+ }
692
+ function draftFromAccount(account) {
693
+ return {
694
+ bankName: account.bankName ?? "",
695
+ bankCode: textMetadata(account, "bankCode"),
696
+ agencyNumber: account.agencyNumber ?? "",
697
+ agencyDigit: textMetadata(account, "agencyDigit"),
698
+ accountNumber: account.accountNumber ?? "",
699
+ accountDigit: textMetadata(account, "accountDigit"),
700
+ agreementCode: textMetadata(account, "cnabAgreementCode")
701
+ };
702
+ }
703
+ function missingFields(draft) {
704
+ return [
705
+ !draft.bankCode.trim() && "c\xF3digo do banco",
706
+ !draft.agencyNumber.trim() && "ag\xEAncia",
707
+ !draft.accountNumber.trim() && "conta"
708
+ ].filter(Boolean);
709
+ }
710
+ function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
711
+ const { currency } = useFinancialConfig();
712
+ const [step, setStep] = React20__default.useState(0);
713
+ const [bankAccountId, setBankAccountId] = React20__default.useState("");
714
+ const [draft, setDraft] = React20__default.useState(null);
715
+ const [saving, setSaving] = React20__default.useState(false);
716
+ const [saveError, setSaveError] = React20__default.useState("");
717
+ const eligibleAccounts2 = bankAccounts.filter((account2) => account2.accountType === "bank" && account2.isActive);
718
+ const total = invoices.reduce((sum, invoice) => sum + Math.max(0, invoice.totalAmount - invoice.paidAmount), 0);
719
+ const account = eligibleAccounts2.find((candidate) => candidate.id === bankAccountId);
720
+ const missingBankData = draft ? missingFields(draft) : [];
721
+ const selectAccount = (id) => {
722
+ const selected = eligibleAccounts2.find((candidate) => candidate.id === id);
723
+ setBankAccountId(id);
724
+ setDraft(selected ? draftFromAccount(selected) : null);
725
+ setSaveError("");
726
+ };
727
+ const updateDraft = (key, value) => {
728
+ setDraft((current) => current ? { ...current, [key]: value } : current);
729
+ setSaveError("");
730
+ };
731
+ const advance = async () => {
732
+ if (step !== 0) {
733
+ setStep((value) => Math.min(value + 1, 3));
734
+ return;
735
+ }
736
+ if (!account || !draft || missingBankData.length > 0) return;
737
+ setSaving(true);
738
+ setSaveError("");
739
+ try {
740
+ await provider.updateBankAccount(account.id, {
741
+ bankName: draft.bankName.trim() || void 0,
742
+ agencyNumber: draft.agencyNumber.trim(),
743
+ accountNumber: draft.accountNumber.trim(),
744
+ metadata: {
745
+ ...account.metadata,
746
+ bankCode: draft.bankCode.trim(),
747
+ agencyDigit: draft.agencyDigit.trim(),
748
+ accountDigit: draft.accountDigit.trim(),
749
+ cnabAgreementCode: draft.agreementCode.trim()
750
+ }
751
+ });
752
+ setStep(1);
753
+ } catch (error) {
754
+ setSaveError(error instanceof Error ? error.message : "N\xE3o foi poss\xEDvel salvar os dados banc\xE1rios.");
755
+ } finally {
756
+ setSaving(false);
757
+ }
758
+ };
759
+ return createPortal(
760
+ /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
761
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-[2px]", onClick: onClose }),
762
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 mx-4 flex max-h-[90vh] w-full max-w-3xl flex-col overflow-hidden rounded-modal border bg-card shadow-xl", role: "dialog", "aria-modal": "true", "aria-labelledby": "cnab-title", children: [
763
+ /* @__PURE__ */ jsxs("div", { className: "border-b px-5 py-4", children: [
764
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
765
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
766
+ /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 text-primary" }) }),
767
+ /* @__PURE__ */ jsxs("div", { children: [
768
+ /* @__PURE__ */ jsx("h2", { id: "cnab-title", className: "text-base font-semibold", children: "Gerar Remessa CNAB 240" }),
769
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
770
+ invoices.length,
771
+ " parcela(s) \xB7 ",
772
+ formatCurrency(total, currency)
773
+ ] })
774
+ ] })
775
+ ] }),
776
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, className: "rounded-md p-2 text-muted-foreground hover:bg-muted hover:text-foreground", "aria-label": "Fechar", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
777
+ ] }),
778
+ /* @__PURE__ */ jsx(WizardSteps, { steps: STEPS, current: step, onSelect: setStep, className: "mt-4", "aria-label": "Etapas da remessa CNAB" })
779
+ ] }),
780
+ /* @__PURE__ */ jsxs("div", { className: "min-h-[340px] flex-1 overflow-y-auto p-5", children: [
781
+ step === 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
782
+ /* @__PURE__ */ jsx(
783
+ SearchSelect,
784
+ {
785
+ label: "Conta banc\xE1ria para d\xE9bito",
786
+ required: true,
787
+ value: bankAccountId,
788
+ displayValue: account?.name ?? "",
789
+ placeholder: "Busque pelo nome da conta",
790
+ minChars: 0,
791
+ loadOnOpen: true,
792
+ noResultsLabel: "Nenhuma conta banc\xE1ria ativa encontrada.",
793
+ searchingLabel: "Buscando contas...",
794
+ onSearch: (query) => {
795
+ const normalized = query.trim().toLocaleLowerCase("pt-BR");
796
+ return eligibleAccounts2.filter((candidate) => !normalized || `${candidate.name} ${candidate.bankName ?? ""} ${candidate.agencyNumber ?? ""} ${candidate.accountNumber ?? ""}`.toLocaleLowerCase("pt-BR").includes(normalized)).map((candidate) => ({
797
+ id: candidate.id,
798
+ label: candidate.name,
799
+ subtitle: [candidate.bankName, candidate.agencyNumber && `Ag ${candidate.agencyNumber}`, candidate.accountNumber && `Cc ${candidate.accountNumber}`].filter(Boolean).join(" \xB7 "),
800
+ icon: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4" })
801
+ }));
802
+ },
803
+ onChange: (id) => selectAccount(id)
804
+ }
805
+ ),
806
+ draft ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border bg-muted/20 p-4", children: [
807
+ /* @__PURE__ */ jsxs("div", { children: [
808
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados da conta para a remessa" }),
809
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Preencha uma vez; os pr\xF3ximos arquivos reutilizam esta configura\xE7\xE3o." })
810
+ ] }),
811
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
812
+ /* @__PURE__ */ jsx(Field, { label: "C\xF3digo do banco", required: true, value: draft.bankCode, placeholder: "Ex.: 341", onChange: (value) => updateDraft("bankCode", value) }),
813
+ /* @__PURE__ */ jsx(Field, { label: "Nome do banco", value: draft.bankName, placeholder: "Ex.: Ita\xFA", onChange: (value) => updateDraft("bankName", value) }),
814
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
815
+ /* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: draft.agencyNumber, onChange: (value) => updateDraft("agencyNumber", value), className: "col-span-2" }),
816
+ /* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.agencyDigit, onChange: (value) => updateDraft("agencyDigit", value) })
817
+ ] }),
818
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
819
+ /* @__PURE__ */ jsx(Field, { label: "Conta", required: true, value: draft.accountNumber, onChange: (value) => updateDraft("accountNumber", value), className: "col-span-2" }),
820
+ /* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.accountDigit, onChange: (value) => updateDraft("accountDigit", value) })
821
+ ] }),
822
+ /* @__PURE__ */ jsx(Field, { label: "Conv\xEAnio / c\xF3digo da empresa", value: draft.agreementCode, placeholder: "Opcional para alguns bancos", onChange: (value) => updateDraft("agreementCode", value), className: "sm:col-span-2" })
823
+ ] }),
824
+ missingBankData.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex gap-2 rounded-lg border border-warning/30 bg-warning/10 p-3 text-sm", children: [
825
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-warning" }),
826
+ /* @__PURE__ */ jsxs("p", { children: [
827
+ "Complete ",
828
+ missingBankData.join(", "),
829
+ " para continuar."
830
+ ] })
831
+ ] }) : null,
832
+ saveError ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: saveError }) : null
833
+ ] }) : null,
834
+ eligibleAccounts2.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Cadastre uma conta banc\xE1ria ativa para iniciar uma remessa." }) : null
835
+ ] }) : null,
836
+ step === 1 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
837
+ /* @__PURE__ */ jsxs("div", { children: [
838
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Pagamentos selecionados" }),
839
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Confira os t\xEDtulos e defina PIX, TED, boleto ou tributo na pr\xF3xima etapa." })
840
+ ] }),
841
+ /* @__PURE__ */ jsx("div", { className: "divide-y rounded-lg border", children: invoices.map((invoice) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 px-3 py-2.5 text-sm", children: [
842
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
843
+ /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: invoice.contactName || invoice.itemsSummary || "Sem descri\xE7\xE3o" }),
844
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
845
+ "Vencimento ",
846
+ invoice.dueDate || "\u2014",
847
+ invoice.fiscalNumber ? ` \xB7 #${invoice.fiscalNumber}` : ""
848
+ ] })
849
+ ] }),
850
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-medium tabular-nums", children: formatCurrency(Math.max(0, invoice.totalAmount - invoice.paidAmount), currency) })
851
+ ] }, invoice.id)) })
852
+ ] }) : null,
853
+ step >= 2 ? /* @__PURE__ */ jsxs("div", { className: "flex min-h-[260px] flex-col items-center justify-center text-center", children: [
854
+ /* @__PURE__ */ jsx("div", { className: "mb-3 flex h-11 w-11 items-center justify-center rounded-full bg-warning/10", children: /* @__PURE__ */ jsx(AlertTriangle, { className: "h-5 w-5 text-warning" }) }),
855
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold", children: "Dados dos favorecidos ainda n\xE3o est\xE3o completos na V2" }),
856
+ /* @__PURE__ */ jsx("p", { className: "mt-2 max-w-lg text-sm text-muted-foreground", children: "A gera\xE7\xE3o continua bloqueada at\xE9 a migra\xE7\xE3o segura de CPF/CNPJ, chave PIX e dados banc\xE1rios dos favorecidos." })
857
+ ] }) : null
858
+ ] }),
859
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-5 py-4", children: [
860
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => step === 0 ? onClose() : setStep((value) => value - 1), disabled: saving, children: [
861
+ step > 0 ? /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }) : null,
862
+ step === 0 ? "Cancelar" : "Voltar"
863
+ ] }),
864
+ step < 2 ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void advance(), disabled: saving || step === 0 && (!account || !draft || missingBankData.length > 0), children: [
865
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
866
+ step === 0 ? "Salvar e avan\xE7ar" : "Avan\xE7ar",
867
+ " ",
868
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" })
869
+ ] }) : /* @__PURE__ */ jsx(Button, { size: "sm", disabled: true, children: "Gerar arquivo CNAB 240" })
870
+ ] })
871
+ ] })
872
+ ] }),
873
+ document.body
874
+ );
875
+ }
876
+ function Field({ label, value, onChange, required, placeholder, className = "" }) {
877
+ return /* @__PURE__ */ jsxs("label", { className: `block space-y-1.5 ${className}`, children: [
878
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
879
+ label,
880
+ required ? /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" }) : null
881
+ ] }),
882
+ /* @__PURE__ */ jsx("input", { value, onChange: (event) => onChange(event.target.value), placeholder, className: "h-9 w-full rounded-input border bg-background px-3 text-sm outline-none focus:border-primary focus:ring-2 focus:ring-primary/15" })
883
+ ] });
884
+ }
681
885
  var STATUS_OPTIONS = [
682
886
  { value: "open", labelKey: "financial.invoice.statusOpen", color: "bg-info-soft text-info-soft-foreground", icon: CircleDashed },
683
887
  { value: "partial", labelKey: "financial.invoice.statusPartial", color: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis },
@@ -688,6 +892,10 @@ var STATUS_OPTIONS = [
688
892
  var DATE_PRESETS = ["thisMonth", "lastMonth", "last30", "last90"];
689
893
  var MAX_RANGE_DAYS = 90;
690
894
  var isoDate = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
895
+ var shortDate = (value) => {
896
+ const [year, month, day] = value.split("-");
897
+ return year && month && day ? `${day}/${month}/${year}` : value;
898
+ };
691
899
  function presetRange(preset) {
692
900
  const now2 = /* @__PURE__ */ new Date();
693
901
  switch (preset) {
@@ -720,7 +928,7 @@ function StatusBadge({ status }) {
720
928
  opt ? t2(opt.labelKey) : status
721
929
  ] });
722
930
  }
723
- function useInvoiceColumns(currency) {
931
+ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts) {
724
932
  const t2 = useTranslation();
725
933
  return useMemo(() => [
726
934
  {
@@ -764,6 +972,23 @@ function useInvoiceColumns(currency) {
764
972
  header: t2("financial.invoice.columnStatus"),
765
973
  cell: ({ getValue }) => /* @__PURE__ */ jsx(StatusBadge, { status: getValue() })
766
974
  },
975
+ {
976
+ id: "paymentMethod",
977
+ header: t2("financial.invoice.columnPaymentMethod"),
978
+ cell: ({ row }) => {
979
+ const method = paymentMethods2.find((candidate) => candidate.id === row.original.paymentMethodId);
980
+ const type = paymentMethodTypes.find((candidate) => candidate.id === (method?.paymentMethodTypeId ?? row.original.paymentMethodTypeId));
981
+ const account = bankAccounts.find((candidate) => candidate.id === row.original.paymentAccountId);
982
+ if (!type && !method && !account) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
983
+ return /* @__PURE__ */ jsxs("div", { className: "max-w-[180px] text-xs", children: [
984
+ /* @__PURE__ */ jsxs("p", { className: "truncate font-medium", children: [
985
+ method?.name ?? type?.name ?? "\u2014",
986
+ row.original.paymentCardInstallments ? ` \xB7 ${row.original.paymentCardInstallments}x` : ""
987
+ ] }),
988
+ account && /* @__PURE__ */ jsx("p", { className: "truncate text-[10px] text-muted-foreground", children: account.name })
989
+ ] });
990
+ }
991
+ },
767
992
  {
768
993
  id: "attachments",
769
994
  header: "",
@@ -798,7 +1023,7 @@ function useInvoiceColumns(currency) {
798
1023
  return /* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: total > 1 ? `${atual}/${total}` : total });
799
1024
  }
800
1025
  }
801
- ], [currency, t2]);
1026
+ ], [currency, t2, paymentMethods2, paymentMethodTypes, bankAccounts]);
802
1027
  }
803
1028
  function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
804
1029
  const t2 = useTranslation();
@@ -807,17 +1032,23 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
807
1032
  const canEdit = can("financial", "edit");
808
1033
  const onNewGated = canCreate ? onNew : void 0;
809
1034
  const onNewReceiptGated = canCreate ? onNewReceipt : void 0;
810
- const { currency } = useFinancialConfig();
1035
+ const { currency, locations } = useFinancialConfig();
811
1036
  const provider = useFinancialProvider();
812
1037
  const invoices = useFinancialStore((s) => s.invoices);
813
1038
  useFinancialStore((s) => s.invoicesTotal);
814
1039
  const invoicesLoading = useFinancialStore((s) => s.invoicesLoading);
815
1040
  const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
816
1041
  const fetchSummary = useFinancialStore((s) => s.fetchSummary);
1042
+ const paymentMethods2 = useFinancialStore((s) => s.paymentMethods);
1043
+ const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
1044
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
1045
+ const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
1046
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
817
1047
  const [search, setSearch] = useState("");
818
1048
  const [statusFilter, setStatusFilter] = useState([]);
819
1049
  const [accountId, setAccountId] = useState("");
820
1050
  const [costCenterId, setCostCenterId] = useState("");
1051
+ const [unitId, setUnitId] = useState("");
821
1052
  const [accounts, setAccounts] = useState([]);
822
1053
  const postable = useMemo(() => postableAccounts(accounts), [accounts]);
823
1054
  const [costCenters, setCostCenters] = useState([]);
@@ -829,7 +1060,6 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
829
1060
  const [dateFrom, setDateFrom] = useState(mesCorrente.from);
830
1061
  const [dateTo, setDateTo] = useState(mesCorrente.to);
831
1062
  const dateRange = useMemo(() => ({ from: dateFrom, to: dateTo }), [dateFrom, dateTo]);
832
- const dateIsDefault = dateFrom === mesCorrente.from && dateTo === mesCorrente.to;
833
1063
  const applyPreset = (value) => {
834
1064
  setDatePreset(value);
835
1065
  if (value === "custom") return;
@@ -870,14 +1100,18 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
870
1100
  active = false;
871
1101
  };
872
1102
  }, [provider]);
1103
+ useEffect(() => {
1104
+ void Promise.all([fetchPaymentMethods(), fetchBankAccounts()]);
1105
+ }, [fetchPaymentMethods, fetchBankAccounts]);
873
1106
  const query = useMemo(() => ({
874
1107
  direction,
875
1108
  status: statusFilter.length > 0 ? statusFilter : void 0,
876
1109
  search: search || void 0,
877
1110
  accountId: accountId || void 0,
878
1111
  costCenterId: costCenterId || void 0,
1112
+ unitId: unitId || void 0,
879
1113
  dateRange
880
- }), [direction, statusFilter, search, accountId, costCenterId, dateRange]);
1114
+ }), [direction, statusFilter, search, accountId, costCenterId, unitId, dateRange]);
881
1115
  useEffect(() => {
882
1116
  fetchInvoices(query);
883
1117
  }, [query]);
@@ -886,7 +1120,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
886
1120
  void fetchSummary();
887
1121
  }, [query]);
888
1122
  const filtered = invoices.filter((inv) => inv.direction === direction);
889
- const columns = useInvoiceColumns(currency);
1123
+ const columns = useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts);
890
1124
  const tags = useMemo(
891
1125
  () => STATUS_OPTIONS.filter((o) => o.value !== "cancelled").map((o) => ({ value: o.value, label: t2(o.labelKey) })),
892
1126
  [t2]
@@ -904,6 +1138,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
904
1138
  return { totalAmount, paidAmount, openAmount };
905
1139
  }, [filtered]);
906
1140
  const [cancelTarget, setCancelTarget] = useState(null);
1141
+ const [cnabTarget, setCnabTarget] = useState(null);
907
1142
  const [bulkBusy, setBulkBusy] = useState(false);
908
1143
  const [cancelReason, setCancelReason] = useState("");
909
1144
  const cancelRequestKey = useRef(createIdempotencyKey("financial:invoice-bulk-cancel"));
@@ -936,9 +1171,41 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
936
1171
  if (done > 0) toast.success(t2("financial.invoice.bulkCancelDone", { count: String(done) }));
937
1172
  if (failed > 0) toast.error(t2("financial.invoice.bulkCancelFailed", { count: String(failed) }));
938
1173
  }, [cancelTarget, cancelReason, closeBulkCancel, provider, fetchInvoices, fetchSummary, query, t2]);
1174
+ const exportSelected = useCallback((rows) => {
1175
+ exportCSV(
1176
+ [
1177
+ { key: "fiscalNumber", label: "#" },
1178
+ { key: "dueDate", label: t2("financial.invoice.columnDueDate") },
1179
+ { key: "contactName", label: t2("financial.invoice.columnDescription") },
1180
+ { key: "itemsSummary", label: t2("financial.invoiceForm.items") },
1181
+ { key: "totalAmount", label: t2("financial.invoice.columnAmount") },
1182
+ { key: "paidAmount", label: t2("financial.invoice.columnPaid") },
1183
+ { key: "status", label: t2("financial.invoice.columnStatus") }
1184
+ ],
1185
+ rows,
1186
+ direction === "debit" ? "contas-a-pagar-selecionadas" : "contas-a-receber-selecionadas"
1187
+ );
1188
+ }, [direction, t2]);
1189
+ const printSelected = useCallback((rows) => {
1190
+ const safe = (value) => String(value ?? "").replace(/[&<>"']/g, (character) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character] ?? character);
1191
+ const body = rows.map((row) => `<tr><td>${safe(row.dueDate)}</td><td>${safe(row.contactName)}<small>${safe(row.itemsSummary)}</small></td><td>${safe(formatCurrency(row.totalAmount, currency))}</td><td>${row.installmentNumber ?? 1}/${row.totalInstallments ?? 1}</td></tr>`).join("");
1192
+ const popup = window.open("", "_blank");
1193
+ if (!popup) {
1194
+ toast.error(t2("financial.invoice.printBlocked"));
1195
+ return;
1196
+ }
1197
+ popup.opener = null;
1198
+ popup.document.write(`<html><head><title>${direction === "debit" ? "Contas a pagar" : "Contas a receber"}</title><style>body{font:12px sans-serif;padding:24px}table{width:100%;border-collapse:collapse}th,td{border-bottom:1px solid #ddd;padding:8px;text-align:left}small{display:block;color:#666;margin-top:2px}</style></head><body><h1>${direction === "debit" ? "Contas a pagar" : "Contas a receber"}</h1><table><thead><tr><th>Vencimento</th><th>Descri\xE7\xE3o / itens</th><th>Valor</th><th>Parcela</th></tr></thead><tbody>${body}</tbody></table><script>window.print();window.close()</script></body></html>`);
1199
+ popup.document.close();
1200
+ }, [currency, direction, t2]);
939
1201
  const bulkActions = useMemo(() => {
940
- if (!canEdit) return void 0;
941
- return [{
1202
+ const actions = [
1203
+ ...direction === "debit" ? [{ id: "cnab", label: t2("financial.invoice.generateCnab"), icon: FileText, onSelect: (rows) => setCnabTarget(rows.filter((row) => row.status !== "paid" && row.status !== "cancelled")) }] : [],
1204
+ { id: "print", label: t2("financial.invoice.printSelected"), icon: Printer, onSelect: printSelected },
1205
+ { id: "export", label: t2("financial.invoice.exportSelected"), icon: Download, onSelect: exportSelected }
1206
+ ];
1207
+ if (!canEdit) return actions;
1208
+ actions.push({
942
1209
  id: "cancel",
943
1210
  label: t2("financial.invoice.bulkCancel"),
944
1211
  icon: Ban,
@@ -952,8 +1219,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
952
1219
  }
953
1220
  setCancelTarget(targets);
954
1221
  }
955
- }];
956
- }, [canEdit, bulkBusy, t2]);
1222
+ });
1223
+ return actions;
1224
+ }, [canEdit, bulkBusy, direction, exportSelected, printSelected, t2]);
957
1225
  const receivedLabel = direction === "debit" ? t2("financial.invoice.summaryPaid") : t2("financial.invoice.summaryReceived");
958
1226
  const pendingLabel = direction === "debit" ? t2("financial.invoice.summaryPayable") : t2("financial.invoice.summaryReceivable");
959
1227
  const exportRows = () => {
@@ -978,14 +1246,12 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
978
1246
  FilterMenu,
979
1247
  {
980
1248
  label: t2("crud.list.filters"),
981
- activeCount: (accountId ? 1 : 0) + (costCenterId ? 1 : 0) + (statusFilter.length ? 1 : 0) + (dateIsDefault ? 0 : 1),
1249
+ activeCount: (accountId ? 1 : 0) + (costCenterId ? 1 : 0) + (unitId ? 1 : 0) + (statusFilter.length ? 1 : 0),
982
1250
  onClear: () => {
983
1251
  setAccountId("");
984
1252
  setCostCenterId("");
1253
+ setUnitId("");
985
1254
  setStatusFilter([]);
986
- setDatePreset("custom");
987
- setDateFrom(mesCorrente.from);
988
- setDateTo(mesCorrente.to);
989
1255
  },
990
1256
  clearLabel: t2("financial.invoice.filterClear"),
991
1257
  fields: [
@@ -1010,22 +1276,6 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1010
1276
  })
1011
1277
  ] })
1012
1278
  },
1013
- {
1014
- id: "period",
1015
- label: t2("financial.invoice.filterPeriod"),
1016
- icon: CalendarDays,
1017
- activeCount: dateIsDefault ? 0 : 1,
1018
- render: () => /* @__PURE__ */ jsxs(Fragment, { children: [
1019
- DATE_PRESETS.map((p) => /* @__PURE__ */ jsx(FilterMenuRow, { active: datePreset === p, onClick: () => applyPreset(p), children: t2(`financial.invoice.filter${p.charAt(0).toUpperCase()}${p.slice(1)}`) }, p)),
1020
- /* @__PURE__ */ jsx(FilterMenuRow, { active: datePreset === "custom", onClick: () => setDatePreset("custom"), children: t2("financial.invoice.filterCustom") }),
1021
- datePreset === "custom" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 p-1.5", children: [
1022
- /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateFrom, onChange: setFromClamped }),
1023
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
1024
- /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateTo, onChange: setToClamped })
1025
- ] }),
1026
- /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-[10px] text-muted-foreground", children: t2("financial.invoice.filterMaxRange") })
1027
- ] })
1028
- },
1029
1279
  ...postable.length > 0 ? [{
1030
1280
  id: "account",
1031
1281
  label: t2("financial.invoice.filterAccount"),
@@ -1063,19 +1313,53 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1063
1313
  c.name
1064
1314
  ] }) }, c.id))
1065
1315
  ] })
1316
+ }] : [],
1317
+ ...locations.length > 1 ? [{
1318
+ id: "unit",
1319
+ label: t2("financial.invoice.filterUnit"),
1320
+ icon: Building2,
1321
+ activeCount: unitId ? 1 : 0,
1322
+ searchable: true,
1323
+ render: (close, q) => /* @__PURE__ */ jsxs(Fragment, { children: [
1324
+ /* @__PURE__ */ jsx(FilterMenuRow, { active: !unitId, onClick: () => {
1325
+ setUnitId("");
1326
+ close();
1327
+ }, children: t2("financial.invoice.filterAllUnits") }),
1328
+ locations.filter((unit) => unit.name.toLowerCase().includes(q.toLowerCase())).map((unit) => /* @__PURE__ */ jsx(FilterMenuRow, { active: unitId === unit.id, onClick: () => {
1329
+ setUnitId(unit.id);
1330
+ close();
1331
+ }, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: unit.name }) }, unit.id))
1332
+ ] })
1066
1333
  }] : []
1067
1334
  ]
1068
1335
  }
1069
1336
  );
1070
- const activeFilterCount = (accountId ? 1 : 0) + (costCenterId ? 1 : 0) + (statusFilter.length > 0 ? 1 : 0) + (dateIsDefault ? 0 : 1);
1337
+ const activeFilterCount = (accountId ? 1 : 0) + (costCenterId ? 1 : 0) + (unitId ? 1 : 0) + (statusFilter.length > 0 ? 1 : 0);
1071
1338
  const clearFilters = () => {
1072
1339
  setAccountId("");
1073
1340
  setCostCenterId("");
1341
+ setUnitId("");
1074
1342
  setStatusFilter([]);
1075
- setDatePreset("custom");
1076
- setDateFrom(mesCorrente.from);
1077
- setDateTo(mesCorrente.to);
1078
1343
  };
1344
+ const periodFilter = /* @__PURE__ */ jsx(
1345
+ RequiredFilter,
1346
+ {
1347
+ label: t2("financial.invoice.filterPeriod"),
1348
+ value: `${shortDate(dateFrom)} \u2013 ${shortDate(dateTo)}`,
1349
+ icon: CalendarDays,
1350
+ align: "end",
1351
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1352
+ DATE_PRESETS.map((preset) => /* @__PURE__ */ jsx(FilterMenuRow, { active: datePreset === preset, onClick: () => applyPreset(preset), children: t2(`financial.invoice.filter${preset.charAt(0).toUpperCase()}${preset.slice(1)}`) }, preset)),
1353
+ /* @__PURE__ */ jsx(FilterMenuRow, { active: datePreset === "custom", onClick: () => setDatePreset("custom"), children: t2("financial.invoice.filterCustom") }),
1354
+ datePreset === "custom" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 p-1.5", children: [
1355
+ /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateFrom, onChange: setFromClamped }),
1356
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
1357
+ /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateTo, onChange: setToClamped })
1358
+ ] }) : null,
1359
+ /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-[10px] text-muted-foreground", children: t2("financial.invoice.filterMaxRange") })
1360
+ ] })
1361
+ }
1362
+ );
1079
1363
  const newActions = onNewGated ? [
1080
1364
  {
1081
1365
  id: "manual",
@@ -1118,7 +1402,10 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1118
1402
  search,
1119
1403
  onSearchChange: setSearch,
1120
1404
  searchDebounce: 0,
1121
- filtersTrigger: filtersMenu,
1405
+ filtersTrigger: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1406
+ periodFilter,
1407
+ filtersMenu
1408
+ ] }),
1122
1409
  activeFilterCount,
1123
1410
  onClearFilters: clearFilters,
1124
1411
  clearFiltersLabel: t2("financial.invoice.filterClear"),
@@ -1180,7 +1467,8 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
1180
1467
  ] })
1181
1468
  ] }),
1182
1469
  document.body
1183
- )
1470
+ ),
1471
+ cnabTarget !== null ? /* @__PURE__ */ jsx(CnabRemittanceModal, { invoices: cnabTarget, bankAccounts, provider, onClose: () => setCnabTarget(null) }) : null
1184
1472
  ] });
1185
1473
  }
1186
1474
  var PAYABLE_INSTRUCTIONS = [
@@ -2211,9 +2499,9 @@ var SUBMIT_DRAFT = {
2211
2499
  parameters: { type: "object", properties: {} }
2212
2500
  };
2213
2501
  function PayableDraftSurface({ config, draft }) {
2214
- const live = React15.useRef(draft);
2502
+ const live = React20.useRef(draft);
2215
2503
  live.current = draft;
2216
- const lookup = React15.useMemo(
2504
+ const lookup = React20.useMemo(
2217
2505
  () => config.contactLookup ?? createArchetypeLookup({
2218
2506
  archetype: "person",
2219
2507
  kind: "supplier",
@@ -2222,7 +2510,7 @@ function PayableDraftSurface({ config, draft }) {
2222
2510
  }),
2223
2511
  [config.contactLookup]
2224
2512
  );
2225
- React15.useEffect(() => {
2513
+ React20.useEffect(() => {
2226
2514
  liveDraft = {
2227
2515
  describe: () => live.current.describe(),
2228
2516
  setSupplier: (name, id) => live.current.setSupplier(name, id),
@@ -2233,7 +2521,7 @@ function PayableDraftSurface({ config, draft }) {
2233
2521
  liveDraft = null;
2234
2522
  };
2235
2523
  }, []);
2236
- const contribution = React15.useMemo(() => ({
2524
+ const contribution = React20.useMemo(() => ({
2237
2525
  id: "financial.payables.draft",
2238
2526
  pluginId: "financial",
2239
2527
  role: "finance",
@@ -2308,7 +2596,7 @@ function PayableDraftSurface({ config, draft }) {
2308
2596
  }
2309
2597
  function createFinancialAgentMount(deps) {
2310
2598
  const FinancialAgentMount = () => {
2311
- const contribution = React15.useMemo(() => ({
2599
+ const contribution = React20.useMemo(() => ({
2312
2600
  id: "financial.payables.global",
2313
2601
  pluginId: "financial",
2314
2602
  role: "finance",
@@ -2410,7 +2698,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
2410
2698
  const [cardInstallments, setCardInstallments] = useState(1);
2411
2699
  const [notes, setNotes] = useState("");
2412
2700
  const [saving, setSaving] = useState(false);
2413
- const command = React15__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
2701
+ const command = React20__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
2414
2702
  const [pending2, setPending] = useState(false);
2415
2703
  const [visible, setVisible] = useState(false);
2416
2704
  const [expandMethodPicker, setExpandMethodPicker] = useState(true);
@@ -2459,7 +2747,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
2459
2747
  e.preventDefault();
2460
2748
  if (amount > 0 && selectedTypeId && bankAccountId && !saving) void handlePay();
2461
2749
  }
2462
- React15__default.useEffect(() => {
2750
+ React20__default.useEffect(() => {
2463
2751
  void fetchCashSessions();
2464
2752
  }, [fetchCashSessions]);
2465
2753
  const needsOpenCashSession = txType === "cash" && !openSession;
@@ -2700,6 +2988,130 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
2700
2988
  document.body
2701
2989
  );
2702
2990
  }
2991
+ var KIND_LABELS = {
2992
+ supplier: "Fornecedor",
2993
+ customer: "Cliente",
2994
+ staff: "Profissional"
2995
+ };
2996
+ function createFinancialCounterpartyLookup(direction) {
2997
+ const kinds = direction === "debit" ? ["supplier", "staff", "customer"] : ["customer", "supplier", "staff"];
2998
+ return createArchetypeLookup({
2999
+ archetype: "person",
3000
+ kind: kinds,
3001
+ createKinds: kinds,
3002
+ kindLabels: KIND_LABELS
3003
+ });
3004
+ }
3005
+ function FinancialCounterpartySearchSelect({
3006
+ lookup,
3007
+ value,
3008
+ displayValue,
3009
+ label,
3010
+ required,
3011
+ onChange,
3012
+ onCreate
3013
+ }) {
3014
+ const search = React20.useCallback(async (query) => {
3015
+ const results = await lookup.search(query);
3016
+ return results.map((result) => ({
3017
+ id: result.id,
3018
+ label: result.label,
3019
+ subtitle: result.subtitle,
3020
+ group: result.group,
3021
+ data: result
3022
+ }));
3023
+ }, [lookup]);
3024
+ return /* @__PURE__ */ jsx(
3025
+ SearchSelect,
3026
+ {
3027
+ value,
3028
+ displayValue,
3029
+ label,
3030
+ required,
3031
+ onChange,
3032
+ onSearch: search,
3033
+ minChars: 2,
3034
+ minCharsLabel: "Digite pelo menos 2 caracteres para buscar.",
3035
+ noResultsLabel: "Nenhum cadastro encontrado.",
3036
+ searchingLabel: "Buscando cadastros...",
3037
+ loadOnOpen: false,
3038
+ groupLayout: "columns",
3039
+ createLabel: "Cadastrar",
3040
+ clearLabel: "Limpar",
3041
+ onCreate,
3042
+ placeholder: "Digite nome, telefone ou documento..."
3043
+ }
3044
+ );
3045
+ }
3046
+
3047
+ // src/lib/invoice-list.ts
3048
+ function availableInvoiceItemTypes(itemTypes, lookupKinds, otherLabel) {
3049
+ const installed = new Set(lookupKinds);
3050
+ const visible = itemTypes.filter((type) => type.value === "other" || !["product", "service"].includes(type.value) || installed.has(type.value));
3051
+ if (visible.some((type) => type.value === "other")) return visible;
3052
+ return [...visible, { value: "other", label: otherLabel, icon: "MoreHorizontal" }];
3053
+ }
3054
+ function stringIds(value) {
3055
+ if (!Array.isArray(value)) return [];
3056
+ return value.flatMap((entry) => {
3057
+ if (typeof entry === "string") return entry ? [entry] : [];
3058
+ if (typeof entry === "number" && Number.isFinite(entry)) return [String(entry)];
3059
+ if (!entry || typeof entry !== "object") return [];
3060
+ const row = entry;
3061
+ const id = row.id ?? row.uuid ?? row.value;
3062
+ return typeof id === "string" && id ? [id] : [];
3063
+ });
3064
+ }
3065
+ function counterpartyAccountingPolicy(optionData) {
3066
+ const result = optionData && typeof optionData === "object" ? optionData : {};
3067
+ const row = result.data && typeof result.data === "object" ? result.data : result;
3068
+ const metadata = row.metadata && typeof row.metadata === "object" ? row.metadata : {};
3069
+ const financial = metadata.financial && typeof metadata.financial === "object" ? metadata.financial : {};
3070
+ const defaultValue = financial.v1_default_chart_account_ref ?? financial.default_chart_account_id ?? metadata["financial.v1_default_chart_account_ref"] ?? metadata["financial.default_chart_account_id"];
3071
+ const allowedValue = financial.v1_allowed_chart_accounts ?? financial.allowed_chart_accounts ?? metadata["financial.v1_allowed_chart_accounts"] ?? metadata["financial.allowed_chart_accounts"];
3072
+ const limitValue = financial.limit_chart_accounts ?? metadata["financial.limit_chart_accounts"];
3073
+ const costCenterValue = financial.v1_default_cost_center_ref ?? financial.default_cost_center_id ?? metadata["financial.v1_default_cost_center_ref"] ?? metadata["financial.default_cost_center_id"];
3074
+ const addItemsValue = financial.add_items_automatically ?? metadata["financial.add_items_automatically"];
3075
+ const defaultAccountId = typeof defaultValue === "string" && defaultValue ? defaultValue : typeof defaultValue === "number" && Number.isFinite(defaultValue) ? String(defaultValue) : void 0;
3076
+ const allowedAccountIds = stringIds(allowedValue);
3077
+ const defaultCostCenterId = typeof costCenterValue === "string" && costCenterValue ? costCenterValue : typeof costCenterValue === "number" && Number.isFinite(costCenterValue) ? String(costCenterValue) : void 0;
3078
+ if (defaultAccountId && !allowedAccountIds.includes(defaultAccountId)) allowedAccountIds.unshift(defaultAccountId);
3079
+ return {
3080
+ defaultAccountId,
3081
+ defaultCostCenterId,
3082
+ allowedAccountIds,
3083
+ limitAccounts: limitValue === true || limitValue === "true" || limitValue === 1,
3084
+ addItemsAutomatically: addItemsValue === true || addItemsValue === "true" || addItemsValue === 1
3085
+ };
3086
+ }
3087
+ function resolveCounterpartyAccountingPolicy(policy, resolvedRefs) {
3088
+ const resolve = (ref) => ref ? resolvedRefs[ref] ?? ref : void 0;
3089
+ const defaultAccountId = resolve(policy.defaultAccountId);
3090
+ const defaultCostCenterId = policy.defaultCostCenterId;
3091
+ const allowedAccountIds = [...new Set(policy.allowedAccountIds.map((ref) => resolve(ref)).filter(Boolean))];
3092
+ if (defaultAccountId && !allowedAccountIds.includes(defaultAccountId)) allowedAccountIds.unshift(defaultAccountId);
3093
+ return { ...policy, defaultAccountId, defaultCostCenterId, allowedAccountIds };
3094
+ }
3095
+ function summarizeInvoiceLines(rows, visible = 2) {
3096
+ const byInvoice = /* @__PURE__ */ new Map();
3097
+ for (const row of rows) {
3098
+ const invoiceId = row.invoice_id;
3099
+ const label = String(row.name ?? row.description ?? "").trim();
3100
+ if (!invoiceId || !label) continue;
3101
+ const quantity = Number(row.quantity) || 1;
3102
+ const rendered = quantity === 1 ? label : `${quantity} \xD7 ${label}`;
3103
+ const current = byInvoice.get(invoiceId) ?? [];
3104
+ current.push(rendered);
3105
+ byInvoice.set(invoiceId, current);
3106
+ }
3107
+ const summaries = /* @__PURE__ */ new Map();
3108
+ for (const [invoiceId, lines] of byInvoice) {
3109
+ const shown = lines.slice(0, visible);
3110
+ const remainder = lines.length - shown.length;
3111
+ summaries.set(invoiceId, `${shown.join(" \xB7 ")}${remainder > 0 ? ` \xB7 +${remainder} itens` : ""}`);
3112
+ }
3113
+ return summaries;
3114
+ }
2703
3115
  function LocationSelector({ locations, value, onChange }) {
2704
3116
  const t2 = useTranslation();
2705
3117
  const [open, setOpen] = useState(false);
@@ -2726,7 +3138,7 @@ function LocationSelector({ locations, value, onChange }) {
2726
3138
  className: "w-full mt-1 flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm text-left hover:bg-muted/30 transition-colors",
2727
3139
  children: [
2728
3140
  /* @__PURE__ */ jsx(Building2, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
2729
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? t2("financial.invoiceForm.selectUnit") }),
3141
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? (value ? t2("financial.invoiceForm.selectUnit") : "Todas / sem unidade") }),
2730
3142
  /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
2731
3143
  ]
2732
3144
  }
@@ -2752,6 +3164,19 @@ function LocationSelector({ locations, value, onChange }) {
2752
3164
  )
2753
3165
  ] }) }),
2754
3166
  /* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-auto py-1", children: [
3167
+ /* @__PURE__ */ jsx(
3168
+ "button",
3169
+ {
3170
+ type: "button",
3171
+ onClick: () => {
3172
+ onChange("");
3173
+ setOpen(false);
3174
+ setSearch("");
3175
+ },
3176
+ className: `w-full px-3 py-2.5 text-left text-sm hover:bg-muted/50 ${!value ? "bg-muted/30" : ""}`,
3177
+ children: "Todas / sem unidade"
3178
+ }
3179
+ ),
2755
3180
  filtered.map((loc) => /* @__PURE__ */ jsxs(
2756
3181
  "button",
2757
3182
  {
@@ -2788,17 +3213,83 @@ function filterByCodeOrName(rows, query) {
2788
3213
  function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fromCardId }) {
2789
3214
  const t2 = useTranslation();
2790
3215
  const config = useFinancialConfig();
3216
+ const provider = useFinancialProvider();
2791
3217
  const { currency, itemTypes: itemTypesRaw, locations } = config;
2792
- const itemTypes = React15__default.useMemo(
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]);
3234
+ const [giftCardsEnabled, setGiftCardsEnabled] = useState(false);
3235
+ useEffect(() => {
3236
+ let cancelled = false;
3237
+ if (direction !== "credit" || !provider.giftCardsEnabled) {
3238
+ setGiftCardsEnabled(false);
3239
+ return;
3240
+ }
3241
+ void provider.giftCardsEnabled().then((enabled) => {
3242
+ if (!cancelled) setGiftCardsEnabled(enabled);
3243
+ }).catch(() => {
3244
+ if (!cancelled) setGiftCardsEnabled(false);
3245
+ });
3246
+ return () => {
3247
+ cancelled = true;
3248
+ };
3249
+ }, [direction, provider]);
3250
+ const giftCardLookup = useMemo(() => {
3251
+ if (!giftCardsEnabled || !provider.getGiftCards) return null;
3252
+ const asOption = (card) => ({
3253
+ id: card.id,
3254
+ label: `Gift Card ${card.code.replace(/(.{4})(?=.)/g, "$1 ")}`,
3255
+ subtitle: card.expiresAt ? `Emitido \xB7 v\xE1lido at\xE9 ${new Date(card.expiresAt).toLocaleDateString("pt-BR")}` : "Emitido \xB7 sem validade",
3256
+ icon: "Gift",
3257
+ price: card.faceValue,
3258
+ data: { price: card.faceValue, code: card.code, state: card.state }
3259
+ });
3260
+ return {
3261
+ async search(query) {
3262
+ const cards = await provider.getGiftCards({ search: query.trim() || void 0, state: "issued" });
3263
+ return cards.map(asOption);
3264
+ },
3265
+ async getById(id) {
3266
+ const cards = await provider.getGiftCards({ state: "issued" });
3267
+ const card = cards.find((entry) => entry.id === id);
3268
+ return card ? asOption(card) : null;
3269
+ },
3270
+ async list() {
3271
+ return (await provider.getGiftCards({ state: "issued" })).map(asOption);
3272
+ }
3273
+ };
3274
+ }, [giftCardsEnabled, provider]);
3275
+ const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
3276
+ const translatedItemTypes = React20__default.useMemo(
2793
3277
  () => itemTypesRaw.map((tp) => {
2794
3278
  const traduzido = t2(`financial.itemType.${tp.value}`);
2795
3279
  return traduzido === `financial.itemType.${tp.value}` ? tp : { ...tp, label: traduzido };
2796
3280
  }),
2797
3281
  [itemTypesRaw, t2]
2798
3282
  );
2799
- const provider = useFinancialProvider();
2800
- const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
2801
- const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
3283
+ const itemTypes = React20__default.useMemo(
3284
+ () => availableInvoiceItemTypes(
3285
+ giftCardsEnabled && !translatedItemTypes.some((type) => type.value === "gift_card") ? [...translatedItemTypes, { value: "gift_card", label: t2("financial.itemType.gift_card"), icon: "Gift" }] : translatedItemTypes,
3286
+ Object.keys(entityLookups),
3287
+ t2("financial.itemType.other")
3288
+ ),
3289
+ [translatedItemTypes, giftCardsEnabled, entityLookups, t2]
3290
+ );
3291
+ const hasItemLookups = itemTypes.some((tp) => entityLookups[tp.value]);
3292
+ const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
2802
3293
  const bankAccounts = useFinancialStore((s) => s.bankAccounts);
2803
3294
  const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
2804
3295
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
@@ -2807,7 +3298,6 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
2807
3298
  const createInvoice = useFinancialStore((s) => s.createInvoice);
2808
3299
  const guardMovements = useLimitGuard("movements_month");
2809
3300
  const can = usePermissionOptional();
2810
- const hasLocations = locations.length > 0;
2811
3301
  const isEdit = !!editId;
2812
3302
  useEffect(() => {
2813
3303
  if (isEdit) return;
@@ -2827,18 +3317,19 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
2827
3317
  };
2828
3318
  }, [provider]);
2829
3319
  const contactLookup = useMemo(
2830
- () => createArchetypeLookup({
2831
- archetype: "person",
2832
- kind: direction === "debit" ? "supplier" : "customer",
2833
- createKinds: direction === "debit" ? ["supplier", "staff", "customer"] : ["customer", "supplier", "staff"],
2834
- kindLabels: { supplier: "Fornecedor", customer: "Cliente", staff: "Funcion\xE1rio" }
2835
- }),
3320
+ () => createFinancialCounterpartyLookup(direction),
2836
3321
  [direction]
2837
3322
  );
2838
3323
  const [loaded, setLoaded] = useState(!editId);
2839
3324
  const [loading, setLoading] = useState(!!editId);
2840
3325
  const [contactName, setContactName] = useState("");
2841
3326
  const [contactId, setContactId] = useState();
3327
+ const [counterpartyAccounting, setCounterpartyAccounting] = useState({
3328
+ allowedAccountIds: [],
3329
+ limitAccounts: false,
3330
+ addItemsAutomatically: false
3331
+ });
3332
+ const accountingPolicyRequest = useRef(0);
2842
3333
  const [unitId, setUnitId] = useState(locations[0]?.id ?? "");
2843
3334
  const [invoiceDate, setInvoiceDate] = useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2844
3335
  const [fiscalNumber, setFiscalNumber] = useState("");
@@ -2846,6 +3337,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
2846
3337
  const [items, setItems] = useState([]);
2847
3338
  const [accounts, setAccounts] = useState([]);
2848
3339
  const postable = useMemo(() => postableAccounts(accounts), [accounts]);
3340
+ const selectableAccounts = useMemo(() => {
3341
+ if (!counterpartyAccounting.limitAccounts) return postable;
3342
+ const allowed = new Set(counterpartyAccounting.allowedAccountIds);
3343
+ return postable.filter((account) => allowed.has(account.id));
3344
+ }, [postable, counterpartyAccounting]);
2849
3345
  const [costCenters, setCostCenters] = useState([]);
2850
3346
  const [expandedItemId, setExpandedItemId] = useState(null);
2851
3347
  const [addingItem, setAddingItem] = useState(false);
@@ -2917,6 +3413,16 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
2917
3413
  if (!invoice) return;
2918
3414
  setContactName(invoice.contactName ?? "");
2919
3415
  setContactId(invoice.contactId);
3416
+ if (invoice.contactId) {
3417
+ try {
3418
+ const contact = await contactLookup.getById(invoice.contactId);
3419
+ const policy = counterpartyAccountingPolicy(contact?.data);
3420
+ const refs = [policy.defaultAccountId, ...policy.allowedAccountIds].filter(Boolean);
3421
+ const resolved = await provider.resolveChartAccountRefs?.(refs) ?? {};
3422
+ setCounterpartyAccounting(resolveCounterpartyAccountingPolicy(policy, resolved));
3423
+ } catch {
3424
+ }
3425
+ }
2920
3426
  setUnitId(invoice.unitId ?? locations[0]?.id ?? "");
2921
3427
  setInvoiceDate(invoice.invoiceDate);
2922
3428
  setFiscalNumber(invoice.fiscalNumber ?? "");
@@ -2976,13 +3482,19 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
2976
3482
  itemKind: kind,
2977
3483
  description: "",
2978
3484
  quantity: 1,
2979
- unitPrice: 0
3485
+ unitPrice: 0,
3486
+ accountId: counterpartyAccounting.defaultAccountId,
3487
+ costCenterId: counterpartyAccounting.defaultCostCenterId
2980
3488
  };
2981
3489
  setItems([...items, newItem]);
2982
3490
  setExpandedItemId(newItem._id);
2983
3491
  setAddingItem(false);
2984
3492
  }
2985
3493
  function startAddItem() {
3494
+ if (!contactId) {
3495
+ toast.error(t2("financial.invoiceForm.contactRequired"));
3496
+ return;
3497
+ }
2986
3498
  if (hasItemLookups) setAddingItem(true);
2987
3499
  else addItemOfKind(defaultItemKind);
2988
3500
  }
@@ -2996,13 +3508,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
2996
3508
  if (isEdit || totalAmount <= 0 || installments.length > 0) return;
2997
3509
  generateInstallments(1);
2998
3510
  }, [isEdit, totalAmount, installments.length]);
2999
- const fromCard = React15__default.useMemo(
3511
+ const fromCard = React20__default.useMemo(
3000
3512
  () => fromCardId ? bankAccounts.find((a) => a.id === fromCardId) : void 0,
3001
3513
  [fromCardId, bankAccounts]
3002
3514
  );
3003
3515
  const cardLast4 = (fromCard?.accountNumber ?? "").replace(/\D/g, "").slice(-4);
3004
3516
  const nomeJaTemOsDigitos = !!cardLast4 && (fromCard?.name ?? "").replace(/\D/g, "").endsWith(cardLast4);
3005
- const cardMethodName = React15__default.useMemo(() => {
3517
+ const cardMethodName = React20__default.useMemo(() => {
3006
3518
  const id = installments[0]?.payMethodTypeId;
3007
3519
  const escolhida = id ? paymentMethodTypes.find((tp) => tp.id === id) : void 0;
3008
3520
  return (escolhida ?? paymentMethodTypes.find((tp) => tp.transactionType === "credit_card"))?.name;
@@ -3084,6 +3596,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3084
3596
  }
3085
3597
  async function handleSave() {
3086
3598
  if (!isEdit && !can("financial", "create")) return;
3599
+ if (!contactId) {
3600
+ toast.error(t2("financial.invoiceForm.contactRequired"));
3601
+ return;
3602
+ }
3087
3603
  if (items.length === 0) {
3088
3604
  toast.error(t2("common.formIncomplete"));
3089
3605
  return;
@@ -3356,52 +3872,70 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3356
3872
  }) })
3357
3873
  ] }) }) })
3358
3874
  );
3359
- const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
3360
- /* @__PURE__ */ jsx("div", { ...tourAnchor("financial.contact"), children: /* @__PURE__ */ jsxs(OrderFormBlock, { title: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"), children: [
3875
+ const contactSection = /* @__PURE__ */ jsx("div", { ...tourAnchor("financial.contact"), children: /* @__PURE__ */ jsxs(OrderFormBlock, { title: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"), children: [
3876
+ /* @__PURE__ */ jsx(
3877
+ FinancialCounterpartySearchSelect,
3878
+ {
3879
+ required: true,
3880
+ lookup: contactLookup,
3881
+ value: contactId ?? "",
3882
+ displayValue: contactName,
3883
+ onChange: (id, opt) => {
3884
+ setContactId(id || void 0);
3885
+ setContactName(opt?.label ?? "");
3886
+ setPendingContact(null);
3887
+ const request = ++accountingPolicyRequest.current;
3888
+ if (!id) {
3889
+ setCounterpartyAccounting({ allowedAccountIds: [], limitAccounts: false, addItemsAutomatically: false });
3890
+ return;
3891
+ }
3892
+ const policy = counterpartyAccountingPolicy(opt?.data);
3893
+ setCounterpartyAccounting({ allowedAccountIds: [], limitAccounts: policy.limitAccounts, addItemsAutomatically: policy.addItemsAutomatically });
3894
+ const refs = [policy.defaultAccountId, ...policy.allowedAccountIds].filter(Boolean);
3895
+ void (async () => {
3896
+ let resolvedRefs = {};
3897
+ try {
3898
+ resolvedRefs = await provider.resolveChartAccountRefs?.(refs) ?? {};
3899
+ } catch {
3900
+ }
3901
+ if (request !== accountingPolicyRequest.current) return;
3902
+ const resolved = resolveCounterpartyAccountingPolicy(policy, resolvedRefs);
3903
+ setCounterpartyAccounting(resolved);
3904
+ if (resolved.defaultAccountId) {
3905
+ setItems((current) => current.map((item) => item.accountId ? item : { ...item, accountId: resolved.defaultAccountId }));
3906
+ }
3907
+ if (resolved.defaultCostCenterId) {
3908
+ setItems((current) => current.map((item) => item.costCenterId ? item : { ...item, costCenterId: resolved.defaultCostCenterId }));
3909
+ }
3910
+ })();
3911
+ },
3912
+ onCreate: contactCreate.onCreate
3913
+ }
3914
+ ),
3915
+ 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: [
3916
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [
3917
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
3918
+ t2("financial.receipt.fromReceipt"),
3919
+ ": "
3920
+ ] }),
3921
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pendingContact })
3922
+ ] }),
3361
3923
  /* @__PURE__ */ jsx(
3362
- SearchSelect,
3924
+ Button,
3363
3925
  {
3364
- required: true,
3365
- value: contactId ?? "",
3366
- displayValue: contactName,
3367
- onChange: (id, opt) => {
3368
- setContactId(id || void 0);
3369
- setContactName(opt?.label ?? "");
3370
- setPendingContact(null);
3371
- },
3372
- onSearch: async (q) => {
3373
- const results = await contactLookup.search(q);
3374
- return results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, group: r.group, data: r }));
3375
- },
3376
- createLabel: t2("financial.invoiceForm.createContact"),
3377
- clearLabel: t2("financial.invoiceForm.clear"),
3378
- onCreate: contactCreate.onCreate,
3379
- placeholder: t2("financial.invoiceForm.searchType")
3926
+ size: "sm",
3927
+ variant: "outline",
3928
+ className: "h-7 shrink-0",
3929
+ onClick: () => contactCreate.onCreate(pendingContact),
3930
+ children: t2("financial.receipt.finishContact")
3380
3931
  }
3381
- ),
3382
- 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: [
3383
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [
3384
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
3385
- t2("financial.receipt.fromReceipt"),
3386
- ": "
3387
- ] }),
3388
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pendingContact })
3389
- ] }),
3390
- /* @__PURE__ */ jsx(
3391
- Button,
3392
- {
3393
- size: "sm",
3394
- variant: "outline",
3395
- className: "h-7 shrink-0",
3396
- onClick: () => contactCreate.onCreate(pendingContact),
3397
- children: t2("financial.receipt.finishContact")
3398
- }
3399
- )
3400
- ] }),
3401
- contactCreate.modal
3402
- ] }) }),
3932
+ )
3933
+ ] }),
3934
+ contactCreate.modal
3935
+ ] }) });
3936
+ const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
3403
3937
  /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3404
- hasLocations && /* @__PURE__ */ jsx(LocationSelector, { locations, value: unitId, onChange: setUnitId }),
3938
+ /* @__PURE__ */ jsx(LocationSelector, { locations: availableLocations, value: unitId, onChange: setUnitId }),
3405
3939
  /* @__PURE__ */ jsxs("div", { children: [
3406
3940
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
3407
3941
  /* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
@@ -3485,18 +4019,19 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3485
4019
  actions,
3486
4020
  sideSections,
3487
4021
  children: [
4022
+ contactSection,
3488
4023
  /* @__PURE__ */ jsx(
3489
4024
  OrderFormItemsCard,
3490
4025
  {
3491
4026
  "data-tour": "financial.items",
3492
4027
  label: t2("financial.invoiceForm.items"),
3493
4028
  addLabel: t2("financial.invoiceForm.addItem"),
3494
- onAdd: startAddItem,
4029
+ onAdd: contactId ? startAddItem : void 0,
3495
4030
  count: items.length,
3496
4031
  countLabel: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
3497
4032
  totalLabel: t2("financial.invoiceForm.total"),
3498
4033
  total: money3(totalAmount),
3499
- emptyLabel: t2("financial.invoiceForm.noItems"),
4034
+ emptyLabel: contactId ? t2("financial.invoiceForm.noItems") : t2("financial.invoiceForm.selectContactFirst"),
3500
4035
  emptyActionLabel: t2("financial.invoiceForm.addFirstItem"),
3501
4036
  header: addingItem && /* @__PURE__ */ jsx(
3502
4037
  OrderFormAddItemStep,
@@ -3548,7 +4083,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3548
4083
  placeholder: t2("financial.invoiceForm.accountNone"),
3549
4084
  minChars: 0,
3550
4085
  noResultsLabel: t2("financial.invoiceForm.accountNone"),
3551
- onSearch: (q) => filterByCodeOrName(postable, q).map((a) => ({
4086
+ onSearch: (q) => filterByCodeOrName(selectableAccounts, q).map((a) => ({
3552
4087
  id: a.id,
3553
4088
  label: accountLabel(a),
3554
4089
  subtitle: a.accountType ?? void 0
@@ -3581,7 +4116,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3581
4116
  )
3582
4117
  }] : []
3583
4118
  ],
3584
- entityLookups: config.entityLookups,
4119
+ entityLookups,
3585
4120
  showTypeSelector: hasItemLookups,
3586
4121
  allowSurcharge: true,
3587
4122
  labels: {
@@ -3800,10 +4335,17 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
3800
4335
  async function loadPaymentDetails() {
3801
4336
  if (detailsLoaded || detailsLoading) return;
3802
4337
  setDetailsLoading(true);
3803
- await Promise.all([fetchPaymentMethods(), fetchBankAccounts()]);
3804
- setDetailsLoaded(true);
3805
- setDetailsLoading(false);
4338
+ try {
4339
+ await Promise.all([fetchPaymentMethods(), fetchBankAccounts()]);
4340
+ setDetailsLoaded(true);
4341
+ } catch {
4342
+ } finally {
4343
+ setDetailsLoading(false);
4344
+ }
3806
4345
  }
4346
+ useEffect(() => {
4347
+ void loadPaymentDetails();
4348
+ }, [invoiceId]);
3807
4349
  function handleExpandMov(movId) {
3808
4350
  const opening = expandedMovId !== movId;
3809
4351
  setExpandedMovId(opening ? movId : null);
@@ -3946,6 +4488,12 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
3946
4488
  const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
3947
4489
  const MovIcon = movStatus.icon;
3948
4490
  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(" / ");
3949
4497
  return /* @__PURE__ */ jsxs("div", { children: [
3950
4498
  /* @__PURE__ */ jsxs(
3951
4499
  "div",
@@ -3972,6 +4520,10 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
3972
4520
  mov.dueDate,
3973
4521
  mov.paidAmount > 0 && !isPaid && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
3974
4522
  mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
4523
+ ] }),
4524
+ paymentSummary && /* @__PURE__ */ jsxs("p", { className: "mt-0.5 flex items-center gap-1 text-[10px] font-medium text-foreground", children: [
4525
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
4526
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: paymentSummary })
3975
4527
  ] })
3976
4528
  ] }),
3977
4529
  attachments.length > 0 && /* @__PURE__ */ jsxs(
@@ -4821,7 +5373,7 @@ function ReviewQueue({ variant }) {
4821
5373
  const me = user?.id ?? null;
4822
5374
  const [busy, setBusy] = useState(null);
4823
5375
  const [error, setError] = useState(null);
4824
- const load = React15__default.useCallback(async () => {
5376
+ const load = React20__default.useCallback(async () => {
4825
5377
  try {
4826
5378
  setError(null);
4827
5379
  setRows(await provider.getCashSessionReviews?.() ?? []);
@@ -5216,7 +5768,23 @@ function newestStatementEntriesFirst(entries) {
5216
5768
  return right.index - left.index;
5217
5769
  }).map(({ entry }) => entry);
5218
5770
  }
5219
- var ALL = "all";
5771
+ var shortDate2 = (value) => {
5772
+ const [year, month, day] = value.split("-");
5773
+ return year && month && day ? `${day}/${month}/${year}` : value;
5774
+ };
5775
+ var ACCOUNT_GROUPS = {
5776
+ person: "Pessoas",
5777
+ unit: "Unidades",
5778
+ bank: "Contas banc\xE1rias",
5779
+ cash_physical: "Caixas",
5780
+ cash_session: "Caixas",
5781
+ card_receivable_credit: "Cart\xF5es e receb\xEDveis",
5782
+ card_receivable_debit: "Cart\xF5es e receb\xEDveis",
5783
+ card_payable: "Cart\xF5es e receb\xEDveis",
5784
+ unidentified: "Outras contas",
5785
+ system: "Contas do sistema"
5786
+ };
5787
+ var financialAccountGroup = (kind) => ACCOUNT_GROUPS[kind] ?? "Outras contas";
5220
5788
  function ReconcileMarker({ kind, label }) {
5221
5789
  return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 150, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
5222
5790
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0 cursor-default", children: kind === "reconciled" ? /* @__PURE__ */ jsx(CheckCheck, { className: "h-3 w-3 text-success/70" }) : /* @__PURE__ */ jsx(Landmark, { className: "h-3 w-3 text-muted-foreground/40" }) }) }),
@@ -5236,15 +5804,15 @@ function ReconciledWithRef({ refLabel, tooltip, onOpen }) {
5236
5804
  function StatementsView({ onNavigate }) {
5237
5805
  const t2 = useTranslation();
5238
5806
  const { currency } = useFinancialConfig();
5239
- const bankAccounts = useFinancialStore((s) => s.bankAccounts);
5807
+ const provider = useFinancialProvider();
5240
5808
  const statement = useFinancialStore((s) => s.statement);
5241
5809
  const statementLoading = useFinancialStore((s) => s.statementLoading);
5242
5810
  const paymentMethods2 = useFinancialStore((s) => s.paymentMethods);
5243
5811
  const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
5244
- const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
5245
5812
  const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
5246
5813
  const fetchStatement = useFinancialStore((s) => s.fetchStatement);
5247
5814
  const [accountId, setAccountId] = useState("");
5815
+ const [selectedAccount, setSelectedAccount] = useState(null);
5248
5816
  const MAX_RANGE_DAYS2 = 90;
5249
5817
  const hojeIso = () => {
5250
5818
  const d = /* @__PURE__ */ new Date();
@@ -5279,9 +5847,8 @@ function StatementsView({ onNavigate }) {
5279
5847
  const [search, setSearch] = useState("");
5280
5848
  const [showTransfer, setShowTransfer] = useState(false);
5281
5849
  useEffect(() => {
5282
- fetchBankAccounts();
5283
5850
  fetchPaymentMethods();
5284
- }, []);
5851
+ }, [fetchPaymentMethods]);
5285
5852
  const methodName = useMemo(() => {
5286
5853
  const byId = new Map(paymentMethods2.map((m) => [m.id, m.name]));
5287
5854
  const typeById = new Map(paymentMethodTypes.map((tp) => [tp.id, tp.name]));
@@ -5300,14 +5867,11 @@ function StatementsView({ onNavigate }) {
5300
5867
  useEffect(() => {
5301
5868
  if (accountId) {
5302
5869
  fetchStatement({
5303
- bankAccountId: accountId === ALL ? void 0 : accountId,
5870
+ bankAccountId: accountId,
5304
5871
  dateRange: { from: dateFrom, to: dateTo }
5305
5872
  });
5306
5873
  }
5307
5874
  }, [accountId, dateFrom, dateTo]);
5308
- useEffect(() => {
5309
- if (!accountId) setAccountId(ALL);
5310
- }, []);
5311
5875
  const fmt = (n) => formatCurrency(n, currency);
5312
5876
  const columns = useMemo(() => [
5313
5877
  {
@@ -5412,7 +5976,7 @@ function StatementsView({ onNavigate }) {
5412
5976
  }
5413
5977
  ], [currency, t2, methodName, onNavigate]);
5414
5978
  const entries = useMemo(() => {
5415
- const all = statement?.entries ?? [];
5979
+ const all = accountId ? statement?.entries ?? [] : [];
5416
5980
  const term = search.trim().toLowerCase();
5417
5981
  const filtered = !term ? all : all.filter((e) => {
5418
5982
  const haystack = [e.invoice?.contactName, e.movement.notes, e.invoice?.fiscalNumber, methodName(e.movement)];
@@ -5441,16 +6005,15 @@ function StatementsView({ onNavigate }) {
5441
6005
  "extrato"
5442
6006
  );
5443
6007
  };
5444
- const headerActions = React15__default.useMemo(() => [{
6008
+ const headerActions = React20__default.useMemo(() => [{
5445
6009
  id: "new-transfer",
5446
6010
  label: t2("financial.statements.newTransfer"),
5447
6011
  icon: ArrowRightLeft,
5448
6012
  intent: "primary",
5449
6013
  // Uma transferência é um lançamento novo — o FAB é um "+".
5450
6014
  creates: true,
5451
- disabled: bankAccounts.length < 2,
5452
6015
  onSelect: () => setShowTransfer(true)
5453
- }], [t2, bankAccounts.length]);
6016
+ }], [t2]);
5454
6017
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
5455
6018
  /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PageActions, { actions: headerActions }) }),
5456
6019
  accountId && statement && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-2", children: [
@@ -5476,49 +6039,52 @@ function StatementsView({ onNavigate }) {
5476
6039
  ] }),
5477
6040
  /* @__PURE__ */ jsxs("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-2", children: [
5478
6041
  /* @__PURE__ */ jsx(
5479
- FilterMenu,
6042
+ SearchSelect,
5480
6043
  {
5481
- label: t2("crud.list.filters"),
5482
- activeCount: (accountId && accountId !== ALL ? 1 : 0) + (dateFrom === defaultFrom && dateTo === hojeIso() ? 0 : 1),
5483
- onClear: () => {
5484
- setAccountId(ALL);
5485
- setDateFrom(defaultFrom);
5486
- setDateTo(hojeIso());
6044
+ variant: "required-filter",
6045
+ required: true,
6046
+ label: t2("financial.statements.account"),
6047
+ value: accountId,
6048
+ displayValue: selectedAccount?.name ?? "",
6049
+ icon: Landmark,
6050
+ placeholder: "Buscar conta ou pessoa...",
6051
+ minChars: 2,
6052
+ minCharsLabel: "Digite pelo menos 2 caracteres para buscar.",
6053
+ loadOnOpen: false,
6054
+ noResultsLabel: "Nenhuma conta ou pessoa encontrada.",
6055
+ searchingLabel: "Buscando contas e pessoas...",
6056
+ clearLabel: "Limpar",
6057
+ onChange: (id, option) => {
6058
+ setAccountId(id);
6059
+ setSelectedAccount(option?.data ?? null);
5487
6060
  },
5488
- clearLabel: t2("financial.invoice.filterClear"),
5489
- fields: [
5490
- {
5491
- id: "account",
5492
- label: t2("financial.statements.account"),
5493
- icon: Landmark,
5494
- activeCount: accountId && accountId !== ALL ? 1 : 0,
5495
- searchable: true,
5496
- render: (close, q) => /* @__PURE__ */ jsxs(Fragment, { children: [
5497
- /* @__PURE__ */ jsx(FilterMenuRow, { active: !accountId || accountId === ALL, onClick: () => {
5498
- setAccountId(ALL);
5499
- close();
5500
- }, children: t2("financial.summary.allAccounts") }),
5501
- bankAccounts.filter((a) => a.name.toLowerCase().includes(q.toLowerCase())).map((a) => /* @__PURE__ */ jsx(FilterMenuRow, { active: accountId === a.id, onClick: () => {
5502
- setAccountId(a.id);
5503
- close();
5504
- }, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: a.name }) }, a.id))
5505
- ] })
5506
- },
5507
- {
5508
- id: "period",
5509
- label: t2("financial.invoice.filterPeriod"),
5510
- icon: CalendarDays,
5511
- activeCount: dateFrom === defaultFrom && dateTo === hojeIso() ? 0 : 1,
5512
- render: () => /* @__PURE__ */ jsxs(Fragment, { children: [
5513
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 p-1.5", children: [
5514
- /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateFrom, onChange: setFromClamped }),
5515
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
5516
- /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateTo, onChange: setToClamped })
5517
- ] }),
5518
- /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-[10px] text-muted-foreground", children: t2("financial.invoice.filterMaxRange") })
5519
- ] })
5520
- }
5521
- ]
6061
+ onSearch: async (query) => {
6062
+ const accounts = provider.searchFinancialAccounts ? await provider.searchFinancialAccounts(query, 20) : (await provider.getBankAccounts()).filter((account) => account.name.toLocaleLowerCase("pt-BR").includes(query.toLocaleLowerCase("pt-BR"))).slice(0, 20);
6063
+ return accounts.map((account) => ({
6064
+ id: account.id,
6065
+ label: account.name,
6066
+ subtitle: account.bankName || account.accountNumber,
6067
+ group: financialAccountGroup(account.accountType),
6068
+ data: account
6069
+ }));
6070
+ }
6071
+ }
6072
+ ),
6073
+ /* @__PURE__ */ jsx(
6074
+ RequiredFilter,
6075
+ {
6076
+ label: t2("financial.invoice.filterPeriod"),
6077
+ value: `${shortDate2(dateFrom)} \u2013 ${shortDate2(dateTo)}`,
6078
+ icon: CalendarDays,
6079
+ align: "end",
6080
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
6081
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 p-1.5", children: [
6082
+ /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateFrom, onChange: setFromClamped }),
6083
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
6084
+ /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateTo, onChange: setToClamped })
6085
+ ] }),
6086
+ /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-[10px] text-muted-foreground", children: t2("financial.invoice.filterMaxRange") })
6087
+ ] })
5522
6088
  }
5523
6089
  ),
5524
6090
  entries.length > 0 && // O mesmo utilitário da barra de toda lista (CrudListView): ghost
@@ -5545,7 +6111,7 @@ function StatementsView({ onNavigate }) {
5545
6111
  variant: "bare",
5546
6112
  compact: true,
5547
6113
  columns,
5548
- data: statementLoading ? [] : entries,
6114
+ data: statementLoading || !accountId ? [] : entries,
5549
6115
  loading: statementLoading,
5550
6116
  skeletonRows: statementLoading ? 8 : void 0,
5551
6117
  emptyMessage: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 py-6", children: [
@@ -5558,7 +6124,7 @@ function StatementsView({ onNavigate }) {
5558
6124
  showTransfer && /* @__PURE__ */ jsx(
5559
6125
  TransferModal,
5560
6126
  {
5561
- defaultFromId: accountId && accountId !== ALL ? accountId : void 0,
6127
+ defaultFromId: accountId || void 0,
5562
6128
  onClose: () => setShowTransfer(false),
5563
6129
  onDone: () => setShowTransfer(false)
5564
6130
  }
@@ -5597,15 +6163,15 @@ function CommissionsView() {
5597
6163
  const fetchCommissions = useFinancialStore((s) => s.fetchCommissions);
5598
6164
  const generateCommissions = useFinancialStore((s) => s.generateCommissions);
5599
6165
  const provider = useFinancialProvider();
5600
- const [from, setFrom] = React15__default.useState(firstOfMonth());
5601
- const [to, setTo] = React15__default.useState(today2());
5602
- const [preset, setPreset] = React15__default.useState("this_month");
5603
- const [running, setRunning] = React15__default.useState(false);
5604
- const [lastRun, setLastRun] = React15__default.useState(null);
5605
- const [generation, setGeneration] = React15__default.useState(null);
5606
- const [busy, setBusy] = React15__default.useState(null);
5607
- const [error, setError] = React15__default.useState(null);
5608
- const loadGeneration = React15__default.useCallback(async () => {
6166
+ const [from, setFrom] = React20__default.useState(firstOfMonth());
6167
+ const [to, setTo] = React20__default.useState(today2());
6168
+ const [preset, setPreset] = React20__default.useState("this_month");
6169
+ const [running, setRunning] = React20__default.useState(false);
6170
+ const [lastRun, setLastRun] = React20__default.useState(null);
6171
+ const [generation, setGeneration] = React20__default.useState(null);
6172
+ const [busy, setBusy] = React20__default.useState(null);
6173
+ const [error, setError] = React20__default.useState(null);
6174
+ const loadGeneration = React20__default.useCallback(async () => {
5609
6175
  try {
5610
6176
  const gens = await provider.getCommissionGenerations?.() ?? [];
5611
6177
  const doPeriodo = gens.filter((g) => g.periodStart === from && g.periodEnd === to);
@@ -5613,11 +6179,11 @@ function CommissionsView() {
5613
6179
  } catch {
5614
6180
  }
5615
6181
  }, [provider, from, to]);
5616
- React15__default.useEffect(() => {
6182
+ React20__default.useEffect(() => {
5617
6183
  void fetchCommissions({ periodStart: from, periodEnd: to });
5618
6184
  void loadGeneration();
5619
6185
  }, [fetchCommissions, loadGeneration, from, to]);
5620
- const byPerson = React15__default.useMemo(() => {
6186
+ const byPerson = React20__default.useMemo(() => {
5621
6187
  const map = /* @__PURE__ */ new Map();
5622
6188
  for (const c of commissions2) {
5623
6189
  const key = c.personId;
@@ -5670,7 +6236,7 @@ function CommissionsView() {
5670
6236
  setBusy(null);
5671
6237
  }
5672
6238
  }
5673
- const headerActions = React15__default.useMemo(() => [{
6239
+ const headerActions = React20__default.useMemo(() => [{
5674
6240
  id: "commission-rules",
5675
6241
  label: "Configurar regras de comiss\xE3o",
5676
6242
  icon: Settings,
@@ -6082,13 +6648,13 @@ function money2(v) {
6082
6648
  function GoalsView() {
6083
6649
  const { labels } = useFinancialConfig();
6084
6650
  const provider = useFinancialProvider();
6085
- const [units, setUnits] = React15__default.useState([]);
6086
- const [unitId, setUnitId] = React15__default.useState("");
6087
- const [year, setYear] = React15__default.useState((/* @__PURE__ */ new Date()).getFullYear());
6088
- const [rows, setRows] = React15__default.useState(null);
6089
- const [editing, setEditing] = React15__default.useState(null);
6090
- const [error, setError] = React15__default.useState(null);
6091
- React15__default.useEffect(() => {
6651
+ const [units, setUnits] = React20__default.useState([]);
6652
+ const [unitId, setUnitId] = React20__default.useState("");
6653
+ const [year, setYear] = React20__default.useState((/* @__PURE__ */ new Date()).getFullYear());
6654
+ const [rows, setRows] = React20__default.useState(null);
6655
+ const [editing, setEditing] = React20__default.useState(null);
6656
+ const [error, setError] = React20__default.useState(null);
6657
+ React20__default.useEffect(() => {
6092
6658
  void (async () => {
6093
6659
  const cat = await provider.getCommissionCatalogs?.();
6094
6660
  const us = cat?.units ?? [];
@@ -6096,7 +6662,7 @@ function GoalsView() {
6096
6662
  setUnitId((cur) => cur || us[0]?.id || "");
6097
6663
  })();
6098
6664
  }, [provider]);
6099
- const load = React15__default.useCallback(async () => {
6665
+ const load = React20__default.useCallback(async () => {
6100
6666
  if (!unitId) return;
6101
6667
  setRows(null);
6102
6668
  try {
@@ -6107,7 +6673,7 @@ function GoalsView() {
6107
6673
  setRows([]);
6108
6674
  }
6109
6675
  }, [provider, unitId, year]);
6110
- React15__default.useEffect(() => {
6676
+ React20__default.useEffect(() => {
6111
6677
  void load();
6112
6678
  }, [load]);
6113
6679
  const totalRealizado = (rows ?? []).reduce((s, r) => s + r.achievedAmount, 0);
@@ -6238,11 +6804,11 @@ function GoalDialog({
6238
6804
  onSaved
6239
6805
  }) {
6240
6806
  const provider = useFinancialProvider();
6241
- const [target, setTarget] = React15__default.useState(String(goal.targetAmount || ""));
6242
- const [sup, setSup] = React15__default.useState(String(goal.superAmount || ""));
6243
- const [hyper, setHyper] = React15__default.useState(String(goal.hyperAmount || ""));
6244
- const [saving, setSaving] = React15__default.useState(false);
6245
- const [error, setError] = React15__default.useState(null);
6807
+ const [target, setTarget] = React20__default.useState(String(goal.targetAmount || ""));
6808
+ const [sup, setSup] = React20__default.useState(String(goal.superAmount || ""));
6809
+ const [hyper, setHyper] = React20__default.useState(String(goal.hyperAmount || ""));
6810
+ const [saving, setSaving] = React20__default.useState(false);
6811
+ const [error, setError] = React20__default.useState(null);
6246
6812
  const t2 = Number(target) || 0;
6247
6813
  const s = Number(sup) || 0;
6248
6814
  const h = Number(hyper) || 0;
@@ -6332,21 +6898,22 @@ function Dash() {
6332
6898
  function GiftCardsView() {
6333
6899
  const provider = useFinancialProvider();
6334
6900
  const { currency } = useFinancialConfig();
6335
- const [enabled, setEnabled] = React15__default.useState(null);
6336
- const [cards, setCards] = React15__default.useState(null);
6337
- const [error, setError] = React15__default.useState(null);
6338
- const [issuing, setIssuing] = React15__default.useState(false);
6339
- const [issued, setIssued] = React15__default.useState(null);
6340
- const [activating, setActivating] = React15__default.useState(null);
6341
- const [search, setSearch] = React15__default.useState("");
6342
- const [state, setState] = React15__default.useState("");
6343
- const [minValue, setMinValue] = React15__default.useState("");
6344
- const [maxValue, setMaxValue] = React15__default.useState("");
6345
- const [from, setFrom] = React15__default.useState("");
6346
- const [to, setTo] = React15__default.useState("");
6347
- const money3 = React15__default.useCallback((v) => formatCurrency(v, currency), [currency]);
6348
- const seq = React15__default.useRef(0);
6349
- const load = React15__default.useCallback(async () => {
6901
+ const can = usePermissionOptional();
6902
+ const [enabled, setEnabled] = React20__default.useState(null);
6903
+ const [cards, setCards] = React20__default.useState(null);
6904
+ const [error, setError] = React20__default.useState(null);
6905
+ const [issuing, setIssuing] = React20__default.useState(false);
6906
+ const [issued, setIssued] = React20__default.useState(null);
6907
+ const [activating, setActivating] = React20__default.useState(null);
6908
+ const [search, setSearch] = React20__default.useState("");
6909
+ const [state, setState] = React20__default.useState("");
6910
+ const [minValue, setMinValue] = React20__default.useState("");
6911
+ const [maxValue, setMaxValue] = React20__default.useState("");
6912
+ const [from, setFrom] = React20__default.useState("");
6913
+ const [to, setTo] = React20__default.useState("");
6914
+ const money3 = React20__default.useCallback((v) => formatCurrency(v, currency), [currency]);
6915
+ const seq = React20__default.useRef(0);
6916
+ const load = React20__default.useCallback(async () => {
6350
6917
  const mine = ++seq.current;
6351
6918
  setCards(null);
6352
6919
  setError(null);
@@ -6367,7 +6934,7 @@ function GiftCardsView() {
6367
6934
  setCards([]);
6368
6935
  }
6369
6936
  }, [provider, search, state, minValue, maxValue, from, to]);
6370
- React15__default.useEffect(() => {
6937
+ React20__default.useEffect(() => {
6371
6938
  void (async () => {
6372
6939
  try {
6373
6940
  setEnabled(await provider.giftCardsEnabled?.() === true);
@@ -6376,9 +6943,64 @@ function GiftCardsView() {
6376
6943
  }
6377
6944
  })();
6378
6945
  }, [provider]);
6379
- React15__default.useEffect(() => {
6946
+ React20__default.useEffect(() => {
6380
6947
  if (enabled) void load();
6381
6948
  }, [enabled, load]);
6949
+ const columns = React20__default.useMemo(() => [
6950
+ {
6951
+ accessorKey: "code",
6952
+ header: "C\xF3digo",
6953
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "font-mono text-xs tabular-nums", children: formatCode(row.original.code) })
6954
+ },
6955
+ {
6956
+ accessorKey: "faceValue",
6957
+ header: "Valor",
6958
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: money3(row.original.faceValue) })
6959
+ },
6960
+ {
6961
+ accessorKey: "balance",
6962
+ header: "Saldo",
6963
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: row.original.balance === null ? /* @__PURE__ */ jsx(Dash, {}) : money3(row.original.balance) })
6964
+ },
6965
+ {
6966
+ accessorKey: "state",
6967
+ header: "Status",
6968
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium ${STATE_META[row.original.state].className}`, children: STATE_META[row.original.state].label })
6969
+ },
6970
+ { accessorKey: "issuedAt", header: "Emitido", cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs", children: formatDate(row.original.issuedAt) ?? /* @__PURE__ */ jsx(Dash, {}) }) },
6971
+ { accessorKey: "expiresAt", header: "Validade", cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs", children: formatDate(row.original.expiresAt) ?? /* @__PURE__ */ jsx(Dash, {}) }) },
6972
+ { accessorKey: "activatedAt", header: "Ativado", cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs", children: formatDate(row.original.activatedAt) ?? /* @__PURE__ */ jsx(Dash, {}) }) },
6973
+ {
6974
+ id: "actions",
6975
+ header: "",
6976
+ cell: ({ row }) => row.original.state === "issued" ? /* @__PURE__ */ jsx(PermissionGate, { feature: "financial", action: "edit", children: /* @__PURE__ */ jsxs(Tooltip, { children: [
6977
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
6978
+ Button,
6979
+ {
6980
+ type: "button",
6981
+ variant: "ghost",
6982
+ size: "icon",
6983
+ "aria-label": `Ativar o vale ${formatCode(row.original.code)}`,
6984
+ disabled: activating === row.original.id,
6985
+ onClick: () => void (async () => {
6986
+ setActivating(row.original.id);
6987
+ setError(null);
6988
+ try {
6989
+ await provider.activateGiftCard?.(row.original.id);
6990
+ await load();
6991
+ } catch (e) {
6992
+ setError(e?.message ?? String(e));
6993
+ } finally {
6994
+ setActivating(null);
6995
+ }
6996
+ })(),
6997
+ children: activating === row.original.id ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(BadgeCheck, { className: "h-3.5 w-3.5" })
6998
+ }
6999
+ ) }),
7000
+ /* @__PURE__ */ jsx(TooltipContent, { className: "text-xs", children: "Ativar na venda \u2014 o saldo passa a valer o valor de face" })
7001
+ ] }) }) : null
7002
+ }
7003
+ ], [activating, load, money3, provider]);
6382
7004
  const outstanding = (cards ?? []).filter((c) => c.state === "active").reduce((s, c) => s + (c.balance ?? 0), 0);
6383
7005
  const faceIssued = (cards ?? []).reduce((s, c) => s + c.faceValue, 0);
6384
7006
  if (enabled === false) {
@@ -6400,11 +7022,7 @@ function GiftCardsView() {
6400
7022
  {
6401
7023
  title: "Gift Cards",
6402
7024
  subtitle: "Vales-presente emitidos em lote",
6403
- icon: "Gift",
6404
- actions: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(PermissionGate, { feature: "financial", action: "create", children: /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => setIssuing(true), disabled: enabled === null, children: [
6405
- /* @__PURE__ */ jsx(Gift, { className: "mr-1.5 h-4 w-4" }),
6406
- " Emitir gift cards"
6407
- ] }) }) })
7025
+ icon: "Gift"
6408
7026
  }
6409
7027
  ),
6410
7028
  /* @__PURE__ */ jsxs("p", { className: "mt-3 max-w-prose text-xs text-muted-foreground", children: [
@@ -6436,141 +7054,59 @@ function GiftCardsView() {
6436
7054
  money3(issued.total),
6437
7055
  " em valor de face. Eles nascem sem saldo: ative cada um na venda."
6438
7056
  ] }),
6439
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2 rounded-2xl border bg-card px-3 py-2", children: [
6440
- /* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: /* @__PURE__ */ jsx(
6441
- Input,
6442
- {
6443
- value: search,
6444
- placeholder: "Buscar c\xF3digo",
6445
- className: "border-0 bg-transparent px-1 text-sm shadow-none focus-visible:ring-0",
6446
- onChange: (e) => setSearch(e.target.value)
6447
- }
6448
- ) }),
6449
- /* @__PURE__ */ jsx(
6450
- ListFilterBar,
6451
- {
6452
- className: "ml-auto",
6453
- filters: [{
6454
- id: "state",
6455
- label: "Status",
6456
- allLabel: "Todos os status",
6457
- options: Object.keys(STATE_META).map((st) => ({ value: st, label: STATE_META[st].label })),
6458
- value: state || void 0,
6459
- onChange: (next) => setState(next ?? "")
6460
- }],
6461
- extraFields: [
6462
- {
6463
- id: "value",
6464
- label: "Valor",
6465
- activeCount: (minValue ? 1 : 0) + (maxValue ? 1 : 0),
6466
- render: () => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 p-1", children: [
6467
- /* @__PURE__ */ jsx(
6468
- Input,
6469
- {
6470
- type: "number",
6471
- value: minValue,
6472
- placeholder: "de",
6473
- className: "h-8 w-24 text-xs",
6474
- onChange: (e) => setMinValue(e.target.value)
6475
- }
6476
- ),
6477
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2013" }),
6478
- /* @__PURE__ */ jsx(
6479
- Input,
6480
- {
6481
- type: "number",
6482
- value: maxValue,
6483
- placeholder: "at\xE9",
6484
- className: "h-8 w-24 text-xs",
6485
- onChange: (e) => setMaxValue(e.target.value)
6486
- }
6487
- )
6488
- ] })
6489
- },
6490
- {
6491
- id: "issued",
6492
- label: "Emiss\xE3o",
6493
- activeCount: (from ? 1 : 0) + (to ? 1 : 0),
6494
- // DatePicker `chip`, não `<input type=date>`: é o mesmo campo
6495
- // de data de todo filtro do produto, e o nativo muda de cara em
6496
- // cada navegador.
6497
- render: () => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 p-1", children: [
6498
- /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", value: from, onChange: setFrom }),
6499
- /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", value: to, onChange: setTo })
6500
- ] })
6501
- }
6502
- ],
6503
- activeCount: (state ? 1 : 0) + (minValue || maxValue ? 1 : 0) + (from || to ? 1 : 0),
6504
- onClear: () => {
6505
- setState("");
6506
- setMinValue("");
6507
- setMaxValue("");
6508
- setFrom("");
6509
- setTo("");
6510
- }
6511
- }
6512
- )
6513
- ] }),
6514
7057
  error && /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-card border border-destructive/30 bg-destructive/5 px-4 py-3 text-xs text-destructive", children: error }),
6515
- /* @__PURE__ */ jsx("div", { className: "mt-3 overflow-x-auto rounded-card border bg-card", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
6516
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b text-[10px] uppercase text-muted-foreground", children: [
6517
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: "C\xF3digo" }),
6518
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-right font-medium", children: "Valor" }),
6519
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-right font-medium", children: "Saldo" }),
6520
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: "Status" }),
6521
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: "Emitido" }),
6522
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: "Validade" }),
6523
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: "Ativado" }),
6524
- /* @__PURE__ */ jsx("th", { className: "px-2 py-2" })
6525
- ] }) }),
6526
- /* @__PURE__ */ jsx("tbody", { children: cards === null ? [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0", children: [
6527
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-36" }) }),
6528
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-4 w-20" }) }),
6529
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-4 w-20" }) }),
6530
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-16 rounded-full" }) }),
6531
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" }) }),
6532
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" }) }),
6533
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" }) }),
6534
- /* @__PURE__ */ jsx("td", { className: "px-2 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-7 rounded-button" }) })
6535
- ] }, i)) : cards.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: 8, className: "px-4 py-10 text-center", children: [
6536
- /* @__PURE__ */ jsx(Gift, { className: "mx-auto h-8 w-8 text-muted-foreground" }),
6537
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium text-foreground", children: "Nenhum vale-presente" }),
6538
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Emita um lote: quantidade e valor unit\xE1rio, e o sistema gera os c\xF3digos." })
6539
- ] }) }) : cards.map((c) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 hover:bg-muted/40", children: [
6540
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-mono text-xs tabular-nums", children: formatCode(c.code) }),
6541
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums", children: money3(c.faceValue) }),
6542
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums", children: c.balance === null ? /* @__PURE__ */ jsx(Dash, {}) : money3(c.balance) }),
6543
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium ${STATE_META[c.state].className}`, children: STATE_META[c.state].label }) }),
6544
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-xs", children: formatDate(c.issuedAt) ?? /* @__PURE__ */ jsx(Dash, {}) }),
6545
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-xs", children: formatDate(c.expiresAt) ?? /* @__PURE__ */ jsx(Dash, {}) }),
6546
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-xs", children: formatDate(c.activatedAt) ?? /* @__PURE__ */ jsx(Dash, {}) }),
6547
- /* @__PURE__ */ jsx("td", { className: "px-2 py-2 text-right", children: c.state === "issued" && /* @__PURE__ */ jsx(PermissionGate, { feature: "financial", action: "edit", children: /* @__PURE__ */ jsxs(Tooltip, { children: [
6548
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
6549
- "button",
6550
- {
6551
- type: "button",
6552
- "aria-label": `Ativar o vale ${formatCode(c.code)}`,
6553
- disabled: activating === c.id,
6554
- onClick: () => void (async () => {
6555
- setActivating(c.id);
6556
- setError(null);
6557
- try {
6558
- await provider.activateGiftCard?.(c.id);
6559
- await load();
6560
- } catch (e) {
6561
- setError(e?.message ?? String(e));
6562
- } finally {
6563
- setActivating(null);
6564
- }
6565
- })(),
6566
- className: "fz-tap inline-flex h-7 w-7 items-center justify-center rounded-button border bg-card text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50",
6567
- children: activating === c.id ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(BadgeCheck, { className: "h-3.5 w-3.5" })
6568
- }
6569
- ) }),
6570
- /* @__PURE__ */ jsx(TooltipContent, { className: "text-xs", children: "Ativar na venda \u2014 o saldo passa a valer o valor de face" })
6571
- ] }) }) })
6572
- ] }, c.id)) })
6573
- ] }) }),
7058
+ /* @__PURE__ */ jsx(
7059
+ ListView,
7060
+ {
7061
+ className: "mt-3",
7062
+ columns,
7063
+ data: cards ?? [],
7064
+ loading: cards === null,
7065
+ search,
7066
+ onSearchChange: setSearch,
7067
+ searchPlaceholder: "Buscar c\xF3digo",
7068
+ filterDefs: [{
7069
+ id: "state",
7070
+ label: "Status",
7071
+ allLabel: "Todos os status",
7072
+ options: Object.keys(STATE_META).map((entry) => ({ value: entry, label: STATE_META[entry].label })),
7073
+ value: state || void 0,
7074
+ onChange: (next) => setState(next ?? "")
7075
+ }],
7076
+ advancedFilters: /* @__PURE__ */ jsxs("div", { className: "grid gap-3 p-2 sm:grid-cols-2", children: [
7077
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
7078
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: "Valor" }),
7079
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
7080
+ /* @__PURE__ */ jsx(Input, { type: "number", value: minValue, placeholder: "de", className: "h-8 w-24 text-xs", onChange: (e) => setMinValue(e.target.value) }),
7081
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2013" }),
7082
+ /* @__PURE__ */ jsx(Input, { type: "number", value: maxValue, placeholder: "at\xE9", className: "h-8 w-24 text-xs", onChange: (e) => setMaxValue(e.target.value) })
7083
+ ] })
7084
+ ] }),
7085
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
7086
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: "Emiss\xE3o" }),
7087
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
7088
+ /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", value: from, onChange: setFrom }),
7089
+ /* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", value: to, onChange: setTo })
7090
+ ] })
7091
+ ] })
7092
+ ] }),
7093
+ activeFilterCount: (state ? 1 : 0) + (minValue || maxValue ? 1 : 0) + (from || to ? 1 : 0),
7094
+ onClearFilters: () => {
7095
+ setState("");
7096
+ setMinValue("");
7097
+ setMaxValue("");
7098
+ setFrom("");
7099
+ setTo("");
7100
+ },
7101
+ clearFiltersLabel: "Limpar",
7102
+ newLabel: "Emitir gift cards",
7103
+ onNew: can?.("financial", "create") && enabled !== null ? () => setIssuing(true) : void 0,
7104
+ emptyIcon: Gift,
7105
+ emptyMessage: "Nenhum vale-presente",
7106
+ emptyActionLabel: can?.("financial", "create") ? "Emitir primeiro lote" : void 0,
7107
+ onEmptyAction: can?.("financial", "create") ? () => setIssuing(true) : void 0
7108
+ }
7109
+ ),
6574
7110
  issuing && /* @__PURE__ */ jsx(
6575
7111
  IssueDialog,
6576
7112
  {
@@ -6591,12 +7127,12 @@ function IssueDialog({
6591
7127
  onIssued
6592
7128
  }) {
6593
7129
  const provider = useFinancialProvider();
6594
- const [quantity, setQuantity] = React15__default.useState("10");
6595
- const [amount, setAmount] = React15__default.useState("100");
6596
- const [expiresAt, setExpiresAt] = React15__default.useState("");
6597
- const [note, setNote] = React15__default.useState("");
6598
- const [saving, setSaving] = React15__default.useState(false);
6599
- const [error, setError] = React15__default.useState(null);
7130
+ const [quantity, setQuantity] = React20__default.useState("10");
7131
+ const [amount, setAmount] = React20__default.useState("100");
7132
+ const [expiresAt, setExpiresAt] = React20__default.useState("");
7133
+ const [note, setNote] = React20__default.useState("");
7134
+ const [saving, setSaving] = React20__default.useState(false);
7135
+ const [error, setError] = React20__default.useState(null);
6600
7136
  const q = Math.trunc(Number(quantity)) || 0;
6601
7137
  const a = Number(amount) || 0;
6602
7138
  const total = q * a;
@@ -6622,12 +7158,12 @@ function IssueDialog({
6622
7158
  }
6623
7159
  return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
6624
7160
  if (!o) onClose();
6625
- }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "md", noPadding: true, className: "gap-0", children: [
6626
- /* @__PURE__ */ jsxs(ModalHeader, { className: "border-b px-5 py-4", children: [
7161
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "md", children: [
7162
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
6627
7163
  /* @__PURE__ */ jsx(ModalTitle, { className: "text-base", children: "Emitir gift cards" }),
6628
7164
  /* @__PURE__ */ jsx(ModalDescription, { className: "text-xs", children: "Um lote \xE9 uniforme: o mesmo valor para todos os cart\xF5es." })
6629
7165
  ] }),
6630
- /* @__PURE__ */ jsx(ModalBody, { className: "fz-container px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
7166
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
6631
7167
  /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
6632
7168
  /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
6633
7169
  /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium uppercase text-muted-foreground", htmlFor: "gc-qty", children: "Quantidade" }),
@@ -6662,16 +7198,7 @@ function IssueDialog({
6662
7198
  ] }),
6663
7199
  /* @__PURE__ */ jsxs("div", { children: [
6664
7200
  /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium uppercase text-muted-foreground", htmlFor: "gc-exp", children: "Data de expira\xE7\xE3o (opcional)" }),
6665
- /* @__PURE__ */ jsx(
6666
- Input,
6667
- {
6668
- id: "gc-exp",
6669
- type: "date",
6670
- value: expiresAt,
6671
- className: "mt-0.5",
6672
- onChange: (e) => setExpiresAt(e.target.value)
6673
- }
6674
- ),
7201
+ /* @__PURE__ */ jsx(DatePicker, { value: expiresAt, onChange: setExpiresAt, className: "mt-0.5 w-full" }),
6675
7202
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "Deixe em branco se n\xE3o expira." })
6676
7203
  ] }),
6677
7204
  /* @__PURE__ */ jsxs("div", { children: [
@@ -6693,7 +7220,7 @@ function IssueDialog({
6693
7220
  invalid && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: invalid }),
6694
7221
  error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
6695
7222
  ] }) }),
6696
- /* @__PURE__ */ jsxs(ModalFooter, { className: "border-t px-5 py-3", children: [
7223
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
6697
7224
  /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onClose, children: "Cancelar" }),
6698
7225
  /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void issue(), disabled: saving || !!invalid, children: [
6699
7226
  saving ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }) : null,
@@ -6764,11 +7291,11 @@ function ReconciliationView() {
6764
7291
  ] });
6765
7292
  }
6766
7293
  const selected = lines.find((l) => l.id === selectedId) ?? null;
6767
- const RefreshIcon = React15__default.useMemo(
7294
+ const RefreshIcon = React20__default.useMemo(
6768
7295
  () => ({ className }) => /* @__PURE__ */ jsx(RefreshCw, { className: `${className ?? ""} ${loading ? "animate-spin" : ""}` }),
6769
7296
  [loading]
6770
7297
  );
6771
- const headerActions = React15__default.useMemo(() => [{
7298
+ const headerActions = React20__default.useMemo(() => [{
6772
7299
  id: "refresh",
6773
7300
  label: t2("financial.reconciliation.refresh"),
6774
7301
  icon: RefreshIcon,
@@ -6947,7 +7474,7 @@ function NotasFiscaisView() {
6947
7474
  /* @__PURE__ */ jsx("p", { className: "mt-4 text-sm text-muted-foreground", children: "Este provedor financeiro ainda n\xE3o exp\xF5e notas fiscais." })
6948
7475
  ] });
6949
7476
  }
6950
- const headerActions = React15__default.useMemo(() => canCreateManually ? [{
7477
+ const headerActions = React20__default.useMemo(() => canCreateManually ? [{
6951
7478
  id: "new-nota",
6952
7479
  label: "Nova nota",
6953
7480
  icon: Plus,
@@ -7046,6 +7573,522 @@ function CreateForm({ onCancel, onCreate }) {
7046
7573
  ] })
7047
7574
  ] });
7048
7575
  }
7576
+ var FREQUENCIES = [
7577
+ { value: "weekly", label: "Semanal" },
7578
+ { value: "monthly", label: "Mensal" },
7579
+ { value: "quarterly", label: "Trimestral" },
7580
+ { value: "yearly", label: "Anual" }
7581
+ ];
7582
+ function today3() {
7583
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7584
+ }
7585
+ function dateLabel(value) {
7586
+ if (!value) return "Sem t\xE9rmino";
7587
+ const [year, month, day] = value.split("-");
7588
+ return year && month && day ? `${day}/${month}/${year}` : value;
7589
+ }
7590
+ function newRecurringItem() {
7591
+ return { _id: crypto.randomUUID(), itemKind: "other", description: "", quantity: 1, unitPrice: 0 };
7592
+ }
7593
+ function RecurringBillsView({
7594
+ direction,
7595
+ intent,
7596
+ onNavigate
7597
+ }) {
7598
+ const prefix = direction === "payable" ? "payables" : "receivables";
7599
+ if (intent.mode === "new" || intent.mode === "edit") {
7600
+ return /* @__PURE__ */ jsx(
7601
+ RecurringBillFormView,
7602
+ {
7603
+ direction,
7604
+ valueId: intent.mode === "edit" ? intent.editId : void 0,
7605
+ onBack: () => onNavigate(`${prefix}-recurring`)
7606
+ }
7607
+ );
7608
+ }
7609
+ return /* @__PURE__ */ jsx(RecurringBillsList, { direction, onNavigate });
7610
+ }
7611
+ function RecurringBillsList({ direction, onNavigate }) {
7612
+ const provider = useFinancialProvider();
7613
+ const { currency } = useFinancialConfig();
7614
+ const can = usePermissionOptional();
7615
+ const [rows, setRows] = React20__default.useState(null);
7616
+ const [search, setSearch] = React20__default.useState("");
7617
+ const [status, setStatus] = React20__default.useState("all");
7618
+ const [changing, setChanging] = React20__default.useState(null);
7619
+ const isPayable = direction === "payable";
7620
+ const prefix = isPayable ? "payables" : "receivables";
7621
+ const load = React20__default.useCallback(async () => {
7622
+ setRows(null);
7623
+ try {
7624
+ setRows(await provider.getRecurringBills?.(direction) ?? []);
7625
+ } catch (error) {
7626
+ setRows([]);
7627
+ toast.error(error?.message ?? "N\xE3o foi poss\xEDvel carregar as recorr\xEAncias.");
7628
+ }
7629
+ }, [provider, direction]);
7630
+ React20__default.useEffect(() => {
7631
+ void load();
7632
+ }, [load]);
7633
+ const filtered = React20__default.useMemo(() => {
7634
+ const term = search.trim().toLowerCase();
7635
+ return (rows ?? []).filter((row) => {
7636
+ if (status === "active" && !row.isActive) return false;
7637
+ if (status === "paused" && row.isActive) return false;
7638
+ if (!term) return true;
7639
+ return [row.description, row.counterpartyName, row.notes].some((field) => field?.toLowerCase().includes(term));
7640
+ });
7641
+ }, [rows, search, status]);
7642
+ async function toggle(row) {
7643
+ setChanging(row.id);
7644
+ try {
7645
+ await provider.updateRecurringBill?.(row.id, { isActive: !row.isActive });
7646
+ await load();
7647
+ toast.success(row.isActive ? "Recorr\xEAncia pausada." : "Recorr\xEAncia reativada.");
7648
+ } catch (error) {
7649
+ toast.error(error?.message ?? "N\xE3o foi poss\xEDvel alterar a recorr\xEAncia.");
7650
+ } finally {
7651
+ setChanging(null);
7652
+ }
7653
+ }
7654
+ const columns = React20__default.useMemo(() => [
7655
+ {
7656
+ accessorKey: "description",
7657
+ header: "Descri\xE7\xE3o",
7658
+ cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7659
+ /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: row.original.description }),
7660
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: row.original.counterpartyName || "Sem contato vinculado" })
7661
+ ] })
7662
+ },
7663
+ { accessorKey: "amount", header: "Valor", cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formatCurrency(row.original.amount, currency) }) },
7664
+ { accessorKey: "frequency", header: "Periodicidade", cell: ({ row }) => FREQUENCIES.find((item) => item.value === row.original.frequency)?.label ?? row.original.frequency },
7665
+ { accessorKey: "dueDay", header: "Vencimento", cell: ({ row }) => `Dia ${row.original.dueDay}` },
7666
+ {
7667
+ id: "validity",
7668
+ header: "Vig\xEAncia",
7669
+ cell: ({ row }) => /* @__PURE__ */ jsxs("span", { className: "text-xs", children: [
7670
+ dateLabel(row.original.startsOn),
7671
+ " \u2013 ",
7672
+ dateLabel(row.original.endsOn)
7673
+ ] })
7674
+ },
7675
+ {
7676
+ accessorKey: "isActive",
7677
+ header: "Status",
7678
+ cell: ({ row }) => /* @__PURE__ */ jsx(Badge, { variant: row.original.isActive ? "success" : "secondary", children: row.original.isActive ? "Ativa" : "Pausada" })
7679
+ },
7680
+ {
7681
+ id: "actions",
7682
+ header: "",
7683
+ cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", onClick: (event) => event.stopPropagation(), children: [
7684
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", title: "Editar", onClick: () => onNavigate(`${prefix}-recurring-edit:${row.original.id}`), children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }) }),
7685
+ /* @__PURE__ */ jsx(
7686
+ Button,
7687
+ {
7688
+ variant: "ghost",
7689
+ size: "icon",
7690
+ title: row.original.isActive ? "Pausar" : "Reativar",
7691
+ disabled: changing === row.original.id,
7692
+ onClick: () => void toggle(row.original),
7693
+ children: changing === row.original.id ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : row.original.isActive ? /* @__PURE__ */ jsx(Pause, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5" })
7694
+ }
7695
+ )
7696
+ ] })
7697
+ }
7698
+ ], [currency, changing, onNavigate, prefix]);
7699
+ const newRoute = `${prefix}-recurring-new`;
7700
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
7701
+ /* @__PURE__ */ jsx(SubpageHeader, { title: isPayable ? "Despesas recorrentes" : "Receitas recorrentes" }),
7702
+ /* @__PURE__ */ jsx(
7703
+ ListView,
7704
+ {
7705
+ columns,
7706
+ data: filtered,
7707
+ loading: rows === null,
7708
+ search,
7709
+ onSearchChange: setSearch,
7710
+ searchPlaceholder: "Buscar descri\xE7\xE3o ou contato",
7711
+ filterDefs: [{
7712
+ id: "status",
7713
+ label: "Status",
7714
+ allLabel: "Todos os status",
7715
+ options: [{ value: "active", label: "Ativas" }, { value: "paused", label: "Pausadas" }],
7716
+ value: status === "all" ? void 0 : status,
7717
+ onChange: (next) => setStatus(next ?? "all")
7718
+ }],
7719
+ activeFilterCount: status === "all" ? 0 : 1,
7720
+ onClearFilters: () => setStatus("all"),
7721
+ newLabel: isPayable ? "Nova despesa recorrente" : "Nova receita recorrente",
7722
+ onNew: can("financial", "create") ? () => onNavigate(newRoute) : void 0,
7723
+ onRowClick: (row) => onNavigate(`${prefix}-recurring-edit:${row.id}`),
7724
+ emptyIcon: CalendarClock,
7725
+ emptyMessage: isPayable ? "Nenhuma despesa recorrente" : "Nenhuma receita recorrente",
7726
+ emptyActionLabel: can("financial", "create") ? "Criar primeira recorr\xEAncia" : void 0,
7727
+ onEmptyAction: can("financial", "create") ? () => onNavigate(newRoute) : void 0
7728
+ }
7729
+ )
7730
+ ] });
7731
+ }
7732
+ function RecurringBillFormView({ direction, valueId, onBack }) {
7733
+ const provider = useFinancialProvider();
7734
+ const { locations, currency, itemTypes: configuredItemTypes, entityLookups } = useFinancialConfig();
7735
+ const isPayable = direction === "payable";
7736
+ const [value, setValue] = React20__default.useState(null);
7737
+ const [loaded, setLoaded] = React20__default.useState(!valueId);
7738
+ const [contact, setContact] = React20__default.useState(null);
7739
+ const [counterpartyAccounting, setCounterpartyAccounting] = React20__default.useState({
7740
+ allowedAccountIds: [],
7741
+ limitAccounts: false,
7742
+ addItemsAutomatically: false
7743
+ });
7744
+ const accountingPolicyRequest = React20__default.useRef(0);
7745
+ const counterpartyLookup = React20__default.useMemo(
7746
+ () => createFinancialCounterpartyLookup(isPayable ? "debit" : "credit"),
7747
+ [isPayable]
7748
+ );
7749
+ const contactCreate = useLookupCreate(counterpartyLookup, (created) => {
7750
+ setContact({ id: created.id, name: created.label });
7751
+ });
7752
+ const initialItem = React20__default.useRef(newRecurringItem());
7753
+ const [items, setItems] = React20__default.useState([initialItem.current]);
7754
+ const [expandedItemId, setExpandedItemId] = React20__default.useState(initialItem.current._id);
7755
+ const [addingItem, setAddingItem] = React20__default.useState(false);
7756
+ const [frequency, setFrequency] = React20__default.useState("monthly");
7757
+ const [dueDay, setDueDay] = React20__default.useState("1");
7758
+ const [startsOn, setStartsOn] = React20__default.useState(today3());
7759
+ const [endsOn, setEndsOn] = React20__default.useState("");
7760
+ const [unitId, setUnitId] = React20__default.useState(locations[0]?.id ?? "");
7761
+ const [availableLocations, setAvailableLocations] = React20__default.useState(locations);
7762
+ const [notes, setNotes] = React20__default.useState("");
7763
+ const [accounts, setAccounts] = React20__default.useState([]);
7764
+ const [costCenters, setCostCenters] = React20__default.useState([]);
7765
+ const [saving, setSaving] = React20__default.useState(false);
7766
+ const [ending, setEnding] = React20__default.useState(false);
7767
+ const [error, setError] = React20__default.useState(null);
7768
+ React20__default.useEffect(() => {
7769
+ if (locations.length > 0) {
7770
+ setAvailableLocations(locations);
7771
+ return;
7772
+ }
7773
+ let cancelled = false;
7774
+ const lookup = createArchetypeLookup({ archetype: "location", entityDef: null });
7775
+ void lookup.list?.().then((rows) => {
7776
+ if (!cancelled) setAvailableLocations(rows.map((row) => ({ id: row.id, name: row.label })));
7777
+ }).catch(() => {
7778
+ });
7779
+ return () => {
7780
+ cancelled = true;
7781
+ };
7782
+ }, [locations]);
7783
+ React20__default.useEffect(() => {
7784
+ let cancelled = false;
7785
+ void Promise.all([
7786
+ provider.getChartOfAccounts(),
7787
+ provider.getCostCenters(),
7788
+ valueId ? provider.getRecurringBills?.(direction) ?? Promise.resolve([]) : Promise.resolve([])
7789
+ ]).then(([nextAccounts, nextCenters, recurring]) => {
7790
+ if (cancelled) return;
7791
+ setAccounts(postableAccounts(nextAccounts));
7792
+ setCostCenters(nextCenters);
7793
+ if (valueId) {
7794
+ const nextValue = recurring.find((item) => item.id === valueId) ?? null;
7795
+ if (!nextValue) {
7796
+ toast.error("Recorr\xEAncia n\xE3o encontrada.");
7797
+ onBack();
7798
+ return;
7799
+ }
7800
+ setValue(nextValue);
7801
+ setContact(nextValue.counterpartyPersonId ? { id: nextValue.counterpartyPersonId, name: nextValue.counterpartyName ?? "" } : null);
7802
+ const loadedItems = nextValue.items.length > 0 ? nextValue.items.map((item) => ({ ...item, _id: crypto.randomUUID() })) : [{
7803
+ ...newRecurringItem(),
7804
+ description: nextValue.description,
7805
+ unitPrice: nextValue.amount,
7806
+ accountId: nextValue.chartAccountId ?? void 0,
7807
+ costCenterId: nextValue.costCenterId ?? void 0
7808
+ }];
7809
+ setItems(loadedItems);
7810
+ setExpandedItemId(loadedItems[0]?._id ?? null);
7811
+ setFrequency(nextValue.frequency);
7812
+ setDueDay(String(nextValue.dueDay));
7813
+ setStartsOn(nextValue.startsOn);
7814
+ setEndsOn(nextValue.endsOn ?? "");
7815
+ setUnitId(nextValue.unitId ?? "");
7816
+ setNotes(nextValue.notes ?? "");
7817
+ }
7818
+ setLoaded(true);
7819
+ }).catch((caught) => {
7820
+ if (cancelled) return;
7821
+ toast.error(caught?.message ?? "N\xE3o foi poss\xEDvel abrir a recorr\xEAncia.");
7822
+ if (valueId) onBack();
7823
+ else setLoaded(true);
7824
+ });
7825
+ return () => {
7826
+ cancelled = true;
7827
+ };
7828
+ }, [provider, direction, valueId]);
7829
+ const itemTypes = React20__default.useMemo(() => availableInvoiceItemTypes(
7830
+ configuredItemTypes,
7831
+ Object.keys(entityLookups),
7832
+ configuredItemTypes.find((item) => item.value === "other")?.label ?? "Outro"
7833
+ ), [configuredItemTypes, entityLookups]);
7834
+ const selectableAccounts = React20__default.useMemo(() => {
7835
+ const postable = postableAccounts(accounts);
7836
+ if (!counterpartyAccounting.limitAccounts) return postable;
7837
+ const allowed = new Set(counterpartyAccounting.allowedAccountIds);
7838
+ return postable.filter((account) => allowed.has(account.id));
7839
+ }, [accounts, counterpartyAccounting]);
7840
+ const hasItemLookups = itemTypes.some((item) => entityLookups[item.value]);
7841
+ const amount = React20__default.useMemo(() => items.reduce(
7842
+ (sum, item) => sum + item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
7843
+ 0
7844
+ ), [items]);
7845
+ const description = items.map((item) => item.description.trim()).filter(Boolean).join(", ");
7846
+ const numericDueDay = Math.trunc(Number(dueDay));
7847
+ const invalid = items.length === 0 ? "Adicione pelo menos um item." : items.some((item) => !item.description.trim()) ? "Informe a descri\xE7\xE3o de todos os itens." : !contact?.id ? `Selecione ${isPayable ? "quem recebe" : "quem paga"}.` : !(amount > 0) ? "Informe um valor maior que zero." : numericDueDay < 1 || numericDueDay > 31 ? "O dia do vencimento vai de 1 a 31." : !startsOn ? "Informe o in\xEDcio da recorr\xEAncia." : endsOn && endsOn < startsOn ? "O t\xE9rmino n\xE3o pode vir antes do in\xEDcio." : null;
7848
+ const fields = { items, contactId: contact?.id ?? "", frequency, dueDay, startsOn, endsOn, unitId, notes };
7849
+ const snapshot = React20__default.useRef(null);
7850
+ React20__default.useEffect(() => {
7851
+ if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
7852
+ }, [loaded]);
7853
+ const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
7854
+ async function save() {
7855
+ if (invalid || !contact?.id || !provider.saveRecurringBill) return;
7856
+ setSaving(true);
7857
+ setError(null);
7858
+ try {
7859
+ const input = {
7860
+ id: value?.id,
7861
+ direction,
7862
+ description: description.trim(),
7863
+ counterpartyPersonId: contact.id,
7864
+ amount,
7865
+ items: items.map(({ _id: _ignored, ...item }) => item),
7866
+ frequency,
7867
+ dueDay: numericDueDay,
7868
+ startsOn,
7869
+ endsOn: endsOn || null,
7870
+ unitId: unitId || null,
7871
+ chartAccountId: null,
7872
+ costCenterId: null,
7873
+ notes: notes.trim() || null,
7874
+ isActive: value?.isActive ?? true
7875
+ };
7876
+ await provider.saveRecurringBill(input);
7877
+ toast.success(value ? "Recorr\xEAncia atualizada." : "Recorr\xEAncia criada.");
7878
+ onBack();
7879
+ } catch (caught) {
7880
+ const message = caught?.message ?? "N\xE3o foi poss\xEDvel salvar a recorr\xEAncia.";
7881
+ setError(message);
7882
+ toast.error(message);
7883
+ } finally {
7884
+ setSaving(false);
7885
+ }
7886
+ }
7887
+ async function endToday() {
7888
+ if (!value || !provider.updateRecurringBill) return;
7889
+ setSaving(true);
7890
+ setError(null);
7891
+ try {
7892
+ await provider.updateRecurringBill(value.id, { isActive: false, endsOn: today3() });
7893
+ toast.success("Recorr\xEAncia encerrada hoje.");
7894
+ onBack();
7895
+ } catch (caught) {
7896
+ const message = caught?.message ?? "N\xE3o foi poss\xEDvel encerrar a recorr\xEAncia.";
7897
+ setError(message);
7898
+ toast.error(message);
7899
+ setEnding(false);
7900
+ } finally {
7901
+ setSaving(false);
7902
+ }
7903
+ }
7904
+ function addItem(kind = "other") {
7905
+ const item = {
7906
+ ...newRecurringItem(),
7907
+ itemKind: kind,
7908
+ accountId: counterpartyAccounting.defaultAccountId,
7909
+ costCenterId: counterpartyAccounting.defaultCostCenterId
7910
+ };
7911
+ setItems((current) => [...current, item]);
7912
+ setExpandedItemId(item._id);
7913
+ setAddingItem(false);
7914
+ }
7915
+ function updateItem(id, patch) {
7916
+ setItems((current) => current.map((item) => item._id === id ? { ...item, ...patch } : item));
7917
+ }
7918
+ useSaveBar({
7919
+ dirty,
7920
+ alwaysVisible: loaded,
7921
+ saving,
7922
+ onSave: () => {
7923
+ void save();
7924
+ },
7925
+ onDiscard: onBack,
7926
+ saveLabel: "Salvar recorr\xEAncia"
7927
+ });
7928
+ if (!loaded) return /* @__PURE__ */ jsx(OrderFormSkeleton, { parentLabel: isPayable ? "Contas a Pagar" : "Contas a Receber", onBack });
7929
+ const title = `${value ? "Editar" : "Nova"} ${isPayable ? "despesa recorrente" : "receita recorrente"}`;
7930
+ const actions = value?.isActive && !ending ? /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "text-destructive", onClick: () => setEnding(true), children: "Encerrar recorr\xEAncia" }) : void 0;
7931
+ return /* @__PURE__ */ jsxs(
7932
+ OrderFormView,
7933
+ {
7934
+ title,
7935
+ parentLabel: isPayable ? "Contas a Pagar" : "Contas a Receber",
7936
+ onBack,
7937
+ actions,
7938
+ banner: ending ? /* @__PURE__ */ jsxs("div", { className: "rounded-card border border-warning/40 bg-warning/5 p-3 text-sm", children: [
7939
+ /* @__PURE__ */ jsx("p", { children: "Encerrar hoje pausa a regra e preserva todos os t\xEDtulos j\xE1 gerados." }),
7940
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex gap-2", children: [
7941
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "destructive", onClick: () => void endToday(), disabled: saving, children: "Confirmar encerramento" }),
7942
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setEnding(false), disabled: saving, children: "Cancelar" })
7943
+ ] })
7944
+ ] }) : void 0,
7945
+ sideSections: /* @__PURE__ */ jsxs(Fragment, { children: [
7946
+ /* @__PURE__ */ jsx(OrderFormBlock, { title: "Recorr\xEAncia", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
7947
+ /* @__PURE__ */ jsx(Field2, { label: "Unidade", children: /* @__PURE__ */ jsxs("select", { value: unitId, onChange: (event) => setUnitId(event.target.value), className: "h-9 w-full rounded-input border border-input bg-card px-3 text-sm", children: [
7948
+ /* @__PURE__ */ jsx("option", { value: "", children: "Todas / sem unidade" }),
7949
+ availableLocations.map((location) => /* @__PURE__ */ jsx("option", { value: location.id, children: location.name }, location.id))
7950
+ ] }) }),
7951
+ /* @__PURE__ */ jsx(Field2, { label: "Periodicidade *", children: /* @__PURE__ */ jsx("select", { value: frequency, onChange: (event) => setFrequency(event.target.value), className: "h-9 w-full rounded-input border border-input bg-card px-3 text-sm", children: FREQUENCIES.map((item) => /* @__PURE__ */ jsx("option", { value: item.value, children: item.label }, item.value)) }) }),
7952
+ /* @__PURE__ */ jsx(Field2, { label: "Dia do vencimento *", children: /* @__PURE__ */ jsx(Input, { type: "number", min: 1, max: 31, value: dueDay, onChange: (event) => setDueDay(event.target.value) }) }),
7953
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
7954
+ /* @__PURE__ */ jsx(Field2, { label: "In\xEDcio *", children: /* @__PURE__ */ jsx(DatePicker, { value: startsOn, onChange: setStartsOn, className: "w-full" }) }),
7955
+ /* @__PURE__ */ jsx(Field2, { label: "T\xE9rmino", children: /* @__PURE__ */ jsx(DatePicker, { value: endsOn, onChange: setEndsOn, className: "w-full" }) })
7956
+ ] })
7957
+ ] }) }),
7958
+ /* @__PURE__ */ jsx(OrderFormBlock, { title: "Observa\xE7\xF5es", children: /* @__PURE__ */ jsx(
7959
+ "textarea",
7960
+ {
7961
+ value: notes,
7962
+ onChange: (event) => setNotes(event.target.value),
7963
+ placeholder: "Informa\xE7\xF5es adicionais",
7964
+ rows: 2,
7965
+ className: "w-full resize-none rounded-input border border-input bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
7966
+ }
7967
+ ) })
7968
+ ] }),
7969
+ children: [
7970
+ /* @__PURE__ */ jsx(OrderFormBlock, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
7971
+ /* @__PURE__ */ jsx(
7972
+ FinancialCounterpartySearchSelect,
7973
+ {
7974
+ lookup: counterpartyLookup,
7975
+ value: contact?.id,
7976
+ displayValue: contact?.name,
7977
+ onChange: (id, option) => {
7978
+ setContact(id && option ? { id, name: option.label } : null);
7979
+ const request = ++accountingPolicyRequest.current;
7980
+ if (!id) {
7981
+ setCounterpartyAccounting({ allowedAccountIds: [], limitAccounts: false, addItemsAutomatically: false });
7982
+ return;
7983
+ }
7984
+ const policy = counterpartyAccountingPolicy(option?.data);
7985
+ setCounterpartyAccounting({
7986
+ allowedAccountIds: [],
7987
+ limitAccounts: policy.limitAccounts,
7988
+ addItemsAutomatically: policy.addItemsAutomatically
7989
+ });
7990
+ void (async () => {
7991
+ let resolvedRefs = {};
7992
+ try {
7993
+ const refs = [policy.defaultAccountId, ...policy.allowedAccountIds].filter(Boolean);
7994
+ resolvedRefs = await provider.resolveChartAccountRefs?.(refs) ?? {};
7995
+ } catch {
7996
+ }
7997
+ if (request !== accountingPolicyRequest.current) return;
7998
+ const resolved = resolveCounterpartyAccountingPolicy(policy, resolvedRefs);
7999
+ setCounterpartyAccounting(resolved);
8000
+ setItems((current) => current.map((item) => ({
8001
+ ...item,
8002
+ accountId: item.accountId ?? resolved.defaultAccountId,
8003
+ costCenterId: item.costCenterId ?? resolved.defaultCostCenterId
8004
+ })));
8005
+ })();
8006
+ },
8007
+ label: isPayable ? "Pagar a" : "Receber de",
8008
+ required: true,
8009
+ onCreate: contactCreate.onCreate
8010
+ }
8011
+ ),
8012
+ contactCreate.modal,
8013
+ error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }) : null
8014
+ ] }) }),
8015
+ /* @__PURE__ */ jsx(
8016
+ OrderFormItemsCard,
8017
+ {
8018
+ label: "Itens",
8019
+ addLabel: "Adicionar item",
8020
+ onAdd: () => hasItemLookups ? setAddingItem(true) : addItem(),
8021
+ count: items.length,
8022
+ total: formatCurrency(amount, currency),
8023
+ header: addingItem ? /* @__PURE__ */ jsx(OrderFormAddItemStep, { itemTypes, onSelect: addItem, onCancel: () => setAddingItem(false) }) : void 0,
8024
+ children: items.map((item, index) => /* @__PURE__ */ jsx(
8025
+ OrderFormItemRow,
8026
+ {
8027
+ ...item,
8028
+ index,
8029
+ itemTypes,
8030
+ currency,
8031
+ formatMoney: (next) => formatCurrency(next, currency),
8032
+ expanded: expandedItemId === item._id,
8033
+ onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
8034
+ onChange: (patch) => updateItem(item._id, patch),
8035
+ onRemove: () => setItems((current) => current.filter((entry) => entry._id !== item._id)),
8036
+ entityLookups,
8037
+ showTypeSelector: hasItemLookups,
8038
+ allowSurcharge: true,
8039
+ extraFieldGroups: [
8040
+ ...selectableAccounts.length > 0 ? [{
8041
+ id: "account",
8042
+ label: "Plano de contas",
8043
+ filled: !!item.accountId,
8044
+ onClear: () => updateItem(item._id, { accountId: void 0 }),
8045
+ field: /* @__PURE__ */ jsx(
8046
+ SearchSelect,
8047
+ {
8048
+ label: "Plano de contas",
8049
+ value: item.accountId,
8050
+ displayValue: selectableAccounts.find((account) => account.id === item.accountId)?.name,
8051
+ placeholder: "Sem classifica\xE7\xE3o",
8052
+ minChars: 0,
8053
+ onSearch: (query) => selectableAccounts.filter((account) => accountLabel(account).toLowerCase().includes(query.toLowerCase())).map((account) => ({ id: account.id, label: accountLabel(account) })),
8054
+ onChange: (id) => updateItem(item._id, { accountId: id || void 0 })
8055
+ }
8056
+ )
8057
+ }] : [],
8058
+ ...costCenters.length > 0 ? [{
8059
+ id: "costCenter",
8060
+ label: "Centro de custo",
8061
+ filled: !!item.costCenterId,
8062
+ onClear: () => updateItem(item._id, { costCenterId: void 0 }),
8063
+ field: /* @__PURE__ */ jsx(
8064
+ SearchSelect,
8065
+ {
8066
+ label: "Centro de custo",
8067
+ value: item.costCenterId,
8068
+ displayValue: costCenters.find((center) => center.id === item.costCenterId)?.name,
8069
+ placeholder: "Sem centro de custo",
8070
+ minChars: 0,
8071
+ onSearch: (query) => costCenters.filter((center) => center.name.toLowerCase().includes(query.toLowerCase())).map((center) => ({ id: center.id, label: center.name })),
8072
+ onChange: (id) => updateItem(item._id, { costCenterId: id || void 0 })
8073
+ }
8074
+ )
8075
+ }] : []
8076
+ ]
8077
+ },
8078
+ item._id
8079
+ ))
8080
+ }
8081
+ )
8082
+ ]
8083
+ }
8084
+ );
8085
+ }
8086
+ function Field2({ label, children }) {
8087
+ return /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-xs font-medium text-muted-foreground", children: [
8088
+ /* @__PURE__ */ jsx("span", { children: label }),
8089
+ children
8090
+ ] });
8091
+ }
7049
8092
  var FINANCIAL_MODULE_DEFAULTS = {
7050
8093
  commissions: true
7051
8094
  };
@@ -7087,7 +8130,7 @@ function FinancialGeneralSettings({ config }) {
7087
8130
  ] })
7088
8131
  ] });
7089
8132
  }
7090
- var STEPS = [
8133
+ var STEPS2 = [
7091
8134
  { id: "welcome", icon: DollarSign, titleKey: "financial.onboarding.welcome", descKey: "financial.onboarding.description" },
7092
8135
  { id: "accounts", icon: Landmark, titleKey: "financial.onboarding.accounts.title", descKey: "financial.onboarding.accounts.description" },
7093
8136
  { id: "payments", icon: CreditCard, titleKey: "financial.onboarding.payments.title", descKey: "financial.onboarding.payments.description" },
@@ -7096,11 +8139,11 @@ var STEPS = [
7096
8139
  function FinancialOnboarding({ onComplete }) {
7097
8140
  const t2 = useTranslation();
7098
8141
  const [step, setStep] = useState(0);
7099
- const current = STEPS[step];
7100
- const isLast = step === STEPS.length - 1;
8142
+ const current = STEPS2[step];
8143
+ const isLast = step === STEPS2.length - 1;
7101
8144
  const Icon = current.icon;
7102
8145
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-lg", children: [
7103
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 mb-8", children: STEPS.map((s, i) => /* @__PURE__ */ jsx(
8146
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 mb-8", children: STEPS2.map((s, i) => /* @__PURE__ */ jsx(
7104
8147
  "div",
7105
8148
  {
7106
8149
  className: `h-1 flex-1 rounded-full transition-colors ${i <= step ? "bg-primary" : "bg-muted"}`
@@ -7113,7 +8156,7 @@ function FinancialOnboarding({ onComplete }) {
7113
8156
  /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold", children: t2(current.titleKey) }),
7114
8157
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2 max-w-sm mx-auto", children: t2(current.descKey) })
7115
8158
  ] }),
7116
- step > 0 && step < STEPS.length - 1 && /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-muted/30 px-4 py-3 text-xs text-muted-foreground", children: t2("financial.onboarding.configureHint") }),
8159
+ step > 0 && step < STEPS2.length - 1 && /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-muted/30 px-4 py-3 text-xs text-muted-foreground", children: t2("financial.onboarding.configureHint") }),
7117
8160
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-3 pt-2", children: [
7118
8161
  step > 0 && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "lg", onClick: () => setStep(step - 1), children: t2("financial.onboarding.back") }),
7119
8162
  isLast ? /* @__PURE__ */ jsxs(Button, { variant: "default", size: "lg", onClick: onComplete, children: [
@@ -7138,6 +8181,14 @@ function FinancialOnboarding({ onComplete }) {
7138
8181
  }
7139
8182
  function parseIntent(activeView) {
7140
8183
  const { base, id } = parseViewId(activeView);
8184
+ const recurring = base.match(/^(payables|receivables)-recurring-(new|edit)$/);
8185
+ if (recurring) {
8186
+ return {
8187
+ view: `${recurring[1]}-recurring`,
8188
+ mode: recurring[2],
8189
+ editId: recurring[2] === "edit" ? id : void 0
8190
+ };
8191
+ }
7141
8192
  const m = base.match(/^(payables|receivables)-(new-receipt|new|list|detail|edit)$/);
7142
8193
  if (m) {
7143
8194
  const raw = m[2];
@@ -7164,16 +8215,8 @@ function buildNav(config, activeView, navigate, t2) {
7164
8215
  active: activeView.startsWith("payables"),
7165
8216
  children: [
7166
8217
  { id: "payables-new", kind: "action", label: t2("financial.nav.new"), active: activeView.startsWith("payables-new"), onClick: () => navigate("payables-new") },
7167
- { id: "payables-list", label: t2("financial.nav.list"), active: activeView === "payables-list", onClick: () => navigate("payables-list") }
7168
- // `payables-recurring` saiu daqui: era link para tela que não existe.
7169
- // O id não casa com o regex do `parseIntent` (new|list|detail|edit),
7170
- // então `renderView` caía no default e a aba "Despesas Recorrentes"
7171
- // abria o RESUMO. E, sendo a segunda filha de Contas a Pagar, era ela
7172
- // que fazia a faixa desenhar o controle segmentado "Lista | Despesas
7173
- // Recorrentes" à direita — dois segmentos para uma vista só.
7174
- // A tabela (`plg_financial_recurring_bills`) e a migration continuam
7175
- // de pé; quando a tela existir, a entrada volta como botão ao lado do
7176
- // cadastro, na barra de ações da lista.
8218
+ { id: "payables-list", label: t2("financial.nav.list"), active: activeView === "payables-list", onClick: () => navigate("payables-list") },
8219
+ { id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") }
7177
8220
  ]
7178
8221
  });
7179
8222
  }
@@ -7185,7 +8228,8 @@ function buildNav(config, activeView, navigate, t2) {
7185
8228
  active: activeView.startsWith("receivables"),
7186
8229
  children: [
7187
8230
  { id: "receivables-new", kind: "action", label: t2("financial.nav.new"), active: activeView.startsWith("receivables-new"), onClick: () => navigate("receivables-new") },
7188
- { id: "receivables-list", label: t2("financial.nav.list"), active: activeView === "receivables-list", onClick: () => navigate("receivables-list") }
8231
+ { id: "receivables-list", label: t2("financial.nav.list"), active: activeView === "receivables-list", onClick: () => navigate("receivables-list") },
8232
+ { id: "receivables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("receivables-recurring"), onClick: () => navigate("receivables-recurring") }
7189
8233
  ]
7190
8234
  });
7191
8235
  }
@@ -7278,6 +8322,12 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
7278
8322
  "payables-new": 1,
7279
8323
  "receivables-list": 0,
7280
8324
  "receivables-new": 1,
8325
+ "payables-recurring": 0,
8326
+ "receivables-recurring": 0,
8327
+ "payables-recurring-new": 1,
8328
+ "payables-recurring-edit": 1,
8329
+ "receivables-recurring-new": 1,
8330
+ "receivables-recurring-edit": 1,
7281
8331
  "cash-registers": 0,
7282
8332
  statements: 0,
7283
8333
  reconciliation: 0,
@@ -7287,29 +8337,17 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
7287
8337
  "notas-fiscais": 0,
7288
8338
  settings: 1
7289
8339
  });
7290
- const [onboardingComplete, setOnboardingComplete] = useState(() => {
7291
- try {
7292
- return localStorage.getItem("saas-core:financial-onboarded") === "true";
7293
- } catch {
7294
- return false;
7295
- }
7296
- });
7297
- useEffect(() => {
7298
- if (onboardingComplete) return;
7299
- let cancelled = false;
7300
- void provider.getMovements({ pageSize: 1 }).then((res) => {
7301
- if (!cancelled && res.total > 0) setOnboardingComplete(true);
7302
- }).catch(() => {
7303
- });
7304
- return () => {
7305
- cancelled = true;
7306
- };
7307
- }, [onboardingComplete, provider]);
8340
+ const checkExistingData = useCallback(
8341
+ async () => (await provider.getMovements({ pageSize: 1 })).total > 0,
8342
+ [provider]
8343
+ );
8344
+ const onboarding = usePluginOnboardingGate("saas-core:financial-onboarded", checkExistingData);
7308
8345
  const t2 = useTranslation();
7309
8346
  const can = usePermissionOptional();
7310
8347
  const canCreate = can("financial", "create");
7311
8348
  const intent = parseIntent(view);
7312
8349
  const isSettings = view === "settings";
8350
+ const isSummary = intent.view === "summary";
7313
8351
  const nav = useModuleNavAccess(buildNav(config, view, navigate, t2));
7314
8352
  const quickActions = useMemo(() => {
7315
8353
  if (!canCreate) return [];
@@ -7343,15 +8381,11 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
7343
8381
  }
7344
8382
  return actions;
7345
8383
  }, [config.modules, canCreate, navigate, t2]);
7346
- function handleOnboardingComplete() {
7347
- setOnboardingComplete(true);
7348
- try {
7349
- localStorage.setItem("saas-core:financial-onboarded", "true");
7350
- } catch {
7351
- }
8384
+ if (isSummary && onboarding.status === "checking") {
8385
+ return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store6, children: /* @__PURE__ */ jsx(PluginOnboardingGateSkeleton, {}) });
7352
8386
  }
7353
- if (!onboardingComplete) {
7354
- return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store6, children: /* @__PURE__ */ jsx(FinancialOnboarding, { onComplete: handleOnboardingComplete }) });
8387
+ if (isSummary && onboarding.status === "needed") {
8388
+ return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store6, children: /* @__PURE__ */ jsx(FinancialOnboarding, { onComplete: onboarding.complete }) });
7355
8389
  }
7356
8390
  if (isSettings && registries && registries.length > 0) {
7357
8391
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store6, children: /* @__PURE__ */ jsx(PageTransition, { transitionKey: "settings", direction, children: /* @__PURE__ */ jsx("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsx(
@@ -7373,6 +8407,10 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
7373
8407
  return /* @__PURE__ */ jsx(PayablesView, { intent, onNavigate: navigate, previousView, back });
7374
8408
  case "receivables":
7375
8409
  return /* @__PURE__ */ jsx(ReceivablesView, { intent, onNavigate: navigate, previousView, back });
8410
+ case "payables-recurring":
8411
+ return /* @__PURE__ */ jsx(RecurringBillsView, { direction: "payable", intent, onNavigate: navigate });
8412
+ case "receivables-recurring":
8413
+ return /* @__PURE__ */ jsx(RecurringBillsView, { direction: "receivable", intent, onNavigate: navigate });
7376
8414
  case "cash-registers":
7377
8415
  return /* @__PURE__ */ jsx(CashRegistersView, {});
7378
8416
  case "statements":
@@ -7565,10 +8603,10 @@ function RecentTransactions() {
7565
8603
  function useDueLabel() {
7566
8604
  const t2 = useTranslation();
7567
8605
  return (dueDate) => {
7568
- const today4 = /* @__PURE__ */ new Date();
7569
- today4.setHours(0, 0, 0, 0);
8606
+ const today5 = /* @__PURE__ */ new Date();
8607
+ today5.setHours(0, 0, 0, 0);
7570
8608
  const due = /* @__PURE__ */ new Date(`${dueDate}T00:00:00`);
7571
- const days = Math.round((due.getTime() - today4.getTime()) / 864e5);
8609
+ const days = Math.round((due.getTime() - today5.getTime()) / 864e5);
7572
8610
  if (days < 0) return t2("financial.home.overdueDays", { count: String(Math.abs(days)) });
7573
8611
  if (days === 0) return t2("financial.home.dueToday");
7574
8612
  if (days === 1) return t2("financial.home.dueTomorrow");
@@ -7581,7 +8619,7 @@ function BalanceHero() {
7581
8619
  const summary = useFinancialStore((s) => s.summary);
7582
8620
  const navigate = useDashboardNavigate();
7583
8621
  useEnsureSummary();
7584
- const [hidden, setHidden] = React15__default.useState(false);
8622
+ const [hidden, setHidden] = React20__default.useState(false);
7585
8623
  const money3 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
7586
8624
  const inflow = summary?.monthlyInflow ?? 0;
7587
8625
  const outflow = summary?.monthlyOutflow ?? 0;
@@ -7654,7 +8692,7 @@ function categorizeSpend(notes) {
7654
8692
  function SpendByCategoryChart() {
7655
8693
  const t2 = useTranslation();
7656
8694
  const provider = useFinancialProvider();
7657
- const [rows, setRows] = React15__default.useState([]);
8695
+ const [rows, setRows] = React20__default.useState([]);
7658
8696
  useEffect(() => {
7659
8697
  let alive = true;
7660
8698
  void (async () => {
@@ -7697,7 +8735,7 @@ function UpcomingBills() {
7697
8735
  const { currency } = useFinancialConfig();
7698
8736
  const provider = useFinancialProvider();
7699
8737
  const dueLabel = useDueLabel();
7700
- const [bills, setBills] = React15__default.useState([]);
8738
+ const [bills, setBills] = React20__default.useState([]);
7701
8739
  useEffect(() => {
7702
8740
  let alive = true;
7703
8741
  void (async () => {
@@ -7798,7 +8836,7 @@ function uid() {
7798
8836
  function now() {
7799
8837
  return (/* @__PURE__ */ new Date()).toISOString();
7800
8838
  }
7801
- function today3() {
8839
+ function today4() {
7802
8840
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7803
8841
  }
7804
8842
  function round22(n) {
@@ -7876,7 +8914,8 @@ function createStore(options) {
7876
8914
  chartOfAccounts: [],
7877
8915
  costCenters: [],
7878
8916
  cardTransactions: [],
7879
- notas: []
8917
+ notas: [],
8918
+ recurringBills: seed?.recurringBills ? [...seed.recurringBills] : []
7880
8919
  };
7881
8920
  }
7882
8921
  function createMockFinancialProvider(options) {
@@ -7889,7 +8928,7 @@ function createMockFinancialProvider(options) {
7889
8928
  invoice.status = "partial";
7890
8929
  } else {
7891
8930
  const movements = store6.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
7892
- const hasOverdue = movements.some((m) => m.status === "overdue" || m.status === "pending" && m.dueDate < today3());
8931
+ const hasOverdue = movements.some((m) => m.status === "overdue" || m.status === "pending" && m.dueDate < today4());
7893
8932
  invoice.status = hasOverdue ? "overdue" : "open";
7894
8933
  }
7895
8934
  invoice.updatedAt = now();
@@ -7901,6 +8940,7 @@ function createMockFinancialProvider(options) {
7901
8940
  if (query.direction) results = results.filter((i) => i.direction === query.direction);
7902
8941
  if (query.status) results = results.filter((i) => matchesStatus(i.status, query.status));
7903
8942
  if (query.contactId) results = results.filter((i) => i.contactId === query.contactId);
8943
+ if (query.unitId) results = results.filter((i) => i.unitId === query.unitId);
7904
8944
  if (query.accountId) {
7905
8945
  const invoiceIds = new Set(store6.invoiceItems.filter((item) => item.accountId === query.accountId).map((item) => item.invoiceId));
7906
8946
  results = results.filter((invoice) => invoiceIds.has(invoice.id));
@@ -8161,6 +9201,11 @@ function createMockFinancialProvider(options) {
8161
9201
  async getBankAccounts() {
8162
9202
  return store6.bankAccounts.filter((a) => a.isActive);
8163
9203
  },
9204
+ async searchFinancialAccounts(query, limit = 20) {
9205
+ const term = query.trim().toLocaleLowerCase("pt-BR");
9206
+ if (term.length < 2) return [];
9207
+ return store6.bankAccounts.filter((account) => account.isActive && account.name.toLocaleLowerCase("pt-BR").includes(term)).sort((a, b) => a.name.localeCompare(b.name, "pt-BR")).slice(0, limit);
9208
+ },
8164
9209
  async createBankAccount(data) {
8165
9210
  const account = {
8166
9211
  id: uid(),
@@ -8255,6 +9300,10 @@ function createMockFinancialProvider(options) {
8255
9300
  async getChartOfAccounts() {
8256
9301
  return store6.chartOfAccounts.filter((n) => n.isActive);
8257
9302
  },
9303
+ async resolveChartAccountRefs(refs) {
9304
+ const ids = new Set(store6.chartOfAccounts.map((account) => account.id));
9305
+ return Object.fromEntries(refs.filter((ref) => ids.has(ref)).map((ref) => [ref, ref]));
9306
+ },
8258
9307
  async getCostCenters() {
8259
9308
  return store6.costCenters.filter((c) => c.isActive);
8260
9309
  },
@@ -8266,7 +9315,7 @@ function createMockFinancialProvider(options) {
8266
9315
  },
8267
9316
  // --- Statements (ERP extract: opening balance + realized cash + per-row net/fee) ---
8268
9317
  async getStatement(query) {
8269
- const todayStr = today3();
9318
+ const todayStr = today4();
8270
9319
  const isRealized = (m) => m.status !== "cancelled" && !!m.paymentDate && affectsCash(m);
8271
9320
  const consolidated = !query.bankAccountId;
8272
9321
  const scopeAccounts = consolidated ? store6.bankAccounts.filter((a) => a.isActive) : store6.bankAccounts.filter((a) => a.id === query.bankAccountId);
@@ -8335,6 +9384,46 @@ function createMockFinancialProvider(options) {
8335
9384
  accountCurrentBalance
8336
9385
  };
8337
9386
  },
9387
+ async getDre(query) {
9388
+ const rows = [];
9389
+ for (const account of store6.chartOfAccounts.filter((candidate) => candidate.accountType === "revenue" || candidate.accountType === "expense")) {
9390
+ rows.push({
9391
+ accountId: account.id,
9392
+ accountCode: account.code,
9393
+ accountName: account.name,
9394
+ accountType: account.accountType,
9395
+ parentAccountId: account.parentId,
9396
+ statementCode: account.code,
9397
+ statementName: account.name,
9398
+ unclassified: false,
9399
+ amount: 0,
9400
+ discount: 0,
9401
+ lineCount: 0
9402
+ });
9403
+ }
9404
+ for (const invoice of store6.invoices.filter((candidate) => candidate.invoiceDate.startsWith(String(query.year)) && (!query.unitId || candidate.unitId === query.unitId))) {
9405
+ for (const item of store6.invoiceItems.filter((candidate) => candidate.invoiceId === invoice.id)) {
9406
+ const account = store6.chartOfAccounts.find((candidate) => candidate.id === item.accountId);
9407
+ const accountType = invoice.direction === "credit" ? "revenue" : "expense";
9408
+ rows.push({
9409
+ unitId: invoice.unitId,
9410
+ competenceMonth: `${invoice.invoiceDate.slice(0, 7)}-01`,
9411
+ accountId: account?.id,
9412
+ accountCode: account?.code,
9413
+ accountName: account?.name,
9414
+ accountType,
9415
+ parentAccountId: account?.parentId,
9416
+ statementCode: account?.accountType === accountType ? account.code : void 0,
9417
+ statementName: account?.accountType === accountType ? account.name : void 0,
9418
+ unclassified: !account || account.accountType !== accountType,
9419
+ amount: item.totalAmount,
9420
+ discount: item.discount,
9421
+ lineCount: 1
9422
+ });
9423
+ }
9424
+ }
9425
+ return rows;
9426
+ },
8338
9427
  // --- Summary ---
8339
9428
  async getSummary(dateRange) {
8340
9429
  const totalBalance = store6.bankAccounts.filter((a) => a.isActive).reduce((sum, a) => sum + a.currentBalance, 0);
@@ -8344,7 +9433,7 @@ function createMockFinancialProvider(options) {
8344
9433
  const pendingPayable = store6.movements.filter(
8345
9434
  (m) => m.direction === "debit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
8346
9435
  );
8347
- const todayStr = today3();
9436
+ const todayStr = today4();
8348
9437
  const monthAgo = /* @__PURE__ */ new Date();
8349
9438
  monthAgo.setDate(monthAgo.getDate() - 30);
8350
9439
  const monthStart = monthAgo.toISOString().slice(0, 10);
@@ -8366,6 +9455,47 @@ function createMockFinancialProvider(options) {
8366
9455
  overduePayableAmount: overduePayable.reduce((s, m) => s + (m.amount - m.paidAmount), 0)
8367
9456
  };
8368
9457
  },
9458
+ // --- Recurring payables and receivables ---
9459
+ async getRecurringBills(direction) {
9460
+ return store6.recurringBills.filter((bill) => bill.direction === direction).sort((a, b) => Number(b.isActive) - Number(a.isActive) || a.description.localeCompare(b.description));
9461
+ },
9462
+ async saveRecurringBill(input) {
9463
+ const timestamp = now();
9464
+ const existing = input.id ? store6.recurringBills.find((bill2) => bill2.id === input.id) : void 0;
9465
+ const bill = {
9466
+ id: existing?.id ?? uid(),
9467
+ direction: input.direction,
9468
+ description: input.description.trim(),
9469
+ counterpartyPersonId: input.counterpartyPersonId,
9470
+ counterpartyName: existing?.counterpartyName,
9471
+ amount: round22(input.amount),
9472
+ items: input.items.map((item) => ({ ...item })),
9473
+ unitId: input.unitId || null,
9474
+ chartAccountId: input.chartAccountId || null,
9475
+ costCenterId: input.costCenterId || null,
9476
+ frequency: input.frequency,
9477
+ dueDay: input.dueDay,
9478
+ startsOn: input.startsOn,
9479
+ endsOn: input.endsOn || null,
9480
+ isActive: input.isActive ?? existing?.isActive ?? true,
9481
+ notes: input.notes?.trim() || null,
9482
+ createdAt: existing?.createdAt ?? timestamp,
9483
+ updatedAt: timestamp
9484
+ };
9485
+ if (existing) Object.assign(existing, bill);
9486
+ else store6.recurringBills.push(bill);
9487
+ return bill;
9488
+ },
9489
+ async updateRecurringBill(id, patch) {
9490
+ const bill = store6.recurringBills.find((candidate) => candidate.id === id);
9491
+ if (!bill) throw new Error("Recurring bill not found");
9492
+ if (patch.isActive !== void 0) bill.isActive = patch.isActive;
9493
+ if (patch.endsOn !== void 0) bill.endsOn = patch.endsOn;
9494
+ bill.updatedAt = now();
9495
+ },
9496
+ async runRecurring() {
9497
+ return { created: 0, total: 0 };
9498
+ },
8369
9499
  // --- Reconciliation (conciliação) ---
8370
9500
  async importBankTransactions(input) {
8371
9501
  let imported = 0, duplicates = 0;
@@ -8551,6 +9681,39 @@ function getClients() {
8551
9681
  function round23(n) {
8552
9682
  return Math.round(n * 100) / 100;
8553
9683
  }
9684
+ function mapRecurringBill(row, counterpartyName) {
9685
+ const items = Array.isArray(row.items) ? row.items.map((item) => ({
9686
+ itemKind: item.itemKind ?? item.item_kind ?? (item.product_id ? "product" : "other"),
9687
+ referenceId: item.referenceId ?? item.reference_id ?? item.product_id ?? void 0,
9688
+ description: item.description ?? "",
9689
+ quantity: Number(item.quantity) || 1,
9690
+ unitPrice: Number(item.unitPrice ?? item.unit_price) || 0,
9691
+ discount: Number(item.discount) || 0,
9692
+ surcharge: Number(item.surcharge ?? item.addition) || 0,
9693
+ accountId: item.accountId ?? item.account_id ?? item.chart_account_id ?? void 0,
9694
+ costCenterId: item.costCenterId ?? item.cost_center_id ?? void 0
9695
+ })) : [];
9696
+ return {
9697
+ id: row.id,
9698
+ direction: row.direction,
9699
+ description: row.description,
9700
+ counterpartyPersonId: row.counterparty_person_id,
9701
+ counterpartyName,
9702
+ amount: Number(row.amount) || 0,
9703
+ items,
9704
+ unitId: row.unit_id,
9705
+ chartAccountId: row.chart_account_id,
9706
+ costCenterId: row.cost_center_id,
9707
+ frequency: row.frequency,
9708
+ dueDay: Number(row.due_day) || 1,
9709
+ startsOn: row.starts_on,
9710
+ endsOn: row.ends_on,
9711
+ isActive: Boolean(row.is_active),
9712
+ notes: row.notes,
9713
+ createdAt: row.created_at,
9714
+ updatedAt: row.updated_at
9715
+ };
9716
+ }
8554
9717
  async function findInvoiceIdsByLineAccounting(core, pub, filters) {
8555
9718
  if (!filters.accountId && !filters.costCenterId) return void 0;
8556
9719
  let sideQ = pub.from(T.invoiceItems).select("item_id");
@@ -8712,6 +9875,7 @@ function createSupabaseFinancialProvider() {
8712
9875
  qb = qb.in("invoice_id", scoped);
8713
9876
  }
8714
9877
  if (query.contactId) qb = qb.eq("counterparty_person_id", query.contactId);
9878
+ if (query.unitId) qb = qb.eq("unit_id", query.unitId);
8715
9879
  if (query.search) qb = qb.or(`description.ilike.%${query.search}%,notes.ilike.%${query.search}%,number.ilike.%${query.search}%,counterparty_name.ilike.%${query.search}%`);
8716
9880
  if (query.dateRange) {
8717
9881
  qb = qb.gte("due_date", query.dateRange.from.slice(0, 10)).lte("due_date", query.dateRange.to.slice(0, 10));
@@ -8723,6 +9887,33 @@ function createSupabaseFinancialProvider() {
8723
9887
  const linhas = (data ?? []).map(rowToInvoiceLine);
8724
9888
  const titulos = [...new Set(linhas.map((l) => l.invoiceId).filter(Boolean))];
8725
9889
  if (titulos.length) {
9890
+ const { data: itemRows } = await pub.from("v_financial_invoice_lines").select("invoice_id,name,description,quantity").in("invoice_id", titulos);
9891
+ const itemSummaries = summarizeInvoiceLines(itemRows ?? []);
9892
+ for (const line of linhas) {
9893
+ line.itemsSummary = itemSummaries.get(line.invoiceId ?? line.id) ?? line.itemsSummary;
9894
+ }
9895
+ const installmentIds = linhas.map((line) => line.id);
9896
+ const { data: allocations } = await pub.from(T.allocations).select("installment_id,payment_movement_id,created_at").in("installment_id", installmentIds).order("created_at", { ascending: false });
9897
+ const movementIds = [...new Set((allocations ?? []).map((row) => row.payment_movement_id).filter(Boolean))];
9898
+ if (movementIds.length > 0) {
9899
+ const { data: paymentRows } = await pub.from(T.movements).select("id,payment_method_id,payment_method_type_id,bank_account_id,card_installments,status,reversed_by_id").in("id", movementIds);
9900
+ const paymentById = new Map((paymentRows ?? []).map((row) => [row.id, row]));
9901
+ const latestByInstallment = /* @__PURE__ */ new Map();
9902
+ for (const allocation of allocations ?? []) {
9903
+ if (latestByInstallment.has(allocation.installment_id)) continue;
9904
+ const payment = paymentById.get(allocation.payment_movement_id);
9905
+ if (!payment || payment.status === "cancelled" || payment.reversed_by_id) continue;
9906
+ latestByInstallment.set(allocation.installment_id, payment);
9907
+ }
9908
+ for (const line of linhas) {
9909
+ const payment = latestByInstallment.get(line.id);
9910
+ if (!payment) continue;
9911
+ line.paymentMethodId = payment.payment_method_id ?? void 0;
9912
+ line.paymentMethodTypeId = payment.payment_method_type_id ?? void 0;
9913
+ line.paymentAccountId = payment.bank_account_id ?? void 0;
9914
+ line.paymentCardInstallments = Number(payment.card_installments) || void 0;
9915
+ }
9916
+ }
8726
9917
  const { data: docs } = await pub.from("documents").select("subject_id").eq("subject_type", "financial_invoice").eq("is_active", true).in("subject_id", titulos);
8727
9918
  const porTitulo = /* @__PURE__ */ new Map();
8728
9919
  for (const d of docs ?? []) {
@@ -8846,7 +10037,7 @@ function createSupabaseFinancialProvider() {
8846
10037
  discount: item.discount ?? 0,
8847
10038
  total: lineTotal(item),
8848
10039
  sort_order: i,
8849
- metadata: { itemKind: item.itemKind, referenceId: item.referenceId }
10040
+ metadata: { ...item.metadata, itemKind: item.itemKind, referenceId: item.referenceId }
8850
10041
  }))
8851
10042
  ).select("id, sort_order");
8852
10043
  await writeLineAccounting(pub, tenantId, inserted, input.items);
@@ -9200,6 +10391,35 @@ function createSupabaseFinancialProvider() {
9200
10391
  accountType: r.kind
9201
10392
  }));
9202
10393
  },
10394
+ async searchFinancialAccounts(query, limit = 20) {
10395
+ const term = query.trim();
10396
+ if (term.length < 2) return [];
10397
+ const { pub } = getClients();
10398
+ const groups = [
10399
+ ["person"],
10400
+ ["bank", "unit"],
10401
+ ["cash_physical", "cash_session"],
10402
+ ["card_receivable_credit", "card_receivable_debit", "card_payable"],
10403
+ ["unidentified", "system"]
10404
+ ];
10405
+ const perGroup = Math.min(Math.max(Math.ceil(limit / groups.length), 4), 12);
10406
+ const answers = await Promise.all(groups.map(async (kinds) => {
10407
+ const { data, error } = await pub.from("plg_financial_accounts").select("*").eq("is_active", true).in("kind", kinds).ilike("name", `%${term}%`).order("name").limit(perGroup);
10408
+ if (error) throw error;
10409
+ return (data ?? []).map((row) => ({
10410
+ ...snakeToCamel(row),
10411
+ accountType: row.kind
10412
+ }));
10413
+ }));
10414
+ const out = [];
10415
+ for (let index = 0; out.length < limit && answers.some((rows) => index < rows.length); index += 1) {
10416
+ for (const rows of answers) {
10417
+ if (rows[index]) out.push(rows[index]);
10418
+ if (out.length === limit) break;
10419
+ }
10420
+ }
10421
+ return out;
10422
+ },
9203
10423
  /**
9204
10424
  * Writes where getBankAccounts READS.
9205
10425
  *
@@ -9357,6 +10577,14 @@ function createSupabaseFinancialProvider() {
9357
10577
  if (error) throw error;
9358
10578
  return (data ?? []).map((r) => snakeToCamel(r));
9359
10579
  },
10580
+ async resolveChartAccountRefs(refs) {
10581
+ const unique = [...new Set(refs.map((ref) => String(ref).trim()).filter(Boolean))];
10582
+ if (unique.length === 0) return {};
10583
+ const { pub } = getClients();
10584
+ const { data, error } = await pub.rpc("financial_resolve_chart_account_refs", { p_refs: unique });
10585
+ if (error) throw error;
10586
+ return Object.fromEntries((data ?? []).map((row) => [String(row.source_ref), String(row.chart_account_id)]));
10587
+ },
9360
10588
  async getCostCenters() {
9361
10589
  const { pub } = getClients();
9362
10590
  const { data, error } = await pub.from(T.costCenters).select("*").eq("is_active", true).order("code");
@@ -9371,7 +10599,7 @@ function createSupabaseFinancialProvider() {
9371
10599
  // --- Statements (ERP extract: opening balance + realized cash + per-row net/fee) ---
9372
10600
  async getStatement(query) {
9373
10601
  const { core, pub } = getClients();
9374
- const today4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
10602
+ const today5 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9375
10603
  const { data: acctRows } = await pub.from("plg_financial_accounts").select("id, name, is_active");
9376
10604
  const allAccounts = acctRows ?? [];
9377
10605
  const consolidated = !query.bankAccountId;
@@ -9451,7 +10679,7 @@ function createSupabaseFinancialProvider() {
9451
10679
  };
9452
10680
  }
9453
10681
  }
9454
- const { data: todayRows } = await scopeFilter(pub.from(T.cashMovements).select("*").neq("status", "cancelled").lte("payment_date", today4));
10682
+ const { data: todayRows } = await scopeFilter(pub.from(T.cashMovements).select("*").neq("status", "cancelled").lte("payment_date", today5));
9455
10683
  const accountCurrentBalance = round23(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
9456
10684
  return {
9457
10685
  entries,
@@ -9465,6 +10693,48 @@ function createSupabaseFinancialProvider() {
9465
10693
  accountCurrentBalance
9466
10694
  };
9467
10695
  },
10696
+ async getDre(query) {
10697
+ const { pub } = getClients();
10698
+ const columns = "unit_id,competence_month,direction,account_id,account_code,account_name,account_type,account_parent_id,statement_code,statement_name,unclassified,line_count,amount,discount";
10699
+ const rpcResult = await pub.rpc("financial_dre", {
10700
+ p_from: `${query.year}-01-01`,
10701
+ p_to: `${query.year}-12-31`,
10702
+ p_unit_id: query.unitId ?? null
10703
+ });
10704
+ if (!rpcResult.error) return (rpcResult.data ?? []).map((row) => ({
10705
+ unitId: row.unit_id ?? void 0,
10706
+ competenceMonth: row.competence_month ?? void 0,
10707
+ accountId: row.account_id ?? void 0,
10708
+ accountCode: row.account_code ?? void 0,
10709
+ accountName: row.account_name ?? void 0,
10710
+ accountType: row.account_type,
10711
+ parentAccountId: row.account_parent_id ?? void 0,
10712
+ statementCode: row.statement_code ?? void 0,
10713
+ statementName: row.statement_name ?? void 0,
10714
+ unclassified: Boolean(row.unclassified),
10715
+ lineCount: Number(row.line_count) || 0,
10716
+ amount: Number(row.amount) || 0,
10717
+ discount: Number(row.discount) || 0
10718
+ }));
10719
+ if (rpcResult.error.code !== "PGRST202" && rpcResult.error.code !== "42883") throw rpcResult.error;
10720
+ const { data, error } = await pub.from("plg_financial_rep_dre").select(columns).or(`competence_month.is.null,and(competence_month.gte.${query.year}-01-01,competence_month.lte.${query.year}-12-31)`).order("account_code", { ascending: true }).order("competence_month", { ascending: true });
10721
+ if (error) throw error;
10722
+ return (data ?? []).filter((row) => !row.competence_month || !query.unitId || row.unit_id === query.unitId).map((row) => ({
10723
+ unitId: row.unit_id ?? void 0,
10724
+ competenceMonth: row.competence_month ?? void 0,
10725
+ accountId: row.account_id ?? void 0,
10726
+ accountCode: row.account_code ?? void 0,
10727
+ accountName: row.account_name ?? void 0,
10728
+ accountType: row.direction === "receivable" ? "revenue" : row.direction === "payable" ? "expense" : row.account_type,
10729
+ parentAccountId: row.account_parent_id ?? void 0,
10730
+ statementCode: row.statement_code ?? void 0,
10731
+ statementName: row.statement_name ?? void 0,
10732
+ unclassified: Boolean(row.unclassified),
10733
+ lineCount: Number(row.line_count) || 0,
10734
+ amount: Number(row.amount) || 0,
10735
+ discount: Number(row.discount) || 0
10736
+ }));
10737
+ },
9468
10738
  // --- Summary ---
9469
10739
  // --- Fechamento e prova do livro (006) ---
9470
10740
  async getPeriods() {
@@ -9534,6 +10804,51 @@ function createSupabaseFinancialProvider() {
9534
10804
  }));
9535
10805
  },
9536
10806
  // --- Contas recorrentes (014) ---
10807
+ async getRecurringBills(direction) {
10808
+ const { pub } = getClients();
10809
+ const { data, error } = await pub.from(T.recurringBills).select("*").eq("direction", direction).order("is_active", { ascending: false }).order("description");
10810
+ if (error) throw error;
10811
+ const rows = data ?? [];
10812
+ const personIds = [...new Set(rows.map((row) => row.counterparty_person_id).filter(Boolean))];
10813
+ const names = /* @__PURE__ */ new Map();
10814
+ if (personIds.length > 0) {
10815
+ const { data: people, error: peopleError } = await pub.from("people").select("id,name").in("id", personIds);
10816
+ if (peopleError) throw peopleError;
10817
+ for (const person of people ?? []) names.set(person.id, person.name);
10818
+ }
10819
+ return rows.map((row) => mapRecurringBill(row, names.get(row.counterparty_person_id) ?? null));
10820
+ },
10821
+ async saveRecurringBill(input) {
10822
+ const { pub } = getClients();
10823
+ const row = {
10824
+ direction: input.direction,
10825
+ description: input.description.trim(),
10826
+ counterparty_person_id: input.counterpartyPersonId,
10827
+ amount: round23(input.amount),
10828
+ items: input.items,
10829
+ unit_id: input.unitId || null,
10830
+ chart_account_id: input.chartAccountId || null,
10831
+ cost_center_id: input.costCenterId || null,
10832
+ frequency: input.frequency,
10833
+ due_day: input.dueDay,
10834
+ starts_on: input.startsOn,
10835
+ ends_on: input.endsOn || null,
10836
+ is_active: input.isActive ?? true,
10837
+ notes: input.notes?.trim() || null
10838
+ };
10839
+ const request = input.id ? pub.from(T.recurringBills).update(row).eq("id", input.id) : pub.from(T.recurringBills).insert({ ...row, tenant_id: getTenantId() });
10840
+ const { data, error } = await request.select("*").single();
10841
+ if (error) throw error;
10842
+ return mapRecurringBill(data);
10843
+ },
10844
+ async updateRecurringBill(id, patch) {
10845
+ const { pub } = getClients();
10846
+ const row = {};
10847
+ if (patch.isActive !== void 0) row.is_active = patch.isActive;
10848
+ if (patch.endsOn !== void 0) row.ends_on = patch.endsOn;
10849
+ const { error } = await pub.from(T.recurringBills).update(row).eq("id", id);
10850
+ if (error) throw error;
10851
+ },
9537
10852
  async runRecurring(through) {
9538
10853
  const { pub } = getClients();
9539
10854
  const { data, error } = await pub.rpc("financial_recurring_run", { p_through: through ?? null });
@@ -11277,11 +12592,11 @@ function buildFinancialOnboarding() {
11277
12592
  function CashSessionHeaderControl({ config, provider, store: store6 }) {
11278
12593
  const sessions = useStore(store6, (s) => s.cashSessions);
11279
12594
  const fetchCashSessions = useStore(store6, (s) => s.fetchCashSessions);
11280
- const [open, setOpen] = React15__default.useState(false);
11281
- React15__default.useEffect(() => {
12595
+ const [open, setOpen] = React20__default.useState(false);
12596
+ React20__default.useEffect(() => {
11282
12597
  void fetchCashSessions?.();
11283
12598
  }, [fetchCashSessions]);
11284
- React15__default.useEffect(() => {
12599
+ React20__default.useEffect(() => {
11285
12600
  if (!open) return;
11286
12601
  const close = () => setOpen(false);
11287
12602
  window.addEventListener("hashchange", close);
@@ -11449,10 +12764,12 @@ var en = {
11449
12764
  "financial.invoiceForm.addFirstItem": "Add your first item",
11450
12765
  "financial.invoiceForm.addItem": "Add Item",
11451
12766
  "financial.invoiceForm.addItemsFirst": "Add items first",
12767
+ "financial.invoiceForm.contactRequired": "Select who will pay or receive before adding items.",
11452
12768
  "financial.invoiceForm.additionalNotes": "Additional notes...",
11453
12769
  "financial.itemType.service": "Service",
11454
12770
  "financial.itemType.product": "Product",
11455
12771
  "financial.itemType.other": "Other",
12772
+ "financial.itemType.gift_card": "Gift Card",
11456
12773
  "financial.invoiceForm.cancel": "Cancel",
11457
12774
  "financial.invoiceForm.close": "Close",
11458
12775
  "financial.invoiceForm.creating": "Creating new invoice",
@@ -11479,8 +12796,10 @@ var en = {
11479
12796
  "financial.invoiceForm.save": "Save",
11480
12797
  "financial.invoiceForm.saving": "Saving...",
11481
12798
  "financial.invoiceForm.searchType": "Type to search...",
12799
+ "financial.invoiceForm.searchMinChars": "Type at least 2 characters to search.",
11482
12800
  "financial.invoiceForm.searchUnit": "Search unit...",
11483
12801
  "financial.invoiceForm.selectInstallments": "Select installment count above",
12802
+ "financial.invoiceForm.selectContactFirst": "Select who will pay or receive before adding items.",
11484
12803
  "financial.invoiceForm.selectItemType": "Select item type:",
11485
12804
  "financial.invoiceForm.selectUnit": "Select unit",
11486
12805
  "financial.invoiceForm.selectUnitTitle": "Select Unit",
@@ -11493,12 +12812,14 @@ var en = {
11493
12812
  "financial.nav.commissions": "Commissions",
11494
12813
  "financial.nav.list": "List",
11495
12814
  "financial.nav.new": "New",
12815
+ "financial.nav.recurring": "Recurring",
11496
12816
  "financial.nav.overview": "Overview",
11497
12817
  "financial.nav.payables": "Accounts Payable",
11498
12818
  "financial.nav.receivables": "Accounts Receivable",
11499
12819
  "financial.nav.reconciliation": "Reconciliation",
11500
12820
  "financial.nav.notasFiscais": "Fiscal Invoices",
11501
12821
  "financial.nav.recurringExpenses": "Recurring Expenses",
12822
+ "financial.nav.recurringIncome": "Recurring Income",
11502
12823
  "financial.nav.rules": "Rules",
11503
12824
  "financial.nav.statements": "Statements",
11504
12825
  "financial.nav.summary": "Summary",
@@ -11518,6 +12839,12 @@ var en = {
11518
12839
  "financial.invoice.filterFrom": "From",
11519
12840
  "financial.invoice.filterTo": "To",
11520
12841
  "financial.invoice.filterClear": "Clear",
12842
+ "financial.invoice.filterUnit": "Unit",
12843
+ "financial.invoice.filterAllUnits": "All units",
12844
+ "financial.invoice.generateCnab": "Generate CNAB 240",
12845
+ "financial.invoice.printSelected": "Print",
12846
+ "financial.invoice.exportSelected": "Export CSV",
12847
+ "financial.invoice.printBlocked": "Allow pop-ups to print the selection.",
11521
12848
  "financial.reconciliation.title": "Reconciliation",
11522
12849
  "financial.reconciliation.subtitle": "Match imported bank lines to your movements",
11523
12850
  "financial.reconciliation.pending": "{{count}} to reconcile",
@@ -11744,6 +13071,7 @@ var en = {
11744
13071
  "financial.invoice.columnDescription": "Description",
11745
13072
  "financial.invoice.columnAmount": "Amount",
11746
13073
  "financial.invoice.columnPaid": "Paid",
13074
+ "financial.invoice.columnPaymentMethod": "Method",
11747
13075
  "financial.invoice.columnStatus": "Status",
11748
13076
  "financial.invoice.columnInst": "Inst.",
11749
13077
  "financial.invoice.columnQty": "Qty",
@@ -12032,10 +13360,12 @@ var ptBR = {
12032
13360
  "financial.invoiceForm.addFirstItem": "Adicione seu primeiro item",
12033
13361
  "financial.invoiceForm.addItem": "Adicionar Item",
12034
13362
  "financial.invoiceForm.addItemsFirst": "Adicione itens primeiro",
13363
+ "financial.invoiceForm.contactRequired": "Selecione quem vai pagar ou receber antes de adicionar itens.",
12035
13364
  "financial.invoiceForm.additionalNotes": "Observa\xE7\xF5es adicionais...",
12036
13365
  "financial.itemType.service": "Servi\xE7o",
12037
13366
  "financial.itemType.product": "Produto",
12038
13367
  "financial.itemType.other": "Outro",
13368
+ "financial.itemType.gift_card": "Gift Card",
12039
13369
  "financial.invoiceForm.cancel": "Cancelar",
12040
13370
  "financial.invoiceForm.close": "Fechar",
12041
13371
  "financial.invoiceForm.creating": "Criando nova fatura",
@@ -12065,8 +13395,10 @@ var ptBR = {
12065
13395
  "financial.invoiceForm.save": "Salvar",
12066
13396
  "financial.invoiceForm.saving": "Salvando...",
12067
13397
  "financial.invoiceForm.searchType": "Digite para buscar...",
13398
+ "financial.invoiceForm.searchMinChars": "Digite pelo menos 2 caracteres para buscar.",
12068
13399
  "financial.invoiceForm.searchUnit": "Buscar unidade...",
12069
13400
  "financial.invoiceForm.selectInstallments": "Selecione a quantidade de parcelas acima",
13401
+ "financial.invoiceForm.selectContactFirst": "Selecione primeiro quem vai pagar ou receber para adicionar itens.",
12070
13402
  "financial.invoiceForm.selectItemType": "Selecione o tipo do item:",
12071
13403
  "financial.invoiceForm.selectUnit": "Selecionar unidade",
12072
13404
  "financial.invoiceForm.selectUnitTitle": "Selecionar Unidade",
@@ -12079,12 +13411,14 @@ var ptBR = {
12079
13411
  "financial.nav.commissions": "Comiss\xF5es",
12080
13412
  "financial.nav.list": "Lista",
12081
13413
  "financial.nav.new": "Novo",
13414
+ "financial.nav.recurring": "Recorrentes",
12082
13415
  "financial.nav.overview": "Vis\xE3o Geral",
12083
13416
  "financial.nav.payables": "Contas a Pagar",
12084
13417
  "financial.nav.receivables": "Contas a Receber",
12085
13418
  "financial.nav.reconciliation": "Concilia\xE7\xE3o",
12086
13419
  "financial.nav.notasFiscais": "Notas Fiscais",
12087
13420
  "financial.nav.recurringExpenses": "Despesas Recorrentes",
13421
+ "financial.nav.recurringIncome": "Receitas Recorrentes",
12088
13422
  "financial.nav.rules": "Regras",
12089
13423
  "financial.nav.statements": "Extratos",
12090
13424
  "financial.nav.summary": "Resumo",
@@ -12104,6 +13438,12 @@ var ptBR = {
12104
13438
  "financial.invoice.filterFrom": "De",
12105
13439
  "financial.invoice.filterTo": "At\xE9",
12106
13440
  "financial.invoice.filterClear": "Limpar",
13441
+ "financial.invoice.filterUnit": "Unidade",
13442
+ "financial.invoice.filterAllUnits": "Todas as unidades",
13443
+ "financial.invoice.generateCnab": "Gerar CNAB 240",
13444
+ "financial.invoice.printSelected": "Imprimir",
13445
+ "financial.invoice.exportSelected": "Exportar CSV",
13446
+ "financial.invoice.printBlocked": "Permita pop-ups para imprimir a sele\xE7\xE3o.",
12107
13447
  "financial.reconciliation.title": "Concilia\xE7\xE3o",
12108
13448
  "financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
12109
13449
  "financial.reconciliation.pending": "{{count}} para conciliar",
@@ -12330,6 +13670,7 @@ var ptBR = {
12330
13670
  "financial.invoice.columnDescription": "Descri\xE7\xE3o",
12331
13671
  "financial.invoice.columnAmount": "Valor",
12332
13672
  "financial.invoice.columnPaid": "Pago",
13673
+ "financial.invoice.columnPaymentMethod": "Forma",
12333
13674
  "financial.invoice.columnStatus": "Status",
12334
13675
  "financial.invoice.columnInst": "Parc.",
12335
13676
  "financial.invoice.columnQty": "Qtd",
@@ -12487,6 +13828,83 @@ var financialLocales = {
12487
13828
  en,
12488
13829
  "pt-BR": ptBR
12489
13830
  };
13831
+ function buildFinancialAnalyticsCards(config, provider) {
13832
+ const unitFilter = config.locations.length > 1 ? [{
13833
+ key: "unit_id",
13834
+ label: tl("Unit", "Unidade"),
13835
+ type: "select",
13836
+ group: tl("General", "Geral"),
13837
+ options: config.locations.map((unit) => ({ label: unit.name, value: unit.id }))
13838
+ }] : [];
13839
+ const dre = {
13840
+ id: "financial.dre",
13841
+ name: tl("Profit & Loss Statement", "DRE \u2014 Demonstra\xE7\xE3o do Resultado do Exerc\xEDcio"),
13842
+ description: tl(
13843
+ "Revenue, operating expenses and result by chart account and accrual month.",
13844
+ "Receitas, despesas operacionais e resultado por plano de contas e m\xEAs de compet\xEAncia."
13845
+ ),
13846
+ icon: "ChartNoAxesCombined",
13847
+ category: tl("Finance", "Financeiro"),
13848
+ domain: "financial",
13849
+ canonicalOwner: "public.plg_financial_rep_dre",
13850
+ grain: "chart_account_month",
13851
+ permission: { feature: "financial", action: "read" },
13852
+ query: {
13853
+ source: {
13854
+ kind: "fn",
13855
+ run: async (context) => {
13856
+ const fromYear = (/* @__PURE__ */ new Date(`${context.dateRange.from}T00:00:00`)).getFullYear();
13857
+ const toYear = (/* @__PURE__ */ new Date(`${context.dateRange.to}T00:00:00`)).getFullYear();
13858
+ const unitId = typeof context.filters.unit_id === "string" ? context.filters.unit_id : void 0;
13859
+ const years = Array.from({ length: Math.max(1, toYear - fromYear + 1) }, (_, index) => fromYear + index);
13860
+ const batches = await Promise.all(years.map((year) => provider.getDre({ year, unitId })));
13861
+ return batches.flat().map((row) => ({
13862
+ statement_code: row.statementCode,
13863
+ statement_name: row.statementName,
13864
+ competence_month: row.competenceMonth,
13865
+ account_type: row.accountType,
13866
+ unit_id: row.unitId ?? unitId ?? null,
13867
+ amount: row.amount
13868
+ }));
13869
+ }
13870
+ },
13871
+ dateColumn: "competence_month",
13872
+ dimensions: [
13873
+ { key: "statement_code", label: tl("Account", "Conta") },
13874
+ { key: "statement_name", label: tl("Description", "Descri\xE7\xE3o") },
13875
+ { key: "competence_month", label: tl("Accrual month", "Compet\xEAncia"), grain: "month" },
13876
+ { key: "account_type", label: tl("Type", "Tipo") }
13877
+ ],
13878
+ measures: [{ key: "amount", label: tl("Amount", "Valor"), agg: "sum", format: "currency" }],
13879
+ filters: unitFilter,
13880
+ defaultSort: "statement_code",
13881
+ defaultSortDir: "asc"
13882
+ },
13883
+ viz: {
13884
+ type: "statement",
13885
+ statement: {
13886
+ codeKey: "statement_code",
13887
+ labelKey: "statement_name",
13888
+ periodKey: "competence_month",
13889
+ valueKey: "amount",
13890
+ sectionKey: "account_type",
13891
+ sections: [
13892
+ { value: "revenue", label: tl("Revenue", "Receitas"), unclassifiedLabel: tl("Unclassified revenue", "Receitas sem classifica\xE7\xE3o") },
13893
+ { value: "expense", label: tl("Operating expenses", "Despesas operacionais"), unclassifiedLabel: tl("Unclassified expenses", "Despesas sem classifica\xE7\xE3o") }
13894
+ ],
13895
+ lines: [
13896
+ { kind: "section", section: "revenue" },
13897
+ { kind: "total", label: tl("= Net revenue", "= Receita l\xEDquida"), of: { revenue: 1 }, emphasis: "strong" },
13898
+ { kind: "section", section: "expense" },
13899
+ { kind: "total", label: tl("= Operating result", "= Resultado operacional"), of: { revenue: 1, expense: -1 }, emphasis: "rule" }
13900
+ ],
13901
+ defaultDepth: 1
13902
+ }
13903
+ }
13904
+ };
13905
+ dre.defaultRange = "this_year";
13906
+ return [dre];
13907
+ }
12490
13908
 
12491
13909
  // src/types.ts
12492
13910
  var UNENFORCED_CRITERIA = ["profession", "professional_group", "payment_method_type"];
@@ -12507,14 +13925,14 @@ function CommissionRuleDialog({
12507
13925
  onClose,
12508
13926
  onSaved
12509
13927
  }) {
12510
- const [name, setName] = React15__default.useState(rule?.name ?? "");
12511
- const [description, setDescription] = React15__default.useState(rule?.description ?? "");
12512
- const [priority, setPriority] = React15__default.useState(String(rule?.priority ?? 1));
12513
- const [isActive, setIsActive] = React15__default.useState(rule?.isActive ?? true);
12514
- const [isDefault, setIsDefault] = React15__default.useState(rule?.isDefault ?? !hasDefault);
12515
- const [saving, setSaving] = React15__default.useState(false);
12516
- const [error, setError] = React15__default.useState(null);
12517
- const initial = React15__default.useMemo(() => {
13928
+ const [name, setName] = React20__default.useState(rule?.name ?? "");
13929
+ const [description, setDescription] = React20__default.useState(rule?.description ?? "");
13930
+ const [priority, setPriority] = React20__default.useState(String(rule?.priority ?? 1));
13931
+ const [isActive, setIsActive] = React20__default.useState(rule?.isActive ?? true);
13932
+ const [isDefault, setIsDefault] = React20__default.useState(rule?.isDefault ?? !hasDefault);
13933
+ const [saving, setSaving] = React20__default.useState(false);
13934
+ const [error, setError] = React20__default.useState(null);
13935
+ const initial = React20__default.useMemo(() => {
12518
13936
  const get = (t2, key = "ids") => rule?.criteria.find((c) => c.criteriaType === t2)?.criteriaValue?.[key] ?? [];
12519
13937
  return {
12520
13938
  services: get("service"),
@@ -12525,12 +13943,12 @@ function CommissionRuleDialog({
12525
13943
  ranges: get("time_range", "ranges")
12526
13944
  };
12527
13945
  }, [rule]);
12528
- const [services, setServices] = React15__default.useState(initial.services);
12529
- const [categories, setCategories] = React15__default.useState(initial.categories);
12530
- const [professionals, setProfessionals] = React15__default.useState(initial.professionals);
12531
- const [units, setUnits] = React15__default.useState(initial.units);
12532
- const [weekdays, setWeekdays] = React15__default.useState(initial.weekdays);
12533
- const [ranges, setRanges] = React15__default.useState(initial.ranges);
13946
+ const [services, setServices] = React20__default.useState(initial.services);
13947
+ const [categories, setCategories] = React20__default.useState(initial.categories);
13948
+ const [professionals, setProfessionals] = React20__default.useState(initial.professionals);
13949
+ const [units, setUnits] = React20__default.useState(initial.units);
13950
+ const [weekdays, setWeekdays] = React20__default.useState(initial.weekdays);
13951
+ const [ranges, setRanges] = React20__default.useState(initial.ranges);
12534
13952
  const criteriaCount = [services, categories, professionals, units].filter((a) => a.length > 0).length + (weekdays.length ? 1 : 0) + (ranges.length ? 1 : 0);
12535
13953
  async function handleSave() {
12536
13954
  if (!name.trim()) {
@@ -12626,7 +14044,7 @@ function CommissionRuleDialog({
12626
14044
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Crit\xE9rio vazio vale para todos. Quanto mais crit\xE9rios casarem com o atendimento, mais espec\xEDfica \u2014 e mais forte \u2014 a regra." })
12627
14045
  ] }),
12628
14046
  /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2", children: [
12629
- /* @__PURE__ */ jsx(Field, { label: "Servi\xE7os", children: /* @__PURE__ */ jsx(
14047
+ /* @__PURE__ */ jsx(Field3, { label: "Servi\xE7os", children: /* @__PURE__ */ jsx(
12630
14048
  MultiSelect,
12631
14049
  {
12632
14050
  allLabel: "Todos os servi\xE7os",
@@ -12635,7 +14053,7 @@ function CommissionRuleDialog({
12635
14053
  options: catalogs.services.map((s) => ({ value: s.id, label: s.name }))
12636
14054
  }
12637
14055
  ) }),
12638
- /* @__PURE__ */ jsx(Field, { label: "Grupos de servi\xE7o", children: /* @__PURE__ */ jsx(
14056
+ /* @__PURE__ */ jsx(Field3, { label: "Grupos de servi\xE7o", children: /* @__PURE__ */ jsx(
12639
14057
  MultiSelect,
12640
14058
  {
12641
14059
  allLabel: "Todos os grupos",
@@ -12644,7 +14062,7 @@ function CommissionRuleDialog({
12644
14062
  options: catalogs.categories.map((c) => ({ value: c.id, label: c.name }))
12645
14063
  }
12646
14064
  ) }),
12647
- /* @__PURE__ */ jsx(Field, { label: "Profissionais", children: /* @__PURE__ */ jsx(
14065
+ /* @__PURE__ */ jsx(Field3, { label: "Profissionais", children: /* @__PURE__ */ jsx(
12648
14066
  MultiSelect,
12649
14067
  {
12650
14068
  allLabel: "Todos os profissionais",
@@ -12653,7 +14071,7 @@ function CommissionRuleDialog({
12653
14071
  options: catalogs.professionals.map((p) => ({ value: p.id, label: p.name }))
12654
14072
  }
12655
14073
  ) }),
12656
- /* @__PURE__ */ jsx(Field, { label: "Unidades", children: /* @__PURE__ */ jsx(
14074
+ /* @__PURE__ */ jsx(Field3, { label: "Unidades", children: /* @__PURE__ */ jsx(
12657
14075
  MultiSelect,
12658
14076
  {
12659
14077
  allLabel: "Todas as unidades",
@@ -12663,7 +14081,7 @@ function CommissionRuleDialog({
12663
14081
  }
12664
14082
  ) })
12665
14083
  ] }),
12666
- /* @__PURE__ */ jsx(Field, { label: "Dias da semana", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3 rounded-input border border-input bg-card px-3 py-2", children: WEEKDAYS.map((d) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-xs", children: [
14084
+ /* @__PURE__ */ jsx(Field3, { label: "Dias da semana", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3 rounded-input border border-input bg-card px-3 py-2", children: WEEKDAYS.map((d) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-xs", children: [
12667
14085
  /* @__PURE__ */ jsx(
12668
14086
  Checkbox,
12669
14087
  {
@@ -12673,7 +14091,7 @@ function CommissionRuleDialog({
12673
14091
  ),
12674
14092
  d.label
12675
14093
  ] }, d.value)) }) }),
12676
- /* @__PURE__ */ jsx(Field, { label: "Faixas de hor\xE1rio", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-input border border-input bg-card px-3 py-2", children: [
14094
+ /* @__PURE__ */ jsx(Field3, { label: "Faixas de hor\xE1rio", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-input border border-input bg-card px-3 py-2", children: [
12677
14095
  ranges.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "Sem faixa, a regra vale em qualquer hor\xE1rio." }),
12678
14096
  ranges.map((r, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
12679
14097
  /* @__PURE__ */ jsx(
@@ -12735,7 +14153,7 @@ var LABEL = {
12735
14153
  professional_group: "Grupos de Profissionais",
12736
14154
  payment_method_type: "Formas de Pagamento"
12737
14155
  };
12738
- function Field({ label, children }) {
14156
+ function Field3({ label, children }) {
12739
14157
  return /* @__PURE__ */ jsxs("div", { children: [
12740
14158
  /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: label }),
12741
14159
  /* @__PURE__ */ jsx("div", { className: "mt-0.5", children })
@@ -12773,11 +14191,11 @@ function blankFunction(order) {
12773
14191
  };
12774
14192
  }
12775
14193
  function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
12776
- const [functions, setFunctions] = React15__default.useState(
14194
+ const [functions, setFunctions] = React20__default.useState(
12777
14195
  rule.functions.length ? rule.functions : [blankFunction(0)]
12778
14196
  );
12779
- const [saving, setSaving] = React15__default.useState(false);
12780
- const [error, setError] = React15__default.useState(null);
14197
+ const [saving, setSaving] = React20__default.useState(false);
14198
+ const [error, setError] = React20__default.useState(null);
12781
14199
  function patch(i, changes) {
12782
14200
  setFunctions((fs) => fs.map((f, j) => j === i ? { ...f, ...changes } : f));
12783
14201
  }
@@ -12806,7 +14224,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
12806
14224
  /* @__PURE__ */ jsx(ModalBody, { className: "fz-container px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
12807
14225
  functions.map((fn, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card p-3", children: [
12808
14226
  /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-3", children: [
12809
- /* @__PURE__ */ jsx(Field2, { label: "Fun\xE7\xE3o", children: /* @__PURE__ */ jsx(
14227
+ /* @__PURE__ */ jsx(Field4, { label: "Fun\xE7\xE3o", children: /* @__PURE__ */ jsx(
12810
14228
  Input,
12811
14229
  {
12812
14230
  value: fn.functionName,
@@ -12815,7 +14233,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
12815
14233
  onChange: (e) => patch(i, { functionName: e.target.value })
12816
14234
  }
12817
14235
  ) }),
12818
- /* @__PURE__ */ jsx(Field2, { label: "Conta padr\xE3o", children: /* @__PURE__ */ jsx(
14236
+ /* @__PURE__ */ jsx(Field4, { label: "Conta padr\xE3o", children: /* @__PURE__ */ jsx(
12819
14237
  NativeSelect,
12820
14238
  {
12821
14239
  value: fn.accountType,
@@ -12823,7 +14241,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
12823
14241
  options: ACCOUNT_TYPES
12824
14242
  }
12825
14243
  ) }),
12826
- /* @__PURE__ */ jsx(Field2, { label: "Sobre", children: /* @__PURE__ */ jsx(
14244
+ /* @__PURE__ */ jsx(Field4, { label: "Sobre", children: /* @__PURE__ */ jsx(
12827
14245
  NativeSelect,
12828
14246
  {
12829
14247
  value: fn.calculationBase,
@@ -12833,7 +14251,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
12833
14251
  ) })
12834
14252
  ] }),
12835
14253
  /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 mt-3", children: [
12836
- /* @__PURE__ */ jsx(Field2, { label: "Faixa de produ\xE7\xE3o", children: /* @__PURE__ */ jsx(
14254
+ /* @__PURE__ */ jsx(Field4, { label: "Faixa de produ\xE7\xE3o", children: /* @__PURE__ */ jsx(
12837
14255
  NativeSelect,
12838
14256
  {
12839
14257
  value: fn.tierMode,
@@ -12841,7 +14259,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
12841
14259
  options: TIER_MODES
12842
14260
  }
12843
14261
  ) }),
12844
- (fn.tierMode === "none" || fn.tierMode === "guaranteed_min") && /* @__PURE__ */ jsx(Field2, { label: "Valor", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
14262
+ (fn.tierMode === "none" || fn.tierMode === "guaranteed_min") && /* @__PURE__ */ jsx(Field4, { label: "Valor", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
12845
14263
  /* @__PURE__ */ jsx(
12846
14264
  Input,
12847
14265
  {
@@ -12862,7 +14280,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
12862
14280
  )
12863
14281
  ] }) })
12864
14282
  ] }),
12865
- fn.tierMode === "guaranteed_min" && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs(Field2, { label: "Piso mensal garantido (R$)", children: [
14283
+ fn.tierMode === "guaranteed_min" && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs(Field4, { label: "Piso mensal garantido (R$)", children: [
12866
14284
  /* @__PURE__ */ jsx(
12867
14285
  Input,
12868
14286
  {
@@ -12991,7 +14409,7 @@ function TierTable({ tiers, onChange }) {
12991
14409
  /* @__PURE__ */ jsx("p", { className: "mt-2 text-[10px] text-muted-foreground", children: "Ex.: 0\u20131.800 fixo R$ 180; acima de 1.800 \u2192 20% s\xF3 do que passar de 1.800." })
12992
14410
  ] });
12993
14411
  }
12994
- function Field2({ label, children }) {
14412
+ function Field4({ label, children }) {
12995
14413
  return /* @__PURE__ */ jsxs("div", { children: [
12996
14414
  /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: label }),
12997
14415
  /* @__PURE__ */ jsx("div", { className: "mt-0.5", children })
@@ -13061,19 +14479,19 @@ function functionChip(fn) {
13061
14479
  return fn.valueType === "percentage" ? `${who}: ${fn.valueAmount}%` : `${who}: R$ ${fn.valueAmount.toFixed(2)}`;
13062
14480
  }
13063
14481
  function CommissionRulesView({ provider }) {
13064
- const [rules, setRules] = React15__default.useState(null);
13065
- const [catalogs, setCatalogs] = React15__default.useState({
14482
+ const [rules, setRules] = React20__default.useState(null);
14483
+ const [catalogs, setCatalogs] = React20__default.useState({
13066
14484
  services: [],
13067
14485
  categories: [],
13068
14486
  professionals: [],
13069
14487
  units: []
13070
14488
  });
13071
- const [error, setError] = React15__default.useState(null);
13072
- const [funnel, setFunnel] = React15__default.useState({});
13073
- const [editing, setEditing] = React15__default.useState(null);
13074
- const [functionsFor, setFunctionsFor] = React15__default.useState(null);
13075
- const [deleting, setDeleting] = React15__default.useState(null);
13076
- const load = React15__default.useCallback(async () => {
14489
+ const [error, setError] = React20__default.useState(null);
14490
+ const [funnel, setFunnel] = React20__default.useState({});
14491
+ const [editing, setEditing] = React20__default.useState(null);
14492
+ const [functionsFor, setFunctionsFor] = React20__default.useState(null);
14493
+ const [deleting, setDeleting] = React20__default.useState(null);
14494
+ const load = React20__default.useCallback(async () => {
13077
14495
  try {
13078
14496
  setError(null);
13079
14497
  const [r, c] = await Promise.all([
@@ -13087,17 +14505,17 @@ function CommissionRulesView({ provider }) {
13087
14505
  setRules([]);
13088
14506
  }
13089
14507
  }, [provider]);
13090
- React15__default.useEffect(() => {
14508
+ React20__default.useEffect(() => {
13091
14509
  void load();
13092
14510
  }, [load]);
13093
14511
  const funnelActive = Object.values(funnel).some(Boolean);
13094
- const visible = React15__default.useMemo(() => {
14512
+ const visible = React20__default.useMemo(() => {
13095
14513
  if (!rules) return [];
13096
14514
  const ranked = rankRules(rules);
13097
14515
  if (!funnelActive) return ranked;
13098
14516
  return ranked.filter((r) => r.isDefault || ruleMatches(r, funnel, catalogs));
13099
14517
  }, [rules, funnel, funnelActive, catalogs]);
13100
- const winner = React15__default.useMemo(() => {
14518
+ const winner = React20__default.useMemo(() => {
13101
14519
  if (!funnelActive) return null;
13102
14520
  return visible.find((r) => r.isActive && !r.isDefault) ?? visible.find((r) => r.isActive && r.isDefault) ?? null;
13103
14521
  }, [visible, funnelActive]);
@@ -13112,7 +14530,7 @@ function CommissionRulesView({ provider }) {
13112
14530
  await provider.duplicateCommissionRule?.(rule.id);
13113
14531
  await load();
13114
14532
  }
13115
- const headerActions = React15__default.useMemo(() => [{
14533
+ const headerActions = React20__default.useMemo(() => [{
13116
14534
  id: "new-rule",
13117
14535
  label: "Adicionar regra",
13118
14536
  icon: Plus,
@@ -13893,6 +15311,100 @@ function PersonStatementWidget({ person, provider, config }) {
13893
15311
  ] }) : /* @__PURE__ */ jsx(DataTable, { columns, data: invoices, compact: true })
13894
15312
  ] });
13895
15313
  }
15314
+ function SupplierFinancialSettings({ person, provider }) {
15315
+ const initial = React20__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
15316
+ const [accounts, setAccounts] = useState([]);
15317
+ const [costCenters, setCostCenters] = useState([]);
15318
+ const [defaultAccountId, setDefaultAccountId] = useState(initial.defaultAccountId ?? "");
15319
+ const [allowedAccountIds, setAllowedAccountIds] = useState(initial.allowedAccountIds);
15320
+ const [defaultCostCenterId, setDefaultCostCenterId] = useState(initial.defaultCostCenterId ?? "");
15321
+ const [loading, setLoading] = useState(true);
15322
+ const [saving, setSaving] = useState(false);
15323
+ useEffect(() => {
15324
+ let alive = true;
15325
+ Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([nextAccounts, nextCenters]) => {
15326
+ if (!alive) return;
15327
+ setAccounts(postableAccounts(nextAccounts));
15328
+ setCostCenters(nextCenters);
15329
+ setLoading(false);
15330
+ }).catch(() => {
15331
+ if (alive) setLoading(false);
15332
+ });
15333
+ return () => {
15334
+ alive = false;
15335
+ };
15336
+ }, [provider]);
15337
+ async function save() {
15338
+ const client = getSupabaseClientOptional();
15339
+ if (!client || !person.id || saving) return;
15340
+ setSaving(true);
15341
+ try {
15342
+ const metadata = person.metadata && typeof person.metadata === "object" ? person.metadata : {};
15343
+ const previous = metadata.financial && typeof metadata.financial === "object" ? metadata.financial : {};
15344
+ const financial = {
15345
+ ...previous,
15346
+ default_chart_account_id: defaultAccountId || null,
15347
+ allowed_chart_accounts: allowedAccountIds,
15348
+ limit_chart_accounts: allowedAccountIds.length > 0,
15349
+ default_cost_center_id: defaultCostCenterId || null
15350
+ };
15351
+ const { error } = await client.from("people").update({ metadata: { ...metadata, financial } }).eq("id", person.id);
15352
+ if (error) throw error;
15353
+ person.metadata = { ...metadata, financial };
15354
+ toast.success("Configura\xE7\xF5es financeiras salvas.");
15355
+ } catch (error) {
15356
+ toast.error("N\xE3o foi poss\xEDvel salvar as configura\xE7\xF5es financeiras.", { description: error?.message });
15357
+ } finally {
15358
+ setSaving(false);
15359
+ }
15360
+ }
15361
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4 py-5", children: [
15362
+ /* @__PURE__ */ jsxs("div", { children: [
15363
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Configura\xE7\xF5es do fornecedor" }),
15364
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Estes padr\xF5es entram nos itens de novas contas a pagar, inclusive recorrentes." })
15365
+ ] }),
15366
+ loading ? /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
15367
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9" }),
15368
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9" })
15369
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
15370
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
15371
+ /* @__PURE__ */ jsx("span", { children: "Plano de contas padr\xE3o" }),
15372
+ /* @__PURE__ */ jsxs(Select, { value: defaultAccountId || "__none__", onValueChange: (value) => setDefaultAccountId(value === "__none__" ? "" : value), children: [
15373
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
15374
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
15375
+ /* @__PURE__ */ jsx(SelectItem, { value: "__none__", children: "Sem plano padr\xE3o" }),
15376
+ accounts.map((account) => /* @__PURE__ */ jsx(SelectItem, { value: account.id, children: accountLabel(account) }, account.id))
15377
+ ] })
15378
+ ] })
15379
+ ] }),
15380
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground", children: [
15381
+ /* @__PURE__ */ jsx("span", { children: "Centro de custo padr\xE3o" }),
15382
+ /* @__PURE__ */ jsxs(Select, { value: defaultCostCenterId || "__none__", onValueChange: (value) => setDefaultCostCenterId(value === "__none__" ? "" : value), children: [
15383
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
15384
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
15385
+ /* @__PURE__ */ jsx(SelectItem, { value: "__none__", children: "Sem centro de custo padr\xE3o" }),
15386
+ costCenters.map((center) => /* @__PURE__ */ jsx(SelectItem, { value: center.id, children: center.name }, center.id))
15387
+ ] })
15388
+ ] })
15389
+ ] }),
15390
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1 text-xs font-medium text-muted-foreground md:col-span-2", children: [
15391
+ /* @__PURE__ */ jsx("span", { children: "Planos de contas permitidos" }),
15392
+ /* @__PURE__ */ jsx(
15393
+ MultiSelect,
15394
+ {
15395
+ options: accounts.map((account) => ({ value: account.id, label: accountLabel(account) })),
15396
+ value: allowedAccountIds,
15397
+ onChange: setAllowedAccountIds,
15398
+ allLabel: "Todos os planos de contas",
15399
+ searchPlaceholder: "Buscar plano de contas"
15400
+ }
15401
+ ),
15402
+ /* @__PURE__ */ jsx("span", { className: "block font-normal", children: "Vazio permite todos; uma sele\xE7\xE3o limita o que aparece nos itens desse fornecedor." })
15403
+ ] })
15404
+ ] }),
15405
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => void save(), disabled: loading || saving, children: saving ? "Salvando\u2026" : "Salvar configura\xE7\xF5es" }) })
15406
+ ] }) });
15407
+ }
13896
15408
  function Stat({ label, value, accent, positive }) {
13897
15409
  return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border px-3 py-2 ${accent ? "bg-muted/40" : "bg-card"}`, children: [
13898
15410
  /* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-muted-foreground uppercase truncate", children: label }),
@@ -33432,6 +34944,267 @@ BEGIN
33432
34944
  END IF;
33433
34945
  END $$;
33434
34946
  `;
34947
+ var MIGRATION_089_O_FORNECEDOR_APONTA_PARA_O_PLANO_MIGRADO = `-- A migrated supplier points to the current chart of accounts.
34948
+ --
34949
+ -- V1 stored supplier defaults and allowlists with legacy numeric IDs. The
34950
+ -- migration deliberately preserved those values in people.metadata because
34951
+ -- contacts can arrive before chart accounts. The invoice form cannot compare
34952
+ -- those values with the UUID primary keys used by V2, so the policy appeared
34953
+ -- configured but never matched an option.
34954
+ --
34955
+ -- This read-only resolver crosses that boundary through migration.ledger. It
34956
+ -- accepts current UUIDs too, keeping the runtime contract useful after the
34957
+ -- migration is over. Tenant and permission checks stay inside the definer.
34958
+
34959
+ CREATE OR REPLACE FUNCTION public.financial_resolve_chart_account_refs(p_refs text[])
34960
+ RETURNS TABLE(source_ref text, chart_account_id uuid)
34961
+ LANGUAGE sql
34962
+ STABLE
34963
+ SECURITY DEFINER
34964
+ SET search_path = ''
34965
+ AS $$
34966
+ WITH wanted AS (
34967
+ SELECT DISTINCT btrim(ref) AS ref
34968
+ FROM unnest(coalesce(p_refs, ARRAY[]::text[])) AS ref
34969
+ WHERE nullif(btrim(ref), '') IS NOT NULL
34970
+ ),
34971
+ candidates AS (
34972
+ SELECT w.ref AS source_ref, c.id AS chart_account_id, 0 AS priority
34973
+ FROM wanted w
34974
+ JOIN public.plg_financial_chart_of_accounts c
34975
+ ON c.tenant_id = app.current_tenant_id()
34976
+ AND c.id::text = w.ref
34977
+ UNION ALL
34978
+ SELECT w.ref AS source_ref, l.target_id AS chart_account_id, 1 AS priority
34979
+ FROM wanted w
34980
+ JOIN migration.ledger l
34981
+ ON l.tenant_id = app.current_tenant_id()
34982
+ AND l.source_table = 'chart_of_accounts'
34983
+ AND l.target_table = 'public.plg_financial_chart_of_accounts'
34984
+ AND (l.source_numeric_id::text = w.ref OR l.source_uuid::text = w.ref)
34985
+ JOIN public.plg_financial_chart_of_accounts c
34986
+ ON c.tenant_id = l.tenant_id
34987
+ AND c.id = l.target_id
34988
+ )
34989
+ SELECT DISTINCT ON (c.source_ref) c.source_ref, c.chart_account_id
34990
+ FROM candidates c
34991
+ WHERE auth.role() = 'service_role'
34992
+ OR app.has_permission('financial.read', NULL::uuid)
34993
+ ORDER BY c.source_ref, c.priority;
34994
+ $$;
34995
+
34996
+ REVOKE ALL ON FUNCTION public.financial_resolve_chart_account_refs(text[]) FROM PUBLIC, anon;
34997
+ GRANT EXECUTE ON FUNCTION public.financial_resolve_chart_account_refs(text[]) TO authenticated, service_role;
34998
+
34999
+ COMMENT ON FUNCTION public.financial_resolve_chart_account_refs(text[]) IS
35000
+ 'Resolves current UUIDs and migrated V1 chart-account references for the active tenant.';
35001
+ `;
35002
+ var MIGRATION_090_O_DRE_RECORTA_ANTES_DE_SOMAR = `-- Filter the income statement before aggregating it.
35003
+ --
35004
+ -- The historical view aggregates every fiscal year before PostgREST applies
35005
+ -- the requested range. On large tenants that exceeds the authenticated role
35006
+ -- timeout. This function accepts the range, resolves tenant/unit/permission
35007
+ -- once, and only then joins line items. It is additive and retains the view as
35008
+ -- the analytics contract for existing consumers.
35009
+
35010
+ CREATE OR REPLACE FUNCTION public.financial_dre(
35011
+ p_from date,
35012
+ p_to date,
35013
+ p_unit_id uuid DEFAULT NULL
35014
+ ) RETURNS TABLE (
35015
+ unit_id uuid,
35016
+ competence_month date,
35017
+ account_id uuid,
35018
+ account_code text,
35019
+ account_name text,
35020
+ account_type text,
35021
+ account_parent_id uuid,
35022
+ statement_code text,
35023
+ statement_name text,
35024
+ unclassified boolean,
35025
+ line_count bigint,
35026
+ amount numeric,
35027
+ discount numeric
35028
+ )
35029
+ LANGUAGE sql
35030
+ STABLE
35031
+ SECURITY DEFINER
35032
+ SET search_path TO ''
35033
+ AS $function$
35034
+ WITH ctx AS MATERIALIZED (
35035
+ SELECT app.current_tenant_id() AS tenant,
35036
+ app.visible_units() AS units,
35037
+ app.has_permission('financial.read', NULL::uuid) AS read_null,
35038
+ p_from AS de,
35039
+ p_to AS ate,
35040
+ p_unit_id AS unidade
35041
+ ),
35042
+ u_read AS MATERIALIZED (
35043
+ SELECT u
35044
+ FROM ctx, unnest(ctx.units) AS u
35045
+ WHERE app.has_permission('financial.read', u)
35046
+ ),
35047
+ inv AS MATERIALIZED (
35048
+ SELECT f.*
35049
+ FROM public.plg_financial_invoices f, ctx
35050
+ WHERE f.tenant_id = ctx.tenant
35051
+ AND f.cancelled_at IS NULL
35052
+ AND f.competence_date >= ctx.de
35053
+ AND f.competence_date <= ctx.ate
35054
+ AND (CASE WHEN ctx.unidade IS NOT NULL THEN f.unit_id = ctx.unidade
35055
+ WHEN f.unit_id IS NULL THEN ctx.read_null
35056
+ ELSE f.unit_id IN (SELECT u FROM u_read) END)
35057
+ ),
35058
+ line AS (
35059
+ SELECT f.tenant_id, f.unit_id, f.competence_date, f.direction,
35060
+ i.id AS item_id, i.total, i.discount,
35061
+ coalesce(x.chart_account_id, i.chart_account_id) AS chart_account_id
35062
+ FROM inv f
35063
+ JOIN public.items i ON i.invoice_id = f.id
35064
+ LEFT JOIN public.plg_financial_invoice_items x ON x.item_id = i.id
35065
+ WHERE i.cancelled_at IS NULL
35066
+ UNION ALL
35067
+ SELECT f.tenant_id, f.unit_id, f.competence_date, f.direction,
35068
+ i.id AS item_id, i.total, i.discount,
35069
+ coalesce(x.chart_account_id, i.chart_account_id) AS chart_account_id
35070
+ FROM inv f
35071
+ JOIN public.items i ON i.order_id = f.order_id
35072
+ LEFT JOIN public.plg_financial_invoice_items x ON x.item_id = i.id
35073
+ WHERE f.order_id IS NOT NULL
35074
+ AND i.invoice_id IS NULL
35075
+ AND i.cancelled_at IS NULL
35076
+ AND NOT EXISTS (SELECT 1 FROM public.items own WHERE own.invoice_id = f.id)
35077
+ ),
35078
+ structure AS (
35079
+ SELECT NULL::uuid AS unit_id, NULL::date AS competence_month,
35080
+ c.id AS account_id, c.code AS account_code, c.name AS account_name,
35081
+ c.account_type, c.parent_id AS account_parent_id,
35082
+ c.code AS statement_code, c.name AS statement_name,
35083
+ false AS unclassified, 0::bigint AS line_count,
35084
+ 0::numeric AS amount, 0::numeric AS discount
35085
+ FROM public.plg_financial_chart_of_accounts c, ctx
35086
+ WHERE c.tenant_id = ctx.tenant
35087
+ AND c.is_active
35088
+ AND c.account_type IN ('revenue', 'expense')
35089
+ AND (ctx.read_null OR EXISTS (SELECT 1 FROM u_read))
35090
+ ),
35091
+ movement AS (
35092
+ SELECT l.unit_id,
35093
+ date_trunc('month', l.competence_date)::date AS competence_month,
35094
+ c.id AS account_id, c.code AS account_code, c.name AS account_name,
35095
+ CASE WHEN l.direction = 'receivable' THEN 'revenue' ELSE 'expense' END AS account_type,
35096
+ c.parent_id AS account_parent_id,
35097
+ CASE WHEN (l.direction = 'receivable' AND c.account_type = 'revenue')
35098
+ OR (l.direction = 'payable' AND c.account_type = 'expense')
35099
+ THEN c.code END AS statement_code,
35100
+ CASE WHEN (l.direction = 'receivable' AND c.account_type = 'revenue')
35101
+ OR (l.direction = 'payable' AND c.account_type = 'expense')
35102
+ THEN c.name END AS statement_name,
35103
+ (c.id IS NULL OR NOT ((l.direction = 'receivable' AND c.account_type = 'revenue')
35104
+ OR (l.direction = 'payable' AND c.account_type = 'expense'))) AS unclassified,
35105
+ count(*) AS line_count,
35106
+ sum(l.total) AS amount,
35107
+ sum(l.discount) AS discount
35108
+ FROM line l
35109
+ LEFT JOIN public.plg_financial_chart_of_accounts c
35110
+ ON c.tenant_id = l.tenant_id AND c.id = l.chart_account_id
35111
+ GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
35112
+ )
35113
+ SELECT * FROM structure
35114
+ UNION ALL
35115
+ SELECT * FROM movement;
35116
+ $function$;
35117
+
35118
+ REVOKE ALL ON FUNCTION public.financial_dre(date, date, uuid) FROM PUBLIC, anon;
35119
+ GRANT EXECUTE ON FUNCTION public.financial_dre(date, date, uuid) TO authenticated, service_role;
35120
+
35121
+ COMMENT ON FUNCTION public.financial_dre(date, date, uuid) IS
35122
+ 'Annual/monthly income statement filtered before aggregation with tenant, unit, and financial.read fences.';
35123
+ `;
35124
+ var MIGRATION_091_A_RECORRENCIA_REPETE_OS_ITENS = `-- A recurring rule is a complete invoice template. When an occurrence creates
35125
+ -- its invoice, copy every template line with its own accounting dimensions.
35126
+
35127
+ CREATE OR REPLACE FUNCTION public.plg_financial_copy_recurring_items()
35128
+ RETURNS trigger
35129
+ LANGUAGE plpgsql
35130
+ SECURITY DEFINER
35131
+ SET search_path TO ''
35132
+ AS $$
35133
+ DECLARE
35134
+ v_bill public.plg_financial_recurring_bills%ROWTYPE;
35135
+ v_row jsonb;
35136
+ v_item_id uuid;
35137
+ v_quantity numeric;
35138
+ v_unit_price numeric;
35139
+ v_discount numeric;
35140
+ v_surcharge numeric;
35141
+ v_sort integer := 0;
35142
+ BEGIN
35143
+ IF coalesce(NEW.metadata ->> 'source', '') <> 'financial_recurring_run'
35144
+ OR coalesce(NEW.metadata ->> 'recurring_bill_id', '') = '' THEN
35145
+ RETURN NEW;
35146
+ END IF;
35147
+
35148
+ SELECT * INTO v_bill
35149
+ FROM public.plg_financial_recurring_bills
35150
+ WHERE id = (NEW.metadata ->> 'recurring_bill_id')::uuid
35151
+ AND tenant_id = NEW.tenant_id;
35152
+
35153
+ IF NOT FOUND OR jsonb_typeof(v_bill.items) <> 'array' THEN
35154
+ RETURN NEW;
35155
+ END IF;
35156
+
35157
+ FOR v_row IN SELECT value FROM jsonb_array_elements(v_bill.items) LOOP
35158
+ v_sort := v_sort + 1;
35159
+ v_quantity := greatest(coalesce((v_row ->> 'quantity')::numeric, 1), 0.0001);
35160
+ v_unit_price := coalesce((v_row ->> 'unitPrice')::numeric, (v_row ->> 'unit_price')::numeric, 0);
35161
+ v_discount := coalesce((v_row ->> 'discount')::numeric, 0);
35162
+ v_surcharge := coalesce((v_row ->> 'surcharge')::numeric, (v_row ->> 'addition')::numeric, 0);
35163
+
35164
+ INSERT INTO public.items
35165
+ (tenant_id, unit_id, invoice_id, product_id, name, description, quantity,
35166
+ unit_price, discount, total, sort_order, metadata, created_by)
35167
+ VALUES
35168
+ (NEW.tenant_id, NEW.unit_id, NEW.id,
35169
+ CASE WHEN coalesce(v_row ->> 'referenceId', v_row ->> 'reference_id', v_row ->> 'product_id', '')
35170
+ ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
35171
+ THEN coalesce(v_row ->> 'referenceId', v_row ->> 'reference_id', v_row ->> 'product_id')::uuid END,
35172
+ nullif(v_row ->> 'description', ''), nullif(v_row ->> 'description', ''),
35173
+ v_quantity, v_unit_price, v_discount,
35174
+ round(v_quantity * v_unit_price - v_discount + v_surcharge, 2), v_sort,
35175
+ jsonb_strip_nulls(jsonb_build_object(
35176
+ 'item_kind', coalesce(v_row ->> 'itemKind', v_row ->> 'item_kind', 'other'),
35177
+ 'financial', jsonb_build_object('additional', v_surcharge)
35178
+ )), NEW.created_by)
35179
+ RETURNING id INTO v_item_id;
35180
+
35181
+ INSERT INTO public.plg_financial_invoice_items
35182
+ (item_id, tenant_id, unit_id, chart_account_id, cost_center_id, owner_id)
35183
+ VALUES
35184
+ (v_item_id, NEW.tenant_id, NEW.unit_id,
35185
+ CASE WHEN coalesce(v_row ->> 'accountId', v_row ->> 'account_id', v_row ->> 'chart_account_id', '')
35186
+ ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
35187
+ THEN coalesce(v_row ->> 'accountId', v_row ->> 'account_id', v_row ->> 'chart_account_id')::uuid END,
35188
+ CASE WHEN coalesce(v_row ->> 'costCenterId', v_row ->> 'cost_center_id', '')
35189
+ ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
35190
+ THEN coalesce(v_row ->> 'costCenterId', v_row ->> 'cost_center_id')::uuid END,
35191
+ NEW.created_by);
35192
+ END LOOP;
35193
+
35194
+ RETURN NEW;
35195
+ END $$;
35196
+
35197
+ DROP TRIGGER IF EXISTS plg_financial_recurring_invoice_items ON public.plg_financial_invoices;
35198
+ CREATE TRIGGER plg_financial_recurring_invoice_items
35199
+ AFTER INSERT ON public.plg_financial_invoices
35200
+ FOR EACH ROW EXECUTE FUNCTION public.plg_financial_copy_recurring_items();
35201
+
35202
+ REVOKE ALL ON FUNCTION public.plg_financial_copy_recurring_items() FROM PUBLIC, anon, authenticated;
35203
+ GRANT EXECUTE ON FUNCTION public.plg_financial_copy_recurring_items() TO service_role;
35204
+
35205
+ COMMENT ON FUNCTION public.plg_financial_copy_recurring_items() IS
35206
+ 'Copies every item of a recurring rule into each generated invoice, preserving per-line accounting.';
35207
+ `;
33435
35208
  var MIGRATIONS = [
33436
35209
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
33437
35210
  { id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
@@ -33521,7 +35294,10 @@ var MIGRATIONS = [
33521
35294
  { id: "085_o_resumo_le_cinco_mil_linhas_e_nao_duzentas_e_oitenta_mil", sql: MIGRATION_085_O_RESUMO_LE_CINCO_MIL_LINHAS_E_NAO_DUZENTAS_E_OITENTA_MIL },
33522
35295
  { id: "086_a_cerca_se_pergunta_uma_vez_e_nao_uma_vez_por_linha", sql: MIGRATION_086_A_CERCA_SE_PERGUNTA_UMA_VEZ_E_NAO_UMA_VEZ_POR_LINHA },
33523
35296
  { id: "087_o_saldo_de_uma_fatura_nao_soma_o_razao_inteiro", sql: MIGRATION_087_O_SALDO_DE_UMA_FATURA_NAO_SOMA_O_RAZAO_INTEIRO },
33524
- { 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 }
35297
+ { 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
+ { id: "089_o_fornecedor_aponta_para_o_plano_migrado", sql: MIGRATION_089_O_FORNECEDOR_APONTA_PARA_O_PLANO_MIGRADO },
35299
+ { 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 }
33525
35301
  ];
33526
35302
  var STANDALONE_CONFIG = {
33527
35303
  currency: { code: "BRL", locale: "pt-BR", symbol: "R$" }
@@ -33533,8 +35309,8 @@ function ReceivePaymentModal({
33533
35309
  onClose,
33534
35310
  onPaid
33535
35311
  }) {
33536
- const provider = React15__default.useMemo(() => createSafeFinancialProvider(), []);
33537
- const store6 = React15__default.useMemo(() => createFinancialStore(provider), [provider]);
35312
+ const provider = React20__default.useMemo(() => createSafeFinancialProvider(), []);
35313
+ const store6 = React20__default.useMemo(() => createFinancialStore(provider), [provider]);
33538
35314
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
33539
35315
  PaymentModal,
33540
35316
  {
@@ -33552,7 +35328,7 @@ function createFinancialSettingsPlugin(options) {
33552
35328
  ...financialRegistries.filter((r) => RECEIVING_REGISTRIES.includes(r.id)),
33553
35329
  ...options?.extraRegistries ?? []
33554
35330
  ];
33555
- const SettingsTab = () => React15__default.createElement(PluginSettingsPanel, {
35331
+ const SettingsTab = () => React20__default.createElement(PluginSettingsPanel, {
33556
35332
  title: "Pagamentos",
33557
35333
  registries,
33558
35334
  hostPluginId: "financial",
@@ -33609,25 +35385,25 @@ function ClientAccountPanel({
33609
35385
  currency = BRL
33610
35386
  }) {
33611
35387
  const provider = useFinancialProvider();
33612
- const [account, setAccount] = React15__default.useState(null);
33613
- const [busy, setBusy] = React15__default.useState(false);
33614
- const [viewInvoice, setViewInvoice] = React15__default.useState(null);
33615
- const [comprando, setComprando] = React15__default.useState(false);
33616
- const [buscaServico, setBuscaServico] = React15__default.useState("");
33617
- const [catalogo, setCatalogo] = React15__default.useState([]);
33618
- const [escolhidos, setEscolhidos] = React15__default.useState([]);
33619
- const [comprandoBusy, setComprandoBusy] = React15__default.useState(false);
33620
- const [erroCompra, setErroCompra] = React15__default.useState(null);
33621
- const load = React15__default.useCallback(async () => {
35388
+ const [account, setAccount] = React20__default.useState(null);
35389
+ const [busy, setBusy] = React20__default.useState(false);
35390
+ const [viewInvoice, setViewInvoice] = React20__default.useState(null);
35391
+ const [comprando, setComprando] = React20__default.useState(false);
35392
+ const [buscaServico, setBuscaServico] = React20__default.useState("");
35393
+ const [catalogo, setCatalogo] = React20__default.useState([]);
35394
+ const [escolhidos, setEscolhidos] = React20__default.useState([]);
35395
+ const [comprandoBusy, setComprandoBusy] = React20__default.useState(false);
35396
+ const [erroCompra, setErroCompra] = React20__default.useState(null);
35397
+ const load = React20__default.useCallback(async () => {
33622
35398
  if (!personId) return;
33623
35399
  setAccount(await provider.getClientAccount(personId));
33624
35400
  }, [personId, provider]);
33625
- React15__default.useEffect(() => {
35401
+ React20__default.useEffect(() => {
33626
35402
  void load();
33627
35403
  }, [load]);
33628
35404
  const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
33629
35405
  const hasForThis = forThis.length > 0;
33630
- React15__default.useEffect(() => {
35406
+ React20__default.useEffect(() => {
33631
35407
  if (!comprando) return;
33632
35408
  let vivo = true;
33633
35409
  const t2 = setTimeout(async () => {
@@ -33888,8 +35664,8 @@ function ClientAccountPanel({
33888
35664
  ] });
33889
35665
  }
33890
35666
  function ClientAccountInline(props) {
33891
- const provider = React15__default.useMemo(() => createSafeFinancialProvider(), []);
33892
- const store6 = React15__default.useMemo(() => createFinancialStore(provider), [provider]);
35667
+ const provider = React20__default.useMemo(() => createSafeFinancialProvider(), []);
35668
+ const store6 = React20__default.useMemo(() => createFinancialStore(provider), [provider]);
33893
35669
  return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(ClientAccountPanel, { ...props }) });
33894
35670
  }
33895
35671
  function ClientAccountModal({
@@ -34007,9 +35783,11 @@ function createFinancialPlugin(options) {
34007
35783
  const store6 = createFinancialStore(provider);
34008
35784
  const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store: store6 });
34009
35785
  const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
34010
- const PersonStatementBound = (props) => React15__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
35786
+ const PersonStatementBound = (props) => React20__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
34011
35787
  PersonStatementBound.displayName = "FinancialPersonStatement";
34012
- const PageComponent = () => React15__default.createElement(FinancialPage, { config, provider, store: store6, registries });
35788
+ const SupplierFinancialSettingsBound = (props) => React20__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
35789
+ SupplierFinancialSettingsBound.displayName = "FinancialSupplierSettings";
35790
+ const PageComponent = () => React20__default.createElement(FinancialPage, { config, provider, store: store6, registries });
34013
35791
  const AgentMount = createFinancialAgentMount({ config, provider });
34014
35792
  return {
34015
35793
  id: "financial",
@@ -34067,7 +35845,7 @@ function createFinancialPlugin(options) {
34067
35845
  // tem — lá é um ícone no cabeçalho, de qualquer tela.
34068
35846
  headerControls: config.modules.cashRegisters ? [{
34069
35847
  id: "financial.cash-session",
34070
- component: (() => React15__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
35848
+ component: (() => React20__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
34071
35849
  order: 90,
34072
35850
  permission: { feature: "financial", action: "read" }
34073
35851
  }] : [],
@@ -34163,6 +35941,13 @@ function createFinancialPlugin(options) {
34163
35941
  }
34164
35942
  ],
34165
35943
  widgets: [
35944
+ {
35945
+ id: "financial-supplier-settings",
35946
+ zone: "person.detail.financial-settings",
35947
+ component: SupplierFinancialSettingsBound,
35948
+ order: 0,
35949
+ permission: { feature: "financial", action: "read" }
35950
+ },
34166
35951
  {
34167
35952
  id: "financial-person-statement",
34168
35953
  zone: "person.detail.financial",
@@ -34183,6 +35968,7 @@ function createFinancialPlugin(options) {
34183
35968
  }
34184
35969
  ],
34185
35970
  dashboardWidgets,
35971
+ analyticsCards: buildFinancialAnalyticsCards(config, provider),
34186
35972
  aiTools: [
34187
35973
  {
34188
35974
  id: "financial.get-revenue",
@@ -34270,9 +36056,9 @@ function createFinancialPlugin(options) {
34270
36056
  // lado, e a corrida de "Plugins" não é partida ao meio.
34271
36057
  group: "Financeiro",
34272
36058
  component: (() => {
34273
- const FinancialSettingsTab = () => React15__default.createElement(PluginSettingsPanel, {
36059
+ const FinancialSettingsTab = () => React20__default.createElement(PluginSettingsPanel, {
34274
36060
  title: "Financial Settings",
34275
- generalSettings: React15__default.createElement(FinancialGeneralSettings, { config }),
36061
+ generalSettings: React20__default.createElement(FinancialGeneralSettings, { config }),
34276
36062
  registries,
34277
36063
  hostPluginId: "financial",
34278
36064
  routeBase: "/settings/financial"
@@ -34297,7 +36083,7 @@ function createFinancialPlugin(options) {
34297
36083
  icon: "HandCoins",
34298
36084
  group: "Financeiro",
34299
36085
  component: (() => {
34300
- const CommissionRulesTab = () => React15__default.createElement(CommissionRulesView, { provider });
36086
+ const CommissionRulesTab = () => React20__default.createElement(CommissionRulesView, { provider });
34301
36087
  CommissionRulesTab.displayName = "CommissionRulesTab";
34302
36088
  return CommissionRulesTab;
34303
36089
  })(),