Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 53 additions & 24 deletions src/main/scala/com/abc/Account.scala
Original file line number Diff line number Diff line change
@@ -1,44 +1,73 @@
package com.abc

import scala.collection.mutable.ListBuffer
import java.util.Date

object AccountType extends Enumeration {
type AccountType = Value
val CHECKING , SAVINGS , MAXI_SAVINGS = Value

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()) {
case class Account(val accountType: AccountType.Value, val accountNumber:String, private var accruedInterest : Double = 0,
private var transactions: ListBuffer[Transaction] = ListBuffer(),
val accountCreationDate : Date = DateProvider.instance.now ) {

def deposit(amount: Double) {
if (amount <= 0)
throw new IllegalArgumentException("amount must be greater than zero")
else
transactions += Transaction(amount)
require(amount >= 0 , "amount must be greater than zero");
transactions.synchronized{
transactions += new Transaction(amount)
}
}

def getInterestEarned = accruedInterest
def getTransactionCount = transactions.size


def getTransactionSummary = transactions.map(t => t.toString)
.mkString(" ", "\n ", "\n")

def withdraw(amount: Double) {
if (amount <= 0)
throw new IllegalArgumentException("amount must be greater than zero")
else
transactions += Transaction(-amount)
require(amount >= 0 && sumTransactions() > amount , "amount must be greater than zero");
transactions.synchronized{
transactions += new Transaction(-amount)
}
}






/*
* This method calculates the daily interest rate that is accumulated
* THis is expected to be executed in some kind of batch process
* assuming 365 days in a year for interest calc basis
* I am accumulating the daily accrued interest in a field here
* Option B was to calculate it daily on the fly . I think we may not want to do that because of performance issues
*
*
*/
def interestEarned: Double = {
val amount: Double = sumTransactions()
val amount: Double = sumTransactions() + accruedInterest
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
case AccountType.SAVINGS =>
if (amount <= 1000) amount * 0.001/365.0
else 1/365.0 + (amount - 1000) * 0.002 / 365.0
case AccountType.MAXI_SAVINGS =>
if (daysSinceLastWithDrawal < 10) amount *.001/365.0 else amount * .05 /365.0
case AccountType.CHECKING => amount * .001 / 365.0
case _ => 0

}
}

def sumTransactions(checkAllTransactions: Boolean = true): Double = transactions.map(_.amount).sum
def sumTransactions(checkAllTransactions: Boolean = true): Double = transactions.synchronized {transactions.map(_.amount).sum}

def lastWithdrawalDate = { val withdrawals = transactions.synchronized {transactions.filter (x => x.amount< 0).map(_.transactionDate)}
if ( ! withdrawals.isEmpty ) withdrawals.max else accountCreationDate
}
def daysSinceLastWithDrawal = DateProvider.instance.daysBetween(DateProvider.instance.now, lastWithdrawalDate)
def accumulateInterest = accruedInterest += interestEarned

}
29 changes: 11 additions & 18 deletions src/main/scala/com/abc/Bank.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,33 @@ package com.abc
import scala.collection.mutable.ListBuffer

class Bank {
var customers = new ListBuffer[Customer]
private var customers = new ListBuffer[Customer]

def addCustomer(customer: Customer) {
customers += customer
}
def noOfCustomer = customers.size

def customerSummary: String = {
var summary: String = "Customer Summary"
for (customer <- customers)
summary = summary + "\n - " + customer.name + " (" + format(customer.numberOfAccounts, "account") + ")"
summary

customers.foldLeft("Customer Summary" ){ ( a, b) => a + "\n - " + b.name + " (" + format(b.numberOfAccounts, "account") + ")"}

}

private def format(number: Int, word: String): String = {
number + " " + (if (number == 1) word else word + "s")
}

def totalInterestPaid: Double = {
var total: Double = 0
for (c <- customers) total += c.totalInterestEarned
return total

customers.foldLeft(0.0){(a,b) => a + b.totalInterestEarned}

}

def getFirstCustomer: String = {
try {
customers = null
customers(0).name
}
catch {
case e: Exception => {
e.printStackTrace
return "Error"
}
}

if (customers.size > 0) customers.head.name else ""

}

}
Expand Down
69 changes: 45 additions & 24 deletions src/main/scala/com/abc/Customer.scala
Original file line number Diff line number Diff line change
@@ -1,53 +1,74 @@
package com.abc

