Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
83d2360
first commit: start with TODOs
diitaz93 Jun 29, 2026
84687fe
TODOs
diitaz93 Jun 29, 2026
700f33b
Merge branch 'master' into stop-ordering-existing-cases
diitaz93 Jul 6, 2026
9c4335f
Merge branch 'master' into stop-ordering-existing-cases
diitaz93 Jul 6, 2026
5c08117
More TODOs
diitaz93 Jul 6, 2026
2a00c84
final set of TODOs
diitaz93 Jul 6, 2026
401b018
Start removing logic for existing cases in the OrderWithCases
diitaz93 Jul 7, 2026
c0bd99c
Refactor: Remove existing case validation logic
diitaz93 Jul 7, 2026
bca4137
Refactor: Remove validation for existing cases belonging to collabora…
diitaz93 Jul 7, 2026
92a38b1
Refactor: Remove case existence validation logic
diitaz93 Jul 7, 2026
8631ef3
Remove property existing cases from OrderWithCases
diitaz93 Jul 7, 2026
529fcf4
Remove property existing cases from OrderWithCases
diitaz93 Jul 7, 2026
4fe9c8d
Remove existing case logic from ticket creation
diitaz93 Jul 7, 2026
d11e191
Remove case existence check and related utility function
diitaz93 Jul 7, 2026
5fac68b
Refactor: Simplify sample case check logic
diitaz93 Jul 7, 2026
7e1624d
Refactor: Rename and update sample source retrieval logic
diitaz93 Jul 7, 2026
a759a77
Refactor: Streamline existing sample names retrieval logic
diitaz93 Jul 7, 2026
0891ca8
remove is_new property
diitaz93 Jul 7, 2026
c14b6a5
Refactor: Replace enumerated_new_cases with enumerated_cases across t…
diitaz93 Jul 7, 2026
5096224
Remove existing case
diitaz93 Jul 7, 2026
3fd4e14
Refactor: Remove unused store parameter from validate_sample_names_di…
diitaz93 Jul 7, 2026
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
13 changes: 2 additions & 11 deletions cg/meta/orders/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from cg.constants.priority import Priority
from cg.models.orders.constants import OrderType
from cg.services.orders.constants import ORDER_TYPE_WORKFLOW_MAP
from cg.services.orders.validation.models.existing_sample import ExistingSample

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment to hold accidental merge to master

from cg.services.orders.validation.models.order import Order
from cg.services.orders.validation.models.order_with_cases import OrderWithCases
from cg.services.orders.validation.models.order_with_samples import OrderWithSamples
Expand All @@ -23,7 +22,7 @@

def contains_existing_data(order: OrderWithCases) -> bool:
"""Check if the order contains any existing data"""
return any(not case.is_new or case.enumerated_existing_samples for case in order.cases)
return any(case.enumerated_existing_samples for case in order.cases)


def contains_external_data(order: Order, status_db: Store) -> bool:
Expand Down Expand Up @@ -87,15 +86,7 @@ def get_existing_samples(order: Order, status_db: Store) -> list[Sample]:
existing_samples.extend(
[
sample
for (_, case) in order.enumerated_existing_cases
for sample in status_db.get_samples_by_case_id(case.internal_id)
]
)

