mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
Remove infraestrutura de series recorrentes
This commit is contained in:
@@ -3,13 +3,11 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { and, asc, eq, inArray, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import type { RecurringSeriesTemplate } from "@/db/schema";
|
||||
import {
|
||||
cards,
|
||||
categories,
|
||||
financialAccounts,
|
||||
payers,
|
||||
recurringSeries,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import {
|
||||
@@ -221,6 +219,22 @@ const refineLancamento = (
|
||||
});
|
||||
}
|
||||
|
||||
if (data.condition === "Recorrente") {
|
||||
if (!data.recurrenceCount) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["recurrenceCount"],
|
||||
message: "Informe por quantos meses a recorrência acontecerá.",
|
||||
});
|
||||
} else if (data.recurrenceCount < 2) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["recurrenceCount"],
|
||||
message: "A recorrência deve ter ao menos dois meses.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data.condition === "Parcelado") {
|
||||
if (!data.installmentCount) {
|
||||
ctx.addIssue({
|
||||
@@ -518,23 +532,33 @@ const buildLancamentoRecords = ({
|
||||
}
|
||||
|
||||
if (data.condition === "Recorrente") {
|
||||
// For the new recurring model, only create 1 row (the current month)
|
||||
// Future rows will be generated lazily by generateRecurringTransactions
|
||||
shares.forEach((share) => {
|
||||
const settled = resolveSettledValue(0);
|
||||
records.push({
|
||||
...basePayload,
|
||||
amount: centsToDecimalString(share.amountCents * amountSign),
|
||||
payerId: share.payerId,
|
||||
purchaseDate,
|
||||
period,
|
||||
isSettled: settled,
|
||||
recurrenceCount: null,
|
||||
dueDate,
|
||||
boletoPaymentDate:
|
||||
data.paymentMethod === "Boleto" && settled ? boletoPaymentDate : null,
|
||||
const recurrenceTotal = data.recurrenceCount ?? 0;
|
||||
|
||||
for (let index = 0; index < recurrenceTotal; index += 1) {
|
||||
const recurrencePeriod = addMonthsToPeriod(period, index);
|
||||
const recurrencePurchaseDate = addMonthsToDate(purchaseDate, index);
|
||||
const recurrenceDueDate = dueDate
|
||||
? addMonthsToDate(dueDate, index)
|
||||
: null;
|
||||
|
||||
shares.forEach((share) => {
|
||||
const settled = resolveSettledValue(index);
|
||||
records.push({
|
||||
...basePayload,
|
||||
amount: centsToDecimalString(share.amountCents * amountSign),
|
||||
payerId: share.payerId,
|
||||
purchaseDate: recurrencePurchaseDate,
|
||||
period: recurrencePeriod,
|
||||
isSettled: settled,
|
||||
recurrenceCount: recurrenceTotal,
|
||||
dueDate: recurrenceDueDate,
|
||||
boletoPaymentDate:
|
||||
data.paymentMethod === "Boleto" && settled
|
||||
? boletoPaymentDate
|
||||
: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
@@ -661,42 +685,7 @@ export async function createTransactionAction(
|
||||
throw new Error("Não foi possível criar os lançamentos solicitados.");
|
||||
}
|
||||
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
// If creating a recurring series, insert the series row first
|
||||
if (data.condition === "Recorrente" && seriesId) {
|
||||
const templateData: RecurringSeriesTemplate = {
|
||||
name: data.name,
|
||||
amount: centsToDecimalString(
|
||||
Math.round(Math.abs(data.amount) * 100) *
|
||||
(data.transactionType === "Despesa" ? -1 : 1),
|
||||
),
|
||||
transactionType: data.transactionType,
|
||||
paymentMethod: data.paymentMethod,
|
||||
categoryId: data.categoryId ?? null,
|
||||
accountId: data.accountId ?? null,
|
||||
cardId: data.cardId ?? null,
|
||||
payerId: data.payerId ?? null,
|
||||
note: data.note ?? null,
|
||||
condition: "Recorrente",
|
||||
};
|
||||
|
||||
await tx.insert(recurringSeries).values({
|
||||
id: seriesId,
|
||||
userId: user.id,
|
||||
status: "active",
|
||||
dayOfMonth: purchaseDate.getDate(),
|
||||
lastGeneratedPeriod: period,
|
||||
templateData,
|
||||
});
|
||||
|
||||
// Link lancamento records to the recurring series
|
||||
for (const record of records) {
|
||||
record.recurringSeriesId = seriesId;
|
||||
}
|
||||
}
|
||||
|
||||
await tx.insert(transactions).values(records);
|
||||
});
|
||||
await db.insert(transactions).values(records);
|
||||
|
||||
const notificationEntries = buildEntriesByPayer(
|
||||
records.map((record) => ({
|
||||
|
||||
@@ -101,14 +101,26 @@ export function ConditionSection({
|
||||
|
||||
{showRecurrence ? (
|
||||
<div className="space-y-1 w-full md:w-1/2">
|
||||
<Label htmlFor="recurrenceInfo">Recorrência</Label>
|
||||
<p
|
||||
id="recurrenceInfo"
|
||||
className="text-xs text-muted-foreground rounded-md border border-dashed border-border p-2.5"
|
||||
<Label htmlFor="recurrenceCount">Repetirá por</Label>
|
||||
<Select
|
||||
value={formState.recurrenceCount}
|
||||
onValueChange={(value) => onFieldChange("recurrenceCount", value)}
|
||||
>
|
||||
Este lançamento será repetido todo mês automaticamente até ser
|
||||
pausado ou cancelado.
|
||||
</p>
|
||||
<SelectTrigger id="recurrenceCount" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.recurrenceCount
|
||||
? `${formState.recurrenceCount} meses`
|
||||
: null}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{[...Array(47)].map((_, index) => (
|
||||
<SelectItem key={index + 2} value={String(index + 2)}>
|
||||
{index + 2} meses
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import { RiAddLine } from "@remixicon/react";
|
||||
import { RiArrowDropDownLine } from "@remixicon/react";
|
||||
import { useEffect, useMemo, useState, useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
@@ -292,7 +292,10 @@ export function TransactionDialog({
|
||||
formState.condition === "Parcelado" && formState.installmentCount
|
||||
? Number(formState.installmentCount)
|
||||
: undefined,
|
||||
recurrenceCount: undefined,
|
||||
recurrenceCount:
|
||||
formState.condition === "Recorrente" && formState.recurrenceCount
|
||||
? Number(formState.recurrenceCount)
|
||||
: undefined,
|
||||
dueDate:
|
||||
formState.paymentMethod === "Boleto" && formState.dueDate
|
||||
? formState.dueDate
|
||||
@@ -375,7 +378,7 @@ export function TransactionDialog({
|
||||
const title =
|
||||
mode === "create"
|
||||
? isImportMode
|
||||
? "Importar para Minha FinancialAccount"
|
||||
? "Importar para Minha Conta"
|
||||
: isCopyMode
|
||||
? "Copiar lançamento"
|
||||
: isNewWithType
|
||||
@@ -476,7 +479,7 @@ export function TransactionDialog({
|
||||
}
|
||||
>
|
||||
<CollapsibleTrigger className="flex w-full items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer [&[data-state=open]>svg]:rotate-180 mt-4">
|
||||
<RiAddLine className="text-primary size-4 transition-transform duration-200" />
|
||||
<RiArrowDropDownLine className="text-primary size-4 transition-transform duration-200" />
|
||||
Condições e anotações
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="space-y-3 pt-3">
|
||||
|
||||
Reference in New Issue
Block a user