From 208010943aeda8e64da435e5a32a309d16920bbb Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:06:39 +0200 Subject: [PATCH 01/56] allow container assigment by staff from prop + beamline as well as visit --- api/src/Page/Assign.php | 70 +++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/api/src/Page/Assign.php b/api/src/Page/Assign.php index 608295afe..62e8ede2c 100644 --- a/api/src/Page/Assign.php +++ b/api/src/Page/Assign.php @@ -23,54 +23,92 @@ class Assign extends Page # ------------------------------------------------------------------------ # Assign a container function _assign() { - if (!$this->has_arg('visit')) $this->_error('No visit specified'); + if (!$this->has_arg('visit') && !$this->has_arg('prop')) $this->_error('No visit or prop specified'); if (!$this->has_arg('cid')) $this->_error('No container id specified'); if (!$this->has_arg('pos')) $this->_error('No position specified'); - + + $where = 'c.containerid=:1'; + $args = array($this->arg('cid')); + + if ($this->has_arg('visit')) { + $where .= " AND CONCAT(p.proposalcode, p.proposalnumber, '-', bl.visit_number) LIKE :".(sizeof($args)+1); + array_push($args, $this->arg('visit')); + } else { + $where .= " AND CONCAT(p.proposalcode, p.proposalnumber) LIKE :".(sizeof($args)+1); + array_push($args, $this->arg('prop')); + } + $cs = $this->db->pq("SELECT d.dewarid,bl.beamlinename,c.containerid,c.code FROM container c INNER JOIN dewar d ON d.dewarid = c.dewarid INNER JOIN shipping s ON s.shippingid = d.shippingid INNER JOIN blsession bl ON bl.proposalid = s.proposalid INNER JOIN proposal p ON s.proposalid = p.proposalid - WHERE CONCAT(CONCAT(CONCAT(p.proposalcode, p.proposalnumber), '-'), bl.visit_number) LIKE :1 AND c.containerid=:2", array($this->arg('visit'), $this->arg('cid'))); + WHERE $where", $args); if (sizeof($cs) > 0) { $c = $cs[0]; + + $bl = $c['BEAMLINENAME']; + if ($this->staff) { + if ($this->has_arg('bl')) { + $bl = $this->arg('bl'); + } + } + $this->db->pq("UPDATE dewar SET dewarstatus='processing' WHERE dewarid=:1", array($c['DEWARID'])); - $this->db->pq("UPDATE container SET beamlinelocation=:1,samplechangerlocation=:2,containerstatus='processing' WHERE containerid=:3", array($c['BEAMLINENAME'], $this->arg('pos'), $c['CONTAINERID'])); - $this->db->pq("INSERT INTO containerhistory (containerid,status,location,beamlinename) VALUES (:1,:2,:3,:4)", array($c['CONTAINERID'], 'processing', $this->arg('pos'), $c['BEAMLINENAME'])); - $this->_update_history($c['DEWARID'], 'processing', $c['BEAMLINENAME'], $c['CODE'].' => '.$this->arg('pos')); + $this->db->pq("UPDATE container SET beamlinelocation=:1,samplechangerlocation=:2,containerstatus='processing' WHERE containerid=:3", array($bl, $this->arg('pos'), $c['CONTAINERID'])); + $this->db->pq("INSERT INTO containerhistory (containerid,status,location,beamlinename) VALUES (:1,:2,:3,:4)", array($c['CONTAINERID'], 'processing', $this->arg('pos'), $bl)); + $this->_update_history($c['DEWARID'], 'processing', $bl, $c['CODE'].' => '.$this->arg('pos')); $this->_output(1); + } else { + $this->_error('No such container'); } - - $this->_output(0); } # ------------------------------------------------------------------------ # Unassign a container function _unassign() { - if (!$this->has_arg('visit')) $this->_error('No visit specified'); + if (!$this->has_arg('visit') && !$this->has_arg('prop')) $this->_error('No visit or prop specified'); if (!$this->has_arg('cid')) $this->_error('No container id specified'); - + + $where = 'c.containerid=:1'; + $args = array($this->arg('cid')); + + if ($this->has_arg('visit')) { + $where .= " AND CONCAT(p.proposalcode, p.proposalnumber, '-', bl.visit_number) LIKE :".(sizeof($args)+1); + array_push($args, $this->arg('visit')); + } else { + $where .= " AND CONCAT(p.proposalcode, p.proposalnumber) LIKE :".(sizeof($args)+1); + array_push($args, $this->arg('prop')); + } + $cs = $this->db->pq("SELECT d.dewarid,bl.beamlinename,c.containerid FROM container c INNER JOIN dewar d ON d.dewarid = c.dewarid INNER JOIN shipping s ON s.shippingid = d.shippingid INNER JOIN blsession bl ON bl.proposalid = s.proposalid INNER JOIN proposal p ON s.proposalid = p.proposalid - WHERE CONCAT(CONCAT(CONCAT(p.proposalcode, p.proposalnumber), '-'), bl.visit_number) LIKE :1 AND c.containerid=:2", array($this->arg('visit'), $this->arg('cid'))); + WHERE $where", $args); if (sizeof($cs) > 0) { $c = $cs[0]; + + $bl = $c['BEAMLINENAME']; + if ($this->staff) { + if ($this->has_arg('bl')) { + $bl = $this->arg('bl'); + } + } $this->db->pq("UPDATE container SET samplechangerlocation='',beamlinelocation='',containerstatus='at facility' WHERE containerid=:1",array($c['CONTAINERID'])); - $this->db->pq("INSERT INTO containerhistory (containerid,status,beamlinename) VALUES (:1,:2,:3)", array($c['CONTAINERID'], 'at facility', $c['BEAMLINENAME'])); + $this->db->pq("INSERT INTO containerhistory (containerid,status,beamlinename) VALUES (:1,:2,:3)", array($c['CONTAINERID'], 'at facility', $bl)); //$this->_update_history($c['DEWARID'], 'unprocessing'); $this->_output(1); + } else { + $this->_error('No such container'); } - $this->_output(0); } @@ -102,10 +140,10 @@ function _deactivate() { $this->db->pq("UPDATE container SET containerstatus='at facility', samplechangerlocation='', beamlinelocation='' WHERE containerid=:1", array($c['ID'])); $this->db->pq("INSERT INTO containerhistory (containerid,status) VALUES (:1,:2)", array($c['ID'], 'at facility')); } - $this->_output(1); - + $this->_output(1); + } else { + $this->_error('No such dewar'); } - $this->_output(0); } From 970b40277795a6c9c3c61f41558a5340434fd387 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:07:46 +0200 Subject: [PATCH 02/56] allow filtering containers by shipment, and by registry --- api/src/Page/Shipment.php | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 39eb681e9..2f5eaaef8 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -87,6 +87,7 @@ class Shipment extends Page 'unassigned' => '[\w-]+', // Container fields + 'REGISTRY' => '([\w-])+', 'DEWARID' => '\d+', 'CAPACITY' => '\d+', 'CONTAINERTYPE' => '\w+', @@ -1332,6 +1333,11 @@ function _get_all_containers() { $where .= " AND c.containertype LIKE '%puck'"; } + # For a specific shipment + if ($this->has_arg('SHIPPINGID')) { + $where .= ' AND sh.shippingid=:'.(sizeof($args)+1); + array_push($args, $this->arg('SHIPPINGID')); + } if ($this->has_arg('did')) { $where .= ' AND d.dewarid=:'.(sizeof($args)+1); @@ -1381,6 +1387,11 @@ function _get_all_containers() { array_push($args, $this->arg('CONTAINERREGISTRYID')); } + if ($this->has_arg('REGISTRY')) { + $where .= ' AND reg.barcode = :'.(sizeof($args)+1); + array_push($args, $this->arg('REGISTRY')); + } + if ($this->has_arg('currentuser')) { $where .= ' AND c.ownerid = :'.(sizeof($args)+1); array_push($args, $this->user->personid); @@ -1398,6 +1409,7 @@ function _get_all_containers() { LEFT OUTER JOIN containerinspection ci ON ci.containerid = c.containerid AND ci.state = 'Completed' LEFT OUTER JOIN containerqueue cq ON cq.containerid = c.containerid AND cq.completedtimestamp IS NULL LEFT OUTER JOIN containerqueue cq2 ON cq2.containerid = c.containerid AND cq2.completedtimestamp IS NOT NULL + LEFT OUTER JOIN containerregistry reg ON reg.containerregistryid = c.containerregistryid $join WHERE $where $having", $args); @@ -1436,7 +1448,8 @@ function _get_all_containers() { if ($this->has_arg('sort_by')) { $cols = array('NAME' => 'c.code', 'DEWAR' => 'd.code', 'SHIPMENT' => 'sh.shippingname', 'SAMPLES' => 'count(s.blsampleid)', 'SHIPPINGID' =>'sh.shippingid', 'LASTINSPECTION' => 'max(ci.bltimestamp)', 'INSPECTIONS' => 'count(ci.containerinspectionid)', 'DCCOUNT' => 'COUNT(distinct dc.datacollectionid)', 'SUBSAMPLES' => 'count(distinct ss.blsubsampleid)', - 'LASTQUEUECOMPLETED' => 'max(cq2.completedtimestamp)', 'QUEUEDTIMESTAMP' => 'max(cq.createdtimestamp)' + 'LASTQUEUECOMPLETED' => 'max(cq2.completedtimestamp)', 'QUEUEDTIMESTAMP' => 'max(cq.createdtimestamp)', + 'BLTIMESTAMP' => 'c.bltimestamp' ); $dir = $this->has_arg('order') ? ($this->arg('order') == 'asc' ? 'ASC' : 'DESC') : 'ASC'; if (array_key_exists($this->arg('sort_by'), $cols)) $order = $cols[$this->arg('sort_by')].' '.$dir; @@ -1446,8 +1459,7 @@ function _get_all_containers() { count(distinct ss.blsubsampleid) as subsamples, ses3.beamlinename as firstexperimentbeamline, pp.name as pipeline, - TO_CHAR(max(cq2.completedtimestamp), 'HH24:MI DD-MM-YYYY') as lastqueuecompleted, TIMESTAMPDIFF('MINUTE', max(cq2.completedtimestamp), max(cq2.createdtimestamp)) as lastqueuedwell, - c.ownerid, CONCAT(pe.givenname, ' ', pe.familyname) as owner + TO_CHAR(max(cq2.completedtimestamp), 'HH24:MI DD-MM-YYYY') as lastqueuecompleted, TIMESTAMPDIFF('MINUTE', max(cq2.completedtimestamp), max(cq2.createdtimestamp)) as lastqueuedwell, c.ownerid, concat(pe.givenname, ' ', pe.familyname) as owner FROM container c INNER JOIN dewar d ON d.dewarid = c.dewarid LEFT OUTER JOIN blsession ses3 ON d.firstexperimentid = ses3.sessionid @@ -1791,7 +1803,9 @@ function _container_registry() { FROM containerregistry r LEFT OUTER JOIN containerregistry_has_proposal rhp on rhp.containerregistryid = r.containerregistryid LEFT OUTER JOIN proposal p ON p.proposalid = rhp.proposalid - WHERE $where", $args); + LEFT OUTER JOIN container c ON c.containerregistryid = r.containerregistryid + WHERE $where + GROUP BY r.containerregistryid", $args); $tot = intval($tot[0]['TOT']); $pp = $this->has_arg('per_page') ? $this->arg('per_page') : 15; @@ -1816,7 +1830,7 @@ function _container_registry() { } $rows = $this->db->paginate("SELECT r.containerregistryid, r.barcode, GROUP_CONCAT(distinct CONCAT(p.proposalcode,p.proposalnumber) SEPARATOR ', ') as proposals, count(distinct c.containerid) as instances, TO_CHAR(r.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp, - TO_CHAR(max(c.bltimestamp),'DD-MM-YYYY') as lastuse, max(CONCAT(p.proposalcode,p.proposalnumber)) as prop, r.comments, COUNT(distinct cr.containerreportid) as reports + TO_CHAR(max(c.bltimestamp),'DD-MM-YYYY') as lastuse, max(CONCAT(p.proposalcode,p.proposalnumber)) as prop, r.comments, COUNT(distinct cr.containerreportid) as reports, c.code as lastname FROM containerregistry r LEFT OUTER JOIN containerregistry_has_proposal rhp on rhp.containerregistryid = r.containerregistryid LEFT OUTER JOIN proposal p ON p.proposalid = rhp.proposalid From f58e11e61903edc5b59120f3b0422cc430fac9fe Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:08:28 +0200 Subject: [PATCH 03/56] add diffractionplan columns to sample, get samples by shipment --- api/src/Page/Sample.php | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index efde4053d..fcfb40548 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -118,6 +118,9 @@ class Sample extends Page 'MONOCHROMATOR' => '\w+', 'PRESET' => '\d', 'BEAMLINENAME' => '[\w-]+', + 'AIMEDRESOLUTION' => '\d+(.\d+)?', + 'COLLECTIONMODE' => '\w+', + 'PRIORITY' => '\d+', 'queued' => '\d', 'UNQUEUE' => '\d', @@ -139,6 +142,7 @@ class Sample extends Page 'TYPE' => '\w+', 'BLSAMPLEGROUPSAMPLEID' => '\d+-\d+', 'PLANORDER' => '\d', + 'SHIPPINGID' => '\d+', 'SAMPLEGROUPID' => '\d+', ); @@ -828,6 +832,12 @@ function _samples() { array_push($args, $this->arg('BLSAMPLEGROUPID')); } + # For a specific shipment + if ($this->has_arg('SHIPPINGID')) { + $where .= ' AND s.shippingid=:'.(sizeof($args)+1); + array_push($args, $this->arg('SHIPPINGID')); + } + # For a specific container if ($this->has_arg('cid')) { $where .= ' AND c.containerid=:'.(sizeof($args)+1); @@ -915,6 +925,7 @@ function _samples() { INNER JOIN dewar d ON d.dewarid = c.dewarid LEFT OUTER JOIN datacollection dc ON b.blsampleid = dc.blsampleid LEFT OUTER JOIN robotaction r ON r.blsampleid = b.blsampleid AND r.actiontype = 'LOAD' + INNER JOIN shipping s ON s.shippingid = d.shippingid $join WHERE $where", $args); $tot = intval($tot[0]['TOT']); @@ -946,7 +957,9 @@ function _samples() { $rows = $this->db->paginate("SELECT distinct b.blsampleid, b.crystalid, b.screencomponentgroupid, ssp.blsampleid as parentsampleid, ssp.name as parentsample, b.blsubsampleid, count(distinct si.blsampleimageid) as inspections, CONCAT(p.proposalcode,p.proposalnumber) as prop, b.code, b.location, pr.acronym, pr.proteinid, cr.spacegroup,b.comments,b.name,s.shippingname as shipment,s.shippingid,d.dewarid,d.code as dewar, c.code as container, c.containerid, c.samplechangerlocation as sclocation, count(distinct IF(dc.overlap != 0,dc.datacollectionid,NULL)) as sc, count(distinct IF(dc.overlap = 0 AND dc.axisrange = 0,dc.datacollectionid,NULL)) as gr, count(distinct IF(dc.overlap = 0 AND dc.axisrange > 0,dc.datacollectionid,NULL)) as dc, count(distinct IF(dcg.experimenttype LIKE 'XRF map', dc.datacollectionid, NULL)) as xm, count(distinct IF(dcg.experimenttype LIKE 'XRF spectrum', dc.datacollectionid, NULL)) as xs, count(distinct IF(dcg.experimenttype LIKE 'Energy scan', dc.datacollectionid, NULL)) as es, count(distinct so.screeningid) as ai, count(distinct app.autoprocprogramid) as ap, count(distinct r.robotactionid) as r, round(min(st.rankingresolution),2) as scresolution, max(ssw.completeness) as sccompleteness, round(min(apss.resolutionlimithigh),2) as dcresolution, round(max(apss.completeness),1) as dccompleteness, dp.anomalousscatterer, dp.requiredresolution, cr.cell_a, cr.cell_b, cr.cell_c, cr.cell_alpha, cr.cell_beta, cr.cell_gamma, b.packingfraction, b.dimension1, b.dimension2, b.dimension3, b.shape, cr.theoreticaldensity, cr.name as crystal, pr.name as protein, b.looptype, dp.centringmethod, dp.experimentkind, cq.containerqueueid, TO_CHAR(cq.createdtimestamp, 'DD-MM-YYYY HH24:MI') as queuedtimestamp , $cseq $sseq string_agg(cpr.name) as componentnames, string_agg(cpr.density) as componentdensities - ,string_agg(cpr.proteinid) as componentids, string_agg(cpr.acronym) as componentacronyms, string_agg(cpr.global) as componentglobals, string_agg(chc.abundance) as componentamounts, string_agg(ct.symbol) as componenttypesymbols, b.volume, pct.symbol,ROUND(cr.abundance,3) as abundance, TO_CHAR(b.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp, dp.radiationsensitivity, dp.energy, dp.userpath + ,string_agg(cpr.proteinid) as componentids, string_agg(cpr.acronym) as componentacronyms, string_agg(cpr.global) as componentglobals, string_agg(chc.abundance) as componentamounts, string_agg(ct.symbol) as componenttypesymbols, b.volume, pct.symbol,ROUND(cr.abundance,3) as abundance, TO_CHAR(b.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp, dp.radiationsensitivity, dp.energy, dp.userpath, + dp.aimedresolution, dp.preferredbeamsizex, dp.preferredbeamsizey, dp.exposuretime, dp.axisstart, dp.axisrange, dp.numberofimages, dp.transmission, + GROUP_CONCAT(dcc.comments, ', ') as dccomments FROM blsample b @@ -968,6 +981,7 @@ function _samples() { LEFT OUTER JOIN diffractionplan dp ON dp.diffractionplanid = b.diffractionplanid LEFT OUTER JOIN datacollection dc ON b.blsampleid = dc.blsampleid LEFT OUTER JOIN datacollectiongroup dcg ON dc.datacollectiongroupid = dcg.datacollectiongroupid + LEFT OUTER JOIN datacollectioncomment dcc ON dc.datacollectionid = dcc.datacollectionid LEFT OUTER JOIN screening sc ON dc.datacollectionid = sc.datacollectionid LEFT OUTER JOIN screeningoutput so ON sc.screeningid = so.screeningid @@ -1045,8 +1059,9 @@ function _update_sample_full() { if (array_key_exists('PROTEINID', $a)) { $this->db->pq("UPDATE crystal set spacegroup=:1,proteinid=:2,cell_a=:3,cell_b=:4,cell_c=:5,cell_alpha=:6,cell_beta=:7,cell_gamma=:8,theoreticaldensity=:9 WHERE crystalid=:10", array($a['SPACEGROUP'], $a['PROTEINID'], $a['CELL_A'], $a['CELL_B'], $a['CELL_C'], $a['CELL_ALPHA'], $a['CELL_BETA'], $a['CELL_GAMMA'], $a['THEORETICALDENSITY'], $samp['CRYSTALID'])); - $this->db->pq("UPDATE diffractionplan set anomalousscatterer=:1,requiredresolution=:2, experimentkind=:3, centringmethod=:4, radiationsensitivity=:5, energy=:6, userpath=:7 WHERE diffractionplanid=:8", - array($a['ANOMALOUSSCATTERER'], $a['REQUIREDRESOLUTION'], $a['EXPERIMENTKIND'], $a['CENTRINGMETHOD'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $samp['DIFFRACTIONPLANID'])); + $this->db->pq("UPDATE diffractionplan set anomalousscatterer=:1,requiredresolution=:2, experimentkind=:3, centringmethod=:4, radiationsensitivity=:5, energy=:6, userpath=:7, aimedresolution=:8, preferredbeamsizex=:9, preferredbeamsizey=:10, exposuretime=:11, axisstart=:12, axisrange=:13, numberofimages=:14, transmission=:15 WHERE diffractionplanid=:16", + array($a['ANOMALOUSSCATTERER'], $a['REQUIREDRESOLUTION'], $a['EXPERIMENTKIND'], $a['CENTRINGMETHOD'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['AIMEDRESOLUTION'], $a['PREFERREDBEAMSIZEX'], $a['PREFERREDBEAMSIZEY'], $a['EXPOSURETIME'], $a['AXISSTART'], $a['AXISRANGE'], $a['NUMBEROFIMAGES'], $a['TRANSMISSION'], + $samp['DIFFRACTIONPLANID'])); } $init_comps = explode(',', $samp['COMPONENTIDS']); @@ -1131,12 +1146,12 @@ function _prepare_sample_args($s=null) { if (!$haskey) $this->_error('One or more fields is missing'); - foreach (array('COMMENTS', 'SPACEGROUP', 'CODE', 'ANOMALOUSSCATTERER') as $f) { + foreach (array('COMMENTS', 'SPACEGROUP', 'CODE', 'ANOMALOUSSCATTERER', 'COLLECTIONMODE') as $f) { if ($s) $a[$f] = array_key_exists($f, $s) ? $s[$f] : ''; else $a[$f] = $this->has_arg($f) ? $this->arg($f) : ''; } - foreach (array('CENTRINGMETHOD', 'EXPERIMENTKIND', 'RADIATIONSENSITIVITY', 'SCREENCOMPONENTGROUPID', 'BLSUBSAMPLEID', 'COMPONENTIDS', 'COMPONENTAMOUNTS', 'REQUIREDRESOLUTION', 'CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA', 'VOLUME', 'ABUNDANCE', 'PACKINGFRACTION', 'DIMENSION1', 'DIMENSION2', 'DIMENSION3', 'SHAPE', 'THEORETICALDENSITY', 'LOOPTYPE', 'ENERGY', 'USERPATH') as $f) { + foreach (array('CENTRINGMETHOD', 'EXPERIMENTKIND', 'RADIATIONSENSITIVITY', 'SCREENCOMPONENTGROUPID', 'BLSUBSAMPLEID', 'COMPONENTIDS', 'COMPONENTAMOUNTS', 'REQUIREDRESOLUTION', 'AIMEDRESOLUTION', 'CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA', 'VOLUME', 'ABUNDANCE', 'PACKINGFRACTION', 'DIMENSION1', 'DIMENSION2', 'DIMENSION3', 'SHAPE', 'THEORETICALDENSITY', 'LOOPTYPE', 'ENERGY', 'USERPATH', 'PRIORITY', 'PREFERREDBEAMSIZEX', 'PREFERREDBEAMSIZEY', 'EXPOSURETIME', 'AXISSTART', 'AXISRANGE', 'NUMBEROFIMAGES', 'TRANSMISSION') as $f) { if ($s) $a[$f] = array_key_exists($f, $s) ? $s[$f] : null; else $a[$f] = $this->has_arg($f) ? $this->arg($f) : null; } @@ -1146,8 +1161,8 @@ function _prepare_sample_args($s=null) { function _do_add_sample($a) { - $this->db->pq("INSERT INTO diffractionplan (diffractionplanid, requiredresolution, anomalousscatterer, centringmethod, experimentkind, radiationsensitivity, energy, userpath) VALUES (s_diffractionplan.nextval, :1, :2, :3, :4, :5, :6, :7) RETURNING diffractionplanid INTO :id", - array($a['REQUIREDRESOLUTION'], $a['ANOMALOUSSCATTERER'], $a['CENTRINGMETHOD'], $a['EXPERIMENTKIND'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'])); + $this->db->pq("INSERT INTO diffractionplan (diffractionplanid, requiredresolution, anomalousscatterer, centringmethod, experimentkind, radiationsensitivity, energy, userpath, aimedresolution, preferredbeamsizex, preferredbeamsizey, exposuretime, axisstart, axisrange, numberofimages, transmission) VALUES (s_diffractionplan.nextval, :1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11, :12, :13, :14, :15) RETURNING diffractionplanid INTO :id", + array($a['REQUIREDRESOLUTION'], $a['ANOMALOUSSCATTERER'], $a['CENTRINGMETHOD'], $a['EXPERIMENTKIND'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['AIMEDRESOLUTION'], $a['PREFERREDBEAMSIZEX'], $a['PREFERREDBEAMSIZEY'], $a['EXPOSURETIME'], $a['AXISSTART'], $a['AXISRANGE'], $a['NUMBEROFIMAGES'], $a['TRANSMISSION'])); $did = $this->db->id(); if (!array_key_exists('CRYSTALID', $a)) { @@ -1451,7 +1466,7 @@ function _update_sample() { } } - $dfields = array('REQUIREDRESOLUTION', 'ANOMALOUSSCATTERER', 'CENTRINGMETHOD', 'EXPERIMENTKIND', 'RADIATIONSENSITIVITY', 'ENERGY', 'USERPATH'); + $dfields = array('REQUIREDRESOLUTION', 'ANOMALOUSSCATTERER', 'CENTRINGMETHOD', 'EXPERIMENTKIND', 'RADIATIONSENSITIVITY', 'ENERGY', 'USERPATH', 'AIMEDRESOLUTION', 'PREFERREDBEAMSIZEX', 'PREFERREDBEAMSIZEY', 'EXPOSURETIME', 'AXISSTART', 'AXISRANGE', 'NUMBEROFIMAGES', 'TRANSMISSION'); foreach ($dfields as $f) { if ($this->has_arg($f)) { $this->db->pq("UPDATE diffractionplan SET $f=:1 WHERE diffractionplanid=:2", array($this->arg($f), $samp['DIFFRACTIONPLANID'])); From f9f8b409b96a75b172f3d46c2f3185f9765c869d Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:17:33 +0200 Subject: [PATCH 04/56] add new diffraction plan columns and container view toggle --- client/src/css/partials/_tables.scss | 4 +- client/src/js/models/sample.js | 54 +++++++++++++++++++ .../js/modules/shipment/views/container.js | 9 ++++ .../js/modules/shipment/views/containeradd.js | 9 ++++ .../js/modules/shipment/views/sampletable.js | 27 ++++++++-- .../src/js/templates/shipment/container.html | 1 + .../js/templates/shipment/containeradd.html | 2 + .../js/templates/shipment/sampletable.html | 10 ++++ .../js/templates/shipment/sampletablenew.html | 10 ++++ .../js/templates/shipment/sampletablerow.html | 11 ++++ .../shipment/sampletablerowedit.html | 28 ++++++++++ .../templates/shipment/sampletablerownew.html | 30 +++++++++++ client/src/js/utils/collectionmode.js | 22 ++++++++ 13 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 client/src/js/utils/collectionmode.js diff --git a/client/src/css/partials/_tables.scss b/client/src/css/partials/_tables.scss index fbd9cdbff..04fb97a00 100644 --- a/client/src/css/partials/_tables.scss +++ b/client/src/css/partials/_tables.scss @@ -66,7 +66,7 @@ They can be overriden by specific classes below padding: 5px; } - td.extra, th.extra, th.xtal, td.xtal, th.non-xtal, td.non-xtal, th.auto, td.auto { + td.extra, th.extra, th.xtal, td.xtal, th.non-xtal, td.non-xtal, th.auto, td.auto, th.dp, td.dp { display: none; &.show { @@ -75,7 +75,7 @@ They can be overriden by specific classes below } @media (max-width: $breakpoint-vsmall) { - td.extra, th.extra,, th.xtal, td.xtal, th.non-xtal, td.non-xtal, th.auto, td.auto { + td.extra, th.extra,, th.xtal, td.xtal, th.non-xtal, td.non-xtal, th.auto, td.auto, th.dp, td.dp { &.show { display: block; } diff --git a/client/src/js/models/sample.js b/client/src/js/models/sample.js index 850e51c4f..cc6cfa262 100644 --- a/client/src/js/models/sample.js +++ b/client/src/js/models/sample.js @@ -60,6 +60,15 @@ define(['backbone', 'collections/components', DIMENSION2: '', DIMENSION3: '', SHAPE: '', + AIMEDRESOLUTION: '', + COLLECTIONMODE: '', + PRIORITY: '', + EXPOSURETIME: '', + AXISSTART: '', + AXISRANGE: '', + NUMBEROFIMAGES: '', + TRANSMISSION: '', + PREFERREDBEAMSIZEX: '', }, validation: { @@ -157,6 +166,51 @@ define(['backbone', 'collections/components', maxLength: 40, }, + AIMEDRESOLUTION: { + required: false, + pattern: 'number', + }, + + COLLECTIONMODE: { + required: false, + pattern: 'word', + }, + + PRIORITY: { + required: false, + pattern: 'number', + }, + + EXPOSURETIME: { + required: false, + pattern: 'number', + }, + + AXISSTART: { + required: false, + pattern: 'number', + }, + + AXISRANGE: { + required: false, + pattern: 'number', + }, + + NUMBEROFIMAGES: { + required: false, + pattern: 'number', + }, + + TRANSMISSION: { + required: false, + pattern: 'number', + }, + + PREFERREDBEAMSIZEX: { + required: false, + pattern: 'number', + }, + COMPONENTAMOUNTS: function(from_ui, attr, all_values) { var values = all_values.components.pluck('ABUNDANCE') diff --git a/client/src/js/modules/shipment/views/container.js b/client/src/js/modules/shipment/views/container.js index 66ad313c6..a33c4de7d 100644 --- a/client/src/js/modules/shipment/views/container.js +++ b/client/src/js/modules/shipment/views/container.js @@ -58,10 +58,12 @@ define(['marionette', ext: '.extrainfo', auto: '.auto', extrastate: '.extra-state', + dpstate: '.dp-state', }, events: { 'click @ui.ext': 'toggleExtra', + 'click a.dpinfo': 'toggleDP', 'click a.queue': 'confirmQueueContainer', 'click a.unqueue': 'confirmUnqueueContainer', }, @@ -79,6 +81,13 @@ define(['marionette', : this.ui.extrastate.addClass('fa-plus').removeClass('fa-minus') }, + toggleDP: function(e) { + e.preventDefault() + this.table.currentView.toggleDP() + this.table.currentView.dpState() ? this.ui.dpstate.addClass('fa-minus').removeClass('fa-plus') + : this.ui.dpstate.addClass('fa-plus').removeClass('fa-minus') + }, + createSamples: function() { this.samples = new Samples(null, { state: { pageSize: 9999 } }) }, diff --git a/client/src/js/modules/shipment/views/containeradd.js b/client/src/js/modules/shipment/views/containeradd.js index 00997d9a8..2acc58d22 100644 --- a/client/src/js/modules/shipment/views/containeradd.js +++ b/client/src/js/modules/shipment/views/containeradd.js @@ -115,6 +115,7 @@ define(['backbone', auto: 'input[name=AUTOMATED]', extrastate: '.extra-state', spacegroups: 'input[name=SPACEGROUPS]', + dp: '.dp-state', }, @@ -135,6 +136,7 @@ define(['backbone', 'change @ui.type': 'setType', 'click @ui.ext': 'toggleExtra', + 'click a.dpinfo': 'toggleDP', 'keypress .ui-combobox input': 'excelNavigate', 'keypress input.sname': 'excelNavigate', @@ -290,6 +292,13 @@ define(['backbone', } }, + toggleDP: function(e) { + e.preventDefault() + this.table.currentView.toggleDP() + this.table.currentView.dpState() ? this.ui.dpstate.addClass('fa-minus').removeClass('fa-plus') + : this.ui.dpstate.addClass('fa-plus').removeClass('fa-minus') + }, + isForImager: function() { return !(!this.ui.imager.val()) }, diff --git a/client/src/js/modules/shipment/views/sampletable.js b/client/src/js/modules/shipment/views/sampletable.js index 64955987b..44cf1cda8 100644 --- a/client/src/js/modules/shipment/views/sampletable.js +++ b/client/src/js/modules/shipment/views/sampletable.js @@ -12,11 +12,11 @@ define(['marionette', 'collections/spacegroups', 'utils/forms', - 'utils/sgs', 'utils/anoms', 'utils/centringmethods', 'utils/experimentkinds', 'utils/radiationsensitivity', + 'utils/collectionmode', 'utils', 'utils/safetylevel', @@ -24,7 +24,7 @@ define(['marionette', 'jquery', ], function(Marionette, Protein, Proteins, ValidatedRow, DistinctProteins, ComponentsView, sampletable, sampletablerow, sampletablerowedit, SpaceGroups, - forms, SG, Anom, CM, EXP, RS, utils, safetyLevel, $) { + forms, Anom, CM, EXP, RS, COLM, utils, safetyLevel, $) { // A Sample Row @@ -74,13 +74,15 @@ define(['marionette', cancelEditSample: function(e) { this.editing = false e.preventDefault() + if (this.model.get('PROTEINID') > -1 && this.model.isNew()) this.model.set({ PROTEINID: -1, CRYSTALID: -1 }) this.template = this.getOption('rowTemplate') this.render() }, setData: function() { var data = {} - _.each(['CODE', 'PROTEINID', 'CRYSTALID', 'NAME', 'COMMENTS', 'SPACEGROUP', 'VOLUME', 'ABUNDANCE', 'PACKINGFRACTION', 'LOOPTYPE', 'CENTRINGMETHOD', 'EXPERIMENTKIND', 'ENERGY', 'RADIATIONSENSITIVITY', 'USERPATH'], function(f) { + _.each(['CODE', 'PROTEINID', 'CRYSTALID', 'NAME', 'COMMENTS', 'SPACEGROUP', 'VOLUME', 'ABUNDANCE', 'PACKINGFRACTION', 'LOOPTYPE', 'CENTRINGMETHOD', 'EXPERIMENTKIND', 'ENERGY', 'RADIATIONSENSITIVITY', 'USERPATH', + 'AIMEDRESOLUTION', 'COLLECTIONMODE', 'PRIORITY', 'EXPOSURETIME', 'AXISSTART', 'AXISRANGE', 'NUMBEROFIMAGES', 'TRANSMISSION', 'PREFERREDBEAMSIZEX'], function(f) { var el = this.$el.find('[name='+f+']') if (el.length) data[f] = el.attr('type') == 'checkbox'? (el.is(':checked')?1:null) : el.val() }, this) @@ -149,6 +151,7 @@ define(['marionette', CELL_A: '', CELL_B: '', CELL_C: '', CELL_ALPHA: '', CELL_BETA: '', CELL_GAMMA: '', REQUIREDRESOLUTION: '', ANOM_NO: '', ANOMALOUSSCATTERER: '', CRYSTALID: -1, PACKINGFRACTION: '', LOOPTYPE: '', DIMENSION1: '', DIMENSION2: '', DIMENSION3: '', SHAPE: '', CENTRINGMETHOD: '', EXPERIMENTKIND: '', ENERGY: '', RADIATIONSENSITIVITY: '', USERPATH: '', + AIMEDRESOLUTION: '', COLLECTIONMODE: '', PRIORITY: '', EXPOSURETIME: '', AXISSTART: '', AXISRANGE: '', NUMBEROFIMAGES: '', TRANSMISSION: '', PREFERREDBEAMSIZEX: '' }) this.model.get('components').reset() this.render() @@ -210,13 +213,15 @@ define(['marionette', //if (this.model.get('CODE')) this.$el.find('input[name=CODE]').val(this.model.get('CODE')) //if (this.model.get('COMMENTS')) this.$el.find('input[name=COMMENTS]').val(this.model.get('COMMENTS')) - _.each(['NAME', 'CODE', 'COMMENTS', 'CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA', 'REQUIREDRESOLUTION', 'ANOM_NO', 'VOLUME', 'PACKINGFRACTION', 'USERPATH'], function(f, i) { + _.each(['NAME', 'CODE', 'COMMENTS', 'CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA', 'REQUIREDRESOLUTION', 'ANOM_NO', 'VOLUME', 'PACKINGFRACTION', 'USERPATH', + 'AIMEDRESOLUTION', 'COLLECTIONMODE', 'PRIORITY', 'EXPOSURETIME', 'AXISSTART', 'AXISRANGE', 'NUMBEROFIMAGES', 'TRANSMISSION', 'PREFERREDBEAMSIZEX'], function(f, i) { if (this.model.get(f)) this.$el.find('input[name='+f+']').val(this.model.get(f)) }, this) this.ui.symbol.text(this.model.get('SYMBOL') ? this.model.get('SYMBOL') : '') if (this.getOption('extra').show) this.$el.find('.extra').addClass('show') + if (this.getOption('dp').show) this.$el.find('.dp').addClass('show') if (this.getOption('type') == 'non-xtal') { this.$el.find('.non-xtal').addClass('show') @@ -237,6 +242,7 @@ define(['marionette', this.$el.find('[name=EXPERIMENTKIND]').html(EXP.opts()).val(this.model.get('EXPERIMENTKIND')) this.$el.find('[name=ENERGY]').val(this.model.get('ENERGY')) this.$el.find('[name=RADIATIONSENSITIVITY]').html(RS.opts()).val(this.model.get('RADIATIONSENSITIVITY')) + this.$el.find('[name=COLLECTIONMODE]').html(COLM.opts()).val(this.model.get('COLLECTIONMODE')) this.compview = new ComponentsView({ collection: this.model.get('components'), editable: this.editing || this.model.get('new') }) this.ui.comps.append(this.compview.render().$el) @@ -384,12 +390,15 @@ define(['marionette', if (options.childEditTemplate) this.options.childViewOptions.editTemplate = options.childEditTemplate this.extra = { show: false } + this.dp = { show: false } this.auto = { show: options.auto == true ? true : false } this.all_spacegroups = { show: options.spacegroups == true ? true : false } this.options.childViewOptions.extra = this.extra this.options.childViewOptions.auto = this.auto + this.options.childViewOptions.dp = this.dp this.options.childViewOptions.all_spacegroups = this.all_spacegroups + this.options.childViewOptions.type = this.getOption('type') }, @@ -414,6 +423,10 @@ define(['marionette', return this.extra.show }, + dpState: function() { + return this.dp.show + }, + toggleExtra: function() { this.extra.show = !this.extra.show @@ -421,6 +434,12 @@ define(['marionette', else this.$el.find('.extra').removeClass('show') }, + toggleDP: function() { + this.dp.show = !this.dp.show + + if (this.dp.show) this.$el.find('.dp').addClass('show') + else this.$el.find('.dp').removeClass('show') + }, toggleAuto: function(val) { this.auto.show = val diff --git a/client/src/js/templates/shipment/container.html b/client/src/js/templates/shipment/container.html index 39090755b..4783d6064 100644 --- a/client/src/js/templates/shipment/container.html +++ b/client/src/js/templates/shipment/container.html @@ -85,5 +85,6 @@

