From a7031ffc850607a53f3ef507c719d4fa8d2a40d1 Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Thu, 14 Dec 2023 15:16:14 +0000 Subject: [PATCH] Revert "Revert "DPL-960- Bioscan input plate state changes"" --- app/models/plate_purpose/input_started.rb | 36 +++++++++++++++++ app/models/receptacle.rb | 2 +- .../state_changer/input_started_plate.rb | 26 +++++++++++++ app/models/transfer/state.rb | 39 +++++++++++++++++++ .../plate_purposes/009_bioscan_purposes.yml | 2 +- 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 app/models/plate_purpose/input_started.rb create mode 100644 app/models/state_changer/input_started_plate.rb diff --git a/app/models/plate_purpose/input_started.rb b/app/models/plate_purpose/input_started.rb new file mode 100644 index 0000000000..9b00386d98 --- /dev/null +++ b/app/models/plate_purpose/input_started.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# Input Plate purposes are the initial stock plates passing into +# external piplines. They have special behaviour governing their state. +# This essentially makes sure that all non-empty wells on a plate have requests +# out of them. This is intended to ensure that submissions have been +# correctly built before a plate has processed. +# +# - Input plates are progressed when all sample containing wells have requests out of them +# +# This version of the input class sets the state as started rather than passed. +class PlatePurpose::InputStarted < PlatePurpose::Input + self.state_changer = StateChanger::InputStartedPlate + + UNREADY_STATE = 'pending' + PREP_STATE = 'started' + READY_STATE = 'passed' + + private + + # The state of the plate is determined by the state of the wells + # In this version we add an extra state PREP_STATE + def calculate_state_of_plate(wells_states) + unique_states = wells_states.uniq + return UNREADY_STATE if unique_states.include?(:unready) + + case unique_states.sort + when ['failed'], %w[cancelled failed] + 'failed' + when ['cancelled'] + 'cancelled' + else + unique_states.all?('pending') ? PREP_STATE : READY_STATE + end + end +end diff --git a/app/models/receptacle.rb b/app/models/receptacle.rb index 4a6848ab46..44429bef48 100644 --- a/app/models/receptacle.rb +++ b/app/models/receptacle.rb @@ -7,7 +7,7 @@ class Receptacle < Asset # rubocop:todo Metrics/ClassLength include Uuid::Uuidable include Commentable - include Transfer::State + include Transfer::State::ReceptacleState include Aliquot::Remover include StudyReport::AssetDetails include Receptacle::DownstreamAliquotsRemoval::Mixin diff --git a/app/models/state_changer/input_started_plate.rb b/app/models/state_changer/input_started_plate.rb new file mode 100644 index 0000000000..1e15c8ef2b --- /dev/null +++ b/app/models/state_changer/input_started_plate.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module StateChanger + # This adds an additional started state to input plates, which is used + # by the input_started plate purpose. + class InputStartedPlate < InputPlate + # Target state of labware to state of associated requests. + # All other transitions will be ignored. + self.map_target_state_to_associated_request_state = { 'failed' => 'failed', 'passed' => 'started' } + + private + + def associated_requests + receptacles.flat_map(&:requests_as_source) + end + + def _receptacles + labware.wells.includes(:requests_as_source) + end + + def transfer_requests + # We don't want to update any transfer requests + [] + end + end +end diff --git a/app/models/transfer/state.rb b/app/models/transfer/state.rb index bd35775706..2039490f31 100644 --- a/app/models/transfer/state.rb +++ b/app/models/transfer/state.rb @@ -30,6 +30,45 @@ def state_from(state_requests) ALL_STATES.detect { |s| unique_states.include?(s) } || default_state || 'unknown' end + # This transfer state is specific to receptacles + # Added so can specifically check the state on certain types of input plates + # and return a default state of passed to allow well failing. + module ReceptacleState + # We have to include this specifically because it does not implicitly include the + # methods from the Transfer::State module. + include Transfer::State + + def default_state + # Well state was 'unknown' without this change for our input plate because input + # plates do not have any transfer requests. + # This check was added specifically for the Bioscan pipeline, where the users wish to be able to fail + # wells at this point in the pipeline, and we need wells to be in state 'passed' for well failing to + # be allowed. + return 'passed' if input_started_plate_with_aliquots? + + nil + end + + private + + def input_started_plate_with_aliquots? + # Had to add labware and purpose checks here as many tests seem to fail otherwise, probably + # due to incomplete factory test data setup (e.g. Well with no Plate, Tube with no purpose) + # which should not happen in reality. + return false unless labware&.purpose + + labware_of_input_started_type? && labware_in_valid_state? && aliquots.present? + end + + def labware_of_input_started_type? + labware.purpose.type == 'PlatePurpose::InputStarted' + end + + def labware_in_valid_state? + %w[started passed].include?(labware.state) + end + end + # Plate specific behaviour module PlateState def self.included(base) # rubocop:todo Metrics/MethodLength diff --git a/config/default_records/plate_purposes/009_bioscan_purposes.yml b/config/default_records/plate_purposes/009_bioscan_purposes.yml index bdf1e20cc8..33c6725b3b 100644 --- a/config/default_records/plate_purposes/009_bioscan_purposes.yml +++ b/config/default_records/plate_purposes/009_bioscan_purposes.yml @@ -9,7 +9,7 @@ # This is the insects in ethanol plate for the start of the Lysate pipeline # It is the original source plate that has come in from the suppliers around the UK. LILYS-96 Stock: - type: PlatePurpose::Input + type: PlatePurpose::InputStarted stock_plate: true cherrypickable_target: false # This is the lysed material plate, ready for the Library prep pipeline. These plates