Skip to content

Commit

Permalink
Merge pull request #838 from bcgov/cfs_rcpt_adjust
Browse files Browse the repository at this point in the history
CFS integration for routing slip write off and refund
  • Loading branch information
sumesh-aot authored Dec 13, 2021
2 parents 07aa80d + 2ffe4dd commit 781c0ea
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 8 deletions.
1 change: 1 addition & 0 deletions jobs/payment-jobs/invoke_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def run(job_name):
elif job_name == 'ROUTING_SLIP':
RoutingSlipTask.link_routing_slips()
RoutingSlipTask.process_nsf()
RoutingSlipTask.adjust_routing_slips()
application.logger.info(f'<<<< Completed Routing Slip tasks >>>>')
elif job_name == 'EJV_PAYMENT':
EjvPaymentTask.create_ejv_file()
Expand Down
2 changes: 1 addition & 1 deletion jobs/payment-jobs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-e git+https://github.com/bcgov/sbc-common-components.git@d9b4616826f8dce6ec16540f24a88bfb530971c2#egg=sbc_common_components&subdirectory=python
-e git+https://github.com/bcgov/sbc-pay.git@b0ccdbee58a4e484a0766fe227157ff57ec45d16#egg=pay_api&subdirectory=pay-api
-e git+https://github.com/bcgov/sbc-pay.git@d40b6d6956155924f24eaf43240b30803ba108ad#egg=pay_api&subdirectory=pay-api
Flask-Caching==1.10.1
Flask-Migrate==2.7.0
Flask-Moment==1.0.2
Expand Down
2 changes: 1 addition & 1 deletion jobs/payment-jobs/requirements/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ pysftp
Flask-Migrate<3

-e git+https://github.com/bcgov/sbc-common-components.git#egg=sbc-common-components&subdirectory=python
-e git+https://github.com/bcgov/sbc-pay.git@main#egg=pay-api&subdirectory=pay-api
-e git+https://github.com/bcgov/sbc-pay.git@cfs_rcpt_adjust#egg=pay-api&subdirectory=pay-api

34 changes: 34 additions & 0 deletions jobs/payment-jobs/tasks/routing_slip_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,40 @@ def process_nsf(cls):
current_app.logger.error(e)
continue

@classmethod
def adjust_routing_slips(cls):
"""Adjust routing slips.
Steps:
1. Adjust routing slip receipts for any Write off routing slips.
2. Adjust routing slip receipts for any Refund approved routing slips.
"""
current_app.logger.info('<<adjust_routing_slips')
adjust_statuses = [RoutingSlipStatus.REFUND_AUTHORIZED.value, RoutingSlipStatus.WRITE_OFF.value]
# For any pending refund/write off balance should be more than $0
routing_slips = db.session.query(RoutingSlipModel) \
.filter(RoutingSlipModel.status.in_(adjust_statuses), RoutingSlipModel.remaining_amount > 0).all()
current_app.logger.info(f'Found {len(routing_slips)} to write off or refund authorized.')
for routing_slip in routing_slips:
# 1.Adjust the routing slip and it's child routing slips for the remaining balance.
current_app.logger.debug(f'Adjusting routing slip {routing_slip.number}')
payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(routing_slip.payment_account_id)
cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id)

# reverse routing slip receipt
# Find all child routing slip and reverse it, as all linked routing slips are also considered as NSF.
child_routing_slips: List[RoutingSlipModel] = RoutingSlipModel.find_children(routing_slip.number)
for rs in (routing_slip, *child_routing_slips):
receipt_number = rs.number
is_refund = routing_slip.status == RoutingSlipStatus.REFUND_AUTHORIZED.value
if rs.parent_number:
receipt_number = f'{receipt_number}L'
# Adjust the receipt to zero in CFS
CFSService.adjust_receipt_to_zero(cfs_account, receipt_number, is_refund)

routing_slip.remaining_amount = 0
routing_slip.save()

