From bf5168c9378780bbb626e940abecb12794d46f79 Mon Sep 17 00:00:00 2001 From: Xantas Date: Sat, 28 Jun 2025 17:28:08 +0200 Subject: [PATCH 1/5] feat: add new route with the formating of the data --- docs/swagger.json | 168 +++++++++++++++++++++++++++ src/modules/kpi/kpi.controller.ts | 94 ++++++++++++++- src/modules/kpi/pipe/useCase.pipe.ts | 2 +- 3 files changed, 261 insertions(+), 3 deletions(-) diff --git a/docs/swagger.json b/docs/swagger.json index 4734d44..9fa8e6f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3529,6 +3529,174 @@ } ] } + }, + "/{restaurant_id}/kpi/revenues": { + "summary": "KPI endpoints", + "get": { + "tags": [ + "KPI" + ], + "summary": "Récupérer les revenus pour une période spécifique", + "description": "Retourne les revenus et le nombre de commandes pour une période donnée. Si le paramètre breakdown est fourni, les données sont segmentées par tranches horaires. Le champ averageWaitingTime n'apparaît que si useCase est défini.", + "parameters": [ + { + "name": "restaurant_id", + "in": "path", + "description": "ID du restaurant", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "integer" + } + }, + { + "name": "timeBegin", + "in": "query", + "description": "Date de début de la période d'analyse (format: YYYY-MM-DD)", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "timeEnd", + "in": "query", + "description": "Date de fin de la période d'analyse (format: YYYY-MM-DD)", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "breakdown", + "in": "query", + "description": "Durée en minutes pour segmenter les données par tranches horaires (optionnel)", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "example": 15 + } + }, + { + "name": "useCase", + "in": "query", + "description": "Cas d'usage (POS ou KDS) - si défini, inclut averageWaitingTime dans la réponse", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string", + "enum": ["statsPOS"] + } + } + ], + "responses": { + "200": { + "description": "Revenus pour la période demandée", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "description": "Réponse sans breakdown", + "properties": { + "revenues": { + "type": "number", + "description": "Montant total des revenus", + "example": 0 + }, + "ordersCount": { + "type": "integer", + "description": "Nombre total de commandes", + "example": 50 + }, + "averageWaitingTime": { + "type": "object", + "description": "Temps d'attente moyen (présent seulement si useCase est défini)", + "properties": { + "hours": { "type": "integer", "example": 0 }, + "minutes": { "type": "integer", "example": 15 }, + "seconds": { "type": "integer", "example": 30 } + } + } + }, + "example": { + "revenues": 0, + "ordersCount": 50, + "averageWaitingTime": { "hours": 0, "minutes": 15, "seconds": 30 } + } + }, + { + "type": "object", + "description": "Réponse avec breakdown par tranches horaires", + "additionalProperties": { + "type": "object", + "properties": { + "revenues": { + "type": "number", + "description": "Montant des revenus pour cette tranche", + "example": 0 + }, + "ordersCount": { + "type": "integer", + "description": "Nombre de commandes pour cette tranche", + "example": 5 + }, + "averageWaitingTime": { + "type": "object", + "description": "Temps d'attente moyen pour cette tranche (présent seulement si useCase est défini)", + "properties": { + "hours": { "type": "integer", "example": 0 }, + "minutes": { "type": "integer", "example": 12 }, + "seconds": { "type": "integer", "example": 45 } + } + } + } + }, + "example": { + "12:00": { + "revenues": 0, + "ordersCount": 5, + "averageWaitingTime": { "hours": 0, "minutes": 12, "seconds": 45 } + }, + "12:15": { + "revenues": 0, + "ordersCount": 3, + "averageWaitingTime": { "hours": 0, "minutes": 10, "seconds": 30 } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Paramètres invalides (ID restaurant ou format de date)" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "5XX": { + "$ref": "#/components/responses/ServerError" + } + }, + "security": [ + { + "BearerAuth": [] + } + ] + } } }, "components": { diff --git a/src/modules/kpi/kpi.controller.ts b/src/modules/kpi/kpi.controller.ts index 2b1df6d..513e5f2 100644 --- a/src/modules/kpi/kpi.controller.ts +++ b/src/modules/kpi/kpi.controller.ts @@ -20,7 +20,7 @@ import { UseCasePipe } from './pipe/useCase.pipe'; @Controller('api/:idRestaurant/kpi') @UseGuards(JwtAuthGuard) export class KpiController { - constructor(private readonly kpiService: KpiService) {} + constructor(private readonly kpiService: KpiService) { } /** * Get the average preparation time for a specific dish @@ -440,4 +440,94 @@ export class KpiController { return res; } } -} + + + /** + * 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 orderData: any = { + "revenues": 0, + "ordersCount": await this.kpiService.clientsCount( + idRestaurant, + slot.timeBegin, + slot.timeEnd, + undefined, + undefined, + ), + }; + useCase !== undefined ? orderData.averageWaitingTime = averageWaitingTime : orderData; + + orders[`${hours}:${minutes}`] = orderData; + } + + return orders; + } else { + const averageWaitingTime = useCase !== undefined ? await this.kpiService.averageTimeOrders( + idRestaurant, + timeBegin, + timeEnd, + undefined, + ) : undefined; + + const orders: any = { + "revenues": 0, + "ordersCount": await this.kpiService.clientsCount( + idRestaurant, + timeBegin, + timeEnd, + undefined, + undefined, + ), + }; + useCase !== undefined ? orders.averageWaitingTime = averageWaitingTime : orders; + + return orders; + } + } +} \ No newline at end of file diff --git a/src/modules/kpi/pipe/useCase.pipe.ts b/src/modules/kpi/pipe/useCase.pipe.ts index 060aa17..f085ad3 100644 --- a/src/modules/kpi/pipe/useCase.pipe.ts +++ b/src/modules/kpi/pipe/useCase.pipe.ts @@ -5,7 +5,7 @@ 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; } } From d01ab28d92b5a812805509602d9cea11ac3e8e12 Mon Sep 17 00:00:00 2001 From: Xantass Date: Mon, 30 Jun 2025 11:55:59 +0200 Subject: [PATCH 2/5] feat: add new service for get the revenue and translate documentation french to english --- docs/swagger.json | 112 +++++++++++++++--------------- src/modules/kpi/kpi.controller.ts | 4 +- src/modules/kpi/kpi.service.ts | 42 +++++++++++ 3 files changed, 100 insertions(+), 58 deletions(-) diff --git a/docs/swagger.json b/docs/swagger.json index 1117ac0..107ed87 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2952,12 +2952,12 @@ "tags": [ "KPI" ], - "summary": "Récupérer le plat le plus populaire sur une période donnée", + "summary": "Retrieve the most popular dish over a given period", "parameters": [ { "name": "restaurant_id", "in": "path", - "description": "ID du restaurant", + "description": "Restaurant ID", "required": true, "style": "simple", "explode": false, @@ -2968,7 +2968,7 @@ { "name": "timeBegin", "in": "query", - "description": "Date de début de la période d'analyse (optionnel)", + "description": "Start date of the analysis period (optional)", "required": false, "style": "form", "explode": true, @@ -2980,7 +2980,7 @@ { "name": "timeEnd", "in": "query", - "description": "Date de fin de la période d'analyse (optionnel)", + "description": "End date of the analysis period (optional)", "required": false, "style": "form", "explode": true, @@ -2992,7 +2992,7 @@ ], "responses": { "200": { - "description": "Succès - Plat le plus populaire retourné", + "description": "Success - Most popular dish returned", "content": { "application/json": { "schema": { @@ -3000,12 +3000,12 @@ "properties": { "food": { "type": "integer", - "description": "ID du plat le plus commandé", + "description": "ID of the most ordered dish", "example": 123 }, "nbrOrders": { "type": "integer", - "description": "Nombre total de commandes pour ce plat", + "description": "Total number of orders for this dish", "example": 100 } } @@ -3014,16 +3014,16 @@ } }, "400": { - "description": "Paramètres invalides" + "description": "Invalid parameters" }, "401": { - "description": "Authentification requise" + "description": "Authentication required" }, "404": { - "description": "Aucun plat commandé sur la période spécifiée" + "description": "No dishes ordered in the specified period" }, "5XX": { - "description": "Erreur interne du serveur" + "description": "Internal server error" } }, "security": [ @@ -3136,13 +3136,13 @@ "tags": [ "KPI" ], - "summary": "Prévision des ventes journalières pour chaque plat", - "description": "Retourne, pour chaque plat, une prévision du nombre de ventes par jour sur la période analysée. La date cible est optionnelle et permet d'obtenir une prévision pour un jour spécifique.", + "summary": "Daily sales forecast for each dish", + "description": "Returns, for each dish, a forecast of the number of sales per day over the analyzed period. The target date is optional and allows for a forecast for a specific day.", "parameters": [ { "name": "restaurant_id", "in": "path", - "description": "ID du restaurant", + "description": "ID of the restaurant", "required": true, "style": "simple", "explode": false, @@ -3153,7 +3153,7 @@ { "name": "date", "in": "query", - "description": "Date cible au format ISO (optionnel, ex: 2025-05-28T20:58:53.621Z)", + "description": "Target date in ISO format (optional, e.g., 2025-05-28T20:58:53.621Z)", "required": false, "schema": { "type": "string", @@ -3163,7 +3163,7 @@ ], "responses": { "200": { - "description": "Prévision des ventes par plat", + "description": "Sales forecast by dish", "content": { "application/json": { "schema": { @@ -3173,17 +3173,17 @@ "properties": { "food": { "type": "integer", - "description": "ID du plat" + "description": "ID of the dish" }, "forecast": { "type": "number", - "description": "Prévision du nombre de ventes par jour" + "description": "Forecast of the number of sales per day" } } } }, "examples": { - "Prévision": { + "Forecast": { "value": [ { "food": 12, "forecast": 15 }, { "food": 13, "forecast": 8 } @@ -3194,13 +3194,13 @@ } }, "400": { - "description": "Paramètres invalides (ID restaurant ou date)" + "description": "Invalid parameters (restaurant ID or date)" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { - "description": "Aucune commande trouvée pour un plat" + "description": "No orders found for a dish" }, "5XX": { "$ref": "#/components/responses/ServerError" @@ -3438,12 +3438,12 @@ "tags": [ "KPI" ], - "summary": "Récupérer les KPIs pour un cas d'usage spécifique (POS ou KDS)", + "summary": "Retrieve KPIs for a specific use case (POS or KDS)", "parameters": [ { "name": "restaurant_id", "in": "path", - "description": "ID du restaurant", + "description": "Restaurant ID", "required": true, "style": "simple", "explode": false, @@ -3454,7 +3454,7 @@ { "name": "useCase", "in": "query", - "description": "Cas d'usage (POS ou KDS)", + "description": "Use case (POS or KDS)", "required": true, "schema": { "type": "string", @@ -3464,7 +3464,7 @@ ], "responses": { "200": { - "description": "KPIs pour le cas d'usage demandé", + "description": "KPIs for the requested use case", "content": { "application/json": { "schema": { @@ -3472,12 +3472,12 @@ { "type": "object", "properties": { - "ordersInProgress": { "type": "integer", "description": "Nombre de commandes en cours" }, - "clientsCount": { "type": "integer", "description": "Nombre de clients sur place aujourd'hui" }, - "averageWaitingTime1h": { "type": "object", "description": "Temps d'attente moyen sur 1h", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, - "averageWaitingTime15m": { "type": "object", "description": "Temps d'attente moyen sur 15min", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, - "averagePrepTime1h": { "type": "object", "description": "Temps de préparation moyen sur 1h", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, - "averagePrepTime15m": { "type": "object", "description": "Temps de préparation moyen sur 15min", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } } + "ordersInProgress": { "type": "integer", "description": "Number of orders in progress" }, + "clientsCount": { "type": "integer", "description": "Number of clients on site today" }, + "averageWaitingTime1h": { "type": "object", "description": "Average waiting time over 1h", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, + "averageWaitingTime15m": { "type": "object", "description": "Average waiting time over 15min", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, + "averagePrepTime1h": { "type": "object", "description": "Average preparation time over 1h", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, + "averagePrepTime15m": { "type": "object", "description": "Average preparation time over 15min", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } } }, "example": { "ordersInProgress": 100, @@ -3491,12 +3491,12 @@ { "type": "object", "properties": { - "last15mOrders": { "type": "integer", "description": "Nombre de commandes sur les 15 dernières minutes" }, - "clientsCount": { "type": "integer", "description": "Nombre de clients sur place aujourd'hui" }, - "averageWaitingTime1h": { "type": "object", "description": "Temps d'attente moyen sur 1h", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, - "averageWaitingTime15m": { "type": "object", "description": "Temps d'attente moyen sur 15min", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, - "averagePrepTime1h": { "type": "object", "description": "Temps de préparation moyen sur 1h", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, - "averagePrepTime15m": { "type": "object", "description": "Temps de préparation moyen sur 15min", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } } + "last15mOrders": { "type": "integer", "description": "Number of orders over the last 15 minutes" }, + "clientsCount": { "type": "integer", "description": "Number of clients on site today" }, + "averageWaitingTime1h": { "type": "object", "description": "Average waiting time over 1h", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, + "averageWaitingTime15m": { "type": "object", "description": "Average waiting time over 15min", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, + "averagePrepTime1h": { "type": "object", "description": "Average preparation time over 1h", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } }, + "averagePrepTime15m": { "type": "object", "description": "Average preparation time over 15min", "properties": { "hours": { "type": "integer" }, "minutes": { "type": "integer" }, "seconds": { "type": "integer" } } } }, "example": { "last15mOrders": 20, @@ -3513,7 +3513,7 @@ } }, "400": { - "description": "Paramètres invalides" + "description": "Invalid parameters" }, "401": { "$ref": "#/components/responses/Unauthorized" @@ -3587,18 +3587,18 @@ } }, "/{restaurant_id}/kpi/revenues": { - "summary": "KPI endpoints", + "summary": "KPI Endpoints", "get": { "tags": [ "KPI" ], - "summary": "Récupérer les revenus pour une période spécifique", - "description": "Retourne les revenus et le nombre de commandes pour une période donnée. Si le paramètre breakdown est fourni, les données sont segmentées par tranches horaires. Le champ averageWaitingTime n'apparaît que si useCase est défini.", + "summary": "Retrieve Revenues for a Specific Period", + "description": "Returns revenues and the number of orders for a given period. If the breakdown parameter is provided, the data is segmented by time slots. The averageWaitingTime field only appears if useCase is defined.", "parameters": [ { "name": "restaurant_id", "in": "path", - "description": "ID du restaurant", + "description": "Restaurant ID", "required": true, "style": "simple", "explode": false, @@ -3609,7 +3609,7 @@ { "name": "timeBegin", "in": "query", - "description": "Date de début de la période d'analyse (format: YYYY-MM-DD)", + "description": "Start date of the analysis period (format: YYYY-MM-DD)", "required": false, "style": "form", "explode": true, @@ -3621,7 +3621,7 @@ { "name": "timeEnd", "in": "query", - "description": "Date de fin de la période d'analyse (format: YYYY-MM-DD)", + "description": "End date of the analysis period (format: YYYY-MM-DD)", "required": false, "style": "form", "explode": true, @@ -3633,7 +3633,7 @@ { "name": "breakdown", "in": "query", - "description": "Durée en minutes pour segmenter les données par tranches horaires (optionnel)", + "description": "Duration in minutes for segmenting data by time slots (optional)", "required": false, "style": "form", "explode": true, @@ -3645,7 +3645,7 @@ { "name": "useCase", "in": "query", - "description": "Cas d'usage (POS ou KDS) - si défini, inclut averageWaitingTime dans la réponse", + "description": "Use case (POS or KDS) - if defined, includes averageWaitingTime in the response", "required": false, "style": "form", "explode": true, @@ -3657,28 +3657,28 @@ ], "responses": { "200": { - "description": "Revenus pour la période demandée", + "description": "Revenues for the requested period", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", - "description": "Réponse sans breakdown", + "description": "Response without breakdown", "properties": { "revenues": { "type": "number", - "description": "Montant total des revenus", + "description": "Total revenue amount", "example": 0 }, "ordersCount": { "type": "integer", - "description": "Nombre total de commandes", + "description": "Total number of orders", "example": 50 }, "averageWaitingTime": { "type": "object", - "description": "Temps d'attente moyen (présent seulement si useCase est défini)", + "description": "Average waiting time (only present if useCase is defined)", "properties": { "hours": { "type": "integer", "example": 0 }, "minutes": { "type": "integer", "example": 15 }, @@ -3694,23 +3694,23 @@ }, { "type": "object", - "description": "Réponse avec breakdown par tranches horaires", + "description": "Response with breakdown by time slots", "additionalProperties": { "type": "object", "properties": { "revenues": { "type": "number", - "description": "Montant des revenus pour cette tranche", + "description": "Revenue amount for this slot", "example": 0 }, "ordersCount": { "type": "integer", - "description": "Nombre de commandes pour cette tranche", + "description": "Number of orders for this slot", "example": 5 }, "averageWaitingTime": { "type": "object", - "description": "Temps d'attente moyen pour cette tranche (présent seulement si useCase est défini)", + "description": "Average waiting time for this slot (only present if useCase is defined)", "properties": { "hours": { "type": "integer", "example": 0 }, "minutes": { "type": "integer", "example": 12 }, @@ -3738,7 +3738,7 @@ } }, "400": { - "description": "Paramètres invalides (ID restaurant ou format de date)" + "description": "Invalid parameters (restaurant ID or date format)" }, "401": { "$ref": "#/components/responses/Unauthorized" diff --git a/src/modules/kpi/kpi.controller.ts b/src/modules/kpi/kpi.controller.ts index bb2eeb6..2a09799 100644 --- a/src/modules/kpi/kpi.controller.ts +++ b/src/modules/kpi/kpi.controller.ts @@ -553,7 +553,7 @@ export class KpiController { ) : undefined; const orderData: any = { - "revenues": 0, + "revenues": await this.kpiService.revenueTotal(idRestaurant, slot.timeBegin, slot.timeEnd, undefined), "ordersCount": await this.kpiService.clientsCount( idRestaurant, slot.timeBegin, @@ -577,7 +577,7 @@ export class KpiController { ) : undefined; const orders: any = { - "revenues": 0, + "revenues": await this.kpiService.revenueTotal(idRestaurant, timeBegin, timeEnd, undefined), "ordersCount": await this.kpiService.clientsCount( idRestaurant, timeBegin, diff --git a/src/modules/kpi/kpi.service.ts b/src/modules/kpi/kpi.service.ts index b871fec..854cb21 100644 --- a/src/modules/kpi/kpi.service.ts +++ b/src/modules/kpi/kpi.service.ts @@ -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 { + 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: false } } }, + { $project: { _id: 0, 'orders.date': 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.date); + return orderDate >= beginDate && orderDate <= endDate; + }); + } + + const totalRevenue = orders.reduce((sum, item) => { + return sum + parseFloat(item.orders.total); + }, 0); + + return totalRevenue; + } } From c5f31d8a414411ff3e30703998dbe29cd0b40ee5 Mon Sep 17 00:00:00 2001 From: Xantass Date: Mon, 30 Jun 2025 14:34:48 +0200 Subject: [PATCH 3/5] linter: fix errors --- src/modules/kpi/kpi.controller.ts | 64 +++++++++++++++++----------- src/modules/kpi/pipe/useCase.pipe.ts | 3 +- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/modules/kpi/kpi.controller.ts b/src/modules/kpi/kpi.controller.ts index 2a09799..938add8 100644 --- a/src/modules/kpi/kpi.controller.ts +++ b/src/modules/kpi/kpi.controller.ts @@ -20,7 +20,7 @@ import { UseCasePipe } from './pipe/useCase.pipe'; @Controller('api/:idRestaurant/kpi') @UseGuards(JwtAuthGuard) export class KpiController { - constructor(private readonly kpiService: KpiService) { } + constructor(private readonly kpiService: KpiService) {} /** * Get the average preparation time for a specific dish @@ -502,7 +502,6 @@ export class KpiController { } } - /** * Get the revenues for a specific period * @param idRestaurant - The restaurant identifier (must be positive) @@ -545,16 +544,24 @@ export class KpiController { 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 averageWaitingTime = + useCase !== undefined + ? await this.kpiService.averageTimeOrders( + idRestaurant, + slot.timeBegin, + slot.timeEnd, + undefined, + ) + : undefined; + const orderData: any = { - "revenues": await this.kpiService.revenueTotal(idRestaurant, slot.timeBegin, slot.timeEnd, undefined), - "ordersCount": await this.kpiService.clientsCount( + revenues: await this.kpiService.revenueTotal( + idRestaurant, + slot.timeBegin, + slot.timeEnd, + undefined, + ), + ordersCount: await this.kpiService.clientsCount( idRestaurant, slot.timeBegin, slot.timeEnd, @@ -562,23 +569,31 @@ export class KpiController { undefined, ), }; - useCase !== undefined ? orderData.averageWaitingTime = averageWaitingTime : orderData; - + 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 averageWaitingTime = + useCase !== undefined + ? await this.kpiService.averageTimeOrders( + idRestaurant, + timeBegin, + timeEnd, + undefined, + ) + : undefined; + const orders: any = { - "revenues": await this.kpiService.revenueTotal(idRestaurant, timeBegin, timeEnd, undefined), - "ordersCount": await this.kpiService.clientsCount( + revenues: await this.kpiService.revenueTotal( + idRestaurant, + timeBegin, + timeEnd, + undefined, + ), + ordersCount: await this.kpiService.clientsCount( idRestaurant, timeBegin, timeEnd, @@ -586,9 +601,8 @@ export class KpiController { undefined, ), }; - useCase !== undefined ? orders.averageWaitingTime = averageWaitingTime : orders; - + if (useCase !== undefined) orders.averageWaitingTime = averageWaitingTime; return orders; } } -} \ No newline at end of file +} diff --git a/src/modules/kpi/pipe/useCase.pipe.ts b/src/modules/kpi/pipe/useCase.pipe.ts index f085ad3..45cbff5 100644 --- a/src/modules/kpi/pipe/useCase.pipe.ts +++ b/src/modules/kpi/pipe/useCase.pipe.ts @@ -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' && value !== 'statsPOS') return undefined; + if (value !== 'POS' && value !== 'KDS' && value !== 'statsPOS') + return undefined; return value; } } From df69da60e53580b9754a106d382e69f3fa2a84d8 Mon Sep 17 00:00:00 2001 From: Xantass Date: Thu, 3 Jul 2025 16:52:28 +0200 Subject: [PATCH 4/5] fix: use date of timePayment for filter the order in the service 'revenue' --- src/modules/kpi/kpi.controller.ts | 64 +++++++++------------- src/modules/kpi/kpi.service.ts | 10 ++-- src/modules/orders/DTO/food_ordered.dto.ts | 4 ++ src/modules/orders/DTO/orders.dto.ts | 4 ++ src/modules/orders/orders.service.ts | 7 ++- 5 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/modules/kpi/kpi.controller.ts b/src/modules/kpi/kpi.controller.ts index 938add8..13b0b7c 100644 --- a/src/modules/kpi/kpi.controller.ts +++ b/src/modules/kpi/kpi.controller.ts @@ -20,7 +20,7 @@ import { UseCasePipe } from './pipe/useCase.pipe'; @Controller('api/:idRestaurant/kpi') @UseGuards(JwtAuthGuard) export class KpiController { - constructor(private readonly kpiService: KpiService) {} + constructor(private readonly kpiService: KpiService) { } /** * Get the average preparation time for a specific dish @@ -547,27 +547,22 @@ export class KpiController { const averageWaitingTime = useCase !== undefined ? await this.kpiService.averageTimeOrders( - idRestaurant, - slot.timeBegin, - slot.timeEnd, - undefined, - ) + idRestaurant, + slot.timeBegin, + slot.timeEnd, + undefined, + ) : undefined; + const { total, ordersCount } = await this.kpiService.revenueTotal( + idRestaurant, + slot.timeBegin, + slot.timeEnd, + undefined, + ); const orderData: any = { - revenues: await this.kpiService.revenueTotal( - idRestaurant, - slot.timeBegin, - slot.timeEnd, - undefined, - ), - ordersCount: await this.kpiService.clientsCount( - idRestaurant, - slot.timeBegin, - slot.timeEnd, - undefined, - undefined, - ), + revenues: total, + ordersCount: ordersCount, }; if (useCase !== undefined) orderData.averageWaitingTime = averageWaitingTime; @@ -579,27 +574,22 @@ export class KpiController { const averageWaitingTime = useCase !== undefined ? await this.kpiService.averageTimeOrders( - idRestaurant, - timeBegin, - timeEnd, - undefined, - ) + idRestaurant, + timeBegin, + timeEnd, + undefined, + ) : undefined; + const { total, ordersCount } = await this.kpiService.revenueTotal( + idRestaurant, + timeBegin, + timeEnd, + undefined, + ); const orders: any = { - revenues: await this.kpiService.revenueTotal( - idRestaurant, - timeBegin, - timeEnd, - undefined, - ), - ordersCount: await this.kpiService.clientsCount( - idRestaurant, - timeBegin, - timeEnd, - undefined, - undefined, - ), + revenues: total, + ordersCount: ordersCount, }; if (useCase !== undefined) orders.averageWaitingTime = averageWaitingTime; return orders; diff --git a/src/modules/kpi/kpi.service.ts b/src/modules/kpi/kpi.service.ts index 854cb21..0a68748 100644 --- a/src/modules/kpi/kpi.service.ts +++ b/src/modules/kpi/kpi.service.ts @@ -627,7 +627,7 @@ export class KpiService extends DB { timeBegin: string, timeEnd: string, channel?: string, - ): Promise { + ): Promise<{ total: number; ordersCount: number }> { const db = this.getDbConnection(); let orders = await db .collection('restaurant') @@ -635,8 +635,8 @@ export class KpiService extends DB { { $match: { id: idRestaurant } }, { $unwind: '$orders' }, { $match: { 'orders.channel': channel || { $exists: true } } }, - { $match: { 'orders.payment': { $exists: false } } }, - { $project: { _id: 0, 'orders.date': 1, 'orders.total': 1 } }, + { $match: { 'orders.payment': { $exists: true } } }, + { $project: { _id: 0, 'orders.timePayment': 1, 'orders.total': 1 } }, ]) .toArray(); @@ -644,7 +644,7 @@ export class KpiService extends DB { const beginDate = new Date(timeBegin); const endDate = new Date(timeEnd); orders = orders.filter((item) => { - const orderDate = new Date(item.orders.date); + const orderDate = new Date(item.orders.timePayment); return orderDate >= beginDate && orderDate <= endDate; }); } @@ -653,6 +653,6 @@ export class KpiService extends DB { return sum + parseFloat(item.orders.total); }, 0); - return totalRevenue; + return { total: totalRevenue, ordersCount: orders.length }; } } diff --git a/src/modules/orders/DTO/food_ordered.dto.ts b/src/modules/orders/DTO/food_ordered.dto.ts index e8e2097..16382b0 100644 --- a/src/modules/orders/DTO/food_ordered.dto.ts +++ b/src/modules/orders/DTO/food_ordered.dto.ts @@ -24,6 +24,10 @@ class ModsIngredient { @IsString() @IsNotEmpty() ingredient: string; + + @IsNumber() + @IsNotEmpty() + suppPrice: number; } export class FoodOrderedDto { diff --git a/src/modules/orders/DTO/orders.dto.ts b/src/modules/orders/DTO/orders.dto.ts index c657254..325f345 100644 --- a/src/modules/orders/DTO/orders.dto.ts +++ b/src/modules/orders/DTO/orders.dto.ts @@ -55,6 +55,10 @@ export class OrdersDto { @Type(() => PaymentDto) payment?: PaymentDto[]; + @IsOptional() + @IsString() + timePayment?: string; + @IsOptional() @IsNumber() id?: number; diff --git a/src/modules/orders/orders.service.ts b/src/modules/orders/orders.service.ts index 12654f5..c4404d7 100644 --- a/src/modules/orders/orders.service.ts +++ b/src/modules/orders/orders.service.ts @@ -682,7 +682,12 @@ export class OrdersService extends DB { .collection('restaurant') .updateOne( { id: idRestaurant, 'orders.id': idOrder }, - { $set: { 'orders.$.payment': payment } }, + { + $set: { + 'orders.$.payment': payment, + 'orders.$.timePayment': new Date().toISOString(), + }, + }, ); } } From 4e94b785a343c0b706ef64fbc4a333a4235c1ce5 Mon Sep 17 00:00:00 2001 From: Xantass Date: Thu, 3 Jul 2025 16:53:06 +0200 Subject: [PATCH 5/5] linter: fix errors --- src/modules/kpi/kpi.controller.ts | 22 +++++++++++----------- src/modules/orders/orders.service.ts | 18 ++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/modules/kpi/kpi.controller.ts b/src/modules/kpi/kpi.controller.ts index 13b0b7c..8bbc14a 100644 --- a/src/modules/kpi/kpi.controller.ts +++ b/src/modules/kpi/kpi.controller.ts @@ -20,7 +20,7 @@ import { UseCasePipe } from './pipe/useCase.pipe'; @Controller('api/:idRestaurant/kpi') @UseGuards(JwtAuthGuard) export class KpiController { - constructor(private readonly kpiService: KpiService) { } + constructor(private readonly kpiService: KpiService) {} /** * Get the average preparation time for a specific dish @@ -547,11 +547,11 @@ export class KpiController { const averageWaitingTime = useCase !== undefined ? await this.kpiService.averageTimeOrders( - idRestaurant, - slot.timeBegin, - slot.timeEnd, - undefined, - ) + idRestaurant, + slot.timeBegin, + slot.timeEnd, + undefined, + ) : undefined; const { total, ordersCount } = await this.kpiService.revenueTotal( @@ -574,11 +574,11 @@ export class KpiController { const averageWaitingTime = useCase !== undefined ? await this.kpiService.averageTimeOrders( - idRestaurant, - timeBegin, - timeEnd, - undefined, - ) + idRestaurant, + timeBegin, + timeEnd, + undefined, + ) : undefined; const { total, ordersCount } = await this.kpiService.revenueTotal( diff --git a/src/modules/orders/orders.service.ts b/src/modules/orders/orders.service.ts index c4404d7..a92a356 100644 --- a/src/modules/orders/orders.service.ts +++ b/src/modules/orders/orders.service.ts @@ -678,16 +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, - 'orders.$.timePayment': new Date().toISOString(), - }, + return await db.collection('restaurant').updateOne( + { id: idRestaurant, 'orders.id': idOrder }, + { + $set: { + 'orders.$.payment': payment, + 'orders.$.timePayment': new Date().toISOString(), }, - ); + }, + ); } }