mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-07-09 22:57:26 +00:00
feat: working on money requests jounrey
This commit is contained in:
@ -20,6 +20,7 @@ import { DocumentModule } from './document/document.module';
|
||||
import { GuardianModule } from './guardian/guardian.module';
|
||||
import { HealthModule } from './health/health.module';
|
||||
import { JuniorModule } from './junior/junior.module';
|
||||
import { MoneyRequestModule } from './money-request/money-request.module';
|
||||
import { SavingGoalsModule } from './saving-goals/saving-goals.module';
|
||||
import { TaskModule } from './task/task.module';
|
||||
@Module({
|
||||
@ -56,6 +57,7 @@ import { TaskModule } from './task/task.module';
|
||||
TaskModule,
|
||||
GuardianModule,
|
||||
SavingGoalsModule,
|
||||
MoneyRequestModule,
|
||||
|
||||
OtpModule,
|
||||
DocumentModule,
|
||||
|
@ -0,0 +1,35 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateMoneyRequestEntity1734503895302 implements MigrationInterface {
|
||||
name = 'CreateMoneyRequestEntity1734503895302';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "money_requests"
|
||||
("id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"requested_amount" numeric(10,3) NOT NULL,
|
||||
"message" character varying NOT NULL,
|
||||
"frequency" character varying NOT NULL DEFAULT 'ONE_TIME',
|
||||
"status" character varying NOT NULL DEFAULT 'PENDING',
|
||||
"reviewed_at" TIMESTAMP WITH TIME ZONE,
|
||||
"end_date" TIMESTAMP WITH TIME ZONE,
|
||||
"requester_id" uuid NOT NULL,
|
||||
"reviewer_id" uuid NOT NULL,
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "PK_28cff23e9fb06cd5dbf73cd53e7" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "money_requests" ADD CONSTRAINT "FK_5cce02836c6033b6e2412995e34" FOREIGN KEY ("requester_id") REFERENCES "juniors"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "money_requests" ADD CONSTRAINT "FK_75ba0766db9a7bf03126facf31c" FOREIGN KEY ("reviewer_id") REFERENCES "guardians"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "money_requests" DROP CONSTRAINT "FK_75ba0766db9a7bf03126facf31c"`);
|
||||
await queryRunner.query(`ALTER TABLE "money_requests" DROP CONSTRAINT "FK_5cce02836c6033b6e2412995e34"`);
|
||||
await queryRunner.query(`DROP TABLE "money_requests"`);
|
||||
}
|
||||
}
|
@ -13,3 +13,4 @@ export * from './1733993920226-create-customer-notifications-settings-table';
|
||||
export * from './1734246386471-create-saving-goals-entities';
|
||||
export * from './1734247702310-seeds-goals-categories';
|
||||
export * from './1734262619426-create-junior-registration-token-table';
|
||||
export * from './1734503895302-create-money-request-entity';
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
} from 'typeorm';
|
||||
import { Customer } from '~/customer/entities';
|
||||
import { Junior } from '~/junior/entities';
|
||||
import { MoneyRequest } from '~/money-request/entities';
|
||||
import { Task } from '~/task/entities';
|
||||
|
||||
@Entity('guardians')
|
||||
@ -31,6 +32,9 @@ export class Guardian extends BaseEntity {
|
||||
@OneToMany(() => Task, (task) => task.assignedBy)
|
||||
tasks?: Task[];
|
||||
|
||||
@OneToMany(() => MoneyRequest, (moneyRequest) => moneyRequest.reviewer)
|
||||
moneyRequests?: MoneyRequest[];
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||
createdAt!: Date;
|
||||
|
||||
|
@ -13,7 +13,7 @@ export class JuniorResponseDto {
|
||||
@ApiProperty({ example: 'relationship' })
|
||||
relationship!: Relationship;
|
||||
|
||||
@ApiProperty()
|
||||
@ApiProperty({ type: DocumentMetaResponseDto })
|
||||
profilePicture!: DocumentMetaResponseDto | null;
|
||||
|
||||
constructor(junior: Junior) {
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
import { Customer } from '~/customer/entities';
|
||||
import { Document } from '~/document/entities';
|
||||
import { Guardian } from '~/guardian/entities/guradian.entity';
|
||||
import { MoneyRequest } from '~/money-request/entities';
|
||||
import { Category, SavingGoal } from '~/saving-goals/entities';
|
||||
import { Task } from '~/task/entities';
|
||||
import { Relationship } from '../enums';
|
||||
@ -70,6 +71,9 @@ export class Junior extends BaseEntity {
|
||||
@OneToMany(() => JuniorRegistrationToken, (token) => token.junior)
|
||||
registrationTokens!: JuniorRegistrationToken[];
|
||||
|
||||
@OneToMany(() => MoneyRequest, (moneyRequest) => moneyRequest.requester)
|
||||
moneyRequests!: MoneyRequest[];
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||
createdAt!: Date;
|
||||
|
||||
|
@ -15,6 +15,6 @@ import { JuniorService, JuniorTokenService, QrcodeService } from './services';
|
||||
forwardRef(() => AuthModule),
|
||||
CustomerModule,
|
||||
],
|
||||
exports: [JuniorTokenService],
|
||||
exports: [JuniorTokenService, JuniorService],
|
||||
})
|
||||
export class JuniorModule {}
|
||||
|
1
src/money-request/controllers/index.ts
Normal file
1
src/money-request/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './money-requests.controller';
|
81
src/money-request/controllers/money-requests.controller.ts
Normal file
81
src/money-request/controllers/money-requests.controller.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { Roles } from '~/auth/enums';
|
||||
import { IJwtPayload } from '~/auth/interfaces';
|
||||
import { AllowedRoles, AuthenticatedUser } from '~/common/decorators';
|
||||
import { RolesGuard } from '~/common/guards';
|
||||
import { ApiDataPageResponse, ApiDataResponse } from '~/core/decorators';
|
||||
import { CustomParseUUIDPipe } from '~/core/pipes';
|
||||
import { ResponseFactory } from '~/core/utils';
|
||||
import { CreateMoneyRequestRequestDto, MoneyRequestsFiltersRequestDto } from '../dtos/request';
|
||||
import { MoneyRequestResponseDto } from '../dtos/response/money-request.response.dto';
|
||||
import { MoneyRequestsService } from '../services';
|
||||
|
||||
@Controller('money-requests')
|
||||
@ApiTags('Money Requests')
|
||||
@ApiBearerAuth()
|
||||
export class MoneyRequestsController {
|
||||
constructor(private readonly moneyRequestsService: MoneyRequestsService) {}
|
||||
|
||||
@Post()
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.JUNIOR)
|
||||
@ApiDataResponse(MoneyRequestResponseDto)
|
||||
async createMoneyRequest(@AuthenticatedUser() { sub }: IJwtPayload, @Body() body: CreateMoneyRequestRequestDto) {
|
||||
const moneyRequest = await this.moneyRequestsService.createMoneyRequest(sub, body);
|
||||
|
||||
return ResponseFactory.data(new MoneyRequestResponseDto(moneyRequest));
|
||||
}
|
||||
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.GUARDIAN)
|
||||
@Get()
|
||||
@ApiDataPageResponse(MoneyRequestResponseDto)
|
||||
async findMoneyRequests(@AuthenticatedUser() { sub }: IJwtPayload, @Query() filters: MoneyRequestsFiltersRequestDto) {
|
||||
const [moneyRequests, itemCount] = await this.moneyRequestsService.findMoneyRequests(sub, filters);
|
||||
|
||||
return ResponseFactory.dataPage(
|
||||
moneyRequests.map((moneyRequest) => new MoneyRequestResponseDto(moneyRequest)),
|
||||
{
|
||||
itemCount,
|
||||
page: filters.page,
|
||||
size: filters.size,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.GUARDIAN)
|
||||
@Get(':moneyRequestId')
|
||||
@ApiDataResponse(MoneyRequestResponseDto)
|
||||
async findMoneyRequestById(
|
||||
@AuthenticatedUser() { sub }: IJwtPayload,
|
||||
@Param('moneyRequestId', CustomParseUUIDPipe) moneyRequestId: string,
|
||||
) {
|
||||
const moneyRequest = await this.moneyRequestsService.findMoneyRequestById(moneyRequestId, sub);
|
||||
|
||||
return ResponseFactory.data(new MoneyRequestResponseDto(moneyRequest));
|
||||
}
|
||||
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.GUARDIAN)
|
||||
@Patch(':moneyRequestId/approve')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async approveMoneyRequest(
|
||||
@AuthenticatedUser() { sub }: IJwtPayload,
|
||||
@Param('moneyRequestId', CustomParseUUIDPipe) moneyRequestId: string,
|
||||
) {
|
||||
await this.moneyRequestsService.approveMoneyRequest(moneyRequestId, sub);
|
||||
}
|
||||
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.GUARDIAN)
|
||||
@Patch(':moneyRequestId/reject')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async rejectMoneyRequest(
|
||||
@AuthenticatedUser() { sub }: IJwtPayload,
|
||||
@Param('moneyRequestId', CustomParseUUIDPipe) moneyRequestId: string,
|
||||
) {
|
||||
await this.moneyRequestsService.rejectMoneyRequest(moneyRequestId, sub);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsDateString, IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString, Max, Min } from 'class-validator';
|
||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||
import { MoneyRequestFrequency } from '~/money-request/enums';
|
||||
const MIN_REQUESTED_AMOUNT = 0.01;
|
||||
const MAX_REQUESTED_AMOUNT = 1000000;
|
||||
export class CreateMoneyRequestRequestDto {
|
||||
@Min(MIN_REQUESTED_AMOUNT, {
|
||||
message: i18n('validation.Min', { path: 'general', property: 'moneyRequest.requestedAmount' }),
|
||||
})
|
||||
@Max(MAX_REQUESTED_AMOUNT, {
|
||||
message: i18n('validation.Max', { path: 'general', property: 'moneyRequest.requestedAmount' }),
|
||||
})
|
||||
@IsNumber(
|
||||
{ allowNaN: false },
|
||||
{ message: i18n('validation.IsNumber', { path: 'general', property: 'moneyRequest.requestedAmount' }) },
|
||||
)
|
||||
@ApiProperty()
|
||||
requestedAmount!: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'moneyRequest.message' }) })
|
||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'moneyRequest.message' }) })
|
||||
message!: string;
|
||||
|
||||
@ApiPropertyOptional({ example: MoneyRequestFrequency.ONE_TIME })
|
||||
@IsEnum(MoneyRequestFrequency, {
|
||||
message: i18n('validation.IsEnum', { path: 'general', property: 'moneyRequest.frequency' }),
|
||||
})
|
||||
@IsOptional()
|
||||
frequency: MoneyRequestFrequency = MoneyRequestFrequency.ONE_TIME;
|
||||
|
||||
@ApiPropertyOptional({ example: '2021-01-01' })
|
||||
@IsDateString({}, { message: i18n('validation.IsDateString', { path: 'general', property: 'moneyRequest.endDate' }) })
|
||||
@IsOptional()
|
||||
endDate?: string;
|
||||
}
|
2
src/money-request/dtos/request/index.ts
Normal file
2
src/money-request/dtos/request/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './create-money-request.request.dto';
|
||||
export * from './money-requests-filters.request.dto';
|
@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum } from 'class-validator';
|
||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||
import { MoneyRequestStatus } from '~/money-request/enums';
|
||||
export class MoneyRequestsFiltersRequestDto extends PageOptionsRequestDto {
|
||||
@ApiProperty({ example: MoneyRequestStatus.PENDING, enum: MoneyRequestStatus })
|
||||
@IsEnum(MoneyRequestStatus, {
|
||||
message: i18n('validation.IsEnum', { path: 'general', property: 'moneyRequest.status' }),
|
||||
})
|
||||
status: MoneyRequestStatus = MoneyRequestStatus.PENDING;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { JuniorResponseDto } from '~/junior/dtos/response';
|
||||
import { MoneyRequest } from '~/money-request/entities';
|
||||
import { MoneyRequestFrequency, MoneyRequestStatus } from '~/money-request/enums';
|
||||
|
||||
export class MoneyRequestResponseDto {
|
||||
@ApiProperty({ example: 'f5c7e193-bc5e-4aa5-837b-c1edc6449880' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ type: JuniorResponseDto })
|
||||
requester!: JuniorResponseDto;
|
||||
|
||||
@ApiProperty({ example: 'f5c7e193-bc5e-4aa5-837b-c1edc6449880' })
|
||||
reviewerId!: string;
|
||||
|
||||
@ApiProperty({ example: 100.0 })
|
||||
requestedAmount!: number;
|
||||
|
||||
@ApiProperty({ example: 'Please give me money' })
|
||||
message!: string;
|
||||
|
||||
@ApiProperty({ example: MoneyRequestFrequency.ONE_TIME })
|
||||
frequency!: MoneyRequestFrequency;
|
||||
|
||||
@ApiProperty({ example: '2021-01-01' })
|
||||
endDate!: Date | null;
|
||||
|
||||
@ApiProperty({ example: MoneyRequestStatus.PENDING })
|
||||
status!: MoneyRequestStatus;
|
||||
|
||||
@ApiProperty()
|
||||
reviewedAt!: Date | null;
|
||||
|
||||
@ApiProperty()
|
||||
createdAt!: Date;
|
||||
|
||||
constructor(moneyRequest: MoneyRequest) {
|
||||
this.id = moneyRequest.id;
|
||||
this.requester = new JuniorResponseDto(moneyRequest.requester);
|
||||
this.reviewerId = moneyRequest.reviewerId;
|
||||
this.requestedAmount = moneyRequest.requestedAmount;
|
||||
this.message = moneyRequest.message;
|
||||
this.frequency = moneyRequest.frequency;
|
||||
this.endDate = moneyRequest.endDate || null;
|
||||
this.status = moneyRequest.status;
|
||||
this.reviewedAt = moneyRequest.reviewedAt || null;
|
||||
this.createdAt = moneyRequest.createdAt;
|
||||
}
|
||||
}
|
1
src/money-request/entities/index.ts
Normal file
1
src/money-request/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './money-request.entity';
|
56
src/money-request/entities/money-request.entity.ts
Normal file
56
src/money-request/entities/money-request.entity.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { Guardian } from '~/guardian/entities/guradian.entity';
|
||||
import { Junior } from '~/junior/entities';
|
||||
import { MoneyRequestFrequency, MoneyRequestStatus } from '../enums';
|
||||
|
||||
@Entity('money_requests')
|
||||
export class MoneyRequest {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 3, name: 'requested_amount' })
|
||||
requestedAmount!: number;
|
||||
|
||||
@Column({ type: 'varchar', name: 'message' })
|
||||
message!: string;
|
||||
|
||||
@Column({ type: 'varchar', name: 'frequency', default: MoneyRequestFrequency.ONE_TIME })
|
||||
frequency!: MoneyRequestFrequency;
|
||||
|
||||
@Column({ type: 'varchar', name: 'status', default: MoneyRequestStatus.PENDING })
|
||||
status!: MoneyRequestStatus;
|
||||
|
||||
@Column({ type: 'timestamp with time zone', name: 'reviewed_at', nullable: true })
|
||||
reviewedAt!: Date;
|
||||
|
||||
@Column({ type: 'timestamp with time zone', name: 'end_date', nullable: true })
|
||||
endDate!: Date | null;
|
||||
|
||||
@Column({ type: 'uuid', name: 'requester_id' })
|
||||
requesterId!: string;
|
||||
|
||||
@Column({ type: 'uuid', name: 'reviewer_id' })
|
||||
reviewerId!: string;
|
||||
|
||||
@ManyToOne(() => Junior, (junior) => junior.moneyRequests)
|
||||
@JoinColumn({ name: 'requester_id' })
|
||||
requester!: Junior;
|
||||
|
||||
@ManyToOne(() => Guardian, (guardian) => guardian.moneyRequests)
|
||||
@JoinColumn({ name: 'reviewer_id' })
|
||||
reviewer!: Guardian;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp with time zone', name: 'created_at' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamp with time zone', name: 'updated_at' })
|
||||
updatedAt!: Date;
|
||||
}
|
2
src/money-request/enums/index.ts
Normal file
2
src/money-request/enums/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './money-request-frequency.enum';
|
||||
export * from './money-request-status.enum';
|
6
src/money-request/enums/money-request-frequency.enum.ts
Normal file
6
src/money-request/enums/money-request-frequency.enum.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export enum MoneyRequestFrequency {
|
||||
ONE_TIME = 'ONE_TIME',
|
||||
DAILY = 'DAILY',
|
||||
WEEKLY = 'WEEKLY',
|
||||
MONTHLY = 'MONTHLY',
|
||||
}
|
5
src/money-request/enums/money-request-status.enum.ts
Normal file
5
src/money-request/enums/money-request-status.enum.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum MoneyRequestStatus {
|
||||
PENDING = 'PENDING',
|
||||
APPROVED = 'APPROVED',
|
||||
REJECTED = 'REJECTED',
|
||||
}
|
15
src/money-request/money-request.module.ts
Normal file
15
src/money-request/money-request.module.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { JuniorModule } from '~/junior/junior.module';
|
||||
import { MoneyRequestsController } from './controllers';
|
||||
import { MoneyRequest } from './entities';
|
||||
import { MoneyRequestsRepository } from './repositories';
|
||||
import { MoneyRequestsService } from './services';
|
||||
|
||||
@Module({
|
||||
controllers: [MoneyRequestsController],
|
||||
providers: [MoneyRequestsService, MoneyRequestsRepository],
|
||||
exports: [],
|
||||
imports: [TypeOrmModule.forFeature([MoneyRequest]), JuniorModule],
|
||||
})
|
||||
export class MoneyRequestModule {}
|
1
src/money-request/repositories/index.ts
Normal file
1
src/money-request/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './money-requests.repository';
|
49
src/money-request/repositories/money-requests.repository.ts
Normal file
49
src/money-request/repositories/money-requests.repository.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CreateMoneyRequestRequestDto, MoneyRequestsFiltersRequestDto } from '../dtos/request';
|
||||
import { MoneyRequest } from '../entities';
|
||||
import { MoneyRequestStatus } from '../enums';
|
||||
const ONE = 1;
|
||||
@Injectable()
|
||||
export class MoneyRequestsRepository {
|
||||
constructor(@InjectRepository(MoneyRequest) private readonly moneyRequestRepository: Repository<MoneyRequest>) {}
|
||||
|
||||
createMoneyRequest(requesterId: string, reviewerId: string, body: CreateMoneyRequestRequestDto) {
|
||||
return this.moneyRequestRepository.save(
|
||||
this.moneyRequestRepository.create({
|
||||
requesterId,
|
||||
reviewerId,
|
||||
requestedAmount: body.requestedAmount,
|
||||
message: body.message,
|
||||
endDate: body.endDate,
|
||||
frequency: body.frequency,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
findMoneyRequestById(moneyRequestId: string, reviewerId?: string) {
|
||||
return this.moneyRequestRepository.findOne({
|
||||
where: { id: moneyRequestId, reviewerId },
|
||||
relations: ['requester', 'requester.customer', 'requester.customer.profilePicture'],
|
||||
});
|
||||
}
|
||||
|
||||
findMoneyRequests(reviewerId: string, filters: MoneyRequestsFiltersRequestDto) {
|
||||
const query = this.moneyRequestRepository.createQueryBuilder('moneyRequest');
|
||||
query.leftJoinAndSelect('moneyRequest.requester', 'requester');
|
||||
query.leftJoinAndSelect('requester.customer', 'customer');
|
||||
query.leftJoinAndSelect('customer.profilePicture', 'profilePicture');
|
||||
query.orderBy('moneyRequest.createdAt', 'DESC');
|
||||
query.where('moneyRequest.reviewerId = :reviewerId', { reviewerId });
|
||||
query.andWhere('moneyRequest.status = :status', { status: filters.status });
|
||||
query.skip((filters.page - ONE) * filters.size);
|
||||
query.take(filters.size);
|
||||
|
||||
return query.getManyAndCount();
|
||||
}
|
||||
|
||||
updateMoneyRequestStatus(moneyRequestId: string, reviewerId: string, status: MoneyRequestStatus) {
|
||||
return this.moneyRequestRepository.update({ id: moneyRequestId, reviewerId }, { status, reviewedAt: new Date() });
|
||||
}
|
||||
}
|
1
src/money-request/services/index.ts
Normal file
1
src/money-request/services/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './money-requests.service';
|
99
src/money-request/services/money-requests.service.ts
Normal file
99
src/money-request/services/money-requests.service.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { OciService } from '~/document/services';
|
||||
import { JuniorService } from '~/junior/services';
|
||||
import { CreateMoneyRequestRequestDto, MoneyRequestsFiltersRequestDto } from '../dtos/request';
|
||||
import { MoneyRequest } from '../entities';
|
||||
import { MoneyRequestFrequency, MoneyRequestStatus } from '../enums';
|
||||
import { MoneyRequestsRepository } from '../repositories';
|
||||
|
||||
@Injectable()
|
||||
export class MoneyRequestsService {
|
||||
constructor(
|
||||
private readonly moneyRequestsRepository: MoneyRequestsRepository,
|
||||
private readonly juniorService: JuniorService,
|
||||
private readonly ociService: OciService,
|
||||
) {}
|
||||
|
||||
async createMoneyRequest(userId: string, body: CreateMoneyRequestRequestDto) {
|
||||
if (body.frequency === MoneyRequestFrequency.ONE_TIME) {
|
||||
delete body.endDate;
|
||||
}
|
||||
|
||||
if (body.endDate && new Date(body.endDate) < new Date()) {
|
||||
throw new BadRequestException('MONEY_REQUEST.END_DATE_IN_THE_PAST');
|
||||
}
|
||||
|
||||
const junior = await this.juniorService.findJuniorById(userId, true);
|
||||
|
||||
const moneyRequest = await this.moneyRequestsRepository.createMoneyRequest(junior.id, junior.guardianId, body);
|
||||
|
||||
return this.findMoneyRequestById(moneyRequest.id);
|
||||
}
|
||||
|
||||
async findMoneyRequestById(moneyRequestId: string, reviewerId?: string) {
|
||||
const moneyRequest = await this.moneyRequestsRepository.findMoneyRequestById(moneyRequestId, reviewerId);
|
||||
|
||||
if (!moneyRequest) {
|
||||
throw new BadRequestException('MONEY_REQUEST.NOT_FOUND');
|
||||
}
|
||||
|
||||
await this.prepareMoneyRequestDocument([moneyRequest]);
|
||||
|
||||
return moneyRequest;
|
||||
}
|
||||
|
||||
async findMoneyRequests(
|
||||
ReviewerId: string,
|
||||
filters: MoneyRequestsFiltersRequestDto,
|
||||
): Promise<[MoneyRequest[], number]> {
|
||||
const [moneyRequests, itemCount] = await this.moneyRequestsRepository.findMoneyRequests(ReviewerId, filters);
|
||||
await this.prepareMoneyRequestDocument(moneyRequests);
|
||||
return [moneyRequests, itemCount];
|
||||
}
|
||||
|
||||
async approveMoneyRequest(moneyRequestId: string, reviewerId: string) {
|
||||
await this.validateMoneyRequestForReview(moneyRequestId, reviewerId);
|
||||
await this.moneyRequestsRepository.updateMoneyRequestStatus(
|
||||
moneyRequestId,
|
||||
reviewerId,
|
||||
MoneyRequestStatus.APPROVED,
|
||||
);
|
||||
|
||||
//@TODO send notification and update junior balance
|
||||
}
|
||||
|
||||
async rejectMoneyRequest(moneyRequestId: string, reviewerId: string) {
|
||||
await this.validateMoneyRequestForReview(moneyRequestId, reviewerId);
|
||||
|
||||
await this.moneyRequestsRepository.updateMoneyRequestStatus(
|
||||
moneyRequestId,
|
||||
reviewerId,
|
||||
MoneyRequestStatus.REJECTED,
|
||||
);
|
||||
|
||||
//@TODO send notification
|
||||
}
|
||||
|
||||
private async prepareMoneyRequestDocument(moneyRequests: MoneyRequest[]) {
|
||||
await Promise.all(
|
||||
moneyRequests.map(async (moneyRequest) => {
|
||||
const profilePicture = moneyRequest.requester.customer.profilePicture;
|
||||
|
||||
if (profilePicture) {
|
||||
profilePicture.url = await this.ociService.generatePreSignedUrl(profilePicture);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async validateMoneyRequestForReview(moneyRequestId: string, reviewerId: string) {
|
||||
const moneyRequest = await this.moneyRequestsRepository.findMoneyRequestById(moneyRequestId, reviewerId);
|
||||
|
||||
if (!moneyRequest) {
|
||||
throw new BadRequestException('MONEY_REQUEST.NOT_FOUND');
|
||||
}
|
||||
if (moneyRequest.status !== MoneyRequestStatus.PENDING) {
|
||||
throw new BadRequestException('MONEY_REQUEST.ALREADY_REVIEWED');
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user