PackagingUnit.java
/*
* Copyright 2005-2025 the original author or authors.
*
* 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.openwms.wms.inventory;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.CascadeType;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.persistence.Transient;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.ameba.integration.jpa.ApplicationEntity;
import org.ameba.integration.jpa.BaseEntity;
import org.hibernate.annotations.CompositeType;
import org.hibernate.envers.AuditOverride;
import org.hibernate.envers.Audited;
import org.hibernate.envers.NotAudited;
import org.openwms.core.units.UnitConstants;
import org.openwms.core.units.api.Measurable;
import org.openwms.core.units.api.Piece;
import org.openwms.core.units.api.Weight;
import org.openwms.core.units.persistence.UnitUserType;
import org.openwms.values.Message;
import org.openwms.wms.api.TimeProvider;
import org.openwms.wms.inventory.api.AvailabilityState;
import org.openwms.wms.location.Location;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.StringJoiner;
import java.util.UUID;
import static org.openwms.wms.api.TimeProvider.DATE_TIME_WITH_TIMEZONE;
import static org.openwms.wms.inventory.InventoryStringListConverter.STRING_LIST_LENGTH;
/**
* A PackagingUnit represents a quantity of a {@link Product} packed into a single physical unit.
*
* @author Heiko Scherrer
*/
@Audited
@AuditOverride(forClass = ApplicationEntity.class)
@AuditOverride(forClass = BaseEntity.class)
@Entity
@Table(name = "WMS_INV_PACKAGING_UNIT")
public class PackagingUnit extends ApplicationEntity implements Serializable {
@Transient
private final transient TimeProvider timeProvider = ServiceLoader.load(TimeProvider.class).iterator().next();
private static final String LOAD_UNIT_MUST_NOT_BE_NULL = "LoadUnit must not be null";
private static final String LOCATION_MUST_NOT_BE_NULL = "Location must not be null";
private static final String QUANTITY_MUST_NOT_BE_NULL = "Quantity must not be null";
private static final String PRODUCT_MUST_NOT_BE_NULL = "Product must not be null";
/** Some hint where to find the {@link PackagingUnit} within its container. */
@Column(name = "C_PHYSICAL_POS")
public String physicalPosition;
/** The packaged {@link Product}. */
@ManyToOne(fetch = FetchType.LAZY) //Define FetchType.LAZY for @ManyToOne association
@JoinColumn(name = "C_PRODUCT_PK", referencedColumnName = "C_PK", foreignKey = @ForeignKey(name = "FK_PU_PRODUCT_PK"))
private Product product;
/**
* A non-unique serial number, that might be {@literal null} in projects. Non-unique because a {@link PackagingUnit} can be split up
* into multiples that share the same serial.
*/
@Column(name = "C_SERIAL_NUMBER")
private String serialNumber;
/** The business key referring to a defined {@code Lot}. */
@Column(name = "C_LOT_ID")
private String lotId;
/**
* The expiration date of this particular {@link PackagingUnit}. The {@code Lot} may also store an expiration date that is only
* considered when it is not explicitly overridden on the {@link PackagingUnit}.
*/
@Column(name = "C_EXPIRES_AT", columnDefinition = "timestamp(0)")
@DateTimeFormat(pattern = DATE_TIME_WITH_TIMEZONE)
private ZonedDateTime expiresAt;
/** The production date of this particular {@link PackagingUnit}. */
@Column(name = "C_PRODUCED_AT", columnDefinition = "timestamp(0)")
@DateTimeFormat(pattern = DATE_TIME_WITH_TIMEZONE)
private ZonedDateTime producedAt;
/** A {@link PackagingUnit} can have arbitrary labels assigned. */
@Size(max = STRING_LIST_LENGTH)
@Column(name="C_LABELS", length = STRING_LIST_LENGTH)
@Convert(converter = InventoryStringListConverter.class)
private List<String> labels;
/** The current quantity of this {@link PackagingUnit}. */
@NotNull
@CompositeType(UnitUserType.class)
@AttributeOverride(name = "unitType", column = @Column(name = "C_QTY_TYPE", nullable = false))
@AttributeOverride(name = "magnitude", column = @Column(name = "C_QTY", length = UnitConstants.QUANTITY_LENGTH, nullable = false))
private Measurable<?,?,?> quantity;
/** Tracks all active {@link Reservation}s on this {@link PackagingUnit}. */
@OneToMany(mappedBy = "packagingUnit", cascade = {CascadeType.ALL})
private List<Reservation> reservations;
/** Availability state of this {@link PackagingUnit}. */
@NotNull
@Column(name = "C_AV_STATE", nullable = false)
@Enumerated(EnumType.STRING)
private AvailabilityState availabilityState = AvailabilityState.AVAILABLE;
/** The first-in-first-out date is used to control the allocation strategy. */
@Column(name = "C_FIFO_DATE")
@Temporal(TemporalType.TIMESTAMP)
private Date fifoDate;
/** The carrying {@link LoadUnit}. */
@ManyToOne
@JoinColumn(name = "C_LOAD_UNIT", foreignKey = @ForeignKey(name = "FK_LU_PK"))
private LoadUnit loadUnit;
/** The {@link Location} the {@link PackagingUnit} is placed on. */
@ManyToOne
@JoinColumn(name = "C_ACTUAL_LOCATION", foreignKey = @ForeignKey(name = "FK_PU_LOC_PK"))
private Location actualLocation;
/** The current weight of the {@link PackagingUnit}. */
@CompositeType(UnitUserType.class)
@AttributeOverride(name = "unitType", column = @Column(name = "C_WEIGHT_TYPE"))
@AttributeOverride(name = "magnitude", column = @Column(name = "C_WEIGHT"))
private Weight weight;
/** The current dimension of the {@link PackagingUnit}. */
@Embedded
private Dimension dimension;
/** A message placed on the {@link PackagingUnit}. */
@Embedded
private Message message;
/** Is the {@code PackagingUnit} marked for deletion? */
@Column(name = "C_ALIVE")
private Boolean alive;
/* ------------------- collection mapping ------------------- */
/** Parent {@link PackagingUnit}. */
@ManyToOne
@JoinColumn(name = "C_PARENT", foreignKey = @ForeignKey(name = "FK_PU_PU_PARENT"))
private PackagingUnit parent;
/** Described in what kind of UOM the {@link PackagingUnit} is stored in. */
@ManyToOne
@JoinColumn(name = "C_UOM_RELATION", foreignKey = @ForeignKey(name = "FK_PU_UOMREL"))
private UomRelation uomRelation;
/** Child {@link PackagingUnit}s. */
@NotAudited
@OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL})
private Set<PackagingUnit> packagingUnits = new HashSet<>();
/** Arbitrary detail information on this {@link PackagingUnit}. */
@ElementCollection
@CollectionTable(name = "WMS_INV_PACKAGING_UNIT_DETAIL",
joinColumns = {
@JoinColumn(name = "C_PU_PK", referencedColumnName = "C_PK")
},
foreignKey = @ForeignKey(name = "FK_DETAILS_PU")
)
@MapKeyColumn(name = "C_KEY")
@Column(name = "C_VALUE")
private Map<String, String> details = new HashMap<>();
/*~ ----------------------------- constructors ------------------- */
/** Dear JPA... */
protected PackagingUnit() {}
/**
* Create a {@link PackagingUnit} with the quantity of {@code qty} and the {@link Product} taken from the {@link LoadUnit lu}.
*
* @param lu The LoadUnit where this PackingUnit is stored in
* @param qty The quantity
*/
public PackagingUnit(LoadUnit lu, Measurable qty) {
Assert.notNull(lu, LOAD_UNIT_MUST_NOT_BE_NULL);
Assert.notNull(qty, QUANTITY_MUST_NOT_BE_NULL);
Assert.notNull(lu.getProduct(), "LoadUnit.Product must not be null");
this.quantity = qty;
this.product = lu.getProduct();
this.alive = true;
assignInitialValues(lu);
}
/**
* Create a {@link PackagingUnit} with the quantity of {@code qty} and the {@link Product} within the given
* {@link LoadUnit}.
*
* @param lu The LoadUnit where this PackingUnit is stored in
* @param qty The quantity
* @param product The Product
*/
public PackagingUnit(LoadUnit lu, Measurable qty, Product product) {
Assert.notNull(lu, LOAD_UNIT_MUST_NOT_BE_NULL);
Assert.notNull(qty, QUANTITY_MUST_NOT_BE_NULL);
Assert.notNull(product, PRODUCT_MUST_NOT_BE_NULL);
this.quantity = qty;
this.product = product;
this.alive = true;
assignInitialValues(lu);
}
/**
* Create a {@link PackagingUnit} with the quantity of {@code qty} and the {@link Product}.
*
* @param qty The quantity
* @param product The Product
*/
public PackagingUnit(Measurable qty, Product product) {
Assert.notNull(qty, QUANTITY_MUST_NOT_BE_NULL);
Assert.notNull(product, PRODUCT_MUST_NOT_BE_NULL);
this.quantity = qty;
this.product = product;
this.alive = true;
}
/*~ ----------------------------- methods ------------------- */
public void assignPKey() {
setPersistentKey(UUID.randomUUID().toString());
}
public PackagingUnit makeSplitPU(LoadUnit loadUnit, Measurable qtyPicked, Product product) {
var targetPU = new PackagingUnit(loadUnit, qtyPicked, product);
targetPU.setAvailabilityState(AvailabilityState.AVAILABLE);
targetPU.setFifoDate(timeProvider.nowAsDate());
targetPU.setLotId(this.getLotId());
targetPU.setSerialNumber(this.getSerialNumber());
targetPU.setExpiresAt(this.getExpiresAt());
targetPU.setAlive(this.getAlive());
return targetPU;
}
public PackagingUnit makeSplitPU(Measurable newQuantity, Product product) {
var targetPU = new PackagingUnit(newQuantity, product);
targetPU.setAvailabilityState(AvailabilityState.AVAILABLE);
targetPU.setFifoDate(timeProvider.nowAsDate());
targetPU.setLotId(this.getLotId());
targetPU.setSerialNumber(this.getSerialNumber());
targetPU.setExpiresAt(this.getExpiresAt());
targetPU.setAlive(this.getAlive());
return targetPU;
}
private void assignInitialValues(LoadUnit lu) {
this.loadUnit = lu;
this.loadUnit.addPackagingUnit(this);
}
public boolean addReservation(Reservation reservation) {
if (!hasReservations()) {
this.reservations = new ArrayList<>(1);
}
return this.reservations.add(reservation);
}
public boolean removeReservation(Reservation reservation) {
if (hasReservations()) {
return this.reservations.remove(reservation);
}
return false;
}
public Measurable getQtyAvailable() {
if (hasReservations()) {
Measurable qtyReserved = Piece.ZERO;
List<Measurable> all = reservations.stream().map(Reservation::getQuantityReserved).toList();
for (Measurable measurable : all) {
qtyReserved = qtyReserved.add(measurable);
}
return this.quantity.subtract(qtyReserved);
}
return this.quantity;
}
/**
* Check whether this {@link PackagingUnit} has a quantity less than 1.
*
* @return {@literal true} if so, otherwise {@literal false}
*/
public boolean isEmpty() {
return quantity == null || quantity.isZero() || quantity.isNegative();
}
public boolean hasActualLocation() {
return this.actualLocation != null;
}
@Transient
@Valid
public boolean isValid() {
return (this.loadUnit != null && this.actualLocation == null) ||
(this.loadUnit == null && this.actualLocation != null);
}
/*~ ----------------------------- accessors ------------------- */
/**
* Get the physicalPosition.
*
* @return The physicalPosition
*/
public String getPhysicalPosition() {
return physicalPosition;
}
/**
* Set the physicalPosition.
*
* @param physicalPosition The physicalPosition
*/
public void setPhysicalPosition(String physicalPosition) {
this.physicalPosition = physicalPosition;
}
/**
* Get the product.
*
* @return the product.
*/
public Product getProduct() {
return product;
}
/**
* Set or override the {@link Product}.
*
* @param product The product to set
*/
public void setProduct(Product product) {
Assert.notNull(product, PRODUCT_MUST_NOT_BE_NULL);
this.product = product;
}
/**
* Get the serialNumber.
*
* @return The serialNumber or {@literal null}
*/
public String getSerialNumber() {
return serialNumber;
}
/**
* Set or override the serialNumber.
*
* @param serialNumber The serialNumber to set
*/
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
/**
* Get the persistent key of the {@code Lot}.
*
* @return The persistent key or {@literal null}
*/
public String getLotId() {
return lotId;
}
/**
* Set or override the persistent key of the {@code Lot}.
*
* @param lotPKey The persistent key of the Lot
*/
public void setLotId(String lotPKey) {
this.lotId = lotPKey;
}
/**
* When the {@link PackagingUnit} is going to expire.
*
* @return As date
*/
public ZonedDateTime getExpiresAt() {
return expiresAt;
}
/**
* Set the expiration date of the {@link PackagingUnit}.
*
* @param expiresAt As date
*/
public void setExpiresAt(ZonedDateTime expiresAt) {
this.expiresAt = expiresAt;
}
/**
* Get the date when the {@link PackagingUnit} has been produced.
*
* @return As date
*/
public ZonedDateTime getProducedAt() {
return producedAt;
}
/**
* Set the production date of the {@link PackagingUnit}.
*
* @param producedAt As date
*/
public void setProducedAt(ZonedDateTime producedAt) {
this.producedAt = producedAt;
}
/**
* Get the labels assigned to the {@link PackagingUnit}.
*
* @return As a list of strings
*/
public List<String> getLabels() {
return labels;
}
/**
* Set the labels on the {@link PackagingUnit}.
*
* @param labels A list of strings to assigne
*/
public void setLabels(List<String> labels) {
this.labels = labels;
}
/**
* Get the quantity.
*
* @return the quantity or {@literal null}
*/
public Measurable getQuantity() {
return quantity;
}
/**
* Set or override the quantity.
*
* @param qty The quantity to set
*/
public void setQuantity(Measurable qty) {
Assert.notNull(qty, QUANTITY_MUST_NOT_BE_NULL);
this.quantity = qty;
}
/**
* Get the reservation.
*
* @return Or {@literal null} if not reserved
*/
public List<Reservation> getReservations() {
return reservations;
}
/**
* Checks whether this {@link PackagingUnit} has reservations or not.
*
* @return {@literal true} if it has reservations on it otherwise {@literal false}
*/
public boolean hasReservations() {
return reservations != null && !reservations.isEmpty();
}
/**
* Set or override reservations on this {@link PackagingUnit}.
*
* @param reservations The reservations to set
*/
public void setReservations(List<Reservation> reservations) {
this.reservations = reservations;
}
/**
* Checks whether this {@link PackagingUnit} is reserved by someone with the {@code reservationId}.
*
* @param reservationId Compared against the reservation.reservedBy field
* @return {@literal true if it matches}
*/
public Optional<Reservation> getReservedBy(String reservationId) {
if (reservationId == null || reservationId.isEmpty()) {
throw new IllegalStateException("ReservationId is null, can't check this PackagingUnit is reserved by someone");
}
if (hasReservations()) {
return reservations.stream()
.filter(r -> reservationId.equals(r.getReservedBy()))
.findFirst();
}
return Optional.empty();
}
/**
* Checks whether this {@link PackagingUnit} is reserved by someone with the {@code reservationId}.
*
* @param reservationId Compared against the reservation.reservedBy field
* @return {@literal true} if it matches
*/
public boolean isReservedBy(String reservationId) {
return getReservedBy(reservationId).isPresent();
}
/**
* Get the availabilityState.
*
* @return the availabilityState
*/
public AvailabilityState getAvailabilityState() {
return availabilityState;
}
public boolean hasAvailabilityState() {
return this.availabilityState != null;
}
/**
* Set or override the availabilityState.
*
* @param availabilityState The availabilityState to set
*/
public void setAvailabilityState(AvailabilityState availabilityState) {
if (availabilityState == null) {
return;
}
this.availabilityState = availabilityState;
}
/**
* Get the fifoDate.
*
* @return the fifoDate or {@literal null}
*/
public Date getFifoDate() {
if (fifoDate == null) {
return null;
}
return new Date(fifoDate.getTime());
}
/**
* Set or override the fifoDate.
*
* @param fifoDate The fifoDate
*/
public void setFifoDate(Date fifoDate) {
this.fifoDate = fifoDate;
}
/**
* Set the fifoDate if not already initialized.
*
* @param fifoDate The fifoDate
*/
private void initFifoDate(Date fifoDate) {
if (this.fifoDate == null) {
this.fifoDate = fifoDate;
}
}
/**
* Get the {@link LoadUnit}.
*
* @return the loadUnit or {@literal null}
*/
public LoadUnit getLoadUnit() {
return loadUnit;
}
/**
* Checks if the {@link PackagingUnit} has a {@link LoadUnit} assigned.
*
* @return {@literal true} if so
*/
public boolean hasLoadUnit() {
return this.loadUnit != null;
}
/**
* Set or override the {@link LoadUnit} this {@link PackagingUnit} is stored in.
*
* @param loadUnit The LoadUnit instance
*/
public void setLoadUnit(LoadUnit loadUnit) {
Assert.notNull(loadUnit, LOAD_UNIT_MUST_NOT_BE_NULL);
this.loadUnit = loadUnit;
this.actualLocation = null; // As soon as a LoadUnit is assigned the Location is not tracked anymore
}
/** Set the assigned {@link LoadUnit} to {@literal null} and the {@link PackagingUnit} to the given {@code newLocation}. */
public void unbindFromLoadUnit(Location newLocation) {
Assert.notNull(newLocation, LOCATION_MUST_NOT_BE_NULL);
this.loadUnit = null;
this.actualLocation = newLocation;
}
/**
* Get the actual {@link Location}.
*
* @return The actual Location
*/
public Location getActualLocation() {
return actualLocation;
}
/**
* Set or override the actual {@link Location}.
*
* @param actualLocation The actualLocation
*/
public void setActualLocation(Location actualLocation) {
this.actualLocation = actualLocation;
}
/**
* Get the current weight.
*
* @return The weight
*/
public Weight getWeight() {
return weight;
}
/**
* Set or override the weight.
*
* @param weight The weight to set
*/
public void setWeight(Weight weight) {
this.weight = weight;
}
/**
* Get the current dimension.
*
* @return The dimension
*/
public Dimension getDimension() {
return dimension;
}
/**
* Set or override the dimension.
*
* @param dimension The dimension to set
*/
public void setDimension(Dimension dimension) {
this.dimension = dimension;
}
/**
* Get the current {@link Message}.
*
* @return The message
*/
public Message getMessage() {
return message;
}
/**
* Set or override a {@link Message}.
*
* @param message The message
*/
public void setMessage(Message message) {
this.message = message;
}
public Boolean getAlive() {
return alive;
}
public void setAlive(Boolean alive) {
if (alive == null || alive == Boolean.FALSE) {
// Do not allow to turn back already removed ones
return;
}
this.alive = alive;
}
public void kill() {
this.alive = Boolean.FALSE;
if (this.hasLoadUnit()) {
this.loadUnit.removePackagingUnits(this);
this.loadUnit = null;
}
if (this.hasActualLocation()) {
this.actualLocation = null;
}
this.product = null; // In order to be able to delete a Product as well
}
public UomRelation getUomRelation() {
return uomRelation;
}
public boolean hasUomRelation() {
return this.uomRelation != null;
}
public void setUomRelation(UomRelation uomRelation) {
this.uomRelation = uomRelation;
}
/**
* Get the parent {@link PackagingUnit}.
*
* @return The parent instance or {@literal null}
*/
public PackagingUnit getParent() {
return parent;
}
/**
* Whether the {@link PackagingUnit} has a parent or not.
*
* @return {@literal true} if it has a parent
*/
public boolean hasParent() {
return this.parent != null;
}
/**
* Set the parent.
*
* @param parent May also be {@literal null}
*/
public void setParent(PackagingUnit parent) {
this.parent = parent;
}
/**
* Get the child {@link PackagingUnit}s.
*
* @return All children or {@literal null}
*/
public Set<PackagingUnit> getPackagingUnits() {
return packagingUnits;
}
/**
* Set the {@link PackagingUnit}.
*
* @param packagingUnits May also be {@literal null}
*/
public void setPackagingUnits(Set<PackagingUnit> packagingUnits) {
this.packagingUnits = packagingUnits;
}
/**
* Get the details.
*
* @return As Map of Strings
*/
public Map<String, String> getDetails() {
return details;
}
public void setDetails(Map<String, String> details) {
this.details = details;
}
/**
* {@inheritDoc}
*
* - Not the timeProvider
* - Not the packagingUnits
* - Not the reservations
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PackagingUnit)) return false;
if (!super.equals(o)) return false;
PackagingUnit that = (PackagingUnit) o;
return Objects.equals(physicalPosition, that.physicalPosition) && Objects.equals(product, that.product) && Objects.equals(serialNumber, that.serialNumber) && Objects.equals(lotId, that.lotId) && Objects.equals(expiresAt, that.expiresAt) && Objects.equals(labels, that.labels) && Objects.equals(quantity, that.quantity) && availabilityState == that.availabilityState && Objects.equals(fifoDate, that.fifoDate) && Objects.equals(loadUnit, that.loadUnit) && Objects.equals(actualLocation, that.actualLocation) && Objects.equals(weight, that.weight) && Objects.equals(dimension, that.dimension) && Objects.equals(message, that.message) && Objects.equals(parent, that.parent) && Objects.equals(uomRelation, that.uomRelation);
}
/**
* {@inheritDoc}
*
* - Not the timeProvider
* - Not the packagingUnits
* - Not the reservations
*/
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), physicalPosition, product, serialNumber, lotId, expiresAt, labels, quantity, availabilityState, fifoDate, loadUnit, actualLocation, weight, dimension, message, parent, uomRelation);
}
/**
* {@inheritDoc}
*
* - Not the timeProvider
* - Not the packagingUnits
* - Not the reservations
*/
@Override
public String toString() {
return new StringJoiner(", ", PackagingUnit.class.getSimpleName() + "[", "]")
.add("persistentKey='" + super.getPersistentKey() + "'")
.add("physicalPosition='" + physicalPosition + "'")
.add("product=" + product)
.add("serialNumber='" + serialNumber + "'")
.add("lotId='" + lotId + "'")
.add("expiresAt=" + expiresAt)
.add("labels=" + labels)
.add("quantity=" + quantity)
.add("availabilityState=" + availabilityState)
.add("fifoDate=" + fifoDate)
.add("loadUnit=" + loadUnit)
.add("actualLocation=" + actualLocation)
.add("weight=" + weight)
.add("dimension=" + dimension)
.add("message=" + message)
.add("parent=" + parent)
.add("uomRelation=" + uomRelation)
.toString();
}
public void validate() {
if (product != null && uomRelation != null) {
if (!product.equals(uomRelation.getProduct())) {
throw new ValidationException("PackagingUnit product is different to the one it references in it's uomRelation");
}
}
if (product == null && uomRelation == null) {
throw new ValidationException("Not allowed that both product and uomRelation are not set");
}
}
public void completeBeforeCreation(Date now) {
if (!this.hasAvailabilityState()) {
this.setAvailabilityState(AvailabilityState.AVAILABLE);
}
this.setAlive(true);
this.assignPKey();
this.initFifoDate(now);
}
}