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
17 changes: 16 additions & 1 deletion abcbank/date_provider.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
"""
Class: DateProvider

Note:

now method is modified to return UTC current date and time.
customedDate method is added to allow user to provide a backdated date if necessary.


"""
from datetime import datetime


class DateProvider:
@staticmethod
def now():
return datetime.now()
# Make sure all the dates are in UTC timezone
return datetime.utcnow()

@staticmethod
def customedDate(year, month, day):
return datetime(year, month, day)
93 changes: 93 additions & 0 deletions account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
Class: Account

Note:

__init__ is changed to accept one more parameter, openDate.
openDate is the date the account is opened. It is used for interest calculation.
If it is not passed during instantiation, openDate will be defaulted to current UTC datetime.

deposit and withdraw methods are also modified to accept one more parameter, transDate.
transDate is used for calculating interest rate. It is particuarly useful for maxi_saving_accounts interest calculation.
If transDate is not set, it will be defaulted to current UTC datetime.

There are two interest calculations: simple daily accrued interest (_cal_Simple_AccruedInt)
and compound daily interest (_cal_Compound_AccruedInt).
Since I am not sure which solution you prefer, it is currently set to use simple daily accrued interest calcuation.

sumTransactions method is modified to calculate the amount of withdrawls for the past 10 days when checkAllTransactions
is set to False. It is particuarly useful for maxi_saving_accounts interest calculation.

"""

from abcbank.transaction import Transaction
from abcbank.date_provider import DateProvider

CHECKING = 0
SAVINGS = 1
MAXI_SAVINGS = 2

class Account:
def __init__(self, accountType, openDate=None):
self.accountType = accountType
self.transactions = []
if openDate is None:
self.openDate = DateProvider().now()
else:
self.openDate = openDate

def deposit(self, amount, transDate=None):
if (amount <= 0):
raise ValueError("amount must be greater than zero")
else:
self.transactions.append(Transaction(amount,transDate))


def withdraw(self, amount, transDate=None):
if (amount <= 0):
raise ValueError("amount must be greater than zero")
else:
# Find out if the account has enough money for withdrawl.
if self.sumTransactions() < amount:
raise Exception("Withdrawal amount exceeds the balance of the account.")
else:
self.transactions.append(Transaction(-amount,transDate))


def _cal_Simple_AccruedInt(self, amount, rate):
days_accumlated = (DateProvider().now() - self.openDate).days
return amount * (rate / 365) * days_accumlated

def _cal_Compound_AccruedInt(self, amount, rate):
days_accumlated = (DateProvider().now() - self.openDate).days
#return amount * (1 + rate/365) ** 365
return amount * (1 + rate/365) ** days_accumlated

def interestEarned(self):
amount = self.sumTransactions()

interestFunc = self._cal_Simple_AccruedInt
#interestFunc = self._cal_Compound_AccruedInt

if self.accountType == SAVINGS:
if (amount <= 1000):
return interestFunc(amount,0.001)
else:
return interestFunc(1000,0.001) + interestFunc((amount-1000),0.002)

if self.accountType == MAXI_SAVINGS:
ten_days_withdrawl_amt = self.sumTransactions(checkAllTransactions=False)
if ten_days_withdrawl_amt > 0:
return interestFunc(amount, 0.001)
else:
return interestFunc(amount, 0.05)
else:
return interestFunc(amount,0.001)

def sumTransactions(self, checkAllTransactions=True):
if checkAllTransactions:
return sum([t.amount for t in self.transactions])
else:
# Only calculate the sum of withdrawls for the past 10 days
now = DateProvider().now()
return -1 * sum([t.amount for t in self.transactions if (0 <= (now - t.transactionDate).days <=10) and t.amount < 0])
34 changes: 34 additions & 0 deletions bank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Class: Bank

Note:

getFirstCustomer method is fixed. It only prints out the firstCustomer name if there is a customer.

"""

from abcbank.customer import Customer

class Bank:
def __init__(self):
self.customers = []

def addCustomer(self, customer):
self.customers.append(customer)
def customerSummary(self):
summary = "Customer Summary"
for customer in self.customers:
summary = summary + "\n - " + customer.name + " (" + self._format(customer.numAccs(), "account") + ")"
return summary
def _format(self, number, word):
return str(number) + " " + (word if (number == 1) else word + "s")
def totalInterestPaid(self):
total = 0
for c in self.customers:
total += c.totalInterestEarned()
return total
def getFirstCustomer(self):
if len(self.customers) == 0:
raise Exception("Bank has no customers.")
return self.customers[0].name

