Skip to content

Commit b6e5a4c

Browse files
authored
COMP-775 prevent editing of requirements/details before IR reopened (#675)
1 parent 44bc84d commit b6e5a4c

File tree

9 files changed

+92
-36
lines changed

9 files changed

+92
-36
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from sqlalchemy import ForeignKey, Integer, String
88
from sqlalchemy.orm import relationship
99

10+
from compliance_api.models.inspection.inspection_enum import InspectionStatusEnum
11+
1012
from .base_model import BaseModelVersioned
1113
from .utils import with_session
1214

@@ -161,3 +163,8 @@ def get_by_inspection_id(cls, inspection_id):
161163
return cls.query.filter_by(
162164
inspection_id=inspection_id, is_deleted=False, is_active=True
163165
).first()
166+
167+
@property
168+
def is_open_for_editing(self):
169+
"""Check if the associated inspection has been reopened."""
170+
return self.inspection.inspection_status == InspectionStatusEnum.OPEN and self.date_issued is None

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,17 @@ class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods
4242
)
4343
record_prepared_by_position = fields.Nested(KeyValueSchema, dump_only=True)
4444

45-
@post_dump
46-
def post_dump_actions(
47-
self, data, many, **kwargs
48-
): # pylint: disable=no-self-use, unused-argument
49-
"""Extract the value of the inspection record status enum."""
45+
@post_dump(pass_original=True)
46+
def post_dump_actions(self, data, original, many, **kwargs): # pylint: disable=no-self-use, unused-argument
47+
"""Post dump actions."""
5048
if "ir_progress" in data and data["ir_progress"] is not None:
5149
data["ir_progress"] = {
5250
"id": data["ir_progress"].name,
5351
"name": data["ir_progress"].value,
5452
}
5553
else:
5654
data["ir_progress"] = ""
55+
data["is_open_for_editing"] = getattr(original, "is_open_for_editing", False)
5756
return data
5857

5958

compliance-web/cypress/components/_components/_App/_Inspections/_Profile/InspectionRequirements.cy.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference types="cypress" />
22
import { mount } from "cypress/react";
3+
import { useReportStore } from "@/components/App/Inspections/Profile/Reports/reportStore";
34
import InspectionRequirements from "@/components/App/Inspections/Profile/InspectionRequirements";
45
import { Inspection } from "@/models/Inspection";
56
import { InspectionRequirement } from "@/models/InspectionRequirement";
@@ -60,6 +61,11 @@ describe("InspectionRequirements", () => {
6061
},
6162
});
6263

