From ff071494b057b2b5a86c211a7e40f2cba340f47d Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:32:30 +0000 Subject: [PATCH 1/5] Setting up GitHub Classroom Feedback From fe4439defea24df426cfb694a0558ed41da42c00 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:32:33 +0000 Subject: [PATCH 2/5] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cab3c08..76507d5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/W3nV4mdD) # Banking management ## Overview From da7d6293d060cbeeecbf53e3c735628f7dc869b2 Mon Sep 17 00:00:00 2001 From: Rahul Bagal Date: Wed, 4 Mar 2026 18:22:51 +0530 Subject: [PATCH 3/5] Implement core banking models and services including Bank, BankAccount, User, GlobalRegistry, and TransactionService --- src/is-equal.ts | 3 ++ src/models/bank-account.ts | 68 ++++++++++++++++++++++++++++++ src/models/bank.ts | 51 ++++++++++++++++++++++ src/models/user.ts | 33 +++++++++++++++ src/services/GlobalRegistry.ts | 52 +++++++++++++++++++++++ src/services/TransactionService.ts | 62 +++++++++++++++++++++++++++ src/types/Common.ts | 3 ++ 7 files changed, 272 insertions(+) create mode 100644 src/is-equal.ts create mode 100644 src/models/bank-account.ts create mode 100644 src/models/bank.ts create mode 100644 src/models/user.ts create mode 100644 src/services/GlobalRegistry.ts create mode 100644 src/services/TransactionService.ts create mode 100644 src/types/Common.ts diff --git a/src/is-equal.ts b/src/is-equal.ts new file mode 100644 index 0000000..78aebbb --- /dev/null +++ b/src/is-equal.ts @@ -0,0 +1,3 @@ +export default function isEqual(a: unknown, b: unknown): boolean { + return JSON.stringify(a) === JSON.stringify(b); +} diff --git a/src/models/bank-account.ts b/src/models/bank-account.ts new file mode 100644 index 0000000..af62ccb --- /dev/null +++ b/src/models/bank-account.ts @@ -0,0 +1,68 @@ +import { v4 as uuidv4 } from 'uuid'; +import type { BankAccountId, BankId } from '@/types/Common'; + +interface BankAccountOptions { + isNegativeAllowed?: boolean; +} + +export default class BankAccount { + private readonly id: BankAccountId; + private readonly bankId: BankId; + private balance: number; + private readonly isNegativeAllowed: boolean; + + private constructor( + id: BankAccountId, + bankId: BankId, + initialBalance: number, + options?: BankAccountOptions + ) { + this.id = id; + this.bankId = bankId; + this.balance = initialBalance; + this.isNegativeAllowed = options?.isNegativeAllowed ?? false; + } + + static create(bankId: BankId, initialBalance = 0, options?: BankAccountOptions): BankAccount { + return new BankAccount(uuidv4(), bankId, initialBalance, options); + } + + getId(): BankAccountId { + return this.id; + } + + getBankId(): BankId { + return this.bankId; + } + + getBalance(): number { + return this.balance; + } + + canDebit(amount: number): boolean { + if (amount < 0) { + return false; + } + if (this.isNegativeAllowed) { + return true; + } + return this.balance >= amount; + } + + credit(amount: number): void { + if (amount <= 0) { + throw new Error('Amount should be greater than zero'); + } + this.balance += amount; + } + + debit(amount: number): void { + if (amount <= 0) { + throw new Error('Amount should be greater than zero'); + } + if (!this.canDebit(amount)) { + throw new Error('Insufficient funds'); + } + this.balance -= amount; + } +} diff --git a/src/models/bank.ts b/src/models/bank.ts new file mode 100644 index 0000000..2201a80 --- /dev/null +++ b/src/models/bank.ts @@ -0,0 +1,51 @@ +import { v4 as uuidv4 } from 'uuid'; +import BankAccount from '@/models/bank-account'; +import type { BankAccountId, BankId, UserId } from '@/types/Common'; +import GlobalRegistry from '@/services/GlobalRegistry'; +import TransactionService from '@/services/TransactionService'; + +interface BankOptions { + isNegativeAllowed?: boolean; +} + +export default class Bank { + private readonly id: BankId; + private readonly isNegativeAllowed: boolean; + private readonly accountIds: Set; + + private constructor(id: BankId, options?: BankOptions) { + this.id = id; + this.isNegativeAllowed = options?.isNegativeAllowed ?? false; + this.accountIds = new Set(); + } + + static create(options?: BankOptions): Bank { + const bank = new Bank(uuidv4(), options); + GlobalRegistry.registerBank(bank); + return bank; + } + + getId(): BankId { + return this.id; + } + + createAccount(initialBalance = 0): BankAccount { + const account = BankAccount.create(this.id, initialBalance, { + isNegativeAllowed: this.isNegativeAllowed + }); + this.accountIds.add(account.getId()); + GlobalRegistry.registerAccount(account); + return account; + } + + getAccount(accountId: BankAccountId): BankAccount { + if (!this.accountIds.has(accountId)) { + throw new Error('Bank account not found'); + } + return GlobalRegistry.getAccount(accountId); + } + + send(fromUserId: UserId, toUserId: UserId, amount: number, toBankId?: BankId): void { + TransactionService.transfer(this.id, fromUserId, toUserId, amount, toBankId); + } +} diff --git a/src/models/user.ts b/src/models/user.ts new file mode 100644 index 0000000..aa9ee1c --- /dev/null +++ b/src/models/user.ts @@ -0,0 +1,33 @@ +import { v4 as uuidv4 } from 'uuid'; +import type { BankAccountId, UserId } from '@/types/Common'; +import GlobalRegistry from '@/services/GlobalRegistry'; + +export default class User { + private readonly id: UserId; + private readonly name: string; + private readonly accountIds: BankAccountId[]; + + private constructor(id: UserId, name: string, accountIds: BankAccountId[]) { + this.id = id; + this.name = name; + this.accountIds = accountIds; + } + + static create(name: string, accountIds: BankAccountId[]): User { + const user = new User(uuidv4(), name, [...accountIds]); + GlobalRegistry.registerUser(user); + return user; + } + + getId(): UserId { + return this.id; + } + + getName(): string { + return this.name; + } + + getAccountIds(): BankAccountId[] { + return [...this.accountIds]; + } +} diff --git a/src/services/GlobalRegistry.ts b/src/services/GlobalRegistry.ts new file mode 100644 index 0000000..461a3ed --- /dev/null +++ b/src/services/GlobalRegistry.ts @@ -0,0 +1,52 @@ +import type Bank from '@/models/bank'; +import type BankAccount from '@/models/bank-account'; +import type User from '@/models/user'; +import type { BankAccountId, BankId, UserId } from '@/types/Common'; + +export default class GlobalRegistry { + private static banks = new Map(); + private static users = new Map(); + private static accounts = new Map(); + + static registerBank(bank: Bank): void { + this.banks.set(bank.getId(), bank); + } + + static registerUser(user: User): void { + this.users.set(user.getId(), user); + } + + static registerAccount(account: BankAccount): void { + this.accounts.set(account.getId(), account); + } + + static getBank(bankId: BankId): Bank { + const bank = this.banks.get(bankId); + if (!bank) { + throw new Error('Bank not found'); + } + return bank; + } + + static getUser(userId: UserId): User { + const user = this.users.get(userId); + if (!user) { + throw new Error('User not found'); + } + return user; + } + + static getAccount(accountId: BankAccountId): BankAccount { + const account = this.accounts.get(accountId); + if (!account) { + throw new Error('Bank account not found'); + } + return account; + } + + static clear(): void { + this.banks.clear(); + this.users.clear(); + this.accounts.clear(); + } +} diff --git a/src/services/TransactionService.ts b/src/services/TransactionService.ts new file mode 100644 index 0000000..096cc41 --- /dev/null +++ b/src/services/TransactionService.ts @@ -0,0 +1,62 @@ +import type BankAccount from '@/models/bank-account'; +import type { BankId, UserId } from '@/types/Common'; +import GlobalRegistry from '@/services/GlobalRegistry'; + +export default class TransactionService { + static transfer( + fromBankId: BankId, + fromUserId: UserId, + toUserId: UserId, + amount: number, + toBankId?: BankId + ): void { + if (amount <= 0) { + throw new Error('Amount should be greater than zero'); + } + + const sourceBankId = fromBankId; + const targetBankId = toBankId ?? fromBankId; + + const fromUser = GlobalRegistry.getUser(fromUserId); + const toUser = GlobalRegistry.getUser(toUserId); + + // Validate banks exist before continuing transfer. + GlobalRegistry.getBank(sourceBankId); + GlobalRegistry.getBank(targetBankId); + + const sourceAccount = this.pickSourceAccount(fromUser.getAccountIds(), sourceBankId, amount); + const targetAccount = this.pickTargetAccount(toUser.getAccountIds(), targetBankId); + + sourceAccount.debit(amount); + targetAccount.credit(amount); + } + + private static pickSourceAccount( + accountIds: string[], + sourceBankId: BankId, + amount: number + ): BankAccount { + const sourceAccounts = accountIds + .map((accountId) => GlobalRegistry.getAccount(accountId)) + .filter((account) => account.getBankId() === sourceBankId); + + for (const account of sourceAccounts) { + if (account.canDebit(amount)) { + return account; + } + } + + throw new Error('Insufficient funds'); + } + + private static pickTargetAccount(accountIds: string[], targetBankId: BankId): BankAccount { + for (const accountId of accountIds) { + const account = GlobalRegistry.getAccount(accountId); + if (account.getBankId() === targetBankId) { + return account; + } + } + + throw new Error('Target account not found'); + } +} diff --git a/src/types/Common.ts b/src/types/Common.ts new file mode 100644 index 0000000..6d2d15c --- /dev/null +++ b/src/types/Common.ts @@ -0,0 +1,3 @@ +export type BankId = string; +export type BankAccountId = string; +export type UserId = string; From acd4aa3dda23e2d011e129311d14ae6d9518fc9c Mon Sep 17 00:00:00 2001 From: Rahul Bagal Date: Wed, 4 Mar 2026 18:31:37 +0530 Subject: [PATCH 4/5] Add BankAccount methods for deposit and withdraw; enhance Bank and User classes with transfer and getAccounts methods; update TransactionService for self-transfer handling; create new service files for GlobalRegistry and TransactionService --- src/models/BankAccount.ts | 1 + src/models/bank-account.ts | 8 ++++++++ src/models/bank.ts | 4 ++++ src/models/user.ts | 4 ++++ src/services/TransactionService.ts | 25 ++++++++++++++++++++++--- src/services/global-registry.ts | 1 + src/services/transaction-service.ts | 1 + 7 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/models/BankAccount.ts create mode 100644 src/services/global-registry.ts create mode 100644 src/services/transaction-service.ts diff --git a/src/models/BankAccount.ts b/src/models/BankAccount.ts new file mode 100644 index 0000000..c94aa11 --- /dev/null +++ b/src/models/BankAccount.ts @@ -0,0 +1 @@ +export { default } from '@/models/bank-account'; diff --git a/src/models/bank-account.ts b/src/models/bank-account.ts index af62ccb..6e042c3 100644 --- a/src/models/bank-account.ts +++ b/src/models/bank-account.ts @@ -65,4 +65,12 @@ export default class BankAccount { } this.balance -= amount; } + + deposit(amount: number): void { + this.credit(amount); + } + + withdraw(amount: number): void { + this.debit(amount); + } } diff --git a/src/models/bank.ts b/src/models/bank.ts index 2201a80..2aa563e 100644 --- a/src/models/bank.ts +++ b/src/models/bank.ts @@ -48,4 +48,8 @@ export default class Bank { send(fromUserId: UserId, toUserId: UserId, amount: number, toBankId?: BankId): void { TransactionService.transfer(this.id, fromUserId, toUserId, amount, toBankId); } + + transfer(fromUserId: UserId, toUserId: UserId, amount: number, toBankId?: BankId): void { + this.send(fromUserId, toUserId, amount, toBankId); + } } diff --git a/src/models/user.ts b/src/models/user.ts index aa9ee1c..147868a 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -30,4 +30,8 @@ export default class User { getAccountIds(): BankAccountId[] { return [...this.accountIds]; } + + getAccounts(): BankAccountId[] { + return this.getAccountIds(); + } } diff --git a/src/services/TransactionService.ts b/src/services/TransactionService.ts index 096cc41..ac0960d 100644 --- a/src/services/TransactionService.ts +++ b/src/services/TransactionService.ts @@ -16,6 +16,7 @@ export default class TransactionService { const sourceBankId = fromBankId; const targetBankId = toBankId ?? fromBankId; + const isSelfTransfer = fromUserId === toUserId; const fromUser = GlobalRegistry.getUser(fromUserId); const toUser = GlobalRegistry.getUser(toUserId); @@ -25,12 +26,26 @@ export default class TransactionService { GlobalRegistry.getBank(targetBankId); const sourceAccount = this.pickSourceAccount(fromUser.getAccountIds(), sourceBankId, amount); - const targetAccount = this.pickTargetAccount(toUser.getAccountIds(), targetBankId); + const targetAccount = this.pickTargetAccount( + toUser.getAccountIds(), + targetBankId, + isSelfTransfer ? sourceAccount.getId() : undefined + ); sourceAccount.debit(amount); targetAccount.credit(amount); } + static send( + fromBankId: BankId, + fromUserId: UserId, + toUserId: UserId, + amount: number, + toBankId?: BankId + ): void { + this.transfer(fromBankId, fromUserId, toUserId, amount, toBankId); + } + private static pickSourceAccount( accountIds: string[], sourceBankId: BankId, @@ -49,10 +64,14 @@ export default class TransactionService { throw new Error('Insufficient funds'); } - private static pickTargetAccount(accountIds: string[], targetBankId: BankId): BankAccount { + private static pickTargetAccount( + accountIds: string[], + targetBankId: BankId, + excludedAccountId?: string + ): BankAccount { for (const accountId of accountIds) { const account = GlobalRegistry.getAccount(accountId); - if (account.getBankId() === targetBankId) { + if (account.getBankId() === targetBankId && account.getId() !== excludedAccountId) { return account; } } diff --git a/src/services/global-registry.ts b/src/services/global-registry.ts new file mode 100644 index 0000000..7f9b813 --- /dev/null +++ b/src/services/global-registry.ts @@ -0,0 +1 @@ +export { default } from '@/services/GlobalRegistry'; diff --git a/src/services/transaction-service.ts b/src/services/transaction-service.ts new file mode 100644 index 0000000..11a9879 --- /dev/null +++ b/src/services/transaction-service.ts @@ -0,0 +1 @@ +export { default } from '@/services/TransactionService'; From e934b21f8e875311bb6c38c615428f8d5a2c21e5 Mon Sep 17 00:00:00 2001 From: Rahul Bagal Date: Wed, 4 Mar 2026 18:34:28 +0530 Subject: [PATCH 5/5] Refactor transfer method to handle multiple source accounts and improve debit logic; add error handling for insufficient funds --- src/services/TransactionService.ts | 60 ++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/src/services/TransactionService.ts b/src/services/TransactionService.ts index ac0960d..ccdf93e 100644 --- a/src/services/TransactionService.ts +++ b/src/services/TransactionService.ts @@ -25,14 +25,14 @@ export default class TransactionService { GlobalRegistry.getBank(sourceBankId); GlobalRegistry.getBank(targetBankId); - const sourceAccount = this.pickSourceAccount(fromUser.getAccountIds(), sourceBankId, amount); + const sourceAccounts = this.pickSourceAccounts(fromUser.getAccountIds(), sourceBankId); const targetAccount = this.pickTargetAccount( toUser.getAccountIds(), targetBankId, - isSelfTransfer ? sourceAccount.getId() : undefined + isSelfTransfer ? sourceAccounts[0]?.getId() : undefined ); - sourceAccount.debit(amount); + this.debitAcrossAccounts(sourceAccounts, amount); targetAccount.credit(amount); } @@ -46,22 +46,60 @@ export default class TransactionService { this.transfer(fromBankId, fromUserId, toUserId, amount, toBankId); } - private static pickSourceAccount( - accountIds: string[], - sourceBankId: BankId, - amount: number - ): BankAccount { + private static pickSourceAccounts(accountIds: string[], sourceBankId: BankId): BankAccount[] { const sourceAccounts = accountIds .map((accountId) => GlobalRegistry.getAccount(accountId)) .filter((account) => account.getBankId() === sourceBankId); + if (sourceAccounts.length === 0) { + throw new Error('Insufficient funds'); + } + + return sourceAccounts; + } + + private static debitAcrossAccounts(sourceAccounts: BankAccount[], amount: number): void { + let remaining = amount; + for (const account of sourceAccounts) { - if (account.canDebit(amount)) { - return account; + if (remaining <= 0) { + break; } + + if (account.canDebit(remaining)) { + remaining = 0; + continue; + } + + const available = account.getBalance(); + if (available > 0) { + remaining -= Math.min(available, remaining); + } + } + + if (remaining > 0) { + throw new Error('Insufficient funds'); } - throw new Error('Insufficient funds'); + remaining = amount; + for (const account of sourceAccounts) { + if (remaining <= 0) { + break; + } + + if (account.canDebit(remaining)) { + account.debit(remaining); + remaining = 0; + break; + } + + const available = account.getBalance(); + if (available > 0) { + const debitAmount = Math.min(available, remaining); + account.debit(debitAmount); + remaining -= debitAmount; + } + } } private static pickTargetAccount(