@fayz-ai/plugin-financial 0.9.0 → 0.9.2

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 (95) hide show
  1. package/README.md +3 -3
  2. package/dist/components/PersonStatementWidget.d.ts +1 -1
  3. package/dist/components/PersonStatementWidget.d.ts.map +1 -1
  4. package/dist/{FinancialContext.d.ts → context.d.ts} +4 -2
  5. package/dist/context.d.ts.map +1 -0
  6. package/dist/data/registries.d.ts.map +1 -0
  7. package/dist/index.d.ts +13 -4
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1426 -1426
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/stripe/connectorDef.d.ts +14 -0
  12. package/dist/integrations/stripe/connectorDef.d.ts.map +1 -0
  13. package/dist/integrations/stripe/data.d.ts +43 -0
  14. package/dist/integrations/stripe/data.d.ts.map +1 -0
  15. package/dist/lib/message-events.d.ts +12 -0
  16. package/dist/lib/message-events.d.ts.map +1 -0
  17. package/dist/{quick-add.d.ts → lib/quick-add.d.ts} +1 -1
  18. package/dist/lib/quick-add.d.ts.map +1 -0
  19. package/dist/locales/en.d.ts.map +1 -1
  20. package/dist/locales/pt-BR.d.ts.map +1 -1
  21. package/dist/{FinancialPage.d.ts → views/FinancialPage.d.ts} +3 -3
  22. package/dist/views/FinancialPage.d.ts.map +1 -0
  23. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  24. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  25. package/dist/views/InvoiceListView.d.ts.map +1 -1
  26. package/dist/views/PayablesView.d.ts +1 -1
  27. package/dist/views/PayablesView.d.ts.map +1 -1
  28. package/dist/views/ReceivablesView.d.ts +1 -1
  29. package/dist/views/ReceivablesView.d.ts.map +1 -1
  30. package/dist/views/StatementsView.d.ts.map +1 -1
  31. package/dist/views/dashboardWidgets.d.ts +1 -1
  32. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  33. package/package.json +12 -14
  34. package/dist/FinancialContext.d.ts.map +0 -1
  35. package/dist/FinancialPage.d.ts.map +0 -1
  36. package/dist/contract.test.d.ts +0 -2
  37. package/dist/contract.test.d.ts.map +0 -1
  38. package/dist/quick-add.d.ts.map +0 -1
  39. package/dist/registries.d.ts.map +0 -1
  40. package/src/FinancialContext.tsx +0 -59
  41. package/src/FinancialPage.tsx +0 -413
  42. package/src/README.md +0 -155
  43. package/src/components/FinancialGeneralSettings.tsx +0 -39
  44. package/src/components/FinancialOnboarding.tsx +0 -87
  45. package/src/components/FinancialSettings.tsx +0 -31
  46. package/src/components/PaymentModal.tsx +0 -369
  47. package/src/components/PersonStatementWidget.tsx +0 -133
  48. package/src/components/TransferModal.tsx +0 -150
  49. package/src/contract.test.ts +0 -58
  50. package/src/data/capability.test.ts +0 -135
  51. package/src/data/index.ts +0 -3
  52. package/src/data/mock.ts +0 -842
  53. package/src/data/supabase.ts +0 -968
  54. package/src/data/tables.ts +0 -13
  55. package/src/data/types.ts +0 -86
  56. package/src/index.ts +0 -487
  57. package/src/lib/tenant.ts +0 -3
  58. package/src/locales/en.ts +0 -493
  59. package/src/locales/index.ts +0 -7
  60. package/src/locales/pt-BR.ts +0 -493
  61. package/src/migrations/000_plg_rename.sql +0 -30
  62. package/src/migrations/001_financial_base.sql +0 -109
  63. package/src/migrations/002_chart_of_accounts.sql +0 -28
  64. package/src/migrations/003_card_brands.sql +0 -12
  65. package/src/migrations/004_order_to_cash.sql +0 -178
  66. package/src/migrations/005_seed_defaults.sql +0 -38
  67. package/src/migrations/006_rls_policies.sql +0 -37
  68. package/src/migrations/006b_extract_fee_amount.sql +0 -20
  69. package/src/migrations/007_reconciliation.sql +0 -32
  70. package/src/migrations/007b_movement_payment_method_type.sql +0 -6
  71. package/src/migrations/008_split_payment_movements.sql +0 -29
  72. package/src/migrations/009_agent_rpcs.sql +0 -165
  73. package/src/migrations/010_invoice_internal.sql +0 -133
  74. package/src/migrations/011_nfe.sql +0 -58
  75. package/src/migrations/index.ts +0 -923
  76. package/src/quick-add.ts +0 -58
  77. package/src/registries.ts +0 -308
  78. package/src/store.ts +0 -329
  79. package/src/types.ts +0 -592
  80. package/src/views/CardsView.tsx +0 -213
  81. package/src/views/CashRegistersView.tsx +0 -597
  82. package/src/views/CommissionsView.tsx +0 -18
  83. package/src/views/InvoiceDetailView.tsx +0 -616
  84. package/src/views/InvoiceFormView.tsx +0 -945
  85. package/src/views/InvoiceListView.tsx +0 -323
  86. package/src/views/NotasFiscaisView.tsx +0 -209
  87. package/src/views/PayablesView.tsx +0 -48
  88. package/src/views/QuickTransactionForm.tsx +0 -548
  89. package/src/views/ReceivablesView.tsx +0 -48
  90. package/src/views/ReconciliationView.tsx +0 -211
  91. package/src/views/StatementsView.tsx +0 -335
  92. package/src/views/SummaryView.tsx +0 -203
  93. package/src/views/dashboardWidgets.tsx +0 -437
  94. package/src/views/navReferrer.ts +0 -24
  95. /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as React18 from 'react';
2
2
  import React18__default, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
3
- import { createSafeDataProvider, registerTranslations, useTranslation, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
4
- import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, Modal, ModalContent, DatePicker, DashboardCanvas, SubpageHeader, Skeleton, Input, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, Card, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
5
- import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, PermissionGate, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, formatCurrency, parseViewId, EntitlementGate, UpgradePrompt, useLimitGuard, invalidateLimit, PersonLink } from '@fayz-ai/saas';
6
- import { Wallet, Plus, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, X, CalendarDays, Paperclip, Camera, Tag, Repeat, ChevronDown, FileText, FileCheck2, ExternalLink, Send, Ban, CalendarClock, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Receipt, Clock, ArrowLeftRight, CheckCheck, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, LayoutList, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, User, Calendar, Hash, Building2, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, Search, Trash2, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, EyeOff, Eye } from 'lucide-react';
3
+ import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, createSafeDataProvider, registerTranslations, registerMessageDefaults, useTranslation, getSupabaseClientOptional, getActiveTenantId, exportCSV } from '@fayz-ai/core';
4
+ import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, Modal, ModalContent, DatePicker, DashboardCanvas, SubpageHeader, Skeleton, Card, FilterChip, Input, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
5
+ import { createPluginContext, fayzConnectorOAuth, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, PermissionGate, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, formatCurrency, parseViewId, EntitlementGate, UpgradePrompt, useLimitGuard, invalidateLimit, PersonLink, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, OrderDetailSkeleton, OrderDetailBlock, OrderDetailView } from '@fayz-ai/admin';
6
+ import { Wallet, Plus, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, X, CalendarDays, Paperclip, Camera, Tag, Repeat, ChevronDown, FileText, FileCheck2, ExternalLink, Send, Ban, CalendarClock, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Search, Download, Receipt, Clock, ArrowLeftRight, CheckCheck, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, CircleCheckBig, CircleDashed, CircleAlert, CircleEllipsis, Pencil, LayoutList, Layers, Building2, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, 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';
@@ -789,7 +789,6 @@ function InvoiceListView({ direction, onNew, onEdit }) {
789
789
  });
790
790
  }, [direction, statusFilter, search, accountId, costCenterId, dateRange]);
791
791
  const filtered = invoices.filter((inv) => inv.direction === direction);
792
- const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
793
792
  const columns = useInvoiceColumns(currency);
794
793
  const tags = useMemo(
795
794
  () => STATUS_OPTIONS.filter((o) => o.value !== "cancelled").map((o) => ({ value: o.value, label: t2(o.labelKey) })),
@@ -809,114 +808,85 @@ function InvoiceListView({ direction, onNew, onEdit }) {
809
808
  }, [filtered]);
810
809
  const receivedLabel = direction === "debit" ? t2("financial.invoice.summaryPaid") : t2("financial.invoice.summaryReceived");
811
810
  const pendingLabel = direction === "debit" ? t2("financial.invoice.summaryPayable") : t2("financial.invoice.summaryReceivable");
812
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
811
+ const exportRows = () => {
812
+ exportCSV(
813
+ [
814
+ { key: "fiscalNumber", label: "#" },
815
+ { key: "invoiceDate", label: t2("financial.invoice.columnDate") },
816
+ { key: "contactName", label: t2("financial.invoice.columnDescription") },
817
+ { key: "totalAmount", label: t2("financial.invoice.columnAmount"), format: (v) => formatCurrency(Number(v) || 0, currency) },
818
+ { key: "paidAmount", label: t2("financial.invoice.columnPaid"), format: (v) => formatCurrency(Number(v) || 0, currency) },
819
+ { key: "status", label: t2("financial.invoice.columnStatus"), format: (v) => {
820
+ const opt = STATUS_OPTIONS.find((o) => o.value === v);
821
+ return opt ? t2(opt.labelKey) : String(v ?? "");
822
+ } },
823
+ { key: "totalInstallments", label: t2("financial.invoice.columnInst") }
824
+ ],
825
+ filtered,
826
+ direction === "debit" ? "contas-a-pagar" : "contas-a-receber"
827
+ );
828
+ };
829
+ const listFilters = /* @__PURE__ */ jsxs(Fragment, { children: [
813
830
  /* @__PURE__ */ jsx(
814
- SubpageHeader,
831
+ FilterChip,
815
832
  {
816
- title: listTitle,
817
- subtitle: t2("financial.invoice.invoices", { count: filtered.length })
833
+ icon: CalendarDays,
834
+ label: t2("financial.invoice.filterPeriod"),
835
+ allLabel: t2("financial.invoice.filterAllPeriods"),
836
+ value: datePreset === "all" ? void 0 : datePreset,
837
+ onChange: (next) => applyPreset(next ?? "all"),
838
+ options: [
839
+ { value: "thisMonth", label: t2("financial.invoice.filterThisMonth") },
840
+ { value: "lastMonth", label: t2("financial.invoice.filterLastMonth") },
841
+ { value: "last30", label: t2("financial.invoice.filterLast30") },
842
+ { value: "thisYear", label: t2("financial.invoice.filterThisYear") },
843
+ { value: "custom", label: t2("financial.invoice.filterCustom") }
844
+ ]
818
845
  }
819
846
  ),
820
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
821
- /* @__PURE__ */ jsxs("label", { className: "flex min-w-[180px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
822
- t2("financial.invoice.filterPeriod"),
823
- /* @__PURE__ */ jsxs(
824
- "select",
825
- {
826
- className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
827
- value: datePreset,
828
- onChange: (event) => applyPreset(event.target.value),
829
- children: [
830
- /* @__PURE__ */ jsx("option", { value: "all", children: t2("financial.invoice.filterAllPeriods") }),
831
- /* @__PURE__ */ jsx("option", { value: "thisMonth", children: t2("financial.invoice.filterThisMonth") }),
832
- /* @__PURE__ */ jsx("option", { value: "lastMonth", children: t2("financial.invoice.filterLastMonth") }),
833
- /* @__PURE__ */ jsx("option", { value: "last30", children: t2("financial.invoice.filterLast30") }),
834
- /* @__PURE__ */ jsx("option", { value: "thisYear", children: t2("financial.invoice.filterThisYear") }),
835
- /* @__PURE__ */ jsx("option", { value: "custom", children: t2("financial.invoice.filterCustom") })
836
- ]
837
- }
838
- )
839
- ] }),
840
- /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
841
- t2("financial.invoice.filterFrom"),
842
- /* @__PURE__ */ jsx(
843
- "input",
844
- {
845
- type: "date",
846
- className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
847
- value: dateFrom,
848
- max: dateTo || void 0,
849
- onChange: (event) => {
850
- setDateFrom(event.target.value);
851
- setDatePreset("custom");
852
- }
853
- }
854
- )
855
- ] }),
856
- /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
857
- t2("financial.invoice.filterTo"),
858
- /* @__PURE__ */ jsx(
859
- "input",
860
- {
861
- type: "date",
862
- className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
863
- value: dateTo,
864
- min: dateFrom || void 0,
865
- onChange: (event) => {
866
- setDateTo(event.target.value);
867
- setDatePreset("custom");
868
- }
869
- }
870
- )
871
- ] }),
872
- (dateFrom || dateTo) && /* @__PURE__ */ jsx(
873
- "button",
874
- {
875
- type: "button",
876
- className: "h-9 rounded-md px-2 text-xs font-medium text-muted-foreground hover:text-foreground",
877
- onClick: () => applyPreset("all"),
878
- children: t2("financial.invoice.filterClear")
879
- }
880
- ),
881
- accounts.length > 0 && /* @__PURE__ */ jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
882
- t2("financial.invoice.filterAccount"),
883
- /* @__PURE__ */ jsxs(
884
- "select",
885
- {
886
- className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
887
- value: accountId,
888
- onChange: (event) => setAccountId(event.target.value),
889
- children: [
890
- /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoice.filterAllAccounts") }),
891
- accounts.map((account) => /* @__PURE__ */ jsxs("option", { value: account.id, children: [
892
- account.code,
893
- " - ",
894
- account.name
895
- ] }, account.id))
896
- ]
897
- }
898
- )
899
- ] }),
900
- costCenters.length > 0 && /* @__PURE__ */ jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
901
- t2("financial.invoice.filterCostCenter"),
902
- /* @__PURE__ */ jsxs(
903
- "select",
904
- {
905
- className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
906
- value: costCenterId,
907
- onChange: (event) => setCostCenterId(event.target.value),
908
- children: [
909
- /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoice.filterAllCostCenters") }),
910
- costCenters.map((costCenter) => /* @__PURE__ */ jsxs("option", { value: costCenter.id, children: [
911
- costCenter.code,
912
- " - ",
913
- costCenter.name
914
- ] }, costCenter.id))
915
- ]
916
- }
917
- )
918
- ] })
847
+ datePreset === "custom" && /* @__PURE__ */ jsxs(Fragment, { children: [
848
+ /* @__PURE__ */ jsx(DatePicker, { value: dateFrom, onChange: setDateFrom, variant: "chip" }),
849
+ /* @__PURE__ */ jsx(DatePicker, { value: dateTo, onChange: setDateTo, variant: "chip" })
919
850
  ] }),
