diff --git a/build.gradle b/build.gradle index 44e2f2ef8..de446a9e1 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ dependencies { } testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.vintage:junit-vintage-engine") + implementation 'com.twilio.sdk:twilio:8.31.1' } test { diff --git a/src/main/java/com/booleanuk/core/dto/CreateAccountRequestDto.java b/src/main/java/com/booleanuk/core/dto/CreateAccountRequestDto.java new file mode 100644 index 000000000..2e3f8065e --- /dev/null +++ b/src/main/java/com/booleanuk/core/dto/CreateAccountRequestDto.java @@ -0,0 +1,6 @@ +package com.booleanuk.core.dto; + +import com.booleanuk.core.enums.AccountType; + +public record CreateAccountRequestDto(int userId, AccountType accountType) { +} diff --git a/src/main/java/com/booleanuk/core/dto/CreateAccountResponseDto.java b/src/main/java/com/booleanuk/core/dto/CreateAccountResponseDto.java new file mode 100644 index 000000000..583c2fb82 --- /dev/null +++ b/src/main/java/com/booleanuk/core/dto/CreateAccountResponseDto.java @@ -0,0 +1,4 @@ +package com.booleanuk.core.dto; + +public record CreateAccountResponseDto(boolean success, String message, int accountId, int userId, String accountType) { +} diff --git a/src/main/java/com/booleanuk/core/dto/ReviewOverdraftResponseDto.java b/src/main/java/com/booleanuk/core/dto/ReviewOverdraftResponseDto.java new file mode 100644 index 000000000..e2a4edd98 --- /dev/null +++ b/src/main/java/com/booleanuk/core/dto/ReviewOverdraftResponseDto.java @@ -0,0 +1,6 @@ +package com.booleanuk.core.dto; + +import java.math.BigDecimal; + +public record ReviewOverdraftResponseDto(boolean approved, String message, int accountId, BigDecimal amount) { +} diff --git a/src/main/java/com/booleanuk/core/dto/TransactionRequestDto.java b/src/main/java/com/booleanuk/core/dto/TransactionRequestDto.java new file mode 100644 index 000000000..0a097e75d --- /dev/null +++ b/src/main/java/com/booleanuk/core/dto/TransactionRequestDto.java @@ -0,0 +1,10 @@ +package com.booleanuk.core.dto; + +import com.booleanuk.core.enums.AccountType; +import com.booleanuk.core.enums.TransactionType; + +import java.math.BigDecimal; +import java.time.LocalDate; + +public record TransactionRequestDto(int accountId, BigDecimal amount, TransactionType transactionType, LocalDate date) { +} diff --git a/src/main/java/com/booleanuk/core/dto/TransactionResponseDto.java b/src/main/java/com/booleanuk/core/dto/TransactionResponseDto.java new file mode 100644 index 000000000..c8b7dae8d --- /dev/null +++ b/src/main/java/com/booleanuk/core/dto/TransactionResponseDto.java @@ -0,0 +1,7 @@ +package com.booleanuk.core.dto; + +import java.math.BigDecimal; +import java.time.LocalDate; + +public record TransactionResponseDto(boolean success, String message, int accountId, String accountType, String transactionType, BigDecimal amount, LocalDate date, boolean overdraftRequest) { +} diff --git a/src/main/java/com/booleanuk/core/enums/AccountType.java b/src/main/java/com/booleanuk/core/enums/AccountType.java new file mode 100644 index 000000000..c06447620 --- /dev/null +++ b/src/main/java/com/booleanuk/core/enums/AccountType.java @@ -0,0 +1,6 @@ +package com.booleanuk.core.enums; + +public enum AccountType { + CURRENT, + SAVINGS +} diff --git a/src/main/java/com/booleanuk/core/enums/TransactionType.java b/src/main/java/com/booleanuk/core/enums/TransactionType.java new file mode 100644 index 000000000..fc8c85e04 --- /dev/null +++ b/src/main/java/com/booleanuk/core/enums/TransactionType.java @@ -0,0 +1,7 @@ +package com.booleanuk.core.enums; + +public enum TransactionType { + DEPOSIT, + WITHDRAW, + OVERDRAFT, +} diff --git a/src/main/java/com/booleanuk/core/model/BankAccount.java b/src/main/java/com/booleanuk/core/model/BankAccount.java new file mode 100644 index 000000000..2f8b7b870 --- /dev/null +++ b/src/main/java/com/booleanuk/core/model/BankAccount.java @@ -0,0 +1,65 @@ +package com.booleanuk.core.model; + +import com.booleanuk.core.enums.AccountType; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +public class BankAccount { + private int id; + private AccountType accountType; + private BigDecimal balance; // not used since started working on extension + private int userId; + List transactions; + + // Constructors + public BankAccount(int id, AccountType accountType, BigDecimal balance, int userId) { + this.id = id; + this.accountType = accountType; + this.balance = balance; + this.userId = userId; + transactions = new ArrayList<>(); + } + + // Getters & setters + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public AccountType getAccountType() { + return accountType; + } + + public void setAccountType(AccountType accountType) { + this.accountType = accountType; + } + + public BigDecimal getBalance() { + return balance; + } + + public void setBalance(BigDecimal balance) { + this.balance = balance; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public List getTransactions() { + return transactions; + } + + public void setTransactions(List transactions) { + this.transactions = transactions; + } +} diff --git a/src/main/java/com/booleanuk/core/model/Customer.java b/src/main/java/com/booleanuk/core/model/Customer.java new file mode 100644 index 000000000..95099fd8e --- /dev/null +++ b/src/main/java/com/booleanuk/core/model/Customer.java @@ -0,0 +1,19 @@ +package com.booleanuk.core.model; + +public class Customer { + private int id; + + // Constructors + public Customer(int id) { + this.id = id; + } + + // Getters & setters + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/src/main/java/com/booleanuk/core/model/OverdraftRequest.java b/src/main/java/com/booleanuk/core/model/OverdraftRequest.java new file mode 100644 index 000000000..bb27cf056 --- /dev/null +++ b/src/main/java/com/booleanuk/core/model/OverdraftRequest.java @@ -0,0 +1,39 @@ +package com.booleanuk.core.model; + +import java.math.BigDecimal; + +public class OverdraftRequest { + private int overdraftId; + private int accountId; + private BigDecimal amount; + + public OverdraftRequest(int overdraftId, int accountId, BigDecimal amount) { + this.overdraftId = overdraftId; + this.accountId = accountId; + this.amount = amount; + } + + public int getOverdraftId() { + return overdraftId; + } + + public void setOverdraftId(int overdraftId) { + this.overdraftId = overdraftId; + } + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int accountId) { + this.accountId = accountId; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } +} diff --git a/src/main/java/com/booleanuk/core/model/Transaction.java b/src/main/java/com/booleanuk/core/model/Transaction.java new file mode 100644 index 000000000..4535cba6c --- /dev/null +++ b/src/main/java/com/booleanuk/core/model/Transaction.java @@ -0,0 +1,54 @@ +package com.booleanuk.core.model; + +import com.booleanuk.core.enums.TransactionType; + +import java.math.BigDecimal; +import java.time.LocalDate; + +public class Transaction { + private int id; + private BigDecimal amount; + private TransactionType transactionType; + private LocalDate date; + + // Constructors + public Transaction(int id, BigDecimal amount, TransactionType transactionType, LocalDate date) { + this.id = id; + this.amount = amount; + this.transactionType = transactionType; + this.date = date; + } + + // Getters and setters + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public TransactionType getTransactionType() { + return transactionType; + } + + public void setTransactionType(TransactionType transactionType) { + this.transactionType = transactionType; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public LocalDate getDate() { + return date; + } + + public void setDateTime(LocalDate date) { + this.date = date; + } +} diff --git a/src/main/java/com/booleanuk/core/repository/BankAccountRepository.java b/src/main/java/com/booleanuk/core/repository/BankAccountRepository.java new file mode 100644 index 000000000..aeba7914d --- /dev/null +++ b/src/main/java/com/booleanuk/core/repository/BankAccountRepository.java @@ -0,0 +1,55 @@ +package com.booleanuk.core.repository; + +import com.booleanuk.core.model.BankAccount; +import com.booleanuk.core.model.OverdraftRequest; +import com.booleanuk.core.model.Transaction; + +import java.util.ArrayList; +import java.util.List; + +public class BankAccountRepository implements IBankAccountRepository { + List accounts; + List overdraftRequests; + + // Constructors + public BankAccountRepository() { + this.accounts = new ArrayList<>(); + this.overdraftRequests = new ArrayList<>(); + } + + // Methods + @Override + public void createAccount(BankAccount bankAccount) { + + } + + @Override + public void saveTransaction(int accountId, Transaction transaction) { + + } + + @Override + public BankAccount findBankAccountById(int accountId) { + return null; + } + + @Override + public List getAccounts() { + return accounts; + } + + @Override + public List getOverdraftRequests() { + return List.of(); + } + + @Override + public OverdraftRequest getOverdraftRequestById(int overdraftId) { + return null; + } + + @Override + public void addOverdraftRequest(OverdraftRequest overdraftRequest) { + + } +} diff --git a/src/main/java/com/booleanuk/core/repository/BankAccountRepositoryStub.java b/src/main/java/com/booleanuk/core/repository/BankAccountRepositoryStub.java new file mode 100644 index 000000000..093159244 --- /dev/null +++ b/src/main/java/com/booleanuk/core/repository/BankAccountRepositoryStub.java @@ -0,0 +1,74 @@ +package com.booleanuk.core.repository; + +import com.booleanuk.core.model.BankAccount; +import com.booleanuk.core.model.OverdraftRequest; +import com.booleanuk.core.model.Transaction; + +import java.util.ArrayList; +import java.util.List; + +public class BankAccountRepositoryStub implements IBankAccountRepository { + List accounts; + List overdraftRequests; // Extensions + + // Constructors + public BankAccountRepositoryStub() { + this.accounts = new ArrayList<>(); + this.overdraftRequests = new ArrayList<>(); // Extensions + } + + // Methods + @Override + public void createAccount(BankAccount bankAccount) { + accounts.add(bankAccount); + } + + @Override + public void saveTransaction(int accountId, Transaction transaction) { + for (BankAccount account : accounts) { + if (account.getId() == accountId) { + account.getTransactions().add(transaction); + return; + } + } + throw new IllegalArgumentException("Account with ID " + accountId + " not found."); + } + + @Override + public BankAccount findBankAccountById(int accountId) { + for (BankAccount account : accounts) { + if (account.getId() == accountId) { + return account; + } + } + throw new IllegalArgumentException("Account with ID " + accountId + " not found."); + } + + @Override + public List getAccounts() { + return accounts; + } + + // Extensions + @Override + public List getOverdraftRequests() { + return overdraftRequests; + } + + @Override + public OverdraftRequest getOverdraftRequestById(int overdraftId) { + for (OverdraftRequest request : overdraftRequests) { + if (request.getOverdraftId() == overdraftId) { + return request; + } + } + throw new IllegalArgumentException("Overdraft request with ID " + overdraftId + " not found."); + } + + @Override + public void addOverdraftRequest(OverdraftRequest overdraftRequest) { + overdraftRequests.add(overdraftRequest); + } + + +} diff --git a/src/main/java/com/booleanuk/core/repository/IBankAccountRepository.java b/src/main/java/com/booleanuk/core/repository/IBankAccountRepository.java new file mode 100644 index 000000000..173d84f67 --- /dev/null +++ b/src/main/java/com/booleanuk/core/repository/IBankAccountRepository.java @@ -0,0 +1,17 @@ +package com.booleanuk.core.repository; + +import com.booleanuk.core.model.BankAccount; +import com.booleanuk.core.model.OverdraftRequest; +import com.booleanuk.core.model.Transaction; + +import java.util.List; + +public interface IBankAccountRepository { + void createAccount(BankAccount bankAccount); + void saveTransaction(int accountId, Transaction transaction); + BankAccount findBankAccountById(int accountId); + List getAccounts(); + List getOverdraftRequests(); + OverdraftRequest getOverdraftRequestById(int overdraftId); + void addOverdraftRequest(OverdraftRequest overdraftRequest); +} diff --git a/src/main/java/com/booleanuk/core/service/BankAccountService.java b/src/main/java/com/booleanuk/core/service/BankAccountService.java new file mode 100644 index 000000000..9f9c1c7b6 --- /dev/null +++ b/src/main/java/com/booleanuk/core/service/BankAccountService.java @@ -0,0 +1,232 @@ +package com.booleanuk.core.service; + +import com.twilio.Twilio; +import com.twilio.rest.api.v2010.account.Message; + +import com.booleanuk.core.dto.*; +import com.booleanuk.core.enums.TransactionType; +import com.booleanuk.core.model.BankAccount; +import com.booleanuk.core.model.OverdraftRequest; +import com.booleanuk.core.model.Transaction; +import com.booleanuk.core.repository.IBankAccountRepository; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +public class BankAccountService { + private IBankAccountRepository bankAccountRepository = null; + + // Twilio + private static final String ACCOUNT_SID = ""; // removed because push not allowed + private static final String AUTH_TOKEN = ""; // removed because push not allowed + private static final String FROM_NUMBER = ""; // removed because push not allowed + + static { + Twilio.init(ACCOUNT_SID, AUTH_TOKEN); + } + + // Constructors + public BankAccountService(IBankAccountRepository bankAccountRepository) { + this.bankAccountRepository = bankAccountRepository; + } + + // Methods + public CreateAccountResponseDto createAccount(CreateAccountRequestDto createAccountRequestDto) { + int accountId = bankAccountRepository.getAccounts().size() + 1; + BankAccount account = new BankAccount( + accountId, + createAccountRequestDto.accountType(), + BigDecimal.valueOf(0), + createAccountRequestDto.userId() + ); + + bankAccountRepository.createAccount(account); + return new CreateAccountResponseDto( + true, + "Account created", + accountId, + createAccountRequestDto.userId(), + createAccountRequestDto.accountType().toString() + ); + } + + public TransactionResponseDto deposit(TransactionRequestDto transactionRequestDto) { + int transactionId = bankAccountRepository + .findBankAccountById(transactionRequestDto.accountId()) + .getTransactions().size() + 1; + + // Not used since started working on extension: + // BankAccount account = bankAccountRepository.findBankAccountById(transactionRequestDto.accountId()); + // account.setBalance(account.getBalance().add(transactionRequestDto.amount())); + + bankAccountRepository.saveTransaction(transactionRequestDto.accountId(), + new Transaction( + transactionId, + transactionRequestDto.amount(), + transactionRequestDto.transactionType(), + transactionRequestDto.date() + ) + ); + + return new TransactionResponseDto( + true, + "Deposit successful", + transactionRequestDto.accountId(), + bankAccountRepository.findBankAccountById(transactionRequestDto.accountId()).getAccountType().toString(), + transactionRequestDto.transactionType().toString(), + transactionRequestDto.amount(), + transactionRequestDto.date(), + false + ); + } + + public TransactionResponseDto withdraw(TransactionRequestDto transactionRequestDto) { + // BigDecimal currentBalance = bankAccountRepository.findBankAccountById(transactionRequestDto.accountId()).getBalance(); + + BigDecimal currentBalance = BigDecimal.valueOf(0); + BankAccount account = bankAccountRepository.findBankAccountById(transactionRequestDto.accountId()); + List transactions = account.getTransactions(); + for (Transaction t : transactions) { + if (t.getTransactionType().equals(TransactionType.DEPOSIT)) { + currentBalance = currentBalance.add(t.getAmount()); + } else if (t.getTransactionType().equals(TransactionType.WITHDRAW)) { + currentBalance = currentBalance.subtract(t.getAmount()); + } + } + + if (currentBalance.doubleValue() < transactionRequestDto.amount().doubleValue()) { + + int overdraftId = bankAccountRepository.getOverdraftRequests().size() + 1; + + bankAccountRepository.addOverdraftRequest( + new OverdraftRequest( + overdraftId, + transactionRequestDto.accountId(), + transactionRequestDto.amount() + ) + ); + + return new TransactionResponseDto( + false, + "Insufficient funds", + transactionRequestDto.accountId(), + bankAccountRepository.findBankAccountById(transactionRequestDto.accountId()).getAccountType().toString(), + transactionRequestDto.transactionType().toString(), + BigDecimal.valueOf(0), + transactionRequestDto.date(), + true + ); + } else { + int transactionId = bankAccountRepository + .findBankAccountById(transactionRequestDto.accountId()) + .getTransactions().size() + 1; + + // Not used since started working on extension: + // BankAccount account = bankAccountRepository.findBankAccountById(transactionRequestDto.accountId()); + // account.setBalance(currentBalance.subtract(transactionRequestDto.amount())); + + bankAccountRepository.saveTransaction(account.getId(), + new Transaction( + transactionId, + transactionRequestDto.amount(), + transactionRequestDto.transactionType(), + transactionRequestDto.date() + ) + ); + + return new TransactionResponseDto( + true, + "Withdraw successful", + transactionRequestDto.accountId(), + account.getAccountType().toString(), + transactionRequestDto.transactionType().toString().toUpperCase(Locale.ROOT), + transactionRequestDto.amount(), + transactionRequestDto.date(), + false + ); + } + } + + public String generateBankAccountStatement(int accountId) { + BankAccount account = bankAccountRepository.findBankAccountById(accountId); + List transactions = new ArrayList<>(account.getTransactions()); + + // Sort transactions by date ascending for balance calculation + transactions.sort(Comparator.comparing(Transaction::getDate)); + + List balances = new ArrayList<>(); + BigDecimal runningBalance = BigDecimal.ZERO; + for (Transaction t : transactions) { + if (t.getTransactionType().toString().equalsIgnoreCase("DEPOSIT")) { + runningBalance = runningBalance.add(t.getAmount()); + } else { + runningBalance = runningBalance.subtract(t.getAmount()); + } + balances.add(runningBalance); + } + + // Prepare lines in descending order + StringBuilder sb = new StringBuilder(); + sb.append("date || credit || debit || balance\n"); + for (int i = transactions.size() - 1; i >= 0; i--) { + Transaction t = transactions.get(i); + String date = String.format("%02d/%02d/%d", t.getDate().getDayOfMonth(), t.getDate().getMonthValue(), t.getDate().getYear()); + String credit = t.getTransactionType().toString().equalsIgnoreCase("DEPOSIT") ? String.format(Locale.US, "%.2f", t.getAmount()) : ""; + String debit = t.getTransactionType().toString().equalsIgnoreCase("WITHDRAW") ? String.format(Locale.US, "%.2f", t.getAmount()) : ""; + String balance = String.format(Locale.US, "%.2f", balances.get(i)); + sb.append(String.format("%-10s || %-7s || %-6s || %s\n", date, credit, debit, balance)); + } + + return sb.toString(); + } + + public ReviewOverdraftResponseDto reviewOverdraftRequest(int overdraftId, boolean approve) { + OverdraftRequest overdraftRequest = bankAccountRepository.getOverdraftRequestById(overdraftId); + BankAccount account = bankAccountRepository.findBankAccountById(overdraftRequest.getAccountId()); + + if (approve) { + // Approve the overdraft request + int transactionId = account.getTransactions().size() + 1; + bankAccountRepository.saveTransaction(account.getId(), + new Transaction( + transactionId, + overdraftRequest.getAmount(), + TransactionType.OVERDRAFT, + java.time.LocalDate.now() + ) + ); + return new ReviewOverdraftResponseDto(true, "Overdraft approved", overdraftRequest.getAccountId(), overdraftRequest.getAmount()); + } else { + // Reject the overdraft request + return new ReviewOverdraftResponseDto(false, "Overdraft rejected", overdraftRequest.getAccountId(), BigDecimal.ZERO); + } + } + + public void sendBankStatementSms(int accountId, String toPhoneNumber) { + String statement = generateBankAccountStatement(accountId); + + Message.creator( + new com.twilio.type.PhoneNumber(toPhoneNumber), + new com.twilio.type.PhoneNumber(FROM_NUMBER), + statement + ).create(); + } +} + + +/* + + { + res.writeHead(200, { 'Content-Type': 'text/html' }); + const html = ` + + Test Page +

