Merge pull request #96 from Zod-Alkhair/feature/allowance-scheduling-cron-queue

feat(allowance): add delete API and configurable test intervals
This commit is contained in:
Majdalkilany0
2026-02-03 12:36:02 +03:00
committed by GitHub
5 changed files with 76 additions and 3 deletions

View File

@ -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);
}
}

View File

@ -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';

View File

@ -89,4 +89,8 @@ export class AllowanceScheduleRepository {
async updateSchedule(schedule: AllowanceSchedule): Promise<AllowanceSchedule> {
return this.allowanceScheduleRepository.save(schedule);
}
async deleteById(id: string): Promise<void> {
await this.allowanceScheduleRepository.delete({ id });
}
}

View File

@ -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<string>('ALLOWANCE_QUEUE_NAME') || ALLOWANCE_QUEUE_NAME;
this.rabbitUrl = this.configService.get<string>('RABBITMQ_URL');
this.maxRetries = Number(this.configService.get<string>('ALLOWANCE_MAX_RETRIES') || 5);
this.isTestMode = this.configService.get<string>('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();

View File

@ -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<string>('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<void> {
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);
}
}