/*
* 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.redtape.entity;
import java.io.Serializable;
import java.lang.ProcessBuilder.Redirect.Type;
import java.util.*;
import javax.persistence.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
import javax.validation.groups.Default;
import eu.ggnet.dwoss.redtape.entity.Position.Key;
import eu.ggnet.dwoss.rules.*;
import lombok.*;
import lombok.experimental.Builder;
import static javax.persistence.CascadeType.*;
/**
* A Position of a Document. A Position has an id and a document associated, but all changes to these values are all done
* from {@link Document}, e.g., {@link Document#append(Position) } or {@link Document#remove(Position) }
* <p>
* Valid Positions are:
* <table>
* <tr><td>Type</td><td>Needed Values</td></tr>
* <tr><td>Unit</td><td>name, afterTaxPrice, price, amount, tax, description, unitId, document, uniqueUnitProductId;</td></tr>
* <tr><td>Service</td><td>name, afterTaxPrice, price, amount, tax, description, document;</td></tr>
* <tr><td>Product_Batch</td><td>name, afterTaxPrice, price, amount, tax, description, document, uniqueUnitProductId;</td></tr>
* <tr><td>Comment</td><td>name, description, document;</td></tr>
* <tr><td>Temporary_Comment</td><td>name, description, document;</td></tr>
* <tr><td>Transportation_Cost</td><td>name, afterTaxPrice, price, amount, tax, description, document;</td></tr>
* </table>
* <p>
* @has 1 - 1 Position.Key
* @has 1 - n Position.Type
* @author bastian.venz, oliver.guenther
*/
@Entity
@IdClass(Key.class)
@NamedQueries({
@NamedQuery(name = "Position.findByDocumentId", query = "SELECT p FROM Position p WHERE p.document.id = ?1"),
@NamedQuery(name = "Position.countByDocumentId", query = "SELECT COUNT(p) FROM Position p WHERE p.document.id = ?1"),
@NamedQuery(name = "Position.findByUniqueUnitId", query = "SELECT p FROM Position p WHERE p.uniqueUnitId = ?1")
})
public class Position implements Serializable, Comparable<Position> {
/**
* Validation Group, for all Positions at all default Document Types (not Returns or Blocks), which must hold in the UI.
*/
public static interface DefaultUi {
};
/**
* Validation Group, for all Positions at Documents of {@link Type#RETURNS}, which must hold in the UI.
*/
public static interface Returns {
};
/**
* Validation Group, for all Positions at Documents of {@link Type#BLOCK}, which must hold in the UI.
*/
public static interface Blocks {
};
@Data
public static class Key implements Serializable {
private long document;
private int id;
public Key(long document, int id) {
this.document = document;
this.id = id;
}
public Key() {
this(0, 0);
}
}
@Version
private Short optLock = 0;
@Id
int id;
@Id
@NotNull
@Valid
@ManyToOne(cascade = {DETACH, MERGE, REFRESH, PERSIST}, optional = false)
Document document;
@NotNull(groups = {Default.class, Returns.class, DefaultUi.class, Blocks.class})
@Basic(optional = false)
@Enumerated
private PositionType type;
@NotNull(groups = {Default.class, Returns.class, DefaultUi.class, Blocks.class})
@Size(min = 1, max = 255, groups = {Default.class, Returns.class, DefaultUi.class, Blocks.class})
private String name;
private double afterTaxPrice;
private double price;
private double amount;
private double tax;
@NotNull(groups = {Default.class, Returns.class, DefaultUi.class, Blocks.class})
@Size(min = 1, groups = {Default.class, Returns.class, DefaultUi.class, Blocks.class})
@Lob
@Column(length = 65536)
private String description;
/**
* Bookingaccounts below or equal zero represents non accountancy relevant data.
*/
private int bookingAccount;
/**
* The optional uniqueUnitId.
* Possible values:
* <ul>
* <li>-1 and Position.Type == Unit → a old SopoUnit whitout a UniqueUnit</li>
* <li>0 and Position.Type != Unit → a non unit based position</li>
* <li>bigger than 0 and Position.Type == Unit → a unit based position with the matching UniqueUnit</li>
* </ul>
*/
private int uniqueUnitId;
private long uniqueUnitProductId;
/**
* The refurbished id associated with the position.
*/
@Getter
@Setter
private String refurbishedId;
/**
* Serialnumber of the associated unit.
*/
@Getter
@Setter
private String serial;
public Position() {
}
@Builder
Position(PositionType type, String name, double afterTaxPrice, double price, double amount, double tax, String description, int bookingAccount, int uniqueUnitId, long uniqueUnitProductId, String refurbishedId, String serialNumber) {
this.type = type;
this.name = name;
this.afterTaxPrice = afterTaxPrice;
this.price = price;
this.amount = amount;
this.tax = tax;
this.description = description;
this.bookingAccount = bookingAccount;
this.uniqueUnitId = uniqueUnitId;
this.uniqueUnitProductId = uniqueUnitProductId;
this.serial = serialNumber;
this.refurbishedId = refurbishedId;
}
/**
* Creates a partial clone of the position, but without the document.
* <p/>
* @return a copy with document == null
*/
public Position partialClone() {
return new Position(type, name, afterTaxPrice, price, amount, tax, description, bookingAccount, uniqueUnitId, uniqueUnitProductId, refurbishedId, serial);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getAfterTaxPrice() {
return afterTaxPrice;
}
public void setAfterTaxPrice(double afterTaxPrice) {
this.afterTaxPrice = afterTaxPrice;
}
public double getPrice() {
return price;
}
public void setPrice(double nettoPrice) {
this.price = nettoPrice;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public double getTax() {
return tax;
}
public void setTax(double tax) {
this.tax = tax;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getBookingAccount() {
return bookingAccount;
}
public void setBookingAccount(int bookingAccount) {
this.bookingAccount = bookingAccount;
}
public PositionType getType() {
return type;
}
public void setType(PositionType type) {
this.type = type;
}
public Document getDocument() {
return document;
}
public int getUniqueUnitId() {
return uniqueUnitId;
}
public void setUniqueUnitId(int uniqueUnitId) {
this.uniqueUnitId = uniqueUnitId;
}
public long getUniqueUnitProductId() {
return uniqueUnitProductId;
}
public void setUniqueUnitProductId(long uniqueUnitProductId) {
this.uniqueUnitProductId = uniqueUnitProductId;
}
@Override
public int compareTo(Position other) {
if ( other == null ) return 1;
return this.id - other.id;
}
@Override
public boolean equals(Object obj) {
if ( obj == null ) return false;
if ( getClass() != obj.getClass() ) return false;
final Position other = (Position)obj;
if ( this.id != other.id ) return false;
if ( !Objects.equals(this.document, other.document) ) return false;
return true;
}
public boolean equalsContentWithoutId(Position p) {
// EqualsContent should also work with unpersitend objects.
if ( this.document == null && p.document != null ) return false;
if ( this.document != null && p.document == null ) return false;
if ( this.document != null && p.document != null && this.document.getId() != p.document.getId() ) return false;
if ( this.type != p.type ) return false;
if ( !Objects.equals(this.name, p.name) ) return false;
if ( !Objects.equals(this.description, p.description) ) return false;
if ( Math.abs(this.price - p.price) > 0.00001 ) return false;
if ( Math.abs(this.afterTaxPrice - p.afterTaxPrice) > 0.00001 ) return false;
if ( Math.abs(this.tax - p.tax) > 0.00001 ) return false;
if ( Math.abs(this.amount - p.amount) > 0.00001 ) return false;
if ( !Objects.equals(this.bookingAccount, p.bookingAccount) ) return false;
if ( this.uniqueUnitId != p.uniqueUnitId ) return false;
return true;
}
public boolean equalsContent(Position p) {
if ( this.id != p.id ) return false;
// EqualsContent should also work with unpersitend objects.
return equalsContentWithoutId(p);
}
@Override
public int hashCode() {
int hash = 7;
hash = 41 * hash + this.id;
hash = 41 * hash + Objects.hashCode(this.document);
return hash;
}
@Override
public String toString() {
return "Position{" + "id=" + id + ", document.id=" + (document == null ? "null" : document.getId()) + ", type=" + type + ", name=" + name + ", afterTaxPrice=" + afterTaxPrice + ", price=" + price + ", amount=" + amount + ", tax=" + tax + ", description=" + description + ", bookingAccount=" + bookingAccount + ", uniqueUnitId=" + uniqueUnitId + '}';
}
@Null(groups = Blocks.class)
public String getBlocksValidationViolations() {
if ( type == null ) return null;
ArrayList<String> violations = new ArrayList<>();
switch (type) {
case UNIT: // Differs
if ( amount != 1 ) violations.add("Die Menge darf nicht kleiner oder größer als 1 sein.");
if ( uniqueUnitId == 0 ) violations.add("UniqueUnitId ist nicht gesetzt!");
if ( uniqueUnitProductId == 0 ) violations.add("UniqueUnitProductId ist nicht gesetzt!");
break;
case SERVICE: // Default
if ( amount < 1 ) violations.add("Die Menge muss größer als 0 sein.");
break;
case PRODUCT_BATCH: // Differs
return "Artikel Positionen sind für Blocker nicht erlaubt.";
case SHIPPING_COST:
return "Versandkosten sind für Blocker nicht erlaubt.";
case COMMENT:
if ( afterTaxPrice != 0 ) violations.add("Brutto Preis muss 0 sein, ist aber " + afterTaxPrice);
if ( price != 0 ) violations.add("Preis muss 0 sein, ist aber " + price);
}
if ( violations.isEmpty() ) return null;
violations.add(0, "type=" + type);
return violations.toString();
}
@Null(groups = Returns.class)
public String getReturnsValidationViolations() {
if ( type == null ) return null;
ArrayList<String> violations = new ArrayList<>();
switch (type) {
case UNIT: // Differs
if ( afterTaxPrice != 0 ) violations.add("Brutto Preis muss 0 sein, ist aber " + afterTaxPrice);
if ( price != 0 ) violations.add("Preis muss 0 sein, ist aber " + price);
if ( amount != 1 ) violations.add("Die Menge darf nicht kleiner oder größer als 1 sein.");
if ( uniqueUnitId == 0 ) violations.add("UniqueUnitId ist nicht gesetzt!");
if ( uniqueUnitProductId == 0 ) violations.add("UniqueUnitProductId ist nicht gesetzt!");
break;
case SERVICE: // Default
if ( afterTaxPrice == 0 ) violations.add("Brutto Preis ist nicht gesetzt!");
if ( price == 0 ) violations.add("Preis ist nicht gesetzt!");
if ( tax == 0 ) violations.add("Mwst nicht gesetzt!");
if ( amount < 1 ) violations.add("Die Menge muss größer als 0 sein.");
break;
case PRODUCT_BATCH: // Differs
return "Artikel Positionen sind für Rückläufer nicht erlaubt.";
case SHIPPING_COST:
return "Versandkosten sind für Rückläufer nicht erlaubt.";
case COMMENT:
if ( afterTaxPrice != 0 ) violations.add("Brutto Preis muss 0 sein, ist aber " + afterTaxPrice);
if ( price != 0 ) violations.add("Preis muss 0 sein, ist aber " + price);
}
if ( violations.isEmpty() ) return null;
violations.add(0, "type=" + type);
return violations.toString();
}
@Null(groups = DefaultUi.class)
public String getDefaultUiValidationViolations() {
// if this is InPersistence, we could also discover the Type of Document and get more detailed.
if ( type == null ) return null;
ArrayList<String> violations = new ArrayList<>();
switch (type) {
case UNIT:
if ( afterTaxPrice == 0 ) violations.add("Brutto Preis ist nicht gesetzt!");
if ( price == 0 ) violations.add("Preis ist nicht gesetzt!");
if ( amount != 1 ) violations.add("Die Menge darf nicht kleiner oder größer als 1 sein.");
if ( tax == 0 ) violations.add("Mwst nicht gesetzt!");
if ( uniqueUnitId == 0 ) violations.add("UniqueUnitId ist nicht gesetzt!");
if ( uniqueUnitProductId == 0 ) violations.add("UniqueUnitProductId ist nicht gesetzt!");
break;
case SERVICE:
if ( afterTaxPrice == 0 ) violations.add("Brutto Preis ist nicht gesetzt!");
if ( price == 0 ) violations.add("Preis ist nicht gesetzt!");
if ( tax == 0 ) violations.add("Mwst nicht gesetzt!");
if ( amount <= 0 ) violations.add("Die Menge muss größer als 0 sein.");
break;
case PRODUCT_BATCH:
if ( afterTaxPrice == 0 ) violations.add("Brutto Preis ist nicht gesetzt!");
if ( price == 0 ) violations.add("Preis ist nicht gesetzt!");
if ( amount < 1 ) violations.add("Die Menge muss größer als 0 sein.");
if ( tax == 0 ) violations.add("Mwst nicht gesetzt!");
if ( uniqueUnitProductId == 0 ) violations.add("UniqueUnitProductId ist nicht gesetzt!");
break;
case SHIPPING_COST:
if ( afterTaxPrice == 0 ) violations.add("Brutto Preis ist nicht gesetzt!");
if ( price == 0 ) violations.add("Preis ist nicht gesetzt!");
if ( amount != 1 ) violations.add("Die Menge darf nicht kleiner oder größer als 1 sein.");
if ( tax == 0 ) violations.add("Mwst nicht gesetzt!");
break;
case COMMENT:
if ( afterTaxPrice != 0 ) violations.add("Brutto Preis muss 0 sein, ist aber " + afterTaxPrice);
if ( price != 0 ) violations.add("Preis muss 0 sein, ist aber " + price);
}
if ( violations.isEmpty() ) return null;
violations.add(0, "type=" + type);
return violations.toString();
}
/**
* Validation that must hold on Persistence Time.
* <p/>
* @return a String if the Validation failed
*/
@Null
public String getPersistenceValidationViolations() {
if ( type == null ) return null;
if ( document == null ) return null;
if ( document.getType() == null ) return null;
String violation;
if ( document.getType() == DocumentType.RETURNS ) {
violation = getReturnsValidationViolations();
} else if ( document.getType() == DocumentType.BLOCK ) {
violation = getBlocksValidationViolations();
} else {
violation = getDefaultUiValidationViolations();
}
if ( violation == null ) return null;
return "[document.Type=" + document.getType() + "]" + violation;
}
public String toSimpleLine() {
return this.getClass().getSimpleName() + "{" + (amount == 1 ? "" : amount + " * ") + "(" + type + ") " + name + "}";
}
}