mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-07-09 11:26:00 +00:00
chore: ajustes de componentes, estilos, dependências e métricas do dashboard
- dashboard: melhorias em métricas, filtros de transações e overview de período - transactions: colunas, tabela e página com novos campos e ajustes de exibição - ui: card, table, navigation-menu, navbar, month-picker, logo-picker, theme-toggler - calculator: ajustes de display, keypad e estado - calendar: melhorias de grid e day-cell - insights: atualização de constantes - settings: pequenos ajustes - pnpm-lock: atualização de dependências - pdf.worker: atualização do worker Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
178
src/features/transactions/actions/refund-action.ts
Normal file
178
src/features/transactions/actions/refund-action.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
"use server";
|
||||
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { cards, categories, transactions } from "@/db/schema";
|
||||
import {
|
||||
buildRefundNote,
|
||||
isRefundNote,
|
||||
} from "@/shared/lib/accounts/constants";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { PERIOD_FORMAT_REGEX } from "@/shared/lib/invoices";
|
||||
import type { ActionResult } from "@/shared/lib/types/actions";
|
||||
import { formatDecimalForDbRequired } from "@/shared/utils/currency";
|
||||
import { parseLocalDateString } from "@/shared/utils/date";
|
||||
import {
|
||||
formatPaidInvoicePeriods,
|
||||
getPaidInvoicePeriods,
|
||||
revalidate,
|
||||
} from "./core";
|
||||
|
||||
const refundSchema = z.object({
|
||||
originalTransactionId: z
|
||||
.string({ message: "Lançamento inválido." })
|
||||
.uuid("Lançamento inválido."),
|
||||
refundDate: z
|
||||
.string({ message: "Data inválida." })
|
||||
.refine(
|
||||
(value) => !Number.isNaN(parseLocalDateString(value).getTime()),
|
||||
"Data inválida.",
|
||||
),
|
||||
refundPeriod: z
|
||||
.string({ message: "Período inválido." })
|
||||
.regex(PERIOD_FORMAT_REGEX, "Período inválido."),
|
||||
});
|
||||
|
||||
type RefundInput = z.infer<typeof refundSchema>;
|
||||
|
||||
export async function refundTransactionAction(
|
||||
input: RefundInput,
|
||||
): Promise<ActionResult<{ refundId: string }>> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = refundSchema.parse(input);
|
||||
|
||||
const original = await db.query.transactions.findFirst({
|
||||
where: and(
|
||||
eq(transactions.id, data.originalTransactionId),
|
||||
eq(transactions.userId, user.id),
|
||||
),
|
||||
});
|
||||
|
||||
if (!original) {
|
||||
return { success: false, error: "Lançamento não encontrado." };
|
||||
}
|
||||
|
||||
if (original.transactionType !== "Despesa") {
|
||||
return {
|
||||
success: false,
|
||||
error: "Apenas despesas podem ser estornadas.",
|
||||
};
|
||||
}
|
||||
|
||||
if (original.condition !== "À vista") {
|
||||
return {
|
||||
success: false,
|
||||
error: "Apenas lançamentos à vista podem ser estornados.",
|
||||
};
|
||||
}
|
||||
|
||||
if (original.splitGroupId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Lançamentos divididos não podem ser estornados.",
|
||||
};
|
||||
}
|
||||
|
||||
if (isRefundNote(original.note)) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Este lançamento já é um reembolso.",
|
||||
};
|
||||
}
|
||||
|
||||
const [existingRefund, card, paidPeriods, refundCategory] =
|
||||
await Promise.all([
|
||||
db.query.transactions.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.note, buildRefundNote(original.id)),
|
||||
),
|
||||
}),
|
||||
original.cardId
|
||||
? db.query.cards.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(cards.id, original.cardId),
|
||||
eq(cards.userId, user.id),
|
||||
),
|
||||
})
|
||||
: Promise.resolve(null),
|
||||
original.cardId
|
||||
? getPaidInvoicePeriods(user.id, original.cardId, [data.refundPeriod])
|
||||
: Promise.resolve([] as string[]),
|
||||
db.query.categories.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(categories.userId, user.id),
|
||||
eq(categories.name, "Reembolso"),
|
||||
),
|
||||
}),
|
||||
]);
|
||||
|
||||
if (existingRefund) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Este lançamento já foi estornado.",
|
||||
};
|
||||
}
|
||||
|
||||
if (original.cardId && !card) {
|
||||
return { success: false, error: "Cartão não encontrado." };
|
||||
}
|
||||
|
||||
if (paidPeriods.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `A fatura de ${formatPaidInvoicePeriods(
|
||||
paidPeriods,
|
||||
)} já está paga. Desfaça o pagamento antes de lançar o reembolso.`,
|
||||
};
|
||||
}
|
||||
|
||||
const amountAbs = Math.abs(Number(original.amount));
|
||||
const refundDate = parseLocalDateString(data.refundDate);
|
||||
|
||||
const [inserted] = await db
|
||||
.insert(transactions)
|
||||
.values({
|
||||
name: `Reembolso de: ${original.name}`,
|
||||
condition: "À vista",
|
||||
paymentMethod: original.paymentMethod,
|
||||
note: buildRefundNote(original.id),
|
||||
amount: formatDecimalForDbRequired(amountAbs),
|
||||
purchaseDate: refundDate,
|
||||
transactionType: "Receita",
|
||||
period: data.refundPeriod,
|
||||
isSettled: false,
|
||||
userId: user.id,
|
||||
cardId: original.cardId,
|
||||
accountId: original.accountId,
|
||||
categoryId: refundCategory?.id ?? null,
|
||||
payerId: original.payerId,
|
||||
})
|
||||
.returning({ id: transactions.id });
|
||||
|
||||
revalidate(user.id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Reembolso registrado.",
|
||||
data: { refundId: inserted?.id ?? "" },
|
||||
};
|
||||
} 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.",
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user