Skip to content

Commit 03ac607

Browse files
authored
Merge pull request #245 from dinesh-aot/COMP-359
inspection record approval
2 parents f4ecd65 + 8043cdb commit 03ac607

16 files changed

+743
-123
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""change_info_inspection_records
2+
3+
Revision ID: 80afcd35ad35
4+
Revises: e85b901b6ba6
5+
Create Date: 2025-03-19 10:50:46.569040
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '80afcd35ad35'
14+
down_revision = 'e85b901b6ba6'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
with op.batch_alter_table('inspection_record_approvals', schema=None) as batch_op:
22+
batch_op.add_column(sa.Column('approved_date', sa.DateTime(timezone=True), nullable=True, comment='The approved date'))
23+
24+
with op.batch_alter_table('inspection_record_approvals_version', schema=None) as batch_op:
25+
batch_op.add_column(sa.Column('approved_date', sa.DateTime(timezone=True), autoincrement=False, nullable=True, comment='The approved date'))
26+
batch_op.add_column(sa.Column('approved_date_mod', sa.Boolean(), server_default=sa.text('false'), nullable=False))
27+
28+
with op.batch_alter_table('inspection_records', schema=None) as batch_op:
29+
batch_op.add_column(sa.Column('field_change_info', sa.JSON(), nullable=True, comment='To indicate if selected fields have changed or not'))
30+
31+
with op.batch_alter_table('inspection_records_version', schema=None) as batch_op:
32+
batch_op.add_column(sa.Column('field_change_info', sa.JSON(), autoincrement=False, nullable=True, comment='To indicate if selected fields have changed or not'))
33+
batch_op.add_column(sa.Column('field_change_info_mod', sa.Boolean(), server_default=sa.text('false'), nullable=False))
34+
35+
# ### end Alembic commands ###
36+
37+
38+
def downgrade():
39+
# ### commands auto generated by Alembic - please adjust! ###
40+
with op.batch_alter_table('inspection_records_version', schema=None) as batch_op:
41+
batch_op.drop_column('field_change_info_mod')
42+
batch_op.drop_column('field_change_info')
43+
44+
with op.batch_alter_table('inspection_records', schema=None) as batch_op:
45+
batch_op.drop_column('field_change_info')
46+
47+
with op.batch_alter_table('inspection_record_approvals_version', schema=None) as batch_op:
48+
batch_op.drop_column('approved_date_mod')
49+
batch_op.drop_column('approved_date')
50+
51+
with op.batch_alter_table('inspection_record_approvals', schema=None) as batch_op:
52+
batch_op.drop_column('approved_date')
53+
54+
# ### end Alembic commands ###

compliance-api/src/compliance_api/models/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
InspectionOtherAttendance, InspectionReqDetailDocument, InspectionReqEnforcementMap, InspectionReqSourceDetail,
3333
InspectionRequirement, InspectionRequirementImage, InspectionRequirementTypeEnum, InspectionStatusEnum,
3434
InspectionType, InspectionTypeOption, IRStatusOption)
35-
from .inspection_record import InspectionRecord
36-
from .inspection_record_approval import InspectionRecordApproval
35+
from .inspection_record import InspectionRecord, IRProgressEnum
36+
from .inspection_record_approval import InspectionRecordApproval, IRApprovalStatusEnum
3737
from .position import Position
3838
from .project import Project
3939
from .req_source_document_map import RequirementSourceDocumentMap

compliance-api/src/compliance_api/models/inspection_record.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from enum import Enum
44

5-
from sqlalchemy import Column, DateTime
5+
from sqlalchemy import JSON, Column, DateTime
66
from sqlalchemy import Enum as SqlEnum
77
from sqlalchemy import ForeignKey, Integer, String
88
from sqlalchemy.orm import relationship
@@ -61,6 +61,11 @@ class InspectionRecord(BaseModelVersioned):
6161
finding_statement = Column(
6262
String, nullable=True, comment="Finding statement from the inspection"
6363
)
64+
field_change_info = Column(
65+
JSON,
66+
nullable=True,
67+
comment="To indicate if selected fields have changed or not",
68+
)
6469
enforcement_summary = Column(
6570
String, nullable=True, comment="Summary of enforcement action"
6671
)

