Skip to content

Commit

Permalink
Merge pull request #45 from VineetBala-AOT/main
Browse files Browse the repository at this point in the history
bug fix for consolidated conditions
  • Loading branch information
VineetBala-AOT authored Jan 16, 2025
2 parents 3ac25a2 + 8fd0d09 commit 6fbf1c4
Show file tree
Hide file tree
Showing 17 changed files with 583 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from flask_restx import Namespace, Resource, cors
from marshmallow import ValidationError

from condition_api.schemas.condition import ProjectDocumentConditionSchema
from condition_api.services import authorization
from condition_api.services.condition_service import ConditionService
from condition_api.utils.util import cors_preflight
Expand Down Expand Up @@ -65,3 +66,39 @@ def get(project_id):
return consolidated_conditions, HTTPStatus.OK
except ValidationError as err:
return {"message": str(err)}, HTTPStatus.BAD_REQUEST



@cors_preflight("GET, OPTIONS")
@API.route("/project/<string:project_id>/consolidated-conditions", methods=["GET", "OPTIONS"])
class ConditionsResource(Resource):
"""Resource for fetching consolidated conditions by project_id and category_id."""

@staticmethod
@ApiHelper.swagger_decorators(API, endpoint_description="Get consolidated conditions")
@API.response(HTTPStatus.BAD_REQUEST, "Bad Request")
@auth.require
@cors.crossdomain(origin="*")
def get(project_id):
"""Fetch consolidated conditions and condition attributes by project and category ID."""
query_params = request.args
all_conditions = query_params.get('all_conditions', '').lower() == 'true'
category_id = query_params.get('category_id', '')

try:
consolidated_conditions = ConditionService.get_consolidated_conditions_by_project_and_category(
project_id,
category_id,
all_conditions
)

if not consolidated_conditions:
return {"message": "Condition not found"}, HTTPStatus.NOT_FOUND

# Instantiate the schema
condition_details_schema = ProjectDocumentConditionSchema()

# Call dump on the schema instance
return condition_details_schema.dump(consolidated_conditions), HTTPStatus.OK
except ValidationError as err:
return {"message": str(err)}, HTTPStatus.BAD_REQUEST
4 changes: 3 additions & 1 deletion condition-api/src/condition_api/schemas/condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ class ConditionSchema(Schema):
is_topic_tags_approved = fields.Bool(data_key="is_topic_tags_approved", allow_none=True)
is_condition_attributes_approved = fields.Bool(data_key="is_condition_attributes_approved", allow_none=True)
condition_attributes = fields.List(fields.Nested(UpdateConditionAttributeSchema), data_key="condition_attributes", allow_none=True)

effective_document_id = fields.Str(data_key="effective_document_id", allow_none=True)
# Condition can also have its own subconditions (recursive nesting)
subconditions = fields.List(fields.Nested(SubconditionSchema), data_key="subconditions")

class ProjectDocumentConditionSchema(Schema):
"""Top-level schema to include project and document names."""
project_name = fields.Str(data_key="project_name")
document_category = fields.Str(data_key="document_category")
conditions = fields.List(fields.Nested(ConditionSchema), data_key="conditions")

