revenue chart
This commit is contained in:
@@ -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