Skip to content

Commit 0ae2fef

Browse files
committed
* more tests
* updated balances processor to pass test scenarios * some refactoring and updates
1 parent a61d573 commit 0ae2fef

10 files changed

+226
-117
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Roomanize
22

3-
* Create `db` dir inside api dir for dev. Make sur no .DS_Store or hidden files are inside. It must be empty
3+
* Create `db` dir inside api dir for dev. Make sure no .DS_Store or hidden files are inside. It must be empty
4+
* Run Tests `./service.sh test --flags -x` # -x stops after first failure. https://docs.pytest.org/en/latest/

api/core/crons/balances.py

+38-54
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from datetime import datetime, timedelta, time as d_time
2+
from logging import Logger
23
from time import time
34
from dateutil.relativedelta import *
45
import sqlalchemy
56
from sqlalchemy.orm import joinedload, load_only, defer
7+
from sqlalchemy.sql import functions
8+
69
from config.constants import *
710
from core import get_logger
811
from dal import db
@@ -24,18 +27,13 @@ def balances_cron():
2427
yesterday = today - timedelta(days=1)
2528
five_days_ago = yesterday - timedelta(days=5)
2629

30+
balances = Balance.query.options(joinedload('agreement'), joinedload('payments')).filter(
31+
Balance.due_date.between(five_days_ago, yesterday),
32+
Balance.id.in_(db.session.query(functions.max(Balance.id)).group_by(Balance.agreement_id).subquery())
33+
)
34+
2735
try:
28-
agreements = RentalAgreement.query.options(
29-
joinedload('balances'), joinedload('balances.payments'), joinedload('interval')).filter(
30-
RentalAgreement.terminated_on.is_(None)).filter(RentalAgreement.id.notin_(
31-
Balance.query.options(
32-
defer(Balance.id),
33-
load_only(Balance.agreement_id)
34-
).filter(Balance.due_date >= today).subquery()))\
35-
.join(Balance).filter(
36-
(Balance.due_date.between(five_days_ago, yesterday)))
37-
38-
process_agreements(agreements.all(), logger, yesterday, five_days_ago)
36+
process_agreements(balances.all(), logger, yesterday, five_days_ago)
3937

4038
except (sqlalchemy.exc.OperationalError, Exception) as e:
4139
logger.error('^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^')
@@ -45,57 +43,43 @@ def balances_cron():
4543
logger.info('Took: ' + str(timedelta(seconds=(time() - start))))
4644

4745

48-
def process_agreements(agreements, logger, yesterday: datetime, five_days_ago: datetime):
49-
from config import debug
50-
debug()
51-
52-
agreement: RentalAgreement
53-
for agreement in agreements:
54-
if len(agreement.balances) > 0:
55-
56-
cycle_balance: Balance
57-
for cycle_balance in agreement.balances:
58-
59-
if (cycle_balance.created_on >= five_days_ago and cycle_balance.init_processed) or \
60-
cycle_balance.due_date <= five_days_ago or cycle_balance.due_date > yesterday:
61-
62-
logger.info('Skipping balance ' + str(cycle_balance.id) + ' for aggrement ' + str(agreement.id) +
63-
' since it was created within the last 5 days and has been init_processed.')
64-
continue
46+
def process_agreements(balances: list, logger: Logger, yesterday: datetime, five_days_ago: datetime):
47+
# this should only contain distinct values
48+
agreement_ids = []
49+
cycle_balance: Balance
50+
for cycle_balance in balances:
6551

66-
logger.info('Processing agreement id: ' + str(agreement.id))
52+
assert cycle_balance.agreement_id not in agreement_ids
6753

68-
if not cycle_balance.init_processed:
69-
cycle_balance.init_processed = True
54+
logger.info('Processing agreement id: ' + str(cycle_balance.agreement_id))
7055

71-
payments = sum(map(lambda b: b.amount, cycle_balance.payments))
56+
payments = sum(map(lambda b: b.amount, cycle_balance.payments))
7257

73-
logger.info(str(len(cycle_balance.payments)) + ' Payments processed Total:' + str(payments))
58+
logger.info(str(len(cycle_balance.payments)) + ' Payments processed Total:' + str(payments))
7459

75-
previous_balance = cycle_balance.balance - payments
76-
new_balance = previous_balance + agreement.rate
60+
previous_balance = cycle_balance.balance - payments
61+
new_balance = previous_balance + cycle_balance.agreement.rate
7762

78-
logger.info('Previous balance: ' + str(previous_balance) + ' | New balance: ' + str(new_balance))
63+
logger.info('Previous balance: ' + str(previous_balance) + ' | New balance: ' + str(new_balance))
7964

