InvitroInventoryApiServiceImpl.java
/*
* Copyright 2026 Global Crop Diversity Trust
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root folder or http://www.apache.org/licenses/LICENSE-2.0
*/
package org.gringlobal.api.v2.facade.impl;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.validation.Valid;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.api.model.InventoryDTO;
import org.gringlobal.api.v2.facade.InvitroInventoryApiService;
import org.gringlobal.api.v2.mapper.MapstructMapper;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.persistence.InventoryMaintenancePolicyRepository;
import org.gringlobal.service.InventoryService;
import org.gringlobal.service.InvitroInventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.apache.commons.lang3.StringUtils;
@Service
@Validated
public class InvitroInventoryApiServiceImpl implements InvitroInventoryApiService {
@Autowired
private InvitroInventoryService service;
@Autowired
private InventoryService inventoryService;
@Autowired
private InventoryMaintenancePolicyRepository inventoryMaintPolicyRepo;
@Autowired
private MapstructMapper mapper;
@Override
@Transactional
public List<InventoryDTO> invitroStart(IVMultiplicationRequestDTO requestDTO) {
var request = mapper.map(requestDTO);
request.sourceInventory = inventoryService.get(request.sourceInventory.getId());
return mapper.map(service.invitroStart(request), mapper::map);
}
@Override
@Transactional
public List<InventoryDTO> invitroMultiply(@Valid List<IVMultiplicationRequestDTO> requests) {
var mappedRequests = requests.stream().map(mapper::map).toList();
var createdList = new ArrayList<Inventory>();
for (InvitroInventoryService.IVMultiplicationRequest request : mappedRequests) {
if (request.multiplicationItems == null || request.multiplicationItems.isEmpty()) {
throw new InvalidApiUsageException("multiplicationItems must not be null or empty");
}
request.sourceInventory = inventoryService.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
}
for (InvitroInventoryService.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);
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.setAvailabilityReasonCode(item.availabilityReasonCode);
inv.setNote(item.note);
inv.setPathogenStatusCode(item.pathogenStatusCode != null ? item.pathogenStatusCode : request.sourceInventory.getPathogenStatusCode()); // Inherit from the source if not present
inv.setGeneration(request.sourceInventory.nextGeneration()); // Increment generation
var created = inventoryService.create(inv);
service.applyTCExtraFromRequest(created, item, request.sourceInventory);
createdList.add(created);
}
if (Objects.equals("Y", request.sourceInventory.getIsAutoDeducted())) {
// Consume source inventory
request.sourceInventory.setQuantityOnHand(request.sourceInventory.getQuantityOnHand() - request.quantityToDeduct);
request.sourceInventory = inventoryService.update(request.sourceInventory);
}
}
return mapper.map(createdList, mapper::map);
}
}