feat(expenses): enhance expenses management with filtering and search capabilities
This commit is contained in:
@@ -88,12 +88,20 @@ export async function listExpenses(params: {
|
||||
status?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
periodicExpenseId?: number;
|
||||
search?: string;
|
||||
}) {
|
||||
const { status, page = 1, pageSize = 10 } = params;
|
||||
const { status, page = 1, pageSize = 10, periodicExpenseId, search } = params;
|
||||
const where: Record<string, unknown> = {};
|
||||
if (status === "PENDING" || status === "PAYED") {
|
||||
where.status = status;
|
||||
}
|
||||
if (periodicExpenseId !== undefined) {
|
||||
where.periodicExpenseId = periodicExpenseId;
|
||||
}
|
||||
if (search) {
|
||||
where.description = { contains: search, mode: "insensitive" };
|
||||
}
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.expense.findMany({
|
||||
where,
|
||||
|
||||
@@ -5,6 +5,14 @@ export async function listExpensesHandler(c: Context) {
|
||||
const status = c.req.query("status");
|
||||
const page = parseInt(c.req.query("page") ?? "1", 10);
|
||||
const pageSize = parseInt(c.req.query("pageSize") ?? "10", 10);
|
||||
const result = await listExpenses({ status, page, pageSize });
|
||||
const periodicExpenseId = c.req.query("periodicExpenseId");
|
||||
const search = c.req.query("search");
|
||||
const result = await listExpenses({
|
||||
status,
|
||||
page,
|
||||
pageSize,
|
||||
periodicExpenseId: periodicExpenseId ? parseInt(periodicExpenseId, 10) : undefined,
|
||||
search: search || undefined,
|
||||
});
|
||||
return c.json(result);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const mockPrisma = {
|
||||
findFirst: mock(),
|
||||
create: mock(),
|
||||
update: mock(),
|
||||
count: mock(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,6 +27,7 @@ beforeEach(() => {
|
||||
fn.mockClear();
|
||||
}
|
||||
}
|
||||
mockPrisma.expense.count.mockResolvedValue(0);
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -229,7 +231,7 @@ describe("listPeriodicExpenses", () => {
|
||||
expect(result).toEqual(mockItems);
|
||||
expect(mockPrisma.periodicExpense.findMany).toHaveBeenCalledWith({
|
||||
where: { isDeleted: false },
|
||||
orderBy: { createdAt: "desc" },
|
||||
orderBy: { description: "asc" },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -289,21 +291,65 @@ describe("softDeletePeriodicExpense", () => {
|
||||
describe("listExpenses", () => {
|
||||
test("returns all expenses when no status filter", async () => {
|
||||
mockPrisma.expense.findMany.mockResolvedValueOnce([]);
|
||||
await listExpenses();
|
||||
await listExpenses({});
|
||||
expect(mockPrisma.expense.findMany).toHaveBeenCalledWith({
|
||||
where: {},
|
||||
include: { periodicExpense: true },
|
||||
orderBy: { dueDate: "asc" },
|
||||
orderBy: { dueDate: "desc" },
|
||||
skip: 0,
|
||||
take: 10,
|
||||
});
|
||||
});
|
||||
|
||||
test("filters by status when provided", async () => {
|
||||
mockPrisma.expense.findMany.mockResolvedValueOnce([]);
|
||||
await listExpenses("PENDING");
|
||||
await listExpenses({ status: "PENDING" });
|
||||
expect(mockPrisma.expense.findMany).toHaveBeenCalledWith({
|
||||
where: { status: "PENDING" },
|
||||
include: { periodicExpense: true },
|
||||
orderBy: { dueDate: "asc" },
|
||||
orderBy: { dueDate: "desc" },
|
||||
skip: 0,
|
||||
take: 10,
|
||||
});
|
||||
});
|
||||
|
||||
test("filters by periodicExpenseId when provided", async () => {
|
||||
mockPrisma.expense.findMany.mockResolvedValueOnce([]);
|
||||
await listExpenses({ periodicExpenseId: 3 });
|
||||
expect(mockPrisma.expense.findMany).toHaveBeenCalledWith({
|
||||
where: { periodicExpenseId: 3 },
|
||||
include: { periodicExpense: true },
|
||||
orderBy: { dueDate: "desc" },
|
||||
skip: 0,
|
||||
take: 10,
|
||||
});
|
||||
});
|
||||
|
||||
test("filters by search query with case-insensitive contains", async () => {
|
||||
mockPrisma.expense.findMany.mockResolvedValueOnce([]);
|
||||
await listExpenses({ search: "Gim" });
|
||||
expect(mockPrisma.expense.findMany).toHaveBeenCalledWith({
|
||||
where: { description: { contains: "Gim", mode: "insensitive" } },
|
||||
include: { periodicExpense: true },
|
||||
orderBy: { dueDate: "desc" },
|
||||
skip: 0,
|
||||
take: 10,
|
||||
});
|
||||
});
|
||||
|
||||
test("combines status, periodicExpenseId, and search filters", async () => {
|
||||
mockPrisma.expense.findMany.mockResolvedValueOnce([]);
|
||||
await listExpenses({ status: "PENDING", periodicExpenseId: 1, search: "Agua" });
|
||||
expect(mockPrisma.expense.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
status: "PENDING",
|
||||
periodicExpenseId: 1,
|
||||
description: { contains: "Agua", mode: "insensitive" },
|
||||
},
|
||||
include: { periodicExpense: true },
|
||||
orderBy: { dueDate: "desc" },
|
||||
skip: 0,
|
||||
take: 10,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user