76 changes: 76 additions & 0 deletions customer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Class: Customer

Note:

Added transferAccounts method to allow user to transfer money between his/her accounts in the bank.

In statementForAccount method, totalSummary is changed to use account.sumTransactions() method.

"""

from abcbank.account import CHECKING, SAVINGS, MAXI_SAVINGS

class Customer:
def __init__(self, name):
self.name = name
self.accounts = []

def openAccount(self, account):

self.accounts.append(account)
return self

def transferAccounts(self, fromAcct, toAcct, amount):

# Make sure there is sufficent amount of money in the fromAcct before transfer
fromAcctBal = fromAcct.sumTransactions()
if fromAcctBal == 0 or fromAcctBal < amount:
raise Exception('The source account does not have sufficient funds for transfer.')

fromAcct.withdraw(amount)
toAcct.deposit(amount)

def numAccs(self):
return len(self.accounts)

def totalInterestEarned(self):
return sum([a.interestEarned() for a in self.accounts])

# This method gets a statement
def getStatement(self):
# JIRA-123 Change by Joe Bloggs 29/7/1988 start
statement = None # reset statement to null here
# JIRA-123 Change by Joe Bloggs 29/7/1988 end
totalAcrossAllAccounts = sum([a.sumTransactions() for a in self.accounts])
statement = "Statement for %s" % self.name
for account in self.accounts:
statement = statement + self.statementForAccount(account)
statement = statement + "\n\nTotal In All Accounts " + _toDollars(totalAcrossAllAccounts)
return statement

def statementForAccount(self, account):
accountType = "\n\n\n"
if account.accountType == CHECKING:
accountType = "\n\nChecking Account\n"
if account.accountType == SAVINGS:
accountType = "\n\nSavings Account\n"
if account.accountType == MAXI_SAVINGS:
accountType = "\n\nMaxi Savings Account\n"
transactionSummary = [self.withdrawalOrDepositText(t) + " " + _toDollars(abs(t.amount))
for t in account.transactions]
transactionSummary = " " + "\n ".join(transactionSummary) + "\n"
totalSummary = "Total " + _toDollars(account.sumTransactions())
return accountType + transactionSummary + totalSummary

def withdrawalOrDepositText(self, transaction):
if transaction.amount < 0:
return "withdrawal"
elif transaction.amount > 0:
return "deposit"
else:
return "N/A"


def _toDollars(number):
return "${:1.2f}".format(number)
92 changes: 82 additions & 10 deletions tests/bank_tests.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
from nose.tools import assert_equals
"""
Note:

from account import Account, CHECKING, MAXI_SAVINGS, SAVINGS
from bank import Bank
from customer import Customer
There are two sets of interest calculation tests: Simple Daily Interest and Compound Daily Interest.
The Compound Daily Interest set is turned off. Please turn it on if that is the calculation you are looking for.
Make sure interestFunc is set to self._cal_Compound_AccruedInt in interestEarned method in Account class file.