class ProjectDocumentConditionDetailSchema(Schema):
Expand Down
101 changes: 99 additions & 2 deletions condition-api/src/condition_api/services/condition_service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Service for condition management."""
from datetime import datetime
from sqlalchemy import and_, func, extract
from sqlalchemy import and_, case, func, extract
from sqlalchemy.orm import aliased
from condition_api.models.amendment import Amendment
from condition_api.models.attribute_key import AttributeKey
Expand Down Expand Up @@ -389,7 +389,7 @@ def update_condition(
Condition.id!=condition_id
).all()

existing_condition_numbers = [num[0] for num in existing_condition_numbers]
existing_condition_numbers = [num[0] for num in existing_condition_numbers if num[0] is not None]
if conditions_data.get("condition_number") in existing_condition_numbers:
raise ValueError("This condition number already exists. Please enter a new one.")

Expand Down Expand Up @@ -791,3 +791,100 @@ def get_consolidated_conditions(
conditions_schema = ConsolidatedConditionSchema(many=True)

return {"conditions": conditions_schema.dump(result)}

@staticmethod
def get_consolidated_conditions_by_project_and_category(
project_id,
category_id,
all_conditions
):
"""Fetch all conditions and their related subconditions by project ID and document ID."""
if all_conditions:
filter_condition = Project.project_id == project_id
else:
filter_condition = and_(
Project.project_id == project_id,
DocumentCategory.id == category_id,
)

amendment_subquery = (
db.session.query(
Condition.condition_number,
func.string_agg(Amendment.amendment_name.distinct(), ', ').label('amendment_names')
)
.select_from(Project)
.join(Document, Document.project_id == Project.project_id)
.join(DocumentType, DocumentType.id == Document.document_type_id)
.join(DocumentCategory, DocumentCategory.id == DocumentType.document_category_id)
.join(Amendment, Amendment.document_id == Document.id)
.join(Condition, Condition.amended_document_id == Amendment.amended_document_id)
.filter(filter_condition)
.group_by(Condition.condition_number)
.subquery()
)

documents = (
db.session.query(
Project.project_name,
Document.id,
Document.document_id,
DocumentCategory.category_name,
extract("year", Document.date_issued).label("year_issued")
)
.join(Document, Document.project_id == Project.project_id)
.join(DocumentType, DocumentType.id == Document.document_type_id)
.join(DocumentCategory, DocumentCategory.id == DocumentType.document_category_id)
.filter(filter_condition)
)

conditions_map = {}

for document in documents:
condition_data = (
db.session.query(
Condition.id.label('condition_id'),
Condition.condition_name,
Condition.condition_number,
Condition.is_approved,
Condition.topic_tags,
amendment_subquery.c.amendment_names,
case(
(Condition.amended_document_id.isnot(None), Condition.amended_document_id),
else_=Condition.document_id
).label('effective_document_id')
)
.outerjoin(
amendment_subquery,
Condition.condition_number == amendment_subquery.c.condition_number
)
.filter(
and_(
Condition.document_id == document[2],
Condition.is_active == True
)
)
.all()
)

for row in condition_data:
cond_id = row.condition_id

# Add each condition to the map if not already present
conditions_map[cond_id] = {
"condition_id": row.condition_id,
"condition_name": row.condition_name,
"condition_number": row.condition_number,
"is_approved": row.is_approved,
"topic_tags": row.topic_tags,
"amendment_names": row.amendment_names,
"year_issued": document.year_issued,
"effective_document_id": row.effective_document_id
}

conditions_list = list(conditions_map.values())

return {
"project_name": documents[0].project_name if documents else None,
"document_category": documents[0].category_name if documents else None,
"conditions": conditions_list
}
4 changes: 3 additions & 1 deletion condition-api/src/condition_api/services/document_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def get_all_documents_by_category(project_id, category_id):
Document.id.label('id'),
Document.document_id.label('document_id'),
Document.document_label.label('document_label'),
Document.is_latest_amendment_added,
Document.created_date,
extract('year', Document.date_issued).label('year_issued'),
case(
Expand Down Expand Up @@ -92,7 +93,8 @@ def get_all_documents_by_category(project_id, category_id):
'document_label': document.document_label,
'created_date': document.created_date,
'year_issued': document.year_issued,
'status': document.status
'status': document.status,
'is_latest_amendment_added': document.is_latest_amendment_added
})

# Append all amendments for the current document
Expand Down
8 changes: 6 additions & 2 deletions condition-web/src/components/Conditions/ConditionTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function ConditionTableRow({
}: ConditionRowProps) {

const navigate = useNavigate();
const handleOnDocumentClick = (projectId: string, documentId: string, conditionId?: number) => {
const handleOnDocumentClick = (projectId: string, documentId?: string, conditionId?: number) => {
navigate({
to: `/conditions/project/${projectId}/document/${documentId}/condition/${conditionId}`,
});
Expand Down Expand Up @@ -65,7 +65,11 @@ export default function ConditionTableRow({
alignItems: "center",
}}
component={"button"}
onClick={() => handleOnDocumentClick(projectId, documentId, condition.condition_id)}
onClick={() => handleOnDocumentClick(
projectId,
documentId !== "" ? documentId : condition.effective_document_id,
condition.condition_id
)}
>
<Typography
color={BCDesignTokens.themeBlue90}
Expand Down
4 changes: 2 additions & 2 deletions condition-web/src/components/Conditions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const Conditions = ({
const [hasAmendments, setHasAmendments] = useState(false);
const [isToggleEnabled, setIsToggleEnabled] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [noConditions, setNoConditions] = useState(false);
const [noConditions, setNoConditions] = useState(conditions?.length === 0);
const [openModal, setOpenModal] = useState(false);
const [selectedDocumentId, setSelectedDocumentId] = useState<string | "">("");
const [loadCondition, setLoadCondition] = useState(false);
Expand Down Expand Up @@ -198,7 +198,7 @@ export const Conditions = ({
<Box sx={{ display: "flex", alignItems: "top", fontWeight: "normal" }}>
<DocumentStatusChip
status={noConditions? "nodata" : String(isToggleEnabled) as DocumentStatus}
/>
/>
</Box>
</Stack>
</Stack>
Expand Down
137 changes: 137 additions & 0 deletions condition-web/src/components/ConsolidatedConditions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useState, useEffect } from "react";
import { BCDesignTokens } from "epic.theme";
import { ConditionModel } from "@/models/Condition";
import { Box, Grid, styled, Stack, Typography } from "@mui/material";
import { ContentBoxSkeleton } from "../Shared/ContentBox/ContentBoxSkeleton";
import { ContentBox } from "../Shared/ContentBox";
import ConditionTable from "../Conditions/ConditionsTable";
import { DocumentStatus } from "@/models/Document";
import DocumentStatusChip from "../Projects/DocumentStatusChip";
import LayersOutlinedIcon from '@mui/icons-material/LayersOutlined';

export const CardInnerBox = styled(Box)({
display: "flex",
alignItems: "flex-start",
justifyContent: "center",
flexDirection: "column",
height: "100%",
padding: "0 12px",
});

type ConditionsParam = {
conditions?: ConditionModel[];
projectName: string;
projectId: string;
documentCategory: string;
};

export const ConsolidatedConditions = ({
projectName,
projectId,
documentCategory,
conditions
}: ConditionsParam) => {
const [noConditions, setNoConditions] = useState(conditions?.length === 0);
const [allApproved, setAllApproved] = useState(false);
const [hasAmendments, setHasAmendments] = useState(false);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
// Check if all conditions have status as true
if (conditions && conditions.length > 0) {
const checkIfAllApproved = conditions.every((condition) => condition.is_approved === true);
const conditionHasAmendments = conditions.some(condition => condition.amendment_names != null);

const invalidConditions =
conditions.length === 1 &&
conditions.some(
(condition) =>
!condition.condition_name ||
!condition.condition_number ||
condition.is_approved === null
);
setNoConditions(invalidConditions);
setAllApproved(checkIfAllApproved);
setHasAmendments(conditionHasAmendments);
}
setIsLoading(false);
}, [conditions]);

if (isLoading) {
return <div>Loading...</div>;
}

return (
<Stack spacing={2} direction={"column"} sx={{ width: '100%' }}>
<ContentBox
mainLabel={
<Box component="span">
<Typography component="span" variant="h5" fontWeight="normal">
{projectName}
</Typography>
</Box>
}
label={""}
>
<Box
sx={{
borderRadius: "3px",
border: `1px solid ${BCDesignTokens.surfaceColorBorderDefault}`,
boxShadow: "0px 1px 2px rgba(0, 0, 0, 0.1)",
}}
>
<Typography
variant="h6"
sx={{
px: BCDesignTokens.layoutPaddingXsmall,
py: BCDesignTokens.layoutPaddingSmall,
borderBottom: `1px solid ${BCDesignTokens.surfaceColorBorderDefault}`,
}}
>
<Grid container direction="row" paddingBottom={3}>
<Grid item xs={6}>
<Stack direction={"row"}>
<Box sx={{ display: "flex", alignItems: "left", ml: 2 }}>
{documentCategory}
{hasAmendments && (
<Box sx={{ display: "flex", alignItems: "top", mr: 1, mt: 1 }}>
<LayersOutlinedIcon fontSize="small" sx={{ ml: 1 }} />
</Box>
)}
</Box>
<Box sx={{ display: "flex", alignItems: "top", fontWeight: "normal" }}>
<DocumentStatusChip
status={noConditions? "nodata" : String(allApproved) as DocumentStatus}
/>
</Box>
</Stack>
</Grid>
</Grid>
<Box height={"100%"} px={BCDesignTokens.layoutPaddingXsmall}>
<CardInnerBox
sx={{ height: "100%", py: BCDesignTokens.layoutPaddingSmall }}
>
<ConditionTable
conditions={conditions || []}
projectId={projectId}
documentId={""}
noConditions={noConditions}
/>
</CardInnerBox>
</Box>
</Typography>
</Box>
</ContentBox>
</Stack>
);
};

export const ConsolidatedConditionsSkeleton = () => {
return (
<Stack spacing={2} direction={"column"} sx={{ width: '100%' }}>
<ContentBoxSkeleton />
<ContentBoxSkeleton />
<ContentBoxSkeleton />
</Stack>
);
};
Loading

0 comments on commit 6fbf1c4

Please sign in to comment.