diff --git a/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py b/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py index c5c8f9636e..9169dd87df 100644 --- a/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py +++ b/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py @@ -479,7 +479,8 @@ def put_basic_checks(identifier, filing, client_request, business) -> Tuple[dict if not filing_type: return ({'message': 'filing/header/name is a required property'}, HTTPStatus.BAD_REQUEST) - if filing_type not in CoreFiling.NEW_BUSINESS_FILING_TYPES and business is None: + if filing_type not in CoreFiling.NEW_BUSINESS_FILING_TYPES + [CoreFiling.FilingTypes.NOTICEOFWITHDRAWAL] \ + and business is None: return ({'message': 'A valid business is required.'}, HTTPStatus.BAD_REQUEST) if client_request.method == 'PUT' and not filing: @@ -497,9 +498,14 @@ def check_authorization(identifier, filing_json: dict, # While filing IA business object will be None. Setting default values in that case. state = business.state if business else Business.State.ACTIVE + if business: + legal_type = business.legal_type + # for temporary business notice of withdraw, get legalType from filing json + elif filing_type == CoreFiling.FilingTypes.NOTICEOFWITHDRAWAL.value: + legal_type = filing_json['filing'].get('business', None).get('legalType') # for incorporationApplication and registration, get legalType from nameRequest - legal_type = business.legal_type if business else \ - filing_json['filing'][filing_type]['nameRequest'].get('legalType') + else: + legal_type = filing_json['filing'][filing_type]['nameRequest'].get('legalType') if not authorized(identifier, jwt, action=['edit']) or \ not is_allowed(business, state, filing_type, legal_type, jwt, filing_sub_type, filing): @@ -680,6 +686,9 @@ def get_filing_types(business: Business, filing_json: dict): # pylint: disable= if filing_type in CoreFiling.NEW_BUSINESS_FILING_TYPES: legal_type = filing_json['filing'][filing_type]['nameRequest']['legalType'] + elif business.identifier.startswith('T') and \ + filing_type == CoreFiling.FilingTypes.NOTICEOFWITHDRAWAL: + legal_type = filing_json['filing'].get('business', None).get('legalType') else: legal_type = business.legal_type @@ -839,6 +848,12 @@ def create_invoice(business: Business, # pylint: disable=too-many-locals,too-ma mailing_address = business.mailing_address.one_or_none() corp_type = business.legal_type if business.legal_type else \ filing.json['filing']['business'].get('legalType') + # deal with withdrawing a new business filing + elif business.identifier.startswith('T') and \ + filing.filing_type == Filing.FILINGS['noticeOfWithdrawal']['name']: + mailing_address, corp_type, legal_name = \ + ListFilingResource._get_address_from_withdrawn_new_business_filing(business, filing) + business.legal_name = legal_name else: mailing_address = business.mailing_address.one_or_none() corp_type = business.legal_type if business.legal_type else \ @@ -1057,3 +1072,27 @@ def submit_filing_for_review(filing: Filing): {'email': {'filingId': filing.id, 'type': filing.filing_type, 'option': review.status}}, current_app.config.get('NATS_EMAILER_SUBJECT') ) + + @staticmethod + def _get_address_from_withdrawn_new_business_filing(business: Business, filing: Filing): + if filing.filing_type != CoreFiling.FilingTypes.NOTICEOFWITHDRAWAL.value: + return None, None, None + withdrawn_filing_id = filing.filing_json['filing']['noticeOfWithdrawal']['filingId'] + withdrawn_filing = Filing.find_by_id(withdrawn_filing_id) + if withdrawn_filing.filing_type in CoreFiling.NEW_BUSINESS_FILING_TYPES: + office_type = OfficeType.REGISTERED + if withdrawn_filing.filing_type == Filing.FILINGS['registration']['name']: + office_type = OfficeType.BUSINESS + + mailing_address = Address.create_address( + withdrawn_filing.json['filing'][withdrawn_filing.filing_type]['offices'][office_type]['mailingAddress']) + corp_type = withdrawn_filing.json['filing'][withdrawn_filing.filing_type]['nameRequest'].get( + 'legalType', Business.LegalTypes.BCOMP.value) + + try: + legal_name = withdrawn_filing.json['filing'][withdrawn_filing.filing_type]['nameRequest']['legalName'] + except KeyError: + legal_name = business.identifier + + return mailing_address, corp_type, legal_name + return None, None, None diff --git a/legal-api/src/legal_api/services/authz.py b/legal-api/src/legal_api/services/authz.py index dbede093b1..a1275cf654 100644 --- a/legal-api/src/legal_api/services/authz.py +++ b/legal-api/src/legal_api/services/authz.py @@ -324,7 +324,8 @@ def get_allowable_filings_dict(): 'legalTypes': ['BC', 'BEN', 'CC', 'ULC', 'C', 'CBEN', 'CUL', 'CCC'], 'blockerChecks': { 'business': [BusinessBlocker.FILING_WITHDRAWAL] - } + }, + 'businessRequirement': BusinessRequirement.NO_RESTRICTION } }, Business.State.HISTORICAL: { diff --git a/legal-api/src/legal_api/services/filings/validations/notice_of_withdrawal.py b/legal-api/src/legal_api/services/filings/validations/notice_of_withdrawal.py index 443bf4bc78..74d1d5f495 100644 --- a/legal-api/src/legal_api/services/filings/validations/notice_of_withdrawal.py +++ b/legal-api/src/legal_api/services/filings/validations/notice_of_withdrawal.py @@ -37,9 +37,11 @@ def validate(filing: Dict) -> Optional[Error]: msg.append({'error': babel('Filing Id is required.'), 'path': withdrawn_filing_id_path}) return msg # cannot continue validation without the to be withdrawn filing id - err = validate_withdrawn_filing(withdrawn_filing_id) - if err: - msg.extend(err) + is_not_found, err_msg = validate_withdrawn_filing(withdrawn_filing_id) + if is_not_found: + return Error(HTTPStatus.NOT_FOUND, err_msg) + if err_msg and not is_not_found: + msg.extend(err_msg) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) @@ -49,12 +51,14 @@ def validate(filing: Dict) -> Optional[Error]: def validate_withdrawn_filing(withdrawn_filing_id: int): """Validate the to be withdrawn filing id exists, the filing has a FED, the filing status is PAID.""" msg = [] + is_not_found = False # check whether the filing ID exists withdrawn_filing = db.session.query(Filing). \ filter(Filing.id == withdrawn_filing_id).one_or_none() if not withdrawn_filing: msg.append({'error': babel('The filing to be withdrawn cannot be found.')}) - return msg # cannot continue if the withdrawn filing doesn't exist + is_not_found = True + return is_not_found, msg # cannot continue if the withdrawn filing doesn't exist # check whether the filing has a Future Effective Date(FED) now = dt.utcnow() @@ -68,5 +72,5 @@ def validate_withdrawn_filing(withdrawn_filing_id: int): msg.append({'error': babel('Only paid filings with a future effective date can be withdrawn.')}) if msg: - return msg - return None + return is_not_found, msg + return None, None diff --git a/legal-api/tests/unit/resources/v1/test_business_tasks.py b/legal-api/tests/unit/resources/v1/test_business_tasks.py index b60019eccf..8095958570 100644 --- a/legal-api/tests/unit/resources/v1/test_business_tasks.py +++ b/legal-api/tests/unit/resources/v1/test_business_tasks.py @@ -96,8 +96,8 @@ def test_get_tasks_no_filings(session, client, jwt): def test_get_tasks_next_year(session, client, jwt): """Assert that one todo item is returned in the calendar year following incorporation.""" identifier = 'CP7654321' - founding_date = datetime.today() + datedelta.datedelta(days=1) - datedelta.datedelta(years=1) - factory_business(identifier, founding_date=founding_date) # incorporation 1 year - 1 day ago + founding_date = datetime.today() - datedelta.datedelta(years=1) + factory_business(identifier, founding_date=founding_date) # incorporation 1 year # To-do are all years from the year after incorporation until this year diff --git a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py index 08c2c9bea8..f8459a2376 100644 --- a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py +++ b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py @@ -17,7 +17,7 @@ Test-Suite to ensure that the /businesses endpoint is working as expected. """ import copy -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from http import HTTPStatus from typing import Final from unittest.mock import patch @@ -44,6 +44,7 @@ FILING_HEADER, INCORPORATION, INCORPORATION_FILING_TEMPLATE, + NOTICE_OF_WITHDRAWAL as SCHEMA_NOTICE_OF_WITHDRAWAL, REGISTRATION, SPECIAL_RESOLUTION, TRANSITION_FILING_TEMPLATE @@ -1595,3 +1596,87 @@ def test_resubmit_filing_failed(session, client, jwt, filing_status, review_stat headers=create_header(jwt, [STAFF_ROLE], identifier)) assert rv.status_code == HTTPStatus.UNAUTHORIZED + +@pytest.mark.parametrize( + 'test_name, legal_type, filing_type, filing_json, is_temp', + [ + ('T-BUSINESS-IA', 'BC', 'incorporationApplication', INCORPORATION, True), + ('T-BUSINESS-CONT-IN', 'BEN', 'continuationIn', CONTINUATION_IN, True), + ('T-BUSINESS-AMALGAMATION', 'CBEN', 'amalgamationApplication', AMALGAMATION_APPLICATION, True), + ('REGULAR-BUSINESS-COA', 'BC', 'changeOfAddress', CHANGE_OF_ADDRESS, False), + ('REGULAR-BUSINESS-CONT-ALTERATION', 'BEN', 'alteration', ALTERATION_FILING_TEMPLATE, False), + ('REGULAR-BUSINESS-DISSOLUTION', 'CBEN', 'dissolution', DISSOLUTION, False) + ] +) +def test_notice_of_withdraw_filing(session, client, jwt, test_name, legal_type, filing_type, filing_json, is_temp): + """Assert that notice of withdraw for new business filings can be filed""" + today = datetime.utcnow().date() + future_effective_date = today + timedelta(days=5) + future_effective_date = future_effective_date.isoformat() + # create a FE new business filing + if is_temp: + identifier = 'Tb31yQIuBw' + temp_reg = RegistrationBootstrap() + temp_reg._identifier = identifier + temp_reg.save() + json_data = copy.deepcopy(FILING_HEADER) + json_data['filing']['header']['name'] = filing_type + del json_data['filing']['business'] + new_bus_filing_json = copy.deepcopy(filing_json) + new_bus_filing_json['nameRequest']['legalType'] = legal_type + json_data['filing'][filing_type] = new_bus_filing_json + new_business_filing = factory_pending_filing(None, json_data) + new_business_filing.temp_reg = identifier + new_business_filing.effective_date = future_effective_date + new_business_filing.payment_completion_date = datetime.utcnow().isoformat() + new_business_filing.save() + withdrawn_filing_id = new_business_filing.id + # create a regular business and file a FE filing + else: + identifier = 'BC1234567' + founding_date = datetime.utcnow() - timedelta(days=5) + business = factory_business(identifier=identifier, founding_date=founding_date, entity_type=legal_type) + filing_data_reg_business = copy.deepcopy(FILING_HEADER) + filing_data_reg_business['filing']['header']['name'] = filing_type + filing_data_reg_business['filing']['business']['identifier'] = identifier + filing_data_reg_business['filing']['business']['legalType'] = legal_type + fe_filing_json = copy.deepcopy(filing_json) + filing_data_reg_business['filing'][filing_type] = fe_filing_json + fe_filing = factory_pending_filing(business, filing_data_reg_business) + fe_filing.effective_date = future_effective_date + fe_filing.payment_completion_date = datetime.utcnow().isoformat() + fe_filing.save() + withdrawn_filing_id = fe_filing.id + + # test filing a notice of withdraw for a temporary business + now_json_data = copy.deepcopy(FILING_HEADER) + now_json_data['filing']['header']['name'] = 'noticeOfWithdrawal' + if is_temp: + del now_json_data['filing']['business'] + now_json_data['filing']['business'] = { + "identifier": identifier, + "legalType": legal_type + } + else: + now_json_data['filing']['business']['identifier'] = identifier + now_json_data['filing']['business']['legalType'] = legal_type + now_json_data['filing']['noticeOfWithdrawal'] = copy.deepcopy(SCHEMA_NOTICE_OF_WITHDRAWAL) + now_json_data['filing']['noticeOfWithdrawal']['filingId'] = withdrawn_filing_id + del now_json_data['filing']['header']['filingId'] + + # Test validation OK + rv_validation = client.post(f'/api/v2/businesses/{identifier}/filings?only_validate=true', + json=now_json_data, + headers=create_header(jwt, [STAFF_ROLE], identifier)) + + assert rv_validation.status_code == HTTPStatus.OK + assert rv_validation.json['filing']['header']['name'] == 'noticeOfWithdrawal' + + # Test can create a draft + rv_draft = client.post(f'/api/v2/businesses/{identifier}/filings?draft=true', + json=now_json_data, + headers=create_header(jwt, [STAFF_ROLE], identifier)) + + # validate + assert rv_draft.status_code == HTTPStatus.CREATED + assert rv_draft.json['filing']['header']['name'] == 'noticeOfWithdrawal' diff --git a/legal-api/tests/unit/resources/v2/test_business_tasks.py b/legal-api/tests/unit/resources/v2/test_business_tasks.py index 979426ca9e..48b0fb8bd2 100644 --- a/legal-api/tests/unit/resources/v2/test_business_tasks.py +++ b/legal-api/tests/unit/resources/v2/test_business_tasks.py @@ -98,8 +98,8 @@ def test_get_tasks_no_filings(session, client, jwt): def test_get_tasks_next_year(session, client, jwt): """Assert that one todo item is returned in the calendar year following incorporation.""" identifier = 'CP7654321' - founding_date = datetime.today() + datedelta.datedelta(days=1) - datedelta.datedelta(years=1) - factory_business(identifier, founding_date=founding_date) # incorporation 1 year - 1 day ago + founding_date = datetime.today() - datedelta.datedelta(years=1) + factory_business(identifier, founding_date=founding_date) # incorporation 1 year # To-do are all years from the year after incorporation until this year diff --git a/legal-api/tests/unit/services/filings/validations/test_notice_of_withdrawal.py b/legal-api/tests/unit/services/filings/validations/test_notice_of_withdrawal.py index 4020aea84b..b4ea68ee92 100644 --- a/legal-api/tests/unit/services/filings/validations/test_notice_of_withdrawal.py +++ b/legal-api/tests/unit/services/filings/validations/test_notice_of_withdrawal.py @@ -44,7 +44,7 @@ ('EXIST_BUSINESS_SUCCESS', True, Filing.Status.PAID, True, True, None, None), ('EXIST_BUSINESS_FAIL_NOT_PAID', True, Filing.Status.PENDING, True, True, HTTPStatus.BAD_REQUEST, [FILING_NOT_PAID_MSG]), ('EXIST_BUSINESS_FAIL_NOT_FED', True, Filing.Status.PAID, False, True, HTTPStatus.BAD_REQUEST, [FILING_NOT_FED_MSG]), - ('EXIST_BUSINESS_FAIL_FILING_NOT_EXIST', False, Filing.Status.PAID, True, True, HTTPStatus.BAD_REQUEST, [FILING_NOT_EXIST_MSG]), + ('EXIST_BUSINESS_FAIL_FILING_NOT_EXIST', False, Filing.Status.PAID, True, True, HTTPStatus.NOT_FOUND, [FILING_NOT_EXIST_MSG]), ('EXIST_BUSINESS_FAIL_MISS_FILING_ID', True, Filing.Status.PAID, True, False, HTTPStatus.UNPROCESSABLE_ENTITY, ''), ('EXIST_BUSINESS_FAIL_NOT_PAID_NOT_FED', True, Filing.Status.PENDING, False, True, HTTPStatus.BAD_REQUEST, [FILING_NOT_FED_MSG, FILING_NOT_PAID_MSG]) ] diff --git a/legal-api/tests/unit/services/test_authorization.py b/legal-api/tests/unit/services/test_authorization.py index fa59a289fd..e8dc133a7e 100644 --- a/legal-api/tests/unit/services/test_authorization.py +++ b/legal-api/tests/unit/services/test_authorization.py @@ -1116,42 +1116,50 @@ def mock_auth(one, two): # pylint: disable=unused-argument; mocks of library me expected_lookup([FilingKey.AMALGAMATION_REGULAR, FilingKey.AMALGAMATION_VERTICAL, FilingKey.AMALGAMATION_HORIZONTAL, - FilingKey.IA_BC])), + FilingKey.IA_BC, + FilingKey.NOTICE_OF_WITHDRAWAL])), ('staff_no_business_c', False, Business.State.ACTIVE, ['C'], 'staff', [STAFF_ROLE], expected_lookup_continue_in_corps([FilingKey.AMALGAMATION_REGULAR, FilingKey.AMALGAMATION_VERTICAL, FilingKey.AMALGAMATION_HORIZONTAL, - FilingKey.CONTINUATION_IN_C])), + FilingKey.CONTINUATION_IN_C, + FilingKey.NOTICE_OF_WITHDRAWAL])), ('staff_no_business_ben', False, Business.State.ACTIVE, ['BEN'], 'staff', [STAFF_ROLE], expected_lookup([FilingKey.AMALGAMATION_REGULAR, FilingKey.AMALGAMATION_VERTICAL, FilingKey.AMALGAMATION_HORIZONTAL, - FilingKey.IA_BEN])), + FilingKey.IA_BEN, + FilingKey.NOTICE_OF_WITHDRAWAL])), ('staff_no_business_cben', False, Business.State.ACTIVE, ['CBEN'], 'staff', [STAFF_ROLE], expected_lookup_continue_in_corps([FilingKey.AMALGAMATION_REGULAR, FilingKey.AMALGAMATION_VERTICAL, FilingKey.AMALGAMATION_HORIZONTAL, - FilingKey.CONTINUATION_IN_CBEN])), + FilingKey.CONTINUATION_IN_CBEN, + FilingKey.NOTICE_OF_WITHDRAWAL])), ('staff_no_business_cc', False, Business.State.ACTIVE, ['CC'], 'staff', [STAFF_ROLE], expected_lookup([FilingKey.AMALGAMATION_REGULAR, FilingKey.AMALGAMATION_VERTICAL, FilingKey.AMALGAMATION_HORIZONTAL, - FilingKey.IA_CC])), + FilingKey.IA_CC, + FilingKey.NOTICE_OF_WITHDRAWAL])), ('staff_no_business_ccc', False, Business.State.ACTIVE, ['CCC'], 'staff', [STAFF_ROLE], expected_lookup_continue_in_corps([FilingKey.AMALGAMATION_REGULAR, FilingKey.AMALGAMATION_VERTICAL, FilingKey.AMALGAMATION_HORIZONTAL, - FilingKey.CONTINUATION_IN_CCC])), + FilingKey.CONTINUATION_IN_CCC, + FilingKey.NOTICE_OF_WITHDRAWAL])), ('staff_no_business_ulc', False, Business.State.ACTIVE, ['ULC'], 'staff', [STAFF_ROLE], expected_lookup([FilingKey.AMALGAMATION_REGULAR, FilingKey.AMALGAMATION_VERTICAL, FilingKey.AMALGAMATION_HORIZONTAL, - FilingKey.IA_ULC])), + FilingKey.IA_ULC, + FilingKey.NOTICE_OF_WITHDRAWAL])), ('staff_no_business_cul', False, Business.State.ACTIVE, ['CUL'], 'staff', [STAFF_ROLE], expected_lookup_continue_in_corps([FilingKey.AMALGAMATION_REGULAR, FilingKey.AMALGAMATION_VERTICAL, FilingKey.AMALGAMATION_HORIZONTAL, - FilingKey.CONTINUATION_IN_CUL])), + FilingKey.CONTINUATION_IN_CUL, + FilingKey.NOTICE_OF_WITHDRAWAL])), ('staff_no_business_llc', False, Business.State.ACTIVE, ['LLC'], 'staff', [STAFF_ROLE], []), ('staff_no_business_sp', False, Business.State.ACTIVE, ['SP'], 'staff', [STAFF_ROLE], expected_lookup([FilingKey.REG_SP])),