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)));
|
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')
|
@Get('child-cards/:cardid/embossing-details')
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@AllowedRoles(Roles.GUARDIAN)
|
@AllowedRoles(Roles.GUARDIAN)
|
||||||
|
|||||||
@ -22,10 +22,28 @@ export class Account {
|
|||||||
@Column('varchar', { length: 255, nullable: false, name: 'currency' })
|
@Column('varchar', { length: 255, nullable: false, name: 'currency' })
|
||||||
currency!: string;
|
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;
|
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;
|
reservedBalance!: number;
|
||||||
|
|
||||||
@OneToMany(() => Card, (card) => card.account, { cascade: true })
|
@OneToMany(() => Card, (card) => card.account, { cascade: true })
|
||||||
|
|||||||
@ -45,6 +45,13 @@ export class CardRepository {
|
|||||||
return this.cardRepository.findOne({ where: { id }, relations: ['account'] });
|
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> {
|
getCardByReferenceNumber(referenceNumber: string): Promise<Card | null> {
|
||||||
return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] });
|
return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export class AccountService {
|
|||||||
|
|
||||||
increaseReservedBalance(account: Account, amount: number) {
|
increaseReservedBalance(account: Account, amount: number) {
|
||||||
if (account.balance < account.reservedBalance + amount) {
|
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);
|
return this.accountRepository.increaseReservedBalance(account.id, amount);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,6 +63,15 @@ export class CardService {
|
|||||||
|
|
||||||
return this.getCardById(createdCard.id);
|
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> {
|
async getCardById(id: string): Promise<Card> {
|
||||||
const card = await this.cardRepository.getCardById(id);
|
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 './1754915164810-seed-default-avatar';
|
||||||
export * from './1757349525708-create-money-requests-table';
|
export * from './1757349525708-create-money-requests-table';
|
||||||
export * from './1757433339849-add-reservation-amount-to-account-entity';
|
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_REQUIRED": "مطلوب بطاقة الهوية المدنية.",
|
||||||
"CIVIL_ID_NOT_CREATED_BY_GUARDIAN": "تم تحميل بطاقة الهوية المدنية من قبل شخص آخر غير ولي الأمر.",
|
"CIVIL_ID_NOT_CREATED_BY_GUARDIAN": "تم تحميل بطاقة الهوية المدنية من قبل شخص آخر غير ولي الأمر.",
|
||||||
"CIVIL_ID_ALREADY_EXISTS": "بطاقة الهوية المدنية مستخدمة بالفعل من قبل طفل آخر.",
|
"CIVIL_ID_ALREADY_EXISTS": "بطاقة الهوية المدنية مستخدمة بالفعل من قبل طفل آخر.",
|
||||||
"CANNOT_UPDATE_REGISTERED_USER": "الطفل قد سجل بالفعل. لا يُسمح بتحديث البيانات."
|
"CANNOT_UPDATE_REGISTERED_USER": "الطفل قد سجل بالفعل. لا يُسمح بتحديث البيانات.",
|
||||||
|
"CANNOT_DELETE_REGISTERED_USER": "الطفل قد سجل بالفعل. لا يُسمح بحذف الطفل."
|
||||||
},
|
},
|
||||||
|
|
||||||
"MONEY_REQUEST": {
|
"MONEY_REQUEST": {
|
||||||
@ -103,6 +104,7 @@
|
|||||||
},
|
},
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"INSUFFICIENT_BALANCE": "البطاقة لا تحتوي على رصيد كافٍ لإكمال هذا التحويل.",
|
"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_REQUIRED": "Civil ID is required.",
|
||||||
"CIVIL_ID_NOT_CREATED_BY_GUARDIAN": "The civil ID document was not uploaded by the guardian.",
|
"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.",
|
"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": {
|
"MONEY_REQUEST": {
|
||||||
@ -102,6 +103,7 @@
|
|||||||
},
|
},
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"INSUFFICIENT_BALANCE": "The card does not have sufficient balance to complete this transfer.",
|
"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 { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
import { Roles } from '~/auth/enums';
|
import { Roles } from '~/auth/enums';
|
||||||
import { IJwtPayload } from '~/auth/interfaces';
|
import { IJwtPayload } from '~/auth/interfaces';
|
||||||
@ -83,6 +95,14 @@ export class JuniorController {
|
|||||||
return ResponseFactory.data(new JuniorResponseDto(junior));
|
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')
|
@Post('set-theme')
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@AllowedRoles(Roles.JUNIOR)
|
@AllowedRoles(Roles.JUNIOR)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {
|
|||||||
BaseEntity,
|
BaseEntity,
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
@ -49,4 +50,7 @@ export class Junior extends BaseEntity {
|
|||||||
|
|
||||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' })
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
updatedAt!: Date;
|
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 { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { IsNull, Not } from 'typeorm';
|
||||||
import { Transactional } from 'typeorm-transactional';
|
import { Transactional } from 'typeorm-transactional';
|
||||||
import { Roles } from '~/auth/enums';
|
import { Roles } from '~/auth/enums';
|
||||||
import { CardService } from '~/card/services';
|
import { CardService } from '~/card/services';
|
||||||
@ -183,6 +184,31 @@ export class JuniorService {
|
|||||||
return this.cardService.transferToChild(juniorId, body.amount);
|
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[]) {
|
private async prepareJuniorImages(juniors: Junior[]) {
|
||||||
this.logger.log(`Preparing junior images`);
|
this.logger.log(`Preparing junior images`);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|||||||
Reference in New Issue
Block a user