Skip to content

Commit 704ccd3

Browse files
Merge pull request #131 from VineetBala-AOT/main
Prevent saving amendment with duplicate condition number
2 parents a0c2271 + 5e7234a commit 704ccd3

File tree

8 files changed

+137
-19
lines changed

8 files changed

+137
-19
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""add_condition_type
2+
3+
Revision ID: 1eb403b982a4
4+
Revises: d909569f8c4e
5+
Create Date: 2025-08-29 13:35:05.915295
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '1eb403b982a4'
14+
down_revision = 'd909569f8c4e'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
condition_type_enum = sa.Enum("ADD", "AMEND", name="condition_type_enum")
20+
21+
def upgrade():
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
condition_type_enum.create(op.get_bind(), checkfirst=True)
24+
25+
with op.batch_alter_table('conditions', schema='condition') as batch_op:
26+
batch_op.add_column(
27+
sa.Column('condition_type',
28+
sa.Enum('ADD', 'AMEND', name='condition_type_enum'),
29+
nullable=True)
30+
)
31+
32+
# Set values based on amended_document_id
33+
op.execute("""
34+
UPDATE condition.conditions
35+
SET condition_type = CASE
36+
WHEN amended_document_id IS NULL THEN 'ADD'::condition_type_enum
37+
ELSE 'AMEND'::condition_type_enum
38+
END
39+
""")
40+
41+
with op.batch_alter_table('conditions', schema='condition') as batch_op:
42+
batch_op.alter_column('condition_type', nullable=False)
43+
44+
# ### end Alembic commands ###
45+
46+
47+
def downgrade():
48+
# ### commands auto generated by Alembic - please adjust! ###
49+
with op.batch_alter_table('conditions', schema='condition') as batch_op:
50+
batch_op.drop_column('condition_type')
51+
52+
# Drop the enum type as well (if you want clean rollback)
53+
op.execute("DROP TYPE condition_type_enum")
54+
55+
# ### end Alembic commands ###

condition-api/src/condition_api/models/condition.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
33
Manages the Condition
44
"""
5-
from sqlalchemy import ARRAY, Boolean, Column, DateTime, ForeignKey, Integer, String, Text
5+
from sqlalchemy import ARRAY, Boolean, Column, DateTime, Enum, ForeignKey, Integer, String, Text
66
from sqlalchemy.orm import relationship
7+
from condition_api.utils.enums import ConditionType
78

89
from .base_model import BaseModel
910

@@ -32,6 +33,11 @@ class Condition(BaseModel):
3233
is_active = Column(Boolean, nullable=False, default=True)
3334
is_standard_condition = Column(Boolean, nullable=True)
3435
requires_management_plan = Column(Boolean, nullable=True)
36+
condition_type = Column(
37+
Enum(ConditionType, name="condition_type_enum"),
38+
nullable=False,
39+
default=ConditionType.ADD,
40+
)
3541

3642
# Establish a one-to-many relationship with subcondition
3743
subconditions = relationship('Subcondition', back_populates='condition', cascade='all, delete-orphan')

condition-api/src/condition_api/schemas/condition.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
33
Manages the condition
44
"""
5-
65
from condition_api.schemas.condition_attribute import ConditionAttributesSchema
76
from condition_api.schemas.subcondition import SubconditionSchema
7+
from condition_api.utils.enums import ConditionType
88

99
from marshmallow import Schema, fields
10+
from marshmallow_enum import EnumField
1011

1112

1213
class ConditionSchema(Schema):
@@ -32,6 +33,7 @@ class ConditionSchema(Schema):
3233
source_document = fields.Str(data_key="source_document", allow_none=True)
3334
# Condition can also have its own subconditions (recursive nesting)
3435
subconditions = fields.List(fields.Nested(SubconditionSchema), data_key="subconditions")
36+
condition_type = EnumField(ConditionType)
3537

3638

3739
class ProjectDocumentConditionSchema(Schema):

condition-api/src/condition_api/services/condition_service.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -525,15 +525,14 @@ def create_condition(project_id, document_id, conditions_data, allow_duplicate_c
525525
.first()
526526
)
527527

