@fayz-ai/plugin-financial 0.20.0 → 0.21.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/CashSessionReviewModal.d.ts.map +1 -1
- package/dist/components/InstallmentRow.d.ts.map +1 -1
- package/dist/components/InvoiceAttachmentsModal.d.ts.map +1 -1
- package/dist/components/ReceiptPicker.d.ts +3 -1
- package/dist/components/ReceiptPicker.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +253 -56
- package/dist/index.js.map +1 -1
- package/dist/lib/routes.d.ts +7 -0
- package/dist/lib/routes.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 +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React25 from 'react';
|
|
2
2
|
import React25__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
3
3
|
import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, getSupabaseClientOptional, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useUnitMode, useDataChanged, countByTenant, tl, resolveDataProvider, useActiveUnitId, 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, ChoiceSelect,
|
|
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, ChoiceSelect, ModalFooter, Hint, PageTabs, Tooltip, TooltipTrigger, TooltipContent, Checkbox, Popover, PopoverTrigger, PopoverContent, openPrintWindow, printDocument, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, SegmentedControl, RequiredFilter, FilterMenuRow, ChoiceList, 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, useUnitFilter, useLimitGuard, usePermissionOptional, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, adminNavigateTo, OrderFormAddItemStep, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, useChatStore, revealPage, useAuth, GetStarted, resolveEntityRoute, PermissionGate, createOneShotAsk, askAI, requestChatUpload, toInlineFile, shrinkForVision } from '@fayz-ai/admin';
|
|
6
|
+
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useUnitFilter, useLimitGuard, usePermissionOptional, useLookupCreate, OrderFormSkeleton, WidgetSlot, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, adminNavigateTo, OrderFormAddItemStep, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, useChatStore, revealPage, useAuth, GetStarted, resolveEntityRoute, PermissionGate, createOneShotAsk, askAI, requestChatUpload, toInlineFile, shrinkForVision } from '@fayz-ai/admin';
|
|
7
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, Image, UploadCloud, Calendar, User, Unlink, ChevronDown, Eye, Inbox, Clock, ClipboardCheck, BookOpen, Info, FolderOpen, Folder, RotateCcw, Settings, ArrowLeft, ChevronLeft, Calculator, Square, Play, ChevronUp, Printer, CalendarClock, BadgeCheck, Target, 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';
|
|
@@ -1874,6 +1874,7 @@ function InvoiceAttachmentsModal({ invoiceId, onClose, onChanged }) {
|
|
|
1874
1874
|
const [uploading, setUploading] = useState(0);
|
|
1875
1875
|
const [over, setOver] = useState(false);
|
|
1876
1876
|
const [preview, setPreview] = useState(null);
|
|
1877
|
+
const [added, setAdded] = useState(0);
|
|
1877
1878
|
async function load() {
|
|
1878
1879
|
try {
|
|
1879
1880
|
setItems(await (provider.getInvoiceAttachments?.(invoiceId) ?? Promise.resolve([])));
|
|
@@ -1896,6 +1897,7 @@ function InvoiceAttachmentsModal({ invoiceId, onClose, onChanged }) {
|
|
|
1896
1897
|
for (const file of ok) {
|
|
1897
1898
|
try {
|
|
1898
1899
|
await provider.addInvoiceAttachment(invoiceId, file);
|
|
1900
|
+
setAdded((n) => n + 1);
|
|
1899
1901
|
} catch (e) {
|
|
1900
1902
|
toast.error(`${file.name}: ${e?.message ?? String(e)}`);
|
|
1901
1903
|
} finally {
|
|
@@ -1988,7 +1990,8 @@ function InvoiceAttachmentsModal({ invoiceId, onClose, onChanged }) {
|
|
|
1988
1990
|
}
|
|
1989
1991
|
}
|
|
1990
1992
|
)
|
|
1991
|
-
] })
|
|
1993
|
+
] }),
|
|
1994
|
+
/* @__PURE__ */ jsx(ModalFooter, { children: /* @__PURE__ */ jsx(Button, { onClick: onClose, disabled: uploading > 0, variant: added > 0 ? "default" : "outline", children: uploading > 0 ? "Enviando\u2026" : added > 0 ? "Concluir" : "Fechar" }) })
|
|
1992
1995
|
] }) }) });
|
|
1993
1996
|
}
|
|
1994
1997
|
function AttachmentPreview({ items, index, onIndex, onBack }) {
|
|
@@ -2489,14 +2492,17 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2489
2492
|
const printSelected = useCallback((rows) => {
|
|
2490
2493
|
const safe = (value) => String(value ?? "").replace(/[&<>"']/g, (character) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[character] ?? character);
|
|
2491
2494
|
const body = rows.map((row) => `<tr><td>${safe(row.dueDate)}</td><td>${safe(row.contactName)}<small>${safe(row.itemsSummary)}</small></td><td>${safe(formatCurrency(row.totalAmount, currency))}</td><td>${row.installmentNumber ?? 1}/${row.totalInstallments ?? 1}</td></tr>`).join("");
|
|
2492
|
-
const
|
|
2495
|
+
const title = direction === "debit" ? "Contas a pagar" : "Contas a receber";
|
|
2496
|
+
const popup = printDocument({
|
|
2497
|
+
title,
|
|
2498
|
+
css: "body{font:12px sans-serif}table{width:100%;border-collapse:collapse}th,td{border-bottom:1px solid #ddd;padding:8px;text-align:left}small{display:block;color:#666;margin-top:2px}",
|
|
2499
|
+
body: `<h1>${title}</h1><table><thead><tr><th>Vencimento</th><th>Descri\xE7\xE3o / itens</th><th>Valor</th><th>Parcela</th></tr></thead><tbody>${body}</tbody></table>`
|
|
2500
|
+
});
|
|
2493
2501
|
if (!popup) {
|
|
2494
2502
|
toast.error(t2("financial.invoice.printBlocked"));
|
|
2495
2503
|
return;
|
|
2496
2504
|
}
|
|
2497
2505
|
popup.opener = null;
|
|
2498
|
-
popup.document.write(`<html><head><title>${direction === "debit" ? "Contas a pagar" : "Contas a receber"}</title><style>body{font:12px sans-serif;padding:24px}table{width:100%;border-collapse:collapse}th,td{border-bottom:1px solid #ddd;padding:8px;text-align:left}small{display:block;color:#666;margin-top:2px}</style></head><body><h1>${direction === "debit" ? "Contas a pagar" : "Contas a receber"}</h1><table><thead><tr><th>Vencimento</th><th>Descri\xE7\xE3o / itens</th><th>Valor</th><th>Parcela</th></tr></thead><tbody>${body}</tbody></table><script>window.print();window.close()</script></body></html>`);
|
|
2499
|
-
popup.document.close();
|
|
2500
2506
|
}, [currency, direction, t2]);
|
|
2501
2507
|
const bulkActions = useMemo(() => {
|
|
2502
2508
|
const actions = [
|
|
@@ -2885,6 +2891,9 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
2885
2891
|
var ACCOUNTS_ROUTE = "/registry/accounts";
|
|
2886
2892
|
var CHART_OF_ACCOUNTS_ROUTE = "/settings/financial/chart-of-accounts";
|
|
2887
2893
|
var COST_CENTERS_ROUTE = "/settings/financial/cost-centers";
|
|
2894
|
+
var FINANCIAL_INVOICE_ZONES = {
|
|
2895
|
+
headerActions: "financial.invoice.header.actions"
|
|
2896
|
+
};
|
|
2888
2897
|
function useWritableUnits(fallback = []) {
|
|
2889
2898
|
const unitMode = useUnitMode();
|
|
2890
2899
|
const activeUnitId = useActiveUnitId() ?? void 0;
|
|
@@ -4055,7 +4064,7 @@ function createFinancialAgentMount(deps) {
|
|
|
4055
4064
|
FinancialAgentMount.displayName = "FinancialAgentMount";
|
|
4056
4065
|
return FinancialAgentMount;
|
|
4057
4066
|
}
|
|
4058
|
-
function useReceiptPicker(onPick) {
|
|
4067
|
+
function useReceiptPicker(onPick, onPickDocument) {
|
|
4059
4068
|
const t2 = useTranslation();
|
|
4060
4069
|
const ref = useRef(null);
|
|
4061
4070
|
const input = /* @__PURE__ */ jsx(
|
|
@@ -4063,13 +4072,17 @@ function useReceiptPicker(onPick) {
|
|
|
4063
4072
|
{
|
|
4064
4073
|
ref,
|
|
4065
4074
|
type: "file",
|
|
4066
|
-
accept: "image/*",
|
|
4067
|
-
capture: "environment",
|
|
4075
|
+
accept: onPickDocument ? "image/*,application/pdf" : "image/*",
|
|
4076
|
+
capture: onPickDocument ? void 0 : "environment",
|
|
4068
4077
|
className: "hidden",
|
|
4069
4078
|
onChange: (e) => {
|
|
4070
4079
|
const file = e.target.files?.[0];
|
|
4071
4080
|
e.target.value = "";
|
|
4072
4081
|
if (!file) return;
|
|
4082
|
+
if (onPickDocument && file.type === "application/pdf") {
|
|
4083
|
+
onPickDocument(file);
|
|
4084
|
+
return;
|
|
4085
|
+
}
|
|
4073
4086
|
if (!file.type.startsWith("image/")) {
|
|
4074
4087
|
toast.error(t2("financial.quickTx.receiptInvalid"));
|
|
4075
4088
|
return;
|
|
@@ -4899,7 +4912,8 @@ function InstallmentRow({
|
|
|
4899
4912
|
] })
|
|
4900
4913
|
] }),
|
|
4901
4914
|
/* @__PURE__ */ jsx("div", { className: "w-44 shrink-0", onClick: stop, children: source }),
|
|
4902
|
-
(actions || onClick) &&
|
|
4915
|
+
(actions || onClick) && // Min width fits attach + "Pagar": rows where Pay is absent (unsaved, paid) keep the source column aligned.
|
|
4916
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-32 shrink-0 items-center justify-end gap-1.5", onClick: stop, children: [
|
|
4903
4917
|
actions,
|
|
4904
4918
|
onClick && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
4905
4919
|
] })
|
|
@@ -4938,10 +4952,10 @@ function InstallmentReceiptButton({ onClick, title, attached, pending: pending2,
|
|
|
4938
4952
|
onClick,
|
|
4939
4953
|
title,
|
|
4940
4954
|
"aria-label": title,
|
|
4941
|
-
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" : ""}`,
|
|
4955
|
+
className: `relative inline-flex shrink-0 items-center gap-1 rounded-lg border bg-card px-2.5 py-1.5 text-xs font-medium shadow-button transition-colors hover:bg-muted active:shadow-button-inset ${attached ? "text-success" : pending2 ? "text-primary" : ""}`,
|
|
4942
4956
|
children: [
|
|
4943
4957
|
/* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3" }),
|
|
4944
|
-
!!count && /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: count })
|
|
4958
|
+
!!count && /* @__PURE__ */ jsx("span", { className: "absolute -right-1.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-primary-foreground tabular-nums", children: count })
|
|
4945
4959
|
]
|
|
4946
4960
|
}
|
|
4947
4961
|
);
|
|
@@ -5110,18 +5124,18 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5110
5124
|
const showUnitField = unitMode ? reachableUnits.length > 1 : unitChoices.length > 1;
|
|
5111
5125
|
const [giftCardsEnabled, setGiftCardsEnabled] = useState(false);
|
|
5112
5126
|
useEffect(() => {
|
|
5113
|
-
let
|
|
5127
|
+
let cancelled2 = false;
|
|
5114
5128
|
if (direction !== "credit" || !provider.giftCardsEnabled) {
|
|
5115
5129
|
setGiftCardsEnabled(false);
|
|
5116
5130
|
return;
|
|
5117
5131
|
}
|
|
5118
5132
|
void provider.giftCardsEnabled().then((enabled) => {
|
|
5119
|
-
if (!
|
|
5133
|
+
if (!cancelled2) setGiftCardsEnabled(enabled);
|
|
5120
5134
|
}).catch(() => {
|
|
5121
|
-
if (!
|
|
5135
|
+
if (!cancelled2) setGiftCardsEnabled(false);
|
|
5122
5136
|
});
|
|
5123
5137
|
return () => {
|
|
5124
|
-
|
|
5138
|
+
cancelled2 = true;
|
|
5125
5139
|
};
|
|
5126
5140
|
}, [direction, provider]);
|
|
5127
5141
|
const giftCardLookup = useMemo(() => {
|
|
@@ -5193,20 +5207,29 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5193
5207
|
const canManageFinancial = can("financial", "manage");
|
|
5194
5208
|
const isEdit = !!editId;
|
|
5195
5209
|
const [attachmentsOpen, setAttachmentsOpen] = useState(false);
|
|
5210
|
+
const [attachmentCount, setAttachmentCount] = useState(0);
|
|
5211
|
+
const loadAttachmentCount = React25__default.useCallback(() => {
|
|
5212
|
+
if (!editId || !provider.getInvoiceAttachments) return;
|
|
5213
|
+
provider.getInvoiceAttachments(editId).then((list) => setAttachmentCount(list.length)).catch(() => {
|
|
5214
|
+
});
|
|
5215
|
+
}, [editId, provider]);
|
|
5216
|
+
useEffect(() => {
|
|
5217
|
+
loadAttachmentCount();
|
|
5218
|
+
}, [loadAttachmentCount]);
|
|
5196
5219
|
useEffect(() => {
|
|
5197
5220
|
void fetchBankAccounts();
|
|
5198
5221
|
void fetchPaymentMethods();
|
|
5199
5222
|
}, []);
|
|
5200
5223
|
useEffect(() => {
|
|
5201
|
-
let
|
|
5224
|
+
let cancelled2 = false;
|
|
5202
5225
|
void Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
|
|
5203
|
-
if (
|
|
5226
|
+
if (cancelled2) return;
|
|
5204
5227
|
setAccounts(accountRows);
|
|
5205
5228
|
setCostCenters(costCenterRows);
|
|
5206
5229
|
}).catch(() => {
|
|
5207
5230
|
});
|
|
5208
5231
|
return () => {
|
|
5209
|
-
|
|
5232
|
+
cancelled2 = true;
|
|
5210
5233
|
};
|
|
5211
5234
|
}, [provider]);
|
|
5212
5235
|
const contactLookup = useMemo(
|
|
@@ -5252,6 +5275,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5252
5275
|
const [expandedItemId, setExpandedItemId] = useState(null);
|
|
5253
5276
|
const [addingItem, setAddingItem] = useState(false);
|
|
5254
5277
|
const [installments, setInstallments] = useState([]);
|
|
5278
|
+
const [cancelled, setCancelled] = useState(false);
|
|
5279
|
+
const [savedTotal, setSavedTotal] = useState(null);
|
|
5255
5280
|
const [savedMovements, setSavedMovements] = useState([]);
|
|
5256
5281
|
const [payingMovement, setPayingMovement] = useState(null);
|
|
5257
5282
|
const [pendingReceipt, setPendingReceipt] = useState();
|
|
@@ -5262,6 +5287,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5262
5287
|
});
|
|
5263
5288
|
const [pendingContact, setPendingContact] = useState(null);
|
|
5264
5289
|
const [receiptTarget, setReceiptTarget] = useState(null);
|
|
5290
|
+
const [pendingDocs, setPendingDocs] = useState([]);
|
|
5265
5291
|
const receiptPicker = useReceiptPicker((dataUrl) => {
|
|
5266
5292
|
const i = receiptTarget;
|
|
5267
5293
|
setReceiptTarget(null);
|
|
@@ -5274,7 +5300,10 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5274
5300
|
return;
|
|
5275
5301
|
}
|
|
5276
5302
|
setInstallments((prev) => prev.map((inst, idx) => idx === i ? { ...inst, receiptUrl: dataUrl } : inst));
|
|
5277
|
-
})
|
|
5303
|
+
}, provider.addInvoiceAttachment ? (file) => {
|
|
5304
|
+
setReceiptTarget(null);
|
|
5305
|
+
setPendingDocs((prev) => [...prev, file]);
|
|
5306
|
+
} : void 0);
|
|
5278
5307
|
const [saving, setSaving] = useState(false);
|
|
5279
5308
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
5280
5309
|
const [confirmCancel, setConfirmCancel] = useState(false);
|
|
@@ -5333,6 +5362,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5333
5362
|
setInvoiceDate(invoice.invoiceDate);
|
|
5334
5363
|
setSavedInvoiceDate(invoice.invoiceDate);
|
|
5335
5364
|
setSavedContactId(invoice.contactId);
|
|
5365
|
+
setCancelled(invoice.status === "cancelled");
|
|
5366
|
+
setSavedTotal(invoice.totalAmount);
|
|
5336
5367
|
setFiscalNumber(String(invoice.metadata?.fiscalNumber ?? ""));
|
|
5337
5368
|
setObservations(invoice.observations ?? "");
|
|
5338
5369
|
const invoiceItems = await provider.getInvoiceItems(editId);
|
|
@@ -5380,7 +5411,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5380
5411
|
return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
|
|
5381
5412
|
}, 0);
|
|
5382
5413
|
const itemsDirty = isEdit && loaded && itemsKey(items) !== savedItemsKey;
|
|
5383
|
-
const scheduleTotal = isEdit && savedMovements.length > 0 && !itemsDirty ? Math.round(savedMovements.reduce((sum, m) => sum + m.amount, 0) * 100) / 100 : totalAmount;
|
|
5414
|
+
const scheduleTotal = isEdit && savedMovements.length > 0 && !itemsDirty ? savedTotal ?? Math.round(savedMovements.reduce((sum, m) => sum + m.amount, 0) * 100) / 100 : totalAmount;
|
|
5384
5415
|
const prevItemCount = useRef(items.length);
|
|
5385
5416
|
useEffect(() => {
|
|
5386
5417
|
if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
|
|
@@ -5608,6 +5639,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5608
5639
|
if (isEdit && editId) {
|
|
5609
5640
|
if (canEditItems && provider.replaceInvoiceItems && itemsDirty) {
|
|
5610
5641
|
await provider.replaceInvoiceItems(editId, items.map(({ _id, ...rest }) => rest));
|
|
5642
|
+
setSavedTotal(Math.round(totalAmount * 100) / 100);
|
|
5611
5643
|
}
|
|
5612
5644
|
await provider.updateInvoice(editId, {
|
|
5613
5645
|
unitId: unitId || void 0,
|
|
@@ -5675,6 +5707,16 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5675
5707
|
});
|
|
5676
5708
|
}
|
|
5677
5709
|
}
|
|
5710
|
+
if (pendingDocs.length > 0 && provider.addInvoiceAttachment) {
|
|
5711
|
+
const failed = [];
|
|
5712
|
+
for (const file of pendingDocs) {
|
|
5713
|
+
await provider.addInvoiceAttachment(invoice.id, file).catch(() => {
|
|
5714
|
+
failed.push(file.name);
|
|
5715
|
+
});
|
|
5716
|
+
}
|
|
5717
|
+
setPendingDocs([]);
|
|
5718
|
+
if (failed.length) toast.error(t2("financial.invoiceForm.attachFailed"), { description: failed.join(", ") });
|
|
5719
|
+
}
|
|
5678
5720
|
createdId.current = invoice.id;
|
|
5679
5721
|
if (!opts?.stay) onSaved?.(invoice.id);
|
|
5680
5722
|
savedId = invoice.id;
|
|
@@ -5747,23 +5789,26 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5747
5789
|
const subtitle = isEdit ? t2("financial.invoiceForm.editing") : t2("financial.invoiceForm.creating");
|
|
5748
5790
|
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?.() });
|
|
5749
5791
|
const money4 = (v) => formatCurrency(v, currency);
|
|
5750
|
-
const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "
|
|
5751
|
-
/* @__PURE__ */ jsx(
|
|
5752
|
-
|
|
5753
|
-
"
|
|
5754
|
-
{
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5792
|
+
const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5793
|
+
/* @__PURE__ */ jsx(WidgetSlot, { zone: FINANCIAL_INVOICE_ZONES.headerActions, invoiceId: editId, direction, cancelled }),
|
|
5794
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
|
|
5795
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
|
|
5796
|
+
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(
|
|
5797
|
+
"button",
|
|
5798
|
+
{
|
|
5799
|
+
onClick: () => {
|
|
5800
|
+
setConfirmCancel(true);
|
|
5801
|
+
setMenuOpen(false);
|
|
5802
|
+
},
|
|
5803
|
+
className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive transition-colors hover:bg-destructive/10",
|
|
5804
|
+
children: [
|
|
5805
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
5806
|
+
" ",
|
|
5807
|
+
t2("financial.invoiceForm.cancelInvoice")
|
|
5808
|
+
]
|
|
5809
|
+
}
|
|
5810
|
+
) })
|
|
5811
|
+
] })
|
|
5767
5812
|
] }) : null;
|
|
5768
5813
|
const paidSoFar = savedMovements.reduce((sum, m) => sum + (m.paidAmount ?? 0), 0);
|
|
5769
5814
|
const hasPayments = paidSoFar > 0;
|
|
@@ -5829,7 +5874,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5829
5874
|
(canAttach || isEdit) && /* @__PURE__ */ jsx(
|
|
5830
5875
|
InstallmentReceiptButton,
|
|
5831
5876
|
{
|
|
5832
|
-
attached: !!inst.receiptUrl,
|
|
5877
|
+
attached: !!inst.receiptUrl || isEdit && attachmentCount > 0,
|
|
5878
|
+
count: isEdit ? attachmentCount : void 0,
|
|
5833
5879
|
pending: !inst.receiptUrl && !!pendingReceipt,
|
|
5834
5880
|
title: !inst.receiptUrl && pendingReceipt ? t2("financial.receipt.attachRead") : t2("financial.quickTx.receiptCapture"),
|
|
5835
5881
|
onClick: () => {
|
|
@@ -5883,7 +5929,25 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
5883
5929
|
)
|
|
5884
5930
|
] })
|
|
5885
5931
|
] }, inst._id);
|
|
5886
|
-
}) })
|
|
5932
|
+
}) }),
|
|
5933
|
+
pendingDocs.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: pendingDocs.map((file, idx) => /* @__PURE__ */ jsxs("span", { className: "inline-flex max-w-full items-center gap-1.5 rounded-md border bg-muted/30 px-2 py-1 text-xs", children: [
|
|
5934
|
+
/* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
5935
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: file.name }),
|
|
5936
|
+
/* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
|
|
5937
|
+
"\xB7 ",
|
|
5938
|
+
t2("financial.invoiceForm.attachOnSave")
|
|
5939
|
+
] }),
|
|
5940
|
+
/* @__PURE__ */ jsx(
|
|
5941
|
+
"button",
|
|
5942
|
+
{
|
|
5943
|
+
type: "button",
|
|
5944
|
+
onClick: () => setPendingDocs((prev) => prev.filter((_, j) => j !== idx)),
|
|
5945
|
+
"aria-label": t2("financial.quickTx.receiptRemove"),
|
|
5946
|
+
className: "flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
5947
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
5948
|
+
}
|
|
5949
|
+
)
|
|
5950
|
+
] }, `${file.name}-${idx}`)) })
|
|
5887
5951
|
] }) }) })
|
|
5888
5952
|
);
|
|
5889
5953
|
const contactSection = /* @__PURE__ */ jsx("div", { ...tourAnchor("financial.contact"), children: /* @__PURE__ */ jsxs(OrderFormBlock, { title: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"), children: [
|
|
@@ -6178,7 +6242,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
6178
6242
|
}
|
|
6179
6243
|
),
|
|
6180
6244
|
receiptPicker.input,
|
|
6181
|
-
attachmentsOpen && editId && /* @__PURE__ */ jsx(InvoiceAttachmentsModal, { invoiceId: editId, onClose: () => setAttachmentsOpen(false) }),
|
|
6245
|
+
attachmentsOpen && editId && /* @__PURE__ */ jsx(InvoiceAttachmentsModal, { invoiceId: editId, onClose: () => setAttachmentsOpen(false), onChanged: loadAttachmentCount }),
|
|
6182
6246
|
payingMovement && /* @__PURE__ */ jsx(
|
|
6183
6247
|
PaymentModal,
|
|
6184
6248
|
{
|
|
@@ -6646,11 +6710,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6646
6710
|
...totalRemaining > 0 ? [{ label: t2("financial.invoice.balance"), value: formatCurrency(totalRemaining, currency) }] : []
|
|
6647
6711
|
];
|
|
6648
6712
|
direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
|
|
6649
|
-
const actions = (
|
|
6650
|
-
|
|
6651
|
-
// This was an outline button beside a hand-rolled popup — its own useRef
|
|
6652
|
-
// outside-click listener and an inline `style={{animation}}` — sitting next
|
|
6653
|
-
// to a Radix Dropdown that already exists.
|
|
6713
|
+
const actions = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
6714
|
+
/* @__PURE__ */ jsx(WidgetSlot, { zone: FINANCIAL_INVOICE_ZONES.headerActions, invoiceId: invoice.id, direction, cancelled }),
|
|
6654
6715
|
/* @__PURE__ */ jsx(
|
|
6655
6716
|
PageActions,
|
|
6656
6717
|
{
|
|
@@ -6660,7 +6721,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
6660
6721
|
] : []
|
|
6661
6722
|
}
|
|
6662
6723
|
)
|
|
6663
|
-
);
|
|
6724
|
+
] });
|
|
6664
6725
|
const sideBlocks = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6665
6726
|
invoice.bookingStartsAt && /* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.booking"), children: /* @__PURE__ */ jsxs(
|
|
6666
6727
|
"button",
|
|
@@ -7115,12 +7176,16 @@ function CashSessionReviewModal({ sessionId, open, onOpenChange, mode, onReviewe
|
|
|
7115
7176
|
}
|
|
7116
7177
|
function handlePrint() {
|
|
7117
7178
|
if (!session) return;
|
|
7118
|
-
const popup =
|
|
7179
|
+
const popup = openPrintWindow("width=720,height=900");
|
|
7119
7180
|
if (!popup) return;
|
|
7120
7181
|
const title = session.bankAccountName ?? t2("financial.cash.title");
|
|
7121
7182
|
const rows = (movements ?? []).map((m) => `<tr><td>${m.paymentDate ? new Date(m.paymentDate).toLocaleString() : "\u2014"}</td><td>${m.method ? t2(`financial.method.${m.method}`) : "\u2014"}</td><td style="text-align:right">${m.direction === "credit" ? "+" : "-"}${formatCurrency(m.paidAmount, currency)}</td></tr>`).join("");
|
|
7122
|
-
|
|
7123
|
-
|
|
7183
|
+
printDocument({
|
|
7184
|
+
title,
|
|
7185
|
+
css: "body{font:12px sans-serif}table{width:100%;border-collapse:collapse;margin-top:16px}th,td{border-bottom:1px solid #ddd;padding:8px;text-align:left}h1{font-size:16px;margin-bottom:8px}p{margin:2px 0}",
|
|
7186
|
+
body: `<h1>${title}</h1><p>${t2("financial.cash.opened")}: ${new Date(session.openedAt).toLocaleString()}${session.openedByName ? ` ${t2("financial.cash.by")} ${session.openedByName}` : ""}</p>` + (session.closedAt ? `<p>${t2("financial.cash.closeLabel")}: ${new Date(session.closedAt).toLocaleString()}${session.closedByName ? ` ${t2("financial.cash.by")} ${session.closedByName}` : ""}</p>` : "") + `<p>${t2("financial.cash.openingBalance")}: ${formatCurrency(session.openingBalance, currency)}</p><p>${t2("financial.cash.finalValue")}: ${formatCurrency(session.closingBalance ?? 0, currency)}</p><p>${t2("financial.cash.difference")}: ${(session.difference ?? 0) >= 0 ? "+" : ""}${formatCurrency(session.difference ?? 0, currency)}</p><table><thead><tr><th>${t2("financial.cash.opened")}</th><th>${t2("financial.cash.method")}</th><th style="text-align:right">${t2("financial.cash.finalValue")}</th></tr></thead><tbody>${rows}</tbody></table>`,
|
|
7187
|
+
target: popup
|
|
7188
|
+
});
|
|
7124
7189
|
}
|
|
7125
7190
|
return /* @__PURE__ */ jsx(Modal, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", noPadding: true, className: "gap-0", children: [
|
|
7126
7191
|
/* @__PURE__ */ jsxs(ModalHeader, { className: "border-b px-4 py-3", children: [
|
|
@@ -18470,6 +18535,8 @@ var en = {
|
|
|
18470
18535
|
"financial.invoiceForm.scheduleSourceHint": "Method and account are set on each due date.",
|
|
18471
18536
|
"financial.invoiceForm.paidNeedsMethod": "Tell us the payment method and the account",
|
|
18472
18537
|
"financial.invoiceForm.willBePaid": "This installment will be settled",
|
|
18538
|
+
"financial.invoiceForm.attachFailed": "The bill was saved, but an attachment did not upload",
|
|
18539
|
+
"financial.invoiceForm.attachOnSave": "attached on save",
|
|
18473
18540
|
"financial.invoiceForm.clear": "Clear",
|
|
18474
18541
|
"financial.invoiceForm.manageAccounts": "Manage accounts",
|
|
18475
18542
|
"financial.invoiceForm.manageChart": "Manage chart of accounts",
|
|
@@ -19238,11 +19305,11 @@ var en = {
|
|
|
19238
19305
|
"financial.quickTx.attachment": "Attachment",
|
|
19239
19306
|
"financial.quickTx.attachmentSoon": "Attachments coming soon",
|
|
19240
19307
|
"financial.quickTx.receipt": "Receipt",
|
|
19241
|
-
"financial.quickTx.receiptCapture": "
|
|
19308
|
+
"financial.quickTx.receiptCapture": "Attach a receipt or bill (photo or PDF)",
|
|
19242
19309
|
"financial.quickTx.receiptAttached": "Receipt attached",
|
|
19243
19310
|
"financial.quickTx.receiptReplace": "Replace",
|
|
19244
19311
|
"financial.quickTx.receiptRemove": "Remove receipt",
|
|
19245
|
-
"financial.quickTx.receiptInvalid": "Please choose an image
|
|
19312
|
+
"financial.quickTx.receiptInvalid": "Please choose an image or PDF",
|
|
19246
19313
|
"financial.quickTx.sendReceipt": "Send receipt",
|
|
19247
19314
|
"financial.quickTx.digitizeExpense": "Digitize expense",
|
|
19248
19315
|
"financial.quickTx.digitizeReceipt": "Digitize receipt",
|
|
@@ -19410,6 +19477,8 @@ var ptBR = {
|
|
|
19410
19477
|
"financial.invoiceForm.scheduleSourceHint": "Forma e conta ficam configur\xE1veis em cada vencimento.",
|
|
19411
19478
|
"financial.invoiceForm.paidNeedsMethod": "Diga a forma de pagamento e a conta",
|
|
19412
19479
|
"financial.invoiceForm.willBePaid": "Esta parcela entra como paga",
|
|
19480
|
+
"financial.invoiceForm.attachFailed": "A conta foi salva, mas um anexo n\xE3o subiu",
|
|
19481
|
+
"financial.invoiceForm.attachOnSave": "anexa ao salvar",
|
|
19413
19482
|
"financial.invoiceForm.clear": "Limpar",
|
|
19414
19483
|
"financial.invoiceForm.manageAccounts": "Gerenciar contas",
|
|
19415
19484
|
"financial.invoiceForm.manageChart": "Gerenciar plano de contas",
|
|
@@ -20181,11 +20250,11 @@ var ptBR = {
|
|
|
20181
20250
|
"financial.quickTx.attachment": "Anexo",
|
|
20182
20251
|
"financial.quickTx.attachmentSoon": "Anexos em breve",
|
|
20183
20252
|
"financial.quickTx.receipt": "Recibo",
|
|
20184
|
-
"financial.quickTx.receiptCapture": "
|
|
20253
|
+
"financial.quickTx.receiptCapture": "Anexar comprovante ou boleto (foto ou PDF)",
|
|
20185
20254
|
"financial.quickTx.receiptAttached": "Recibo anexado",
|
|
20186
20255
|
"financial.quickTx.receiptReplace": "Substituir",
|
|
20187
20256
|
"financial.quickTx.receiptRemove": "Remover recibo",
|
|
20188
|
-
"financial.quickTx.receiptInvalid": "Escolha
|
|
20257
|
+
"financial.quickTx.receiptInvalid": "Escolha uma imagem ou PDF",
|
|
20189
20258
|
"financial.quickTx.sendReceipt": "Enviar recibo",
|
|
20190
20259
|
"financial.quickTx.digitizeExpense": "Digitalizar despesa",
|
|
20191
20260
|
"financial.quickTx.digitizeReceipt": "Digitalizar comprovante",
|
|
@@ -45131,6 +45200,133 @@ REVOKE ALL ON FUNCTION public.financial_gift_card_by_code(text) FROM anon;
|
|
|
45131
45200
|
GRANT EXECUTE ON FUNCTION public.financial_gift_card_by_code(text) TO authenticated;
|
|
45132
45201
|
GRANT EXECUTE ON FUNCTION public.financial_gift_card_by_code(text) TO service_role;
|
|
45133
45202
|
`;
|
|
45203
|
+
var MIGRATION_119_AN_UNPAID_LEGACY_BILL_CAN_BE_RESCHEDULED = `-- An unpaid bill migrated from V1 could not be rescheduled.
|
|
45204
|
+
--
|
|
45205
|
+
-- Editing the lines of such a bill saved the new lines and total, then
|
|
45206
|
+
-- \`financial_reschedule_invoice\` refused with "carries legacy bill movements":
|
|
45207
|
+
-- the installment kept the old amount and every later save failed the same way.
|
|
45208
|
+
--
|
|
45209
|
+
-- The \`bill\` mirror in plg_financial_movements has had no reader since 044 \u2014
|
|
45210
|
+
-- the installment is the obligation. Its amount is append-only, its status is
|
|
45211
|
+
-- not, so an unpaid mirror is cancelled (stamped in metadata) and the schedule
|
|
45212
|
+
-- goes through. A mirror that records a payment still blocks.
|
|
45213
|
+
--
|
|
45214
|
+
-- Idempotent: replaces the function.
|
|
45215
|
+
|
|
45216
|
+
CREATE OR REPLACE FUNCTION public.financial_reschedule_invoice(
|
|
45217
|
+
p_invoice_id uuid,
|
|
45218
|
+
p_installments jsonb,
|
|
45219
|
+
p_idempotency_key text DEFAULT NULL::text
|
|
45220
|
+
)
|
|
45221
|
+
RETURNS jsonb
|
|
45222
|
+
LANGUAGE plpgsql
|
|
45223
|
+
SECURITY DEFINER
|
|
45224
|
+
SET search_path TO ''
|
|
45225
|
+
AS $function$
|
|
45226
|
+
DECLARE
|
|
45227
|
+
v_tenant uuid := public.plg_financial_require_tenant();
|
|
45228
|
+
v_invoice public.plg_financial_invoices%ROWTYPE;
|
|
45229
|
+
v_paid numeric;
|
|
45230
|
+
v_mirror int;
|
|
45231
|
+
v_sum numeric := 0;
|
|
45232
|
+
v_i jsonb;
|
|
45233
|
+
v_n int := 0;
|
|
45234
|
+
v_out jsonb := '[]'::jsonb;
|
|
45235
|
+
v_inst_id uuid;
|
|
45236
|
+
v_due date;
|
|
45237
|
+
v_amount numeric;
|
|
45238
|
+
v_meta jsonb;
|
|
45239
|
+
v_total int;
|
|
45240
|
+
BEGIN
|
|
45241
|
+
SELECT * INTO v_invoice FROM public.plg_financial_invoices WHERE id = p_invoice_id AND tenant_id = v_tenant;
|
|
45242
|
+
IF NOT FOUND THEN
|
|
45243
|
+
RAISE EXCEPTION 'financial_reschedule_invoice: invoice % not found', p_invoice_id USING ERRCODE = 'P0002';
|
|
45244
|
+
END IF;
|
|
45245
|
+
PERFORM public.plg_financial_require_permission('financial.edit', v_invoice.unit_id);
|
|
45246
|
+
PERFORM public.plg_financial_claim_idempotency(v_tenant, p_idempotency_key);
|
|
45247
|
+
|
|
45248
|
+
IF v_invoice.cancelled_at IS NOT NULL THEN
|
|
45249
|
+
RAISE EXCEPTION 'financial_reschedule_invoice: invoice % is cancelled', v_invoice.number USING ERRCODE = '55000';
|
|
45250
|
+
END IF;
|
|
45251
|
+
|
|
45252
|
+
SELECT coalesce(sum(b.paid), 0) INTO v_paid
|
|
45253
|
+
FROM public.v_financial_installment_balances b WHERE b.invoice_id = p_invoice_id;
|
|
45254
|
+
IF v_paid > 0 THEN
|
|
45255
|
+
RAISE EXCEPTION 'financial_reschedule_invoice: invoice % has % already received; its schedule is history now', v_invoice.number, v_paid
|
|
45256
|
+
USING ERRCODE = '55000',
|
|
45257
|
+
HINT = 'Move the due date of an unpaid installment instead, or cancel and reissue.';
|
|
45258
|
+
END IF;
|
|
45259
|
+
|
|
45260
|
+
-- The legacy \`bill\` mirror can't change amount, but nothing reads it any more
|
|
45261
|
+
-- (the installment is the obligation since 044) and its status may move.
|
|
45262
|
+
-- Retire it unless it records a payment the installments don't know about.
|
|
45263
|
+
SELECT count(*) INTO v_mirror FROM public.plg_financial_movements m
|
|
45264
|
+
WHERE m.financial_invoice_id = p_invoice_id AND m.movement_kind = 'bill'
|
|
45265
|
+
AND m.status <> 'cancelled' AND coalesce(m.paid_amount, 0) > 0;
|
|
45266
|
+
IF v_mirror > 0 THEN
|
|
45267
|
+
RAISE EXCEPTION 'financial_reschedule_invoice: invoice % carries a paid legacy bill movement and cannot be rescheduled', v_invoice.number
|
|
45268
|
+
USING ERRCODE = '55000',
|
|
45269
|
+
HINT = 'Cancel and reissue this invoice.';
|
|
45270
|
+
END IF;
|
|
45271
|
+
UPDATE public.plg_financial_movements m
|
|
45272
|
+
SET status = 'cancelled', updated_at = now(),
|
|
45273
|
+
metadata = coalesce(m.metadata, '{}'::jsonb)
|
|
45274
|
+
|| jsonb_build_object('retired_by', 'financial_reschedule_invoice', 'retired_at', now())
|
|
45275
|
+
WHERE m.financial_invoice_id = p_invoice_id AND m.movement_kind = 'bill' AND m.status <> 'cancelled';
|
|
45276
|
+
|
|
45277
|
+
IF jsonb_typeof(p_installments) <> 'array' OR jsonb_array_length(p_installments) = 0 THEN
|
|
45278
|
+
RAISE EXCEPTION 'financial_reschedule_invoice: installments must be a non-empty array' USING ERRCODE = '22023';
|
|
45279
|
+
END IF;
|
|
45280
|
+
FOR v_i IN SELECT * FROM jsonb_array_elements(p_installments) LOOP
|
|
45281
|
+
v_sum := v_sum + round(coalesce((v_i ->> 'amount')::numeric, 0), 2);
|
|
45282
|
+
END LOOP;
|
|
45283
|
+
IF v_sum <> round(v_invoice.total, 2) THEN
|
|
45284
|
+
RAISE EXCEPTION 'financial_reschedule_invoice: installments sum to % but the invoice total is %', v_sum, v_invoice.total
|
|
45285
|
+
USING ERRCODE = '23514';
|
|
45286
|
+
END IF;
|
|
45287
|
+
|
|
45288
|
+
v_total := jsonb_array_length(p_installments);
|
|
45289
|
+
|
|
45290
|
+
-- 1. A sobra sai PRIMEIRO. Se ela tiver movimento, o banco recusa aqui \u2014 e \xE9
|
|
45291
|
+
-- a recusa certa: encolher o cronograma apagaria a linha que um
|
|
45292
|
+
-- lan\xE7amento aponta.
|
|
45293
|
+
DELETE FROM public.plg_financial_invoice_installments
|
|
45294
|
+
WHERE invoice_id = p_invoice_id AND number > v_total;
|
|
45295
|
+
|
|
45296
|
+
-- 2. E a parcela de n\xFAmero N continua sendo a de n\xFAmero N.
|
|
45297
|
+
FOR v_i IN SELECT * FROM jsonb_array_elements(p_installments) LOOP
|
|
45298
|
+
v_n := v_n + 1;
|
|
45299
|
+
v_due := coalesce((v_i ->> 'due_date')::date, v_invoice.competence_date, current_date);
|
|
45300
|
+
v_amount := round(coalesce((v_i ->> 'amount')::numeric, 0), 2);
|
|
45301
|
+
v_meta := CASE WHEN jsonb_typeof(v_i -> 'metadata') = 'object' THEN v_i -> 'metadata' ELSE NULL END;
|
|
45302
|
+
|
|
45303
|
+
UPDATE public.plg_financial_invoice_installments
|
|
45304
|
+
SET due_date = v_due,
|
|
45305
|
+
amount = v_amount,
|
|
45306
|
+
metadata = coalesce(v_meta, metadata),
|
|
45307
|
+
updated_at = now()
|
|
45308
|
+
WHERE invoice_id = p_invoice_id AND number = v_n
|
|
45309
|
+
RETURNING id INTO v_inst_id;
|
|
45310
|
+
|
|
45311
|
+
IF v_inst_id IS NULL THEN
|
|
45312
|
+
INSERT INTO public.plg_financial_invoice_installments
|
|
45313
|
+
(tenant_id, unit_id, invoice_id, number, due_date, amount, metadata)
|
|
45314
|
+
VALUES (v_tenant, v_invoice.unit_id, p_invoice_id, v_n, v_due, v_amount, coalesce(v_meta, '{}'::jsonb))
|
|
45315
|
+
RETURNING id INTO v_inst_id;
|
|
45316
|
+
END IF;
|
|
45317
|
+
|
|
45318
|
+
v_out := v_out || jsonb_build_object('id', v_inst_id, 'number', v_n, 'due_date', v_due, 'amount', v_amount);
|
|
45319
|
+
END LOOP;
|
|
45320
|
+
|
|
45321
|
+
PERFORM public.plg_financial_audit(v_tenant, 'financial.invoice.reschedule', 'plg_financial_invoices', p_invoice_id::text,
|
|
45322
|
+
jsonb_build_object('number', v_invoice.number, 'installments', v_n, 'idempotency_key', p_idempotency_key));
|
|
45323
|
+
|
|
45324
|
+
RETURN jsonb_build_object('invoice_id', p_invoice_id, 'installments', v_out);
|
|
45325
|
+
END $function$;
|
|
45326
|
+
|
|
45327
|
+
COMMENT ON FUNCTION public.financial_reschedule_invoice(uuid, jsonb, text) IS
|
|
45328
|
+
'Rewrites the schedule by UPDATING each numbered installment in place, and retires an unpaid legacy bill mirror instead of refusing (119).';
|
|
45329
|
+
`;
|
|
45134
45330
|
var MIGRATIONS = [
|
|
45135
45331
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
45136
45332
|
{ id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
|
|
@@ -45250,7 +45446,8 @@ var MIGRATIONS = [
|
|
|
45250
45446
|
{ id: "115_a_remessa_aponta_para_a_conta_que_existe", sql: MIGRATION_115_A_REMESSA_APONTA_PARA_A_CONTA_QUE_EXISTE },
|
|
45251
45447
|
{ id: "116_um_estorno_nao_se_estorna", sql: MIGRATION_116_UM_ESTORNO_NAO_SE_ESTORNA },
|
|
45252
45448
|
{ id: "117_copying_lines_is_an_internal_step", sql: MIGRATION_117_COPYING_LINES_IS_AN_INTERNAL_STEP },
|
|
45253
|
-
{ id: "118_gift_card_found_by_exact_code", sql: MIGRATION_118_GIFT_CARD_FOUND_BY_EXACT_CODE }
|
|
45449
|
+
{ id: "118_gift_card_found_by_exact_code", sql: MIGRATION_118_GIFT_CARD_FOUND_BY_EXACT_CODE },
|
|
45450
|
+
{ id: "119_an_unpaid_legacy_bill_can_be_rescheduled", sql: MIGRATION_119_AN_UNPAID_LEGACY_BILL_CAN_BE_RESCHEDULED }
|
|
45254
45451
|
];
|
|
45255
45452
|
var STANDALONE_CONFIG = resolveConfig();
|
|
45256
45453
|
registerTranslations(financialLocales);
|
|
@@ -46037,6 +46234,6 @@ function createFinancialPlugin(options) {
|
|
|
46037
46234
|
};
|
|
46038
46235
|
}
|
|
46039
46236
|
|
|
46040
|
-
export { ClientAccountInline, ClientAccountModal, ClientAccountPanel, FINANCIAL_NAV_FEATURES, ReceivePaymentModal, buildFinancialDomainEvents, buildFinancialMessageDefaults, createFinancialPlugin, createFinancialSettingsPlugin, createMockFinancialProvider, createSafeFinancialProvider };
|
|
46237
|
+
export { ClientAccountInline, ClientAccountModal, ClientAccountPanel, FINANCIAL_INVOICE_ZONES, FINANCIAL_NAV_FEATURES, ReceivePaymentModal, buildFinancialDomainEvents, buildFinancialMessageDefaults, createFinancialPlugin, createFinancialSettingsPlugin, createMockFinancialProvider, createSafeFinancialProvider };
|
|
46041
46238
|
//# sourceMappingURL=index.js.map
|
|
46042
46239
|
//# sourceMappingURL=index.js.map
|