From a4f8c3d466c67a54c76e9bcb0503e07ba21e98f5 Mon Sep 17 00:00:00 2001 From: Rajandeep Date: Wed, 17 Dec 2025 09:14:57 -0800 Subject: [PATCH 1/4] 30660 - Completing Party Editable Validation --- .../filings/validations/common_validations.py | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) 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 333b254704..4dcf4948e4 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 @@ -896,4 +896,62 @@ def validate_party_role_firms(parties: list) -> list: if role_type in [PartyRole.RoleTypes.PARTNER.value, PartyRole.RoleTypes.PROPRIETOR.value]: return False - return True \ No newline at end of file + return True + +def validate_completing_party(filing_json: dict, filing_type: str, business=None) -> list: + """Validate completing party edited.""" + msg = [] + parties = filing_json["filing"][filing_type].get("parties", {}) + + officer = None + for party in parties: + roles = party.get("roles", []) + if any(role.get("roleType").lower() == PartyRole.RoleTypes.COMPLETING_PARTY.value for role in roles): + officer = party.get("officer", {}) + break + if not officer: + msg.append({ + "error": "Completing party is required.", + "path": f"/filing/{filing_type}/parties" + }) + return msg + + is_party_changed = True + if business: + existing_roles = PartyRole.get_party_roles(business.id, datetime.now(tz=timezone.utc).date(), role= PartyRole.RoleTypes.COMPLETING_PARTY.value) + if existing_roles: + existing_party = existing_roles[0].party + filing_identifier = officer.get("identifier") + + if filing_identifier and existing_party and existing_party.identifier == filing_identifier: + is_party_changed = False + else: + filing_party_type = officer.get("partyType", "").lower() + existing_party_type = (existing_party_type.party_type or "").lower() + + if filing_party_type == existing_party_type: + if filing_party_type == "person": + if is_name_changed( + { + "firstName": existing_party.first_name, + "middleName": existing_party.middle_initial, + "lastName": existing_party.last_name, + "organizationName": existing_party.organization_name + }, + { + "firstName": officer.get("firstName"), + "middleName": officer.get("middleName"), + "lastName": officer.get("lastName"), + "organizationName": officer.get("organizationName") + } + ): + is_party_changed = True + else: + is_party_changed = False + elif filing_party_type == "organization": + if not is_same_str(existing_party.organization_name, officer.get("organizationName")): + is_party_changed = True + else: + is_party_changed = False + if is_party_changed: + return msg \ No newline at end of file From 4781007d5b77a6b50040747e147e4f3ab98b6c72 Mon Sep 17 00:00:00 2001 From: Rajandeep Date: Tue, 23 Dec 2025 09:51:18 -0800 Subject: [PATCH 2/4] updated --- legal-api/src/legal_api/services/bootstrap.py | 21 +++++ .../filings/validations/common_validations.py | 76 +++++++++---------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/legal-api/src/legal_api/services/bootstrap.py b/legal-api/src/legal_api/services/bootstrap.py index 033c1c12aa..1f0aad0212 100644 --- a/legal-api/src/legal_api/services/bootstrap.py +++ b/legal-api/src/legal_api/services/bootstrap.py @@ -320,3 +320,24 @@ def get_affiliations(cls, account: int): return affiliates.json().get("entities") return None + + @classmethod + def get_contacts(cls, config, org_id: str): + """Get contacts for the business.""" + token = cls.get_bearer_token(config) + auth_url = config.AUTH_SVC_URL + + if not token: + return HTTPStatus.UNAUTHORIZED + + rv = requests.get( + url=f'{auth_url}/{org_id}/contacts', + headers={**cls.CONTENT_TYPE_JSON, + 'Authorization': cls.BEARER + token}, + timeout=cls.timeout + ) + + if rv.status_code == HTTPStatus.OK: + return rv.json() + + return None \ No newline at end of file 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 4dcf4948e4..1d469e45ac 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 @@ -17,6 +17,8 @@ from datetime import datetime, timedelta, timezone from typing import Final, Optional +from legal_api.services.bootstrap import AccountService +from legal_api.services.permissions import ListActionsPermissionsAllowed, PermissionService import pycountry import PyPDF2 from flask import current_app, g @@ -898,7 +900,7 @@ def validate_party_role_firms(parties: list) -> list: return False return True -def validate_completing_party(filing_json: dict, filing_type: str, business=None) -> list: +def validate_completing_party(filing_json: dict, filing_type: str, org_id: int) -> list: """Validate completing party edited.""" msg = [] parties = filing_json["filing"][filing_type].get("parties", {}) @@ -916,42 +918,36 @@ def validate_completing_party(filing_json: dict, filing_type: str, business=None }) return msg - is_party_changed = True - if business: - existing_roles = PartyRole.get_party_roles(business.id, datetime.now(tz=timezone.utc).date(), role= PartyRole.RoleTypes.COMPLETING_PARTY.value) - if existing_roles: - existing_party = existing_roles[0].party - filing_identifier = officer.get("identifier") - - if filing_identifier and existing_party and existing_party.identifier == filing_identifier: - is_party_changed = False - else: - filing_party_type = officer.get("partyType", "").lower() - existing_party_type = (existing_party_type.party_type or "").lower() - - if filing_party_type == existing_party_type: - if filing_party_type == "person": - if is_name_changed( - { - "firstName": existing_party.first_name, - "middleName": existing_party.middle_initial, - "lastName": existing_party.last_name, - "organizationName": existing_party.organization_name - }, - { - "firstName": officer.get("firstName"), - "middleName": officer.get("middleName"), - "lastName": officer.get("lastName"), - "organizationName": officer.get("organizationName") - } - ): - is_party_changed = True - else: - is_party_changed = False - elif filing_party_type == "organization": - if not is_same_str(existing_party.organization_name, officer.get("organizationName")): - is_party_changed = True - else: - is_party_changed = False - if is_party_changed: - return msg \ No newline at end of file + filing_completing_party_mailing_address = officer.get("mailingAddress", {}) + + contacts_response = AccountService.get_contacts(current_app.config, org_id) + if contacts_response is None: + msg.append({ + "error": "Unable to verify completing party against account contacts.", + "path": f"/filing/{filing_type}/parties" + }) + return msg + + contact = contacts_response["contacts"][0] + existing_cp_mailing_address = { + "streetAddress": contact.get("street", ""), + "addressCity": contact.get("city", ""), + "addressRegion": contact.get("region", ""), + "postalCode": contact.get("postalCode", ""), + "addressCountry": contact.get("country", ""), + "deliveryInstructions": contact.get("deliveryInstructions", ""), + "streetAddressAdditional": contact.get("streetAdditional", "") + } + + if is_address_changed(existing_cp_mailing_address, filing_completing_party_mailing_address): + permission_error = PermissionService.check_user_permission( + ListActionsPermissionsAllowed.EDITABLE_COMPLETING_PARTY.value, + message="Permission Denied - You do not have rights to edit completing address." + ) + if permission_error: + error_msg = permission_error.message[0]["message"] if permission_error.message else "You do not have rights to edit completing address." + msg.append({ + "error": error_msg, + "path": f"/filing/{filing_type}/parties" + }) + return msg From 9e7951cc727eeb1e721e76ddee2b2dffed974e99 Mon Sep 17 00:00:00 2001 From: Rajandeep Date: Tue, 23 Dec 2025 14:45:51 -0800 Subject: [PATCH 3/4] updated existing completing party --- legal-api/src/legal_api/services/bootstrap.py | 53 +++++++++++++++++-- .../filings/validations/common_validations.py | 27 ++++++++-- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/legal-api/src/legal_api/services/bootstrap.py b/legal-api/src/legal_api/services/bootstrap.py index 1f0aad0212..428ffdb6f8 100644 --- a/legal-api/src/legal_api/services/bootstrap.py +++ b/legal-api/src/legal_api/services/bootstrap.py @@ -323,21 +323,64 @@ def get_affiliations(cls, account: int): @classmethod def get_contacts(cls, config, org_id: str): - """Get contacts for the business.""" + """Get contacts for the business. + Fetch Compelting Party Details from Auth API. + - GET /orgs/{org_id}/memeberships for user contacts details + - GET /orgs/{org_id} for org contacts details + """ token = cls.get_bearer_token(config) auth_url = config.AUTH_SVC_URL if not token: return HTTPStatus.UNAUTHORIZED - rv = requests.get( - url=f'{auth_url}/{org_id}/contacts', + membership_response = requests.get( + url=f'{auth_url}/{org_id}/memberships', headers={**cls.CONTENT_TYPE_JSON, 'Authorization': cls.BEARER + token}, timeout=cls.timeout ) - if rv.status_code == HTTPStatus.OK: - return rv.json() + org_info_response = requests.get( + url=f'{auth_url}/orgs/{org_id}', + headers={**cls.CONTENT_TYPE_JSON, + 'Authorization': cls.BEARER + token}, + timeout=cls.timeout + ) + if membership_response.status_code == HTTPStatus.OK and org_info_response.status_code == HTTPStatus.OK: + return None + + try: + membership_data = membership_response.json() + org_info = org_info_response.json() + + user_info = membership_data.get("user", {}) + first_name = user_info.get("firstName", "") + last_name = user_info.get("lastName", "") + + user_contacts = user_info.get("contacts", []) + user_contact = user_contacts[0] if user_contacts else {} + email = user_contact.get("email", "") + phone = user_contact.get("phone", "") + + org_contacts = org_info.get("contacts", []) + org_contact = org_contacts[0] if org_contacts else {} + + contact = { + "street": org_contact.get("street", ""), + "city": org_contact.get("city", ""), + "region": org_contact.get("region", ""), + "country": org_contact.get("country", ""), + "postalCode": org_contact.get("postalCode", ""), + "firstName": first_name, + "lastName": last_name, + "email": email, + "phone": phone, + "streetAdditional": org_contact.get("streetAdditional", ""), + "delieveryInstructions": org_contact.get("deliveryInstructions", "") + } + return {"contact": [contact]} + except Exception as e: + current_app.logger.error(f"Error fetching contacts: {e}") return None \ No newline at end of file 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 1d469e45ac..ee186fdd59 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 @@ -919,7 +919,10 @@ def validate_completing_party(filing_json: dict, filing_type: str, org_id: int) return msg filing_completing_party_mailing_address = officer.get("mailingAddress", {}) - + filing_firstname = officer.get("firstName", "") + filing_lastname = officer.get("lastName", "") + filing_email = officer.get("email", "") + contacts_response = AccountService.get_contacts(current_app.config, org_id) if contacts_response is None: msg.append({ @@ -938,8 +941,26 @@ def validate_completing_party(filing_json: dict, filing_type: str, org_id: int) "deliveryInstructions": contact.get("deliveryInstructions", ""), "streetAddressAdditional": contact.get("streetAdditional", "") } - - if is_address_changed(existing_cp_mailing_address, filing_completing_party_mailing_address): + existing_firstname = contact.get("firstName", "") + existing_lastname = contact.get("lastName", "") + existing_email = contact.get("email", "") + + address_changed = is_address_changed(existing_cp_mailing_address, filing_completing_party_mailing_address) + + existing_name = { + "firstName": existing_firstname, + "lastName": existing_lastname + } + filing_name = { + "firstName": filing_firstname, + "lastName": filing_lastname + } + + name_changed = is_name_changed(existing_name, filing_name) + + email_changed = not is_same_str(existing_email, filing_email) + + if address_changed or name_changed or email_changed: permission_error = PermissionService.check_user_permission( ListActionsPermissionsAllowed.EDITABLE_COMPLETING_PARTY.value, message="Permission Denied - You do not have rights to edit completing address." From e65751c051d7def9b0387db8ca01449393f7e89d Mon Sep 17 00:00:00 2001 From: Rajandeep Date: Mon, 29 Dec 2025 08:30:12 -0800 Subject: [PATCH 4/4] fix conflicts --- legal-api/src/legal_api/services/bootstrap.py | 8 +-- .../filings/validations/common_validations.py | 60 ++++++++----------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/legal-api/src/legal_api/services/bootstrap.py b/legal-api/src/legal_api/services/bootstrap.py index 428ffdb6f8..001a5e939d 100644 --- a/legal-api/src/legal_api/services/bootstrap.py +++ b/legal-api/src/legal_api/services/bootstrap.py @@ -335,16 +335,16 @@ def get_contacts(cls, config, org_id: str): return HTTPStatus.UNAUTHORIZED membership_response = requests.get( - url=f'{auth_url}/{org_id}/memberships', + url=f"{auth_url}/{org_id}/memberships", headers={**cls.CONTENT_TYPE_JSON, - 'Authorization': cls.BEARER + token}, + "Authorization": cls.BEARER + token}, timeout=cls.timeout ) org_info_response = requests.get( - url=f'{auth_url}/orgs/{org_id}', + url=f"{auth_url}/orgs/{org_id}", headers={**cls.CONTENT_TYPE_JSON, - 'Authorization': cls.BEARER + token}, + "Authorization": cls.BEARER + token}, timeout=cls.timeout ) 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 feba07a942..04b3dd592f 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 @@ -18,8 +18,6 @@ from http import HTTPStatus from typing import Final, Optional -from legal_api.services.bootstrap import AccountService -from legal_api.services.permissions import ListActionsPermissionsAllowed, PermissionService import pycountry import PyPDF2 from flask import current_app, g @@ -28,6 +26,7 @@ from legal_api.errors import Error from legal_api.models import Address, Business, PartyRole from legal_api.services import MinioService, colin, flags, namex +from legal_api.services.bootstrap import AccountService from legal_api.services.permissions import ListActionsPermissionsAllowed, PermissionService from legal_api.services.utils import get_str from legal_api.utils.datetime import datetime as dt @@ -919,13 +918,31 @@ def validate_party_role_firms(parties: list, filing_type: str) -> list: msg = [] for party in parties: - roles = party.get("roles", []) - for role in roles: - role_type = role.get("roleType") - if role_type in [PartyRole.RoleTypes.PARTNER.value, - PartyRole.RoleTypes.PROPRIETOR.value]: - return False - return True + officer = party.get("officer", {}) + party_type = officer.get("partyType", "") + + if party_type == "organization": + business_identifier = officer.get("identifier", None) + business_found = False + + if business_identifier: + business_found = Business.find_by_identifier(business_identifier) is not None + if not business_found: + colin_business = colin.query_business(business_identifier) + business_found = colin_business.status_code == HTTPStatus.OK + + if business_found: + continue + + if err_msg := PermissionService.check_user_permission( + ListActionsPermissionsAllowed.FIRM_ADD_BUSINESS.value, + message="Permission Denied: You do not have permission to add a business or corporation which is not registered in BC." + ): + msg.append({"error": err_msg.msg[0].get("message"), + "path": f"/filing/{filing_type}/parties" + }) + + return msg def validate_completing_party(filing_json: dict, filing_type: str, org_id: int) -> list: """Validate completing party edited.""" @@ -999,28 +1016,3 @@ def validate_completing_party(filing_json: dict, filing_type: str, org_id: int) "path": f"/filing/{filing_type}/parties" }) return msg - officer = party.get("officer", {}) - party_type = officer.get("partyType", "") - - if party_type == "organization": - business_identifier = officer.get("identifier", None) - business_found = False - - if business_identifier: - business_found = Business.find_by_identifier(business_identifier) is not None - if not business_found: - colin_business = colin.query_business(business_identifier) - business_found = colin_business.status_code == HTTPStatus.OK - - if business_found: - continue - - if err_msg := PermissionService.check_user_permission( - ListActionsPermissionsAllowed.FIRM_ADD_BUSINESS.value, - message="Permission Denied: You do not have permission to add a business or corporation which is not registered in BC." - ): - msg.append({"error": err_msg.msg[0].get("message"), - "path": f"/filing/{filing_type}/parties" - }) - - return msg