851
+ (dateFrom || dateTo) && /* @__PURE__ */ jsx(
852
+ "button",
853
+ {
854
+ type: "button",
855
+ className: "h-9 rounded-md px-2 text-xs font-medium text-muted-foreground hover:text-foreground",
856
+ onClick: () => applyPreset("all"),
857
+ children: t2("financial.invoice.filterClear")
858
+ }
859
+ ),
860
+ accounts.length > 0 && /* @__PURE__ */ jsx(
861
+ FilterChip,
862
+ {
863
+ icon: Landmark,
864
+ label: t2("financial.invoice.filterAccount"),
865
+ allLabel: t2("financial.invoice.filterAllAccounts"),
866
+ value: accountId || void 0,
867
+ onChange: (next) => setAccountId(next ?? ""),
868
+ options: accounts.map((account) => ({
869
+ value: account.id,
870
+ label: `${account.code} - ${account.name}`
871
+ }))
872
+ }
873
+ ),
874
+ costCenters.length > 0 && /* @__PURE__ */ jsx(
875
+ FilterChip,
876
+ {
877
+ icon: Building2,
878
+ label: t2("financial.invoice.filterCostCenter"),
879
+ allLabel: t2("financial.invoice.filterAllCostCenters"),
880
+ value: costCenterId || void 0,
881
+ onChange: (next) => setCostCenterId(next ?? ""),
882
+ options: costCenters.map((costCenter) => ({
883
+ value: costCenter.id,
884
+ label: `${costCenter.code} - ${costCenter.name}`
885
+ }))
886
+ }
887
+ )
888
+ ] });
889
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
920
890
  /* @__PURE__ */ jsx(
921
891
  ListView,
922
892
  {
@@ -928,8 +898,12 @@ function InvoiceListView({ direction, onNew, onEdit }) {
928
898
  onSearchChange: setSearch,
929
899
  searchDebounce: 0,
930
900
  tags,
901
+ allTagLabel: t2("crud.list.allFacet"),
931
902
  activeTag: statusFilter.length === 1 ? statusFilter[0] : void 0,
932
903
  onTagChange: (v) => setStatusFilter(v ? [v] : []),
904
+ filters: listFilters,
905
+ onExport: filtered.length > 0 ? exportRows : void 0,
906
+ exportLabel: t2("crud.export.action"),
933
907
  newLabel: t2("financial.invoice.new"),
934
908
  onNew: onNewGated,
935
909
  onRowClick: (row) => onEdit?.(row.id),
@@ -941,810 +915,20 @@ function InvoiceListView({ direction, onNew, onEdit }) {
941
915
  ),
942
916
  filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
943
917
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
944
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.summaryTotal") }),
945
- /* @__PURE__ */ jsx("span", { className: "font-semibold", children: formatCurrency(summary.totalAmount, currency) })
946
- ] }),
947
- /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
948
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
949
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: receivedLabel }),
950
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-success", children: formatCurrency(summary.paidAmount, currency) })
951
- ] }),
952
- /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
953
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
954
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: pendingLabel }),
955
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-warning", children: formatCurrency(summary.openAmount, currency) })
956
- ] })
957
- ] })
958
- ] });
959
- }
960
- function LocationSelector({ locations, value, onChange }) {
961
- const t2 = useTranslation();
962
- const [open, setOpen] = useState(false);
963
- const [search, setSearch] = useState("");
964
- const ref = useRef(null);
965
- useEffect(() => {
966
- function handler(e) {
967
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
968
- }
969
- document.addEventListener("mousedown", handler);
970
- return () => document.removeEventListener("mousedown", handler);
971
- }, []);
972
- const selected = locations.find((l) => l.id === value);
973
- const filtered = locations.filter(
974
- (l) => !search.trim() || l.name.toLowerCase().includes(search.toLowerCase())
975
- );
976
- return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
977
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.unit") }),
978
- /* @__PURE__ */ jsxs(
979
- "button",
980
- {
981
- type: "button",
982
- onClick: () => setOpen(!open),
983
- 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",
984
- children: [
985
- /* @__PURE__ */ jsx(Building2, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
986
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? t2("financial.invoiceForm.selectUnit") }),
987
- /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
988
- ]
989
- }
990
- ),
991
- open && /* @__PURE__ */ jsxs("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden min-w-[260px]", children: [
992
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [
993
- /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }),
994
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.selectUnitTitle") }),
995
- /* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "ml-auto text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }) })
996
- ] }),
997
- /* @__PURE__ */ jsx("div", { className: "p-2 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5", children: [
998
- /* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground" }),
999
- /* @__PURE__ */ jsx(
1000
- "input",
1001
- {
1002
- type: "text",
1003
- autoFocus: true,
1004
- value: search,
1005
- onChange: (e) => setSearch(e.target.value),
1006
- placeholder: t2("financial.invoiceForm.searchUnit"),
1007
- className: "flex-1 bg-transparent text-sm outline-none"
1008
- }
1009
- )
1010
- ] }) }),
1011
- /* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-auto py-1", children: [
1012
- filtered.map((loc) => /* @__PURE__ */ jsxs(
1013
- "button",
1014
- {
1015
- onClick: () => {
1016
- onChange(loc.id);
1017
- setOpen(false);
1018
- setSearch("");
1019
- },
1020
- className: `w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-muted/50 transition-colors ${loc.id === value ? "bg-muted/30" : ""}`,
1021
- children: [
1022
- /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-muted shrink-0", children: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }) }),
1023
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1024
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: loc.name }),
1025
- loc.isHQ && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoiceForm.headquarters") })
1026
- ] })
1027
- ]
1028
- },
1029
- loc.id
1030
- )),
1031
- filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: t2("financial.invoiceForm.noUnitsFound") })
1032
- ] })
1033
- ] })
1034
- ] });
1035
- }
1036
- var formId = 1;
1037
- function fid() {
1038
- return `f${formId++}`;
1039
- }
1040
- function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdate, onRemove, entityLookups, showTypeSelector }) {
1041
- const t2 = useTranslation();
1042
- const total = item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0);
1043
- const kindLabel = itemTypes.find((tp) => tp.value === item.itemKind)?.label ?? item.itemKind;
1044
- const hasAdjustments = (item.discount ?? 0) > 0 || (item.surcharge ?? 0) > 0;
1045
- const [showAdjustments, setShowAdjustments] = useState(hasAdjustments);
1046
- function handleFieldKeyDown(e) {
1047
- if (e.key === "Enter" && !e.shiftKey) {
1048
- e.preventDefault();
1049
- onToggle();
1050
- }
1051
- }
1052
- return /* @__PURE__ */ jsxs("div", { className: "border-b last:border-0", children: [
1053
- /* @__PURE__ */ jsxs(
1054
- "div",
1055
- {
1056
- className: "flex items-center gap-2 px-4 py-3 cursor-pointer hover:bg-muted/30 transition-colors group",
1057
- onClick: onToggle,
1058
- children: [
1059
- /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground shrink-0 transition-transform ${expanded ? "rotate-0" : "-rotate-90"}` }),
1060
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-muted-foreground w-5 shrink-0", children: [
1061
- index + 1,
1062
- "."
1063
- ] }),
1064
- /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded px-1.5 py-0.5 text-[9px] font-medium bg-muted text-muted-foreground shrink-0", children: kindLabel }),
1065
- /* @__PURE__ */ jsx("span", { className: "text-sm truncate flex-1 min-w-0", children: item.description || /* @__PURE__ */ jsx("span", { className: "text-muted-foreground italic", children: t2("financial.invoiceForm.noDescription") }) }),
1066
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground shrink-0", children: [
1067
- item.quantity,
1068
- " x ",
1069
- formatCurrency(item.unitPrice, currency)
1070
- ] }),
1071
- hasAdjustments && /* @__PURE__ */ jsx(Tag, { className: "h-3 w-3 text-warning shrink-0" }),
1072
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold shrink-0 min-w-[80px] text-right", children: formatCurrency(total, currency) }),
1073
- /* @__PURE__ */ jsx(
1074
- "button",
1075
- {
1076
- onClick: (e) => {
1077
- e.stopPropagation();
1078
- onRemove();
1079
- },
1080
- className: "p-1 text-muted-foreground opacity-0 group-hover:opacity-100 hover:text-destructive transition-all shrink-0",
1081
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
1082
- }
1083
- )
1084
- ]
1085
- }
1086
- ),
1087
- expanded && /* @__PURE__ */ jsxs("div", { className: "px-4 pb-4 pt-1 ml-8 space-y-3 bg-muted/10", children: [
1088
- showTypeSelector && /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: itemTypes.map((tp) => /* @__PURE__ */ jsx(
1089
- "button",
1090
- {
1091
- onClick: () => onUpdate({ itemKind: tp.value }),
1092
- className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors ${item.itemKind === tp.value ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
1093
- children: tp.label
1094
- },
1095
- tp.value
1096
- )) }),
1097
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3 grid-cols-12", children: [
1098
- /* @__PURE__ */ jsx("div", { className: "col-span-6 sm:col-span-7", children: entityLookups[item.itemKind] ? /* @__PURE__ */ jsx(
1099
- SearchSelect,
1100
- {
1101
- label: t2("financial.invoiceForm.item"),
1102
- value: item.referenceId ?? "",
1103
- displayValue: item.description,
1104
- loadOnOpen: true,
1105
- onChange: (id, opt) => {
1106
- const price = opt?.data?.price;
1107
- onUpdate({
1108
- referenceId: id || void 0,
1109
- description: opt?.label ?? "",
1110
- ...price != null ? { unitPrice: price } : {}
1111
- });
1112
- },
1113
- onSearch: async (q) => {
1114
- const results = await entityLookups[item.itemKind].search(q);
1115
- return results.map((r) => ({
1116
- id: r.id,
1117
- label: r.label,
1118
- subtitle: r.subtitle,
1119
- group: r.group,
1120
- data: r
1121
- }));
1122
- },
1123
- placeholder: `Search ${itemTypes.find((tp) => tp.value === item.itemKind)?.label?.toLowerCase() ?? "item"}...`,
1124
- renderOption: (opt) => /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1125
- /* @__PURE__ */ jsx("p", { className: "text-sm truncate", children: opt.label }),
1126
- opt.subtitle && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: opt.subtitle })
1127
- ] })
1128
- }
1129
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1130
- /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.description") }),
1131
- /* @__PURE__ */ jsx(
1132
- "input",
1133
- {
1134
- type: "text",
1135
- value: item.description,
1136
- onChange: (e) => onUpdate({ description: e.target.value }),
1137
- onKeyDown: handleFieldKeyDown,
1138
- placeholder: t2("financial.invoiceForm.itemDescription"),
1139
- autoFocus: true,
1140
- className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
1141
- }
1142
- )
1143
- ] }) }),
1144
- /* @__PURE__ */ jsxs("div", { className: "col-span-2 sm:col-span-1", children: [
1145
- /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.qty") }),
1146
- /* @__PURE__ */ jsx(
1147
- "input",
1148
- {
1149
- type: "number",
1150
- min: 1,
1151
- value: item.quantity,
1152
- onChange: (e) => onUpdate({ quantity: Number(e.target.value) || 1 }),
1153
- onKeyDown: handleFieldKeyDown,
1154
- className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 py-1.5 text-sm text-center focus:outline-none focus:ring-2 focus:ring-ring"
1155
- }
1156
- )
1157
- ] }),
1158
- /* @__PURE__ */ jsx("div", { className: "col-span-4", onKeyDown: handleFieldKeyDown, children: /* @__PURE__ */ jsx(
1159
- CurrencyInput,
1160
- {
1161
- label: t2("financial.invoiceForm.unitPrice"),
1162
- value: item.unitPrice,
1163
- onChange: (v) => onUpdate({ unitPrice: v }),
1164
- symbol: currency.symbol,
1165
- locale: currency.locale,
1166
- currencyCode: currency.code
1167
- }
1168
- ) })
1169
- ] }),
1170
- !showAdjustments ? /* @__PURE__ */ jsxs(
1171
- "button",
1172
- {
1173
- onClick: () => setShowAdjustments(true),
1174
- className: "inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors",
1175
- children: [
1176
- /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
1177
- " ",
1178
- t2("financial.invoiceForm.discountSurcharge")
1179
- ]
1180
- }
1181
- ) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1182
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
1183
- /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.discount") }),
1184
- /* @__PURE__ */ jsxs("div", { className: "flex items-center mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus-within:ring-2 focus-within:ring-primary/20", children: [
1185
- /* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: currency.symbol }),
1186
- /* @__PURE__ */ jsx(
1187
- "input",
1188
- {
1189
- type: "number",
1190
- min: 0,
1191
- step: 0.01,
1192
- value: item.discount ?? 0,
1193
- onChange: (e) => onUpdate({ discount: Number(e.target.value) || 0 }),
1194
- onKeyDown: handleFieldKeyDown,
1195
- className: "flex-1 bg-transparent px-2 py-1 text-xs outline-none"
1196
- }
1197
- )
1198
- ] })
1199
- ] }),
1200
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
1201
- /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.surcharge") }),
1202
- /* @__PURE__ */ jsxs("div", { className: "flex items-center mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus-within:ring-2 focus-within:ring-primary/20", children: [
1203
- /* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: currency.symbol }),
1204
- /* @__PURE__ */ jsx(
1205
- "input",
1206
- {
1207
- type: "number",
1208
- min: 0,
1209
- step: 0.01,
1210
- value: item.surcharge ?? 0,
1211
- onChange: (e) => onUpdate({ surcharge: Number(e.target.value) || 0 }),
1212
- onKeyDown: handleFieldKeyDown,
1213
- className: "flex-1 bg-transparent px-2 py-1 text-xs outline-none"
1214
- }
1215
- )
1216
- ] })
1217
- ] }),
1218
- /* @__PURE__ */ jsx(
1219
- "button",
1220
- {
1221
- onClick: () => {
1222
- onUpdate({ discount: 0, surcharge: 0 });
1223
- setShowAdjustments(false);
1224
- },
1225
- className: "mt-3 p-1 text-muted-foreground hover:text-destructive transition-colors",
1226
- title: t2("financial.invoiceForm.removeAdjustments"),
1227
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
1228
- }
1229
- )
1230
- ] }),
1231
- /* @__PURE__ */ jsx("div", { className: "flex justify-end pt-1", children: /* @__PURE__ */ jsxs(
1232
- "button",
1233
- {
1234
- onClick: onToggle,
1235
- className: "inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-[11px] text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors",
1236
- children: [
1237
- /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
1238
- " ",
1239
- t2("financial.invoiceForm.done")
1240
- ]
1241
- }
1242
- ) })
1243
- ] })
1244
- ] });
1245
- }
1246
- function AddItemStep({ itemTypes, onSelect, onCancel }) {
1247
- const t2 = useTranslation();
1248
- return /* @__PURE__ */ jsxs("div", { className: "border-b px-4 py-3 bg-primary/5 animate-in fade-in-0 slide-in-from-top-1", children: [
1249
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-2", children: t2("financial.invoiceForm.selectItemType") }),
1250
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 flex-wrap", children: [
1251
- itemTypes.map((t3) => /* @__PURE__ */ jsx(
1252
- "button",
1253
- {
1254
- onClick: () => onSelect(t3.value),
1255
- className: "inline-flex items-center gap-1.5 rounded-lg border-2 border-transparent bg-card px-4 py-2 text-sm font-medium shadow-sm hover:border-primary hover:shadow-md transition-all",
1256
- children: t3.label
1257
- },
1258
- t3.value
1259
- )),
1260
- /* @__PURE__ */ jsx(
1261
- "button",
1262
- {
1263
- onClick: onCancel,
1264
- className: "inline-flex items-center rounded-lg px-3 py-2 text-xs text-muted-foreground hover:text-foreground transition-colors",
1265
- children: t2("financial.invoiceForm.cancel")
1266
- }
1267
- )
1268
- ] })
1269
- ] });
1270
- }
1271
- function InvoiceFormView({ direction, editId, onSaved }) {
1272
- const t2 = useTranslation();
1273
- const config = useFinancialConfig();
1274
- const { currency, itemTypes, locations } = config;
1275
- const provider = useFinancialProvider();
1276
- const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
1277
- const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
1278
- const createInvoice = useFinancialStore((s) => s.createInvoice);
1279
- const guardMovements = useLimitGuard("movements_month");
1280
- const can = usePermissionOptional();
1281
- const hasLocations = locations.length > 0;
1282
- const isEdit = !!editId;
1283
- const [loaded, setLoaded] = useState(!editId);
1284
- const [loading, setLoading] = useState(!!editId);
1285
- const [contactName, setContactName] = useState("");
1286
- const [contactId, setContactId] = useState();
1287
- const [unitId, setUnitId] = useState(locations[0]?.id ?? "");
1288
- const [invoiceDate, setInvoiceDate] = useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
1289
- const [fiscalNumber, setFiscalNumber] = useState("");
1290
- const [observations, setObservations] = useState("");
1291
- const [items, setItems] = useState([]);
1292
- const [expandedItemId, setExpandedItemId] = useState(null);
1293
- const [addingItem, setAddingItem] = useState(false);
1294
- const [installments, setInstallments] = useState([]);
1295
- const [saving, setSaving] = useState(false);
1296
- const [menuOpen, setMenuOpen] = useState(false);
1297
- const [confirmCancel, setConfirmCancel] = useState(false);
1298
- const [cancelling, setCancelling] = useState(false);
1299
- const menuRef = useRef(null);
1300
- const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
1301
- useEffect(() => {
1302
- if (!menuOpen) return;
1303
- function handleClick(e) {
1304
- if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
1305
- }
1306
- document.addEventListener("mousedown", handleClick);
1307
- return () => document.removeEventListener("mousedown", handleClick);
1308
- }, [menuOpen]);
1309
- async function handleCancelInvoice() {
1310
- if (!editId) return;
1311
- setCancelling(true);
1312
- try {
1313
- await cancelInvoice(editId);
1314
- onSaved?.();
1315
- } catch {
1316
- } finally {
1317
- setCancelling(false);
1318
- }
1319
- }
1320
- useEffect(() => {
1321
- if (!editId || loaded) return;
1322
- setLoading(true);
1323
- (async () => {
1324
- const invoice = await provider.getInvoiceById(editId);
1325
- if (!invoice) return;
1326
- setContactName(invoice.contactName ?? "");
1327
- setContactId(invoice.contactId);
1328
- setUnitId(invoice.unitId ?? locations[0]?.id ?? "");
1329
- setInvoiceDate(invoice.invoiceDate);
1330
- setFiscalNumber(invoice.fiscalNumber ?? "");
1331
- setObservations(invoice.observations ?? "");
1332
- const invoiceItems = await provider.getInvoiceItems(editId);
1333
- setItems(invoiceItems.map((it) => ({
1334
- _id: fid(),
1335
- itemKind: it.itemKind,
1336
- referenceId: it.referenceId,
1337
- description: it.description,
1338
- quantity: it.quantity,
1339
- unitPrice: it.unitPrice,
1340
- discount: it.discount,
1341
- surcharge: it.surcharge
1342
- })));
1343
- const movements = await provider.getMovements({ direction: invoice.direction });
1344
- const invoiceMovements = movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0));
1345
- setInstallments(invoiceMovements.map((m) => ({
1346
- _id: fid(),
1347
- amount: m.amount,
1348
- dueDate: m.dueDate
1349
- })));
1350
- setLoaded(true);
1351
- setLoading(false);
1352
- })();
1353
- }, [editId]);
1354
- const totalAmount = items.reduce((sum, it) => {
1355
- return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
1356
- }, 0);
1357
- const prevItemCount = useRef(items.length);
1358
- useEffect(() => {
1359
- if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
1360
- const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
1361
- if (total > 0) {
1362
- setInstallments([{ _id: fid(), amount: total, dueDate: invoiceDate }]);
1363
- }
1364
- }
1365
- prevItemCount.current = items.length;
1366
- }, [items.length]);
1367
- useEffect(() => {
1368
- if (installments.length > 0 && totalAmount > 0 && !isEdit) {
1369
- const count = installments.length;
1370
- const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
1371
- const remainder = Math.round((totalAmount - perInstallment * count) * 100) / 100;
1372
- setInstallments((prev) => prev.map((inst, i) => ({
1373
- ...inst,
1374
- amount: i === 0 ? perInstallment + remainder : perInstallment
1375
- })));
1376
- }
1377
- }, [totalAmount]);
1378
- function addItemOfKind(kind) {
1379
- const newItem = {
1380
- _id: fid(),
1381
- itemKind: kind,
1382
- description: "",
1383
- quantity: 1,
1384
- unitPrice: 0
1385
- };
1386
- setItems([...items, newItem]);
1387
- setExpandedItemId(newItem._id);
1388
- setAddingItem(false);
1389
- }
1390
- function startAddItem() {
1391
- if (hasItemLookups) setAddingItem(true);
1392
- else addItemOfKind(defaultItemKind);
1393
- }
1394
- function updateItem(id, data) {
1395
- setItems(items.map((it) => it._id === id ? { ...it, ...data } : it));
1396
- }
1397
- function removeItem(id) {
1398
- setItems(items.filter((it) => it._id !== id));
1399
- }
1400
- function generateInstallments(count) {
1401
- if (count < 1 || totalAmount <= 0) return;
1402
- const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
1403
- const remainder = Math.round((totalAmount - perInstallment * count) * 100) / 100;
1404
- const base = new Date(invoiceDate);
1405
- const inst = [];
1406
- for (let i = 0; i < count; i++) {
1407
- const due = new Date(base);
1408
- due.setMonth(due.getMonth() + i + 1);
1409
- inst.push({
1410
- _id: fid(),
1411
- amount: i === 0 ? perInstallment + remainder : perInstallment,
1412
- dueDate: due.toISOString().slice(0, 10)
1413
- });
1414
- }
1415
- setInstallments(inst);
1416
- }
1417
- async function handleSave() {
1418
- if (!isEdit && !can("financial", "create")) return;
1419
- if (items.length === 0) {
1420
- toast.error(t2("common.formIncomplete"));
1421
- return;
1422
- }
1423
- if (!isEdit && await guardMovements(Math.max(1, installments.length)) === "blocked") return;
1424
- setSaving(true);
1425
- try {
1426
- const movInputs = installments.map((inst, i) => ({
1427
- direction,
1428
- movementKind: "bill",
1429
- amount: inst.amount,
1430
- dueDate: inst.dueDate,
1431
- installmentNumber: i + 1
1432
- }));
1433
- if (movInputs.length === 0) {
1434
- movInputs.push({
1435
- direction,
1436
- movementKind: "bill",
1437
- amount: totalAmount,
1438
- dueDate: invoiceDate,
1439
- installmentNumber: 1
1440
- });
1441
- }
1442
- if (isEdit && editId) {
1443
- await provider.updateInvoice(editId, {
1444
- invoiceDate,
1445
- contactId,
1446
- contactName: contactName || void 0,
1447
- unitId: unitId || void 0,
1448
- fiscalNumber: fiscalNumber || void 0,
1449
- observations: observations || void 0,
1450
- totalAmount
1451
- });
1452
- onSaved?.(editId);
1453
- } else {
1454
- const invoice = await createInvoice({
1455
- direction,
1456
- invoiceDate,
1457
- contactId,
1458
- contactName: contactName || void 0,
1459
- unitId: unitId || void 0,
1460
- fiscalNumber: fiscalNumber || void 0,
1461
- observations: observations || void 0,
1462
- items: items.map(({ _id, ...rest }) => rest),
1463
- installments: movInputs
1464
- });
1465
- invalidateLimit("movements_month");
1466
- onSaved?.(invoice.id);
1467
- }
1468
- } finally {
1469
- setSaving(false);
1470
- }
1471
- }
1472
- const fields = {
1473
- contactName,
1474
- contactId,
1475
- unitId,
1476
- invoiceDate,
1477
- fiscalNumber,
1478
- observations,
1479
- items: items.map(({ _id, ...rest }) => rest),
1480
- installments: installments.map(({ _id, ...rest }) => rest)
1481
- };
1482
- const snapshot = useRef(null);
1483
- useEffect(() => {
1484
- if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
1485
- }, [loaded]);
1486
- const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
1487
- useSaveBar({
1488
- dirty,
1489
- saving,
1490
- onSave: () => {
1491
- void handleSave();
1492
- },
1493
- onDiscard: () => onSaved?.(),
1494
- saveLabel: t2("financial.invoiceForm.save")
1495
- });
1496
- const title = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
1497
- const subtitle = isEdit ? t2("financial.invoiceForm.editing") : t2("financial.invoiceForm.creating");
1498
- return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
1499
- /* @__PURE__ */ jsx(
1500
- SubpageHeader,
1501
- {
1502
- title,
1503
- subtitle,
1504
- onBack: onSaved,
1505
- parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
1506
- actions: isEdit ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
1507
- /* @__PURE__ */ jsx(
1508
- Button,
1509
- {
1510
- variant: "outline",
1511
- size: "icon",
1512
- onClick: () => setMenuOpen(!menuOpen),
1513
- "aria-label": "More actions",
1514
- children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" })
1515
- }
1516
- ),
1517
- menuOpen && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute right-0 top-full mt-1 w-44 rounded-lg border bg-popover shadow-md z-20 py-1", style: { animation: "field-slide-in 150ms ease-out" }, children: /* @__PURE__ */ jsxs(
1518
- "button",
1519
- {
1520
- onClick: () => {
1521
- setConfirmCancel(true);
1522
- setMenuOpen(false);
1523
- },
1524
- className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive hover:bg-destructive/10 transition-colors",
1525
- children: [
1526
- /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
1527
- " ",
1528
- t2("financial.invoiceForm.cancelInvoice")
1529
- ]
1530
- }
1531
- ) })
1532
- ] }) }) : null
1533
- }
1534
- ),
1535
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card shadow-sm p-5", children: /* @__PURE__ */ jsxs("div", { className: `grid gap-4 ${hasLocations ? "sm:grid-cols-12" : "sm:grid-cols-10"}`, children: [
1536
- /* @__PURE__ */ jsx("div", { className: hasLocations ? "sm:col-span-4" : "sm:col-span-4", children: /* @__PURE__ */ jsx(
1537
- SearchSelect,
1538
- {
1539
- label: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"),
1540
- required: true,
1541
- value: contactId ?? "",
1542
- displayValue: contactName,
1543
- onChange: (id, opt) => {
1544
- setContactId(id || void 0);
1545
- setContactName(opt?.label ?? "");
1546
- },
1547
- onSearch: async (q) => {
1548
- if (!config.contactLookup) return [];
1549
- const results = await config.contactLookup.search(q);
1550
- return results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, group: r.group, data: r }));
1551
- },
1552
- placeholder: t2("financial.invoiceForm.searchType")
1553
- }
1554
- ) }),
1555
- hasLocations && /* @__PURE__ */ jsx("div", { className: "sm:col-span-3", children: /* @__PURE__ */ jsx(
1556
- LocationSelector,
1557
- {
1558
- locations,
1559
- value: unitId,
1560
- onChange: setUnitId
1561
- }
1562
- ) }),
1563
- /* @__PURE__ */ jsxs("div", { className: hasLocations ? "sm:col-span-2" : "sm:col-span-3", children: [
1564
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
1565
- /* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
1566
- ] }),
1567
- /* @__PURE__ */ jsxs("div", { className: hasLocations ? "sm:col-span-3" : "sm:col-span-3", children: [
1568
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.fiscalNumber") }),
1569
- /* @__PURE__ */ jsx(
1570
- "input",
1571
- {
1572
- type: "text",
1573
- value: fiscalNumber,
1574
- onChange: (e) => setFiscalNumber(e.target.value),
1575
- placeholder: t2("financial.invoiceForm.fiscalNumber"),
1576
- className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
1577
- }
1578
- )
1579
- ] })
1580
- ] }) }),
1581
- loading ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [
1582
- /* @__PURE__ */ jsxs("div", { className: "lg:col-span-2 rounded-lg border bg-card shadow-sm overflow-hidden", children: [
1583
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-4 py-3 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1584
- /* @__PURE__ */ jsx(LayoutList, { className: "h-4 w-4 text-muted-foreground" }),
1585
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.items") })
1586
- ] }) }),
1587
- /* @__PURE__ */ jsx("div", { className: "p-4 space-y-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-2 py-3", children: [
1588
- /* @__PURE__ */ jsx("div", { className: "h-4 w-4 rounded bg-muted animate-pulse" }),
1589
- /* @__PURE__ */ jsx("div", { className: "h-4 w-8 rounded bg-muted animate-pulse" }),
1590
- /* @__PURE__ */ jsx("div", { className: "h-4 w-16 rounded bg-muted animate-pulse" }),
1591
- /* @__PURE__ */ jsx("div", { className: "h-4 flex-1 rounded bg-muted animate-pulse" }),
1592
- /* @__PURE__ */ jsx("div", { className: "h-4 w-20 rounded bg-muted animate-pulse" }),
1593
- /* @__PURE__ */ jsx("div", { className: "h-4 w-16 rounded bg-muted animate-pulse" })
1594
- ] }, i)) })
1595
- ] }),
1596
- /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
1597
- /* @__PURE__ */ jsx("div", { className: "px-5 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.installments") }) }),
1598
- /* @__PURE__ */ jsx("div", { className: "p-4 space-y-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "rounded border p-2.5 space-y-2", children: [
1599
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
1600
- /* @__PURE__ */ jsx("div", { className: "h-3 w-6 rounded bg-muted animate-pulse" }),
1601
- /* @__PURE__ */ jsx("div", { className: "h-4 w-16 rounded bg-muted animate-pulse" })
1602
- ] }),
1603
- /* @__PURE__ */ jsx("div", { className: "h-7 rounded bg-muted animate-pulse" })
1604
- ] }, i)) })
1605
- ] })
1606
- ] }) : /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [
1607
- /* @__PURE__ */ jsxs("div", { className: "lg:col-span-2 rounded-lg border bg-card shadow-sm overflow-hidden flex flex-col", children: [
1608
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b", children: [
1609
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1610
- /* @__PURE__ */ jsx(LayoutList, { className: "h-4 w-4 text-muted-foreground" }),
1611
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.items") })
1612
- ] }),
1613
- /* @__PURE__ */ jsxs(
1614
- "button",
1615
- {
1616
- onClick: startAddItem,
1617
- className: "inline-flex items-center gap-1.5 rounded-lg bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors",
1618
- children: [
1619
- /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
1620
- " ",
1621
- t2("financial.invoiceForm.addItem")
1622
- ]
1623
- }
1624
- )
1625
- ] }),
1626
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
1627
- addingItem && /* @__PURE__ */ jsx(
1628
- AddItemStep,
1629
- {
1630
- itemTypes,
1631
- onSelect: addItemOfKind,
1632
- onCancel: () => setAddingItem(false)
1633
- }
1634
- ),
1635
- items.length === 0 && !addingItem ? /* @__PURE__ */ jsxs("div", { className: "py-10 text-center", children: [
1636
- /* @__PURE__ */ jsx(LayoutList, { className: "h-8 w-8 text-muted-foreground/30 mx-auto mb-2" }),
1637
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.invoiceForm.noItems") }),
1638
- /* @__PURE__ */ jsx("button", { onClick: startAddItem, className: "text-xs text-primary hover:underline mt-1", children: t2("financial.invoiceForm.addFirstItem") })
1639
- ] }) : items.map((item, i) => /* @__PURE__ */ jsx(
1640
- ItemRow,
1641
- {
1642
- item,
1643
- index: i,
1644
- itemTypes,
1645
- currency,
1646
- expanded: expandedItemId === item._id,
1647
- onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
1648
- onUpdate: (data) => updateItem(item._id, data),
1649
- onRemove: () => {
1650
- removeItem(item._id);
1651
- if (expandedItemId === item._id) setExpandedItemId(null);
1652
- },
1653
- entityLookups: config.entityLookups,
1654
- showTypeSelector: hasItemLookups
1655
- },
1656
- item._id
1657
- ))
1658
- ] }),
1659
- items.length > 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 border-t bg-muted/20 flex justify-between items-center mt-auto", children: [
1660
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }) }),
1661
- /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
1662
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
1663
- t2("financial.invoiceForm.total"),
1664
- " "
1665
- ] }),
1666
- /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: formatCurrency(totalAmount, currency) })
1667
- ] })
1668
- ] })
1669
- ] }),
1670
- /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
1671
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Installments" }) }),
1672
- /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
1673
- /* @__PURE__ */ jsx("div", { className: "flex gap-1.5 flex-wrap", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
1674
- "button",
1675
- {
1676
- onClick: () => generateInstallments(n),
1677
- disabled: totalAmount <= 0,
1678
- className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
1679
- children: [
1680
- n,
1681
- "x"
1682
- ]
1683
- },
1684
- n
1685
- )) }),
1686
- installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground py-4 text-center", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: installments.map((inst, i) => /* @__PURE__ */ jsxs("div", { className: "rounded border p-2.5 space-y-1.5", children: [
1687
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1688
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-muted-foreground", children: [
1689
- "#",
1690
- i + 1
1691
- ] }),
1692
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: formatCurrency(inst.amount, currency) })
1693
- ] }),
1694
- /* @__PURE__ */ jsx(
1695
- DatePicker,
1696
- {
1697
- value: inst.dueDate,
1698
- onChange: (v) => {
1699
- const updated = [...installments];
1700
- updated[i] = { ...inst, dueDate: v };
1701
- setInstallments(updated);
1702
- }
1703
- }
1704
- )
1705
- ] }, inst._id)) })
1706
- ] })
1707
- ] })
1708
- ] }),
1709
- /* @__PURE__ */ jsxs("div", { children: [
1710
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.observations") }),
1711
- /* @__PURE__ */ jsx(
1712
- "textarea",
1713
- {
1714
- value: observations,
1715
- onChange: (e) => setObservations(e.target.value),
1716
- placeholder: t2("financial.invoiceForm.additionalNotes"),
1717
- rows: 2,
1718
- className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring resize-none"
1719
- }
1720
- )
1721
- ] }),
1722
- confirmCancel && createPortal(
1723
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setConfirmCancel(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-modal border bg-card shadow-lg mx-4 p-5", onClick: (e) => e.stopPropagation(), style: { animation: "field-slide-in 200ms ease-out" }, children: [
1724
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
1725
- /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 shrink-0", children: /* @__PURE__ */ jsx(Ban, { className: "h-5 w-5 text-destructive" }) }),
1726
- /* @__PURE__ */ jsxs("div", { children: [
1727
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.cancelInvoice") }),
1728
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.cancelConfirm") })
1729
- ] })
1730
- ] }),
1731
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-4", children: t2("financial.invoiceForm.cancelDesc") }),
1732
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
1733
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmCancel(false), children: t2("financial.invoiceForm.keepInvoice") }),
1734
- /* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancelInvoice, disabled: cancelling, children: [
1735
- /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
1736
- cancelling ? t2("financial.invoiceForm.cancelling") : t2("financial.invoiceForm.yesCancel")
1737
- ] })
1738
- ] })
1739
- ] }) }),
1740
- document.body
1741
- ),
1742
- /* @__PURE__ */ jsx("style", { children: `
1743
- @keyframes field-slide-in {
1744
- from { opacity: 0; transform: translateY(8px); }
1745
- to { opacity: 1; transform: translateY(0); }
1746
- }
1747
- ` })
918
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.summaryTotal") }),
919
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: formatCurrency(summary.totalAmount, currency) })
920
+ ] }),
921
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
922
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
923
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: receivedLabel }),
924
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-success", children: formatCurrency(summary.paidAmount, currency) })
925
+ ] }),
926
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
927
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
928
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: pendingLabel }),
929
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-warning", children: formatCurrency(summary.openAmount, currency) })
930
+ ] })
931
+ ] })
1748
932
  ] });
1749
933
  }
1750
934
  var ACCOUNT_TYPES_FOR_TX = {
@@ -2022,39 +1206,634 @@ function PaymentModal({ movement, onClose, onPaid }) {
2022
1206
  from { opacity: 0; transform: translateY(8px); }
2023
1207
  to { opacity: 1; transform: translateY(0); }
2024
1208
  }
2025
- ` }),
2026
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-4 border-t sticky bottom-0 bg-card", children: [
2027
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, children: t2("financial.payment.cancel") }),
2028
- /* @__PURE__ */ jsxs(
2029
- Button,
2030
- {
2031
- variant: "default",
2032
- size: "sm",
2033
- onClick: handlePay,
2034
- disabled: amount <= 0 || !selectedTypeId || !bankAccountId || saving,
2035
- children: [
2036
- /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
2037
- saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: formatCurrency(amount, currency) })
2038
- ]
2039
- }
2040
- )
2041
- ] })
2042
- ]
1209
+ ` }),
1210
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-4 border-t sticky bottom-0 bg-card", children: [
1211
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, children: t2("financial.payment.cancel") }),
1212
+ /* @__PURE__ */ jsxs(
1213
+ Button,
1214
+ {
1215
+ variant: "default",
1216
+ size: "sm",
1217
+ onClick: handlePay,
1218
+ disabled: amount <= 0 || !selectedTypeId || !bankAccountId || saving,
1219
+ children: [
1220
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
1221
+ saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: formatCurrency(amount, currency) })
1222
+ ]
1223
+ }
1224
+ )
1225
+ ] })
1226
+ ]
1227
+ }
1228
+ )
1229
+ }
1230
+ ),
1231
+ document.body
1232
+ );
1233
+ }
1234
+ function LocationSelector({ locations, value, onChange }) {
1235
+ const t2 = useTranslation();
1236
+ const [open, setOpen] = useState(false);
1237
+ const [search, setSearch] = useState("");
1238
+ const ref = useRef(null);
1239
+ useEffect(() => {
1240
+ function handler(e) {
1241
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
1242
+ }
1243
+ document.addEventListener("mousedown", handler);
1244
+ return () => document.removeEventListener("mousedown", handler);
1245
+ }, []);
1246
+ const selected = locations.find((l) => l.id === value);
1247
+ const filtered = locations.filter(
1248
+ (l) => !search.trim() || l.name.toLowerCase().includes(search.toLowerCase())
1249
+ );
1250
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
1251
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.unit") }),
1252
+ /* @__PURE__ */ jsxs(
1253
+ "button",
1254
+ {
1255
+ type: "button",
1256
+ onClick: () => setOpen(!open),
1257
+ 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",
1258
+ children: [
1259
+ /* @__PURE__ */ jsx(Building2, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
1260
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? t2("financial.invoiceForm.selectUnit") }),
1261
+ /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
1262
+ ]
1263
+ }
1264
+ ),
1265
+ open && /* @__PURE__ */ jsxs("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden min-w-[260px]", children: [
1266
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [
1267
+ /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }),
1268
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.selectUnitTitle") }),
1269
+ /* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "ml-auto text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }) })
1270
+ ] }),
1271
+ /* @__PURE__ */ jsx("div", { className: "p-2 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5", children: [
1272
+ /* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground" }),
1273
+ /* @__PURE__ */ jsx(
1274
+ "input",
1275
+ {
1276
+ type: "text",
1277
+ autoFocus: true,
1278
+ value: search,
1279
+ onChange: (e) => setSearch(e.target.value),
1280
+ placeholder: t2("financial.invoiceForm.searchUnit"),
1281
+ className: "flex-1 bg-transparent text-sm outline-none"
1282
+ }
1283
+ )
1284
+ ] }) }),
1285
+ /* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-auto py-1", children: [
1286
+ filtered.map((loc) => /* @__PURE__ */ jsxs(
1287
+ "button",
1288
+ {
1289
+ onClick: () => {
1290
+ onChange(loc.id);
1291
+ setOpen(false);
1292
+ setSearch("");
1293
+ },
1294
+ className: `w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-muted/50 transition-colors ${loc.id === value ? "bg-muted/30" : ""}`,
1295
+ children: [
1296
+ /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-muted shrink-0", children: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }) }),
1297
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1298
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: loc.name }),
1299
+ loc.isHQ && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoiceForm.headquarters") })
1300
+ ] })
1301
+ ]
1302
+ },
1303
+ loc.id
1304
+ )),
1305
+ filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: t2("financial.invoiceForm.noUnitsFound") })
1306
+ ] })
1307
+ ] })
1308
+ ] });
1309
+ }
1310
+ var formId = 1;
1311
+ function fid() {
1312
+ return `f${formId++}`;
1313
+ }
1314
+ function InvoiceFormView({ direction, editId, onSaved }) {
1315
+ const t2 = useTranslation();
1316
+ const config = useFinancialConfig();
1317
+ const { currency, itemTypes, locations } = config;
1318
+ const provider = useFinancialProvider();
1319
+ const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
1320
+ const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
1321
+ const createInvoice = useFinancialStore((s) => s.createInvoice);
1322
+ const guardMovements = useLimitGuard("movements_month");
1323
+ const can = usePermissionOptional();
1324
+ const hasLocations = locations.length > 0;
1325
+ const isEdit = !!editId;
1326
+ const [loaded, setLoaded] = useState(!editId);
1327
+ const [loading, setLoading] = useState(!!editId);
1328
+ const [contactName, setContactName] = useState("");
1329
+ const [contactId, setContactId] = useState();
1330
+ const [unitId, setUnitId] = useState(locations[0]?.id ?? "");
1331
+ const [invoiceDate, setInvoiceDate] = useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
1332
+ const [fiscalNumber, setFiscalNumber] = useState("");
1333
+ const [observations, setObservations] = useState("");
1334
+ const [items, setItems] = useState([]);
1335
+ const [expandedItemId, setExpandedItemId] = useState(null);
1336
+ const [addingItem, setAddingItem] = useState(false);
1337
+ const [installments, setInstallments] = useState([]);
1338
+ const [savedMovements, setSavedMovements] = useState([]);
1339
+ const [payingMovement, setPayingMovement] = useState(null);
1340
+ const [saving, setSaving] = useState(false);
1341
+ const [menuOpen, setMenuOpen] = useState(false);
1342
+ const [confirmCancel, setConfirmCancel] = useState(false);
1343
+ const [cancelling, setCancelling] = useState(false);
1344
+ const menuRef = useRef(null);
1345
+ const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
1346
+ useEffect(() => {
1347
+ if (!menuOpen) return;
1348
+ function handleClick(e) {
1349
+ if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
1350
+ }
1351
+ document.addEventListener("mousedown", handleClick);
1352
+ return () => document.removeEventListener("mousedown", handleClick);
1353
+ }, [menuOpen]);
1354
+ async function handleCancelInvoice() {
1355
+ if (!editId) return;
1356
+ setCancelling(true);
1357
+ try {
1358
+ await cancelInvoice(editId);
1359
+ onSaved?.();
1360
+ } catch {
1361
+ } finally {
1362
+ setCancelling(false);
1363
+ }
1364
+ }
1365
+ useEffect(() => {
1366
+ if (!editId || loaded) return;
1367
+ setLoading(true);
1368
+ (async () => {
1369
+ const invoice = await provider.getInvoiceById(editId);
1370
+ if (!invoice) return;
1371
+ setContactName(invoice.contactName ?? "");
1372
+ setContactId(invoice.contactId);
1373
+ setUnitId(invoice.unitId ?? locations[0]?.id ?? "");
1374
+ setInvoiceDate(invoice.invoiceDate);
1375
+ setFiscalNumber(invoice.fiscalNumber ?? "");
1376
+ setObservations(invoice.observations ?? "");
1377
+ const invoiceItems = await provider.getInvoiceItems(editId);
1378
+ setItems(invoiceItems.map((it) => ({
1379
+ _id: fid(),
1380
+ itemKind: it.itemKind,
1381
+ referenceId: it.referenceId,
1382
+ description: it.description,
1383
+ quantity: it.quantity,
1384
+ unitPrice: it.unitPrice,
1385
+ discount: it.discount,
1386
+ surcharge: it.surcharge
1387
+ })));
1388
+ const movements = await provider.getMovements({ direction: invoice.direction });
1389
+ const invoiceMovements = movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0));
1390
+ setSavedMovements(invoiceMovements);
1391
+ setInstallments(invoiceMovements.map((m) => ({
1392
+ _id: fid(),
1393
+ amount: m.amount,
1394
+ dueDate: m.dueDate
1395
+ })));
1396
+ setLoaded(true);
1397
+ setLoading(false);
1398
+ })();
1399
+ }, [editId]);
1400
+ const totalAmount = items.reduce((sum, it) => {
1401
+ return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
1402
+ }, 0);
1403
+ const prevItemCount = useRef(items.length);
1404
+ useEffect(() => {
1405
+ if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
1406
+ const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
1407
+ if (total > 0) {
1408
+ setInstallments([{ _id: fid(), amount: total, dueDate: invoiceDate }]);
1409
+ }
1410
+ }
1411
+ prevItemCount.current = items.length;
1412
+ }, [items.length]);
1413
+ useEffect(() => {
1414
+ if (installments.length > 0 && totalAmount > 0 && !isEdit) {
1415
+ const count = installments.length;
1416
+ const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
1417
+ const remainder = Math.round((totalAmount - perInstallment * count) * 100) / 100;
1418
+ setInstallments((prev) => prev.map((inst, i) => ({
1419
+ ...inst,
1420
+ amount: i === 0 ? perInstallment + remainder : perInstallment
1421
+ })));
1422
+ }
1423
+ }, [totalAmount]);
1424
+ function addItemOfKind(kind) {
1425
+ const newItem = {
1426
+ _id: fid(),
1427
+ itemKind: kind,
1428
+ description: "",
1429
+ quantity: 1,
1430
+ unitPrice: 0
1431
+ };
1432
+ setItems([...items, newItem]);
1433
+ setExpandedItemId(newItem._id);
1434
+ setAddingItem(false);
1435
+ }
1436
+ function startAddItem() {
1437
+ if (hasItemLookups) setAddingItem(true);
1438
+ else addItemOfKind(defaultItemKind);
1439
+ }
1440
+ function updateItem(id, data) {
1441
+ setItems(items.map((it) => it._id === id ? { ...it, ...data } : it));
1442
+ }
1443
+ function removeItem(id) {
1444
+ setItems(items.filter((it) => it._id !== id));
1445
+ }
1446
+ function generateInstallments(count) {
1447
+ if (count < 1 || totalAmount <= 0) return;
1448
+ const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
1449
+ const remainder = Math.round((totalAmount - perInstallment * count) * 100) / 100;
1450
+ const base = new Date(invoiceDate);
1451
+ const inst = [];
1452
+ for (let i = 0; i < count; i++) {
1453
+ const due = new Date(base);
1454
+ due.setMonth(due.getMonth() + i + 1);
1455
+ inst.push({
1456
+ _id: fid(),
1457
+ amount: i === 0 ? perInstallment + remainder : perInstallment,
1458
+ dueDate: due.toISOString().slice(0, 10)
1459
+ });
1460
+ }
1461
+ setInstallments(inst);
1462
+ }
1463
+ async function handleSave() {
1464
+ if (!isEdit && !can("financial", "create")) return;
1465
+ if (items.length === 0) {
1466
+ toast.error(t2("common.formIncomplete"));
1467
+ return;
1468
+ }
1469
+ if (!isEdit && await guardMovements(Math.max(1, installments.length)) === "blocked") return;
1470
+ setSaving(true);
1471
+ try {
1472
+ const movInputs = installments.map((inst, i) => ({
1473
+ direction,
1474
+ movementKind: "bill",
1475
+ amount: inst.amount,
1476
+ dueDate: inst.dueDate,
1477
+ installmentNumber: i + 1
1478
+ }));
1479
+ if (movInputs.length === 0) {
1480
+ movInputs.push({
1481
+ direction,
1482
+ movementKind: "bill",
1483
+ amount: totalAmount,
1484
+ dueDate: invoiceDate,
1485
+ installmentNumber: 1
1486
+ });
1487
+ }
1488
+ if (isEdit && editId) {
1489
+ await provider.updateInvoice(editId, {
1490
+ invoiceDate,
1491
+ contactId,
1492
+ contactName: contactName || void 0,
1493
+ unitId: unitId || void 0,
1494
+ fiscalNumber: fiscalNumber || void 0,
1495
+ observations: observations || void 0,
1496
+ totalAmount
1497
+ });
1498
+ onSaved?.(editId);
1499
+ } else {
1500
+ const invoice = await createInvoice({
1501
+ direction,
1502
+ invoiceDate,
1503
+ contactId,
1504
+ contactName: contactName || void 0,
1505
+ unitId: unitId || void 0,
1506
+ fiscalNumber: fiscalNumber || void 0,
1507
+ observations: observations || void 0,
1508
+ items: items.map(({ _id, ...rest }) => rest),
1509
+ installments: movInputs
1510
+ });
1511
+ invalidateLimit("movements_month");
1512
+ onSaved?.(invoice.id);
1513
+ }
1514
+ } finally {
1515
+ setSaving(false);
1516
+ }
1517
+ }
1518
+ const fields = {
1519
+ contactName,
1520
+ contactId,
1521
+ unitId,
1522
+ invoiceDate,
1523
+ fiscalNumber,
1524
+ observations,
1525
+ items: items.map(({ _id, ...rest }) => rest),
1526
+ installments: installments.map(({ _id, ...rest }) => rest)
1527
+ };
1528
+ const snapshot = useRef(null);
1529
+ useEffect(() => {
1530
+ if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
1531
+ }, [loaded]);
1532
+ const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
1533
+ useSaveBar({
1534
+ dirty,
1535
+ saving,
1536
+ onSave: () => {
1537
+ void handleSave();
1538
+ },
1539
+ onDiscard: () => onSaved?.(),
1540
+ saveLabel: t2("financial.invoiceForm.save")
1541
+ });
1542
+ const title = isEdit ? t2("financial.invoiceForm.editTitle") : t2("financial.invoiceForm.newTitle");
1543
+ const subtitle = isEdit ? t2("financial.invoiceForm.editing") : t2("financial.invoiceForm.creating");
1544
+ if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, {});
1545
+ const money = (v) => formatCurrency(v, currency);
1546
+ const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
1547
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
1548
+ menuOpen && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute right-0 top-full z-20 mt-1 w-44 rounded-lg border bg-popover py-1 shadow-md", style: { animation: "field-slide-in 150ms ease-out" }, children: /* @__PURE__ */ jsxs(
1549
+ "button",
1550
+ {
1551
+ onClick: () => {
1552
+ setConfirmCancel(true);
1553
+ setMenuOpen(false);
1554
+ },
1555
+ className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive transition-colors hover:bg-destructive/10",
1556
+ children: [
1557
+ /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
1558
+ " ",
1559
+ t2("financial.invoiceForm.cancelInvoice")
1560
+ ]
1561
+ }
1562
+ ) })
1563
+ ] }) : null;
1564
+ const paidSoFar = savedMovements.reduce((sum, m) => sum + (m.paidAmount ?? 0), 0);
1565
+ const hasPayments = paidSoFar > 0;
1566
+ const installmentsEditor = /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoiceForm.installments"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1567
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
1568
+ "button",
1569
+ {
1570
+ type: "button",
1571
+ onClick: () => generateInstallments(n),
1572
+ disabled: totalAmount <= 0,
1573
+ className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
1574
+ children: [
1575
+ n,
1576
+ "x"
1577
+ ]
1578
+ },
1579
+ n
1580
+ )) }),
1581
+ installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: installments.map((inst, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded border p-2.5", children: [
1582
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1583
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-muted-foreground", children: [
1584
+ "#",
1585
+ i + 1
1586
+ ] }),
1587
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children: money(inst.amount) })
1588
+ ] }),
1589
+ /* @__PURE__ */ jsx(
1590
+ DatePicker,
1591
+ {
1592
+ value: inst.dueDate,
1593
+ onChange: (v) => {
1594
+ const updated = [...installments];
1595
+ updated[i] = { ...inst, dueDate: v };
1596
+ setInstallments(updated);
1597
+ }
1598
+ }
1599
+ )
1600
+ ] }, inst._id)) })
1601
+ ] }) });
1602
+ const scheduleSection = isEdit ? /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1603
+ hasPayments && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.scheduleLocked") }),
1604
+ !hasPayments && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
1605
+ "button",
1606
+ {
1607
+ type: "button",
1608
+ onClick: () => generateInstallments(n),
1609
+ disabled: totalAmount <= 0,
1610
+ className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
1611
+ children: [
1612
+ n,
1613
+ "x"
1614
+ ]
1615
+ },
1616
+ n
1617
+ )) }),
1618
+ installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded border", children: installments.map((inst, i) => {
1619
+ const saved = savedMovements[i];
1620
+ const isPaid = saved?.status === "paid";
1621
+ const isPartial = saved?.status === "partial";
1622
+ const StatusIcon = isPaid ? CircleCheckBig : CircleDashed;
1623
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 px-3 py-2.5", children: [
1624
+ /* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
1625
+ "#",
1626
+ i + 1
1627
+ ] }),
1628
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money(inst.amount) }),
1629
+ saved && /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${isPaid ? "bg-success-soft text-success-soft-foreground" : isPartial ? "bg-warning-soft text-warning-soft-foreground" : "bg-muted text-muted-foreground"}`, children: [
1630
+ /* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5" }),
1631
+ t2(isPaid ? "financial.invoice.statusPaid" : isPartial ? "financial.invoice.statusPartial" : "financial.invoice.statusPending")
1632
+ ] }),
1633
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
1634
+ !hasPayments ? /* @__PURE__ */ jsx(
1635
+ DatePicker,
1636
+ {
1637
+ value: inst.dueDate,
1638
+ variant: "chip",
1639
+ onChange: (v) => {
1640
+ const updated = [...installments];
1641
+ updated[i] = { ...inst, dueDate: v };
1642
+ setInstallments(updated);
1643
+ }
1644
+ }
1645
+ ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
1646
+ saved && !isPaid && /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: () => setPayingMovement(saved), children: [
1647
+ /* @__PURE__ */ jsx(DollarSign, { className: "mr-1 h-3.5 w-3.5" }),
1648
+ t2("financial.invoice.pay")
1649
+ ] })
1650
+ ] })
1651
+ ] }, inst._id);
1652
+ }) })
1653
+ ] }) }) : null;
1654
+ const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
1655
+ /* @__PURE__ */ jsx(OrderFormBlock, { title: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"), children: /* @__PURE__ */ jsx(
1656
+ SearchSelect,
1657
+ {
1658
+ required: true,
1659
+ value: contactId ?? "",
1660
+ displayValue: contactName,
1661
+ onChange: (id, opt) => {
1662
+ setContactId(id || void 0);
1663
+ setContactName(opt?.label ?? "");
1664
+ },
1665
+ onSearch: async (q) => {
1666
+ if (!config.contactLookup) return [];
1667
+ const results = await config.contactLookup.search(q);
1668
+ return results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, group: r.group, data: r }));
1669
+ },
1670
+ placeholder: t2("financial.invoiceForm.searchType")
1671
+ }
1672
+ ) }),
1673
+ /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1674
+ hasLocations && /* @__PURE__ */ jsx(LocationSelector, { locations, value: unitId, onChange: setUnitId }),
1675
+ /* @__PURE__ */ jsxs("div", { children: [
1676
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
1677
+ /* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
1678
+ ] }),
1679
+ /* @__PURE__ */ jsxs("div", { children: [
1680
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.fiscalNumber") }),
1681
+ /* @__PURE__ */ jsx(
1682
+ "input",
1683
+ {
1684
+ type: "text",
1685
+ value: fiscalNumber,
1686
+ onChange: (e) => setFiscalNumber(e.target.value),
1687
+ placeholder: t2("financial.invoiceForm.fiscalNumber"),
1688
+ className: "mt-1 w-full rounded-input border border-input bg-card px-3 py-2 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring"
2043
1689
  }
2044
1690
  )
1691
+ ] })
1692
+ ] }) }),
1693
+ !isEdit && installmentsEditor,
1694
+ /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoiceForm.observations"), children: /* @__PURE__ */ jsx(
1695
+ "textarea",
1696
+ {
1697
+ value: observations,
1698
+ onChange: (e) => setObservations(e.target.value),
1699
+ placeholder: t2("financial.invoiceForm.additionalNotes"),
1700
+ rows: 2,
1701
+ className: "w-full resize-none rounded-input border border-input bg-card px-3 py-2 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring"
1702
+ }
1703
+ ) })
1704
+ ] });
1705
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1706
+ /* @__PURE__ */ jsxs(
1707
+ OrderFormView,
1708
+ {
1709
+ title,
1710
+ subtitle,
1711
+ parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
1712
+ onBack: onSaved,
1713
+ actions,
1714
+ sideSections,
1715
+ children: [
1716
+ /* @__PURE__ */ jsx(
1717
+ OrderFormItemsCard,
1718
+ {
1719
+ label: t2("financial.invoiceForm.items"),
1720
+ addLabel: t2("financial.invoiceForm.addItem"),
1721
+ onAdd: startAddItem,
1722
+ count: items.length,
1723
+ countLabel: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
1724
+ totalLabel: t2("financial.invoiceForm.total"),
1725
+ total: money(totalAmount),
1726
+ emptyLabel: t2("financial.invoiceForm.noItems"),
1727
+ emptyActionLabel: t2("financial.invoiceForm.addFirstItem"),
1728
+ header: addingItem && /* @__PURE__ */ jsx(
1729
+ OrderFormAddItemStep,
1730
+ {
1731
+ itemTypes,
1732
+ onSelect: addItemOfKind,
1733
+ onCancel: () => setAddingItem(false),
1734
+ label: t2("financial.invoiceForm.selectItemType"),
1735
+ cancelLabel: t2("financial.invoiceForm.cancel")
1736
+ }
1737
+ ),
1738
+ children: items.map((item, i) => /* @__PURE__ */ jsx(
1739
+ OrderFormItemRow,
1740
+ {
1741
+ index: i,
1742
+ itemKind: item.itemKind,
1743
+ description: item.description,
1744
+ quantity: item.quantity,
1745
+ unitPrice: item.unitPrice,
1746
+ discount: item.discount,
1747
+ surcharge: item.surcharge,
1748
+ referenceId: item.referenceId,
1749
+ itemTypes,
1750
+ currency,
1751
+ formatMoney: money,
1752
+ expanded: expandedItemId === item._id,
1753
+ onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
1754
+ onChange: (patch) => updateItem(item._id, patch),
1755
+ onRemove: () => {
1756
+ removeItem(item._id);
1757
+ if (expandedItemId === item._id) setExpandedItemId(null);
1758
+ },
1759
+ entityLookups: config.entityLookups,
1760
+ showTypeSelector: hasItemLookups,
1761
+ allowSurcharge: true,
1762
+ labels: {
1763
+ item: t2("financial.invoiceForm.item"),
1764
+ description: t2("financial.invoiceForm.description"),
1765
+ descriptionPlaceholder: t2("financial.invoiceForm.itemDescription"),
1766
+ noDescription: t2("financial.invoiceForm.noDescription"),
1767
+ qty: t2("financial.invoiceForm.qty"),
1768
+ unitPrice: t2("financial.invoiceForm.unitPrice"),
1769
+ discount: t2("financial.invoiceForm.discount"),
1770
+ surcharge: t2("financial.invoiceForm.surcharge"),
1771
+ adjustments: t2("financial.invoiceForm.discountSurcharge"),
1772
+ removeAdjustments: t2("financial.invoiceForm.removeAdjustments"),
1773
+ done: t2("financial.invoiceForm.done")
1774
+ }
1775
+ },
1776
+ item._id
1777
+ ))
1778
+ }
1779
+ ),
1780
+ scheduleSection
1781
+ ]
2045
1782
  }
2046
1783
  ),
