diff --git a/cg/meta/orders/utils.py b/cg/meta/orders/utils.py index 937735dab84..4290f634387 100644 --- a/cg/meta/orders/utils.py +++ b/cg/meta/orders/utils.py @@ -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 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 @@ -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: @@ -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)) ] diff --git a/cg/services/orders/storing/implementations/case_order_service.py b/cg/services/orders/storing/implementations/case_order_service.py index e185b9bdd5a..a16c1ff3350 100644 --- a/cg/services/orders/storing/implementations/case_order_service.py +++ b/cg/services/orders/storing/implementations/case_order_service.py @@ -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 @@ -67,30 +66,23 @@ 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 @@ -98,16 +90,6 @@ 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, @@ -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: diff --git a/cg/services/orders/submitter/ticket_handler.py b/cg/services/orders/submitter/ticket_handler.py index eec5f0b9d39..4be2b277d36 100644 --- a/cg/services/orders/submitter/ticket_handler.py +++ b/cg/services/orders/submitter/ticket_handler.py @@ -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__) @@ -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), @@ -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]) diff --git a/cg/services/orders/validation/errors/case_errors.py b/cg/services/orders/validation/errors/case_errors.py index ce759f93696..cf70c27f334 100644 --- a/cg/services/orders/validation/errors/case_errors.py +++ b/cg/services/orders/validation/errors/case_errors.py @@ -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" @@ -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" diff --git a/cg/services/orders/validation/model_validator/model_validator.py b/cg/services/orders/validation/model_validator/model_validator.py index 5ff97d792f8..6a50c5a40b6 100644 --- a/cg/services/orders/validation/model_validator/model_validator.py +++ b/cg/services/orders/validation/model_validator/model_validator.py @@ -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 diff --git a/cg/services/orders/validation/model_validator/utils.py b/cg/services/orders/validation/model_validator/utils.py index 0d290f6a547..d78a25bdfd3 100644 --- a/cg/services/orders/validation/model_validator/utils.py +++ b/cg/services/orders/validation/model_validator/utils.py @@ -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 @@ -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, @@ -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'. """ @@ -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] diff --git a/cg/services/orders/validation/models/case.py b/cg/services/orders/validation/models/case.py index 7ea56df37a5..51fe80fbdc8 100644 --- a/cg/services/orders/validation/models/case.py +++ b/cg/services/orders/validation/models/case.py @@ -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) diff --git a/cg/services/orders/validation/models/existing_case.py b/cg/services/orders/validation/models/existing_case.py deleted file mode 100644 index 3bb7a508dec..00000000000 --- a/cg/services/orders/validation/models/existing_case.py +++ /dev/null @@ -1,10 +0,0 @@ -from pydantic import BaseModel - - -class ExistingCase(BaseModel): - internal_id: str - panels: list[str] | None = None - - @property - def is_new(self) -> bool: - return False diff --git a/cg/services/orders/validation/models/order_with_cases.py b/cg/services/orders/validation/models/order_with_cases.py index c56397ec1c9..db8ca1d5a88 100644 --- a/cg/services/orders/validation/models/order_with_cases.py +++ b/cg/services/orders/validation/models/order_with_cases.py @@ -1,11 +1,6 @@ from typing import Generic, TypeVar -from pydantic import Discriminator, Tag -from typing_extensions import Annotated - from cg.services.orders.validation.models.case import Case -from cg.services.orders.validation.models.discriminators import has_internal_id -from cg.services.orders.validation.models.existing_case import ExistingCase from cg.services.orders.validation.models.existing_sample import ExistingSample from cg.services.orders.validation.models.order import Order from cg.services.orders.validation.models.sample import Sample @@ -15,38 +10,17 @@ class OrderWithCases(Order, Generic[CaseType, SampleType]): - cases: list[ - Annotated[ - Annotated[CaseType, Tag("new")] | Annotated[ExistingCase, Tag("existing")], - Discriminator(has_internal_id), - ] - ] + cases: list[CaseType] @property - def enumerated_cases(self) -> enumerate[CaseType | ExistingCase]: + def enumerated_cases(self) -> enumerate[CaseType]: return enumerate(self.cases) - @property - def enumerated_new_cases(self) -> list[tuple[int, CaseType]]: - cases: list[tuple[int, CaseType]] = [] - for case_index, case in self.enumerated_cases: - if not isinstance(case, ExistingCase): - cases.append((case_index, case)) - return cases - - @property - def enumerated_existing_cases(self) -> list[tuple[int, ExistingCase]]: - cases: list[tuple[int, ExistingCase]] = [] - for case_index, case in self.enumerated_cases: - if isinstance(case, ExistingCase): - cases.append((case_index, case)) - return cases - @property def enumerated_new_samples(self) -> list[tuple[int, int, SampleType]]: return [ (case_index, sample_index, sample) - for case_index, case in self.enumerated_new_cases + for case_index, case in self.enumerated_cases for sample_index, sample in case.enumerated_new_samples ] @@ -54,6 +28,6 @@ def enumerated_new_samples(self) -> list[tuple[int, int, SampleType]]: def enumerated_existing_samples(self) -> list[tuple[int, int, ExistingSample]]: return [ (case_index, sample_index, sample) - for case_index, case in self.enumerated_new_cases + for case_index, case in self.enumerated_cases for sample_index, sample in case.enumerated_existing_samples ] diff --git a/cg/services/orders/validation/order_types/balsamic/validation_rules.py b/cg/services/orders/validation/order_types/balsamic/validation_rules.py index 86418e288dd..f6229e4a543 100644 --- a/cg/services/orders/validation/order_types/balsamic/validation_rules.py +++ b/cg/services/orders/validation/order_types/balsamic/validation_rules.py @@ -2,10 +2,8 @@ from cg.services.orders.validation.rules.case.rules import ( validate_at_most_two_samples_per_case, - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, - validate_existing_cases_belong_to_collaboration, validate_number_of_normal_samples, ) from cg.services.orders.validation.rules.case_sample.rules import ( @@ -40,10 +38,8 @@ BALSAMIC_CASE_RULES: list[Callable] = [ validate_at_most_two_samples_per_case, - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, - validate_existing_cases_belong_to_collaboration, validate_number_of_normal_samples, ] diff --git a/cg/services/orders/validation/order_types/mip_dna/validation_rules.py b/cg/services/orders/validation/order_types/mip_dna/validation_rules.py index dccf55a1ce3..33aa8e954b1 100644 --- a/cg/services/orders/validation/order_types/mip_dna/validation_rules.py +++ b/cg/services/orders/validation/order_types/mip_dna/validation_rules.py @@ -2,12 +2,9 @@ from cg.services.orders.validation.rules.case.rules import ( validate_case_contains_related_samples, - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, validate_each_new_case_has_an_affected_sample, - validate_existing_cases_belong_to_collaboration, - validate_existing_cases_have_an_affected_sample, validate_gene_panels_exist, validate_gene_panels_unique, validate_samples_in_case_have_same_prep_category, @@ -46,12 +43,9 @@ ) MIP_DNA_CASE_RULES: list[Callable] = [ - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, validate_each_new_case_has_an_affected_sample, - validate_existing_cases_have_an_affected_sample, - validate_existing_cases_belong_to_collaboration, validate_gene_panels_exist, validate_gene_panels_unique, validate_samples_in_case_have_same_prep_category, diff --git a/cg/services/orders/validation/order_types/mip_rna/validation_rules.py b/cg/services/orders/validation/order_types/mip_rna/validation_rules.py index f1e71427b11..c902395a112 100644 --- a/cg/services/orders/validation/order_types/mip_rna/validation_rules.py +++ b/cg/services/orders/validation/order_types/mip_rna/validation_rules.py @@ -1,10 +1,8 @@ from typing import Callable from cg.services.orders.validation.rules.case.rules import ( - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, - validate_existing_cases_belong_to_collaboration, ) from cg.services.orders.validation.rules.case_sample.rules import ( validate_application_compatibility, @@ -33,10 +31,8 @@ ) MIP_RNA_CASE_RULES: list[Callable] = [ - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, - validate_existing_cases_belong_to_collaboration, ] MIP_RNA_CASE_SAMPLE_RULES: list[Callable] = [ diff --git a/cg/services/orders/validation/order_types/nallo/validation_rules.py b/cg/services/orders/validation/order_types/nallo/validation_rules.py index aedf53d3c74..a064ecf0a0d 100644 --- a/cg/services/orders/validation/order_types/nallo/validation_rules.py +++ b/cg/services/orders/validation/order_types/nallo/validation_rules.py @@ -1,12 +1,9 @@ from typing import Callable from cg.services.orders.validation.rules.case.rules import ( - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, validate_each_new_case_has_an_affected_sample, - validate_existing_cases_belong_to_collaboration, - validate_existing_cases_have_an_affected_sample, validate_gene_panels_exist, validate_gene_panels_unique, ) @@ -41,12 +38,9 @@ ) NALLO_CASE_RULES: list[Callable] = [ - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, validate_each_new_case_has_an_affected_sample, - validate_existing_cases_have_an_affected_sample, - validate_existing_cases_belong_to_collaboration, validate_gene_panels_exist, validate_gene_panels_unique, ] diff --git a/cg/services/orders/validation/order_types/raredisease/validation_rules.py b/cg/services/orders/validation/order_types/raredisease/validation_rules.py index 020b289cc6f..a36cd6eb114 100644 --- a/cg/services/orders/validation/order_types/raredisease/validation_rules.py +++ b/cg/services/orders/validation/order_types/raredisease/validation_rules.py @@ -2,12 +2,9 @@ from cg.services.orders.validation.rules.case.rules import ( validate_case_contains_related_samples, - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, validate_each_new_case_has_an_affected_sample, - validate_existing_cases_belong_to_collaboration, - validate_existing_cases_have_an_affected_sample, validate_gene_panels_exist, validate_gene_panels_unique, validate_samples_in_case_have_same_prep_category, @@ -45,12 +42,9 @@ ) RAREDISEASE_CASE_RULES: list[Callable] = [ - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, validate_each_new_case_has_an_affected_sample, - validate_existing_cases_have_an_affected_sample, - validate_existing_cases_belong_to_collaboration, validate_gene_panels_exist, validate_gene_panels_unique, validate_samples_in_case_have_same_prep_category, diff --git a/cg/services/orders/validation/order_types/rna_fusion/validation_rules.py b/cg/services/orders/validation/order_types/rna_fusion/validation_rules.py index 80b9b59e913..33ab42d39e3 100644 --- a/cg/services/orders/validation/order_types/rna_fusion/validation_rules.py +++ b/cg/services/orders/validation/order_types/rna_fusion/validation_rules.py @@ -1,10 +1,8 @@ from typing import Callable from cg.services.orders.validation.rules.case.rules import ( - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, - validate_existing_cases_belong_to_collaboration, validate_one_sample_per_case, ) from cg.services.orders.validation.rules.case_sample.rules import ( @@ -37,10 +35,8 @@ ) RNAFUSION_CASE_RULES: list[Callable] = [ - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, - validate_existing_cases_belong_to_collaboration, validate_one_sample_per_case, ] diff --git a/cg/services/orders/validation/order_types/tomte/validation_rules.py b/cg/services/orders/validation/order_types/tomte/validation_rules.py index bb66b64f417..0661dabe370 100644 --- a/cg/services/orders/validation/order_types/tomte/validation_rules.py +++ b/cg/services/orders/validation/order_types/tomte/validation_rules.py @@ -1,10 +1,8 @@ from typing import Callable from cg.services.orders.validation.rules.case.rules import ( - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, - validate_existing_cases_belong_to_collaboration, validate_gene_panels_exist, validate_gene_panels_unique, validate_samples_have_same_source, @@ -43,10 +41,8 @@ ) TOMTE_CASE_RULES: list[Callable] = [ - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, - validate_existing_cases_belong_to_collaboration, validate_gene_panels_exist, validate_gene_panels_unique, validate_samples_have_same_source, diff --git a/cg/services/orders/validation/rules/case/rules.py b/cg/services/orders/validation/rules/case/rules.py index 2366f3eb6bb..9c6d961ea5e 100644 --- a/cg/services/orders/validation/rules/case/rules.py +++ b/cg/services/orders/validation/rules/case/rules.py @@ -1,12 +1,9 @@ from cg.apps.lims import LimsAPI from cg.models.orders.sample_base import StatusEnum from cg.services.orders.validation.errors.case_errors import ( - CaseDoesNotExistError, CaseNameNotAvailableError, - CaseOutsideOfCollaborationError, DoubleNormalError, DoubleTumourError, - ExistingCaseWithoutAffectedSampleError, InvalidGenePanelsError, MoreThanTwoSamplesInCaseError, MultiplePrepCategoriesError, @@ -28,26 +25,22 @@ from cg.services.orders.validation.order_types.tomte.models.order import TomteOrder from cg.services.orders.validation.rules.case.utils import ( contains_duplicates, - does_case_exist, get_case_prep_categories, get_invalid_panels, get_sample_name, - get_sample_sources, - is_case_not_from_collaboration, + get_sample_sources_from_case, is_double_normal, is_double_tumour, is_normal_only_wgs, is_sample_related_in_case, - is_single_sample_case, ) from cg.services.orders.validation.rules.case_sample.utils import get_repeated_case_name_errors -from cg.store.models import Case as DbCase from cg.store.store import Store def validate_gene_panels_unique(order: OrderWithCases, **kwargs) -> list[RepeatedGenePanelsError]: errors: list[RepeatedGenePanelsError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: if contains_duplicates(case.panels): error = RepeatedGenePanelsError(case_index=case_index) errors.append(error) @@ -61,42 +54,13 @@ def validate_case_names_available( ) -> list[CaseNameNotAvailableError]: errors: list[CaseNameNotAvailableError] = [] customer = store.get_customer_by_internal_id(order.customer) - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: if store.get_case_by_name_and_customer(case_name=case.name, customer=customer): error = CaseNameNotAvailableError(case_index=case_index) errors.append(error) return errors -def validate_case_internal_ids_exist( - order: OrderWithCases, - store: Store, - **kwargs, -) -> list[CaseDoesNotExistError]: - errors: list[CaseDoesNotExistError] = [] - for case_index, case in order.enumerated_existing_cases: - db_case: DbCase | None = store.get_case_by_internal_id(case.internal_id) - if not db_case: - error = CaseDoesNotExistError(case_index=case_index) - errors.append(error) - return errors - - -def validate_existing_cases_belong_to_collaboration( - order: OrderWithCases, - store: Store, - **kwargs, -) -> list[CaseOutsideOfCollaborationError]: - """Validates that all existing cases within the order belong to a customer - within the order's customer's collaboration.""" - errors: list[CaseOutsideOfCollaborationError] = [] - for case_index, case in order.enumerated_existing_cases: - if is_case_not_from_collaboration(case=case, customer_id=order.customer, store=store): - error = CaseOutsideOfCollaborationError(case_index=case_index) - errors.append(error) - return errors - - def validate_case_names_not_repeated( order: OrderWithCases, **kwargs, @@ -110,7 +74,7 @@ def validate_one_sample_per_case( """Validates that there is only one sample in each case. Only applicable to RNAFusion.""" errors: list[MultipleSamplesInCaseError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: if len(case.samples) > 1: error = MultipleSamplesInCaseError(case_index=case_index) errors.append(error) @@ -123,7 +87,7 @@ def validate_at_most_two_samples_per_case( """Validates that there is at most two samples in each case. Only applicable to Balsamic and Balsamic-UMI.""" errors: list[MoreThanTwoSamplesInCaseError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: if len(case.samples) > 2: error = MoreThanTwoSamplesInCaseError(case_index=case_index) errors.append(error) @@ -139,7 +103,7 @@ def validate_number_of_normal_samples( Only applicable to Balsamic and Balsamic-UMI. """ errors: list[NumberOfNormalSamplesError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: if is_double_normal(case=case, store=store): error = DoubleNormalError(case_index=case_index) errors.append(error) @@ -157,32 +121,18 @@ def validate_each_new_case_has_an_affected_sample( ) -> list[NewCaseWithoutAffectedSampleError]: """Validates that each case in the order contains at least one sample with affected status.""" errors: list[NewCaseWithoutAffectedSampleError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: if all(sample.status != StatusEnum.affected for sample in case.samples): error = NewCaseWithoutAffectedSampleError(case_index=case_index) errors.append(error) return errors -def validate_existing_cases_have_an_affected_sample( - order: MIPDNAOrder | NalloOrder, store: Store, **kwargs -) -> list[ExistingCaseWithoutAffectedSampleError]: - errors: list[ExistingCaseWithoutAffectedSampleError] = [] - for case_index, case in order.enumerated_existing_cases: - db_case: DbCase | None = store.get_case_by_internal_id(case.internal_id) - if not db_case: # Error should be returned elsewhere - continue - if all(link.status != StatusEnum.affected for link in db_case.links): - error = ExistingCaseWithoutAffectedSampleError(case_index=case_index) - errors.append(error) - return errors - - def validate_samples_in_case_have_same_prep_category( order: OrderWithCases, store: Store, **kwargs ) -> list[MultiplePrepCategoriesError]: errors: list[MultiplePrepCategoriesError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: prep_categories: set[str] = get_case_prep_categories(case=case, store=store) if len(prep_categories) > 1: error = MultiplePrepCategoriesError(case_index=case_index) @@ -194,10 +144,8 @@ def validate_case_contains_related_samples( order: MIPDNAOrder | RarediseaseOrder, store: Store, **kwargs ) -> list[SamplesNotRelatedError]: errors: list[SamplesNotRelatedError] = [] - for case_index, case in order.enumerated_new_cases: - if not does_case_exist(case=case, store=store): # Error should be raised elsewhere - continue - if is_single_sample_case(case=case, store=store): # This should always pass + for case_index, case in order.enumerated_cases: + if len(case.samples) == 1: continue case_has_error = False isolated_samples: list[str] = [] @@ -219,7 +167,9 @@ def validate_samples_have_same_source( ) -> list[SampleSourceMismatchError]: errors: list[SampleSourceMismatchError] = [] for case_index, case in order.enumerated_cases: - sample_sources: set = get_sample_sources(case=case, lims_api=lims_api, store=store) + sample_sources: set = get_sample_sources_from_case( + case=case, lims_api=lims_api, store=store + ) if len(sample_sources) > 1: error = SampleSourceMismatchError( case_index=case_index, @@ -235,7 +185,7 @@ def validate_gene_panels_exist( **kwargs, ) -> list[InvalidGenePanelsError]: errors: list[InvalidGenePanelsError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: if invalid_panels := get_invalid_panels(panels=case.panels, store=store): case_error = InvalidGenePanelsError(case_index=case_index, panels=invalid_panels) errors.append(case_error) diff --git a/cg/services/orders/validation/rules/case/utils.py b/cg/services/orders/validation/rules/case/utils.py index 194bd970f6e..4d9e305b9ee 100644 --- a/cg/services/orders/validation/rules/case/utils.py +++ b/cg/services/orders/validation/rules/case/utils.py @@ -1,7 +1,6 @@ from cg.apps.lims import LimsAPI from cg.constants.sequencing import SeqLibraryPrepCategory 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.existing_sample import ExistingSample from cg.services.orders.validation.order_types.balsamic.models.case import BalsamicCase from cg.services.orders.validation.order_types.balsamic.models.sample import BalsamicSample @@ -12,9 +11,7 @@ from cg.services.orders.validation.order_types.raredisease.models.case import RarediseaseCase from cg.services.orders.validation.order_types.raredisease.models.sample import RarediseaseSample from cg.services.orders.validation.order_types.tomte.models.case import TomteCase -from cg.store.models import Application -from cg.store.models import Case as DbCase -from cg.store.models import Customer, Sample +from cg.store.models import Application, Sample from cg.store.store import Store @@ -63,12 +60,6 @@ def _is_sample_wgs_normal( ) -def is_case_not_from_collaboration(case: ExistingCase, customer_id: str, store: Store) -> bool: - db_case: DbCase | None = store.get_case_by_internal_id(case.internal_id) - customer: Customer | None = store.get_customer_by_internal_id(customer_id) - return db_case and customer and db_case.customer not in customer.collaborators - - def is_sample_in_case(case: Case, sample_name: str, store: Store) -> bool: if case.get_new_sample(sample_name): return True @@ -95,21 +86,6 @@ def get_case_prep_categories(case: Case, store: Store) -> set[str]: return prep_categories -def does_case_exist(case: MIPDNACase | RarediseaseCase | ExistingCase, store: Store): - if isinstance(case, ExistingCase): - return bool(store.get_case_by_internal_id(case.internal_id)) - return True - - -def is_single_sample_case(case: MIPDNACase | RarediseaseCase | ExistingCase, store: Store): - if isinstance(case, ExistingCase): - db_case: DbCase = store.get_case_by_internal_id_strict(case.internal_id) - contains_one_sample = bool(len(db_case.samples) == 1) - else: - contains_one_sample = bool(len(case.samples) == 1) - return contains_one_sample - - def is_sample_related_in_case( sample: MIPDNASample | RarediseaseSample | ExistingSample, case: MIPDNACase | RarediseaseCase, @@ -133,14 +109,8 @@ def get_sample_name(sample: MIPDNASample | RarediseaseSample | ExistingSample, s return sample_name -def get_sample_sources(case: TomteCase | ExistingCase, lims_api: LimsAPI, store: Store) -> set: - if isinstance(case, ExistingCase): - return _get_existing_case_sources(case=case, lims_api=lims_api, store=store) - else: - return _get_new_case_sources(case=case, lims_api=lims_api, store=store) - - -def _get_new_case_sources(case: TomteCase, lims_api: LimsAPI, store: Store) -> set: +def get_sample_sources_from_case(case: TomteCase, lims_api: LimsAPI, store: Store) -> set: + """Return unique sources from the samples of the provided case fetched from LIMS.""" sources = set() for sample in case.samples: if isinstance(sample, ExistingSample): @@ -155,16 +125,6 @@ def _get_new_case_sources(case: TomteCase, lims_api: LimsAPI, store: Store) -> s return sources -def _get_existing_case_sources(case: ExistingCase, lims_api: LimsAPI, store: Store) -> set: - db_case = store.get_case_by_internal_id(case.internal_id) - if not db_case: # This should result in an error elsewhere - return set() - sources = set() - for sample in db_case.samples: - sources.add(lims_api.get_source(sample.from_sample or sample.internal_id)) - return sources - - def get_invalid_panels(panels: list[str], store: Store) -> list[str]: invalid_panels: list[str] = [ panel for panel in panels if not store.does_gene_panel_exist(panel) diff --git a/cg/services/orders/validation/rules/case_sample/rules.py b/cg/services/orders/validation/rules/case_sample/rules.py index f3290f49499..df7be805f6d 100644 --- a/cg/services/orders/validation/rules/case_sample/rules.py +++ b/cg/services/orders/validation/rules/case_sample/rules.py @@ -62,7 +62,6 @@ from cg.services.orders.validation.rules.case_sample.utils import ( are_all_samples_unknown, get_counter_container_names, - get_existing_case_names, get_existing_sample_names, get_father_case_errors, get_father_sex_errors, @@ -104,7 +103,7 @@ def validate_application_compatibility( ) -> list[ApplicationNotCompatibleError]: errors: list[ApplicationNotCompatibleError] = [] order_type: OrderType = order.order_type - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if not is_application_compatible( order_type=order_type, @@ -128,7 +127,7 @@ def validate_buffer_skip_rc_condition(order: OrderWithCases, **kwargs) -> list[I def validate_buffers_are_allowed(order: OrderWithCases, **kwargs) -> list[InvalidBufferError]: errors: list[InvalidBufferError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if sample.elution_buffer not in ALLOWED_SKIP_RC_BUFFERS: error = InvalidBufferError(case_index=case_index, sample_index=sample_index) @@ -142,7 +141,7 @@ def validate_concentration_required_if_skip_rc( if not order.skip_reception_control: return [] errors: list[ConcentrationRequiredIfSkipRCError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_concentration_missing(sample): error = ConcentrationRequiredIfSkipRCError( @@ -157,7 +156,7 @@ def validate_subject_ids_different_from_sample_names( order: OrderWithCases, **kwargs ) -> list[SubjectIdSameAsSampleNameError]: errors: list[SubjectIdSameAsSampleNameError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if sample.name == sample.subject_id: error = SubjectIdSameAsSampleNameError( @@ -172,7 +171,7 @@ def validate_well_positions_required( order: OrderWithCases, **kwargs ) -> list[WellPositionMissingError]: errors: list[WellPositionMissingError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_well_position_missing(sample): error = WellPositionMissingError(case_index=case_index, sample_index=sample_index) @@ -184,7 +183,7 @@ def validate_container_name_required( order: OrderWithCases, **kwargs ) -> list[ContainerNameMissingError]: errors: list[ContainerNameMissingError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_container_name_missing(sample): error = ContainerNameMissingError( @@ -201,7 +200,7 @@ def validate_application_exists( **kwargs, ) -> list[ApplicationNotValidError]: errors: list[ApplicationNotValidError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if not store.get_application_by_tag(sample.application): error = ApplicationNotValidError(case_index=case_index, sample_index=sample_index) @@ -215,7 +214,7 @@ def validate_application_not_archived( **kwargs, ) -> list[ApplicationArchivedError]: errors: list[ApplicationArchivedError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if store.is_application_archived(sample.application): error = ApplicationArchivedError(case_index=case_index, sample_index=sample_index) @@ -225,7 +224,7 @@ def validate_application_not_archived( def validate_volume_interval(order: OrderWithCases, **kwargs) -> list[InvalidVolumeError]: errors: list[InvalidVolumeError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_volume_invalid(sample): error = InvalidVolumeError(case_index=case_index, sample_index=sample_index) @@ -235,7 +234,7 @@ def validate_volume_interval(order: OrderWithCases, **kwargs) -> list[InvalidVol def validate_volume_required(order: OrderWithCases, **kwargs) -> list[VolumeRequiredError]: errors: list[VolumeRequiredError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_volume_missing(sample): error = VolumeRequiredError(case_index=case_index, sample_index=sample_index) @@ -249,7 +248,7 @@ def validate_samples_exist( **kwargs, ) -> list[SampleDoesNotExistError]: errors: list[SampleDoesNotExistError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_existing_samples: db_sample: DbSample | None = store.get_sample_by_internal_id(sample.internal_id) if not db_sample: @@ -288,15 +287,14 @@ def validate_sample_names_not_repeated( def validate_sample_names_different_from_case_names( - order: OrderWithCases, store: Store, **kwargs + order: OrderWithCases, + **kwargs, ) -> list[SampleNameSameAsCaseNameError]: """Return errors with the indexes of samples having the same name as any case in the order.""" errors: list[SampleNameSameAsCaseNameError] = [] - new_case_names: set[str] = {case.name for _, case in order.enumerated_new_cases} - existing_case_names: set[str] = get_existing_case_names(order=order, status_db=store) - all_case_names = new_case_names.union(existing_case_names) + case_names: set[str] = {case.name for _, case in order.enumerated_cases} for case_index, sample_index, sample in order.enumerated_new_samples: - if sample.name in all_case_names: + if sample.name in case_names: error = SampleNameSameAsCaseNameError( case_index=case_index, sample_index=sample_index, @@ -309,7 +307,7 @@ def validate_fathers_are_male( order: OrderWithCases, store: Store, **kwargs ) -> list[InvalidFatherSexError]: errors: list[InvalidFatherSexError] = [] - for index, case in order.enumerated_new_cases: + for index, case in order.enumerated_cases: case_errors: list[InvalidFatherSexError] = get_father_sex_errors( case=case, case_index=index, store=store ) @@ -321,7 +319,7 @@ def validate_fathers_in_same_case_as_children( order: OrderWithCases, store: Store, **kwargs ) -> list[FatherNotInCaseError]: errors: list[FatherNotInCaseError] = [] - for index, case in order.enumerated_new_cases: + for index, case in order.enumerated_cases: case_errors: list[FatherNotInCaseError] = get_father_case_errors( case=case, case_index=index, store=store ) @@ -333,7 +331,7 @@ def validate_mothers_are_female( order: OrderWithCases, store: Store, **kwargs ) -> list[InvalidMotherSexError]: errors: list[InvalidMotherSexError] = [] - for index, case in order.enumerated_new_cases: + for index, case in order.enumerated_cases: case_errors: list[InvalidMotherSexError] = get_mother_sex_errors( case=case, case_index=index, store=store ) @@ -345,7 +343,7 @@ def validate_mothers_in_same_case_as_children( order: OrderWithCases, store: Store, **kwargs ) -> list[MotherNotInCaseError]: errors: list[MotherNotInCaseError] = [] - for index, case in order.enumerated_new_cases: + for index, case in order.enumerated_cases: case_errors: list[MotherNotInCaseError] = get_mother_case_errors( case=case, case_index=index, store=store ) @@ -355,7 +353,7 @@ def validate_mothers_in_same_case_as_children( def validate_pedigree(order: OrderWithCases, store: Store, **kwargs) -> list[PedigreeError]: errors: list[PedigreeError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: case_errors: list[PedigreeError] = get_pedigree_errors( case=case, case_index=case_index, store=store ) @@ -389,7 +387,7 @@ def validate_subject_ids_different_from_case_names( order: OrderWithCases, **kwargs ) -> list[SubjectIdSameAsCaseNameError]: errors: list[SubjectIdSameAsCaseNameError] = [] - for index, case in order.enumerated_new_cases: + for index, case in order.enumerated_cases: case_errors: list[SubjectIdSameAsCaseNameError] = validate_subject_ids_in_case( case=case, case_index=index, @@ -404,7 +402,7 @@ def validate_concentration_interval_if_skip_rc( if not order.skip_reception_control: return [] errors: list[InvalidConcentrationIfSkipRCError] = [] - for index, case in order.enumerated_new_cases: + for index, case in order.enumerated_cases: case_errors: list[InvalidConcentrationIfSkipRCError] = validate_concentration_in_case( case=case, case_index=index, @@ -416,7 +414,7 @@ def validate_concentration_interval_if_skip_rc( def validate_well_position_format(order: OrderWithCases, **kwargs) -> list[WellFormatError]: errors: list[WellFormatError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_invalid_plate_well_format(sample=sample): error = WellFormatError(case_index=case_index, sample_index=sample_index) @@ -431,7 +429,7 @@ def validate_tube_container_name_unique( container_name_counter: Counter = get_counter_container_names(order) - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_sample_tube_name_reused(sample=sample, counter=container_name_counter): error = ContainerNameRepeatedError(case_index=case_index, sample_index=sample_index) @@ -444,7 +442,7 @@ def validate_not_all_samples_unknown_in_case( ) -> list[StatusUnknownError]: errors: list[StatusUnknownError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: if are_all_samples_unknown(case): for sample_index, _ in case.enumerated_samples: error = StatusUnknownError(case_index=case_index, sample_index=sample_index) @@ -471,7 +469,7 @@ def reset_optional_capture_kits( to be rendered as a warning for each such sample. """ errors: list[CaptureKitResetError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if not does_sample_need_capture_kit(sample=sample, store=store) and sample.capture_kit: sample.capture_kit = None @@ -488,7 +486,7 @@ def validate_capture_kit_requirement( Applicable to Balsamic and Balsamic-UMI orders only. """ errors: list[CaptureKitMissingError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_sample_missing_capture_kit(sample=sample, store=store): error = CaptureKitMissingError(case_index=case_index, sample_index=sample_index) @@ -512,7 +510,7 @@ def validate_existing_samples_belong_to_collaboration( ) -> list[SampleOutsideOfCollaborationError]: """Validates that existing samples belong to the same collaboration as the order's customer.""" errors: list[SampleOutsideOfCollaborationError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_existing_samples: if is_sample_not_from_collaboration( customer_id=order.customer, sample=sample, store=store @@ -563,7 +561,7 @@ def validate_source_comment_required( **kwargs, ) -> list[MissingSourceCommentError]: errors: list[MissingSourceCommentError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if sample.source == "other" and not sample.source_comment: error = MissingSourceCommentError(case_index=case_index, sample_index=sample_index) @@ -619,7 +617,7 @@ def validate_matching_normal_dna_for_rna_samples( if DataDelivery.SCOUT not in order.delivery_type: return [] errors: list[CaseSampleError] = [] - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_samples: try: subject_id: str | None = get_subject_id(sample=sample, store=store) diff --git a/cg/services/orders/validation/rules/case_sample/utils.py b/cg/services/orders/validation/rules/case_sample/utils.py index 2fc73a8d1dd..dbc8d280651 100644 --- a/cg/services/orders/validation/rules/case_sample/utils.py +++ b/cg/services/orders/validation/rules/case_sample/utils.py @@ -69,7 +69,7 @@ def get_well_sample_map( well_position, provided the sample is on a plate. """ well_position_to_sample_map = {} - for case_index, case in order.enumerated_new_cases: + for case_index, case in order.enumerated_cases: for sample_index, sample in case.enumerated_new_samples: if is_sample_on_plate(sample): key: tuple[str, str] = (sample.container_name, sample.well_position) @@ -89,10 +89,10 @@ def get_occupied_well_errors(colliding_samples: list[tuple[int, int]]) -> list[O def get_indices_for_repeated_case_names(order: OrderWithCases) -> list[int]: - counter = Counter([case.name for _, case in order.enumerated_new_cases]) + counter = Counter([case.name for _, case in order.enumerated_cases]) indices: list[int] = [] - for index, case in order.enumerated_new_cases: + for index, case in order.enumerated_cases: if counter.get(case.name) > 1: indices.append(index) @@ -260,7 +260,7 @@ def is_sample_tube_name_reused(sample: Sample, counter: Counter) -> bool: def get_counter_container_names(order: OrderWithCases) -> Counter: counter = Counter( sample.container_name - for case_index, case in order.enumerated_new_cases + for case_index, case in order.enumerated_cases for sample_index, sample in case.enumerated_new_samples ) return counter @@ -269,14 +269,9 @@ def get_counter_container_names(order: OrderWithCases) -> Counter: def get_existing_sample_names(order: OrderWithCases, status_db: Store) -> set[str]: existing_sample_names: set[str] = set() for case in order.cases: - if case.is_new: - for sample_index, sample in case.enumerated_existing_samples: - db_sample = status_db.get_sample_by_internal_id(sample.internal_id) - existing_sample_names.add(db_sample.name) - else: - db_case = status_db.get_case_by_internal_id(case.internal_id) - for sample in db_case.samples: - existing_sample_names.add(sample.name) + for sample_index, sample in case.enumerated_existing_samples: + db_sample = status_db.get_sample_by_internal_id(sample.internal_id) + existing_sample_names.add(db_sample.name) return existing_sample_names @@ -301,14 +296,6 @@ def is_sample_not_from_collaboration( return db_sample and customer and db_sample.customer not in customer.collaborators -def get_existing_case_names(order: OrderWithCases, status_db: Store) -> set[str]: - existing_case_names: set[str] = set() - for _, case in order.enumerated_existing_cases: - if db_case := status_db.get_case_by_internal_id(case.internal_id): - existing_case_names.add(db_case.name) - return existing_case_names - - def is_sample_compatible_with_order_type( order_type: OrderType, sample: ExistingSample, store: Store ) -> bool: diff --git a/tests/services/orders/submitter/test_order_submitter.py b/tests/services/orders/submitter/test_order_submitter.py index 168238d5fb5..8517b50864b 100644 --- a/tests/services/orders/submitter/test_order_submitter.py +++ b/tests/services/orders/submitter/test_order_submitter.py @@ -15,7 +15,6 @@ from cg.services.orders.submitter.service import OrderSubmitter from cg.services.orders.validation.errors.validation_errors import ValidationErrors from cg.services.orders.validation.models.case import Case as ValidationCase -from cg.services.orders.validation.models.existing_case import ExistingCase from cg.services.orders.validation.models.existing_sample import ExistingSample from cg.services.orders.validation.models.order import Order from cg.services.orders.validation.models.order_with_cases import OrderWithCases @@ -186,17 +185,6 @@ def order_with_new_case_and_existing_external_samples(existing_sample_id: str) - ) -@pytest.fixture -def order_with_existing_case_and_external_sample(existing_case_id: str) -> OrderWithCases: - return OrderWithCases( - delivery_type=DataDelivery.ANALYSIS_FILES, - cases=[ExistingCase(internal_id=existing_case_id)], - customer="test_customer", - project_type=OrderType.BALSAMIC_UMI, - name="order with existing case and external sample data", - ) - - @pytest.mark.parametrize( "order_type, order_fixture", [ @@ -342,7 +330,7 @@ def test_get_ticket_tags( ): """Test that the correct tags are generated based on the order and order type.""" - # GIVEN an order with existing data and no external samples + # GIVEN an order without external samples order: OrderWithCases = request.getfixturevalue(order_fixture) # WHEN getting the ticket tags @@ -405,22 +393,6 @@ def test_get_ticket_tags_with_external_data_for_order_with_new_case_and_new_samp assert "external-data" in tags -def test_get_ticket_tags_with_external_data_for_order_with_existing_case( - order_with_existing_case_and_external_sample: OrderWithCases, store_with_externals: Store -): - - # GIVEN an existing case with an existing sample with external data - order: OrderWithCases = order_with_existing_case_and_external_sample - - # WHEN getting the ticket tags - tags: list[str] = get_ticket_tags( - order=order, order_type=order.order_type, status_db=store_with_externals - ) - - # THEN the tags should include external data - assert "external-data" in tags - - @pytest.mark.parametrize( "order_fixture, expected_status", [ diff --git a/tests/services/orders/validation_service/test_case_rules.py b/tests/services/orders/validation_service/test_case_rules.py index d47694ff2a3..610c0f4c30f 100644 --- a/tests/services/orders/validation_service/test_case_rules.py +++ b/tests/services/orders/validation_service/test_case_rules.py @@ -1,14 +1,10 @@ from unittest.mock import Mock, create_autospec from cg.apps.lims import LimsAPI -from cg.constants import GenePanelMasterList from cg.models.orders.constants import OrderType from cg.models.orders.sample_base import ContainerEnum, SexEnum, StatusEnum from cg.services.orders.validation.errors.case_errors import ( - CaseDoesNotExistError, CaseNameNotAvailableError, - CaseOutsideOfCollaborationError, - ExistingCaseWithoutAffectedSampleError, InvalidGenePanelsError, MultiplePrepCategoriesError, MultipleSamplesInCaseError, @@ -17,7 +13,6 @@ SamplesNotRelatedError, SampleSourceMismatchError, ) -from cg.services.orders.validation.models.existing_case import ExistingCase from cg.services.orders.validation.models.existing_sample import ExistingSample from cg.services.orders.validation.models.order_with_cases import OrderWithCases from cg.services.orders.validation.order_types.mip_dna.constants import MIPDNADeliveryType @@ -32,12 +27,9 @@ from cg.services.orders.validation.order_types.tomte.models.sample import TomteSample from cg.services.orders.validation.rules.case.rules import ( validate_case_contains_related_samples, - validate_case_internal_ids_exist, validate_case_names_available, validate_case_names_not_repeated, validate_each_new_case_has_an_affected_sample, - validate_existing_cases_belong_to_collaboration, - validate_existing_cases_have_an_affected_sample, validate_gene_panels_exist, validate_one_sample_per_case, validate_samples_have_same_source, @@ -70,28 +62,6 @@ def test_case_name_not_available( assert isinstance(errors[0], CaseNameNotAvailableError) -def test_case_internal_ids_does_not_exist( - valid_order: OrderWithCases, - store_with_multiple_cases_and_samples: Store, -): - - # GIVEN an order with a case marked as existing but which does not exist in the database - existing_case = ExistingCase(internal_id="Non-existent case", panels=[GenePanelMasterList.AID]) - valid_order.cases.append(existing_case) - - # WHEN validating that the internal ids match existing cases - errors: list[CaseDoesNotExistError] = validate_case_internal_ids_exist( - order=valid_order, - store=store_with_multiple_cases_and_samples, - ) - - # THEN an error should be returned - assert errors - - # THEN the error should concern the non-existent case - assert isinstance(errors[0], CaseDoesNotExistError) - - def test_repeated_case_names_not_allowed(order_with_repeated_case_names: OrderWithCases): # GIVEN an order with cases with the same name @@ -132,39 +102,6 @@ def test_multiple_samples_in_case(rnafusion_order: RNAFusionOrder): assert errors[0].case_index == 0 -def test_case_outside_of_collaboration( - mip_dna_order: MIPDNAOrder, store_with_multiple_cases_and_samples: Store -): - - # GIVEN a customer from outside the order's customer's collaboration - new_customer = store_with_multiple_cases_and_samples.add_customer( - internal_id="NewCustomer", - name="New customer", - invoice_address="Test street", - invoice_reference="Invoice reference", - ) - store_with_multiple_cases_and_samples.add_item_to_store(new_customer) - store_with_multiple_cases_and_samples.commit_to_store() - - # GIVEN a case belonging to the customer is added to the order - existing_cases: list[Case] = store_with_multiple_cases_and_samples.get_cases() - case = existing_cases[0] - case.customer = new_customer - existing_case = ExistingCase(internal_id=case.internal_id, panels=case.panels) - mip_dna_order.cases.append(existing_case) - - # WHEN validating that the order does not contain cases from outside the customer's collaboration - errors: list[CaseOutsideOfCollaborationError] = validate_existing_cases_belong_to_collaboration( - order=mip_dna_order, store=store_with_multiple_cases_and_samples - ) - - # THEN an error should be returned - assert errors - - # THEN the error should concern the added existing case - assert isinstance(errors[0], CaseOutsideOfCollaborationError) - - def test_new_case_without_affected_samples(mip_dna_order: MIPDNAOrder): """Tests that an error is returned if a new case does not contain any affected samples.""" @@ -184,35 +121,6 @@ def test_new_case_without_affected_samples(mip_dna_order: MIPDNAOrder): assert errors[0].case_index == 0 -def test_existing_case_without_affected_samples( - mip_dna_order: MIPDNAOrder, - store_with_multiple_cases_and_samples: Store, - case_id_with_single_sample: str, -): - """Tests that an error is returned if an existing case does not contain any affected samples.""" - - # GIVEN an order containing an existing case without any affected samples - db_case: Case = store_with_multiple_cases_and_samples.get_case_by_internal_id( - case_id_with_single_sample - ) - assert all(link.status != StatusEnum.affected for link in db_case.links) - existing_case = ExistingCase(internal_id=db_case.internal_id, panels=db_case.panels) - mip_dna_order.cases.append(existing_case) - - # WHEN validating that each case contains at least one affected sample - errors: list[ExistingCaseWithoutAffectedSampleError] = ( - validate_existing_cases_have_an_affected_sample( - order=mip_dna_order, store=store_with_multiple_cases_and_samples - ) - ) - - # THEN an error should be returned - assert errors - - # THEN the error should concern the first case - assert errors[0].case_index == mip_dna_order.cases.index(existing_case) - - def test_case_samples_multiple_prep_categories( mip_dna_order: MIPDNAOrder, store_to_submit_and_validate_orders: Store, diff --git a/tests/services/orders/validation_service/test_case_sample_rules.py b/tests/services/orders/validation_service/test_case_sample_rules.py index 7d1e2e1d002..6667dfe4a7d 100644 --- a/tests/services/orders/validation_service/test_case_sample_rules.py +++ b/tests/services/orders/validation_service/test_case_sample_rules.py @@ -35,7 +35,6 @@ WellFormatError, WellPositionMissingError, ) -from cg.services.orders.validation.models.existing_case import ExistingCase from cg.services.orders.validation.models.existing_sample import ExistingSample from cg.services.orders.validation.models.order_with_cases import OrderWithCases from cg.services.orders.validation.order_types.mip_dna.models.order import MIPDNAOrder @@ -538,32 +537,7 @@ def test_validate_sample_names_different_from_case_names( assert errors[1].sample_index == 1 -def test_validate_sample_names_different_from_existing_case_names( - valid_order: TomteOrder, store_with_multiple_cases_and_samples: Store -): - # GIVEN an order with a case holding samples with the same name as an existing case in the order - case = store_with_multiple_cases_and_samples.get_cases()[0] - existing_case = ExistingCase(internal_id=case.internal_id, panels=case.panels) - valid_order.cases.append(existing_case) - valid_order.cases[0].samples[0].name = case.name - - # WHEN validating that the sample names are different from the case names - errors: list[SampleNameSameAsCaseNameError] = validate_sample_names_different_from_case_names( - order=valid_order, store=store_with_multiple_cases_and_samples - ) - - # THEN a list with one error should be returned - assert len(errors) == 1 - - # THEN the errors should concern the same case and sample name and hold the correct indices - error = errors[0] - assert isinstance(error, SampleNameSameAsCaseNameError) - assert error.case_index == 0 - assert error.sample_index == 0 - - def test_validate_not_all_samples_unknown_in_case(valid_order: OrderWithCases): - # GIVEN an order with a case with all samples unknown for sample in valid_order.cases[0].samples: sample.status = StatusEnum.unknown