compliance-api/src/compliance_api/models/inspection_record_approval.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ class InspectionRecordApproval(BaseModelVersioned):
6868
comment="State of the inspection record",
6969
default=IRApprovalStatusEnum.DECISION_PENDING,
7070
)
71+
approved_date = Column(
72+
DateTime(timezone=True),
73+
nullable=True,
74+
comment="The approved date",
75+
)
7176
approved_by = relationship(
7277
"StaffUser", foreign_keys=[approved_by_id], lazy="joined"
7378
)
@@ -87,3 +92,40 @@ def create_inspection_record_approval(
8792
session.add(inspection_record_approval)
8893
session.flush()
8994
return inspection_record_approval
95+
96+
@classmethod
97+
def get_approvals_by_ir(cls, inspection_record_id: int):
98+
"""Return all the approval entries by inspection record id."""
99+
return (
100+
cls.query.filter(
101+
cls.inspection_record_id == inspection_record_id,
102+
cls.is_active.is_(True),
103+
cls.is_deleted.is_(False),
104+
)
105+
.order_by(cls.created_date.desc())
106+
.all()
107+
)
108+
109+
@classmethod
110+
def get_latest_approval_by_ir(cls, inspection_record_id: int):
111+
"""Return all the approval entries by inspection record id."""
112+
return (
113+
cls.query.filter(
114+
cls.inspection_record_id == inspection_record_id,
115+
cls.is_active.is_(True),
116+
cls.is_deleted.is_(False),
117+
)
118+
.order_by(cls.created_date.desc())
119+
.first()
120+
)
121+
122+
@classmethod
123+
@with_session
124+
def update_approval(cls, approval_id, approval_update_data, session=None):
125+
"""Update the inspection record approval."""
126+
approval = cls.find_by_id(approval_id)
127+
if not approval:
128+
return None
129+
approval.update(approval_update_data, commit=False)
130+
session.flush()
131+
return approval

compliance-api/src/compliance_api/resources/inspection_record.py

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@
77
from compliance_api.auth import auth
88
from compliance_api.exceptions import ResourceNotFoundError
99
from compliance_api.schemas import (
10-
InspectionRecordCreateSchema, InspectionRecordSchema, ResetInspectionRecordFieldSchema,
10+
CreateInspectionRecordApprovalSchema, InspectionRecordApprovalSchema, InspectionRecordCreateSchema,
11+
InspectionRecordSchema, ResetInspectionRecordFieldSchema, UpdateInspectionRecordApprovalSchema,
1112
UpdateInspectionRecordSchema)
12-
from compliance_api.services import InspectionRecordService
13+
from compliance_api.services import InspectionRecordApprovalService, InspectionRecordService
1314
from compliance_api.utils.enum import PermissionEnum
1415
from compliance_api.utils.util import cors_preflight
1516

1617
from .apihelper import Api as ApiHelper
1718

1819

19-
API = Namespace("inspection-records", description="Endpoints for Inspection Record")
20+
API = Namespace("inspection-records",
21+
description="Endpoints for Inspection Record")
2022

2123
ir_create_request_model = ApiHelper.convert_ma_schema_to_restx_model(
2224
API, InspectionRecordCreateSchema(), "IRCreateRequest"
@@ -27,12 +29,21 @@
2729
ir_update_request_model = ApiHelper.convert_ma_schema_to_restx_model(
2830
API, UpdateInspectionRecordSchema(), "UpdateInspection"
2931
)
32+
ir_approval_create_request = ApiHelper.convert_ma_schema_to_restx_model(
33+
API, CreateInspectionRecordApprovalSchema(), "IRApproval"
34+
)
35+
ir_approval_schema = ApiHelper.convert_ma_schema_to_restx_model(
36+
API, InspectionRecordApprovalSchema(), "IRApproval"
37+
)
38+
ir_approval_update_request = ApiHelper.convert_ma_schema_to_restx_model(
39+
API, UpdateInspectionRecordApprovalSchema(), "IRApprovalUpdate"
40+
)
3041
ir_reset_field_model = ApiHelper.convert_ma_schema_to_restx_model(
3142
API, ResetInspectionRecordFieldSchema(), "ResetInspectionField"
3243
)
3344

3445

35-
@cors_preflight("GET, OPTIONS, POST")
46+
@cors_preflight("GET, OPTIONS, POST, PATCH")
3647
@API.route("", methods=["POST", "GET", "OPTIONS"])
3748
class InspectionRecords(Resource):
3849
"""Resource for managing inspection records."""
@@ -61,7 +72,8 @@ def get(inspection_id):
6172
def post(inspection_id):
6273
"""Create a agency."""
6374
ir_create_request = InspectionRecordCreateSchema().load(API.payload)
64-
created_ir = InspectionRecordService.create(ir_create_request, inspection_id)
75+
created_ir = InspectionRecordService.create(
76+
ir_create_request, inspection_id)
6577
return InspectionRecordSchema().dump(created_ir), HTTPStatus.CREATED
6678

6779

@@ -79,7 +91,8 @@ class InspectionRecord(Resource):
7991
@auth.require
8092
def get(inspection_record_id):
8193
"""Fetch inspection record by id."""
82-
inspection_record = InspectionRecordService.get_by_id(inspection_record_id)
94+
inspection_record = InspectionRecordService.get_by_id(
95+
inspection_record_id)
8396
if not inspection_record:
8497
raise ResourceNotFoundError(
8598
f"No inspection found for the given ID : {inspection_record_id}"
@@ -99,12 +112,95 @@ def patch(inspection_id, inspection_record_id):
99112
updated_ir = InspectionRecordService.update(
100113
inspection_id, inspection_record_id, ir_update_data
101114
)
102-
if not updated_ir:
103-
raise ResourceNotFoundError("Inspection record not found")
104115
return InspectionRecordSchema().dump(updated_ir), HTTPStatus.OK
105116

106117

107118
@cors_preflight("PATCH, OPTIONS")
119+
@API.route("/<int:inspection_record_id>/switch-to-final", methods=["PATCH", "OPTIONS"])
120+
class InspectionRecordFinal(Resource):
121+
"""Resource to handle InspectionRecord."""
122+
123+
@staticmethod
124+
@API.response(code=200, description="Sucess", model=ir_list_model)
125+
@API.expect(ir_update_request_model)
126+
@ApiHelper.swagger_decorators(API, endpoint_description="Switch to FINAL IR")
127+
@API.response(404, "Not Found")
128+
@auth.require
129+
def patch(inspection_id, inspection_record_id):
130+
"""Swith IR to FINAL."""
131+
final_ir = InspectionRecordService.switch_to_final(
132+
inspection_id, inspection_record_id
133+
)
134+
return InspectionRecordSchema().dump(final_ir), HTTPStatus.OK
135+
136+
137+
@cors_preflight("GET, OPTIONS, POST, PATCH")
138+
@API.route("/<int:inspection_record_id>/approvals", methods=["POST", "GET", "OPTIONS"])
139+
class InspectionRecordApprovals(Resource):
140+
"""Resource for managing inspection records."""
141+
142+
@staticmethod
143+
@API.response(code=200, description="Success", model=[ir_approval_schema])
144+
@ApiHelper.swagger_decorators(
145+
API, endpoint_description="Fetch all inspection record approvals"
146+
)
147+
@auth.require
148+
def get(inspection_id, inspection_record_id): # pylint: disable=no-self-use, unused-argument
149+
"""Fetch all inspection record approvals."""
150+
approvals = InspectionRecordApprovalService.get_all_approvals(
151+
inspection_record_id
152+
)
153+
approval_schema = InspectionRecordApprovalSchema(many=True)
154+
return approval_schema.dump(approvals), HTTPStatus.OK
155+
156+
@staticmethod
157+
@auth.require
158+
@ApiHelper.swagger_decorators(
159+
API, endpoint_description="Create an inspection record approval"
160+
)
161+
@API.expect(ir_approval_create_request)
162+
@API.response(
163+
code=201, model=ir_approval_schema, description="IRApprovalRequestCreated"
164+
)
165+
@API.response(400, "Bad Request")
166+
def post(inspection_id, inspection_record_id):
167+
"""Create a agency."""
168+
ir_approval_request = CreateInspectionRecordApprovalSchema().load(API.payload)
169+
created_aproval = InspectionRecordApprovalService.create_approval(
170+
ir_approval_request, inspection_id, inspection_record_id
171+
)
172+
return (
173+
InspectionRecordApprovalSchema().dump(created_aproval),
174+
HTTPStatus.CREATED,
175+
)
176+
177+
178+
@cors_preflight("OPTIONS, PATCH, GET")
179+
@API.route(
180+
"/<int:inspection_record_id>/approvals/<int:approval_id>",
181+
methods=["PATCH", "GET", "OPTIONS"],
182+
)
183+
class InspectionRecordApproval(Resource):
184+
"""Resource for managing inspection record approval."""
185+
186+
@staticmethod
187+
@API.response(code=200, description="Sucess", model=ir_approval_schema)
188+
@API.expect(ir_approval_update_request)
189+
@ApiHelper.swagger_decorators(
190+
API, endpoint_description="Update inspection record approval"
191+
)
192+
@API.response(404, "Not Found")
193+
@API.response(400, "Bad Request")
194+
@auth.require
195+
def patch(inspection_id, inspection_record_id, approval_id):
196+
"""Update inspection record approval."""
197+
approval_update_data = UpdateInspectionRecordApprovalSchema().load(API.payload)
198+
updated_approval = InspectionRecordApprovalService.update_approval(
199+
inspection_id, inspection_record_id, approval_id, approval_update_data
200+
)
201+
return InspectionRecordApprovalSchema().dump(updated_approval), HTTPStatus.OK
202+
203+
108204
@API.route("/<int:inspection_record_id>/reset", methods=["PATCH", "OPTIONS"])
109205
class InspectionRecordReset(Resource):
110206
"""Resource for resetting inspection record fields."""

compliance-api/src/compliance_api/schemas/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
from .inspection import (
2828
InspectionAttendanceSchema, InspectionCreateSchema, InspectionOfficerSchema, InspectionSchema,
2929
InspectionStatusSchema, InspectionUpdateSchema)
30+
from .inspection_approval import (
31+
CreateInspectionRecordApprovalSchema, InspectionRecordApprovalSchema, UpdateInspectionRecordApprovalSchema)
3032
from .inspection_record import (
3133
InspectionRecordCreateSchema, InspectionRecordSchema, ResetInspectionRecordFieldSchema,
3234
UpdateInspectionRecordSchema)

compliance-api/src/compliance_api/schemas/inspection.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -390,15 +390,15 @@ def extract_status_value(
390390
data["status"] = status_enum.value
391391
return data
392392

393-
@validates_schema
394-
def validate_status(
395-
self, data, **kwargs
396-
): # pylint: disable=no-self-use, unused-argument
397-
"""Ensure only Closed and Canceled status are passed."""
398-
# Retrieve the context to access other fields
399-
status = data.get("status")
400-
if status not in [InspectionStatusEnum.CANCELED, InspectionStatusEnum.CLOSED]:
401-
raise ValidationError(
402-
"Invalid status value passed",
403-
field_name="status",
404-
)
393+
# @validates_schema
394+
# def validate_status(
395+
# self, data, **kwargs
396+
# ): # pylint: disable=no-self-use, unused-argument
397+
# """Ensure only Closed and Canceled status are passed."""
398+
# # Retrieve the context to access other fields
399+
# status = data.get("status")
400+
# if status not in [InspectionStatusEnum.CANCELED, InspectionStatusEnum.CLOSED]:
401+
# raise ValidationError(
402+
# "Invalid status value passed",
403+
# field_name="status",
404+
# )

0 commit comments

Comments
 (0)