def validate(business: Business, court_order: Dict) -> Optional[Error]: """Validate the Court Order filing.""" if not business or not court_order: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': babel('A valid business and filing are required.') }]) msg = [] if not get_str(court_order, '/filing/courtOrder/orderDetails'): msg.append({ 'error': babel('Court Order is required.'), 'path': '/filing/courtOrder/orderDetails' }) effect_of_order = get_str(court_order, '/filing/courtOrder/effectOfOrder') if effect_of_order and effect_of_order != 'planOfArrangement': msg.append({ 'error': babel('Invalid effectOfOrder.'), 'path': '/filing/courtOrder/effectOfOrder' }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, con: Dict) -> Error: """Validate the Voluntary dissolution filing.""" if not business or not con: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] dissolution_date_path = '/filing/voluntaryDissolution/dissolutionDate' dissolution_date = get_date(con, dissolution_date_path) if not dissolution_date: msg.append({ 'error': _('Dissolution date must be provided.'), 'path': dissolution_date_path }) has_liabilities_path = '/filing/voluntaryDissolution/hasLiabilities' has_liabilities = get_bool(con, has_liabilities_path) if has_liabilities is None: msg.append({ 'error': _('Liabilities flag must be provided.'), 'path': has_liabilities_path }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(incorporation_json: Dict): """Validate the Incorporation filing.""" if not incorporation_json: return Error(HTTPStatus.BAD_REQUEST, [{ 'error': babel('A valid filing is required.') }]) msg = [] err = validate_offices(incorporation_json) if err: msg.extend(err) err = validate_roles(incorporation_json) if err: msg.extend(err) err = validate_parties_mailing_address(incorporation_json) if err: msg.extend(err) err = validate_share_structure( incorporation_json, coreFiling.FilingTypes.INCORPORATIONAPPLICATION.value) if err: msg.extend(err) err = validate_incorporation_effective_date(incorporation_json) if err: msg.extend(err) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, dissolution: Dict) -> Optional[Error]: """Validate the dissolution filing.""" if not business or not dissolution: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) legal_type = get_str(dissolution, '/filing/business/legalType') msg = [] err = validate_dissolution_type(dissolution, legal_type) if err: msg.extend(err) err = validate_dissolution_statement_type(dissolution, legal_type) if err: msg.extend(err) err = validate_parties_address(dissolution, legal_type) if err: msg.extend(err) err = validate_affidavit(dissolution, legal_type) if err: msg.extend(err) msg.extend(_validate_court_order(dissolution)) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, filing: Dict) -> Error: """Validate the Correction filing.""" if not business or not filing: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] # confirm corrected filing ID is a valid complete filing corrected_filing = Filing.find_by_id( filing['filing']['correction']['correctedFilingId']) if not corrected_filing or corrected_filing.status != Filing.Status.COMPLETED.value: path = '/filing/correction/correctedFilingId' msg.append({ 'error': _('Corrected filing is not a valid filing.'), 'path': path }) # confirm that this business owns the corrected filing elif not business.id == corrected_filing.business_id: path = '/filing/correction/correctedFilingId' msg.append({ 'error': _('Corrected filing is not a valid filing for this business.'), 'path': path }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, registrars_order: Dict) -> Optional[Error]: """Validate the Registrars Order filing.""" if not business or not registrars_order: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': babel('A valid business and filing are required.') }]) msg = [] effect_of_order = get_str(registrars_order, '/filing/registrarsOrder/effectOfOrder') if effect_of_order: if effect_of_order == 'planOfArrangement': file_number = get_str(registrars_order, '/filing/registrarsOrder/fileNumber') if not file_number: msg.append({ 'error': babel( 'Court Order Number is required when this filing is pursuant to a Plan of Arrangement.' ), 'path': '/filing/registrarsOrder/fileNumber' }) else: msg.append({ 'error': babel('Invalid effectOfOrder.'), 'path': '/filing/registrarsOrder/effectOfOrder' }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, filing_json: Dict) -> Error: """Validate the Special Resolution filing.""" if not business or not filing_json: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] resolution_path = '/filing/specialResolution/resolution' resolution_name = get_str(filing_json, resolution_path) if not resolution_name: msg.append({ 'error': _('Resolution must be provided.'), 'path': resolution_path }) err = validate_resolution_date(business, filing_json) msg.extend(err) err = validate_signing_date(filing_json) msg.extend(err) err = validate_signatory_name(filing_json) msg.extend(err) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate_ar_year(*, business: Business, current_annual_report: Dict) -> Error: """Validate that the annual report year is valid.""" ar_date = get_date(current_annual_report, 'filing/annualReport/annualReportDate') if not ar_date: return Error(HTTPStatus.BAD_REQUEST, [{'error': _('Annual Report Date must be a valid date.'), 'path': 'filing/annualReport/annualReportDate'}]) # The AR Date cannot be in the future (eg. before now() ) if ar_date > datetime.utcnow().date(): return Error(HTTPStatus.BAD_REQUEST, [{'error': _('Annual Report Date cannot be in the future.'), 'path': 'filing/annualReport/annualReportDate'}]) # The AR Date cannot be before the last AR Filed next_ar_year = (business.last_ar_year if business.last_ar_year else business.founding_date.year) + 1 ar_min_date, ar_max_date = business.get_ar_dates(next_ar_year) if ar_date < ar_min_date: return Error(HTTPStatus.BAD_REQUEST, [{'error': _('Annual Report Date cannot be before a previous Annual Report or the Founding Date.'), 'path': 'filing/annualReport/annualReportDate'}]) # AR Date must be the next contiguous year, from either the last AR or foundingDate if ar_date > ar_max_date: return Error(HTTPStatus.BAD_REQUEST, [{'error': _('Annual Report Date must be the next Annual Report in contiguous order.'), 'path': 'filing/annualReport/annualReportDate'}]) return None
def validate(business: Business, incorporation_json: Dict): """Validate the Incorporation filing.""" if not business or not incorporation_json: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': babel('A valid business and filing are required.') }]) msg = [] # What are the rules for saving an incorporation temp_identifier = incorporation_json['filing']['incorporationApplication'][ 'nameRequest']['nrNumber'] if business.identifier != temp_identifier: msg.append({ 'error': babel( 'Business Identifier does not match the identifier in filing.') }) err = validate_offices(incorporation_json) if err: msg.append(err) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, cod: Dict) -> Error: """Validate the Change of Directors filing.""" if not business or not cod: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] directors = cod['filing']['changeOfDirectors']['directors'] for idx, director in enumerate(directors): try: pycountry.countries.search_fuzzy( get_str(director, '/deliveryAddress/addressCountry'))[0].alpha_2 except LookupError: msg.append({ 'error': _('Address Country must resolve to a valid ISO-2 country.'), 'path': f'/filing/changeOfDirectors/directors/{idx}/deliveryAddress/addressCountry' }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(incorporation_json: Dict): """Validate the Incorporation filing.""" if not incorporation_json: return Error(HTTPStatus.BAD_REQUEST, [{'error': babel('A valid filing is required.')}]) msg = [] err = validate_offices(incorporation_json) if err: msg.append(err) err = validate_roles(incorporation_json) if err: msg.append(err) err = validate_parties_mailing_address(incorporation_json) if err: msg.append(err) err = validate_share_structure(incorporation_json) if err: msg.append(err) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(comment: Dict) -> Error: """Validate a standalone comment.""" if not comment: return Error(HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid comment is required.') }]) msg = [] comment_text_path = '/comment/comment' comment_text = get_str(comment, comment_text_path) if not comment_text: msg.append({ 'error': _('Comment text must be provided.'), 'path': comment_text_path }) filing_id_path = '/comment/filingId' filing_id = get_str(comment, filing_id_path) if not filing_id: msg.append({ 'error': _('Filing ID must be provided.'), 'path': filing_id_path }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, con: Dict) -> Error: """Validate the Special Resolution filing.""" if not business or not con: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] resolution_path = '/filing/specialResolution/resolution' resolution_name = get_str(con, resolution_path) if not resolution_name: msg.append({ 'error': _('Resolution must be provided.'), 'path': resolution_path }) resolution_date_path = '/filing/specialResolution/meetingDate' resolution_date = get_date(con, resolution_date_path) if not resolution_date: msg.append({ 'error': _('Resolution date must be provided.'), 'path': resolution_date_path }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, cod: Dict) -> Error: """Validate the Change ofAddress filing.""" if not business or not cod: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] # Check Delivery Address da_region_path = '/filing/changeOfAddress/deliveryAddress/addressRegion' if get_str(cod, da_region_path) != 'BC': msg.append({ 'error': _("Address Region must be 'BC'."), 'path': da_region_path }) da_country_path = '/filing/changeOfAddress/deliveryAddress/addressCountry' raw_da_country = get_str(cod, da_country_path) try: da_country = pycountry.countries.search_fuzzy( raw_da_country)[0].alpha_2 if da_country != 'CA': raise LookupError except LookupError: msg.append({ 'error': _("Address Country must be 'CA'."), 'path': da_country_path }) ma_region_path = '/filing/changeOfAddress/mailingAddress/addressRegion' if get_str(cod, ma_region_path) != 'BC': msg.append({ 'error': _("Address Region must be 'BC'."), 'path': ma_region_path }) ma_country_path = '/filing/changeOfAddress/mailingAddress/addressCountry' raw_ma_country = get_str(cod, ma_country_path) try: ma_country = pycountry.countries.search_fuzzy( raw_ma_country)[0].alpha_2 if ma_country != 'CA': raise LookupError except LookupError: msg.append({ 'error': _("Address Country must be 'CA'."), 'path': ma_country_path }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate_agm_year(*, business: Business, annual_report: Dict) -> Tuple[int, List[Dict]]: """Validate the AGM year.""" ar_date = get_date(annual_report, 'filing/annualReport/annualReportDate') if not ar_date: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('Annual Report Date must be a valid date.'), 'path': 'filing/annualReport/annualReportDate' }]) submission_date = get_date(annual_report, 'filing/header/date') if not submission_date: return Error(HTTPStatus.BAD_REQUEST, [{ 'error': _('Submission date must be a valid date.'), 'path': 'filing/header/date' }]) agm_date = get_date(annual_report, 'filing/annualReport/annualGeneralMeetingDate') if ar_date.year == submission_date.year \ and agm_date is None: return Error(HTTPStatus.BAD_REQUEST, [{ 'error': _('Annual General Meeting Date must be a valid date when ' 'submitting an Annual Report in the current year.'), 'path': 'filing/annualReport/annualGeneralMeetingDate' }]) # # ar filed for previous year, agm skipped, warn of pending dissolution # if agm_date is None and business.last_agm_date.year == (ar_date - datedelta.datedelta(years=1)).year: # return Error(HTTPStatus.OK, # [{'warning': _('Annual General Meeting Date (AGM) is being skipped. ' # 'If another AGM is skipped, the business will be dissolved.'), # 'path': 'filing/annualReport/annualGeneralMeetingDate'}]) # # # ar filed for previous year, agm skipped, warn of pending dissolution # if agm_date is None and business.last_agm_date.year <= (ar_date - datedelta.datedelta(years=2)).year: # return Error(HTTPStatus.OK, # [{'warning': _('Annual General Meeting Date (AGM) is being skipped. ' # 'The business will be dissolved, unless an extension and an AGM are held.'), # 'path': 'filing/annualReport/annualGeneralMeetingDate'}]) # return None
def validate(business: Business, filing: Dict) -> Error: """Validate the Correction filing.""" if not business or not filing: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] # confirm corrected filing ID is a valid complete filing corrected_filing = Filing.find_by_id( filing['filing']['correction']['correctedFilingId']) if not corrected_filing or corrected_filing.status != Filing.Status.COMPLETED.value: path = '/filing/correction/correctedFilingId' msg.append({ 'error': _('Corrected filing is not a valid filing.'), 'path': path }) # confirm that this business owns the corrected filing elif not business.id == corrected_filing.business_id: path = '/filing/correction/correctedFilingId' msg.append({ 'error': _('Corrected filing is not a valid filing for this business.'), 'path': path }) if err := has_at_least_one_share_class(filing, 'incorporationApplication'): msg.append({ 'error': _(err), 'path': '/filing/incorporationApplication/shareStructure' })
def validate(business: Business, cod: Dict) -> Error: """Validate the Change of Directors filing.""" if not business or not cod: return Error(HTTPStatus.BAD_REQUEST, [{'error': babel('A valid business and filing are required.')}]) msg = [] msg_directors_addresses = validate_directors_addresses(cod) if msg_directors_addresses: msg += msg_directors_addresses msg_effective_date = validate_effective_date(business, cod) if msg_effective_date: msg += msg_effective_date if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, filing: Dict) -> Error: # pylint: disable=too-many-branches """Validate the Alteration filing.""" if not business or not filing: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': babel('A valid business and filing are required.') }]) msg = [] msg.extend(company_name_validation(filing)) msg.extend(share_structure_validation(filing)) msg.extend(court_order_validation(filing)) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate_correction_ia(filing: Dict) -> Optional[Error]: """Validate correction of Incorporation Application.""" if not (corrected_filing # pylint: disable=superfluous-parens; needed to pass pylance := Filing.find_by_id( filing['filing']['correction']['correctedFilingId'])): return Error( HTTPStatus.BAD_REQUEST, [{ 'error': babel('Missing the id of the filing being corrected.') }])
def validate(business: Business, con: Dict) -> Error: """Validate the Change of Name filing.""" if not business or not con: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] legal_name_path = '/filing/changeOfName/legalName' legal_name = get_str(con, legal_name_path) if not legal_name: msg.append({ 'error': _('Legal Name must be provided.'), 'path': legal_name_path }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, cod: Dict) -> Error: """Validate the Change ofAddress filing.""" if not business or not cod: return Error( HTTPStatus.BAD_REQUEST, [{ 'error': _('A valid business and filing are required.') }]) msg = [] offices_array = json.dumps(cod['filing']['changeOfAddress']['offices']) addresses = json.loads(offices_array) for item in addresses.keys(): for k, v in addresses[item].items(): region = v['addressRegion'] country = v['addressCountry'] if region != 'BC': path = '/filing/changeOfAddress/offices/%s/%s/addressRegion' % ( item, k) msg.append({ 'error': _("Address Region must be 'BC'."), 'path': path }) try: country = pycountry.countries.search_fuzzy(country)[0].alpha_2 if country != 'CA': raise LookupError except LookupError: err_path = '/filing/changeOfAddress/offices/%s/%s/addressCountry' % ( item, k) msg.append({ 'error': _("Address Country must be 'CA'."), 'path': err_path }) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(incorporation_json: Dict): """Validate the Incorporation filing.""" if not incorporation_json: return Error(HTTPStatus.BAD_REQUEST, [{ 'error': babel('A valid filing is required.') }]) legal_type = get_str(incorporation_json, '/filing/business/legalType') msg = [] err = validate_offices(incorporation_json) if err: msg.extend(err) err = validate_roles(incorporation_json) if err: msg.extend(err) err = validate_parties_mailing_address(incorporation_json) if err: msg.extend(err) if legal_type == Business.LegalTypes.BCOMP.value: err = validate_share_structure( incorporation_json, coreFiling.FilingTypes.INCORPORATIONAPPLICATION.value) if err: msg.extend(err) elif legal_type == Business.LegalTypes.COOP.value: err = validate_cooperative_documents(incorporation_json) if err: msg.extend(err) err = validate_incorporation_effective_date(incorporation_json) if err: msg.extend(err) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None
def validate(business: Business, filing: Dict) -> Error: # pylint: disable=too-many-branches """Validate the Alteration filing.""" if not business or not filing: return Error(HTTPStatus.BAD_REQUEST, [{'error': babel('A valid business and filing are required.')}]) msg = [] msg.extend(company_name_validation(filing)) msg.extend(share_structure_validation(filing)) msg.extend(court_order_validation(filing)) msg.extend(type_change_validation(filing)) if err := has_at_least_one_share_class(filing, 'alteration'): msg.append({'error': babel(err), 'path': '/filing/alteration/shareStructure'})
def validate_ar_year(*, business: Business, previous_annual_report: Dict, current_annual_report: Dict) -> Error: """Validate that the annual report year is valid.""" ar_date = get_date(current_annual_report, 'filing/annualReport/annualReportDate') if not ar_date: return Error(HTTPStatus.BAD_REQUEST, [{'error': _('Annual Report Date must be a valid date.'), 'path': 'filing/annualReport/annualReportDate'}]) # The AR Date cannot be in the future (eg. before now() ) if ar_date > datetime.utcnow().date(): return Error(HTTPStatus.BAD_REQUEST, [{'error': _('Annual Report Date cannot be in the future.'), 'path': 'filing/annualReport/annualReportDate'}]) # The AR Date cannot be before the last AR Filed # or in or before the foundingDate expected_date = get_date(previous_annual_report, 'filing/annualReport/annualReportDate') if expected_date: expected_date += datedelta.YEAR elif business.last_ar_date: expected_date = business.last_ar_date + datedelta.YEAR else: expected_date = business.founding_date + datedelta.YEAR if ar_date.year < expected_date.year: return Error(HTTPStatus.BAD_REQUEST, [{'error': _('Annual Report Date cannot be before a previous Annual Report or the Founding Date.'), 'path': 'filing/annualReport/annualReportDate'}]) # AR Date must be the next contiguous year, from either the last AR or foundingDate if ar_date.year > expected_date.year: return Error(HTTPStatus.BAD_REQUEST, [{'error': _('Annual Report Date must be the next Annual Report in contiguous order.'), 'path': 'filing/annualReport/annualReportDate'}]) return None
def validate(business: Business, filing_json: Dict) -> Error: # pylint: disable=too-many-branches """Validate the filing JSON.""" err = validate_against_schema(filing_json) if err: return err err = None # check if this is a correction - if yes, ignore all other filing types in the filing since they will be validated # differently in a future version of corrections if 'correction' in filing_json['filing'].keys(): err = correction_validate(business, filing_json) if err: return err # For now the correction validators will get called here, these might be the same rules # so these 2 sections could get collapsed for k in filing_json['filing'].keys(): # Check if the JSON key exists in the FILINGS reference Dictionary if Filing.FILINGS.get(k, None): if k == Filing.FILINGS['changeOfAddress'].get('name'): err = coa_validate(business, filing_json) elif k == Filing.FILINGS['incorporationApplication'].get( 'name'): err = validate_correction_ia(filing_json) if err: return err elif 'dissolution' in filing_json['filing'].keys() \ and (dissolution_type := filing_json['filing']['dissolution'].get('dissolutionType', None)) \ and dissolution_type == 'voluntary': err = dissolution_validate(business, filing_json) if err: return err legal_type = get_str(filing_json, '/filing/business/legalType') if legal_type == Business.LegalTypes.COOP.value: if 'specialResolution' in filing_json['filing'].keys(): err = special_resolution_validate(business, filing_json) else: err = Error( HTTPStatus.BAD_REQUEST, [{ 'error': babel('Special Resolution is required.'), 'path': '/filing/specialResolution' }]) if err: return err
def validate_against_schema(json_data: Dict = None) -> Error: """Validate against the filing schema. Returns: int: status code of the validation operation using HTTPStatus List[Dict]: a list of errors defined as {error:message, path:schemaPath} """ valid, err = rsbc_schemas.validate(json_data, 'comment') if valid: return None errors = [] for error in err: errors.append({'path': '/'.join(error.path), 'error': error.message}) return Error(HTTPStatus.UNPROCESSABLE_ENTITY, errors)
def validate(business: Business, annual_report: Dict) -> Error: """Validate the annual report JSON.""" if not business or not annual_report: return Error(HTTPStatus.BAD_REQUEST, [{'error': _('A valid business and filing are required.')}]) err = validate_ar_year(business=business, current_annual_report=annual_report) if err: return err if requires_agm(business): err = validate_agm_year(business=business, annual_report=annual_report) if err: return err return None
def validate(business: Business, annual_report: Dict) -> Error: """Validate the annual report JSON.""" if not business or not annual_report: return Error(HTTPStatus.BAD_REQUEST, [{'error': _('A valid business and filing are required.')}]) last_filing = Filing.get_a_businesses_most_recent_filing_of_a_type( business.id, Filing.FILINGS['annualReport']['name']) err = validate_ar_year(business=business, previous_annual_report=last_filing, current_annual_report=annual_report) if err: return err if requires_agm(business): err = validate_agm_year(business=business, annual_report=annual_report) if err: return err return None
filing['filing']['correction']['correctedFilingId'])): return Error( HTTPStatus.BAD_REQUEST, [{ 'error': babel('Missing the id of the filing being corrected.') }]) msg = [] if err := validate_correction_name_request(filing, corrected_filing): msg.extend(err) if err := validate_correction_effective_date(filing, corrected_filing): msg.append(err) if msg: return Error(HTTPStatus.BAD_REQUEST, msg) return None def validate_correction_effective_date( filing: Dict, corrected_filing: Dict) -> Optional[Dict]: """Validate that effective dates follow the rules. Currently effective dates cannot be changed. """ if new_effective_date := filing.get('filing', {}).get('header', {}).get('effectiveDate'): if new_effective_date != corrected_filing.get('filing', {}).get( 'header', {}).get('effectiveDate'):