2047
- document.body
2048
- );
1784
+ payingMovement && /* @__PURE__ */ jsx(
1785
+ PaymentModal,
1786
+ {
1787
+ movement: payingMovement,
1788
+ onClose: () => setPayingMovement(null),
1789
+ onPaid: () => {
1790
+ setPayingMovement(null);
1791
+ void (async () => {
1792
+ if (!editId) return;
1793
+ const movements = await provider.getMovements({ direction });
1794
+ setSavedMovements(
1795
+ movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
1796
+ );
1797
+ })();
1798
+ }
1799
+ }
1800
+ ),
1801
+ confirmCancel && createPortal(
1802
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setConfirmCancel(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-modal border bg-card shadow-lg mx-4 p-5", onClick: (e) => e.stopPropagation(), style: { animation: "field-slide-in 200ms ease-out" }, children: [
1803
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
1804
+ /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 shrink-0", children: /* @__PURE__ */ jsx(Ban, { className: "h-5 w-5 text-destructive" }) }),
1805
+ /* @__PURE__ */ jsxs("div", { children: [
1806
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.cancelInvoice") }),
1807
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.cancelConfirm") })
1808
+ ] })
1809
+ ] }),
1810
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-4", children: t2("financial.invoiceForm.cancelDesc") }),
1811
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
1812
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmCancel(false), children: t2("financial.invoiceForm.keepInvoice") }),
1813
+ /* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancelInvoice, disabled: cancelling, children: [
1814
+ /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
1815
+ cancelling ? t2("financial.invoiceForm.cancelling") : t2("financial.invoiceForm.yesCancel")
1816
+ ] })
1817
+ ] })
1818
+ ] }) }),
1819
+ document.body
1820
+ ),
1821
+ /* @__PURE__ */ jsx("style", { children: `
1822
+ @keyframes field-slide-in {
1823
+ from { opacity: 0; transform: translateY(8px); }
1824
+ to { opacity: 1; transform: translateY(0); }
1825
+ }
1826
+ ` })
1827
+ ] });
2049
1828
  }
