InventoryController.java
/*
* Copyright 2019 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.api.v1.impl;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.ArrayUtils;
import org.genesys.blocks.auditlog.model.AuditLog;
import org.genesys.blocks.auditlog.model.filters.AuditLogFilter;
import org.genesys.blocks.auditlog.service.AuditTrailService;
import org.genesys.blocks.model.filters.NumberFilter;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.gringlobal.api.v1.ActionController;
import org.gringlobal.api.v1.ApiBaseController;
import org.gringlobal.api.v1.CRUDController;
import org.gringlobal.api.v1.FilteredCRUDController;
import org.gringlobal.api.v1.FilteredPage;
import org.gringlobal.api.v1.Pagination;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.AccessionInvAttach;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.InventoryAction;
import org.gringlobal.model.InventoryQualityStatus;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.QInventory;
import org.gringlobal.model.QInventoryAction;
import org.gringlobal.model.SeedInventoryExtra;
import org.gringlobal.model.Site;
import org.gringlobal.persistence.InventoryRepositoryCustom;
import org.gringlobal.service.InventoryActionService;
import org.gringlobal.service.InventoryActionService.InventoryActionRequest;
import org.gringlobal.service.InventoryActionService.InventoryActionScheduleFilter;
import org.gringlobal.service.InventoryService.ComparedSitesResponse;
import org.gringlobal.service.InventoryAttachmentService;
import org.gringlobal.service.InventoryExtraService;
import org.gringlobal.service.InventoryQualityStatusService;
import org.gringlobal.service.InventoryService;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.querydsl.core.types.OrderSpecifier;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import io.swagger.v3.oas.annotations.tags.Tag;
@RestController("inventoryApi1")
@RequestMapping(InventoryController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = "Inventory")
public class InventoryController extends FilteredCRUDController<Inventory, InventoryService, InventoryFilter> {
/** The Constant API_URL. */
public static final String API_URL = ApiBaseController.APIv1_BASE + "/i";
@Autowired
private InventoryAttachmentService attachmentFileService;
@Autowired
private AuditTrailService auditService;
@Autowired
protected InventoryExtraService inventoryExtraService;
@Override
protected OrderSpecifier<?>[] defaultSort() {
return new OrderSpecifier[] { QInventory.inventory.id.desc() };
}
@Override
protected Class<InventoryFilter> filterType() {
return InventoryFilter.class;
}
/**
* Retrieve inventory details by id
*
* @param id the id
* @return the inventory details
*/
@GetMapping(value = "/details/{id}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "getDetails", description = "Retrieve inventory details by ID", summary = "Details")
public InventoryService.InventoryDetails details(@PathVariable("id") final long id) {
return crudService.getInventoryDetails(crudService.get(id));
}
/**
* Retrieve inventory details by barcode
*
* @param barcode the barcode
* @return the inventory details
*/
@GetMapping(value = "/details", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "getDetails", description = "Retrieve inventory details by barcode", summary = "Details")
public InventoryService.InventoryDetails details(@RequestParam("barcode") final String barcode) {
return crudService.getInventoryDetails(crudService.getByBarcode(barcode));
}
/**
* Retrieve a list of audit logs for the specified inventory
*
* @param inventoryId the inventoryId
* @param page the page request
* @return the list of all log entries
*/
@GetMapping("/auditlog/{id}")
public Page<AuditLog> inventoryAuditLogs(@PathVariable(value = "id") final Long inventoryId, @ParameterObject final Pagination page) {
var filter = new AuditLogFilter();
filter
.entityId(new NumberFilter<Long>().eq(Set.of(crudService.get(inventoryId).getId())))
.classname(Inventory.class.getName());
return auditService.listAuditLogs(filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE));
}
/**
* Generate and assign the barcode for Inventory based on a template
*
* @param inventoryId the ID of inventory
* @return the barcode
*/
@PostMapping(value = "/{id}/barcode", produces = { MediaType.TEXT_PLAIN_VALUE })
public String assignBarcode(@PathVariable(value = "id") final Long inventoryId) {
return crudService.assignBarcode(crudService.get(inventoryId));
}
@PostMapping("/quantity")
@Operation(summary = "Set inventory quantity on hand", description = "Update current quantity on hand.")
public Inventory setInventoryQuantity(@RequestBody @Valid InventoryService.InventoryQuantityRequest inventoryQuantity) {
return crudService.setInventoryQuantity(inventoryQuantity);
}
@PostMapping("/discard-material")
@Operation(summary = "Update inventory quantity on hand", description = "Update current quantity on hand.")
public ResponseEntity<HttpStatus> discardMaterial(@RequestBody final Map<Long, Integer> discardQuantities) {
crudService.discardMaterial(discardQuantities);
return ResponseEntity.ok().build();
}
@Override
@Operation(operationId = "list")
public FilteredPage<Inventory, InventoryFilter> list(@ParameterObject final Pagination page, @RequestBody InventoryFilter filter) throws SearchException, IOException {
return super.list(page, filter);
}
@PostMapping(value = "/aggregate-quantity", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "aggregateQuantity", description = "Returns the aggregate of inventory quantities.", summary = "Sum inventory quantities.")
public FilteredPage<InventoryRepositoryCustom.AggregatedInventoryQuantity, InventoryFilter> aggregateQuantity(@ParameterObject final Pagination page, @RequestBody InventoryFilter filter) throws IOException {
InventoryFilter normalizedFilter = shortFilterService.normalizeFilter(filter, filterType());
Pageable pageable = ArrayUtils.isEmpty(page.getS()) ? page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, QInventory.inventory.formTypeCode.asc()) : page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE);
return new FilteredPage<>(normalizedFilter, crudService.aggregateQuantity(filter, pageable));
}
@Override
@Operation(operationId = "filter", description = "Retrieve list of records matching the filter or filter code", summary = "List by filter code or filter")
public FilteredPage<Inventory, InventoryFilter> filter(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page, @RequestBody InventoryFilter filter) throws IOException, SearchException {
return super.filter(filterCode, page, filter);
}
@PostMapping(value = "/overview/{groupBy:.+}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "inventoryOverview", description = "Get inventory statistics", summary = "Overview")
public Map<?, ?> inventoryOverview(@PathVariable(name = "groupBy", required = true) String groupBy, @RequestBody InventoryFilter filter) {
return crudService.inventoryOverview(groupBy, filter);
}
@PostMapping(value = "/attach/{inventoryId}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "uploadFile", description = "Attach accession file", summary = "Attach file")
public AccessionInvAttach uploadFile(@PathVariable(name = "inventoryId") final Long inventoryId, @RequestPart(name = "file") final MultipartFile file,
@RequestPart(name = "metadata") final InventoryAttachmentService.InventoryAttachmentRequest metadata) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException {
return attachmentFileService.uploadFile(crudService.get(inventoryId), file, metadata);
}
@DeleteMapping(value = "/attach/{inventoryId}/{attachmentId}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "removeFile", description = "Remove attached file", summary = "Remove file")
public AccessionInvAttach removeFile(@PathVariable(name = "inventoryId") final Long inventoryId, @PathVariable(name = "attachmentId") final Long attachmentId) {
return attachmentFileService.removeFile(crudService.get(inventoryId), attachmentId);
}
@PostMapping(value = "/attach/{attachId}/share", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "shareAttachment", description = "Share attachment to inventories", summary = "Share attachment file")
public void shareAttachment(@PathVariable(name = "attachId") final Long attachId, @RequestBody @NotEmpty List<Long> inventoryIds) {
crudService.shareAttachment(attachId, inventoryIds);
}
@PostMapping(value = "/split", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "split", description = "Split inventory", summary = "split")
public List<Inventory> splitInventory(@RequestBody @Valid InventoryService.SplitInventoryRequest splitInventoryRequest) {
return crudService.splitInventory(splitInventoryRequest);
}
@PostMapping(value = "/assign-location", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "assignLocation", description = "Assign location to inventories", summary = "Assign location")
public List<Inventory> assignLocation(@RequestBody InventoryService.AssignLocationRequest assignLocationRequest) {
return crudService.assignLocation(assignLocationRequest);
}
@PostMapping(value = "/assign-locations", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "assignLocations", description = "Assign multiple location to inventories", summary = "Assign locations")
public List<Inventory> assignLocations(@RequestBody List<InventoryService.AssignLocationRequest> assignLocationRequests) {
return crudService.assignLocations(assignLocationRequests);
}
@PostMapping("/multiplication/order")
@Operation(summary = "Create multiplication OrderRequest", description = "Create new OrderRequest for multiplication")
public OrderRequest multiplicationOrder(@RequestBody @Valid MultiplicationOrderRequest request) {
return crudService.multiplicationOrder(request.site, request.inventoryIds, request.cooperator, request.orderTypeCode, request.intendedUseCode);
}
public static class MultiplicationOrderRequest {
public Site site;
@NotNull
public Cooperator cooperator;
@NotNull
@Size(min = 1)
public Set<Long> inventoryIds;
@NotNull
public String orderTypeCode;
@NotNull
public String intendedUseCode;
}
@PostMapping("/extra/{extraId}/moisture-content")
@Operation(summary = "Update moisture content", description = "Update moisture content in inventory extra")
public SeedInventoryExtra update(@PathVariable(name = "extraId") final Long extraId, @RequestBody @Valid InventoryService.MoistureContentRequest request) {
return crudService.updateMoistureContent((SeedInventoryExtra) inventoryExtraService.load(extraId), request);
}
@PostMapping("/site/compare")
@Operation(summary = "Compare sites", description = "Compare the number of inventories across sites")
public Page<ComparedSitesResponse> compareSites(@ParameterObject final Pagination page, @RequestBody @Valid CompareSitesRequest request) {
return crudService.compareSites(request.filter, request.sites, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE));
}
public static class CompareSitesRequest {
public AccessionFilter filter;
@NotNull
@Size(min = 2)
public Map<Long, NumberFilter<Long>> sites;
}
@RestController("accessionInvAttachApi1")
@RequestMapping(AccessionInvAttachController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = "Inventory")
public static class AccessionInvAttachController extends CRUDController<AccessionInvAttach, InventoryAttachmentService> {
/** The Constant API_URL. */
public static final String API_URL = InventoryController.API_URL + "/attach/meta";
@Override
@Operation(operationId = "createAccessionInvAttach", description = "Create AccessionInvAttach", summary = "Create")
public AccessionInvAttach create(@RequestBody AccessionInvAttach entity) {
// Throws UnsupportedOperationException
return super.create(entity);
}
@Override
@Operation(operationId = "updateAccessionInvAttach", description = "Update an existing record", summary = "Update")
public AccessionInvAttach update(@RequestBody AccessionInvAttach entity) {
return super.update(entity);
}
@Override
@Operation(operationId = "getAccessionInvAttach", description = "Get record by ID", summary = "Get")
public AccessionInvAttach get(@PathVariable long id) {
return super.get(id);
}
@Override
@Operation(operationId = "deleteAccessionInvAttach", description = "Delete existing record by ID", summary = "Delete")
public AccessionInvAttach remove(@PathVariable long id) {
return super.remove(id);
}
}
@RestController("inventoryActionApi1")
@RequestMapping(InventoryActionController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = "Inventory")
public static class InventoryActionController extends ActionController<InventoryAction, InventoryActionFilter, InventoryActionRequest, InventoryActionScheduleFilter, InventoryActionService> {
public static final String API_URL = InventoryController.API_URL;
@Override
protected Class<InventoryActionFilter> filterType() {
return InventoryActionFilter.class;
}
@Override
protected OrderSpecifier<?>[] defaultSort() {
return new OrderSpecifier[] { QInventoryAction.inventoryAction.createdDate.desc() };
}
}
@RestController("inventoryQualityStatusApi1")
@RequestMapping(InventoryQualityStatusController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = "Inventory")
public static class InventoryQualityStatusController extends FilteredCRUDController<InventoryQualityStatus, InventoryQualityStatusService, InventoryQualityStatusFilter> {
public static final String API_URL = InventoryController.API_URL + "/quality";
}
}