fix: corrige antecipacao e fechamento de compras no cartao
This commit is contained in:
@@ -17,10 +17,10 @@ import {
|
||||
generateAnticipationNote,
|
||||
} from "@/lib/installments/anticipation-helpers";
|
||||
import type {
|
||||
InstallmentAnticipationWithRelations,
|
||||
CancelAnticipationInput,
|
||||
CreateAnticipationInput,
|
||||
EligibleInstallment,
|
||||
InstallmentAnticipationWithRelations,
|
||||
} from "@/lib/installments/anticipation-types";
|
||||
import { uuidSchema } from "@/lib/schemas/common";
|
||||
import { formatDecimalForDbRequired } from "@/lib/utils/currency";
|
||||
@@ -94,7 +94,7 @@ export async function getEligibleInstallmentsAction(
|
||||
},
|
||||
});
|
||||
|
||||
const eligibleInstallments: EligibleInstallment[] = rows.map((row) => ({
|
||||
const eligibleInstallments: EligibleInstallment[] = rows.map((row: any) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
amount: row.amount,
|
||||
@@ -110,10 +110,11 @@ export async function getEligibleInstallmentsAction(
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Parcelas elegíveis carregadas.",
|
||||
data: eligibleInstallments,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
return handleActionError(error) as ActionResult<EligibleInstallment[]>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +155,7 @@ export async function createInstallmentAnticipationAction(
|
||||
|
||||
// 2. Calcular valor total
|
||||
const totalAmountCents = installments.reduce(
|
||||
(sum, inst) => sum + Number(inst.amount) * 100,
|
||||
(sum: number, inst: any) => sum + Number(inst.amount) * 100,
|
||||
0,
|
||||
);
|
||||
const totalAmount = totalAmountCents / 100;
|
||||
@@ -181,7 +182,7 @@ export async function createInstallmentAnticipationAction(
|
||||
const firstInstallment = installments[0];
|
||||
|
||||
// 4. Criar lançamento e antecipação em transação
|
||||
await db.transaction(async (tx) => {
|
||||
await db.transaction(async (tx: any) => {
|
||||
// 4.1. Criar o lançamento de antecipação (com desconto aplicado)
|
||||
const [newLancamento] = await tx
|
||||
.insert(lancamentos)
|
||||
@@ -205,7 +206,7 @@ export async function createInstallmentAnticipationAction(
|
||||
note:
|
||||
data.note ||
|
||||
generateAnticipationNote(
|
||||
installments.map((inst) => ({
|
||||
installments.map((inst: any) => ({
|
||||
id: inst.id,
|
||||
name: inst.name,
|
||||
amount: inst.amount,
|
||||
@@ -329,10 +330,13 @@ export async function getInstallmentAnticipationsAction(
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Antecipações carregadas.",
|
||||
data: anticipations,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
return handleActionError(
|
||||
error,
|
||||
) as ActionResult<InstallmentAnticipationWithRelations[]>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +351,7 @@ export async function cancelInstallmentAnticipationAction(
|
||||
const user = await getUser();
|
||||
const data = cancelAnticipationSchema.parse(input);
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
await db.transaction(async (tx: any) => {
|
||||
// 1. Buscar antecipação usando query builder
|
||||
const anticipationRows = await tx
|
||||
.select({
|
||||
@@ -469,9 +473,12 @@ export async function getAnticipationDetailsAction(
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Detalhes da antecipação carregados.",
|
||||
data: anticipation,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
return handleActionError(
|
||||
error,
|
||||
) as ActionResult<InstallmentAnticipationWithRelations>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ interface AnticipateInstallmentsDialogProps {
|
||||
|
||||
type AnticipationFormValues = {
|
||||
anticipationPeriod: string;
|
||||
discount: number;
|
||||
discount: string;
|
||||
pagadorId: string;
|
||||
categoriaId: string;
|
||||
note: string;
|
||||
@@ -95,7 +95,7 @@ export function AnticipateInstallmentsDialog({
|
||||
const { formState, updateField, setFormState } =
|
||||
useFormState<AnticipationFormValues>({
|
||||
anticipationPeriod: defaultPeriod,
|
||||
discount: 0,
|
||||
discount: "0",
|
||||
pagadorId: "",
|
||||
categoriaId: "",
|
||||
note: "",
|
||||
@@ -110,24 +110,26 @@ export function AnticipateInstallmentsDialog({
|
||||
|
||||
getEligibleInstallmentsAction(seriesId)
|
||||
.then((result) => {
|
||||
if (result.success && result.data) {
|
||||
setEligibleInstallments(result.data);
|
||||
if (!result.success) {
|
||||
toast.error(result.error || "Erro ao carregar parcelas");
|
||||
setEligibleInstallments([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const installments = result.data ?? [];
|
||||
setEligibleInstallments(installments);
|
||||
|
||||
// Pré-preencher pagador e categoria da primeira parcela
|
||||
if (result.data.length > 0) {
|
||||
const first = result.data[0];
|
||||
if (installments.length > 0) {
|
||||
const first = installments[0];
|
||||
setFormState({
|
||||
anticipationPeriod: defaultPeriod,
|
||||
discount: 0,
|
||||
discount: "0",
|
||||
pagadorId: first.pagadorId ?? "",
|
||||
categoriaId: first.categoriaId ?? "",
|
||||
note: "",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toast.error(result.error || "Erro ao carregar parcelas");
|
||||
setEligibleInstallments([]);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erro ao buscar parcelas:", error);
|
||||
@@ -268,9 +270,7 @@ export function AnticipateInstallmentsDialog({
|
||||
<CurrencyInput
|
||||
id="anticipation-discount"
|
||||
value={formState.discount}
|
||||
onValueChange={(value) =>
|
||||
updateField("discount", value ?? 0)
|
||||
}
|
||||
onValueChange={(value) => updateField("discount", value)}
|
||||
placeholder="R$ 0,00"
|
||||
disabled={isPending}
|
||||
/>
|
||||
|
||||
@@ -59,14 +59,15 @@ export function AnticipationHistoryDialog({
|
||||
try {
|
||||
const result = await getInstallmentAnticipationsAction(seriesId);
|
||||
|
||||
if (result.success && result.data) {
|
||||
setAnticipations(result.data);
|
||||
} else {
|
||||
if (!result.success) {
|
||||
toast.error(
|
||||
result.error || "Erro ao carregar histórico de antecipações",
|
||||
);
|
||||
setAnticipations([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setAnticipations(result.data ?? []);
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar antecipações:", error);
|
||||
toast.error("Erro ao carregar histórico de antecipações");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { RiArrowDownFill, RiCheckLine } from "@remixicon/react";
|
||||
import {
|
||||
calculateLastInstallmentDate,
|
||||
formatCurrentInstallment,
|
||||
formatLastInstallmentDate,
|
||||
formatPurchaseDate,
|
||||
formatLastInstallmentDate,
|
||||
formatCurrentInstallment,
|
||||
} from "@/lib/installments/utils";
|
||||
|
||||
type InstallmentTimelineProps = {
|
||||
|
||||
@@ -38,6 +38,7 @@ export type CreateAnticipationInput = {
|
||||
seriesId: string;
|
||||
installmentIds: string[];
|
||||
anticipationPeriod: string;
|
||||
discount?: number;
|
||||
pagadorId?: string;
|
||||
categoriaId?: string;
|
||||
note?: string;
|
||||
|
||||
@@ -71,9 +71,6 @@ export function formatPurchaseDate(date: Date): string {
|
||||
* Formata o texto da parcela atual
|
||||
* Exemplo: "1 de 6"
|
||||
*/
|
||||
export function formatCurrentInstallment(
|
||||
current: number,
|
||||
total: number,
|
||||
): string {
|
||||
export function formatCurrentInstallment(current: number, total: number): string {
|
||||
return `${current} de ${total}`;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
* and due day. The period represents the month the fatura is due (vencimento).
|
||||
*
|
||||
* Steps:
|
||||
* 1. If purchase day > closing day → the purchase missed this month's closing,
|
||||
* 1. If purchase day >= closing day → the purchase missed this month's closing,
|
||||
* so it enters the NEXT month's billing cycle (+1 month from purchase).
|
||||
* 2. Then, if dueDay < closingDay, the due date falls in the month AFTER the
|
||||
* closing month (e.g., closes 22nd, due 1st → closes Mar/22, due Apr/1),
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
*
|
||||
* // Card closes day 5, due day 15 (dueDay >= closingDay → no extra)
|
||||
* deriveCreditCardPeriod("2026-02-10", "5", "15") // "2026-03" (missed Feb closing → Mar cycle → due Mar)
|
||||
* deriveCreditCardPeriod("2026-02-05", "5", "15") // "2026-03" (closing day itself already goes to next cycle)
|
||||
* deriveCreditCardPeriod("2026-02-03", "5", "15") // "2026-02" (in Feb cycle → due Feb)
|
||||
*/
|
||||
export function deriveCreditCardPeriod(
|
||||
@@ -44,8 +45,8 @@ export function deriveCreditCardPeriod(
|
||||
// Start with the purchase month as the billing cycle
|
||||
let period = basePeriod;
|
||||
|
||||
// If purchase is after closing day, it enters the next billing cycle
|
||||
if (purchaseDayNum > closingDayNum) {
|
||||
// If purchase is on/after closing day, it enters the next billing cycle
|
||||
if (purchaseDayNum >= closingDayNum) {
|
||||
period = getNextPeriod(period);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user