-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaccount_classes.py
270 lines (217 loc) · 9.86 KB
/
account_classes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
import datetime
import setup_db as ldb
# Connect to local MySql server
my_local_db = ldb.initialise_local_db_connection('bank')
class Bank:
def __init__(self, name, parent=None):
self.name = name
self.parent = parent
self.account_types = {'Current Account', 'Savings Account', 'Business Account'}
self.branches = []
def __repr__(self):
return "{}('{}','{}')".format(
__class__.__name__, self.name, self.parent
)
def __str__(self):
return self.name
def __eq__(self, other):
if not isinstance(other, Bank):
return NotImplemented
return self.name == other.name
def add_branch(self, new_branch):
if new_branch not in self.branches:
new_branch.bank = self
self.branches.append(new_branch)
class Branch:
def __init__(self, address_one, sort_code):
self.address_one = address_one
self.sort_code = sort_code
self.customers = []
self.bank = None
def __repr__(self):
return "{}('{}','{}')".format(
__class__.__name__, self.address_one, self.sort_code
)
def __str__(self):
return str(self.address_one)
def __eq__(self, other):
if not isinstance(other, Branch):
return NotImplemented
return self.sort_code == other.sort_code
def add_customer(self, new_customer):
if new_customer not in self.customers:
new_customer.branch = self
new_customer.accounts = {
item: [] for item in self.bank.account_types
}
self.customers.append(new_customer)
class Customer:
def __init__(self, first_name, last_name, overdraft=None):
self.first_name = first_name
self.last_name = last_name
self.overdraft = overdraft
self.branch = None
self.accounts = {}
self.cards = []
def __repr__(self):
return "{}('{}','{}')".format(
__class__.__name__, self.first_name, self.last_name
)
def __str__(self):
return self.first_name + " " + self.last_name
# Customer object not defined with d.o.b. or address hence limited equality condition
def __eq__(self, other):
if not isinstance(other, Customer):
return NotImplemented
return self.first_name == other.first_name and self.last_name == other.last_name
def add_account(self, new_account, account_type):
if account_type not in self.accounts.keys():
self.accounts[account_type] = []
if new_account not in self.accounts[account_type]:
new_account.holder.append(self)
self.accounts[account_type].append(new_account)
def add_card(self, new_card):
if new_card not in self.cards:
new_card.holder = self
self.cards.append(new_card)
def balance(self):
total = 0
for account_type in self.accounts:
for account in account_type:
total += account.balance
return total
# Parent class for general bank account with no interest, no fees, no overdraft and no cap on number of debits.
class Account:
interest = 0.0
overdrawn_fee = None
maintenance_fee_period = 1
maintenance_fee = None
transaction_fee = None
debit_cap = None
waive_atm_fee = False
def __init__(self, number, currency='gbp', overdraft=None):
self.number = number
self.currency = currency
self.overdraft = overdraft
self.transaction_history = {
'credit': [],
'debit': []
}
self.is_frozen = False
self.holder = []
def __repr__(self):
return "{}({},'{}',{})".format(
__class__.__name__, self.number, self.currency, self.overdraft
)
def __str__(self):
return self.number
def __eq__(self, other):
if not isinstance(other, Account):
return NotImplemented
return self.number == other.number
# Add a new joint account holder to the existing account
def add_joint_holder(self, new_holder):
if new_holder not in self.holder:
self.holder.append(new_holder)
# return transactions in reverse chronological order
def return_transaction(self, max_num):
transactions = ldb.sql_query_fetchall(my_local_db, f"SELECT * FROM transactions WHERE account_id = {self.number}")
return sorted(transactions, key=lambda item: item[5], reverse=True)[:max_num]
# Add a new transaction to the transactions table
def add_transaction(self, amount, trans_type, category, description=None):
my_sql_query = "INSERT INTO transactions (account_id, amount, type, category, description, date_time) VALUES (%s, %s, %s, %s, %s, %s)"
ldb.sql_query_commit(my_local_db, my_sql_query, (self.number, amount, trans_type, category, description, datetime.datetime.utcnow()))
if trans_type == 'debit':
# Add a new transaction for any transaction fees
if self.transaction_fee is not None:
ldb.sql_query_commit(my_local_db, my_sql_query, (self.number, self.transaction_fee, trans_type, 'fee', 'transaction fee', datetime.datetime.utcnow()))
# Add a new transaction for an ATM fee refund if applicable
if self.waive_atm_fee is True and description == 'atm fee':
ldb.sql_query_commit(my_local_db, my_sql_query, (self.number, amount, 'credit', 'cr', 'refund atm fee', datetime.datetime.utcnow()))
if self.balance() < 0.0 - float(self.overdraft):
# Add a new transaction for an overdraft fee
if self.overdrawn_fee is not None:
ldb.sql_query_commit(my_local_db, my_sql_query, (self.number, self.overdrawn_fee, trans_type, 'fee', 'account overdrawn fee', datetime.datetime.utcnow()))
else:
# Add a new transaction to offset the debit transaction if the account has overdraft protection
# Code assumes Accounts with None overdrawn fee also waive atm fee
trans_type = 'credit'
ldb.sql_query_commit(my_local_db, my_sql_query, (self.number, amount, trans_type, 'ref', 'unpaid item', datetime.datetime.utcnow()))
if self.transaction_fee is not None:
ldb.sql_query_commit(my_local_db, my_sql_query, (self.number, self.transaction_fee, trans_type, 'ref', 'unpaid item', datetime.datetime.utcnow()))
return False
return True
def balance(self):
total = 0.0
transactions = ldb.sql_query_fetchall(my_local_db, f"SELECT amount, type FROM transactions WHERE account_id = {self.number}")
for transaction in transactions:
if transaction[1] == 'credit':
total += float(transaction[0])
else:
total -= float(transaction[0])
return total
# Parent class for general card with default pin and no link to a bank account
class Card:
def __init__(self, number, pin, expiry_date=datetime.date.today()+datetime.timedelta(days=365*3)):
self.number = number
self.transaction_networks = {'link'}
self.expiry_date = expiry_date
self.holder = None
self.account = None
self.pin = pin
def __repr__(self):
return "{}('{}', '{}')".format(
__class__.__name__, self.number, self.pin
)
def __str__(self):
return self.number
def __eq__(self, other):
if not isinstance(other, Card):
return NotImplemented
return self.number == other.number and self.expiry_date == other.expiry_date and self.holder == other.holder
def set_pin(self, new_pin):
my_sql_query = "UPDATE cards SET pin = (%s) WHERE number = (%s) AND holder_firstname = (%s) AND holder_lastname = (%s)"
if len(new_pin) == 4 and new_pin.isnumeric():
ldb.sql_query_commit(my_local_db, my_sql_query, (new_pin, self.number, self.holder.first_name, self.holder.last_name))
def link_account(self, new_account):
my_sql_query = "UPDATE cards SET linked_account = (%s) WHERE number = (%s) AND holder_firstname = (%s) AND holder_lastname = (%s)"
ldb.sql_query_commit(my_local_db, my_sql_query, (new_account.number, self.number, self.holder.first_name, self.holder.last_name))
class CurrentAccount(Account):
interest = 0.01
overdrawn_fee = 15.0
maintenance_fee = 5.0
def __init__(self, number, currency='gbp', overdraft=250.0):
super().__init__(number, currency, overdraft)
class SavingsAccount(Account):
interest = 0.04
debit_cap = 5
waive_atm_fee = True
def __init__(self, number, currency='gbp', overdraft=0.0):
super().__init__(number, currency, overdraft)
class BusinessAccount(Account):
overdrawn_fee = 40.0
maintenance_fee = 10.0
transaction_fee = 1.5
def __init__(self, number, currency='gbp', overdraft=1000.0):
super().__init__(number, currency, overdraft)
# Transaction class for debit or credit that stores amounts as float
class Transaction:
def __init__(self, amount, transaction_type, category, description=None):
self.amount = float(amount)
self.transaction_type = transaction_type
self.category = category
self.description = description
self.date_time = datetime.datetime.utcnow()
self.account = None
def __repr__(self):
return "{}({},'{}','{}','{}')".format(
__class__.__name__, self.amount, self.transaction_type, self.category, self.description
)
def __str__(self):
return str(self.amount)
def __float__(self):
return self.amount
def __eq__(self, other):
if not isinstance(other, Transaction):
return NotImplemented
return self.account == other.account and self.transaction_type == other.transaction_type and self.date_time == other.date_time