"""

from nose.tools import assert_equals, assert_raises, nottest

from abcbank.account import Account, CHECKING, MAXI_SAVINGS, SAVINGS
from abcbank.bank import Bank
from abcbank.customer import Customer

from datetime import datetime, time
from dateutil.relativedelta import relativedelta

# Figure out the date one year from now.
# This is easier for calculate the interest
backDated_one_year = datetime.utcnow().date() + relativedelta(days=-365)
backDated_one_year = datetime.combine(backDated_one_year, time.min)

def test_customer_summary():
bank = Bank()
Expand All @@ -12,27 +28,83 @@ def test_customer_summary():
assert_equals(bank.customerSummary(),
"Customer Summary\n - John (1 account)")

def test_firstCustomer():
bank = Bank()
assert_raises(Exception, bank.getFirstCustomer)


def test_checking_account():
# Simple Daily Interest Calculation Test.
bank = Bank()
checkingAccount = Account(CHECKING)
checkingAccount = Account(CHECKING,backDated_one_year)
bill = Customer("Bill").openAccount(checkingAccount)
bank.addCustomer(bill)
checkingAccount.deposit(100.0)
assert_equals(bank.totalInterestPaid(), 0.1)
assert_equals(bank.totalInterestPaid(), 0.10000000000000002)


def test_savings_account():
# Simple Daily Interest Calculation Test.
bank = Bank()
checkingAccount = Account(SAVINGS)
checkingAccount = Account(SAVINGS,backDated_one_year)
bank.addCustomer(Customer("Bill").openAccount(checkingAccount))
checkingAccount.deposit(1500.0)
assert_equals(bank.totalInterestPaid(), 2.0)
assert_equals(bank.totalInterestPaid(), 2.00)


def test_maxi_savings_account():
# Simple Daily Interest Calculation Test.
bank = Bank()
checkingAccount = Account(MAXI_SAVINGS)
checkingAccount = Account(MAXI_SAVINGS,backDated_one_year)
bank.addCustomer(Customer("Bill").openAccount(checkingAccount))
checkingAccount.deposit(3000.0)
assert_equals(bank.totalInterestPaid(), 170.0)
assert_equals(bank.totalInterestPaid(), 150.0)


def test_maxi_savings_acct_diffInt():
# Simple Daily Interest Calculation Test.
bank = Bank()
checkingAccount = Account(MAXI_SAVINGS,backDated_one_year)
bank.addCustomer(Customer("Bill").openAccount(checkingAccount))
checkingAccount.deposit(3000.0)
checkingAccount.withdraw(1000.0)
assert_equals(bank.totalInterestPaid(), 2.0)

@nottest
def test_checking_account_2():
# Compond Daily Interest Calculation Test.
bank = Bank()
checkingAccount = Account(CHECKING,backDated_one_year)
bill = Customer("Bill").openAccount(checkingAccount)
bank.addCustomer(bill)
checkingAccount.deposit(100.0)
assert_equals(bank.totalInterestPaid(), 100.10004987954706 )

@nottest
def test_savings_account_2():
# Compond Daily Interest Calculation Test.
bank = Bank()
checkingAccount = Account(SAVINGS,backDated_one_year)
bank.addCustomer(Customer("Bill").openAccount(checkingAccount))
checkingAccount.deposit(1500.0)
assert_equals(bank.totalInterestPaid(), 1502.0014967172629)

@nottest
def test_maxi_savings_account_2():
# Compond Daily Interest Calculation Test.
bank = Bank()
checkingAccount = Account(MAXI_SAVINGS,backDated_one_year)
bank.addCustomer(Customer("Bill").openAccount(checkingAccount))
checkingAccount.deposit(3000.0)
assert_equals(bank.totalInterestPaid(), 3153.802489402342)

@nottest
def test_maxi_savings_acct_diffInt_2():
# Compond Daily Interest Calculation Test.
bank = Bank()
checkingAccount = Account(MAXI_SAVINGS,backDated_one_year)
bank.addCustomer(Customer("Bill").openAccount(checkingAccount))
checkingAccount.deposit(3000.0)
checkingAccount.withdraw(1000.0)
assert_equals(bank.totalInterestPaid(), 2002.000997590941)

35 changes: 30 additions & 5 deletions tests/customer_tests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from nose.tools import assert_equals, nottest
from nose.tools import assert_equals, assert_raises, nottest

from account import Account, CHECKING, SAVINGS
from customer import Customer
from abcbank.account import Account, CHECKING, SAVINGS, MAXI_SAVINGS
from abcbank.customer import Customer


def test_statement():
Expand Down Expand Up @@ -29,8 +29,33 @@ def test_twoAccounts():
assert_equals(oscar.numAccs(), 2)


@nottest
def test_threeAccounts():
oscar = Customer("Oscar").openAccount(Account(SAVINGS))
oscar.openAccount(Account(CHECKING))
assert_equals(oscar.numAccs(), 3)
oscar.openAccount(Account(MAXI_SAVINGS))
assert_equals(oscar.numAccs(), 3)

def test_withdrawl_error():
checkingAccount = Account(CHECKING)
david = Customer("David").openAccount(checkingAccount)
checkingAccount.deposit(100.0)
assert_raises(Exception, checkingAccount.withdraw, 200)

def test_transfer():
checkingAccount = Account(CHECKING)
savingsAccount = Account(SAVINGS)
henry = Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount)
checkingAccount.deposit(100.0)
savingsAccount.deposit(4000.0)
henry.transferAccounts(savingsAccount, checkingAccount, 1000)
assert_equals(savingsAccount.sumTransactions(), 3000)
assert_equals(checkingAccount.sumTransactions(), 1100)

def test_transfer_with_insufficientfund():
checkingAccount = Account(CHECKING)
savingsAccount = Account(SAVINGS)
john = Customer("John").openAccount(checkingAccount).openAccount(savingsAccount)
checkingAccount.deposit(100.0)
savingsAccount.deposit(4000.0)
assert_raises(Exception, john.transferAccounts, (savingsAccount, checkingAccount, 5000))

Loading