TransportUnit.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.common.transport;
- import jakarta.persistence.AttributeOverride;
- import jakarta.persistence.CascadeType;
- import jakarta.persistence.Column;
- import jakarta.persistence.Embedded;
- import jakarta.persistence.Entity;
- import jakarta.persistence.ForeignKey;
- import jakarta.persistence.JoinColumn;
- import jakarta.persistence.ManyToOne;
- import jakarta.persistence.OneToMany;
- import jakarta.persistence.OrderBy;
- import jakarta.persistence.Table;
- import jakarta.persistence.UniqueConstraint;
- import jakarta.validation.constraints.NotNull;
- import org.ameba.integration.jpa.ApplicationEntity;
- import org.ameba.integration.jpa.BaseEntity;
- import org.hibernate.envers.AuditOverride;
- import org.hibernate.envers.Audited;
- import org.hibernate.envers.NotAudited;
- import org.openwms.common.app.Default;
- import org.openwms.common.location.Location;
- import org.openwms.common.transport.barcode.Barcode;
- import org.openwms.common.transport.reservation.TransportUnitReservation;
- import org.openwms.core.units.api.Weight;
- import org.openwms.core.values.CoreTypeDefinitions;
- import org.springframework.beans.factory.annotation.Configurable;
- import org.springframework.util.Assert;
- import java.beans.ConstructorProperties;
- import java.io.Serializable;
- import java.time.LocalDateTime;
- import java.util.ArrayList;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Objects;
- import java.util.Set;
- import static org.hibernate.envers.RelationTargetAuditMode.NOT_AUDITED;
- /**
- * A TransportUnit is a physical item like a box, a toad, a bin, a pallet etc., used as a container that is moved between warehouse
- * {@code Location}s and might carry goods or other items like {@code LoadUnit}s on top. A TransportUnit must have some kind of identifier,
- * like a physical Barcode, an RFID tag or others. There might be projects where TransportUnits are solely identified by virtual identifiers
- * and don't have any physical identifiers. A TransportUnit may even carry other TransportUnits.
- *
- * @author Heiko Scherrer
- * @GlossaryTerm
- */
- @Configurable
- @Audited(targetAuditMode = NOT_AUDITED)
- @AuditOverride(forClass = ApplicationEntity.class)
- @AuditOverride(forClass = BaseEntity.class)
- @Entity
- @Table(name = "COM_TRANSPORT_UNIT", uniqueConstraints = @UniqueConstraint(name = "COM_TRANSPORT_UNIT_BARCODE", columnNames = {"C_BARCODE"}))
- public class TransportUnit extends ApplicationEntity implements Serializable {
- /** Unique natural key. */
- @Embedded
- @AttributeOverride(name = "value", column = @Column(name = "C_BARCODE", length = Barcode.BARCODE_LENGTH, nullable = false))
- @OrderBy
- private Barcode barcode;
- /** Indicates whether the {@code TransportUnit} is empty or not (nullable). */
- @Column(name = "C_EMPTY")
- private Boolean empty;
- /** A {@code TransportUnit} may belong to a group of {@code TransportUnits}. */
- @Column(name = "C_GROUP_NAME")
- private String groupName;
- /** Date when the {@code TransportUnit} has been moved to the current {@link Location}. */
- @Column(name = "C_ACTUAL_LOCATION_DATE")
- private LocalDateTime actualLocationDate;
- /** Weight of the {@code TransportUnit}. */
- @Embedded
- @AttributeOverride(name = "unitType", column = @Column(name = "C_WEIGHT_UOM", length = CoreTypeDefinitions.QUANTITY_LENGTH))
- @AttributeOverride(name = "magnitude", column = @Column(name = "C_WEIGHT"))
- private Weight weight = Weight.ZERO;
- /** State of the {@code TransportUnit}. */
- @Column(name = "C_STATE")
- private String state = TransportUnitState.AVAILABLE.name();
- /** The current {@link Location} of the {@code TransportUnit}. */
- @ManyToOne
- @JoinColumn(name = "C_ACTUAL_LOCATION", nullable = false, foreignKey = @ForeignKey(name = "COM_TU_FK_LOC_ACTUAL"))
- private Location actualLocation;
- /** The target {@link Location} of the {@code TransportUnit}. This property is set when a {@code TransportOrder} is started. */
- @ManyToOne
- @JoinColumn(name = "C_TARGET_LOCATION", foreignKey = @ForeignKey(name = "COM_TU_FK_LOC_TARGET"))
- private Location targetLocation;
- /** The {@link TransportUnitType} of the {@code TransportUnit}. */
- @ManyToOne
- @JoinColumn(name = "C_TRANSPORT_UNIT_TYPE", nullable = false, foreignKey = @ForeignKey(name = "COM_TU_FK_TUT"))
- private TransportUnitType transportUnitType;
- /** Owning {@code TransportUnit}. */
- @ManyToOne
- @JoinColumn(name = "C_PARENT", foreignKey = @ForeignKey(name = "COM_TU_FK_TU_PARENT"))
- private TransportUnit parent;
- /** The {@code User} who performed the last inventory action on the {@code TransportUnit}. */
- @Column(name = "C_INVENTORY_USER")
- private String inventoryUser;
- /** Date of last inventory check. */
- @Column(name = "C_INVENTORY_DATE")
- private LocalDateTime inventoryDate;
- /** A set of all child {@code TransportUnit}s, ordered by id. */
- @OneToMany(mappedBy = "parent", cascade = {CascadeType.MERGE, CascadeType.PERSIST})
- @OrderBy("actualLocationDate DESC")
- private Set<TransportUnit> children = new HashSet<>();
- /** A List of errors occurred on the {@code TransportUnit}. */
- @OneToMany(mappedBy = "transportUnit", cascade = {CascadeType.ALL})
- @NotAudited
- private List<UnitError> errors = new ArrayList<>(0);
- /** Tracks all active {@link TransportUnitReservation}s on this {@link TransportUnit}. */
- @OneToMany(mappedBy = "transportUnit", cascade = {CascadeType.ALL})
- private List<TransportUnitReservation> reservations;
-
- /*~ ----------------------------- constructors ------------------- */
- /** Dear JPA... */
- protected TransportUnit() { }
- @Default
- @ConstructorProperties({"barcode"})
- public TransportUnit(@NotNull Barcode barcode) {
- Assert.notNull(barcode, "Barcode must not be null");
- this.barcode = barcode;
- initInventory();
- }
- /**
- * Create a new {@code TransportUnit} with an unique {@link Barcode}.
- *
- * @param barcode The unique identifier of this {@code TransportUnit} is the {@link Barcode} - must not be {@literal null}
- * @param transportUnitType The {@code TransportUnitType} of this {@code TransportUnit} - must not be {@literal null}
- * @param actualLocation The current {@code Location} of this {@code TransportUnit} - must not be {@literal null}
- * @throws IllegalArgumentException when one of the params is {@literal null}
- */
- @ConstructorProperties({"barcode", "transportUnitType", "actualLocation"})
- public TransportUnit(@NotNull Barcode barcode, @NotNull TransportUnitType transportUnitType, @NotNull Location actualLocation) {
- Assert.notNull(barcode, "Barcode must not be null");
- Assert.notNull(transportUnitType, "TransportUnitType must not be null");
- Assert.notNull(actualLocation, "ActualLocation must not be null");
- this.barcode = barcode;
- setTransportUnitType(transportUnitType);
- setActualLocation(actualLocation);
- initInventory();
- }
- /*~ ----------------------------- methods ------------------- */
- /** Required for the Mapper. */
- @Override
- public void setPersistentKey(String pKey) {
- super.setPersistentKey(pKey);
- }
- /**
- * Get the actual {@link Location} of the {@code TransportUnit}.
- *
- * @return The {@link Location} where the {@code TransportUnit} is placed on
- */
- public Location getActualLocation() {
- return actualLocation;
- }
- /**
- * Place the {@code TransportUnit} and all its children to a {@link Location}.
- *
- * @param actualLocation The new {@link Location} of the {@code TransportUnit} and all its children
- * @throws IllegalArgumentException when {@code actualLocation} is {@literal null}
- */
- public void setActualLocation(Location actualLocation) {
- Assert.notNull(actualLocation, "ActualLocation must not be null, this: " + this);
- this.actualLocation = actualLocation;
- this.actualLocationDate = LocalDateTime.now();
- this.actualLocation.setLastMovement(this.actualLocationDate);
- if (this.getChildren() != null) {
- this.getChildren().forEach(child -> child.setActualLocation(actualLocation));
- }
- }
- /**
- * Initialize inventory info of the {@code TransportUnit}.
- */
- public void initInventory() {
- setInventoryUser("init");
- setInventoryDate(LocalDateTime.now());
- }
- /**
- * Get the target {@link Location} of the {@code TransportUnit}. This property can not be {@literal null} when an active {@code
- * TransportOrder} exists.
- *
- * @return The target location
- */
- public Location getTargetLocation() {
- return this.targetLocation;
- }
- /**
- * Set the target {@link Location} of the {@code TransportUnit}. Shall only be set in combination with an active {@code
- * TransportOrder}.
- *
- * @param targetLocation The target {@link Location} where this {@code TransportUnit} shall be transported to
- */
- public void setTargetLocation(Location targetLocation) {
- this.targetLocation = targetLocation;
- }
- /**
- * Indicates whether the {@code TransportUnit} is empty or not.
- *
- * @return {@literal true} if empty, {@literal false} if not empty, {@literal null} when not defined
- */
- public Boolean getEmpty() {
- return this.empty;
- }
- /**
- * Marks the {@code TransportUnit} to be empty.
- *
- * @param empty {@literal true} to mark the {@code TransportUnit} as empty, {@literal false} to mark it as not empty and {@literal null}
- * for no definition
- */
- public void setEmpty(Boolean empty) {
- this.empty = empty;
- }
- /**
- * Get the groupId.
- *
- * @return The groupId
- */
- public String getGroupName() {
- return groupName;
- }
- /**
- * Set the groupId.
- *
- * @param groupId The groupId
- */
- public void setGroupName(String groupId) {
- this.groupName = groupId;
- }
- /**
- * Returns the username of the User who performed the last inventory action on the {@code TransportUnit}.
- *
- * @return The username who did the last inventory check
- */
- public String getInventoryUser() {
- return this.inventoryUser;
- }
- /**
- * Set the username who performed the last inventory action on the {@code TransportUnit}.
- *
- * @param inventoryUser The username who did the last inventory check
- */
- public void setInventoryUser(String inventoryUser) {
- this.inventoryUser = inventoryUser;
- }
- /**
- * Number of {@code TransportUnit}s belonging to the {@code TransportUnit}.
- *
- * @return The number of all {@code TransportUnit}s belonging to this one
- */
- public int getNoTransportUnits() {
- return this.children.size();
- }
- /**
- * Returns the date when the {@code TransportUnit} moved to the actualLocation.
- *
- * @return The timestamp when the {@code TransportUnit} moved the last time
- */
- public LocalDateTime getActualLocationDate() {
- return actualLocationDate;
- }
- /**
- * Returns the timestamp of the last inventory check of the {@code TransportUnit}.
- *
- * @return The timestamp of the last inventory check of the {@code TransportUnit}.
- */
- public LocalDateTime getInventoryDate() {
- return inventoryDate;
- }
- /**
- * Set the timestamp of the last inventory action of the {@code TransportUnit}.
- *
- * @param inventoryDate The timestamp of the last inventory check
- * @throws IllegalArgumentException when {@code inventoryDate} is {@literal null}
- */
- public void setInventoryDate(LocalDateTime inventoryDate) {
- Assert.notNull(inventoryDate, () -> "InventoryDate must not be null, this: " + this);
- this.inventoryDate = inventoryDate;
- }
- /**
- * Returns the current weight of the {@code TransportUnit}.
- *
- * @return The current weight of the {@code TransportUnit}
- */
- public Weight getWeight() {
- return weight;
- }
- /**
- * Sets the current weight of the {@code TransportUnit}.
- *
- * @param weight The current weight of the {@code TransportUnit}
- */
- public void setWeight(Weight weight) {
- this.weight = weight;
- }
- public List<UnitError> getErrors() {
- return this.errors;
- }
- /**
- * Add an error to the {@code TransportUnit}.
- *
- * @param error An {@link UnitError} to be added
- * @return The key.
- * @throws IllegalArgumentException when {@code error} is {@literal null}
- */
- public UnitError addError(UnitError error) {
- Assert.notNull(error, () -> "Error must not be null, this: " + this);
- error.setTransportUnit(this);
- this.errors.add(error);
- return error;
- }
- /**
- * Checks whether this {@code TransportUnit} has one or more reservations.
- *
- * @return {@literal true} if so
- */
- public boolean hasReservations() {
- return this.reservations != null && !this.reservations.isEmpty();
- }
- /**
- * Return the state of the {@code TransportUnit}.
- *
- * @return The current state of the {@code TransportUnit}
- */
- public String getState() {
- return this.state;
- }
- /**
- * Set the state of the {@code TransportUnit}.
- *
- * @param state The state to set on the {@code TransportUnit}
- */
- public void setState(String state) {
- this.state = state;
- }
- /**
- * Return the {@link TransportUnitType} of the {@code TransportUnit}.
- *
- * @return The {@link TransportUnitType} the {@code TransportUnit} belongs to
- */
- public TransportUnitType getTransportUnitType() {
- return this.transportUnitType;
- }
- /**
- * Set the {@link TransportUnitType} of the {@code TransportUnit}.
- *
- * @param transportUnitType The type of the {@code TransportUnit}
- */
- public void setTransportUnitType(TransportUnitType transportUnitType) {
- Assert.notNull(transportUnitType, () -> "TransportUnitType must not be null, this: " + this);
- this.transportUnitType = transportUnitType;
- }
- /**
- * Return the {@link Barcode} of the {@code TransportUnit}.
- *
- * @return The current {@link Barcode}
- */
- public Barcode getBarcode() {
- return barcode;
- }
- /**
- * Returns the parent {@code TransportUnit}.
- *
- * @return the parent.
- */
- public TransportUnit getParent() {
- return parent;
- }
- /**
- * Set a parent {@code TransportUnit}.
- *
- * @param parent The parent to set.
- */
- public void setParent(TransportUnit parent) {
- this.parent = parent;
- }
- /**
- * Get all child {@code TransportUnit}s.
- *
- * @return the transportUnits.
- */
- public Set<TransportUnit> getChildren() {
- return this.children;
- }
- /**
- * Add a {@code TransportUnit} to the children.
- *
- * @param transportUnit The {@code TransportUnit} to be added to the list of children
- * @throws IllegalArgumentException when transportUnit is {@literal null}
- */
- public void addChild(TransportUnit transportUnit) {
- Assert.notNull(transportUnit, () -> "TransportUnitType must not be null, this: " + this);
- if (transportUnit.hasParent()) {
- if (transportUnit.getParent().equals(this)) {
- // if this instance is already the parent, we just return
- return;
- }
- // disconnect post from it's current relationship
- transportUnit.getParent().removeChild(transportUnit);
- }
- // make this instance the new parent
- transportUnit.setParent(this);
- this.children.add(transportUnit);
- }
- /**
- * Checks whether this {@code TransportUnit} has a parent {@code TransportUnit} or not.
- *
- * @return {@code true} it has a parent, otherwise {@code false}
- */
- public boolean hasParent() {
- return parent != null;
- }
- /**
- * Checks whether this {@code TransportUnit} has child {@code TransportUnit}s or not.
- *
- * @return {@code true} it has children, otherwise {@code false}
- */
- public boolean hasChildren() {
- return this.children != null && !this.children.isEmpty();
- }
- /**
- * Remove a {@code TransportUnit} from the list of children.
- *
- * @param transportUnit The {@code TransportUnit} to be removed from the list of children
- * @throws IllegalArgumentException when {@code transportUnit} is {@literal null} or not a child of this instance
- */
- public void removeChild(TransportUnit transportUnit) {
- Assert.notNull(transportUnit, () -> "TransportUnit must not be null, this: " + this);
- // make sure this is the parent before we break the relationship
- if (transportUnit.parent == null || !transportUnit.parent.equals(this)) {
- throw new IllegalArgumentException("Child TransportUnit not associated with this instance, this: " + this);
- }
- transportUnit.setParent(null);
- this.children.remove(transportUnit);
- }
- /**
- * {@inheritDoc}
- *
- * Return the {@link Barcode} as String.
- */
- @Override
- public String toString() {
- return barcode.toString();
- }
- /**
- * {@inheritDoc}
- *
- * Uses barcode for comparison.
- */
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- var that = (TransportUnit) o;
- return barcode.equals(that.barcode);
- }
- /**
- * {@inheritDoc}
- *
- * Uses barcode for calculation.
- */
- @Override
- public int hashCode() {
- return Objects.hash(barcode);
- }
- }