Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions compliance-api/src/compliance_api/models/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import enum

from sqlalchemy import Boolean, Column, DateTime, Enum, ForeignKey, Index, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.orm import joinedload, relationship

from compliance_api.models.inspection import InspectionRequirement
from compliance_api.utils.constant import DELETE_DIC_PARAMS
Expand Down Expand Up @@ -284,25 +284,50 @@ def get_orders_by_case_files(cls, case_file_ids: list[int]):

@classmethod
def get_by_inspection_id(cls, inspection_id):
"""Find all orders by inspection id that have entries in the requirement map for the given inspection."""
return (
cls.query.join(
OrderInspectionRequirementMap,
OrderInspectionRequirementMap.order_id
== cls.id,
)
"""Find all orders by inspection id that have entries in the requirement map for the given inspection.

Uses a two-query approach to ensure all requirement maps are loaded for each order,
not just the ones for the queried inspection (important for linked orders).
"""
# First, get order IDs that have at least one requirement for this inspection
# Using a completely separate query to avoid contaminating the relationship context
order_ids = (
db.session.query(OrderInspectionRequirementMap.order_id)
.join(
InspectionRequirement,
InspectionRequirement.id
== OrderInspectionRequirementMap.inspection_requirement_id,
)
.join(
cls,
cls.id == OrderInspectionRequirementMap.order_id,
)
.filter(
InspectionRequirement.inspection_id == inspection_id,
OrderInspectionRequirementMap.is_active.is_(True),
OrderInspectionRequirementMap.is_deleted.is_(False),
cls.is_deleted.is_(False),
)
.distinct()
.all()
)

# Extract just the IDs from the result tuples
order_id_list = [order_id[0] for order_id in order_ids]

if not order_id_list:
return []

# Expire all objects in the session to ensure fresh load
# This prevents SQLAlchemy from using cached relationship data
db.session.expire_all()

# Load the full orders with ALL their requirement maps using a fresh query
# Use joinedload to eagerly load all requirement maps without any filters
return (
db.session.query(cls)
.filter(cls.id.in_(order_id_list))
.options(joinedload(cls.order_requirement_maps))
.order_by(cls.created_date.desc())
.all()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods

inspection_requirement = fields.Nested(
InspectionRequirementSchema(),
only=("id", "summary", "inspection_id"),
only=("id", "summary", "inspection_id", "requirement_source_details"),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class Meta: # pylint: disable=too-few-public-methods

inspection_requirement = fields.Nested(
InspectionRequirementSchema(),
only=("id", "summary"),
only=("id", "summary", "requirement_source_details"),
)


Expand Down
2 changes: 1 addition & 1 deletion compliance-api/src/compliance_api/schemas/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods

inspection_requirement = fields.Nested(
InspectionRequirementSchema(),
only=("id", "summary"),
only=("id", "summary", "requirement_source_details"),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Meta: # pylint: disable=too-few-public-methods

inspection_requirement = fields.Nested(
InspectionRequirementSchema(),
only=("id", "summary"),
only=("id", "summary", "requirement_source_details"),
)

inspection_requirement_id = fields.Integer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods

inspection_requirement = fields.Nested(
InspectionRequirementSchema(),
only=("id", "summary"),
only=("id", "summary", "requirement_source_details"),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods

inspection_requirement = fields.Nested(
InspectionRequirementSchema(),
only=("id", "summary"),
only=("id", "summary", "requirement_source_details"),
)


Expand Down
18 changes: 13 additions & 5 deletions compliance-api/src/compliance_api/services/order/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def create_order(cls, order_data: dict) -> OrderModel:
created_order = OrderModel.create(order_obj, session)
cls.insert_or_update_inspection_requirements(
created_order.id,
inspection_id,
order_data.get("inspection_requirement_ids", []),
session,
)
Expand Down Expand Up @@ -122,6 +123,7 @@ def update_order(cls, order_id: int, update_data: dict) -> OrderModel:
updated_order = OrderModel.update_order(order_id, update_data, session)
cls.insert_or_update_inspection_requirements(
updated_order.id,
inspection.id,
update_data.get("inspection_requirement_ids", []),
session,
)
Expand Down Expand Up @@ -153,12 +155,17 @@ def delete_order(cls, order_id: int) -> OrderModel:

@classmethod
def insert_or_update_inspection_requirements(
cls, order_id: int, inspection_requirement_ids: list[int], session=None
cls, order_id: int, inspection_id: int, inspection_requirement_ids: list[int], session=None
):
"""Insert/Update inspection requirements associated with a given order."""
"""Insert/Update inspection requirements associated with a given order for a specific inspection.

This method only modifies requirement maps that belong to the specified inspection_id,
leaving requirement maps from other inspections (in case of linked orders) untouched.
"""
if inspection_requirement_ids is not None:
existing_requirements = OrderInspectionRequirementMapModel.get_by_order_id(
order_id
# Only get existing requirements for this specific inspection
existing_requirements = OrderInspectionRequirementMapModel.get_by_inspection_and_order_id(
inspection_id, order_id
)
existing_requirement_ids = {
req.inspection_requirement_id for req in existing_requirements
Expand Down Expand Up @@ -292,7 +299,8 @@ def link(cls, order_id: int, link: dict) -> OrderModel:
"Order is already linked to the given inspection requirements"
)
with session_scope() as session:
cls.insert_or_update_inspection_requirements(
# Use bulk_insert to ADD requirements without deleting existing ones
OrderInspectionRequirementMapModel.bulk_insert(
order_id, requirement_ids, session
)
return order
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,70 +152,83 @@ export const formatRequirementSources = (
violationTicket?: ViolationTicket,
restorativeJustice?: RestorativeJustice
): string[] => {
const orderRequirementIds = order?.order_requirement_maps?.map(
(map) => map.inspection_requirement_id
);
const warningLetterRequirementIds =
warningLetter?.warning_letter_requirement_maps?.map(
(map) => map.inspection_requirement_id
);
// Collect all requirement maps from all enforcement types
const allRequirementMaps = [
...(order?.order_requirement_maps || []).map(map => map.inspection_requirement),
...(warningLetter?.warning_letter_requirement_maps || []).map(map => map.inspection_requirement),
...(administrativePenalty?.administrative_penalty_requirement_maps || []).map(map => map.inspection_requirement),
...(chargeRecommendation?.charge_recommendation_requirement_maps || []).map(map => map.inspection_requirement),
...(violationTicket?.violation_ticket_requirement_maps || []).map(map => map.inspection_requirement),
...(restorativeJustice?.restorative_justice_requirement_maps || []).map(map => map.inspection_requirement),
];

const administrativePenaltyRequirementIds = administrativePenalty?.administrative_penalty_requirement_maps?.map(
(map) => map.inspection_requirement_id
// Check if any requirement has source details from API
const hasSourceDetailsFromAPI = allRequirementMaps.some(
req => req.requirement_source_details && req.requirement_source_details.length > 0
);

const chargeRecommendationRequirementIds = chargeRecommendation?.charge_recommendation_requirement_maps?.map(
(map) => map.inspection_requirement_id
);
const result: string[] = [];

const violationTicketRequirementIds = violationTicket?.violation_ticket_requirement_maps?.map(
(map) => map.inspection_requirement_id
);
if (hasSourceDetailsFromAPI) {
allRequirementMaps.forEach((requirement) => {
const sourceMap = new Map<number, { name: string; numbers: string[] }>();

const restorativeJusticeRequirementIds = restorativeJustice?.restorative_justice_requirement_maps?.map(
(map) => map.inspection_requirement_id
);
requirement.requirement_source_details?.forEach((source) => {
const sourceId = source.requirement_source_id;
const sourceName = source.requirement_source?.name || "";
const number = source.condition_number ?? source.section_number ?? "";

const requirementIds = [
...(orderRequirementIds || []),
...(warningLetterRequirementIds || []),
...(administrativePenaltyRequirementIds || []),
...(chargeRecommendationRequirementIds || []),
...(violationTicketRequirementIds || []),
...(restorativeJusticeRequirementIds || []),
];
if (!sourceMap.has(sourceId)) {
sourceMap.set(sourceId, { name: sourceName, numbers: [] });
}

const requirements = requirementEnforcements.filter((requirement) =>
requirementIds?.includes(requirement.id)
);
if (number) {
sourceMap.get(sourceId)?.numbers.push(`#${number.trim()}`);
}
});

const result: string[] = [];
sourceMap.forEach((value) => {
if (value.numbers.length > 0) {
result.push(`${value.name}, ${value.numbers.join(", ")}`);
} else {
result.push(value.name);
}
});
});
} else {
// Fallback to the old way if no source details are available from API for data safety
const requirementIds = allRequirementMaps.map(req => req.id);
const requirements = requirementEnforcements.filter((requirement) =>
requirementIds?.includes(requirement.id)
);

requirements.forEach((requirement) => {
const sourceMap = new Map<number, { name: string; numbers: string[] }>();
requirements.forEach((requirement) => {
const sourceMap = new Map<number, { name: string; numbers: string[] }>();

requirement.requirement_source_details.forEach((source) => {
const sourceId = source.requirement_source_id;
const sourceName = source.requirement_source?.name || "";
const number = source.condition_number ?? source.section_number ?? "";
requirement.requirement_source_details?.forEach((source) => {
const sourceId = source.requirement_source_id;
const sourceName = source.requirement_source?.name || "";
const number = source.condition_number ?? source.section_number ?? "";

if (!sourceMap.has(sourceId)) {
sourceMap.set(sourceId, { name: sourceName, numbers: [] });
}
if (!sourceMap.has(sourceId)) {
sourceMap.set(sourceId, { name: sourceName, numbers: [] });
}

if (number) {
sourceMap.get(sourceId)?.numbers.push(`#${number.trim()}`);
}
});
if (number) {
sourceMap.get(sourceId)?.numbers.push(`#${number.trim()}`);
}
});

sourceMap.forEach((value) => {
if (value.numbers.length > 0) {
result.push(`${value.name}, ${value.numbers.join(", ")}`);
} else {
result.push(value.name);
}
sourceMap.forEach((value) => {
if (value.numbers.length > 0) {
result.push(`${value.name}, ${value.numbers.join(", ")}`);
} else {
result.push(value.name);
}
});
});
});
}

return result;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,7 @@ const OrderCreationOptions: FC<OrderCreationOptionsProps> = ({
}}
/>

{/* Hiding linking for now */}
{false && <FormControlLabel
<FormControlLabel
value="link_existing"
control={<Radio />}
label={
Expand All @@ -162,7 +161,7 @@ const OrderCreationOptions: FC<OrderCreationOptionsProps> = ({
mb: 2,
alignItems: 'flex-start'
}}
/>}
/>
</RadioGroup>

{/* Existing Order Selection */}
Expand Down
2 changes: 2 additions & 0 deletions compliance-web/src/models/AdministrativePenalty.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Inspection } from './Inspection';
import { InspectionRequirement } from './InspectionRequirement';
import { InspectionRequirementSource } from './InspectionRequirementSource';
import { Option } from './common';

export interface AdministrativePenalty {
Expand All @@ -24,6 +25,7 @@ interface AdministrativePenaltyRequirementMap {
id: number;
summary: string;
inspection_id: number;
requirement_source_details: InspectionRequirementSource[];
};
}

Expand Down
2 changes: 2 additions & 0 deletions compliance-web/src/models/ChargeRecommendation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InspectionRequirementSource } from "./InspectionRequirementSource";
import { Option } from "./common";

export interface ChargeRecommendation {
Expand Down Expand Up @@ -25,6 +26,7 @@ interface ChargeRecommendationRequirementMap {
inspection_requirement: {
id: number;
summary: string;
requirement_source_details: InspectionRequirementSource[];
};
}

Expand Down
3 changes: 3 additions & 0 deletions compliance-web/src/models/InspectionOrder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import { OrderApproval } from "./OrderApproval";
import { StaffUser } from "./Staff";
import { InspectionRequirementSource } from "./InspectionRequirementSource";

export interface InspectionOrder {
issuing_officer?: StaffUser;
section?: {
Expand Down Expand Up @@ -40,6 +42,7 @@ interface OrderRequirementMap {
inspection_requirement: {
id: number;
summary: string;
requirement_source_details: InspectionRequirementSource[];
};
}

Expand Down
2 changes: 2 additions & 0 deletions compliance-web/src/models/InspectionWarningLetter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InspectionRequirementSource } from "./InspectionRequirementSource";
import { StaffUser } from "./Staff";
import { WarningLetterApproval } from "./WarningLetterApproval";

Expand Down Expand Up @@ -33,6 +34,7 @@ interface WarningLetterRequirementMap {
inspection_requirement: {
id: number;
summary: string;
requirement_source_details: InspectionRequirementSource[];
};
}

Expand Down
3 changes: 3 additions & 0 deletions compliance-web/src/models/RestorativeJustice.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { InspectionRequirementSource } from "./InspectionRequirementSource";

export interface RestorativeJusticeRequirementMap {
id: number;
restorative_justice_id: number;
inspection_requirement_id: number;
inspection_requirement: {
id: number;
summary: string;
requirement_source_details: InspectionRequirementSource[]
};
}

Expand Down
Loading