diff --git a/src/reserves/reserve-automation.service.ts b/src/reserves/reserve-automation.service.ts new file mode 100644 index 0000000..c574643 --- /dev/null +++ b/src/reserves/reserve-automation.service.ts @@ -0,0 +1,27 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { ReservesService } from './reserves.service'; + +@Injectable() +export class ReserveAutomationService { + private readonly logger = new Logger(ReserveAutomationService.name); + + constructor(private readonly reservesService: ReservesService) {} + + /** + * Marks past reserves without a final status as completed once at least one + * full day has passed since the appointment date. + */ + @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT, { + name: 'auto-complete-past-reserves', + }) + async autoCompletePastReserves(): Promise { + const updatedCount = await this.reservesService.autoCompletePastReserves(); + + this.logger.log( + `Auto-completed ${updatedCount} past reserves without a final status`, + ); + + return updatedCount; + } +} diff --git a/src/reserves/reserves.module.ts b/src/reserves/reserves.module.ts index aaa724b..a0ad03b 100644 --- a/src/reserves/reserves.module.ts +++ b/src/reserves/reserves.module.ts @@ -13,6 +13,7 @@ import { StylistsModule } from '../stylists/stylists.module'; import { ReserveItem } from './entities/reserve-item.entity'; import { ReserveStatusHistory } from './entities/reserve-status-history.entity'; import { Reserve } from './entities/reserve.entity'; +import { ReserveAutomationService } from './reserve-automation.service'; import { ReservesController } from './reserves.controller'; import { ReservesService } from './reserves.service'; @@ -35,7 +36,7 @@ import { ReservesService } from './reserves.service'; NotificationsModule, ], controllers: [ReservesController], - providers: [ReservesService], + providers: [ReservesService, ReserveAutomationService], exports: [ReservesService], }) export class ReservesModule {} diff --git a/src/reserves/reserves.service.ts b/src/reserves/reserves.service.ts index 4176176..5850846 100644 --- a/src/reserves/reserves.service.ts +++ b/src/reserves/reserves.service.ts @@ -5,7 +5,7 @@ import { NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { In, LessThan, Repository } from 'typeorm'; import { AuthorizationService } from '../authorization/authorization.service'; import { ReservePolicy } from '../authorization/policies/reserve.policy'; import { AuthUser } from '../auth/interfaces/auth-user.interface'; @@ -292,6 +292,32 @@ export class ReservesService { }); } + async autoCompletePastReserves(): Promise { + const today = this.getTodayDateString(); + const reserves = await this.reservesRepository.find({ + where: { + status: In([ReserveStatus.PENDING, ReserveStatus.CONFIRMED]), + appointmentDate: LessThan(today), + }, + }); + + for (const reserve of reserves) { + await this.logStatusChange( + reserve.id, + reserve.status, + ReserveStatus.COMPLETED, + null, + ); + reserve.status = ReserveStatus.COMPLETED; + } + + if (reserves.length > 0) { + await this.reservesRepository.save(reserves); + } + + return reserves.length; + } + async confirmDepositPayment( reserveId: string, changedByUserId: string | null, @@ -359,6 +385,10 @@ export class ReservesService { return time.length === 5 ? `${time}:00` : time; } + private getTodayDateString(): string { + return new Date().toISOString().slice(0, 10); + } + private async validateItems(items: CreateReserveDto['items']): Promise { for (const item of items) { const serviceExists = await this.servicesRepository.existsBy({