From ed3579696134902c1b0185fce6fee09216f1ba5f Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Sun, 21 Aug 2016 23:42:51 -0400 Subject: [PATCH 1/9] v0.0.1 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. --- abcbank/date_provider.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/abcbank/date_provider.py b/abcbank/date_provider.py index 33b64eb..36f1737 100644 --- a/abcbank/date_provider.py +++ b/abcbank/date_provider.py @@ -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() \ No newline at end of file + # Make sure all the dates are in UTC timezone + return datetime.utcnow() + + @staticmethod + def customedDate(year, month, day): + return datetime(year, month, day) From ad381c085e9f62b066dfa53f953c4f4b831b9789 Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Sun, 21 Aug 2016 23:50:41 -0400 Subject: [PATCH 2/9] v0.0.1 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. --- tests/bank_tests.py | 92 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/tests/bank_tests.py b/tests/bank_tests.py index 6de98db..897f5a5 100644 --- a/tests/bank_tests.py +++ b/tests/bank_tests.py @@ -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() @@ -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) \ No newline at end of file + 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) + From 1be2354412d32fc96b1e1cffbef270cfa103d0ce Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Sun, 21 Aug 2016 23:52:36 -0400 Subject: [PATCH 3/9] v0.0.1 Added account transfer and account overdrawn test. --- .gitignore | 3 ++- tests/customer_tests.py | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index de3c501..50e5ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -98,4 +98,5 @@ out/ atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml \ No newline at end of file +com_crashlytics_export_strings.xml +tests/bank_another_tests.py diff --git a/tests/customer_tests.py b/tests/customer_tests.py index 0211a4f..6b650e3 100644 --- a/tests/customer_tests.py +++ b/tests/customer_tests.py @@ -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(): @@ -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) \ No newline at end of file + 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)) + From 09fbe85160fde52082b472c81b56960fba8bdeb1 Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Sun, 21 Aug 2016 23:53:19 -0400 Subject: [PATCH 4/9] v0.01 Added a transaction creation with customed date --- tests/transaction_tests.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/transaction_tests.py b/tests/transaction_tests.py index 62caa8a..a16d66c 100644 --- a/tests/transaction_tests.py +++ b/tests/transaction_tests.py @@ -1,8 +1,13 @@ -from nose.tools import assert_is_instance - -from transaction import Transaction +from nose.tools import assert_is_instance, assert_equal +from abcbank.transaction import Transaction +from abcbank.date_provider import DateProvider +from datetime import datetime def test_type(): t = Transaction(5) - assert_is_instance(t, Transaction, "correct type") \ No newline at end of file + assert_is_instance(t, Transaction, "correct type") + +def test_transDate(): + t = Transaction(10, DateProvider().customedDate(2016, 8, 20)) + assert_equal(t.transactionDate, datetime(2016, 8, 20)) From c8223b9b8ede0f4627b79df076da7121ec3c5475 Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Mon, 22 Aug 2016 00:01:20 -0400 Subject: [PATCH 5/9] v0.01 Modified to support new daily accrued interest calculations. Currently, interestEarned is set to use simple daily accrued interest method. Compound daily accrued interest calculation is included. This can be switched to use compound interest calculation. Set interestFunc = self._cal_Simple_AccruedInt --- account.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 account.py diff --git a/account.py b/account.py new file mode 100644 index 0000000..7e50215 --- /dev/null +++ b/account.py @@ -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]) From cc29a359631f4658ba5bfa8eba29b1d770f35a2b Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Mon, 22 Aug 2016 00:01:33 -0400 Subject: [PATCH 6/9] v0.0.1 getFirstCustomer method is fixed. It only prints out the firstCustomer name if there is a customer. --- bank.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 bank.py diff --git a/bank.py b/bank.py new file mode 100644 index 0000000..683da9d --- /dev/null +++ b/bank.py @@ -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 + From 8593234df547cbd7d39a2b903fbc54bad4eedbd0 Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Mon, 22 Aug 2016 00:01:49 -0400 Subject: [PATCH 7/9] v0.0.1 dded 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. --- customer.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 customer.py diff --git a/customer.py b/customer.py new file mode 100644 index 0000000..f96d9bf --- /dev/null +++ b/customer.py @@ -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) From feac269a99854a8835b86f3dd857590517207fcd Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Mon, 22 Aug 2016 00:02:11 -0400 Subject: [PATCH 8/9] The class init method is modified to take an additional parameter: transDate. transDate is for interest calculation purpose. If transDate is not passed, it will be defaulted to current UTC datetime. --- transaction.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 transaction.py diff --git a/transaction.py b/transaction.py new file mode 100644 index 0000000..1a55e0d --- /dev/null +++ b/transaction.py @@ -0,0 +1,21 @@ +""" +Class: Transaction + +Note: + +The class init method is modified to take an additional parameter: transDate. +transDate is for interest calculation purpose. If transDate is not passed, +it will be defaulted to current UTC datetime. + +""" + + +from abcbank.date_provider import DateProvider + +class Transaction: + def __init__(self, amount, transDate=None): + self.amount = amount + if transDate is None: + self.transactionDate = DateProvider.now() + else: + self.transactionDate = transDate From 566e4d0e3fc104ad9556823fcc1279cd7f4cc957 Mon Sep 17 00:00:00 2001 From: carsonsterlingchan Date: Mon, 22 Aug 2016 00:18:29 -0400 Subject: [PATCH 9/9] v0.0.1 Restored the original version from the master. --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 50e5ac0..de3c501 100644 --- a/.gitignore +++ b/.gitignore @@ -98,5 +98,4 @@ out/ atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -tests/bank_another_tests.py +com_crashlytics_export_strings.xml \ No newline at end of file