@fayz-ai/plugin-financial 0.14.11 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ClientAccountModal.d.ts +3 -0
- package/dist/components/ClientAccountModal.d.ts.map +1 -1
- package/dist/components/FinancialGeneralSettings.d.ts +18 -1
- package/dist/components/FinancialGeneralSettings.d.ts.map +1 -1
- package/dist/components/PaymentModal.d.ts +1 -1
- package/dist/components/PaymentModal.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/types.d.ts +25 -0
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2438 -717
- 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 +9 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/ClientAccountPanel.d.ts.map +1 -1
- package/dist/views/CommissionRulesView.d.ts.map +1 -1
- package/dist/views/CommissionsView.d.ts.map +1 -1
- package/dist/views/FinancialPage.d.ts +1 -1
- package/dist/views/FinancialPage.d.ts.map +1 -1
- package/dist/views/GiftCardsView.d.ts.map +1 -1
- package/dist/views/InvoiceDetailView.d.ts +6 -2
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/NotasFiscaisView.d.ts.map +1 -1
- package/dist/views/ReconciliationView.d.ts.map +1 -1
- package/dist/views/StatementsView.d.ts.map +1 -1
- package/dist/views/payable-agent.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React15 from 'react';
|
|
2
|
-
import React15__default, { useState, useEffect, useMemo,
|
|
3
|
-
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, countByTenant, getSupabaseClientOptional, getActiveTenantId, exportCSV
|
|
4
|
-
import { toast, CurrencyInput, DatePicker, Button, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, PageTransition, ModulePage,
|
|
5
|
-
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, ModuleActionBar,
|
|
6
|
-
import { CircleDollarSign, FileText,
|
|
2
|
+
import React15__default, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
3
|
+
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, registerTranslations, createSafeDataProvider, registerMessageDefaults, reportDataError, useTranslation, createPendingCommand, useDataChanged, countByTenant, getSupabaseClientOptional, getActiveTenantId, exportCSV } from '@fayz-ai/core';
|
|
4
|
+
import { ModalTask, toast, useInModalTaskHost, CurrencyInput, DatePicker, Button, SubpageHeader, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DataTable, PageTransition, ModulePage, Skeleton, Badge, TooltipProvider, ConfirmDialog, MultiSelect, Tooltip, TooltipTrigger, TooltipContent, Input, Checkbox, ModalFooter, DashboardCanvas, ListFilterBar, DateRangeControl, PageHeaderActions, Card, FilterMenu, FilterMenuRow, Hint, useSaveBar, tourAnchor, SearchSelect, ListView, KpiCard, ChartWidget, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
|
|
5
|
+
import { createPluginContext, fayzConnectorOAuth, formatCurrency, RightRailPage, PluginSettingsPanel, dedup, useUnitStore, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailView, PersonLink, useTenantPluginSettings, useModuleNavigation, usePermissionOptional, useModuleNavAccess, ModuleActionBar, SettingsGroup, ToggleRow, useAgentSurface, parseViewId, EntitlementGate, UpgradePrompt, createArchetypeLookup, useChatStore, adminNavigateTo, revealPage, GetStarted, useAuth, resolveEntityRoute, PermissionGate, useLimitGuard, useLookupCreate, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, invalidateLimit, askAI, requestChatUpload, toInlineFile } from '@fayz-ai/admin';
|
|
6
|
+
import { CircleDollarSign, FileText, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, Pencil, Check, X, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, CalendarDays, ExternalLink, DollarSign, LayoutList, Layers, Paperclip, Camera, Undo2, ChevronDown, Building2, Wallet, Plus, AlertTriangle, Filter, TrendingUp, Copy, Trash2, Landmark, Clock, BookOpen, ChevronRight, Info, Calculator, StickyNote, Play, Square, ChevronUp, ClipboardCheck, FileCheck2, FileX2, RefreshCw, Send, CalendarClock, Gift, Loader2, BadgeCheck, Target, Settings, ArrowRight, Receipt, Users, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, Search, Download, CheckCheck, MoreVertical, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, ArrowUpRight, ArrowDownRight, EyeOff, Eye } from 'lucide-react';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
8
|
import { createPortal } from 'react-dom';
|
|
9
9
|
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
@@ -685,6 +685,8 @@ var STATUS_OPTIONS = [
|
|
|
685
685
|
{ value: "overdue", labelKey: "financial.invoice.statusOverdue", color: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert },
|
|
686
686
|
{ value: "cancelled", labelKey: "financial.invoice.statusCancelled", color: "bg-muted text-muted-foreground", icon: Ban }
|
|
687
687
|
];
|
|
688
|
+
var DATE_PRESETS = ["thisMonth", "lastMonth", "last30", "last90"];
|
|
689
|
+
var MAX_RANGE_DAYS = 90;
|
|
688
690
|
var isoDate = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
689
691
|
function presetRange(preset) {
|
|
690
692
|
const now2 = /* @__PURE__ */ new Date();
|
|
@@ -698,10 +700,16 @@ function presetRange(preset) {
|
|
|
698
700
|
from.setDate(from.getDate() - 29);
|
|
699
701
|
return { from: isoDate(from), to: isoDate(now2) };
|
|
700
702
|
}
|
|
701
|
-
case "
|
|
702
|
-
|
|
703
|
+
case "last90": {
|
|
704
|
+
const from = new Date(now2);
|
|
705
|
+
from.setDate(from.getDate() - (MAX_RANGE_DAYS - 1));
|
|
706
|
+
return { from: isoDate(from), to: isoDate(now2) };
|
|
707
|
+
}
|
|
703
708
|
}
|
|
704
709
|
}
|
|
710
|
+
function spanDays(from, to) {
|
|
711
|
+
return Math.round(((/* @__PURE__ */ new Date(`${to}T00:00:00`)).getTime() - (/* @__PURE__ */ new Date(`${from}T00:00:00`)).getTime()) / 864e5) + 1;
|
|
712
|
+
}
|
|
705
713
|
function StatusBadge({ status }) {
|
|
706
714
|
const t2 = useTranslation();
|
|
707
715
|
const opt = STATUS_OPTIONS.find((o) => o.value === status);
|
|
@@ -820,22 +828,33 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
820
828
|
const [datePreset, setDatePreset] = useState("custom");
|
|
821
829
|
const [dateFrom, setDateFrom] = useState(mesCorrente.from);
|
|
822
830
|
const [dateTo, setDateTo] = useState(mesCorrente.to);
|
|
823
|
-
const dateRange = useMemo(() => {
|
|
824
|
-
|
|
825
|
-
return { from: dateFrom || "0001-01-01", to: dateTo || "9999-12-31" };
|
|
826
|
-
}, [dateFrom, dateTo]);
|
|
831
|
+
const dateRange = useMemo(() => ({ from: dateFrom, to: dateTo }), [dateFrom, dateTo]);
|
|
832
|
+
const dateIsDefault = dateFrom === mesCorrente.from && dateTo === mesCorrente.to;
|
|
827
833
|
const applyPreset = (value) => {
|
|
828
834
|
setDatePreset(value);
|
|
829
|
-
if (value === "all") {
|
|
830
|
-
setDateFrom("");
|
|
831
|
-
setDateTo("");
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
835
|
if (value === "custom") return;
|
|
835
836
|
const range = presetRange(value);
|
|
836
837
|
setDateFrom(range.from);
|
|
837
838
|
setDateTo(range.to);
|
|
838
839
|
};
|
|
840
|
+
const setFromClamped = (from) => {
|
|
841
|
+
if (!from) return;
|
|
842
|
+
setDateFrom(from);
|
|
843
|
+
if (dateTo && spanDays(from, dateTo) > MAX_RANGE_DAYS) {
|
|
844
|
+
const to = /* @__PURE__ */ new Date(`${from}T00:00:00`);
|
|
845
|
+
to.setDate(to.getDate() + (MAX_RANGE_DAYS - 1));
|
|
846
|
+
setDateTo(isoDate(to));
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
const setToClamped = (to) => {
|
|
850
|
+
if (!to) return;
|
|
851
|
+
setDateTo(to);
|
|
852
|
+
if (dateFrom && spanDays(dateFrom, to) > MAX_RANGE_DAYS) {
|
|
853
|
+
const from = /* @__PURE__ */ new Date(`${to}T00:00:00`);
|
|
854
|
+
from.setDate(from.getDate() - (MAX_RANGE_DAYS - 1));
|
|
855
|
+
setDateFrom(isoDate(from));
|
|
856
|
+
}
|
|
857
|
+
};
|
|
839
858
|
useEffect(() => {
|
|
840
859
|
let active = true;
|
|
841
860
|
Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
|
|
@@ -955,68 +974,107 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
955
974
|
direction === "debit" ? "contas-a-pagar" : "contas-a-receber"
|
|
956
975
|
);
|
|
957
976
|
};
|
|
958
|
-
const
|
|
959
|
-
|
|
977
|
+
const filtersMenu = /* @__PURE__ */ jsx(
|
|
978
|
+
FilterMenu,
|
|
960
979
|
{
|
|
961
|
-
label: t2("
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
setDateFrom(from);
|
|
969
|
-
setDateTo(to);
|
|
980
|
+
label: t2("crud.list.filters"),
|
|
981
|
+
activeCount: (accountId ? 1 : 0) + (costCenterId ? 1 : 0) + (statusFilter.length ? 1 : 0) + (dateIsDefault ? 0 : 1),
|
|
982
|
+
onClear: () => {
|
|
983
|
+
setAccountId("");
|
|
984
|
+
setCostCenterId("");
|
|
985
|
+
setStatusFilter([]);
|
|
986
|
+
setDatePreset("custom");
|
|
987
|
+
setDateFrom(mesCorrente.from);
|
|
988
|
+
setDateTo(mesCorrente.to);
|
|
970
989
|
},
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
{
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
990
|
+
clearLabel: t2("financial.invoice.filterClear"),
|
|
991
|
+
fields: [
|
|
992
|
+
{
|
|
993
|
+
id: "status",
|
|
994
|
+
label: t2("financial.invoice.statusCol"),
|
|
995
|
+
icon: CircleDashed,
|
|
996
|
+
activeCount: statusFilter.length,
|
|
997
|
+
render: (_close, q) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
998
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: statusFilter.length === 0, onClick: () => setStatusFilter([]), children: t2("crud.list.allFacet") }),
|
|
999
|
+
tags.filter((o) => o.label.toLowerCase().includes(q.toLowerCase())).map((o) => {
|
|
1000
|
+
const on = statusFilter.includes(o.value);
|
|
1001
|
+
return /* @__PURE__ */ jsx(
|
|
1002
|
+
FilterMenuRow,
|
|
1003
|
+
{
|
|
1004
|
+
active: on,
|
|
1005
|
+
onClick: () => setStatusFilter(on ? statusFilter.filter((s) => s !== o.value) : [...statusFilter, o.value]),
|
|
1006
|
+
children: /* @__PURE__ */ jsx("span", { className: "truncate", children: o.label })
|
|
1007
|
+
},
|
|
1008
|
+
o.value
|
|
1009
|
+
);
|
|
1010
|
+
})
|
|
1011
|
+
] })
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
id: "period",
|
|
1015
|
+
label: t2("financial.invoice.filterPeriod"),
|
|
1016
|
+
icon: CalendarDays,
|
|
1017
|
+
activeCount: dateIsDefault ? 0 : 1,
|
|
1018
|
+
render: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1019
|
+
DATE_PRESETS.map((p) => /* @__PURE__ */ jsx(FilterMenuRow, { active: datePreset === p, onClick: () => applyPreset(p), children: t2(`financial.invoice.filter${p.charAt(0).toUpperCase()}${p.slice(1)}`) }, p)),
|
|
1020
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: datePreset === "custom", onClick: () => setDatePreset("custom"), children: t2("financial.invoice.filterCustom") }),
|
|
1021
|
+
datePreset === "custom" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 p-1.5", children: [
|
|
1022
|
+
/* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateFrom, onChange: setFromClamped }),
|
|
1023
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
|
|
1024
|
+
/* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateTo, onChange: setToClamped })
|
|
1025
|
+
] }),
|
|
1026
|
+
/* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-[10px] text-muted-foreground", children: t2("financial.invoice.filterMaxRange") })
|
|
1027
|
+
] })
|
|
1028
|
+
},
|
|
1029
|
+
...postable.length > 0 ? [{
|
|
1030
|
+
id: "account",
|
|
1031
|
+
label: t2("financial.invoice.filterAccount"),
|
|
1032
|
+
icon: Landmark,
|
|
1033
|
+
activeCount: accountId ? 1 : 0,
|
|
1034
|
+
searchable: true,
|
|
1035
|
+
render: (close, q) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1036
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !accountId, onClick: () => {
|
|
1037
|
+
setAccountId("");
|
|
1038
|
+
close();
|
|
1039
|
+
}, children: t2("financial.invoice.filterAllAccounts") }),
|
|
1040
|
+
postable.filter((a) => accountLabel(a).toLowerCase().includes(q.toLowerCase())).map((a) => /* @__PURE__ */ jsx(FilterMenuRow, { active: accountId === a.id, onClick: () => {
|
|
1041
|
+
setAccountId(a.id);
|
|
1042
|
+
close();
|
|
1043
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: accountLabel(a) }) }, a.id))
|
|
1044
|
+
] })
|
|
1045
|
+
}] : [],
|
|
1046
|
+
...costCenters.length > 0 ? [{
|
|
1047
|
+
id: "costCenter",
|
|
1048
|
+
label: t2("financial.invoice.filterCostCenter"),
|
|
1049
|
+
icon: Building2,
|
|
1050
|
+
activeCount: costCenterId ? 1 : 0,
|
|
1051
|
+
searchable: true,
|
|
1052
|
+
render: (close, q) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1053
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !costCenterId, onClick: () => {
|
|
1054
|
+
setCostCenterId("");
|
|
1055
|
+
close();
|
|
1056
|
+
}, children: t2("financial.invoice.filterAllCostCenters") }),
|
|
1057
|
+
costCenters.filter((c) => `${c.code} ${c.name}`.toLowerCase().includes(q.toLowerCase())).map((c) => /* @__PURE__ */ jsx(FilterMenuRow, { active: costCenterId === c.id, onClick: () => {
|
|
1058
|
+
setCostCenterId(c.id);
|
|
1059
|
+
close();
|
|
1060
|
+
}, children: /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
1061
|
+
c.code,
|
|
1062
|
+
" - ",
|
|
1063
|
+
c.name
|
|
1064
|
+
] }) }, c.id))
|
|
1065
|
+
] })
|
|
1066
|
+
}] : []
|
|
1067
|
+
]
|
|
981
1068
|
}
|
|
982
1069
|
);
|
|
983
|
-
const
|
|
984
|
-
postable.length > 0 && /* @__PURE__ */ jsx(
|
|
985
|
-
FilterChip,
|
|
986
|
-
{
|
|
987
|
-
icon: Landmark,
|
|
988
|
-
label: t2("financial.invoice.filterAccount"),
|
|
989
|
-
allLabel: t2("financial.invoice.filterAllAccounts"),
|
|
990
|
-
value: accountId || void 0,
|
|
991
|
-
onChange: (next) => setAccountId(next ?? ""),
|
|
992
|
-
options: postable.map((account) => ({
|
|
993
|
-
value: account.id,
|
|
994
|
-
label: accountLabel(account)
|
|
995
|
-
}))
|
|
996
|
-
}
|
|
997
|
-
),
|
|
998
|
-
costCenters.length > 0 && /* @__PURE__ */ jsx(
|
|
999
|
-
FilterChip,
|
|
1000
|
-
{
|
|
1001
|
-
icon: Building2,
|
|
1002
|
-
label: t2("financial.invoice.filterCostCenter"),
|
|
1003
|
-
allLabel: t2("financial.invoice.filterAllCostCenters"),
|
|
1004
|
-
value: costCenterId || void 0,
|
|
1005
|
-
onChange: (next) => setCostCenterId(next ?? ""),
|
|
1006
|
-
options: costCenters.map((costCenter) => ({
|
|
1007
|
-
value: costCenter.id,
|
|
1008
|
-
label: `${costCenter.code} - ${costCenter.name}`
|
|
1009
|
-
}))
|
|
1010
|
-
}
|
|
1011
|
-
)
|
|
1012
|
-
] });
|
|
1013
|
-
const advancedFilterCount = (accountId ? 1 : 0) + (costCenterId ? 1 : 0);
|
|
1014
|
-
const activeFilterCount = advancedFilterCount + (statusFilter.length > 0 ? 1 : 0) + (dateFrom || dateTo ? 1 : 0);
|
|
1070
|
+
const activeFilterCount = (accountId ? 1 : 0) + (costCenterId ? 1 : 0) + (statusFilter.length > 0 ? 1 : 0) + (dateIsDefault ? 0 : 1);
|
|
1015
1071
|
const clearFilters = () => {
|
|
1016
1072
|
setAccountId("");
|
|
1017
1073
|
setCostCenterId("");
|
|
1018
1074
|
setStatusFilter([]);
|
|
1019
|
-
|
|
1075
|
+
setDatePreset("custom");
|
|
1076
|
+
setDateFrom(mesCorrente.from);
|
|
1077
|
+
setDateTo(mesCorrente.to);
|
|
1020
1078
|
};
|
|
1021
1079
|
const newActions = onNewGated ? [
|
|
1022
1080
|
{
|
|
@@ -1060,14 +1118,8 @@ function InvoiceListView({ direction, onNew, onNewReceipt, onEdit }) {
|
|
|
1060
1118
|
search,
|
|
1061
1119
|
onSearchChange: setSearch,
|
|
1062
1120
|
searchDebounce: 0,
|
|
1063
|
-
|
|
1064
|
-
allTagLabel: t2("crud.list.allFacet"),
|
|
1065
|
-
activeTag: statusFilter.length === 1 ? statusFilter[0] : void 0,
|
|
1066
|
-
onTagChange: (v) => setStatusFilter(v ? [v] : []),
|
|
1067
|
-
filters: listFilters,
|
|
1068
|
-
advancedFilters,
|
|
1121
|
+
filtersTrigger: filtersMenu,
|
|
1069
1122
|
activeFilterCount,
|
|
1070
|
-
advancedFilterCount,
|
|
1071
1123
|
onClearFilters: clearFilters,
|
|
1072
1124
|
clearFiltersLabel: t2("financial.invoice.filterClear"),
|
|
1073
1125
|
filtersLabel: t2("crud.list.filters"),
|
|
@@ -1544,7 +1596,12 @@ function askHowItWasPaid(invoiceId, options) {
|
|
|
1544
1596
|
};
|
|
1545
1597
|
}
|
|
1546
1598
|
function createPayableTools({ config, provider }) {
|
|
1547
|
-
const lookup = config.contactLookup ?? createArchetypeLookup({
|
|
1599
|
+
const lookup = config.contactLookup ?? createArchetypeLookup({
|
|
1600
|
+
archetype: "person",
|
|
1601
|
+
kind: "supplier",
|
|
1602
|
+
createKinds: ["supplier", "staff", "customer"],
|
|
1603
|
+
kindLabels: { supplier: "Fornecedor", customer: "Cliente", staff: "Funcion\xE1rio" }
|
|
1604
|
+
});
|
|
1548
1605
|
return [
|
|
1549
1606
|
{
|
|
1550
1607
|
tool: RESOLVE_CONTEXT,
|
|
@@ -2157,7 +2214,12 @@ function PayableDraftSurface({ config, draft }) {
|
|
|
2157
2214
|
const live = React15.useRef(draft);
|
|
2158
2215
|
live.current = draft;
|
|
2159
2216
|
const lookup = React15.useMemo(
|
|
2160
|
-
() => config.contactLookup ?? createArchetypeLookup({
|
|
2217
|
+
() => config.contactLookup ?? createArchetypeLookup({
|
|
2218
|
+
archetype: "person",
|
|
2219
|
+
kind: "supplier",
|
|
2220
|
+
createKinds: ["supplier", "staff", "customer"],
|
|
2221
|
+
kindLabels: { supplier: "Fornecedor", customer: "Cliente", staff: "Funcion\xE1rio" }
|
|
2222
|
+
}),
|
|
2161
2223
|
[config.contactLookup]
|
|
2162
2224
|
);
|
|
2163
2225
|
React15.useEffect(() => {
|
|
@@ -2432,6 +2494,173 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
2432
2494
|
}
|
|
2433
2495
|
}
|
|
2434
2496
|
const SelectedIcon = selectedType ? TYPE_ICONS[txType] ?? Banknote : null;
|
|
2497
|
+
const inTaskHost = useInModalTaskHost();
|
|
2498
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2499
|
+
pending2 && /* @__PURE__ */ jsx("p", { role: "status", className: "px-5 pt-4 text-sm text-warning", children: t2("financial.payment.pendingRetry") }),
|
|
2500
|
+
/* @__PURE__ */ jsxs("fieldset", { disabled: pending2, className: "px-5 py-4 space-y-4", children: [
|
|
2501
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2502
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.method") }),
|
|
2503
|
+
/* @__PURE__ */ jsx(
|
|
2504
|
+
"div",
|
|
2505
|
+
{
|
|
2506
|
+
className: "grid transition-all duration-200 ease-out",
|
|
2507
|
+
style: { gridTemplateRows: selectedTypeId && !expandMethodPicker ? "1fr" : "0fr" },
|
|
2508
|
+
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: selectedType && /* @__PURE__ */ jsxs(
|
|
2509
|
+
"button",
|
|
2510
|
+
{
|
|
2511
|
+
onClick: () => setExpandMethodPicker(true),
|
|
2512
|
+
className: `flex items-center gap-2 mt-1.5 w-full rounded-button border px-3 py-2.5 text-left transition-colors hover:bg-muted/30 ${TYPE_COLORS[txType] ?? ""}`,
|
|
2513
|
+
"data-active": "true",
|
|
2514
|
+
children: [
|
|
2515
|
+
SelectedIcon && /* @__PURE__ */ jsx(SelectedIcon, { className: `h-4 w-4 ${ICON_COLORS[txType] ?? ""}` }),
|
|
2516
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium flex-1", children: selectedType?.name }),
|
|
2517
|
+
/* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3 text-muted-foreground" })
|
|
2518
|
+
]
|
|
2519
|
+
}
|
|
2520
|
+
) })
|
|
2521
|
+
}
|
|
2522
|
+
),
|
|
2523
|
+
/* @__PURE__ */ jsx(
|
|
2524
|
+
"div",
|
|
2525
|
+
{
|
|
2526
|
+
className: "grid transition-all duration-200 ease-out",
|
|
2527
|
+
style: { gridTemplateRows: expandMethodPicker ? "1fr" : "0fr" },
|
|
2528
|
+
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: paymentMethodTypes.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-input bg-muted/30 px-3 py-3 text-center mt-1.5", children: [
|
|
2529
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.payment.noMethods") }),
|
|
2530
|
+
/* @__PURE__ */ jsxs("a", { href: "#/financial/settings", className: "mt-1.5 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
2531
|
+
t2("financial.payment.configureMethods"),
|
|
2532
|
+
" \u2192"
|
|
2533
|
+
] })
|
|
2534
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2 mt-1.5", children: paymentMethodTypes.map((type) => {
|
|
2535
|
+
const tt = type.transactionType ?? "";
|
|
2536
|
+
const Icon = TYPE_ICONS[tt] ?? Banknote;
|
|
2537
|
+
const isActive = selectedTypeId === type.id;
|
|
2538
|
+
return /* @__PURE__ */ jsxs(
|
|
2539
|
+
"button",
|
|
2540
|
+
{
|
|
2541
|
+
"data-active": isActive,
|
|
2542
|
+
onClick: () => handleSelectType(type.id),
|
|
2543
|
+
className: `flex flex-col items-center gap-1.5 rounded-button border px-2 py-3 text-center transition-all ${TYPE_COLORS[tt] ?? TYPE_COLORS.cash}`,
|
|
2544
|
+
children: [
|
|
2545
|
+
/* @__PURE__ */ jsx(Icon, { className: `h-5 w-5 ${isActive ? ICON_COLORS[tt] ?? "" : "text-muted-foreground"}` }),
|
|
2546
|
+
/* @__PURE__ */ jsx("span", { className: `text-[11px] font-medium leading-tight ${isActive ? "text-foreground" : "text-muted-foreground"}`, children: type.name })
|
|
2547
|
+
]
|
|
2548
|
+
},
|
|
2549
|
+
type.id
|
|
2550
|
+
);
|
|
2551
|
+
}) }) })
|
|
2552
|
+
}
|
|
2553
|
+
)
|
|
2554
|
+
] }),
|
|
2555
|
+
selectedTypeId && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2556
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 stagger-field", style: { animationDelay: "0ms" }, children: [
|
|
2557
|
+
/* @__PURE__ */ jsx(
|
|
2558
|
+
CurrencyInput,
|
|
2559
|
+
{
|
|
2560
|
+
label: t2("financial.payment.amount"),
|
|
2561
|
+
value: amount,
|
|
2562
|
+
onChange: setAmount,
|
|
2563
|
+
symbol: currency.symbol,
|
|
2564
|
+
locale: currency.locale,
|
|
2565
|
+
currencyCode: currency.code
|
|
2566
|
+
}
|
|
2567
|
+
),
|
|
2568
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2569
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.date") }),
|
|
2570
|
+
/* @__PURE__ */ jsx(DatePicker, { value: paymentDate, onChange: setPaymentDate, className: "mt-1" })
|
|
2571
|
+
] })
|
|
2572
|
+
] }),
|
|
2573
|
+
filteredMethods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
|
|
2574
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: needsCard ? t2("financial.payment.card") : t2("financial.payment.account") }),
|
|
2575
|
+
/* @__PURE__ */ jsxs("select", { value: paymentMethodId, onChange: (e) => setPaymentMethodId(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
|
|
2576
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.select") }),
|
|
2577
|
+
filteredMethods.map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: m.name }, m.id))
|
|
2578
|
+
] })
|
|
2579
|
+
] }),
|
|
2580
|
+
needsCard && /* @__PURE__ */ jsxs("div", { className: `grid gap-3 stagger-field ${needsCardInstallments ? "grid-cols-2" : "grid-cols-1"}`, style: { animationDelay: "100ms" }, children: [
|
|
2581
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2582
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.cardBrand") }),
|
|
2583
|
+
/* @__PURE__ */ jsxs("select", { value: cardBrand, onChange: (e) => setCardBrand(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
|
|
2584
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.selectBrand") }),
|
|
2585
|
+
["Visa", "Mastercard", "American Express", "Elo", "Hipercard", "Diners Club"].map((b) => /* @__PURE__ */ jsx("option", { value: b, children: b }, b))
|
|
2586
|
+
] })
|
|
2587
|
+
] }),
|
|
2588
|
+
needsCardInstallments && /* @__PURE__ */ jsxs("div", { children: [
|
|
2589
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.cardInstallments") }),
|
|
2590
|
+
/* @__PURE__ */ jsx("select", { value: cardInstallments, onChange: (e) => setCardInstallments(Number(e.target.value)), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: Array.from({ length: 12 }, (_, i) => i + 1).map((n) => /* @__PURE__ */ jsxs("option", { value: n, children: [
|
|
2591
|
+
n,
|
|
2592
|
+
"x ",
|
|
2593
|
+
n > 1 ? formatCurrency(amount / n, currency) : ""
|
|
2594
|
+
] }, n)) })
|
|
2595
|
+
] })
|
|
2596
|
+
] }),
|
|
2597
|
+
needsOpenCashSession && /* @__PURE__ */ jsxs("div", { className: "stagger-field rounded-lg border border-dashed border-warning/40 bg-warning/5 px-3 py-2.5 text-center", children: [
|
|
2598
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.payment.cashNeedsOpenSession") }),
|
|
2599
|
+
/* @__PURE__ */ jsxs("a", { href: "#/financial/cash-registers", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
2600
|
+
t2("financial.payment.openCashSession"),
|
|
2601
|
+
" \u2192"
|
|
2602
|
+
] })
|
|
2603
|
+
] }),
|
|
2604
|
+
/* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
|
|
2605
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.bankAccount") }),
|
|
2606
|
+
eligibleAccounts2.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
|
|
2607
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: bankAccounts.length > 0 && selectedType ? t2("financial.payment.noAccountForMethod", { method: selectedType.name }) : t2("financial.payment.noAccounts") }),
|
|
2608
|
+
/* @__PURE__ */ jsxs("a", { href: "#/settings/financial/bank-accounts", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
2609
|
+
t2("financial.payment.configureAccounts"),
|
|
2610
|
+
" \u2192"
|
|
2611
|
+
] })
|
|
2612
|
+
] }) : /* @__PURE__ */ jsxs("select", { value: bankAccountId, onChange: (e) => setBankAccountId(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
|
|
2613
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.selectAccount") }),
|
|
2614
|
+
eligibleAccounts2.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
|
|
2615
|
+
] })
|
|
2616
|
+
] }),
|
|
2617
|
+
/* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "150ms" }, children: [
|
|
2618
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.notes") }),
|
|
2619
|
+
/* @__PURE__ */ jsx("input", { type: "text", value: notes, onChange: (e) => setNotes(e.target.value), placeholder: t2("financial.payment.optionalNotes"), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" })
|
|
2620
|
+
] })
|
|
2621
|
+
] }, selectedTypeId)
|
|
2622
|
+
] }),
|
|
2623
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
2624
|
+
.stagger-field {
|
|
2625
|
+
animation: field-slide-in 250ms ease-out both;
|
|
2626
|
+
}
|
|
2627
|
+
@keyframes field-slide-in {
|
|
2628
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
2629
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2630
|
+
}
|
|
2631
|
+
` })
|
|
2632
|
+
] });
|
|
2633
|
+
const footer = /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 justify-end gap-2 px-5 py-4 border-t sticky bottom-0 bg-card", children: [
|
|
2634
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, disabled: pending2 || saving, children: t2("financial.payment.cancel") }),
|
|
2635
|
+
/* @__PURE__ */ jsxs(
|
|
2636
|
+
Button,
|
|
2637
|
+
{
|
|
2638
|
+
variant: "default",
|
|
2639
|
+
size: "sm",
|
|
2640
|
+
onClick: handlePay,
|
|
2641
|
+
disabled: saving || !pending2 && (amount <= 0 || !selectedTypeId || !bankAccountId || needsOpenCashSession),
|
|
2642
|
+
children: [
|
|
2643
|
+
/* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
2644
|
+
saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: formatCurrency(amount, currency) })
|
|
2645
|
+
]
|
|
2646
|
+
}
|
|
2647
|
+
)
|
|
2648
|
+
] });
|
|
2649
|
+
if (inTaskHost) {
|
|
2650
|
+
return /* @__PURE__ */ jsxs(
|
|
2651
|
+
ModalTask,
|
|
2652
|
+
{
|
|
2653
|
+
open: true,
|
|
2654
|
+
title: t2("financial.payment.title"),
|
|
2655
|
+
hint: `${t2("financial.payment.installment", { number: String(movement.installmentNumber ?? 1) })} \xB7 ${t2("financial.payment.remaining", { amount: formatCurrency(remaining, currency) })}`,
|
|
2656
|
+
onClose: handleClose,
|
|
2657
|
+
children: [
|
|
2658
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", onKeyDown: handleKeyDown, children: body }),
|
|
2659
|
+
footer
|
|
2660
|
+
]
|
|
2661
|
+
}
|
|
2662
|
+
);
|
|
2663
|
+
}
|
|
2435
2664
|
return createPortal(
|
|
2436
2665
|
/* @__PURE__ */ jsx(
|
|
2437
2666
|
"div",
|
|
@@ -2461,155 +2690,8 @@ function PaymentModal({ movement, receiptUrl, onClose, onPaid }) {
|
|
|
2461
2690
|
] }),
|
|
2462
2691
|
/* @__PURE__ */ jsx("button", { onClick: handleClose, className: "p-1 text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
|
|
2463
2692
|
] }),
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2467
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.method") }),
|
|
2468
|
-
/* @__PURE__ */ jsx(
|
|
2469
|
-
"div",
|
|
2470
|
-
{
|
|
2471
|
-
className: "grid transition-all duration-200 ease-out",
|
|
2472
|
-
style: { gridTemplateRows: selectedTypeId && !expandMethodPicker ? "1fr" : "0fr" },
|
|
2473
|
-
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: selectedType && /* @__PURE__ */ jsxs(
|
|
2474
|
-
"button",
|
|
2475
|
-
{
|
|
2476
|
-
onClick: () => setExpandMethodPicker(true),
|
|
2477
|
-
className: `flex items-center gap-2 mt-1.5 w-full rounded-button border px-3 py-2.5 text-left transition-colors hover:bg-muted/30 ${TYPE_COLORS[txType] ?? ""}`,
|
|
2478
|
-
"data-active": "true",
|
|
2479
|
-
children: [
|
|
2480
|
-
SelectedIcon && /* @__PURE__ */ jsx(SelectedIcon, { className: `h-4 w-4 ${ICON_COLORS[txType] ?? ""}` }),
|
|
2481
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium flex-1", children: selectedType?.name }),
|
|
2482
|
-
/* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3 text-muted-foreground" })
|
|
2483
|
-
]
|
|
2484
|
-
}
|
|
2485
|
-
) })
|
|
2486
|
-
}
|
|
2487
|
-
),
|
|
2488
|
-
/* @__PURE__ */ jsx(
|
|
2489
|
-
"div",
|
|
2490
|
-
{
|
|
2491
|
-
className: "grid transition-all duration-200 ease-out",
|
|
2492
|
-
style: { gridTemplateRows: expandMethodPicker ? "1fr" : "0fr" },
|
|
2493
|
-
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: paymentMethodTypes.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-input bg-muted/30 px-3 py-3 text-center mt-1.5", children: [
|
|
2494
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.payment.noMethods") }),
|
|
2495
|
-
/* @__PURE__ */ jsxs("a", { href: "#/financial/settings", className: "mt-1.5 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
2496
|
-
t2("financial.payment.configureMethods"),
|
|
2497
|
-
" \u2192"
|
|
2498
|
-
] })
|
|
2499
|
-
] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2 mt-1.5", children: paymentMethodTypes.map((type) => {
|
|
2500
|
-
const tt = type.transactionType ?? "";
|
|
2501
|
-
const Icon = TYPE_ICONS[tt] ?? Banknote;
|
|
2502
|
-
const isActive = selectedTypeId === type.id;
|
|
2503
|
-
return /* @__PURE__ */ jsxs(
|
|
2504
|
-
"button",
|
|
2505
|
-
{
|
|
2506
|
-
"data-active": isActive,
|
|
2507
|
-
onClick: () => handleSelectType(type.id),
|
|
2508
|
-
className: `flex flex-col items-center gap-1.5 rounded-button border px-2 py-3 text-center transition-all ${TYPE_COLORS[tt] ?? TYPE_COLORS.cash}`,
|
|
2509
|
-
children: [
|
|
2510
|
-
/* @__PURE__ */ jsx(Icon, { className: `h-5 w-5 ${isActive ? ICON_COLORS[tt] ?? "" : "text-muted-foreground"}` }),
|
|
2511
|
-
/* @__PURE__ */ jsx("span", { className: `text-[11px] font-medium leading-tight ${isActive ? "text-foreground" : "text-muted-foreground"}`, children: type.name })
|
|
2512
|
-
]
|
|
2513
|
-
},
|
|
2514
|
-
type.id
|
|
2515
|
-
);
|
|
2516
|
-
}) }) })
|
|
2517
|
-
}
|
|
2518
|
-
)
|
|
2519
|
-
] }),
|
|
2520
|
-
selectedTypeId && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2521
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 stagger-field", style: { animationDelay: "0ms" }, children: [
|
|
2522
|
-
/* @__PURE__ */ jsx(
|
|
2523
|
-
CurrencyInput,
|
|
2524
|
-
{
|
|
2525
|
-
label: t2("financial.payment.amount"),
|
|
2526
|
-
value: amount,
|
|
2527
|
-
onChange: setAmount,
|
|
2528
|
-
symbol: currency.symbol,
|
|
2529
|
-
locale: currency.locale,
|
|
2530
|
-
currencyCode: currency.code
|
|
2531
|
-
}
|
|
2532
|
-
),
|
|
2533
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2534
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.date") }),
|
|
2535
|
-
/* @__PURE__ */ jsx(DatePicker, { value: paymentDate, onChange: setPaymentDate, className: "mt-1" })
|
|
2536
|
-
] })
|
|
2537
|
-
] }),
|
|
2538
|
-
filteredMethods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "50ms" }, children: [
|
|
2539
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: needsCard ? t2("financial.payment.card") : t2("financial.payment.account") }),
|
|
2540
|
-
/* @__PURE__ */ jsxs("select", { value: paymentMethodId, onChange: (e) => setPaymentMethodId(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
|
|
2541
|
-
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.select") }),
|
|
2542
|
-
filteredMethods.map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: m.name }, m.id))
|
|
2543
|
-
] })
|
|
2544
|
-
] }),
|
|
2545
|
-
needsCard && /* @__PURE__ */ jsxs("div", { className: `grid gap-3 stagger-field ${needsCardInstallments ? "grid-cols-2" : "grid-cols-1"}`, style: { animationDelay: "100ms" }, children: [
|
|
2546
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2547
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.cardBrand") }),
|
|
2548
|
-
/* @__PURE__ */ jsxs("select", { value: cardBrand, onChange: (e) => setCardBrand(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
|
|
2549
|
-
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.selectBrand") }),
|
|
2550
|
-
["Visa", "Mastercard", "American Express", "Elo", "Hipercard", "Diners Club"].map((b) => /* @__PURE__ */ jsx("option", { value: b, children: b }, b))
|
|
2551
|
-
] })
|
|
2552
|
-
] }),
|
|
2553
|
-
needsCardInstallments && /* @__PURE__ */ jsxs("div", { children: [
|
|
2554
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.cardInstallments") }),
|
|
2555
|
-
/* @__PURE__ */ jsx("select", { value: cardInstallments, onChange: (e) => setCardInstallments(Number(e.target.value)), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: Array.from({ length: 12 }, (_, i) => i + 1).map((n) => /* @__PURE__ */ jsxs("option", { value: n, children: [
|
|
2556
|
-
n,
|
|
2557
|
-
"x ",
|
|
2558
|
-
n > 1 ? formatCurrency(amount / n, currency) : ""
|
|
2559
|
-
] }, n)) })
|
|
2560
|
-
] })
|
|
2561
|
-
] }),
|
|
2562
|
-
needsOpenCashSession && /* @__PURE__ */ jsxs("div", { className: "stagger-field rounded-lg border border-dashed border-warning/40 bg-warning/5 px-3 py-2.5 text-center", children: [
|
|
2563
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.payment.cashNeedsOpenSession") }),
|
|
2564
|
-
/* @__PURE__ */ jsxs("a", { href: "#/financial/cash-registers", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
2565
|
-
t2("financial.payment.openCashSession"),
|
|
2566
|
-
" \u2192"
|
|
2567
|
-
] })
|
|
2568
|
-
] }),
|
|
2569
|
-
/* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "100ms" }, children: [
|
|
2570
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.bankAccount") }),
|
|
2571
|
-
eligibleAccounts2.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-dashed border-input bg-muted/30 px-3 py-2.5 text-center", children: [
|
|
2572
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: bankAccounts.length > 0 && selectedType ? t2("financial.payment.noAccountForMethod", { method: selectedType.name }) : t2("financial.payment.noAccounts") }),
|
|
2573
|
-
/* @__PURE__ */ jsxs("a", { href: "#/settings/financial/bank-accounts", className: "mt-1 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
2574
|
-
t2("financial.payment.configureAccounts"),
|
|
2575
|
-
" \u2192"
|
|
2576
|
-
] })
|
|
2577
|
-
] }) : /* @__PURE__ */ jsxs("select", { value: bankAccountId, onChange: (e) => setBankAccountId(e.target.value), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
|
|
2578
|
-
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.payment.selectAccount") }),
|
|
2579
|
-
eligibleAccounts2.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
|
|
2580
|
-
] })
|
|
2581
|
-
] }),
|
|
2582
|
-
/* @__PURE__ */ jsxs("div", { className: "stagger-field", style: { animationDelay: "150ms" }, children: [
|
|
2583
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.payment.notes") }),
|
|
2584
|
-
/* @__PURE__ */ jsx("input", { type: "text", value: notes, onChange: (e) => setNotes(e.target.value), placeholder: t2("financial.payment.optionalNotes"), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" })
|
|
2585
|
-
] })
|
|
2586
|
-
] }, selectedTypeId)
|
|
2587
|
-
] }),
|
|
2588
|
-
/* @__PURE__ */ jsx("style", { children: `
|
|
2589
|
-
.stagger-field {
|
|
2590
|
-
animation: field-slide-in 250ms ease-out both;
|
|
2591
|
-
}
|
|
2592
|
-
@keyframes field-slide-in {
|
|
2593
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
2594
|
-
to { opacity: 1; transform: translateY(0); }
|
|
2595
|
-
}
|
|
2596
|
-
` }),
|
|
2597
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-4 border-t sticky bottom-0 bg-card", children: [
|
|
2598
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, disabled: pending2 || saving, children: t2("financial.payment.cancel") }),
|
|
2599
|
-
/* @__PURE__ */ jsxs(
|
|
2600
|
-
Button,
|
|
2601
|
-
{
|
|
2602
|
-
variant: "default",
|
|
2603
|
-
size: "sm",
|
|
2604
|
-
onClick: handlePay,
|
|
2605
|
-
disabled: saving || !pending2 && (amount <= 0 || !selectedTypeId || !bankAccountId || needsOpenCashSession),
|
|
2606
|
-
children: [
|
|
2607
|
-
/* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
2608
|
-
saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: formatCurrency(amount, currency) })
|
|
2609
|
-
]
|
|
2610
|
-
}
|
|
2611
|
-
)
|
|
2612
|
-
] })
|
|
2693
|
+
body,
|
|
2694
|
+
footer
|
|
2613
2695
|
]
|
|
2614
2696
|
}
|
|
2615
2697
|
)
|
|
@@ -2748,7 +2830,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
|
|
|
2748
2830
|
() => createArchetypeLookup({
|
|
2749
2831
|
archetype: "person",
|
|
2750
2832
|
kind: direction === "debit" ? "supplier" : "customer",
|
|
2751
|
-
|
|
2833
|
+
createKinds: direction === "debit" ? ["supplier", "staff", "customer"] : ["customer", "supplier", "staff"],
|
|
2834
|
+
kindLabels: { supplier: "Fornecedor", customer: "Cliente", staff: "Funcion\xE1rio" }
|
|
2752
2835
|
}),
|
|
2753
2836
|
[direction]
|
|
2754
2837
|
);
|
|
@@ -3621,7 +3704,7 @@ function itemKindLabel(kind, t2) {
|
|
|
3621
3704
|
const label = t2(`financial.itemKind.${kind}`);
|
|
3622
3705
|
return label.startsWith("financial.itemKind.") ? void 0 : label;
|
|
3623
3706
|
}
|
|
3624
|
-
function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel }) {
|
|
3707
|
+
function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, embedded }) {
|
|
3625
3708
|
const t2 = useTranslation();
|
|
3626
3709
|
const config = useFinancialConfig();
|
|
3627
3710
|
const { currency } = config;
|
|
@@ -3643,6 +3726,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
3643
3726
|
const [movements, setMovements] = useState([]);
|
|
3644
3727
|
const [payments, setPayments] = useState([]);
|
|
3645
3728
|
const [loading, setLoading] = useState(true);
|
|
3729
|
+
const [error, setError] = useState(null);
|
|
3646
3730
|
const [reversing, setReversing] = useState(null);
|
|
3647
3731
|
const [reverseReason, setReverseReason] = useState("");
|
|
3648
3732
|
const [payingMovement, setPayingMovement] = useState(null);
|
|
@@ -3666,31 +3750,41 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
3666
3750
|
setCancelReason("");
|
|
3667
3751
|
cancelRequestKey.current = createIdempotencyKey("financial:invoice-cancel");
|
|
3668
3752
|
}
|
|
3753
|
+
const reqRef = useRef(0);
|
|
3669
3754
|
async function loadData() {
|
|
3755
|
+
const req = ++reqRef.current;
|
|
3670
3756
|
setLoading(true);
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3757
|
+
setError(null);
|
|
3758
|
+
try {
|
|
3759
|
+
const [inv, invItems, movResult, anexos] = await Promise.all([
|
|
3760
|
+
provider.getInvoiceById(invoiceId),
|
|
3761
|
+
provider.getInvoiceItems(invoiceId),
|
|
3762
|
+
// Scope to THIS invoice (not the tenant-wide first-50 page) so every
|
|
3763
|
+
// installment/payment loads regardless of how many movements exist.
|
|
3764
|
+
provider.getMovements({ direction, invoiceId, pageSize: 500 }),
|
|
3765
|
+
// Opcional no provider: um provider que não sabe de anexo devolve vazio
|
|
3766
|
+
// em vez de derrubar a tela inteira do detalhe.
|
|
3767
|
+
provider.getInvoiceAttachments?.(invoiceId).catch(() => []) ?? Promise.resolve([])
|
|
3768
|
+
]);
|
|
3769
|
+
if (req !== reqRef.current) return;
|
|
3770
|
+
setInvoice(inv);
|
|
3771
|
+
setItems(invItems);
|
|
3772
|
+
setAttachments(anexos);
|
|
3773
|
+
setMovements(
|
|
3774
|
+
movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
|
|
3775
|
+
);
|
|
3776
|
+
setPayments(
|
|
3777
|
+
movResult.data.filter((m) => m.invoiceId === invoiceId && m.movementKind === "payment").sort((a, b) => (a.paymentDate ?? "").localeCompare(b.paymentDate ?? ""))
|
|
3778
|
+
);
|
|
3779
|
+
} catch (e) {
|
|
3780
|
+
if (req !== reqRef.current) return;
|
|
3781
|
+
setError(e?.message ?? String(e));
|
|
3782
|
+
} finally {
|
|
3783
|
+
if (req === reqRef.current) setLoading(false);
|
|
3784
|
+
}
|
|
3691
3785
|
}
|
|
3692
3786
|
useEffect(() => {
|
|
3693
|
-
loadData();
|
|
3787
|
+
void loadData();
|
|
3694
3788
|
}, [invoiceId]);
|
|
3695
3789
|
useDataChanged({ table: T.movements }, () => {
|
|
3696
3790
|
void loadData();
|
|
@@ -3732,7 +3826,17 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
3732
3826
|
}
|
|
3733
3827
|
}
|
|
3734
3828
|
if (loading) {
|
|
3735
|
-
return /* @__PURE__ */ jsx(OrderDetailSkeleton, { parentLabel, onBack });
|
|
3829
|
+
return /* @__PURE__ */ jsx(OrderDetailSkeleton, { parentLabel: embedded ? void 0 : parentLabel, onBack: embedded ? void 0 : onBack });
|
|
3830
|
+
}
|
|
3831
|
+
if (error) {
|
|
3832
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
3833
|
+
/* @__PURE__ */ jsx(SubpageHeader, { title: t2("financial.invoice.loadFailed"), onBack, parentLabel }),
|
|
3834
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-card border border-destructive/30 bg-destructive/5 px-4 py-3", children: [
|
|
3835
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: t2("financial.invoice.loadFailedDesc") }),
|
|
3836
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive/80 break-words", children: error })
|
|
3837
|
+
] }),
|
|
3838
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void loadData(), children: t2("financial.invoice.retry") })
|
|
3839
|
+
] });
|
|
3736
3840
|
}
|
|
3737
3841
|
if (!invoice) {
|
|
3738
3842
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
@@ -3780,7 +3884,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
3780
3884
|
PageActions,
|
|
3781
3885
|
{
|
|
3782
3886
|
actions: !cancelled && invoice.status !== "paid" ? [
|
|
3783
|
-
{ id: "edit", label: t2("financial.invoice.edit"), icon: Pencil, intent: "primary", onSelect: () => onEdit
|
|
3887
|
+
...onEdit ? [{ id: "edit", label: t2("financial.invoice.edit"), icon: Pencil, intent: "primary", onSelect: () => onEdit() }] : [],
|
|
3784
3888
|
{ id: "cancel", label: t2("financial.invoice.cancelInvoice"), icon: Ban, intent: "danger", onSelect: () => setConfirmCancel(true) }
|
|
3785
3889
|
] : []
|
|
3786
3890
|
}
|
|
@@ -3808,10 +3912,11 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
3808
3912
|
/* @__PURE__ */ jsx(
|
|
3809
3913
|
OrderDetailView,
|
|
3810
3914
|
{
|
|
3915
|
+
embedded,
|
|
3811
3916
|
title: invoice.fiscalNumber ? `#${invoice.fiscalNumber}` : invoice.contactName ?? invoice.invoiceDate,
|
|
3812
3917
|
badges: [{ label: t2(statusConfig.labelKey), tone: statusConfig.tone }],
|
|
3813
|
-
onBack,
|
|
3814
|
-
backLabel: parentLabel,
|
|
3918
|
+
onBack: embedded ? void 0 : onBack,
|
|
3919
|
+
backLabel: embedded ? void 0 : parentLabel,
|
|
3815
3920
|
meta: invoice,
|
|
3816
3921
|
actions,
|
|
3817
3922
|
stats: [
|
|
@@ -4036,7 +4141,8 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
4036
4141
|
return /* @__PURE__ */ jsx(
|
|
4037
4142
|
"div",
|
|
4038
4143
|
{
|
|
4039
|
-
|
|
4144
|
+
"data-modal-passthrough": true,
|
|
4145
|
+
className: "pointer-events-auto fixed inset-0 z-[200] flex flex-col bg-black/80 p-4",
|
|
4040
4146
|
onClick: () => setViewing(null),
|
|
4041
4147
|
role: "dialog",
|
|
4042
4148
|
"aria-modal": "true",
|
|
@@ -4117,53 +4223,54 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
4117
4223
|
})(),
|
|
4118
4224
|
document.body
|
|
4119
4225
|
),
|
|
4120
|
-
reversing &&
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4226
|
+
reversing && /* @__PURE__ */ jsxs(
|
|
4227
|
+
ModalTask,
|
|
4228
|
+
{
|
|
4229
|
+
open: true,
|
|
4230
|
+
title: t2("financial.invoice.reverseTitle"),
|
|
4231
|
+
hint: `${formatCurrency(reversing.amount ?? 0, currency)} \xB7 ${reversing.paymentDate ?? ""}`,
|
|
4232
|
+
width: "max-w-sm",
|
|
4233
|
+
onClose: () => {
|
|
4234
|
+
setReversing(null);
|
|
4235
|
+
setReverseReason("");
|
|
4236
|
+
},
|
|
4237
|
+
children: [
|
|
4238
|
+
/* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto px-5 py-4", children: [
|
|
4239
|
+
/* @__PURE__ */ jsx("label", { className: "block text-[10px] font-medium uppercase text-muted-foreground", children: t2("financial.invoice.reverseReason") }),
|
|
4240
|
+
/* @__PURE__ */ jsx(
|
|
4241
|
+
"textarea",
|
|
4242
|
+
{
|
|
4243
|
+
rows: 3,
|
|
4244
|
+
autoFocus: true,
|
|
4245
|
+
value: reverseReason,
|
|
4246
|
+
onChange: (e) => setReverseReason(e.target.value),
|
|
4247
|
+
className: "mt-1 w-full rounded-input border border-input bg-card px-3 py-2 text-sm"
|
|
4248
|
+
}
|
|
4249
|
+
)
|
|
4250
|
+
] }),
|
|
4251
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 justify-end gap-2 border-t px-5 py-3", children: [
|
|
4252
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => {
|
|
4253
|
+
setReversing(null);
|
|
4254
|
+
setReverseReason("");
|
|
4255
|
+
}, children: t2("common.cancel") }),
|
|
4256
|
+
/* @__PURE__ */ jsx(
|
|
4257
|
+
Button,
|
|
4258
|
+
{
|
|
4259
|
+
variant: "destructive",
|
|
4260
|
+
size: "sm",
|
|
4261
|
+
disabled: !reverseReason.trim(),
|
|
4262
|
+
onClick: async () => {
|
|
4263
|
+
await reverseMovement(reversing.id, reverseReason.trim());
|
|
4264
|
+
setReversing(null);
|
|
4265
|
+
setReverseReason("");
|
|
4266
|
+
loadData();
|
|
4267
|
+
},
|
|
4268
|
+
children: t2("financial.invoice.reverse")
|
|
4269
|
+
}
|
|
4270
|
+
)
|
|
4271
|
+
] })
|
|
4272
|
+
]
|
|
4273
|
+
}
|
|
4167
4274
|
),
|
|
4168
4275
|
payingMovement && /* @__PURE__ */ jsx(
|
|
4169
4276
|
PaymentModal,
|
|
@@ -4181,37 +4288,39 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
4181
4288
|
}
|
|
4182
4289
|
}
|
|
4183
4290
|
),
|
|
4184
|
-
confirmCancel &&
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
/* @__PURE__ */ jsxs(
|
|
4209
|
-
/* @__PURE__ */ jsx(
|
|
4210
|
-
|
|
4291
|
+
confirmCancel && /* @__PURE__ */ jsxs(
|
|
4292
|
+
ModalTask,
|
|
4293
|
+
{
|
|
4294
|
+
open: true,
|
|
4295
|
+
title: t2("financial.invoice.cancelInvoice"),
|
|
4296
|
+
hint: t2("financial.invoice.cancelConfirm"),
|
|
4297
|
+
width: "max-w-sm",
|
|
4298
|
+
onClose: closeCancelDialog,
|
|
4299
|
+
children: [
|
|
4300
|
+
/* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto px-5 py-4", children: [
|
|
4301
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-4", children: t2("financial.invoice.cancelDesc") }),
|
|
4302
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", htmlFor: "invoice-cancel-reason", children: t2("financial.invoice.cancelReason") }),
|
|
4303
|
+
/* @__PURE__ */ jsx(
|
|
4304
|
+
"textarea",
|
|
4305
|
+
{
|
|
4306
|
+
id: "invoice-cancel-reason",
|
|
4307
|
+
value: cancelReason,
|
|
4308
|
+
onChange: (event) => setCancelReason(event.target.value),
|
|
4309
|
+
placeholder: t2("financial.invoice.cancelReasonPlaceholder"),
|
|
4310
|
+
className: "min-h-20 w-full resize-y rounded-md border bg-background px-3 py-2 text-sm",
|
|
4311
|
+
autoFocus: true
|
|
4312
|
+
}
|
|
4313
|
+
)
|
|
4314
|
+
] }),
|
|
4315
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 justify-end gap-2 border-t px-5 py-3", children: [
|
|
4316
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: closeCancelDialog, children: t2("financial.invoice.keepInvoice") }),
|
|
4317
|
+
/* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancel, disabled: cancelling || !cancelReason.trim(), children: [
|
|
4318
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
4319
|
+
cancelling ? t2("financial.invoice.cancelling") : t2("financial.invoice.yesCancel")
|
|
4320
|
+
] })
|
|
4211
4321
|
] })
|
|
4212
|
-
]
|
|
4213
|
-
|
|
4214
|
-
document.body
|
|
4322
|
+
]
|
|
4323
|
+
}
|
|
4215
4324
|
),
|
|
4216
4325
|
/* @__PURE__ */ jsx("style", { children: `
|
|
4217
4326
|
@keyframes field-slide-in {
|
|
@@ -5136,12 +5245,37 @@ function StatementsView({ onNavigate }) {
|
|
|
5136
5245
|
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
|
|
5137
5246
|
const fetchStatement = useFinancialStore((s) => s.fetchStatement);
|
|
5138
5247
|
const [accountId, setAccountId] = useState("");
|
|
5139
|
-
const
|
|
5248
|
+
const MAX_RANGE_DAYS2 = 90;
|
|
5249
|
+
const hojeIso = () => {
|
|
5140
5250
|
const d = /* @__PURE__ */ new Date();
|
|
5141
|
-
d.
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5251
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
5252
|
+
};
|
|
5253
|
+
const defaultFrom = useMemo(() => {
|
|
5254
|
+
const d = /* @__PURE__ */ new Date();
|
|
5255
|
+
d.setDate(d.getDate() - 29);
|
|
5256
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
5257
|
+
}, []);
|
|
5258
|
+
const [dateFrom, setDateFrom] = useState(defaultFrom);
|
|
5259
|
+
const [dateTo, setDateTo] = useState(hojeIso);
|
|
5260
|
+
const spanDays2 = (from, to) => Math.round(((/* @__PURE__ */ new Date(`${to}T00:00:00`)).getTime() - (/* @__PURE__ */ new Date(`${from}T00:00:00`)).getTime()) / 864e5) + 1;
|
|
5261
|
+
const setFromClamped = (from) => {
|
|
5262
|
+
if (!from) return;
|
|
5263
|
+
setDateFrom(from);
|
|
5264
|
+
if (dateTo && spanDays2(from, dateTo) > MAX_RANGE_DAYS2) {
|
|
5265
|
+
const d = /* @__PURE__ */ new Date(`${from}T00:00:00`);
|
|
5266
|
+
d.setDate(d.getDate() + (MAX_RANGE_DAYS2 - 1));
|
|
5267
|
+
setDateTo(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`);
|
|
5268
|
+
}
|
|
5269
|
+
};
|
|
5270
|
+
const setToClamped = (to) => {
|
|
5271
|
+
if (!to) return;
|
|
5272
|
+
setDateTo(to);
|
|
5273
|
+
if (dateFrom && spanDays2(dateFrom, to) > MAX_RANGE_DAYS2) {
|
|
5274
|
+
const d = /* @__PURE__ */ new Date(`${to}T00:00:00`);
|
|
5275
|
+
d.setDate(d.getDate() - (MAX_RANGE_DAYS2 - 1));
|
|
5276
|
+
setDateFrom(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`);
|
|
5277
|
+
}
|
|
5278
|
+
};
|
|
5145
5279
|
const [search, setSearch] = useState("");
|
|
5146
5280
|
const [showTransfer, setShowTransfer] = useState(false);
|
|
5147
5281
|
useEffect(() => {
|
|
@@ -5307,11 +5441,18 @@ function StatementsView({ onNavigate }) {
|
|
|
5307
5441
|
"extrato"
|
|
5308
5442
|
);
|
|
5309
5443
|
};
|
|
5444
|
+
const headerActions = React15__default.useMemo(() => [{
|
|
5445
|
+
id: "new-transfer",
|
|
5446
|
+
label: t2("financial.statements.newTransfer"),
|
|
5447
|
+
icon: ArrowRightLeft,
|
|
5448
|
+
intent: "primary",
|
|
5449
|
+
// Uma transferência é um lançamento novo — o FAB é um "+".
|
|
5450
|
+
creates: true,
|
|
5451
|
+
disabled: bankAccounts.length < 2,
|
|
5452
|
+
onSelect: () => setShowTransfer(true)
|
|
5453
|
+
}], [t2, bankAccounts.length]);
|
|
5310
5454
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
5311
|
-
/* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */
|
|
5312
|
-
/* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
|
|
5313
|
-
t2("financial.statements.newTransfer")
|
|
5314
|
-
] }) }),
|
|
5455
|
+
/* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PageActions, { actions: headerActions }) }),
|
|
5315
5456
|
accountId && statement && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-2", children: [
|
|
5316
5457
|
/* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.opening"), value: fmt(statement.openingBalance) }),
|
|
5317
5458
|
/* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.totalCredits"), value: fmt(statement.totalCredits), positive: true }),
|
|
@@ -5335,29 +5476,67 @@ function StatementsView({ onNavigate }) {
|
|
|
5335
5476
|
] }),
|
|
5336
5477
|
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-2", children: [
|
|
5337
5478
|
/* @__PURE__ */ jsx(
|
|
5338
|
-
|
|
5479
|
+
FilterMenu,
|
|
5339
5480
|
{
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5481
|
+
label: t2("crud.list.filters"),
|
|
5482
|
+
activeCount: (accountId && accountId !== ALL ? 1 : 0) + (dateFrom === defaultFrom && dateTo === hojeIso() ? 0 : 1),
|
|
5483
|
+
onClear: () => {
|
|
5484
|
+
setAccountId(ALL);
|
|
5485
|
+
setDateFrom(defaultFrom);
|
|
5486
|
+
setDateTo(hojeIso());
|
|
5487
|
+
},
|
|
5488
|
+
clearLabel: t2("financial.invoice.filterClear"),
|
|
5489
|
+
fields: [
|
|
5490
|
+
{
|
|
5491
|
+
id: "account",
|
|
5492
|
+
label: t2("financial.statements.account"),
|
|
5493
|
+
icon: Landmark,
|
|
5494
|
+
activeCount: accountId && accountId !== ALL ? 1 : 0,
|
|
5495
|
+
searchable: true,
|
|
5496
|
+
render: (close, q) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5497
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !accountId || accountId === ALL, onClick: () => {
|
|
5498
|
+
setAccountId(ALL);
|
|
5499
|
+
close();
|
|
5500
|
+
}, children: t2("financial.summary.allAccounts") }),
|
|
5501
|
+
bankAccounts.filter((a) => a.name.toLowerCase().includes(q.toLowerCase())).map((a) => /* @__PURE__ */ jsx(FilterMenuRow, { active: accountId === a.id, onClick: () => {
|
|
5502
|
+
setAccountId(a.id);
|
|
5503
|
+
close();
|
|
5504
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: a.name }) }, a.id))
|
|
5505
|
+
] })
|
|
5506
|
+
},
|
|
5507
|
+
{
|
|
5508
|
+
id: "period",
|
|
5509
|
+
label: t2("financial.invoice.filterPeriod"),
|
|
5510
|
+
icon: CalendarDays,
|
|
5511
|
+
activeCount: dateFrom === defaultFrom && dateTo === hojeIso() ? 0 : 1,
|
|
5512
|
+
render: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5513
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 p-1.5", children: [
|
|
5514
|
+
/* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateFrom, onChange: setFromClamped }),
|
|
5515
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
|
|
5516
|
+
/* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", className: "w-full", value: dateTo, onChange: setToClamped })
|
|
5517
|
+
] }),
|
|
5518
|
+
/* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-[10px] text-muted-foreground", children: t2("financial.invoice.filterMaxRange") })
|
|
5519
|
+
] })
|
|
5520
|
+
}
|
|
5521
|
+
]
|
|
5346
5522
|
}
|
|
5347
5523
|
),
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5524
|
+
entries.length > 0 && // O mesmo utilitário da barra de toda lista (CrudListView): ghost
|
|
5525
|
+
// + `size="icon"` + `fz-tap`. À mão era um quadrado de 32px, um
|
|
5526
|
+
// quarto desenho de botão de ícone e abaixo do piso do toque; e o
|
|
5527
|
+
// `title=` nativo não é anunciado por leitor de tela — Hint é.
|
|
5528
|
+
/* @__PURE__ */ jsx(Hint, { label: t2("crud.export.action"), children: /* @__PURE__ */ jsx(
|
|
5529
|
+
Button,
|
|
5352
5530
|
{
|
|
5353
5531
|
type: "button",
|
|
5354
|
-
|
|
5532
|
+
variant: "ghost",
|
|
5533
|
+
size: "icon",
|
|
5534
|
+
className: "fz-tap shrink-0 text-muted-foreground",
|
|
5355
5535
|
"aria-label": t2("crud.export.action"),
|
|
5356
|
-
|
|
5357
|
-
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
5536
|
+
onClick: exportEntries,
|
|
5358
5537
|
children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
|
|
5359
5538
|
}
|
|
5360
|
-
)
|
|
5539
|
+
) })
|
|
5361
5540
|
] })
|
|
5362
5541
|
] }),
|
|
5363
5542
|
/* @__PURE__ */ jsx(
|
|
@@ -5420,6 +5599,7 @@ function CommissionsView() {
|
|
|
5420
5599
|
const provider = useFinancialProvider();
|
|
5421
5600
|
const [from, setFrom] = React15__default.useState(firstOfMonth());
|
|
5422
5601
|
const [to, setTo] = React15__default.useState(today2());
|
|
5602
|
+
const [preset, setPreset] = React15__default.useState("this_month");
|
|
5423
5603
|
const [running, setRunning] = React15__default.useState(false);
|
|
5424
5604
|
const [lastRun, setLastRun] = React15__default.useState(null);
|
|
5425
5605
|
const [generation, setGeneration] = React15__default.useState(null);
|
|
@@ -5490,70 +5670,41 @@ function CommissionsView() {
|
|
|
5490
5670
|
setBusy(null);
|
|
5491
5671
|
}
|
|
5492
5672
|
}
|
|
5673
|
+
const headerActions = React15__default.useMemo(() => [{
|
|
5674
|
+
id: "commission-rules",
|
|
5675
|
+
label: "Configurar regras de comiss\xE3o",
|
|
5676
|
+
icon: Settings,
|
|
5677
|
+
intent: "utility",
|
|
5678
|
+
onSelect: () => {
|
|
5679
|
+
window.location.hash = "/settings/financial-commissions";
|
|
5680
|
+
}
|
|
5681
|
+
}], []);
|
|
5493
5682
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
5494
5683
|
/* @__PURE__ */ jsx(
|
|
5495
5684
|
SubpageHeader,
|
|
5496
5685
|
{
|
|
5497
5686
|
title: labels.commissions,
|
|
5498
5687
|
subtitle: t2("financial.commissions.subtitle"),
|
|
5499
|
-
actions: /* @__PURE__ */ jsx(
|
|
5500
|
-
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
5501
|
-
Button,
|
|
5502
|
-
{
|
|
5503
|
-
variant: "outline",
|
|
5504
|
-
size: "sm",
|
|
5505
|
-
"aria-label": "Configurar regras de comiss\xE3o",
|
|
5506
|
-
onClick: () => {
|
|
5507
|
-
window.location.hash = "/settings/financial-commissions";
|
|
5508
|
-
},
|
|
5509
|
-
children: [
|
|
5510
|
-
/* @__PURE__ */ jsx(Settings, { className: "h-3.5 w-3.5" }),
|
|
5511
|
-
"Regras"
|
|
5512
|
-
]
|
|
5513
|
-
}
|
|
5514
|
-
) }),
|
|
5515
|
-
/* @__PURE__ */ jsx(TooltipContent, { className: "text-xs", children: "Configurar as regras que geram estes valores" })
|
|
5516
|
-
] }) }) })
|
|
5688
|
+
actions: /* @__PURE__ */ jsx(PageActions, { actions: headerActions })
|
|
5517
5689
|
}
|
|
5518
5690
|
),
|
|
5519
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-
|
|
5520
|
-
/* @__PURE__ */
|
|
5521
|
-
|
|
5522
|
-
/* @__PURE__ */ jsx(
|
|
5523
|
-
"input",
|
|
5524
|
-
{
|
|
5525
|
-
type: "date",
|
|
5526
|
-
value: from,
|
|
5527
|
-
onChange: (e) => setFrom(e.target.value),
|
|
5528
|
-
className: "mt-1 block rounded-input border border-input bg-card px-2 py-1.5 text-sm"
|
|
5529
|
-
}
|
|
5530
|
-
)
|
|
5531
|
-
] }),
|
|
5532
|
-
/* @__PURE__ */ jsxs("label", { className: "text-xs font-medium text-muted-foreground", children: [
|
|
5533
|
-
"At\xE9",
|
|
5534
|
-
/* @__PURE__ */ jsx(
|
|
5535
|
-
"input",
|
|
5536
|
-
{
|
|
5537
|
-
type: "date",
|
|
5538
|
-
value: to,
|
|
5539
|
-
onChange: (e) => setTo(e.target.value),
|
|
5540
|
-
className: "mt-1 block rounded-input border border-input bg-card px-2 py-1.5 text-sm"
|
|
5541
|
-
}
|
|
5542
|
-
)
|
|
5543
|
-
] }),
|
|
5544
|
-
/* @__PURE__ */ jsxs(
|
|
5545
|
-
"button",
|
|
5691
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 rounded-2xl border border-border bg-card px-4 py-3", children: [
|
|
5692
|
+
/* @__PURE__ */ jsx(
|
|
5693
|
+
DateRangeControl,
|
|
5546
5694
|
{
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
]
|
|
5695
|
+
value: { from, to },
|
|
5696
|
+
onChange: (r) => {
|
|
5697
|
+
setFrom(r.from);
|
|
5698
|
+
setTo(r.to);
|
|
5699
|
+
},
|
|
5700
|
+
preset,
|
|
5701
|
+
onPresetChange: (p) => setPreset(p)
|
|
5555
5702
|
}
|
|
5556
5703
|
),
|
|
5704
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", onClick: () => void run(), disabled: running, children: [
|
|
5705
|
+
running ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Play, { className: "h-4 w-4", "aria-hidden": true }),
|
|
5706
|
+
"Apurar per\xEDodo"
|
|
5707
|
+
] }),
|
|
5557
5708
|
lastRun && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: lastRun }),
|
|
5558
5709
|
/* @__PURE__ */ jsxs("div", { className: "ml-auto text-right", children: [
|
|
5559
5710
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Total do per\xEDodo" }),
|
|
@@ -5584,43 +5735,18 @@ function CommissionsView() {
|
|
|
5584
5735
|
}
|
|
5585
5736
|
),
|
|
5586
5737
|
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
|
|
5587
|
-
generation.status === "draft" && /* @__PURE__ */ jsxs(
|
|
5588
|
-
"
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
),
|
|
5600
|
-
generation.status === "approved" && /* @__PURE__ */ jsxs(
|
|
5601
|
-
"button",
|
|
5602
|
-
{
|
|
5603
|
-
type: "button",
|
|
5604
|
-
onClick: () => void payRun(),
|
|
5605
|
-
disabled: busy !== null,
|
|
5606
|
-
className: "inline-flex items-center gap-1.5 rounded-button bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
|
|
5607
|
-
children: [
|
|
5608
|
-
busy === "payrun" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Receipt, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
5609
|
-
"Gerar contas a pagar"
|
|
5610
|
-
]
|
|
5611
|
-
}
|
|
5612
|
-
),
|
|
5613
|
-
generation.status === "paid" && /* @__PURE__ */ jsxs(
|
|
5614
|
-
"a",
|
|
5615
|
-
{
|
|
5616
|
-
href: "#/financial/payables/list",
|
|
5617
|
-
className: "inline-flex items-center gap-1.5 rounded-button border bg-card px-3 py-1.5 text-xs font-medium hover:bg-muted",
|
|
5618
|
-
children: [
|
|
5619
|
-
/* @__PURE__ */ jsx(Receipt, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
5620
|
-
" Ver em Contas a Pagar"
|
|
5621
|
-
]
|
|
5622
|
-
}
|
|
5623
|
-
)
|
|
5738
|
+
generation.status === "draft" && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void approve(), disabled: busy !== null, children: [
|
|
5739
|
+
busy === "approve" ? /* @__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 }),
|
|
5740
|
+
"Aprovar lan\xE7amentos"
|
|
5741
|
+
] }),
|
|
5742
|
+
generation.status === "approved" && /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: () => void payRun(), disabled: busy !== null, children: [
|
|
5743
|
+
busy === "payrun" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Receipt, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
5744
|
+
"Gerar contas a pagar"
|
|
5745
|
+
] }),
|
|
5746
|
+
generation.status === "paid" && /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsxs("a", { href: "#/financial/payables/list", children: [
|
|
5747
|
+
/* @__PURE__ */ jsx(Receipt, { className: "h-3.5 w-3.5", "aria-hidden": true }),
|
|
5748
|
+
" Ver em Contas a Pagar"
|
|
5749
|
+
] }) })
|
|
5624
5750
|
] })
|
|
5625
5751
|
] }),
|
|
5626
5752
|
/* @__PURE__ */ jsx("p", { className: "mt-2 text-[11px] text-muted-foreground", children: generation.status === "draft" ? "Apurar n\xE3o \xE9 autorizar: o border\xF4 s\xF3 recolhe o que estiver aprovado." : generation.status === "approved" ? "O border\xF4 junta o aprovado por pessoa e levanta UMA conta a pagar para cada." : "As comiss\xF5es viraram conta a pagar \u2014 vivem onde vive o aluguel: extrato e DRE." })
|
|
@@ -6310,64 +6436,80 @@ function GiftCardsView() {
|
|
|
6310
6436
|
money3(issued.total),
|
|
6311
6437
|
" em valor de face. Eles nascem sem saldo: ative cada um na venda."
|
|
6312
6438
|
] }),
|
|
6313
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-
|
|
6314
|
-
/* @__PURE__ */ jsx(
|
|
6439
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2 rounded-2xl border bg-card px-3 py-2", children: [
|
|
6440
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: /* @__PURE__ */ jsx(
|
|
6315
6441
|
Input,
|
|
6316
6442
|
{
|
|
6317
6443
|
value: search,
|
|
6318
|
-
placeholder: "
|
|
6319
|
-
className: "
|
|
6444
|
+
placeholder: "Buscar c\xF3digo",
|
|
6445
|
+
className: "border-0 bg-transparent px-1 text-sm shadow-none focus-visible:ring-0",
|
|
6320
6446
|
onChange: (e) => setSearch(e.target.value)
|
|
6321
6447
|
}
|
|
6322
6448
|
) }),
|
|
6323
|
-
/* @__PURE__ */ jsx(
|
|
6324
|
-
|
|
6325
|
-
{
|
|
6326
|
-
value: state,
|
|
6327
|
-
onChange: (e) => setState(e.target.value),
|
|
6328
|
-
className: "mt-1 block rounded-input border border-input bg-card px-2 py-1.5 text-sm",
|
|
6329
|
-
children: [
|
|
6330
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "Todos" }),
|
|
6331
|
-
Object.keys(STATE_META).map((s) => /* @__PURE__ */ jsx("option", { value: s, children: STATE_META[s].label }, s))
|
|
6332
|
-
]
|
|
6333
|
-
}
|
|
6334
|
-
) }),
|
|
6335
|
-
/* @__PURE__ */ jsx(Field, { label: "Valor de", children: /* @__PURE__ */ jsx(
|
|
6336
|
-
Input,
|
|
6337
|
-
{
|
|
6338
|
-
type: "number",
|
|
6339
|
-
value: minValue,
|
|
6340
|
-
className: "w-28 text-sm",
|
|
6341
|
-
onChange: (e) => setMinValue(e.target.value)
|
|
6342
|
-
}
|
|
6343
|
-
) }),
|
|
6344
|
-
/* @__PURE__ */ jsx(Field, { label: "at\xE9", children: /* @__PURE__ */ jsx(
|
|
6345
|
-
Input,
|
|
6346
|
-
{
|
|
6347
|
-
type: "number",
|
|
6348
|
-
value: maxValue,
|
|
6349
|
-
className: "w-28 text-sm",
|
|
6350
|
-
onChange: (e) => setMaxValue(e.target.value)
|
|
6351
|
-
}
|
|
6352
|
-
) }),
|
|
6353
|
-
/* @__PURE__ */ jsx(Field, { label: "Emiss\xE3o de", children: /* @__PURE__ */ jsx(
|
|
6354
|
-
Input,
|
|
6355
|
-
{
|
|
6356
|
-
type: "date",
|
|
6357
|
-
value: from,
|
|
6358
|
-
className: "w-40 text-sm",
|
|
6359
|
-
onChange: (e) => setFrom(e.target.value)
|
|
6360
|
-
}
|
|
6361
|
-
) }),
|
|
6362
|
-
/* @__PURE__ */ jsx(Field, { label: "at\xE9", children: /* @__PURE__ */ jsx(
|
|
6363
|
-
Input,
|
|
6449
|
+
/* @__PURE__ */ jsx(
|
|
6450
|
+
ListFilterBar,
|
|
6364
6451
|
{
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6452
|
+
className: "ml-auto",
|
|
6453
|
+
filters: [{
|
|
6454
|
+
id: "state",
|
|
6455
|
+
label: "Status",
|
|
6456
|
+
allLabel: "Todos os status",
|
|
6457
|
+
options: Object.keys(STATE_META).map((st) => ({ value: st, label: STATE_META[st].label })),
|
|
6458
|
+
value: state || void 0,
|
|
6459
|
+
onChange: (next) => setState(next ?? "")
|
|
6460
|
+
}],
|
|
6461
|
+
extraFields: [
|
|
6462
|
+
{
|
|
6463
|
+
id: "value",
|
|
6464
|
+
label: "Valor",
|
|
6465
|
+
activeCount: (minValue ? 1 : 0) + (maxValue ? 1 : 0),
|
|
6466
|
+
render: () => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 p-1", children: [
|
|
6467
|
+
/* @__PURE__ */ jsx(
|
|
6468
|
+
Input,
|
|
6469
|
+
{
|
|
6470
|
+
type: "number",
|
|
6471
|
+
value: minValue,
|
|
6472
|
+
placeholder: "de",
|
|
6473
|
+
className: "h-8 w-24 text-xs",
|
|
6474
|
+
onChange: (e) => setMinValue(e.target.value)
|
|
6475
|
+
}
|
|
6476
|
+
),
|
|
6477
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2013" }),
|
|
6478
|
+
/* @__PURE__ */ jsx(
|
|
6479
|
+
Input,
|
|
6480
|
+
{
|
|
6481
|
+
type: "number",
|
|
6482
|
+
value: maxValue,
|
|
6483
|
+
placeholder: "at\xE9",
|
|
6484
|
+
className: "h-8 w-24 text-xs",
|
|
6485
|
+
onChange: (e) => setMaxValue(e.target.value)
|
|
6486
|
+
}
|
|
6487
|
+
)
|
|
6488
|
+
] })
|
|
6489
|
+
},
|
|
6490
|
+
{
|
|
6491
|
+
id: "issued",
|
|
6492
|
+
label: "Emiss\xE3o",
|
|
6493
|
+
activeCount: (from ? 1 : 0) + (to ? 1 : 0),
|
|
6494
|
+
// DatePicker `chip`, não `<input type=date>`: é o mesmo campo
|
|
6495
|
+
// de data de todo filtro do produto, e o nativo muda de cara em
|
|
6496
|
+
// cada navegador.
|
|
6497
|
+
render: () => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 p-1", children: [
|
|
6498
|
+
/* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", value: from, onChange: setFrom }),
|
|
6499
|
+
/* @__PURE__ */ jsx(DatePicker, { variant: "chip", format: "numeric", value: to, onChange: setTo })
|
|
6500
|
+
] })
|
|
6501
|
+
}
|
|
6502
|
+
],
|
|
6503
|
+
activeCount: (state ? 1 : 0) + (minValue || maxValue ? 1 : 0) + (from || to ? 1 : 0),
|
|
6504
|
+
onClear: () => {
|
|
6505
|
+
setState("");
|
|
6506
|
+
setMinValue("");
|
|
6507
|
+
setMaxValue("");
|
|
6508
|
+
setFrom("");
|
|
6509
|
+
setTo("");
|
|
6510
|
+
}
|
|
6369
6511
|
}
|
|
6370
|
-
)
|
|
6512
|
+
)
|
|
6371
6513
|
] }),
|
|
6372
6514
|
error && /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-card border border-destructive/30 bg-destructive/5 px-4 py-3 text-xs text-destructive", children: error }),
|
|
6373
6515
|
/* @__PURE__ */ jsx("div", { className: "mt-3 overflow-x-auto rounded-card border bg-card", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
@@ -6443,12 +6585,6 @@ function GiftCardsView() {
|
|
|
6443
6585
|
)
|
|
6444
6586
|
] }) });
|
|
6445
6587
|
}
|
|
6446
|
-
function Field({ label, children }) {
|
|
6447
|
-
return /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium text-muted-foreground", children: [
|
|
6448
|
-
label,
|
|
6449
|
-
children
|
|
6450
|
-
] });
|
|
6451
|
-
}
|
|
6452
6588
|
function IssueDialog({
|
|
6453
6589
|
money: money3,
|
|
6454
6590
|
onClose,
|
|
@@ -6628,13 +6764,22 @@ function ReconciliationView() {
|
|
|
6628
6764
|
] });
|
|
6629
6765
|
}
|
|
6630
6766
|
const selected = lines.find((l) => l.id === selectedId) ?? null;
|
|
6767
|
+
const RefreshIcon = React15__default.useMemo(
|
|
6768
|
+
() => ({ className }) => /* @__PURE__ */ jsx(RefreshCw, { className: `${className ?? ""} ${loading ? "animate-spin" : ""}` }),
|
|
6769
|
+
[loading]
|
|
6770
|
+
);
|
|
6771
|
+
const headerActions = React15__default.useMemo(() => [{
|
|
6772
|
+
id: "refresh",
|
|
6773
|
+
label: t2("financial.reconciliation.refresh"),
|
|
6774
|
+
icon: RefreshIcon,
|
|
6775
|
+
intent: "utility",
|
|
6776
|
+
disabled: loading || busy,
|
|
6777
|
+
onSelect: () => void load()
|
|
6778
|
+
}], [t2, RefreshIcon, loading, busy, load]);
|
|
6631
6779
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
6632
6780
|
/* @__PURE__ */ jsxs(PageHeaderActions, { children: [
|
|
6633
6781
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: lines.length > 0 ? t2("financial.reconciliation.pending", { count: lines.length }) : t2("financial.reconciliation.allDone") }),
|
|
6634
|
-
/* @__PURE__ */
|
|
6635
|
-
/* @__PURE__ */ jsx(RefreshCw, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }),
|
|
6636
|
-
t2("financial.reconciliation.refresh")
|
|
6637
|
-
] })
|
|
6782
|
+
/* @__PURE__ */ jsx(PageActions, { actions: headerActions })
|
|
6638
6783
|
] }),
|
|
6639
6784
|
lines.length === 0 && !loading ? /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm py-16 text-center", children: [
|
|
6640
6785
|
/* @__PURE__ */ jsx(Check, { className: "h-8 w-8 text-success/40 mx-auto mb-2" }),
|
|
@@ -6802,16 +6947,22 @@ function NotasFiscaisView() {
|
|
|
6802
6947
|
/* @__PURE__ */ jsx("p", { className: "mt-4 text-sm text-muted-foreground", children: "Este provedor financeiro ainda n\xE3o exp\xF5e notas fiscais." })
|
|
6803
6948
|
] });
|
|
6804
6949
|
}
|
|
6950
|
+
const headerActions = React15__default.useMemo(() => canCreateManually ? [{
|
|
6951
|
+
id: "new-nota",
|
|
6952
|
+
label: "Nova nota",
|
|
6953
|
+
icon: Plus,
|
|
6954
|
+
intent: "primary",
|
|
6955
|
+
creates: true,
|
|
6956
|
+
permission: { feature: "financial", action: "create" },
|
|
6957
|
+
onSelect: () => setCreating(true)
|
|
6958
|
+
}] : [], [canCreateManually]);
|
|
6805
6959
|
return /* @__PURE__ */ jsxs("div", { className: "p-1", children: [
|
|
6806
6960
|
/* @__PURE__ */ jsx(
|
|
6807
6961
|
SubpageHeader,
|
|
6808
6962
|
{
|
|
6809
6963
|
title: "Notas Fiscais",
|
|
6810
6964
|
subtitle: "Emiss\xE3o e acompanhamento de NF-e / NFC-e / NFS-e",
|
|
6811
|
-
actions:
|
|
6812
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
|
|
6813
|
-
"Nova nota"
|
|
6814
|
-
] }) }) })
|
|
6965
|
+
actions: headerActions.length > 0 ? /* @__PURE__ */ jsx(PageActions, { actions: headerActions }) : void 0
|
|
6815
6966
|
}
|
|
6816
6967
|
),
|
|
6817
6968
|
fiscalProvider ? /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-md border border-emerald-300/50 bg-emerald-50 px-3 py-2 text-xs text-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-300", children: [
|
|
@@ -6895,6 +7046,9 @@ function CreateForm({ onCancel, onCreate }) {
|
|
|
6895
7046
|
] })
|
|
6896
7047
|
] });
|
|
6897
7048
|
}
|
|
7049
|
+
var FINANCIAL_MODULE_DEFAULTS = {
|
|
7050
|
+
commissions: true
|
|
7051
|
+
};
|
|
6898
7052
|
var FINANCIAL_DEFAULTS = {
|
|
6899
7053
|
autoInstallments: true,
|
|
6900
7054
|
requireDoc: false,
|
|
@@ -6904,10 +7058,20 @@ var FINANCIAL_DEFAULTS = {
|
|
|
6904
7058
|
overdueAlerts: true,
|
|
6905
7059
|
dailySummary: false
|
|
6906
7060
|
};
|
|
6907
|
-
function FinancialGeneralSettings() {
|
|
7061
|
+
function FinancialGeneralSettings({ config }) {
|
|
6908
7062
|
const t2 = useTranslation();
|
|
6909
7063
|
const s = useTenantPluginSettings("financial", FINANCIAL_DEFAULTS);
|
|
7064
|
+
const mods = useTenantPluginSettings("financial", FINANCIAL_MODULE_DEFAULTS);
|
|
6910
7065
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
7066
|
+
config?.modules.commissions && /* @__PURE__ */ jsx(SettingsGroup, { title: t2("financial.settings.modules"), description: t2("financial.settings.modulesDesc"), children: /* @__PURE__ */ jsx(
|
|
7067
|
+
ToggleRow,
|
|
7068
|
+
{
|
|
7069
|
+
label: t2("financial.settings.commissions"),
|
|
7070
|
+
description: t2("financial.settings.commissionsDesc"),
|
|
7071
|
+
checked: mods.get("commissions"),
|
|
7072
|
+
onChange: (v) => mods.set("commissions", v)
|
|
7073
|
+
}
|
|
7074
|
+
) }),
|
|
6911
7075
|
/* @__PURE__ */ jsxs(SettingsGroup, { title: t2("financial.settings.invoices"), description: t2("financial.settings.invoicesDesc"), children: [
|
|
6912
7076
|
/* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.autoInstallments"), description: t2("financial.settings.autoInstallmentsDesc"), checked: s.get("autoInstallments"), onChange: (v) => s.set("autoInstallments", v) }),
|
|
6913
7077
|
/* @__PURE__ */ jsx(ToggleRow, { label: t2("financial.settings.requireDoc"), description: t2("financial.settings.requireDocDesc"), checked: s.get("requireDoc"), onChange: (v) => s.set("requireDoc", v) }),
|
|
@@ -7101,7 +7265,13 @@ function buildNav(config, activeView, navigate, t2) {
|
|
|
7101
7265
|
}
|
|
7102
7266
|
return items;
|
|
7103
7267
|
}
|
|
7104
|
-
function FinancialPage({ config, provider, store: store6, registries }) {
|
|
7268
|
+
function FinancialPage({ config: appConfig, provider, store: store6, registries }) {
|
|
7269
|
+
const houseModules = useTenantPluginSettings("financial", FINANCIAL_MODULE_DEFAULTS);
|
|
7270
|
+
const commissionsOn = houseModules.get("commissions");
|
|
7271
|
+
const config = useMemo(
|
|
7272
|
+
() => appConfig.modules.commissions && commissionsOn ? appConfig : { ...appConfig, modules: { ...appConfig.modules, commissions: false } },
|
|
7273
|
+
[appConfig, commissionsOn]
|
|
7274
|
+
);
|
|
7105
7275
|
const { view, direction, navigate, previousView, back } = useModuleNavigation("/financial", {
|
|
7106
7276
|
summary: 0,
|
|
7107
7277
|
"payables-list": 0,
|
|
@@ -7188,7 +7358,7 @@ function FinancialPage({ config, provider, store: store6, registries }) {
|
|
|
7188
7358
|
PluginSettingsPanel,
|
|
7189
7359
|
{
|
|
7190
7360
|
title: t2("financial.settingsPage.title"),
|
|
7191
|
-
generalSettings: /* @__PURE__ */ jsx(FinancialGeneralSettings, {}),
|
|
7361
|
+
generalSettings: /* @__PURE__ */ jsx(FinancialGeneralSettings, { config: appConfig }),
|
|
7192
7362
|
registries,
|
|
7193
7363
|
hostPluginId: "financial",
|
|
7194
7364
|
routeBase: "/settings/financial"
|
|
@@ -8378,29 +8548,6 @@ function getClients() {
|
|
|
8378
8548
|
if (!supabase) throw new Error("Supabase not initialized");
|
|
8379
8549
|
return { core: supabase, pub: supabase };
|
|
8380
8550
|
}
|
|
8381
|
-
var _overdueSwept = false;
|
|
8382
|
-
async function sweepOverdue() {
|
|
8383
|
-
if (_overdueSwept) return;
|
|
8384
|
-
_overdueSwept = true;
|
|
8385
|
-
try {
|
|
8386
|
-
const { core, pub } = getClients();
|
|
8387
|
-
const today4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8388
|
-
const { data: overdueMovs } = await pub.from(T.movements).update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today4).select("invoice_id");
|
|
8389
|
-
if (overdueMovs && overdueMovs.length > 0) {
|
|
8390
|
-
const invoiceIds = [...new Set(overdueMovs.map((m) => m.invoice_id).filter(Boolean))];
|
|
8391
|
-
for (const invId of invoiceIds) {
|
|
8392
|
-
const { data: allMovs } = await pub.from(T.bills).select("status").eq("order_id", invId);
|
|
8393
|
-
const movs = allMovs ?? [];
|
|
8394
|
-
const hasOverdue = movs.some((m) => m.status === "overdue");
|
|
8395
|
-
const allPaid = movs.every((m) => m.status === "paid");
|
|
8396
|
-
const invoiceStatus = allPaid ? "paid" : hasOverdue ? "overdue" : "open";
|
|
8397
|
-
const invoiceStage = allPaid ? "paid" : hasOverdue ? "overdue" : "invoiced";
|
|
8398
|
-
await core.from("orders").update({ stage: invoiceStage }).eq("id", invId);
|
|
8399
|
-
}
|
|
8400
|
-
}
|
|
8401
|
-
} catch {
|
|
8402
|
-
}
|
|
8403
|
-
}
|
|
8404
8551
|
function round23(n) {
|
|
8405
8552
|
return Math.round(n * 100) / 100;
|
|
8406
8553
|
}
|
|
@@ -8849,6 +8996,29 @@ function createSupabaseFinancialProvider() {
|
|
|
8849
8996
|
* por fatura, então os indicadores são uma soma — não uma segunda contagem
|
|
8850
8997
|
* que poderia divergir da lista logo abaixo dela.
|
|
8851
8998
|
*/
|
|
8999
|
+
async searchSellableServices(term) {
|
|
9000
|
+
const { pub } = getClients();
|
|
9001
|
+
let q = pub.from("products").select("id, name, price").eq("kind", "service").not("is_active", "is", false).order("name").limit(20);
|
|
9002
|
+
if (term.trim()) q = q.ilike("name", `%${term.trim()}%`);
|
|
9003
|
+
const { data, error } = await q;
|
|
9004
|
+
if (error) throw error;
|
|
9005
|
+
return (data ?? []).map((p) => ({ id: p.id, name: p.name, price: Number(p.price ?? 0) }));
|
|
9006
|
+
},
|
|
9007
|
+
async prepayServices(personId, lines) {
|
|
9008
|
+
const { pub } = getClients();
|
|
9009
|
+
const { data, error } = await pub.rpc("clinical_prepay_services", {
|
|
9010
|
+
p_party: personId,
|
|
9011
|
+
p_lines: lines.map((l) => ({
|
|
9012
|
+
service_id: l.serviceId,
|
|
9013
|
+
name: l.name,
|
|
9014
|
+
quantity: l.quantity,
|
|
9015
|
+
unit_price: l.unitPrice
|
|
9016
|
+
}))
|
|
9017
|
+
});
|
|
9018
|
+
if (error) throw error;
|
|
9019
|
+
const r = data ?? {};
|
|
9020
|
+
return { ok: r.ok === true, reason: r.reason, orderId: r.order_id, total: r.total };
|
|
9021
|
+
},
|
|
8852
9022
|
async getClientAccount(personId) {
|
|
8853
9023
|
const { pub } = getClients();
|
|
8854
9024
|
const { data, error } = await pub.from("v_financial_invoice_balances").select("id, number, order_id, competence_date, total, paid, balance, status, description").eq("counterparty_person_id", personId).eq("direction", "receivable").is("cancelled_at", null).order("competence_date", { ascending: false });
|
|
@@ -8865,11 +9035,23 @@ function createSupabaseFinancialProvider() {
|
|
|
8865
9035
|
status: r.status,
|
|
8866
9036
|
description: r.description
|
|
8867
9037
|
}));
|
|
9038
|
+
let paidToExecute;
|
|
9039
|
+
let openToExecute;
|
|
9040
|
+
try {
|
|
9041
|
+
const { data: exec } = await pub.from("v_clinical_to_execute").select("paid_to_execute, open_to_execute").eq("party_id", personId).maybeSingle();
|
|
9042
|
+
if (exec) {
|
|
9043
|
+
paidToExecute = Number(exec.paid_to_execute) || 0;
|
|
9044
|
+
openToExecute = Number(exec.open_to_execute) || 0;
|
|
9045
|
+
}
|
|
9046
|
+
} catch {
|
|
9047
|
+
}
|
|
8868
9048
|
return {
|
|
8869
9049
|
personId,
|
|
8870
9050
|
contracted: invoices.reduce((a, i) => a + i.total, 0),
|
|
8871
9051
|
paid: invoices.reduce((a, i) => a + i.paid, 0),
|
|
8872
9052
|
open: invoices.reduce((a, i) => a + i.balance, 0),
|
|
9053
|
+
paidToExecute,
|
|
9054
|
+
openToExecute,
|
|
8873
9055
|
invoices
|
|
8874
9056
|
};
|
|
8875
9057
|
},
|
|
@@ -9747,39 +9929,24 @@ function createSupabaseFinancialProvider() {
|
|
|
9747
9929
|
if (error) throw error;
|
|
9748
9930
|
},
|
|
9749
9931
|
async getSummary(dateRange) {
|
|
9750
|
-
sweepOverdue();
|
|
9751
9932
|
const { pub } = getClients();
|
|
9752
|
-
const
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
if (cashError) throw cashError;
|
|
9760
|
-
const totalBalance = (accounts ?? []).reduce((s, a) => s + (Number(a.balance) || 0), 0);
|
|
9761
|
-
const allMovs = movs ?? [];
|
|
9762
|
-
const unpaid = allMovs.filter((m) => ["pending", "partial", "overdue"].includes(m.status));
|
|
9763
|
-
const today4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9764
|
-
const unpaidCredit = unpaid.filter((m) => m.direction === "credit");
|
|
9765
|
-
const unpaidDebit = unpaid.filter((m) => m.direction === "debit");
|
|
9766
|
-
const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today4);
|
|
9767
|
-
const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today4);
|
|
9768
|
-
const monthAgo = /* @__PURE__ */ new Date();
|
|
9769
|
-
monthAgo.setDate(monthAgo.getDate() - 30);
|
|
9770
|
-
const monthStart = monthAgo.toISOString().slice(0, 10);
|
|
9771
|
-
const period = dateRange ?? { from: monthStart, to: today4 };
|
|
9772
|
-
const monthPaid = (cashMovs ?? []).filter((m) => m.movement_kind === "payment" && m.payment_date >= period.from && m.payment_date <= period.to);
|
|
9933
|
+
const { data, error } = await pub.rpc("financial_summary", {
|
|
9934
|
+
p_from: dateRange?.from ?? null,
|
|
9935
|
+
p_to: dateRange?.to ?? null
|
|
9936
|
+
});
|
|
9937
|
+
if (error) throw error;
|
|
9938
|
+
const row = (Array.isArray(data) ? data[0] : data) ?? {};
|
|
9939
|
+
const n = (v) => Number(v) || 0;
|
|
9773
9940
|
return {
|
|
9774
|
-
totalBalance,
|
|
9775
|
-
totalReceivable:
|
|
9776
|
-
totalPayable:
|
|
9777
|
-
monthlyInflow:
|
|
9778
|
-
monthlyOutflow:
|
|
9779
|
-
overdueReceivableCount:
|
|
9780
|
-
overdueReceivableAmount:
|
|
9781
|
-
overduePayableCount:
|
|
9782
|
-
overduePayableAmount:
|
|
9941
|
+
totalBalance: n(row.total_balance),
|
|
9942
|
+
totalReceivable: n(row.total_receivable),
|
|
9943
|
+
totalPayable: n(row.total_payable),
|
|
9944
|
+
monthlyInflow: n(row.monthly_inflow),
|
|
9945
|
+
monthlyOutflow: n(row.monthly_outflow),
|
|
9946
|
+
overdueReceivableCount: n(row.overdue_receivable_count),
|
|
9947
|
+
overdueReceivableAmount: n(row.overdue_receivable_amount),
|
|
9948
|
+
overduePayableCount: n(row.overdue_payable_count),
|
|
9949
|
+
overduePayableAmount: n(row.overdue_payable_amount)
|
|
9783
9950
|
};
|
|
9784
9951
|
},
|
|
9785
9952
|
// --- Reconciliation (conciliação) ---
|
|
@@ -11255,6 +11422,9 @@ var en = {
|
|
|
11255
11422
|
"financial.invoice.summaryReceivable": "Receivable",
|
|
11256
11423
|
"financial.invoice.summaryPaid": "Paid",
|
|
11257
11424
|
"financial.invoice.summaryPayable": "Payable",
|
|
11425
|
+
"financial.invoice.loadFailed": "Could not load",
|
|
11426
|
+
"financial.invoice.loadFailedDesc": "Reading this invoice failed. The data is still there \u2014 it just did not reach the screen.",
|
|
11427
|
+
"financial.invoice.retry": "Try again",
|
|
11258
11428
|
"financial.invoice.notFound": "Invoice not found",
|
|
11259
11429
|
"financial.invoice.notFoundDesc": "This invoice could not be loaded.",
|
|
11260
11430
|
"financial.invoice.notes": "Notes",
|
|
@@ -11341,6 +11511,8 @@ var en = {
|
|
|
11341
11511
|
"financial.invoice.filterThisMonth": "This month",
|
|
11342
11512
|
"financial.invoice.filterLastMonth": "Last month",
|
|
11343
11513
|
"financial.invoice.filterLast30": "Last 30 days",
|
|
11514
|
+
"financial.invoice.filterLast90": "Last 90 days",
|
|
11515
|
+
"financial.invoice.filterMaxRange": "90-day maximum range",
|
|
11344
11516
|
"financial.invoice.filterThisYear": "This year",
|
|
11345
11517
|
"financial.invoice.filterCustom": "Custom",
|
|
11346
11518
|
"financial.invoice.filterFrom": "From",
|
|
@@ -11411,6 +11583,10 @@ var en = {
|
|
|
11411
11583
|
"financial.settings.cashRegisterDesc": "Cash session behavior",
|
|
11412
11584
|
"financial.settings.dailySummary": "Daily financial summary",
|
|
11413
11585
|
"financial.settings.dailySummaryDesc": "Send a daily email summary of receivables and payables",
|
|
11586
|
+
"financial.settings.modules": "Modules",
|
|
11587
|
+
"financial.settings.modulesDesc": "What this house shows inside Financial",
|
|
11588
|
+
"financial.settings.commissions": "Commissions",
|
|
11589
|
+
"financial.settings.commissionsDesc": "The tab that settles what the team earns per sale",
|
|
11414
11590
|
"financial.settings.invoices": "Invoices",
|
|
11415
11591
|
"financial.settings.invoicesDesc": "How invoices and payments are handled",
|
|
11416
11592
|
"financial.settings.notifications": "Notifications",
|
|
@@ -11829,6 +12005,9 @@ var ptBR = {
|
|
|
11829
12005
|
"financial.invoice.summaryReceivable": "A Receber",
|
|
11830
12006
|
"financial.invoice.summaryPaid": "Pago",
|
|
11831
12007
|
"financial.invoice.summaryPayable": "A Pagar",
|
|
12008
|
+
"financial.invoice.loadFailed": "N\xE3o foi poss\xEDvel carregar",
|
|
12009
|
+
"financial.invoice.loadFailedDesc": "A leitura desta fatura falhou. O dado continua no lugar \u2014 s\xF3 n\xE3o chegou at\xE9 aqui.",
|
|
12010
|
+
"financial.invoice.retry": "Tentar de novo",
|
|
11832
12011
|
"financial.invoice.notFound": "Fatura n\xE3o encontrada",
|
|
11833
12012
|
"financial.invoice.notFoundDesc": "Esta fatura n\xE3o p\xF4de ser carregada.",
|
|
11834
12013
|
"financial.invoice.notes": "Observa\xE7\xF5es",
|
|
@@ -11918,6 +12097,8 @@ var ptBR = {
|
|
|
11918
12097
|
"financial.invoice.filterThisMonth": "Este m\xEAs",
|
|
11919
12098
|
"financial.invoice.filterLastMonth": "M\xEAs passado",
|
|
11920
12099
|
"financial.invoice.filterLast30": "\xDAltimos 30 dias",
|
|
12100
|
+
"financial.invoice.filterLast90": "\xDAltimos 90 dias",
|
|
12101
|
+
"financial.invoice.filterMaxRange": "Per\xEDodo m\xE1ximo de 90 dias",
|
|
11921
12102
|
"financial.invoice.filterThisYear": "Este ano",
|
|
11922
12103
|
"financial.invoice.filterCustom": "Personalizado",
|
|
11923
12104
|
"financial.invoice.filterFrom": "De",
|
|
@@ -11988,6 +12169,10 @@ var ptBR = {
|
|
|
11988
12169
|
"financial.settings.cashRegisterDesc": "Comportamento da sess\xE3o de caixa",
|
|
11989
12170
|
"financial.settings.dailySummary": "Resumo financeiro di\xE1rio",
|
|
11990
12171
|
"financial.settings.dailySummaryDesc": "Enviar um resumo di\xE1rio por e-mail de contas a receber e a pagar",
|
|
12172
|
+
"financial.settings.modules": "M\xF3dulos",
|
|
12173
|
+
"financial.settings.modulesDesc": "O que desta casa aparece no Financeiro",
|
|
12174
|
+
"financial.settings.commissions": "Comiss\xF5es",
|
|
12175
|
+
"financial.settings.commissionsDesc": "A aba de apura\xE7\xE3o do que a equipe recebe por venda",
|
|
11991
12176
|
"financial.settings.invoices": "Faturas",
|
|
11992
12177
|
"financial.settings.invoicesDesc": "Como faturas e pagamentos s\xE3o processados",
|
|
11993
12178
|
"financial.settings.notifications": "Notifica\xE7\xF5es",
|
|
@@ -12441,7 +12626,7 @@ function CommissionRuleDialog({
|
|
|
12441
12626
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Crit\xE9rio vazio vale para todos. Quanto mais crit\xE9rios casarem com o atendimento, mais espec\xEDfica \u2014 e mais forte \u2014 a regra." })
|
|
12442
12627
|
] }),
|
|
12443
12628
|
/* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2", children: [
|
|
12444
|
-
/* @__PURE__ */ jsx(
|
|
12629
|
+
/* @__PURE__ */ jsx(Field, { label: "Servi\xE7os", children: /* @__PURE__ */ jsx(
|
|
12445
12630
|
MultiSelect,
|
|
12446
12631
|
{
|
|
12447
12632
|
allLabel: "Todos os servi\xE7os",
|
|
@@ -12450,7 +12635,7 @@ function CommissionRuleDialog({
|
|
|
12450
12635
|
options: catalogs.services.map((s) => ({ value: s.id, label: s.name }))
|
|
12451
12636
|
}
|
|
12452
12637
|
) }),
|
|
12453
|
-
/* @__PURE__ */ jsx(
|
|
12638
|
+
/* @__PURE__ */ jsx(Field, { label: "Grupos de servi\xE7o", children: /* @__PURE__ */ jsx(
|
|
12454
12639
|
MultiSelect,
|
|
12455
12640
|
{
|
|
12456
12641
|
allLabel: "Todos os grupos",
|
|
@@ -12459,7 +12644,7 @@ function CommissionRuleDialog({
|
|
|
12459
12644
|
options: catalogs.categories.map((c) => ({ value: c.id, label: c.name }))
|
|
12460
12645
|
}
|
|
12461
12646
|
) }),
|
|
12462
|
-
/* @__PURE__ */ jsx(
|
|
12647
|
+
/* @__PURE__ */ jsx(Field, { label: "Profissionais", children: /* @__PURE__ */ jsx(
|
|
12463
12648
|
MultiSelect,
|
|
12464
12649
|
{
|
|
12465
12650
|
allLabel: "Todos os profissionais",
|
|
@@ -12468,7 +12653,7 @@ function CommissionRuleDialog({
|
|
|
12468
12653
|
options: catalogs.professionals.map((p) => ({ value: p.id, label: p.name }))
|
|
12469
12654
|
}
|
|
12470
12655
|
) }),
|
|
12471
|
-
/* @__PURE__ */ jsx(
|
|
12656
|
+
/* @__PURE__ */ jsx(Field, { label: "Unidades", children: /* @__PURE__ */ jsx(
|
|
12472
12657
|
MultiSelect,
|
|
12473
12658
|
{
|
|
12474
12659
|
allLabel: "Todas as unidades",
|
|
@@ -12478,7 +12663,7 @@ function CommissionRuleDialog({
|
|
|
12478
12663
|
}
|
|
12479
12664
|
) })
|
|
12480
12665
|
] }),
|
|
12481
|
-
/* @__PURE__ */ jsx(
|
|
12666
|
+
/* @__PURE__ */ jsx(Field, { label: "Dias da semana", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3 rounded-input border border-input bg-card px-3 py-2", children: WEEKDAYS.map((d) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-xs", children: [
|
|
12482
12667
|
/* @__PURE__ */ jsx(
|
|
12483
12668
|
Checkbox,
|
|
12484
12669
|
{
|
|
@@ -12488,7 +12673,7 @@ function CommissionRuleDialog({
|
|
|
12488
12673
|
),
|
|
12489
12674
|
d.label
|
|
12490
12675
|
] }, d.value)) }) }),
|
|
12491
|
-
/* @__PURE__ */ jsx(
|
|
12676
|
+
/* @__PURE__ */ jsx(Field, { label: "Faixas de hor\xE1rio", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-input border border-input bg-card px-3 py-2", children: [
|
|
12492
12677
|
ranges.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "Sem faixa, a regra vale em qualquer hor\xE1rio." }),
|
|
12493
12678
|
ranges.map((r, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
12494
12679
|
/* @__PURE__ */ jsx(
|
|
@@ -12550,7 +12735,7 @@ var LABEL = {
|
|
|
12550
12735
|
professional_group: "Grupos de Profissionais",
|
|
12551
12736
|
payment_method_type: "Formas de Pagamento"
|
|
12552
12737
|
};
|
|
12553
|
-
function
|
|
12738
|
+
function Field({ label, children }) {
|
|
12554
12739
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
12555
12740
|
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: label }),
|
|
12556
12741
|
/* @__PURE__ */ jsx("div", { className: "mt-0.5", children })
|
|
@@ -12621,7 +12806,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
|
|
|
12621
12806
|
/* @__PURE__ */ jsx(ModalBody, { className: "fz-container px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
12622
12807
|
functions.map((fn, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card p-3", children: [
|
|
12623
12808
|
/* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-3", children: [
|
|
12624
|
-
/* @__PURE__ */ jsx(
|
|
12809
|
+
/* @__PURE__ */ jsx(Field2, { label: "Fun\xE7\xE3o", children: /* @__PURE__ */ jsx(
|
|
12625
12810
|
Input,
|
|
12626
12811
|
{
|
|
12627
12812
|
value: fn.functionName,
|
|
@@ -12630,7 +12815,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
|
|
|
12630
12815
|
onChange: (e) => patch(i, { functionName: e.target.value })
|
|
12631
12816
|
}
|
|
12632
12817
|
) }),
|
|
12633
|
-
/* @__PURE__ */ jsx(
|
|
12818
|
+
/* @__PURE__ */ jsx(Field2, { label: "Conta padr\xE3o", children: /* @__PURE__ */ jsx(
|
|
12634
12819
|
NativeSelect,
|
|
12635
12820
|
{
|
|
12636
12821
|
value: fn.accountType,
|
|
@@ -12638,7 +12823,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
|
|
|
12638
12823
|
options: ACCOUNT_TYPES
|
|
12639
12824
|
}
|
|
12640
12825
|
) }),
|
|
12641
|
-
/* @__PURE__ */ jsx(
|
|
12826
|
+
/* @__PURE__ */ jsx(Field2, { label: "Sobre", children: /* @__PURE__ */ jsx(
|
|
12642
12827
|
NativeSelect,
|
|
12643
12828
|
{
|
|
12644
12829
|
value: fn.calculationBase,
|
|
@@ -12648,7 +12833,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
|
|
|
12648
12833
|
) })
|
|
12649
12834
|
] }),
|
|
12650
12835
|
/* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 mt-3", children: [
|
|
12651
|
-
/* @__PURE__ */ jsx(
|
|
12836
|
+
/* @__PURE__ */ jsx(Field2, { label: "Faixa de produ\xE7\xE3o", children: /* @__PURE__ */ jsx(
|
|
12652
12837
|
NativeSelect,
|
|
12653
12838
|
{
|
|
12654
12839
|
value: fn.tierMode,
|
|
@@ -12656,7 +12841,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
|
|
|
12656
12841
|
options: TIER_MODES
|
|
12657
12842
|
}
|
|
12658
12843
|
) }),
|
|
12659
|
-
(fn.tierMode === "none" || fn.tierMode === "guaranteed_min") && /* @__PURE__ */ jsx(
|
|
12844
|
+
(fn.tierMode === "none" || fn.tierMode === "guaranteed_min") && /* @__PURE__ */ jsx(Field2, { label: "Valor", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
12660
12845
|
/* @__PURE__ */ jsx(
|
|
12661
12846
|
Input,
|
|
12662
12847
|
{
|
|
@@ -12677,7 +12862,7 @@ function CommissionFunctionsDialog({ rule, provider, onClose, onSaved }) {
|
|
|
12677
12862
|
)
|
|
12678
12863
|
] }) })
|
|
12679
12864
|
] }),
|
|
12680
|
-
fn.tierMode === "guaranteed_min" && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs(
|
|
12865
|
+
fn.tierMode === "guaranteed_min" && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs(Field2, { label: "Piso mensal garantido (R$)", children: [
|
|
12681
12866
|
/* @__PURE__ */ jsx(
|
|
12682
12867
|
Input,
|
|
12683
12868
|
{
|
|
@@ -12806,7 +12991,7 @@ function TierTable({ tiers, onChange }) {
|
|
|
12806
12991
|
/* @__PURE__ */ jsx("p", { className: "mt-2 text-[10px] text-muted-foreground", children: "Ex.: 0\u20131.800 fixo R$ 180; acima de 1.800 \u2192 20% s\xF3 do que passar de 1.800." })
|
|
12807
12992
|
] });
|
|
12808
12993
|
}
|
|
12809
|
-
function
|
|
12994
|
+
function Field2({ label, children }) {
|
|
12810
12995
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
12811
12996
|
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: label }),
|
|
12812
12997
|
/* @__PURE__ */ jsx("div", { className: "mt-0.5", children })
|
|
@@ -12927,15 +13112,20 @@ function CommissionRulesView({ provider }) {
|
|
|
12927
13112
|
await provider.duplicateCommissionRule?.(rule.id);
|
|
12928
13113
|
await load();
|
|
12929
13114
|
}
|
|
13115
|
+
const headerActions = React15__default.useMemo(() => [{
|
|
13116
|
+
id: "new-rule",
|
|
13117
|
+
label: "Adicionar regra",
|
|
13118
|
+
icon: Plus,
|
|
13119
|
+
intent: "primary",
|
|
13120
|
+
creates: true,
|
|
13121
|
+
onSelect: () => setEditing("new")
|
|
13122
|
+
}], []);
|
|
12930
13123
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
12931
13124
|
/* @__PURE__ */ jsx(
|
|
12932
13125
|
SubpageHeader,
|
|
12933
13126
|
{
|
|
12934
13127
|
title: "Regras de Comiss\xE3o",
|
|
12935
|
-
actions: /* @__PURE__ */ jsx(
|
|
12936
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
|
|
12937
|
-
" Adicionar"
|
|
12938
|
-
] }) })
|
|
13128
|
+
actions: /* @__PURE__ */ jsx(PageActions, { actions: headerActions })
|
|
12939
13129
|
}
|
|
12940
13130
|
),
|
|
12941
13131
|
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
@@ -31908,110 +32098,1453 @@ REVOKE ALL ON FUNCTION migration.upsert_fin_movement(uuid, uuid, jsonb, jsonb)
|
|
|
31908
32098
|
REVOKE ALL ON FUNCTION migration._fin_installment_from_bill(uuid, uuid, uuid, uuid, integer, date, numeric, boolean, boolean)
|
|
31909
32099
|
FROM PUBLIC, anon, authenticated;
|
|
31910
32100
|
`;
|
|
31911
|
-
var
|
|
31912
|
-
|
|
31913
|
-
|
|
31914
|
-
|
|
31915
|
-
|
|
31916
|
-
|
|
31917
|
-
|
|
31918
|
-
|
|
31919
|
-
|
|
31920
|
-
|
|
31921
|
-
|
|
31922
|
-
|
|
31923
|
-
|
|
31924
|
-
|
|
31925
|
-
|
|
31926
|
-
|
|
31927
|
-
|
|
31928
|
-
|
|
31929
|
-
|
|
31930
|
-
|
|
31931
|
-
|
|
31932
|
-
|
|
31933
|
-
|
|
31934
|
-
|
|
31935
|
-
|
|
31936
|
-
|
|
31937
|
-
|
|
31938
|
-
|
|
31939
|
-
|
|
31940
|
-
|
|
31941
|
-
|
|
31942
|
-
|
|
31943
|
-
|
|
31944
|
-
|
|
31945
|
-
|
|
31946
|
-
|
|
31947
|
-
|
|
31948
|
-
|
|
31949
|
-
|
|
31950
|
-
|
|
31951
|
-
|
|
31952
|
-
|
|
31953
|
-
|
|
31954
|
-
|
|
31955
|
-
|
|
31956
|
-
|
|
31957
|
-
|
|
31958
|
-
|
|
31959
|
-
|
|
31960
|
-
|
|
31961
|
-
|
|
31962
|
-
|
|
31963
|
-
|
|
31964
|
-
|
|
31965
|
-
|
|
31966
|
-
|
|
31967
|
-
|
|
31968
|
-
|
|
31969
|
-
|
|
31970
|
-
|
|
31971
|
-
|
|
31972
|
-
|
|
31973
|
-
|
|
31974
|
-
|
|
31975
|
-
|
|
31976
|
-
|
|
31977
|
-
|
|
31978
|
-
|
|
31979
|
-
|
|
31980
|
-
|
|
31981
|
-
|
|
31982
|
-
|
|
31983
|
-
|
|
31984
|
-
|
|
31985
|
-
|
|
31986
|
-
|
|
31987
|
-
|
|
31988
|
-
|
|
31989
|
-
|
|
31990
|
-
|
|
31991
|
-
|
|
31992
|
-
|
|
31993
|
-
|
|
31994
|
-
|
|
31995
|
-
|
|
31996
|
-
|
|
31997
|
-
|
|
31998
|
-
|
|
31999
|
-
|
|
32000
|
-
|
|
32001
|
-
|
|
32002
|
-
|
|
32003
|
-
|
|
32004
|
-
|
|
32005
|
-
|
|
32006
|
-
|
|
32007
|
-
|
|
32008
|
-
|
|
32009
|
-
|
|
32010
|
-
|
|
32011
|
-
|
|
32012
|
-
|
|
32013
|
-
|
|
32014
|
-
|
|
32101
|
+
var MIGRATION_080_A_REMESSA_E_A_COMISSAO_A_PAGAR_TEM_ONDE_MORAR = `-- A remessa banc\xE1ria e a comiss\xE3o a pagar t\xEAm onde morar.
|
|
32102
|
+
--
|
|
32103
|
+
-- Cinco tabelas do V1 sem destino, dois assuntos:
|
|
32104
|
+
--
|
|
32105
|
+
-- cnab_remessas 59 o arquivo de remessa enviado ao banco
|
|
32106
|
+
-- cnab_remessa_items 700 o pagamento dentro dele
|
|
32107
|
+
-- commission_payables 1 a comiss\xE3o apurada virando conta a pagar
|
|
32108
|
+
-- commission_payable_items 3 a linha da apura\xE7\xE3o dentro dela
|
|
32109
|
+
-- commission_payee_prefs 1 para qual conta cont\xE1bil vai a comiss\xE3o
|
|
32110
|
+
--
|
|
32111
|
+
-- O volume \xE9 pequeno \u2014 CNAB \xE9 do Pertinho do C\xE9u, comiss\xE3o a pagar mal come\xE7ou
|
|
32112
|
+
-- no Espa\xE7o Facial. Mas nenhuma das duas \xE9 deriv\xE1vel: a remessa \xE9 um arquivo
|
|
32113
|
+
-- que j\xE1 saiu para o banco com um n\xFAmero de sequ\xEAncia que n\xE3o pode repetir, e a
|
|
32114
|
+
-- comiss\xE3o a pagar \xE9 a ponte entre o que foi APURADO e o T\xEDtulo que algu\xE9m vai
|
|
32115
|
+
-- pagar. Reconstituir qualquer uma seria inventar.
|
|
32116
|
+
--
|
|
32117
|
+
-- \u2500\u2500 O que o V2 j\xE1 tinha, e por isso n\xE3o se repete aqui \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
32118
|
+
--
|
|
32119
|
+
-- \`plg_financial_commission_entries\`, \`_generations\`, \`_rules\`,
|
|
32120
|
+
-- \`_rule_criteria\` e \`_functions\` j\xE1 existem. O que faltava era o PAG\xC1VEL \u2014 o
|
|
32121
|
+
-- elo entre a apura\xE7\xE3o e o contas a pagar. Por isso estas duas tabelas e n\xE3o um
|
|
32122
|
+
-- dom\xEDnio inteiro.
|
|
32123
|
+
--
|
|
32124
|
+
-- \u2500\u2500 O arquivo de remessa \xE9 arquivo \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
|
|
32125
|
+
--
|
|
32126
|
+
-- \`cnab_remessas.file_content\` guarda o CNAB inteiro numa coluna de texto.
|
|
32127
|
+
-- Mesma decis\xE3o da caixa de entrada da NF-e: arquivo mora em
|
|
32128
|
+
-- \`public.documents\`, e aqui fica o ponteiro. O que N\xC3O sai da linha \xE9
|
|
32129
|
+
-- \`sequence_number\`: ele \xE9 do banco, n\xE3o do arquivo \u2014 dois arquivos com o mesmo
|
|
32130
|
+
-- n\xFAmero s\xE3o uma remessa rejeitada.
|
|
32131
|
+
--
|
|
32132
|
+
-- Idempotente.
|
|
32133
|
+
|
|
32134
|
+
CREATE TABLE IF NOT EXISTS public.plg_financial_cnab_batches (
|
|
32135
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
32136
|
+
tenant_id uuid NOT NULL,
|
|
32137
|
+
unit_id uuid,
|
|
32138
|
+
bank_account_id uuid,
|
|
32139
|
+
layout text,
|
|
32140
|
+
-- O n\xFAmero que o banco conta. \xDAnico por conta: repetir \xE9 remessa rejeitada.
|
|
32141
|
+
sequence_number integer,
|
|
32142
|
+
total_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
|
32143
|
+
items_count integer DEFAULT 0 NOT NULL,
|
|
32144
|
+
file_name text,
|
|
32145
|
+
-- O CNAB em si vive em public.documents; aqui fica o ponteiro.
|
|
32146
|
+
document_id uuid,
|
|
32147
|
+
generated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
32148
|
+
owner_id uuid,
|
|
32149
|
+
metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
32150
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
32151
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
32152
|
+
);
|
|
32153
|
+
|
|
32154
|
+
COMMENT ON TABLE public.plg_financial_cnab_batches IS
|
|
32155
|
+
'O arquivo de remessa enviado ao banco. V1: cnab_remessas. O arquivo mora em public.documents; o n\xFAmero de sequ\xEAncia mora aqui, porque \xE9 do banco.';
|
|
32156
|
+
|
|
32157
|
+
CREATE TABLE IF NOT EXISTS public.plg_financial_cnab_items (
|
|
32158
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
32159
|
+
tenant_id uuid NOT NULL,
|
|
32160
|
+
batch_id uuid NOT NULL,
|
|
32161
|
+
movement_id uuid,
|
|
32162
|
+
payment_form text,
|
|
32163
|
+
payment_date date,
|
|
32164
|
+
amount numeric(14,2) DEFAULT 0 NOT NULL,
|
|
32165
|
+
metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
32166
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
32167
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
32168
|
+
);
|
|
32169
|
+
|
|
32170
|
+
COMMENT ON TABLE public.plg_financial_cnab_items IS
|
|
32171
|
+
'O pagamento dentro da remessa, ligado ao movimento financeiro que ele quita. V1: cnab_remessa_items.';
|
|
32172
|
+
|
|
32173
|
+
CREATE TABLE IF NOT EXISTS public.plg_financial_commission_payables (
|
|
32174
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
32175
|
+
tenant_id uuid NOT NULL,
|
|
32176
|
+
unit_id uuid,
|
|
32177
|
+
-- O T\xEDtulo que a apura\xE7\xE3o gerou. \xC9 este elo que faltava no V2.
|
|
32178
|
+
invoice_id uuid,
|
|
32179
|
+
beneficiary_party_id uuid,
|
|
32180
|
+
chart_account_id uuid,
|
|
32181
|
+
due_date date,
|
|
32182
|
+
total_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
|
32183
|
+
entries_count integer DEFAULT 0 NOT NULL,
|
|
32184
|
+
status text DEFAULT 'pending'::text NOT NULL,
|
|
32185
|
+
launched_at timestamp with time zone,
|
|
32186
|
+
canceled_at timestamp with time zone,
|
|
32187
|
+
-- Quem cancelou: a tela, o estorno do T\xEDtulo, ou a reapura\xE7\xE3o. O V1 guarda
|
|
32188
|
+
-- a palavra e ela explica por que a comiss\xE3o sumiu do relat\xF3rio.
|
|
32189
|
+
cancel_source text,
|
|
32190
|
+
owner_id uuid,
|
|
32191
|
+
metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
32192
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
32193
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
32194
|
+
);
|
|
32195
|
+
|
|
32196
|
+
COMMENT ON TABLE public.plg_financial_commission_payables IS
|
|
32197
|
+
'A comiss\xE3o apurada virando conta a pagar \u2014 o elo entre plg_financial_commission_entries e o T\xEDtulo. V1: commission_payables.';
|
|
32198
|
+
|
|
32199
|
+
CREATE TABLE IF NOT EXISTS public.plg_financial_commission_payable_items (
|
|
32200
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
32201
|
+
tenant_id uuid NOT NULL,
|
|
32202
|
+
payable_id uuid NOT NULL,
|
|
32203
|
+
-- A linha da venda que gerou a comiss\xE3o, e a entrada de apura\xE7\xE3o dela.
|
|
32204
|
+
source_item_id uuid,
|
|
32205
|
+
item_id uuid,
|
|
32206
|
+
aggregate_entry_id uuid,
|
|
32207
|
+
description text,
|
|
32208
|
+
amount numeric(14,2) DEFAULT 0 NOT NULL,
|
|
32209
|
+
-- \`entry_ids\` do V1 \xE9 um array de apura\xE7\xF5es agregadas numa linha s\xF3. Vem
|
|
32210
|
+
-- como jsonb de refer\xEAncias: s\xE3o ids do V1 que s\xF3 resolvem depois que
|
|
32211
|
+
-- commission_entries atravessar, e um array de uuid faria a linha mentir
|
|
32212
|
+
-- enquanto isso.
|
|
32213
|
+
entry_refs jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
32214
|
+
metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
32215
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
32216
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
32217
|
+
);
|
|
32218
|
+
|
|
32219
|
+
CREATE TABLE IF NOT EXISTS public.plg_financial_commission_payee_prefs (
|
|
32220
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
32221
|
+
tenant_id uuid NOT NULL,
|
|
32222
|
+
party_id uuid NOT NULL,
|
|
32223
|
+
chart_account_id uuid,
|
|
32224
|
+
metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
32225
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
32226
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
32227
|
+
);
|
|
32228
|
+
|
|
32229
|
+
COMMENT ON TABLE public.plg_financial_commission_payee_prefs IS
|
|
32230
|
+
'Para qual conta cont\xE1bil a comiss\xE3o daquela pessoa vai. Uma por pessoa. V1: commission_payee_prefs.';
|
|
32231
|
+
|
|
32232
|
+
-- \u2500\u2500 chaves \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
32233
|
+
DO $$
|
|
32234
|
+
DECLARE t text;
|
|
32235
|
+
BEGIN
|
|
32236
|
+
FOREACH t IN ARRAY ARRAY['plg_financial_cnab_batches', 'plg_financial_cnab_items',
|
|
32237
|
+
'plg_financial_commission_payables', 'plg_financial_commission_payable_items',
|
|
32238
|
+
'plg_financial_commission_payee_prefs'] LOOP
|
|
32239
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = ('public.' || t)::regclass AND contype = 'p') THEN
|
|
32240
|
+
EXECUTE format('ALTER TABLE ONLY public.%I ADD CONSTRAINT %I PRIMARY KEY (id)', t, t || '_pkey');
|
|
32241
|
+
END IF;
|
|
32242
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = t || '_tenant_id_id_key') THEN
|
|
32243
|
+
EXECUTE format('ALTER TABLE ONLY public.%I ADD CONSTRAINT %I UNIQUE (tenant_id, id)', t, t || '_tenant_id_id_key');
|
|
32244
|
+
END IF;
|
|
32245
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = t || '_tenant_fkey') THEN
|
|
32246
|
+
EXECUTE format('ALTER TABLE ONLY public.%I ADD CONSTRAINT %I FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE', t, t || '_tenant_fkey');
|
|
32247
|
+
END IF;
|
|
32248
|
+
END LOOP;
|
|
32249
|
+
END $$;
|
|
32250
|
+
|
|
32251
|
+
DO $$ BEGIN
|
|
32252
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_cnab_batches_unit_fkey') THEN
|
|
32253
|
+
ALTER TABLE ONLY public.plg_financial_cnab_batches ADD CONSTRAINT plg_financial_cnab_batches_unit_fkey
|
|
32254
|
+
FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;
|
|
32255
|
+
END IF;
|
|
32256
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_cnab_batches_account_fkey') THEN
|
|
32257
|
+
ALTER TABLE ONLY public.plg_financial_cnab_batches ADD CONSTRAINT plg_financial_cnab_batches_account_fkey
|
|
32258
|
+
FOREIGN KEY (bank_account_id) REFERENCES public.plg_financial_bank_accounts(id) ON DELETE SET NULL;
|
|
32259
|
+
END IF;
|
|
32260
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_cnab_batches_document_fkey') THEN
|
|
32261
|
+
ALTER TABLE ONLY public.plg_financial_cnab_batches ADD CONSTRAINT plg_financial_cnab_batches_document_fkey
|
|
32262
|
+
FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE SET NULL;
|
|
32263
|
+
END IF;
|
|
32264
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_cnab_items_batch_fkey') THEN
|
|
32265
|
+
ALTER TABLE ONLY public.plg_financial_cnab_items ADD CONSTRAINT plg_financial_cnab_items_batch_fkey
|
|
32266
|
+
FOREIGN KEY (tenant_id, batch_id) REFERENCES public.plg_financial_cnab_batches(tenant_id, id) ON DELETE CASCADE;
|
|
32267
|
+
END IF;
|
|
32268
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_cnab_items_movement_fkey') THEN
|
|
32269
|
+
ALTER TABLE ONLY public.plg_financial_cnab_items ADD CONSTRAINT plg_financial_cnab_items_movement_fkey
|
|
32270
|
+
FOREIGN KEY (movement_id) REFERENCES public.plg_financial_movements(id) ON DELETE SET NULL;
|
|
32271
|
+
END IF;
|
|
32272
|
+
|
|
32273
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payables_unit_fkey') THEN
|
|
32274
|
+
ALTER TABLE ONLY public.plg_financial_commission_payables ADD CONSTRAINT plg_financial_commission_payables_unit_fkey
|
|
32275
|
+
FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;
|
|
32276
|
+
END IF;
|
|
32277
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payables_invoice_fkey') THEN
|
|
32278
|
+
ALTER TABLE ONLY public.plg_financial_commission_payables ADD CONSTRAINT plg_financial_commission_payables_invoice_fkey
|
|
32279
|
+
FOREIGN KEY (invoice_id) REFERENCES public.plg_financial_invoices(id) ON DELETE SET NULL;
|
|
32280
|
+
END IF;
|
|
32281
|
+
-- S\xF3 enquanto a coluna existe com ESTE nome. A 082 renomeia
|
|
32282
|
+
-- \`beneficiary_party_id\` para \`beneficiary_account_id\` (o benefici\xE1rio da
|
|
32283
|
+
-- comiss\xE3o \xE9 uma CONTA, n\xE3o uma pessoa), e num replay este arquivo roda de
|
|
32284
|
+
-- novo sobre a tabela j\xE1 corrigida: sem a guarda, ele estoura com "column
|
|
32285
|
+
-- beneficiary_party_id referenced in foreign key constraint does not exist" e
|
|
32286
|
+
-- derruba a corrente inteira.
|
|
32287
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payables_party_fkey')
|
|
32288
|
+
AND EXISTS (SELECT 1 FROM information_schema.columns
|
|
32289
|
+
WHERE table_schema = 'public' AND table_name = 'plg_financial_commission_payables'
|
|
32290
|
+
AND column_name = 'beneficiary_party_id') THEN
|
|
32291
|
+
ALTER TABLE ONLY public.plg_financial_commission_payables ADD CONSTRAINT plg_financial_commission_payables_party_fkey
|
|
32292
|
+
FOREIGN KEY (beneficiary_party_id) REFERENCES public.people(id) ON DELETE SET NULL;
|
|
32293
|
+
END IF;
|
|
32294
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payables_chart_fkey') THEN
|
|
32295
|
+
ALTER TABLE ONLY public.plg_financial_commission_payables ADD CONSTRAINT plg_financial_commission_payables_chart_fkey
|
|
32296
|
+
FOREIGN KEY (chart_account_id) REFERENCES public.plg_financial_chart_of_accounts(id) ON DELETE SET NULL;
|
|
32297
|
+
END IF;
|
|
32298
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payable_items_payable_fkey') THEN
|
|
32299
|
+
ALTER TABLE ONLY public.plg_financial_commission_payable_items ADD CONSTRAINT plg_financial_commission_payable_items_payable_fkey
|
|
32300
|
+
FOREIGN KEY (tenant_id, payable_id) REFERENCES public.plg_financial_commission_payables(tenant_id, id) ON DELETE CASCADE;
|
|
32301
|
+
END IF;
|
|
32302
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payable_items_item_fkey') THEN
|
|
32303
|
+
ALTER TABLE ONLY public.plg_financial_commission_payable_items ADD CONSTRAINT plg_financial_commission_payable_items_item_fkey
|
|
32304
|
+
FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE SET NULL;
|
|
32305
|
+
END IF;
|
|
32306
|
+
-- Mesma hist\xF3ria: a 082 renomeia \`party_id\` para \`account_id\` aqui.
|
|
32307
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payee_prefs_party_fkey')
|
|
32308
|
+
AND EXISTS (SELECT 1 FROM information_schema.columns
|
|
32309
|
+
WHERE table_schema = 'public' AND table_name = 'plg_financial_commission_payee_prefs'
|
|
32310
|
+
AND column_name = 'party_id') THEN
|
|
32311
|
+
ALTER TABLE ONLY public.plg_financial_commission_payee_prefs ADD CONSTRAINT plg_financial_commission_payee_prefs_party_fkey
|
|
32312
|
+
FOREIGN KEY (party_id) REFERENCES public.people(id) ON DELETE CASCADE;
|
|
32313
|
+
END IF;
|
|
32314
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payee_prefs_chart_fkey') THEN
|
|
32315
|
+
ALTER TABLE ONLY public.plg_financial_commission_payee_prefs ADD CONSTRAINT plg_financial_commission_payee_prefs_chart_fkey
|
|
32316
|
+
FOREIGN KEY (chart_account_id) REFERENCES public.plg_financial_chart_of_accounts(id) ON DELETE SET NULL;
|
|
32317
|
+
END IF;
|
|
32318
|
+
END $$;
|
|
32319
|
+
|
|
32320
|
+
-- Dois arquivos de remessa com o mesmo n\xFAmero na mesma conta \xE9 remessa
|
|
32321
|
+
-- rejeitada pelo banco. O \xEDndice \xE9 a trava que o V1 n\xE3o tinha.
|
|
32322
|
+
CREATE UNIQUE INDEX IF NOT EXISTS plg_financial_cnab_batches_sequence_uidx
|
|
32323
|
+
ON public.plg_financial_cnab_batches USING btree (tenant_id, bank_account_id, sequence_number)
|
|
32324
|
+
WHERE bank_account_id IS NOT NULL AND sequence_number IS NOT NULL;
|
|
32325
|
+
CREATE INDEX IF NOT EXISTS plg_financial_cnab_items_batch_idx
|
|
32326
|
+
ON public.plg_financial_cnab_items USING btree (tenant_id, batch_id);
|
|
32327
|
+
DO $$ BEGIN
|
|
32328
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns
|
|
32329
|
+
WHERE table_schema = 'public' AND table_name = 'plg_financial_commission_payables'
|
|
32330
|
+
AND column_name = 'beneficiary_party_id') THEN
|
|
32331
|
+
CREATE INDEX IF NOT EXISTS plg_financial_commission_payables_party_idx
|
|
32332
|
+
ON public.plg_financial_commission_payables USING btree (tenant_id, beneficiary_party_id, due_date DESC);
|
|
32333
|
+
END IF;
|
|
32334
|
+
END $$;
|
|
32335
|
+
CREATE INDEX IF NOT EXISTS plg_financial_commission_payables_open_idx
|
|
32336
|
+
ON public.plg_financial_commission_payables USING btree (tenant_id, unit_id, due_date)
|
|
32337
|
+
WHERE canceled_at IS NULL;
|
|
32338
|
+
CREATE INDEX IF NOT EXISTS plg_financial_commission_payable_items_payable_idx
|
|
32339
|
+
ON public.plg_financial_commission_payable_items USING btree (tenant_id, payable_id);
|
|
32340
|
+
-- Uma prefer\xEAncia por pessoa: duas seriam duas respostas para "onde lan\xE7ar".
|
|
32341
|
+
-- Guardado pelo mesmo motivo da FK acima: a 082 renomeia esta coluna.
|
|
32342
|
+
DO $$ BEGIN
|
|
32343
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns
|
|
32344
|
+
WHERE table_schema = 'public' AND table_name = 'plg_financial_commission_payee_prefs'
|
|
32345
|
+
AND column_name = 'party_id') THEN
|
|
32346
|
+
CREATE UNIQUE INDEX IF NOT EXISTS plg_financial_commission_payee_prefs_party_uidx
|
|
32347
|
+
ON public.plg_financial_commission_payee_prefs USING btree (tenant_id, party_id);
|
|
32348
|
+
END IF;
|
|
32349
|
+
END $$;
|
|
32350
|
+
|
|
32351
|
+
-- \u2500\u2500 gatilho de updated_at \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
|
|
32352
|
+
DO $$
|
|
32353
|
+
DECLARE t text;
|
|
32354
|
+
BEGIN
|
|
32355
|
+
FOREACH t IN ARRAY ARRAY['plg_financial_cnab_batches', 'plg_financial_cnab_items',
|
|
32356
|
+
'plg_financial_commission_payables', 'plg_financial_commission_payable_items',
|
|
32357
|
+
'plg_financial_commission_payee_prefs'] LOOP
|
|
32358
|
+
EXECUTE format('DROP TRIGGER IF EXISTS %I ON public.%I', t || '_updated_at', t);
|
|
32359
|
+
EXECUTE format('CREATE TRIGGER %I BEFORE UPDATE ON public.%I FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at()', t || '_updated_at', t);
|
|
32360
|
+
END LOOP;
|
|
32361
|
+
END $$;
|
|
32362
|
+
|
|
32363
|
+
-- \u2500\u2500 quem enxerga: o scaffold, e n\xE3o policy escrita \xE0 m\xE3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
32364
|
+
--
|
|
32365
|
+
-- \`app.scaffold_table\` \xE9 a porta \xFAnica de autoriza\xE7\xE3o do financeiro: ele
|
|
32366
|
+
-- registra a tabela em \`app.scaffold_registry\`, gera as pol\xEDticas com o nome
|
|
32367
|
+
-- que o gate cobra (\`*_authz_*\`) e liga a permiss\xE3o do plugin junto com as
|
|
32368
|
+
-- escotilhas de compartilhamento. Escrever a policy \xE0 m\xE3o criaria a SEGUNDA
|
|
32369
|
+
-- regra de autoriza\xE7\xE3o do financeiro \u2014 e foi exatamente o que este arquivo fez
|
|
32370
|
+
-- na primeira vers\xE3o: 41 asser\xE7\xF5es de \`004_negative_authz\` viraram 40, porque
|
|
32371
|
+
-- as cinco tabelas tinham pol\xEDtica e n\xE3o tinham declara\xE7\xE3o.
|
|
32372
|
+
--
|
|
32373
|
+
-- \`unit_optional\` nas que t\xEAm \`unit_id\` anul\xE1vel (a remessa pode ser da rede, a
|
|
32374
|
+
-- comiss\xE3o pode n\xE3o ter filial); \`tenant\` nas tr\xEAs que n\xE3o t\xEAm unidade nenhuma
|
|
32375
|
+
-- \u2014 declarar \`unit\` numa coluna que n\xE3o existe esconde a tabela inteira.
|
|
32376
|
+
SELECT app.scaffold_table('public.plg_financial_cnab_batches'::regclass,
|
|
32377
|
+
'financial.cnab_batch', 'financial', false, 'enforce');
|
|
32378
|
+
SELECT app.scaffold_scope('public.plg_financial_cnab_batches'::regclass, 'unit_optional', true);
|
|
32379
|
+
|
|
32380
|
+
SELECT app.scaffold_table('public.plg_financial_cnab_items'::regclass,
|
|
32381
|
+
'financial.cnab_item', 'financial', false, 'enforce');
|
|
32382
|
+
SELECT app.scaffold_scope('public.plg_financial_cnab_items'::regclass, 'tenant', true);
|
|
32383
|
+
|
|
32384
|
+
SELECT app.scaffold_table('public.plg_financial_commission_payables'::regclass,
|
|
32385
|
+
'financial.commission_payable', 'financial', false, 'enforce');
|
|
32386
|
+
SELECT app.scaffold_scope('public.plg_financial_commission_payables'::regclass, 'unit_optional', true);
|
|
32387
|
+
|
|
32388
|
+
SELECT app.scaffold_table('public.plg_financial_commission_payable_items'::regclass,
|
|
32389
|
+
'financial.commission_payable_item', 'financial', false, 'enforce');
|
|
32390
|
+
SELECT app.scaffold_scope('public.plg_financial_commission_payable_items'::regclass, 'tenant', true);
|
|
32391
|
+
|
|
32392
|
+
SELECT app.scaffold_table('public.plg_financial_commission_payee_prefs'::regclass,
|
|
32393
|
+
'financial.commission_payee_pref', 'financial', false, 'enforce');
|
|
32394
|
+
SELECT app.scaffold_scope('public.plg_financial_commission_payee_prefs'::regclass, 'tenant', true);
|
|
32395
|
+
`;
|
|
32396
|
+
var MIGRATION_081_A_REMESSA_E_A_COMISSAO_ATRAVESSAM = `-- A remessa e a comiss\xE3o atravessam.
|
|
32397
|
+
--
|
|
32398
|
+
-- A 080 fez as cinco tabelas. Esta escreve o de-para, com o writer gen\xE9rico
|
|
32399
|
+
-- \`by_shape\` (spine 123) \u2014 as colunas do destino t\xEAm o nome que o de-para
|
|
32400
|
+
-- produz, ent\xE3o o que falta \xE9 a tradu\xE7\xE3o, n\xE3o uma fun\xE7\xE3o por tabela.
|
|
32401
|
+
--
|
|
32402
|
+
-- \u2500\u2500 O arquivo de remessa n\xE3o vem por aqui \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
|
|
32403
|
+
--
|
|
32404
|
+
-- \`cnab_remessas.file_content\` guarda o CNAB inteiro em texto. Sai com \`-\`:
|
|
32405
|
+
-- arquivo atravessa pelo est\xE1gio de assets e vira \`public.documents\`. O que
|
|
32406
|
+
-- FICA na linha \xE9 \`sequence_number\`, porque ele \xE9 do banco \u2014 dois arquivos com
|
|
32407
|
+
-- o mesmo n\xFAmero s\xE3o uma remessa rejeitada, e isso \xE9 dado, n\xE3o anexo.
|
|
32408
|
+
--
|
|
32409
|
+
-- \u2500\u2500 As apura\xE7\xF5es agregadas ficam como refer\xEAncia \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
|
|
32410
|
+
--
|
|
32411
|
+
-- \`commission_payable_items.entry_ids\` \xE9 um array de ids de apura\xE7\xE3o agregados
|
|
32412
|
+
-- numa linha s\xF3. Vem como \`entry_refs\` jsonb com os ids do V1 dentro: eles s\xF3
|
|
32413
|
+
-- resolvem depois que \`commission_entries\` atravessar, e um array de uuid faria
|
|
32414
|
+
-- a linha mentir enquanto isso.
|
|
32415
|
+
--
|
|
32416
|
+
-- Idempotente.
|
|
32417
|
+
|
|
32418
|
+
-- \u2500\u2500 \`by_shape\` volta ao vocabul\xE1rio \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
|
|
32419
|
+
--
|
|
32420
|
+
-- O spine 123 acrescenta \`by_shape\` ao \`allowlist_writer_check\`. Ele N\xC3O
|
|
32421
|
+
-- sobrevive at\xE9 aqui: \`financial/061\` reescreve a restri\xE7\xE3o com uma lista
|
|
32422
|
+
-- LITERAL, e a lista dele foi escrita antes de o spine 123 existir. Toda
|
|
32423
|
+
-- palavra que o spine acrescenta morre na passagem por 061 \u2014 foi assim que a
|
|
32424
|
+
-- corrente do zero parou nesta linha com
|
|
32425
|
+
-- \`new row for relation "allowlist" violates check constraint\`.
|
|
32426
|
+
--
|
|
32427
|
+
-- A ironia est\xE1 no cabe\xE7alho do pr\xF3prio 061: "A LISTA \xC9 A UNI\xC3O, E N\xC3O UMA
|
|
32428
|
+
-- LISTA LITERAL". Ele descreve a regra e faz o contr\xE1rio.
|
|
32429
|
+
--
|
|
32430
|
+
-- O acr\xE9scimo \xE9 refeito aqui porque este \xE9 o PRIMEIRO consumidor de \`by_shape\`
|
|
32431
|
+
-- depois de 061 \u2014 fiscal_br/003, inventory/007 e tables/007 v\xEAm adiante e
|
|
32432
|
+
-- herdam o conserto. A corre\xE7\xE3o de verdade \xE9 converter 061 para o splice, e
|
|
32433
|
+
-- isso \xE9 do dono daquele arquivo.
|
|
32434
|
+
DO $$
|
|
32435
|
+
DECLARE v_def text;
|
|
32436
|
+
BEGIN
|
|
32437
|
+
SELECT pg_get_constraintdef(oid) INTO v_def
|
|
32438
|
+
FROM pg_constraint WHERE conname = 'allowlist_writer_check';
|
|
32439
|
+
IF v_def IS NULL OR strpos(v_def, 'by_shape') > 0 THEN RETURN; END IF;
|
|
32440
|
+
ALTER TABLE migration.allowlist DROP CONSTRAINT allowlist_writer_check;
|
|
32441
|
+
EXECUTE replace(
|
|
32442
|
+
'ALTER TABLE migration.allowlist ADD CONSTRAINT allowlist_writer_check ' || v_def,
|
|
32443
|
+
'''person''::text', '''person''::text, ''by_shape''::text');
|
|
32444
|
+
END $$;
|
|
32445
|
+
|
|
32446
|
+
SET LOCAL migration.allowlist_unlock = 'on';
|
|
32447
|
+
|
|
32448
|
+
SELECT migration._allow(
|
|
32449
|
+
'cnab_remessas', 'public.plg_financial_cnab_batches', 'financial', 'by_shape',
|
|
32450
|
+
jsonb_build_object('target', 'public.plg_financial_cnab_batches'),
|
|
32451
|
+
NULL::text[],
|
|
32452
|
+
jsonb_build_object(
|
|
32453
|
+
'uuid', 'id',
|
|
32454
|
+
'unit_id', 'unit_id@companies',
|
|
32455
|
+
'bank_account_id', 'bank_account_id@bank_accounts',
|
|
32456
|
+
'layout', 'layout',
|
|
32457
|
+
'sequence_number', 'sequence_number',
|
|
32458
|
+
'total_amount', 'total_amount',
|
|
32459
|
+
'items_count', 'items_count',
|
|
32460
|
+
'file_name', 'file_name',
|
|
32461
|
+
'created_at', 'generated_at',
|
|
32462
|
+
'updated_at', 'updated_at',
|
|
32463
|
+
'tenant_id', '-',
|
|
32464
|
+
'generated_by', '-',
|
|
32465
|
+
'file_content', '-',
|
|
32466
|
+
'id', 'jsonb:financial.v1_ref'),
|
|
32467
|
+
'O arquivo de remessa enviado ao banco. O CNAB em si vai pelo est\xE1gio de '
|
|
32468
|
+
'assets; o n\xFAmero de sequ\xEAncia fica na linha porque \xE9 do banco.'
|
|
32469
|
+
);
|
|
32470
|
+
|
|
32471
|
+
SELECT migration._allow(
|
|
32472
|
+
'cnab_remessa_items', 'public.plg_financial_cnab_items', 'financial', 'by_shape',
|
|
32473
|
+
jsonb_build_object('target', 'public.plg_financial_cnab_items'),
|
|
32474
|
+
NULL::text[],
|
|
32475
|
+
jsonb_build_object(
|
|
32476
|
+
'remessa_id', 'batch_id@cnab_remessas',
|
|
32477
|
+
'financial_movement_id', 'movement_id@financial_movements',
|
|
32478
|
+
'payment_form', 'payment_form',
|
|
32479
|
+
'payment_date', 'payment_date',
|
|
32480
|
+
'amount', 'amount',
|
|
32481
|
+
'created_at', 'created_at',
|
|
32482
|
+
'tenant_id', '-',
|
|
32483
|
+
'id', 'jsonb:financial.v1_ref'),
|
|
32484
|
+
'O pagamento dentro da remessa. Sem \`uuid\` na origem \u2014 o id do destino nasce '
|
|
32485
|
+
'determin\xEDstico da chave num\xE9rica.'
|
|
32486
|
+
);
|
|
32487
|
+
|
|
32488
|
+
SELECT migration._allow(
|
|
32489
|
+
'commission_payables', 'public.plg_financial_commission_payables', 'financial', 'by_shape',
|
|
32490
|
+
jsonb_build_object('target', 'public.plg_financial_commission_payables'),
|
|
32491
|
+
NULL::text[],
|
|
32492
|
+
jsonb_build_object(
|
|
32493
|
+
'uuid', 'id',
|
|
32494
|
+
'unit_id', 'unit_id@companies',
|
|
32495
|
+
'invoice_id', 'invoice_id@invoices',
|
|
32496
|
+
'beneficiary_account_central_id', 'beneficiary_party_id@account_central',
|
|
32497
|
+
'chart_account_id', 'chart_account_id@chart_of_accounts',
|
|
32498
|
+
'due_date', 'due_date',
|
|
32499
|
+
'total_amount', 'total_amount',
|
|
32500
|
+
'entries_count', 'entries_count',
|
|
32501
|
+
'status', 'status',
|
|
32502
|
+
'launched_at', 'launched_at',
|
|
32503
|
+
'canceled_at', 'canceled_at',
|
|
32504
|
+
'cancel_source', 'cancel_source',
|
|
32505
|
+
'created_at', 'created_at',
|
|
32506
|
+
'updated_at', 'updated_at',
|
|
32507
|
+
'tenant_id', '-',
|
|
32508
|
+
'invoice_uuid', '-',
|
|
32509
|
+
'launched_by', '-',
|
|
32510
|
+
'id', 'jsonb:financial.v1_ref'),
|
|
32511
|
+
'A comiss\xE3o apurada virando conta a pagar \u2014 o elo entre a apura\xE7\xE3o e o '
|
|
32512
|
+
'T\xEDtulo, que era o que faltava no V2. \`cancel_source\` vem junto porque \xE9 ele '
|
|
32513
|
+
'que explica por que a comiss\xE3o sumiu do relat\xF3rio.'
|
|
32514
|
+
);
|
|
32515
|
+
|
|
32516
|
+
SELECT migration._allow(
|
|
32517
|
+
'commission_payable_items', 'public.plg_financial_commission_payable_items', 'financial', 'by_shape',
|
|
32518
|
+
jsonb_build_object('target', 'public.plg_financial_commission_payable_items'),
|
|
32519
|
+
NULL::text[],
|
|
32520
|
+
jsonb_build_object(
|
|
32521
|
+
'uuid', 'id',
|
|
32522
|
+
'payable_id', 'payable_id@commission_payables',
|
|
32523
|
+
'source_invoice_item_id', 'source_item_id@invoice_items',
|
|
32524
|
+
'invoice_item_id', 'item_id@invoice_items',
|
|
32525
|
+
'description', 'description',
|
|
32526
|
+
'amount', 'amount',
|
|
32527
|
+
'entry_ids', 'entry_refs',
|
|
32528
|
+
'aggregate_entry_id', 'jsonb:financial.v1_aggregate_entry_ref',
|
|
32529
|
+
'created_at', 'created_at',
|
|
32530
|
+
'updated_at', 'updated_at',
|
|
32531
|
+
'tenant_id', '-',
|
|
32532
|
+
'id', 'jsonb:financial.v1_ref'),
|
|
32533
|
+
'A linha da apura\xE7\xE3o dentro do pag\xE1vel. \`entry_ids\` vira \`entry_refs\` jsonb '
|
|
32534
|
+
'com os ids do V1: eles s\xF3 resolvem quando commission_entries atravessar.'
|
|
32535
|
+
);
|
|
32536
|
+
|
|
32537
|
+
SELECT migration._allow(
|
|
32538
|
+
'commission_payee_prefs', 'public.plg_financial_commission_payee_prefs', 'financial', 'by_shape',
|
|
32539
|
+
jsonb_build_object('target', 'public.plg_financial_commission_payee_prefs'),
|
|
32540
|
+
NULL::text[],
|
|
32541
|
+
jsonb_build_object(
|
|
32542
|
+
'account_central_id', 'party_id@account_central',
|
|
32543
|
+
'chart_account_id', 'chart_account_id@chart_of_accounts',
|
|
32544
|
+
'updated_at', 'updated_at',
|
|
32545
|
+
'tenant_id', '-',
|
|
32546
|
+
'id', 'jsonb:financial.v1_ref'),
|
|
32547
|
+
'Para qual conta cont\xE1bil a comiss\xE3o daquela pessoa vai. Uma por pessoa \u2014 '
|
|
32548
|
+
'duas seriam duas respostas para "onde lan\xE7ar".'
|
|
32549
|
+
);
|
|
32550
|
+
|
|
32551
|
+
DELETE FROM migration.excluded_tables
|
|
32552
|
+
WHERE source_table IN ('cnab_remessas', 'cnab_remessa_items', 'commission_payables',
|
|
32553
|
+
'commission_payable_items', 'commission_payee_prefs');
|
|
32554
|
+
|
|
32555
|
+
RESET migration.allowlist_unlock;
|
|
32556
|
+
`;
|
|
32557
|
+
var MIGRATION_082_O_BENEFICIARIO_DA_COMISSAO_E_UMA_CONTA = `-- O benefici\xE1rio da comiss\xE3o \xE9 uma CONTA, n\xE3o uma pessoa.
|
|
32558
|
+
--
|
|
32559
|
+
-- A 080 declarou \`beneficiary_party_id uuid \u2192 public.people\` e a 081 mapeou
|
|
32560
|
+
-- \`commission_payables.beneficiary_account_central_id\` para ela. As duas
|
|
32561
|
+
-- estavam erradas, e a FK cobrou na primeira onda:
|
|
32562
|
+
--
|
|
32563
|
+
-- 23503: insert or update on table "plg_financial_commission_payables"
|
|
32564
|
+
-- violates foreign key constraint "plg_financial_commission_payables_party_fkey"
|
|
32565
|
+
--
|
|
32566
|
+
-- \`account_central\` do V1 n\xE3o \xE9 pessoa: \xE9 o raz\xE3o de QUEM RECEBE, e ele
|
|
32567
|
+
-- atravessa para \`public.plg_financial_accounts\` (writer \`fin_account\`). O id
|
|
32568
|
+
-- que o ledger devolveu \xE9 de uma conta, e a coluna exigia uma pessoa.
|
|
32569
|
+
--
|
|
32570
|
+
-- N\xE3o \xE9 um detalhe de nome. Uma comiss\xE3o pode ser paga a uma conta que n\xE3o \xE9 de
|
|
32571
|
+
-- ningu\xE9m \u2014 um centro, um fundo, a pr\xF3pria casa \u2014 e for\xE7ar uma pessoa ali faria
|
|
32572
|
+
-- essas linhas n\xE3o existirem. A mesma corre\xE7\xE3o vale para
|
|
32573
|
+
-- \`commission_payee_prefs\`, que responde "para qual conta cont\xE1bil a comiss\xE3o
|
|
32574
|
+
-- daquele benefici\xE1rio vai".
|
|
32575
|
+
--
|
|
32576
|
+
-- S\xE3o 5 linhas no Espa\xE7o Facial. O volume \xE9 irrelevante; o modelo, n\xE3o.
|
|
32577
|
+
--
|
|
32578
|
+
-- Idempotente.
|
|
32579
|
+
|
|
32580
|
+
DO $$ BEGIN
|
|
32581
|
+
IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payables_party_fkey') THEN
|
|
32582
|
+
ALTER TABLE public.plg_financial_commission_payables
|
|
32583
|
+
DROP CONSTRAINT plg_financial_commission_payables_party_fkey;
|
|
32584
|
+
END IF;
|
|
32585
|
+
IF EXISTS (SELECT 1 FROM pg_attribute a
|
|
32586
|
+
WHERE a.attrelid = 'public.plg_financial_commission_payables'::regclass
|
|
32587
|
+
AND a.attname = 'beneficiary_party_id' AND NOT a.attisdropped) THEN
|
|
32588
|
+
ALTER TABLE public.plg_financial_commission_payables
|
|
32589
|
+
RENAME COLUMN beneficiary_party_id TO beneficiary_account_id;
|
|
32590
|
+
END IF;
|
|
32591
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payables_account_fkey') THEN
|
|
32592
|
+
ALTER TABLE public.plg_financial_commission_payables
|
|
32593
|
+
ADD CONSTRAINT plg_financial_commission_payables_account_fkey
|
|
32594
|
+
FOREIGN KEY (beneficiary_account_id) REFERENCES public.plg_financial_accounts(id) ON DELETE SET NULL;
|
|
32595
|
+
END IF;
|
|
32596
|
+
|
|
32597
|
+
IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payee_prefs_party_fkey') THEN
|
|
32598
|
+
ALTER TABLE public.plg_financial_commission_payee_prefs
|
|
32599
|
+
DROP CONSTRAINT plg_financial_commission_payee_prefs_party_fkey;
|
|
32600
|
+
END IF;
|
|
32601
|
+
IF EXISTS (SELECT 1 FROM pg_attribute a
|
|
32602
|
+
WHERE a.attrelid = 'public.plg_financial_commission_payee_prefs'::regclass
|
|
32603
|
+
AND a.attname = 'party_id' AND NOT a.attisdropped) THEN
|
|
32604
|
+
ALTER TABLE public.plg_financial_commission_payee_prefs
|
|
32605
|
+
RENAME COLUMN party_id TO account_id;
|
|
32606
|
+
END IF;
|
|
32607
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_commission_payee_prefs_account_fkey') THEN
|
|
32608
|
+
ALTER TABLE public.plg_financial_commission_payee_prefs
|
|
32609
|
+
ADD CONSTRAINT plg_financial_commission_payee_prefs_account_fkey
|
|
32610
|
+
FOREIGN KEY (account_id) REFERENCES public.plg_financial_accounts(id) ON DELETE CASCADE;
|
|
32611
|
+
END IF;
|
|
32612
|
+
END $$;
|
|
32613
|
+
|
|
32614
|
+
DROP INDEX IF EXISTS public.plg_financial_commission_payables_party_idx;
|
|
32615
|
+
DROP INDEX IF EXISTS public.plg_financial_commission_payee_prefs_party_uidx;
|
|
32616
|
+
CREATE INDEX IF NOT EXISTS plg_financial_commission_payables_account_idx
|
|
32617
|
+
ON public.plg_financial_commission_payables USING btree (tenant_id, beneficiary_account_id, due_date DESC);
|
|
32618
|
+
CREATE UNIQUE INDEX IF NOT EXISTS plg_financial_commission_payee_prefs_account_uidx
|
|
32619
|
+
ON public.plg_financial_commission_payee_prefs USING btree (tenant_id, account_id);
|
|
32620
|
+
|
|
32621
|
+
COMMENT ON COLUMN public.plg_financial_commission_payables.beneficiary_account_id IS
|
|
32622
|
+
'V1: commission_payables.beneficiary_account_central_id \u2014 o raz\xE3o de quem recebe, que pode n\xE3o ser uma pessoa.';
|
|
32623
|
+
|
|
32624
|
+
SET LOCAL migration.allowlist_unlock = 'on';
|
|
32625
|
+
|
|
32626
|
+
UPDATE migration.allowlist
|
|
32627
|
+
SET columns = columns
|
|
32628
|
+
- 'beneficiary_account_central_id'
|
|
32629
|
+
|| jsonb_build_object('beneficiary_account_central_id', 'beneficiary_account_id@account_central')
|
|
32630
|
+
WHERE source_table = 'commission_payables';
|
|
32631
|
+
|
|
32632
|
+
UPDATE migration.allowlist
|
|
32633
|
+
SET columns = columns
|
|
32634
|
+
- 'account_central_id'
|
|
32635
|
+
|| jsonb_build_object('account_central_id', 'account_id@account_central')
|
|
32636
|
+
WHERE source_table = 'commission_payee_prefs';
|
|
32637
|
+
|
|
32638
|
+
RESET migration.allowlist_unlock;
|
|
32639
|
+
`;
|
|
32640
|
+
var MIGRATION_083_A_CONTA_DO_BANCO_E_UMA_CONTA_E_A_FATURA_TEM_DOIS_DESTINOS = `-- A conta do banco \xE9 uma conta, e a fatura tem dois destinos.
|
|
32641
|
+
--
|
|
32642
|
+
-- Duas corre\xE7\xF5es que a primeira onda cobrou, das mesmas 59 + 1 linhas.
|
|
32643
|
+
-- (A terceira, do \xEDndice de grupo fiscal, mudou de arquivo: ela toca uma
|
|
32644
|
+
-- tabela do fiscal_br, e o fiscal_br roda DEPOIS do financeiro na corrente \u2014
|
|
32645
|
+
-- um arquivo do financeiro que mexe na tabela de outro plugin quebra do zero.
|
|
32646
|
+
-- Ver fiscal_br/004.)
|
|
32647
|
+
--
|
|
32648
|
+
-- \u2500\u2500 1. \`bank_accounts\` do V1 n\xE3o vira \`plg_financial_bank_accounts\` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
32649
|
+
--
|
|
32650
|
+
-- A 080 declarou \`plg_financial_cnab_batches.bank_account_id\` apontando para
|
|
32651
|
+
-- \`public.plg_financial_bank_accounts\`. Mas o de-para manda \`bank_accounts\` do
|
|
32652
|
+
-- V1 para \`public.plg_financial_accounts\` \u2014 o mesmo destino de
|
|
32653
|
+
-- \`account_central\`, porque no V2 a conta banc\xE1ria \xE9 uma CONTA do raz\xE3o, n\xE3o
|
|
32654
|
+
-- uma tabela paralela. As 59 remessas do Pertinho do C\xE9u bateram na FK.
|
|
32655
|
+
--
|
|
32656
|
+
-- \xC9 o mesmo erro da 082, no arquivo ao lado: supor o destino pelo nome da
|
|
32657
|
+
-- tabela de origem em vez de ler a allowlist.
|
|
32658
|
+
--
|
|
32659
|
+
-- \u2500\u2500 2. \`invoices\` resolve para DOIS destinos \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
|
|
32660
|
+
--
|
|
32661
|
+
-- \`invoices\` do V1 tem duas linhas de de-para: \`public.orders\` (a Venda) e
|
|
32662
|
+
-- \`public.plg_financial_invoices\` (o T\xEDtulo). \`migration.resolve_ref\` sem alvo
|
|
32663
|
+
-- declarado l\xEA \`SELECT target_table ... WHERE source_table = 'invoices'\` e pega
|
|
32664
|
+
-- UMA das duas, sem ordem \u2014 ent\xE3o \`invoice_id@invoices\` devolvia \xE0s vezes o id
|
|
32665
|
+
-- de uma Venda para uma coluna que exige um T\xEDtulo.
|
|
32666
|
+
--
|
|
32667
|
+
-- O de-para j\xE1 tem a sintaxe para isso e a 081 n\xE3o a usou:
|
|
32668
|
+
-- \`coluna@tabela_de_origem:tabela_de_destino\`. Uma refer\xEAncia a uma origem de
|
|
32669
|
+
-- dois destinos SEM o alvo \xE9 amb\xEDgua por constru\xE7\xE3o, e acerta por acidente
|
|
32670
|
+
-- enquanto houver sorte.
|
|
32671
|
+
--
|
|
32672
|
+
-- Idempotente.
|
|
32673
|
+
|
|
32674
|
+
DO $$ BEGIN
|
|
32675
|
+
IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_cnab_batches_account_fkey') THEN
|
|
32676
|
+
ALTER TABLE public.plg_financial_cnab_batches DROP CONSTRAINT plg_financial_cnab_batches_account_fkey;
|
|
32677
|
+
END IF;
|
|
32678
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plg_financial_cnab_batches_ledger_account_fkey') THEN
|
|
32679
|
+
ALTER TABLE public.plg_financial_cnab_batches
|
|
32680
|
+
ADD CONSTRAINT plg_financial_cnab_batches_ledger_account_fkey
|
|
32681
|
+
FOREIGN KEY (bank_account_id) REFERENCES public.plg_financial_accounts(id) ON DELETE SET NULL;
|
|
32682
|
+
END IF;
|
|
32683
|
+
END $$;
|
|
32684
|
+
|
|
32685
|
+
COMMENT ON COLUMN public.plg_financial_cnab_batches.bank_account_id IS
|
|
32686
|
+
'A conta do raz\xE3o de onde a remessa sai. V1: cnab_remessas.bank_account_id \u2192 bank_accounts, que atravessa para plg_financial_accounts.';
|
|
32687
|
+
|
|
32688
|
+
SET LOCAL migration.allowlist_unlock = 'on';
|
|
32689
|
+
|
|
32690
|
+
-- \`:public.plg_financial_invoices\` \xE9 o alvo declarado. Sem ele a resolu\xE7\xE3o
|
|
32691
|
+
-- escolhe entre a Venda e o T\xEDtulo, e escolher n\xE3o \xE9 resolver.
|
|
32692
|
+
UPDATE migration.allowlist
|
|
32693
|
+
SET columns = columns - 'invoice_id'
|
|
32694
|
+
|| jsonb_build_object('invoice_id', 'invoice_id@invoices:public.plg_financial_invoices')
|
|
32695
|
+
WHERE source_table = 'commission_payables';
|
|
32696
|
+
|
|
32697
|
+
RESET migration.allowlist_unlock;
|
|
32698
|
+
`;
|
|
32699
|
+
var MIGRATION_084_A_NOTA_DE_ENTRADA_TEM_UM_TITULO_A_PAGAR = `-- A nota de entrada tem um t\xEDtulo a pagar.
|
|
32700
|
+
--
|
|
32701
|
+
-- \`danfe_payments\` \xE9 o plano de pagamento que o operador montou ao importar a
|
|
32702
|
+
-- DANFE: 895 linhas (657 no Pertinho do C\xE9u, 238 no Espa\xE7o Facial),
|
|
32703
|
+
-- R$ 1.293.490,86, e nenhuma delas atravessava. \xC9 a \xFAltima das quatro tabelas
|
|
32704
|
+
-- da entrada de notas; as outras tr\xEAs v\xE3o para o invent\xE1rio
|
|
32705
|
+
-- (plugin-inventory/010). Esta mora aqui porque o destino dela \xE9 uma tabela do
|
|
32706
|
+
-- FINANCEIRO, e o financeiro roda ANTES do invent\xE1rio na corrente
|
|
32707
|
+
-- (packages/db/chain.json) \u2014 um arquivo do invent\xE1rio que criasse a parcela
|
|
32708
|
+
-- quebraria a aplica\xE7\xE3o do zero.
|
|
32709
|
+
--
|
|
32710
|
+
-- \u2500\u2500 O t\xEDtulo n\xE3o existe no V1, e a parcela n\xE3o vive sem ele \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
32711
|
+
--
|
|
32712
|
+
-- \`plg_financial_invoice_installments.invoice_id\` \xE9 NOT NULL, e no V1 n\xE3o h\xE1
|
|
32713
|
+
-- nada para resolver: \`danfe_payments\` aponta para \`danfe_imports\`, e nenhuma
|
|
32714
|
+
-- linha de \`invoices\` referencia uma nota de entrada \u2014 as tr\xEAs FKs que chegam
|
|
32715
|
+
-- em \`danfe_imports\` s\xE3o \`danfe_import_items\`, \`danfe_payments\` e \`dfe_inbox\`.
|
|
32716
|
+
-- No V1 a conta a pagar do fornecedor simplesmente n\xE3o \xE9 um t\xEDtulo: \xE9 uma lista
|
|
32717
|
+
-- de vencimentos pendurada na tela de importa\xE7\xE3o.
|
|
32718
|
+
--
|
|
32719
|
+
-- Ent\xE3o o t\xEDtulo nasce aqui, um por nota, determin\xEDstico:
|
|
32720
|
+
--
|
|
32721
|
+
-- id md5('danfe-invoice:' || uuid da nota)::uuid
|
|
32722
|
+
-- direction payable
|
|
32723
|
+
-- idempotency_key danfe:<chave de acesso>
|
|
32724
|
+
--
|
|
32725
|
+
-- \xC9 o mesmo padr\xE3o de id derivado que o porte j\xE1 usa para o anexo (spine 054).
|
|
32726
|
+
-- Determin\xEDstico importa duas vezes: a segunda parcela da mesma nota encontra o
|
|
32727
|
+
-- t\xEDtulo que a primeira criou, e a segunda ONDA encontra o mesmo id.
|
|
32728
|
+
--
|
|
32729
|
+
-- \u2500\u2500 O total do t\xEDtulo \xE9 a soma das PARCELAS, n\xE3o o total da nota \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
32730
|
+
--
|
|
32731
|
+
-- Medido em 21/09/2026: em 38 das 607 notas (6,3%) a soma das parcelas n\xE3o bate
|
|
32732
|
+
-- com \`danfe_imports.total_value\` \u2014 R$ 926.668,06 de notas contra R$ 850.454,65
|
|
32733
|
+
-- de parcelas no tenant 8. Um t\xEDtulo cujo total n\xE3o \xE9 a soma das parcelas nasce
|
|
32734
|
+
-- com saldo em aberto errado em toda tela que faz "total \u2212 pago", e o total da
|
|
32735
|
+
-- nota continua leg\xEDvel em \`metadata.danfe.total_value\` e na caixa de entrada
|
|
32736
|
+
-- fiscal. A soma vem do extrator (\`note_payments_total\`), j\xE1 pronta na primeira
|
|
32737
|
+
-- parcela, porque \`plg_financial_invoices\` \xE9 IMUT\xC1VEL depois de emitido: o
|
|
32738
|
+
-- gatilho recusa alterar \`total\`, e acumular parcela a parcela n\xE3o \xE9 op\xE7\xE3o.
|
|
32739
|
+
--
|
|
32740
|
+
-- \u2500\u2500 \`number\` do t\xEDtulo fica nulo de prop\xF3sito \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
|
|
32741
|
+
--
|
|
32742
|
+
-- \`plg_financial_invoices_number_uq\` \xE9 UNIQUE (tenant_id, number). O n\xFAmero da
|
|
32743
|
+
-- NF-e do fornecedor n\xE3o \xE9 \xFAnico dentro do tenant \u2014 dois fornecedores emitem a
|
|
32744
|
+
-- nota 1234 no mesmo ano, e ele ainda colidiria com o n\xFAmero das vendas. O
|
|
32745
|
+
-- n\xFAmero vai para \`description\` e para \`metadata\`, e a identidade \xFAnica \xE9 a
|
|
32746
|
+
-- chave de acesso, em \`idempotency_key\`.
|
|
32747
|
+
--
|
|
32748
|
+
-- \u2500\u2500 \`number\` da PARCELA \xE9 a ordem dentro da nota \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
|
|
32749
|
+
--
|
|
32750
|
+
-- \`(invoice_id, number)\` \xE9 UNIQUE, e 251 das 895 parcelas n\xE3o t\xEAm
|
|
32751
|
+
-- \`installment_number\` no V1 (o pagamento \xE0 vista \u2014 pix, dinheiro \u2014 n\xE3o numera).
|
|
32752
|
+
-- Usar \`coalesce(installment_number, 1)\` colidiria na nota que tem pix E
|
|
32753
|
+
-- boleto 1/2. O extrator traz a ordem pelo id da pr\xF3pria origem
|
|
32754
|
+
-- (\`payment_rank\`), que \xE9 \xFAnica por constru\xE7\xE3o, e o \`installment_number\` do V1
|
|
32755
|
+
-- fica em metadata.
|
|
32756
|
+
--
|
|
32757
|
+
-- E 58 das 895 n\xE3o t\xEAm vencimento: o pagamento \xE0 vista vence no dia da nota, e
|
|
32758
|
+
-- \xE9 a data de emiss\xE3o que entra \u2014 \`due_date\` \xE9 NOT NULL no destino.
|
|
32759
|
+
--
|
|
32760
|
+
-- Idempotente.
|
|
32761
|
+
|
|
32762
|
+
-- \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\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
|
|
32763
|
+
-- 1. O writer
|
|
32764
|
+
-- \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\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
|
|
32765
|
+
|
|
32766
|
+
CREATE OR REPLACE FUNCTION migration.upsert_fin_danfe_installment(
|
|
32767
|
+
p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb
|
|
32768
|
+
) RETURNS jsonb
|
|
32769
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''
|
|
32770
|
+
AS $function$
|
|
32771
|
+
DECLARE
|
|
32772
|
+
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
32773
|
+
v_danfe jsonb := coalesce(v_meta -> 'danfe', '{}'::jsonb);
|
|
32774
|
+
v_note uuid;
|
|
32775
|
+
v_unit uuid := (p_row ->> 'unit_id')::uuid;
|
|
32776
|
+
v_invoice uuid;
|
|
32777
|
+
v_issue date;
|
|
32778
|
+
v_due date;
|
|
32779
|
+
v_amount numeric := round(coalesce((p_row ->> 'amount')::numeric, 0), 2);
|
|
32780
|
+
v_number integer := coalesce((p_row ->> 'number')::integer, 1);
|
|
32781
|
+
v_total numeric;
|
|
32782
|
+
v_desc text;
|
|
32783
|
+
v_existing uuid;
|
|
32784
|
+
v_created boolean := false;
|
|
32785
|
+
v_standalone boolean := migration._standalone();
|
|
32786
|
+
BEGIN
|
|
32787
|
+
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
32788
|
+
|
|
32789
|
+
IF p_id IS NULL THEN
|
|
32790
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state',
|
|
32791
|
+
'reason', 'uma parcela precisa de id');
|
|
32792
|
+
END IF;
|
|
32793
|
+
|
|
32794
|
+
BEGIN v_note := (v_danfe ->> 'import_uuid')::uuid; EXCEPTION WHEN OTHERS THEN v_note := NULL; END;
|
|
32795
|
+
IF v_note IS NULL THEN
|
|
32796
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state',
|
|
32797
|
+
'reason', 'parcela de nota de entrada sem a nota \u2014 o t\xEDtulo dela n\xE3o tem de que nascer');
|
|
32798
|
+
END IF;
|
|
32799
|
+
|
|
32800
|
+
-- A emiss\xE3o da nota \xE9 timestamptz na origem e date no destino. Data que n\xE3o
|
|
32801
|
+
-- converte vira nulo aqui e o vencimento cai para hoje, em vez de derrubar a
|
|
32802
|
+
-- parcela inteira: o valor e a nota s\xE3o o dado, a data \xE9 o enfeite.
|
|
32803
|
+
BEGIN v_issue := (v_danfe ->> 'issue_date')::timestamptz::date; EXCEPTION WHEN OTHERS THEN v_issue := NULL; END;
|
|
32804
|
+
BEGIN v_due := (p_row ->> 'due_date')::date; EXCEPTION WHEN OTHERS THEN v_due := NULL; END;
|
|
32805
|
+
v_due := coalesce(v_due, v_issue, current_date);
|
|
32806
|
+
|
|
32807
|
+
IF v_amount < 0 THEN
|
|
32808
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value',
|
|
32809
|
+
'reason', format('parcela de valor negativo (%s) \u2014 o destino exige amount >= 0', v_amount));
|
|
32810
|
+
END IF;
|
|
32811
|
+
|
|
32812
|
+
-- O T\xCDTULO. \`total\` \xE9 a soma das parcelas da nota (vem do extrator); sem ela,
|
|
32813
|
+
-- o valor desta parcela, que \xE9 o m\xEDnimo verdadeiro.
|
|
32814
|
+
v_total := round(coalesce((v_danfe ->> 'payments_total')::numeric, v_amount), 2);
|
|
32815
|
+
v_desc := btrim('NF-e ' || coalesce(v_danfe ->> 'nfe_number', 's/n')
|
|
32816
|
+
|| coalesce('/' || (v_danfe ->> 'nfe_series'), '')
|
|
32817
|
+
|| coalesce(' \u2014 ' || (v_danfe ->> 'supplier_name'), ''));
|
|
32818
|
+
v_invoice := md5('danfe-invoice:' || v_note::text)::uuid;
|
|
32819
|
+
|
|
32820
|
+
INSERT INTO public.plg_financial_invoices
|
|
32821
|
+
(id, tenant_id, unit_id, direction, number, competence_date, issue_date, total,
|
|
32822
|
+
description, idempotency_key, metadata)
|
|
32823
|
+
VALUES (v_invoice, p_tenant, v_unit, 'payable', NULL,
|
|
32824
|
+
coalesce(v_issue, v_due), coalesce(v_issue, v_due), v_total,
|
|
32825
|
+
nullif(v_desc, ''),
|
|
32826
|
+
'danfe:' || coalesce(v_danfe ->> 'access_key', v_note::text),
|
|
32827
|
+
jsonb_build_object('danfe', v_danfe - 'payment'))
|
|
32828
|
+
ON CONFLICT (id) DO NOTHING;
|
|
32829
|
+
|
|
32830
|
+
SELECT tenant_id INTO v_existing FROM public.plg_financial_invoice_installments WHERE id = p_id;
|
|
32831
|
+
IF v_existing IS NOT NULL AND v_existing <> p_tenant THEN
|
|
32832
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant',
|
|
32833
|
+
'reason', format('a parcela %s j\xE1 existe em outro tenant', p_id));
|
|
32834
|
+
END IF;
|
|
32835
|
+
v_created := v_existing IS NULL;
|
|
32836
|
+
|
|
32837
|
+
INSERT INTO public.plg_financial_invoice_installments
|
|
32838
|
+
(id, tenant_id, unit_id, invoice_id, number, due_date, amount, metadata, created_at)
|
|
32839
|
+
VALUES (p_id, p_tenant, v_unit, v_invoice, v_number, v_due, v_amount, v_meta,
|
|
32840
|
+
coalesce((p_row ->> 'created_at')::timestamptz, now()))
|
|
32841
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
32842
|
+
unit_id = coalesce(EXCLUDED.unit_id, public.plg_financial_invoice_installments.unit_id),
|
|
32843
|
+
invoice_id = EXCLUDED.invoice_id,
|
|
32844
|
+
number = EXCLUDED.number,
|
|
32845
|
+
due_date = EXCLUDED.due_date,
|
|
32846
|
+
amount = EXCLUDED.amount,
|
|
32847
|
+
metadata = migration._merge_meta(public.plg_financial_invoice_installments.metadata, EXCLUDED.metadata),
|
|
32848
|
+
updated_at = now();
|
|
32849
|
+
|
|
32850
|
+
IF v_standalone THEN
|
|
32851
|
+
PERFORM migration._audit(p_tenant, 'public.plg_financial_invoice_installments', p_id,
|
|
32852
|
+
jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END,
|
|
32853
|
+
'invoice_id', v_invoice));
|
|
32854
|
+
END IF;
|
|
32855
|
+
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id,
|
|
32856
|
+
'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
32857
|
+
END $function$;
|
|
32858
|
+
|
|
32859
|
+
COMMENT ON FUNCTION migration.upsert_fin_danfe_installment(uuid, uuid, jsonb, jsonb) IS
|
|
32860
|
+
'A parcela do plano de pagamento de uma DANFE importada. Cria, uma vez por nota '
|
|
32861
|
+
'e com id derivado da nota, o t\xEDtulo a pagar que o V1 n\xE3o tem \u2014 sem ele a '
|
|
32862
|
+
'parcela n\xE3o existe, porque invoice_id \xE9 NOT NULL.';
|
|
32863
|
+
|
|
32864
|
+
-- \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\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
|
|
32865
|
+
-- 2. O vocabul\xE1rio de writers aceita o nome novo
|
|
32866
|
+
-- \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\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
|
|
32867
|
+
--
|
|
32868
|
+
-- Lendo e costurando o CHECK que est\xE1 l\xE1: reescrev\xEA-lo com lista literal
|
|
32869
|
+
-- apagaria as palavras que os outros passos da corrente acrescentaram.
|
|
32870
|
+
|
|
32871
|
+
DO $$
|
|
32872
|
+
DECLARE v_def text;
|
|
32873
|
+
BEGIN
|
|
32874
|
+
SELECT pg_get_constraintdef(oid) INTO v_def
|
|
32875
|
+
FROM pg_constraint WHERE conname = 'allowlist_writer_check';
|
|
32876
|
+
IF v_def IS NULL OR strpos(v_def, 'fin_danfe_installment') > 0 THEN RETURN; END IF;
|
|
32877
|
+
ALTER TABLE migration.allowlist DROP CONSTRAINT allowlist_writer_check;
|
|
32878
|
+
EXECUTE replace(
|
|
32879
|
+
'ALTER TABLE migration.allowlist ADD CONSTRAINT allowlist_writer_check ' || v_def,
|
|
32880
|
+
'''person''::text', '''person''::text, ''fin_danfe_installment''::text');
|
|
32881
|
+
END $$;
|
|
32882
|
+
|
|
32883
|
+
-- \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\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
|
|
32884
|
+
-- 3. O de-para
|
|
32885
|
+
-- \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\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
|
|
32886
|
+
--
|
|
32887
|
+
-- Tudo que o t\xEDtulo precisa chega pelo augment do extrator, a partir da pr\xF3pria
|
|
32888
|
+
-- nota. \xC9 de prop\xF3sito: este writer \xE9 do financeiro, e o financeiro roda antes
|
|
32889
|
+
-- do invent\xE1rio \u2014 ler \`plg_inventory_operations\` daqui seria depender de uma
|
|
32890
|
+
-- tabela que ainda n\xE3o existe quando este arquivo \xE9 aplicado.
|
|
32891
|
+
|
|
32892
|
+
SET LOCAL migration.allowlist_unlock = 'on';
|
|
32893
|
+
|
|
32894
|
+
SELECT migration._allow(
|
|
32895
|
+
'danfe_payments', 'public.plg_financial_invoice_installments', 'financial',
|
|
32896
|
+
'fin_danfe_installment', '{}'::jsonb, ARRAY['amount'],
|
|
32897
|
+
jsonb_build_object(
|
|
32898
|
+
'uuid', 'id',
|
|
32899
|
+
'note_unit_id', 'unit_id@companies',
|
|
32900
|
+
'payment_rank', 'number',
|
|
32901
|
+
'due_date', 'due_date',
|
|
32902
|
+
'value', 'amount',
|
|
32903
|
+
'created_at', 'created_at',
|
|
32904
|
+
'tenant_id', '-',
|
|
32905
|
+
-- O que o writer l\xEA para montar o t\xEDtulo. Vem em metadata e n\xE3o em coluna:
|
|
32906
|
+
-- \xE9 a nota, n\xE3o a parcela, e duplic\xE1-la em coluna daria duas respostas para
|
|
32907
|
+
-- "de que nota \xE9 este vencimento".
|
|
32908
|
+
'note_uuid', 'jsonb:danfe.import_uuid',
|
|
32909
|
+
'note_access_key', 'jsonb:danfe.access_key',
|
|
32910
|
+
'note_number', 'jsonb:danfe.nfe_number',
|
|
32911
|
+
'note_series', 'jsonb:danfe.nfe_series',
|
|
32912
|
+
'note_issue_date', 'jsonb:danfe.issue_date',
|
|
32913
|
+
'note_supplier', 'jsonb:danfe.supplier_name',
|
|
32914
|
+
'note_total', 'jsonb:danfe.total_value',
|
|
32915
|
+
'note_payments_total', 'jsonb:danfe.payments_total',
|
|
32916
|
+
'import_id', 'jsonb:danfe.v1_import_ref',
|
|
32917
|
+
'payment_type', 'jsonb:danfe.payment.type',
|
|
32918
|
+
'payment_type_code', 'jsonb:danfe.payment.type_code',
|
|
32919
|
+
'installment_number', 'jsonb:danfe.payment.v1_installment_number',
|
|
32920
|
+
'installment_ref', 'jsonb:danfe.payment.v1_installment_ref',
|
|
32921
|
+
'barcode', 'jsonb:danfe.payment.barcode',
|
|
32922
|
+
'pix_key', 'jsonb:danfe.payment.pix_key',
|
|
32923
|
+
'raw_json', 'jsonb:danfe.payment.raw',
|
|
32924
|
+
'id', 'jsonb:danfe.payment.v1_ref'),
|
|
32925
|
+
'O plano de pagamento da nota de entrada: 895 parcelas, R$ 1.293.490,86, que '
|
|
32926
|
+
'hoje n\xE3o chegam a lugar nenhum. O t\xEDtulo a pagar nasce com a primeira '
|
|
32927
|
+
'parcela, um por nota, com id derivado do uuid dela \u2014 no V1 ele n\xE3o existe, e '
|
|
32928
|
+
'invoice_id \xE9 NOT NULL. O boleto e a chave pix ficam em metadata: cobran\xE7a de '
|
|
32929
|
+
'fornecedor \xE9 evid\xEAncia, n\xE3o mais uma coluna do t\xEDtulo.'
|
|
32930
|
+
);
|
|
32931
|
+
|
|
32932
|
+
DELETE FROM migration.excluded_tables WHERE source_table = 'danfe_payments';
|
|
32933
|
+
|
|
32934
|
+
RESET migration.allowlist_unlock;
|
|
32935
|
+
|
|
32936
|
+
|
|
32937
|
+
-- \u2500\u2500 quem pode executar \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\u2500
|
|
32938
|
+
--
|
|
32939
|
+
-- Fun\xE7\xE3o nova nasce execut\xE1vel por PUBLIC, e \`anon\`/\`authenticated\` herdam
|
|
32940
|
+
-- dali. Esta \xE9 de PORTE: SECURITY DEFINER, escreve dados de qualquer inquilino,
|
|
32941
|
+
-- e alcan\xE7\xE1vel pela chave an\xF4nima seria a base inteira alcan\xE7\xE1vel pela chave
|
|
32942
|
+
-- an\xF4nima. S\xF3 o service_role, que \xE9 quem roda a onda.
|
|
32943
|
+
REVOKE ALL ON FUNCTION migration.upsert_fin_danfe_installment(uuid, uuid, jsonb, jsonb) FROM PUBLIC, anon, authenticated;
|
|
32944
|
+
GRANT EXECUTE ON FUNCTION migration.upsert_fin_danfe_installment(uuid, uuid, jsonb, jsonb) TO service_role;
|
|
32945
|
+
`;
|
|
32946
|
+
var MIGRATION_085_O_RESUMO_LE_CINCO_MIL_LINHAS_E_NAO_DUZENTAS_E_OITENTA_MIL = `-- O resumo l\xEA cinco mil linhas, n\xE3o duzentas e oitenta mil.
|
|
32947
|
+
--
|
|
32948
|
+
-- \`financial_summary\` (migra\xE7\xE3o 077) j\xE1 soma no banco em vez de baixar a tabela
|
|
32949
|
+
-- para o navegador, e ainda assim o painel devolvia 500: "canceling statement
|
|
32950
|
+
-- due to statement timeout". Somar no lugar certo n\xE3o basta se para somar for
|
|
32951
|
+
-- preciso LER tudo.
|
|
32952
|
+
--
|
|
32953
|
+
-- O que a fun\xE7\xE3o pede, e o que a tabela obrigava:
|
|
32954
|
+
--
|
|
32955
|
+
-- \xB7 aberto \u2192 \`status IN ('pending','partial','overdue')\`: 5.100 linhas de
|
|
32956
|
+
-- 281.174. Nenhum \xEDndice come\xE7a por \`status\`, ent\xE3o era varredura
|
|
32957
|
+
-- sequencial em 287 MB.
|
|
32958
|
+
-- \xB7 caixa \u2192 \`movement_kind = 'payment'\` numa janela de 30 dias: 870 linhas.
|
|
32959
|
+
-- O \xEDndice \`(tenant_id, payment_date)\` existente \xE9 parcial e vale
|
|
32960
|
+
-- s\xF3 para concilia\xE7\xE3o banc\xE1ria (\`external_source IS NOT NULL\`),
|
|
32961
|
+
-- ent\xE3o este caminho tamb\xE9m varria tudo.
|
|
32962
|
+
-- \xB7 a view \`v_financial_cash_movements\` faz LEFT JOIN da tabela COM ELA MESMA
|
|
32963
|
+
-- em \`reverses_id\`, e n\xE3o havia \xEDndice nessa coluna \u2014 a segunda
|
|
32964
|
+
-- varredura dos mesmos 287 MB, por chamada.
|
|
32965
|
+
--
|
|
32966
|
+
-- E a cerca de RLS cobra por linha: \`app.has_unit(unit_id)\` e
|
|
32967
|
+
-- \`app.has_permission('financial.read', unit_id)\` dependem da LINHA, ent\xE3o n\xE3o
|
|
32968
|
+
-- saem do la\xE7o. Com 281 mil linhas a pergunta \xE9 feita 281 mil vezes; com 5 mil,
|
|
32969
|
+
-- cinco mil. O \xEDndice n\xE3o muda a pol\xEDtica, muda quantas vezes ela \xE9 consultada
|
|
32970
|
+
-- \u2014 \xE9 a mesma corre\xE7\xE3o que levou /clients de 10s para 15ms.
|
|
32971
|
+
--
|
|
32972
|
+
-- \`INCLUDE\` carrega as colunas que a soma usa (valor, pago, e as duas que a RLS
|
|
32973
|
+
-- precisa ler) para a varredura terminar no \xEDndice e n\xE3o voltar \xE0 heap 5.100
|
|
32974
|
+
-- vezes.
|
|
32975
|
+
--
|
|
32976
|
+
-- S\xF3 \xEDndices: nenhuma pol\xEDtica, coluna ou dado muda. Idempotente.
|
|
32977
|
+
|
|
32978
|
+
-- T\xEDtulos em aberto \u2014 o "a receber", o "a pagar" e os dois vencidos.
|
|
32979
|
+
CREATE INDEX IF NOT EXISTS plg_financial_movements_open_idx
|
|
32980
|
+
ON public.plg_financial_movements (tenant_id, direction, due_date)
|
|
32981
|
+
INCLUDE (amount, paid_amount, status, unit_id, owner_id)
|
|
32982
|
+
WHERE status IN ('pending', 'partial', 'overdue');
|
|
32983
|
+
|
|
32984
|
+
-- Eventos de dinheiro na janela \u2014 a entrada e a sa\xEDda do per\xEDodo.
|
|
32985
|
+
CREATE INDEX IF NOT EXISTS plg_financial_movements_payment_idx
|
|
32986
|
+
ON public.plg_financial_movements (tenant_id, payment_date, direction)
|
|
32987
|
+
INCLUDE (paid_amount, unit_id, owner_id)
|
|
32988
|
+
WHERE movement_kind = 'payment';
|
|
32989
|
+
|
|
32990
|
+
-- A auto-jun\xE7\xE3o da view de caixa. Parcial porque estorno \xE9 exce\xE7\xE3o: o \xEDndice
|
|
32991
|
+
-- fica pequeno e o planejador o usa para o lado que de fato tem valor.
|
|
32992
|
+
CREATE INDEX IF NOT EXISTS plg_financial_movements_reverses_idx
|
|
32993
|
+
ON public.plg_financial_movements (reverses_id)
|
|
32994
|
+
WHERE reverses_id IS NOT NULL;
|
|
32995
|
+
|
|
32996
|
+
-- O saldo por conta. \`v_financial_account_balances\` soma, POR CONTA, os
|
|
32997
|
+
-- movimentos que entram e os que saem dela. Os \xEDndices existentes s\xE3o de uma
|
|
32998
|
+
-- coluna s\xF3 (\`debit_account_id\`, \`credit_account_id\`), ent\xE3o o planejador
|
|
32999
|
+
-- cruzava um deles com o \xEDndice de \`tenant_id\` \u2014 46.862 linhas de bitmap por
|
|
33000
|
+
-- conta, vezes o n\xFAmero de contas. Compostos e com as colunas da soma dentro,
|
|
33001
|
+
-- cada conta \xE9 uma varredura curta do pr\xF3prio \xEDndice.
|
|
33002
|
+
CREATE INDEX IF NOT EXISTS plg_financial_movements_debit_balance_idx
|
|
33003
|
+
ON public.plg_financial_movements (tenant_id, debit_account_id)
|
|
33004
|
+
INCLUDE (paid_amount, amount, unit_id, owner_id, reverses_id)
|
|
33005
|
+
WHERE debit_account_id IS NOT NULL
|
|
33006
|
+
AND status <> 'cancelled'
|
|
33007
|
+
AND movement_kind IN ('payment', 'transfer');
|
|
33008
|
+
|
|
33009
|
+
CREATE INDEX IF NOT EXISTS plg_financial_movements_credit_balance_idx
|
|
33010
|
+
ON public.plg_financial_movements (tenant_id, credit_account_id)
|
|
33011
|
+
INCLUDE (paid_amount, amount, unit_id, owner_id, reverses_id)
|
|
33012
|
+
WHERE credit_account_id IS NOT NULL
|
|
33013
|
+
AND status <> 'cancelled'
|
|
33014
|
+
AND movement_kind IN ('payment', 'transfer');
|
|
33015
|
+
`;
|
|
33016
|
+
var MIGRATION_086_A_CERCA_SE_PERGUNTA_UMA_VEZ_E_NAO_UMA_VEZ_POR_LINHA = `-- A cerca se pergunta uma vez, n\xE3o uma vez por linha.
|
|
33017
|
+
--
|
|
33018
|
+
-- \`financial_summary\` (077) j\xE1 soma no banco, e os \xEDndices (085) j\xE1 a fazem
|
|
33019
|
+
-- ler 5.100 linhas em vez de 281.174. Continuava dando 57014 \u2014 "canceling
|
|
33020
|
+
-- statement due to statement timeout" \u2014 e o custo que sobrou n\xE3o \xE9 varredura,
|
|
33021
|
+
-- \xE9 a POL\xCDTICA.
|
|
33022
|
+
--
|
|
33023
|
+
-- A pol\xEDtica de SELECT de \`plg_financial_movements\` faz, POR LINHA:
|
|
33024
|
+
--
|
|
33025
|
+
-- (unit_id IS NULL OR app.has_unit(unit_id))
|
|
33026
|
+
-- AND CASE unit_id \u2026 app.has_permission('financial.read', unit_id) END
|
|
33027
|
+
-- AND (\u2026 app.has_permission('financial.read_all', unit_id) \u2026)
|
|
33028
|
+
--
|
|
33029
|
+
-- As tr\xEAs dependem da LINHA, ent\xE3o n\xE3o saem do la\xE7o (as que n\xE3o dependem j\xE1
|
|
33030
|
+
-- est\xE3o i\xE7adas em \`(SELECT \u2026)\`). \`has_unit\` e \`has_permission\` s\xE3o memoizadas
|
|
33031
|
+
-- num GUC, mas memoizado n\xE3o \xE9 gr\xE1tis: cada chamada monta a chave e faz o
|
|
33032
|
+
-- parse de um JSONB que cresce. Vezes tr\xEAs, vezes o n\xFAmero de linhas \u2014 e o
|
|
33033
|
+
-- saldo por conta l\xEA os movimentos de cada conta, ent\xE3o ali s\xE3o milh\xF5es de
|
|
33034
|
+
-- avalia\xE7\xF5es.
|
|
33035
|
+
--
|
|
33036
|
+
-- A mesma pergunta, feita como CONJUNTO, custa uma vez:
|
|
33037
|
+
--
|
|
33038
|
+
-- app.visible_units() -- exatamente o que has_unit aceita
|
|
33039
|
+
-- \xD7 app.has_permission(perm, u) por UNIDADE -- meia d\xFAzia, n\xE3o 281 mil
|
|
33040
|
+
--
|
|
33041
|
+
-- Da\xED em diante o filtro da linha \xE9 \`unit_id = ANY(array)\`, que \xE9 compara\xE7\xE3o,
|
|
33042
|
+
-- n\xE3o chamada de fun\xE7\xE3o. \xC9 a corre\xE7\xE3o que levou /clients de 10s para 15ms.
|
|
33043
|
+
--
|
|
33044
|
+
-- \u2500\u2500 O que N\xC3O muda \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\u2500\u2500\u2500\u2500\u2500\u2500
|
|
33045
|
+
--
|
|
33046
|
+
-- A fun\xE7\xE3o passa a \`SECURITY DEFINER\`, ent\xE3o a cerca \xE9 responsabilidade DELA, e
|
|
33047
|
+
-- est\xE1 escrita abaixo como tradu\xE7\xE3o literal das duas pol\xEDticas (movimento e
|
|
33048
|
+
-- conta) \u2014 mesma ordem, mesmos recursos, mesmas permiss\xF5es:
|
|
33049
|
+
--
|
|
33050
|
+
-- \xB7 tenant \u2192 \`tenant_id = app.current_tenant_id()\`
|
|
33051
|
+
-- \xB7 unidade \u2192 \`unit_id IS NULL\` com \`financial.read\` sem unidade, ou
|
|
33052
|
+
-- unidade dentro de \`visible_units()\` COM \`financial.read\` nela
|
|
33053
|
+
-- \xB7 dono \u2192 s\xF3 quando \`app.owner_scoped(<recurso>)\`; a\xED valem, na ordem da
|
|
33054
|
+
-- pol\xEDtica, \`owner_id IS NULL\`, \`owner_id = auth.uid()\`, o
|
|
33055
|
+
-- compartilhamento (\`has_grant\`, por linha \u2014 \xE9 o \xFAnico que n\xE3o
|
|
33056
|
+
-- tem forma de conjunto, e s\xF3 \xE9 avaliado quando o escopo por
|
|
33057
|
+
-- dono est\xE1 ligado, que \xE9 a exce\xE7\xE3o) e \`financial.read_all\`.
|
|
33058
|
+
--
|
|
33059
|
+
-- O estorno (\`v_financial_cash_movements\`) \xE9 reescrito aqui como uma coluna
|
|
33060
|
+
-- calculada, em vez de LEFT JOIN da tabela com ela mesma: mesma regra, sem a
|
|
33061
|
+
-- segunda varredura dos mesmos 287 MB por chamada.
|
|
33062
|
+
--
|
|
33063
|
+
-- Idempotente. Nenhuma pol\xEDtica, coluna ou dado muda.
|
|
33064
|
+
|
|
33065
|
+
CREATE OR REPLACE FUNCTION public.financial_summary(
|
|
33066
|
+
p_from date DEFAULT NULL,
|
|
33067
|
+
p_to date DEFAULT NULL
|
|
33068
|
+
) RETURNS TABLE (
|
|
33069
|
+
total_balance numeric,
|
|
33070
|
+
total_receivable numeric,
|
|
33071
|
+
total_payable numeric,
|
|
33072
|
+
monthly_inflow numeric,
|
|
33073
|
+
monthly_outflow numeric,
|
|
33074
|
+
overdue_receivable_count integer,
|
|
33075
|
+
overdue_receivable_amount numeric,
|
|
33076
|
+
overdue_payable_count integer,
|
|
33077
|
+
overdue_payable_amount numeric
|
|
33078
|
+
)
|
|
33079
|
+
LANGUAGE sql
|
|
33080
|
+
STABLE
|
|
33081
|
+
SECURITY DEFINER
|
|
33082
|
+
SET search_path TO ''
|
|
33083
|
+
AS $function$
|
|
33084
|
+
WITH ctx AS (
|
|
33085
|
+
SELECT app.current_tenant_id() AS tenant,
|
|
33086
|
+
auth.uid() AS me,
|
|
33087
|
+
app.visible_units() AS units,
|
|
33088
|
+
app.has_permission('financial.read', NULL::uuid) AS read_null,
|
|
33089
|
+
app.has_permission('financial.read_all', NULL::uuid) AS read_all_null,
|
|
33090
|
+
app.owner_scoped('financial.movement') AS own_mov,
|
|
33091
|
+
app.owner_scoped('financial.account') AS own_acc,
|
|
33092
|
+
app.grants_exist('financial.movement', ARRAY['shared_with']) AS grants_mov,
|
|
33093
|
+
app.grants_exist('financial.account', ARRAY['shared_with']) AS grants_acc,
|
|
33094
|
+
coalesce(p_from, (current_date - 30)) AS de,
|
|
33095
|
+
coalesce(p_to, current_date) AS ate
|
|
33096
|
+
),
|
|
33097
|
+
-- Uma pergunta por unidade alcan\xE7\xE1vel, n\xE3o uma por linha.
|
|
33098
|
+
u_read AS (
|
|
33099
|
+
SELECT u FROM ctx, unnest(ctx.units) u
|
|
33100
|
+
WHERE app.has_permission('financial.read', u)
|
|
33101
|
+
),
|
|
33102
|
+
u_read_all AS (
|
|
33103
|
+
SELECT u FROM ctx, unnest(ctx.units) u
|
|
33104
|
+
WHERE app.has_permission('financial.read_all', u)
|
|
33105
|
+
),
|
|
33106
|
+
-- Movimentos que esta pessoa veria na lista: o WHERE abaixo \xE9 a pol\xEDtica de
|
|
33107
|
+
-- SELECT de plg_financial_movements, termo por termo.
|
|
33108
|
+
-- NOT MATERIALIZED de prop\xF3sito: a partir do PG12 uma CTE citada mais de uma
|
|
33109
|
+
-- vez \xE9 materializada, e materializar a cerca inteira \xE9 justamente juntar as
|
|
33110
|
+
-- 281 mil linhas numa tabela tempor\xE1ria. Inlinada, cada uso leva o pr\xF3prio
|
|
33111
|
+
-- \xEDndice (aberto, pagamento, saldo por conta).
|
|
33112
|
+
--
|
|
33113
|
+
-- \`caixa_visivel\` \xE9 a regra de estorno de v_financial_cash_movements dita
|
|
33114
|
+
-- sem a auto-jun\xE7\xE3o: quando o movimento estorna outro, quem decide \xE9 o
|
|
33115
|
+
-- estornado; se n\xE3o estorna ningu\xE9m, decide ele mesmo. \`reverses_id IS NULL\`
|
|
33116
|
+
-- \xE9 a esmagadora maioria, ent\xE3o a subconsulta quase nunca roda \u2014 e quando
|
|
33117
|
+
-- roda, \xE9 pela chave prim\xE1ria.
|
|
33118
|
+
mov AS NOT MATERIALIZED (
|
|
33119
|
+
SELECT m.*,
|
|
33120
|
+
CASE
|
|
33121
|
+
WHEN m.reverses_id IS NULL THEN
|
|
33122
|
+
(m.external_source IS NULL
|
|
33123
|
+
OR (m.reconciled_at IS NOT NULL AND m.matched_movement_id IS NULL))
|
|
33124
|
+
ELSE coalesce(
|
|
33125
|
+
(SELECT r.external_source IS NULL
|
|
33126
|
+
OR (r.reconciled_at IS NOT NULL AND r.matched_movement_id IS NULL)
|
|
33127
|
+
FROM public.plg_financial_movements r
|
|
33128
|
+
WHERE r.id = m.reverses_id),
|
|
33129
|
+
(m.external_source IS NULL
|
|
33130
|
+
OR (m.reconciled_at IS NOT NULL AND m.matched_movement_id IS NULL)))
|
|
33131
|
+
END AS caixa_visivel
|
|
33132
|
+
FROM public.plg_financial_movements m, ctx
|
|
33133
|
+
WHERE m.tenant_id = ctx.tenant
|
|
33134
|
+
AND (CASE WHEN m.unit_id IS NULL THEN ctx.read_null
|
|
33135
|
+
ELSE m.unit_id IN (SELECT u FROM u_read) END)
|
|
33136
|
+
AND (NOT ctx.own_mov
|
|
33137
|
+
OR m.owner_id IS NULL
|
|
33138
|
+
OR m.owner_id = ctx.me
|
|
33139
|
+
OR (ctx.grants_mov
|
|
33140
|
+
AND app.has_grant('financial.movement', m.id, ARRAY['shared_with']))
|
|
33141
|
+
OR (CASE WHEN m.unit_id IS NULL THEN ctx.read_all_null
|
|
33142
|
+
ELSE m.unit_id IN (SELECT u FROM u_read_all) END))
|
|
33143
|
+
),
|
|
33144
|
+
-- O mesmo, para as contas.
|
|
33145
|
+
acc AS (
|
|
33146
|
+
SELECT a.id, a.kind
|
|
33147
|
+
FROM public.plg_financial_accounts a, ctx
|
|
33148
|
+
WHERE a.tenant_id = ctx.tenant
|
|
33149
|
+
AND a.is_active
|
|
33150
|
+
AND a.kind IN ('bank', 'cash_physical', 'cash_session',
|
|
33151
|
+
'card_receivable_credit', 'card_receivable_debit')
|
|
33152
|
+
AND (CASE WHEN a.unit_id IS NULL THEN ctx.read_null
|
|
33153
|
+
ELSE a.unit_id IN (SELECT u FROM u_read) END)
|
|
33154
|
+
AND (NOT ctx.own_acc
|
|
33155
|
+
OR a.owner_id IS NULL
|
|
33156
|
+
OR a.owner_id = ctx.me
|
|
33157
|
+
OR (ctx.grants_acc
|
|
33158
|
+
AND app.has_grant('financial.account', a.id, ARRAY['shared_with']))
|
|
33159
|
+
OR (CASE WHEN a.unit_id IS NULL THEN ctx.read_all_null
|
|
33160
|
+
ELSE a.unit_id IN (SELECT u FROM u_read_all) END))
|
|
33161
|
+
),
|
|
33162
|
+
-- O saldo de uma conta \xE9 o que entrou menos o que saiu dela, e s\xF3 conta
|
|
33163
|
+
-- pagamento e transfer\xEAncia \u2014 a obriga\xE7\xE3o n\xE3o \xE9 dinheiro. Para os cinco
|
|
33164
|
+
-- \`kind\` de caixa que o cart\xE3o soma, v_financial_account_balances calcula
|
|
33165
|
+
-- \`debit_total - credit_total\`; \xE9 esse sinal que est\xE1 aqui.
|
|
33166
|
+
saldo AS (
|
|
33167
|
+
SELECT coalesce(sum(
|
|
33168
|
+
coalesce((SELECT sum(d.amount) FROM mov d
|
|
33169
|
+
WHERE d.debit_account_id = acc.id
|
|
33170
|
+
AND d.status <> 'cancelled'
|
|
33171
|
+
AND d.movement_kind IN ('payment', 'transfer')
|
|
33172
|
+
AND d.caixa_visivel), 0)
|
|
33173
|
+
- coalesce((SELECT sum(c.amount) FROM mov c
|
|
33174
|
+
WHERE c.credit_account_id = acc.id
|
|
33175
|
+
AND c.status <> 'cancelled'
|
|
33176
|
+
AND c.movement_kind IN ('payment', 'transfer')
|
|
33177
|
+
AND c.caixa_visivel), 0)
|
|
33178
|
+
), 0) AS v
|
|
33179
|
+
FROM acc
|
|
33180
|
+
),
|
|
33181
|
+
aberto AS (
|
|
33182
|
+
SELECT
|
|
33183
|
+
coalesce(sum(m.amount - m.paid_amount) FILTER (WHERE m.direction = 'credit'), 0) AS receber,
|
|
33184
|
+
coalesce(sum(m.amount - m.paid_amount) FILTER (WHERE m.direction = 'debit'), 0) AS pagar,
|
|
33185
|
+
count(*) FILTER (WHERE m.direction = 'credit'
|
|
33186
|
+
AND (m.status = 'overdue' OR m.due_date < current_date)) AS venc_receber_n,
|
|
33187
|
+
coalesce(sum(m.amount - m.paid_amount) FILTER (WHERE m.direction = 'credit'
|
|
33188
|
+
AND (m.status = 'overdue' OR m.due_date < current_date)), 0) AS venc_receber_v,
|
|
33189
|
+
count(*) FILTER (WHERE m.direction = 'debit'
|
|
33190
|
+
AND (m.status = 'overdue' OR m.due_date < current_date)) AS venc_pagar_n,
|
|
33191
|
+
coalesce(sum(m.amount - m.paid_amount) FILTER (WHERE m.direction = 'debit'
|
|
33192
|
+
AND (m.status = 'overdue' OR m.due_date < current_date)), 0) AS venc_pagar_v
|
|
33193
|
+
FROM mov m
|
|
33194
|
+
WHERE m.status IN ('pending', 'partial', 'overdue')
|
|
33195
|
+
),
|
|
33196
|
+
caixa AS (
|
|
33197
|
+
SELECT
|
|
33198
|
+
coalesce(sum(m.paid_amount) FILTER (WHERE m.direction = 'credit'), 0) AS entrada,
|
|
33199
|
+
coalesce(sum(m.paid_amount) FILTER (WHERE m.direction = 'debit'), 0) AS saida
|
|
33200
|
+
FROM mov m, ctx
|
|
33201
|
+
WHERE m.movement_kind = 'payment'
|
|
33202
|
+
AND m.payment_date >= ctx.de AND m.payment_date <= ctx.ate
|
|
33203
|
+
AND m.caixa_visivel
|
|
33204
|
+
)
|
|
33205
|
+
SELECT saldo.v, aberto.receber, aberto.pagar, caixa.entrada, caixa.saida,
|
|
33206
|
+
aberto.venc_receber_n::integer, aberto.venc_receber_v,
|
|
33207
|
+
aberto.venc_pagar_n::integer, aberto.venc_pagar_v
|
|
33208
|
+
FROM saldo, aberto, caixa;
|
|
33209
|
+
$function$;
|
|
33210
|
+
|
|
33211
|
+
COMMENT ON FUNCTION public.financial_summary(date, date) IS
|
|
33212
|
+
'Os nove numeros do cartao de resumo, somados no banco, com a cerca de '
|
|
33213
|
+
'tenant/unidade/dono perguntada UMA vez em vez de uma vez por linha. '
|
|
33214
|
+
'SECURITY DEFINER: a politica de plg_financial_movements e de '
|
|
33215
|
+
'plg_financial_accounts esta reescrita aqui, termo por termo, em forma de '
|
|
33216
|
+
'conjunto - ver o cabecalho da migracao 086.';
|
|
33217
|
+
|
|
33218
|
+
GRANT EXECUTE ON FUNCTION public.financial_summary(date, date) TO authenticated, service_role;
|
|
33219
|
+
`;
|
|
33220
|
+
var MIGRATION_087_O_SALDO_DE_UMA_FATURA_NAO_SOMA_O_RAZAO_INTEIRO = `-- O saldo de uma fatura n\xE3o soma o raz\xE3o inteiro.
|
|
33221
|
+
--
|
|
33222
|
+
-- \`v_financial_installment_balances\` abre com uma CTE que agrega TUDO:
|
|
33223
|
+
--
|
|
33224
|
+
-- WITH paid AS (
|
|
33225
|
+
-- SELECT a.installment_id, sum(a.amount)
|
|
33226
|
+
-- FROM plg_financial_allocations a
|
|
33227
|
+
-- JOIN plg_financial_movements m ON m.id = a.payment_movement_id
|
|
33228
|
+
-- WHERE m.status <> 'cancelled' AND m.reversed_by_id IS NULL
|
|
33229
|
+
-- GROUP BY a.installment_id)
|
|
33230
|
+
--
|
|
33231
|
+
-- Nenhum predicado do chamador alcan\xE7a essa CTE: ela agrupa por
|
|
33232
|
+
-- \`installment_id\`, e o \`LEFT JOIN paid p ON p.installment_id = i.id\` s\xF3
|
|
33233
|
+
-- acontece DEPOIS. O planejador n\xE3o tem como empurrar \`invoice_id = X\` para
|
|
33234
|
+
-- dentro, ent\xE3o materializa o raz\xE3o inteiro \u2014 toda vez, para qualquer pergunta.
|
|
33235
|
+
--
|
|
33236
|
+
-- Medido no cluster, pedindo UMA fatura:
|
|
33237
|
+
--
|
|
33238
|
+
-- Index Scan ... plg_financial_invoice_installments 1 linha 0,077 ms
|
|
33239
|
+
-- Seq Scan plg_financial_movements 281.113 linhas
|
|
33240
|
+
-- Seq Scan plg_financial_allocations 140.073 linhas
|
|
33241
|
+
-- HashAggregate 9 lotes, 7.288 kB em disco
|
|
33242
|
+
-- \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
33243
|
+
-- CTE paid 2.251 ms
|
|
33244
|
+
-- total 2.627 ms
|
|
33245
|
+
--
|
|
33246
|
+
-- 2.627 ms como superusu\xE1rio. Pelo PostgREST, com RLS por cima, passa dos 8 s
|
|
33247
|
+
-- e a tela recebe 500 / 57014. A parcela que a pessoa pediu custou 0,077 ms; o
|
|
33248
|
+
-- resto \xE9 o raz\xE3o de dois tenants sendo somado para ser jogado fora.
|
|
33249
|
+
--
|
|
33250
|
+
-- \u2500\u2500 O conserto \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
33251
|
+
--
|
|
33252
|
+
-- LATERAL correlacionado em \`i.id\`. A soma passa a acontecer POR PARCELA, e
|
|
33253
|
+
-- \`plg_financial_allocations_installment_idx\` \u2014 que j\xE1 existe \u2014 a resolve com
|
|
33254
|
+
-- um index scan de poucas linhas. Uma fatura com 8 parcelas faz 8 buscas
|
|
33255
|
+
-- pequenas em vez de uma agrega\xE7\xE3o de 140 mil linhas.
|
|
33256
|
+
--
|
|
33257
|
+
-- \xC9 o mesmo movimento da 085 e da 086: a pergunta \xE9 de UMA linha, e a resposta
|
|
33258
|
+
-- n\xE3o pode ter custo de tabela inteira.
|
|
33259
|
+
--
|
|
33260
|
+
-- \u2500\u2500 O que N\xC3O muda \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\u2500\u2500\u2500\u2500\u2500\u2500
|
|
33261
|
+
--
|
|
33262
|
+
-- As colunas, na mesma ordem e com os mesmos tipos \u2014 \`CREATE OR REPLACE VIEW\`
|
|
33263
|
+
-- exige isso, e quem l\xEA a vista n\xE3o percebe diferen\xE7a nenhuma al\xE9m do tempo.
|
|
33264
|
+
-- \`security_invoker\` continua ligado: a vista \xE9 uma lente, a RLS \xE9 das tabelas.
|
|
33265
|
+
--
|
|
33266
|
+
-- Idempotente.
|
|
33267
|
+
|
|
33268
|
+
CREATE OR REPLACE VIEW public.v_financial_installment_balances
|
|
33269
|
+
WITH (security_invoker = true) AS
|
|
33270
|
+
SELECT i.id AS installment_id,
|
|
33271
|
+
i.tenant_id,
|
|
33272
|
+
i.unit_id,
|
|
33273
|
+
i.invoice_id,
|
|
33274
|
+
t.number AS invoice_number,
|
|
33275
|
+
t.direction,
|
|
33276
|
+
t.counterparty_person_id,
|
|
33277
|
+
t.order_id,
|
|
33278
|
+
i.number,
|
|
33279
|
+
i.due_date,
|
|
33280
|
+
i.amount,
|
|
33281
|
+
COALESCE(p.paid, 0::numeric)::numeric(14,2) AS paid,
|
|
33282
|
+
CASE
|
|
33283
|
+
WHEN i.cancelled_at IS NOT NULL OR t.cancelled_at IS NOT NULL THEN 0::numeric
|
|
33284
|
+
ELSE GREATEST(i.amount - COALESCE(p.paid, 0::numeric), 0::numeric)
|
|
33285
|
+
END::numeric(14,2) AS balance,
|
|
33286
|
+
CASE
|
|
33287
|
+
WHEN i.cancelled_at IS NOT NULL OR t.cancelled_at IS NOT NULL THEN 'cancelled'::text
|
|
33288
|
+
WHEN COALESCE(p.paid, 0::numeric) >= i.amount THEN 'paid'::text
|
|
33289
|
+
WHEN COALESCE(p.paid, 0::numeric) > 0::numeric THEN 'partial'::text
|
|
33290
|
+
WHEN i.due_date < CURRENT_DATE THEN 'overdue'::text
|
|
33291
|
+
ELSE 'pending'::text
|
|
33292
|
+
END AS status,
|
|
33293
|
+
i.cancelled_at,
|
|
33294
|
+
i.metadata,
|
|
33295
|
+
i.created_at,
|
|
33296
|
+
i.updated_at
|
|
33297
|
+
FROM plg_financial_invoice_installments i
|
|
33298
|
+
JOIN plg_financial_invoices t ON t.id = i.invoice_id
|
|
33299
|
+
-- A soma \xE9 POR PARCELA. \`ON true\` mant\xE9m o LEFT: parcela sem pagamento
|
|
33300
|
+
-- continua aparecendo, com \`paid\` nulo, que o COALESCE zera.
|
|
33301
|
+
LEFT JOIN LATERAL (
|
|
33302
|
+
SELECT sum(a.amount) AS paid
|
|
33303
|
+
FROM plg_financial_allocations a
|
|
33304
|
+
JOIN plg_financial_movements m ON m.id = a.payment_movement_id
|
|
33305
|
+
WHERE a.installment_id = i.id
|
|
33306
|
+
AND m.status <> 'cancelled'::text
|
|
33307
|
+
AND m.reversed_by_id IS NULL
|
|
33308
|
+
) p ON true;
|
|
33309
|
+
|
|
33310
|
+
-- O movimento \xE9 olhado uma vez por aloca\xE7\xE3o, pelo id \u2014 e o filtro de vivo
|
|
33311
|
+
-- (\`status\`, \`reversed_by_id\`) cabe no \xEDndice, ent\xE3o a visita \xE0 heap s\xF3
|
|
33312
|
+
-- acontece para o que de fato soma.
|
|
33313
|
+
CREATE INDEX IF NOT EXISTS plg_financial_movements_alive_idx
|
|
33314
|
+
ON public.plg_financial_movements (id)
|
|
33315
|
+
WHERE status <> 'cancelled'::text AND reversed_by_id IS NULL;
|
|
33316
|
+
`;
|
|
33317
|
+
var MIGRATION_088_A_CONTA_SEM_PESSOA_E_DO_SISTEMA_NAO_DE_NINGUEM = `-- A conta sem pessoa \xE9 do sistema, n\xE3o de ningu\xE9m.
|
|
33318
|
+
--
|
|
33319
|
+
-- \`migration.upsert_fin_account\` inventa um valor que a tabela n\xE3o aceita:
|
|
33320
|
+
--
|
|
33321
|
+
-- IF v_kind = 'person' AND coalesce(v_ref_id, 0) = 0 THEN
|
|
33322
|
+
-- v_kind := 'unidentified';
|
|
33323
|
+
--
|
|
33324
|
+
-- Uma conta de PESSOA sem pessoa referenciada vira \`'unidentified'\`. S\xF3 que
|
|
33325
|
+
-- \`plg_financial_accounts_kind_check\` declara dez esp\xE9cies e essa n\xE3o \xE9 uma
|
|
33326
|
+
-- delas \u2014 a migration 063 recria o CHECK e ele estoura com \`23514\` em cima das
|
|
33327
|
+
-- linhas que o porte gravou. O efeito \xE9 pior do que parece: 063 \xE9 a 64\xAA de 88
|
|
33328
|
+
-- na cadeia do financeiro, ent\xE3o DUAS linhas travam a aplica\xE7\xE3o de todo o resto
|
|
33329
|
+
-- do plugin, migrations de desempenho inclusive.
|
|
33330
|
+
--
|
|
33331
|
+
-- \u2500\u2500 Quem s\xE3o as duas \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\u2500\u2500\u2500\u2500
|
|
33332
|
+
--
|
|
33333
|
+
-- Pertinho do C\xE9u, vindas de \`account_central\`:
|
|
33334
|
+
--
|
|
33335
|
+
-- 653309 Cliente n\xE3o identificado
|
|
33336
|
+
-- 654242 M\xFAltiplos participantes
|
|
33337
|
+
--
|
|
33338
|
+
-- 94 movimentos creditam nelas. N\xE3o s\xE3o erro de dado: s\xE3o o balc\xE3o an\xF4nimo e a
|
|
33339
|
+
-- conta dividida \u2014 dois baldes que a CASA mant\xE9m. Nenhuma das duas \xE9 pessoa, e
|
|
33340
|
+
-- nenhuma \xE9 banco.
|
|
33341
|
+
--
|
|
33342
|
+
-- \u2500\u2500 Por que \`system\` e n\xE3o mais um valor no CHECK \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
|
|
33343
|
+
--
|
|
33344
|
+
-- \`system\` j\xE1 existe no vocabul\xE1rio e significa exatamente isto: conta que o
|
|
33345
|
+
-- estabelecimento mant\xE9m e que n\xE3o \xE9 de ningu\xE9m. Acrescentar \`unidentified\`
|
|
33346
|
+
-- seria criar um d\xE9cimo primeiro nome para um conceito que j\xE1 tem o seu, e
|
|
33347
|
+
-- alargar um CHECK que outro dono fechou de prop\xF3sito.
|
|
33348
|
+
--
|
|
33349
|
+
-- O nome continua leg\xEDvel \u2014 "Cliente n\xE3o identificado" est\xE1 em \`name\`, que \xE9 o
|
|
33350
|
+
-- que a tela mostra. O que muda \xE9 a ESP\xC9CIE, que \xE9 o que o raz\xE3o classifica,
|
|
33351
|
+
-- mais um \`code\` est\xE1vel, que \xE9 como conta de sistema se procura.
|
|
33352
|
+
--
|
|
33353
|
+
-- \u2500\u2500 Ordem \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
33354
|
+
--
|
|
33355
|
+
-- Numa cadeia do zero isto nunca acontece: sem porte n\xE3o h\xE1 linha, e a 063
|
|
33356
|
+
-- passa. O conserto aqui \xE9 para o cluster onde o porte j\xE1 rodou \u2014 normaliza o
|
|
33357
|
+
-- que existe e, sobretudo, corrige o writer para n\xE3o produzir mais.
|
|
33358
|
+
--
|
|
33359
|
+
-- Idempotente.
|
|
33360
|
+
|
|
33361
|
+
-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
33362
|
+
-- 1. O que j\xE1 est\xE1 gravado
|
|
33363
|
+
-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
33364
|
+
|
|
33365
|
+
-- \`plg_financial_accounts_check1\` cobra \`code\` de toda conta de sistema, e
|
|
33366
|
+
-- cobra com raz\xE3o: conta de sistema se endere\xE7a por c\xF3digo est\xE1vel, n\xE3o por
|
|
33367
|
+
-- nome \u2014 o nome \xE9 o que a tela mostra, o c\xF3digo \xE9 o que o raz\xE3o procura.
|
|
33368
|
+
-- Os dois baldes ganham c\xF3digo no estilo dos que j\xE1 existem (\`legacy\`,
|
|
33369
|
+
-- \`payables\`, \`receivables\`).
|
|
33370
|
+
UPDATE public.plg_financial_accounts
|
|
33371
|
+
SET kind = 'system',
|
|
33372
|
+
code = coalesce(code, CASE
|
|
33373
|
+
WHEN name ILIKE '%%ltiplos%%' OR name ILIKE '%%multiple%%' THEN 'multiple_parties'
|
|
33374
|
+
ELSE 'unidentified_party'
|
|
33375
|
+
END),
|
|
33376
|
+
metadata = jsonb_set(coalesce(metadata, '{}'::jsonb), '{v1,account_kind}', '"unidentified"'::jsonb, true)
|
|
33377
|
+
WHERE kind = 'unidentified';
|
|
33378
|
+
|
|
33379
|
+
-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
33380
|
+
-- 2. O writer para de produzir
|
|
33381
|
+
-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
33382
|
+
--
|
|
33383
|
+
-- Duas trocas no corpo, nada mais: a atribui\xE7\xE3o e a lista de aceitos. Reescrever
|
|
33384
|
+
-- a fun\xE7\xE3o inteira aqui duplicaria 60 linhas que a 061 j\xE1 define \u2014 e a c\xF3pia
|
|
33385
|
+
-- envelheceria na pr\xF3xima vez que algu\xE9m mexesse l\xE1.
|
|
33386
|
+
|
|
33387
|
+
DO $$
|
|
33388
|
+
DECLARE
|
|
33389
|
+
v_src text;
|
|
33390
|
+
v_new text;
|
|
33391
|
+
BEGIN
|
|
33392
|
+
SELECT pg_get_functiondef(p.oid) INTO v_src
|
|
33393
|
+
FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
33394
|
+
WHERE n.nspname = 'migration' AND p.proname = 'upsert_fin_account'
|
|
33395
|
+
LIMIT 1;
|
|
33396
|
+
|
|
33397
|
+
IF v_src IS NULL THEN
|
|
33398
|
+
RAISE NOTICE 'migration.upsert_fin_account n\xE3o existe neste cluster \u2014 nada a corrigir';
|
|
33399
|
+
RETURN;
|
|
33400
|
+
END IF;
|
|
33401
|
+
|
|
33402
|
+
IF position('''unidentified''' in v_src) = 0 THEN
|
|
33403
|
+
RAISE NOTICE 'migration.upsert_fin_account j\xE1 n\xE3o produz unidentified';
|
|
33404
|
+
RETURN;
|
|
33405
|
+
END IF;
|
|
33406
|
+
|
|
33407
|
+
v_new := replace(v_src, 'v_kind := ''unidentified'';', 'v_kind := ''system'';');
|
|
33408
|
+
-- A mesma palavra aparece na lista de esp\xE9cies aceitas pelo writer; l\xE1 ela
|
|
33409
|
+
-- some, porque \`system\` j\xE1 est\xE1 na lista.
|
|
33410
|
+
v_new := replace(v_new, ',' || chr(10) || ' ''unidentified'')', ')');
|
|
33411
|
+
v_new := replace(v_new, ', ''unidentified'')', ')');
|
|
33412
|
+
|
|
33413
|
+
IF v_new = v_src THEN
|
|
33414
|
+
RAISE EXCEPTION 'upsert_fin_account mudou de forma: o conserto de unidentified n\xE3o casou. Conferir \xE0 m\xE3o.';
|
|
33415
|
+
END IF;
|
|
33416
|
+
|
|
33417
|
+
EXECUTE v_new;
|
|
33418
|
+
END $$;
|
|
33419
|
+
|
|
33420
|
+
-- A prova: nenhuma linha fora do vocabul\xE1rio, e o writer sem a palavra.
|
|
33421
|
+
DO $$
|
|
33422
|
+
BEGIN
|
|
33423
|
+
IF EXISTS (SELECT 1 FROM public.plg_financial_accounts WHERE kind = 'unidentified') THEN
|
|
33424
|
+
RAISE EXCEPTION 'ainda h\xE1 conta com kind=unidentified';
|
|
33425
|
+
END IF;
|
|
33426
|
+
IF EXISTS (
|
|
33427
|
+
SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
33428
|
+
WHERE n.nspname = 'migration' AND p.proname = 'upsert_fin_account'
|
|
33429
|
+
AND position('''unidentified''' in pg_get_functiondef(p.oid)) > 0
|
|
33430
|
+
) THEN
|
|
33431
|
+
RAISE EXCEPTION 'upsert_fin_account ainda produz unidentified';
|
|
33432
|
+
END IF;
|
|
33433
|
+
END $$;
|
|
33434
|
+
`;
|
|
33435
|
+
var MIGRATIONS = [
|
|
33436
|
+
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
33437
|
+
{ id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
|
|
33438
|
+
{ id: "002_payment_types_are_the_country_not_the_tenant", sql: MIGRATION_002_PAYMENT_TYPES_ARE_THE_COUNTRY_NOT_THE_TENANT },
|
|
33439
|
+
{ id: "003_the_commission_rule_gets_a_table", sql: MIGRATION_003_THE_COMMISSION_RULE_GETS_A_TABLE },
|
|
33440
|
+
{ id: "004_one_bridge_for_three_doors", sql: MIGRATION_004_ONE_BRIDGE_FOR_THREE_DOORS },
|
|
33441
|
+
{ id: "005_the_appointment_closes_like_the_comanda", sql: MIGRATION_005_THE_APPOINTMENT_CLOSES_LIKE_THE_COMANDA },
|
|
33442
|
+
{ id: "006_the_month_can_be_closed_and_the_book_can_be_proven", sql: MIGRATION_006_THE_MONTH_CAN_BE_CLOSED_AND_THE_BOOK_CAN_BE_PROVEN },
|
|
33443
|
+
{ id: "007_the_invoice_knows_who_issued_it_and_what_it_owes", sql: MIGRATION_007_THE_INVOICE_KNOWS_WHO_ISSUED_IT_AND_WHAT_IT_OWES },
|
|
33444
|
+
{ id: "008_the_commission_rule_learns_to_choose_and_to_split", sql: MIGRATION_008_THE_COMMISSION_RULE_LEARNS_TO_CHOOSE_AND_TO_SPLIT },
|
|
33445
|
+
{ id: "009_the_commission_is_calculated_line_by_line", sql: MIGRATION_009_THE_COMMISSION_IS_CALCULATED_LINE_BY_LINE },
|
|
33446
|
+
{ id: "010_the_invoice_raised_from_a_sale_gets_its_own_lines", sql: MIGRATION_010_THE_INVOICE_RAISED_FROM_A_SALE_GETS_ITS_OWN_LINES },
|
|
33447
|
+
{ id: "011_a_new_tenant_can_take_money_on_the_first_day", sql: MIGRATION_011_A_NEW_TENANT_CAN_TAKE_MONEY_ON_THE_FIRST_DAY },
|
|
33448
|
+
{ id: "012_the_commissions_become_one_bill_per_person", sql: MIGRATION_012_THE_COMMISSIONS_BECOME_ONE_BILL_PER_PERSON },
|
|
33449
|
+
{ id: "013_a_discount_has_a_reason_and_someone_who_allowed_it", sql: MIGRATION_013_A_DISCOUNT_HAS_A_REASON_AND_SOMEONE_WHO_ALLOWED_IT },
|
|
33450
|
+
{ id: "014_the_bill_that_comes_back_every_month", sql: MIGRATION_014_THE_BILL_THAT_COMES_BACK_EVERY_MONTH },
|
|
33451
|
+
{ id: "015_the_sale_knows_what_it_cost", sql: MIGRATION_015_THE_SALE_KNOWS_WHAT_IT_COST },
|
|
33452
|
+
{ id: "016_the_overdue_bill_gets_a_letter", sql: MIGRATION_016_THE_OVERDUE_BILL_GETS_A_LETTER },
|
|
33453
|
+
{ id: "017_the_receipt_is_a_query_not_a_layout", sql: MIGRATION_017_THE_RECEIPT_IS_A_QUERY_NOT_A_LAYOUT },
|
|
33454
|
+
{ id: "018_the_caixa_shows_what_it_should_have", sql: MIGRATION_018_THE_CAIXA_SHOWS_WHAT_IT_SHOULD_HAVE },
|
|
33455
|
+
{ id: "019_who_did_it_is_already_known", sql: MIGRATION_019_WHO_DID_IT_IS_ALREADY_KNOWN },
|
|
33456
|
+
{ id: "020_a_rule_with_nobody_to_pay_pays_the_executor", sql: MIGRATION_020_A_RULE_WITH_NOBODY_TO_PAY_PAYS_THE_EXECUTOR },
|
|
33457
|
+
{ id: "021_the_most_specific_rule_wins", sql: MIGRATION_021_THE_MOST_SPECIFIC_RULE_WINS },
|
|
33458
|
+
{ id: "022_an_unknown_attribute_does_not_match", sql: MIGRATION_022_AN_UNKNOWN_ATTRIBUTE_DOES_NOT_MATCH },
|
|
33459
|
+
{ id: "023_reapurar_nao_cria_um_lote_orfao", sql: MIGRATION_023_REAPURAR_NAO_CRIA_UM_LOTE_ORFAO },
|
|
33460
|
+
{ id: "024_o_bordero_paga_o_que_ele_soma", sql: MIGRATION_024_O_BORDERO_PAGA_O_QUE_ELE_SOMA },
|
|
33461
|
+
{ id: "025_a_meta_do_mes_tem_tres_degraus", sql: MIGRATION_025_A_META_DO_MES_TEM_TRES_DEGRAUS },
|
|
33462
|
+
{ id: "026_quem_fecha_o_caixa_nao_e_quem_confere", sql: MIGRATION_026_QUEM_FECHA_O_CAIXA_NAO_E_QUEM_CONFERE },
|
|
33463
|
+
{ id: "027_o_vale_presente_nasce_em_lote_e_e_um_passivo", sql: MIGRATION_027_O_VALE_PRESENTE_NASCE_EM_LOTE_E_E_UM_PASSIVO },
|
|
33464
|
+
{ id: "028_o_cartao_tem_cor", sql: MIGRATION_028_O_CARTAO_TEM_COR },
|
|
33465
|
+
{ id: "029_the_kiosk_sale_is_a_sale_too", sql: MIGRATION_029_THE_KIOSK_SALE_IS_A_SALE_TOO },
|
|
33466
|
+
{ id: "030_the_census_can_see_the_kiosk", sql: MIGRATION_030_THE_CENSUS_CAN_SEE_THE_KIOSK },
|
|
33467
|
+
{ id: "031_the_bank_line_is_evidence_not_a_second_payment", sql: MIGRATION_031_THE_BANK_LINE_IS_EVIDENCE_NOT_A_SECOND_PAYMENT },
|
|
33468
|
+
{ id: "032_contas_a_pagar_ganham_onde_morar", sql: MIGRATION_032_CONTAS_A_PAGAR_GANHAM_ONDE_MORAR },
|
|
33469
|
+
{ id: "033_o_vencimento_aponta_para_o_titulo", sql: MIGRATION_033_O_VENCIMENTO_APONTA_PARA_O_TITULO },
|
|
33470
|
+
{ id: "034_a_unidade_tem_conta_e_a_pessoa_tem_uma_so", sql: MIGRATION_034_A_UNIDADE_TEM_CONTA_E_A_PESSOA_TEM_UMA_SO },
|
|
33471
|
+
{ id: "035_a_classificacao_contabil_chega_inteira", sql: MIGRATION_035_A_CLASSIFICACAO_CONTABIL_CHEGA_INTEIRA },
|
|
33472
|
+
{ id: "036_a_fusao_para_de_dobrar_a_propria_memoria", sql: MIGRATION_036_A_FUSAO_PARA_DE_DOBRAR_A_PROPRIA_MEMORIA },
|
|
33473
|
+
{ id: "037_a_venda_de_balcao_tem_conta", sql: MIGRATION_037_A_VENDA_DE_BALCAO_TEM_CONTA },
|
|
33474
|
+
{ id: "038_a_linha_da_fatura_alcanca_quem_veio_pelo_pedido", sql: MIGRATION_038_A_LINHA_DA_FATURA_ALCANCA_QUEM_VEIO_PELO_PEDIDO },
|
|
33475
|
+
{ id: "039_o_titulo_de_uma_venda_cancelada_nasce_cancelado", sql: MIGRATION_039_O_TITULO_DE_UMA_VENDA_CANCELADA_NASCE_CANCELADO },
|
|
33476
|
+
{ id: "040_a_dre_le_a_linha_da_venda", sql: MIGRATION_040_A_DRE_LE_A_LINHA_DA_VENDA },
|
|
33477
|
+
{ id: "041_o_caixa_do_v1_nao_segura_o_movimento", sql: MIGRATION_041_O_CAIXA_DO_V1_NAO_SEGURA_O_MOVIMENTO },
|
|
33478
|
+
{ id: "042_o_cancelado_do_v1_e_o_cancelled_do_v2", sql: MIGRATION_042_O_CANCELADO_DO_V1_E_O_CANCELLED_DO_V2 },
|
|
33479
|
+
{ id: "043_o_movimento_sabe_de_que_unidade_ele_e", sql: MIGRATION_043_O_MOVIMENTO_SABE_DE_QUE_UNIDADE_ELE_E },
|
|
33480
|
+
{ id: "044_a_dre_nao_paga_pedagio_por_item", sql: MIGRATION_044_A_DRE_NAO_PAGA_PEDAGIO_POR_ITEM },
|
|
33481
|
+
{ id: "045_a_conta_so_classifica_se_a_natureza_casa", sql: MIGRATION_045_A_CONTA_SO_CLASSIFICA_SE_A_NATUREZA_CASA },
|
|
33482
|
+
{ id: "046_o_demonstrativo_mostra_o_plano_inteiro", sql: MIGRATION_046_O_DEMONSTRATIVO_MOSTRA_O_PLANO_INTEIRO },
|
|
33483
|
+
{ id: "047_a_conta_reconhece_a_contraparte_na_segunda_oferta", sql: MIGRATION_047_A_CONTA_RECONHECE_A_CONTRAPARTE_NA_SEGUNDA_OFERTA },
|
|
33484
|
+
{ id: "048_a_parcela_e_a_conta_do_v1_e_a_baixa_e_a_alocacao", sql: MIGRATION_048_A_PARCELA_E_A_CONTA_DO_V1_E_A_BAIXA_E_A_ALOCACAO },
|
|
33485
|
+
{ id: "049_a_parcela_carrega_o_cancelamento", sql: MIGRATION_049_A_PARCELA_CARREGA_O_CANCELAMENTO },
|
|
33486
|
+
{ id: "050_o_saldo_do_titulo_se_calcula_uma_vez", sql: MIGRATION_050_O_SALDO_DO_TITULO_SE_CALCULA_UMA_VEZ },
|
|
33487
|
+
{ id: "051_o_saldo_se_calcula_so_na_linha_que_a_tela_mostra", sql: MIGRATION_051_O_SALDO_SE_CALCULA_SO_NA_LINHA_QUE_A_TELA_MOSTRA },
|
|
33488
|
+
{ id: "052_a_lista_de_contas_fala_vencimento", sql: MIGRATION_052_A_LISTA_DE_CONTAS_FALA_VENCIMENTO },
|
|
33489
|
+
{ id: "053_um_titulo_parcelado_vence_varias_vezes", sql: MIGRATION_053_UM_TITULO_PARCELADO_VENCE_VARIAS_VEZES },
|
|
33490
|
+
{ id: "054_as_datas_em_que_o_titulo_vence", sql: MIGRATION_054_AS_DATAS_EM_QUE_O_TITULO_VENCE },
|
|
33491
|
+
{ id: "055_a_lista_de_contas_lista_parcelas", sql: MIGRATION_055_A_LISTA_DE_CONTAS_LISTA_PARCELAS },
|
|
33492
|
+
{ id: "056_what_the_port_left_open", sql: MIGRATION_056_WHAT_THE_PORT_LEFT_OPEN },
|
|
33493
|
+
{ id: "057_a_linha_diz_que_tem_recibo", sql: MIGRATION_057_A_LINHA_DIZ_QUE_TEM_RECIBO },
|
|
33494
|
+
{ id: "058_o_clipe_usa_o_indice", sql: MIGRATION_058_O_CLIPE_USA_O_INDICE },
|
|
33495
|
+
{ id: "059_o_clipe_sai_da_view", sql: MIGRATION_059_O_CLIPE_SAI_DA_VIEW },
|
|
33496
|
+
{ id: "060_o_recebivel_volta_da_arvore_derrubada", sql: MIGRATION_060_O_RECEBIVEL_VOLTA_DA_ARVORE_DERRUBADA },
|
|
33497
|
+
{ id: "061_contas_a_pagar_ganham_onde_morar", sql: MIGRATION_061_CONTAS_A_PAGAR_GANHAM_ONDE_MORAR },
|
|
33498
|
+
{ id: "062_o_vencimento_aponta_para_o_titulo", sql: MIGRATION_062_O_VENCIMENTO_APONTA_PARA_O_TITULO },
|
|
33499
|
+
{ id: "063_a_unidade_tem_conta_e_a_pessoa_tem_uma_so", sql: MIGRATION_063_A_UNIDADE_TEM_CONTA_E_A_PESSOA_TEM_UMA_SO },
|
|
33500
|
+
{ id: "064_a_classificacao_contabil_chega_inteira", sql: MIGRATION_064_A_CLASSIFICACAO_CONTABIL_CHEGA_INTEIRA },
|
|
33501
|
+
{ id: "065_a_fusao_para_de_dobrar_a_propria_memoria", sql: MIGRATION_065_A_FUSAO_PARA_DE_DOBRAR_A_PROPRIA_MEMORIA },
|
|
33502
|
+
{ id: "066_a_venda_de_balcao_tem_conta", sql: MIGRATION_066_A_VENDA_DE_BALCAO_TEM_CONTA },
|
|
33503
|
+
{ id: "067_o_recibo_da_conta_atravessa", sql: MIGRATION_067_O_RECIBO_DA_CONTA_ATRAVESSA },
|
|
33504
|
+
{ id: "068_a_unidade_entra_com_a_linha_nao_depois_dela", sql: MIGRATION_068_A_UNIDADE_ENTRA_COM_A_LINHA_NAO_DEPOIS_DELA },
|
|
33505
|
+
{ id: "069_o_titulo_que_ja_existe_esta_migrado", sql: MIGRATION_069_O_TITULO_QUE_JA_EXISTE_ESTA_MIGRADO },
|
|
33506
|
+
{ id: "070_a_parcela_nasce_antes_do_movimento", sql: MIGRATION_070_A_PARCELA_NASCE_ANTES_DO_MOVIMENTO },
|
|
33507
|
+
{ id: "071_a_parcela_tem_uma_assinatura_so", sql: MIGRATION_071_A_PARCELA_TEM_UMA_ASSINATURA_SO },
|
|
33508
|
+
{ id: "072_o_caixa_do_v1_guarda_quem_o_abriu", sql: MIGRATION_072_O_CAIXA_DO_V1_GUARDA_QUEM_O_ABRIU },
|
|
33509
|
+
{ id: "073_o_caixa_pertence_a_conta_que_o_v1_lhe_deu", sql: MIGRATION_073_O_CAIXA_PERTENCE_A_CONTA_QUE_O_V1_LHE_DEU },
|
|
33510
|
+
{ id: "074_o_movimento_volta_a_apontar_para_o_caixa", sql: MIGRATION_074_O_MOVIMENTO_VOLTA_A_APONTAR_PARA_O_CAIXA },
|
|
33511
|
+
{ id: "075_a_venda_cancelada_cancela_o_titulo_dela", sql: MIGRATION_075_A_VENDA_CANCELADA_CANCELA_O_TITULO_DELA },
|
|
33512
|
+
{ id: "076_o_plano_de_contas_do_cliente_vence_o_do_modelo", sql: MIGRATION_076_O_PLANO_DE_CONTAS_DO_CLIENTE_VENCE_O_DO_MODELO },
|
|
33513
|
+
{ id: "077_o_resumo_financeiro_se_soma_no_banco", sql: MIGRATION_077_O_RESUMO_FINANCEIRO_SE_SOMA_NO_BANCO },
|
|
33514
|
+
{ id: "078_o_saldo_da_conta_se_calcula_so_na_conta_pedida", sql: MIGRATION_078_O_SALDO_DA_CONTA_SE_CALCULA_SO_NA_CONTA_PEDIDA },
|
|
33515
|
+
{ id: "079_as_portas_de_porte_novas_nao_nascem_abertas", sql: MIGRATION_079_AS_PORTAS_DE_PORTE_NOVAS_NAO_NASCEM_ABERTAS },
|
|
33516
|
+
{ id: "080_a_remessa_e_a_comissao_a_pagar_tem_onde_morar", sql: MIGRATION_080_A_REMESSA_E_A_COMISSAO_A_PAGAR_TEM_ONDE_MORAR },
|
|
33517
|
+
{ id: "081_a_remessa_e_a_comissao_atravessam", sql: MIGRATION_081_A_REMESSA_E_A_COMISSAO_ATRAVESSAM },
|
|
33518
|
+
{ id: "082_o_beneficiario_da_comissao_e_uma_conta", sql: MIGRATION_082_O_BENEFICIARIO_DA_COMISSAO_E_UMA_CONTA },
|
|
33519
|
+
{ id: "083_a_conta_do_banco_e_uma_conta_e_a_fatura_tem_dois_destinos", sql: MIGRATION_083_A_CONTA_DO_BANCO_E_UMA_CONTA_E_A_FATURA_TEM_DOIS_DESTINOS },
|
|
33520
|
+
{ id: "084_a_nota_de_entrada_tem_um_titulo_a_pagar", sql: MIGRATION_084_A_NOTA_DE_ENTRADA_TEM_UM_TITULO_A_PAGAR },
|
|
33521
|
+
{ id: "085_o_resumo_le_cinco_mil_linhas_e_nao_duzentas_e_oitenta_mil", sql: MIGRATION_085_O_RESUMO_LE_CINCO_MIL_LINHAS_E_NAO_DUZENTAS_E_OITENTA_MIL },
|
|
33522
|
+
{ id: "086_a_cerca_se_pergunta_uma_vez_e_nao_uma_vez_por_linha", sql: MIGRATION_086_A_CERCA_SE_PERGUNTA_UMA_VEZ_E_NAO_UMA_VEZ_POR_LINHA },
|
|
33523
|
+
{ id: "087_o_saldo_de_uma_fatura_nao_soma_o_razao_inteiro", sql: MIGRATION_087_O_SALDO_DE_UMA_FATURA_NAO_SOMA_O_RAZAO_INTEIRO },
|
|
33524
|
+
{ id: "088_a_conta_sem_pessoa_e_do_sistema_nao_de_ninguem", sql: MIGRATION_088_A_CONTA_SEM_PESSOA_E_DO_SISTEMA_NAO_DE_NINGUEM }
|
|
33525
|
+
];
|
|
33526
|
+
var STANDALONE_CONFIG = {
|
|
33527
|
+
currency: { code: "BRL", locale: "pt-BR", symbol: "R$" }
|
|
33528
|
+
};
|
|
33529
|
+
registerTranslations(financialLocales);
|
|
33530
|
+
function ReceivePaymentModal({
|
|
33531
|
+
installment,
|
|
33532
|
+
receiptUrl,
|
|
33533
|
+
onClose,
|
|
33534
|
+
onPaid
|
|
33535
|
+
}) {
|
|
33536
|
+
const provider = React15__default.useMemo(() => createSafeFinancialProvider(), []);
|
|
33537
|
+
const store6 = React15__default.useMemo(() => createFinancialStore(provider), [provider]);
|
|
33538
|
+
return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
|
|
33539
|
+
PaymentModal,
|
|
33540
|
+
{
|
|
33541
|
+
movement: installment,
|
|
33542
|
+
receiptUrl,
|
|
33543
|
+
onClose,
|
|
33544
|
+
onPaid
|
|
33545
|
+
}
|
|
33546
|
+
) });
|
|
33547
|
+
}
|
|
32015
33548
|
var RECEIVING_REGISTRIES = ["bank-accounts", "payment-method-types", "payment-methods", "card-brands"];
|
|
32016
33549
|
function createFinancialSettingsPlugin(options) {
|
|
32017
33550
|
registerTranslations(financialLocales);
|
|
@@ -32053,7 +33586,21 @@ function createFinancialSettingsPlugin(options) {
|
|
|
32053
33586
|
locales: financialLocales
|
|
32054
33587
|
};
|
|
32055
33588
|
}
|
|
33589
|
+
var RECUSA_COMPRA = {
|
|
33590
|
+
sem_sessao: "Fa\xE7a login para registrar a compra.",
|
|
33591
|
+
sem_tenant: "Escolha a conta antes de registrar.",
|
|
33592
|
+
cliente_nao_encontrado: "Este cliente n\xE3o est\xE1 nesta conta.",
|
|
33593
|
+
nada_escolhido: "Escolha pelo menos um servi\xE7o."
|
|
33594
|
+
};
|
|
32056
33595
|
var BRL = { code: "BRL", locale: "pt-BR", symbol: "R$" };
|
|
33596
|
+
var STATUS_CHIP = {
|
|
33597
|
+
paid: { label: "Pago", cls: "bg-success-soft text-success-soft-foreground" },
|
|
33598
|
+
partial: { label: "Parcial", cls: "bg-warning-soft text-warning-soft-foreground" },
|
|
33599
|
+
pending: { label: "Aberto", cls: "bg-info-soft text-info-soft-foreground" },
|
|
33600
|
+
open: { label: "Aberto", cls: "bg-info-soft text-info-soft-foreground" },
|
|
33601
|
+
overdue: { label: "Vencida", cls: "bg-destructive-soft text-destructive-soft-foreground" },
|
|
33602
|
+
cancelled: { label: "Cancelada", cls: "bg-muted text-muted-foreground" }
|
|
33603
|
+
};
|
|
32057
33604
|
function ClientAccountPanel({
|
|
32058
33605
|
personId,
|
|
32059
33606
|
orderId,
|
|
@@ -32064,12 +33611,13 @@ function ClientAccountPanel({
|
|
|
32064
33611
|
const provider = useFinancialProvider();
|
|
32065
33612
|
const [account, setAccount] = React15__default.useState(null);
|
|
32066
33613
|
const [busy, setBusy] = React15__default.useState(false);
|
|
32067
|
-
const [
|
|
32068
|
-
|
|
32069
|
-
|
|
32070
|
-
|
|
32071
|
-
|
|
32072
|
-
|
|
33614
|
+
const [viewInvoice, setViewInvoice] = React15__default.useState(null);
|
|
33615
|
+
const [comprando, setComprando] = React15__default.useState(false);
|
|
33616
|
+
const [buscaServico, setBuscaServico] = React15__default.useState("");
|
|
33617
|
+
const [catalogo, setCatalogo] = React15__default.useState([]);
|
|
33618
|
+
const [escolhidos, setEscolhidos] = React15__default.useState([]);
|
|
33619
|
+
const [comprandoBusy, setComprandoBusy] = React15__default.useState(false);
|
|
33620
|
+
const [erroCompra, setErroCompra] = React15__default.useState(null);
|
|
32073
33621
|
const load = React15__default.useCallback(async () => {
|
|
32074
33622
|
if (!personId) return;
|
|
32075
33623
|
setAccount(await provider.getClientAccount(personId));
|
|
@@ -32079,18 +33627,165 @@ function ClientAccountPanel({
|
|
|
32079
33627
|
}, [load]);
|
|
32080
33628
|
const forThis = account?.invoices.filter((i) => orderId && i.orderId === orderId) ?? [];
|
|
32081
33629
|
const hasForThis = forThis.length > 0;
|
|
33630
|
+
React15__default.useEffect(() => {
|
|
33631
|
+
if (!comprando) return;
|
|
33632
|
+
let vivo = true;
|
|
33633
|
+
const t2 = setTimeout(async () => {
|
|
33634
|
+
try {
|
|
33635
|
+
const r = await provider.searchSellableServices?.(buscaServico);
|
|
33636
|
+
if (vivo) setCatalogo(r ?? []);
|
|
33637
|
+
} catch {
|
|
33638
|
+
if (vivo) setCatalogo([]);
|
|
33639
|
+
}
|
|
33640
|
+
}, 250);
|
|
33641
|
+
return () => {
|
|
33642
|
+
vivo = false;
|
|
33643
|
+
clearTimeout(t2);
|
|
33644
|
+
};
|
|
33645
|
+
}, [comprando, buscaServico]);
|
|
33646
|
+
async function confirmarCompra() {
|
|
33647
|
+
if (!escolhidos.length) return;
|
|
33648
|
+
setComprandoBusy(true);
|
|
33649
|
+
try {
|
|
33650
|
+
const r = await provider.prepayServices?.(personId, escolhidos.map((e) => ({
|
|
33651
|
+
serviceId: e.id,
|
|
33652
|
+
name: e.name,
|
|
33653
|
+
quantity: e.qtd,
|
|
33654
|
+
unitPrice: e.price
|
|
33655
|
+
})));
|
|
33656
|
+
if (!r?.ok) {
|
|
33657
|
+
setErroCompra(RECUSA_COMPRA[r?.reason ?? ""] ?? "N\xE3o foi poss\xEDvel registrar a compra.");
|
|
33658
|
+
return;
|
|
33659
|
+
}
|
|
33660
|
+
setComprando(false);
|
|
33661
|
+
setEscolhidos([]);
|
|
33662
|
+
await load();
|
|
33663
|
+
} finally {
|
|
33664
|
+
setComprandoBusy(false);
|
|
33665
|
+
}
|
|
33666
|
+
}
|
|
32082
33667
|
const tiles = [
|
|
32083
33668
|
{ label: "Contratado", value: account?.contracted ?? 0, hint: "Tudo que o cliente j\xE1 contratou" },
|
|
32084
33669
|
{ label: "Pago", value: account?.paid ?? 0 },
|
|
32085
33670
|
{ label: "Em aberto", value: account?.open ?? 0 }
|
|
32086
33671
|
];
|
|
32087
|
-
|
|
33672
|
+
const aExecutar = (account?.paidToExecute ?? 0) + (account?.openToExecute ?? 0);
|
|
33673
|
+
if (account?.paidToExecute !== void 0 || account?.openToExecute !== void 0) {
|
|
33674
|
+
tiles.push({
|
|
33675
|
+
label: "A executar",
|
|
33676
|
+
value: aExecutar,
|
|
33677
|
+
hint: (account?.paidToExecute ?? 0) > 0 ? `${formatCurrency(account?.paidToExecute ?? 0, currency)} j\xE1 pagos \u2014 servi\xE7o que a casa ainda deve prestar` : "Contratado e ainda n\xE3o executado"
|
|
33678
|
+
});
|
|
33679
|
+
}
|
|
33680
|
+
if (account === null) {
|
|
33681
|
+
return /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-3", "aria-busy": "true", "aria-label": "Carregando a conta do cliente", children: [
|
|
33682
|
+
/* @__PURE__ */ jsx("div", { className: "fz-tiles fz-tiles-3", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-xl border px-3 py-2", children: [
|
|
33683
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-16 animate-pulse rounded bg-muted" }),
|
|
33684
|
+
/* @__PURE__ */ jsx("div", { className: "h-5 w-20 animate-pulse rounded bg-muted/70" })
|
|
33685
|
+
] }, i)) }),
|
|
33686
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-xl border px-3 py-3", children: [
|
|
33687
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-40 animate-pulse rounded bg-muted" }),
|
|
33688
|
+
/* @__PURE__ */ jsx("div", { className: "h-7 w-32 animate-pulse rounded-button bg-muted/70" })
|
|
33689
|
+
] }, i))
|
|
33690
|
+
] });
|
|
33691
|
+
}
|
|
33692
|
+
return /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-3", children: [
|
|
32088
33693
|
clientName && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: clientName }),
|
|
32089
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
33694
|
+
/* @__PURE__ */ jsx("div", { className: `fz-tiles ${tiles.length > 3 ? "fz-tiles-4" : "fz-tiles-3"}`, children: tiles.map((t2) => /* @__PURE__ */ jsxs("div", { title: t2.hint, className: "rounded-xl border px-3 py-2", children: [
|
|
32090
33695
|
/* @__PURE__ */ jsx("p", { className: "text-[10px] uppercase text-muted-foreground", children: t2.label }),
|
|
32091
33696
|
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm font-semibold tabular-nums", children: formatCurrency(t2.value, currency) })
|
|
32092
33697
|
] }, t2.label)) }),
|
|
32093
|
-
|
|
33698
|
+
provider.prepayServices && !comprando && /* @__PURE__ */ jsx(
|
|
33699
|
+
"button",
|
|
33700
|
+
{
|
|
33701
|
+
type: "button",
|
|
33702
|
+
onClick: () => setComprando(true),
|
|
33703
|
+
className: "w-full rounded-xl border border-dashed px-3 py-2 text-xs font-medium text-muted-foreground hover:bg-muted/40 hover:text-foreground",
|
|
33704
|
+
children: "+ Compra antecipada"
|
|
33705
|
+
}
|
|
33706
|
+
),
|
|
33707
|
+
comprando && /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-xl border px-3 py-3", children: [
|
|
33708
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: "Compra antecipada" }),
|
|
33709
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground", children: [
|
|
33710
|
+
"O servi\xE7o fica pago e ",
|
|
33711
|
+
/* @__PURE__ */ jsx("strong", { children: "a executar" }),
|
|
33712
|
+
", esperando o dia. N\xE3o gera contrato nem proposta."
|
|
33713
|
+
] }),
|
|
33714
|
+
escolhidos.length > 0 && /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-lg border", children: escolhidos.map((e) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
|
|
33715
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs", children: e.name }),
|
|
33716
|
+
/* @__PURE__ */ jsx(
|
|
33717
|
+
"input",
|
|
33718
|
+
{
|
|
33719
|
+
type: "number",
|
|
33720
|
+
min: 1,
|
|
33721
|
+
value: e.qtd,
|
|
33722
|
+
onChange: (ev) => setEscolhidos((xs) => xs.map((x) => x.id === e.id ? { ...x, qtd: Math.max(1, Math.floor(Number(ev.target.value) || 1)) } : x)),
|
|
33723
|
+
className: "w-12 shrink-0 rounded-input border border-input bg-card px-1 py-0.5 text-center text-xs",
|
|
33724
|
+
"aria-label": `Quantidade de ${e.name}`
|
|
33725
|
+
}
|
|
33726
|
+
),
|
|
33727
|
+
/* @__PURE__ */ jsx("span", { className: "w-20 shrink-0 text-right text-xs tabular-nums", children: formatCurrency(e.price * e.qtd, currency) }),
|
|
33728
|
+
/* @__PURE__ */ jsx(
|
|
33729
|
+
"button",
|
|
33730
|
+
{
|
|
33731
|
+
type: "button",
|
|
33732
|
+
onClick: () => setEscolhidos((xs) => xs.filter((x) => x.id !== e.id)),
|
|
33733
|
+
className: "shrink-0 text-[11px] text-destructive hover:underline",
|
|
33734
|
+
children: "Tirar"
|
|
33735
|
+
}
|
|
33736
|
+
)
|
|
33737
|
+
] }, e.id)) }),
|
|
33738
|
+
/* @__PURE__ */ jsx(
|
|
33739
|
+
"input",
|
|
33740
|
+
{
|
|
33741
|
+
value: buscaServico,
|
|
33742
|
+
onChange: (e) => setBuscaServico(e.target.value),
|
|
33743
|
+
placeholder: "Buscar servi\xE7o\u2026",
|
|
33744
|
+
className: "w-full rounded-input border border-input bg-card px-2 py-1.5 text-xs focus:outline-none focus:ring-2 focus:ring-ring"
|
|
33745
|
+
}
|
|
33746
|
+
),
|
|
33747
|
+
/* @__PURE__ */ jsx("ul", { className: "max-h-40 space-y-0.5 overflow-y-auto", children: catalogo.filter((c) => !escolhidos.some((e) => e.id === c.id)).map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
33748
|
+
"button",
|
|
33749
|
+
{
|
|
33750
|
+
type: "button",
|
|
33751
|
+
onClick: () => setEscolhidos((xs) => [...xs, { ...c, qtd: 1 }]),
|
|
33752
|
+
className: "flex w-full items-center gap-2 rounded px-2 py-1 text-left hover:bg-muted",
|
|
33753
|
+
children: [
|
|
33754
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs", children: c.name }),
|
|
33755
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] tabular-nums text-muted-foreground", children: formatCurrency(c.price, currency) })
|
|
33756
|
+
]
|
|
33757
|
+
}
|
|
33758
|
+
) }, c.id)) }),
|
|
33759
|
+
erroCompra && /* @__PURE__ */ jsx("p", { className: "rounded-lg bg-destructive/10 px-2 py-1.5 text-[11px] text-destructive", children: erroCompra }),
|
|
33760
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 pt-1", children: [
|
|
33761
|
+
/* @__PURE__ */ jsx(
|
|
33762
|
+
"button",
|
|
33763
|
+
{
|
|
33764
|
+
type: "button",
|
|
33765
|
+
onClick: () => {
|
|
33766
|
+
setComprando(false);
|
|
33767
|
+
setEscolhidos([]);
|
|
33768
|
+
},
|
|
33769
|
+
className: "rounded-button px-2 py-1 text-xs text-muted-foreground hover:text-foreground",
|
|
33770
|
+
children: "Agora n\xE3o"
|
|
33771
|
+
}
|
|
33772
|
+
),
|
|
33773
|
+
/* @__PURE__ */ jsx(
|
|
33774
|
+
"button",
|
|
33775
|
+
{
|
|
33776
|
+
type: "button",
|
|
33777
|
+
onClick: confirmarCompra,
|
|
33778
|
+
disabled: comprandoBusy || !escolhidos.length,
|
|
33779
|
+
className: "rounded-button bg-foreground px-3 py-1.5 text-xs font-semibold text-background disabled:opacity-40",
|
|
33780
|
+
children: comprandoBusy ? "Registrando\u2026" : `Registrar \xB7 ${formatCurrency(
|
|
33781
|
+
escolhidos.reduce((t2, e) => t2 + e.price * e.qtd, 0),
|
|
33782
|
+
currency
|
|
33783
|
+
)}`
|
|
33784
|
+
}
|
|
33785
|
+
)
|
|
33786
|
+
] })
|
|
33787
|
+
] }),
|
|
33788
|
+
onGenerate && !hasForThis && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 rounded-xl border border-dashed px-3 py-3", children: [
|
|
32094
33789
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
32095
33790
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: "Este atendimento ainda n\xE3o tem cobran\xE7a" }),
|
|
32096
33791
|
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "Gere o recebimento para cobrar os servi\xE7os deste atendimento." })
|
|
@@ -32138,16 +33833,19 @@ function ClientAccountPanel({
|
|
|
32138
33833
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold tabular-nums", children: formatCurrency(i.total, currency) }),
|
|
32139
33834
|
/* @__PURE__ */ jsx("p", { className: `text-[10px] tabular-nums ${i.balance > 0 ? "text-destructive" : "text-success"}`, children: i.balance > 0 ? `resta ${formatCurrency(i.balance, currency)}` : "pago" })
|
|
32140
33835
|
] }),
|
|
32141
|
-
|
|
33836
|
+
/* @__PURE__ */ jsx(
|
|
32142
33837
|
"button",
|
|
32143
33838
|
{
|
|
32144
33839
|
type: "button",
|
|
32145
|
-
onClick: () =>
|
|
33840
|
+
onClick: () => setViewInvoice(i),
|
|
32146
33841
|
className: "inline-flex shrink-0 items-center gap-1 rounded-button border border-input px-2.5 py-1.5 text-xs font-medium hover:bg-muted",
|
|
32147
|
-
children: [
|
|
33842
|
+
children: i.balance > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
32148
33843
|
/* @__PURE__ */ jsx(CircleDollarSign, { className: "h-3.5 w-3.5" }),
|
|
32149
33844
|
" Receber"
|
|
32150
|
-
]
|
|
33845
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
33846
|
+
/* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5" }),
|
|
33847
|
+
" Ver fatura"
|
|
33848
|
+
] })
|
|
32151
33849
|
}
|
|
32152
33850
|
)
|
|
32153
33851
|
]
|
|
@@ -32156,19 +33854,44 @@ function ClientAccountPanel({
|
|
|
32156
33854
|
);
|
|
32157
33855
|
}) })
|
|
32158
33856
|
] }),
|
|
32159
|
-
|
|
32160
|
-
|
|
33857
|
+
viewInvoice && /* @__PURE__ */ jsx(
|
|
33858
|
+
ModalTask,
|
|
32161
33859
|
{
|
|
32162
|
-
|
|
32163
|
-
|
|
32164
|
-
|
|
32165
|
-
|
|
33860
|
+
open: true,
|
|
33861
|
+
title: /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-0 items-center gap-2", children: [
|
|
33862
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: viewInvoice.number ?? "Recebimento" }),
|
|
33863
|
+
(() => {
|
|
33864
|
+
const s = STATUS_CHIP[viewInvoice.status];
|
|
33865
|
+
return s ? /* @__PURE__ */ jsx("span", { className: `shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium ${s.cls}`, children: s.label }) : null;
|
|
33866
|
+
})()
|
|
33867
|
+
] }),
|
|
33868
|
+
hint: clientName ?? void 0,
|
|
33869
|
+
width: "max-w-3xl",
|
|
33870
|
+
onClose: () => {
|
|
33871
|
+
setViewInvoice(null);
|
|
32166
33872
|
void load();
|
|
32167
|
-
}
|
|
33873
|
+
},
|
|
33874
|
+
children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-3", children: /* @__PURE__ */ jsx(
|
|
33875
|
+
InvoiceDetailView,
|
|
33876
|
+
{
|
|
33877
|
+
embedded: true,
|
|
33878
|
+
invoiceId: viewInvoice.id,
|
|
33879
|
+
direction: "credit",
|
|
33880
|
+
onBack: () => {
|
|
33881
|
+
setViewInvoice(null);
|
|
33882
|
+
void load();
|
|
33883
|
+
}
|
|
33884
|
+
}
|
|
33885
|
+
) })
|
|
32168
33886
|
}
|
|
32169
33887
|
)
|
|
32170
33888
|
] });
|
|
32171
33889
|
}
|
|
33890
|
+
function ClientAccountInline(props) {
|
|
33891
|
+
const provider = React15__default.useMemo(() => createSafeFinancialProvider(), []);
|
|
33892
|
+
const store6 = React15__default.useMemo(() => createFinancialStore(provider), [provider]);
|
|
33893
|
+
return /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(ClientAccountPanel, { ...props }) });
|
|
33894
|
+
}
|
|
32172
33895
|
function ClientAccountModal({
|
|
32173
33896
|
personId,
|
|
32174
33897
|
orderId,
|
|
@@ -32176,17 +33899,15 @@ function ClientAccountModal({
|
|
|
32176
33899
|
onGenerate,
|
|
32177
33900
|
onClose
|
|
32178
33901
|
}) {
|
|
32179
|
-
|
|
32180
|
-
|
|
32181
|
-
return /* @__PURE__ */ jsx(RightRailPage, { title: "Conta do cliente", open: true, onClose, width: "max-w-md", children: /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(FinancialContextProvider, { config: STANDALONE_CONFIG, provider, store: store6, children: /* @__PURE__ */ jsx(
|
|
32182
|
-
ClientAccountPanel,
|
|
33902
|
+
return /* @__PURE__ */ jsx(RightRailPage, { title: "Conta do cliente", open: true, onClose, width: "max-w-md", children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-3", children: /* @__PURE__ */ jsx(
|
|
33903
|
+
ClientAccountInline,
|
|
32183
33904
|
{
|
|
32184
33905
|
personId,
|
|
32185
33906
|
orderId,
|
|
32186
33907
|
clientName,
|
|
32187
33908
|
onGenerate
|
|
32188
33909
|
}
|
|
32189
|
-
) }) })
|
|
33910
|
+
) }) });
|
|
32190
33911
|
}
|
|
32191
33912
|
|
|
32192
33913
|
// src/index.ts
|
|
@@ -32551,7 +34272,7 @@ function createFinancialPlugin(options) {
|
|
|
32551
34272
|
component: (() => {
|
|
32552
34273
|
const FinancialSettingsTab = () => React15__default.createElement(PluginSettingsPanel, {
|
|
32553
34274
|
title: "Financial Settings",
|
|
32554
|
-
generalSettings: React15__default.createElement(FinancialGeneralSettings),
|
|
34275
|
+
generalSettings: React15__default.createElement(FinancialGeneralSettings, { config }),
|
|
32555
34276
|
registries,
|
|
32556
34277
|
hostPluginId: "financial",
|
|
32557
34278
|
routeBase: "/settings/financial"
|
|
@@ -32613,6 +34334,6 @@ function createFinancialPlugin(options) {
|
|
|
32613
34334
|
};
|
|
32614
34335
|
}
|
|
32615
34336
|
|
|
32616
|
-
export { ClientAccountModal, ClientAccountPanel, FINANCIAL_NAV_FEATURES, ReceivePaymentModal, buildFinancialDomainEvents, buildFinancialMessageDefaults, createFinancialPlugin, createFinancialSettingsPlugin, createMockFinancialProvider, createSafeFinancialProvider };
|
|
34337
|
+
export { ClientAccountInline, ClientAccountModal, ClientAccountPanel, FINANCIAL_NAV_FEATURES, ReceivePaymentModal, buildFinancialDomainEvents, buildFinancialMessageDefaults, createFinancialPlugin, createFinancialSettingsPlugin, createMockFinancialProvider, createSafeFinancialProvider };
|
|
32617
34338
|
//# sourceMappingURL=index.js.map
|
|
32618
34339
|
//# sourceMappingURL=index.js.map
|