528-
if allow_duplicate_condition:
529-
ConditionService._check_duplicate_condition_number(
530-
condition=Condition(
531-
document_id=final_document_id,
532-
amended_document_id=amended_document_id
533-
),
534-
condition_id=None, # None since we are creating a new one
535-
condition_number=conditions_data.get("condition_number")
536-
)
528+
ConditionService._check_duplicate_condition_number(
529+
condition=Condition(
530+
document_id=final_document_id,
531+
amended_document_id=amended_document_id
532+
),
533+
condition_id=None, # None since we are creating a new one
534+
condition_number=conditions_data.get("condition_number")
535+
)
537536

538537
# If it exists, update is_active to False
539538
if existing_condition and not allow_duplicate_condition:
@@ -554,6 +553,7 @@ def create_condition(project_id, document_id, conditions_data, allow_duplicate_c
554553
is_standard_condition=conditions_data.get("is_standard_condition"),
555554
topic_tags=conditions_data.get("topic_tags"),
556555
subtopic_tags=conditions_data.get("subtopic_tags"),
556+
condition_type=conditions_data.get("condition_type"),
557557
effective_from=datetime.utcnow()
558558
)
559559
db.session.add(new_condition)

condition-api/src/condition_api/utils/enums.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
"""Enum definitions."""
15-
from enum import IntEnum
15+
from enum import IntEnum, Enum
1616
from typing import List
1717

1818

@@ -79,3 +79,10 @@ def required_attribute_keys() -> List[int]:
7979
AttributeKeys.TIME_ASSOCIATED_WITH_SUBMISSION_MILESTONE,
8080
AttributeKeys.REQUIRES_CONSULTATION,
8181
]
82+
83+
84+
class ConditionType(Enum):
85+
"""Condition Type — whether a condition is newly added or an amendment."""
86+
87+
ADD = "add"
88+
AMEND = "amend"

condition-web/src/components/ConditionDetails/CreateCondition/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import ChipInput from "../../Shared/Chips/ChipInput";
2323
import { useNavigate } from "@tanstack/react-router";
2424
import CloseIcon from '@mui/icons-material/Close';
2525
import LoadingButton from "../../Shared/Buttons/LoadingButton";
26+
import { useQueryClient } from "@tanstack/react-query";
27+
import { QUERY_KEY } from "@/hooks/api/constants";
2628