existing_samples.extend(
[
sample
for (_, case) in order.enumerated_new_cases
for (_, case) in order.enumerated_cases
for (_, existing_sample) in case.enumerated_existing_samples
if (sample := status_db.get_sample_by_internal_id(existing_sample.internal_id))
]
Expand Down
59 changes: 17 additions & 42 deletions cg/services/orders/storing/implementations/case_order_service.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import logging
from datetime import datetime

from cg.constants.constants import CaseActions, DataDelivery, Workflow
from cg.constants.constants import DataDelivery, Workflow
from cg.constants.lims import LimsStatus
from cg.constants.pedigree import Pedigree
from cg.services.orders.constants import ORDER_TYPE_WORKFLOW_MAP
from cg.services.orders.lims_service.service import OrderLimsService
from cg.services.orders.storing.service import StoreOrderService
from cg.services.orders.validation.models.case import Case
from cg.services.orders.validation.models.existing_case import ExistingCase
from cg.services.orders.validation.models.order_with_cases import OrderWithCases
from cg.services.orders.validation.models.sample_aliases import SampleInCase
from cg.store.models import ApplicationVersion
Expand Down Expand Up @@ -67,47 +66,30 @@ def store_order_data_in_status_db(self, order: OrderWithCases) -> list[DbCase]:
new_cases: list[DbCase] = []
db_order = self._create_db_order(order)
for case in order.cases:
if case.is_new:
db_case: DbCase = self._create_db_case(
case=case,
customer=db_order.customer,
ticket=str(order._generated_ticket_id),
workflow=ORDER_TYPE_WORKFLOW_MAP[order.order_type],
delivery_type=order.delivery_type,
)
new_cases.append(db_case)
self._update_case_panel(panels=getattr(case, "panels", []), case=db_case)
case_samples: dict[str, DbSample] = self._create_db_sample_dict(
case=case, order=order, customer=db_order.customer
)
self._create_links(case=case, db_case=db_case, case_samples=case_samples)

else:
db_case: DbCase = self._update_existing_case(
existing_case=case, ticket_id=order._generated_ticket_id
)

db_case: DbCase = self._create_db_case(
case=case,
customer=db_order.customer,
ticket=str(order._generated_ticket_id),
workflow=ORDER_TYPE_WORKFLOW_MAP[order.order_type],
delivery_type=order.delivery_type,
)
new_cases.append(db_case)
self._update_case_panel(panels=getattr(case, "panels", []), case=db_case)
case_samples: dict[str, DbSample] = self._create_db_sample_dict(
case=case, order=order, customer=db_order.customer
)
self._create_links(case=case, db_case=db_case, case_samples=case_samples)
db_order.cases.append(db_case)
self.status_db.add_multiple_items_to_store(new_cases)
self.status_db.add_item_to_store(db_order)
self.status_db.commit_to_store()
self.status_db.add_multiple_items_to_store(new_cases)
self.status_db.add_item_to_store(db_order)
self.status_db.commit_to_store()
return new_cases

@staticmethod
def _update_case_panel(panels: list[str], case: DbCase) -> None:
"""Update case panels."""
case.panels = panels

@staticmethod
def _append_ticket(ticket_id: str, case: DbCase) -> None:
"""Add a ticket to the case."""
case.tickets = f"{case.tickets},{ticket_id}"

@staticmethod
def _update_action(action: str, case: DbCase) -> None:
"""Update action of a case."""
case.action = action

def _create_link(
self,
case: DbCase,
Expand Down Expand Up @@ -184,13 +166,6 @@ def _create_db_order(self, order: OrderWithCases) -> DbOrder:
ticket_id=order._generated_ticket_id,
)

def _update_existing_case(self, existing_case: ExistingCase, ticket_id: int) -> DbCase:
status_db_case = self.status_db.get_case_by_internal_id(existing_case.internal_id)
self._append_ticket(ticket_id=str(ticket_id), case=status_db_case)
self._update_action(action=CaseActions.ANALYZE, case=status_db_case)
self._update_case_panel(panels=getattr(existing_case, "panels", []), case=status_db_case)
return status_db_case

def _create_links(self, case: Case, db_case: DbCase, case_samples: dict[str, DbSample]) -> None:
"""Creates entries in the CaseSample table.
Input:
Expand Down
66 changes: 26 additions & 40 deletions cg/services/orders/submitter/ticket_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from cg.services.orders.validation.models.order import Order
from cg.services.orders.validation.models.order_with_cases import OrderWithCases
from cg.services.orders.validation.models.order_with_samples import OrderWithSamples
from cg.store.models import Case, Customer, Sample
from cg.store.models import Customer, Sample
from cg.store.store import Store

LOG = logging.getLogger(__name__)
Expand All @@ -31,7 +31,7 @@ def __init__(self, db: Store, client: FreshdeskClient, system_email_id: int, env

def create_ticket(
self, order: Order, user_name: str, user_mail: str, order_type: OrderType
) -> int | None:
) -> int:
"""Create a ticket and return the ticket number"""
message: str = self.create_new_ticket_header(
message=self.create_xml_sample_list(order=order, user_name=user_name),
Expand Down Expand Up @@ -180,58 +180,44 @@ def replace_empty_string_with_none(cls, obj: Any) -> Any:
obj[key] = cls.replace_empty_string_with_none(item)
return obj

def create_case_xml_sample_list(self, order, message: str) -> str:
def create_case_xml_sample_list(self, order: OrderWithCases, message: str) -> str:
for case in order.cases:
if not case.is_new:
db_case = self.status_db.get_case_by_internal_id(case.internal_id)
for sample in db_case.samples:
for sample in case.samples:
if not sample.is_new:
message += self.NEW_LINE
message = self.add_existing_sample_info_to_message(
message=message,
customer_id=sample.customer.internal_id,
customer_id=order.customer,
internal_id=sample.internal_id,
case_name=db_case.name,
case_name=case.name,
)
else:
message = self.add_sample_name_to_message(
message=message, sample_name=sample.name
)
message = self.add_sample_apptag_to_message(
message=message, application=sample.application
)
message = self.add_sample_case_name_to_message(
message=message, case_name=case.name
)
message = self.add_sample_priority_to_message(
message=message, priority=case.priority
)
message = self.add_sample_comment_to_message(
message=message, comment=sample.comment
)
else:
for sample in case.samples:
if not sample.is_new:
message += self.NEW_LINE
message = self.add_existing_sample_info_to_message(
message=message,
customer_id=order.customer,
internal_id=sample.internal_id,
case_name=case.name,
)
else:
message = self.add_sample_name_to_message(
message=message, sample_name=sample.name
)
message = self.add_sample_apptag_to_message(
message=message, application=sample.application
)
message = self.add_sample_case_name_to_message(
message=message, case_name=case.name
)
message = self.add_sample_priority_to_message(
message=message, priority=case.priority
)
message = self.add_sample_comment_to_message(
message=message, comment=sample.comment
)
return message

def _get_max_case_priority(self, order: Order) -> Priority:
@staticmethod
def _get_max_case_priority(order: Order) -> Priority:
"""Get max case priority for a given order."""
priority_list: list[Priority] = []

if isinstance(order, OrderWithCases):
for index, new_case in order.enumerated_new_cases:
for index, new_case in order.enumerated_cases:
priority_list.append(Priority[new_case.priority])

for index, case in order.enumerated_existing_cases:
case: Case = self.status_db.get_case_by_internal_id(case.internal_id)
priority_list.append(case.priority)

if isinstance(order, OrderWithSamples):
for sample in order.samples:
priority_list.append(Priority[sample.priority])
Expand Down
17 changes: 0 additions & 17 deletions cg/services/orders/validation/errors/case_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,6 @@ class CaseNameNotAvailableError(CaseError):
message: str = "Case name already used in a previous order"


class CaseDoesNotExistError(CaseError):
field: str = "internal_id"
message: str = "The case does not exist"


class CaseOutsideOfCollaborationError(CaseError):
field: str = "internal_id"
message: str = "Case does not belong to collaboration"


class MultipleSamplesInCaseError(CaseError):
field: str = "sample_errors"
message: str = "Multiple samples in the same case not allowed"
Expand Down Expand Up @@ -67,13 +57,6 @@ class NewCaseWithoutAffectedSampleError(CaseError):
message: str = "Each case needs at least one affected sample"


class ExistingCaseWithoutAffectedSampleError(CaseError):
field: str = "sample_errors"
message: str = (
"This case contains no affected sample. Please create a new case with at least one affected sample."
)


class MultiplePrepCategoriesError(CaseError):
field: str = "sample_errors"
message: str = "Case cannot contain samples with incompatible applications"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import TypeVar

from pydantic_core import ValidationError
from pydantic import ValidationError

from cg.services.orders.validation.errors.validation_errors import ValidationErrors
from cg.services.orders.validation.model_validator.utils import convert_errors
Expand Down
33 changes: 16 additions & 17 deletions cg/services/orders/validation/model_validator/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pydantic_core import ErrorDetails, ValidationError
from pydantic import ValidationError
from pydantic_core import ErrorDetails

from cg.services.orders.validation.errors.case_errors import CaseError
from cg.services.orders.validation.errors.case_sample_errors import CaseSampleError
Expand All @@ -10,11 +11,9 @@
def convert_errors(pydantic_errors: ValidationError) -> ValidationErrors:
error_details: list[ErrorDetails] = pydantic_errors.errors()
order_errors: list[OrderError] = convert_order_errors(error_details)
case_errors: list[CaseError] = convert_case_errors(error_details=error_details)
case_sample_errors: list[CaseSampleError] = convert_case_sample_errors(
error_details=error_details
)
sample_errors: list[SampleError] = convert_sample_errors(error_details=error_details)
case_errors: list[CaseError] = convert_case_errors(error_details)
case_sample_errors: list[CaseSampleError] = convert_case_sample_errors(error_details)
sample_errors: list[SampleError] = convert_sample_errors(error_details)
return ValidationErrors(
order_errors=order_errors,
case_errors=case_errors,
Expand Down Expand Up @@ -97,18 +96,18 @@ def create_case_sample_error(error: ErrorDetails) -> CaseSampleError:


"""
What follows below are ways of extracting data from a Pydantic ErrorDetails object. The aim is to find out
where the error occurred, for which the 'loc' value (which is a tuple) can be used. It is generally structured in
alternating strings and ints, specifying field names and list indices. An example:
if loc = ('samples', 2, 'well_position'), that means that the error stems from the well_position of the
third sample in the order.
What follows below are ways of extracting data from a Pydantic ErrorDetails object. The aim is to
find out where the error occurred, for which the 'loc' value (which is a tuple) can be used. It is
generally structured in alternating strings and ints, specifying field names and list indices.
An example:
if loc = ('samples', 2, 'well_position'),
that means that the error stems from the well_position of the third sample in the order.

As an additional point of complexity, the discriminator is also added to the loc, specifically in
OrdersWithCases which have a discriminator for both cases and samples specifying if it is a new
or existing case/sample. So
loc = ('cases', 0, 'new', 'priority')
means that the error concerns the first case in the order, which is a new case, and it concerns the field
'priority'.
Case which have a discriminator for case samples specifying if it is a new or existing sample. So
loc = ('samples', 0, 'new', 'volume')
means that the error concerns the first sample in the case, which is a new sample, and it concerns
the field 'volume'.
"""


Expand Down Expand Up @@ -154,7 +153,7 @@ def get_sample_field_name(error: ErrorDetails) -> str:


def get_case_field_name(error: ErrorDetails) -> str:
index_for_field_name: int = error["loc"].index("cases") + 3
index_for_field_name: int = error["loc"].index("cases") + 2
return error["loc"][index_for_field_name]


Expand Down
4 changes: 0 additions & 4 deletions cg/services/orders/validation/models/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ class Case(BaseModel, Generic[SampleType]):
]
]

@property
def is_new(self) -> bool:
return True

@property
def enumerated_samples(self) -> enumerate[SampleType | ExistingSample]:
return enumerate(self.samples)
Expand Down
10 changes: 0 additions & 10 deletions cg/services/orders/validation/models/existing_case.py

This file was deleted.

Loading
Loading