InventoryServiceImpl.java
/*
* Copyright 2020 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 static org.gringlobal.model.community.CommunityAppSettings.BARCODE_INVENTORY;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.persistence.EntityManager;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import com.blazebit.persistence.CriteriaBuilderFactory;
import com.blazebit.persistence.querydsl.BlazeJPAQuery;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.model.filters.NumberFilter;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.api.exception.NotFoundElement;
import org.gringlobal.api.v1.Pagination;
import org.gringlobal.application.config.GGCESecurityConfig;
import org.gringlobal.component.GGCE;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.Accession;
import org.gringlobal.model.AccessionInvAttach;
import org.gringlobal.model.AppSetting;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.DateVersionEntityId.EntityIdAndModifiedDate;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.InventoryAction;
import org.gringlobal.model.InventoryExtra;
import org.gringlobal.model.InventoryMaintenancePolicy;
import org.gringlobal.model.InventoryQualityStatus;
import org.gringlobal.model.LazyLoading;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.OrderRequestItem;
import org.gringlobal.model.QAccession;
import org.gringlobal.model.QAccessionInvAttach;
import org.gringlobal.model.QInventory;
import org.gringlobal.model.QInventoryAction;
import org.gringlobal.model.QSiteCounter;
import org.gringlobal.model.SeedInventoryExtra;
import org.gringlobal.model.Site;
import org.gringlobal.model.TaxonomyGenus;
import org.gringlobal.model.TaxonomySpecies;
import org.gringlobal.model.TissueCultureExtra;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.model.community.SecurityAction;
import org.gringlobal.model.community.CommunityCodeValues.CodeValueDef;
import org.gringlobal.model.workflow.WorkflowActionStep;
import org.gringlobal.persistence.AccessionInvAttachRepository;
import org.gringlobal.persistence.AccessionInvNameRepository;
import org.gringlobal.persistence.AccessionRepository;
import org.gringlobal.persistence.InventoryActionRepository;
import org.gringlobal.persistence.InventoryExtraRepository;
import org.gringlobal.persistence.InventoryMaintenancePolicyRepository;
import org.gringlobal.persistence.InventoryQualityStatusRepository;
import org.gringlobal.persistence.InventoryRepository;
import org.gringlobal.persistence.InventoryRepositoryCustom;
import org.gringlobal.persistence.SiteRepository;
import org.gringlobal.service.AccessionInvGroupService;
import org.gringlobal.service.AppSettingsService;
import org.gringlobal.service.InventoryActionService;
import org.gringlobal.service.InventoryActionService.InventoryActionRequest;
import org.gringlobal.service.InventoryActionService.InventoryActionScheduleFilter;
import org.gringlobal.service.InventoryAttachmentService;
import org.gringlobal.service.InventoryAttachmentService.InventoryAttachmentRequest;
import org.gringlobal.service.InventoryExtraService;
import org.gringlobal.service.InventoryQualityStatusService;
import org.gringlobal.service.InventoryService;
import org.gringlobal.service.InvitroInventoryService;
import org.gringlobal.service.MethodService;
import org.gringlobal.service.OrderRequestService;
import org.gringlobal.service.TemplatingService;
import org.gringlobal.service.filter.AccessionFilter;
import org.gringlobal.service.filter.InventoryActionFilter;
import org.gringlobal.service.filter.InventoryFilter;
import org.gringlobal.service.filter.InventoryQualityStatusFilter;
import org.gringlobal.spring.TransactionHelper;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.security.access.AccessDeniedException;
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.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
import com.google.common.collect.Lists;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.jpa.impl.JPAQuery;
@Service
@Transactional(readOnly = true)
@Validated
@Slf4j
public class InventoryServiceImpl extends FilteredCRUDService2Impl<Inventory, InventoryFilter, InventoryRepository> implements InventoryService, InvitroInventoryService {
private static final String[] BOOST_FIELDS = { "inventoryNumber", "names.plantName", "accession.accessionNumber", "accession.taxonomySpecies.name" };
@Autowired
private InventoryMaintenancePolicyRepository inventoryMaintPolicyRepo;
@Autowired
private AccessionInvNameRepository accessionInvNameRepository;
@Autowired
private AccessionInvGroupService accessionInvGroupService;
@Autowired
private AccessionInvAttachRepository attachRepository;
@Autowired
private InventoryActionRepository inventoryActionRepository;
@Autowired
private AccessionRepository accessionRepository;
@Autowired
private OverviewHelper overviewHelper;
@Autowired
private AppSettingsService appSettingsService;
@Autowired
private TemplatingService templatingService;
@Autowired
private InventoryActionService inventoryActionService;
@Autowired
QuerydslPredicateExecutor<InventoryAction> actionFinder;
@Autowired
private EntityManager em;
@Autowired
private InventoryExtraService inventoryExtraService;
@Autowired
@Lazy
private OrderRequestService orderRequestService;
@Autowired
private GGCESecurityConfig.GgceSec ggceSec;
@Autowired
private InventoryAttachmentService attachmentService;
@Autowired
private SiteRepository siteRepository;
@Autowired
private CriteriaBuilderFactory criteriaBuilderFactory;
@Component
protected static class AttachmentSupport extends BaseAttachmentSupport<Inventory, AccessionInvAttach, InventoryAttachmentRequest> implements InventoryAttachmentService {
public AttachmentSupport() {
super(QAccessionInvAttach.accessionInvAttach.inventory().id, QAccessionInvAttach.accessionInvAttach.id);
}
/**
* Based on the original `inventory_attach_wizard_get_filepath` dataview.
*
* <ul>
* <li>AIA/</li>
* <li>${accession.taxonomySpecies.taxonomyGenus.genusName}</li>
* <li>Conditional: last 2 digits of accession ID: ${accession.id % 100} if there are over 2000 accessions of that `TaxonomyGenus`</li>
* <li>${accession.id}</li>
* </ul>
*
* @param inventory
* @return repository path for attachments
*/
@Override
protected Path createRepositoryPath(Inventory inventory) {
inventory = owningEntityRepository.getReferenceById(inventory.getId());
Hibernate.initialize(inventory.getAccession());
Accession accession = inventory.getAccession();
Hibernate.initialize(accession.getTaxonomySpecies());
TaxonomySpecies taxonomySpecies = accession.getTaxonomySpecies();
Hibernate.initialize(taxonomySpecies.getTaxonomyGenus());
TaxonomyGenus taxonomyGenus = taxonomySpecies.getTaxonomyGenus();
StringBuilder builder = new StringBuilder("/").append("AIA").append("/")
.append(taxonomyGenus.getGenusName()).append("/")
.append(accession.getId() % 100).append("/") // last 2 digits
.append(accession.getId()).append("/")
.append(inventory.getId());
return Paths.get(builder.toString());
}
@Override
protected AccessionInvAttach createAttach(Inventory entity, AccessionInvAttach source) {
AccessionInvAttach attach = new AccessionInvAttach();
attach.apply(source);
attach.setVirtualPath(source.getVirtualPath()); // SOAP uses this to create the record
attach.setInventory(entity);
return attach;
}
@Override
@Transactional
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('InventoryAttachment', 'CREATE', #entity.site)")
public AccessionInvAttach uploadFile(Inventory entity, MultipartFile file, InventoryAttachmentRequest metadata) throws IOException, InvalidRepositoryPathException,
InvalidRepositoryFileDataException {
return super.uploadFile(entity, file, metadata);
}
@Override
@Transactional
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('InventoryAttachment', 'DELETE', #entity.site)")
public AccessionInvAttach removeFile(Inventory entity, Long attachmentId) {
return super.removeFile(entity, attachmentId);
}
@Override
@Transactional
@PostAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('InventoryAttachment', 'CREATE', returnObject.inventory.site)")
public AccessionInvAttach create(AccessionInvAttach source) {
var inventory = owningEntityRepository.getReferenceById(source.getInventory().getId());
Hibernate.initialize(inventory.getSite()); // For permissions
var attach = createAttach(inventory, source);
var savedAttach = repository.save(attach);
return _lazyLoad(savedAttach);
}
@Override
@Transactional
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('InventoryAttachment', 'WRITE', #target.inventory.site)")
public AccessionInvAttach update(AccessionInvAttach updated, AccessionInvAttach target) {
target.apply(updated);
return _lazyLoad(repository.save(target));
}
@Override
@Transactional
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('InventoryAttachment', 'DELETE', #entity.inventory.site)")
public AccessionInvAttach remove(AccessionInvAttach entity) {
return super.remove(entity);
}
/**
* Utility to find attachments belonging to an inventory.
* Used by SOAP FilesEndpoint.
*/
@Override
public AccessionInvAttach findAttachment(Inventory inventory, Path filePath) {
return attachFinder.findOne(
QAccessionInvAttach.accessionInvAttach.inventory().eq(inventory)
.and(QAccessionInvAttach.accessionInvAttach.virtualPath.eq(filePath.toString()))
).orElse(null);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('InventoryAttachment', 'WRITE', #source.inventory.site)")
public void shareAttachment(AccessionInvAttach source, List<Inventory> inventories) {
List<AccessionInvAttach> createdAttachments = new ArrayList<>(inventories.size());
for (Inventory inventory : inventories) {
var attach = createAttach(inventory, source);
attach.setContentType(source.getContentType());
attach.setTitle(source.getTitle());
attach.setVirtualPath(source.getVirtualPath());
attach.setRepositoryFile(source.getRepositoryFile());
createdAttachments.add(attach);
}
attachRepository.saveAll(createdAttachments);
}
}
@Component
protected static class ActionSupport extends BaseActionSupport<Inventory, InventoryAction, InventoryActionFilter, InventoryActionRepository, InventoryActionRequest, InventoryActionScheduleFilter>
implements InventoryActionService {
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private MethodService methodService;
@Override
protected EntityPath<Inventory> getOwningEntityPath() {
return QInventoryAction.inventoryAction.inventory();
}
@Override
protected void initializeActionDetails(List<InventoryAction> actions) {
actions.forEach(action -> {
Hibernate.initialize(action.getInventory());
action.getInventory().lazyLoad();
});
}
@Override
protected void applyOwningEntityFilter(InventoryActionScheduleFilter filter, String owningEntityAlias, List<Predicate> predicates) {
QInventory qInventory = new QInventory(owningEntityAlias);
if (predicates != null && filter.inventory != null) {
predicates.addAll(filter.inventory.collectPredicates(qInventory));
}
}
@Override
protected InventoryAction createAction(Inventory owningEntity) {
InventoryAction action = new InventoryAction();
action.setInventory(owningEntity);
return action;
}
@Override
protected void updateAction(InventoryAction action, InventoryActionRequest request) {
action.setQuantity(request.quantity);
action.setQuantityUnitCode(request.quantityUnitCode);
action.setFormCode(request.formCode);
if (request.method != null && !request.method.isNew()) {
action.setMethod(methodService.get(request.method.getId()));
}
}
@Override
protected InventoryAction prepareNextWorkflowStepAction(WorkflowActionStep nextStep, InventoryAction completedAction) {
InventoryAction nextAction = new InventoryAction();
nextAction.setInventory(new Inventory(completedAction.getInventory().getId()));
return nextAction;
}
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('InventoryData', 'ADMINISTRATION', returnObject.inventory.site)")
public InventoryAction create(InventoryAction source) {
log.debug("Create InventoryAction. Input data {}", source);
InventoryAction inventoryAction = new InventoryAction();
inventoryAction.apply(source);
InventoryAction saved = repository.save(inventoryAction);
if (saved.getCompletedDate() != null) {
if (CommunityCodeValues.INVENTORY_ACTION_WITHDRAW.value.equals(saved.getActionNameCode())) {
var updatedInventory = reduceInventory(saved, inventoryRepository.getReferenceById(saved.getInventory().getId()));
saved.setInventory(updatedInventory);
}
}
return _lazyLoad(saved);
}
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('InventoryData', 'ADMINISTRATION', returnObject.inventory.site)")
public InventoryAction update(InventoryAction updated) {
return super.update(updated);
}
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('InventoryData', 'ADMINISTRATION', returnObject.inventory.site)")
public InventoryAction remove(InventoryAction entity) {
return super.remove(entity);
}
@Override
public InventoryAction update(InventoryAction updated, InventoryAction target) {
if (target.getCompletedDate() == null && updated.getCompletedDate() != null) {
if (CommunityCodeValues.INVENTORY_ACTION_WITHDRAW.value.equals(updated.getActionNameCode())) {
var updatedInventory = reduceInventory(updated, target.getInventory());
updated.setInventory(updatedInventory);
}
}
return super.update(updated, target);
}
@Override
protected Iterable<Inventory> findOwningEntities(Set<Long> id) {
return inventoryRepository.findAll(QInventory.inventory.id.in(id));
}
private Inventory reduceInventory(InventoryAction inventoryAction, Inventory inventory) {
assert(CommunityCodeValues.INVENTORY_ACTION_WITHDRAW.value.equals(inventoryAction.getActionNameCode()));
// if flagged as auto-deducted, reduce quantity on hand
if (!"Y".equals(inventory.getIsAutoDeducted())) {
return inventory;
} else {
if (!inventory.getQuantityOnHandUnitCode().equals(inventoryAction.getQuantityUnitCode())) {
throw new InvalidApiUsageException("Quantity Unit Code of the action does not match the unit code of inventory item.");
}
if (inventory.getQuantityOnHand() < inventoryAction.getQuantity()) {
throw new InvalidApiUsageException("Insufficient quantity on hand.");
} else {
// update quantity
inventory.setQuantityOnHand(inventory.getQuantityOnHand() - inventoryAction.getQuantity());
return inventoryRepository.save(inventory);
}
}
}
}
@Service
@Transactional(readOnly = true)
@Validated
protected static class InventoryQualityStatusServiceImpl extends FilteredCRUDServiceImpl<InventoryQualityStatus, InventoryQualityStatusFilter, InventoryQualityStatusRepository>
implements InventoryQualityStatusService {
@Override
public InventoryQualityStatus create(InventoryQualityStatus source) {
InventoryQualityStatus statusForSave = new InventoryQualityStatus();
statusForSave.apply(source);
return _lazyLoad(repository.save(statusForSave));
}
@Override
public InventoryQualityStatus update(InventoryQualityStatus updated, InventoryQualityStatus target) {
target.apply(updated);
return _lazyLoad(repository.save(target));
}
}
@Transactional
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
@Override
public int ensureSystemInventories() {
var accessionPath = QAccession.accession;
var subQuery = jpaQueryFactory.selectDistinct(accessionPath.id).from(accessionPath)
.where(accessionPath.inventories.any().formTypeCode.eq(Inventory.SYSTEM_INVENTORY_FTC));
var accessionsWithoutSystemInv = jpaQueryFactory.select(accessionPath.id).from(accessionPath)
.where(accessionPath.id.notIn(subQuery))
.fetch();
accessionRepository.findAllById(accessionsWithoutSystemInv).forEach(this::assureSystemInventory);
return accessionsWithoutSystemInv.size();
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('InventoryData', 'CREATE', #source.site)")
public Inventory createFast(Inventory source) {
return create(source);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('InventoryData', 'CREATE', #source.site)")
public Inventory create(Inventory source) {
Inventory saved = new Inventory();
saved.apply(source);
saved = repository.save(saved);
if (!saved.isSystemInventory() && StringUtils.isBlank(saved.getBarcode())) {
mintBarcode(saved);
saved = repository.save(saved);
}
if (source.getExtra() != null) {
var extra = source.getExtra();
extra.setInventory(saved);
var createdExtra = inventoryExtraService.create(extra);
saved.setExtra(createdExtra);
}
// Save
return saved;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('InventoryData', 'WRITE', #target.site)")
public Inventory update(Inventory input, Inventory target) {
if (target.isSystemInventory()) {
throw new InvalidApiUsageException("The system inventory cannot be modified.");
}
if (target.equals(input.getParentInventory())) {
throw new InvalidApiUsageException("Cannot set a parent inventory, a cyclical dependency was detected.");
}
target.apply(input);
if (input.getExtra() != null) {
if (target.getExtra() != null) {
target.getExtra().getId();
target.setExtra(inventoryExtraService.update(target.getExtra().apply(input.getExtra())));
} else {
var inputExtra = input.getExtra();
inputExtra.setInventory(target);
target.setExtra(inventoryExtraService.create(inputExtra));
};
}
return _lazyLoad(repository.save(target));
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('InventoryData', 'WRITE', #target.site)")
public Inventory updateFast(@NotNull @Valid Inventory updated, Inventory target) {
if (target.isSystemInventory()) {
throw new InvalidApiUsageException("The system inventory cannot be modified.");
}
if (target.equals(updated.getParentInventory())) {
throw new InvalidApiUsageException("Cannot set a parent inventory, a cyclical dependency was detected.");
}
target.apply(updated);
if (updated.getExtra() != null) {
if (target.getExtra() != null) {
target.getExtra().getId();
target.setExtra(inventoryExtraService.update(target.getExtra().apply(updated.getExtra())));
} else {
var inputExtra = updated.getExtra();
inputExtra.setInventory(target);
target.setExtra(inventoryExtraService.create(inputExtra));
};
}
return repository.save(target);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('InventoryData', 'DELETE', #entity.site)")
public Inventory remove(Inventory entity) {
if (entity.isSystemInventory()) {
throw new InvalidApiUsageException("System inventory cannot be removed");
}
return super.remove(entity);
}
@Override
@Transactional
// FIXME @PreAuthorize
public Inventory setInventoryQuantity(InventoryQuantityRequest inventoryQuantity) {
Inventory inventory = get(inventoryQuantity.id);
if (inventory.isSystemInventory()) {
throw new InvalidApiUsageException("Cannot set quantity for a SYSTEM inventory");
}
// Only update 100 seed weight if not null
if (inventoryQuantity.hundredSeedWeight != null) {
if (inventoryQuantity.hundredSeedWeight == 0 && inventory.getHundredSeedWeight() != null) {
// If 0, clear it
completeInventoryAction(inventory, CommunityCodeValues.INVENTORY_ACTION_100SEEDWEIGHT, "Cleared. Was " + inventory.getHundredSeedWeight());
inventory.setHundredSeedWeight(null);
} else if (inventoryQuantity.hundredSeedWeight != 0 && (inventory.getHundredSeedWeight() == null || BigDecimal.valueOf(inventory.getHundredSeedWeight()).compareTo(BigDecimal.valueOf(inventoryQuantity.hundredSeedWeight)) != 0)) {
// Otherwise update
completeInventoryAction(inventory, CommunityCodeValues.INVENTORY_ACTION_100SEEDWEIGHT, "100 seed weight set to " + inventoryQuantity.hundredSeedWeight + ". Was " + inventory.getHundredSeedWeight());
inventory.setHundredSeedWeight(inventoryQuantity.hundredSeedWeight);
}
}
inventory.setQuantityOnHand(inventoryQuantity.quantityOnHand);
inventory.setQuantityOnHandUnitCode(inventoryQuantity.quantityOnHandUnitCode);
inventory = repository.save(inventory);
completeInventoryAction(inventory, CommunityCodeValues.INVENTORY_ACTION_QUANTITYSET, inventoryQuantity.note);
return _lazyLoad(inventory);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('InventoryData', 'WRITE', #inventory.site)")
public String assignBarcode(Inventory inventory) {
inventory = get(inventory);
if (inventory.isSystemInventory()) {
throw new InvalidApiUsageException("System inventories do not have barcodes");
}
if (StringUtils.isNotBlank(inventory.getBarcode()))
return inventory.getBarcode(); // return existing barcode
mintBarcode(inventory);
repository.save(inventory);
return inventory.getBarcode();
}
/**
* Utility to mint barcode without loading or saving the inventory
*/
private void mintBarcode(Inventory inventory) {
AppSetting setting = appSettingsService.getSetting(BARCODE_INVENTORY.categoryTag, BARCODE_INVENTORY.name);
inventory.setBarcode(StringUtils.stripToNull(templatingService.fillTemplate(setting.getValue(), Map.of("inventory", inventory, "randomUUID", UUID.randomUUID()))));
}
@Override
@Transactional
public void discardMaterial(Map<Long, Integer> discardQuantities) {
for (var entry : discardQuantities.entrySet()) {
Inventory inventory = get(entry.getKey());
if (inventory.isSystemInventory())
throw new InvalidApiUsageException("Cannot update quantity for a SYSTEM inventory");
int discardQuantity = entry.getValue();
if (inventory.getQuantityOnHand() < discardQuantity)
throw new InvalidApiUsageException("Refusing to discard " + discardQuantity + " items, the current quantity on hand is " + inventory.getQuantityOnHand());
inventory.setQuantityOnHand(inventory.getQuantityOnHand() - discardQuantity);
var savedInventory = repository.save(inventory);
addCompletedInventoryAction(savedInventory, CommunityCodeValues.INVENTORY_ACTION_DISCARD, (action) -> {
action.setQuantity((double) discardQuantity);
action.setQuantityUnitCode(savedInventory.getQuantityOnHandUnitCode());
action.setFormCode(savedInventory.getFormTypeCode());
action.setNote("Discarded " + discardQuantity + " " + inventory.getQuantityOnHandUnitCode());
});
}
}
@Override
public Page<InventoryRepositoryCustom.AggregatedInventoryQuantity> aggregateQuantity(InventoryFilter filter, Pageable page) {
if (filter.isFulltextQuery())
throw new InvalidApiUsageException("Elasticsearch filters not supported.");
var resultPage = repository.aggregateQuantity(filter.buildPredicate(), page);
resultPage.stream().forEach(e -> e.accession = accessionRepository.findById(e.accession.getId()).orElseThrow(() -> {
// should not happen
throw new NotFoundElement("No such accession");
}));
return resultPage;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('SplitInventory', 'CREATE')")
public List<Inventory> splitInventory(SplitInventoryRequest splitInventoryRequest) {
var splitSource = splitInventoryRequest.sourceSplitInventory;
Inventory source = get(splitSource.id, splitSource.modifiedDate);
if (source.isSystemInventory()) {
throw new InvalidApiUsageException("Cannot split a system inventory");
}
if (splitSource.containerTypeCode != null) {
source.setContainerTypeCode(splitSource.containerTypeCode);
source = update(source); // updated source inventory
}
if (splitSource.quantityOnHand != null) {
var quantityUpdate = new InventoryQuantityRequest();
quantityUpdate.id = source.getId();
quantityUpdate.quantityOnHand = splitSource.quantityOnHand.doubleValue();
quantityUpdate.quantityOnHandUnitCode = splitSource.quantityOnHandUnitCode;
quantityUpdate.note = splitSource.note;
source = setInventoryQuantity(quantityUpdate);
}
// New inventories
var parentInventory = source;
var parentExtra = parentInventory.getExtra();
List<Inventory> inventoriesForSave = new ArrayList<>();
splitInventoryRequest.splits.forEach(splitRequest -> {
var splitInventory = new Inventory();
splitInventory.apply(parentInventory); // Copy from source
splitInventory.setBarcode(null);
splitInventory.setStorageLocationPart1(null);
splitInventory.setStorageLocationPart2(null);
splitInventory.setStorageLocationPart3(null);
splitInventory.setStorageLocationPart4(null);
splitInventory.setBackupInventory(null);
if (splitRequest.inventoryMaintenancePolicy != null) {
if (splitRequest.inventoryMaintenancePolicy.id == null) {
throw new InvalidApiUsageException("InventoryMaintenancePolicy.id not specified");
}
splitInventory.applyInventoryMaintenancePolicy(inventoryMaintPolicyRepo.getReferenceById(splitRequest.inventoryMaintenancePolicy.id));
} else {
splitInventory.applyInventoryMaintenancePolicy(parentInventory.getInventoryMaintenancePolicy());
}
splitInventory.setParentInventory(parentInventory); // Set parent
splitInventory.setQuantityOnHand(splitRequest.quantityOnHand); // Optional quantity
splitInventory.setQuantityOnHandUnitCode(StringUtils.defaultIfBlank(splitRequest.quantityOnHandUnitCode, parentInventory.getQuantityOnHandUnitCode())); // Set unit code
splitInventory.setContainerTypeCode(splitRequest.containerTypeCode);
splitInventory.setInventoryNumberPart1(splitRequest.inventoryNumberPart1);
splitInventory.setInventoryNumberPart2(splitRequest.inventoryNumberPart2);
splitInventory.setInventoryNumberPart3(splitRequest.inventoryNumberPart3);
splitInventory.setNote(splitRequest.note);
splitInventory.setAvailabilityStatusCode(CommunityCodeValues.INVENTORY_AVAILABILITY_NOTSET.value);
var savedSplit = repository.save(splitInventory);
if (parentExtra != null) {
var splitInventoryExtra = parentExtra.copy();
if (splitInventoryExtra != null) { // We have a copy
splitInventoryExtra.setInventory(splitInventory);
inventoryExtraService.create(splitInventoryExtra);
}
}
inventoriesForSave.add(savedSplit);
});
// Assign barcodes to new inventories
inventoriesForSave.forEach(this::mintBarcode);
// Record the SPLIT action on source inventory
completeInventoryAction(parentInventory, CommunityCodeValues.INVENTORY_ACTION_SPLIT, splitSource.note);
return repository.saveAll(inventoriesForSave); // Do a final save (for barcodes)
}
@Override
@Transactional
public List<Inventory> assignLocation(AssignLocationRequest assignLocationRequest) {
var location = assignLocationRequest.location;
if (location == null || location.siteId == null) {
throw new InvalidApiUsageException("Location must be provided");
}
if (CollectionUtils.isEmpty(assignLocationRequest.inventories)) {
throw new InvalidApiUsageException("Inventories must be provided");
}
var site = siteRepository.findById(location.siteId).orElseThrow(() -> new NotFoundElement("No site with id=" + location.siteId));
if (!ggceSec.actionAllowed(SecurityAction.InventoryData.name(), "WRITE", site)) {
throw new AccessDeniedException("Don't have permission to assign inventories to this site");
}
var inventoriesForAssign = findByIdAndVersion(assignLocationRequest.inventories);
if (inventoriesForAssign.size() < assignLocationRequest.inventories.size()) {
throw new NotFoundElement("Some inventories not found");
}
var hasUnavailableSite = getHasUnavailableSite(inventoriesForAssign, SecurityAction.InventoryData, "WRITE");
if (hasUnavailableSite) {
throw new AccessDeniedException("Don't have permission to change location of the selected inventories");
}
for (Inventory inventory : inventoriesForAssign) {
inventory.setSite(site);
inventory.setStorageLocationPart1(location.storageLocationPart1);
inventory.setStorageLocationPart2(location.storageLocationPart2);
inventory.setStorageLocationPart3(location.storageLocationPart3);
inventory.setStorageLocationPart4(location.storageLocationPart4);
}
return repository.saveAll(inventoriesForAssign);
}
@Override
@Transactional
public List<Inventory> assignLocations(List<AssignLocationRequest> assignLocationRequests) {
return assignLocationRequests.stream()
.map(this::assignLocation)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
@Override
public Inventory getByBarcode(String barcode) {
return repository.findOne(QInventory.inventory.barcode.eq(barcode)).orElseThrow(() -> new NotFoundElement("Inventory not found by barcode: " + barcode));
}
private Collection<Inventory> findByIdAndVersion(Set<EntityIdAndModifiedDate> inventories) {
assert(CollectionUtils.isNotEmpty(inventories));
BooleanBuilder builder = new BooleanBuilder();
for (var i : inventories) {
builder.orAllOf(QInventory.inventory.id.eq(i.id), QInventory.inventory.modifiedDate.eq(i.modifiedDate));
}
return (Collection<Inventory>) repository.findAll(builder);
}
private void completeInventoryAction(Inventory inventory, CodeValueDef actionCodeValue, String note) {
addCompletedInventoryAction(inventory, actionCodeValue, (action) -> {
action.setNote(note);
// Update action with inventory data
action.setFormCode(inventory.getFormTypeCode());
action.setQuantity(inventory.getQuantityOnHand());
action.setQuantityUnitCode(inventory.getQuantityOnHandUnitCode());
});
}
private void addCompletedInventoryAction(Inventory inventory, CodeValueDef actionCodeValue, Consumer<InventoryAction> customizer) {
var nowDate = Instant.now();
BooleanExpression expression = QInventoryAction.inventoryAction.inventory().id.in(inventory.getId())
.and(QInventoryAction.inventoryAction.completedDate.isNull())
.and(QInventoryAction.inventoryAction.notBeforeDate.isNull().or(QInventoryAction.inventoryAction.notBeforeDate.loe(nowDate)))
.and(QInventoryAction.inventoryAction.actionNameCode.eq(actionCodeValue.value));
var pendingActions = StreamSupport.stream(actionFinder.findAll(expression).spliterator(), false)
.peek(action -> {
action.setCompletedDate(nowDate);
action.setCompletedDateCode(CommunityCodeValues.DATE_FORMAT_DATETIME.value);
if (customizer != null) {
customizer.accept(action);
}
}).collect(Collectors.toList());
var result = inventoryActionService.update(pendingActions);
if (result.success.size() > 0) {
return;
}
InventoryAction quantityAction = new InventoryAction();
quantityAction.setActionNameCode(actionCodeValue.value);
quantityAction.setInventory(inventory);
quantityAction.setCompletedDate(nowDate);
quantityAction.setCompletedDateCode(CommunityCodeValues.DATE_FORMAT_DATETIME.value);
quantityAction.setStartedDate(quantityAction.getCompletedDate());
quantityAction.setStartedDateCode(quantityAction.getCompletedDateCode());
if (customizer != null) {
customizer.accept(quantityAction);
}
inventoryActionRepository.save(quantityAction);
}
//
// @Override
// public Page<InventoryAction> listInventoriesWithAction(InventoryActionFilter actionFilter, InventoryFilter inventoryFilter, Pageable page) {
//
// var qInvA = QInventoryAction.inventoryAction;
// QInventory qInv = new QInventory("inventory");
// BooleanBuilder predicate = new BooleanBuilder();
//
// if (actionFilter != null) {
// predicate.and(ExpressionUtils.allOf(actionFilter.collectPredicates(qInvA)));
// }
// if (inventoryFilter != null) {
// predicate.and(ExpressionUtils.allOf(inventoryFilter.collectPredicates(qInv)));
// }
//
// var query = jpaQueryFactory.selectFrom(qInvA)
// // join
// .innerJoin(qInvA.inventory, qInv).fetchJoin();
//
// query = joinDetails(query, qInv)
// // where
// .where(predicate);
//
// query.select(qInvA);
//
// // get total elements
// var totalElements = query.fetchCount();
//
// // apply pagination
// Querydsl querydsl = new Querydsl(entityManager, new PathBuilder<>(qInvA.getType(), qInvA.getMetadata()));
// querydsl.applyPagination(page, query);
//
// var content = query.fetch();
//
// return new PageImpl<>(content, page, totalElements);
// }
@Override
public Page<Inventory> list(InventoryFilter filter, Pageable page) throws SearchException {
return list(Inventory.class, filter, page, BOOST_FIELDS);
}
@Override
protected Page<Inventory> list(Class<Inventory> clazz, InventoryFilter filter, Pageable page, String... boostFields) throws SearchException {
page = Pagination.addSortByParams(page, idSortParams);
// if (ftf.isFulltextQuery() && elasticsearchService != null) { // This could be quietly ignoring full-test search
if (filter != null && filter.isFulltextQuery()) {
return elasticsearchService.findAll(clazz, filter, null, page, (entityIds) -> list(entityIds), boostFields);
}
BooleanBuilder predicate = new BooleanBuilder();
if (filter != null) {
predicate.and(filter.buildPredicate());
}
if (entityListQuery() != null) {
JPAQuery<Inventory> query = entityListQuery().where(predicate);
var inventoryExtraProp = QInventory.inventory.extra().getMetadata().getName().concat(".");
var hasExtraSorts = page.getSort().stream().anyMatch(sort -> sort.getProperty().startsWith(inventoryExtraProp));
if (hasExtraSorts) {
var queryDsl = new Querydsl(entityManager, new PathBuilder<Inventory>(QInventory.inventory.getType(), QInventory.inventory.getMetadata()));
query.offset(page.getOffset());
query.limit(page.getPageSize());
applyOrderWithExtra(query, page, inventoryExtraProp, queryDsl);
Long total = query.fetchCount();
return PageableExecutionUtils.getPage(query.fetch(), page, total::longValue);
}
return repository.findAll(query, page);
} else {
// default implementation without custom loading
return repository.findAll(predicate, page);
}
}
private void applyOrderWithExtra(JPQLQuery<Inventory> query, Pageable page, String inventoryExtraProp, Querydsl queryDsl) {
for (Sort.Order order : page.getSort()) {
if (order.getProperty().startsWith(inventoryExtraProp)) {
var extraOrderPath = Expressions.path(Comparable.class, QInventory.inventory, order.getProperty());
query.orderBy(new OrderSpecifier<Comparable>(
order.isAscending() ? com.querydsl.core.types.Order.ASC : com.querydsl.core.types.Order.DESC, extraOrderPath)
);
} else {
queryDsl.applySorting(Sort.by(order), query);
}
}
}
@Override
protected JPAQuery<Inventory> entityListQuery() {
return joinDetails(jpaQueryFactory.selectFrom(QInventory.inventory), QInventory.inventory);
}
private <T> JPAQuery<T> joinDetails(JPAQuery<T> query, QInventory inventory) {
QAccession aliasAccession = new QAccession("a");
return query
// site
.join(inventory.site()).fetchJoin()
// acce
.join(inventory.accession(), aliasAccession).fetchJoin() // Needs alias because of fetchJoin
// maint pol
.join(inventory.inventoryMaintenancePolicy()).fetchJoin()
// species
.join(aliasAccession.taxonomySpecies()).fetchJoin()
// extra
.leftJoin(inventory.extra()).fetchJoin()
// production location geography
.leftJoin(inventory.productionLocationGeography()).fetchJoin()
;
}
@Override
protected NumberPath<Long> entityIdPredicate() {
return QInventory.inventory.id;
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
@PreAuthorize("@ggceSec.actionAllowed('PassportData', 'CREATE', #accession.site)")
public Inventory assureSystemInventory(Accession accession) {
log.debug("Assuring a SYSTEM inventory for new accession {}", accession.getId());
final Inventory inventory = new Inventory();
inventory.setAccession(accession);
inventory.setInventoryNumberPart1(accession.getAccessionNumberPart1());
inventory.setInventoryNumberPart2(accession.getAccessionNumberPart2());
inventory.setInventoryNumberPart3(accession.getAccessionNumberPart3());
inventory.setFormTypeCode(Inventory.SYSTEM_INVENTORY_FTC);
inventory.setSite(accession.getSite());
final InventoryMaintenancePolicy inventoryMaintenancePolicy = inventoryMaintPolicyRepo.getSystemMaintenancePolicy();
inventory.setInventoryMaintenancePolicy(inventoryMaintenancePolicy);
inventory.setIsDistributable("N");
inventory.setIsAvailable("N");
inventory.setAvailabilityStatusCode(CommunityCodeValues.INVENTORY_AVAILABILITY_NOTSET.value);
inventory.setIsAutoDeducted("N");
inventory.setNote("Default Association Record for Accession -> Inventory");
final var now = Instant.now();
inventory.setOwnedBy(accession.getOwnedBy());
inventory.setOwnedDate(now);
inventory.setSite(accession.getSite());
// inventory.setCreatedBy(accession.getCreatedBy());
// inventory.setCreatedDate(now);
return repository.save(inventory);
}
@Override
public InventoryDetails getInventoryDetails(Inventory inventory) {
if (inventory == null) {
throw new NotFoundElement();
}
inventory = this.reload(inventory);
inventory.lazyLoad();
// initialize lazy data
Hibernate.initialize(inventory.getActions());
Hibernate.initialize(inventory.getViability());
Hibernate.initialize(inventory.getQuality());
Hibernate.initialize(inventory.getInventoryMaintenancePolicy());
Hibernate.initialize(inventory.getParentInventory());
Hibernate.initialize(inventory.getProductionLocationGeography());
InventoryDetails inventoryDetails = new InventoryDetails();
inventoryDetails.inventory = inventory;
inventoryDetails.actions = inventory.getActions();
inventoryDetails.viability = inventory.getViability();
if (inventoryDetails.viability != null) {
inventoryDetails.viability.forEach((viab) -> {
if (viab.getInventoryViabilityRule() != null)
viab.getInventoryViabilityRule().getId();
});
}
inventoryDetails.qualityStatus = inventory.getQuality();
if (inventoryDetails.qualityStatus != null) {
inventoryDetails.qualityStatus.forEach(LazyLoading::lazyLoad);
}
inventoryDetails.names = accessionInvNameRepository.findInventoryNames(inventory);
inventoryDetails.attachments = attachRepository.findInventoryAttachments(inventory);
inventoryDetails.attachments.forEach(accessionInvAttach -> {
if (accessionInvAttach.getAttachCooperator() != null) {
accessionInvAttach.getAttachCooperator().getId();
}
});
inventoryDetails.groups = accessionInvGroupService.listInventoryGroups(inventory);
return inventoryDetails;
}
@Override
public Map<Object, Number> inventoryOverview(String groupBy, InventoryFilter filter) {
return overviewHelper.getOverview(Inventory.class, QInventory.inventory, QInventory.inventory.id.countDistinct(), groupBy, filter);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
public void recalculateAllInventoryNumbers() {
boolean lastPage = false;
int pageNumber = 0;
do {
Page<Inventory> page = repository.findAll(PageRequest.of(pageNumber, 1000));
lastPage = page.isLast();
pageNumber += 1;
log.warn("Updating inventoryNumber for {} records, page {}", page.getNumberOfElements(), pageNumber);
Lists.partition(page.getContent(), 100).forEach(batch -> TransactionHelper.executeInTransaction(false, () -> {
batch.forEach(i -> repository.setInventoryNumber(i.getId(), GGCE.inventoryNumber(i)));
return true;
}));
// Clear anything cached in the entity manager
em.clear();
} while (!lastPage);
log.info("Done.");
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
public void assignMissingInventoryNumbers() {
boolean lastPage = false;
int pageNumber = 0;
do {
Page<Inventory> page = repository.findAll(QInventory.inventory.inventoryNumber.isNull(), PageRequest.of(0, 1000));
lastPage = page.isLast();
pageNumber++;
log.warn("Assigning inventoryNumber for {} records, page {}", page.getNumberOfElements(), pageNumber);
Lists.partition(page.getContent(), 100).forEach(batch -> TransactionHelper.executeInTransaction(false, () -> {
batch.forEach(a -> repository.setInventoryNumber(a.getId(), GGCE.inventoryNumber(a)));
return true;
}));
// Clear anything cached in the entity manager
em.clear();
} while (!lastPage && pageNumber < 5000); // at most 5000 loops
log.info("Done.");
}
@Service
@Transactional(readOnly = true)
@Validated
protected static class InventoryExtraServiceImpl extends CRUDServiceImpl<InventoryExtra, InventoryExtraRepository>
implements InventoryExtraService {
@Override
public InventoryExtra create(InventoryExtra source) {
return make(source);
}
@Override
public <T extends InventoryExtra> T make(T extra) {
var inventory = extra.getInventory();
if (inventory == null || inventory.getId() == null) {
throw new InvalidApiUsageException("Inventory must be provided");
}
return repository.save(extra);
}
@Override
public InventoryExtra update(InventoryExtra updated, InventoryExtra target) {
return repository.save(target.apply(updated));
}
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Invitro', 'CREATE', #request.sourceInventory.site)")
public List<Inventory> invitroStart(IVMultiplicationRequest request) {
assert (request.multiplicationItems != null && !request.multiplicationItems.isEmpty());
request.sourceInventory = get(request.sourceInventory.getId());
if (! Objects.equals("Y", request.sourceInventory.getIsAutoDeducted())) {
// Do not deduct from source (grin-global/grin-global-server#488)
} else if (request.sourceInventory.getQuantityOnHand() == null || request.sourceInventory.getQuantityOnHand() == 0 || request.sourceInventory.getQuantityOnHand() < request.quantityToDeduct) {
throw new InvalidApiUsageException("Source inventory does not have sufficient quantity"); // Require quantity
}
var createdList = new ArrayList<Inventory>(request.multiplicationItems.size());
for (IVMultiplicationItem item : request.multiplicationItems) {
if (item.inventoryMaintenancePolicy == null || item.inventoryMaintenancePolicy.getId() == null) {
throw new InvalidApiUsageException("Inventory maintenance policy id is empty");
}
item.inventoryMaintenancePolicy = inventoryMaintPolicyRepo.getReferenceById(item.inventoryMaintenancePolicy.getId());
var inv = new Inventory();
inv.setParentInventory(request.sourceInventory);
inv.setAccession(request.sourceInventory.getAccession());
inv.setFormTypeCode(StringUtils.defaultIfBlank(item.formTypeCode, CommunityCodeValues.GERMPLASM_FORM_INVITRO.value));
inv.setInventoryMaintenancePolicy(item.inventoryMaintenancePolicy);
inv.setInventoryNumberPart1(StringUtils.isBlank(item.inventoryNumberPart1) ? inv.getAccession().getAccessionNumber() : item.inventoryNumberPart1);
inv.setInventoryNumberPart2(-1L); // Auto-increment
inv.setInventoryNumberPart3(item.inventoryNumberPart3); // Not sure about this one...
inv.setQuantityOnHand(item.quantityOnHand == null ? 1.0d : item.quantityOnHand);
inv.setQuantityOnHandUnitCode(item.quantityOnHandUnitCode);
inv.setIsAutoDeducted(StringUtils.defaultIfBlank(item.isAutoDeducted, "Y")); // Make IV inventory auto-deducted when doing first introduction
inv.setContainerTypeCode(item.containerTypeCode);
inv.setAvailabilityStatusCode(CommunityCodeValues.INVENTORY_AVAILABILITY_NOTSET.value);
inv.setSite(request.sourceInventory.getSite());
inv.setPropagationDate(new Date());
inv.setPropagationDateCode(CommunityCodeValues.DATE_FORMAT_DATE.value);
inv.setPathogenStatusCode(request.sourceInventory.getPathogenStatusCode()); // Inherit from source
inv.setGeneration(request.sourceInventory.nextGeneration()); // Increment generation
if (inv.getGeneration() == null) inv.setGeneration(1L); // Set 1st generation
var created = create(inv);
applyExtraFromRequest(created, item);
createdList.add(created);
}
if (Objects.equals("Y", request.sourceInventory.getIsAutoDeducted())) {
// Consume source inventory
request.sourceInventory.setQuantityOnHand(request.sourceInventory.getQuantityOnHand() - request.quantityToDeduct);
request.sourceInventory = update(request.sourceInventory);
}
return createdList;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Invitro', 'CREATE', #request.sourceInventory.site)")
public List<Inventory> invitroMultiply(IVMultiplicationRequest request) {
assert (request.multiplicationItems != null && !request.multiplicationItems.isEmpty());
request.sourceInventory = get(request.sourceInventory.getId());
if (! Objects.equals("Y", request.sourceInventory.getIsAutoDeducted())) {
// Do not deduct from source (grin-global/grin-global-server#488)
} else if (request.sourceInventory.getQuantityOnHand() == null || request.sourceInventory.getQuantityOnHand() == 0 || request.sourceInventory.getQuantityOnHand() < request.quantityToDeduct) {
throw new InvalidApiUsageException("Source inventory does not have sufficient quantity"); // Require quantity
}
var createdList = new ArrayList<Inventory>();
for (IVMultiplicationItem item : request.multiplicationItems) {
item.formTypeCode = item.formTypeCode != null ? item.formTypeCode : request.sourceInventory.getFormTypeCode();
item.inventoryMaintenancePolicy = item.inventoryMaintenancePolicy != null && item.inventoryMaintenancePolicy.getId() != null ?
inventoryMaintPolicyRepo.getReferenceById(item.inventoryMaintenancePolicy.getId()) : request.sourceInventory.getInventoryMaintenancePolicy();
item.inventoryNumberPart3 = item.inventoryNumberPart3 != null ? item.inventoryNumberPart3 : request.sourceInventory.getInventoryNumberPart3();
item.quantityOnHandUnitCode = item.quantityOnHandUnitCode != null ? item.quantityOnHandUnitCode : request.sourceInventory.getQuantityOnHandUnitCode();
item.containerTypeCode = item.containerTypeCode != null ? item.containerTypeCode : request.sourceInventory.getContainerTypeCode();
var inv = new Inventory();
inv.setParentInventory(request.sourceInventory);
inv.setAccession(request.sourceInventory.getAccession());
inv.setFormTypeCode(StringUtils.defaultIfBlank(item.formTypeCode, CommunityCodeValues.GERMPLASM_FORM_INVITRO.value));
inv.setInventoryMaintenancePolicy(item.inventoryMaintenancePolicy);
inv.setInventoryNumberPart1(StringUtils.isBlank(item.inventoryNumberPart1) ?
StringUtils.joinWith(".", request.sourceInventory.getInventoryNumberPart1(), request.sourceInventory.getInventoryNumberPart2())
: item.inventoryNumberPart1);
inv.setInventoryNumberPart2(-1L); // Auto-increment
inv.setInventoryNumberPart3(item.inventoryNumberPart3); // Not sure about this one...
inv.setQuantityOnHand(item.quantityOnHand == null ? 1.0d : item.quantityOnHand);
inv.setQuantityOnHandUnitCode(item.quantityOnHandUnitCode);
inv.setIsAutoDeducted(StringUtils.defaultIfBlank(item.isAutoDeducted, request.sourceInventory.getIsAutoDeducted())); // Use parent auto-deducted in multiplication
inv.setContainerTypeCode(item.containerTypeCode);
inv.setAvailabilityStatusCode(CommunityCodeValues.INVENTORY_AVAILABILITY_NOTSET.value);
inv.setSite(request.sourceInventory.getSite());
inv.setPropagationDate(new Date());
inv.setPropagationDateCode(CommunityCodeValues.DATE_FORMAT_DATE.value);
inv.setPathogenStatusCode(request.sourceInventory.getPathogenStatusCode()); // Inherit from source
inv.setGeneration(request.sourceInventory.nextGeneration()); // Increment generation
var created = create(inv);
applyExtraFromRequest(created, item);
createdList.add(created);
}
if (Objects.equals("Y", request.sourceInventory.getIsAutoDeducted())) {
// Consume source inventory
request.sourceInventory.setQuantityOnHand(request.sourceInventory.getQuantityOnHand() - request.quantityToDeduct);
request.sourceInventory = update(request.sourceInventory);
}
return createdList;
}
// private List<Inventory> applyExtraFromRequest(List<Inventory> inventoryList, IVMultiplicationItem request) {
// // Apply medium
// if (request.medium != null) {
// var newExtras = new ArrayList<InventoryExtra>();
// inventoryList.forEach(inventory -> {
// TissueCultureExtra extra = new TissueCultureExtra();
// extra.setInventory(inventory);
// extra.setMedium(request.medium);
// newExtras.add(extra);
// });
// var createdExtras = inventoryExtraService.create(newExtras).success;
// inventoryList = inventoryList.stream().peek(inventory -> {
// var invExtra = createdExtras.stream().filter(extra -> Objects.equals(inventory.getId(), extra.getInventory().getId())).findFirst().orElse(null);
// inventory.setExtra(invExtra);
// }).collect(Collectors.toList());
// }
// return inventoryList;
// }
private void applyExtraFromRequest(Inventory inventory, IVMultiplicationItem request) {
// Apply medium
if (request.medium != null) {
TissueCultureExtra extra = new TissueCultureExtra();
extra.setInventory(inventory);
extra.setMedium(request.medium);
var createdExtra = inventoryExtraService.create(extra);
inventory.setExtra(createdExtra);
}
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('InventoryData', 'ADMINISTRATION', #site)")
public OrderRequest multiplicationOrder(Site site, Set<Long> inventoryIds, Cooperator multiplicationCooperator, String orderType, String intendedUseCode) {
var inventories = Lists.newArrayList(repository.findAll(QInventory.inventory.id.in(inventoryIds)));
final var orderRequestItems = new ArrayList<OrderRequestItem>(inventories.size());
var sequenceNumber = new AtomicInteger(1);
inventories.forEach(inventory -> {
OrderRequestItem ori = new OrderRequestItem();
ori.setInventory(inventory);
ori.setSequenceNumber(sequenceNumber.getAndIncrement());
ori.setQuantityShippedUnitCode(inventory.getDistributionUnitCode());
ori.setDistributionFormCode(inventory.getDistributionDefaultFormCode());
ori.setQuantityShipped(inventory.getDistributionDefaultQuantity());
orderRequestItems.add(ori);
// Start the CommunityCodeValues.INVENTORY_ACTION_MULTIPLICATION action by setting the start date
InventoryActionService.InventoryActionRequest iar = InventoryActionService.InventoryActionRequest.builder()
.actionNameCode(CommunityCodeValues.INVENTORY_ACTION_MULTIPLICATION.value)
.quantity(null)
.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);
});
var orderRequest = new OrderRequest();
orderRequest.setOrderTypeCode(orderType);
orderRequest.setIntendedUseCode(intendedUseCode);
orderRequest.setRequestorCooperator(multiplicationCooperator);
orderRequest.setShipToCooperator(multiplicationCooperator);
orderRequest.setFinalRecipientCooperator(multiplicationCooperator);
orderRequest = orderRequestService.create(orderRequest, orderRequestItems);
return orderRequest;
}
@Override
@Transactional
public void shareAttachment(Long attachId, List<Long> inventoryIds) {
var inventories = repository.findAll(QInventory.inventory.id.in(inventoryIds), Pageable.unpaged()).getContent();
var hasUnavailableSite = getHasUnavailableSite(inventories, SecurityAction.InventoryAttachment, "CREATE");
if (hasUnavailableSite) {
throw new AccessDeniedException("Don't have permission to create attachments for the selected inventories");
}
var attachment = attachRepository.findById(attachId).orElseThrow(() -> new NotFoundElement("No such attach for provided id"));
attachmentService.shareAttachment(attachment, inventories);
}
/**
* Find sites where permission is not granted.
*/
private boolean getHasUnavailableSite(Collection<Inventory> inventories, SecurityAction action, String permission) {
var hasUnavailableSite = inventories.stream()
.map(Inventory::getSite)
.distinct()
.anyMatch(site -> !ggceSec.actionAllowed(action.name(), permission, site));
return hasUnavailableSite;
}
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('InventoryData', 'WRITE', returnObject.inventory.site)")
public SeedInventoryExtra updateMoistureContent(SeedInventoryExtra extra, MoistureContentRequest request) {
assert (extra != null && extra.getId() != null);
SeedInventoryExtra target = (SeedInventoryExtra) inventoryExtraService.get(extra.getId());
target.setMoistureContent(request.moistureContent);
target.setMoistureContentDate(request.moistureContentDate);
target.setMoistureContentDateCode(request.moistureContentDateCode);
return (SeedInventoryExtra) inventoryExtraService.update(target);
}
@Override
public Page<ComparedSitesResponse> compareSites(AccessionFilter filter, Map<Long, NumberFilter<Long>> siteItems, Pageable page) {
// (Site, NumberFilter) entries
var siteAndFilters = siteRepository.findAllById(siteItems.keySet()).stream()
.map(site -> Map.entry(site, siteItems.get(site.getId())))
.collect(Collectors.toList());
if (siteAndFilters.size() != siteItems.size()) {
throw new IllegalArgumentException("Some of the provided sites are missing");
}
var accession = QAccession.accession;
List<Expression<?>> selectExpressions = new ArrayList<>();
selectExpressions.add(accession.id);
selectExpressions.add(accession.accessionNumber);
var bigQuery = new BlazeJPAQuery<>(entityManager, criteriaBuilderFactory).from(accession)
.where(filter != null ? filter.buildPredicate() : new AccessionFilter().buildPredicate())
.orderBy(accession.accessionNumberPart1.asc(), accession.accessionNumberPart2.asc(), accession.accessionNumberPart3.asc());
var fetchCountQuery = new BlazeJPAQuery<>(entityManager, criteriaBuilderFactory)
.from(accession)
.select(accession.id.count())
.where(filter != null ? filter.buildPredicate() : new AccessionFilter().buildPredicate());
for (var site : siteAndFilters) {
QSiteCounter siteCounter = new QSiteCounter("CTEFor" + site.getKey().getId()); // CTE (accessionId, count(inventories))
var siteInv = new QInventory("subQuerySite" + site.getKey().getId());
var siteCountQuery = new BlazeJPAQuery<>(entityManager, criteriaBuilderFactory).from(siteInv).select(siteCounter)
.bind(siteCounter.siteCount, siteInv.id.count())
.bind(siteCounter.accessionId, siteInv.accession().id)
.where(
siteInv.site().eq(site.getKey())
.and(siteInv.accession().eq(accession))
.and(siteInv.formTypeCode.ne(Inventory.SYSTEM_INVENTORY_FTC))
.and(siteInv.quantityOnHand.isNull().or(siteInv.quantityOnHand.gt(0)))
)
.groupBy(siteInv.accession().id);
bigQuery.leftJoin(siteCountQuery, siteCounter).on(siteCounter.accessionId.eq(accession.id));
selectExpressions.add(siteCounter.siteCount.coalesce(0L));
// support filtering by count at this site
if (site.getValue() != null && !site.getValue().isEmpty()) {
var siteCounterPredicate = site.getValue().buildQuery(siteCounter.siteCount.coalesce(0L)).getValue();
bigQuery.where(siteCounterPredicate);
// add join to the fetch count query only if it has filter
fetchCountQuery.leftJoin(siteCountQuery, siteCounter).on(siteCounter.accessionId.eq(accession.id));
fetchCountQuery.where(siteCounterPredicate);
}
}
bigQuery.select(selectExpressions.toArray(new Expression[0]));
var total = fetchCountQuery.fetchOne();
if (total == 0) {
return new PageImpl<>(new ArrayList<>(), page, total);
}
// Apply pagination
bigQuery.offset(page.getOffset());
bigQuery.limit(page.getPageSize());
var responseItems = bigQuery.fetch().stream().map(result -> {
var response = new ComparedSitesResponse();
response.setAccessionNumber(((Tuple)result).get(accession.accessionNumber));
response.setAccessionId(((Tuple)result).get(accession.id));
Map<String, Long> siteInvCounter = new HashMap<>();
for (int siteIndex = 0, resultIndex = siteIndex + 2; siteIndex < siteAndFilters.size(); siteIndex++, resultIndex++) {
siteInvCounter.put(
siteAndFilters.get(siteIndex).getKey().getSiteShortName(),
((Tuple)result).get(resultIndex, Long.class)
);
}
response.setSiteInventories(siteInvCounter);
return response;
}).collect(Collectors.toList());
return new PageImpl<>(responseItems, page, total);
}
@Override
protected void prepareLabelContext(Map<String, Object> context, Inventory entity) {
context.put("inventory", entity);
context.put("accession", entity.getAccession());
context.put("taxon", entity.getAccession().getTaxonomySpecies().getName());
}
}