From b34b09ee5f04e30a715483646fcb90aa53297c15 Mon Sep 17 00:00:00 2001 From: dinesh Date: Fri, 3 Jan 2025 11:19:22 -0800 Subject: [PATCH 1/2] InspectionRequirement - Update --- .../inspection/inspection_requirement.py | 12 ++--- .../resources/inspection_requirement.py | 51 +++++++++++++++++-- .../src/compliance_api/schemas/__init__.py | 3 +- .../schemas/inspection_requirement.py | 32 +++++++++++- .../services/inspection_requirement.py | 11 ++++ 5 files changed, 98 insertions(+), 11 deletions(-) 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 37ee16e..d410246 100644 --- a/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py +++ b/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py @@ -62,7 +62,7 @@ class InspectionRequirement(BaseModelVersioned): requirement_source_details = relationship( "InspectionReqSourceDetail", back_populates="inspection_requirement", - lazy="select" + lazy="select", ) @classmethod @@ -79,8 +79,8 @@ def create_requirement(cls, requirement_obj, session=None): @classmethod def get_by_inspection_id(cls, inspection_id): """Get requirements by inspection id.""" - return db.session.query(InspectionRequirement).filter( - InspectionRequirement.inspection_id == inspection_id, - InspectionRequirement.is_deleted.is_(False), - InspectionRequirement.is_active.is_(True) - ).all() + return ( + db.session.query(InspectionRequirement) + .filter_by(inspection_id == inspection_id, is_deleted=False, is_active=True) + .all() + ) diff --git a/compliance-api/src/compliance_api/resources/inspection_requirement.py b/compliance-api/src/compliance_api/resources/inspection_requirement.py index 057f3c5..ebf5a1f 100644 --- a/compliance-api/src/compliance_api/resources/inspection_requirement.py +++ b/compliance-api/src/compliance_api/resources/inspection_requirement.py @@ -5,7 +5,8 @@ from flask_restx import Namespace, Resource from compliance_api.auth import auth -from compliance_api.schemas.inspection_requirement import InspectionRequirementCreateSchema, InspectionRequirementSchema +from compliance_api.schemas.inspection_requirement import ( + InspectionRequirementCreateSchema, InspectionRequirementSchema, InspectionRequirementUpdateSchema) from compliance_api.services import InspectionRequirementService from compliance_api.utils.util import cors_preflight @@ -21,6 +22,10 @@ API, InspectionRequirementSchema(), "InspectionRequirementList" ) +inspection_requirement_update_model = ApiHelper.convert_ma_schema_to_restx_model( + API, InspectionRequirementUpdateSchema(), "InspectionRequirementUpdate" +) + @cors_preflight("GET, OPTIONS, POST") @API.route("", methods=["POST", "GET", "OPTIONS"]) @@ -28,9 +33,13 @@ class InspectionRequirements(Resource): """InspectionRequirements.""" @staticmethod - @ApiHelper.swagger_decorators(API, endpoint_description="Get all requirements by inspection") + @ApiHelper.swagger_decorators( + API, endpoint_description="Get all requirements by inspection" + ) @auth.require - @API.response(code=200, description="Success", model=[inspection_requirement_list_model]) + @API.response( + code=200, description="Success", model=[inspection_requirement_list_model] + ) def get(inspection_id): """Get requirements by inspection id.""" requirements = InspectionRequirementService.get_all(inspection_id) @@ -56,3 +65,39 @@ def post(inspection_id): InspectionRequirementSchema().dump(created_requirement), HTTPStatus.CREATED, ) + + +@cors_preflight("GET, PATCH, DELETE, OPTIONS") +@API.route("/", methods=["GET", "PATCH", "OPTIONS", "DELETE"]) +class InspectionRequirement(Resource): + """InspectionRequirement resource.""" + + @staticmethod + @API.response( + code=200, description="Success", model=[inspection_requirement_list_model] + ) + @ApiHelper.swagger_decorators( + API, endpoint_description="Fetch inspection requirement by id" + ) + @auth.require + def get(requirement_id): + """Fetch all inspection requirement.""" + requirement = InspectionRequirementService.get_by_id(requirement_id) + return InspectionRequirementSchema().dump(requirement), HTTPStatus.OK + + @staticmethod + @API.response( + code=200, description="Sucess", model=[inspection_requirement_list_model] + ) + @API.expect(inspection_requirement_update_model) + @ApiHelper.swagger_decorators( + API, endpoint_description="Update inspection requirement" + ) + @auth.require + def patch(inspection_id, requirement_id): + """Update inspection inspection requirement.""" + requirement_data = InspectionRequirementUpdateSchema().load(API.payload) + updated_requirement = InspectionRequirementService.update( + inspection_id, requirement_id, requirement_data + ) + return InspectionRequirementSchema().dump(updated_requirement), HTTPStatus.OK diff --git a/compliance-api/src/compliance_api/schemas/__init__.py b/compliance-api/src/compliance_api/schemas/__init__.py index 1bc4a84..232b8b6 100644 --- a/compliance-api/src/compliance_api/schemas/__init__.py +++ b/compliance-api/src/compliance_api/schemas/__init__.py @@ -26,7 +26,8 @@ from .inspection import ( InspectionAttendanceSchema, InspectionCreateSchema, InspectionOfficerSchema, InspectionSchema, InspectionStatusSchema, InspectionUpdateSchema) -from .inspection_requirement import InspectionRequirementCreateSchema, InspectionRequirementSchema +from .inspection_requirement import ( + InspectionRequirementCreateSchema, InspectionRequirementSchema, InspectionRequirementUpdateSchema) 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 index d7c71ef..bfa7b77 100644 --- a/compliance-api/src/compliance_api/schemas/inspection_requirement.py +++ b/compliance-api/src/compliance_api/schemas/inspection_requirement.py @@ -37,6 +37,14 @@ class InspectionReqDetailDocCreateSchema(BaseSchema): ) +class InspectionReqDetailDocUpdateSchema(InspectionReqDetailDocCreateSchema): + id = fields.Int( + metadata={ + "description": "The unique identifier of the requirement detail document" + } + ) + + class InspectionReqSourceDetailCreateSchema(BaseSchema): """InspectionReqSourceDetailSchema.""" @@ -70,7 +78,18 @@ class InspectionReqSourceDetailCreateSchema(BaseSchema): documents = fields.List(fields.Nested(InspectionReqDetailDocCreateSchema)) -class InspectionRequirementCreateSchema(BaseSchema): +class InspectionReqSourceDetailUpdateSchema(InspectionReqSourceDetailCreateSchema): + """InspectionRequirementUpdateSchema.""" + + id = fields.Int( + metadata={ + "description": "The unique identifier of the requirement source detail." + } + ) + documents = fields.List(fields.Nested(InspectionReqDetailDocUpdateSchema)) + + +class InspectionRequirementCreateSchema(InspectionReqSourceDetailCreateSchema): """InspectionRequirementCreateSchema.""" summary = fields.Str( @@ -100,6 +119,17 @@ class InspectionRequirementCreateSchema(BaseSchema): ) +class InspectionRequirementUpdateSchema(BaseSchema): + """InspectionRequirementUpdateSchema.""" + + id = fields.Int( + metadata={"description": "The unique identifier of the requirement"} + ) + requirement_source_details = fields.List( + fields.Nested(InspectionReqSourceDetailUpdateSchema) + ) + + class InspectionReqDetailDocSchema( AutoSchemaBase ): # pylint: disable=too-many-ancestors diff --git a/compliance-api/src/compliance_api/services/inspection_requirement.py b/compliance-api/src/compliance_api/services/inspection_requirement.py index 6489adb..f4aaa6b 100644 --- a/compliance-api/src/compliance_api/services/inspection_requirement.py +++ b/compliance-api/src/compliance_api/services/inspection_requirement.py @@ -14,6 +14,11 @@ def get_all(cls, inspection_id): """Get all requirements by inspection id.""" return InspectionRequirementModel.get_by_inspection_id(inspection_id) + @classmethod + def get_by_id(cls, requirement_id): + """Get inspection requirement by id.""" + return InspectionReqDetailDocumentModel.find_by_id(requirement_id) + @classmethod def create(cls, inspection_id, requirement_data): """Create inspection requirement.""" @@ -39,6 +44,12 @@ def create(cls, inspection_id, requirement_data): return created_requirement + @classmethod + def update(cls, inspection_id, requirement_id, requirement_data): + """Update inspection requirement.""" + requirement_obj = _create_requirement_obj(inspection_id, requirement_data) + with session_scope() as session: + updated_requirement = InspectionRequirementModel def _create_requirement_obj(inspection_id, requirement_data): """Create inspection requirement object.""" return { From 2cfaf5922978cbca3866f4c2ba95ca514ce6c68b Mon Sep 17 00:00:00 2001 From: dinesh Date: Fri, 3 Jan 2025 14:49:05 -0800 Subject: [PATCH 2/2] InspectionRequirement - GET by Id, Update --- .../src/compliance_api/models/__init__.py | 2 +- .../inspection/inspection_req_detail_doc.py | 29 ++++- .../inspection_req_source_detail.py | 38 ++++++- .../inspection/inspection_requirement.py | 16 ++- .../schemas/inspection_requirement.py | 61 +++++++++- .../services/inspection_requirement.py | 107 +++++++++++++++--- 6 files changed, 228 insertions(+), 25 deletions(-) diff --git a/compliance-api/src/compliance_api/models/__init__.py b/compliance-api/src/compliance_api/models/__init__.py index 4ac90cd..1591368 100644 --- a/compliance-api/src/compliance_api/models/__init__.py +++ b/compliance-api/src/compliance_api/models/__init__.py @@ -33,7 +33,7 @@ from .position import Position from .project import Project from .req_source_document_map import RequirementSourceDocumentMap -from .requirement_source import RequirementSource +from .requirement_source import RequirementSource, RequirementSourceEnum from .staff_user import StaffUser from .topic import Topic from .unapproved_project import UnapprovedProject 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 6df4367..d16bab5 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 @@ -3,7 +3,7 @@ from sqlalchemy import Column, ForeignKey, Integer, String from sqlalchemy.orm import relationship -from ..base_model import BaseModelVersioned +from ..base_model import BaseModelVersioned, db class InspectionReqDetailDocument(BaseModelVersioned): @@ -52,7 +52,7 @@ class InspectionReqDetailDocument(BaseModelVersioned): "InspectionReqSourceDetail", back_populates="documents", lazy="select", - uselist=False + uselist=False, ) document_type = relationship( "DocumentType", foreign_keys=[document_type_id], lazy="select" @@ -68,3 +68,28 @@ def create_doc_detail(cls, doc_detail_obj, session=None): else: doc_detail.save() return doc_detail + + @classmethod + def update_doc_detail(cls, doc_detail_id, doc_detail_data, session=None): + """Update requirement doc detail.""" + query = cls.query.filter_by(id=doc_detail_id) + doc_detail: InspectionReqDetailDocument = query.first() + if not doc_detail or doc_detail.is_deleted: + return None + query.update(doc_detail_data) + if session: + session.flush() + else: + db.session.commit() + return doc_detail + + @classmethod + def delete_req_doc_details_by_ids(cls, req_doc_detail_ids, session=None): + """Delete the requirement doc details by req_doc_detail_ids.""" + cls.query.filter(InspectionReqDetailDocument.id.in_(req_doc_detail_ids)).update( + {cls.is_deleted: True, cls.is_active: False} + ) + if session: + session.flush() + else: + db.session.commit() 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 189d122..5875819 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 @@ -3,7 +3,7 @@ from sqlalchemy import Column, ForeignKey, Integer, String from sqlalchemy.orm import relationship -from ..base_model import BaseModelVersioned +from ..base_model import BaseModelVersioned, db class InspectionReqSourceDetail(BaseModelVersioned): @@ -60,7 +60,7 @@ class InspectionReqSourceDetail(BaseModelVersioned): "InspectionRequirement", back_populates="requirement_source_details", lazy="select", - uselist=False + uselist=False, ) requirement_source = relationship( "RequirementSource", foreign_keys=[requirement_source_id], lazy="joined" @@ -81,3 +81,37 @@ def create_source_detail(cls, source_detail_obj, session=None): else: source_detail.save() return source_detail + + @classmethod + def update_requirement_source_detail( + cls, req_detail_id, source_detail_data, session=None + ): + """Update requirement detail.""" + query = cls.query.filter_by(id=req_detail_id) + source_detail: InspectionReqSourceDetail = query.first() + if not source_detail or source_detail.is_deleted: + return None + query.update(source_detail_data) + if session: + session.flush() + else: + db.session.commit() + return source_detail + + @classmethod + def get_all_by_requirement_id(cls, requirement_id): + """Get all requirement detail entries by requirement_id.""" + return cls.query.filter_by( + requirement_id=requirement_id, is_active=True, is_deleted=False + ).all() + + @classmethod + def delete_req_details_by_ids(cls, req_detail_ids, session=None): + """Delete the requirement details by req_detail_ids.""" + cls.query.filter(InspectionReqSourceDetail.id.in_(req_detail_ids)).update( + {cls.is_deleted: True, cls.is_active: False} + ) + if session: + session.flush() + else: + db.session.commit() 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 d410246..428712d 100644 --- a/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py +++ b/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py @@ -81,6 +81,20 @@ def get_by_inspection_id(cls, inspection_id): """Get requirements by inspection id.""" return ( db.session.query(InspectionRequirement) - .filter_by(inspection_id == inspection_id, is_deleted=False, is_active=True) + .filter_by(inspection_id=inspection_id, is_deleted=False, is_active=True) .all() ) + + @classmethod + def update_requirement(cls, requirement_id, requirement_data, session=None): + """Update inspection requirement.""" + query = cls.query.filter_by(id=requirement_id) + requirement: InspectionRequirement = query.first() + if not requirement or requirement.is_deleted: + return None + query.update(requirement_data) + if session: + session.flush() + else: + db.session.commit() + return requirement diff --git a/compliance-api/src/compliance_api/schemas/inspection_requirement.py b/compliance-api/src/compliance_api/schemas/inspection_requirement.py index bfa7b77..a03020f 100644 --- a/compliance-api/src/compliance_api/schemas/inspection_requirement.py +++ b/compliance-api/src/compliance_api/schemas/inspection_requirement.py @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. """Inspection requirement Schema Schema.""" -from marshmallow import EXCLUDE, fields +from marshmallow import EXCLUDE, ValidationError, fields, validates_schema from compliance_api.models import InspectionReqDetailDocument, InspectionReqSourceDetail, InspectionRequirement +from compliance_api.models.requirement_source import RequirementSourceEnum from .base_schema import AutoSchemaBase, BaseSchema @@ -38,6 +39,8 @@ class InspectionReqDetailDocCreateSchema(BaseSchema): class InspectionReqDetailDocUpdateSchema(InspectionReqDetailDocCreateSchema): + """InspectionReqDetailDocUpdateSchema.""" + id = fields.Int( metadata={ "description": "The unique identifier of the requirement detail document" @@ -77,6 +80,58 @@ class InspectionReqSourceDetailCreateSchema(BaseSchema): ) documents = fields.List(fields.Nested(InspectionReqDetailDocCreateSchema)) + @validates_schema + def validate_section_number( + self, data, **kwargs + ): # pylint: disable=no-self-use, unused-argument + """Ensure the correct requirement is selected for the section number.""" + section_number = data.get("section_number", []) + requirement_source_id = data.get("requirement_source_id", None) + if section_number and RequirementSourceEnum(requirement_source_id) not in [ + RequirementSourceEnum.ACT_2002, + RequirementSourceEnum.ACT_2018, + RequirementSourceEnum.COMPLIANCE_AGREEMENT, + RequirementSourceEnum.CERTIFIED_PROJECT_DESCRIPTION, + RequirementSourceEnum.NOT_EA_ACT, + ]: + raise ValidationError( + "Invalid requirement source for the given section number", + field_name="section_number", + ) + + @validates_schema + def validate_amendment_number( + self, data, **kwargs + ): # pylint: disable=no-self-use, unused-argument + """Ensure the correct requirement is selected for the amendment number.""" + amendment_number = data.get("amendment_number", []) + requirement_source_id = data.get("requirement_source_id", None) + if ( + amendment_number + and RequirementSourceEnum(requirement_source_id) + != RequirementSourceEnum.EAC_AMENDMENT + ): + raise ValidationError( + "Invalid requirement source for the given amendment number", + field_name="amendment_number", + ) + + @validates_schema + def validate_condition_number( + self, data, **kwargs + ): # pylint: disable=no-self-use, unused-argument + """Ensure the correct requirement is selected for the condition number.""" + condition_number = data.get("condition_number", []) + requirement_source_id = data.get("requirement_source_id", None) + if condition_number and RequirementSourceEnum(requirement_source_id) not in [ + RequirementSourceEnum.SCHEDULE_B, + RequirementSourceEnum.EAC_CERTIFICATE, + ]: + raise ValidationError( + "Invalid requirement source for the given condition number", + field_name="condition_number", + ) + class InspectionReqSourceDetailUpdateSchema(InspectionReqSourceDetailCreateSchema): """InspectionRequirementUpdateSchema.""" @@ -89,7 +144,7 @@ class InspectionReqSourceDetailUpdateSchema(InspectionReqSourceDetailCreateSchem documents = fields.List(fields.Nested(InspectionReqDetailDocUpdateSchema)) -class InspectionRequirementCreateSchema(InspectionReqSourceDetailCreateSchema): +class InspectionRequirementCreateSchema(BaseSchema): """InspectionRequirementCreateSchema.""" summary = fields.Str( @@ -119,7 +174,7 @@ class InspectionRequirementCreateSchema(InspectionReqSourceDetailCreateSchema): ) -class InspectionRequirementUpdateSchema(BaseSchema): +class InspectionRequirementUpdateSchema(InspectionRequirementCreateSchema): """InspectionRequirementUpdateSchema.""" id = fields.Int( diff --git a/compliance-api/src/compliance_api/services/inspection_requirement.py b/compliance-api/src/compliance_api/services/inspection_requirement.py index f4aaa6b..954feba 100644 --- a/compliance-api/src/compliance_api/services/inspection_requirement.py +++ b/compliance-api/src/compliance_api/services/inspection_requirement.py @@ -27,29 +27,104 @@ def create(cls, inspection_id, requirement_data): 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) + _create_update_source_details_nd_docs( + created_requirement.id, requirement_data, session + ) return created_requirement - @classmethod def update(cls, inspection_id, requirement_id, requirement_data): """Update inspection requirement.""" requirement_obj = _create_requirement_obj(inspection_id, requirement_data) with session_scope() as session: - updated_requirement = InspectionRequirementModel + updated_requirement = InspectionRequirementModel.update_requirement( + requirement_id, requirement_obj, session + ) + _handle_deletion_req_detail_nd_doc( + requirement_id, requirement_data, session + ) + _create_update_source_details_nd_docs( + requirement_id, requirement_data, session + ) + return updated_requirement + + +def _create_update_source_details_nd_docs( + requirement_id, requirement_data, session=None +): + """ + Persist the source details and related document details. + + This function check if the id is present in the data. If it is present, no need to + create object again. + """ + for source_detail_data in requirement_data.get("requirement_source_details", []): + req_detail_id = source_detail_data.get("id", None) + source_detail_obj = _create_requirement_source_detail_obj( + requirement_id, source_detail_data + ) + if not req_detail_id: + created_source_detail = InspectionReqSourceDetailModel.create_source_detail( + source_detail_obj, session + ) + req_detail_id = created_source_detail.id + else: + source_detail_obj = {**source_detail_obj, "id": req_detail_id} + InspectionReqSourceDetailModel.update_requirement_source_detail( + req_detail_id, source_detail_obj, session + ) + for doc_detail_data in source_detail_data.get("documents", []): + doc_detail_id = doc_detail_data.get("id", None) + doc_detail_obj = _create_requirement_source_doc_obj( + req_detail_id, doc_detail_data + ) + if not doc_detail_id: + InspectionReqDetailDocumentModel.create_doc_detail( + doc_detail_obj, session + ) + else: + doc_detail_obj = {**doc_detail_obj, "id": doc_detail_id} + InspectionReqDetailDocumentModel.update_doc_detail( + doc_detail_id, doc_detail_obj, session + ) + + +def _handle_deletion_req_detail_nd_doc( + requirement_id, + requirement_data, + session=None, +): + """Handle the deletion of requirement details and related document entry.""" + existing_details = InspectionReqSourceDetailModel.get_all_by_requirement_id( + requirement_id + ) + existing_detail_ids = {detail.id for detail in existing_details} + incoming_details_ids = { + detail.get("id", None) + for detail in requirement_data.get("requirement_source_details") + if detail.get("id", None) is not None + } + incoming_doc_detail_ids = set( + doc.get("id", None) + for detail in requirement_data.get("requirement_source_details") + for doc in detail.get("documents", []) + if doc.get("id", None) is not None + ) + existing_doc_detail_ids = { + doc.id for detail in existing_details for doc in detail.documents + } + details_to_be_deleted = existing_detail_ids.difference(incoming_details_ids) + doc_details_to_be_deleted = existing_doc_detail_ids.difference( + incoming_doc_detail_ids + ) + InspectionReqDetailDocumentModel.delete_req_doc_details_by_ids( + details_to_be_deleted, session + ) + InspectionReqDetailDocumentModel.delete_req_doc_details_by_ids( + doc_details_to_be_deleted, session + ) + + def _create_requirement_obj(inspection_id, requirement_data): """Create inspection requirement object.""" return {