@classmethod
def _create_nsf_invoice(cls, cfs_account: CfsAccountModel, rs_number: str,
payment_account: PaymentAccountModel) -> InvoiceModel:
Expand Down
2 changes: 1 addition & 1 deletion jobs/payment-jobs/tests/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ services:
image: stoplight/prism:3.3.0
command: >
mock -p 4010 --host 0.0.0.0
https://raw.githubusercontent.com/bcgov/sbc-pay/cfs_rs_links/docs/docs/PayBC%20Mocking/paybc-1.0.0.yaml
https://raw.githubusercontent.com/bcgov/sbc-pay/main/docs/docs/PayBC%20Mocking/paybc-1.0.0.yaml
auth:
image: stoplight/prism:3.3.0
command: >
Expand Down
3 changes: 2 additions & 1 deletion jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
Test-Suite to ensure that the CreateAccountTask is working as expected.
"""
from unittest.mock import patch

import requests
from requests.exceptions import HTTPError
from pay_api.models import CfsAccount, PaymentAccount
from pay_api.services.online_banking_service import OnlineBankingService
from pay_api.services.pad_service import PadService
from pay_api.utils.enums import CfsAccountStatus
from requests.exceptions import HTTPError

from tasks.cfs_create_account_task import CreateAccountTask
from utils import mailer
Expand Down
25 changes: 25 additions & 0 deletions jobs/payment-jobs/tests/jobs/test_routing_slip_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"""
from unittest.mock import patch

import pytest
from pay_api.models import CfsAccount as CfsAccountModel
from pay_api.models import FeeSchedule as FeeScheduleModel
from pay_api.models import Invoice as InvoiceModel
Expand Down Expand Up @@ -200,3 +201,27 @@ def test_link_to_nsf_rs(session):

# Now the invoice status should be PAID as RS has recovered.
assert InvoiceModel.find_by_id(invoice.id).invoice_status_code == InvoiceStatus.PAID.value


@pytest.mark.parametrize('rs_status', [RoutingSlipStatus.WRITE_OFF.value, RoutingSlipStatus.REFUND_AUTHORIZED.value])
def test_receipt_adjustments(session, rs_status):
"""Test routing slip adjustments."""
child_rs_number = '1234'
parent_rs_number = '89799'
factory_routing_slip_account(number=child_rs_number, status=CfsAccountStatus.ACTIVE.value)
factory_routing_slip_account(number=parent_rs_number, status=CfsAccountStatus.ACTIVE.value, total=10,
remaining_amount=10)
child_rs = RoutingSlipModel.find_by_number(child_rs_number)
parent_rs = RoutingSlipModel.find_by_number(parent_rs_number)
# Do Link
child_rs.status = RoutingSlipStatus.LINKED.value
child_rs.parent_number = parent_rs.number
child_rs.save()

parent_rs.status = rs_status

with patch('pay_api.services.CFSService.adjust_receipt_to_zero'):
RoutingSlipTask.adjust_routing_slips()

