revenue chart

This commit is contained in:
2025-12-22 00:10:17 +03:30
parent 2f409e5c8c
commit 746c822778
4 changed files with 187 additions and 6 deletions
@@ -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;
}
}