mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2026-03-10 20:51:44 +00:00
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:
@ -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 { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { Roles } from '~/auth/enums';
|
import { Roles } from '~/auth/enums';
|
||||||
import { IJwtPayload } from '~/auth/interfaces';
|
import { IJwtPayload } from '~/auth/interfaces';
|
||||||
@ -65,4 +65,14 @@ export class AllowanceController {
|
|||||||
const schedule = await this.allowanceService.updateSchedule(sub, scheduleId, body);
|
const schedule = await this.allowanceService.updateSchedule(sub, scheduleId, body);
|
||||||
return ResponseFactory.data(new AllowanceScheduleResponseDto(schedule));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,4 +89,8 @@ export class AllowanceScheduleRepository {
|
|||||||
async updateSchedule(schedule: AllowanceSchedule): Promise<AllowanceSchedule> {
|
async updateSchedule(schedule: AllowanceSchedule): Promise<AllowanceSchedule> {
|
||||||
return this.allowanceScheduleRepository.save(schedule);
|
return this.allowanceScheduleRepository.save(schedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteById(id: string): Promise<void> {
|
||||||
|
await this.allowanceScheduleRepository.delete({ id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export class AllowanceWorkerService implements OnModuleInit, OnModuleDestroy {
|
|||||||
private readonly queueName: string;
|
private readonly queueName: string;
|
||||||
private readonly rabbitUrl?: string;
|
private readonly rabbitUrl?: string;
|
||||||
private readonly maxRetries: number;
|
private readonly maxRetries: number;
|
||||||
|
private readonly isTestMode: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly configService: ConfigService,
|
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.queueName = this.configService.get<string>('ALLOWANCE_QUEUE_NAME') || ALLOWANCE_QUEUE_NAME;
|
||||||
this.rabbitUrl = this.configService.get<string>('RABBITMQ_URL');
|
this.rabbitUrl = this.configService.get<string>('RABBITMQ_URL');
|
||||||
this.maxRetries = Number(this.configService.get<string>('ALLOWANCE_MAX_RETRIES') || 5);
|
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() {
|
async onModuleInit() {
|
||||||
@ -142,6 +147,22 @@ export class AllowanceWorkerService implements OnModuleInit, OnModuleDestroy {
|
|||||||
|
|
||||||
private computeNextRunAt(frequency: AllowanceFrequency): Date {
|
private computeNextRunAt(frequency: AllowanceFrequency): Date {
|
||||||
const base = moment();
|
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) {
|
switch (frequency) {
|
||||||
case AllowanceFrequency.DAILY:
|
case AllowanceFrequency.DAILY:
|
||||||
return base.add(1, 'day').toDate();
|
return base.add(1, 'day').toDate();
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Junior } from '~/junior/entities';
|
import { Junior } from '~/junior/entities';
|
||||||
import { JuniorService } from '~/junior/services';
|
import { JuniorService } from '~/junior/services';
|
||||||
@ -11,11 +12,18 @@ import { AllowanceScheduleRepository } from '../repositories';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AllowanceService {
|
export class AllowanceService {
|
||||||
private readonly logger = new Logger(AllowanceService.name);
|
private readonly logger = new Logger(AllowanceService.name);
|
||||||
|
private readonly isTestMode: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly allowanceScheduleRepository: AllowanceScheduleRepository,
|
private readonly allowanceScheduleRepository: AllowanceScheduleRepository,
|
||||||
private readonly juniorService: JuniorService,
|
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.
|
* Gets all allowance schedules for a guardian, grouped by juniors with and without schedules.
|
||||||
@ -104,6 +112,21 @@ export class AllowanceService {
|
|||||||
return base.toDate();
|
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) {
|
switch (frequency) {
|
||||||
case AllowanceFrequency.DAILY:
|
case AllowanceFrequency.DAILY:
|
||||||
return base.add(1, 'day').toDate();
|
return base.add(1, 'day').toDate();
|
||||||
@ -172,4 +195,19 @@ export class AllowanceService {
|
|||||||
|
|
||||||
return { nextPaymentAt, monthlyTotal };
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user