Skip to content

Commit

Permalink
Merge pull request #480 from sanger/strip
Browse files Browse the repository at this point in the history
x1206 Label printing for strip-tube labels
  • Loading branch information
khelwood authored Nov 21, 2024
2 parents 25e8e17 + 73dbe06 commit 4d309f9
Show file tree
Hide file tree
Showing 12 changed files with 337 additions and 22 deletions.
29 changes: 29 additions & 0 deletions src/main/java/uk/ac/sanger/sccp/stan/repo/WorkRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import java.util.function.Function;

import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static uk.ac.sanger.sccp.utils.BasicUtils.inMap;
import static uk.ac.sanger.sccp.utils.BasicUtils.stream;

public interface WorkRepo extends CrudRepository<Work, Integer> {
Optional<Work> findByWorkNumber(String workNumber);
Expand Down Expand Up @@ -146,6 +149,32 @@ default Map<Integer, String> findWorkNumbersForReleaseIds(Collection<Integer> re
@Query(value="select * from work_sample ws join work on (ws.work_id=work.id) where ws.sample_id=(?1) and ws.slot_id = (?2)", nativeQuery = true)
Set<Work> findWorkForSampleIdAndSlotId(Integer sampleId, Integer slotId);

@Query(value="select slot_id, sample_id, work_id from work_sample ws where ws.slot_id in (?1)", nativeQuery = true)
List<Object[]> slotSampleWorkIdsForSlotIds(Collection<Integer> slotIds);

/**
* Loads works linked to the given slot ids.
* @param slotIds slot ids to look for works
* @return a map from slot/sample ids to the set of linked works
*/
default Map<SlotIdSampleId, Set<Work>> slotSampleWorksForSlotIds(Collection<Integer> slotIds) {
List<Object[]> rows = slotIds.isEmpty() ? List.of() : slotSampleWorkIdsForSlotIds(slotIds);
if (rows.isEmpty()) {
return Map.of();
}
Set<Integer> workIds = rows.stream()
.map(arr -> (Integer) arr[2])
.collect(toSet());
Map<Integer, Work> workMap = stream(findAllById(workIds))
.collect(inMap(Work::getId));
Map<SlotIdSampleId, Set<Work>> map = new HashMap<>();
for (Object[] row: rows) {
SlotIdSampleId key = new SlotIdSampleId((Integer) row[0], (Integer) row[1]);
map.computeIfAbsent(key, k -> new HashSet<>()).add(workMap.get((Integer) row[2]));
}
return map;
}

default Set<Work> getSetByWorkNumberIn(Collection<String> workNumbers) throws EntityNotFoundException {
return RepoUtils.getSetByField(this::findAllByWorkNumberIn, workNumbers, Work::getWorkNumber,
"Unknown work number{s}: ", String::toUpperCase);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import java.util.*;

import static uk.ac.sanger.sccp.utils.BasicUtils.nullToEmpty;

/**
* A collection of information that may be printed onto a labware label.
* @author dr6
Expand All @@ -13,16 +15,22 @@ public class LabwareLabelData {
private final String externalBarcode;
private final String medium;
private final String date;

private final List<LabelContent> contents;
private final Map<String, String> extraFields;

public LabwareLabelData(String barcode, String externalBarcode, String medium, String date,
List<LabelContent> contents) {
List<LabelContent> contents, Map<String, String> extraFields) {
this.barcode = barcode;
this.externalBarcode = externalBarcode;
this.medium = medium;
this.date = date;
this.contents = List.copyOf(contents);
this.contents = nullToEmpty(contents);
this.extraFields = nullToEmpty(extraFields);
}

public LabwareLabelData(String barcode, String externalBarcode, String medium, String date,
List<LabelContent> contents) {
this(barcode, externalBarcode, medium, date, contents, null);
}

public String getBarcode() {
Expand All @@ -45,6 +53,10 @@ public List<LabelContent> getContents() {
return this.contents;
}

public Map<String, String> getExtraFields() {
return this.extraFields;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -54,7 +66,9 @@ public boolean equals(Object o) {
&& Objects.equals(this.externalBarcode, that.externalBarcode)
&& Objects.equals(this.medium, that.medium)
&& Objects.equals(this.date, that.date)
&& Objects.equals(this.contents, that.contents));
&& Objects.equals(this.contents, that.contents)
&& Objects.equals(this.extraFields, that.extraFields)
);
}

@Override
Expand All @@ -70,6 +84,7 @@ public String toString() {
.add("medium", medium)
.add("date", date)
.add("contents", contents)
.add("extraFields", extraFields)
.omitNullValues()
.reprStringValues()
.toString();
Expand All @@ -81,6 +96,7 @@ public Map<String, String> getFields() {
fields.put("medium", getMedium());
fields.put("date", getDate());
fields.put("external", getExternalBarcode());
fields.putAll(getExtraFields());
int index = 0;
for (LabelContent content : contents) {
addField(fields, "donor", index, content.donorName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static uk.ac.sanger.sccp.utils.BasicUtils.reverseIter;

/**
* Service for creating LabwareLabelData from labware.
Expand Down Expand Up @@ -96,6 +97,53 @@ public LabwareLabelData toLabelData(Labware labware, List<LabelContent> content,
return new LabwareLabelData(labware.getBarcode(), labware.getExternalBarcode(), medium, dateString, content);
}

/**
* Label data for labware that has a label per slot
* @param lw the labware being labelled
* @param workNumbers the work number linked to each slot/sample combination
* @param lp the lp number linked to the labware, if any
* @return label data for the labware
*/
public List<LabwareLabelData> getSplitLabelData(Labware lw, Map<SlotIdSampleId, String> workNumbers, String lp) {
List<LabwareLabelData> datas = new ArrayList<>(lw.getSlots().size());
// Iterate the slots in reverse order
for (Slot slot : reverseIter(lw.getSlots())) {
if (!slot.getSamples().isEmpty()) {
Sample sample = slot.getSamples().getFirst();
String workNumber = workNumbers.get(new SlotIdSampleId(slot, sample));
Tissue tissue = sample.getTissue();
LabelContent lc = new LabelContent(tissue.getDonor().getDonorName(), tissue.getExternalName(),
null, sample.getBioState().toString());
Map<String, String> extra = filteredMap("lp", lp, "work", workNumber,
"address", slot.getAddress().toString());
datas.add(new LabwareLabelData(lw.getBarcode(), lw.getExternalBarcode(), null, null,
List.of(lc), extra));
}
}
return datas;
}

/**
* Returns a map containing the given keys and values, omitted null keys and null values
* @param data alternating keys and values
* @return a map containing the data, excluding null keys and values
* @param <B> type of data array given (base type of K and V)
* @param <K> key type
* @param <V> value type
*/
@SuppressWarnings("unchecked")
static <B, K extends B, V extends B> Map<K,V> filteredMap(B... data) {
Map<K,V> map = new HashMap<>(data.length/2);
for (int i = 0; i < data.length; i+=2) {
K key = (K) data[i];
V value = (V) data[i+1];
if (key!=null && value!=null) {
map.put(key, value);
}
}
return map;
}

/**
* Label data where we list the tissue for each row.
* @param labware the labware the label describes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package uk.ac.sanger.sccp.stan.service.label.print;

import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import uk.ac.sanger.sccp.stan.model.*;
import uk.ac.sanger.sccp.stan.repo.*;
import uk.ac.sanger.sccp.stan.service.LabwareNoteService;
import uk.ac.sanger.sccp.stan.service.LabwareService;
import uk.ac.sanger.sccp.stan.service.label.*;
import uk.ac.sanger.sccp.stan.service.work.WorkService;
import uk.ac.sanger.sccp.utils.UCMap;

import javax.persistence.EntityNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.function.Function;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static java.util.stream.Collectors.*;
import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty;
import static uk.ac.sanger.sccp.utils.UCMap.toUCMap;

/**
* Service to perform and record labware label printing
Expand All @@ -29,18 +33,22 @@ public class LabelPrintService {
private final LabwarePrintRepo labwarePrintRepo;
private final LabelTypeRepo labelTypeRepo;
private final LabwareService labwareService;
private final LabwareNoteService noteService;
private final WorkService workService;

@Autowired
public LabelPrintService(LabwareLabelDataService labwareLabelDataService, PrintClientFactory printClientFactory,
LabwareRepo labwareRepo, PrinterRepo printerRepo, LabwarePrintRepo labwarePrintRepo,
LabelTypeRepo labelTypeRepo, LabwareService labwareService) {
LabelTypeRepo labelTypeRepo, LabwareService labwareService, LabwareNoteService noteService, WorkService workService) {
this.labwareLabelDataService = labwareLabelDataService;
this.printClientFactory = printClientFactory;
this.labwareRepo = labwareRepo;
this.printerRepo = printerRepo;
this.labwarePrintRepo = labwarePrintRepo;
this.labelTypeRepo = labelTypeRepo;
this.labwareService = labwareService;
this.noteService = noteService;
this.workService = workService;
}

public void printLabwareBarcodes(User user, String printerName, List<String> barcodes) throws IOException {
Expand All @@ -66,20 +74,46 @@ public void printLabware(User user, String printerName, List<Labware> labware) t
throw new IllegalArgumentException("Cannot perform a print request incorporating multiple different label types.");
}
LabelType labelType = labelTypes.iterator().next();
final Function<Labware, LabwareLabelData> labelFunction;
if (labelType.getName().equalsIgnoreCase("adh")) {
labelFunction = labwareLabelDataService::getRowBasedLabelData;
List<LabwareLabelData> labelData;
if (labelType.getName().equalsIgnoreCase("strip")) {
// NB if we try and label empty strip tubes from planned actions, it won't work
labelData = stripLabwareLabelData(labware);
} else {
labelFunction = labwareLabelDataService::getLabelData;
final Function<Labware, LabwareLabelData> labelFunction;
if (labelType.getName().equalsIgnoreCase("adh")) {
labelFunction = labwareLabelDataService::getRowBasedLabelData;
} else {
labelFunction = labwareLabelDataService::getLabelData;
}
labelData = labware.stream()
.map(labelFunction)
.toList();
}
List<LabwareLabelData> labelData = labware.stream()
.map(labelFunction)
.collect(toList());
LabelPrintRequest request = new LabelPrintRequest(labelType, labelData);
print(printer, request);
recordPrint(printer, user, labware);
}

/**
* Loads the strip label data for the given labware.
* Strip tube labware has multiple labels for each item of labware.
* @param labware the labware being labelled
* @return the label data for all the labware
*/
@NotNull
List<LabwareLabelData> stripLabwareLabelData(List<Labware> labware) {
UCMap<String> lpNumbers = noteService.findNoteValuesForLabware(labware, "lp number").entrySet().stream()
.filter(e -> !nullOrEmpty(e.getValue()))
.collect(toUCMap(Map.Entry::getKey, e -> e.getValue().iterator().next()));
Map<SlotIdSampleId, Set<Work>> slotWorks = workService.loadWorksForSlotsIn(labware);
Map<SlotIdSampleId, String> slotWorkNumbers = slotWorks.entrySet().stream()
.filter(e -> !nullOrEmpty(e.getValue()))
.collect(toMap(Map.Entry::getKey, e -> e.getValue().iterator().next().getWorkNumber()));
return labware.stream()
.flatMap(lw -> labwareLabelDataService.getSplitLabelData(lw, slotWorkNumbers, lpNumbers.get(lw.getBarcode())).stream())
.toList();
}

public void print(Printer printer, LabelPrintRequest request) throws IOException {
PrintClient<? super LabelPrintRequest> printClient = printClientFactory.getClient(printer.getService());
printClient.print(printer.getName(), request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import uk.ac.sanger.sccp.stan.request.WorkWithComment;
import uk.ac.sanger.sccp.utils.UCMap;

import java.util.Collection;
import java.util.List;
import java.util.*;
import java.util.stream.Stream;

/**
Expand Down Expand Up @@ -222,6 +221,13 @@ Work createWork(User user, String prefix, String workTypeName, String workReques
*/
List<Work> getWorksCreatedBy(User user);

/**
* Loads works linked to the slots in the given labware
* @param labware labware
* @return map of slot/sample ids to works
*/
Map<SlotIdSampleId, Set<Work>> loadWorksForSlotsIn(Collection<Labware> labware);

/**
* struct-like container for a work and an operation
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,17 @@ public List<Work> getWorksCreatedBy(User user) {
.collect(toList());
}

@Override
public Map<SlotIdSampleId, Set<Work>> loadWorksForSlotsIn(Collection<Labware> labware) {
return loadWorksForSlots(labware.stream()
.flatMap(lw -> lw.getSlots().stream()));
}

public Map<SlotIdSampleId, Set<Work>> loadWorksForSlots(Stream<Slot> slots) {
List<Integer> slotIds = slots.map(Slot::getId).toList();
return workRepo.slotSampleWorksForSlotIds(slotIds);
}

public void fillInComments(Collection<WorkWithComment> wcs, Map<Integer, WorkEvent> workEvents) {
for (WorkWithComment wc : wcs) {
Work work = wc.getWork();
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/sprint.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ sprint.host = http://sprint.psd.sanger.ac.uk/graphql
sprint.template_dir = sprint

sprint.templates = {\
'strip' : 'strip.json',\
'adh' : 'adh.json',\
'slide@3' : 'slide3.json',\
'slide@6' : 'slide6.json',\
Expand Down
61 changes: 61 additions & 0 deletions src/main/resources/sprint/strip.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"labelSize": {
"width": 39,
"height": 8,
"displacement": 8
},
"barcodeFields": [
{
"x": 8,
"y": 1,
"value": "#barcode#",
"barcodeType": "datamatrix",
"cellWidth": 0.25
},
{
"x": 32,
"y": 1,
"value": "#barcode#",
"barcodeType": "datamatrix",
"cellWidth": 0.25
}
],
"textFields": [
{
"x": 20,
"y": 2,
"value": "#work#",
"fontSize": 1.7
},
{
"x": 27,
"y": 2,
"value": "#lp#",
"fontSize": 1.7
},
{
"x": 12,
"y": 4,
"value": "#address#",
"fontSize": 1.8
},
{
"x": 20,
"y": 4,
"value": "#state[0]#",
"fontSize": 1.8
},
{
"x": 20,
"y": 6,
"value": "#tissue[0]#",
"fontSize": 1.5
},
{
"x": 8,
"y": 7,
"value": "#barcode#",
"fontSize": 1.5
}
]
}
Loading

0 comments on commit 4d309f9

Please sign in to comment.