Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Barcode Endpoint + Introduction of Specimen Sample Numbers #255

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions jsx/biobankIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ class BiobankIndex extends React.Component {
*/
createSpecimens(list, current, print) {
const {options, data} = this.state;
const labelParams = [];
const projectIds = current.projectIds;
const centerId = current.centerId;
const availableId = Object.keys(options.container.stati).find(
Expand Down Expand Up @@ -321,10 +320,6 @@ class BiobankIndex extends React.Component {

// if specimen type id is not set yet, this will throw an error
if (specimen.typeId) {
labelParams.push({
barcode: container.barcode,
type: options.specimen.types[specimen.typeId].label,
});
}

specimen.container = container;
Expand Down Expand Up @@ -353,7 +348,7 @@ class BiobankIndex extends React.Component {
return Promise.reject(errors);
}

const printBarcodes = () => {
const printBarcodes = (entities) => {
return new Promise((resolve) => {
if (print) {
Swal.fire({
Expand All @@ -363,21 +358,40 @@ class BiobankIndex extends React.Component {
cancelButtonText: 'No',
showCancelButton: true,
})
.then((result) => result.value && this.printLabel(labelParams))
.then((result) => {
if (result.value) {
const labelParams = [];
Object.values(entities.specimens).forEach((specimen) => {
labelParams.push({
barcode: specimen.barcode,
type: options.specimen.types[specimen.typeId].label,
pscid: specimen.candidatePSCID,
sampleNumber: specimen.sampleNumber,
});
});
return this.printLabel(labelParams);
}
})
.then(() => resolve());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TYPOOOOOOO semicolon

.catch((error) => {
console.error('Printing error:', error);
resolve();
});
} else {
resolve();
}
});
};

return printBarcodes()
.then(() => post(list, this.props.specimenAPI, 'POST'))
.then((entities) => {
this.setData('containers', entities.containers);
this.setData('specimens', entities.specimens);
})
.then(() => Promise.resolve());
return post(list, this.props.specimenAPI, 'POST')
.then((entities) => {
return printBarcodes(entities)
.then(() => {
this.setData('containers', entities.containers);
this.setData('specimens', entities.specimens);
});
})
.then(() => Promise.resolve());
}

/**
Expand Down
4 changes: 3 additions & 1 deletion jsx/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ class Header extends Component {
const barcodePathDisplay = this.props.getBarcodePathDisplay(parentBarcodes);
const printBarcode = () => {
const labelParams = [{
barcode: container.barcode,
barcode: specimen.barcode,
type: options.specimen.types[specimen.typeId].label,
pscid: specimen.candidatePSCID,
sampleNumber: specimen.sampleNumber,
}];
this.props.printLabel(labelParams)
.then(() => (Swal.fire('Print Barcode Number: ' + container.barcode)));
Expand Down
41 changes: 25 additions & 16 deletions jsx/specimenForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,25 +151,35 @@ class SpecimenForm extends React.Component {
return increment;
};

async fetchBarcodes(count) {
try {
const response = await fetch(`${loris.BaseURL}/biobank/barcodes?count=${count}`);
const data = await response.json();
return data.barcodes;
} catch (error) {
console.error('Error fetching barcodes:', error);
return [];
}
}

/**
* Generate barcodes and store in the component state.
*/
generateBarcodes() {
const {options} = this.props;
let {list, current} = this.state;
const pscid = options.candidates[current.candidateId].pscid;
[list] = Object.keys(list)
.reduce(([result, increment], key, i) => {
const specimen = this.state.list[key];
if (!specimen.container.barcode) {
const barcode = padBarcode(pscid, increment);
specimen.container.barcode = barcode;
increment = this.incrementBarcode(pscid, increment);
}
async generateBarcodes() {
const { options } = this.props;
let { list, current } = this.state;
const count = Object.keys(list).length;

const barcodes = await this.fetchBarcodes(count);

list = Object.keys(list).reduce((result, key, index) => {
const specimen = list[key];
specimen.container.barcode = barcodes[index];
result[key] = specimen;
return [result, increment];
}, [{}, this.incrementBarcode(pscid)]);
this.setState({list});
return result;
}, {});

this.setState({ list });
};

/**
Expand Down Expand Up @@ -377,7 +387,6 @@ class SpecimenForm extends React.Component {
label='Generate Barcodes'
type='button'
onUserInput={this.generateBarcodes}
disabled={current.candidateId ? false : true}
/>
<CheckboxElement
name='printBarcodes'
Expand Down
121 changes: 121 additions & 0 deletions php/barcodes.class.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace LORIS\biobank;

use \LORIS\Http\Endpoint;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

class Barcodes extends Endpoint
{
protected $db;
protected $user;

/**
* Returns true if user has access to this Endpoint.
*
* @param \User $user The user whose access is being checked
*
* @return bool
*/
function _hasAccess(\User $user) : bool
{
return true;
}

/**
* Return which methods are supported by this endpoint.
*
* @return array supported HTTP methods
*/
protected function allowedMethods() : array
{
return ['GET', 'OPTIONS'];
}

/**
* This function can be overridden in a module's page to load the necessary
* resources to check the permissions of a user.
*
* @param User $user The user to load the resources for
* @param ServerRequestInterface $request The PSR15 Request being handled
*
* @return void
*/
public function loadResources(
\User $user, ServerRequestInterface $request
) : void {
}

/**
* Handles Endpoint requests
*
* @param ServerRequestInterface $request The incoming PSR7 request
*
* @return ResponseInterface The outgoing PSR7 response
*/
public function handle(
ServerRequestInterface $request
) : ResponseInterface {
$this->db = \NDB_Factory::singleton()->database();
$this->user = $request->getAttribute('user');

try {
switch ($request->getMethod()) {
case 'GET':
return $this->_handleGET($request);
case 'OPTIONS':
return (new \LORIS\Http\Response())
->withHeader('Allow', $this->allowedMethods());
}
} catch (\Exception $e) {
$this->db->rollBack();
return new \LORIS\Http\Response\JSON\InternalServerError(
$e->getMessage()
);
}
}

// NOTE: the algorithm can stay here but should
/**
* Handle the logic of an incoming HTTP GET request
*
* @param ServerRequestInterface $request The incoming PSR7 request
* @return ResponseInterface
*/
private function _handleGET(
ServerRequestInterface $request
) : ResponseInterface {
$currentYear = date('y');

$queryParams = $request->getQueryParams();
$count = isset($queryParams['count'])
? intval($queryParams['count'])
: 1;

// Query to get the largest barcode for the current year
$query = "SELECT MAX(CAST(SUBSTRING(Barcode, 3, 6) AS UNSIGNED)) AS max_number
FROM biobank_container
WHERE SUBSTRING(Barcode, 1, 2) = :year";
$params = [':year' => $currentYear];
$maxNumber = $this->db->pselectOne($query, $params);

$newNumber = $maxNumber
? $maxNumber + 1
: 1;

$barcodes = [];
for ($i = 0; $i < $count; $i++) {
$newNumberFormatted = str_pad(
$newNumber + $i,
6,
'0',
STR_PAD_LEFT
);
$newBarcode = $currentYear . $newNumberFormatted;
$barcodes[] = $newBarcode;
}

return new \LORIS\Http\Response\JSON\OK(['barcodes' => $barcodes]);
}
}
88 changes: 68 additions & 20 deletions php/labelendpoint.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* @link http://www.github.com/aces/Loris/
*/
namespace LORIS\biobank;
use \LORIS\Http\Response\JSON\InternalServerError;
use \Psr\Http\Message\ServerRequestInterface;
use \Psr\Http\Server\RequestHandlerInterface;
use \Psr\Http\Message\ResponseInterface;
Expand Down Expand Up @@ -50,8 +51,6 @@ class LabelEndpoint implements RequestHandlerInterface
protected function allowedMethods() : array
{
return [
'GET',
'PUT',
'POST',
'OPTIONS',
];
Expand Down Expand Up @@ -98,26 +97,75 @@ class LabelEndpoint implements RequestHandlerInterface
*/
public function handle(ServerRequestInterface $request) : ResponseInterface
{
// TODO: Place a try catch here in case printing doesn't work for some
// reason.
switch ($request->getMethod()) {
case 'POST':
$params = json_decode($request->getBody()->getContents(), true);;
foreach ($params as $param) {
$barcode = $param['barcode'];
$type = $param['type'];
$zpl = "^XA,^FO330,80^BY1,^BCN,100,Y,Y,N,^CFA,"
. "20^A0N,30,29^BCN,100,Y,Y,N^FD$barcode^FS,"
. "^FO323,190,^CFA,20^A0N,30,29^FD$type^XZ";
$config = \NDB_Config::singleton();
$path = $config->getSetting('labelPrintingPath');
$path = $path.'barcode.zpl';
$fp = fopen($path, 'w');
fwrite($fp, $zpl);
try {
switch ($request->getMethod()) {
case 'POST':
return $this->_handlePOST($request);
}
} catch (\Exception $e) {
// Log the error
error_log($e->getMessage());
return new InternalServerError('Printing failed');
}
}

/**
* Handle the logic of an incoming HTTP GET request
*
* @param ServerRequestInterface $request The incoming PSR7 request
* @return ResponseInterface
*/
private function _handlePOST(
ServerRequestInterface $request
) : ResponseInterface {
$params = json_decode($request->getBody()->getContents(), true);;
foreach ($params as $param) {
$barcode = $param['barcode'];
$type = $param['type'];
$pscid = $param['pscid'];
$sampleNum = $param['sampleNumber'];

// Construct ZPL string
$zpl = "^XA"
. "^FO330,80"
. "^BY1"
. "^BCN,100,Y,Y,N"
. "^CFA,20"
. "^A0N,30,29"
. "^BCN,100,Y,Y,N^FD$barcode^FS"
. "^FO323,190"
. "^CFA,20"
. "^A0N,30,29^FD$type^FS"
. "^FO323,250"
. "^CFA,20"
. "^A0N,30,29^FD$pscid $sampleNum^FS"
. "^XZ";

$config = \NDB_Config::singleton();
$path = $config->getSetting('labelPrintingPath') . 'barcode.zpl';

// Ensure directory exists
$directory = dirname($path);
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}

// Write ZPL to file
if (false === ($fp = fopen($path, 'w'))) {
throw new \RuntimeException("Unable to open file: $path");
}
if (false === fwrite($fp, $zpl)) {
fclose($fp);
shell_exec('lp -d ZDesigner -o raw '.escapeshellarg($path));
throw new \RuntimeException("Unable to write to file: $path");
}
fclose($fp);

// Send the file to the printer
$output = shell_exec('lp -d ZDesigner -o raw ' . escapeshellarg($path));
if ($output === null) {
throw new \RuntimeException("Failed to send print job to printer");
}
return new \LORIS\Http\Response\JSON\OK();
}
return new \LORIS\Http\Response\JSON\OK();
}
}
1 change: 1 addition & 0 deletions php/module.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class Module extends \Module
|| $path == 'poolendpoint'
|| $path == 'containerendpoint'
|| $path == 'specimenendpoint'
|| $path == 'barcodes'
) {
// There is a subpage
$pagename = explode("/", $path)[0];
Expand Down
Loading