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.wms.transport;
import jakarta.persistence.AttributeOverride;
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.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.ameba.i18n.Translator;
import org.ameba.integration.jpa.ApplicationEntity;
import org.openwms.values.Synchronizable;
import org.openwms.wms.api.TimeProvider;
import org.openwms.wms.location.Location;
import org.openwms.wms.transport.barcode.Barcode;
import org.openwms.wms.transport.commands.TransportUnitCommand;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.ServiceLoader;
import static org.openwms.wms.api.TimeProvider.DATE_TIME_WITH_TIMEZONE;
/**
* A TransportUnit.
*
* @author Heiko Scherrer
*/
@Entity
@Table(name = "WMS_INV_TRANSPORT_UNIT", uniqueConstraints = {
@UniqueConstraint(name = "UC_INV_TU_BARCODE", columnNames = {"C_TRANSPORT_UNIT_BK"}),
@UniqueConstraint(name = "UC_INV_TU_FOREIGN_PID", columnNames = {"C_FOREIGN_PID"})
})
public class TransportUnit extends ApplicationEntity implements Serializable, Synchronizable {
@Transient
private final transient TimeProvider timeProvider = ServiceLoader.load(TimeProvider.class).iterator().next();
/** The foreign persistent key of the {@code Location}. */
@Column(name = "C_FOREIGN_PID")
private String foreignPKey;
/** The business key of the {@code TransportUnit}. */
@Embedded
@AttributeOverride(name = "value", column = @Column(name = "C_TRANSPORT_UNIT_BK", nullable = false))
@NotNull
private Barcode transportUnitBK;
/** The current {@code Location} coordinate of the {@code TransportUnit}. */
@NotNull
@ManyToOne
@JoinColumn(name = "C_ACTUAL_LOCATION", nullable = false, foreignKey = @ForeignKey(name = "FK_LOC_PK"))
private Location actualLocation;
/** Date when the {@code TransportUnit} has been moved to the current {@code Location}. */
@Column(name = "C_ACTUAL_LOCATION_DATE")
private LocalDateTime actualLocationDate;
/** The current target {@code Location} coordinate or {@code LocationGroup} name of the {@code TransportUnit}. */
@Column(name = "C_TARGET")
private String target;
/** State of the {@code TransportUnit}. */
@Column(name = "C_STATE")
private String state = DEFAULT_STATE;
/** The default for the TU state. */
public static final String DEFAULT_STATE = "AVAILABLE";
/** The {@code TransportUnitType} of the {@code TransportUnit}. */
@Column(name = "C_TRANSPORT_UNIT_TYPE", nullable = false)
@NotEmpty
private String transportUnitType;
/** The current length of the {@code TransportUnit}. */
@Column(name = "C_LENGTH")
private Integer length;
/** The current width of the {@code TransportUnit}. */
@Column(name = "C_WIDTH")
private Integer width;
/** The current height of the {@code TransportUnit}. */
@Column(name = "C_HEIGHT")
private Integer height;
/** Whether this instance has been synchronized with the master data source correctly. */
@Column(name = "C_ACKNOWLEDGED")
private boolean acknowledged;
/** An optional assignment to a customer order. */
@Column(name = "C_CUSTOMER_ORDER_ID")
private String customerOrderId;
/** The {@code User} who performed the last reconciliation on the {@code TransportUnit}. */
@Column(name = "C_RECONCILED_BY")
private String reconciledBy;
/** Date of last reconciliation. */
@Column(name = "C_RECONCILED_AT", columnDefinition = "timestamp(0)")
@DateTimeFormat(pattern = DATE_TIME_WITH_TIMEZONE)
private ZonedDateTime reconciledAt;
/*~ -------------------------- constructors -------------------------- */
/** Dear JPA ... */
protected TransportUnit() { }
private TransportUnit(Builder builder) {
super.setPersistentKey(builder.pKey);
foreignPKey = builder.foreignPKey;
if (builder.transportUnitBK != null) {
setTransportUnitBK(builder.transportUnitBK.getValue());
}
actualLocation = builder.actualLocation;
actualLocationDate = builder.actualLocationDate;
target = builder.target;
setState(builder.state);
transportUnitType = builder.transportUnitType;
length = builder.length;
width = builder.width;
height = builder.height;
acknowledged = builder.acknowledged;
customerOrderId = builder.customerOrderId;
reconciledBy = builder.reconciledBy;
reconciledAt = builder.reconciledAt;
}
public static Builder newBuilder() {
return new Builder();
}
/*~ -------------------------- methods -------------------------- */
/**
* Block the TransportUnit from being used.
*
* @param eventPublisher An instance of the EventPublisher in order to send proper events if this happens
* @deprecated Unused
*/
@Deprecated(forRemoval = true)
public void block(final ApplicationEventPublisher eventPublisher) {
this.setState("BLOCKED");
var tuCommand = new TransportUnitCommand(TransportUnitCommand.Type.BLOCK, this);
eventPublisher.publishEvent(tuCommand);
}
/**
* @see Location#verifyFreeSpaceAvailable(Translator, int)
*/
public void validateAndSetActualLocation(final Translator translator, final Location actualLocation, final int numberOfTransportUnits) {
actualLocation.verifyFreeSpaceAvailable(translator, numberOfTransportUnits);
this.actualLocation = actualLocation;
this.actualLocationDate = timeProvider.nowAsZonedDateTime().toLocalDateTime();
}
public void synchronizeActualLocationChange(Location actualLocation, LocalDateTime actualLocationDate) {
setActualLocation(actualLocation);
setActualLocationDate(actualLocationDate);
}
/*~ -------------------------- accessors -------------------------- */
public String getForeignPKey() {
return foreignPKey;
}
public void setForeignPKey(String foreignPKey) {
this.foreignPKey = foreignPKey;
}
public Barcode getTransportUnitBK() {
return transportUnitBK;
}
public void setTransportUnitBK(String transportUnitBK) {
this.transportUnitBK = Barcode.of(transportUnitBK);
}
public Location getActualLocation() {
return actualLocation;
}
/**
* Set the actualLocation.
*
* @param actualLocation The actualLocation to set
* @deprecated Used for the Mapper only, don't call from application code.
*/
@Deprecated
public void setActualLocation(Location actualLocation) {
this.actualLocation = actualLocation;
}
public LocalDateTime getActualLocationDate() {
return actualLocationDate;
}
/**
* Set the actualLocationDate.
*
* @param actualLocationDate The actualLocationDate to set
* @deprecated Used for the Mapper only, don't call from application code.
*/
@Deprecated
private void setActualLocationDate(LocalDateTime actualLocationDate) {
this.actualLocationDate = actualLocationDate;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
public String getState() {
return state;
}
public void setState(String state) {
if (state == null) {
state = DEFAULT_STATE;
}
this.state = state;
}
public String getTransportUnitType() {
return transportUnitType;
}
public void setTransportUnitType(String transportUnitType) {
this.transportUnitType = transportUnitType;
}
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
this.length = length;
}
public Integer getWidth() {
return width;
}
public void setWidth(Integer width) {
this.width = width;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
public void acknowledge() {
this.acknowledged = true;
}
public boolean isAcknowledged() {
return acknowledged;
}
public String getCustomerOrderId() {
return customerOrderId;
}
public void setCustomerOrderId(String customerOrderId) {
this.customerOrderId = customerOrderId;
}
public String getReconciledBy() {
return reconciledBy;
}
public void setReconciledBy(String reconciledBy) {
this.reconciledBy = reconciledBy;
}
public ZonedDateTime getReconciledAt() {
return reconciledAt;
}
public void setReconciledAt(ZonedDateTime reconciledAt) {
this.reconciledAt = reconciledAt;
}
@Override
public void setOl(long ol) {
super.setOl(ol);
}
public static final class Builder {
private String pKey;
private String foreignPKey;
private @NotEmpty Barcode transportUnitBK;
private Location actualLocation;
private LocalDateTime actualLocationDate;
private String target;
private String state;
private String transportUnitType;
private Integer length;
private Integer width;
private Integer height;
private boolean acknowledged;
private String customerOrderId;
private String reconciledBy;
private ZonedDateTime reconciledAt;
private Builder() {
}
public Builder foreignPKey(@NotEmpty String val) {
foreignPKey = val;
return this;
}
public Builder pKey(@NotEmpty String val) {
pKey = val;
return this;
}
public Builder transportUnitBK(@NotEmpty Barcode val) {
transportUnitBK = val;
return this;
}
public Builder actualLocation(Location val) {
actualLocation = val;
return this;
}
public Builder actualLocationDate(LocalDateTime val) {
actualLocationDate = val;
return this;
}
public Builder target(String val) {
target = val;
return this;
}
public Builder state(String val) {
if (val == null || val.isEmpty()) {
state = DEFAULT_STATE;
}
state = val;
return this;
}
public Builder transportUnitType(String val) {
transportUnitType = val;
return this;
}
public Builder length(Integer val) {
length = val;
return this;
}
public Builder width(Integer val) {
width = val;
return this;
}
public Builder height(Integer val) {
height = val;
return this;
}
public Builder acknowledged(boolean val) {
acknowledged = val;
return this;
}
public Builder customerOrderId(String val) {
customerOrderId = val;
return this;
}
public Builder reconciledBy(String val) {
reconciledBy = val;
return this;
}
public Builder reconciledAt(ZonedDateTime val) {
reconciledAt = val;
return this;
}
public TransportUnit build() {
return new TransportUnit(this);
}
}
@Override
public String toString() {
return transportUnitBK == null ? "" : transportUnitBK.toString();
}
/**
* {@inheritDoc}
*
* All fields.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TransportUnit)) return false;
if (!super.equals(o)) return false;
TransportUnit that = (TransportUnit) o;
return acknowledged == that.acknowledged && Objects.equals(timeProvider, that.timeProvider) && Objects.equals(foreignPKey, that.foreignPKey) && Objects.equals(transportUnitBK, that.transportUnitBK) && Objects.equals(actualLocation, that.actualLocation) && Objects.equals(actualLocationDate, that.actualLocationDate) && Objects.equals(target, that.target) && Objects.equals(state, that.state) && Objects.equals(transportUnitType, that.transportUnitType) && Objects.equals(length, that.length) && Objects.equals(width, that.width) && Objects.equals(height, that.height) && Objects.equals(customerOrderId, that.customerOrderId) && Objects.equals(reconciledBy, that.reconciledBy) && Objects.equals(reconciledAt, that.reconciledAt);
}
/**
* {@inheritDoc}
*
* All fields.
*/
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), timeProvider, foreignPKey, transportUnitBK, actualLocation, actualLocationDate, target, state, transportUnitType, length, width, height, acknowledged, customerOrderId, reconciledBy, reconciledAt);
}
}