parent_rs = RoutingSlipModel.find_by_number(parent_rs.number)
assert parent_rs.remaining_amount == 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""fas_routing_slip_status_updates
Revision ID: aa9207187d6b
Revises: b336780735dc
Create Date: 2021-12-10 10:28:11.033487
"""
from alembic import op
import sqlalchemy as sa

import sqlalchemy as sa
from alembic import op
from sqlalchemy import String
from sqlalchemy.sql import column, table

# revision identifiers, used by Alembic.
revision = 'aa9207187d6b'
down_revision = 'b336780735dc'
branch_labels = None
depends_on = None


def upgrade():
rs_status_table = table('routing_slip_status_codes',
column('code', sa.String),
column('description', sa.String),
)
op.bulk_insert(
rs_status_table,
[
{'code': 'WRITE_OFF', 'description': 'Write Off'},
{'code': 'REFUND_REJECTED', 'description': 'Refund Rejected'}
]
)
op.execute("delete from routing_slip_status_codes where code in ('BOUNCED', 'REFUND');")


def downgrade():
op.execute("delete from routing_slip_status_codes where code in ('WRITE_OFF', 'REFUND_REJECTED');")
29 changes: 29 additions & 0 deletions pay-api/src/pay_api/services/cfs_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,35 @@ def create_cms(cls, line_items: List[PaymentLineItemModel], cfs_account: CfsAcco
current_app.logger.debug('>Received CMS response')
return cms_response.json()

@classmethod
def adjust_receipt_to_zero(cls, cfs_account: CfsAccountModel, receipt_number: str, is_refund: bool = False):
"""Adjust Receipt in CFS to bring it down to zero.
1. Query the receipt and check if balance is more than zero.
2. Adjust the receipt with activity name corresponding to refund or write off.
"""
current_app.logger.debug('<adjust_receipt_to_zero: %s %s', cfs_account, receipt_number)
access_token: str = CFSService.get_token().json().get('access_token')
cfs_base: str = current_app.config.get('CFS_BASE_URL')
receipt_url = f'{cfs_base}/cfs/parties/{cfs_account.cfs_party}/accs/{cfs_account.cfs_account}/' \
f'sites/{cfs_account.cfs_site}/rcpts/{receipt_number}/'
adjustment_url = f'{receipt_url}adjustment'
current_app.logger.debug('Receipt Adjustment URL %s', adjustment_url)

receipt_response = cls.get(receipt_url, access_token, AuthHeaderType.BEARER, ContentType.JSON)
current_app.logger.info(f"Balance on {receipt_number} - {receipt_response.json().get('unapplied_amount')}")
if (unapplied_amount := float(receipt_response.json().get('unapplied_amount', 0))) > 0:
adjustment = dict(
activity_name='Refund Adjustment FAS' if is_refund else 'Write-off Adjustment FAS',
adjustment_amount=str(unapplied_amount)
)

cls.post(adjustment_url, access_token, AuthHeaderType.BEARER, ContentType.JSON, adjustment)
receipt_response = cls.get(receipt_url, access_token, AuthHeaderType.BEARER, ContentType.JSON)
current_app.logger.info(f"Balance on {receipt_number} - {receipt_response.json().get('unapplied_amount')}")

current_app.logger.debug('>adjust_receipt_to_zero')


def get_non_null_value(value: str, default_value: str):
"""Return non null value for the value by replacing default value."""
Expand Down
3 changes: 1 addition & 2 deletions pay-api/src/pay_api/utils/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,15 +211,14 @@ class RoutingSlipStatus(Enum):

ACTIVE = 'ACTIVE'
COMPLETE = 'COMPLETE'
BOUNCED = 'BOUNCED'
NSF = 'NSF'
REFUND = 'REFUND'
LAST = 'LAST'
LINKED = 'LINKED'
REFUND_REQUESTED = 'REFUND_REQUESTED'
REFUND_AUTHORIZED = 'REFUND_AUTHORIZED'
REFUND_REJECTED = 'REFUND_REJECTED'
REFUND_COMPLETED = 'REFUND_COMPLETED'
WRITE_OFF = 'WRITE_OFF'


class PatchActions(Enum):
Expand Down
4 changes: 2 additions & 2 deletions pay-api/tests/unit/api/fas/test_routing_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,12 +426,12 @@ def test_update_routing_slip_status(client, jwt, app):

# assert invalid action.
rv = client.patch(f'/api/v1/fas/routing-slips/{rs_number}?action=TEST',
data=json.dumps({'status': RoutingSlipStatus.BOUNCED.value}), headers=headers)
data=json.dumps({'status': RoutingSlipStatus.ACTIVE.value}), headers=headers)
assert rv.status_code == 400

# Assert invalid number
rv = client.patch(f'/api/v1/fas/routing-slips/TEST?action={PatchActions.UPDATE_STATUS.value}',
data=json.dumps({'status': RoutingSlipStatus.BOUNCED.value}), headers=headers)
data=json.dumps({'status': RoutingSlipStatus.ACTIVE.value}), headers=headers)
assert rv.status_code == 400


Expand Down

0 comments on commit 781c0ea

Please sign in to comment.