From 77272606c4af5ca7f8990353c0b4c83e5cc60d8b Mon Sep 17 00:00:00 2001 From: dinesh Date: Thu, 2 Jan 2025 15:30:00 -0800 Subject: [PATCH] Create inspection requirement --- ...c75df1c4_inspection_requirement_patches.py | 68 +++++++++ .../inspection/inspection_req_detail_doc.py | 17 ++- .../inspection_req_source_detail.py | 20 ++- .../inspection/inspection_requirement.py | 26 +++- .../src/compliance_api/resources/__init__.py | 2 + .../resources/inspection_requirement.py | 49 ++++++ .../src/compliance_api/schemas/__init__.py | 1 + .../schemas/inspection_requirement.py | 144 ++++++++++++++++++ .../src/compliance_api/services/__init__.py | 1 + .../services/inspection_requirement.py | 74 +++++++++ 10 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 compliance-api/migrations/versions/791cc75df1c4_inspection_requirement_patches.py create mode 100644 compliance-api/src/compliance_api/resources/inspection_requirement.py create mode 100644 compliance-api/src/compliance_api/schemas/inspection_requirement.py create mode 100644 compliance-api/src/compliance_api/services/inspection_requirement.py diff --git a/compliance-api/migrations/versions/791cc75df1c4_inspection_requirement_patches.py b/compliance-api/migrations/versions/791cc75df1c4_inspection_requirement_patches.py new file mode 100644 index 0000000..a6db14a --- /dev/null +++ b/compliance-api/migrations/versions/791cc75df1c4_inspection_requirement_patches.py @@ -0,0 +1,68 @@ +"""inspection_requirement patches + +Revision ID: 791cc75df1c4 +Revises: a684c47ac3e8 +Create Date: 2025-01-02 15:02:27.066126 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '791cc75df1c4' +down_revision = 'a684c47ac3e8' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('inspection_req_detail_documents', schema=None) as batch_op: + batch_op.alter_column('document_title', + existing_type=sa.VARCHAR(), + nullable=True, + existing_comment='The title of the document') + + with op.batch_alter_table('inspection_requirements', schema=None) as batch_op: + batch_op.add_column(sa.Column('sort_order', sa.Integer(), nullable=False, comment='The order of requirements')) + batch_op.alter_column('enforcement_action_id', + existing_type=sa.INTEGER(), + nullable=True, + existing_comment='The enforcement action taken on the requirement') + batch_op.alter_column('compliance_finding_id', + existing_type=sa.INTEGER(), + nullable=True, + existing_comment='Compliance finding of the requirement') + + with op.batch_alter_table('inspection_requirements_version', schema=None) as batch_op: + batch_op.add_column(sa.Column('sort_order', sa.Integer(), autoincrement=False, nullable=True, comment='The order of requirements')) + batch_op.add_column(sa.Column('sort_order_mod', sa.Boolean(), server_default=sa.text('false'), nullable=False)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('inspection_requirements_version', schema=None) as batch_op: + batch_op.drop_column('sort_order_mod') + batch_op.drop_column('sort_order') + + with op.batch_alter_table('inspection_requirements', schema=None) as batch_op: + batch_op.alter_column('compliance_finding_id', + existing_type=sa.INTEGER(), + nullable=False, + existing_comment='Compliance finding of the requirement') + batch_op.alter_column('enforcement_action_id', + existing_type=sa.INTEGER(), + nullable=False, + existing_comment='The enforcement action taken on the requirement') + batch_op.drop_column('sort_order') + + with op.batch_alter_table('inspection_req_detail_documents', schema=None) as batch_op: + batch_op.alter_column('document_title', + existing_type=sa.VARCHAR(), + nullable=False, + existing_comment='The title of the document') + + # ### end Alembic commands ### diff --git a/compliance-api/src/compliance_api/models/inspection/inspection_req_detail_doc.py b/compliance-api/src/compliance_api/models/inspection/inspection_req_detail_doc.py index 24d5940..d732654 100644 --- a/compliance-api/src/compliance_api/models/inspection/inspection_req_detail_doc.py +++ b/compliance-api/src/compliance_api/models/inspection/inspection_req_detail_doc.py @@ -34,7 +34,7 @@ class InspectionReqDetailDocument(BaseModelVersioned): comment="The unique identifier of the document type", nullable=False, ) - document_title = Column(String, nullable=False, comment="The title of the document") + document_title = Column(String, nullable=True, comment="The title of the document") section_number = Column( String, nullable=True, @@ -49,8 +49,21 @@ class InspectionReqDetailDocument(BaseModelVersioned): String, nullable=True, comment="Additional description of the document" ) requirement_source_detail = relationship( - "InspectionReqSourceDetail", foreign_keys=[req_detail_id], lazy="select" + "InspectionReqSourceDetail", + back_populates="documents", + lazy="select", ) document_type = relationship( "DocumentType", foreign_keys=[document_type_id], lazy="select" ) + + @classmethod + def create_doc_detail(cls, doc_detail_obj, session=None): + """Persist doc detail in database.""" + doc_detail = InspectionReqDetailDocument(**doc_detail_obj) + if session: + session.add(doc_detail) + session.flush() + else: + doc_detail.save() + return doc_detail diff --git a/compliance-api/src/compliance_api/models/inspection/inspection_req_source_detail.py b/compliance-api/src/compliance_api/models/inspection/inspection_req_source_detail.py index b2ec9fa..95257b6 100644 --- a/compliance-api/src/compliance_api/models/inspection/inspection_req_source_detail.py +++ b/compliance-api/src/compliance_api/models/inspection/inspection_req_source_detail.py @@ -57,8 +57,26 @@ class InspectionReqSourceDetail(BaseModelVersioned): comment="The description of the requirement source detail", ) inspection_requirement = relationship( - "InspectionRequirement", foreign_keys=[requirement_id], lazy="select" + "InspectionRequirement", + back_populates="requirement_source_details", + lazy="select", ) requirement_source = relationship( "RequirementSource", foreign_keys=[requirement_source_id], lazy="joined" ) + documents = relationship( + "InspectionReqDetailDocument", + back_populates="requirement_source_detail", + lazy="joined", + ) + + @classmethod + def create_source_detail(cls, source_detail_obj, session=None): + """Persist source detail in database.""" + source_detail = InspectionReqSourceDetail(**source_detail_obj) + if session: + session.add(source_detail) + session.flush() + else: + source_detail.save() + return source_detail diff --git a/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py b/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py index f83d54d..892d6cd 100644 --- a/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py +++ b/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py @@ -35,7 +35,7 @@ class InspectionRequirement(BaseModelVersioned): "enforcement_action_options.id", name="insepction_requirements_enforcement_action_fkey", ), - nullable=False, + nullable=True, comment="The enforcement action taken on the requirement", ) compliance_finding_id = Column( @@ -44,12 +44,12 @@ class InspectionRequirement(BaseModelVersioned): "compliance_finding_options.id", name="inspection_req_compliance_finding_fkey", ), - nullable=False, + nullable=True, comment="Compliance finding of the requirement", ) - findings = Column( - String, comment="The findings of the requirement" - ) + findings = Column(String, nullable=True, comment="The findings of the requirement") + sort_order = Column(Integer, nullable=False, comment="The order of requirements") + inspection = relationship("Inspection", foreign_keys=[inspection_id], lazy="select") topic = relationship("Topic", foreign_keys=[topic_id], lazy="joined") enforcement_action = relationship( @@ -58,3 +58,19 @@ class InspectionRequirement(BaseModelVersioned): compliance_finding = relationship( "ComplianceFindingOption", foreign_keys=[compliance_finding_id], lazy="joined" ) + requirement_source_details = relationship( + "InspectionReqSourceDetail", + back_populates="inspection_requirement", + lazy="joined", + ) + + @classmethod + def create_requirement(cls, requirement_obj, session=None): + """Persist inspection requirement in database.""" + requirement = InspectionRequirement(**requirement_obj) + if session: + session.add(requirement) + session.flush() + else: + requirement.save() + return requirement diff --git a/compliance-api/src/compliance_api/resources/__init__.py b/compliance-api/src/compliance_api/resources/__init__.py index e031f25..4f3671b 100644 --- a/compliance-api/src/compliance_api/resources/__init__.py +++ b/compliance-api/src/compliance_api/resources/__init__.py @@ -31,6 +31,7 @@ from .continuation_report import API as CONTINUATION_REPORT_API from .enforcement_action import API as ENFORCEMENT_ACTION_API from .inspection import API as INSPECTION_API +from .inspection_requirement import API as INSPECTION_REQUIREMENT_API from .ops import API as OPS_API from .position import API as POSITION_API from .project import API as PROJECT_API @@ -85,3 +86,4 @@ API.add_namespace(CONTINUATION_REPORT_API) API.add_namespace(ENFORCEMENT_ACTION_API) API.add_namespace(COMPLIANCE_FINDING_API) +API.add_namespace(INSPECTION_REQUIREMENT_API, path="inspections//requirements") diff --git a/compliance-api/src/compliance_api/resources/inspection_requirement.py b/compliance-api/src/compliance_api/resources/inspection_requirement.py new file mode 100644 index 0000000..43f481b --- /dev/null +++ b/compliance-api/src/compliance_api/resources/inspection_requirement.py @@ -0,0 +1,49 @@ +"""InspectionRequirementResource.""" + +from http import HTTPStatus + +from flask_restx import Namespace, Resource + +from compliance_api.auth import auth +from compliance_api.schemas.inspection_requirement import InspectionRequirementCreateSchema, InspectionRequirementSchema +from compliance_api.services import InspectionRequirementService +from compliance_api.utils.util import cors_preflight + +from .apihelper import Api as ApiHelper + + +API = Namespace("requirements", description="Endpoints for Inspection Requirement") +inspection_requirement_create_model = ApiHelper.convert_ma_schema_to_restx_model( + API, InspectionRequirementCreateSchema(), "InspectionRequirement" +) + +inspection_requirement_list_model = ApiHelper.convert_ma_schema_to_restx_model( + API, InspectionRequirementSchema(), "InspectionRequirementList" +) + + +@cors_preflight("GET, OPTIONS, POST") +@API.route("", methods=["POST", "GET", "OPTIONS"]) +class InspectionRequirements(Resource): + """InspectionRequirements.""" + + @staticmethod + @ApiHelper.swagger_decorators(API, endpoint_description="Create an inspection") + @API.expect(inspection_requirement_create_model) + @API.response( + code=201, + model=inspection_requirement_list_model, + description="InspectionCreated", + ) + @API.response(400, "Bad Request") + @auth.require + def post(inspection_id): + """Create an inspection.""" + requirement_data = InspectionRequirementCreateSchema().load(API.payload) + created_requirement = InspectionRequirementService.create( + inspection_id, requirement_data + ) + return ( + InspectionRequirementSchema().dump(created_requirement), + HTTPStatus.CREATED, + ) diff --git a/compliance-api/src/compliance_api/schemas/__init__.py b/compliance-api/src/compliance_api/schemas/__init__.py index 201fd45..1bc4a84 100644 --- a/compliance-api/src/compliance_api/schemas/__init__.py +++ b/compliance-api/src/compliance_api/schemas/__init__.py @@ -26,6 +26,7 @@ from .inspection import ( InspectionAttendanceSchema, InspectionCreateSchema, InspectionOfficerSchema, InspectionSchema, InspectionStatusSchema, InspectionUpdateSchema) +from .inspection_requirement import InspectionRequirementCreateSchema, InspectionRequirementSchema from .paginate import PaginationParameterSchema from .project import ProjectSchema from .staff_user import StaffUserCreateSchema, StaffUserSchema, StaffUserUpdateSchema diff --git a/compliance-api/src/compliance_api/schemas/inspection_requirement.py b/compliance-api/src/compliance_api/schemas/inspection_requirement.py new file mode 100644 index 0000000..4411687 --- /dev/null +++ b/compliance-api/src/compliance_api/schemas/inspection_requirement.py @@ -0,0 +1,144 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Inspection requirement Schema Schema.""" +from marshmallow import EXCLUDE, fields + +from compliance_api.models import InspectionReqDetailDocument, InspectionReqSourceDetail, InspectionRequirement + +from .base_schema import AutoSchemaBase, BaseSchema + + +class InspectionReqDetailDocCreateSchema(BaseSchema): + """InpsectionReqDetailDocCreateSchema.""" + + document_type_id = fields.Int( + metadata={"description": "The unique identifier of the document type"}, + required=True, + ) + document_title = fields.Str(metadata={"description": "The title of the document"}) + section_number = fields.Str( + metadata={ + "description": "The highlighted section number in the uploaded document" + } + ) + section_title = fields.Str( + metadata={"description": "Additional description of the document"} + ) + + +class InspectionReqSourceDetailCreateSchema(BaseSchema): + """InspectionReqSourceDetailSchema.""" + + requirement_source_id = fields.Int( + metadata={"description": "The unique identifier of the requirement."}, + required=True, + ) + section_number = fields.Str( + metadata={ + "description": "The optional section number associated with requirement sources" + "(Act (2018), Schedule A, Compliance Agreement, Act (2002))" + } + ) + condition_number = fields.Str( + metadata={ + "description": "The optional condition number associated with" + "rquirement sources(Schedule B, EAC Certificate)" + } + ) + amendment_number = fields.Str( + metadata={ + "description": "The optional amendment number if the requirement source is EAC Amendment" + } + ) + title = fields.Str( + metadata={"description": "The title of the requirement source detail"} + ) + description = fields.Str( + metadata={"description": "The description of the requirement source detail"} + ) + documents = fields.List(fields.Nested(InspectionReqDetailDocCreateSchema)) + + +class InspectionRequirementCreateSchema(BaseSchema): + """InspectionRequirementCreateSchema.""" + + summary = fields.Str( + metadata={"description": "The summary of the requirement."}, required=True + ) + topic_id = fields.Int( + metadata={ + "description": "The unique identifier of the topic associated with the inspection" + }, + required=True, + ) + enforcement_action_id = fields.Int( + metadata={"description": "The enforcement action identifier."} + ) + compliance_finding_id = fields.Int( + metadata={"description": "The unique identifier of the compliance findings."} + ) + findings = fields.Str( + metadata={"description": "The requirement findings in html format."} + ) + sort_order = fields.Int( + metadata={"description": "The order of the inspection requirements"}, + required=True, + ) + requirement_source_details = fields.List( + fields.Nested(InspectionReqSourceDetailCreateSchema) + ) + + +class InspectionReqDetailDocSchema( + AutoSchemaBase +): # pylint: disable=too-many-ancestors + """InspectionReqDetailDocSchema.""" + + class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods + """Meta.""" + + unknown = EXCLUDE + model = InspectionReqDetailDocument + include_fk = True + + +class InspectionReqSourceDetailSchema( + AutoSchemaBase +): # pylint: disable=too-many-ancestors + """InspectionReqSourceSchema.""" + + class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods + """Meta.""" + + unknown = EXCLUDE + model = InspectionReqSourceDetail + include_fk = True + + +documents = fields.List(fields.Nested(InspectionReqDetailDocSchema)) + + +class InspectionRequirementSchema(AutoSchemaBase): # pylint: disable=too-many-ancestors + """InspectionRequirementSchema.""" + + class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods + """Meta.""" + + unknown = EXCLUDE + model = InspectionRequirement + include_fk = True + + requirement_source_details = fields.List( + fields.Nested(InspectionReqSourceDetailSchema) + ) diff --git a/compliance-api/src/compliance_api/services/__init__.py b/compliance-api/src/compliance_api/services/__init__.py index 1c78f26..d48be22 100644 --- a/compliance-api/src/compliance_api/services/__init__.py +++ b/compliance-api/src/compliance_api/services/__init__.py @@ -19,6 +19,7 @@ from .continuation_report import ContinuationReportService from .enforcement_action import EnforcementActionService from .inspection import InspectionService +from .inspection_requirement import InspectionRequirementService from .position import PositionService from .project import ProjectService from .project_status import ProjectStatusService diff --git a/compliance-api/src/compliance_api/services/inspection_requirement.py b/compliance-api/src/compliance_api/services/inspection_requirement.py new file mode 100644 index 0000000..d55f6ed --- /dev/null +++ b/compliance-api/src/compliance_api/services/inspection_requirement.py @@ -0,0 +1,74 @@ +"""InspectionRequirementService.""" + +from compliance_api.models import InspectionReqDetailDocument as InspectionReqDetailDocumentModel +from compliance_api.models import InspectionReqSourceDetail as InspectionReqSourceDetailModel +from compliance_api.models import InspectionRequirement as InspectionRequirementModel +from compliance_api.models.db import session_scope + + +class InspectionRequirementService: + """InspectionRequirementService.""" + + @classmethod + def create(cls, inspection_id, requirement_data): + """Create inspection requirement.""" + requirement_obj = _create_requirement_obj(inspection_id, requirement_data) + with session_scope() as session: + created_requirement = InspectionRequirementModel.create_requirement( + requirement_obj, session + ) + for source_detail_data in requirement_data["requirement_source_details"]: + source_detail_obj = _create_requirement_source_detail_obj( + created_requirement.id, source_detail_data + ) + created_source_detail = ( + InspectionReqSourceDetailModel.create_source_detail( + source_detail_obj, session + ) + ) + for doc_detail_data in source_detail_data["documents"]: + doc_detail_obj = _create_requirement_source_doc_obj( + created_source_detail.id, doc_detail_data + ) + InspectionReqDetailDocumentModel.create_doc_detail(doc_detail_obj) + return created_requirement + + +def _create_requirement_obj(inspection_id, requirement_data): + """Create inspection requirement object.""" + return { + "inspection_id": inspection_id, + "summary": requirement_data.get("summary"), + "topic_id": requirement_data.get("topic_id"), + "sort_order": requirement_data.get("sort_order"), + "enforcement_action_id": requirement_data.get("enforcement_action_id", None), + "compliance_finding_id": requirement_data.get("compliance_finding_id", None), + "findings": requirement_data.get("findings", None), + } + + +def _create_requirement_source_detail_obj(requirement_id, requirement_source_data): + """Create requirement source details object.""" + return { + "requirement_id": requirement_id, + "requirement_source_id": requirement_source_data.get("requirement_source_id"), + "section_number": requirement_source_data.get("section_number", None), + "condition_number": requirement_source_data.get("condition_number", None), + "amendment_number": requirement_source_data.get("amendment_number", None), + "title": requirement_source_data.get("title", None), + "description": requirement_source_data.get("description", None), + } + + +def _create_requirement_source_doc_obj( + requirement_source_detail_id, requirement_source_doc_data +): + """Create requirement source doc details object.""" + return { + "req_detail_id": requirement_source_detail_id, + "document_type_id": requirement_source_doc_data.get("document_type_id"), + "document_title": requirement_source_doc_data.get("document_title"), + "section_number": requirement_source_doc_data.get("section_number", None), + "section_title": requirement_source_doc_data.get("section_title", None), + "description": requirement_source_doc_data.get("description", None), + }