mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-11-26 16:44:54 +00:00
Compare commits
3 Commits
872d231f72
...
454ded627f
| Author | SHA1 | Date | |
|---|---|---|---|
| 454ded627f | |||
| f1484e125b | |||
| df4d2e3c1f |
@ -34,6 +34,15 @@ export class CardsController {
|
||||
return ResponseFactory.data(cards.map((card) => new ChildCardResponseDto(card)));
|
||||
}
|
||||
|
||||
@Get('child-cards/:childid')
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.GUARDIAN)
|
||||
@ApiDataResponse(ChildCardResponseDto)
|
||||
async getChildCardById(@Param('childid') childId: string, @AuthenticatedUser() { sub }: IJwtPayload) {
|
||||
const card = await this.cardService.getCardByChildId(sub, childId);
|
||||
return ResponseFactory.data(new ChildCardResponseDto(card));
|
||||
}
|
||||
|
||||
@Get('child-cards/:cardid/embossing-details')
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.GUARDIAN)
|
||||
|
||||
@ -22,10 +22,28 @@ export class Account {
|
||||
@Column('varchar', { length: 255, nullable: false, name: 'currency' })
|
||||
currency!: string;
|
||||
|
||||
@Column('decimal', { precision: 10, scale: 2, default: 0.0, name: 'balance' })
|
||||
@Column('decimal', {
|
||||
precision: 10,
|
||||
scale: 2,
|
||||
default: 0.0,
|
||||
name: 'balance',
|
||||
transformer: {
|
||||
to: (value: number) => value,
|
||||
from: (value: string) => parseFloat(value),
|
||||
},
|
||||
})
|
||||
balance!: number;
|
||||
|
||||
@Column('decimal', { precision: 10, scale: 2, default: 0.0, name: 'reserved_balance' })
|
||||
@Column('decimal', {
|
||||
precision: 10,
|
||||
scale: 2,
|
||||
default: 0.0,
|
||||
name: 'reserved_balance',
|
||||
transformer: {
|
||||
to: (value: number) => value,
|
||||
from: (value: string) => parseFloat(value),
|
||||
},
|
||||
})
|
||||
reservedBalance!: number;
|
||||
|
||||
@OneToMany(() => Card, (card) => card.account, { cascade: true })
|
||||
|
||||
@ -45,6 +45,13 @@ export class CardRepository {
|
||||
return this.cardRepository.findOne({ where: { id }, relations: ['account'] });
|
||||
}
|
||||
|
||||
findCardByChildId(guardianId: string, childId: string): Promise<Card | null> {
|
||||
return this.cardRepository.findOne({
|
||||
where: { parentId: guardianId, customerId: childId, customerType: CustomerType.CHILD },
|
||||
relations: ['account', 'customer', 'customer.user', 'customer.user.profilePicture', 'customer.junior'],
|
||||
});
|
||||
}
|
||||
|
||||
getCardByReferenceNumber(referenceNumber: string): Promise<Card | null> {
|
||||
return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] });
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ export class AccountService {
|
||||
|
||||
increaseReservedBalance(account: Account, amount: number) {
|
||||
if (account.balance < account.reservedBalance + amount) {
|
||||
throw new UnprocessableEntityException('ACCOUNT.INSUFFICIENT_BALANCE');
|
||||
throw new UnprocessableEntityException('CARD.INSUFFICIENT_BALANCE');
|
||||
}
|
||||
return this.accountRepository.increaseReservedBalance(account.id, amount);
|
||||
}
|
||||
|
||||
@ -63,6 +63,15 @@ export class CardService {
|
||||
|
||||
return this.getCardById(createdCard.id);
|
||||
}
|
||||
|
||||
async getCardByChildId(guardianId: string, childId: string): Promise<Card> {
|
||||
const card = await this.cardRepository.findCardByChildId(guardianId, childId);
|
||||
if (!card) {
|
||||
throw new BadRequestException('CARD.NOT_FOUND');
|
||||
}
|
||||
await this.prepareJuniorImages([card]);
|
||||
return card;
|
||||
}
|
||||
async getCardById(id: string): Promise<Card> {
|
||||
const card = await this.cardRepository.getCardById(id);
|
||||
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddDeletedAtColumnToJunior1757915357218 implements MigrationInterface {
|
||||
name = 'AddDeletedAtColumnToJunior1757915357218';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "juniors" ADD "deleted_at" TIMESTAMP WITH TIME ZONE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "juniors" DROP COLUMN "deleted_at"`);
|
||||
}
|
||||
}
|
||||
@ -3,3 +3,4 @@ export * from './1754915164809-create-neoleap-related-entities';
|
||||
export * from './1754915164810-seed-default-avatar';
|
||||
export * from './1757349525708-create-money-requests-table';
|
||||
export * from './1757433339849-add-reservation-amount-to-account-entity';
|
||||
export * from './1757915357218-add-deleted-at-column-to-junior';
|
||||
|
||||
@ -68,7 +68,8 @@
|
||||
"CIVIL_ID_REQUIRED": "مطلوب بطاقة الهوية المدنية.",
|
||||
"CIVIL_ID_NOT_CREATED_BY_GUARDIAN": "تم تحميل بطاقة الهوية المدنية من قبل شخص آخر غير ولي الأمر.",
|
||||
"CIVIL_ID_ALREADY_EXISTS": "بطاقة الهوية المدنية مستخدمة بالفعل من قبل طفل آخر.",
|
||||
"CANNOT_UPDATE_REGISTERED_USER": "الطفل قد سجل بالفعل. لا يُسمح بتحديث البيانات."
|
||||
"CANNOT_UPDATE_REGISTERED_USER": "الطفل قد سجل بالفعل. لا يُسمح بتحديث البيانات.",
|
||||
"CANNOT_DELETE_REGISTERED_USER": "الطفل قد سجل بالفعل. لا يُسمح بحذف الطفل."
|
||||
},
|
||||
|
||||
"MONEY_REQUEST": {
|
||||
@ -103,6 +104,7 @@
|
||||
},
|
||||
"CARD": {
|
||||
"INSUFFICIENT_BALANCE": "البطاقة لا تحتوي على رصيد كافٍ لإكمال هذا التحويل.",
|
||||
"DOES_NOT_BELONG_TO_GUARDIAN": "البطاقة لا تنتمي إلى ولي الأمر."
|
||||
"DOES_NOT_BELONG_TO_GUARDIAN": "البطاقة لا تنتمي إلى ولي الأمر.",
|
||||
"NOT_FOUND": "لم يتم العثور على البطاقة."
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,7 +67,8 @@
|
||||
"CIVIL_ID_REQUIRED": "Civil ID is required.",
|
||||
"CIVIL_ID_NOT_CREATED_BY_GUARDIAN": "The civil ID document was not uploaded by the guardian.",
|
||||
"CIVIL_ID_ALREADY_EXISTS": "The civil ID is already used by another junior.",
|
||||
"CANNOT_UPDATE_REGISTERED_USER": "The junior has already registered. Updating details is not allowed."
|
||||
"CANNOT_UPDATE_REGISTERED_USER": "The junior has already registered. Updating details is not allowed.",
|
||||
"CANNOT_DELETE_REGISTERED_USER": "The junior has already registered. Deleting the junior is not allowed."
|
||||
},
|
||||
|
||||
"MONEY_REQUEST": {
|
||||
@ -102,6 +103,7 @@
|
||||
},
|
||||
"CARD": {
|
||||
"INSUFFICIENT_BALANCE": "The card does not have sufficient balance to complete this transfer.",
|
||||
"DOES_NOT_BELONG_TO_GUARDIAN": "The card does not belong to the guardian."
|
||||
"DOES_NOT_BELONG_TO_GUARDIAN": "The card does not belong to the guardian.",
|
||||
"NOT_FOUND": "The card was not found."
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,16 @@
|
||||
import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
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';
|
||||
@ -83,6 +95,14 @@ export class JuniorController {
|
||||
return ResponseFactory.data(new JuniorResponseDto(junior));
|
||||
}
|
||||
|
||||
@Delete(':juniorId')
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.GUARDIAN)
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async deleteJunior(@AuthenticatedUser() user: IJwtPayload, @Param('juniorId', CustomParseUUIDPipe) juniorId: string) {
|
||||
await this.juniorService.deleteJunior(juniorId, user.sub);
|
||||
}
|
||||
|
||||
@Post('set-theme')
|
||||
@UseGuards(RolesGuard)
|
||||
@AllowedRoles(Roles.JUNIOR)
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
@ -49,4 +50,7 @@ export class Junior extends BaseEntity {
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@DeleteDateColumn({ name: 'deleted_at', type: 'timestamp with time zone', nullable: true })
|
||||
deletedAt!: Date | null;
|
||||
}
|
||||
|
||||
@ -65,4 +65,8 @@ export class JuniorRepository {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
softDelete(juniorId: string) {
|
||||
return this.juniorRepository.softDelete({ id: juniorId });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
import { Transactional } from 'typeorm-transactional';
|
||||
import { Roles } from '~/auth/enums';
|
||||
import { CardService } from '~/card/services';
|
||||
@ -183,6 +184,31 @@ export class JuniorService {
|
||||
return this.cardService.transferToChild(juniorId, body.amount);
|
||||
}
|
||||
|
||||
async deleteJunior(juniorId: string, guardianId: string) {
|
||||
const doesBelong = await this.doesJuniorBelongToGuardian(guardianId, juniorId);
|
||||
|
||||
if (!doesBelong) {
|
||||
this.logger.error(`Junior ${juniorId} does not belong to guardian ${guardianId}`);
|
||||
throw new BadRequestException('JUNIOR.NOT_BELONG_TO_GUARDIAN');
|
||||
}
|
||||
|
||||
const hasPassword = await this.userService.findUser({ id: juniorId, password: Not(IsNull()) });
|
||||
|
||||
if (hasPassword) {
|
||||
this.logger.error(`Cannot delete junior ${juniorId} with registered user`);
|
||||
throw new BadRequestException('JUNIOR.CANNOT_DELETE_REGISTERED_USER');
|
||||
}
|
||||
|
||||
const { affected } = await this.juniorRepository.softDelete(juniorId);
|
||||
|
||||
if (affected === 0) {
|
||||
this.logger.error(`Junior ${juniorId} not found`);
|
||||
throw new BadRequestException('JUNIOR.NOT_FOUND');
|
||||
}
|
||||
|
||||
this.logger.log(`Junior ${juniorId} deleted successfully`);
|
||||
}
|
||||
|
||||
private async prepareJuniorImages(juniors: Junior[]) {
|
||||
this.logger.log(`Preparing junior images`);
|
||||
await Promise.all(
|
||||
|
||||
Reference in New Issue
Block a user