Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions gcp-jobs/bn-retry/devops/vaults.gcp.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ ACCOUNT_SVC_CLIENT_ID="op://keycloak/$APP_ENV/entity-service-account/ENTITY_SERV
ACCOUNT_SVC_CLIENT_SECRET="op://keycloak/$APP_ENV/entity-service-account/ENTITY_SERVICE_ACCOUNT_CLIENT_SECRET"
ACCOUNT_SVC_TIMEOUT="op://keycloak/$APP_ENV/entity-service-account/ENTITY_SERVICE_ACCOUNT_SVC_TIMEOUT"

BUSINESS_API_URL="op://API/$APP_ENV/business-api/BUSINESS_API_URL"
BUSINESS_API_VERSION_2="op://API/$APP_ENV/business-api/BUSINESS_API_VERSION_2"

# Colin API
COLIN_API_URL="op://API/$APP_ENV/colin-api-entity/COLIN_API_URL"
COLIN_API_VERSION="op://API/$APP_ENV/colin-api-entity/COLIN_API_VERSION"
Expand Down
2 changes: 2 additions & 0 deletions gcp-jobs/bn-retry/src/bn_retry/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class _Config: # pylint: disable=too-few-public-methods

PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))

LEGAL_API_URL = os.getenv("BUSINESS_API_URL", "") + os.getenv("BUSINESS_API_VERSION_2", "")

# Colin API configuration
COLIN_API_URL = os.getenv("COLIN_API_URL", "")
COLIN_API_VERSION = os.getenv("COLIN_API_VERSION", "/api/v1")
Expand Down
22 changes: 21 additions & 1 deletion gcp-jobs/bn-retry/src/bn_retry/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@
import uuid
from datetime import UTC, datetime

import requests
from flask import current_app
from simple_cloudevent import SimpleCloudEvent, to_queue_message
from sqlalchemy import func, or_

from bn_retry import db
from bn_retry.services import check_bn15_status_batch, gcp_queue
from bn_retry.services import check_bn15_status_batch, gcp_queue, get_bearer_token
from business_model.models import Business


Expand Down Expand Up @@ -123,6 +124,24 @@
raise


def regenerate_documents(identifier: str):
"""Regenerate documents for business."""
try:
timeout = int(current_app.config.get("ACCOUNT_SVC_TIMEOUT"))
token = get_bearer_token(timeout)
legal_api_url = current_app.config.get("LEGAL_API_URL")
url = f"{legal_api_url}/businesses/{identifier}/documents/regenerate?only_required=true&previous=true"
response = requests.post(
url,
headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}"},
timeout=timeout,
data={}
)
response.raise_for_status()
except Exception as err:
current_app.logger.error("Failed to regenerate documents for %s %s", identifier, err, exc_info=True)

Check failure on line 142 in gcp-jobs/bn-retry/src/bn_retry/worker.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=bcgov_lear&issues=AZ6Og6XXdXz7WczTyfrH&open=AZ6Og6XXdXz7WczTyfrH&pullRequest=4454


