diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Account.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Account.java new file mode 100644 index 000000000..2d5d36210 --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Account.java @@ -0,0 +1,11 @@ +package com.codedifferently.lesson17.bank; + +public interface Account { + void deposit(double account); + + void withdraw(double amount); + + double getBalance(); + + String getAccountNumber(); +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/AuditLog.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/AuditLog.java new file mode 100644 index 000000000..110161219 --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/AuditLog.java @@ -0,0 +1,40 @@ +package com.codedifferently.lesson17.bank; + +import java.util.ArrayList; +import java.util.List; + +/** Records and prints transaction logs. */ +public class AuditLog { + private final List logEntries; + + /** Creates a new, empty audit log. */ + public AuditLog() { + logEntries = new ArrayList<>(); + } + + /** + * Adds a transaction entry to the log and prints it. + * + * @param accountNumber The account number involved. + * @param message A message describing the transaction. + * @param amount The amount of money in the transaction. + * @param type The type of transaction (e.g., Deposit, Withdrawal). + */ + public void logTransaction(String accountNumber, String message, double amount, String type) { + String logEntry = + String.format( + "Account: %s | Transaction: %s | Amount: %.2f | Type: %s", + accountNumber, message, amount, type); + logEntries.add(logEntry); + System.out.println(logEntry); + } + + /** + * Returns all the transaction logs. + * + * @return A list of all log entries. + */ + public List getLogEntries() { + return logEntries; + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java index 8cbcd3cc0..7ccb84d7d 100644 --- a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java @@ -11,13 +11,27 @@ public class BankAtm { private final Map customerById = new HashMap<>(); private final Map accountByNumber = new HashMap<>(); + private final AuditLog auditLog = new AuditLog(); /** - * Adds a checking account to the bank. + * Adds a checking account to the bank. If the account is a BusinessCheckingAccount, it ensures + * that at least one of the account owners is a business. * * @param account The account to add. + * @throws IllegalArgumentException if the account is a BusinessCheckingAccount and none of its + * owners are business. */ public void addAccount(CheckingAccount account) { + if (account instanceof BusinessCheckingAccount) { + boolean hasBusinessAccount = + account.getOwners().stream().anyMatch(owner -> owner.isBusiness()); + + if (!hasBusinessAccount) { + throw new IllegalArgumentException( + "At least one owning account must be a business account."); + } + } + accountByNumber.put(account.getAccountNumber(), account); account .getOwners() @@ -48,6 +62,8 @@ public Set findAccountsByCustomerId(UUID customerId) { public void depositFunds(String accountNumber, double amount) { CheckingAccount account = getAccountOrThrow(accountNumber); account.deposit(amount); + auditLog.logTransaction( + "Deposited to account: " + accountNumber, accountNumber, amount, "Deposit"); } /** @@ -59,6 +75,11 @@ public void depositFunds(String accountNumber, double amount) { public void depositFunds(String accountNumber, Check check) { CheckingAccount account = getAccountOrThrow(accountNumber); check.depositFunds(account); + auditLog.logTransaction( + "Deposited check to account: " + accountNumber, + accountNumber, + check.getAmount(), + "Check Deposit"); } /** @@ -70,6 +91,8 @@ public void depositFunds(String accountNumber, Check check) { public void withdrawFunds(String accountNumber, double amount) { CheckingAccount account = getAccountOrThrow(accountNumber); account.withdraw(amount); + auditLog.logTransaction( + "Withdrew from account: " + accountNumber, accountNumber, amount, "Withdrawal"); } /** diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BusinessCheckingAccount.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BusinessCheckingAccount.java new file mode 100644 index 000000000..31de4f1bd --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BusinessCheckingAccount.java @@ -0,0 +1,26 @@ +package com.codedifferently.lesson17.bank; + +import java.util.Set; + +public class BusinessCheckingAccount extends CheckingAccount { + private final String businessName; + + /** + * Creates a new business checking account. + * + * @param accountNumber The account number. + * @param owners The owners of the account. + * @param businessName The name of the business. + * @param initialBalance The initial balance of the account. + * @throws IllegalArgumentException if no business owner is provided. + */ + public BusinessCheckingAccount( + String accountNumber, Set owners, String businessName, double initialBalance) { + super(accountNumber, owners, initialBalance); + this.businessName = businessName; + } + + public String getBusinessName() { + return businessName; + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Check.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Check.java index 061fa4a5c..926c0ffe2 100644 --- a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Check.java +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Check.java @@ -54,6 +54,10 @@ public void depositFunds(CheckingAccount toAccount) { voidCheck(); } + public double getAmount() { + return amount; + } + @Override public int hashCode() { return checkNumber.hashCode(); diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Customer.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Customer.java index af0847134..70b7b8592 100644 --- a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Customer.java +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Customer.java @@ -10,6 +10,7 @@ public class Customer { private final UUID id; private final String name; private final Set accounts = new HashSet<>(); + private final boolean isBusiness; /** * Creates a new customer. @@ -17,9 +18,10 @@ public class Customer { * @param id The ID of the customer. * @param name The name of the customer. */ - public Customer(UUID id, String name) { + public Customer(UUID id, String name, boolean isBusiness) { this.id = id; this.name = name; + this.isBusiness = isBusiness; } /** @@ -40,6 +42,10 @@ public String getName() { return name; } + public boolean isBusiness() { + return isBusiness; + } + /** * Adds a checking account to the customer. * diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/MoneyOrder.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/MoneyOrder.java new file mode 100644 index 000000000..496e4cb38 --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/MoneyOrder.java @@ -0,0 +1,45 @@ +package com.codedifferently.lesson17.bank; + +/** Represents a Money Order transaction. */ +public class MoneyOrder { + private final CheckingAccount sourceAccount; + private final double amount; + private AuditLog auditLog; + + /** + * Creates a new money order transaction by withdrawing the specified amount from the source + * account. The transaction is logged for auditing purposes. + * + * @param sourceAccount The account from which funds will be withdrawn. + * @param amount The amount to withdraw and transfer in the money order. + * @param auditLog The audit log used to record the transaction details. + */ + public MoneyOrder(CheckingAccount sourceAccount, double amount, AuditLog auditLog) { + this.sourceAccount = sourceAccount; + this.amount = amount; + this.auditLog = auditLog; + + sourceAccount.withdraw(amount); + + auditLog.logTransaction( + sourceAccount.getAccountNumber(), "MoneyOrder created", -amount, "MoneyOrder"); + } + + /** + * Gets the source account from which the money was withdrawn. + * + * @return The source checking account. + */ + public CheckingAccount getSourceAccount() { + return sourceAccount; + } + + /** + * Gets the amount of money involved in the Money Order transaction. + * + * @return The amount of the money order. + */ + public double getAmount() { + return amount; + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/SavingsAccount.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/SavingsAccount.java new file mode 100644 index 000000000..4fb3e5fe6 --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/SavingsAccount.java @@ -0,0 +1,50 @@ +package com.codedifferently.lesson17.bank; + +import com.codedifferently.lesson17.bank.exceptions.CheckNotAllowedException; +import java.util.Set; + +/** Represents a savings account. */ +public class SavingsAccount extends CheckingAccount { + + /** + * Creates a new savings account. + * + * @param accountNumber The account number. + * @param owners The owners of the account. + * @param initialBalance The initial balance of the account. If it's negative, it will be set to + * zero. + */ + public SavingsAccount(String accountNumber, Set owners, double initialBalance) { + super(accountNumber, owners, Math.max(initialBalance, 0)); + } + + /** + * Deposits funds into the savings account. + * + * @param amount The amount to deposit. + */ + @Override + public void deposit(double amount) { + super.deposit(amount); + } + + /** + * Withdraws funds from the savings account. + * + * @param amount The amount to withdraw. + */ + @Override + public void withdraw(double amount) { + super.withdraw(amount); + } + + /** + * Throws an exception because savings accounts cannot write checks. + * + * @param amount The amount to write a check for. + * @throws CheckNotAllowedException if attempting to write a check. + */ + public void writeCheck(double amount) { + throw new CheckNotAllowedException("Savings account cannot write checks"); + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/CheckNotAllowedException.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/CheckNotAllowedException.java new file mode 100644 index 000000000..9ae06ad8b --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/CheckNotAllowedException.java @@ -0,0 +1,7 @@ +package com.codedifferently.lesson17.bank.exceptions; + +public class CheckNotAllowedException extends RuntimeException { + public CheckNotAllowedException(String message) { + super(message); + } +} diff --git a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/BankAtmTest.java b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/BankAtmTest.java index fa4a913a2..ca84ebdda 100644 --- a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/BankAtmTest.java +++ b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/BankAtmTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.codedifferently.lesson17.bank.exceptions.AccountNotFoundException; import com.codedifferently.lesson17.bank.exceptions.CheckVoidedException; @@ -21,8 +22,8 @@ class BankAtmTest { @BeforeEach void setUp() { classUnderTest = new BankAtm(); - customer1 = new Customer(UUID.randomUUID(), "John Doe"); - customer2 = new Customer(UUID.randomUUID(), "Jane Smith"); + customer1 = new Customer(UUID.randomUUID(), "John Doe", true); + customer2 = new Customer(UUID.randomUUID(), "Jane Smith", true); account1 = new CheckingAccount("123456789", Set.of(customer1), 100.0); account2 = new CheckingAccount("987654321", Set.of(customer1, customer2), 200.0); customer1.addAccount(account1); @@ -35,7 +36,7 @@ void setUp() { @Test void testAddAccount() { // Arrange - Customer customer3 = new Customer(UUID.randomUUID(), "Alice Johnson"); + Customer customer3 = new Customer(UUID.randomUUID(), "Alice Johnson", true); CheckingAccount account3 = new CheckingAccount("555555555", Set.of(customer3), 300.0); customer3.addAccount(account3); @@ -47,6 +48,25 @@ void testAddAccount() { assertThat(accounts).containsOnly(account3); } + @Test + void testAddBusinessAccountWithNoBusinessOwner() { + // Arrange + Customer regularCustomer = new Customer(UUID.randomUUID(), "Bob's Bakery", false); + CheckingAccount businessAccount = + new BusinessCheckingAccount("666666666", Set.of(regularCustomer), "name", 500.0); + + // Act & Assert + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> { + classUnderTest.addAccount(businessAccount); + }); + + assertThat(thrown) + .hasMessageContaining("At least one owning account must be a business account."); + } + @Test void testFindAccountsByCustomerId() { // Act @@ -107,4 +127,16 @@ void testWithdrawFunds_AccountNotFound() { .isThrownBy(() -> classUnderTest.withdrawFunds(nonExistingAccountNumber, 50.0)) .withMessage("Account not found"); } + + @Test + void testGetBusinessName() { + // Arrange + Customer businessOwner = new Customer(UUID.randomUUID(), "Business Owner", true); + Set owners = Set.of(businessOwner); + BusinessCheckingAccount businessAccount = + new BusinessCheckingAccount("123456789", owners, "Business Inc.", 1000.0); + + // Act & Assert + assertThat(businessAccount.getBusinessName()); + } } diff --git a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckingAccountTest.java b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckingAccountTest.java index f155d8e5b..2ca05f63c 100644 --- a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckingAccountTest.java +++ b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckingAccountTest.java @@ -20,8 +20,8 @@ class CheckingAccountTest { @BeforeEach void setUp() { owners = new HashSet<>(); - owners.add(new Customer(UUID.randomUUID(), "John Doe")); - owners.add(new Customer(UUID.randomUUID(), "Jane Smith")); + owners.add(new Customer(UUID.randomUUID(), "John Doe", false)); + owners.add(new Customer(UUID.randomUUID(), "Jane Smith", false)); classUnderTest = new CheckingAccount("123456789", owners, 100.0); } diff --git a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/MoneyOrderTest.java b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/MoneyOrderTest.java new file mode 100644 index 000000000..085a84645 --- /dev/null +++ b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/MoneyOrderTest.java @@ -0,0 +1,43 @@ +package com.codedifferently.lesson17.bank; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class MoneyOrderTest { + + private CheckingAccount sourceAccount; + private AuditLog auditLog; + + @BeforeEach + public void setup() { + // Arrange + sourceAccount = new CheckingAccount("12345678", null, 1000.0); + auditLog = new AuditLog(); + } + + @Test + public void testMoneyOrderWithdrawal() { + // Act + MoneyOrder moneyOrder = new MoneyOrder(sourceAccount, 500.0, auditLog); + + // Assert + assertEquals(500.0, sourceAccount.getBalance(), "Balance after money order should be 500"); + + // Assert + assertNotNull(moneyOrder, "MoneyOrder should not be null"); + assertEquals(1, auditLog.getLogEntries().size(), "Audit log should contain 1 entry"); + String logEntry = auditLog.getLogEntries().get(0); + + assertTrue( + logEntry.contains("Account: 12345678"), "Log should contain the correct account number"); + assertTrue( + logEntry.contains("MoneyOrder created"), "Log should describe the MoneyOrder creation"); + assertTrue(logEntry.contains("-500.00"), "Log should reflect the correct withdrawal amount"); + assertTrue( + logEntry.contains("MoneyOrder"), "Log should specify the transaction type as 'MoneyOrder'"); + } +} diff --git a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/SavingsAccountTest.java b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/SavingsAccountTest.java new file mode 100644 index 000000000..caccb8d65 --- /dev/null +++ b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/SavingsAccountTest.java @@ -0,0 +1,49 @@ +package com.codedifferently.lesson17.bank; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.codedifferently.lesson17.bank.exceptions.CheckNotAllowedException; +import org.junit.jupiter.api.Test; + +public class SavingsAccountTest { + @Test + void testDeposit() { + // Arrange + SavingsAccount account = new SavingsAccount("12345678", null, 5.00); + account.deposit(200); + + // Assert + assertEquals(205.00, account.getBalance()); + } + + @Test + void testWithdraw() { + // Arrange + SavingsAccount account = new SavingsAccount("12345678", null, 0.00); + account.deposit(1000); + + // Act + account.withdraw(500); + + // Assert + assertEquals(500, account.getBalance()); + } + + @Test + void testWriteCheck() { + // Arrange + SavingsAccount account = new SavingsAccount("12345678", null, 1000.00); + + // Act & Assert + CheckNotAllowedException thrown = + assertThrows( + CheckNotAllowedException.class, + () -> { + account.writeCheck(100); + }); + + // Assert + assertEquals("Savings account cannot write checks", thrown.getMessage()); + } +}