InventoryViabilityServiceImpl.java
/*
* Copyright 2021 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.service.impl;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.api.v1.Pagination;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.InventoryAction;
import org.gringlobal.model.InventoryViability;
import org.gringlobal.model.InventoryViabilityAction;
import org.gringlobal.model.InventoryViabilityAttach;
import org.gringlobal.model.InventoryViabilityData;
import org.gringlobal.model.InventoryViabilityRule;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.OrderRequestItem;
import org.gringlobal.model.QInventory;
import org.gringlobal.model.QInventoryViability;
import org.gringlobal.model.QInventoryViabilityAction;
import org.gringlobal.model.QInventoryViabilityAttach;
import org.gringlobal.model.QInventoryViabilityData;
import org.gringlobal.model.Site;
import org.gringlobal.model.InventoryViability.ViabilityStatus;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.model.workflow.WorkflowActionStep;
import org.gringlobal.persistence.InventoryRepository;
import org.gringlobal.persistence.InventoryViabilityActionRepository;
import org.gringlobal.persistence.InventoryViabilityDataRepository;
import org.gringlobal.persistence.InventoryViabilityRepository;
import org.gringlobal.persistence.InventoryViabilityRuleRepository;
import org.gringlobal.service.InventoryActionService;
import org.gringlobal.service.InventoryActionService.InventoryActionRequest;
import org.gringlobal.service.InventoryViabilityActionService;
import org.gringlobal.service.InventoryViabilityActionService.*;
import org.gringlobal.service.InventoryViabilityAttachmentService;
import org.gringlobal.service.InventoryViabilityDataService;
import org.gringlobal.service.InventoryViabilityService;
import org.gringlobal.service.MethodService;
import org.gringlobal.service.OrderRequestService;
import org.gringlobal.service.filter.InventoryViabilityActionFilter;
import org.gringlobal.service.filter.InventoryViabilityAttachFilter;
import org.gringlobal.service.filter.InventoryViabilityDataFilter;
import org.gringlobal.service.filter.InventoryViabilityFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.jpa.impl.JPAQuery;
import org.springframework.web.multipart.MultipartFile;
@Service
@Transactional(readOnly = true)
@Slf4j
public class InventoryViabilityServiceImpl extends FilteredCRUDService2Impl<InventoryViability, InventoryViabilityFilter, InventoryViabilityRepository> implements
InventoryViabilityService {
@Autowired
private OrderRequestService orderRequestService;
@Autowired
private InventoryActionService inventoryActionService;
@Autowired
private InventoryViabilityDataRepository viabilityDataRepository;
@Autowired
private InventoryViabilityRuleRepository viabilityRuleRepository;
@Autowired
private InventoryRepository inventoryRepository;
@Component
protected static class AttachmentSupport extends BaseAttachmentSupport<InventoryViability, InventoryViabilityAttach, InventoryViabilityAttachmentService.InventoryViabilityAttachmentRequest> implements InventoryViabilityAttachmentService {
public AttachmentSupport() {
super(QInventoryViabilityAttach.inventoryViabilityAttach.inventoryViability().id, QInventoryViabilityAttach.inventoryViabilityAttach.id);
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('ViabilityTest', 'WRITE')")
public InventoryViabilityAttach uploadFile(InventoryViability entity, MultipartFile file, InventoryViabilityAttachmentRequest metadata) throws IOException, InvalidRepositoryPathException, InvalidRepositoryFileDataException {
return super.uploadFile(entity, file, metadata);
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('ViabilityTest', 'DELETE')")
public InventoryViabilityAttach removeFile(InventoryViability entity, Long attachmentId) {
return super.removeFile(entity, attachmentId);
}
@Override
protected Path createRepositoryPath(InventoryViability inventoryViability) {
inventoryViability = owningEntityRepository.getReferenceById(inventoryViability.getId());
return Paths.get("/inventoryViability/" + inventoryViability.getId());
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('ViabilityTest', 'WRITE')")
protected InventoryViabilityAttach createAttach(InventoryViability entity, InventoryViabilityAttach source) {
InventoryViabilityAttach attach = new InventoryViabilityAttach();
attach.apply(source);
attach.setVirtualPath(source.getVirtualPath()); // SOAP uses this to create the record
attach.setInventoryViability(entity);
return attach;
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('ViabilityTest', 'WRITE')")
public InventoryViabilityAttach create(InventoryViabilityAttach source) {
var owningEntity = owningEntityRepository.getReferenceById(source.getInventoryViability().getId());
var attach = createAttach(owningEntity, source);
var savedAttach = repository.save(attach);
return _lazyLoad(savedAttach);
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('ViabilityTest', 'WRITE')")
public InventoryViabilityAttach update(InventoryViabilityAttach updated, InventoryViabilityAttach target) {
target.apply(updated);
return _lazyLoad(repository.save(target));
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('ViabilityTest', 'DELETE')")
public InventoryViabilityAttach remove(InventoryViabilityAttach entity) {
return super.remove(entity);
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('ViabilityTest', 'READ')")
public List<InventoryViabilityAttach> getAllAttachments(InventoryViability inventoryViability) {
var attachments = Lists.newArrayList(((QuerydslPredicateExecutor<InventoryViabilityAttach>)repository).findAll(QInventoryViability.inventoryViability.id.eq(inventoryViability.getId())));
attachments.forEach(InventoryViabilityAttach::lazyLoad);
return attachments;
}
@Override
public Page<InventoryViabilityAttach> list(InventoryViabilityAttachFilter filter, Pageable page) throws SearchException {
page = Pagination.addSortByParams(page, idSortParams);
BooleanBuilder predicate = new BooleanBuilder();
if (filter != null) {
predicate.and(filter.buildPredicate());
}
var entityListQuery = entityListQuery();
if (entityListQuery != null) {
JPAQuery<InventoryViabilityAttach> query = entityListQuery.where(predicate);
return repository.findAll(query, page);
} else {
// default implementation without custom loading
return ((QuerydslPredicateExecutor<InventoryViabilityAttach>)repository).findAll(predicate, page);
}
}
@Override
protected JPAQuery<InventoryViabilityAttach> entityListQuery() {
return jpaQueryFactory.selectFrom(QInventoryViabilityAttach.inventoryViabilityAttach)
// attach cooperator
.leftJoin(QInventoryViabilityAttach.inventoryViabilityAttach.attachCooperator()).fetchJoin()
// repository file
.leftJoin(QInventoryViabilityAttach.inventoryViabilityAttach.repositoryFile()).fetchJoin()
// inventory viability
.join(QInventoryViabilityAttach.inventoryViabilityAttach.inventoryViability()).fetchJoin()
;
}
}
@Override
public Page<InventoryViability> list(InventoryViabilityFilter filter, Pageable page) throws SearchException {
return super.list(InventoryViability.class, filter, page);
}
@Override
protected JPAQuery<InventoryViability> entityListQuery() {
QInventory inv = new QInventory("i");
return jpaQueryFactory.selectFrom(QInventoryViability.inventoryViability)
// inventory
.join(QInventoryViability.inventoryViability.inventory(), inv).fetchJoin()
// accession
.join(inv.accession()).fetchJoin()
// viability rule
.leftJoin(QInventoryViability.inventoryViability.inventoryViabilityRule()).fetchJoin()
;
}
@Override
protected NumberPath<Long> entityIdPredicate() {
return QInventoryViability.inventoryViability.id;
}
@Override
// @PostAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'READ', returnObject.inventoryViability.inventory.site)")
public InventoryViabilityDetails loadDetails(InventoryViability inventoryViability) {
var reload = reload(inventoryViability);
reload.getInventory().lazyLoad();
if (reload.getInventoryViabilityRule() != null) {
reload.getInventoryViabilityRule().getId();
}
var details = new InventoryViabilityDetails(reload);
details.datas = reload.getDatas();
if (details.datas != null) {
for (var inventoryViabilityData : details.datas) {
inventoryViabilityData.lazyLoad();
}
}
return details;
}
@Override
public InventoryViabilityDetails calculateResult(InventoryViability iv, Collection<Integer> selectedReplicationNumbers) {
iv = get(iv);
// if (iv.getReplicationCount() < 1)
// throw new InvalidApiUsageException("replicationCount less than 1, nothing to calculate");
// Get the last observations of each of the replicates (by observation date)
List<InventoryViabilityData> sumOfObservations = new ArrayList<>();
var datas = Lists.newArrayList(viabilityDataRepository.findAll(QInventoryViabilityData.inventoryViabilityData.inventoryViability().eq(iv), Sort.by(Sort.Direction.DESC, "countNumber")));
// distinct replicateCount values
Collection<Integer> analyzedReplicationNumbers = selectedReplicationNumbers;
if (CollectionUtils.isEmpty(analyzedReplicationNumbers)) {
// Find existing replicates
analyzedReplicationNumbers = datas.stream().map(InventoryViabilityData::getReplicationNumber).distinct().sorted().collect(Collectors.toList());
}
// Use selected replicates only
for (var selectedReplicationNumber : analyzedReplicationNumbers) {
var sumForReplicate = // filter for replicationNumber
datas.stream().filter(ivd -> ivd.getReplicationNumber() == selectedReplicationNumber)
// Make new InventoryViabilityData and summarize the counts across the replicates
.reduce(new InventoryViabilityData(), InventoryViabilityServiceImpl::summarizeForReplicate);
sumOfObservations.add(sumForReplicate);
if (sumForReplicate.sumOfCounts() != sumForReplicate.getReplicationCount().intValue()) {
throw new InvalidApiUsageException("Sum of counts does not match replicationCount for replication " + selectedReplicationNumber);
}
}
if (sumOfObservations.size() == 0) {
throw new InvalidApiUsageException("Replication data not available");
}
var summarizedIVD = sumOfObservations.stream().reduce(new InventoryViabilityData(), InventoryViabilityServiceImpl::summarize);
if (summarizedIVD.sumOfCounts() != summarizedIVD.getReplicationCount().intValue()) {
throw new InvalidApiUsageException("Sum of all counts does not match sum of replicationCounts");
}
// After the sums are calculated, a new InventoryViability object is used to calculate values of percent*
final int totalCount = summarizedIVD.getReplicationCount();
// Update results of loaded InventoryViability
InventoryViability result = iv;
result.setPercentNormal(toPercent(summarizedIVD.getNormalCount(), totalCount));
result.setPercentHard(toPercent(summarizedIVD.getHardCount(), totalCount));
result.setPercentUnknown(toPercent(summarizedIVD.getUnknownCount(), totalCount));
result.setPercentAbnormal(toPercent(summarizedIVD.getAbnormalCount(), totalCount));
result.setPercentDead(toPercent(summarizedIVD.getDeadCount(), totalCount));
result.setPercentInfested(toPercent(summarizedIVD.getInfestedCount(), totalCount));
result.setPercentEmpty(toPercent(summarizedIVD.getEmptyCount(), totalCount));
// TZ
result.setPercentTzPositive(toPercent(summarizedIVD.getTzPositiveCount(), totalCount));
result.setPercentTzNegative(toPercent(summarizedIVD.getTzNegativeCount(), totalCount));
// Dormant
// Legacy GG: percent_dormant is calculated from rep_dormant_count + rep_estimated_dormant_count + rep_confirmed_dormant_count
result.setPercentDormant(toPercent(
// percent_dormant is calculated from rep_dormant_count + rep_estimated_dormant_count + rep_confirmed_dormant_count
summarizedIVD.getDormantCount() + summarizedIVD.getEstimatedDormantCount() + summarizedIVD.getConfirmedDormantCount()
// unlike the original, we include + tz_positive_count
+ summarizedIVD.getTzPositiveCount()
// divided by total number of tested seed
, totalCount));
// Legacy GG: percent_viable uses rep_normal_count + rep_hard_count + rep_dormant_count + rep_estimated_dormant_count + rep_confirmed_dormant_count
result.setPercentViable(toPercent(
// percent_viable uses rep_normal_count + rep_hard_count + rep_dormant_count
summarizedIVD.getNormalCount() + summarizedIVD.getHardCount()
// + rep_dormant_count + rep_estimated_dormant_count + rep_confirmed_dormant_count
+ summarizedIVD.getDormantCount() + summarizedIVD.getEstimatedDormantCount() + summarizedIVD.getConfirmedDormantCount()
// unlike the original, we include + tz_positive_count
+ summarizedIVD.getTzPositiveCount()
// divided by total number of tested seed
, totalCount));
var details = new InventoryViabilityDetails(iv);
details.datas = sumOfObservations;
return details;
}
private double toPercent(int sumColumnCount, int replicationCount) {
return sumColumnCount * 100. / replicationCount;
}
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'CREATE', returnObject.inventory.site)")
public InventoryViability createFast(InventoryViability source) {
assert(source.getId() == null);
source.setInventory(inventoryRepository.getReferenceById(source.getInventory().getId()));
var rule = source.getInventoryViabilityRule();
if (rule != null) {
var savedRule = viabilityRuleRepository.getReferenceById(source.getInventoryViabilityRule().getId());
source.setInventoryViabilityRule(savedRule);
int numberOfReplicates = Optional.ofNullable(savedRule.getNumberOfReplicates()).orElse(1);
Integer testQuantity = savedRule.getSeedsPerReplicate() == null ? null : (int) savedRule.getSeedsPerReplicate() * numberOfReplicates;
source.setReplicationCount(numberOfReplicates);
source.setTotalTestedCount(testQuantity);
}
if (source.getTestedDate() == null) {
source.setTestedDate(new Date());
}
source.setTestedDateCode(CommunityCodeValues.DATE_FORMAT_DATE.value);
var saved = repository.save(source);
// Start inventory action: Viability test
inventoryActionService.startAction(InventoryActionRequest.builder()
.id(Set.of(saved.getInventory().getId()))
.actionNameCode(CommunityCodeValues.INVENTORY_ACTION_VABILITYTEST.value)
.build()
);
return saved;
}
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'CREATE', returnObject.inventory.site)")
public InventoryViability create(InventoryViability source) {
assert(source.getId() == null);
source.setInventory(inventoryRepository.getReferenceById(source.getInventory().getId()));
var rule = source.getInventoryViabilityRule();
if (rule != null) {
int numberOfReplicates = Optional.ofNullable(rule.getNumberOfReplicates()).orElse(1);
Integer testQuantity = rule.getSeedsPerReplicate() == null ? null : (int) rule.getSeedsPerReplicate() * numberOfReplicates;
source.setReplicationCount(numberOfReplicates);
source.setTotalTestedCount(testQuantity);
}
if (source.getTestedDate() == null) {
source.setTestedDate(new Date());
}
source.setTestedDateCode(CommunityCodeValues.DATE_FORMAT_DATE.value);
var saved = repository.save(source);
saved.lazyLoad();
// Start inventory action: Viability test
inventoryActionService.startAction(InventoryActionRequest.builder()
.id(Set.of(saved.getInventory().getId()))
.actionNameCode(CommunityCodeValues.INVENTORY_ACTION_VABILITYTEST.value)
.build()
);
return _lazyLoad(saved);
}
@Override
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', #target.inventory.site)")
public InventoryViability updateFast(@NotNull @Valid InventoryViability updated, InventoryViability target) {
log.debug("Update InventoryViability. Input data {}", updated);
target.apply(updated);
var saved = repository.save(target);
if (saved.getStatus() == ViabilityStatus.CONCLUSIVE) {
var request = InventoryActionService.InventoryActionRequest.builder()
.actionNameCode(CommunityCodeValues.INVENTORY_ACTION_VABILITYTEST.value)
.id(Set.of(saved.getInventory().getId()))
.build();
var inProgress = inventoryActionService.listInProgressActions(request);
if (inProgress.size() > 0) {
var completedViabilityAction = inventoryActionService.completeAction(request).get(0);
log.info("Completed viability action {}", completedViabilityAction.getId());
}
}
return saved;
}
@Override
@PostAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', returnObject.inventory.site)")
public InventoryViability update(InventoryViability updated) {
return super.update(updated);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', #target.inventory.site)")
public InventoryViability update(InventoryViability input, InventoryViability target) {
if (input == null) {
throw new InvalidApiUsageException("Source object must be provided.");
}
log.debug("Update InventoryViability. Input data {}", input);
target.apply(input);
var saved = repository.save(target);
if (saved.getStatus() == ViabilityStatus.CONCLUSIVE) {
var request = InventoryActionService.InventoryActionRequest.builder()
.actionNameCode(CommunityCodeValues.INVENTORY_ACTION_VABILITYTEST.value)
.id(Set.of(saved.getInventory().getId()))
.build();
var inProgress = inventoryActionService.listInProgressActions(request);
if (inProgress.size() > 0) {
var completedViabilityAction = inventoryActionService.completeAction(request).get(0);
log.info("Completed viability action {}", completedViabilityAction.getId());
}
}
return _lazyLoad(saved);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'DELETE', #entity.inventory.site)")
public InventoryViability remove(InventoryViability entity) {
return super.remove(entity);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'ADMINISTRATION', #site)")
public OrderRequest orderViabilityTest(Site site, Map<Inventory, InventoryViabilityRule> inventoriesAndRules, Cooperator viabilityCooperator) {
// inventories.forEach((inv) -> {
// if (! inv.getSite().getId().equals(site.getId())) {
// throw new InvalidApiUsageException("Inventory does not belong to the site");
// }
// });
final var orderRequestItems = new ArrayList<OrderRequestItem>(inventoriesAndRules.size());
var inventoryViabilities = inventoriesAndRules.entrySet().stream().map((viabReq) -> {
var inventory = viabReq.getKey();
var rule = viabReq.getValue();
int numberOfReplicates = Optional.ofNullable(rule.getNumberOfReplicates()).orElse(1);
Integer testQuantity = rule.getSeedsPerReplicate() == null ? null : (int) rule.getSeedsPerReplicate() * numberOfReplicates;
InventoryViability inventoryViability = new InventoryViability();
inventoryViability.setInventory(inventory);
inventoryViability.setInventoryViabilityRule(rule);
inventoryViability.setTestedDate(new Date());
inventoryViability.setTestedDateCode(CommunityCodeValues.DATE_FORMAT_DATE.value);
inventoryViability.setReplicationCount(numberOfReplicates);
inventoryViability.setTotalTestedCount(testQuantity);
// This will successfully crash without values
OrderRequestItem ori = new OrderRequestItem();
ori.setInventory(inventory);
ori.setQuantityShippedUnitCode(CommunityCodeValues.UNIT_OF_QUANTITY_SEED.value); // Always seed!
if (testQuantity != null) {
ori.setQuantityShipped(testQuantity.doubleValue());
ori.setNote(MessageFormat.format("{0,number,integer} replicate(s) of {1,number,integer} seed each = {2,number,integer} seed", numberOfReplicates, rule.getSeedsPerReplicate(), ori.getQuantityShipped()));
} else {
ori.setNote("Quantity not specified by Inventory Viability Rule");
}
orderRequestItems.add(ori);
// Start the CommunityCodeValues.INVENTORY_ACTION_VABILITYTEST action by setting the start date
InventoryActionService.InventoryActionRequest iar = InventoryActionService.InventoryActionRequest.builder()
.actionNameCode(CommunityCodeValues.INVENTORY_ACTION_VABILITYTEST.value)
.quantity(testQuantity == null ? null : testQuantity.doubleValue())
.quantityUnitCode(CommunityCodeValues.UNIT_OF_QUANTITY_SEED.value)
.formCode(CommunityCodeValues.GERMPLASM_FORM_SEED.value)
.id(Set.of(inventory.getId()))
.build();
var startedActions = inventoryActionService.startAction(iar);
log.info("Started {} action(s) for {}", startedActions.size(), iar.actionNameCode);
return inventoryViability;
}).collect(Collectors.toList());
// Save
inventoryViabilities = repository.saveAll(inventoryViabilities);
var orderRequest = new OrderRequest();
orderRequest.setOrderTypeCode(CommunityCodeValues.ORDER_REQUEST_TYPE_VIABILITYTEST.value);
orderRequest.setIntendedUseCode(CommunityCodeValues.ORDER_INTENDED_USE_OTHER.value);
orderRequest.setRequestorCooperator(viabilityCooperator);
orderRequest.setShipToCooperator(viabilityCooperator);
orderRequest.setFinalRecipientCooperator(viabilityCooperator);
orderRequest = orderRequestService.create(orderRequest, orderRequestItems);
return orderRequest;
}
@Override
protected void prepareLabelContext(Map<String, Object> context, InventoryViability entity) {
context.put("inventoryViability", entity);
context.put("inventory", entity.getInventory());
context.put("accession", entity.getInventory().getAccession());
context.put("taxon", entity.getInventory().getAccession().getTaxonomySpecies().getName());
context.put("inventoryViabilityRule", entity.getInventoryViabilityRule());
}
@Service
@Transactional(readOnly = true)
protected static class InventoryViabilityDataServiceImpl extends FilteredCRUDService2Impl<InventoryViabilityData, InventoryViabilityDataFilter, InventoryViabilityDataRepository> implements InventoryViabilityDataService {
@Autowired
private InventoryViabilityRepository inventoryViabilityRepository;
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', returnObject.inventoryViability.inventory.site)")
public InventoryViabilityData createFast(InventoryViabilityData source) {
assert(source.getId() == null);
// reload from database: for post-authorize
source.setInventoryViability(inventoryViabilityRepository.getReferenceById(source.getInventoryViability().getId()));
var saved = repository.save(source);
// Validate sum counts
validateSumOfCounts(saved);
return saved;
}
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', returnObject.inventoryViability.inventory.site)")
public InventoryViabilityData create(InventoryViabilityData source) {
assert(source.getId() == null);
// reload from database: for post-authorize
source.setInventoryViability(inventoryViabilityRepository.getReferenceById(source.getInventoryViability().getId()));
var saved = repository.save(source);
// Validate sum counts
validateSumOfCounts(saved);
return _lazyLoad(saved);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', #target.inventoryViability.inventory.site)")
public InventoryViabilityData updateFast(@NotNull @Valid InventoryViabilityData updated, InventoryViabilityData target) {
target.apply(updated);
var saved = repository.save(target);
// Validate sum counts
validateSumOfCounts(saved);
return saved;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', #target.inventoryViability.inventory.site)")
public InventoryViabilityData update(InventoryViabilityData input, InventoryViabilityData target) {
if (input == null) {
throw new InvalidApiUsageException("Source object must be provided.");
}
log.debug("Update InventoryViabilityData. Input data {}", input);
target.apply(input);
var saved = repository.save(target);
// Validate sum counts
validateSumOfCounts(saved);
return _lazyLoad(saved);
}
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'DELETE', returnObject.inventoryViability.inventory.site)")
public InventoryViabilityData remove(InventoryViabilityData entity) {
return super.remove(entity);
}
/**
* Load records from the database and check the sums
* @param sample InventoryViabilityData to use as sample
* @return true of OK
* @throws InvalidApiUsageException when the sums do not tally up
*/
private void validateSumOfCounts(InventoryViabilityData sample) {
var qIVD = QInventoryViabilityData.inventoryViabilityData;
var existingReplicateData = Lists.newArrayList(repository.findAll(
// match InventoryViablity and replicationNumber
qIVD.inventoryViability().eq(sample.getInventoryViability()).and(qIVD.replicationNumber.eq(sample.getReplicationNumber())),
// Sort by countNumber
qIVD.countNumber.desc()))
// Summarize (for replicate)
.stream().reduce(new InventoryViabilityData(), InventoryViabilityServiceImpl::summarizeForReplicate);
Integer replicationCount = existingReplicateData.getReplicationCount();
if (replicationCount == null) {
// nothing to validate
return;
}
if (existingReplicateData.sumOfCounts() > replicationCount.intValue()) {
throw new InvalidApiUsageException("Sum of counts exceeds replicationCount");
}
}
}
@Component
protected static class ActionSupport extends BaseActionSupport<InventoryViability, InventoryViabilityAction, InventoryViabilityActionFilter, InventoryViabilityActionRepository, InventoryViabilityActionRequest, InventoryViabilityActionScheduleFilter>
implements InventoryViabilityActionService {
@Autowired
private InventoryViabilityRepository inventoryViabilityRepository;
@Autowired
private MethodService methodService;
@Override
protected EntityPath<InventoryViability> getOwningEntityPath() {
return QInventoryViabilityAction.inventoryViabilityAction.inventoryViability();
}
@Override
protected void applyOwningEntityFilter(InventoryViabilityActionScheduleFilter filter, String owningEntityAlias, List<Predicate> predicates) {
QInventoryViability qInventoryViability = new QInventoryViability(owningEntityAlias);
if (predicates != null && filter.inventoryViability != null) {
predicates.addAll(filter.inventoryViability.collectPredicates(qInventoryViability));
}
}
@Override
protected InventoryViabilityAction createAction(final InventoryViability owningEntity) {
var action = new InventoryViabilityAction();
action.setInventoryViability(owningEntity);
return action;
}
@Override
protected void updateAction(final InventoryViabilityAction action, final InventoryViabilityActionRequest request) {
if (request.method != null && !request.method.isNew()) {
action.setMethod(methodService.get(request.method.getId()));
}
}
@Override
protected InventoryViabilityAction prepareNextWorkflowStepAction(WorkflowActionStep nextStep, InventoryViabilityAction completedAction) {
InventoryViabilityAction nextAction = new InventoryViabilityAction();
nextAction.setInventoryViability(new InventoryViability(completedAction.getInventoryViability().getId()));
return nextAction;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'ADMINISTRATION')")
public InventoryViabilityAction create(InventoryViabilityAction source) {
log.debug("Create InventoryViabilityAction. Input data {}", source);
InventoryViabilityAction entity = new InventoryViabilityAction();
entity.apply(source);
InventoryViabilityAction saved = get(repository.save(entity));
saved.lazyLoad();
return saved;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'ADMINISTRATION')")
public InventoryViabilityAction update(InventoryViabilityAction updated) {
return super.update(updated);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'ADMINISTRATION')")
public InventoryViabilityAction remove(InventoryViabilityAction entity) {
return super.remove(entity);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'ADMINISTRATION')")
public List<InventoryViabilityAction> reopenAction(InventoryViabilityActionRequest actionData) {
return super.reopenAction(actionData);
}
@Override
protected void initializeActionDetails(List<InventoryViabilityAction> actions) {
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'ADMINISTRATION')")
public List<InventoryViabilityAction> completeAction(InventoryViabilityActionRequest actionData) {
return super.completeAction(actionData);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'ADMINISTRATION')")
public List<InventoryViabilityAction> startAction(InventoryViabilityActionRequest actionData) {
return super.startAction(actionData);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'ADMINISTRATION')")
public List<InventoryViabilityAction> scheduleAction(InventoryViabilityActionRequest actionData) {
return super.scheduleAction(actionData);
}
@Override
protected Iterable<InventoryViability> findOwningEntities(final Set<Long> id) {
return inventoryViabilityRepository.findAll(QInventoryViability.inventoryViability.id.in(id));
}
}
/**
* A customized reducer that will not update the replicationCount if it is already set.
*/
private static InventoryViabilityData summarizeForReplicate(InventoryViabilityData summarized, InventoryViabilityData source) {
var keepReplicationCount = summarized.getReplicationCount();
var r = summarize(summarized, source);
if (keepReplicationCount != null) {
r.setReplicationCount(keepReplicationCount);
}
r.setReplicationNumber(source.getReplicationNumber());
return r;
}
/**
* Increment summarized *Count fields with values of the extra. The countDate is set to max(countDate)
* @param summarized summarized count
* @param extra increments
* @return updated summarized
*/
private static InventoryViabilityData summarize(InventoryViabilityData summarized, InventoryViabilityData extra) {
if (summarized.getCountDate() == null) {
summarized.setCountDate(extra.getCountDate());
} else if (extra.getCountDate() != null && extra.getCountDate().after(summarized.getCountDate())) {
summarized.setCountDate(extra.getCountDate());
}
summarized.setReplicationCount(Optional.ofNullable(summarized.getReplicationCount()).orElse(0) + Optional.ofNullable(extra.getReplicationCount()).orElse(0));
summarized.setNormalCount(Optional.ofNullable(summarized.getNormalCount()).orElse(0) + Optional.ofNullable(extra.getNormalCount()).orElse(0));
summarized.setAbnormalCount(Optional.ofNullable(summarized.getAbnormalCount()).orElse(0) + Optional.ofNullable(extra.getAbnormalCount()).orElse(0));
summarized.setUnknownCount(Optional.ofNullable(summarized.getUnknownCount()).orElse(0) + Optional.ofNullable(extra.getUnknownCount()).orElse(0));
// Dormant
summarized.setConfirmedDormantCount(Optional.ofNullable(summarized.getConfirmedDormantCount()).orElse(0) + Optional.ofNullable(extra.getConfirmedDormantCount()).orElse(0));
summarized.setDormantCount(Optional.ofNullable(summarized.getDormantCount()).orElse(0) + Optional.ofNullable(extra.getDormantCount()).orElse(0));
summarized.setEstimatedDormantCount(Optional.ofNullable(summarized.getEstimatedDormantCount()).orElse(0) + Optional.ofNullable(extra.getEstimatedDormantCount()).orElse(0));
summarized.setTreatedDormantCount(Optional.ofNullable(summarized.getTreatedDormantCount()).orElse(0) + Optional.ofNullable(extra.getTreatedDormantCount()).orElse(0));
// Categories of abnormal
summarized.setDeadCount(Optional.ofNullable(summarized.getDeadCount()).orElse(0) + Optional.ofNullable(extra.getDeadCount()).orElse(0));
summarized.setEmptyCount(Optional.ofNullable(summarized.getEmptyCount()).orElse(0) + Optional.ofNullable(extra.getEmptyCount()).orElse(0));
summarized.setHardCount(Optional.ofNullable(summarized.getHardCount()).orElse(0) + Optional.ofNullable(extra.getHardCount()).orElse(0));
summarized.setInfestedCount(Optional.ofNullable(summarized.getInfestedCount()).orElse(0) + Optional.ofNullable(extra.getInfestedCount()).orElse(0));
// Tetrazolium
summarized.setTzPositiveCount(Optional.ofNullable(summarized.getTzPositiveCount()).orElse(0) + Optional.ofNullable(extra.getTzPositiveCount()).orElse(0));
summarized.setTzNegativeCount(Optional.ofNullable(summarized.getTzNegativeCount()).orElse(0) + Optional.ofNullable(extra.getTzNegativeCount()).orElse(0));
return summarized;
}
/**
* We are generating one label for each replicate in the test.
*/
@Override
protected Collection<String> generateLabelsForEntity(String template, Map<String, Object> params, InventoryViability entity) {
var ivr = entity.getInventoryViabilityRule();
Integer replicates = entity.getReplicationCount() == null ? (ivr == null ? null : ivr.getNumberOfReplicates()) : entity.getReplicationCount();
if (replicates == null) replicates = 1;
var labelsForReplicates = new ArrayList<String>(replicates);
for (var i = 0; i < replicates; i++) {
params.put("replicate", i+1);
labelsForReplicates.add(templatingService.fillTemplate(template, params));
}
return labelsForReplicates;
}
}