From 5bbec294c08517ce65b13760567395b9e74925e6 Mon Sep 17 00:00:00 2001 From: Kial Jinnah Date: Thu, 12 Mar 2026 14:31:04 -0400 Subject: [PATCH 1/8] Business API - correction updates to support relationships schema and liquidators/receivers, liquidator tweaks Signed-off-by: Kial Jinnah --- legal-api/poetry.lock | 16 +- legal-api/pyproject.toml | 4 +- .../resources/v2/business/colin_sync.py | 110 ++++++++---- .../validations/change_of_liquidators.py | 14 +- .../validations/change_of_receivers.py | 2 +- .../filings/validations/common_validations.py | 164 ++++++++++++++---- .../filings/validations/correction.py | 16 ++ .../filings/validations/transition.py | 2 +- legal-api/tests/unit/models/__init__.py | 13 ++ .../unit/resources/v2/test_colin_sync.py | 83 ++++++++- .../filings/validations/test_correction.py | 40 ++++- 11 files changed, 370 insertions(+), 94 deletions(-) diff --git a/legal-api/poetry.lock b/legal-api/poetry.lock index 200c993859..9714cf7012 100644 --- a/legal-api/poetry.lock +++ b/legal-api/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.1 and should not be changed by hand. [[package]] name = "alembic" @@ -785,11 +785,11 @@ files = [ ] [package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" +google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0.dev0" +google-auth = ">=1.25.0,<3.0.dev0" [package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] +grpc = ["grpcio (>=1.38.0,<2.0.dev0)", "grpcio-status (>=1.38.0,<2.0.dev0)"] [[package]] name = "google-cloud-datastore" @@ -1237,7 +1237,7 @@ fqdn = {version = "*", optional = true, markers = "extra == \"format\""} idna = {version = "*", optional = true, markers = "extra == \"format\""} isoduration = {version = "*", optional = true, markers = "extra == \"format\""} jsonpointer = {version = ">1.13", optional = true, markers = "extra == \"format\""} -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format\""} rfc3987 = {version = "*", optional = true, markers = "extra == \"format\""} @@ -2231,8 +2231,8 @@ strict-rfc3339 = "*" [package.source] type = "git" url = "https://github.com/bcgov/business-schemas.git" -reference = "2.18.63" -resolved_reference = "0e584cc385d1cd00722f96032af346b48f875d72" +reference = "2.18.64" +resolved_reference = "18b577402737130667e275d78a64e7a1ab3c9751" [[package]] name = "reportlab" @@ -3134,4 +3134,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9.22,<3.10" -content-hash = "1cb0b50b3e444405eec28df9a200178315601fa43e355662e85a1b1db1a4c03e" +content-hash = "9b6d5880cd7ec64a44c915635951a73309acfa6f88b1c32c2e1e888a8ca783ac" diff --git a/legal-api/pyproject.toml b/legal-api/pyproject.toml index 6a0c91de97..505f463167 100644 --- a/legal-api/pyproject.toml +++ b/legal-api/pyproject.toml @@ -52,7 +52,7 @@ dependencies = [ "blinker (==1.4)", "pyjwt (==2.8.0)", - "registry_schemas @ git+https://github.com/bcgov/business-schemas.git@2.18.63#egg=registry_schemas", + "registry_schemas @ git+https://github.com/bcgov/business-schemas.git@2.18.64#egg=registry_schemas", "sql-versioning @ git+https://github.com/bcgov/lear.git@main#subdirectory=python/common/sql-versioning", "gcp-queue @ git+https://github.com/bcgov/sbc-connect-common.git@main#subdirectory=python/gcp-queue", "structured-logging @ git+https://github.com/bcgov/sbc-connect-common.git@main#subdirectory=python/structured-logging" @@ -174,7 +174,7 @@ minversion = "2.0" testpaths = [ "tests", ] -addopts = "--cov=src/legal_api" +# addopts = "--cov=src/legal_api" python_files = [ "test*.py" ] diff --git a/legal-api/src/legal_api/resources/v2/business/colin_sync.py b/legal-api/src/legal_api/resources/v2/business/colin_sync.py index c33c306b3e..db9e308f0c 100644 --- a/legal-api/src/legal_api/resources/v2/business/colin_sync.py +++ b/legal-api/src/legal_api/resources/v2/business/colin_sync.py @@ -52,7 +52,7 @@ @bp.route("/internal/filings", methods=["GET"]) @cross_origin(origin="*") @jwt.has_one_of_roles([UserRoles.colin]) -def get_completed_filings_for_colin(): # noqa: PLR0912 +def get_completed_filings_for_colin(): """Get filings by status formatted in json.""" filings = [] @@ -62,41 +62,20 @@ def get_completed_filings_for_colin(): # noqa: PLR0912 for filing in pending_filings: business = Business.find_by_internal_id(filing.business_id) - filing_json = copy.deepcopy(filing.filing_json) - filing_json["filingId"] = filing.id - filing_json["filing"]["header"]["source"] = Filing.Source.LEAR.value - filing_json["filing"]["header"]["date"] = (filing.payment_completion_date or filing.filing_date).isoformat() - filing_json["filing"]["header"]["learEffectiveDate"] = filing.effective_date.isoformat() - filing_json["filing"]["header"]["isFutureEffective"] = filing.is_future_effective - filing_json["filing"]["header"]["hideInLedger"] = filing.hide_in_ledger - - if filing.submitter_roles: - filing_json["filing"]["header"]["isStaff"] = ( - UserRoles.staff in filing.submitter_roles or UserRoles.system in filing.submitter_roles - ) - if filing.filing_submitter: - filing_json["filing"]["header"]["filedBy"] = { - "userName": filing.filing_submitter.username, - "firstName": filing.filing_submitter.firstname, - "lastName": filing.filing_submitter.lastname, - "email": filing.filing_submitter.email - } - - if not filing_json["filing"].get("business"): - if filing.transaction_id: - business_revision = VersionedBusinessDetailsService.get_business_revision_obj(filing, business.id) - filing_json["filing"]["business"] = VersionedBusinessDetailsService.business_revision_json( - business_revision, business.json()) - else: - # should never happen unless its a test data created directly in db. - # found some filing in DEV, adding this check to avoid exception - filing_json["filing"]["business"] = business.json() - elif not filing_json["filing"]["business"].get("legalName"): - filing_json["filing"]["business"]["legalName"] = business.legal_name + filing_json = _get_initial_filing_json(filing, business) if filing.filing_type == "correction" and business.legal_type != Business.LegalTypes.COOP.value: try: set_correction_flags(filing_json, filing) + has_non_party_change = has_alias_changed(filing) or has_office_changed(filing) or has_resolution_changed(filing) or has_share_changed(filing) + inner_filing_json = filing_json["filing"].get(filing.filing_type, {}) + # NOTE: has_party_changed specifically checks for Director changes + if inner_filing_json.get("relationships") and has_party_changed(filing): + # set directors and completing party from the db when filing_json is using relationships schema - ignore other parties + _set_relationship_parties(business, filing, inner_filing_json) + elif not filing.meta_data.get("commentOnly", False) and not has_non_party_change: + # Only change was to non director parties so skip the colin sync for this correction + continue except Exception as ex: current_app.logger.error(f"correction: filingId={filing.id}, error: {ex!s}") # to skip this filing and block subsequent filing from syncing in update-colin-filings @@ -127,6 +106,43 @@ def get_completed_filings_for_colin(): # noqa: PLR0912 return jsonify({"filings": filings}), HTTPStatus.OK +def _get_initial_filing_json(filing: Filing, business: Business): + """Return the initial filing json with common colin sync values set.""" + filing_json = copy.deepcopy(filing.filing_json) + filing_json["filingId"] = filing.id + filing_json["filing"]["header"]["source"] = Filing.Source.LEAR.value + filing_json["filing"]["header"]["date"] = (filing.payment_completion_date or filing.filing_date).isoformat() + filing_json["filing"]["header"]["learEffectiveDate"] = filing.effective_date.isoformat() + filing_json["filing"]["header"]["isFutureEffective"] = filing.is_future_effective + filing_json["filing"]["header"]["hideInLedger"] = filing.hide_in_ledger + + if filing.submitter_roles: + filing_json["filing"]["header"]["isStaff"] = ( + UserRoles.staff in filing.submitter_roles or UserRoles.system in filing.submitter_roles + ) + if filing.filing_submitter: + filing_json["filing"]["header"]["filedBy"] = { + "userName": filing.filing_submitter.username, + "firstName": filing.filing_submitter.firstname, + "lastName": filing.filing_submitter.lastname, + "email": filing.filing_submitter.email + } + + if not filing_json["filing"].get("business"): + if filing.transaction_id: + business_revision = VersionedBusinessDetailsService.get_business_revision_obj(filing, business.id) + filing_json["filing"]["business"] = VersionedBusinessDetailsService.business_revision_json( + business_revision, business.json()) + else: + # should never happen unless its a test data created directly in db. + # found some filing in DEV, adding this check to avoid exception + filing_json["filing"]["business"] = business.json() + elif not filing_json["filing"]["business"].get("legalName"): + filing_json["filing"]["business"]["legalName"] = business.legal_name + + return filing_json + + def set_correction_flags(filing_json, filing: Filing): """Set what section changed in this correction.""" if filing.meta_data.get("commentOnly", False): @@ -324,6 +340,36 @@ def _set_shares(primary_or_holding_business, amalgamation_filing, transaction_id amalgamation_filing["shareStructure"]["resolutionDates"] = business_dates +def _map_entity_to_officer(entity: dict[str, str]): + return { + "firstName": entity.get("givenName"), + "lastName": entity.get("familyName"), + "middleInitial": entity.get("middleInitial"), + "organizationName": entity.get("businessName"), + "id": entity.get("identifier") + } + + +def _set_relationship_parties(business: Business, filing: Filing, filing_json: dict): + """Override filing_json with parties set from the db. Only Directors and Completing Party will be added.""" + parties: list = VersionedBusinessDetailsService.get_party_role_revision(filing, + business.id, + role=PartyRole.RoleTypes.DIRECTOR.value) + + # copy completing party from filing json + for party_info in filing_json.get("relationships"): + if comp_party_role := next((x for x in party_info.get("roles") + if x["roleType"].lower() == "completing party"), None): + mapped_party_info = { + "officer": _map_entity_to_officer(party_info["entity"]), + "roles": [comp_party_role] + } + parties.append(mapped_party_info) + break + + filing_json["parties"] = parties + + @bp.route("/internal/filings/", methods=["PATCH"]) @cross_origin(origin="*") @jwt.has_one_of_roles([UserRoles.colin]) diff --git a/legal-api/src/legal_api/services/filings/validations/change_of_liquidators.py b/legal-api/src/legal_api/services/filings/validations/change_of_liquidators.py index 6b74dc91fe..452842731b 100644 --- a/legal-api/src/legal_api/services/filings/validations/change_of_liquidators.py +++ b/legal-api/src/legal_api/services/filings/validations/change_of_liquidators.py @@ -52,14 +52,22 @@ def validate(business: Business, filing_json: dict) -> Optional[Error]: business, filing_json, filing_type, - PartyRole.RoleTypes.LIQUIDATOR, + [PartyRole.RoleTypes.LIQUIDATOR], filing_sub_type in ["appointLiquidator", "intentToLiquidate"], filing_sub_type in ["ceaseLiquidator", "changeAddressLiquidator"] )) if filing_json["filing"][filing_type].get("offices"): - allowed_offices = ["liquidationRecordsOffice"] if filing_sub_type in ["intentToLiquidate", "changeAddressLiquidator"] else [] - required_offices = ["liquidationRecordsOffice"] if filing_sub_type in ["intentToLiquidate"] else [] + allowed_offices = [] + required_offices = [] + if filing_sub_type in ["intentToLiquidate", "changeAddressLiquidator"]: + allowed_offices = ["liquidationRecordsOffice"] + if filing_sub_type == "intentToLiquidate": + required_offices = ["liquidationRecordsOffice"] + elif filing_sub_type == "appointLiquidator" and not business.in_liquidation: + allowed_offices = ["liquidationRecordsOffice"] + required_offices = ["liquidationRecordsOffice"] + msg.extend(validate_offices(filing_json, filing_type, allowed_offices, required_offices, False)) if msg: diff --git a/legal-api/src/legal_api/services/filings/validations/change_of_receivers.py b/legal-api/src/legal_api/services/filings/validations/change_of_receivers.py index f17599dc04..f482a575fd 100644 --- a/legal-api/src/legal_api/services/filings/validations/change_of_receivers.py +++ b/legal-api/src/legal_api/services/filings/validations/change_of_receivers.py @@ -51,7 +51,7 @@ def validate(business: Business, filing_json: dict) -> Optional[Error]: business, filing_json, filing_type, - PartyRole.RoleTypes.RECEIVER, + [PartyRole.RoleTypes.RECEIVER], filing_sub_type in ["amendReceiver", "appointReceiver"], filing_sub_type in ["amendReceiver", "ceaseReceiver", "changeAddressReceiver"] )) diff --git a/legal-api/src/legal_api/services/filings/validations/common_validations.py b/legal-api/src/legal_api/services/filings/validations/common_validations.py index 69f86bf786..42587dec21 100644 --- a/legal-api/src/legal_api/services/filings/validations/common_validations.py +++ b/legal-api/src/legal_api/services/filings/validations/common_validations.py @@ -538,9 +538,10 @@ def validate_relationships( # noqa: PLR0913 business: Business, filing_json: dict, filing_type: str, - role_type: PartyRole.RoleTypes, + role_types: list[PartyRole.RoleTypes], allow_new: bool, - allow_edits: bool + allow_edits: bool, + role_types_for_colin_sync: Optional[list[PartyRole.RoleTypes]] = None ) -> list: """Validate the relationships information.""" msg = [] @@ -548,75 +549,176 @@ def validate_relationships( # noqa: PLR0913 party_path = f"/filing/{filing_type}/relationships" # get relevant parties for the business - party_roles: list[PartyRole] = PartyRole.get_party_roles(business.id, - datetime.now(tz=timezone.utc).date(), - role_type.value) + today = datetime.now(tz=timezone.utc).date() + party_roles: list[PartyRole] = [] + for role_type in role_types: + if roles := PartyRole.get_party_roles(business.id, today, role_type.value): + party_roles.extend(roles) + party_ids = [str(party_role.party_id) for party_role in party_roles] # Check if party is a valid party of the given role for index, relationship in enumerate(relationships): + path = f"{party_path}/{index}" identifier = relationship.get("entity", {}).get("identifier") if identifier and not allow_edits: - msg.append({"error": "Relationship edits are not allowed in this filing.", "path": f"{party_path}/{index}/entity"}) + msg.append({"error": "Relationship edits are not allowed in this filing.", "path": f"{path}/entity"}) elif identifier and identifier not in party_ids: - msg.append({"error": "Relationship with this identifier is not valid for this filing.", "path": f"{party_path}/{index}/entity/identifier"}) + msg.append({"error": "Relationship with this identifier is not valid for this filing.", "path": f"{path}/entity/identifier"}) elif not identifier and not allow_new: - msg.append({"error": "New Relationships are not allowed in this filing.", "path": f"{party_path}/{index}/entity"}) + msg.append({"error": "New Relationships are not allowed in this filing.", "path": f"{path}/entity"}) - msg.extend(validate_relationship_entity_name(relationship, party_path, index)) + msg.extend(validate_relationship_entity_name(relationship, path)) + msg.extend(validate_relationship_roles(relationship["roles"], role_types, path)) + + # Below is for colin sync checking only (i.e. any relationship with Director roles) + converted_sync_roles = [role.value.lower().replace(" ", "_") for role in role_types_for_colin_sync or []] + if any(role for role in relationship["roles"] if role["roleType"].lower() in converted_sync_roles): + validate_relationship_entity_colin_sync(relationship, business.legal_type, f"{path}/entity") msg.extend(validate_parties_addresses(filing_json, filing_type, "relationships")) - return msg -def validate_relationship_entity_name(party: dict, party_path: str, index: int) -> list: - """Validate relationship entity name.""" - msg = [] - - entity = party["entity"] - organization_name = entity.get("businessName", None) - family_name = entity.get("familyName", None) +def _get_relationship_entity_values(party: dict) -> tuple[str, str, str, str, str, str]: + """Return the expected mapped entity values.""" + entity: dict[str, str] = party["entity"] + organization_name = entity.get("businessName") or "" + given_name = entity.get("givenName") or "" + family_name = entity.get("familyName") or "" + middle_initial = entity.get("middleInitial") or "" party_type = "person" if family_name else "organization" party_roles = [x.get("roleType") for x in party["roles"]] party_roles_str = ", ".join(party_roles) + return organization_name, given_name, family_name, middle_initial, party_type, party_roles_str + +def validate_relationship_entity_name(party: dict, path: str) -> list: + """Validate relationship entity name.""" + msg = [] + organization_name, given_name, family_name, middle_initial, party_type, party_roles_str = _get_relationship_entity_values(party) + entity = party["entity"] if party_type == "person": # Only familyName is required if not family_name or not family_name.strip(): - msg.append({"error": f"{party_roles_str} familyName is required", "path": f"{party_path}/{index}/entity/familyName"}) + msg.append({"error": f"{party_roles_str} familyName is required", "path": f"{path}/entity/familyName"}) if organization_name: err_msg = f"{party_roles_str} businessName should not be set for a person relationship entity" - msg.append({"error": err_msg, "path": f"{party_path}/{index}/entity/businessName"}) + msg.append({"error": err_msg, "path": f"{path}/entity/businessName"}) if entity.get("businessIdentifier"): err_msg = f"{party_roles_str} businessIdentifier should not be set for a person relationship entity" - msg.append({"error": err_msg, "path": f"{party_path}/{index}/entity/businessIdentifier"}) + msg.append({"error": err_msg, "path": f"{path}/entity/businessIdentifier"}) elif party_type == "organization": if not organization_name or not organization_name.strip(): - msg.append({"error": f"{party_roles_str} businessName is required", "path": f"{party_path}/{index}/entity/businessName"}) + msg.append({"error": f"{party_roles_str} businessName is required", "path": f"{path}/entity/businessName"}) - if entity.get("givenName") not in (None, ""): + if given_name not in (None, ""): err_msg = f"{party_roles_str} givenName should not be set for an organization relationship entity" - msg.append({"error": err_msg, "path": f"{party_path}/{index}/entity/givenName"}) + msg.append({"error": err_msg, "path": f"{path}/entity/givenName"}) - if entity.get("middleInitial") not in (None, ""): + if middle_initial not in (None, ""): err_msg = f"{party_roles_str} middleInitial should not be set for an organization relationship entity" - msg.append({"error": err_msg, "path": f"{party_path}/{index}/entity/middleInitial"}) - - if entity.get("additionalName") not in (None, ""): - err_msg = f"{party_roles_str} additionalName should not be set for an organization relationship entity" - msg.append({"error": err_msg, "path": f"{party_path}/{index}/entity/additionalName"}) + msg.append({"error": err_msg, "path": f"{path}/entity/middleInitial"}) if entity.get("alternateName") not in (None, ""): err_msg = f"{party_roles_str} alternateName should not be set for an organization relationship entity" - msg.append({"error": err_msg, "path": f"{party_path}/{index}/entity/alternateName"}) + msg.append({"error": err_msg, "path": f"{path}/entity/alternateName"}) if entity.get("fullName") not in (None, ""): err_msg = f"{party_roles_str} fullName should not be set for an organization relationship entity" - msg.append({"error": err_msg, "path": f"{party_path}/{index}/entity/fullName"}) + msg.append({"error": err_msg, "path": f"{path}/entity/fullName"}) + + return msg + + +def _validate_relationship_entity_person_colin_sync(relationship: dict, legal_type: str, entity_path: str, custom_max_length: int, family_name_max_length: int): + """Validate the relationship entity name for a person for the COLIN sync.""" + msg = [] + _, given_name, family_name, middle_initial, _, party_roles_str = _get_relationship_entity_values(relationship) + stripped_given_name = given_name.strip() + if (legal_type in Business.CORPS) and (not stripped_given_name): + msg.append({"error": f"{party_roles_str} giveName is required", "path": f"{entity_path}/givenName"}) + elif given_name != stripped_given_name: + msg.append({ + "error": f"{party_roles_str} giveName cannot start or end with whitespace", + "path": f"{entity_path}/givenName" + }) + elif len(given_name) > custom_max_length: + err_msg = f"{party_roles_str} given name cannot be longer than {custom_max_length} characters" + msg.append({"error": err_msg, "path": f"{entity_path}/givenName"}) + + stripped_middle_initial = middle_initial.strip() + if middle_initial is not None and stripped_middle_initial: + if middle_initial != stripped_middle_initial: + msg.append({"error": f"{party_roles_str} middleInitial cannot start or end with whitespace", + "path": f"{entity_path}/middleInitial"}) + elif len(middle_initial) > custom_max_length: + err_msg = f"{party_roles_str} middleInitial cannot be longer than {custom_max_length} characters" + msg.append({"error": err_msg, "path": f"{entity_path}/middleInitial"}) + + stripped_family_name = family_name.strip() + if (legal_type in Business.CORPS) and (not stripped_family_name): + msg.append({"error": f"{party_roles_str} familyName is required", "path": f"{entity_path}/familyName"}) + elif family_name != stripped_family_name: + msg.append({ + "error": f"{party_roles_str} familyName cannot start or end with whitespace", + "path": f"{entity_path}/familyName" + }) + elif len(family_name) > family_name_max_length: + err_msg = f"{party_roles_str} family name cannot be longer than {family_name_max_length} characters" + msg.append({"error": err_msg, "path": f"{entity_path}/familyName"}) + + return msg + + +def _validate_relationship_entity_org_colin_sync(relationship: dict, entity_path: str, max_length: int): + """Validate the relationship entity name for an organization for the COLIN sync.""" + msg = [] + organization_name, _, _, _, _, party_roles_str = _get_relationship_entity_values(relationship) + stripped = organization_name.strip() + if organization_name != stripped: + err_msg = f"{party_roles_str} businessName cannot start or end with whitespace" + msg.append({"error": err_msg, "path": f"{entity_path}/businessName"}) + elif len(organization_name) > max_length: + err_msg = f"{party_roles_str} businessName cannot be longer than {max_length} characters" + msg.append({"error": err_msg, "path": f"{entity_path}/businessName"}) + + return msg + + +def validate_relationship_entity_colin_sync(relationship: dict, legal_type: str, path: str) -> list: + """Validate the relationship entity name for COLIN sync.""" + # FUTURE: This validation should be removed when COLIN sync is no longer required. + msg = [] + + custom_allowed_max_length = 20 + family_name_max_length = PARTY_NAME_MAX_LENGTH + + _, _, _, _, party_type, _ = _get_relationship_entity_values(relationship) + + if party_type == "person": + msg.extend(_validate_relationship_entity_person_colin_sync(relationship, legal_type, path, custom_allowed_max_length, family_name_max_length)) + + elif party_type == "organization": + msg.extend(_validate_relationship_entity_org_colin_sync(relationship, path, family_name_max_length)) + + return msg + + +def validate_relationship_roles(roles: list[dict[str, str]], + allowed_roles: list[PartyRole.RoleTypes], + path: str) -> list: + """Validate relationship roles.""" + msg = [] + converted_allowed_roles = [role.value.lower().replace(" ", "_") for role in allowed_roles] + for index, role in enumerate(roles): + if role.get("roleType").lower() not in converted_allowed_roles: + err_msg = "Invalid role type for this filing." + msg.append({"error": err_msg, "path": f"{path}/{index}/roleType"}) + # FUTURE: appointment/cessation date checks (currently set to filing effective date by filer) return msg diff --git a/legal-api/src/legal_api/services/filings/validations/correction.py b/legal-api/src/legal_api/services/filings/validations/correction.py index 951a35b43d..9da1cc83f1 100644 --- a/legal-api/src/legal_api/services/filings/validations/correction.py +++ b/legal-api/src/legal_api/services/filings/validations/correction.py @@ -30,6 +30,7 @@ validate_parties_addresses, validate_parties_names, validate_pdf, + validate_relationships, validate_resolution_date_in_share_structure, validate_share_structure, ) @@ -79,6 +80,21 @@ def validate(business: Business, filing: dict) -> Error: if not is_comment_only_correction: if filing.get("filing", {}).get("correction", {}).get("parties", None): msg.extend(validate_parties_addresses(filing, filing_type)) + if filing.get("filing", {}).get("correction", {}).get("relationships", None): + msg.extend(validate_relationships( + business, + filing, + filing_type, + [ + PartyRole.RoleTypes.DIRECTOR, + PartyRole.RoleTypes.LIQUIDATOR, + PartyRole.RoleTypes.RECEIVER, + PartyRole.RoleTypes.COMPLETING_PARTY + ], + True, + True, + [PartyRole.RoleTypes.DIRECTOR, PartyRole.RoleTypes.COMPLETING_PARTY] + )) if filing.get("filing", {}).get("correction", {}).get("offices", None): msg.extend(validate_offices_addresses(filing, filing_type)) # validations for firms diff --git a/legal-api/src/legal_api/services/filings/validations/transition.py b/legal-api/src/legal_api/services/filings/validations/transition.py index 6ca9cc08e0..6bcda8f52d 100644 --- a/legal-api/src/legal_api/services/filings/validations/transition.py +++ b/legal-api/src/legal_api/services/filings/validations/transition.py @@ -53,7 +53,7 @@ def validate(business: Business, filing_json: dict) -> Optional[Error]: msg.extend(validate_relationships(business, filing_json, filing_type, - PartyRole.RoleTypes.DIRECTOR, + [PartyRole.RoleTypes.DIRECTOR], False, True)) diff --git a/legal-api/tests/unit/models/__init__.py b/legal-api/tests/unit/models/__init__.py index 5b32830f09..c74aecae60 100644 --- a/legal-api/tests/unit/models/__init__.py +++ b/legal-api/tests/unit/models/__init__.py @@ -172,6 +172,18 @@ def factory_business(identifier, return business +def factory_address(street: str, address_type: str): + """Return an Address.""" + return Address( + city='Test City', + street=street, + postal_code='T3S3T3', + country='TA', + region='BC', + address_type=address_type + ) + + def factory_business_mailing_address(business): """Create a business entity.""" address = Address( @@ -260,6 +272,7 @@ def factory_completed_filing(business, filing.payment_token = payment_token filing.effective_date = filing_date filing.payment_completion_date = filing_date + filing._meta_data = {} if colin_id: colin_event = ColinEventId() colin_event.colin_event_id = colin_id diff --git a/legal-api/tests/unit/resources/v2/test_colin_sync.py b/legal-api/tests/unit/resources/v2/test_colin_sync.py index fa3f291846..2a6c205bb2 100644 --- a/legal-api/tests/unit/resources/v2/test_colin_sync.py +++ b/legal-api/tests/unit/resources/v2/test_colin_sync.py @@ -14,31 +14,36 @@ """Tests to assure the colin sync end-point.""" import copy +from datetime import datetime from http import HTTPStatus import pytest from registry_schemas.example_data import ( ANNUAL_REPORT, CORRECTION_AR, - CORRECTION_INCORPORATION, - INCORPORATION_FILING_TEMPLATE, + CORRECTION_COL, + CORRECTION_COR ) -from legal_api.models import Business, Filing +from legal_api.models import Business, Filing, PartyRole +from legal_api.models.colin_event_id import ColinEventId from legal_api.services.authz import COLIN_SVC_ROLE from tests.unit.services.utils import create_header from tests.unit.models import ( + factory_address, factory_business, factory_business_mailing_address, factory_completed_filing, + factory_error_filing, + factory_party_role, factory_filing, + factory_pending_filing ) +from tests.unit.models import db def test_get_internal_filings(session, client, jwt): """Assert that the internal filings get endpoint returns all completed filings without colin ids.""" - from legal_api.models.colin_event_id import ColinEventId - from tests.unit.models import factory_error_filing, factory_pending_filing # setup identifier = 'CP7654321' b = factory_business(identifier) @@ -82,7 +87,6 @@ def test_get_internal_filings(session, client, jwt): def test_patch_internal_filings(session, client, jwt): """Assert that the internal filings patch endpoint updates the colin_event_id.""" - from legal_api.models.colin_event_id import ColinEventId # setup identifier = 'CP7654321' b = factory_business(identifier) @@ -106,7 +110,6 @@ def test_patch_internal_filings(session, client, jwt): def test_get_colin_id(session, client, jwt): """Assert the internal/filings/colin_id get endpoint returns properly.""" - from legal_api.models.colin_event_id import ColinEventId # setup identifier = 'CP7654321' b = factory_business(identifier) @@ -129,7 +132,6 @@ def test_get_colin_id(session, client, jwt): def test_get_colin_last_update(session, client, jwt): """Assert the get endpoint for ColinLastUpdate returns last updated colin id.""" - from tests.unit.models import db # setup colin_id = 1234 db.session.execute( @@ -153,3 +155,68 @@ def test_post_colin_last_update(session, client, jwt): ) assert rv.status_code == HTTPStatus.CREATED assert rv.json == {'maxId': colin_id} + + +def test_get_completed_filings_for_colin_corps_correction(session, client, jwt): + """Assert that corps corrections are returned as expected.""" + # setup + identifier = 'BC7654321' + b = factory_business(identifier=identifier, entity_type=Business.LegalTypes.COMP.value) + factory_business_mailing_address(b) + + correction_cod = copy.deepcopy(CORRECTION_COL) + correction_cod['filing']['correction']['correctedFilingType'] = 'changeOfDirectors' + correction_cod['filing']['correction']['relationships'][0]['roles'][0]['roleType'] = 'Director' + + filing_col = factory_completed_filing(b, CORRECTION_COL) + filing_cor = factory_completed_filing(b, CORRECTION_COR) + filing_cod = factory_completed_filing(b, correction_cod) + # Need to apply the relationships to the db + for filing in [filing_col, filing_cor, filing_cod]: + for relationship in filing.filing_json['filing']['correction']['relationships']: + mailing_address = factory_address(relationship['mailingAddress']['streetAddress'], 'mailing') + delivery_address = factory_address(relationship['deliveryAddress']['streetAddress'], 'delivery') + officer = { + 'firstName': relationship['entity']['givenName'], + 'lastName': relationship['entity']['familyName'], + 'middleInitial': relationship['entity'].get('middleInitial'), + 'partyType': 'person', + 'organizationName': '' + } + role_type = PartyRole.RoleTypes.DIRECTOR + if relationship['roles'][0]['roleType'].lower() == 'receiver': + role_type = PartyRole.RoleTypes.RECEIVER + elif relationship['roles'][0]['roleType'].lower() == 'liquidator': + role_type = PartyRole.RoleTypes.LIQUIDATOR + + party_role = factory_party_role( + delivery_address, + mailing_address, + officer, + filing.effective_date, + None, + role_type + ) + b.party_roles.append(party_role) + b.save() + + assert filing_col.status == Filing.Status.COMPLETED.value + assert filing_cor.status == Filing.Status.COMPLETED.value + assert filing_cod.status == Filing.Status.COMPLETED.value + + # test endpoint returned filing1 only (completed, no corrections, with no colin id set) + rv = client.get('/api/v2/businesses/internal/filings', + headers=create_header(jwt, [COLIN_SVC_ROLE])) + assert rv.status_code == HTTPStatus.OK + filings = rv.json.get('filings') + # Should only return the filing with directors + assert len(filings) == 1 + assert filings[0]['filingId'] == filing_cod.id + assert filings[0]['filing']['correction']['correctedFilingType'] == 'changeOfDirectors' + assert filings[0]['filing']['correction']['correctedFilingType'] == 'changeOfDirectors' + assert filings[0]['filing']['correction']['partyChanged'] == True + assert len(filings[0]['filing']['correction']['parties']) == 1 + assert filings[0]['filing']['correction']['parties'][0]['officer']['firstName'] == correction_cod['filing']['correction']['relationships'][0]['entity']['givenName'] + assert filings[0]['filing']['correction']['parties'][0]['mailingAddress']['streetAddress'] == correction_cod['filing']['correction']['relationships'][0]['mailingAddress']['streetAddress'] + assert filings[0]['filing']['correction']['parties'][0]['deliveryAddress']['streetAddress'] == correction_cod['filing']['correction']['relationships'][0]['deliveryAddress']['streetAddress'] + assert filings[0]['filing']['correction']['parties'][0]['role'] == 'director' diff --git a/legal-api/tests/unit/services/filings/validations/test_correction.py b/legal-api/tests/unit/services/filings/validations/test_correction.py index 702ebd90a4..2c493f7425 100644 --- a/legal-api/tests/unit/services/filings/validations/test_correction.py +++ b/legal-api/tests/unit/services/filings/validations/test_correction.py @@ -13,22 +13,46 @@ # limitations under the License. """Test Correction validations.""" import copy +import pytest from http import HTTPStatus -from registry_schemas.example_data import ANNUAL_REPORT, CORRECTION_AR +from registry_schemas.example_data import ( + ANNUAL_REPORT, + CHANGE_OF_DIRECTORS, + CHANGE_OF_LIQUIDATORS, + CHANGE_OF_RECEIVERS, + CORRECTION_AR, + CORRECTION_COL, + CORRECTION_COR, + FILING_TEMPLATE) from legal_api.services.filings import validate -from tests.unit.models import factory_business, factory_completed_filing, factory_filing +from tests.unit.models import factory_business, factory_business_mailing_address, factory_completed_filing, factory_filing -def test_valid_correction(mocker, session): +CORRECTION_COD = copy.deepcopy(CORRECTION_COL) +CORRECTION_COD['filing']['correction']['correctedFilingType'] = 'changeOfDirectors' +CORRECTION_COD['filing']['correction']['relationships'][0]['roles'][0]['roleType'] = 'Director' + + +@pytest.mark.parametrize('test_name, legal_type, identifier, initial_filing, correction_filing', [ + ('AR', 'CP', 'CP1234567', ANNUAL_REPORT, CORRECTION_AR), + ('COD', 'BC', 'BC1234567', CHANGE_OF_DIRECTORS, CORRECTION_COD), + # ('COL', 'BC', 'BC1234567', CHANGE_OF_LIQUIDATORS, CORRECTION_COL), + # ('COR', 'BC', 'BC1234567', CHANGE_OF_RECEIVERS, CORRECTION_COR),X +]) +def test_valid_correction(mocker, session, test_name, legal_type, identifier, initial_filing, correction_filing): """Test that a valid correction passes validation.""" # setup - identifier = 'CP1234567' - business = factory_business(identifier) - corrected_filing = factory_completed_filing(business, ANNUAL_REPORT) - - f = copy.deepcopy(CORRECTION_AR) + filing_template = copy.deepcopy(FILING_TEMPLATE) + initial_filing_type = correction_filing['filing']['correction']['correctedFilingType'] + filing_template['filing']['header']['name'] = initial_filing_type + filing_template['filing'][initial_filing_type] = initial_filing + business = factory_business(identifier, entity_type=legal_type) + factory_business_mailing_address(business) + corrected_filing = factory_completed_filing(business, filing_template) + + f = copy.deepcopy(correction_filing) f['filing']['header']['identifier'] = identifier f['filing']['correction']['correctedFilingId'] = corrected_filing.id From 5cb9717cddbeb28b82574b2277b98d0bdb7ce167 Mon Sep 17 00:00:00 2001 From: Kial Jinnah Date: Thu, 12 Mar 2026 14:34:42 -0400 Subject: [PATCH 2/8] chore: cleanup Signed-off-by: Kial Jinnah --- legal-api/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/legal-api/pyproject.toml b/legal-api/pyproject.toml index 505f463167..170ad13c6f 100644 --- a/legal-api/pyproject.toml +++ b/legal-api/pyproject.toml @@ -174,7 +174,7 @@ minversion = "2.0" testpaths = [ "tests", ] -# addopts = "--cov=src/legal_api" +addopts = "--cov=src/legal_api" python_files = [ "test*.py" ] From ad5ad0f28b3d635cfcf3f205b8976f99870bfd7a Mon Sep 17 00:00:00 2001 From: Kial Jinnah Date: Thu, 12 Mar 2026 14:51:21 -0400 Subject: [PATCH 3/8] chore: cleanup Signed-off-by: Kial Jinnah --- .../unit/services/filings/validations/test_correction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/legal-api/tests/unit/services/filings/validations/test_correction.py b/legal-api/tests/unit/services/filings/validations/test_correction.py index 2c493f7425..d16733aeec 100644 --- a/legal-api/tests/unit/services/filings/validations/test_correction.py +++ b/legal-api/tests/unit/services/filings/validations/test_correction.py @@ -38,8 +38,8 @@ @pytest.mark.parametrize('test_name, legal_type, identifier, initial_filing, correction_filing', [ ('AR', 'CP', 'CP1234567', ANNUAL_REPORT, CORRECTION_AR), ('COD', 'BC', 'BC1234567', CHANGE_OF_DIRECTORS, CORRECTION_COD), - # ('COL', 'BC', 'BC1234567', CHANGE_OF_LIQUIDATORS, CORRECTION_COL), - # ('COR', 'BC', 'BC1234567', CHANGE_OF_RECEIVERS, CORRECTION_COR),X + ('COL', 'BC', 'BC1234567', CHANGE_OF_LIQUIDATORS, CORRECTION_COL), + ('COR', 'BC', 'BC1234567', CHANGE_OF_RECEIVERS, CORRECTION_COR) ]) def test_valid_correction(mocker, session, test_name, legal_type, identifier, initial_filing, correction_filing): """Test that a valid correction passes validation.""" From a8c8f0292c85a2af4f16c5d481dc5ee29f08eb3d Mon Sep 17 00:00:00 2001 From: Kial Jinnah Date: Thu, 12 Mar 2026 15:14:31 -0400 Subject: [PATCH 4/8] Business Model - update registry schemas Signed-off-by: Kial Jinnah --- python/common/business-registry-model/poetry.lock | 8 ++++---- python/common/business-registry-model/pyproject.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/common/business-registry-model/poetry.lock b/python/common/business-registry-model/poetry.lock index 338aa7a26c..d4bda154b5 100644 --- a/python/common/business-registry-model/poetry.lock +++ b/python/common/business-registry-model/poetry.lock @@ -1472,7 +1472,7 @@ rpds-py = ">=0.7.0" [[package]] name = "registry_schemas" -version = "2.18.56" +version = "2.18.64" description = "A short description of the project" optional = false python-versions = ">=3.6" @@ -1490,8 +1490,8 @@ strict-rfc3339 = "*" [package.source] type = "git" url = "https://github.com/bcgov/business-schemas.git" -reference = "2.18.62" -resolved_reference = "1cea81cf14104fb7d4989169236eff13b3df16c4" +reference = "2.18.64" +resolved_reference = "18b577402737130667e275d78a64e7a1ab3c9751" [[package]] name = "requests" @@ -2085,4 +2085,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.13,<3.14" -content-hash = "12f11946bf62a6bf3d1a21472a765fe85e24629b2811aa213527d1e88d6fb22e" +content-hash = "2252d662bcc7f5a43d0e61651e96b9e7a0efd5b71c61eb3f1cda5f630cff19da" diff --git a/python/common/business-registry-model/pyproject.toml b/python/common/business-registry-model/pyproject.toml index cfb6a4a05d..424f9ef1a2 100644 --- a/python/common/business-registry-model/pyproject.toml +++ b/python/common/business-registry-model/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "business-model" -version = "3.3.22" +version = "3.3.23" description = "" authors = [ {name = "thor",email = "1042854+thorwolpert@users.noreply.github.com"} @@ -9,7 +9,7 @@ readme = "README.md" requires-python = ">=3.13,<3.14" dependencies = [ "sql-versioning @ git+https://github.com/bcgov/lear.git@main#subdirectory=python/common/sql-versioning-alt", - "registry-schemas @ git+https://github.com/bcgov/business-schemas.git@2.18.62", + "registry-schemas @ git+https://github.com/bcgov/business-schemas.git@2.18.64", "flask-migrate (>=4.1.0,<5.0.0)", "pg8000 (>=1.31.2,<2.0.0)", "pydantic (>=2.10.6,<3.0.0)", From 6e81a417ebd5317357992f56769c4e646b579552 Mon Sep 17 00:00:00 2001 From: Kial Jinnah Date: Thu, 12 Mar 2026 17:18:47 -0400 Subject: [PATCH 5/8] Add relationship handling in correction reports Signed-off-by: Kial Jinnah --- legal-api/src/legal_api/reports/report.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/legal-api/src/legal_api/reports/report.py b/legal-api/src/legal_api/reports/report.py index 6aa09098ab..5b6783bfff 100644 --- a/legal-api/src/legal_api/reports/report.py +++ b/legal-api/src/legal_api/reports/report.py @@ -1291,6 +1291,10 @@ def _format_office_data(self, filing, prev_completed_filing: Filing): def _format_party_data(self, filing, prev_completed_filing: Filing): filing["parties"] = filing.get("correction").get("parties", []) + if relationships := filing.get("correction").get("relationships"): + # map relationships to parties for pdf templates + filing["parties"].extend([self._map_relationship_to_party(relationship) for relationship in relationships]) + if filing.get("parties"): self._format_directors(filing["parties"]) filing["partyChange"] = False @@ -1490,6 +1494,24 @@ def _get_environment(): if namespace.endswith("test"): return "TEST" return "" + + @staticmethod + def _map_relationship_to_party(relationship): + # FUTURE: update pdf templates to expect relationships schema and remove this + organization_name = relationship["entity"].get("businessName") + return { + "officer": { + "id": relationship["entity"].get("identifier"), + "firstName": relationship["entity"].get("givenName"), + "middleName": relationship["entity"].get("middleInitial"), + "lastName": relationship["entity"].get("familyName"), + "organizationName": organization_name, + "partyType": "organization" if organization_name else "person" + }, + "deliveryAddress": relationship.get("deliveryAddress"), + "mailingAddress": relationship.get("mailingAddress"), + "roles": relationship.get("roles", []) + } class ReportMeta: # pylint: disable=too-few-public-methods From 76c09a9b8c4c49f5b12b80bd6c0b9d018bbc8c64 Mon Sep 17 00:00:00 2001 From: Kial Jinnah Date: Fri, 13 Mar 2026 09:34:00 -0400 Subject: [PATCH 6/8] add lear_only column on filings model Signed-off-by: Kial Jinnah --- .../versions/b5ded56cab5b_filing_lear_only.py | 28 +++++++++++++++++++ legal-api/src/legal_api/models/filing.py | 3 ++ .../src/business_model/models/filing.py | 2 ++ .../versions/b5ded56cab5b_filing_lear_only.py | 28 +++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 legal-api/migrations/versions/b5ded56cab5b_filing_lear_only.py create mode 100644 python/common/business-registry-model/src/business_model_migrations/versions/b5ded56cab5b_filing_lear_only.py diff --git a/legal-api/migrations/versions/b5ded56cab5b_filing_lear_only.py b/legal-api/migrations/versions/b5ded56cab5b_filing_lear_only.py new file mode 100644 index 0000000000..1e6f2f5cd6 --- /dev/null +++ b/legal-api/migrations/versions/b5ded56cab5b_filing_lear_only.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: b5ded56cab5b +Revises: 2467e09986f2 +Create Date: 2026-03-13 09:27:35.973908 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'b5ded56cab5b' +down_revision = '2467e09986f2' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('filings', sa.Column('lear_only', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('filings', 'lear_only') + # ### end Alembic commands ### diff --git a/legal-api/src/legal_api/models/filing.py b/legal-api/src/legal_api/models/filing.py index f51533537d..eb1e5f6f3e 100644 --- a/legal-api/src/legal_api/models/filing.py +++ b/legal-api/src/legal_api/models/filing.py @@ -773,6 +773,7 @@ class Source(Enum): "court_order_file_number", "deletion_locked", "hide_in_ledger", + "lear_only", "effective_date", "order_details", "paper_only", @@ -805,6 +806,7 @@ class Source(Enum): _source = db.Column("source", db.String(15), default=Source.LEAR.value) paper_only = db.Column("paper_only", db.Boolean, unique=False, default=False) colin_only = db.Column("colin_only", db.Boolean, unique=False, default=False) + lear_only = db.Column("lear_only", db.Boolean, unique=False, default=False) payment_account = db.Column("payment_account", db.String(30)) effective_date = db.Column("effective_date", db.DateTime(timezone=True), default=datetime.utcnow) submitter_roles = db.Column("submitter_roles", db.String(200)) @@ -1343,6 +1345,7 @@ def get_completed_filings_for_colin(limit=20, offset=0): ~Business.legal_type.in_(excluded_businesses), ~Filing._filing_type.in_(excluded_filings), ~and_(Filing._filing_type == "dissolution", Filing._filing_sub_type == "delay"), + ~Filing.lear_only, Filing.colin_event_ids == None, # pylint: disable=singleton-comparison # noqa: E711; Filing._status == Filing.Status.COMPLETED.value, Filing._source == Filing.Source.LEAR.value, diff --git a/python/common/business-registry-model/src/business_model/models/filing.py b/python/common/business-registry-model/src/business_model/models/filing.py index 225e37fd30..27ff78a6dd 100644 --- a/python/common/business-registry-model/src/business_model/models/filing.py +++ b/python/common/business-registry-model/src/business_model/models/filing.py @@ -774,6 +774,7 @@ class Source(Enum): 'court_order_file_number', 'deletion_locked', 'hide_in_ledger', + 'lear_only', 'effective_date', 'order_details', 'paper_only', @@ -806,6 +807,7 @@ class Source(Enum): _source = db.Column('source', db.String(15), default=Source.LEAR.value) paper_only = db.Column('paper_only', db.Boolean, unique=False, default=False) colin_only = db.Column('colin_only', db.Boolean, unique=False, default=False) + lear_only = db.Column('lear_only', db.Boolean, unique=False, default=False) payment_account = db.Column('payment_account', db.String(30)) effective_date = db.Column('effective_date', db.DateTime(timezone=True), default=func.now()) submitter_roles = db.Column('submitter_roles', db.String(200)) diff --git a/python/common/business-registry-model/src/business_model_migrations/versions/b5ded56cab5b_filing_lear_only.py b/python/common/business-registry-model/src/business_model_migrations/versions/b5ded56cab5b_filing_lear_only.py new file mode 100644 index 0000000000..1e6f2f5cd6 --- /dev/null +++ b/python/common/business-registry-model/src/business_model_migrations/versions/b5ded56cab5b_filing_lear_only.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: b5ded56cab5b +Revises: 2467e09986f2 +Create Date: 2026-03-13 09:27:35.973908 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'b5ded56cab5b' +down_revision = '2467e09986f2' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('filings', sa.Column('lear_only', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('filings', 'lear_only') + # ### end Alembic commands ### From 9d8d8a2d31b893e49daf810d5cbd0208c06b4307 Mon Sep 17 00:00:00 2001 From: Kial Jinnah Date: Fri, 13 Mar 2026 09:39:40 -0400 Subject: [PATCH 7/8] chore: ruff fix Signed-off-by: Kial Jinnah --- .../versions/b5ded56cab5b_filing_lear_only.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/common/business-registry-model/src/business_model_migrations/versions/b5ded56cab5b_filing_lear_only.py b/python/common/business-registry-model/src/business_model_migrations/versions/b5ded56cab5b_filing_lear_only.py index 1e6f2f5cd6..9e2ba0d9a3 100644 --- a/python/common/business-registry-model/src/business_model_migrations/versions/b5ded56cab5b_filing_lear_only.py +++ b/python/common/business-registry-model/src/business_model_migrations/versions/b5ded56cab5b_filing_lear_only.py @@ -5,8 +5,8 @@ Create Date: 2026-03-13 09:27:35.973908 """ -from alembic import op import sqlalchemy as sa +from alembic import op from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. From dc1fea9b704c7bfe7f83769cbc6648aea83ef482 Mon Sep 17 00:00:00 2001 From: Kial Jinnah Date: Fri, 13 Mar 2026 12:25:53 -0400 Subject: [PATCH 8/8] Update test and remove unnecessary code based off lear_only col Signed-off-by: Kial Jinnah --- .../src/legal_api/resources/v2/business/colin_sync.py | 9 +++------ legal-api/tests/unit/resources/v2/test_colin_sync.py | 7 +++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/legal-api/src/legal_api/resources/v2/business/colin_sync.py b/legal-api/src/legal_api/resources/v2/business/colin_sync.py index db9e308f0c..5621674e86 100644 --- a/legal-api/src/legal_api/resources/v2/business/colin_sync.py +++ b/legal-api/src/legal_api/resources/v2/business/colin_sync.py @@ -67,15 +67,12 @@ def get_completed_filings_for_colin(): if filing.filing_type == "correction" and business.legal_type != Business.LegalTypes.COOP.value: try: set_correction_flags(filing_json, filing) - has_non_party_change = has_alias_changed(filing) or has_office_changed(filing) or has_resolution_changed(filing) or has_share_changed(filing) inner_filing_json = filing_json["filing"].get(filing.filing_type, {}) - # NOTE: has_party_changed specifically checks for Director changes - if inner_filing_json.get("relationships") and has_party_changed(filing): + if inner_filing_json.get("relationships"): # set directors and completing party from the db when filing_json is using relationships schema - ignore other parties + # NOTE: in the case where only non director parties were changed then set_correction_flags will not set partyChanged and parties will not be processed by the colin-api + # if no other colin relevant corrections were made then the lear_only flag will be set during the filer processing and it will not be picked up by 'get_completed_filings_for_colin' _set_relationship_parties(business, filing, inner_filing_json) - elif not filing.meta_data.get("commentOnly", False) and not has_non_party_change: - # Only change was to non director parties so skip the colin sync for this correction - continue except Exception as ex: current_app.logger.error(f"correction: filingId={filing.id}, error: {ex!s}") # to skip this filing and block subsequent filing from syncing in update-colin-filings diff --git a/legal-api/tests/unit/resources/v2/test_colin_sync.py b/legal-api/tests/unit/resources/v2/test_colin_sync.py index 2a6c205bb2..a3ec4efbaf 100644 --- a/legal-api/tests/unit/resources/v2/test_colin_sync.py +++ b/legal-api/tests/unit/resources/v2/test_colin_sync.py @@ -169,7 +169,13 @@ def test_get_completed_filings_for_colin_corps_correction(session, client, jwt): correction_cod['filing']['correction']['relationships'][0]['roles'][0]['roleType'] = 'Director' filing_col = factory_completed_filing(b, CORRECTION_COL) + # Filer will set this for corrections on liquidators only + filing_col.lear_only = True + filing_col.save() filing_cor = factory_completed_filing(b, CORRECTION_COR) + # Filer will set this for corrections on receivers only + filing_cor.lear_only = True + filing_cor.save() filing_cod = factory_completed_filing(b, correction_cod) # Need to apply the relationships to the db for filing in [filing_col, filing_cor, filing_cod]: @@ -211,6 +217,7 @@ def test_get_completed_filings_for_colin_corps_correction(session, client, jwt): filings = rv.json.get('filings') # Should only return the filing with directors assert len(filings) == 1 + # Should have been mapped to expected filing json assert filings[0]['filingId'] == filing_cod.id assert filings[0]['filing']['correction']['correctedFilingType'] == 'changeOfDirectors' assert filings[0]['filing']['correction']['correctedFilingType'] == 'changeOfDirectors'