diff --git a/queue_services/business-filer/poetry.lock b/queue_services/business-filer/poetry.lock index 337e0468ab..f7fc95e170 100644 --- a/queue_services/business-filer/poetry.lock +++ b/queue_services/business-filer/poetry.lock @@ -113,7 +113,7 @@ files = [ [[package]] name = "business-model" -version = "3.3.20" +version = "3.3.22" description = "" optional = false python-versions = ">=3.13,<3.14" @@ -140,7 +140,7 @@ sql-versioning = {git = "https://github.com/bcgov/lear.git", rev = "main", subdi type = "git" url = "https://github.com/bcgov/lear.git" reference = "main" -resolved_reference = "b029c78337ef6bcc83d91b335185b4a770cb1d33" +resolved_reference = "6ad7e1989f612f541c3468d459ccfff784bb6a29" subdirectory = "python/common/business-registry-model" [[package]] diff --git a/queue_services/business-filer/src/business_filer/filing_processors/change_of_liquidators.py b/queue_services/business-filer/src/business_filer/filing_processors/change_of_liquidators.py index 061e9654e0..e05bc25cb2 100644 --- a/queue_services/business-filer/src/business_filer/filing_processors/change_of_liquidators.py +++ b/queue_services/business-filer/src/business_filer/filing_processors/change_of_liquidators.py @@ -33,8 +33,10 @@ # POSSIBILITY OF SUCH DAMAGE. """File processing rules and actions for change of liquidators filings.""" import copy +from datetime import UTC, datetime from business_model.models import Business, Filing, PartyRole +from business_model.utils.legislation_datetime import LegislationDatetime from business_filer.filing_meta import FilingMeta from business_filer.filing_processors.filing_components.filings import update_filing_court_order @@ -46,20 +48,33 @@ ) +def _init_liquidation(business: Business, filing_meta: FilingMeta): + """Put the business into liquidation.""" + if not business.in_liquidation: + business.in_liquidation = True + business.in_liquidation_date = filing_meta.application_date or datetime.now(UTC) + + +def _update_last_lr_year(business: Business): + """Update the business last liquidation report year.""" + if business.last_lr_year: + business.last_lr_year += 1 + else: + in_liquidation_date = LegislationDatetime.as_legislation_timezone(business.in_liquidation_date).date() + business.last_lr_year = in_liquidation_date.year + 1 + + def process(business: Business, filing_rec: Filing, filing_meta: FilingMeta): """Render the changeOfLiquidators onto the business model objects.""" filing_json = copy.deepcopy(filing_rec.filing_json) relationships = filing_json["filing"]["changeOfLiquidators"].get("relationships", []) offices = filing_json["filing"]["changeOfLiquidators"].get("offices", {}) - if filing_rec.filing_sub_type == "intentToLiquidate": + if filing_rec.filing_sub_type in ["intentToLiquidate", "appointLiquidator"]: create_relationships(relationships, business, filing_rec) update_or_create_offices(business, offices) - business.in_liquidation = True + _init_liquidation(business, filing_meta) - elif filing_rec.filing_sub_type == "appointLiquidator": - create_relationships(relationships, business, filing_rec) - elif filing_rec.filing_sub_type == "ceaseLiquidator": cease_relationships(relationships, business, [PartyRole.RoleTypes.LIQUIDATOR.value], filing_meta.application_date) @@ -68,8 +83,7 @@ def process(business: Business, filing_rec: Filing, filing_meta: FilingMeta): update_or_create_offices(business, offices) elif filing_rec.filing_sub_type == "liquidationReport": - # FUTURE: updates for this will be made as part of #31714 - pass + _update_last_lr_year(business) # update court order, if any is present if court_order := filing_json["filing"]["changeOfLiquidators"].get("courtOrder"): diff --git a/queue_services/business-filer/src/business_filer/filing_processors/intent_to_liquidate.py b/queue_services/business-filer/src/business_filer/filing_processors/intent_to_liquidate.py deleted file mode 100644 index bbde2f6f04..0000000000 --- a/queue_services/business-filer/src/business_filer/filing_processors/intent_to_liquidate.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright © 2025 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""File processing rules and actions for the Intent to Liquidate filing.""" -from business_model.models import Business, Comment, Filing -from flask import current_app - -from business_filer.exceptions import QueueException -from business_filer.filing_meta import FilingMeta -from business_filer.filing_processors.filing_components import filings - - -def process(business: Business, - filing: dict, - filing_rec: Filing, - filing_meta: FilingMeta): - """Render the intent to liquidate filing unto the model objects.""" - if not (intent_to_liquidate := filing.get("intentToLiquidate")): - current_app.logger.error("Could not find intentToLiquidate in: %s", filing) - raise QueueException(f"legal_filing:intentToLiquidate missing from {filing}") - - current_app.logger.debug("processing intentToLiquidate: %s", filing) - - liquidation_date = intent_to_liquidate.get("dateOfCommencementOfLiquidation") - - filing_meta.intent_to_liquidate = {} - filing_meta.intent_to_liquidate = { - **filing_meta.intent_to_liquidate, - "dateOfCommencementOfLiquidation": liquidation_date - } - - # Add comment about liquidation date - filing_rec.comments.append( - Comment( - comment=f"Liquidation is scheduled to commence on {liquidation_date}.", - staff_id=filing_rec.submitter_id - ) - ) - - # Update business in_liquidation flag - business.in_liquidation = True - - # update court order, if any is present - if court_order := intent_to_liquidate.get("courtOrder"): - filings.update_filing_court_order(filing_rec, court_order) diff --git a/queue_services/business-filer/src/business_filer/services/filer.py b/queue_services/business-filer/src/business_filer/services/filer.py index c4a1336e89..6a3a3d4b5d 100644 --- a/queue_services/business-filer/src/business_filer/services/filer.py +++ b/queue_services/business-filer/src/business_filer/services/filer.py @@ -67,7 +67,6 @@ court_order, dissolution, incorporation_filing, - intent_to_liquidate, notice_of_withdrawal, put_back_off, put_back_on, @@ -222,9 +221,6 @@ def process_filing(filing_message: FilingMessage): # noqa: PLR0915, PLR0912 filing_submission.json, filing_submission, filing_meta) - - case "intentToLiquidate": - intent_to_liquidate.process(business, filing, filing_submission, filing_meta) case "noticeOfWithdrawal": notice_of_withdrawal.process(filing_submission, filing, filing_meta) diff --git a/queue_services/business-filer/tests/unit/test_filer/test_change_of_liquidators.py b/queue_services/business-filer/tests/unit/test_filer/test_change_of_liquidators.py index b9613737bc..ffbe915c48 100644 --- a/queue_services/business-filer/tests/unit/test_filer/test_change_of_liquidators.py +++ b/queue_services/business-filer/tests/unit/test_filer/test_change_of_liquidators.py @@ -122,6 +122,7 @@ def _assert_party_roles_addresses(party_roles, party_id, expected_delivery, expe return def _assert_office_addresses(offices, expected_office): + # Should have registered office and liquidations office assert len(offices) == 2 has_liquidation_office = False @@ -140,24 +141,38 @@ def _assert_office_addresses(offices, expected_office): assert address.street == expected_mailing_street assert has_liquidation_office -def test_process_col_filing(app, session, mocker): - """Assert that all COL filings can be applied to the model correctly.""" - payment_id = str(random.SystemRandom().getrandbits(0x58)) - effective_date = datetime(2023, 10, 10, 10, 0, 0, tzinfo=timezone.utc) - identifier = f'BC{random.randint(1000000, 9999999)}' - drs_publish_mock = mocker.patch('business_filer.services.gcp_queue.publish', return_value=None) - - business = create_business(identifier) +def _assert_common_data(business: Business, filing: Filing, expected_date, expected_lr_year, expected_next_lr_yr): + """Assert the expected common data was updated by the liquidation filing processing.""" + assert filing.transaction_id + assert filing.business_id == business.id + assert filing.status == Filing.Status.COMPLETED.value + assert business.in_liquidation == True + assert business.in_liquidation_date == expected_date + assert business.last_lr_year == expected_lr_year + assert business.next_lr_min_date.year == expected_next_lr_yr +def _get_liquidation_filing(sub_type: str, effective_date: datetime, identifier = 'BC1234567'): + """Return a valid change of liquidators filing for the sub type.""" + payment_id = str(random.SystemRandom().getrandbits(0x58)) filing = copy.deepcopy(FILING_TEMPLATE) filing['filing']['header']['name'] = 'changeOfLiquidators' filing['filing']['header']['effectiveDate'] = effective_date.isoformat() filing['filing']['business']['identifier'] = identifier filing['filing']['business']['legalType'] = 'BC' filing['filing']['changeOfLiquidators'] = copy.deepcopy(CHANGE_OF_LIQUIDATORS_INTENT) + return filing, payment_id, identifier + + +def test_process_col_filing(app, session, mocker): + """Assert that all COL filings can be applied to the model correctly.""" + # NOTE: this is actually in 2023 pacific time + expected_in_liquidation_date = datetime(2024, 1, 1, 1, 0, 0, tzinfo=timezone.utc) + drs_publish_mock = mocker.patch('business_filer.services.gcp_queue.publish', return_value=None) + filing, payment_id, identifier = _get_liquidation_filing('intentToLiquidate', expected_in_liquidation_date) + business = create_business(identifier) filing_rec = create_filing(payment_id, filing, business.id) - filing_rec.effective_date = effective_date + filing_rec.effective_date = expected_in_liquidation_date filing_rec.save() # setup @@ -171,10 +186,7 @@ def test_process_col_filing(app, session, mocker): business: Business = Business.find_by_internal_id(business.id) # assert changes - assert intent_filing.transaction_id - assert intent_filing.business_id == business.id - assert intent_filing.status == Filing.Status.COMPLETED.value - assert business.in_liquidation == True + _assert_common_data(business, intent_filing, expected_in_liquidation_date, None, 2024) check_drs_publish(drs_publish_mock, app, business, intent_filing, '') drs_publish_mock.reset_mock() @@ -186,23 +198,7 @@ def test_process_col_filing(app, session, mocker): assert role.role == PartyRole.RoleTypes.LIQUIDATOR.value offices: list[Office] = business.offices.all() - # NOTE: will have a registered office too - assert len(offices) == 2 - has_liquidation_office = False - for office in offices: - if office.office_type == OfficeType.LIQUIDATION: - has_liquidation_office = True - officeAddresses: list[Address] = office.addresses.all() - assert len(officeAddresses) == 2 - expected_delivery_street = filing['filing']['changeOfLiquidators']['offices']['liquidationRecordsOffice']['deliveryAddress']['streetAddress'] - expected_mailing_street = filing['filing']['changeOfLiquidators']['offices']['liquidationRecordsOffice']['mailingAddress']['streetAddress'] - for address in officeAddresses: - if address.address_type == Address.DELIVERY: - assert address.street == expected_delivery_street - else: - assert address.address_type == Address.MAILING - assert address.street == expected_mailing_street - assert has_liquidation_office + _assert_office_addresses(offices, filing['filing']['changeOfLiquidators']['offices']['liquidationRecordsOffice']) # Test cease liquidator @@ -241,9 +237,7 @@ def test_process_col_filing(app, session, mocker): business: Business = Business.find_by_internal_id(business.id) # assert changes - assert cease_filing.transaction_id - assert cease_filing.business_id == business.id - assert cease_filing.status == Filing.Status.COMPLETED.value + _assert_common_data(business, cease_filing, expected_in_liquidation_date, None, 2024) check_drs_publish(drs_publish_mock, app, business, cease_filing, '') drs_publish_mock.reset_mock() @@ -305,9 +299,7 @@ def test_process_col_filing(app, session, mocker): business: Business = Business.find_by_internal_id(business.id) # assert changes - assert change_address_filing.transaction_id - assert change_address_filing.business_id == business.id - assert change_address_filing.status == Filing.Status.COMPLETED.value + _assert_common_data(business, change_address_filing, expected_in_liquidation_date, None, 2024) check_drs_publish(drs_publish_mock, app, business, change_address_filing, '') drs_publish_mock.reset_mock() @@ -353,9 +345,7 @@ def test_process_col_filing(app, session, mocker): business: Business = Business.find_by_internal_id(business.id) # assert changes - assert change_address_filing.transaction_id - assert change_address_filing.business_id == business.id - assert change_address_filing.status == Filing.Status.COMPLETED.value + _assert_common_data(business, change_address_filing, expected_in_liquidation_date, None, 2024) check_drs_publish(drs_publish_mock, app, business, change_address_filing, '') drs_publish_mock.reset_mock() @@ -398,9 +388,7 @@ def test_process_col_filing(app, session, mocker): business: Business = Business.find_by_internal_id(business.id) # assert changes - assert change_address_filing.transaction_id - assert change_address_filing.business_id == business.id - assert change_address_filing.status == Filing.Status.COMPLETED.value + _assert_common_data(business, change_address_filing, expected_in_liquidation_date, None, 2024) check_drs_publish(drs_publish_mock, app, business, change_address_filing, '') drs_publish_mock.reset_mock() @@ -462,9 +450,7 @@ def test_process_col_filing(app, session, mocker): business: Business = Business.find_by_internal_id(business.id) # assert changes - assert appoint_filing.transaction_id - assert appoint_filing.business_id == business.id - assert appoint_filing.status == Filing.Status.COMPLETED.value + _assert_common_data(business, appoint_filing, expected_in_liquidation_date, None, 2024) check_drs_publish(drs_publish_mock, app, business, appoint_filing, new_document_id) drs_publish_mock.reset_mock() @@ -486,4 +472,93 @@ def test_process_col_filing(app, session, mocker): assert mailing_address.address_type == 'mailing' assert mailing_address.street == new_relationship['mailingAddress']['streetAddress'] - # FUTURE: liquidation report - will be done in #31714 \ No newline at end of file + # liquidation report + expected_last_lr_year = 2024 + + filing['filing']['changeOfLiquidators'] = { + 'type': 'liquidationReport', + } + payment_id = str(random.SystemRandom().getrandbits(0x58)) + effective_date = datetime(2025, 11, 10, 10, 0, 0, tzinfo=timezone.utc) + + filing_rec = create_filing(payment_id, filing, business.id) + filing_rec.effective_date = effective_date + filing_rec.save() + filing_msg = FilingMessage(filing_identifier=filing_rec.id) + + # TEST + process_filing(filing_msg) + + # Get modified data + lr_filing_1: Filing = Filing.find_by_id(filing_rec.id) + business: Business = Business.find_by_internal_id(business.id) + + # assert changes + _assert_common_data(business, lr_filing_1, expected_in_liquidation_date, expected_last_lr_year, 2025) + check_drs_publish(drs_publish_mock, app, business, lr_filing_1, '') + drs_publish_mock.reset_mock() + + # 2nd liquidation report + second_expected_last_lr_year = expected_last_lr_year + 1 + filing['filing']['changeOfLiquidators'] = { + 'type': 'liquidationReport', + } + payment_id = str(random.SystemRandom().getrandbits(0x58)) + effective_date = datetime(2025, 11, 10, 10, 0, 0, tzinfo=timezone.utc) + + filing_rec = create_filing(payment_id, filing, business.id) + filing_rec.effective_date = effective_date + filing_rec.save() + filing_msg = FilingMessage(filing_identifier=filing_rec.id) + + # TEST + process_filing(filing_msg) + + # Get modified data + lr_filing_2: Filing = Filing.find_by_id(filing_rec.id) + business: Business = Business.find_by_internal_id(business.id) + + # assert changes + _assert_common_data(business, lr_filing_2, expected_in_liquidation_date, second_expected_last_lr_year, 2026) + check_drs_publish(drs_publish_mock, app, business, lr_filing_2, '') + drs_publish_mock.reset_mock() + + +def test_process_col_filing_initiated_with_appoint(app, session, mocker): + """Assert that appointLiquidator filings can put the business into liquidation and can include liquidation office.""" + expected_in_liquidation_date = datetime(2023, 10, 10, 10, 0, 0, tzinfo=timezone.utc) + filing, payment_id, identifier = _get_liquidation_filing('appointLiquidator', expected_in_liquidation_date) + drs_publish_mock = mocker.patch('business_filer.services.gcp_queue.publish', return_value=None) + + business = create_business(identifier) + + filing_rec = create_filing(payment_id, filing, business.id) + filing_rec.effective_date = expected_in_liquidation_date + filing_rec.save() + + # setup + filing_msg = FilingMessage(filing_identifier=filing_rec.id) + + # TEST + process_filing(filing_msg) + + # Get modified data + appoint_filing: Filing = Filing.find_by_id(filing_rec.id) + business: Business = Business.find_by_internal_id(business.id) + + # assert changes + _assert_common_data(business, appoint_filing, expected_in_liquidation_date, None, 2024) + check_drs_publish(drs_publish_mock, app, business, appoint_filing, '') + drs_publish_mock.reset_mock() + + party_roles: list[PartyRole] = business.party_roles.all() + assert len(party_roles) == 2 + for role in party_roles: + assert role.appointment_date + assert not role.cessation_date + assert role.role == PartyRole.RoleTypes.LIQUIDATOR.value + + offices: list[Office] = business.offices.all() + # NOTE: will have a registered office too + assert len(offices) == 2 + _assert_office_addresses(offices, filing['filing']['changeOfLiquidators']['offices']['liquidationRecordsOffice'])