All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openwms.common.location.Location Maven / Gradle / Ivy

Go to download

A library with essential functionality on common resources like Locations, LocationGroups and TransportUnits

The newest version!
/*
 * 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.location;

import jakarta.persistence.AttributeOverride;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.openwms.common.account.Account;
import org.openwms.common.app.Default;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import static java.lang.String.format;
import static org.openwms.common.location.StringListConverter.STRING_LIST_LENGTH;

/**
 * A Location, represents a physical or virtual place in a warehouse. Could be something like a storage location in the stock or a conveyor
 * location. Even error locations can be represented with the Location. Multiple Locations with same characteristics are grouped to a
 * {@link LocationGroup}.
 *
 * @author Heiko Scherrer
 * @GlossaryTerm
 * @see org.openwms.common.location.LocationGroup
 */
@Entity
@Table(name = Location.TABLE, uniqueConstraints = {
        @UniqueConstraint(name = "UC_LOC_ID", columnNames = {"C_AREA", "C_AISLE", "C_X", "C_Y", "C_Z"}),
        @UniqueConstraint(name = "UC_LOC_PLC_CODE", columnNames = "C_PLC_CODE"),
        @UniqueConstraint(name = "UC_LOC_ERP_CODE", columnNames = "C_ERP_CODE")
})
public class Location extends Target implements Serializable {

    /** Table name. */
    public static final String TABLE = "COM_LOCATION";
    private static final String CREATION_OF_LOCATION_WITH_LOCATION_ID_NULL = "Creation of Location with locationId null";

    /** Unique natural key. */
    @Embedded
    @NotNull
    @AttributeOverride(name = "area", column = @Column(name = "C_AREA"))
    @AttributeOverride(name = "aisle", column = @Column(name = "C_AISLE"))
    @AttributeOverride(name = "x", column = @Column(name = "C_X"))
    @AttributeOverride(name = "y", column = @Column(name = "C_Y"))
    @AttributeOverride(name = "z", column = @Column(name = "C_Z"))
    private LocationPK locationId;

    /** The {@code Location} might be assigned to an {@link Account}. */
    @ManyToOne
    @JoinColumn(name = "C_ACCOUNT", referencedColumnName = "C_IDENTIFIER", foreignKey = @ForeignKey(name = "FK_LOC_ACC"))
    private Account account;

    /** PLC code of the {@code Location}. */
    @Column(name = "C_PLC_CODE")
    private String plcCode;

    /** ERP code of the {@code Location}. */
    @Column(name = "C_ERP_CODE", unique = true)
    private String erpCode;

    /** Description of the {@code Location}. */
    @Column(name = "C_DESCRIPTION")
    @Size(max = 255)
    private String description;

    /** Sort order index used by strategies for putaway, or picking. */
    @Column(name = "C_SORT")
    private Integer sortOrder;

    /** Might be assigned to a particular zone in stock. */
    @Column(name = "C_STOCK_ZONE")
    private String stockZone;

    /** A {@code Location} can be assigned to a particular labels. */
    @Column(name="C_LABELS", length = STRING_LIST_LENGTH)
    @Convert(converter = StringListConverter.class)
    @Size(max = STRING_LIST_LENGTH)
    private List labels;

    /** Maximum number of {@code TransportUnit}s allowed on the {@code Location}. */
    @Column(name = "C_NO_MAX_TRANSPORT_UNITS")
    private int noMaxTransportUnits = DEF_MAX_TU;
    /** Default value of {@link #noMaxTransportUnits}. */
    public static final int DEF_MAX_TU = 1;

    /** Maximum allowed weight on the {@code Location}. */
    @Column(name = "C_MAXIMUM_WEIGHT")
    private BigDecimal maximumWeight;

    /**
     * Date of last movement. When a {@code TransportUnit} is moving to or away from the {@code Location}, {@code lastMovement} is updated.
     * This is useful to get the history of {@code TransportUnit}s as well as for inventory calculation.
     */
    @Column(name = "C_LAST_MOVEMENT")
    private LocalDateTime lastMovement;

