@fayz-ai/plugin-financial 0.25.9 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CashRegistersView-2G7DTIAA.js +4 -0
- package/dist/CashRegistersView-2G7DTIAA.js.map +1 -0
- package/dist/CommissionRulesView-2RW4ZCDT.js +878 -0
- package/dist/CommissionRulesView-2RW4ZCDT.js.map +1 -0
- package/dist/FinancialGeneralSettings-WXJOCJPZ.js +3 -0
- package/dist/FinancialGeneralSettings-WXJOCJPZ.js.map +1 -0
- package/dist/FinancialPage-PGODULVH.js +6484 -0
- package/dist/FinancialPage-PGODULVH.js.map +1 -0
- package/dist/PersonStatementWidget-ULXXXYZ4.js +214 -0
- package/dist/PersonStatementWidget-ULXXXYZ4.js.map +1 -0
- package/dist/chunk-7VKJLBYY.js +1736 -0
- package/dist/chunk-7VKJLBYY.js.map +1 -0
- package/dist/chunk-CNXAVGSW.js +13 -0
- package/dist/chunk-CNXAVGSW.js.map +1 -0
- package/dist/chunk-K37Q7SKX.js +4340 -0
- package/dist/chunk-K37Q7SKX.js.map +1 -0
- package/dist/chunk-MGDAJLUQ.js +823 -0
- package/dist/chunk-MGDAJLUQ.js.map +1 -0
- package/dist/chunk-POYZFEAN.js +64 -0
- package/dist/chunk-POYZFEAN.js.map +1 -0
- package/dist/components/BatchClassify.d.ts +26 -0
- package/dist/components/BatchClassify.d.ts.map +1 -0
- package/dist/components/CashSessionHeaderControl.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4944 -45345
- package/dist/index.js.map +1 -1
- package/dist/migrations/main.browser.js +143 -0
- package/dist/migrations/main.browser.js.map +1 -0
- package/dist/migrations/main.js +26230 -0
- package/dist/migrations/main.js.map +1 -0
- package/dist/payable-agent-TCKHUAQG.js +3 -0
- package/dist/payable-agent-TCKHUAQG.js.map +1 -0
- package/dist/views/ReconciliationView.d.ts.map +1 -1
- package/package.json +11 -5
|
@@ -0,0 +1,4340 @@
|
|
|
1
|
+
import { consumePayableDraft, PayableDraftSurface } from './chunk-7VKJLBYY.js';
|
|
2
|
+
import { useFinancialConfig, useFinancialStore, formatCurrency, useFinancialProvider } from './chunk-CNXAVGSW.js';
|
|
3
|
+
import { FINANCIAL_INVOICE_ZONES } from '@fayz-ai/financial/views/zones';
|
|
4
|
+
export { FINANCIAL_INVOICE_ZONES } from '@fayz-ai/financial/views/zones';
|
|
5
|
+
import * as React13 from 'react';
|
|
6
|
+
import React13__default, { useState, useRef, useEffect, useMemo } from 'react';
|
|
7
|
+
import { useTranslation, createPendingCommand, useUnitMode, useDataChanged, useActiveUnitId, appHref, resolveCurrency, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
8
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
import { Landmark, CreditCard, Banknote, TriangleAlert, Loader2, Check, X, Paperclip, Image, FileText, ChevronRight, UploadCloud, ArrowLeft, ChevronLeft, ExternalLink, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Package, PackagePlus, Receipt, Gift, Briefcase, CalendarDays, DollarSign, LayoutList, Layers, Ticket, FileCheck, ArrowRightLeft, QrCode, Undo2, Building2, MoreVertical, Calendar, User, Wallet, Unlink, Trash2, ChevronDown, Search, Settings } from 'lucide-react';
|
|
10
|
+
import { INLINE_LINK_TONE, SearchSelect, Button, useInModalTaskHost, ModalTask, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, toast, Skeleton, SubpageHeader, DatePicker, Checkbox, Textarea, ChoiceSelect, useSaveBar, tourAnchor, Input, SegmentedControl, CurrencyInput, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Badge, Hint, Popover, PopoverTrigger, PopoverContent } from '@fayz-ai/ui';
|
|
11
|
+
import { createPortal } from 'react-dom';
|
|
12
|
+
import { createIdempotencyKey } from '@fayz-ai/financial/data/idempotency';
|
|
13
|
+
import { useCurrency, useUnitStore, OrderDetailSkeleton, WidgetSlot, PageActions, OrderDetailBlock, TagChip, OrderDetailView, PersonLink, CurrencyScope, useUnitFilter, usePermissionOptional, adminNavigateTo, createArchetypeLookup, useLimitGuard, useLookupCreate, OrderFormSkeleton, OrderFormBlock, TagsField, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, RecordMetaFooter, invalidateLimit, useHouseCurrency } from '@fayz-ai/admin';
|
|
14
|
+
import { T } from '@fayz-ai/financial/data/tables';
|
|
15
|
+
import { groupPaymentsByBill, reversiblePayments } from '@fayz-ai/financial/domain/movement';
|
|
16
|
+
import { describePayment, paymentHolderId } from '@fayz-ai/financial/domain/payment-method';
|
|
17
|
+
import { postableAccounts, accountLabel } from '@fayz-ai/financial/domain/chart-of-accounts';
|
|
18
|
+
import { availableInvoiceItemTypes, splitInstallments } from '@fayz-ai/financial/domain/invoice';
|
|
19
|
+
import { counterpartyAccountingPolicy, resolveCounterpartyAccountingPolicy } from '@fayz-ai/financial/domain/counterparty';
|
|
20
|
+
|
|
21
|
+
var ACCOUNTS_ROUTE = "/registry/accounts";
|
|
22
|
+
var CHART_OF_ACCOUNTS_ROUTE = "/settings/financial/chart-of-accounts";
|
|
23
|
+
var COST_CENTERS_ROUTE = "/settings/financial/cost-centers";
|
|
24
|
+
function ink(hex) {
|
|
25
|
+
const h = hex.replace("#", "");
|
|
26
|
+
if (h.length !== 6) return "#fff";
|
|
27
|
+
const [r, g, b] = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
28
|
+
return (r * 299 + g * 587 + b * 114) / 1e3 > 150 ? "#111" : "#fff";
|
|
29
|
+
}
|
|
30
|
+
function brandShort(name) {
|
|
31
|
+
const n = name.trim();
|
|
32
|
+
const map = {
|
|
33
|
+
"american express": "AMEX",
|
|
34
|
+
mastercard: "MASTER",
|
|
35
|
+
"diners club": "DINERS",
|
|
36
|
+
hipercard: "HIPER"
|
|
37
|
+
};
|
|
38
|
+
return (map[n.toLowerCase()] ?? n).toUpperCase().slice(0, 7);
|
|
39
|
+
}
|
|
40
|
+
var brandCache = null;
|
|
41
|
+
var brandPromise = null;
|
|
42
|
+
function loadBrands() {
|
|
43
|
+
if (!brandPromise) {
|
|
44
|
+
const client = getSupabaseClientOptional();
|
|
45
|
+
brandPromise = client ? client.from("plg_financial_card_brands").select("id, name").then(({ data }) => {
|
|
46
|
+
brandCache = new Map((data ?? []).map((b) => [b.id, b.name]));
|
|
47
|
+
}).catch(() => {
|
|
48
|
+
brandCache = /* @__PURE__ */ new Map();
|
|
49
|
+
}) : Promise.resolve().then(() => {
|
|
50
|
+
brandCache = /* @__PURE__ */ new Map();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return brandPromise;
|
|
54
|
+
}
|
|
55
|
+
function useBrandName(id) {
|
|
56
|
+
const [, force] = useState(0);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!id || brandCache) return;
|
|
59
|
+
let alive = true;
|
|
60
|
+
void loadBrands().then(() => {
|
|
61
|
+
if (alive) force((n) => n + 1);
|
|
62
|
+
});
|
|
63
|
+
return () => {
|
|
64
|
+
alive = false;
|
|
65
|
+
};
|
|
66
|
+
}, [id]);
|
|
67
|
+
return id ? brandCache?.get(id) : void 0;
|
|
68
|
+
}
|
|
69
|
+
function CardVisual({
|
|
70
|
+
lastFour,
|
|
71
|
+
color,
|
|
72
|
+
brandId,
|
|
73
|
+
size: size2 = "sm"
|
|
74
|
+
}) {
|
|
75
|
+
const brand = useBrandName(brandId);
|
|
76
|
+
const last = (lastFour ?? "").trim();
|
|
77
|
+
if (!last && !color && !brand) return null;
|
|
78
|
+
const bg = color && /^#[0-9a-fA-F]{6}$/.test(color) ? color : void 0;
|
|
79
|
+
const lg = size2 === "lg";
|
|
80
|
+
if (size2 === "xs") {
|
|
81
|
+
return /* @__PURE__ */ jsx(
|
|
82
|
+
"span",
|
|
83
|
+
{
|
|
84
|
+
className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[3px] border align-middle text-[6px] font-semibold leading-none tabular-nums",
|
|
85
|
+
style: bg ? { backgroundColor: bg, color: ink(bg), borderColor: "transparent" } : void 0,
|
|
86
|
+
title: [brand, last && `\u2022\u2022\u2022\u2022 ${last}`].filter(Boolean).join(" "),
|
|
87
|
+
children: last
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return /* @__PURE__ */ jsxs(
|
|
92
|
+
"span",
|
|
93
|
+
{
|
|
94
|
+
className: `inline-flex flex-col justify-between border align-middle ${lg ? "h-20 w-32 rounded-lg px-2.5 py-2" : "h-[30px] w-[48px] rounded-[4px] px-1 py-1"}`,
|
|
95
|
+
style: bg ? { backgroundColor: bg, color: ink(bg), borderColor: "transparent" } : void 0,
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-start justify-between gap-1", children: [
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
"span",
|
|
100
|
+
{
|
|
101
|
+
className: `rounded-[1px] ${lg ? "h-3 w-6" : "h-1.5 w-2.5"}`,
|
|
102
|
+
style: { backgroundColor: bg ? "rgba(0,0,0,.25)" : "var(--muted)" }
|
|
103
|
+
}
|
|
104
|
+
),
|
|
105
|
+
brand && /* @__PURE__ */ jsx("span", { className: `truncate font-semibold leading-none tracking-wide opacity-80 ${lg ? "text-[10px]" : "text-[6px]"}`, children: brandShort(brand) })
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ jsx("span", { className: `font-medium leading-none tabular-nums ${lg ? "text-sm" : "text-[9px]"}`, children: last ? `\u2022\u2022\u2022\u2022 ${last}` : "" })
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function renderCardChip(value, row) {
|
|
113
|
+
const r = row;
|
|
114
|
+
return /* @__PURE__ */ jsx(CardVisual, { lastFour: value == null ? "" : String(value), color: r?.color, brandId: r?.cardBrandId });
|
|
115
|
+
}
|
|
116
|
+
function renderAccountAvatar(item) {
|
|
117
|
+
const r = item;
|
|
118
|
+
if (!r || r.kind !== "card_payable") return null;
|
|
119
|
+
return /* @__PURE__ */ jsx(CardVisual, { size: "lg", lastFour: r.lastFour, color: r.color, brandId: r.cardBrandId });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/lib/br-banks.ts
|
|
123
|
+
var LOGO_BASE = "https://cdn.jsdelivr.net/gh/Tgentil/Bancos-em-SVG@fe1d43f0cf379135bd01c987bc147a04fdf48c6d";
|
|
124
|
+
var logo = (path) => `${LOGO_BASE}/${path.split("/").map(encodeURIComponent).join("/")}`;
|
|
125
|
+
var BR_BANKS = [
|
|
126
|
+
{ code: "001", name: "Banco do Brasil", aliases: ["bb"], logo: logo("Banco do Brasil S.A/banco-do-brasil-sem-fundo.svg") },
|
|
127
|
+
{ code: "341", name: "Ita\xFA", aliases: ["itau", "ita\xFA unibanco"], logo: logo("Ita\xFA Unibanco S.A/itau.svg") },
|
|
128
|
+
{ code: "237", name: "Bradesco", logo: logo("Bradesco S.A/bradesco.svg") },
|
|
129
|
+
{ code: "104", name: "Caixa Econ\xF4mica Federal", aliases: ["caixa", "cef"], logo: logo("Caixa Econ\xF4mica Federal/caixa-economica-federal-1.svg") },
|
|
130
|
+
{ code: "033", name: "Santander", logo: logo("Banco Santander Brasil S.A/banco-santander-logo.svg") },
|
|
131
|
+
{ code: "260", name: "Nubank", aliases: ["nu pagamentos"], logo: logo("Nu Pagamentos S.A/nubank-logo-2021.svg") },
|
|
132
|
+
{ code: "077", name: "Inter", aliases: ["banco inter"], logo: logo("Banco Inter S.A/inter.svg") },
|
|
133
|
+
{ code: "336", name: "C6 Bank", aliases: ["c6"], logo: logo("Banco C6 S.A/c6 bank.svg") },
|
|
134
|
+
{ code: "208", name: "BTG Pactual", aliases: ["btg"], logo: logo("Banco BTG Pacutal/btg-pactual.svg") },
|
|
135
|
+
{ code: "756", name: "Sicoob", logo: logo("Sicoob/sicoob-vector-logo.svg") },
|
|
136
|
+
{ code: "748", name: "Sicredi", logo: logo("Sicredi/logo-svg2.svg") },
|
|
137
|
+
{ code: "290", name: "PagBank", aliases: ["pagseguro"], logo: logo("PagSeguro Internet S.A/logo-pagbank.svg") },
|
|
138
|
+
{ code: "197", name: "Stone", logo: logo("Stone Pagamentos S.A/stone.svg") },
|
|
139
|
+
{ code: "323", name: "Mercado Pago", logo: logo("Mercado Pago/mercado-pago.svg") },
|
|
140
|
+
{ code: "542", name: "InfinitePay", aliases: ["cloudwalk"], logo: logo("InfinitePay/InfitePay.svg") },
|
|
141
|
+
{ code: "403", name: "Cora", logo: logo("Cora Sociedade Credito Direto S.A/icone-cora-rosa-pequeno.svg") },
|
|
142
|
+
{ code: "380", name: "PicPay", logo: logo("PicPay/Logo-PicPay.svg") },
|
|
143
|
+
{ code: "364", name: "Ef\xED", aliases: ["gerencianet", "efi bank"], logo: logo("Ef\xED - Gerencianet/logo-efi-bank-laranja.svg") },
|
|
144
|
+
{ code: "461", name: "Asaas", logo: logo("Asaas IP S.A/header-logo-azul.svg") },
|
|
145
|
+
{ code: "422", name: "Safra", logo: logo("Banco Safra S.A/logo-safra.svg") },
|
|
146
|
+
{ code: "212", name: "Banco Original", aliases: ["original"], logo: logo("Banco Original S.A/banco-original-logo-verde.svg") },
|
|
147
|
+
{ code: "655", name: "Banco BV", aliases: ["votorantim", "bv"], logo: logo("Banco Votorantim/banco-bv-logo.svg") },
|
|
148
|
+
{ code: "041", name: "Banrisul", logo: logo("Banrisul/banrisul-logo-2023.svg") },
|
|
149
|
+
{ code: "070", name: "BRB", aliases: ["banco de bras\xEDlia"], logo: logo("BRB - Banco de Brasilia/brb-logo.svg") },
|
|
150
|
+
{ code: "136", name: "Unicred", logo: logo("Unicred/verde.svg") },
|
|
151
|
+
{ code: "133", name: "Cresol", logo: logo("Cresol/Icone-original.svg") },
|
|
152
|
+
{ code: "085", name: "Ailos", logo: logo("Ailos/logo-ailos.svg") },
|
|
153
|
+
{ code: "004", name: "Banco do Nordeste", aliases: ["bnb"], logo: logo("Banco do Nordeste do Brasil S.A/Logo_BNB.svg") },
|
|
154
|
+
{ code: "021", name: "Banestes", logo: logo("Banco do Estado do Espirito Santo/banestes.svg") },
|
|
155
|
+
{ code: "037", name: "Banpar\xE1", logo: logo("Banco do Estado do Para/Logo_do_Banpar\xE1.svg") },
|
|
156
|
+
{ code: "047", name: "Banese", logo: logo("Banco do Estado do Sergipe/logo banese.svg") },
|
|
157
|
+
{ code: "389", name: "Mercantil do Brasil", aliases: ["mercantil"], logo: logo("Banco Mercantil do Brasil S.A/banco-mercantil-novo-azul.svg") },
|
|
158
|
+
{ code: "318", name: "BMG", logo: logo("Banco BMG/banco-bmg-logo.svg") },
|
|
159
|
+
{ code: "707", name: "Daycoval", logo: logo("Banco Daycoval/logo-Daycoval.svg") },
|
|
160
|
+
{ code: "246", name: "ABC Brasil", logo: logo("ABC Brasil/logoabc.svg") },
|
|
161
|
+
{ code: "218", name: "BS2", logo: logo("Banco BS2 S.A/Banco_BS2.svg") },
|
|
162
|
+
{ code: "102", name: "XP", aliases: ["xp investimentos"], logo: logo("XP Investimentos/xp-investimentos-logo.svg") },
|
|
163
|
+
{ code: "536", name: "Neon", logo: logo("Neon/header-logo-neon.svg") }
|
|
164
|
+
];
|
|
165
|
+
var norm = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
|
|
166
|
+
var digits = (s) => s.replace(/\D/g, "");
|
|
167
|
+
function findBrBank(code, name) {
|
|
168
|
+
const c = digits(code ?? "");
|
|
169
|
+
if (c) {
|
|
170
|
+
const byCode = BR_BANKS.find((b) => Number(b.code) === Number(c));
|
|
171
|
+
if (byCode) return byCode;
|
|
172
|
+
}
|
|
173
|
+
const n = norm(name ?? "");
|
|
174
|
+
if (!n) return void 0;
|
|
175
|
+
return BR_BANKS.find((b) => norm(b.name) === n || b.aliases?.some((a) => norm(a) === n));
|
|
176
|
+
}
|
|
177
|
+
function searchBrBanks(query) {
|
|
178
|
+
const q = norm(query);
|
|
179
|
+
if (!q) return BR_BANKS;
|
|
180
|
+
const qd = digits(q);
|
|
181
|
+
return BR_BANKS.filter(
|
|
182
|
+
(b) => qd && qd === q && (b.code.startsWith(qd) || b.code.replace(/^0+/, "").startsWith(qd)) || norm(b.name).includes(q) || b.aliases?.some((a) => norm(a).includes(q))
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
function BankLogo({ bank, size: size2 = 20 }) {
|
|
186
|
+
const [broken, setBroken] = useState(false);
|
|
187
|
+
if (!bank?.logo || broken) {
|
|
188
|
+
return /* @__PURE__ */ jsx(Landmark, { className: "text-muted-foreground", style: { width: size2 * 0.8, height: size2 * 0.8 }, "aria-hidden": true });
|
|
189
|
+
}
|
|
190
|
+
return /* @__PURE__ */ jsx(
|
|
191
|
+
"img",
|
|
192
|
+
{
|
|
193
|
+
src: bank.logo,
|
|
194
|
+
alt: "",
|
|
195
|
+
width: size2,
|
|
196
|
+
height: size2,
|
|
197
|
+
loading: "lazy",
|
|
198
|
+
onError: () => setBroken(true),
|
|
199
|
+
className: "shrink-0 object-contain",
|
|
200
|
+
style: { width: size2, height: size2 }
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
var fromCatalog = (entry) => entry.compe ? { code: entry.compe, name: entry.name, logo: entry.logoUrl } : null;
|
|
205
|
+
function BankPicker({ code, name, search, onChange }) {
|
|
206
|
+
const known = findBrBank(code, name);
|
|
207
|
+
const toOption = (b) => ({
|
|
208
|
+
id: b.code,
|
|
209
|
+
label: b.name,
|
|
210
|
+
subtitle: b.code,
|
|
211
|
+
icon: /* @__PURE__ */ jsx(BankLogo, { bank: b }),
|
|
212
|
+
data: b
|
|
213
|
+
});
|
|
214
|
+
const onSearch = async (query) => {
|
|
215
|
+
const local = searchBrBanks(query);
|
|
216
|
+
if (!search) return local.map(toOption);
|
|
217
|
+
try {
|
|
218
|
+
const remote = (await search(query)).map(fromCatalog).filter(Boolean);
|
|
219
|
+
if (!remote.length) return local.map(toOption);
|
|
220
|
+
const merged = remote.map((bank) => findBrBank(bank.code) ?? bank);
|
|
221
|
+
const seen = new Set(merged.map((bank) => bank.code));
|
|
222
|
+
return [...merged, ...local.filter((bank) => !seen.has(bank.code))].map(toOption);
|
|
223
|
+
} catch {
|
|
224
|
+
return local.map(toOption);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
return /* @__PURE__ */ jsx(
|
|
228
|
+
SearchSelect,
|
|
229
|
+
{
|
|
230
|
+
value: name ? known?.code ?? `name:${name}` : "",
|
|
231
|
+
displayValue: name ?? "",
|
|
232
|
+
leading: name ? /* @__PURE__ */ jsx(BankLogo, { bank: known, size: 18 }) : void 0,
|
|
233
|
+
placeholder: "Busque pelo nome ou n\xFAmero",
|
|
234
|
+
minChars: 0,
|
|
235
|
+
loadOnOpen: true,
|
|
236
|
+
onSearch,
|
|
237
|
+
onChange: (_id, option) => {
|
|
238
|
+
const b = option?.data;
|
|
239
|
+
onChange(b ? { code: b.code, name: b.name } : { code: "", name: "" });
|
|
240
|
+
},
|
|
241
|
+
allowCreate: true,
|
|
242
|
+
createLabel: "Usar",
|
|
243
|
+
clearLabel: "Limpar",
|
|
244
|
+
onCreate: (q) => onChange({ code: code ?? "", name: q.trim() }),
|
|
245
|
+
noResultsLabel: "Nenhum banco com esse nome",
|
|
246
|
+
debounce: 0
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
var METHOD_ICONS = {
|
|
251
|
+
cash: Banknote,
|
|
252
|
+
pix: QrCode,
|
|
253
|
+
credit_card: CreditCard,
|
|
254
|
+
debit_card: CreditCard,
|
|
255
|
+
bank_transfer: ArrowRightLeft,
|
|
256
|
+
check: FileCheck,
|
|
257
|
+
boleto: Receipt,
|
|
258
|
+
voucher: Ticket
|
|
259
|
+
};
|
|
260
|
+
function MethodIcon({ type }) {
|
|
261
|
+
const Icon = METHOD_ICONS[type?.transactionType ?? ""] ?? Wallet;
|
|
262
|
+
return /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
263
|
+
}
|
|
264
|
+
function AccountIcon({ account }) {
|
|
265
|
+
if (!account) return null;
|
|
266
|
+
if (account.accountType === "card_payable" && (account.lastFour || account.color || account.cardBrandId)) {
|
|
267
|
+
return /* @__PURE__ */ jsx(CardVisual, { size: "xs", lastFour: account.lastFour ?? void 0, color: account.color ?? void 0, brandId: account.cardBrandId ?? void 0 });
|
|
268
|
+
}
|
|
269
|
+
if (account.accountType.startsWith("card_")) return /* @__PURE__ */ jsx(CreditCard, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
270
|
+
if (account.accountType === "cash_physical" || account.accountType === "cash_session") {
|
|
271
|
+
return /* @__PURE__ */ jsx(Banknote, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
272
|
+
}
|
|
273
|
+
const bank = findBrBank(account.bankCode, account.bankName ?? account.name);
|
|
274
|
+
return bank?.logo ? /* @__PURE__ */ jsx(BankLogo, { bank, size: 16 }) : /* @__PURE__ */ jsx(Landmark, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
275
|
+
}
|
|
276
|
+
var FIELD_CLASS = "mt-1 flex w-full items-center gap-2 rounded-input border border-input bg-card px-3 py-2 text-left text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring";
|
|
277
|
+
var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
278
|
+
function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className, onManage, manageLabel, variant = "inline" }) {
|
|
279
|
+
const [open, setOpen] = React13__default.useState(false);
|
|
280
|
+
const [q, setQ] = React13__default.useState("");
|
|
281
|
+
const selected = accounts.find((a) => a.id === value);
|
|
282
|
+
const list = q ? accounts.filter((a) => norm2(a.name).includes(norm2(q))) : accounts;
|
|
283
|
+
const pick = (id) => {
|
|
284
|
+
onChange(id);
|
|
285
|
+
setOpen(false);
|
|
286
|
+
setQ("");
|
|
287
|
+
};
|
|
288
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (o) => {
|
|
289
|
+
setOpen(o);
|
|
290
|
+
if (!o) setQ("");
|
|
291
|
+
}, children: [
|
|
292
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
293
|
+
Button,
|
|
294
|
+
{
|
|
295
|
+
variant: "bare",
|
|
296
|
+
size: "bare",
|
|
297
|
+
type: "button",
|
|
298
|
+
disabled,
|
|
299
|
+
title,
|
|
300
|
+
"aria-label": ariaLabel,
|
|
301
|
+
className: `font-normal whitespace-normal justify-start ${variant === "field" ? FIELD_CLASS : "inline-flex max-w-full items-center gap-1 text-xs"} disabled:cursor-not-allowed disabled:opacity-50 ${className ?? ""}`,
|
|
302
|
+
children: [
|
|
303
|
+
selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
304
|
+
/* @__PURE__ */ jsx(AccountIcon, { account: selected }),
|
|
305
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: selected.name })
|
|
306
|
+
] }) : /* @__PURE__ */ jsx("span", { className: variant === "field" ? "text-muted-foreground" : void 0, children: placeholder }),
|
|
307
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: `${variant === "field" ? "ml-auto h-4 w-4" : "h-3 w-3"} shrink-0 opacity-50`, "aria-hidden": true })
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
) }),
|
|
311
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: variant === "field" ? "w-[var(--radix-popover-trigger-width)] p-1" : "w-64 p-1", children: [
|
|
312
|
+
/* @__PURE__ */ jsxs("div", { className: "relative mb-1", children: [
|
|
313
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground", "aria-hidden": true }),
|
|
314
|
+
/* @__PURE__ */ jsx(
|
|
315
|
+
Input,
|
|
316
|
+
{
|
|
317
|
+
autoFocus: true,
|
|
318
|
+
value: q,
|
|
319
|
+
onChange: (e) => setQ(e.target.value),
|
|
320
|
+
onKeyDown: (e) => {
|
|
321
|
+
if (e.key === "Enter" && list[0]) {
|
|
322
|
+
e.preventDefault();
|
|
323
|
+
pick(list[0].id);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
placeholder: "Buscar conta\u2026",
|
|
327
|
+
className: "h-8 rounded-md border-0 bg-muted/50 pl-7 pr-2 text-xs shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
] }),
|
|
331
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto", children: list.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: "Nenhuma conta encontrada" }) : list.map((a) => /* @__PURE__ */ jsxs(
|
|
332
|
+
Button,
|
|
333
|
+
{
|
|
334
|
+
variant: "bare",
|
|
335
|
+
size: "bare",
|
|
336
|
+
type: "button",
|
|
337
|
+
onClick: () => pick(a.id),
|
|
338
|
+
className: `font-normal whitespace-normal justify-start flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted ${a.id === value ? "font-medium" : ""}`,
|
|
339
|
+
children: [
|
|
340
|
+
/* @__PURE__ */ jsx(AccountIcon, { account: a }),
|
|
341
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: a.name }),
|
|
342
|
+
a.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
|
|
343
|
+
]
|
|
344
|
+
},
|
|
345
|
+
a.id
|
|
346
|
+
)) }),
|
|
347
|
+
value && /* @__PURE__ */ jsx(Button, { variant: "bare", size: "bare", type: "button", onClick: () => pick(void 0), className: "font-normal whitespace-normal justify-start mt-1 w-full rounded-md px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted", children: clearLabel }),
|
|
348
|
+
onManage && /* @__PURE__ */ jsxs(
|
|
349
|
+
Button,
|
|
350
|
+
{
|
|
351
|
+
variant: "bare",
|
|
352
|
+
size: "bare",
|
|
353
|
+
type: "button",
|
|
354
|
+
onClick: () => {
|
|
355
|
+
setOpen(false);
|
|
356
|
+
onManage();
|
|
357
|
+
},
|
|
358
|
+
className: "font-normal whitespace-normal justify-start mt-1 flex w-full items-center gap-2 rounded-md border-t px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
359
|
+
children: [
|
|
360
|
+
/* @__PURE__ */ jsx(Settings, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
|
|
361
|
+
manageLabel
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
] })
|
|
366
|
+
] });
|
|
367
|
+
}
|
|
368
|
+
var ACCOUNT_TYPES_FOR_TX = {
|
|
369
|
+
cash: ["cash_physical", "cash_session"],
|
|
370
|
+
credit_card: ["card_receivable_credit"],
|
|
371
|
+
debit_card: ["card_receivable_debit"],
|
|
372
|
+
pix: ["bank"],
|
|
373
|
+
bank_transfer: ["bank"],
|
|
374
|
+
boleto: ["bank"],
|
|
375
|
+
check: ["bank"],
|
|
376
|
+
voucher: ["bank"]
|
|
377
|
+
};
|
|
378
|
+
var TYPE_ICONS = {
|
|
379
|
+
cash: Banknote,
|
|
380
|
+
pix: QrCode,
|
|
381
|
+
credit_card: CreditCard,
|
|
382
|
+
debit_card: CreditCard,
|
|
383
|
+
bank_transfer: ArrowRightLeft,
|
|
384
|
+
check: FileCheck
|
|
385
|
+
};
|
|
386
|
+
var TYPE_COLORS = {
|
|
387
|
+
cash: "border-success/30 bg-success/5 hover:bg-success/10 data-[active=true]:border-success data-[active=true]:bg-success/15",
|
|
388
|
+
pix: "border-teal-500/30 bg-teal-500/5 hover:bg-teal-500/10 data-[active=true]:border-teal-500 data-[active=true]:bg-teal-500/15",
|
|
389
|
+
credit_card: "border-info/30 bg-info/5 hover:bg-info/10 data-[active=true]:border-info data-[active=true]:bg-info/15",
|
|
390
|
+
debit_card: "border-magic/30 bg-magic/5 hover:bg-magic/10 data-[active=true]:border-magic data-[active=true]:bg-magic/15",
|
|
391
|
+
bank_transfer: "border-warning/30 bg-warning/5 hover:bg-warning/10 data-[active=true]:border-warning data-[active=true]:bg-warning/15",
|
|
392
|
+
check: "border-border bg-muted/40 hover:bg-muted/60 data-[active=true]:border-foreground data-[active=true]:bg-muted"
|
|
393
|
+
};
|
|
394
|
+
var ICON_COLORS = {
|
|
395
|
+
cash: "text-success",
|
|
396
|
+
pix: "text-teal-500",
|
|
397
|
+
credit_card: "text-info",
|
|
398
|
+
debit_card: "text-magic",
|
|
399
|
+
bank_transfer: "text-warning",
|
|
400
|
+
check: "text-muted-foreground"
|
|
401
|
+
};
|
|
402
|
+
function eligibleAccountsFor(bankAccounts, direction, type) {
|
|
403
|
+
const txType = type?.transactionType ?? "";
|
|
404
|
+
const active = bankAccounts.filter((a) => a.isActive !== false);
|
|
405
|
+
if (direction === "debit") {
|
|
406
|
+
if (txType === "credit_card") return active.filter((a) => a.accountType === "card_payable");
|
|
407
|
+
const own = (type?.allowedAccountTypes?.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[txType])?.filter((k) => !k.startsWith("card_"));
|
|
408
|
+
const kinds = own?.length ? own : ["bank", "cash_physical", "cash_session"];
|
|
409
|
+
return active.filter((a) => kinds.includes(a.accountType));
|
|
410
|
+
}
|
|
411
|
+
const allowed = type?.allowedAccountTypes && type.allowedAccountTypes.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[txType];
|
|
412
|
+
if (!allowed || !allowed.length) return bankAccounts;
|
|
413
|
+
return bankAccounts.filter((a) => allowed.includes(a.accountType));
|
|
414
|
+
}
|
|
415
|
+
function usePaymentFields(options) {
|
|
416
|
+
const t = useTranslation();
|
|
417
|
+
const { currency } = useFinancialConfig();
|
|
418
|
+
const {
|
|
419
|
+
direction,
|
|
420
|
+
initialAmount,
|
|
421
|
+
initialDate,
|
|
422
|
+
lockAmount = false,
|
|
423
|
+
disabled = false,
|
|
424
|
+
onSubmit,
|
|
425
|
+
initialMethodTypeId,
|
|
426
|
+
initialMethodId,
|
|
427
|
+
initialAccountId
|
|
428
|
+
} = options;
|
|
429
|
+
const cashSessions = useFinancialStore((s) => s.cashSessions);
|
|
430
|
+
const fetchCashSessions = useFinancialStore((s) => s.fetchCashSessions);
|
|
431
|
+
const openSession = cashSessions.find((s) => s.status === "open");
|
|
432
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
433
|
+
const paymentMethods = useFinancialStore((s) => s.paymentMethods);
|
|
434
|
+
const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
|
|
435
|
+
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
436
|
+
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
437
|
+
const [amount, setAmount] = useState(initialAmount);
|
|
438
|
+
const [paymentDate, setPaymentDate] = useState(initialDate ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
439
|
+
const [selectedTypeId, setSelectedTypeId] = useState(initialMethodTypeId ?? "");
|
|
440
|
+
const [paymentMethodId, setPaymentMethodId] = useState(initialMethodId ?? "");
|
|
441
|
+
const [bankAccountId, setBankAccountId] = useState(initialAccountId ?? "");
|
|
442
|
+
const [cardBrand, setCardBrand] = useState("");
|
|
443
|
+
const [cardInstallments, setCardInstallments] = useState(1);
|
|
444
|
+
const [notes, setNotes] = useState("");
|
|
445
|
+
const [expandMethodPicker, setExpandMethodPicker] = useState(!initialMethodTypeId);
|
|
446
|
+
const [editSource, setEditSource] = useState(false);
|
|
447
|
+
const sourceAnswered = !!initialMethodTypeId && !!initialAccountId && !editSource;
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
fetchBankAccounts();
|
|
450
|
+
fetchPaymentMethods();
|
|
451
|
+
}, []);
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
void fetchCashSessions();
|
|
454
|
+
}, [fetchCashSessions]);
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
if (lockAmount) setAmount(initialAmount);
|
|
457
|
+
}, [lockAmount, initialAmount]);
|
|
458
|
+
const selectedType = useMemo(
|
|
459
|
+
() => paymentMethodTypes.find((tp) => tp.id === selectedTypeId),
|
|
460
|
+
[paymentMethodTypes, selectedTypeId]
|
|
461
|
+
);
|
|
462
|
+
const txType = selectedType?.transactionType ?? "";
|
|
463
|
+
const filteredMethods = useMemo(
|
|
464
|
+
() => paymentMethods.filter((m) => m.paymentMethodTypeId === selectedTypeId),
|
|
465
|
+
[paymentMethods, selectedTypeId]
|
|
466
|
+
);
|
|
467
|
+
const isPayable = direction === "debit";
|
|
468
|
+
const payingByCompanyCard = isPayable && txType === "credit_card";
|
|
469
|
+
const needsCard = !isPayable && (txType === "credit_card" || txType === "debit_card");
|
|
470
|
+
const needsCardInstallments = txType === "credit_card";
|
|
471
|
+
const eligibleAccounts = useMemo(
|
|
472
|
+
() => eligibleAccountsFor(bankAccounts, direction, selectedType),
|
|
473
|
+
[bankAccounts, direction, selectedType]
|
|
474
|
+
);
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
if (selectedTypeId && !bankAccountId && eligibleAccounts.length) {
|
|
477
|
+
setBankAccountId(eligibleAccounts[0].id);
|
|
478
|
+
}
|
|
479
|
+
}, [selectedTypeId, eligibleAccounts]);
|
|
480
|
+
function handleSelectType(typeId) {
|
|
481
|
+
setSelectedTypeId(typeId);
|
|
482
|
+
setExpandMethodPicker(false);
|
|
483
|
+
setPaymentMethodId("");
|
|
484
|
+
setBankAccountId("");
|
|
485
|
+
setCardBrand("");
|
|
486
|
+
setCardInstallments(1);
|
|
487
|
+
}
|
|
488
|
+
const needsOpenCashSession = txType === "cash" && !openSession;
|
|
489
|
+
const blockedReason = amount <= 0 ? t("financial.payment.amountRequired") : !selectedTypeId ? t("financial.payment.methodRequired") : needsOpenCashSession ? t("financial.payment.cashNeedsOpenSession") : !bankAccountId ? t("financial.payment.accountRequired") : null;
|
|
490
|
+
function handleKeyDown(e) {
|
|
491
|
+
if (!onSubmit) return;
|
|
492
|
+
if (e.key !== "Enter" || e.shiftKey) return;
|
|
493
|
+
if (e.target.tagName === "TEXTAREA") return;
|
|
494
|
+
e.preventDefault();
|
|
495
|
+
if (!blockedReason) onSubmit();
|
|
496
|
+
}
|
|
497
|
+
const SelectedIcon = selectedType ? TYPE_ICONS[txType] ?? Banknote : null;
|
|
498
|
+
const selectedAccount = bankAccounts.find((a) => a.id === bankAccountId);
|
|
499
|
+
const node = /* @__PURE__ */ jsxs("div", { onKeyDown: handleKeyDown, children: [
|
|
500
|
+
/* @__PURE__ */ jsxs("fieldset", { disabled, className: "space-y-4", children: [
|
|
501
|
+
sourceAnswered && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-button border bg-muted/30 px-3 py-2.5", children: [
|
|
502
|
+
SelectedIcon && /* @__PURE__ */ jsx(SelectedIcon, { className: `h-4 w-4 shrink-0 ${ICON_COLORS[txType] ?? ""}`, "aria-hidden": true }),
|
|
503
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [
|
|
504
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: selectedType?.name ?? "" }),
|
|
505
|
+
selectedAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
506
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: " \xB7 " }),
|
|
507
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: selectedAccount.name })
|
|
508
|
+
] })
|
|
509
|
+
] }),
|
|
510
|
+
/* @__PURE__ */ jsx(
|
|
511
|
+
Button,
|
|
512
|
+
{
|
|
513
|
+
variant: "bare",
|
|
514
|
+
size: "bare",
|
|
515
|
+
type: "button",
|
|
516
|
+
onClick: () => {
|
|
517
|
+
setEditSource(true);
|
|
518
|
+
setExpandMethodPicker(true);
|
|
519
|
+
},
|
|
520
|
+
"aria-label": t("financial.payment.changeSource"),
|
|
521
|
+
title: t("financial.payment.changeSource"),
|
|
522
|
+
className: "shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
523
|
+
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3" })
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
] }),
|
|
527
|
+
/* @__PURE__ */ jsxs("div", { hidden: sourceAnswered, children: [
|
|
528
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.payment.method") }),
|
|
529
|
+
/* @__PURE__ */ jsx(
|
|
530
|
+
"div",
|
|
531
|
+
{
|
|
532
|
+
className: "grid transition-all duration-200 ease-out",
|
|
533
|
+
style: { gridTemplateRows: selectedTypeId && !expandMethodPicker ? "1fr" : "0fr" },
|
|
534
|
+
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: selectedType && /* @__PURE__ */ jsxs(
|
|
535
|
+
Button,
|
|
536
|
+
{
|
|
537
|
+
variant: "bare",
|
|
538
|
+
size: "bare",
|
|
539
|
+
type: "button",
|
|
540
|
+
onClick: () => setExpandMethodPicker(true),
|
|
541
|
+
className: `font-normal justify-start whitespace-normal flex items-center gap-2 mt-1.5 w-full rounded-button border px-3 py-2.5 text-left transition-colors hover:bg-muted/30 ${TYPE_COLORS[txType] ?? ""}`,
|
|
542
|
+
"data-active": "true",
|
|
543
|
+
children: [
|
|
544
|
+
SelectedIcon && /* @__PURE__ */ jsx(SelectedIcon, { className: `h-4 w-4 ${ICON_COLORS[txType] ?? ""}` }),
|
|
545
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium flex-1", children: selectedType?.name }),
|
|
546
|
+
/* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3 text-muted-foreground" })
|
|
547
|
+
]
|
|
548
|
+
}
|
|
549
|
+
) })
|
|
550
|
+
}
|
|
551
|
+
),
|
|
552
|
+
/* @__PURE__ */ jsx(
|
|
553
|
+
"div",
|
|
554
|
+
{
|
|
555
|
+
className: "grid transition-all duration-200 ease-out",
|
|
556
|
+
style: { gridTemplateRows: expandMethodPicker ? "1fr" : "0fr" },
|
|
557
|
+
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: paymentMethodTypes.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-input bg-muted/30 px-3 py-3 text-center mt-1.5", children: [
|
|
558
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("financial.payment.noMethods") }),
|
|
559
|
+
/* @__PURE__ */ jsxs("a", { href: appHref("/financial/settings"), className: "mt-1.5 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
560
|
+
t("financial.payment.configureMethods"),
|
|
561
|
+
" \u2192"
|
|
562
|
+
] })
|
|
563
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2 mt-1.5", children: paymentMethodTypes.map((type) => {
|
|
564
|
+
const tt = type.transactionType ?? "";
|
|
565
|
+
const Icon = TYPE_ICONS[tt] ?? Banknote;
|
|
566
|
+
const isActive = selectedTypeId === type.id;
|
|
567
|
+
return /* @__PURE__ */ jsxs(
|
|
568
|
+
Button,
|
|
569
|
+
{
|
|
570
|
+
variant: "bare",
|
|
571
|
+
size: "bare",
|
|
572
|
+
type: "button",
|
|
573
|
+
"data-active": isActive,
|
|
574
|
+
onClick: () => handleSelectType(type.id),
|
|
575
|
+
className: `font-normal flex flex-col items-center justify-start gap-1.5 rounded-button border px-2 py-3 text-center transition-all ${TYPE_COLORS[tt] ?? TYPE_COLORS.cash}`,
|
|
576
|
+
children: [
|
|
577
|
+
/* @__PURE__ */ jsx(Icon, { className: `h-5 w-5 ${isActive ? ICON_COLORS[tt] ?? "" : "text-muted-foreground"}` }),
|
|
578
|
+
/* @__PURE__ */ jsx("span", { className: `text-[11px] font-medium leading-tight ${isActive ? "text-foreground" : "text-muted-foreground"}`, children: type.name })
|
|
579
|
+
]
|
|
580
|
+
},
|
|
581
|
+
type.id
|
|
582
|
+
);
|
|
583
|
+
}) }) })
|
|
584
|
+
}
|
|
585
|
+
)
|
|
586
|
+
] }),
|
|
587
|
+
selectedTypeId && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
588
|
+
/* @__PURE__ */ jsxs("div", { className: "stagger-field grid grid-cols-2 items-start gap-3", style: { animationDelay: "0ms" }, children: [
|
|
589
|
+
lockAmount ? (
|
|
590
|
+
// O extrato mandou o valor. Ele aparece como FATO, e a linha
|
|
591
|
+
// abaixo diz por quê — um campo cinza sem explicação faz a
|
|
592
|
+
// pessoa procurar onde se edita.
|
|
593
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
594
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.payment.amount") }),
|
|
595
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-base font-semibold", children: formatCurrency(amount, currency) }),
|
|
596
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t("financial.payment.amountFromStatement") })
|
|
597
|
+
] })
|
|
598
|
+
) : /* @__PURE__ */ jsx(
|
|
599
|
+
CurrencyInput,
|
|
600
|
+
{
|
|
601
|
+
label: t("financial.payment.amount"),
|
|
602
|
+
value: amount,
|
|
603
|
+
onChange: setAmount,
|
|
604
|
+
symbol: currency.symbol,
|
|
605
|
+
locale: currency.locale,
|
|
606
|
+
currencyCode: currency.code
|
|
607
|
+
}
|
|
608
|
+
),
|
|
609
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
610
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.payment.date") }),
|
|
611
|
+
/* @__PURE__ */ jsx(DatePicker, { value: paymentDate, onChange: setPaymentDate, className: "mt-1" })
|
|
612
|
+
] })
|
|
613
|
+
] }),
|
|
614
|
+
filteredMethods.length > 0 && !isPayable && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
|
|
615
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: needsCard ? t("financial.payment.card") : t("financial.payment.account") }),
|
|
616
|
+
/* @__PURE__ */ jsxs(Select, { value: paymentMethodId, onValueChange: setPaymentMethodId, children: [
|
|
617
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("financial.payment.select") }) }),
|
|
618
|
+
/* @__PURE__ */ jsx(SelectContent, { children: filteredMethods.map((m) => /* @__PURE__ */ jsx(SelectItem, { value: m.id, children: m.name }, m.id)) })
|
|
619
|
+
] })
|
|
620
|
+
] }),
|
|
621
|
+
(needsCard || payingByCompanyCard) && /* @__PURE__ */ jsxs("div", { className: `stagger-field ${needsCard && needsCardInstallments ? "fz-fields-2" : "fz-fields"}`, style: { animationDelay: "100ms" }, children: [
|
|
622
|
+
needsCard && /* @__PURE__ */ jsxs("div", { children: [
|
|
623
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.payment.cardBrand") }),
|
|
624
|
+
/* @__PURE__ */ jsxs(Select, { value: cardBrand, onValueChange: setCardBrand, children: [
|
|
625
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("financial.payment.selectBrand") }) }),
|
|
626
|
+
/* @__PURE__ */ jsx(SelectContent, { children: ["Visa", "Mastercard", "American Express", "Elo", "Hipercard", "Diners Club"].map((b) => /* @__PURE__ */ jsx(SelectItem, { value: b, children: b }, b)) })
|
|
627
|
+
] })
|
|
628
|
+
] }),
|
|
629
|
+
needsCardInstallments && /* @__PURE__ */ jsxs("div", { children: [
|
|
630
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.payment.cardInstallments") }),
|
|
631
|
+
/* @__PURE__ */ jsxs(Select, { value: String(cardInstallments), onValueChange: (v) => setCardInstallments(Number(v)), children: [
|
|
632
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
633
|
+
/* @__PURE__ */ jsx(SelectContent, { children: Array.from({ length: 12 }, (_, i) => i + 1).map((n) => /* @__PURE__ */ jsxs(SelectItem, { value: String(n), children: [
|
|
634
|
+
n,
|
|
635
|
+
"x ",
|
|
636
|
+
n > 1 ? formatCurrency(amount / n, currency) : ""
|
|
637
|
+
] }, n)) })
|
|
638
|
+
] })
|
|
639
|
+
] })
|
|
640
|
+
] }),
|
|
641
|
+
needsOpenCashSession && /* @__PURE__ */ jsxs("div", { className: "stagger-field rounded-lg border border-dashed border-warning/40 bg-warning/5 px-3 py-2.5 text-center", children: [
|
|
642
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("financial.payment.cashNeedsOpenSession") }),
|
|
643
|
+
/* @__PURE__ */ jsxs("a", { href: appHref("/financial/cash-registers"), className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
644
|
+
t("financial.payment.openCashSession"),
|
|
645
|
+
" \u2192"
|
|
646
|
+
] })
|
|
647
|
+
] }),
|
|
648
|
+
payingByCompanyCard && !sourceAnswered && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
|
|
649
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.payment.whichCard") }),
|
|
650
|
+
eligibleAccounts.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
|
|
651
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("financial.payment.noCompanyCards") }),
|
|
652
|
+
/* @__PURE__ */ jsxs("a", { href: appHref("/settings/financial/bank-accounts"), className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
653
|
+
t("financial.payment.registerCard"),
|
|
654
|
+
" \u2192"
|
|
655
|
+
] })
|
|
656
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "mt-1.5 grid grid-cols-2 gap-2", role: "radiogroup", children: eligibleAccounts.map((card) => {
|
|
657
|
+
const last4 = (card.lastFour ?? card.accountNumber ?? "").replace(/\D/g, "").slice(-4);
|
|
658
|
+
const active = bankAccountId === card.id;
|
|
659
|
+
return /* @__PURE__ */ jsxs(
|
|
660
|
+
Button,
|
|
661
|
+
{
|
|
662
|
+
variant: "bare",
|
|
663
|
+
size: "bare",
|
|
664
|
+
type: "button",
|
|
665
|
+
role: "radio",
|
|
666
|
+
"aria-checked": active,
|
|
667
|
+
onClick: () => setBankAccountId(card.id),
|
|
668
|
+
className: `font-normal whitespace-normal flex items-center gap-2.5 rounded-button border px-2.5 py-2 text-left transition-colors ${active ? "border-primary bg-primary/5 ring-2 ring-primary/20" : "hover:bg-muted/40"}`,
|
|
669
|
+
children: [
|
|
670
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "shrink-0", children: /* @__PURE__ */ jsx(
|
|
671
|
+
CardVisual,
|
|
672
|
+
{
|
|
673
|
+
lastFour: last4,
|
|
674
|
+
color: card.color ?? void 0,
|
|
675
|
+
brandId: card.cardBrandId ?? void 0
|
|
676
|
+
}
|
|
677
|
+
) }),
|
|
678
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
679
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-xs font-medium", children: card.name }),
|
|
680
|
+
card.bankName && /* @__PURE__ */ jsx("span", { className: "block truncate text-[10px] text-muted-foreground", children: card.bankName })
|
|
681
|
+
] })
|
|
682
|
+
]
|
|
683
|
+
},
|
|
684
|
+
card.id
|
|
685
|
+
);
|
|
686
|
+
}) })
|
|
687
|
+
] }),
|
|
688
|
+
!payingByCompanyCard && !sourceAnswered && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
|
|
689
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.payment.bankAccount") }),
|
|
690
|
+
eligibleAccounts.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
|
|
691
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: bankAccounts.length > 0 && selectedType ? t("financial.payment.noAccountForMethod", { method: selectedType.name }) : t("financial.payment.noAccounts") }),
|
|
692
|
+
/* @__PURE__ */ jsxs("a", { href: appHref("/settings/financial/bank-accounts"), className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
693
|
+
t("financial.payment.configureAccounts"),
|
|
694
|
+
" \u2192"
|
|
695
|
+
] })
|
|
696
|
+
] }) : /* @__PURE__ */ jsx(
|
|
697
|
+
AccountPicker,
|
|
698
|
+
{
|
|
699
|
+
variant: "field",
|
|
700
|
+
accounts: eligibleAccounts,
|
|
701
|
+
value: bankAccountId || void 0,
|
|
702
|
+
onChange: (id) => setBankAccountId(id ?? ""),
|
|
703
|
+
placeholder: t("financial.payment.selectAccount"),
|
|
704
|
+
ariaLabel: t("financial.payment.bankAccount"),
|
|
705
|
+
clearLabel: t("financial.invoiceForm.clear")
|
|
706
|
+
}
|
|
707
|
+
)
|
|
708
|
+
] }),
|
|
709
|
+
/* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "150ms" }, children: [
|
|
710
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.payment.notes") }),
|
|
711
|
+
/* @__PURE__ */ jsx(Input, { type: "text", value: notes, onChange: (e) => setNotes(e.target.value), placeholder: t("financial.payment.optionalNotes"), className: "mt-1" })
|
|
712
|
+
] })
|
|
713
|
+
] }, selectedTypeId)
|
|
714
|
+
] }),
|
|
715
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
716
|
+
.stagger-field {
|
|
717
|
+
animation: field-slide-in 250ms ease-out both;
|
|
718
|
+
}
|
|
719
|
+
@keyframes field-slide-in {
|
|
720
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
721
|
+
to { opacity: 1; transform: translateY(0); }
|
|
722
|
+
}
|
|
723
|
+
` })
|
|
724
|
+
] });
|
|
725
|
+
return {
|
|
726
|
+
node,
|
|
727
|
+
blockedReason,
|
|
728
|
+
setAmount,
|
|
729
|
+
value: {
|
|
730
|
+
amount,
|
|
731
|
+
paymentDate,
|
|
732
|
+
paymentMethodId: paymentMethodId || void 0,
|
|
733
|
+
paymentMethodTypeId: selectedTypeId || void 0,
|
|
734
|
+
bankAccountId: bankAccountId || void 0,
|
|
735
|
+
// Todo pagamento feito com caixa aberto pertence a ele, não só o em
|
|
736
|
+
// dinheiro: o fechamento confere o cartão da sessão também.
|
|
737
|
+
cashSessionId: openSession?.id,
|
|
738
|
+
cardBrand: needsCard ? cardBrand || void 0 : void 0,
|
|
739
|
+
cardInstallments: needsCardInstallments ? cardInstallments : void 0,
|
|
740
|
+
notes: notes.trim() || void 0
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
745
|
+
const t = useTranslation();
|
|
746
|
+
const { currency } = useFinancialConfig();
|
|
747
|
+
const payMovement = useFinancialStore((s) => s.payMovement);
|
|
748
|
+
const remaining = movement.amount - movement.paidAmount;
|
|
749
|
+
const [saving, setSaving] = useState(false);
|
|
750
|
+
const command = React13__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
|
|
751
|
+
const [pending, setPending] = useState(false);
|
|
752
|
+
const [visible, setVisible] = useState(false);
|
|
753
|
+
React13__default.useEffect(() => {
|
|
754
|
+
requestAnimationFrame(() => setVisible(true));
|
|
755
|
+
}, []);
|
|
756
|
+
const source = movement.metadata ?? {};
|
|
757
|
+
const str = (v) => typeof v === "string" && v ? v : void 0;
|
|
758
|
+
const fields = usePaymentFields({
|
|
759
|
+
direction: movement.direction,
|
|
760
|
+
initialAmount: remaining,
|
|
761
|
+
disabled: pending,
|
|
762
|
+
initialMethodTypeId: str(source.payMethodTypeId),
|
|
763
|
+
initialMethodId: str(source.payMethodId),
|
|
764
|
+
initialAccountId: str(source.payAccountId),
|
|
765
|
+
onSubmit: () => {
|
|
766
|
+
if (!saving) void handlePay();
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
function handleClose() {
|
|
770
|
+
if (command.current.pending || command.current.running) return;
|
|
771
|
+
setVisible(false);
|
|
772
|
+
setTimeout(onClose, 200);
|
|
773
|
+
}
|
|
774
|
+
async function handlePay() {
|
|
775
|
+
if (command.current.running) return;
|
|
776
|
+
if (!command.current.pending && fields.blockedReason) return;
|
|
777
|
+
setSaving(true);
|
|
778
|
+
setPending(true);
|
|
779
|
+
try {
|
|
780
|
+
await command.current.run({
|
|
781
|
+
// O comprovante viaja com o pagamento — é dele que ele é prova.
|
|
782
|
+
metadata: receiptUrl ? { receiptUrl } : void 0,
|
|
783
|
+
movementId: movement.id,
|
|
784
|
+
...fields.value
|
|
785
|
+
}, payMovement);
|
|
786
|
+
setVisible(false);
|
|
787
|
+
setTimeout(onPaid, 200);
|
|
788
|
+
} catch {
|
|
789
|
+
} finally {
|
|
790
|
+
setSaving(false);
|
|
791
|
+
setPending(command.current.pending);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const receiving = movement.direction === "credit";
|
|
795
|
+
const title = t(receiving ? "financial.payment.titleReceive" : "financial.payment.title");
|
|
796
|
+
const body = /* @__PURE__ */ jsx("div", { className: "px-5 py-4", children: fields.node });
|
|
797
|
+
const footer = /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-t sticky bottom-0 bg-card px-5 py-4 space-y-2", children: [
|
|
798
|
+
pending && !saving && /* @__PURE__ */ jsxs("p", { role: "status", className: "flex items-start gap-1.5 text-[11px] leading-snug text-muted-foreground", children: [
|
|
799
|
+
/* @__PURE__ */ jsx(TriangleAlert, { className: "mt-px h-3 w-3 shrink-0 text-warning", "aria-hidden": true }),
|
|
800
|
+
t("financial.payment.pendingRetry")
|
|
801
|
+
] }),
|
|
802
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
803
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, disabled: pending || saving, children: t("financial.payment.cancel") }),
|
|
804
|
+
/* @__PURE__ */ jsxs(
|
|
805
|
+
Button,
|
|
806
|
+
{
|
|
807
|
+
variant: "default",
|
|
808
|
+
size: "sm",
|
|
809
|
+
onClick: handlePay,
|
|
810
|
+
disabled: saving || !pending && !!fields.blockedReason,
|
|
811
|
+
title: fields.blockedReason ?? void 0,
|
|
812
|
+
children: [
|
|
813
|
+
saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
814
|
+
saving ? t("financial.payment.processing") : fields.blockedReason && !pending ? fields.blockedReason : t(receiving ? "financial.payment.receiveAmount" : "financial.payment.payAmount", { amount: formatCurrency(fields.value.amount, currency) })
|
|
815
|
+
]
|
|
816
|
+
}
|
|
817
|
+
)
|
|
818
|
+
] })
|
|
819
|
+
] });
|
|
820
|
+
const inTaskHost = useInModalTaskHost();
|
|
821
|
+
if (inTaskHost) {
|
|
822
|
+
return /* @__PURE__ */ jsxs(
|
|
823
|
+
ModalTask,
|
|
824
|
+
{
|
|
825
|
+
open: true,
|
|
826
|
+
title,
|
|
827
|
+
hint: `${t("financial.payment.installment", { number: String(movement.installmentNumber ?? 1) })} \xB7 ${t("financial.payment.remaining", { amount: formatCurrency(remaining, currency) })}`,
|
|
828
|
+
onClose: handleClose,
|
|
829
|
+
children: [
|
|
830
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: body }),
|
|
831
|
+
footer
|
|
832
|
+
]
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
return createPortal(
|
|
837
|
+
/* @__PURE__ */ jsx(
|
|
838
|
+
"div",
|
|
839
|
+
{
|
|
840
|
+
className: "fixed inset-0 z-50 flex items-center justify-center transition-colors duration-200",
|
|
841
|
+
style: { backgroundColor: visible ? "rgba(0,0,0,0.5)" : "rgba(0,0,0,0)" },
|
|
842
|
+
onClick: handleClose,
|
|
843
|
+
children: /* @__PURE__ */ jsxs(
|
|
844
|
+
"div",
|
|
845
|
+
{
|
|
846
|
+
className: "fz-container w-full max-w-md rounded-modal border bg-card shadow-lg mx-4 transition-all duration-200 max-h-[90vh] overflow-y-auto",
|
|
847
|
+
style: {
|
|
848
|
+
opacity: visible ? 1 : 0,
|
|
849
|
+
transform: visible ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)"
|
|
850
|
+
},
|
|
851
|
+
onClick: (e) => e.stopPropagation(),
|
|
852
|
+
children: [
|
|
853
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b sticky top-0 bg-card z-10", children: [
|
|
854
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
855
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: title }),
|
|
856
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
857
|
+
t("financial.payment.installment", { number: String(movement.installmentNumber ?? 1) }),
|
|
858
|
+
" \xB7 ",
|
|
859
|
+
t("financial.payment.remaining", { amount: formatCurrency(remaining, currency) })
|
|
860
|
+
] })
|
|
861
|
+
] }),
|
|
862
|
+
/* @__PURE__ */ jsx(Button, { variant: "bare", size: "bare", onClick: handleClose, className: "p-1 text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
|
|
863
|
+
] }),
|
|
864
|
+
body,
|
|
865
|
+
footer
|
|
866
|
+
]
|
|
867
|
+
}
|
|
868
|
+
)
|
|
869
|
+
}
|
|
870
|
+
),
|
|
871
|
+
document.body
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
var ITEM_ICONS = {
|
|
875
|
+
product: Package,
|
|
876
|
+
service: Briefcase,
|
|
877
|
+
gift_card: Gift,
|
|
878
|
+
other: Receipt
|
|
879
|
+
};
|
|
880
|
+
var NONE = "__none__";
|
|
881
|
+
var TRIGGER_CLASS = `h-auto w-auto justify-start gap-1 rounded-none border-0 bg-transparent p-0 text-xs font-normal shadow-none ring-offset-0 focus:ring-0 focus:ring-offset-0 [&>svg]:h-3 [&>svg]:w-3 ${INLINE_LINK_TONE.dashed}`;
|
|
882
|
+
function InstallmentSourceFields({
|
|
883
|
+
direction,
|
|
884
|
+
value,
|
|
885
|
+
onChange,
|
|
886
|
+
disabled = false
|
|
887
|
+
}) {
|
|
888
|
+
const t = useTranslation();
|
|
889
|
+
const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
|
|
890
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
891
|
+
const can = usePermissionOptional();
|
|
892
|
+
const selectedType = paymentMethodTypes.find((tp) => tp.id === value.payMethodTypeId);
|
|
893
|
+
const selectedAccount = bankAccounts.find((a) => a.id === value.payAccountId);
|
|
894
|
+
const accounts = eligibleAccountsFor(bankAccounts, direction, selectedType);
|
|
895
|
+
function pickMethod(raw) {
|
|
896
|
+
const typeId = raw === NONE ? "" : raw;
|
|
897
|
+
const tipo = paymentMethodTypes.find((tp) => tp.id === typeId);
|
|
898
|
+
const elegiveis = eligibleAccountsFor(bankAccounts, direction, tipo);
|
|
899
|
+
const conta = elegiveis.length === 1 ? elegiveis[0].id : elegiveis.some((a) => a.id === value.payAccountId) ? value.payAccountId : void 0;
|
|
900
|
+
onChange({ payMethodTypeId: typeId || void 0, payAccountId: typeId ? conta : void 0 });
|
|
901
|
+
}
|
|
902
|
+
const tone = (filled) => filled ? "text-foreground" : "text-foreground border-b border-dashed border-foreground/60";
|
|
903
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-start gap-0.5", children: [
|
|
904
|
+
/* @__PURE__ */ jsxs(
|
|
905
|
+
Select,
|
|
906
|
+
{
|
|
907
|
+
value: value.payMethodTypeId ?? "",
|
|
908
|
+
disabled,
|
|
909
|
+
onValueChange: pickMethod,
|
|
910
|
+
children: [
|
|
911
|
+
/* @__PURE__ */ jsx(
|
|
912
|
+
SelectTrigger,
|
|
913
|
+
{
|
|
914
|
+
"aria-label": t("financial.invoiceForm.paidWith"),
|
|
915
|
+
className: `${TRIGGER_CLASS} ${tone(!!selectedType)}`,
|
|
916
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("financial.invoiceForm.paidWith") })
|
|
917
|
+
}
|
|
918
|
+
),
|
|
919
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
920
|
+
paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
921
|
+
/* @__PURE__ */ jsx(MethodIcon, { type: tp }),
|
|
922
|
+
tp.name
|
|
923
|
+
] }) }, tp.id)),
|
|
924
|
+
value.payMethodTypeId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t("financial.invoiceForm.clear") })
|
|
925
|
+
] })
|
|
926
|
+
]
|
|
927
|
+
}
|
|
928
|
+
),
|
|
929
|
+
/* @__PURE__ */ jsx(
|
|
930
|
+
AccountPicker,
|
|
931
|
+
{
|
|
932
|
+
accounts,
|
|
933
|
+
value: value.payAccountId,
|
|
934
|
+
disabled: disabled || !value.payMethodTypeId,
|
|
935
|
+
title: value.payMethodTypeId ? void 0 : t("financial.invoiceForm.selectMethodFirst"),
|
|
936
|
+
placeholder: t("financial.payment.account"),
|
|
937
|
+
ariaLabel: t("financial.invoiceForm.paidFrom"),
|
|
938
|
+
clearLabel: t("financial.invoiceForm.clear"),
|
|
939
|
+
className: `${INLINE_LINK_TONE.dashed} ${tone(!!selectedAccount)}`,
|
|
940
|
+
onChange: (id) => onChange({ ...value, payAccountId: id }),
|
|
941
|
+
onManage: can("financial", "manage") ? () => adminNavigateTo(ACCOUNTS_ROUTE) : void 0,
|
|
942
|
+
manageLabel: t("financial.invoiceForm.manageAccounts")
|
|
943
|
+
}
|
|
944
|
+
)
|
|
945
|
+
] });
|
|
946
|
+
}
|
|
947
|
+
var STATUS = {
|
|
948
|
+
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
|
|
949
|
+
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
|
|
950
|
+
partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial" },
|
|
951
|
+
paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid" },
|
|
952
|
+
overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue" },
|
|
953
|
+
draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
|
|
954
|
+
cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
|
|
955
|
+
};
|
|
956
|
+
function InstallmentStatusBadge({ status }) {
|
|
957
|
+
const t = useTranslation();
|
|
958
|
+
const s = STATUS[status ?? ""] ?? STATUS.pending;
|
|
959
|
+
const Icon = s.icon;
|
|
960
|
+
return /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${s.bg}`, children: [
|
|
961
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-2 w-2" }),
|
|
962
|
+
" ",
|
|
963
|
+
t(s.labelKey)
|
|
964
|
+
] });
|
|
965
|
+
}
|
|
966
|
+
function InlineAmount({ label, value, onCommit }) {
|
|
967
|
+
const [draft, setDraft] = React13__default.useState(null);
|
|
968
|
+
if (draft === null) {
|
|
969
|
+
return /* @__PURE__ */ jsx(
|
|
970
|
+
Button,
|
|
971
|
+
{
|
|
972
|
+
variant: "bare",
|
|
973
|
+
size: "bare",
|
|
974
|
+
type: "button",
|
|
975
|
+
onClick: (e) => {
|
|
976
|
+
e.stopPropagation();
|
|
977
|
+
setDraft(value);
|
|
978
|
+
},
|
|
979
|
+
className: `font-normal whitespace-normal shrink-0 text-sm font-medium tabular-nums hover:opacity-80 ${INLINE_LINK_TONE.dashed}`,
|
|
980
|
+
children: label
|
|
981
|
+
}
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
const commit = () => {
|
|
985
|
+
setDraft(null);
|
|
986
|
+
if (Math.round(draft * 100) !== Math.round(value * 100)) onCommit(draft);
|
|
987
|
+
};
|
|
988
|
+
return /* @__PURE__ */ jsx(
|
|
989
|
+
"div",
|
|
990
|
+
{
|
|
991
|
+
onClick: (e) => e.stopPropagation(),
|
|
992
|
+
onBlur: commit,
|
|
993
|
+
onKeyDown: (e) => {
|
|
994
|
+
if (e.key === "Enter") commit();
|
|
995
|
+
if (e.key === "Escape") setDraft(null);
|
|
996
|
+
},
|
|
997
|
+
children: /* @__PURE__ */ jsx(CurrencyInput, { autoFocus: true, value: draft, onChange: setDraft, className: "w-36" })
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
function InstallmentRow({
|
|
1002
|
+
number,
|
|
1003
|
+
amount,
|
|
1004
|
+
amountValue,
|
|
1005
|
+
onAmountChange,
|
|
1006
|
+
status,
|
|
1007
|
+
dueDate,
|
|
1008
|
+
onDueDateChange,
|
|
1009
|
+
dueExtra,
|
|
1010
|
+
source,
|
|
1011
|
+
actions,
|
|
1012
|
+
onClick
|
|
1013
|
+
}) {
|
|
1014
|
+
const t = useTranslation();
|
|
1015
|
+
const stop = (e) => e.stopPropagation();
|
|
1016
|
+
return /* @__PURE__ */ jsxs(
|
|
1017
|
+
"div",
|
|
1018
|
+
{
|
|
1019
|
+
className: `flex flex-wrap items-center gap-3 px-4 py-3 ${onClick ? "cursor-pointer transition-colors hover:bg-muted/20" : ""}`,
|
|
1020
|
+
onClick,
|
|
1021
|
+
children: [
|
|
1022
|
+
/* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
|
|
1023
|
+
"#",
|
|
1024
|
+
number
|
|
1025
|
+
] }),
|
|
1026
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-start gap-0.5", children: [
|
|
1027
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1028
|
+
onAmountChange && amountValue !== void 0 ? /* @__PURE__ */ jsx(InlineAmount, { label: amount, value: amountValue, onCommit: onAmountChange }) : /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: amount }),
|
|
1029
|
+
status && /* @__PURE__ */ jsx(InstallmentStatusBadge, { status })
|
|
1030
|
+
] }),
|
|
1031
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground", onClick: stop, children: [
|
|
1032
|
+
t("financial.payment.due"),
|
|
1033
|
+
onDueDateChange ? /* @__PURE__ */ jsx(DatePicker, { value: dueDate, variant: "inline", format: "numeric", onChange: onDueDateChange }) : dueDate,
|
|
1034
|
+
dueExtra
|
|
1035
|
+
] })
|
|
1036
|
+
] }),
|
|
1037
|
+
/* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", onClick: stop, children: source }),
|
|
1038
|
+
(actions || onClick) && // Min width fits attach + "Pagar": rows where Pay is absent (unsaved, paid) keep the source column aligned.
|
|
1039
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-32 shrink-0 items-center justify-end gap-1.5", onClick: stop, children: [
|
|
1040
|
+
actions,
|
|
1041
|
+
onClick && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
1042
|
+
] })
|
|
1043
|
+
]
|
|
1044
|
+
}
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
var BTN = "inline-flex shrink-0 items-center gap-1 px-3 py-1.5 text-xs font-medium transition-colors active:shadow-button-inset disabled:pointer-events-none disabled:opacity-40";
|
|
1048
|
+
function InstallmentPayButton({ onClick, disabled, title, children, variant = "primary" }) {
|
|
1049
|
+
return /* @__PURE__ */ jsxs(
|
|
1050
|
+
Button,
|
|
1051
|
+
{
|
|
1052
|
+
variant: "bare",
|
|
1053
|
+
size: "bare",
|
|
1054
|
+
type: "button",
|
|
1055
|
+
onClick,
|
|
1056
|
+
disabled,
|
|
1057
|
+
title,
|
|
1058
|
+
className: `font-normal ${variant === "primary" ? `${BTN} rounded-button border border-primary bg-primary text-primary-foreground shadow-button-primary hover:bg-primary/90` : `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`}`,
|
|
1059
|
+
children: [
|
|
1060
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
1061
|
+
children
|
|
1062
|
+
]
|
|
1063
|
+
}
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
function InstallmentActionButton({ onClick, icon: Icon, children }) {
|
|
1067
|
+
return /* @__PURE__ */ jsxs(Button, { variant: "bare", size: "bare", type: "button", onClick, className: `font-normal ${`${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`}`, children: [
|
|
1068
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
|
|
1069
|
+
children
|
|
1070
|
+
] });
|
|
1071
|
+
}
|
|
1072
|
+
function InstallmentReceiptButton({ onClick, title, attached, pending, count }) {
|
|
1073
|
+
return /* @__PURE__ */ jsxs(
|
|
1074
|
+
Button,
|
|
1075
|
+
{
|
|
1076
|
+
variant: "bare",
|
|
1077
|
+
size: "bare",
|
|
1078
|
+
type: "button",
|
|
1079
|
+
onClick,
|
|
1080
|
+
title,
|
|
1081
|
+
"aria-label": title,
|
|
1082
|
+
className: `relative inline-flex shrink-0 items-center gap-1 rounded-lg border bg-card px-2.5 py-1.5 text-xs font-medium shadow-button transition-colors hover:bg-muted active:shadow-button-inset ${attached ? "text-success" : pending ? "text-primary" : ""}`,
|
|
1083
|
+
children: [
|
|
1084
|
+
/* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3" }),
|
|
1085
|
+
!!count && /* @__PURE__ */ jsx("span", { className: "absolute -right-1.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-primary-foreground tabular-nums", children: count })
|
|
1086
|
+
]
|
|
1087
|
+
}
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
var dataBR = (iso) => {
|
|
1091
|
+
const [ano, mes, dia] = iso.slice(0, 10).split("-");
|
|
1092
|
+
return ano && mes && dia ? `${dia}/${mes}/${ano}` : iso;
|
|
1093
|
+
};
|
|
1094
|
+
function Campo({ icon: Icon, label, children, tone }) {
|
|
1095
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1096
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1097
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
1098
|
+
label
|
|
1099
|
+
] }),
|
|
1100
|
+
/* @__PURE__ */ jsx("span", { className: `min-w-0 truncate text-right text-sm ${tone === "money" ? "text-base font-bold text-success" : "font-medium"}`, children })
|
|
1101
|
+
] });
|
|
1102
|
+
}
|
|
1103
|
+
var ehEstorno = (m) => !!m.metadata?.reversal_of;
|
|
1104
|
+
function PaymentReceiptModal({
|
|
1105
|
+
payments,
|
|
1106
|
+
howCtx,
|
|
1107
|
+
counterpartyName,
|
|
1108
|
+
isReceivable,
|
|
1109
|
+
onReverse,
|
|
1110
|
+
onClose
|
|
1111
|
+
}) {
|
|
1112
|
+
const t = useTranslation();
|
|
1113
|
+
const { currency } = useFinancialConfig();
|
|
1114
|
+
const fmt = (n) => formatCurrency(n, currency);
|
|
1115
|
+
const ordenados = [...payments].sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
|
|
1116
|
+
const estornaveis = new Set(reversiblePayments(ordenados).map((m) => m.id));
|
|
1117
|
+
const vivos = ordenados.filter((m) => estornaveis.has(m.id));
|
|
1118
|
+
const desfeitos = ordenados.filter((m) => !ehEstorno(m) && !estornaveis.has(m.id)).map((m) => ({
|
|
1119
|
+
payment: m,
|
|
1120
|
+
reversal: ordenados.find((r) => r.metadata?.reversal_of === m.id)
|
|
1121
|
+
}));
|
|
1122
|
+
const [verDesfeitos, setVerDesfeitos] = React13__default.useState(false);
|
|
1123
|
+
const total = vivos.reduce((soma, m) => soma + (m.paidAmount || m.amount), 0);
|
|
1124
|
+
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
1125
|
+
if (!o) onClose();
|
|
1126
|
+
}, children: /* @__PURE__ */ jsxs(ModalContent, { size: "sm", children: [
|
|
1127
|
+
/* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs(ModalTitle, { className: "flex items-center gap-2", children: [
|
|
1128
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-5 w-5 text-success", "aria-hidden": true }),
|
|
1129
|
+
t("financial.paymentReceipt.title")
|
|
1130
|
+
] }) }),
|
|
1131
|
+
/* @__PURE__ */ jsxs(ModalBody, { className: "space-y-2", children: [
|
|
1132
|
+
vivos.length > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-card bg-muted/60 px-4 py-2", children: [
|
|
1133
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: t("financial.paymentReceipt.count", { count: vivos.length }) }),
|
|
1134
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-success", children: fmt(total) })
|
|
1135
|
+
] }),
|
|
1136
|
+
vivos.map((payment, indice) => {
|
|
1137
|
+
const how = describePayment(payment, howCtx);
|
|
1138
|
+
const estorno = ehEstorno(payment);
|
|
1139
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5 rounded-card bg-muted/40 px-4 py-3", children: [
|
|
1140
|
+
vivos.length > 1 && /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: estorno ? t("financial.paymentReceipt.reversalNumber", { number: indice + 1 }) : t("financial.paymentReceipt.paymentNumber", { number: indice + 1 }) }),
|
|
1141
|
+
payment.paymentDate && /* @__PURE__ */ jsx(Campo, { icon: Calendar, label: t("financial.paymentReceipt.date"), children: dataBR(payment.paymentDate) }),
|
|
1142
|
+
/* @__PURE__ */ jsxs(Campo, { icon: DollarSign, label: t("financial.paymentReceipt.amount"), tone: "money", children: [
|
|
1143
|
+
estorno ? "\u2212 " : "",
|
|
1144
|
+
fmt(payment.paidAmount || payment.amount)
|
|
1145
|
+
] }),
|
|
1146
|
+
/* @__PURE__ */ jsx(Campo, { icon: FileText, label: isReceivable ? t("financial.paymentReceipt.methodIn") : t("financial.paymentReceipt.methodOut"), children: /* @__PURE__ */ jsx("span", { className: "rounded border px-1.5 py-0.5 text-xs", children: how.method || t("financial.paymentReceipt.methodNone") }) }),
|
|
1147
|
+
how.account && /* @__PURE__ */ jsx(Campo, { icon: Building2, label: t("financial.paymentReceipt.account"), children: how.account.name }),
|
|
1148
|
+
payment.cardBrand && /* @__PURE__ */ jsxs(Campo, { icon: CreditCard, label: t("financial.paymentReceipt.cardBrand"), children: [
|
|
1149
|
+
payment.cardBrand,
|
|
1150
|
+
payment.cardInstallments && payment.cardInstallments > 1 ? ` \xB7 ${payment.cardInstallments}x` : ""
|
|
1151
|
+
] }),
|
|
1152
|
+
(payment.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsx(Campo, { icon: Banknote, label: t("financial.paymentReceipt.fee"), children: /* @__PURE__ */ jsx("span", { className: "text-warning", children: fmt(payment.feeAmount ?? 0) }) }),
|
|
1153
|
+
counterpartyName && /* @__PURE__ */ jsx(Campo, { icon: User, label: isReceivable ? t("financial.paymentReceipt.receivedFrom") : t("financial.paymentReceipt.payer"), children: counterpartyName }),
|
|
1154
|
+
payment.cashSessionId && /* @__PURE__ */ jsx(Campo, { icon: Wallet, label: t("financial.paymentReceipt.cashSession"), children: t("financial.paymentReceipt.cashSessionYes") }),
|
|
1155
|
+
payment.notes && /* @__PURE__ */ jsxs("div", { className: "border-t border-border/60 pt-2", children: [
|
|
1156
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
1157
|
+
t("financial.paymentReceipt.notes"),
|
|
1158
|
+
": "
|
|
1159
|
+
] }),
|
|
1160
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs", children: payment.notes })
|
|
1161
|
+
] }),
|
|
1162
|
+
onReverse && estornaveis.has(payment.id) && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
|
|
1163
|
+
/* @__PURE__ */ jsx(Hint, { label: t("financial.paymentReceipt.unlinkUnavailable"), children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "w-full", disabled: true, children: [
|
|
1164
|
+
/* @__PURE__ */ jsx(Unlink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
1165
|
+
t("financial.paymentReceipt.unlink")
|
|
1166
|
+
] }) }),
|
|
1167
|
+
/* @__PURE__ */ jsxs(
|
|
1168
|
+
Button,
|
|
1169
|
+
{
|
|
1170
|
+
variant: "outline",
|
|
1171
|
+
size: "sm",
|
|
1172
|
+
className: "w-full border-destructive/40 text-destructive hover:border-destructive hover:bg-destructive/10",
|
|
1173
|
+
onClick: () => onReverse(payment),
|
|
1174
|
+
children: [
|
|
1175
|
+
/* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
1176
|
+
t("financial.paymentReceipt.reverse")
|
|
1177
|
+
]
|
|
1178
|
+
}
|
|
1179
|
+
)
|
|
1180
|
+
] })
|
|
1181
|
+
] }, payment.id);
|
|
1182
|
+
}),
|
|
1183
|
+
vivos.length === 0 && /* @__PURE__ */ jsx("p", { className: "py-8 text-center text-sm text-muted-foreground", children: t("financial.paymentReceipt.empty") }),
|
|
1184
|
+
desfeitos.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-card border border-dashed px-4 py-2", children: [
|
|
1185
|
+
/* @__PURE__ */ jsxs(
|
|
1186
|
+
Button,
|
|
1187
|
+
{
|
|
1188
|
+
variant: "bare",
|
|
1189
|
+
size: "bare",
|
|
1190
|
+
type: "button",
|
|
1191
|
+
className: "font-normal whitespace-normal flex w-full items-center justify-between text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
1192
|
+
onClick: () => setVerDesfeitos((v) => !v),
|
|
1193
|
+
children: [
|
|
1194
|
+
/* @__PURE__ */ jsx("span", { children: t("financial.paymentReceipt.reversedCount", { count: desfeitos.length }) }),
|
|
1195
|
+
/* @__PURE__ */ jsx("span", { children: verDesfeitos ? t("financial.paymentReceipt.hide") : t("financial.paymentReceipt.show") })
|
|
1196
|
+
]
|
|
1197
|
+
}
|
|
1198
|
+
),
|
|
1199
|
+
verDesfeitos && /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-2", children: desfeitos.map(({ payment, reversal }) => {
|
|
1200
|
+
const how = describePayment(payment, howCtx);
|
|
1201
|
+
const reason = reversal?.metadata?.reason;
|
|
1202
|
+
return /* @__PURE__ */ jsxs("li", { className: "text-xs text-muted-foreground", children: [
|
|
1203
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1204
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
1205
|
+
payment.paymentDate ? dataBR(payment.paymentDate) : "",
|
|
1206
|
+
how.account ? ` \xB7 ${how.account.name}` : ""
|
|
1207
|
+
] }),
|
|
1208
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 line-through", children: fmt(payment.paidAmount || payment.amount) })
|
|
1209
|
+
] }),
|
|
1210
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
1211
|
+
reversal?.paymentDate ? t("financial.paymentReceipt.reversedOn", { date: dataBR(reversal.paymentDate) }) : t("financial.paymentReceipt.reversed"),
|
|
1212
|
+
reason ? ` \u2014 ${reason}` : ""
|
|
1213
|
+
] })
|
|
1214
|
+
] }, payment.id);
|
|
1215
|
+
}) })
|
|
1216
|
+
] })
|
|
1217
|
+
] }),
|
|
1218
|
+
/* @__PURE__ */ jsx(ModalFooter, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onClose, children: t("common.close") }) })
|
|
1219
|
+
] }) });
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/lib/installment-amounts.ts
|
|
1223
|
+
var cents = (v) => Math.round(v * 100);
|
|
1224
|
+
function rebalanceInstallments(input) {
|
|
1225
|
+
const { amounts, index, total } = input;
|
|
1226
|
+
const locked = input.locked ?? [];
|
|
1227
|
+
const pinned = input.pinned ?? [];
|
|
1228
|
+
if (locked[index]) return null;
|
|
1229
|
+
const open = amounts.map((_, i) => i).filter((i) => i !== index && !locked[i]);
|
|
1230
|
+
if (open.length === 0) return null;
|
|
1231
|
+
const free = open.filter((i) => !pinned[i]);
|
|
1232
|
+
const absorbers = free.length > 0 ? free : open;
|
|
1233
|
+
const fixed = amounts.reduce((sum, a, i) => i === index || absorbers.includes(i) ? sum : sum + cents(a), 0);
|
|
1234
|
+
const room = cents(total) - fixed;
|
|
1235
|
+
if (room < 0) return null;
|
|
1236
|
+
const mine = Math.min(Math.max(cents(input.value), 0), room);
|
|
1237
|
+
const rest = room - mine;
|
|
1238
|
+
const each = Math.floor(rest / absorbers.length);
|
|
1239
|
+
const next = amounts.map(cents);
|
|
1240
|
+
next[index] = mine;
|
|
1241
|
+
absorbers.forEach((i, n) => {
|
|
1242
|
+
next[i] = each + (n === 0 ? rest - each * absorbers.length : 0);
|
|
1243
|
+
});
|
|
1244
|
+
return next.map((c) => c / 100);
|
|
1245
|
+
}
|
|
1246
|
+
var ACCEPT = "image/*,application/pdf";
|
|
1247
|
+
var MAX_BYTES = 15 * 1024 * 1024;
|
|
1248
|
+
function size(bytes) {
|
|
1249
|
+
if (!bytes) return "";
|
|
1250
|
+
return bytes < 1024 * 1024 ? `${Math.max(1, Math.round(bytes / 1024))} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
1251
|
+
}
|
|
1252
|
+
function InvoiceAttachmentsModal({ invoiceId, onClose, onChanged }) {
|
|
1253
|
+
const provider = useFinancialProvider();
|
|
1254
|
+
const inputRef = useRef(null);
|
|
1255
|
+
const [items, setItems] = useState(null);
|
|
1256
|
+
const [uploading, setUploading] = useState(0);
|
|
1257
|
+
const [over, setOver] = useState(false);
|
|
1258
|
+
const [preview, setPreview] = useState(null);
|
|
1259
|
+
const [added, setAdded] = useState(0);
|
|
1260
|
+
async function load() {
|
|
1261
|
+
try {
|
|
1262
|
+
setItems(await (provider.getInvoiceAttachments?.(invoiceId) ?? Promise.resolve([])));
|
|
1263
|
+
} catch (e) {
|
|
1264
|
+
setItems([]);
|
|
1265
|
+
toast.error(e?.message ?? String(e));
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
useEffect(() => {
|
|
1269
|
+
void load();
|
|
1270
|
+
}, [invoiceId]);
|
|
1271
|
+
async function upload(files) {
|
|
1272
|
+
const list = Array.from(files);
|
|
1273
|
+
if (!list.length || !provider.addInvoiceAttachment) return;
|
|
1274
|
+
const invalid = list.filter((f) => !(f.type.startsWith("image/") || f.type === "application/pdf") || f.size > MAX_BYTES);
|
|
1275
|
+
if (invalid.length) toast.error("Envie imagem ou PDF de at\xE9 15 MB.");
|
|
1276
|
+
const ok = list.filter((f) => !invalid.includes(f));
|
|
1277
|
+
if (!ok.length) return;
|
|
1278
|
+
setUploading((n) => n + ok.length);
|
|
1279
|
+
for (const file of ok) {
|
|
1280
|
+
try {
|
|
1281
|
+
await provider.addInvoiceAttachment(invoiceId, file);
|
|
1282
|
+
setAdded((n) => n + 1);
|
|
1283
|
+
} catch (e) {
|
|
1284
|
+
toast.error(`${file.name}: ${e?.message ?? String(e)}`);
|
|
1285
|
+
} finally {
|
|
1286
|
+
setUploading((n) => n - 1);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
await load();
|
|
1290
|
+
onChanged?.();
|
|
1291
|
+
}
|
|
1292
|
+
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
1293
|
+
if (!o) onClose();
|
|
1294
|
+
}, children: /* @__PURE__ */ jsx(ModalContent, { size: preview !== null ? "3xl" : "sm", children: preview !== null && items?.[preview] ? /* @__PURE__ */ jsx(
|
|
1295
|
+
AttachmentPreview,
|
|
1296
|
+
{
|
|
1297
|
+
items,
|
|
1298
|
+
index: preview,
|
|
1299
|
+
onIndex: setPreview,
|
|
1300
|
+
onBack: () => setPreview(null)
|
|
1301
|
+
}
|
|
1302
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1303
|
+
/* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs(ModalTitle, { className: "flex items-center gap-2", children: [
|
|
1304
|
+
/* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }),
|
|
1305
|
+
"Anexos da conta"
|
|
1306
|
+
] }) }),
|
|
1307
|
+
/* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
|
|
1308
|
+
items === null ? /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", "aria-busy": "true", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-2.5", children: [
|
|
1309
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 shrink-0 animate-pulse rounded bg-muted" }),
|
|
1310
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
|
|
1311
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-2/3 animate-pulse rounded bg-muted" }),
|
|
1312
|
+
/* @__PURE__ */ jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-muted" })
|
|
1313
|
+
] })
|
|
1314
|
+
] }, i)) }) : items.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-card bg-muted/40 px-4 py-5 text-center", children: [
|
|
1315
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhum anexo ainda" }),
|
|
1316
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Guarde aqui o boleto, a nota ou o comprovante de pagamento \u2014 fica tudo junto da conta." })
|
|
1317
|
+
] }) : /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", children: items.map((a, i) => {
|
|
1318
|
+
const Icon = (a.mimeType ?? "").startsWith("image/") ? Image : FileText;
|
|
1319
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
1320
|
+
Button,
|
|
1321
|
+
{
|
|
1322
|
+
variant: "bare",
|
|
1323
|
+
size: "bare",
|
|
1324
|
+
type: "button",
|
|
1325
|
+
onClick: () => setPreview(i),
|
|
1326
|
+
className: "font-normal whitespace-normal justify-start flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-muted/40",
|
|
1327
|
+
children: [
|
|
1328
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
1329
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1330
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: a.fileName }),
|
|
1331
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: a.pending ? "Arquivo ainda n\xE3o dispon\xEDvel" : [size(a.fileSize), a.createdAt?.slice(0, 10).split("-").reverse().join("/")].filter(Boolean).join(" \xB7 ") })
|
|
1332
|
+
] }),
|
|
1333
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
1334
|
+
]
|
|
1335
|
+
}
|
|
1336
|
+
) }, a.id);
|
|
1337
|
+
}) }),
|
|
1338
|
+
provider.addInvoiceAttachment && /* @__PURE__ */ jsxs(
|
|
1339
|
+
Button,
|
|
1340
|
+
{
|
|
1341
|
+
variant: "bare",
|
|
1342
|
+
size: "bare",
|
|
1343
|
+
type: "button",
|
|
1344
|
+
onClick: () => inputRef.current?.click(),
|
|
1345
|
+
onDragOver: (e) => {
|
|
1346
|
+
e.preventDefault();
|
|
1347
|
+
setOver(true);
|
|
1348
|
+
},
|
|
1349
|
+
onDragLeave: () => setOver(false),
|
|
1350
|
+
onDrop: (e) => {
|
|
1351
|
+
e.preventDefault();
|
|
1352
|
+
setOver(false);
|
|
1353
|
+
void upload(e.dataTransfer.files);
|
|
1354
|
+
},
|
|
1355
|
+
disabled: uploading > 0,
|
|
1356
|
+
className: `font-normal whitespace-normal justify-start ${`flex w-full flex-col items-center gap-1 rounded-card border-2 border-dashed px-4 py-5 text-center transition-colors ${over ? "border-primary bg-primary/5" : "border-border hover:bg-muted/40"}`}`,
|
|
1357
|
+
children: [
|
|
1358
|
+
uploading > 0 ? /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsx(UploadCloud, { className: "h-5 w-5 text-muted-foreground", "aria-hidden": true }),
|
|
1359
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: uploading > 0 ? "Enviando\u2026" : "Solte arquivos aqui ou clique para escolher" }),
|
|
1360
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Imagem ou PDF, at\xE9 15 MB" })
|
|
1361
|
+
]
|
|
1362
|
+
}
|
|
1363
|
+
),
|
|
1364
|
+
/* @__PURE__ */ jsx(
|
|
1365
|
+
"input",
|
|
1366
|
+
{
|
|
1367
|
+
ref: inputRef,
|
|
1368
|
+
type: "file",
|
|
1369
|
+
accept: ACCEPT,
|
|
1370
|
+
multiple: true,
|
|
1371
|
+
className: "hidden",
|
|
1372
|
+
onChange: (e) => {
|
|
1373
|
+
const f = e.target.files;
|
|
1374
|
+
if (f) void upload(f);
|
|
1375
|
+
e.target.value = "";
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
)
|
|
1379
|
+
] }),
|
|
1380
|
+
/* @__PURE__ */ jsx(ModalFooter, { children: /* @__PURE__ */ jsx(Button, { onClick: onClose, disabled: uploading > 0, variant: added > 0 ? "default" : "outline", children: uploading > 0 ? "Enviando\u2026" : added > 0 ? "Concluir" : "Fechar" }) })
|
|
1381
|
+
] }) }) });
|
|
1382
|
+
}
|
|
1383
|
+
function AttachmentPreview({ items, index, onIndex, onBack }) {
|
|
1384
|
+
const doc = items[index];
|
|
1385
|
+
const isImage = (doc.mimeType ?? "").startsWith("image/");
|
|
1386
|
+
const isPdf = doc.mimeType === "application/pdf";
|
|
1387
|
+
const go = (d) => onIndex((index + d + items.length) % items.length);
|
|
1388
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1389
|
+
/* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pr-8", children: [
|
|
1390
|
+
/* @__PURE__ */ jsxs(
|
|
1391
|
+
Button,
|
|
1392
|
+
{
|
|
1393
|
+
variant: "bare",
|
|
1394
|
+
size: "bare",
|
|
1395
|
+
type: "button",
|
|
1396
|
+
onClick: onBack,
|
|
1397
|
+
className: "font-normal inline-flex shrink-0 items-center gap-1 rounded-md px-1.5 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
1398
|
+
children: [
|
|
1399
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
|
|
1400
|
+
" Voltar"
|
|
1401
|
+
]
|
|
1402
|
+
}
|
|
1403
|
+
),
|
|
1404
|
+
/* @__PURE__ */ jsx(ModalTitle, { className: "min-w-0 flex-1 truncate text-sm font-medium", title: doc.fileName, children: doc.fileName }),
|
|
1405
|
+
items.length > 1 && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
|
|
1406
|
+
/* @__PURE__ */ jsx(Button, { variant: "bare", size: "bare", type: "button", onClick: () => go(-1), "aria-label": "Anterior", className: "font-normal rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }) }),
|
|
1407
|
+
index + 1,
|
|
1408
|
+
" de ",
|
|
1409
|
+
items.length,
|
|
1410
|
+
/* @__PURE__ */ jsx(Button, { variant: "bare", size: "bare", type: "button", onClick: () => go(1), "aria-label": "Pr\xF3ximo", className: "font-normal rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
|
|
1411
|
+
] }),
|
|
1412
|
+
doc.url && /* @__PURE__ */ jsx(
|
|
1413
|
+
"a",
|
|
1414
|
+
{
|
|
1415
|
+
href: doc.url,
|
|
1416
|
+
target: "_blank",
|
|
1417
|
+
rel: "noreferrer",
|
|
1418
|
+
"aria-label": "Abrir em nova aba",
|
|
1419
|
+
className: "shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
1420
|
+
children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" })
|
|
1421
|
+
}
|
|
1422
|
+
)
|
|
1423
|
+
] }) }),
|
|
1424
|
+
/* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx("div", { className: "flex h-[65vh] items-center justify-center overflow-hidden rounded-card bg-muted/40", children: doc.pending || !doc.url ? /* @__PURE__ */ jsxs("p", { className: "max-w-sm p-8 text-center text-sm text-muted-foreground", children: [
|
|
1425
|
+
"O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por",
|
|
1426
|
+
" ",
|
|
1427
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
|
|
1428
|
+
"."
|
|
1429
|
+
] }) : isImage ? /* @__PURE__ */ jsx("img", { src: doc.url, alt: doc.fileName, className: "max-h-full max-w-full object-contain" }) : isPdf ? /* @__PURE__ */ jsx("iframe", { src: doc.url, title: doc.fileName, className: "h-full w-full bg-white" }) : /* @__PURE__ */ jsx("p", { className: "p-8 text-center text-sm text-muted-foreground", children: "Este tipo de arquivo n\xE3o tem pr\xE9-visualiza\xE7\xE3o. Use o bot\xE3o de abrir." }) }) })
|
|
1430
|
+
] });
|
|
1431
|
+
}
|
|
1432
|
+
var DESTINATIONS = [
|
|
1433
|
+
{ value: "devolver", label: "Devolver ao fornecedor" },
|
|
1434
|
+
{ value: "perda", label: "Baixar como perda" },
|
|
1435
|
+
{ value: "manter", label: "Manter no estoque" }
|
|
1436
|
+
];
|
|
1437
|
+
function day(iso) {
|
|
1438
|
+
const [y, m, d] = iso.slice(0, 10).split("-");
|
|
1439
|
+
return `${d}/${m}/${y}`;
|
|
1440
|
+
}
|
|
1441
|
+
function CancelReasonFields({ reasons, reasonId, onReason, detail, onDetail }) {
|
|
1442
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1443
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: "Motivo do cancelamento" }),
|
|
1444
|
+
reasons === null ? /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" }) : /* @__PURE__ */ jsx(
|
|
1445
|
+
ChoiceSelect,
|
|
1446
|
+
{
|
|
1447
|
+
"aria-label": "Motivo do cancelamento",
|
|
1448
|
+
options: reasons.map((r) => ({ value: r.id, label: r.name })),
|
|
1449
|
+
value: reasonId,
|
|
1450
|
+
onChange: onReason,
|
|
1451
|
+
placeholder: "Selecione um motivo..."
|
|
1452
|
+
}
|
|
1453
|
+
),
|
|
1454
|
+
/* @__PURE__ */ jsx(
|
|
1455
|
+
"textarea",
|
|
1456
|
+
{
|
|
1457
|
+
value: detail,
|
|
1458
|
+
onChange: (e) => onDetail(e.target.value),
|
|
1459
|
+
placeholder: "Detalhe (opcional)...",
|
|
1460
|
+
className: "min-h-20 w-full resize-y rounded-input border border-input bg-card px-3 py-2 text-sm"
|
|
1461
|
+
}
|
|
1462
|
+
)
|
|
1463
|
+
] });
|
|
1464
|
+
}
|
|
1465
|
+
function CancelInvoiceDialog({ invoiceId, counterpartyName, totalAmount, items, preselectItemId, onClose, onCancelled }) {
|
|
1466
|
+
const { currency } = useFinancialConfig();
|
|
1467
|
+
const provider = useFinancialProvider();
|
|
1468
|
+
const [selected, setSelected] = useState(
|
|
1469
|
+
() => new Set(preselectItemId ? [preselectItemId] : items.map((i) => i.id))
|
|
1470
|
+
);
|
|
1471
|
+
const [reasons, setReasons] = useState(null);
|
|
1472
|
+
const [reasonId, setReasonId] = useState();
|
|
1473
|
+
const [detail, setDetail] = useState("");
|
|
1474
|
+
const [entries, setEntries] = useState(null);
|
|
1475
|
+
const [destinations, setDestinations] = useState({});
|
|
1476
|
+
const [busy, setBusy] = useState(false);
|
|
1477
|
+
const requestKey = useRef(createIdempotencyKey("financial:invoice-cancel-items"));
|
|
1478
|
+
useEffect(() => {
|
|
1479
|
+
let alive = true;
|
|
1480
|
+
Promise.all([
|
|
1481
|
+
provider.listCancellationReasons?.() ?? Promise.resolve([]),
|
|
1482
|
+
provider.getInvoiceStockEntries?.(invoiceId) ?? Promise.resolve([])
|
|
1483
|
+
]).then(([r, e]) => {
|
|
1484
|
+
if (alive) {
|
|
1485
|
+
setReasons(r);
|
|
1486
|
+
setEntries(e);
|
|
1487
|
+
}
|
|
1488
|
+
}).catch((err) => {
|
|
1489
|
+
if (alive) {
|
|
1490
|
+
setReasons([]);
|
|
1491
|
+
setEntries([]);
|
|
1492
|
+
toast.error("N\xE3o foi poss\xEDvel carregar o cancelamento", { description: err?.message });
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
return () => {
|
|
1496
|
+
alive = false;
|
|
1497
|
+
};
|
|
1498
|
+
}, [provider, invoiceId]);
|
|
1499
|
+
const whole = items.length > 0 && items.every((i) => selected.has(i.id));
|
|
1500
|
+
const amount = whole ? totalAmount : items.filter((i) => selected.has(i.id)).reduce((s, i) => s + i.totalAmount, 0);
|
|
1501
|
+
const stocked = useMemo(() => {
|
|
1502
|
+
const byItem = /* @__PURE__ */ new Map();
|
|
1503
|
+
for (const e of entries ?? []) {
|
|
1504
|
+
const cur = byItem.get(e.itemId);
|
|
1505
|
+
byItem.set(e.itemId, { entered: (cur?.entered ?? 0) + e.entered, enteredOn: cur?.enteredOn ?? e.enteredOn });
|
|
1506
|
+
}
|
|
1507
|
+
return byItem;
|
|
1508
|
+
}, [entries]);
|
|
1509
|
+
const stockedSelected = items.filter((i) => selected.has(i.id) && stocked.has(i.id));
|
|
1510
|
+
const missing = selected.size === 0 ? "Selecione pelo menos um item" : stockedSelected.find((i) => !destinations[i.id]?.destination) ? "Diga o que acontece com a mercadoria" : !reasonId ? "Escolha o motivo do cancelamento" : null;
|
|
1511
|
+
function toggle(id, on) {
|
|
1512
|
+
setSelected((prev) => {
|
|
1513
|
+
const next = new Set(prev);
|
|
1514
|
+
if (on) next.add(id);
|
|
1515
|
+
else next.delete(id);
|
|
1516
|
+
return next;
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
async function submit() {
|
|
1520
|
+
if (missing || !reasonId || !provider.cancelInvoiceItems) return;
|
|
1521
|
+
setBusy(true);
|
|
1522
|
+
try {
|
|
1523
|
+
const result = await provider.cancelInvoiceItems({
|
|
1524
|
+
invoiceId,
|
|
1525
|
+
itemIds: whole ? [] : [...selected],
|
|
1526
|
+
reasonId,
|
|
1527
|
+
detail,
|
|
1528
|
+
stock: stockedSelected.map((i) => ({
|
|
1529
|
+
itemId: i.id,
|
|
1530
|
+
destination: destinations[i.id].destination,
|
|
1531
|
+
note: destinations[i.id]?.note
|
|
1532
|
+
})),
|
|
1533
|
+
idempotencyKey: requestKey.current
|
|
1534
|
+
});
|
|
1535
|
+
if (result.creditAmount > 0) {
|
|
1536
|
+
toast.success(result.whole ? "Conta cancelada" : "Itens cancelados", {
|
|
1537
|
+
description: `${formatCurrency(result.creditAmount, currency)} em pagamentos ficaram como cr\xE9dito${counterpartyName ? ` de ${counterpartyName}` : ""}.`
|
|
1538
|
+
});
|
|
1539
|
+
} else {
|
|
1540
|
+
toast.success(result.whole ? "Conta cancelada" : "Itens cancelados");
|
|
1541
|
+
}
|
|
1542
|
+
onCancelled();
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
toast.error("N\xE3o foi poss\xEDvel cancelar", { description: err?.message });
|
|
1545
|
+
requestKey.current = createIdempotencyKey("financial:invoice-cancel-items");
|
|
1546
|
+
} finally {
|
|
1547
|
+
setBusy(false);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
const loading = reasons === null || entries === null;
|
|
1551
|
+
return /* @__PURE__ */ jsxs(
|
|
1552
|
+
ModalTask,
|
|
1553
|
+
{
|
|
1554
|
+
open: true,
|
|
1555
|
+
title: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 text-destructive", children: [
|
|
1556
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-4 w-4" }),
|
|
1557
|
+
" Cancelar"
|
|
1558
|
+
] }),
|
|
1559
|
+
width: "max-w-lg",
|
|
1560
|
+
onClose,
|
|
1561
|
+
children: [
|
|
1562
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-container min-h-0 flex-1 space-y-4 overflow-y-auto px-5 py-4", children: [
|
|
1563
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
1564
|
+
"Selecione o que cancelar. Pagamentos j\xE1 alocados ao que for cancelado ser\xE3o liberados como cr\xE9dito",
|
|
1565
|
+
counterpartyName ? ` de ${counterpartyName}` : "",
|
|
1566
|
+
"."
|
|
1567
|
+
] }),
|
|
1568
|
+
items.length > 1 && /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-3 rounded-lg border border-destructive/30 bg-destructive-soft px-3 py-2.5 text-sm font-medium text-destructive", children: [
|
|
1569
|
+
/* @__PURE__ */ jsx(
|
|
1570
|
+
Checkbox,
|
|
1571
|
+
{
|
|
1572
|
+
checked: whole,
|
|
1573
|
+
indeterminate: !whole && selected.size > 0,
|
|
1574
|
+
color: "destructive",
|
|
1575
|
+
onChange: (on) => setSelected(on ? new Set(items.map((i) => i.id)) : /* @__PURE__ */ new Set())
|
|
1576
|
+
}
|
|
1577
|
+
),
|
|
1578
|
+
"Cancelar a conta inteira"
|
|
1579
|
+
] }),
|
|
1580
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: items.map((it) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2.5", children: [
|
|
1581
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: selected.has(it.id), onChange: (on) => toggle(it.id, on) }),
|
|
1582
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm", children: it.description }),
|
|
1583
|
+
loading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-16" }) : stocked.has(it.id) && /* @__PURE__ */ jsx(Badge, { variant: "success", className: "shrink-0", children: "No estoque" }),
|
|
1584
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm tabular-nums text-muted-foreground", children: formatCurrency(it.totalAmount, currency) })
|
|
1585
|
+
] }, it.id)) }),
|
|
1586
|
+
stockedSelected.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-4 rounded-lg border border-warning/40 bg-warning-soft p-3", children: [
|
|
1587
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-warning-soft-foreground", children: "Estes itens deram entrada no estoque. Diga o que acontece com a mercadoria: o destino vale para o saldo que ainda est\xE1 na posi\xE7\xE3o; o que j\xE1 foi consumido fica registrado." }),
|
|
1588
|
+
stockedSelected.map((it) => {
|
|
1589
|
+
const info = stocked.get(it.id);
|
|
1590
|
+
const choice = destinations[it.id];
|
|
1591
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1592
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1593
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: it.description }),
|
|
1594
|
+
/* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
|
|
1595
|
+
"Entrou no estoque: ",
|
|
1596
|
+
info.entered.toLocaleString("pt-BR"),
|
|
1597
|
+
" \xB7 ",
|
|
1598
|
+
day(info.enteredOn)
|
|
1599
|
+
] })
|
|
1600
|
+
] }),
|
|
1601
|
+
/* @__PURE__ */ jsx(
|
|
1602
|
+
SegmentedControl,
|
|
1603
|
+
{
|
|
1604
|
+
"aria-label": `Destino de ${it.description}`,
|
|
1605
|
+
options: DESTINATIONS,
|
|
1606
|
+
value: choice?.destination ?? "",
|
|
1607
|
+
onChange: (v) => setDestinations((d) => ({ ...d, [it.id]: { note: d[it.id]?.note ?? "", destination: v || void 0 } })),
|
|
1608
|
+
className: "flex-wrap"
|
|
1609
|
+
}
|
|
1610
|
+
),
|
|
1611
|
+
/* @__PURE__ */ jsx(
|
|
1612
|
+
Input,
|
|
1613
|
+
{
|
|
1614
|
+
value: choice?.note ?? "",
|
|
1615
|
+
onChange: (e) => setDestinations((d) => ({ ...d, [it.id]: { destination: d[it.id]?.destination, note: e.target.value } })),
|
|
1616
|
+
placeholder: "Observa\xE7\xE3o (ex.: nota de devolu\xE7\xE3o 8226400)"
|
|
1617
|
+
}
|
|
1618
|
+
)
|
|
1619
|
+
] }, it.id);
|
|
1620
|
+
})
|
|
1621
|
+
] }),
|
|
1622
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t pt-3 text-sm font-medium", children: [
|
|
1623
|
+
/* @__PURE__ */ jsx("span", { children: whole ? "Conta inteira ser\xE1 cancelada" : "Total a cancelar" }),
|
|
1624
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formatCurrency(amount, currency) })
|
|
1625
|
+
] }),
|
|
1626
|
+
/* @__PURE__ */ jsx(CancelReasonFields, { reasons, reasonId, onReason: setReasonId, detail, onDetail: setDetail })
|
|
1627
|
+
] }),
|
|
1628
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-end gap-2 border-t px-5 py-3", children: [
|
|
1629
|
+
missing && !loading && /* @__PURE__ */ jsx("span", { className: "mr-auto text-xs text-muted-foreground", children: missing }),
|
|
1630
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onClose, disabled: busy, children: "Voltar" }),
|
|
1631
|
+
/* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: submit, disabled: busy || loading || !!missing, children: [
|
|
1632
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
1633
|
+
busy ? "Cancelando\u2026" : whole ? "Cancelar conta" : "Cancelar itens"
|
|
1634
|
+
] })
|
|
1635
|
+
] })
|
|
1636
|
+
]
|
|
1637
|
+
}
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
function CancelInvoicesDialog({ targets, onClose, onCancelled }) {
|
|
1641
|
+
if (targets.length === 1) return /* @__PURE__ */ jsx(CancelOneFromList, { target: targets[0], onClose, onCancelled });
|
|
1642
|
+
return /* @__PURE__ */ jsx(CancelManyDialog, { targets, onClose, onCancelled });
|
|
1643
|
+
}
|
|
1644
|
+
function CancelOneFromList({ target, onClose, onCancelled }) {
|
|
1645
|
+
const provider = useFinancialProvider();
|
|
1646
|
+
const [loaded, setLoaded] = useState(null);
|
|
1647
|
+
useEffect(() => {
|
|
1648
|
+
let alive = true;
|
|
1649
|
+
Promise.all([provider.getInvoiceItems(target.id), provider.getInvoiceById(target.id)]).then(([items, invoice]) => {
|
|
1650
|
+
if (!alive) return;
|
|
1651
|
+
setLoaded({
|
|
1652
|
+
items: items.filter((i) => !i.cancelledAt),
|
|
1653
|
+
// The list row may be one installment; the dialog cancels the invoice.
|
|
1654
|
+
totalAmount: invoice?.totalAmount ?? target.totalAmount,
|
|
1655
|
+
counterpartyName: invoice?.contactName ?? target.counterpartyName
|
|
1656
|
+
});
|
|
1657
|
+
}).catch((err) => {
|
|
1658
|
+
if (alive) {
|
|
1659
|
+
toast.error("N\xE3o foi poss\xEDvel carregar o cancelamento", { description: err?.message });
|
|
1660
|
+
onClose();
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
return () => {
|
|
1664
|
+
alive = false;
|
|
1665
|
+
};
|
|
1666
|
+
}, [provider, target.id]);
|
|
1667
|
+
if (!loaded) {
|
|
1668
|
+
return /* @__PURE__ */ jsx(
|
|
1669
|
+
ModalTask,
|
|
1670
|
+
{
|
|
1671
|
+
open: true,
|
|
1672
|
+
title: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 text-destructive", children: [
|
|
1673
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-4 w-4" }),
|
|
1674
|
+
" Cancelar"
|
|
1675
|
+
] }),
|
|
1676
|
+
width: "max-w-lg",
|
|
1677
|
+
onClose,
|
|
1678
|
+
children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-5 py-4", children: [
|
|
1679
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" }),
|
|
1680
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" }),
|
|
1681
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-20 w-full" })
|
|
1682
|
+
] })
|
|
1683
|
+
}
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
return /* @__PURE__ */ jsx(
|
|
1687
|
+
CancelInvoiceDialog,
|
|
1688
|
+
{
|
|
1689
|
+
invoiceId: target.id,
|
|
1690
|
+
counterpartyName: loaded.counterpartyName,
|
|
1691
|
+
totalAmount: loaded.totalAmount,
|
|
1692
|
+
items: loaded.items,
|
|
1693
|
+
onClose,
|
|
1694
|
+
onCancelled
|
|
1695
|
+
}
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
function CancelManyDialog({ targets, onClose, onCancelled }) {
|
|
1699
|
+
const { currency } = useFinancialConfig();
|
|
1700
|
+
const provider = useFinancialProvider();
|
|
1701
|
+
const [reasons, setReasons] = useState(null);
|
|
1702
|
+
const [reasonId, setReasonId] = useState();
|
|
1703
|
+
const [detail, setDetail] = useState("");
|
|
1704
|
+
const [busy, setBusy] = useState(false);
|
|
1705
|
+
const requestKey = useRef(createIdempotencyKey("financial:invoice-bulk-cancel"));
|
|
1706
|
+
useEffect(() => {
|
|
1707
|
+
let alive = true;
|
|
1708
|
+
(provider.listCancellationReasons?.() ?? Promise.resolve([])).then((r) => {
|
|
1709
|
+
if (alive) setReasons(r);
|
|
1710
|
+
}).catch((err) => {
|
|
1711
|
+
if (alive) {
|
|
1712
|
+
setReasons([]);
|
|
1713
|
+
toast.error("N\xE3o foi poss\xEDvel carregar o cancelamento", { description: err?.message });
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
return () => {
|
|
1717
|
+
alive = false;
|
|
1718
|
+
};
|
|
1719
|
+
}, [provider]);
|
|
1720
|
+
const total = targets.reduce((s, x) => s + x.totalAmount, 0);
|
|
1721
|
+
async function submit() {
|
|
1722
|
+
if (!reasonId) return;
|
|
1723
|
+
setBusy(true);
|
|
1724
|
+
let done = 0;
|
|
1725
|
+
let failed = 0;
|
|
1726
|
+
let firstError;
|
|
1727
|
+
const reasonName = reasons?.find((r) => r.id === reasonId)?.name ?? "";
|
|
1728
|
+
for (const target of targets) {
|
|
1729
|
+
const idempotencyKey = `${requestKey.current}:${target.id}`;
|
|
1730
|
+
try {
|
|
1731
|
+
if (provider.cancelInvoiceItems) {
|
|
1732
|
+
await provider.cancelInvoiceItems({ invoiceId: target.id, itemIds: [], reasonId, detail, stock: [], idempotencyKey });
|
|
1733
|
+
} else {
|
|
1734
|
+
await provider.cancelInvoice({ invoiceId: target.id, reason: [reasonName, detail.trim()].filter(Boolean).join(" \u2014 "), idempotencyKey });
|
|
1735
|
+
}
|
|
1736
|
+
done++;
|
|
1737
|
+
} catch (err) {
|
|
1738
|
+
failed++;
|
|
1739
|
+
firstError ?? (firstError = err?.message);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
setBusy(false);
|
|
1743
|
+
if (done > 0) toast.success(done === 1 ? "1 conta cancelada" : `${done} contas canceladas`);
|
|
1744
|
+
if (failed > 0) {
|
|
1745
|
+
toast.error(failed === 1 ? "1 conta n\xE3o p\xF4de ser cancelada" : `${failed} contas n\xE3o puderam ser canceladas`, {
|
|
1746
|
+
description: firstError ?? "Abra a conta para cancelar: ela pode ter mercadoria no estoque."
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
onCancelled();
|
|
1750
|
+
}
|
|
1751
|
+
return /* @__PURE__ */ jsxs(
|
|
1752
|
+
ModalTask,
|
|
1753
|
+
{
|
|
1754
|
+
open: true,
|
|
1755
|
+
title: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 text-destructive", children: [
|
|
1756
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-4 w-4" }),
|
|
1757
|
+
" Cancelar"
|
|
1758
|
+
] }),
|
|
1759
|
+
width: "max-w-lg",
|
|
1760
|
+
onClose,
|
|
1761
|
+
children: [
|
|
1762
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-container min-h-0 flex-1 space-y-4 overflow-y-auto px-5 py-4", children: [
|
|
1763
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
1764
|
+
"As ",
|
|
1765
|
+
targets.length,
|
|
1766
|
+
" contas abaixo ser\xE3o canceladas por inteiro. Pagamentos j\xE1 alocados a elas ser\xE3o liberados como cr\xE9dito."
|
|
1767
|
+
] }),
|
|
1768
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-48 space-y-2 overflow-y-auto", children: targets.map((x) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-lg border px-3 py-2.5", children: [
|
|
1769
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm", children: x.label }),
|
|
1770
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm tabular-nums text-muted-foreground", children: formatCurrency(x.totalAmount, currency) })
|
|
1771
|
+
] }, x.id)) }),
|
|
1772
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t pt-3 text-sm font-medium", children: [
|
|
1773
|
+
/* @__PURE__ */ jsx("span", { children: "Total a cancelar" }),
|
|
1774
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formatCurrency(total, currency) })
|
|
1775
|
+
] }),
|
|
1776
|
+
/* @__PURE__ */ jsx(CancelReasonFields, { reasons, reasonId, onReason: setReasonId, detail, onDetail: setDetail })
|
|
1777
|
+
] }),
|
|
1778
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-end gap-2 border-t px-5 py-3", children: [
|
|
1779
|
+
!reasonId && reasons !== null && /* @__PURE__ */ jsx("span", { className: "mr-auto text-xs text-muted-foreground", children: "Escolha o motivo do cancelamento" }),
|
|
1780
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onClose, disabled: busy, children: "Voltar" }),
|
|
1781
|
+
/* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: submit, disabled: busy || reasons === null || !reasonId, children: [
|
|
1782
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
1783
|
+
busy ? "Cancelando\u2026" : "Cancelar contas"
|
|
1784
|
+
] })
|
|
1785
|
+
] })
|
|
1786
|
+
]
|
|
1787
|
+
}
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
var num = (text) => Number(text.replace(",", ".")) || 0;
|
|
1791
|
+
function ReceiveStockDialog({ item, invoiceCurrency, entered, onClose, onReceived }) {
|
|
1792
|
+
const { currency } = useFinancialConfig();
|
|
1793
|
+
const provider = useFinancialProvider();
|
|
1794
|
+
const house = useHouseCurrency();
|
|
1795
|
+
const billCurrency = resolveCurrency(currency, invoiceCurrency);
|
|
1796
|
+
const linkedProductId = item.itemKind === "product" ? item.referenceId : void 0;
|
|
1797
|
+
const [locations, setLocations] = useState(null);
|
|
1798
|
+
const [locationId, setLocationId] = useState();
|
|
1799
|
+
const [product, setProduct] = useState(null);
|
|
1800
|
+
const [variants, setVariants] = useState(null);
|
|
1801
|
+
const [quantities, setQuantities] = useState({});
|
|
1802
|
+
const billedUnitCost = item.quantity ? item.totalAmount / item.quantity : item.unitPrice;
|
|
1803
|
+
const [unitCost, setUnitCost] = useState(Math.round(billedUnitCost * 100) / 100);
|
|
1804
|
+
const [costTouched, setCostTouched] = useState(false);
|
|
1805
|
+
const [fx, setFx] = useState(void 0);
|
|
1806
|
+
const [busy, setBusy] = useState(false);
|
|
1807
|
+
const requestKey = useRef(createIdempotencyKey("financial:invoice-item-stock"));
|
|
1808
|
+
const alreadyIn = (entered ?? []).reduce((sum, e) => sum + e.quantity, 0);
|
|
1809
|
+
const productId = linkedProductId ?? product?.id;
|
|
1810
|
+
useEffect(() => {
|
|
1811
|
+
let alive = true;
|
|
1812
|
+
(provider.listStockLocations?.() ?? Promise.resolve([])).then((rows) => {
|
|
1813
|
+
if (!alive) return;
|
|
1814
|
+
setLocations(rows);
|
|
1815
|
+
if (rows.length === 1) setLocationId(rows[0].id);
|
|
1816
|
+
});
|
|
1817
|
+
if (!linkedProductId) {
|
|
1818
|
+
void provider.searchStockProducts?.(item.description.slice(0, 24)).then((rows) => {
|
|
1819
|
+
if (alive && rows.length === 1) setProduct(rows[0]);
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
return () => {
|
|
1823
|
+
alive = false;
|
|
1824
|
+
};
|
|
1825
|
+
}, [provider, item.id]);
|
|
1826
|
+
useEffect(() => {
|
|
1827
|
+
if (!productId) {
|
|
1828
|
+
setVariants(null);
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
let alive = true;
|
|
1832
|
+
setVariants(null);
|
|
1833
|
+
(provider.listProductVariants?.(productId) ?? Promise.resolve([])).then((rows) => {
|
|
1834
|
+
if (!alive) return;
|
|
1835
|
+
const sized = rows.filter((v) => !v.isDefault);
|
|
1836
|
+
const shown = sized.length > 0 ? sized : rows.slice(0, 1);
|
|
1837
|
+
setVariants(shown);
|
|
1838
|
+
const remaining = Math.max(item.quantity - alreadyIn, 0);
|
|
1839
|
+
setQuantities(shown.length === 1 ? { [shown[0].id]: String(remaining || item.quantity) } : {});
|
|
1840
|
+
});
|
|
1841
|
+
return () => {
|
|
1842
|
+
alive = false;
|
|
1843
|
+
};
|
|
1844
|
+
}, [provider, productId]);
|
|
1845
|
+
const single = (variants?.length ?? 0) <= 1;
|
|
1846
|
+
const entries = useMemo(
|
|
1847
|
+
() => (variants ?? []).map((v) => ({ variant: v, quantity: num(quantities[v.id] ?? "") })).filter((e) => e.quantity > 0),
|
|
1848
|
+
[variants, quantities]
|
|
1849
|
+
);
|
|
1850
|
+
const total = entries.reduce((sum, e) => sum + e.quantity, 0);
|
|
1851
|
+
const foreign = !!invoiceCurrency && !!house && invoiceCurrency !== house;
|
|
1852
|
+
useEffect(() => {
|
|
1853
|
+
if (costTouched || total <= 0) return;
|
|
1854
|
+
setUnitCost(Math.round(item.totalAmount / total * 100) / 100);
|
|
1855
|
+
}, [costTouched, total, item.totalAmount]);
|
|
1856
|
+
useEffect(() => {
|
|
1857
|
+
if (!foreign || !invoiceCurrency || !house) return;
|
|
1858
|
+
let alive = true;
|
|
1859
|
+
setFx(void 0);
|
|
1860
|
+
(provider.getFxRate?.(invoiceCurrency, house) ?? Promise.resolve(null)).then((value) => {
|
|
1861
|
+
if (alive) setFx(value);
|
|
1862
|
+
}).catch(() => {
|
|
1863
|
+
if (alive) setFx(null);
|
|
1864
|
+
});
|
|
1865
|
+
return () => {
|
|
1866
|
+
alive = false;
|
|
1867
|
+
};
|
|
1868
|
+
}, [provider, foreign, invoiceCurrency, house]);
|
|
1869
|
+
const missing = !productId ? "Escolha o produto desta linha" : !locationId ? "Escolha onde a mercadoria entra" : total <= 0 ? "Informe quanto chegou" : null;
|
|
1870
|
+
async function submit() {
|
|
1871
|
+
if (missing || !locationId || !provider.receiveInvoiceItemStock) return;
|
|
1872
|
+
setBusy(true);
|
|
1873
|
+
try {
|
|
1874
|
+
await provider.receiveInvoiceItemStock({
|
|
1875
|
+
itemId: item.id,
|
|
1876
|
+
locationId,
|
|
1877
|
+
productId: linkedProductId ? void 0 : product?.id,
|
|
1878
|
+
// The stand-in variant is the stock side's default; naming it is not needed.
|
|
1879
|
+
entries: entries.map((e) => ({ variantId: e.variant.isDefault ? void 0 : e.variant.id, quantity: e.quantity })),
|
|
1880
|
+
unitCost,
|
|
1881
|
+
fxRate: foreign && fx ? fx : void 0,
|
|
1882
|
+
idempotencyKey: requestKey.current
|
|
1883
|
+
});
|
|
1884
|
+
toast.success(`${total.toLocaleString("pt-BR")} em estoque`);
|
|
1885
|
+
onReceived();
|
|
1886
|
+
} catch (err) {
|
|
1887
|
+
toast.error("N\xE3o foi poss\xEDvel dar entrada", { description: err?.message });
|
|
1888
|
+
requestKey.current = createIdempotencyKey("financial:invoice-item-stock");
|
|
1889
|
+
} finally {
|
|
1890
|
+
setBusy(false);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
return /* @__PURE__ */ jsxs(
|
|
1894
|
+
ModalTask,
|
|
1895
|
+
{
|
|
1896
|
+
open: true,
|
|
1897
|
+
title: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2", children: [
|
|
1898
|
+
/* @__PURE__ */ jsx(PackagePlus, { className: "h-4 w-4" }),
|
|
1899
|
+
" Dar entrada no estoque"
|
|
1900
|
+
] }),
|
|
1901
|
+
width: "max-w-lg",
|
|
1902
|
+
onClose,
|
|
1903
|
+
children: [
|
|
1904
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-container min-h-0 flex-1 space-y-4 overflow-y-auto px-5 py-4", children: [
|
|
1905
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-lg border px-3 py-2.5", children: [
|
|
1906
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1907
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm", children: item.description }),
|
|
1908
|
+
/* @__PURE__ */ jsxs("span", { className: "shrink-0 text-sm tabular-nums text-muted-foreground", children: [
|
|
1909
|
+
item.quantity,
|
|
1910
|
+
" \xD7 ",
|
|
1911
|
+
formatCurrency(billedUnitCost, billCurrency)
|
|
1912
|
+
] })
|
|
1913
|
+
] }),
|
|
1914
|
+
alreadyIn > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
|
|
1915
|
+
"J\xE1 entrou: ",
|
|
1916
|
+
alreadyIn.toLocaleString("pt-BR"),
|
|
1917
|
+
(entered ?? []).some((e) => e.variantName) ? ` (${(entered ?? []).map((e) => `${e.quantity.toLocaleString("pt-BR")} ${e.variantName ?? ""}`.trim()).join(", ")})` : ""
|
|
1918
|
+
] })
|
|
1919
|
+
] }),
|
|
1920
|
+
!linkedProductId && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1921
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: "Produto" }),
|
|
1922
|
+
/* @__PURE__ */ jsx(
|
|
1923
|
+
SearchSelect,
|
|
1924
|
+
{
|
|
1925
|
+
value: product?.id,
|
|
1926
|
+
displayValue: product?.name,
|
|
1927
|
+
onChange: (id, option) => setProduct(id && option ? { id, name: option.label } : null),
|
|
1928
|
+
onSearch: async (q) => (await provider.searchStockProducts?.(q) ?? []).map((r) => ({ id: r.id, label: r.name })),
|
|
1929
|
+
minChars: 0,
|
|
1930
|
+
placeholder: "Buscar produto do cat\xE1logo..."
|
|
1931
|
+
}
|
|
1932
|
+
),
|
|
1933
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "A linha passa a apontar para este produto." })
|
|
1934
|
+
] }),
|
|
1935
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1936
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: "Local" }),
|
|
1937
|
+
locations === null ? /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" }) : locations.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Nenhum local de estoque cadastrado." }) : /* @__PURE__ */ jsx(
|
|
1938
|
+
ChoiceSelect,
|
|
1939
|
+
{
|
|
1940
|
+
"aria-label": "Local de estoque",
|
|
1941
|
+
options: locations.map((l) => ({ value: l.id, label: l.name })),
|
|
1942
|
+
value: locationId,
|
|
1943
|
+
onChange: setLocationId,
|
|
1944
|
+
placeholder: "Selecione o local..."
|
|
1945
|
+
}
|
|
1946
|
+
)
|
|
1947
|
+
] }),
|
|
1948
|
+
productId && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1949
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
|
|
1950
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: single ? "Quantidade recebida" : "Quanto chegou de cada varia\xE7\xE3o" }),
|
|
1951
|
+
!single && total > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs tabular-nums text-muted-foreground", children: [
|
|
1952
|
+
total.toLocaleString("pt-BR"),
|
|
1953
|
+
" no total \xB7 ",
|
|
1954
|
+
item.quantity.toLocaleString("pt-BR"),
|
|
1955
|
+
" na conta"
|
|
1956
|
+
] })
|
|
1957
|
+
] }),
|
|
1958
|
+
variants === null ? /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" }) : /* @__PURE__ */ jsx("div", { className: single ? "" : "grid grid-cols-2 gap-2 @md:grid-cols-3", children: variants.map((v) => /* @__PURE__ */ jsxs("label", { className: single ? "block" : "flex items-center gap-2 rounded-lg border px-3 py-2", children: [
|
|
1959
|
+
!single && /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm", children: v.name }),
|
|
1960
|
+
/* @__PURE__ */ jsx(
|
|
1961
|
+
Input,
|
|
1962
|
+
{
|
|
1963
|
+
inputMode: "decimal",
|
|
1964
|
+
"aria-label": single ? "Quantidade recebida" : `Quantidade de ${v.name}`,
|
|
1965
|
+
value: quantities[v.id] ?? "",
|
|
1966
|
+
onChange: (e) => setQuantities((q) => ({ ...q, [v.id]: e.target.value })),
|
|
1967
|
+
placeholder: "0",
|
|
1968
|
+
wrapperClassName: single ? "w-full" : "w-16",
|
|
1969
|
+
className: `h-9 tabular-nums ${single ? "" : "text-right"}`
|
|
1970
|
+
}
|
|
1971
|
+
)
|
|
1972
|
+
] }, v.id)) }),
|
|
1973
|
+
single && variants !== null && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Este produto n\xE3o tem varia\xE7\xF5es. Para separar por tamanho ou cor, cadastre-as no produto (aba Varia\xE7\xF5es)." })
|
|
1974
|
+
] }),
|
|
1975
|
+
productId && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1976
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: "Custo por unidade" }),
|
|
1977
|
+
/* @__PURE__ */ jsx(
|
|
1978
|
+
CurrencyInput,
|
|
1979
|
+
{
|
|
1980
|
+
value: unitCost,
|
|
1981
|
+
onChange: (v) => {
|
|
1982
|
+
setCostTouched(true);
|
|
1983
|
+
setUnitCost(v);
|
|
1984
|
+
},
|
|
1985
|
+
symbol: billCurrency.symbol,
|
|
1986
|
+
locale: billCurrency.locale,
|
|
1987
|
+
currencyCode: billCurrency.code
|
|
1988
|
+
}
|
|
1989
|
+
),
|
|
1990
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
1991
|
+
costTouched ? /* @__PURE__ */ jsx(
|
|
1992
|
+
Button,
|
|
1993
|
+
{
|
|
1994
|
+
variant: "bare",
|
|
1995
|
+
size: "bare",
|
|
1996
|
+
type: "button",
|
|
1997
|
+
onClick: () => setCostTouched(false),
|
|
1998
|
+
className: "font-normal whitespace-normal text-primary underline decoration-dashed underline-offset-2 hover:opacity-80",
|
|
1999
|
+
children: "Voltar ao custo calculado"
|
|
2000
|
+
}
|
|
2001
|
+
) : total > 0 ? `${formatCurrency(item.totalAmount, billCurrency)} da linha \xF7 ${total.toLocaleString("pt-BR")} recebidas.` : "Calculado pelo valor da linha e pela quantidade recebida.",
|
|
2002
|
+
foreign && fx ? ` Em estoque: ${formatCurrency(unitCost * fx, resolveCurrency(currency, house))} cada.` : ""
|
|
2003
|
+
] })
|
|
2004
|
+
] }),
|
|
2005
|
+
foreign && productId && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: fx === void 0 ? "Buscando o c\xE2mbio do dia\u2026" : fx ? `C\xE2mbio do dia: 1 ${invoiceCurrency} = ${fx.toLocaleString("pt-BR", { maximumFractionDigits: 4 })} ${house}. A conta \xE9 em ${invoiceCurrency} e o estoque \xE9 guardado em ${house}.` : `Sem c\xE2mbio ${invoiceCurrency} \u2192 ${house} dispon\xEDvel agora. A entrada tenta buscar de novo ao confirmar.` })
|
|
2006
|
+
] }),
|
|
2007
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-end gap-2 border-t px-5 py-3", children: [
|
|
2008
|
+
missing && /* @__PURE__ */ jsx("span", { className: "mr-auto text-xs text-muted-foreground", children: missing }),
|
|
2009
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onClose, disabled: busy, children: "Voltar" }),
|
|
2010
|
+
/* @__PURE__ */ jsxs(Button, { size: "sm", onClick: submit, disabled: busy || !!missing, children: [
|
|
2011
|
+
/* @__PURE__ */ jsx(PackagePlus, { className: "h-3.5 w-3.5" }),
|
|
2012
|
+
busy ? "Lan\xE7ando\u2026" : total > 0 ? `Dar entrada em ${total.toLocaleString("pt-BR")}` : "Dar entrada"
|
|
2013
|
+
] })
|
|
2014
|
+
] })
|
|
2015
|
+
]
|
|
2016
|
+
}
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
var day2 = (iso) => {
|
|
2020
|
+
if (!iso) return "";
|
|
2021
|
+
const [y, m, d] = iso.slice(0, 10).split("-");
|
|
2022
|
+
return `${d}/${m}/${y}`;
|
|
2023
|
+
};
|
|
2024
|
+
var qty = (n) => n.toLocaleString("pt-BR");
|
|
2025
|
+
var real = (name) => name && name !== "Padr\xE3o" ? name : void 0;
|
|
2026
|
+
function StockEntryNote({ entries, onOpen }) {
|
|
2027
|
+
if (!entries || entries.length === 0) return null;
|
|
2028
|
+
const total = entries.reduce((sum, e) => sum + e.quantity, 0);
|
|
2029
|
+
const named = entries.filter((e) => real(e.variantName));
|
|
2030
|
+
const first = entries.map((e) => e.enteredOn).filter(Boolean).sort()[0];
|
|
2031
|
+
const text = /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
2032
|
+
"Entrou no estoque: ",
|
|
2033
|
+
qty(total),
|
|
2034
|
+
named.length > 0 && ` (${named.map((e) => `${qty(e.quantity)} ${e.variantName}`).join(", ")})`,
|
|
2035
|
+
first && ` \xB7 ${day2(first)}`
|
|
2036
|
+
] });
|
|
2037
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2038
|
+
/* @__PURE__ */ jsx(Package, { className: "h-3 w-3 shrink-0", "aria-hidden": true }),
|
|
2039
|
+
text
|
|
2040
|
+
] });
|
|
2041
|
+
if (!onOpen) return /* @__PURE__ */ jsx("span", { className: "inline-flex max-w-full items-center gap-1 text-success", children: body });
|
|
2042
|
+
return /* @__PURE__ */ jsx(
|
|
2043
|
+
Button,
|
|
2044
|
+
{
|
|
2045
|
+
variant: "bare",
|
|
2046
|
+
size: "bare",
|
|
2047
|
+
type: "button",
|
|
2048
|
+
onClick: (e) => {
|
|
2049
|
+
e.stopPropagation();
|
|
2050
|
+
onOpen();
|
|
2051
|
+
},
|
|
2052
|
+
className: `font-normal whitespace-normal inline-flex max-w-full items-center gap-1 text-success hover:opacity-80 ${INLINE_LINK_TONE.dashed}`,
|
|
2053
|
+
children: body
|
|
2054
|
+
}
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
function StockEntryDetailsDialog({ title, entries, onClose }) {
|
|
2058
|
+
const { currency } = useFinancialConfig();
|
|
2059
|
+
const total = entries.reduce((sum, e) => sum + e.quantity, 0);
|
|
2060
|
+
const stockCode = entries.find((e) => e.currency)?.currency;
|
|
2061
|
+
const stockCurrency = resolveCurrency(currency, stockCode);
|
|
2062
|
+
const stockValue = entries.reduce((sum, e) => sum + e.quantity * (e.unitCost ?? 0), 0);
|
|
2063
|
+
const paidCode = entries.find((e) => e.sourceCurrency)?.sourceCurrency;
|
|
2064
|
+
const paidValue = entries.reduce((sum, e) => sum + e.quantity * (e.sourceUnitCost ?? 0), 0);
|
|
2065
|
+
const rate = entries.find((e) => e.fxRate)?.fxRate;
|
|
2066
|
+
return /* @__PURE__ */ jsx(
|
|
2067
|
+
ModalTask,
|
|
2068
|
+
{
|
|
2069
|
+
open: true,
|
|
2070
|
+
title: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2", children: [
|
|
2071
|
+
/* @__PURE__ */ jsx(Package, { className: "h-4 w-4" }),
|
|
2072
|
+
" Entrada no estoque"
|
|
2073
|
+
] }),
|
|
2074
|
+
width: "max-w-lg",
|
|
2075
|
+
onClose,
|
|
2076
|
+
children: /* @__PURE__ */ jsxs("div", { className: "fz-container min-h-0 flex-1 space-y-4 overflow-y-auto px-5 py-4", children: [
|
|
2077
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: title }),
|
|
2078
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y rounded-lg border", children: entries.map((e, i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-2.5 text-sm", children: [
|
|
2079
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2080
|
+
/* @__PURE__ */ jsxs("p", { className: "truncate font-medium", children: [
|
|
2081
|
+
qty(e.quantity),
|
|
2082
|
+
real(e.variantName) ? ` \xD7 ${e.variantName}` : ""
|
|
2083
|
+
] }),
|
|
2084
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: [e.locationName, day2(e.enteredOn)].filter(Boolean).join(" \xB7 ") })
|
|
2085
|
+
] }),
|
|
2086
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
|
|
2087
|
+
/* @__PURE__ */ jsxs("p", { className: "tabular-nums", children: [
|
|
2088
|
+
formatCurrency(e.unitCost ?? 0, resolveCurrency(currency, e.currency)),
|
|
2089
|
+
" cada"
|
|
2090
|
+
] }),
|
|
2091
|
+
e.sourceCurrency && e.sourceUnitCost != null && /* @__PURE__ */ jsxs("p", { className: "text-xs tabular-nums text-muted-foreground", children: [
|
|
2092
|
+
"pago ",
|
|
2093
|
+
formatCurrency(e.sourceUnitCost, resolveCurrency(currency, e.sourceCurrency))
|
|
2094
|
+
] })
|
|
2095
|
+
] })
|
|
2096
|
+
] }, i)) }),
|
|
2097
|
+
/* @__PURE__ */ jsxs("dl", { className: "space-y-1 text-sm", children: [
|
|
2098
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
2099
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Total recebido" }),
|
|
2100
|
+
/* @__PURE__ */ jsx("dd", { className: "font-medium tabular-nums", children: qty(total) })
|
|
2101
|
+
] }),
|
|
2102
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
2103
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Valor em estoque" }),
|
|
2104
|
+
/* @__PURE__ */ jsx("dd", { className: "font-medium tabular-nums", children: formatCurrency(stockValue, stockCurrency) })
|
|
2105
|
+
] }),
|
|
2106
|
+
paidCode && /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
2107
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Valor pago" }),
|
|
2108
|
+
/* @__PURE__ */ jsx("dd", { className: "tabular-nums", children: formatCurrency(paidValue, resolveCurrency(currency, paidCode)) })
|
|
2109
|
+
] }),
|
|
2110
|
+
paidCode && rate && /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
2111
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "C\xE2mbio usado" }),
|
|
2112
|
+
/* @__PURE__ */ jsxs("dd", { className: "tabular-nums", children: [
|
|
2113
|
+
"1 ",
|
|
2114
|
+
paidCode,
|
|
2115
|
+
" = ",
|
|
2116
|
+
rate.toLocaleString("pt-BR", { maximumFractionDigits: 4 }),
|
|
2117
|
+
" ",
|
|
2118
|
+
stockCode
|
|
2119
|
+
] })
|
|
2120
|
+
] })
|
|
2121
|
+
] })
|
|
2122
|
+
] })
|
|
2123
|
+
}
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
var STATUS_COLORS = {
|
|
2127
|
+
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
|
|
2128
|
+
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
|
|
2129
|
+
partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial", tone: "warning" },
|
|
2130
|
+
paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid", tone: "success" },
|
|
2131
|
+
overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue", tone: "destructive" },
|
|
2132
|
+
draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft", tone: "muted" },
|
|
2133
|
+
cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled", tone: "muted" }
|
|
2134
|
+
};
|
|
2135
|
+
function itemKindLabel(kind, t) {
|
|
2136
|
+
if (!kind || kind === "other") return void 0;
|
|
2137
|
+
const label = t(`financial.itemKind.${kind}`);
|
|
2138
|
+
return label.startsWith("financial.itemKind.") ? void 0 : label;
|
|
2139
|
+
}
|
|
2140
|
+
function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, embedded }) {
|
|
2141
|
+
const t = useTranslation();
|
|
2142
|
+
const config = useFinancialConfig();
|
|
2143
|
+
const parentLabel = backLabel ?? (direction === "debit" ? t("financial.nav.payables") : t("financial.nav.receivables"));
|
|
2144
|
+
const provider = useFinancialProvider();
|
|
2145
|
+
const paymentMethods = useFinancialStore((s) => s.paymentMethods);
|
|
2146
|
+
const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
|
|
2147
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
2148
|
+
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
2149
|
+
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
2150
|
+
const reverseMovements = useFinancialStore((s) => s.reverseMovements);
|
|
2151
|
+
const [detailsLoaded, setDetailsLoaded] = useState(false);
|
|
2152
|
+
const [detailsLoading, setDetailsLoading] = useState(false);
|
|
2153
|
+
const [invoice, setInvoice] = useState(null);
|
|
2154
|
+
const currency = useCurrency(config.currency, invoice?.currency);
|
|
2155
|
+
const unitMode = useUnitMode();
|
|
2156
|
+
const unitName = useUnitStore((st) => unitMode && invoice?.unitId ? st.byId[invoice.unitId]?.name : void 0);
|
|
2157
|
+
const [items, setItems] = useState([]);
|
|
2158
|
+
const [attachments, setAttachments] = useState([]);
|
|
2159
|
+
const [movements, setMovements] = useState([]);
|
|
2160
|
+
const [payments, setPayments] = useState([]);
|
|
2161
|
+
const [holders, setHolders] = useState(/* @__PURE__ */ new Map());
|
|
2162
|
+
const [loading, setLoading] = useState(true);
|
|
2163
|
+
const [error, setError] = useState(null);
|
|
2164
|
+
const [reversing, setReversing] = useState(null);
|
|
2165
|
+
const [reverseKeep, setReverseKeep] = useState(/* @__PURE__ */ new Set());
|
|
2166
|
+
const [receiptBill, setReceiptBill] = useState(null);
|
|
2167
|
+
const [reverseReason, setReverseReason] = useState("");
|
|
2168
|
+
const [payingMovement, setPayingMovement] = useState(null);
|
|
2169
|
+
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
|
|
2170
|
+
const [pendingReceipt, setPendingReceipt] = useState();
|
|
2171
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
2172
|
+
const [cancelTarget, setCancelTarget] = useState(null);
|
|
2173
|
+
const menuRef = useRef(null);
|
|
2174
|
+
async function patchSource(mov, next) {
|
|
2175
|
+
const metadata = { ...mov.metadata ?? {}, ...next };
|
|
2176
|
+
setMovements((prev) => prev.map((m) => m.id === mov.id ? { ...m, metadata } : m));
|
|
2177
|
+
try {
|
|
2178
|
+
await provider.updateInstallment?.(mov.id, { metadata });
|
|
2179
|
+
} catch {
|
|
2180
|
+
void loadData();
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
async function patchDueDate(mov, dueDate) {
|
|
2184
|
+
if (!dueDate || dueDate === mov.dueDate) return;
|
|
2185
|
+
setMovements((prev) => prev.map((m) => m.id === mov.id ? { ...m, dueDate } : m));
|
|
2186
|
+
try {
|
|
2187
|
+
await provider.updateInstallment?.(mov.id, { dueDate });
|
|
2188
|
+
} catch {
|
|
2189
|
+
void loadData();
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
const [pinnedAmounts, setPinnedAmounts] = useState(/* @__PURE__ */ new Set());
|
|
2193
|
+
const [showCancelledRows, setShowCancelledRows] = useState(false);
|
|
2194
|
+
const [receiveTarget, setReceiveTarget] = useState(null);
|
|
2195
|
+
const [stockDetailTarget, setStockDetailTarget] = useState(null);
|
|
2196
|
+
const [stockDetails, setStockDetails] = useState(/* @__PURE__ */ new Map());
|
|
2197
|
+
const stockSummary = (itemId, total) => {
|
|
2198
|
+
const named = (stockDetails.get(itemId) ?? []).filter((d) => d.variantName && d.variantName !== "Padr\xE3o");
|
|
2199
|
+
const count = total.toLocaleString("pt-BR");
|
|
2200
|
+
return named.length ? `${count} (${named.map((d) => `${d.quantity.toLocaleString("pt-BR")} ${d.variantName}`).join(", ")})` : count;
|
|
2201
|
+
};
|
|
2202
|
+
const [stockVersion, setStockVersion] = useState(0);
|
|
2203
|
+
const [stockByItem, setStockByItem] = useState(null);
|
|
2204
|
+
useEffect(() => {
|
|
2205
|
+
let alive = true;
|
|
2206
|
+
setStockByItem(null);
|
|
2207
|
+
if (direction !== "debit" || !provider.getInvoiceStockEntries) return;
|
|
2208
|
+
provider.getInvoiceStockEntries(invoiceId).then((entries) => {
|
|
2209
|
+
if (!alive) return;
|
|
2210
|
+
const byItem = /* @__PURE__ */ new Map();
|
|
2211
|
+
for (const e of entries) byItem.set(e.itemId, (byItem.get(e.itemId) ?? 0) + e.entered);
|
|
2212
|
+
setStockByItem(byItem);
|
|
2213
|
+
}).catch(() => {
|
|
2214
|
+
if (alive) setStockByItem(/* @__PURE__ */ new Map());
|
|
2215
|
+
});
|
|
2216
|
+
void provider.getInvoiceStockDetails?.(invoiceId).then((rows) => {
|
|
2217
|
+
if (!alive) return;
|
|
2218
|
+
const byItem = /* @__PURE__ */ new Map();
|
|
2219
|
+
for (const r of rows) byItem.set(r.itemId, [...byItem.get(r.itemId) ?? [], r]);
|
|
2220
|
+
setStockDetails(byItem);
|
|
2221
|
+
}).catch(() => {
|
|
2222
|
+
});
|
|
2223
|
+
return () => {
|
|
2224
|
+
alive = false;
|
|
2225
|
+
};
|
|
2226
|
+
}, [provider, invoiceId, direction, stockVersion]);
|
|
2227
|
+
const canChangeAmounts = !!provider.rescheduleInvoice && invoice?.status !== "cancelled" && movements.length > 1 && movements.every((m) => m.status !== "paid" && m.status !== "partial" && m.status !== "cancelled" && !(m.paidAmount > 0));
|
|
2228
|
+
async function patchAmount(mov, value) {
|
|
2229
|
+
if (!invoice || !provider.rescheduleInvoice) return;
|
|
2230
|
+
const index = movements.findIndex((m) => m.id === mov.id);
|
|
2231
|
+
const next = rebalanceInstallments({
|
|
2232
|
+
amounts: movements.map((m) => m.amount),
|
|
2233
|
+
index,
|
|
2234
|
+
value,
|
|
2235
|
+
total: invoice.totalAmount,
|
|
2236
|
+
pinned: movements.map((m) => pinnedAmounts.has(m.installmentNumber ?? 0))
|
|
2237
|
+
});
|
|
2238
|
+
if (!next) return;
|
|
2239
|
+
setPinnedAmounts((prev) => new Set(prev).add(mov.installmentNumber ?? 0));
|
|
2240
|
+
setMovements((prev) => prev.map((m, n) => ({ ...m, amount: next[n] })));
|
|
2241
|
+
try {
|
|
2242
|
+
await provider.rescheduleInvoice(invoice.id, movements.map((m, n) => ({
|
|
2243
|
+
dueDate: m.dueDate,
|
|
2244
|
+
amount: next[n],
|
|
2245
|
+
metadata: m.metadata ?? {}
|
|
2246
|
+
})));
|
|
2247
|
+
} catch (err) {
|
|
2248
|
+
toast.error("N\xE3o foi poss\xEDvel mudar o valor da parcela", { description: err?.message });
|
|
2249
|
+
}
|
|
2250
|
+
void loadData();
|
|
2251
|
+
}
|
|
2252
|
+
async function patchInvoiceDate(invoiceDate) {
|
|
2253
|
+
if (!invoice || !invoiceDate || invoiceDate === invoice.invoiceDate) return;
|
|
2254
|
+
const antes = invoice;
|
|
2255
|
+
setInvoice({ ...invoice, invoiceDate });
|
|
2256
|
+
try {
|
|
2257
|
+
await provider.updateInvoice(invoice.id, { invoiceDate });
|
|
2258
|
+
} catch (e) {
|
|
2259
|
+
setInvoice(antes);
|
|
2260
|
+
toast.error(e?.message ?? String(e));
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
const reqRef = useRef(0);
|
|
2264
|
+
async function loadData() {
|
|
2265
|
+
const req = ++reqRef.current;
|
|
2266
|
+
setLoading(true);
|
|
2267
|
+
setError(null);
|
|
2268
|
+
try {
|
|
2269
|
+
const [inv, invItems, movResult, anexos] = await Promise.all([
|
|
2270
|
+
provider.getInvoiceById(invoiceId),
|
|
2271
|
+
provider.getInvoiceItems(invoiceId),
|
|
2272
|
+
// Scope to THIS invoice (not the tenant-wide first-50 page) so every
|
|
2273
|
+
// installment/payment loads regardless of how many movements exist.
|
|
2274
|
+
provider.getMovements({ direction, invoiceId, pageSize: 500 }),
|
|
2275
|
+
// Opcional no provider: um provider que não sabe de anexo devolve vazio
|
|
2276
|
+
// em vez de derrubar a tela inteira do detalhe.
|
|
2277
|
+
provider.getInvoiceAttachments?.(invoiceId).catch(() => []) ?? Promise.resolve([])
|
|
2278
|
+
]);
|
|
2279
|
+
if (req !== reqRef.current) return;
|
|
2280
|
+
setInvoice(inv);
|
|
2281
|
+
setItems(invItems);
|
|
2282
|
+
setAttachments(anexos);
|
|
2283
|
+
setMovements(
|
|
2284
|
+
movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
|
|
2285
|
+
);
|
|
2286
|
+
const pays = movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "payment").sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? ""));
|
|
2287
|
+
setPayments(pays);
|
|
2288
|
+
if (pays.length > 0) void loadPaymentLabels(pays, req);
|
|
2289
|
+
} catch (e) {
|
|
2290
|
+
if (req !== reqRef.current) return;
|
|
2291
|
+
setError(e?.message ?? String(e));
|
|
2292
|
+
} finally {
|
|
2293
|
+
if (req === reqRef.current) setLoading(false);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
useEffect(() => {
|
|
2297
|
+
void loadData();
|
|
2298
|
+
}, [invoiceId]);
|
|
2299
|
+
useDataChanged({ table: T.movements }, () => {
|
|
2300
|
+
void loadData();
|
|
2301
|
+
}, [invoiceId, direction]);
|
|
2302
|
+
useEffect(() => {
|
|
2303
|
+
if (!menuOpen) return;
|
|
2304
|
+
function handleClick(e) {
|
|
2305
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
|
|
2306
|
+
}
|
|
2307
|
+
document.addEventListener("mousedown", handleClick);
|
|
2308
|
+
return () => document.removeEventListener("mousedown", handleClick);
|
|
2309
|
+
}, [menuOpen]);
|
|
2310
|
+
async function loadPaymentLabels(pays, req) {
|
|
2311
|
+
const ids = [...new Set(pays.map(paymentHolderId).filter((id) => Boolean(id)))];
|
|
2312
|
+
try {
|
|
2313
|
+
const [accounts] = await Promise.all([
|
|
2314
|
+
ids.length > 0 && provider.getAccountsByIds ? provider.getAccountsByIds(ids) : Promise.resolve([]),
|
|
2315
|
+
fetchPaymentMethods(),
|
|
2316
|
+
fetchBankAccounts()
|
|
2317
|
+
]);
|
|
2318
|
+
if (req !== reqRef.current) return;
|
|
2319
|
+
setHolders(new Map(accounts.map((a) => [a.id, a])));
|
|
2320
|
+
} catch {
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
async function loadPaymentDetails() {
|
|
2324
|
+
if (detailsLoaded || detailsLoading) return;
|
|
2325
|
+
setDetailsLoading(true);
|
|
2326
|
+
try {
|
|
2327
|
+
await Promise.all([fetchPaymentMethods(), fetchBankAccounts()]);
|
|
2328
|
+
setDetailsLoaded(true);
|
|
2329
|
+
} catch {
|
|
2330
|
+
} finally {
|
|
2331
|
+
setDetailsLoading(false);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
useEffect(() => {
|
|
2335
|
+
void loadPaymentDetails();
|
|
2336
|
+
}, [invoiceId]);
|
|
2337
|
+
const [chartNames, setChartNames] = React13__default.useState(/* @__PURE__ */ new Map());
|
|
2338
|
+
React13__default.useEffect(() => {
|
|
2339
|
+
let cancelled2 = false;
|
|
2340
|
+
void provider.getChartOfAccounts().then((rows) => {
|
|
2341
|
+
if (!cancelled2) setChartNames(new Map(rows.map((r) => [r.id, r.name])));
|
|
2342
|
+
}).catch(() => {
|
|
2343
|
+
});
|
|
2344
|
+
return () => {
|
|
2345
|
+
cancelled2 = true;
|
|
2346
|
+
};
|
|
2347
|
+
}, [provider]);
|
|
2348
|
+
if (loading) {
|
|
2349
|
+
return /* @__PURE__ */ jsx(OrderDetailSkeleton, { parentLabel: embedded ? void 0 : parentLabel, onBack: embedded ? void 0 : onBack });
|
|
2350
|
+
}
|
|
2351
|
+
if (error) {
|
|
2352
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
2353
|
+
/* @__PURE__ */ jsx(SubpageHeader, { title: t("financial.invoice.loadFailed"), onBack, parentLabel }),
|
|
2354
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-card border border-destructive/30 bg-destructive/5 px-4 py-3", children: [
|
|
2355
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: t("financial.invoice.loadFailedDesc") }),
|
|
2356
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive/80 break-words", children: error })
|
|
2357
|
+
] }),
|
|
2358
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void loadData(), children: t("financial.invoice.retry") })
|
|
2359
|
+
] });
|
|
2360
|
+
}
|
|
2361
|
+
if (!invoice) {
|
|
2362
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
2363
|
+
/* @__PURE__ */ jsx(SubpageHeader, { title: t("financial.invoice.notFound"), onBack, parentLabel }),
|
|
2364
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("financial.invoice.notFoundDesc") })
|
|
2365
|
+
] });
|
|
2366
|
+
}
|
|
2367
|
+
const settlements = groupPaymentsByBill(movements, payments);
|
|
2368
|
+
const accountsById = new Map(bankAccounts.map((a) => [a.id, a]));
|
|
2369
|
+
for (const [id, a] of holders) accountsById.set(id, a);
|
|
2370
|
+
const howCtx = { paymentMethods, paymentMethodTypes, accountsById, t };
|
|
2371
|
+
const statusConfig = STATUS_COLORS[invoice.status] ?? STATUS_COLORS.pending;
|
|
2372
|
+
statusConfig.icon;
|
|
2373
|
+
const totalPaid = movements.reduce((sum, m) => sum + m.paidAmount, 0);
|
|
2374
|
+
const liveInstallments = movements.filter((m) => m.status !== "cancelled");
|
|
2375
|
+
const splitCancelled = invoice.status !== "cancelled" && liveInstallments.length > 0;
|
|
2376
|
+
const scheduleRows = splitCancelled ? liveInstallments : movements;
|
|
2377
|
+
const cancelledRows = splitCancelled ? movements.filter((m) => m.status === "cancelled") : [];
|
|
2378
|
+
const totalRemaining = invoice.totalAmount - totalPaid;
|
|
2379
|
+
const counted = items.filter((it) => !it.cancelledAt);
|
|
2380
|
+
const subtotal = counted.reduce((sum, it) => sum + it.quantity * it.unitPrice, 0);
|
|
2381
|
+
const totalDiscount = counted.reduce((sum, it) => sum + it.discount, 0);
|
|
2382
|
+
const totalSurcharge = counted.reduce((sum, it) => sum + it.surcharge, 0);
|
|
2383
|
+
const liveItems = items.filter((it) => !it.cancelledAt);
|
|
2384
|
+
const detailItems = items.map((it, i) => ({
|
|
2385
|
+
id: it.id ?? String(i),
|
|
2386
|
+
// Uma linha sem texto próprio herda o da fatura — melhor repetir o assunto
|
|
2387
|
+
// do que mostrar um campo vazio com um enum embaixo.
|
|
2388
|
+
name: it.description || invoice.itemsSummary || invoice.observations || t("financial.invoice.item"),
|
|
2389
|
+
// `itemKind` é vocabulário do banco, não do balcão: 'other' não diz nada a
|
|
2390
|
+
// ninguém e era o que sobrava quando a linha vinha sem nome. Só aparece
|
|
2391
|
+
// quando tem uma tradução de verdade.
|
|
2392
|
+
description: it.cancelledAt ? `Cancelado${it.cancelReason ? ` \u2014 ${it.cancelReason}` : ""}` : itemKindLabel(it.itemKind, t),
|
|
2393
|
+
struck: !!it.cancelledAt,
|
|
2394
|
+
// A classificação vai no campo dela, que o arquétipo desenha como pastilha
|
|
2395
|
+
// no meio da linha — o mesmo lugar que ela ocupa no formulário.
|
|
2396
|
+
account: it.accountId ? chartNames.get(it.accountId) : void 0,
|
|
2397
|
+
icon: ITEM_ICONS[it.itemKind ?? "other"],
|
|
2398
|
+
imageUrl: it.imageUrl,
|
|
2399
|
+
note: stockDetails.has(it.id) ? /* @__PURE__ */ jsx(StockEntryNote, { entries: stockDetails.get(it.id), onOpen: () => setStockDetailTarget(it) }) : void 0,
|
|
2400
|
+
// O LÁPIS DA LINHA. Aparece no hover (o arquétipo reserva o espaço, então
|
|
2401
|
+
// a coluna de valores não dança) e leva para a edição com ESTA linha
|
|
2402
|
+
// aberta — sem ele, conferir um item e querer corrigi-lo custava abrir a
|
|
2403
|
+
// edição e reencontrar ali o que o dedo já estava apontando.
|
|
2404
|
+
...invoice.status !== "cancelled" && !it.cancelledAt ? { actions: [
|
|
2405
|
+
...onEdit && movements.every((m) => m.paidAmount <= 0) ? [{ id: "edit", label: t("financial.invoiceForm.edit"), icon: Pencil, onSelect: () => onEdit(i) }] : [],
|
|
2406
|
+
// What already entered is stated; receiving (more) is always offered,
|
|
2407
|
+
// because goods rarely arrive the way the bill reads.
|
|
2408
|
+
...direction === "debit" && stockByItem && stockByItem.has(it.id) ? [{
|
|
2409
|
+
id: "stock-status",
|
|
2410
|
+
icon: Package,
|
|
2411
|
+
disabled: true,
|
|
2412
|
+
onSelect: () => {
|
|
2413
|
+
},
|
|
2414
|
+
label: `No estoque: ${stockSummary(it.id, stockByItem.get(it.id))}`
|
|
2415
|
+
}] : [],
|
|
2416
|
+
...direction === "debit" && stockByItem && provider.receiveInvoiceItemStock ? [{
|
|
2417
|
+
id: "stock",
|
|
2418
|
+
icon: PackagePlus,
|
|
2419
|
+
onSelect: () => setReceiveTarget(it),
|
|
2420
|
+
label: stockByItem.has(it.id) ? "Dar entrada em mais" : "Dar entrada no estoque"
|
|
2421
|
+
}] : [],
|
|
2422
|
+
{ id: "cancel", label: "Cancelar", icon: Ban, intent: "danger", onSelect: () => setCancelTarget(it.id) }
|
|
2423
|
+
] } : {},
|
|
2424
|
+
quantity: it.quantity,
|
|
2425
|
+
unitPrice: formatCurrency(it.unitPrice, currency),
|
|
2426
|
+
total: formatCurrency(it.totalAmount, currency)
|
|
2427
|
+
}));
|
|
2428
|
+
if (detailItems.length === 0 && invoice.totalAmount > 0) {
|
|
2429
|
+
detailItems.push({
|
|
2430
|
+
id: "no-lines",
|
|
2431
|
+
name: invoice.itemsSummary || invoice.observations || t("financial.invoice.item"),
|
|
2432
|
+
description: t("financial.invoice.noLines"),
|
|
2433
|
+
quantity: 1,
|
|
2434
|
+
unitPrice: formatCurrency(invoice.totalAmount, currency),
|
|
2435
|
+
total: formatCurrency(invoice.totalAmount, currency)
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
const cancelled = invoice.status === "cancelled";
|
|
2439
|
+
const totalRows = [
|
|
2440
|
+
...totalDiscount > 0 || totalSurcharge > 0 ? [{ label: t("financial.invoice.subtotal"), value: formatCurrency(subtotal, currency), muted: true }] : [],
|
|
2441
|
+
...totalDiscount > 0 ? [{ label: t("financial.invoice.discount"), value: `-${formatCurrency(totalDiscount, currency)}`, muted: true }] : [],
|
|
2442
|
+
...totalSurcharge > 0 ? [{ label: t("financial.invoice.surcharge"), value: `+${formatCurrency(totalSurcharge, currency)}`, muted: true }] : [],
|
|
2443
|
+
{ label: t("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), strong: true },
|
|
2444
|
+
{ label: t("financial.invoice.columnPaid"), value: formatCurrency(totalPaid, currency) },
|
|
2445
|
+
...totalRemaining > 0 ? [{ label: t("financial.invoice.balance"), value: formatCurrency(totalRemaining, currency) }] : []
|
|
2446
|
+
];
|
|
2447
|
+
direction === "debit" ? t("financial.invoice.accountsPayable") : t("financial.invoice.accountsReceivable");
|
|
2448
|
+
const actions = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2449
|
+
/* @__PURE__ */ jsx(WidgetSlot, { zone: FINANCIAL_INVOICE_ZONES.headerActions, invoiceId: invoice.id, direction, cancelled }),
|
|
2450
|
+
/* @__PURE__ */ jsx(
|
|
2451
|
+
PageActions,
|
|
2452
|
+
{
|
|
2453
|
+
actions: !cancelled ? [
|
|
2454
|
+
...onEdit && invoice.status !== "paid" ? [{ id: "edit", label: t("financial.invoice.edit"), icon: Pencil, intent: "primary", onSelect: () => onEdit() }] : [],
|
|
2455
|
+
// Also on a paid bill: what was paid becomes the counterparty's credit (V1).
|
|
2456
|
+
{ id: "cancel", label: "Cancelar", icon: Ban, intent: "danger", onSelect: () => setCancelTarget("") }
|
|
2457
|
+
] : []
|
|
2458
|
+
}
|
|
2459
|
+
)
|
|
2460
|
+
] });
|
|
2461
|
+
const sideBlocks = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2462
|
+
invoice.bookingStartsAt && /* @__PURE__ */ jsx(OrderDetailBlock, { title: t("financial.invoice.booking"), children: /* @__PURE__ */ jsxs(
|
|
2463
|
+
Button,
|
|
2464
|
+
{
|
|
2465
|
+
variant: "bare",
|
|
2466
|
+
size: "bare",
|
|
2467
|
+
type: "button",
|
|
2468
|
+
onClick: () => config.onBookingClick?.(invoice.id),
|
|
2469
|
+
className: "inline-flex items-center gap-1 text-sm font-medium text-magic hover:underline",
|
|
2470
|
+
children: [
|
|
2471
|
+
/* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
2472
|
+
new Date(invoice.bookingStartsAt).toLocaleDateString(void 0, { weekday: "short", day: "2-digit", month: "short" }),
|
|
2473
|
+
" ",
|
|
2474
|
+
new Date(invoice.bookingStartsAt).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }),
|
|
2475
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "h-2.5 w-2.5" })
|
|
2476
|
+
]
|
|
2477
|
+
}
|
|
2478
|
+
) }),
|
|
2479
|
+
invoice.observations && /* @__PURE__ */ jsx(OrderDetailBlock, { title: t("financial.invoice.notes"), children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: invoice.observations }) }),
|
|
2480
|
+
!!invoice.tags?.length && /* @__PURE__ */ jsx(OrderDetailBlock, { title: t("financial.invoiceForm.tags"), children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: invoice.tags.map((tag) => /* @__PURE__ */ jsx(TagChip, { tag }, tag)) }) })
|
|
2481
|
+
] });
|
|
2482
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2483
|
+
/* @__PURE__ */ jsx(
|
|
2484
|
+
OrderDetailView,
|
|
2485
|
+
{
|
|
2486
|
+
embedded,
|
|
2487
|
+
title: invoice.fiscalNumber ? `#${invoice.fiscalNumber}` : invoice.contactName ?? invoice.invoiceDate,
|
|
2488
|
+
badges: [{ label: t(statusConfig.labelKey), tone: statusConfig.tone }],
|
|
2489
|
+
onBack: embedded ? void 0 : onBack,
|
|
2490
|
+
backLabel: embedded ? void 0 : parentLabel,
|
|
2491
|
+
meta: invoice,
|
|
2492
|
+
actions,
|
|
2493
|
+
stats: [
|
|
2494
|
+
{ id: "total", label: t("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
|
|
2495
|
+
{ id: "items", label: t("financial.invoice.items"), value: liveItems.length, icon: LayoutList },
|
|
2496
|
+
{ id: "installments", label: t("financial.invoice.installments"), value: `${invoice.totalInstallments}x`, icon: Layers },
|
|
2497
|
+
{
|
|
2498
|
+
id: "date",
|
|
2499
|
+
label: t("financial.invoice.date"),
|
|
2500
|
+
icon: CalendarDays,
|
|
2501
|
+
value: invoice.status !== "cancelled" ? /* @__PURE__ */ jsx(DatePicker, { value: invoice.invoiceDate, variant: "inline", format: "numeric", onChange: (v) => {
|
|
2502
|
+
void patchInvoiceDate(v);
|
|
2503
|
+
} }) : invoice.invoiceDate
|
|
2504
|
+
}
|
|
2505
|
+
],
|
|
2506
|
+
customerLabel: direction === "debit" ? t("financial.invoice.payTo") : t("financial.invoice.receiveFrom"),
|
|
2507
|
+
unit: unitName ? { label: t("financial.invoice.unit"), value: unitName } : void 0,
|
|
2508
|
+
customer: { name: invoice.contactName, email: null },
|
|
2509
|
+
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" }),
|
|
2510
|
+
items: detailItems,
|
|
2511
|
+
itemActionsMenu: true,
|
|
2512
|
+
itemsLabel: t("financial.invoice.items"),
|
|
2513
|
+
totals: totalRows,
|
|
2514
|
+
totalsLabel: t("financial.invoice.payment"),
|
|
2515
|
+
mainSections: (
|
|
2516
|
+
// O cronograma no MESMO bloco que os itens e o pagamento — antes o título
|
|
2517
|
+
// ficava fora do cartão, e a seção lia como se fosse de outra tela.
|
|
2518
|
+
/* @__PURE__ */ jsx(
|
|
2519
|
+
OrderDetailBlock,
|
|
2520
|
+
{
|
|
2521
|
+
title: t("financial.invoice.paymentSchedule"),
|
|
2522
|
+
actions: attachments.length > 0 ? /* @__PURE__ */ jsxs(
|
|
2523
|
+
Button,
|
|
2524
|
+
{
|
|
2525
|
+
variant: "bare",
|
|
2526
|
+
size: "bare",
|
|
2527
|
+
type: "button",
|
|
2528
|
+
onClick: () => setAttachmentsOpen(true),
|
|
2529
|
+
title: attachments.map((a) => a.fileName).join("\n"),
|
|
2530
|
+
className: "font-normal -my-1 inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
2531
|
+
children: [
|
|
2532
|
+
/* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
2533
|
+
attachments.length === 1 ? "Ver o anexo" : `Ver os ${attachments.length} anexos`
|
|
2534
|
+
]
|
|
2535
|
+
}
|
|
2536
|
+
) : void 0,
|
|
2537
|
+
children: /* @__PURE__ */ jsxs("div", { className: "-mx-4 -mb-4", children: [
|
|
2538
|
+
movements.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-4 text-center text-sm text-muted-foreground", children: t("financial.invoice.noInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y border-t", children: [...scheduleRows, ...showCancelledRows ? cancelledRows : []].map((mov) => {
|
|
2539
|
+
const remaining = mov.amount - mov.paidAmount;
|
|
2540
|
+
const isPaid = mov.status === "paid";
|
|
2541
|
+
const isPartial = mov.status === "partial";
|
|
2542
|
+
const hasPaidDetails = isPaid || isPartial;
|
|
2543
|
+
const instPayments = settlements.byBillId.get(mov.id) ?? [];
|
|
2544
|
+
const hows = instPayments.map((payment) => describePayment(payment, howCtx));
|
|
2545
|
+
const paymentSummary = [...new Set(hows.map((how2) => [how2.method, how2.account?.name].filter(Boolean).join(" \xB7 ")).filter(Boolean))].join(" / ");
|
|
2546
|
+
const how = hows[0];
|
|
2547
|
+
const howCard = how?.account?.accountType === "card_payable" ? how.account : void 0;
|
|
2548
|
+
const HowIcon = METHOD_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
|
|
2549
|
+
const srcMeta = mov.metadata ?? {};
|
|
2550
|
+
const srcStr = (v) => typeof v === "string" && v ? v : void 0;
|
|
2551
|
+
const srcTypeId = srcStr(srcMeta.payMethodTypeId);
|
|
2552
|
+
const srcAccountId = srcStr(srcMeta.payAccountId);
|
|
2553
|
+
const editavel = !hasPaidDetails && invoice.status !== "cancelled";
|
|
2554
|
+
const canChooseSource = editavel && Math.abs(mov.amount) >= 5e-3;
|
|
2555
|
+
const ativa = invoice.status !== "cancelled";
|
|
2556
|
+
return /* @__PURE__ */ jsx(
|
|
2557
|
+
InstallmentRow,
|
|
2558
|
+
{
|
|
2559
|
+
number: mov.installmentNumber ?? 1,
|
|
2560
|
+
amount: formatCurrency(mov.amount, currency),
|
|
2561
|
+
amountValue: mov.amount,
|
|
2562
|
+
onAmountChange: canChangeAmounts ? (v) => {
|
|
2563
|
+
void patchAmount(mov, v);
|
|
2564
|
+
} : void 0,
|
|
2565
|
+
status: mov.status,
|
|
2566
|
+
dueDate: mov.dueDate,
|
|
2567
|
+
onDueDateChange: editavel ? (v) => {
|
|
2568
|
+
void patchDueDate(mov, v);
|
|
2569
|
+
} : void 0,
|
|
2570
|
+
dueExtra: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2571
|
+
mov.paidAmount > 0 && !isPaid && ` \xB7 ${t("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
|
|
2572
|
+
mov.paymentDate && ` \xB7 ${t("financial.invoice.paidOn")} ${mov.paymentDate}`
|
|
2573
|
+
] }),
|
|
2574
|
+
onClick: hasPaidDetails ? () => setReceiptBill(mov) : void 0,
|
|
2575
|
+
source: canChooseSource ? /* @__PURE__ */ jsx(
|
|
2576
|
+
InstallmentSourceFields,
|
|
2577
|
+
{
|
|
2578
|
+
direction,
|
|
2579
|
+
value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
|
|
2580
|
+
onChange: (next) => {
|
|
2581
|
+
void patchSource(mov, {
|
|
2582
|
+
payMethodTypeId: next.payMethodTypeId ?? null,
|
|
2583
|
+
payAccountId: next.payAccountId ?? null
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
) : void 0,
|
|
2588
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2589
|
+
paymentSummary && /* @__PURE__ */ jsx(
|
|
2590
|
+
Button,
|
|
2591
|
+
{
|
|
2592
|
+
variant: "bare",
|
|
2593
|
+
size: "bare",
|
|
2594
|
+
type: "button",
|
|
2595
|
+
onClick: () => {
|
|
2596
|
+
if (hasPaidDetails) setReceiptBill(mov);
|
|
2597
|
+
},
|
|
2598
|
+
title: paymentSummary,
|
|
2599
|
+
"aria-label": paymentSummary,
|
|
2600
|
+
className: "font-normal inline-flex shrink-0 items-center rounded-md px-1 py-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
2601
|
+
children: howCard ? /* @__PURE__ */ jsx(CardVisual, { lastFour: howCard.lastFour ?? void 0, color: howCard.color ?? void 0, brandId: howCard.cardBrandId ?? void 0 }) : HowIcon ? /* @__PURE__ */ jsx(HowIcon, { className: "h-4 w-4", "aria-hidden": true }) : null
|
|
2602
|
+
}
|
|
2603
|
+
),
|
|
2604
|
+
/* @__PURE__ */ jsx(
|
|
2605
|
+
InstallmentReceiptButton,
|
|
2606
|
+
{
|
|
2607
|
+
title: "Anexos",
|
|
2608
|
+
attached: (isPaid || isPartial) && attachments.length > 0,
|
|
2609
|
+
count: isPaid || isPartial ? attachments.length : 0,
|
|
2610
|
+
onClick: () => setAttachmentsOpen(true)
|
|
2611
|
+
}
|
|
2612
|
+
),
|
|
2613
|
+
!isPaid && !isPartial && ativa && /* @__PURE__ */ jsxs(
|
|
2614
|
+
InstallmentPayButton,
|
|
2615
|
+
{
|
|
2616
|
+
onClick: () => setPayingMovement(mov),
|
|
2617
|
+
children: [
|
|
2618
|
+
t(direction === "credit" ? "financial.invoice.receive" : "financial.invoice.pay"),
|
|
2619
|
+
" ",
|
|
2620
|
+
remaining < mov.amount ? formatCurrency(remaining, currency) : ""
|
|
2621
|
+
]
|
|
2622
|
+
}
|
|
2623
|
+
),
|
|
2624
|
+
isPaid && reversiblePayments(instPayments).length > 0 && ativa && /* @__PURE__ */ jsx(InstallmentActionButton, { icon: Undo2, onClick: () => {
|
|
2625
|
+
setReverseKeep(/* @__PURE__ */ new Set());
|
|
2626
|
+
setReversing(reversiblePayments(instPayments));
|
|
2627
|
+
}, children: t("financial.invoice.reverse") }),
|
|
2628
|
+
isPartial && ativa && /* @__PURE__ */ jsxs(InstallmentPayButton, { variant: "outline", onClick: () => setPayingMovement(mov), children: [
|
|
2629
|
+
t(direction === "credit" ? "financial.invoice.receive" : "financial.invoice.pay"),
|
|
2630
|
+
" ",
|
|
2631
|
+
formatCurrency(remaining, currency)
|
|
2632
|
+
] })
|
|
2633
|
+
] })
|
|
2634
|
+
},
|
|
2635
|
+
mov.id
|
|
2636
|
+
);
|
|
2637
|
+
}) }),
|
|
2638
|
+
cancelledRows.length > 0 && /* @__PURE__ */ jsx(
|
|
2639
|
+
Button,
|
|
2640
|
+
{
|
|
2641
|
+
variant: "bare",
|
|
2642
|
+
size: "bare",
|
|
2643
|
+
type: "button",
|
|
2644
|
+
onClick: () => setShowCancelledRows((v) => !v),
|
|
2645
|
+
className: "font-normal whitespace-normal justify-start w-full border-t px-4 py-2 text-left text-xs text-muted-foreground transition-colors hover:bg-muted/30 hover:text-foreground",
|
|
2646
|
+
children: showCancelledRows ? "Ocultar parcelas canceladas" : cancelledRows.length === 1 ? "Ver 1 parcela cancelada" : `Ver ${cancelledRows.length} parcelas canceladas`
|
|
2647
|
+
}
|
|
2648
|
+
),
|
|
2649
|
+
settlements.unattributed.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t bg-warning-soft/40 px-4 py-2.5", children: [
|
|
2650
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground", children: t("financial.invoice.unlinkedPayments") }),
|
|
2651
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-[10px] text-muted-foreground", children: t("financial.invoice.unlinkedPaymentsDesc") }),
|
|
2652
|
+
settlements.unattributed.map((p) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
|
|
2653
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
|
|
2654
|
+
p.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2655
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2656
|
+
/* @__PURE__ */ jsx("span", { children: p.paymentDate })
|
|
2657
|
+
] })
|
|
2658
|
+
] }, p.id))
|
|
2659
|
+
] })
|
|
2660
|
+
] })
|
|
2661
|
+
}
|
|
2662
|
+
)
|
|
2663
|
+
),
|
|
2664
|
+
extraSections: sideBlocks
|
|
2665
|
+
}
|
|
2666
|
+
),
|
|
2667
|
+
attachmentsOpen && /* @__PURE__ */ jsx(
|
|
2668
|
+
InvoiceAttachmentsModal,
|
|
2669
|
+
{
|
|
2670
|
+
invoiceId: invoice.id,
|
|
2671
|
+
onClose: () => setAttachmentsOpen(false),
|
|
2672
|
+
onChanged: () => {
|
|
2673
|
+
void loadData();
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
),
|
|
2677
|
+
receiptBill && /* @__PURE__ */ jsx(
|
|
2678
|
+
PaymentReceiptModal,
|
|
2679
|
+
{
|
|
2680
|
+
payments: settlements.byBillId.get(receiptBill.id) ?? [],
|
|
2681
|
+
howCtx,
|
|
2682
|
+
counterpartyName: invoice.contactName,
|
|
2683
|
+
isReceivable: invoice.direction === "credit",
|
|
2684
|
+
onReverse: invoice.status !== "cancelled" ? (p) => {
|
|
2685
|
+
setReceiptBill(null);
|
|
2686
|
+
setReverseKeep(/* @__PURE__ */ new Set());
|
|
2687
|
+
setReversing([p]);
|
|
2688
|
+
} : void 0,
|
|
2689
|
+
onClose: () => setReceiptBill(null)
|
|
2690
|
+
}
|
|
2691
|
+
),
|
|
2692
|
+
reversing && (() => {
|
|
2693
|
+
const chosen = reversing.filter((p) => !reverseKeep.has(p.id));
|
|
2694
|
+
const total = chosen.reduce((sum, p) => sum + (Number(p.amount) || 0), 0);
|
|
2695
|
+
const close = () => {
|
|
2696
|
+
setReversing(null);
|
|
2697
|
+
setReverseReason("");
|
|
2698
|
+
setReverseKeep(/* @__PURE__ */ new Set());
|
|
2699
|
+
};
|
|
2700
|
+
return /* @__PURE__ */ jsxs(
|
|
2701
|
+
ModalTask,
|
|
2702
|
+
{
|
|
2703
|
+
open: true,
|
|
2704
|
+
title: t("financial.invoice.reverseTitle"),
|
|
2705
|
+
hint: t("financial.invoice.reverseHint", { count: chosen.length, total: formatCurrency(total, currency) }),
|
|
2706
|
+
width: "max-w-md",
|
|
2707
|
+
onClose: close,
|
|
2708
|
+
children: [
|
|
2709
|
+
/* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 space-y-4 overflow-y-auto px-5 py-4", children: [
|
|
2710
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2711
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: t("financial.invoice.reversePayments") }),
|
|
2712
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", children: reversing.map((p) => {
|
|
2713
|
+
const on = !reverseKeep.has(p.id);
|
|
2714
|
+
const account = p.bankAccountId ? accountsById.get(p.bankAccountId)?.name : void 0;
|
|
2715
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", { className: "flex min-h-11 cursor-pointer items-center gap-3 px-3 py-2 text-sm", children: [
|
|
2716
|
+
/* @__PURE__ */ jsx(
|
|
2717
|
+
Checkbox,
|
|
2718
|
+
{
|
|
2719
|
+
checked: on,
|
|
2720
|
+
disabled: reversing.length === 1,
|
|
2721
|
+
onChange: () => setReverseKeep((cur) => {
|
|
2722
|
+
const next = new Set(cur);
|
|
2723
|
+
if (next.has(p.id)) next.delete(p.id);
|
|
2724
|
+
else next.add(p.id);
|
|
2725
|
+
return next;
|
|
2726
|
+
})
|
|
2727
|
+
}
|
|
2728
|
+
),
|
|
2729
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 tabular-nums font-medium", children: formatCurrency(Number(p.amount) || 0, currency) }),
|
|
2730
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
2731
|
+
p.paymentDate ? p.paymentDate.slice(0, 10).split("-").reverse().join("/") : "",
|
|
2732
|
+
account ? ` \xB7 ${account}` : ""
|
|
2733
|
+
] })
|
|
2734
|
+
] }) }, p.id);
|
|
2735
|
+
}) }),
|
|
2736
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: t("financial.invoice.reverseExplain") })
|
|
2737
|
+
] }),
|
|
2738
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2739
|
+
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-medium uppercase text-muted-foreground", children: t("financial.invoice.reverseReason") }),
|
|
2740
|
+
/* @__PURE__ */ jsx(
|
|
2741
|
+
Textarea,
|
|
2742
|
+
{
|
|
2743
|
+
rows: 3,
|
|
2744
|
+
autoFocus: true,
|
|
2745
|
+
value: reverseReason,
|
|
2746
|
+
onChange: (e) => setReverseReason(e.target.value),
|
|
2747
|
+
className: "mt-1"
|
|
2748
|
+
}
|
|
2749
|
+
)
|
|
2750
|
+
] })
|
|
2751
|
+
] }),
|
|
2752
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 justify-end gap-2 border-t px-5 py-3", children: [
|
|
2753
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: close, children: t("common.cancel") }),
|
|
2754
|
+
/* @__PURE__ */ jsx(
|
|
2755
|
+
Button,
|
|
2756
|
+
{
|
|
2757
|
+
variant: "destructive",
|
|
2758
|
+
size: "sm",
|
|
2759
|
+
disabled: !reverseReason.trim() || chosen.length === 0,
|
|
2760
|
+
onClick: async () => {
|
|
2761
|
+
try {
|
|
2762
|
+
await reverseMovements(chosen.map((p) => p.id), reverseReason.trim());
|
|
2763
|
+
} catch {
|
|
2764
|
+
}
|
|
2765
|
+
close();
|
|
2766
|
+
loadData();
|
|
2767
|
+
},
|
|
2768
|
+
children: t("financial.invoice.reverseConfirm", { total: formatCurrency(total, currency) })
|
|
2769
|
+
}
|
|
2770
|
+
)
|
|
2771
|
+
] })
|
|
2772
|
+
]
|
|
2773
|
+
}
|
|
2774
|
+
);
|
|
2775
|
+
})(),
|
|
2776
|
+
payingMovement && /* @__PURE__ */ jsx(CurrencyScope, { code: invoice?.currency, children: /* @__PURE__ */ jsx(
|
|
2777
|
+
PaymentModal,
|
|
2778
|
+
{
|
|
2779
|
+
movement: payingMovement,
|
|
2780
|
+
receiptUrl: pendingReceipt,
|
|
2781
|
+
onClose: () => {
|
|
2782
|
+
setPayingMovement(null);
|
|
2783
|
+
setPendingReceipt(void 0);
|
|
2784
|
+
},
|
|
2785
|
+
onPaid: () => {
|
|
2786
|
+
setPayingMovement(null);
|
|
2787
|
+
setPendingReceipt(void 0);
|
|
2788
|
+
loadData();
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
) }),
|
|
2792
|
+
stockDetailTarget && stockDetails.has(stockDetailTarget.id) && /* @__PURE__ */ jsx(
|
|
2793
|
+
StockEntryDetailsDialog,
|
|
2794
|
+
{
|
|
2795
|
+
title: stockDetailTarget.description,
|
|
2796
|
+
entries: stockDetails.get(stockDetailTarget.id),
|
|
2797
|
+
onClose: () => setStockDetailTarget(null)
|
|
2798
|
+
}
|
|
2799
|
+
),
|
|
2800
|
+
receiveTarget && /* @__PURE__ */ jsx(
|
|
2801
|
+
ReceiveStockDialog,
|
|
2802
|
+
{
|
|
2803
|
+
item: receiveTarget,
|
|
2804
|
+
invoiceCurrency: invoice.currency,
|
|
2805
|
+
entered: stockDetails.get(receiveTarget.id),
|
|
2806
|
+
onClose: () => setReceiveTarget(null),
|
|
2807
|
+
onReceived: () => {
|
|
2808
|
+
setReceiveTarget(null);
|
|
2809
|
+
setStockVersion((v) => v + 1);
|
|
2810
|
+
void loadData();
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
),
|
|
2814
|
+
cancelTarget !== null && /* @__PURE__ */ jsx(CurrencyScope, { code: invoice?.currency, children: /* @__PURE__ */ jsx(
|
|
2815
|
+
CancelInvoiceDialog,
|
|
2816
|
+
{
|
|
2817
|
+
invoiceId: invoice.id,
|
|
2818
|
+
counterpartyName: invoice.contactName,
|
|
2819
|
+
totalAmount: invoice.totalAmount,
|
|
2820
|
+
items: liveItems,
|
|
2821
|
+
preselectItemId: cancelTarget || void 0,
|
|
2822
|
+
onClose: () => setCancelTarget(null),
|
|
2823
|
+
onCancelled: () => {
|
|
2824
|
+
setCancelTarget(null);
|
|
2825
|
+
setMenuOpen(false);
|
|
2826
|
+
void loadData();
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
) }),
|
|
2830
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
2831
|
+
@keyframes field-slide-in {
|
|
2832
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
2833
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2834
|
+
}
|
|
2835
|
+
` })
|
|
2836
|
+
] });
|
|
2837
|
+
}
|
|
2838
|
+
function useWritableUnits(fallback = []) {
|
|
2839
|
+
const unitMode = useUnitMode();
|
|
2840
|
+
const activeUnitId = useActiveUnitId() ?? void 0;
|
|
2841
|
+
const reachable = useUnitFilter().options;
|
|
2842
|
+
const choices = !unitMode ? fallback : activeUnitId ? reachable.filter((u) => u.id === activeUnitId) : reachable;
|
|
2843
|
+
return { unitMode, activeUnitId, choices };
|
|
2844
|
+
}
|
|
2845
|
+
function UnitField({ choices, value, onChange, label }) {
|
|
2846
|
+
const t = useTranslation();
|
|
2847
|
+
const can = usePermissionOptional();
|
|
2848
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
2849
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: label ?? t("financial.invoiceForm.unit") }),
|
|
2850
|
+
/* @__PURE__ */ jsx(
|
|
2851
|
+
ChoiceSelect,
|
|
2852
|
+
{
|
|
2853
|
+
className: "mt-1",
|
|
2854
|
+
icon: Building2,
|
|
2855
|
+
"aria-label": label ?? t("financial.invoiceForm.unit"),
|
|
2856
|
+
options: choices.map((c) => ({ value: c.id, label: c.name })),
|
|
2857
|
+
value: value || void 0,
|
|
2858
|
+
onChange: (v) => onChange(v ?? ""),
|
|
2859
|
+
allLabel: "Todas / sem unidade",
|
|
2860
|
+
searchable: true,
|
|
2861
|
+
searchPlaceholder: t("financial.invoiceForm.searchUnit"),
|
|
2862
|
+
manage: can("units", "manage") ? { label: "Gerenciar unidades", onSelect: () => adminNavigateTo("/settings/locations") } : void 0
|
|
2863
|
+
}
|
|
2864
|
+
)
|
|
2865
|
+
] });
|
|
2866
|
+
}
|
|
2867
|
+
var KIND_LABELS = {
|
|
2868
|
+
supplier: "Fornecedor",
|
|
2869
|
+
customer: "Cliente",
|
|
2870
|
+
staff: "Profissional"
|
|
2871
|
+
};
|
|
2872
|
+
function createFinancialCounterpartyLookup(direction) {
|
|
2873
|
+
const kinds = direction === "debit" ? ["supplier", "staff", "customer"] : ["customer", "supplier", "staff"];
|
|
2874
|
+
return createArchetypeLookup({
|
|
2875
|
+
archetype: "person",
|
|
2876
|
+
kind: kinds,
|
|
2877
|
+
createKinds: kinds,
|
|
2878
|
+
kindLabels: KIND_LABELS
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
function FinancialCounterpartySearchSelect({
|
|
2882
|
+
lookup,
|
|
2883
|
+
value,
|
|
2884
|
+
displayValue,
|
|
2885
|
+
label,
|
|
2886
|
+
required,
|
|
2887
|
+
onChange,
|
|
2888
|
+
onCreate,
|
|
2889
|
+
onEdit,
|
|
2890
|
+
disabled
|
|
2891
|
+
}) {
|
|
2892
|
+
const search = React13.useCallback(async (query) => {
|
|
2893
|
+
const results = await lookup.search(query);
|
|
2894
|
+
return results.map((result) => ({
|
|
2895
|
+
id: result.id,
|
|
2896
|
+
label: result.label,
|
|
2897
|
+
subtitle: result.subtitle,
|
|
2898
|
+
group: result.group,
|
|
2899
|
+
data: result
|
|
2900
|
+
}));
|
|
2901
|
+
}, [lookup]);
|
|
2902
|
+
return /* @__PURE__ */ jsx(
|
|
2903
|
+
SearchSelect,
|
|
2904
|
+
{
|
|
2905
|
+
value,
|
|
2906
|
+
displayValue,
|
|
2907
|
+
label,
|
|
2908
|
+
required,
|
|
2909
|
+
disabled,
|
|
2910
|
+
onChange,
|
|
2911
|
+
onSearch: search,
|
|
2912
|
+
minChars: 2,
|
|
2913
|
+
minCharsLabel: "Digite pelo menos 2 caracteres para buscar.",
|
|
2914
|
+
noResultsLabel: "Nenhum cadastro encontrado.",
|
|
2915
|
+
searchingLabel: "Buscando cadastros...",
|
|
2916
|
+
loadOnOpen: false,
|
|
2917
|
+
groupLayout: "columns",
|
|
2918
|
+
createLabel: "Cadastrar",
|
|
2919
|
+
clearLabel: "Limpar",
|
|
2920
|
+
onCreate,
|
|
2921
|
+
onEdit,
|
|
2922
|
+
editLabel: "Editar",
|
|
2923
|
+
placeholder: "Digite nome, telefone ou documento..."
|
|
2924
|
+
}
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
// src/views/focusItem.ts
|
|
2929
|
+
var CHAVE = "fayz:financial:focus-item";
|
|
2930
|
+
var VALIDADE_MS = 1e4;
|
|
2931
|
+
function marcarItemParaAbrir(invoiceId, index) {
|
|
2932
|
+
try {
|
|
2933
|
+
sessionStorage.setItem(CHAVE, `${Date.now()}:${index}:${invoiceId}`);
|
|
2934
|
+
} catch {
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
function consumirItemParaAbrir(invoiceId) {
|
|
2938
|
+
try {
|
|
2939
|
+
const bruto = sessionStorage.getItem(CHAVE);
|
|
2940
|
+
if (!bruto) return void 0;
|
|
2941
|
+
const [quando, posicao, ...resto] = bruto.split(":");
|
|
2942
|
+
if (resto.join(":") !== invoiceId) return void 0;
|
|
2943
|
+
if (Date.now() - Number(quando) > VALIDADE_MS) return void 0;
|
|
2944
|
+
const n = Number(posicao);
|
|
2945
|
+
return Number.isFinite(n) ? n : void 0;
|
|
2946
|
+
} catch {
|
|
2947
|
+
return void 0;
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
function useStockedItemTypes(itemTypes, entityLookups) {
|
|
2951
|
+
const [empty, setEmpty] = React13__default.useState(null);
|
|
2952
|
+
const backed = itemTypes.filter((type) => entityLookups[type.value]?.count).map((type) => type.value).sort().join(",");
|
|
2953
|
+
React13__default.useEffect(() => {
|
|
2954
|
+
const kinds = backed ? backed.split(",") : [];
|
|
2955
|
+
if (!kinds.length) {
|
|
2956
|
+
setEmpty(null);
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
let cancelled = false;
|
|
2960
|
+
void Promise.all(kinds.map(async (kind) => {
|
|
2961
|
+
try {
|
|
2962
|
+
return { kind, total: await entityLookups[kind].count() };
|
|
2963
|
+
} catch {
|
|
2964
|
+
return { kind, total: 1 };
|
|
2965
|
+
}
|
|
2966
|
+
})).then((rows) => {
|
|
2967
|
+
if (cancelled) return;
|
|
2968
|
+
setEmpty(new Set(rows.filter((r) => r.total === 0).map((r) => r.kind)));
|
|
2969
|
+
});
|
|
2970
|
+
return () => {
|
|
2971
|
+
cancelled = true;
|
|
2972
|
+
};
|
|
2973
|
+
}, [backed]);
|
|
2974
|
+
return React13__default.useMemo(
|
|
2975
|
+
() => empty?.size ? itemTypes.filter((type) => !empty.has(type.value)) : itemTypes,
|
|
2976
|
+
[itemTypes, empty]
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
function useReceiptPicker(onPick, onPickDocument) {
|
|
2980
|
+
const t = useTranslation();
|
|
2981
|
+
const ref = useRef(null);
|
|
2982
|
+
const input = /* @__PURE__ */ jsx(
|
|
2983
|
+
"input",
|
|
2984
|
+
{
|
|
2985
|
+
ref,
|
|
2986
|
+
type: "file",
|
|
2987
|
+
accept: onPickDocument ? "image/*,application/pdf" : "image/*",
|
|
2988
|
+
capture: onPickDocument ? void 0 : "environment",
|
|
2989
|
+
className: "hidden",
|
|
2990
|
+
onChange: (e) => {
|
|
2991
|
+
const file = e.target.files?.[0];
|
|
2992
|
+
e.target.value = "";
|
|
2993
|
+
if (!file) return;
|
|
2994
|
+
if (onPickDocument && file.type === "application/pdf") {
|
|
2995
|
+
onPickDocument(file);
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
if (!file.type.startsWith("image/")) {
|
|
2999
|
+
toast.error(t("financial.quickTx.receiptInvalid"));
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
const reader = new FileReader();
|
|
3003
|
+
reader.onload = () => {
|
|
3004
|
+
if (typeof reader.result === "string") onPick(reader.result);
|
|
3005
|
+
};
|
|
3006
|
+
reader.onerror = () => toast.error(t("financial.quickTx.receiptInvalid"));
|
|
3007
|
+
reader.readAsDataURL(file);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
);
|
|
3011
|
+
return { open: () => ref.current?.click(), input };
|
|
3012
|
+
}
|
|
3013
|
+
var formId = 1;
|
|
3014
|
+
function fid() {
|
|
3015
|
+
return `f${formId++}`;
|
|
3016
|
+
}
|
|
3017
|
+
function sourceMeta(inst) {
|
|
3018
|
+
return {
|
|
3019
|
+
payMethodTypeId: inst.payMethodTypeId || null,
|
|
3020
|
+
payMethodId: inst.payMethodId || null,
|
|
3021
|
+
payAccountId: inst.payAccountId || null
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
function filterByCodeOrName(rows, query) {
|
|
3025
|
+
const q = query.trim().toLowerCase();
|
|
3026
|
+
if (!q) return rows;
|
|
3027
|
+
return rows.filter((r) => `${r.code ?? ""} ${r.name}`.toLowerCase().includes(q));
|
|
3028
|
+
}
|
|
3029
|
+
function itemsKey(list) {
|
|
3030
|
+
return JSON.stringify(list.map((it) => [
|
|
3031
|
+
it.itemKind ?? "",
|
|
3032
|
+
it.referenceId ?? "",
|
|
3033
|
+
it.description ?? "",
|
|
3034
|
+
it.quantity ?? 0,
|
|
3035
|
+
it.unitPrice ?? 0,
|
|
3036
|
+
it.discount ?? 0,
|
|
3037
|
+
it.surcharge ?? 0,
|
|
3038
|
+
it.accountId ?? "",
|
|
3039
|
+
it.costCenterId ?? ""
|
|
3040
|
+
]));
|
|
3041
|
+
}
|
|
3042
|
+
function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fromCardId, embedded, onChanged }) {
|
|
3043
|
+
const t = useTranslation();
|
|
3044
|
+
const config = useFinancialConfig();
|
|
3045
|
+
const provider = useFinancialProvider();
|
|
3046
|
+
const [pictures, setPictures] = useState({});
|
|
3047
|
+
const asked = useRef(/* @__PURE__ */ new Set());
|
|
3048
|
+
const { itemTypes: itemTypesRaw = [], locations = [] } = config;
|
|
3049
|
+
const [documentCurrency, setDocumentCurrency] = useState();
|
|
3050
|
+
const [stockNotes, setStockNotes] = useState(/* @__PURE__ */ new Map());
|
|
3051
|
+
const currency = useCurrency(config.currency, documentCurrency);
|
|
3052
|
+
const houseCurrency = useCurrency(config.currency).code;
|
|
3053
|
+
const [currencyChoices, setCurrencyChoices] = useState([]);
|
|
3054
|
+
useEffect(() => {
|
|
3055
|
+
let alive = true;
|
|
3056
|
+
provider.listCurrencies?.().then((found) => {
|
|
3057
|
+
if (alive) setCurrencyChoices([.../* @__PURE__ */ new Set([houseCurrency, ...found])]);
|
|
3058
|
+
}).catch(() => {
|
|
3059
|
+
});
|
|
3060
|
+
return () => {
|
|
3061
|
+
alive = false;
|
|
3062
|
+
};
|
|
3063
|
+
}, [provider, houseCurrency]);
|
|
3064
|
+
const { unitMode, activeUnitId, choices: unitChoices } = useWritableUnits(locations);
|
|
3065
|
+
const reachableUnits = useUnitFilter().options;
|
|
3066
|
+
const showUnitField = unitMode ? reachableUnits.length > 1 : unitChoices.length > 1;
|
|
3067
|
+
const [giftCardsEnabled, setGiftCardsEnabled] = useState(false);
|
|
3068
|
+
useEffect(() => {
|
|
3069
|
+
let cancelled2 = false;
|
|
3070
|
+
if (direction !== "credit" || !provider.giftCardsEnabled) {
|
|
3071
|
+
setGiftCardsEnabled(false);
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
void provider.giftCardsEnabled().then((enabled) => {
|
|
3075
|
+
if (!cancelled2) setGiftCardsEnabled(enabled);
|
|
3076
|
+
}).catch(() => {
|
|
3077
|
+
if (!cancelled2) setGiftCardsEnabled(false);
|
|
3078
|
+
});
|
|
3079
|
+
return () => {
|
|
3080
|
+
cancelled2 = true;
|
|
3081
|
+
};
|
|
3082
|
+
}, [direction, provider]);
|
|
3083
|
+
const giftCardLookup = useMemo(() => {
|
|
3084
|
+
if (!giftCardsEnabled || !provider.findGiftCardByCode) return null;
|
|
3085
|
+
const asOption = (card) => ({
|
|
3086
|
+
id: card.id,
|
|
3087
|
+
label: `Gift Card ${card.code.replace(/(.{4})(?=.)/g, "$1 ")}`,
|
|
3088
|
+
subtitle: card.expiresAt ? `Emitido \xB7 v\xE1lido at\xE9 ${new Date(card.expiresAt).toLocaleDateString("pt-BR")}` : "Emitido \xB7 sem validade",
|
|
3089
|
+
icon: "Gift",
|
|
3090
|
+
price: card.faceValue,
|
|
3091
|
+
data: { price: card.faceValue, code: card.code, state: card.state }
|
|
3092
|
+
});
|
|
3093
|
+
const resolve = async (raw) => {
|
|
3094
|
+
const card = await provider.findGiftCardByCode(raw);
|
|
3095
|
+
if (!card) throw new Error(t("financial.giftCardLine.notFound"));
|
|
3096
|
+
if (card.state === "revoked") throw new Error(t("financial.giftCardLine.revoked"));
|
|
3097
|
+
if (card.state === "expired") throw new Error(t("financial.giftCardLine.expired"));
|
|
3098
|
+
if (card.state !== "issued") throw new Error(t("financial.giftCardLine.alreadySold"));
|
|
3099
|
+
return asOption(card);
|
|
3100
|
+
};
|
|
3101
|
+
return {
|
|
3102
|
+
async search(query) {
|
|
3103
|
+
try {
|
|
3104
|
+
return [await resolve(query)];
|
|
3105
|
+
} catch {
|
|
3106
|
+
return [];
|
|
3107
|
+
}
|
|
3108
|
+
},
|
|
3109
|
+
async getById() {
|
|
3110
|
+
return null;
|
|
3111
|
+
},
|
|
3112
|
+
byCode: {
|
|
3113
|
+
resolve,
|
|
3114
|
+
label: t("financial.giftCardLine.label"),
|
|
3115
|
+
placeholder: t("financial.giftCardLine.placeholder"),
|
|
3116
|
+
hint: t("financial.giftCardLine.hint"),
|
|
3117
|
+
display: ({ description }) => description.replace(/^Gift Card\s+/, "")
|
|
3118
|
+
}
|
|
3119
|
+
};
|
|
3120
|
+
}, [giftCardsEnabled, provider, t]);
|
|
3121
|
+
const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
|
|
3122
|
+
const translatedItemTypes = React13__default.useMemo(
|
|
3123
|
+
() => itemTypesRaw.map((tp) => {
|
|
3124
|
+
const traduzido = t(`financial.itemType.${tp.value}`);
|
|
3125
|
+
return traduzido === `financial.itemType.${tp.value}` ? tp : { ...tp, label: traduzido };
|
|
3126
|
+
}),
|
|
3127
|
+
[itemTypesRaw, t]
|
|
3128
|
+
);
|
|
3129
|
+
const offeredItemTypes = React13__default.useMemo(
|
|
3130
|
+
() => availableInvoiceItemTypes(
|
|
3131
|
+
giftCardsEnabled && !translatedItemTypes.some((type) => type.value === "gift_card") ? [...translatedItemTypes, { value: "gift_card", label: t("financial.itemType.gift_card"), icon: "Gift" }] : translatedItemTypes,
|
|
3132
|
+
Object.keys(entityLookups),
|
|
3133
|
+
t("financial.itemType.other"),
|
|
3134
|
+
direction
|
|
3135
|
+
),
|
|
3136
|
+
[translatedItemTypes, giftCardsEnabled, entityLookups, t, direction]
|
|
3137
|
+
);
|
|
3138
|
+
const itemTypes = useStockedItemTypes(offeredItemTypes, entityLookups);
|
|
3139
|
+
const hasItemLookups = itemTypes.some((tp) => entityLookups[tp.value]);
|
|
3140
|
+
const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
|
|
3141
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts);
|
|
3142
|
+
const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes);
|
|
3143
|
+
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
|
|
3144
|
+
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
3145
|
+
const payMovement = useFinancialStore((s) => s.payMovement);
|
|
3146
|
+
const createInvoice = useFinancialStore((s) => s.createInvoice);
|
|
3147
|
+
const guardMovements = useLimitGuard("movements_month");
|
|
3148
|
+
const can = usePermissionOptional();
|
|
3149
|
+
const canManageFinancial = can("financial", "manage");
|
|
3150
|
+
const isEdit = !!editId;
|
|
3151
|
+
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
|
|
3152
|
+
const [attachmentCount, setAttachmentCount] = useState(0);
|
|
3153
|
+
const loadAttachmentCount = React13__default.useCallback(() => {
|
|
3154
|
+
if (!editId || !provider.getInvoiceAttachments) return;
|
|
3155
|
+
provider.getInvoiceAttachments(editId).then((list) => setAttachmentCount(list.length)).catch(() => {
|
|
3156
|
+
});
|
|
3157
|
+
}, [editId, provider]);
|
|
3158
|
+
useEffect(() => {
|
|
3159
|
+
loadAttachmentCount();
|
|
3160
|
+
}, [loadAttachmentCount]);
|
|
3161
|
+
useEffect(() => {
|
|
3162
|
+
void fetchBankAccounts();
|
|
3163
|
+
void fetchPaymentMethods();
|
|
3164
|
+
}, []);
|
|
3165
|
+
useEffect(() => {
|
|
3166
|
+
let cancelled2 = false;
|
|
3167
|
+
void Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
|
|
3168
|
+
if (cancelled2) return;
|
|
3169
|
+
setAccounts(accountRows);
|
|
3170
|
+
setCostCenters(costCenterRows);
|
|
3171
|
+
}).catch(() => {
|
|
3172
|
+
});
|
|
3173
|
+
return () => {
|
|
3174
|
+
cancelled2 = true;
|
|
3175
|
+
};
|
|
3176
|
+
}, [provider]);
|
|
3177
|
+
const contactLookup = useMemo(
|
|
3178
|
+
() => createFinancialCounterpartyLookup(direction),
|
|
3179
|
+
[direction]
|
|
3180
|
+
);
|
|
3181
|
+
const [savedItemsKey, setSavedItemsKey] = useState("");
|
|
3182
|
+
const [loaded, setLoaded] = useState(!editId);
|
|
3183
|
+
const [loading, setLoading] = useState(!!editId);
|
|
3184
|
+
const [contactName, setContactName] = useState("");
|
|
3185
|
+
const [contactId, setContactId] = useState();
|
|
3186
|
+
const [counterpartyAccounting, setCounterpartyAccounting] = useState({
|
|
3187
|
+
allowedAccountIds: [],
|
|
3188
|
+
limitAccounts: false,
|
|
3189
|
+
addItemsAutomatically: false
|
|
3190
|
+
});
|
|
3191
|
+
const accountingPolicyRequest = useRef(0);
|
|
3192
|
+
const defaultUnitId = () => {
|
|
3193
|
+
if (!unitMode) return void 0;
|
|
3194
|
+
if (activeUnitId && reachableUnits.some((u) => u.id === activeUnitId)) return activeUnitId;
|
|
3195
|
+
return reachableUnits.length === 1 ? reachableUnits[0].id : void 0;
|
|
3196
|
+
};
|
|
3197
|
+
const [unitId, setUnitId] = useState(() => (isEdit ? void 0 : defaultUnitId()) ?? locations[0]?.id ?? "");
|
|
3198
|
+
useEffect(() => {
|
|
3199
|
+
if (isEdit || unitId) return;
|
|
3200
|
+
const next = defaultUnitId();
|
|
3201
|
+
if (next) setUnitId(next);
|
|
3202
|
+
}, [isEdit, unitMode, unitId, activeUnitId, reachableUnits]);
|
|
3203
|
+
const [invoiceDate, setInvoiceDate] = useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
3204
|
+
const [savedInvoiceDate, setSavedInvoiceDate] = useState("");
|
|
3205
|
+
const [savedContactId, setSavedContactId] = useState();
|
|
3206
|
+
const [fiscalNumber, setFiscalNumber] = useState("");
|
|
3207
|
+
const [observations, setObservations] = useState("");
|
|
3208
|
+
const [tags, setTags] = useState([]);
|
|
3209
|
+
const [savedTags, setSavedTags] = useState("[]");
|
|
3210
|
+
const [items, setItems] = useState([]);
|
|
3211
|
+
const [accounts, setAccounts] = useState([]);
|
|
3212
|
+
const postable = useMemo(() => postableAccounts(accounts), [accounts]);
|
|
3213
|
+
const selectableAccounts = useMemo(() => {
|
|
3214
|
+
if (!counterpartyAccounting.limitAccounts) return postable;
|
|
3215
|
+
const allowed = new Set(counterpartyAccounting.allowedAccountIds);
|
|
3216
|
+
return postable.filter((account) => allowed.has(account.id));
|
|
3217
|
+
}, [postable, counterpartyAccounting]);
|
|
3218
|
+
const [costCenters, setCostCenters] = useState([]);
|
|
3219
|
+
const [expandedItemId, setExpandedItemId] = useState(null);
|
|
3220
|
+
const [addingItem, setAddingItem] = useState(false);
|
|
3221
|
+
const [installments, setInstallments] = useState([]);
|
|
3222
|
+
const [pinnedAmounts, setPinnedAmounts] = useState(/* @__PURE__ */ new Set());
|
|
3223
|
+
const [cancelled, setCancelled] = useState(false);
|
|
3224
|
+
const [savedTotal, setSavedTotal] = useState(null);
|
|
3225
|
+
const [savedMovements, setSavedMovements] = useState([]);
|
|
3226
|
+
const [hasCancelledInstallments, setHasCancelledInstallments] = useState(false);
|
|
3227
|
+
const [payingMovement, setPayingMovement] = useState(null);
|
|
3228
|
+
const [pendingReceipt, setPendingReceipt] = useState();
|
|
3229
|
+
const contactCreate = useLookupCreate(contactLookup, (created) => {
|
|
3230
|
+
setContactId(created.id);
|
|
3231
|
+
setContactName(created.label);
|
|
3232
|
+
setPendingContact(null);
|
|
3233
|
+
});
|
|
3234
|
+
const [pendingContact, setPendingContact] = useState(null);
|
|
3235
|
+
const [receiptTarget, setReceiptTarget] = useState(null);
|
|
3236
|
+
const [pendingDocs, setPendingDocs] = useState([]);
|
|
3237
|
+
const receiptPicker = useReceiptPicker((dataUrl) => {
|
|
3238
|
+
const i = receiptTarget;
|
|
3239
|
+
setReceiptTarget(null);
|
|
3240
|
+
if (i == null) return;
|
|
3241
|
+
if (isEdit) {
|
|
3242
|
+
const saved = savedMovements[i];
|
|
3243
|
+
if (!saved) return;
|
|
3244
|
+
setPendingReceipt(dataUrl);
|
|
3245
|
+
setPayingMovement(saved);
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3248
|
+
setInstallments((prev) => prev.map((inst, idx) => idx === i ? { ...inst, receiptUrl: dataUrl } : inst));
|
|
3249
|
+
}, provider.addInvoiceAttachment ? (file) => {
|
|
3250
|
+
setReceiptTarget(null);
|
|
3251
|
+
setPendingDocs((prev) => [...prev, file]);
|
|
3252
|
+
} : void 0);
|
|
3253
|
+
const [saving, setSaving] = useState(false);
|
|
3254
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
3255
|
+
const [cancelData, setCancelData] = useState(null);
|
|
3256
|
+
const [cancelledLines, setCancelledLines] = useState([]);
|
|
3257
|
+
const menuRef = useRef(null);
|
|
3258
|
+
async function openCancelDialog() {
|
|
3259
|
+
if (!editId) return;
|
|
3260
|
+
try {
|
|
3261
|
+
const [inv, lines] = await Promise.all([provider.getInvoiceById(editId), provider.getInvoiceItems(editId)]);
|
|
3262
|
+
setCancelData({ items: lines.filter((l) => !l.cancelledAt), total: inv?.totalAmount ?? 0, name: inv?.contactName });
|
|
3263
|
+
} catch (err) {
|
|
3264
|
+
toast.error("N\xE3o foi poss\xEDvel abrir o cancelamento", { description: err?.message });
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
useEffect(() => {
|
|
3268
|
+
if (!menuOpen) return;
|
|
3269
|
+
function handleClick(e) {
|
|
3270
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
|
|
3271
|
+
}
|
|
3272
|
+
document.addEventListener("mousedown", handleClick);
|
|
3273
|
+
return () => document.removeEventListener("mousedown", handleClick);
|
|
3274
|
+
}, [menuOpen]);
|
|
3275
|
+
const [recordMeta, setRecordMeta] = useState(null);
|
|
3276
|
+
useEffect(() => {
|
|
3277
|
+
if (!editId || loaded) return;
|
|
3278
|
+
setLoading(true);
|
|
3279
|
+
(async () => {
|
|
3280
|
+
const invoice = await provider.getInvoiceById(editId);
|
|
3281
|
+
if (!invoice) return;
|
|
3282
|
+
setContactName(invoice.contactName ?? "");
|
|
3283
|
+
setContactId(invoice.contactId);
|
|
3284
|
+
if (invoice.contactId) {
|
|
3285
|
+
try {
|
|
3286
|
+
const contact = await contactLookup.getById(invoice.contactId);
|
|
3287
|
+
const policy = counterpartyAccountingPolicy(contact?.data);
|
|
3288
|
+
const refs = [policy.defaultAccountId, ...policy.allowedAccountIds].filter(Boolean);
|
|
3289
|
+
const resolved = await provider.resolveChartAccountRefs?.(refs) ?? {};
|
|
3290
|
+
setCounterpartyAccounting(resolveCounterpartyAccountingPolicy(policy, resolved));
|
|
3291
|
+
} catch {
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
setUnitId(invoice.unitId ?? locations[0]?.id ?? "");
|
|
3295
|
+
setInvoiceDate(invoice.invoiceDate);
|
|
3296
|
+
setSavedInvoiceDate(invoice.invoiceDate);
|
|
3297
|
+
setSavedContactId(invoice.contactId);
|
|
3298
|
+
setCancelled(invoice.status === "cancelled");
|
|
3299
|
+
setSavedTotal(invoice.totalAmount);
|
|
3300
|
+
setRecordMeta({ createdAt: invoice.createdAt, updatedAt: invoice.updatedAt, createdBy: invoice.createdBy });
|
|
3301
|
+
setDocumentCurrency(invoice.currency);
|
|
3302
|
+
setFiscalNumber(String(invoice.metadata?.fiscalNumber ?? ""));
|
|
3303
|
+
setObservations(invoice.observations ?? "");
|
|
3304
|
+
setTags(invoice.tags ?? []);
|
|
3305
|
+
setSavedTags(JSON.stringify(invoice.tags ?? []));
|
|
3306
|
+
const allLines = await provider.getInvoiceItems(editId);
|
|
3307
|
+
const invoiceItems = allLines.filter((it) => !it.cancelledAt);
|
|
3308
|
+
setCancelledLines(allLines.filter((it) => !!it.cancelledAt));
|
|
3309
|
+
setSavedItemsKey(itemsKey(invoiceItems));
|
|
3310
|
+
const idsCarregados = [];
|
|
3311
|
+
setItems(invoiceItems.map((it) => {
|
|
3312
|
+
const _id = fid();
|
|
3313
|
+
idsCarregados.push(_id);
|
|
3314
|
+
return {
|
|
3315
|
+
_id,
|
|
3316
|
+
itemKind: it.itemKind,
|
|
3317
|
+
referenceId: it.referenceId,
|
|
3318
|
+
description: it.description,
|
|
3319
|
+
quantity: it.quantity,
|
|
3320
|
+
unitPrice: it.unitPrice,
|
|
3321
|
+
discount: it.discount,
|
|
3322
|
+
surcharge: it.surcharge,
|
|
3323
|
+
accountId: it.accountId,
|
|
3324
|
+
costCenterId: it.costCenterId,
|
|
3325
|
+
metadata: it.metadata
|
|
3326
|
+
};
|
|
3327
|
+
}));
|
|
3328
|
+
void provider.getInvoiceStockDetails?.(editId).then((rows) => {
|
|
3329
|
+
const byDraft = /* @__PURE__ */ new Map();
|
|
3330
|
+
for (const r of rows) {
|
|
3331
|
+
const at = invoiceItems.findIndex((it) => it.id === r.itemId);
|
|
3332
|
+
const draftId = idsCarregados[at];
|
|
3333
|
+
if (!draftId) continue;
|
|
3334
|
+
byDraft.set(draftId, [...byDraft.get(draftId) ?? [], { variantName: r.variantName, quantity: r.quantity, enteredOn: r.enteredOn }]);
|
|
3335
|
+
}
|
|
3336
|
+
setStockNotes(byDraft);
|
|
3337
|
+
}).catch(() => {
|
|
3338
|
+
});
|
|
3339
|
+
const aAbrir = consumirItemParaAbrir(editId);
|
|
3340
|
+
if (aAbrir !== void 0 && idsCarregados[aAbrir]) setExpandedItemId(idsCarregados[aAbrir]);
|
|
3341
|
+
const movements = await provider.getMovements({ invoiceId: editId });
|
|
3342
|
+
const billMovements = movements.data.filter((m) => m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0));
|
|
3343
|
+
const liveMovements = billMovements.filter((m) => m.status !== "cancelled");
|
|
3344
|
+
const invoiceMovements = liveMovements.length > 0 ? liveMovements : billMovements;
|
|
3345
|
+
setHasCancelledInstallments(liveMovements.length > 0 && liveMovements.length < billMovements.length);
|
|
3346
|
+
setSavedMovements(invoiceMovements);
|
|
3347
|
+
setInstallments(invoiceMovements.map((m) => {
|
|
3348
|
+
const meta = m.metadata ?? {};
|
|
3349
|
+
const str = (v) => typeof v === "string" && v ? v : void 0;
|
|
3350
|
+
return {
|
|
3351
|
+
_id: fid(),
|
|
3352
|
+
amount: m.amount,
|
|
3353
|
+
dueDate: m.dueDate,
|
|
3354
|
+
payMethodTypeId: str(meta.payMethodTypeId),
|
|
3355
|
+
payMethodId: str(meta.payMethodId),
|
|
3356
|
+
payAccountId: str(meta.payAccountId)
|
|
3357
|
+
};
|
|
3358
|
+
}));
|
|
3359
|
+
setLoaded(true);
|
|
3360
|
+
setLoading(false);
|
|
3361
|
+
})();
|
|
3362
|
+
}, [editId]);
|
|
3363
|
+
const totalAmount = items.reduce((sum, it) => {
|
|
3364
|
+
return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
|
|
3365
|
+
}, 0);
|
|
3366
|
+
const itemsDirty = isEdit && loaded && itemsKey(items) !== savedItemsKey;
|
|
3367
|
+
const scheduleTotal = isEdit && savedMovements.length > 0 && !itemsDirty ? savedTotal ?? Math.round(savedMovements.reduce((sum, m) => sum + m.amount, 0) * 100) / 100 : totalAmount;
|
|
3368
|
+
const prevItemCount = useRef(items.length);
|
|
3369
|
+
useEffect(() => {
|
|
3370
|
+
if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
|
|
3371
|
+
const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
|
|
3372
|
+
if (total > 0) {
|
|
3373
|
+
setInstallments([{ _id: fid(), amount: total, dueDate: invoiceDate }]);
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
prevItemCount.current = items.length;
|
|
3377
|
+
}, [items.length]);
|
|
3378
|
+
const somaParcelas = Math.round(installments.reduce((s, i) => s + i.amount, 0) * 100);
|
|
3379
|
+
useEffect(() => {
|
|
3380
|
+
if (installments.length === 0) return;
|
|
3381
|
+
if (scheduleTotal <= 0) {
|
|
3382
|
+
if (!isEdit) setInstallments([]);
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
if (somaParcelas === Math.round(scheduleTotal * 100)) return;
|
|
3386
|
+
const count = installments.length;
|
|
3387
|
+
const perInstallment = Math.floor(scheduleTotal * 100 / count) / 100;
|
|
3388
|
+
const remainder = Math.round((scheduleTotal - perInstallment * count) * 100) / 100;
|
|
3389
|
+
setPinnedAmounts(/* @__PURE__ */ new Set());
|
|
3390
|
+
setInstallments((prev) => prev.map((inst, i) => ({
|
|
3391
|
+
...inst,
|
|
3392
|
+
amount: i === 0 ? perInstallment + remainder : perInstallment
|
|
3393
|
+
})));
|
|
3394
|
+
}, [scheduleTotal, somaParcelas, installments.length, isEdit]);
|
|
3395
|
+
useEffect(() => {
|
|
3396
|
+
const ids = items.filter((it) => it.itemKind === "product" && it.referenceId && !asked.current.has(it.referenceId)).map((it) => it.referenceId);
|
|
3397
|
+
if (ids.length === 0 || !provider.getProductPictures) return;
|
|
3398
|
+
ids.forEach((id) => asked.current.add(id));
|
|
3399
|
+
provider.getProductPictures(ids).then((found) => setPictures((prev) => ({ ...prev, ...found }))).catch(() => {
|
|
3400
|
+
});
|
|
3401
|
+
}, [items, provider]);
|
|
3402
|
+
function addItemOfKind(kind) {
|
|
3403
|
+
const newItem = {
|
|
3404
|
+
_id: fid(),
|
|
3405
|
+
itemKind: kind,
|
|
3406
|
+
description: "",
|
|
3407
|
+
quantity: 1,
|
|
3408
|
+
unitPrice: 0,
|
|
3409
|
+
accountId: counterpartyAccounting.defaultAccountId,
|
|
3410
|
+
costCenterId: counterpartyAccounting.defaultCostCenterId
|
|
3411
|
+
};
|
|
3412
|
+
setItems((current) => [...current, newItem]);
|
|
3413
|
+
setExpandedItemId(newItem._id);
|
|
3414
|
+
setAddingItem(false);
|
|
3415
|
+
}
|
|
3416
|
+
function startAddItem() {
|
|
3417
|
+
if (!contactId) {
|
|
3418
|
+
toast.error(t("financial.invoiceForm.contactRequired"));
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
if (hasItemLookups) setAddingItem(true);
|
|
3422
|
+
else addItemOfKind(defaultItemKind);
|
|
3423
|
+
}
|
|
3424
|
+
function updateItem(id, data) {
|
|
3425
|
+
setItems((current) => current.map((it) => it._id === id ? { ...it, ...data } : it));
|
|
3426
|
+
}
|
|
3427
|
+
async function applyItemPatch(item, patch) {
|
|
3428
|
+
const expand = patch.referenceId ? entityLookups[item.itemKind]?.expand : void 0;
|
|
3429
|
+
if (!expand) {
|
|
3430
|
+
updateItem(item._id, patch);
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
let linhas = [];
|
|
3434
|
+
try {
|
|
3435
|
+
linhas = await expand(patch.referenceId);
|
|
3436
|
+
} catch (err) {
|
|
3437
|
+
console.error("[financial] failed to expand package", err);
|
|
3438
|
+
}
|
|
3439
|
+
if (!linhas.length) {
|
|
3440
|
+
updateItem(item._id, patch);
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
const oferecidos = new Set(itemTypes.map((tp) => tp.value));
|
|
3444
|
+
const novas = linhas.map((linha) => ({
|
|
3445
|
+
_id: fid(),
|
|
3446
|
+
itemKind: linha.itemKind && oferecidos.has(linha.itemKind) ? linha.itemKind : item.itemKind,
|
|
3447
|
+
referenceId: linha.referenceId,
|
|
3448
|
+
description: linha.description,
|
|
3449
|
+
quantity: linha.quantity,
|
|
3450
|
+
unitPrice: linha.unitPrice,
|
|
3451
|
+
...linha.discount ? { discount: linha.discount } : {},
|
|
3452
|
+
...linha.surcharge ? { surcharge: linha.surcharge } : {},
|
|
3453
|
+
...linha.metadata ? { metadata: linha.metadata } : {},
|
|
3454
|
+
accountId: item.accountId,
|
|
3455
|
+
costCenterId: item.costCenterId
|
|
3456
|
+
}));
|
|
3457
|
+
setItems((current) => current.flatMap((it) => it._id === item._id ? novas : [it]));
|
|
3458
|
+
setExpandedItemId(null);
|
|
3459
|
+
}
|
|
3460
|
+
function removeItem(id) {
|
|
3461
|
+
setItems((current) => current.filter((it) => it._id !== id));
|
|
3462
|
+
}
|
|
3463
|
+
useEffect(() => {
|
|
3464
|
+
if (isEdit || totalAmount <= 0 || installments.length > 0) return;
|
|
3465
|
+
generateInstallments(1);
|
|
3466
|
+
}, [isEdit, totalAmount, installments.length]);
|
|
3467
|
+
const fromCard = React13__default.useMemo(
|
|
3468
|
+
() => fromCardId ? bankAccounts.find((a) => a.id === fromCardId) : void 0,
|
|
3469
|
+
[fromCardId, bankAccounts]
|
|
3470
|
+
);
|
|
3471
|
+
const cardLast4 = (fromCard?.accountNumber ?? "").replace(/\D/g, "").slice(-4);
|
|
3472
|
+
const nomeJaTemOsDigitos = !!cardLast4 && (fromCard?.name ?? "").replace(/\D/g, "").endsWith(cardLast4);
|
|
3473
|
+
const cardMethodName = React13__default.useMemo(() => {
|
|
3474
|
+
const id = installments[0]?.payMethodTypeId;
|
|
3475
|
+
const escolhida = id ? paymentMethodTypes.find((tp) => tp.id === id) : void 0;
|
|
3476
|
+
return (escolhida ?? paymentMethodTypes.find((tp) => tp.transactionType === "credit_card"))?.name;
|
|
3477
|
+
}, [installments, paymentMethodTypes]);
|
|
3478
|
+
const cardSeeded = useRef(false);
|
|
3479
|
+
useEffect(() => {
|
|
3480
|
+
if (isEdit || !fromCardId || cardSeeded.current) return;
|
|
3481
|
+
if (installments.length === 0) return;
|
|
3482
|
+
const credito = paymentMethodTypes.find((tp) => tp.transactionType === "credit_card");
|
|
3483
|
+
setInstallments((prev) => prev.map((inst, i) => i === 0 ? { ...inst, payAccountId: fromCardId, payMethodTypeId: credito?.id ?? inst.payMethodTypeId } : inst));
|
|
3484
|
+
cardSeeded.current = true;
|
|
3485
|
+
}, [isEdit, fromCardId, installments.length, paymentMethodTypes]);
|
|
3486
|
+
const draftSeeded = useRef(false);
|
|
3487
|
+
useEffect(() => {
|
|
3488
|
+
if (isEdit || draftSeeded.current) return;
|
|
3489
|
+
const draft = consumePayableDraft(direction);
|
|
3490
|
+
if (!draft) return;
|
|
3491
|
+
draftSeeded.current = true;
|
|
3492
|
+
if (draft.date) setInvoiceDate(draft.date);
|
|
3493
|
+
if (draft.fiscalNumber) setFiscalNumber(draft.fiscalNumber);
|
|
3494
|
+
if (draft.notes) setObservations(draft.notes);
|
|
3495
|
+
const counterparty = draft.supplier ?? draft.customer;
|
|
3496
|
+
if (counterparty) {
|
|
3497
|
+
setContactName(counterparty);
|
|
3498
|
+
void (async () => {
|
|
3499
|
+
const hit = (await contactLookup.search(counterparty).catch(() => []))?.find((r) => r.label.trim().toLowerCase() === counterparty.trim().toLowerCase());
|
|
3500
|
+
if (hit) {
|
|
3501
|
+
setContactId(hit.id);
|
|
3502
|
+
setContactName(hit.label);
|
|
3503
|
+
} else setPendingContact(counterparty);
|
|
3504
|
+
})();
|
|
3505
|
+
}
|
|
3506
|
+
setItems(
|
|
3507
|
+
draft.items?.length ? [
|
|
3508
|
+
...draft.items.map((line) => ({
|
|
3509
|
+
_id: fid(),
|
|
3510
|
+
itemKind: "other",
|
|
3511
|
+
description: line.description,
|
|
3512
|
+
quantity: line.quantity,
|
|
3513
|
+
unitPrice: line.unitPrice
|
|
3514
|
+
})),
|
|
3515
|
+
...draft.shipping ? [{ _id: fid(), itemKind: "other", description: "Frete", quantity: 1, unitPrice: draft.shipping }] : []
|
|
3516
|
+
] : [{
|
|
3517
|
+
_id: fid(),
|
|
3518
|
+
itemKind: "other",
|
|
3519
|
+
description: draft.description ?? counterparty ?? "",
|
|
3520
|
+
quantity: 1,
|
|
3521
|
+
unitPrice: draft.amount - (draft.shipping ?? 0)
|
|
3522
|
+
}]
|
|
3523
|
+
);
|
|
3524
|
+
setInstallments([{ _id: fid(), amount: draft.amount, dueDate: draft.date ?? invoiceDate }]);
|
|
3525
|
+
if (draft.receiptUrl) setPendingReceipt(draft.receiptUrl);
|
|
3526
|
+
}, [isEdit]);
|
|
3527
|
+
const receiptAutoOpened = useRef(false);
|
|
3528
|
+
useEffect(() => {
|
|
3529
|
+
if (!focusReceipt || isEdit || receiptAutoOpened.current) return;
|
|
3530
|
+
if (installments.length === 0 || installments[0]?.receiptUrl) return;
|
|
3531
|
+
receiptAutoOpened.current = true;
|
|
3532
|
+
const id = window.setTimeout(() => {
|
|
3533
|
+
setReceiptTarget(0);
|
|
3534
|
+
receiptPicker.open();
|
|
3535
|
+
}, 250);
|
|
3536
|
+
return () => window.clearTimeout(id);
|
|
3537
|
+
}, [focusReceipt, isEdit, installments.length]);
|
|
3538
|
+
function generateInstallments(count) {
|
|
3539
|
+
if (count < 1 || scheduleTotal <= 0) return;
|
|
3540
|
+
const amounts = splitInstallments(scheduleTotal, count);
|
|
3541
|
+
const base = new Date(invoiceDate);
|
|
3542
|
+
const inst = [];
|
|
3543
|
+
for (let i = 0; i < count; i++) {
|
|
3544
|
+
const due = new Date(base);
|
|
3545
|
+
due.setMonth(due.getMonth() + (count === 1 ? 0 : i + 1));
|
|
3546
|
+
inst.push({
|
|
3547
|
+
_id: fid(),
|
|
3548
|
+
amount: amounts[i],
|
|
3549
|
+
dueDate: due.toISOString().slice(0, 10)
|
|
3550
|
+
});
|
|
3551
|
+
}
|
|
3552
|
+
setInstallments(inst);
|
|
3553
|
+
}
|
|
3554
|
+
async function handleSave(opts) {
|
|
3555
|
+
if (!isEdit && !can("financial", "create")) return null;
|
|
3556
|
+
if (!contactId) {
|
|
3557
|
+
toast.error(t("financial.invoiceForm.contactRequired"));
|
|
3558
|
+
return null;
|
|
3559
|
+
}
|
|
3560
|
+
if (items.length === 0) {
|
|
3561
|
+
toast.error(t("common.formIncomplete"));
|
|
3562
|
+
return null;
|
|
3563
|
+
}
|
|
3564
|
+
if (!isEdit && installments.some((i) => i.receiptUrl && (!i.payMethodTypeId || !i.payAccountId))) {
|
|
3565
|
+
toast.error(t("financial.invoiceForm.paidNeedsMethod"));
|
|
3566
|
+
return null;
|
|
3567
|
+
}
|
|
3568
|
+
if (!isEdit && await guardMovements(Math.max(1, installments.length)) === "blocked") return null;
|
|
3569
|
+
if (!isEdit && createdId.current) {
|
|
3570
|
+
if (!opts?.stay) onSaved?.(createdId.current);
|
|
3571
|
+
return createdId.current;
|
|
3572
|
+
}
|
|
3573
|
+
setSaving(true);
|
|
3574
|
+
let savedId = editId ?? null;
|
|
3575
|
+
try {
|
|
3576
|
+
const movInputs = installments.map((inst, i) => ({
|
|
3577
|
+
direction,
|
|
3578
|
+
movementKind: "bill",
|
|
3579
|
+
amount: inst.amount,
|
|
3580
|
+
dueDate: inst.dueDate,
|
|
3581
|
+
installmentNumber: i + 1
|
|
3582
|
+
}));
|
|
3583
|
+
if (movInputs.length === 0) {
|
|
3584
|
+
movInputs.push({
|
|
3585
|
+
direction,
|
|
3586
|
+
movementKind: "bill",
|
|
3587
|
+
amount: totalAmount,
|
|
3588
|
+
dueDate: invoiceDate,
|
|
3589
|
+
installmentNumber: 1
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3592
|
+
installments.forEach((inst, i) => {
|
|
3593
|
+
const extra = {
|
|
3594
|
+
...inst.receiptUrl ? { receiptUrl: inst.receiptUrl } : {},
|
|
3595
|
+
...sourceMeta(inst)
|
|
3596
|
+
};
|
|
3597
|
+
if (Object.keys(extra).length > 0 && movInputs[i]) {
|
|
3598
|
+
movInputs[i] = { ...movInputs[i], metadata: { ...movInputs[i].metadata ?? {}, ...extra } };
|
|
3599
|
+
}
|
|
3600
|
+
});
|
|
3601
|
+
if (isEdit && editId) {
|
|
3602
|
+
if (canEditItems && provider.replaceInvoiceItems && itemsDirty) {
|
|
3603
|
+
await provider.replaceInvoiceItems(editId, items.map(({ _id, ...rest }) => rest));
|
|
3604
|
+
setSavedTotal(Math.round(totalAmount * 100) / 100);
|
|
3605
|
+
}
|
|
3606
|
+
await provider.updateInvoice(editId, {
|
|
3607
|
+
unitId: unitId || void 0,
|
|
3608
|
+
fiscalNumber: fiscalNumber || void 0,
|
|
3609
|
+
observations,
|
|
3610
|
+
// Only when changed: an untouched bill must not write a column an older pool lacks.
|
|
3611
|
+
...JSON.stringify(tags) !== savedTags ? { tags } : {},
|
|
3612
|
+
// 098 abriu a competência enquanto nada foi recebido.
|
|
3613
|
+
...invoiceDate !== savedInvoiceDate ? { invoiceDate } : {},
|
|
3614
|
+
// 110 abriu a CONTRAPARTE, com o mesmo limite: quem digita seis
|
|
3615
|
+
// contas seguidas escolhe o fornecedor da linha de cima na quarta, e
|
|
3616
|
+
// até aqui a correção era cancelar a conta e lançar outra.
|
|
3617
|
+
...contactId !== savedContactId ? { contactId, contactName } : {}
|
|
3618
|
+
});
|
|
3619
|
+
const shapeChanged = itemsDirty || installments.length !== savedMovements.length || installments.some((inst, i) => Math.round(inst.amount * 100) !== Math.round((savedMovements[i]?.amount ?? 0) * 100));
|
|
3620
|
+
if (canReschedule && provider.rescheduleInvoice && shapeChanged) {
|
|
3621
|
+
await provider.rescheduleInvoice(editId, installments.map((inst, i) => ({
|
|
3622
|
+
dueDate: inst.dueDate,
|
|
3623
|
+
amount: inst.amount,
|
|
3624
|
+
// A RPC SUBSTITUI a metadata quando ela vem. O que já estava lá —
|
|
3625
|
+
// o comprovante, acima de tudo — tem de ir junto, ou reparcelar
|
|
3626
|
+
// apagaria a prova de um pagamento.
|
|
3627
|
+
metadata: { ...savedMovements[i]?.metadata ?? {}, ...sourceMeta(inst) }
|
|
3628
|
+
})));
|
|
3629
|
+
} else if (provider.updateInstallment) {
|
|
3630
|
+
await Promise.all(installments.map((inst, i) => {
|
|
3631
|
+
const saved = savedMovements[i];
|
|
3632
|
+
if (!saved || saved.status === "paid") return null;
|
|
3633
|
+
const meta = sourceMeta(inst);
|
|
3634
|
+
const savedMeta = saved.metadata ?? {};
|
|
3635
|
+
const sourceChanged = Object.keys(meta).some((k) => (savedMeta[k] ?? null) !== meta[k]);
|
|
3636
|
+
if (inst.dueDate === saved.dueDate && !sourceChanged) return null;
|
|
3637
|
+
return provider.updateInstallment(saved.id, {
|
|
3638
|
+
...inst.dueDate !== saved.dueDate ? { dueDate: inst.dueDate } : {},
|
|
3639
|
+
...sourceChanged ? { metadata: { ...savedMeta, ...meta } } : {}
|
|
3640
|
+
});
|
|
3641
|
+
}).filter(Boolean));
|
|
3642
|
+
}
|
|
3643
|
+
onSaved?.(editId);
|
|
3644
|
+
} else {
|
|
3645
|
+
const invoice = await createInvoice({
|
|
3646
|
+
direction,
|
|
3647
|
+
invoiceDate,
|
|
3648
|
+
contactId,
|
|
3649
|
+
contactName: contactName || void 0,
|
|
3650
|
+
unitId: unitId || void 0,
|
|
3651
|
+
currency: documentCurrency,
|
|
3652
|
+
tags,
|
|
3653
|
+
fiscalNumber: fiscalNumber || void 0,
|
|
3654
|
+
observations: observations || void 0,
|
|
3655
|
+
items: items.map(({ _id, ...rest }) => rest),
|
|
3656
|
+
installments: movInputs
|
|
3657
|
+
});
|
|
3658
|
+
invalidateLimit("movements_month");
|
|
3659
|
+
const paidOnes = installments.map((inst, i) => ({ inst, number: i + 1 })).filter(({ inst }) => inst.receiptUrl);
|
|
3660
|
+
if (paidOnes.length > 0) {
|
|
3661
|
+
const movements = await provider.getMovements({ direction, status: "pending" });
|
|
3662
|
+
const bills = movements.data.filter((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
|
|
3663
|
+
for (const { inst, number } of paidOnes) {
|
|
3664
|
+
const bill = bills.find((m) => (m.installmentNumber ?? 1) === number);
|
|
3665
|
+
if (!bill) continue;
|
|
3666
|
+
await payMovement({
|
|
3667
|
+
movementId: bill.id,
|
|
3668
|
+
amount: bill.amount,
|
|
3669
|
+
paymentDate: inst.dueDate,
|
|
3670
|
+
paymentMethodTypeId: inst.payMethodTypeId || void 0,
|
|
3671
|
+
bankAccountId: inst.payAccountId || void 0,
|
|
3672
|
+
idempotencyKey: `financial:invoice:${invoice.id}:installment:${bill.id}:receipt`
|
|
3673
|
+
});
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
if (pendingDocs.length > 0 && provider.addInvoiceAttachment) {
|
|
3677
|
+
const failed = [];
|
|
3678
|
+
for (const file of pendingDocs) {
|
|
3679
|
+
await provider.addInvoiceAttachment(invoice.id, file).catch(() => {
|
|
3680
|
+
failed.push(file.name);
|
|
3681
|
+
});
|
|
3682
|
+
}
|
|
3683
|
+
setPendingDocs([]);
|
|
3684
|
+
if (failed.length) toast.error(t("financial.invoiceForm.attachFailed"), { description: failed.join(", ") });
|
|
3685
|
+
}
|
|
3686
|
+
createdId.current = invoice.id;
|
|
3687
|
+
if (!opts?.stay) onSaved?.(invoice.id);
|
|
3688
|
+
savedId = invoice.id;
|
|
3689
|
+
}
|
|
3690
|
+
snapshot.current = JSON.stringify(fields);
|
|
3691
|
+
return savedId;
|
|
3692
|
+
} catch (e) {
|
|
3693
|
+
toast.error(e?.message || t("common.saveFailed"));
|
|
3694
|
+
throw e;
|
|
3695
|
+
} finally {
|
|
3696
|
+
setSaving(false);
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
const [payingNewId, setPayingNewId] = useState(null);
|
|
3700
|
+
const createdId = useRef(null);
|
|
3701
|
+
async function pagarParcela(index) {
|
|
3702
|
+
const comFonte = (mov) => ({
|
|
3703
|
+
...mov,
|
|
3704
|
+
metadata: { ...mov.metadata ?? {}, ...sourceMeta(installments[index]) }
|
|
3705
|
+
});
|
|
3706
|
+
const saved = savedMovements[index];
|
|
3707
|
+
if (saved) {
|
|
3708
|
+
setPayingMovement(comFonte(saved));
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
const id = await handleSave({ stay: true });
|
|
3712
|
+
if (!id) return;
|
|
3713
|
+
const movements = await provider.getMovements({ direction, invoiceId: id });
|
|
3714
|
+
const bill = movements.data.find((m) => m.movementKind === "bill" && (m.installmentNumber ?? 1) === index + 1);
|
|
3715
|
+
if (!bill) {
|
|
3716
|
+
onSaved?.(id);
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
setPayingNewId(id);
|
|
3720
|
+
setPayingMovement(comFonte(bill));
|
|
3721
|
+
}
|
|
3722
|
+
const fields = {
|
|
3723
|
+
contactName,
|
|
3724
|
+
contactId,
|
|
3725
|
+
unitId,
|
|
3726
|
+
invoiceDate,
|
|
3727
|
+
fiscalNumber,
|
|
3728
|
+
observations,
|
|
3729
|
+
tags,
|
|
3730
|
+
items: items.map(({ _id, ...rest }) => rest),
|
|
3731
|
+
installments: installments.map(({ _id, ...rest }) => rest)
|
|
3732
|
+
};
|
|
3733
|
+
const snapshot = useRef(null);
|
|
3734
|
+
useEffect(() => {
|
|
3735
|
+
if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
|
|
3736
|
+
}, [loaded]);
|
|
3737
|
+
const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
|
|
3738
|
+
useSaveBar({
|
|
3739
|
+
dirty,
|
|
3740
|
+
// Criar ou editar: a barra é o caminho de salvar, e sumir porque nada mudou
|
|
3741
|
+
// ainda deixa a tela sem botão. Ela some quando o formulário fecha.
|
|
3742
|
+
//
|
|
3743
|
+
// EMBUTIDO é o contrário: o modal já tem o próprio fechar, então a barra não
|
|
3744
|
+
// é o único caminho de saída — e «Alterações não salvas» sobre um formulário
|
|
3745
|
+
// que ninguém tocou é uma mentira que ensina a ignorar o aviso. Ali ela
|
|
3746
|
+
// aparece quando há o que salvar.
|
|
3747
|
+
alwaysVisible: !embedded,
|
|
3748
|
+
saving,
|
|
3749
|
+
onSave: () => {
|
|
3750
|
+
void handleSave();
|
|
3751
|
+
},
|
|
3752
|
+
onDiscard: () => onSaved?.(),
|
|
3753
|
+
saveLabel: t("financial.invoiceForm.save")
|
|
3754
|
+
});
|
|
3755
|
+
const title = isEdit ? t("financial.invoiceForm.editTitle") : t("financial.invoiceForm.newTitle");
|
|
3756
|
+
const subtitle = isEdit ? t("financial.invoiceForm.editing") : t("financial.invoiceForm.creating");
|
|
3757
|
+
if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, { parentLabel: embedded ? void 0 : direction === "debit" ? t("financial.nav.payables") : t("financial.nav.receivables"), onBack: embedded ? void 0 : () => onSaved?.() });
|
|
3758
|
+
const money = (v) => formatCurrency(v, currency);
|
|
3759
|
+
const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3760
|
+
/* @__PURE__ */ jsx(WidgetSlot, { zone: FINANCIAL_INVOICE_ZONES.headerActions, invoiceId: editId, direction, cancelled }),
|
|
3761
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
|
|
3762
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
|
|
3763
|
+
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(
|
|
3764
|
+
Button,
|
|
3765
|
+
{
|
|
3766
|
+
variant: "bare",
|
|
3767
|
+
size: "bare",
|
|
3768
|
+
onClick: () => {
|
|
3769
|
+
void openCancelDialog();
|
|
3770
|
+
setMenuOpen(false);
|
|
3771
|
+
},
|
|
3772
|
+
className: "font-normal whitespace-normal justify-start flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive transition-colors hover:bg-destructive/10",
|
|
3773
|
+
children: [
|
|
3774
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
3775
|
+
" Cancelar"
|
|
3776
|
+
]
|
|
3777
|
+
}
|
|
3778
|
+
) })
|
|
3779
|
+
] })
|
|
3780
|
+
] }) : null;
|
|
3781
|
+
const paidSoFar = savedMovements.reduce((sum, m) => sum + (m.paidAmount ?? 0), 0);
|
|
3782
|
+
const hasPayments = paidSoFar > 0;
|
|
3783
|
+
const canReschedule = !hasPayments && !hasCancelledInstallments && (!isEdit || !!provider.rescheduleInvoice);
|
|
3784
|
+
const canEditItems = !hasPayments && !hasCancelledInstallments && (!isEdit || !!provider.replaceInvoiceItems);
|
|
3785
|
+
const scheduleSection = (
|
|
3786
|
+
// Tour anchor: the guided setup points here for the installment schedule.
|
|
3787
|
+
/* @__PURE__ */ jsx("div", { ...tourAnchor("financial.installments"), children: /* @__PURE__ */ jsx(OrderFormBlock, { title: t("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3788
|
+
hasPayments ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("financial.invoiceForm.scheduleLocked") }) : hasCancelledInstallments ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("financial.invoiceForm.scheduleHasCancelled") }) : !canReschedule ? null : (
|
|
3789
|
+
// Atalhos de parcelamento — 1x, 2x, 3x, 6x, 12x cobrem quase tudo.
|
|
3790
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
|
|
3791
|
+
Button,
|
|
3792
|
+
{
|
|
3793
|
+
variant: "bare",
|
|
3794
|
+
size: "bare",
|
|
3795
|
+
type: "button",
|
|
3796
|
+
onClick: () => generateInstallments(n),
|
|
3797
|
+
disabled: totalAmount <= 0,
|
|
3798
|
+
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"}`,
|
|
3799
|
+
children: [
|
|
3800
|
+
n,
|
|
3801
|
+
"x"
|
|
3802
|
+
]
|
|
3803
|
+
},
|
|
3804
|
+
n
|
|
3805
|
+
)) })
|
|
3806
|
+
),
|
|
3807
|
+
installments.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("financial.invoiceForm.scheduleSourceHint") }),
|
|
3808
|
+
installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t("financial.invoiceForm.addItemsFirst") : t("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded border", children: installments.map((inst, i) => {
|
|
3809
|
+
const patch = (next) => {
|
|
3810
|
+
const updated = [...installments];
|
|
3811
|
+
updated[i] = { ...inst, ...next };
|
|
3812
|
+
setInstallments(updated);
|
|
3813
|
+
};
|
|
3814
|
+
const saved = savedMovements[i];
|
|
3815
|
+
const isPaid = saved?.status === "paid";
|
|
3816
|
+
const isPartial = saved?.status === "partial";
|
|
3817
|
+
const lockedAmounts = installments.map((_, n) => {
|
|
3818
|
+
const st = savedMovements[n]?.status;
|
|
3819
|
+
return st === "paid" || st === "partial" || st === "cancelled";
|
|
3820
|
+
});
|
|
3821
|
+
const changeAmount = (value) => {
|
|
3822
|
+
const next = rebalanceInstallments({
|
|
3823
|
+
amounts: installments.map((x) => x.amount),
|
|
3824
|
+
index: i,
|
|
3825
|
+
value,
|
|
3826
|
+
total: scheduleTotal,
|
|
3827
|
+
locked: lockedAmounts,
|
|
3828
|
+
pinned: installments.map((x) => pinnedAmounts.has(x._id))
|
|
3829
|
+
});
|
|
3830
|
+
if (next) {
|
|
3831
|
+
setPinnedAmounts((prev) => new Set(prev).add(inst._id));
|
|
3832
|
+
setInstallments(installments.map((x, n) => ({ ...x, amount: next[n] })));
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
const rest = Math.round((inst.amount - value) * 100) / 100;
|
|
3836
|
+
if (installments.length !== 1 || value <= 0 || rest <= 0) return;
|
|
3837
|
+
const due = /* @__PURE__ */ new Date(`${inst.dueDate}T12:00:00`);
|
|
3838
|
+
due.setMonth(due.getMonth() + 1);
|
|
3839
|
+
setPinnedAmounts((prev) => new Set(prev).add(inst._id));
|
|
3840
|
+
setInstallments([
|
|
3841
|
+
{ ...inst, amount: value },
|
|
3842
|
+
{ _id: fid(), amount: rest, dueDate: due.toISOString().slice(0, 10) }
|
|
3843
|
+
]);
|
|
3844
|
+
};
|
|
3845
|
+
const canChangeAmount = canReschedule && !lockedAmounts[i] && (installments.length === 1 || installments.some((_, n) => n !== i && !lockedAmounts[n]));
|
|
3846
|
+
const canAttach = isEdit ? !!saved && !isPaid : true;
|
|
3847
|
+
const semFonte = Math.abs(inst.amount) < 5e-3 && !isPaid && !isPartial;
|
|
3848
|
+
const fonteTravada = isPaid || isPartial;
|
|
3849
|
+
const formaEscolhida = paymentMethodTypes.find((tp) => tp.id === inst.payMethodTypeId);
|
|
3850
|
+
const contaEscolhida = bankAccounts.find((a) => a.id === inst.payAccountId);
|
|
3851
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
3852
|
+
/* @__PURE__ */ jsx(
|
|
3853
|
+
InstallmentRow,
|
|
3854
|
+
{
|
|
3855
|
+
number: i + 1,
|
|
3856
|
+
amount: money(inst.amount),
|
|
3857
|
+
amountValue: inst.amount,
|
|
3858
|
+
onAmountChange: canChangeAmount ? changeAmount : void 0,
|
|
3859
|
+
status: saved?.status,
|
|
3860
|
+
dueDate: inst.dueDate,
|
|
3861
|
+
onDueDateChange: !hasPayments ? (v) => patch({ dueDate: v }) : void 0,
|
|
3862
|
+
source: semFonte ? (
|
|
3863
|
+
// A zero-value line (gift, 100% discount) has no method to ask for.
|
|
3864
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" })
|
|
3865
|
+
) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
|
|
3866
|
+
InstallmentSourceFields,
|
|
3867
|
+
{
|
|
3868
|
+
direction,
|
|
3869
|
+
value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
|
|
3870
|
+
onChange: (next) => patch(next)
|
|
3871
|
+
}
|
|
3872
|
+
),
|
|
3873
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3874
|
+
(canAttach || isEdit) && /* @__PURE__ */ jsx(
|
|
3875
|
+
InstallmentReceiptButton,
|
|
3876
|
+
{
|
|
3877
|
+
attached: !!inst.receiptUrl || isEdit && attachmentCount > 0,
|
|
3878
|
+
count: isEdit ? attachmentCount : void 0,
|
|
3879
|
+
pending: !inst.receiptUrl && !!pendingReceipt,
|
|
3880
|
+
title: !inst.receiptUrl && pendingReceipt ? t("financial.receipt.attachRead") : t("financial.quickTx.receiptCapture"),
|
|
3881
|
+
onClick: () => {
|
|
3882
|
+
if (isEdit && editId) {
|
|
3883
|
+
setAttachmentsOpen(true);
|
|
3884
|
+
return;
|
|
3885
|
+
}
|
|
3886
|
+
if (!inst.receiptUrl && pendingReceipt) {
|
|
3887
|
+
patch({ receiptUrl: pendingReceipt });
|
|
3888
|
+
setPendingReceipt(void 0);
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3891
|
+
setReceiptTarget(i);
|
|
3892
|
+
receiptPicker.open();
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
),
|
|
3896
|
+
!isPaid && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsx(
|
|
3897
|
+
InstallmentPayButton,
|
|
3898
|
+
{
|
|
3899
|
+
disabled: saving,
|
|
3900
|
+
onClick: () => {
|
|
3901
|
+
void pagarParcela(i);
|
|
3902
|
+
},
|
|
3903
|
+
children: t(direction === "credit" ? "financial.invoice.receive" : "financial.invoice.pay")
|
|
3904
|
+
}
|
|
3905
|
+
)
|
|
3906
|
+
] })
|
|
3907
|
+
}
|
|
3908
|
+
),
|
|
3909
|
+
!isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
|
|
3910
|
+
/* @__PURE__ */ jsx(
|
|
3911
|
+
"img",
|
|
3912
|
+
{
|
|
3913
|
+
src: inst.receiptUrl,
|
|
3914
|
+
alt: t("financial.quickTx.receipt"),
|
|
3915
|
+
className: "h-8 w-8 shrink-0 rounded border object-cover"
|
|
3916
|
+
}
|
|
3917
|
+
),
|
|
3918
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[10px] font-medium text-success", children: t("financial.invoiceForm.willBePaid") }),
|
|
3919
|
+
/* @__PURE__ */ jsx(
|
|
3920
|
+
Button,
|
|
3921
|
+
{
|
|
3922
|
+
variant: "bare",
|
|
3923
|
+
size: "bare",
|
|
3924
|
+
type: "button",
|
|
3925
|
+
onClick: () => patch({ receiptUrl: void 0, payMethodTypeId: void 0, payAccountId: void 0 }),
|
|
3926
|
+
"aria-label": t("financial.quickTx.receiptRemove"),
|
|
3927
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
3928
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
3929
|
+
}
|
|
3930
|
+
)
|
|
3931
|
+
] })
|
|
3932
|
+
] }, inst._id);
|
|
3933
|
+
}) }),
|
|
3934
|
+
pendingDocs.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: pendingDocs.map((file, idx) => /* @__PURE__ */ jsxs("span", { className: "inline-flex max-w-full items-center gap-1.5 rounded-md border bg-muted/30 px-2 py-1 text-xs", children: [
|
|
3935
|
+
/* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
3936
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: file.name }),
|
|
3937
|
+
/* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
|
|
3938
|
+
"\xB7 ",
|
|
3939
|
+
t("financial.invoiceForm.attachOnSave")
|
|
3940
|
+
] }),
|
|
3941
|
+
/* @__PURE__ */ jsx(
|
|
3942
|
+
Button,
|
|
3943
|
+
{
|
|
3944
|
+
variant: "bare",
|
|
3945
|
+
size: "bare",
|
|
3946
|
+
type: "button",
|
|
3947
|
+
onClick: () => setPendingDocs((prev) => prev.filter((_, j) => j !== idx)),
|
|
3948
|
+
"aria-label": t("financial.quickTx.receiptRemove"),
|
|
3949
|
+
className: "font-normal flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
3950
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
3951
|
+
}
|
|
3952
|
+
)
|
|
3953
|
+
] }, `${file.name}-${idx}`)) })
|
|
3954
|
+
] }) }) })
|
|
3955
|
+
);
|
|
3956
|
+
const contactSection = /* @__PURE__ */ jsx("div", { ...tourAnchor("financial.contact"), children: /* @__PURE__ */ jsxs(OrderFormBlock, { title: direction === "debit" ? t("financial.invoiceForm.payTo") : t("financial.invoiceForm.receiveFrom"), children: [
|
|
3957
|
+
/* @__PURE__ */ jsx(
|
|
3958
|
+
FinancialCounterpartySearchSelect,
|
|
3959
|
+
{
|
|
3960
|
+
required: true,
|
|
3961
|
+
disabled: isEdit && hasPayments,
|
|
3962
|
+
lookup: contactLookup,
|
|
3963
|
+
value: contactId ?? "",
|
|
3964
|
+
displayValue: contactName,
|
|
3965
|
+
onChange: (id, opt) => {
|
|
3966
|
+
setContactId(id || void 0);
|
|
3967
|
+
setContactName(opt?.label ?? "");
|
|
3968
|
+
setPendingContact(null);
|
|
3969
|
+
const request = ++accountingPolicyRequest.current;
|
|
3970
|
+
if (!id) {
|
|
3971
|
+
setCounterpartyAccounting({ allowedAccountIds: [], limitAccounts: false, addItemsAutomatically: false });
|
|
3972
|
+
return;
|
|
3973
|
+
}
|
|
3974
|
+
const policy = counterpartyAccountingPolicy(opt?.data);
|
|
3975
|
+
setCounterpartyAccounting({ allowedAccountIds: [], limitAccounts: policy.limitAccounts, addItemsAutomatically: policy.addItemsAutomatically });
|
|
3976
|
+
const refs = [policy.defaultAccountId, ...policy.allowedAccountIds].filter(Boolean);
|
|
3977
|
+
void (async () => {
|
|
3978
|
+
let resolvedRefs = {};
|
|
3979
|
+
try {
|
|
3980
|
+
resolvedRefs = await provider.resolveChartAccountRefs?.(refs) ?? {};
|
|
3981
|
+
} catch {
|
|
3982
|
+
}
|
|
3983
|
+
if (request !== accountingPolicyRequest.current) return;
|
|
3984
|
+
const resolved = resolveCounterpartyAccountingPolicy(policy, resolvedRefs);
|
|
3985
|
+
setCounterpartyAccounting(resolved);
|
|
3986
|
+
if (resolved.defaultAccountId) {
|
|
3987
|
+
setItems((current) => current.map((item) => item.accountId ? item : { ...item, accountId: resolved.defaultAccountId }));
|
|
3988
|
+
}
|
|
3989
|
+
if (resolved.defaultCostCenterId) {
|
|
3990
|
+
setItems((current) => current.map((item) => item.costCenterId ? item : { ...item, costCenterId: resolved.defaultCostCenterId }));
|
|
3991
|
+
}
|
|
3992
|
+
})();
|
|
3993
|
+
},
|
|
3994
|
+
onCreate: contactCreate.onCreate,
|
|
3995
|
+
onEdit: contactCreate.onEdit
|
|
3996
|
+
}
|
|
3997
|
+
),
|
|
3998
|
+
pendingContact && !contactId && contactCreate.onCreate && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2 rounded-lg border border-dashed border-border bg-muted/30 px-2.5 py-2", children: [
|
|
3999
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [
|
|
4000
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
4001
|
+
t("financial.receipt.fromReceipt"),
|
|
4002
|
+
": "
|
|
4003
|
+
] }),
|
|
4004
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pendingContact })
|
|
4005
|
+
] }),
|
|
4006
|
+
/* @__PURE__ */ jsx(
|
|
4007
|
+
Button,
|
|
4008
|
+
{
|
|
4009
|
+
size: "sm",
|
|
4010
|
+
variant: "outline",
|
|
4011
|
+
className: "h-7 shrink-0",
|
|
4012
|
+
onClick: () => contactCreate.onCreate(pendingContact),
|
|
4013
|
+
children: t("financial.receipt.finishContact")
|
|
4014
|
+
}
|
|
4015
|
+
)
|
|
4016
|
+
] }),
|
|
4017
|
+
contactCreate.modal
|
|
4018
|
+
] }) });
|
|
4019
|
+
const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4020
|
+
/* @__PURE__ */ jsx(OrderFormBlock, { title: t("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
4021
|
+
showUnitField && /* @__PURE__ */ jsx(UnitField, { choices: unitChoices, value: unitId, onChange: setUnitId }),
|
|
4022
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4023
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.invoiceForm.date") }),
|
|
4024
|
+
isEdit && hasPayments ? /* @__PURE__ */ jsx("p", { className: "mt-1 px-1 py-2 text-sm", children: (/* @__PURE__ */ new Date(`${invoiceDate}T00:00:00`)).toLocaleDateString() }) : /* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
|
|
4025
|
+
] }),
|
|
4026
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4027
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("financial.invoiceForm.fiscalNumber") }),
|
|
4028
|
+
/* @__PURE__ */ jsx(
|
|
4029
|
+
Input,
|
|
4030
|
+
{
|
|
4031
|
+
type: "text",
|
|
4032
|
+
value: fiscalNumber,
|
|
4033
|
+
onChange: (e) => setFiscalNumber(e.target.value),
|
|
4034
|
+
placeholder: t("financial.invoiceForm.fiscalNumber"),
|
|
4035
|
+
className: "mt-1"
|
|
4036
|
+
}
|
|
4037
|
+
)
|
|
4038
|
+
] })
|
|
4039
|
+
] }) }),
|
|
4040
|
+
/* @__PURE__ */ jsx(OrderFormBlock, { title: t("financial.invoiceForm.observations"), children: /* @__PURE__ */ jsx(
|
|
4041
|
+
Textarea,
|
|
4042
|
+
{
|
|
4043
|
+
value: observations,
|
|
4044
|
+
onChange: (e) => setObservations(e.target.value),
|
|
4045
|
+
placeholder: t("financial.invoiceForm.additionalNotes"),
|
|
4046
|
+
rows: 2,
|
|
4047
|
+
className: "resize-none"
|
|
4048
|
+
}
|
|
4049
|
+
) }),
|
|
4050
|
+
/* @__PURE__ */ jsx(OrderFormBlock, { title: t("financial.invoiceForm.tags"), children: /* @__PURE__ */ jsx(TagsField, { value: tags, onChange: setTags, table: "plg_financial_invoices" }) })
|
|
4051
|
+
] });
|
|
4052
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4053
|
+
!isEdit && direction === "debit" && /* @__PURE__ */ jsx(
|
|
4054
|
+
PayableDraftSurface,
|
|
4055
|
+
{
|
|
4056
|
+
config,
|
|
4057
|
+
draft: {
|
|
4058
|
+
describe: () => ({
|
|
4059
|
+
supplier: contactName || void 0,
|
|
4060
|
+
supplierResolved: !!contactId,
|
|
4061
|
+
description: items.map((i) => i.description).filter(Boolean).join(", ") || void 0,
|
|
4062
|
+
total: totalAmount,
|
|
4063
|
+
dueDate: installments[0]?.dueDate,
|
|
4064
|
+
itemCount: items.length
|
|
4065
|
+
}),
|
|
4066
|
+
setSupplier: (name, id) => {
|
|
4067
|
+
setContactName(name);
|
|
4068
|
+
setContactId(id);
|
|
4069
|
+
setPendingContact(id ? null : name);
|
|
4070
|
+
},
|
|
4071
|
+
setDueDate: (iso) => setInstallments((prev) => prev.map((inst, i) => i === 0 ? { ...inst, dueDate: iso } : inst)),
|
|
4072
|
+
submit: async () => {
|
|
4073
|
+
await handleSave();
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
),
|
|
4078
|
+
fromCard && /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-3 rounded-card border bg-card px-4 py-3", children: [
|
|
4079
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "shrink-0", children: /* @__PURE__ */ jsx(
|
|
4080
|
+
CardVisual,
|
|
4081
|
+
{
|
|
4082
|
+
lastFour: cardLast4 || void 0,
|
|
4083
|
+
color: fromCard.color ?? void 0,
|
|
4084
|
+
brandId: fromCard.cardBrandId ?? void 0
|
|
4085
|
+
}
|
|
4086
|
+
) }),
|
|
4087
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
4088
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-foreground", children: [
|
|
4089
|
+
fromCard.name,
|
|
4090
|
+
cardLast4 && !nomeJaTemOsDigitos && /* @__PURE__ */ jsxs("span", { className: "ml-2 tabular-nums text-muted-foreground", children: [
|
|
4091
|
+
"\u2022\u2022\u2022\u2022 ",
|
|
4092
|
+
cardLast4
|
|
4093
|
+
] })
|
|
4094
|
+
] }),
|
|
4095
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: cardMethodName ? `${t("financial.cards.expenseOnCard")} \xB7 ${cardMethodName}` : t("financial.cards.expenseOnCard") })
|
|
4096
|
+
] })
|
|
4097
|
+
] }),
|
|
4098
|
+
/* @__PURE__ */ jsxs(
|
|
4099
|
+
OrderFormView,
|
|
4100
|
+
{
|
|
4101
|
+
embedded,
|
|
4102
|
+
title,
|
|
4103
|
+
subtitle,
|
|
4104
|
+
parentLabel: embedded ? void 0 : direction === "debit" ? t("financial.nav.payables") : t("financial.nav.receivables"),
|
|
4105
|
+
onBack: embedded ? void 0 : () => onSaved?.(),
|
|
4106
|
+
actions,
|
|
4107
|
+
banner: isEdit && hasPayments ? /* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning-soft px-4 py-2.5 text-xs text-warning-soft-foreground", children: t("financial.invoiceForm.issuedLocked") }) : void 0,
|
|
4108
|
+
sideSections,
|
|
4109
|
+
children: [
|
|
4110
|
+
contactSection,
|
|
4111
|
+
/* @__PURE__ */ jsxs(
|
|
4112
|
+
OrderFormItemsCard,
|
|
4113
|
+
{
|
|
4114
|
+
"data-tour": "financial.items",
|
|
4115
|
+
label: t("financial.invoiceForm.items"),
|
|
4116
|
+
addLabel: t("financial.invoiceForm.addItem"),
|
|
4117
|
+
headerExtra: !isEdit && currencyChoices.length > 1 ? /* @__PURE__ */ jsx(
|
|
4118
|
+
SegmentedControl,
|
|
4119
|
+
{
|
|
4120
|
+
size: "md",
|
|
4121
|
+
"aria-label": t("financial.invoiceForm.currency"),
|
|
4122
|
+
options: currencyChoices,
|
|
4123
|
+
value: currency.code,
|
|
4124
|
+
onChange: setDocumentCurrency
|
|
4125
|
+
}
|
|
4126
|
+
) : void 0,
|
|
4127
|
+
onAdd: canEditItems ? startAddItem : void 0,
|
|
4128
|
+
addDisabled: !contactId,
|
|
4129
|
+
addDisabledHint: t("financial.invoiceForm.selectContactFirst"),
|
|
4130
|
+
count: items.length,
|
|
4131
|
+
countLabel: t("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
|
|
4132
|
+
totalLabel: t("financial.invoiceForm.total"),
|
|
4133
|
+
total: money(totalAmount),
|
|
4134
|
+
emptyLabel: contactId ? t("financial.invoiceForm.noItems") : t("financial.invoiceForm.selectContactFirst"),
|
|
4135
|
+
emptyActionLabel: t("financial.invoiceForm.addFirstItem"),
|
|
4136
|
+
header: addingItem && /* @__PURE__ */ jsx(
|
|
4137
|
+
OrderFormAddItemStep,
|
|
4138
|
+
{
|
|
4139
|
+
itemTypes,
|
|
4140
|
+
onSelect: addItemOfKind,
|
|
4141
|
+
onCancel: () => setAddingItem(false),
|
|
4142
|
+
label: t("financial.invoiceForm.selectItemType"),
|
|
4143
|
+
cancelLabel: t("financial.invoiceForm.cancel")
|
|
4144
|
+
}
|
|
4145
|
+
),
|
|
4146
|
+
children: [
|
|
4147
|
+
items.map((item, i) => /* @__PURE__ */ jsx(
|
|
4148
|
+
OrderFormItemRow,
|
|
4149
|
+
{
|
|
4150
|
+
index: i,
|
|
4151
|
+
itemKind: item.itemKind,
|
|
4152
|
+
description: item.description,
|
|
4153
|
+
quantity: item.quantity,
|
|
4154
|
+
unitPrice: item.unitPrice,
|
|
4155
|
+
discount: item.discount,
|
|
4156
|
+
surcharge: item.surcharge,
|
|
4157
|
+
referenceId: item.referenceId,
|
|
4158
|
+
icon: ITEM_ICONS[item.itemKind] ?? ITEM_ICONS.other,
|
|
4159
|
+
imageUrl: item.itemKind === "product" && item.referenceId ? pictures[item.referenceId] : void 0,
|
|
4160
|
+
note: stockNotes.has(item._id) ? /* @__PURE__ */ jsx(StockEntryNote, { entries: stockNotes.get(item._id) }) : void 0,
|
|
4161
|
+
itemTypes,
|
|
4162
|
+
currency,
|
|
4163
|
+
formatMoney: money,
|
|
4164
|
+
expanded: expandedItemId === item._id,
|
|
4165
|
+
readOnly: !canEditItems,
|
|
4166
|
+
onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
|
|
4167
|
+
onChange: (patch) => {
|
|
4168
|
+
void applyItemPatch(item, patch);
|
|
4169
|
+
},
|
|
4170
|
+
onRemove: () => {
|
|
4171
|
+
removeItem(item._id);
|
|
4172
|
+
if (expandedItemId === item._id) setExpandedItemId(null);
|
|
4173
|
+
},
|
|
4174
|
+
extraFieldGroups: [
|
|
4175
|
+
...postable.length > 0 ? [{
|
|
4176
|
+
id: "account",
|
|
4177
|
+
label: t("financial.invoiceForm.account"),
|
|
4178
|
+
filled: !!item.accountId,
|
|
4179
|
+
// O plano de contas na linha fechada: classificar deixa de
|
|
4180
|
+
// ser trabalho invisível. Ver OrderFormExtraFieldGroup.summary.
|
|
4181
|
+
summary: postable.find((a) => a.id === item.accountId)?.name,
|
|
4182
|
+
onClear: () => updateItem(item._id, { accountId: void 0 }),
|
|
4183
|
+
field: (
|
|
4184
|
+
// Busca, não <select>: um plano de contas real tem dezenas
|
|
4185
|
+
// ou centenas de folhas, e a lista nativa obriga a rolar
|
|
4186
|
+
// atrás de um código que a pessoa já sabe de cor.
|
|
4187
|
+
/* @__PURE__ */ jsx(
|
|
4188
|
+
SearchSelect,
|
|
4189
|
+
{
|
|
4190
|
+
label: t("financial.invoiceForm.account"),
|
|
4191
|
+
value: item.accountId,
|
|
4192
|
+
displayValue: postable.find((a) => a.id === item.accountId)?.name,
|
|
4193
|
+
placeholder: t("financial.invoiceForm.accountNone"),
|
|
4194
|
+
minChars: 0,
|
|
4195
|
+
noResultsLabel: t("financial.invoiceForm.accountNone"),
|
|
4196
|
+
onSearch: (q) => filterByCodeOrName(selectableAccounts, q).map((a) => ({
|
|
4197
|
+
id: a.id,
|
|
4198
|
+
label: accountLabel(a),
|
|
4199
|
+
subtitle: a.accountType ?? void 0
|
|
4200
|
+
})),
|
|
4201
|
+
onChange: (id) => updateItem(item._id, { accountId: id || void 0 }),
|
|
4202
|
+
onManage: canManageFinancial ? () => adminNavigateTo(CHART_OF_ACCOUNTS_ROUTE) : void 0,
|
|
4203
|
+
manageLabel: t("financial.invoiceForm.manageChart"),
|
|
4204
|
+
clearLabel: t("financial.invoiceForm.clear")
|
|
4205
|
+
}
|
|
4206
|
+
)
|
|
4207
|
+
)
|
|
4208
|
+
}] : [],
|
|
4209
|
+
...costCenters.length > 0 ? [{
|
|
4210
|
+
id: "costCenter",
|
|
4211
|
+
label: t("financial.invoiceForm.costCenter"),
|
|
4212
|
+
filled: !!item.costCenterId,
|
|
4213
|
+
onClear: () => updateItem(item._id, { costCenterId: void 0 }),
|
|
4214
|
+
field: /* @__PURE__ */ jsx(
|
|
4215
|
+
SearchSelect,
|
|
4216
|
+
{
|
|
4217
|
+
label: t("financial.invoiceForm.costCenter"),
|
|
4218
|
+
value: item.costCenterId,
|
|
4219
|
+
displayValue: costCenters.find((c) => c.id === item.costCenterId)?.name,
|
|
4220
|
+
placeholder: t("financial.invoiceForm.costCenterNone"),
|
|
4221
|
+
minChars: 0,
|
|
4222
|
+
noResultsLabel: t("financial.invoiceForm.costCenterNone"),
|
|
4223
|
+
onSearch: (q) => filterByCodeOrName(costCenters, q).map((c) => ({
|
|
4224
|
+
id: c.id,
|
|
4225
|
+
label: c.code ? `${c.code} - ${c.name}` : c.name
|
|
4226
|
+
})),
|
|
4227
|
+
onChange: (id) => updateItem(item._id, { costCenterId: id || void 0 }),
|
|
4228
|
+
onManage: canManageFinancial ? () => adminNavigateTo(COST_CENTERS_ROUTE) : void 0,
|
|
4229
|
+
manageLabel: t("financial.invoiceForm.manageCostCenters"),
|
|
4230
|
+
clearLabel: t("financial.invoiceForm.clear")
|
|
4231
|
+
}
|
|
4232
|
+
)
|
|
4233
|
+
}] : []
|
|
4234
|
+
],
|
|
4235
|
+
entityLookups,
|
|
4236
|
+
pickOnly: Boolean(entityLookups[item.itemKind]?.expand) && !item.referenceId,
|
|
4237
|
+
showTypeSelector: hasItemLookups,
|
|
4238
|
+
allowSurcharge: true,
|
|
4239
|
+
labels: {
|
|
4240
|
+
item: t("financial.invoiceForm.item"),
|
|
4241
|
+
description: t("financial.invoiceForm.description"),
|
|
4242
|
+
descriptionPlaceholder: t("financial.invoiceForm.itemDescription"),
|
|
4243
|
+
noDescription: t("financial.invoiceForm.noDescription"),
|
|
4244
|
+
qty: t("financial.invoiceForm.qty"),
|
|
4245
|
+
unitPrice: t("financial.invoiceForm.unitPrice"),
|
|
4246
|
+
discount: t("financial.invoiceForm.discount"),
|
|
4247
|
+
surcharge: t("financial.invoiceForm.surcharge"),
|
|
4248
|
+
adjustments: t("financial.invoiceForm.discountSurcharge"),
|
|
4249
|
+
removeAdjustments: t("financial.invoiceForm.removeAdjustments"),
|
|
4250
|
+
done: t("financial.invoiceForm.done"),
|
|
4251
|
+
edit: t("financial.invoiceForm.edit")
|
|
4252
|
+
}
|
|
4253
|
+
},
|
|
4254
|
+
item._id
|
|
4255
|
+
)),
|
|
4256
|
+
cancelledLines.map((line) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-t py-2 pl-4 pr-11 text-muted-foreground", children: [
|
|
4257
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
4258
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm line-through", children: line.description }),
|
|
4259
|
+
/* @__PURE__ */ jsxs("p", { className: "truncate text-xs", children: [
|
|
4260
|
+
"Cancelado",
|
|
4261
|
+
line.cancelReason ? ` \u2014 ${line.cancelReason}` : ""
|
|
4262
|
+
] })
|
|
4263
|
+
] }),
|
|
4264
|
+
/* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs tabular-nums line-through", children: [
|
|
4265
|
+
line.quantity,
|
|
4266
|
+
" \xD7 ",
|
|
4267
|
+
money(line.unitPrice)
|
|
4268
|
+
] }),
|
|
4269
|
+
/* @__PURE__ */ jsx("span", { className: "w-24 shrink-0 text-right text-sm tabular-nums line-through", children: money(line.totalAmount) })
|
|
4270
|
+
] }, line.id))
|
|
4271
|
+
]
|
|
4272
|
+
}
|
|
4273
|
+
),
|
|
4274
|
+
scheduleSection,
|
|
4275
|
+
isEdit && recordMeta && /* @__PURE__ */ jsx(RecordMetaFooter, { item: recordMeta })
|
|
4276
|
+
]
|
|
4277
|
+
}
|
|
4278
|
+
),
|
|
4279
|
+
receiptPicker.input,
|
|
4280
|
+
attachmentsOpen && editId && /* @__PURE__ */ jsx(InvoiceAttachmentsModal, { invoiceId: editId, onClose: () => setAttachmentsOpen(false), onChanged: loadAttachmentCount }),
|
|
4281
|
+
payingMovement && /* @__PURE__ */ jsx(CurrencyScope, { code: documentCurrency, children: /* @__PURE__ */ jsx(
|
|
4282
|
+
PaymentModal,
|
|
4283
|
+
{
|
|
4284
|
+
movement: payingMovement,
|
|
4285
|
+
receiptUrl: pendingReceipt,
|
|
4286
|
+
onClose: () => {
|
|
4287
|
+
setPayingMovement(null);
|
|
4288
|
+
setPendingReceipt(void 0);
|
|
4289
|
+
if (payingNewId) {
|
|
4290
|
+
const id = payingNewId;
|
|
4291
|
+
setPayingNewId(null);
|
|
4292
|
+
onSaved?.(id);
|
|
4293
|
+
}
|
|
4294
|
+
},
|
|
4295
|
+
onPaid: () => {
|
|
4296
|
+
setPayingMovement(null);
|
|
4297
|
+
setPendingReceipt(void 0);
|
|
4298
|
+
if (payingNewId) {
|
|
4299
|
+
const id = payingNewId;
|
|
4300
|
+
setPayingNewId(null);
|
|
4301
|
+
onSaved?.(id);
|
|
4302
|
+
return;
|
|
4303
|
+
}
|
|
4304
|
+
void (async () => {
|
|
4305
|
+
if (!editId) return;
|
|
4306
|
+
const movements = await provider.getMovements({ direction });
|
|
4307
|
+
setSavedMovements(
|
|
4308
|
+
movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
|
|
4309
|
+
);
|
|
4310
|
+
onChanged?.();
|
|
4311
|
+
})();
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
) }),
|
|
4315
|
+
cancelData && editId && /* @__PURE__ */ jsx(
|
|
4316
|
+
CancelInvoiceDialog,
|
|
4317
|
+
{
|
|
4318
|
+
invoiceId: editId,
|
|
4319
|
+
counterpartyName: cancelData.name,
|
|
4320
|
+
totalAmount: cancelData.total,
|
|
4321
|
+
items: cancelData.items,
|
|
4322
|
+
onClose: () => setCancelData(null),
|
|
4323
|
+
onCancelled: () => {
|
|
4324
|
+
setCancelData(null);
|
|
4325
|
+
onSaved?.();
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
),
|
|
4329
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
4330
|
+
@keyframes field-slide-in {
|
|
4331
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
4332
|
+
to { opacity: 1; transform: translateY(0); }
|
|
4333
|
+
}
|
|
4334
|
+
` })
|
|
4335
|
+
] });
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
export { ACCOUNTS_ROUTE, AccountIcon, BankLogo, BankPicker, CancelInvoicesDialog, FinancialCounterpartySearchSelect, InvoiceAttachmentsModal, InvoiceDetailView, InvoiceFormView, PaymentModal, UnitField, createFinancialCounterpartyLookup, findBrBank, marcarItemParaAbrir, renderAccountAvatar, renderCardChip, useStockedItemTypes, useWritableUnits };
|
|
4339
|
+
//# sourceMappingURL=chunk-K37Q7SKX.js.map
|
|
4340
|
+
//# sourceMappingURL=chunk-K37Q7SKX.js.map
|