import scala.collection.mutable.ListBuffer
import scala.collection.mutable.Map

class Customer(val name: String, var accounts: ListBuffer[Account] = ListBuffer()) {
case class Customer(val name: String, private var accounts: Map[String ,Account] = Map[String,Account]()) {

// Ideal would be to use the akka framework and create Customer, Account etc. as Actors instead of explicit synchronization...

def openAccount(account: Account): Customer = {
accounts += account
this
this.synchronized {
accounts += (account.accountNumber -> account)
}
this

}

def numberOfAccounts: Int = accounts.size

def totalInterestEarned: Double = accounts.map(_.interestEarned).sum
def totalInterestEarned: Double = accounts.mapValues { x => x.getInterestEarned }.values.sum





/**
* This method gets a statement
*/
def getStatement: String = {
//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
statement = f"Statement for $name\n" +
accounts.map(statementForAccount).mkString("\n", "\n\n", "\n") +

val totalAcrossAllAccounts = accounts.mapValues(_.sumTransactions()).values.sum
//statement =
s"Statement for $name\n" +
accounts.values.map(statementForAccount).mkString("\n", "\n\n", "\n") +
s"\nTotal In All Accounts ${toDollars(totalAcrossAllAccounts)}"
statement

}

/*
* Transfer money between 2 accounts for same customer
* returns a tuple of balance of fromAccount and 2 account
*/
def transfer(fromAccount : String , toAccount : String , transferAmount : Double) = {
val from = accounts.get(fromAccount)
val to = accounts.get(toAccount)

require ( from != None && to != None, " Invalid accounts numbers provided.")
require (transferAmount > 0 , " Transfer amount must be greater than 0")

require (from.get.sumTransactions() > transferAmount , "Insufficient Balance")
this.synchronized {
from.get.withdraw(transferAmount)
to.get.deposit(transferAmount)
(from.get.sumTransactions(), to.get.sumTransactions())
}
}

private def statementForAccount(a: Account): String = {
val accountType = a.accountType match {
case Account.CHECKING =>
case AccountType.CHECKING =>
"Checking Account\n"
case Account.SAVINGS =>
case AccountType.SAVINGS =>
"Savings Account\n"
case Account.MAXI_SAVINGS =>
case AccountType.MAXI_SAVINGS =>
"Maxi Savings Account\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)}"
val transactionSummary = a.getTransactionSummary
val totalSummary = s"Total ${toDollars(a.sumTransactions())}"
accountType + transactionSummary + totalSummary
}

private def withdrawalOrDepositText(t: Transaction) =
t.amount match {
case a if a < 0 => "withdrawal"
case a if a > 0 => "deposit"
case _ => "N/A"
}


private def toDollars(number: Double): String = f"$$$number%.2f"
}
Expand Down
12 changes: 7 additions & 5 deletions src/main/scala/com/abc/DateProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ package com.abc

import java.util.Calendar
import java.util.Date
import java.util.concurrent.TimeUnit



object DateProvider {
def getInstance: DateProvider = {
if (instance == null) instance = new DateProvider
instance
}


private var instance: DateProvider = null
val instance: DateProvider = new DateProvider
}

class DateProvider {
def now: Date = {
return Calendar.getInstance.getTime
}
def daysBetween ( d1 : Date , d2 : Date) = TimeUnit.DAYS.convert( (d2.getTime - d1.getTime) , TimeUnit.MILLISECONDS);

}

18 changes: 16 additions & 2 deletions src/main/scala/com/abc/Transaction.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
package com.abc

case class Transaction(var amount: Double) {
val transactionDate = DateProvider.getInstance.now
import java.util.Date

class Transaction(val amount: Double) {
val transactionDate = DateProvider.instance.now

private def withdrawalOrDepositText =
this.amount match {
case a if a < 0 => "withdrawal"
case a if a > 0 => "deposit"
case _ => "N/A"
}
private def toDollars : String = f"$$$amount%.2f"
override val toString = withdrawalOrDepositText + " " + toDollars

}

// the following is only for testing
case class TestTransaction( override val amount: Double,override val transactionDate : Date) extends Transaction (amount: Double);
92 changes: 92 additions & 0 deletions src/test/scala/com/abc/AccountTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.abc

import org.scalatest.{ Matchers, FlatSpec }
import java.util.Calendar

class AccountTest extends FlatSpec with Matchers {
"Account" should "fail when we try to deposit negative amount" in {
val checkingAccount: Account = Account(AccountType.CHECKING, "C1")
val savingsAccount: Account = Account(AccountType.SAVINGS, "S1")

an[Exception] should be thrownBy checkingAccount.deposit(-10)
}

it should "successfully take positive deposits" in {
val checkingAccount: Account = Account(AccountType.CHECKING, "C1")
val savingsAccount: Account = Account(AccountType.SAVINGS, "S1")

checkingAccount.deposit(10)
checkingAccount.getTransactionCount should equal(1)
}

it should "fail when we withdraw negative amounts" in {
val checkingAccount: Account = Account(AccountType.CHECKING, "C1")
val savingsAccount: Account = Account(AccountType.SAVINGS, "S1")

an[Exception] should be thrownBy checkingAccount.withdraw(-10)

}

it should "fail when we withdraw amounts more than the balance" in {
val checkingAccount: Account = Account(AccountType.CHECKING, "C1")
val savingsAccount: Account = Account(AccountType.SAVINGS, "S1")

an[Exception] should be thrownBy checkingAccount.withdraw(10)

}

it should "successfully take withdraw deposits" in {
val checkingAccount: Account = Account(AccountType.CHECKING, "C1")
val savingsAccount: Account = Account(AccountType.SAVINGS, "S1")

checkingAccount.deposit(100)
checkingAccount.withdraw(50)
checkingAccount.sumTransactions() should be(50)
}

it should "successfully retrieve last withdrwal date from a bunch of transaction " in {
//Ideally we should use some mock transaction but I am subclassing it to override the date for testing
var after10Days = Calendar.getInstance
after10Days.add(Calendar.DAY_OF_MONTH, 10)
val expectedDate = after10Days.getTime

val t1 = new TestTransaction(100, Calendar.getInstance.getTime)
val t2 = new TestTransaction(-10, expectedDate)
val t3 = new TestTransaction(-20, Calendar.getInstance.getTime)

import scala.collection.mutable.ListBuffer
val checkingAccount: Account = Account(AccountType.CHECKING, "C1",0, ListBuffer(t1, t2, t3))
checkingAccount.lastWithdrawalDate should be(expectedDate)

}

it should "successfully calculate the number of days since last withdrawal " in {
//Ideally we should use some mock transaction but I am subclassing it to override the date for testing
var after10Days = Calendar.getInstance
after10Days.add(Calendar.DAY_OF_MONTH, 10)
val expectedDate = after10Days.getTime

val t1 = new TestTransaction(100, Calendar.getInstance.getTime)
val t2 = new TestTransaction(-10, expectedDate)
val t3 = new TestTransaction(-20, Calendar.getInstance.getTime)

import scala.collection.mutable.ListBuffer
val checkingAccount: Account = Account(AccountType.CHECKING, "C1", 0,ListBuffer(t1, t2, t3))
//The test may be flaky since we have the time component hence I am checking the range

assert (checkingAccount.daysSinceLastWithDrawal >= 9 && checkingAccount.daysSinceLastWithDrawal <= 11)
}

it should "successfully calculate the account balance" in {
val checkingAccount: Account = Account(AccountType.CHECKING, "C1")

checkingAccount.deposit(10)
checkingAccount.deposit(1000)
checkingAccount.withdraw(10)

checkingAccount.sumTransactions() should be(1000)

}

}

Loading