diff --git a/docs/mhr-api-internal.yaml b/docs/mhr-api-internal.yaml index 418dd1fb6..7e2220a65 100644 --- a/docs/mhr-api-internal.yaml +++ b/docs/mhr-api-internal.yaml @@ -722,6 +722,46 @@ paths: $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' + + '/mhr/api/v1/documents/qs-document-ids': + parameters: + - $ref: '#/components/parameters/accountId' + get: + tags: + - Registration + summary: Retrieve a Qualified Supplier document ID for a new staff review registration. + description: For MHR registrations that are submitted by qualified suppliers and reviewed by staff, retrieve a document ID to save in DRS before the registration is submitted for review. Intended to only be called once per registration, and only when the registration is submitted by a qualified supplier and will be reviewed by staff. + operationId: get-qs-document-id + responses: + '200': + description: OK + headers: + Access-Control-Allow-Origin: + $ref: '#/components/headers/AccessControlAllowOrigin' + Access-Control-Allow-Methods: + $ref: '#/components/headers/AccessControlAllowMethods' + Access-Control-Allow-Headers: + $ref: '#/components/headers/AccessControlAllowHeaders' + Access-Control-Max-Age: + $ref: '#/components/headers/AccessControlMaxAge' + content: + application/json: + schema: + $ref: '#/components/schemas/documentId' + examples: + /api/v1/documents/qs-document-ids/get: + summary: Request a QS document ID response. + value: + documentId: '10109535' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + '/mhr/api/v1/documents/verify/{documentId}': parameters: - $ref: '#/components/parameters/accountId' @@ -3155,13 +3195,7 @@ paths: $ref: '#/components/responses/InternalServerError' '/mhr/api/v1/reviews/{reviewId}': parameters: - - name: reviewId - in: path - description: 'The identifier of a staff review registration. If an invalid value is submitted then the response is a [404] status code.' - required: true - schema: - type: string - example: '123' + - $ref: '#/components/parameters/reviewId' - name: Account-Id in: header description: The account that the user is operating on behalf of. @@ -5178,6 +5212,13 @@ components: csaStandard: 'Z240,' rebuiltRemarks: 'REBUILT AS A DOUBLE WIDE, MAKE/MODEL CUSTOM, YEAR 1995' otherRemarks: 'BC SAFETY AUTHORITY #339556, PERMIT# EL-721296-2018' + documentId: + type: object + description: The unique identifier of one or more documents that are associated with a single MH registration. + properties: + documentId: + type: string + example: '10590422' documentSummary: title: documentSummary type: object diff --git a/mhr-api/pyproject.toml b/mhr-api/pyproject.toml index af4272946..51aa1c587 100644 --- a/mhr-api/pyproject.toml +++ b/mhr-api/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mhr-api" -version = "2.1.10" +version = "2.1.11" description = "" authors = ["dlovett "] license = "BSD 3" diff --git a/mhr-api/src/mhr_api/models/registration_utils.py b/mhr-api/src/mhr_api/models/registration_utils.py index 5867c45c1..ad10b2987 100644 --- a/mhr-api/src/mhr_api/models/registration_utils.py +++ b/mhr-api/src/mhr_api/models/registration_utils.py @@ -114,6 +114,8 @@ SORT_DESCENDING = "descending" DOC_ID_QUALIFIED_CLAUSE = ", get_mhr_doc_qualified_id() AS doc_id" DOC_ID_MANUFACTURER_CLAUSE = ", get_mhr_doc_manufacturer_id() AS doc_id" +QUERY_NEXT_QUALIFIED_DOC_ID = "select get_mhr_doc_qualified_id()" +QUERY_NEXT_MANUFACTURER_DOC_ID = "select get_mhr_doc_manufacturer_id()" DOC_ID_GOV_AGENT_CLAUSE = ", get_mhr_doc_gov_agent_id() AS doc_id" DOC_ID_STAFF_CLAUSE = ", get_mhr_doc_staff_id() AS doc_id" BATCH_DOC_NAME_MANUFACTURER_MHREG = "batch-manufacturer-mhreg-report-{time}.pdf" @@ -442,6 +444,16 @@ def get_change_generated_values(registration, draft, user_group: str = None, sta return registration +def get_qs_document_id(user_group: str) -> str: + """Get db generated qualifed supplier document ID based on the user group. Only intended for DRS integration.""" + query: str = QUERY_NEXT_QUALIFIED_DOC_ID + if user_group is not None and user_group == MANUFACTURER_GROUP: + query = QUERY_NEXT_MANUFACTURER_DOC_ID + result = db.session.execute(text(query)) + row = result.first() + return str(row[0]) + + def get_registration_id() -> int: """Get db generated registration id, initially for creating a manufacturer.""" result = db.session.execute(text(QUERY_REG_ID_PKEY)) diff --git a/mhr-api/src/mhr_api/resources/v1/documents.py b/mhr-api/src/mhr_api/resources/v1/documents.py index 35a3d94aa..d45dda7c2 100755 --- a/mhr-api/src/mhr_api/resources/v1/documents.py +++ b/mhr-api/src/mhr_api/resources/v1/documents.py @@ -15,11 +15,12 @@ from http import HTTPStatus -from flask import Blueprint, request +from flask import Blueprint, jsonify, request from flask_cors import cross_origin from mhr_api.exceptions import BusinessException, DatabaseException from mhr_api.models import MhrRegistration +from mhr_api.models.registration_utils import get_qs_document_id from mhr_api.models.type_tables import MhrRegistrationTypes from mhr_api.reports.v2.report_utils import ReportTypes from mhr_api.resources import registration_utils as reg_utils @@ -129,6 +130,33 @@ def get_documents(document_id: str): # pylint: disable=too-many-return-statemen return resource_utils.default_exception_response(default_exception) +@bp.route("/qs-document-ids", methods=["GET", "OPTIONS"]) +@cross_origin(origin="*") +@jwt.requires_auth +def get_qs_document_ids(): + """Get a unique qualified supplier document ID based on the user token for DRS integration.""" + try: + # Quick check: must provide an account ID. + account_id = resource_utils.get_account_id(request) + if account_id is None: + return resource_utils.account_required_response() + user_group: str = get_group(jwt) + logger.info(f"get next QS document_id starting account_id={account_id} user group={user_group}") + # Verify request JWT and account ID + if not authorized(account_id, jwt): + return resource_utils.unauthorized_error_response(account_id) + if is_staff(jwt): + logger.warning("Get QS document ID endpoint is not intended for staff users.") + doc_id: str = get_qs_document_id(user_group) + logger.info(f"New group {user_group} doc Id={doc_id}") + response_json = {"documentId": doc_id} + return jsonify(response_json), HTTPStatus.OK + except DatabaseException as db_exception: + return resource_utils.db_exception_response(db_exception, account_id, "GET QS document id") + except Exception as default_exception: # noqa: B902; return nicer default error + return resource_utils.default_exception_response(default_exception) + + def map_report_type(reg_json: dict, staff: bool) -> str: """Map the registration type to the report type.""" if staff: diff --git a/mhr-api/tests/unit/api/test_documents.py b/mhr-api/tests/unit/api/test_documents.py index 8dcc12248..17db54b23 100644 --- a/mhr-api/tests/unit/api/test_documents.py +++ b/mhr-api/tests/unit/api/test_documents.py @@ -21,10 +21,14 @@ import pytest from flask import current_app -from mhr_api.services.authz import COLIN_ROLE, MHR_ROLE, STAFF_ROLE +from mhr_api.services.authz import MHR_ROLE, STAFF_ROLE, COLIN_ROLE, REQUEST_EXEMPTION_RES, \ + TRANSFER_DEATH_JT, TRANSFER_SALE_BENEFICIARY, REQUEST_TRANSPORT_PERMIT, \ + REGISTER_MH from tests.unit.services.utils import create_header, create_header_account +MANUFACTURER_ROLES = [MHR_ROLE, TRANSFER_SALE_BENEFICIARY, REQUEST_TRANSPORT_PERMIT, REGISTER_MH] +QUALIFIED_USER = [MHR_ROLE, REQUEST_EXEMPTION_RES, TRANSFER_DEATH_JT, TRANSFER_SALE_BENEFICIARY] # testdata pattern is ({desc}, {roles}, {status}, {has_account}, {doc_id}, {exists}, {valid}) TEST_VERIFY_ID_DATA = [ ('Missing account', [MHR_ROLE], HTTPStatus.BAD_REQUEST, False, '40583993', True, True), @@ -47,6 +51,13 @@ ('Not exists no checksum staff', [MHR_ROLE, STAFF_ROLE], HTTPStatus.NOT_FOUND, True, '1001000000'), ('Invalid checksum', [MHR_ROLE], HTTPStatus.BAD_REQUEST, True, '79289200') ] +# testdata pattern is ({desc}, {roles}, {status}, {has_account}, {start_digit}) +TEST_DATA_QS_DOC_ID_DATA = [ + ('Missing account', [MHR_ROLE], HTTPStatus.BAD_REQUEST, False, None), + ('Invalid role', [COLIN_ROLE], HTTPStatus.UNAUTHORIZED, True, None), + ('Valid request QS lawyyer/notary', QUALIFIED_USER, HTTPStatus.OK, True, '1'), + ('Valid request QS manufacturer', MANUFACTURER_ROLES, HTTPStatus.OK, True, '8'), +] @pytest.mark.parametrize('desc,roles,status,has_account,doc_id,exists,valid', TEST_VERIFY_ID_DATA) @@ -93,3 +104,23 @@ def test_get_document(session, client, jwt, desc, roles, status, has_account, do current_app.logger.debug(response) assert response assert response['documentId'] == doc_id + + +@pytest.mark.parametrize('desc,roles,status,has_account,start_digit', TEST_DATA_QS_DOC_ID_DATA) +def test_get_qs_doc_id(session, client, jwt, desc, roles, status, has_account, start_digit): + """Assert that the get QS document id endpoint works as expected.""" + headers = None + # setup + if has_account: + headers = create_header_account(jwt, roles) + else: + headers = create_header(jwt, roles) + # test + rv = client.get('/api/v1/documents/qs-document-ids',headers=headers) + + # check + assert rv.status_code == status + if rv.status_code == HTTPStatus.OK: + response = rv.json + assert response + assert str(response.get("documentId")).startswith(start_digit) diff --git a/mhr-api/tests/unit/models/test_registration_utils.py b/mhr-api/tests/unit/models/test_registration_utils.py index 6234f31ed..683435937 100644 --- a/mhr-api/tests/unit/models/test_registration_utils.py +++ b/mhr-api/tests/unit/models/test_registration_utils.py @@ -117,6 +117,12 @@ ('Invalid exists', '000900', False), ('Invalid too high', '999900', False) ] +# testdata pattern is ({description}, {group_id}, {start_digit}) +TEST_DATA_DOC_ID = [ + ('QS Dealer', DEALERSHIP_GROUP, '1'), + ('QS lawyer/notary', QUALIFIED_USER_GROUP, '1'), + ('QS manufacturer', MANUFACTURER_GROUP, '8') +] # testdata pattern is ({account_id}, {sort_criteria}, {sort_order}, {mhr_numbers}, {expected_clause}) TEST_QUERY_ORDER_DATA = [ ('PS12345', None, None, "'000900'", queries.REG_ORDER_BY_DATE), @@ -303,6 +309,14 @@ def test_validate_mhr_number(session, desc, mhr_number, valid): assert result == valid +@pytest.mark.parametrize('desc, user_group, start_digit', TEST_DATA_DOC_ID) +def test_qs_doc_id(session, desc, user_group, start_digit): + """Assert that the QS get next document id works as expected.""" + result: str = reg_utils.get_qs_document_id(user_group) + assert result + assert result.startswith(start_digit) + + @pytest.mark.parametrize('account_id,sort_criteria,sort_order,mhr_numbers,expected_clause', TEST_QUERY_ORDER_DATA) def test_account_reg_order(session, account_id, sort_criteria, sort_order, mhr_numbers, expected_clause): """Assert that account registration query order by clause is as expected."""