64+
// Set Zustand store state for useReportStore
65+
useReportStore.setState({
66+
inspectionReportsData: { is_open_for_editing: true }
67+
});
68+
6369
// Default mock inspection data
6470
mockInspection = {
6571
id: 1,
@@ -346,6 +352,14 @@ describe("InspectionRequirements", () => {
346352
});
347353

348354
describe("Action Button Functionality", () => {
355+
it("does not show action buttons when is_open_for_editing is false", () => {
356+
useReportStore.setState({
357+
inspectionReportsData: { is_open_for_editing: false },
358+
});
359+
mountComponent();
360+
cy.contains("New Requirement").should("not.exist");
361+
cy.contains("Regulatory Consideration").should("not.exist");
362+
});
349363
beforeEach(() => {
350364
mockInspection.inspection_status = "Open";
351365
queryClient.setQueryData(["inspection-requirement-images", mockInspection.id], mockImages);

compliance-web/cypress/components/_components/_App/_Inspections/_Requirements/RequirementDrawer.cy.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import RequirementDrawer from "@/components/App/Inspections/Profile/Requirements
22
import { baseRequirement, mockInspection } from "./mockData";
33
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
44
import { InspectionRequirement } from "@/models/InspectionRequirement";
5+
import { useReportStore } from "@/components/App/Inspections/Profile/Reports/reportStore";
56

67
describe("RequirementDrawer Component", () => {
78
const queryClient = new QueryClient();
@@ -31,6 +32,19 @@ describe("RequirementDrawer Component", () => {
3132
{ id: "REG", name: "Regulatory Consideration" },
3233
]),
3334
});
35+
// Set Zustand store state for useReportStore
36+
useReportStore.setState({
37+
inspectionReportsData: { is_open_for_editing: true },
38+
});
39+
});
40+
it("does not show action buttons when is_open_for_editing is false", () => {
41+
useReportStore.setState({
42+
inspectionReportsData: { is_open_for_editing: false },
43+
});
44+
mountComponent(baseRequirement);
45+
cy.contains("Delete").should("not.exist");
46+
cy.contains("Edit Requirement #1").should("not.exist");
47+
// If there are other action buttons, add similar checks here
3448
});
3549

3650
it("renders create requirement drawer correctly", () => {
@@ -54,7 +68,7 @@ describe("RequirementDrawer Component", () => {
5468

5569
// Confirm deletion
5670
cy.contains("button", "Delete").click();
57-
71+
5872
});
5973

6074
});

compliance-web/src/components/App/Inspections/Profile/InspectionRequirements.tsx

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { useRequirementStore } from "./Requirements/requirementStore";
2727
import RequirementLoading from "./Requirements/RequirementLoading";
2828
import DynamicHeightBox from "@/components/Shared/DynamicHeightBox";
2929
import useResponsiveDrawerWidth from "@/hooks/useResponsiveDrawerWidth";
30+
import { useReportStore } from "./Reports/reportStore";
3031

3132
interface InspectionRequirementsProps {
3233
inspectionData: Inspection;
@@ -44,6 +45,7 @@ const InspectionRequirements: React.FC<InspectionRequirementsProps> = ({
4445
setRequirementFigures,
4546
setRequirementsList,
4647
} = useRequirementStore();
48+
const { inspectionReportsData } = useReportStore();
4749
const [activeRequirementId, setActiveRequirementId] = React.useState<
4850
number | null
4951
>(null);
@@ -303,30 +305,32 @@ const InspectionRequirements: React.FC<InspectionRequirementsProps> = ({
303305
>
304306
<Box display={"flex"} justifyContent={"space-between"} mt={3} mb={2}>
305307
<Typography variant="h6">Requirements</Typography>
306-
{!isDataLoading && isRequirementsAllowed && (
307-
<Box display={"flex"} gap={2}>
308-
<Button
309-
variant="text"
310-
color="primary"
311-
size="small"
312-
onClick={handleOpenAddRegulatoryConsiderationModal}
313-
startIcon={<AddRounded />}
314-
data-cy="new-regulatory-consideration-button"
315-
disabled={!!regulatoryConsideration}
316-
>
317-
Regulatory Consideration
318-
</Button>
319-
<Button
320-
color="secondary"
321-
size="small"
322-
onClick={handleOpenAddRequirementModal}
323-
startIcon={<AddRounded />}
324-
data-cy="new-requirement-button"
325-
>
326-
New Requirement
327-
</Button>
328-
</Box>
329-
)}
308+
{!isDataLoading &&
309+
isRequirementsAllowed &&
310+
inspectionReportsData?.is_open_for_editing && (
311+
<Box display={"flex"} gap={2}>
312+
<Button
313+
variant="text"
314+
color="primary"
315+
size="small"
316+
onClick={handleOpenAddRegulatoryConsiderationModal}
317+
startIcon={<AddRounded />}
318+
data-cy="new-regulatory-consideration-button"
319+
disabled={!!regulatoryConsideration}
320+
>
321+
Regulatory Consideration
322+
</Button>
323+
<Button
324+
color="secondary"
325+
size="small"
326+
onClick={handleOpenAddRequirementModal}
327+
startIcon={<AddRounded />}
328+
data-cy="new-requirement-button"
329+
>
330+
New Requirement
331+
</Button>
332+
</Box>
333+
)}
330334
</Box>
331335
{isDataLoading ? (
332336
<RequirementLoading />

compliance-web/src/components/App/Inspections/Profile/Reports/ReportTopSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ export default function ReportTopSection() {
339339
</Box>
340340
{!isReportsReadOnly &&
341341
!isHistorical &&
342-
!inspectionReportsData?.date_issued && (
342+
inspectionReportsData?.is_open_for_editing && (
343343
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
344344
<Typography variant="body2">Wrong Version?</Typography>
345345
<Link

compliance-web/src/components/App/Inspections/Profile/Requirements/RequirementDrawer.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import * as yup from "yup";
3434
import { useRequirementStore } from "./requirementStore";
3535
import { useQueryClient } from "@tanstack/react-query";
3636
import { MQ } from "@/styles/responsive";
37+
import { useReportStore } from "../Reports/reportStore";
38+
import { InspectionStatusEnum } from "@/utils/constants";
3739

3840
type RequirementDrawerProps = {
3941
inspectionData: Inspection;
@@ -81,12 +83,16 @@ const RequirementDrawer: React.FC<RequirementDrawerProps> = ({
8183
restoreRequirementStoreFromSnapshot,
8284
} = useRequirementStore();
8385

86+
const { inspectionReportsData } = useReportStore();
87+
8488
const { data: inspectionRequirementTypesList } =
8589
useInspectionRequirementTypesData();
8690

8791
const isRequirementEditable = useMemo(
88-
() => inspectionData?.inspection_status?.toLowerCase() === "open",
89-
[inspectionData]
92+
() =>
93+
inspectionData?.inspection_status === InspectionStatusEnum.OPEN &&
94+
inspectionReportsData?.is_open_for_editing,
95+
[inspectionData, inspectionReportsData]
9096
);
9197

9298
const GeneratedFormSchema = RequirementFormSchema(isRegulatoryConsideration);
@@ -114,7 +120,13 @@ const RequirementDrawer: React.FC<RequirementDrawerProps> = ({
114120
}
115121
);
116122
}, [inspectionRequirementData, resetForm, inspectionRequirementTypesList]);
117-
const isInspectionClosed = useMemo(() => inspectionData?.inspection_status?.toLowerCase() === "closed", [inspectionData]);
123+
124+
const isInspectionClosed = useMemo(
125+
() =>
126+
inspectionData?.inspection_status === InspectionStatusEnum.CLOSED ||
127+
!inspectionReportsData?.is_open_for_editing,
128+
[inspectionData, inspectionReportsData]
129+
);
118130
const onCreateSuccess = useCallback(() => {
119131
onSubmit("Requirement created successfully!", true);
120132
resetForm();

compliance-web/src/models/InspectionRecord.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface InspectionRecord {
2424
record_prepared_by_position_id?: number;
2525
record_prepared_by?: StaffUser;
2626
record_prepared_by_position?: Position;
27+
is_open_for_editing?: boolean;
2728
field_change_info?: {
2829
inspection_scope_changed?: boolean;
2930
finding_statement_changed?: boolean;

compliance-web/src/routes/_authenticated/ce-database/inspections/$inspectionNumber.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ import {
2323
CR_CONTEXT_TYPE,
2424
DRAWER_WIDTHS,
2525
FILE_PROFILE_CONTEXT,
26+
InspectionStatusEnum,
2627
} from "@/utils/constants";
2728
import InspectionEnforcements from "@/components/App/Inspections/Profile/InspectionEnforcements";
2829
import useResponsiveDrawerWidth from "@/hooks/useResponsiveDrawerWidth";
30+
import { useReportStore } from "@/components/App/Inspections/Profile/Reports/reportStore";
2931

3032
export const Route = createFileRoute(
3133
"/_authenticated/ce-database/inspections/$inspectionNumber"
@@ -53,6 +55,7 @@ function InspectionProfilePage() {
5355
error,
5456
isLoading,
5557
} = useInspectionByNumber(inspectionNumber!);
58+
const { inspectionReportsData } = useReportStore();
5659

5760
const { data: caseFileData } = useCaseFileByNumber(
5861
inspectionData?.case_file.case_file_number ?? ""
@@ -71,9 +74,11 @@ function InspectionProfilePage() {
7174

7275
const isInspectionEditable = useMemo(() => {
7376
return (
74-
isUserEditAllowed && inspectionData?.inspection_status?.toLowerCase() === "open"
77+
isUserEditAllowed &&
78+
inspectionData?.inspection_status === InspectionStatusEnum.OPEN &&
79+
inspectionReportsData?.is_open_for_editing
7580
);
76-
}, [inspectionData, isUserEditAllowed]);
81+
}, [inspectionData, isUserEditAllowed, inspectionReportsData]);
7782

7883
// Event handlers
7984
const handleOnSubmit = useCallback(

0 commit comments

Comments
 (0)