Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 957e0bc

Browse files
34822 validate correction based on business state (#4762)
1 parent bed903a commit 957e0bc

7 files changed

Lines changed: 368 additions & 153 deletions

File tree

legal-api/src/legal_api/resources/v2/business/business_court_orders.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from flask_cors import cross_origin
1919

2020
from business_model.models import Business, CourtOrder, Filing
21+
from business_model.models.types.filings import FilingTypes
2122
from legal_api.services import authorized
2223
from legal_api.utils.auth import jwt
2324

@@ -47,24 +48,28 @@ def get_court_orders(identifier, court_order_id=None):
4748
return jsonify(court_order), code
4849

4950
court_orders_list = CourtOrder.get_json_with_filing_type(business.id)
51+
for court_order in court_orders_list:
52+
_include_court_order_files(court_order, business)
5053
return jsonify({
5154
"courtOrders": court_orders_list
5255
}), HTTPStatus.OK
5356

5457

55-
def _get_court_order(business, court_order_id=None):
58+
def _get_court_order(business, court_order_id):
5659
if court_order := CourtOrder.get_by_id(court_order_id):
5760
court_order_json = court_order.json
58-
filing = Filing.find_by_id(court_order.filing_id)
59-
if filing.filing_type == "courtOrder":
60-
_include_court_order_files(court_order_json, filing, business)
61+
_include_court_order_files(court_order_json, business)
6162

6263
return {"courtOrder": court_order_json}, HTTPStatus.OK
6364

6465
return {"message": f"{business.identifier} court order not found"}, HTTPStatus.NOT_FOUND
6566

6667

67-
def _include_court_order_files(court_order_json, filing, business):
68+
def _include_court_order_files(court_order_json, business):
69+
filing = Filing.find_by_id(court_order_json["filingId"])
70+
if filing.filing_type != FilingTypes.COURTORDER:
71+
return
72+
6873
if documents := filing.documents.all():
6974
base_url = current_app.config.get("BUSINESS_API_GW_URL")
7075
doc_url = url_for(

legal-api/src/legal_api/services/filings/validations/correction.py

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
"""Validation for the Correction filing."""
15-
from datetime import timedelta
15+
from datetime import UTC, datetime, timedelta
1616
from http import HTTPStatus
1717
from typing import Final
1818

@@ -45,6 +45,7 @@
4545
validate_continuation_in_expro_business_in_colin,
4646
validate_continuation_in_foreign_jurisdiction,
4747
)
48+
from legal_api.services.filings.validations.dissolution import validate_custodian_email
4849
from legal_api.services.filings.validations.incorporation_application import (
4950
validate_coop_parties_mailing_address,
5051
validate_roles,
@@ -97,21 +98,6 @@ def validate(business: Business, filing: dict) -> Error:
9798
if not is_comment_only_correction:
9899
if filing.get("filing", {}).get("correction", {}).get("parties", None):
99100
msg.extend(validate_parties_addresses(filing, filing_type))
100-
if filing.get("filing", {}).get("correction", {}).get("relationships", None):
101-
msg.extend(validate_relationships(
102-
business,
103-
filing,
104-
filing_type,
105-
[
106-
PartyRole.RoleTypes.DIRECTOR,
107-
PartyRole.RoleTypes.LIQUIDATOR,
108-
PartyRole.RoleTypes.RECEIVER,
109-
PartyRole.RoleTypes.COMPLETING_PARTY
110-
],
111-
True,
112-
True,
113-
[PartyRole.RoleTypes.DIRECTOR, PartyRole.RoleTypes.COMPLETING_PARTY]
114-
))
115101
if filing.get("filing", {}).get("correction", {}).get("offices", None):
116102
msg.extend(validate_offices_addresses(filing, filing_type))
117103

@@ -145,6 +131,81 @@ def _validate_firms_correction(business: Business, filing, legal_type, msg):
145131

146132

147133
def _validate_corps_correction(business: Business, filing_dict, legal_type, msg):
134+
if filing_dict.get("filing", {}).get("correction", {}).get("courtOrder", None):
135+
msg.extend(court_order_validation(filing_dict))
136+
msg.extend(_validate_court_orders_correction(filing_dict, business))
137+
138+
if relationships := filing_dict.get("filing", {}).get("correction", {}).get("relationships", None):
139+
relationships_path = "/filing/correction/relationships"
140+
completing_parties = [
141+
x for x in relationships
142+
if any(
143+
role for role in x.get("roles", [])
144+
if role["roleType"].lower().replace(" ", "_") == PartyRole.RoleTypes.COMPLETING_PARTY.value
145+
)
146+
]
147+
correction_type = filing_dict.get("filing").get("correction").get("type", "STAFF")
148+
if correction_type == "STAFF":
149+
if len(completing_parties) != 0:
150+
msg.append({
151+
"error": "Should not provide completing party when correction type is STAFF",
152+
"path": relationships_path
153+
})
154+
elif len(completing_parties) == 0:
155+
msg.append({"error": "Completing party is required.", "path": relationships_path})
156+
elif len(completing_parties) > 1:
157+
msg.append({"error": "Only one completing party is allowed.", "path": relationships_path})
158+
159+
if business.state == Business.State.HISTORICAL.value:
160+
_validate_corps_correction_historical(business, filing_dict, msg)
161+
else:
162+
_validate_corps_correction_active(business, filing_dict, legal_type, msg)
163+
164+
165+
def _validate_corps_correction_historical(business: Business, filing_dict, msg):
166+
filing_type = "correction"
167+
msg.extend(_validate_out_correction(filing_dict, filing_type, business))
168+
if relationships := filing_dict.get("filing", {}).get("correction", {}).get("relationships", None):
169+
custodian_parties = [
170+
x for x in relationships
171+
if any(
172+
role for role in x.get("roles", [])
173+
if role["roleType"].lower() == PartyRole.RoleTypes.CUSTODIAN.value
174+
)
175+
]
176+
relationships_path = "/filing/correction/relationships"
177+
if len(custodian_parties) > 1:
178+
msg.append({"error": "Only one custodian is allowed.", "path": relationships_path})
179+
elif len(custodian_parties) == 1:
180+
today = datetime.now(tz=UTC).date()
181+
existing_custodian = PartyRole.get_party_roles(business.id, today, PartyRole.RoleTypes.CUSTODIAN.value)
182+
if not custodian_parties[0].get("entity", {}).get("identifier") and len(existing_custodian) > 0:
183+
msg.append({
184+
"error": "Custodian already exists for this business, cannot create another custodian.",
185+
"path": relationships_path
186+
})
187+
msg.extend(
188+
validate_custodian_email(
189+
custodian_parties[0].get("entity", {}).get("email"),
190+
f"{relationships_path}/entity/email"
191+
)
192+
)
193+
194+
msg.extend(validate_relationships(
195+
business,
196+
filing_dict,
197+
filing_type,
198+
[
199+
PartyRole.RoleTypes.CUSTODIAN,
200+
PartyRole.RoleTypes.COMPLETING_PARTY
201+
],
202+
True,
203+
True,
204+
[PartyRole.RoleTypes.CUSTODIAN, PartyRole.RoleTypes.COMPLETING_PARTY]
205+
))
206+
207+
208+
def _validate_corps_correction_active(business: Business, filing_dict, legal_type, msg):
148209
filing_type = "correction"
149210
if new_legal_type := filing_dict.get("filing", {}).get("correction", {}).get("newLegalType"):
150211
if business.legal_type == new_legal_type:
@@ -163,6 +224,23 @@ def _validate_corps_correction(business: Business, filing_dict, legal_type, msg)
163224
msg.extend(err)
164225
# FUTURE: this should be removed when COLIN sync back is no longer required.
165226
msg.extend(validate_parties_names(filing_dict, filing_type, legal_type))
227+
228+
if filing_dict.get("filing", {}).get("correction", {}).get("relationships", None):
229+
msg.extend(validate_relationships(
230+
business,
231+
filing_dict,
232+
filing_type,
233+
[
234+
PartyRole.RoleTypes.DIRECTOR,
235+
PartyRole.RoleTypes.LIQUIDATOR,
236+
PartyRole.RoleTypes.RECEIVER,
237+
PartyRole.RoleTypes.COMPLETING_PARTY
238+
],
239+
True,
240+
True,
241+
[PartyRole.RoleTypes.DIRECTOR, PartyRole.RoleTypes.COMPLETING_PARTY]
242+
))
243+
166244
if filing_dict.get("filing", {}).get("correction", {}).get("shareStructure", None):
167245
err = validate_share_structure(filing_dict, filing_type, legal_type)
168246
if err:
@@ -171,13 +249,8 @@ def _validate_corps_correction(business: Business, filing_dict, legal_type, msg)
171249
msg.extend(validate_share_currency(filing_dict, filing_type, business))
172250
msg.extend(validate_resolution_date_in_share_structure(filing_dict, filing_type, business))
173251

174-
if filing_dict.get("filing", {}).get("correction", {}).get("courtOrder", None):
175-
msg.extend(court_order_validation(filing_dict))
176-
177252
msg.extend(_validate_continuation_in_correction(filing_dict, filing_type, legal_type, business))
178-
msg.extend(_validate_out_correction(filing_dict, filing_type, business))
179253
msg.extend(_validate_amalgamation_correction(filing_dict, filing_type, business))
180-
msg.extend(_validate_court_orders_correction(filing_dict, business))
181254

182255

183256
def _validate_court_orders_correction(filing_dict, business: Business):

legal-api/src/legal_api/services/filings/validations/dissolution.py

Lines changed: 55 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,7 @@ def validate(business: Business, dissolution: dict) -> Error | None:
9797
if err:
9898
msg.extend(err)
9999

100-
# Specific validation for addresses in dissolution
101-
err = validate_dissolution_parties_address(dissolution, business.legal_type, dissolution_type)
102-
if err:
103-
msg.extend(err)
100+
msg.extend(validate_dissolution_parties_address(dissolution, business.legal_type, dissolution_type))
104101

105102
if dissolution["filing"]["dissolution"].get("parties"):
106103
# Common validation for addresses
@@ -253,31 +250,39 @@ def validate_dissolution_parties_address(filing_json, legal_type, dissolution_ty
253250
This needs not to be validated for SP and GP
254251
This needs not to be validated for administrative dissolution
255252
"""
256-
if dissolution_type in [DissolutionTypes.ADMINISTRATIVE, DissolutionTypes.DELAY]:
257-
return None
253+
msg = []
254+
if (
255+
dissolution_type in [DissolutionTypes.ADMINISTRATIVE, DissolutionTypes.DELAY] or
256+
legal_type in [Business.LegalTypes.SOLE_PROP.value, Business.LegalTypes.PARTNERSHIP.value] or
257+
"parties" not in filing_json["filing"]["dissolution"]
258+
):
259+
return msg
258260

259-
if legal_type in [Business.LegalTypes.SOLE_PROP.value, Business.LegalTypes.PARTNERSHIP.value]:
260-
return None
261+
parties_json = filing_json["filing"]["dissolution"]["parties"]
262+
custodian_count = 0
261263

262-
if "parties" not in filing_json["filing"]["dissolution"]:
263-
return None
264+
for idx, party in enumerate(parties_json):
265+
if _is_custodian_role(party.get("roles", [])):
266+
custodian_count += 1
267+
path = f"/filing/dissolution/parties/{idx}"
268+
if legal_type in Business.CORPS and dissolution_type == DissolutionTypes.VOLUNTARY.value:
269+
# only validate email for CORP voluntary dissolution
270+
email = party.get("officer", {}).get("email")
271+
msg.extend(validate_custodian_email(email, f"{path}/officer/email"))
272+
msg.extend(_validate_custodian_name(party, path))
264273

265-
parties_json = filing_json["filing"]["dissolution"]["parties"]
266-
custodians = list(filter(lambda x: _is_custodian_role(x.get("roles", [])), parties_json))
274+
for address_type in Address.JSON_ADDRESS_TYPES:
275+
msg.extend(_validate_party_address(party, idx, address_type, legal_type in Business.CORPS))
267276

268-
if not custodians:
277+
if custodian_count == 0:
269278
# Handle case where there are no custodians, but there is a liquidator role
270279
# (this is not implemented in the Create UI, keeping behavior here)
271280
if any(_is_liquidator_role(p.get("roles", [])) for p in parties_json):
272-
return None
273-
return [{"error": "Dissolution party is required.", "path": "/filing/dissolution/parties"}]
274-
275-
msg = []
276-
msg.extend(_validate_custodian_email(custodians, dissolution_type, legal_type))
277-
msg.extend(_validate_custodian_name(custodians, dissolution_type, legal_type))
278-
msg.extend(_validate_address_location(custodians, legal_type))
281+
return msg
282+
msg.append({"error": "Dissolution party is required.", "path": "/filing/dissolution/parties"})
283+
return msg
279284

280-
return msg or None
285+
return msg
281286

282287

283288
def _is_custodian_role(roles: list) -> bool:
@@ -290,16 +295,6 @@ def _is_liquidator_role(roles: list) -> bool:
290295
for role in roles)
291296

292297

293-
def _validate_address_location(parties, legal_type):
294-
"""Every party address must be in Canada; CORP types also require the BC province."""
295-
msg = []
296-
require_bc = legal_type in Business.CORPS
297-
for idx, party in enumerate(parties):
298-
for address_type in Address.JSON_ADDRESS_TYPES:
299-
msg.extend(_validate_party_address(party, idx, address_type, require_bc))
300-
return msg
301-
302-
303298
def _validate_party_address(party, idx, address_type, require_bc):
304299
address_path = f"/filing/dissolution/parties/{idx}/{address_type}"
305300
if address_type not in party:
@@ -356,58 +351,50 @@ def _validate_court_order(filing):
356351
return []
357352

358353

359-
def _validate_custodian_email(parties, dissolution_type, legal_type) -> list:
354+
def validate_custodian_email(email, path) -> list:
360355
"""Validate custodian email for voluntary dissolution."""
361-
# Only validate for CORP voluntary dissolution
362-
if not (legal_type in Business.CORPS and dissolution_type == DissolutionTypes.VOLUNTARY.value):
363-
return []
364-
365356
msg = []
366-
for idx, party in enumerate(parties):
367-
email = get_str(party, "/officer/email")
368-
if not email:
369-
msg.append({"error": "Custodian email is required for voluntary dissolution.",
370-
"path": f"/filing/dissolution/parties/{idx}/officer/email"})
371-
elif any(char.isspace() for char in email):
372-
msg.append({
373-
"error": "Custodian email cannot contain any whitespaces.",
374-
"path": f"/filing/dissolution/parties/{idx}/officer/email"
375-
})
357+
if not email:
358+
msg.append({"error": "Custodian email is required.",
359+
"path": path})
360+
elif any(char.isspace() for char in email):
361+
msg.append({
362+
"error": "Custodian email cannot contain any whitespaces.",
363+
"path": path
364+
})
376365
return msg
377366

378-
def _validate_custodian_name(parties, dissolution_type, legal_type) -> list:
379-
"""Validate custodian name of the dissolution filing and trim it."""
380-
# Only validate for CORP voluntary dissolution
381-
if not (legal_type in Business.CORPS and dissolution_type == DissolutionTypes.VOLUNTARY.value):
382-
return []
383367

368+
def _validate_custodian_name(custodian, path) -> list:
369+
"""Validate custodian name of the dissolution filing and trim it."""
384370
msg = []
385-
for idx, party in enumerate(parties):
386-
party_type = get_str(party, "/officer/partyType")
387-
# Organization custodian name (required + no surrounding whitespace) is enforced by the
388-
# schema (business-schemas parties officer.organizationName pattern). firstName is not
389-
# schema-patterned, so it is still validated here.
390-
if party_type != "organization":
391-
first_name = get_str(party, "/officer/firstName")
392-
393-
if first_name is None or not first_name.strip():
394-
msg.append({
395-
"error": "Custodian first name is required.",
396-
"path": f"/filing/dissolution/parties/{idx}/officer/firstName"
371+
party_type = get_str(custodian, "/officer/partyType")
372+
# Organization custodian name (required + no surrounding whitespace) is enforced by the
373+
# schema (business-schemas parties officer.organizationName pattern).
374+
# firstName is not schema-patterned, so it is still validated here.
375+
if party_type != "organization":
376+
first_name = get_str(custodian, "/officer/firstName")
377+
378+
if first_name is None or not first_name.strip():
379+
msg.append({
380+
"error": "Custodian first name is required.",
381+
"path": f"{path}/officer/firstName"
397382
})
398-
elif first_name != first_name.strip():
399-
msg.append({
400-
"error": "Custodian first name cannot have leading or trailing spaces.",
401-
"path": f"/filing/dissolution/parties/{idx}/officer/firstName"
383+
elif first_name != first_name.strip():
384+
msg.append({
385+
"error": "Custodian first name cannot have leading or trailing spaces.",
386+
"path": f"{path}/officer/firstName"
402387
})
403388

404389
return msg
405390

391+
406392
def _check_dissolution_permission(required_permission: str, dissolution_type: str, filing_type: str) -> Error | None:
407393
"""Check if the user has the required permission for the dissolution filing."""
408394
message = "Permission Denied - You do not have permissions file {dissolution_type} {filing_type} filing."
409395
return PermissionService.check_user_permission(required_permission, message=message)
410396

397+
411398
def _validate_dissolution_permission(business: Business, dissolution: dict, dissolution_type: str, filing_type: str, msg: list) -> Error | None:
412399
"""Validate dissolution permission based on business and dissolution type."""
413400

@@ -449,4 +436,4 @@ def _validate_dissolution_permission(business: Business, dissolution: dict, diss
449436
"check_email":True,
450437
"check_address":True,
451438
"check_document_email":True}
452-
)
439+
)

0 commit comments

Comments
 (0)