Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

20719- Interim Statements Settings Fix #1584

Merged
merged 3 commits into from
Jun 26, 2024
Merged
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
3 changes: 2 additions & 1 deletion jobs/payment-jobs/invoke_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def run(job_name, argument=None):
if __name__ == "__main__":
print('----------------------------Scheduler Ran With Argument--', sys.argv[1])
if (len(sys.argv) > 2):
run(sys.argv[1], sys.argv[2])
params = sys.argv[2:len(sys.argv)]
run(sys.argv[1], params)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update to allow any number of parameters to be passed in when running a job.

else:
run(sys.argv[1])
61 changes: 40 additions & 21 deletions jobs/payment-jobs/tasks/statement_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,43 @@ class StatementTask: # pylint:disable=too-few-public-methods
"""Task to generate statements."""

has_date_override: bool = False
has_account_override: bool = False

@classmethod
def generate_statements(cls, date_override=None):
def generate_statements(cls, arguments=None):
"""Generate statements.

Steps:
1. Get all payment accounts and it's active statement settings.
"""
date_override = arguments[0] if arguments and len(arguments) > 0 else None
auth_account_override = arguments[1] if arguments and len(arguments) > 1 else None
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow a specific account number to be filtered on. This allows us to generate a monthly statement using the interim statement settings before the month is up without affecting other accounts

Copy link
Collaborator

@seeker25 seeker25 Jun 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, I was thinking about this the other day, when I had to regenerate statements for a client


target_time = get_local_time(datetime.now()) if date_override is None \
else datetime.strptime(date_override, '%Y-%m-%d') + timedelta(days=1)
cls.has_date_override = date_override is not None
cls.has_account_override = auth_account_override is not None
if date_override:
current_app.logger.debug(f'Generating statements for: {date_override} using date override.')
if auth_account_override:
current_app.logger.debug(f'Generating statements for: {auth_account_override} using account override.')
# If today is sunday - generate all weekly statements for pervious week
# If today is month beginning - generate all monthly statements for previous month
# For every day generate all daily statements for previous day
generate_weekly = target_time.weekday() == 6 # Sunday is 6
generate_monthly = target_time.day == 1

cls._generate_daily_statements(target_time)
cls._generate_daily_statements(target_time, auth_account_override)
if generate_weekly:
cls._generate_weekly_statements(target_time)
cls._generate_weekly_statements(target_time, auth_account_override)
if generate_monthly:
cls._generate_monthly_statements(target_time)
cls._generate_monthly_statements(target_time, auth_account_override)

# Commit transaction
db.session.commit()

@classmethod
def _generate_daily_statements(cls, target_time: datetime):
def _generate_daily_statements(cls, target_time: datetime, account_override: str):
"""Generate daily statements for all accounts with settings to generate daily."""
previous_day = get_previous_day(target_time)
statement_settings = StatementSettingsModel.find_accounts_settings_by_frequency(previous_day,
Expand All @@ -74,10 +81,10 @@ def _generate_daily_statements(cls, target_time: datetime):
'endDate': previous_day.strftime('%Y-%m-%d')
}
}
cls._create_statement_records(search_filter, statement_settings)
cls._create_statement_records(search_filter, statement_settings, account_override)

@classmethod
def _generate_weekly_statements(cls, target_time: datetime):
def _generate_weekly_statements(cls, target_time: datetime, account_override: str):
"""Generate weekly statements for all accounts with settings to generate weekly."""
previous_day = get_previous_day(target_time)
statement_settings = StatementSettingsModel.find_accounts_settings_by_frequency(previous_day,
Expand All @@ -91,10 +98,10 @@ def _generate_weekly_statements(cls, target_time: datetime):
}
}

cls._create_statement_records(search_filter, statement_settings)
cls._create_statement_records(search_filter, statement_settings, account_override)

