From 2bb5a62eef4522c980938bc2e17004c6f1d2d1dc Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Tue, 14 Oct 2025 15:28:09 +0100 Subject: [PATCH 1/6] LIMS-1818: Allow download of CSV of dispensing positions --- api/src/Page/Download.php | 40 +++++++++++++++++++ .../modules/shipment/views/containerplate.js | 28 +++++++++++++ .../shipment/containerplateimage.html | 3 +- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/api/src/Page/Download.php b/api/src/Page/Download.php index 9f9794d20..861624a2d 100644 --- a/api/src/Page/Download.php +++ b/api/src/Page/Download.php @@ -35,6 +35,7 @@ class Download extends Page 'ppl' => '\w+', 'aid' => '\w+', + 'cid' => '\d+', 'filetype' => '\w+', 'blsampleid' => '\d+', @@ -49,6 +50,7 @@ class Download extends Page public static $dispatch = array( array('/plots', 'get', '_auto_processing_plots'), array('/csv/visit/:visit', 'get', '_csv_report'), + array('/csv/container/:cid', 'get', '_dispensing_csv'), array('/sign', 'post', '_sign_url'), array('/data/visit/:visit', 'get', '_download_visit'), array('/attachments', 'get', '_get_attachments'), @@ -317,6 +319,44 @@ function _csv_report() } } + # ------------------------------------------------------------------------ + # CSV Report of dispensing positions for a plate + function _dispensing_csv() + { + if (!$this->has_arg('cid')) + $this->_error('No container id specified'); + $rows = $this->db->pq("SELECT c.code, s.location, ct.capacity, ct.wellperrow, + bsp.posx, bsp.posy, si.imagefullpath, si.micronsperpixelx, si.micronsperpixely + FROM blsample s + INNER JOIN container c ON c.containerid = s.containerid + INNER JOIN blsampleimage si ON si.blsampleid = s.blsampleid + LEFT OUTER JOIN containertype ct ON (c.containertypeid IS NOT NULL AND c.containertypeid = ct.containertypeid) OR (c.containertypeid IS NULL AND c.containertype = ct.name) + LEFT OUTER JOIN + (SELECT * FROM blsampleposition bsp1 WHERE bsp1.blsamplepositionid = + (SELECT MAX(blsamplepositionid) FROM blsampleposition bsp2 WHERE bsp2.blsampleid = bsp1.blsampleid AND bsp2.positiontype='dispensing') + ) bsp ON bsp.blsampleid = s.blsampleid + WHERE c.containerid=:1 + ORDER BY s.location+0", + array($this->arg('cid'))); + $plate = $rows[0]; + $rowNames = range("A", "H"); + $dropsPerWell = $plate["CAPACITY"] / (count($rowNames) * $plate["WELLPERROW"]); + $this->app->response->headers->set("Content-type", "application/vnd.ms-excel"); + $this->_set_disposition_attachment($this->app->response, $plate["CODE"] . ".csv"); + list($width, $height, $type, $attr) = getimagesize($plate['IMAGEFULLPATH']); + foreach ($rows as $r) { + if (!$r["POSX"]) continue; + $wellNumber = intval(($r["LOCATION"] - 1) / $dropsPerWell); # 0 indexed + $rowNumber = intval($wellNumber / $plate["WELLPERROW"]); # 0 indexed + $row = $rowNames[$rowNumber]; + $column = $wellNumber - ($rowNumber * $plate["WELLPERROW"]) + 1; + $drop = intval($r["LOCATION"] - ($dropsPerWell * $wellNumber)); + $xval = ($r["POSX"] - $width/2) * $r["MICRONSPERPIXELX"]; + $yval = ($r["POSY"] - $height/2) * $r["MICRONSPERPIXELY"]; + print $row . $column . "d" . $drop . "," . $xval . "," . $yval . "\n"; + } + } + # ------------------------------------------------------------------------ # Get dc attachmmnts diff --git a/client/src/js/modules/shipment/views/containerplate.js b/client/src/js/modules/shipment/views/containerplate.js index 87980a4c4..359c3805c 100644 --- a/client/src/js/modules/shipment/views/containerplate.js +++ b/client/src/js/modules/shipment/views/containerplate.js @@ -281,6 +281,7 @@ define(['marionette', 'click @ui.adr': 'setAddSubsampleRegion', 'click @ui.addis': 'setAddDispensing', 'click @ui.deldis': 'deleteDispensing', + 'click a.csv_dispensing': 'downloadDispensingCSV', 'click a.add_inspection': 'showAddInspection', 'click a.view_sched': 'showViewSchedule', 'click @ui.play': 'playInspection', @@ -309,6 +310,33 @@ define(['marionette', 'change:QUEUED': 'updatedQueued', }, + downloadDispensingCSV: function(e) { + e.preventDefault() + this.signHandler(app.apiurl+'/download/csv/container/'+this.model.get('CONTAINERID')); + }, + + signHandler(url) { + this.sign({ + url: url, + callback: function(resp) { + window.location = url+'?token='+resp.token + } + }) + }, + + sign(options) { + Backbone.ajax({ + url: app.apiurl+'/download/sign', + method: 'POST', + data: { + validity: options.url.replace(app.apiurl, ''), + }, + success: function(resp) { + if (options && options.callback) options.callback(resp) + } + }) + }, + setSampleStatusShown: function() { const showCurrentScore = this.ui.sampleStatusCurrent.is(':checked') diff --git a/client/src/js/templates/shipment/containerplateimage.html b/client/src/js/templates/shipment/containerplateimage.html index b784387c7..02af9f9f1 100644 --- a/client/src/js/templates/shipment/containerplateimage.html +++ b/client/src/js/templates/shipment/containerplateimage.html @@ -184,7 +184,8 @@

