diff --git a/DOMAINMODEL.md b/DOMAINMODEL.md new file mode 100644 index 000000000..a8f29f3a8 --- /dev/null +++ b/DOMAINMODEL.md @@ -0,0 +1,24 @@ + +# Domain Model – Banking Application + +| Class | Fields | Method | What it does | Return values | +|--------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------|------------------------------------------------------------------------|-------------------------------| +| **Customer** | `customerId: String`
`accounts: Map` | `createCurrentAccount()` | Creates and stores a new CurrentAccount for this customer | `CurrentAccount` | +| | | `createSavingsAccount()` | Creates and stores a new SavingsAccount for this customer | `SavingsAccount` | +| | | `getAccount(accountId)` | Retrieves an Account from the customer’s accounts by ID | `Account` | +| | | `getAccountId()` | Returns the customer’s unique ID | `String` | +| | | `addAccount(account)` | Adds an Account to the customer’s collection | `void` | +| **Account (abstract)** | `accountId: String`
`customerId: String`
`balance: int`
`transactions: List` | `deposit(amount)` | Adds funds, updates balance, and records a Transaction | `void` | +| | | `withdraw(amount)` | Subtracts funds, updates balance, records a Transaction (checks funds) | `void` | +| | | `generateStatement()` | Processes transactions into a formatted bank statement | `String` | +| | | `getBalance()` | Returns the current balance | `int` | +| | | `getAccountId()` | Returns this account’s unique ID | `String` | +| | | `addTransaction(transaction)` | Adds a Transaction to the account’s ledger | `void` | +| | | `getTransactions()` | Returns the list of all Transactions for this account | `List` | +| **CurrentAccount** | *(inherits fields from Account)* | *(inherits all methods from Account)* | Same behavior as Account | Same as Account | +| **SavingsAccount** | *(inherits fields from Account)* | *(inherits all methods from Account)* | Same behavior as Account | Same as Account | +| **Transaction** |`accountId: String`
`date: LocalDate`
`amount: float`
`type: String (CREDIT/DEBIT)`
`balanceAtTime: float` | `getDate()` | Returns the transaction date | `LocalDate` | +| | | `getAmount()` | Returns the transaction amount | `float` | +| | | `getBalanceAtTime()` | Returns the balance after this transaction | `gloat` | +| | | `getType()` | Returns whether it was a CREDIT or DEBIT | `String` | +| | | `getAccountId()` | Returns acountId | `String` | \ No newline at end of file diff --git a/src/main/java/com/booleanuk/core/Account.java b/src/main/java/com/booleanuk/core/Account.java new file mode 100644 index 000000000..877760924 --- /dev/null +++ b/src/main/java/com/booleanuk/core/Account.java @@ -0,0 +1,92 @@ +package com.booleanuk.core; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public abstract class Account { + private final String accountId; + private final String customerId; + private float balance; + private final List transactions; + + public Account(String accountId, String customerId, float balance) { + this.accountId = accountId; + this.customerId = customerId; + this.balance = balance; + this.transactions = new ArrayList<>(); + + } + + public void deposit(float amount) { + if (amount <= 0.0f) { + throw new IllegalArgumentException("deposit amount must be positive"); + } + this.balance += amount; + + this.transactions.add(new Transaction( + this.accountId, LocalDate.now(), amount,"CREDIT", this.balance + )); + } + + public void withdraw(float amount) { + if (amount <= 0.0f) { + throw new IllegalArgumentException("deposit amount must be positive"); + } + this.balance -= amount; + this.transactions.add(new Transaction( + this.accountId, + LocalDate.now(), + amount, + "DEBIT", + this.balance + )); + } + + public String generateStatement() { + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + List transactions = new ArrayList<>(this.getTransactions()); + transactions.sort((a, b) -> b.getDate().compareTo(a.getDate())); + + StringBuilder sb = new StringBuilder(); + sb.append("date || credit || debit || balance"); + + for (int i = 0; i < transactions.size(); i++) { + Transaction trans = transactions.get(i); + String date = trans.getDate().format(dtf); + String credit = "CREDIT".equals(trans.getType()) ? String.format("%.2f", trans.getAmount()) : " "; + String debit = "DEBIT".equals(trans.getType()) ? String.format("%.2f", trans.getAmount()) : " "; + String balance = String.format("%.2f", trans.getBalanceAtTime()); + + + sb.append("\n") + .append(date).append(" || ") + .append(credit).append(" || ") + .append(debit).append(" || ") + .append(balance); + } + + return sb.toString(); + + } + + + public float getBalance() { + return this.balance; + } + + public String getAccountId() { + return this.accountId; + } + + public void addTransaction(Transaction transaction) { + this.transactions.add(transaction); + } + + + public List getTransactions() { + return this.transactions; + } + +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/core/CurrentAccount.java b/src/main/java/com/booleanuk/core/CurrentAccount.java new file mode 100644 index 000000000..e32891816 --- /dev/null +++ b/src/main/java/com/booleanuk/core/CurrentAccount.java @@ -0,0 +1,8 @@ +package com.booleanuk.core; + +public class CurrentAccount extends Account { + + public CurrentAccount(String accountId, String customerId) { + super(accountId, customerId, 0.0f); + } +} diff --git a/src/main/java/com/booleanuk/core/Customer.java b/src/main/java/com/booleanuk/core/Customer.java new file mode 100644 index 000000000..f0b0fee2d --- /dev/null +++ b/src/main/java/com/booleanuk/core/Customer.java @@ -0,0 +1,43 @@ +package com.booleanuk.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class Customer { + private String customerId; + private Map accounts; + + public Customer(String customerId) { + this.customerId = customerId; + this.accounts = new HashMap<>(); + } + + public Account createCurrentAccount(){ + String accountId = UUID.randomUUID().toString(); + CurrentAccount newAccount = new CurrentAccount(accountId, this.customerId); + this.accounts.put(accountId, newAccount); + return newAccount; + } + + public Account createSavingsAccount(){ + String accountId = UUID.randomUUID().toString(); + SavingsAccount newSavingsAccount = new SavingsAccount(accountId, this.customerId); + this.accounts.put(accountId, newSavingsAccount); + return newSavingsAccount; + } + + public Account getAccount(String accountId){ + return accounts.get(accountId); + } + + public String getCustomerId() { + return this.customerId; + } + public void setCustomerId(String customerId) {} + + + public void addAccount(Account account){ + accounts.put(account.getAccountId(), account); + } +} diff --git a/src/main/java/com/booleanuk/core/Main.java b/src/main/java/com/booleanuk/core/Main.java new file mode 100644 index 000000000..b9bb3b888 --- /dev/null +++ b/src/main/java/com/booleanuk/core/Main.java @@ -0,0 +1,41 @@ +package com.booleanuk.core; + +import java.time.LocalDate; + +public class Main { + public static void main(String[] args) { + + // Create a customer + Customer customer = new Customer("C-001"); + System.out.println("Customer ID: " + customer.getCustomerId()); + // Create accounts via methods + Account current = customer.createCurrentAccount(); + Account savings = customer.createSavingsAccount(); + + System.out.println("Created Current Account ID: " + current.getAccountId()); + System.out.println("Created Savings Account ID: " + savings.getAccountId()); + + Account foundCurrent = customer.getAccount(current.getAccountId()); + Account foundSavings = customer.getAccount(savings.getAccountId()); + + System.out.println("Found Current equals created? " + (foundCurrent == current)); + System.out.println("Found Savings equals created? " + (foundSavings == savings)); + + Account external = new CurrentAccount("ACC-001", "C-001"); + customer.addAccount(external); + System.out.println("Added external account with ID: " + external.getAccountId()); + System.out.println("Lookup external: " + (customer.getAccount("ACC-001") == external)); + + Account account = new CurrentAccount("ACC-023", "C-1"); + + // Add the three example transactions + account.addTransaction(new Transaction("ACC-023", LocalDate.of(2012, 1, 10), 1000.0f, "CREDIT", 1000.0f)); + account.addTransaction(new Transaction("ACC-023", LocalDate.of(2012, 1, 13), 2000.0f, "CREDIT", 3000.0f)); + account.addTransaction(new Transaction("ACC-023", LocalDate.of(2012, 1, 14), 500.0f, "DEBIT", 2500.0f)); + + // Print the statement + System.out.println(account.generateStatement()); + } + + +} diff --git a/src/main/java/com/booleanuk/core/SavingsAccount.java b/src/main/java/com/booleanuk/core/SavingsAccount.java new file mode 100644 index 000000000..bf6390054 --- /dev/null +++ b/src/main/java/com/booleanuk/core/SavingsAccount.java @@ -0,0 +1,8 @@ +package com.booleanuk.core; + +public class SavingsAccount extends Account { + public SavingsAccount(String accountId, String customerId) { + super(accountId, customerId, 0.0f); + + } +} diff --git a/src/main/java/com/booleanuk/core/Transaction.java b/src/main/java/com/booleanuk/core/Transaction.java new file mode 100644 index 000000000..4a08cce98 --- /dev/null +++ b/src/main/java/com/booleanuk/core/Transaction.java @@ -0,0 +1,39 @@ +package com.booleanuk.core; + +import java.time.LocalDate; + +public class Transaction { + private String accountId; + private LocalDate date; + private float amount; + private String type; + private float balanceAtTime; + + public Transaction(String accountId, LocalDate date, float amount, String type, float balanceAtTime) { + this.accountId = accountId; + this.date = date; + this.amount = amount; + this.type = type; + this.balanceAtTime = balanceAtTime; + } + + public LocalDate getDate() { + return date; + } + + public float getBalanceAtTime() { + return balanceAtTime; + } + + public String getType() { + return type; + } + + public float getAmount() { + return amount; + } + + public String getAccountId() { + return accountId; + } +} diff --git a/src/test/java/com/booleanuk/core/AccountTest.java b/src/test/java/com/booleanuk/core/AccountTest.java new file mode 100644 index 000000000..9fc2e83e1 --- /dev/null +++ b/src/test/java/com/booleanuk/core/AccountTest.java @@ -0,0 +1,129 @@ +package com.booleanuk.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +public class AccountTest { + + @Test + public void testDepositForCurrentAccount() { + Account account = new CurrentAccount("ACC-001", "C-123"); + float initialBalance = account.getBalance(); + float depositAmount = 500.0f; + + account.deposit(depositAmount); + + Assertions.assertEquals(initialBalance + depositAmount, account.getBalance()); + + } + @Test + public void testDepositForSavingAccount() { + Account account = new CurrentAccount("ACC-001", "C-123"); + float initialBalance = account.getBalance(); + float depositAmount = 400.0f; + account.deposit(depositAmount); + + Assertions.assertEquals(initialBalance + depositAmount, account.getBalance()); + } + + + @Test + public void testMultipleDepositsBalance() { + Account account = new CurrentAccount("ACC-001", "C-123"); + + account.deposit(1000f); + account.deposit(2000f); + + Assertions.assertEquals(3000f, account.getBalance()); + Assertions.assertEquals(2, account.getTransactions().size()); + } + + @Test + public void testWithdrawForCurrentAccount() { + Account account = new CurrentAccount("ACC-001", "C-123"); + + account.deposit(500.0f); + float startingBalance = account.getBalance(); + + float withdrawAmount = 200.0f; + account.withdraw(withdrawAmount); + + Assertions.assertEquals(startingBalance - withdrawAmount, account.getBalance()); + + } + + @Test + public void testMultipleWithdrawals() { + Account account = new CurrentAccount("ACC-001", "C-123"); + account.deposit(1000f); + + account.withdraw(100f); + account.withdraw(200f); + + Assertions.assertEquals(700f, account.getBalance(), 0.0001f); + Assertions.assertEquals(3, account.getTransactions().size()); + + Transaction last = account.getTransactions().get(2); + Assertions.assertEquals("DEBIT", last.getType()); + Assertions.assertEquals(200f, last.getAmount(), 0.0001f); + } + + @Test + public void testAddTransactionStoresTransaction() { + Account account = new CurrentAccount("ACC-100", "C-123"); + + Transaction tx = new Transaction("ACC-100", LocalDate.now(), 500, "CREDIT", 500); + account.addTransaction(tx); + + Assertions.assertEquals(1, account.getTransactions().size()); + Assertions.assertEquals(tx, account.getTransactions().get(0)); + } + + @Test + public void testGetTransactionsInitiallyEmpty() { + Account account = new CurrentAccount("ACC-101", "C-123"); + + Assertions.assertNotNull(account.getTransactions()); + Assertions.assertTrue(account.getTransactions().isEmpty()); + + } + + @Test + public void testGenerateStatementContainsHeader() { + Account account = new CurrentAccount("ACC-102", "C-123"); + + account.addTransaction(new Transaction("ACC-102", LocalDate.of(2012, 1, 10), 1000f, "CREDIT", 1000f)); + account.addTransaction(new Transaction("ACC-102", LocalDate.of(2012, 1, 13), 2000f, "CREDIT", 3000f)); + account.addTransaction(new Transaction("ACC-102", LocalDate.of(2012, 1, 14), 500f, "DEBIT", 2500f)); + + String statement = account.generateStatement(); + + Assertions.assertTrue(statement.contains("date || credit || debit || balance")); + Assertions.assertTrue(statement.contains("14/01/2012")); + Assertions.assertTrue(statement.contains("13/01/2012")); + Assertions.assertTrue(statement.contains("10/01/2012")); + } + + @Test + public void testStatementMatchesAcceptanceCriteriaExact() { + Account account = new CurrentAccount("ACC-023", "C-1"); + + account.addTransaction(new Transaction("ACC-023", java.time.LocalDate.of(2012, 1, 10), 1000.0f, "CREDIT", 1000.0f)); + account.addTransaction(new Transaction("ACC-023", java.time.LocalDate.of(2012, 1, 13), 2000.0f, "CREDIT", 3000.0f)); + account.addTransaction(new Transaction("ACC-023", java.time.LocalDate.of(2012, 1, 14), 500.0f, "DEBIT", 2500.0f)); + + String expected = String.join("\n", + "date || credit || debit || balance", + "14/01/2012 || || 500.00 || 2500.00", + "13/01/2012 || 2000.00 || || 3000.00", + "10/01/2012 || 1000.00 || || 1000.00" + ); + + Assertions.assertEquals(expected, account.generateStatement()); + } + + + +} diff --git a/src/test/java/com/booleanuk/core/CustomerTest.java b/src/test/java/com/booleanuk/core/CustomerTest.java new file mode 100644 index 000000000..b1381d9f1 --- /dev/null +++ b/src/test/java/com/booleanuk/core/CustomerTest.java @@ -0,0 +1,56 @@ +package com.booleanuk.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class CustomerTest { + + @Test + public void testCreateCurrentAccount() { + Customer customer = new Customer("A1-Idd"); + + Account account = customer.createCurrentAccount(); + + + Assertions.assertEquals(account, customer.getAccount(account.getAccountId())); + Assertions.assertNotNull(account); + + } + + @Test + public void testCreateSavingsAccount() { + Customer customer = new Customer("B1-Idd"); + Account account = customer.createSavingsAccount(); + + Assertions.assertEquals(account, customer.getAccount(account.getAccountId())); + Assertions.assertNotNull(account); + } + + @Test + public void testAddAccount() { + Customer customer = new Customer("C-123"); + Account account = new CurrentAccount("ACC-001", "C-123"); + + customer.addAccount(account); + + Assertions.assertEquals(account, customer.getAccount("ACC-001")); + } + + @Test + public void testGetAccountReturnsNullWhenMissing() { + Customer customer = new Customer("C-404"); + Assertions.assertEquals(null, customer.getAccount("does-not-exist")); + } + + @Test + public void testCreatedAccountsHaveDifferentIds() { + Customer customer = new Customer("C-uniq"); + + Account a1 = customer.createCurrentAccount(); + Account a2 = customer.createSavingsAccount(); + + Assertions.assertEquals(false, a1.getAccountId().equals(a2.getAccountId())); + } + + +} diff --git a/src/test/java/com/booleanuk/core/TransactionTest.java b/src/test/java/com/booleanuk/core/TransactionTest.java new file mode 100644 index 000000000..1cfc6981a --- /dev/null +++ b/src/test/java/com/booleanuk/core/TransactionTest.java @@ -0,0 +1,33 @@ +package com.booleanuk.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +class TransactionTest { + + @Test + void creditTransaction_storesAllFields() { + LocalDate date = LocalDate.of(2012, 1, 13); + Transaction tx = new Transaction("ACC-1", date, 2000.0f, "CREDIT", 3000.0f); + + Assertions.assertEquals("ACC-1", tx.getAccountId()); + Assertions.assertEquals(date, tx.getDate()); + Assertions.assertEquals(2000.0f, tx.getAmount(), 0.0001f); + Assertions.assertEquals("CREDIT", tx.getType()); + Assertions.assertEquals(3000.0f, tx.getBalanceAtTime(), 0.0001f); + } + + @Test + void debitTransaction_storesAllFields() { + LocalDate date = LocalDate.of(2012, 1, 14); + Transaction tx = new Transaction("ACC-1", date, 500.0f, "DEBIT", 2500.0f); + + Assertions.assertEquals("ACC-1", tx.getAccountId()); + Assertions.assertEquals(date, tx.getDate()); + Assertions.assertEquals(500.0f, tx.getAmount(), 0.0001f); + Assertions.assertEquals("DEBIT", tx.getType()); + Assertions.assertEquals(2500.0f, tx.getBalanceAtTime(), 0.0001f); + } +}