OrderRequestServiceImpl.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 java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.InventoryAction;
import org.gringlobal.model.InventoryMaintenancePolicy;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.OrderRequestAction;
import org.gringlobal.model.OrderRequestAttach;
import org.gringlobal.model.OrderRequestItem;
import org.gringlobal.model.QInventory;
import org.gringlobal.model.QOrderRequest;
import org.gringlobal.model.QOrderRequestAction;
import org.gringlobal.model.QOrderRequestAttach;
import org.gringlobal.model.QOrderRequestItem;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.model.workflow.WorkflowActionStep;
import org.gringlobal.persistence.InventoryActionRepository;
import org.gringlobal.persistence.InventoryMaintenancePolicyRepository;
import org.gringlobal.persistence.InventoryRepository;
import org.gringlobal.persistence.OrderRequestActionRepository;
import org.gringlobal.persistence.OrderRequestItemRepository;
import org.gringlobal.persistence.OrderRequestRepository;
import org.gringlobal.service.InventoryExtraService;
import org.gringlobal.service.InventoryService;
import org.gringlobal.service.OrderRequestActionService;
import org.gringlobal.service.OrderRequestActionService.OrderRequestActionRequest;
import org.gringlobal.service.OrderRequestActionService.OrderRequestActionScheduleFilter;
import org.gringlobal.service.OrderRequestAttachmentService;
import org.gringlobal.service.OrderRequestItemService;
import org.gringlobal.service.OrderRequestService;
import org.gringlobal.service.filter.OrderRequestActionFilter;
import org.gringlobal.service.filter.OrderRequestFilter;
import org.gringlobal.service.filter.OrderRequestItemFilter;
import org.gringlobal.service.glis.impl.GlisSMTAReportingManager;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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.google.common.collect.Sets;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.jpa.impl.JPAQuery;
import static org.gringlobal.service.glis.impl.GlisSMTAReportingManager.RESPONSE_ERROR;
/**
* @author Maxym Borodenko
*/
@Service
@Transactional(readOnly = true)
@Slf4j
public class OrderRequestServiceImpl extends FilteredCRUDService2Impl<OrderRequest, OrderRequestFilter, OrderRequestRepository> implements OrderRequestService {
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private InventoryService inventoryService;
@Autowired
private InventoryExtraService inventoryExtraService;
@Autowired
private InventoryMaintenancePolicyRepository inventoryMaintenancePolicyRepository;
@Autowired
private InventoryActionRepository inventoryActionRepository;
@Autowired
private OrderRequestItemRepository itemRepository;
@Autowired
private OrderRequestItemService itemService;
@Autowired
private OrderRequestActionService actionSupport;
@Autowired
private GlisSMTAReportingManager glisSMTAReportingManager;
@Component
protected static class ActionSupport extends BaseActionSupport<OrderRequest, OrderRequestAction, OrderRequestActionFilter, OrderRequestActionRepository, OrderRequestActionRequest, OrderRequestActionScheduleFilter>
implements OrderRequestActionService {
@Autowired
private OrderRequestRepository orderRequestRepository;
@Override
protected EntityPath<OrderRequest> getOwningEntityPath() {
return QOrderRequestAction.orderRequestAction.orderRequest();
}
@Override
protected void initializeActionDetails(List<OrderRequestAction> actions) {
actions.forEach(action -> {
Hibernate.initialize(action.getOrderRequest());
Hibernate.initialize(action.getOrderRequest().getFinalRecipientCooperator());
});
}
@Override
protected void applyOwningEntityFilter(OrderRequestActionScheduleFilter filter, String owningEntityAlias, List<Predicate> predicates) {
QOrderRequest qOrderRequest = new QOrderRequest(owningEntityAlias);
if (predicates != null && filter.orderRequest != null) {
predicates.addAll(filter.orderRequest.collectPredicates(qOrderRequest));
}
}
@Override
protected OrderRequestAction createAction(OrderRequest owningEntity) {
OrderRequestAction action = new OrderRequestAction();
action.setOrderRequest(owningEntity);
return action;
}
@Override
protected void updateAction(OrderRequestAction action, OrderRequestActionRequest request) {
action.setActionCost(request.actionCost);
action.setActionInformation(request.actionInformation);
}
@Override
protected OrderRequestAction prepareNextWorkflowStepAction(WorkflowActionStep nextStep, OrderRequestAction completedAction) {
OrderRequestAction nextAction = new OrderRequestAction();
nextAction.setOrderRequest(new OrderRequest(completedAction.getOrderRequest().getId()));
return nextAction;
}
@Override
@Transactional
public OrderRequestAction create(OrderRequestAction source) {
log.debug("Create OrderRequestAction. Input data {}", source);
OrderRequestAction entity = new OrderRequestAction();
entity.apply(source);
OrderRequestAction saved = get(repository.save(entity));
saved.lazyLoad();
return saved;
}
@Override
protected Iterable<OrderRequest> findOwningEntities(Set<Long> id) {
return orderRequestRepository.findAll(QOrderRequest.orderRequest.id.in(id));
}
@Override
@Transactional
public void logAction(OrderRequest orderRequest, String note, List<OrderRequestItem> updatedItems) {
OrderRequestAction action = new OrderRequestAction();
action.setOrderRequest(orderRequest);
action.setActionNameCode(CommunityCodeValues.ORDER_REQUEST_ACTION_LOG.value);
action.setStartedDate(Instant.now());
action.setStartedDateCode(CommunityCodeValues.DATE_FORMAT_DATETIME.value);
action.setCompletedDate(action.getStartedDate());
action.setCompletedDateCode(CommunityCodeValues.DATE_FORMAT_DATETIME.value);
if (CollectionUtils.isNotEmpty(updatedItems)) {
action.setActionInformation(updatedItems.stream().map((item) -> item.getId().toString()).collect(Collectors.joining(";")));
}
action.setNote(note);
actionRepository.save(action);
}
@Override
public Page<OrderRequestAction> listByOrderRequest(OrderRequest orderRequest, Pageable page) {
orderRequest = orderRequestRepository.getReferenceById(orderRequest.getId());
// build filter
OrderRequestActionFilter filter = new OrderRequestActionFilter();
filter.orderRequest().id(Set.of(orderRequest.getId()));
var query = jpaQueryFactory.selectFrom(QOrderRequestAction.orderRequestAction)
// order request
.join(QOrderRequestAction.orderRequestAction.orderRequest()).fetchJoin()
// cooperator
.leftJoin(QOrderRequestAction.orderRequestAction.cooperator()).fetchJoin()
.where(filter.buildPredicate());
return actionRepository.findAll(query, page);
}
}
@Component
protected static class AttachmentSupport extends BaseAttachmentSupport<OrderRequest, OrderRequestAttach, OrderRequestAttachmentService.OrderRequestAttachmentRequest> implements OrderRequestAttachmentService {
public AttachmentSupport() {
super(QOrderRequestAttach.orderRequestAttach.orderRequest().id, QOrderRequestAttach.orderRequestAttach.id);
}
@Override
protected Path createRepositoryPath(OrderRequest orderRequest) {
orderRequest = owningEntityRepository.getReferenceById(orderRequest.getId());
return Paths.get("/ORA/" + orderRequest.getId());
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Request', 'WRITE')")
protected OrderRequestAttach createAttach(OrderRequest entity, OrderRequestAttach source) {
OrderRequestAttach attach = new OrderRequestAttach();
attach.apply(source);
attach.setVirtualPath(source.getVirtualPath()); // SOAP uses this to create the record
attach.setOrderRequest(entity);
return attach;
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Request', 'WRITE')")
public OrderRequestAttach create(OrderRequestAttach source) {
var owningEntity = owningEntityRepository.getReferenceById(source.getOrderRequest().getId());
var attach = createAttach(owningEntity, source);
var savedAttach = repository.save(attach);
return _lazyLoad(savedAttach);
}
@Override
@Transactional
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Request', 'WRITE')")
public OrderRequestAttach update(OrderRequestAttach updated, OrderRequestAttach target) {
target.apply(updated);
return _lazyLoad(repository.save(target));
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Request', 'WRITE')")
public OrderRequestAttach remove(OrderRequestAttach entity) {
return super.remove(entity);
}
}
@Override
protected JPAQuery<OrderRequest> entityListQuery() {
return jpaQueryFactory.selectFrom(QOrderRequest.orderRequest)
// final recipient
.join(QOrderRequest.orderRequest.finalRecipientCooperator()).fetchJoin()
// requestor
.leftJoin(QOrderRequest.orderRequest.requestorCooperator()).fetchJoin()
// shipTo
.leftJoin(QOrderRequest.orderRequest.shipToCooperator()).fetchJoin()
// authorizedOfficial
.leftJoin(QOrderRequest.orderRequest.authorizedOfficialCooperator()).fetchJoin()
// owner
.join(QOrderRequest.orderRequest.ownedBy()).fetchJoin();
}
@Override
protected NumberPath<Long> entityIdPredicate() {
return QOrderRequest.orderRequest.id;
}
@Override
public Page<OrderRequestItem> filterItems(final OrderRequest orderRequest, OrderRequestItemFilter filter, Pageable page) throws SearchException {
filter.orderRequest = Sets.newHashSet(orderRequest.getId());
var items = itemService.list(filter, page);
items.getContent().forEach(item -> {
if (item.getWithdrawnInventory() != null) {
item.getWithdrawnInventory().getId();
}
});
return items;
}
@Override
@PreAuthorize("@ggceSec.actionAllowed('Request', 'READ')")
public OrderRequestDetails getOrderRequestDetails(OrderRequest orderRequest) {
orderRequest = reload(orderRequest);
// initialize lazy data
if (orderRequest.getAttachments() != null) {
orderRequest.getAttachments().size();
orderRequest.getAttachments().forEach(a -> {
if (a.getRepositoryFile() != null) {
a.getRepositoryFile().getId();
}
if (a.getAttachCooperator() != null) {
a.getAttachCooperator().getId();
}
});
}
var details = new OrderRequestDetails();
details.orderRequest = orderRequest;
details.attachments = orderRequest.getAttachments();
return details;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public List<OrderRequestItem> addInventories(OrderRequest orderRequest, List<OrderRequestedInventory> inventories) {
AtomicInteger sequenceNumber = new AtomicInteger(0);
LinkedList<OrderRequestedInventory> toAdd = new LinkedList<>(inventories);
List<OrderRequestItem> existingItems;
// don't add an item with the same inventory
if (CollectionUtils.isNotEmpty(toAdd)) {
existingItems = Lists.newArrayList(itemRepository.findAll(QOrderRequestItem.orderRequestItem.orderRequest().id.eq(orderRequest.getId())));
if (!existingItems.isEmpty()) {
existingItems.forEach(e -> toAdd.removeIf((ri) -> Objects.equals(e.getInventory().getId(), ri.inventoryId)));
Integer maxSeqNo = existingItems.stream().map(OrderRequestItem::getSequenceNumber).filter(Objects::nonNull).max(Comparator.comparing(Integer::valueOf)).orElse(0);
sequenceNumber.set(maxSeqNo);
}
}
if (toAdd.isEmpty()) {
return Collections.emptyList();
}
Map<Long, OrderRequestedInventory> orderRequestedInventories = new HashMap<>();
toAdd.forEach(ri -> {
orderRequestedInventories.put(ri.inventoryId, ri);
});
Set<Long> inventoryIds = orderRequestedInventories.keySet();
List<OrderRequestItem> items = inventoryRepository.findAllById(inventoryIds).stream().map(inventory -> {
OrderRequestItem item = new OrderRequestItem();
item.setSequenceNumber(sequenceNumber.incrementAndGet());
item.setOrderRequest(orderRequest);
item.setInventory(inventory);
if (! inventory.isSystemInventory()) {
item.setDistributionFormCode(inventory.getDistributionDefaultFormCode());
item.setQuantityShipped(inventory.getDistributionDefaultQuantity());
item.setQuantityShippedUnitCode(inventory.getDistributionUnitCode());
}
OrderRequestedInventory orderRequestedInventory = orderRequestedInventories.get(inventory.getId());
if (orderRequestedInventory != null) {
item.setName(orderRequestedInventory.requestedName);
item.setExternalTaxonomy(orderRequestedInventory.requestedTaxon);
}
item.setStatusCode(CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_NEW.value);
return itemRepository.save(item);
}).collect(Collectors.toList());
log.debug("Done saving {} items.", items.size());
return items;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public List<OrderRequestItem> setDefaultInventories(OrderRequest orderRequest, Set<Long> itemIds) {
List<OrderRequestItem> existingItems = Lists.newArrayList(itemRepository.findAll(QOrderRequestItem.orderRequestItem.orderRequest().id.eq(orderRequest.getId())));
List<OrderRequestItem> toAssign = existingItems.stream()
.filter(item -> itemIds.contains(item.getId()) && item.getStatusCode().equals(CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_NEW.value))
.filter(item -> item.getWithdrawnInventory() == null)
.collect(Collectors.toList());
if (toAssign.isEmpty()) {
return toAssign;
}
Set<Long> itemsAccessionIds = toAssign.stream().map(item -> item.getInventory().getAccession().getId()).collect(Collectors.toSet());
var accessionInventories = StreamSupport
.stream(inventoryRepository.findAll(QInventory.inventory.accession().id.in(itemsAccessionIds)).spliterator(), false)
.collect(Collectors.groupingBy(inventory -> inventory.getAccession().getId()));
for (OrderRequestItem item : toAssign) {
var inventories = accessionInventories.get(item.getInventory().getAccession().getId());
var firstDistributableInventory = inventories.stream()
.filter(inventory -> Objects.equals(inventory.getIsDistributable(), "Y") && inventory.getQuantityOnHand() != null && inventory.getDistributionCriticalQuantity() != null && inventory.getQuantityOnHand() >= inventory.getDistributionCriticalQuantity())
.min(Comparator.comparing(Inventory::getDistributionRank, Comparator.nullsLast(Comparator.naturalOrder())))
.orElse(inventories.stream().filter(Inventory::isSystemInventory).findFirst().get());
item.setInventory(firstDistributableInventory);
if (! firstDistributableInventory.isSystemInventory()) {
item.setDistributionFormCode(firstDistributableInventory.getDistributionDefaultFormCode());
item.setQuantityShipped(firstDistributableInventory.getDistributionDefaultQuantity());
item.setQuantityShippedUnitCode(firstDistributableInventory.getDistributionUnitCode());
} else {
item.setDistributionFormCode(null);
item.setQuantityShipped(null);
item.setQuantityShippedUnitCode(null);
}
item.setStatusCode(CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_NEW.value);
}
return itemRepository.saveAllAndFlush(toAssign);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public List<OrderRequestItem> removeOrderItems(final OrderRequest orderRequest, final Set<Long> itemIds) {
log.debug("Remove inventories {} from order request {}.", itemIds, orderRequest.getId());
if (CollectionUtils.isEmpty(itemIds)) {
return List.of();
}
List<OrderRequestItem> existingItems = Lists.newArrayList(itemRepository.findAll(QOrderRequestItem.orderRequestItem.orderRequest().id.eq(orderRequest.getId())));
List<OrderRequestItem> toRemove = existingItems.stream().filter(x -> itemIds.contains(x.getId())).collect(Collectors.toList());
itemRepository.deleteAll(toRemove);
log.debug("Done removing {} items from order request {}", toRemove.size(), orderRequest.getId());
return toRemove;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public OrderRequest updateFast(@NotNull @Valid OrderRequest updated, OrderRequest target) {
target.apply(updated);
return repository.save(target);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public OrderRequest update(final OrderRequest input, OrderRequest target) {
log.debug("Update order request. Input data {}", input);
target.apply(input);
OrderRequest saved = repository.save(target);
return _lazyLoad(saved);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'CREATE')")
public OrderRequest createFast(OrderRequest source) {
if (source.getOrderedDate() == null) {
source.setOrderedDate(new Date());
}
return repository.save(source);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'CREATE')")
public OrderRequest create(final OrderRequest source) {
log.debug("Create order request. Input data {}", source);
OrderRequest orderRequest = new OrderRequest();
orderRequest.apply(source);
if (orderRequest.getOrderedDate() == null) {
orderRequest.setOrderedDate(new Date());
}
OrderRequest saved = repository.save(orderRequest);
return _lazyLoad(saved);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'CREATE')")
public OrderRequest create(final OrderRequest source, final List<OrderRequestItem> orderRequestItems) {
log.debug("Create order request. Input data {}", source);
OrderRequest orderRequest = new OrderRequest();
orderRequest.apply(source);
if (orderRequest.getOrderedDate() == null) {
orderRequest.setOrderedDate(new Date());
}
OrderRequest saved = repository.save(orderRequest);
AtomicInteger sequenceNumber = new AtomicInteger(0);
orderRequestItems.forEach((ori) -> {
if (! ori.isNew()) {
throw new InvalidApiUsageException("OrderRequestItem must not be persisted");
}
ori.setOrderRequest(orderRequest);
ori.setSequenceNumber(sequenceNumber.incrementAndGet()); // order
ori.setStatusCode(CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_NEW.value); // NEW item
ori.setStatusDate(new Date());
});
itemRepository.saveAll(orderRequestItems); // Save order items
return _lazyLoad(saved);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public void renumberOrderRequestItems(OrderRequest orderRequest) {
orderRequest = get(orderRequest);
try {
var items = itemRepository.findAll(QOrderRequestItem.orderRequestItem.orderRequest().eq(orderRequest), QOrderRequestItem.orderRequestItem.sequenceNumber.asc().nullsLast());
int sequenceNumber = 1;
for (OrderRequestItem item : items) {
if (item.getStatusCode().equals(CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_CANCEL.value) || item.getStatusCode().equals(CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_SPLIT.value)) {
item.setSequenceNumber(null);
} else {
item.setSequenceNumber(sequenceNumber);
sequenceNumber++;
}
}
itemRepository.saveAll(items);
} catch (Exception e) {
throw new InvalidApiUsageException("Error while renumbering items of OrderRequest " + orderRequest, e);
}
}
@Override
@PreAuthorize("@ggceSec.actionAllowed('Request', 'READ')")
public Page<OrderRequest> list(OrderRequestFilter filter, Pageable page) throws SearchException {
return super.list(OrderRequest.class, filter, page);
}
@Override
@PreAuthorize("@ggceSec.actionAllowed('Request', 'DELETE')")
public OrderRequest remove(OrderRequest entity) {
entity = get(entity);
var hasNotNewItems = itemRepository.exists(QOrderRequestItem.orderRequestItem.orderRequest().eq(entity)
.and(QOrderRequestItem.orderRequestItem.statusCode.ne(CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_NEW.value)));
if (hasNotNewItems) {
throw new InvalidApiUsageException("Refusing to remove order. All items of the order must be in the 'NEW' status.");
}
var filter = new OrderRequestActionFilter();
filter.orderRequest().id(Set.of(entity.getId()));
if (actionSupport.countActions(filter) > 0) {
throw new InvalidApiUsageException("Refusing to remove order with actions.");
}
return super.remove(entity);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public List<OrderRequestItem> updateItemStatus(OrderRequest orderRequest, String newStatus, Set<Long> itemIds) {
return updateItemStatus(orderRequest, newStatus, itemRepository.findAllById(itemIds));
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public List<OrderRequestItem> updateItemStatus(OrderRequest orderRequest, String newStatus, List<OrderRequestItem> orderItems) {
orderItems = Lists.newArrayList(itemRepository.findAll(
// order
QOrderRequestItem.orderRequestItem.orderRequest().id.eq(orderRequest.getId())
// selected items
.and(QOrderRequestItem.orderRequestItem.in(orderItems))
// not having selected status
.and(QOrderRequestItem.orderRequestItem.statusCode.ne(newStatus))));
var shippedItemsIds = orderItems.stream().filter(item -> CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_SHIPPED.is(item.getStatusCode()))
.map(OrderRequestItem::getId).collect(Collectors.toList());
// Updated items only
List<OrderRequestItem> updatedItems = orderItems.stream().map((orderItem) -> updateItemStatus(orderItem, newStatus))
// if updatedItem is null, the change is not permitted
.filter((updatedItem) -> updatedItem != null).collect(Collectors.toList());
if (CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_SPLIT.is(newStatus) && updatedItems.size() > 0) {
// TODO Needs testing that some items remain in this order
OrderRequest splitOrder = splitOrder(orderRequest, updatedItems);
log.warn("Created split order id={} with {} items", splitOrder.getId(), splitOrder.getOrderRequestItems().size());
}
itemRepository.saveAll(updatedItems);
Set<Inventory> updatedInventories = new HashSet<>();
if (CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_SHIPPED.is(newStatus)) {
// update inventory quantity if item status is updated to shipped
updatedInventories = updatedItems.stream().map(item -> {
var inventory = item.getInventory();
if (shouldAutoDeductInventory(item)) {
if (inventory.getQuantityOnHand() < item.getQuantityShipped()) {
throw new InvalidApiUsageException("Insufficient inventory quantity on hand");
}
inventory.setQuantityOnHand(inventory.getQuantityOnHand() - item.getQuantityShipped());
}
return inventory;
}).collect(Collectors.toSet());
} else if (!shippedItemsIds.isEmpty()) {
// update inventory quantity if item status is updated from shipped to something else
updatedInventories = updatedItems.stream().filter(updatedItem -> shippedItemsIds.contains(updatedItem.getId())).map(item -> {
var inventory = item.getInventory();
if (shouldAutoDeductInventory(item)) {
inventory.setQuantityOnHand(inventory.getQuantityOnHand() + item.getQuantityShipped());
}
return inventory;
}).collect(Collectors.toSet());
}
if (!updatedInventories.isEmpty()) {
inventoryRepository.saveAll(updatedInventories);
}
// Log change based on: "Order Request Item status_code changed by xxxxxx to
// SHIPPED for 11 items."
actionSupport.logAction(orderRequest, MessageFormat.format("Item status changed to {0} for {1} items.", newStatus, updatedItems.size()), updatedItems);
// Return selected items regardless of status
return Lists.newArrayList(itemRepository.findAll(
// order
QOrderRequestItem.orderRequestItem.orderRequest().id.eq(orderRequest.getId())
// selected items
.and(QOrderRequestItem.orderRequestItem.in(orderItems))));
}
/**
* Determine if the inventory of order request item should be automatically deducted
* @param item order request item
* @return <code>true</code> if form and unit codes match, and inventory is auto-deductible
*/
private boolean shouldAutoDeductInventory(OrderRequestItem item) {
Inventory inventory = item.getInventory();
if (inventory == null)
return false;
return
// inventory is auto-deductible
StringUtils.equals(inventory.getIsAutoDeducted(), "Y")
// item form type code matches inventory distribtion form code
&& StringUtils.equals(item.getDistributionFormCode(), inventory.getFormTypeCode())
// item units match inventory units
&& StringUtils.equals(item.getQuantityShippedUnitCode(), inventory.getQuantityOnHandUnitCode())
;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public OrderRequest generateInventories(OrderRequest order, InventoryMaintenancePolicy withdrawnInventoriesPolicy) {
assert order.getId() != null;
order = get(order);
var withdrawnPolicy = withdrawnInventoriesPolicy != null && withdrawnInventoriesPolicy.getId() != null ?
inventoryMaintenancePolicyRepository.getReferenceById(withdrawnInventoriesPolicy.getId()) : null;
var orderRequestId = order.getId();
List<OrderRequestItem> itemsForUpdate = new ArrayList<>();
List<InventoryAction> logActions = new ArrayList<>();
var currentDateTime = Instant.now();
order.getOrderRequestItems().forEach(item -> {
if (item.getWithdrawnInventory() != null) {
return; // We already have an inventory!
}
if (Objects.equals(item.getStatusCode(), CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_CANCEL.value)) {
return; // Item is canceled
}
if (Objects.equals(item.getStatusCode(), CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_SPLIT.value)) {
return; // Item is split
}
Inventory inventory = item.getInventory();
if (inventory.isSystemInventory()) {
return; // Never withdraw SYSTEM inventory
}
Inventory withdrawnInventory = new Inventory();
// withdrawnInventory.apply(inventory); // Do NOT apply all
withdrawnInventory.setSite(inventory.getSite());
withdrawnInventory.setAccession(inventory.getAccession());
withdrawnInventory.setParentInventory(inventory);
withdrawnInventory.setInventoryNumberPart1(inventory.getInventoryNumberPart1());
withdrawnInventory.setInventoryNumberPart2(-1L);
withdrawnInventory.setInventoryNumberPart3(inventory.getInventoryNumberPart3());
withdrawnInventory.setInventoryMaintenancePolicy(withdrawnPolicy != null ? withdrawnPolicy : inventory.getInventoryMaintenancePolicy());
withdrawnInventory.setFormTypeCode(inventory.getFormTypeCode());
withdrawnInventory.setGeneration(inventory.getGeneration());
withdrawnInventory.setHundredSeedWeight(inventory.getHundredSeedWeight());
withdrawnInventory.setQuantityOnHand(item.getQuantityShipped());
withdrawnInventory.setPathogenStatusCode(inventory.getPathogenStatusCode());
withdrawnInventory.setPlantSexCode(inventory.getPlantSexCode());
withdrawnInventory.setRootstock(inventory.getRootstock());
withdrawnInventory.setPollinationMethodCode(inventory.getPollinationMethodCode());
withdrawnInventory.setPollinationVectorCode(inventory.getPollinationVectorCode());
withdrawnInventory.setPropagationDate(inventory.getPropagationDate());
withdrawnInventory.setPropagationDateCode(inventory.getPropagationDateCode());
withdrawnInventory.setContainerTypeCode(item.getContainerTypeCode());
withdrawnInventory.setPreservationMethod(inventory.getPreservationMethod());
withdrawnInventory.setRegenerationMethod(inventory.getRegenerationMethod());
// withdrawnInventory.setIsAvailable("N"); // default = N
// withdrawnInventory.setIsDistributable("N"); // default = N
// withdrawnInventory.setIsAutoDeducted("N"); // default = N
withdrawnInventory.setAvailabilityStatusCode(CommunityCodeValues.INVENTORY_AVAILABILITY_NOTSET.value); // TODO determine
withdrawnInventory.setNote("Created from order_request_id=" + orderRequestId + " order_request_item_id=" + item.getId());
var saved = inventoryRepository.save(withdrawnInventory);
inventoryService.assignBarcode(saved);
var extra = inventory.getExtra();
if (extra != null) {
var withdrawnExtra = extra.copy();
if (withdrawnExtra != null) {
withdrawnExtra.setInventory(saved);
inventoryExtraService.create(withdrawnExtra);
}
}
saved = inventoryRepository.getReferenceById(saved.getId());
item.setWithdrawnInventory(saved);
itemsForUpdate.add(item);
InventoryAction inventoryAction = new InventoryAction();
inventoryAction.setActionNameCode(CommunityCodeValues.INVENTORY_ACTION_LOG.value);
inventoryAction.setInventory(saved);
inventoryAction.setStartedDate(currentDateTime);
inventoryAction.setStartedDateCode(CommunityCodeValues.DATE_FORMAT_DATE.value);
inventoryAction.setCompletedDate(currentDateTime);
inventoryAction.setCompletedDateCode(CommunityCodeValues.DATE_FORMAT_DATE.value);
inventoryAction.setNote(String.format("Created from order_request_id=%s order_request_item_id=%s", orderRequestId, item.getId()));
logActions.add(inventoryAction);
inventoryAction = new InventoryAction();
inventoryAction.setActionNameCode(CommunityCodeValues.INVENTORY_ACTION_QUANTITYSET.value);
inventoryAction.setInventory(saved);
inventoryAction.setNotBeforeDate(currentDateTime);
inventoryAction.setNotBeforeDateCode(CommunityCodeValues.DATE_FORMAT_DATE.value);
logActions.add(inventoryAction);
});
itemRepository.saveAllAndFlush(itemsForUpdate);
inventoryActionRepository.saveAllAndFlush(logActions);
return order;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public GlisSMTAReportingManager.GlisSMTAReportResponse reportSMTA(OrderRequest order) throws Exception {
order = load(order.getId());
OrderRequestActionFilter filter = (OrderRequestActionFilter) new OrderRequestActionFilter()
.orderRequest((OrderRequestFilter) new OrderRequestFilter().id(Set.of(order.getId())))
.actionNameCode(Set.of(CommunityCodeValues.ORDER_REQUEST_ACTION_REPORT_TO_ITPGRFA.value));
var actions = actionSupport.listActions(filter, Pageable.unpaged()).getContent();
if (actions.stream().anyMatch(action -> action.getCompletedDate() != null)) {
throw new InvalidApiUsageException("Order request with id=" + order.getId() + " is already reported.");
} else {
if (actions.isEmpty() || actions.get(0).getStartedDate() == null) {
OrderRequestActionRequest actionRequest = OrderRequestActionRequest.builder()
.actionNameCode(CommunityCodeValues.ORDER_REQUEST_ACTION_REPORT_TO_ITPGRFA.value)
.id(Set.of(order.getId()))
.build();
actionSupport.startAction(actionRequest);
}
}
return glisSMTAReportingManager.uploadOrderRequestReport(order);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
public void generateSMTA(OrderRequest order, OutputStream outputStream) throws Exception {
order = load(order.getId());
var response = glisSMTAReportingManager.generateSMTA(order);
if (RESPONSE_ERROR.equals(response.status)) {
throw new InvalidApiUsageException(RESPONSE_ERROR.concat(": ").concat(String.join("\n", response.errors)));
} else {
outputStream.write(Base64.getDecoder().decode(response.pdf));
}
}
/**
* Split order.
*
* @param originalRequest the original (source) order request
* @param originalItems the items split from the source order request
* @return the split order
*/
private OrderRequest splitOrder(OrderRequest originalRequest, List<OrderRequestItem> originalItems) {
log.warn("Splitting {} items to new order", originalItems.size());
OrderRequest splitRequest = new OrderRequest();
splitRequest.apply(originalRequest);
splitRequest.setFeedback(null);
splitRequest.setCompletedDate(null); // reset completion
splitRequest.setOriginalOrderRequest(originalRequest); // update original
OrderRequest savedSplitRequest = repository.save(splitRequest);
List<OrderRequestItem> splitItems = originalItems.stream().map(orderItem -> {
OrderRequestItem splitItem = new OrderRequestItem();
splitItem.apply(orderItem);
splitItem.setStatusCode(CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_NEW.value);
splitItem.setStatusDate(new Date());
splitItem.setOrderRequest(savedSplitRequest);
return splitItem;
}).collect(Collectors.toList());
savedSplitRequest.setOrderRequestItems(itemRepository.saveAll(splitItems));
return savedSplitRequest;
}
/**
* Change item status and update statusDate.
*
* @param orderItem the order item
* @param newStatus
* @return the updated item
*/
private OrderRequestItem updateItemStatus(OrderRequestItem orderItem, String newStatus) {
if (CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_SPLIT.is(orderItem.getStatusCode())) {
log.warn("Item is SPLIT, ignoring all changes");
return null;
}
if (StringUtils.equals(orderItem.getStatusCode(), newStatus)) {
log.info("Status code {} not altered for id={}", newStatus, orderItem.getId());
return null;
}
orderItem.setStatusCode(newStatus);
orderItem.setStatusDate(new Date());
return orderItem;
}
}