LineItem.java

package com.tradecloud.domain.item;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tradecloud.common.base.HibernateUtils;
import com.tradecloud.domain.CommissionInformation;
import com.tradecloud.domain.base.utils.MathUtils;
import com.tradecloud.domain.comment.AddedCommentIncCost;
import com.tradecloud.domain.comment.CommentType;
import com.tradecloud.domain.comment.Commentable;
import com.tradecloud.domain.common.HangerType;
import com.tradecloud.domain.common.ProductProperty;
import com.tradecloud.domain.costing.clean.CostingVisitor;
import com.tradecloud.domain.duties.DutySchedule;
import com.tradecloud.domain.event.Event;
import com.tradecloud.domain.event.LineItemEvent;
import com.tradecloud.domain.model.Original;
import com.tradecloud.domain.model.goodsreceivedreceipt.GoodsReceivedReceiptItem;
import com.tradecloud.domain.model.ordermanagement.Order;
import com.tradecloud.domain.model.ordermanagement.ProductState;
import com.tradecloud.domain.place.Country;
import com.tradecloud.domain.state.Stateful;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.ForeignKey;
import org.springframework.stereotype.Component;

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

@Inheritance(strategy = InheritanceType.JOINED)
@Entity
@Component(value = "lineitem")
@Table(name = "lineitem")
@Access(AccessType.FIELD)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "LineItem")
@NamedQueries({
        @NamedQuery(name = "lineItem.findOriginalByCodeAndOrderReference",
                query = "from LineItem i where i.code = :code and i.order.orderReference = :orderReference " + "and i.copy is false"),
        @NamedQuery(name = "lineItem.findCopyByCodeAndOrderReference",
                query = "from LineItem i where i.code = :code and i.order.orderReference = :orderReference and i.copy is true"),
        @NamedQuery(name = "lineItem.countFindCopyByCodeAndOrderReference",
                query = "select count(i) from LineItem i where i.code = :code and i.order.orderReference = :orderReference"),
        @NamedQuery(name = "lineItem.findLineItemBySupplierReferenceAndReference",
                query = "from LineItem i where i.order = :order and i.supplierReference = :supplierReference and i.code = :code"),
        @NamedQuery(name = "lineItem.findItemByCode", query = "from LineItem i where i.code = :code"),
        @NamedQuery(name = "lineItem.findItemById", query = "from LineItem i where i.id = :id"),
        @NamedQuery(name = "lineItem.findSampleAndSpareItems", query = "from LineItem i where i.order = :order and i.code in (:sample,:spare)"),
        @NamedQuery(name = "lineItem.findItemsByOrder",
                query = "select distinct i from LineItem i left join fetch i.events where i.order = :order order by i.created"),
        @NamedQuery(name = "lineItem.findAdditionalItemsByOrder",
                query = "from LineItem i where i.additional is true and i.order.consignment.shipment.id = :shipmentId"),
        @NamedQuery(name = "lineItem.findItemByCodeAndOrderReferenceAndNumber",
                query = "from LineItem i where i.code = :code and i.order.orderReference = :orderReference and i.order.number = :orderNumber"),
        @NamedQuery(name = "lineItem.findNonCopiedByCodeAndSupplierReferenceAndOrderReference",
                query = "from LineItem i where i.code = :code and i.supplierReference =:supplierReference and "
                        + "i.order.orderReference = :orderReference and i.copy is false")})
