mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 13:49:40 +00:00
feat: add-account-details
This commit is contained in:
@ -11,6 +11,14 @@ export class Account {
|
|||||||
@Index({ unique: true })
|
@Index({ unique: true })
|
||||||
accountReference!: string;
|
accountReference!: string;
|
||||||
|
|
||||||
|
@Index({ unique: true })
|
||||||
|
@Column('varchar', { length: 255, nullable: false, name: 'account_number' })
|
||||||
|
accountNumber!: string;
|
||||||
|
|
||||||
|
@Index({ unique: true })
|
||||||
|
@Column('varchar', { length: 255, nullable: false, name: 'iban' })
|
||||||
|
iban!: string;
|
||||||
|
|
||||||
@Column('varchar', { length: 255, nullable: false, name: 'currency' })
|
@Column('varchar', { length: 255, nullable: false, name: 'currency' })
|
||||||
currency!: string;
|
currency!: string;
|
||||||
|
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response';
|
||||||
import { Account } from '../entities/account.entity';
|
import { Account } from '../entities/account.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountRepository {
|
export class AccountRepository {
|
||||||
constructor(@InjectRepository(Account) private readonly accountRepository: Repository<Account>) {}
|
constructor(@InjectRepository(Account) private readonly accountRepository: Repository<Account>) {}
|
||||||
|
|
||||||
createAccount(accountId: string): Promise<Account> {
|
createAccount(data: CreateApplicationResponse): Promise<Account> {
|
||||||
return this.accountRepository.save(
|
return this.accountRepository.save(
|
||||||
this.accountRepository.create({
|
this.accountRepository.create({
|
||||||
accountReference: accountId,
|
accountReference: data.accountId,
|
||||||
|
accountNumber: data.accountNumber,
|
||||||
|
iban: data.iBan,
|
||||||
balance: 0,
|
balance: 0,
|
||||||
currency: '682',
|
currency: '682',
|
||||||
}),
|
}),
|
||||||
@ -24,6 +27,13 @@ export class AccountRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAccountByAccountNumber(accountNumber: string): Promise<Account | null> {
|
||||||
|
return this.accountRepository.findOne({
|
||||||
|
where: { accountNumber },
|
||||||
|
relations: ['cards'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
topUpAccountBalance(accountReference: string, amount: number) {
|
topUpAccountBalance(accountReference: string, amount: number) {
|
||||||
return this.accountRepository.increment({ accountReference }, 'balance', amount);
|
return this.accountRepository.increment({ accountReference }, 'balance', amount);
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,13 @@ export class CardRepository {
|
|||||||
return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] });
|
return this.cardRepository.findOne({ where: { cardReference: referenceNumber }, relations: ['account'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCardByAccountNumber(accountNumber: string): Promise<Card | null> {
|
||||||
|
return this.cardRepository.findOne({
|
||||||
|
where: { account: { accountNumber } },
|
||||||
|
relations: ['account'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getActiveCardForCustomer(customerId: string): Promise<Card | null> {
|
getActiveCardForCustomer(customerId: string): Promise<Card | null> {
|
||||||
return this.cardRepository.findOne({
|
return this.cardRepository.findOne({
|
||||||
where: { customerId, status: CardStatus.ACTIVE },
|
where: { customerId, status: CardStatus.ACTIVE },
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
|
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
|
||||||
|
import { CreateApplicationResponse } from '~/common/modules/neoleap/dtos/response';
|
||||||
import { Account } from '../entities/account.entity';
|
import { Account } from '../entities/account.entity';
|
||||||
import { AccountRepository } from '../repositories/account.repository';
|
import { AccountRepository } from '../repositories/account.repository';
|
||||||
|
|
||||||
@ -6,8 +7,8 @@ import { AccountRepository } from '../repositories/account.repository';
|
|||||||
export class AccountService {
|
export class AccountService {
|
||||||
constructor(private readonly accountRepository: AccountRepository) {}
|
constructor(private readonly accountRepository: AccountRepository) {}
|
||||||
|
|
||||||
createAccount(accountId: string): Promise<Account> {
|
createAccount(data: CreateApplicationResponse): Promise<Account> {
|
||||||
return this.accountRepository.createAccount(accountId);
|
return this.accountRepository.createAccount(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccountByReferenceNumber(accountReference: string): Promise<Account> {
|
async getAccountByReferenceNumber(accountReference: string): Promise<Account> {
|
||||||
@ -18,6 +19,14 @@ export class AccountService {
|
|||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAccountByAccountNumber(accountNumber: string): Promise<Account> {
|
||||||
|
const account = await this.accountRepository.getAccountByAccountNumber(accountNumber);
|
||||||
|
if (!account) {
|
||||||
|
throw new UnprocessableEntityException('ACCOUNT.NOT_FOUND');
|
||||||
|
}
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
async creditAccountBalance(accountReference: string, amount: number) {
|
async creditAccountBalance(accountReference: string, amount: number) {
|
||||||
return this.accountRepository.topUpAccountBalance(accountReference, amount);
|
return this.accountRepository.topUpAccountBalance(accountReference, amount);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ export class CardService {
|
|||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async createCard(customerId: string, cardData: CreateApplicationResponse): Promise<Card> {
|
async createCard(customerId: string, cardData: CreateApplicationResponse): Promise<Card> {
|
||||||
const account = await this.accountService.createAccount(cardData.accountId);
|
const account = await this.accountService.createAccount(cardData);
|
||||||
return this.cardRepository.createCard(customerId, account.id, cardData);
|
return this.cardRepository.createCard(customerId, account.id, cardData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +37,15 @@ export class CardService {
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCardByAccountNumber(accountNumber: string): Promise<Card> {
|
||||||
|
const card = await this.cardRepository.getCardByAccountNumber(accountNumber);
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
throw new BadRequestException('CARD.NOT_FOUND');
|
||||||
|
}
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
async getActiveCardForCustomer(customerId: string): Promise<Card> {
|
async getActiveCardForCustomer(customerId: string): Promise<Card> {
|
||||||
const card = await this.cardRepository.getActiveCardForCustomer(customerId);
|
const card = await this.cardRepository.getActiveCardForCustomer(customerId);
|
||||||
if (!card) {
|
if (!card) {
|
||||||
|
@ -20,7 +20,7 @@ export class TransactionService {
|
|||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async createCardTransaction(body: CardTransactionWebhookRequest) {
|
async createCardTransaction(body: CardTransactionWebhookRequest) {
|
||||||
const card = await this.cardService.getCardByReferenceNumber(body.cardId);
|
const card = await this.cardService.getCardByAccountNumber(body.cardId);
|
||||||
const existingTransaction = await this.findExistingTransaction(body.transactionId, card.account.accountReference);
|
const existingTransaction = await this.findExistingTransaction(body.transactionId, card.account.accountReference);
|
||||||
|
|
||||||
if (existingTransaction) {
|
if (existingTransaction) {
|
||||||
|
@ -37,4 +37,14 @@ export class CreateApplicationResponse extends InquireApplicationResponse {
|
|||||||
@Expose()
|
@Expose()
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
accountId!: string;
|
accountId!: string;
|
||||||
|
|
||||||
|
@Transform(({ obj }) => obj.AccountDetailsList?.[0]?.AccountNumber)
|
||||||
|
@Expose()
|
||||||
|
@ApiProperty()
|
||||||
|
accountNumber!: string;
|
||||||
|
|
||||||
|
@Transform(({ obj }) => obj.AccountDetailsList?.[0]?.UserData5)
|
||||||
|
@Expose()
|
||||||
|
@ApiProperty()
|
||||||
|
iBan!: string;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
export class AddAccountNumberAndIbanToAccountEntity1753948642040 implements MigrationInterface {
|
||||||
|
name = 'AddAccountNumberAndIbanToAccountEntity1753948642040';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// Step 1: Add columns as nullable
|
||||||
|
await queryRunner.query(`ALTER TABLE "accounts" ADD "account_number" character varying(255)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "accounts" ADD "iban" character varying(255)`);
|
||||||
|
|
||||||
|
// Step 2: Populate dummy values or correct ones
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE "accounts"
|
||||||
|
SET "account_number" = 'TEMP_ACC_' || id,
|
||||||
|
"iban" = 'TEMP_IBAN_' || id
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Step 3: Alter columns to be NOT NULL
|
||||||
|
await queryRunner.query(`ALTER TABLE "accounts" ALTER COLUMN "account_number" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "accounts" ALTER COLUMN "iban" SET NOT NULL`);
|
||||||
|
|
||||||
|
// Step 4: Add unique indexes
|
||||||
|
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ffd1ae96513bfb2c6eada0f7d3" ON "accounts" ("account_number")`);
|
||||||
|
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_9a4b004902294416b096e7556e" ON "accounts" ("iban")`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_9a4b004902294416b096e7556e"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_ffd1ae96513bfb2c6eada0f7d3"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "accounts" DROP COLUMN "iban"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "accounts" DROP COLUMN "account_number"`);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
export * from './1753869637732-seed-default-avatar';
|
export * from './1733750228289-initial-migration';
|
||||||
export * from './1733990253208-seeds-default-tasks-logo';
|
export * from './1733990253208-seeds-default-tasks-logo';
|
||||||
export * from './1734247702310-seeds-goals-categories';
|
export * from './1734247702310-seeds-goals-categories';
|
||||||
export * from './1733750228289-initial-migration';
|
export * from './1753869637732-seed-default-avatar';
|
||||||
export * from './1753874205042-add-neoleap-related-entities';
|
export * from './1753874205042-add-neoleap-related-entities';
|
||||||
|
export * from './1753948642040-add-account-number-and-iban-to-account-entity';
|
||||||
|
Reference in New Issue
Block a user