80-
if agreement.interval.interval == SEMANAL:
81-
delta = timedelta(weeks=1)
82-
elif agreement.interval.interval == QUINCENAL:
83-
delta = timedelta(days=14)
84-
else:
85-
delta = relativedelta(months=1)
65+
if cycle_balance.agreement.interval.interval == SEMANAL:
66+
delta = timedelta(weeks=1)
67+
elif cycle_balance.agreement.interval.interval == QUINCENAL:
68+
delta = timedelta(days=14)
69+
else:
70+
delta = relativedelta(months=1)
8671

87-
new_cycle_balance = Balance(
88-
agreement=agreement,
89-
balance=new_balance,
90-
previous_balance=previous_balance,
91-
due_date=cycle_balance.due_date + delta,
92-
init_processed=True,
93-
)
94-
logger.info('Agreement entered on: ' + str(agreement.entered_on))
95-
logger.info('Pay cycle: ' + agreement.interval.interval)
96-
logger.info('Last due date: ' + str(cycle_balance.due_date))
97-
logger.info('Next due date: ' + str(new_cycle_balance.due_date))
72+
new_cycle_balance = Balance(
73+
agreement=cycle_balance.agreement,
74+
balance=new_balance,
75+
previous_balance=previous_balance,
76+
due_date=cycle_balance.due_date + delta,
77+
)
78+
logger.info('Agreement entered on: ' + str(cycle_balance.agreement.entered_on))
79+
logger.info('Pay cycle: ' + cycle_balance.agreement.interval.interval)
80+
logger.info('Last due date: ' + str(cycle_balance.due_date))
81+
logger.info('Next due date: ' + str(new_cycle_balance.due_date))
9882

99-
db.session.add(new_cycle_balance)
83+
db.session.add(new_cycle_balance)
10084

10185
db.session.commit()

api/core/utils.py

+1-21
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def configure_loggers(app: Flask):
2424
db_logging.handlers = get_logger('sql', level).handlers
2525

2626

27-
def get_logger(name: str = 'app', level: int = logging.INFO):
27+
def get_logger(name: str = 'app', level: int = logging.INFO) -> logging.Logger:
2828
"""
2929
return a logger with default settings
3030
@@ -53,26 +53,6 @@ def get_logger(name: str = 'app', level: int = logging.INFO):
5353
return logger
5454

5555

56-
class Lock:
57-
"""
58-
handy class to create a lock file
59-
use it like lock = Lock('file')
60-
with lock:
61-
# run things
62-
"""
63-
64-
def __init__(self, lock_file):
65-
self.lock_file = lock_file
66-
# raise exception if pid file exists and log it. remove if it's stale
67-
68-
def __enter__(self):
69-
# write pid file
70-
pass
71-
72-
def __exit__(self, type, value, traceback):
73-
# remove pid file
74-
pass
75-
7656

7757
def local_to_utc(date: str) -> datetime:
7858
"""

api/dal/models.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,13 @@ class RentalAgreement(db.Model, ModelIter):
207207
room_id = db.Column(BigInteger, db.ForeignKey('rooms.id'), index=True, nullable=False)
208208
project_id = db.Column(BigInteger, db.ForeignKey('projects.id'), index=True, nullable=False)
209209
time_interval_id = db.Column(db.Integer, db.ForeignKey('time_intervals.id'), nullable=False)
210-
rate = db.Column(db.DECIMAL(10, 2), nullable=False)
211-
deposit = db.Column(db.DECIMAL(10, 2), nullable=False)
210+
rate = db.Column(db.Numeric(10, 2), nullable=False)
211+
deposit = db.Column(db.Numeric(10, 2), nullable=False)
212212
created_on = db.Column('created_on', db.DateTime(), nullable=False, default=datetime.utcnow)
213213
entered_on = db.Column(db.DateTime(), nullable=False)
214214
terminated_on = db.Column(db.DateTime())
215215

216-
tenant_history = relationship(TenantHistory, uselist=False, back_populates='rental_agreement')
216+
tenant_history = relationship(TenantHistory, uselist=False, back_populates='rental_agreement', cascade='all, delete')
217217
room = relationship('Room', uselist=False)
218218
project = relationship(Project, uselist=False)
219219
interval = relationship(TimeInterval, uselist=False)
@@ -258,14 +258,13 @@ class Balance(db.Model, ModelIter):
258258

