diff --git a/ppr-api/report-templates/template-parts/registration/securitiesActNotice.html b/ppr-api/report-templates/template-parts/registration/securitiesActNotice.html index 17be91439..5a5c8ff59 100644 --- a/ppr-api/report-templates/template-parts/registration/securitiesActNotice.html +++ b/ppr-api/report-templates/template-parts/registration/securitiesActNotice.html @@ -56,7 +56,7 @@ {% for notice in change.addSecuritiesActNotices %}
{{ notice.registrationDescription|title }} - {% if notice.amendNoticeId is defined%}CHANGED{% else %}ADDED{% endif %} + {% if notice.amendNoticeId is defined%}AMENDED{% else %}ADDED{% endif %}
@@ -70,7 +70,16 @@ {% if notice.securitiesActOrders %} {% for order in notice.securitiesActOrders %} -
{% if order.courtOrder %}Court Order {% else %}Security Commission Order{% endif %}
+
+ {% if order.courtOrder %}Court Order {% else %}Security Commission Order{% endif %} + {% if notice.amendNoticeId is defined and order.amendDeleted is defined %} + DELETED + {% elif notice.amendNoticeId is defined and order.amendOrderId is defined and order.unchanged is not defined %} + AMENDED + {% elif order.unchanged is not defined %} + ADDED + {% endif %} +
{% if order.courtName is defined %} @@ -120,7 +129,7 @@ {% if notice.securitiesActOrders %} {% for order in notice.securitiesActOrders %} -
{% if order.courtOrder %}Court Order {% else %}Security Commission Order{% endif %}
+
{% if order.courtOrder %}Court Order {% else %}Security Commission Order{% endif %} DELETED
{% if order.courtName is defined %} diff --git a/ppr-api/report-templates/template-parts/search-result/securitiesActNotice.html b/ppr-api/report-templates/template-parts/search-result/securitiesActNotice.html index 53862d549..8333ce3af 100644 --- a/ppr-api/report-templates/template-parts/search-result/securitiesActNotice.html +++ b/ppr-api/report-templates/template-parts/search-result/securitiesActNotice.html @@ -56,7 +56,7 @@ {% for notice in change.addSecuritiesActNotices %}
{{ notice.registrationDescription|title }} - {% if notice.amendNoticeId is defined%}CHANGED{% else %}ADDED{% endif %} + {% if notice.amendNoticeId is defined%}AMENDED{% else %}ADDED{% endif %}
@@ -70,7 +70,16 @@ {% if notice.securitiesActOrders %} {% for order in notice.securitiesActOrders %} -
{% if order.courtOrder %}Court Order {% else %}Security Commission Order{% endif %}
+
+ {% if order.courtOrder %}Court Order {% else %}Security Commission Order{% endif %} + {% if notice.amendNoticeId is defined and order.amendDeleted is defined %} + DELETED + {% elif notice.amendNoticeId is defined and order.amendOrderId is defined and order.unchanged is not defined %} + AMENDED + {% elif order.unchanged is not defined %} + ADDED + {% endif %} +
{% if order.courtName is defined %} @@ -120,7 +129,7 @@ {% if notice.securitiesActOrders %} {% for order in notice.securitiesActOrders %} -
{% if order.courtOrder %}Court Order {% else %}Security Commission Order{% endif %}
+
{% if order.courtOrder %}Court Order {% else %}Security Commission Order{% endif %} DELETED
{% if order.courtName is defined %} diff --git a/ppr-api/src/database/patch/15187-ppr-securities-act.sql b/ppr-api/src/database/patch/15187-ppr-securities-act.sql index c287d1bbc..8f9a60cc9 100644 --- a/ppr-api/src/database/patch/15187-ppr-securities-act.sql +++ b/ppr-api/src/database/patch/15187-ppr-securities-act.sql @@ -47,6 +47,7 @@ CREATE TABLE public.securities_act_orders ( court_registry VARCHAR (64) NULL, file_number VARCHAR (20) NULL, effect_of_order VARCHAR (512) NULL, + previous_order_id INTEGER NULL, FOREIGN KEY (securities_act_notice_id) REFERENCES securities_act_notices (id), FOREIGN KEY (registration_id) @@ -57,4 +58,10 @@ CREATE TABLE public.securities_act_orders ( CREATE INDEX ix_sec_orders_sec_id ON public.securities_act_orders USING btree (securities_act_notice_id); CREATE INDEX ix_sec_orders_registration_id ON public.securities_act_orders USING btree (registration_id); CREATE INDEX ix_sec_orders_change_registration_id ON public.securities_act_orders USING btree (registration_id_end); + +INSERT INTO registration_types(registration_type, registration_type_cl, registration_desc, registration_act) VALUES +('A1', 'AMENDMENT', 'AMENDMENT - NOTICE ADDED', 'SECURITIES ACT'), +('A2', 'AMENDMENT', 'AMENDMENT - NOTICE REMOVEd', 'SECURITIES ACT'), +('A3', 'AMENDMENT', 'AMENDMENT - NOTICE AMENDED', 'SECURITIES ACT') +; -- 15178 end release 1.2.5 diff --git a/ppr-api/src/ppr_api/models/securities_act_order.py b/ppr-api/src/ppr_api/models/securities_act_order.py index 9efac8aaf..d1aae9fc7 100644 --- a/ppr-api/src/ppr_api/models/securities_act_order.py +++ b/ppr-api/src/ppr_api/models/securities_act_order.py @@ -29,6 +29,8 @@ class SecuritiesActOrder(db.Model): file_number = db.mapped_column('file_number', db.String(20), nullable=True) effect_of_order = db.mapped_column('effect_of_order', db.String(512), nullable=True) registration_id_end = db.mapped_column('registration_id_end', db.Integer, nullable=True, index=True) + # For amendment distinguishing notice edit from remove/add + previous_order_id = db.mapped_column('previous_order_id', db.Integer, nullable=True) # parent keys registration_id = db.mapped_column('registration_id', db.Integer, @@ -50,6 +52,7 @@ class SecuritiesActOrder(db.Model): def json(self) -> dict: """Return the court_order as a json object.""" order = { + 'orderId': self.id, 'courtOrder': self.court_order_ind == 'Y' } if self.court_name: @@ -62,6 +65,8 @@ def json(self) -> dict: order['orderDate'] = format_ts(self.order_date) if self.effect_of_order: order['effectOfOrder'] = self.effect_of_order + if self.previous_order_id is not None: + order['amendOrderId'] = self.previous_order_id return order @classmethod @@ -100,5 +105,6 @@ def create_from_json(json_data, registration_id: int, notice_id: int): order.order_date = ts_from_date_iso_format(json_data['orderDate']) if json_data.get('effectOfOrder'): order.effect_of_order = json_data['effectOfOrder'] - + if json_data.get('amendOrderId'): + order.previous_order_id = json_data.get('amendOrderId') return order diff --git a/ppr-api/src/ppr_api/models/utils.py b/ppr-api/src/ppr_api/models/utils.py index 2c5486c61..3055aef87 100644 --- a/ppr-api/src/ppr_api/models/utils.py +++ b/ppr-api/src/ppr_api/models/utils.py @@ -69,6 +69,9 @@ REG_TYPE_AMEND_PARIAL_DISCHARGE = 'AP' REG_TYPE_AMEND_SP_TRANSFER = 'AS' REG_TYPE_AMEND_SUBSTITUTION_COLLATERAL = 'AU' +REG_TYPE_AMEND_SECURITIES_ADD = 'A1' +REG_TYPE_AMEND_SECURITIES_DELETE = 'A2' +REG_TYPE_AMEND_SECURITIES = 'A3' SEARCH_MATCH_EXACT = 'EXACT' SEARCH_MATCH_SIMILAR = 'SIMILAR' @@ -802,14 +805,28 @@ def cleanup_amendment(json_data): return json_data -def amendment_change_type(json_data): +def amendment_securities_change_type(json_data: dict) -> str: + """Try to assign a securities act notice specific amendment change type based on the request data.""" + if json_data.get('addVehicleCollateral') or json_data.get('deleteVehicleCollateral') or \ + json_data.get('addGeneralCollateral') or json_data.get('deleteGeneralCollateral') or \ + json_data.get('addDebtors') or json_data.get('deleteDebtors'): + return REG_TYPE_AMEND + if json_data.get('addSecuritiesActNotices') and not json_data.get('deleteSecuritiesActNotices'): + return REG_TYPE_AMEND_SECURITIES_ADD + if not json_data.get('addSecuritiesActNotices') and json_data.get('deleteSecuritiesActNotices'): + return REG_TYPE_AMEND_SECURITIES_DELETE + return REG_TYPE_AMEND_SECURITIES + + +def amendment_change_type(json_data: dict) -> str: # pylint: disable=too-many-boolean-expressions """Try to assign a more specific amendment change type based on the request data.""" if 'courtOrderInformation' in json_data: return REG_TYPE_AMEND_COURT - if 'addTrustIndenture' in json_data or 'removeTrustIndenture' in json_data or \ - json_data.get('deleteSecuritiesActNotices') or json_data.get('addSecuritiesActNotices'): + if 'addTrustIndenture' in json_data or 'removeTrustIndenture' in json_data: return REG_TYPE_AMEND + if json_data.get('deleteSecuritiesActNotices') or json_data.get('addSecuritiesActNotices'): + return amendment_securities_change_type(json_data) change_type = json_data['changeType'] if 'addVehicleCollateral' not in json_data and 'deleteVehicleCollateral' not in json_data and \ 'addGeneralCollateral' not in json_data and 'deleteGeneralCollateral' not in json_data: diff --git a/ppr-api/src/ppr_api/reports/v2/report_utils.py b/ppr-api/src/ppr_api/reports/v2/report_utils.py index e17484fba..e73d62340 100755 --- a/ppr-api/src/ppr_api/reports/v2/report_utils.py +++ b/ppr-api/src/ppr_api/reports/v2/report_utils.py @@ -93,6 +93,9 @@ } # Map post go-live amendment descriptions TO_AMEND_TYPE_DESCRIPTION = { + 'A1': 'Amendment - Notice Added', + 'A2': 'Amendment - Notice Removed', + 'A3': 'Amendment - Notice Amended', 'AA': 'Amendment - Collateral Added', 'AM': 'Amendment', 'CO': 'Amendment - Court Order', @@ -665,14 +668,57 @@ def set_notice_date_time(notice: dict): order['orderDate'] = to_report_datetime(order['orderDate'], False) +def is_order_unchanged(order1: dict, order2: dict) -> bool: + """Determine if 2 Securities Act Notice orders are the same.""" + if order1['courtOrder'] != order2['courtOrder']: + return False + if order1.get('fileNumber') != order2.get('fileNumber') or order1.get('orderDate') != order2.get('orderDate'): + return False + if order1.get('courtOrder') and order2.get('courtOrder') and \ + (order1.get('courtName') != order2.get('courtName') or + order1.get('courtRegistry') != order2.get('courtRegistry')): + return False + return (not order1.get('effectOfOrder') and not order2.get('effectOfOrder')) or \ + (order1.get('effectOfOrder') == order2.get('effectOfOrder')) + + +def set_modified_order(add_notice: dict, del_notice: dict): + """Conditionally set amended notice order unchanged flag.""" + for add_order in add_notice.get('securitiesActOrders'): + if add_order.get('amendOrderId'): + for del_order in del_notice.get('securitiesActOrders'): + if del_order.get('orderId') and del_order.get('orderId') == add_order.get('amendOrderId') and \ + is_order_unchanged(add_order, del_order): + # If identical mark as unchanged + add_order['unchanged'] = True + # Amended notice deleted orders added to new orders but marked as deleted. + merged_orders = [] + for add_order in add_notice.get('securitiesActOrders'): + if add_order.get('amendOrderId'): + for del_order in del_notice.get('securitiesActOrders'): + if del_order.get('orderId') and del_order.get('orderId') != add_order.get('amendOrderId'): + order = copy.deepcopy(del_order) + order['amendDeleted'] = True + merged_orders.append(order) + if merged_orders: + for add_order in add_notice.get('securitiesActOrders'): + merged_orders.append(add_order) + add_notice['securitiesActOrders'] = merged_orders + + def set_modified_notice(statement): """Conditionally set amendment deleted notice edited flag.""" if statement.get('deleteSecuritiesActNotices') and statement.get('addSecuritiesActNotices'): for add_notice in statement['addSecuritiesActNotices']: + del_notice = None for delete_notice in statement['deleteSecuritiesActNotices']: if add_notice.get('reg_id') and delete_notice.get('reg_id') and \ add_notice['reg_id'] == delete_notice['reg_id'] and 'edit' not in delete_notice: if add_notice.get('amendNoticeId', 0) > 0 and \ add_notice['amendNoticeId'] == delete_notice.get('noticeId'): delete_notice['edit'] = True + del_notice = delete_notice break + # Amend order check + if del_notice and del_notice.get('securitiesActOrders') and add_notice.get('securitiesActOrders'): + set_modified_order(add_notice, del_notice) diff --git a/ppr-api/tests/unit/api/test_user_profile.py b/ppr-api/tests/unit/api/test_user_profile.py index 35ddbfafe..caeaa8ee8 100644 --- a/ppr-api/tests/unit/api/test_user_profile.py +++ b/ppr-api/tests/unit/api/test_user_profile.py @@ -20,6 +20,7 @@ import pytest +from ppr_api.models import UserProfile from ppr_api.services.authz import ( STAFF_ROLE, PPR_ROLE, @@ -101,6 +102,11 @@ def test_get_user_profile_agreement(session, client, jwt, desc, account_id, idp_ else: headers = create_header_account(jwt, roles) + if agreement_required: + profile: UserProfile = UserProfile.find_by_id(int(idp_userid)) + if profile and not profile.service_agreements.get('acceptAgreementRequired'): + profile.service_agreements['acceptAgreementRequired'] = True + # test rv = client.get('/api/v1/user-profile', headers=headers) # check diff --git a/ppr-api/tests/unit/models/test_securities_act_order.py b/ppr-api/tests/unit/models/test_securities_act_order.py index 6e043527a..aedc8e228 100644 --- a/ppr-api/tests/unit/models/test_securities_act_order.py +++ b/ppr-api/tests/unit/models/test_securities_act_order.py @@ -92,23 +92,28 @@ def test_securities_act_json(session): registry = 'registry' file = 'file' effect = 'effect' + order_id = 10001 + amend_id = 12345 order = SecuritiesActOrder( - id=10001, + id=order_id, securities_act_notice_id=10001, court_order_ind='Y', court_name=name, order_date = now, court_registry = registry, file_number=file, - effect_of_order=effect + effect_of_order=effect, + previous_order_id=amend_id ) order_json = { + 'orderId': order_id, 'courtOrder': True, 'courtName': name, 'courtRegistry': registry, 'fileNumber': file, 'orderDate': model_utils.format_ts(now), - 'effectOfOrder': effect + 'effectOfOrder': effect, + 'amendOrderId': amend_id } assert order.json == order_json diff --git a/ppr-api/tests/unit/models/test_user_profile.py b/ppr-api/tests/unit/models/test_user_profile.py index 2d496ff64..e99da4f46 100644 --- a/ppr-api/tests/unit/models/test_user_profile.py +++ b/ppr-api/tests/unit/models/test_user_profile.py @@ -18,6 +18,8 @@ """ import pytest +from flask import current_app + from ppr_api.models import User, UserProfile @@ -283,6 +285,6 @@ def test_find_service_agreements(session, client, jwt, id, agreement_required): if profile: if agreement_required: assert profile.service_agreements - assert profile.service_agreements.get('acceptAgreementRequired') + assert 'acceptAgreementRequired' in profile.service_agreements else: assert not profile.service_agreements or not profile.service_agreements.get('acceptAgreementRequired') diff --git a/ppr-api/tests/unit/models/test_utils.py b/ppr-api/tests/unit/models/test_utils.py index e6e1c8506..de17391e7 100644 --- a/ppr-api/tests/unit/models/test_utils.py +++ b/ppr-api/tests/unit/models/test_utils.py @@ -23,6 +23,53 @@ from ppr_api.models import utils as model_utils, Registration, SearchRequest +AMENDMENT_SE = { + 'baseRegistrationNumber': 'TEST0022', + 'debtorName': { + 'businessName': 'TEST 22 DEBTOR INC.' + }, + 'authorizationReceived': True, + 'registeringParty': { + 'businessName': 'ABC SEARCHING COMPANY', + 'address': { + 'street': '222 SUMMER STREET', + 'city': 'VICTORIA', + 'region': 'BC', + 'country': 'CA', + 'postalCode': 'V8W 2V8' + }, + 'emailAddress': 'bsmith@abc-search.com' + }, + 'changeType': 'AM', + 'description': 'Test amendment.', + 'deleteSecuritiesActNotices': [ + { + 'noticeId': 200000000, + 'securitiesActNoticeType': 'PRESERVATION' + } + ], + 'addSecuritiesActNotices': [ + { + 'amendNoticeId': 200000000, + 'securitiesActNoticeType': 'PRESERVATION', + 'effectiveDateTime': '2024-04-22T06:59:59+00:00', + 'securitiesActOrders': [ + { + 'courtOrder': True, + 'courtName': 'court name', + 'courtRegistry': 'registry', + 'fileNumber': 'filenumber', + 'orderDate': '2024-04-22T06:59:59+00:00', + 'effectOfOrder': 'Effect of order summary.' + } + ] + }, + { + 'securitiesActNoticeType': 'PROCEEDINGS', + 'effectiveDateTime': '2024-05-13T06:59:59+00:00' + } + ] +} # testdata pattern is ({registration_ts}, {years}, {expiry_ts}) TEST_DATA_EXPIRY = [ ('2021-08-31T00:00:01-07:00', 1, '2022-09-01T06:59:59+00:00'), @@ -130,6 +177,15 @@ (model_utils.REG_TYPE_AMEND_PARIAL_DISCHARGE, False), (model_utils.REG_TYPE_AMEND_SP_TRANSFER, False) ] +# testdata pattern is ({change_type}, {add_notice}, {delete_notice}, {has_vc}, {has_gc}, {has_debtor}) +TEST_DATA_AMENDMENT_CHANGE_TYPE_SE = [ + (model_utils.REG_TYPE_AMEND, True, True, True, False, False), + (model_utils.REG_TYPE_AMEND, True, True, False, True, False), + (model_utils.REG_TYPE_AMEND, True, True, False, False, True), + (model_utils.REG_TYPE_AMEND_SECURITIES_ADD, True, False, False, False, False), + (model_utils.REG_TYPE_AMEND_SECURITIES_DELETE, False, True, False, False, False), + (model_utils.REG_TYPE_AMEND_SECURITIES, True, True, False, False, False) +] # testdata pattern is ({desc}, {reg_num}, {doc_name}) TEST_DATA_DOC_STORAGE_NAME = [ ('Financing', 'TEST0001', 'ppsalien-200000000-TEST0001.pdf'), @@ -392,6 +448,28 @@ def test_amendment_change_type(change_type, is_general_collateral): assert type == change_type +@pytest.mark.parametrize('change_type,add_notice,delete_notice,has_vc,has_gc,has_debtor', + TEST_DATA_AMENDMENT_CHANGE_TYPE_SE) +def test_amendment_change_type_se(change_type, add_notice, delete_notice, has_vc, has_gc, has_debtor): + """Assert that setting the securities act notice amendment change type works as expected.""" + json_data = copy.deepcopy(AMENDMENT_SE) + if not add_notice: + del json_data['addSecuritiesActNotices'] + if not delete_notice: + del json_data['deleteSecuritiesActNotices'] + if has_vc: + json_data['addVehicleCollateral'] = copy.deepcopy(AMENDMENT_STATEMENT.get('addVehicleCollateral')) + json_data['deleteVehicleCollateral'] = copy.deepcopy(AMENDMENT_STATEMENT.get('deleteVehicleCollateral')) + if has_gc: + json_data['addGeneralCollateral'] = copy.deepcopy(AMENDMENT_STATEMENT.get('addGeneralCollateral')) + json_data['deleteGeneralCollateral'] = copy.deepcopy(AMENDMENT_STATEMENT.get('deleteGeneralCollateral')) + if has_debtor: + json_data['addDebtors'] = copy.deepcopy(AMENDMENT_STATEMENT.get('addDebtors')) + + type = model_utils.amendment_change_type(json_data) + assert type == change_type + + def test_cleanup_amendment(): """Assert that removing empty lists/arrays from amendment data works as expected.""" json_data = copy.deepcopy(AMENDMENT_STATEMENT)