diff --git a/app/(dashboard)/lancamentos/anticipation-actions.ts b/app/(dashboard)/lancamentos/anticipation-actions.ts index 3371abe..8632f71 100644 --- a/app/(dashboard)/lancamentos/anticipation-actions.ts +++ b/app/(dashboard)/lancamentos/anticipation-actions.ts @@ -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; } } @@ -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; } } @@ -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; } } diff --git a/components/lancamentos/dialogs/anticipate-installments-dialog/anticipate-installments-dialog.tsx b/components/lancamentos/dialogs/anticipate-installments-dialog/anticipate-installments-dialog.tsx index 4b6eadb..0c5cd6c 100644 --- a/components/lancamentos/dialogs/anticipate-installments-dialog/anticipate-installments-dialog.tsx +++ b/components/lancamentos/dialogs/anticipate-installments-dialog/anticipate-installments-dialog.tsx @@ -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({ anticipationPeriod: defaultPeriod, - discount: 0, + discount: "0", pagadorId: "", categoriaId: "", note: "", @@ -110,23 +110,25 @@ export function AnticipateInstallmentsDialog({ getEligibleInstallmentsAction(seriesId) .then((result) => { - if (result.success && result.data) { - setEligibleInstallments(result.data); - - // Pré-preencher pagador e categoria da primeira parcela - if (result.data.length > 0) { - const first = result.data[0]; - setFormState({ - anticipationPeriod: defaultPeriod, - discount: 0, - pagadorId: first.pagadorId ?? "", - categoriaId: first.categoriaId ?? "", - note: "", - }); - } - } else { + 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 (installments.length > 0) { + const first = installments[0]; + setFormState({ + anticipationPeriod: defaultPeriod, + discount: "0", + pagadorId: first.pagadorId ?? "", + categoriaId: first.categoriaId ?? "", + note: "", + }); } }) .catch((error) => { @@ -268,9 +270,7 @@ export function AnticipateInstallmentsDialog({ - updateField("discount", value ?? 0) - } + onValueChange={(value) => updateField("discount", value)} placeholder="R$ 0,00" disabled={isPending} /> diff --git a/components/lancamentos/dialogs/anticipate-installments-dialog/anticipation-history-dialog.tsx b/components/lancamentos/dialogs/anticipate-installments-dialog/anticipation-history-dialog.tsx index 2b0730b..64e20ff 100644 --- a/components/lancamentos/dialogs/anticipate-installments-dialog/anticipation-history-dialog.tsx +++ b/components/lancamentos/dialogs/anticipate-installments-dialog/anticipation-history-dialog.tsx @@ -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"); diff --git a/components/lancamentos/shared/installment-timeline.tsx b/components/lancamentos/shared/installment-timeline.tsx index 54ea01b..3cfcd88 100644 --- a/components/lancamentos/shared/installment-timeline.tsx +++ b/components/lancamentos/shared/installment-timeline.tsx @@ -1,9 +1,9 @@ import { RiArrowDownFill, RiCheckLine } from "@remixicon/react"; import { calculateLastInstallmentDate, - formatCurrentInstallment, - formatLastInstallmentDate, formatPurchaseDate, + formatLastInstallmentDate, + formatCurrentInstallment, } from "@/lib/installments/utils"; type InstallmentTimelineProps = { diff --git a/lib/installments/anticipation-types.ts b/lib/installments/anticipation-types.ts index 2d84259..62b1009 100644 --- a/lib/installments/anticipation-types.ts +++ b/lib/installments/anticipation-types.ts @@ -38,6 +38,7 @@ export type CreateAnticipationInput = { seriesId: string; installmentIds: string[]; anticipationPeriod: string; + discount?: number; pagadorId?: string; categoriaId?: string; note?: string; diff --git a/lib/installments/utils.ts b/lib/installments/utils.ts index 53c6d7b..a8cc09d 100644 --- a/lib/installments/utils.ts +++ b/lib/installments/utils.ts @@ -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}`; } diff --git a/lib/lancamentos/form-helpers.ts b/lib/lancamentos/form-helpers.ts index 1a2e27b..85fd14d 100644 --- a/lib/lancamentos/form-helpers.ts +++ b/lib/lancamentos/form-helpers.ts @@ -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); }