diff --git a/src/main/java/com/abc/Account.java b/src/main/java/com/abc/Account.java index 099691e0..593a3520 100644 --- a/src/main/java/com/abc/Account.java +++ b/src/main/java/com/abc/Account.java @@ -1,73 +1,144 @@ package com.abc; import java.util.ArrayList; +import java.util.Date; import java.util.List; +import com.abc.Transaction.TransactionType; +import com.abc.Utils.DateProvider; +import com.abc.exception.AccountOperationException; + public class Account { - public static final int CHECKING = 0; - public static final int SAVINGS = 1; - public static final int MAXI_SAVINGS = 2; + /*enum added to remove hard coding of the account types with numbers 0,1,2 + */ + enum AccountType{ + CHECKING,SAVINGS,MAXI_SAVINGS; + } + + + private final AccountType accountType; + private List transactions; + private Date lastPaymentDate; + + public void setLastPaymentDate(Date lastPaymentDate) { + this.lastPaymentDate = lastPaymentDate; + } + + public Date getLastPaymentDate() { + return lastPaymentDate; + } - private final int accountType; - public List transactions; + public List getTransactions() { + return transactions; + } - public Account(int accountType) { + public Account(AccountType accountType) { this.accountType = accountType; this.transactions = new ArrayList(); + //Setting up the last payment date to account opening day. + this.lastPaymentDate=DateProvider.getInstance().now(); } - public void deposit(double amount) { + public void deposit(double amount) throws AccountOperationException { if (amount <= 0) { - throw new IllegalArgumentException("amount must be greater than zero"); + throw new AccountOperationException("amount must be greater than zero"); } else { - transactions.add(new Transaction(amount)); + transactions.add(new Transaction(amount,TransactionType.DEPOSIT)); } } -public void withdraw(double amount) { + public void withdraw(double amount) throws AccountOperationException { + if (amount <= 0) { - throw new IllegalArgumentException("amount must be greater than zero"); - } else { - transactions.add(new Transaction(-amount)); + throw new AccountOperationException("amount must be greater than zero"); + } + else if(amount>sumTransactions()){ + throw new AccountOperationException("amount exceeds available balance"); + } + else { + transactions.add(new Transaction(-amount,TransactionType.WITHDRAW)); } } - public double interestEarned() { + /*This method calculates the daily interest including weekends + * Formula - Principal Balance X (Annual Interest Rate* / Year Count**) X Number of Days Since Last Payment + */ + public double dailyInterestEarned() { double amount = sumTransactions(); + int noOfDaysSinceLastPayement=(int)((DateProvider.getInstance().now().getTime() - getLastPaymentDate().getTime()) + / (1000 * 60 * 60 * 24) ); + switch(accountType){ case SAVINGS: if (amount <= 1000) - return amount * 0.001; + return amount * (0.001/365) * noOfDaysSinceLastPayement; else - return 1 + (amount-1000) * 0.002; -// case SUPER_SAVINGS: -// if (amount <= 4000) -// return 20; + return amount * (0.002/365) * noOfDaysSinceLastPayement; case MAXI_SAVINGS: - if (amount <= 1000) - return amount * 0.02; - if (amount <= 2000) - return 20 + (amount-1000) * 0.05; - return 70 + (amount-2000) * 0.1; + Transaction lastWithdrawTransaction=getLastWithdrawTransaction(); + if(lastWithdrawTransaction==null) + return amount * (0.05/365) * noOfDaysSinceLastPayement; + + int lastTransactionDay=(int)((DateProvider.getInstance().now().getTime() - lastWithdrawTransaction.getTransactionDate().getTime()) + / (1000 * 60 * 60 * 24) ); + + if(lastTransactionDay>10) + return amount * (0.05/365) * noOfDaysSinceLastPayement; + else + return amount * (0.001/365) * noOfDaysSinceLastPayement; + default: - return amount * 0.001; + return amount * (0.001/365) * noOfDaysSinceLastPayement; } } - public double sumTransactions() { - return checkIfTransactionsExist(true); - } + private Transaction getLastWithdrawTransaction() { + Transaction lastTransaction= null; + + int lastIndex=transactions.size()-1; + lastTransaction = transactions.get(lastIndex); + while(lastIndex>=0){ + if(lastTransaction.getTransactionType()==TransactionType.WITHDRAW) + { + return lastTransaction; + } + else + { + lastIndex--; + } + } + return null; + + } - private double checkIfTransactionsExist(boolean checkAll) { - double amount = 0.0; + /*Removing checkIfTransactionsExist separate method call , the call not doing anything additional + * other than just forwarding the call. + * Removing parameter checkAll , its not used*/ + public double sumTransactions() { + double amount = 0.0; for (Transaction t: transactions) - amount += t.amount; + amount += t.getAmount(); return amount; } - public int getAccountType() { + + public AccountType getAccountType() { return accountType; } + /* + * This method is added , which will be called during the interest payments for the accounts. + * This will set the lastPaymentDate. + * lastPayment date will be used to calculate the daily interest rates + */ + public boolean payIntrest(double amount,Date date){ + transactions.add(new Transaction(amount,TransactionType.DEPOSIT)); + if(date==null) + setLastPaymentDate(DateProvider.getInstance().now()); + else + setLastPaymentDate(date); + return true; + } + } diff --git a/src/main/java/com/abc/AccountOperationException.java b/src/main/java/com/abc/AccountOperationException.java new file mode 100644 index 00000000..31655b3e --- /dev/null +++ b/src/main/java/com/abc/AccountOperationException.java @@ -0,0 +1,24 @@ +package com.abc.exception; +public class AccountOperationException extends Exception { + + public AccountOperationException() { + } + + public AccountOperationException(String message) { + super(message); + } + + public AccountOperationException(Throwable cause) { + super(cause); + } + + public AccountOperationException(String message, Throwable cause) { + super(message, cause); + } + + public AccountOperationException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/src/main/java/com/abc/Bank.java b/src/main/java/com/abc/Bank.java index 5dd535bd..a025329e 100644 --- a/src/main/java/com/abc/Bank.java +++ b/src/main/java/com/abc/Bank.java @@ -17,30 +17,30 @@ public void addCustomer(Customer customer) { public String customerSummary() { String summary = "Customer Summary"; for (Customer c : customers) - summary += "\n - " + c.getName() + " (" + format(c.getNumberOfAccounts(), "account") + ")"; + summary += "\n - " +c.toString(); return summary; } - //Make sure correct plural of word is created based on the number passed in: - //If number passed in is 1 just return the word otherwise add an 's' at the end - private String format(int number, String word) { - return number + " " + (number == 1 ? word : word + "s"); - } + public double totalInterestPaid() { double total = 0; for(Customer c: customers) total += c.totalInterestEarned(); + + total=Math.floor(total * 100) / 100; return total; } + /*customers = null;<- This statement will cause null pointer, hence removed*/ public String getFirstCustomer() { try { - customers = null; return customers.get(0).getName(); } catch (Exception e){ e.printStackTrace(); return "Error"; } } + + } diff --git a/src/main/java/com/abc/Customer.java b/src/main/java/com/abc/Customer.java index 31571685..7e286c12 100644 --- a/src/main/java/com/abc/Customer.java +++ b/src/main/java/com/abc/Customer.java @@ -3,6 +3,9 @@ import java.util.ArrayList; import java.util.List; +import com.abc.Utils.Utils; +import com.abc.exception.AccountOperationException; + import static java.lang.Math.abs; public class Customer { @@ -30,7 +33,7 @@ public int getNumberOfAccounts() { public double totalInterestEarned() { double total = 0; for (Account a : accounts) - total += a.interestEarned(); + total += a.dailyInterestEarned(); return total; } @@ -46,27 +49,28 @@ public String getStatement() { return statement; } + /*This logic has been modified to make use of enum constants*/ private String statementForAccount(Account a) { String s = ""; //Translate to pretty account type switch(a.getAccountType()){ - case Account.CHECKING: + case CHECKING: s += "Checking Account\n"; break; - case Account.SAVINGS: + case SAVINGS: s += "Savings Account\n"; break; - case Account.MAXI_SAVINGS: + case MAXI_SAVINGS: s += "Maxi Savings Account\n"; break; } //Now total up all the transactions double total = 0.0; - for (Transaction t : a.transactions) { - s += " " + (t.amount < 0 ? "withdrawal" : "deposit") + " " + toDollars(t.amount) + "\n"; - total += t.amount; + for (Transaction t : a.getTransactions()) { + s += " " + (t.getAmount() < 0 ? "withdrawal" : "deposit") + " " + toDollars(t.getAmount()) + "\n"; + total += t.getAmount(); } s += "Total " + toDollars(total); return s; @@ -75,4 +79,26 @@ private String statementForAccount(Account a) { private String toDollars(double d){ return String.format("$%,.2f", abs(d)); } + + public boolean transfer(Account source,Account destination,double amount) throws AccountOperationException{ + + if(accounts.contains(source) && accounts.contains(destination)){ + synchronized(source){ + synchronized (destination) { + source.withdraw(amount); + destination.deposit(amount); + } + } + return true; + } + else{ + throw new AccountOperationException("Sorry, money transfer is not allowed between these accounts."); + } + } + + + public String toString(){ + return getName() + " (" + Utils.format(getNumberOfAccounts(), "account") + ")"; + + } } diff --git a/src/main/java/com/abc/DateProvider.java b/src/main/java/com/abc/DateProvider.java index 035ee90b..502e2320 100644 --- a/src/main/java/com/abc/DateProvider.java +++ b/src/main/java/com/abc/DateProvider.java @@ -1,4 +1,4 @@ -package com.abc; +package com.abc.Utils; import java.util.Calendar; import java.util.Date; diff --git a/src/main/java/com/abc/Transaction.java b/src/main/java/com/abc/Transaction.java index c1f7c67e..5c149f40 100644 --- a/src/main/java/com/abc/Transaction.java +++ b/src/main/java/com/abc/Transaction.java @@ -1,16 +1,39 @@ package com.abc; -import java.util.Calendar; import java.util.Date; +import com.abc.Utils.DateProvider; + public class Transaction { - public final double amount; + private final double amount; + + + + enum TransactionType{ + DEPOSIT,WITHDRAW + } private Date transactionDate; + private TransactionType transactionType; - public Transaction(double amount) { + public Date getTransactionDate() { + return transactionDate; + } + + public double getAmount() { + return amount; + } + + public TransactionType getTransactionType() { + return transactionType; + } + + public Transaction(double amount,TransactionType type) { this.amount = amount; this.transactionDate = DateProvider.getInstance().now(); + this.transactionType=type; } + + } diff --git a/src/main/java/com/abc/Utils.java b/src/main/java/com/abc/Utils.java new file mode 100644 index 00000000..1e975517 --- /dev/null +++ b/src/main/java/com/abc/Utils.java @@ -0,0 +1,12 @@ +package com.abc.Utils; + +public class Utils { + public static final double DOUBLE_DELTA = 1e-5; + + //Make sure correct plural of word is created based on the number passed in: + //If number passed in is 1 just return the word otherwise add an 's' at the end + public static String format(int number, String word) { + return number + " " + (number == 1 ? word : word + "s"); + } + } + diff --git a/src/test/java/com/abc/AccountTest.java b/src/test/java/com/abc/AccountTest.java new file mode 100644 index 00000000..6ef61e41 --- /dev/null +++ b/src/test/java/com/abc/AccountTest.java @@ -0,0 +1,42 @@ +package com.abc; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.abc.Utils.Utils; +import com.abc.exception.AccountOperationException; + +public class AccountTest { + + @Test(expected=AccountOperationException.class) + public void testDeposit() throws AccountOperationException{ + Account checkingAccount = new Account(Account.AccountType.CHECKING); + checkingAccount.deposit(-100.0); + } + + @Test(expected=AccountOperationException.class) + public void testWithdraw() throws AccountOperationException{ + Account checkingAccount = new Account(Account.AccountType.CHECKING); + checkingAccount.withdraw(-100.0); + } + + + @Test(expected=AccountOperationException.class) + public void testWithdrawWithLowBalance() throws AccountOperationException{ + Account checkingAccount = new Account(Account.AccountType.CHECKING); + checkingAccount.deposit(100.0); + checkingAccount.withdraw(-200.0); + } + + @Test + public void testInterestEarned() throws AccountOperationException{ + Account savingsAccount = new Account(Account.AccountType.SAVINGS); + savingsAccount.deposit(100.0); + assertEquals(0.00000,savingsAccount.dailyInterestEarned(),Utils.DOUBLE_DELTA); + } + + + + +} diff --git a/src/test/java/com/abc/BankTest.java b/src/test/java/com/abc/BankTest.java index f8a82896..9a4b5bd4 100644 --- a/src/test/java/com/abc/BankTest.java +++ b/src/test/java/com/abc/BankTest.java @@ -2,53 +2,79 @@ import org.junit.Test; +import com.abc.Utils.Utils; +import com.abc.exception.AccountOperationException; + import static org.junit.Assert.assertEquals; -public class BankTest { - private static final double DOUBLE_DELTA = 1e-15; +import java.util.Calendar; +public class BankTest { + @Test public void customerSummary() { Bank bank = new Bank(); Customer john = new Customer("John"); - john.openAccount(new Account(Account.CHECKING)); + john.openAccount(new Account(Account.AccountType.CHECKING)); bank.addCustomer(john); assertEquals("Customer Summary\n - John (1 account)", bank.customerSummary()); } @Test - public void checkingAccount() { + public void checkingAccount() throws AccountOperationException { Bank bank = new Bank(); - Account checkingAccount = new Account(Account.CHECKING); + Account checkingAccount = new Account(Account.AccountType.CHECKING); Customer bill = new Customer("Bill").openAccount(checkingAccount); bank.addCustomer(bill); checkingAccount.deposit(100.0); - assertEquals(0.1, bank.totalInterestPaid(), DOUBLE_DELTA); + assertEquals(0.0, bank.totalInterestPaid(), Utils.DOUBLE_DELTA); } @Test - public void savings_account() { + public void savingsAccount() throws AccountOperationException { Bank bank = new Bank(); - Account checkingAccount = new Account(Account.SAVINGS); + Account checkingAccount = new Account(Account.AccountType.SAVINGS); bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)); + //Setting up the payment date as 04/2016, so that 4 months interest will be calculated checkingAccount.deposit(1500.0); - - assertEquals(2.0, bank.totalInterestPaid(), DOUBLE_DELTA); + Calendar c=Calendar.getInstance(); + c.set(2016, 4, 06); + checkingAccount.payIntrest(200,c.getTime()); + + assertEquals(1.14, bank.totalInterestPaid(), Utils.DOUBLE_DELTA); } @Test - public void maxi_savings_account() { + public void maxiSavingsAccount() throws AccountOperationException { Bank bank = new Bank(); - Account checkingAccount = new Account(Account.MAXI_SAVINGS); + Account checkingAccount = new Account(Account.AccountType.MAXI_SAVINGS); bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)); + //Setting up the payment date as 04/2016, so that 4 months interest will be calculated checkingAccount.deposit(3000.0); + Calendar c=Calendar.getInstance(); + c.set(2016, 4, 06); + checkingAccount.payIntrest(200,c.getTime()); + + assertEquals(53.91, bank.totalInterestPaid(), Utils.DOUBLE_DELTA); + } + + @Test + public void testFirstCustomer() throws AccountOperationException { + Bank bank = new Bank(); + + Account checkingAccount = new Account(Account.AccountType.MAXI_SAVINGS); + bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)); - assertEquals(170.0, bank.totalInterestPaid(), DOUBLE_DELTA); + Account savingAccount = new Account(Account.AccountType.MAXI_SAVINGS); + bank.addCustomer(new Customer("Oscar").openAccount(savingAccount)); + + assertEquals( bank.getFirstCustomer(), "Bill"); } + } diff --git a/src/test/java/com/abc/CustomerTest.java b/src/test/java/com/abc/CustomerTest.java index b8df9498..b67cb99b 100644 --- a/src/test/java/com/abc/CustomerTest.java +++ b/src/test/java/com/abc/CustomerTest.java @@ -3,15 +3,17 @@ import org.junit.Ignore; import org.junit.Test; +import com.abc.exception.AccountOperationException; + import static org.junit.Assert.assertEquals; public class CustomerTest { @Test //Test customer statement generation - public void testApp(){ + public void testApp() throws AccountOperationException{ - Account checkingAccount = new Account(Account.CHECKING); - Account savingsAccount = new Account(Account.SAVINGS); + Account checkingAccount = new Account(Account.AccountType.CHECKING); + Account savingsAccount = new Account(Account.AccountType.SAVINGS); Customer henry = new Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount); @@ -35,23 +37,54 @@ public void testApp(){ @Test public void testOneAccount(){ - Customer oscar = new Customer("Oscar").openAccount(new Account(Account.SAVINGS)); + Customer oscar = new Customer("Oscar").openAccount(new Account(Account.AccountType.SAVINGS)); assertEquals(1, oscar.getNumberOfAccounts()); } @Test - public void testTwoAccount(){ + public void testTwoAccounts(){ Customer oscar = new Customer("Oscar") - .openAccount(new Account(Account.SAVINGS)); - oscar.openAccount(new Account(Account.CHECKING)); + .openAccount(new Account(Account.AccountType.SAVINGS)); + oscar.openAccount(new Account(Account.AccountType.CHECKING)); assertEquals(2, oscar.getNumberOfAccounts()); } @Ignore public void testThreeAcounts() { Customer oscar = new Customer("Oscar") - .openAccount(new Account(Account.SAVINGS)); - oscar.openAccount(new Account(Account.CHECKING)); + .openAccount(new Account(Account.AccountType.SAVINGS)); + oscar.openAccount(new Account(Account.AccountType.CHECKING)); assertEquals(3, oscar.getNumberOfAccounts()); } + + @Test + public void testAccountTransferSuccess() throws AccountOperationException{ + Customer oscar = new Customer("Oscar"); + Account checkingAccount = new Account(Account.AccountType.CHECKING); + Account savingsAccount = new Account(Account.AccountType.SAVINGS); + + oscar.openAccount(checkingAccount); + oscar.openAccount(savingsAccount); + + checkingAccount.deposit(100.0); + savingsAccount.deposit(4000.0); + savingsAccount.withdraw(200.0); + assertEquals(true, oscar.transfer(checkingAccount,savingsAccount,100.0)); + } + + + @Test(expected=AccountOperationException.class) + public void testAccountTransferFailure() throws AccountOperationException{ + Customer oscar = new Customer("Oscar"); + Customer henry = new Customer("Henry"); + + Account checkingAccountForOscar = new Account(Account.AccountType.CHECKING); + Account savingsAccountForHenry = new Account(Account.AccountType.SAVINGS); + + oscar.openAccount(checkingAccountForOscar); + henry.openAccount(savingsAccountForHenry); + + oscar.transfer(checkingAccountForOscar,savingsAccountForHenry,100.0); + + } } diff --git a/src/test/java/com/abc/TransactionTest.java b/src/test/java/com/abc/TransactionTest.java index 28983234..8307dde8 100644 --- a/src/test/java/com/abc/TransactionTest.java +++ b/src/test/java/com/abc/TransactionTest.java @@ -2,12 +2,27 @@ import org.junit.Test; +import com.abc.Transaction.TransactionType; + import static org.junit.Assert.assertTrue; public class TransactionTest { @Test public void transaction() { - Transaction t = new Transaction(5); + Transaction t = new Transaction(5,TransactionType.DEPOSIT); assertTrue(t instanceof Transaction); } + + @Test + public void transactionDeposit() { + Transaction t = new Transaction(5,TransactionType.DEPOSIT); + assertTrue(t.getTransactionType()==TransactionType.DEPOSIT); + } + + @Test + public void transactionWithdraw() { + Transaction t = new Transaction(5,TransactionType.WITHDRAW); + assertTrue(t.getTransactionType()==TransactionType.WITHDRAW); + } + }