2729
export const CardInnerBox = styled(Box)({
2830
display: "flex",
@@ -40,7 +42,7 @@ type ConditionsParam = {
4042
export const CreateConditionPage = ({
4143
conditionData
4244
}: ConditionsParam) => {
43-
45+
const queryClient = useQueryClient();
4446
const navigate = useNavigate();
4547

4648
const [condition, setCondition] = useState<ConditionModel>(
@@ -129,6 +131,11 @@ export const CreateConditionPage = ({
129131
};
130132
const response = await updateCondition(data);
131133
if (response) {
134+
await queryClient.refetchQueries({
135+
queryKey: [QUERY_KEY.CONDITIONS, conditionData?.project_id, conditionData?.document_id],
136+
exact: true,
137+
});
138+
132139
navigate({
133140
to: `/conditions/project/${conditionData?.project_id}/document/${conditionData?.document_id}`,
134141
});

condition-web/src/components/Conditions/CreateConditionModal.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { useCreateCondition } from "@/hooks/api/useConditions";
1919
import { useGetDocumentsByProject } from "@/hooks/api/useDocuments";
2020
import { useGetConditions } from "@/hooks/api/useConditions";
2121
import { DocumentModel } from "@/models/Document";
22-
import { ConditionModel } from "@/models/Condition";
22+
import { ConditionModel, ConditionType } from "@/models/Condition";
2323
import { notify } from "@/components/Shared/Snackbar/snackbarStore";
2424
import { useNavigate } from "@tanstack/react-router";
2525
import { HTTP_STATUS_CODES } from "../../hooks/api/constants";
@@ -68,8 +68,22 @@ export const ConditionModal: FC<ConditionModalProps> = ({ open, onClose, project
6868
}
6969
}
7070

71+
const handleClose = () => {
72+
// reset fields
73+
setSelectedMode("amend");
74+
setConditionNumber("");
75+
setConditionName("");
76+
setConditionConflictError(false);
77+
setSelectedDocumentId("");
78+
setSelectedConditionId(null);
79+
setLoadCondition(false);
80+
81+
// call parent-provided close
82+
onClose();
83+
};
84+
7185
return (
72-
<Modal open={open} onClose={onClose}>
86+
<Modal open={open} onClose={handleClose}>
7387
<Paper sx={{
7488
position: "absolute",
7589
top: "50%",
@@ -82,7 +96,7 @@ export const ConditionModal: FC<ConditionModalProps> = ({ open, onClose, project
8296
}}>
8397
<Box display="flex" justifyContent="space-between" alignItems="center" padding={"14px 5px 14px 14px"}>
8498
<Typography variant="h6">Manual Condition Entry</Typography>
85-
<IconButton onClick={onClose}>
99+
<IconButton onClick={handleClose}>
86100
<CloseIcon />
87101
</IconButton>
88102
</Box>
@@ -161,6 +175,19 @@ export const ConditionModal: FC<ConditionModalProps> = ({ open, onClose, project
161175
rows={4}
162176
variant="outlined"
163177
/>
178+
{conditionConflictError && (
179+
<Box
180+
sx={{
181+
display: "flex",
182+
flexDirection: "row",
183+
marginBottom: "15px",
184+
color: "#CE3E39",
185+
marginTop: "-20px",
186+
}}
187+
>
188+
This condition number already exists within this amendment.
189+
</Box>
190+
) }
164191
</Box>
165192
)}
166193
</Stack>
@@ -272,7 +299,7 @@ export const ConditionModal: FC<ConditionModalProps> = ({ open, onClose, project
272299

273300
<Divider />
274301
<Box sx={{ display: "flex", justifyContent: "right", padding: "14px" }}>
275-
<Button variant="outlined" sx={{ minWidth: "100px" }} onClick={onClose}>Cancel</Button>
302+
<Button variant="outlined" sx={{ minWidth: "100px" }} onClick={handleClose}>Cancel</Button>
276303
<Button
277304
variant="contained"
278305
sx={{ marginLeft: "8px", minWidth: "100px" }}
@@ -281,12 +308,18 @@ export const ConditionModal: FC<ConditionModalProps> = ({ open, onClose, project
281308
handleCreateNewCondition({
282309
condition_number: Number(conditionNumber) || undefined,
283310
condition_name: conditionName,
311+
condition_type: ConditionType.ADD
284312
});
285313
} else {
286314
const selectedCondition = documentConditions?.conditions?.find(
287315
(condition) => condition.condition_id === selectedConditionId
288316
);
289-
handleCreateNewCondition(selectedCondition);
317+
if (selectedCondition) {
318+
handleCreateNewCondition({
319+
...selectedCondition,
320+
condition_type: ConditionType.AMEND,
321+
});
322+
}
290323
}
291324
}}
292325
>

condition-web/src/models/Condition.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { SubconditionModel } from "./Subcondition";
22
import { ConditionAttributeModel } from "./ConditionAttribute";
33

4+
export enum ConditionType {
5+
ADD = "ADD",
6+
AMEND = "AMEND",
7+
}
8+
49
export interface ConditionModel {
510
condition_id?: number;
611
condition_name?: string;
@@ -19,6 +24,7 @@ export interface ConditionModel {
1924
subtopic_tags?: string[];
2025
subconditions?: SubconditionModel[]; // Nested subconditions
2126
condition_attributes?: ConditionAttributeModel;
27+
condition_type?: ConditionType;
2228
}
2329

2430
export const createDefaultCondition = (): ConditionModel => {
@@ -41,6 +47,7 @@ export const createDefaultCondition = (): ConditionModel => {
4147
independent_attributes: [],
4248
management_plans: [],
4349
},
50+
condition_type: ConditionType.ADD,
4451
};
4552
};
4653

@@ -51,7 +58,8 @@ export interface updateTopicTagsModel {
5158
is_approved?: boolean;
5259
is_topic_tags_approved?: boolean;
5360
is_condition_attributes_approved?: boolean;
54-
subconditions?: SubconditionModel[],
61+
subconditions?: SubconditionModel[];
62+
condition_type?: ConditionType;
5563
}
5664

5765
export type PartialUpdateTopicTagsModel = Partial<updateTopicTagsModel>;

0 commit comments

Comments
 (0)