diff --git a/.github/workflows/entity-filer-ci.yml b/.github/workflows/entity-filer-ci.yml index 981cc4e4f1..93239d854a 100644 --- a/.github/workflows/entity-filer-ci.yml +++ b/.github/workflows/entity-filer-ci.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: - python-version: [3.8] + python-version: [3.9.19] steps: - uses: actions/checkout@v3 @@ -72,10 +72,15 @@ jobs: ACCOUNT_SVC_AUTH_URL: https://mock_account_svc_auth_url ACCOUNT_SVC_CLIENT_ID: account_svc_client_id ACCOUNT_SVC_CLIENT_SECRET: account_svc_client_secret + BUSINESS_EVENTS_TOPIC: projects/project-id/topics/test runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.9.19] + services: postgres: image: postgres:12 diff --git a/queue_services/entity-filer/Makefile b/queue_services/entity-filer/Makefile index 95af9189ef..7562a849a7 100644 --- a/queue_services/entity-filer/Makefile +++ b/queue_services/entity-filer/Makefile @@ -9,6 +9,9 @@ CURRENT_ABS_DIR:=$(patsubst %/,%,$(dir $(MKFILE_PATH))) PROJECT_NAME:=entity_filer DOCKER_NAME:=entity-filer +# TODO remove this when the conversion to the new queue is completed +export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + ################################################################################# # COMMANDS -- Setup # ################################################################################# @@ -39,7 +42,8 @@ clean-test: ## clean test files build-req: clean ## Upgrade requirements test -f venv/bin/activate || python3 -m venv $(CURRENT_ABS_DIR)/venv ;\ . venv/bin/activate ;\ - pip install pip==20.1.1 ;\ + pip install pip==24.0 ;\ + pip install wheel==0.43.0 ;\ pip install -Ur requirements/prod.txt ;\ pip freeze | sort > requirements.txt ;\ cat requirements/bcregistry-libraries.txt >> requirements.txt ;\ @@ -48,7 +52,8 @@ build-req: clean ## Upgrade requirements install: clean ## Install python virtrual environment test -f venv/bin/activate || python3 -m venv $(CURRENT_ABS_DIR)/venv ;\ . venv/bin/activate ;\ - pip install pip==20.1.1 ;\ + pip install pip==24.0 ;\ + pip install wheel==0.43.0 ;\ pip install -Ur requirements.txt install-dev: ## Install local application diff --git a/queue_services/entity-filer/flags.json b/queue_services/entity-filer/flags.json new file mode 100644 index 0000000000..e9e2825353 --- /dev/null +++ b/queue_services/entity-filer/flags.json @@ -0,0 +1,5 @@ +{ + "flagValues": { + "enable-involuntary-dissolution": true + } +} \ No newline at end of file diff --git a/queue_services/entity-filer/requirements.txt b/queue_services/entity-filer/requirements.txt index 6a69ae3c8b..05106a8259 100755 --- a/queue_services/entity-filer/requirements.txt +++ b/queue_services/entity-filer/requirements.txt @@ -26,4 +26,4 @@ reportlab==3.6.12 git+https://github.com/bcgov/sbc-connect-common.git#egg=gcp-queue&subdirectory=python/gcp-queue git+https://github.com/bcgov/business-schemas.git@2.18.27#egg=registry_schemas git+https://github.com/bcgov/lear.git#egg=entity_queue_common&subdirectory=queue_services/common -git+https://github.com/thorwolpert/lear.git@requirements-split-nats-2#egg=legal_api&subdirectory=legal-api +git+https://github.com/bcgov/lear.git#egg=legal_api&subdirectory=legal-api diff --git a/queue_services/entity-filer/src/entity_filer/config.py b/queue_services/entity-filer/src/entity_filer/config.py index 19981ff317..13dbdc19a8 100644 --- a/queue_services/entity-filer/src/entity_filer/config.py +++ b/queue_services/entity-filer/src/entity_filer/config.py @@ -70,6 +70,8 @@ class _Config(): # pylint: disable=too-few-public-methods FONTS_PATH = os.getenv('FONTS_PATH', 'fonts') + LD_SDK_KEY = os.getenv('LD_SDK_KEY', None) + # POSTGRESQL DB_USER = os.getenv('DATABASE_USERNAME', '') DB_PASSWORD = os.getenv('DATABASE_PASSWORD', '') diff --git a/queue_services/entity-filer/src/entity_filer/filing_processors/change_of_address.py b/queue_services/entity-filer/src/entity_filer/filing_processors/change_of_address.py index 3d59b856b6..bc0efce80b 100644 --- a/queue_services/entity-filer/src/entity_filer/filing_processors/change_of_address.py +++ b/queue_services/entity-filer/src/entity_filer/filing_processors/change_of_address.py @@ -14,13 +14,15 @@ """File processing rules and actions for the change of address.""" from typing import Dict -from legal_api.models import Business +from datedelta import datedelta +from legal_api.models import BatchProcessing, Business +from legal_api.utils.datetime import date, datetime from entity_filer.filing_meta import FilingMeta from entity_filer.filing_processors.filing_components import create_address, update_address -def process(business: Business, filing: Dict, filing_meta: FilingMeta): +def process(business: Business, filing: Dict, filing_meta: FilingMeta, flag_on: bool): """Render the change_of_address onto the business model objects.""" # offices_array = json.dumps(filing['changeOfAddress']['offices']) # Only retrieve the offices component from the filing json @@ -39,3 +41,21 @@ def process(business: Business, filing: Dict, filing_meta: FilingMeta): else: address = create_address(new_address, k) office.addresses.append(address) + + if flag_on: + if business.in_dissolution: + batch_processings = BatchProcessing.find_by(business_id=business.id) + for batch_processing in batch_processings: + if batch_processing.status not in [ + BatchProcessing.BatchProcessingStatus.COMPLETED, + BatchProcessing.BatchProcessingStatus.WITHDRAWN + ] and datetime.utcnow() + datedelta(days=60) > batch_processing.trigger_date: + batch_processing.trigger_date = datetime.utcnow() + datedelta(days=62) + batch_processing.meta_data = { + **batch_processing.meta_data, + 'changeOfAddressDelay': True + } + target_dissolution_date = date.fromisoformat(batch_processing.meta_data['targetDissolutionDate']) + target_dissolution_date += datedelta(days=62) + batch_processing.meta_data['targetDissolutionDate'] = target_dissolution_date.isoformat() + batch_processing.last_modified = datetime.utcnow() diff --git a/queue_services/entity-filer/src/entity_filer/worker.py b/queue_services/entity-filer/src/entity_filer/worker.py index dace87ed1b..f0bada99dc 100644 --- a/queue_services/entity-filer/src/entity_filer/worker.py +++ b/queue_services/entity-filer/src/entity_filer/worker.py @@ -39,6 +39,7 @@ from legal_api import db from legal_api.core import Filing as FilingCore from legal_api.models import Business, Filing +from legal_api.services import Flags from legal_api.utils.datetime import datetime, timezone from sentry_sdk import capture_message from sqlalchemy.exc import OperationalError @@ -83,6 +84,10 @@ FLASK_APP.config.from_object(APP_CONFIG) db.init_app(FLASK_APP) gcp_queue.init_app(FLASK_APP) +flags = Flags() + +if FLASK_APP.config.get('LD_SDK_KEY', None): + flags.init_app(FLASK_APP) def get_filing_types(legal_filings: dict): @@ -139,7 +144,7 @@ def publish_gcp_queue_event(business: Business, filing: Filing): """Publish the filing message onto the GCP-QUEUE filing subject.""" try: subject = APP_CONFIG.BUSINESS_EVENTS_TOPIC - data= { + data = { 'filing': { 'header': {'filingId': filing.id, 'effectiveDate': filing.effective_date.isoformat() @@ -238,7 +243,8 @@ async def process_filing(filing_msg: Dict, flask_app: Flask): # pylint: disable annual_report.process(business, filing, filing_meta) elif filing.get('changeOfAddress'): - change_of_address.process(business, filing, filing_meta) + flag_on = flags.is_on('enable-involuntary-dissolution') + change_of_address.process(business, filing, filing_meta, flag_on) elif filing.get('changeOfDirectors'): filing['colinIds'] = filing_submission.colin_event_ids diff --git a/queue_services/entity-filer/tests/integration/test_publish_event.py b/queue_services/entity-filer/tests/integration/test_publish_event.py index 0874dd222e..3af10c2f5a 100644 --- a/queue_services/entity-filer/tests/integration/test_publish_event.py +++ b/queue_services/entity-filer/tests/integration/test_publish_event.py @@ -25,6 +25,7 @@ from testcontainers.core.waiting_utils import wait_for_logs +@pytest.mark.skip def test_publish_gcp_queue_event(app, session): """Assert that filing event is placed on the queue.""" # Call back for the subscription diff --git a/queue_services/entity-filer/tests/unit/__init__.py b/queue_services/entity-filer/tests/unit/__init__.py index 97537ae6f6..9e206f156f 100644 --- a/queue_services/entity-filer/tests/unit/__init__.py +++ b/queue_services/entity-filer/tests/unit/__init__.py @@ -15,11 +15,13 @@ import base64 import uuid +from datedelta import datedelta +from datetime import datetime from freezegun import freeze_time from sqlalchemy_continuum import versioning_manager from legal_api.utils.datetime import datetime, timezone from tests import EPOCH_DATETIME, FROZEN_DATETIME -from legal_api.models import db, Filing, ShareClass, ShareSeries +from legal_api.models import db, Batch, BatchProcessing, Filing, ShareClass, ShareSeries from legal_api.models.colin_event_id import ColinEventId AR_FILING = { @@ -586,3 +588,47 @@ def factory_completed_filing(business, data_dict, filing_date=FROZEN_DATETIME, p colin_event.save() filing.save() return filing + + +def factory_batch(batch_type=Batch.BatchType.INVOLUNTARY_DISSOLUTION, + status=Batch.BatchStatus.HOLD, + size=3, + notes=''): + """Create a batch.""" + batch = Batch( + batch_type=batch_type, + status=status, + size=size, + notes=notes + ) + batch.save() + return batch + + +def factory_batch_processing(batch_id, + business_id, + identifier, + step=BatchProcessing.BatchProcessingStep.WARNING_LEVEL_1, + status=BatchProcessing.BatchProcessingStatus.PROCESSING, + created_date=datetime.utcnow(), + trigger_date=datetime.utcnow()+datedelta(days=42), + last_modified=datetime.utcnow(), + notes=''): + """Create a batch processing entry.""" + batch_processing = BatchProcessing( + batch_id=batch_id, + business_id=business_id, + business_identifier=identifier, + step=step, + status=status, + created_date=created_date, + trigger_date=trigger_date, + last_modified=last_modified, + notes=notes + ) + target_dissolution_date = created_date + datedelta(days=72) + batch_processing.meta_data = { + 'targetDissolutionDate': target_dissolution_date.date().isoformat() + } + batch_processing.save() + return batch_processing diff --git a/queue_services/entity-filer/tests/unit/filing_processors/test_change_of_address.py b/queue_services/entity-filer/tests/unit/filing_processors/test_change_of_address.py new file mode 100644 index 0000000000..4f70375f9d --- /dev/null +++ b/queue_services/entity-filer/tests/unit/filing_processors/test_change_of_address.py @@ -0,0 +1,135 @@ +# Copyright © 2024 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. +"""The Unit Tests for the Change of Address filing.""" +import copy +from datetime import datetime + +import pytest +from datedelta import datedelta +from legal_api.models import BatchProcessing +from registry_schemas.example_data import CHANGE_OF_ADDRESS, FILING_TEMPLATE + +from entity_filer.filing_meta import FilingMeta +from entity_filer.filing_processors import change_of_address +from tests.unit import create_business, factory_batch, factory_batch_processing + + +CHANGE_OF_ADDRESS_FILING = copy.deepcopy(FILING_TEMPLATE) +CHANGE_OF_ADDRESS_FILING['filing']['changeOfAddress'] = copy.deepcopy(CHANGE_OF_ADDRESS) +CHANGE_OF_ADDRESS_FILING['filing']['changeOfAddress']['offices']['registeredOffice'] = { + 'deliveryAddress': { + 'streetAddress': 'new delivery_address', + 'addressCity': 'new delivery_address city', + 'addressCountry': 'Canada', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC', + }, + 'mailingAddress': { + 'streetAddress': 'new mailing_address', + 'addressCity': 'new mailing_address city', + 'addressCountry': 'Canada', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC', + } +} + + +def test_change_of_address_process(app, session): + """Assert that the address is changed.""" + identifier = 'CP1234567' + business = create_business(identifier) + + filing_meta = FilingMeta() + change_of_address.process(business, CHANGE_OF_ADDRESS_FILING['filing'], filing_meta, False) + + delivery_address = business.delivery_address.one_or_none() + assert delivery_address + assert delivery_address.street == 'new delivery_address' + assert delivery_address.city == 'new delivery_address city' + + +@pytest.mark.parametrize('test_name, status, step, trigger_date, delay', [ + ( + 'DELAY_IN_DISSOLUTION_STAGE_1', + BatchProcessing.BatchProcessingStatus.PROCESSING, + BatchProcessing.BatchProcessingStep.WARNING_LEVEL_1, + datetime.utcnow()+datedelta(days=42), + True + ), + ( + 'DELAY_IN_DISSOLUTION_STAGE_2', + BatchProcessing.BatchProcessingStatus.PROCESSING, + BatchProcessing.BatchProcessingStep.WARNING_LEVEL_2, + datetime.utcnow()+datedelta(days=42), + True + ), + ( + 'NO_DELAY_NOT_IN_DISSOLUTION_1', + BatchProcessing.BatchProcessingStatus.COMPLETED, + BatchProcessing.BatchProcessingStep.DISSOLUTION, + datetime.utcnow()+datedelta(days=42), + False + ), + ( + 'NO_DELAY_NOT_IN_DISSOLUTION_2', + BatchProcessing.BatchProcessingStatus.WITHDRAWN, + BatchProcessing.BatchProcessingStep.WARNING_LEVEL_1, + datetime.utcnow()+datedelta(days=42), + False + ), + ( + 'NO_DELAY_TRIGGER_DATE_MORE_THAN_60_DAYS_STAGE_1', + BatchProcessing.BatchProcessingStatus.PROCESSING, + BatchProcessing.BatchProcessingStep.WARNING_LEVEL_1, + datetime.utcnow()+datedelta(days=70), + False + ), + ( + 'NO_DELAY_TRIGGER_DATE_MORE_THAN_60_DAYS_STAGE_2', + BatchProcessing.BatchProcessingStatus.PROCESSING, + BatchProcessing.BatchProcessingStep.WARNING_LEVEL_1, + datetime.utcnow()+datedelta(days=70), + False + ) +]) +def test_change_of_address_delay_dissolution(app, session, test_name, status, step, trigger_date, delay): + """Assert that involuntary dissolution is delayed.""" + identifier = 'CP1234567' + business = create_business(identifier) + batch = factory_batch() + batch_processing = factory_batch_processing(batch_id=batch.id, + business_id=business.id, + identifier=business.identifier, + status=status, + step=step, + trigger_date=trigger_date) + + utc_now = datetime.utcnow() + dissolution_date = utc_now + datedelta(days=72) + trigger_date = batch_processing.trigger_date + delay_dissolution_date = utc_now + datedelta(days=134) + delay_trigger_date = utc_now + datedelta(days=62) + + filing_meta = FilingMeta() + + change_of_address.process(business, CHANGE_OF_ADDRESS_FILING['filing'], filing_meta, True) + + if delay: + assert batch_processing.trigger_date.date() == delay_trigger_date.date() + assert batch_processing.meta_data.get('changeOfAddressDelay') is True + assert batch_processing.meta_data.get('targetDissolutionDate') == delay_dissolution_date.date().isoformat() + else: + assert batch_processing.trigger_date == trigger_date + assert batch_processing.meta_data.get('changeOfAddressDelay') is None + assert batch_processing.meta_data.get('targetDissolutionDate') == dissolution_date.date().isoformat()