Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

21476 update good standing logic to include transition filing criteria #2756

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions legal-api/src/legal_api/models/business.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
from flask import current_app
from sqlalchemy.exc import OperationalError, ResourceClosedError
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import backref
from sqlalchemy.sql import func
from sqlalchemy.orm import aliased, backref
from sqlalchemy.sql import and_, exists, func, not_, text
from sqlalchemy_continuum import version_class

from legal_api.exceptions import BusinessException
Expand All @@ -34,15 +34,15 @@
from legal_api.utils.legislation_datetime import LegislationDatetime

from .amalgamation import Amalgamation # noqa: F401, I001, I003 pylint: disable=unused-import
from .batch import Batch # noqa: F401, I001, I003 pylint: disable=unused-import
from .batch_processing import BatchProcessing # noqa: F401, I001, I003 pylint: disable=unused-import
from .db import db # noqa: I001
from .party import Party
from .share_class import ShareClass # noqa: F401,I001,I003 pylint: disable=unused-import


from .address import Address # noqa: F401,I003 pylint: disable=unused-import; needed by the SQLAlchemy relationship
from .alias import Alias # noqa: F401 pylint: disable=unused-import; needed by the SQLAlchemy relationship
from .batch import Batch # noqa: F401, I001, I003 pylint: disable=unused-import
from .batch_processing import BatchProcessing # noqa: F401, I001, I003 pylint: disable=unused-import
from .filing import Filing # noqa: F401, I003 pylint: disable=unused-import; needed by the SQLAlchemy backref
from .office import Office # noqa: F401 pylint: disable=unused-import; needed by the SQLAlchemy relationship
from .party_role import PartyRole # noqa: F401 pylint: disable=unused-import; needed by the SQLAlchemy relationship
Expand Down Expand Up @@ -397,9 +397,15 @@ def is_firm(self):
@property
def good_standing(self):
"""Return true if in good standing, otherwise false."""
from legal_api.services import flags # pylint: disable=import-outside-toplevel

# A firm is always in good standing
if self.is_firm:
return True
# When involuntary dissolution feature flag is on, check transition filing
if flags.is_on('enable_involuntary_dissolution'):
if self._has_no_transition_filed_after_restoration():
return False
chenhongjing marked this conversation as resolved.
Show resolved Hide resolved
# Date of last AR or founding date if they haven't yet filed one
last_ar_date = self.last_ar_date or self.founding_date
# Good standing is if last AR was filed within the past 1 year, 2 months and 1 day and is in an active state
Expand All @@ -412,6 +418,48 @@ def good_standing(self):
return date_cutoff.replace(tzinfo=pytz.UTC) > datetime.utcnow()
return True

def _has_no_transition_filed_after_restoration(self) -> bool:
"""Return True for no transition filed after restoration check. Otherwise, return False.

Check whether the business needs to file Transition but does not file it within 12 months after restoration.
"""
from legal_api.core.filing import Filing as CoreFiling # pylint: disable=import-outside-toplevel

new_act_date = func.date('2004-03-29 00:00:00+00:00')
restoration_filing = aliased(Filing)
transition_filing = aliased(Filing)

restoration_filing_effective_cutoff = restoration_filing.effective_date + text("""INTERVAL '1 YEAR'""")
condition = exists().where(
and_(
self.legal_type != Business.LegalTypes.EXTRA_PRO_A.value,
self.founding_date < new_act_date,
restoration_filing.business_id == self.id,
restoration_filing._filing_type.in_([ # pylint: disable=protected-access
CoreFiling.FilingTypes.RESTORATION.value,
CoreFiling.FilingTypes.RESTORATIONAPPLICATION.value
]),
restoration_filing._status == Filing.Status.COMPLETED.value, # pylint: disable=protected-access
restoration_filing_effective_cutoff <= func.timezone('UTC', func.now()),
not_(
exists().where(
and_(
transition_filing.business_id == self.id,
transition_filing._filing_type == \
CoreFiling.FilingTypes.TRANSITION.value, # pylint: disable=protected-access
transition_filing._status == \
Filing.Status.COMPLETED.value, # pylint: disable=protected-access
transition_filing.effective_date.between(
restoration_filing.effective_date,
restoration_filing_effective_cutoff
)
)
)
)
)
)
return db.session.query(condition).scalar()

@property
def in_dissolution(self):
"""Return true if in dissolution, otherwise false."""
Expand Down
40 changes: 39 additions & 1 deletion legal-api/tests/unit/models/test_business.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,28 @@

Test-Suite to ensure that the Business Model is working as expected.
"""
import copy
from datetime import datetime, timedelta
from flask import current_app
from unittest.mock import patch

import datedelta
import pytest
from sqlalchemy_continuum import versioning_manager
from registry_schemas.example_data import FILING_HEADER, RESTORATION, TRANSITION_FILING_TEMPLATE

from legal_api.exceptions import BusinessException
from legal_api.models import AmalgamatingBusiness, Amalgamation, Batch, BatchProcessing, Business, Filing, Party, PartyRole, db
from legal_api.services import flags
from legal_api.utils.legislation_datetime import LegislationDatetime
from tests import EPOCH_DATETIME, TIMEZONE_OFFSET
from tests.unit import has_expected_date_str_format
from tests.unit.models import factory_party_role, factory_batch
from tests.unit.models import (
factory_party_role,
factory_batch,
factory_business as factory_business_from_tests,
factory_completed_filing
)


def factory_business(designation: str = '001'):
Expand Down Expand Up @@ -255,6 +262,37 @@ def test_good_standing(session, last_ar_date, legal_type, state, limited_restora
assert business.good_standing is expected


RESTORATION_FILING = copy.deepcopy(FILING_HEADER)
RESTORATION_FILING['filing']['restoration'] = RESTORATION
chenhongjing marked this conversation as resolved.
Show resolved Hide resolved


@pytest.mark.parametrize('test_name, has_no_transition_filed, good_standing', [
('NO_NEED_TRANSITION_NEW_ACT', False, True),
('NO_NEED_TRANSITION_BUT_IN_LIMITED_RESTORATION', False, False),
('TRANSITION_COMPLETED', False, True),
('TRANSITION_NOT_FILED', True, False)
])
def test_good_standing_check_transition_filing(session, test_name, has_no_transition_filed, good_standing):
"Assert that the business is in good standing with additional check for transition filing"
business = factory_business_from_tests(identifier='BC1234567', entity_type=Business.LegalTypes.COMP.value, last_ar_date=datetime.utcnow())
restoration_filing = factory_completed_filing(business, RESTORATION_FILING, filing_type='restoration')
if test_name == 'NO_NEED_TRANSITION_NEW_ACT':
business.founding_date = datetime.utcnow()
business.save()
elif test_name == 'NO_NEED_TRANSITION_BUT_IN_LIMITED_RESTORATION':
business.restoration_expiry_date = datetime.utcnow() + datedelta.datedelta(years=1)
business.save()
restoration_filing.effective_date = datetime.utcnow()
restoration_filing.save()
elif test_name == 'TRANSITION_COMPLETED':
factory_completed_filing(business, TRANSITION_FILING_TEMPLATE, filing_type='transition')

check_result = business._has_no_transition_filed_after_restoration()
assert check_result == has_no_transition_filed
with patch.object(flags, 'is_on', return_value=True):
assert business.good_standing == good_standing


def test_business_json(session):
"""Assert that the business model is saved correctly."""
business = Business(legal_name='legal_name',
Expand Down
Loading