feat: add-account-details

This commit is contained in:
Abdalhamid Alhamad
2025-07-31 14:07:01 +03:00
parent a245545811
commit 7e63abb2fb
9 changed files with 94 additions and 8 deletions

View File

@ -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;

View File

@ -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);
} }

View File

@ -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 },

View File

@ -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);
} }

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
} }

View File

@ -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"`);
}
}

View File

@ -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';