From 8514a5663d97c3647c0e843a9ad755f24865755b Mon Sep 17 00:00:00 2001 From: leodube-aot <122323255+leodube-aot@users.noreply.github.com> Date: Fri, 31 May 2024 17:14:10 -0600 Subject: [PATCH] 21092 Dissolution Job - add logic to initiate dissolution process for businesses (first pass) (#2714) --- jobs/involuntary-dissolutions/Makefile | 4 +- .../involuntary_dissolutions.py | 69 +++++++++++++++++-- .../involuntary-dissolutions/requirements.txt | 3 +- .../services/involuntary_dissolution.py | 11 +++ 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/jobs/involuntary-dissolutions/Makefile b/jobs/involuntary-dissolutions/Makefile index 53aec5eb1c..d0281f4d61 100644 --- a/jobs/involuntary-dissolutions/Makefile +++ b/jobs/involuntary-dissolutions/Makefile @@ -39,7 +39,7 @@ clean-test: ## clean test files build-req: clean ## Upgrade requirements test -f venv/bin/activate || python3.8 -m venv $(CURRENT_ABS_DIR)/venv ;\ . venv/bin/activate ;\ - pip install pip==20.1.1 ;\ + pip install --upgrade pip ;\ pip install -Ur requirements/prod.txt ;\ pip freeze | sort > requirements.txt ;\ cat requirements/bcregistry-libraries.txt >> requirements.txt ;\ @@ -48,7 +48,7 @@ build-req: clean ## Upgrade requirements install: clean ## Install python virtrual environment test -f venv/bin/activate || python3.8 -m venv $(CURRENT_ABS_DIR)/venv ;\ . venv/bin/activate ;\ - pip install pip==20.1.1 ;\ + pip install --upgrade pip ;\ pip install -Ur requirements.txt install-dev: ## Install local application diff --git a/jobs/involuntary-dissolutions/involuntary_dissolutions.py b/jobs/involuntary-dissolutions/involuntary_dissolutions.py index 58fc21bbb4..434e9dc8fe 100644 --- a/jobs/involuntary-dissolutions/involuntary_dissolutions.py +++ b/jobs/involuntary-dissolutions/involuntary_dissolutions.py @@ -12,17 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. """Involuntary dissolutions job.""" +import asyncio import logging import os +from datetime import datetime +import pytz import sentry_sdk # noqa: I001, E501; pylint: disable=ungrouped-imports; conflicts with Flake8 +from croniter import croniter from flask import Flask -from legal_api.models import db # noqa: I001 +from legal_api.models import Batch, BatchProcessing, Configuration, db # noqa: I001 from legal_api.services.flags import Flags +from legal_api.services.involuntary_dissolution import InvoluntaryDissolutionService from sentry_sdk.integrations.logging import LoggingIntegration import config # pylint: disable=import-error from utils.logging import setup_logging # pylint: disable=import-error + + # noqa: I003 setup_logging( @@ -65,11 +72,63 @@ def shell_context(): app.shell_context_processor(shell_context) -if __name__ == '__main__': - application = create_app() +def initiate_dissolution_process(app: Flask): # pylint: disable=redefined-outer-name + """Initiate dissolution process for new businesses that meet dissolution criteria""" + try: + # check if batch has already run today + batch_today = Batch.find_by(batch_type=Batch.BatchType.INVOLUNTARY_DISSOLUTION, start_date=datetime.today()) + if batch_today: + app.logger.debug('Skipping job run since batch job has already run today.') + return + + # get first NUM_DISSOLUTIONS_ALLOWED number of businesses + num_dissolutions_allowed = Configuration.find_by_name(config_name='NUM_DISSOLUTIONS_ALLOWED').val + businesses = InvoluntaryDissolutionService.get_businesses_eligible(num_dissolutions_allowed) + + # create new entry in batches table + batch = Batch(batch_type=Batch.BatchType.INVOLUNTARY_DISSOLUTION, + status=Batch.BatchStatus.PROCESSING, + size=len(businesses), + start_date=datetime.now()) + batch.save() + + # create batch processing entries for each business being dissolved + for business in businesses: + batch_processing = BatchProcessing(business_identifier=business.identifier, + step=BatchProcessing.BatchProcessingStep.WARNING_LEVEL_1, + status=BatchProcessing.BatchProcessingStatus.PROCESSING, + created_date=datetime.now(), + batch_id=batch.id, + business_id=business.id) + batch_processing.save() + + except Exception as err: # pylint: disable=redefined-outer-name; noqa: B902 + app.logger.error(err) + + +async def run(loop, application: Flask = None): # pylint: disable=redefined-outer-name + """Run the stage 1-3 methods for dissolving businesses.""" + if application is None: + application = create_app() + with application.app_context(): flag_on = flags.is_on('enable-involuntary-dissolution') application.logger.debug(f'enable-involuntary-dissolution flag on: {flag_on}') if flag_on: - # TODO: detailed implementation - application.logger.debug('Running involuntary dissolutions job') + # check if batch can be run today + new_dissolutions_schedule_config = Configuration.find_by_name(config_name='DISSOLUTIONS_STAGE_1_SCHEDULE') + tz = pytz.timezone('US/Pacific') + cron_valid = croniter.match(new_dissolutions_schedule_config.val, tz.localize(datetime.today())) + if cron_valid: + initiate_dissolution_process(application) + else: + application.logger.debug('Skipping job run since current day of the week does not match the cron schedule.') # noqa: E501 + +if __name__ == '__main__': + application = create_app() + try: + event_loop = asyncio.get_event_loop() + event_loop.run_until_complete(run(event_loop, application)) + except Exception as err: # pylint: disable=broad-except; Catching all errors from the frameworks + application.logger.error(err) # pylint: disable=no-member + raise err diff --git a/jobs/involuntary-dissolutions/requirements.txt b/jobs/involuntary-dissolutions/requirements.txt index d9f4096ef1..55323ba9b5 100644 --- a/jobs/involuntary-dissolutions/requirements.txt +++ b/jobs/involuntary-dissolutions/requirements.txt @@ -9,6 +9,7 @@ attrs==23.1.0 blinker==1.4 certifi==2020.12.5 click==8.1.3 +croniter==2.0.5 ecdsa==0.14.1 flask-jwt-oidc==0.3.0 gunicorn==20.1.0 @@ -18,7 +19,7 @@ pyasn1==0.4.8 pyrsistent==0.17.3 python-dotenv==0.17.1 python-jose==3.2.0 -pytz==2021.1 +pytz==2024.1 rsa==4.7.2 sentry-sdk==1.20.0 six==1.15.0 diff --git a/legal-api/src/legal_api/services/involuntary_dissolution.py b/legal-api/src/legal_api/services/involuntary_dissolution.py index 362d9d4257..ac8f18aba7 100644 --- a/legal-api/src/legal_api/services/involuntary_dissolution.py +++ b/legal-api/src/legal_api/services/involuntary_dissolution.py @@ -51,6 +51,17 @@ def check_business_eligibility( eligibility_details = cls.EligibilityDetails(ar_overdue=result[1], transition_overdue=result[2]) return True, eligibility_details + + @classmethod + def get_businesses_eligible(cls, num_allowed: int = None): + """Return the businesses eligible for involuntary dissolution.""" + query = cls._get_businesses_eligible_query() + if num_allowed: + eligible_businesses = query.limit(num_allowed).all() + else: + eligible_businesses = query.all() + + return eligible_businesses @classmethod def get_businesses_eligible_count(cls):