InventoryService.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;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.genesys.blocks.model.filters.NumberFilter;
import org.gringlobal.api.FilteredPage;
import org.gringlobal.custom.validation.javax.CodeValueField;
import org.gringlobal.custom.validation.javax.SimpleString;
import org.gringlobal.model.Accession;
import org.gringlobal.model.AccessionInvAttach;
import org.gringlobal.model.AccessionInvGroup;
import org.gringlobal.model.AccessionInvName;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.Geography;
import org.gringlobal.model.DateVersionEntityId.EntityIdAndModifiedDate;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.InventoryAction;
import org.gringlobal.model.InventoryMaintenancePolicy;
import org.gringlobal.model.InventoryQualityStatus;
import org.gringlobal.model.InventoryViability;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.SeedInventoryExtra;
import org.gringlobal.model.Site;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.persistence.InventoryRepositoryCustom;
import org.gringlobal.service.filter.AccessionFilter;
import org.gringlobal.service.filter.InventoryActionFilter;
import org.gringlobal.service.filter.InventoryFilter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import com.fasterxml.jackson.annotation.JsonUnwrapped;

import lombok.Data;

/**
 * The Interface InventoryService.
 */
public interface InventoryService extends FilteredCRUDService2<Inventory, InventoryFilter> {

	Inventory getSystemInventory(String instituteCode, String accessionNumber);

	Inventory assureSystemInventory(Accession accession);

	int ensureSystemInventories();

	InventoryDetails getInventoryDetails(Inventory inventory);

	/**
	 * Set inventory quantity on hand
	 * 
	 * @param inventoryQuantity Inventory quantity
	 * @return the updated inventory
	 */
	Inventory setInventoryQuantity(@Valid InventoryQuantityRequest inventoryQuantity);

	/**
	 * Generate barcode for Inventory based on a template
	 *
	 * @param inventory the inventory
	 * @return the barcode
	 */
	String assignBarcode(Inventory inventory);

	/**
	 * Discard the specified quantities for inventories.
	 *
	 * @param discardQuantities A map of `inventoryId` and `quantity` to discard.
	 */
	void discardMaterial(@NotNull Map<Long, Integer> discardQuantities);

	/**
	 * Get the aggregated `quantityOnHand` for specified inventories.
	 *
	 * @param filter inventory filters
	 * @param page pagination and sort options
	 * 
	 * @return a {@link Page} of {@link InventoryRepositoryCustom.AggregatedInventoryQuantity}.
	 */
	Page<InventoryRepositoryCustom.AggregatedInventoryQuantity> aggregateQuantity(@NotNull InventoryFilter filter, Pageable page);

	List<Inventory> splitInventory(@Valid SplitInventoryRequest splitInventoryRequest);

	List<Inventory> assignLocation(@Valid AssignLocationRequest assignLocationRequest);

	List<Inventory> assignLocations(@Valid @Size(min = 1) List<AssignLocationRequest> assignLocationRequests);

	/**
	 * Get inventory by barcode value
	 *
	 * @param barcode the barcode
	 * @return the inventory
	 */
	Inventory getByBarcode(String barcode);

	class AssignLocationRequest {
		@NotNull
		public Location location;
		@NotNull
		@Size(min = 1)
		public Set<EntityIdAndModifiedDate> inventories;

		public static class Location {
			@NotNull
			public Long siteId;
			public String storageLocationPart1;
			public String storageLocationPart2;
			public String storageLocationPart3;
			public String storageLocationPart4;
		}
	}

	class InventoryDetails {
		@JsonUnwrapped
		public Inventory inventory;

		public List<AccessionInvName> names;
		public List<AccessionInvAttach> attachments;
		public List<AccessionInvGroup> groups;

		public List<InventoryAction> actions;
		public List<InventoryViability> viability;
		public List<InventoryQualityStatus> qualityStatus;
	}

	class InventoryQuantityRequest {
		@NotNull
		public long id; // inventory id
		@Min(value = 0)
		public double quantityOnHand; // Required new quantity
		@CodeValueField("UNIT_OF_QUANTITY")
		public String quantityOnHandUnitCode;
		@Min(value = 0)
		public Double hundredSeedWeight;
		public String note;
	}

	
	static class SplitInventoryRequest {
		public SourceSplitInventory sourceSplitInventory;
		public List<SplitInventory> splits;

		public static class SourceSplitInventory extends EntityIdAndModifiedDate {
			public String note; // note to add to split action
			@Min(value = 0)
			public Double quantityOnHand; // optionally update quantity
			@CodeValueField("UNIT_OF_QUANTITY")
			public String quantityOnHandUnitCode;
			@CodeValueField("CONTAINER_TYPE")
			public String containerTypeCode;
		}