    /**
     * Shall the {@code Location} be included in the calculation of {@code TransportUnit}s of the parent {@link LocationGroup}.
     * 
    *
  • {@literal true} : {@code Location} is included in calculation of {@code TransportUnit}s.
  • *
  • {@literal false}: {@code Location} is not included in calculation of {@code TransportUnit}s.
  • *
*/ @Column(name = "C_LG_COUNTING_ACTIVE") private Boolean locationGroupCountingActive = DEF_LG_COUNTING_ACTIVE; /** Default value of {@link #locationGroupCountingActive}. */ public static final boolean DEF_LG_COUNTING_ACTIVE = false; /** * Signals the incoming state of the {@code Location}. * {@code Location}s which are blocked for incoming movements do not accept {@code TransportUnit}s. *
    *
  • {@literal true} : {@code Location} is ready to pick up {@code TransportUnit}s.
  • *
  • {@literal false}: {@code Location} is locked, and cannot pick up {@code TransportUnit}s.
  • *
*/ @Column(name = "C_INCOMING_ACTIVE") private boolean incomingActive = DEF_INCOMING_ACTIVE; /** Default value of {@link #incomingActive}. */ public static final boolean DEF_INCOMING_ACTIVE = true; /** * Signals the outgoing state of the {@code Location}. * {@code Location}s which are blocked for outgoing do not accept to move {@code TransportUnit}s away. *
    *
  • {@literal true} : {@code Location} is enabled for outgoing {@code TransportUnit}s.
  • *
  • {@literal false}: {@code Location} is locked, {@code TransportUnit}s can't leave the {@code Location}.
  • *
*/ @Column(name = "C_OUTGOING_ACTIVE") private boolean outgoingActive = DEF_OUTGOING_ACTIVE; /** Default value of {@link #outgoingActive}. */ public static final boolean DEF_OUTGOING_ACTIVE = true; /** * The PLC is able to change the state of a {@code Location}. This property stores the last state, received from the PLC. *
    *
  • 0 : No PLC error, everything okay
  • *
  • < 0: Not defined
  • *
  • > 0: Some defined error code
  • *
*/ @Column(name = "C_PLC_STATE") private int plcState = DEF_PLC_STATE; /** Default value of {@link #plcState}. */ public static final int DEF_PLC_STATE = 0; /** * Determines whether the {@code Location} is considered in the allocation procedure. *
    *
  • {@literal true} : The {@code Location} is considered in storage calculation by an allocation procedure.
  • *
  • {@literal false} : The {@code Location} is not considered in the allocation process.
  • *
*/ @Column(name = "C_CONSIDERED_IN_ALLOCATION") private Boolean consideredInAllocation = DEF_CONSIDERED_IN_ALLOCATION; /** Default value of {@link #consideredInAllocation}. */ public static final boolean DEF_CONSIDERED_IN_ALLOCATION = true; /** The {@link LocationType} the {@code Location} belongs to. */ @ManyToOne @JoinColumn(name = "C_LOCATION_TYPE", foreignKey = @ForeignKey(name = "FK_LOC_LT")) private LocationType locationType; /** Some group the {@code Location} belongs to. */ @Column(name = "C_GROUP") private String group; /** The {@code Location} may be classified, like 'hazardous'. */ @Column(name = "C_CLASSIFICATION") @Size(max = 255) private String classification; /** The {@link LocationGroup} the {@code Location} belongs to. */ @ManyToOne @JoinColumn(name = "C_LOCATION_GROUP", foreignKey = @ForeignKey(name = "FK_LOC_LG")) private LocationGroup locationGroup; /** Stored {@link Message}s on the {@code Location}. */ @OneToMany(cascade = {CascadeType.ALL}) @JoinTable(name = "COM_LOCATION_MESSAGE", uniqueConstraints = @UniqueConstraint(name = "UC_LOCM_ID", columnNames = "C_MESSAGE_ID"), joinColumns = @JoinColumn(name = "C_LOCATION_ID", foreignKey = @ForeignKey(name = "FK_LOCM_LOCPK")), inverseJoinColumns = @JoinColumn(name = "C_MESSAGE_ID", foreignKey = @ForeignKey(name = "FK_LOCM_MSGPK")) ) private Set messages = new HashSet<>(); /*~ ----------------------------- constructors ------------------- */ /** * Create a new Location with the business key. * * @param locationId The unique natural key of the Location */ protected Location(LocationPK locationId) { Assert.notNull(locationId, CREATION_OF_LOCATION_WITH_LOCATION_ID_NULL); this.locationId = locationId; } /** * Create a new Location. * * @param locationId The unique natural key of the Location * @param locationGroup The LocationGroup the Location belongs to */ @Default Location(LocationPK locationId, Account account, LocationGroup locationGroup, LocationType locationType, String erpCode, String plcCode, Integer sortOrder, String stockZone) { Assert.notNull(locationId, CREATION_OF_LOCATION_WITH_LOCATION_ID_NULL); this.locationId = locationId; this.account = account; this.locationGroup = locationGroup; this.locationType = locationType; this.erpCode = erpCode; this.plcCode = plcCode; this.sortOrder = sortOrder; this.stockZone = stockZone; } /** Dear JPA... */ protected Location() { } /** * Create a new Location with the business key. * * @param locationId The unique natural key of the Location * @return The Location */ public static Location create(LocationPK locationId) { return new Location(locationId); } /*~ ----------------------------- methods ------------------- */ /** Required for the Mapper. */ @Override public void setPersistentKey(String pKey) { super.setPersistentKey(pKey); } /** * Check if the Location has a {@code locationId} set. * * @return {@literal true} if so */ public boolean hasLocationId() { return locationId != null; } /** * Return the {@link Account} this {@code Location} is assigned to. * * @return The Account */ public Account getAccount() { return account; } /** * Get the ERP Code of the Location. * * @return The ERP code */ public String getErpCode() { return erpCode; } /** * Get the PLC Code of the Location. * * @return The PLC code */ public String getPlcCode() { return plcCode; } /** * Add a new {@link Message} to this Location. * * @param message The {@link Message} to be added * @return {@literal true} if the {@link Message} is new in the collection of messages, otherwise {@literal false} */ public boolean addMessage(Message message) { Assert.notNull(message, "null passed to addMessage, this: " + this); return this.messages.add(message); } /** * Determine whether the Location is considered during allocation. * * @return {@literal true} when considered in allocation, otherwise {@literal false} */ public boolean isConsideredInAllocation() { return this.consideredInAllocation; } /** * Returns the description of the Location. * * @return The description text */ public String getDescription() { return this.description; } /** * Set the description text of the Location. * * @param description The description text */ public void setDescription(String description) { this.description = description; } /** * Get the sortOrder. * * @return A sequence number */ public Integer getSortOrder() { return sortOrder; } /** * Returns the stockZone. * * @return As string */ public String getStockZone() { return stockZone; } /** * Returns the list of Strings set as labels for the Location. * * @return A list of Strings or an empty list */ public List getLabels() { return labels; } /** * Set a list of labels to the Location. * * @param labels A comma-separated list of labels */ public void setLabels(List labels) { this.labels = labels; } /** * Determine whether incoming mode is activated and {@code TransportUnit}s can be put on this Location. * * @return {@literal true} when incoming mode is activated, otherwise {@literal false} */ public boolean isInfeedActive() { return this.incomingActive; } /** * Set the incoming mode of this Location. * * @param infeedActive {@literal true} means Infeed movements are possible, {@literal false} means Infeed movements are blocked */ public void setInfeed(boolean infeedActive) { this.incomingActive = infeedActive; } /** * Check whether infeed is blocked and moving {@code TransportUnit}s to here is forbidden. * * @return {@literal true} is blocked, otherwise {@literal false} */ public boolean isInfeedBlocked() { return !this.incomingActive; } /** * Return the date when the Location was updated the last time. * * @return Timestamp of the last update */ public LocalDateTime getLastMovement() { return this.lastMovement; } /** * Change the date when a TransportUnit was put or left the Location the last time. * * @param lastMovement The date of change. */ public void setLastMovement(LocalDateTime lastMovement) { this.lastMovement = lastMovement; } /** * Return the {@link LocationGroup} where the Location belongs to. * * @return The {@link LocationGroup} of the Location */ public LocationGroup getLocationGroup() { return this.locationGroup; } /** * Determine whether the Location is part of the parent {@link LocationGroup}s calculation procedure of {@code TransportUnit}s. * * @return {@literal true} if calculation is activated, otherwise {@literal false} */ public boolean isLocationGroupCountingActive() { return this.locationGroupCountingActive; } /** * Returns the locationId (natural key) of the Location. * * @return The locationId */ public LocationPK getLocationId() { return this.locationId; } /** * Returns the type of Location. * * @return The type */ public LocationType getLocationType() { return this.locationType; } public void setLocationType(LocationType locationType) { if (this.locationType != null && !this.locationType.equals(locationType)) { throw new IllegalArgumentException(format("LocationType of Location [%s] is already defined and can't be changed", locationType)); } this.locationType = locationType; } /** * Returns the group the Location belongs to. * * @return The group as String */ public String getGroup() { return group; } /** * Returns the classification of the Location. * * @return As a String */ public String getClassification() { return classification; } /** * Set the classification. * * @param classification As an arbitrary String */ public void setClassification(String classification) { this.classification = classification; } /** * Return the maximum allowed weight on the Location. * * @return The maximum allowed weight */ public BigDecimal getMaximumWeight() { return this.maximumWeight; } /** * Returns an unmodifiable Set of {@link Message}s stored for the Location. * * @return An unmodifiable Set */ public Set getMessages() { return new HashSet<>(messages); } /** * Returns the maximum number of {@code TransportUnit}s allowed on the Location. * * @return The maximum number of {@code TransportUnit}s */ public int getNoMaxTransportUnits() { return noMaxTransportUnits; } /** * Determine whether outgoing mode is activated and {@code TransportUnit}s can leave this Location. * * @return {@literal true} when outgoing mode is activated, otherwise {@literal false} */ public boolean isOutfeedActive() { return this.outgoingActive; } /** * Check whether outfeed is blocked and moving {@code TransportUnit}s from here is forbidden. * * @return {@literal true} is blocked, otherwise {@literal false} */ public boolean isOutfeedBlocked() { return !this.outgoingActive; } /** * Set the outfeed mode of this Location. * * @param outfeedActive {@literal true} means Outfeed movements are possible, {@literal false} means Outfeed movements are blocked */ public void setOutfeed(boolean outfeedActive) { this.outgoingActive = outfeedActive; } /** * Return the current set plc state. * * @return the plc state */ public int getPlcState() { return plcState; } /** * Set the plc state. * * @param plcState the plc state */ public void setPlcState(int plcState) { this.plcState = plcState; } /** * Remove one or more {@link Message}s from this Location. * * @param msgs An array of {@link Message}s to be removed * @return {@literal true} if the {@link Message}s were found and removed, otherwise {@literal false} * @throws IllegalArgumentException when messages is {@literal null} */ public boolean removeMessages(Message... msgs) { Assert.notNull(msgs, () -> "null passed to removeMessages, this: " + this); return this.messages.removeAll(Arrays.asList(msgs)); } /** * Add this {@code Location} to the {@literal locationGroup}. When the argument is {@literal null} an existing {@link LocationGroup} is * removed from the {@code Location}. * * @param locationGroup The {@link LocationGroup} to be assigned */ void setLocationGroup(LocationGroup locationGroup) { Assert.notNull(locationGroup, () -> "Not allowed to call location#setLocationGroup with null argument, this: " + this); if (this.locationGroup != null) { this.locationGroup.removeLocation(this); } this.setLocationGroupCountingActive(locationGroup.isLocationGroupCountingActive()); this.locationGroup = locationGroup; } /** * Define whether or not the Location shall be considered in counting {@code TransportUnit}s of the parent {@link LocationGroup}. * * @param locationGroupCountingActive {@literal true} if considered, otherwise {@literal false} */ public void setLocationGroupCountingActive(boolean locationGroupCountingActive) { this.locationGroupCountingActive = locationGroupCountingActive; } /** * Checks whether this {@code Location} belongs to a {@code LocationGroup}. * * @return {@literal true} if it belongs to a {@code LocationGroup}, otherwise {@literal false} */ public boolean belongsToLocationGroup() { return locationGroup != null; } /** * Checks whether this {@code Location} belongs NOT to a {@code LocationGroup}. * * @return {@literal true} if it does not belong to a {@code LocationGroup}, otherwise {@literal false} */ public boolean belongsNotToLocationGroup() { return !belongsToLocationGroup(); } /** * Set the locationGroup to {@literal null}. */ void unsetLocationGroup() { this.locationGroup = null; } /** * {@inheritDoc} *

* Only use the unique natural key for comparison. */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Location location = (Location) o; return Objects.equals(locationId, location.locationId); } /** * {@inheritDoc} *

* Only use the unique natural key for hashCode calculation. */ @Override public int hashCode() { return Objects.hash(locationId); } /** * Return the {@link LocationPK} as String. * * @return String locationId * @see LocationPK#toString() */ @Override public String toString() { return locationId.toString(); } public static final class LocationBuilder { private final Location target; private LocationBuilder(Location target) { this.target = target; } public static LocationBuilder aLocation(Location target) { return new LocationBuilder(target); } public LocationBuilder withAccount(Account account) { this.target.account = account; return this; } public LocationBuilder withPlcCode(String plcCode) { this.target.plcCode = plcCode; return this; } public LocationBuilder withErpCode(String erpCode) { this.target.erpCode = erpCode; return this; } public LocationBuilder withDescription(String description) { this.target.description = description; return this; } public LocationBuilder withSortOrder(Integer sortOrder) { this.target.sortOrder = sortOrder; return this; } public LocationBuilder withStockZone(String stockZone) { this.target.stockZone = stockZone; return this; } public LocationBuilder withLabels(List labels) { this.target.labels = labels; return this; } public LocationBuilder withNoMaxTransportUnits(int noMaxTransportUnits) { this.target.noMaxTransportUnits = noMaxTransportUnits; return this; } public LocationBuilder withMaximumWeight(BigDecimal maximumWeight) { this.target.maximumWeight = maximumWeight; return this; } public LocationBuilder withLastMovement(LocalDateTime lastMovement) { this.target.lastMovement = lastMovement; return this; } public LocationBuilder withLocationGroupCountingActive(boolean locationGroupCountingActive) { this.target.locationGroupCountingActive = locationGroupCountingActive; return this; } public LocationBuilder withIncomingActive(boolean incomingActive) { this.target.incomingActive = incomingActive; return this; } public LocationBuilder withOutgoingActive(boolean outgoingActive) { this.target.outgoingActive = outgoingActive; return this; } public LocationBuilder withPlcState(int plcState) { this.target.plcState = plcState; return this; } public LocationBuilder withConsideredInAllocation(boolean consideredInAllocation) { this.target.consideredInAllocation = consideredInAllocation; return this; } public LocationBuilder withLocationType(LocationType locationType) { this.target.locationType = locationType; return this; } public LocationBuilder withGroup(String group) { this.target.group = group; return this; } public LocationBuilder withClassification(String classification) { this.target.classification = classification; return this; } public LocationBuilder withLocationGroup(LocationGroup locationGroup) { this.target.locationGroup = locationGroup; return this; } public LocationBuilder withMessages(Set messages) { this.target.messages = messages; return this; } public Location build() { return target; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy