From 562393d170d323f6f55b12a74010c86114ebcf2c Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:06:39 +0200 Subject: [PATCH 01/37] 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 7489a29babdaacd7872a58c9bb32d3ae368e5227 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:07:46 +0200 Subject: [PATCH 02/37] allow filtering containers by shipment, and by registry --- api/src/Page/Shipment.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index a4735e5c3..b0eeaba6e 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -84,6 +84,7 @@ class Shipment extends Page 'unassigned' => '[\w-]+', // Container fields + 'REGISTRY' => '([\w-])+', 'DEWARID' => '\d+', 'CAPACITY' => '\d+', 'CONTAINERTYPE' => '\w+', @@ -1235,6 +1236,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); @@ -1284,6 +1290,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); @@ -1304,6 +1315,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); @@ -1344,7 +1356,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; @@ -1357,7 +1370,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 + 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 @@ -1379,6 +1392,7 @@ 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 pe.personid = c.ownerid $join WHERE $where @@ -1640,7 +1654,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; @@ -1665,7 +1681,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 b64444445141b19364b288e9877cf2145112474c Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:08:28 +0200 Subject: [PATCH 03/37] add diffractionplan columns to sample, get samples by shipment --- api/src/Page/Sample.php | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index 8d7165710..9552755ef 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -115,6 +115,9 @@ class Sample extends Page 'MONOCHROMATOR' => '\w+', 'PRESET' => '\d', 'BEAMLINENAME' => '[\w-]+', + 'AIMEDRESOLUTION' => '\d+(.\d+)?', + 'COLLECTIONMODE' => '\w+', + 'PRIORITY' => '\d+', 'queued' => '\d', 'UNQUEUE' => '\d', @@ -132,6 +135,7 @@ class Sample extends Page 'GROUPORDER' => '\d+', 'TYPE' => '\w+', 'BLSAMPLEGROUPSAMPLEID' => '\d+-\d+', + 'SHIPPINGID' => '\d+', ); @@ -812,6 +816,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); @@ -880,7 +890,9 @@ function _samples() { LEFT OUTER JOIN blsampletype_has_component chc ON b.crystalid = chc.blsampletypeid INNER JOIN proposal p ON p.proposalid = pr.proposalid INNER JOIN container c ON c.containerid = b.containerid - INNER JOIN dewar d ON d.dewarid = c.dewarid $join WHERE $where", $args); + INNER JOIN dewar d ON d.dewarid = c.dewarid + INNER JOIN shipping s ON s.shippingid = d.shippingid + $join WHERE $where", $args); $tot = intval($tot[0]['TOT']); @@ -911,7 +923,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 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 @@ -933,6 +947,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 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 @@ -1002,8 +1017,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']); @@ -1088,12 +1104,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; } @@ -1103,8 +1119,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)) { @@ -1368,7 +1384,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 b4f721b4072c891db55bbeecf3bf1dccf85359b9 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:17:33 +0200 Subject: [PATCH 04/37] 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 | 26 +++++++-- .../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(+), 5 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 8961c3f99..8179a795e 100644 --- a/client/src/css/partials/_tables.scss +++ b/client/src/css/partials/_tables.scss @@ -17,7 +17,7 @@ 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 { @@ -26,7 +26,7 @@ } @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 49309f426..156bc3746 100644 --- a/client/src/js/models/sample.js +++ b/client/src/js/models/sample.js @@ -52,6 +52,15 @@ define(['backbone', 'collections/components', DIMENSION2: '', DIMENSION3: '', SHAPE: '', + AIMEDRESOLUTION: '', + COLLECTIONMODE: '', + PRIORITY: '', + EXPOSURETIME: '', + AXISSTART: '', + AXISRANGE: '', + NUMBEROFIMAGES: '', + TRANSMISSION: '', + PREFERREDBEAMSIZEX: '', }, validation: { @@ -148,6 +157,51 @@ define(['backbone', 'collections/components', pattern: 'twopath', }, + 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 9f6926cff..8253870b2 100644 --- a/client/src/js/modules/shipment/views/container.js +++ b/client/src/js/modules/shipment/views/container.js @@ -56,10 +56,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', }, @@ -72,6 +74,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 3469d561f..c53a0b3d9 100644 --- a/client/src/js/modules/shipment/views/containeradd.js +++ b/client/src/js/modules/shipment/views/containeradd.js @@ -116,6 +116,7 @@ define(['backbone', autoprocessing_pipeline: 'select[name=PIPELINE]', auto: 'input[name=AUTOMATED]', extrastate: '.extra-state', + dp: '.dp-state', }, @@ -136,6 +137,7 @@ define(['backbone', 'change @ui.type': 'setType', 'click @ui.ext': 'toggleExtra', + 'click a.dpinfo': 'toggleDP', 'keypress .ui-combobox input': 'excelNavigate', 'keypress input.sname': 'excelNavigate', @@ -276,6 +278,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 0bcee73f8..07336be93 100644 --- a/client/src/js/modules/shipment/views/sampletable.js +++ b/client/src/js/modules/shipment/views/sampletable.js @@ -16,12 +16,13 @@ define(['marionette', 'utils/centringmethods', 'utils/experimentkinds', 'utils/radiationsensitivity', + 'utils/collectionmode', 'utils', 'jquery', ], function(Marionette, Protein, Proteins, ValidatedRow, DistinctProteins, ComponentsView, sampletable, sampletablerow, sampletablerowedit, - forms, SG, Anom, CM, EXP, RS, utils, $) { + forms, SG, Anom, CM, EXP, RS, COLM, utils, $) { // A Sample Row @@ -71,13 +72,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) @@ -142,6 +145,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() @@ -191,13 +195,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') @@ -218,6 +224,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) @@ -346,9 +353,12 @@ 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.options.childViewOptions.extra = this.extra this.options.childViewOptions.auto = this.auto + this.options.childViewOptions.dp = this.dp this.options.childViewOptions.type = this.getOption('type') }, @@ -370,6 +380,10 @@ define(['marionette', return this.extra.show }, + dpState: function() { + return this.dp.show + }, + toggleExtra: function() { this.extra.show = !this.extra.show @@ -377,6 +391,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 e9c3020c3..871149c3a 100644 --- a/client/src/js/templates/shipment/container.html +++ b/client/src/js/templates/shipment/container.html @@ -76,5 +76,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 db492aa83..db3f19e18 100644 --- a/client/src/js/templates/shipment/containeradd.html +++ b/client/src/js/templates/shipment/containeradd.html @@ -126,6 +126,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 642f93576..374c99796 100644 --- a/client/src/js/templates/shipment/sampletablerowedit.html +++ b/client/src/js/templates/shipment/sampletablerowedit.html @@ -31,6 +31,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +   Save Changes diff --git a/client/src/js/templates/shipment/sampletablerownew.html b/client/src/js/templates/shipment/sampletablerownew.html index 15029dccd..5b28a6bed 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 981736f6989d0b18552f7e74edfb5b45245591ab Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:19:15 +0200 Subject: [PATCH 05/37] add csv importer --- client/src/js/csv/imca.js | 92 +++ client/src/js/modules/shipment/controller.js | 34 +- 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, 702 insertions(+), 1 deletion(-) 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 c205fd781..a7b8578b8 100644 --- a/client/src/js/modules/shipment/controller.js +++ b/client/src/js/modules/shipment/controller.js @@ -7,6 +7,7 @@ define(['backbone', 'modules/shipment/views/shipments', 'modules/shipment/views/shipment', 'modules/shipment/views/shipmentadd', + 'modules/shipment/views/fromcsv', 'models/container', 'collections/containers', @@ -46,7 +47,7 @@ define(['backbone', ], function(Backbone, GetView, Dewar, Shipment, Shipments, - ShipmentsView, ShipmentView, ShipmentAddView, + ShipmentsView, ShipmentView, ShipmentAddView, ImportFromCSV, Container, Containers, ContainerView, ContainerPlateView, /*ContainerAddView,*/ ContainersView, QueueContainerView, ContainerRegistry, ContainersRegistry, ContainerRegistryView, RegisteredContainer, RegisteredDewar, DewarRegistry, DewarRegView, RegDewarView, RegDewarAddView, @@ -115,6 +116,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 833545802..92f7383fa 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 b9d260dce..2714c8eee 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 900f2977c54484d8e67ba3ca5bb289b75b3e8ab2 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 Aug 2020 16:20:06 +0200 Subject: [PATCH 06/37] add barcode scan container assign page --- client/package.json | 1 + client/src/css/partials/_assign.scss | 7 + 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 + 7 files changed, 283 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 1f6b2e9fc..00a724761 100644 --- a/client/package.json +++ b/client/package.json @@ -65,6 +65,7 @@ "jquery.flot.tooltip": "^0.9.0", "markdown": "^0.5.0", "moment": "^2.24.0", + "papaparse": "^5.2.0", "plotly.js": "^1.52.2", "promise": "^8.0.3", "three": "^0.83.0", diff --git a/client/src/css/partials/_assign.scss b/client/src/css/partials/_assign.scss index d75075225..144d91556 100644 --- a/client/src/css/partials/_assign.scss +++ b/client/src/css/partials/_assign.scss @@ -13,6 +13,13 @@ background: $content-sub-header-background; float: left; + input { + width: calc(99% - 10px); + padding: 10px 5px; + border-radius: 5px; + border: none; + } + @media (max-width: $breakpoint-small) { @include cols(4,0.5%,0.2%); } diff --git a/client/src/css/partials/_content.scss b/client/src/css/partials/_content.scss index 457052a54..35794f013 100644 --- a/client/src/css/partials/_content.scss +++ b/client/src/css/partials/_content.scss @@ -1991,3 +1991,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 55fad21db6f6368695f2b1dc1a30cde831a2f904 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sun, 23 Aug 2020 12:42:34 +0200 Subject: [PATCH 07/37] add new config.json option --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b949f6fea..41e9165cb 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ 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 | | 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 | ### Build front end See package.json for the full list of commands that can be run. From fb625f0c6fa836bea04b92bc7805f3b609d6477b Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sun, 23 Aug 2020 13:16:48 +0200 Subject: [PATCH 08/37] 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 b0eeaba6e..2a000667d 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1650,13 +1650,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 1419e69d3bf0241bab0441fa60cd21036347879e Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 29 Aug 2020 17:15:08 +0200 Subject: [PATCH 09/37] 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 c333c73dd87e86913122c5201f1ccd6126a0791f Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 31 Aug 2020 07:50:07 +0200 Subject: [PATCH 10/37] 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 b4bf60283420258a0c71341a699cdc9d71448e2c Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 31 Aug 2020 07:50:16 +0200 Subject: [PATCH 11/37] 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 10507dcd79ce2cb825af5cc00d4917b2c861b7f6 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Tue, 1 Sep 2020 21:48:13 +0200 Subject: [PATCH 12/37] 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 2a000667d..6b6abab66 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1370,7 +1370,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 fcccc64f70ab04523bb49cb7e5c0e7c94084af7a Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 14 Sep 2020 18:43:34 +0200 Subject: [PATCH 13/37] 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 a12e859dd..2231fdd94 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -924,7 +924,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 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 @@ -1017,8 +1017,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'])); } @@ -1119,8 +1119,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)) { @@ -1384,7 +1384,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 d1543880eb1e901f19e2f8422e50ab8896924795 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Wed, 16 Sep 2020 21:06:14 +0200 Subject: [PATCH 14/37] 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 1c6f369c437ba1213e03a0fa3a226055e2f59383 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Thu, 17 Sep 2020 08:07:15 +0200 Subject: [PATCH 15/37] 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 e64e24a8b4963133b7a585bf6787f3309aa0e378 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Tue, 22 Sep 2020 22:16:05 +0200 Subject: [PATCH 16/37] 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 432d7aa3078a416838b826c6c9c55e22e8f58dba Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Wed, 23 Dec 2020 22:03:20 +0100 Subject: [PATCH 17/37] 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 a8b99eed9a00f143fa67ae91ec0999dfdf76b188 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 10 Apr 2021 09:05:39 +0200 Subject: [PATCH 18/37] wip: mailin enhancements --- api/src/Page/Assign.php | 17 +- api/src/Page/Sample.php | 16 +- 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, 241 insertions(+), 12 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 644f7a933..540a448f4 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -62,6 +62,7 @@ class Sample extends Page 'NAME' => '[\w\s-()]+', 'COMMENTS' => '.*', + 'STAFFCOMMENTS' => '.*', 'SPACEGROUP' => '(\w+)|^$', // Any word character or empty string 'CELL_A' => '\d+(.\d+)?', 'CELL_B' => '\d+(.\d+)?', @@ -942,11 +943,11 @@ 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 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 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 @@ -967,7 +968,6 @@ 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 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 @@ -979,6 +979,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 @@ -1033,8 +1035,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", @@ -1126,7 +1128,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) : ''; } @@ -1408,7 +1410,7 @@ function _update_sample() { if (!sizeof($samp)) $this->_error('No such sample'); else $samp = $samp[0]; - $sfields = array('CODE', 'NAME', 'COMMENTS', 'VOLUME', 'PACKINGFRACTION', 'DIMENSION1', 'DIMENSION2', 'DIMENSION3', 'SHAPE', 'POSITION', 'CONTAINERID', 'LOOPTYPE'); + $sfields = array('CODE', 'NAME', 'COMMENTS', 'STAFFCOMMENTS', 'VOLUME', 'PACKINGFRACTION', 'DIMENSION1', 'DIMENSION2', 'DIMENSION3', 'SHAPE', 'POSITION', 'CONTAINERID', 'LOOPTYPE'); 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 8f38b6a76..013b34a3d 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1412,7 +1412,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 44088a9d5..baae2fa22 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 f98928b381154a25a564a89fba96a787365f4567 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 26 Apr 2021 14:57:54 +0200 Subject: [PATCH 19/37] 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 e606b3beb..0077abddf 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 aa9b3a747..b707b12b9 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 ac774f3d0..6e121c981 100644 --- a/client/src/js/templates/shipment/container.html +++ b/client/src/js/templates/shipment/container.html @@ -83,6 +83,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 db3f19e18..65b026a05 100644 --- a/client/src/js/templates/shipment/containeradd.html +++ b/client/src/js/templates/shipment/containeradd.html @@ -127,7 +127,9 @@

