refactor: migrate from ESLint to Biome and extract SQL queries to data.ts
- Replace ESLint with Biome for linting and formatting - Configure Biome with tabs, double quotes, and organized imports - Move all SQL/Drizzle queries from page.tsx files to data.ts files - Create new data.ts files for: ajustes, dashboard, relatorios/categorias - Update existing data.ts files: extrato, fatura (add lancamentos queries) - Remove all drizzle-orm imports from page.tsx files - Update README.md with new tooling info Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,119 +1,117 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
cartoes,
|
||||
categorias,
|
||||
faturas,
|
||||
lancamentos,
|
||||
pagadores,
|
||||
} from "@/db/schema";
|
||||
import { buildInvoicePaymentNote } from "@/lib/accounts/constants";
|
||||
import { db } from "@/lib/db";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import {
|
||||
INVOICE_PAYMENT_STATUS,
|
||||
INVOICE_STATUS_VALUES,
|
||||
PERIOD_FORMAT_REGEX,
|
||||
type InvoicePaymentStatus,
|
||||
} from "@/lib/faturas";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
|
||||
import { parseLocalDateString } from "@/lib/utils/date";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
cartoes,
|
||||
categorias,
|
||||
faturas,
|
||||
lancamentos,
|
||||
pagadores,
|
||||
} from "@/db/schema";
|
||||
import { buildInvoicePaymentNote } from "@/lib/accounts/constants";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { db } from "@/lib/db";
|
||||
import {
|
||||
INVOICE_PAYMENT_STATUS,
|
||||
INVOICE_STATUS_VALUES,
|
||||
type InvoicePaymentStatus,
|
||||
PERIOD_FORMAT_REGEX,
|
||||
} from "@/lib/faturas";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
|
||||
import { parseLocalDateString } from "@/lib/utils/date";
|
||||
|
||||
const updateInvoicePaymentStatusSchema = z.object({
|
||||
cartaoId: z
|
||||
.string({ message: "Cartão inválido." })
|
||||
.uuid("Cartão inválido."),
|
||||
period: z
|
||||
.string({ message: "Período inválido." })
|
||||
.regex(PERIOD_FORMAT_REGEX, "Período inválido."),
|
||||
status: z.enum(
|
||||
INVOICE_STATUS_VALUES as [InvoicePaymentStatus, ...InvoicePaymentStatus[]]
|
||||
),
|
||||
paymentDate: z.string().optional(),
|
||||
cartaoId: z.string({ message: "Cartão inválido." }).uuid("Cartão inválido."),
|
||||
period: z
|
||||
.string({ message: "Período inválido." })
|
||||
.regex(PERIOD_FORMAT_REGEX, "Período inválido."),
|
||||
status: z.enum(
|
||||
INVOICE_STATUS_VALUES as [InvoicePaymentStatus, ...InvoicePaymentStatus[]],
|
||||
),
|
||||
paymentDate: z.string().optional(),
|
||||
});
|
||||
|
||||
type UpdateInvoicePaymentStatusInput = z.infer<
|
||||
typeof updateInvoicePaymentStatusSchema
|
||||
typeof updateInvoicePaymentStatusSchema
|
||||
>;
|
||||
|
||||
type ActionResult =
|
||||
| { success: true; message: string }
|
||||
| { success: false; error: string };
|
||||
| { success: true; message: string }
|
||||
| { success: false; error: string };
|
||||
|
||||
const successMessageByStatus: Record<InvoicePaymentStatus, string> = {
|
||||
[INVOICE_PAYMENT_STATUS.PAID]: "Fatura marcada como paga.",
|
||||
[INVOICE_PAYMENT_STATUS.PENDING]: "Pagamento da fatura foi revertido.",
|
||||
[INVOICE_PAYMENT_STATUS.PAID]: "Fatura marcada como paga.",
|
||||
[INVOICE_PAYMENT_STATUS.PENDING]: "Pagamento da fatura foi revertido.",
|
||||
};
|
||||
|
||||
const formatDecimal = (value: number) =>
|
||||
(Math.round(value * 100) / 100).toFixed(2);
|
||||
(Math.round(value * 100) / 100).toFixed(2);
|
||||
|
||||
export async function updateInvoicePaymentStatusAction(
|
||||
input: UpdateInvoicePaymentStatusInput
|
||||
input: UpdateInvoicePaymentStatusInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = updateInvoicePaymentStatusSchema.parse(input);
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = updateInvoicePaymentStatusSchema.parse(input);
|
||||
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
const card = await tx.query.cartoes.findFirst({
|
||||
columns: { id: true, contaId: true, name: true },
|
||||
where: and(eq(cartoes.id, data.cartaoId), eq(cartoes.userId, user.id)),
|
||||
});
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
const card = await tx.query.cartoes.findFirst({
|
||||
columns: { id: true, contaId: true, name: true },
|
||||
where: and(eq(cartoes.id, data.cartaoId), eq(cartoes.userId, user.id)),
|
||||
});
|
||||
|
||||
if (!card) {
|
||||
throw new Error("Cartão não encontrado.");
|
||||
}
|
||||
if (!card) {
|
||||
throw new Error("Cartão não encontrado.");
|
||||
}
|
||||
|
||||
const existingInvoice = await tx.query.faturas.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
},
|
||||
where: and(
|
||||
eq(faturas.cartaoId, data.cartaoId),
|
||||
eq(faturas.userId, user.id),
|
||||
eq(faturas.period, data.period)
|
||||
),
|
||||
});
|
||||
const existingInvoice = await tx.query.faturas.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
},
|
||||
where: and(
|
||||
eq(faturas.cartaoId, data.cartaoId),
|
||||
eq(faturas.userId, user.id),
|
||||
eq(faturas.period, data.period),
|
||||
),
|
||||
});
|
||||
|
||||
if (existingInvoice) {
|
||||
await tx
|
||||
.update(faturas)
|
||||
.set({
|
||||
paymentStatus: data.status,
|
||||
})
|
||||
.where(eq(faturas.id, existingInvoice.id));
|
||||
} else {
|
||||
await tx.insert(faturas).values({
|
||||
cartaoId: data.cartaoId,
|
||||
period: data.period,
|
||||
paymentStatus: data.status,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
if (existingInvoice) {
|
||||
await tx
|
||||
.update(faturas)
|
||||
.set({
|
||||
paymentStatus: data.status,
|
||||
})
|
||||
.where(eq(faturas.id, existingInvoice.id));
|
||||
} else {
|
||||
await tx.insert(faturas).values({
|
||||
cartaoId: data.cartaoId,
|
||||
period: data.period,
|
||||
paymentStatus: data.status,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
const shouldMarkAsPaid = data.status === INVOICE_PAYMENT_STATUS.PAID;
|
||||
const shouldMarkAsPaid = data.status === INVOICE_PAYMENT_STATUS.PAID;
|
||||
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set({ isSettled: shouldMarkAsPaid })
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.cartaoId, card.id),
|
||||
eq(lancamentos.period, data.period)
|
||||
)
|
||||
);
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set({ isSettled: shouldMarkAsPaid })
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.cartaoId, card.id),
|
||||
eq(lancamentos.period, data.period),
|
||||
),
|
||||
);
|
||||
|
||||
const invoiceNote = buildInvoicePaymentNote(card.id, data.period);
|
||||
const invoiceNote = buildInvoicePaymentNote(card.id, data.period);
|
||||
|
||||
if (shouldMarkAsPaid) {
|
||||
const [adminShareRow] = await tx
|
||||
.select({
|
||||
total: sql<number>`
|
||||
if (shouldMarkAsPaid) {
|
||||
const [adminShareRow] = await tx
|
||||
.select({
|
||||
total: sql<number>`
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
@@ -124,177 +122,175 @@ export async function updateInvoicePaymentStatusAction(
|
||||
0
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.leftJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.cartaoId, card.id),
|
||||
eq(lancamentos.period, data.period),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN)
|
||||
)
|
||||
);
|
||||
})
|
||||
.from(lancamentos)
|
||||
.leftJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.cartaoId, card.id),
|
||||
eq(lancamentos.period, data.period),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
),
|
||||
);
|
||||
|
||||
const adminShare = Math.abs(Number(adminShareRow?.total ?? 0));
|
||||
const adminShare = Math.abs(Number(adminShareRow?.total ?? 0));
|
||||
|
||||
if (adminShare > 0 && card.contaId) {
|
||||
const adminPagador = await tx.query.pagadores.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(pagadores.userId, user.id),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN)
|
||||
),
|
||||
});
|
||||
if (adminShare > 0 && card.contaId) {
|
||||
const adminPagador = await tx.query.pagadores.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(pagadores.userId, user.id),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
),
|
||||
});
|
||||
|
||||
const paymentCategory = await tx.query.categorias.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(categorias.userId, user.id),
|
||||
eq(categorias.name, "Pagamentos")
|
||||
),
|
||||
});
|
||||
const paymentCategory = await tx.query.categorias.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(categorias.userId, user.id),
|
||||
eq(categorias.name, "Pagamentos"),
|
||||
),
|
||||
});
|
||||
|
||||
if (adminPagador) {
|
||||
// Usar a data customizada ou a data atual como data de pagamento
|
||||
const invoiceDate = data.paymentDate
|
||||
? parseLocalDateString(data.paymentDate)
|
||||
: new Date();
|
||||
if (adminPagador) {
|
||||
// Usar a data customizada ou a data atual como data de pagamento
|
||||
const invoiceDate = data.paymentDate
|
||||
? parseLocalDateString(data.paymentDate)
|
||||
: new Date();
|
||||
|
||||
const amount = `-${formatDecimal(adminShare)}`;
|
||||
const payload = {
|
||||
condition: "À vista",
|
||||
name: `Pagamento fatura - ${card.name}`,
|
||||
paymentMethod: "Pix",
|
||||
note: invoiceNote,
|
||||
amount,
|
||||
purchaseDate: invoiceDate,
|
||||
transactionType: "Despesa" as const,
|
||||
period: data.period,
|
||||
isSettled: true,
|
||||
userId: user.id,
|
||||
contaId: card.contaId,
|
||||
categoriaId: paymentCategory?.id ?? null,
|
||||
pagadorId: adminPagador.id,
|
||||
};
|
||||
const amount = `-${formatDecimal(adminShare)}`;
|
||||
const payload = {
|
||||
condition: "À vista",
|
||||
name: `Pagamento fatura - ${card.name}`,
|
||||
paymentMethod: "Pix",
|
||||
note: invoiceNote,
|
||||
amount,
|
||||
purchaseDate: invoiceDate,
|
||||
transactionType: "Despesa" as const,
|
||||
period: data.period,
|
||||
isSettled: true,
|
||||
userId: user.id,
|
||||
contaId: card.contaId,
|
||||
categoriaId: paymentCategory?.id ?? null,
|
||||
pagadorId: adminPagador.id,
|
||||
};
|
||||
|
||||
const existingPayment = await tx.query.lancamentos.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote)
|
||||
),
|
||||
});
|
||||
const existingPayment = await tx.query.lancamentos.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote),
|
||||
),
|
||||
});
|
||||
|
||||
if (existingPayment) {
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set(payload)
|
||||
.where(eq(lancamentos.id, existingPayment.id));
|
||||
} else {
|
||||
await tx.insert(lancamentos).values(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await tx
|
||||
.delete(lancamentos)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
if (existingPayment) {
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set(payload)
|
||||
.where(eq(lancamentos.id, existingPayment.id));
|
||||
} else {
|
||||
await tx.insert(lancamentos).values(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await tx
|
||||
.delete(lancamentos)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
revalidatePath(`/cartoes/${data.cartaoId}/fatura`);
|
||||
revalidatePath("/cartoes");
|
||||
revalidatePath("/contas");
|
||||
revalidatePath(`/cartoes/${data.cartaoId}/fatura`);
|
||||
revalidatePath("/cartoes");
|
||||
revalidatePath("/contas");
|
||||
|
||||
return { success: true, message: successMessageByStatus[data.status] };
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.issues[0]?.message ?? "Dados inválidos.",
|
||||
};
|
||||
}
|
||||
return { success: true, message: successMessageByStatus[data.status] };
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.issues[0]?.message ?? "Dados inválidos.",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro inesperado.",
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro inesperado.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const updatePaymentDateSchema = z.object({
|
||||
cartaoId: z
|
||||
.string({ message: "Cartão inválido." })
|
||||
.uuid("Cartão inválido."),
|
||||
period: z
|
||||
.string({ message: "Período inválido." })
|
||||
.regex(PERIOD_FORMAT_REGEX, "Período inválido."),
|
||||
paymentDate: z.string({ message: "Data de pagamento inválida." }),
|
||||
cartaoId: z.string({ message: "Cartão inválido." }).uuid("Cartão inválido."),
|
||||
period: z
|
||||
.string({ message: "Período inválido." })
|
||||
.regex(PERIOD_FORMAT_REGEX, "Período inválido."),
|
||||
paymentDate: z.string({ message: "Data de pagamento inválida." }),
|
||||
});
|
||||
|
||||
type UpdatePaymentDateInput = z.infer<typeof updatePaymentDateSchema>;
|
||||
|
||||
export async function updatePaymentDateAction(
|
||||
input: UpdatePaymentDateInput
|
||||
input: UpdatePaymentDateInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = updatePaymentDateSchema.parse(input);
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = updatePaymentDateSchema.parse(input);
|
||||
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
const card = await tx.query.cartoes.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(eq(cartoes.id, data.cartaoId), eq(cartoes.userId, user.id)),
|
||||
});
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
const card = await tx.query.cartoes.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(eq(cartoes.id, data.cartaoId), eq(cartoes.userId, user.id)),
|
||||
});
|
||||
|
||||
if (!card) {
|
||||
throw new Error("Cartão não encontrado.");
|
||||
}
|
||||
if (!card) {
|
||||
throw new Error("Cartão não encontrado.");
|
||||
}
|
||||
|
||||
const invoiceNote = buildInvoicePaymentNote(card.id, data.period);
|
||||
const invoiceNote = buildInvoicePaymentNote(card.id, data.period);
|
||||
|
||||
const existingPayment = await tx.query.lancamentos.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote)
|
||||
),
|
||||
});
|
||||
const existingPayment = await tx.query.lancamentos.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote),
|
||||
),
|
||||
});
|
||||
|
||||
if (!existingPayment) {
|
||||
throw new Error("Pagamento não encontrado.");
|
||||
}
|
||||
if (!existingPayment) {
|
||||
throw new Error("Pagamento não encontrado.");
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set({
|
||||
purchaseDate: parseLocalDateString(data.paymentDate),
|
||||
})
|
||||
.where(eq(lancamentos.id, existingPayment.id));
|
||||
});
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.set({
|
||||
purchaseDate: parseLocalDateString(data.paymentDate),
|
||||
})
|
||||
.where(eq(lancamentos.id, existingPayment.id));
|
||||
});
|
||||
|
||||
revalidatePath(`/cartoes/${data.cartaoId}/fatura`);
|
||||
revalidatePath("/cartoes");
|
||||
revalidatePath("/contas");
|
||||
revalidatePath(`/cartoes/${data.cartaoId}/fatura`);
|
||||
revalidatePath("/cartoes");
|
||||
revalidatePath("/contas");
|
||||
|
||||
return { success: true, message: "Data de pagamento atualizada." };
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.issues[0]?.message ?? "Dados inválidos.",
|
||||
};
|
||||
}
|
||||
return { success: true, message: "Data de pagamento atualizada." };
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.issues[0]?.message ?? "Dados inválidos.",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro inesperado.",
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Erro inesperado.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user