public class LineItem extends AbstractItem implements Commentable<AddedCommentIncCost>, Original, Stateful<LineItemState, LineItemEvent>,
        Comparable<LineItem>, AdditionalItem {

    private static final long serialVersionUID = 1L;

    /**
     * Reference back to the owning Order parent.
     */
    // NB: DO NOT WANT A CASCADE.ALL ADDED HERE, should not cascade delete up to
    // the parent!
    @ManyToOne(fetch = FetchType.LAZY)
    @ForeignKey(name = "fk_order")
    @NotNull(message = "Parent order should not be null")
    @XmlAttribute(required = true)
    @XmlIDREF
    @JsonBackReference
    private Order order;

    /**
     * LineItemState INITIALISED after object initialisation. (This is an
     * internal state only).
     */
    @Enumerated(EnumType.STRING)
    @XmlAttribute(name = "State")
    private LineItemState state = LineItemState.UNTARIFFED;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    @JoinColumn(name = "hangeritem_id")
    private HangerLineItem hangerLineItem;
    /**
     * List of internal system events to track the history of an entity as it
     * passes through the various defined states.
     */
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @Fetch(value = FetchMode.SUBSELECT)
    @XmlElementWrapper(name = "LineItemEvents")
    @XmlElement(name = "LineItemEvent")
    @OrderBy("createDateTime")
    private List<LineItemEvent> events = new LinkedList<LineItemEvent>();

    @ManyToOne
    @ForeignKey(name = "fk_unittype")
    @NotNull(message = "Unit type should not be null")
    @XmlElement(name = "UnitType", required = true)
    protected UnitType unitType;

    @ManyToOne
    @ForeignKey(name = "fk_packagetype")
    @NotNull(message = "Package type should not be null")
    @XmlElement(name = "PackageType", required = true)
    protected PackageType packageType;

    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "lineitem_freetextcomments", joinColumns = {@JoinColumn(name = "lineitem_id", unique = false)})
    @Column(name = "reason", unique = true)
    @ForeignKey(name = "fk_lineitem")
    @XmlElementWrapper(name = "FreeTextComments")
    @XmlElement(name = "FreeTextComment")
    @Fetch(value = FetchMode.SUBSELECT)
    protected List<AddedCommentIncCost> comments = new ArrayList<AddedCommentIncCost>();

    @XmlAttribute
    @Temporal(TemporalType.TIMESTAMP)
    protected Date createDate;

    @XmlAttribute
    protected String handlingDescription;

    @XmlAttribute
    protected String packingInstruction;

    @XmlAttribute
    protected String elcStyleDescription;

    @NotNull(message = "Unit quantity should not be null")
    @XmlAttribute(required = true)
    protected BigDecimal unitQuantity;

    @XmlAttribute
    protected BigDecimal packageQuantity;

    @XmlAttribute
    protected BigDecimal roundedPackageQuantity;

    @XmlAttribute
    protected BigDecimal packagePrice;

    @XmlAttribute
    protected BigDecimal unitQuantityReceived;

    @XmlAttribute
    protected BigDecimal unitQuantityInvoiced;

    @XmlAttribute
    protected BigDecimal unitQuantityAvailable;

    @XmlAttribute
    protected BigDecimal unitQuantityReserved;

    @XmlAttribute
    protected BigDecimal unitQuantityToBePacked;

    @XmlAttribute
    protected BigDecimal unitQuantityPacked;

    @XmlAttribute
    protected boolean balanceClosed;

    @XmlAttribute
    protected BigDecimal unitSellingPrice;

    @XmlAttribute
    protected BigDecimal sabsPercentage;

    @XmlAttribute
    protected BigDecimal royaltyPercentage;

    @XmlAttribute
    protected BigDecimal exciseDutyPercentage;

    @XmlAttribute
    private BigDecimal lastLandedCostAmount;

    @XmlAttribute
    private BigDecimal sellingPrice;

    @XmlAttribute
    private BigDecimal dutyCharge;

    @XmlAttribute
    private BigDecimal exciseDuty;

    @XmlAttribute
    protected boolean freeOfCharge;

    @XmlAttribute
    private BigDecimal valueForCustom;

    @Enumerated(EnumType.STRING)
    @XmlAttribute(name = "ItemType")
    private ItemType itemType = ItemType.GENERAL;

    @Transient
    private boolean fromApi;

    @XmlAttribute
    protected Integer lineNumber;

    @XmlAttribute
    @Enumerated(value = EnumType.STRING)
    private HangerType hangerType;

    @XmlAttribute
    private Long parentLineItemId;

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

    @OneToOne(mappedBy = "lineItem", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @ForeignKey(name = "fk_lineitempromotion")
    @XmlElement(name = "Promotion")
    @JsonIgnore
    private LineItemPromotion promotion = new LineItemPromotion();

    @XmlAttribute
    private boolean portHealthInspectionFee;

    @Transient
    private boolean isItemDutiesUpdated = false;

    @XmlAttribute
    @Temporal(TemporalType.TIMESTAMP)
    protected Date requiredOnSiteDate;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    @Fetch(value = FetchMode.SUBSELECT)
    @JoinTable(name = "lineitem_AdditionalNotes", joinColumns = @JoinColumn(name = "lineitem_id"),
            inverseJoinColumns = @JoinColumn(name = "notes_id"))
    private Set<AdditionalNotes> additionalNotes = new HashSet<>();

    @Enumerated(EnumType.STRING)
    private AdditionalLineItemType freeStockType;

    public enum Type {
        FULL, ELC, CARTON_ITEM
    }

    //used for ordering item as per user ordering
    @XmlAttribute
    protected Integer position;

    @Enumerated(value = EnumType.STRING)
    private HomologationStatus homologationStatus;

    public BigDecimal getValueForCustom() {
        return valueForCustom;
    }

    @Override
    public CommentType getCommentType() {
        return CommentType.ITEM;
    }

    public void setValueForCustom(BigDecimal valueForCustom) {
        this.valueForCustom = valueForCustom;
    }

    public boolean isFreeOfCharge() {
        return freeOfCharge;
    }

    public void setFreeOfCharge(boolean freeOfCharge) {
        this.freeOfCharge = freeOfCharge;
    }

    public Date getRequiredOnSiteDate() {
        return requiredOnSiteDate;
    }

    public void setRequiredOnSiteDate(Date requiredOnSiteDate) {
        this.requiredOnSiteDate = requiredOnSiteDate;
    }

    /**
     * Flag to indicate whether the packageQuantity field was the last to be
     * set. To be used in the calculation of the the unitQuantity and
     * packageQuantity fields if true: packageQuantity field was the last to be
     * set and will be used as a priority in the calculation of the unitQuantity
     * if false: unitQuantity field was the last to be set and will be used as a
     * priority in the calculation of the packageQuantity
     *
     * Default value is false which is explicitly set to avoid confusion, we
     * always initially assume unitQuantity was the last set
     */
    @XmlTransient
    @Transient
    @JsonIgnore
    private boolean packageQuantityLastSet = false;

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

    /**
     * Indicates if this line item was added after estimate level.
     */
    private boolean additional;

    /**
     * Indicates if this line item is a copy of an original one. This concept is
     * used to allow users to change actual-level line items.
     *
     * @see <[CDATA[https://connect.devstream.net/display/Dev/2014/03/18/Adding+or
     * +editing+items+at+shipment+level]]>
     */
    private boolean copy;

    @XmlAttribute
    @Temporal(TemporalType.TIMESTAMP)
    private Date addedToOrderDate;

    @XmlAttribute
    private BigDecimal sellPriceExclusiveAmount;

    @XmlAttribute
    private BigDecimal sellPriceInclusiveAmount;

    @XmlAttribute
    private String materialComposition;

    private boolean elc;

    private String itemTemplateDescription;

    @ElementCollection(targetClass = ProductProperty.class, fetch = FetchType.EAGER)
    @Enumerated(EnumType.STRING)
    @CollectionTable(name = "lineitemproperties", uniqueConstraints = @UniqueConstraint(columnNames = {"lineitem_id", "lineitem_property"}))
    @Column(name = "lineitem_property")
    protected Set<ProductProperty> itemProperties = new HashSet<>();

    private Long bulkItemId;//soft link, no foreign key

    private BigDecimal childLinkedQuantity = BigDecimal.ZERO;

    public LineItem() {
        promotion.setLineItem(this);
    }

    /**
     * Copy constructor. Creates a new LineItem based on the supplied LineItem's
     * fields.
     *
     * @param lineItem The line item whose fields will be copied into this instance.
     * @throws IllegalArgumentException if lineItem is null
     */
    public LineItem(LineItem lineItem) {
        copyFieldsFrom(lineItem);
    }

    /**
     * Creates a new LineItem instance based on the supplied LineItem's fields.
     * The difference between this method and
     * {@code LineItem#LineItem(LineItem)} is that this line item will return
     * true for {@code LineItem#isCopy()}.
     *
     * @param lineItem the line item whose fields will be copied into this instance
     * @return a new LineItem instance with it's copy field set to true i.e.
     * {@code LineItem#isCopy()} will be true
     */
    public static LineItem createCopy(LineItem lineItem) {
        LineItem copy = new LineItem(lineItem);
        copy.setCopy(true);
        return copy;
    }

    /**
     * Creates a new LineItem instance with the relevant fields set that
     * identifies it as a Sample item.
     *
     * @return a new LineItem initialised as a sample
     */
    public static LineItem initialiseSample() {
        return initialiseAdditionalLineItem(AdditionalLineItemType.SAMPLE);
    }

    /**
     * Creates a new LineItem instance with the relevant fields set that
     * identifies it as a Spare Part item.
     *
     * @return a new LineItem initialised as a spare part
     */
    public static LineItem initialiseSparePart() {
        return initialiseAdditionalLineItem(AdditionalLineItemType.SPARE_PART);
    }

    public void setState(ProductState productState) {
    }

    /**
     * Initialises a new LineItem instance with the relevant fields set based on
     * the AdditionalLineItemType supplied here.
     *
     * @param additionalLineItemType the line item type that determines the initial field values of
     *                               the line item
     * @return @return a new LineItem initialised with the relevant fields set
     */
    private static LineItem initialiseAdditionalLineItem(AdditionalLineItemType additionalLineItemType) {
        LineItem lineItem = new LineItem();
        lineItem.setAdditional(true);
        /*
         * Only set the following fields for Samples and Spare Parts. See: Process Flow section in
         * https://connect.devstream.net/display/Dev/Samples+and+Spare+Parts
         */
        if (AdditionalLineItemType.SAMPLE == additionalLineItemType || AdditionalLineItemType.SPARE_PART == additionalLineItemType) {
            lineItem.setCode(additionalLineItemType.getName());
            lineItem.setDescription(additionalLineItemType.getName());
            lineItem.setSupplierReference(additionalLineItemType.getName());
            lineItem.setFreeStockType(additionalLineItemType);
        }
        return lineItem;
    }

    /**
     * Copies the fields of the supplied line item into this instance's fields.
     *
     * @param lineItem the {@code LineItem} to copy
     * @throws IllegalArgumentException if lineItem is null
     */
    @Transient
    public void copyFieldsFrom(LineItem lineItem) {
        if (lineItem == null) {
            throw new IllegalArgumentException("Line item cannot be null");
        }
        this.supplier = lineItem.supplier;
        this.organisationalUnit = lineItem.organisationalUnit;
        this.balanceClosed = lineItem.balanceClosed;
        this.created = lineItem.created;
        this.createDate = lineItem.createDate;
        this.currency = lineItem.currency;
        this.getCommissionInformation().setForeignCommissionPercentage(lineItem.getCommissionInformation().getForeignCommissionPercentage());
        this.getCommissionInformation().setWarehousingCommissionPercentage(lineItem.getCommissionInformation().getWarehousingCommissionPercentage());
        this.getCommissionInformation().setSourcingCommissionPercentage(lineItem.getCommissionInformation().getSourcingCommissionPercentage());
        this.getCommissionInformation().setMerchandisingCommissionPercentage(
                lineItem.getCommissionInformation().getMerchandisingCommissionPercentage());
        this.code = lineItem.code;
        this.description = lineItem.description;
        this.countryOfOrigin = lineItem.countryOfOrigin;
        this.supplierReference = lineItem.supplierReference;
        this.barcode = lineItem.barcode;
        this.lastLandedCostAmount = lineItem.lastLandedCostAmount;
        this.unitType = lineItem.unitType;
        this.unitQuantity = lineItem.unitQuantity;
        this.unitPrice = lineItem.unitPrice;
        this.unitVolume = lineItem.unitVolume;
        this.unitWeight = lineItem.unitWeight;
        this.packageType = lineItem.packageType;
        this.packageQuantity = lineItem.packageQuantity;
        this.packagePrice = lineItem.packagePrice;
        this.packageVolume = lineItem.packageVolume;
        this.packageWeight = lineItem.packageWeight;
        this.unitsPerPackage = lineItem.unitsPerPackage;
        this.exciseDuty = lineItem.exciseDuty;
        this.exciseDutyPercentage = lineItem.exciseDutyPercentage;
        this.dutyCharge = lineItem.dutyCharge;
        this.promotion.enabled = lineItem.promotion.enabled;
        this.promotion.name = lineItem.promotion.name;
        this.promotion.quantity = lineItem.promotion.quantity;
        this.promotion.fromDate = lineItem.promotion.fromDate;
        this.promotion.toDate = lineItem.promotion.toDate;
        this.promotion.lineItem = this;
        this.order = lineItem.order;
        this.packageQuantityLastSet = lineItem.packageQuantityLastSet;
        this.weightUOM = lineItem.weightUOM;
        this.volumeUOM = lineItem.volumeUOM;
        this.schedule1Part1A = lineItem.schedule1Part1A == null ? null : lineItem.schedule1Part1A.clone();
        this.schedule1Part2A = lineItem.schedule1Part2A == null ? null : lineItem.schedule1Part2A.clone();
        this.schedule1Part2B = lineItem.schedule1Part2B == null ? null : lineItem.schedule1Part2B.clone();
        this.schedule2Part1 = lineItem.schedule2Part1 == null ? null : lineItem.schedule2Part1.clone();
        this.schedule2Part2 = lineItem.schedule2Part2 == null ? null : lineItem.schedule2Part2.clone();
        this.schedule3Part1 = lineItem.schedule3Part1 == null ? null : lineItem.schedule3Part1.clone();
        this.schedule4Part1 = lineItem.schedule4Part1 == null ? null : lineItem.schedule4Part1.clone();
        this.getCommissionInformation().setForeignCommissionValueType(lineItem.getCommissionInformation().getForeignCommissionValueType());
        this.getCommissionInformation().setWarehousingCommissionValueType(lineItem.getCommissionInformation().getWarehousingCommissionValueType());
        this.getCommissionInformation().setSourcingCommissionValueType(lineItem.getCommissionInformation().getSourcingCommissionValueType());
        this.getCommissionInformation().setMerchandisingCommissionValueType(
                lineItem.getCommissionInformation().getMerchandisingCommissionValueType());
        this.portHealthInspectionFee = lineItem.portHealthInspectionFee;
        this.styleDescription = lineItem.description;
        this.styleReference = lineItem.styleReference;
        this.sellPriceExclusiveAmount = lineItem.sellPriceExclusiveAmount;
        this.sellPriceInclusiveAmount = lineItem.sellPriceInclusiveAmount;
        this.materialComposition = lineItem.materialComposition;
        this.elc = lineItem.elc;
        this.valueDeterminationNumber = lineItem.valueDeterminationNumber == null ? null : lineItem.valueDeterminationNumber;
        this.factory = lineItem.getFactory();
    }

    @Override
    public BigDecimal getUnitPrice() {
        return (unitPrice == null && packagePrice != null && unitsPerPackage != null && !MathUtils.isZero(unitsPerPackage)) ? packagePrice.divide(
                unitsPerPackage, MathUtils.SCALE_VERY_ACCURATE, MathUtils.ROUNDING_MODE) : unitPrice != null ? unitPrice : BigDecimal.ZERO;
    }

    @Override
    public void setUnitPrice(BigDecimal unitPrice) {
        super.setUnitPrice(unitPrice != null ? unitPrice.setScale(MathUtils.SCALE_VERY_ACCURATE, RoundingMode.HALF_UP) : null);
        this.packagePrice = null;
    }

    public BigDecimal getUnitVolumeField() {
        return unitVolume;
    }

    public BigDecimal getUnitWeightField() {
        return unitWeight;
    }

    @Override
    public BigDecimal getUnitWeight() {
//        return (packageWeight != null && unitsPerPackage != null && !MathUtils.isZero(unitsPerPackage) && !MathUtils.isZero(packageWeight))
//                ? packageWeight.divide(unitsPerPackage, MathUtils.SCALE, MathUtils.ROUNDING_MODE)
//                : (unitWeight != null ? unitWeight : BigDecimal.ZERO);
        return unitWeight;
    }

    public Order getOrder() {
        return order;
    }

    public void setOrder(Order order) {
        this.order = order;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public String getHandlingDescription() {
        return handlingDescription;
    }

    public void setHandlingDescription(String handlingDescription) {
        this.handlingDescription = handlingDescription;
    }

    public String getPackingInstruction() {
        return packingInstruction;
    }

    public void setPackingInstruction(String packingInstruction) {
        this.packingInstruction = packingInstruction;
    }

    @Override
    public LineItemState getState() {
        return state;
    }

    @Override
    public void setState(LineItemState state) {
        this.state = state;
    }

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

    public String getElcStyleDescription() {
        return elcStyleDescription;
    }

    public void setElcStyleDescription(String elcStyleDescription) {
        this.elcStyleDescription = elcStyleDescription;
    }

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

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

    public BigDecimal getUnitQuantity() {
        // if the packageQuantity was last set then unitQuantity needs to be recalculated
        unitQuantity = (isPackageQuantityLastSet() && unitsPerPackage != null) ?
                packageQuantity.multiply(unitsPerPackage).setScale(MathUtils.SCALE, MathUtils.ROUNDING_MODE) :
                unitQuantity;
        return unitQuantity;
    }

    public void setUnitQuantity(BigDecimal unitQuantity) {
        if (unitQuantity != null && !unitQuantity.equals(this.unitQuantity)) {
            // only if the value is changing then indicate that the unitQuantity
            // was the last field to be set
            setPackageQuantityLastSet(false);
        }
        this.unitQuantity = unitQuantity;
    }

    public BigDecimal getPackageQuantity() {
        // if the unitQuantity was last set then packageQuantity needs to be recalculated
        packageQuantity =
                (isUnitQuantityLastSet() && unitsPerPackage != null && !MathUtils.isZero(unitsPerPackage)) ? unitQuantity.divide(unitsPerPackage,
                        MathUtils.SCALE, MathUtils.ROUNDING_MODE) : packageQuantity;
        return packageQuantity;
    }

    public void setPackageQuantity(BigDecimal packageQuantity) {
        if (packageQuantity != null && !packageQuantity.equals(this.packageQuantity)) {
            // only if the value is changing then indicate that the unitQuantity
            // was the last field to be set
            setPackageQuantityLastSet(true);
        }
        this.packageQuantity = packageQuantity;

    }

    public BigDecimal getRoundedPackageQuantity() {
        if (packageQuantity != null) {
            roundedPackageQuantity = packageQuantity.setScale(0, RoundingMode.UP);
        }

        return roundedPackageQuantity;
    }

    /**
     * Setter is private, this field will only be controlled from inside this
     * class by rounding t.
     *
     * @param roundedPackageQuantity
     * @see
     */
    @SuppressWarnings("unused")
    private void setRoundedPackageQuantity(BigDecimal roundedPackageQuantity) {
        this.roundedPackageQuantity = roundedPackageQuantity;
    }

    public BigDecimal getPackagePrice() {
        return (unitPrice != null && unitsPerPackage != null) ? unitPrice.multiply(unitsPerPackage).setScale(MathUtils.SCALE,
                MathUtils.ROUNDING_MODE) : packagePrice;
    }

    public void setPackagePrice(BigDecimal packagePrice) {
        this.packagePrice = packagePrice;
    }

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

    @Override
    public BigDecimal getPackageWeight() {
//        return packageWeight != null ? packageWeight : (unitWeight != null && unitsPerPackage != null
//                && !MathUtils.isZero(unitWeight)
//                && !MathUtils.isZero(unitsPerPackage)) ? unitWeight.multiply(unitsPerPackage).setScale(MathUtils.SCALE,
//                MathUtils.ROUNDING_MODE) : packageWeight;
        return packageWeight;
    }

    public BigDecimal getTotalUnitWeight() {
        return (getUnitWeight() != null && unitQuantity != null) ?
                unitQuantity.multiply(getUnitWeight()).setScale(MathUtils.SCALE_DISPLAY, MathUtils.ROUNDING_MODE)
                : BigDecimal.ZERO;
    }

    public BigDecimal getTotalUnitVolume() {
        return (getUnitVolume() != null && unitQuantity != null) ?
                unitQuantity.multiply(getUnitVolume()).setScale(MathUtils.SCALE_DISPLAY, MathUtils.ROUNDING_MODE)
                : BigDecimal.ZERO;
    }

    public BigDecimal getTotalPackageQuantity() {
        // if the unitQuantity was last set then packageQuantity needs to be recalculated
        packageQuantity =
                (isUnitQuantityLastSet() && unitsPerPackage != null && !MathUtils.isZero(unitsPerPackage))
                        ? unitQuantity.divide(unitsPerPackage, MathUtils.SCALE_DISPLAY, MathUtils.ROUNDING_MODE)
                        : packageQuantity;
        return packageQuantity;
    }

    public BigDecimal getUnitQuantityReceived() {
        return unitQuantityReceived;
    }

    public void setUnitQuantityReceived(BigDecimal unitQuantityReceived) {
        this.unitQuantityReceived = unitQuantityReceived;
    }

    public BigDecimal getUnitQuantityInvoiced() {
        return unitQuantityInvoiced;
    }

    public void setUnitQuantityInvoiced(BigDecimal unitQuantityInvoiced) {
        this.unitQuantityInvoiced = unitQuantityInvoiced;
    }

    public BigDecimal getUnitQuantityAvailable() {
        return unitQuantityAvailable;
    }

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

    public BigDecimal getUnitQuantityReserved() {
        return unitQuantityReserved;
    }

    public void setUnitQuantityReserved(BigDecimal unitQuantityReserved) {
        this.unitQuantityReserved = unitQuantityReserved;
    }

    public BigDecimal getUnitQuantityToBePacked() {
        return unitQuantityToBePacked;
    }

    public void setUnitQuantityToBePacked(BigDecimal unitQuantityToBePacked) {
        this.unitQuantityToBePacked = unitQuantityToBePacked;
    }

    public BigDecimal getUnitQuantityPacked() {
        return unitQuantityPacked;
    }

    public void setUnitQuantityPacked(BigDecimal unitQuantityPacked) {
        this.unitQuantityPacked = unitQuantityPacked;
    }

    public boolean isBalanceClosed() {
        return balanceClosed;
    }

    public void setBalanceClosed(boolean balanceClosed) {
        this.balanceClosed = balanceClosed;
    }

    public BigDecimal getUnitSellingPrice() {
        return unitSellingPrice;
    }

    public void setUnitSellingPrice(BigDecimal unitSellingPrice) {
        this.unitSellingPrice = unitSellingPrice;
    }

    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 getLastLandedCostAmount() {
        return this.lastLandedCostAmount;
    }

    public void setLastLandedCostAmount(BigDecimal lastLandedCostAmount) {
        this.lastLandedCostAmount = lastLandedCostAmount;
    }

    public BigDecimal getSellingPrice() {
        return this.sellingPrice;
    }

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

    public BigDecimal getDutyCharge() {
        return this.dutyCharge;
    }

    public void setDutyCharge(BigDecimal dutyCharge) {
        this.dutyCharge = dutyCharge;
    }

    public BigDecimal getExciseDuty() {
        return this.exciseDuty;
    }

    public void setExciseDuty(BigDecimal exciseDuty) {
        this.exciseDuty = exciseDuty;
    }

    @OneToOne(mappedBy = "lineItem")
    public LineItemPromotion getPromotion() {
        return promotion;
    }

    public void setPromotion(LineItemPromotion promotion) {
        this.promotion = promotion;
        promotion.setLineItem(this);
    }

    @Override
    public String toString() {
        return "LineItem{" +
                "code='" + code + '\'' +
                "id='" + getId() + '\'' +
                ", description='" + description + '\'' +
                '}';
    }

    @Override
    public String getDescription() {
        return description;
    }

    public BigDecimal getTotalCost() {
        BigDecimal totalCost = null;
        // don't want to display a zero total if we don't have unit price or quantity set
        if (unitQuantity != null && unitPrice != null) {
            totalCost = MathUtils.multiplyVA(unitQuantity, unitPrice);
        }
        return totalCost;
    }

    public BigDecimal getTotalSalesValue() {
        BigDecimal totalSalesValue = BigDecimal.ZERO;
        // don't want to display a zero total if we don't have unit price or quantity set
        if (unitQuantity != null && unitSellingPrice != null) {
            totalSalesValue = MathUtils.multiplyVA(unitQuantity, unitSellingPrice);
        }
        return totalSalesValue;
    }

    public UnitType getUnitType() {
        return unitType;
    }

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

    public PackageType getPackageType() {
        return packageType;
    }

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

    /**
     * Sets the valid flag of each duty schedule, if it's not null, to the value
     * supplied.
     *
     * @param valid the new value of the valid flag
     */
    public void setDutyInformationValid(boolean valid) {
        setScheduleValidFlag(schedule1Part1A, valid);
        setScheduleValidFlag(schedule1Part2A, valid);
        setScheduleValidFlag(schedule1Part2B, valid);
        setScheduleValidFlag(schedule2Part1, valid);
        setScheduleValidFlag(schedule2Part2, valid);
        setScheduleValidFlag(schedule3Part1, valid);
        setScheduleValidFlag(schedule4Part1, valid);
    }

    /**
     * Determines if this line item is tariffed.
     *
     * @return true if this line item's state equals
     * {@link LineItemState#TARIFFED}, false otherwise
     */
    public boolean isTariffed() {
        return LineItemState.TARIFFED.equals(state);
    }

    /**
     * Convenience method to be used to check if the packageQuantity was set
     * last over the unitQuantity field.
     *
     * @return the packageQuantityLastSet flag, true if the packageQuantity
     * field was set last over the unitQuantity field
     */
    public boolean isPackageQuantityLastSet() {
        return (packageQuantityLastSet && packageQuantity != null);
    }

    /**
     * Convenience method to be used to check if the unitQuantity was set last
     * over the packageQuantity field.
     *
     * @return the opposite of the packageQuantityLastSet flag, true if the
     * unitQuantity field was set last over the packageQuantity field
     */
    public boolean isUnitQuantityLastSet() {
        return (!packageQuantityLastSet && unitQuantity != null);
    }

    /**
     * Convenience method to be used to check if the packageQuantity was a
     * calculated value based on the unit quantity divided by the units per
     * package.
     *
     * @return true if packageQuantityLastSet is false and unitsPerPackage != null
     */
    public boolean isPackageQuantityACalculatedValue() {
        return (!packageQuantityLastSet && packageQuantity != null && unitsPerPackage != null);
    }

    /**
     * Convenience method to be used to check if the unitQuantity was a
     * calculated value based on the package quantity multiplied by the units
     * per package.
     *
     * @return true if packageQuantityLastSet is true and unitsPerPackage != null
     */
    public boolean isUnitQuantityACalculatedValue() {
        return (packageQuantityLastSet && unitQuantity != null && unitsPerPackage != null);
    }

    /**
     * Private method, only this class can control this flag.
     *
     * @param packageQuantityLastSet
     */
    private void setPackageQuantityLastSet(boolean packageQuantityLastSet) {
        this.packageQuantityLastSet = packageQuantityLastSet;
    }

    /**
     * Sets the schedule's valid flag to the valid value supplied if the
     * schedule is not null.
     *
     * @param schedule the schedule whose valid flag to set
     * @param valid    the new value of the schedule's valid flag
     */
    private void setScheduleValidFlag(DutySchedule schedule, boolean valid) {
        if (schedule != null) {
            schedule.setValid(valid);
        }
    }

    public boolean isItemDutiesUpdated() {
        return isItemDutiesUpdated;
    }

    public void setItemDutiesUpdated(boolean isItemDutiesUpdated) {
        this.isItemDutiesUpdated = isItemDutiesUpdated;
    }

    @Override
    public void accept(CostingVisitor costingVisitor) {
        costingVisitor.visit(this);
    }

    /**
     * Requirement is to always display line items in the order that they are
     * created in.
     *
     * @param li
     */
    @Override
    public int compareTo(LineItem li) {
        CompareToBuilder compareToBuilder = new CompareToBuilder();
        return compareToBuilder.append(position, li.getPosition())
                .append(created, li.getCreated()).append(code, li.getCode()).toComparison();


    }

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

    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;
    }

    @Override
    public boolean isAdditional() {
        return additional;
    }

    public boolean isIntegrated() {
        return integrated;
    }

    public void setAdditional(boolean additional) {
        this.additional = additional;
    }

    public boolean isCopy() {
        return copy;
    }

    public void setCopy(boolean copy) {
        this.copy = copy;
    }

    @Override
    public String getKey() {
        return new StringBuilder(toString()).append("-").append(hashCode()).toString();
    }

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

    @Override
    public boolean isSample() {
        return additional && (AdditionalLineItemType.SAMPLE.getName().equals(code) || freeStockType == AdditionalLineItemType.SAMPLE);
    }

    @Override
    public boolean isSparePart() {
        return additional && (AdditionalLineItemType.SPARE_PART.getName().equals(code) || freeStockType == AdditionalLineItemType.SPARE_PART);
    }

    public boolean equalsOnBaseFields(Object obj) {
        return super.equals(obj);
    }

    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (!HibernateUtils.proxyClassEquals(this, obj)) {
            return false;
        }
        LineItem other = (LineItem) obj;
        return new EqualsBuilder().appendSuper(super.equals(obj)).append(copy, other.copy)
                .append(lineNumber, other.getLineNumber()).isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().appendSuper(super.hashCode()).append(copy).append(lineNumber).toHashCode();
    }

    public Date getAddedToOrderDate() {
        return addedToOrderDate;
    }

    public void setAddedToOrderDate(Date addedToOrderDate) {
        this.addedToOrderDate = addedToOrderDate;
    }

    public BigDecimal getSellPriceExclusiveAmount() {
        return sellPriceExclusiveAmount;
    }

    public void setSellPriceExclusiveAmount(BigDecimal sellPriceExclusiveAmount) {
        if (hangerType == null) {
            this.sellPriceExclusiveAmount = sellPriceExclusiveAmount;
        } else {
            this.sellPriceExclusiveAmount = BigDecimal.ZERO;
        }
    }

    public BigDecimal getSellPriceInclusiveAmount() {
        return sellPriceInclusiveAmount;
    }

    public void setSellPriceInclusiveAmount(BigDecimal sellPriceInclusiveAmount) {
        if (hangerType == null) {
            this.sellPriceInclusiveAmount = sellPriceInclusiveAmount;
        } else {
            this.sellPriceInclusiveAmount = BigDecimal.ZERO;
        }
    }

    public String getMaterialComposition() {
        return materialComposition;
    }

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

    public ItemType getItemType() {
        if (itemType == null)
            itemType = ItemType.GENERAL;
        return itemType;
    }

    public void setItemType(ItemType itemtype) {
        this.itemType = itemtype;
        getItacPermit().setRebateSchedule(itemType);
    }

    public HangerLineItem getHangerItem() {
        return hangerLineItem;
    }

    public void setHangerItem(HangerLineItem hangerLineItem) {
        this.hangerLineItem = hangerLineItem;
    }

    public boolean isFromApi() {
        return fromApi;
    }

    public void setFromApi(boolean fromApi) {
        this.fromApi = fromApi;
    }

    public boolean isElc() {
        return elc;
    }

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

    public boolean isLineItem() {
        return true;
    }

    public String getItemTemplateDescription() {
        return itemTemplateDescription;
    }

    public void setItemTemplateDescription(String itemTemplateDescription) {
        this.itemTemplateDescription = itemTemplateDescription;
    }

    public Integer getLineNumber() {
        return lineNumber;
    }

    public void setLineNumber(Integer lineNumber) {
        this.lineNumber = lineNumber;
    }

    public Integer getPosition() {
        return position;
    }

    public void setPosition(Integer position) {
        this.position = position;
    }

    public Set<ProductProperty> getItemProperties() {
        return itemProperties;
    }

    public void setItemProperties(Set<ProductProperty> itemProperties) {
        this.itemProperties = itemProperties;
    }

    public Type getElcOrFull() {
        return this.isElc() ? Type.ELC : Type.FULL;
    }

    public List<AddedCommentIncCost> getComments() {
        return comments;
    }

    public void setComments(List<AddedCommentIncCost> comments) {
        this.comments = comments;
    }

    public HangerType getHangerType() {
        return hangerType;
    }

    public void setHangerType(HangerType hangerType) {
        this.hangerType = hangerType;
        if (hangerType != null) {
            this.sellPriceInclusiveAmount = BigDecimal.ZERO;
            this.sellPriceExclusiveAmount = BigDecimal.ZERO;
        }
    }

    public Long getParentLineItemId() {
        return parentLineItemId;
    }

    public void setParentLineItemId(Long parentLineItemId) {
        this.parentLineItemId = parentLineItemId;
    }

    /**
     * Creates a new LineItem instance based on the supplied LineItem's fields.
     * The difference between this method and
     * {@code LineItem#LineItem(LineItem)} is that this line item will return
     * true for {@code LineItem#isCopy()}.
     *
     * @param lineItem the line item whose fields will be copied into this instance
     * @return a new LineItem instance with it's copy field set to true i.e.
     * {@code LineItem#isCopy()} will be true
     */
    public static LineItem createHangerItem(LineItem lineItem) {
        LineItem copy = new LineItem(lineItem);
        copy.setCopy(true);
        return copy;
    }

    @Override
    public boolean applyItacPermit() {
        return itemType == ItemType.GENERAL_REBATE || itemType == ItemType.INDUSTRIAL_REBATE;
    }

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

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

    @Override
    public void setPenaltyAmount(BigDecimal penaltyAmount) {

    }

    public Long getBulkItemId() {
        return bulkItemId;
    }

    public void setBulkItemId(Long bulkItemId) {
        this.bulkItemId = bulkItemId;
    }

    public BigDecimal getChildLinkedQuantity() {
        return childLinkedQuantity;
    }

    public void setChildLinkedQuantity(BigDecimal childLinkedQuantity) {
        this.childLinkedQuantity = childLinkedQuantity;
    }

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

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

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

    public AdditionalLineItemType getFreeStockType() {
        return freeStockType;
    }

    public void setFreeStockType(AdditionalLineItemType freeStockType) {
        this.freeStockType = freeStockType;
        if (freeStockType != null && freeStockType.isFreeStock()) {
            this.additional = true;
        }
    }

    public String getImplementationType() {
        return "ITEM";
    }

    public HomologationStatus getHomologationStatus() {
        return homologationStatus;
    }

    public void setHomologationStatus(HomologationStatus homologationStatus) {
        this.homologationStatus = homologationStatus;
    }

    public boolean equalsGRRItem(GoodsReceivedReceiptItem item) {
        if (item != null && getCode().equals(item.getReference()) &&
                Objects.equals(lineNumber, item.getLineNumber()) &&
                getOrganisationalUnit().equals(item.getOrganisationalUnit())) {
            return true;
        }
        return false;
    }

    public boolean equalsReceiptItem(String reference, Integer lineNumber, String department, Country coo) {
        if (getCode().equals(reference) && Objects.equals(this.lineNumber, lineNumber) && getCountryOfOrigin().equals(coo)
            /*getOrganisationalUnit().equals(item.getOrganisationalUnit())*/) {
            return true;
        }
        return false;
    }

}