revenue chart
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Query } from '@nestjs/common';
|
||||
import { PaymentsService } from '../services/payments.service';
|
||||
import {
|
||||
ApiTags,
|
||||
@@ -14,6 +14,7 @@ import { PaymentMethodService } from '../services/payment-method.service';
|
||||
import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto';
|
||||
import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto';
|
||||
import { VerifyPaymentDto } from '../dto/verify-payment.dto';
|
||||
import { PaymentChartDto } from '../dto/payment-chart.dto';
|
||||
import { RestId, UserId } from 'src/common/decorators';
|
||||
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
|
||||
import { API_HEADER_SLUG } from 'src/common/constants';
|
||||
@@ -121,4 +122,13 @@ export class PaymentsController {
|
||||
verifyCashPayment(@Param('paymentId') paymentId: string) {
|
||||
return this.paymentMethodService.remove(paymentId);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Get('admin/payments/chart')
|
||||
@ApiOperation({ summary: 'Get payment chart data with date and period filters' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
getPaymentChart(@Query() query: PaymentChartDto, @RestId() restId: string) {
|
||||
return this.paymentsService.getChartData(query, restId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { IsOptional, IsString, IsEnum, IsDateString } from 'class-validator';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export enum ChartPeriodEnum {
|
||||
Daily = 'daily',
|
||||
Weekly = 'weekly',
|
||||
Monthly = 'monthly',
|
||||
}
|
||||
|
||||
export class PaymentChartDto {
|
||||
@ApiPropertyOptional({
|
||||
description: 'Start date for filtering (ISO date string)',
|
||||
type: String,
|
||||
format: 'date-time',
|
||||
example: '2024-01-01T00:00:00Z',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
startDate?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'End date for filtering (ISO date string)',
|
||||
type: String,
|
||||
format: 'date-time',
|
||||
example: '2024-12-31T23:59:59Z',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
endDate?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Period type for grouping data',
|
||||
enum: ChartPeriodEnum,
|
||||
default: ChartPeriodEnum.Daily,
|
||||
example: ChartPeriodEnum.Daily,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsEnum(ChartPeriodEnum)
|
||||
type?: ChartPeriodEnum = ChartPeriodEnum.Daily;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { PaymentMethodEnum } from "../interface/payment";
|
||||
|
||||
export class paymentSucceedEvent {
|
||||
export class paymentSucceedEvent {
|
||||
constructor(
|
||||
public readonly orderId: string,
|
||||
public readonly restaurantId: string,
|
||||
public readonly orderNumber: string,
|
||||
public readonly paymentMethod: PaymentMethodEnum,
|
||||
public readonly total:number
|
||||
) {}
|
||||
public readonly total: number
|
||||
) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { GatewayManager } from '../gateways/gateway.manager';
|
||||
import { UserWallet } from 'src/modules/users/entities/user-wallet.entity';
|
||||
import { OrderPaymentContext } from '../interface/payment';
|
||||
import { OrderStatus } from 'src/modules/orders/interface/order.interface';
|
||||
import { ChartPeriodEnum, PaymentChartDto } from '../dto/payment-chart.dto';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentsService {
|
||||
@@ -16,7 +17,7 @@ export class PaymentsService {
|
||||
constructor(
|
||||
private readonly em: EntityManager,
|
||||
private readonly gatewayManager: GatewayManager,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async payOrder(orderId: string): Promise<{ paymentUrl: string | null }> {
|
||||
const ctx = await this.loadAndValidateOrder(orderId);
|
||||
@@ -288,4 +289,133 @@ export class PaymentsService {
|
||||
await em.flush();
|
||||
return payment;
|
||||
}
|
||||
|
||||
async getChartData(dto: PaymentChartDto, restaurantId: string): Promise<Array<{ date: string; cash: number; online: number }>> {
|
||||
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 end = endDate ? new Date(endDate) : new Date();
|
||||
|
||||
let dateGrouping: string;
|
||||
let dateSelect: string;
|
||||
|
||||
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 conditions: string[] = [`p.status = 'paid'`, 'p.paid_at IS NOT NULL'];
|
||||
const params: any[] = [];
|
||||
|
||||
conditions.push(`p.paid_at::timestamp >= ?`);
|
||||
params.push(start);
|
||||
|
||||
conditions.push(`p.paid_at::timestamp <= ?`);
|
||||
params.push(end);
|
||||
|
||||
if (restaurantId) {
|
||||
conditions.push(`o.restaurant_id = ?`);
|
||||
params.push(restaurantId);
|
||||
}
|
||||
|
||||
const whereClause = conditions.join(' AND ');
|
||||
|
||||
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
|
||||
FROM payments p
|
||||
INNER JOIN orders o ON p.order_id = o.id
|
||||
WHERE ${whereClause}
|
||||
GROUP BY ${dateGrouping}
|
||||
ORDER BY ${dateGrouping} ASC
|
||||
`;
|
||||
|
||||
const result = await this.em.execute(query, params);
|
||||
|
||||
// Create a map of existing data
|
||||
const dataMap = new Map<string, { cash: number; online: number }>();
|
||||
result.forEach((row: any) => {
|
||||
dataMap.set(row.date, {
|
||||
cash: Number(row.cash),
|
||||
online: Number(row.online),
|
||||
});
|
||||
});
|
||||
|
||||
// Generate all dates in the range based on period type
|
||||
const allDates = this.generateDateRange(start, end, type);
|
||||
|
||||
// Return array with all dates, filling missing ones with zeros
|
||||
return allDates.map(date => ({
|
||||
date,
|
||||
cash: dataMap.get(date)?.cash ?? 0,
|
||||
online: dataMap.get(date)?.online ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
private generateDateRange(start: Date, end: Date, type: ChartPeriodEnum): string[] {
|
||||
const dates: string[] = [];
|
||||
const current = new Date(start);
|
||||
const endDate = new Date(end);
|
||||
|
||||
// Normalize start date based on period type
|
||||
switch (type) {
|
||||
case ChartPeriodEnum.Weekly:
|
||||
// Start of week (Monday) - PostgreSQL DATE_TRUNC('week') uses ISO week (Monday as first day)
|
||||
const dayOfWeek = current.getDay();
|
||||
const diff = current.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1);
|
||||
current.setDate(diff);
|
||||
current.setHours(0, 0, 0, 0);
|
||||
// Also normalize end date to include the week containing it
|
||||
const endDayOfWeek = endDate.getDay();
|
||||
const endDiff = endDate.getDate() - endDayOfWeek + (endDayOfWeek === 0 ? -6 : 1);
|
||||
endDate.setDate(endDiff);
|
||||
endDate.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case ChartPeriodEnum.Monthly:
|
||||
// Start of month
|
||||
current.setDate(1);
|
||||
current.setHours(0, 0, 0, 0);
|
||||
// Also normalize end date to include the month containing it
|
||||
endDate.setDate(1);
|
||||
endDate.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case ChartPeriodEnum.Daily:
|
||||
default:
|
||||
current.setHours(0, 0, 0, 0);
|
||||
endDate.setHours(23, 59, 59, 999);
|
||||
}
|
||||
|
||||
while (current <= endDate) {
|
||||
const dateStr = current.toISOString().split('T')[0];
|
||||
dates.push(dateStr);
|
||||
|
||||
// Increment based on period type
|
||||
switch (type) {
|
||||
case ChartPeriodEnum.Weekly:
|
||||
current.setDate(current.getDate() + 7);
|
||||
break;
|
||||
case ChartPeriodEnum.Monthly:
|
||||
current.setMonth(current.getMonth() + 1);
|
||||
break;
|
||||
case ChartPeriodEnum.Daily:
|
||||
default:
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return dates;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user