From bfb40bdcf92b81bf06f4fb75f658197ac0777fcd Mon Sep 17 00:00:00 2001 From: Xantass Date: Thu, 26 Jun 2025 14:22:44 +0200 Subject: [PATCH 1/3] feat: add new route for get all KPI for POS or KDS --- docs/swagger.json | 97 ++++++++++++++++++++++++++++ src/modules/kpi/kpi.controller.ts | 44 +++++++++++++ src/modules/kpi/pipe/useCase.pipe.ts | 11 ++++ 3 files changed, 152 insertions(+) create mode 100644 src/modules/kpi/pipe/useCase.pipe.ts diff --git a/docs/swagger.json b/docs/swagger.json index 7765b93..6fa8be5 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3312,6 +3312,103 @@ } ] } + }, + "/{restaurant_id}/kpi/displayKpi": { + "summary": "KPI endpoints", + "get": { + "tags": [ + "KPI" + ], + "summary": "Récupérer les KPIs pour un cas d'usage spécifique (POS ou KDS)", + "parameters": [ + { + "name": "restaurant_id", + "in": "path", + "description": "ID du restaurant", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "integer" + } + }, + { + "name": "useCase", + "in": "query", + "description": "Cas d'usage (POS ou KDS)", + "required": true, + "schema": { + "type": "string", + "enum": ["POS", "KDS"] + } + } + ], + "responses": { + "200": { + "description": "KPIs pour le cas d'usage demandé", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "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" } } } + }, + "example": { + "ordersInProgress": 100, + "clientsCount": 100, + "averageWaitingTime1h": { "hours": 0, "minutes": 10, "seconds": 0 }, + "averageWaitingTime15m": { "hours": 0, "minutes": 8, "seconds": 30 }, + "averagePrepTime1h": { "hours": 0, "minutes": 12, "seconds": 0 }, + "averagePrepTime15m": { "hours": 0, "minutes": 9, "seconds": 45 } + } + }, + { + "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" } } } + }, + "example": { + "last15mOrders": 20, + "clientsCount": 100, + "averageWaitingTime1h": { "hours": 0, "minutes": 10, "seconds": 0 }, + "averageWaitingTime15m": { "hours": 0, "minutes": 8, "seconds": 30 }, + "averagePrepTime1h": { "hours": 0, "minutes": 12, "seconds": 0 }, + "averagePrepTime15m": { "hours": 0, "minutes": 9, "seconds": 45 } + } + } + ] + } + } + } + }, + "400": { + "description": "Paramètres invalides" + }, + "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 9befad2..9990179 100644 --- a/src/modules/kpi/kpi.controller.ts +++ b/src/modules/kpi/kpi.controller.ts @@ -15,6 +15,7 @@ import { JwtAuthGuard } from '../../shared/guards/jwt-auth.guard'; import { BreakdownPipe } from './pipe/breakdown.pipe'; import { ChannelPipe } from './pipe/channel.pipe'; import { ServedPipe } from './pipe/served.pipe'; +import { UseCasePipe } from './pipe/useCase.pipe'; @Controller('api/:idRestaurant/kpi') @UseGuards(JwtAuthGuard) @@ -291,4 +292,47 @@ export class KpiController { throw new InternalServerErrorException('Server error'); } } + + /** + * Get the KPIs for a specific use case + * @param idRestaurant - The restaurant identifier (must be positive) + * @param useCase - The use case (POS or KDS) + * @returns The KPIs for the specified use case + * @throws {BadRequestException} When input parameters are invalid + * @throws {InternalServerErrorException} When server encounters an error + * @example + * GET /api/1/kpi/displayKpi?useCase=POS + * // returns { ordersInProgress: 100, clientsCount: 100, averageWaitingTime1h: 100, averageWaitingTime15m: 100, averagePrepTime1h: 100, averagePrepTime15m: 100 } + */ + @Get('displayKpi') + async kpiDisplayKpi( + @Param('idRestaurant', PositiveNumberPipe) idRestaurant: number, + @Query('useCase', UseCasePipe) useCase: string, + ) { + let today = new Date().toISOString(); + let oneHourAgo = new Date(new Date().getTime() - 1 * 60 * 60 * 1000).toISOString(); + let fifteenMinutesAgo = new Date(new Date().getTime() - 15 * 60 * 1000).toISOString(); + if (useCase === "POS") { + let res = { + ordersInProgress: await this.kpiService.clientsCount(idRestaurant, today.split('T')[0], today.split('T')[0], undefined, false), + clientsCount: await this.kpiService.clientsCount(idRestaurant, today.split('T')[0], today.split('T')[0], "Sur place", false), + averageWaitingTime1h: await this.kpiService.averageAllDishesTime(idRestaurant, oneHourAgo, today, true), + averageWaitingTime15m: await this.kpiService.averageAllDishesTime(idRestaurant, fifteenMinutesAgo, today, true), + averagePrepTime1h: await this.kpiService.averageTimeOrders(idRestaurant, oneHourAgo, today, undefined), + averagePrepTime15m: await this.kpiService.averageTimeOrders(idRestaurant, fifteenMinutesAgo, today, undefined), + }; + return res; + } + else { + let res = { + last15mOrders: await this.kpiService.clientsCount(idRestaurant, fifteenMinutesAgo, today, undefined, false), + clientsCount: await this.kpiService.clientsCount(idRestaurant, today.split('T')[0], today.split('T')[0], "Sur place", false), + averageWaitingTime1h: await this.kpiService.averageAllDishesTime(idRestaurant, oneHourAgo, today, true), + averageWaitingTime15m: await this.kpiService.averageAllDishesTime(idRestaurant, fifteenMinutesAgo, today, true), + averagePrepTime1h: await this.kpiService.averageTimeOrders(idRestaurant, oneHourAgo, today, undefined), + averagePrepTime15m: await this.kpiService.averageTimeOrders(idRestaurant, fifteenMinutesAgo, today, undefined), + }; + return res; + } + } } diff --git a/src/modules/kpi/pipe/useCase.pipe.ts b/src/modules/kpi/pipe/useCase.pipe.ts new file mode 100644 index 0000000..7293b6f --- /dev/null +++ b/src/modules/kpi/pipe/useCase.pipe.ts @@ -0,0 +1,11 @@ +import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'; + +@Injectable() +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; + return value; + } +} \ No newline at end of file From 3df7ea0d938a6609cfd0e722797b0ff6791e3935 Mon Sep 17 00:00:00 2001 From: Xantass Date: Thu, 26 Jun 2025 14:32:19 +0200 Subject: [PATCH 2/3] linter: fix error --- src/modules/kpi/kpi.controller.ts | 107 ++++++++++++++++++++++----- src/modules/kpi/pipe/useCase.pipe.ts | 4 +- 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/src/modules/kpi/kpi.controller.ts b/src/modules/kpi/kpi.controller.ts index 9990179..581eb6c 100644 --- a/src/modules/kpi/kpi.controller.ts +++ b/src/modules/kpi/kpi.controller.ts @@ -309,28 +309,95 @@ export class KpiController { @Param('idRestaurant', PositiveNumberPipe) idRestaurant: number, @Query('useCase', UseCasePipe) useCase: string, ) { - let today = new Date().toISOString(); - let oneHourAgo = new Date(new Date().getTime() - 1 * 60 * 60 * 1000).toISOString(); - let fifteenMinutesAgo = new Date(new Date().getTime() - 15 * 60 * 1000).toISOString(); - if (useCase === "POS") { - let res = { - ordersInProgress: await this.kpiService.clientsCount(idRestaurant, today.split('T')[0], today.split('T')[0], undefined, false), - clientsCount: await this.kpiService.clientsCount(idRestaurant, today.split('T')[0], today.split('T')[0], "Sur place", false), - averageWaitingTime1h: await this.kpiService.averageAllDishesTime(idRestaurant, oneHourAgo, today, true), - averageWaitingTime15m: await this.kpiService.averageAllDishesTime(idRestaurant, fifteenMinutesAgo, today, true), - averagePrepTime1h: await this.kpiService.averageTimeOrders(idRestaurant, oneHourAgo, today, undefined), - averagePrepTime15m: await this.kpiService.averageTimeOrders(idRestaurant, fifteenMinutesAgo, today, undefined), + const today = new Date().toISOString(); + const oneHourAgo = new Date( + new Date().getTime() - 1 * 60 * 60 * 1000, + ).toISOString(); + const fifteenMinutesAgo = new Date( + new Date().getTime() - 15 * 60 * 1000, + ).toISOString(); + if (useCase === 'POS') { + const res = { + ordersInProgress: await this.kpiService.clientsCount( + idRestaurant, + today.split('T')[0], + today.split('T')[0], + undefined, + false, + ), + clientsCount: await this.kpiService.clientsCount( + idRestaurant, + today.split('T')[0], + today.split('T')[0], + 'Sur place', + false, + ), + averageWaitingTime1h: await this.kpiService.averageAllDishesTime( + idRestaurant, + oneHourAgo, + today, + true, + ), + averageWaitingTime15m: await this.kpiService.averageAllDishesTime( + idRestaurant, + fifteenMinutesAgo, + today, + true, + ), + averagePrepTime1h: await this.kpiService.averageTimeOrders( + idRestaurant, + oneHourAgo, + today, + undefined, + ), + averagePrepTime15m: await this.kpiService.averageTimeOrders( + idRestaurant, + fifteenMinutesAgo, + today, + undefined, + ), }; return res; - } - else { - let res = { - last15mOrders: await this.kpiService.clientsCount(idRestaurant, fifteenMinutesAgo, today, undefined, false), - clientsCount: await this.kpiService.clientsCount(idRestaurant, today.split('T')[0], today.split('T')[0], "Sur place", false), - averageWaitingTime1h: await this.kpiService.averageAllDishesTime(idRestaurant, oneHourAgo, today, true), - averageWaitingTime15m: await this.kpiService.averageAllDishesTime(idRestaurant, fifteenMinutesAgo, today, true), - averagePrepTime1h: await this.kpiService.averageTimeOrders(idRestaurant, oneHourAgo, today, undefined), - averagePrepTime15m: await this.kpiService.averageTimeOrders(idRestaurant, fifteenMinutesAgo, today, undefined), + } else { + const res = { + last15mOrders: await this.kpiService.clientsCount( + idRestaurant, + fifteenMinutesAgo, + today, + undefined, + false, + ), + clientsCount: await this.kpiService.clientsCount( + idRestaurant, + today.split('T')[0], + today.split('T')[0], + 'Sur place', + false, + ), + averageWaitingTime1h: await this.kpiService.averageAllDishesTime( + idRestaurant, + oneHourAgo, + today, + true, + ), + averageWaitingTime15m: await this.kpiService.averageAllDishesTime( + idRestaurant, + fifteenMinutesAgo, + today, + true, + ), + averagePrepTime1h: await this.kpiService.averageTimeOrders( + idRestaurant, + oneHourAgo, + today, + undefined, + ), + averagePrepTime15m: await this.kpiService.averageTimeOrders( + idRestaurant, + fifteenMinutesAgo, + today, + undefined, + ), }; return res; } diff --git a/src/modules/kpi/pipe/useCase.pipe.ts b/src/modules/kpi/pipe/useCase.pipe.ts index 7293b6f..060aa17 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') return undefined; return value; } -} \ No newline at end of file +} From d93cafc9294cb3338a89e4325818d14110496c14 Mon Sep 17 00:00:00 2001 From: Xantass Date: Fri, 27 Jun 2025 10:54:51 +0200 Subject: [PATCH 3/3] fix: all issue mention by jules --- src/modules/kpi/kpi.controller.ts | 32 ++++++++++++++++--------------- src/modules/kpi/kpi.service.ts | 14 ++++++++------ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/modules/kpi/kpi.controller.ts b/src/modules/kpi/kpi.controller.ts index 581eb6c..94bab78 100644 --- a/src/modules/kpi/kpi.controller.ts +++ b/src/modules/kpi/kpi.controller.ts @@ -310,6 +310,8 @@ export class KpiController { @Query('useCase', UseCasePipe) useCase: string, ) { const today = new Date().toISOString(); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); const oneHourAgo = new Date( new Date().getTime() - 1 * 60 * 60 * 1000, ).toISOString(); @@ -321,36 +323,36 @@ export class KpiController { ordersInProgress: await this.kpiService.clientsCount( idRestaurant, today.split('T')[0], - today.split('T')[0], + tomorrow.toISOString().split('T')[0], + undefined, undefined, - false, ), clientsCount: await this.kpiService.clientsCount( idRestaurant, today.split('T')[0], - today.split('T')[0], + tomorrow.toISOString().split('T')[0], 'Sur place', - false, + undefined, ), - averageWaitingTime1h: await this.kpiService.averageAllDishesTime( + averagePrepTime1h: await this.kpiService.averageAllDishesTime( idRestaurant, oneHourAgo, today, true, ), - averageWaitingTime15m: await this.kpiService.averageAllDishesTime( + averagePrepTime15m: await this.kpiService.averageAllDishesTime( idRestaurant, fifteenMinutesAgo, today, true, ), - averagePrepTime1h: await this.kpiService.averageTimeOrders( + averageWaitingTime1h: await this.kpiService.averageTimeOrders( idRestaurant, oneHourAgo, today, undefined, ), - averagePrepTime15m: await this.kpiService.averageTimeOrders( + averageWaitingTime15m: await this.kpiService.averageTimeOrders( idRestaurant, fifteenMinutesAgo, today, @@ -365,34 +367,34 @@ export class KpiController { fifteenMinutesAgo, today, undefined, - false, + undefined, ), clientsCount: await this.kpiService.clientsCount( idRestaurant, today.split('T')[0], - today.split('T')[0], + tomorrow.toISOString().split('T')[0], 'Sur place', - false, + undefined, ), - averageWaitingTime1h: await this.kpiService.averageAllDishesTime( + averagePrepTime1h: await this.kpiService.averageAllDishesTime( idRestaurant, oneHourAgo, today, true, ), - averageWaitingTime15m: await this.kpiService.averageAllDishesTime( + averagePrepTime15m: await this.kpiService.averageAllDishesTime( idRestaurant, fifteenMinutesAgo, today, true, ), - averagePrepTime1h: await this.kpiService.averageTimeOrders( + averageWaitingTime1h: await this.kpiService.averageTimeOrders( idRestaurant, oneHourAgo, today, undefined, ), - averagePrepTime15m: await this.kpiService.averageTimeOrders( + averageWaitingTime15m: await this.kpiService.averageTimeOrders( idRestaurant, fifteenMinutesAgo, today, diff --git a/src/modules/kpi/kpi.service.ts b/src/modules/kpi/kpi.service.ts index 6c1631f..aeea53e 100644 --- a/src/modules/kpi/kpi.service.ts +++ b/src/modules/kpi/kpi.service.ts @@ -137,10 +137,7 @@ export class KpiService extends DB { }); } - console.log(breakdown); - if (breakdown) { - console.log('breakdown'); result.map((item) => { const orderDate = new Date(item.orders.date); @@ -152,7 +149,14 @@ export class KpiService extends DB { }); if (preparationTimes.length === 0) { - return null; + return { + time: { + hours: 0, + minutes: 0, + seconds: 0, + }, + nbrOrders: 0, + }; } const averageTime = @@ -189,8 +193,6 @@ export class KpiService extends DB { }); }); - console.log(dishMap); - const resultArray = []; for (const [food, times] of dishMap.entries()) { const averageTime =