Plate Details

-
+

+
Download Dispensing CSV
<% } %> From 9192062751302ee5d3d01354a88f16d1ff1af0d6 Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Tue, 14 Oct 2025 16:12:55 +0100 Subject: [PATCH 2/6] LIMS-1818: Always show CSV button --- client/src/js/templates/shipment/containerplateimage.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/src/js/templates/shipment/containerplateimage.html b/client/src/js/templates/shipment/containerplateimage.html index 02af9f9f1..7b14e20fb 100644 --- a/client/src/js/templates/shipment/containerplateimage.html +++ b/client/src/js/templates/shipment/containerplateimage.html @@ -173,11 +173,11 @@

Plate Details

<% } %> - <% if (IMAGERID) { %>
  • Actions - +
    + <% if (IMAGERID) { %>
    @@ -185,10 +185,11 @@

    Plate Details


    + <% } %>
    Download Dispensing CSV
  • - <% } %> +
  • Location History From 1801625fc66418deb445dd944334e5fd069d258d Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Fri, 31 Oct 2025 14:14:49 +0000 Subject: [PATCH 3/6] LIMS-1818: Rename file, update drop names, add blank rows --- api/src/Page/Download.php | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/api/src/Page/Download.php b/api/src/Page/Download.php index 861624a2d..fca4ac05e 100644 --- a/api/src/Page/Download.php +++ b/api/src/Page/Download.php @@ -325,7 +325,7 @@ function _dispensing_csv() { if (!$this->has_arg('cid')) $this->_error('No container id specified'); - $rows = $this->db->pq("SELECT c.code, s.location, ct.capacity, ct.wellperrow, + $rows = $this->db->pq("SELECT c.code, s.location, ct.name, ct.capacity, ct.wellperrow, bsp.posx, bsp.posy, si.imagefullpath, si.micronsperpixelx, si.micronsperpixely FROM blsample s INNER JOIN container c ON c.containerid = s.containerid @@ -340,20 +340,29 @@ function _dispensing_csv() array($this->arg('cid'))); $plate = $rows[0]; $rowNames = range("A", "H"); + $dropNames = range("a", "z"); + // SWISSCI 3 Drop have drops a/c/d + if ($plate["NAME"] == "SWISSCI 3 Drop") { + $dropNames = array_merge(array("a"), range("c","z")); + } $dropsPerWell = $plate["CAPACITY"] / (count($rowNames) * $plate["WELLPERROW"]); $this->app->response->headers->set("Content-type", "application/vnd.ms-excel"); - $this->_set_disposition_attachment($this->app->response, $plate["CODE"] . ".csv"); + $this->_set_disposition_attachment($this->app->response, $plate["CODE"] . "_targets.csv"); list($width, $height, $type, $attr) = getimagesize($plate['IMAGEFULLPATH']); foreach ($rows as $r) { - if (!$r["POSX"]) continue; $wellNumber = intval(($r["LOCATION"] - 1) / $dropsPerWell); # 0 indexed - $rowNumber = intval($wellNumber / $plate["WELLPERROW"]); # 0 indexed - $row = $rowNames[$rowNumber]; - $column = $wellNumber - ($rowNumber * $plate["WELLPERROW"]) + 1; - $drop = intval($r["LOCATION"] - ($dropsPerWell * $wellNumber)); - $xval = ($r["POSX"] - $width/2) * $r["MICRONSPERPIXELX"]; - $yval = ($r["POSY"] - $height/2) * $r["MICRONSPERPIXELY"]; - print $row . $column . "d" . $drop . "," . $xval . "," . $yval . "\n"; + $rowNumber = intval($wellNumber / $plate["WELLPERROW"]); # 0 indexed + $row = $rowNames[$rowNumber]; + $column = str_pad($wellNumber - ($rowNumber * $plate["WELLPERROW"]) + 1, 2, 0, STR_PAD_LEFT); # pad with a zero if needed + $dropNumber = intval($r["LOCATION"] - ($dropsPerWell * $wellNumber)); # 1 indexed + $drop = $dropNames[$dropNumber-1]; + if (!$r["POSX"]) { + print $row . $column . $drop . "\n"; + } else { + $xval = round(($r["POSX"] - $width/2) * $r["MICRONSPERPIXELX"]); # integers + $yval = round(($height/2 - $r["POSY"]) * $r["MICRONSPERPIXELY"]); # integers + print $row . $column . $drop . "," . $xval . "," . $yval . "\n"; + } } } From 17ff5b2c2a62a486f27239a9f33c01b392c3be3f Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Fri, 31 Oct 2025 15:25:18 +0000 Subject: [PATCH 4/6] LIMS-1818: Print commas on empty lines --- api/src/Page/Download.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/api/src/Page/Download.php b/api/src/Page/Download.php index fca4ac05e..560c56550 100644 --- a/api/src/Page/Download.php +++ b/api/src/Page/Download.php @@ -356,13 +356,9 @@ function _dispensing_csv() $column = str_pad($wellNumber - ($rowNumber * $plate["WELLPERROW"]) + 1, 2, 0, STR_PAD_LEFT); # pad with a zero if needed $dropNumber = intval($r["LOCATION"] - ($dropsPerWell * $wellNumber)); # 1 indexed $drop = $dropNames[$dropNumber-1]; - if (!$r["POSX"]) { - print $row . $column . $drop . "\n"; - } else { - $xval = round(($r["POSX"] - $width/2) * $r["MICRONSPERPIXELX"]); # integers - $yval = round(($height/2 - $r["POSY"]) * $r["MICRONSPERPIXELY"]); # integers - print $row . $column . $drop . "," . $xval . "," . $yval . "\n"; - } + $xval = $r["POSX"] ? round(($r["POSX"] - $width/2) * $r["MICRONSPERPIXELX"]) : ""; # integers + $yval = $r["POSY"] ? round(($height/2 - $r["POSY"]) * $r["MICRONSPERPIXELY"]) : ""; # integers + print $row . $column . $drop . "," . $xval . "," . $yval . "\n"; } } From 2f24f206c6ca0695774972969c004637ec31027e Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:07:53 +0000 Subject: [PATCH 5/6] Fix mime type Co-authored-by: Guilherme Francisco --- api/src/Page/Download.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/Page/Download.php b/api/src/Page/Download.php index 560c56550..25cdca731 100644 --- a/api/src/Page/Download.php +++ b/api/src/Page/Download.php @@ -346,7 +346,7 @@ function _dispensing_csv() $dropNames = array_merge(array("a"), range("c","z")); } $dropsPerWell = $plate["CAPACITY"] / (count($rowNames) * $plate["WELLPERROW"]); - $this->app->response->headers->set("Content-type", "application/vnd.ms-excel"); + $this->app->response->headers->set("Content-type", "text/csv"); $this->_set_disposition_attachment($this->app->response, $plate["CODE"] . "_targets.csv"); list($width, $height, $type, $attr) = getimagesize($plate['IMAGEFULLPATH']); foreach ($rows as $r) { From e372dade02c10ca76ccd251c8891732c4014fd73 Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Tue, 9 Dec 2025 10:55:50 +0000 Subject: [PATCH 6/6] LIMS-1818: Remove empty lines entirely --- api/src/Page/Download.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/src/Page/Download.php b/api/src/Page/Download.php index 25cdca731..f15095db1 100644 --- a/api/src/Page/Download.php +++ b/api/src/Page/Download.php @@ -350,14 +350,17 @@ function _dispensing_csv() $this->_set_disposition_attachment($this->app->response, $plate["CODE"] . "_targets.csv"); list($width, $height, $type, $attr) = getimagesize($plate['IMAGEFULLPATH']); foreach ($rows as $r) { + if (!isset($r["POSX"]) || !isset($r["POSY"])) { + continue; # skip empty rows + } $wellNumber = intval(($r["LOCATION"] - 1) / $dropsPerWell); # 0 indexed $rowNumber = intval($wellNumber / $plate["WELLPERROW"]); # 0 indexed $row = $rowNames[$rowNumber]; $column = str_pad($wellNumber - ($rowNumber * $plate["WELLPERROW"]) + 1, 2, 0, STR_PAD_LEFT); # pad with a zero if needed $dropNumber = intval($r["LOCATION"] - ($dropsPerWell * $wellNumber)); # 1 indexed $drop = $dropNames[$dropNumber-1]; - $xval = $r["POSX"] ? round(($r["POSX"] - $width/2) * $r["MICRONSPERPIXELX"]) : ""; # integers - $yval = $r["POSY"] ? round(($height/2 - $r["POSY"]) * $r["MICRONSPERPIXELY"]) : ""; # integers + $xval = round(($r["POSX"] - $width/2) * $r["MICRONSPERPIXELX"]); # integers + $yval = round(($height/2 - $r["POSY"]) * $r["MICRONSPERPIXELY"]); # integers print $row . $column . $drop . "," . $xval . "," . $yval . "\n"; } }