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

x1211 Add kit costing to probe operation request #410

Merged
merged 1 commit into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ public class ProbeOperationRequest {
public static class ProbeOperationLabware {
private String barcode;
private String workNumber;
private SlideCosting kitCosting;
private List<ProbeLot> probes = List.of();

public ProbeOperationLabware() {}

public ProbeOperationLabware(String barcode, String workNumber, List<ProbeLot> probes) {
public ProbeOperationLabware(String barcode, String workNumber, SlideCosting kitCosting, List<ProbeLot> probes) {
setBarcode(barcode);
setWorkNumber(workNumber);
setKitCosting(kitCosting);
setProbes(probes);
}

Expand All @@ -56,6 +58,15 @@ public void setWorkNumber(String workNumber) {
this.workNumber = workNumber;
}

/** The costing for the kit used on this labware. */
public SlideCosting getKitCosting() {
return this.kitCosting;
}

public void setKitCosting(SlideCosting kitCosting) {
this.kitCosting = kitCosting;
}

/**
* The probes used on this labware.
*/
Expand All @@ -73,8 +84,10 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ProbeOperationLabware that = (ProbeOperationLabware) o;
return (Objects.equals(this.barcode, that.barcode)
&& Objects.equals(this.workNumber, that.workNumber)
&& this.kitCosting==that.kitCosting
&& Objects.equals(this.probes, that.probes)
&& Objects.equals(this.workNumber, that.workNumber));
);
}

@Override
Expand All @@ -84,7 +97,7 @@ public int hashCode() {

@Override
public String toString() {
return String.format("(%s, workNumber: %s, probes: %s)", repr(barcode), repr(workNumber), probes);
return String.format("(%s, %s, workNumber: %s, probes: %s)", repr(barcode), kitCosting, repr(workNumber), probes);
}
}

Expand All @@ -99,6 +112,7 @@ public static class ProbeLot {

private SlideCosting costing;

// Deserialisation constructor
public ProbeLot() {}

public ProbeLot(String name, String lot, Integer plex, SlideCosting costing) {
Expand Down
42 changes: 37 additions & 5 deletions src/main/java/uk/ac/sanger/sccp/stan/service/ProbeServiceImp.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,31 @@
import java.util.*;
import java.util.stream.Stream;

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

/**
* @author dr6
*/
@Service
public class ProbeServiceImp implements ProbeService {
public static final String KIT_COSTING_NAME = "kit costing";

private final LabwareValidatorFactory lwValFac;
private final LabwareRepo lwRepo;
private final OperationTypeRepo opTypeRepo;
private final OperationRepo opRepo;
private final ProbePanelRepo probePanelRepo;
private final LabwareProbeRepo lwProbeRepo;
private final LabwareNoteRepo noteRepo;
private final OperationService opService;
private final WorkService workService;
private final Validator<String> probeLotValidator;
private final Clock clock;

@Autowired
public ProbeServiceImp(LabwareValidatorFactory lwValFac,
LabwareRepo lwRepo, OperationTypeRepo opTypeRepo,
OperationRepo opRepo, ProbePanelRepo probePanelRepo, LabwareProbeRepo lwProbeRepo,
LabwareRepo lwRepo, OperationTypeRepo opTypeRepo, OperationRepo opRepo,
ProbePanelRepo probePanelRepo, LabwareProbeRepo lwProbeRepo, LabwareNoteRepo noteRepo,
OperationService opService, WorkService workService,
@Qualifier("probeLotNumberValidator") Validator<String> probeLotValidator,
Clock clock) {
Expand All @@ -48,6 +50,7 @@ public ProbeServiceImp(LabwareValidatorFactory lwValFac,
this.opRepo = opRepo;
this.probePanelRepo = probePanelRepo;
this.lwProbeRepo = lwProbeRepo;
this.noteRepo = noteRepo;
this.opService = opService;
this.workService = workService;
this.probeLotValidator = probeLotValidator;
Expand All @@ -73,8 +76,9 @@ public OperationResult recordProbeOperation(User user, ProbeOperationRequest req
OperationType opType = validateOpType(problems, request.getOperationType());
List<String> workNumbers = request.getLabware().stream()
.map(ProbeOperationLabware::getWorkNumber)
.collect(toList());
.toList();
UCMap<Work> work = workService.validateUsableWorks(problems, workNumbers);
checkKitCostings(problems, request.getLabware());
UCMap<ProbePanel> probes = validateProbes(problems, request.getLabware());
if (request.getPerformed()!=null) {
validateTimestamp(problems, request.getPerformed(), labware);
Expand Down Expand Up @@ -145,6 +149,17 @@ public OperationType validateOpType(Collection<String> problems, String opName)
return opType;
}

/**
* Checks that kit costings are supplied in the request
* @param problems receptacle for problems
* @param pols request details
*/
public void checkKitCostings(Collection<String> problems, Collection<ProbeOperationLabware> pols) {
if (pols!=null && pols.stream().anyMatch(pos -> pos.getKitCosting()==null)) {
problems.add("Missing kit costing for labware.");
}
}

/**
* Loads and checks the probes indicated in the request, their lot numbers and plexes.
* @param problems receptacle for problems
Expand Down Expand Up @@ -185,7 +200,7 @@ public UCMap<ProbePanel> validateProbes(Collection<String> problems, Collection<
UCMap<ProbePanel> probes = UCMap.from(probePanelRepo.findAllByNameIn(probeNames), ProbePanel::getName);
List<String> missingProbeNames = probeNames.stream()
.filter(name -> probes.get(name)==null)
.collect(toList());
.toList();
if (!missingProbeNames.isEmpty()) {
problems.add("Unknown probe panels: "+reprCollection(missingProbeNames));
}
Expand Down Expand Up @@ -224,6 +239,7 @@ public OperationResult perform(User user, Collection<ProbeOperationLabware> pols
LocalDateTime time, UCMap<Labware> lwMap, UCMap<ProbePanel> probeMap,
UCMap<Work> workMap) {
UCMap<Operation> lwOps = makeOps(user, opType, pols, lwMap, time);
saveKitCostings(pols, lwMap, lwOps);
linkWork(pols, lwOps, workMap);
saveProbes(pols, lwOps, lwMap, probeMap);
return assembleResult(pols, lwMap, lwOps);
Expand Down Expand Up @@ -255,6 +271,22 @@ public UCMap<Operation> makeOps(User user, OperationType opType, Collection<Prob
return opMap;
}

/**
* Saves the kit costings specified as labware notes
* @param pols the request details
* @param lwMap the labware mapped from barcode
* @param lwOps the operations recorded for each labware
*/
public void saveKitCostings(Collection<ProbeOperationLabware> pols, UCMap<Labware> lwMap, UCMap<Operation> lwOps) {
List<LabwareNote> notes = pols.stream()
.map(pol -> {
String barcode = pol.getBarcode();
return new LabwareNote(null, lwMap.get(barcode).getId(), lwOps.get(barcode).getId(),
KIT_COSTING_NAME, pol.getKitCosting().name());
}).toList();
noteRepo.saveAll(notes);
}

/**
* Links the newly created operations to the indicated work
* @param pols parts of the request linking labware to work
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -1732,6 +1732,8 @@ input ProbeOperationLabware {
barcode: String!
"""The work number of the operation on this labware."""
workNumber: String!
"""The costing for the kit used on this labware."""
kitCosting: SlideCosting!
"""The probes used on this labware."""
probes: [ProbeLot!]!
}
Expand Down
91 changes: 74 additions & 17 deletions src/test/java/uk/ac/sanger/sccp/stan/service/ProbeServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class ProbeServiceTest {
@Mock
private LabwareProbeRepo mockLwProbeRepo;
@Mock
private LabwareNoteRepo mockNoteRepo;
@Mock
private OperationService mockOpService;
@Mock
private WorkService mockWorkService;
Expand Down Expand Up @@ -87,6 +89,7 @@ public void testRecordProbeOperation(User user, ProbeOperationRequest request,
UCMap<ProbePanel> ppMap = UCMap.from(ProbePanel::getName, new ProbePanel("bananas"));
doReturn(ppMap).when(service).validateProbes(any(), any());
doNothing().when(service).validateTimestamp(any(), any(), any());
doNothing().when(service).checkKitCostings(any(), any());

if (nullOrEmpty(expectedProblems)) {
OperationResult opres = new OperationResult(List.of(), List.of(lw));
Expand Down Expand Up @@ -117,6 +120,7 @@ public void testRecordProbeOperation(User user, ProbeOperationRequest request,
.map(ProbeOperationLabware::getWorkNumber)
.collect(toList())));
verify(service).validateProbes(any(), eq(pols));
verify(service).checkKitCostings(any(), eq(pols));
if (request.getPerformed()==null) {
verify(service, never()).validateTimestamp(any(), any(), any());
} else {
Expand All @@ -135,25 +139,27 @@ public void testRecordProbeOperationProblems() {
UCMap<ProbePanel> ppMap = UCMap.from(ProbePanel::getName, new ProbePanel("pp1"));
doAnswer(addProblem("Bad probe", ppMap)).when(service).validateProbes(any(), any());
doAnswer(addProblem("Bad time")).when(service).validateTimestamp(any(), any(), any());
doAnswer(addProblem("Bad costing")).when(service).checkKitCostings(any(), any());
User user = EntityFactory.getUser();
ProbeOperationRequest request = new ProbeOperationRequest("optype", LocalDateTime.now(),
List.of(new ProbeOperationLabware("Alpha", "Beta", List.of())));
List.of(new ProbeOperationLabware("Alpha", "Beta", SlideCosting.SGP, List.of())));
Matchers.assertValidationException(() -> service.recordProbeOperation(user, request),
List.of("Bad labware", "Bad op type", "Bad work", "Bad probe", "Bad time"));
List.of("Bad labware", "Bad op type", "Bad work", "Bad probe", "Bad time", "Bad costing"));
ArgumentCaptor<Stream<String>> streamCaptor = Matchers.streamCaptor();
verify(service).validateLabware(any(), streamCaptor.capture());
assertThat(streamCaptor.getValue()).containsExactly("Alpha");
verify(service).validateOpType(any(), eq("optype"));
verify(mockWorkService).validateUsableWorks(any(), eq(List.of("Beta")));
verify(service).validateProbes(any(), same(request.getLabware()));
verify(service).validateTimestamp(any(), same(request.getPerformed()), same(lwMap));
verify(service).checkKitCostings(any(), same(request.getLabware()));
verify(service, never()).perform(any(), any(), any(), any(), any(), any(), any());
}

static Stream<Arguments> recordProbeOperationArgs() {
User user = EntityFactory.getUser();
ProbeOperationRequest emptyRequest = new ProbeOperationRequest();
ProbeOperationLabware pol = new ProbeOperationLabware("STAN-A1", "SGP-1", List.of());
ProbeOperationLabware pol = new ProbeOperationLabware("STAN-A1", "SGP-1", SlideCosting.SGP, List.of());
LocalDateTime time = LocalDateTime.now();
ProbeOperationRequest completeRequest = new ProbeOperationRequest("optype", time, List.of(pol));
return Arrays.stream(new Object[][] {
Expand Down Expand Up @@ -231,13 +237,33 @@ public void testValidateOpType(String opName, boolean exists, boolean inplace, b
assertProblem(problems, expectedProblem);
}

@ParameterizedTest
@ValueSource(booleans={false,true})
public void testCheckKitCostings(boolean anyMissing) {
List<SlideCosting> costings;
String expectedProblem;
if (anyMissing) {
costings = Arrays.asList(SlideCosting.SGP, SlideCosting.Faculty, null, SlideCosting.SGP, null);
expectedProblem = "Missing kit costing for labware.";
} else {
costings = List.of(SlideCosting.SGP, SlideCosting.Faculty, SlideCosting.SGP);
expectedProblem = null;
}
List<ProbeOperationLabware> pols = costings.stream()
.map(costing -> new ProbeOperationLabware(null, null, costing, null))
.toList();
List<String> problems = new ArrayList<>(anyMissing ? 1 : 0);
service.checkKitCostings(problems, pols);
assertProblem(problems, expectedProblem);
}

@Test
public void testValidateProbes() {
List<ProbeOperationLabware> pols = List.of(
new ProbeOperationLabware("BC1", "SGP1",
new ProbeOperationLabware("BC1", "SGP1", SlideCosting.SGP,
List.of(new ProbeLot("p1", "lot1", 1, SlideCosting.SGP),
new ProbeLot("p2", "lot2", 2, SlideCosting.SGP))),
new ProbeOperationLabware("BC2", "SGP2",
new ProbeOperationLabware("BC2", "SGP2", SlideCosting.Faculty,
List.of(new ProbeLot("p2", "lot3", 3, SlideCosting.Faculty),
new ProbeLot("p3", "lot4", 4, SlideCosting.SGP)))
);
Expand All @@ -258,9 +284,9 @@ public void testValidateProbes() {
@Test
public void testValidateNoProbes() {
List<ProbeOperationLabware> pols = List.of(
new ProbeOperationLabware("BC1", "SGP1",
new ProbeOperationLabware("BC1", "SGP1", SlideCosting.SGP,
List.of(new ProbeLot("p1", "lot1", 1, SlideCosting.Faculty))),
new ProbeOperationLabware("BC2", "SGP2",
new ProbeOperationLabware("BC2", "SGP2", SlideCosting.Faculty,
List.of())
);
List<ProbePanel> probes = List.of(new ProbePanel(1, "p1"));
Expand All @@ -285,7 +311,7 @@ public void testValidateNoProbes() {
"p1, lot2, 2, , Probe costing is missing.",
})
public void testValidateProbes_problems(String probeName, String lot, Integer plex, SlideCosting costing, String expectedProblem) {
ProbeOperationLabware pol = new ProbeOperationLabware("BC", "SGP1",
ProbeOperationLabware pol = new ProbeOperationLabware("BC", "SGP1", SlideCosting.SGP,
List.of(new ProbeLot(probeName, lot, plex, costing)));
when(mockProbeLotValidator.validate(any(), any())).then(invocation -> {
String lotArg = invocation.getArgument(0);
Expand Down Expand Up @@ -350,13 +376,15 @@ public void testPerform() {
doReturn(lwOps).when(service).makeOps(any(), any(), any(), any(), any());
doNothing().when(service).linkWork(any(), any(), any());
doNothing().when(service).saveProbes(any(), any(), any(), any());
doNothing().when(service).saveKitCostings(any(), any(), any());
OperationResult opres = new OperationResult(List.of(), List.of());
doReturn(opres).when(service).assembleResult(any(), any(), any());

assertSame(opres, service.perform(user, pols, opType, time, lwMap, ppMap, workMap));
verify(service).makeOps(user, opType, pols, lwMap, time);
verify(service).linkWork(pols, lwOps, workMap);
verify(service).saveProbes(pols, lwOps, lwMap, ppMap);
verify(service).saveKitCostings(pols, lwMap, lwOps);
verify(service).assembleResult(pols, lwMap, lwOps);
}

Expand All @@ -366,8 +394,8 @@ public void testMakeOps(boolean hasTime) {
User user = EntityFactory.getUser();
OperationType optype = EntityFactory.makeOperationType("opname", null);
List<ProbeOperationLabware> pols = List.of(
new ProbeOperationLabware("STAN-1", null, null),
new ProbeOperationLabware("STAN-2", null, null)
new ProbeOperationLabware("STAN-1", null, SlideCosting.SGP, null),
new ProbeOperationLabware("STAN-2", null, SlideCosting.Faculty, null)
);
LabwareType lt = EntityFactory.getTubeType();
UCMap<Labware> lwMap = UCMap.from(Labware::getBarcode, EntityFactory.makeEmptyLabware(lt, "STAN-1"),
Expand Down Expand Up @@ -398,9 +426,9 @@ public void testMakeOps(boolean hasTime) {
@Test
public void testLinkWork() {
List<ProbeOperationLabware> pols = List.of(
new ProbeOperationLabware("STAN-1", "SGP1", null),
new ProbeOperationLabware("STAN-2", "SGP1", null),
new ProbeOperationLabware("STAN-3", "SGP2", null)
new ProbeOperationLabware("STAN-1", "SGP1", SlideCosting.SGP, null),
new ProbeOperationLabware("STAN-2", "SGP1", SlideCosting.SGP, null),
new ProbeOperationLabware("STAN-3", "SGP2", SlideCosting.Faculty, null)
);
Operation[] ops = IntStream.range(1, 4)
.mapToObj(i -> {
Expand All @@ -423,14 +451,43 @@ public void testLinkWork() {
verify(mockWorkService).link(works[1], List.of(ops[2]));
}

@Test
public void testSaveKitCostings() {
LabwareType lt = EntityFactory.getTubeType();
Labware lw1 = EntityFactory.makeEmptyLabware(lt);
Labware lw2 = EntityFactory.makeEmptyLabware(lt);
UCMap<Labware> lwMap = UCMap.from(Labware::getBarcode, lw1, lw2);
Operation op1 = new Operation();
op1.setId(501);
Operation op2 = new Operation();
op2.setId(502);
UCMap<Operation> opMap = new UCMap<>(2);
opMap.put(lw1.getBarcode(), op1);
opMap.put(lw2.getBarcode(), op2);
final String noteName = ProbeServiceImp.KIT_COSTING_NAME;

List<ProbeOperationLabware> pols = List.of(
new ProbeOperationLabware(lw1.getBarcode(), null, SlideCosting.SGP, null),
new ProbeOperationLabware(lw2.getBarcode(), null, SlideCosting.Faculty, null)
);

service.saveKitCostings(pols, lwMap, opMap);

List<LabwareNote> expectedNotes = List.of(
new LabwareNote(null, lw1.getId(), op1.getId(), noteName, "SGP"),
new LabwareNote(null, lw2.getId(), op2.getId(), noteName, "Faculty")
);
verify(mockNoteRepo).saveAll(expectedNotes);
}

@Test
public void testSaveProbes() {
List<ProbeOperationLabware> pols = List.of(
new ProbeOperationLabware("STAN-1", null, List.of(
new ProbeOperationLabware("STAN-1", null, SlideCosting.Faculty, List.of(
new ProbeLot("probe1", "lot1", 1, SlideCosting.SGP),
new ProbeLot("probe2", "lot2", 2, SlideCosting.SGP)
)),
new ProbeOperationLabware("STAN-2", null, List.of(
new ProbeOperationLabware("STAN-2", null, SlideCosting.SGP, List.of(
new ProbeLot("probe1", "lot3", 3, SlideCosting.Faculty)
))
);
Expand Down Expand Up @@ -466,8 +523,8 @@ public void testAssembleResult() {
Labware[] labware = { EntityFactory.makeEmptyLabware(lt, "STAN-1"), EntityFactory.makeEmptyLabware(lt, "STAN-2")};
UCMap<Labware> lwMap = UCMap.from(Labware::getBarcode, labware);
List<ProbeOperationLabware> pols = List.of(
new ProbeOperationLabware("STAN-1", null, null),
new ProbeOperationLabware("STAN-2", null, null)
new ProbeOperationLabware("STAN-1", null, SlideCosting.SGP, null),
new ProbeOperationLabware("STAN-2", null, SlideCosting.Faculty, null)
);
Operation[] ops = { new Operation(), new Operation()};
ops[0].setId(1);
Expand Down
Loading
Loading