Product.java

package com.tradecloud.domain.item;

import com.tradecloud.domain.CommissionInformation;
import com.tradecloud.domain.base.utils.MathUtils;
import com.tradecloud.domain.common.ProductProperty;
import com.tradecloud.domain.costing.ElcDimensions;
import com.tradecloud.domain.dms.DocumentManagementHardCoding;
import com.tradecloud.domain.duties.*;
import com.tradecloud.domain.event.Event;
import com.tradecloud.domain.event.ProductStateHistory;
import com.tradecloud.domain.model.DMSLinked;
import com.tradecloud.domain.model.hfcmanagement.ProductGroup;
import com.tradecloud.domain.model.ordermanagement.ProductState;
import com.tradecloud.domain.model.organisationalunit.OrganisationalUnit;
import com.tradecloud.domain.place.Country;
import com.tradecloud.domain.state.Stateful;
import com.tradecloud.domain.supplier.OrganisationalUnitSupplier;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.ForeignKey;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.*;
import java.math.BigDecimal;
import java.util.*;

@Entity
@Table(name = "product", uniqueConstraints = {@UniqueConstraint(columnNames = {"code", "supplier_id", "countryoforigin_code",
        "organisationalunit_id"})})
@Access(AccessType.FIELD)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Product")
public class Product extends AbstractItem implements Comparable<Product>, ElcDimensions, Stateful<ProductState,
        ProductStateHistory>, ItemInterface, DMSLinked {

    private static final long serialVersionUID = 1L;

    public Product(String code, OrganisationalUnitSupplier supplier, OrganisationalUnit organisationalUnit, Country countryOfOrigin) {
        this.setCode(code);
        this.setOrganisationalUnit(organisationalUnit);
        this.setSupplier(supplier);
        this.setCountryOfOrigin(countryOfOrigin);
    }

    public enum Type {
        FULL, TEMPLATE, ELC, PRE_PACK
    }

    @ManyToOne
    @JoinColumn(name = "unittype_code")
    @ForeignKey(name = "fk_unittype")
    @XmlElement(name = "UnitType")
    @NotNull(message = "Unit Type is a required field")
    protected UnitType unitType;

    @ManyToOne
    @JoinColumn(name = "packagetype_code")
    @ForeignKey(name = "fk_packagetype")
    @XmlElement(name = "PackageType")
    protected PackageType packageType;

    @ManyToOne
    @XmlElement(name = "ProductCategory")
    private ProductCategory productCategory;

    @ManyToOne
    @XmlElement(name = "ProductsubCategory")
    private ProductSubCategory productSubCategory;

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
    @XmlTransient
    private ProductGroup productGroup;

    /**
     * Bit of hassle with Postgres and the @Lob annotation so this is the
     * recommended approach for now. TODO - untested as yet.
     */
    @Basic(fetch = FetchType.LAZY)
    @org.hibernate.annotations.Type(type = "org.hibernate.type.BinaryType")
    private byte[] image;

    @XmlAttribute
    protected String tariffDescription;

    @XmlAttribute
    protected String lastLandedCostCurrencyCode;

    @XmlAttribute
    protected BigDecimal unitQuantityAvailable;

    @XmlAttribute
    protected BigDecimal sabsPercentage;

    @XmlAttribute
    protected BigDecimal royaltyPercentage;

    @XmlAttribute
    protected BigDecimal exciseDutyPercentage;

    @Transient
    @XmlAttribute
    private BigDecimal customsDuty;

    @Transient
    @XmlAttribute
    private String tariffed;

    @Transient
    @XmlAttribute
    private String tariffHeading;

    @Transient
    @XmlAttribute
    private String additionalTariff;

    /**
     * Product statuses.
     */
    @Enumerated(EnumType.STRING)
    @XmlAttribute(name = "State")
    private ProductState state = ProductState.UNTARIFFED;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @Fetch(value = FetchMode.SELECT)
    @XmlElementWrapper(name = "ProductEvent")
    @XmlElement(name = "ProductEvent")
    @OrderBy("createDateTime")
    @JoinTable(name = "product_productstatehistory",
            joinColumns = @JoinColumn(name = "product_id"),
            inverseJoinColumns = @JoinColumn(name = "productstatehistory_id"))
    private List<ProductStateHistory> events = new LinkedList<ProductStateHistory>();

    /**
     * Indicates if order was created via integration interface.
     */
    @XmlAttribute
    protected boolean integrated;

    @XmlAttribute
    private boolean portHealthInspectionFee;

    @XmlAttribute
    private BigDecimal sellPriceExclusiveAmount;

    @XmlAttribute
    private BigDecimal sellPriceInclusiveAmount;

    @XmlAttribute
    private String materialComposition;

    @XmlAttribute
    private BigDecimal lastLandedCostAmount;

    @Embedded
    @XmlElement(name = "CommissionInformation")
    private CommissionInformation commissionInformation = new CommissionInformation();

    @XmlAttribute
    private BigDecimal sellingPrice;
    //todo product cannot be both export and import, we need just one boolean field(importProduct or exportProduct)
    @XmlAttribute
    protected boolean importProduct;

    @XmlAttribute
    protected boolean exportProduct;

    private boolean complete;

    @XmlTransient
    @Enumerated(EnumType.STRING)
    private Type type = Type.FULL;

    @ElementCollection(fetch = FetchType.EAGER, targetClass = ProductProperty.class)
    @Enumerated(EnumType.STRING)
    @CollectionTable(name = "productproperties")
    @Column(name = "product_property")
    @Fetch(value = FetchMode.SELECT)
    protected Set<ProductProperty> productProperties = new HashSet<>();

    @XmlElementWrapper(name = "additionalNotes")
    @XmlElement(name = "ProductNote")
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    @Fetch(value = FetchMode.SELECT)
    @JoinTable(name = "product_productnotes", joinColumns = @JoinColumn(name = "product_id"),
            inverseJoinColumns = @JoinColumn(name = "product_notes_id"))
    private List<AdditionalNotes> additionalNotes = new ArrayList<>();

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @Fetch(value = FetchMode.SELECT)
    @CollectionTable(name = "product_producthistory")
    @Column(name = "product_id")
    @JoinTable(name = "product_producthistory", joinColumns = @JoinColumn(name = "product_id"),
            inverseJoinColumns = @JoinColumn(name = "history_id"))
    private List<ProductHistory> history = new LinkedList<ProductHistory>();

//    @ManyToOne(fetch = FetchType.LAZY)
//    @JoinFormula("(SELECT MAX(product_producthistory.history_id) " +
//            "FROM product_producthistory left join producthistory on product_producthistory.history_id = producthistory.id " +
//            "WHERE product_producthistory.product_id = id and producthistory.type = 'LAST_GRN' AND producthistory.type = 'LAST_LC')")
//    private ProductHistory lastLandedCostGRN;

    @Transient
    private boolean elc;
    @Transient
    private String supplierName;

    private String templateDescription;

    private boolean sadcCertificateAdded;
    @OneToOne(cascade = CascadeType.ALL)
    private Homologation homologation;

    private boolean useForRfq;

    public boolean isImportProduct() {
        return importProduct;
    }

    public void setImportProduct(boolean importProduct) {
        this.importProduct = importProduct;
    }

    public boolean isExportProduct() {
        return exportProduct;
    }

    public void setExportProduct(boolean exportProduct) {
        this.exportProduct = exportProduct;
    }

    public BigDecimal getSellingPrice() {
        return sellingPrice;
    }

    public void setSellingPrice(BigDecimal sellingPrice) {
        this.sellingPrice = sellingPrice;
    }

    public ProductCategory getProductCategory() {
        return productCategory;
    }

    public void setProductCategory(ProductCategory productCategory) {
        this.productCategory = productCategory;
    }

    public ProductSubCategory getProductSubCategory() {
        return productSubCategory;
    }

    public void setProductSubCategory(ProductSubCategory productSubCategory) {
        this.productSubCategory = productSubCategory;
    }

    public Product() {

    }

    public String getLastLandedCostCurrencyCode() {
        return lastLandedCostCurrencyCode;
    }

    public void setLastLandedCostCurrencyCode(String lastLandedCostCurrencyCode) {
        this.lastLandedCostCurrencyCode = lastLandedCostCurrencyCode;
    }

    public BigDecimal getUnitQuantityAvailable() {
        return unitQuantityAvailable;
    }

    public void setUnitQuantityAvailable(BigDecimal unitQuantityAvailable) {
        this.unitQuantityAvailable = unitQuantityAvailable;
    }

    public String getTariffDescription() {
        return tariffDescription;
    }

    public void setTariffDescription(String tariffDescription) {
        this.tariffDescription = tariffDescription;
    }

    public byte[] getImage() {
        return image;
    }

    public void setImage(byte[] image) {
        this.image = image;
    }

    public String getSupplierReference() {
        return supplierReference;
    }

    public void setSupplierReference(String supplierReference) {
        this.supplierReference = supplierReference;
    }

    public UnitType getUnitType() {
        return unitType;
    }

    public void setUnitType(UnitType unitType) {
        this.unitType = unitType;
    }

    public PackageType getPackageType() {
        return packageType;
    }

    @Override
    public boolean applyItacPermit() {
        return ItemProductUtil.isValidRebateSchedule4(this) || ItemProductUtil.isValidRebateSchedule3(this);
    }

    public void setPackageType(PackageType packageType) {
        this.packageType = packageType;
    }

    @Override
    public String toString() {
        return this.getDescription() + " [id: " + getId() + ", reference: " + getCode() + "]";
    }

    public String getStyleReference() {
        return styleReference;
    }

    public void setStyleReference(String styleReference) {
        this.styleReference = styleReference;
    }

    public String getStyleDescription() {
        return styleDescription;
    }

    public void setStyleDescription(String styleDescription) {
        this.styleDescription = styleDescription;
    }

    public ProductGroup getProductGroup() {
        return productGroup;
    }

    public void setProductGroup(ProductGroup productGroup) {
        this.productGroup = productGroup;
    }

    public String getTariffed() {
        TariffHeading tariffHeading = this.getSchedule1Part1A() != null ? this.getSchedule1Part1A().getTariffHeading() : null;

        if (tariffHeading != null && StringUtils.isNoneEmpty(tariffHeading.getTariff()) && this.getSchedule1Part1A().isValid()) {
            tariffed = "Yes";
        } else {
            tariffed = "No";
        }
        return tariffed;
    }

    public void setDutyPercentage(BigDecimal customsDuty) {
        this.customsDuty = customsDuty;
    }

    public BigDecimal getDutyPercentage() {
        if (getSchedule1Part1A() != null && getSchedule1Part1A().getDutyRate() != null) {
            customsDuty = getSchedule1Part1A().getDutyRate().getPercentage();
        }

        return customsDuty;
    }

    public void setTariffed(String tariffed) {
        this.tariffed = tariffed;
    }

    public String getTariffHeading() {
        TariffHeading tHeading = this.getSchedule1Part1A() != null ? this.getSchedule1Part1A().getTariffHeading() : null;

        if (tHeading != null)
            tariffHeading = tHeading.getTariffHeading();

        return tariffHeading;
    }

    public void setTariffHeading(String tHeading) {
        this.tariffHeading = tHeading;
    }

    public String getAdditionalTariff() {
        TariffHeading heading1_2A = this.getSchedule1Part2A() != null ? this.getSchedule1Part2A().getTariffHeading() : null;
        TariffHeading heading1_2B = this.getSchedule1Part2B() != null ? this.getSchedule1Part2B().getTariffHeading() : null;
        TariffHeading heading1_3E = this.getSchedule1Part3E() != null ? this.getSchedule1Part3E().getTariffHeading() : null;
        TariffHeading heading2_1 = this.getSchedule2Part1() != null ? this.getSchedule2Part1().getTariffHeading() : null;
        TariffHeading heading2_2 = this.getSchedule2Part2() != null ? this.getSchedule2Part2().getTariffHeading() : null;

        if (heading1_2A != null && StringUtils.isNoneEmpty(heading1_2A.getTariff()) && this.getSchedule1Part2A().isValid()) {
            additionalTariff = "Yes";
        } else if (heading1_2B != null && StringUtils.isNoneEmpty(heading1_2B.getTariff()) && this.getSchedule1Part2B().isValid()) {
            additionalTariff = "Yes";
        } else if (heading1_3E != null && StringUtils.isNoneEmpty(heading1_3E.getTariff()) && this.getSchedule1Part3E().isValid()) {
            additionalTariff = "Yes";
        } else if (heading2_1 != null && StringUtils.isNoneEmpty(heading2_1.getTariff()) && this.getSchedule2Part1().isValid()) {
            additionalTariff = "Yes";
        } else if (heading2_2 != null && StringUtils.isNoneEmpty(heading2_2.getTariff()) && this.getSchedule2Part2().isValid()) {
            additionalTariff = "Yes";
        } else {
            additionalTariff = "No";
        }

        return additionalTariff;
    }

    public void setAdditionalTariff(String additionalTariff) {
        this.additionalTariff = additionalTariff;
    }

    @Override
    public int compareTo(Product o) {
        if (created != null && o.created != null) {
            return o.created.compareTo(created);
        }
        return 0;
    }

    public void init() {
        if (getSchedule1Part2A() == null) {
            setSchedule1Part2A(new Schedule1Part2A());
            getSchedule1Part2A().setDutyRate(new DutyRate());
            getSchedule1Part2A().getDutyRate().setValue(BigDecimal.ZERO);
        }

        if (getSchedule1Part2B() == null) {
            setSchedule1Part2B(new Schedule1Part2B());
            getSchedule1Part2B().setDutyRate(new TypedDutyRate());
            getSchedule1Part2B().getDutyRate().setValue(BigDecimal.ZERO);
        }

        if (getSchedule2Part1() == null) {
            setSchedule2Part1(new Schedule2Part1());
            getSchedule2Part1().setDutyRate(new TypedDutyRate());
            getSchedule2Part1().getDutyRate().setValue(BigDecimal.ZERO);
        }

        if (getSchedule1Part1A() == null) {
            setSchedule1Part1A(new Schedule1Part1A());
            getSchedule1Part1A().setDutyRate(new TypedDutyRate());
            getSchedule1Part1A().getDutyRate().setValue(BigDecimal.ZERO);
        }

        if (getSchedule2Part2() == null) {
            setSchedule2Part2(new Schedule2Part2());
            getSchedule2Part2().setDutyRate(new TypedDutyRate());
            getSchedule2Part2().getDutyRate().setValue(BigDecimal.ZERO);
        }

        if (getSchedule1Part3E() == null) {
            setSchedule1Part3E(new Schedule1Part3E());
            getSchedule1Part3E().setDutyRate(new TypedDutyRate());
            getSchedule1Part3E().getDutyRate().setValue(BigDecimal.ZERO);
        }

        if (getSchedule1Part7() == null) {
            setSchedule1Part7(new Schedule1Part7());
            getSchedule1Part7().setDutyRate(new TypedDutyRate());
            getSchedule1Part7().getDutyRate().setValue(BigDecimal.ZERO);
        }

        if (getSchedule3Part1() == null) {
            setSchedule3Part1(new Schedule3Part1());
            getSchedule3Part1().setDutyRate(new TypedDutyRate());
            getSchedule3Part1().getDutyRate().setValue(BigDecimal.ZERO);
        }

        if (getSchedule4Part1() == null) {
            setSchedule4Part1(new Schedule4Part1());
            getSchedule4Part1().setDutyRate(new TypedDutyRate());
            getSchedule4Part1().getDutyRate().setValue(BigDecimal.ZERO);
        }

    }

    public boolean isIntegrated() {
        return integrated;
    }

    public void setIntegrated(boolean integrated) {
        this.integrated = integrated;
    }

    public boolean isPortHealthInspectionFee() {
        return portHealthInspectionFee;
    }

    public void setPortHealthInspectionFee(boolean portHealthInspectionFee) {
        this.portHealthInspectionFee = portHealthInspectionFee;
    }

    public CommissionInformation getCommissionInformation() {
        if (commissionInformation == null) {
            commissionInformation = new CommissionInformation();
        }
        return commissionInformation;
    }

    public void setCommissionInformation(CommissionInformation commissionInformation) {
        this.commissionInformation = commissionInformation;
    }

    public BigDecimal getSabsPercentage() {
        return sabsPercentage;
    }

    public void setSabsPercentage(BigDecimal sabsPercentage) {
        this.sabsPercentage = sabsPercentage;
    }

    public BigDecimal getRoyaltyPercentage() {
        return royaltyPercentage;
    }

    public void setRoyaltyPercentage(BigDecimal royaltyPercentage) {
        this.royaltyPercentage = royaltyPercentage;
    }

    public BigDecimal getExciseDutyPercentage() {
        return exciseDutyPercentage;
    }

    public void setExciseDutyPercentage(BigDecimal exciseDutyPercentage) {
        this.exciseDutyPercentage = exciseDutyPercentage;
    }

    public BigDecimal getSellPriceExclusiveAmount() {
        return sellPriceExclusiveAmount;
    }

    public void setSellPriceExclusiveAmount(BigDecimal sellPriceExclusiveAmount) {
        this.sellPriceExclusiveAmount = sellPriceExclusiveAmount;
    }

    public BigDecimal getSellPriceInclusiveAmount() {
        return sellPriceInclusiveAmount;
    }

    public void setSellPriceInclusiveAmount(BigDecimal sellPriceInclusiveAmount) {
        this.sellPriceInclusiveAmount = sellPriceInclusiveAmount;
    }

    public String getMaterialComposition() {
        return materialComposition;
    }

    public void setMaterialComposition(String materialComposition) {
        this.materialComposition = materialComposition;
    }

    public BigDecimal getLastLandedCostAmount() {
        return lastLandedCostAmount;
    }

    public void setLastLandedCostAmount(BigDecimal lastLandedCostAmount) {
        //it is not mandatory but it cannot be cleared once set.
        if (lastLandedCostAmount != null) {
            this.lastLandedCostAmount = lastLandedCostAmount;
        }
    }

    public boolean isComplete() {
        return complete;
    }

    public void setComplete(boolean complete) {
        this.complete = complete;
    }

    @Override
    public BigDecimal getPackageVolume() {
        if (packageVolume != null && !MathUtils.isZero(packageVolume)) {
            return packageVolume;
        }
        ItemProductUtil.calculateAndSetVolume(this, packageVolume, unitVolume);
        return packageVolume;
    }

    @Override
    public Date getAddedToOrderDate() {
        return null;
    }

    public ProductState getState() {
        return state;
    }

    public void setState(ProductState state) {
        this.state = state;
    }

    @Override
    public ProductStateHistory getLastEvent() {
        return Event.getLastEvent(events);
    }

    @Override
    public List<ProductStateHistory> getEvents() {
        return events;
    }

    @Override
    public boolean inNonEditableState() {
        return false;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public void setEvents(List<ProductStateHistory> events) {
        this.events = events;
    }

    public List<AdditionalNotes> getAdditionalNotes() {
        return additionalNotes;
    }

    public void setAdditionalNotes(List<AdditionalNotes> additionalNotes) {
        this.additionalNotes = additionalNotes;
    }

    public Set<ProductProperty> getProductProperties() {
        if (productProperties == null)
            productProperties = new HashSet<>();
        return productProperties;
    }

    public void setProductProperties(Set<ProductProperty> productProperties) {
        this.productProperties = productProperties;
    }

    @Override
    public BigDecimal getUnitQuantity() {
        return unitQuantityAvailable;
    }

    @Override
    public void setUnitQuantity(BigDecimal unitQuantity) {
        unitQuantityAvailable = unitQuantity;
    }

    public Type getType() {
        if (type == null) {
            type = Type.FULL;
        }
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public BigDecimal getCustomsDuty() {
        return customsDuty;
    }

    public void setCustomsDuty(BigDecimal customsDuty) {
        this.customsDuty = customsDuty;
    }

    public List<ProductHistory> getHistory() {
        return history;
    }

    public void setHistory(List<ProductHistory> history) {
        this.history = history;
    }

//    public ProductHistory getLastLandedCostGRN() {
//        return lastLandedCostGRN;
//    }

    public Type getElcOrFull() {
        if (this.type == Type.ELC) {
            return this.type;
        }
        return Type.FULL;
    }

    public boolean isElc() {
        return elc;
    }

    public void setElc(boolean elc) {
        this.elc = elc;
    }

    public String getSupplierName() {
        return supplierName;
    }

    public void setSupplierName(String supplierName) {
        this.supplierName = supplierName;
    }

    public String getTemplateDescription() {
        return templateDescription;
    }

    public void setTemplateDescription(String templateDescription) {
        this.templateDescription = templateDescription;
    }

    @Override
    public Set<ProductProperty> getProperties() {
        return getProductProperties();
    }

    public boolean isSadcCertificateAdded() {
        return sadcCertificateAdded;
    }

    public void setSadcCertificateAdded(boolean sadcCertificateAdded) {
        this.sadcCertificateAdded = sadcCertificateAdded;
    }

    @Override
    public BigDecimal getPenaltyAmount() {
        return null;
    }

    @Override
    public void setPenaltyAmount(BigDecimal penaltyAmount) {

    }

    @Override
    public boolean inTariffiedState() {
        return state == ProductState.TARIFFED.TARIFFED;
    }

    @Override
    public String getDMSKey() {
        if (getSupplier() != null) {
            return new StringBuilder()
                    .append(getCode())
                    .append(getCountryOfOrigin().getCode())
                    .append(getSupplier().getSupplier().getCode())
                    .append(getOrganisationalUnit().getCode())
                    .toString();
        } else {
            return new StringBuilder()
                    .append(getCode())
                    .append(getCountryOfOrigin().getCode())
                    .append(getOrganisationalUnit().getCode())
                    .toString();
        }
    }

    @Override
    public String getDocumentGroupName() {
        return DocumentManagementHardCoding.PRODUCT.name();
    }

    @Override
    public String getReference() {
        return getCode();
    }

    @Override
    public String getImplementationType() {
        return "PRODUCT";
    }

    public Homologation getHomologation() {
        return homologation;
    }

    public void setHomologation(Homologation homologation) {
        this.homologation = homologation;
    }

    public boolean isUseForRfq() {
        return useForRfq;
    }

    public void setUseForRfq(boolean useForRfq) {
        this.useForRfq = useForRfq;
    }
}