Add New Container

Clear Puck + <% if (ENABLE_EXP_PLAN) { %> Plan Fields + <% } %> Extra Fields
From 3feb552f7c97a61f3b82632efc3e890d97f523ca Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 26 Apr 2021 14:58:48 +0200 Subject: [PATCH 20/37] 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 2714c8eee..a00a0c251 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 b6cb466abe6de7745ac1ce9b0262ba98d13e8ae3 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:10:17 +0200 Subject: [PATCH 21/37] add mark shipment returned button and api, add mark queue completed api, add prop code filter to containers --- api/src/Page/Shipment.php | 58 ++++++++++++++++++- .../src/js/modules/shipment/views/shipment.js | 25 +++++++- .../src/js/templates/shipment/shipment.html | 4 ++ 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index c7ec4803e..93122c1ab 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -123,6 +123,8 @@ class Shipment extends Page 'manifest' => '\d', 'currentuser' => '\d', + 'PROPOSALCODE' => '\w+', + 'CONTAINERQUEUEID' => '\d+' ); @@ -130,6 +132,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'), @@ -165,6 +168,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'), @@ -1208,6 +1212,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 @@ -1303,6 +1335,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'"; @@ -1435,7 +1471,9 @@ function _get_all_containers() { 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 + c.ownerid, CONCAT(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 @@ -1458,6 +1496,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 @@ -1536,6 +1577,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 a00a0c251..d6c062fb0 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 813ce91a892039578bed57ecf27e6529fdf14d7c Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:11:43 +0200 Subject: [PATCH 22/37] document disabling exp_plan fields --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c35f9c017..3326b8ed7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ This file should be copied to create a client/src/js/config.json file and edited | site_link | URL to site home 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 | ### Build front end See package.json for the full list of commands that can be run. From 886aa2bd6166b44db08c6a6693be783307ebc176 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:24:14 +0200 Subject: [PATCH 23/37] 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 baae2fa22..6224a0f33 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({ @@ -536,6 +548,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 092c3d7634de164ae0df5f311589f083284ee951 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:24:41 +0200 Subject: [PATCH 24/37] 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 6e121c981..c79114ed8 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 04b1f4f98a9a8ce2adbe4eddf7bbf278baf7fc51 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Fri, 30 Apr 2021 17:27:09 +0200 Subject: [PATCH 25/37] 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 540a448f4..639685608 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -947,7 +947,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 c8a4f8e972a91d36853dbb87250b54f874fd94e9 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 11:54:47 +0200 Subject: [PATCH 26/37] 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 6224a0f33..98992b7b6 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 b244d6d00ad44c27cb49d7769faf81be7e2b0cdb Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 11:54:59 +0200 Subject: [PATCH 27/37] 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 04a3b3d4f..3c5cdc912 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: 'database', permission: 'auto_dash' }, 'runs/overview': { title: 'Run Overview', icon: 'bar-chart', permission: 'all_breakdown' }, 'stats/overview/beamlines': { title: 'Reporting', icon: 'line-chart', permission: 'all_prop_stats' }, 'admin/imaging': { title: 'Imaging', icon: 'image', permission: 'imaging_dash' }, From 19e51ba3c17df2a13efaf74ccb3eaf0989246fb0 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:24:29 +0200 Subject: [PATCH 28/37] 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 3326b8ed7..f54a1dc4a 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 | 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' | ### Build front end See package.json for the full list of commands that can be run. diff --git a/client/src/js/modules/samples/views/view.js b/client/src/js/modules/samples/views/view.js index bd0031f56..796d32d3e 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 0077abddf..78d350a78 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 b707b12b9..b19ede558 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 } }, @@ -527,6 +528,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 7d18df4c3..4e5d62030 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 c79114ed8..7f3f160d1 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 65b026a05..e60d540a9 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 6d4c5a1125733f66c66e3e1866c09344daef0576 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:24:36 +0200 Subject: [PATCH 29/37] 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 0bde4e58ed69219bcb8f0089cab7e7a5a179b226 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:24:43 +0200 Subject: [PATCH 30/37] 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 dd5496d5abefb340e8e78837c73f823425321be0 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:50:26 +0200 Subject: [PATCH 31/37] 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 d6c062fb0..d9512fc2d 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 045682c4b1a5074b56986a025c7cf508564af877 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 22 May 2021 12:50:32 +0200 Subject: [PATCH 32/37] 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 707f0d5a4f214c5b2f032d702fb74a737ded6a52 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 24 May 2021 10:03:28 +0200 Subject: [PATCH 33/37] 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 93122c1ab..3d04e1509 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1224,6 +1224,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 98992b7b6..b53fe3047 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 96c1c098b354bcb42a28ba114f12c482acbccfe1 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 29 May 2021 17:40:22 +0200 Subject: [PATCH 34/37] 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 f54a1dc4a..0997ef017 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 | 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 | ### Build front end See package.json for the full list of commands that can be run. 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 d9512fc2d..dfa466fc7 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 6d8818d5af8dea20274be81884061cc6cd5b31a7 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 29 May 2021 17:46:35 +0200 Subject: [PATCH 35/37] 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 | 33 ++++++++++ .../modules/shipment/views/containerreview.js | 66 ++++++++++++++++--- 4 files changed, 178 insertions(+), 22 deletions(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index 639685608..e259e2b7d 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -141,6 +141,7 @@ class Sample extends Page 'BLSAMPLEGROUPSAMPLEID' => '\d+-\d+', 'SHIPPINGID' => '\d+', + 'QUEUESTATUS' => '\w+', ); @@ -153,6 +154,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'), @@ -947,7 +950,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 @@ -965,7 +969,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 screening sc ON dc.datacollectionid = sc.datacollectionid @@ -1068,6 +1077,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 3d04e1509..9c2062a92 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1556,7 +1556,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(); @@ -1567,23 +1575,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", 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", 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"); @@ -1687,6 +1717,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 35794f013..1de1142a1 100644 --- a/client/src/css/partials/_content.scss +++ b/client/src/css/partials/_content.scss @@ -1176,6 +1176,39 @@ ul.status { 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; + } } } 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 cc1e13dcebcb20cc902b0c23b361c84127c576ea Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Mon, 31 May 2021 19:37:20 +0200 Subject: [PATCH 36/37] 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 8886b9f2a542e7bbc69ca957960642ee6d1821a4 Mon Sep 17 00:00:00 2001 From: Stu Fisher Date: Sat, 5 Jun 2021 17:25:37 +0200 Subject: [PATCH 37/37] 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 = []