mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-06-09 23:06:01 +00:00
feat(lancamentos): aprimora parcelamentos e protecoes
This commit is contained in:
@@ -43,6 +43,15 @@ type PageProps = {
|
|||||||
const capitalize = (value: string) =>
|
const capitalize = (value: string) =>
|
||||||
value.length > 0 ? value[0]?.toUpperCase().concat(value.slice(1)) : value;
|
value.length > 0 ? value[0]?.toUpperCase().concat(value.slice(1)) : value;
|
||||||
|
|
||||||
|
const resolveDefaultPaymentMethod = (
|
||||||
|
accountType: string | null | undefined,
|
||||||
|
) => {
|
||||||
|
if (accountType === "Dinheiro") return "Dinheiro";
|
||||||
|
if (accountType === "Pré-Pago | VR/VA") return "Pré-Pago | VR/VA";
|
||||||
|
|
||||||
|
return "Pix";
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Page({ params, searchParams }: PageProps) {
|
export default async function Page({ params, searchParams }: PageProps) {
|
||||||
await connection();
|
await connection();
|
||||||
const { accountId } = await params;
|
const { accountId } = await params;
|
||||||
@@ -197,7 +206,11 @@ export default async function Page({ params, searchParams }: PageProps) {
|
|||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
settledOnly: true,
|
settledOnly: true,
|
||||||
}}
|
}}
|
||||||
allowCreate={false}
|
allowCreate
|
||||||
|
defaultAccountId={account.id}
|
||||||
|
defaultPaymentMethod={resolveDefaultPaymentMethod(
|
||||||
|
account.accountType,
|
||||||
|
)}
|
||||||
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
|
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
|
||||||
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
|
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
|
||||||
attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50}
|
attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export function InstallmentAnalysisPage({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{/* Card de resumo principal */}
|
{/* Card de resumo principal */}
|
||||||
<Card className="border-none bg-primary/10 dark:bg-primary/10">
|
<Card className="border-none bg-primary/10 shadow-none">
|
||||||
<CardContent className="flex flex-col items-start justify-center gap-2 py-2">
|
<CardContent className="flex flex-col items-start justify-center gap-2 py-2">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Se você pagar tudo que está selecionado:
|
Se você pagar tudo que está selecionado:
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ export function InstallmentGroupCard({
|
|||||||
const hasSelection = selectedInstallments.size > 0;
|
const hasSelection = selectedInstallments.size > 0;
|
||||||
|
|
||||||
const progress =
|
const progress =
|
||||||
group.totalInstallments > 0
|
group.trackedInstallments > 0
|
||||||
? (group.paidInstallments / group.totalInstallments) * 100
|
? (group.paidInstallments / group.trackedInstallments) * 100
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const selectedAmount = group.pendingInstallments
|
const selectedAmount = group.pendingInstallments
|
||||||
@@ -83,6 +83,10 @@ export function InstallmentGroupCard({
|
|||||||
);
|
);
|
||||||
const cardLogoSrc = resolveLogoSrc(group.cartaoLogo);
|
const cardLogoSrc = resolveLogoSrc(group.cartaoLogo);
|
||||||
const cardName = group.cartaoName ?? "Compra parcelada";
|
const cardName = group.cartaoName ?? "Compra parcelada";
|
||||||
|
const untrackedLabel =
|
||||||
|
group.untrackedInstallments === 1
|
||||||
|
? "1 parcela anterior fora do acompanhamento"
|
||||||
|
: `${group.untrackedInstallments} parcelas anteriores fora do acompanhamento`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -153,7 +157,7 @@ export function InstallmentGroupCard({
|
|||||||
<div className="grid grid-cols-2 gap-4 p-4 rounded-lg bg-primary/5 mb-4">
|
<div className="grid grid-cols-2 gap-4 p-4 rounded-lg bg-primary/5 mb-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-xs text-muted-foreground font-medium">
|
<p className="text-xs text-muted-foreground font-medium">
|
||||||
Valor total
|
Valor acompanhado
|
||||||
</p>
|
</p>
|
||||||
<MoneyValues
|
<MoneyValues
|
||||||
amount={totalAmount}
|
amount={totalAmount}
|
||||||
@@ -180,8 +184,8 @@ export function InstallmentGroupCard({
|
|||||||
<div className="flex items-center gap-1 text-muted-foreground">
|
<div className="flex items-center gap-1 text-muted-foreground">
|
||||||
<RiCheckboxCircleFill className="size-3.5 text-success" />
|
<RiCheckboxCircleFill className="size-3.5 text-success" />
|
||||||
<span>
|
<span>
|
||||||
{group.paidInstallments} de {group.totalInstallments} parcelas
|
{group.paidInstallments} de {group.trackedInstallments}{" "}
|
||||||
pagas
|
parcelas acompanhadas pagas
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{unpaidCount > 0 && (
|
{unpaidCount > 0 && (
|
||||||
@@ -198,6 +202,9 @@ export function InstallmentGroupCard({
|
|||||||
className="h-2.5 bg-muted"
|
className="h-2.5 bg-muted"
|
||||||
indicatorClassName="bg-success"
|
indicatorClassName="bg-success"
|
||||||
/>
|
/>
|
||||||
|
{group.untrackedInstallments > 0 && (
|
||||||
|
<p className="text-xs text-muted-foreground">{untrackedLabel}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Valor selecionado */}
|
{/* Valor selecionado */}
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ export type InstallmentGroup = {
|
|||||||
cartaoDueDay: string | null;
|
cartaoDueDay: string | null;
|
||||||
cartaoLogo: string | null;
|
cartaoLogo: string | null;
|
||||||
totalInstallments: number;
|
totalInstallments: number;
|
||||||
|
trackedStartInstallment: number;
|
||||||
|
trackedInstallments: number;
|
||||||
|
untrackedInstallments: number;
|
||||||
paidInstallments: number;
|
paidInstallments: number;
|
||||||
pendingInstallments: InstallmentDetail[];
|
pendingInstallments: InstallmentDetail[];
|
||||||
totalPendingAmount: number;
|
totalPendingAmount: number;
|
||||||
@@ -153,6 +156,12 @@ export async function fetchInstallmentAnalysis(
|
|||||||
cartaoDueDay: row.cartaoDueDay,
|
cartaoDueDay: row.cartaoDueDay,
|
||||||
cartaoLogo: row.cartaoLogo,
|
cartaoLogo: row.cartaoLogo,
|
||||||
totalInstallments: row.installmentCount ?? 0,
|
totalInstallments: row.installmentCount ?? 0,
|
||||||
|
trackedStartInstallment: installmentDetail.currentInstallment,
|
||||||
|
trackedInstallments: 1,
|
||||||
|
untrackedInstallments: Math.max(
|
||||||
|
0,
|
||||||
|
installmentDetail.currentInstallment - 1,
|
||||||
|
),
|
||||||
paidInstallments: 0,
|
paidInstallments: 0,
|
||||||
pendingInstallments: [installmentDetail],
|
pendingInstallments: [installmentDetail],
|
||||||
totalPendingAmount: amount,
|
totalPendingAmount: amount,
|
||||||
@@ -168,7 +177,13 @@ export async function fetchInstallmentAnalysis(
|
|||||||
const paidCount = group.pendingInstallments.filter(
|
const paidCount = group.pendingInstallments.filter(
|
||||||
(i) => i.isSettled,
|
(i) => i.isSettled,
|
||||||
).length;
|
).length;
|
||||||
|
const trackedStartInstallment = Math.min(
|
||||||
|
...group.pendingInstallments.map((i) => i.currentInstallment),
|
||||||
|
);
|
||||||
group.paidInstallments = paidCount;
|
group.paidInstallments = paidCount;
|
||||||
|
group.trackedStartInstallment = trackedStartInstallment;
|
||||||
|
group.trackedInstallments = group.pendingInstallments.length;
|
||||||
|
group.untrackedInstallments = Math.max(0, trackedStartInstallment - 1);
|
||||||
return group;
|
return group;
|
||||||
})
|
})
|
||||||
// Filtrar apenas séries que têm pelo menos uma parcela em aberto (não paga)
|
// Filtrar apenas séries que têm pelo menos uma parcela em aberto (não paga)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
TRANSACTION_CONDITIONS,
|
TRANSACTION_CONDITIONS,
|
||||||
TRANSACTION_TYPES,
|
TRANSACTION_TYPES,
|
||||||
} from "@/features/transactions/lib/constants";
|
} from "@/features/transactions/lib/constants";
|
||||||
|
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
||||||
import { handleActionError } from "@/shared/lib/actions/helpers";
|
import { handleActionError } from "@/shared/lib/actions/helpers";
|
||||||
import { getUser } from "@/shared/lib/auth/server";
|
import { getUser } from "@/shared/lib/auth/server";
|
||||||
import { db } from "@/shared/lib/db";
|
import { db } from "@/shared/lib/db";
|
||||||
@@ -30,6 +31,7 @@ import {
|
|||||||
fetchOwnedPayerIds,
|
fetchOwnedPayerIds,
|
||||||
formatPaidInvoicePeriods,
|
formatPaidInvoicePeriods,
|
||||||
getPaidInvoicePeriods,
|
getPaidInvoicePeriods,
|
||||||
|
isInitialBalanceTransaction,
|
||||||
type MassAddInput,
|
type MassAddInput,
|
||||||
massAddSchema,
|
massAddSchema,
|
||||||
resolvePeriod,
|
resolvePeriod,
|
||||||
@@ -47,6 +49,19 @@ const getPeriodOffset = (basePeriod: string, targetPeriod: string) => {
|
|||||||
return (target.year - base.year) * 12 + (target.month - base.month);
|
return (target.year - base.year) * 12 + (target.month - base.month);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ProtectedTransactionCandidate = {
|
||||||
|
note: string | null;
|
||||||
|
transactionType: string | null;
|
||||||
|
condition: string | null;
|
||||||
|
paymentMethod: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isProtectedTransaction = (
|
||||||
|
record: ProtectedTransactionCandidate,
|
||||||
|
): boolean =>
|
||||||
|
Boolean(record.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) ||
|
||||||
|
isInitialBalanceTransaction(record);
|
||||||
|
|
||||||
export async function deleteTransactionBulkAction(
|
export async function deleteTransactionBulkAction(
|
||||||
input: DeleteBulkInput,
|
input: DeleteBulkInput,
|
||||||
): Promise<ActionResult> {
|
): Promise<ActionResult> {
|
||||||
@@ -61,6 +76,9 @@ export async function deleteTransactionBulkAction(
|
|||||||
seriesId: true,
|
seriesId: true,
|
||||||
period: true,
|
period: true,
|
||||||
condition: true,
|
condition: true,
|
||||||
|
transactionType: true,
|
||||||
|
paymentMethod: true,
|
||||||
|
note: true,
|
||||||
},
|
},
|
||||||
where: and(
|
where: and(
|
||||||
eq(transactions.id, data.id),
|
eq(transactions.id, data.id),
|
||||||
@@ -79,6 +97,13 @@ export async function deleteTransactionBulkAction(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProtectedTransaction(existing)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Lançamentos protegidos não podem ser removidos em massa.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let scopeFilter: ReturnType<typeof and>;
|
let scopeFilter: ReturnType<typeof and>;
|
||||||
let successMessage: string;
|
let successMessage: string;
|
||||||
|
|
||||||
@@ -171,6 +196,7 @@ export async function updateTransactionBulkAction(
|
|||||||
purchaseDate: true,
|
purchaseDate: true,
|
||||||
payerId: true,
|
payerId: true,
|
||||||
cardId: true,
|
cardId: true,
|
||||||
|
note: true,
|
||||||
},
|
},
|
||||||
where: and(
|
where: and(
|
||||||
eq(transactions.id, data.id),
|
eq(transactions.id, data.id),
|
||||||
@@ -189,6 +215,13 @@ export async function updateTransactionBulkAction(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProtectedTransaction(existing)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Lançamentos protegidos não podem ser atualizados em massa.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const baseUpdatePayload: Record<string, unknown> = {
|
const baseUpdatePayload: Record<string, unknown> = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
categoryId: data.categoryId ?? null,
|
categoryId: data.categoryId ?? null,
|
||||||
@@ -753,6 +786,13 @@ export async function deleteMultipleTransactionsAction(
|
|||||||
return { success: false, error: "Nenhum lançamento encontrado." };
|
return { success: false, error: "Nenhum lançamento encontrado." };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existing.some(isProtectedTransaction)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Lançamentos protegidos não podem ser removidos em massa.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const linkedAttachments = await db
|
const linkedAttachments = await db
|
||||||
.select({ id: attachments.id, fileKey: attachments.fileKey })
|
.select({ id: attachments.id, fileKey: attachments.fileKey })
|
||||||
.from(transactionAttachments)
|
.from(transactionAttachments)
|
||||||
|
|||||||
@@ -335,6 +335,12 @@ const baseFields = z.object({
|
|||||||
.min(1, "Selecione uma quantidade válida.")
|
.min(1, "Selecione uma quantidade válida.")
|
||||||
.max(60, "Selecione uma quantidade válida.")
|
.max(60, "Selecione uma quantidade válida.")
|
||||||
.optional(),
|
.optional(),
|
||||||
|
startInstallment: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(1, "Selecione uma parcela válida.")
|
||||||
|
.max(60, "Selecione uma parcela válida.")
|
||||||
|
.optional(),
|
||||||
recurrenceCount: z.coerce
|
recurrenceCount: z.coerce
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
@@ -415,6 +421,15 @@ const refineLancamento = (
|
|||||||
path: ["installmentCount"],
|
path: ["installmentCount"],
|
||||||
message: "Selecione pelo menos duas parcelas.",
|
message: "Selecione pelo menos duas parcelas.",
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
data.startInstallment &&
|
||||||
|
data.startInstallment > data.installmentCount
|
||||||
|
) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["startInstallment"],
|
||||||
|
message: "A parcela inicial não pode ser maior que o total.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,24 +666,27 @@ export const buildTransactionRecords = ({
|
|||||||
|
|
||||||
if (data.condition === "Parcelado") {
|
if (data.condition === "Parcelado") {
|
||||||
const installmentTotal = data.installmentCount ?? 0;
|
const installmentTotal = data.installmentCount ?? 0;
|
||||||
|
const startInstallment = data.startInstallment ?? 1;
|
||||||
const amountsByShare = shares.map((share) =>
|
const amountsByShare = shares.map((share) =>
|
||||||
splitAmount(share.amountCents, installmentTotal),
|
splitAmount(share.amountCents, installmentTotal),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (
|
for (
|
||||||
let installment = 0;
|
let index = 0;
|
||||||
installment < installmentTotal;
|
index <= installmentTotal - startInstallment;
|
||||||
installment += 1
|
index += 1
|
||||||
) {
|
) {
|
||||||
const installmentPeriod = addMonthsToPeriod(period, installment);
|
const currentInstallment = startInstallment + index;
|
||||||
|
const installmentPeriod = addMonthsToPeriod(period, index);
|
||||||
const installmentDueDate = dueDate
|
const installmentDueDate = dueDate
|
||||||
? addMonthsToDate(dueDate, installment)
|
? addMonthsToDate(dueDate, index)
|
||||||
: null;
|
: null;
|
||||||
const splitGroupId = cycleSplitGroupId();
|
const splitGroupId = cycleSplitGroupId();
|
||||||
|
|
||||||
shares.forEach((share, shareIndex) => {
|
shares.forEach((share, shareIndex) => {
|
||||||
const amountCents = amountsByShare[shareIndex]?.[installment] ?? 0;
|
const amountCents =
|
||||||
const settled = resolveSettledValue(installment);
|
amountsByShare[shareIndex]?.[currentInstallment - 1] ?? 0;
|
||||||
|
const settled = resolveSettledValue(index);
|
||||||
records.push({
|
records.push({
|
||||||
...basePayload,
|
...basePayload,
|
||||||
amount: centsToDecimalString(amountCents * amountSign),
|
amount: centsToDecimalString(amountCents * amountSign),
|
||||||
@@ -677,7 +695,7 @@ export const buildTransactionRecords = ({
|
|||||||
period: installmentPeriod,
|
period: installmentPeriod,
|
||||||
isSettled: settled,
|
isSettled: settled,
|
||||||
installmentCount: installmentTotal,
|
installmentCount: installmentTotal,
|
||||||
currentInstallment: installment + 1,
|
currentInstallment,
|
||||||
recurrenceCount: null,
|
recurrenceCount: null,
|
||||||
dueDate: installmentDueDate,
|
dueDate: installmentDueDate,
|
||||||
splitGroupId,
|
splitGroupId,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
transactionAttachments,
|
transactionAttachments,
|
||||||
transactions,
|
transactions,
|
||||||
} from "@/db/schema";
|
} from "@/db/schema";
|
||||||
|
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
||||||
import { handleActionError } from "@/shared/lib/actions/helpers";
|
import { handleActionError } from "@/shared/lib/actions/helpers";
|
||||||
import { getUser } from "@/shared/lib/auth/server";
|
import { getUser } from "@/shared/lib/auth/server";
|
||||||
import { db } from "@/shared/lib/db";
|
import { db } from "@/shared/lib/db";
|
||||||
@@ -230,13 +231,6 @@ export async function updateTransactionAction(
|
|||||||
eq(transactions.id, data.id),
|
eq(transactions.id, data.id),
|
||||||
eq(transactions.userId, user.id),
|
eq(transactions.userId, user.id),
|
||||||
),
|
),
|
||||||
with: {
|
|
||||||
category: {
|
|
||||||
columns: {
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})) as
|
})) as
|
||||||
| {
|
| {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -248,7 +242,6 @@ export async function updateTransactionAction(
|
|||||||
accountId: string | null;
|
accountId: string | null;
|
||||||
cardId: string | null;
|
cardId: string | null;
|
||||||
categoryId: string | null;
|
categoryId: string | null;
|
||||||
category: { name: string } | null;
|
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
@@ -256,14 +249,17 @@ export async function updateTransactionAction(
|
|||||||
return { success: false, error: "Lançamento não encontrado." };
|
return { success: false, error: "Lançamento não encontrado." };
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoriasProtegidasEdicao = ["Saldo inicial", "Pagamentos"];
|
if (existing.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) {
|
||||||
if (
|
|
||||||
existing.category?.name &&
|
|
||||||
categoriasProtegidasEdicao.includes(existing.category.name)
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Lançamentos com a categoria '${existing.category.name}' não podem ser editados.`,
|
error: "Pagamentos automáticos de fatura não podem ser editados.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInitialBalanceTransaction(existing)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Lançamentos de saldo inicial não podem ser editados.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,13 +387,6 @@ export async function deleteTransactionAction(
|
|||||||
eq(transactions.id, data.id),
|
eq(transactions.id, data.id),
|
||||||
eq(transactions.userId, user.id),
|
eq(transactions.userId, user.id),
|
||||||
),
|
),
|
||||||
with: {
|
|
||||||
category: {
|
|
||||||
columns: {
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})) as
|
})) as
|
||||||
| {
|
| {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -411,7 +400,6 @@ export async function deleteTransactionAction(
|
|||||||
period: string;
|
period: string;
|
||||||
note: string | null;
|
note: string | null;
|
||||||
categoryId: string | null;
|
categoryId: string | null;
|
||||||
category: { name: string } | null;
|
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
@@ -419,14 +407,17 @@ export async function deleteTransactionAction(
|
|||||||
return { success: false, error: "Lançamento não encontrado." };
|
return { success: false, error: "Lançamento não encontrado." };
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoriasProtegidasRemocao = ["Saldo inicial", "Pagamentos"];
|
if (existing.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) {
|
||||||
if (
|
|
||||||
existing.category?.name &&
|
|
||||||
categoriasProtegidasRemocao.includes(existing.category.name)
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Lançamentos com a categoria '${existing.category.name}' não podem ser removidos.`,
|
error: "Pagamentos automáticos de fatura não podem ser removidos.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInitialBalanceTransaction(existing)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Lançamentos de saldo inicial não podem ser removidos.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import { TRANSACTION_CONDITIONS } from "@/features/transactions/lib/constants";
|
import { TRANSACTION_CONDITIONS } from "@/features/transactions/lib/constants";
|
||||||
import { Label } from "@/shared/components/ui/label";
|
import { Label } from "@/shared/components/ui/label";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/shared/components/ui/popover";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -14,6 +20,61 @@ import { cn } from "@/shared/utils/ui";
|
|||||||
import { ConditionSelectContent } from "../../select-items";
|
import { ConditionSelectContent } from "../../select-items";
|
||||||
import type { ConditionSectionProps } from "./transaction-dialog-types";
|
import type { ConditionSectionProps } from "./transaction-dialog-types";
|
||||||
|
|
||||||
|
function InlineStartInstallmentPicker({
|
||||||
|
value,
|
||||||
|
options,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
options: number[];
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const selected = Number(value || "1");
|
||||||
|
const selectedLabel =
|
||||||
|
!Number.isNaN(selected) && selected > 0
|
||||||
|
? `${selected}ª parcela`
|
||||||
|
: "1ª parcela";
|
||||||
|
const disabled = options.length === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ml-1">
|
||||||
|
<span className="text-xs text-muted-foreground">Começar em </span>
|
||||||
|
<Popover modal open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer text-xs text-primary underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:text-muted-foreground disabled:hover:no-underline"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{selectedLabel}
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-40 p-1" align="start">
|
||||||
|
<div className="max-h-56 overflow-y-auto">
|
||||||
|
{options.map((option) => (
|
||||||
|
<button
|
||||||
|
key={option}
|
||||||
|
type="button"
|
||||||
|
className={cn(
|
||||||
|
"flex w-full items-center rounded-sm px-2 py-1.5 text-left text-sm hover:bg-accent hover:text-accent-foreground",
|
||||||
|
option === selected && "font-medium text-primary",
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
onChange(String(option));
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option}ª parcela
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function ConditionSection({
|
export function ConditionSection({
|
||||||
formState,
|
formState,
|
||||||
onFieldChange,
|
onFieldChange,
|
||||||
@@ -37,11 +98,17 @@ export function ConditionSection({
|
|||||||
const installmentSummary =
|
const installmentSummary =
|
||||||
showInstallments &&
|
showInstallments &&
|
||||||
formState.installmentCount &&
|
formState.installmentCount &&
|
||||||
amount &&
|
|
||||||
!Number.isNaN(installmentCount) &&
|
!Number.isNaN(installmentCount) &&
|
||||||
installmentCount > 0
|
installmentCount > 0
|
||||||
? getInstallmentLabel(installmentCount)
|
? getInstallmentLabel(installmentCount)
|
||||||
: null;
|
: null;
|
||||||
|
const startInstallmentOptions =
|
||||||
|
showInstallments &&
|
||||||
|
formState.installmentCount &&
|
||||||
|
!Number.isNaN(installmentCount) &&
|
||||||
|
installmentCount > 0
|
||||||
|
? Array.from({ length: installmentCount }, (_, index) => index + 1)
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||||
@@ -96,6 +163,11 @@ export function ConditionSection({
|
|||||||
})}
|
})}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
<InlineStartInstallmentPicker
|
||||||
|
value={formState.startInstallment}
|
||||||
|
options={startInstallmentOptions}
|
||||||
|
onChange={(value) => onFieldChange("startInstallment", value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export interface TransactionDialogProps {
|
|||||||
estabelecimentos: string[];
|
estabelecimentos: string[];
|
||||||
transaction?: TransactionItem;
|
transaction?: TransactionItem;
|
||||||
defaultPeriod?: string;
|
defaultPeriod?: string;
|
||||||
|
defaultAccountId?: string | null;
|
||||||
defaultCardId?: string | null;
|
defaultCardId?: string | null;
|
||||||
defaultPaymentMethod?: string | null;
|
defaultPaymentMethod?: string | null;
|
||||||
defaultPurchaseDate?: string | null;
|
defaultPurchaseDate?: string | null;
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export function TransactionDialog({
|
|||||||
estabelecimentos,
|
estabelecimentos,
|
||||||
transaction,
|
transaction,
|
||||||
defaultPeriod,
|
defaultPeriod,
|
||||||
|
defaultAccountId,
|
||||||
defaultCardId,
|
defaultCardId,
|
||||||
defaultPaymentMethod,
|
defaultPaymentMethod,
|
||||||
defaultPurchaseDate,
|
defaultPurchaseDate,
|
||||||
@@ -88,6 +89,7 @@ export function TransactionDialog({
|
|||||||
|
|
||||||
const [formState, setFormState] = useState<FormState>(() =>
|
const [formState, setFormState] = useState<FormState>(() =>
|
||||||
buildTransactionInitialState(transaction, defaultPayerId, defaultPeriod, {
|
buildTransactionInitialState(transaction, defaultPayerId, defaultPeriod, {
|
||||||
|
defaultAccountId,
|
||||||
defaultCardId,
|
defaultCardId,
|
||||||
defaultPaymentMethod,
|
defaultPaymentMethod,
|
||||||
defaultPurchaseDate,
|
defaultPurchaseDate,
|
||||||
@@ -112,6 +114,7 @@ export function TransactionDialog({
|
|||||||
defaultPayerId,
|
defaultPayerId,
|
||||||
defaultPeriod,
|
defaultPeriod,
|
||||||
{
|
{
|
||||||
|
defaultAccountId,
|
||||||
defaultCardId,
|
defaultCardId,
|
||||||
defaultPaymentMethod,
|
defaultPaymentMethod,
|
||||||
defaultPurchaseDate,
|
defaultPurchaseDate,
|
||||||
@@ -151,6 +154,7 @@ export function TransactionDialog({
|
|||||||
transaction,
|
transaction,
|
||||||
defaultPayerId,
|
defaultPayerId,
|
||||||
defaultPeriod,
|
defaultPeriod,
|
||||||
|
defaultAccountId,
|
||||||
defaultCardId,
|
defaultCardId,
|
||||||
defaultPaymentMethod,
|
defaultPaymentMethod,
|
||||||
defaultPurchaseDate,
|
defaultPurchaseDate,
|
||||||
@@ -327,6 +331,12 @@ export function TransactionDialog({
|
|||||||
formState.condition === "Parcelado" && formState.installmentCount
|
formState.condition === "Parcelado" && formState.installmentCount
|
||||||
? Number(formState.installmentCount)
|
? Number(formState.installmentCount)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
startInstallment:
|
||||||
|
mode === "create" &&
|
||||||
|
formState.condition === "Parcelado" &&
|
||||||
|
formState.startInstallment
|
||||||
|
? Number(formState.startInstallment)
|
||||||
|
: undefined,
|
||||||
recurrenceCount:
|
recurrenceCount:
|
||||||
formState.condition === "Recorrente" && formState.recurrenceCount
|
formState.condition === "Recorrente" && formState.recurrenceCount
|
||||||
? Number(formState.recurrenceCount)
|
? Number(formState.recurrenceCount)
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ interface TransactionsPageProps {
|
|||||||
categoryFilterOptions: TransactionFilterOption[];
|
categoryFilterOptions: TransactionFilterOption[];
|
||||||
accountCardFilterOptions: AccountCardFilterOption[];
|
accountCardFilterOptions: AccountCardFilterOption[];
|
||||||
selectedPeriod: string;
|
selectedPeriod: string;
|
||||||
|
defaultAccountId?: string | null;
|
||||||
estabelecimentos: string[];
|
estabelecimentos: string[];
|
||||||
allowCreate?: boolean;
|
allowCreate?: boolean;
|
||||||
noteAsColumn?: boolean;
|
noteAsColumn?: boolean;
|
||||||
@@ -96,6 +97,7 @@ export function TransactionsPage({
|
|||||||
categoryFilterOptions,
|
categoryFilterOptions,
|
||||||
accountCardFilterOptions,
|
accountCardFilterOptions,
|
||||||
selectedPeriod,
|
selectedPeriod,
|
||||||
|
defaultAccountId,
|
||||||
estabelecimentos,
|
estabelecimentos,
|
||||||
allowCreate = true,
|
allowCreate = true,
|
||||||
noteAsColumn = false,
|
noteAsColumn = false,
|
||||||
@@ -562,6 +564,7 @@ export function TransactionsPage({
|
|||||||
categoryOptions={categoryOptions}
|
categoryOptions={categoryOptions}
|
||||||
estabelecimentos={estabelecimentos}
|
estabelecimentos={estabelecimentos}
|
||||||
defaultPeriod={selectedPeriod}
|
defaultPeriod={selectedPeriod}
|
||||||
|
defaultAccountId={defaultAccountId}
|
||||||
defaultCardId={defaultCardId}
|
defaultCardId={defaultCardId}
|
||||||
defaultPaymentMethod={defaultPaymentMethod}
|
defaultPaymentMethod={defaultPaymentMethod}
|
||||||
lockCardSelection={lockCardSelection}
|
lockCardSelection={lockCardSelection}
|
||||||
@@ -585,6 +588,7 @@ export function TransactionsPage({
|
|||||||
categoryOptions={categoryOptions}
|
categoryOptions={categoryOptions}
|
||||||
estabelecimentos={estabelecimentos}
|
estabelecimentos={estabelecimentos}
|
||||||
defaultPeriod={selectedPeriod}
|
defaultPeriod={selectedPeriod}
|
||||||
|
defaultAccountId={defaultAccountId}
|
||||||
defaultCardId={defaultCardId}
|
defaultCardId={defaultCardId}
|
||||||
defaultPaymentMethod={defaultPaymentMethod}
|
defaultPaymentMethod={defaultPaymentMethod}
|
||||||
lockCardSelection={lockCardSelection}
|
lockCardSelection={lockCardSelection}
|
||||||
@@ -648,6 +652,7 @@ export function TransactionsPage({
|
|||||||
estabelecimentos={estabelecimentos}
|
estabelecimentos={estabelecimentos}
|
||||||
transaction={transactionToCopy ?? undefined}
|
transaction={transactionToCopy ?? undefined}
|
||||||
defaultPeriod={selectedPeriod}
|
defaultPeriod={selectedPeriod}
|
||||||
|
defaultAccountId={defaultAccountId}
|
||||||
maxSizeMb={attachmentMaxSizeMb}
|
maxSizeMb={attachmentMaxSizeMb}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -669,6 +674,7 @@ export function TransactionsPage({
|
|||||||
estabelecimentos={estabelecimentos}
|
estabelecimentos={estabelecimentos}
|
||||||
transaction={transactionToImport ?? undefined}
|
transaction={transactionToImport ?? undefined}
|
||||||
defaultPeriod={selectedPeriod}
|
defaultPeriod={selectedPeriod}
|
||||||
|
defaultAccountId={defaultAccountId}
|
||||||
isImporting={true}
|
isImporting={true}
|
||||||
maxSizeMb={attachmentMaxSizeMb}
|
maxSizeMb={attachmentMaxSizeMb}
|
||||||
/>
|
/>
|
||||||
@@ -697,6 +703,7 @@ export function TransactionsPage({
|
|||||||
estabelecimentos={estabelecimentos}
|
estabelecimentos={estabelecimentos}
|
||||||
transaction={selectedTransaction ?? undefined}
|
transaction={selectedTransaction ?? undefined}
|
||||||
defaultPeriod={selectedPeriod}
|
defaultPeriod={selectedPeriod}
|
||||||
|
defaultAccountId={defaultAccountId}
|
||||||
onBulkEditRequest={handleBulkEditRequest}
|
onBulkEditRequest={handleBulkEditRequest}
|
||||||
onSplitEditRequest={handleSplitEditRequest}
|
onSplitEditRequest={handleSplitEditRequest}
|
||||||
maxSizeMb={attachmentMaxSizeMb}
|
maxSizeMb={attachmentMaxSizeMb}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiCalendarCheckLine, RiCloseLine, RiEyeLine } from "@remixicon/react";
|
import { RiCalendarCheckLine } from "@remixicon/react";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { ptBR } from "date-fns/locale";
|
import { ptBR } from "date-fns/locale";
|
||||||
import { useTransition } from "react";
|
import { useTransition } from "react";
|
||||||
@@ -164,16 +164,14 @@ export function AnticipationCard({
|
|||||||
onClick={handleViewLancamento}
|
onClick={handleViewLancamento}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
>
|
>
|
||||||
<RiEyeLine className="mr-2 size-4" />
|
Cancelar
|
||||||
Ver Lançamento
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{canCancel && (
|
{canCancel && (
|
||||||
<ConfirmActionDialog
|
<ConfirmActionDialog
|
||||||
trigger={
|
trigger={
|
||||||
<Button variant="destructive" size="sm" disabled={isPending}>
|
<Button variant="destructive" size="sm" disabled={isPending}>
|
||||||
<RiCloseLine className="mr-2 size-4" />
|
Desfazer Antecipação
|
||||||
Cancelar Antecipação
|
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
title="Cancelar antecipação?"
|
title="Cancelar antecipação?"
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ function buildColumns({
|
|||||||
const initial = displayName.charAt(0).toUpperCase() || "?";
|
const initial = displayName.charAt(0).toUpperCase() || "?";
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<Avatar className="size-7">
|
<Avatar className="size-8">
|
||||||
<AvatarImage src={avatarSrc} alt={`Avatar de ${label}`} />
|
<AvatarImage src={avatarSrc} alt={`Avatar de ${label}`} />
|
||||||
<AvatarFallback className="text-xs font-medium uppercase">
|
<AvatarFallback className="text-xs font-medium uppercase">
|
||||||
{initial}
|
{initial}
|
||||||
@@ -477,15 +477,21 @@ function buildColumns({
|
|||||||
const content = (
|
const content = (
|
||||||
<span className="inline-flex items-center gap-2">
|
<span className="inline-flex items-center gap-2">
|
||||||
{logoSrc && (
|
{logoSrc && (
|
||||||
<Image
|
<Avatar className="size-8">
|
||||||
src={logoSrc}
|
<AvatarImage src={logoSrc} alt={`Logo de ${label}`} />
|
||||||
alt={`Logo de ${label}`}
|
<AvatarFallback className="text-xs font-medium uppercase">
|
||||||
width={30}
|
{label}
|
||||||
height={30}
|
</AvatarFallback>
|
||||||
className="rounded-full"
|
</Avatar>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<span className="truncate">{label}</span>
|
<span
|
||||||
|
className={cn(
|
||||||
|
"truncate underline-offset-2",
|
||||||
|
isOwnData && href && "group-hover:underline",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -503,7 +509,7 @@ function buildColumns({
|
|||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Link href={href} className="hover:underline">
|
<Link href={href} className="group">
|
||||||
{content}
|
{content}
|
||||||
</Link>
|
</Link>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -654,14 +660,14 @@ function buildColumns({
|
|||||||
Editar
|
Editar
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{row.original.categoriaName !== "Pagamentos" &&
|
{!row.original.readonly &&
|
||||||
row.original.userId === currentUserId && (
|
row.original.userId === currentUserId && (
|
||||||
<DropdownMenuItem onSelect={() => handleCopy(row.original)}>
|
<DropdownMenuItem onSelect={() => handleCopy(row.original)}>
|
||||||
<RiFileCopyLine className="size-4" />
|
<RiFileCopyLine className="size-4" />
|
||||||
Copiar
|
Copiar
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{row.original.categoriaName !== "Pagamentos" &&
|
{!row.original.readonly &&
|
||||||
row.original.userId !== currentUserId && (
|
row.original.userId !== currentUserId && (
|
||||||
<DropdownMenuItem onSelect={() => handleImport(row.original)}>
|
<DropdownMenuItem onSelect={() => handleImport(row.original)}>
|
||||||
<RiFileCopyLine className="size-4" />
|
<RiFileCopyLine className="size-4" />
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export function TransactionsTable({
|
|||||||
: getPaginationRowModel(),
|
: getPaginationRowModel(),
|
||||||
manualPagination: isServerPaginated,
|
manualPagination: isServerPaginated,
|
||||||
pageCount: serverPagination?.totalPages,
|
pageCount: serverPagination?.totalPages,
|
||||||
enableRowSelection: true,
|
enableRowSelection: (row) => !row.original.readonly,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rowModel = table.getRowModel();
|
const rowModel = table.getRowModel();
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export type TransactionFormState = {
|
|||||||
cardId: string | undefined;
|
cardId: string | undefined;
|
||||||
categoryId: string | undefined;
|
categoryId: string | undefined;
|
||||||
installmentCount: string;
|
installmentCount: string;
|
||||||
|
startInstallment: string;
|
||||||
recurrenceCount: string;
|
recurrenceCount: string;
|
||||||
dueDate: string;
|
dueDate: string;
|
||||||
boletoPaymentDate: string;
|
boletoPaymentDate: string;
|
||||||
@@ -92,6 +93,7 @@ export type TransactionFormState = {
|
|||||||
*/
|
*/
|
||||||
type TransactionFormOverrides = {
|
type TransactionFormOverrides = {
|
||||||
defaultCardId?: string | null;
|
defaultCardId?: string | null;
|
||||||
|
defaultAccountId?: string | null;
|
||||||
defaultPaymentMethod?: string | null;
|
defaultPaymentMethod?: string | null;
|
||||||
defaultPurchaseDate?: string | null;
|
defaultPurchaseDate?: string | null;
|
||||||
defaultName?: string | null;
|
defaultName?: string | null;
|
||||||
@@ -178,7 +180,9 @@ export function buildTransactionInitialState(
|
|||||||
? undefined
|
? undefined
|
||||||
: isImporting
|
: isImporting
|
||||||
? undefined
|
? undefined
|
||||||
: (transaction?.accountId ?? undefined),
|
: (transaction?.accountId ??
|
||||||
|
overrides?.defaultAccountId ??
|
||||||
|
undefined),
|
||||||
cardId:
|
cardId:
|
||||||
paymentMethod === "Cartão de crédito"
|
paymentMethod === "Cartão de crédito"
|
||||||
? isImporting
|
? isImporting
|
||||||
@@ -191,6 +195,12 @@ export function buildTransactionInitialState(
|
|||||||
installmentCount: transaction?.installmentCount
|
installmentCount: transaction?.installmentCount
|
||||||
? String(transaction.installmentCount)
|
? String(transaction.installmentCount)
|
||||||
: "",
|
: "",
|
||||||
|
startInstallment:
|
||||||
|
isImporting &&
|
||||||
|
transaction?.condition === "Parcelado" &&
|
||||||
|
transaction.currentInstallment
|
||||||
|
? String(transaction.currentInstallment)
|
||||||
|
: "1",
|
||||||
recurrenceCount: transaction?.recurrenceCount
|
recurrenceCount: transaction?.recurrenceCount
|
||||||
? String(transaction.recurrenceCount)
|
? String(transaction.recurrenceCount)
|
||||||
: "",
|
: "",
|
||||||
@@ -252,12 +262,25 @@ export function applyFieldDependencies(
|
|||||||
if (key === "condition" && typeof value === "string") {
|
if (key === "condition" && typeof value === "string") {
|
||||||
if (value !== "Parcelado") {
|
if (value !== "Parcelado") {
|
||||||
updates.installmentCount = "";
|
updates.installmentCount = "";
|
||||||
|
updates.startInstallment = "1";
|
||||||
}
|
}
|
||||||
if (value !== "Recorrente") {
|
if (value !== "Recorrente") {
|
||||||
updates.recurrenceCount = "";
|
updates.recurrenceCount = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key === "installmentCount" && typeof value === "string" && value) {
|
||||||
|
const nextCount = Number.parseInt(value, 10);
|
||||||
|
const currentStart = Number.parseInt(currentState.startInstallment, 10);
|
||||||
|
if (
|
||||||
|
!Number.isNaN(nextCount) &&
|
||||||
|
!Number.isNaN(currentStart) &&
|
||||||
|
currentStart > nextCount
|
||||||
|
) {
|
||||||
|
updates.startInstallment = String(nextCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// When payment method changes, adjust related fields
|
// When payment method changes, adjust related fields
|
||||||
if (key === "paymentMethod" && typeof value === "string") {
|
if (key === "paymentMethod" && typeof value === "string") {
|
||||||
if (value === "Cartão de crédito") {
|
if (value === "Cartão de crédito") {
|
||||||
|
|||||||
@@ -27,7 +27,13 @@ import {
|
|||||||
TRANSACTION_CONDITIONS,
|
TRANSACTION_CONDITIONS,
|
||||||
TRANSACTION_TYPES,
|
TRANSACTION_TYPES,
|
||||||
} from "@/features/transactions/lib/constants";
|
} from "@/features/transactions/lib/constants";
|
||||||
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
import {
|
||||||
|
ACCOUNT_AUTO_INVOICE_NOTE_PREFIX,
|
||||||
|
INITIAL_BALANCE_CONDITION,
|
||||||
|
INITIAL_BALANCE_NOTE,
|
||||||
|
INITIAL_BALANCE_PAYMENT_METHOD,
|
||||||
|
INITIAL_BALANCE_TRANSACTION_TYPE,
|
||||||
|
} from "@/shared/lib/accounts/constants";
|
||||||
import {
|
import {
|
||||||
PAYER_ROLE_ADMIN,
|
PAYER_ROLE_ADMIN,
|
||||||
PAYER_ROLE_THIRD_PARTY,
|
PAYER_ROLE_THIRD_PARTY,
|
||||||
@@ -551,8 +557,10 @@ export const mapTransactionsData = (rows: TransactionRowWithRelations[]) =>
|
|||||||
hasAttachments: item.hasAttachments ?? false,
|
hasAttachments: item.hasAttachments ?? false,
|
||||||
readonly:
|
readonly:
|
||||||
Boolean(item.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) ||
|
Boolean(item.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) ||
|
||||||
item.category?.name === "Saldo inicial" ||
|
(item.note === INITIAL_BALANCE_NOTE &&
|
||||||
item.category?.name === "Pagamentos",
|
item.transactionType === INITIAL_BALANCE_TRANSACTION_TYPE &&
|
||||||
|
item.condition === INITIAL_BALANCE_CONDITION &&
|
||||||
|
item.paymentMethod === INITIAL_BALANCE_PAYMENT_METHOD),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const sortByLabel = <T extends { label: string }>(items: T[]) =>
|
const sortByLabel = <T extends { label: string }>(items: T[]) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user