feat(expenses): add walletId to expense model and implement wallet adjustments in expense operations
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
||||
import { PaymentStatus } from "../src/generated/prisma/client";
|
||||
|
||||
const mockWalletFindUnique = mock();
|
||||
const mockWalletUpdate = mock();
|
||||
const mockMovementCreate = mock();
|
||||
const mockTransaction = mock(async (operations: Array<Promise<unknown>>) =>
|
||||
Promise.all(operations),
|
||||
);
|
||||
|
||||
const mockPrisma = {
|
||||
$transaction: mockTransaction,
|
||||
periodicExpense: {
|
||||
findMany: mock(),
|
||||
findUniqueOrThrow: mock(),
|
||||
@@ -16,6 +24,13 @@ const mockPrisma = {
|
||||
update: mock(),
|
||||
count: mock(),
|
||||
},
|
||||
wallet: {
|
||||
findUnique: mockWalletFindUnique,
|
||||
update: mockWalletUpdate,
|
||||
},
|
||||
walletMovement: {
|
||||
create: mockMovementCreate,
|
||||
},
|
||||
quote: {
|
||||
findFirst: mock(),
|
||||
},
|
||||
@@ -30,6 +45,7 @@ mock.module("../src/lib/prisma", () => ({
|
||||
|
||||
beforeEach(() => {
|
||||
for (const model of Object.values(mockPrisma)) {
|
||||
if (typeof model === "function") continue;
|
||||
for (const fn of Object.values(
|
||||
model as Record<string, ReturnType<typeof mock>>,
|
||||
)) {
|
||||
@@ -155,6 +171,7 @@ describe("schemas", () => {
|
||||
description: "Ropa",
|
||||
amount: 2500,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
walletId: 1,
|
||||
});
|
||||
expect(result.description).toBe("Ropa");
|
||||
});
|
||||
@@ -175,12 +192,16 @@ describe("schemas", () => {
|
||||
const result = payExpenseSchema.parse({
|
||||
amountPayed: 1400,
|
||||
paymentDate: "2026-06-10T00:00:00.000Z",
|
||||
walletId: 1,
|
||||
});
|
||||
expect(result.amountPayed).toBe(1400);
|
||||
});
|
||||
|
||||
test("allows missing paymentDate", () => {
|
||||
const result = payExpenseSchema.parse({ amountPayed: 1400 });
|
||||
const result = payExpenseSchema.parse({
|
||||
amountPayed: 1400,
|
||||
walletId: 1,
|
||||
});
|
||||
expect(result.amountPayed).toBe(1400);
|
||||
expect(result.paymentDate).toBeUndefined();
|
||||
});
|
||||
@@ -407,6 +428,7 @@ describe("createNonPeriodicExpense", () => {
|
||||
description: "Ropa",
|
||||
amount: 2500,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
walletId: 1,
|
||||
};
|
||||
const created = {
|
||||
id: 1,
|
||||
@@ -417,7 +439,11 @@ describe("createNonPeriodicExpense", () => {
|
||||
year: 2026,
|
||||
month: 6,
|
||||
};
|
||||
const wallet = { id: 1, balance: 10000 };
|
||||
mockPrisma.expense.create.mockResolvedValueOnce(created);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 7500 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await createNonPeriodicExpense(input);
|
||||
expect(result.status).toBe(PaymentStatus.PAYED);
|
||||
@@ -436,23 +462,48 @@ describe("createNonPeriodicExpense", () => {
|
||||
|
||||
describe("payExpense", () => {
|
||||
test("updates status to PAYED with amountPayed and paymentDate", async () => {
|
||||
const expense = {
|
||||
id: 1,
|
||||
description: "Ropa",
|
||||
status: PaymentStatus.PENDING,
|
||||
amount: 1400,
|
||||
amountPayed: null,
|
||||
paymentDate: null,
|
||||
};
|
||||
const paidExpense = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amountPayed: 1400,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
};
|
||||
const wallet = { id: 1, balance: 10000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(expense);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockResolvedValueOnce(paidExpense);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 8600 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await payExpense(1, {
|
||||
amountPayed: 1400,
|
||||
paymentDate: "2026-06-10T00:00:00.000Z",
|
||||
walletId: 1,
|
||||
});
|
||||
expect(result.status).toBe(PaymentStatus.PAYED);
|
||||
expect(result.amountPayed).toBe(1400);
|
||||
});
|
||||
|
||||
test("defaults paymentDate to now when not provided", async () => {
|
||||
const expense = {
|
||||
id: 1,
|
||||
description: "Ropa",
|
||||
status: PaymentStatus.PENDING,
|
||||
amount: 1500,
|
||||
amountPayed: null,
|
||||
paymentDate: null,
|
||||
};
|
||||
const wallet = { id: 1, balance: 10000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(expense);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({
|
||||
id: where.id,
|
||||
@@ -462,8 +513,10 @@ describe("payExpense", () => {
|
||||
paymentDate: data.paymentDate,
|
||||
}),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 8500 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await payExpense(1, { amountPayed: 1500 });
|
||||
const result = await payExpense(1, { amountPayed: 1500, walletId: 1 });
|
||||
expect(result.status).toBe(PaymentStatus.PAYED);
|
||||
expect(result.paymentDate).toBeInstanceOf(Date);
|
||||
});
|
||||
@@ -739,7 +792,18 @@ describe("getBeloSellPriceForDate", () => {
|
||||
|
||||
describe("payExpense with BELO", () => {
|
||||
test("sets beloPrice and usdcEquivalent when BELO quote is available", async () => {
|
||||
const expense = {
|
||||
id: 1,
|
||||
description: "Ropa",
|
||||
status: PaymentStatus.PENDING,
|
||||
amount: 15000,
|
||||
amountPayed: null,
|
||||
paymentDate: null,
|
||||
};
|
||||
const paymentDate = new Date("2026-06-10T00:00:00.000Z");
|
||||
const wallet = { id: 1, balance: 100000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(expense);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.quoteHistory.findFirst
|
||||
.mockResolvedValueOnce({
|
||||
sell: "1500.00",
|
||||
@@ -757,10 +821,13 @@ describe("payExpense with BELO", () => {
|
||||
usdcEquivalent: data.usdcEquivalent,
|
||||
}),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 85000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await payExpense(1, {
|
||||
amountPayed: 15000,
|
||||
paymentDate: "2026-06-10T00:00:00.000Z",
|
||||
walletId: 1,
|
||||
});
|
||||
|
||||
expect(result.beloPrice).toBe(1500);
|
||||
@@ -768,6 +835,17 @@ describe("payExpense with BELO", () => {
|
||||
});
|
||||
|
||||
test("does not set BELO fields when no quote is available", async () => {
|
||||
const expense = {
|
||||
id: 1,
|
||||
description: "Ropa",
|
||||
status: PaymentStatus.PENDING,
|
||||
amount: 15000,
|
||||
amountPayed: null,
|
||||
paymentDate: null,
|
||||
};
|
||||
const wallet = { id: 1, balance: 100000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(expense);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.quoteHistory.findFirst
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce(null);
|
||||
@@ -780,10 +858,13 @@ describe("payExpense with BELO", () => {
|
||||
paymentDate: data.paymentDate,
|
||||
}),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 85000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await payExpense(1, {
|
||||
amountPayed: 15000,
|
||||
paymentDate: "2026-06-10T00:00:00.000Z",
|
||||
walletId: 1,
|
||||
});
|
||||
|
||||
expect(result.beloPrice).toBeUndefined();
|
||||
@@ -794,6 +875,8 @@ describe("payExpense with BELO", () => {
|
||||
describe("createNonPeriodicExpense with BELO", () => {
|
||||
test("sets beloPrice and usdcEquivalent when BELO quote is available", async () => {
|
||||
const dueDate = "2026-06-15T00:00:00.000Z";
|
||||
const wallet = { id: 1, balance: 100000, currency: "ARS" };
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.quoteHistory.findFirst
|
||||
.mockResolvedValueOnce({
|
||||
sell: "1500.00",
|
||||
@@ -812,11 +895,14 @@ describe("createNonPeriodicExpense with BELO", () => {
|
||||
usdcEquivalent: data.usdcEquivalent,
|
||||
}),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 70000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await createNonPeriodicExpense({
|
||||
description: "Ropa",
|
||||
amount: 30000,
|
||||
dueDate,
|
||||
walletId: 1,
|
||||
});
|
||||
|
||||
expect(result.beloPrice).toBe(1500);
|
||||
@@ -824,6 +910,8 @@ describe("createNonPeriodicExpense with BELO", () => {
|
||||
});
|
||||
|
||||
test("does not set BELO fields when no quote is available", async () => {
|
||||
const wallet = { id: 1, balance: 100000, currency: "ARS" };
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.quoteHistory.findFirst
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce(null);
|
||||
@@ -837,11 +925,14 @@ describe("createNonPeriodicExpense with BELO", () => {
|
||||
status: PaymentStatus.PAYED,
|
||||
}),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 97500 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await createNonPeriodicExpense({
|
||||
description: "Ropa",
|
||||
amount: 2500,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
walletId: 1,
|
||||
});
|
||||
|
||||
expect(result.beloPrice).toBeUndefined();
|
||||
@@ -938,3 +1029,402 @@ describe("updateExpense with BELO", () => {
|
||||
expect(mockPrisma.quoteHistory.findFirst).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateExpense wallet adjustments", () => {
|
||||
test("creates WITHDRAWAL when amount increases on a PAYED expense", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
const wallet = { id: 1, balance: 100000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 90000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 35000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(result.amount).toBe(35000);
|
||||
expect(mockWalletUpdate).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { balance: { increment: -10000 } },
|
||||
});
|
||||
expect(mockMovementCreate).toHaveBeenCalledWith({
|
||||
data: {
|
||||
walletId: 1,
|
||||
type: "WITHDRAWAL",
|
||||
amount: -10000,
|
||||
description: "Ajuste por edición de gasto",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("creates DEPOSIT when amount decreases on a PAYED expense", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 35000,
|
||||
amountPayed: 35000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
const wallet = { id: 1, balance: 75000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 85000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 25000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(result.amount).toBe(25000);
|
||||
expect(mockWalletUpdate).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { balance: { increment: 10000 } },
|
||||
});
|
||||
expect(mockMovementCreate).toHaveBeenCalledWith({
|
||||
data: {
|
||||
walletId: 1,
|
||||
type: "DEPOSIT",
|
||||
amount: 10000,
|
||||
description: "Ajuste por edición de gasto",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("does not create wallet adjustment when amount is unchanged", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 25000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(result.amount).toBe(25000);
|
||||
expect(mockWalletUpdate).not.toHaveBeenCalled();
|
||||
expect(mockMovementCreate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("does not create wallet adjustment when existing expense has no walletId", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
};
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 35000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(result.amount).toBe(35000);
|
||||
expect(mockWalletUpdate).not.toHaveBeenCalled();
|
||||
expect(mockMovementCreate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("creates DEPOSIT refund when reverting PAYED expense to PENDING with walletId", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
const wallet = { id: 1, balance: 75000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 100000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 25000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
status: "PENDING",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(PaymentStatus.PENDING);
|
||||
expect(result.amountPayed).toBeNull();
|
||||
expect(mockWalletUpdate).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { balance: { increment: 25000 } },
|
||||
});
|
||||
expect(mockMovementCreate).toHaveBeenCalledWith({
|
||||
data: {
|
||||
walletId: 1,
|
||||
type: "DEPOSIT",
|
||||
amount: 25000,
|
||||
description: "Anulación de gasto",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("does not create refund when existing expense has no walletId for PENDING revert", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
};
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 25000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
status: "PENDING",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(PaymentStatus.PENDING);
|
||||
expect(mockWalletUpdate).not.toHaveBeenCalled();
|
||||
expect(mockMovementCreate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("throws when wallet is not found during adjustment", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(null);
|
||||
|
||||
expect(
|
||||
updateExpense(1, {
|
||||
amount: 35000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
}),
|
||||
).rejects.toThrow("Billetera no encontrada");
|
||||
});
|
||||
|
||||
test("only creates refund (not amount adjustment) when both amount and status change", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
const wallet = { id: 1, balance: 75000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 100000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 35000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
status: "PENDING",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(PaymentStatus.PENDING);
|
||||
expect(mockWalletUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(mockWalletUpdate).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { balance: { increment: 25000 } },
|
||||
});
|
||||
expect(mockMovementCreate).toHaveBeenCalledWith({
|
||||
data: {
|
||||
walletId: 1,
|
||||
type: "DEPOSIT",
|
||||
amount: 25000,
|
||||
description: "Anulación de gasto",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("uses amountPayed for refund when expense has different amountPayed and amount", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 30000,
|
||||
amountPayed: 28000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
const wallet = { id: 1, balance: 75000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 103000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 30000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
status: "PENDING",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(PaymentStatus.PENDING);
|
||||
expect(mockWalletUpdate).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { balance: { increment: 28000 } },
|
||||
});
|
||||
expect(mockMovementCreate).toHaveBeenCalledWith({
|
||||
data: {
|
||||
walletId: 1,
|
||||
type: "DEPOSIT",
|
||||
amount: 28000,
|
||||
description: "Anulación de gasto",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("creates WITHDRAWAL when amountPayed increases while amount stays the same", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
const wallet = { id: 1, balance: 75000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 65000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 25000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
amountPayed: 35000,
|
||||
});
|
||||
|
||||
expect(result.amount).toBe(25000);
|
||||
expect(mockWalletUpdate).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { balance: { increment: -10000 } },
|
||||
});
|
||||
expect(mockMovementCreate).toHaveBeenCalledWith({
|
||||
data: {
|
||||
walletId: 1,
|
||||
type: "WITHDRAWAL",
|
||||
amount: -10000,
|
||||
description: "Ajuste por edición de gasto",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("creates adjustment based on amountPayed when both amount and amountPayed change", async () => {
|
||||
const existing = {
|
||||
id: 1,
|
||||
status: PaymentStatus.PAYED,
|
||||
amount: 25000,
|
||||
amountPayed: 25000,
|
||||
paymentDate: new Date("2026-06-10T00:00:00.000Z"),
|
||||
dueDate: new Date("2026-06-15T00:00:00.000Z"),
|
||||
year: 2026,
|
||||
month: 6,
|
||||
walletId: 1,
|
||||
};
|
||||
const wallet = { id: 1, balance: 75000, currency: "ARS" };
|
||||
mockPrisma.expense.findUniqueOrThrow.mockResolvedValueOnce(existing);
|
||||
mockWalletFindUnique.mockResolvedValueOnce(wallet);
|
||||
mockPrisma.expense.update.mockImplementationOnce(
|
||||
async ({ where, data }) => ({ id: where.id, ...existing, ...data }),
|
||||
);
|
||||
mockWalletUpdate.mockResolvedValueOnce({ ...wallet, balance: 72000 });
|
||||
mockMovementCreate.mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const result = await updateExpense(1, {
|
||||
amount: 30000,
|
||||
dueDate: "2026-06-15T00:00:00.000Z",
|
||||
amountPayed: 28000,
|
||||
});
|
||||
|
||||
expect(result.amount).toBe(30000);
|
||||
expect(mockWalletUpdate).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { balance: { increment: -3000 } },
|
||||
});
|
||||
expect(mockMovementCreate).toHaveBeenCalledWith({
|
||||
data: {
|
||||
walletId: 1,
|
||||
type: "WITHDRAWAL",
|
||||
amount: -3000,
|
||||
description: "Ajuste por edición de gasto",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user