Hello from Node.js HTTP Server!

+ + `; + res.end(html); +}); + +const port = 8080; +server.listen(port, () => { + console.log(`Serving custom HTML at http://localhost:${port}`); +}); \ No newline at end of file diff --git a/src/test/java/com/booleanuk/core/service/BankAccountServiceTest.java b/src/test/java/com/booleanuk/core/service/BankAccountServiceTest.java new file mode 100644 index 000000000..2e92168f6 --- /dev/null +++ b/src/test/java/com/booleanuk/core/service/BankAccountServiceTest.java @@ -0,0 +1,305 @@ +package com.booleanuk.core.service; + +import com.booleanuk.core.dto.CreateAccountRequestDto; +import com.booleanuk.core.dto.CreateAccountResponseDto; +import com.booleanuk.core.dto.TransactionRequestDto; +import com.booleanuk.core.dto.TransactionResponseDto; +import com.booleanuk.core.enums.AccountType; +import com.booleanuk.core.enums.TransactionType; +import com.booleanuk.core.repository.BankAccountRepositoryStub; +import com.booleanuk.core.repository.IBankAccountRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +public class BankAccountServiceTest { + private IBankAccountRepository repository; + private BankAccountService service; + + @BeforeEach + void setUp() { + repository = new BankAccountRepositoryStub(); + service = new BankAccountService(repository); + } + + @Test + void customerCreatesNewCurrentAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.CURRENT); + CreateAccountResponseDto result = service.createAccount(request); + assertNotNull(result); + assertTrue(result.success()); + assertEquals("Account created", result.message()); + assertEquals(1, result.userId()); + assertEquals("CURRENT", result.accountType()); + } + + @Test + void customerCreatesNewSavingsAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.SAVINGS); + CreateAccountResponseDto result = service.createAccount(request); + assertNotNull(result); + assertTrue(result.success()); + assertEquals("Account created", result.message()); + assertEquals(1, result.userId()); + assertEquals("SAVINGS", result.accountType()); + } + + @Test + void customerDepositsMoneyIntoCurrentAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.CURRENT); + CreateAccountResponseDto account = service.createAccount(request); + TransactionRequestDto deposit = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(100), TransactionType.DEPOSIT, LocalDate.now()); + TransactionResponseDto result = service.deposit(deposit); + assertTrue(result.success()); + assertEquals("Deposit successful", result.message()); + assertEquals("CURRENT", result.accountType()); + assertEquals("DEPOSIT", result.transactionType()); + assertEquals(BigDecimal.valueOf(100), result.amount()); + } + + @Test + void customerDepositsMoneyIntoSavingsAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.SAVINGS); + CreateAccountResponseDto account = service.createAccount(request); + TransactionRequestDto deposit = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(100), TransactionType.DEPOSIT, LocalDate.now()); + TransactionResponseDto result = service.deposit(deposit); + assertTrue(result.success()); + assertEquals("Deposit successful", result.message()); + assertEquals("SAVINGS", result.accountType()); + assertEquals("DEPOSIT", result.transactionType()); + assertEquals(BigDecimal.valueOf(100), result.amount()); + } + + @Test + void customerTriesToWithdrawMoreFundsThanTheyHaveFromCurrent() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.CURRENT); + CreateAccountResponseDto account = service.createAccount(request); + TransactionRequestDto withdraw = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(1000), TransactionType.WITHDRAW, LocalDate.now()); + TransactionResponseDto result = service.withdraw(withdraw); + assertFalse(result.success()); + assertEquals("Insufficient funds", result.message()); + assertEquals("CURRENT", result.accountType()); + assertEquals("WITHDRAW", result.transactionType()); + assertEquals(BigDecimal.valueOf(0), result.amount()); + } + + @Test + void customerTriesToWithdrawMoreFundsThanTheyHaveFromSavings() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.SAVINGS); + CreateAccountResponseDto account = service.createAccount(request); + TransactionRequestDto withdraw = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(1000), TransactionType.WITHDRAW, LocalDate.now()); + TransactionResponseDto result = service.withdraw(withdraw); + assertFalse(result.success()); + assertEquals("Insufficient funds", result.message()); + assertEquals("SAVINGS", result.accountType()); + assertEquals("WITHDRAW", result.transactionType()); + assertEquals(BigDecimal.valueOf(0), result.amount()); + } + + @Test + void customerWithdrawsFromCurrentAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.CURRENT); + CreateAccountResponseDto account = service.createAccount(request); + service.deposit(new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(100), TransactionType.DEPOSIT, LocalDate.now())); + TransactionRequestDto withdraw = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(50), TransactionType.WITHDRAW, LocalDate.now()); + TransactionResponseDto result = service.withdraw(withdraw); + assertTrue(result.success()); + assertEquals("Withdraw successful", result.message()); + assertEquals("CURRENT", result.accountType()); + assertEquals("WITHDRAW", result.transactionType()); + assertEquals(BigDecimal.valueOf(50), result.amount()); + } + + @Test + void customerWithdrawsFromSavingsAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.SAVINGS); + CreateAccountResponseDto account = service.createAccount(request); + service.deposit(new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(100), TransactionType.DEPOSIT, LocalDate.now())); + TransactionRequestDto withdraw = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(75), TransactionType.WITHDRAW, LocalDate.now()); + TransactionResponseDto result = service.withdraw(withdraw); + assertTrue(result.success()); + assertEquals("Withdraw successful", result.message()); + assertEquals("SAVINGS", result.accountType()); + assertEquals("WITHDRAW", result.transactionType()); + assertEquals(BigDecimal.valueOf(75), result.amount()); + } + + @Test + void customerGeneratesBankStatementFromCurrentAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.CURRENT); + CreateAccountResponseDto account = service.createAccount(request); + service.deposit( + new TransactionRequestDto( + 1, + BigDecimal.valueOf(1000), + TransactionType.DEPOSIT, + LocalDate.of(2012, 1, 10) + ) + ); + service.deposit( + new TransactionRequestDto( + 1, + BigDecimal.valueOf(2000), + TransactionType.DEPOSIT, + LocalDate.of(2012, 1, 13) + ) + ); + service.deposit( + new TransactionRequestDto( + 1, + BigDecimal.valueOf(500), + TransactionType.WITHDRAW, + LocalDate.of(2012, 1, 14) + ) + ); + + String statement = service.generateBankAccountStatement(account.accountId()); + String expected = """ + 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 + """; + assertEquals(expected, statement); + } + + @Test + void customerGeneratesBankStatementFromSavingsAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.SAVINGS); + CreateAccountResponseDto account = service.createAccount(request); + service.deposit( + new TransactionRequestDto( + 1, + BigDecimal.valueOf(1000), + TransactionType.DEPOSIT, + LocalDate.of(2012, 1, 10) + ) + ); + service.deposit( + new TransactionRequestDto( + 1, + BigDecimal.valueOf(2000), + TransactionType.DEPOSIT, + LocalDate.of(2012, 1, 13) + ) + ); + service.deposit( + new TransactionRequestDto( + 1, + BigDecimal.valueOf(500), + TransactionType.WITHDRAW, + LocalDate.of(2012, 1, 14) + ) + ); + + String statement = service.generateBankAccountStatement(account.accountId()); + String expected = """ + 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 + """; + assertEquals(expected, statement); + } + + + // Extensions + @Test + void customerRequestsOverdraftOnCurrentAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.CURRENT); + CreateAccountResponseDto account = service.createAccount(request); + TransactionRequestDto overdraftRequest = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(500), TransactionType.WITHDRAW, LocalDate.now()); + TransactionResponseDto result = service.withdraw(overdraftRequest); + assertFalse(result.success()); + assertEquals("Insufficient funds", result.message()); + assertEquals("CURRENT", result.accountType()); + assertEquals("WITHDRAW", result.transactionType()); + assertEquals(BigDecimal.valueOf(0), result.amount()); + assertTrue(result.overdraftRequest()); + } + + @Test + void customerRequestsOverdraftOnSavingsAccount() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.SAVINGS); + CreateAccountResponseDto account = service.createAccount(request); + TransactionRequestDto overdraftRequest = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(500), TransactionType.WITHDRAW, LocalDate.now()); + TransactionResponseDto result = service.withdraw(overdraftRequest); + assertFalse(result.success()); + assertEquals("Insufficient funds", result.message()); + assertEquals("SAVINGS", result.accountType()); + assertEquals("WITHDRAW", result.transactionType()); + assertEquals(BigDecimal.valueOf(0), result.amount()); + assertTrue(result.overdraftRequest()); + } + + @Test + void managerApprovesOverdraftRequest() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.CURRENT); + CreateAccountResponseDto account = service.createAccount(request); + TransactionRequestDto overdraftRequest = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(500), TransactionType.WITHDRAW, LocalDate.now()); + TransactionResponseDto result = service.withdraw(overdraftRequest); + assertFalse(result.success()); + assertEquals("Insufficient funds", result.message()); + assertTrue(result.overdraftRequest()); + + // Approve the overdraft request + var reviewResult = service.reviewOverdraftRequest(1, true); + assertTrue(reviewResult.approved()); + assertEquals("Overdraft approved", reviewResult.message()); + } + + @Test + void managerRejectsOverdraftRequest() { + CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.CURRENT); + CreateAccountResponseDto account = service.createAccount(request); + TransactionRequestDto overdraftRequest = new TransactionRequestDto(account.accountId(), BigDecimal.valueOf(500), TransactionType.WITHDRAW, LocalDate.now()); + TransactionResponseDto result = service.withdraw(overdraftRequest); + assertFalse(result.success()); + assertEquals("Insufficient funds", result.message()); + assertTrue(result.overdraftRequest()); + + // Reject the overdraft request + var reviewResult = service.reviewOverdraftRequest(1, false); + assertFalse(reviewResult.approved()); + assertEquals("Overdraft rejected", reviewResult.message()); + } + +// @Test +// void sendSms() { +// CreateAccountRequestDto request = new CreateAccountRequestDto(1, AccountType.SAVINGS); +// CreateAccountResponseDto account = service.createAccount(request); +// service.deposit( +// new TransactionRequestDto( +// 1, +// BigDecimal.valueOf(1000), +// TransactionType.DEPOSIT, +// LocalDate.of(2012, 1, 10) +// ) +// ); +// service.deposit( +// new TransactionRequestDto( +// 1, +// BigDecimal.valueOf(2000), +// TransactionType.DEPOSIT, +// LocalDate.of(2012, 1, 13) +// ) +// ); +// service.deposit( +// new TransactionRequestDto( +// 1, +// BigDecimal.valueOf(500), +// TransactionType.WITHDRAW, +// LocalDate.of(2012, 1, 14) +// ) +// ); +// +// // Sending sms worked! +// service.sendBankStatementSms(account.accountId(), "+46704320616"); +// assertTrue(true); +// } + +}