diff --git a/src/main/scala/com/abc/Account.scala b/src/main/scala/com/abc/Account.scala index fa23d85..359a6f5 100644 --- a/src/main/scala/com/abc/Account.scala +++ b/src/main/scala/com/abc/Account.scala @@ -1,44 +1,74 @@ package com.abc import scala.collection.mutable.ListBuffer +import java.util.Calendar +import java.util.Date -object Account { - final val CHECKING: Int = 0 - final val SAVINGS: Int = 1 - final val MAXI_SAVINGS: Int = 2 -} - -class Account(val accountType: Int, var transactions: ListBuffer[Transaction] = ListBuffer()) { +abstract class Account(val transactions: ListBuffer[Transaction] = ListBuffer()) { def deposit(amount: Double) { if (amount <= 0) throw new IllegalArgumentException("amount must be greater than zero") else - transactions += Transaction(amount) + transactions += new RealTransaction(amount) } def withdraw(amount: Double) { if (amount <= 0) throw new IllegalArgumentException("amount must be greater than zero") else - transactions += Transaction(-amount) + transactions += new RealTransaction(-amount) } def interestEarned: Double = { - val amount: Double = sumTransactions() - accountType match { - case Account.SAVINGS => - if (amount <= 1000) amount * 0.001 - else 1 + (amount - 1000) * 0.002 - case Account.MAXI_SAVINGS => - if (amount <= 1000) return amount * 0.02 - if (amount <= 2000) return 20 + (amount - 1000) * 0.05 - 70 + (amount - 2000) * 0.1 - case _ => - amount * 0.001 - } + val amount: Double = sumTransactions + calculateInterest(amount) + } + + def addTransaction(t: Transaction): Unit = transactions += t + + def sumTransactions: Double = transactions.map(_.amount).sum + + protected def calculateInterest(amount: Double): Double + +} + +case class CheckingAccount(override val transactions: ListBuffer[Transaction] = ListBuffer()) extends Account(transactions) { + def calculateInterest(amount: Double) = amount * 0.001 + override def toString = "Checking Account" +} + +case class SavingsAccount(override val transactions: ListBuffer[Transaction] = ListBuffer()) extends Account(transactions) { + def calculateInterest(amount: Double) = { + if (amount <= 1000) amount * 0.001 + else 1 + (amount - 1000) * 0.002 } + override def toString = "Savings Account" +} - def sumTransactions(checkAllTransactions: Boolean = true): Double = transactions.map(_.amount).sum +case class MaxiSavingsAccount(override val transactions: ListBuffer[Transaction] = ListBuffer()) extends Account(transactions) { + def calculateInterest(amount: Double) = { + /** + if (amount <= 1000) amount * 0.02 + else if (amount <= 2000) 20 + (amount - 1000) * 0.05 + else 70 + (amount - 2000) * 0.1 + */ + if ( hasWithdrawalInPastTenDays ) + amount * 0.001 + else + amount * 0.05 + } + override def toString = "Maxi Savings Account" + + def hasWithdrawalInPastTenDays: Boolean = { + val now = DateProvider.now + + val cal = Calendar.getInstance() + cal.setTime(now) + cal.add(Calendar.DATE, -10) + val tenDaysAgo = cal.getTime + + transactions.exists( t => t.transactionDate.after(tenDaysAgo) && t.amount < 0.0 ) + } } \ No newline at end of file diff --git a/src/main/scala/com/abc/Bank.scala b/src/main/scala/com/abc/Bank.scala index 4667c0e..a534644 100644 --- a/src/main/scala/com/abc/Bank.scala +++ b/src/main/scala/com/abc/Bank.scala @@ -23,22 +23,15 @@ class Bank { def totalInterestPaid: Double = { var total: Double = 0 for (c <- customers) total += c.totalInterestEarned - return total + total } def getFirstCustomer: String = { - try { - customers = null - customers(0).name - } - catch { - case e: Exception => { - e.printStackTrace - return "Error" - } - } + if(customers.nonEmpty ) + customers.head.name + else + "Error" } - } diff --git a/src/main/scala/com/abc/Customer.scala b/src/main/scala/com/abc/Customer.scala index 6c517b0..fdeed76 100644 --- a/src/main/scala/com/abc/Customer.scala +++ b/src/main/scala/com/abc/Customer.scala @@ -20,7 +20,7 @@ class Customer(val name: String, var accounts: ListBuffer[Account] = ListBuffer( //JIRA-123 Change by Joe Bloggs 29/7/1988 start var statement: String = null //reset statement to null here //JIRA-123 Change by Joe Bloggs 29/7/1988 end - val totalAcrossAllAccounts = accounts.map(_.sumTransactions()).sum + val totalAcrossAllAccounts = accounts.map(_.sumTransactions).sum statement = f"Statement for $name\n" + accounts.map(statementForAccount).mkString("\n", "\n\n", "\n") + s"\nTotal In All Accounts ${toDollars(totalAcrossAllAccounts)}" @@ -28,14 +28,8 @@ class Customer(val name: String, var accounts: ListBuffer[Account] = ListBuffer( } private def statementForAccount(a: Account): String = { - val accountType = a.accountType match { - case Account.CHECKING => - "Checking Account\n" - case Account.SAVINGS => - "Savings Account\n" - case Account.MAXI_SAVINGS => - "Maxi Savings Account\n" - } + val accountType = a.toString + "\n" + val transactionSummary = a.transactions.map(t => withdrawalOrDepositText(t) + " " + toDollars(t.amount.abs)) .mkString(" ", "\n ", "\n") val totalSummary = s"Total ${toDollars(a.transactions.map(_.amount).sum)}" @@ -50,5 +44,16 @@ class Customer(val name: String, var accounts: ListBuffer[Account] = ListBuffer( } private def toDollars(number: Double): String = f"$$$number%.2f" + + def transfer(fromAccount: Account, toAccount:Account, amount: Double) = { + if( ! accounts.contains(fromAccount) || ! accounts.contains(toAccount) ) { + throw new IllegalArgumentException("cannot tranfers between accounts not owned") + } + if( amount <= 0.0 ) { + throw new IllegalArgumentException("transfer amount must be greater than zero") + } + fromAccount.withdraw(amount) + toAccount.deposit(amount) + } } diff --git a/src/main/scala/com/abc/DateProvider.scala b/src/main/scala/com/abc/DateProvider.scala index c43f6e1..11792bc 100644 --- a/src/main/scala/com/abc/DateProvider.scala +++ b/src/main/scala/com/abc/DateProvider.scala @@ -4,17 +4,10 @@ import java.util.Calendar import java.util.Date object DateProvider { - def getInstance: DateProvider = { - if (instance == null) instance = new DateProvider - instance - } - - private var instance: DateProvider = null -} -class DateProvider { def now: Date = { - return Calendar.getInstance.getTime + Calendar.getInstance.getTime } } + diff --git a/src/main/scala/com/abc/Transaction.scala b/src/main/scala/com/abc/Transaction.scala index ba6cec8..8421915 100644 --- a/src/main/scala/com/abc/Transaction.scala +++ b/src/main/scala/com/abc/Transaction.scala @@ -1,6 +1,15 @@ package com.abc -case class Transaction(var amount: Double) { - val transactionDate = DateProvider.getInstance.now +import java.util.Date + +trait Transaction { + def amount: Double + def transactionDate: Date +} + +case class RealTransaction(var amount: Double) extends Transaction { + def transactionDate = DateProvider.now } +case class FakeTransaction(var amount: Double, var transactionDate: Date) extends Transaction + diff --git a/src/test/scala/com/abc/BankTest.scala b/src/test/scala/com/abc/BankTest.scala index 13b3067..f835481 100644 --- a/src/test/scala/com/abc/BankTest.scala +++ b/src/test/scala/com/abc/BankTest.scala @@ -1,19 +1,36 @@ package com.abc import org.scalatest.{Matchers, FlatSpec} +import java.util.Date +import java.util.Calendar class BankTest extends FlatSpec with Matchers { "Bank" should "customer summary" in { val bank: Bank = new Bank - var john: Customer = new Customer("John").openAccount(new Account(Account.CHECKING)) + val john: Customer = new Customer("John").openAccount(new CheckingAccount()) bank.addCustomer(john) bank.customerSummary should be("Customer Summary\n - John (1 account)") + john.openAccount(new SavingsAccount()) + val jane: Customer = new Customer("Jane").openAccount(new MaxiSavingsAccount()) + bank.addCustomer(jane) + bank.customerSummary should be("Customer Summary\n - John (2 accounts)\n - Jane (1 account)") + } + + it should "first customer" in { + val bank: Bank = new Bank + bank.getFirstCustomer should be("Error") + + val john: Customer = new Customer("John").openAccount(new CheckingAccount()) + bank.addCustomer(john) + val jane: Customer = new Customer("Jane").openAccount(new MaxiSavingsAccount()) + bank.addCustomer(jane) + bank.getFirstCustomer should be("John") } it should "checking account" in { val bank: Bank = new Bank - val checkingAccount: Account = new Account(Account.CHECKING) + val checkingAccount: Account = new CheckingAccount() val bill: Customer = new Customer("Bill").openAccount(checkingAccount) bank.addCustomer(bill) checkingAccount.deposit(100.0) @@ -22,18 +39,55 @@ class BankTest extends FlatSpec with Matchers { it should "savings account" in { val bank: Bank = new Bank - val checkingAccount: Account = new Account(Account.SAVINGS) - bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)) - checkingAccount.deposit(1500.0) + val savingsAccount: Account = new SavingsAccount() + bank.addCustomer(new Customer("Bill").openAccount(savingsAccount)) + savingsAccount.deposit(1500.0) bank.totalInterestPaid should be(2.0) + savingsAccount.withdraw(700) + bank.totalInterestPaid should be(0.8) } it should "maxi savings account" in { val bank: Bank = new Bank - val checkingAccount: Account = new Account(Account.MAXI_SAVINGS) - bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)) + val maxiSavingsAccount: Account = new MaxiSavingsAccount() + bank.addCustomer(new Customer("Bill").openAccount(maxiSavingsAccount)) + /** checkingAccount.deposit(3000.0) bank.totalInterestPaid should be(170.0) + checkingAccount.withdraw(1200.0) + bank.totalInterestPaid should be(60.0) + checkingAccount.withdraw(1200.0) + bank.totalInterestPaid should be(12.0) + */ + val now:Date = DateProvider.now + maxiSavingsAccount.addTransaction(new FakeTransaction(1000, now)) + bank.totalInterestPaid should be(50) + + val cal = Calendar.getInstance() + cal.setTime(now) + cal.add(Calendar.DATE, -11) + val elevenDaysAgo = cal.getTime + + maxiSavingsAccount.addTransaction(new FakeTransaction(-200, elevenDaysAgo)) + bank.totalInterestPaid should be(40) + + cal.add(Calendar.DATE, 3) + val eightDaysAgo = cal.getTime + + maxiSavingsAccount.addTransaction(new FakeTransaction(-200, eightDaysAgo)) + + bank.totalInterestPaid should be(0.6) } + it should "total interest" in { + val bank: Bank = new Bank + val checkingAccount: Account = new CheckingAccount() + val bill: Customer = new Customer("Bill").openAccount(checkingAccount) + bank.addCustomer(bill) + checkingAccount.deposit(100.0) + val savingsAccount: Account = new SavingsAccount() + bank.addCustomer(new Customer("Carol").openAccount(savingsAccount)) + savingsAccount.deposit(1500.0) + bank.totalInterestPaid should be(2.1) + } } diff --git a/src/test/scala/com/abc/CustomerTest.scala b/src/test/scala/com/abc/CustomerTest.scala index 96652b4..c455768 100644 --- a/src/test/scala/com/abc/CustomerTest.scala +++ b/src/test/scala/com/abc/CustomerTest.scala @@ -4,8 +4,8 @@ import org.scalatest.{Matchers, FlatSpec} class CustomerTest extends FlatSpec with Matchers { "Customer" should "statement" in { - val checkingAccount: Account = new Account(Account.CHECKING) - val savingsAccount: Account = new Account(Account.SAVINGS) + val checkingAccount: Account = new CheckingAccount() + val savingsAccount: Account = new SavingsAccount() val henry: Customer = new Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount) checkingAccount.deposit(100.0) savingsAccount.deposit(4000.0) @@ -17,19 +17,40 @@ class CustomerTest extends FlatSpec with Matchers { } it should "testOneAccount" in { - val oscar: Customer = new Customer("Oscar").openAccount(new Account(Account.SAVINGS)) + val oscar: Customer = new Customer("Oscar").openAccount(new SavingsAccount()) oscar.numberOfAccounts should be(1) - } - it should "testTwoAccount" in { - val oscar: Customer = new Customer("Oscar").openAccount(new Account(Account.SAVINGS)) - oscar.openAccount(new Account(Account.CHECKING)) + oscar.openAccount(new CheckingAccount()) oscar.numberOfAccounts should be(2) } - ignore should "testThreeAcounts" in { - val oscar: Customer = new Customer("Oscar").openAccount(new Account(Account.SAVINGS)) - oscar.openAccount(new Account(Account.CHECKING)) - oscar.numberOfAccounts should be(3) + it should "throw IllegalArgumentException for negative withdrawal/deposit" in { + val checkingAccount: Account = new CheckingAccount() + a [IllegalArgumentException] should be thrownBy { + checkingAccount.withdraw(-1000) + } + a [IllegalArgumentException] should be thrownBy { + checkingAccount.deposit(-1000) + } } + + it should "transfer" in { + val checkingAccount: Account = new CheckingAccount() + checkingAccount.deposit(1000.0) + val savingsAccount: Account = new SavingsAccount() + savingsAccount.deposit(200.0) + val henry: Customer = new Customer("Henry") + + a [IllegalArgumentException] should be thrownBy { + henry.transfer(checkingAccount, savingsAccount, 500.0) + } + + henry.openAccount(checkingAccount) + henry.openAccount(savingsAccount) + henry.transfer(checkingAccount, savingsAccount, 500.0) + + checkingAccount.sumTransactions should be(500.0) + savingsAccount.sumTransactions should be (700.0) + } + } diff --git a/src/test/scala/com/abc/TransactionTest.scala b/src/test/scala/com/abc/TransactionTest.scala index cb24c59..b9c0b28 100644 --- a/src/test/scala/com/abc/TransactionTest.scala +++ b/src/test/scala/com/abc/TransactionTest.scala @@ -4,7 +4,7 @@ import org.scalatest.{FlatSpec, Matchers} class TransactionTest extends FlatSpec with Matchers { "Transaction" should "type" in { - val t = new Transaction(5) + val t = new RealTransaction(5) t.isInstanceOf[Transaction] should be(true) } }