def run_job():
"""Run the BN15 retry job with batch processing.

Expand Down Expand Up @@ -169,6 +188,7 @@
update_business_bn(business, bn15)
publish_email_notification(identifier)
publish_business_event(identifier)
regenerate_documents(identifier)
except Exception as ex:
current_app.logger.error(f"Error updating {identifier}: {ex}")
# Continue with other updates in batch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import requests
from flask import current_app, jsonify, request
from flask_cors import cross_origin
from flask_pydantic import validate as pydantic_validate
from pydantic import BaseModel

from legal_api.core import Filing
from legal_api.exceptions import ErrorCode, get_error_message
from legal_api.models import Business, Document
from legal_api.models import Business, Document, UserRoles
from legal_api.models import Filing as FilingModel
from legal_api.reports import get_pdf
from legal_api.reports.document_service import DocumentService
Expand Down Expand Up @@ -102,8 +104,13 @@
file_name=file_name, filing_id=filing_id, identifier=identifier)
), HTTPStatus.NOT_FOUND

if _regenerate(legal_filing_name):
return get_pdf(filing.storage, legal_filing_name, True)
if _should_regenerate(legal_filing_name):
if jwt.validate_roles([UserRoles.staff, UserRoles.system]):
return get_pdf(filing.storage, legal_filing_name, True)
else:
return jsonify(
message="Unauthorized to regenerate"
), HTTPStatus.UNAUTHORIZED

if drs_params := _get_drs_params():
return _get_drs_documents(drs_params)
Expand Down Expand Up @@ -137,7 +144,7 @@
return params


def _regenerate(legal_filing_name: str) -> bool:
def _should_regenerate(legal_filing_name: str) -> bool:
"""Determine if individual report request should regenerate and update the DRS."""
if legal_filing_name and legal_filing_name.lower().startswith("receipt"):
return False
Expand Down Expand Up @@ -255,3 +262,87 @@
return Business.BUSINESSES.get(legal_type, {}).get("numberedDescription", "")

return ""


class RegenerateQueryModel(BaseModel):
"""Document regenerate query model."""

previous: Optional[bool]

Check failure on line 270 in legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit default value to this optional field.

See more on https://sonarcloud.io/project/issues?id=bcgov_lear&issues=AZ6OrCcc37g2-D0NTSLg&open=AZ6OrCcc37g2-D0NTSLg&pullRequest=4454
only_required: Optional[bool]

Check failure on line 271 in legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit default value to this optional field.

See more on https://sonarcloud.io/project/issues?id=bcgov_lear&issues=AZ6OrCcc37g2-D0NTSLh&open=AZ6OrCcc37g2-D0NTSLh&pullRequest=4454


@cors_preflight("POST")
@bp.route("/<string:identifier>/documents/regenerate", methods=["POST", "OPTIONS"])
@bp.route(DOCUMENTS_BASE_ROUTE + "/regenerate", methods=["POST", "OPTIONS"])
@cross_origin(origin="*")
@jwt.has_one_of_roles([UserRoles.system])
@pydantic_validate()
def regenerate_document(query: RegenerateQueryModel, identifier: str, filing_id: Optional[int] = None):
"""
Regenerate documents for a business.
"""
if identifier.startswith("T"):
# not supported for temp registrations
return {}, HTTPStatus.NOT_FOUND

business = Business.find_by_identifier(identifier)
if filing_id is not None:
filing = Filing.find_by_id(filing_id)
else:
filing_storage = FilingModel.get_most_recent_filing(business.id)
filing = Filing()
filing.storage = filing_storage

if not business or not filing:
return {}, HTTPStatus.NOT_FOUND

_regenerate_documents(business, filing, query)
if query.previous: # regenerate documents for previous completed filings
while filing_storage := FilingModel.get_previous_completed_filing(filing.storage):
prev_filing = Filing()
prev_filing.storage = filing_storage
_regenerate_documents(business, prev_filing, query)
filing = prev_filing

return {}, HTTPStatus.OK


def _regenerate_documents(business: Business, filing: Filing, query: RegenerateQueryModel):
"""Regenerate documents for a business."""
# these documents shows BN15
regeneration_required = [
"registration",
"amendedRegistrationStatement",
"correctedRegistrationStatement",
"changeOfRegistration",
"annualReport",
"dissolution"
]
document_list = Filing.get_document_list(business, filing, jwt)
if not document_list or "documents" not in document_list:
return {}, HTTPStatus.OK

docs = document_list.get("documents", {})

doc_keys = []
if legal_filings := docs.pop("legalFilings", None):
for doc in legal_filings:
doc_keys.extend(doc.keys())

docs.pop("receipt", None)
docs.pop("staticDocuments", None)
docs.pop("uploadedCourtOrder", None)
doc_keys.extend(docs.keys())

for doc_name in doc_keys:
if query.only_required and doc_name not in regeneration_required:
continue

response = get_pdf(filing.storage, doc_name, True)
status_code = response[1] if isinstance(response, tuple) else getattr(response, "status_code", HTTPStatus.OK)

if status_code != HTTPStatus.OK:
current_app.logger.error(
f"Failed to regenerate document {doc_name} for filing {filing.id} of {business.identifier}"
)
return response
34 changes: 32 additions & 2 deletions legal-api/tests/unit/resources/v2/test_business_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
from http import HTTPStatus
from legal_api.models.business import Business


from legal_api.services.authz import STAFF_ROLE
from legal_api.services.authz import STAFF_ROLE, SYSTEM_ROLE
from legal_api.models.document import Document, DocumentType
from tests import integration_reports
from tests.unit.models import factory_business, factory_completed_filing, factory_incorporation_filing
Expand Down Expand Up @@ -322,3 +321,34 @@ def test_get_business_summary_involuntary_dissolution(requests_mock, session, cl
assert state_filing['filingType'] == 'dissolution'
assert state_filing['filingName'] == 'Involuntary Dissolution'


def test_regenerate_documents(mocker, session, client, jwt):
"""Assert that we can regenerate documents for a filing."""
# setup
identifier = 'CP7654321'
business = factory_business(identifier)
filing_json = copy.deepcopy(FILING_HEADER)
filing_json['filing']['header']['name'] = 'specialResolution'
filing_json['filing']['alteration'] = copy.deepcopy(ALTERATION)
filing_json['filing']['alteration']['memorandumInResolution'] = True
filing_json['filing']['alteration']['rulesInResolution'] = True
filing = factory_completed_filing(business, filing_json)

mocker.patch('legal_api.resources.v2.business.business_filings.business_documents.get_pdf',
return_value=(b'pdf-content', HTTPStatus.OK))
headers = create_header(jwt, [SYSTEM_ROLE], identifier)
# test
rv = client.post(f'/api/v2/businesses/{identifier}/filings/{filing.id}/documents/regenerate', headers=headers)
# check
assert rv.status_code == HTTPStatus.OK


def test_regenerate_documents_invalid_business_filing(session, client, jwt):
"""Assert that regenerating documents for an invalid business or filing returns 404."""
identifier = 'CP7654321'
headers = create_header(jwt, [SYSTEM_ROLE], identifier)
# test with invalid filing id
rv = client.post(f'/api/v2/businesses/{identifier}/filings/9999/documents/regenerate', headers=headers)
assert rv.status_code == HTTPStatus.NOT_FOUND


2 changes: 2 additions & 0 deletions queue_services/business-bn/devops/vaults.gcp.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ BN_HUB_CLIENT_SECRET="op://entity/$APP_ENV/business-bn/BN_HUB_CLIENT_SECRET"
BN_HUB_MAX_RETRY="op://entity/$APP_ENV/business-bn/BN_HUB_MAX_RETRY"
LEGISLATIVE_TIMEZONE="op://entity/$APP_ENV/business-api/LEGISLATIVE_TIMEZONE"
LD_SDK_KEY="op://launchdarkly/$APP_ENV/business-filings/BUSINESS_FILING_LD_CLIENT_ID"
BUSINESS_API_URL="op://API/$APP_ENV/business-api/BUSINESS_API_URL"
BUSINESS_API_VERSION_2="op://API/$APP_ENV/business-api/BUSINESS_API_VERSION_2"
COLIN_API_URL="op://API/$APP_ENV/colin-api-entity/COLIN_API_URL"
COLIN_API_VERSION="op://API/$APP_ENV/colin-api-entity/COLIN_API_VERSION"
REGISTRIES_SEARCH_API_INTERNAL_URL="op://registries-search/$APP_ENV/search-api/INTERNAL_URL"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,25 @@
except Exception as err: # pylint: disable=broad-except;
current_app.logger.error("Failed to publish BN update for %s %s", business.identifier, err, exc_info=True)

_regenerate_documents(business)


def _regenerate_documents(business: Business):
"""Regenerate documents for business."""
try:
token = AccountService.get_bearer_token()
legal_api_url = current_app.config.get("LEGAL_API_URL")
url = f"{legal_api_url}/businesses/{business.identifier}/documents/regenerate?only_required=true&previous=true"
response = requests.post(
url,
headers={**AccountService.CONTENT_TYPE_JSON, "Authorization": AccountService.BEARER + token},
timeout=AccountService.timeout,
data={}
)
response.raise_for_status()
except Exception as err:
current_app.logger.error("Failed to regenerate documents for %s %s", business.identifier, err, exc_info=True)

Check failure on line 184 in queue_services/business-bn/src/business_bn/bn_processors/registration.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=bcgov_lear&issues=AZ6Og6W-dXz7WczTyfrG&open=AZ6Og6W-dXz7WczTyfrG&pullRequest=4454


def _inform_cra(
business: Business, # pylint: disable=too-many-locals
Expand Down
1 change: 1 addition & 0 deletions queue_services/business-bn/src/business_bn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Config: # pylint: disable=too-few-public-methods
PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))

LD_SDK_KEY = os.getenv("LD_SDK_KEY", None)
LEGAL_API_URL = os.getenv("BUSINESS_API_URL", "") + os.getenv("BUSINESS_API_VERSION_2", "")
COLIN_API = f"{os.getenv('COLIN_API_URL', '')}{os.getenv('COLIN_API_VERSION', '')}"

SEARCH_API = (
Expand Down
Loading