/*
* Copyright (C) 2014 GG-Net GmbH - Oliver Günther
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.ggnet.dwoss.stock.entity;
import java.io.Serializable;
import javax.persistence.*;
import javax.validation.Valid;
import javax.validation.constraints.Null;
import eu.ggnet.dwoss.util.persistence.EagerAble;
import static javax.persistence.FetchType.EAGER;
/**
* Represents a actual unit in the stock.
* <p/>
* The implementation captures the following cases:
* <ul>
* <li>An existing instance represents one and only one unit</li>
* <li>There exist special {@link StockTransaction} which allow the creation and deletion of units</li>
* <li>Units which are
* <code>{@link StockUnit#isInStock()} == True</code> are physical in the stock</li>
* <li>Units are either
* <code>{@link StockUnit#isInStock()} or {@link StockUnit#isInTransaction()}</code> never both, with the one exception, that the
* transaction is in status prepared.</li>
* <li>Units which are
* <code>{@link StockUnit#isInTransaction()} == True</code> not in stock but somewhere else</li>
* </ul>
* <p/>
* @has n - 1 Stock
*/
@Entity
@NamedQueries({
@NamedQuery(name = "all", query = "Select su from StockUnit su"),
@NamedQuery(name = "StockUnit.byUniqueUnitId", query = "Select p from StockUnit as p where p.uniqueUnitId = ?1"),
@NamedQuery(name = "StockUnit.byRefurbishId", query = "Select p from StockUnit as p where p.refurbishId = ?1"),
@NamedQuery(name = "StockUnit.byRefurbishIds", query = "SELECT p FROM StockUnit AS p WHERE p.refurbishId IN (?1)"),
@NamedQuery(name = "StockUnit.byStockId", query = "Select p from StockUnit as p where p.stock.id = ?1"),
@NamedQuery(name = "StockUnit.countByTypeStatusSource", query = "Select count(su) from StockUnit su where su.position.transaction.type = ?1 "
+ "and su.position.transaction.status.type = ?2 and su.position.transaction.source.id = ?3 "),
@NamedQuery(name = "StockUnit.countByStockNoLogicTransaciton", query = "SELECT COUNT(su) FROM StockUnit su WHERE su.stock.id = ?1 AND su.logicTransaction IS NULL"),
@NamedQuery(name = "StockUnit.byNoTransaciton", query = "SELECT su FROM StockUnit su WHERE su.logicTransaction IS NULL AND su.position IS NULL"),
@NamedQuery(name = "StockUnit.byNoLogicTransaciton", query = "SELECT su FROM StockUnit su WHERE su.logicTransaction IS NULL"),
@NamedQuery(name = "StockUnit.byNoLogicTransacitonAndPresentStock", query = "SELECT su FROM StockUnit su WHERE su.logicTransaction IS NULL AND su.stock IS NOT NULL"),
@NamedQuery(name = "StockUnit.byNoLogicTransacitonAsUniqueUnitId", query = "SELECT su.uniqueUnitId FROM StockUnit su WHERE su.logicTransaction IS NULL"),
@NamedQuery(name = "StockUnit.countByStockOnLogicTransaciton", query = "SELECT COUNT(su) FROM StockUnit su WHERE su.stock.id = ?1 AND su.logicTransaction IS NOT NULL"),
@NamedQuery(name = "StockUnit.findByUniqueUnitIds", query = "SELECT u FROM StockUnit u WHERE u.uniqueUnitId IN (?1)")
})
public class StockUnit implements Serializable, EagerAble {
@Id
@GeneratedValue
private int id;
@Version
private short optLock;
/**
* The name of the StockUnit.
*/
private String name;
/**
* An id which helps to find the unit in reality. At the moment, this is either the SopoNr or the Serial
*/
private String refurbishId;
@Valid
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = EAGER)
private Stock stock;
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = EAGER)
private StockLocation stockLocation;
@Valid
@OneToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, mappedBy = "stockUnit", fetch = EAGER)
StockTransactionPosition position;
@Valid
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
private LogicTransaction logicTransaction;
private Integer uniqueUnitId;
/**
* Default constructor, needed for persistence
*/
public StockUnit() {
}
/**
* Test constructor, must only be used if this stock unit is never persisted.
*
* @param id the database identifier
*/
StockUnit(int id) {
this.id = id;
}
/**
* Constructor.
*
* @param uniqueUnitId the unique unit reference
* @param unitId the unit id for the stock
*/
public StockUnit(String unitId, Integer uniqueUnitId) {
this(unitId, null, uniqueUnitId);
}
/**
* Constructor.
*
* @param uniqueUnitId the unique unit reference
* @param unitId the unit id for the stock
* @param name a human understandable name
*/
public StockUnit(String unitId, String name, Integer uniqueUnitId) {
this.refurbishId = unitId;
this.name = name;
this.uniqueUnitId = uniqueUnitId;
}
/**
* Returns null if Instance is valid, otherwise a message describing the problem
* <p>
* Possible cause;
* <ul>
* <li>StockUnit is neither on stock nor on transaction</li>
* <li>StockUnit is on stock and on transaction, and the transaction is not of type status prepared</li>
* </ul>
* </p>
* <p/>
* @return null if Instance is valid, otherwise a message describing the problem
*/
@Null
public String getValidationViolations() {
if ( isInStock() ^ isInTransaction() ) return null;
if ( isInStock() && isInTransaction() && getTransaction().getStatus() != null
&& getTransaction().getStatus().getType() == StockTransactionStatusType.PREPARED ) return null;
if ( isInStock() && isInTransaction() )
return "StockUnit is in Stock and in Transaction, and the Transaciton is not in status prepared";
if ( !isInStock() && !isInTransaction() )
return "StockUnit is neither in Stock nor in Transaction";
return "StockUnit is invalid, because something mystirious has happend, i don't know, this point should never be reached.";
}
public StockTransactionPosition getPosition() {
return position;
}
/**
* Sets this StockUnit to be on a StockTransactionPosition, bidirectional handling is implemented.
* <p/>
* @param position the position to be set
*/
public void setPosition(StockTransactionPosition position) {
StockTransactionPosition.internalSetUnitPosition(this, position);
}
/**
* Returns true, if the unit is in stock.
*
* @return true, if the unit is in stock.
*/
public boolean isInStock() {
return getStock() != null;
}
/**
* Returns true, if the unit is on a transaction.
* <p/>
* @return true, if the unit is on a transaction.
*/
public boolean isInTransaction() {
return getPosition() != null;
}
/**
* Returns the Stock, where this unit is located or null if dead or in transaction
*
* @return the Stock, where this unit is located or null if dead or in transaction
*/
public Stock getStock() {
return stock;
}
/**
* Returns the transaction, where this unit is located or null if dead or in stock
*
* @return the transaction, where this unit is located or null if dead or in stock
*/
public StockTransaction getTransaction() {
return position == null ? null : position.getTransaction();
}
/**
* Sets the Stock for the Unit, sets the StockLocation to null, bidirectional handling implemented
*
* @param stock the stock to be set
*/
public void setStock(Stock stock) {
if ( stock == null && this.stock == null ) return;
if ( this.stock != null && this.stock.equals(stock) ) return;
if ( this.stock != null ) {
this.stock.units.remove(this);
}
if ( stock != null ) {
stock.units.add(this);
}
this.stockLocation = null;
this.stock = stock;
}
/**
* Returns the StockLocation
* <p/>
* @return the StockLocation
*/
public StockLocation getStockLocation() {
return stockLocation;
}
/**
* Sets the StockLocation and the Stock
* <p/>
* @param stockLocation the StockLocation to be set
*/
public void setStockLocation(StockLocation stockLocation) {
setStock(stockLocation == null ? null : stockLocation.getStock());
this.stockLocation = stockLocation;
}
public Integer getUniqueUnitId() {
return uniqueUnitId;
}
public void setUniqueUnitId(Integer uniqueUnitId) {
this.uniqueUnitId = uniqueUnitId;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRefurbishId() {
return refurbishId;
}
public void setRefurbishId(String unitId) {
this.refurbishId = unitId;
}
public LogicTransaction getLogicTransaction() {
return logicTransaction;
}
public void setLogicTransaction(LogicTransaction logicTransaction) {
if ( this.logicTransaction == logicTransaction ) return; // Implies both null
if ( this.logicTransaction != null ) this.logicTransaction.units.remove(this);
if ( logicTransaction != null ) logicTransaction.units.add(this);
this.logicTransaction = logicTransaction;
}
@Override
public boolean equals(Object obj) {
if ( obj == null ) return false;
if ( getClass() != obj.getClass() ) return false;
final StockUnit other = (StockUnit)obj;
if ( this.id != other.id ) return false;
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 83 * hash + this.id;
return hash;
}
public void fetchEager() {
if ( getTransaction() != null ) getTransaction().fetchEager();
}
public String toSimple() {
return "StockUnit{id=" + id + ",refurbishId=" + refurbishId + "}";
}
@Override
public String toString() {
String location = "unknown";
if ( isInTransaction() ) {
location = "Transaction(id=" + position.getTransaction().getId() + ",type=" + position.getTransaction().getType() + ",status=" + position.getTransaction().getStatus().getType() + ")";
} else if ( isInStock() ) {
location = "Stock(id=" + stock.getId() + ",name=" + stock.getName() + ")";
}
return "StockUnit{" + "id=" + id + ",logicTransaction="
+ (logicTransaction == null ? "null" : "[id=" + logicTransaction.getId() + ",units=" + logicTransaction.getUnits().size() + ",dossierId=" + logicTransaction.getDossierId() + "]")
+ ",location=" + location + ",uniqueUnitId=" + uniqueUnitId + ",refurbishId=" + refurbishId + '}';
}
}