@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.
- package/README.md +3 -3
- package/dist/components/PersonStatementWidget.d.ts +1 -1
- package/dist/components/PersonStatementWidget.d.ts.map +1 -1
- package/dist/{FinancialContext.d.ts → context.d.ts} +4 -2
- package/dist/context.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1426 -1426
- package/dist/index.js.map +1 -1
- package/dist/integrations/stripe/connectorDef.d.ts +14 -0
- package/dist/integrations/stripe/connectorDef.d.ts.map +1 -0
- package/dist/integrations/stripe/data.d.ts +43 -0
- package/dist/integrations/stripe/data.d.ts.map +1 -0
- package/dist/lib/message-events.d.ts +12 -0
- package/dist/lib/message-events.d.ts.map +1 -0
- package/dist/{quick-add.d.ts → lib/quick-add.d.ts} +1 -1
- package/dist/lib/quick-add.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/{FinancialPage.d.ts → views/FinancialPage.d.ts} +3 -3
- package/dist/views/FinancialPage.d.ts.map +1 -0
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/PayablesView.d.ts +1 -1
- package/dist/views/PayablesView.d.ts.map +1 -1
- package/dist/views/ReceivablesView.d.ts +1 -1
- package/dist/views/ReceivablesView.d.ts.map +1 -1
- package/dist/views/StatementsView.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/package.json +12 -14
- package/dist/FinancialContext.d.ts.map +0 -1
- package/dist/FinancialPage.d.ts.map +0 -1
- package/dist/contract.test.d.ts +0 -2
- package/dist/contract.test.d.ts.map +0 -1
- package/dist/quick-add.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/FinancialContext.tsx +0 -59
- package/src/FinancialPage.tsx +0 -413
- package/src/README.md +0 -155
- package/src/components/FinancialGeneralSettings.tsx +0 -39
- package/src/components/FinancialOnboarding.tsx +0 -87
- package/src/components/FinancialSettings.tsx +0 -31
- package/src/components/PaymentModal.tsx +0 -369
- package/src/components/PersonStatementWidget.tsx +0 -133
- package/src/components/TransferModal.tsx +0 -150
- package/src/contract.test.ts +0 -58
- package/src/data/capability.test.ts +0 -135
- package/src/data/index.ts +0 -3
- package/src/data/mock.ts +0 -842
- package/src/data/supabase.ts +0 -968
- package/src/data/tables.ts +0 -13
- package/src/data/types.ts +0 -86
- package/src/index.ts +0 -487
- package/src/lib/tenant.ts +0 -3
- package/src/locales/en.ts +0 -493
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -493
- package/src/migrations/000_plg_rename.sql +0 -30
- package/src/migrations/001_financial_base.sql +0 -109
- package/src/migrations/002_chart_of_accounts.sql +0 -28
- package/src/migrations/003_card_brands.sql +0 -12
- package/src/migrations/004_order_to_cash.sql +0 -178
- package/src/migrations/005_seed_defaults.sql +0 -38
- package/src/migrations/006_rls_policies.sql +0 -37
- package/src/migrations/006b_extract_fee_amount.sql +0 -20
- package/src/migrations/007_reconciliation.sql +0 -32
- package/src/migrations/007b_movement_payment_method_type.sql +0 -6
- package/src/migrations/008_split_payment_movements.sql +0 -29
- package/src/migrations/009_agent_rpcs.sql +0 -165
- package/src/migrations/010_invoice_internal.sql +0 -133
- package/src/migrations/011_nfe.sql +0 -58
- package/src/migrations/index.ts +0 -923
- package/src/quick-add.ts +0 -58
- package/src/registries.ts +0 -308
- package/src/store.ts +0 -329
- package/src/types.ts +0 -592
- package/src/views/CardsView.tsx +0 -213
- package/src/views/CashRegistersView.tsx +0 -597
- package/src/views/CommissionsView.tsx +0 -18
- package/src/views/InvoiceDetailView.tsx +0 -616
- package/src/views/InvoiceFormView.tsx +0 -945
- package/src/views/InvoiceListView.tsx +0 -323
- package/src/views/NotasFiscaisView.tsx +0 -209
- package/src/views/PayablesView.tsx +0 -48
- package/src/views/QuickTransactionForm.tsx +0 -548
- package/src/views/ReceivablesView.tsx +0 -48
- package/src/views/ReconciliationView.tsx +0 -211
- package/src/views/StatementsView.tsx +0 -335
- package/src/views/SummaryView.tsx +0 -203
- package/src/views/dashboardWidgets.tsx +0 -437
- package/src/views/navReferrer.ts +0 -24
- /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,
|
|
5
|
-
import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, PermissionGate, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, formatCurrency, parseViewId, EntitlementGate, UpgradePrompt, useLimitGuard, invalidateLimit, PersonLink } from '@fayz-ai/
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
831
|
+
FilterChip,
|
|
815
832
|
{
|
|
816
|
-
|
|
817
|
-
|
|
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(
|
|
821
|
-
/* @__PURE__ */
|
|
822
|
-
|
|
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
|
-
|
|
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__ */
|
|
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
|
-
|
|
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
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
},
|
|
2219
|
-
{
|
|
2220
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
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 =
|
|
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
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
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
|
-
|
|
3471
|
-
/* @__PURE__ */
|
|
3472
|
-
|
|
3473
|
-
|
|
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
|
|
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 =
|
|
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 = [...
|
|
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(
|
|
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(
|
|
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
|
|
4488
|
+
return store2.invoices.find((i) => i.id === id) ?? null;
|
|
4806
4489
|
},
|
|
4807
4490
|
async getInvoiceItems(invoiceId) {
|
|
4808
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4560
|
+
store2.movements.push(movement);
|
|
4878
4561
|
}
|
|
4879
4562
|
return invoice;
|
|
4880
4563
|
},
|
|
4881
4564
|
async updateInvoice(id, data) {
|
|
4882
|
-
const invoice =
|
|
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 =
|
|
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
|
-
|
|
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 = [...
|
|
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 =
|
|
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 ?
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
4638
|
+
const invoice = store2.invoices.find((i) => i.id === bill.invoiceId);
|
|
4956
4639
|
if (invoice) {
|
|
4957
|
-
const bills =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5007
|
-
const to =
|
|
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
|
|
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
|
-
|
|
4724
|
+
store2.bankAccounts.push(account);
|
|
5042
4725
|
return account;
|
|
5043
4726
|
},
|
|
5044
4727
|
async updateBankAccount(id, data) {
|
|
5045
|
-
const account =
|
|
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 = [...
|
|
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
|
|
4740
|
+
return store2.cashSessions.find((s) => s.bankAccountId === bankAccountId && s.status === "open") ?? null;
|
|
5058
4741
|
},
|
|
5059
4742
|
async openCashSession(input) {
|
|
5060
|
-
const account =
|
|
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
|
-
|
|
4757
|
+
store2.cashSessions.push(session);
|
|
5075
4758
|
return session;
|
|
5076
4759
|
},
|
|
5077
4760
|
async closeCashSession(input) {
|
|
5078
|
-
const session =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
4790
|
+
return store2.paymentMethods.filter((pm) => pm.isActive);
|
|
5108
4791
|
},
|
|
5109
4792
|
async getPaymentMethodTypes() {
|
|
5110
|
-
return
|
|
4793
|
+
return store2.paymentMethodTypes.filter((t2) => t2.isActive);
|
|
5111
4794
|
},
|
|
5112
4795
|
// --- Chart of Accounts ---
|
|
5113
4796
|
async getChartOfAccounts() {
|
|
5114
|
-
return
|
|
4797
|
+
return store2.chartOfAccounts.filter((n) => n.isActive);
|
|
5115
4798
|
},
|
|
5116
4799
|
async getCostCenters() {
|
|
5117
|
-
return
|
|
4800
|
+
return store2.costCenters.filter((c) => c.isActive);
|
|
5118
4801
|
},
|
|
5119
4802
|
// --- Card Transactions ---
|
|
5120
4803
|
async getCardTransactions(dateRange) {
|
|
5121
|
-
let results = [...
|
|
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 ?
|
|
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(
|
|
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 =
|
|
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 ?
|
|
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 ?
|
|
4855
|
+
const mm = mid ? store2.movements.find((m) => m.id === mid) : void 0;
|
|
5173
4856
|
if (!mm) continue;
|
|
5174
|
-
const inv = mm.invoiceId ?
|
|
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 =
|
|
5199
|
-
const pendingReceivable =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 = [...
|
|
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
|
|
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
|
-
|
|
5025
|
+
store2.notas.unshift(nota);
|
|
5343
5026
|
return nota;
|
|
5344
5027
|
},
|
|
5345
5028
|
async updateNota(id, patch) {
|
|
5346
|
-
const nota =
|
|
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 =
|
|
5035
|
+
const nota = store2.notas.find((n) => n.id === id);
|
|
5353
5036
|
if (!nota) throw new Error("Nota fiscal not found");
|
|
5354
|
-
const issuedCount =
|
|
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 =
|
|
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
|
|
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
|