diff --git a/src/features/transactions/application/dtos/transaction.dto.ts b/src/features/transactions/application/dtos/transaction.dto.ts index 48b1abc..728f8b3 100644 --- a/src/features/transactions/application/dtos/transaction.dto.ts +++ b/src/features/transactions/application/dtos/transaction.dto.ts @@ -58,8 +58,8 @@ export const updateTransactionSchema = transactionBaseSchema }); export const transactionFiltersSchema = z.object({ - startDate: z.string().datetime().optional(), - endDate: z.string().datetime().optional(), + startDate: z.string().date().optional(), + endDate: z.string().date().optional(), type: z.enum(["INCOME", "EXPENSE"]).optional(), category_id: z.coerce.number().int().positive().optional(), payment_method_id: z.coerce.number().int().positive().optional(), diff --git a/src/features/transactions/application/services/transactions.service.ts b/src/features/transactions/application/services/transactions.service.ts index d7401d4..0eb2ffe 100644 --- a/src/features/transactions/application/services/transactions.service.ts +++ b/src/features/transactions/application/services/transactions.service.ts @@ -4,402 +4,405 @@ import { createHandler } from "@/core/infrastructure/lib/handler.wrapper,"; import { TransactionApiAdapter } from "@/transactions/infrastructure/adapters/transaction-api.adapter"; import * as HttpStatusCodes from "stoker/http-status-codes"; import { - CreateRoute, - DeleteRoute, - FilterTransactionsRoute, - GetByIdRoute, - GetCategoryTotalsRoute, - GetMonthlyBalanceRoute, - GetMonthlyTrendsRoute, - ListByUserRoute, - ListRoute, - UpdateRoute, + CreateRoute, + DeleteRoute, + FilterTransactionsRoute, + GetByIdRoute, + GetCategoryTotalsRoute, + GetMonthlyBalanceRoute, + GetMonthlyTrendsRoute, + ListByUserRoute, + ListRoute, + UpdateRoute, } from "@/transactions/infrastructure/controllers/transaction.routes"; import { TransactionUtilsService } from "./transactions-utils.service"; export class TransactionService implements ITransactionService { - private static instance: TransactionService; - - constructor( - private readonly transactionRepository: ITransactionRepository, - private readonly transactionUtils: TransactionUtilsService - ) {} - - public static getInstance( - transactionRepository: ITransactionRepository, - transactionUtils: TransactionUtilsService - ): TransactionService { - if (!TransactionService.instance) { - TransactionService.instance = new TransactionService( - transactionRepository, - transactionUtils - ); - } - return TransactionService.instance; - } - - getAll = createHandler(async (c) => { - const transactions = await this.transactionRepository.findAll(); - return c.json( - { - success: true, - data: TransactionApiAdapter.toApiResponseList(transactions), - message: "Transactions retrieved successfully", - }, - HttpStatusCodes.OK - ); - }); - - getById = createHandler(async (c) => { - const id = c.req.param("id"); - const transaction = await this.transactionRepository.findById(Number(id)); - - if (!transaction) { - return c.json( - { - success: false, - data: null, - message: "Transaction not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - return c.json( - { - success: true, - data: TransactionApiAdapter.toApiResponse(transaction), - message: "Transaction retrieved successfully", - }, - HttpStatusCodes.OK - ); - }); - - getByUserId = createHandler(async (c) => { - const userId = c.req.param("userId"); - - const userValidation = await this.transactionUtils.validateUser( - Number(userId) - ); - if (!userValidation.isValid) { - return c.json( - { - success: false, - data: null, - message: "User not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - const transactions = await this.transactionRepository.findByUserId( - Number(userId) - ); - return c.json( - { - success: true, - data: TransactionApiAdapter.toApiResponseList(transactions), - message: "User transactions retrieved successfully", - }, - HttpStatusCodes.OK - ); - }); - - getFiltered = createHandler(async (c) => { - const userId = c.req.param("userId"); - const filters = c.req.valid("query"); - - const userValidation = await this.transactionUtils.validateUser( - Number(userId) - ); - if (!userValidation.isValid) { - return c.json( - { - success: false, - data: null, - message: "User not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - const transactions = await this.transactionRepository.findByFilters( - Number(userId), - filters - ); - - return c.json( - { - success: true, - data: TransactionApiAdapter.toApiResponseList(transactions), - message: "Filtered transactions retrieved successfully", - }, - HttpStatusCodes.OK - ); - }); - - create = createHandler(async (c) => { - const data = c.req.valid("json"); - - // Validar que el usuario existe - const userValidation = await this.transactionUtils.validateUser( - data.user_id - ); - if (!userValidation.isValid) { - return c.json( - { - success: false, - data: null, - message: "User not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - // Si se proporciona un método de pago, validar que existe y pertenece al usuario - if (data.payment_method_id) { - const paymentMethodValidation = - await this.transactionUtils.validatePaymentMethod( - data.payment_method_id, - data.user_id - ); - if (!paymentMethodValidation.isValid) { - return c.json( - { - success: false, - data: null, - message: - paymentMethodValidation.message || "Invalid payment method", - }, - HttpStatusCodes.BAD_REQUEST - ); - } - } - - const transaction = await this.transactionRepository.create({ - userId: data.user_id, - amount: data.amount, - type: data.type, - categoryId: data.category_id || null, - description: data.description, - paymentMethodId: data.payment_method_id, - scheduledTransactionId: data.scheduled_transaction_id, - debtId: data.debt_id, - }); - - return c.json( - { - success: true, - data: TransactionApiAdapter.toApiResponse(transaction), - message: "Transaction created successfully", - }, - HttpStatusCodes.CREATED - ); - }); - - update = createHandler(async (c) => { - const id = c.req.param("id"); - const data = c.req.valid("json"); - - console.log(data); - - const transaction = await this.transactionRepository.findById(Number(id)); - if (!transaction) { - return c.json( - { - success: false, - data: null, - message: "Transaction not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - if (data.payment_method_id !== undefined) { - if (data.payment_method_id !== null) { - const paymentMethodValidation = - await this.transactionUtils.validatePaymentMethod( - data.payment_method_id, - transaction.userId - ); - if (!paymentMethodValidation.isValid) { - return c.json( - { - success: false, - data: null, - message: - paymentMethodValidation.message || "Invalid payment method", - }, - HttpStatusCodes.BAD_REQUEST - ); - } - } - } - - const updatedTransaction = await this.transactionRepository.update( - Number(id), - { - amount: data.amount, - type: data.type, - categoryId: data.category_id, - description: data.description, - paymentMethodId: data.payment_method_id, - scheduledTransactionId: data.scheduled_transaction_id, - debtId: data.debt_id, - contributionId: data.contribution_id, - } - ); - - return c.json( - { - success: true, - data: TransactionApiAdapter.toApiResponse(updatedTransaction), - message: "Transaction updated successfully", - }, - HttpStatusCodes.OK - ); - }); - - delete = createHandler(async (c) => { - const id = c.req.param("id"); - const transaction = await this.transactionRepository.findById(Number(id)); - - if (!transaction) { - return c.json( - { - success: false, - data: null, - message: "Transaction not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - const deleted = await this.transactionRepository.delete(Number(id)); - return c.json( - { - success: true, - data: { deleted }, - message: "Transaction deleted successfully", - }, - HttpStatusCodes.OK - ); - }); - - getMonthlyBalance = createHandler(async (c) => { - const userId = c.req.param("userId"); - const { month } = c.req.valid("query"); - - const userValidation = await this.transactionUtils.validateUser( - Number(userId) - ); - if (!userValidation.isValid) { - return c.json( - { - success: false, - data: null, - message: "User not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - const balance = await this.transactionRepository.getMonthlyBalance( - Number(userId), - new Date(month) - ); - - return c.json( - { - success: true, - data: balance, - message: "Monthly balance retrieved successfully", - }, - HttpStatusCodes.OK - ); - }); - - getCategoryTotals = createHandler(async (c) => { - const userId = c.req.param("userId"); - const { startDate, endDate } = c.req.valid("query"); - - const userValidation = await this.transactionUtils.validateUser( - Number(userId) - ); - if (!userValidation.isValid) { - return c.json( - { - success: false, - data: null, - message: "User not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - const totals = await this.transactionRepository.getCategoryTotals( - Number(userId), - new Date(startDate), - new Date(endDate) - ); - - return c.json( - { - success: true, - data: totals.map((t) => ({ - category: t.categoryId.toString(), - total: t.total, - })), - message: "Category totals retrieved successfully", - }, - HttpStatusCodes.OK - ); - }); - - getMonthlyTrends = createHandler(async (c) => { - const userId = c.req.param("userId"); - - const userValidation = await this.transactionUtils.validateUser( - Number(userId) - ); - if (!userValidation.isValid) { - return c.json( - { - success: false, - data: null, - message: "User not found", - }, - HttpStatusCodes.NOT_FOUND - ); - } - - try { - const trends = await this.transactionRepository.getMonthlyTrends( - Number(userId) - ); - - return c.json( - { - success: true, - data: trends.map((t) => ({ - month: t.month, - income: t.income, - expense: t.expense, - })), - message: "Monthly trends retrieved successfully", - }, - HttpStatusCodes.OK - ); - } catch (error) { - console.error("Error getting monthly trends:", error); - return c.json( - { - success: false, - data: null, - message: "Error retrieving monthly trends", - }, - HttpStatusCodes.INTERNAL_SERVER_ERROR - ); - } - }); + private static instance: TransactionService; + + constructor( + private readonly transactionRepository: ITransactionRepository, + private readonly transactionUtils: TransactionUtilsService + ) {} + + public static getInstance( + transactionRepository: ITransactionRepository, + transactionUtils: TransactionUtilsService + ): TransactionService { + if (!TransactionService.instance) { + TransactionService.instance = new TransactionService( + transactionRepository, + transactionUtils + ); + } + return TransactionService.instance; + } + + getAll = createHandler(async (c) => { + const transactions = await this.transactionRepository.findAll(); + return c.json( + { + success: true, + data: TransactionApiAdapter.toApiResponseList(transactions), + message: "Transactions retrieved successfully", + }, + HttpStatusCodes.OK + ); + }); + + getById = createHandler(async (c) => { + const id = c.req.param("id"); + const transaction = await this.transactionRepository.findById(Number(id)); + + if (!transaction) { + return c.json( + { + success: false, + data: null, + message: "Transaction not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + return c.json( + { + success: true, + data: TransactionApiAdapter.toApiResponse(transaction), + message: "Transaction retrieved successfully", + }, + HttpStatusCodes.OK + ); + }); + + getByUserId = createHandler(async (c) => { + const userId = c.req.param("userId"); + + const userValidation = await this.transactionUtils.validateUser( + Number(userId) + ); + if (!userValidation.isValid) { + return c.json( + { + success: false, + data: null, + message: "User not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + const transactions = await this.transactionRepository.findByUserId( + Number(userId) + ); + return c.json( + { + success: true, + data: TransactionApiAdapter.toApiResponseList(transactions), + message: "User transactions retrieved successfully", + }, + HttpStatusCodes.OK + ); + }); + + getFiltered = createHandler(async (c) => { + const userId = c.req.param("userId"); + const filters = c.req.valid("query"); + + const userValidation = await this.transactionUtils.validateUser( + Number(userId) + ); + if (!userValidation.isValid) { + return c.json( + { + success: false, + data: null, + message: "User not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + const transactions = await this.transactionRepository.findByFilters( + Number(userId), + filters + ); + + return c.json( + { + success: true, + data: TransactionApiAdapter.toApiResponseList(transactions), + message: "Filtered transactions retrieved successfully", + }, + HttpStatusCodes.OK + ); + }); + + create = createHandler(async (c) => { + const data = c.req.valid("json"); + + // Validar que el usuario existe + const userValidation = await this.transactionUtils.validateUser( + data.user_id + ); + if (!userValidation.isValid) { + return c.json( + { + success: false, + data: null, + message: "User not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + // Si se proporciona un método de pago, validar que existe y pertenece al usuario + if (data.payment_method_id) { + const paymentMethodValidation = + await this.transactionUtils.validatePaymentMethod( + data.payment_method_id, + data.user_id + ); + if (!paymentMethodValidation.isValid) { + return c.json( + { + success: false, + data: null, + message: + paymentMethodValidation.message || "Invalid payment method", + }, + HttpStatusCodes.BAD_REQUEST + ); + } + } + + const transaction = await this.transactionRepository.create({ + userId: data.user_id, + amount: data.amount, + type: data.type, + categoryId: data.category_id || null, + category: null, + description: data.description, + paymentMethodId: data.payment_method_id, + paymentMethod: null, + scheduledTransactionId: data.scheduled_transaction_id, + debtId: data.debt_id, + }); + + return c.json( + { + success: true, + data: TransactionApiAdapter.toApiResponse(transaction), + message: "Transaction created successfully", + }, + HttpStatusCodes.CREATED + ); + }); + + update = createHandler(async (c) => { + const id = c.req.param("id"); + const data = c.req.valid("json"); + + console.log(data); + + const transaction = await this.transactionRepository.findById(Number(id)); + if (!transaction) { + return c.json( + { + success: false, + data: null, + message: "Transaction not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + if (data.payment_method_id !== undefined) { + if (data.payment_method_id !== null) { + const paymentMethodValidation = + await this.transactionUtils.validatePaymentMethod( + data.payment_method_id, + transaction.userId + ); + if (!paymentMethodValidation.isValid) { + return c.json( + { + success: false, + data: null, + message: + paymentMethodValidation.message || "Invalid payment method", + }, + HttpStatusCodes.BAD_REQUEST + ); + } + } + } + + const updatedTransaction = await this.transactionRepository.update( + Number(id), + { + amount: data.amount, + type: data.type, + categoryId: data.category_id, + description: data.description, + paymentMethodId: data.payment_method_id, + scheduledTransactionId: data.scheduled_transaction_id, + debtId: data.debt_id, + contributionId: data.contribution_id, + } + ); + + return c.json( + { + success: true, + data: TransactionApiAdapter.toApiResponse(updatedTransaction), + message: "Transaction updated successfully", + }, + HttpStatusCodes.OK + ); + }); + + delete = createHandler(async (c) => { + const id = c.req.param("id"); + const transaction = await this.transactionRepository.findById(Number(id)); + + if (!transaction) { + return c.json( + { + success: false, + data: null, + message: "Transaction not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + const deleted = await this.transactionRepository.delete(Number(id)); + return c.json( + { + success: true, + data: { deleted }, + message: "Transaction deleted successfully", + }, + HttpStatusCodes.OK + ); + }); + + getMonthlyBalance = createHandler(async (c) => { + const userId = c.req.param("userId"); + const { month } = c.req.valid("query"); + + const userValidation = await this.transactionUtils.validateUser( + Number(userId) + ); + if (!userValidation.isValid) { + return c.json( + { + success: false, + data: null, + message: "User not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + const balance = await this.transactionRepository.getMonthlyBalance( + Number(userId), + new Date(month) + ); + + return c.json( + { + success: true, + data: balance, + message: "Monthly balance retrieved successfully", + }, + HttpStatusCodes.OK + ); + }); + + getCategoryTotals = createHandler(async (c) => { + const userId = c.req.param("userId"); + const { startDate, endDate } = c.req.valid("query"); + + const userValidation = await this.transactionUtils.validateUser( + Number(userId) + ); + if (!userValidation.isValid) { + return c.json( + { + success: false, + data: null, + message: "User not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + const totals = await this.transactionRepository.getCategoryTotals( + Number(userId), + new Date(startDate), + new Date(endDate) + ); + + return c.json( + { + success: true, + data: totals.map((t) => ({ + category: t.categoryId.toString(), + categoryName: t.categoryName, + total: t.total, + })), + message: "Category totals retrieved successfully", + }, + HttpStatusCodes.OK + ); + }); + + getMonthlyTrends = createHandler(async (c) => { + const userId = c.req.param("userId"); + + const userValidation = await this.transactionUtils.validateUser( + Number(userId) + ); + if (!userValidation.isValid) { + return c.json( + { + success: false, + data: null, + message: "User not found", + }, + HttpStatusCodes.NOT_FOUND + ); + } + + try { + const trends = await this.transactionRepository.getMonthlyTrends( + Number(userId) + ); + + return c.json( + { + success: true, + data: trends.map((t) => ({ + month: t.month, + income: t.income, + expense: t.expense, + })), + message: "Monthly trends retrieved successfully", + }, + HttpStatusCodes.OK + ); + } catch (error) { + console.error("Error getting monthly trends:", error); + return c.json( + { + success: false, + data: null, + message: "Error retrieving monthly trends", + }, + HttpStatusCodes.INTERNAL_SERVER_ERROR + ); + } + }); } diff --git a/src/features/transactions/domain/entities/ITrends.ts b/src/features/transactions/domain/entities/ITrends.ts index db6a5a2..2d470e0 100644 --- a/src/features/transactions/domain/entities/ITrends.ts +++ b/src/features/transactions/domain/entities/ITrends.ts @@ -9,3 +9,9 @@ export interface MonthlyTrendData { income: number; expense: number; } + +export interface CategoryTotalData { + categoryId: number; + categoryName: string | null; + total: number; +} diff --git a/src/features/transactions/domain/ports/transaction-repository.port.ts b/src/features/transactions/domain/ports/transaction-repository.port.ts index 43ac222..aee5d66 100644 --- a/src/features/transactions/domain/ports/transaction-repository.port.ts +++ b/src/features/transactions/domain/ports/transaction-repository.port.ts @@ -28,6 +28,7 @@ export interface ITransactionRepository { ): Promise< Array<{ categoryId: number; + categoryName: string | null; total: number; }> >; diff --git a/src/features/transactions/infrastructure/adapters/transaction.repository.ts b/src/features/transactions/infrastructure/adapters/transaction.repository.ts index 7e26640..9dd4aa7 100644 --- a/src/features/transactions/infrastructure/adapters/transaction.repository.ts +++ b/src/features/transactions/infrastructure/adapters/transaction.repository.ts @@ -5,586 +5,608 @@ import { ITransactionRepository } from "../../domain/ports/transaction-repositor import { ITransaction } from "../../domain/entities/ITransaction"; import { TransactionFilters } from "../../application/dtos/transaction.dto"; import { MonthlyTrendData } from "@/transactions/domain/entities/ITrends"; +import { setEndOfDay, setStartOfDay } from "@/shared/utils/date.utils"; export class PgTransactionRepository implements ITransactionRepository { - private db = DatabaseConnection.getInstance().db; - private static instance: PgTransactionRepository; - - private constructor() {} - - public static getInstance(): PgTransactionRepository { - if (!PgTransactionRepository.instance) { - PgTransactionRepository.instance = new PgTransactionRepository(); - } - return PgTransactionRepository.instance; - } - - async findAll(): Promise { - const result = await this.db - .select({ - id: transactions.id, - user_id: transactions.user_id, - amount: transactions.amount, - type: transactions.type, - category_id: transactions.category_id, - category: { - id: categories.id, - name: categories.name, - description: categories.description, - }, - description: transactions.description, - payment_method_id: transactions.payment_method_id, - payment_method: { - id: payment_methods.id, - name: payment_methods.name, - type: payment_methods.type, - last_four_digits: payment_methods.last_four_digits, - user_id: payment_methods.user_id, - }, - date: transactions.date, - scheduled_transaction_id: transactions.scheduled_transaction_id, - debt_id: transactions.debt_id, - budget_id: transactions.budget_id, - contribution_id: transactions.contribution_id, - }) - .from(transactions) - .leftJoin(categories, eq(transactions.category_id, categories.id)) - .leftJoin(payment_methods, eq(transactions.payment_method_id, payment_methods.id)); - return result.map(this.mapToEntity); - } - - async findById(id: number): Promise { - const result = await this.db - .select({ - id: transactions.id, - user_id: transactions.user_id, - amount: transactions.amount, - type: transactions.type, - category_id: transactions.category_id, - category: { - id: categories.id, - name: categories.name, - description: categories.description, - }, - description: transactions.description, - payment_method_id: transactions.payment_method_id, - payment_method: { - id: payment_methods.id, - name: payment_methods.name, - type: payment_methods.type, - last_four_digits: payment_methods.last_four_digits, - user_id: payment_methods.user_id, - }, - date: transactions.date, - scheduled_transaction_id: transactions.scheduled_transaction_id, - debt_id: transactions.debt_id, - budget_id: transactions.budget_id, - contribution_id: transactions.contribution_id, - }) - .from(transactions) - .leftJoin(categories, eq(transactions.category_id, categories.id)) - .leftJoin(payment_methods, eq(transactions.payment_method_id, payment_methods.id)) - .where(eq(transactions.id, id)); - return result[0] ? this.mapToEntity(result[0]) : null; - } - - async findByUserId(userId: number): Promise { - const result = await this.db - .select({ - id: transactions.id, - user_id: transactions.user_id, - amount: transactions.amount, - type: transactions.type, - category_id: transactions.category_id, - category: { - id: categories.id, - name: categories.name, - description: categories.description, - }, - description: transactions.description, - payment_method_id: transactions.payment_method_id, - payment_method: { - id: payment_methods.id, - name: payment_methods.name, - type: payment_methods.type, - last_four_digits: payment_methods.last_four_digits, - user_id: payment_methods.user_id, - }, - date: transactions.date, - scheduled_transaction_id: transactions.scheduled_transaction_id, - debt_id: transactions.debt_id, - budget_id: transactions.budget_id, - contribution_id: transactions.contribution_id, - }) - .from(transactions) - .leftJoin(categories, eq(transactions.category_id, categories.id)) - .leftJoin(payment_methods, eq(transactions.payment_method_id, payment_methods.id)) - .where(eq(transactions.user_id, userId)); - return result.map(this.mapToEntity); - } - - async findByUserIdAndDateRange(userId: number, startDate: Date, endDate: Date): Promise { - const result = await this.db - .select() - .from(transactions) - .where( - and( - eq(transactions.user_id, userId), - gte(transactions.date, startDate), - lte(transactions.date, endDate) - ) - ); - return result.map(this.mapToEntity); - } - - async findByFilters( - userId: number, - filters: TransactionFilters - ): Promise { - const conditions = [eq(transactions.user_id, userId)]; - - if (filters.startDate && filters.endDate) { - conditions.push( - between( - transactions.date, - new Date(filters.startDate), - new Date(filters.endDate) - ) - ); - } else if (filters.startDate) { - conditions.push(gte(transactions.date, new Date(filters.startDate))); - } else if (filters.endDate) { - conditions.push(lte(transactions.date, new Date(filters.endDate))); - } - - if (filters.type) { - conditions.push(eq(transactions.type, filters.type)); - } - - if (filters.category_id) { - conditions.push(eq(transactions.category_id, filters.category_id)); - } - - if (filters.payment_method_id) { - conditions.push( - eq(transactions.payment_method_id, filters.payment_method_id) - ); - } - - if (filters.min_amount) { - conditions.push( - gte(transactions.amount, sql`${filters.min_amount}::numeric`) - ); - } - - if (filters.max_amount) { - conditions.push( - lte(transactions.amount, sql`${filters.max_amount}::numeric`) - ); - } - - if (filters.debt_id) { - conditions.push(eq(transactions.debt_id, filters.debt_id)); - } - - if (filters.contribution_id) { - conditions.push(eq(transactions.contribution_id, filters.contribution_id)); - } - - if (filters.budget_id) { - conditions.push(eq(transactions.budget_id, filters.budget_id)); - } - - const result = await this.db - .select({ - id: transactions.id, - user_id: transactions.user_id, - amount: transactions.amount, - type: transactions.type, - category_id: transactions.category_id, - category: { - id: categories.id, - name: categories.name, - description: categories.description, - }, - description: transactions.description, - payment_method_id: transactions.payment_method_id, - payment_method: { - id: payment_methods.id, - name: payment_methods.name, - type: payment_methods.type, - last_four_digits: payment_methods.last_four_digits, - user_id: payment_methods.user_id, - }, - date: transactions.date, - scheduled_transaction_id: transactions.scheduled_transaction_id, - debt_id: transactions.debt_id, - budget_id: transactions.budget_id, - contribution_id: transactions.contribution_id, - }) - .from(transactions) - .leftJoin(categories, eq(transactions.category_id, categories.id)) - .leftJoin(payment_methods, eq(transactions.payment_method_id, payment_methods.id)) - .where(and(...conditions)) - .orderBy(transactions.date); - - return result.map(this.mapToEntity); - } - - async create( - transactionData: Omit - ): Promise { - const result = await this.db - .insert(transactions) - .values({ - user_id: transactionData.userId, - amount: transactionData.amount.toString(), - type: transactionData.type, - category_id: transactionData.categoryId, - description: transactionData.description || null, - payment_method_id: transactionData.paymentMethodId || null, - scheduled_transaction_id: - transactionData.scheduledTransactionId || null, - debt_id: transactionData.debtId || null, - contribution_id: transactionData.contributionId || null, - budget_id: transactionData.budgetId || null, - }) - .returning(); - - return this.mapToEntity(result[0]); - } - - async update( - id: number, - transactionData: Partial - ): Promise { - const updateData: Record = {}; - - if (transactionData.amount !== undefined) { - updateData.amount = transactionData.amount; - } - if (transactionData.type !== undefined) { - updateData.type = transactionData.type; - } - if (transactionData.categoryId !== undefined) { - updateData.category_id = transactionData.categoryId; - } - if (transactionData.description !== undefined) { - updateData.description = transactionData.description; - } - if (transactionData.paymentMethodId !== undefined) { - updateData.payment_method_id = transactionData.paymentMethodId; - } - if (transactionData.scheduledTransactionId !== undefined) { - updateData.scheduled_transaction_id = - transactionData.scheduledTransactionId; - } - if (transactionData.debtId !== undefined) { - updateData.debt_id = transactionData.debtId; - } - - const result = await this.db - .update(transactions) - .set(updateData) - .where(eq(transactions.id, id)) - .returning(); - - return this.mapToEntity(result[0]); - } - - async delete(id: number): Promise { - const result = await this.db - .delete(transactions) - .where(eq(transactions.id, id)) - .returning(); - - return result.length > 0; - } - - async getMonthlyBalance( - userId: number, - month: Date - ): Promise<{ - totalIncome: number; - totalExpense: number; - balance: number; - }> { - const year = month.getUTCFullYear(); - const monthNumber = month.getUTCMonth(); - - const startDate = new Date(Date.UTC(year, monthNumber, 1)); - const endDate = new Date(Date.UTC(year, monthNumber + 1, 0)); - - const result = await this.db - .select({ - type: transactions.type, - total: sql`sum(${transactions.amount})`, - }) - .from(transactions) - .where( - and( - eq(transactions.user_id, userId), - between(transactions.date, startDate, endDate) - ) - ) - .groupBy(transactions.type); - - const totals = { - totalIncome: 0, - totalExpense: 0, - balance: 0, - }; - - result.forEach((row) => { - if (row.type === "INCOME") { - totals.totalIncome = Number(row.total) || 0; - } else { - totals.totalExpense = Number(row.total) || 0; - } - }); - - totals.balance = totals.totalIncome - totals.totalExpense; - return totals; - } - - async getCategoryTotals( - userId: number, - startDate: Date, - endDate: Date - ): Promise> { - const utcStartDate = new Date( - Date.UTC( - startDate.getUTCFullYear(), - startDate.getUTCMonth(), - startDate.getUTCDate() - ) - ); - - const utcEndDate = new Date( - Date.UTC( - endDate.getUTCFullYear(), - endDate.getUTCMonth(), - endDate.getUTCDate(), - 23, - 59, - 59, - 999 - ) - ); - - console.log("UTC Start date:", utcStartDate.toISOString()); - console.log("UTC End date:", utcEndDate.toISOString()); - - const result = await this.db - .select({ - categoryId: transactions.category_id, - total: sql`sum(${transactions.amount})`, - }) - .from(transactions) - .where( - and( - eq(transactions.user_id, userId), - between(transactions.date, utcStartDate, utcEndDate) - ) - ) - .groupBy(transactions.category_id); - - return result.map((row) => ({ - categoryId: row.categoryId || 0, - total: Number(row.total) || 0, - })); - } - private mapToEntity(raw: any): ITransaction { - return { - id: raw.id, - userId: raw.user_id, - amount: Number(raw.amount), - type: raw.type, - categoryId: raw.category_id, - category: raw.category ? { - id: raw.category.id, - name: raw.category.name, - description: raw.category.description, - } : null, - description: raw.description, - paymentMethodId: raw.payment_method_id, - paymentMethod: raw.payment_method ? { - id: raw.payment_method.id, - name: raw.payment_method.name, - type: raw.payment_method.type, - lastFourDigits: raw.payment_method.last_four_digits, - userId: raw.payment_method.user_id, - createdAt: raw.payment_method.created_at, - updatedAt: raw.payment_method.updated_at, - } : null, - date: raw.date, - scheduledTransactionId: raw.scheduled_transaction_id, - debtId: raw.debt_id, - budgetId: raw.budget_id, - contributionId: raw.contribution_id, - createdAt: raw.created_at, - updatedAt: raw.updated_at, - }; - } - - async getMonthlyTrends(userId: number): Promise { - console.log("Getting monthly trends for user:", userId); - - try { - const result = await this.db - .select({ - month: sql`date_trunc('month', ${transactions.date})::date`, - type: transactions.type, - total: sql`COALESCE(sum(${transactions.amount}::numeric), 0)`, - }) - .from(transactions) - .where(eq(transactions.user_id, userId)) - .groupBy( - sql`date_trunc('month', ${transactions.date})::date`, - transactions.type - ) - .orderBy(sql`date_trunc('month', ${transactions.date})::date`); - - console.log("Raw query result:", result); - - if (!result || result.length === 0) { - return []; - } - - const monthlyData: Record = {}; - - result.forEach(({ month, type, total }) => { - const monthKey = month.substring(0, 7); - - if (!monthlyData[monthKey]) { - monthlyData[monthKey] = { - month: monthKey, - income: 0, - expense: 0, - }; - } - - if (type === "INCOME") { - monthlyData[monthKey].income = Number(total) || 0; - } else { - monthlyData[monthKey].expense = Number(total) || 0; - } - }); - - const trends = Object.values(monthlyData).sort( - (a, b) => new Date(a.month).getTime() - new Date(b.month).getTime() - ); - - console.log("Processed trends:", trends); - return trends; - } catch (error) { - console.error("Error in getMonthlyTrends:", error); - throw error; - } - } - - async findByDebtId(debtId: number): Promise { - const result = await this.db - .select({ - id: transactions.id, - user_id: transactions.user_id, - amount: transactions.amount, - type: transactions.type, - category_id: transactions.category_id, - category: { - id: categories.id, - name: categories.name, - description: categories.description, - }, - description: transactions.description, - payment_method_id: transactions.payment_method_id, - payment_method: { - id: payment_methods.id, - name: payment_methods.name, - type: payment_methods.type, - last_four_digits: payment_methods.last_four_digits, - user_id: payment_methods.user_id, - }, - date: transactions.date, - scheduled_transaction_id: transactions.scheduled_transaction_id, - debt_id: transactions.debt_id, - budget_id: transactions.budget_id, - contribution_id: transactions.contribution_id, - }) - .from(transactions) - .leftJoin(categories, eq(transactions.category_id, categories.id)) - .leftJoin(payment_methods, eq(transactions.payment_method_id, payment_methods.id)) - .where(eq(transactions.debt_id, debtId)); - return result.map(this.mapToEntity); - } - - async findByContributionId(contributionId: number): Promise { - const result = await this.db - .select({ - id: transactions.id, - user_id: transactions.user_id, - amount: transactions.amount, - type: transactions.type, - category_id: transactions.category_id, - category: { - id: categories.id, - name: categories.name, - description: categories.description, - }, - description: transactions.description, - payment_method_id: transactions.payment_method_id, - payment_method: { - id: payment_methods.id, - name: payment_methods.name, - type: payment_methods.type, - last_four_digits: payment_methods.last_four_digits, - user_id: payment_methods.user_id, - }, - date: transactions.date, - scheduled_transaction_id: transactions.scheduled_transaction_id, - debt_id: transactions.debt_id, - budget_id: transactions.budget_id, - contribution_id: transactions.contribution_id, - }) - .from(transactions) - .leftJoin(categories, eq(transactions.category_id, categories.id)) - .leftJoin(payment_methods, eq(transactions.payment_method_id, payment_methods.id)) - .where(eq(transactions.contribution_id, contributionId)); - return result.map(this.mapToEntity); - } - - async findByBudgetId(budgetId: number): Promise { - const result = await this.db - .select({ - id: transactions.id, - user_id: transactions.user_id, - amount: transactions.amount, - type: transactions.type, - category_id: transactions.category_id, - category: { - id: categories.id, - name: categories.name, - description: categories.description, - }, - description: transactions.description, - payment_method_id: transactions.payment_method_id, - payment_method: { - id: payment_methods.id, - name: payment_methods.name, - type: payment_methods.type, - last_four_digits: payment_methods.last_four_digits, - user_id: payment_methods.user_id, - }, - date: transactions.date, - scheduled_transaction_id: transactions.scheduled_transaction_id, - debt_id: transactions.debt_id, - budget_id: transactions.budget_id, - contribution_id: transactions.contribution_id, - }) - .from(transactions) - .leftJoin(categories, eq(transactions.category_id, categories.id)) - .leftJoin(payment_methods, eq(transactions.payment_method_id, payment_methods.id)) - .where(eq(transactions.budget_id, budgetId)); - return result.map(this.mapToEntity); - } + private db = DatabaseConnection.getInstance().db; + private static instance: PgTransactionRepository; + + private constructor() {} + + public static getInstance(): PgTransactionRepository { + if (!PgTransactionRepository.instance) { + PgTransactionRepository.instance = new PgTransactionRepository(); + } + return PgTransactionRepository.instance; + } + + async findAll(): Promise { + const result = await this.db + .select({ + id: transactions.id, + user_id: transactions.user_id, + amount: transactions.amount, + type: transactions.type, + category_id: transactions.category_id, + category: { + id: categories.id, + name: categories.name, + description: categories.description, + }, + description: transactions.description, + payment_method_id: transactions.payment_method_id, + payment_method: { + id: payment_methods.id, + name: payment_methods.name, + type: payment_methods.type, + last_four_digits: payment_methods.last_four_digits, + user_id: payment_methods.user_id, + }, + date: transactions.date, + scheduled_transaction_id: transactions.scheduled_transaction_id, + debt_id: transactions.debt_id, + budget_id: transactions.budget_id, + contribution_id: transactions.contribution_id, + }) + .from(transactions) + .leftJoin(categories, eq(transactions.category_id, categories.id)) + .leftJoin( + payment_methods, + eq(transactions.payment_method_id, payment_methods.id) + ); + return result.map(this.mapToEntity); + } + + async findById(id: number): Promise { + const result = await this.db + .select({ + id: transactions.id, + user_id: transactions.user_id, + amount: transactions.amount, + type: transactions.type, + category_id: transactions.category_id, + category: { + id: categories.id, + name: categories.name, + description: categories.description, + }, + description: transactions.description, + payment_method_id: transactions.payment_method_id, + payment_method: { + id: payment_methods.id, + name: payment_methods.name, + type: payment_methods.type, + last_four_digits: payment_methods.last_four_digits, + user_id: payment_methods.user_id, + }, + date: transactions.date, + scheduled_transaction_id: transactions.scheduled_transaction_id, + debt_id: transactions.debt_id, + budget_id: transactions.budget_id, + contribution_id: transactions.contribution_id, + }) + .from(transactions) + .leftJoin(categories, eq(transactions.category_id, categories.id)) + .leftJoin( + payment_methods, + eq(transactions.payment_method_id, payment_methods.id) + ) + .where(eq(transactions.id, id)); + return result[0] ? this.mapToEntity(result[0]) : null; + } + + async findByUserId(userId: number): Promise { + const result = await this.db + .select({ + id: transactions.id, + user_id: transactions.user_id, + amount: transactions.amount, + type: transactions.type, + category_id: transactions.category_id, + category: { + id: categories.id, + name: categories.name, + description: categories.description, + }, + description: transactions.description, + payment_method_id: transactions.payment_method_id, + payment_method: { + id: payment_methods.id, + name: payment_methods.name, + type: payment_methods.type, + last_four_digits: payment_methods.last_four_digits, + user_id: payment_methods.user_id, + }, + date: transactions.date, + scheduled_transaction_id: transactions.scheduled_transaction_id, + debt_id: transactions.debt_id, + budget_id: transactions.budget_id, + contribution_id: transactions.contribution_id, + }) + .from(transactions) + .leftJoin(categories, eq(transactions.category_id, categories.id)) + .leftJoin( + payment_methods, + eq(transactions.payment_method_id, payment_methods.id) + ) + .where(eq(transactions.user_id, userId)); + return result.map(this.mapToEntity); + } + + async findByUserIdAndDateRange( + userId: number, + startDate: Date, + endDate: Date + ): Promise { + const result = await this.db + .select() + .from(transactions) + .where( + and( + eq(transactions.user_id, userId), + gte(transactions.date, setStartOfDay(startDate)), + lte(transactions.date, setEndOfDay(endDate)) + ) + ); + return result.map(this.mapToEntity); + } + + async findByFilters( + userId: number, + filters: TransactionFilters + ): Promise { + const conditions = [eq(transactions.user_id, userId)]; + + if (filters.startDate && filters.endDate) { + conditions.push( + between( + transactions.date, + setStartOfDay(new Date(filters.startDate)), + setEndOfDay(new Date(filters.endDate)) + ) + ); + } else if (filters.startDate) { + conditions.push( + gte(transactions.date, setStartOfDay(new Date(filters.startDate))) + ); + } else if (filters.endDate) { + conditions.push( + lte(transactions.date, setEndOfDay(new Date(filters.endDate))) + ); + } + + if (filters.type) { + conditions.push(eq(transactions.type, filters.type)); + } + + if (filters.category_id) { + conditions.push(eq(transactions.category_id, filters.category_id)); + } + + if (filters.payment_method_id) { + conditions.push( + eq(transactions.payment_method_id, filters.payment_method_id) + ); + } + + if (filters.min_amount) { + conditions.push( + gte(transactions.amount, sql`${filters.min_amount}::numeric`) + ); + } + + if (filters.max_amount) { + conditions.push( + lte(transactions.amount, sql`${filters.max_amount}::numeric`) + ); + } + + if (filters.debt_id) { + conditions.push(eq(transactions.debt_id, filters.debt_id)); + } + + if (filters.contribution_id) { + conditions.push( + eq(transactions.contribution_id, filters.contribution_id) + ); + } + + if (filters.budget_id) { + conditions.push(eq(transactions.budget_id, filters.budget_id)); + } + + const result = await this.db + .select({ + id: transactions.id, + user_id: transactions.user_id, + amount: transactions.amount, + type: transactions.type, + category_id: transactions.category_id, + category: { + id: categories.id, + name: categories.name, + description: categories.description, + }, + description: transactions.description, + payment_method_id: transactions.payment_method_id, + payment_method: { + id: payment_methods.id, + name: payment_methods.name, + type: payment_methods.type, + last_four_digits: payment_methods.last_four_digits, + user_id: payment_methods.user_id, + }, + date: transactions.date, + scheduled_transaction_id: transactions.scheduled_transaction_id, + debt_id: transactions.debt_id, + budget_id: transactions.budget_id, + contribution_id: transactions.contribution_id, + }) + .from(transactions) + .leftJoin(categories, eq(transactions.category_id, categories.id)) + .leftJoin( + payment_methods, + eq(transactions.payment_method_id, payment_methods.id) + ) + .where(and(...conditions)) + .orderBy(transactions.date); + + return result.map(this.mapToEntity); + } + + async create( + transactionData: Omit + ): Promise { + const result = await this.db + .insert(transactions) + .values({ + user_id: transactionData.userId, + amount: transactionData.amount.toString(), + type: transactionData.type, + category_id: transactionData.categoryId, + description: transactionData.description || null, + payment_method_id: transactionData.paymentMethodId || null, + scheduled_transaction_id: + transactionData.scheduledTransactionId || null, + debt_id: transactionData.debtId || null, + contribution_id: transactionData.contributionId || null, + budget_id: transactionData.budgetId || null, + }) + .returning(); + + return this.mapToEntity(result[0]); + } + + async update( + id: number, + transactionData: Partial + ): Promise { + const updateData: Record = {}; + + if (transactionData.amount !== undefined) { + updateData.amount = transactionData.amount; + } + if (transactionData.type !== undefined) { + updateData.type = transactionData.type; + } + if (transactionData.categoryId !== undefined) { + updateData.category_id = transactionData.categoryId; + } + if (transactionData.description !== undefined) { + updateData.description = transactionData.description; + } + if (transactionData.paymentMethodId !== undefined) { + updateData.payment_method_id = transactionData.paymentMethodId; + } + if (transactionData.scheduledTransactionId !== undefined) { + updateData.scheduled_transaction_id = + transactionData.scheduledTransactionId; + } + if (transactionData.debtId !== undefined) { + updateData.debt_id = transactionData.debtId; + } + + const result = await this.db + .update(transactions) + .set(updateData) + .where(eq(transactions.id, id)) + .returning(); + + return this.mapToEntity(result[0]); + } + + async delete(id: number): Promise { + const result = await this.db + .delete(transactions) + .where(eq(transactions.id, id)) + .returning(); + + return result.length > 0; + } + + async getMonthlyBalance( + userId: number, + month: Date + ): Promise<{ + totalIncome: number; + totalExpense: number; + balance: number; + }> { + const year = month.getUTCFullYear(); + const monthNumber = month.getUTCMonth(); + + const startDate = new Date(Date.UTC(year, monthNumber, 1)); + const endDate = new Date(Date.UTC(year, monthNumber + 1, 0)); + + const result = await this.db + .select({ + type: transactions.type, + total: sql`sum(${transactions.amount})`, + }) + .from(transactions) + .where( + and( + eq(transactions.user_id, userId), + between(transactions.date, startDate, endDate) + ) + ) + .groupBy(transactions.type); + + const totals = { + totalIncome: 0, + totalExpense: 0, + balance: 0, + }; + + result.forEach((row) => { + if (row.type === "INCOME") { + totals.totalIncome = Number(row.total) || 0; + } else { + totals.totalExpense = Number(row.total) || 0; + } + }); + + totals.balance = totals.totalIncome - totals.totalExpense; + return totals; + } + + async getCategoryTotals( + userId: number, + startDate: Date, + endDate: Date + ): Promise< + Array<{ categoryId: number; categoryName: string | null; total: number }> + > { + const utcStartDate = setStartOfDay(startDate); + const utcEndDate = setEndOfDay(endDate); + + + const result = await this.db + .select({ + categoryId: transactions.category_id, + categoryName: categories.name, + total: sql`sum(${transactions.amount})`, + }) + .from(transactions) + .leftJoin(categories, eq(transactions.category_id, categories.id)) + .where( + and( + eq(transactions.user_id, userId), + between(transactions.date, utcStartDate, utcEndDate) + ) + ) + .groupBy(transactions.category_id, categories.name); + + return result.map((row) => ({ + categoryId: row.categoryId || 0, + categoryName: row.categoryName, + total: Number(row.total) || 0, + })); + } + private mapToEntity(raw: any): ITransaction { + return { + id: raw.id, + userId: raw.user_id, + amount: Number(raw.amount), + type: raw.type, + categoryId: raw.category_id, + category: raw.category + ? { + id: raw.category.id, + name: raw.category.name, + description: raw.category.description, + } + : null, + description: raw.description, + paymentMethodId: raw.payment_method_id, + paymentMethod: raw.payment_method + ? { + id: raw.payment_method.id, + name: raw.payment_method.name, + type: raw.payment_method.type, + lastFourDigits: raw.payment_method.last_four_digits, + userId: raw.payment_method.user_id, + createdAt: raw.payment_method.created_at, + updatedAt: raw.payment_method.updated_at, + } + : null, + date: raw.date, + scheduledTransactionId: raw.scheduled_transaction_id, + debtId: raw.debt_id, + budgetId: raw.budget_id, + contributionId: raw.contribution_id, + createdAt: raw.created_at, + updatedAt: raw.updated_at, + }; + } + + async getMonthlyTrends(userId: number): Promise { + console.log("Getting monthly trends for user:", userId); + + try { + const result = await this.db + .select({ + month: sql`date_trunc('month', ${transactions.date})::date`, + type: transactions.type, + total: sql`COALESCE(sum(${transactions.amount}::numeric), 0)`, + }) + .from(transactions) + .where(eq(transactions.user_id, userId)) + .groupBy( + sql`date_trunc('month', ${transactions.date})::date`, + transactions.type + ) + .orderBy(sql`date_trunc('month', ${transactions.date})::date`); + + console.log("Raw query result:", result); + + if (!result || result.length === 0) { + return []; + } + + const monthlyData: Record = {}; + + result.forEach(({ month, type, total }) => { + const monthKey = month.substring(0, 7); + + if (!monthlyData[monthKey]) { + monthlyData[monthKey] = { + month: monthKey, + income: 0, + expense: 0, + }; + } + + if (type === "INCOME") { + monthlyData[monthKey].income = Number(total) || 0; + } else { + monthlyData[monthKey].expense = Number(total) || 0; + } + }); + + const trends = Object.values(monthlyData).sort( + (a, b) => new Date(a.month).getTime() - new Date(b.month).getTime() + ); + + console.log("Processed trends:", trends); + return trends; + } catch (error) { + console.error("Error in getMonthlyTrends:", error); + throw error; + } + } + + async findByDebtId(debtId: number): Promise { + const result = await this.db + .select({ + id: transactions.id, + user_id: transactions.user_id, + amount: transactions.amount, + type: transactions.type, + category_id: transactions.category_id, + category: { + id: categories.id, + name: categories.name, + description: categories.description, + }, + description: transactions.description, + payment_method_id: transactions.payment_method_id, + payment_method: { + id: payment_methods.id, + name: payment_methods.name, + type: payment_methods.type, + last_four_digits: payment_methods.last_four_digits, + user_id: payment_methods.user_id, + }, + date: transactions.date, + scheduled_transaction_id: transactions.scheduled_transaction_id, + debt_id: transactions.debt_id, + budget_id: transactions.budget_id, + contribution_id: transactions.contribution_id, + }) + .from(transactions) + .leftJoin(categories, eq(transactions.category_id, categories.id)) + .leftJoin( + payment_methods, + eq(transactions.payment_method_id, payment_methods.id) + ) + .where(eq(transactions.debt_id, debtId)); + return result.map(this.mapToEntity); + } + + async findByContributionId(contributionId: number): Promise { + const result = await this.db + .select({ + id: transactions.id, + user_id: transactions.user_id, + amount: transactions.amount, + type: transactions.type, + category_id: transactions.category_id, + category: { + id: categories.id, + name: categories.name, + description: categories.description, + }, + description: transactions.description, + payment_method_id: transactions.payment_method_id, + payment_method: { + id: payment_methods.id, + name: payment_methods.name, + type: payment_methods.type, + last_four_digits: payment_methods.last_four_digits, + user_id: payment_methods.user_id, + }, + date: transactions.date, + scheduled_transaction_id: transactions.scheduled_transaction_id, + debt_id: transactions.debt_id, + budget_id: transactions.budget_id, + contribution_id: transactions.contribution_id, + }) + .from(transactions) + .leftJoin(categories, eq(transactions.category_id, categories.id)) + .leftJoin( + payment_methods, + eq(transactions.payment_method_id, payment_methods.id) + ) + .where(eq(transactions.contribution_id, contributionId)); + return result.map(this.mapToEntity); + } + + async findByBudgetId(budgetId: number): Promise { + const result = await this.db + .select({ + id: transactions.id, + user_id: transactions.user_id, + amount: transactions.amount, + type: transactions.type, + category_id: transactions.category_id, + category: { + id: categories.id, + name: categories.name, + description: categories.description, + }, + description: transactions.description, + payment_method_id: transactions.payment_method_id, + payment_method: { + id: payment_methods.id, + name: payment_methods.name, + type: payment_methods.type, + last_four_digits: payment_methods.last_four_digits, + user_id: payment_methods.user_id, + }, + date: transactions.date, + scheduled_transaction_id: transactions.scheduled_transaction_id, + debt_id: transactions.debt_id, + budget_id: transactions.budget_id, + contribution_id: transactions.contribution_id, + }) + .from(transactions) + .leftJoin(categories, eq(transactions.category_id, categories.id)) + .leftJoin( + payment_methods, + eq(transactions.payment_method_id, payment_methods.id) + ) + .where(eq(transactions.budget_id, budgetId)); + return result.map(this.mapToEntity); + } } diff --git a/src/shared/utils/date.utils.ts b/src/shared/utils/date.utils.ts new file mode 100644 index 0000000..ff768a0 --- /dev/null +++ b/src/shared/utils/date.utils.ts @@ -0,0 +1,22 @@ +/** + * Sets the end date with the last millisecond of the day (23:59:59.999) + * to ensure all transactions from the day are included + * @param date - Base date + * @returns New date with the last millisecond of the day + */ +export function setEndOfDay(date: Date): Date { + const endOfDay = new Date(date); + endOfDay.setHours(23, 59, 59, 999); + return endOfDay; +} + +/** + * Sets the start date with the first hour of the day (00:00:00.000) + * @param date - Base date + * @returns New date with the first hour of the day + */ +export function setStartOfDay(date: Date): Date { + const startOfDay = new Date(date); + startOfDay.setHours(0, 0, 0, 0); + return startOfDay; +}