feat(wallets): implement wallet management features including create, update, delete, deposit, transfer, and list movements
- Added createWallet, updateWallet, deleteWallet, depositWallet, transferWallet, and listMovements handlers. - Created corresponding routes for wallet operations. - Developed frontend components for wallet management including dialogs for creating, editing, depositing, adjusting balance, transferring, and viewing movements. - Integrated wallet management into the authenticated routes.
This commit is contained in:
164
apps/backend/src/modules/wallets/wallets.service.ts
Normal file
164
apps/backend/src/modules/wallets/wallets.service.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { z } from "zod";
|
||||
import { Prisma } from "../../generated/prisma/client";
|
||||
import { prisma } from "../../lib/prisma";
|
||||
|
||||
export const createWalletSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
currency: z.string().min(1).max(10),
|
||||
initialBalance: z.number().optional().default(0),
|
||||
});
|
||||
|
||||
export const updateWalletSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
currency: z.string().min(1).max(10),
|
||||
});
|
||||
|
||||
export const depositSchema = z.object({
|
||||
amount: z.number().positive(),
|
||||
description: z.string().max(255).optional(),
|
||||
});
|
||||
|
||||
export const adjustBalanceSchema = z.object({
|
||||
newBalance: z.number().min(0),
|
||||
description: z.string().max(255).optional(),
|
||||
});
|
||||
|
||||
export const transferSchema = z.object({
|
||||
fromWalletId: z.number().int().positive(),
|
||||
toWalletId: z.number().int().positive(),
|
||||
outgoingAmount: z.number().positive(),
|
||||
incomingAmount: z.number().positive(),
|
||||
description: z.string().max(255).optional(),
|
||||
});
|
||||
|
||||
export type CreateWalletInput = z.infer<typeof createWalletSchema>;
|
||||
export type UpdateWalletInput = z.infer<typeof updateWalletSchema>;
|
||||
export type DepositInput = z.infer<typeof depositSchema>;
|
||||
export type AdjustBalanceInput = z.infer<typeof adjustBalanceSchema>;
|
||||
export type TransferInput = z.infer<typeof transferSchema>;
|
||||
|
||||
export async function listWallets() {
|
||||
const wallets = await prisma.wallet.findMany({ orderBy: { createdAt: "desc" } });
|
||||
return wallets.map((w) => ({ ...w, balance: Number(w.balance) }));
|
||||
}
|
||||
|
||||
export async function createWallet(input: CreateWalletInput) {
|
||||
const wallet = await prisma.wallet.create({
|
||||
data: {
|
||||
name: input.name,
|
||||
currency: input.currency,
|
||||
balance: input.initialBalance ?? 0,
|
||||
},
|
||||
});
|
||||
return { ...wallet, balance: Number(wallet.balance) };
|
||||
}
|
||||
|
||||
export async function updateWallet(id: number, input: UpdateWalletInput) {
|
||||
const wallet = await prisma.wallet.update({
|
||||
where: { id },
|
||||
data: { name: input.name, currency: input.currency },
|
||||
});
|
||||
return { ...wallet, balance: Number(wallet.balance) };
|
||||
}
|
||||
|
||||
export async function deleteWallet(id: number) {
|
||||
const wallet = await prisma.wallet.findUniqueOrThrow({ where: { id } });
|
||||
if (Number(wallet.balance) !== 0) {
|
||||
throw new Error("No se puede eliminar una billetera con saldo distinto de cero");
|
||||
}
|
||||
await prisma.walletMovement.deleteMany({ where: { walletId: id } });
|
||||
await prisma.wallet.delete({ where: { id } });
|
||||
}
|
||||
|
||||
export async function depositWallet(id: number, input: DepositInput) {
|
||||
const [wallet] = await prisma.$transaction([
|
||||
prisma.wallet.update({
|
||||
where: { id },
|
||||
data: { balance: { increment: input.amount } },
|
||||
}),
|
||||
prisma.walletMovement.create({
|
||||
data: {
|
||||
walletId: id,
|
||||
type: "DEPOSIT",
|
||||
amount: input.amount,
|
||||
description: input.description ?? null,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return { ...wallet, balance: Number(wallet.balance) };
|
||||
}
|
||||
|
||||
export async function adjustBalance(id: number, input: AdjustBalanceInput) {
|
||||
const wallet = await prisma.wallet.findUniqueOrThrow({ where: { id } });
|
||||
const currentBalance = Number(wallet.balance);
|
||||
const difference = input.newBalance - currentBalance;
|
||||
|
||||
const [updated] = await prisma.$transaction([
|
||||
prisma.wallet.update({
|
||||
where: { id },
|
||||
data: { balance: input.newBalance },
|
||||
}),
|
||||
prisma.walletMovement.create({
|
||||
data: {
|
||||
walletId: id,
|
||||
type: "ADJUSTMENT",
|
||||
amount: difference,
|
||||
description: input.description ?? null,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return { ...updated, balance: Number(updated.balance) };
|
||||
}
|
||||
|
||||
export async function transferWallet(input: TransferInput) {
|
||||
const groupId = crypto.randomUUID();
|
||||
|
||||
const [fromWallet, toWallet] = await prisma.$transaction(async (tx) => {
|
||||
const from = await tx.wallet.update({
|
||||
where: { id: input.fromWalletId },
|
||||
data: { balance: { decrement: input.outgoingAmount } },
|
||||
});
|
||||
|
||||
const to = await tx.wallet.update({
|
||||
where: { id: input.toWalletId },
|
||||
data: { balance: { increment: input.incomingAmount } },
|
||||
});
|
||||
|
||||
await tx.walletMovement.create({
|
||||
data: {
|
||||
walletId: input.fromWalletId,
|
||||
type: "TRANSFER_OUT",
|
||||
amount: -input.outgoingAmount,
|
||||
description: input.description ?? null,
|
||||
referenceWalletId: input.toWalletId,
|
||||
transferGroupId: groupId,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.walletMovement.create({
|
||||
data: {
|
||||
walletId: input.toWalletId,
|
||||
type: "TRANSFER_IN",
|
||||
amount: input.incomingAmount,
|
||||
description: input.description ?? null,
|
||||
referenceWalletId: input.fromWalletId,
|
||||
transferGroupId: groupId,
|
||||
},
|
||||
});
|
||||
|
||||
return [from, to];
|
||||
});
|
||||
|
||||
return {
|
||||
from: { ...fromWallet, balance: Number(fromWallet.balance) },
|
||||
to: { ...toWallet, balance: Number(toWallet.balance) },
|
||||
};
|
||||
}
|
||||
|
||||
export async function listMovements(walletId: number) {
|
||||
const movements = await prisma.walletMovement.findMany({
|
||||
where: { walletId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
return movements.map((m) => ({ ...m, amount: Number(m.amount) }));
|
||||
}
|
||||
Reference in New Issue
Block a user