259259
id = db.Column(BigInteger, primary_key=True)
260260
agreement_id = db.Column(BigInteger, db.ForeignKey('rental_agreements.id'), index=True)
261-
balance = db.Column(db.DECIMAL(10, 2), nullable=False)
262-
previous_balance = db.Column(db.DECIMAL(10, 2), nullable=False)
261+
balance = db.Column(db.Numeric(10, 2), nullable=False)
262+
previous_balance = db.Column(db.Numeric(10, 2), nullable=False)
263263
created_on = db.Column(db.DateTime(), nullable=False, index=True, default=datetime.utcnow)
264264
due_date = db.Column(db.DateTime(), nullable=False, index=True)
265-
init_processed = db.Column(db.Boolean(), nullable=False, default=False)
266265

267-
agreement = relationship(RentalAgreement, uselist=False, backref='balances')
268-
payments = relationship('Payment', backref='balances')
266+
agreement = relationship(RentalAgreement, uselist=False, backref='balances', cascade='all, delete')
267+
payments = relationship('Payment', backref='balances', cascade='all, delete')
269268

270269

271270
class PaymentType(db.Model, ModelIter):
@@ -280,7 +279,7 @@ class Payment(db.Model, ModelIter):
280279

281280
id = db.Column(BigInteger, Sequence('payments_id_seq', start=1000, increment=1), primary_key=True)
282281
balance_id = db.Column(BigInteger, db.ForeignKey('balances.id'), index=True)
283-
amount = db.Column(db.DECIMAL(10, 2), nullable=False)
282+
amount = db.Column(db.Numeric(10, 2), nullable=False)
284283
paid_date = db.Column(db.DateTime(), nullable=False, index=True, default=datetime.utcnow)
285284
payment_type_id = db.Column(db.Integer, db.ForeignKey('payment_types.id'), nullable=False)
286285

@@ -297,7 +296,7 @@ class Expense(db.Model, ModelIter):
297296

298297
id = db.Column(BigInteger, primary_key=True)
299298
project_id = db.Column(BigInteger, db.ForeignKey('projects.id'), index=True, nullable=False)
300-
amount = db.Column(db.DECIMAL(10, 2), nullable=False)
299+
amount = db.Column(db.Numeric(10, 2), nullable=False)
301300
description = db.Column(db.String(512), nullable=False)
302301
input_date = db.Column(db.DateTime(), nullable=False, default=datetime.utcnow)
303302
expense_date = db.Column(db.DateTime(), nullable=False, index=True, default=datetime.utcnow)

api/job_runner.py

-6
This file was deleted.

api/tests/seeders.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
}
3232

3333
registration_sample = {
34-
'date': front_end_date(),
34+
'date': front_end_date(), # defaults to today
3535
'deposit': '4400.00',
36-
'interval': '100',
36+
'interval': '100', # weekly, 200 every two weeks and 400 monthly
3737
'rate': '1500.00',
3838
'reference1': '5555555555',
3939
'reference2': '',
@@ -52,33 +52,41 @@ def seed_project(client: FlaskClient, token: str):
5252
}
5353
return client.post(endpoint('/projects'), json=project_sample, headers=auth)
5454

55-
def seed_room(client: FlaskClient, token: str, overrides: dict):
55+
def seed_room(client: FlaskClient, token: str, override=None):
5656
auth = {
5757
'X-Access-Token': token
5858
}
5959

6060
data = {}
6161
data.update(room_sample)
62-
data.update(overrides)
62+
if override:
63+
data.update(override)
6364

6465
return client.post(endpoint('/rooms'), json=data, headers=auth)
6566

6667

67-
def seed_tenant(client: FlaskClient, token: str):
68+
def seed_tenant(client: FlaskClient, token: str, override=None):
6869
auth = {
6970
'X-Access-Token': token
7071
}
72+
data = {}
73+
data.update(tenant_sample)
74+
if override:
75+
data.update(override)
7176

72-
return client.post(endpoint('/tenants'), json=tenant_sample, headers=auth)
77+
return client.post(endpoint('/tenants'), json=data, headers=auth)
7378

7479

75-
def seed_new_agreement(client: FlaskClient, token: str, overrides: dict):
80+
def seed_new_agreement(client: FlaskClient, token: str, override=None):
7681

82+
if override is None:
83+
override = dict()
7784
auth = {
7885
'X-Access-Token': token
7986
}
8087
data = {}
8188
data.update(registration_sample)
82-
data.update(overrides)
89+
if override:
90+
data.update(override)
8391

8492
return client.post(endpoint('/agreements'), json=data, headers=auth)

0 commit comments

Comments
 (0)