2050
1829
  var STATUS_COLORS = {
2051
- open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
2052
- pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
2053
- partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial" },
2054
- paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid" },
2055
- overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue" },
2056
- draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
2057
- cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
1830
+ open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
1831
+ pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
1832
+ partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial", tone: "warning" },
1833
+ paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid", tone: "success" },
1834
+ overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue", tone: "destructive" },
1835
+ draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft", tone: "muted" },
1836
+ cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled", tone: "muted" }
2058
1837
  };
2059
1838
  function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel }) {
2060
1839
  const t2 = useTranslation();
@@ -2136,58 +1915,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
2136
1915
  }
2137
1916
  }
2138
1917
  if (loading) {
2139
- return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
2140
- /* @__PURE__ */ jsx(SubpageHeader, { title: "", onBack, parentLabel }),
2141
- /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card shadow-sm overflow-hidden", children: [
2142
- /* @__PURE__ */ jsx("div", { className: "h-1 bg-muted animate-pulse" }),
2143
- /* @__PURE__ */ jsxs("div", { className: "p-5 pb-4", children: [
2144
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
2145
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2146
- /* @__PURE__ */ jsx("div", { className: "h-10 w-10 rounded-lg bg-muted/40 animate-pulse" }),
2147
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2148
- /* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
2149
- /* @__PURE__ */ jsx("div", { className: "h-6 w-32 rounded bg-muted/40 animate-pulse" })
2150
- ] })
2151
- ] }),
2152
- /* @__PURE__ */ jsx("div", { className: "h-6 w-20 rounded-full bg-muted/40 animate-pulse" })
2153
- ] }),
2154
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3 mt-4 pt-4 border-t", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2155
- /* @__PURE__ */ jsx("div", { className: "h-3.5 w-3.5 rounded bg-muted/30 animate-pulse" }),
2156
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2157
- /* @__PURE__ */ jsx("div", { className: "h-2 w-10 rounded bg-muted/30 animate-pulse" }),
2158
- /* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/40 animate-pulse" })
2159
- ] })
2160
- ] }, i)) })
2161
- ] }),
2162
- /* @__PURE__ */ jsx("div", { className: "px-5 pt-3 pb-1.5", children: /* @__PURE__ */ jsx("div", { className: "h-3 w-12 rounded bg-muted/30 animate-pulse" }) }),
2163
- /* @__PURE__ */ jsxs("div", { className: "px-5", children: [
2164
- /* @__PURE__ */ jsxs("div", { className: "border-b py-2 flex justify-between", children: [
2165
- /* @__PURE__ */ jsx("div", { className: "h-2 w-24 rounded bg-muted/20 animate-pulse" }),
2166
- /* @__PURE__ */ jsx("div", { className: "h-2 w-16 rounded bg-muted/20 animate-pulse" })
2167
- ] }),
2168
- [1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-2.5 border-b last:border-0", children: [
2169
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2170
- /* @__PURE__ */ jsx("div", { className: "h-3 w-4 rounded bg-muted/30 animate-pulse" }),
2171
- /* @__PURE__ */ jsx("div", { className: "h-5 w-14 rounded bg-muted/30 animate-pulse" }),
2172
- /* @__PURE__ */ jsx("div", { className: "h-3 w-28 rounded bg-muted/40 animate-pulse" })
2173
- ] }),
2174
- /* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" })
2175
- ] }, i))
2176
- ] }),
2177
- /* @__PURE__ */ jsx("div", { className: "px-5 py-3 border-t bg-muted/10 flex justify-end", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-32 rounded bg-muted/40 animate-pulse" }) })
2178
- ] }),
2179
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2180
- /* @__PURE__ */ jsx("div", { className: "h-4 w-28 rounded bg-muted/30 animate-pulse" }),
2181
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card shadow-sm divide-y", children: [1].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
2182
- /* @__PURE__ */ jsx("div", { className: "h-3 w-5 rounded bg-muted/30 animate-pulse" }),
2183
- /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
2184
- /* @__PURE__ */ jsx("div", { className: "h-4 w-24 rounded bg-muted/40 animate-pulse" }),
2185
- /* @__PURE__ */ jsx("div", { className: "h-2 w-32 rounded bg-muted/30 animate-pulse" })
2186
- ] }),
2187
- /* @__PURE__ */ jsx("div", { className: "h-7 w-16 rounded-lg bg-muted/30 animate-pulse" })
2188
- ] }, i)) })
2189
- ] })
2190
- ] });
1918
+ return /* @__PURE__ */ jsx(OrderDetailSkeleton, {});
2191
1919
  }
2192
1920
  if (!invoice) {
2193
1921
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
@@ -2196,335 +1924,235 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
2196
1924
  ] });
2197
1925
  }
2198
1926
  const statusConfig = STATUS_COLORS[invoice.status] ?? STATUS_COLORS.pending;
2199
- const StatusIcon = statusConfig.icon;
1927
+ statusConfig.icon;
2200
1928
  const totalPaid = movements.reduce((sum, m) => sum + m.paidAmount, 0);
2201
1929
  const totalRemaining = invoice.totalAmount - totalPaid;
2202
1930
  const subtotal = items.reduce((sum, it) => sum + it.quantity * it.unitPrice, 0);
2203
1931
  const totalDiscount = items.reduce((sum, it) => sum + it.discount, 0);
2204
1932
  const totalSurcharge = items.reduce((sum, it) => sum + it.surcharge, 0);
2205
- const itemColumns = [
2206
- {
2207
- id: "index",
2208
- header: t2("financial.invoice.columnNumber"),
2209
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: row.index + 1 })
2210
- },
2211
- {
2212
- accessorKey: "description",
2213
- header: t2("financial.invoice.columnDescription"),
2214
- cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2215
- /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded px-1.5 py-0.5 text-[9px] font-medium bg-muted text-muted-foreground capitalize", children: row.original.itemKind }),
2216
- /* @__PURE__ */ jsx("span", { className: "truncate", children: row.original.description })
2217
- ] })
2218
- },
2219
- {
2220
- accessorKey: "quantity",
2221
- header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: t2("financial.invoice.columnQty") }),
2222
- cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "block text-right text-xs tabular-nums", children: getValue() })
2223
- },
2224
- {
2225
- accessorKey: "unitPrice",
2226
- header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: t2("financial.invoice.columnUnitPrice") }),
2227
- cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "block text-right text-xs tabular-nums", children: formatCurrency(getValue(), currency) })
2228
- },
2229
- {
2230
- accessorKey: "totalAmount",
2231
- header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: t2("financial.invoice.columnTotal") }),
2232
- cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium tabular-nums", children: formatCurrency(getValue(), currency) })
2233
- }
1933
+ const detailItems = items.map((it, i) => ({
1934
+ id: it.id ?? String(i),
1935
+ name: it.description,
1936
+ description: it.itemKind,
1937
+ quantity: it.quantity,
1938
+ unitPrice: formatCurrency(it.unitPrice, currency),
1939
+ total: formatCurrency(it.totalAmount, currency)
1940
+ }));
1941
+ const cancelled = invoice.status === "cancelled";
1942
+ const totalRows = [
1943
+ ...totalDiscount > 0 || totalSurcharge > 0 ? [{ label: t2("financial.invoice.subtotal"), value: formatCurrency(subtotal, currency), muted: true }] : [],
1944
+ ...totalDiscount > 0 ? [{ label: t2("financial.invoice.discount"), value: `-${formatCurrency(totalDiscount, currency)}`, muted: true }] : [],
1945
+ ...totalSurcharge > 0 ? [{ label: t2("financial.invoice.surcharge"), value: `+${formatCurrency(totalSurcharge, currency)}`, muted: true }] : [],
1946
+ { label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), strong: true },
1947
+ { label: t2("financial.invoice.columnPaid"), value: formatCurrency(totalPaid, currency) },
1948
+ ...totalRemaining > 0 ? [{ label: t2("financial.invoice.balance"), value: formatCurrency(totalRemaining, currency) }] : []
2234
1949
  ];
