@fayz-ai/plugin-financial 0.23.0 → 0.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AccountPicker.d.ts +19 -0
- package/dist/components/AccountPicker.d.ts.map +1 -0
- package/dist/components/InstallmentSource.d.ts +0 -7
- package/dist/components/InstallmentSource.d.ts.map +1 -1
- package/dist/components/PaymentFields.d.ts.map +1 -1
- package/dist/components/PaymentModal.d.ts.map +1 -1
- package/dist/index.js +665 -193
- package/dist/index.js.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/CashRegistersView.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/ReconciliationView.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { recordRailFields, RECORD_GROUP, activeFacet, createConnectionStore, con
|
|
|
4
4
|
import { INLINE_LINK_TONE, SearchSelect, ModalTask, toast, Button, useInModalTaskHost, useSaveBar, tourAnchor, DatePicker, SegmentedControl, SubpageHeader, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, Card, CardContent, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MultiSelect, PageTransition, ModulePage, Badge, TooltipProvider, ConfirmDialog, CurrencyInput, Input, PageHeaderActions, ChoiceSelect, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, Checkbox, Hint, ModalDescription, Tooltip, TooltipTrigger, TooltipContent, Popover, PopoverTrigger, PopoverContent, PageTabs, DashboardCanvas, ComingSoon, StatTiles, ListView, DateRangeControl, FilterPanel, FilterPanelField, RequiredFilter, FilterMenuRow, openPrintWindow, printDocument, 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
6
|
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, createCrudPage, PluginSettingsPanel, dedup, useCurrency, useUnitFilter, useLimitGuard, usePermissionOptional, useLookupCreate, OrderFormSkeleton, WidgetSlot, OrderFormBlock, TagsField, OrderFormView, OrderFormItemsCard, OrderFormItemRow, adminNavigateTo, OrderFormAddItemStep, CurrencyScope, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, TagChip, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, useModuleNavAccess, PluginOnboardingGateSkeleton, ModuleActionBar, SettingsGroup, ToggleRow, createArchetypeLookup, invalidateLimit, useAgentSurface, useHouseCurrency, parseViewId, EntitlementGate, UpgradePrompt, useChatStore, revealPage, resolveEntityRoute, useAuth, GetStarted, PermissionGate, createOneShotAsk, askAI, requestChatUpload, toInlineFile, shrinkForVision } from '@fayz-ai/admin';
|
|
7
|
-
import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban, Receipt, Gift, Briefcase, Package, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, PackagePlus, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Ticket, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Undo2, Paperclip, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, ChevronRight, Building2, Image, UploadCloud, Calendar, User, Unlink, BookOpen, Info,
|
|
7
|
+
import { Landmark, CircleDollarSign, FileText, TriangleAlert, Loader2, Check, X, MoreVertical, Ban, Receipt, Gift, Briefcase, Package, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, PackagePlus, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Ticket, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Undo2, Paperclip, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Search, ChevronsUpDown, ChevronsDownUp, ChevronRight, Building2, Image, UploadCloud, Calendar, User, Unlink, BookOpen, Info, ChevronDown, Settings, FolderOpen, Folder, RotateCcw, ArrowLeft, ChevronLeft, Eye, Inbox, Clock, ClipboardCheck, 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';
|
|
10
10
|
import { useStore } from 'zustand';
|
|
@@ -4520,6 +4520,118 @@ function renderAccountAvatar(item) {
|
|
|
4520
4520
|
if (!r || r.kind !== "card_payable") return null;
|
|
4521
4521
|
return /* @__PURE__ */ jsx(CardVisual, { size: "lg", lastFour: r.lastFour, color: r.color, brandId: r.cardBrandId });
|
|
4522
4522
|
}
|
|
4523
|
+
var METHOD_ICONS = {
|
|
4524
|
+
cash: Banknote,
|
|
4525
|
+
pix: QrCode,
|
|
4526
|
+
credit_card: CreditCard,
|
|
4527
|
+
debit_card: CreditCard,
|
|
4528
|
+
bank_transfer: ArrowRightLeft,
|
|
4529
|
+
check: FileCheck,
|
|
4530
|
+
boleto: Receipt,
|
|
4531
|
+
voucher: Ticket
|
|
4532
|
+
};
|
|
4533
|
+
function MethodIcon({ type }) {
|
|
4534
|
+
const Icon = METHOD_ICONS[type?.transactionType ?? ""] ?? Wallet;
|
|
4535
|
+
return /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4536
|
+
}
|
|
4537
|
+
function AccountIcon({ account }) {
|
|
4538
|
+
if (!account) return null;
|
|
4539
|
+
if (account.accountType === "card_payable" && (account.lastFour || account.color || account.cardBrandId)) {
|
|
4540
|
+
return /* @__PURE__ */ jsx(CardVisual, { size: "xs", lastFour: account.lastFour ?? void 0, color: account.color ?? void 0, brandId: account.cardBrandId ?? void 0 });
|
|
4541
|
+
}
|
|
4542
|
+
if (account.accountType.startsWith("card_")) return /* @__PURE__ */ jsx(CreditCard, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4543
|
+
if (account.accountType === "cash_physical" || account.accountType === "cash_session") {
|
|
4544
|
+
return /* @__PURE__ */ jsx(Banknote, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
4545
|
+
}
|
|
4546
|
+
const bank = findBrBank(account.bankCode, account.bankName ?? account.name);
|
|
4547
|
+
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 });
|
|
4548
|
+
}
|
|
4549
|
+
var FIELD_CLASS = "mt-1 flex w-full items-center gap-2 rounded-input border border-input bg-card px-3 py-2 text-left text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring";
|
|
4550
|
+
var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
4551
|
+
function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className, onManage, manageLabel, variant = "inline" }) {
|
|
4552
|
+
const [open, setOpen] = React29__default.useState(false);
|
|
4553
|
+
const [q, setQ] = React29__default.useState("");
|
|
4554
|
+
const selected = accounts.find((a) => a.id === value);
|
|
4555
|
+
const list = q ? accounts.filter((a) => norm2(a.name).includes(norm2(q))) : accounts;
|
|
4556
|
+
const pick = (id) => {
|
|
4557
|
+
onChange(id);
|
|
4558
|
+
setOpen(false);
|
|
4559
|
+
setQ("");
|
|
4560
|
+
};
|
|
4561
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (o) => {
|
|
4562
|
+
setOpen(o);
|
|
4563
|
+
if (!o) setQ("");
|
|
4564
|
+
}, children: [
|
|
4565
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
4566
|
+
"button",
|
|
4567
|
+
{
|
|
4568
|
+
type: "button",
|
|
4569
|
+
disabled,
|
|
4570
|
+
title,
|
|
4571
|
+
"aria-label": ariaLabel,
|
|
4572
|
+
className: `${variant === "field" ? FIELD_CLASS : "inline-flex max-w-full items-center gap-1 text-xs"} disabled:cursor-not-allowed disabled:opacity-50 ${className ?? ""}`,
|
|
4573
|
+
children: [
|
|
4574
|
+
selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4575
|
+
/* @__PURE__ */ jsx(AccountIcon, { account: selected }),
|
|
4576
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: selected.name })
|
|
4577
|
+
] }) : /* @__PURE__ */ jsx("span", { className: variant === "field" ? "text-muted-foreground" : void 0, children: placeholder }),
|
|
4578
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: `${variant === "field" ? "ml-auto h-4 w-4" : "h-3 w-3"} shrink-0 opacity-50`, "aria-hidden": true })
|
|
4579
|
+
]
|
|
4580
|
+
}
|
|
4581
|
+
) }),
|
|
4582
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: variant === "field" ? "w-[var(--radix-popover-trigger-width)] p-1" : "w-64 p-1", children: [
|
|
4583
|
+
/* @__PURE__ */ jsxs("div", { className: "relative mb-1", children: [
|
|
4584
|
+
/* @__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 }),
|
|
4585
|
+
/* @__PURE__ */ jsx(
|
|
4586
|
+
"input",
|
|
4587
|
+
{
|
|
4588
|
+
autoFocus: true,
|
|
4589
|
+
value: q,
|
|
4590
|
+
onChange: (e) => setQ(e.target.value),
|
|
4591
|
+
onKeyDown: (e) => {
|
|
4592
|
+
if (e.key === "Enter" && list[0]) {
|
|
4593
|
+
e.preventDefault();
|
|
4594
|
+
pick(list[0].id);
|
|
4595
|
+
}
|
|
4596
|
+
},
|
|
4597
|
+
placeholder: "Buscar conta\u2026",
|
|
4598
|
+
className: "h-8 w-full rounded-md border-0 bg-muted/50 pl-7 pr-2 text-xs outline-none focus:ring-0"
|
|
4599
|
+
}
|
|
4600
|
+
)
|
|
4601
|
+
] }),
|
|
4602
|
+
/* @__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(
|
|
4603
|
+
"button",
|
|
4604
|
+
{
|
|
4605
|
+
type: "button",
|
|
4606
|
+
onClick: () => pick(a.id),
|
|
4607
|
+
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" : ""}`,
|
|
4608
|
+
children: [
|
|
4609
|
+
/* @__PURE__ */ jsx(AccountIcon, { account: a }),
|
|
4610
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: a.name }),
|
|
4611
|
+
a.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
|
|
4612
|
+
]
|
|
4613
|
+
},
|
|
4614
|
+
a.id
|
|
4615
|
+
)) }),
|
|
4616
|
+
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 }),
|
|
4617
|
+
onManage && /* @__PURE__ */ jsxs(
|
|
4618
|
+
"button",
|
|
4619
|
+
{
|
|
4620
|
+
type: "button",
|
|
4621
|
+
onClick: () => {
|
|
4622
|
+
setOpen(false);
|
|
4623
|
+
onManage();
|
|
4624
|
+
},
|
|
4625
|
+
className: "mt-1 flex w-full items-center gap-2 rounded-md border-t px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
4626
|
+
children: [
|
|
4627
|
+
/* @__PURE__ */ jsx(Settings, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
|
|
4628
|
+
manageLabel
|
|
4629
|
+
]
|
|
4630
|
+
}
|
|
4631
|
+
)
|
|
4632
|
+
] })
|
|
4633
|
+
] });
|
|
4634
|
+
}
|
|
4523
4635
|
var ACCOUNT_TYPES_FOR_TX2 = {
|
|
4524
4636
|
cash: ["cash_physical", "cash_session"],
|
|
4525
4637
|
credit_card: ["card_receivable_credit"],
|
|
@@ -4734,7 +4846,7 @@ function usePaymentFields(options) {
|
|
|
4734
4846
|
)
|
|
4735
4847
|
] }),
|
|
4736
4848
|
selectedTypeId && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
4737
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
4849
|
+
/* @__PURE__ */ jsxs("div", { className: "stagger-field grid grid-cols-2 items-start gap-3", style: { animationDelay: "0ms" }, children: [
|
|
4738
4850
|
lockAmount ? (
|
|
4739
4851
|
// O extrato mandou o valor. Ele aparece como FATO, e a linha
|
|
4740
4852
|
// abaixo diz por quê — um campo cinza sem explicação faz a
|
|
@@ -4837,10 +4949,18 @@ function usePaymentFields(options) {
|
|
|
4837
4949
|
t2("financial.payment.configureAccounts"),
|
|
4838
4950
|
" \u2192"
|
|
4839
4951
|
] })
|
|
4840
|
-
] }) : /* @__PURE__ */
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4952
|
+
] }) : /* @__PURE__ */ jsx(
|
|
4953
|
+
AccountPicker,
|
|
4954
|
+
{
|
|
4955
|
+
variant: "field",
|
|
4956
|
+
accounts: eligibleAccounts2,
|
|
4957
|
+
value: bankAccountId || void 0,
|
|
4958
|
+
onChange: (id) => setBankAccountId(id ?? ""),
|
|
4959
|
+
placeholder: t2("financial.payment.selectAccount"),
|
|
4960
|
+
ariaLabel: t2("financial.payment.bankAccount"),
|
|
4961
|
+
clearLabel: t2("financial.invoiceForm.clear")
|
|
4962
|
+
}
|
|
4963
|
+
)
|
|
4844
4964
|
] }),
|
|
4845
4965
|
/* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "150ms" }, children: [
|
|
4846
4966
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.notes") }),
|
|
@@ -4927,6 +5047,8 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
4927
5047
|
setPending(command.current.pending);
|
|
4928
5048
|
}
|
|
4929
5049
|
}
|
|
5050
|
+
const receiving = movement.direction === "credit";
|
|
5051
|
+
const title = t2(receiving ? "financial.payment.titleReceive" : "financial.payment.title");
|
|
4930
5052
|
const body = /* @__PURE__ */ jsx("div", { className: "px-5 py-4", children: fields.node });
|
|
4931
5053
|
const footer = /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-t sticky bottom-0 bg-card px-5 py-4 space-y-2", children: [
|
|
4932
5054
|
pending2 && !saving && /* @__PURE__ */ jsxs("p", { role: "status", className: "flex items-start gap-1.5 text-[11px] leading-snug text-muted-foreground", children: [
|
|
@@ -4945,7 +5067,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
4945
5067
|
title: fields.blockedReason ?? void 0,
|
|
4946
5068
|
children: [
|
|
4947
5069
|
saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
4948
|
-
saving ? t2("financial.payment.processing") : fields.blockedReason && !pending2 ? fields.blockedReason : t2("financial.payment.payAmount", { amount: formatCurrency(fields.value.amount, currency) })
|
|
5070
|
+
saving ? t2("financial.payment.processing") : fields.blockedReason && !pending2 ? fields.blockedReason : t2(receiving ? "financial.payment.receiveAmount" : "financial.payment.payAmount", { amount: formatCurrency(fields.value.amount, currency) })
|
|
4949
5071
|
]
|
|
4950
5072
|
}
|
|
4951
5073
|
)
|
|
@@ -4957,7 +5079,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
4957
5079
|
ModalTask,
|
|
4958
5080
|
{
|
|
4959
5081
|
open: true,
|
|
4960
|
-
title
|
|
5082
|
+
title,
|
|
4961
5083
|
hint: `${t2("financial.payment.installment", { number: String(movement.installmentNumber ?? 1) })} \xB7 ${t2("financial.payment.remaining", { amount: formatCurrency(remaining, currency) })}`,
|
|
4962
5084
|
onClose: handleClose,
|
|
4963
5085
|
children: [
|
|
@@ -4986,7 +5108,7 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
4986
5108
|
children: [
|
|
4987
5109
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b sticky top-0 bg-card z-10", children: [
|
|
4988
5110
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
4989
|
-
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children:
|
|
5111
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: title }),
|
|
4990
5112
|
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
4991
5113
|
t2("financial.payment.installment", { number: String(movement.installmentNumber ?? 1) }),
|
|
4992
5114
|
" \xB7 ",
|
|
@@ -5005,32 +5127,6 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
5005
5127
|
document.body
|
|
5006
5128
|
);
|
|
5007
5129
|
}
|
|
5008
|
-
var METHOD_ICONS = {
|
|
5009
|
-
cash: Banknote,
|
|
5010
|
-
pix: QrCode,
|
|
5011
|
-
credit_card: CreditCard,
|
|
5012
|
-
debit_card: CreditCard,
|
|
5013
|
-
bank_transfer: ArrowRightLeft,
|
|
5014
|
-
check: FileCheck,
|
|
5015
|
-
boleto: Receipt,
|
|
5016
|
-
voucher: Ticket
|
|
5017
|
-
};
|
|
5018
|
-
function MethodIcon({ type }) {
|
|
5019
|
-
const Icon = METHOD_ICONS[type?.transactionType ?? ""] ?? Wallet;
|
|
5020
|
-
return /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
5021
|
-
}
|
|
5022
|
-
function AccountIcon({ account }) {
|
|
5023
|
-
if (!account) return null;
|
|
5024
|
-
if (account.accountType === "card_payable" && (account.lastFour || account.color || account.cardBrandId)) {
|
|
5025
|
-
return /* @__PURE__ */ jsx(CardVisual, { size: "xs", lastFour: account.lastFour ?? void 0, color: account.color ?? void 0, brandId: account.cardBrandId ?? void 0 });
|
|
5026
|
-
}
|
|
5027
|
-
if (account.accountType.startsWith("card_")) return /* @__PURE__ */ jsx(CreditCard, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
5028
|
-
if (account.accountType === "cash_physical" || account.accountType === "cash_session") {
|
|
5029
|
-
return /* @__PURE__ */ jsx(Banknote, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true });
|
|
5030
|
-
}
|
|
5031
|
-
const bank = findBrBank(account.bankCode, account.bankName ?? account.name);
|
|
5032
|
-
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 });
|
|
5033
|
-
}
|
|
5034
5130
|
var NONE = "__none__";
|
|
5035
5131
|
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}`;
|
|
5036
5132
|
function InstallmentSourceFields({
|
|
@@ -5053,7 +5149,7 @@ function InstallmentSourceFields({
|
|
|
5053
5149
|
const conta = elegiveis.length === 1 ? elegiveis[0].id : elegiveis.some((a) => a.id === value.payAccountId) ? value.payAccountId : void 0;
|
|
5054
5150
|
onChange({ payMethodTypeId: typeId || void 0, payAccountId: typeId ? conta : void 0 });
|
|
5055
5151
|
}
|
|
5056
|
-
const tone = (filled) => filled ? "text-foreground" : "text-
|
|
5152
|
+
const tone = (filled) => filled ? "text-foreground" : "text-foreground border-b border-dashed border-foreground/60";
|
|
5057
5153
|
return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-start gap-0.5", children: [
|
|
5058
5154
|
/* @__PURE__ */ jsxs(
|
|
5059
5155
|
Select,
|
|
@@ -5098,91 +5194,6 @@ function InstallmentSourceFields({
|
|
|
5098
5194
|
)
|
|
5099
5195
|
] });
|
|
5100
5196
|
}
|
|
5101
|
-
var norm2 = (v) => v.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
5102
|
-
function AccountPicker({ accounts, value, onChange, disabled, title, placeholder, ariaLabel, clearLabel, className, onManage, manageLabel }) {
|
|
5103
|
-
const [open, setOpen] = React29__default.useState(false);
|
|
5104
|
-
const [q, setQ] = React29__default.useState("");
|
|
5105
|
-
const selected = accounts.find((a) => a.id === value);
|
|
5106
|
-
const list = q ? accounts.filter((a) => norm2(a.name).includes(norm2(q))) : accounts;
|
|
5107
|
-
const pick = (id) => {
|
|
5108
|
-
onChange(id);
|
|
5109
|
-
setOpen(false);
|
|
5110
|
-
setQ("");
|
|
5111
|
-
};
|
|
5112
|
-
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: (o) => {
|
|
5113
|
-
setOpen(o);
|
|
5114
|
-
if (!o) setQ("");
|
|
5115
|
-
}, children: [
|
|
5116
|
-
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
5117
|
-
"button",
|
|
5118
|
-
{
|
|
5119
|
-
type: "button",
|
|
5120
|
-
disabled,
|
|
5121
|
-
title,
|
|
5122
|
-
"aria-label": ariaLabel,
|
|
5123
|
-
className: `inline-flex max-w-full items-center gap-1 text-xs disabled:cursor-not-allowed disabled:opacity-50 ${className ?? ""}`,
|
|
5124
|
-
children: [
|
|
5125
|
-
selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5126
|
-
/* @__PURE__ */ jsx(AccountIcon, { account: selected }),
|
|
5127
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: selected.name })
|
|
5128
|
-
] }) : placeholder,
|
|
5129
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true })
|
|
5130
|
-
]
|
|
5131
|
-
}
|
|
5132
|
-
) }),
|
|
5133
|
-
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-64 p-1", children: [
|
|
5134
|
-
/* @__PURE__ */ jsxs("div", { className: "relative mb-1", children: [
|
|
5135
|
-
/* @__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 }),
|
|
5136
|
-
/* @__PURE__ */ jsx(
|
|
5137
|
-
"input",
|
|
5138
|
-
{
|
|
5139
|
-
autoFocus: true,
|
|
5140
|
-
value: q,
|
|
5141
|
-
onChange: (e) => setQ(e.target.value),
|
|
5142
|
-
onKeyDown: (e) => {
|
|
5143
|
-
if (e.key === "Enter" && list[0]) {
|
|
5144
|
-
e.preventDefault();
|
|
5145
|
-
pick(list[0].id);
|
|
5146
|
-
}
|
|
5147
|
-
},
|
|
5148
|
-
placeholder: "Buscar conta\u2026",
|
|
5149
|
-
className: "h-8 w-full rounded-md border-0 bg-muted/50 pl-7 pr-2 text-xs outline-none focus:ring-0"
|
|
5150
|
-
}
|
|
5151
|
-
)
|
|
5152
|
-
] }),
|
|
5153
|
-
/* @__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(
|
|
5154
|
-
"button",
|
|
5155
|
-
{
|
|
5156
|
-
type: "button",
|
|
5157
|
-
onClick: () => pick(a.id),
|
|
5158
|
-
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" : ""}`,
|
|
5159
|
-
children: [
|
|
5160
|
-
/* @__PURE__ */ jsx(AccountIcon, { account: a }),
|
|
5161
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: a.name }),
|
|
5162
|
-
a.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
|
|
5163
|
-
]
|
|
5164
|
-
},
|
|
5165
|
-
a.id
|
|
5166
|
-
)) }),
|
|
5167
|
-
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 }),
|
|
5168
|
-
onManage && /* @__PURE__ */ jsxs(
|
|
5169
|
-
"button",
|
|
5170
|
-
{
|
|
5171
|
-
type: "button",
|
|
5172
|
-
onClick: () => {
|
|
5173
|
-
setOpen(false);
|
|
5174
|
-
onManage();
|
|
5175
|
-
},
|
|
5176
|
-
className: "mt-1 flex w-full items-center gap-2 rounded-md border-t px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
5177
|
-
children: [
|
|
5178
|
-
/* @__PURE__ */ jsx(Settings, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
|
|
5179
|
-
manageLabel
|
|
5180
|
-
]
|
|
5181
|
-
}
|
|
5182
|
-
)
|
|
5183
|
-
] })
|
|
5184
|
-
] });
|
|
5185
|
-
}
|
|
5186
5197
|
var STATUS = {
|
|
5187
5198
|
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
|
|
5188
5199
|
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
|
|
@@ -6412,7 +6423,6 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
6412
6423
|
const fonteTravada = isPaid2 || isPartial;
|
|
6413
6424
|
const formaEscolhida = paymentMethodTypes.find((tp) => tp.id === inst.payMethodTypeId);
|
|
6414
6425
|
const contaEscolhida = bankAccounts.find((a) => a.id === inst.payAccountId);
|
|
6415
|
-
const faltaParaPagar = !inst.payMethodTypeId ? t2("financial.payment.methodRequired") : !inst.payAccountId ? t2("financial.payment.accountRequired") : null;
|
|
6416
6426
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
6417
6427
|
/* @__PURE__ */ jsx(
|
|
6418
6428
|
InstallmentRow,
|
|
@@ -6461,12 +6471,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
6461
6471
|
!isPaid2 && (saved || !isEdit && !!contactId && items.length > 0 && inst.amount > 0) && /* @__PURE__ */ jsx(
|
|
6462
6472
|
InstallmentPayButton,
|
|
6463
6473
|
{
|
|
6464
|
-
disabled: saving
|
|
6465
|
-
title: faltaParaPagar ?? void 0,
|
|
6474
|
+
disabled: saving,
|
|
6466
6475
|
onClick: () => {
|
|
6467
6476
|
void pagarParcela(i);
|
|
6468
6477
|
},
|
|
6469
|
-
children: t2("financial.invoice.pay")
|
|
6478
|
+
children: t2(direction === "credit" ? "financial.invoice.receive" : "financial.invoice.pay")
|
|
6470
6479
|
}
|
|
6471
6480
|
)
|
|
6472
6481
|
] })
|
|
@@ -7711,7 +7720,6 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
7711
7720
|
const srcAccountId = srcStr(srcMeta.payAccountId);
|
|
7712
7721
|
const editavel = !hasPaidDetails && invoice.status !== "cancelled";
|
|
7713
7722
|
const canChooseSource = editavel && Math.abs(mov.amount) >= 5e-3;
|
|
7714
|
-
const faltaParaPagar = !srcTypeId ? t2("financial.payment.methodRequired") : !srcAccountId ? t2("financial.payment.accountRequired") : null;
|
|
7715
7723
|
const ativa = invoice.status !== "cancelled";
|
|
7716
7724
|
return /* @__PURE__ */ jsx(
|
|
7717
7725
|
InstallmentRow,
|
|
@@ -7772,10 +7780,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
7772
7780
|
InstallmentPayButton,
|
|
7773
7781
|
{
|
|
7774
7782
|
onClick: () => setPayingMovement(mov),
|
|
7775
|
-
disabled: !!faltaParaPagar,
|
|
7776
|
-
title: faltaParaPagar ?? void 0,
|
|
7777
7783
|
children: [
|
|
7778
|
-
t2("financial.invoice.pay"),
|
|
7784
|
+
t2(direction === "credit" ? "financial.invoice.receive" : "financial.invoice.pay"),
|
|
7779
7785
|
" ",
|
|
7780
7786
|
remaining < mov.amount ? formatCurrency(remaining, currency) : ""
|
|
7781
7787
|
]
|
|
@@ -7783,7 +7789,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
|
|
|
7783
7789
|
),
|
|
7784
7790
|
isPaid2 && reversiblePayments(instPayments).length > 0 && ativa && /* @__PURE__ */ jsx(InstallmentActionButton, { icon: Undo2, onClick: () => setReversing(reversiblePayments(instPayments).at(-1)), children: t2("financial.invoice.reverse") }),
|
|
7785
7791
|
isPartial && ativa && /* @__PURE__ */ jsxs(InstallmentPayButton, { variant: "outline", onClick: () => setPayingMovement(mov), children: [
|
|
7786
|
-
t2("financial.invoice.pay"),
|
|
7792
|
+
t2(direction === "credit" ? "financial.invoice.receive" : "financial.invoice.pay"),
|
|
7787
7793
|
" ",
|
|
7788
7794
|
formatCurrency(remaining, currency)
|
|
7789
7795
|
] })
|
|
@@ -8471,6 +8477,50 @@ function CashClosingPanel({
|
|
|
8471
8477
|
)
|
|
8472
8478
|
] });
|
|
8473
8479
|
}
|
|
8480
|
+
function dayKeyOf(iso) {
|
|
8481
|
+
if (!iso) return "";
|
|
8482
|
+
const d = new Date(iso);
|
|
8483
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
8484
|
+
}
|
|
8485
|
+
function groupByClosingDay(rows) {
|
|
8486
|
+
const days = [];
|
|
8487
|
+
const sorted = [...rows].sort((a, b) => (b.closedAt ?? "").localeCompare(a.closedAt ?? ""));
|
|
8488
|
+
for (const r of sorted) {
|
|
8489
|
+
const key = dayKeyOf(r.closedAt);
|
|
8490
|
+
const last = days[days.length - 1];
|
|
8491
|
+
if (last && last.key === key) last.rows.push(r);
|
|
8492
|
+
else days.push({ key, rows: [r] });
|
|
8493
|
+
}
|
|
8494
|
+
return days;
|
|
8495
|
+
}
|
|
8496
|
+
function DayHeader({ dayKey, count }) {
|
|
8497
|
+
const t2 = useTranslation();
|
|
8498
|
+
let label = "\u2014";
|
|
8499
|
+
if (dayKey === dayKeyOf((/* @__PURE__ */ new Date()).toISOString())) label = t2("financial.cash.reviewToday");
|
|
8500
|
+
else if (dayKey === dayKeyOf(new Date(Date.now() - 864e5).toISOString())) label = t2("financial.cash.reviewYesterday");
|
|
8501
|
+
else if (dayKey) {
|
|
8502
|
+
const [y, m, d] = dayKey.split("-").map(Number);
|
|
8503
|
+
label = new Date(y, m - 1, d).toLocaleDateString(void 0, { weekday: "long", day: "2-digit", month: "2-digit" });
|
|
8504
|
+
}
|
|
8505
|
+
return (
|
|
8506
|
+
// Stays on screen while its day scrolls, stopping under the page's tab band
|
|
8507
|
+
// like a list's column header. Opaque: rows pass underneath it.
|
|
8508
|
+
/* @__PURE__ */ jsxs(
|
|
8509
|
+
"div",
|
|
8510
|
+
{
|
|
8511
|
+
className: "sticky z-10 flex items-center gap-2 border-b bg-muted px-4 py-1.5 text-xs font-medium text-muted-foreground",
|
|
8512
|
+
style: { top: "var(--fz-page-band-h, 0px)" },
|
|
8513
|
+
children: [
|
|
8514
|
+
/* @__PURE__ */ jsx("span", { className: "first-letter:uppercase", children: label }),
|
|
8515
|
+
/* @__PURE__ */ jsx("span", { className: "rounded-full bg-background px-1.5 text-[10px] tabular-nums", children: count })
|
|
8516
|
+
]
|
|
8517
|
+
}
|
|
8518
|
+
)
|
|
8519
|
+
);
|
|
8520
|
+
}
|
|
8521
|
+
var HOUR_MINUTE = { hour: "2-digit", minute: "2-digit" };
|
|
8522
|
+
var PANEL_REVIEW_LIMIT = 3;
|
|
8523
|
+
var REVIEW_TAB_QUERY = "tab=review";
|
|
8474
8524
|
function ReviewQueue({
|
|
8475
8525
|
variant,
|
|
8476
8526
|
rows,
|
|
@@ -8484,6 +8534,11 @@ function ReviewQueue({
|
|
|
8484
8534
|
const me = user?.id ?? null;
|
|
8485
8535
|
const [reviewing, setReviewing] = useState(null);
|
|
8486
8536
|
const pendentes = (rows ?? []).filter((r) => r.reviewStatus === "pending");
|
|
8537
|
+
const setCashSessionModalOpen = useFinancialStore((s) => s.setCashSessionModalOpen);
|
|
8538
|
+
const compact = variant === "panel";
|
|
8539
|
+
const shown = compact ? [...pendentes].sort((a, b) => Math.abs(b.difference) - Math.abs(a.difference) || (a.closedAt ?? "").localeCompare(b.closedAt ?? "")).slice(0, PANEL_REVIEW_LIMIT) : pendentes;
|
|
8540
|
+
const hidden = pendentes.length - shown.length;
|
|
8541
|
+
const days = compact ? [] : groupByClosingDay(pendentes);
|
|
8487
8542
|
const sectionClass = variant === "panel" ? "" : "rounded-card border bg-card shadow-sm";
|
|
8488
8543
|
return /* @__PURE__ */ jsxs("div", { className: sectionClass, children: [
|
|
8489
8544
|
showHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-4 py-3", children: [
|
|
@@ -8491,39 +8546,68 @@ function ReviewQueue({
|
|
|
8491
8546
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.cash.reviewQueue") }),
|
|
8492
8547
|
pendentes.length > 0 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-info/10 px-2 py-0.5 text-[10px] font-semibold text-info", children: pendentes.length })
|
|
8493
8548
|
] }),
|
|
8494
|
-
showHeader && /* @__PURE__ */ jsx("p", { className: "px-4 pt-3 text-[11px] text-muted-foreground", children: "Quem abre e fecha o caixa n\xE3o \xE9 quem confere. Um fechamento fica aqui at\xE9 algu\xE9m da ger\xEAncia ou do financeiro aprovar." }),
|
|
8549
|
+
showHeader && !compact && /* @__PURE__ */ jsx("p", { className: "px-4 pt-3 text-[11px] text-muted-foreground", children: "Quem abre e fecha o caixa n\xE3o \xE9 quem confere. Um fechamento fica aqui at\xE9 algu\xE9m da ger\xEAncia ou do financeiro aprovar." }),
|
|
8495
8550
|
error && /* @__PURE__ */ jsx("p", { className: "px-4 pt-2 text-xs text-destructive", children: error }),
|
|
8496
8551
|
rows === null ? /* @__PURE__ */ jsx("div", { className: "space-y-2 p-4", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
8497
8552
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-48" }),
|
|
8498
8553
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-32" })
|
|
8499
|
-
] }, i)) }) : pendentes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-4 py-6 text-center text-sm text-muted-foreground", children: t2("financial.cash.reviewQueueEmpty") }) : /* @__PURE__ */
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8554
|
+
] }, i)) }) : pendentes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-4 py-6 text-center text-sm text-muted-foreground", children: t2("financial.cash.reviewQueueEmpty") }) : /* @__PURE__ */ jsxs("div", { className: "divide-y", children: [
|
|
8555
|
+
(compact ? [{ key: "", rows: shown }] : days).map((day3) => /* @__PURE__ */ jsxs(React29__default.Fragment, { children: [
|
|
8556
|
+
!compact && /* @__PURE__ */ jsx(DayHeader, { dayKey: day3.key, count: day3.rows.length }),
|
|
8557
|
+
day3.rows.map((r) => {
|
|
8558
|
+
const fechei = !!me && r.closedByUserId === me;
|
|
8559
|
+
const amount = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8504
8560
|
formatCurrency(r.closingBalance, currency),
|
|
8505
8561
|
r.difference !== 0 && /* @__PURE__ */ jsxs("span", { className: `ml-2 text-xs tabular-nums ${r.difference > 0 ? "text-success" : "text-destructive"}`, children: [
|
|
8506
8562
|
r.difference > 0 ? "+" : "",
|
|
8507
8563
|
formatCurrency(r.difference, currency)
|
|
8508
8564
|
] })
|
|
8509
|
-
] })
|
|
8510
|
-
|
|
8511
|
-
|
|
8565
|
+
] });
|
|
8566
|
+
const action = fechei ? (
|
|
8567
|
+
// Estado que impede não oferece a ação — e diz por quê.
|
|
8568
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.cash.cannotReviewOwn") })
|
|
8569
|
+
) : /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "shrink-0", onClick: () => setReviewing(r.sessionId), children: [
|
|
8570
|
+
/* @__PURE__ */ jsx(ClipboardCheck, { className: "h-3.5 w-3.5" }),
|
|
8512
8571
|
" ",
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8572
|
+
t2("financial.cash.review")
|
|
8573
|
+
] });
|
|
8574
|
+
if (compact) {
|
|
8575
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-2", children: [
|
|
8576
|
+
/* @__PURE__ */ jsx("p", { className: "shrink-0 text-sm font-medium tabular-nums text-foreground", children: amount }),
|
|
8577
|
+
/* @__PURE__ */ jsxs("p", { className: "min-w-0 flex-1 truncate text-xs text-muted-foreground", children: [
|
|
8578
|
+
r.closedByName ?? "\u2014",
|
|
8579
|
+
r.closedAt ? ` \xB7 ${new Date(r.closedAt).toLocaleDateString(void 0, { day: "2-digit", month: "2-digit" })}` : ""
|
|
8580
|
+
] }),
|
|
8581
|
+
action
|
|
8582
|
+
] }, r.sessionId);
|
|
8583
|
+
}
|
|
8584
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 px-4 py-3", children: [
|
|
8585
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
8586
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: amount }),
|
|
8587
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
8588
|
+
t2("financial.cash.closedBy"),
|
|
8589
|
+
" ",
|
|
8590
|
+
r.closedByName ?? "\u2014",
|
|
8591
|
+
r.closedAt ? ` \xB7 ${new Date(r.closedAt).toLocaleTimeString(void 0, HOUR_MINUTE)}` : ""
|
|
8592
|
+
] })
|
|
8593
|
+
] }),
|
|
8594
|
+
action
|
|
8595
|
+
] }, r.sessionId);
|
|
8596
|
+
})
|
|
8597
|
+
] }, day3.key || "all")),
|
|
8598
|
+
hidden > 0 && /* @__PURE__ */ jsxs(
|
|
8599
|
+
"a",
|
|
8600
|
+
{
|
|
8601
|
+
href: `#/financial/cash/registers?${REVIEW_TAB_QUERY}`,
|
|
8602
|
+
onClick: () => setCashSessionModalOpen(false),
|
|
8603
|
+
className: "flex items-center justify-between px-4 py-2.5 text-xs font-medium text-primary transition-colors hover:bg-muted/50",
|
|
8604
|
+
children: [
|
|
8605
|
+
t2("financial.cash.reviewSeeAll", { count: String(pendentes.length) }),
|
|
8606
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" })
|
|
8607
|
+
]
|
|
8608
|
+
}
|
|
8609
|
+
)
|
|
8610
|
+
] }),
|
|
8527
8611
|
reviewing && /* @__PURE__ */ jsx(
|
|
8528
8612
|
CashSessionReviewModal,
|
|
8529
8613
|
{
|
|
@@ -8554,7 +8638,9 @@ function CashRegistersView({ variant = "page" } = {}) {
|
|
|
8554
8638
|
const setCashSessionModalOpen = useFinancialStore((s) => s.setCashSessionModalOpen);
|
|
8555
8639
|
const [expandedClosing, setExpandedClosing] = useState(null);
|
|
8556
8640
|
const [viewingDetails, setViewingDetails] = useState(null);
|
|
8557
|
-
const [tab, setTab] = useState(
|
|
8641
|
+
const [tab, setTab] = useState(
|
|
8642
|
+
() => typeof window !== "undefined" && window.location.hash.includes(REVIEW_TAB_QUERY) ? "review" : "open"
|
|
8643
|
+
);
|
|
8558
8644
|
const [reviewRows, setReviewRows] = useState(null);
|
|
8559
8645
|
const [reviewError, setReviewError] = useState(null);
|
|
8560
8646
|
const loadReviews = React29__default.useCallback(async () => {
|
|
@@ -8614,49 +8700,52 @@ function CashRegistersView({ variant = "page" } = {}) {
|
|
|
8614
8700
|
}
|
|
8615
8701
|
)
|
|
8616
8702
|
] }, session.id)) });
|
|
8617
|
-
const closedSessionsList = /* @__PURE__ */ jsx("div", { className: "divide-y", children: closedSessions.map((
|
|
8618
|
-
/* @__PURE__ */
|
|
8619
|
-
|
|
8620
|
-
/* @__PURE__ */ jsxs("
|
|
8621
|
-
|
|
8622
|
-
"
|
|
8623
|
-
|
|
8703
|
+
const closedSessionsList = /* @__PURE__ */ jsx("div", { className: "divide-y", children: groupByClosingDay(closedSessions).map((day3) => /* @__PURE__ */ jsxs(React29__default.Fragment, { children: [
|
|
8704
|
+
/* @__PURE__ */ jsx(DayHeader, { dayKey: day3.key, count: day3.rows.length }),
|
|
8705
|
+
day3.rows.map((session) => /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
8706
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
8707
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: session.bankAccountName ?? t2("financial.cash.title") }),
|
|
8708
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
8709
|
+
dayKeyOf(session.openedAt) === dayKeyOf(session.closedAt) ? new Date(session.openedAt).toLocaleTimeString(void 0, HOUR_MINUTE) : new Date(session.openedAt).toLocaleString(void 0, { day: "2-digit", month: "2-digit", ...HOUR_MINUTE }),
|
|
8710
|
+
" \u2014 ",
|
|
8711
|
+
session.closedAt ? new Date(session.closedAt).toLocaleTimeString(void 0, HOUR_MINUTE) : ""
|
|
8712
|
+
] }),
|
|
8713
|
+
session.openedByName && /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
8714
|
+
t2("financial.cash.by"),
|
|
8715
|
+
" ",
|
|
8716
|
+
session.openedByName
|
|
8717
|
+
] })
|
|
8624
8718
|
] }),
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8719
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-start gap-3", children: [
|
|
8720
|
+
/* @__PURE__ */ jsxs("div", { className: "text-right", children: [
|
|
8721
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
|
|
8722
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
8723
|
+
t2("financial.cash.openLabel"),
|
|
8724
|
+
": ",
|
|
8725
|
+
formatCurrency(session.openingBalance, currency)
|
|
8726
|
+
] }),
|
|
8727
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
8728
|
+
t2("financial.cash.closeLabel"),
|
|
8729
|
+
": ",
|
|
8730
|
+
formatCurrency(session.closingBalance ?? 0, currency)
|
|
8731
|
+
] })
|
|
8638
8732
|
] }),
|
|
8639
|
-
/* @__PURE__ */ jsxs("
|
|
8640
|
-
t2("financial.cash.
|
|
8733
|
+
session.difference !== void 0 && session.difference !== 0 && /* @__PURE__ */ jsxs("p", { className: `text-xs font-semibold tabular-nums mt-0.5 ${session.difference > 0 ? "text-success" : "text-destructive"}`, children: [
|
|
8734
|
+
t2("financial.cash.diff"),
|
|
8641
8735
|
": ",
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
t2("financial.cash.diff"),
|
|
8647
|
-
": ",
|
|
8648
|
-
session.difference >= 0 ? "+" : "",
|
|
8649
|
-
formatCurrency(session.difference, currency)
|
|
8736
|
+
session.difference >= 0 ? "+" : "",
|
|
8737
|
+
formatCurrency(session.difference, currency)
|
|
8738
|
+
] }),
|
|
8739
|
+
session.notes && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5 max-w-[200px] truncate", title: session.notes, children: session.notes })
|
|
8650
8740
|
] }),
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
t2("financial.cash.details")
|
|
8741
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setViewingDetails(session.id), children: [
|
|
8742
|
+
/* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" }),
|
|
8743
|
+
" ",
|
|
8744
|
+
t2("financial.cash.details")
|
|
8745
|
+
] })
|
|
8657
8746
|
] })
|
|
8658
|
-
] })
|
|
8659
|
-
] }
|
|
8747
|
+
] }) }, session.id))
|
|
8748
|
+
] }, day3.key || "all")) });
|
|
8660
8749
|
return /* @__PURE__ */ jsxs("div", { className: inPanel ? "" : "space-y-4", children: [
|
|
8661
8750
|
!inPanel && /* @__PURE__ */ jsx(
|
|
8662
8751
|
SubpageHeader,
|
|
@@ -11999,6 +12088,7 @@ function ReconciliationView() {
|
|
|
11999
12088
|
value: accountId,
|
|
12000
12089
|
displayValue: selectedAccount?.name ?? "",
|
|
12001
12090
|
icon: Landmark,
|
|
12091
|
+
leading: selectedAccount ? /* @__PURE__ */ jsx(AccountIcon, { account: selectedAccount }) : void 0,
|
|
12002
12092
|
placeholder: "Digite...",
|
|
12003
12093
|
noResultsLabel: t2("financial.reconciliation.noIntegratedAccount"),
|
|
12004
12094
|
searchingLabel: "Buscando contas...",
|
|
@@ -12016,6 +12106,7 @@ function ReconciliationView() {
|
|
|
12016
12106
|
id: account.id,
|
|
12017
12107
|
label: account.name,
|
|
12018
12108
|
subtitle: account.bankName || account.accountNumber,
|
|
12109
|
+
icon: /* @__PURE__ */ jsx(AccountIcon, { account }),
|
|
12019
12110
|
group: ACCOUNT_GROUPS2[account.accountType] ?? "Outras contas",
|
|
12020
12111
|
data: account
|
|
12021
12112
|
}));
|
|
@@ -19812,6 +19903,7 @@ var en = {
|
|
|
19812
19903
|
"financial.invoice.paidLabel": "Paid:",
|
|
19813
19904
|
"financial.invoice.paidOn": "Paid on:",
|
|
19814
19905
|
"financial.invoice.pay": "Pay",
|
|
19906
|
+
"financial.invoice.receive": "Receive",
|
|
19815
19907
|
"financial.invoice.reverse": "Reverse",
|
|
19816
19908
|
"financial.invoice.reverseTitle": "Reverse payment",
|
|
19817
19909
|
"financial.invoice.reverseReason": "Reason for reversal",
|
|
@@ -20215,12 +20307,14 @@ var en = {
|
|
|
20215
20307
|
"financial.payment.notes": "Notes",
|
|
20216
20308
|
"financial.payment.optionalNotes": "Optional notes...",
|
|
20217
20309
|
"financial.payment.payAmount": "Pay {{amount}}",
|
|
20310
|
+
"financial.payment.receiveAmount": "Receive {{amount}}",
|
|
20218
20311
|
"financial.payment.processing": "Processing...",
|
|
20219
20312
|
"financial.payment.remaining": "{{amount}} remaining",
|
|
20220
20313
|
"financial.payment.select": "Select...",
|
|
20221
20314
|
"financial.payment.selectAccount": "Select account...",
|
|
20222
20315
|
"financial.payment.selectBrand": "Select brand...",
|
|
20223
20316
|
"financial.payment.title": "Record Payment",
|
|
20317
|
+
"financial.payment.titleReceive": "Record Receipt",
|
|
20224
20318
|
"financial.payment.changeSource": "Change method and account",
|
|
20225
20319
|
"financial.settings.allowPartial": "Allow partial payments",
|
|
20226
20320
|
"financial.settings.allowPartialDesc": "Accept payments less than the full installment amount",
|
|
@@ -20380,6 +20474,9 @@ var en = {
|
|
|
20380
20474
|
"financial.cash.by": "by",
|
|
20381
20475
|
"financial.cash.closeThisSession": "Close this session",
|
|
20382
20476
|
"financial.cash.reviewQueue": "Close review",
|
|
20477
|
+
"financial.cash.reviewSeeAll": "See all {count} closes",
|
|
20478
|
+
"financial.cash.reviewToday": "Today",
|
|
20479
|
+
"financial.cash.reviewYesterday": "Yesterday",
|
|
20383
20480
|
"financial.cash.reviewQueueEmpty": "No closes waiting for review.",
|
|
20384
20481
|
"financial.cash.closedBy": "Closed by",
|
|
20385
20482
|
"financial.cash.approve": "Approve",
|
|
@@ -20765,6 +20862,7 @@ var ptBR = {
|
|
|
20765
20862
|
"financial.invoice.paidLabel": "Pago:",
|
|
20766
20863
|
"financial.invoice.paidOn": "Pago em:",
|
|
20767
20864
|
"financial.invoice.pay": "Pagar",
|
|
20865
|
+
"financial.invoice.receive": "Receber",
|
|
20768
20866
|
"financial.invoice.reverse": "Estornar",
|
|
20769
20867
|
"financial.invoice.reverseTitle": "Estornar pagamento",
|
|
20770
20868
|
"financial.invoice.reverseReason": "Motivo do estorno",
|
|
@@ -21171,12 +21269,14 @@ var ptBR = {
|
|
|
21171
21269
|
"financial.payment.notes": "Observa\xE7\xF5es",
|
|
21172
21270
|
"financial.payment.optionalNotes": "Observa\xE7\xF5es opcionais...",
|
|
21173
21271
|
"financial.payment.payAmount": "Pagar {{amount}}",
|
|
21272
|
+
"financial.payment.receiveAmount": "Receber {{amount}}",
|
|
21174
21273
|
"financial.payment.processing": "Processando...",
|
|
21175
21274
|
"financial.payment.remaining": "{{amount}} restante",
|
|
21176
21275
|
"financial.payment.select": "Selecionar...",
|
|
21177
21276
|
"financial.payment.selectAccount": "Selecionar conta...",
|
|
21178
21277
|
"financial.payment.selectBrand": "Selecionar bandeira...",
|
|
21179
21278
|
"financial.payment.title": "Registrar Pagamento",
|
|
21279
|
+
"financial.payment.titleReceive": "Registrar Recebimento",
|
|
21180
21280
|
"financial.payment.changeSource": "Alterar forma e conta",
|
|
21181
21281
|
"financial.settings.allowPartial": "Permitir pagamentos parciais",
|
|
21182
21282
|
"financial.settings.allowPartialDesc": "Aceitar pagamentos menores que o valor total da parcela",
|
|
@@ -21336,6 +21436,9 @@ var ptBR = {
|
|
|
21336
21436
|
"financial.cash.by": "por",
|
|
21337
21437
|
"financial.cash.closeThisSession": "Fechar esta sess\xE3o",
|
|
21338
21438
|
"financial.cash.reviewQueue": "Confer\xEAncia de fechamentos",
|
|
21439
|
+
"financial.cash.reviewSeeAll": "Ver todos os {count} fechamentos",
|
|
21440
|
+
"financial.cash.reviewToday": "Hoje",
|
|
21441
|
+
"financial.cash.reviewYesterday": "Ontem",
|
|
21339
21442
|
"financial.cash.reviewQueueEmpty": "Nenhum fechamento esperando confer\xEAncia.",
|
|
21340
21443
|
"financial.cash.closedBy": "Fechado por",
|
|
21341
21444
|
"financial.cash.approve": "Aprovar",
|
|
@@ -48480,6 +48583,374 @@ COMMENT ON COLUMN public.plg_financial_invoices.tags IS 'Free labels the house p
|
|
|
48480
48583
|
CREATE INDEX IF NOT EXISTS plg_financial_invoices_tags_idx
|
|
48481
48584
|
ON public.plg_financial_invoices USING gin (tags);
|
|
48482
48585
|
`;
|
|
48586
|
+
var MIGRATION_131_THE_BILL_FOLLOWS_THE_PRICE_OF_THE_SALE = `-- 131 \u2014 The bill follows the price of the sale.
|
|
48587
|
+
--
|
|
48588
|
+
-- A receivable is raised when the sale closes, at the total of that instant.
|
|
48589
|
+
-- Both bridges (\`order_raise_receivable\`, \`fn_invoice_from_order_internal\`) are
|
|
48590
|
+
-- idempotent: once the invoice exists they return without looking at the
|
|
48591
|
+
-- amount. So a price changed AFTER closing never reached the bill \u2014 a clinic
|
|
48592
|
+
-- had an appointment at R$ 700 whose receivable still asked for R$ 140, and
|
|
48593
|
+
-- the only symptom was two different numbers on two screens.
|
|
48594
|
+
--
|
|
48595
|
+
-- The rule, in one place:
|
|
48596
|
+
-- * nothing received yet \u2192 the invoice, its lines and its open instalments
|
|
48597
|
+
-- take the sale's total;
|
|
48598
|
+
-- * something received \u2192 nothing moves. A paid invoice is immutable
|
|
48599
|
+
-- (PRD 04, 120); the screens show both numbers and a person decides;
|
|
48600
|
+
-- * the sale drops to zero \u2192 the unpaid invoice is cancelled, not left at 0.
|
|
48601
|
+
--
|
|
48602
|
+
-- The lines follow the model the bill already has. A bill raised here owns a
|
|
48603
|
+
-- copy of the sale's lines (010) and that copy is refreshed. A bill ported
|
|
48604
|
+
-- from V1 has no lines of its own \u2014 \`v_financial_invoice_lines\` reads them
|
|
48605
|
+
-- from the order \u2014 and it stays that way: giving it a copy would freeze lines
|
|
48606
|
+
-- the sync still writes on the order.
|
|
48607
|
+
--
|
|
48608
|
+
-- Receivables of SELLING orders only. A purchase's bill diverges from its
|
|
48609
|
+
-- order on purpose (interest, supplier credit \u2014 120).
|
|
48610
|
+
|
|
48611
|
+
-- \u2500\u2500 1. the reconciliation \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
48612
|
+
CREATE OR REPLACE FUNCTION public.order_reconcile_receivable_internal(p_order_id uuid)
|
|
48613
|
+
RETURNS text
|
|
48614
|
+
LANGUAGE plpgsql
|
|
48615
|
+
SECURITY DEFINER
|
|
48616
|
+
SET search_path TO ''
|
|
48617
|
+
AS $$
|
|
48618
|
+
DECLARE
|
|
48619
|
+
v_order public.orders%ROWTYPE;
|
|
48620
|
+
v_invoice public.plg_financial_invoices%ROWTYPE;
|
|
48621
|
+
v_total numeric;
|
|
48622
|
+
v_paid numeric;
|
|
48623
|
+
v_open_sum numeric;
|
|
48624
|
+
v_last uuid;
|
|
48625
|
+
v_summary text;
|
|
48626
|
+
v_own boolean;
|
|
48627
|
+
BEGIN
|
|
48628
|
+
SELECT * INTO v_order FROM public.orders WHERE id = p_order_id;
|
|
48629
|
+
IF NOT FOUND OR NOT (v_order.kind = ANY (public.order_selling_kinds())) THEN
|
|
48630
|
+
RETURN 'none';
|
|
48631
|
+
END IF;
|
|
48632
|
+
|
|
48633
|
+
-- (tenant_id, order_id) is the index; naming the tenant is what uses it.
|
|
48634
|
+
SELECT * INTO v_invoice FROM public.plg_financial_invoices
|
|
48635
|
+
WHERE tenant_id = v_order.tenant_id AND order_id = p_order_id
|
|
48636
|
+
AND cancelled_at IS NULL AND direction = 'receivable'
|
|
48637
|
+
ORDER BY created_at LIMIT 1;
|
|
48638
|
+
IF NOT FOUND THEN RETURN 'none'; END IF;
|
|
48639
|
+
|
|
48640
|
+
-- A sale billed across several live invoices has no single bill that should
|
|
48641
|
+
-- carry its whole total. Nobody does this today; deciding it here would be a guess.
|
|
48642
|
+
IF (SELECT count(*) FROM public.plg_financial_invoices
|
|
48643
|
+
WHERE tenant_id = v_order.tenant_id AND order_id = p_order_id
|
|
48644
|
+
AND cancelled_at IS NULL AND direction = 'receivable') > 1 THEN
|
|
48645
|
+
RETURN 'none';
|
|
48646
|
+
END IF;
|
|
48647
|
+
|
|
48648
|
+
v_total := round(coalesce(v_order.total, 0), 2);
|
|
48649
|
+
|
|
48650
|
+
SELECT coalesce(sum(b.paid), 0) INTO v_paid
|
|
48651
|
+
FROM public.v_financial_installment_balances b WHERE b.invoice_id = v_invoice.id;
|
|
48652
|
+
|
|
48653
|
+
v_own := EXISTS (SELECT 1 FROM public.items WHERE invoice_id = v_invoice.id);
|
|
48654
|
+
|
|
48655
|
+
IF round(coalesce(v_invoice.total, 0), 2) = v_total THEN
|
|
48656
|
+
-- Same amount, but the lines may still be the old ones: a screen that
|
|
48657
|
+
-- writes the total and THEN the lines reaches here with the bill already
|
|
48658
|
+
-- at the new amount. A line with no live sale line behind it is stale.
|
|
48659
|
+
IF v_paid = 0 AND v_own AND (
|
|
48660
|
+
(SELECT count(*) FROM public.items WHERE invoice_id = v_invoice.id)
|
|
48661
|
+
IS DISTINCT FROM (SELECT count(*) FROM public.items WHERE order_id = p_order_id)
|
|
48662
|
+
OR EXISTS (
|
|
48663
|
+
SELECT 1 FROM public.items l
|
|
48664
|
+
WHERE l.invoice_id = v_invoice.id
|
|
48665
|
+
AND NOT EXISTS (
|
|
48666
|
+
SELECT 1 FROM public.item_associations a
|
|
48667
|
+
JOIN public.items s ON s.id = a.from_item_id
|
|
48668
|
+
WHERE a.to_item_id = l.id AND s.order_id = p_order_id AND s.total = l.total))
|
|
48669
|
+
) THEN
|
|
48670
|
+
DELETE FROM public.items WHERE invoice_id = v_invoice.id;
|
|
48671
|
+
PERFORM public.plg_financial_copy_order_lines_to_invoice(p_order_id, v_invoice.id);
|
|
48672
|
+
END IF;
|
|
48673
|
+
RETURN 'in_sync';
|
|
48674
|
+
END IF;
|
|
48675
|
+
|
|
48676
|
+
IF v_paid > 0 THEN RETURN 'kept_paid'; END IF;
|
|
48677
|
+
|
|
48678
|
+
IF v_total <= 0 THEN
|
|
48679
|
+
UPDATE public.plg_financial_invoices
|
|
48680
|
+
SET cancelled_at = now(),
|
|
48681
|
+
cancel_reason = coalesce(cancel_reason, 'The sale it was raised from no longer has an amount')
|
|
48682
|
+
WHERE id = v_invoice.id;
|
|
48683
|
+
RETURN 'amended';
|
|
48684
|
+
END IF;
|
|
48685
|
+
|
|
48686
|
+
SELECT string_agg(name, ', ' ORDER BY sort_order) INTO v_summary
|
|
48687
|
+
FROM public.items WHERE order_id = p_order_id;
|
|
48688
|
+
|
|
48689
|
+
UPDATE public.plg_financial_invoices
|
|
48690
|
+
SET total = v_total,
|
|
48691
|
+
description = coalesce(nullif(v_summary, ''), description)
|
|
48692
|
+
WHERE id = v_invoice.id;
|
|
48693
|
+
|
|
48694
|
+
-- A stale copy is replaced whole. The copier refuses to run over existing
|
|
48695
|
+
-- lines, hence the delete first.
|
|
48696
|
+
IF v_own THEN
|
|
48697
|
+
DELETE FROM public.items WHERE invoice_id = v_invoice.id;
|
|
48698
|
+
PERFORM public.plg_financial_copy_order_lines_to_invoice(p_order_id, v_invoice.id);
|
|
48699
|
+
END IF;
|
|
48700
|
+
|
|
48701
|
+
-- The open instalments keep their proportions; the last one takes the cents.
|
|
48702
|
+
SELECT coalesce(sum(amount), 0) INTO v_open_sum
|
|
48703
|
+
FROM public.plg_financial_invoice_installments
|
|
48704
|
+
WHERE invoice_id = v_invoice.id AND cancelled_at IS NULL;
|
|
48705
|
+
SELECT id INTO v_last FROM public.plg_financial_invoice_installments
|
|
48706
|
+
WHERE invoice_id = v_invoice.id AND cancelled_at IS NULL
|
|
48707
|
+
ORDER BY number DESC LIMIT 1;
|
|
48708
|
+
|
|
48709
|
+
IF v_last IS NULL THEN
|
|
48710
|
+
INSERT INTO public.plg_financial_invoice_installments
|
|
48711
|
+
(tenant_id, unit_id, invoice_id, number, due_date, amount)
|
|
48712
|
+
VALUES (v_invoice.tenant_id, v_invoice.unit_id, v_invoice.id,
|
|
48713
|
+
coalesce((SELECT max(number) FROM public.plg_financial_invoice_installments
|
|
48714
|
+
WHERE invoice_id = v_invoice.id), 0) + 1,
|
|
48715
|
+
current_date, v_total);
|
|
48716
|
+
ELSE
|
|
48717
|
+
IF v_open_sum > 0 THEN
|
|
48718
|
+
UPDATE public.plg_financial_invoice_installments
|
|
48719
|
+
SET amount = round(v_total * amount / v_open_sum, 2)
|
|
48720
|
+
WHERE invoice_id = v_invoice.id AND cancelled_at IS NULL AND id <> v_last;
|
|
48721
|
+
END IF;
|
|
48722
|
+
UPDATE public.plg_financial_invoice_installments
|
|
48723
|
+
SET amount = v_total - coalesce((
|
|
48724
|
+
SELECT sum(amount) FROM public.plg_financial_invoice_installments
|
|
48725
|
+
WHERE invoice_id = v_invoice.id AND cancelled_at IS NULL AND id <> v_last), 0)
|
|
48726
|
+
WHERE id = v_last;
|
|
48727
|
+
END IF;
|
|
48728
|
+
|
|
48729
|
+
RETURN 'amended';
|
|
48730
|
+
EXCEPTION WHEN OTHERS THEN
|
|
48731
|
+
-- A closed period, or any other refusal from the books, never fails the
|
|
48732
|
+
-- sale's own write (same rule as raising the receivable, 001). The block
|
|
48733
|
+
-- rolls back whatever it had started, so the bill is left whole.
|
|
48734
|
+
RAISE WARNING 'order_reconcile_receivable: sale % kept its bill as it was: %', p_order_id, SQLERRM;
|
|
48735
|
+
RETURN 'kept_closed';
|
|
48736
|
+
END $$;
|
|
48737
|
+
|
|
48738
|
+
COMMENT ON FUNCTION public.order_reconcile_receivable_internal(uuid) IS
|
|
48739
|
+
'Makes an UNPAID receivable take its sale''s current total, lines and instalments. Returns none | in_sync | amended | kept_paid | kept_closed. A paid invoice is never touched (131).';
|
|
48740
|
+
|
|
48741
|
+
REVOKE ALL ON FUNCTION public.order_reconcile_receivable_internal(uuid) FROM PUBLIC, anon, authenticated;
|
|
48742
|
+
GRANT EXECUTE ON FUNCTION public.order_reconcile_receivable_internal(uuid) TO service_role;
|
|
48743
|
+
|
|
48744
|
+
-- \u2500\u2500 2. the door a screen uses after rewriting the sale's lines \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
48745
|
+
-- The trigger below fires on the total; a screen that writes the total and
|
|
48746
|
+
-- THEN the lines (the agenda does) calls this once it is done, so the bill's
|
|
48747
|
+
-- lines are the final ones.
|
|
48748
|
+
CREATE OR REPLACE FUNCTION public.order_reconcile_receivable(p_order_id uuid)
|
|
48749
|
+
RETURNS text
|
|
48750
|
+
LANGUAGE plpgsql
|
|
48751
|
+
SECURITY DEFINER
|
|
48752
|
+
SET search_path TO ''
|
|
48753
|
+
AS $$
|
|
48754
|
+
DECLARE v_tenant uuid;
|
|
48755
|
+
BEGIN
|
|
48756
|
+
SELECT tenant_id INTO v_tenant FROM public.orders WHERE id = p_order_id;
|
|
48757
|
+
IF v_tenant IS NULL OR v_tenant IS DISTINCT FROM app.current_tenant_id() THEN
|
|
48758
|
+
RAISE EXCEPTION 'order_reconcile_receivable: forbidden' USING ERRCODE = '42501';
|
|
48759
|
+
END IF;
|
|
48760
|
+
RETURN public.order_reconcile_receivable_internal(p_order_id);
|
|
48761
|
+
END $$;
|
|
48762
|
+
|
|
48763
|
+
COMMENT ON FUNCTION public.order_reconcile_receivable(uuid) IS
|
|
48764
|
+
'Tenant-checked door to order_reconcile_receivable_internal: it only ever aligns a bill with a sale the caller''s tenant already owns (131).';
|
|
48765
|
+
|
|
48766
|
+
REVOKE ALL ON FUNCTION public.order_reconcile_receivable(uuid) FROM PUBLIC, anon;
|
|
48767
|
+
GRANT EXECUTE ON FUNCTION public.order_reconcile_receivable(uuid) TO authenticated, service_role;
|
|
48768
|
+
|
|
48769
|
+
-- \u2500\u2500 3. the guarantee: any writer of the total, known or future \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
48770
|
+
CREATE OR REPLACE FUNCTION public.trg_order_total_reaches_its_invoice()
|
|
48771
|
+
RETURNS trigger
|
|
48772
|
+
LANGUAGE plpgsql
|
|
48773
|
+
SECURITY DEFINER
|
|
48774
|
+
SET search_path TO ''
|
|
48775
|
+
AS $$
|
|
48776
|
+
BEGIN
|
|
48777
|
+
PERFORM public.order_reconcile_receivable_internal(NEW.id);
|
|
48778
|
+
RETURN NULL;
|
|
48779
|
+
END $$;
|
|
48780
|
+
|
|
48781
|
+
DROP TRIGGER IF EXISTS orders_total_reaches_the_invoice ON public.orders;
|
|
48782
|
+
CREATE TRIGGER orders_total_reaches_the_invoice
|
|
48783
|
+
AFTER UPDATE OF total ON public.orders
|
|
48784
|
+
FOR EACH ROW
|
|
48785
|
+
WHEN (NEW.total IS DISTINCT FROM OLD.total)
|
|
48786
|
+
EXECUTE FUNCTION public.trg_order_total_reaches_its_invoice();
|
|
48787
|
+
|
|
48788
|
+
-- \u2500\u2500 4. raising again is the natural repair, and now it repairs the amount \u2500\u2500\u2500
|
|
48789
|
+
CREATE OR REPLACE FUNCTION public.order_raise_receivable(p_order_id uuid, p_due_date date DEFAULT NULL::date)
|
|
48790
|
+
RETURNS uuid
|
|
48791
|
+
LANGUAGE plpgsql
|
|
48792
|
+
SECURITY DEFINER
|
|
48793
|
+
SET search_path TO ''
|
|
48794
|
+
AS $$
|
|
48795
|
+
DECLARE
|
|
48796
|
+
v_order public.orders%ROWTYPE;
|
|
48797
|
+
v_invoice_id uuid;
|
|
48798
|
+
v_number text;
|
|
48799
|
+
v_summary text;
|
|
48800
|
+
v_total numeric;
|
|
48801
|
+
BEGIN
|
|
48802
|
+
SELECT * INTO v_order FROM public.orders WHERE id = p_order_id;
|
|
48803
|
+
IF NOT FOUND THEN RETURN NULL; END IF;
|
|
48804
|
+
|
|
48805
|
+
SELECT id INTO v_invoice_id FROM public.plg_financial_invoices
|
|
48806
|
+
WHERE order_id = p_order_id AND cancelled_at IS NULL LIMIT 1;
|
|
48807
|
+
IF v_invoice_id IS NOT NULL THEN
|
|
48808
|
+
-- Re-raising stamps the lines on a bill that has none, as before (010),
|
|
48809
|
+
-- and now also repairs the amount.
|
|
48810
|
+
PERFORM public.order_reconcile_receivable_internal(p_order_id);
|
|
48811
|
+
PERFORM public.plg_financial_copy_order_lines_to_invoice(p_order_id, v_invoice_id);
|
|
48812
|
+
RETURN v_invoice_id;
|
|
48813
|
+
END IF;
|
|
48814
|
+
|
|
48815
|
+
v_total := round(coalesce(v_order.total, 0), 2);
|
|
48816
|
+
IF v_total <= 0 THEN RETURN NULL; END IF;
|
|
48817
|
+
|
|
48818
|
+
SELECT string_agg(name, ', ' ORDER BY sort_order) INTO v_summary
|
|
48819
|
+
FROM public.items WHERE order_id = p_order_id;
|
|
48820
|
+
|
|
48821
|
+
v_number := public.plg_financial_next_invoice_number(v_order.tenant_id, 'receivable');
|
|
48822
|
+
|
|
48823
|
+
INSERT INTO public.plg_financial_invoices
|
|
48824
|
+
(tenant_id, unit_id, direction, counterparty_person_id, order_id, number,
|
|
48825
|
+
competence_date, issue_date, total, description, metadata, created_by)
|
|
48826
|
+
VALUES
|
|
48827
|
+
(v_order.tenant_id, v_order.unit_id, 'receivable', v_order.party_id, p_order_id, v_number,
|
|
48828
|
+
coalesce(v_order.created_at::date, current_date), current_date,
|
|
48829
|
+
v_total, nullif(v_summary, ''),
|
|
48830
|
+
jsonb_build_object('source', 'order_raise_receivable', 'orderKind', v_order.kind),
|
|
48831
|
+
auth.uid())
|
|
48832
|
+
RETURNING id INTO v_invoice_id;
|
|
48833
|
+
|
|
48834
|
+
PERFORM public.plg_financial_copy_order_lines_to_invoice(p_order_id, v_invoice_id);
|
|
48835
|
+
|
|
48836
|
+
INSERT INTO public.plg_financial_invoice_installments
|
|
48837
|
+
(tenant_id, unit_id, invoice_id, number, due_date, amount)
|
|
48838
|
+
VALUES (v_order.tenant_id, v_order.unit_id, v_invoice_id, 1,
|
|
48839
|
+
coalesce(p_due_date, current_date), v_total);
|
|
48840
|
+
|
|
48841
|
+
UPDATE public.orders SET
|
|
48842
|
+
stage = 'invoiced',
|
|
48843
|
+
direction = 'credit',
|
|
48844
|
+
metadata = coalesce(metadata, '{}'::jsonb)
|
|
48845
|
+
|| jsonb_build_object('invoiceNumber', v_number, 'installmentCount', 1),
|
|
48846
|
+
updated_at = now()
|
|
48847
|
+
WHERE id = p_order_id;
|
|
48848
|
+
|
|
48849
|
+
RETURN v_invoice_id;
|
|
48850
|
+
END $$;
|
|
48851
|
+
|
|
48852
|
+
CREATE OR REPLACE FUNCTION public.fn_invoice_from_order_internal(p_order_id uuid, p_due_date date DEFAULT NULL::date, p_status text DEFAULT NULL::text, p_installments integer DEFAULT 1, p_direction text DEFAULT 'credit'::text)
|
|
48853
|
+
RETURNS uuid
|
|
48854
|
+
LANGUAGE plpgsql
|
|
48855
|
+
SECURITY DEFINER
|
|
48856
|
+
SET search_path TO ''
|
|
48857
|
+
AS $$
|
|
48858
|
+
DECLARE
|
|
48859
|
+
v_order public.orders%ROWTYPE;
|
|
48860
|
+
v_invoice_id uuid;
|
|
48861
|
+
v_dir text;
|
|
48862
|
+
v_number text;
|
|
48863
|
+
v_summary text;
|
|
48864
|
+
v_contact text;
|
|
48865
|
+
v_n int := GREATEST(COALESCE(p_installments, 1), 1);
|
|
48866
|
+
v_total numeric;
|
|
48867
|
+
v_base numeric;
|
|
48868
|
+
v_amt numeric;
|
|
48869
|
+
v_alloc numeric := 0;
|
|
48870
|
+
v_due date;
|
|
48871
|
+
v_status text;
|
|
48872
|
+
i int;
|
|
48873
|
+
BEGIN
|
|
48874
|
+
SELECT * INTO v_order FROM public.orders WHERE id = p_order_id;
|
|
48875
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'fn_invoice_from_order: order % not found', p_order_id; END IF;
|
|
48876
|
+
|
|
48877
|
+
SELECT id INTO v_invoice_id FROM public.plg_financial_invoices
|
|
48878
|
+
WHERE order_id = p_order_id AND cancelled_at IS NULL LIMIT 1;
|
|
48879
|
+
IF v_invoice_id IS NOT NULL THEN
|
|
48880
|
+
-- Idempotent, and the natural repair: an order invoiced before 010 gets its
|
|
48881
|
+
-- lines, and one whose price moved since gets its amount (131). The
|
|
48882
|
+
-- reconciliation is a no-op on anything but an unpaid receivable.
|
|
48883
|
+
PERFORM public.order_reconcile_receivable_internal(p_order_id);
|
|
48884
|
+
PERFORM public.plg_financial_copy_order_lines_to_invoice(p_order_id, v_invoice_id);
|
|
48885
|
+
RETURN p_order_id;
|
|
48886
|
+
END IF;
|
|
48887
|
+
IF EXISTS (SELECT 1 FROM public.plg_financial_movements
|
|
48888
|
+
WHERE invoice_id = p_order_id AND movement_kind = 'bill'
|
|
48889
|
+
AND status <> 'cancelled') THEN
|
|
48890
|
+
RETURN p_order_id;
|
|
48891
|
+
END IF;
|
|
48892
|
+
|
|
48893
|
+
v_dir := CASE WHEN p_direction = 'debit' THEN 'payable' ELSE 'receivable' END;
|
|
48894
|
+
v_number := public.plg_financial_next_invoice_number(v_order.tenant_id, v_dir);
|
|
48895
|
+
|
|
48896
|
+
SELECT string_agg(name, ', ' ORDER BY sort_order) INTO v_summary
|
|
48897
|
+
FROM public.items WHERE order_id = p_order_id;
|
|
48898
|
+
SELECT name INTO v_contact FROM public.people WHERE id = v_order.party_id;
|
|
48899
|
+
|
|
48900
|
+
v_status := v_order.status;
|
|
48901
|
+
IF p_status IS NOT NULL AND EXISTS (
|
|
48902
|
+
SELECT 1 FROM public.order_kinds k
|
|
48903
|
+
WHERE k.kind = v_order.kind
|
|
48904
|
+
AND (k.statuses IS NULL OR p_status = ANY (k.statuses))
|
|
48905
|
+
) THEN
|
|
48906
|
+
v_status := p_status;
|
|
48907
|
+
END IF;
|
|
48908
|
+
|
|
48909
|
+
UPDATE public.orders SET
|
|
48910
|
+
status = v_status,
|
|
48911
|
+
stage = 'invoiced',
|
|
48912
|
+
direction = p_direction,
|
|
48913
|
+
metadata = COALESCE(metadata, '{}'::jsonb) || jsonb_build_object(
|
|
48914
|
+
'itemsSummary', COALESCE(v_summary, ''),
|
|
48915
|
+
'contactName', v_contact,
|
|
48916
|
+
'installmentCount', v_n,
|
|
48917
|
+
'quoteNumber', v_order.reference_number,
|
|
48918
|
+
'direction', p_direction,
|
|
48919
|
+
'invoiceNumber', v_number
|
|
48920
|
+
),
|
|
48921
|
+
updated_at = now()
|
|
48922
|
+
WHERE id = p_order_id;
|
|
48923
|
+
|
|
48924
|
+
v_total := round(COALESCE(v_order.total, 0), 2);
|
|
48925
|
+
v_due := COALESCE(p_due_date, current_date);
|
|
48926
|
+
|
|
48927
|
+
INSERT INTO public.plg_financial_invoices
|
|
48928
|
+
(tenant_id, unit_id, direction, counterparty_person_id, order_id, number, competence_date, issue_date,
|
|
48929
|
+
total, description, metadata, created_by)
|
|
48930
|
+
VALUES
|
|
48931
|
+
(v_order.tenant_id, v_order.unit_id, v_dir, v_order.party_id, p_order_id, v_number,
|
|
48932
|
+
COALESCE(v_order.created_at::date, current_date), current_date,
|
|
48933
|
+
v_total, NULLIF(v_summary, ''),
|
|
48934
|
+
jsonb_build_object('source', 'fn_invoice_from_order', 'orderKind', v_order.kind),
|
|
48935
|
+
auth.uid())
|
|
48936
|
+
RETURNING id INTO v_invoice_id;
|
|
48937
|
+
|
|
48938
|
+
PERFORM public.plg_financial_copy_order_lines_to_invoice(p_order_id, v_invoice_id);
|
|
48939
|
+
|
|
48940
|
+
v_base := round(v_total / v_n, 2);
|
|
48941
|
+
FOR i IN 1..v_n LOOP
|
|
48942
|
+
IF i = v_n THEN v_amt := round(v_total - v_alloc, 2);
|
|
48943
|
+
ELSE v_amt := v_base; v_alloc := v_alloc + v_base; END IF;
|
|
48944
|
+
|
|
48945
|
+
INSERT INTO public.plg_financial_invoice_installments
|
|
48946
|
+
(tenant_id, unit_id, invoice_id, number, due_date, amount)
|
|
48947
|
+
VALUES (v_order.tenant_id, v_order.unit_id, v_invoice_id, i,
|
|
48948
|
+
(v_due + ((i - 1) || ' months')::interval)::date, v_amt);
|
|
48949
|
+
END LOOP;
|
|
48950
|
+
|
|
48951
|
+
RETURN p_order_id;
|
|
48952
|
+
END $$;
|
|
48953
|
+
`;
|
|
48483
48954
|
var MIGRATIONS = [
|
|
48484
48955
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
48485
48956
|
{ id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
|
|
@@ -48611,7 +49082,8 @@ var MIGRATIONS = [
|
|
|
48611
49082
|
{ id: "127_a_bill_line_enters_stock_as_it_really_arrived", sql: MIGRATION_127_A_BILL_LINE_ENTERS_STOCK_AS_IT_REALLY_ARRIVED },
|
|
48612
49083
|
{ id: "128_a_stock_receipt_shows_what_was_paid", sql: MIGRATION_128_A_STOCK_RECEIPT_SHOWS_WHAT_WAS_PAID },
|
|
48613
49084
|
{ id: "129_a_bill_can_be_written_in_another_currency", sql: MIGRATION_129_A_BILL_CAN_BE_WRITTEN_IN_ANOTHER_CURRENCY },
|
|
48614
|
-
{ id: "130_a_bill_can_be_tagged", sql: MIGRATION_130_A_BILL_CAN_BE_TAGGED }
|
|
49085
|
+
{ id: "130_a_bill_can_be_tagged", sql: MIGRATION_130_A_BILL_CAN_BE_TAGGED },
|
|
49086
|
+
{ id: "131_the_bill_follows_the_price_of_the_sale", sql: MIGRATION_131_THE_BILL_FOLLOWS_THE_PRICE_OF_THE_SALE }
|
|
48615
49087
|
];
|
|
48616
49088
|
var STANDALONE_CONFIG = resolveConfig();
|
|
48617
49089
|
registerTranslations(financialLocales);
|