Skip to content

Commit

Permalink
adding custom barcode generation
Browse files Browse the repository at this point in the history
  • Loading branch information
ridz1208 committed Nov 29, 2024
1 parent cfd881c commit 9134deb
Show file tree
Hide file tree
Showing 14 changed files with 1,058 additions and 92 deletions.
20 changes: 10 additions & 10 deletions SQL/New_patches/2024-11-21_biobank_addmodule.sql
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
INSERT INTO modules (Name, Active) VALUES ('biobank', 'Y');

INSERT INTO `permissions` VALUES
(68,'biobank_specimen_view','View Specimen Data',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2'),
(69,'biobank_specimen_create','Create Specimens',(SELECT ID FROM modules WHERE Name='biobank'), 'Create', '2'),
(70,'biobank_specimen_edit','Edit Specimen Data',(SELECT ID FROM modules WHERE Name='biobank'), 'Edit', '2'),
(71,'biobank_container_view','View Container Data',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2'),
(72,'biobank_container_create','Create Containers',(SELECT ID FROM modules WHERE Name='biobank'), 'Create', '2'),
(73,'biobank_container_edit','Edit Container Data',(SELECT ID FROM modules WHERE Name='biobank'), 'Edit', '2'),
(74,'biobank_pool_view','View Pool Data',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2'),
(75,'biobank_pool_create','Create Pools',(SELECT ID FROM modules WHERE Name='biobank'), 'Create', '2'),
(76,'biobank_fullsiteaccess','Full Site Access',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2'),
(77,'biobank_fullprojectaccess','Full Project Access',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2');
('biobank_specimen_view','View Specimen Data',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2'),
('biobank_specimen_create','Create Specimens',(SELECT ID FROM modules WHERE Name='biobank'), 'Create', '2'),
('biobank_specimen_edit','Edit Specimen Data',(SELECT ID FROM modules WHERE Name='biobank'), 'Edit', '2'),
('biobank_container_view','View Container Data',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2'),
('biobank_container_create','Create Containers',(SELECT ID FROM modules WHERE Name='biobank'), 'Create', '2'),
('biobank_container_edit','Edit Container Data',(SELECT ID FROM modules WHERE Name='biobank'), 'Edit', '2'),
('biobank_pool_view','View Pool Data',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2'),
('biobank_pool_create','Create Pools',(SELECT ID FROM modules WHERE Name='biobank'), 'Create', '2'),
('biobank_fullsiteaccess','Full Site Access',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2'),
('biobank_fullprojectaccess','Full Project Access',(SELECT ID FROM modules WHERE Name='biobank'), 'View', '2');
39 changes: 27 additions & 12 deletions modules/biobank/jsx/biobankIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,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 @@ -305,10 +304,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 @@ -337,7 +332,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 @@ -347,19 +342,39 @@ class BiobankIndex extends React.Component {
cancelButtonText: 'No',
showCancelButton: true,
})
.then((result) => result.value && this.printLabel(labelParams))
.then(() => resolve());
.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())
.catch((error) => {
console.error('Printing error:', error);
resolve();
});
} else {
resolve();
}
});
};

return printBarcodes()
.then(() => post(list, this.props.specimenAPI, 'POST'))

return post(list, this.props.specimenAPI, 'POST')
.then((entities) => {
this.setData('containers', entities.containers);
this.setData('specimens', entities.specimens);
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 modules/biobank/jsx/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,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
113 changes: 81 additions & 32 deletions modules/biobank/jsx/poolSpecimenForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ import Swal from 'sweetalert2';
const initialState = {
pool: {},
list: {},
filter: {
candidateId: null,
sessionid: null,
typeId: null,
centerId: null
},
poolId: null,
count: 0,
current: {},
errors: {},
containerId: null,
};
Expand All @@ -34,6 +40,7 @@ class PoolSpecimenForm extends React.Component {
super();
this.state = initialState;
this.setPool = this.setPool.bind(this);
this.setFilter = this.setFilter.bind(this);
this.validateListItem = this.validateListItem.bind(this);
this.setPoolList = this.setPoolList.bind(this);
}
Expand All @@ -50,6 +57,24 @@ class PoolSpecimenForm extends React.Component {
this.setState({pool});
}

/**
* Set the current filter on specimens to be selected.
*
* @param {string} name - the filter name
* @param {string} value - the filter values
*/
setFilter(name, value) {
console.log(name+': '+value);
const {filter} = clone(this.state);

if (name == 'candidateId') {
filter.sessionId = null;
}

filter[name] = value;
this.setState({filter});
}

/**
* Sets the current pool list
*
Expand All @@ -67,10 +92,10 @@ class PoolSpecimenForm extends React.Component {

// Set current global values
if (isEmpty(list)) {
current.candidateId = specimen.candidateId;
current.sessionId = specimen.sessionId;
current.typeId = specimen.typeId;
current.centerId = container.centerId;
filter.candidateId = specimen.candidateId;
filter.sessionId = specimen.sessionId;
filter.typeId = specimen.typeId;
filter.centerId = container.centerId;
}

// Set list values
Expand All @@ -82,7 +107,7 @@ class PoolSpecimenForm extends React.Component {
pool.specimenIds = specimenIds;

this.setState(
{pool, list, count, current, containerId},
{pool, list, count, filter},
this.setState({containerId: null})
);
}
Expand All @@ -93,21 +118,21 @@ class PoolSpecimenForm extends React.Component {
* @param {string} key - the key to be removed
*/
removeListItem(key) {
let {pool, list, current} = clone(this.state);
let {pool, list, filter} = clone(this.state);
// remove specimenId from pool.
pool.specimenIds = pool.specimenIds
.filter((id) => id != this.state.list[key].specimen.id);

// delete list at key.
delete list[key];

// reset current values if list is empty.
current = isEmpty(list) ? {} : current;
// remove center if list is empty.
filter = isEmpty(list) ? {} : filter;

// empty barcode input.
const containerId = null;

this.setState({pool, list, current, containerId});
this.setState({pool, list, containerId, filter});
}

/**
Expand All @@ -117,16 +142,16 @@ class PoolSpecimenForm extends React.Component {
* @return {Promise} - a resolved or rejected promise
*/
validateListItem(containerId) {
const {current, list} = clone(this.state);
const {list, filter} = clone(this.state);
const container = this.props.data.containers[containerId];
const specimen = this.props.data.specimens[container.specimenId];

// Throw error if new list item does not meet requirements.
if (!isEmpty(list)
&& (specimen.candidateId !== current.candidateId
|| specimen.sessionId !== current.sessionId
|| specimen.typeId !== current.typeId
|| container.centerId !== current.centerId)
if (!isEmpty(list)
&& (specimen.candidateId != filter.candidateId
|| specimen.sessionId != filter.sessionId
|| specimen.typeId != filter.typeId
|| container.centerId !== filter.centerId)
) {
Swal.fire(
{
Expand All @@ -148,7 +173,7 @@ class PoolSpecimenForm extends React.Component {
*/
render() {
const {data, options} = this.props;
const {current, pool, list, containerId, errors} = this.state;
const {pool, list, filter, containerId, errors} = this.state;

// generate barcode list from list object.
const barcodeList = Object.entries(list)
Expand Down Expand Up @@ -189,21 +214,29 @@ class PoolSpecimenForm extends React.Component {
a pool. Once pooled, the Status of specimen will be changed
to 'Dispensed' and there Quantity set to '0'"
/>
<StaticElement
<SearchableDropdown
name='typeId'
label='Specimen Type'
text={
(options.specimen.types[current.typeId]||{}).label || '—'
}
onUserInput={this.setFilter}
disabled={!isEmpty(list)}
value={filter.typeId}
options={mapFormOptions(options.specimen.types, 'label')}
/>
<StaticElement
<SearchableDropdown
name='candidateId'
label='PSCID'
text={
(options.candidates[current.candidateId]||{}).pscid || '—'
}
onUserInput={this.setFilter}
disabled={!isEmpty(list)}
value={filter.candidateId}
options={mapFormOptions(options.candidates, 'pscid')}
/>
<StaticElement
<SearchableDropdown
name='sessionId'
label='Visit Label'
text={(options.sessions[current.sessionId]||{}).label || '—'}
onUserInput={this.setFilter}
disabled={!isEmpty(list) || !filter.candidateId}
value={filter.sessionId}
options={mapFormOptions((options?.candidateSessions?.[filter.candidateId] || {}), 'label')}
/>
<div className='row'>
<div className='col-xs-6'>
Expand All @@ -212,6 +245,7 @@ class PoolSpecimenForm extends React.Component {
<BarcodeInput
list={list}
data={data}
filter={filter}
options={options}
errors={errors}
containerId={containerId}
Expand Down Expand Up @@ -314,6 +348,11 @@ PoolSpecimenForm.propTypes = {
})
).isRequired,
}).isRequired,
filter: PropTypes.shape({
candidateId: PropTypes.string,
sessionId: PropTypes.string,
typeId: PropTypes.string,
}).isRequired,
options: PropTypes.shape({
specimen: PropTypes.shape({
units: PropTypes.string,
Expand All @@ -337,7 +376,7 @@ class BarcodeInput extends PureComponent {
* @return {JSX}
*/
render() {
const {list, data, options, errors, containerId} = this.props;
const {list, data, filter, options, errors, containerId} = this.props;

// Restrict list of barcodes to only those that would be valid.
const barcodesPrimary = Object.values(data.containers)
Expand All @@ -351,11 +390,21 @@ class BarcodeInput extends PureComponent {
const inList = Object.values(list)
.find((i) => i.container.id == container.id);

const candidateMatch = !filter.candidateId
|| specimen.candidateId == filter.candidateId;
const sessionMatch = !filter.sessionId
|| specimen.sessionId == filter.sessionId;
const typeMatch = !filter.typeId
|| specimen.typeId == filter.typeId;

if (specimen.quantity > 0
&& container.statusId == availableId
&& specimen.poolId == null
&& !inList
) {
&& container.statusId == availableId
&& specimen.poolId == null
&& !inList
&& candidateMatch
&& sessionMatch
typeMatch
) {
result[container.id] = container.barcode;
}
}
Expand Down
45 changes: 30 additions & 15 deletions modules/biobank/jsx/specimenForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,26 +156,41 @@ class SpecimenForm extends React.Component {
return increment;
}

/**
* Fetch Barcodes from the backend.
*
* @param {number} limit - the number of barcodes to be generated
* @return {array} an array of barcodes
*/
async fetchBarcodes(limit) {
try {
const response = await fetch(`${loris.BaseURL}/biobank/barcodes?limit=${limit}`);
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() {
async 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);
}
result[key] = specimen;
return [result, increment];
}, [{}, this.incrementBarcode(pscid)]
);
const limit = Object.keys(list).length;

const barcodes = await this.fetchBarcodes(limit);
console.log(barcodes);

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

this.setState({list});
}

Expand Down
Loading

0 comments on commit 9134deb

Please sign in to comment.