		public static class SplitInventory {
			public InventoryMaintenancePolicyId inventoryMaintenancePolicy;
			@NotNull @SimpleString
			public String inventoryNumberPart1;
			@Min(-1)
			public Long inventoryNumberPart2;
			public String inventoryNumberPart3;

			@Min(value = 0)
			public Double quantityOnHand; // optional quantity of the new inventory
			@CodeValueField("UNIT_OF_QUANTITY")
			public String quantityOnHandUnitCode; // optional unit code
			@CodeValueField("CONTAINER_TYPE")
			public String containerTypeCode; // optional container type
			public String note;

			public static class InventoryMaintenancePolicyId {
				@NotNull
				public Long id; // inventory maintenance policy id
			}
		}
	}

	/**
	 * Inventory overview.
	 *
	 * @param groupBy Group by property of Inventory
	 * @param filter the inventory filters
	 * @return the map with statistics
	 */
	Map<?, ?> inventoryOverview(String groupBy, InventoryFilter filter);

	/**
	 * Recalculate all inventory_number fields in the DB for existing records.
	 */
	void recalculateAllInventoryNumbers();

	/**
	 * Calculate {@link Inventory#inventoryNumber} if null
	 */
	void assignMissingInventoryNumbers();

	/**
	 * Create multiplication OrderRequest for inventories.
	 */
	OrderRequest multiplicationOrder(Site site, Set<Long> inventoryIds, Cooperator multiplicationCooperator, String orderType, String intendedUseCode);

	/**
	 * Share attachment for inventories.
	 */
	void shareAttachment(Long attachId, List<Long> inventoryIds);

	/**
	 * Update moisture content for SeedInventoryExtra.
	 */
	SeedInventoryExtra updateMoistureContent(SeedInventoryExtra extra, MoistureContentRequest request);
	
	class MoistureContentRequest {

		@Min(0) @Max(100)
		public Float moistureContent;

		public Date moistureContentDate;

		@CodeValueField(CommunityCodeValues.DATE_FORMAT)
		public String moistureContentDateCode;
	}

	/**
	 * Compare the number of inventories across sites
	 */
	Page<ComparedSitesResponse> compareSites(AccessionFilter filter, Map<Long, NumberFilter<Long>> sites, Pageable page);

	@Data
	public static class ComparedSitesResponse {
		private Long accessionId;
		private String accessionNumber;
		private Map<String, Long> siteInventories;
	}

	/**
	 * Delete the specified inventory, clearing all data associated with it
	 *
	 * @param inventory the Inventory
	 * @return the removed inventory
	 */
	@Override
	Inventory remove(Inventory inventory);

	/**
	 * List inventories with HARVEST action
	 */
	FilteredPage<InventoryHarvest, InventoryActionFilter> listInventoryHarvest(InventoryActionFilter filter, Pageable page);

	/**
	 * Data on the planted inventory, the associated HARVEST action, and the resulting harvested inventory.
	 */
	@Data
	public class InventoryHarvest {

		public InventoryHarvest(InventoryAction action) {
			assert(action.getActionGroupName().equals(CommunityCodeValues.INVENTORY_ACTION) && action.getActionNameCode().equals(CommunityCodeValues.INVENTORY_ACTION_HARVEST.value));
			this.plantedInventory = action.getInventory();
			this.harvestAction = action;
		}

		/** The planted inventory */
		private Inventory plantedInventory;

		/** The resulting harvested inventory */
		private Inventory harvestedInventory;

		/** The associated harvest action */
		private InventoryAction harvestAction;

		// Storage location
		private String storageLocationPart1;
		private String storageLocationPart2;
		private String storageLocationPart3;
		private String storageLocationPart4;
	}

	/**
	 * Register harvested inventory
	 * 
	 * @param isFinalBatch IF harvesting the final batch, then the HARVEST action will be closed
	 */
	InventoryHarvest createHarvestedInventory(Inventory plantedInventory, Site site, InventoryMaintenancePolicy harvestedInventoryMaintenancePolicy, String inventoryNumberPart1, String inventoryNumberPart3, Double harvestedQuantity, String harvestedQuantityUnitCode, String containerTypeCode, Geography productionLocation, String storageLocationPart1, String storageLocationPart2, String storageLocationPart3, String storageLocationPart4, boolean isFinalBatch);

	/** Check if two units of measure can be convered with {@link #convertQuantity(String, Double, String, Double)} */
	boolean areCompatibleUnits(String unitCode1, String unitCode2, Double hundredSeedWeight);

	/**
	 * Convert quantity between compatible units
	 *
	 * @param fromUnit source unit
	 * @param quantity source quantity
	 * @param toUnit target unit
	 * @param hundredSeedWeight hundred seed weight
	 * @return converted quantity in target units
	 */
	Double convertQuantity(String fromUnit, Double quantity, String toUnit, Double hundredSeedWeight);

}