diff --git a/src/allowance/controllers/allowance.controller.ts b/src/allowance/controllers/allowance.controller.ts index 3d2f980..974a934 100644 --- a/src/allowance/controllers/allowance.controller.ts +++ b/src/allowance/controllers/allowance.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Param, Patch, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { Roles } from '~/auth/enums'; import { IJwtPayload } from '~/auth/interfaces'; @@ -65,4 +65,14 @@ export class AllowanceController { const schedule = await this.allowanceService.updateSchedule(sub, scheduleId, body); return ResponseFactory.data(new AllowanceScheduleResponseDto(schedule)); } + + @Delete(':scheduleId') + @HttpCode(HttpStatus.NO_CONTENT) + @ApiOperation({ summary: 'Delete an allowance schedule' }) + async deleteSchedule( + @AuthenticatedUser() { sub }: IJwtPayload, + @Param('scheduleId') scheduleId: string, + ) { + await this.allowanceService.deleteSchedule(sub, scheduleId); + } } diff --git a/src/allowance/dtos/request/index.ts b/src/allowance/dtos/request/index.ts index f4ece39..579df08 100644 --- a/src/allowance/dtos/request/index.ts +++ b/src/allowance/dtos/request/index.ts @@ -1,2 +1,2 @@ export * from './create-allowance-schedule.request.dto'; -export * from './update-allowance-schedule.request.dto'; +export * from './update-allowance-schedule.request.dto'; \ No newline at end of file diff --git a/src/allowance/repositories/allowance-schedule.repository.ts b/src/allowance/repositories/allowance-schedule.repository.ts index 214f96f..3e9fcf7 100644 --- a/src/allowance/repositories/allowance-schedule.repository.ts +++ b/src/allowance/repositories/allowance-schedule.repository.ts @@ -89,4 +89,8 @@ export class AllowanceScheduleRepository { async updateSchedule(schedule: AllowanceSchedule): Promise { return this.allowanceScheduleRepository.save(schedule); } + + async deleteById(id: string): Promise { + await this.allowanceScheduleRepository.delete({ id }); + } } diff --git a/src/allowance/services/allowance-worker.service.ts b/src/allowance/services/allowance-worker.service.ts index 04acd0a..558a053 100644 --- a/src/allowance/services/allowance-worker.service.ts +++ b/src/allowance/services/allowance-worker.service.ts @@ -21,6 +21,7 @@ export class AllowanceWorkerService implements OnModuleInit, OnModuleDestroy { private readonly queueName: string; private readonly rabbitUrl?: string; private readonly maxRetries: number; + private readonly isTestMode: boolean; constructor( private readonly configService: ConfigService, @@ -31,6 +32,10 @@ export class AllowanceWorkerService implements OnModuleInit, OnModuleDestroy { this.queueName = this.configService.get('ALLOWANCE_QUEUE_NAME') || ALLOWANCE_QUEUE_NAME; this.rabbitUrl = this.configService.get('RABBITMQ_URL'); this.maxRetries = Number(this.configService.get('ALLOWANCE_MAX_RETRIES') || 5); + this.isTestMode = this.configService.get('ALLOWANCE_TEST_MODE') === 'true'; + if (this.isTestMode) { + this.logger.warn('ALLOWANCE_TEST_MODE is enabled - using short intervals (5/10/15 min)'); + } } async onModuleInit() { @@ -142,6 +147,22 @@ export class AllowanceWorkerService implements OnModuleInit, OnModuleDestroy { private computeNextRunAt(frequency: AllowanceFrequency): Date { const base = moment(); + + if (this.isTestMode) { + // Test mode: DAILY=5min, WEEKLY=10min, MONTHLY=15min + switch (frequency) { + case AllowanceFrequency.DAILY: + return base.add(5, 'minutes').toDate(); + case AllowanceFrequency.WEEKLY: + return base.add(10, 'minutes').toDate(); + case AllowanceFrequency.MONTHLY: + return base.add(15, 'minutes').toDate(); + default: + return base.toDate(); + } + } + + // Production mode: real intervals switch (frequency) { case AllowanceFrequency.DAILY: return base.add(1, 'day').toDate(); diff --git a/src/allowance/services/allowance.service.ts b/src/allowance/services/allowance.service.ts index fc1df67..3127bd2 100644 --- a/src/allowance/services/allowance.service.ts +++ b/src/allowance/services/allowance.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import moment from 'moment'; import { Junior } from '~/junior/entities'; import { JuniorService } from '~/junior/services'; @@ -11,11 +12,18 @@ import { AllowanceScheduleRepository } from '../repositories'; @Injectable() export class AllowanceService { private readonly logger = new Logger(AllowanceService.name); + private readonly isTestMode: boolean; constructor( private readonly allowanceScheduleRepository: AllowanceScheduleRepository, private readonly juniorService: JuniorService, - ) {} + private readonly configService: ConfigService, + ) { + this.isTestMode = this.configService.get('ALLOWANCE_TEST_MODE') === 'true'; + if (this.isTestMode) { + this.logger.warn('ALLOWANCE_TEST_MODE is enabled - using short intervals (5/10/15 min)'); + } + } /** * Gets all allowance schedules for a guardian, grouped by juniors with and without schedules. @@ -104,6 +112,21 @@ export class AllowanceService { return base.toDate(); } + if (this.isTestMode) { + // Test mode: DAILY=5min, WEEKLY=10min, MONTHLY=15min + switch (frequency) { + case AllowanceFrequency.DAILY: + return base.add(5, 'minutes').toDate(); + case AllowanceFrequency.WEEKLY: + return base.add(10, 'minutes').toDate(); + case AllowanceFrequency.MONTHLY: + return base.add(15, 'minutes').toDate(); + default: + return base.toDate(); + } + } + + // Production mode: real intervals switch (frequency) { case AllowanceFrequency.DAILY: return base.add(1, 'day').toDate(); @@ -172,4 +195,19 @@ export class AllowanceService { return { nextPaymentAt, monthlyTotal }; } + + /** + * Deletes an allowance schedule. + */ + async deleteSchedule(guardianId: string, scheduleId: string): Promise { + const schedule = await this.allowanceScheduleRepository.findByIdAndGuardian(scheduleId, guardianId); + + if (!schedule) { + this.logger.error(`Schedule ${scheduleId} not found for guardian ${guardianId}`); + throw new NotFoundException('ALLOWANCE.NOT_FOUND'); + } + + this.logger.log(`Deleting schedule ${scheduleId} for guardian ${guardianId}`); + await this.allowanceScheduleRepository.deleteById(scheduleId); + } }