Movement.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.movements.impl;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import org.ameba.integration.jpa.ApplicationEntity;
import org.openwms.common.transport.Barcode;
import org.openwms.wms.movements.Message;
import org.openwms.wms.movements.api.MovementType;
import org.openwms.wms.movements.api.StartMode;
import org.openwms.wms.movements.api.ValidationGroups;
import org.openwms.wms.movements.spi.DefaultMovementState;
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.List;
import java.util.Objects;
import static org.openwms.wms.movements.MovementConstants.DATE_TIME_WITH_TIMEZONE;
/**
* A Movement is a simple task to move a {@code TransportUnit} from one source {@code Location} to a target {@code Location}. This is
* often used for manual warehouses or manual activities.
*
* @author Heiko Scherrer
*/
@Entity
@Table(name = "MVM_MOVEMENT")
public class Movement extends ApplicationEntity implements Serializable {
/** The business key of the {@code TransportUnit} to move. */
@NotNull
@Embedded
@AttributeOverride(name = "value", column = @Column(name = "C_TRANSPORT_UNIT_BK", nullable = false))
private Barcode transportUnitBk;
/** Type of the {@code Movement}. */
@Column(name = "C_TYPE", nullable = false)
@Enumerated(EnumType.STRING)
@NotNull
private MovementType type;
/** Initiator of the {@code Movement}, who ordered or triggered it. */
@Column(name = "C_INITIATOR", nullable = false)
@NotNull
private String initiator;
/** The {@link MovementGroup}, the {@code Movement} belongs to. */
@ManyToOne
@JoinColumn(name = "C_GROUP_PK", nullable = true, foreignKey = @ForeignKey(name = "FK_MVM_GRP"))
private MovementGroup group;
/** Refers to the demanded {@code Product} for that the {@code Movement} has been created. */
@Column(name = "C_SKU")
private String sku;
/**
* A priority level of the {@code Movement}. The lower the value the lower the priority. The priority level affects the execution of the
* {@code Movement}. An order with high priority will be processed faster than those with lower priority.
*/
@Column(name = "C_PRIORITY", nullable = false)
@Enumerated(EnumType.STRING)
@NotNull
private PriorityLevel priority;
/** Defines how the resulting {@code TransportOrder} is started. */
@Column(name = "C_MODE", nullable = false)
@Enumerated(EnumType.STRING)
@NotNull
private StartMode mode = StartMode.MANUAL;
/** The current state the {@link Movement} resides in. */
@Column(name = "C_STATE")
@Enumerated(EnumType.STRING)
private DefaultMovementState state;
/** A message with the reason for this {@code Movement}. */
@Embedded
private Message message;
/** Reported problems on the {@code Movement}. */
@OneToMany(mappedBy = "movement", cascade = CascadeType.ALL)
private List<ProblemHistory> problems;
/** Where the {@code Movement} is picked up. */
@Column(name = "C_SOURCE_LOCATION")
private String sourceLocation;
/** The name of the {@code LocationGroup} where the {@code sourceLocation} belongs to. */
@Column(name = "C_SOURCE_LOCATION_GROUP_NAME")
private String sourceLocationGroupName;
/** The target {@code Location} of the {@code Movement}. This property is set before the {@code Movement} is started. */
@Column(name = "C_TARGET_LOCATION")
@Null(groups = ValidationGroups.Movement.Create.class)
private String targetLocation;
/** A {@code LocationGroup} can also be set as target. At least one target must be set when the {@code Movement} is being started. */
@Column(name = "C_TARGET_LOCATION_GROUP_NAME")
@NotNull
private String targetLocationGroup;
/** Date when the {@code Movement} can be started earliest. */
@Column(name = "C_START_EARLIEST_DATE", columnDefinition = "timestamp(0)")
@DateTimeFormat(pattern = DATE_TIME_WITH_TIMEZONE)
private ZonedDateTime startEarliestDate;
/** Date when the {@code Movement} was started. */
@Column(name = "C_START_DATE", columnDefinition = "timestamp(0)")
@DateTimeFormat(pattern = DATE_TIME_WITH_TIMEZONE)
private ZonedDateTime startDate;
/** Latest possible finish date of this {@code Movement}. */
@Column(name = "C_LATEST_DUE", columnDefinition = "timestamp(0)")
@DateTimeFormat(pattern = DATE_TIME_WITH_TIMEZONE)
private ZonedDateTime latestDueDate;
/** Date when the {@code Movement} ended. */
@Column(name = "C_END_DATE", columnDefinition = "timestamp(0)")
@DateTimeFormat(pattern = DATE_TIME_WITH_TIMEZONE)
private ZonedDateTime endDate;
/*~ -------------- Constructors -------------- */
/** Dear JPA... */
protected Movement() {}
/*~ ---------------- Methods ----------------- */
@Override
public void setPersistentKey(String pKey) {
super.setPersistentKey(pKey);
}
/**
* Add a new problem to the {@code Movement}s {@code problemHistory}.
*
* @param problem The problem to store
* @return {@literal true} if added successfully
*/
public boolean addProblem(ProblemHistory problem) {
if (this.problems == null) {
this.problems = new ArrayList<>(1);
}
return this.problems.add(problem);
}
/**
* Check whether the {@code targetLocation} is empty.
*
* @return {@literal true} if so
*/
public boolean emptyTargetLocation() {
return targetLocation == null || targetLocation.isEmpty();
}
/**
* Set the initiator, or the given default value.
*
* @param initiator The initiator
* @param defaultVal The default value
*/
public void setInitiatorOrDefault(String initiator, String defaultVal) {
if (initiator == null || initiator.isEmpty()) {
Assert.hasText(defaultVal, "The default value for initiator must be given");
this.initiator = defaultVal;
} else {
this.initiator = initiator;
}
}
/**
* Checks if this {@code Movement} has a {@code SKU} set.
*
* @return {@literal true} if so
*/
public boolean hasSKU() {
return this.sku != null && !this.sku.isEmpty();
}
/**
* Set a {@code startDate} for this {@code Movement} if not already set.
*
* @param startDate The start date to set
*/
public void initStartDate(ZonedDateTime startDate) {
if (this.startDate == null) {
this.startDate = startDate;
}
}
/*~ --------------- Accessors ---------------- */
public Barcode getTransportUnitBk() {
return transportUnitBk;
}
public void setTransportUnitBk(Barcode transportUnitBk) {
this.transportUnitBk = transportUnitBk;
}
public MovementType getType() {
return type;
}
public void setType(MovementType type) {
this.type = type;
}
public String getInitiator() {
return initiator;
}
public void setInitiator(String initiator) {
this.initiator = initiator;
}
public MovementGroup getGroup() {
return group;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public PriorityLevel getPriority() {
return priority;
}
public void setPriority(PriorityLevel priority) {
this.priority = priority;
}
public StartMode getMode() {
return mode;
}
public void setMode(StartMode mode) {
this.mode = mode;
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
public DefaultMovementState getState() {
return state;
}
public void setState(DefaultMovementState state) {
this.state = state;
}
public List<ProblemHistory> getProblems() {
return problems;
}
public String getSourceLocation() {
return sourceLocation;
}
public void setSourceLocation(String sourceLocation) {
this.sourceLocation = sourceLocation;
}
public String getSourceLocationGroupName() {
return sourceLocationGroupName;
}
public void setSourceLocationGroupName(String sourceLocationGroupName) {
this.sourceLocationGroupName = sourceLocationGroupName;
}
public String getTargetLocation() {
return targetLocation;
}
public void setTargetLocation(String targetLocation) {
this.targetLocation = targetLocation;
}
public String getTargetLocationGroup() {
return targetLocationGroup;
}
public boolean emptyTargetLocationGroup() {
return targetLocationGroup == null;
}
public void setTargetLocationGroup(String targetLocationGroup) {
this.targetLocationGroup = targetLocationGroup;
}
public ZonedDateTime getStartEarliestDate() {
return startEarliestDate;
}
public ZonedDateTime getStartDate() {
return startDate;
}
public void setStartDate(ZonedDateTime startDate) {
this.startDate = startDate;
}
public ZonedDateTime getLatestDueDate() {
return latestDueDate;
}
public ZonedDateTime getEndDate() {
return endDate;
}
public void setEndDate(ZonedDateTime endDate) {
this.endDate = endDate;
}
/**
* {@inheritDoc}
*
* Not the group and not the history.
*/
@Override
public String toString() {
return "Movement{" +
"transportUnitBk=" + transportUnitBk +
", pKey=" + getPersistentKey() +
", type=" + type +
", initiator=" + initiator +
", priority=" + priority +
", mode=" + mode +
", state=" + state +
", message=" + message +
", sourceLocation='" + sourceLocation + '\'' +
", sourceLocationGroupName='" + sourceLocationGroupName + '\'' +
", targetLocation='" + targetLocation + '\'' +
", targetLocationGroup='" + targetLocationGroup + '\'' +
", startEarliestDate=" + startEarliestDate +
", startDate=" + startDate +
", latestDueDate=" + latestDueDate +
", endDate=" + endDate +
'}';
}
/**
* {@inheritDoc}
*
* Not the group and not the history.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Movement)) return false;
if (!super.equals(o)) return false;
Movement movement = (Movement) o;
return Objects.equals(transportUnitBk, movement.transportUnitBk) && type == movement.type && Objects.equals(initiator, movement.initiator) && Objects.equals(sku, movement.sku) && priority == movement.priority && mode == movement.mode && state == movement.state && Objects.equals(message, movement.message) && Objects.equals(sourceLocation, movement.sourceLocation) && Objects.equals(sourceLocationGroupName, movement.sourceLocationGroupName) && Objects.equals(targetLocation, movement.targetLocation) && Objects.equals(targetLocationGroup, movement.targetLocationGroup) && Objects.equals(startEarliestDate, movement.startEarliestDate) && Objects.equals(startDate, movement.startDate) && Objects.equals(latestDueDate, movement.latestDueDate) && Objects.equals(endDate, movement.endDate);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), transportUnitBk, type, initiator, sku, priority, mode, state, message, sourceLocation, sourceLocationGroupName, targetLocation, targetLocationGroup, startEarliestDate, startDate, latestDueDate, endDate);
}
}