@fayz-ai/plugin-financial 0.18.3 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BankPicker.d.ts +25 -0
- package/dist/components/BankPicker.d.ts.map +1 -0
- package/dist/components/CnabRemittanceModal.d.ts.map +1 -1
- package/dist/components/InstallmentRow.d.ts +42 -0
- package/dist/components/InstallmentRow.d.ts.map +1 -0
- package/dist/components/InstallmentSource.d.ts.map +1 -1
- package/dist/components/InstallmentStatusBadge.d.ts +6 -0
- package/dist/components/InstallmentStatusBadge.d.ts.map +1 -0
- package/dist/components/InvoiceAttachmentsModal.d.ts +12 -0
- package/dist/components/InvoiceAttachmentsModal.d.ts.map +1 -0
- package/dist/components/PaymentFields.d.ts.map +1 -1
- package/dist/components/PaymentIcons.d.ts +12 -0
- package/dist/components/PaymentIcons.d.ts.map +1 -0
- package/dist/components/PaymentReceiptModal.d.ts.map +1 -1
- package/dist/components/ReceivePaymentModal.d.ts +14 -3
- package/dist/components/ReceivePaymentModal.d.ts.map +1 -1
- package/dist/components/ReconcileFlow.d.ts.map +1 -1
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +7 -0
- package/dist/data/tables.d.ts.map +1 -1
- package/dist/data/types.d.ts +28 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2385 -851
- package/dist/index.js.map +1 -1
- package/dist/lib/br-banks.d.ts +13 -0
- package/dist/lib/br-banks.d.ts.map +1 -0
- package/dist/lib/cnab240.d.ts +106 -0
- package/dist/lib/cnab240.d.ts.map +1 -0
- package/dist/lib/invoice-list.d.ts +15 -0
- package/dist/lib/invoice-list.d.ts.map +1 -1
- package/dist/lib/settlements.d.ts +6 -0
- package/dist/lib/settlements.d.ts.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +94 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/ClientAccountPanel.d.ts.map +1 -1
- package/dist/views/FinancialPage.d.ts.map +1 -1
- package/dist/views/GapScreens.d.ts +0 -2
- package/dist/views/GapScreens.d.ts.map +1 -1
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts +18 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts +3 -2
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/PayablesView.d.ts.map +1 -1
- package/dist/views/ReceivablesView.d.ts.map +1 -1
- package/dist/views/cardChip.d.ts +1 -1
- package/dist/views/cardChip.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,15 +1,94 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import
|
|
3
|
-
import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, getSupabaseClientOptional, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode, useDataChanged, countByTenant, tl, resolveDataProvider, getActiveTenantId, emitDataChanged, exportCSV
|
|
4
|
-
import { ModalTask, toast, Button, useInModalTaskHost,
|
|
1
|
+
import * as React25 from 'react';
|
|
2
|
+
import React25__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
3
|
+
import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, getSupabaseClientOptional, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode, useActiveUnitId, useDataChanged, countByTenant, tl, resolveDataProvider, getActiveTenantId, emitDataChanged, exportCSV } from '@fayz-ai/core';
|
|
4
|
+
import { INLINE_LINK_TONE, SearchSelect, ModalTask, toast, Button, useInModalTaskHost, useSaveBar, tourAnchor, DatePicker, SubpageHeader, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, Card, CardContent, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MultiSelect, PageTransition, ModulePage, Badge, TooltipProvider, ConfirmDialog, CurrencyInput, Input, PageHeaderActions, Hint, ModalFooter, PageTabs, Tooltip, TooltipTrigger, TooltipContent, Checkbox, Popover, PopoverTrigger, PopoverContent, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, SegmentedControl, RequiredFilter, FilterMenuRow, ModalFlow, Switch, WizardSteps, KpiCard, ChartWidget, CardHeader, CardTitle, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
|
|
5
5
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
-
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate,
|
|
7
|
-
import { CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Receipt, Gift, Briefcase, Package, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote,
|
|
6
|
+
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useUnitFilter, useLimitGuard, usePermissionOptional, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, useChatStore, adminNavigateTo, revealPage, useAuth, GetStarted, resolveEntityRoute, PermissionGate, createOneShotAsk, askAI, requestChatUpload, toInlineFile, shrinkForVision } from '@fayz-ai/admin';
|
|
7
|
+
import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Receipt, Gift, Briefcase, Package, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, Ticket, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Undo2, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, ChevronRight, Building2, ChevronDown, Image, UploadCloud, Calendar, User, Unlink, Eye, Inbox, Clock, ClipboardCheck, BookOpen, Info, FolderOpen, Folder, RotateCcw, ArrowLeft, ChevronLeft, Calculator, Square, Play, ChevronUp, Printer, CalendarClock, BadgeCheck, Target, Settings, ArrowRight, Users, RefreshCw, Download, Camera, Sparkles, Link2, FileSearch, CheckCheck, Pause, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, FileDown, ArrowUpRight, ArrowDownRight, EyeOff } from 'lucide-react';
|
|
8
8
|
import { createPortal } from 'react-dom';
|
|
9
9
|
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
10
10
|
import { useStore } from 'zustand';
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
13
|
+
|
|
14
|
+
// src/config.ts
|
|
15
|
+
var DEFAULT_LABELS = {
|
|
16
|
+
pageTitle: "Financial",
|
|
17
|
+
pageSubtitle: "Financial overview and cash flow management",
|
|
18
|
+
summary: "Summary",
|
|
19
|
+
payables: "Accounts Payable",
|
|
20
|
+
payablesNew: "New",
|
|
21
|
+
payablesList: "List",
|
|
22
|
+
payablesRecurring: "Recurring Expenses",
|
|
23
|
+
receivables: "Accounts Receivable",
|
|
24
|
+
receivablesNew: "New",
|
|
25
|
+
receivablesList: "List",
|
|
26
|
+
cashRegisters: "Cash Registers",
|
|
27
|
+
statements: "Statements",
|
|
28
|
+
commissions: "Commissions",
|
|
29
|
+
commissionsOverview: "Overview",
|
|
30
|
+
commissionsRules: "Rules",
|
|
31
|
+
cards: "Cards",
|
|
32
|
+
goals: "Metas",
|
|
33
|
+
giftCards: "Gift Cards",
|
|
34
|
+
cardsOverview: "Overview",
|
|
35
|
+
cardsReconciliation: "Reconciliation",
|
|
36
|
+
notasFiscais: "Fiscal Invoices"
|
|
37
|
+
};
|
|
38
|
+
var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
|
|
39
|
+
var DEFAULT_ITEM_TYPES = [
|
|
40
|
+
{ value: "service", label: "Service", icon: "Briefcase" },
|
|
41
|
+
// O pacote é o terceiro catálogo da casa que atende: um bloco de sessões
|
|
42
|
+
// vendido de uma vez. Só aparece onde há `entityLookups.package` instalado —
|
|
43
|
+
// e, dentro dessas, só onde o catálogo tem linhas (useStockedItemTypes).
|
|
44
|
+
{ value: "package", label: "Package", icon: "PackageCheck" },
|
|
45
|
+
{ value: "product", label: "Product", icon: "Package" },
|
|
46
|
+
{ value: "other", label: "Other", icon: "MoreHorizontal" }
|
|
47
|
+
];
|
|
48
|
+
function resolveConfig(options) {
|
|
49
|
+
return {
|
|
50
|
+
modules: {
|
|
51
|
+
payables: options?.modules?.payables !== false,
|
|
52
|
+
receivables: options?.modules?.receivables !== false,
|
|
53
|
+
cashRegisters: options?.modules?.cashRegisters !== false,
|
|
54
|
+
statements: options?.modules?.statements !== false,
|
|
55
|
+
// Era opt-in porque a comissão era um cadastro que nada lia: o item de
|
|
56
|
+
// menu só podia levar a uma tela vazia. Desde 009 existe apuração e desde
|
|
57
|
+
// 012 existe borderô, e a tela mostra o que eles produziram — então o
|
|
58
|
+
// motivo do opt-in acabou junto com a tela vazia.
|
|
59
|
+
commissions: options?.modules?.commissions !== false,
|
|
60
|
+
cards: options?.modules?.cards !== false,
|
|
61
|
+
goals: options?.modules?.goals !== false,
|
|
62
|
+
// Ligado como as outras: quando a loja não está instalada a própria tela
|
|
63
|
+
// diz isso, então o item de menu nunca leva a um beco.
|
|
64
|
+
giftCards: options?.modules?.giftCards !== false,
|
|
65
|
+
// Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
|
|
66
|
+
reconciliation: options?.modules?.reconciliation === true,
|
|
67
|
+
// Opt-in: only shown when an app explicitly enables fiscal-invoice tracking.
|
|
68
|
+
// LIGADA como as outras (`!== false`), e não opt-in. Estava em
|
|
69
|
+
// `=== true`: quem não passasse a opção nunca via a tela, e o V1 tem
|
|
70
|
+
// Notas Fiscais no menu do Financeiro desde sempre. A tela se explica
|
|
71
|
+
// sozinha quando o provider não expõe notas, então ligá-la não deixa
|
|
72
|
+
// ninguém diante de um beco.
|
|
73
|
+
notasFiscais: options?.modules?.notasFiscais !== false
|
|
74
|
+
},
|
|
75
|
+
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
76
|
+
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
77
|
+
moduleNav: options?.moduleNav,
|
|
78
|
+
itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
|
|
79
|
+
enableServiceExecution: options?.enableServiceExecution ?? false,
|
|
80
|
+
contactEntity: {
|
|
81
|
+
archetypeKind: options?.contactEntity?.archetypeKind ?? "client",
|
|
82
|
+
label: options?.contactEntity?.label ?? "Contact"
|
|
83
|
+
},
|
|
84
|
+
locations: options?.locations ?? [],
|
|
85
|
+
entityLookups: options?.entityLookups ?? {},
|
|
86
|
+
contactLookup: options?.contactLookup,
|
|
87
|
+
fiscalProvider: options?.fiscalProvider,
|
|
88
|
+
onBookingClick: options?.onBookingClick,
|
|
89
|
+
navFeatureMap: { ...options?.navFeatureMap }
|
|
90
|
+
};
|
|
91
|
+
}
|
|
13
92
|
function PettyCashView() {
|
|
14
93
|
return /* @__PURE__ */ jsx(
|
|
15
94
|
ComingSoon,
|
|
@@ -26,22 +105,6 @@ function PettyCashView() {
|
|
|
26
105
|
}
|
|
27
106
|
);
|
|
28
107
|
}
|
|
29
|
-
function DigitizeExpenseView() {
|
|
30
|
-
return /* @__PURE__ */ jsx(
|
|
31
|
-
ComingSoon,
|
|
32
|
-
{
|
|
33
|
-
title: "Digitalizar Despesa",
|
|
34
|
-
summary: "Fotografar o boleto ou a nota e deixar a m\xE1quina ler valor, vencimento e fornecedor \u2014 em vez de digitar os quatro campos de novo.",
|
|
35
|
-
v1Path: "Financeiro \u2192 Contas a Pagar \u2192 Digitalizar Despesa",
|
|
36
|
-
measured: "1.436 anexos de conta no V1, 55 da Artorius. A Laissa anexa comprovante 25 vezes por m\xEAs \u2014 mas digita os campos \xE0 m\xE3o todas elas.",
|
|
37
|
-
missing: [
|
|
38
|
-
"Ler o comprovante e PR\xC9-PREENCHER o formul\xE1rio, deixando o usu\xE1rio confirmar \u2014 n\xE3o gravar sozinho",
|
|
39
|
-
"Guardar o arquivo junto do t\xEDtulo, que \xE9 o que a contabilidade pede depois",
|
|
40
|
-
"A Entrada de Notas j\xE1 faz isto para XML de NF-e; aqui \xE9 o mesmo trabalho para o que chega em papel"
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
108
|
function ImportPayrollView() {
|
|
46
109
|
return /* @__PURE__ */ jsx(
|
|
47
110
|
ComingSoon,
|
|
@@ -388,7 +451,14 @@ var T = {
|
|
|
388
451
|
marginByMonth: "v_financial_margin_by_month",
|
|
389
452
|
recurringBills: "plg_financial_recurring_bills",
|
|
390
453
|
recurringOccurrences: "plg_financial_recurring_bill_occurrences",
|
|
391
|
-
discountsGiven: "v_financial_discounts_given"
|
|
454
|
+
discountsGiven: "v_financial_discounts_given",
|
|
455
|
+
/** O arquivo de remessa que já saiu para o banco, e o pagamento dentro dele (080). */
|
|
456
|
+
cnabBatches: "plg_financial_cnab_batches",
|
|
457
|
+
cnabItems: "plg_financial_cnab_items",
|
|
458
|
+
/** Para onde o dinheiro SAI — a conta de quem a casa paga, nunca conta do razão (149). */
|
|
459
|
+
payeeAccounts: "payee_accounts",
|
|
460
|
+
/** A empresa pagadora da unidade: é o CNPJ dela que vai no cabeçalho do CNAB. */
|
|
461
|
+
unitCompanies: "v_unit_company_legal_entities"
|
|
392
462
|
};
|
|
393
463
|
|
|
394
464
|
// src/lib/accounts.ts
|
|
@@ -838,9 +908,9 @@ function startReceiptCapture(labels) {
|
|
|
838
908
|
});
|
|
839
909
|
}
|
|
840
910
|
function ReceiptAgentSurface({ onOpenDraft }) {
|
|
841
|
-
const live =
|
|
911
|
+
const live = React25.useRef(onOpenDraft);
|
|
842
912
|
live.current = onOpenDraft;
|
|
843
|
-
const contribution =
|
|
913
|
+
const contribution = React25.useMemo(() => ({
|
|
844
914
|
id: "financial.payables.receipt",
|
|
845
915
|
pluginId: "financial",
|
|
846
916
|
role: "finance",
|
|
@@ -865,83 +935,685 @@ function createIdempotencyKey(scope) {
|
|
|
865
935
|
}
|
|
866
936
|
return `${scope}:${suffix}`;
|
|
867
937
|
}
|
|
938
|
+
|
|
939
|
+
// src/lib/cnab240.ts
|
|
940
|
+
var onlyDigits = (s) => (s || "").replace(/\D/g, "");
|
|
941
|
+
var padL = (v, n, c = "0") => String(v ?? "").slice(0, n).padStart(n, c);
|
|
942
|
+
var padR = (v, n, c = " ") => String(v ?? "").slice(0, n).padEnd(n, c);
|
|
943
|
+
var ascii = (s) => (s || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9 .,&'/()-]/g, " ").toUpperCase();
|
|
944
|
+
var num = (v, n) => padL(onlyDigits(String(v ?? "0")), n);
|
|
945
|
+
var alpha = (v, n) => padR(ascii(String(v ?? "")), n);
|
|
946
|
+
var money = (v, n = 15) => padL(Math.round((Number(v) || 0) * 100).toString(), n);
|
|
947
|
+
var dateYMD = (s) => {
|
|
948
|
+
if (!s) return "00000000";
|
|
949
|
+
const d = /* @__PURE__ */ new Date(s + (s.length === 10 ? "T00:00:00" : ""));
|
|
950
|
+
if (Number.isNaN(d.getTime())) return "00000000";
|
|
951
|
+
return `${padL(d.getDate(), 2)}${padL(d.getMonth() + 1, 2)}${padL(d.getFullYear(), 4)}`;
|
|
952
|
+
};
|
|
953
|
+
var stampDate = (d) => `${padL(d.getDate(), 2)}${padL(d.getMonth() + 1, 2)}${padL(d.getFullYear(), 4)}`;
|
|
954
|
+
var stampTime = (d) => `${padL(d.getHours(), 2)}${padL(d.getMinutes(), 2)}${padL(d.getSeconds(), 2)}`;
|
|
955
|
+
var finalize = (s) => s.length < 240 ? s.padEnd(240, " ") : s.slice(0, 240);
|
|
956
|
+
var inscriptionType = (document2) => onlyDigits(document2).length > 11 ? "2" : "1";
|
|
957
|
+
var serviceTypeFor = (form) => form === "tributo" ? "22" : "20";
|
|
958
|
+
var settlementFor = (form, payeeBank, ourBank) => {
|
|
959
|
+
if (form === "pix") return "45";
|
|
960
|
+
if (form === "boleto") return "30";
|
|
961
|
+
if (form === "tributo") return "11";
|
|
962
|
+
if (form === "ted") {
|
|
963
|
+
return payeeBank && ourBank && onlyDigits(payeeBank) === onlyDigits(ourBank) ? "01" : "41";
|
|
964
|
+
}
|
|
965
|
+
return "01";
|
|
966
|
+
};
|
|
967
|
+
var clearingHouseFor = (form, payeeBank, ourBank) => {
|
|
968
|
+
if (form !== "ted") return "000";
|
|
969
|
+
return payeeBank && ourBank && onlyDigits(payeeBank) === onlyDigits(ourBank) ? "000" : "018";
|
|
970
|
+
};
|
|
971
|
+
var pixInitiationCodeFor = (pixKeyType, pixKey) => {
|
|
972
|
+
const type = (pixKeyType || "").toLowerCase();
|
|
973
|
+
const key = String(pixKey || "").trim();
|
|
974
|
+
const digits2 = onlyDigits(key);
|
|
975
|
+
switch (type) {
|
|
976
|
+
case "phone":
|
|
977
|
+
case "telefone":
|
|
978
|
+
return "01";
|
|
979
|
+
case "email":
|
|
980
|
+
return "02";
|
|
981
|
+
case "cpf":
|
|
982
|
+
case "cnpj":
|
|
983
|
+
return "03";
|
|
984
|
+
case "random":
|
|
985
|
+
case "aleatoria":
|
|
986
|
+
case "evp":
|
|
987
|
+
return "04";
|
|
988
|
+
case "bank":
|
|
989
|
+
case "dados_bancarios":
|
|
990
|
+
return "05";
|
|
991
|
+
default:
|
|
992
|
+
if (digits2.length === 11 || digits2.length === 14) return "03";
|
|
993
|
+
if (key.includes("@")) return "02";
|
|
994
|
+
if (key.startsWith("+") || digits2.length >= 10) return "01";
|
|
995
|
+
if (key) return "04";
|
|
996
|
+
return "05";
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
var initiationFieldFor = (form, payee) => form === "pix" ? `${pixInitiationCodeFor(payee.pixKeyType, payee.pixKey)} ` : "03 ";
|
|
1000
|
+
var pixKeyValueFor = (pixKey, initiationCode) => {
|
|
1001
|
+
const key = String(pixKey || "").trim();
|
|
1002
|
+
if (initiationCode === "01" || initiationCode === "03") return onlyDigits(key);
|
|
1003
|
+
if (initiationCode === "02") return key.toLowerCase();
|
|
1004
|
+
return key;
|
|
1005
|
+
};
|
|
1006
|
+
var pixAlpha = (v, n) => padR(
|
|
1007
|
+
String(v ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\x20-\x7E]/g, " "),
|
|
1008
|
+
n
|
|
1009
|
+
);
|
|
1010
|
+
function linhaDigitavelToBarcode(input) {
|
|
1011
|
+
const d = onlyDigits(input || "");
|
|
1012
|
+
if (d.length === 44) return d;
|
|
1013
|
+
if (d.length === 47) {
|
|
1014
|
+
const c1 = d.substring(0, 9);
|
|
1015
|
+
const c2 = d.substring(10, 20);
|
|
1016
|
+
const c3 = d.substring(21, 31);
|
|
1017
|
+
const dv = d.substring(32, 33);
|
|
1018
|
+
const vv = d.substring(33, 47);
|
|
1019
|
+
return c1.substring(0, 4) + dv + vv + c1.substring(4, 9) + c2 + c3;
|
|
1020
|
+
}
|
|
1021
|
+
if (d.length === 48) {
|
|
1022
|
+
return d.substring(0, 11) + d.substring(12, 23) + d.substring(24, 35) + d.substring(36, 47);
|
|
1023
|
+
}
|
|
1024
|
+
return d;
|
|
1025
|
+
}
|
|
1026
|
+
function headerArquivo(ctx2, sequence, at) {
|
|
1027
|
+
return finalize(
|
|
1028
|
+
num(ctx2.bank.code, 3) + "00000" + padR("", 9) + inscriptionType(ctx2.company.document) + num(ctx2.company.document, 14) + padR(ctx2.bank.agreement || "", 20) + num(ctx2.bank.agency, 5) + alpha(ctx2.bank.agencyDigit || "", 1) + num(ctx2.bank.account, 12) + alpha(ctx2.bank.accountDigit || "", 1) + alpha("", 1) + alpha(ctx2.company.name, 30) + alpha(ctx2.bank.name, 30) + padR("", 10) + "1" + stampDate(at) + stampTime(at) + num(sequence, 6) + "10300000" + padR("", 20) + padR("", 20) + padR("", 29)
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
function trailerArquivo(lotes, records) {
|
|
1032
|
+
return finalize("99999999" + padR("", 9) + num(lotes, 6) + num(records, 6) + num(0, 6) + padR("", 205));
|
|
1033
|
+
}
|
|
1034
|
+
function headerLote(ctx2, lote, form, settlement) {
|
|
1035
|
+
const agreement = form === "pix" ? "" : ctx2.bank.agreement || "";
|
|
1036
|
+
return finalize(
|
|
1037
|
+
num(ctx2.bank.code, 3) + num(lote, 4) + "1C" + serviceTypeFor(form) + settlement + "046 " + inscriptionType(ctx2.company.document) + num(ctx2.company.document, 14) + padR(agreement, 20) + num(ctx2.bank.agency, 5) + alpha(ctx2.bank.agencyDigit || "", 1) + num(ctx2.bank.account, 12) + alpha(ctx2.bank.accountDigit || "", 1) + alpha("", 1) + alpha(ctx2.company.name, 30) + alpha("", 40) + alpha("", 30) + num("", 5) + alpha("", 15) + alpha("", 20) + num("", 8) + alpha("", 2) + " " + padR("", 6) + padR("", 10)
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
function trailerLote(lote, records, total) {
|
|
1041
|
+
return finalize(
|
|
1042
|
+
"000" + num(lote, 4) + "5" + padR("", 9) + num(records, 6) + money(total, 18) + num(0, 18) + padR("", 6) + padR("", 165) + padR("", 10)
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
function segmentoA(ctx2, lote, index, form, item, docNumber) {
|
|
1046
|
+
const payee = item.payee;
|
|
1047
|
+
const byPixKey = form === "pix" && !!payee.pixKey;
|
|
1048
|
+
const favBank = byPixKey ? "0" : payee.bankCode || "0";
|
|
1049
|
+
const favAgency = byPixKey ? "0" : payee.agency || "0";
|
|
1050
|
+
const favAgencyDigit = byPixKey ? "0" : payee.agencyDigit || "0";
|
|
1051
|
+
const favAccount = byPixKey ? "0" : payee.account || "0";
|
|
1052
|
+
const favAccountDigit = byPixKey ? "0" : payee.accountDigit || "0";
|
|
1053
|
+
return finalize(
|
|
1054
|
+
num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "A000" + clearingHouseFor(form, payee.bankCode, ctx2.bank.code) + num(favBank, 3) + num(favAgency, 5) + num(favAgencyDigit, 1) + num(favAccount, 12) + num(favAccountDigit, 1) + alpha("", 1) + alpha(payee.name, 30) + alpha(`DOC${docNumber}`, 20) + dateYMD(item.paymentDate) + "BRL" + num(0, 15) + money(item.amount, 15) + alpha("", 20) + num(0, 8) + money(0, 15) + num(byPixKey ? "0" : docNumber, 14) + num("0", 8) + num(byPixKey ? "0" : "01", 2) + padR("", 29) + padR("", 10)
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
function segmentoB(ctx2, lote, index, form, item) {
|
|
1058
|
+
const payee = item.payee;
|
|
1059
|
+
const byPixKey = form === "pix" && !!payee.pixKey;
|
|
1060
|
+
const initiation = pixInitiationCodeFor(payee.pixKeyType, payee.pixKey);
|
|
1061
|
+
const identifier = byPixKey && initiation === "03" ? payee.pixKey : payee.document;
|
|
1062
|
+
const header = num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "B" + initiationFieldFor(form, payee);
|
|
1063
|
+
if (byPixKey) {
|
|
1064
|
+
return finalize(
|
|
1065
|
+
header + inscriptionType(identifier || "") + num(identifier, 14) + pixAlpha("", 35) + pixAlpha("", 60) + pixAlpha(initiation === "03" ? "" : pixKeyValueFor(payee.pixKey, initiation), 99) + padR("", 6) + num(0, 8)
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
return finalize(
|
|
1069
|
+
header + inscriptionType(payee.document) + num(payee.document, 14) + alpha(payee.street || "", 30) + num(payee.number || "0", 5) + alpha(payee.complement || "", 15) + alpha(payee.district || "", 15) + alpha(payee.city || "", 20) + num(payee.postalCode || "0", 8) + alpha(payee.state || "", 2) + dateYMD(item.paymentDate) + money(item.amount, 15) + money(0, 15) + money(0, 15) + money(0, 15) + money(0, 15) + padR("", 99)
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
function segmentoJ(ctx2, lote, index, item, docNumber) {
|
|
1073
|
+
return finalize(
|
|
1074
|
+
num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "J000" + padL(linhaDigitavelToBarcode(item.barcode || ""), 44) + alpha(item.payee.name, 30) + dateYMD(item.paymentDate) + money(item.amount, 15) + money(0, 15) + money(0, 15) + dateYMD(item.paymentDate) + money(item.amount, 15) + num("0", 15) + num(docNumber, 20) + num("0", 20) + padR("", 8) + padR("", 10)
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
function segmentoJ52(ctx2, lote, index, item, docNumber) {
|
|
1078
|
+
const payerDoc = onlyDigits(ctx2.company.document);
|
|
1079
|
+
const payeeDoc = onlyDigits(item.payee.document);
|
|
1080
|
+
return finalize(
|
|
1081
|
+
num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "J 0052" + inscriptionType(payerDoc) + num(payerDoc, 15) + alpha(ctx2.company.name, 40) + inscriptionType(payeeDoc) + num(payeeDoc, 15) + alpha(item.payee.name, 40) + padR("", 56) + num(docNumber, 53)
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
function segmentoO(ctx2, lote, index, item, docNumber) {
|
|
1085
|
+
return finalize(
|
|
1086
|
+
num(ctx2.bank.code, 3) + num(lote, 4) + "3" + num(index, 5) + "O000" + padL(linhaDigitavelToBarcode(item.barcode || ""), 44) + alpha(item.payee.name, 30) + dateYMD(item.paymentDate) + dateYMD(item.paymentDate) + money(item.amount, 15) + alpha(`DOC${docNumber}`, 20) + padR("", 6) + padR("", 33)
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
function groupItems(items, ourBankCode) {
|
|
1090
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1091
|
+
for (const item of items) {
|
|
1092
|
+
let settlement = settlementFor(item.paymentForm, item.payee.bankCode, ourBankCode);
|
|
1093
|
+
if (item.paymentForm === "boleto") {
|
|
1094
|
+
const barcode = linhaDigitavelToBarcode(item.barcode || "");
|
|
1095
|
+
settlement = barcode.substring(0, 3) === padL(onlyDigits(ourBankCode), 3) ? "30" : "31";
|
|
1096
|
+
}
|
|
1097
|
+
const key = `${item.paymentForm}:${settlement}`;
|
|
1098
|
+
const group = groups.get(key) ?? { form: item.paymentForm, settlement, list: [] };
|
|
1099
|
+
group.list.push(item);
|
|
1100
|
+
groups.set(key, group);
|
|
1101
|
+
}
|
|
1102
|
+
return [...groups.values()];
|
|
1103
|
+
}
|
|
1104
|
+
var docNumberFor = (sequence, index) => sequence * 1e4 + index + 1;
|
|
1105
|
+
function generateCnab240(input) {
|
|
1106
|
+
if (!input.items.length) throw new Error("Uma remessa sem itens n\xE3o \xE9 um arquivo.");
|
|
1107
|
+
const at = input.generatedAt ?? /* @__PURE__ */ new Date();
|
|
1108
|
+
const ctx2 = { company: input.company, bank: input.bank };
|
|
1109
|
+
const lines = [headerArquivo(ctx2, input.sequence, at)];
|
|
1110
|
+
const groups = groupItems(input.items, input.bank.code);
|
|
1111
|
+
const batches = [];
|
|
1112
|
+
let lote = 0;
|
|
1113
|
+
let totalRecords = 1;
|
|
1114
|
+
let line = 0;
|
|
1115
|
+
for (const group of groups) {
|
|
1116
|
+
lote += 1;
|
|
1117
|
+
lines.push(headerLote(ctx2, lote, group.form, group.settlement));
|
|
1118
|
+
let loteRecords = 1;
|
|
1119
|
+
let loteTotal = 0;
|
|
1120
|
+
let index = 0;
|
|
1121
|
+
for (const item of group.list) {
|
|
1122
|
+
const docNumber = docNumberFor(input.sequence, line);
|
|
1123
|
+
line += 1;
|
|
1124
|
+
loteTotal += Number(item.amount) || 0;
|
|
1125
|
+
if (group.form === "boleto") {
|
|
1126
|
+
lines.push(segmentoJ(ctx2, lote, index += 1, item, docNumber));
|
|
1127
|
+
lines.push(segmentoJ52(ctx2, lote, index += 1, item, docNumber));
|
|
1128
|
+
loteRecords += 2;
|
|
1129
|
+
} else if (group.form === "tributo") {
|
|
1130
|
+
lines.push(segmentoO(ctx2, lote, index += 1, item, docNumber));
|
|
1131
|
+
loteRecords += 1;
|
|
1132
|
+
} else {
|
|
1133
|
+
lines.push(segmentoA(ctx2, lote, index += 1, group.form, item, docNumber));
|
|
1134
|
+
lines.push(segmentoB(ctx2, lote, index += 1, group.form, item));
|
|
1135
|
+
loteRecords += 2;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
loteRecords += 1;
|
|
1139
|
+
lines.push(trailerLote(lote, loteRecords, loteTotal));
|
|
1140
|
+
totalRecords += loteRecords;
|
|
1141
|
+
batches.push({ form: group.form, count: group.list.length, total: loteTotal });
|
|
1142
|
+
}
|
|
1143
|
+
totalRecords += 1;
|
|
1144
|
+
lines.push(trailerArquivo(lote, totalRecords));
|
|
1145
|
+
const totalAmount = input.items.reduce((sum, item) => sum + (Number(item.amount) || 0), 0);
|
|
1146
|
+
const bankLabel = String(parseInt(onlyDigits(input.bank.code) || "0", 10));
|
|
1147
|
+
return {
|
|
1148
|
+
fileName: `CI240_${bankLabel}_${String(input.sequence).padStart(4, "0")}.REM`,
|
|
1149
|
+
content: lines.join("\r\n") + "\r\n",
|
|
1150
|
+
layout: `febraban-${padL(onlyDigits(input.bank.code), 3)}`,
|
|
1151
|
+
totalAmount,
|
|
1152
|
+
itemsCount: input.items.length,
|
|
1153
|
+
batches
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
function cnabPayeeGaps(items) {
|
|
1157
|
+
const gaps = [];
|
|
1158
|
+
items.forEach((item, index) => {
|
|
1159
|
+
const payee = item.payee;
|
|
1160
|
+
const name = payee.name || `Favorecido #${index + 1}`;
|
|
1161
|
+
const push = (field, message) => gaps.push({ reference: item.reference, name, field, message });
|
|
1162
|
+
if (!onlyDigits(payee.document)) push("document", "CPF/CNPJ");
|
|
1163
|
+
if (item.paymentForm === "pix") {
|
|
1164
|
+
if (!payee.pixKey?.trim()) push("pixKey", "chave PIX");
|
|
1165
|
+
else if (payee.pixKey.trim().toUpperCase().startsWith("00020")) {
|
|
1166
|
+
push("pixKeyTypeEmv", "o campo tem um PIX Copia-e-Cola (EMV), que o CNAB 240 n\xE3o aceita \u2014 informe a chave real");
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (item.paymentForm === "ted" && (!payee.bankCode?.trim() || !payee.account?.trim())) {
|
|
1170
|
+
push("bankDetails", "banco, ag\xEAncia e conta");
|
|
1171
|
+
}
|
|
1172
|
+
if (item.paymentForm === "boleto" || item.paymentForm === "tributo") {
|
|
1173
|
+
const barcode = linhaDigitavelToBarcode(item.barcode || "");
|
|
1174
|
+
if (barcode.length !== 44) push("barcode", "c\xF3digo de barras ou linha digit\xE1vel");
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
return gaps;
|
|
1178
|
+
}
|
|
1179
|
+
function suggestPaymentForm(barcode) {
|
|
1180
|
+
const raw = (barcode || "").trim();
|
|
1181
|
+
const digits2 = onlyDigits(raw);
|
|
1182
|
+
if (/^\d+$/.test(raw) && digits2.length >= 44 && digits2.length <= 48) return "boleto";
|
|
1183
|
+
return "pix";
|
|
1184
|
+
}
|
|
1185
|
+
function inferPixKeyType(key) {
|
|
1186
|
+
const raw = key.trim();
|
|
1187
|
+
const digits2 = onlyDigits(raw);
|
|
1188
|
+
if (raw.toUpperCase().startsWith("00020")) return "aleatoria";
|
|
1189
|
+
if (digits2.length === 11 && !raw.includes("@")) return "cpf";
|
|
1190
|
+
if (digits2.length === 14) return "cnpj";
|
|
1191
|
+
if (raw.includes("@")) return "email";
|
|
1192
|
+
if (digits2.length >= 10 && digits2.length <= 13) return "telefone";
|
|
1193
|
+
return "aleatoria";
|
|
1194
|
+
}
|
|
1195
|
+
function splitAccountDigit(value, digit) {
|
|
1196
|
+
if (digit.trim()) return [value.trim(), digit.trim()];
|
|
1197
|
+
const match = value.trim().match(/^(\d+)\s*[-/.]\s*([0-9xX])$/);
|
|
1198
|
+
return match ? [match[1], match[2].toUpperCase()] : [value.trim(), ""];
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/lib/br-banks.ts
|
|
1202
|
+
var LOGO_BASE = "https://cdn.jsdelivr.net/gh/Tgentil/Bancos-em-SVG@fe1d43f0cf379135bd01c987bc147a04fdf48c6d";
|
|
1203
|
+
var logo = (path) => `${LOGO_BASE}/${path.split("/").map(encodeURIComponent).join("/")}`;
|
|
1204
|
+
var BR_BANKS = [
|
|
1205
|
+
{ code: "001", name: "Banco do Brasil", aliases: ["bb"], logo: logo("Banco do Brasil S.A/banco-do-brasil-sem-fundo.svg") },
|
|
1206
|
+
{ code: "341", name: "Ita\xFA", aliases: ["itau", "ita\xFA unibanco"], logo: logo("Ita\xFA Unibanco S.A/itau.svg") },
|
|
1207
|
+
{ code: "237", name: "Bradesco", logo: logo("Bradesco S.A/bradesco.svg") },
|
|
1208
|
+
{ code: "104", name: "Caixa Econ\xF4mica Federal", aliases: ["caixa", "cef"], logo: logo("Caixa Econ\xF4mica Federal/caixa-economica-federal-1.svg") },
|
|
1209
|
+
{ code: "033", name: "Santander", logo: logo("Banco Santander Brasil S.A/banco-santander-logo.svg") },
|
|
1210
|
+
{ code: "260", name: "Nubank", aliases: ["nu pagamentos"], logo: logo("Nu Pagamentos S.A/nubank-logo-2021.svg") },
|
|
1211
|
+
{ code: "077", name: "Inter", aliases: ["banco inter"], logo: logo("Banco Inter S.A/inter.svg") },
|
|
1212
|
+
{ code: "336", name: "C6 Bank", aliases: ["c6"], logo: logo("Banco C6 S.A/c6 bank.svg") },
|
|
1213
|
+
{ code: "208", name: "BTG Pactual", aliases: ["btg"], logo: logo("Banco BTG Pacutal/btg-pactual.svg") },
|
|
1214
|
+
{ code: "756", name: "Sicoob", logo: logo("Sicoob/sicoob-vector-logo.svg") },
|
|
1215
|
+
{ code: "748", name: "Sicredi", logo: logo("Sicredi/logo-svg2.svg") },
|
|
1216
|
+
{ code: "290", name: "PagBank", aliases: ["pagseguro"], logo: logo("PagSeguro Internet S.A/logo-pagbank.svg") },
|
|
1217
|
+
{ code: "197", name: "Stone", logo: logo("Stone Pagamentos S.A/stone.svg") },
|
|
1218
|
+
{ code: "323", name: "Mercado Pago", logo: logo("Mercado Pago/mercado-pago.svg") },
|
|
1219
|
+
{ code: "542", name: "InfinitePay", aliases: ["cloudwalk"], logo: logo("InfinitePay/InfitePay.svg") },
|
|
1220
|
+
{ code: "403", name: "Cora", logo: logo("Cora Sociedade Credito Direto S.A/icone-cora-rosa-pequeno.svg") },
|
|
1221
|
+
{ code: "380", name: "PicPay", logo: logo("PicPay/Logo-PicPay.svg") },
|
|
1222
|
+
{ code: "364", name: "Ef\xED", aliases: ["gerencianet", "efi bank"], logo: logo("Ef\xED - Gerencianet/logo-efi-bank-laranja.svg") },
|
|
1223
|
+
{ code: "461", name: "Asaas", logo: logo("Asaas IP S.A/header-logo-azul.svg") },
|
|
1224
|
+
{ code: "422", name: "Safra", logo: logo("Banco Safra S.A/logo-safra.svg") },
|
|
1225
|
+
{ code: "212", name: "Banco Original", aliases: ["original"], logo: logo("Banco Original S.A/banco-original-logo-verde.svg") },
|
|
1226
|
+
{ code: "655", name: "Banco BV", aliases: ["votorantim", "bv"], logo: logo("Banco Votorantim/banco-bv-logo.svg") },
|
|
1227
|
+
{ code: "041", name: "Banrisul", logo: logo("Banrisul/banrisul-logo-2023.svg") },
|
|
1228
|
+
{ code: "070", name: "BRB", aliases: ["banco de bras\xEDlia"], logo: logo("BRB - Banco de Brasilia/brb-logo.svg") },
|
|
1229
|
+
{ code: "136", name: "Unicred", logo: logo("Unicred/verde.svg") },
|
|
1230
|
+
{ code: "133", name: "Cresol", logo: logo("Cresol/Icone-original.svg") },
|
|
1231
|
+
{ code: "085", name: "Ailos", logo: logo("Ailos/logo-ailos.svg") },
|
|
1232
|
+
{ code: "004", name: "Banco do Nordeste", aliases: ["bnb"], logo: logo("Banco do Nordeste do Brasil S.A/Logo_BNB.svg") },
|
|
1233
|
+
{ code: "021", name: "Banestes", logo: logo("Banco do Estado do Espirito Santo/banestes.svg") },
|
|
1234
|
+
{ code: "037", name: "Banpar\xE1", logo: logo("Banco do Estado do Para/Logo_do_Banpar\xE1.svg") },
|
|
1235
|
+
{ code: "047", name: "Banese", logo: logo("Banco do Estado do Sergipe/logo banese.svg") },
|
|
1236
|
+
{ code: "389", name: "Mercantil do Brasil", aliases: ["mercantil"], logo: logo("Banco Mercantil do Brasil S.A/banco-mercantil-novo-azul.svg") },
|
|
1237
|
+
{ code: "318", name: "BMG", logo: logo("Banco BMG/banco-bmg-logo.svg") },
|
|
1238
|
+
{ code: "707", name: "Daycoval", logo: logo("Banco Daycoval/logo-Daycoval.svg") },
|
|
1239
|
+
{ code: "246", name: "ABC Brasil", logo: logo("ABC Brasil/logoabc.svg") },
|
|
1240
|
+
{ code: "218", name: "BS2", logo: logo("Banco BS2 S.A/Banco_BS2.svg") },
|
|
1241
|
+
{ code: "102", name: "XP", aliases: ["xp investimentos"], logo: logo("XP Investimentos/xp-investimentos-logo.svg") },
|
|
1242
|
+
{ code: "536", name: "Neon", logo: logo("Neon/header-logo-neon.svg") }
|
|
1243
|
+
];
|
|
1244
|
+
var norm = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
|
|
1245
|
+
var digits = (s) => s.replace(/\D/g, "");
|
|
1246
|
+
function findBrBank(code, name) {
|
|
1247
|
+
const c = digits(code ?? "");
|
|
1248
|
+
if (c) {
|
|
1249
|
+
const byCode = BR_BANKS.find((b) => Number(b.code) === Number(c));
|
|
1250
|
+
if (byCode) return byCode;
|
|
1251
|
+
}
|
|
1252
|
+
const n = norm(name ?? "");
|
|
1253
|
+
if (!n) return void 0;
|
|
1254
|
+
return BR_BANKS.find((b) => norm(b.name) === n || b.aliases?.some((a) => norm(a) === n));
|
|
1255
|
+
}
|
|
1256
|
+
function searchBrBanks(query) {
|
|
1257
|
+
const q = norm(query);
|
|
1258
|
+
if (!q) return BR_BANKS;
|
|
1259
|
+
const qd = digits(q);
|
|
1260
|
+
return BR_BANKS.filter(
|
|
1261
|
+
(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))
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
function BankLogo({ bank, size: size2 = 20 }) {
|
|
1265
|
+
const [broken, setBroken] = useState(false);
|
|
1266
|
+
if (!bank?.logo || broken) {
|
|
1267
|
+
return /* @__PURE__ */ jsx(Landmark, { className: "text-muted-foreground", style: { width: size2 * 0.8, height: size2 * 0.8 }, "aria-hidden": true });
|
|
1268
|
+
}
|
|
1269
|
+
return /* @__PURE__ */ jsx(
|
|
1270
|
+
"img",
|
|
1271
|
+
{
|
|
1272
|
+
src: bank.logo,
|
|
1273
|
+
alt: "",
|
|
1274
|
+
width: size2,
|
|
1275
|
+
height: size2,
|
|
1276
|
+
loading: "lazy",
|
|
1277
|
+
onError: () => setBroken(true),
|
|
1278
|
+
className: "shrink-0 object-contain",
|
|
1279
|
+
style: { width: size2, height: size2 }
|
|
1280
|
+
}
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
var fromCatalog = (entry) => entry.compe ? { code: entry.compe, name: entry.name, logo: entry.logoUrl } : null;
|
|
1284
|
+
function BankPicker({ code, name, search, onChange }) {
|
|
1285
|
+
const known = findBrBank(code, name);
|
|
1286
|
+
const toOption = (b) => ({
|
|
1287
|
+
id: b.code,
|
|
1288
|
+
label: b.name,
|
|
1289
|
+
subtitle: b.code,
|
|
1290
|
+
icon: /* @__PURE__ */ jsx(BankLogo, { bank: b }),
|
|
1291
|
+
data: b
|
|
1292
|
+
});
|
|
1293
|
+
const onSearch = async (query) => {
|
|
1294
|
+
const local = searchBrBanks(query);
|
|
1295
|
+
if (!search) return local.map(toOption);
|
|
1296
|
+
try {
|
|
1297
|
+
const remote = (await search(query)).map(fromCatalog).filter(Boolean);
|
|
1298
|
+
if (!remote.length) return local.map(toOption);
|
|
1299
|
+
const merged = remote.map((bank) => findBrBank(bank.code) ?? bank);
|
|
1300
|
+
const seen = new Set(merged.map((bank) => bank.code));
|
|
1301
|
+
return [...merged, ...local.filter((bank) => !seen.has(bank.code))].map(toOption);
|
|
1302
|
+
} catch {
|
|
1303
|
+
return local.map(toOption);
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
return /* @__PURE__ */ jsx(
|
|
1307
|
+
SearchSelect,
|
|
1308
|
+
{
|
|
1309
|
+
value: name ? known?.code ?? `name:${name}` : "",
|
|
1310
|
+
displayValue: name ?? "",
|
|
1311
|
+
leading: name ? /* @__PURE__ */ jsx(BankLogo, { bank: known, size: 18 }) : void 0,
|
|
1312
|
+
placeholder: "Busque pelo nome ou n\xFAmero",
|
|
1313
|
+
minChars: 0,
|
|
1314
|
+
loadOnOpen: true,
|
|
1315
|
+
onSearch,
|
|
1316
|
+
onChange: (_id, option) => {
|
|
1317
|
+
const b = option?.data;
|
|
1318
|
+
onChange(b ? { code: b.code, name: b.name } : { code: "", name: "" });
|
|
1319
|
+
},
|
|
1320
|
+
allowCreate: true,
|
|
1321
|
+
createLabel: "Usar",
|
|
1322
|
+
clearLabel: "Limpar",
|
|
1323
|
+
onCreate: (q) => onChange({ code: code ?? "", name: q.trim() }),
|
|
1324
|
+
noResultsLabel: "Nenhum banco com esse nome",
|
|
1325
|
+
debounce: 0
|
|
1326
|
+
}
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
868
1329
|
var STEPS = [
|
|
869
1330
|
{ id: "account", label: "Conta banc\xE1ria" },
|
|
870
1331
|
{ id: "payments", label: "Pagamentos" },
|
|
871
1332
|
{ id: "beneficiaries", label: "Favorecidos" },
|
|
872
1333
|
{ id: "review", label: "Revis\xE3o" }
|
|
873
1334
|
];
|
|
1335
|
+
var FORM_LABEL = {
|
|
1336
|
+
pix: "PIX",
|
|
1337
|
+
ted: "TED/DOC",
|
|
1338
|
+
boleto: "Boleto",
|
|
1339
|
+
tributo: "Tributo"
|
|
1340
|
+
};
|
|
1341
|
+
var PIX_KEY_TYPES = [
|
|
1342
|
+
{ value: "cpf", label: "CPF" },
|
|
1343
|
+
{ value: "cnpj", label: "CNPJ" },
|
|
1344
|
+
{ value: "email", label: "E-mail" },
|
|
1345
|
+
{ value: "telefone", label: "Telefone" },
|
|
1346
|
+
{ value: "aleatoria", label: "Aleat\xF3ria" }
|
|
1347
|
+
];
|
|
1348
|
+
var todayISO = () => {
|
|
1349
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
1350
|
+
return `${now2.getFullYear()}-${String(now2.getMonth() + 1).padStart(2, "0")}-${String(now2.getDate()).padStart(2, "0")}`;
|
|
1351
|
+
};
|
|
874
1352
|
function textMetadata(account, key) {
|
|
875
1353
|
const value = account.metadata?.[key];
|
|
876
1354
|
return typeof value === "string" ? value : "";
|
|
877
1355
|
}
|
|
878
1356
|
function draftFromAccount(account) {
|
|
1357
|
+
const [agencyNumber, agencyDigit] = splitAccountDigit(account.agencyNumber || "", textMetadata(account, "agencyDigit"));
|
|
1358
|
+
const [accountNumber, accountDigit] = splitAccountDigit(account.accountNumber || "", textMetadata(account, "accountDigit"));
|
|
879
1359
|
return {
|
|
880
|
-
bankName: account.bankName
|
|
881
|
-
bankCode: textMetadata(account, "bankCode"),
|
|
882
|
-
agencyNumber
|
|
883
|
-
agencyDigit
|
|
884
|
-
accountNumber
|
|
885
|
-
accountDigit
|
|
1360
|
+
bankName: account.bankName || "",
|
|
1361
|
+
bankCode: account.bankCode || textMetadata(account, "bankCode"),
|
|
1362
|
+
agencyNumber,
|
|
1363
|
+
agencyDigit,
|
|
1364
|
+
accountNumber,
|
|
1365
|
+
accountDigit,
|
|
886
1366
|
agreementCode: textMetadata(account, "cnabAgreementCode")
|
|
887
1367
|
};
|
|
888
1368
|
}
|
|
889
|
-
function missingFields(draft) {
|
|
1369
|
+
function missingFields(draft, companyDocument) {
|
|
890
1370
|
return [
|
|
891
1371
|
!draft.bankCode.trim() && "c\xF3digo do banco",
|
|
892
1372
|
!draft.agencyNumber.trim() && "ag\xEAncia",
|
|
893
|
-
!draft.accountNumber.trim() && "conta"
|
|
1373
|
+
!draft.accountNumber.trim() && "conta",
|
|
1374
|
+
!companyDocument.replace(/\D/g, "") && "CNPJ da empresa"
|
|
894
1375
|
].filter(Boolean);
|
|
895
1376
|
}
|
|
1377
|
+
var emptyPayee = (invoice) => ({
|
|
1378
|
+
contactId: invoice.contactId ?? "",
|
|
1379
|
+
name: invoice.contactName || invoice.itemsSummary || "Sem favorecido",
|
|
1380
|
+
document: ""
|
|
1381
|
+
});
|
|
1382
|
+
function lineFromInvoice(invoice, payee) {
|
|
1383
|
+
const base = payee ?? emptyPayee(invoice);
|
|
1384
|
+
const barcode = typeof invoice.metadata?.barcode === "string" ? invoice.metadata.barcode : "";
|
|
1385
|
+
const paymentForm = suggestPaymentForm(barcode);
|
|
1386
|
+
const resolved = { ...base, name: base.name || invoice.contactName || "Sem favorecido" };
|
|
1387
|
+
if (!resolved.pixKey?.trim() && resolved.document.replace(/\D/g, "")) {
|
|
1388
|
+
resolved.pixKey = resolved.document;
|
|
1389
|
+
resolved.pixKeyType = inferPixKeyType(resolved.document);
|
|
1390
|
+
} else if (resolved.pixKey?.trim() && !resolved.pixKeyType?.trim()) {
|
|
1391
|
+
resolved.pixKeyType = inferPixKeyType(resolved.pixKey);
|
|
1392
|
+
}
|
|
1393
|
+
const today5 = todayISO();
|
|
1394
|
+
return {
|
|
1395
|
+
invoice,
|
|
1396
|
+
paymentForm,
|
|
1397
|
+
// Paying on a date already past is a rejected line; the due date only wins
|
|
1398
|
+
// while it is still ahead.
|
|
1399
|
+
paymentDate: invoice.dueDate && invoice.dueDate >= today5 ? invoice.dueDate : today5,
|
|
1400
|
+
barcode: paymentForm === "boleto" ? barcode : "",
|
|
1401
|
+
payee: resolved
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
var openAmount = (invoice) => Math.max(0, invoice.totalAmount - invoice.paidAmount);
|
|
896
1405
|
function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
|
|
897
1406
|
const { currency } = useFinancialConfig();
|
|
898
|
-
const [step, setStep] =
|
|
899
|
-
const [bankAccountId, setBankAccountId] =
|
|
900
|
-
const [draft, setDraft] =
|
|
901
|
-
const [
|
|
902
|
-
const [
|
|
1407
|
+
const [step, setStep] = React25__default.useState(0);
|
|
1408
|
+
const [bankAccountId, setBankAccountId] = React25__default.useState("");
|
|
1409
|
+
const [draft, setDraft] = React25__default.useState(null);
|
|
1410
|
+
const [context, setContext] = React25__default.useState(null);
|
|
1411
|
+
const [companyName, setCompanyName] = React25__default.useState("");
|
|
1412
|
+
const [companyDocument, setCompanyDocument] = React25__default.useState("");
|
|
1413
|
+
const [lines, setLines] = React25__default.useState([]);
|
|
1414
|
+
const [companyUnsaved, setCompanyUnsaved] = React25__default.useState(false);
|
|
1415
|
+
const [loadingContext, setLoadingContext] = React25__default.useState(false);
|
|
1416
|
+
const [busy, setBusy] = React25__default.useState(false);
|
|
1417
|
+
const [error, setError] = React25__default.useState("");
|
|
903
1418
|
const eligibleAccounts2 = bankAccounts.filter((account2) => account2.accountType === "bank" && account2.isActive);
|
|
904
|
-
const total = invoices.reduce((sum, invoice) => sum +
|
|
1419
|
+
const total = invoices.reduce((sum, invoice) => sum + openAmount(invoice), 0);
|
|
905
1420
|
const account = eligibleAccounts2.find((candidate) => candidate.id === bankAccountId);
|
|
906
|
-
const missingBankData = draft ? missingFields(draft) : [];
|
|
907
|
-
const
|
|
1421
|
+
const missingBankData = draft ? missingFields(draft, companyDocument) : [];
|
|
1422
|
+
const items = React25__default.useMemo(() => lines.map((line) => ({
|
|
1423
|
+
reference: line.invoice.id,
|
|
1424
|
+
amount: openAmount(line.invoice),
|
|
1425
|
+
paymentDate: line.paymentDate,
|
|
1426
|
+
paymentForm: line.paymentForm,
|
|
1427
|
+
barcode: line.barcode,
|
|
1428
|
+
payee: {
|
|
1429
|
+
name: line.payee.name,
|
|
1430
|
+
document: line.payee.document,
|
|
1431
|
+
bankCode: line.payee.bankCode,
|
|
1432
|
+
agency: line.payee.agencyNumber,
|
|
1433
|
+
agencyDigit: line.payee.agencyDigit,
|
|
1434
|
+
account: line.payee.accountNumber,
|
|
1435
|
+
accountDigit: line.payee.accountDigit,
|
|
1436
|
+
pixKey: line.payee.pixKey,
|
|
1437
|
+
pixKeyType: line.payee.pixKeyType,
|
|
1438
|
+
street: line.payee.street,
|
|
1439
|
+
number: line.payee.number,
|
|
1440
|
+
complement: line.payee.complement,
|
|
1441
|
+
district: line.payee.district,
|
|
1442
|
+
city: line.payee.city,
|
|
1443
|
+
state: line.payee.state,
|
|
1444
|
+
postalCode: line.payee.postalCode
|
|
1445
|
+
}
|
|
1446
|
+
})), [lines]);
|
|
1447
|
+
const gaps = React25__default.useMemo(() => cnabPayeeGaps(items), [items]);
|
|
1448
|
+
const batches = React25__default.useMemo(() => {
|
|
1449
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1450
|
+
for (const line of lines) {
|
|
1451
|
+
const current = grouped.get(line.paymentForm) ?? { count: 0, total: 0 };
|
|
1452
|
+
current.count += 1;
|
|
1453
|
+
current.total += openAmount(line.invoice);
|
|
1454
|
+
grouped.set(line.paymentForm, current);
|
|
1455
|
+
}
|
|
1456
|
+
return [...grouped.entries()];
|
|
1457
|
+
}, [lines]);
|
|
1458
|
+
const selectAccount = async (id) => {
|
|
908
1459
|
const selected = eligibleAccounts2.find((candidate) => candidate.id === id);
|
|
909
1460
|
setBankAccountId(id);
|
|
910
1461
|
setDraft(selected ? draftFromAccount(selected) : null);
|
|
911
|
-
|
|
1462
|
+
setError("");
|
|
1463
|
+
if (!selected || !provider.getCnabContext) {
|
|
1464
|
+
setLines(invoices.map((invoice) => lineFromInvoice(invoice, void 0)));
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
setLoadingContext(true);
|
|
1468
|
+
try {
|
|
1469
|
+
const loaded = await provider.getCnabContext({
|
|
1470
|
+
contactIds: invoices.map((invoice) => invoice.contactId ?? "").filter(Boolean),
|
|
1471
|
+
bankAccountId: id,
|
|
1472
|
+
unitId: selected.unitId
|
|
1473
|
+
});
|
|
1474
|
+
setContext(loaded);
|
|
1475
|
+
setCompanyName(loaded.company.name);
|
|
1476
|
+
setCompanyDocument(loaded.company.document);
|
|
1477
|
+
const byContact = new Map(loaded.payees.map((payee) => [payee.contactId, payee]));
|
|
1478
|
+
setLines(invoices.map((invoice) => lineFromInvoice(invoice, byContact.get(invoice.contactId ?? ""))));
|
|
1479
|
+
} catch (cause) {
|
|
1480
|
+
setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel carregar os favorecidos.");
|
|
1481
|
+
setLines(invoices.map((invoice) => lineFromInvoice(invoice, void 0)));
|
|
1482
|
+
} finally {
|
|
1483
|
+
setLoadingContext(false);
|
|
1484
|
+
}
|
|
912
1485
|
};
|
|
913
1486
|
const updateDraft = (key, value) => {
|
|
914
1487
|
setDraft((current) => current ? { ...current, [key]: value } : current);
|
|
915
|
-
|
|
1488
|
+
setError("");
|
|
1489
|
+
};
|
|
1490
|
+
const updateLine = (index, patch) => setLines((current) => current.map((line, i) => i === index ? { ...line, ...patch } : line));
|
|
1491
|
+
const updatePayee = (index, patch) => setLines((current) => current.map((line, i) => i === index ? { ...line, payee: { ...line.payee, ...patch } } : line));
|
|
1492
|
+
const saveBankDraft = async () => {
|
|
1493
|
+
if (!account || !draft) return;
|
|
1494
|
+
await provider.updateBankAccount(account.id, {
|
|
1495
|
+
bankCode: draft.bankCode.trim(),
|
|
1496
|
+
bankName: draft.bankName.trim() || void 0,
|
|
1497
|
+
agencyNumber: draft.agencyNumber.trim(),
|
|
1498
|
+
accountNumber: draft.accountNumber.trim(),
|
|
1499
|
+
metadata: {
|
|
1500
|
+
...account.metadata,
|
|
1501
|
+
agencyDigit: draft.agencyDigit.trim(),
|
|
1502
|
+
accountDigit: draft.accountDigit.trim(),
|
|
1503
|
+
cnabAgreementCode: draft.agreementCode.trim()
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
};
|
|
1507
|
+
const saveCompany = async () => {
|
|
1508
|
+
if (!provider.saveCompany) return;
|
|
1509
|
+
const loaded = context?.company;
|
|
1510
|
+
const unchanged = loaded && loaded.document.replace(/\D/g, "") === companyDocument.replace(/\D/g, "") && loaded.name === companyName;
|
|
1511
|
+
if (unchanged) return;
|
|
1512
|
+
try {
|
|
1513
|
+
const saved = await provider.saveCompany({
|
|
1514
|
+
id: loaded?.id,
|
|
1515
|
+
unitId: account?.unitId,
|
|
1516
|
+
name: companyName,
|
|
1517
|
+
document: companyDocument
|
|
1518
|
+
});
|
|
1519
|
+
setContext((current) => current ? { ...current, company: { ...current.company, id: saved.id, name: companyName, document: companyDocument } } : current);
|
|
1520
|
+
} catch {
|
|
1521
|
+
setCompanyUnsaved(true);
|
|
1522
|
+
}
|
|
916
1523
|
};
|
|
917
1524
|
const advance = async () => {
|
|
918
|
-
|
|
919
|
-
|
|
1525
|
+
setError("");
|
|
1526
|
+
if (step === 0) {
|
|
1527
|
+
if (!account || !draft || missingBankData.length > 0) return;
|
|
1528
|
+
setBusy(true);
|
|
1529
|
+
try {
|
|
1530
|
+
await saveBankDraft();
|
|
1531
|
+
await saveCompany();
|
|
1532
|
+
setStep(1);
|
|
1533
|
+
} catch (cause) {
|
|
1534
|
+
setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel salvar os dados banc\xE1rios.");
|
|
1535
|
+
} finally {
|
|
1536
|
+
setBusy(false);
|
|
1537
|
+
}
|
|
920
1538
|
return;
|
|
921
1539
|
}
|
|
922
|
-
if (
|
|
923
|
-
|
|
924
|
-
|
|
1540
|
+
if (step === 2 && gaps.length > 0) return;
|
|
1541
|
+
setStep((value) => Math.min(value + 1, 3));
|
|
1542
|
+
};
|
|
1543
|
+
const generate = async () => {
|
|
1544
|
+
if (!account || !draft) return;
|
|
1545
|
+
setBusy(true);
|
|
1546
|
+
setError("");
|
|
925
1547
|
try {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1548
|
+
const sequence = context?.nextSequence ?? 1;
|
|
1549
|
+
const file = generateCnab240({
|
|
1550
|
+
sequence,
|
|
1551
|
+
company: { name: companyName, document: companyDocument },
|
|
1552
|
+
bank: {
|
|
1553
|
+
code: draft.bankCode,
|
|
1554
|
+
name: draft.bankName,
|
|
1555
|
+
agency: draft.agencyNumber,
|
|
1556
|
+
agencyDigit: draft.agencyDigit,
|
|
1557
|
+
account: draft.accountNumber,
|
|
1558
|
+
accountDigit: draft.accountDigit,
|
|
1559
|
+
agreement: draft.agreementCode
|
|
1560
|
+
},
|
|
1561
|
+
items
|
|
937
1562
|
});
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1563
|
+
if (provider.saveCnabPayees) {
|
|
1564
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1565
|
+
const payload = lines.filter((line) => {
|
|
1566
|
+
if (!line.payee.contactId || seen.has(line.payee.contactId)) return false;
|
|
1567
|
+
seen.add(line.payee.contactId);
|
|
1568
|
+
return true;
|
|
1569
|
+
}).map((line) => ({
|
|
1570
|
+
contactId: line.payee.contactId,
|
|
1571
|
+
payeeAccountId: line.payee.payeeAccountId,
|
|
1572
|
+
document: line.payee.document,
|
|
1573
|
+
bankCode: line.payee.bankCode,
|
|
1574
|
+
bankName: line.payee.bankName,
|
|
1575
|
+
agencyNumber: line.payee.agencyNumber,
|
|
1576
|
+
agencyDigit: line.payee.agencyDigit,
|
|
1577
|
+
accountNumber: line.payee.accountNumber,
|
|
1578
|
+
accountDigit: line.payee.accountDigit,
|
|
1579
|
+
accountType: line.payee.accountType,
|
|
1580
|
+
pixKey: line.payee.pixKey,
|
|
1581
|
+
pixKeyType: line.payee.pixKeyType
|
|
1582
|
+
}));
|
|
1583
|
+
if (payload.length) await provider.saveCnabPayees(payload);
|
|
1584
|
+
}
|
|
1585
|
+
if (provider.recordCnabBatch) {
|
|
1586
|
+
await provider.recordCnabBatch({
|
|
1587
|
+
bankAccountId: account.id,
|
|
1588
|
+
unitId: account.unitId,
|
|
1589
|
+
layout: file.layout,
|
|
1590
|
+
sequenceNumber: sequence,
|
|
1591
|
+
totalAmount: file.totalAmount,
|
|
1592
|
+
fileName: file.fileName,
|
|
1593
|
+
fileContent: file.content,
|
|
1594
|
+
items: lines.map((line) => ({
|
|
1595
|
+
movementId: line.invoice.id,
|
|
1596
|
+
paymentForm: line.paymentForm,
|
|
1597
|
+
paymentDate: line.paymentDate,
|
|
1598
|
+
amount: openAmount(line.invoice)
|
|
1599
|
+
}))
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
const blob = new Blob([file.content], { type: "text/plain;charset=us-ascii" });
|
|
1603
|
+
const url = URL.createObjectURL(blob);
|
|
1604
|
+
const anchor = document.createElement("a");
|
|
1605
|
+
anchor.href = url;
|
|
1606
|
+
anchor.download = file.fileName;
|
|
1607
|
+
anchor.click();
|
|
1608
|
+
URL.revokeObjectURL(url);
|
|
1609
|
+
onClose();
|
|
1610
|
+
} catch (cause) {
|
|
1611
|
+
setError(cause instanceof Error ? cause.message : "N\xE3o foi poss\xEDvel gerar o arquivo.");
|
|
941
1612
|
} finally {
|
|
942
|
-
|
|
1613
|
+
setBusy(false);
|
|
943
1614
|
}
|
|
944
1615
|
};
|
|
1616
|
+
const canAdvance = step === 0 ? !!account && !!draft && missingBankData.length === 0 && !loadingContext : step === 2 ? gaps.length === 0 : true;
|
|
945
1617
|
return createPortal(
|
|
946
1618
|
/* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
947
1619
|
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-[2px]", onClick: onClose }),
|
|
@@ -983,82 +1655,203 @@ function CnabRemittanceModal({ invoices, bankAccounts, provider, onClose }) {
|
|
|
983
1655
|
id: candidate.id,
|
|
984
1656
|
label: candidate.name,
|
|
985
1657
|
subtitle: [candidate.bankName, candidate.agencyNumber && `Ag ${candidate.agencyNumber}`, candidate.accountNumber && `Cc ${candidate.accountNumber}`].filter(Boolean).join(" \xB7 "),
|
|
986
|
-
icon: /* @__PURE__ */ jsx(
|
|
1658
|
+
icon: /* @__PURE__ */ jsx(BankLogo, { bank: findBrBank(candidate.bankCode || textMetadata(candidate, "bankCode"), candidate.bankName) })
|
|
987
1659
|
}));
|
|
988
1660
|
},
|
|
989
|
-
onChange: (id) => selectAccount(id)
|
|
1661
|
+
onChange: (id) => void selectAccount(id)
|
|
990
1662
|
}
|
|
991
1663
|
),
|
|
992
|
-
draft ? /* @__PURE__ */ jsxs(
|
|
993
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
994
|
-
/* @__PURE__ */
|
|
995
|
-
|
|
1664
|
+
draft ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1665
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border bg-muted/20 p-4", children: [
|
|
1666
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1667
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Empresa pagadora" }),
|
|
1668
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: companyUnsaved ? "Vale para este arquivo: seu perfil n\xE3o pode gravar o cadastro da empresa. Pe\xE7a a um respons\xE1vel para cadastr\xE1-la." : context?.company.id ? "\xC9 este CNPJ que vai no cabe\xE7alho do arquivo." : "Esta unidade ainda n\xE3o tem empresa cadastrada. O que voc\xEA informar aqui vira o cadastro dela, e a pr\xF3xima remessa j\xE1 abre preenchida." })
|
|
1669
|
+
] }),
|
|
1670
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
1671
|
+
/* @__PURE__ */ jsx(Field, { label: "Raz\xE3o social / nome", value: companyName, onChange: setCompanyName }),
|
|
1672
|
+
/* @__PURE__ */ jsx(Field, { label: "CNPJ", required: true, value: companyDocument, placeholder: "00.000.000/0000-00", onChange: setCompanyDocument })
|
|
1673
|
+
] })
|
|
996
1674
|
] }),
|
|
997
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
998
|
-
/* @__PURE__ */
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
/* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: draft.agencyNumber, onChange: (value) => updateDraft("agencyNumber", value), className: "col-span-2" }),
|
|
1002
|
-
/* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.agencyDigit, onChange: (value) => updateDraft("agencyDigit", value) })
|
|
1675
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border bg-muted/20 p-4", children: [
|
|
1676
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1677
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados da conta para a remessa" }),
|
|
1678
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Preencha uma vez; os pr\xF3ximos arquivos reutilizam esta configura\xE7\xE3o." })
|
|
1003
1679
|
] }),
|
|
1004
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-
|
|
1005
|
-
/* @__PURE__ */
|
|
1006
|
-
|
|
1680
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
1681
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5 sm:col-span-2", children: [
|
|
1682
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
|
|
1683
|
+
"Banco",
|
|
1684
|
+
/* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
|
|
1685
|
+
] }),
|
|
1686
|
+
/* @__PURE__ */ jsx(
|
|
1687
|
+
BankPicker,
|
|
1688
|
+
{
|
|
1689
|
+
code: draft.bankCode,
|
|
1690
|
+
name: draft.bankName,
|
|
1691
|
+
search: provider.searchBanks ? (query) => provider.searchBanks(query) : void 0,
|
|
1692
|
+
onChange: ({ code, name }) => setDraft((current) => current ? { ...current, bankCode: code || current.bankCode, bankName: name } : current)
|
|
1693
|
+
}
|
|
1694
|
+
),
|
|
1695
|
+
draft.bankCode ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
1696
|
+
"C\xF3digo ",
|
|
1697
|
+
draft.bankCode
|
|
1698
|
+
] }) : null
|
|
1699
|
+
] }),
|
|
1700
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
1701
|
+
/* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: draft.agencyNumber, onChange: (value) => updateDraft("agencyNumber", value), className: "col-span-2" }),
|
|
1702
|
+
/* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.agencyDigit, onChange: (value) => updateDraft("agencyDigit", value) })
|
|
1703
|
+
] }),
|
|
1704
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
1705
|
+
/* @__PURE__ */ jsx(Field, { label: "Conta", required: true, value: draft.accountNumber, onChange: (value) => updateDraft("accountNumber", value), className: "col-span-2" }),
|
|
1706
|
+
/* @__PURE__ */ jsx(Field, { label: "D\xEDgito", value: draft.accountDigit, onChange: (value) => updateDraft("accountDigit", value) })
|
|
1707
|
+
] }),
|
|
1708
|
+
/* @__PURE__ */ jsx(Field, { label: "Conv\xEAnio / c\xF3digo da empresa", value: draft.agreementCode, placeholder: "Opcional para alguns bancos", onChange: (value) => updateDraft("agreementCode", value), className: "sm:col-span-2" })
|
|
1007
1709
|
] }),
|
|
1008
|
-
/* @__PURE__ */
|
|
1009
|
-
] }),
|
|
1010
|
-
missingBankData.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex gap-2 rounded-lg border border-warning/30 bg-warning/10 p-3 text-sm", children: [
|
|
1011
|
-
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-warning" }),
|
|
1012
|
-
/* @__PURE__ */ jsxs("p", { children: [
|
|
1710
|
+
missingBankData.length > 0 ? /* @__PURE__ */ jsxs(Warning, { children: [
|
|
1013
1711
|
"Complete ",
|
|
1014
1712
|
missingBankData.join(", "),
|
|
1015
1713
|
" para continuar."
|
|
1016
|
-
] })
|
|
1017
|
-
] })
|
|
1018
|
-
saveError ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: saveError }) : null
|
|
1714
|
+
] }) : null
|
|
1715
|
+
] })
|
|
1019
1716
|
] }) : null,
|
|
1020
1717
|
eligibleAccounts2.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Cadastre uma conta banc\xE1ria ativa para iniciar uma remessa." }) : null
|
|
1021
1718
|
] }) : null,
|
|
1022
1719
|
step === 1 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1023
1720
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
1024
1721
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Pagamentos selecionados" }),
|
|
1025
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "
|
|
1722
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Defina a forma de pagamento e a data de pagamento de cada parcela." })
|
|
1026
1723
|
] }),
|
|
1027
|
-
|
|
1028
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1029
|
-
/* @__PURE__ */ jsx("p", { className: "truncate font-medium", children:
|
|
1724
|
+
lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-3 rounded-lg border p-3 sm:grid-cols-12", children: [
|
|
1725
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 sm:col-span-4", children: [
|
|
1726
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: line.payee.name }),
|
|
1030
1727
|
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
1031
|
-
"
|
|
1032
|
-
invoice.dueDate || "\u2014",
|
|
1033
|
-
invoice.fiscalNumber ? ` \xB7
|
|
1728
|
+
"Venc.: ",
|
|
1729
|
+
line.invoice.dueDate || "\u2014",
|
|
1730
|
+
line.invoice.fiscalNumber ? ` \xB7 NF ${line.invoice.fiscalNumber}` : ""
|
|
1034
1731
|
] })
|
|
1035
1732
|
] }),
|
|
1036
|
-
/* @__PURE__ */ jsx("
|
|
1037
|
-
|
|
1733
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-semibold tabular-nums sm:col-span-2", children: formatCurrency(openAmount(line.invoice), currency) }),
|
|
1734
|
+
/* @__PURE__ */ jsx("div", { className: "sm:col-span-3", children: /* @__PURE__ */ jsxs(Select, { value: line.paymentForm, onValueChange: (value) => updateLine(index, { paymentForm: value }), children: [
|
|
1735
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-9", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
1736
|
+
/* @__PURE__ */ jsx(SelectContent, { children: Object.keys(FORM_LABEL).map((form) => /* @__PURE__ */ jsx(SelectItem, { value: form, children: FORM_LABEL[form] }, form)) })
|
|
1737
|
+
] }) }),
|
|
1738
|
+
/* @__PURE__ */ jsx("div", { className: "sm:col-span-3", children: /* @__PURE__ */ jsx(DatePicker, { value: line.paymentDate, format: "numeric", onChange: (value) => updateLine(index, { paymentDate: value }) }) })
|
|
1739
|
+
] }, line.invoice.id))
|
|
1740
|
+
] }) : null,
|
|
1741
|
+
step === 2 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1742
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1743
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Dados dos favorecidos" }),
|
|
1744
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Confirme ou preencha o que falta. O que voc\xEA informar \xE9 salvo no cadastro para o pr\xF3ximo arquivo." })
|
|
1745
|
+
] }),
|
|
1746
|
+
lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border p-3", children: [
|
|
1747
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1748
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
|
|
1749
|
+
line.payee.name,
|
|
1750
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 rounded-full border px-2 py-0.5 text-xs font-normal text-muted-foreground", children: FORM_LABEL[line.paymentForm] })
|
|
1751
|
+
] }),
|
|
1752
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: formatCurrency(openAmount(line.invoice), currency) })
|
|
1753
|
+
] }),
|
|
1754
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-12", children: [
|
|
1755
|
+
/* @__PURE__ */ jsx(Field, { label: "CPF/CNPJ", required: true, value: line.payee.document, onChange: (value) => updatePayee(index, { document: value }), className: "sm:col-span-4" }),
|
|
1756
|
+
line.paymentForm === "pix" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1757
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5 sm:col-span-3", children: [
|
|
1758
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Tipo da chave" }),
|
|
1759
|
+
/* @__PURE__ */ jsxs(Select, { value: line.payee.pixKeyType || "", onValueChange: (value) => updatePayee(index, { pixKeyType: value }), children: [
|
|
1760
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-9", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Tipo" }) }),
|
|
1761
|
+
/* @__PURE__ */ jsx(SelectContent, { children: PIX_KEY_TYPES.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
|
|
1762
|
+
] })
|
|
1763
|
+
] }),
|
|
1764
|
+
/* @__PURE__ */ jsx(Field, { label: "Chave PIX", required: true, value: line.payee.pixKey || "", onChange: (value) => updatePayee(index, { pixKey: value, pixKeyType: line.payee.pixKeyType || inferPixKeyType(value) }), className: "sm:col-span-5" })
|
|
1765
|
+
] }) : null,
|
|
1766
|
+
line.paymentForm === "ted" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1767
|
+
/* @__PURE__ */ jsx(Field, { label: "Banco", required: true, value: line.payee.bankCode || "", onChange: (value) => updatePayee(index, { bankCode: value }), className: "sm:col-span-2" }),
|
|
1768
|
+
/* @__PURE__ */ jsx(Field, { label: "Ag\xEAncia", required: true, value: line.payee.agencyNumber || "", onChange: (value) => updatePayee(index, { agencyNumber: value }), className: "sm:col-span-2" }),
|
|
1769
|
+
/* @__PURE__ */ jsx(Field, { label: "DV", value: line.payee.agencyDigit || "", onChange: (value) => updatePayee(index, { agencyDigit: value }), className: "sm:col-span-1" }),
|
|
1770
|
+
/* @__PURE__ */ jsx(Field, { label: "Conta", required: true, value: line.payee.accountNumber || "", onChange: (value) => updatePayee(index, { accountNumber: value }), className: "sm:col-span-2" }),
|
|
1771
|
+
/* @__PURE__ */ jsx(Field, { label: "DV", value: line.payee.accountDigit || "", onChange: (value) => updatePayee(index, { accountDigit: value }), className: "sm:col-span-1" })
|
|
1772
|
+
] }) : null,
|
|
1773
|
+
line.paymentForm === "boleto" || line.paymentForm === "tributo" ? /* @__PURE__ */ jsx(Field, { label: "C\xF3digo de barras / linha digit\xE1vel", required: true, value: line.barcode, onChange: (value) => updateLine(index, { barcode: value }), className: "sm:col-span-8" }) : null
|
|
1774
|
+
] })
|
|
1775
|
+
] }, line.invoice.id)),
|
|
1776
|
+
gaps.length > 0 ? /* @__PURE__ */ jsxs(Warning, { children: [
|
|
1777
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Falta preencher:" }),
|
|
1778
|
+
/* @__PURE__ */ jsxs("ul", { className: "mt-1 space-y-0.5", children: [
|
|
1779
|
+
gaps.slice(0, 5).map((gap, index) => /* @__PURE__ */ jsxs("li", { children: [
|
|
1780
|
+
gap.name,
|
|
1781
|
+
": ",
|
|
1782
|
+
gap.message
|
|
1783
|
+
] }, `${gap.reference}-${gap.field}-${index}`)),
|
|
1784
|
+
gaps.length > 5 ? /* @__PURE__ */ jsxs("li", { className: "text-muted-foreground", children: [
|
|
1785
|
+
"e mais ",
|
|
1786
|
+
gaps.length - 5,
|
|
1787
|
+
"\u2026"
|
|
1788
|
+
] }) : null
|
|
1789
|
+
] })
|
|
1790
|
+
] }) : null
|
|
1038
1791
|
] }) : null,
|
|
1039
|
-
step
|
|
1040
|
-
/* @__PURE__ */ jsx("
|
|
1041
|
-
/* @__PURE__ */
|
|
1042
|
-
|
|
1043
|
-
|
|
1792
|
+
step === 3 ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1793
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Resumo da remessa" }),
|
|
1794
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 text-sm sm:grid-cols-2", children: [
|
|
1795
|
+
/* @__PURE__ */ jsx(Summary, { label: "Banco pagador", value: `${draft?.bankCode ?? ""} \u2014 ${draft?.bankName || "\u2014"}` }),
|
|
1796
|
+
/* @__PURE__ */ jsx(Summary, { label: "Conta", value: `Ag ${draft?.agencyNumber ?? ""} / Cc ${draft?.accountNumber ?? ""}${draft?.accountDigit ? `-${draft.accountDigit}` : ""}` }),
|
|
1797
|
+
/* @__PURE__ */ jsx(Summary, { label: "Empresa", value: `${companyName || "\u2014"} (${companyDocument || "\u2014"})` }),
|
|
1798
|
+
/* @__PURE__ */ jsx(Summary, { label: "Total", value: formatCurrency(total, currency), strong: true }),
|
|
1799
|
+
/* @__PURE__ */ jsx(Summary, { label: "N\xBA da remessa (NSA)", value: String(context?.nextSequence ?? 1) })
|
|
1800
|
+
] }),
|
|
1801
|
+
/* @__PURE__ */ jsxs("div", { className: "border-t pt-3", children: [
|
|
1802
|
+
/* @__PURE__ */ jsx("h4", { className: "mb-2 text-sm font-medium", children: "Lotes" }),
|
|
1803
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1 text-sm", children: batches.map(([form, group]) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
1804
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1805
|
+
FORM_LABEL[form],
|
|
1806
|
+
" \u2014 ",
|
|
1807
|
+
group.count,
|
|
1808
|
+
" t\xEDtulo(s)"
|
|
1809
|
+
] }),
|
|
1810
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums", children: formatCurrency(group.total, currency) })
|
|
1811
|
+
] }, form)) })
|
|
1812
|
+
] }),
|
|
1813
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Ao gerar, o arquivo .REM \xE9 baixado, a remessa \xE9 registrada no hist\xF3rico e os dados informados s\xE3o salvos nos cadastros. O status das contas n\xE3o muda." })
|
|
1814
|
+
] }) : null,
|
|
1815
|
+
error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-4 text-sm text-destructive", children: error }) : null
|
|
1044
1816
|
] }),
|
|
1045
1817
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-5 py-4", children: [
|
|
1046
|
-
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => step === 0 ? onClose() : setStep((value) => value - 1), disabled:
|
|
1818
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => step === 0 ? onClose() : setStep((value) => value - 1), disabled: busy, children: [
|
|
1047
1819
|
step > 0 ? /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }) : null,
|
|
1048
1820
|
step === 0 ? "Cancelar" : "Voltar"
|
|
1049
1821
|
] }),
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1822
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1823
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
1824
|
+
invoices.length,
|
|
1825
|
+
" parcela(s) selecionada(s)"
|
|
1826
|
+
] }),
|
|
1827
|
+
step < 3 ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void advance(), disabled: busy || !canAdvance, children: [
|
|
1828
|
+
busy || loadingContext ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
|
|
1829
|
+
step === 0 ? "Salvar e avan\xE7ar" : "Avan\xE7ar",
|
|
1830
|
+
" ",
|
|
1831
|
+
/* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" })
|
|
1832
|
+
] }) : /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => void generate(), disabled: busy || gaps.length > 0, children: [
|
|
1833
|
+
busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(FileDown, { className: "h-4 w-4" }),
|
|
1834
|
+
" Gerar arquivo .REM"
|
|
1835
|
+
] })
|
|
1836
|
+
] })
|
|
1056
1837
|
] })
|
|
1057
1838
|
] })
|
|
1058
1839
|
] }),
|
|
1059
1840
|
document.body
|
|
1060
1841
|
);
|
|
1061
1842
|
}
|
|
1843
|
+
function Warning({ children }) {
|
|
1844
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 rounded-lg border border-warning/30 bg-warning/10 p-3 text-sm", children: [
|
|
1845
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-warning" }),
|
|
1846
|
+
/* @__PURE__ */ jsx("div", { children })
|
|
1847
|
+
] });
|
|
1848
|
+
}
|
|
1849
|
+
function Summary({ label, value, strong }) {
|
|
1850
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
1851
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: label }),
|
|
1852
|
+
/* @__PURE__ */ jsx("div", { className: strong ? "text-lg font-semibold" : "font-medium", children: value })
|
|
1853
|
+
] });
|
|
1854
|
+
}
|
|
1062
1855
|
function Field({ label, value, onChange, required, placeholder, className = "" }) {
|
|
1063
1856
|
return /* @__PURE__ */ jsxs("label", { className: `block space-y-1.5 ${className}`, children: [
|
|
1064
1857
|
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
|
|
@@ -1068,6 +1861,183 @@ function Field({ label, value, onChange, required, placeholder, className = "" }
|
|
|
1068
1861
|
/* @__PURE__ */ jsx("input", { value, onChange: (event) => onChange(event.target.value), placeholder, className: "h-9 w-full rounded-input border bg-background px-3 text-sm outline-none focus:border-primary focus:ring-2 focus:ring-primary/15" })
|
|
1069
1862
|
] });
|
|
1070
1863
|
}
|
|
1864
|
+
var ACCEPT = "image/*,application/pdf";
|
|
1865
|
+
var MAX_BYTES = 15 * 1024 * 1024;
|
|
1866
|
+
function size(bytes) {
|
|
1867
|
+
if (!bytes) return "";
|
|
1868
|
+
return bytes < 1024 * 1024 ? `${Math.max(1, Math.round(bytes / 1024))} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
1869
|
+
}
|
|
1870
|
+
function InvoiceAttachmentsModal({ invoiceId, onClose, onChanged }) {
|
|
1871
|
+
const provider = useFinancialProvider();
|
|
1872
|
+
const inputRef = useRef(null);
|
|
1873
|
+
const [items, setItems] = useState(null);
|
|
1874
|
+
const [uploading, setUploading] = useState(0);
|
|
1875
|
+
const [over, setOver] = useState(false);
|
|
1876
|
+
const [preview, setPreview] = useState(null);
|
|
1877
|
+
async function load() {
|
|
1878
|
+
try {
|
|
1879
|
+
setItems(await (provider.getInvoiceAttachments?.(invoiceId) ?? Promise.resolve([])));
|
|
1880
|
+
} catch (e) {
|
|
1881
|
+
setItems([]);
|
|
1882
|
+
toast.error(e?.message ?? String(e));
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
useEffect(() => {
|
|
1886
|
+
void load();
|
|
1887
|
+
}, [invoiceId]);
|
|
1888
|
+
async function upload(files) {
|
|
1889
|
+
const list = Array.from(files);
|
|
1890
|
+
if (!list.length || !provider.addInvoiceAttachment) return;
|
|
1891
|
+
const invalid = list.filter((f) => !(f.type.startsWith("image/") || f.type === "application/pdf") || f.size > MAX_BYTES);
|
|
1892
|
+
if (invalid.length) toast.error("Envie imagem ou PDF de at\xE9 15 MB.");
|
|
1893
|
+
const ok = list.filter((f) => !invalid.includes(f));
|
|
1894
|
+
if (!ok.length) return;
|
|
1895
|
+
setUploading((n) => n + ok.length);
|
|
1896
|
+
for (const file of ok) {
|
|
1897
|
+
try {
|
|
1898
|
+
await provider.addInvoiceAttachment(invoiceId, file);
|
|
1899
|
+
} catch (e) {
|
|
1900
|
+
toast.error(`${file.name}: ${e?.message ?? String(e)}`);
|
|
1901
|
+
} finally {
|
|
1902
|
+
setUploading((n) => n - 1);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
await load();
|
|
1906
|
+
onChanged?.();
|
|
1907
|
+
}
|
|
1908
|
+
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
1909
|
+
if (!o) onClose();
|
|
1910
|
+
}, children: /* @__PURE__ */ jsx(ModalContent, { size: preview !== null ? "3xl" : "sm", children: preview !== null && items?.[preview] ? /* @__PURE__ */ jsx(
|
|
1911
|
+
AttachmentPreview,
|
|
1912
|
+
{
|
|
1913
|
+
items,
|
|
1914
|
+
index: preview,
|
|
1915
|
+
onIndex: setPreview,
|
|
1916
|
+
onBack: () => setPreview(null)
|
|
1917
|
+
}
|
|
1918
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1919
|
+
/* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs(ModalTitle, { className: "flex items-center gap-2", children: [
|
|
1920
|
+
/* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }),
|
|
1921
|
+
"Anexos da conta"
|
|
1922
|
+
] }) }),
|
|
1923
|
+
/* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
|
|
1924
|
+
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: [
|
|
1925
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 shrink-0 animate-pulse rounded bg-muted" }),
|
|
1926
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
|
|
1927
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-2/3 animate-pulse rounded bg-muted" }),
|
|
1928
|
+
/* @__PURE__ */ jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-muted" })
|
|
1929
|
+
] })
|
|
1930
|
+
] }, i)) }) : items.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-card bg-muted/40 px-4 py-5 text-center", children: [
|
|
1931
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Nenhum anexo ainda" }),
|
|
1932
|
+
/* @__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." })
|
|
1933
|
+
] }) : /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-card border", children: items.map((a, i) => {
|
|
1934
|
+
const Icon = (a.mimeType ?? "").startsWith("image/") ? Image : FileText;
|
|
1935
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
1936
|
+
"button",
|
|
1937
|
+
{
|
|
1938
|
+
type: "button",
|
|
1939
|
+
onClick: () => setPreview(i),
|
|
1940
|
+
className: "flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-muted/40",
|
|
1941
|
+
children: [
|
|
1942
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
1943
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1944
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: a.fileName }),
|
|
1945
|
+
/* @__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 ") })
|
|
1946
|
+
] }),
|
|
1947
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
1948
|
+
]
|
|
1949
|
+
}
|
|
1950
|
+
) }, a.id);
|
|
1951
|
+
}) }),
|
|
1952
|
+
provider.addInvoiceAttachment && /* @__PURE__ */ jsxs(
|
|
1953
|
+
"button",
|
|
1954
|
+
{
|
|
1955
|
+
type: "button",
|
|
1956
|
+
onClick: () => inputRef.current?.click(),
|
|
1957
|
+
onDragOver: (e) => {
|
|
1958
|
+
e.preventDefault();
|
|
1959
|
+
setOver(true);
|
|
1960
|
+
},
|
|
1961
|
+
onDragLeave: () => setOver(false),
|
|
1962
|
+
onDrop: (e) => {
|
|
1963
|
+
e.preventDefault();
|
|
1964
|
+
setOver(false);
|
|
1965
|
+
void upload(e.dataTransfer.files);
|
|
1966
|
+
},
|
|
1967
|
+
disabled: uploading > 0,
|
|
1968
|
+
className: `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"}`,
|
|
1969
|
+
children: [
|
|
1970
|
+
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 }),
|
|
1971
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: uploading > 0 ? "Enviando\u2026" : "Solte arquivos aqui ou clique para escolher" }),
|
|
1972
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Imagem ou PDF, at\xE9 15 MB" })
|
|
1973
|
+
]
|
|
1974
|
+
}
|
|
1975
|
+
),
|
|
1976
|
+
/* @__PURE__ */ jsx(
|
|
1977
|
+
"input",
|
|
1978
|
+
{
|
|
1979
|
+
ref: inputRef,
|
|
1980
|
+
type: "file",
|
|
1981
|
+
accept: ACCEPT,
|
|
1982
|
+
multiple: true,
|
|
1983
|
+
className: "hidden",
|
|
1984
|
+
onChange: (e) => {
|
|
1985
|
+
const f = e.target.files;
|
|
1986
|
+
if (f) void upload(f);
|
|
1987
|
+
e.target.value = "";
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
)
|
|
1991
|
+
] })
|
|
1992
|
+
] }) }) });
|
|
1993
|
+
}
|
|
1994
|
+
function AttachmentPreview({ items, index, onIndex, onBack }) {
|
|
1995
|
+
const doc = items[index];
|
|
1996
|
+
const isImage = (doc.mimeType ?? "").startsWith("image/");
|
|
1997
|
+
const isPdf = doc.mimeType === "application/pdf";
|
|
1998
|
+
const go = (d) => onIndex((index + d + items.length) % items.length);
|
|
1999
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2000
|
+
/* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pr-8", children: [
|
|
2001
|
+
/* @__PURE__ */ jsxs(
|
|
2002
|
+
"button",
|
|
2003
|
+
{
|
|
2004
|
+
type: "button",
|
|
2005
|
+
onClick: onBack,
|
|
2006
|
+
className: "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",
|
|
2007
|
+
children: [
|
|
2008
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
|
|
2009
|
+
" Voltar"
|
|
2010
|
+
]
|
|
2011
|
+
}
|
|
2012
|
+
),
|
|
2013
|
+
/* @__PURE__ */ jsx(ModalTitle, { className: "min-w-0 flex-1 truncate text-sm font-medium", title: doc.fileName, children: doc.fileName }),
|
|
2014
|
+
items.length > 1 && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
|
|
2015
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => go(-1), "aria-label": "Anterior", className: "rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }) }),
|
|
2016
|
+
index + 1,
|
|
2017
|
+
" de ",
|
|
2018
|
+
items.length,
|
|
2019
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => go(1), "aria-label": "Pr\xF3ximo", className: "rounded-md p-1 hover:bg-muted", children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
|
|
2020
|
+
] }),
|
|
2021
|
+
doc.url && /* @__PURE__ */ jsx(
|
|
2022
|
+
"a",
|
|
2023
|
+
{
|
|
2024
|
+
href: doc.url,
|
|
2025
|
+
target: "_blank",
|
|
2026
|
+
rel: "noreferrer",
|
|
2027
|
+
"aria-label": "Abrir em nova aba",
|
|
2028
|
+
className: "shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
2029
|
+
children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" })
|
|
2030
|
+
}
|
|
2031
|
+
)
|
|
2032
|
+
] }) }),
|
|
2033
|
+
/* @__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: [
|
|
2034
|
+
"O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por",
|
|
2035
|
+
" ",
|
|
2036
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
|
|
2037
|
+
"."
|
|
2038
|
+
] }) : 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." }) }) })
|
|
2039
|
+
] });
|
|
2040
|
+
}
|
|
1071
2041
|
var KIND_LABELS = {
|
|
1072
2042
|
supplier: "Fornecedor",
|
|
1073
2043
|
customer: "Cliente",
|
|
@@ -1093,7 +2063,7 @@ function FinancialCounterpartySearchSelect({
|
|
|
1093
2063
|
onEdit,
|
|
1094
2064
|
disabled
|
|
1095
2065
|
}) {
|
|
1096
|
-
const search =
|
|
2066
|
+
const search = React25.useCallback(async (query) => {
|
|
1097
2067
|
const results = await lookup.search(query);
|
|
1098
2068
|
return results.map((result) => ({
|
|
1099
2069
|
id: result.id,
|
|
@@ -1173,49 +2143,61 @@ function StatusBadge({ status }) {
|
|
|
1173
2143
|
opt ? t2(opt.labelKey) : status
|
|
1174
2144
|
] });
|
|
1175
2145
|
}
|
|
1176
|
-
function
|
|
2146
|
+
function formatDueDate(iso) {
|
|
2147
|
+
if (!iso) return "\u2014";
|
|
2148
|
+
const [y, m, d] = iso.slice(0, 10).split("-");
|
|
2149
|
+
return d && m && y ? `${d}/${m}/${y}` : iso;
|
|
2150
|
+
}
|
|
2151
|
+
function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments) {
|
|
1177
2152
|
const t2 = useTranslation();
|
|
1178
2153
|
return useMemo(() => [
|
|
1179
|
-
{
|
|
1180
|
-
accessorKey: "fiscalNumber",
|
|
1181
|
-
header: "#",
|
|
1182
|
-
size: 110,
|
|
1183
|
-
cell: ({ getValue }) => {
|
|
1184
|
-
const num2 = getValue();
|
|
1185
|
-
return num2 ? /* @__PURE__ */ jsxs("span", { className: "text-xs font-mono text-muted-foreground", children: [
|
|
1186
|
-
"#",
|
|
1187
|
-
num2
|
|
1188
|
-
] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
1189
|
-
}
|
|
1190
|
-
},
|
|
1191
2154
|
{
|
|
1192
2155
|
accessorKey: "dueDate",
|
|
1193
2156
|
header: t2("financial.invoice.columnDueDate"),
|
|
1194
|
-
cell: ({
|
|
2157
|
+
cell: ({ row }) => {
|
|
2158
|
+
const total = row.original.totalInstallments ?? 1;
|
|
2159
|
+
const atual = row.original.installmentNumber ?? 1;
|
|
2160
|
+
return /* @__PURE__ */ jsxs("div", { className: "whitespace-nowrap", children: [
|
|
2161
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm tabular-nums", children: [
|
|
2162
|
+
formatDueDate(row.original.dueDate),
|
|
2163
|
+
total > 1 && /* @__PURE__ */ jsxs("span", { className: "ml-1.5 text-xs text-muted-foreground", title: t2("financial.invoice.columnInst"), children: [
|
|
2164
|
+
atual,
|
|
2165
|
+
"/",
|
|
2166
|
+
total
|
|
2167
|
+
] })
|
|
2168
|
+
] }),
|
|
2169
|
+
row.original.fiscalNumber && /* @__PURE__ */ jsxs("p", { className: "font-mono text-[10px] text-muted-foreground", children: [
|
|
2170
|
+
"#",
|
|
2171
|
+
row.original.fiscalNumber
|
|
2172
|
+
] })
|
|
2173
|
+
] });
|
|
2174
|
+
}
|
|
1195
2175
|
},
|
|
1196
2176
|
{
|
|
1197
2177
|
id: "description",
|
|
1198
2178
|
header: t2("financial.invoice.columnDescription"),
|
|
1199
|
-
cell: ({ row }) =>
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
2179
|
+
cell: ({ row }) => (
|
|
2180
|
+
// Full cell width with the badge pinned right, so the statuses line up in
|
|
2181
|
+
// one column instead of trailing each name.
|
|
2182
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full min-w-[250px] items-center gap-3", onClick: (e) => e.stopPropagation(), children: [
|
|
2183
|
+
/* @__PURE__ */ jsxs("div", { className: "w-0 min-w-0 flex-1", children: [
|
|
2184
|
+
row.original.contactName ? /* @__PURE__ */ jsx(PersonLink, { personId: row.original.contactId, name: row.original.contactName, size: "sm", className: "block max-w-full truncate text-sm font-medium" }) : /* @__PURE__ */ jsx("p", { className: "font-medium", children: "\u2014" }),
|
|
2185
|
+
row.original.itemsSummary && /* @__PURE__ */ jsx("p", { className: "truncate text-[10px] text-muted-foreground", children: row.original.itemsSummary })
|
|
2186
|
+
] }),
|
|
2187
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsx(StatusBadge, { status: row.original.status }) })
|
|
2188
|
+
] })
|
|
2189
|
+
)
|
|
1203
2190
|
},
|
|
1204
2191
|
{
|
|
1205
2192
|
accessorKey: "totalAmount",
|
|
1206
2193
|
header: t2("financial.invoice.columnAmount"),
|
|
1207
2194
|
enableSorting: true,
|
|
1208
|
-
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `font-medium ${row.original.status === "cancelled" ? "line-through text-muted-foreground" : ""}`, children: formatCurrency(row.original.totalAmount, currency) })
|
|
2195
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `whitespace-nowrap font-medium ${row.original.status === "cancelled" ? "line-through text-muted-foreground" : ""}`, children: formatCurrency(row.original.totalAmount, currency) })
|
|
1209
2196
|
},
|
|
1210
2197
|
{
|
|
1211
2198
|
accessorKey: "paidAmount",
|
|
1212
2199
|
header: t2("financial.invoice.columnPaid"),
|
|
1213
|
-
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: row.original.status === "cancelled" ? "line-through text-muted-foreground" : "text-muted-foreground"
|
|
1214
|
-
},
|
|
1215
|
-
{
|
|
1216
|
-
accessorKey: "status",
|
|
1217
|
-
header: t2("financial.invoice.columnStatus"),
|
|
1218
|
-
cell: ({ getValue }) => /* @__PURE__ */ jsx(StatusBadge, { status: getValue() })
|
|
2200
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: `whitespace-nowrap ${row.original.status === "cancelled" ? "line-through text-muted-foreground" : "text-muted-foreground"}`, children: formatCurrency(row.original.paidAmount, currency) })
|
|
1219
2201
|
},
|
|
1220
2202
|
{
|
|
1221
2203
|
id: "paymentMethod",
|
|
@@ -1225,7 +2207,7 @@ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAc
|
|
|
1225
2207
|
const type = paymentMethodTypes.find((candidate) => candidate.id === (method?.paymentMethodTypeId ?? row.original.paymentMethodTypeId));
|
|
1226
2208
|
const account = bankAccounts.find((candidate) => candidate.id === row.original.paymentAccountId);
|
|
1227
2209
|
if (!type && !method && !account) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
1228
|
-
return /* @__PURE__ */ jsxs("div", { className: "max-w-[
|
|
2210
|
+
return /* @__PURE__ */ jsxs("div", { className: "max-w-[150px] text-xs", children: [
|
|
1229
2211
|
/* @__PURE__ */ jsxs("p", { className: "truncate font-medium", children: [
|
|
1230
2212
|
method?.name ?? type?.name ?? "\u2014",
|
|
1231
2213
|
row.original.paymentCardInstallments ? ` \xB7 ${row.original.paymentCardInstallments}x` : ""
|
|
@@ -1235,69 +2217,62 @@ function useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAc
|
|
|
1235
2217
|
}
|
|
1236
2218
|
},
|
|
1237
2219
|
{
|
|
1238
|
-
id: "
|
|
2220
|
+
id: "actions",
|
|
1239
2221
|
header: "",
|
|
1240
2222
|
// O ÚNICO CAMINHO QUE TRANSFORMA PREVISÃO EM DÍVIDA.
|
|
1241
2223
|
//
|
|
1242
2224
|
// O V1 troca as ações da linha recorrente por este botão só — não há
|
|
1243
2225
|
// anexo, nem estorno, nem fatura para abrir: a ocorrência ainda não é
|
|
1244
2226
|
// nada disso. Consolidar é o clique que a faz existir.
|
|
2227
|
+
//
|
|
2228
|
+
// Fora dela, o clipe, como no V1: diz que a conta tem comprovante sem
|
|
2229
|
+
// obrigar a abri-la. Sem anexo não desenha nada.
|
|
1245
2230
|
cell: ({ row }) => {
|
|
1246
|
-
if (
|
|
1247
|
-
|
|
1248
|
-
|
|
2231
|
+
if (row.original.isProvisionedOccurrence) {
|
|
2232
|
+
const id = row.original.occurrenceId;
|
|
2233
|
+
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
|
|
2234
|
+
Button,
|
|
2235
|
+
{
|
|
2236
|
+
size: "sm",
|
|
2237
|
+
variant: "outline",
|
|
2238
|
+
className: "h-8 px-2",
|
|
2239
|
+
disabled: !id || consolidating === id,
|
|
2240
|
+
title: t2("financial.invoice.consolidate"),
|
|
2241
|
+
onClick: (e) => {
|
|
2242
|
+
e.stopPropagation();
|
|
2243
|
+
if (id) void consolidar(id);
|
|
2244
|
+
},
|
|
2245
|
+
children: [
|
|
2246
|
+
consolidating === id ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Zap, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
2247
|
+
/* @__PURE__ */ jsx("span", { className: "ml-1 hidden xl:inline", children: t2("financial.invoice.consolidate") })
|
|
2248
|
+
]
|
|
2249
|
+
}
|
|
2250
|
+
) });
|
|
2251
|
+
}
|
|
2252
|
+
const n = row.original.attachmentCount ?? 0;
|
|
2253
|
+
if (!n) return null;
|
|
2254
|
+
const label = n === 1 ? "1 anexo" : `${n} anexos`;
|
|
2255
|
+
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
|
|
1249
2256
|
Button,
|
|
1250
2257
|
{
|
|
1251
|
-
size: "
|
|
1252
|
-
variant: "
|
|
1253
|
-
|
|
2258
|
+
size: "icon",
|
|
2259
|
+
variant: "ghost",
|
|
2260
|
+
className: "relative h-8 w-8 text-muted-foreground hover:text-foreground",
|
|
2261
|
+
title: label,
|
|
2262
|
+
"aria-label": label,
|
|
1254
2263
|
onClick: (e) => {
|
|
1255
2264
|
e.stopPropagation();
|
|
1256
|
-
|
|
2265
|
+
openAttachments(row.original.invoiceId ?? row.original.id);
|
|
1257
2266
|
},
|
|
1258
2267
|
children: [
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
]
|
|
1262
|
-
}
|
|
1263
|
-
);
|
|
1264
|
-
}
|
|
1265
|
-
},
|
|
1266
|
-
{
|
|
1267
|
-
id: "attachments",
|
|
1268
|
-
header: "",
|
|
1269
|
-
// O clipe, como no V1: diz que a conta tem comprovante sem obrigar a
|
|
1270
|
-
// abri-la. Sem anexo não desenha nada — uma coluna de clipes apagados é
|
|
1271
|
-
// ruído do tamanho da lista.
|
|
1272
|
-
cell: ({ row }) => {
|
|
1273
|
-
const n = row.original.attachmentCount ?? 0;
|
|
1274
|
-
if (!n) return null;
|
|
1275
|
-
return /* @__PURE__ */ jsxs(
|
|
1276
|
-
"span",
|
|
1277
|
-
{
|
|
1278
|
-
className: "inline-flex items-center gap-0.5 text-muted-foreground",
|
|
1279
|
-
title: n === 1 ? "1 anexo" : `${n} anexos`,
|
|
1280
|
-
children: [
|
|
1281
|
-
/* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
1282
|
-
n > 1 ? /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium tabular-nums", children: n }) : null,
|
|
1283
|
-
/* @__PURE__ */ jsx("span", { className: "sr-only", children: n === 1 ? "1 anexo" : `${n} anexos` })
|
|
2268
|
+
/* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4", "aria-hidden": true }),
|
|
2269
|
+
n > 0 && /* @__PURE__ */ jsx("span", { className: "absolute -right-0.5 -top-0.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: n })
|
|
1284
2270
|
]
|
|
1285
2271
|
}
|
|
1286
|
-
);
|
|
1287
|
-
}
|
|
1288
|
-
},
|
|
1289
|
-
{
|
|
1290
|
-
accessorKey: "totalInstallments",
|
|
1291
|
-
header: t2("financial.invoice.columnInst"),
|
|
1292
|
-
// "3/4", como o V1 — a linha é uma parcela, e o número sozinho não diz
|
|
1293
|
-
// QUAL delas. Parcela única fica só "1".
|
|
1294
|
-
cell: ({ row }) => {
|
|
1295
|
-
const total = row.original.totalInstallments ?? 1;
|
|
1296
|
-
const atual = row.original.installmentNumber ?? 1;
|
|
1297
|
-
return /* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: total > 1 ? `${atual}/${total}` : total });
|
|
2272
|
+
) });
|
|
1298
2273
|
}
|
|
1299
2274
|
}
|
|
1300
|
-
], [currency, t2, paymentMethods2, paymentMethodTypes, bankAccounts]);
|
|
2275
|
+
], [currency, t2, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments]);
|
|
1301
2276
|
}
|
|
1302
2277
|
function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
1303
2278
|
const t2 = useTranslation();
|
|
@@ -1430,7 +2405,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1430
2405
|
setConsolidating(occurrenceId);
|
|
1431
2406
|
void provider.consolidateOccurrence(occurrenceId).then((invoiceId) => {
|
|
1432
2407
|
toast.success(t2("financial.invoice.consolidated"));
|
|
1433
|
-
if (invoiceId && onEdit) onEdit(invoiceId);
|
|
2408
|
+
if (invoiceId && onEdit) onEdit(invoiceId, "open");
|
|
1434
2409
|
else void fetchInvoices(query);
|
|
1435
2410
|
}).catch((err) => {
|
|
1436
2411
|
toast.error(t2("financial.invoice.consolidateFailed"), {
|
|
@@ -1439,7 +2414,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1439
2414
|
}).finally(() => setConsolidating(null));
|
|
1440
2415
|
}, [provider, t2, fetchInvoices, query, onEdit]);
|
|
1441
2416
|
const filtered = invoices.filter((inv) => inv.direction === direction);
|
|
1442
|
-
const
|
|
2417
|
+
const [attachmentsOf, setAttachmentsOf] = useState(null);
|
|
2418
|
+
const openAttachments = useCallback((invoiceId) => setAttachmentsOf(invoiceId), []);
|
|
2419
|
+
const columns = useInvoiceColumns(currency, paymentMethods2, paymentMethodTypes, bankAccounts, consolidar, consolidating, openAttachments);
|
|
1443
2420
|
const contactLookup = useMemo(() => createFinancialCounterpartyLookup(direction), [direction]);
|
|
1444
2421
|
const tags = useMemo(
|
|
1445
2422
|
// `provisioned` fora da barra: ele não é um recorte do que existe, é o
|
|
@@ -1451,14 +2428,14 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1451
2428
|
const summary = useMemo(() => {
|
|
1452
2429
|
let totalAmount = 0;
|
|
1453
2430
|
let paidAmount = 0;
|
|
1454
|
-
let
|
|
2431
|
+
let openAmount2 = 0;
|
|
1455
2432
|
for (const inv of filtered) {
|
|
1456
2433
|
if (inv.status === "cancelled") continue;
|
|
1457
2434
|
totalAmount += inv.totalAmount;
|
|
1458
2435
|
paidAmount += inv.paidAmount;
|
|
1459
2436
|
}
|
|
1460
|
-
|
|
1461
|
-
return { totalAmount, paidAmount, openAmount };
|
|
2437
|
+
openAmount2 = totalAmount - paidAmount;
|
|
2438
|
+
return { totalAmount, paidAmount, openAmount: openAmount2 };
|
|
1462
2439
|
}, [filtered]);
|
|
1463
2440
|
const [cancelTarget, setCancelTarget] = useState(null);
|
|
1464
2441
|
const [cnabTarget, setCnabTarget] = useState(null);
|
|
@@ -1782,7 +2759,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1782
2759
|
},
|
|
1783
2760
|
...onNewReceiptGated ? [{
|
|
1784
2761
|
id: "receipt",
|
|
1785
|
-
|
|
2762
|
+
// Same flow for payables and receivables: the photo is the request and
|
|
2763
|
+
// the assistant returns the form filled in.
|
|
2764
|
+
label: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
|
|
1786
2765
|
description: t2("financial.quickTx.receiptCapture"),
|
|
1787
2766
|
icon: Camera,
|
|
1788
2767
|
intent: "secondary",
|
|
@@ -1792,10 +2771,20 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1792
2771
|
// preenchido. Ver receipt-agent.ts.
|
|
1793
2772
|
onSelect: () => startReceiptCapture({
|
|
1794
2773
|
ask: t2("financial.receipt.ask"),
|
|
1795
|
-
uploadLabel: t2("financial.quickTx.
|
|
2774
|
+
uploadLabel: t2(direction === "debit" ? "financial.quickTx.digitizeExpense" : "financial.quickTx.digitizeReceipt"),
|
|
1796
2775
|
uploadHint: t2("financial.quickTx.receiptCapture"),
|
|
1797
2776
|
prompt: t2("financial.receipt.prompt")
|
|
1798
2777
|
})
|
|
2778
|
+
}] : [],
|
|
2779
|
+
...direction === "debit" ? [{
|
|
2780
|
+
id: "payroll",
|
|
2781
|
+
label: t2("financial.invoice.importPayroll"),
|
|
2782
|
+
description: t2("financial.invoice.importPayrollSoon"),
|
|
2783
|
+
icon: Users,
|
|
2784
|
+
intent: "secondary",
|
|
2785
|
+
disabled: true,
|
|
2786
|
+
onSelect: () => {
|
|
2787
|
+
}
|
|
1799
2788
|
}] : []
|
|
1800
2789
|
] : [];
|
|
1801
2790
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
@@ -1816,7 +2805,7 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1816
2805
|
actions: newActions,
|
|
1817
2806
|
bulkActions,
|
|
1818
2807
|
onRowClick: (row) => {
|
|
1819
|
-
if (!row.isProvisionedOccurrence) onEdit?.(row.invoiceId ?? row.id);
|
|
2808
|
+
if (!row.isProvisionedOccurrence) onEdit?.(row.invoiceId ?? row.id, row.status);
|
|
1820
2809
|
},
|
|
1821
2810
|
rowClassName: (row) => row.isProvisionedOccurrence ? "border-dashed !cursor-default bg-muted/10 text-muted-foreground" : void 0,
|
|
1822
2811
|
emptyIcon: FileText,
|
|
@@ -1825,6 +2814,16 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1825
2814
|
onEmptyAction: onNewGated
|
|
1826
2815
|
}
|
|
1827
2816
|
),
|
|
2817
|
+
attachmentsOf && /* @__PURE__ */ jsx(
|
|
2818
|
+
InvoiceAttachmentsModal,
|
|
2819
|
+
{
|
|
2820
|
+
invoiceId: attachmentsOf,
|
|
2821
|
+
onClose: () => setAttachmentsOf(null),
|
|
2822
|
+
onChanged: () => {
|
|
2823
|
+
void fetchInvoices(query);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
),
|
|
1828
2827
|
filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
|
|
1829
2828
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1830
2829
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.summaryTotal") }),
|
|
@@ -2123,12 +3122,12 @@ async function settlePayable(provider, input) {
|
|
|
2123
3122
|
};
|
|
2124
3123
|
}
|
|
2125
3124
|
const invoice = await provider.getInvoiceById(input.invoiceId).catch(() => null);
|
|
2126
|
-
const
|
|
3125
|
+
const money4 = (n) => n.toLocaleString("pt-BR", { style: "currency", currency: input.currency ?? "BRL" });
|
|
2127
3126
|
const invoiceLabel = [
|
|
2128
3127
|
invoice?.fiscalNumber ? `#${invoice.fiscalNumber}` : null,
|
|
2129
3128
|
invoice?.contactName ?? null,
|
|
2130
|
-
|
|
2131
|
-
].filter(Boolean).join(" \u2014 ") ||
|
|
3129
|
+
money4(bill.amount)
|
|
3130
|
+
].filter(Boolean).join(" \u2014 ") || money4(bill.amount);
|
|
2132
3131
|
const needsAccount = input.method.transactionType !== "cash";
|
|
2133
3132
|
const accounts = needsAccount ? await eligibleAccounts(provider, input.method) : [];
|
|
2134
3133
|
if (needsAccount && !accounts.length) {
|
|
@@ -2318,7 +3317,7 @@ function createPayableTools({ config, provider }) {
|
|
|
2318
3317
|
ok: true,
|
|
2319
3318
|
read: {
|
|
2320
3319
|
supplier: supplierName,
|
|
2321
|
-
supplierDocument:
|
|
3320
|
+
supplierDocument: onlyDigits2(args.supplierDocument) || void 0,
|
|
2322
3321
|
paymentMethod: text(args.paymentMethod),
|
|
2323
3322
|
cardBrand: text(args.cardBrand),
|
|
2324
3323
|
cardLast4: lastFour(args.cardLast4) || void 0
|
|
@@ -2686,16 +3685,16 @@ function normalizeCompanyName(raw) {
|
|
|
2686
3685
|
if (typeof raw !== "string") return "";
|
|
2687
3686
|
return raw.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w && !COMPANY_NOISE.has(w)).join(" ").trim();
|
|
2688
3687
|
}
|
|
2689
|
-
function
|
|
3688
|
+
function onlyDigits2(raw) {
|
|
2690
3689
|
return typeof raw === "string" ? raw.replace(/\D/g, "") : "";
|
|
2691
3690
|
}
|
|
2692
3691
|
function lastFour(raw) {
|
|
2693
|
-
const
|
|
2694
|
-
return
|
|
3692
|
+
const digits2 = onlyDigits2(raw);
|
|
3693
|
+
return digits2.length >= 4 ? digits2.slice(-4) : "";
|
|
2695
3694
|
}
|
|
2696
3695
|
async function findSupplier(lookup, rawName, rawDocument) {
|
|
2697
3696
|
const wanted = normalizeCompanyName(rawName);
|
|
2698
|
-
const doc =
|
|
3697
|
+
const doc = onlyDigits2(rawDocument);
|
|
2699
3698
|
if (!wanted) return { status: "missing", candidates: [] };
|
|
2700
3699
|
const core = wanted.split(" ")[0] ?? wanted;
|
|
2701
3700
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -2705,7 +3704,7 @@ async function findSupplier(lookup, rawName, rawDocument) {
|
|
|
2705
3704
|
seen.set(hit.id, {
|
|
2706
3705
|
id: hit.id,
|
|
2707
3706
|
name: hit.label,
|
|
2708
|
-
document:
|
|
3707
|
+
document: onlyDigits2(row.document_number) || void 0,
|
|
2709
3708
|
why: ""
|
|
2710
3709
|
});
|
|
2711
3710
|
}
|
|
@@ -2903,9 +3902,9 @@ var SUBMIT_DRAFT = {
|
|
|
2903
3902
|
parameters: { type: "object", properties: {} }
|
|
2904
3903
|
};
|
|
2905
3904
|
function PayableDraftSurface({ config, draft }) {
|
|
2906
|
-
const live =
|
|
3905
|
+
const live = React25.useRef(draft);
|
|
2907
3906
|
live.current = draft;
|
|
2908
|
-
const lookup =
|
|
3907
|
+
const lookup = React25.useMemo(
|
|
2909
3908
|
() => config.contactLookup ?? createArchetypeLookup({
|
|
2910
3909
|
archetype: "person",
|
|
2911
3910
|
kind: "supplier",
|
|
@@ -2914,7 +3913,7 @@ function PayableDraftSurface({ config, draft }) {
|
|
|
2914
3913
|
}),
|
|
2915
3914
|
[config.contactLookup]
|
|
2916
3915
|
);
|
|
2917
|
-
|
|
3916
|
+
React25.useEffect(() => {
|
|
2918
3917
|
liveDraft = {
|
|
2919
3918
|
describe: () => live.current.describe(),
|
|
2920
3919
|
setSupplier: (name, id) => live.current.setSupplier(name, id),
|
|
@@ -2925,7 +3924,7 @@ function PayableDraftSurface({ config, draft }) {
|
|
|
2925
3924
|
liveDraft = null;
|
|
2926
3925
|
};
|
|
2927
3926
|
}, []);
|
|
2928
|
-
const contribution =
|
|
3927
|
+
const contribution = React25.useMemo(() => ({
|
|
2929
3928
|
id: "financial.payables.draft",
|
|
2930
3929
|
pluginId: "financial",
|
|
2931
3930
|
role: "finance",
|
|
@@ -3000,7 +3999,7 @@ function PayableDraftSurface({ config, draft }) {
|
|
|
3000
3999
|
}
|
|
3001
4000
|
function createFinancialAgentMount(deps) {
|
|
3002
4001
|
const FinancialAgentMount = () => {
|
|
3003
|
-
const contribution =
|
|
4002
|
+
const contribution = React25.useMemo(() => ({
|
|
3004
4003
|
id: "financial.payables.global",
|
|
3005
4004
|
pluginId: "financial",
|
|
3006
4005
|
role: "finance",
|
|
@@ -3095,13 +4094,24 @@ function CardVisual({
|
|
|
3095
4094
|
lastFour: lastFour2,
|
|
3096
4095
|
color,
|
|
3097
4096
|
brandId,
|
|
3098
|
-
size = "sm"
|
|
4097
|
+
size: size2 = "sm"
|
|
3099
4098
|
}) {
|
|
3100
4099
|
const brand = useBrandName(brandId);
|
|
3101
4100
|
const last = (lastFour2 ?? "").trim();
|
|
3102
4101
|
if (!last && !color && !brand) return null;
|
|
3103
4102
|
const bg = color && /^#[0-9a-fA-F]{6}$/.test(color) ? color : void 0;
|
|
3104
|
-
const lg =
|
|
4103
|
+
const lg = size2 === "lg";
|
|
4104
|
+
if (size2 === "xs") {
|
|
4105
|
+
return /* @__PURE__ */ jsx(
|
|
4106
|
+
"span",
|
|
4107
|
+
{
|
|
4108
|
+
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",
|
|
4109
|
+
style: bg ? { backgroundColor: bg, color: ink(bg), borderColor: "transparent" } : void 0,
|
|
4110
|
+
title: [brand, last && `\u2022\u2022\u2022\u2022 ${last}`].filter(Boolean).join(" "),
|
|
4111
|
+
children: last
|
|
4112
|
+
}
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
3105
4115
|
return /* @__PURE__ */ jsxs(
|
|
3106
4116
|
"span",
|
|
3107
4117
|
{
|
|
@@ -3171,7 +4181,9 @@ function eligibleAccountsFor(bankAccounts, direction, type) {
|
|
|
3171
4181
|
const active = bankAccounts.filter((a) => a.isActive !== false);
|
|
3172
4182
|
if (direction === "debit") {
|
|
3173
4183
|
if (txType === "credit_card") return active.filter((a) => a.accountType === "card_payable");
|
|
3174
|
-
|
|
4184
|
+
const own = (type?.allowedAccountTypes?.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType])?.filter((k) => !k.startsWith("card_"));
|
|
4185
|
+
const kinds = own?.length ? own : ["bank", "cash_physical", "cash_session"];
|
|
4186
|
+
return active.filter((a) => kinds.includes(a.accountType));
|
|
3175
4187
|
}
|
|
3176
4188
|
const allowed = type?.allowedAccountTypes && type.allowedAccountTypes.length ? type.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX2[txType];
|
|
3177
4189
|
if (!allowed || !allowed.length) return bankAccounts;
|
|
@@ -3493,10 +4505,10 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
3493
4505
|
const payMovement = useFinancialStore((s) => s.payMovement);
|
|
3494
4506
|
const remaining = movement.amount - movement.paidAmount;
|
|
3495
4507
|
const [saving, setSaving] = useState(false);
|
|
3496
|
-
const command =
|
|
4508
|
+
const command = React25__default.useRef(createPendingCommand(() => createIdempotencyKey("financial:payment"), (input) => input.movementId));
|
|
3497
4509
|
const [pending2, setPending] = useState(false);
|
|
3498
4510
|
const [visible, setVisible] = useState(false);
|
|
3499
|
-
|
|
4511
|
+
React25__default.useEffect(() => {
|
|
3500
4512
|
requestAnimationFrame(() => setVisible(true));
|
|
3501
4513
|
}, []);
|
|
3502
4514
|
const source = movement.metadata ?? {};
|
|
@@ -3615,8 +4627,34 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
3615
4627
|
document.body
|
|
3616
4628
|
);
|
|
3617
4629
|
}
|
|
4630
|
+
var METHOD_ICONS = {
|
|
4631
|
+
cash: Banknote,
|
|
4632
|
+
pix: QrCode,
|
|
4633
|
+
credit_card: CreditCard,
|
|
4634
|
+
debit_card: CreditCard,
|
|
4635
|
+
bank_transfer: ArrowRightLeft,
|
|
4636
|
+
check: FileCheck,
|
|
4637
|
+
boleto: Receipt,
|
|
4638
|
+
voucher: Ticket
|
|
4639
|
+
};
|
|
4640
|
+
function MethodIcon({ type }) {
|
|
4641
|
+
const Icon = METHOD_ICONS[type?.transactionType ?? ""] ?? Wallet;
|
|
4642
|
+
return /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4643
|
+
}
|
|
4644
|
+
function AccountIcon({ account }) {
|
|
4645
|
+
if (!account) return null;
|
|
4646
|
+
if (account.accountType === "card_payable" && (account.lastFour || account.color || account.cardBrandId)) {
|
|
4647
|
+
return /* @__PURE__ */ jsx(CardVisual, { size: "xs", lastFour: account.lastFour ?? void 0, color: account.color ?? void 0, brandId: account.cardBrandId ?? void 0 });
|
|
4648
|
+
}
|
|
4649
|
+
if (account.accountType.startsWith("card_")) return /* @__PURE__ */ jsx(CreditCard, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4650
|
+
if (account.accountType === "cash_physical" || account.accountType === "cash_session") {
|
|
4651
|
+
return /* @__PURE__ */ jsx(Banknote, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4652
|
+
}
|
|
4653
|
+
const bank = findBrBank(account.bankCode, account.bankName ?? account.name);
|
|
4654
|
+
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 });
|
|
4655
|
+
}
|
|
3618
4656
|
var NONE = "__none__";
|
|
3619
|
-
var TRIGGER_CLASS =
|
|
4657
|
+
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}`;
|
|
3620
4658
|
function InstallmentSourceFields({
|
|
3621
4659
|
direction,
|
|
3622
4660
|
value,
|
|
@@ -3636,8 +4674,8 @@ function InstallmentSourceFields({
|
|
|
3636
4674
|
const conta = elegiveis.length === 1 ? elegiveis[0].id : elegiveis.some((a) => a.id === value.payAccountId) ? value.payAccountId : void 0;
|
|
3637
4675
|
onChange({ payMethodTypeId: typeId || void 0, payAccountId: typeId ? conta : void 0 });
|
|
3638
4676
|
}
|
|
3639
|
-
const tone = (filled) => filled ? "text-foreground" : "text-
|
|
3640
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-
|
|
4677
|
+
const tone = (filled) => filled ? "text-foreground" : "text-destructive border-b border-dashed border-destructive";
|
|
4678
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-start gap-0.5", children: [
|
|
3641
4679
|
/* @__PURE__ */ jsxs(
|
|
3642
4680
|
Select,
|
|
3643
4681
|
{
|
|
@@ -3654,37 +4692,202 @@ function InstallmentSourceFields({
|
|
|
3654
4692
|
}
|
|
3655
4693
|
),
|
|
3656
4694
|
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
3657
|
-
paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children:
|
|
4695
|
+
paymentMethodTypes.map((tp) => /* @__PURE__ */ jsx(SelectItem, { value: tp.id, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
4696
|
+
/* @__PURE__ */ jsx(MethodIcon, { type: tp }),
|
|
4697
|
+
tp.name
|
|
4698
|
+
] }) }, tp.id)),
|
|
3658
4699
|
value.payMethodTypeId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
|
|
3659
4700
|
] })
|
|
3660
4701
|
]
|
|
3661
4702
|
}
|
|
3662
4703
|
),
|
|
3663
|
-
/* @__PURE__ */
|
|
3664
|
-
|
|
4704
|
+
/* @__PURE__ */ jsx(
|
|
4705
|
+
AccountPicker,
|
|
3665
4706
|
{
|
|
3666
|
-
|
|
4707
|
+
accounts,
|
|
4708
|
+
value: value.payAccountId,
|
|
3667
4709
|
disabled: disabled || !value.payMethodTypeId,
|
|
3668
|
-
|
|
4710
|
+
title: value.payMethodTypeId ? void 0 : t2("financial.invoiceForm.selectMethodFirst"),
|
|
4711
|
+
placeholder: t2("financial.payment.account"),
|
|
4712
|
+
ariaLabel: t2("financial.invoiceForm.paidFrom"),
|
|
4713
|
+
clearLabel: t2("financial.invoiceForm.clear"),
|
|
4714
|
+
className: `${INLINE_LINK_TONE.dashed} ${tone(!!selectedAccount)}`,
|
|
4715
|
+
onChange: (id) => onChange({ ...value, payAccountId: id })
|
|
4716
|
+
}
|
|
4717
|
+
)
|
|
4718
|
+
] });
|
|
4719
|
+
}
|
|
4720
|
+
var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
4721
|
+
function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className }) {
|
|
4722
|
+
const [open, setOpen] = React25__default.useState(false);
|
|
4723
|
+
const [q, setQ] = React25__default.useState("");
|
|
4724
|
+
const selected = accounts.find((a) => a.id === value);
|
|
4725
|
+
const list = q ? accounts.filter((a) => norm2(a.name).includes(norm2(q))) : accounts;
|
|
4726
|
+
const pick = (id) => {
|
|
4727
|
+
onChange(id);
|
|
4728
|
+
setOpen(false);
|
|
4729
|
+
setQ("");
|
|
4730
|
+
};
|
|
4731
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (o) => {
|
|
4732
|
+
setOpen(o);
|
|
4733
|
+
if (!o) setQ("");
|
|
4734
|
+
}, children: [
|
|
4735
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
4736
|
+
"button",
|
|
4737
|
+
{
|
|
4738
|
+
type: "button",
|
|
4739
|
+
disabled,
|
|
4740
|
+
title,
|
|
4741
|
+
"aria-label": ariaLabel,
|
|
4742
|
+
className: `inline-flex max-w-full items-center gap-1 text-xs disabled:cursor-not-allowed disabled:opacity-50 ${className ?? ""}`,
|
|
3669
4743
|
children: [
|
|
3670
|
-
/* @__PURE__ */
|
|
3671
|
-
|
|
3672
|
-
{
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
className: `${TRIGGER_CLASS} ${tone(!!selectedAccount)}`,
|
|
3676
|
-
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t2("financial.payment.account") })
|
|
3677
|
-
}
|
|
3678
|
-
),
|
|
3679
|
-
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
3680
|
-
accounts.map((a) => /* @__PURE__ */ jsx(SelectItem, { value: a.id, children: a.name }, a.id)),
|
|
3681
|
-
value.payAccountId && /* @__PURE__ */ jsx(SelectItem, { value: NONE, className: "text-muted-foreground", children: t2("financial.invoiceForm.clear") })
|
|
3682
|
-
] })
|
|
4744
|
+
selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4745
|
+
/* @__PURE__ */ jsx(AccountIcon, { account: selected }),
|
|
4746
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: selected.name })
|
|
4747
|
+
] }) : placeholder,
|
|
4748
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true })
|
|
3683
4749
|
]
|
|
3684
4750
|
}
|
|
3685
|
-
)
|
|
4751
|
+
) }),
|
|
4752
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-64 p-1", children: [
|
|
4753
|
+
/* @__PURE__ */ jsxs("div", { className: "relative mb-1", children: [
|
|
4754
|
+
/* @__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 }),
|
|
4755
|
+
/* @__PURE__ */ jsx(
|
|
4756
|
+
"input",
|
|
4757
|
+
{
|
|
4758
|
+
autoFocus: true,
|
|
4759
|
+
value: q,
|
|
4760
|
+
onChange: (e) => setQ(e.target.value),
|
|
4761
|
+
onKeyDown: (e) => {
|
|
4762
|
+
if (e.key === "Enter" && list[0]) {
|
|
4763
|
+
e.preventDefault();
|
|
4764
|
+
pick(list[0].id);
|
|
4765
|
+
}
|
|
4766
|
+
},
|
|
4767
|
+
placeholder: "Buscar conta\u2026",
|
|
4768
|
+
className: "h-8 w-full rounded-md border-0 bg-muted/50 pl-7 pr-2 text-xs outline-none focus:ring-0"
|
|
4769
|
+
}
|
|
4770
|
+
)
|
|
4771
|
+
] }),
|
|
4772
|
+
/* @__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(
|
|
4773
|
+
"button",
|
|
4774
|
+
{
|
|
4775
|
+
type: "button",
|
|
4776
|
+
onClick: () => pick(a.id),
|
|
4777
|
+
className: `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" : ""}`,
|
|
4778
|
+
children: [
|
|
4779
|
+
/* @__PURE__ */ jsx(AccountIcon, { account: a }),
|
|
4780
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: a.name }),
|
|
4781
|
+
a.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
|
|
4782
|
+
]
|
|
4783
|
+
},
|
|
4784
|
+
a.id
|
|
4785
|
+
)) }),
|
|
4786
|
+
value && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => pick(void 0), className: "mt-1 w-full rounded-md px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted", children: clearLabel })
|
|
4787
|
+
] })
|
|
4788
|
+
] });
|
|
4789
|
+
}
|
|
4790
|
+
var STATUS = {
|
|
4791
|
+
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
|
|
4792
|
+
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
|
|
4793
|
+
partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial" },
|
|
4794
|
+
paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid" },
|
|
4795
|
+
overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue" },
|
|
4796
|
+
draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
|
|
4797
|
+
cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
|
|
4798
|
+
};
|
|
4799
|
+
function InstallmentStatusBadge({ status }) {
|
|
4800
|
+
const t2 = useTranslation();
|
|
4801
|
+
const s = STATUS[status ?? ""] ?? STATUS.pending;
|
|
4802
|
+
const Icon = s.icon;
|
|
4803
|
+
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: [
|
|
4804
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-2 w-2" }),
|
|
4805
|
+
" ",
|
|
4806
|
+
t2(s.labelKey)
|
|
4807
|
+
] });
|
|
4808
|
+
}
|
|
4809
|
+
function InstallmentRow({
|
|
4810
|
+
number,
|
|
4811
|
+
amount,
|
|
4812
|
+
status,
|
|
4813
|
+
dueDate,
|
|
4814
|
+
onDueDateChange,
|
|
4815
|
+
dueExtra,
|
|
4816
|
+
source,
|
|
4817
|
+
actions,
|
|
4818
|
+
onClick
|
|
4819
|
+
}) {
|
|
4820
|
+
const t2 = useTranslation();
|
|
4821
|
+
const stop = (e) => e.stopPropagation();
|
|
4822
|
+
return /* @__PURE__ */ jsxs(
|
|
4823
|
+
"div",
|
|
4824
|
+
{
|
|
4825
|
+
className: `flex flex-wrap items-center gap-3 px-4 py-3 ${onClick ? "cursor-pointer transition-colors hover:bg-muted/20" : ""}`,
|
|
4826
|
+
onClick,
|
|
4827
|
+
children: [
|
|
4828
|
+
/* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
|
|
4829
|
+
"#",
|
|
4830
|
+
number
|
|
4831
|
+
] }),
|
|
4832
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-start gap-0.5", children: [
|
|
4833
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4834
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: amount }),
|
|
4835
|
+
status && /* @__PURE__ */ jsx(InstallmentStatusBadge, { status })
|
|
4836
|
+
] }),
|
|
4837
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground", onClick: stop, children: [
|
|
4838
|
+
t2("financial.payment.due"),
|
|
4839
|
+
onDueDateChange ? /* @__PURE__ */ jsx(DatePicker, { value: dueDate, variant: "inline", format: "numeric", onChange: onDueDateChange }) : dueDate,
|
|
4840
|
+
dueExtra
|
|
4841
|
+
] })
|
|
4842
|
+
] }),
|
|
4843
|
+
/* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", onClick: stop, children: source }),
|
|
4844
|
+
(actions || onClick) && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", onClick: stop, children: [
|
|
4845
|
+
actions,
|
|
4846
|
+
onClick && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
4847
|
+
] })
|
|
4848
|
+
]
|
|
4849
|
+
}
|
|
4850
|
+
);
|
|
4851
|
+
}
|
|
4852
|
+
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";
|
|
4853
|
+
function InstallmentPayButton({ onClick, disabled, title, children, variant = "primary" }) {
|
|
4854
|
+
return /* @__PURE__ */ jsxs(
|
|
4855
|
+
"button",
|
|
4856
|
+
{
|
|
4857
|
+
type: "button",
|
|
4858
|
+
onClick,
|
|
4859
|
+
disabled,
|
|
4860
|
+
title,
|
|
4861
|
+
className: 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`,
|
|
4862
|
+
children: [
|
|
4863
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
4864
|
+
children
|
|
4865
|
+
]
|
|
4866
|
+
}
|
|
4867
|
+
);
|
|
4868
|
+
}
|
|
4869
|
+
function InstallmentActionButton({ onClick, icon: Icon, children }) {
|
|
4870
|
+
return /* @__PURE__ */ jsxs("button", { type: "button", onClick, className: `${BTN} rounded-lg border bg-card shadow-button hover:bg-muted`, children: [
|
|
4871
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
|
|
4872
|
+
children
|
|
3686
4873
|
] });
|
|
3687
4874
|
}
|
|
4875
|
+
function InstallmentReceiptButton({ onClick, title, attached, pending: pending2, count }) {
|
|
4876
|
+
return /* @__PURE__ */ jsxs(
|
|
4877
|
+
"button",
|
|
4878
|
+
{
|
|
4879
|
+
type: "button",
|
|
4880
|
+
onClick,
|
|
4881
|
+
title,
|
|
4882
|
+
"aria-label": title,
|
|
4883
|
+
className: `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" : pending2 ? "text-primary" : ""}`,
|
|
4884
|
+
children: [
|
|
4885
|
+
/* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3" }),
|
|
4886
|
+
!!count && /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: count })
|
|
4887
|
+
]
|
|
4888
|
+
}
|
|
4889
|
+
);
|
|
4890
|
+
}
|
|
3688
4891
|
|
|
3689
4892
|
// src/views/focusItem.ts
|
|
3690
4893
|
var CHAVE = "fayz:financial:focus-item";
|
|
@@ -3709,9 +4912,9 @@ function consumirItemParaAbrir(invoiceId) {
|
|
|
3709
4912
|
}
|
|
3710
4913
|
}
|
|
3711
4914
|
function useStockedItemTypes(itemTypes, entityLookups) {
|
|
3712
|
-
const [empty, setEmpty] =
|
|
4915
|
+
const [empty, setEmpty] = React25__default.useState(null);
|
|
3713
4916
|
const backed = itemTypes.filter((type) => entityLookups[type.value]?.count).map((type) => type.value).sort().join(",");
|
|
3714
|
-
|
|
4917
|
+
React25__default.useEffect(() => {
|
|
3715
4918
|
const kinds = backed ? backed.split(",") : [];
|
|
3716
4919
|
if (!kinds.length) {
|
|
3717
4920
|
setEmpty(null);
|
|
@@ -3732,17 +4935,17 @@ function useStockedItemTypes(itemTypes, entityLookups) {
|
|
|
3732
4935
|
cancelled = true;
|
|
3733
4936
|
};
|
|
3734
4937
|
}, [backed]);
|
|
3735
|
-
return
|
|
4938
|
+
return React25__default.useMemo(
|
|
3736
4939
|
() => empty?.size ? itemTypes.filter((type) => !empty.has(type.value)) : itemTypes,
|
|
3737
4940
|
[itemTypes, empty]
|
|
3738
4941
|
);
|
|
3739
4942
|
}
|
|
3740
4943
|
|
|
3741
4944
|
// src/lib/invoice-list.ts
|
|
3742
|
-
var S\u00D3_RECEBIVEL = /* @__PURE__ */ new Set(["service", "gift_card"]);
|
|
4945
|
+
var S\u00D3_RECEBIVEL = /* @__PURE__ */ new Set(["service", "package", "gift_card"]);
|
|
3743
4946
|
function availableInvoiceItemTypes(itemTypes, lookupKinds, otherLabel, direction) {
|
|
3744
4947
|
const installed = new Set(lookupKinds);
|
|
3745
|
-
const visible = itemTypes.filter((type) => (!(direction === "debit" || direction === "payable") || !S\u00D3_RECEBIVEL.has(type.value)) && (type.value === "other" || !["product", "service"].includes(type.value) || installed.has(type.value)));
|
|
4948
|
+
const visible = itemTypes.filter((type) => (!(direction === "debit" || direction === "payable") || !S\u00D3_RECEBIVEL.has(type.value)) && (type.value === "other" || !["product", "service", "package"].includes(type.value) || installed.has(type.value)));
|
|
3746
4949
|
if (visible.some((type) => type.value === "other")) return visible;
|
|
3747
4950
|
return [...visible, { value: "other", label: otherLabel, icon: "MoreHorizontal" }];
|
|
3748
4951
|
}
|
|
@@ -3807,6 +5010,9 @@ function summarizeInvoiceLines(rows, visible = 2) {
|
|
|
3807
5010
|
}
|
|
3808
5011
|
return summaries;
|
|
3809
5012
|
}
|
|
5013
|
+
function invoiceOpensForEditing(status) {
|
|
5014
|
+
return status !== "paid" && status !== "cancelled";
|
|
5015
|
+
}
|
|
3810
5016
|
function LocationSelector({ locations, value, onChange }) {
|
|
3811
5017
|
const t2 = useTranslation();
|
|
3812
5018
|
const [open, setOpen] = useState(false);
|
|
@@ -3925,11 +5131,11 @@ function itemsKey(list) {
|
|
|
3925
5131
|
it.costCenterId ?? ""
|
|
3926
5132
|
]));
|
|
3927
5133
|
}
|
|
3928
|
-
function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fromCardId }) {
|
|
5134
|
+
function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fromCardId, embedded, onChanged }) {
|
|
3929
5135
|
const t2 = useTranslation();
|
|
3930
5136
|
const config = useFinancialConfig();
|
|
3931
5137
|
const provider = useFinancialProvider();
|
|
3932
|
-
const { currency, itemTypes: itemTypesRaw, locations } = config;
|
|
5138
|
+
const { currency, itemTypes: itemTypesRaw = [], locations = [] } = config;
|
|
3933
5139
|
const unitMode = useUnitMode();
|
|
3934
5140
|
const activeUnitId = useActiveUnitId();
|
|
3935
5141
|
const reachableUnits = useUnitFilter().options;
|
|
@@ -3977,14 +5183,14 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
3977
5183
|
};
|
|
3978
5184
|
}, [giftCardsEnabled, provider]);
|
|
3979
5185
|
const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
|
|
3980
|
-
const translatedItemTypes =
|
|
5186
|
+
const translatedItemTypes = React25__default.useMemo(
|
|
3981
5187
|
() => itemTypesRaw.map((tp) => {
|
|
3982
5188
|
const traduzido = t2(`financial.itemType.${tp.value}`);
|
|
3983
5189
|
return traduzido === `financial.itemType.${tp.value}` ? tp : { ...tp, label: traduzido };
|
|
3984
5190
|
}),
|
|
3985
5191
|
[itemTypesRaw, t2]
|
|
3986
5192
|
);
|
|
3987
|
-
const offeredItemTypes =
|
|
5193
|
+
const offeredItemTypes = React25__default.useMemo(
|
|
3988
5194
|
() => availableInvoiceItemTypes(
|
|
3989
5195
|
giftCardsEnabled && !translatedItemTypes.some((type) => type.value === "gift_card") ? [...translatedItemTypes, { value: "gift_card", label: t2("financial.itemType.gift_card"), icon: "Gift" }] : translatedItemTypes,
|
|
3990
5196
|
Object.keys(entityLookups),
|
|
@@ -4005,11 +5211,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4005
5211
|
const guardMovements = useLimitGuard("movements_month");
|
|
4006
5212
|
const can = usePermissionOptional();
|
|
4007
5213
|
const isEdit = !!editId;
|
|
5214
|
+
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
|
|
4008
5215
|
useEffect(() => {
|
|
4009
|
-
if (isEdit) return;
|
|
4010
5216
|
void fetchBankAccounts();
|
|
4011
5217
|
void fetchPaymentMethods();
|
|
4012
|
-
}, [
|
|
5218
|
+
}, []);
|
|
4013
5219
|
useEffect(() => {
|
|
4014
5220
|
let cancelled = false;
|
|
4015
5221
|
void Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
|
|
@@ -4164,7 +5370,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4164
5370
|
discount: it.discount,
|
|
4165
5371
|
surcharge: it.surcharge,
|
|
4166
5372
|
accountId: it.accountId,
|
|
4167
|
-
costCenterId: it.costCenterId
|
|
5373
|
+
costCenterId: it.costCenterId,
|
|
5374
|
+
metadata: it.metadata
|
|
4168
5375
|
};
|
|
4169
5376
|
}));
|
|
4170
5377
|
const aAbrir = consumirItemParaAbrir(editId);
|
|
@@ -4240,6 +5447,39 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4240
5447
|
function updateItem(id, data) {
|
|
4241
5448
|
setItems((current) => current.map((it) => it._id === id ? { ...it, ...data } : it));
|
|
4242
5449
|
}
|
|
5450
|
+
async function applyItemPatch(item, patch) {
|
|
5451
|
+
const expand = patch.referenceId ? entityLookups[item.itemKind]?.expand : void 0;
|
|
5452
|
+
if (!expand) {
|
|
5453
|
+
updateItem(item._id, patch);
|
|
5454
|
+
return;
|
|
5455
|
+
}
|
|
5456
|
+
let linhas = [];
|
|
5457
|
+
try {
|
|
5458
|
+
linhas = await expand(patch.referenceId);
|
|
5459
|
+
} catch (err) {
|
|
5460
|
+
console.error("[financial] failed to expand package", err);
|
|
5461
|
+
}
|
|
5462
|
+
if (!linhas.length) {
|
|
5463
|
+
updateItem(item._id, patch);
|
|
5464
|
+
return;
|
|
5465
|
+
}
|
|
5466
|
+
const oferecidos = new Set(itemTypes.map((tp) => tp.value));
|
|
5467
|
+
const novas = linhas.map((linha) => ({
|
|
5468
|
+
_id: fid(),
|
|
5469
|
+
itemKind: linha.itemKind && oferecidos.has(linha.itemKind) ? linha.itemKind : item.itemKind,
|
|
5470
|
+
referenceId: linha.referenceId,
|
|
5471
|
+
description: linha.description,
|
|
5472
|
+
quantity: linha.quantity,
|
|
5473
|
+
unitPrice: linha.unitPrice,
|
|
5474
|
+
...linha.discount ? { discount: linha.discount } : {},
|
|
5475
|
+
...linha.surcharge ? { surcharge: linha.surcharge } : {},
|
|
5476
|
+
...linha.metadata ? { metadata: linha.metadata } : {},
|
|
5477
|
+
accountId: item.accountId,
|
|
5478
|
+
costCenterId: item.costCenterId
|
|
5479
|
+
}));
|
|
5480
|
+
setItems((current) => current.flatMap((it) => it._id === item._id ? novas : [it]));
|
|
5481
|
+
setExpandedItemId(null);
|
|
5482
|
+
}
|
|
4243
5483
|
function removeItem(id) {
|
|
4244
5484
|
setItems((current) => current.filter((it) => it._id !== id));
|
|
4245
5485
|
}
|
|
@@ -4247,13 +5487,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4247
5487
|
if (isEdit || totalAmount <= 0 || installments.length > 0) return;
|
|
4248
5488
|
generateInstallments(1);
|
|
4249
5489
|
}, [isEdit, totalAmount, installments.length]);
|
|
4250
|
-
const fromCard =
|
|
5490
|
+
const fromCard = React25__default.useMemo(
|
|
4251
5491
|
() => fromCardId ? bankAccounts.find((a) => a.id === fromCardId) : void 0,
|
|
4252
5492
|
[fromCardId, bankAccounts]
|
|
4253
5493
|
);
|
|
4254
5494
|
const cardLast4 = (fromCard?.accountNumber ?? "").replace(/\D/g, "").slice(-4);
|
|
4255
5495
|
const nomeJaTemOsDigitos = !!cardLast4 && (fromCard?.name ?? "").replace(/\D/g, "").endsWith(cardLast4);
|
|
4256
|
-
const cardMethodName =
|
|
5496
|
+
const cardMethodName = React25__default.useMemo(() => {
|
|
4257
5497
|
const id = installments[0]?.payMethodTypeId;
|
|
4258
5498
|
const escolhida = id ? paymentMethodTypes.find((tp) => tp.id === id) : void 0;
|
|
4259
5499
|
return (escolhida ?? paymentMethodTypes.find((tp) => tp.transactionType === "credit_card"))?.name;
|
|
@@ -4505,7 +5745,12 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4505
5745
|
dirty,
|
|
4506
5746
|
// Criar ou editar: a barra é o caminho de salvar, e sumir porque nada mudou
|
|
4507
5747
|
// ainda deixa a tela sem botão. Ela some quando o formulário fecha.
|
|
4508
|
-
|
|
5748
|
+
//
|
|
5749
|
+
// EMBUTIDO é o contrário: o modal já tem o próprio fechar, então a barra não
|
|
5750
|
+
// é o único caminho de saída — e «Alterações não salvas» sobre um formulário
|
|
5751
|
+
// que ninguém tocou é uma mentira que ensina a ignorar o aviso. Ali ela
|
|
5752
|
+
// aparece quando há o que salvar.
|
|
5753
|
+
alwaysVisible: !embedded,
|
|
4509
5754
|
saving,
|
|
4510
5755
|
onSave: () => {
|
|
4511
5756
|
void handleSave();
|
|
@@ -4515,8 +5760,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4515
5760
|
});
|
|
4516
5761
|
const title = isEdit ? t2("financial.invoiceForm.editTitle") : t2("financial.invoiceForm.newTitle");
|
|
4517
5762
|
const subtitle = isEdit ? t2("financial.invoiceForm.editing") : t2("financial.invoiceForm.creating");
|
|
4518
|
-
if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, { parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"), onBack: () => onSaved?.() });
|
|
4519
|
-
const
|
|
5763
|
+
if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, { parentLabel: embedded ? void 0 : direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"), onBack: embedded ? void 0 : () => onSaved?.() });
|
|
5764
|
+
const money4 = (v) => formatCurrency(v, currency);
|
|
4520
5765
|
const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
|
|
4521
5766
|
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
|
|
4522
5767
|
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(
|
|
@@ -4569,7 +5814,6 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4569
5814
|
const saved = savedMovements[i];
|
|
4570
5815
|
const isPaid2 = saved?.status === "paid";
|
|
4571
5816
|
const isPartial = saved?.status === "partial";
|
|
4572
|
-
const StatusIcon = isPaid2 ? CircleCheckBig : CircleDashed;
|
|
4573
5817
|
const canAttach = isEdit ? !!saved && !isPaid2 : true;
|
|
4574
5818
|
const semFonte = Math.abs(inst.amount) < 5e-3 && !isPaid2 && !isPartial;
|
|
4575
5819
|
const fonteTravada = isPaid2 || isPartial;
|
|
@@ -4577,75 +5821,61 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4577
5821
|
const contaEscolhida = bankAccounts.find((a) => a.id === inst.payAccountId);
|
|
4578
5822
|
const faltaParaPagar = !inst.payMethodTypeId ? t2("financial.payment.methodRequired") : !inst.payAccountId ? t2("financial.payment.accountRequired") : null;
|
|
4579
5823
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
4580
|
-
/* @__PURE__ */
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
i + 1
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: "\u2014" })
|
|
4594
|
-
) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
|
|
4595
|
-
InstallmentSourceFields,
|
|
4596
|
-
{
|
|
4597
|
-
direction,
|
|
4598
|
-
value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
|
|
4599
|
-
onChange: (next) => patch(next)
|
|
4600
|
-
}
|
|
4601
|
-
),
|
|
4602
|
-
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [
|
|
4603
|
-
!hasPayments ? /* @__PURE__ */ jsx(
|
|
4604
|
-
DatePicker,
|
|
4605
|
-
{
|
|
4606
|
-
value: inst.dueDate,
|
|
4607
|
-
variant: "inline",
|
|
4608
|
-
format: "numeric",
|
|
4609
|
-
onChange: (v) => patch({ dueDate: v })
|
|
4610
|
-
}
|
|
4611
|
-
) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
|
|
4612
|
-
canAttach && /* @__PURE__ */ jsx(
|
|
4613
|
-
"button",
|
|
5824
|
+
/* @__PURE__ */ jsx(
|
|
5825
|
+
InstallmentRow,
|
|
5826
|
+
{
|
|
5827
|
+
number: i + 1,
|
|
5828
|
+
amount: money4(inst.amount),
|
|
5829
|
+
status: saved?.status,
|
|
5830
|
+
dueDate: inst.dueDate,
|
|
5831
|
+
onDueDateChange: !hasPayments ? (v) => patch({ dueDate: v }) : void 0,
|
|
5832
|
+
source: semFonte ? (
|
|
5833
|
+
// A zero-value line (gift, 100% discount) has no method to ask for.
|
|
5834
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" })
|
|
5835
|
+
) : fonteTravada ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: [formaEscolhida?.name, contaEscolhida?.name].filter(Boolean).join(" \xB7 ") }) : /* @__PURE__ */ jsx(
|
|
5836
|
+
InstallmentSourceFields,
|
|
4614
5837
|
{
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
patch({ receiptUrl: pendingReceipt });
|
|
4619
|
-
setPendingReceipt(void 0);
|
|
4620
|
-
return;
|
|
4621
|
-
}
|
|
4622
|
-
setReceiptTarget(i);
|
|
4623
|
-
receiptPicker.open();
|
|
4624
|
-
},
|
|
4625
|
-
title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
|
|
4626
|
-
"aria-label": !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
|
|
4627
|
-
className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors ${inst.receiptUrl ? "text-success hover:bg-success-soft" : pendingReceipt ? "text-primary hover:bg-primary/10" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
4628
|
-
children: inst.receiptUrl ? /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Camera, { className: "h-3.5 w-3.5" })
|
|
5838
|
+
direction,
|
|
5839
|
+
value: { payMethodTypeId: inst.payMethodTypeId, payAccountId: inst.payAccountId },
|
|
5840
|
+
onChange: (next) => patch(next)
|
|
4629
5841
|
}
|
|
4630
5842
|
),
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
5843
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5844
|
+
(canAttach || isEdit) && /* @__PURE__ */ jsx(
|
|
5845
|
+
InstallmentReceiptButton,
|
|
5846
|
+
{
|
|
5847
|
+
attached: !!inst.receiptUrl,
|
|
5848
|
+
pending: !inst.receiptUrl && !!pendingReceipt,
|
|
5849
|
+
title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
|
|
5850
|
+
onClick: () => {
|
|
5851
|
+
if (isEdit && editId) {
|
|
5852
|
+
setAttachmentsOpen(true);
|
|
5853
|
+
return;
|
|
5854
|
+
}
|
|
5855
|
+
if (!inst.receiptUrl && pendingReceipt) {
|
|
5856
|
+
patch({ receiptUrl: pendingReceipt });
|
|
5857
|
+
setPendingReceipt(void 0);
|
|
5858
|
+
return;
|
|
5859
|
+
}
|
|
5860
|
+
setReceiptTarget(i);
|
|
5861
|
+
receiptPicker.open();
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5864
|
+
),
|
|
5865
|
+
!isPaid2 && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsx(
|
|
5866
|
+
InstallmentPayButton,
|
|
5867
|
+
{
|
|
5868
|
+
disabled: saving || !!faltaParaPagar,
|
|
5869
|
+
title: faltaParaPagar ?? void 0,
|
|
5870
|
+
onClick: () => {
|
|
5871
|
+
void pagarParcela(i);
|
|
5872
|
+
},
|
|
5873
|
+
children: t2("financial.invoice.pay")
|
|
5874
|
+
}
|
|
5875
|
+
)
|
|
5876
|
+
] })
|
|
5877
|
+
}
|
|
5878
|
+
),
|
|
4649
5879
|
!isEdit && inst.receiptUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t bg-muted/20 px-3 py-2", children: [
|
|
4650
5880
|
/* @__PURE__ */ jsx(
|
|
4651
5881
|
"img",
|
|
@@ -4815,10 +6045,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4815
6045
|
/* @__PURE__ */ jsxs(
|
|
4816
6046
|
OrderFormView,
|
|
4817
6047
|
{
|
|
6048
|
+
embedded,
|
|
4818
6049
|
title,
|
|
4819
6050
|
subtitle,
|
|
4820
|
-
parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
|
|
4821
|
-
onBack: () => onSaved?.(),
|
|
6051
|
+
parentLabel: embedded ? void 0 : direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
|
|
6052
|
+
onBack: embedded ? void 0 : () => onSaved?.(),
|
|
4822
6053
|
actions,
|
|
4823
6054
|
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: t2("financial.invoiceForm.issuedLocked") }) : void 0,
|
|
4824
6055
|
sideSections,
|
|
@@ -4836,7 +6067,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4836
6067
|
count: items.length,
|
|
4837
6068
|
countLabel: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
|
|
4838
6069
|
totalLabel: t2("financial.invoiceForm.total"),
|
|
4839
|
-
total:
|
|
6070
|
+
total: money4(totalAmount),
|
|
4840
6071
|
emptyLabel: contactId ? t2("financial.invoiceForm.noItems") : t2("financial.invoiceForm.selectContactFirst"),
|
|
4841
6072
|
emptyActionLabel: t2("financial.invoiceForm.addFirstItem"),
|
|
4842
6073
|
header: addingItem && /* @__PURE__ */ jsx(
|
|
@@ -4862,11 +6093,13 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4862
6093
|
referenceId: item.referenceId,
|
|
4863
6094
|
itemTypes,
|
|
4864
6095
|
currency,
|
|
4865
|
-
formatMoney:
|
|
6096
|
+
formatMoney: money4,
|
|
4866
6097
|
expanded: expandedItemId === item._id,
|
|
4867
6098
|
readOnly: !canEditItems,
|
|
4868
6099
|
onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
|
|
4869
|
-
onChange: (patch) =>
|
|
6100
|
+
onChange: (patch) => {
|
|
6101
|
+
void applyItemPatch(item, patch);
|
|
6102
|
+
},
|
|
4870
6103
|
onRemove: () => {
|
|
4871
6104
|
removeItem(item._id);
|
|
4872
6105
|
if (expandedItemId === item._id) setExpandedItemId(null);
|
|
@@ -4927,6 +6160,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4927
6160
|
}] : []
|
|
4928
6161
|
],
|
|
4929
6162
|
entityLookups,
|
|
6163
|
+
pickOnly: Boolean(entityLookups[item.itemKind]?.expand) && !item.referenceId,
|
|
4930
6164
|
showTypeSelector: hasItemLookups,
|
|
4931
6165
|
allowSurcharge: true,
|
|
4932
6166
|
labels: {
|
|
@@ -4953,6 +6187,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4953
6187
|
}
|
|
4954
6188
|
),
|
|
4955
6189
|
receiptPicker.input,
|
|
6190
|
+
attachmentsOpen && editId && /* @__PURE__ */ jsx(InvoiceAttachmentsModal, { invoiceId: editId, onClose: () => setAttachmentsOpen(false) }),
|
|
4956
6191
|
payingMovement && /* @__PURE__ */ jsx(
|
|
4957
6192
|
PaymentModal,
|
|
4958
6193
|
{
|
|
@@ -4982,6 +6217,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
4982
6217
|
setSavedMovements(
|
|
4983
6218
|
movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
|
|
4984
6219
|
);
|
|
6220
|
+
onChanged?.();
|
|
4985
6221
|
})();
|
|
4986
6222
|
}
|
|
4987
6223
|
}
|
|
@@ -5047,6 +6283,11 @@ function groupPaymentsByBill(bills, payments) {
|
|
|
5047
6283
|
}
|
|
5048
6284
|
return { byBillId, unattributed };
|
|
5049
6285
|
}
|
|
6286
|
+
var reversalOf = (m) => m.metadata?.reversal_of;
|
|
6287
|
+
function reversiblePayments(payments) {
|
|
6288
|
+
const reversed = new Set(payments.map(reversalOf).filter(Boolean));
|
|
6289
|
+
return payments.filter((m) => !reversalOf(m) && !reversed.has(m.id));
|
|
6290
|
+
}
|
|
5050
6291
|
|
|
5051
6292
|
// src/lib/payment-how.ts
|
|
5052
6293
|
var NOT_A_HOLDER = /* @__PURE__ */ new Set(["person", "system"]);
|
|
@@ -5094,6 +6335,7 @@ function PaymentReceiptModal({
|
|
|
5094
6335
|
const { currency } = useFinancialConfig();
|
|
5095
6336
|
const fmt = (n) => formatCurrency(n, currency);
|
|
5096
6337
|
const ordenados = [...payments].sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? "") || (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
|
|
6338
|
+
const estornaveis = new Set(reversiblePayments(ordenados).map((m) => m.id));
|
|
5097
6339
|
const total = ordenados.reduce((soma, m) => soma + (ehEstorno(m) ? -1 : 1) * (m.paidAmount || m.amount), 0);
|
|
5098
6340
|
return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (o) => {
|
|
5099
6341
|
if (!o) onClose();
|
|
@@ -5133,7 +6375,7 @@ function PaymentReceiptModal({
|
|
|
5133
6375
|
] }),
|
|
5134
6376
|
/* @__PURE__ */ jsx("span", { className: "text-xs", children: payment.notes })
|
|
5135
6377
|
] }),
|
|
5136
|
-
onReverse &&
|
|
6378
|
+
onReverse && estornaveis.has(payment.id) && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 pt-2", children: [
|
|
5137
6379
|
/* @__PURE__ */ jsx(Hint, { label: t2("financial.paymentReceipt.unlinkUnavailable"), children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "w-full", disabled: true, children: [
|
|
5138
6380
|
/* @__PURE__ */ jsx(Unlink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
5139
6381
|
t2("financial.paymentReceipt.unlink")
|
|
@@ -5165,14 +6407,6 @@ var ITEM_ICONS = {
|
|
|
5165
6407
|
gift_card: Gift,
|
|
5166
6408
|
other: Receipt
|
|
5167
6409
|
};
|
|
5168
|
-
var HOW_ICONS = {
|
|
5169
|
-
cash: Banknote,
|
|
5170
|
-
pix: QrCode,
|
|
5171
|
-
credit_card: CreditCard,
|
|
5172
|
-
debit_card: CreditCard,
|
|
5173
|
-
bank_transfer: ArrowRightLeft,
|
|
5174
|
-
check: FileCheck
|
|
5175
|
-
};
|
|
5176
6410
|
var STATUS_COLORS = {
|
|
5177
6411
|
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
|
|
5178
6412
|
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
|
|
@@ -5206,7 +6440,6 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5206
6440
|
const unitName = useUnitStore((st) => unitMode && invoice?.unitId ? st.byId[invoice.unitId]?.name : void 0);
|
|
5207
6441
|
const [items, setItems] = useState([]);
|
|
5208
6442
|
const [attachments, setAttachments] = useState([]);
|
|
5209
|
-
const [viewing, setViewing] = useState(null);
|
|
5210
6443
|
const [movements, setMovements] = useState([]);
|
|
5211
6444
|
const [payments, setPayments] = useState([]);
|
|
5212
6445
|
const [holders, setHolders] = useState(/* @__PURE__ */ new Map());
|
|
@@ -5216,13 +6449,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5216
6449
|
const [receiptBill, setReceiptBill] = useState(null);
|
|
5217
6450
|
const [reverseReason, setReverseReason] = useState("");
|
|
5218
6451
|
const [payingMovement, setPayingMovement] = useState(null);
|
|
5219
|
-
const [
|
|
6452
|
+
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
|
|
5220
6453
|
const [pendingReceipt, setPendingReceipt] = useState();
|
|
5221
|
-
const receiptPicker = useReceiptPicker((dataUrl) => {
|
|
5222
|
-
setPendingReceipt(dataUrl);
|
|
5223
|
-
setPayingMovement(receiptFor);
|
|
5224
|
-
setReceiptFor(null);
|
|
5225
|
-
});
|
|
5226
6454
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
5227
6455
|
const [confirmCancel, setConfirmCancel] = useState(false);
|
|
5228
6456
|
const [cancelling, setCancelling] = useState(false);
|
|
@@ -5253,6 +6481,17 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5253
6481
|
void loadData();
|
|
5254
6482
|
}
|
|
5255
6483
|
}
|
|
6484
|
+
async function patchInvoiceDate(invoiceDate) {
|
|
6485
|
+
if (!invoice || !invoiceDate || invoiceDate === invoice.invoiceDate) return;
|
|
6486
|
+
const antes = invoice;
|
|
6487
|
+
setInvoice({ ...invoice, invoiceDate });
|
|
6488
|
+
try {
|
|
6489
|
+
await provider.updateInvoice(invoice.id, { invoiceDate });
|
|
6490
|
+
} catch (e) {
|
|
6491
|
+
setInvoice(antes);
|
|
6492
|
+
toast.error(e?.message ?? String(e));
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
5256
6495
|
const reqRef = useRef(0);
|
|
5257
6496
|
async function loadData() {
|
|
5258
6497
|
const req = ++reqRef.current;
|
|
@@ -5343,8 +6582,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5343
6582
|
setCancelling(false);
|
|
5344
6583
|
}
|
|
5345
6584
|
}
|
|
5346
|
-
const [chartNames, setChartNames] =
|
|
5347
|
-
|
|
6585
|
+
const [chartNames, setChartNames] = React25__default.useState(/* @__PURE__ */ new Map());
|
|
6586
|
+
React25__default.useEffect(() => {
|
|
5348
6587
|
let cancelled2 = false;
|
|
5349
6588
|
void provider.getChartOfAccounts().then((rows) => {
|
|
5350
6589
|
if (!cancelled2) setChartNames(new Map(rows.map((r) => [r.id, r.name])));
|
|
@@ -5464,7 +6703,14 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5464
6703
|
{ id: "total", label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
|
|
5465
6704
|
{ id: "items", label: t2("financial.invoice.items"), value: items.length, icon: LayoutList },
|
|
5466
6705
|
{ id: "installments", label: t2("financial.invoice.installments"), value: `${invoice.totalInstallments}x`, icon: Layers },
|
|
5467
|
-
{
|
|
6706
|
+
{
|
|
6707
|
+
id: "date",
|
|
6708
|
+
label: t2("financial.invoice.date"),
|
|
6709
|
+
icon: CalendarDays,
|
|
6710
|
+
value: invoice.status !== "cancelled" ? /* @__PURE__ */ jsx(DatePicker, { value: invoice.invoiceDate, variant: "inline", format: "numeric", onChange: (v) => {
|
|
6711
|
+
void patchInvoiceDate(v);
|
|
6712
|
+
} }) : invoice.invoiceDate
|
|
6713
|
+
}
|
|
5468
6714
|
],
|
|
5469
6715
|
customerLabel: direction === "debit" ? t2("financial.invoice.payTo") : t2("financial.invoice.receiveFrom"),
|
|
5470
6716
|
unit: unitName ? { label: t2("financial.invoice.unit"), value: unitName } : void 0,
|
|
@@ -5482,7 +6728,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5482
6728
|
"button",
|
|
5483
6729
|
{
|
|
5484
6730
|
type: "button",
|
|
5485
|
-
onClick: () =>
|
|
6731
|
+
onClick: () => setAttachmentsOpen(true),
|
|
5486
6732
|
title: attachments.map((a) => a.fileName).join("\n"),
|
|
5487
6733
|
className: "mb-2 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",
|
|
5488
6734
|
children: [
|
|
@@ -5497,14 +6743,12 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5497
6743
|
const isPaid2 = mov.status === "paid";
|
|
5498
6744
|
const isPartial = mov.status === "partial";
|
|
5499
6745
|
const hasPaidDetails = isPaid2 || isPartial;
|
|
5500
|
-
const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
|
|
5501
|
-
const MovIcon = movStatus.icon;
|
|
5502
6746
|
const instPayments = settlements.byBillId.get(mov.id) ?? [];
|
|
5503
6747
|
const hows = instPayments.map((payment) => describePayment(payment, howCtx));
|
|
5504
6748
|
const paymentSummary = [...new Set(hows.map((how2) => [how2.method, how2.account?.name].filter(Boolean).join(" \xB7 ")).filter(Boolean))].join(" / ");
|
|
5505
6749
|
const how = hows[0];
|
|
5506
6750
|
const howCard = how?.account?.accountType === "card_payable" ? how.account : void 0;
|
|
5507
|
-
const HowIcon =
|
|
6751
|
+
const HowIcon = METHOD_ICONS[how?.typeKind ?? ""] ?? (how ? CreditCard : null);
|
|
5508
6752
|
const srcMeta = mov.metadata ?? {};
|
|
5509
6753
|
const srcStr = (v) => typeof v === "string" && v ? v : void 0;
|
|
5510
6754
|
const srcTypeId = srcStr(srcMeta.payMethodTypeId);
|
|
@@ -5512,66 +6756,41 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5512
6756
|
const editavel = !hasPaidDetails && invoice.status !== "cancelled";
|
|
5513
6757
|
const canChooseSource = editavel && Math.abs(mov.amount) >= 5e-3;
|
|
5514
6758
|
const faltaParaPagar = !srcTypeId ? t2("financial.payment.methodRequired") : !srcAccountId ? t2("financial.payment.accountRequired") : null;
|
|
5515
|
-
|
|
5516
|
-
|
|
6759
|
+
const ativa = invoice.status !== "cancelled";
|
|
6760
|
+
return /* @__PURE__ */ jsx(
|
|
6761
|
+
InstallmentRow,
|
|
5517
6762
|
{
|
|
5518
|
-
|
|
6763
|
+
number: mov.installmentNumber ?? 1,
|
|
6764
|
+
amount: formatCurrency(mov.amount, currency),
|
|
6765
|
+
status: mov.status,
|
|
6766
|
+
dueDate: mov.dueDate,
|
|
6767
|
+
onDueDateChange: editavel ? (v) => {
|
|
6768
|
+
void patchDueDate(mov, v);
|
|
6769
|
+
} : void 0,
|
|
6770
|
+
dueExtra: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6771
|
+
mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
|
|
6772
|
+
mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
|
|
6773
|
+
] }),
|
|
5519
6774
|
onClick: hasPaidDetails ? () => setReceiptBill(mov) : void 0,
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
t2(movStatus.labelKey)
|
|
5531
|
-
] }),
|
|
5532
|
-
/* @__PURE__ */ jsxs(
|
|
5533
|
-
"span",
|
|
5534
|
-
{
|
|
5535
|
-
className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-[10px] text-muted-foreground",
|
|
5536
|
-
onClick: (e) => e.stopPropagation(),
|
|
5537
|
-
children: [
|
|
5538
|
-
t2("financial.payment.due"),
|
|
5539
|
-
editavel ? /* @__PURE__ */ jsx(
|
|
5540
|
-
DatePicker,
|
|
5541
|
-
{
|
|
5542
|
-
value: mov.dueDate,
|
|
5543
|
-
variant: "inline",
|
|
5544
|
-
format: "numeric",
|
|
5545
|
-
onChange: (v) => {
|
|
5546
|
-
void patchDueDate(mov, v);
|
|
5547
|
-
}
|
|
5548
|
-
}
|
|
5549
|
-
) : mov.dueDate,
|
|
5550
|
-
mov.paidAmount > 0 && !isPaid2 && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
|
|
5551
|
-
mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
|
|
5552
|
-
]
|
|
5553
|
-
}
|
|
5554
|
-
)
|
|
5555
|
-
] }),
|
|
5556
|
-
canChooseSource && /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
|
|
5557
|
-
InstallmentSourceFields,
|
|
5558
|
-
{
|
|
5559
|
-
direction,
|
|
5560
|
-
value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
|
|
5561
|
-
onChange: (next) => {
|
|
5562
|
-
void patchSource(mov, {
|
|
5563
|
-
payMethodTypeId: next.payMethodTypeId ?? null,
|
|
5564
|
-
payAccountId: next.payAccountId ?? null
|
|
5565
|
-
});
|
|
5566
|
-
}
|
|
6775
|
+
source: canChooseSource ? /* @__PURE__ */ jsx(
|
|
6776
|
+
InstallmentSourceFields,
|
|
6777
|
+
{
|
|
6778
|
+
direction,
|
|
6779
|
+
value: { payMethodTypeId: srcTypeId, payAccountId: srcAccountId },
|
|
6780
|
+
onChange: (next) => {
|
|
6781
|
+
void patchSource(mov, {
|
|
6782
|
+
payMethodTypeId: next.payMethodTypeId ?? null,
|
|
6783
|
+
payAccountId: next.payAccountId ?? null
|
|
6784
|
+
});
|
|
5567
6785
|
}
|
|
5568
|
-
|
|
6786
|
+
}
|
|
6787
|
+
) : void 0,
|
|
6788
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5569
6789
|
paymentSummary && /* @__PURE__ */ jsx(
|
|
5570
6790
|
"button",
|
|
5571
6791
|
{
|
|
5572
6792
|
type: "button",
|
|
5573
|
-
onClick: (
|
|
5574
|
-
e.stopPropagation();
|
|
6793
|
+
onClick: () => {
|
|
5575
6794
|
if (hasPaidDetails) setReceiptBill(mov);
|
|
5576
6795
|
},
|
|
5577
6796
|
title: paymentSummary,
|
|
@@ -5580,72 +6799,38 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5580
6799
|
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
|
|
5581
6800
|
}
|
|
5582
6801
|
),
|
|
5583
|
-
|
|
5584
|
-
|
|
6802
|
+
/* @__PURE__ */ jsx(
|
|
6803
|
+
InstallmentReceiptButton,
|
|
5585
6804
|
{
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
},
|
|
5591
|
-
title: t2("financial.quickTx.receiptCapture"),
|
|
5592
|
-
"aria-label": t2("financial.quickTx.receiptCapture"),
|
|
5593
|
-
className: "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",
|
|
5594
|
-
children: /* @__PURE__ */ jsx(Camera, { className: "h-3 w-3" })
|
|
6805
|
+
title: "Anexos",
|
|
6806
|
+
attached: attachments.length > 0,
|
|
6807
|
+
count: attachments.length,
|
|
6808
|
+
onClick: () => setAttachmentsOpen(true)
|
|
5595
6809
|
}
|
|
5596
6810
|
),
|
|
5597
|
-
!isPaid2 && !isPartial &&
|
|
5598
|
-
|
|
6811
|
+
!isPaid2 && !isPartial && ativa && /* @__PURE__ */ jsxs(
|
|
6812
|
+
InstallmentPayButton,
|
|
5599
6813
|
{
|
|
5600
|
-
onClick: (
|
|
5601
|
-
e.stopPropagation();
|
|
5602
|
-
setPayingMovement(mov);
|
|
5603
|
-
},
|
|
6814
|
+
onClick: () => setPayingMovement(mov),
|
|
5604
6815
|
disabled: !!faltaParaPagar,
|
|
5605
6816
|
title: faltaParaPagar ?? void 0,
|
|
5606
|
-
className: "inline-flex items-center gap-1 rounded-button bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0 disabled:opacity-40 disabled:pointer-events-none",
|
|
5607
6817
|
children: [
|
|
5608
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
5609
6818
|
t2("financial.invoice.pay"),
|
|
5610
6819
|
" ",
|
|
5611
6820
|
remaining < mov.amount ? formatCurrency(remaining, currency) : ""
|
|
5612
6821
|
]
|
|
5613
6822
|
}
|
|
5614
6823
|
),
|
|
5615
|
-
isPaid2 && instPayments.length > 0 &&
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
t2("financial.invoice.reverse")
|
|
5626
|
-
]
|
|
5627
|
-
}
|
|
5628
|
-
),
|
|
5629
|
-
isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
5630
|
-
"button",
|
|
5631
|
-
{
|
|
5632
|
-
onClick: (e) => {
|
|
5633
|
-
e.stopPropagation();
|
|
5634
|
-
setPayingMovement(mov);
|
|
5635
|
-
},
|
|
5636
|
-
className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
|
|
5637
|
-
children: [
|
|
5638
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
5639
|
-
t2("financial.invoice.pay"),
|
|
5640
|
-
" ",
|
|
5641
|
-
formatCurrency(remaining, currency)
|
|
5642
|
-
]
|
|
5643
|
-
}
|
|
5644
|
-
),
|
|
5645
|
-
hasPaidDetails && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
5646
|
-
]
|
|
5647
|
-
}
|
|
5648
|
-
) }, mov.id);
|
|
6824
|
+
isPaid2 && reversiblePayments(instPayments).length > 0 && ativa && /* @__PURE__ */ jsx(InstallmentActionButton, { icon: Undo2, onClick: () => setReversing(reversiblePayments(instPayments).at(-1)), children: t2("financial.invoice.reverse") }),
|
|
6825
|
+
isPartial && ativa && /* @__PURE__ */ jsxs(InstallmentPayButton, { variant: "outline", onClick: () => setPayingMovement(mov), children: [
|
|
6826
|
+
t2("financial.invoice.pay"),
|
|
6827
|
+
" ",
|
|
6828
|
+
formatCurrency(remaining, currency)
|
|
6829
|
+
] })
|
|
6830
|
+
] })
|
|
6831
|
+
},
|
|
6832
|
+
mov.id
|
|
6833
|
+
);
|
|
5649
6834
|
}) }),
|
|
5650
6835
|
settlements.unattributed.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t bg-warning-soft/40 px-4 py-2.5", children: [
|
|
5651
6836
|
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground", children: t2("financial.invoice.unlinkedPayments") }),
|
|
@@ -5664,97 +6849,15 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
5664
6849
|
extraSections: sideBlocks
|
|
5665
6850
|
}
|
|
5666
6851
|
),
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
{
|
|
5677
|
-
"data-modal-passthrough": true,
|
|
5678
|
-
className: "pointer-events-auto fixed inset-0 z-[200] flex flex-col bg-black/80 p-4",
|
|
5679
|
-
onClick: () => setViewing(null),
|
|
5680
|
-
role: "dialog",
|
|
5681
|
-
"aria-modal": "true",
|
|
5682
|
-
"aria-label": doc.fileName,
|
|
5683
|
-
children: /* @__PURE__ */ jsxs(
|
|
5684
|
-
"div",
|
|
5685
|
-
{
|
|
5686
|
-
className: "mx-auto flex w-full max-w-4xl flex-1 flex-col gap-3 overflow-hidden",
|
|
5687
|
-
onClick: (e) => e.stopPropagation(),
|
|
5688
|
-
children: [
|
|
5689
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-white", children: [
|
|
5690
|
-
/* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 shrink-0 opacity-70", "aria-hidden": true }),
|
|
5691
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
5692
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", title: doc.fileName, children: doc.fileName }),
|
|
5693
|
-
/* @__PURE__ */ jsxs("p", { className: "text-[11px] opacity-60", children: [
|
|
5694
|
-
attachments.length > 1 ? `${viewing + 1} de ${attachments.length}` : null,
|
|
5695
|
-
doc.fileSize ? `${attachments.length > 1 ? " \xB7 " : ""}${Math.round(doc.fileSize / 1024)} KB` : null
|
|
5696
|
-
] })
|
|
5697
|
-
] }),
|
|
5698
|
-
doc.url ? /* @__PURE__ */ jsxs(
|
|
5699
|
-
"a",
|
|
5700
|
-
{
|
|
5701
|
-
href: doc.url,
|
|
5702
|
-
target: "_blank",
|
|
5703
|
-
rel: "noreferrer",
|
|
5704
|
-
className: "inline-flex shrink-0 items-center gap-1 rounded-lg border border-white/20 px-2.5 py-1.5 text-xs font-medium text-white hover:bg-white/10",
|
|
5705
|
-
children: [
|
|
5706
|
-
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
5707
|
-
" Abrir"
|
|
5708
|
-
]
|
|
5709
|
-
}
|
|
5710
|
-
) : null,
|
|
5711
|
-
/* @__PURE__ */ jsx(
|
|
5712
|
-
"button",
|
|
5713
|
-
{
|
|
5714
|
-
type: "button",
|
|
5715
|
-
onClick: () => setViewing(null),
|
|
5716
|
-
"aria-label": "Fechar",
|
|
5717
|
-
className: "shrink-0 rounded-lg border border-white/20 px-2.5 py-1.5 text-xs font-medium text-white hover:bg-white/10",
|
|
5718
|
-
children: "Fechar"
|
|
5719
|
-
}
|
|
5720
|
-
)
|
|
5721
|
-
] }),
|
|
5722
|
-
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center rounded-xl bg-white/5", children: doc.pending || !doc.url ? /* @__PURE__ */ jsxs("p", { className: "max-w-sm p-8 text-center text-sm text-white/70", children: [
|
|
5723
|
-
"O registro deste anexo atravessou, o arquivo ainda n\xE3o. Ele continua no V1 \u2014 procure por ",
|
|
5724
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: doc.fileName }),
|
|
5725
|
-
"."
|
|
5726
|
-
] }) : ehImagem ? /* @__PURE__ */ jsx("img", { src: doc.url, alt: doc.fileName, className: "max-h-full max-w-full object-contain" }) : ehPdf ? /* @__PURE__ */ jsx("iframe", { src: doc.url, title: doc.fileName, className: "h-full w-full rounded-xl bg-white" }) : /* @__PURE__ */ jsxs("p", { className: "p-8 text-center text-sm text-white/70", children: [
|
|
5727
|
-
"Este tipo de arquivo n\xE3o tem pr\xE9-visualiza\xE7\xE3o. Use ",
|
|
5728
|
-
/* @__PURE__ */ jsx("strong", { children: "Abrir" }),
|
|
5729
|
-
"."
|
|
5730
|
-
] }) }),
|
|
5731
|
-
attachments.length > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
|
|
5732
|
-
/* @__PURE__ */ jsx(
|
|
5733
|
-
"button",
|
|
5734
|
-
{
|
|
5735
|
-
type: "button",
|
|
5736
|
-
onClick: () => irPara(-1),
|
|
5737
|
-
className: "rounded-lg border border-white/20 px-3 py-1.5 text-xs font-medium text-white hover:bg-white/10",
|
|
5738
|
-
children: "Anterior"
|
|
5739
|
-
}
|
|
5740
|
-
),
|
|
5741
|
-
/* @__PURE__ */ jsx(
|
|
5742
|
-
"button",
|
|
5743
|
-
{
|
|
5744
|
-
type: "button",
|
|
5745
|
-
onClick: () => irPara(1),
|
|
5746
|
-
className: "rounded-lg border border-white/20 px-3 py-1.5 text-xs font-medium text-white hover:bg-white/10",
|
|
5747
|
-
children: "Pr\xF3ximo"
|
|
5748
|
-
}
|
|
5749
|
-
)
|
|
5750
|
-
] })
|
|
5751
|
-
]
|
|
5752
|
-
}
|
|
5753
|
-
)
|
|
5754
|
-
}
|
|
5755
|
-
);
|
|
5756
|
-
})(),
|
|
5757
|
-
document.body
|
|
6852
|
+
attachmentsOpen && /* @__PURE__ */ jsx(
|
|
6853
|
+
InvoiceAttachmentsModal,
|
|
6854
|
+
{
|
|
6855
|
+
invoiceId: invoice.id,
|
|
6856
|
+
onClose: () => setAttachmentsOpen(false),
|
|
6857
|
+
onChanged: () => {
|
|
6858
|
+
void loadData();
|
|
6859
|
+
}
|
|
6860
|
+
}
|
|
5758
6861
|
),
|
|
5759
6862
|
receiptBill && /* @__PURE__ */ jsx(
|
|
5760
6863
|
PaymentReceiptModal,
|
|
@@ -5924,7 +7027,7 @@ function PayablesView({ intent, onNavigate, previousView, back }) {
|
|
|
5924
7027
|
direction: "debit",
|
|
5925
7028
|
onNew: () => onNavigate("payables-new"),
|
|
5926
7029
|
onNewReceipt: () => onNavigate("payables-new-receipt"),
|
|
5927
|
-
onEdit: (id) => onNavigate(`payables
|
|
7030
|
+
onEdit: (id, status) => onNavigate(`payables-${invoiceOpensForEditing(status) ? "edit" : "detail"}:${id}`)
|
|
5928
7031
|
}
|
|
5929
7032
|
);
|
|
5930
7033
|
}
|
|
@@ -5958,7 +7061,7 @@ function ReceivablesView({ intent, onNavigate, previousView, back }) {
|
|
|
5958
7061
|
direction: "credit",
|
|
5959
7062
|
onNew: () => onNavigate("receivables-new"),
|
|
5960
7063
|
onNewReceipt: () => onNavigate("receivables-new-receipt"),
|
|
5961
|
-
onEdit: (id) => onNavigate(`receivables
|
|
7064
|
+
onEdit: (id, status) => onNavigate(`receivables-${invoiceOpensForEditing(status) ? "edit" : "detail"}:${id}`)
|
|
5962
7065
|
}
|
|
5963
7066
|
);
|
|
5964
7067
|
}
|
|
@@ -6476,7 +7579,7 @@ function CashRegistersView({ variant = "page" } = {}) {
|
|
|
6476
7579
|
const [tab, setTab] = useState("open");
|
|
6477
7580
|
const [reviewRows, setReviewRows] = useState(null);
|
|
6478
7581
|
const [reviewError, setReviewError] = useState(null);
|
|
6479
|
-
const loadReviews =
|
|
7582
|
+
const loadReviews = React25__default.useCallback(async () => {
|
|
6480
7583
|
try {
|
|
6481
7584
|
setReviewError(null);
|
|
6482
7585
|
setReviewRows(await provider.getCashSessionReviews?.() ?? []);
|
|
@@ -7076,7 +8179,7 @@ function StatementsView({ onNavigate }) {
|
|
|
7076
8179
|
"extrato"
|
|
7077
8180
|
);
|
|
7078
8181
|
};
|
|
7079
|
-
const headerActions =
|
|
8182
|
+
const headerActions = React25__default.useMemo(() => [{
|
|
7080
8183
|
id: "new-transfer",
|
|
7081
8184
|
label: t2("financial.statements.newTransfer"),
|
|
7082
8185
|
icon: ArrowRightLeft,
|
|
@@ -7255,7 +8358,7 @@ function firstOfMonth() {
|
|
|
7255
8358
|
function today2() {
|
|
7256
8359
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7257
8360
|
}
|
|
7258
|
-
function
|
|
8361
|
+
function money2(v) {
|
|
7259
8362
|
return v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
|
|
7260
8363
|
}
|
|
7261
8364
|
function Step({ n, label, done, state }) {
|
|
@@ -7272,15 +8375,15 @@ function CommissionsView() {
|
|
|
7272
8375
|
const fetchCommissions = useFinancialStore((s) => s.fetchCommissions);
|
|
7273
8376
|
const generateCommissions = useFinancialStore((s) => s.generateCommissions);
|
|
7274
8377
|
const provider = useFinancialProvider();
|
|
7275
|
-
const [from, setFrom] =
|
|
7276
|
-
const [to, setTo] =
|
|
7277
|
-
const [preset, setPreset] =
|
|
7278
|
-
const [running, setRunning] =
|
|
7279
|
-
const [lastRun, setLastRun] =
|
|
7280
|
-
const [generation, setGeneration] =
|
|
7281
|
-
const [busy, setBusy] =
|
|
7282
|
-
const [error, setError] =
|
|
7283
|
-
const loadGeneration =
|
|
8378
|
+
const [from, setFrom] = React25__default.useState(firstOfMonth());
|
|
8379
|
+
const [to, setTo] = React25__default.useState(today2());
|
|
8380
|
+
const [preset, setPreset] = React25__default.useState("this_month");
|
|
8381
|
+
const [running, setRunning] = React25__default.useState(false);
|
|
8382
|
+
const [lastRun, setLastRun] = React25__default.useState(null);
|
|
8383
|
+
const [generation, setGeneration] = React25__default.useState(null);
|
|
8384
|
+
const [busy, setBusy] = React25__default.useState(null);
|
|
8385
|
+
const [error, setError] = React25__default.useState(null);
|
|
8386
|
+
const loadGeneration = React25__default.useCallback(async () => {
|
|
7284
8387
|
try {
|
|
7285
8388
|
const gens = await provider.getCommissionGenerations?.() ?? [];
|
|
7286
8389
|
const doPeriodo = gens.filter((g) => g.periodStart === from && g.periodEnd === to);
|
|
@@ -7288,11 +8391,11 @@ function CommissionsView() {
|
|
|
7288
8391
|
} catch {
|
|
7289
8392
|
}
|
|
7290
8393
|
}, [provider, from, to]);
|
|
7291
|
-
|
|
8394
|
+
React25__default.useEffect(() => {
|
|
7292
8395
|
void fetchCommissions({ periodStart: from, periodEnd: to });
|
|
7293
8396
|
void loadGeneration();
|
|
7294
8397
|
}, [fetchCommissions, loadGeneration, from, to]);
|
|
7295
|
-
const byPerson =
|
|
8398
|
+
const byPerson = React25__default.useMemo(() => {
|
|
7296
8399
|
const map = /* @__PURE__ */ new Map();
|
|
7297
8400
|
for (const c of commissions2) {
|
|
7298
8401
|
const key = c.personId;
|
|
@@ -7309,7 +8412,7 @@ function CommissionsView() {
|
|
|
7309
8412
|
setError(null);
|
|
7310
8413
|
try {
|
|
7311
8414
|
const r = await generateCommissions({ periodStart: from, periodEnd: to });
|
|
7312
|
-
setLastRun(r ? `${r.entries} lan\xE7amento(s) \xB7 ${
|
|
8415
|
+
setLastRun(r ? `${r.entries} lan\xE7amento(s) \xB7 ${money2(r.total)}` : null);
|
|
7313
8416
|
await loadGeneration();
|
|
7314
8417
|
} catch (e) {
|
|
7315
8418
|
setError(e?.message ?? String(e));
|
|
@@ -7337,7 +8440,7 @@ function CommissionsView() {
|
|
|
7337
8440
|
setError(null);
|
|
7338
8441
|
try {
|
|
7339
8442
|
const r = await provider.payRunCommissions?.(generation.id);
|
|
7340
|
-
setLastRun(`${r?.payables ?? 0} conta(s) a pagar \xB7 ${
|
|
8443
|
+
setLastRun(`${r?.payables ?? 0} conta(s) a pagar \xB7 ${money2(r?.total ?? 0)}`);
|
|
7341
8444
|
await Promise.all([loadGeneration(), fetchCommissions({ periodStart: from, periodEnd: to })]);
|
|
7342
8445
|
} catch (e) {
|
|
7343
8446
|
setError(e?.message ?? String(e));
|
|
@@ -7345,7 +8448,7 @@ function CommissionsView() {
|
|
|
7345
8448
|
setBusy(null);
|
|
7346
8449
|
}
|
|
7347
8450
|
}
|
|
7348
|
-
const headerActions =
|
|
8451
|
+
const headerActions = React25__default.useMemo(() => [{
|
|
7349
8452
|
id: "commission-rules",
|
|
7350
8453
|
label: "Configurar regras de comiss\xE3o",
|
|
7351
8454
|
icon: Settings,
|
|
@@ -7383,7 +8486,7 @@ function CommissionsView() {
|
|
|
7383
8486
|
lastRun && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: lastRun }),
|
|
7384
8487
|
/* @__PURE__ */ jsxs("div", { className: "ml-auto text-right", children: [
|
|
7385
8488
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Total do per\xEDodo" }),
|
|
7386
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children:
|
|
8489
|
+
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money2(total) })
|
|
7387
8490
|
] })
|
|
7388
8491
|
] }),
|
|
7389
8492
|
generation && /* @__PURE__ */ jsxs("div", { className: "rounded-2xl border border-border bg-card px-4 py-3", children: [
|
|
@@ -7449,7 +8552,7 @@ function CommissionsView() {
|
|
|
7449
8552
|
p.count,
|
|
7450
8553
|
" lan\xE7amento(s)"
|
|
7451
8554
|
] }),
|
|
7452
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children:
|
|
8555
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children: money2(p.total) })
|
|
7453
8556
|
] })
|
|
7454
8557
|
] }, id)) })
|
|
7455
8558
|
] }),
|
|
@@ -7468,9 +8571,9 @@ function CommissionsView() {
|
|
|
7468
8571
|
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: c.itemName ?? "\u2014" }),
|
|
7469
8572
|
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: c.invoiceNumber ?? "\u2014" }),
|
|
7470
8573
|
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: c.ruleName ?? "\u2014" }),
|
|
7471
|
-
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums text-muted-foreground", children:
|
|
8574
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums text-muted-foreground", children: money2(c.baseAmount) }),
|
|
7472
8575
|
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums text-muted-foreground", children: c.rate }),
|
|
7473
|
-
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right font-semibold tabular-nums", children:
|
|
8576
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right font-semibold tabular-nums", children: money2(c.amount) })
|
|
7474
8577
|
] }, c.id)) })
|
|
7475
8578
|
] }) })
|
|
7476
8579
|
] })
|
|
@@ -7787,19 +8890,19 @@ var MESES = [
|
|
|
7787
8890
|
"Novembro",
|
|
7788
8891
|
"Dezembro"
|
|
7789
8892
|
];
|
|
7790
|
-
function
|
|
8893
|
+
function money3(v) {
|
|
7791
8894
|
return v.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
|
|
7792
8895
|
}
|
|
7793
8896
|
function GoalsView() {
|
|
7794
8897
|
const { labels } = useFinancialConfig();
|
|
7795
8898
|
const provider = useFinancialProvider();
|
|
7796
|
-
const [units, setUnits] =
|
|
7797
|
-
const [unitId, setUnitId] =
|
|
7798
|
-
const [year, setYear] =
|
|
7799
|
-
const [rows, setRows] =
|
|
7800
|
-
const [editing, setEditing] =
|
|
7801
|
-
const [error, setError] =
|
|
7802
|
-
|
|
8899
|
+
const [units, setUnits] = React25__default.useState([]);
|
|
8900
|
+
const [unitId, setUnitId] = React25__default.useState("");
|
|
8901
|
+
const [year, setYear] = React25__default.useState((/* @__PURE__ */ new Date()).getFullYear());
|
|
8902
|
+
const [rows, setRows] = React25__default.useState(null);
|
|
8903
|
+
const [editing, setEditing] = React25__default.useState(null);
|
|
8904
|
+
const [error, setError] = React25__default.useState(null);
|
|
8905
|
+
React25__default.useEffect(() => {
|
|
7803
8906
|
void (async () => {
|
|
7804
8907
|
const cat = await provider.getCommissionCatalogs?.();
|
|
7805
8908
|
const us = cat?.units ?? [];
|
|
@@ -7807,7 +8910,7 @@ function GoalsView() {
|
|
|
7807
8910
|
setUnitId((cur) => cur || us[0]?.id || "");
|
|
7808
8911
|
})();
|
|
7809
8912
|
}, [provider]);
|
|
7810
|
-
const load =
|
|
8913
|
+
const load = React25__default.useCallback(async () => {
|
|
7811
8914
|
if (!unitId) return;
|
|
7812
8915
|
setRows(null);
|
|
7813
8916
|
try {
|
|
@@ -7818,7 +8921,7 @@ function GoalsView() {
|
|
|
7818
8921
|
setRows([]);
|
|
7819
8922
|
}
|
|
7820
8923
|
}, [provider, unitId, year]);
|
|
7821
|
-
|
|
8924
|
+
React25__default.useEffect(() => {
|
|
7822
8925
|
void load();
|
|
7823
8926
|
}, [load]);
|
|
7824
8927
|
const totalRealizado = (rows ?? []).reduce((s, r) => s + r.achievedAmount, 0);
|
|
@@ -7867,11 +8970,11 @@ function GoalsView() {
|
|
|
7867
8970
|
] }),
|
|
7868
8971
|
/* @__PURE__ */ jsxs("div", { className: "ml-auto text-right", children: [
|
|
7869
8972
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Realizado no ano" }),
|
|
7870
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children:
|
|
8973
|
+
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money3(totalRealizado) })
|
|
7871
8974
|
] }),
|
|
7872
8975
|
totalMeta > 0 && /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
|
|
7873
8976
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Meta do ano" }),
|
|
7874
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children:
|
|
8977
|
+
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money3(totalMeta) })
|
|
7875
8978
|
] })
|
|
7876
8979
|
] }),
|
|
7877
8980
|
semMeta > 0 && /* @__PURE__ */ jsxs("p", { className: "rounded-card border border-warning/30 bg-warning/5 px-4 py-2 text-xs", children: [
|
|
@@ -7900,7 +9003,7 @@ function GoalsView() {
|
|
|
7900
9003
|
/* @__PURE__ */ jsx("td", {})
|
|
7901
9004
|
] }, i)) : rows.map((r) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 hover:bg-muted/40", children: [
|
|
7902
9005
|
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-medium", children: MESES[r.month - 1] }),
|
|
7903
|
-
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums", children:
|
|
9006
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-right tabular-nums", children: money3(r.achievedAmount) }),
|
|
7904
9007
|
/* @__PURE__ */ jsx(Degrau, { valor: r.targetAmount, percentual: r.targetPercent }),
|
|
7905
9008
|
/* @__PURE__ */ jsx(Degrau, { valor: r.superAmount, percentual: r.superPercent }),
|
|
7906
9009
|
/* @__PURE__ */ jsx(Degrau, { valor: r.hyperAmount, percentual: r.hyperPercent }),
|
|
@@ -7936,7 +9039,7 @@ function Degrau({ valor, percentual }) {
|
|
|
7936
9039
|
}
|
|
7937
9040
|
const bateu = percentual >= 100;
|
|
7938
9041
|
return /* @__PURE__ */ jsxs("td", { className: "px-4 py-2 text-right", children: [
|
|
7939
|
-
/* @__PURE__ */ jsx("div", { className: "tabular-nums", children:
|
|
9042
|
+
/* @__PURE__ */ jsx("div", { className: "tabular-nums", children: money3(valor) }),
|
|
7940
9043
|
/* @__PURE__ */ jsxs("div", { className: `text-[10px] tabular-nums ${bateu ? "font-semibold text-success" : "text-muted-foreground"}`, children: [
|
|
7941
9044
|
percentual.toFixed(2),
|
|
7942
9045
|
"%"
|
|
@@ -7949,11 +9052,11 @@ function GoalDialog({
|
|
|
7949
9052
|
onSaved
|
|
7950
9053
|
}) {
|
|
7951
9054
|
const provider = useFinancialProvider();
|
|
7952
|
-
const [target, setTarget] =
|
|
7953
|
-
const [sup, setSup] =
|
|
7954
|
-
const [hyper, setHyper] =
|
|
7955
|
-
const [saving, setSaving] =
|
|
7956
|
-
const [error, setError] =
|
|
9055
|
+
const [target, setTarget] = React25__default.useState(String(goal.targetAmount || ""));
|
|
9056
|
+
const [sup, setSup] = React25__default.useState(String(goal.superAmount || ""));
|
|
9057
|
+
const [hyper, setHyper] = React25__default.useState(String(goal.hyperAmount || ""));
|
|
9058
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
9059
|
+
const [error, setError] = React25__default.useState(null);
|
|
7957
9060
|
const t2 = Number(target) || 0;
|
|
7958
9061
|
const s = Number(sup) || 0;
|
|
7959
9062
|
const h = Number(hyper) || 0;
|
|
@@ -7991,7 +9094,7 @@ function GoalDialog({
|
|
|
7991
9094
|
] }),
|
|
7992
9095
|
/* @__PURE__ */ jsxs(ModalDescription, { className: "text-xs", children: [
|
|
7993
9096
|
"Realizado at\xE9 agora: ",
|
|
7994
|
-
/* @__PURE__ */ jsx("strong", { children:
|
|
9097
|
+
/* @__PURE__ */ jsx("strong", { children: money3(goal.achievedAmount) })
|
|
7995
9098
|
] })
|
|
7996
9099
|
] }),
|
|
7997
9100
|
/* @__PURE__ */ jsx(ModalBody, { className: "fz-container px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
@@ -8044,21 +9147,21 @@ function GiftCardsView() {
|
|
|
8044
9147
|
const provider = useFinancialProvider();
|
|
8045
9148
|
const { currency } = useFinancialConfig();
|
|
8046
9149
|
const can = usePermissionOptional();
|
|
8047
|
-
const [enabled, setEnabled] =
|
|
8048
|
-
const [cards, setCards] =
|
|
8049
|
-
const [error, setError] =
|
|
8050
|
-
const [issuing, setIssuing] =
|
|
8051
|
-
const [issued, setIssued] =
|
|
8052
|
-
const [activating, setActivating] =
|
|
8053
|
-
const [search, setSearch] =
|
|
8054
|
-
const [state, setState] =
|
|
8055
|
-
const [minValue, setMinValue] =
|
|
8056
|
-
const [maxValue, setMaxValue] =
|
|
8057
|
-
const [from, setFrom] =
|
|
8058
|
-
const [to, setTo] =
|
|
8059
|
-
const
|
|
8060
|
-
const seq =
|
|
8061
|
-
const load =
|
|
9150
|
+
const [enabled, setEnabled] = React25__default.useState(null);
|
|
9151
|
+
const [cards, setCards] = React25__default.useState(null);
|
|
9152
|
+
const [error, setError] = React25__default.useState(null);
|
|
9153
|
+
const [issuing, setIssuing] = React25__default.useState(false);
|
|
9154
|
+
const [issued, setIssued] = React25__default.useState(null);
|
|
9155
|
+
const [activating, setActivating] = React25__default.useState(null);
|
|
9156
|
+
const [search, setSearch] = React25__default.useState("");
|
|
9157
|
+
const [state, setState] = React25__default.useState("");
|
|
9158
|
+
const [minValue, setMinValue] = React25__default.useState("");
|
|
9159
|
+
const [maxValue, setMaxValue] = React25__default.useState("");
|
|
9160
|
+
const [from, setFrom] = React25__default.useState("");
|
|
9161
|
+
const [to, setTo] = React25__default.useState("");
|
|
9162
|
+
const money4 = React25__default.useCallback((v) => formatCurrency(v, currency), [currency]);
|
|
9163
|
+
const seq = React25__default.useRef(0);
|
|
9164
|
+
const load = React25__default.useCallback(async () => {
|
|
8062
9165
|
const mine = ++seq.current;
|
|
8063
9166
|
setCards(null);
|
|
8064
9167
|
setError(null);
|
|
@@ -8079,7 +9182,7 @@ function GiftCardsView() {
|
|
|
8079
9182
|
setCards([]);
|
|
8080
9183
|
}
|
|
8081
9184
|
}, [provider, search, state, minValue, maxValue, from, to]);
|
|
8082
|
-
|
|
9185
|
+
React25__default.useEffect(() => {
|
|
8083
9186
|
void (async () => {
|
|
8084
9187
|
try {
|
|
8085
9188
|
setEnabled(await provider.giftCardsEnabled?.() === true);
|
|
@@ -8088,10 +9191,10 @@ function GiftCardsView() {
|
|
|
8088
9191
|
}
|
|
8089
9192
|
})();
|
|
8090
9193
|
}, [provider]);
|
|
8091
|
-
|
|
9194
|
+
React25__default.useEffect(() => {
|
|
8092
9195
|
if (enabled) void load();
|
|
8093
9196
|
}, [enabled, load]);
|
|
8094
|
-
const columns =
|
|
9197
|
+
const columns = React25__default.useMemo(() => [
|
|
8095
9198
|
{
|
|
8096
9199
|
accessorKey: "code",
|
|
8097
9200
|
header: "C\xF3digo",
|
|
@@ -8100,12 +9203,12 @@ function GiftCardsView() {
|
|
|
8100
9203
|
{
|
|
8101
9204
|
accessorKey: "faceValue",
|
|
8102
9205
|
header: "Valor",
|
|
8103
|
-
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children:
|
|
9206
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: money4(row.original.faceValue) })
|
|
8104
9207
|
},
|
|
8105
9208
|
{
|
|
8106
9209
|
accessorKey: "balance",
|
|
8107
9210
|
header: "Saldo",
|
|
8108
|
-
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: row.original.balance === null ? /* @__PURE__ */ jsx(Dash, {}) :
|
|
9211
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: row.original.balance === null ? /* @__PURE__ */ jsx(Dash, {}) : money4(row.original.balance) })
|
|
8109
9212
|
},
|
|
8110
9213
|
{
|
|
8111
9214
|
accessorKey: "state",
|
|
@@ -8145,7 +9248,7 @@ function GiftCardsView() {
|
|
|
8145
9248
|
/* @__PURE__ */ jsx(TooltipContent, { className: "text-xs", children: "Ativar na venda \u2014 o saldo passa a valer o valor de face" })
|
|
8146
9249
|
] }) }) : null
|
|
8147
9250
|
}
|
|
8148
|
-
], [activating, load,
|
|
9251
|
+
], [activating, load, money4, provider]);
|
|
8149
9252
|
const outstanding = (cards ?? []).filter((c) => c.state === "active").reduce((s, c) => s + (c.balance ?? 0), 0);
|
|
8150
9253
|
const faceIssued = (cards ?? []).reduce((s, c) => s + c.faceValue, 0);
|
|
8151
9254
|
if (enabled === false) {
|
|
@@ -8180,11 +9283,11 @@ function GiftCardsView() {
|
|
|
8180
9283
|
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-6 rounded-card border bg-card px-4 py-3", children: [
|
|
8181
9284
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
8182
9285
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Valor de face emitido" }),
|
|
8183
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children:
|
|
9286
|
+
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money4(faceIssued) })
|
|
8184
9287
|
] }),
|
|
8185
9288
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
8186
9289
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Passivo em aberto" }),
|
|
8187
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children:
|
|
9290
|
+
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold tabular-nums", children: money4(outstanding) }),
|
|
8188
9291
|
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "s\xF3 os cart\xF5es j\xE1 vendidos" })
|
|
8189
9292
|
] })
|
|
8190
9293
|
] }),
|
|
@@ -8196,7 +9299,7 @@ function GiftCardsView() {
|
|
|
8196
9299
|
] }),
|
|
8197
9300
|
" ",
|
|
8198
9301
|
"\u2014 ",
|
|
8199
|
-
|
|
9302
|
+
money4(issued.total),
|
|
8200
9303
|
" em valor de face. Eles nascem sem saldo: ative cada um na venda."
|
|
8201
9304
|
] }),
|
|
8202
9305
|
error && /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-card border border-destructive/30 bg-destructive/5 px-4 py-3 text-xs text-destructive", children: error }),
|
|
@@ -8255,7 +9358,7 @@ function GiftCardsView() {
|
|
|
8255
9358
|
issuing && /* @__PURE__ */ jsx(
|
|
8256
9359
|
IssueDialog,
|
|
8257
9360
|
{
|
|
8258
|
-
money:
|
|
9361
|
+
money: money4,
|
|
8259
9362
|
onClose: () => setIssuing(false),
|
|
8260
9363
|
onIssued: async (count, total) => {
|
|
8261
9364
|
setIssuing(false);
|
|
@@ -8267,17 +9370,17 @@ function GiftCardsView() {
|
|
|
8267
9370
|
] }) });
|
|
8268
9371
|
}
|
|
8269
9372
|
function IssueDialog({
|
|
8270
|
-
money:
|
|
9373
|
+
money: money4,
|
|
8271
9374
|
onClose,
|
|
8272
9375
|
onIssued
|
|
8273
9376
|
}) {
|
|
8274
9377
|
const provider = useFinancialProvider();
|
|
8275
|
-
const [quantity, setQuantity] =
|
|
8276
|
-
const [amount, setAmount] =
|
|
8277
|
-
const [expiresAt, setExpiresAt] =
|
|
8278
|
-
const [note, setNote] =
|
|
8279
|
-
const [saving, setSaving] =
|
|
8280
|
-
const [error, setError] =
|
|
9378
|
+
const [quantity, setQuantity] = React25__default.useState("10");
|
|
9379
|
+
const [amount, setAmount] = React25__default.useState("100");
|
|
9380
|
+
const [expiresAt, setExpiresAt] = React25__default.useState("");
|
|
9381
|
+
const [note, setNote] = React25__default.useState("");
|
|
9382
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
9383
|
+
const [error, setError] = React25__default.useState(null);
|
|
8281
9384
|
const q = Math.trunc(Number(quantity)) || 0;
|
|
8282
9385
|
const a = Number(amount) || 0;
|
|
8283
9386
|
const total = q * a;
|
|
@@ -8360,7 +9463,7 @@ function IssueDialog({
|
|
|
8360
9463
|
] }),
|
|
8361
9464
|
/* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-muted/40 px-4 py-2 text-sm", children: [
|
|
8362
9465
|
"Total do lote: ",
|
|
8363
|
-
/* @__PURE__ */ jsx("strong", { className: "tabular-nums", children:
|
|
9466
|
+
/* @__PURE__ */ jsx("strong", { className: "tabular-nums", children: money4(total) })
|
|
8364
9467
|
] }),
|
|
8365
9468
|
invalid && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: invalid }),
|
|
8366
9469
|
error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
|
|
@@ -8642,7 +9745,7 @@ function stripFence(text2) {
|
|
|
8642
9745
|
const end = trimmed.lastIndexOf("}");
|
|
8643
9746
|
return start >= 0 && end > start ? trimmed.slice(start, end + 1) : trimmed;
|
|
8644
9747
|
}
|
|
8645
|
-
var
|
|
9748
|
+
var num2 = (v, fallback = 0) => {
|
|
8646
9749
|
const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
|
|
8647
9750
|
return Number.isFinite(n) ? n : fallback;
|
|
8648
9751
|
};
|
|
@@ -8661,8 +9764,8 @@ function parseReceiptReply(content) {
|
|
|
8661
9764
|
const item = row ?? {};
|
|
8662
9765
|
const description = str(item.description);
|
|
8663
9766
|
if (!description) return null;
|
|
8664
|
-
const quantity =
|
|
8665
|
-
const unitPrice = item.unitPrice != null ?
|
|
9767
|
+
const quantity = num2(item.quantity, 1) || 1;
|
|
9768
|
+
const unitPrice = item.unitPrice != null ? num2(item.unitPrice) : num2(item.total) / quantity;
|
|
8666
9769
|
return { description, quantity, unitPrice: Math.round(unitPrice * 100) / 100 };
|
|
8667
9770
|
}).filter((i) => i !== null) : [];
|
|
8668
9771
|
const warnings = Array.isArray(raw.warnings) ? raw.warnings.map((w) => str(w)).filter((w) => !!w) : [];
|
|
@@ -8805,16 +9908,10 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
8805
9908
|
}, [planoDoTipo]);
|
|
8806
9909
|
const valorAQuitar = intent === "link" && target ? target.openAmount : round23(line.amount);
|
|
8807
9910
|
const quita = intent === "bill" || intent === "link" && target?.mode === "settle";
|
|
8808
|
-
const agentConnection = useMemo(() => resolveFayzAgentConnection(), []);
|
|
8809
9911
|
const leitor = useMemo(() => {
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
chat: (input) => client.chat(input),
|
|
8814
|
-
toInlineFile: toInlineFile,
|
|
8815
|
-
shrinkForVision
|
|
8816
|
-
});
|
|
8817
|
-
}, [agentConnection]);
|
|
9912
|
+
const ask = createOneShotAsk();
|
|
9913
|
+
return ask ? createAgentReceiptReader({ chat: ask, toInlineFile: toInlineFile, shrinkForVision }) : null;
|
|
9914
|
+
}, []);
|
|
8818
9915
|
async function lerComprovante(file) {
|
|
8819
9916
|
if (!leitor) return;
|
|
8820
9917
|
if (file.size > 10 * 1024 * 1024) {
|
|
@@ -8979,7 +10076,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
8979
10076
|
candidatesLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8980
10077
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full rounded-md" }),
|
|
8981
10078
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full rounded-md" })
|
|
8982
|
-
] }) : candidates.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: t2("financial.reconciliation.noCandidates") }) : candidates.map(({ candidate: c, mode, openAmount }) => {
|
|
10079
|
+
] }) : candidates.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: t2("financial.reconciliation.noCandidates") }) : candidates.map(({ candidate: c, mode, openAmount: openAmount2 }) => {
|
|
8983
10080
|
const escolhido = targetId === c.movement.id;
|
|
8984
10081
|
return /* @__PURE__ */ jsxs(
|
|
8985
10082
|
"button",
|
|
@@ -8994,7 +10091,7 @@ function ReconcileFlow({ line, initialIntent, onClose, onDone }) {
|
|
|
8994
10091
|
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
8995
10092
|
shortDate2(c.movement.dueDate),
|
|
8996
10093
|
/* @__PURE__ */ jsx("span", { className: "ml-1.5 inline-flex items-center rounded bg-magic/10 px-1 py-px font-medium text-magic-soft-foreground", children: t2("financial.reconciliation.matchScore", { score: Math.round(c.score * 100) }) }),
|
|
8997
|
-
/* @__PURE__ */ jsx("span", { className: `ml-1.5 inline-flex items-center rounded px-1 py-px font-medium ${mode === "settle" ? "bg-warning/10 text-warning" : "bg-success/10 text-success"}`, children: mode === "settle" ? t2("financial.reconciliation.candidateOpen", { amount: fmt(
|
|
10094
|
+
/* @__PURE__ */ jsx("span", { className: `ml-1.5 inline-flex items-center rounded px-1 py-px font-medium ${mode === "settle" ? "bg-warning/10 text-warning" : "bg-success/10 text-success"}`, children: mode === "settle" ? t2("financial.reconciliation.candidateOpen", { amount: fmt(openAmount2) }) : t2("financial.reconciliation.candidatePaid") })
|
|
8998
10095
|
] })
|
|
8999
10096
|
] }),
|
|
9000
10097
|
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold", children: fmt(c.movement.amount) })
|
|
@@ -9558,11 +10655,11 @@ function ReconciliationView() {
|
|
|
9558
10655
|
setBusy(false);
|
|
9559
10656
|
}
|
|
9560
10657
|
}
|
|
9561
|
-
const RefreshIcon =
|
|
10658
|
+
const RefreshIcon = React25__default.useMemo(
|
|
9562
10659
|
() => ({ className }) => /* @__PURE__ */ jsx(RefreshCw, { className: `${className ?? ""} ${loading ? "animate-spin" : ""}` }),
|
|
9563
10660
|
[loading]
|
|
9564
10661
|
);
|
|
9565
|
-
const headerActions =
|
|
10662
|
+
const headerActions = React25__default.useMemo(() => [{
|
|
9566
10663
|
id: "refresh",
|
|
9567
10664
|
label: t2("financial.reconciliation.refresh"),
|
|
9568
10665
|
icon: RefreshIcon,
|
|
@@ -9987,13 +11084,13 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
9987
11084
|
const provider = useFinancialProvider();
|
|
9988
11085
|
const { currency } = useFinancialConfig();
|
|
9989
11086
|
const can = usePermissionOptional();
|
|
9990
|
-
const [rows, setRows] =
|
|
9991
|
-
const [search, setSearch] =
|
|
9992
|
-
const [status, setStatus] =
|
|
9993
|
-
const [changing, setChanging] =
|
|
11087
|
+
const [rows, setRows] = React25__default.useState(null);
|
|
11088
|
+
const [search, setSearch] = React25__default.useState("");
|
|
11089
|
+
const [status, setStatus] = React25__default.useState("all");
|
|
11090
|
+
const [changing, setChanging] = React25__default.useState(null);
|
|
9994
11091
|
const isPayable = direction === "payable";
|
|
9995
11092
|
const prefix = isPayable ? "payables" : "receivables";
|
|
9996
|
-
const load =
|
|
11093
|
+
const load = React25__default.useCallback(async () => {
|
|
9997
11094
|
setRows(null);
|
|
9998
11095
|
try {
|
|
9999
11096
|
setRows(await provider.getRecurringBills?.(direction) ?? []);
|
|
@@ -10002,10 +11099,10 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
10002
11099
|
toast.error(error?.message ?? "N\xE3o foi poss\xEDvel carregar as recorr\xEAncias.");
|
|
10003
11100
|
}
|
|
10004
11101
|
}, [provider, direction]);
|
|
10005
|
-
|
|
11102
|
+
React25__default.useEffect(() => {
|
|
10006
11103
|
void load();
|
|
10007
11104
|
}, [load]);
|
|
10008
|
-
const filtered =
|
|
11105
|
+
const filtered = React25__default.useMemo(() => {
|
|
10009
11106
|
const term = search.trim().toLowerCase();
|
|
10010
11107
|
const visiveis = (rows ?? []).filter((row) => {
|
|
10011
11108
|
if (status === "active" && !row.isActive) return false;
|
|
@@ -10015,7 +11112,7 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
10015
11112
|
});
|
|
10016
11113
|
return [...visiveis].sort((a, b) => Number(b.isActive) - Number(a.isActive) || (a.dueDay ?? 0) - (b.dueDay ?? 0) || a.description.localeCompare(b.description));
|
|
10017
11114
|
}, [rows, search, status]);
|
|
10018
|
-
const porMes =
|
|
11115
|
+
const porMes = React25__default.useMemo(() => {
|
|
10019
11116
|
const fator = { weekly: 52 / 12, monthly: 1, quarterly: 1 / 3, yearly: 1 / 12 };
|
|
10020
11117
|
const ativas = filtered.filter((row) => row.isActive);
|
|
10021
11118
|
return {
|
|
@@ -10036,7 +11133,7 @@ function RecurringBillsList({ direction, onNavigate }) {
|
|
|
10036
11133
|
setChanging(null);
|
|
10037
11134
|
}
|
|
10038
11135
|
}
|
|
10039
|
-
const columns =
|
|
11136
|
+
const columns = React25__default.useMemo(() => [
|
|
10040
11137
|
{
|
|
10041
11138
|
accessorKey: "description",
|
|
10042
11139
|
header: "Descri\xE7\xE3o",
|
|
@@ -10133,40 +11230,40 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
10133
11230
|
const provider = useFinancialProvider();
|
|
10134
11231
|
const { locations, currency, itemTypes: configuredItemTypes, entityLookups } = useFinancialConfig();
|
|
10135
11232
|
const isPayable = direction === "payable";
|
|
10136
|
-
const [value, setValue] =
|
|
10137
|
-
const [loaded, setLoaded] =
|
|
10138
|
-
const [contact, setContact] =
|
|
10139
|
-
const [counterpartyAccounting, setCounterpartyAccounting] =
|
|
11233
|
+
const [value, setValue] = React25__default.useState(null);
|
|
11234
|
+
const [loaded, setLoaded] = React25__default.useState(!valueId);
|
|
11235
|
+
const [contact, setContact] = React25__default.useState(null);
|
|
11236
|
+
const [counterpartyAccounting, setCounterpartyAccounting] = React25__default.useState({
|
|
10140
11237
|
allowedAccountIds: [],
|
|
10141
11238
|
limitAccounts: false,
|
|
10142
11239
|
addItemsAutomatically: false
|
|
10143
11240
|
});
|
|
10144
|
-
const accountingPolicyRequest =
|
|
10145
|
-
const counterpartyLookup =
|
|
11241
|
+
const accountingPolicyRequest = React25__default.useRef(0);
|
|
11242
|
+
const counterpartyLookup = React25__default.useMemo(
|
|
10146
11243
|
() => createFinancialCounterpartyLookup(isPayable ? "debit" : "credit"),
|
|
10147
11244
|
[isPayable]
|
|
10148
11245
|
);
|
|
10149
11246
|
const contactCreate = useLookupCreate(counterpartyLookup, (created) => {
|
|
10150
11247
|
setContact({ id: created.id, name: created.label });
|
|
10151
11248
|
});
|
|
10152
|
-
const initialItem =
|
|
10153
|
-
const [items, setItems] =
|
|
10154
|
-
const [expandedItemId, setExpandedItemId] =
|
|
10155
|
-
const [addingItem, setAddingItem] =
|
|
10156
|
-
const [frequency, setFrequency] =
|
|
10157
|
-
const [dueDay, setDueDay] =
|
|
10158
|
-
const [startsOn, setStartsOn] =
|
|
10159
|
-
const [endsOn, setEndsOn] =
|
|
10160
|
-
const [unitId, setUnitId] =
|
|
10161
|
-
const [availableLocations, setAvailableLocations] =
|
|
10162
|
-
const [notes, setNotes] =
|
|
10163
|
-
const [isActive, setIsActive] =
|
|
10164
|
-
const [accounts, setAccounts] =
|
|
10165
|
-
const [costCenters, setCostCenters] =
|
|
10166
|
-
const [saving, setSaving] =
|
|
10167
|
-
const [ending, setEnding] =
|
|
10168
|
-
const [error, setError] =
|
|
10169
|
-
|
|
11249
|
+
const initialItem = React25__default.useRef(newRecurringItem());
|
|
11250
|
+
const [items, setItems] = React25__default.useState([initialItem.current]);
|
|
11251
|
+
const [expandedItemId, setExpandedItemId] = React25__default.useState(initialItem.current._id);
|
|
11252
|
+
const [addingItem, setAddingItem] = React25__default.useState(false);
|
|
11253
|
+
const [frequency, setFrequency] = React25__default.useState("monthly");
|
|
11254
|
+
const [dueDay, setDueDay] = React25__default.useState("1");
|
|
11255
|
+
const [startsOn, setStartsOn] = React25__default.useState(today3());
|
|
11256
|
+
const [endsOn, setEndsOn] = React25__default.useState("");
|
|
11257
|
+
const [unitId, setUnitId] = React25__default.useState(locations[0]?.id ?? "");
|
|
11258
|
+
const [availableLocations, setAvailableLocations] = React25__default.useState(locations);
|
|
11259
|
+
const [notes, setNotes] = React25__default.useState("");
|
|
11260
|
+
const [isActive, setIsActive] = React25__default.useState(true);
|
|
11261
|
+
const [accounts, setAccounts] = React25__default.useState([]);
|
|
11262
|
+
const [costCenters, setCostCenters] = React25__default.useState([]);
|
|
11263
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
11264
|
+
const [ending, setEnding] = React25__default.useState(false);
|
|
11265
|
+
const [error, setError] = React25__default.useState(null);
|
|
11266
|
+
React25__default.useEffect(() => {
|
|
10170
11267
|
if (locations.length > 0) {
|
|
10171
11268
|
setAvailableLocations(locations);
|
|
10172
11269
|
return;
|
|
@@ -10181,7 +11278,7 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
10181
11278
|
cancelled = true;
|
|
10182
11279
|
};
|
|
10183
11280
|
}, [locations]);
|
|
10184
|
-
|
|
11281
|
+
React25__default.useEffect(() => {
|
|
10185
11282
|
let cancelled = false;
|
|
10186
11283
|
void Promise.all([
|
|
10187
11284
|
provider.getChartOfAccounts(),
|
|
@@ -10228,21 +11325,21 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
10228
11325
|
cancelled = true;
|
|
10229
11326
|
};
|
|
10230
11327
|
}, [provider, direction, valueId]);
|
|
10231
|
-
const offeredItemTypes =
|
|
11328
|
+
const offeredItemTypes = React25__default.useMemo(() => availableInvoiceItemTypes(
|
|
10232
11329
|
configuredItemTypes,
|
|
10233
11330
|
Object.keys(entityLookups),
|
|
10234
11331
|
configuredItemTypes.find((item) => item.value === "other")?.label ?? "Outro",
|
|
10235
11332
|
direction
|
|
10236
11333
|
), [configuredItemTypes, entityLookups, direction]);
|
|
10237
11334
|
const itemTypes = useStockedItemTypes(offeredItemTypes, entityLookups);
|
|
10238
|
-
const selectableAccounts =
|
|
11335
|
+
const selectableAccounts = React25__default.useMemo(() => {
|
|
10239
11336
|
const postable = postableAccounts(accounts);
|
|
10240
11337
|
if (!counterpartyAccounting.limitAccounts) return postable;
|
|
10241
11338
|
const allowed = new Set(counterpartyAccounting.allowedAccountIds);
|
|
10242
11339
|
return postable.filter((account) => allowed.has(account.id));
|
|
10243
11340
|
}, [accounts, counterpartyAccounting]);
|
|
10244
11341
|
const hasItemLookups = itemTypes.some((item) => entityLookups[item.value]);
|
|
10245
|
-
const amount =
|
|
11342
|
+
const amount = React25__default.useMemo(() => items.reduce(
|
|
10246
11343
|
(sum, item) => sum + item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
|
|
10247
11344
|
0
|
|
10248
11345
|
), [items]);
|
|
@@ -10250,8 +11347,8 @@ function RecurringBillFormView({ direction, valueId, onBack }) {
|
|
|
10250
11347
|
const numericDueDay = Math.trunc(Number(dueDay));
|
|
10251
11348
|
const invalid = items.length === 0 ? "Adicione pelo menos um item." : items.some((item) => !item.description.trim()) ? "Informe a descri\xE7\xE3o de todos os itens." : !contact?.id ? `Selecione ${isPayable ? "quem recebe" : "quem paga"}.` : !(amount > 0) ? "Informe um valor maior que zero." : numericDueDay < 1 || numericDueDay > 31 ? "O dia do vencimento vai de 1 a 31." : !startsOn ? "Informe o in\xEDcio da recorr\xEAncia." : endsOn && endsOn < startsOn ? "O t\xE9rmino n\xE3o pode vir antes do in\xEDcio." : null;
|
|
10252
11349
|
const fields = { items, contactId: contact?.id ?? "", frequency, dueDay, startsOn, endsOn, unitId, notes, isActive };
|
|
10253
|
-
const snapshot =
|
|
10254
|
-
|
|
11350
|
+
const snapshot = React25__default.useRef(null);
|
|
11351
|
+
React25__default.useEffect(() => {
|
|
10255
11352
|
if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
|
|
10256
11353
|
}, [loaded]);
|
|
10257
11354
|
const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
|
|
@@ -10641,16 +11738,8 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
10641
11738
|
children: [
|
|
10642
11739
|
{ id: "payables-new", kind: "action", label: t2("financial.nav.new"), active: activeView.startsWith("payables-new"), onClick: () => navigate("payables-new") },
|
|
10643
11740
|
{ id: "payables-list", label: t2("financial.nav.list"), active: activeView === "payables-list", onClick: () => navigate("payables-list") },
|
|
10644
|
-
{ id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") }
|
|
10645
|
-
//
|
|
10646
|
-
//
|
|
10647
|
-
// «Da minha caixinha» SAIU do menu. No V1 ela é `{caixaAberto && …}`:
|
|
10648
|
-
// aparece enquanto a sessão daquela pessoa está aberta e some quando
|
|
10649
|
-
// ela fecha. Como aba fixa ela prometia uma tela que quase ninguém vê
|
|
10650
|
-
// — e pagar do dinheiro da gaveta é uma FORMA de pagamento do mesmo
|
|
10651
|
-
// lançamento, não um cadastro à parte. A rota segue de pé.
|
|
10652
|
-
{ id: "payables-digitize", label: "Digitalizar Despesa", active: activeView === "payables-digitize", onClick: () => navigate("payables-digitize") },
|
|
10653
|
-
{ id: "payables-payroll", label: "Importar Folha", active: activeView === "payables-payroll", onClick: () => navigate("payables-payroll") }
|
|
11741
|
+
{ id: "payables-recurring", label: t2("financial.nav.recurring"), active: activeView.startsWith("payables-recurring"), onClick: () => navigate("payables-recurring") }
|
|
11742
|
+
// Digitize expense and import payroll live in the list's create menu, not here.
|
|
10654
11743
|
]
|
|
10655
11744
|
});
|
|
10656
11745
|
}
|
|
@@ -10900,8 +11989,9 @@ function FinancialPage({ config: appConfig, provider, store: store6, registries
|
|
|
10900
11989
|
// tamanho do buraco, medido no banco do V1.
|
|
10901
11990
|
case "payables-petty-cash":
|
|
10902
11991
|
return /* @__PURE__ */ jsx(PettyCashView, {});
|
|
11992
|
+
// Digitizing is the list's receipt capture now — the old link lands on the list.
|
|
10903
11993
|
case "payables-digitize":
|
|
10904
|
-
return /* @__PURE__ */ jsx(
|
|
11994
|
+
return /* @__PURE__ */ jsx(PayablesView, { intent: { view: "payables", mode: "list" }, onNavigate: navigate, previousView, back });
|
|
10905
11995
|
case "payables-payroll":
|
|
10906
11996
|
return /* @__PURE__ */ jsx(ImportPayrollView, {});
|
|
10907
11997
|
case "cash-reviewers":
|
|
@@ -11108,14 +12198,14 @@ function BalanceHero() {
|
|
|
11108
12198
|
const summary = useFinancialStore((s) => s.summary);
|
|
11109
12199
|
const navigate = useDashboardNavigate();
|
|
11110
12200
|
useEnsureSummary();
|
|
11111
|
-
const [hidden, setHidden] =
|
|
11112
|
-
const
|
|
12201
|
+
const [hidden, setHidden] = React25__default.useState(false);
|
|
12202
|
+
const money4 = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
|
|
11113
12203
|
const inflow = summary?.monthlyInflow ?? 0;
|
|
11114
12204
|
const outflow = summary?.monthlyOutflow ?? 0;
|
|
11115
12205
|
return /* @__PURE__ */ jsxs(GoldCard, { branded: true, className: "px-5 py-4 text-center md:px-8 md:py-5", children: [
|
|
11116
12206
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide opacity-75", children: t2("financial.home.balance") }),
|
|
11117
12207
|
/* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center justify-center gap-2.5", children: [
|
|
11118
|
-
/* @__PURE__ */ jsx("span", { className: "text-3xl font-bold tracking-tight tabular-nums md:text-4xl", children:
|
|
12208
|
+
/* @__PURE__ */ jsx("span", { className: "text-3xl font-bold tracking-tight tabular-nums md:text-4xl", children: money4(summary?.totalBalance) }),
|
|
11119
12209
|
/* @__PURE__ */ jsx(
|
|
11120
12210
|
"button",
|
|
11121
12211
|
{
|
|
@@ -11139,7 +12229,7 @@ function BalanceHero() {
|
|
|
11139
12229
|
/* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-emerald-500/90", children: /* @__PURE__ */ jsx(ArrowUpRight, { className: "h-4 w-4 text-white" }) }),
|
|
11140
12230
|
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
11141
12231
|
/* @__PURE__ */ jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.inflow") }),
|
|
11142
|
-
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children:
|
|
12232
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money4(inflow) })
|
|
11143
12233
|
] })
|
|
11144
12234
|
]
|
|
11145
12235
|
}
|
|
@@ -11155,7 +12245,7 @@ function BalanceHero() {
|
|
|
11155
12245
|
/* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-rose-500/90", children: /* @__PURE__ */ jsx(ArrowDownRight, { className: "h-4 w-4 text-white" }) }),
|
|
11156
12246
|
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
11157
12247
|
/* @__PURE__ */ jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.outflow") }),
|
|
11158
|
-
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children:
|
|
12248
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money4(outflow) })
|
|
11159
12249
|
] })
|
|
11160
12250
|
]
|
|
11161
12251
|
}
|
|
@@ -11181,7 +12271,7 @@ function categorizeSpend(notes) {
|
|
|
11181
12271
|
function SpendByCategoryChart() {
|
|
11182
12272
|
const t2 = useTranslation();
|
|
11183
12273
|
const provider = useFinancialProvider();
|
|
11184
|
-
const [rows, setRows] =
|
|
12274
|
+
const [rows, setRows] = React25__default.useState([]);
|
|
11185
12275
|
useEffect(() => {
|
|
11186
12276
|
let alive = true;
|
|
11187
12277
|
void (async () => {
|
|
@@ -11224,7 +12314,7 @@ function UpcomingBills() {
|
|
|
11224
12314
|
const { currency } = useFinancialConfig();
|
|
11225
12315
|
const provider = useFinancialProvider();
|
|
11226
12316
|
const dueLabel = useDueLabel();
|
|
11227
|
-
const [bills, setBills] =
|
|
12317
|
+
const [bills, setBills] = React25__default.useState([]);
|
|
11228
12318
|
useEffect(() => {
|
|
11229
12319
|
let alive = true;
|
|
11230
12320
|
void (async () => {
|
|
@@ -12846,9 +13936,17 @@ function createSupabaseFinancialProvider() {
|
|
|
12846
13936
|
totalAmount: i.total ?? 0,
|
|
12847
13937
|
discount: i.discount ?? 0,
|
|
12848
13938
|
surcharge: 0,
|
|
12849
|
-
|
|
13939
|
+
// `product_id` quando a linha veio de uma porta que o preenche (a
|
|
13940
|
+
// RPC da agenda); do metadata quando veio DESTA tela, que é onde a
|
|
13941
|
+
// escolha de catálogo é gravada. Lendo só a coluna, reabrir uma
|
|
13942
|
+
// fatura apagava o item escolhido de toda linha e deixava o texto
|
|
13943
|
+
// solto — e salvar de novo confirmava a perda.
|
|
13944
|
+
referenceId: i.productId ?? i.metadata?.referenceId,
|
|
12850
13945
|
accountId: i.chartAccountId,
|
|
12851
13946
|
costCenterId: i.costCenterId,
|
|
13947
|
+
// De onde a linha veio — o pacote que a gerou, por exemplo. Sem
|
|
13948
|
+
// devolvê-lo, editar a fatura desfazia o vínculo em silêncio.
|
|
13949
|
+
metadata: i.metadata ?? void 0,
|
|
12852
13950
|
createdAt: i.createdAt
|
|
12853
13951
|
};
|
|
12854
13952
|
});
|
|
@@ -12890,6 +13988,33 @@ function createSupabaseFinancialProvider() {
|
|
|
12890
13988
|
};
|
|
12891
13989
|
});
|
|
12892
13990
|
},
|
|
13991
|
+
async addInvoiceAttachment(invoiceId, file) {
|
|
13992
|
+
const { pub } = getClients();
|
|
13993
|
+
const tenantId = getTenantId();
|
|
13994
|
+
if (!tenantId) throw new Error("Conta sem empresa ativa");
|
|
13995
|
+
const safe = file.name.replace(/[^\w.\-]+/g, "_");
|
|
13996
|
+
const path = `${tenantId}/financial_invoice/${invoiceId}/${Date.now()}_${safe}`;
|
|
13997
|
+
const { error: upErr } = await pub.storage.from("attachments").upload(path, file, { contentType: file.type || void 0 });
|
|
13998
|
+
if (upErr) throw upErr;
|
|
13999
|
+
const { error } = await pub.from("documents").insert({
|
|
14000
|
+
tenant_id: tenantId,
|
|
14001
|
+
kind: "attachment",
|
|
14002
|
+
status: "active",
|
|
14003
|
+
subject_type: "financial_invoice",
|
|
14004
|
+
subject_id: invoiceId,
|
|
14005
|
+
file_name: file.name,
|
|
14006
|
+
file_size: file.size,
|
|
14007
|
+
mime_type: file.type || null,
|
|
14008
|
+
storage_provider: "supabase",
|
|
14009
|
+
storage_bucket: "attachments",
|
|
14010
|
+
storage_path: path
|
|
14011
|
+
});
|
|
14012
|
+
if (error) {
|
|
14013
|
+
await pub.storage.from("attachments").remove([path]).catch(() => {
|
|
14014
|
+
});
|
|
14015
|
+
throw error;
|
|
14016
|
+
}
|
|
14017
|
+
},
|
|
12893
14018
|
async createInvoice(input) {
|
|
12894
14019
|
const { core, pub } = getClients();
|
|
12895
14020
|
const tenantId = getTenantId();
|
|
@@ -14631,6 +15756,237 @@ function createSupabaseFinancialProvider() {
|
|
|
14631
15756
|
const { data, error } = await pub.from(T.notas).update({ status: "cancelled", error_message: reason ?? null }).eq("id", id).select().single();
|
|
14632
15757
|
if (error) throw error;
|
|
14633
15758
|
return snakeToCamel(data);
|
|
15759
|
+
},
|
|
15760
|
+
// --- Remessa CNAB 240 (080) ---
|
|
15761
|
+
/**
|
|
15762
|
+
* The Bacen registry, by COMPE code or by name.
|
|
15763
|
+
*
|
|
15764
|
+
* A bank is the country's, not the tenant's, so `public.banks` is global and
|
|
15765
|
+
* read-only. A pool where migration 167 has not run yet answers with an
|
|
15766
|
+
* empty list, and the caller falls back to the static catalogue — a missing
|
|
15767
|
+
* table costs a longer list, never the screen.
|
|
15768
|
+
*/
|
|
15769
|
+
async searchBanks(query, limit = 12) {
|
|
15770
|
+
const term = query.trim();
|
|
15771
|
+
const { pub } = getClients();
|
|
15772
|
+
let qb = pub.from("banks").select("ispb,compe,name,legal_name,brand_color,logo_url").eq("is_active", true);
|
|
15773
|
+
if (term) {
|
|
15774
|
+
const digits2 = term.replace(/\D/g, "");
|
|
15775
|
+
qb = digits2.length >= 2 && digits2.length <= 3 ? qb.like("compe", `${digits2}%`) : qb.or(`name.ilike.%${term}%,legal_name.ilike.%${term}%`);
|
|
15776
|
+
}
|
|
15777
|
+
const { data, error } = await qb.order("compe", { nullsFirst: false }).limit(limit);
|
|
15778
|
+
if (error) return [];
|
|
15779
|
+
return (data ?? []).map((row) => ({
|
|
15780
|
+
ispb: row.ispb,
|
|
15781
|
+
compe: row.compe ?? void 0,
|
|
15782
|
+
name: row.name,
|
|
15783
|
+
legalName: row.legal_name ?? void 0,
|
|
15784
|
+
brandColor: row.brand_color ?? void 0,
|
|
15785
|
+
logoUrl: row.logo_url ?? void 0
|
|
15786
|
+
}));
|
|
15787
|
+
},
|
|
15788
|
+
/**
|
|
15789
|
+
* Who gets paid, who pays, and which file number is next.
|
|
15790
|
+
*
|
|
15791
|
+
* The payee's document lives on `people`, the account on `payee_accounts`
|
|
15792
|
+
* and the address on `addresses` — three tables because each answers a
|
|
15793
|
+
* different question, and the CNPJ of the paying company is a fourth. The
|
|
15794
|
+
* wizard asks once and gets all of it, so step 3 can show a form with the
|
|
15795
|
+
* blanks already filled instead of a sentence saying data is missing.
|
|
15796
|
+
*/
|
|
15797
|
+
async getCnabContext({ contactIds, bankAccountId, unitId }) {
|
|
15798
|
+
const { pub } = getClients();
|
|
15799
|
+
const ids = [...new Set(contactIds.filter(Boolean))];
|
|
15800
|
+
const [peopleRes, accountsRes, addressRes, companyRes, unitRes, seqRes] = await Promise.all([
|
|
15801
|
+
ids.length ? pub.from("people").select("id,name,document_number").in("id", ids) : Promise.resolve({ data: [], error: null }),
|
|
15802
|
+
ids.length ? pub.from(T.payeeAccounts).select("*").eq("owner_type", "person").in("owner_id", ids).order("is_default", { ascending: false }).order("created_at") : Promise.resolve({ data: [], error: null }),
|
|
15803
|
+
ids.length ? pub.from("addresses").select("*").eq("owner_type", "person").in("owner_id", ids) : Promise.resolve({ data: [], error: null }),
|
|
15804
|
+
// As empresas do tenant. São unidades de linhas, então vêm todas e a
|
|
15805
|
+
// escolha é feita aqui — a da unidade da conta, senão a sem unidade,
|
|
15806
|
+
// senão a única que existe.
|
|
15807
|
+
pub.from("company_legal_entities").select("id,unit_id,kind,legal_name,trade_name,document_number").eq("is_active", true).limit(20),
|
|
15808
|
+
// O nome da unidade é o que a tela escreve enquanto não há empresa
|
|
15809
|
+
// cadastrada — melhor que um campo em branco pedindo razão social.
|
|
15810
|
+
unitId ? pub.from(T.unitCompanies).select("unit_id,unit_name").eq("unit_id", unitId).maybeSingle() : Promise.resolve({ data: null, error: null }),
|
|
15811
|
+
pub.from(T.cnabBatches).select("sequence_number").eq("bank_account_id", bankAccountId).order("sequence_number", { ascending: false }).limit(1).maybeSingle()
|
|
15812
|
+
]);
|
|
15813
|
+
if (peopleRes.error) throw peopleRes.error;
|
|
15814
|
+
if (accountsRes.error) throw accountsRes.error;
|
|
15815
|
+
const accountOf = /* @__PURE__ */ new Map();
|
|
15816
|
+
for (const row of accountsRes.data ?? []) {
|
|
15817
|
+
if (!accountOf.has(row.owner_id)) accountOf.set(row.owner_id, row);
|
|
15818
|
+
}
|
|
15819
|
+
const addressOf = /* @__PURE__ */ new Map();
|
|
15820
|
+
for (const row of addressRes.data ?? []) {
|
|
15821
|
+
if (!addressOf.has(row.owner_id)) addressOf.set(row.owner_id, row);
|
|
15822
|
+
}
|
|
15823
|
+
const payees = (peopleRes.data ?? []).map((person) => {
|
|
15824
|
+
const account = accountOf.get(person.id);
|
|
15825
|
+
const address = addressOf.get(person.id);
|
|
15826
|
+
return {
|
|
15827
|
+
contactId: person.id,
|
|
15828
|
+
name: person.name ?? "",
|
|
15829
|
+
document: person.document_number ?? "",
|
|
15830
|
+
payeeAccountId: account?.id,
|
|
15831
|
+
bankCode: account?.bank_code ?? "",
|
|
15832
|
+
bankName: account?.bank_name ?? "",
|
|
15833
|
+
agencyNumber: account?.agency_number ?? "",
|
|
15834
|
+
agencyDigit: account?.agency_digit ?? "",
|
|
15835
|
+
accountNumber: account?.account_number ?? "",
|
|
15836
|
+
accountDigit: account?.account_digit ?? "",
|
|
15837
|
+
accountType: account?.account_type ?? "",
|
|
15838
|
+
pixKey: account?.pix_key ?? "",
|
|
15839
|
+
pixKeyType: account?.pix_key_type ?? "",
|
|
15840
|
+
street: address?.street ?? "",
|
|
15841
|
+
number: address?.number ?? "",
|
|
15842
|
+
complement: address?.complement ?? "",
|
|
15843
|
+
district: address?.district ?? "",
|
|
15844
|
+
city: address?.city ?? "",
|
|
15845
|
+
state: address?.state ?? "",
|
|
15846
|
+
postalCode: address?.postal_code ?? ""
|
|
15847
|
+
};
|
|
15848
|
+
});
|
|
15849
|
+
const companies = companyRes.data ?? [];
|
|
15850
|
+
const companyRow = companies.find((row) => unitId && row.unit_id === unitId) ?? companies.find((row) => !row.unit_id) ?? companies[0];
|
|
15851
|
+
const unitName = unitRes.data?.unit_name ?? "";
|
|
15852
|
+
const company = {
|
|
15853
|
+
id: companyRow?.id ?? void 0,
|
|
15854
|
+
unitId,
|
|
15855
|
+
name: companyRow?.trade_name || companyRow?.legal_name || unitName,
|
|
15856
|
+
document: companyRow?.document_number || ""
|
|
15857
|
+
};
|
|
15858
|
+
return {
|
|
15859
|
+
company,
|
|
15860
|
+
nextSequence: Number(seqRes.data?.sequence_number ?? 0) + 1,
|
|
15861
|
+
payees
|
|
15862
|
+
};
|
|
15863
|
+
},
|
|
15864
|
+
/**
|
|
15865
|
+
* A empresa pagadora, cadastrada de dentro da remessa.
|
|
15866
|
+
*
|
|
15867
|
+
* `company_legal_entities` nasceu sem tela: nenhuma superfície do SDK
|
|
15868
|
+
* escreve nela, e o resultado é um pool com treze unidades e zero empresas.
|
|
15869
|
+
* Como o CNPJ é obrigatório no cabeçalho do CNAB, é aqui que a pessoa o
|
|
15870
|
+
* digita — e gravá-lo é o que faz a PRÓXIMA remessa já vir preenchida.
|
|
15871
|
+
*
|
|
15872
|
+
* `unit_id` na linha é o que recorta a empresa por unidade. O elo inverso
|
|
15873
|
+
* (`app.units.company_legal_entity_id`) fica para quem tiver a RPC: o schema
|
|
15874
|
+
* `app` não é alcançável por aqui, e a leitura já resolve pelos dois lados.
|
|
15875
|
+
*/
|
|
15876
|
+
async saveCompany(input) {
|
|
15877
|
+
const { pub } = getClients();
|
|
15878
|
+
const tenantId = getTenantId();
|
|
15879
|
+
const digits2 = (input.document || "").replace(/\D/g, "");
|
|
15880
|
+
const name = input.name.trim();
|
|
15881
|
+
if (!name) throw new Error("A empresa precisa de um nome.");
|
|
15882
|
+
if (digits2.length !== 11 && digits2.length !== 14) {
|
|
15883
|
+
throw new Error("CNPJ com 14 d\xEDgitos ou CPF com 11 \u2014 o cabe\xE7alho do CNAB n\xE3o aceita outro.");
|
|
15884
|
+
}
|
|
15885
|
+
const row = {
|
|
15886
|
+
kind: digits2.length === 14 ? "company" : "individual",
|
|
15887
|
+
legal_name: name,
|
|
15888
|
+
trade_name: name,
|
|
15889
|
+
document_number: digits2
|
|
15890
|
+
};
|
|
15891
|
+
if (input.id) {
|
|
15892
|
+
const { error: error2 } = await pub.from("company_legal_entities").update(row).eq("id", input.id);
|
|
15893
|
+
if (error2) throw error2;
|
|
15894
|
+
return { id: input.id };
|
|
15895
|
+
}
|
|
15896
|
+
const { data, error } = await pub.from("company_legal_entities").insert({ ...row, tenant_id: tenantId, unit_id: input.unitId ?? null, is_active: true }).select("id").single();
|
|
15897
|
+
if (error) throw error;
|
|
15898
|
+
return { id: data.id };
|
|
15899
|
+
},
|
|
15900
|
+
/**
|
|
15901
|
+
* What the wizard collected goes back to the cadastro, not to the file only.
|
|
15902
|
+
* Otherwise the same CPF is typed again on the next remittance, and the two
|
|
15903
|
+
* typings disagree.
|
|
15904
|
+
*/
|
|
15905
|
+
async saveCnabPayees(payees) {
|
|
15906
|
+
const { pub } = getClients();
|
|
15907
|
+
const tenantId = getTenantId();
|
|
15908
|
+
const text2 = (v) => {
|
|
15909
|
+
const s = (v ?? "").trim();
|
|
15910
|
+
return s === "" ? null : s;
|
|
15911
|
+
};
|
|
15912
|
+
for (const payee of payees) {
|
|
15913
|
+
if (text2(payee.document)) {
|
|
15914
|
+
await pub.from("people").update({ document_number: text2(payee.document) }).eq("id", payee.contactId);
|
|
15915
|
+
}
|
|
15916
|
+
const row = {
|
|
15917
|
+
bank_code: text2(payee.bankCode),
|
|
15918
|
+
bank_name: text2(payee.bankName),
|
|
15919
|
+
agency_number: text2(payee.agencyNumber),
|
|
15920
|
+
agency_digit: text2(payee.agencyDigit),
|
|
15921
|
+
account_number: text2(payee.accountNumber),
|
|
15922
|
+
account_digit: text2(payee.accountDigit),
|
|
15923
|
+
account_type: text2(payee.accountType),
|
|
15924
|
+
pix_key: text2(payee.pixKey),
|
|
15925
|
+
pix_key_type: text2(payee.pixKeyType)
|
|
15926
|
+
};
|
|
15927
|
+
if (!row.account_number && !row.pix_key) continue;
|
|
15928
|
+
let targetId = payee.payeeAccountId;
|
|
15929
|
+
if (!targetId) {
|
|
15930
|
+
const { data: existing } = await pub.from(T.payeeAccounts).select("id").eq("owner_type", "person").eq("owner_id", payee.contactId).order("is_default", { ascending: false }).order("created_at").limit(1).maybeSingle();
|
|
15931
|
+
targetId = existing?.id;
|
|
15932
|
+
}
|
|
15933
|
+
if (targetId) {
|
|
15934
|
+
await pub.from(T.payeeAccounts).update(row).eq("id", targetId);
|
|
15935
|
+
} else {
|
|
15936
|
+
await pub.from(T.payeeAccounts).insert({
|
|
15937
|
+
...row,
|
|
15938
|
+
tenant_id: tenantId,
|
|
15939
|
+
owner_type: "person",
|
|
15940
|
+
owner_id: payee.contactId,
|
|
15941
|
+
label: "Principal",
|
|
15942
|
+
is_default: true
|
|
15943
|
+
});
|
|
15944
|
+
}
|
|
15945
|
+
}
|
|
15946
|
+
},
|
|
15947
|
+
/**
|
|
15948
|
+
* The file that already left for the bank. `sequence_number` is the bank's
|
|
15949
|
+
* count, not the file's — two remittances carrying the same one is a
|
|
15950
|
+
* rejected remittance, so it is written with the batch and never derived
|
|
15951
|
+
* again.
|
|
15952
|
+
*/
|
|
15953
|
+
async recordCnabBatch(input) {
|
|
15954
|
+
const { pub } = getClients();
|
|
15955
|
+
const tenantId = getTenantId();
|
|
15956
|
+
const { data, error } = await pub.from(T.cnabBatches).insert({
|
|
15957
|
+
tenant_id: tenantId,
|
|
15958
|
+
unit_id: input.unitId ?? null,
|
|
15959
|
+
bank_account_id: input.bankAccountId,
|
|
15960
|
+
layout: input.layout,
|
|
15961
|
+
sequence_number: input.sequenceNumber,
|
|
15962
|
+
total_amount: input.totalAmount,
|
|
15963
|
+
items_count: input.items.length,
|
|
15964
|
+
file_name: input.fileName,
|
|
15965
|
+
// The file's home is `public.documents` through `document_id`; until the
|
|
15966
|
+
// upload path exists it rides in metadata, which keeps a generated
|
|
15967
|
+
// remittance downloadable instead of gone.
|
|
15968
|
+
metadata: { file_content: input.fileContent }
|
|
15969
|
+
}).select("id").single();
|
|
15970
|
+
if (error) throw error;
|
|
15971
|
+
const batchId = data.id;
|
|
15972
|
+
if (input.items.length) {
|
|
15973
|
+
const ids = [...new Set(input.items.map((item) => item.movementId))];
|
|
15974
|
+
const { data: movementRows } = await pub.from(T.movements).select("id").in("id", ids);
|
|
15975
|
+
const isMovement = new Set((movementRows ?? []).map((row) => row.id));
|
|
15976
|
+
const { error: itemsError } = await pub.from(T.cnabItems).insert(
|
|
15977
|
+
input.items.map((item) => ({
|
|
15978
|
+
tenant_id: tenantId,
|
|
15979
|
+
batch_id: batchId,
|
|
15980
|
+
installment_id: item.movementId,
|
|
15981
|
+
movement_id: isMovement.has(item.movementId) ? item.movementId : null,
|
|
15982
|
+
payment_form: item.paymentForm,
|
|
15983
|
+
payment_date: item.paymentDate,
|
|
15984
|
+
amount: item.amount
|
|
15985
|
+
}))
|
|
15986
|
+
);
|
|
15987
|
+
if (itemsError) throw itemsError;
|
|
15988
|
+
}
|
|
15989
|
+
return { id: batchId };
|
|
14634
15990
|
}
|
|
14635
15991
|
};
|
|
14636
15992
|
return provider;
|
|
@@ -15133,21 +16489,21 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
|
|
|
15133
16489
|
const canCreate = can("financial", "create");
|
|
15134
16490
|
const canEdit = can("financial", "edit");
|
|
15135
16491
|
const canDelete = can("financial", "delete");
|
|
15136
|
-
const provider =
|
|
16492
|
+
const provider = React25__default.useMemo(
|
|
15137
16493
|
() => dataProvider ?? resolveDataProvider(entity, mockData ?? []),
|
|
15138
16494
|
[entity, mockData, dataProvider]
|
|
15139
16495
|
);
|
|
15140
16496
|
const table = entity.data?.table;
|
|
15141
|
-
const [nodes, setNodes] =
|
|
15142
|
-
const [loadError, setLoadError] =
|
|
15143
|
-
const [expanded, setExpanded] =
|
|
15144
|
-
const [query, setQuery] =
|
|
15145
|
-
const [showInactive, setShowInactive] =
|
|
15146
|
-
const [editing, setEditing] =
|
|
15147
|
-
const [confirmDelete, setConfirmDelete] =
|
|
15148
|
-
const [busy, setBusy] =
|
|
15149
|
-
const initialized =
|
|
15150
|
-
const reload =
|
|
16497
|
+
const [nodes, setNodes] = React25__default.useState(null);
|
|
16498
|
+
const [loadError, setLoadError] = React25__default.useState(null);
|
|
16499
|
+
const [expanded, setExpanded] = React25__default.useState(/* @__PURE__ */ new Set());
|
|
16500
|
+
const [query, setQuery] = React25__default.useState("");
|
|
16501
|
+
const [showInactive, setShowInactive] = React25__default.useState(true);
|
|
16502
|
+
const [editing, setEditing] = React25__default.useState(null);
|
|
16503
|
+
const [confirmDelete, setConfirmDelete] = React25__default.useState(null);
|
|
16504
|
+
const [busy, setBusy] = React25__default.useState(false);
|
|
16505
|
+
const initialized = React25__default.useRef(false);
|
|
16506
|
+
const reload = React25__default.useCallback(async () => {
|
|
15151
16507
|
try {
|
|
15152
16508
|
const rows = await loadAll(provider);
|
|
15153
16509
|
setNodes(rows);
|
|
@@ -15162,17 +16518,17 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
|
|
|
15162
16518
|
setNodes((prev) => prev ?? []);
|
|
15163
16519
|
}
|
|
15164
16520
|
}, [provider]);
|
|
15165
|
-
|
|
16521
|
+
React25__default.useEffect(() => {
|
|
15166
16522
|
void reload();
|
|
15167
16523
|
}, [reload]);
|
|
15168
|
-
const shown =
|
|
16524
|
+
const shown = React25__default.useMemo(
|
|
15169
16525
|
() => (nodes ?? []).filter((n) => showInactive || n.isActive),
|
|
15170
16526
|
[nodes, showInactive]
|
|
15171
16527
|
);
|
|
15172
|
-
const tree =
|
|
15173
|
-
const fullTree =
|
|
15174
|
-
const search =
|
|
15175
|
-
const groupIds =
|
|
16528
|
+
const tree = React25__default.useMemo(() => buildChartTree(shown), [shown]);
|
|
16529
|
+
const fullTree = React25__default.useMemo(() => buildChartTree(nodes ?? []), [nodes]);
|
|
16530
|
+
const search = React25__default.useMemo(() => searchVisibleIds(tree, query), [tree, query]);
|
|
16531
|
+
const groupIds = React25__default.useMemo(
|
|
15176
16532
|
() => [...tree.byId.values()].filter((n) => isGroupNode(tree, n)).map((n) => n.id),
|
|
15177
16533
|
[tree]
|
|
15178
16534
|
);
|
|
@@ -15268,7 +16624,7 @@ function ChartOfAccountsTree({ entity, mockData, dataProvider, embedded }) {
|
|
|
15268
16624
|
const ok = await run(() => provider.remove(node.id), t2("financial.chartTree.deleted", { name: node.name }));
|
|
15269
16625
|
if (ok) setConfirmDelete(null);
|
|
15270
16626
|
}
|
|
15271
|
-
const parentOptions =
|
|
16627
|
+
const parentOptions = React25__default.useCallback((q, selfId) => {
|
|
15272
16628
|
const blocked = selfId ? descendantIds(fullTree, selfId).add(selfId) : /* @__PURE__ */ new Set();
|
|
15273
16629
|
const needle = q.trim().toLowerCase();
|
|
15274
16630
|
return [...fullTree.byId.values()].filter((n) => !blocked.has(n.id) && isGroupNode(fullTree, n)).filter((n) => !needle || accountLabel(n).toLowerCase().includes(needle)).sort((a, b) => a.code.localeCompare(b.code, void 0, { numeric: true })).slice(0, 50).map((n) => ({ id: n.id, label: accountLabel(n) }));
|
|
@@ -15571,7 +16927,7 @@ var chartOfAccountsSeed = [
|
|
|
15571
16927
|
{ id: "coa-4.2", code: "4.2", name: "Utilities", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true },
|
|
15572
16928
|
{ id: "coa-4.3", code: "4.3", name: "Payroll", nodeType: "leaf", accountType: "expense", parentId: "coa-4", isActive: true }
|
|
15573
16929
|
];
|
|
15574
|
-
var ChartOfAccountsRegistryView = (props) =>
|
|
16930
|
+
var ChartOfAccountsRegistryView = (props) => React25__default.createElement(ChartOfAccountsTree, { ...props, entity: chartOfAccountsEntity, mockData: chartOfAccountsSeed });
|
|
15575
16931
|
ChartOfAccountsRegistryView.displayName = "ChartOfAccountsRegistryView";
|
|
15576
16932
|
var costCenterEntity = {
|
|
15577
16933
|
name: "Cost Center",
|
|
@@ -15591,6 +16947,7 @@ var costCenterEntity = {
|
|
|
15591
16947
|
};
|
|
15592
16948
|
var isCard = (t2) => t2 === "card_receivable_credit" || t2 === "card_receivable_debit" || t2 === "card_payable";
|
|
15593
16949
|
var isBank = (t2) => t2 === "bank";
|
|
16950
|
+
var hasIssuer = (t2) => isBank(t2) || t2 === "card_payable";
|
|
15594
16951
|
var ACCOUNT_KINDS = ["cash_physical", "bank", "card_receivable_credit", "card_receivable_debit", "card_payable"];
|
|
15595
16952
|
var bankAccountEntity = {
|
|
15596
16953
|
name: "Conta",
|
|
@@ -15611,7 +16968,12 @@ var bankAccountEntity = {
|
|
|
15611
16968
|
// sozinho quando nenhum dos seus campos se aplica.
|
|
15612
16969
|
fieldGroups: [
|
|
15613
16970
|
{ id: "identity", label: "Identifica\xE7\xE3o", columns: 2 },
|
|
15614
|
-
{
|
|
16971
|
+
{
|
|
16972
|
+
id: "bank",
|
|
16973
|
+
label: "Onde o dinheiro est\xE1",
|
|
16974
|
+
columns: 2,
|
|
16975
|
+
labelWhen: (v) => v.kind === "card_payable" ? "Quem emitiu o cart\xE3o" : void 0
|
|
16976
|
+
},
|
|
15615
16977
|
{ id: "holder", label: "Titular", columns: 2, description: "A conta de uma rede raramente est\xE1 no nome da rede." },
|
|
15616
16978
|
{ id: "card", label: "Cart\xE3o", columns: 2 },
|
|
15617
16979
|
{ ...RECORD_GROUP }
|
|
@@ -15651,16 +17013,26 @@ var bankAccountEntity = {
|
|
|
15651
17013
|
label: "N\xFAmero do banco",
|
|
15652
17014
|
type: "text",
|
|
15653
17015
|
showInTable: false,
|
|
15654
|
-
visibleWhen: (v) =>
|
|
17016
|
+
visibleWhen: (v) => hasIssuer(v.kind),
|
|
15655
17017
|
group: "bank"
|
|
15656
17018
|
},
|
|
17019
|
+
// Picking the bank fills its number too, so the two cannot disagree.
|
|
15657
17020
|
{
|
|
15658
17021
|
key: "bankName",
|
|
15659
17022
|
label: "Banco",
|
|
15660
|
-
type: "
|
|
17023
|
+
type: "custom",
|
|
15661
17024
|
showInTable: false,
|
|
15662
|
-
|
|
15663
|
-
|
|
17025
|
+
labelWhen: (v) => v.kind === "card_payable" ? "Banco emissor" : void 0,
|
|
17026
|
+
visibleWhen: (v) => hasIssuer(v.kind),
|
|
17027
|
+
group: "bank",
|
|
17028
|
+
renderInput: ({ value, values, onChange, setValue }) => React25__default.createElement(BankPicker, {
|
|
17029
|
+
code: values.bankCode ?? "",
|
|
17030
|
+
name: value ?? "",
|
|
17031
|
+
onChange: (next) => {
|
|
17032
|
+
onChange(next.name);
|
|
17033
|
+
if (next.code || !next.name) setValue?.("bankCode", next.code);
|
|
17034
|
+
}
|
|
17035
|
+
})
|
|
15664
17036
|
},
|
|
15665
17037
|
{
|
|
15666
17038
|
key: "agencyNumber",
|
|
@@ -16522,10 +17894,10 @@ function CashSessionHeaderControl({ config, provider, store: store6 }) {
|
|
|
16522
17894
|
const fetchCashSessions = useStore(store6, (s) => s.fetchCashSessions);
|
|
16523
17895
|
const open = useStore(store6, (s) => s.cashSessionModalOpen);
|
|
16524
17896
|
const setOpen = useStore(store6, (s) => s.setCashSessionModalOpen);
|
|
16525
|
-
|
|
17897
|
+
React25__default.useEffect(() => {
|
|
16526
17898
|
void fetchCashSessions?.();
|
|
16527
17899
|
}, [fetchCashSessions]);
|
|
16528
|
-
|
|
17900
|
+
React25__default.useEffect(() => {
|
|
16529
17901
|
if (!open) return;
|
|
16530
17902
|
const close = () => setOpen(false);
|
|
16531
17903
|
window.addEventListener("hashchange", close);
|
|
@@ -16645,7 +18017,7 @@ var en = {
|
|
|
16645
18017
|
"financial.invoice.items": "Items",
|
|
16646
18018
|
"financial.invoice.keepInvoice": "Keep Invoice",
|
|
16647
18019
|
"financial.invoice.new": "New",
|
|
16648
|
-
"financial.invoice.newManual": "
|
|
18020
|
+
"financial.invoice.newManual": "Create",
|
|
16649
18021
|
"financial.invoiceForm.createContact": "Create",
|
|
16650
18022
|
"financial.invoiceForm.alreadyPaid": "Already paid",
|
|
16651
18023
|
"financial.invoiceForm.paidWith": "Payment method",
|
|
@@ -16701,6 +18073,7 @@ var en = {
|
|
|
16701
18073
|
"financial.invoiceForm.contactRequired": "Select who will pay or receive before adding items.",
|
|
16702
18074
|
"financial.invoiceForm.additionalNotes": "Additional notes...",
|
|
16703
18075
|
"financial.itemType.service": "Service",
|
|
18076
|
+
"financial.itemType.package": "Package",
|
|
16704
18077
|
"financial.itemType.product": "Product",
|
|
16705
18078
|
"financial.itemType.other": "Other",
|
|
16706
18079
|
"financial.itemType.gift_card": "Gift Card",
|
|
@@ -17383,6 +18756,10 @@ var en = {
|
|
|
17383
18756
|
"financial.quickTx.receiptRemove": "Remove receipt",
|
|
17384
18757
|
"financial.quickTx.receiptInvalid": "Please choose an image file",
|
|
17385
18758
|
"financial.quickTx.sendReceipt": "Send receipt",
|
|
18759
|
+
"financial.quickTx.digitizeExpense": "Digitize expense",
|
|
18760
|
+
"financial.quickTx.digitizeReceipt": "Digitize receipt",
|
|
18761
|
+
"financial.invoice.importPayroll": "Import payroll",
|
|
18762
|
+
"financial.invoice.importPayrollSoon": "Coming soon",
|
|
17386
18763
|
"financial.receipt.ask": "Send the receipt photo\u2026",
|
|
17387
18764
|
"financial.receipt.attachRead": "Attach the receipt the assistant read",
|
|
17388
18765
|
"financial.receipt.fromReceipt": "From the receipt",
|
|
@@ -17534,7 +18911,7 @@ var ptBR = {
|
|
|
17534
18911
|
"financial.invoice.items": "Itens",
|
|
17535
18912
|
"financial.invoice.keepInvoice": "Manter Fatura",
|
|
17536
18913
|
"financial.invoice.new": "Nova",
|
|
17537
|
-
"financial.invoice.newManual": "Cadastrar
|
|
18914
|
+
"financial.invoice.newManual": "Cadastrar",
|
|
17538
18915
|
"financial.invoiceForm.createContact": "Cadastrar",
|
|
17539
18916
|
"financial.invoiceForm.alreadyPaid": "J\xE1 foi pago",
|
|
17540
18917
|
"financial.invoiceForm.paidWith": "Forma de pagamento",
|
|
@@ -17590,6 +18967,7 @@ var ptBR = {
|
|
|
17590
18967
|
"financial.invoiceForm.contactRequired": "Selecione quem vai pagar ou receber antes de adicionar itens.",
|
|
17591
18968
|
"financial.invoiceForm.additionalNotes": "Observa\xE7\xF5es adicionais...",
|
|
17592
18969
|
"financial.itemType.service": "Servi\xE7o",
|
|
18970
|
+
"financial.itemType.package": "Pacote",
|
|
17593
18971
|
"financial.itemType.product": "Produto",
|
|
17594
18972
|
"financial.itemType.other": "Outro",
|
|
17595
18973
|
"financial.itemType.gift_card": "Gift Card",
|
|
@@ -18275,6 +19653,10 @@ var ptBR = {
|
|
|
18275
19653
|
"financial.quickTx.receiptRemove": "Remover recibo",
|
|
18276
19654
|
"financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
|
|
18277
19655
|
"financial.quickTx.sendReceipt": "Enviar recibo",
|
|
19656
|
+
"financial.quickTx.digitizeExpense": "Digitalizar despesa",
|
|
19657
|
+
"financial.quickTx.digitizeReceipt": "Digitalizar comprovante",
|
|
19658
|
+
"financial.invoice.importPayroll": "Importar folha",
|
|
19659
|
+
"financial.invoice.importPayrollSoon": "Em breve",
|
|
18278
19660
|
"financial.receipt.ask": "Mande a foto do recibo\u2026",
|
|
18279
19661
|
"financial.receipt.attachRead": "Anexar o recibo que o assistente leu",
|
|
18280
19662
|
"financial.receipt.fromReceipt": "Do recibo",
|
|
@@ -18440,14 +19822,14 @@ function CommissionRuleDialog({
|
|
|
18440
19822
|
onClose,
|
|
18441
19823
|
onSaved
|
|
18442
19824
|
}) {
|
|
18443
|
-
const [name, setName] =
|
|
18444
|
-
const [description, setDescription] =
|
|
18445
|
-
const [priority, setPriority] =
|
|
18446
|
-
const [isActive, setIsActive] =
|
|
18447
|
-
const [isDefault, setIsDefault] =
|
|
18448
|
-
const [saving, setSaving] =
|
|
18449
|
-
const [error, setError] =
|
|
18450
|
-
const initial =
|
|
19825
|
+
const [name, setName] = React25__default.useState(rule?.name ?? "");
|
|
19826
|
+
const [description, setDescription] = React25__default.useState(rule?.description ?? "");
|
|
19827
|
+
const [priority, setPriority] = React25__default.useState(String(rule?.priority ?? 1));
|
|
19828
|
+
const [isActive, setIsActive] = React25__default.useState(rule?.isActive ?? true);
|
|
19829
|
+
const [isDefault, setIsDefault] = React25__default.useState(rule?.isDefault ?? !hasDefault);
|
|
19830
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
19831
|
+
const [error, setError] = React25__default.useState(null);
|
|
19832
|
+
const initial = React25__default.useMemo(() => {
|
|
18451
19833
|
const get = (t2, key = "ids") => rule?.criteria.find((c) => c.criteriaType === t2)?.criteriaValue?.[key] ?? [];
|
|
18452
19834
|
return {
|
|
18453
19835
|
services: get("service"),
|
|
@@ -18458,12 +19840,12 @@ function CommissionRuleDialog({
|
|
|
18458
19840
|
ranges: get("time_range", "ranges")
|
|
18459
19841
|
};
|
|
18460
19842
|
}, [rule]);
|
|
18461
|
-
const [services, setServices] =
|
|
18462
|
-
const [categories, setCategories] =
|
|
18463
|
-
const [professionals, setProfessionals] =
|
|
18464
|
-
const [units, setUnits] =
|
|
18465
|
-
const [weekdays, setWeekdays] =
|
|
18466
|
-
const [ranges, setRanges] =
|
|
19843
|
+
const [services, setServices] = React25__default.useState(initial.services);
|
|
19844
|
+
const [categories, setCategories] = React25__default.useState(initial.categories);
|
|
19845
|
+
const [professionals, setProfessionals] = React25__default.useState(initial.professionals);
|
|
19846
|
+
const [units, setUnits] = React25__default.useState(initial.units);
|
|
19847
|
+
const [weekdays, setWeekdays] = React25__default.useState(initial.weekdays);
|
|
19848
|
+
const [ranges, setRanges] = React25__default.useState(initial.ranges);
|
|
18467
19849
|
const criteriaCount = [services, categories, professionals, units].filter((a) => a.length > 0).length + (weekdays.length ? 1 : 0) + (ranges.length ? 1 : 0);
|
|
18468
19850
|
async function handleSave() {
|
|
18469
19851
|
if (!name.trim()) {
|
|
@@ -18706,11 +20088,11 @@ function blankFunction(order) {
|
|
|
18706
20088
|
};
|
|
18707
20089
|
}
|
|
18708
20090
|
function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
|
|
18709
|
-
const [functions, setFunctions] =
|
|
20091
|
+
const [functions, setFunctions] = React25__default.useState(
|
|
18710
20092
|
rule.functions.length ? rule.functions : [blankFunction(0)]
|
|
18711
20093
|
);
|
|
18712
|
-
const [saving, setSaving] =
|
|
18713
|
-
const [error, setError] =
|
|
20094
|
+
const [saving, setSaving] = React25__default.useState(false);
|
|
20095
|
+
const [error, setError] = React25__default.useState(null);
|
|
18714
20096
|
function patch(i, changes) {
|
|
18715
20097
|
setFunctions((fs) => fs.map((f, j) => j === i ? { ...f, ...changes } : f));
|
|
18716
20098
|
}
|
|
@@ -18994,19 +20376,19 @@ function functionChip(fn) {
|
|
|
18994
20376
|
return fn.valueType === "percentage" ? `${who}: ${fn.valueAmount}%` : `${who}: R$ ${fn.valueAmount.toFixed(2)}`;
|
|
18995
20377
|
}
|
|
18996
20378
|
function CommissionRulesView({ provider }) {
|
|
18997
|
-
const [rules, setRules] =
|
|
18998
|
-
const [catalogs, setCatalogs] =
|
|
20379
|
+
const [rules, setRules] = React25__default.useState(null);
|
|
20380
|
+
const [catalogs, setCatalogs] = React25__default.useState({
|
|
18999
20381
|
services: [],
|
|
19000
20382
|
categories: [],
|
|
19001
20383
|
professionals: [],
|
|
19002
20384
|
units: []
|
|
19003
20385
|
});
|
|
19004
|
-
const [error, setError] =
|
|
19005
|
-
const [funnel, setFunnel] =
|
|
19006
|
-
const [editing, setEditing] =
|
|
19007
|
-
const [functionsFor, setFunctionsFor] =
|
|
19008
|
-
const [deleting, setDeleting] =
|
|
19009
|
-
const load =
|
|
20386
|
+
const [error, setError] = React25__default.useState(null);
|
|
20387
|
+
const [funnel, setFunnel] = React25__default.useState({});
|
|
20388
|
+
const [editing, setEditing] = React25__default.useState(null);
|
|
20389
|
+
const [functionsFor, setFunctionsFor] = React25__default.useState(null);
|
|
20390
|
+
const [deleting, setDeleting] = React25__default.useState(null);
|
|
20391
|
+
const load = React25__default.useCallback(async () => {
|
|
19010
20392
|
try {
|
|
19011
20393
|
setError(null);
|
|
19012
20394
|
const [r, c] = await Promise.all([
|
|
@@ -19020,17 +20402,17 @@ function CommissionRulesView({ provider }) {
|
|
|
19020
20402
|
setRules([]);
|
|
19021
20403
|
}
|
|
19022
20404
|
}, [provider]);
|
|
19023
|
-
|
|
20405
|
+
React25__default.useEffect(() => {
|
|
19024
20406
|
void load();
|
|
19025
20407
|
}, [load]);
|
|
19026
20408
|
const funnelActive = Object.values(funnel).some(Boolean);
|
|
19027
|
-
const visible =
|
|
20409
|
+
const visible = React25__default.useMemo(() => {
|
|
19028
20410
|
if (!rules) return [];
|
|
19029
20411
|
const ranked = rankRules(rules);
|
|
19030
20412
|
if (!funnelActive) return ranked;
|
|
19031
20413
|
return ranked.filter((r) => r.isDefault || ruleMatches(r, funnel, catalogs));
|
|
19032
20414
|
}, [rules, funnel, funnelActive, catalogs]);
|
|
19033
|
-
const winner =
|
|
20415
|
+
const winner = React25__default.useMemo(() => {
|
|
19034
20416
|
if (!funnelActive) return null;
|
|
19035
20417
|
return visible.find((r) => r.isActive && !r.isDefault) ?? visible.find((r) => r.isActive && r.isDefault) ?? null;
|
|
19036
20418
|
}, [visible, funnelActive]);
|
|
@@ -19045,7 +20427,7 @@ function CommissionRulesView({ provider }) {
|
|
|
19045
20427
|
await provider.duplicateCommissionRule?.(rule.id);
|
|
19046
20428
|
await load();
|
|
19047
20429
|
}
|
|
19048
|
-
const headerActions =
|
|
20430
|
+
const headerActions = React25__default.useMemo(() => [{
|
|
19049
20431
|
id: "new-rule",
|
|
19050
20432
|
label: "Adicionar regra",
|
|
19051
20433
|
icon: Plus,
|
|
@@ -19827,7 +21209,7 @@ function PersonStatementWidget({ person, provider, config }) {
|
|
|
19827
21209
|
] });
|
|
19828
21210
|
}
|
|
19829
21211
|
function SupplierFinancialSettings({ person, provider }) {
|
|
19830
|
-
const initial =
|
|
21212
|
+
const initial = React25__default.useMemo(() => counterpartyAccountingPolicy(person), [person]);
|
|
19831
21213
|
const [accounts, setAccounts] = useState([]);
|
|
19832
21214
|
const [costCenters, setCostCenters] = useState([]);
|
|
19833
21215
|
const [defaultAccountId, setDefaultAccountId] = useState(initial.defaultAccountId ?? "");
|
|
@@ -42899,6 +44281,215 @@ COMMENT ON VIEW public.v_financial_invoice_lines_due IS
|
|
|
42899
44281
|
|
|
42900
44282
|
ALTER VIEW public.v_financial_invoice_lines_due SET (security_invoker = true);
|
|
42901
44283
|
GRANT SELECT ON public.v_financial_invoice_lines_due TO authenticated, service_role;
|
|
44284
|
+
`;
|
|
44285
|
+
var MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE = `-- ---------------------------------------------------------------------------
|
|
44286
|
+
-- 115 \u2014 a remessa aponta para a conta que EXISTE.
|
|
44287
|
+
--
|
|
44288
|
+
-- \`plg_financial_cnab_batches.bank_account_id\` carrega DUAS chaves estrangeiras
|
|
44289
|
+
-- para a mesma coluna:
|
|
44290
|
+
--
|
|
44291
|
+
-- plg_financial_cnab_batches_account_fkey \u2192 plg_financial_bank_accounts
|
|
44292
|
+
-- plg_financial_cnab_batches_ledger_account_fkey \u2192 plg_financial_accounts
|
|
44293
|
+
--
|
|
44294
|
+
-- A segunda \xE9 a certa. A primeira aponta para a tabela LEGADA \u2014 aquela que a
|
|
44295
|
+
-- 021 aposentou e que hoje \xE9 s\xF3 o encaminhamento por gatilho. Neste cluster ela
|
|
44296
|
+
-- tem 5 linhas contra 63 contas banc\xE1rias no raz\xE3o, e o efeito \xE9 exato: gerar
|
|
44297
|
+
-- uma remessa por qualquer conta criada depois do porte morre com
|
|
44298
|
+
--
|
|
44299
|
+
-- violates foreign key constraint "plg_financial_cnab_batches_account_fkey"
|
|
44300
|
+
-- Key is not present in table "plg_financial_bank_accounts"
|
|
44301
|
+
--
|
|
44302
|
+
-- Uma coluna com duas FKs exige que o id esteja nas DUAS tabelas, e \xE9 por isso
|
|
44303
|
+
-- que a tela nunca conseguiu gravar: ela escreve onde \`getBankAccounts\` l\xEA, que
|
|
44304
|
+
-- \xE9 o raz\xE3o, e o legado nunca soube daquela conta.
|
|
44305
|
+
--
|
|
44306
|
+
-- A 080 trouxe a tabela do V1 com a FK que o V1 tinha. O que faltou foi
|
|
44307
|
+
-- aposentar a chave junto com a tabela a que ela se referia.
|
|
44308
|
+
--
|
|
44309
|
+
-- Derrubar a FK legada n\xE3o perde nada: a do raz\xE3o continua guardando a coluna, e
|
|
44310
|
+
-- \xE9 ela que diz a verdade. As 60 remessas j\xE1 migradas apontam para contas que
|
|
44311
|
+
-- est\xE3o nas duas tabelas e seguem v\xE1lidas.
|
|
44312
|
+
--
|
|
44313
|
+
-- Idempotente.
|
|
44314
|
+
-- ---------------------------------------------------------------------------
|
|
44315
|
+
|
|
44316
|
+
ALTER TABLE public.plg_financial_cnab_batches
|
|
44317
|
+
DROP CONSTRAINT IF EXISTS plg_financial_cnab_batches_account_fkey;
|
|
44318
|
+
|
|
44319
|
+
-- A do raz\xE3o pode n\xE3o existir num pool que recebeu a 080 e n\xE3o a corre\xE7\xE3o; \xE9
|
|
44320
|
+
-- ela que fica, ent\xE3o se faltar, nasce aqui.
|
|
44321
|
+
DO $$
|
|
44322
|
+
BEGIN
|
|
44323
|
+
IF NOT EXISTS (
|
|
44324
|
+
SELECT 1 FROM pg_constraint
|
|
44325
|
+
WHERE conrelid = 'public.plg_financial_cnab_batches'::regclass
|
|
44326
|
+
AND conname = 'plg_financial_cnab_batches_ledger_account_fkey'
|
|
44327
|
+
) THEN
|
|
44328
|
+
ALTER TABLE public.plg_financial_cnab_batches
|
|
44329
|
+
ADD CONSTRAINT plg_financial_cnab_batches_ledger_account_fkey
|
|
44330
|
+
FOREIGN KEY (bank_account_id)
|
|
44331
|
+
REFERENCES public.plg_financial_accounts(id) ON DELETE SET NULL;
|
|
44332
|
+
END IF;
|
|
44333
|
+
END $$;
|
|
44334
|
+
|
|
44335
|
+
COMMENT ON COLUMN public.plg_financial_cnab_batches.bank_account_id IS
|
|
44336
|
+
'A conta do RAZ\xC3O que o arquivo debita \u2014 plg_financial_accounts, a mesma que getBankAccounts l\xEA. N\xE3o \xE9 a tabela legada plg_financial_bank_accounts: uma conta criada depois do porte s\xF3 existe no raz\xE3o.';
|
|
44337
|
+
|
|
44338
|
+
-- \u2500\u2500 a parcela que n\xE3o tem movimento g\xEAmeo \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
44339
|
+
--
|
|
44340
|
+
-- \`plg_financial_cnab_items.movement_id\` aponta para \`plg_financial_movements\`.
|
|
44341
|
+
-- A tela manda a PARCELA, e o porte deixou parcela e movimento com o mesmo id
|
|
44342
|
+
-- na maioria das linhas \u2014 mas n\xE3o em todas: aqui s\xE3o 171.978 parcelas contra
|
|
44343
|
+
-- 143.175 com movimento de mesmo id. As outras 28.803 fariam a remessa morrer
|
|
44344
|
+
-- na hora de arquivar um arquivo que J\xC1 foi baixado e j\xE1 foi para o banco.
|
|
44345
|
+
--
|
|
44346
|
+
-- O item passa a aceitar a parcela em coluna pr\xF3pria. \`movement_id\` continua
|
|
44347
|
+
-- sendo o elo com o raz\xE3o onde ele existe; \`installment_id\` \xE9 o que a tela
|
|
44348
|
+
-- sempre teve na m\xE3o, e \xE9 por ele que \xABesta conta j\xE1 foi numa remessa\xBB tem
|
|
44349
|
+
-- resposta.
|
|
44350
|
+
ALTER TABLE public.plg_financial_cnab_items
|
|
44351
|
+
ADD COLUMN IF NOT EXISTS installment_id uuid;
|
|
44352
|
+
|
|
44353
|
+
DO $$
|
|
44354
|
+
BEGIN
|
|
44355
|
+
IF NOT EXISTS (
|
|
44356
|
+
SELECT 1 FROM pg_constraint
|
|
44357
|
+
WHERE conrelid = 'public.plg_financial_cnab_items'::regclass
|
|
44358
|
+
AND conname = 'plg_financial_cnab_items_installment_fkey'
|
|
44359
|
+
) THEN
|
|
44360
|
+
ALTER TABLE public.plg_financial_cnab_items
|
|
44361
|
+
ADD CONSTRAINT plg_financial_cnab_items_installment_fkey
|
|
44362
|
+
FOREIGN KEY (installment_id)
|
|
44363
|
+
REFERENCES public.plg_financial_invoice_installments(id) ON DELETE SET NULL;
|
|
44364
|
+
END IF;
|
|
44365
|
+
END $$;
|
|
44366
|
+
|
|
44367
|
+
-- As linhas j\xE1 migradas: onde o movimento tem parcela de mesmo id, o elo novo
|
|
44368
|
+
-- se preenche sozinho. O que n\xE3o casar fica nulo, que \xE9 a verdade.
|
|
44369
|
+
UPDATE public.plg_financial_cnab_items ci
|
|
44370
|
+
SET installment_id = ci.movement_id
|
|
44371
|
+
WHERE ci.installment_id IS NULL
|
|
44372
|
+
AND ci.movement_id IS NOT NULL
|
|
44373
|
+
AND EXISTS (
|
|
44374
|
+
SELECT 1 FROM public.plg_financial_invoice_installments i WHERE i.id = ci.movement_id
|
|
44375
|
+
);
|
|
44376
|
+
|
|
44377
|
+
CREATE INDEX IF NOT EXISTS plg_financial_cnab_items_installment_idx
|
|
44378
|
+
ON public.plg_financial_cnab_items (tenant_id, installment_id)
|
|
44379
|
+
WHERE installment_id IS NOT NULL;
|
|
44380
|
+
|
|
44381
|
+
COMMENT ON COLUMN public.plg_financial_cnab_items.installment_id IS
|
|
44382
|
+
'A PARCELA que esta linha da remessa paga \u2014 \xE9 o que a tela de contas tem na m\xE3o. movement_id continua apontando para o raz\xE3o onde o movimento g\xEAmeo existe.';
|
|
44383
|
+
|
|
44384
|
+
-- \u2500\u2500 uma conta padr\xE3o por favorecido, sem derrubar a remessa \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
44385
|
+
--
|
|
44386
|
+
-- \`payee_accounts_one_default_uidx\` \xE9 \xFAnico em (tenant_id, owner_type, owner_id)
|
|
44387
|
+
-- WHERE is_default, e est\xE1 certo: duas contas padr\xE3o fazem a tela mostrar uma e
|
|
44388
|
+
-- a remessa pagar a outra.
|
|
44389
|
+
--
|
|
44390
|
+
-- O que estava errado era quem escrevia: a remessa inseria sempre com
|
|
44391
|
+
-- \`is_default = true\`, ent\xE3o a SEGUNDA tentativa de gerar o mesmo arquivo \u2014
|
|
44392
|
+
-- depois de a primeira morrer na FK acima \u2014 batia em
|
|
44393
|
+
--
|
|
44394
|
+
-- duplicate key value violates unique constraint
|
|
44395
|
+
--
|
|
44396
|
+
-- O conserto \xE9 do lado de quem escreve (\`saveCnabPayees\` procura antes de
|
|
44397
|
+
-- inserir), e esta fun\xE7\xE3o \xE9 a rede: um \`insert\` que j\xE1 tem padr\xE3o vira o padr\xE3o
|
|
44398
|
+
-- da pessoa sem precisar que o chamador saiba disso.
|
|
44399
|
+
CREATE OR REPLACE FUNCTION public.trg_payee_accounts_one_default()
|
|
44400
|
+
RETURNS trigger
|
|
44401
|
+
LANGUAGE plpgsql
|
|
44402
|
+
SECURITY INVOKER
|
|
44403
|
+
SET search_path = public, pg_catalog
|
|
44404
|
+
AS $$
|
|
44405
|
+
BEGIN
|
|
44406
|
+
IF NEW.is_default THEN
|
|
44407
|
+
UPDATE public.payee_accounts
|
|
44408
|
+
SET is_default = false
|
|
44409
|
+
WHERE tenant_id = NEW.tenant_id
|
|
44410
|
+
AND owner_type = NEW.owner_type
|
|
44411
|
+
AND owner_id = NEW.owner_id
|
|
44412
|
+
AND is_default
|
|
44413
|
+
AND id <> NEW.id;
|
|
44414
|
+
END IF;
|
|
44415
|
+
RETURN NEW;
|
|
44416
|
+
END
|
|
44417
|
+
$$;
|
|
44418
|
+
|
|
44419
|
+
DROP TRIGGER IF EXISTS payee_accounts_one_default ON public.payee_accounts;
|
|
44420
|
+
CREATE TRIGGER payee_accounts_one_default
|
|
44421
|
+
BEFORE INSERT OR UPDATE OF is_default ON public.payee_accounts
|
|
44422
|
+
FOR EACH ROW EXECUTE FUNCTION public.trg_payee_accounts_one_default();
|
|
44423
|
+
`;
|
|
44424
|
+
var MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA = `-- ---------------------------------------------------------------------------
|
|
44425
|
+
-- 116 \u2014 um estorno n\xE3o se estorna.
|
|
44426
|
+
--
|
|
44427
|
+
-- \`financial_reverse_movement\` aceitava qualquer \`payment\`, inclusive a linha
|
|
44428
|
+
-- que ela mesma lan\xE7a como contrapartida. A tela mirava \xABo \xFAltimo pagamento da
|
|
44429
|
+
-- parcela\xBB, que depois do primeiro estorno \xC9 o estorno: cada clique estornava o
|
|
44430
|
+
-- estorno anterior, o dinheiro ia e voltava, e o pagamento vivo continuava l\xE1 \u2014
|
|
44431
|
+
-- a conta seguia \xABpaga\xBB para sempre. Agora o banco recusa com uma mensagem que
|
|
44432
|
+
-- a tela mostra.
|
|
44433
|
+
--
|
|
44434
|
+
-- Idempotente: s\xF3 rep\xF5e a fun\xE7\xE3o.
|
|
44435
|
+
-- ---------------------------------------------------------------------------
|
|
44436
|
+
|
|
44437
|
+
CREATE OR REPLACE FUNCTION public.financial_reverse_movement(p_movement_id uuid, p_reason text, p_idempotency_key text DEFAULT NULL::text) RETURNS jsonb
|
|
44438
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
44439
|
+
SET search_path TO ''
|
|
44440
|
+
AS $$
|
|
44441
|
+
DECLARE
|
|
44442
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
44443
|
+
v_m public.plg_financial_movements%ROWTYPE;
|
|
44444
|
+
v_rev uuid;
|
|
44445
|
+
BEGIN
|
|
44446
|
+
IF nullif(btrim(coalesce(p_reason, '')), '') IS NULL THEN
|
|
44447
|
+
RAISE EXCEPTION 'financial_reverse_movement: a reason is required' USING ERRCODE = '22023';
|
|
44448
|
+
END IF;
|
|
44449
|
+
SELECT * INTO v_m FROM public.plg_financial_movements WHERE id = p_movement_id AND tenant_id = v_tenant;
|
|
44450
|
+
IF NOT FOUND THEN
|
|
44451
|
+
RAISE EXCEPTION 'financial_reverse_movement: movement % not found', p_movement_id USING ERRCODE = 'P0002';
|
|
44452
|
+
END IF;
|
|
44453
|
+
PERFORM public.plg_financial_require_permission('financial.edit', v_m.unit_id);
|
|
44454
|
+
IF v_m.movement_kind NOT IN ('payment', 'transfer') OR v_m.debit_account_id IS NULL THEN
|
|
44455
|
+
RAISE EXCEPTION 'financial_reverse_movement: % is not a money event', p_movement_id USING ERRCODE = '55000';
|
|
44456
|
+
END IF;
|
|
44457
|
+
IF v_m.reverses_id IS NOT NULL THEN
|
|
44458
|
+
RAISE EXCEPTION 'Este lan\xE7amento j\xE1 \xE9 um estorno e n\xE3o pode ser estornado.' USING ERRCODE = '55000';
|
|
44459
|
+
END IF;
|
|
44460
|
+
IF v_m.reversed_by_id IS NOT NULL THEN
|
|
44461
|
+
RETURN jsonb_build_object('reversal_id', v_m.reversed_by_id, 'movement_id', v_m.id, 'replayed', true);
|
|
44462
|
+
END IF;
|
|
44463
|
+
IF p_idempotency_key IS NOT NULL THEN
|
|
44464
|
+
PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);
|
|
44465
|
+
SELECT m.id INTO v_rev FROM public.plg_financial_movements m WHERE m.tenant_id = v_tenant AND m.idempotency_key = p_idempotency_key;
|
|
44466
|
+
IF v_rev IS NOT NULL THEN
|
|
44467
|
+
RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', true);
|
|
44468
|
+
END IF;
|
|
44469
|
+
END IF;
|
|
44470
|
+
|
|
44471
|
+
INSERT INTO public.plg_financial_movements
|
|
44472
|
+
(tenant_id, unit_id, invoice_id, financial_invoice_id, financial_invoice_installment_id, direction, movement_kind, amount, paid_amount, status,
|
|
44473
|
+
due_date, payment_date, installment_number, method, cash_session_id, bank_account_id, reason, notes, metadata,
|
|
44474
|
+
idempotency_key, created_by, reverses_id, debit_account_id, credit_account_id)
|
|
44475
|
+
VALUES
|
|
44476
|
+
(v_m.tenant_id, v_m.unit_id, v_m.invoice_id, v_m.financial_invoice_id, v_m.financial_invoice_installment_id,
|
|
44477
|
+
CASE WHEN v_m.direction = 'credit' THEN 'debit' ELSE 'credit' END, v_m.movement_kind, v_m.amount, v_m.amount, 'paid',
|
|
44478
|
+
current_date, current_date, v_m.installment_number, v_m.method, v_m.cash_session_id, v_m.bank_account_id, btrim(p_reason),
|
|
44479
|
+
'Estorno de ' || v_m.id::text,
|
|
44480
|
+
jsonb_build_object('source', 'financial_reverse_movement', 'reversal_of', v_m.id, 'reason', btrim(p_reason)),
|
|
44481
|
+
p_idempotency_key, auth.uid(), v_m.id, v_m.credit_account_id, v_m.debit_account_id)
|
|
44482
|
+
RETURNING id INTO v_rev;
|
|
44483
|
+
|
|
44484
|
+
UPDATE public.plg_financial_movements SET reversed_by_id = v_rev, reason = coalesce(reason, btrim(p_reason)), updated_at = now()
|
|
44485
|
+
WHERE id = v_m.id;
|
|
44486
|
+
IF v_m.financial_invoice_installment_id IS NOT NULL THEN PERFORM public.plg_financial_sync_mirror(v_m.financial_invoice_installment_id); END IF;
|
|
44487
|
+
|
|
44488
|
+
PERFORM public.plg_financial_audit(v_tenant, 'financial.movement.reverse', 'plg_financial_movements', v_m.id::text,
|
|
44489
|
+
jsonb_build_object('reversal_id', v_rev, 'amount', v_m.amount, 'reason', btrim(p_reason), 'idempotency_key', p_idempotency_key));
|
|
44490
|
+
RETURN jsonb_build_object('reversal_id', v_rev, 'movement_id', v_m.id, 'replayed', false);
|
|
44491
|
+
END $$;
|
|
44492
|
+
|
|
42902
44493
|
`;
|
|
42903
44494
|
var MIGRATIONS = [
|
|
42904
44495
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
@@ -43015,11 +44606,11 @@ var MIGRATIONS = [
|
|
|
43015
44606
|
{ id: "111_reparcelar_sem_apagar_a_parcela", sql: MIGRATION_111_REPARCELAR_SEM_APAGAR_A_PARCELA },
|
|
43016
44607
|
{ id: "112_a_linha_do_extrato_nao_e_um_lancamento_ainda", sql: MIGRATION_112_A_LINHA_DO_EXTRATO_NAO_E_UM_LANCAMENTO_AINDA },
|
|
43017
44608
|
{ id: "113_so_a_conta_integrada_aparece_na_conciliacao", sql: MIGRATION_113_SO_A_CONTA_INTEGRADA_APARECE_NA_CONCILIACAO },
|
|
43018
|
-
{ id: "114_a_parcela_diz_por_qual_conta_ela_foi_paga", sql: MIGRATION_114_A_PARCELA_DIZ_POR_QUAL_CONTA_ELA_FOI_PAGA }
|
|
44609
|
+
{ id: "114_a_parcela_diz_por_qual_conta_ela_foi_paga", sql: MIGRATION_114_A_PARCELA_DIZ_POR_QUAL_CONTA_ELA_FOI_PAGA },
|
|
44610
|
+
{ id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE },
|
|
44611
|
+
{ id: "116_um_estorno_nao_se_estorna", sql: MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA }
|
|
43019
44612
|
];
|
|
43020
|
-
var STANDALONE_CONFIG =
|
|
43021
|
-
currency: { code: "BRL", locale: "pt-BR", symbol: "R$" }
|
|
43022
|
-
};
|
|
44613
|
+
var STANDALONE_CONFIG = resolveConfig();
|
|
43023
44614
|
registerTranslations(financialLocales);
|
|
43024
44615
|
function ReceivePaymentModal({
|
|
43025
44616
|
installment,
|
|
@@ -43027,8 +44618,8 @@ function ReceivePaymentModal({
|
|
|
43027
44618
|
onClose,
|
|
43028
44619
|
onPaid
|
|
43029
44620
|
}) {
|
|
43030
|
-
const provider =
|
|
43031
|
-
const store6 =
|
|
44621
|
+
const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
|
|
44622
|
+
const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
|
|
43032
44623
|
return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
|
|
43033
44624
|
PaymentModal,
|
|
43034
44625
|
{
|
|
@@ -43046,7 +44637,7 @@ function createFinancialSettingsPlugin(options) {
|
|
|
43046
44637
|
...financialRegistries.filter((r) => RECEIVING_REGISTRIES.includes(r.id)),
|
|
43047
44638
|
...options?.extraRegistries ?? []
|
|
43048
44639
|
];
|
|
43049
|
-
const SettingsTab = () =>
|
|
44640
|
+
const SettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
|
|
43050
44641
|
title: "Pagamentos",
|
|
43051
44642
|
registries,
|
|
43052
44643
|
hostPluginId: "financial",
|
|
@@ -43103,25 +44694,27 @@ function ClientAccountPanel({
|
|
|
43103
44694
|
currency = BRL
|
|
43104
44695
|
}) {
|
|
43105
44696
|
const provider = useFinancialProvider();
|
|
43106
|
-
const [account, setAccount] =
|
|
43107
|
-
const [busy, setBusy] =
|
|
43108
|
-
const [
|
|
43109
|
-
const [comprando, setComprando] =
|
|
43110
|
-
const [buscaServico, setBuscaServico] =
|
|
43111
|
-
const [catalogo, setCatalogo] =
|
|
43112
|
-
const [escolhidos, setEscolhidos] =
|
|
43113
|
-
const [comprandoBusy, setComprandoBusy] =
|
|
43114
|
-
const [erroCompra, setErroCompra] =
|
|
43115
|
-
const load =
|
|
44697
|
+
const [account, setAccount] = React25__default.useState(null);
|
|
44698
|
+
const [busy, setBusy] = React25__default.useState(false);
|
|
44699
|
+
const [viewInvoiceId, setViewInvoiceId] = React25__default.useState(null);
|
|
44700
|
+
const [comprando, setComprando] = React25__default.useState(false);
|
|
44701
|
+
const [buscaServico, setBuscaServico] = React25__default.useState("");
|
|
44702
|
+
const [catalogo, setCatalogo] = React25__default.useState([]);
|
|
44703
|
+
const [escolhidos, setEscolhidos] = React25__default.useState([]);
|
|
44704
|
+
const [comprandoBusy, setComprandoBusy] = React25__default.useState(false);
|
|
44705
|
+
const [erroCompra, setErroCompra] = React25__default.useState(null);
|
|
44706
|
+
const load = React25__default.useCallback(async () => {
|
|
43116
44707
|
if (!personId) return;
|
|
43117
44708
|
setAccount(await provider.getClientAccount(personId));
|
|
43118
44709
|
}, [personId, provider]);
|
|
43119
|
-
|
|
44710
|
+
React25__default.useEffect(() => {
|
|
43120
44711
|
void load();
|
|
43121
44712
|
}, [load]);
|
|
44713
|
+
const viewInvoice = account?.invoices.find((i) => i.id === viewInvoiceId) ?? null;
|
|
44714
|
+
const viewEditable = invoiceOpensForEditing(viewInvoice?.status);
|
|
43122
44715
|
const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
|
|
43123
44716
|
const hasForThis = forThis.length > 0;
|
|
43124
|
-
|
|
44717
|
+
React25__default.useEffect(() => {
|
|
43125
44718
|
if (!comprando) return;
|
|
43126
44719
|
let vivo = true;
|
|
43127
44720
|
const t2 = setTimeout(async () => {
|
|
@@ -43331,7 +44924,7 @@ function ClientAccountPanel({
|
|
|
43331
44924
|
"button",
|
|
43332
44925
|
{
|
|
43333
44926
|
type: "button",
|
|
43334
|
-
onClick: () =>
|
|
44927
|
+
onClick: () => setViewInvoiceId(i.id),
|
|
43335
44928
|
className: "inline-flex shrink-0 items-center gap-1 rounded-button border border-input px-2.5 py-1.5 text-xs font-medium hover:bg-muted",
|
|
43336
44929
|
children: i.balance > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
43337
44930
|
/* @__PURE__ */ jsx(CircleDollarSign, { className: "h-3.5 w-3.5" }),
|
|
@@ -43362,17 +44955,31 @@ function ClientAccountPanel({
|
|
|
43362
44955
|
hint: clientName ?? void 0,
|
|
43363
44956
|
width: "max-w-3xl",
|
|
43364
44957
|
onClose: () => {
|
|
43365
|
-
|
|
44958
|
+
setViewInvoiceId(null);
|
|
43366
44959
|
void load();
|
|
43367
44960
|
},
|
|
43368
|
-
children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-3", children: /* @__PURE__ */ jsx(
|
|
44961
|
+
children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-3", children: viewEditable ? /* @__PURE__ */ jsx(
|
|
44962
|
+
InvoiceFormView,
|
|
44963
|
+
{
|
|
44964
|
+
embedded: true,
|
|
44965
|
+
direction: "credit",
|
|
44966
|
+
editId: viewInvoice.id,
|
|
44967
|
+
onSaved: () => {
|
|
44968
|
+
void load();
|
|
44969
|
+
},
|
|
44970
|
+
onChanged: () => {
|
|
44971
|
+
setViewInvoiceId(null);
|
|
44972
|
+
void load();
|
|
44973
|
+
}
|
|
44974
|
+
}
|
|
44975
|
+
) : /* @__PURE__ */ jsx(
|
|
43369
44976
|
InvoiceDetailView,
|
|
43370
44977
|
{
|
|
43371
44978
|
embedded: true,
|
|
43372
44979
|
invoiceId: viewInvoice.id,
|
|
43373
44980
|
direction: "credit",
|
|
43374
44981
|
onBack: () => {
|
|
43375
|
-
|
|
44982
|
+
setViewInvoiceId(null);
|
|
43376
44983
|
void load();
|
|
43377
44984
|
}
|
|
43378
44985
|
}
|
|
@@ -43382,8 +44989,8 @@ function ClientAccountPanel({
|
|
|
43382
44989
|
] });
|
|
43383
44990
|
}
|
|
43384
44991
|
function ClientAccountInline(props) {
|
|
43385
|
-
const provider =
|
|
43386
|
-
const store6 =
|
|
44992
|
+
const provider = React25__default.useMemo(() => createSafeFinancialProvider(), []);
|
|
44993
|
+
const store6 = React25__default.useMemo(() => createFinancialStore(provider), [provider]);
|
|
43387
44994
|
return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(ClientAccountPanel, { ...props }) });
|
|
43388
44995
|
}
|
|
43389
44996
|
function ClientAccountModal({
|
|
@@ -43412,30 +45019,6 @@ function createSafeFinancialProvider() {
|
|
|
43412
45019
|
);
|
|
43413
45020
|
}
|
|
43414
45021
|
var ACCOUNTS_ROUTE = "/registry/accounts";
|
|
43415
|
-
var DEFAULT_LABELS = {
|
|
43416
|
-
pageTitle: "Financial",
|
|
43417
|
-
pageSubtitle: "Financial overview and cash flow management",
|
|
43418
|
-
summary: "Summary",
|
|
43419
|
-
payables: "Accounts Payable",
|
|
43420
|
-
payablesNew: "New",
|
|
43421
|
-
payablesList: "List",
|
|
43422
|
-
payablesRecurring: "Recurring Expenses",
|
|
43423
|
-
receivables: "Accounts Receivable",
|
|
43424
|
-
receivablesNew: "New",
|
|
43425
|
-
receivablesList: "List",
|
|
43426
|
-
cashRegisters: "Cash Registers",
|
|
43427
|
-
statements: "Statements",
|
|
43428
|
-
commissions: "Commissions",
|
|
43429
|
-
commissionsOverview: "Overview",
|
|
43430
|
-
commissionsRules: "Rules",
|
|
43431
|
-
cards: "Cards",
|
|
43432
|
-
goals: "Metas",
|
|
43433
|
-
giftCards: "Gift Cards",
|
|
43434
|
-
cardsOverview: "Overview",
|
|
43435
|
-
cardsReconciliation: "Reconciliation",
|
|
43436
|
-
notasFiscais: "Fiscal Invoices"
|
|
43437
|
-
};
|
|
43438
|
-
var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
|
|
43439
45022
|
var FINANCIAL_NAV_FEATURES = {
|
|
43440
45023
|
reconciliation: "fin_reconciliation",
|
|
43441
45024
|
cards: "fin_cards",
|
|
@@ -43445,55 +45028,6 @@ var FINANCIAL_NAV_FEATURES = {
|
|
|
43445
45028
|
payables: "fin_payables",
|
|
43446
45029
|
"cash-registers": "fin_cash_registers"
|
|
43447
45030
|
};
|
|
43448
|
-
var DEFAULT_ITEM_TYPES = [
|
|
43449
|
-
{ value: "service", label: "Service", icon: "Briefcase" },
|
|
43450
|
-
{ value: "product", label: "Product", icon: "Package" },
|
|
43451
|
-
{ value: "other", label: "Other", icon: "MoreHorizontal" }
|
|
43452
|
-
];
|
|
43453
|
-
function resolveConfig(options) {
|
|
43454
|
-
return {
|
|
43455
|
-
modules: {
|
|
43456
|
-
payables: options?.modules?.payables !== false,
|
|
43457
|
-
receivables: options?.modules?.receivables !== false,
|
|
43458
|
-
cashRegisters: options?.modules?.cashRegisters !== false,
|
|
43459
|
-
statements: options?.modules?.statements !== false,
|
|
43460
|
-
// Era opt-in porque a comissão era um cadastro que nada lia: o item de
|
|
43461
|
-
// menu só podia levar a uma tela vazia. Desde 009 existe apuração e desde
|
|
43462
|
-
// 012 existe borderô, e a tela mostra o que eles produziram — então o
|
|
43463
|
-
// motivo do opt-in acabou junto com a tela vazia.
|
|
43464
|
-
commissions: options?.modules?.commissions !== false,
|
|
43465
|
-
cards: options?.modules?.cards !== false,
|
|
43466
|
-
goals: options?.modules?.goals !== false,
|
|
43467
|
-
// Ligado como as outras: quando a loja não está instalada a própria tela
|
|
43468
|
-
// diz isso, então o item de menu nunca leva a um beco.
|
|
43469
|
-
giftCards: options?.modules?.giftCards !== false,
|
|
43470
|
-
// Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
|
|
43471
|
-
reconciliation: options?.modules?.reconciliation === true,
|
|
43472
|
-
// Opt-in: only shown when an app explicitly enables fiscal-invoice tracking.
|
|
43473
|
-
// LIGADA como as outras (`!== false`), e não opt-in. Estava em
|
|
43474
|
-
// `=== true`: quem não passasse a opção nunca via a tela, e o V1 tem
|
|
43475
|
-
// Notas Fiscais no menu do Financeiro desde sempre. A tela se explica
|
|
43476
|
-
// sozinha quando o provider não expõe notas, então ligá-la não deixa
|
|
43477
|
-
// ninguém diante de um beco.
|
|
43478
|
-
notasFiscais: options?.modules?.notasFiscais !== false
|
|
43479
|
-
},
|
|
43480
|
-
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
43481
|
-
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
43482
|
-
moduleNav: options?.moduleNav,
|
|
43483
|
-
itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
|
|
43484
|
-
enableServiceExecution: options?.enableServiceExecution ?? false,
|
|
43485
|
-
contactEntity: {
|
|
43486
|
-
archetypeKind: options?.contactEntity?.archetypeKind ?? "client",
|
|
43487
|
-
label: options?.contactEntity?.label ?? "Contact"
|
|
43488
|
-
},
|
|
43489
|
-
locations: options?.locations ?? [],
|
|
43490
|
-
entityLookups: options?.entityLookups ?? {},
|
|
43491
|
-
contactLookup: options?.contactLookup,
|
|
43492
|
-
fiscalProvider: options?.fiscalProvider,
|
|
43493
|
-
onBookingClick: options?.onBookingClick,
|
|
43494
|
-
navFeatureMap: { ...options?.navFeatureMap }
|
|
43495
|
-
};
|
|
43496
|
-
}
|
|
43497
45031
|
function createFinancialPlugin(options) {
|
|
43498
45032
|
const config = resolveConfig(options);
|
|
43499
45033
|
registerTranslations(financialLocales);
|
|
@@ -43505,11 +45039,11 @@ function createFinancialPlugin(options) {
|
|
|
43505
45039
|
const settingsRegistries = registries.filter((r) => r.id !== "bank-accounts");
|
|
43506
45040
|
const AccountsRegistryPage = createCrudPage(financialAccountEntity, { feature: "financial" });
|
|
43507
45041
|
AccountsRegistryPage.displayName = "FinancialAccountsRegistryPage";
|
|
43508
|
-
const PersonStatementBound = (props) =>
|
|
45042
|
+
const PersonStatementBound = (props) => React25__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
|
|
43509
45043
|
PersonStatementBound.displayName = "FinancialPersonStatement";
|
|
43510
|
-
const SupplierFinancialSettingsBound = (props) =>
|
|
45044
|
+
const SupplierFinancialSettingsBound = (props) => React25__default.createElement(SupplierFinancialSettings, { person: props.item, provider });
|
|
43511
45045
|
SupplierFinancialSettingsBound.displayName = "FinancialSupplierSettings";
|
|
43512
|
-
const PageComponent = () =>
|
|
45046
|
+
const PageComponent = () => React25__default.createElement(FinancialPage, { config, provider, store: store6, registries });
|
|
43513
45047
|
const AgentMount = createFinancialAgentMount({ config, provider });
|
|
43514
45048
|
return {
|
|
43515
45049
|
id: "financial",
|
|
@@ -43567,7 +45101,7 @@ function createFinancialPlugin(options) {
|
|
|
43567
45101
|
// tem — lá é um ícone no cabeçalho, de qualquer tela.
|
|
43568
45102
|
headerControls: config.modules.cashRegisters ? [{
|
|
43569
45103
|
id: "financial.cash-session",
|
|
43570
|
-
component: (() =>
|
|
45104
|
+
component: (() => React25__default.createElement(CashSessionHeaderControl, { config, provider, store: store6 })),
|
|
43571
45105
|
order: 90,
|
|
43572
45106
|
permission: { feature: "financial", action: "read" }
|
|
43573
45107
|
}] : [],
|
|
@@ -43798,9 +45332,9 @@ function createFinancialPlugin(options) {
|
|
|
43798
45332
|
// lado, e a corrida de "Plugins" não é partida ao meio.
|
|
43799
45333
|
group: "Financeiro",
|
|
43800
45334
|
component: (() => {
|
|
43801
|
-
const FinancialSettingsTab = () =>
|
|
45335
|
+
const FinancialSettingsTab = () => React25__default.createElement(PluginSettingsPanel, {
|
|
43802
45336
|
title: "Financial Settings",
|
|
43803
|
-
generalSettings:
|
|
45337
|
+
generalSettings: React25__default.createElement(FinancialGeneralSettings, { config }),
|
|
43804
45338
|
registries: settingsRegistries,
|
|
43805
45339
|
hostPluginId: "financial",
|
|
43806
45340
|
routeBase: "/settings/financial"
|
|
@@ -43825,7 +45359,7 @@ function createFinancialPlugin(options) {
|
|
|
43825
45359
|
icon: "HandCoins",
|
|
43826
45360
|
group: "Financeiro",
|
|
43827
45361
|
component: (() => {
|
|
43828
|
-
const CommissionRulesTab = () =>
|
|
45362
|
+
const CommissionRulesTab = () => React25__default.createElement(CommissionRulesView, { provider });
|
|
43829
45363
|
CommissionRulesTab.displayName = "CommissionRulesTab";
|
|
43830
45364
|
return CommissionRulesTab;
|
|
43831
45365
|
})(),
|