@classmethod
def _generate_monthly_statements(cls, target_time: datetime):
def _generate_monthly_statements(cls, target_time: datetime, account_override: str):
"""Generate monthly statements for all accounts with settings to generate monthly."""
previous_day = get_previous_day(target_time)
statement_settings = StatementSettingsModel.find_accounts_settings_by_frequency(previous_day,
Expand All @@ -108,10 +115,10 @@ def _generate_monthly_statements(cls, target_time: datetime):
}
}

cls._create_statement_records(search_filter, statement_settings)
cls._create_statement_records(search_filter, statement_settings, account_override)

@classmethod
def _create_statement_records(cls, search_filter, statement_settings):
def _create_statement_records(cls, search_filter, statement_settings, account_override: str):
statement_from = None
statement_to = None
if search_filter.get('dateFilter', None):
Expand All @@ -125,7 +132,12 @@ def _create_statement_records(cls, search_filter, statement_settings):
statement_from, statement_to = get_first_and_last_dates_of_month(
search_filter.get('monthFilter').get('month'), search_filter.get('monthFilter').get('year'))
current_app.logger.debug(f'Statements for month: {statement_from.date()} to {statement_to.date()}')
auth_account_ids = [pay_account.auth_account_id for _, pay_account in statement_settings]
if cls.has_account_override:
auth_account_ids = [account_override]
statement_settings = cls._filter_settings_by_override(statement_settings, account_override)
current_app.logger.debug(f'Override Filtered to {len(statement_settings)} accounts to generate statements.')
else:
auth_account_ids = [pay_account.auth_account_id for _, pay_account in statement_settings]
search_filter['authAccountIds'] = auth_account_ids
# Force match on these methods where if the payment method is in matchPaymentMethods, the invoice payment method
# must match the account payment method. Used for EFT so the statements only show EFT invoices and interim
Expand All @@ -136,15 +148,15 @@ def _create_statement_records(cls, search_filter, statement_settings):
cls._clean_up_old_statements(statement_settings, statement_from, statement_to)
current_app.logger.debug('Inserting statements.')
statements = [StatementModel(
frequency=setting.frequency,
statement_settings_id=setting.id,
payment_account_id=pay_account.id,
created_on=get_local_time(datetime.now()),
from_date=statement_from,
to_date=statement_to,
notification_status_code=NotificationStatus.PENDING.value
if pay_account.statement_notification_enabled is True and cls.has_date_override is False
else NotificationStatus.SKIP.value
frequency=setting.frequency,
statement_settings_id=setting.id,
payment_account_id=pay_account.id,
created_on=get_local_time(datetime.now()),
from_date=statement_from,
to_date=statement_to,
notification_status_code=NotificationStatus.PENDING.value
if pay_account.statement_notification_enabled is True and cls.has_date_override is False
else NotificationStatus.SKIP.value
) for setting, pay_account in statement_settings]
# Return defaults which returns the id.
db.session.bulk_save_objects(statements, return_defaults=True)
Expand Down Expand Up @@ -182,3 +194,10 @@ def _clean_up_old_statements(cls, statement_settings, statement_from, statement_
db.session.flush()
delete_statement = delete(StatementModel).where(StatementModel.id.in_(remove_statements_ids))
db.session.execute(delete_statement)

@classmethod
def _filter_settings_by_override(cls, statement_settings, auth_account_id: str):
"""Return filtered Statement settings by payment account."""
return [settings
for settings in statement_settings
if settings.PaymentAccount.auth_account_id == auth_account_id]
6 changes: 3 additions & 3 deletions jobs/payment-jobs/tests/jobs/test_generate_statements.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_statements(session):

# Test date override.
# Override computes for the target date, not the previous date like above.
StatementTask.generate_statements((datetime.utcnow() - timedelta(days=1)).strftime('%Y-%m-%d'))
StatementTask.generate_statements([(datetime.utcnow() - timedelta(days=1)).strftime('%Y-%m-%d')])

statements = Statement.find_all_statements_for_account(auth_account_id=bcol_account.auth_account_id, page=1,
limit=100)
Expand Down Expand Up @@ -180,7 +180,7 @@ def test_bcol_weekly_to_eft_statement(session):
assert monthly_invoice is not None

# Regenerate monthly statement using date override - it will clean up the previous empty monthly statement first
StatementTask.generate_statements((generate_date - timedelta(days=1)).strftime('%Y-%m-%d'))
StatementTask.generate_statements([(generate_date - timedelta(days=1)).strftime('%Y-%m-%d')])

statements = Statement.find_all_statements_for_account(auth_account_id=account.auth_account_id, page=1,
limit=100)
Expand Down Expand Up @@ -258,7 +258,7 @@ def test_bcol_monthly_to_eft_statement(session):
assert monthly_invoice is not None

# Regenerate monthly statement using date override - it will clean up the previous empty monthly statement first
StatementTask.generate_statements((generate_date - timedelta(days=1)).strftime('%Y-%m-%d'))
StatementTask.generate_statements([(generate_date - timedelta(days=1)).strftime('%Y-%m-%d')])

statements = Statement.find_all_statements_for_account(auth_account_id=account.auth_account_id, page=1,
limit=100)
Expand Down
3 changes: 1 addition & 2 deletions pay-api/src/pay_api/services/payment_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,7 @@ def _check_and_update_statement_settings(cls, payment_account: PaymentAccountMod
if payment_account and payment_account.payment_method == PaymentMethod.EFT.value:
# EFT payment method should automatically set statement frequency to MONTHLY
auth_account_id = str(payment_account.auth_account_id)
statements_settings: StatementSettingsModel = StatementSettingsModel\
.find_active_settings(auth_account_id, datetime.today())
statements_settings: StatementSettingsModel = StatementSettingsModel.find_latest_settings(auth_account_id)

if statements_settings is not None and statements_settings.frequency != StatementFrequency.MONTHLY.value:
StatementSettings.update_statement_settings(auth_account_id, StatementFrequency.MONTHLY.value)
Expand Down
2 changes: 1 addition & 1 deletion pay-api/src/pay_api/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
Development release segment: .devN
"""

__version__ = '1.21.0' # pylint: disable=invalid-name
__version__ = '1.21.1' # pylint: disable=invalid-name
111 changes: 109 additions & 2 deletions pay-api/tests/unit/services/test_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@

Test-Suite to ensure that the Statement Service is working as expected.
"""
from datetime import datetime, timezone

from datetime import datetime, timedelta, timezone
from dateutil.relativedelta import relativedelta
from unittest.mock import patch

Expand Down Expand Up @@ -322,6 +321,114 @@ def test_get_monthly_interim_statement(session, admin_users_mock):
assert monthly_invoices[0].invoice_id == monthly_invoice.id


def test_interim_statement_settings_eft(db, session, admin_users_mock):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ken found some anomalies which was more prevalent when swapping payment methods within the same day.
Fix was for this issue and added a test.

"""Assert statement setting properly generated when transitioning to and from EFT payment method."""
account_create_date = datetime(2024, 5, 30, 12, 0)
with freeze_time(account_create_date):
account: PaymentAccountService = PaymentAccountService.create(
get_premium_account_payload(payment_method=PaymentMethod.DRAWDOWN.value))

assert account is not None
assert account.payment_method == PaymentMethod.DRAWDOWN.value

# Confirm initial default settings when account is created
initial_settings: StatementSettingsModel = StatementSettingsModel \
.find_active_settings(str(account.auth_account_id), datetime.today())

assert initial_settings is not None
assert initial_settings.frequency == StatementFrequency.WEEKLY.value
assert initial_settings.from_date == account_create_date.date()
assert initial_settings.to_date is None

update_date = localize_date(datetime(2024, 6, 13, 12, 0))
with freeze_time(update_date):
account = PaymentAccountService.update(account.auth_account_id,
get_eft_enable_account_payload(payment_method=PaymentMethod.EFT.value,
account_id=account.auth_account_id))
# Assert initial settings are properly end dated
assert initial_settings is not None
assert initial_settings.frequency == StatementFrequency.WEEKLY.value
assert initial_settings.from_date == account_create_date.date()
assert initial_settings.to_date == update_date.date()

# Assert new EFT Monthly settings are created
latest_statement_settings: StatementSettingsModel = StatementSettingsModel \
.find_latest_settings(account.auth_account_id)

assert latest_statement_settings is not None
assert latest_statement_settings.id != initial_settings.id
assert latest_statement_settings.frequency == StatementFrequency.MONTHLY.value
assert latest_statement_settings.from_date == (update_date + timedelta(days=1)).date()
assert latest_statement_settings.to_date is None

# Same day payment method change back to DRAWDOWN
update_date = localize_date(datetime(2024, 6, 13, 12, 5))
with freeze_time(update_date):
account = PaymentAccountService.update(account.auth_account_id,
get_premium_account_payload(payment_method=PaymentMethod.DRAWDOWN.value))

latest_statement_settings: StatementSettingsModel = StatementSettingsModel \
.find_latest_settings(account.auth_account_id)

assert latest_statement_settings is not None
assert latest_statement_settings.id != initial_settings.id
assert latest_statement_settings.frequency == StatementFrequency.WEEKLY.value
assert latest_statement_settings.from_date == (update_date + timedelta(days=1)).date()
assert latest_statement_settings.to_date is None

# Same day payment method change back to EFT
update_date = localize_date(datetime(2024, 6, 13, 12, 6))
with freeze_time(update_date):
account = PaymentAccountService.update(account.auth_account_id,
get_eft_enable_account_payload(payment_method=PaymentMethod.EFT.value,
account_id=account.auth_account_id))

latest_statement_settings: StatementSettingsModel = StatementSettingsModel \
.find_latest_settings(account.auth_account_id)

assert latest_statement_settings is not None
assert latest_statement_settings.id != initial_settings.id
assert latest_statement_settings.frequency == StatementFrequency.MONTHLY.value
assert latest_statement_settings.from_date == (update_date + timedelta(days=1)).date()
assert latest_statement_settings.to_date is None

all_settings = (db.session.query(StatementSettingsModel)
.filter(StatementSettingsModel.payment_account_id == account.id)
.order_by(StatementSettingsModel.id)).all()

assert all_settings is not None
assert len(all_settings) == 2

expected_from_date = latest_statement_settings.from_date

# Change payment method to DRAWDOWN 1 day later - should create a new statement settings record
update_date = localize_date(datetime(2024, 6, 14, 12, 6))
with freeze_time(update_date):
account = PaymentAccountService.update(account.auth_account_id,
get_premium_account_payload(payment_method=PaymentMethod.DRAWDOWN.value))

latest_statement_settings: StatementSettingsModel = StatementSettingsModel \
.find_latest_settings(account.auth_account_id)

assert latest_statement_settings is not None
assert latest_statement_settings.id != initial_settings.id
assert latest_statement_settings.frequency == StatementFrequency.WEEKLY.value
assert latest_statement_settings.from_date == (update_date + timedelta(days=1)).date()
assert latest_statement_settings.to_date is None

all_settings = (db.session.query(StatementSettingsModel)
.filter(StatementSettingsModel.payment_account_id == account.id)
.order_by(StatementSettingsModel.id)).all()

assert all_settings is not None
assert len(all_settings) == 3

# Assert previous settings properly end dated
assert all_settings[1].frequency == StatementFrequency.MONTHLY.value
assert all_settings[1].from_date == expected_from_date
assert all_settings[1].to_date == update_date.date()


def get_statement_date_string(datetime_value: datetime) -> str:
"""Get formatted date string for report input."""
date_format = '%Y-%m-%d'
Expand Down
Loading