2235
- return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1950
+ direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
1951
+ const actions = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
1952
+ !cancelled && invoice.status !== "paid" && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onEdit, children: [
1953
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
1954
+ " ",
1955
+ t2("financial.invoice.edit")
1956
+ ] }),
1957
+ !cancelled && invoice.status !== "paid" && /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
1958
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
1959
+ menuOpen && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute right-0 top-full mt-1 w-44 rounded-lg border bg-popover shadow-md z-20 py-1", style: { animation: "field-slide-in 150ms ease-out" }, children: /* @__PURE__ */ jsxs(
1960
+ "button",
1961
+ {
1962
+ onClick: () => {
1963
+ setConfirmCancel(true);
1964
+ setMenuOpen(false);
1965
+ },
1966
+ className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive hover:bg-destructive/10 transition-colors",
1967
+ children: [
1968
+ /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
1969
+ " ",
1970
+ t2("financial.invoice.cancelInvoice")
1971
+ ]
1972
+ }
1973
+ ) })
1974
+ ] })
1975
+ ] });
1976
+ const sideBlocks = /* @__PURE__ */ jsxs(Fragment, { children: [
1977
+ invoice.bookingStartsAt && /* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.booking"), children: /* @__PURE__ */ jsxs(
1978
+ "button",
1979
+ {
1980
+ type: "button",
1981
+ onClick: () => config.onBookingClick?.(invoice.id),
1982
+ className: "inline-flex items-center gap-1 text-sm font-medium text-magic hover:underline",
1983
+ children: [
1984
+ /* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5 shrink-0" }),
1985
+ new Date(invoice.bookingStartsAt).toLocaleDateString(void 0, { weekday: "short", day: "2-digit", month: "short" }),
1986
+ " ",
1987
+ new Date(invoice.bookingStartsAt).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }),
1988
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-2.5 w-2.5" })
1989
+ ]
1990
+ }
1991
+ ) }),
1992
+ invoice.observations && /* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.notes"), children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: invoice.observations }) })
1993
+ ] });
1994
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2236
1995
  /* @__PURE__ */ jsx(
2237
- SubpageHeader,
1996
+ OrderDetailView,
2238
1997
  {
2239
1998
  title: invoice.fiscalNumber ? `#${invoice.fiscalNumber}` : invoice.contactName ?? invoice.invoiceDate,
1999
+ badges: [{ label: t2(statusConfig.labelKey), tone: statusConfig.tone }],
2000
+ subtitle: invoice.invoiceDate,
2240
2001
  onBack,
2241
- parentLabel,
2242
- actions: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2243
- invoice.status !== "cancelled" && invoice.status !== "paid" && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onEdit, children: [
2244
- /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
2245
- " ",
2246
- t2("financial.invoice.edit")
2247
- ] }),
2248
- invoice.status !== "cancelled" && invoice.status !== "paid" && /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
2249
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
2250
- menuOpen && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute right-0 top-full mt-1 w-44 rounded-lg border bg-popover shadow-md z-20 py-1", style: { animation: "field-slide-in 150ms ease-out" }, children: /* @__PURE__ */ jsxs(
2251
- "button",
2252
- {
2253
- onClick: () => {
2254
- setConfirmCancel(true);
2255
- setMenuOpen(false);
2256
- },
2257
- className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive hover:bg-destructive/10 transition-colors",
2258
- children: [
2259
- /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
2260
- " ",
2261
- t2("financial.invoice.cancelInvoice")
2262
- ]
2263
- }
2264
- ) })
2265
- ] })
2266
- ] })
2002
+ backLabel: parentLabel,
2003
+ actions,
2004
+ stats: [
2005
+ { id: "total", label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
2006
+ { id: "items", label: t2("financial.invoice.items"), value: items.length, icon: LayoutList },
2007
+ { id: "installments", label: t2("financial.invoice.installments"), value: `${invoice.totalInstallments}x`, icon: Layers },
2008
+ { id: "date", label: t2("financial.invoice.date"), value: invoice.invoiceDate, icon: CalendarDays }
2009
+ ],
2010
+ customer: { name: invoice.contactName, email: null },
2011
+ customerNode: invoice.contactName ? /* @__PURE__ */ jsx(PersonLink, { personId: invoice.contactId, name: invoice.contactName, size: "sm" }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "\u2014" }),
2012
+ items: detailItems,
2013
+ itemsLabel: t2("financial.invoice.items"),
2014
+ totals: totalRows,
2015
+ totalsLabel: t2("financial.invoice.payment"),
2016
+ mainSections: (
2017
+ // O cronograma no MESMO bloco que os itens e o pagamento — antes o título
2018
+ // ficava fora do cartão, e a seção lia como se fosse de outra tela.
2019
+ /* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsx("div", { className: "-mx-4 -mb-4", children: movements.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-4 text-center text-sm text-muted-foreground", children: t2("financial.invoice.noInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y border-t", children: movements.map((mov) => {
2020
+ const remaining = mov.amount - mov.paidAmount;
2021
+ const isPaid = mov.status === "paid";
2022
+ const isPartial = mov.status === "partial";
2023
+ const hasPaidDetails = isPaid || isPartial;
2024
+ const isExpanded = expandedMovId === mov.id;
2025
+ const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
2026
+ const MovIcon = movStatus.icon;
2027
+ const instPayments = payments.filter((p) => (p.installmentNumber ?? null) === (mov.installmentNumber ?? null));
2028
+ return /* @__PURE__ */ jsxs("div", { children: [
2029
+ /* @__PURE__ */ jsxs(
2030
+ "div",
2031
+ {
2032
+ className: `flex items-center gap-3 px-4 py-3 ${hasPaidDetails ? "cursor-pointer hover:bg-muted/20 transition-colors" : ""}`,
2033
+ onClick: hasPaidDetails ? () => handleExpandMov(mov.id) : void 0,
2034
+ children: [
2035
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground w-6 shrink-0", children: [
2036
+ "#",
2037
+ mov.installmentNumber
2038
+ ] }),
2039
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2040
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2041
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: formatCurrency(mov.amount, currency) }),
2042
+ /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${movStatus.bg}`, children: [
2043
+ /* @__PURE__ */ jsx(MovIcon, { className: "h-2 w-2" }),
2044
+ " ",
2045
+ t2(movStatus.labelKey)
2046
+ ] })
2047
+ ] }),
2048
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2049
+ t2("financial.invoice.due"),
2050
+ " ",
2051
+ mov.dueDate,
2052
+ mov.paidAmount > 0 && !isPaid && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
2053
+ mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
2054
+ ] })
2055
+ ] }),
2056
+ !isPaid && !isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
2057
+ "button",
2058
+ {
2059
+ onClick: (e) => {
2060
+ e.stopPropagation();
2061
+ setPayingMovement(mov);
2062
+ },
2063
+ className: "inline-flex items-center gap-1 rounded-lg bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0",
2064
+ children: [
2065
+ /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
2066
+ t2("financial.invoice.pay"),
2067
+ " ",
2068
+ remaining < mov.amount ? formatCurrency(remaining, currency) : ""
2069
+ ]
2070
+ }
2071
+ ),
2072
+ isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
2073
+ "button",
2074
+ {
2075
+ onClick: (e) => {
2076
+ e.stopPropagation();
2077
+ setPayingMovement(mov);
2078
+ },
2079
+ className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
2080
+ children: [
2081
+ /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
2082
+ t2("financial.invoice.pay"),
2083
+ " ",
2084
+ formatCurrency(remaining, currency)
2085
+ ]
2086
+ }
2087
+ ),
2088
+ hasPaidDetails && /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 text-muted-foreground shrink-0 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })
2089
+ ]
2090
+ }
2091
+ ),
2092
+ /* @__PURE__ */ jsx(
2093
+ "div",
2094
+ {
2095
+ className: "grid transition-all duration-200 ease-out",
2096
+ style: { gridTemplateRows: isExpanded ? "1fr" : "0fr" },
2097
+ children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-2.5 pl-14", children: detailsLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2098
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
2099
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
2100
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
2101
+ ] }) : instPayments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
2102
+ const pMethod = paymentMethods.find((m) => m.id === p.paymentMethodId);
2103
+ const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
2104
+ const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
2105
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
2106
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
2107
+ pType && /* @__PURE__ */ jsxs(Fragment, { children: [
2108
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2109
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pType.name }),
2110
+ pMethod && /* @__PURE__ */ jsxs("span", { children: [
2111
+ "(",
2112
+ pMethod.name,
2113
+ ")"
2114
+ ] })
2115
+ ] }),
2116
+ p.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
2117
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2118
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
2119
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: p.cardBrand }),
2120
+ p.cardInstallments && /* @__PURE__ */ jsxs("span", { children: [
2121
+ p.cardInstallments,
2122
+ "x"
2123
+ ] })
2124
+ ] }),
2125
+ pAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
2126
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2127
+ /* @__PURE__ */ jsx(Building2, { className: "h-3 w-3 shrink-0" }),
2128
+ /* @__PURE__ */ jsx("span", { children: pAccount.name })
2129
+ ] }),
2130
+ (p.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2131
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2132
+ /* @__PURE__ */ jsxs("span", { className: "text-warning", children: [
2133
+ t2("financial.statements.columnFee"),
2134
+ " ",
2135
+ formatCurrency(p.feeAmount ?? 0, currency)
2136
+ ] })
2137
+ ] }),
2138
+ p.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
2139
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2140
+ /* @__PURE__ */ jsx("span", { children: p.paymentDate })
2141
+ ] }),
2142
+ p.notes && /* @__PURE__ */ jsxs(Fragment, { children: [
2143
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2144
+ /* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", children: p.notes })
2145
+ ] })
2146
+ ] }, p.id);
2147
+ }) }) }) })
2148
+ }
2149
+ )
2150
+ ] }, mov.id);
2151
+ }) }) }) })
2152
+ ),
2153
+ extraSections: sideBlocks
2267
2154
  }
2268
2155
  ),
2269
- /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card shadow-sm overflow-hidden", children: [
2270
- /* @__PURE__ */ jsx("div", { className: "h-1 bg-primary" }),
2271
- /* @__PURE__ */ jsxs("div", { className: "p-5 pb-4", children: [
2272
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
2273
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2274
- /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 shrink-0", children: /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 text-primary" }) }),
2275
- /* @__PURE__ */ jsxs("div", { children: [
2276
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable") }),
2277
- /* @__PURE__ */ jsx("p", { className: `text-xl font-bold mt-0.5 ${invoice.status === "cancelled" ? "line-through text-muted-foreground" : ""}`, children: formatCurrency(invoice.totalAmount, currency) })
2278
- ] })
2279
- ] }),
2280
- /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[11px] font-semibold capitalize ${statusConfig.bg}`, children: [
2281
- /* @__PURE__ */ jsx(StatusIcon, { className: "h-3 w-3" }),
2282
- " ",
2283
- t2(statusConfig.labelKey)
2284
- ] })
2285
- ] }),
2286
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3 mt-4 pt-4 border-t", children: [
2287
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2288
- /* @__PURE__ */ jsx(User, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
2289
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2290
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.contact") }),
2291
- invoice.contactName ? /* @__PURE__ */ jsx(PersonLink, { personId: invoice.contactId, name: invoice.contactName, size: "sm" }) : /* @__PURE__ */ jsx("p", { className: "text-xs font-medium truncate", children: "\u2014" })
2292
- ] })
2293
- ] }),
2294
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2295
- /* @__PURE__ */ jsx(Calendar, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
2296
- /* @__PURE__ */ jsxs("div", { children: [
2297
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.date") }),
2298
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: invoice.invoiceDate })
2299
- ] })
2300
- ] }),
2301
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2302
- /* @__PURE__ */ jsx(Hash, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
2303
- /* @__PURE__ */ jsxs("div", { children: [
2304
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.installments") }),
2305
- /* @__PURE__ */ jsxs("p", { className: "text-xs font-medium", children: [
2306
- invoice.totalInstallments,
2307
- "x"
2308
- ] })
2309
- ] })
2310
- ] }),
2311
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2312
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
2313
- /* @__PURE__ */ jsxs("div", { children: [
2314
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.columnPaid") }),
2315
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: formatCurrency(totalPaid, currency) })
2316
- ] })
2317
- ] })
2318
- ] }),
2319
- invoice.bookingStartsAt && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-3 pt-3 border-t", children: [
2320
- /* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5 text-magic shrink-0" }),
2321
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
2322
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.booking") }),
2323
- /* @__PURE__ */ jsxs(
2324
- "button",
2325
- {
2326
- type: "button",
2327
- onClick: () => config.onBookingClick?.(invoice.id),
2328
- className: "inline-flex items-center gap-1 text-xs font-medium text-magic hover:underline",
2329
- children: [
2330
- new Date(invoice.bookingStartsAt).toLocaleDateString(void 0, { weekday: "short", day: "2-digit", month: "short" }),
2331
- " ",
2332
- new Date(invoice.bookingStartsAt).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }),
2333
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-2.5 w-2.5" })
2334
- ]
2335
- }
2336
- )
2337
- ] })
2338
- ] })
2339
- ] }),
2340
- items.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2341
- /* @__PURE__ */ jsx("div", { className: "px-5 pt-3 pb-1.5", children: /* @__PURE__ */ jsx("h3", { className: "text-[11px] font-semibold text-muted-foreground uppercase tracking-wider", children: t2("financial.invoice.items") }) }),
2342
- /* @__PURE__ */ jsx("div", { className: "px-5", children: /* @__PURE__ */ jsx(DataTable, { columns: itemColumns, data: items, variant: "flat", compact: true }) }),
2343
- /* @__PURE__ */ jsx("div", { className: "px-5 py-3 mt-1 border-t bg-muted/10", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end gap-0.5", children: [
2344
- (totalDiscount > 0 || totalSurcharge > 0) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 text-xs", children: [
2345
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.subtotal") }),
2346
- /* @__PURE__ */ jsx("span", { className: "tabular-nums w-24 text-right", children: formatCurrency(subtotal, currency) })
2347
- ] }),
2348
- totalDiscount > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 text-xs text-success", children: [
2349
- /* @__PURE__ */ jsx("span", { children: t2("financial.invoice.discount") }),
2350
- /* @__PURE__ */ jsxs("span", { className: "tabular-nums w-24 text-right", children: [
2351
- "-",
2352
- formatCurrency(totalDiscount, currency)
2353
- ] })
2354
- ] }),
2355
- totalSurcharge > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 text-xs text-warning", children: [
2356
- /* @__PURE__ */ jsx("span", { children: t2("financial.invoice.surcharge") }),
2357
- /* @__PURE__ */ jsxs("span", { className: "tabular-nums w-24 text-right", children: [
2358
- "+",
2359
- formatCurrency(totalSurcharge, currency)
2360
- ] })
2361
- ] }),
2362
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 text-sm font-bold pt-1", children: [
2363
- /* @__PURE__ */ jsx("span", { children: t2("financial.invoice.total") }),
2364
- /* @__PURE__ */ jsx("span", { className: `tabular-nums w-24 text-right ${invoice.status === "cancelled" ? "line-through text-muted-foreground" : ""}`, children: formatCurrency(invoice.totalAmount, currency) })
2365
- ] })
2366
- ] }) })
2367
- ] }),
2368
- invoice.observations && /* @__PURE__ */ jsxs("div", { className: "px-5 py-3 border-t", children: [
2369
- /* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1", children: t2("financial.invoice.notes") }),
2370
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: invoice.observations })
2371
- ] })
2372
- ] }),
2373
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2374
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-muted-foreground", children: t2("financial.invoice.paymentSchedule") }),
2375
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card shadow-sm", children: movements.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center text-sm text-muted-foreground", children: t2("financial.invoice.noInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y", children: movements.map((mov) => {
2376
- const remaining = mov.amount - mov.paidAmount;
2377
- const isPaid = mov.status === "paid";
2378
- const isPartial = mov.status === "partial";
2379
- const hasPaidDetails = isPaid || isPartial;
2380
- const isExpanded = expandedMovId === mov.id;
2381
- const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
2382
- const MovIcon = movStatus.icon;
2383
- const instPayments = payments.filter((p) => (p.installmentNumber ?? null) === (mov.installmentNumber ?? null));
2384
- return /* @__PURE__ */ jsxs("div", { children: [
2385
- /* @__PURE__ */ jsxs(
2386
- "div",
2387
- {
2388
- className: `flex items-center gap-3 px-4 py-3 ${hasPaidDetails ? "cursor-pointer hover:bg-muted/20 transition-colors" : ""}`,
2389
- onClick: hasPaidDetails ? () => handleExpandMov(mov.id) : void 0,
2390
- children: [
2391
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground w-6 shrink-0", children: [
2392
- "#",
2393
- mov.installmentNumber
2394
- ] }),
2395
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2396
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2397
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: formatCurrency(mov.amount, currency) }),
2398
- /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${movStatus.bg}`, children: [
2399
- /* @__PURE__ */ jsx(MovIcon, { className: "h-2 w-2" }),
2400
- " ",
2401
- t2(movStatus.labelKey)
2402
- ] })
2403
- ] }),
2404
- /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2405
- t2("financial.invoice.due"),
2406
- " ",
2407
- mov.dueDate,
2408
- mov.paidAmount > 0 && !isPaid && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
2409
- mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
2410
- ] })
2411
- ] }),
2412
- !isPaid && !isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
2413
- "button",
2414
- {
2415
- onClick: (e) => {
2416
- e.stopPropagation();
2417
- setPayingMovement(mov);
2418
- },
2419
- className: "inline-flex items-center gap-1 rounded-lg bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0",
2420
- children: [
2421
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
2422
- t2("financial.invoice.pay"),
2423
- " ",
2424
- remaining < mov.amount ? formatCurrency(remaining, currency) : ""
2425
- ]
2426
- }
2427
- ),
2428
- isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
2429
- "button",
2430
- {
2431
- onClick: (e) => {
2432
- e.stopPropagation();
2433
- setPayingMovement(mov);
2434
- },
2435
- className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
2436
- children: [
2437
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
2438
- t2("financial.invoice.pay"),
2439
- " ",
2440
- formatCurrency(remaining, currency)
2441
- ]
2442
- }
2443
- ),
2444
- hasPaidDetails && /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 text-muted-foreground shrink-0 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })
2445
- ]
2446
- }
2447
- ),
2448
- /* @__PURE__ */ jsx(
2449
- "div",
2450
- {
2451
- className: "grid transition-all duration-200 ease-out",
2452
- style: { gridTemplateRows: isExpanded ? "1fr" : "0fr" },
2453
- children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-2.5 pl-14", children: detailsLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2454
- /* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
2455
- /* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
2456
- /* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
2457
- ] }) : instPayments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
2458
- const pMethod = paymentMethods.find((m) => m.id === p.paymentMethodId);
2459
- const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
2460
- const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
2461
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
2462
- /* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
2463
- pType && /* @__PURE__ */ jsxs(Fragment, { children: [
2464
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2465
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pType.name }),
2466
- pMethod && /* @__PURE__ */ jsxs("span", { children: [
2467
- "(",
2468
- pMethod.name,
2469
- ")"
2470
- ] })
2471
- ] }),
2472
- p.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
2473
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2474
- /* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
2475
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: p.cardBrand }),
2476
- p.cardInstallments && /* @__PURE__ */ jsxs("span", { children: [
2477
- p.cardInstallments,
2478
- "x"
2479
- ] })
2480
- ] }),
2481
- pAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
2482
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2483
- /* @__PURE__ */ jsx(Building2, { className: "h-3 w-3 shrink-0" }),
2484
- /* @__PURE__ */ jsx("span", { children: pAccount.name })
2485
- ] }),
2486
- (p.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2487
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2488
- /* @__PURE__ */ jsxs("span", { className: "text-warning", children: [
2489
- t2("financial.statements.columnFee"),
2490
- " ",
2491
- formatCurrency(p.feeAmount ?? 0, currency)
2492
- ] })
2493
- ] }),
2494
- p.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
2495
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2496
- /* @__PURE__ */ jsx("span", { children: p.paymentDate })
2497
- ] }),
2498
- p.notes && /* @__PURE__ */ jsxs(Fragment, { children: [
2499
- /* @__PURE__ */ jsx("span", { children: "\xB7" }),
2500
- /* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", children: p.notes })
2501
- ] })
2502
- ] }, p.id);
2503
- }) }) }) })
2504
- }
2505
- )
2506
- ] }, mov.id);
2507
- }) }) }),
2508
- movements.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-lg border bg-muted/20 px-4 py-2.5", children: [
2509
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 text-xs", children: [
2510
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
2511
- t2("financial.invoice.paidLabel"),
2512
- " ",
2513
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-success", children: formatCurrency(totalPaid, currency) })
2514
- ] }),
2515
- totalRemaining > 0 && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
2516
- t2("financial.invoice.remaining"),
2517
- " ",
2518
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: formatCurrency(totalRemaining, currency) })
2519
- ] })
2520
- ] }),
2521
- totalRemaining <= 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-medium text-success", children: [
2522
- /* @__PURE__ */ jsx(CircleCheckBig, { className: "h-3 w-3" }),
2523
- " ",
2524
- t2("financial.invoice.fullyPaid")
2525
- ] })
2526
- ] })
2527
- ] }),
2528
2156
  payingMovement && /* @__PURE__ */ jsx(
2529
2157
  PaymentModal,
2530
2158
  {
@@ -3289,6 +2917,7 @@ function StatementsView({ onNavigate }) {
3289
2917
  return d.toISOString().slice(0, 10);
3290
2918
  });
3291
2919
  const [dateTo, setDateTo] = useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2920
+ const [search, setSearch] = useState("");
3292
2921
  const [showTransfer, setShowTransfer] = useState(false);
3293
2922
  useEffect(() => {
3294
2923
  fetchBankAccounts();
@@ -3423,54 +3052,105 @@ function StatementsView({ onNavigate }) {
3423
3052
  cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block font-medium text-xs", children: fmt(row.original.runningBalance) })
3424
3053
  }
3425
3054
  ], [currency, t2, methodName, onNavigate]);
3426
- const entries = statement?.entries ?? [];
3055
+ const entries = useMemo(() => {
3056
+ const all = statement?.entries ?? [];
3057
+ const term = search.trim().toLowerCase();
3058
+ if (!term) return all;
3059
+ return all.filter((e) => {
3060
+ const haystack = [e.invoice?.contactName, e.movement.notes, e.invoice?.fiscalNumber, methodName(e.movement)];
3061
+ return haystack.some((v) => v?.toLowerCase().includes(term));
3062
+ });
3063
+ }, [statement, search, methodName]);
3064
+ const exportEntries = () => {
3065
+ exportCSV(
3066
+ [
3067
+ { key: "date", label: t2("financial.statements.columnDate") },
3068
+ { key: "description", label: t2("financial.statements.columnDescription") },
3069
+ { key: "method", label: t2("financial.statements.columnMethod") },
3070
+ { key: "debit", label: t2("financial.statements.columnDebit") },
3071
+ { key: "credit", label: t2("financial.statements.columnCredit") },
3072
+ { key: "balance", label: t2("financial.statements.columnBalance") }
3073
+ ],
3074
+ entries.map((e) => ({
3075
+ date: e.movement.paymentDate ?? e.movement.dueDate,
3076
+ description: e.invoice?.contactName ?? e.movement.notes ?? "",
3077
+ method: methodName(e.movement),
3078
+ debit: e.movement.direction === "debit" ? fmt(e.movement.amount) : "",
3079
+ credit: e.movement.direction === "credit" ? fmt(e.movement.amount) : "",
3080
+ balance: fmt(e.runningBalance)
3081
+ })),
3082
+ "extrato"
3083
+ );
3084
+ };
3427
3085
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3428
- /* @__PURE__ */ jsx(
3429
- SubpageHeader,
3430
- {
3431
- title: t2("financial.statements.title"),
3432
- subtitle: t2("financial.statements.subtitle"),
3433
- actions: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
3434
- /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
3435
- t2("financial.statements.newTransfer")
3436
- ] })
3437
- }
3438
- ),
3439
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-end", children: [
3440
- /* @__PURE__ */ jsxs("div", { className: "flex-1 max-w-xs", children: [
3441
- /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground uppercase", children: t2("financial.statements.account") }),
3442
- /* @__PURE__ */ jsxs(
3443
- "select",
3444
- {
3445
- value: accountId,
3446
- onChange: (e) => setAccountId(e.target.value),
3447
- className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm",
3448
- children: [
3449
- /* @__PURE__ */ jsx("option", { value: ALL, children: t2("financial.summary.allAccounts") }),
3450
- bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
3451
- ]
3452
- }
3453
- )
3454
- ] }),
3455
- /* @__PURE__ */ jsxs("div", { children: [
3456
- /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground uppercase", children: t2("financial.statements.from") }),
3457
- /* @__PURE__ */ jsx(DatePicker, { value: dateFrom, onChange: setDateFrom, className: "mt-0.5" })
3458
- ] }),
3459
- /* @__PURE__ */ jsxs("div", { children: [
3460
- /* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground uppercase", children: t2("financial.statements.to") }),
3461
- /* @__PURE__ */ jsx(DatePicker, { value: dateTo, onChange: setDateTo, className: "mt-0.5" })
3462
- ] })
3463
- ] }),
3086
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-end gap-3", children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
3087
+ /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
3088
+ t2("financial.statements.newTransfer")
3089
+ ] }) }),
3464
3090
  accountId && statement && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-2", children: [
3465
3091
  /* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.opening"), value: fmt(statement.openingBalance) }),
3466
3092
  /* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.totalCredits"), value: fmt(statement.totalCredits), positive: true }),
3467
3093
  /* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.totalDebits"), value: fmt(statement.totalDebits), negative: true }),
3468
3094
  /* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.closing"), value: fmt(statement.closingBalance), accent: true })
3469
3095
  ] }),
3470
- !accountId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border-2 border-dashed border-muted", children: [
3471
- /* @__PURE__ */ jsx(Receipt, { className: "h-8 w-8 text-muted-foreground mb-2" }),
3472
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.statements.selectToView") })
3473
- ] }) : statementLoading ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], loading: true, skeletonRows: 8, compact: true }) : entries.length === 0 ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], emptyMessage: t2("financial.statements.noTransactions"), compact: true }) : /* @__PURE__ */ jsx(DataTable, { columns, data: entries, compact: true }),
3096
+ /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
3097
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: [
3098
+ /* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
3099
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
3100
+ /* @__PURE__ */ jsx(
3101
+ "input",
3102
+ {
3103
+ type: "text",
3104
+ value: search,
3105
+ onChange: (e) => setSearch(e.target.value),
3106
+ placeholder: t2("financial.statements.searchPlaceholder"),
3107
+ className: "w-full bg-transparent py-1.5 pl-8 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none"
3108
+ }
3109
+ )
3110
+ ] }),
3111
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-2", children: [
3112
+ /* @__PURE__ */ jsx(
3113
+ FilterChip,
3114
+ {
3115
+ icon: Landmark,
3116
+ label: t2("financial.statements.account"),
3117
+ allLabel: t2("financial.summary.allAccounts"),
3118
+ value: accountId && accountId !== ALL ? accountId : void 0,
3119
+ onChange: (next) => setAccountId(next ?? ALL),
3120
+ options: bankAccounts.map((a) => ({ value: a.id, label: a.name }))
3121
+ }
3122
+ ),
3123
+ /* @__PURE__ */ jsx(DatePicker, { value: dateFrom, onChange: setDateFrom, variant: "chip" }),
3124
+ /* @__PURE__ */ jsx(DatePicker, { value: dateTo, onChange: setDateTo, variant: "chip" }),
3125
+ entries.length > 0 && /* @__PURE__ */ jsx(
3126
+ "button",
3127
+ {
3128
+ type: "button",
3129
+ onClick: exportEntries,
3130
+ "aria-label": t2("crud.export.action"),
3131
+ title: t2("crud.export.action"),
3132
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
3133
+ children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
3134
+ }
3135
+ )
3136
+ ] })
3137
+ ] }),
3138
+ /* @__PURE__ */ jsx(
3139
+ DataTable,
3140
+ {
3141
+ variant: "bare",
3142
+ compact: true,
3143
+ columns,
3144
+ data: statementLoading ? [] : entries,
3145
+ loading: statementLoading,
3146
+ skeletonRows: statementLoading ? 8 : void 0,
3147
+ emptyMessage: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 py-6", children: [
3148
+ /* @__PURE__ */ jsx(Receipt, { className: "mb-2 h-9 w-9 text-muted-foreground/40" }),
3149
+ /* @__PURE__ */ jsx("p", { className: "text-base font-semibold text-foreground", children: accountId ? t2("financial.statements.noTransactions") : t2("financial.statements.selectToView") })
3150
+ ] })
3151
+ }
3152
+ )
3153
+ ] }),
3474
3154
  showTransfer && /* @__PURE__ */ jsx(
3475
3155
  TransferModal,
3476
3156
  {
@@ -4113,7 +3793,7 @@ function buildNav(config, activeView, navigate, t2) {
4113
3793
  }
4114
3794
  return items;
4115
3795
  }
4116
- function FinancialPage({ config, provider, store, registries }) {
3796
+ function FinancialPage({ config, provider, store: store2, registries }) {
4117
3797
  const { view, direction, navigate, previousView, back } = useModuleNavigation("/financial", {
4118
3798
  summary: 0,
4119
3799
  "payables-list": 0,
@@ -4211,10 +3891,10 @@ function FinancialPage({ config, provider, store, registries }) {
4211
3891
  }
4212
3892
  }
4213
3893
  if (!onboardingComplete) {
4214
- return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(FinancialOnboarding, { onComplete: handleOnboardingComplete }) });
3894
+ return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store2, children: /* @__PURE__ */ jsx(FinancialOnboarding, { onComplete: handleOnboardingComplete }) });
4215
3895
  }
4216
3896
  if (isSettings && registries && registries.length > 0) {
4217
- return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(PageTransition, { transitionKey: "settings", direction, children: /* @__PURE__ */ jsx("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsx(
3897
+ return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store2, children: /* @__PURE__ */ jsx(PageTransition, { transitionKey: "settings", direction, children: /* @__PURE__ */ jsx("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsx(
4218
3898
  PluginSettingsPanel,
4219
3899
  {
4220
3900
  title: t2("financial.settingsPage.title"),
@@ -4227,6 +3907,8 @@ function FinancialPage({ config, provider, store, registries }) {
4227
3907
  ) }) }) });
4228
3908
  }
4229
3909
  function renderView() {
3910
+ const off = intent.view === "commissions" && !config.modules.commissions || intent.view === "cards" && !config.modules.cards || intent.view === "cash-registers" && !config.modules.cashRegisters || intent.view === "reconciliation" && !config.modules.reconciliation || intent.view === "notas-fiscais" && !config.modules.notasFiscais;
3911
+ if (off) return /* @__PURE__ */ jsx(SummaryView, {});
4230
3912
  switch (intent.view) {
4231
3913
  case "payables":
4232
3914
  return /* @__PURE__ */ jsx(PayablesView, { intent, onNavigate: navigate, previousView, back });
@@ -4253,12 +3935,13 @@ function FinancialPage({ config, provider, store, registries }) {
4253
3935
  if (!feature) return renderView();
4254
3936
  return /* @__PURE__ */ jsx(EntitlementGate, { feature, fallback: /* @__PURE__ */ jsx(UpgradePrompt, { feature }), children: renderView() });
4255
3937
  }
4256
- return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxs(
3938
+ return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store: store2, children: /* @__PURE__ */ jsxs(
4257
3939
  ModulePage,
4258
3940
  {
4259
3941
  title: config.labels.pageTitle,
4260
3942
  subtitle: config.labels.pageSubtitle,
4261
3943
  nav,
3944
+ navVariant: config.moduleNav,
4262
3945
  showHeader: intent.view === "summary" || view === "summary",
4263
3946
  viewKey: view,
4264
3947
  direction,
@@ -4762,7 +4445,7 @@ function createStore(options) {
4762
4445
  };
4763
4446
  }
4764
4447
  function createMockFinancialProvider(options) {
4765
- const store = createStore(options);
4448
+ const store2 = createStore(options);
4766
4449
  const tenantId = "mock-tenant";
4767
4450
  function recalcInvoiceStatus(invoice) {
4768
4451
  if (invoice.paidAmount >= invoice.totalAmount) {
@@ -4770,7 +4453,7 @@ function createMockFinancialProvider(options) {
4770
4453
  } else if (invoice.paidAmount > 0) {
4771
4454
  invoice.status = "partial";
4772
4455
  } else {
4773
- const movements = store.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
4456
+ const movements = store2.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
4774
4457
  const hasOverdue = movements.some((m) => m.status === "overdue" || m.status === "pending" && m.dueDate < today());
4775
4458
  invoice.status = hasOverdue ? "overdue" : "open";
4776
4459
  }
@@ -4779,16 +4462,16 @@ function createMockFinancialProvider(options) {
4779
4462
  const provider = {
4780
4463
  // --- Invoices ---
4781
4464
  async getInvoices(query) {
4782
- let results = [...store.invoices];
4465
+ let results = [...store2.invoices];
4783
4466
  if (query.direction) results = results.filter((i) => i.direction === query.direction);
4784
4467
  if (query.status) results = results.filter((i) => matchesStatus(i.status, query.status));
4785
4468
  if (query.contactId) results = results.filter((i) => i.contactId === query.contactId);
4786
4469
  if (query.accountId) {
4787
- const invoiceIds = new Set(store.invoiceItems.filter((item) => item.accountId === query.accountId).map((item) => item.invoiceId));
4470
+ const invoiceIds = new Set(store2.invoiceItems.filter((item) => item.accountId === query.accountId).map((item) => item.invoiceId));
4788
4471
  results = results.filter((invoice) => invoiceIds.has(invoice.id));
4789
4472
  }
4790
4473
  if (query.costCenterId) {
4791
- const invoiceIds = new Set(store.invoiceItems.filter((item) => item.costCenterId === query.costCenterId).map((item) => item.invoiceId));
4474
+ const invoiceIds = new Set(store2.invoiceItems.filter((item) => item.costCenterId === query.costCenterId).map((item) => item.invoiceId));
4792
4475
  results = results.filter((invoice) => invoiceIds.has(invoice.id));
4793
4476
  }
4794
4477
  if (query.dateRange) results = results.filter((i) => matchesDateRange(i.invoiceDate, query.dateRange));
@@ -4802,10 +4485,10 @@ function createMockFinancialProvider(options) {
4802
4485
  return paginate(results, query.page, query.pageSize);
4803
4486
  },
4804
4487
  async getInvoiceById(id) {
4805
- return store.invoices.find((i) => i.id === id) ?? null;
4488
+ return store2.invoices.find((i) => i.id === id) ?? null;
4806
4489
  },
4807
4490
  async getInvoiceItems(invoiceId) {
4808
- return store.invoiceItems.filter((i) => i.invoiceId === invoiceId);
4491
+ return store2.invoiceItems.filter((i) => i.invoiceId === invoiceId);
4809
4492
  },
4810
4493
  async createInvoice(input) {
4811
4494
  const invoiceId = uid();
@@ -4834,7 +4517,7 @@ function createMockFinancialProvider(options) {
4834
4517
  createdAt: now(),
4835
4518
  updatedAt: now()
4836
4519
  };
4837
- store.invoices.push(invoice);
4520
+ store2.invoices.push(invoice);
4838
4521
  for (const itemInput of input.items) {
4839
4522
  const itemTotal = itemInput.quantity * itemInput.unitPrice - (itemInput.discount ?? 0) + (itemInput.surcharge ?? 0);
4840
4523
  const item = {
@@ -4853,7 +4536,7 @@ function createMockFinancialProvider(options) {
4853
4536
  metadata: itemInput.metadata,
4854
4537
  createdAt: now()
4855
4538
  };
4856
- store.invoiceItems.push(item);
4539
+ store2.invoiceItems.push(item);
4857
4540
  }
4858
4541
  for (const movInput of input.installments) {
4859
4542
  const movement = {
@@ -4874,29 +4557,29 @@ function createMockFinancialProvider(options) {
4874
4557
  createdAt: now(),
4875
4558
  updatedAt: now()
4876
4559
  };
4877
- store.movements.push(movement);
4560
+ store2.movements.push(movement);
4878
4561
  }
4879
4562
  return invoice;
4880
4563
  },
4881
4564
  async updateInvoice(id, data) {
4882
- const invoice = store.invoices.find((i) => i.id === id);
4565
+ const invoice = store2.invoices.find((i) => i.id === id);
4883
4566
  if (!invoice) throw new Error(`Invoice ${id} not found`);
4884
4567
  Object.assign(invoice, data, { updatedAt: now() });
4885
4568
  return invoice;
4886
4569
  },
4887
4570
  async cancelInvoice(id) {
4888
- const invoice = store.invoices.find((i) => i.id === id);
4571
+ const invoice = store2.invoices.find((i) => i.id === id);
4889
4572
  if (!invoice) throw new Error(`Invoice ${id} not found`);
4890
4573
  invoice.status = "cancelled";
4891
4574
  invoice.updatedAt = now();
4892
- store.movements.filter((m) => m.invoiceId === id && m.status === "pending").forEach((m) => {
4575
+ store2.movements.filter((m) => m.invoiceId === id && m.status === "pending").forEach((m) => {
4893
4576
  m.status = "cancelled";
4894
4577
  m.updatedAt = now();
4895
4578
  });
4896
4579
  },
4897
4580
  // --- Movements ---
4898
4581
  async getMovements(query) {
4899
- let results = [...store.movements];
4582
+ let results = [...store2.movements];
4900
4583
  if (query.direction) results = results.filter((m) => m.direction === query.direction);
4901
4584
  if (query.status) results = results.filter((m) => matchesStatus(m.status, query.status));
4902
4585
  if (query.bankAccountId) results = results.filter((m) => m.bankAccountId === query.bankAccountId);
@@ -4906,9 +4589,9 @@ function createMockFinancialProvider(options) {
4906
4589
  return paginate(results, query.page, query.pageSize);
4907
4590
  },
4908
4591
  async payMovement(input) {
4909
- const bill = store.movements.find((m) => m.id === input.movementId);
4592
+ const bill = store2.movements.find((m) => m.id === input.movementId);
4910
4593
  if (!bill) throw new Error(`Movement ${input.movementId} not found`);
4911
- const method = input.paymentMethodId ? store.paymentMethods.find((pm) => pm.id === input.paymentMethodId) : void 0;
4594
+ const method = input.paymentMethodId ? store2.paymentMethods.find((pm) => pm.id === input.paymentMethodId) : void 0;
4912
4595
  const feePct = bill.direction === "credit" ? method?.discountValue ?? 0 : 0;
4913
4596
  const thisFee = round2(input.amount * (feePct / 100));
4914
4597
  const payment = {
@@ -4934,14 +4617,14 @@ function createMockFinancialProvider(options) {
4934
4617
  createdAt: now(),
4935
4618
  updatedAt: now()
4936
4619
  };
4937
- store.movements.push(payment);
4620
+ store2.movements.push(payment);
4938
4621
  bill.paidAmount = round2(bill.paidAmount + input.amount);
4939
4622
  bill.status = bill.paidAmount >= bill.amount ? "paid" : "partial";
4940
4623
  bill.paymentDate = void 0;
4941
4624
  bill.bankAccountId = void 0;
4942
4625
  bill.updatedAt = now();
4943
4626
  if (input.bankAccountId) {
4944
- const account = store.bankAccounts.find((a) => a.id === input.bankAccountId);
4627
+ const account = store2.bankAccounts.find((a) => a.id === input.bankAccountId);
4945
4628
  if (account) {
4946
4629
  if (bill.direction === "credit") {
4947
4630
  account.currentBalance = round2(account.currentBalance + (input.amount - thisFee));
@@ -4952,9 +4635,9 @@ function createMockFinancialProvider(options) {
4952
4635
  }
4953
4636
  }
4954
4637
  if (bill.invoiceId) {
4955
- const invoice = store.invoices.find((i) => i.id === bill.invoiceId);
4638
+ const invoice = store2.invoices.find((i) => i.id === bill.invoiceId);
4956
4639
  if (invoice) {
4957
- const bills = store.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
4640
+ const bills = store2.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
4958
4641
  invoice.paidAmount = bills.reduce((sum, m) => sum + m.paidAmount, 0);
4959
4642
  recalcInvoiceStatus(invoice);
4960
4643
  }
@@ -4962,7 +4645,7 @@ function createMockFinancialProvider(options) {
4962
4645
  return payment;
4963
4646
  },
4964
4647
  async cancelMovement(id) {
4965
- const movement = store.movements.find((m) => m.id === id);
4648
+ const movement = store2.movements.find((m) => m.id === id);
4966
4649
  if (!movement) throw new Error(`Movement ${id} not found`);
4967
4650
  movement.status = "cancelled";
4968
4651
  movement.updatedAt = now();
@@ -4989,22 +4672,22 @@ function createMockFinancialProvider(options) {
4989
4672
  createdAt: now(),
4990
4673
  updatedAt: now()
4991
4674
  };
4992
- store.movements.push({
4675
+ store2.movements.push({
4993
4676
  ...base,
4994
4677
  id: debitMovementId,
4995
4678
  direction: "debit",
4996
4679
  bankAccountId: input.fromAccountId,
4997
4680
  metadata: { transferId, transferRole: "out", counterAccountId: input.toAccountId }
4998
4681
  });
4999
- store.movements.push({
4682
+ store2.movements.push({
5000
4683
  ...base,
5001
4684
  id: creditMovementId,
5002
4685
  direction: "credit",
5003
4686
  bankAccountId: input.toAccountId,
5004
4687
  metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
5005
4688
  });
5006
- const from = store.bankAccounts.find((a) => a.id === input.fromAccountId);
5007
- const to = store.bankAccounts.find((a) => a.id === input.toAccountId);
4689
+ const from = store2.bankAccounts.find((a) => a.id === input.fromAccountId);
4690
+ const to = store2.bankAccounts.find((a) => a.id === input.toAccountId);
5008
4691
  if (from) {
5009
4692
  from.currentBalance = round2(from.currentBalance - input.amount);
5010
4693
  from.updatedAt = now();
@@ -5017,7 +4700,7 @@ function createMockFinancialProvider(options) {
5017
4700
  },
5018
4701
  // --- Bank Accounts ---
5019
4702
  async getBankAccounts() {
5020
- return store.bankAccounts.filter((a) => a.isActive);
4703
+ return store2.bankAccounts.filter((a) => a.isActive);
5021
4704
  },
5022
4705
  async createBankAccount(data) {
5023
4706
  const account = {
@@ -5038,26 +4721,26 @@ function createMockFinancialProvider(options) {
5038
4721
  createdAt: now(),
5039
4722
  updatedAt: now()
5040
4723
  };
5041
- store.bankAccounts.push(account);
4724
+ store2.bankAccounts.push(account);
5042
4725
  return account;
5043
4726
  },
5044
4727
  async updateBankAccount(id, data) {
5045
- const account = store.bankAccounts.find((a) => a.id === id);
4728
+ const account = store2.bankAccounts.find((a) => a.id === id);
5046
4729
  if (!account) throw new Error(`Bank account ${id} not found`);
5047
4730
  Object.assign(account, data, { updatedAt: now() });
5048
4731
  return account;
5049
4732
  },
5050
4733
  // --- Cash Sessions ---
5051
4734
  async getCashSessions(bankAccountId) {
5052
- let results = [...store.cashSessions];
4735
+ let results = [...store2.cashSessions];
5053
4736
  if (bankAccountId) results = results.filter((s) => s.bankAccountId === bankAccountId);
5054
4737
  return results.sort((a, b) => b.openedAt.localeCompare(a.openedAt));
5055
4738
  },
5056
4739
  async getOpenSession(bankAccountId) {
5057
- return store.cashSessions.find((s) => s.bankAccountId === bankAccountId && s.status === "open") ?? null;
4740
+ return store2.cashSessions.find((s) => s.bankAccountId === bankAccountId && s.status === "open") ?? null;
5058
4741
  },
5059
4742
  async openCashSession(input) {
5060
- const account = store.bankAccounts.find((a) => a.id === input.bankAccountId);
4743
+ const account = store2.bankAccounts.find((a) => a.id === input.bankAccountId);
5061
4744
  const session = {
5062
4745
  id: uid(),
5063
4746
  bankAccountId: input.bankAccountId,
@@ -5071,13 +4754,13 @@ function createMockFinancialProvider(options) {
5071
4754
  tenantId,
5072
4755
  createdAt: now()
5073
4756
  };
5074
- store.cashSessions.push(session);
4757
+ store2.cashSessions.push(session);
5075
4758
  return session;
5076
4759
  },
5077
4760
  async closeCashSession(input) {
5078
- const session = store.cashSessions.find((s) => s.id === input.sessionId);
4761
+ const session = store2.cashSessions.find((s) => s.id === input.sessionId);
5079
4762
  if (!session) throw new Error(`Cash session ${input.sessionId} not found`);
5080
- const sessionMovements = store.movements.filter((m) => m.cashSessionId === session.id && m.status === "paid");
4763
+ const sessionMovements = store2.movements.filter((m) => m.cashSessionId === session.id && m.status === "paid");
5081
4764
  const inflow = sessionMovements.filter((m) => m.direction === "credit").reduce((s, m) => s + m.paidAmount, 0);
5082
4765
  const outflow = sessionMovements.filter((m) => m.direction === "debit").reduce((s, m) => s + m.paidAmount, 0);
5083
4766
  const expectedBalance = session.openingBalance + inflow - outflow;
@@ -5092,9 +4775,9 @@ function createMockFinancialProvider(options) {
5092
4775
  return session;
5093
4776
  },
5094
4777
  async getCashSessionSummary(sessionId) {
5095
- const session = store.cashSessions.find((s) => s.id === sessionId);
4778
+ const session = store2.cashSessions.find((s) => s.id === sessionId);
5096
4779
  if (!session) throw new Error(`Cash session ${sessionId} not found`);
5097
- const sessionMovements = store.movements.filter((m) => m.cashSessionId === sessionId && m.status === "paid");
4780
+ const sessionMovements = store2.movements.filter((m) => m.cashSessionId === sessionId && m.status === "paid");
5098
4781
  return {
5099
4782
  session,
5100
4783
  movementCount: sessionMovements.length,
@@ -5104,21 +4787,21 @@ function createMockFinancialProvider(options) {
5104
4787
  },
5105
4788
  // --- Payment Methods ---
5106
4789
  async getPaymentMethods() {
5107
- return store.paymentMethods.filter((pm) => pm.isActive);
4790
+ return store2.paymentMethods.filter((pm) => pm.isActive);
5108
4791
  },
5109
4792
  async getPaymentMethodTypes() {
5110
- return store.paymentMethodTypes.filter((t2) => t2.isActive);
4793
+ return store2.paymentMethodTypes.filter((t2) => t2.isActive);
5111
4794
  },
5112
4795
  // --- Chart of Accounts ---
5113
4796
  async getChartOfAccounts() {
5114
- return store.chartOfAccounts.filter((n) => n.isActive);
4797
+ return store2.chartOfAccounts.filter((n) => n.isActive);
5115
4798
  },
5116
4799
  async getCostCenters() {
5117
- return store.costCenters.filter((c) => c.isActive);
4800
+ return store2.costCenters.filter((c) => c.isActive);
5118
4801
  },
5119
4802
  // --- Card Transactions ---
5120
4803
  async getCardTransactions(dateRange) {
5121
- let results = [...store.cardTransactions];
4804
+ let results = [...store2.cardTransactions];
5122
4805
  if (dateRange) results = results.filter((t2) => matchesDateRange(t2.expectedDate, dateRange));
5123
4806
  return results.sort((a, b) => a.expectedDate.localeCompare(b.expectedDate));
5124
4807
  },
@@ -5127,15 +4810,15 @@ function createMockFinancialProvider(options) {
5127
4810
  const todayStr = today();
5128
4811
  const isRealized = (m) => m.status !== "cancelled" && !!m.paymentDate;
5129
4812
  const consolidated = !query.bankAccountId;
5130
- const scopeAccounts = consolidated ? store.bankAccounts.filter((a) => a.isActive) : store.bankAccounts.filter((a) => a.id === query.bankAccountId);
4813
+ const scopeAccounts = consolidated ? store2.bankAccounts.filter((a) => a.isActive) : store2.bankAccounts.filter((a) => a.id === query.bankAccountId);
5131
4814
  const accountIds = new Set(scopeAccounts.map((a) => a.id));
5132
- const accountNameById = new Map(store.bankAccounts.map((a) => [a.id, a.name]));
4815
+ const accountNameById = new Map(store2.bankAccounts.map((a) => [a.id, a.name]));
5133
4816
  const initialSum = scopeAccounts.reduce((s, a) => s + a.initialBalance, 0);
5134
4817
  if (!consolidated && accountIds.size === 0) {
5135
4818
  return { entries: [], openingBalance: 0, closingBalance: 0, totalCredits: 0, totalDebits: 0, totalFees: 0, net: 0, accountId: query.bankAccountId };
5136
4819
  }
5137
4820
  const netOf = (m) => m.direction === "credit" ? m.paidAmount - (m.feeAmount ?? 0) : -m.paidAmount;
5138
- const inScope = store.movements.filter((m) => isRealized(m) && (consolidated || m.bankAccountId && accountIds.has(m.bankAccountId)));
4821
+ const inScope = store2.movements.filter((m) => isRealized(m) && (consolidated || m.bankAccountId && accountIds.has(m.bankAccountId)));
5139
4822
  const openingBalance = round2(initialSum + inScope.filter((m) => (m.paymentDate ?? "") < query.dateRange.from).reduce((s, m) => s + netOf(m), 0));
5140
4823
  const rangeMovs = inScope.filter((m) => matchesDateRange(m.paymentDate, query.dateRange)).sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || a.createdAt.localeCompare(b.createdAt));
5141
4824
  let balance = openingBalance;
@@ -5157,7 +4840,7 @@ function createMockFinancialProvider(options) {
5157
4840
  }
5158
4841
  return {
5159
4842
  movement,
5160
- invoice: movement.invoiceId ? store.invoices.find((i) => i.id === movement.invoiceId) : void 0,
4843
+ invoice: movement.invoiceId ? store2.invoices.find((i) => i.id === movement.invoiceId) : void 0,
5161
4844
  runningBalance: balance,
5162
4845
  gross,
5163
4846
  fee,
@@ -5169,9 +4852,9 @@ function createMockFinancialProvider(options) {
5169
4852
  });
5170
4853
  for (const e of entries) {
5171
4854
  const mid = e.movement.matchedMovementId;
5172
- const mm = mid ? store.movements.find((m) => m.id === mid) : void 0;
4855
+ const mm = mid ? store2.movements.find((m) => m.id === mid) : void 0;
5173
4856
  if (!mm) continue;
5174
- const inv = mm.invoiceId ? store.invoices.find((i) => i.id === mm.invoiceId) : void 0;
4857
+ const inv = mm.invoiceId ? store2.invoices.find((i) => i.id === mm.invoiceId) : void 0;
5175
4858
  e.reconciledWith = {
5176
4859
  movementId: mm.id,
5177
4860
  description: mm.notes ?? void 0,
@@ -5195,11 +4878,11 @@ function createMockFinancialProvider(options) {
5195
4878
  },
5196
4879
  // --- Summary ---
5197
4880
  async getSummary(dateRange) {
5198
- const totalBalance = store.bankAccounts.filter((a) => a.isActive).reduce((sum, a) => sum + a.currentBalance, 0);
5199
- const pendingReceivable = store.movements.filter(
4881
+ const totalBalance = store2.bankAccounts.filter((a) => a.isActive).reduce((sum, a) => sum + a.currentBalance, 0);
4882
+ const pendingReceivable = store2.movements.filter(
5200
4883
  (m) => m.direction === "credit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
5201
4884
  );
5202
- const pendingPayable = store.movements.filter(
4885
+ const pendingPayable = store2.movements.filter(
5203
4886
  (m) => m.direction === "debit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
5204
4887
  );
5205
4888
  const todayStr = today();
@@ -5207,7 +4890,7 @@ function createMockFinancialProvider(options) {
5207
4890
  monthAgo.setDate(monthAgo.getDate() - 30);
5208
4891
  const monthStart = monthAgo.toISOString().slice(0, 10);
5209
4892
  const monthEnd = todayStr;
5210
- const monthMovements = store.movements.filter(
4893
+ const monthMovements = store2.movements.filter(
5211
4894
  (m) => m.movementKind === "payment" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
5212
4895
  );
5213
4896
  const overdueReceivable = pendingReceivable.filter((m) => m.dueDate < todayStr);
@@ -5228,12 +4911,12 @@ function createMockFinancialProvider(options) {
5228
4911
  async importBankTransactions(input) {
5229
4912
  let imported = 0, duplicates = 0;
5230
4913
  for (const line of input.lines) {
5231
- const dup = store.movements.some((m) => m.externalSource === line.externalSource && m.externalId === line.externalId);
4914
+ const dup = store2.movements.some((m) => m.externalSource === line.externalSource && m.externalId === line.externalId);
5232
4915
  if (dup) {
5233
4916
  duplicates++;
5234
4917
  continue;
5235
4918
  }
5236
- store.movements.push({
4919
+ store2.movements.push({
5237
4920
  id: uid(),
5238
4921
  direction: line.direction,
5239
4922
  movementKind: "payment",
@@ -5255,38 +4938,38 @@ function createMockFinancialProvider(options) {
5255
4938
  return { imported, duplicates };
5256
4939
  },
5257
4940
  async getUnreconciled(query) {
5258
- let results = store.movements.filter((m) => m.externalSource && !m.reconciledAt);
4941
+ let results = store2.movements.filter((m) => m.externalSource && !m.reconciledAt);
5259
4942
  if (query?.bankAccountId) results = results.filter((m) => m.bankAccountId === query.bankAccountId);
5260
4943
  if (query?.dateRange) results = results.filter((m) => matchesDateRange(m.paymentDate, query.dateRange));
5261
4944
  return results.sort((a, b) => (b.paymentDate ?? "").localeCompare(a.paymentDate ?? ""));
5262
4945
  },
5263
4946
  async suggestReconciliation(bankMovementId) {
5264
- const line = store.movements.find((m) => m.id === bankMovementId);
4947
+ const line = store2.movements.find((m) => m.id === bankMovementId);
5265
4948
  if (!line) return [];
5266
4949
  const amount = line.amount;
5267
4950
  const tol = Math.max(0.01, round2(amount * 0.02));
5268
4951
  const lineDate = line.paymentDate ?? line.dueDate;
5269
- return store.movements.filter((m) => !m.externalSource && !m.reconciledAt && m.direction === line.direction && m.id !== bankMovementId && Math.abs(m.amount - amount) <= tol).map((m) => {
4952
+ return store2.movements.filter((m) => !m.externalSource && !m.reconciledAt && m.direction === line.direction && m.id !== bankMovementId && Math.abs(m.amount - amount) <= tol).map((m) => {
5270
4953
  const amtScore = amount > 0 ? Math.max(0, 1 - Math.abs(m.amount - amount) / amount) : 0;
5271
4954
  const refDate = m.dueDate ?? m.paymentDate;
5272
4955
  const days = lineDate && refDate ? Math.abs((new Date(lineDate).getTime() - new Date(refDate).getTime()) / 864e5) : 30;
5273
4956
  const dateScore = Math.max(0, 1 - days / 30);
5274
4957
  return {
5275
4958
  movement: m,
5276
- invoice: m.invoiceId ? store.invoices.find((i) => i.id === m.invoiceId) : void 0,
4959
+ invoice: m.invoiceId ? store2.invoices.find((i) => i.id === m.invoiceId) : void 0,
5277
4960
  score: round2(0.7 * amtScore + 0.3 * dateScore)
5278
4961
  };
5279
4962
  }).sort((a, b) => b.score - a.score).slice(0, 25);
5280
4963
  },
5281
4964
  async reconcileMovement(input) {
5282
- const line = store.movements.find((m) => m.id === input.bankMovementId);
4965
+ const line = store2.movements.find((m) => m.id === input.bankMovementId);
5283
4966
  if (line) {
5284
4967
  line.reconciledAt = now();
5285
4968
  line.matchedMovementId = input.matchedMovementId;
5286
4969
  line.updatedAt = now();
5287
4970
  }
5288
4971
  if (input.matchedMovementId) {
5289
- const internal = store.movements.find((m) => m.id === input.matchedMovementId);
4972
+ const internal = store2.movements.find((m) => m.id === input.matchedMovementId);
5290
4973
  if (internal) {
5291
4974
  internal.reconciledAt = now();
5292
4975
  internal.updatedAt = now();
@@ -5294,14 +4977,14 @@ function createMockFinancialProvider(options) {
5294
4977
  }
5295
4978
  },
5296
4979
  async unreconcileMovement(bankMovementId) {
5297
- const line = store.movements.find((m) => m.id === bankMovementId);
4980
+ const line = store2.movements.find((m) => m.id === bankMovementId);
5298
4981
  if (!line) return;
5299
4982
  const matchedId = line.matchedMovementId;
5300
4983
  line.reconciledAt = void 0;
5301
4984
  line.matchedMovementId = void 0;
5302
4985
  line.updatedAt = now();
5303
4986
  if (matchedId) {
5304
- const internal = store.movements.find((m) => m.id === matchedId);
4987
+ const internal = store2.movements.find((m) => m.id === matchedId);
5305
4988
  if (internal) {
5306
4989
  internal.reconciledAt = void 0;
5307
4990
  internal.updatedAt = now();
@@ -5310,7 +4993,7 @@ function createMockFinancialProvider(options) {
5310
4993
  },
5311
4994
  // --- Notas fiscais (NF-e / NFC-e / NFS-e) ---
5312
4995
  async getNotas(query) {
5313
- let list = [...store.notas].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
4996
+ let list = [...store2.notas].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
5314
4997
  if (query.status) list = list.filter((n) => matchesStatus(n.status, query.status));
5315
4998
  if (query.model) list = list.filter((n) => n.model === query.model);
5316
4999
  if (query.orderId) list = list.filter((n) => n.orderId === query.orderId);
@@ -5321,7 +5004,7 @@ function createMockFinancialProvider(options) {
5321
5004
  return paginate(list, query.page, query.pageSize);
5322
5005
  },
5323
5006
  async getNotaById(id) {
5324
- return store.notas.find((n) => n.id === id) ?? null;
5007
+ return store2.notas.find((n) => n.id === id) ?? null;
5325
5008
  },
5326
5009
  async createNota(input) {
5327
5010
  const nota = {
@@ -5339,19 +5022,19 @@ function createMockFinancialProvider(options) {
5339
5022
  createdAt: now(),
5340
5023
  updatedAt: now()
5341
5024
  };
5342
- store.notas.unshift(nota);
5025
+ store2.notas.unshift(nota);
5343
5026
  return nota;
5344
5027
  },
5345
5028
  async updateNota(id, patch) {
5346
- const nota = store.notas.find((n) => n.id === id);
5029
+ const nota = store2.notas.find((n) => n.id === id);
5347
5030
  if (!nota) throw new Error("Nota fiscal not found");
5348
5031
  Object.assign(nota, patch, { updatedAt: now() });
5349
5032
  return nota;
5350
5033
  },
5351
5034
  async issueNota(id) {
5352
- const nota = store.notas.find((n) => n.id === id);
5035
+ const nota = store2.notas.find((n) => n.id === id);
5353
5036
  if (!nota) throw new Error("Nota fiscal not found");
5354
- const issuedCount = store.notas.filter((n) => n.status === "issued").length;
5037
+ const issuedCount = store2.notas.filter((n) => n.status === "issued").length;
5355
5038
  nota.status = "issued";
5356
5039
  nota.number = String(issuedCount + 1).padStart(6, "0");
5357
5040
  nota.series = nota.series ?? "1";
@@ -5361,7 +5044,7 @@ function createMockFinancialProvider(options) {
5361
5044
  return nota;
5362
5045
  },
5363
5046
  async cancelNota(id, reason) {
5364
- const nota = store.notas.find((n) => n.id === id);
5047
+ const nota = store2.notas.find((n) => n.id === id);
5365
5048
  if (!nota) throw new Error("Nota fiscal not found");
5366
5049
  nota.status = "cancelled";
5367
5050
  nota.errorMessage = reason;
@@ -6190,6 +5873,143 @@ function createSupabaseFinancialProvider() {
6190
5873
  };
6191
5874
  return provider;
6192
5875
  }
5876
+
5877
+ // src/lib/message-events.ts
5878
+ var INVOICE_FIELDS = [
5879
+ { name: "customer_name", label: "Cliente", type: "string", example: "Maria Silva" },
5880
+ { name: "invoice_number", label: "N\xFAmero", type: "string", example: "0042" },
5881
+ { name: "invoice_total", label: "Valor", type: "money", example: "R$ 1.250,00" },
5882
+ { name: "due_date", label: "Vencimento", type: "date", example: "20/08/2026" },
5883
+ { name: "invoice_url", label: "Link da fatura", type: "url", example: "https://sua.app/faturas/0042" },
5884
+ { name: "business_name", label: "Seu neg\xF3cio", type: "string", example: "Seu Neg\xF3cio" }
5885
+ ];
5886
+ function buildFinancialDomainEvents() {
5887
+ return [
5888
+ {
5889
+ name: "financial.invoice_created",
5890
+ title: "Fatura emitida",
5891
+ icon: "FilePlus2",
5892
+ audit: true,
5893
+ description: "Uma cobran\xE7a foi gerada",
5894
+ trigger: "Quando a fatura \xE9 emitida",
5895
+ fields: INVOICE_FIELDS
5896
+ },
5897
+ {
5898
+ name: "financial.invoice_due_soon",
5899
+ title: "Fatura a vencer",
5900
+ icon: "CalendarClock",
5901
+ audit: false,
5902
+ description: "O vencimento est\xE1 pr\xF3ximo",
5903
+ trigger: "Nos dias que antecedem o vencimento",
5904
+ fields: INVOICE_FIELDS
5905
+ },
5906
+ {
5907
+ name: "financial.invoice_paid",
5908
+ title: "Fatura paga",
5909
+ icon: "CircleCheck",
5910
+ audit: true,
5911
+ description: "O pagamento foi recebido",
5912
+ trigger: "Quando a fatura \xE9 baixada como paga",
5913
+ fields: INVOICE_FIELDS
5914
+ },
5915
+ {
5916
+ name: "financial.invoice_overdue",
5917
+ title: "Fatura vencida",
5918
+ icon: "AlertTriangle",
5919
+ audit: true,
5920
+ description: "A cobran\xE7a passou do vencimento",
5921
+ trigger: "Quando a fatura vence sem pagamento",
5922
+ fields: INVOICE_FIELDS
5923
+ },
5924
+ {
5925
+ name: "financial.invoice_cancelled",
5926
+ title: "Fatura cancelada",
5927
+ icon: "FileX2",
5928
+ audit: true,
5929
+ description: "A cobran\xE7a foi cancelada",
5930
+ trigger: "Quando a fatura \xE9 cancelada",
5931
+ fields: INVOICE_FIELDS
5932
+ }
5933
+ ];
5934
+ }
5935
+ function buildFinancialMessageDefaults() {
5936
+ return [
5937
+ {
5938
+ eventKey: "financial.invoice_created",
5939
+ kind: "transactional",
5940
+ channels: ["email", "whatsapp"],
5941
+ defaults: {
5942
+ email: {
5943
+ subject: "Fatura {{invoice_number}} \u2014 {{invoice_total}}",
5944
+ body: "Ol\xE1 {{customer_name}},\n\nSegue a fatura {{invoice_number}}, no valor de {{invoice_total}}, com vencimento em {{due_date}}.\n\n{{invoice_url}}\n\nQualquer d\xFAvida, \xE9 s\xF3 responder este e-mail.\n\n{{business_name}}"
5945
+ },
5946
+ whatsapp: {
5947
+ body: "Oi {{customer_name}}! Segue a fatura {{invoice_number}} \u{1F4C4}\n\nValor: {{invoice_total}}\nVencimento: {{due_date}}\n{{invoice_url}}\n\n\u2014 {{business_name}}"
5948
+ }
5949
+ },
5950
+ defaultEnabled: { email: true }
5951
+ },
5952
+ {
5953
+ eventKey: "financial.invoice_due_soon",
5954
+ kind: "transactional",
5955
+ channels: ["whatsapp", "email"],
5956
+ defaults: {
5957
+ whatsapp: {
5958
+ body: "Oi {{customer_name}}! Lembrete tranquilo: a fatura {{invoice_number}} ({{invoice_total}}) vence em {{due_date}}.\n\n{{invoice_url}}\n\n\u2014 {{business_name}}"
5959
+ },
5960
+ email: {
5961
+ subject: "Sua fatura {{invoice_number}} vence em {{due_date}}",
5962
+ body: "Ol\xE1 {{customer_name}},\n\nUm lembrete: a fatura {{invoice_number}}, de {{invoice_total}}, vence em {{due_date}}.\n\n{{invoice_url}}\n\nSe j\xE1 pagou, pode desconsiderar.\n\n{{business_name}}"
5963
+ }
5964
+ },
5965
+ defaultEnabled: { email: true }
5966
+ },
5967
+ {
5968
+ eventKey: "financial.invoice_paid",
5969
+ kind: "transactional",
5970
+ channels: ["email", "whatsapp"],
5971
+ defaults: {
5972
+ email: {
5973
+ subject: "Recebemos o pagamento da fatura {{invoice_number}}",
5974
+ body: "Ol\xE1 {{customer_name}},\n\nConfirmamos o recebimento de {{invoice_total}} referente \xE0 fatura {{invoice_number}}.\n\nObrigado!\n\n{{business_name}}"
5975
+ },
5976
+ whatsapp: {
5977
+ body: "Oi {{customer_name}}! Pagamento da fatura {{invoice_number}} confirmado \u2705\n\nObrigado! \u2014 {{business_name}}"
5978
+ }
5979
+ },
5980
+ // Confirmar recebimento é o mínimo: sem isso o cliente fica sem saber se
5981
+ // o dinheiro chegou, e a próxima mensagem dele é uma pergunta.
5982
+ defaultEnabled: { email: true }
5983
+ },
5984
+ {
5985
+ eventKey: "financial.invoice_overdue",
5986
+ kind: "transactional",
5987
+ channels: ["whatsapp", "email"],
5988
+ defaults: {
5989
+ whatsapp: {
5990
+ body: "Oi {{customer_name}}, tudo bem?\n\nA fatura {{invoice_number}} ({{invoice_total}}) venceu em {{due_date}} e consta em aberto por aqui.\n\n{{invoice_url}}\n\nSe precisar de outra data, me avisa que a gente resolve. \u2014 {{business_name}}"
5991
+ },
5992
+ email: {
5993
+ subject: "Fatura {{invoice_number}} em aberto",
5994
+ body: "Ol\xE1 {{customer_name}},\n\nA fatura {{invoice_number}}, de {{invoice_total}}, venceu em {{due_date}} e consta em aberto.\n\n{{invoice_url}}\n\nSe j\xE1 efetuou o pagamento, desconsidere este aviso. Se precisar de outra data, \xE9 s\xF3 responder.\n\n{{business_name}}"
5995
+ }
5996
+ }
5997
+ // Cobrança de atraso NÃO nasce ligada: a primeira mensagem que uma conta
5998
+ // nova manda sozinha não pode ser uma cobrança escrita por outra pessoa.
5999
+ },
6000
+ {
6001
+ eventKey: "financial.invoice_cancelled",
6002
+ kind: "transactional",
6003
+ channels: ["email"],
6004
+ defaults: {
6005
+ email: {
6006
+ subject: "Fatura {{invoice_number}} cancelada",
6007
+ body: "Ol\xE1 {{customer_name}},\n\nA fatura {{invoice_number}}, de {{invoice_total}}, foi cancelada e n\xE3o precisa ser paga.\n\n{{business_name}}"
6008
+ }
6009
+ }
6010
+ }
6011
+ ];
6012
+ }
6193
6013
  function createFinancialStore(provider) {
6194
6014
  return createStore$1((set, get) => ({
6195
6015
  invoices: [],
@@ -6420,7 +6240,7 @@ function createFinancialStore(provider) {
6420
6240
  }));
6421
6241
  }
6422
6242
 
6423
- // src/registries.ts
6243
+ // src/data/registries.ts
6424
6244
  var paymentMethodTypeEntity = {
6425
6245
  name: "Payment Type",
6426
6246
  namePlural: "Payment Types",
@@ -6762,6 +6582,10 @@ var en = {
6762
6582
  "financial.invoice.paidOn": "Paid on:",
6763
6583
  "financial.invoice.pay": "Pay",
6764
6584
  "financial.invoice.paymentSchedule": "Payment Schedule",
6585
+ "financial.invoiceForm.scheduleLocked": "Some installments are paid \u2014 the schedule can no longer be regenerated.",
6586
+ "financial.invoice.payment": "Payment",
6587
+ "financial.invoice.balance": "Remaining",
6588
+ "financial.invoice.details": "Details",
6765
6589
  "financial.invoice.remaining": "Remaining:",
6766
6590
  "financial.invoice.searchPlaceholder": "Search by contact or description...",
6767
6591
  "financial.invoice.showing": "Showing {{shown}} of {{total}}",
@@ -6781,6 +6605,8 @@ var en = {
6781
6605
  "financial.invoiceForm.discountSurcharge": "Discount / Surcharge",
6782
6606
  "financial.invoiceForm.done": "Done",
6783
6607
  "financial.invoiceForm.editing": "Editing invoice",
6608
+ "financial.invoiceForm.editTitle": "Edit",
6609
+ "financial.invoiceForm.newTitle": "New",
6784
6610
  "financial.invoiceForm.fiscalNumber": "Fiscal Number (optional)",
6785
6611
  "financial.invoiceForm.headquarters": "Headquarters",
6786
6612
  "financial.invoiceForm.installments": "Installments",
@@ -6917,6 +6743,7 @@ var en = {
6917
6743
  "financial.statements.from": "From",
6918
6744
  "financial.statements.noTransactions": "No transactions in this period",
6919
6745
  "financial.statements.selectAccount": "Select an account to view statement",
6746
+ "financial.statements.searchPlaceholder": "Search by contact, description or document...",
6920
6747
  "financial.statements.selectToView": "Select an account to view statement",
6921
6748
  "financial.statements.subtitle": "Bank account transactions and balances",
6922
6749
  "financial.statements.title": "Statements",
@@ -7256,6 +7083,10 @@ var ptBR = {
7256
7083
  "financial.invoice.paidOn": "Pago em:",
7257
7084
  "financial.invoice.pay": "Pagar",
7258
7085
  "financial.invoice.paymentSchedule": "Cronograma de Pagamento",
7086
+ "financial.invoiceForm.scheduleLocked": "Parcelas j\xE1 pagas \u2014 o parcelamento n\xE3o pode mais ser refeito.",
7087
+ "financial.invoice.payment": "Pagamento",
7088
+ "financial.invoice.balance": "Restante",
7089
+ "financial.invoice.details": "Detalhes",
7259
7090
  "financial.invoice.remaining": "Restante:",
7260
7091
  "financial.invoice.searchPlaceholder": "Buscar por contato ou descri\xE7\xE3o...",
7261
7092
  "financial.invoice.showing": "Exibindo {{shown}} de {{total}}",
@@ -7275,6 +7106,11 @@ var ptBR = {
7275
7106
  "financial.invoiceForm.discountSurcharge": "Desconto / Acr\xE9scimo",
7276
7107
  "financial.invoiceForm.done": "Pronto",
7277
7108
  "financial.invoiceForm.editing": "Editando fatura",
7109
+ // Título da migalha ("← Mensalidades / Nova"). Genérico de propósito: o nome
7110
+ // do documento é do app (Mensalidades, Contas a Receber), e repeti-lo aqui
7111
+ // escreveria "fatura" numa tela que o app chama de outra coisa.
7112
+ "financial.invoiceForm.editTitle": "Editar",
7113
+ "financial.invoiceForm.newTitle": "Nova",
7278
7114
  "financial.invoiceForm.fiscalNumber": "N\xFAmero Fiscal (opcional)",
7279
7115
  "financial.invoiceForm.headquarters": "Sede",
7280
7116
  "financial.invoiceForm.installments": "Parcelas",
@@ -7411,6 +7247,7 @@ var ptBR = {
7411
7247
  "financial.statements.from": "De",
7412
7248
  "financial.statements.noTransactions": "Sem transa\xE7\xF5es neste per\xEDodo",
7413
7249
  "financial.statements.selectAccount": "Selecione uma conta para ver o extrato",
7250
+ "financial.statements.searchPlaceholder": "Buscar por contato, descri\xE7\xE3o ou documento...",
7414
7251
  "financial.statements.selectToView": "Selecione uma conta para ver o extrato",
7415
7252
  "financial.statements.subtitle": "Transa\xE7\xF5es e saldos de contas banc\xE1rias",
7416
7253
  "financial.statements.title": "Extratos",
@@ -7670,6 +7507,156 @@ var financialLocales = {
7670
7507
  en,
7671
7508
  "pt-BR": ptBR
7672
7509
  };
7510
+ var STRIPE_CONNECTOR_ID = "stripe";
7511
+ var STRIPE_SETTINGS = [
7512
+ {
7513
+ key: "paymentMethods",
7514
+ label: "Formas de pagamento",
7515
+ description: "O que o cliente pode escolher para pagar a fatura.",
7516
+ type: "multi-select",
7517
+ options: [
7518
+ { value: "card", label: "Cart\xE3o de cr\xE9dito" },
7519
+ { value: "pix", label: "Pix" },
7520
+ { value: "boleto", label: "Boleto" }
7521
+ ],
7522
+ requiredToOperate: true
7523
+ },
7524
+ {
7525
+ key: "daysUntilDue",
7526
+ label: "Prazo de vencimento",
7527
+ description: "Dias entre a emiss\xE3o e o vencimento da fatura.",
7528
+ type: "text",
7529
+ placeholder: "7",
7530
+ pattern: "\\d{1,3}",
7531
+ patternHint: "S\xF3 n\xFAmeros, at\xE9 tr\xEAs d\xEDgitos.",
7532
+ defaultValue: "7"
7533
+ },
7534
+ {
7535
+ // The difference between "a fatura foi emitida" and "o cliente recebeu a
7536
+ // cobrança" is a decision, not a default: finalizing at Stripe sends the
7537
+ // e-mail, and a tenant that reviews before sending needs the draft.
7538
+ key: "finalizeAutomatically",
7539
+ label: "Enviar a cobran\xE7a automaticamente",
7540
+ description: "Desligado, a fatura fica como rascunho na Stripe at\xE9 voc\xEA enviar.",
7541
+ type: "toggle",
7542
+ defaultValue: true
7543
+ }
7544
+ ];
7545
+ var store = createConnectionStore(STRIPE_CONNECTOR_ID, {
7546
+ settings: STRIPE_SETTINGS
7547
+ });
7548
+ var METHOD_LABELS = { card: "Cart\xE3o", pix: "Pix", boleto: "Boleto" };
7549
+ function methodsDetail(value) {
7550
+ const list = Array.isArray(value) ? value : [];
7551
+ const labels = list.map((v) => METHOD_LABELS[String(v)] ?? String(v)).filter(Boolean);
7552
+ return labels.length > 0 ? labels.join(", ") : void 0;
7553
+ }
7554
+ var stripeConnectorDef = {
7555
+ id: STRIPE_CONNECTOR_ID,
7556
+ hostPluginId: "financial",
7557
+ name: "Stripe",
7558
+ description: "Emita as faturas do Financeiro na Stripe e receba a baixa quando o cliente pagar.",
7559
+ icon: "CreditCard",
7560
+ category: "payments",
7561
+ authKind: "oauth",
7562
+ // Issuing the invoice is an effect that files a run; the settlement arrives by
7563
+ // webhook. Neither happens while a buyer waits, so nothing here is `request`.
7564
+ planes: ["sync"],
7565
+ direction: "outbound",
7566
+ docsUrl: "https://docs.stripe.com/invoicing",
7567
+ // Declared here and not as the legacy `entities`/`subscribesTo` pair: those are
7568
+ // mirrors this list already derives (connectorEntities/connectorSubscribesTo),
7569
+ // and two spellings of the same claim is one that can disagree with itself.
7570
+ //
7571
+ // `invoice_due_soon` and `invoice_overdue` are deliberately absent. Stripe has
7572
+ // its own dunning and neither fact changes anything we would send it; asking to
7573
+ // hear them would be asking for reach this connector does not use.
7574
+ permissions: [
7575
+ {
7576
+ kind: "data",
7577
+ resource: "invoice",
7578
+ actions: ["read", "write"],
7579
+ reason: "Ler a fatura a receber para emiti-la na Stripe, e gravar de volta o n\xFAmero da fatura e o link de pagamento. Nome do cliente e itens v\xE3o na cobran\xE7a.",
7580
+ sensitive: true
7581
+ },
7582
+ {
7583
+ kind: "data",
7584
+ resource: "payment",
7585
+ actions: ["write"],
7586
+ reason: "Lan\xE7ar a baixa no receb\xEDvel quando a Stripe confirmar que o cliente pagou.",
7587
+ sensitive: true
7588
+ },
7589
+ {
7590
+ kind: "event",
7591
+ resource: "financial.invoice_created",
7592
+ actions: ["listen"],
7593
+ reason: "Saber que uma fatura foi emitida, para criar a cobran\xE7a na Stripe."
7594
+ },
7595
+ {
7596
+ kind: "event",
7597
+ resource: "financial.invoice_cancelled",
7598
+ actions: ["listen"],
7599
+ reason: "Saber que uma fatura foi cancelada, para anular a cobran\xE7a na Stripe."
7600
+ },
7601
+ {
7602
+ // The connector is what makes this fact appear, so the tenant should agree
7603
+ // to it here: the app's own automations (recibo, mensagem) react to it.
7604
+ kind: "event",
7605
+ resource: "financial.invoice_paid",
7606
+ actions: ["emit"],
7607
+ reason: "Anunciar dentro do app que a fatura foi paga, para o que depende disso acontecer."
7608
+ }
7609
+ ],
7610
+ // Consent is the whole form: an OAuth connector declares no fields, and the
7611
+ // grant never reaches this code.
7612
+ fields: [],
7613
+ settings: STRIPE_SETTINGS,
7614
+ invokedAt: [
7615
+ {
7616
+ key: "financial.invoice.issue",
7617
+ label: "Uma fatura a receber \xE9 emitida na Stripe com o link de pagamento",
7618
+ plane: "sync",
7619
+ trigger: "manual"
7620
+ },
7621
+ {
7622
+ key: "financial.payment.settle",
7623
+ label: "A Stripe avisa que o cliente pagou e a baixa \xE9 lan\xE7ada no receb\xEDvel",
7624
+ plane: "sync",
7625
+ trigger: "webhook"
7626
+ }
7627
+ ],
7628
+ ...connectionStatus(
7629
+ STRIPE_CONNECTOR_ID,
7630
+ (connection) => ({
7631
+ connected: !!connection?.active,
7632
+ detail: methodsDetail(connection?.settings?.paymentMethods),
7633
+ plane: "sync",
7634
+ ...settingsStatus(STRIPE_SETTINGS, connection)
7635
+ }),
7636
+ { store: () => store }
7637
+ ),
7638
+ ...connectionSettings(store),
7639
+ /**
7640
+ * The whole connect step, through the platform broker (FAY-1390).
7641
+ *
7642
+ * It used to call an edge function's `oauth_start` — a function that was
7643
+ * never written, so pressing Conectar reached a URL that did not exist and
7644
+ * the browser reported it as a CORS failure. The broker replaces it
7645
+ * end-to-end: consent starts at the platform, the callback lands there, and
7646
+ * the grant is stored in the platform vault.
7647
+ *
7648
+ * `saveConnection` is deliberately absent, like every other OAuth connector
7649
+ * here: `active = true` is written by the redirect route AFTER the grant.
7650
+ * Nothing in a browser is in a position to know an account was linked.
7651
+ */
7652
+ startOAuth: fayzConnectorOAuth(STRIPE_CONNECTOR_ID),
7653
+ // Local only. Handing the grant back to Stripe is the platform's job — it
7654
+ // holds the client pair and the connected account id, and its deauthorize is
7655
+ // not the same call every other provider takes.
7656
+ disconnect: async () => {
7657
+ await store.disconnect();
7658
+ }
7659
+ };
7673
7660
  var STATUS_STYLE = {
7674
7661
  paid: "bg-success/15 text-success",
7675
7662
  partial: "bg-warning/15 text-warning",
@@ -7828,6 +7815,7 @@ function resolveConfig(options) {
7828
7815
  quickAdd: options?.quickAdd === true,
7829
7816
  labels: { ...DEFAULT_LABELS, ...options?.labels },
7830
7817
  currency: { ...DEFAULT_CURRENCY, ...options?.currency },
7818
+ moduleNav: options?.moduleNav,
7831
7819
  itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
7832
7820
  enableServiceExecution: options?.enableServiceExecution ?? false,
7833
7821
  contactEntity: {
@@ -7844,13 +7832,14 @@ function resolveConfig(options) {
7844
7832
  function createFinancialPlugin(options) {
7845
7833
  const config = resolveConfig(options);
7846
7834
  registerTranslations(financialLocales);
7835
+ registerMessageDefaults(buildFinancialMessageDefaults());
7847
7836
  const provider = options?.dataProvider ?? createSafeFinancialProvider();
7848
- const store = createFinancialStore(provider);
7849
- const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
7837
+ const store2 = createFinancialStore(provider);
7838
+ const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store: store2 });
7850
7839
  const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
7851
7840
  const PersonStatementBound = (props) => React18__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
7852
7841
  PersonStatementBound.displayName = "FinancialPersonStatement";
7853
- const PageComponent = () => React18__default.createElement(FinancialPage, { config, provider, store, registries });
7842
+ const PageComponent = () => React18__default.createElement(FinancialPage, { config, provider, store: store2, registries });
7854
7843
  return {
7855
7844
  id: "financial",
7856
7845
  name: config.labels.pageTitle,
@@ -7860,6 +7849,8 @@ function createFinancialPlugin(options) {
7860
7849
  verticalId: options?.verticalId,
7861
7850
  defaultEnabled: true,
7862
7851
  dependencies: [],
7852
+ // Os FATOS do financeiro — só expostos enquanto o módulo está ATIVO.
7853
+ events: buildFinancialDomainEvents(),
7863
7854
  declaredFeatures: [
7864
7855
  { id: "financial", label: config.labels.pageTitle, group: config.labels.pageTitle },
7865
7856
  ...config.modules.receivables ? [{ id: "financial.receivables", label: config.labels.receivables, group: config.labels.pageTitle }] : [],
@@ -8069,10 +8060,19 @@ function createFinancialPlugin(options) {
8069
8060
  permission: { feature: "financial", action: "read" }
8070
8061
  }
8071
8062
  ],
8063
+ // Stripe is the first connector this module implements rather than merely
8064
+ // shelves — it stays in `integrations` too, because the union is what the
8065
+ // catalogue row joins on.
8066
+ connectors: [stripeConnectorDef],
8067
+ // TecnoSpeed is live already — the openbanking addon's `plugbank`
8068
+ // connector IS this slug. AbacatePay and Pagar.me were named as what
8069
+ // having Financeiro should put on the shelf; Mercado Pago, Bling and Stone
8070
+ // come from the Fayz plugin page.
8071
+ integrations: ["tecnospeed", "abacatepay", "pagarme", "stripe", "mercadopago", "bling", "stone"],
8072
8072
  locales: financialLocales
8073
8073
  };
8074
8074
  }
8075
8075
 
8076
- export { FINANCIAL_NAV_FEATURES, createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider, openQuickAdd };
8076
+ export { FINANCIAL_NAV_FEATURES, buildFinancialDomainEvents, buildFinancialMessageDefaults, createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider, openQuickAdd };
8077
8077
  //# sourceMappingURL=index.js.map
8078
8078
  //# sourceMappingURL=index.js.map