From f58aef57bdfe4a17688bdb87da0ab781dd9cd7fd Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:02:20 +0100 Subject: [PATCH] LIMS-1099: Limit what can be done with red proteins (#827) * LIMS-1099: Limit what can be done with red proteins * LIMS-1099: Revert shipment safety level if new value is not valid --------- Co-authored-by: Mark Williams --- api/src/Page/Sample.php | 17 +++++--- api/src/Page/Shipment.php | 39 ++++++++++++++++++- client/src/js/models/shipment.js | 5 +++ .../src/js/modules/shipment/views/shipment.js | 2 +- .../js/modules/shipment/views/shipmentadd.js | 19 ++++++++- .../mx/shipment/views/container-mixin.js | 2 +- .../mx/shipment/views/mx-container-add.vue | 23 +++-------- .../mx/shipment/views/mx-container-view.vue | 39 +++++++++++-------- .../js/templates/shipment/shipmentadd.html | 14 ++++--- client/src/js/utils/editable.js | 9 ++++- 10 files changed, 117 insertions(+), 52 deletions(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index eb8850184..d1a051493 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -1819,12 +1819,17 @@ function _distinct_proteins() $where .= ' AND pr.externalid IS NOT NULL'; } - $has_safety_level = $this->has_arg('SAFETYLEVEL'); - if ($has_safety_level && $this->arg('SAFETYLEVEL') === 'ALL') { - $where .= ' AND pr.safetyLevel IS NOT NULL'; - } else if ($has_safety_level && $this->arg('SAFETYLEVEL') !== 'ALL') { - $where .= ' AND pr.safetyLevel=:' . (sizeof($args) + 1); - array_push($args, $this->arg('SAFETYLEVEL')); + if ($this->has_arg('SAFETYLEVEL')) { + if ($this->arg('SAFETYLEVEL') === 'ALL') { + $where .= ' AND pr.safetyLevel IS NOT NULL'; + } else if (strtolower($this->arg('SAFETYLEVEL')) === 'yellow') { + $where .= " AND pr.safetyLevel in ('green','yellow')"; + } else if (strtolower($this->arg('SAFETYLEVEL')) === 'red') { + $where .= " AND pr.safetyLevel in ('green','yellow','red')"; + } else { + $where .= ' AND pr.safetyLevel=:' . (sizeof($args) + 1); + array_push($args, $this->arg('SAFETYLEVEL')); + } } if ($this->has_arg('term')) { diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 6b3f16f4a..fdb44b725 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1626,7 +1626,7 @@ function _get_dewars() $order = $cols[$this->arg('sort_by')] . ' ' . $dir; } - $dewars = $this->db->paginate("SELECT CONCAT(p.proposalcode, p.proposalnumber) as prop, CONCAT(p.proposalcode, p.proposalnumber, '-', se.visit_number) as firstexperiment, r.labcontactid, se.beamlineoperator as localcontact, se.beamlinename, TO_CHAR(se.startdate, 'HH24:MI DD-MM-YYYY') as firstexperimentst, d.firstexperimentid, s.shippingid, s.shippingname, d.facilitycode, count(c.containerid) as ccount, (case when se.visit_number > 0 then (CONCAT(p.proposalcode, p.proposalnumber, '-', se.visit_number)) else '' end) as exp, d.code, d.barcode, d.storagelocation, d.dewarstatus, d.dewarid, d.trackingnumbertosynchrotron, d.trackingnumberfromsynchrotron, d.externalShippingIdFromSynchrotron, s.deliveryagent_agentname, d.weight, d.deliveryagent_barcode, GROUP_CONCAT(c.code SEPARATOR ', ') as containers, s.sendinglabcontactid, s.returnlabcontactid, pe.givenname, pe.familyname + $dewars = $this->db->paginate("SELECT CONCAT(p.proposalcode, p.proposalnumber) as prop, CONCAT(p.proposalcode, p.proposalnumber, '-', se.visit_number) as firstexperiment, r.labcontactid, se.beamlineoperator as localcontact, se.beamlinename, TO_CHAR(se.startdate, 'HH24:MI DD-MM-YYYY') as firstexperimentst, d.firstexperimentid, s.shippingid, s.shippingname, d.facilitycode, count(c.containerid) as ccount, (case when se.visit_number > 0 then (CONCAT(p.proposalcode, p.proposalnumber, '-', se.visit_number)) else '' end) as exp, d.code, d.barcode, d.storagelocation, d.dewarstatus, d.dewarid, d.trackingnumbertosynchrotron, d.trackingnumberfromsynchrotron, d.externalShippingIdFromSynchrotron, s.deliveryagent_agentname, d.weight, d.deliveryagent_barcode, GROUP_CONCAT(c.code SEPARATOR ', ') as containers, s.sendinglabcontactid, s.returnlabcontactid, pe.givenname, pe.familyname, s.safetylevel as shippingsafetylevel FROM dewar d LEFT OUTER JOIN container c ON c.dewarid = d.dewarid INNER JOIN shipping s ON d.shippingid = s.shippingid @@ -1700,6 +1700,39 @@ function _add_dewar() $this->_output(array('DEWARID' => $id)); } + # Check new safety level is ok + function _check_safety_level() + { + $ship = $this->db->pq("SELECT cq.containerqueueid, p.safetylevel FROM dewar d + LEFT OUTER JOIN container c on c.dewarid = d.dewarid + LEFT OUTER JOIN containerqueue cq on cq.containerid = c.containerid + LEFT OUTER JOIN blsample b ON b.containerid = c.containerid + LEFT OUTER JOIN crystal cr ON cr.crystalid = b.crystalid + LEFT OUTER JOIN protein p ON p.proteinid = cr.proteinid + WHERE d.shippingid = :1", array($this->arg('sid'))); + if (strtolower($this->arg('SAFETYLEVEL')) == 'green') { + foreach ($ship as $s) { + if (strtolower($s['SAFETYLEVEL']) == 'yellow') + $this->_error('Cannot set safety level to green as one or more samples are yellow.'); + if (strtolower($s['SAFETYLEVEL']) == 'red') + $this->_error('Cannot set safety level to green as one or more samples are red.'); + } + } else if (strtolower($this->arg('SAFETYLEVEL')) == 'yellow') { + foreach ($ship as $s) { + if ($s['CONTAINERQUEUEID'] != null) + $this->_error('Cannot set safety level to yellow as one or more containers are queued.'); + if (strtolower($s['SAFETYLEVEL']) == 'red') + $this->_error('Cannot set safety level to yellow as one or more samples are red.'); + } + } else { + foreach ($ship as $s) { + if ($s['CONTAINERQUEUEID'] != null) + $this->_error('Cannot set safety level to red as one or more containers are queued.'); + } + } + } + + # Update shipment function _update_shipment() { @@ -1721,6 +1754,9 @@ function _update_shipment() if (in_array($f, array('DELIVERYAGENT_DELIVERYDATE', 'DELIVERYAGENT_SHIPPINGDATE'))) { $fl = "TO_DATE(:1, 'DD-MM-YYYY')"; } + if ($f == 'SAFETYLEVEL') { + $this->_check_safety_level(); + } $this->db->pq("UPDATE shipping SET $f=$fl WHERE shippingid=:2", array($this->arg($f), $this->arg('sid'))); @@ -2120,6 +2156,7 @@ function _get_all_containers() } // $this->db->set_debug(True); $rows = $this->db->paginate("SELECT round(TIMESTAMPDIFF('HOUR', min(ci.bltimestamp), CURRENT_TIMESTAMP)/24,1) as age, case when count(ci2.containerinspectionid) > 1 then 0 else 1 end as allow_adhoc, c.scheduleid, c.screenid, sc.name as screen, c.imagerid, i.temperature as temperature, i.name as imager, TO_CHAR(max(ci.bltimestamp), 'HH24:MI DD-MM-YYYY') as lastinspection, round(TIMESTAMPDIFF('HOUR', max(ci.bltimestamp), CURRENT_TIMESTAMP)/24,1) as lastinspectiondays, count(distinct ci.containerinspectionid) as inspections, CONCAT(p.proposalcode, p.proposalnumber) as prop, c.bltimestamp, c.samplechangerlocation, c.beamlinelocation, d.dewarstatus, c.containertype, c.capacity, c.containerstatus, c.containerid, c.code as name, d.code as dewar, sh.shippingname as shipment, d.dewarid, sh.shippingid, count(distinct s.blsampleid) as samples, cq.containerqueueid, TO_CHAR(cq.createdtimestamp, 'DD-MM-YYYY HH24:MI') as queuedtimestamp, CONCAT(p.proposalcode, p.proposalnumber, '-', ses.visit_number) as visit, ses.beamlinename, c.requestedreturn, c.requestedimagerid, c.comments, c.experimenttype, c.storagetemperature, c.barcode, reg.barcode as registry, reg.containerregistryid, + sh.safetylevel as shippingsafetylevel, (SELECT sch.name FROM schedule sch WHERE sch.scheduleid = c.scheduleid) as schedule, (SELECT i2.name FROM imager i2 WHERE i2.imagerid = c.requestedimagerid) as requestedimager, (SELECT count(distinct ss.blsubsampleid) FROM blsubsample ss RIGHT OUTER JOIN blsample s2 ON s2.blsampleid = ss.blsampleid WHERE s2.containerid = c.containerid AND ss.source='manual') as subsamples, diff --git a/client/src/js/models/shipment.js b/client/src/js/models/shipment.js index e1849b9bf..eccef8c27 100644 --- a/client/src/js/models/shipment.js +++ b/client/src/js/models/shipment.js @@ -18,6 +18,11 @@ define(['backbone'], function (Backbone) { pattern: 'fcode', }, + FIRSTEXPERIMENTID: { + required: false, + pattern: 'number', + }, + REMOTEORMAILIN: { required: false, }, diff --git a/client/src/js/modules/shipment/views/shipment.js b/client/src/js/modules/shipment/views/shipment.js index 6932f8645..bbde464f3 100644 --- a/client/src/js/modules/shipment/views/shipment.js +++ b/client/src/js/modules/shipment/views/shipment.js @@ -200,7 +200,7 @@ define(['marionette', var edit = new Editable({ model: this.model, el: this.$el }) edit.create('SHIPPINGNAME', 'textlong') - edit.create('SAFETYLEVEL', 'select', { data: {'Green': 'Green', 'Yellow':'Yellow', 'Red': 'Red'} }) + edit.create('SAFETYLEVEL', 'select', { data: {'Green': 'Green', 'Yellow':'Yellow', 'Red': 'Red'}, alert: true, revert: true }) edit.create('COMMENTS', 'textarea') edit.create('DELIVERYAGENT_AGENTNAME', 'text') edit.create('DELIVERYAGENT_AGENTCODE', 'text') diff --git a/client/src/js/modules/shipment/views/shipmentadd.js b/client/src/js/modules/shipment/views/shipmentadd.js index a5fea270b..602e33487 100644 --- a/client/src/js/modules/shipment/views/shipmentadd.js +++ b/client/src/js/modules/shipment/views/shipmentadd.js @@ -63,6 +63,7 @@ define(['marionette', 'views/form', 'click a.add_lc': 'addLC', 'click @ui.noexp': 'updateFirstExp', 'click @ui.dynamic': 'updateDynamicSchedule', + 'change select[name^=SAFETYLEVEL]': 'changeSafetyLevel', }, ui: { @@ -73,6 +74,8 @@ define(['marionette', 'views/form', noexp: 'input[name=noexp]', dynamic: 'input[name=DYNAMIC]', // A checkbox to indicate dynamic/remote mail-in scheduling comments: 'textarea[name=COMMENTS]', // We need this so we can prefill comments to aid users + safetylevel: 'select[name^=SAFETYLEVEL]', + udcresponsive: '.udcresponsive', }, addLC: function(e) { @@ -97,12 +100,24 @@ define(['marionette', 'views/form', app.alert({ message: 'Something went wrong registering this shipment, please try again'}) }, + changeSafetyLevel: function() { + if (this.ui.safetylevel.val() === 'Green') { + this.ui.udcresponsive.show() + } else { + this.ui.noexp.prop('checked', false) + this.updateFirstExp() + this.ui.dynamic.prop('checked', false) + this.updateDynamicSchedule() + this.ui.udcresponsive.hide() + } + }, + updateFirstExp: function() { if (this.ui.noexp.is(':checked')) { this.ui.first.html('') this.ui.dynamic.prop('checked', false) } else { - this.ui.first.html(this.visits.opts()) + this.ui.first.html(''+this.visits.opts()) } }, @@ -121,7 +136,7 @@ define(['marionette', 'views/form', } } else { this.model.validation.REMOTEORMAILIN.required = false - this.ui.first.html(this.visits.opts()) + this.ui.first.html(''+this.visits.opts()) this.$el.find(".remoteform").hide() if (industrial_visit) { this.$el.find(".remoteormailin").hide() diff --git a/client/src/js/modules/types/mx/shipment/views/container-mixin.js b/client/src/js/modules/types/mx/shipment/views/container-mixin.js index f622ac683..e29c60b4d 100644 --- a/client/src/js/modules/types/mx/shipment/views/container-mixin.js +++ b/client/src/js/modules/types/mx/shipment/views/container-mixin.js @@ -128,7 +128,7 @@ export default { proteinsCollection.queryParams.external = 1 } - proteinsCollection.queryParams.SAFETYLEVEL = 'ALL' + proteinsCollection.queryParams.SAFETYLEVEL = this.shippingSafetyLevel const result = await this.$store.dispatch('getCollection', proteinsCollection) this.proteins = result.toJSON() diff --git a/client/src/js/modules/types/mx/shipment/views/mx-container-add.vue b/client/src/js/modules/types/mx/shipment/views/mx-container-add.vue index f08642bef..9f2e59ee1 100644 --- a/client/src/js/modules/types/mx/shipment/views/mx-container-add.vue +++ b/client/src/js/modules/types/mx/shipment/views/mx-container-add.vue @@ -90,9 +90,13 @@
+ + Cannot queue containers in {{ shippingSafetyLevel }} shipments +
@@ -461,6 +465,7 @@ export default { // The dewar that this container will belong to dewar: null, + shippingSafetyLevel: null, processingPipeline: '', processingPipelines: [], @@ -542,23 +547,6 @@ export default { } } }, - AUTOMATED: { - immediate: true, - handler: function(newVal) { - const proteinsCollection = new DistinctProteins() - // If now on, add safety level to query - // Automated collections limited to GREEN Low risk samples - if (newVal) { - proteinsCollection.queryParams.SAFETYLEVEL = 'GREEN'; - } else { - proteinsCollection.queryParams.SAFETYLEVEL = 'ALL'; - } - this.$store.dispatch('getCollection', proteinsCollection).then( (result) => { - this.proteins = result.toJSON() - }) - app.trigger('samples:automated', newVal) - } - }, CONTAINERREGISTRYID: { immediate: true, handler: function(newVal) { @@ -593,6 +581,7 @@ export default { created: function() { this.containerType = INITIAL_CONTAINER_TYPE this.dewar = this.options.dewar.toJSON() + this.shippingSafetyLevel = this.dewar.SHIPPINGSAFETYLEVEL this.DEWARID = this.dewar.DEWARID this.resetSamples(this.containerType.CAPACITY) diff --git a/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue b/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue index 20c5b46ac..9de2394dc 100644 --- a/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue +++ b/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue @@ -106,24 +106,25 @@ @click="onUnQueueContainer" > Unqueue + + Cannot queue containers in {{ shippingSafetyLevel }} shipments + + + There was an error submitting the container to the queue. Please fix any errors in the samples table. + Try again + Cancel + - - There was an error submitting the container to the queue. Please fix any errors in the samples table. - Try again - Cancel - - - Queue this container for Auto Collect - + Queue this container for Auto Collect @@ -324,6 +325,7 @@ export default { dewarsCollection: null, selectedDewarId: null, selectedShipmentId: null, + shippingSafetyLevel: null, editingSampleLocation: null } }, @@ -354,6 +356,7 @@ export default { loadContainerData() { this.container = Object.assign({}, this.containerModel.toJSON()) this.containerId = this.containerModel.get('CONTAINERID') + this.shippingSafetyLevel = this.containerModel.get('SHIPPINGSAFETYLEVEL') this.containerQueueId = this.containerModel.get('CONTAINERQUEUEID') if (this.containerQueueId) this.QUEUEFORUDC = true }, @@ -477,6 +480,7 @@ export default { }) this.$nextTick(() => { this.loadContainerData() + this.getProteins() // TODO: Toggle Auto in the samples table }) } catch (error) { @@ -497,6 +501,7 @@ export default { this.$emit('update-container-state', { CONTAINERQUEUEID: null }) this.$nextTick(() => { this.loadContainerData() + this.getProteins() // TODO: Toggle Auto in the samples table }) } catch (error) { diff --git a/client/src/js/templates/shipment/shipmentadd.html b/client/src/js/templates/shipment/shipmentadd.html index 3866fe012..927b68a39 100644 --- a/client/src/js/templates/shipment/shipmentadd.html +++ b/client/src/js/templates/shipment/shipmentadd.html @@ -52,12 +52,14 @@

Add New Shipment

- - + + + +
diff --git a/client/src/js/utils/editable.js b/client/src/js/utils/editable.js index 3515c210d..622779b54 100644 --- a/client/src/js/utils/editable.js +++ b/client/src/js/utils/editable.js @@ -158,10 +158,13 @@ define(['marionette', */ create: function(attr, type, options, refetch) { var submit = function(value, settings) { + prevValue = this.model.get(attr) this.model.set(attr, value) console.log('valid', this.model.isValid(true), attr, 'changed', this.model.changedAttributes()) var self = this + toReturn = refetch ? '' : _.escape(value) this.model.save(this.model.changedAttributes(), { patch: true, validate: false, + async: !(options && options.revert), success: function() { if (refetch) self.model.fetch() }, @@ -177,10 +180,14 @@ define(['marionette', if (json.message) app.alert({ message: json.message }) else app.alert({ message: 'Something went wrong' }) } + if(options && options.revert) { + self.model.set(attr, prevValue) + toReturn = prevValue + } } }) - return refetch ? '' : _.escape(value) + return toReturn } var onsubmit = function(settings, td) {