diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts index 2a4e451..aaba575 100644 --- a/src/modules/payments/services/payments.service.ts +++ b/src/modules/payments/services/payments.service.ts @@ -169,7 +169,7 @@ export class PaymentsService { } async verifyPayment(transactionId: string, orderId: string): Promise { - const payment =await this.em.transactional(async em => { + const payment = await this.em.transactional(async em => { const payment = await em.findOne( Payment, { transactionId, order: { id: orderId } }, @@ -203,9 +203,9 @@ export class PaymentsService { this.failPayment(payment); return payment; } - + this.markPaid(payment, result.referenceId); - if(payment.order.status === OrderStatus.PENDING_PAYMENT) { + if (payment.order.status === OrderStatus.PENDING_PAYMENT) { payment.order.status = OrderStatus.PAID; } @@ -213,7 +213,7 @@ export class PaymentsService { return payment; }); this.eventEmitter.emit( - onlinePaymentSucceedEvent.name , + onlinePaymentSucceedEvent.name, new onlinePaymentSucceedEvent(payment.id, payment.order.restaurant.id, String(payment.order?.orderNumber) || '', payment.amount), ); return payment; @@ -239,9 +239,9 @@ export class PaymentsService { if (payment.status === PaymentStatusEnum.Paid) { throw new BadRequestException(PaymentMessage.PAYMENT_ALREADY_PAID); } - if(payment.order.status === OrderStatus.PENDING_PAYMENT) { + if (payment.order.status === OrderStatus.PENDING_PAYMENT) { payment.order.status = OrderStatus.PAID; - } + } payment.status = PaymentStatusEnum.Paid; payment.paidAt = new Date(); em.persist(payment); @@ -308,59 +308,53 @@ export class PaymentsService { async getChartData(dto: PaymentChartDto, restaurantId: string): Promise> { const { startDate, endDate, type = ChartPeriodEnum.Daily } = dto; - // Set default date range if not provided - const start = startDate ? new Date(startDate) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // Default: last 30 days + const start = startDate ? new Date(startDate) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const end = endDate ? new Date(endDate) : new Date(); - let dateGrouping: string; - let dateSelect: string; + const startOfDay = new Date(start); + startOfDay.setHours(0, 0, 0, 0); - switch (type) { - case ChartPeriodEnum.Weekly: - dateGrouping = "DATE_TRUNC('week', p.paid_at::timestamp)"; - dateSelect = "TO_CHAR(DATE_TRUNC('week', p.paid_at::timestamp), 'YYYY-MM-DD')"; - break; - case ChartPeriodEnum.Monthly: - dateGrouping = "DATE_TRUNC('month', p.paid_at::timestamp)"; - dateSelect = "TO_CHAR(DATE_TRUNC('month', p.paid_at::timestamp), 'YYYY-MM-DD')"; - break; - case ChartPeriodEnum.Daily: - default: - dateGrouping = "DATE_TRUNC('day', p.paid_at::timestamp)"; - dateSelect = "TO_CHAR(DATE_TRUNC('day', p.paid_at::timestamp), 'YYYY-MM-DD')"; - } + const endOfDay = new Date(end); + endOfDay.setHours(23, 59, 59, 999); - const conditions: string[] = [`p.status = 'paid'`, 'p.paid_at IS NOT NULL']; - const params: any[] = []; + // 1. Map your Enum to valid Postgres date_trunc units + // 'Daily' is not valid in PG, it must be 'day' + const pgPeriod = { + [ChartPeriodEnum.Daily]: 'day', + [ChartPeriodEnum.Weekly]: 'week', + [ChartPeriodEnum.Monthly]: 'month', + }[type] || 'day'; - conditions.push(`p.paid_at::timestamp >= ?`); - params.push(start); - - conditions.push(`p.paid_at::timestamp <= ?`); - params.push(end); + const params: any[] = [startOfDay, endOfDay]; + let restaurantFilter = ''; if (restaurantId) { - conditions.push(`o.restaurant_id = ?`); params.push(restaurantId); + restaurantFilter = `AND o.restaurant_id = ?`; } - const whereClause = conditions.join(' AND '); - + // MikroORM uses ? placeholders for parameter binding + // Ensure paid_at is not NULL and handle timestamp casting properly const query = ` - SELECT - ${dateSelect} as date, - COALESCE(SUM(CASE WHEN p.method = 'Cash' THEN p.amount ELSE 0 END), 0)::numeric as cash, - COALESCE(SUM(CASE WHEN p.method = 'Online' THEN p.amount ELSE 0 END), 0)::numeric as online + SELECT + TO_CHAR(DATE_TRUNC('${pgPeriod}', CAST(p.paid_at AS timestamp)), 'YYYY-MM-DD') as "date", + COALESCE(SUM(CASE WHEN p.method = '${PaymentMethodEnum.Cash}' THEN p.amount ELSE 0 END), 0)::numeric as cash, + COALESCE(SUM(CASE WHEN p.method = '${PaymentMethodEnum.Online}' THEN p.amount ELSE 0 END), 0)::numeric as online FROM payments p INNER JOIN orders o ON p.order_id = o.id - WHERE ${whereClause} - GROUP BY ${dateGrouping} - ORDER BY ${dateGrouping} ASC + WHERE p.status = '${PaymentStatusEnum.Paid}' + AND p.paid_at IS NOT NULL + AND p.paid_at >= ? + AND p.paid_at <= ? + ${restaurantFilter} + GROUP BY DATE_TRUNC('${pgPeriod}', CAST(p.paid_at AS timestamp)) + ORDER BY DATE_TRUNC('${pgPeriod}', CAST(p.paid_at AS timestamp)) ASC `; + this.logger.debug(`Chart query params: startOfDay=${startOfDay.toISOString()}, endOfDay=${endOfDay.toISOString()}, restaurantId=${restaurantId}`); const result = await this.em.execute(query, params); + this.logger.debug(`Chart query returned ${result.length} rows`); - // Create a map of existing data const dataMap = new Map(); result.forEach((row: any) => { dataMap.set(row.date, { @@ -369,10 +363,8 @@ export class PaymentsService { }); }); - // Generate all dates in the range based on period type - const allDates = this.generateDateRange(start, end, type); + const allDates = this.generateDateRange(startOfDay, endOfDay, type); - // Return array with all dates, filling missing ones with zeros return allDates.map(date => ({ date, cash: dataMap.get(date)?.cash ?? 0,