Container: <%-NAME%>

+ Plan Fields Extra Fields
\ No newline at end of file diff --git a/client/src/js/templates/shipment/containeradd.html b/client/src/js/templates/shipment/containeradd.html index 74e63b08d..9be8d0ac1 100644 --- a/client/src/js/templates/shipment/containeradd.html +++ b/client/src/js/templates/shipment/containeradd.html @@ -136,6 +136,8 @@

Add New Container

Clone from First Sample Clear Puck + + Plan Fields Extra Fields
diff --git a/client/src/js/templates/shipment/sampletable.html b/client/src/js/templates/shipment/sampletable.html index e61e0ada6..d78885c6a 100644 --- a/client/src/js/templates/shipment/sampletable.html +++ b/client/src/js/templates/shipment/sampletable.html @@ -21,6 +21,16 @@ Components Unit Cell + + Aimed Res + Mode + Priority + Exposure + Axis Start + Axis Range + No. Images + Transmission + Beamsize Status   diff --git a/client/src/js/templates/shipment/sampletablenew.html b/client/src/js/templates/shipment/sampletablenew.html index f253cbbd9..df23ae1b9 100644 --- a/client/src/js/templates/shipment/sampletablenew.html +++ b/client/src/js/templates/shipment/sampletablenew.html @@ -23,6 +23,16 @@ Unit Cell + Aimed Res + Mode + Priority + Exposure + Axis Start + Axis Range + No. Images + Transmission + Beamsize +   diff --git a/client/src/js/templates/shipment/sampletablerow.html b/client/src/js/templates/shipment/sampletablerow.html index bf4d40e61..e63012469 100644 --- a/client/src/js/templates/shipment/sampletablerow.html +++ b/client/src/js/templates/shipment/sampletablerow.html @@ -7,6 +7,7 @@       +   <% } else { %> @@ -59,6 +60,16 @@ + <%-AIMEDRESOLUTION%> + <%-COLLECTIONMODE%> + <%-PRIORITY%> + <%-EXPOSURETIME%> + <%-AXISSTART%> + <%-AXISRANGE%> + <%-NUMBEROFIMAGES%> + <%-TRANSMISSION%> + <%-PREFERREDBEAMSIZEX%> + <% if (BLSAMPLEID && STATUS) { %>
    diff --git a/client/src/js/templates/shipment/sampletablerowedit.html b/client/src/js/templates/shipment/sampletablerowedit.html index c2735a23f..e2964196d 100644 --- a/client/src/js/templates/shipment/sampletablerowedit.html +++ b/client/src/js/templates/shipment/sampletablerowedit.html @@ -49,6 +49,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +   Save Changes diff --git a/client/src/js/templates/shipment/sampletablerownew.html b/client/src/js/templates/shipment/sampletablerownew.html index 736a01166..bae0b7c14 100644 --- a/client/src/js/templates/shipment/sampletablerownew.html +++ b/client/src/js/templates/shipment/sampletablerownew.html @@ -32,6 +32,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Clone Clear diff --git a/client/src/js/utils/collectionmode.js b/client/src/js/utils/collectionmode.js new file mode 100644 index 000000000..15ce438c3 --- /dev/null +++ b/client/src/js/utils/collectionmode.js @@ -0,0 +1,22 @@ +define([], function() { + + return { + opts: function() { + return _.map(this.list, function(v,s) { return '' }).join() + }, + obj: function() { + return _.invert(this.list) + }, + + key: function(value) { + return _.invert(this.list)[value] + }, + + list: { + 'auto': 'auto', + 'manual': 'manual', + } + + } + +}) From 80e18cb634ff8eca43cb6df84e2aac4ff9e29b0c Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:19:15 +0200 Subject: [PATCH 05/56] add csv importer --- client/src/js/csv/imca.js | 92 +++ client/src/js/modules/shipment/controller.js | 38 +- client/src/js/modules/shipment/router.js | 2 + .../src/js/modules/shipment/views/fromcsv.js | 543 ++++++++++++++++++ client/src/js/templates/shipment/fromcsv.html | 19 + .../templates/shipment/fromcsvcontainer.html | 5 + .../js/templates/shipment/fromcsvtable.html | 4 + .../src/js/templates/shipment/shipment.html | 4 + 8 files changed, 704 insertions(+), 3 deletions(-) create mode 100644 client/src/js/csv/imca.js create mode 100644 client/src/js/modules/shipment/views/fromcsv.js create mode 100644 client/src/js/templates/shipment/fromcsv.html create mode 100644 client/src/js/templates/shipment/fromcsvcontainer.html create mode 100644 client/src/js/templates/shipment/fromcsvtable.html diff --git a/client/src/js/csv/imca.js b/client/src/js/csv/imca.js new file mode 100644 index 000000000..c30579501 --- /dev/null +++ b/client/src/js/csv/imca.js @@ -0,0 +1,92 @@ +define([], function() { + + return { + // The csv column names + headers: ['Puck', 'Pin', 'Project', 'Priority', 'Mode', 'Notes to Staff', 'Collection strategy', 'Contact person', 'Expected space group', 'Expected Cell Dimensions', 'Expected Resolution', 'Minimum Resolution Required to Collect', 'Recipe', 'Exposure time', 'Image Width', 'Phi', 'Attenuation', 'Aperture', 'Detector Distance', 'Prefix for frames', 'Observed Resolution', 'Comments From Staff', 'Status'], + + // ... and their ISPyB table mapping + mapping: ['CONTAINER', 'LOCATION', 'ACRONYM', 'PRIORITY', 'COLLECTIONMODE', 'COMMENTS', 'COMMENTS', 'OWNER', 'SPACEGROUP', 'CELL', 'AIMEDRESOLUTION', 'REQUIREDRESOLUTION', 'RECIPE', 'EXPOSURETIME', 'AXISRANGE', 'AXISROTATION', 'TRANSMISSION', 'PREFERREDBEAMSIZEX', 'DETECTORDISTANCE', 'PREFIX', 'DCRESOLUTION', 'DCCOMMENTS', 'STATUS'], + + // Columns to show on the import page + columns: { + LOCATION: 'Location', + PROTEINID: 'Protein', + NAME: 'Sample', + PRIORITY: 'Priority', + COLLECTIONMODE: 'Mode', + COMMENTS: 'Comments', + SPACEGROUP: 'Spacegroup', + CELL: 'Cell', + AIMEDRESOLUTION: 'Aimed Res', + REQUIREDRESOLUTION: 'Required Res', + EXPOSURETIME: 'Exposure (s)', + AXISRANGE: 'Axis Osc', + NUMBEROFIMAGES: 'No. Images', + TRANSMISSION: 'Transmission', + PREFERREDBEAMSIZEX: 'Beamsize', + }, + + // Import transforms + transforms: { + CELL: function(v, m) { + var comps = v.split(/\s+/) + _.each(['CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA'], function(ax, i) { + if (comps.length > i) m[ax] = comps[i].replace(',', '') + }) + }, + AXISROTATION: function(v, m) { + if (m.AXISRANGE) m.NUMBEROFIMAGES = m.AXISROTATION / m.AXISRANGE + }, + SPACEGROUP: function(v, m) { + m.SPACEGROUP = v.replace(/[\(\)]/g, '') + }, + LOCATION: function(v, m) { + if (!this.xcount) this.xcount = 1 + m.NAME = 'x'+(this.xcount++) + }, + COLLECTIONMODE: function(v, m) { + m.COLLECTIONMODE = v.toLowerCase() + } + }, + + // Export transforms + export: { + CELL: function(m) { + return `${m.CELL_A}, ${m.CELL_B}, ${m.CELL_C}, ${m.CELL_ALPHA}, ${m.CELL_BETA}, ${m.CELL_GAMMA}`.trim() + }, + + STATUS: function(m) { + var status = 'skipped' + if (m.QUEUEDTIMESTAMP) status = 'queued'; + if (m.R > 0) status = 'recieved' + if (m.DC > 0) status = 'collected' + + return status + }, + + AXISROTATION: function(m) { + return m.AXISRANGE * m.NUMBEROFIMAGES + }, + + COMMENTS: function(m, h) { + var comments = m.COMMENTS.split(' | ') + return comments.length > 1 && h == 'Collection strategy' ? comments[1] : comments[0] + } + }, + + exampleCSV: `Puck,Pin,Project,Priority,Mode,Notes to Staff,Collection strategy,Contact person,Expected space group,Expected Cell Dimensions,Expected Resolution,Minimum Resolution Required to Collect,Recipe,Exposure time,Image Width,Phi,Attenuation,Aperture,Detector Distance,Prefix for frames,Observed Resolution,Comments From Staff,Status +Blue53,1,a,1,Manual,Tricky,Do best you can,Luke,C2,"143.734, 67.095, 76.899, 90, 110.45, 90",1.9-3.5,4,luke-360.rcp,,,,,,,,,, +Blue53,2,a,1,Manual,Very tricky,New crystals,Luke,C2,140 65 75 90 110 90,1.8-2.4,3.5,,0.1,0.25,,95,5,250,image_,,, +Blue53,2,a,1,Manual,Very tricky,New crystals,Luke,C2,140 65 75 90 110 90,1.8-2.4,3.5,,0.1,0.25,,95,5,250,image_,,, +Blue53,3,b,3,Auto,Routine,SeMet,Luke,P2,52.4 39.8 65.0 108.5,1.5,1.7,,0.04,0.25,360,,10,300,,,, +Blue53,4,c,3,Auto,Rods,Native,Luke,P21,39 69.2 60 90 105.3,1.5,1.7,,0.04,0.25,360,95,20,,image_,,, +Blue53,5,d,8,,Plates,,Luke,C222,280 45 112 102 90,1.5,1.7,,0.04,0.25,360,95,50,300,image_,,, +Blue54,1,e,,Auto,,,,P212121,67 82 276,2.1,2.5,,,0.25,180,,10,350,image_,,, +Blue54,2,e,4,,,,Luke,P2(1)2(1)2(1),67 82 276,,1.7,luke-180.rcp,,,,,,,,,, +Blue54,3,f,,Auto,,,,P222,,2.1,,,0.04,,180,95,,350,image_,,, +Blue54,4,g,4,Auto,,,Luke,,,2.1,2.5,,0.04,0.25,180,75,,350,image_,,, +Blue54,5,h,99,Auto,,,Luke,P222,,2.2,2.5,,0.04,0.25,180,95,,400,image_,,, + ` + } + +}) diff --git a/client/src/js/modules/shipment/controller.js b/client/src/js/modules/shipment/controller.js index 945391477..5fbfb72ff 100644 --- a/client/src/js/modules/shipment/controller.js +++ b/client/src/js/modules/shipment/controller.js @@ -7,7 +7,8 @@ define(['backbone', 'modules/shipment/views/shipments', 'modules/shipment/views/shipment', 'modules/shipment/views/shipmentadd', - + 'modules/shipment/views/fromcsv', + 'models/container', 'collections/containers', 'modules/shipment/views/container', @@ -46,8 +47,8 @@ define(['backbone', ], function(Backbone, GetView, - Dewar, Shipment, Shipments, - ShipmentsView, ShipmentView, ShipmentAddView, + Dewar, Shipment, Shipments, + ShipmentsView, ShipmentView, ShipmentAddView, ImportFromCSV, Container, Containers, ContainerView, ContainerPlateView, /*ContainerAddView,*/ ContainersView, QueueContainerView, ContainerRegistry, ContainersRegistry, ContainerRegistryView, RegisteredContainer, RegisteredDewar, DewarRegistry, DewarRegView, RegDewarView, RegDewarAddView, DewarRegistryView, @@ -116,6 +117,37 @@ define(['backbone', } }, + // Import csv based on selected profile + import_csv: function(sid) { + if (!app.config.csv_profile) { + app.message({ title: 'CSV Import Not Enabled', message: 'Shipment CSV import is not currently enabled'}) + return + } + + var lookup = new ProposalLookup({ field: 'SHIPPINGID', value: sid }) + lookup.find({ + success: function() { + var shipment = new Shipment({ SHIPPINGID: sid }) + shipment.fetch({ + success: function() { + app.bc.reset([bc, { title: shipment.get('SHIPPINGNAME') }, { title: 'Import from CSV' }]) + app.content.show(new ImportFromCSV({ model: shipment, format: 'imca' })) + }, + error: function() { + app.bc.reset([bc]) + app.message({ title: 'No such shipment', message: 'The specified shipment could not be found'}) + }, + }) + }, + + error: function() { + app.bc.reset([bc, { title: 'No such shipment' }]) + app.message({ title: 'No such shipment', message: 'The specified shipment could not be found'}) + } + }) + }, + + create_awb: function(sid) { var shipment = new Shipment({ SHIPPINGID: sid }) shipment.fetch({ diff --git a/client/src/js/modules/shipment/router.js b/client/src/js/modules/shipment/router.js index 99fc5c71c..7e2fc5b6f 100644 --- a/client/src/js/modules/shipment/router.js +++ b/client/src/js/modules/shipment/router.js @@ -9,6 +9,8 @@ define(['utils/lazyrouter'], function(LazyRouter) { 'shipments/awb/sid/:sid': 'create_awb', 'shipments/pickup/sid/:sid': 'rebook_pickup', + 'shipments/csv/:sid': 'import_csv', + 'containers/cid/:cid(/iid/:iid)(/sid/:sid)': 'view_container', 'containers/queue/:cid': 'queue_container', 'containers/add/did/:did': 'add_container', diff --git a/client/src/js/modules/shipment/views/fromcsv.js b/client/src/js/modules/shipment/views/fromcsv.js new file mode 100644 index 000000000..9525ff14f --- /dev/null +++ b/client/src/js/modules/shipment/views/fromcsv.js @@ -0,0 +1,543 @@ +define(['backbone', + 'marionette', + 'papaparse', + 'models/protein', + 'collections/proteins', + 'models/sample', + 'collections/samples', + 'models/container', + 'collections/containers', + 'collections/dewars', + 'views/validatedrow', + 'views/form', + 'modules/shipment/collections/platetypes', + 'modules/shipment/collections/containerregistry', + 'collections/users', + 'modules/shipment/collections/distinctproteins', + + 'utils/sgs', + 'utils/collectionmode', + + 'templates/shipment/fromcsv.html', + 'templates/shipment/fromcsvtable.html', + 'templates/shipment/fromcsvcontainer.html' + ], function( + Backbone, + Marionette, + Papa, + Protein, + Proteins, + Sample, + Samples, + Container, + Containers, + Dewars, + ValidatedRow, + FormView, + PlateTypes, + ContainerRegistry, + Users, + DistinctProteins, + SG, + COLM, + template, table, container) { + + + var GridRow = ValidatedRow.extend({ + template: false, + tagName: 'tr', + + columnTypes: { + LOCATION: function(v) { + return ''+v+'' + }, + + CELL: function(v, model) { + return ` + + + + + + + + ` + }, + COLLECTIONMODE: function() { + return '' + }, + PROTEINID: function(v, m) { + var newProtein = v == 0 ? 'active' : '' + return ''+m.escape('ACRONYM')+'' + }, + SPACEGROUP: function(v, m) { + return '' + } + }, + + onRender: function() { + var cts = this.getOption('columnTypes') + var columns = _.map(this.getOption('profile').columns, function(c, k) { + var val = this.model.get(k) || '' + return cts[k] ? cts[k](val, this.model, this) : '' + }, this) + + this.$el.html(columns.join('')) + this.$el.find('select[name=SPACEGROUP]').val(this.model.get('SPACEGROUP')) + this.$el.find('select[name=COLLECTIONMODE]').val(this.model.get('COLLECTIONMODE')) + this.model.validate() + }, + }) + + var TableView = Marionette.CompositeView.extend({ + tagName: "table", + className: 'samples reflow', + template: table, + childView: GridRow, + childViewOptions: function() { + return { + profile: this.getOption('profile'), + } + }, + + ui: { + tr: 'thead tr', + }, + + onRender: function() { + var headers = _.map(this.getOption('profile').columns, function(c, k) { + return ''+c+'' + }) + + this.ui.tr.html(headers.join('')) + }, + + }) + + var ContainerView = Marionette.LayoutView.extend({ + template: _.template('

    <%-NAME%>

    '), + + regions: { + rsamples: '.rsamples', + rcontainer: '.rcontainer', + }, + + initialize: function(options) { + this.samples = new Samples() + this.listenTo(options.samples, 'sync reset', this.generateSamples) + this.generateSamples() + }, + + generateSamples: function() { + this.samples.reset(this.getOption('samples').where({ CONTAINER: this.model.get('NAME') })) + }, + + onRender: function() { + this.rsamples.show(new TableView({ + collection: this.samples, + profile: this.getOption('profile'), + })) + this.rcontainer.show(new ModifyContainerView({ + model: this.model, + platetypes: this.getOption('platetypes'), + users: this.getOption('users'), + containerregistry: this.getOption('containerregistry'), + })) + } + }) + + var ModifyContainerView = FormView.extend({ + template: container, + ui: { + containertype: '[name=CONTAINERTYPE]', + registry: '[name=CONTAINERREGISTRYID]', + person: '[name=PERSONID]', + }, + + events: { + 'change select': 'updateModel', + 'change input': 'updateModel', + }, + + createModel: function() { + + }, + + updateModel: function(e) { + var attr = $(e.target).attr('name') + console.log('updateModel', attr, e.target.value) + if (attr == 'CONTAINERTYPE') { + this.updateContainerType() + } else { + this.model.set({ [attr]: e.target.value }) + } + }, + + updateContainerType: function() { + var containerType = this.getOption('platetypes').findWhere({ name: this.ui.containertype.val() }) + this.model.set({ CONTAINERTYPE: this.ui.containertype.val(), CAPACITY: containerType.get('capacity') }) + }, + + onRender: function() { + this.ui.containertype.html(this.getOption('platetypes').opts()) + this.ui.registry.html(''+this.getOption('containerregistry').opts({ empty: true })) + this.ui.person.html(this.getOption('users').opts()).val(this.model.get('OWNERID') || app.personid) + + if (!this.model.get('CONTAINERTYPE')) { + this.updateContainerType() + } + + if (!this.model.get('CONTAINERREGISTRYID')) { + var reg = this.getOption('containerregistry') + var nearest = reg.findWhere({ LASTNAME: this.model.get('NAME') }) + this.model.set({ CONTAINERREGISTRYID: nearest ? nearest.get('CONTAINERREGISTRYID') : '!' }) + } + this.ui.registry.val(this.model.get('CONTAINERREGISTRYID')) + + this.model.isValid(true) + } + }) + + var ContainersView = Marionette.CollectionView.extend({ + childView: ContainerView, + childViewOptions: function() { + return { + samples: this.getOption('samples'), + profile: this.getOption('profile'), + platetypes: this.getOption('platetypes'), + containerregistry: this.getOption('containerregistry'), + users: this.getOption('users'), + proteins: this.getOption('proteins'), + } + } + }) + + + var MessageView = Marionette.ItemView.extend({ + tagName: 'li', + template: _.template('<%-message%>') + }) + + var MessagesView = Marionette.CollectionView.extend({ + tagName: 'ul', + childView: MessageView + }) + + var Message = Backbone.Model.extend({ + + }) + + return Marionette.LayoutView.extend({ + template: template, + className: 'content', + + regions: { + rcontainers: '.rcontainers', + rmessages: '.rmessages', + }, + + ui: { + drop: '.dropimage', + pnew: '.pnew', + }, + + events: { + 'dragover @ui.drop': 'dragHover', + 'dragleave @ui.drop': 'dragHover', + 'drop @ui.drop': 'uploadFile', + 'click .submit': 'import', + 'click a.export': 'export', + }, + + addMessage: function(options) { + this.messages.add(new Message({ message: options.message })) + }, + + import: function(e) { + e.preventDefault() + this.messages.reset() + + if (!this.containers.length && !this.samples.length) { + app.alert({ message: 'No containers and samples found' }) + return + } + + var valid = true + this.containers.each(function(c) { + var cValid = c.isValid(true) + console.log(c.get('CODE'), c) + if (!cValid) { + valid = false + this.addMessage({ message: `Container ${c.get('NAME')} is invalid` }) + } + }, this) + + this.samples.each(function(s) { + var sValid = s.isValid(true) + console.log(s.get('NAME'), s) + if (!sValid) { + valid = false + this.addMessage({ message: `Sample ${s.get('NAME')} is invalid` }) + } + }, this) + + var pos = this.$el.find('.top').offset().top + $('html, body').animate({scrollTop: pos}, 300); + + if (!valid) { + app.alert({ message: 'Shipment is not valid' }) + return + } + + this.messages.reset() + + var self = this + var pp = [] + this.newProteins.each(function(p) { + pp.push(p.save({}, { + success: function() { + self.addMessage({ message: 'Created component: '+p.get('ACRONYM') }) + }, + error: function(xhr, status, error) { + self.addMessage({ messages: 'Error creating component: '+error}) + } + })) + }, this) + + $.when.apply($, pp).done(function() { + var cp = [] + self.containers.each(function(c) { + if (c.isNew()) { + cp.push(c.save({}, { + success: function() { + self.addMessage({ message: 'Created container: '+c.get('NAME') }) + }, + error: function(xhr, status, error) { + self.addMessage({ messages: 'Error creating container: '+error}) + } + })) + } + }) + + $.when.apply($, cp).done(function() { + var news = new Samples(self.samples.filter(function(s) { + return s.isNew() + })) + + if (news.length == 0) return + + news.each(function(s) { + if (!s.get('CONTAINERID')) { + var c = self.containers.findWhere({ NAME: s.get('CONTAINER')}) + s.set({ CONTAINERID: c.get('CONTAINERID') }, { silent: true }) + } + + if (s.get('PROTEINID') == 0) { + var p = self.newProteins.findWhere({ ACRONYM: s.get('ACRONYM')}) + s.set({ PROTEINID: p.get('PROTEINID') }, { silent: true }) + } + }) + + news.save({ + success: function() { + app.alert({ message: 'Shipment contents imported, Click here to view it', persist: 'simport'+self.model.escape('SHIPPINGID'), className: 'message notify' }) + self.addMessage({ messages: 'Samples created' }) + }, + error: function(xhr, status, error) { + self.addMessage({ messages: 'Error creating samples: '+error}) + } + }) + }) + + }) + }, + + export: function() { + var rows = [] + rows.push(this.csvProfile.headers) + this.samples.each(function(s) { + var row = [] + _.each(this.csvProfile.mapping, function(k, i) { + if (k in this.csvProfile.export) { + row.push(this.csvProfile.export[k](s.toJSON(), this.csvProfile.headers[i])) + } else { + row.push(s.get(k)) + } + }, this) + + rows.push(row) + }, this) + + var csv = Papa.unparse(rows) + var a = document.createElement('a') + var file = new Blob([csv], {type: 'application/octet-stream'}) + + a.href= URL.createObjectURL(file) + a.download = this.model.get('SHIPPINGNAME') + '.csv' + a.click() + + URL.revokeObjectURL(a.href); + + console.log(csv) + }, + + initialize: function(options) { + this.messages = new Backbone.Collection() + + this.samples = new Samples() + this.samples.queryParams.SHIPPINGID = this.model.get('SHIPPINGID') + this.samples.fetch() + + this.containers = new Containers() + this.containers.queryParams.SHIPPINGID = this.model.get('SHIPPINGID') + this.containers.setSorting('BLTIMESTAMP', 0) + this.containers.fetch() + + this.platetypes = new PlateTypes() + + this.ready = [] + this.containerregistry = new ContainerRegistry(null, { state: { pageSize: 9999 }}) + this.ready.push(this.containerregistry.fetch()) + + this.users = new Users(null, { state: { pageSize: 9999 }}) + this.users.queryParams.all = 1 + this.users.queryParams.pid = app.proposal.get('PROPOSALID') + this.ready.push(this.users.fetch()) + + this.newProteins = new Proteins() + this.proteins = new DistinctProteins() + if (app.valid_samples) { + this.proteins.queryParams.external = 1 + } + this.ready.push(this.proteins.fetch()) + + this.dewars = new Dewars() + this.dewars.queryParams.sid = this.model.get('SHIPPINGID') + this.ready.push(this.dewars.fetch()) + + this.csvProfile = require('csv/'+app.config.csv_profile+'.js') + console.log('initialize', this.csvProfile) + }, + + + dragHover: function(e) { + e.stopPropagation() + e.preventDefault() + if (e.type == 'dragover') this.ui.drop.addClass('active') + else this.ui.drop.removeClass('active') + }, + + uploadFile: function(e) { + this.dragHover(e) + var files = e.originalEvent.dataTransfer.files + var f = files[0] + + if (f.type == 'text/csv') { + var reader = new FileReader() + var self = this + reader.onload = function(e) { + self.parseCSVContents(e.target.result) + } + reader.readAsText(f) + } + }, + + createObjects: function(raw) { + var parsed = Papa.parse(raw) + + if (parsed.errors.length) { + var errs = [] + _.each(parsed.errors, function(e) { + errs.push({ message: e.code + ': ' + e.message + ' at row ' + e.row }) + }) + this.messages.reset(errs) + app.alert({ message: 'Error parsing csv file, see messages below' }) + return + } + + var objects = parsed.data + var headers = this.csvProfile.mapping + var transforms = this.csvProfile.transforms + objects.splice(0, 1) + + var newProteins = [] + var populatedObject = [] + _.each(objects, function(item){ + if (!item.length || (item.length == 1 && !item[0].trim())) return + var obj = {} + _.each(item, function(v, i) { + var key = headers[i] + if (v) obj[key] ? obj[key] += ' | '+v : obj[key] = v + }) + + _.each(obj, function(v, k) { + if (k in transforms) { + transforms[k](v, obj) + } + }, this) + + if (!obj.PROTEINID) { + var protein = this.proteins.findWhere({ ACRONYM: obj.ACRONYM }) + if (protein) { + obj.PROTEINID = protein.get('PROTEINID') + } else { + obj.PROTEINID = 0 + var newp = _.findWhere(newProteins, { ACRONYM: obj.ACRONYM }) + if (!newp) { + newProteins.push({ + ACRONYM: obj.ACRONYM, + NAME: obj.ACRONYM, + }) + } + } + } + + populatedObject.push(obj) + }, this) + + this.newProteins.reset(newProteins) + return populatedObject + }, + + parseCSVContents: function(raw) { + var samples = this.createObjects(raw) + console.log('parseCSVContents', samples) + this.samples.reset(samples) + + this.containers.reset(_.map(_.unique(_.pluck(samples, 'CONTAINER')), function(name) { + var sample = this.samples.findWhere({ CONTAINER: name }) + + var ownerid = null + if (sample) { + var oid = this.users.findWhere({ FULLNAME: sample.get('OWNER') }) + if (oid) ownerid = oid.get('PERSONID') + } + + return { NAME: name, DEWARID: this.dewars.at(0).get('DEWARID'), OWNERID: ownerid } + }, this)) + + if (this.newProteins.length) this.ui.pnew.text('Need to create '+this.newProteins.length+' components: '+this.newProteins.pluck('ACRONYM').join(', ')) + }, + + onRender: function() { + $.when.apply($, this.ready).done(this.doOnRender.bind(this)) + this.rmessages.show(new MessagesView({ collection: this.messages })) + }, + + doOnRender: function() { + // this.parseCSVContents(this.csvProfile.exampleCSV) + + this.rcontainers.show(new ContainersView({ + collection: this.containers, + samples: this.samples, + profile: this.csvProfile, + platetypes: this.platetypes, + containerregistry: this.containerregistry, + users: this.users, + proteins: this.proteins, + })) + }, + + }) + +}) diff --git a/client/src/js/templates/shipment/fromcsv.html b/client/src/js/templates/shipment/fromcsv.html new file mode 100644 index 000000000..84ae03a6c --- /dev/null +++ b/client/src/js/templates/shipment/fromcsv.html @@ -0,0 +1,19 @@ +

    <%-SHIPPINGNAME%>: Import from CSV

    + Export to CSV + +
    + Drop CSV File Here +
    + +

    Messages

    +
    +
    + +
    + +
    + +
    diff --git a/client/src/js/templates/shipment/fromcsvcontainer.html b/client/src/js/templates/shipment/fromcsvcontainer.html new file mode 100644 index 000000000..eea78f63b --- /dev/null +++ b/client/src/js/templates/shipment/fromcsvcontainer.html @@ -0,0 +1,5 @@ +
    + Type: + Registered Container: + Owner: +
    diff --git a/client/src/js/templates/shipment/fromcsvtable.html b/client/src/js/templates/shipment/fromcsvtable.html new file mode 100644 index 000000000..696e046e5 --- /dev/null +++ b/client/src/js/templates/shipment/fromcsvtable.html @@ -0,0 +1,4 @@ + + + + diff --git a/client/src/js/templates/shipment/shipment.html b/client/src/js/templates/shipment/shipment.html index 4ff46e623..0cc1fc263 100644 --- a/client/src/js/templates/shipment/shipment.html +++ b/client/src/js/templates/shipment/shipment.html @@ -32,6 +32,10 @@

    Shipment: <%-SHIPPINGNAME%>

    Print Shipment Labels Print Contents + + <% if (app.config.csv_profile) { %> + Import from CSV + <% } %> <% } %> From 80708bd7dcac1dbffbb1c9973d645c1ceace838b Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:20:06 +0200 Subject: [PATCH 06/56] add barcode scan container assign page --- client/package.json | 1 + client/src/css/partials/_content.scss | 16 ++ client/src/js/modules/assign/controller.js | 18 +- client/src/js/modules/assign/router.js | 1 + .../src/js/modules/assign/views/scanassign.js | 236 ++++++++++++++++++ .../src/js/templates/assign/scanassign.html | 7 + 6 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 client/src/js/modules/assign/views/scanassign.js create mode 100644 client/src/js/templates/assign/scanassign.html diff --git a/client/package.json b/client/package.json index 666c76b85..a6a35cf6e 100644 --- a/client/package.json +++ b/client/package.json @@ -78,6 +78,7 @@ "jquery.flot.tooltip": "^0.9.0", "luxon": "^1.25.0", "markdown": "^0.5.0", + "papaparse": "^5.2.0", "plotly.js": "^1.52.2", "promise": "^8.0.3", "tailwindcss": "^1.9.5", diff --git a/client/src/css/partials/_content.scss b/client/src/css/partials/_content.scss index 2241a7611..0e629d617 100644 --- a/client/src/css/partials/_content.scss +++ b/client/src/css/partials/_content.scss @@ -2026,3 +2026,19 @@ ul.messages { } } } + + +.dropimage { + color: $content-search-background; + padding: 20px; + border: 2px dashed $content-search-background; + margin: 2% 0; + text-align: center; + border-radius: 5px; + + &.active { + color: $content-header-color; + background: $content-dark-background; + text-decoration: italic; + } +} \ No newline at end of file diff --git a/client/src/js/modules/assign/controller.js b/client/src/js/modules/assign/controller.js index a5a5e6a9f..d73cfc69e 100644 --- a/client/src/js/modules/assign/controller.js +++ b/client/src/js/modules/assign/controller.js @@ -4,7 +4,8 @@ define(['marionette', 'modules/assign/views/selectvisit', 'modules/assign/views/assign', - ], function(Marionette, Visit, Visits, SelectVisitView, AssignView) { + 'modules/assign/views/scanassign', + ], function(Marionette, Visit, Visits, SelectVisitView, AssignView, ScanAssignView) { var bc = { title: 'Assign Containers', url: '/assign' } @@ -16,7 +17,7 @@ define(['marionette', var visits = new Visits(null, { queryParams: { next: 1 } }) visits.fetch({ success: function() { - app.bc.reset([bc]), + app.bc.reset([bc]) app.content.show(new SelectVisitView({ collection: visits })) }, error: function() { @@ -45,7 +46,18 @@ define(['marionette', app.message({ title: 'No such visit', message: 'The specified visit doesnt exist' }) } }) - } + }, + + // A simple assign page by scanning barcodes + scanAssign: function(bl) { + if (!app.staff) { + app.message({ title: 'No access', message: 'You do not have access to that page' }) + return + } + + app.bc.reset([bc, { title: 'Assign Containers' }]) + app.content.show(new ScanAssignView({ bl: bl })) + }, } app.addInitializer(function() { diff --git a/client/src/js/modules/assign/router.js b/client/src/js/modules/assign/router.js index 4b61df45e..d7576902f 100644 --- a/client/src/js/modules/assign/router.js +++ b/client/src/js/modules/assign/router.js @@ -6,6 +6,7 @@ define(['marionette', 'modules/assign/controller'], function(Marionette, c) { appRoutes: { 'assign': 'selectVisit', 'assign/visit/:visit(/page/:page)': 'assignVisit', + 'assign/scan/:bl': 'scanAssign', }, loadEvents: ['assign:visit'], diff --git a/client/src/js/modules/assign/views/scanassign.js b/client/src/js/modules/assign/views/scanassign.js new file mode 100644 index 000000000..ea9f3456c --- /dev/null +++ b/client/src/js/modules/assign/views/scanassign.js @@ -0,0 +1,236 @@ +define(['marionette', 'backbone', + 'views/pages', + 'collections/containers', + 'modules/assign/collections/pucknames', + 'utils', + 'templates/assign/scanassign.html', + ], function(Marionette, + Backbone, + Pages, + Containers, + PuckNames, + utils, + template) { + + + var ContainerView = Marionette.CompositeView.extend({ + template: _.template(' View Container

    <%-PROP%>: <%-NAME%>

    '), + className: 'container assigned', + + events: { + click: 'unassignContainer' + }, + + // Unassign Containers + unassignContainer: function(e, options) { + if ($(e.target).is('a') || $(e.target).is('i')) return; + + console.log('this.beal', this.getOption('bl')) + utils.confirm({ + title: 'Confirm Container Unassignment', + content: 'Are you sure you want to unassign "'+this.model.get('NAME')+'" from sample changer position "'+this.model.get('SAMPLECHANGERLOCATION')+'"?', + callback: this.doUnAssign.bind(this, options) + }) + }, + + doUnAssign: function() { + Backbone.ajax({ + url: app.apiurl+'/assign/unassign', + data: { prop: this.model.get('PROP'), cid: this.model.get('CONTAINERID'), bl: this.getOption('bl') }, + success: this.unassignUpdateGUI.bind(this), + error: function() { + app.alert({ message: 'Something went wrong unassigning this container' }) + + }, + }) + }, + + unassignUpdateGUI: function() { + this.model.set({ SAMPLECHANGERLOCATION: null }) + } + + }) + + + // Sample Changer Positions + var PositionView = Marionette.CompositeView.extend({ + template: _.template('<%-id%>
    '), + className: 'bl_puck', + + childView: ContainerView, + childViewContainer: '.ac', + childViewOptions: function() { + return { + bl: this.getOption('bl') + } + }, + + ui: { + name: '.name', + barcode: 'input[name=barcode]', + }, + + events: { + click: 'focusInput', + 'change @ui.barcode': 'findContainer', + 'keyup @ui.barcode': 'findContainer', + }, + + collectionEvents: { + 'change reset': 'render', + }, + + focusInput: function() { + this.ui.barcode.focus() + }, + + findContainer: function() { + this.containers.fetch().done(this.assignContainer.bind(this)) + }, + + assignContainer: function() { + if (this.containers.length) { + var container = this.containers.at(0) + + utils.confirm({ + title: 'Confirm Assign Container', + content: 'Barcode matched "'+container.get('PROP')+': '+container.get('NAME')+'" from dewar "'+container.get('DEWAR')+'" with owner "'+container.get('OWNER')+'". Do you want to assign this to sample changer position "'+this.model.get('id')+'"?', + callback: this.doAssignContainer.bind(this) + }) + } else { + app.alert({ message: 'No containers found for barcode: '+this.ui.barcode.val() }) + } + + }, + + doAssignContainer: function() { + var container = this.containers.at(0) + Backbone.ajax({ + url: app.apiurl+'/assign/assign', + data: { + prop: container.get('PROP'), + cid: container.get('CONTAINERID'), + pos: this.model.get('id'), + bl: this.getOption('bl') + }, + success: this.assignUpdateGUI.bind(this), + error: function() { + app.alert({ message: 'Something went wrong assigning this container' }) + }, + }) + }, + + assignUpdateGUI: function() { + var container = this.containers.at(0) + container.set({ SAMPLECHANGERLOCATION: this.model.get('id').toString() }) + this.assigned.add(container) + }, + + getBarcode: function() { + return this.ui.barcode.val() + }, + + initialize: function(options) { + this.collection = new Containers() + this.assigned = options.assigned + this.listenTo(this.assigned, 'change:SAMPLECHANGERLOCATION change sync add remove', this.updateCollection, this) + this.updateCollection() + + this.listenTo(this.getOption('pucknames'), 'sync', this.getNameModel) + + this.assignContainer = _.debounce(this.assignContainer.bind(this), 500) + + this.containers = new Containers() + this.containers.queryParams.all = 1 + this.containers.queryParams.REGISTRY = this.getBarcode.bind(this) + }, + + getNameModel: function() { + this.name = this.getOption('pucknames').findWhere({ id: this.model.get('id') }) + if (this.name) { + this.listenTo(this.name, 'change update', this.updateName) + this.updateName() + } + }, + + updateName: function() { + if (this.name && this.name.get('name')) this.ui.name.text(' - '+this.name.get('name')) + }, + + updateCollection: function() { + this.collection.reset(this.assigned.findWhere({ SAMPLECHANGERLOCATION: this.model.get('id').toString() })) + }, + + onRender: function() { + this.updateName() + + if (this.collection.length > 0) { + this.ui.barcode.hide() + } + }, + + + }) + + + var SampleChangerView = Marionette.CollectionView.extend({ + className: 'clearfix', + childView: PositionView, + childViewOptions: function() { + return { + assigned: this.getOption('assigned'), + pucknames: this.getOption('pucknames'), + bl: this.getOption('bl'), + } + } + }) + + + + return Marionette.LayoutView.extend({ + template: template, + className: 'content', + + regions: { + rassigned: '.rassigned' + }, + + templateHelpers: function() { + return { + bl: this.getOption('bl'), + } + }, + + refresh: function() { + this.assigned.fetch() + }, + + initialize: function() { + this.assigned = new Containers(null, { queryParams: { assigned: 1, bl: this.getOption('bl'), all: 1 }, state: { pageSize: 9999 } }) + this.assigned.fetch() + + this.pucknames = new PuckNames() + this.pucknames.state.pageSize = 100 + this.pucknames.queryParams.bl = this.getOption('bl') + this.pucknames.fetch() + }, + + onShow: function() { + var pucks = this.getOption('bl') in app.config.pucks ? app.config.pucks[this.getOption('bl')] : 10 + + var positions = new Backbone.Collection(_.map(_.range(1,pucks+1), function(i) { return { id: i } })) + this.rassigned.show(new SampleChangerView({ + collection: positions, + assigned: this.assigned, + pucknames: this.pucknames, + bl: this.getOption('bl') + })) + + }, + + onDestroy: function() { + this.pucknames.stop() + }, + }) + +}) diff --git a/client/src/js/templates/assign/scanassign.html b/client/src/js/templates/assign/scanassign.html new file mode 100644 index 000000000..040f17981 --- /dev/null +++ b/client/src/js/templates/assign/scanassign.html @@ -0,0 +1,7 @@ +

    Container Allocation for <%-bl%>

    + +

    This page is designed to allocate containers via barcode scanning. Click on a sample change position and scan a barcode to assign it to that position. You will be presented with a list of matching containers

    + +
    +
    +
    From 3d6491101bbb5bab7c5d4abba373fc6c7387a643 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sun, 23 Aug 2020 12:42:34 +0200 Subject: [PATCH 07/56] add new config.json option --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9c5aa8fd3..a889f95f6 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ This file should be copied to create a client/src/js/config.json file and edited | site_name | Site Name to display in footer | | site_link | URL to site home page | | data_catalogue | Object that includes name and url property for a link to a data catalogue - displayed on the landing page | +| site_image | PNG image of site logo to display in header| +| csv_profile | The csv profile for importing shipments, currently only imca, see src/js/csv/imca.js | Site Image can be customised via the tailwind.config.js header-site-logo and footer-site-logo values. From f123ba1939a33e9c5089d0d45c83a6782d3b7c01 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sun, 23 Aug 2020 13:16:48 +0200 Subject: [PATCH 08/56] count distinct rather than groupby --- api/src/Page/Shipment.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 2f5eaaef8..c9740a1bc 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1799,13 +1799,12 @@ function _container_registry() { } - $tot = $this->db->pq("SELECT count(r.containerregistryid) as tot + $tot = $this->db->pq("SELECT count(distinct r.containerregistryid) as tot FROM containerregistry r LEFT OUTER JOIN containerregistry_has_proposal rhp on rhp.containerregistryid = r.containerregistryid LEFT OUTER JOIN proposal p ON p.proposalid = rhp.proposalid LEFT OUTER JOIN container c ON c.containerregistryid = r.containerregistryid - WHERE $where - GROUP BY r.containerregistryid", $args); + WHERE $where", $args); $tot = intval($tot[0]['TOT']); $pp = $this->has_arg('per_page') ? $this->arg('per_page') : 15; From 9a44717099b41ee2ad1000c0265379cafc7aa7b7 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 29 Aug 2020 17:15:08 +0200 Subject: [PATCH 09/56] dont fetch if val empty, debounce right function --- client/src/js/modules/assign/views/scanassign.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/js/modules/assign/views/scanassign.js b/client/src/js/modules/assign/views/scanassign.js index ea9f3456c..18575aaf3 100644 --- a/client/src/js/modules/assign/views/scanassign.js +++ b/client/src/js/modules/assign/views/scanassign.js @@ -85,7 +85,9 @@ define(['marionette', 'backbone', }, findContainer: function() { - this.containers.fetch().done(this.assignContainer.bind(this)) + if (this.ui.barcode.val().trim()) { + this.containers.fetch().done(this.assignContainer.bind(this)) + } }, assignContainer: function() { @@ -138,7 +140,7 @@ define(['marionette', 'backbone', this.listenTo(this.getOption('pucknames'), 'sync', this.getNameModel) - this.assignContainer = _.debounce(this.assignContainer.bind(this), 500) + this.findContainer = _.debounce(this.findContainer.bind(this), 500) this.containers = new Containers() this.containers.queryParams.all = 1 From 387f20db1dd040ee029cf0b5f28db48a5dc89e9e Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 31 Aug 2020 07:50:07 +0200 Subject: [PATCH 10/56] update breadcrumbs --- client/src/js/modules/assign/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/modules/assign/controller.js b/client/src/js/modules/assign/controller.js index d73cfc69e..4a97ac323 100644 --- a/client/src/js/modules/assign/controller.js +++ b/client/src/js/modules/assign/controller.js @@ -55,7 +55,7 @@ define(['marionette', return } - app.bc.reset([bc, { title: 'Assign Containers' }]) + app.bc.reset([{ title: 'Assign Containers' }, { title: 'Barcode Scan'}, { title: bl }]) app.content.show(new ScanAssignView({ bl: bl })) }, } From 4c6c6141b96f047884fc25174e46705648e20bd8 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 31 Aug 2020 07:50:16 +0200 Subject: [PATCH 11/56] punctuation --- client/src/js/templates/assign/scanassign.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/templates/assign/scanassign.html b/client/src/js/templates/assign/scanassign.html index 040f17981..75de719b5 100644 --- a/client/src/js/templates/assign/scanassign.html +++ b/client/src/js/templates/assign/scanassign.html @@ -1,6 +1,6 @@

    Container Allocation for <%-bl%>

    -

    This page is designed to allocate containers via barcode scanning. Click on a sample change position and scan a barcode to assign it to that position. You will be presented with a list of matching containers

    +

    This page is designed to allocate containers via barcode scanning. Click on a sample change position and scan a barcode to assign it to that position. You will be presented with a list of matching containers.

    From c0acf243d6a932591de72cd1dddcd6e12483aaee Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Tue, 1 Sep 2020 21:48:13 +0200 Subject: [PATCH 12/56] use concat_ws incase some values are null --- api/src/Page/Shipment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index c9740a1bc..24ca0cc2d 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1459,7 +1459,7 @@ function _get_all_containers() { count(distinct ss.blsubsampleid) as subsamples, ses3.beamlinename as firstexperimentbeamline, pp.name as pipeline, - TO_CHAR(max(cq2.completedtimestamp), 'HH24:MI DD-MM-YYYY') as lastqueuecompleted, TIMESTAMPDIFF('MINUTE', max(cq2.completedtimestamp), max(cq2.createdtimestamp)) as lastqueuedwell, c.ownerid, concat(pe.givenname, ' ', pe.familyname) as owner + TO_CHAR(max(cq2.completedtimestamp), 'HH24:MI DD-MM-YYYY') as lastqueuecompleted, TIMESTAMPDIFF('MINUTE', max(cq2.completedtimestamp), max(cq2.createdtimestamp)) as lastqueuedwell, c.ownerid, concat_ws(' ', pe.givenname, pe.familyname) as owner FROM container c INNER JOIN dewar d ON d.dewarid = c.dewarid LEFT OUTER JOIN blsession ses3 ON d.firstexperimentid = ses3.sessionid From 3bfa5a489d73dbadb6ba6fb6a6dd282222065a7a Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 14 Sep 2020 18:43:34 +0200 Subject: [PATCH 13/56] add collectionmode and priority --- api/src/Page/Sample.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index fcfb40548..a2181c112 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -958,7 +958,7 @@ function _samples() { $rows = $this->db->paginate("SELECT distinct b.blsampleid, b.crystalid, b.screencomponentgroupid, ssp.blsampleid as parentsampleid, ssp.name as parentsample, b.blsubsampleid, count(distinct si.blsampleimageid) as inspections, CONCAT(p.proposalcode,p.proposalnumber) as prop, b.code, b.location, pr.acronym, pr.proteinid, cr.spacegroup,b.comments,b.name,s.shippingname as shipment,s.shippingid,d.dewarid,d.code as dewar, c.code as container, c.containerid, c.samplechangerlocation as sclocation, count(distinct IF(dc.overlap != 0,dc.datacollectionid,NULL)) as sc, count(distinct IF(dc.overlap = 0 AND dc.axisrange = 0,dc.datacollectionid,NULL)) as gr, count(distinct IF(dc.overlap = 0 AND dc.axisrange > 0,dc.datacollectionid,NULL)) as dc, count(distinct IF(dcg.experimenttype LIKE 'XRF map', dc.datacollectionid, NULL)) as xm, count(distinct IF(dcg.experimenttype LIKE 'XRF spectrum', dc.datacollectionid, NULL)) as xs, count(distinct IF(dcg.experimenttype LIKE 'Energy scan', dc.datacollectionid, NULL)) as es, count(distinct so.screeningid) as ai, count(distinct app.autoprocprogramid) as ap, count(distinct r.robotactionid) as r, round(min(st.rankingresolution),2) as scresolution, max(ssw.completeness) as sccompleteness, round(min(apss.resolutionlimithigh),2) as dcresolution, round(max(apss.completeness),1) as dccompleteness, dp.anomalousscatterer, dp.requiredresolution, cr.cell_a, cr.cell_b, cr.cell_c, cr.cell_alpha, cr.cell_beta, cr.cell_gamma, b.packingfraction, b.dimension1, b.dimension2, b.dimension3, b.shape, cr.theoreticaldensity, cr.name as crystal, pr.name as protein, b.looptype, dp.centringmethod, dp.experimentkind, cq.containerqueueid, TO_CHAR(cq.createdtimestamp, 'DD-MM-YYYY HH24:MI') as queuedtimestamp , $cseq $sseq string_agg(cpr.name) as componentnames, string_agg(cpr.density) as componentdensities ,string_agg(cpr.proteinid) as componentids, string_agg(cpr.acronym) as componentacronyms, string_agg(cpr.global) as componentglobals, string_agg(chc.abundance) as componentamounts, string_agg(ct.symbol) as componenttypesymbols, b.volume, pct.symbol,ROUND(cr.abundance,3) as abundance, TO_CHAR(b.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp, dp.radiationsensitivity, dp.energy, dp.userpath, - dp.aimedresolution, dp.preferredbeamsizex, dp.preferredbeamsizey, dp.exposuretime, dp.axisstart, dp.axisrange, dp.numberofimages, dp.transmission, + dp.aimedresolution, dp.preferredbeamsizex, dp.preferredbeamsizey, dp.exposuretime, dp.axisstart, dp.axisrange, dp.numberofimages, dp.transmission, dp.collectionmode, dp.priority, GROUP_CONCAT(dcc.comments, ', ') as dccomments FROM blsample b @@ -1059,8 +1059,8 @@ function _update_sample_full() { if (array_key_exists('PROTEINID', $a)) { $this->db->pq("UPDATE crystal set spacegroup=:1,proteinid=:2,cell_a=:3,cell_b=:4,cell_c=:5,cell_alpha=:6,cell_beta=:7,cell_gamma=:8,theoreticaldensity=:9 WHERE crystalid=:10", array($a['SPACEGROUP'], $a['PROTEINID'], $a['CELL_A'], $a['CELL_B'], $a['CELL_C'], $a['CELL_ALPHA'], $a['CELL_BETA'], $a['CELL_GAMMA'], $a['THEORETICALDENSITY'], $samp['CRYSTALID'])); - $this->db->pq("UPDATE diffractionplan set anomalousscatterer=:1,requiredresolution=:2, experimentkind=:3, centringmethod=:4, radiationsensitivity=:5, energy=:6, userpath=:7, aimedresolution=:8, preferredbeamsizex=:9, preferredbeamsizey=:10, exposuretime=:11, axisstart=:12, axisrange=:13, numberofimages=:14, transmission=:15 WHERE diffractionplanid=:16", - array($a['ANOMALOUSSCATTERER'], $a['REQUIREDRESOLUTION'], $a['EXPERIMENTKIND'], $a['CENTRINGMETHOD'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['AIMEDRESOLUTION'], $a['PREFERREDBEAMSIZEX'], $a['PREFERREDBEAMSIZEY'], $a['EXPOSURETIME'], $a['AXISSTART'], $a['AXISRANGE'], $a['NUMBEROFIMAGES'], $a['TRANSMISSION'], + $this->db->pq("UPDATE diffractionplan set anomalousscatterer=:1,requiredresolution=:2, experimentkind=:3, centringmethod=:4, radiationsensitivity=:5, energy=:6, userpath=:7, aimedresolution=:8, preferredbeamsizex=:9, preferredbeamsizey=:10, exposuretime=:11, axisstart=:12, axisrange=:13, numberofimages=:14, transmission=:15, collectionmode=:16, priority=:17 WHERE diffractionplanid=:18", + array($a['ANOMALOUSSCATTERER'], $a['REQUIREDRESOLUTION'], $a['EXPERIMENTKIND'], $a['CENTRINGMETHOD'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['AIMEDRESOLUTION'], $a['PREFERREDBEAMSIZEX'], $a['PREFERREDBEAMSIZEY'], $a['EXPOSURETIME'], $a['AXISSTART'], $a['AXISRANGE'], $a['NUMBEROFIMAGES'], $a['TRANSMISSION'], $a['COLLECTIONMODE'], $a['PRIORITY'], $samp['DIFFRACTIONPLANID'])); } @@ -1161,8 +1161,8 @@ function _prepare_sample_args($s=null) { function _do_add_sample($a) { - $this->db->pq("INSERT INTO diffractionplan (diffractionplanid, requiredresolution, anomalousscatterer, centringmethod, experimentkind, radiationsensitivity, energy, userpath, aimedresolution, preferredbeamsizex, preferredbeamsizey, exposuretime, axisstart, axisrange, numberofimages, transmission) VALUES (s_diffractionplan.nextval, :1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11, :12, :13, :14, :15) RETURNING diffractionplanid INTO :id", - array($a['REQUIREDRESOLUTION'], $a['ANOMALOUSSCATTERER'], $a['CENTRINGMETHOD'], $a['EXPERIMENTKIND'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['AIMEDRESOLUTION'], $a['PREFERREDBEAMSIZEX'], $a['PREFERREDBEAMSIZEY'], $a['EXPOSURETIME'], $a['AXISSTART'], $a['AXISRANGE'], $a['NUMBEROFIMAGES'], $a['TRANSMISSION'])); + $this->db->pq("INSERT INTO diffractionplan (diffractionplanid, requiredresolution, anomalousscatterer, centringmethod, experimentkind, radiationsensitivity, energy, userpath, aimedresolution, preferredbeamsizex, preferredbeamsizey, exposuretime, axisstart, axisrange, numberofimages, transmission, collectionmode, priority) VALUES (s_diffractionplan.nextval, :1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11, :12, :13, :14, :15, :16, :17) RETURNING diffractionplanid INTO :id", + array($a['REQUIREDRESOLUTION'], $a['ANOMALOUSSCATTERER'], $a['CENTRINGMETHOD'], $a['EXPERIMENTKIND'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['AIMEDRESOLUTION'], $a['PREFERREDBEAMSIZEX'], $a['PREFERREDBEAMSIZEY'], $a['EXPOSURETIME'], $a['AXISSTART'], $a['AXISRANGE'], $a['NUMBEROFIMAGES'], $a['TRANSMISSION'], $a['COLLECTIONMODE'], $a['PRIORITY'])); $did = $this->db->id(); if (!array_key_exists('CRYSTALID', $a)) { @@ -1466,7 +1466,7 @@ function _update_sample() { } } - $dfields = array('REQUIREDRESOLUTION', 'ANOMALOUSSCATTERER', 'CENTRINGMETHOD', 'EXPERIMENTKIND', 'RADIATIONSENSITIVITY', 'ENERGY', 'USERPATH', 'AIMEDRESOLUTION', 'PREFERREDBEAMSIZEX', 'PREFERREDBEAMSIZEY', 'EXPOSURETIME', 'AXISSTART', 'AXISRANGE', 'NUMBEROFIMAGES', 'TRANSMISSION'); + $dfields = array('REQUIREDRESOLUTION', 'ANOMALOUSSCATTERER', 'CENTRINGMETHOD', 'EXPERIMENTKIND', 'RADIATIONSENSITIVITY', 'ENERGY', 'USERPATH', 'AIMEDRESOLUTION', 'PREFERREDBEAMSIZEX', 'PREFERREDBEAMSIZEY', 'EXPOSURETIME', 'AXISSTART', 'AXISRANGE', 'NUMBEROFIMAGES', 'TRANSMISSION', 'COLLECTIONMODE', 'PRIORITY'); foreach ($dfields as $f) { if ($this->has_arg($f)) { $this->db->pq("UPDATE diffractionplan SET $f=:1 WHERE diffractionplanid=:2", array($this->arg($f), $samp['DIFFRACTIONPLANID'])); From f4d07e57cdc3af8566a60a473771f2abf8494b26 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Wed, 16 Sep 2020 21:06:14 +0200 Subject: [PATCH 14/56] add other csv mime types --- client/src/js/modules/shipment/views/fromcsv.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/js/modules/shipment/views/fromcsv.js b/client/src/js/modules/shipment/views/fromcsv.js index 9525ff14f..327d68d75 100644 --- a/client/src/js/modules/shipment/views/fromcsv.js +++ b/client/src/js/modules/shipment/views/fromcsv.js @@ -432,13 +432,16 @@ define(['backbone', var files = e.originalEvent.dataTransfer.files var f = files[0] - if (f.type == 'text/csv') { + var types = ['text/csv', 'application/vnd.ms-excel', 'application/csv', 'text/x-csv', 'application/x-csv', 'text/comma-separated-values', 'text/x-comma-separated-values'] + if (types.indexOf(f.type) > -1) { var reader = new FileReader() var self = this reader.onload = function(e) { self.parseCSVContents(e.target.result) } reader.readAsText(f) + } else { + app.alert({ message: 'Cannot import file, type "'+f.type+'" is not in allowed formats: '+types.join(', ') }) } }, From 2f4e62b0784c3cee6c5bd2bbe215e306302bf040 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Thu, 17 Sep 2020 08:07:15 +0200 Subject: [PATCH 15/56] mime type is not sent on windows :| --- client/src/js/modules/shipment/views/fromcsv.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/js/modules/shipment/views/fromcsv.js b/client/src/js/modules/shipment/views/fromcsv.js index 327d68d75..97e3391e4 100644 --- a/client/src/js/modules/shipment/views/fromcsv.js +++ b/client/src/js/modules/shipment/views/fromcsv.js @@ -432,8 +432,7 @@ define(['backbone', var files = e.originalEvent.dataTransfer.files var f = files[0] - var types = ['text/csv', 'application/vnd.ms-excel', 'application/csv', 'text/x-csv', 'application/x-csv', 'text/comma-separated-values', 'text/x-comma-separated-values'] - if (types.indexOf(f.type) > -1) { + if (f.name.endsWith('csv')) { var reader = new FileReader() var self = this reader.onload = function(e) { @@ -441,7 +440,7 @@ define(['backbone', } reader.readAsText(f) } else { - app.alert({ message: 'Cannot import file, type "'+f.type+'" is not in allowed formats: '+types.join(', ') }) + app.alert({ message: 'Cannot import file "'+f.name+'" is not a csv file' }) } }, From c534cf4d59109986768f6b8078bbbfa4f8b4ffe3 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Tue, 22 Sep 2020 22:16:05 +0200 Subject: [PATCH 16/56] increase page size for sample --- client/src/js/modules/shipment/views/fromcsv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/modules/shipment/views/fromcsv.js b/client/src/js/modules/shipment/views/fromcsv.js index 97e3391e4..9a46fcc08 100644 --- a/client/src/js/modules/shipment/views/fromcsv.js +++ b/client/src/js/modules/shipment/views/fromcsv.js @@ -384,7 +384,7 @@ define(['backbone', initialize: function(options) { this.messages = new Backbone.Collection() - this.samples = new Samples() + this.samples = new Samples(null, { state: { pageSize: 9999 }}) this.samples.queryParams.SHIPPINGID = this.model.get('SHIPPINGID') this.samples.fetch() From 9cc0d66587c541ddb7f9eb5ca16ee4ea448b10e6 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Wed, 23 Dec 2020 22:03:20 +0100 Subject: [PATCH 17/56] validate barcode on scan assign page --- .../src/js/modules/assign/views/scanassign.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/client/src/js/modules/assign/views/scanassign.js b/client/src/js/modules/assign/views/scanassign.js index 18575aaf3..9eedbed6d 100644 --- a/client/src/js/modules/assign/views/scanassign.js +++ b/client/src/js/modules/assign/views/scanassign.js @@ -2,16 +2,21 @@ define(['marionette', 'backbone', 'views/pages', 'collections/containers', 'modules/assign/collections/pucknames', + 'modules/shipment/models/containerregistry', 'utils', 'templates/assign/scanassign.html', + 'backbone-validation' ], function(Marionette, Backbone, Pages, Containers, PuckNames, + ContainerRegistry, utils, template) { + var ValidatedContainerRegistry = ContainerRegistry.extend({}) + _.extend(ValidatedContainerRegistry.prototype, Backbone.Validation.mixin); var ContainerView = Marionette.CompositeView.extend({ template: _.template(' View Container

    <%-PROP%>: <%-NAME%>

    '), @@ -85,11 +90,20 @@ define(['marionette', 'backbone', }, findContainer: function() { - if (this.ui.barcode.val().trim()) { + if (this.ui.barcode.val() && this.validate()) { this.containers.fetch().done(this.assignContainer.bind(this)) } }, + validate: function() { + var error = this.registryModel.preValidate('BARCODE', this.ui.barcode.val()) + + if (error) this.ui.barcode.addClass('ferror').removeClass('fvalid') + else this.ui.barcode.removeClass('ferror').addClass('fvalid') + + return error ? false : true + }, + assignContainer: function() { if (this.containers.length) { var container = this.containers.at(0) @@ -145,6 +159,8 @@ define(['marionette', 'backbone', this.containers = new Containers() this.containers.queryParams.all = 1 this.containers.queryParams.REGISTRY = this.getBarcode.bind(this) + + this.registryModel = new ValidatedContainerRegistry() }, getNameModel: function() { From 6936828be198529ebc8b057772adef1f78aa59ec Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 10 Apr 2021 09:05:39 +0200 Subject: [PATCH 18/56] wip: mailin enhancements --- api/src/Page/Assign.php | 17 +- api/src/Page/Sample.php | 20 +- api/src/Page/Shipment.php | 2 +- client/src/js/csv/imca.js | 2 +- .../src/js/modules/assign/views/scanassign.js | 5 +- client/src/js/modules/shipment/controller.js | 28 ++- client/src/js/modules/shipment/router.js | 1 + .../modules/shipment/views/containerreview.js | 182 ++++++++++++++++++ 8 files changed, 246 insertions(+), 11 deletions(-) create mode 100644 client/src/js/modules/shipment/views/containerreview.js diff --git a/api/src/Page/Assign.php b/api/src/Page/Assign.php index 62e8ede2c..2834fdf9f 100644 --- a/api/src/Page/Assign.php +++ b/api/src/Page/Assign.php @@ -7,7 +7,7 @@ class Assign extends Page { - public static $arg_list = array('visit' => '\w+\d+-\d+', 'cid' => '\d+', 'did' => '\d+', 'pos' => '\d+', 'bl' => '[\w-]+'); + public static $arg_list = array('visit' => '\w+\d+-\d+', 'cid' => '\d+', 'did' => '\d+', 'pos' => '\d+', 'bl' => '[\w-]+', 'nodup' => '\d'); public static $dispatch = array(array('/visits(/:visit)', 'get', '_blsr_visits'), array('/assign', 'get', '_assign'), @@ -55,6 +55,21 @@ function _assign() { } } + if ($this->has_arg(('nodup'))) { + $existing = $this->db->pq("SELECT c.containerid, c.name, CONCAT(p.proposalcode, p.proposalnumber) as prop + FROM container c + INNER JOIN dewar d ON d.dewarid = c.dewarid + INNER JOIN shipping s ON s.shippingid = d.shippingid + INNER JOIN proposal p ON s.proposalid = s.proposalid + WHERE beamlinelocation=1 AND samplechangerlocation:2", array($bl, $this->arg('pos'))); + + if (sizeof($existing)) { + $ex = $existing[0]; + return $this->_error('A container is already a assigned that position: '+$ex[0]['NAME'] + '('+$ex['PROP']+')'); + } + } + + $this->db->pq("UPDATE dewar SET dewarstatus='processing' WHERE dewarid=:1", array($c['DEWARID'])); $this->db->pq("UPDATE container SET beamlinelocation=:1,samplechangerlocation=:2,containerstatus='processing' WHERE containerid=:3", array($bl, $this->arg('pos'), $c['CONTAINERID'])); diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index a2181c112..2d8c513b2 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -64,6 +64,8 @@ class Sample extends Page 'NAME' => '[\w\s-()]+', 'COMMENTS' => '.*', 'SPACEGROUP' => '(\w|\s|\-|\/)+|^$', // Any word character (inc spaces bars and slashes) or empty string + 'STAFFCOMMENTS' => '.*', + 'CELL_A' => '\d+(.\d+)?', 'CELL_B' => '\d+(.\d+)?', 'CELL_C' => '\d+(.\d+)?', @@ -955,11 +957,15 @@ function _samples() { if (array_key_exists($this->arg('sort_by'), $cols)) $order = $cols[$this->arg('sort_by')].' '.$dir; } - $rows = $this->db->paginate("SELECT distinct b.blsampleid, b.crystalid, b.screencomponentgroupid, ssp.blsampleid as parentsampleid, ssp.name as parentsample, b.blsubsampleid, count(distinct si.blsampleimageid) as inspections, CONCAT(p.proposalcode,p.proposalnumber) as prop, b.code, b.location, pr.acronym, pr.proteinid, cr.spacegroup,b.comments,b.name,s.shippingname as shipment,s.shippingid,d.dewarid,d.code as dewar, c.code as container, c.containerid, c.samplechangerlocation as sclocation, count(distinct IF(dc.overlap != 0,dc.datacollectionid,NULL)) as sc, count(distinct IF(dc.overlap = 0 AND dc.axisrange = 0,dc.datacollectionid,NULL)) as gr, count(distinct IF(dc.overlap = 0 AND dc.axisrange > 0,dc.datacollectionid,NULL)) as dc, count(distinct IF(dcg.experimenttype LIKE 'XRF map', dc.datacollectionid, NULL)) as xm, count(distinct IF(dcg.experimenttype LIKE 'XRF spectrum', dc.datacollectionid, NULL)) as xs, count(distinct IF(dcg.experimenttype LIKE 'Energy scan', dc.datacollectionid, NULL)) as es, count(distinct so.screeningid) as ai, count(distinct app.autoprocprogramid) as ap, count(distinct r.robotactionid) as r, round(min(st.rankingresolution),2) as scresolution, max(ssw.completeness) as sccompleteness, round(min(apss.resolutionlimithigh),2) as dcresolution, round(max(apss.completeness),1) as dccompleteness, dp.anomalousscatterer, dp.requiredresolution, cr.cell_a, cr.cell_b, cr.cell_c, cr.cell_alpha, cr.cell_beta, cr.cell_gamma, b.packingfraction, b.dimension1, b.dimension2, b.dimension3, b.shape, cr.theoreticaldensity, cr.name as crystal, pr.name as protein, b.looptype, dp.centringmethod, dp.experimentkind, cq.containerqueueid, TO_CHAR(cq.createdtimestamp, 'DD-MM-YYYY HH24:MI') as queuedtimestamp + $rows = $this->db->paginate("SELECT distinct b.blsampleid, b.crystalid, b.screencomponentgroupid, ssp.blsampleid as parentsampleid, ssp.name as parentsample, b.blsubsampleid, count(distinct si.blsampleimageid) as inspections, CONCAT(p.proposalcode,p.proposalnumber) as prop, b.code, b.location, pr.acronym, pr.proteinid, cr.spacegroup,b.comments, + b.staffcomments, + b.name,s.shippingname as shipment,s.shippingid,d.dewarid,d.code as dewar, c.code as container, c.containerid, c.samplechangerlocation as sclocation, count(distinct IF(dc.overlap != 0,dc.datacollectionid,NULL)) as sc, count(distinct IF(dc.overlap = 0 AND dc.axisrange = 0,dc.datacollectionid,NULL)) as gr, count(distinct IF(dc.overlap = 0 AND dc.axisrange > 0,dc.datacollectionid,NULL)) as dc, + count(distinct IF(dcg.experimenttype LIKE 'XRF map', dc.datacollectionid, NULL)) as xm, count(distinct IF(dcg.experimenttype LIKE 'XRF spectrum', dc.datacollectionid, NULL)) as xs, count(distinct IF(dcg.experimenttype LIKE 'Energy scan', dc.datacollectionid, NULL)) as es, + count(distinct so.screeningid) as ai, count(distinct app.autoprocprogramid) as ap, count(distinct r.robotactionid) as r, round(min(st.rankingresolution),2) as scresolution, max(ssw.completeness) as sccompleteness, round(min(apss.resolutionlimithigh),2) as dcresolution, round(max(apss.completeness),1) as dccompleteness, dp.anomalousscatterer, dp.requiredresolution, cr.cell_a, cr.cell_b, cr.cell_c, cr.cell_alpha, cr.cell_beta, cr.cell_gamma, b.packingfraction, b.dimension1, b.dimension2, b.dimension3, b.shape, cr.theoreticaldensity, cr.name as crystal, pr.name as protein, b.looptype, dp.centringmethod, dp.experimentkind, cq.containerqueueid, TO_CHAR(cq.createdtimestamp, 'DD-MM-YYYY HH24:MI') as queuedtimestamp , $cseq $sseq string_agg(cpr.name) as componentnames, string_agg(cpr.density) as componentdensities ,string_agg(cpr.proteinid) as componentids, string_agg(cpr.acronym) as componentacronyms, string_agg(cpr.global) as componentglobals, string_agg(chc.abundance) as componentamounts, string_agg(ct.symbol) as componenttypesymbols, b.volume, pct.symbol,ROUND(cr.abundance,3) as abundance, TO_CHAR(b.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp, dp.radiationsensitivity, dp.energy, dp.userpath, dp.aimedresolution, dp.preferredbeamsizex, dp.preferredbeamsizey, dp.exposuretime, dp.axisstart, dp.axisrange, dp.numberofimages, dp.transmission, dp.collectionmode, dp.priority, - GROUP_CONCAT(dcc.comments, ', ') as dccomments + GROUP_CONCAT(distinct a.spacegroup) as dcspacegroup FROM blsample b @@ -993,6 +999,8 @@ function _samples() { LEFT OUTER JOIN autoprocscaling_has_int aph ON aph.autoprocintegrationid = ap.autoprocintegrationid LEFT OUTER JOIN autoprocscalingstatistics apss ON apss.autoprocscalingid = aph.autoprocscalingid LEFT OUTER JOIN autoprocprogram app ON app.autoprocprogramid = ap.autoprocprogramid AND app.processingstatus = 1 + LEFT OUTER JOIN autoprocscaling aps ON aph.autoprocscalingid = aps.autoprocscalingid + LEFT OUTER JOIN autoproc a ON aps.autoprocid = a.autoprocid LEFT OUTER JOIN blsampleimage si ON b.blsampleid = si.blsampleid @@ -1053,8 +1061,8 @@ function _update_sample_full() { if (!sizeof($samp)) $this->_error('No such sample'); else $samp = $samp[0]; - $this->db->pq("UPDATE blsample set name=:1,comments=:2,code=:3,volume=:4,packingfraction=:5,dimension1=:6,dimension2=:7,dimension3=:8,shape=:9,looptype=:10 WHERE blsampleid=:11", - array($a['NAME'],$a['COMMENTS'],$a['CODE'],$a['VOLUME'],$a['PACKINGFRACTION'],$a['DIMENSION1'],$a['DIMENSION2'],$a['DIMENSION3'],$a['SHAPE'],$a['LOOPTYPE'],$this->arg('sid'))); + $this->db->pq("UPDATE blsample set name=:1,comments=:2,code=:3,volume=:4,packingfraction=:5,dimension1=:6,dimension2=:7,dimension3=:8,shape=:9,looptype=:10,staffcomments=:11 WHERE blsampleid=:12", + array($a['NAME'],$a['COMMENTS'],$a['CODE'],$a['VOLUME'],$a['PACKINGFRACTION'],$a['DIMENSION1'],$a['DIMENSION2'],$a['DIMENSION3'],$a['SHAPE'],$a['LOOPTYPE'],$a['STAFFCOMMENTS'],$this->arg('sid'))); if (array_key_exists('PROTEINID', $a)) { $this->db->pq("UPDATE crystal set spacegroup=:1,proteinid=:2,cell_a=:3,cell_b=:4,cell_c=:5,cell_alpha=:6,cell_beta=:7,cell_gamma=:8,theoreticaldensity=:9 WHERE crystalid=:10", @@ -1146,7 +1154,7 @@ function _prepare_sample_args($s=null) { if (!$haskey) $this->_error('One or more fields is missing'); - foreach (array('COMMENTS', 'SPACEGROUP', 'CODE', 'ANOMALOUSSCATTERER', 'COLLECTIONMODE') as $f) { + foreach (array('COMMENTS', 'STAFFCOMMENTS', 'SPACEGROUP', 'CODE', 'ANOMALOUSSCATTERER', 'COLLECTIONMODE') as $f) { if ($s) $a[$f] = array_key_exists($f, $s) ? $s[$f] : ''; else $a[$f] = $this->has_arg($f) ? $this->arg($f) : ''; } @@ -1444,7 +1452,7 @@ function _update_sample() { $maxLocation = $this->_get_current_max_dcp_plan_order($this->args['CONTAINERID']); $maxLocation = sizeof($maxLocation) ? $maxLocation : -1; - $sfields = array('CODE', 'NAME', 'COMMENTS', 'VOLUME', 'PACKINGFRACTION', 'DIMENSION1', 'DIMENSION2', 'DIMENSION3', 'SHAPE', 'POSITION', 'CONTAINERID', 'LOOPTYPE', 'LOCATION'); + $sfields = array('CODE', 'NAME', 'COMMENTS', 'STAFFCOMMENTS', 'VOLUME', 'PACKINGFRACTION', 'DIMENSION1', 'DIMENSION2', 'DIMENSION3', 'SHAPE', 'POSITION', 'CONTAINERID', 'LOOPTYPE', 'LOCATION'); foreach ($sfields as $f) { if ($this->has_arg($f)) { $this->db->pq("UPDATE blsample SET $f=:1 WHERE blsampleid=:2", array($this->arg($f), $samp['BLSAMPLEID'])); diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 24ca0cc2d..fab6c7f8b 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1433,7 +1433,7 @@ function _get_all_containers() { array_push($args, $start); array_push($args, $end); - $order = 'c.bltimestamp DESC'; + $order = 'c.containerid DESC'; if ($this->has_arg('ty')) { if ($this->arg('ty') == 'todispose') { diff --git a/client/src/js/csv/imca.js b/client/src/js/csv/imca.js index c30579501..45cd28ac8 100644 --- a/client/src/js/csv/imca.js +++ b/client/src/js/csv/imca.js @@ -5,7 +5,7 @@ define([], function() { headers: ['Puck', 'Pin', 'Project', 'Priority', 'Mode', 'Notes to Staff', 'Collection strategy', 'Contact person', 'Expected space group', 'Expected Cell Dimensions', 'Expected Resolution', 'Minimum Resolution Required to Collect', 'Recipe', 'Exposure time', 'Image Width', 'Phi', 'Attenuation', 'Aperture', 'Detector Distance', 'Prefix for frames', 'Observed Resolution', 'Comments From Staff', 'Status'], // ... and their ISPyB table mapping - mapping: ['CONTAINER', 'LOCATION', 'ACRONYM', 'PRIORITY', 'COLLECTIONMODE', 'COMMENTS', 'COMMENTS', 'OWNER', 'SPACEGROUP', 'CELL', 'AIMEDRESOLUTION', 'REQUIREDRESOLUTION', 'RECIPE', 'EXPOSURETIME', 'AXISRANGE', 'AXISROTATION', 'TRANSMISSION', 'PREFERREDBEAMSIZEX', 'DETECTORDISTANCE', 'PREFIX', 'DCRESOLUTION', 'DCCOMMENTS', 'STATUS'], + mapping: ['CONTAINER', 'LOCATION', 'ACRONYM', 'PRIORITY', 'COLLECTIONMODE', 'COMMENTS', 'COMMENTS', 'OWNER', 'SPACEGROUP', 'CELL', 'AIMEDRESOLUTION', 'REQUIREDRESOLUTION', 'RECIPE', 'EXPOSURETIME', 'AXISRANGE', 'AXISROTATION', 'TRANSMISSION', 'PREFERREDBEAMSIZEX', 'DETECTORDISTANCE', 'PREFIX', 'DCRESOLUTION', 'STAFFCOMMENTS', 'STATUS'], // Columns to show on the import page columns: { diff --git a/client/src/js/modules/assign/views/scanassign.js b/client/src/js/modules/assign/views/scanassign.js index 9eedbed6d..02b8f0641 100644 --- a/client/src/js/modules/assign/views/scanassign.js +++ b/client/src/js/modules/assign/views/scanassign.js @@ -41,7 +41,10 @@ define(['marionette', 'backbone', doUnAssign: function() { Backbone.ajax({ url: app.apiurl+'/assign/unassign', - data: { prop: this.model.get('PROP'), cid: this.model.get('CONTAINERID'), bl: this.getOption('bl') }, + data: { + nodup: 1, + prop: this.model.get('PROP'), cid: this.model.get('CONTAINERID'), bl: this.getOption('bl') + }, success: this.unassignUpdateGUI.bind(this), error: function() { app.alert({ message: 'Something went wrong unassigning this container' }) diff --git a/client/src/js/modules/shipment/controller.js b/client/src/js/modules/shipment/controller.js index 5fbfb72ff..0f42929ba 100644 --- a/client/src/js/modules/shipment/controller.js +++ b/client/src/js/modules/shipment/controller.js @@ -16,6 +16,7 @@ define(['backbone', // 'modules/shipment/views/containeradd', 'modules/shipment/views/containers', 'modules/imaging/views/queuecontainer', + 'modules/shipment/views/containerreview', 'modules/shipment/models/containerregistry', 'modules/shipment/collections/containerregistry', @@ -49,7 +50,8 @@ define(['backbone', GetView, Dewar, Shipment, Shipments, ShipmentsView, ShipmentView, ShipmentAddView, ImportFromCSV, - Container, Containers, ContainerView, ContainerPlateView, /*ContainerAddView,*/ ContainersView, QueueContainerView, + Container, Containers, ContainerView, ContainerPlateView, /*ContainerAddView,*/ ContainersView, QueueContainerView, + MailinContainers, ReviewContainer, ContainerRegistry, ContainersRegistry, ContainerRegistryView, RegisteredContainer, RegisteredDewar, DewarRegistry, DewarRegView, RegDewarView, RegDewarAddView, DewarRegistryView, DispatchView, TransferView, Dewars, DewarOverview, ManifestView, DewarStats, CreateAWBView, RebookPickupView, @@ -357,6 +359,30 @@ define(['backbone', }) }, + container_review: function(cid) { + var lookup = new ProposalLookup({ field: 'CONTAINERID', value: cid }) + lookup.find({ + success: function() { + var container = new Container({ CONTAINERID: cid }) + container.fetch({ + success: function() { + app.bc.reset([bc, { title: container.get('SHIPMENT'), url: '/shipments/sid/'+container.get('SHIPPINGID') }, { title: 'Containers' }, { title: 'Review' }, { title: container.get('NAME') }]) + app.content.show(new ReviewContainer({ model: container })) + }, + error: function() { + app.bc.reset([bc, { title: 'No such container' }]) + app.message({ title: 'No such container', message: 'The specified container could not be found'}) + }, + }) + }, + + error: function() { + app.bc.reset([bc, { title: 'No such container' }]) + app.message({ title: 'No such container', message: 'The specified container could not be found'}) + } + }) + }, + dewar_registry: function(ty, s, page) { app.loading() diff --git a/client/src/js/modules/shipment/router.js b/client/src/js/modules/shipment/router.js index 7e2fc5b6f..938100095 100644 --- a/client/src/js/modules/shipment/router.js +++ b/client/src/js/modules/shipment/router.js @@ -18,6 +18,7 @@ define(['utils/lazyrouter'], function(LazyRouter) { 'containers(/s/:s)(/ty/:ty)(/page/:page)': 'container_list', 'containers/registry(/ty/:ty)(/s/:s)(/page/:page)': 'container_registry', 'containers/registry/:crid': 'view_rcontainer', + 'containers/review/:cid': 'container_review', 'dewars(/s/:s)(/page/:page)': 'dewar_list', 'dewars/dispatch/:did': 'dispatch_dewar', diff --git a/client/src/js/modules/shipment/views/containerreview.js b/client/src/js/modules/shipment/views/containerreview.js new file mode 100644 index 000000000..fb78b4691 --- /dev/null +++ b/client/src/js/modules/shipment/views/containerreview.js @@ -0,0 +1,182 @@ +define(['marionette', + 'backgrid', + 'views/table', + 'utils/table', + 'collections/samples', +], function(Marionette, + Backgrid, + TableView, + table, + Samples) { + + var UCTemplate = '\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
    ABCαβγ
    <%-CELL_A%><%-CELL_B%><%-CELL_C%><%-CELL_ALPHA%><%-CELL_BETA%><%-CELL_GAMMA%>
    ' + + var ActionCell = Backgrid.Cell.extend({ + events: { + 'click a.reinspect': 'markInspect', + 'click a.skip': 'markSkip' + }, + + render: function() { + if (app.staff) { + this.$el.html('') + this.$el.append(' ') + } + + return this + } + }) + + return Marionette.LayoutView.extend({ + className: 'content', + template: _.template('

    Review: <%-NAME%>

    '), + + regions: { + rsamples: '.rsamples' + }, + + initialize: function(options) { + this.samples = new Samples(null, { state: { pageSize: 9999 } }) + this.samples.queryParams.cid = options.model.get('CONTAINERID') + this.samples.fetch() + }, + + onRender: function() { + var columns = [ + { name: 'LOCATION', label: 'Location', cell: 'string', editable: false }, + { name: 'NAME', label: 'Name', cell: 'string', editable: false }, + { name: 'ACRONYM', label: 'Protein', cell: 'string', editable: false }, + { name: 'COMMENTS', label: 'Comment', cell: 'string', editable: false }, + { name: 'SPACEGROUP', label: 'SG', cell: 'string', editable: false }, + { label: 'Unit Cell', cell: table.TemplateCell, template: UCTemplate, editable: false }, + { name: 'REQUIREDRESOLUTION', label: 'Required Res', cell: 'string', editable: false }, + { name: 'AIMEDRESOLUTION', label: 'Aimed Res', cell: 'string', editable: false }, + { name: 'COLLECTIONMODE', label: 'Mode', cell: 'string', editable: false }, + { name: 'EXPOSURETIME', label: 'Exposure (s)', cell: 'string', editable: false }, + { name: 'AXISRANGE', label: 'Axis Range', cell: 'string', editable: false }, + { name: 'AXISSTART', label: 'No Images', cell: 'string', editable: false }, + { name: 'TRASMISSION', label: 'Transmission', cell: 'string', editable: false }, + { name: 'DCRESOLUTION', label: 'Observed Res', cell: 'string', editable: false }, + { name: 'DCSPACEGROUP', label: 'Observed SG', cell: 'string', editable: false }, + { name: 'STAFFCOMMENTS', label: 'Staff Comments', cell: 'string', editable: app.staff }, + { label: 'Status', cell: table.StatusCell, editable: false }, + { label: '', cell: ActionCell, editable: false }, + ] + + this.rsamples.show(new TableView({ + collection: this.samples, + columns: columns + })) + + this.listenTo(this.samples, 'change:STAFFCOMMENTS', this.saveStaffComment, this) + }, + + saveStaffComment: function(m, v) { + m.save(m.changedAttributes(), { patch: true }) + }, + }) +}) + +a = { + "BLSAMPLEID": "3221723", + "CRYSTALID": "2794556", + "SCREENCOMPONENTGROUPID": null, + "PARENTSAMPLEID": null, + "PARENTSAMPLE": null, + "BLSUBSAMPLEID": null, + "INSPECTIONS": "0", + "PROP": "cm28170", + "CODE": "", + "LOCATION": "16", + "ACRONYM": "Blah", + "PROTEINID": "440420", + "SPACEGROUP": "", + "COMMENTS": "", + "STAFFCOMMENTS": null, + "NAME": "xtal_32", + "SHIPMENT": "test_samples", + "SHIPPINGID": "39740", + "DEWARID": "45092", + "DEWAR": "DLS-MX-0001", + "CONTAINER": "I03R-007", + "CONTAINERID": "192068", + "SCLOCATION": "", + "SC": "1", + "GR": "2", + "DC": "0", + "AI": "8", + "AP": "0", + "R": "1", + "SCRESOLUTION": null, + "SCCOMPLETENESS": null, + "DCRESOLUTION": null, + "DCCOMPLETENESS": null, + "ANOMALOUSSCATTERER": "", + "REQUIREDRESOLUTION": null, + "CELL_A": null, + "CELL_B": null, + "CELL_C": null, + "CELL_ALPHA": null, + "CELL_BETA": null, + "CELL_GAMMA": null, + "PACKINGFRACTION": null, + "DIMENSION1": null, + "DIMENSION2": null, + "DIMENSION3": null, + "SHAPE": null, + "THEORETICALDENSITY": null, + "CRYSTAL": null, + "PROTEIN": "Blah", + "LOOPTYPE": null, + "CENTRINGMETHOD": null, + "EXPERIMENTKIND": null, + "CONTAINERQUEUEID": null, + "QUEUEDTIMESTAMP": null, + "COMPONENTNAMES": null, + "COMPONENTDENSITIES": null, + "COMPONENTIDS": null, + "COMPONENTACRONYMS": null, + "COMPONENTGLOBALS": null, + "COMPONENTAMOUNTS": null, + "COMPONENTTYPESYMBOLS": null, + "VOLUME": null, + "SYMBOL": null, + "ABUNDANCE": null, + "RECORDTIMESTAMP": "12-01-2021", + "RADIATIONSENSITIVITY": null, + "ENERGY": null, + "USERPATH": null, + "AIMEDRESOLUTION": null, + "PREFERREDBEAMSIZEX": null, + "PREFERREDBEAMSIZEY": null, + "EXPOSURETIME": null, + "AXISSTART": null, + "AXISRANGE": null, + "NUMBEROFIMAGES": null, + "TRANSMISSION": null, + "COLLECTIONMODE": null, + "PRIORITY": null, + "DCSPACEGROUP": null + } \ No newline at end of file From aca1db8b3534ae14c6a7af5c013c29b6c53da8f9 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 26 Apr 2021 14:57:54 +0200 Subject: [PATCH 19/56] make possible to disable diff plan columns for containers --- client/src/js/modules/shipment/views/container.js | 1 + client/src/js/modules/shipment/views/containeradd.js | 1 + client/src/js/templates/shipment/container.html | 2 ++ client/src/js/templates/shipment/containeradd.html | 2 ++ 4 files changed, 6 insertions(+) diff --git a/client/src/js/modules/shipment/views/container.js b/client/src/js/modules/shipment/views/container.js index a33c4de7d..e9d2cf2fd 100644 --- a/client/src/js/modules/shipment/views/container.js +++ b/client/src/js/modules/shipment/views/container.js @@ -71,6 +71,7 @@ define(['marionette', templateHelpers: function() { return { IS_STAFF: app.staff, + ENABLE_EXP_PLAN: app.config.enable_exp_plan } }, diff --git a/client/src/js/modules/shipment/views/containeradd.js b/client/src/js/modules/shipment/views/containeradd.js index 2acc58d22..b9b6f312f 100644 --- a/client/src/js/modules/shipment/views/containeradd.js +++ b/client/src/js/modules/shipment/views/containeradd.js @@ -95,6 +95,7 @@ define(['backbone', SHIPPINGID: this.dewar.get('SHIPPINGID'), SHIPMENT: this.dewar.get('SHIPPINGNAME'), DEWAR: this.dewar.get('CODE'), + ENABLE_EXP_PLAN: app.config.enable_exp_plan } }, diff --git a/client/src/js/templates/shipment/container.html b/client/src/js/templates/shipment/container.html index 4783d6064..1fac8b406 100644 --- a/client/src/js/templates/shipment/container.html +++ b/client/src/js/templates/shipment/container.html @@ -85,6 +85,8 @@

    Container: <%-NAME%>

    +<% if (ENABLE_EXP_PLAN) { %> Plan Fields +<% } %> Extra Fields
    \ No newline at end of file diff --git a/client/src/js/templates/shipment/containeradd.html b/client/src/js/templates/shipment/containeradd.html index 9be8d0ac1..eadd4dbb3 100644 --- a/client/src/js/templates/shipment/containeradd.html +++ b/client/src/js/templates/shipment/containeradd.html @@ -137,7 +137,9 @@

    Add New Container

    Clear Puck + <% if (ENABLE_EXP_PLAN) { %> Plan Fields + <% } %> Extra Fields
    From 35f1d48de4aade98a189a879bed61539fee6bd7d Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 26 Apr 2021 14:58:48 +0200 Subject: [PATCH 20/56] rename csv button --- client/src/js/templates/shipment/shipment.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/templates/shipment/shipment.html b/client/src/js/templates/shipment/shipment.html index 0cc1fc263..49033d707 100644 --- a/client/src/js/templates/shipment/shipment.html +++ b/client/src/js/templates/shipment/shipment.html @@ -34,7 +34,7 @@

    Shipment: <%-SHIPPINGNAME%>

    Print Contents <% if (app.config.csv_profile) { %> - Import from CSV + Import/Export CSV <% } %> <% } %> From 555483227c25de710763907ae5532d9c12981227 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:10:17 +0200 Subject: [PATCH 21/56] add mark shipment returned button and api, add mark queue completed api, add prop code filter to containers --- api/src/Page/Shipment.php | 59 ++++++++++++++++++- .../src/js/modules/shipment/views/shipment.js | 25 +++++++- .../src/js/templates/shipment/shipment.html | 4 ++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index fab6c7f8b..12f00fb19 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -125,6 +125,8 @@ class Shipment extends Page 'manifest' => '\d', 'currentuser' => '\d', + 'PROPOSALCODE' => '\w+', + 'CONTAINERQUEUEID' => '\d+' ); @@ -132,6 +134,7 @@ class Shipment extends Page array('/shipments', 'post', '_add_shipment'), array('/shipments/:sid', 'patch', '_update_shipment'), array('/send/:sid', 'get', '_send_shipment'), + array('/return/:sid', 'get', '_return_shipment'), array('/countries', 'get', '_get_countries'), @@ -168,6 +171,7 @@ class Shipment extends Page array('/containers/:cid', 'patch', '_update_container'), array('/containers/move', 'get', '_move_container'), array('/containers/queue', 'get', '_queue_container'), + array('/containers/queue/:CONTAINERQUEUEID', 'post', '_update_container_queue'), array('/containers/barcode/:BARCODE', 'get', '_check_container'), @@ -1233,6 +1237,34 @@ function _send_shipment() { $this->_output(1); } + + function _return_shipment() { + if (!$this->has_arg('prop')) $this->_error('No proposal specified'); + if (!$this->has_arg('sid')) $this->_error('No shipping id specified'); + + $ship = $this->db->pq("SELECT s.shippingid + FROM shipping s + INNER JOIN proposal p ON s.proposalid = p.proposalid + WHERE p.proposalid = :1 AND s.shippingid = :2", array($this->proposalid,$this->arg('sid'))); + + if (!sizeof($ship)) $this->_error('No such shipment'); + $ship = $ship[0]; + + $this->db->pq("UPDATE shipping SET shippingstatus='returned' where shippingid=:1", array($ship['SHIPPINGID'])); + $this->db->pq("UPDATE dewar SET dewarstatus='returned' where shippingid=:1", array($ship['SHIPPINGID'])); + + $dewars = $this->db->pq("SELECT d.dewarid, s.visit_number as vn, s.beamlinename as bl, TO_CHAR(s.startdate, 'DD-MM-YYYY HH24:MI') as startdate + FROM dewar d + LEFT OUTER JOIN blsession s ON s.sessionid = d.firstexperimentid + WHERE d.shippingid=:1", array($ship['SHIPPINGID'])); + foreach ($dewars as $d) { + $this->db->pq("INSERT INTO dewartransporthistory (dewartransporthistoryid,dewarid,dewarstatus,arrivaldate) + VALUES (s_dewartransporthistory.nextval,:1,'returned',CURRENT_TIMESTAMP)", + array($d['DEWARID'])); + } + + $this->_output(1); + } # Show and accept terms to use diamonds shipping account @@ -1328,6 +1360,10 @@ function _get_all_containers() { } } + if ($this->has_arg('PROPOSALCODE')) { + $where .= " AND p.proposalcode LIKE :".(sizeof($args)+1); + array_push($args, $this->arg('PROPOSALCODE')); + } if ($this->has_arg('PUCK')) { $where .= " AND c.containertype LIKE '%puck'"; @@ -1459,7 +1495,10 @@ function _get_all_containers() { count(distinct ss.blsubsampleid) as subsamples, ses3.beamlinename as firstexperimentbeamline, pp.name as pipeline, - TO_CHAR(max(cq2.completedtimestamp), 'HH24:MI DD-MM-YYYY') as lastqueuecompleted, TIMESTAMPDIFF('MINUTE', max(cq2.completedtimestamp), max(cq2.createdtimestamp)) as lastqueuedwell, c.ownerid, concat_ws(' ', pe.givenname, pe.familyname) as owner + TO_CHAR(max(cq2.completedtimestamp), 'HH24:MI DD-MM-YYYY') as lastqueuecompleted, TIMESTAMPDIFF('MINUTE', max(cq2.completedtimestamp), max(cq2.createdtimestamp)) as lastqueuedwell, + c.ownerid, concat_ws(' ', pe.givenname, pe.familyname) as owner, + CONCAT(SUM(IF(dp.collectionmode = 'auto', 1, 0)), 'A, ', SUM(IF(dp.collectionmode = 'manual', 1, 0)), 'M') as modes, + lc.cardname FROM container c INNER JOIN dewar d ON d.dewarid = c.dewarid LEFT OUTER JOIN blsession ses3 ON d.firstexperimentid = ses3.sessionid @@ -1482,6 +1521,9 @@ function _get_all_containers() { LEFT OUTER JOIN blsession ses ON c.sessionid = ses.sessionid LEFT OUTER JOIN processingpipeline pp ON c.prioritypipelineid = pp.processingpipelineid LEFT OUTER JOIN person pe ON c.ownerid = pe.personid + LEFT OUTER JOIN diffractionplan dp ON dp.diffractionplanid = s.diffractionplanid + + LEFT OUTER JOIN labcontact lc ON sh.sendinglabcontactid = lc.labcontactid $join WHERE $where @@ -1560,6 +1602,21 @@ function _queue_container() { } } + # Manually update a container queue status to completed + function _update_container_queue() { + if (!$this->staff) $this->_error("No access"); + + $cq = $this->db->pq("SELECT containerqueueid + FROM containerqueue WHERE containerqueueid=:1", + array($this->arg('CONTAINERQUEUEID'))); + + if (!sizeof($cq)) $this->_error("No such container queue"); + + $this->db->pq("UPDATE containerqueue SET completedtimestamp=CURRENT_TIMESTAMP WHERE containerqueueid=:1", + array($this->arg('CONTAINERQUEUEID'))); + + $this->_output(1); + } # Move Container function _move_container() { diff --git a/client/src/js/modules/shipment/views/shipment.js b/client/src/js/modules/shipment/views/shipment.js index d9c9051cf..383124250 100644 --- a/client/src/js/modules/shipment/views/shipment.js +++ b/client/src/js/modules/shipment/views/shipment.js @@ -43,12 +43,14 @@ define(['marionette', APIURL: app.apiurl, PROP: app.prop, DHL_ENABLE: app.options.get('dhl_enable'), + IS_STAFF: app.staff } }, events: { 'click #add_dewar': 'addDewar', 'click a.send': 'sendShipment', + 'click a.return': 'returnShipment', 'click a.pdf': utils.signHandler, 'click a.cancel_pickup': 'cancelPickup', }, @@ -101,7 +103,7 @@ define(['marionette', Backbone.ajax({ url: app.apiurl+'/shipment/send/'+this.model.get('SHIPPINGID'), success: function() { - self.model.set({ SHIPPINGSTATUS: 'send to DLS' }) + self.model.set({ SHIPPINGSTATUS: 'sent to facility' }) app.alert({ className: 'message notify', message: 'Shipment successfully marked as sent' }) self.render() }, @@ -111,7 +113,26 @@ define(['marionette', }) }, - + + returnShipment: function(e) { + e.preventDefault() + var self = this + Backbone.ajax({ + url: app.apiurl+'/shipment/return/'+this.model.get('SHIPPINGID'), + success: function() { + self.model.set({ SHIPPINGSTATUS: 'returned' }) + self.render() + setTimeout(function() { + app.alert({ className: 'message notify', message: 'Shipment successfully marked as returned to user' }) + }, 500) + + }, + error: function() { + app.alert({ message: 'Something went wrong marking this shipment returned, please try again' }) + }, + + }) + }, addDewar: function(e) { e.preventDefault() diff --git a/client/src/js/templates/shipment/shipment.html b/client/src/js/templates/shipment/shipment.html index 49033d707..e3debfca0 100644 --- a/client/src/js/templates/shipment/shipment.html +++ b/client/src/js/templates/shipment/shipment.html @@ -17,6 +17,10 @@

    Shipment: <%-SHIPPINGNAME%>

    Mark as Sent <% } %> + <% if (SHIPPINGSTATUS != 'returned' && IS_STAFF) { %> + Mark as Returned + <% } %> + <% if (DHL_ENABLE) { %> <% if (DELIVERYAGENT_HAS_LABEL == '1') { %> Print Airway Bill From 7544d8a7d9e3a9bbafcdcae94b8bcf76f0e9ebbd Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:11:43 +0200 Subject: [PATCH 22/56] document disabling exp_plan fields --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a889f95f6..646b15fb6 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ This file should be copied to create a client/src/js/config.json file and edited | data_catalogue | Object that includes name and url property for a link to a data catalogue - displayed on the landing page | | site_image | PNG image of site logo to display in header| | csv_profile | The csv profile for importing shipments, currently only imca, see src/js/csv/imca.js | +| enable_exp_plan | Whether to enable editing of experimental plan fields when creating samples | Site Image can be customised via the tailwind.config.js header-site-logo and footer-site-logo values. From 2a14dcdcaac822a573ba03ccb0ef86651689b043 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:24:14 +0200 Subject: [PATCH 23/56] add initial queued containers view --- client/src/js/modules/shipment/controller.js | 19 +- client/src/js/modules/shipment/router.js | 4 +- .../shipment/views/queuedcontainers.js | 174 ++++++++++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 client/src/js/modules/shipment/views/queuedcontainers.js diff --git a/client/src/js/modules/shipment/controller.js b/client/src/js/modules/shipment/controller.js index 0f42929ba..20e2799e0 100644 --- a/client/src/js/modules/shipment/controller.js +++ b/client/src/js/modules/shipment/controller.js @@ -16,6 +16,8 @@ define(['backbone', // 'modules/shipment/views/containeradd', 'modules/shipment/views/containers', 'modules/imaging/views/queuecontainer', + + 'modules/shipment/views/queuedcontainers', 'modules/shipment/views/containerreview', 'modules/shipment/models/containerregistry', @@ -51,7 +53,7 @@ define(['backbone', Dewar, Shipment, Shipments, ShipmentsView, ShipmentView, ShipmentAddView, ImportFromCSV, Container, Containers, ContainerView, ContainerPlateView, /*ContainerAddView,*/ ContainersView, QueueContainerView, - MailinContainers, ReviewContainer, + QueuedContainers, ReviewContainer, ContainerRegistry, ContainersRegistry, ContainerRegistryView, RegisteredContainer, RegisteredDewar, DewarRegistry, DewarRegView, RegDewarView, RegDewarAddView, DewarRegistryView, DispatchView, TransferView, Dewars, DewarOverview, ManifestView, DewarStats, CreateAWBView, RebookPickupView, @@ -359,6 +361,16 @@ define(['backbone', }) }, + queued_containers: function(s, ty, pt, page) { + if (!app.staff) { + app.message({ title: 'No access', message: 'You do not have access to that page'}) + return + } + + app.bc.reset([bc, { title: 'Queued Containers' }]) + app.content.show(new QueuedContainers({ params: { s: s, ty: ty, pt: pt, page: page }})) + }, + container_review: function(cid) { var lookup = new ProposalLookup({ field: 'CONTAINERID', value: cid }) lookup.find({ @@ -546,6 +558,11 @@ define(['backbone', controller.view_container(cid, iid, sid) }) + app.on('container:review', function(cid) { + app.navigate('containers/review/'+cid) + controller.container_review(cid) + }) + app.on('rdewar:show', function(fc) { app.navigate('dewars/registry/'+fc) controller.view_dewar(fc) diff --git a/client/src/js/modules/shipment/router.js b/client/src/js/modules/shipment/router.js index 938100095..db3ee0170 100644 --- a/client/src/js/modules/shipment/router.js +++ b/client/src/js/modules/shipment/router.js @@ -18,6 +18,8 @@ define(['utils/lazyrouter'], function(LazyRouter) { 'containers(/s/:s)(/ty/:ty)(/page/:page)': 'container_list', 'containers/registry(/ty/:ty)(/s/:s)(/page/:page)': 'container_registry', 'containers/registry/:crid': 'view_rcontainer', + + 'containers/queued(/s/:s)(/ty/:ty)(/pt/:pt)(/page/:page)': 'queued_containers', 'containers/review/:cid': 'container_review', 'dewars(/s/:s)(/page/:page)': 'dewar_list', @@ -35,7 +37,7 @@ define(['utils/lazyrouter'], function(LazyRouter) { 'migrate': 'migrate', }, - loadEvents: ['shipments:show', 'shipment:show', 'rcontainer:show', 'rdewar:show'], + loadEvents: ['shipments:show', 'shipment:show', 'rcontainer:show', 'rdewar:show', 'container:review'], loadModule: function(loadedCallback) { import(/* webpackChunkName: "shipping" */ 'modules/shipment/controller').then(controller => { diff --git a/client/src/js/modules/shipment/views/queuedcontainers.js b/client/src/js/modules/shipment/views/queuedcontainers.js new file mode 100644 index 000000000..302eaf53c --- /dev/null +++ b/client/src/js/modules/shipment/views/queuedcontainers.js @@ -0,0 +1,174 @@ +define(['backbone', 'marionette', + 'backgrid', + 'views/table', + 'views/filter', + 'utils/table', + 'utils', + 'collections/proposaltypes', + 'collections/containers'], function(Backbone, Marionette, Backgrid, TableView, FilterView, table, utils, + ProposalTypes, + Containers) { + + + var ClickableRow = table.ClickableRow.extend({ + event: 'container:review', + argument: 'CONTAINERID', + cookie: true, + }) + + var ActionCell = Backgrid.Cell.extend({ + events: { + 'click a.completed': 'markCompleted' + }, + + markCompleted: function(e) { + e.preventDefault() + utils.confirm({ + title: 'Confirm Mark Completed', + content: 'Are you sure you want to mark "'+this.model.get('NAME')+'" completed?', + callback: this.doMarkCompleted.bind(this) + }) + }, + + doMarkCompleted: function() { + var self = this + Backbone.ajax({ + url: app.apiurl+'/shipment/containers/queue/'+this.model.get('CONTAINERQUEUEID'), + method: 'POST', + success: function() { + app.alert({ className: 'message notify', message: 'Container queue successfully marked as completed' }) + self.model.collection.fetch() + }, + error: function() { + app.alert({ message: 'Something went wrong marking this container queue as completed, please try again' }) + }, + + }) + }, + + render: function() { + if (app.staff && this.model.get('CONTAINERQUEUEID')) { + this.$el.html('') + } + + return this + } + }) + + var FilterWithDefault = FilterView.extend({ + default: null, + + selected: function() { + var selected = this.collection.findWhere({ isSelected: true }) + if (!selected) { + var selected = this.collection.findWhere({ id: this.getOption('default')}) + selected.set({isSelected: true}) + } + return selected ? selected.get('id') : null + }, + }) + + return Marionette.LayoutView.extend({ + className: 'content', + template: '

    Queued Containers (-)

    ', + regions: { wrap: '.wrapper', type: '.type', type2: '.type2' }, + + + hiddenColumns: [3,4,5,7,9,10,11], + + columns: [ + { name: 'NAME', label: 'Name', cell: 'string', editable: false }, + { name: 'PROP', label: 'Proposal', cell: 'string', editable: false }, + { name: 'OWNER', label: 'Owner', cell: 'string', editable: false }, + { name: 'CARDNAME', label: 'Contact', cell: 'string', editable: false }, + { name: 'SHIPMENT', label: 'Shipment', cell: 'string', editable: false }, + { name: 'DEWAR', label: 'Dewar', cell: 'string', editable: false }, + { name: 'SAMPLES', label: '# Samples', cell: 'string', editable: false }, + { name: 'MODES', label: 'Modes', cell: 'string', editable: false }, + { name: 'CONTAINERSTATUS', label: 'Status', cell: 'string', editable: false }, + { name: 'COMMENTS', label: 'Comments', cell: 'string', editable: false }, + { name: 'QUEUEDTIMESTAMP', label: 'Queued', cell: 'string', editable: false }, + { name: 'LASTQUEUECOMPLETED', label: 'Completed', cell: 'string', editable: false }, + { label: '', cell: ActionCell, editable: false }, + ], + + showFilter: true, + filters: [ + { id: 'queued', name: 'Queued'}, + { id: 'completed', name: 'Completed'}, + ], + + ui: { + total: 'span.total', + }, + + refresh: function() { + this.collection.fetch() + }, + + updateTotal: function() { + console.log('updatetotal', this.collection) + this.ui.total.text(this.collection.state.totalRecords) + }, + + initialize: function(options) { + this.types = new ProposalTypes() + this.ready = this.types.fetch() + + this.collection = new Containers() + this.collection.queryParams.all = 1 + this.collection.queryParams.PUCK = 1 + this.collection.queryParams.ty = 'queued' + this.collection.state.currentPage = options.params.page + this.listenTo(this.collection, 'sync', this.updateTotal) + + var filters = this.getOption('filters').slice(0) + var columns = this.getOption('columns').slice(0) + + if (app.mobile()) { + _.each(this.getOption('hiddenColumns'), function(v) { + columns[v].renderable = false + }) + } + + this.table = new TableView({ + collection: this.collection, + columns: columns, + tableClass: 'containers', filter: 's', search: options.params.s, loading: true, + backgrid: { row: ClickableRow, emptyText: 'No containers found' } }) + + this.ty = new FilterWithDefault({ + default: 'queued', + collection: this.collection, + value: options.params && options.params.ty, + name: 'ty', + filters: filters + }) + }, + + onRender: function() { + this.wrap.show(this.table) + this.type.show(this.ty) + + this.ready.done(this.showFilter2.bind(this)) + }, + + showFilter2: function() { + this.ty2 = new FilterView({ + collection: this.collection, + name: 'PROPOSALCODE', + urlFragment: 'pt', + value: this.getOption('params') && this.getOption('params').pt, + filters: this.types.map(function(b) { return { id: b.get('PROPOSALCODE'), name: b.get('PROPOSALCODE') } }), + }) + this.type2.show(this.ty2) + this.refresh() + }, + + updateFilter2: function(selected) { + this.collection.queryParams.proposalcode = selected + this.refresh() + }, + }) + +}) From 548c8f8fef45c68c387f52012ef8b2e345cd3377 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:24:41 +0200 Subject: [PATCH 24/56] update container review page, link from container --- .../modules/shipment/views/containerreview.js | 15 ++++++-- .../src/js/templates/shipment/container.html | 4 ++ .../templates/shipment/containerreview.html | 38 +++++++++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 client/src/js/templates/shipment/containerreview.html diff --git a/client/src/js/modules/shipment/views/containerreview.js b/client/src/js/modules/shipment/views/containerreview.js index fb78b4691..737f4195d 100644 --- a/client/src/js/modules/shipment/views/containerreview.js +++ b/client/src/js/modules/shipment/views/containerreview.js @@ -3,11 +3,13 @@ define(['marionette', 'views/table', 'utils/table', 'collections/samples', + 'templates/shipment/containerreview.html' ], function(Marionette, Backgrid, TableView, table, - Samples) { + Samples, + template) { var UCTemplate = '\ \ @@ -34,6 +36,8 @@ define(['marionette',
    ' var ActionCell = Backgrid.Cell.extend({ + className: 'nowrap', + events: { 'click a.reinspect': 'markInspect', 'click a.skip': 'markSkip' @@ -41,9 +45,11 @@ define(['marionette', render: function() { if (app.staff) { - this.$el.html('') - this.$el.append(' ') + this.$el.html('') + this.$el.append(' ') } + + this.$el.append('   View Sample') return this } @@ -51,7 +57,7 @@ define(['marionette', return Marionette.LayoutView.extend({ className: 'content', - template: _.template('

    Review: <%-NAME%>

    '), + template: template, regions: { rsamples: '.rsamples' @@ -74,6 +80,7 @@ define(['marionette', { name: 'REQUIREDRESOLUTION', label: 'Required Res', cell: 'string', editable: false }, { name: 'AIMEDRESOLUTION', label: 'Aimed Res', cell: 'string', editable: false }, { name: 'COLLECTIONMODE', label: 'Mode', cell: 'string', editable: false }, + { name: 'PRIORITY', label: 'Priority', cell: 'string', editable: false }, { name: 'EXPOSURETIME', label: 'Exposure (s)', cell: 'string', editable: false }, { name: 'AXISRANGE', label: 'Axis Range', cell: 'string', editable: false }, { name: 'AXISSTART', label: 'No Images', cell: 'string', editable: false }, diff --git a/client/src/js/templates/shipment/container.html b/client/src/js/templates/shipment/container.html index 1fac8b406..6570527e2 100644 --- a/client/src/js/templates/shipment/container.html +++ b/client/src/js/templates/shipment/container.html @@ -7,6 +7,10 @@

    Container: <%-NAME%>

    This container is currently assigned and in use on a beamline sample changer. Unassign it to make it editable

    <% } %> + +
    diff --git a/client/src/js/templates/shipment/containerreview.html b/client/src/js/templates/shipment/containerreview.html new file mode 100644 index 000000000..a2d2a86b8 --- /dev/null +++ b/client/src/js/templates/shipment/containerreview.html @@ -0,0 +1,38 @@ +

    Review: <%-NAME%>

    + + +

    This page shows the data collection and sample status of the selected container.

    + +
    +
    + +
      +
    • + Shipment + <%-SHIPMENT%> +
    • + +
    • + Dewar + <%-DEWAR%> +
    • +
    • + Container Type + <%-CONTAINERTYPE%> +
    • + +
    • + Owner + <%-OWNER%> +
    • + +
    • + Registered Container + <%-REGISTRY%> + <% if (CONTAINERREGISTRYID) { %>[View]<% } %> +
    • + +
    +
    + +
    From 94c266f2640918b08980884545e974ea38d4d93d Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:27:09 +0200 Subject: [PATCH 25/56] separate spacegroups --- api/src/Page/Sample.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index 2d8c513b2..63b79b31b 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -965,7 +965,7 @@ function _samples() { , $cseq $sseq string_agg(cpr.name) as componentnames, string_agg(cpr.density) as componentdensities ,string_agg(cpr.proteinid) as componentids, string_agg(cpr.acronym) as componentacronyms, string_agg(cpr.global) as componentglobals, string_agg(chc.abundance) as componentamounts, string_agg(ct.symbol) as componenttypesymbols, b.volume, pct.symbol,ROUND(cr.abundance,3) as abundance, TO_CHAR(b.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp, dp.radiationsensitivity, dp.energy, dp.userpath, dp.aimedresolution, dp.preferredbeamsizex, dp.preferredbeamsizey, dp.exposuretime, dp.axisstart, dp.axisrange, dp.numberofimages, dp.transmission, dp.collectionmode, dp.priority, - GROUP_CONCAT(distinct a.spacegroup) as dcspacegroup + GROUP_CONCAT(distinct a.spacegroup SEPARATOR ', ') as dcspacegroup FROM blsample b From 86fb5c6efb5b8d47b7b8b58025fb590b6ca6b7ea Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 11:54:47 +0200 Subject: [PATCH 26/56] add additional filters to queue view --- client/src/js/modules/shipment/controller.js | 4 +- client/src/js/modules/shipment/router.js | 2 +- .../shipment/views/queuedcontainers.js | 55 +++++++++++++++++-- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/client/src/js/modules/shipment/controller.js b/client/src/js/modules/shipment/controller.js index 20e2799e0..a73f0dfcf 100644 --- a/client/src/js/modules/shipment/controller.js +++ b/client/src/js/modules/shipment/controller.js @@ -361,14 +361,14 @@ define(['backbone', }) }, - queued_containers: function(s, ty, pt, page) { + queued_containers: function(s, ty, pt, bl, page) { if (!app.staff) { app.message({ title: 'No access', message: 'You do not have access to that page'}) return } app.bc.reset([bc, { title: 'Queued Containers' }]) - app.content.show(new QueuedContainers({ params: { s: s, ty: ty, pt: pt, page: page }})) + app.content.show(new QueuedContainers({ params: { s: s, ty: ty, pt: pt, bl: bl, page: page }})) }, container_review: function(cid) { diff --git a/client/src/js/modules/shipment/router.js b/client/src/js/modules/shipment/router.js index db3ee0170..685ceb8c5 100644 --- a/client/src/js/modules/shipment/router.js +++ b/client/src/js/modules/shipment/router.js @@ -19,7 +19,7 @@ define(['utils/lazyrouter'], function(LazyRouter) { 'containers/registry(/ty/:ty)(/s/:s)(/page/:page)': 'container_registry', 'containers/registry/:crid': 'view_rcontainer', - 'containers/queued(/s/:s)(/ty/:ty)(/pt/:pt)(/page/:page)': 'queued_containers', + 'containers/queued(/s/:s)(/ty/:ty)(/pt/:pt)(/bl/:bl)(/page/:page)': 'queued_containers', 'containers/review/:cid': 'container_review', 'dewars(/s/:s)(/page/:page)': 'dewar_list', diff --git a/client/src/js/modules/shipment/views/queuedcontainers.js b/client/src/js/modules/shipment/views/queuedcontainers.js index 302eaf53c..51b0aac73 100644 --- a/client/src/js/modules/shipment/views/queuedcontainers.js +++ b/client/src/js/modules/shipment/views/queuedcontainers.js @@ -5,8 +5,10 @@ define(['backbone', 'marionette', 'utils/table', 'utils', 'collections/proposaltypes', + 'collections/bls', 'collections/containers'], function(Backbone, Marionette, Backgrid, TableView, FilterView, table, utils, ProposalTypes, + Beamlines, Containers) { @@ -55,6 +57,16 @@ define(['backbone', 'marionette', } }) + var LocationCell = Backgrid.Cell.extend({ + render: function() { + this.$el.html(this.model.escape('BEAMLINELOCATION')) + if (this.model.get('SAMPLECHANGERLOCATION')) { + this.$el.append(' - ' + this.model.escape('SAMPLECHANGERLOCATION')) + } + return this + } + }) + var FilterWithDefault = FilterView.extend({ default: null, @@ -70,8 +82,11 @@ define(['backbone', 'marionette', return Marionette.LayoutView.extend({ className: 'content', - template: '

    Queued Containers (-)

    ', - regions: { wrap: '.wrapper', type: '.type', type2: '.type2' }, + template: '

    Queued Containers (-)

    ', + regions: { + wrap: '.wrapper', + type: '.type', type2: '.type2', typeas: '.typeas', typebl: '.typebl' + }, hiddenColumns: [3,4,5,7,9,10,11], @@ -89,6 +104,7 @@ define(['backbone', 'marionette', { name: 'COMMENTS', label: 'Comments', cell: 'string', editable: false }, { name: 'QUEUEDTIMESTAMP', label: 'Queued', cell: 'string', editable: false }, { name: 'LASTQUEUECOMPLETED', label: 'Completed', cell: 'string', editable: false }, + { label: 'SC', cell: LocationCell, editable: false }, { label: '', cell: ActionCell, editable: false }, ], @@ -113,7 +129,11 @@ define(['backbone', 'marionette', initialize: function(options) { this.types = new ProposalTypes() - this.ready = this.types.fetch() + this.ready = [] + this.ready.push(this.types.fetch()) + + this.beamlines = new Beamlines(null, { ty: app.type }) + this.ready.push(this.beamlines.fetch()) this.collection = new Containers() this.collection.queryParams.all = 1 @@ -144,16 +164,29 @@ define(['backbone', 'marionette', name: 'ty', filters: filters }) + + this.assigned = new FilterView({ + collection: this.collection, + name: 'assigned', + filters: { id: 1, name: 'Assigned'}, + }) }, onRender: function() { this.wrap.show(this.table) this.type.show(this.ty) + this.typeas.show(this.assigned) - this.ready.done(this.showFilter2.bind(this)) + $.when.apply($, this.ready).then(this.doOnRender.bind(this)) }, - showFilter2: function() { + doOnRender: function() { + this.showProposalFilter() + this.showBeamlineFilter() + this.refresh() + }, + + showProposalFilter: function() { this.ty2 = new FilterView({ collection: this.collection, name: 'PROPOSALCODE', @@ -162,13 +195,23 @@ define(['backbone', 'marionette', filters: this.types.map(function(b) { return { id: b.get('PROPOSALCODE'), name: b.get('PROPOSALCODE') } }), }) this.type2.show(this.ty2) - this.refresh() }, updateFilter2: function(selected) { this.collection.queryParams.proposalcode = selected this.refresh() }, + + showBeamlineFilter: function() { + this.tybl = new FilterView({ + collection: this.collection, + name: 'bl', + urlFragment: 'bl', + value: this.getOption('params') && this.getOption('params').bl, + filters: this.beamlines.map(function(b) { return { id: b.get('BEAMLINE'), name: b.get('BEAMLINE') } }), + }) + this.typebl.show(this.tybl) + }, }) }) From 3fe0a46b346c04a74338a23fa641b747f2c4379d Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 11:54:59 +0200 Subject: [PATCH 27/56] add queue button to admin menu --- client/src/js/modules/types/mx/menu.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/js/modules/types/mx/menu.js b/client/src/js/modules/types/mx/menu.js index d1e37b516..bbff750bc 100644 --- a/client/src/js/modules/types/mx/menu.js +++ b/client/src/js/modules/types/mx/menu.js @@ -23,6 +23,7 @@ define([], function() { }, admin: { + 'containers/queued': { title: 'Queue', icon: 'fa-database', permission: 'auto_dash' }, 'runs/overview': { title: 'Run Overview', icon: 'fa-bar-chart', permission: 'all_breakdown' }, 'stats/overview/beamlines': { title: 'Reporting', icon: 'fa-line-chart', permission: 'all_prop_stats' }, 'admin/imaging': { title: 'Imaging', icon: 'fa-image', permission: 'imaging_dash' }, From 0263e9555f2c72910250a869e9e5af727d558d40 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:24:29 +0200 Subject: [PATCH 28/56] allow customising auto collect label --- README.md | 1 + client/src/js/modules/samples/views/view.js | 8 ++++++++ client/src/js/modules/shipment/views/container.js | 11 +++++++---- client/src/js/modules/shipment/views/containeradd.js | 5 ++++- client/src/js/templates/samples/sample.html | 2 +- client/src/js/templates/shipment/container.html | 2 +- client/src/js/templates/shipment/containeradd.html | 2 +- 7 files changed, 23 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 646b15fb6..768ee7ef3 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ This file should be copied to create a client/src/js/config.json file and edited | site_image | PNG image of site logo to display in header| | csv_profile | The csv profile for importing shipments, currently only imca, see src/js/csv/imca.js | | enable_exp_plan | Whether to enable editing of experimental plan fields when creating samples | +| auto_collect_label | Customise the auto collect label from the default 'Automated' | Site Image can be customised via the tailwind.config.js header-site-logo and footer-site-logo values. diff --git a/client/src/js/modules/samples/views/view.js b/client/src/js/modules/samples/views/view.js index 945810f86..fdb0f0a76 100644 --- a/client/src/js/modules/samples/views/view.js +++ b/client/src/js/modules/samples/views/view.js @@ -53,8 +53,16 @@ define(['marionette', ui: { comp: 'input[name=COMPONENTID]', }, + + templateHelpers: function() { + return { + AUTO_LABEL: this.automated_label + } + }, initialize: function(options) { + this.automated_label = app.config.auto_collect_label || 'Automated' + Backbone.Validation.bind(this); this.dcs = new DCCol(null, { queryParams: { sid: this.model.get('BLSAMPLEID'), pp: 5 } }) diff --git a/client/src/js/modules/shipment/views/container.js b/client/src/js/modules/shipment/views/container.js index e9d2cf2fd..7cfa3ac9e 100644 --- a/client/src/js/modules/shipment/views/container.js +++ b/client/src/js/modules/shipment/views/container.js @@ -71,7 +71,8 @@ define(['marionette', templateHelpers: function() { return { IS_STAFF: app.staff, - ENABLE_EXP_PLAN: app.config.enable_exp_plan + ENABLE_EXP_PLAN: app.config.enable_exp_plan, + AUTO_LABEL: this.automated_label } }, @@ -94,6 +95,8 @@ define(['marionette', }, initialize: function(options) { + this.automated_label = app.config.auto_collect_label || 'Automated' + var self = this this.createSamples() this.samples.queryParams.cid = options.model.get('CONTAINERID') @@ -170,10 +173,10 @@ define(['marionette', updateAutoCollection: function() { if (this.model.get('CONTAINERQUEUEID')) { - this.ui.auto.html('This container was queued for auto collection on '+this.model.escape('QUEUEDTIMESTAMP')) + this.ui.auto.html('This container was queued for '+this.automated_label.toLowerCase()+' collection on '+this.model.escape('QUEUEDTIMESTAMP')) this.ui.auto.append(' Unqueue') } else { - this.ui.auto.html(' Queue this container for Auto Collect') + this.ui.auto.html(' Queue this container for '+this.automated_label.toLowerCase()+' collection') } }, @@ -181,7 +184,7 @@ define(['marionette', e.preventDefault() utils.confirm({ title: 'Queue Container?', - content: 'Are you sure you want to queue this container for auto collection?', + content: 'Are you sure you want to queue this container for '+this.automated_label.toLowerCase()+' collection?', callback: this.doQueueContainer.bind(this) }) }, diff --git a/client/src/js/modules/shipment/views/containeradd.js b/client/src/js/modules/shipment/views/containeradd.js index b9b6f312f..5eebca004 100644 --- a/client/src/js/modules/shipment/views/containeradd.js +++ b/client/src/js/modules/shipment/views/containeradd.js @@ -95,7 +95,8 @@ define(['backbone', SHIPPINGID: this.dewar.get('SHIPPINGID'), SHIPMENT: this.dewar.get('SHIPPINGNAME'), DEWAR: this.dewar.get('CODE'), - ENABLE_EXP_PLAN: app.config.enable_exp_plan + ENABLE_EXP_PLAN: app.config.enable_exp_plan, + AUTO_LABEL: this.automated_label } }, @@ -540,6 +541,8 @@ define(['backbone', }, initialize: function(options) { + this.automated_label = app.config.auto_collect_label || 'Automated' + this.ready = [] this.dewar = options.dewar diff --git a/client/src/js/templates/samples/sample.html b/client/src/js/templates/samples/sample.html index 59bb555ab..7f17cbb9e 100644 --- a/client/src/js/templates/samples/sample.html +++ b/client/src/js/templates/samples/sample.html @@ -134,7 +134,7 @@

    Sample Details

    <% if (CONTAINERQUEUEID) { %>
  • - Auto Collect Queued + <%-AUTO_LABEL%> Collection Queued <%-QUEUEDTIMESTAMP%>
  • diff --git a/client/src/js/templates/shipment/container.html b/client/src/js/templates/shipment/container.html index 6570527e2..10c12e93d 100644 --- a/client/src/js/templates/shipment/container.html +++ b/client/src/js/templates/shipment/container.html @@ -52,7 +52,7 @@

    Container: <%-NAME%>

  • - Automated Collection + <%-AUTO_LABEL%> Collection
  • <% if (EXPERIMENTTYPE) { %> diff --git a/client/src/js/templates/shipment/containeradd.html b/client/src/js/templates/shipment/containeradd.html index eadd4dbb3..6567999c7 100644 --- a/client/src/js/templates/shipment/containeradd.html +++ b/client/src/js/templates/shipment/containeradd.html @@ -45,7 +45,7 @@

    Add New Container

  • - +
  • From 6fd7c79c83652a525e02a5a701ad444cab09a077 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:24:36 +0200 Subject: [PATCH 29/56] remove debug --- client/src/js/modules/shipment/views/queuedcontainers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/js/modules/shipment/views/queuedcontainers.js b/client/src/js/modules/shipment/views/queuedcontainers.js index 51b0aac73..d879aa318 100644 --- a/client/src/js/modules/shipment/views/queuedcontainers.js +++ b/client/src/js/modules/shipment/views/queuedcontainers.js @@ -123,7 +123,6 @@ define(['backbone', 'marionette', }, updateTotal: function() { - console.log('updatetotal', this.collection) this.ui.total.text(this.collection.state.totalRecords) }, From b5b5ba7a9bea74720d613752dab4971ce2acb979 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:24:43 +0200 Subject: [PATCH 30/56] add link back to container --- client/src/js/templates/shipment/containerreview.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/js/templates/shipment/containerreview.html b/client/src/js/templates/shipment/containerreview.html index a2d2a86b8..9dc21bd16 100644 --- a/client/src/js/templates/shipment/containerreview.html +++ b/client/src/js/templates/shipment/containerreview.html @@ -3,6 +3,10 @@

    Review: <%-NAME%>

    This page shows the data collection and sample status of the selected container.

    + +
    From 60b03e4a40ef873548f888fcd0ad525aff02719e Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:50:26 +0200 Subject: [PATCH 31/56] allow staff to queue entire shipment --- .../src/js/modules/shipment/views/shipment.js | 43 +++++++++++++++++++ .../src/js/templates/shipment/shipment.html | 4 ++ 2 files changed, 47 insertions(+) diff --git a/client/src/js/modules/shipment/views/shipment.js b/client/src/js/modules/shipment/views/shipment.js index 383124250..b9282a2a2 100644 --- a/client/src/js/modules/shipment/views/shipment.js +++ b/client/src/js/modules/shipment/views/shipment.js @@ -53,6 +53,7 @@ define(['marionette', 'click a.return': 'returnShipment', 'click a.pdf': utils.signHandler, 'click a.cancel_pickup': 'cancelPickup', + 'click a.queue': 'queueShipment', }, ui: { @@ -133,6 +134,48 @@ define(['marionette', }) }, + + queueShipment: function(e) { + e.preventDefault() + + var containers = new Containers() + containers.queryParams.SHIPPINGID = this.model.get('SHIPPINGID') + containers.fetch().done(function () { + var promises = [] + var success = 0 + var failure = 0 + + containers.each(function(c) { + promises.push(Backbone.ajax({ + url: app.apiurl+'/shipment/containers/queue', + data: { + CONTAINERID: c.get('CONTAINERID') + }, + success: function() { + success++ + }, + error: function(xhr) { + var json = {}; + if (xhr.responseText) { + try { + json = $.parseJSON(xhr.responseText) + } catch(err) { + + } + } + app.alert({ message: c.get('CONTAINERID') + ': ' + json.message }) + failure++ + } + })) + }) + + $.when.apply($, promises).then(function() { + app.alert({ message: success+ ' Container(s) Successfully Queued, ' + failure + ' Failed' }) + }).fail(function() { + app.alert({ message: success+ ' Container(s) Successfully Queued, ' + failure + ' Failed' }) + }) + }) + }, addDewar: function(e) { e.preventDefault() diff --git a/client/src/js/templates/shipment/shipment.html b/client/src/js/templates/shipment/shipment.html index e3debfca0..e2e2be064 100644 --- a/client/src/js/templates/shipment/shipment.html +++ b/client/src/js/templates/shipment/shipment.html @@ -13,6 +13,10 @@

    Shipment: <%-SHIPPINGNAME%>

    <% if (LCOUT && LCRET) { %>
    + <% if (IS_STAFF) { %> + Queue + <% } %> + <% if (SHIPPINGSTATUS == 'opened' || SHIPPINGSTATUS == 'awb created' || SHIPPINGSTATUS == 'pickup booked') { %> Mark as Sent <% } %> From a4393c0b802afc83669d273edc1ecbb7fbe3cab6 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:50:32 +0200 Subject: [PATCH 32/56] remove debug --- .../modules/shipment/views/containerreview.js | 82 ------------------- 1 file changed, 82 deletions(-) diff --git a/client/src/js/modules/shipment/views/containerreview.js b/client/src/js/modules/shipment/views/containerreview.js index 737f4195d..9e90962a6 100644 --- a/client/src/js/modules/shipment/views/containerreview.js +++ b/client/src/js/modules/shipment/views/containerreview.js @@ -105,85 +105,3 @@ define(['marionette', }, }) }) - -a = { - "BLSAMPLEID": "3221723", - "CRYSTALID": "2794556", - "SCREENCOMPONENTGROUPID": null, - "PARENTSAMPLEID": null, - "PARENTSAMPLE": null, - "BLSUBSAMPLEID": null, - "INSPECTIONS": "0", - "PROP": "cm28170", - "CODE": "", - "LOCATION": "16", - "ACRONYM": "Blah", - "PROTEINID": "440420", - "SPACEGROUP": "", - "COMMENTS": "", - "STAFFCOMMENTS": null, - "NAME": "xtal_32", - "SHIPMENT": "test_samples", - "SHIPPINGID": "39740", - "DEWARID": "45092", - "DEWAR": "DLS-MX-0001", - "CONTAINER": "I03R-007", - "CONTAINERID": "192068", - "SCLOCATION": "", - "SC": "1", - "GR": "2", - "DC": "0", - "AI": "8", - "AP": "0", - "R": "1", - "SCRESOLUTION": null, - "SCCOMPLETENESS": null, - "DCRESOLUTION": null, - "DCCOMPLETENESS": null, - "ANOMALOUSSCATTERER": "", - "REQUIREDRESOLUTION": null, - "CELL_A": null, - "CELL_B": null, - "CELL_C": null, - "CELL_ALPHA": null, - "CELL_BETA": null, - "CELL_GAMMA": null, - "PACKINGFRACTION": null, - "DIMENSION1": null, - "DIMENSION2": null, - "DIMENSION3": null, - "SHAPE": null, - "THEORETICALDENSITY": null, - "CRYSTAL": null, - "PROTEIN": "Blah", - "LOOPTYPE": null, - "CENTRINGMETHOD": null, - "EXPERIMENTKIND": null, - "CONTAINERQUEUEID": null, - "QUEUEDTIMESTAMP": null, - "COMPONENTNAMES": null, - "COMPONENTDENSITIES": null, - "COMPONENTIDS": null, - "COMPONENTACRONYMS": null, - "COMPONENTGLOBALS": null, - "COMPONENTAMOUNTS": null, - "COMPONENTTYPESYMBOLS": null, - "VOLUME": null, - "SYMBOL": null, - "ABUNDANCE": null, - "RECORDTIMESTAMP": "12-01-2021", - "RADIATIONSENSITIVITY": null, - "ENERGY": null, - "USERPATH": null, - "AIMEDRESOLUTION": null, - "PREFERREDBEAMSIZEX": null, - "PREFERREDBEAMSIZEY": null, - "EXPOSURETIME": null, - "AXISSTART": null, - "AXISRANGE": null, - "NUMBEROFIMAGES": null, - "TRANSMISSION": null, - "COLLECTIONMODE": null, - "PRIORITY": null, - "DCSPACEGROUP": null - } \ No newline at end of file From 5ad92d865bbd08163f2ac30ac9204440dfadedc5 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 24 May 2021 10:03:28 +0200 Subject: [PATCH 33/56] stop shipment return if there are still outstanding queued containers --- api/src/Page/Shipment.php | 7 +++++++ client/src/js/modules/shipment/controller.js | 4 ++-- client/src/js/modules/shipment/router.js | 2 +- .../shipment/views/queuedcontainers.js | 1 + .../src/js/modules/shipment/views/shipment.js | 19 ++++++++++++++++--- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 12f00fb19..640ba54a8 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1249,6 +1249,13 @@ function _return_shipment() { if (!sizeof($ship)) $this->_error('No such shipment'); $ship = $ship[0]; + + $containers = $this->db->pq("SELECT c.containerid + FROM container c + INNER JOIN dewar d ON d.dewarid = c.dewarid + INNER JOIN containerqueue cq ON cq.containerid = c.containerid AND cq.completedtimestamp IS NULL + WHERE d.shippingid = :1", array($ship['SHIPPINGID'])); + if (sizeof($containers)) $this->_error('Cannot return shipment, there are still uncompleted queued containers: View'); $this->db->pq("UPDATE shipping SET shippingstatus='returned' where shippingid=:1", array($ship['SHIPPINGID'])); $this->db->pq("UPDATE dewar SET dewarstatus='returned' where shippingid=:1", array($ship['SHIPPINGID'])); diff --git a/client/src/js/modules/shipment/controller.js b/client/src/js/modules/shipment/controller.js index a73f0dfcf..521c725d9 100644 --- a/client/src/js/modules/shipment/controller.js +++ b/client/src/js/modules/shipment/controller.js @@ -361,14 +361,14 @@ define(['backbone', }) }, - queued_containers: function(s, ty, pt, bl, page) { + queued_containers: function(s, ty, pt, bl, sid, page) { if (!app.staff) { app.message({ title: 'No access', message: 'You do not have access to that page'}) return } app.bc.reset([bc, { title: 'Queued Containers' }]) - app.content.show(new QueuedContainers({ params: { s: s, ty: ty, pt: pt, bl: bl, page: page }})) + app.content.show(new QueuedContainers({ params: { s: s, ty: ty, pt: pt, bl: bl, sid: sid, page: page }})) }, container_review: function(cid) { diff --git a/client/src/js/modules/shipment/router.js b/client/src/js/modules/shipment/router.js index 685ceb8c5..3009b7db0 100644 --- a/client/src/js/modules/shipment/router.js +++ b/client/src/js/modules/shipment/router.js @@ -19,7 +19,7 @@ define(['utils/lazyrouter'], function(LazyRouter) { 'containers/registry(/ty/:ty)(/s/:s)(/page/:page)': 'container_registry', 'containers/registry/:crid': 'view_rcontainer', - 'containers/queued(/s/:s)(/ty/:ty)(/pt/:pt)(/bl/:bl)(/page/:page)': 'queued_containers', + 'containers/queued(/s/:s)(/ty/:ty)(/pt/:pt)(/bl/:bl)(/sid/:sid)(/page/:page)': 'queued_containers', 'containers/review/:cid': 'container_review', 'dewars(/s/:s)(/page/:page)': 'dewar_list', diff --git a/client/src/js/modules/shipment/views/queuedcontainers.js b/client/src/js/modules/shipment/views/queuedcontainers.js index d879aa318..78a162d7f 100644 --- a/client/src/js/modules/shipment/views/queuedcontainers.js +++ b/client/src/js/modules/shipment/views/queuedcontainers.js @@ -138,6 +138,7 @@ define(['backbone', 'marionette', this.collection.queryParams.all = 1 this.collection.queryParams.PUCK = 1 this.collection.queryParams.ty = 'queued' + if (options.params.sid) this.collection.queryParams.SHIPPINGID = options.params.sid this.collection.state.currentPage = options.params.page this.listenTo(this.collection, 'sync', this.updateTotal) diff --git a/client/src/js/modules/shipment/views/shipment.js b/client/src/js/modules/shipment/views/shipment.js index b9282a2a2..4a654764f 100644 --- a/client/src/js/modules/shipment/views/shipment.js +++ b/client/src/js/modules/shipment/views/shipment.js @@ -128,8 +128,21 @@ define(['marionette', }, 500) }, - error: function() { - app.alert({ message: 'Something went wrong marking this shipment returned, please try again' }) + error: function(xhr) { + var json = {}; + if (xhr.responseText) { + try { + json = JSON.parse(xhr.responseText) + } catch(err) { + + } + } + + if (json.message) { + app.alert({ message: json.message }) + } else { + app.alert({ message: 'Something went wrong marking this shipment returned, please try again' }) + } }, }) @@ -158,7 +171,7 @@ define(['marionette', var json = {}; if (xhr.responseText) { try { - json = $.parseJSON(xhr.responseText) + json = JSON.parse(xhr.responseText) } catch(err) { } From 27961468a97f5a57e44194619143d33e13de0150 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 29 May 2021 17:40:22 +0200 Subject: [PATCH 34/56] allow anyone to use queue shipment button if enabled --- README.md | 1 + client/src/js/modules/shipment/views/shipment.js | 4 +++- client/src/js/templates/shipment/shipment.html | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 768ee7ef3..4369ca704 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ This file should be copied to create a client/src/js/config.json file and edited | csv_profile | The csv profile for importing shipments, currently only imca, see src/js/csv/imca.js | | enable_exp_plan | Whether to enable editing of experimental plan fields when creating samples | | auto_collect_label | Customise the auto collect label from the default 'Automated' | +| queue_shipment | Allow entire shipment to be queued for automated / mail-in collection | Site Image can be customised via the tailwind.config.js header-site-logo and footer-site-logo values. diff --git a/client/src/js/modules/shipment/views/shipment.js b/client/src/js/modules/shipment/views/shipment.js index 4a654764f..5ad18f92f 100644 --- a/client/src/js/modules/shipment/views/shipment.js +++ b/client/src/js/modules/shipment/views/shipment.js @@ -43,7 +43,9 @@ define(['marionette', APIURL: app.apiurl, PROP: app.prop, DHL_ENABLE: app.options.get('dhl_enable'), - IS_STAFF: app.staff + IS_STAFF: app.staff, + QUEUE_SHIPMENT: app.options.get('queue_shipment'), + AUTO_LABEL: app.config.auto_collect_label || 'Automated' } }, diff --git a/client/src/js/templates/shipment/shipment.html b/client/src/js/templates/shipment/shipment.html index e2e2be064..07457d67c 100644 --- a/client/src/js/templates/shipment/shipment.html +++ b/client/src/js/templates/shipment/shipment.html @@ -13,8 +13,8 @@

    Shipment: <%-SHIPPINGNAME%>

    <% if (LCOUT && LCRET) { %>
    - <% if (IS_STAFF) { %> - Queue + <% if (QUEUE_SHIPMENT) { %> + Queue for <%-AUTO_LABEL%> <% } %> <% if (SHIPPINGSTATUS == 'opened' || SHIPPINGSTATUS == 'awb created' || SHIPPINGSTATUS == 'pickup booked') { %> From 27112d6a0681c380bf51001e2dd206328f789ed6 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 29 May 2021 17:46:35 +0200 Subject: [PATCH 35/56] add samples to containerqueue for pucks and allow editing their status manually --- api/src/Page/Sample.php | 42 +++++++++++- api/src/Page/Shipment.php | 59 +++++++++++++---- client/src/css/partials/_content.scss | 35 +++++++++- .../modules/shipment/views/containerreview.js | 66 ++++++++++++++++--- 4 files changed, 179 insertions(+), 23 deletions(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index 63b79b31b..9b1e9db90 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -147,6 +147,7 @@ class Sample extends Page 'SHIPPINGID' => '\d+', 'SAMPLEGROUPID' => '\d+', + 'QUEUESTATUS' => '\w+', ); @@ -159,6 +160,8 @@ class Sample extends Page array('/components', 'post', '_add_sample_component'), array('/components/:scid', 'delete', '_remove_sample_component'), + array('/queue/:CONTAINERQUEUESAMPLEID', 'patch', '_update_sample_queue'), + array('/sub(/:ssid)(/sid/:sid)', 'get', '_sub_samples'), array('/sub/:ssid', 'patch', '_update_sub_sample'), array('/sub/:ssid', 'put', '_update_sub_sample_full'), @@ -965,7 +968,8 @@ function _samples() { , $cseq $sseq string_agg(cpr.name) as componentnames, string_agg(cpr.density) as componentdensities ,string_agg(cpr.proteinid) as componentids, string_agg(cpr.acronym) as componentacronyms, string_agg(cpr.global) as componentglobals, string_agg(chc.abundance) as componentamounts, string_agg(ct.symbol) as componenttypesymbols, b.volume, pct.symbol,ROUND(cr.abundance,3) as abundance, TO_CHAR(b.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp, dp.radiationsensitivity, dp.energy, dp.userpath, dp.aimedresolution, dp.preferredbeamsizex, dp.preferredbeamsizey, dp.exposuretime, dp.axisstart, dp.axisrange, dp.numberofimages, dp.transmission, dp.collectionmode, dp.priority, - GROUP_CONCAT(distinct a.spacegroup SEPARATOR ', ') as dcspacegroup + GROUP_CONCAT(distinct a.spacegroup SEPARATOR ', ') as dcspacegroup, + cqss.status as lastqueuestatus, cq.containerqueueid, cqs.containerqueuesampleid FROM blsample b @@ -983,7 +987,12 @@ function _samples() { INNER JOIN proposal p ON p.proposalid = pr.proposalid LEFT OUTER JOIN containerqueue cq ON cq.containerid = c.containerid AND cq.completedtimestamp IS NULL - + LEFT OUTER JOIN containerqueuesample cqs ON cqs.blsampleid = b.blsampleid AND cq.containerqueueid = cqs.containerqueueid + + LEFT OUTER JOIN containerqueuesample cqss ON cqss.containerqueuesampleid = ( + SELECT MAX(containerqueuesampleid) FROM containerqueuesample _cqs WHERE _cqs.blsampleid = b.blsampleid + ) + LEFT OUTER JOIN diffractionplan dp ON dp.diffractionplanid = b.diffractionplanid LEFT OUTER JOIN datacollection dc ON b.blsampleid = dc.blsampleid LEFT OUTER JOIN datacollectiongroup dcg ON dc.datacollectiongroupid = dcg.datacollectiongroupid @@ -1094,6 +1103,35 @@ function _update_sample_components($initial, $final, $amounts, $crystalid) { } } + // Manually update the status of a sample in the queue + function _update_sample_queue() { + $statuses = array('completed', 'skipped', 'reinspect', 'failed'); + + if (!$this->staff) $this->_error('No access'); + if (!$this->has_arg('prop')) $this->_error('No proposal specified'); + if (!$this->has_arg('CONTAINERQUEUESAMPLEID')) $this->_error('No sample container queue id specified'); + if (!$this->has_arg('QUEUESTATUS') || !in_array($this->arg('QUEUESTATUS'), $statuses)) $this->_error('No status specified'); + + $chk = $this->db->pq("SELECT s.blsampleid + FROM blsample s + INNER JOIN containerqueuesample cqs ON cqs.blsampleid = s.blsampleid + INNER JOIN container c ON c.containerid = s.containerid + INNER JOIN dewar d ON d.dewarid = c.dewarid + INNER JOIN shipping sh ON sh.shippingid = d.shippingid + WHERE sh.proposalid=:1 AND cqs.containerqueuesampleid=:2", + array($this->proposalid, $this->arg('CONTAINERQUEUESAMPLEID'))); + + if (!sizeof($chk)) $this->_error('Sample not queued'); + + $this->db->pq('UPDATE containerqueuesample SET endtime=CURRENT_TIMESTAMP, status=:1 WHERE containerqueuesampleid=:2', + array($this->arg('QUEUESTATUS'), $this->arg('CONTAINERQUEUESAMPLEID'))); + + $this->_output(array( + 'CONTAINERQUEUESTATUSID' => $this->arg('CONTAINERQUEUESAMPLEID'), + 'QUEUESTATUS' => $this->arg('QUEUESTATUS') + )); + } + function _add_sample() { if (!$this->has_arg('prop')) $this->_error('No proposal specified'); diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 640ba54a8..406d9bfb7 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1581,7 +1581,15 @@ function _queue_container() { $cqid = $chkq[0]['CONTAINERQUEUEID']; - $this->db->pq("UPDATE containerqueuesample SET containerqueueid = NULL WHERE containerqueueid=:1", array($cqid)); + // For pucks delete the containerqueuesample items + if (stripos($chkc[0]['CONTAINERTYPE'], 'puck') !== false) { + $this->db->pq("DELETE FROM containerqueuesample WHERE containerqueueid=:1", array($cqid)); + + // For plates we have a pre queued "sample is ready to queue", where containerqueueid is null + // so just unset containerqueueid in containerqueuesample + } else { + $this->db->pq("UPDATE containerqueuesample SET containerqueueid = NULL WHERE containerqueueid=:1", array($cqid)); + } $this->db->pq("DELETE FROM containerqueue WHERE containerqueueid=:1", array($cqid)); $this->_output(); @@ -1592,23 +1600,45 @@ function _queue_container() { $this->db->pq("INSERT INTO containerqueue (containerid, personid) VALUES (:1, :2)", array($this->arg('CONTAINERID'), $this->user->personid)); $qid = $this->db->id(); - $samples = $this->db->pq("SELECT ss.blsubsampleid, cqs.containerqueuesampleid FROM blsubsample ss - INNER JOIN blsample s ON s.blsampleid = ss.blsampleid - INNER JOIN container c ON c.containerid = s.containerid - INNER JOIN dewar d ON d.dewarid = c.dewarid - INNER JOIN shipping sh ON sh.shippingid = d.shippingid - INNER JOIN proposal p ON p.proposalid = sh.proposalid - INNER JOIN containerqueuesample cqs ON cqs.blsubsampleid = ss.blsubsampleid - WHERE p.proposalid=:1 AND c.containerid=:2 AND cqs.containerqueueid IS NULL AND ss.source='manual'", array($this->proposalid, $this->arg('CONTAINERID'))); - - foreach ($samples as $s) { - $this->db->pq("UPDATE containerqueuesample SET containerqueueid=:1 WHERE containerqueuesampleid=:2", array($qid, $s['CONTAINERQUEUESAMPLEID'])); + // For pucks samples are queued + if (stripos($chkc[0]['CONTAINERTYPE'], 'puck') !== false) { + $this->_queue_samples($this->arg('CONTAINERID'), $qid); + + // For plates subsamples are queued + } else { + $subsamples = $this->db->pq("SELECT ss.blsubsampleid, cqs.containerqueuesampleid FROM blsubsample ss + INNER JOIN blsample s ON s.blsampleid = ss.blsampleid + INNER JOIN container c ON c.containerid = s.containerid + INNER JOIN dewar d ON d.dewarid = c.dewarid + INNER JOIN shipping sh ON sh.shippingid = d.shippingid + INNER JOIN proposal p ON p.proposalid = sh.proposalid + INNER JOIN containerqueuesample cqs ON cqs.blsubsampleid = ss.blsubsampleid + WHERE p.proposalid=:1 AND c.containerid=:2 AND cqs.containerqueueid IS NULL AND ss.source='manual'", array($this->proposalid, $this->arg('CONTAINERID'))); + + foreach ($subsamples as $s) { + $this->db->pq("UPDATE containerqueuesample SET containerqueueid=:1 WHERE containerqueuesampleid=:2", array($qid, $s['CONTAINERQUEUESAMPLEID'])); + } } $this->_output(array('CONTAINERQUEUEID' => $qid)); } } + function _queue_samples($cid, $qid) { + $samples = $this->db->pq("SELECT s.blsampleid + FROM blsample s + INNER JOIN container c ON c.containerid = s.containerid + INNER JOIN dewar d ON d.dewarid = c.dewarid + INNER JOIN shipping sh ON sh.shippingid = d.shippingid + INNER JOIN proposal p ON p.proposalid = sh.proposalid + WHERE p.proposalid=:1 AND c.containerid=:2", + array($this->proposalid, $cid)); + + foreach ($samples as $s) { + $this->db->pq("INSERT INTO containerqueuesample (blsampleid, containerqueueid) VALUES (:1, :2)", array($s['BLSAMPLEID'], $qid)); + } + } + # Manually update a container queue status to completed function _update_container_queue() { if (!$this->staff) $this->_error("No access"); @@ -1712,6 +1742,11 @@ function _add_container() { if ($this->has_arg('AUTOMATED')) { $this->db->pq("INSERT INTO containerqueue (containerid, personid) VALUES (:1, :2)", array($cid, $this->user->personid)); + $qid = $this->db->id(); + + if (stripos($this->arg('CONTAINERTYPE'), 'puck') !== false) { + $this->_queue_samples($cid, $qid); + } } $this->_output(array('CONTAINERID' => $cid)); diff --git a/client/src/css/partials/_content.scss b/client/src/css/partials/_content.scss index 0e629d617..2f2e14edb 100644 --- a/client/src/css/partials/_content.scss +++ b/client/src/css/partials/_content.scss @@ -603,12 +603,45 @@ ul.status { &.COMP { &:before { - content: "Completed"; + content: 'Completed' } background-color: #87ceeb; } + // Queue statuses + &.skipped { + &:before { + content: 'Skipped' + } + + background-color: #fdfd96; + } + + &.reinspect { + &:before { + content: 'Re-inspect' + } + + background-color: #ffb347; + } + + &.completed { + &:before { + content: 'Completed' + } + + background-color: #77dd77; + } + + &.failed { + &:before { + content: 'Failed' + } + + background-color: #ff6961; + } + &.XS { &:before { content: "XRF Spectrum"; diff --git a/client/src/js/modules/shipment/views/containerreview.js b/client/src/js/modules/shipment/views/containerreview.js index 9e90962a6..f5a65bcbe 100644 --- a/client/src/js/modules/shipment/views/containerreview.js +++ b/client/src/js/modules/shipment/views/containerreview.js @@ -1,16 +1,27 @@ -define(['marionette', +define(['backbone', + 'marionette', 'backgrid', 'views/table', 'utils/table', 'collections/samples', 'templates/shipment/containerreview.html' -], function(Marionette, +], function(Backbone, + Marionette, Backgrid, TableView, table, Samples, template) { + var QueueStatusCell = Backgrid.Cell.extend({ + render: function() { + var st = this.model.escape('LASTQUEUESTATUS') + if (st) this.$el.html('
    ') + + return this + } + }) + var UCTemplate = '\ \ \ @@ -39,20 +50,58 @@ define(['marionette', className: 'nowrap', events: { - 'click a.reinspect': 'markInspect', - 'click a.skip': 'markSkip' + 'click a.reinspect': 'markReinspect', + 'click a.skip': 'markSkip', + 'click a.completed': 'markCompleted' }, render: function() { - if (app.staff) { - this.$el.html('') - this.$el.append(' ') + if (app.staff && this.model.get('CONTAINERQUEUESAMPLEID')) { + var cs = this.model.get('LASTQUEUESTATUS') + if (cs != 'reinspect') this.$el.html('') + if (cs != 'skipped') this.$el.append(' ') + if (cs != 'completed') this.$el.append(' ') } this.$el.append('   View Sample') return this - } + }, + + markReinspect: function(e) { + e.preventDefault() + this.doMarkSample('reinspect') + }, + + markSkip: function(e) { + e.preventDefault() + this.doMarkSample('skipped') + }, + + markCompleted: function(e) { + e.preventDefault() + this.doMarkSample('completed') + }, + + doMarkSample: function(status) { + var self = this + Backbone.ajax({ + url: app.apiurl+'/sample/queue/'+this.model.get('CONTAINERQUEUESAMPLEID'), + data: JSON.stringify({ + prop: app.prop, + QUEUESTATUS: status + }), + type: 'PATCH', + success: function() { + app.alert({ className: 'message notify', message: 'Sample queue status upated', scrollTo: false }) + self.model.collection.fetch() + }, + error: function() { + app.alert({ message: 'Something went wrong updating this samples queue status, please try again' }) + }, + + }) + }, }) return Marionette.LayoutView.extend({ @@ -89,6 +138,7 @@ define(['marionette', { name: 'DCSPACEGROUP', label: 'Observed SG', cell: 'string', editable: false }, { name: 'STAFFCOMMENTS', label: 'Staff Comments', cell: 'string', editable: app.staff }, { label: 'Status', cell: table.StatusCell, editable: false }, + { label: 'Queue', cell: QueueStatusCell, editable: false }, { label: '', cell: ActionCell, editable: false }, ] From 2fdd64b2f85f950dbb75063b0b0aff4dd1988f2d Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 31 May 2021 19:37:20 +0200 Subject: [PATCH 36/56] correct where option comes from... --- client/src/js/modules/shipment/views/shipment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/modules/shipment/views/shipment.js b/client/src/js/modules/shipment/views/shipment.js index 5ad18f92f..a49016a3d 100644 --- a/client/src/js/modules/shipment/views/shipment.js +++ b/client/src/js/modules/shipment/views/shipment.js @@ -44,7 +44,7 @@ define(['marionette', PROP: app.prop, DHL_ENABLE: app.options.get('dhl_enable'), IS_STAFF: app.staff, - QUEUE_SHIPMENT: app.options.get('queue_shipment'), + QUEUE_SHIPMENT: app.config.queue_shipment, AUTO_LABEL: app.config.auto_collect_label || 'Automated' } }, From edf5155eef44bbfdc69ca8a2ec2182898d07799e Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 5 Jun 2021 17:25:37 +0200 Subject: [PATCH 37/56] increase number of containers when queuing a shipment --- client/src/js/modules/shipment/views/shipment.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/js/modules/shipment/views/shipment.js b/client/src/js/modules/shipment/views/shipment.js index a49016a3d..3e6440ad4 100644 --- a/client/src/js/modules/shipment/views/shipment.js +++ b/client/src/js/modules/shipment/views/shipment.js @@ -154,6 +154,8 @@ define(['marionette', e.preventDefault() var containers = new Containers() + // make sure to return all containers in the shipment + containers.state.pageSize = 100 containers.queryParams.SHIPPINGID = this.model.get('SHIPPINGID') containers.fetch().done(function () { var promises = [] From 168330ab1168e530dcd9ca1570f2a35db6bd3a7d Mon Sep 17 00:00:00 2001 From: Neil A Smith Date: Mon, 26 Jul 2021 10:57:49 +0100 Subject: [PATCH 38/56] Enable UAS API to set delivered time on close --- api/src/UAS.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/UAS.php b/api/src/UAS.php index dcf700a75..8c21d7883 100644 --- a/api/src/UAS.php +++ b/api/src/UAS.php @@ -83,10 +83,10 @@ function update_session($sessionid, $data=array()) { return $this->code; } - function close_session($sessionid) { + function close_session($sessionid, $deliveredTime=0) { $resp = $this->_curl(array( 'URL' => $this->url.'/uas/rest/v1/session/'.$sessionid, - 'FIELDS' => array('endAt' => date('Y-m-d\TH:i:s.000\Z')), + 'FIELDS' => array('endAt' => date('Y-m-d\TH:i:s.000\Z'), $deliveredTime), 'PATCH' => 1, 'HEADERS' => array( 'Content-type: application/json', From 38d43616f7949488b1880cba482d4e6c47f5384b Mon Sep 17 00:00:00 2001 From: Neil A Smith Date: Mon, 26 Jul 2021 10:58:16 +0100 Subject: [PATCH 39/56] Add hooks for determining delivered time on session close api call --- api/src/Page/Proposal.php | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/api/src/Page/Proposal.php b/api/src/Page/Proposal.php index e60f42953..e938bfdfd 100644 --- a/api/src/Page/Proposal.php +++ b/api/src/Page/Proposal.php @@ -1168,24 +1168,30 @@ function _close_auto_visit() { if (!sizeof($cont)) $this->_error('No such container'); $cont = $cont[0]; - if ($cont['SESSIONID']) { - $uas = new UAS($auto_user, $auto_pass); - $code = $uas->close_session($cont['EXTERNALID']); + if (!$cont['SESSIONID']) $this->_error('That container does not have a session'); - if ($code == 200) { - // Don't wait for UAS sync - set end Date in ISPyB now as well - $this->db->pq("UPDATE blsession SET endDate=CURRENT_TIMESTAMP WHERE sessionid=:1", array($cont['SESSIONID'])); - $this->_output(array('MESSAGE' => 'Session closed', 'VISIT' => $cont['VISIT'])); - } else if ($code == 403) { - $this->_output(array('MESSAGE' => 'Session already closed', 'VISIT' => $cont['VISIT'])); + $delivered_time = $this->_calculate_delivered_time($cont['SESSIONID']); + $uas = new UAS($auto_user, $auto_pass); + $code = $uas->close_session($cont['EXTERNALID'], $delivered_time); - } else { - $this->_error('Something went wrong closing that session, response code was: '.$code); - } + if ($code == 200) { + // Don't wait for UAS sync - set end Date in ISPyB now as well + $this->db->pq("UPDATE blsession SET endDate=CURRENT_TIMESTAMP WHERE sessionid=:1", array($cont['SESSIONID'])); + $this->_output(array('MESSAGE' => 'Session closed', 'VISIT' => $cont['VISIT'])); + } else if ($code == 403) { + $this->_output(array('MESSAGE' => 'Session already closed', 'VISIT' => $cont['VISIT'])); } else { - $this->_error('That container does not have a session'); + $this->_error('Something went wrong closing that session, response code was: '.$code); } - + } + /* + * TODO - add algorithm from Mark W - determine how much delivered time this session has used + * @return delivered time in minutes. + */ + function _calculate_delivered_time($sessionId) { + $delivered_time = 42; + + return $delivered_time; } } From f1d61b94ed57959ac0d9516caf9bd5ae78ebbcec Mon Sep 17 00:00:00 2001 From: Neil Smith Date: Thu, 29 Jul 2021 10:30:25 +0100 Subject: [PATCH 40/56] Add missing import in shipment routes file --- client/src/js/modules/shipment/routes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/js/modules/shipment/routes.js b/client/src/js/modules/shipment/routes.js index e96564423..db61f3e5f 100644 --- a/client/src/js/modules/shipment/routes.js +++ b/client/src/js/modules/shipment/routes.js @@ -11,6 +11,7 @@ import Backbone from 'backbone' import Shipments from 'collections/shipments.js' import Shipment from 'models/shipment.js' +import Container from 'models/container.js' import Containers from 'collections/containers.js' import ContainerRegistry from 'modules/shipment/models/containerregistry' import ContainersRegistry from 'modules/shipment/collections/containerregistry' From 5d868a15ba876da02576d2ef68923825cca6f455 Mon Sep 17 00:00:00 2001 From: Neil A Smith Date: Thu, 29 Jul 2021 14:25:01 +0100 Subject: [PATCH 41/56] Update comments to describe custom site logo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4369ca704..daf52d96b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ $ git clone https://github.com/DiamondLightSource/SynchWeb ### Customise front end - config.json An example configuration is provided in client/src/js/config_sample.json This file should be copied to create a client/src/js/config.json file and edited to customise the application for your site. +To create your own logo at the top of the page, update the tailwind.config.js header-site-logo. +The footer logo (ispyb logo image) can also be customised by setting the tailwind.config.js footer-site-logo before building the client. | Parameter | Description | | ------ | ------ | @@ -35,8 +37,6 @@ This file should be copied to create a client/src/js/config.json file and edited | maintenance_message | Can be used so app serves static page under maintenance periods | | maintenance | Flag to indicate if client is in maintenance mode| | ga_ident | Google Analytics id| -| site_name | Site Name to display in footer | -| site_link | URL to site home page | | data_catalogue | Object that includes name and url property for a link to a data catalogue - displayed on the landing page | | site_image | PNG image of site logo to display in header| | csv_profile | The csv profile for importing shipments, currently only imca, see src/js/csv/imca.js | From 3c09b294638294c0911265a58c8826a6c52faf4c Mon Sep 17 00:00:00 2001 From: Neil A Smith Date: Thu, 29 Jul 2021 14:54:10 +0100 Subject: [PATCH 42/56] Added missing route for import csv --- client/src/js/modules/shipment/routes.js | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/client/src/js/modules/shipment/routes.js b/client/src/js/modules/shipment/routes.js index db61f3e5f..ac420ee39 100644 --- a/client/src/js/modules/shipment/routes.js +++ b/client/src/js/modules/shipment/routes.js @@ -36,6 +36,7 @@ const ManifestView = import(/* webpackChunkName: "shipment" */ 'modules/shipment const DewarStats = import(/* webpackChunkName: "shipment-stats" */ 'modules/shipment/views/dewarstats') const DispatchView = import(/*webpackChunkName: "shipment" */ 'modules/shipment/views/dispatch') const TransferView = import(/*webpackChunkName: "shipment" */ 'modules/shipment/views/transfer') +const ImportFromCSV = import(/*webpackChunkName: "shipment" */ 'modules/shipment/views/fromcsv') // In future may want to move these into wrapper components // Similar approach was used for samples with a samples-map to determine the correct view @@ -311,6 +312,31 @@ const routes = [ breadcrumbs: [bc, { title: 'Dewar Stats' }] } }, + { + path: 'csv/:sid', + name: 'shipment-import-csv', + component: MarionetteView, + props: route => ({ + mview: ImportFromCSV, + breadcrumbs: [bc, { title: 'Import from CSV' }], + breadcrumb_tags: ['SHIPPINGNAME'], // Append shipment model name to the bc + options: { + model: new Shipment({ SHIPPINGID: route.params.sid }) + } + }), + beforeEnter: (to, from, next) => { + // Call the loading state here because we are finding the proposal based on this shipment id + // Prop lookup sets the proposal and type via set application.cookie method which we mapped to the store + store.dispatch('proposal/proposalLookup', { field: 'SHIPPINGID', value: to.params.sid } ) + .then( () => { + console.log("Calling next - Success, shipment model will be prefetched in marionette view") + next() + }, () => { + console.log("Error, no proposal found from the shipment id") + next('/notfound') + }) + } + }, ], }, // From 252811f72d842c63e0aba51d35ea97ffaffc8ac4 Mon Sep 17 00:00:00 2001 From: Neil A Smith Date: Thu, 29 Jul 2021 14:54:46 +0100 Subject: [PATCH 43/56] Make clear csv_profile in config.json does not need file extension --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index daf52d96b..a7eeb2f8a 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,6 @@ The footer logo (ispyb logo image) can also be customised by setting the tailwin | maintenance | Flag to indicate if client is in maintenance mode| | ga_ident | Google Analytics id| | data_catalogue | Object that includes name and url property for a link to a data catalogue - displayed on the landing page | -| site_image | PNG image of site logo to display in header| | csv_profile | The csv profile for importing shipments, currently only imca, see src/js/csv/imca.js | | enable_exp_plan | Whether to enable editing of experimental plan fields when creating samples | | auto_collect_label | Customise the auto collect label from the default 'Automated' | From bf67b48a47d1bccc07081799becce876d09aa8b5 Mon Sep 17 00:00:00 2001 From: Neil A Smith Date: Thu, 29 Jul 2021 15:46:56 +0100 Subject: [PATCH 44/56] Prevent sample patch failing because no container id --- api/src/Page/Sample.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index 9b1e9db90..5d266cd07 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -1520,7 +1520,20 @@ function _update_sample() { } } - if($this->has_arg('PLANORDER')) { + // Deal with special case (xpdf?) + // This method is used to update/patch samples so it does not necessarily have a containerid + // Isolate the requirement for a container id here because its only important when updating collection plans + $maxLocation = null; + + if($this->has_arg('CONTAINERID') && $this->arg('CONTAINERID') == 0) { + $defaultContainerLocation = $this->_get_default_sample_container(); + $this->args['CONTAINERID'] = $defaultContainerLocation['CONTAINERID']; + $this->args['LOCATION'] = $defaultContainerLocation['LOCATION']; + $maxLocation = $this->_get_current_max_dcp_plan_order($this->args['CONTAINERID']); + $maxLocation = sizeof($maxLocation) ? $maxLocation : -1; + } + + if($this->has_arg('PLANORDER') && $maxLocation != null) { // If we're moving a BLSample to a new container we need to adjust the DCP plan order not to clash with existing plans for samples in the new container $dcps = $this->db->pq("SELECT dataCollectionPlanId FROM BLSample_has_DataCollectionPlan WHERE blSampleId = :1", array($this->arg('sid'))); From 35dfcb98add9d2772980539733e5a86347706e5c Mon Sep 17 00:00:00 2001 From: Neil A Smith Date: Mon, 26 Jul 2021 10:57:49 +0100 Subject: [PATCH 45/56] Revert "Enable UAS API to set delivered time on close" This reverts commit 168330ab1168e530dcd9ca1570f2a35db6bd3a7d. --- api/src/UAS.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/UAS.php b/api/src/UAS.php index 8c21d7883..dcf700a75 100644 --- a/api/src/UAS.php +++ b/api/src/UAS.php @@ -83,10 +83,10 @@ function update_session($sessionid, $data=array()) { return $this->code; } - function close_session($sessionid, $deliveredTime=0) { + function close_session($sessionid) { $resp = $this->_curl(array( 'URL' => $this->url.'/uas/rest/v1/session/'.$sessionid, - 'FIELDS' => array('endAt' => date('Y-m-d\TH:i:s.000\Z'), $deliveredTime), + 'FIELDS' => array('endAt' => date('Y-m-d\TH:i:s.000\Z')), 'PATCH' => 1, 'HEADERS' => array( 'Content-type: application/json', From d6be16e20a333c9eca259c5dc7b39370c92cf56a Mon Sep 17 00:00:00 2001 From: Neil A Smith Date: Mon, 26 Jul 2021 10:58:16 +0100 Subject: [PATCH 46/56] Revert "Add hooks for determining delivered time on session close api call" This reverts commit 38d43616f7949488b1880cba482d4e6c47f5384b. --- api/src/Page/Proposal.php | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/api/src/Page/Proposal.php b/api/src/Page/Proposal.php index e938bfdfd..e60f42953 100644 --- a/api/src/Page/Proposal.php +++ b/api/src/Page/Proposal.php @@ -1168,30 +1168,24 @@ function _close_auto_visit() { if (!sizeof($cont)) $this->_error('No such container'); $cont = $cont[0]; - if (!$cont['SESSIONID']) $this->_error('That container does not have a session'); + if ($cont['SESSIONID']) { + $uas = new UAS($auto_user, $auto_pass); + $code = $uas->close_session($cont['EXTERNALID']); - $delivered_time = $this->_calculate_delivered_time($cont['SESSIONID']); - $uas = new UAS($auto_user, $auto_pass); - $code = $uas->close_session($cont['EXTERNALID'], $delivered_time); + if ($code == 200) { + // Don't wait for UAS sync - set end Date in ISPyB now as well + $this->db->pq("UPDATE blsession SET endDate=CURRENT_TIMESTAMP WHERE sessionid=:1", array($cont['SESSIONID'])); + $this->_output(array('MESSAGE' => 'Session closed', 'VISIT' => $cont['VISIT'])); + } else if ($code == 403) { + $this->_output(array('MESSAGE' => 'Session already closed', 'VISIT' => $cont['VISIT'])); - if ($code == 200) { - // Don't wait for UAS sync - set end Date in ISPyB now as well - $this->db->pq("UPDATE blsession SET endDate=CURRENT_TIMESTAMP WHERE sessionid=:1", array($cont['SESSIONID'])); - $this->_output(array('MESSAGE' => 'Session closed', 'VISIT' => $cont['VISIT'])); - } else if ($code == 403) { - $this->_output(array('MESSAGE' => 'Session already closed', 'VISIT' => $cont['VISIT'])); + } else { + $this->_error('Something went wrong closing that session, response code was: '.$code); + } } else { - $this->_error('Something went wrong closing that session, response code was: '.$code); + $this->_error('That container does not have a session'); } - } - /* - * TODO - add algorithm from Mark W - determine how much delivered time this session has used - * @return delivered time in minutes. - */ - function _calculate_delivered_time($sessionId) { - $delivered_time = 42; - - return $delivered_time; + } } From d20a907785660da06ba06dd5563267181003a8c8 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 13 Sep 2021 21:46:12 +0200 Subject: [PATCH 47/56] add missing routes --- client/src/js/modules/assign/routes.js | 18 ++++++++- client/src/js/modules/shipment/routes.js | 47 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/client/src/js/modules/assign/routes.js b/client/src/js/modules/assign/routes.js index c8f55e558..869e6c98e 100644 --- a/client/src/js/modules/assign/routes.js +++ b/client/src/js/modules/assign/routes.js @@ -7,6 +7,7 @@ import Visits from 'collections/visits' import SelectVisitView from 'modules/assign/views/selectvisit' import AssignView from 'modules/assign/views/assign' +import ScanAssignView from 'modules/assign/views/scanassign' let bc = { title: 'Assign Containers', url: '/assign' } @@ -93,7 +94,22 @@ const routes = [ // In either case we can stop the loading animation app.loading(false) }) - } + }, + }, + { + path: 'scan/:bl([a-zA-Z0-9_-]+)', + name: 'assign-scan', + meta: { + permission: 'scan_assign' + }, + component: MarionetteView, + props: route => ({ + mview: ScanAssignView, + options: { + bl: route.params.bl + }, + breadcrumbs: [bc,{ title: 'Assign Containers' }, { title: 'Barcode Scan'}, { title: route.params.bl }] + }), } ] } diff --git a/client/src/js/modules/shipment/routes.js b/client/src/js/modules/shipment/routes.js index ac420ee39..09f4bb264 100644 --- a/client/src/js/modules/shipment/routes.js +++ b/client/src/js/modules/shipment/routes.js @@ -46,6 +46,8 @@ const XpdfContainersView = import(/* webpackChunkName: "shipment" */ 'modules/ty const ContainerRegistryView = import(/* webpackChunkName: "shipment" */ 'modules/shipment/views/containerregistry') const RegisteredContainer = import(/* webpackChunkName: "shipment" */ 'modules/shipment/views/registeredcontainer') +const QueuedContainers = import(/* webpackChunkName: "shipment" */ 'modules/shipment/views/queuedcontainers') +const ReviewContainer = import(/* webpackChunkName: "shipment" */ 'modules/shipment/views/containerreview') const MigrateView = import(/* webpackChunkName: "shipment" */ 'modules/shipment/views/migrate') @@ -79,6 +81,10 @@ app.addInitializer(function() { application.on('rcontainer:show', function(crid) { application.navigate('/containers/registry/'+crid) }) + + application.on('container:review', function(cid) { + application.navigate('/containers/review/'+cid) + }) }) let bc = { title: 'Shipments', url: '/shipments' } @@ -400,6 +406,47 @@ const routes = [ cid: +route.params.cid, }), }, + { + path: '/containers/queued(/s/)?:s([a-zA-Z0-9_-]+)?(/ty/)?:ty([a-zA-Z0-9]+)?(/pt/)?:pt([a-zA-Z0-9_-]+)?(/bl/)?:bl([a-zA-Z0-9_-]+)?(/sid/)?:sid([0-9]+)?(/page/)?:page([0-9]+)?', + name: 'containers-queued', + meta: { + permission: 'queued_cont' + }, + component: MarionetteView, + props: route => ({ + mview: QueuedContainers, + options: { + params: { + s: route.params.s, ty: route.params.ty, pt: route.params.pt, + bl: route.params.bl, sid: route.params.sid, page: route.params.page + } + }, + breadcrumbs: [bc, { title: 'Queued Containers' }] + }), + }, + { + path: '/containers/review/:cid([0-9]+)', + name: 'container-review', + component: MarionetteView, + props: route => ({ + mview: ReviewContainer, + options: { + model: new Container({ CONTAINERID: route.params.cid }) + }, + breadcrumbs: [bc, { title: 'Containers' }, { title: 'Review' }], + breadcrumb_tags: ['SHIPMENT', 'NAME'], + beforeEnter: (to, from, next) => { + store.dispatch('proposal/proposalLookup', { field: 'CONTAINERID', value: to.params.cid } ) + .then( () => { + console.log("Calling next - Success. model will be prefetched in marionette view") + next() + }, () => { + console.log("Calling next - Error, no container found") + next('/notfound') + }) + } + }), + }, { path: '/containers/plan/:cid([0-9]+)', component: ContainerPlanWrapper, From 5e4833202a418279cd0bd395a1cecd07f9f57e61 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 10:18:06 +0100 Subject: [PATCH 48/56] fix no images in review page --- client/src/js/modules/shipment/views/containerreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/modules/shipment/views/containerreview.js b/client/src/js/modules/shipment/views/containerreview.js index f5a65bcbe..d95c72afb 100644 --- a/client/src/js/modules/shipment/views/containerreview.js +++ b/client/src/js/modules/shipment/views/containerreview.js @@ -132,7 +132,7 @@ define(['backbone', { name: 'PRIORITY', label: 'Priority', cell: 'string', editable: false }, { name: 'EXPOSURETIME', label: 'Exposure (s)', cell: 'string', editable: false }, { name: 'AXISRANGE', label: 'Axis Range', cell: 'string', editable: false }, - { name: 'AXISSTART', label: 'No Images', cell: 'string', editable: false }, + { name: 'NUMBEROFIMAGES', label: 'No. Images', cell: 'string', editable: false }, { name: 'TRASMISSION', label: 'Transmission', cell: 'string', editable: false }, { name: 'DCRESOLUTION', label: 'Observed Res', cell: 'string', editable: false }, { name: 'DCSPACEGROUP', label: 'Observed SG', cell: 'string', editable: false }, From 9dea442a69b85bd1fd1055d6f575f5cd8aedaf30 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 11:56:43 +0100 Subject: [PATCH 49/56] correct permission for queued containers --- client/src/js/modules/shipment/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/modules/shipment/routes.js b/client/src/js/modules/shipment/routes.js index 09f4bb264..d3230044a 100644 --- a/client/src/js/modules/shipment/routes.js +++ b/client/src/js/modules/shipment/routes.js @@ -410,7 +410,7 @@ const routes = [ path: '/containers/queued(/s/)?:s([a-zA-Z0-9_-]+)?(/ty/)?:ty([a-zA-Z0-9]+)?(/pt/)?:pt([a-zA-Z0-9_-]+)?(/bl/)?:bl([a-zA-Z0-9_-]+)?(/sid/)?:sid([0-9]+)?(/page/)?:page([0-9]+)?', name: 'containers-queued', meta: { - permission: 'queued_cont' + permission: 'auto_dash' }, component: MarionetteView, props: route => ({ From 55cc14849759428510dcf51be3742a7194859671 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 12:51:30 +0100 Subject: [PATCH 50/56] allow searching containers by owner(person).login --- api/src/Page/Shipment.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 406d9bfb7..cd0036808 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1453,6 +1453,7 @@ function _get_all_containers() { LEFT OUTER JOIN containerqueue cq ON cq.containerid = c.containerid AND cq.completedtimestamp IS NULL LEFT OUTER JOIN containerqueue cq2 ON cq2.containerid = c.containerid AND cq2.completedtimestamp IS NOT NULL LEFT OUTER JOIN containerregistry reg ON reg.containerregistryid = c.containerregistryid + LEFT OUTER JOIN person pe ON c.ownerid = pe.personid $join WHERE $where $having", $args); @@ -1460,7 +1461,8 @@ function _get_all_containers() { if ($this->has_arg('s')) { $st = sizeof($args) + 1; - $where .= " AND (lower(c.code) LIKE lower(CONCAT(CONCAT('%',:".$st."), '%')) OR lower(c.barcode) LIKE lower(CONCAT(CONCAT('%',:".($st+1)."), '%')))"; + $where .= " AND (lower(c.code) LIKE lower(CONCAT(CONCAT('%',:".$st."), '%')) OR lower(c.barcode) LIKE lower(CONCAT(CONCAT('%',:".($st+1)."), '%')) OR lower(pe.login) LIKE lower(CONCAT(CONCAT('%',:".($st+2)."), '%')))"; + array_push($args, $this->arg('s')); array_push($args, $this->arg('s')); array_push($args, $this->arg('s')); } From c2438fd804c9835582d3058da9ce07db35a38d03 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 13:33:19 +0100 Subject: [PATCH 51/56] preserve search term in url (ty always first on queued containers) --- client/src/js/modules/shipment/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/modules/shipment/routes.js b/client/src/js/modules/shipment/routes.js index d3230044a..586dfb91e 100644 --- a/client/src/js/modules/shipment/routes.js +++ b/client/src/js/modules/shipment/routes.js @@ -407,7 +407,7 @@ const routes = [ }), }, { - path: '/containers/queued(/s/)?:s([a-zA-Z0-9_-]+)?(/ty/)?:ty([a-zA-Z0-9]+)?(/pt/)?:pt([a-zA-Z0-9_-]+)?(/bl/)?:bl([a-zA-Z0-9_-]+)?(/sid/)?:sid([0-9]+)?(/page/)?:page([0-9]+)?', + path: '/containers/queued(/ty/)?:ty([a-zA-Z0-9]+)?(/s/)?:s([a-zA-Z0-9_-]+)?(/pt/)?:pt([a-zA-Z0-9_-]+)?(/bl/)?:bl([a-zA-Z0-9_-]+)?(/sid/)?:sid([0-9]+)?(/page/)?:page([0-9]+)?', name: 'containers-queued', meta: { permission: 'auto_dash' From a487761a6d8d27bf8d9c365c9ac07b43225bb878 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 13:38:14 +0100 Subject: [PATCH 52/56] apply search params to count query --- api/src/Page/Shipment.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index cd0036808..1549d2a30 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1440,6 +1440,14 @@ function _get_all_containers() { array_push($args, $this->user->personid); } + if ($this->has_arg('s')) { + $st = sizeof($args) + 1; + $where .= " AND (lower(c.code) LIKE lower(CONCAT(CONCAT('%',:".$st."), '%')) OR lower(c.barcode) LIKE lower(CONCAT(CONCAT('%',:".($st+1)."), '%')) OR lower(pe.login) LIKE lower(CONCAT(CONCAT('%',:".($st+2)."), '%')))"; + array_push($args, $this->arg('s')); + array_push($args, $this->arg('s')); + array_push($args, $this->arg('s')); + } + $tot = $this->db->pq("SELECT count(distinct c.containerid) as tot FROM container c INNER JOIN dewar d ON d.dewarid = c.dewarid @@ -1457,16 +1465,7 @@ function _get_all_containers() { $join WHERE $where $having", $args); - $tot = sizeof($tot) ? intval($tot[0]['TOT']) : 0; - - if ($this->has_arg('s')) { - $st = sizeof($args) + 1; - $where .= " AND (lower(c.code) LIKE lower(CONCAT(CONCAT('%',:".$st."), '%')) OR lower(c.barcode) LIKE lower(CONCAT(CONCAT('%',:".($st+1)."), '%')) OR lower(pe.login) LIKE lower(CONCAT(CONCAT('%',:".($st+2)."), '%')))"; - array_push($args, $this->arg('s')); - array_push($args, $this->arg('s')); - array_push($args, $this->arg('s')); - } - + $tot = sizeof($tot) ? intval($tot[0]['TOT']) : 0; $pp = $this->has_arg('per_page') ? $this->arg('per_page') : 15; $pg = $this->has_arg('page') ? $this->arg('page')-1 : 0; From 4904a8c82d813ec97282995b89695a319435cbfe Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 15:16:22 +0100 Subject: [PATCH 53/56] make sure currentPage is int --- client/src/js/modules/shipment/views/queuedcontainers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/js/modules/shipment/views/queuedcontainers.js b/client/src/js/modules/shipment/views/queuedcontainers.js index 78a162d7f..8f431f1ed 100644 --- a/client/src/js/modules/shipment/views/queuedcontainers.js +++ b/client/src/js/modules/shipment/views/queuedcontainers.js @@ -139,7 +139,7 @@ define(['backbone', 'marionette', this.collection.queryParams.PUCK = 1 this.collection.queryParams.ty = 'queued' if (options.params.sid) this.collection.queryParams.SHIPPINGID = options.params.sid - this.collection.state.currentPage = options.params.page + if (options.params.page) this.collection.state.currentPage = parseInt(options.params.page) this.listenTo(this.collection, 'sync', this.updateTotal) var filters = this.getOption('filters').slice(0) @@ -150,7 +150,7 @@ define(['backbone', 'marionette', columns[v].renderable = false }) } - + this.table = new TableView({ collection: this.collection, columns: columns, From 4927bbb09563ef9402f0b6bc1278898cb0c96db8 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 15:43:30 +0100 Subject: [PATCH 54/56] add staffcomments to swagger api --- api/docs/definitions/sample-new.yaml | 5 +++++ api/docs/dist/spec.json | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/api/docs/definitions/sample-new.yaml b/api/docs/definitions/sample-new.yaml index 09738ccc2..3a8958861 100644 --- a/api/docs/definitions/sample-new.yaml +++ b/api/docs/definitions/sample-new.yaml @@ -31,6 +31,11 @@ properties: description: Sample comments example: in 25% PEG4000 50um + STAFFCOMMENTS: + type: string + description: Sample staff comments + example: no crystal in loop + SPACEGROUP: type: string description: Spacegroup diff --git a/api/docs/dist/spec.json b/api/docs/dist/spec.json index 4e2e12716..328be053f 100644 --- a/api/docs/dist/spec.json +++ b/api/docs/dist/spec.json @@ -3714,6 +3714,11 @@ "description": "Sample comments", "example": "in 25% PEG4000 50um" }, + "STAFFCOMMENTS": { + "type": "string", + "description": "Sample staff comments", + "example": "no crystal in loop" + }, "SPACEGROUP": { "type": "string", "description": "Spacegroup", @@ -4018,6 +4023,11 @@ "description": "Sample comments", "example": "in 25% PEG4000 50um" }, + "STAFFCOMMENTS": { + "type": "string", + "description": "Sample staff comments", + "example": "no crystal in loop" + }, "SPACEGROUP": { "type": "string", "description": "Spacegroup", From 94a2e0825a62bce3829a51994cdaa87017e55997 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 15:45:16 +0100 Subject: [PATCH 55/56] received typo --- client/src/js/csv/imca.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/csv/imca.js b/client/src/js/csv/imca.js index 45cd28ac8..8abdbbb16 100644 --- a/client/src/js/csv/imca.js +++ b/client/src/js/csv/imca.js @@ -58,7 +58,7 @@ define([], function() { STATUS: function(m) { var status = 'skipped' if (m.QUEUEDTIMESTAMP) status = 'queued'; - if (m.R > 0) status = 'recieved' + if (m.R > 0) status = 'received' if (m.DC > 0) status = 'collected' return status From 24c5e7969697125c2549523e43277a5b1480ea47 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 7 Jan 2022 16:35:54 +0100 Subject: [PATCH 56/56] prevent container / sample duplication for csv import --- .../src/js/modules/shipment/views/fromcsv.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/client/src/js/modules/shipment/views/fromcsv.js b/client/src/js/modules/shipment/views/fromcsv.js index 9a46fcc08..6d7a3c8e4 100644 --- a/client/src/js/modules/shipment/views/fromcsv.js +++ b/client/src/js/modules/shipment/views/fromcsv.js @@ -504,6 +504,29 @@ define(['backbone', parseCSVContents: function(raw) { var samples = this.createObjects(raw) console.log('parseCSVContents', samples) + + var valid = true + var existingContainers = this.containers.pluck('NAME') + _.each(_.unique(_.pluck(samples, 'CONTAINER')), function(name) { + if (existingContainers.indexOf(name) > -1) { + app.alert({ message: 'Container ' + name + ' already exists' }) + valid = false + } + }) + + var existingSamples = this.samples.pluck('NAME') + _.each(samples, function(sample) { + if (existingSamples.indexOf(sample.NAME) > -1) { + app.alert({ message: 'Sample ' + sample.NAME + ' already exists' }) + valid = false + } + }) + + if (!valid) { + app.alert({ message: 'Duplicate containers and/or samples, aborting' }) + return + } + this.samples.reset(samples) this.containers.reset(_.map(_.unique(_.pluck(samples, 'CONTAINER')), function(name) {