Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 206 additions & 38 deletions docs/swagger.json

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions src/modules/kpi/kpi.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,4 +501,98 @@ export class KpiController {
);
}
}

/**
* Get the revenues for a specific period
* @param idRestaurant - The restaurant identifier (must be positive)
* @param timeBegin - Start date of the analysis period (optional)
* @param timeEnd - End date of the analysis period (optional)
* @param breakdown - The breakdown of the analysis period (optional)
* @param useCase - The use case (POS or KDS)
* @returns The revenues for the specified period
* @throws {BadRequestException} When input parameters are invalid
* @throws {InternalServerErrorException} When server encounters an error
* @example
* GET /api/1/kpi/revenues?timeBegin=2024-01-01&timeEnd=2024-01-31&breakdown=15&useCase=POS
* // returns { "12:00": { revenues: 100, ordersCount: 10, averageWaitingTime: 10 }, "12:15": { revenues: 100, ordersCount: 10, averageWaitingTime: 10 } }
*/
@Get('revenues')
async kpiRevenues(
@Param('idRestaurant', PositiveNumberPipe) idRestaurant: number,
@Query('timeBegin', DatePipe) timeBegin: string,
@Query('timeEnd', DatePipe) timeEnd: string,
@Query('breakdown') breakdown?: number,
@Query('useCase', UseCasePipe) useCase?: string,
) {
if (breakdown) {
const slots: { timeBegin: string; timeEnd: string }[] = [];
let current = new Date(timeBegin);
const end = new Date(timeEnd);
const orders = {};

while (current < end) {
const slotStart = new Date(current);
const slotEnd = new Date(current.getTime() + breakdown * 60000);
slots.push({
timeBegin: slotStart.toISOString(),
timeEnd: (slotEnd < end ? slotEnd : end).toISOString(),
});
current = slotEnd;
}

for (const slot of slots) {
const date = new Date(slot.timeBegin);
const hours = date.getUTCHours().toString().padStart(2, '0');
const minutes = date.getUTCMinutes().toString().padStart(2, '0');
const averageWaitingTime =
useCase !== undefined
? await this.kpiService.averageTimeOrders(
idRestaurant,
slot.timeBegin,
slot.timeEnd,
undefined,
)
: undefined;

const { total, ordersCount } = await this.kpiService.revenueTotal(
idRestaurant,
slot.timeBegin,
slot.timeEnd,
undefined,
);
const orderData: any = {
revenues: total,
ordersCount: ordersCount,
};
if (useCase !== undefined)
orderData.averageWaitingTime = averageWaitingTime;
orders[`${hours}:${minutes}`] = orderData;
}

return orders;
} else {
const averageWaitingTime =
useCase !== undefined
? await this.kpiService.averageTimeOrders(
idRestaurant,
timeBegin,
timeEnd,
undefined,
)
: undefined;

const { total, ordersCount } = await this.kpiService.revenueTotal(
idRestaurant,
timeBegin,
timeEnd,
undefined,
);
const orders: any = {
revenues: total,
ordersCount: ordersCount,
};
if (useCase !== undefined) orders.averageWaitingTime = averageWaitingTime;
return orders;
}
}
}
42 changes: 42 additions & 0 deletions src/modules/kpi/kpi.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,4 +613,46 @@ export class KpiService extends DB {
}));
}
}

/**
* Calculates the total revenue of a restaurant over a given period
* @param idRestaurant - The restaurant identifier
* @param timeBegin - Start date of the period (optional)
* @param timeEnd - End date of the period (optional)
* @param channel - The order channel (optional)
* @returns The total revenue and the number of orders
*/
async revenueTotal(
idRestaurant: number,
timeBegin: string,
timeEnd: string,
channel?: string,
): Promise<{ total: number; ordersCount: number }> {
const db = this.getDbConnection();
let orders = await db
.collection('restaurant')
.aggregate([
{ $match: { id: idRestaurant } },
{ $unwind: '$orders' },
{ $match: { 'orders.channel': channel || { $exists: true } } },
{ $match: { 'orders.payment': { $exists: true } } },
{ $project: { _id: 0, 'orders.timePayment': 1, 'orders.total': 1 } },
])
.toArray();

if (timeBegin && timeEnd) {
const beginDate = new Date(timeBegin);
const endDate = new Date(timeEnd);
orders = orders.filter((item) => {
const orderDate = new Date(item.orders.timePayment);
return orderDate >= beginDate && orderDate <= endDate;
});
}

const totalRevenue = orders.reduce((sum, item) => {
return sum + parseFloat(item.orders.total);
}, 0);

return { total: totalRevenue, ordersCount: orders.length };
}
}
3 changes: 2 additions & 1 deletion src/modules/kpi/pipe/useCase.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export class UseCasePipe implements PipeTransform {
transform(value: any) {
if (value === undefined) return value;
if (typeof value !== 'string') throw new BadRequestException();
if (value !== 'POS' && value !== 'KDS') return undefined;
if (value !== 'POS' && value !== 'KDS' && value !== 'statsPOS')
return undefined;
return value;
}
}
4 changes: 4 additions & 0 deletions src/modules/orders/DTO/food_ordered.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class ModsIngredient {
@IsString()
@IsNotEmpty()
ingredient: string;

@IsNumber()
@IsNotEmpty()
suppPrice: number;
}

export class FoodOrderedDto {
Expand Down
4 changes: 4 additions & 0 deletions src/modules/orders/DTO/orders.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class OrdersDto {
@Type(() => PaymentDto)
payment?: PaymentDto[];

@IsOptional()
@IsString()
timePayment?: string;

@IsOptional()
@IsNumber()
id?: number;
Expand Down
15 changes: 9 additions & 6 deletions src/modules/orders/orders.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,11 +678,14 @@ export class OrdersService extends DB {
$unset: { 'pos_config.tables.$.orderId': '' },
},
);
return await db
.collection('restaurant')
.updateOne(
{ id: idRestaurant, 'orders.id': idOrder },
{ $set: { 'orders.$.payment': payment } },
);
return await db.collection('restaurant').updateOne(
{ id: idRestaurant, 'orders.id': idOrder },
{
$set: {
'orders.$.payment': payment,
'orders.$.timePayment': new Date().toISOString(),
},
},
);
}
}