/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved.                *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. 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, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 * For the text or an alternative of this public license, you may reach us    *
 * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA        *
 * or via info@compiere.org or http://www.compiere.org/license.html           *
 *****************************************************************************/
package org.compiere.model;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.ResultSet;
import java.util.Properties;

import org.adempiere.base.Core;
import org.adempiere.base.IProductPricing;
import org.adempiere.exceptions.AdempiereException;
import org.adempiere.model.ITaxProvider;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Util;

/**
 *	RMA Line Model
 *	
 *  @author Jorg Janke
 *  @version $Id: MRMALine.java,v 1.3 2006/07/30 00:51:03 jjanke Exp $
 */
public class MRMALine extends X_M_RMALine
{
	/**
	 * generated serial id 
	 */
	private static final long serialVersionUID = 3088864372141663734L;

    /**
     * UUID based Constructor
     * @param ctx  Context
     * @param M_RMALine_UU  UUID key
     * @param trxName Transaction
     */
    public MRMALine(Properties ctx, String M_RMALine_UU, String trxName) {
        super(ctx, M_RMALine_UU, trxName);
		if (Util.isEmpty(M_RMALine_UU))
			setInitialDefaults();
		init();
    }

	/**
	 * 	Standard Constructor
	 *	@param ctx context
	 *	@param M_RMALine_ID id
	 *	@param trxName transaction
	 */
	public MRMALine (Properties ctx, int M_RMALine_ID, String trxName)
	{
		this (ctx, M_RMALine_ID, trxName, (String[]) null);
	}	//	MRMALine

	/**
	 * @param ctx
	 * @param M_RMALine_ID
	 * @param trxName
	 * @param virtualColumns
	 */
	public MRMALine(Properties ctx, int M_RMALine_ID, String trxName, String... virtualColumns) {
		super(ctx, M_RMALine_ID, trxName, virtualColumns);
		if (M_RMALine_ID == 0)
			setInitialDefaults();
		init();
	}

	/**
	 * Set the initial defaults for a new record
	 */
	private void setInitialDefaults() {
		setQty(Env.ONE);
		this.setQtyDelivered(Env.ZERO);
	}

	/**
	 * 	Load Constructor
	 *	@param ctx context
	 *	@param rs result set
	 *	@param trxName transaction
	 */
	public MRMALine (Properties ctx, ResultSet rs, String trxName)
	{
		super(ctx, rs, trxName);
        init();
	}	//	MRMALine
	
	/**	Shipment Line			*/
	protected MInOutLine	m_ioLine = null;
	/**	Product					*/
	protected MProduct	m_product = null;
	/**	Charge					*/
	protected MCharge		m_charge = null;
	/** Tax							*/
	protected MTax 		m_tax = null;
	/** Parent                  */
	protected MRMA		m_parent = null;
    
    protected int precision = 0;
    protected BigDecimal unitAmount = Env.ZERO;
    protected BigDecimal originalQty = Env.ZERO;
    protected int taxId = 0;
    
    /**
     * Initialise precision, unitAmount, originalQty and taxId
     */
    protected void init()
    {
    	// Load m_ioLine
        getShipLine();
        
        if (m_ioLine != null)
        {        	
            // Set unitAmount and originalQty from invoice or order line
            if (getInvoiceLineId() != 0)
            {
                MInvoiceLine invoiceLine = new MInvoiceLine(getCtx(), getInvoiceLineId(), get_TrxName());
                precision = invoiceLine.getPrecision();
                unitAmount = invoiceLine.getPriceActual();
                originalQty = invoiceLine.getQtyInvoiced();
                taxId = invoiceLine.getC_Tax_ID();
            }
            else if (m_ioLine.getC_OrderLine_ID() != 0)
            {
                MOrderLine orderLine = new MOrderLine (getCtx(), m_ioLine.getC_OrderLine_ID(), get_TrxName());
                precision = orderLine.getPrecision();
                unitAmount = orderLine.getPriceActual();
                originalQty = orderLine.getQtyDelivered();
                taxId = orderLine.getC_Tax_ID();
            }
            else
            {
                throw new IllegalStateException("No Invoice/Order line found the Shipment/Receipt line associated");
            }
        }
        else if (getC_Charge_ID() != 0)
        {
        	// Set unitAmount to Charge Amount
            MCharge charge = MCharge.get(this.getCtx(), getC_Charge_ID());
            unitAmount = charge.getChargeAmt();
            
            MInvoice invoice = getParent().getOriginalInvoice();
    		if (invoice != null) 
    			precision = invoice.getPrecision();
    		else 
    		{
    			MOrder order = getParent().getOriginalOrder();
    			if (order != null) 
    				precision = order.getPrecision();
    			else
    				throw new IllegalStateException("No Invoice/Order found the Shipment/Receipt associated");
    		}
            
            // Retrieve tax Exempt
            String sql = "SELECT C_Tax_ID FROM C_Tax WHERE AD_Client_ID=? AND IsActive='Y' "
                + "AND IsTaxExempt='Y' AND ValidFrom < getDate() ORDER BY IsDefault DESC";
            
            // Set tax for charge as exempt        
            taxId = DB.getSQLValueEx(null, sql, Env.getAD_Client_ID(getCtx()));
            m_ioLine = null;
        }
        else if (getM_Product_ID() != 0)
        {
        	// Set unitAmount to standard product price of price list
        	IProductPricing pp = Core.getProductPricing();
    		pp.setRMALine(this, get_TrxName());
        	
        	MInvoice invoice = getParent().getOriginalInvoice();
        	if (invoice != null)
        	{
        		int dropshipLocationId = -1;
        		MOrder order = invoice.getOriginalOrder();
        		if (order != null)
        			dropshipLocationId = order.getDropShip_Location_ID();
        		pp.setM_PriceList_ID(invoice.getM_PriceList_ID());
        		pp.setPriceDate(invoice.getDateInvoiced());
        		
        		precision = invoice.getPrecision();
        		String deliveryViaRule = null;
        		if (invoice.getC_Order_ID() > 0) {
        			deliveryViaRule = new MOrder(getCtx(), invoice.getC_Order_ID(), get_TrxName()).getDeliveryViaRule();
        		}
        		taxId = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID(), invoice.getDateInvoiced(), invoice.getDateInvoiced(),
            			getAD_Org_ID(), getParent().getShipment().getM_Warehouse_ID(),
            			invoice.getC_BPartner_Location_ID(),		//	should be bill to
            			invoice.getC_BPartner_Location_ID(), dropshipLocationId, getParent().isSOTrx(), deliveryViaRule, get_TrxName());
        	}
        	else 
        	{
        		MOrder order = getParent().getOriginalOrder();
        		if (order != null)
        		{
        			pp.setM_PriceList_ID(order.getM_PriceList_ID());
        			pp.setPriceDate(order.getDateOrdered());
        			
        			precision = order.getPrecision();
        			taxId = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID(), order.getDateOrdered(), order.getDateOrdered(),
                			getAD_Org_ID(), order.getM_Warehouse_ID(),
                			order.getC_BPartner_Location_ID(),		//	should be bill to
                			order.getC_BPartner_Location_ID(), order.getDropShip_Location_ID(), getParent().isSOTrx(), order.getDeliveryViaRule(), get_TrxName());
        		}
            	else
            		throw new IllegalStateException("No Invoice/Order found the Shipment/Receipt associated");
        	}
        	
        	pp.calculatePrice();
        	unitAmount = pp.getPriceStd();
        	
        	m_ioLine = null;
        }
    }
	
    /**
     *  Get Parent
     *  @return parent
     */
    public MRMA getParent()
    {
        if (m_parent == null)
            m_parent = new MRMA(getCtx(), getM_RMA_ID(), get_TrxName());
        return m_parent;
    }   //  getParent
	
	/**
	 * 	Set M_InOutLine_ID
	 *	@param M_InOutLine_ID
	 */
	public void setM_InOutLine_ID (int M_InOutLine_ID)
	{
		super.setM_InOutLine_ID (M_InOutLine_ID);
		m_ioLine = null;
	}	//	setM_InOutLine_ID
	
	/**
	 * 	Get Shipment Line
	 *	@return shipment line
	 */
	public MInOutLine getShipLine()
	{
		if ((m_ioLine == null || is_ValueChanged(COLUMNNAME_M_InOutLine_ID)) && getM_InOutLine_ID() != 0)
			m_ioLine = new MInOutLine (getCtx(), getM_InOutLine_ID(), get_TrxName());
		return m_ioLine;
	}	//	getShipLine
	
    /**
     * Retrieves the invoiceLine Id associated with the Shipment/Receipt Line
     * @return Invoice Line ID
     */
    protected int getInvoiceLineId()
    {
    	int invoiceLine_ID = new Query(getCtx(), I_C_InvoiceLine.Table_Name, "M_InOutLine_ID=?", get_TrxName())
    	.setParameters(getM_InOutLine_ID())
    	.firstId();
    	return invoiceLine_ID <= 0 ? 0 : invoiceLine_ID;
    }
    
    /**
     * Get unit amount for product/charge
     * @return Unit Amount
     */
    public BigDecimal getUnitAmt()
    {
        return unitAmount;
    }
    
    /**
     *  Get Total Amount for the line including tax
     *  @return total amount
     */
    public BigDecimal getTotalAmt()
    {
        BigDecimal bd = getAmt().multiply(getQty()); 
		int precision = getPrecision();
		if (bd.scale() > precision)
			bd = bd.setScale(precision, RoundingMode.HALF_UP);
		
        return bd;
    }   //  getAmt
    
    /**
     * Get whether the Ship line has been invoiced
     * @return true if invoiced
     */
    public boolean isShipLineInvoiced()
    {
        return (getInvoiceLineId() != 0);
    }
    
    @Override
    protected boolean beforeSave(boolean newRecord)
    {
		if (newRecord && getParent().isProcessed()) 
		{
			log.saveError("ParentComplete", Msg.translate(getCtx(), "M_RMA_ID"));
			return false;
		}
		
		// Must fill one of M_InOutLine_ID or C_Charge_ID or M_Product_ID
        if (getM_InOutLine_ID() == 0 && getC_Charge_ID() == 0 && getM_Product_ID() == 0)
        {
            log.saveError("FillShipLineOrProductOrCharge", "");
            return false;
        }
        
        // Fill either M_Product_ID or C_Charge_ID, not both
        if (getM_Product_ID() != 0 && getC_Charge_ID() != 0)
        {
            log.saveError("JustProductOrCharge", "");
            return false;
        }
        
        init();
        if (m_ioLine != null)
        {
        	// Validate line quantity
        	if (! checkQty()) 
        	{
                log.saveError("AmtReturned>Shipped", "");
                return false;
        	}
        	
        	// Validate duplicate RMA line for 1 M_InOutLine record
            if (newRecord || is_ValueChanged(COLUMNNAME_M_InOutLine_ID))
            {
                String whereClause = "M_RMA_ID=" + getM_RMA_ID() + " AND M_InOutLine_ID=" + getM_InOutLine_ID() + " AND M_RMALine_ID!=" + getM_RMALine_ID();

                int lineIds[] = MRMALine.getAllIDs(MRMALine.Table_Name, whereClause, this.get_TrxName());
                
                if (lineIds.length > 0)
                {
                    log.saveError("InOutLineAlreadyEntered", "");
                    return false;
                }
            }
        }
        
        // Set default amount and qty for product
        if (this.getM_Product_ID() != 0 && this.getQty().doubleValue() <= 0 && !MRMA.DOCACTION_Void.equals(getParent().getDocAction()))
        {
            if (getQty().signum() == 0)
                this.setQty(Env.ONE);
            if (getAmt().signum() == 0)
                this.setAmt(getUnitAmt());        	
        }

        // Set default amount and qty for charge
        if (this.getC_Charge_ID() != 0 && this.getQty().doubleValue() <= 0 && !MRMA.DOCACTION_Void.equals(getParent().getDocAction()))
        {
            if (getQty().signum() == 0)
                this.setQty(Env.ONE);
            if (getAmt().signum() == 0)
                this.setAmt(getUnitAmt());
        }
        
        // Set amount and qty for product or charge from shipment line
        if (this.getM_InOutLine_ID() != 0 && !MRMA.DOCACTION_Void.equals(getParent().getDocAction()))
        {
        	this.setM_Product_ID(m_ioLine.getM_Product_ID());
        	this.setC_Charge_ID(m_ioLine.getC_Charge_ID());
            this.setAmt(getUnitAmt());
            
            if (newRecord && getQty().signum() == 0)
                this.setQty(originalQty);
        }
        
        // Set tax
        if (this.getC_Tax_ID() == 0)
        	this.setC_Tax_ID(taxId);
        
        // Set Line No
		if (getLine() == 0)
		{
			String sql = "SELECT COALESCE(MAX(Line),0)+10 FROM M_RMALine WHERE M_RMA_ID=?";
			int ii = DB.getSQLValue (get_TrxName(), sql, getM_RMA_ID());
			setLine (ii);
		}
        
        this.setLineNetAmt(getTotalAmt());
        
        return true;
    }
    
    /**
     * Validate that line quantity is &lt;= MInOutLine quantity
     * @return true if pass validation
     */
    public boolean checkQty() 
    {
        if (m_ioLine.getMovementQty().compareTo(getQty()) < 0)
        	return false;
        
        BigDecimal totalQty = DB.getSQLValueBD(get_TrxName(), 
        		"SELECT SUM(Qty) FROM M_RMALine rl JOIN M_RMA r ON (r.M_RMA_ID = rl.M_RMA_ID) WHERE M_InOutLine_ID = ? AND M_RMALine_ID != ? AND r.Processed = 'Y' AND r.DocStatus IN ('CO','CL')", 
        		getM_InOutLine_ID(), getM_RMALine_ID());
        if (totalQty == null)
        	totalQty = Env.ZERO;
        totalQty = totalQty.add(getQty());
        if (m_ioLine.getMovementQty().compareTo(totalQty) < 0)
        	return false;
        
		return true;
	}
    
    /**
     * Update RMA tax
     * @param oldTax true if the old C_Tax_ID should be used
     * @return true if success, false otherwise
     */
    protected boolean updateOrderTax(boolean oldTax) 
    {
    	int C_Tax_ID = getC_Tax_ID();
		boolean isOldTax = oldTax && is_ValueChanged(MRMALine.COLUMNNAME_C_Tax_ID); 
		if (isOldTax)
		{
			Object old = get_ValueOld(MRMALine.COLUMNNAME_C_Tax_ID);
			if (old == null)
			{
				return true;
			}
			C_Tax_ID = ((Integer)old).intValue();
		}
		if (C_Tax_ID == 0)
		{
			return true;
		}
		
		MTax t = MTax.get(C_Tax_ID);
		if (t.isSummary())
		{
			MRMATax[] taxes = MRMATax.getChildTaxes(this, getPrecision(), oldTax, get_TrxName());
			if (taxes != null && taxes.length > 0)
			{
				for(MRMATax tax : taxes)
				{
					if (!tax.calculateTaxFromLines())
						return false;
					if (tax.getTaxAmt().signum() != 0) 
					{
						if (!tax.save(get_TrxName()))
							return false;
					}
					else 
					{
						if (!tax.is_new() && !tax.delete(false, get_TrxName()))
							return false;
					}
				}
			}
		}
		else
		{
			MRMATax tax = MRMATax.get (this, getPrecision(), oldTax, get_TrxName());
			if (tax != null) 
			{
				if (!tax.calculateTaxFromLines())
					return false;
				if (tax.getTaxAmt().signum() != 0) 
				{
					if (!tax.save(get_TrxName()))
						return false;
				}
				else 
				{
					if (!tax.is_new() && !tax.delete(false, get_TrxName()))
						return false;
				}
			}
		}
		return true;
	}

	@Override
    protected  boolean afterSave(boolean newRecord, boolean success)
    {
        if (!success)
            return success;
		MTax tax = new MTax(getCtx(), getC_Tax_ID(), get_TrxName());
        MTaxProvider provider = new MTaxProvider(tax.getCtx(), tax.getC_TaxProvider_ID(), tax.get_TrxName());
		ITaxProvider calculator = Core.getTaxProvider(provider);
		if (calculator == null)
			throw new AdempiereException(Msg.getMsg(getCtx(), "TaxNoProvider"));
    	return calculator.recalculateTax(provider, this, newRecord);
    }
    
    @Override
    protected  boolean afterDelete(boolean success)
    {
        if (!success)
            return success;        
        return updateHeaderAmt();
    }

	/**
	 *	Update Amount on Header
	 *	@return true if header updated
	 */
	public boolean updateHeaderAmt()
	{
		// Update header only if the document is not processed
		if (isProcessed() && !is_ValueChanged(COLUMNNAME_Processed))
			return true;

		MTax tax = new MTax(getCtx(), getC_Tax_ID(), get_TrxName());
        MTaxProvider provider = new MTaxProvider(tax.getCtx(), tax.getC_TaxProvider_ID(), tax.get_TrxName());
		ITaxProvider calculator = Core.getTaxProvider(provider);
		if (calculator == null)
			throw new AdempiereException(Msg.getMsg(getCtx(), "TaxNoProvider"));
    	if (!calculator.updateRMATax(provider, this))
			return false;

    	return calculator.updateHeaderTax(provider, this);
	}	//	updateHeaderTax

    /**
     *  Add to Description
     *  @param description text
     */
    public void addDescription (String description)
    {
        String desc = getDescription();
        if (desc == null)
            setDescription(description);
        else
            setDescription(desc + " | " + description);
    }   //  addDescription
    
    /**
     * Get precision.
     * Based on Invoice if the shipment was invoiced, on Order otherwise.
     */
    public int getPrecision()
    {
        return precision;
    }
    
    /**
     * Get UOM
     * @return C_UOM_ID <br/>
     * - if shipment line exists, from shipment line <br/> 
     * - otherwise return 100 (Each) for charge line or product UOM for product line
     */
    public int getC_UOM_ID()
    {
        if (m_ioLine == null && getC_Charge_ID() != 0) // Charge
            return 100; // Each
        else if (m_ioLine == null && getM_Product_ID() != 0)
        {
        	MProduct product = getProduct();
        	return product.getC_UOM_ID();
        }
        return m_ioLine.getC_UOM_ID();
    }
    
    /**
	 * 	Get Product
	 *	@return product or null
	 */
	public MProduct getProduct()
	{
		if (m_product == null && getM_Product_ID() != 0)
			m_product =  MProduct.getCopy(getCtx(), getM_Product_ID(), get_TrxName());
		return m_product;
	}
	
	/**
	 * 	Get Charge
	 *	@return charge or null
	 */
	public MCharge getCharge()
	{
		if (m_charge == null && getC_Charge_ID() != 0)
			m_charge =  MCharge.getCopy(getCtx(), getC_Charge_ID(), get_TrxName());
		return m_charge;
	}
	
	/**
	 * 	Get Tax (immutable)
	 *	@return tax
	 */
	protected MTax getTax()
	{
		if (m_tax == null)
			m_tax = MTax.get(getCtx(), getC_Tax_ID());
		return m_tax;
	}
    
    /**
     * Get Project
     * @return If based on shipment line, return C_Project_ID from shipment line. Otherwise, return 0
     */
    public int getC_Project_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getC_Project_ID();
    }
    
    /**
     * Get Project Phase
     * @return If based on shipment line, return C_ProjectPhase_ID from shipment line. Otherwise, return 0
     */
    public int getC_ProjectPhase_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getC_ProjectPhase_ID();
    }
    
    /**
     * Get Project Task
     * @return If based on shipment line, return C_ProjectTask_ID from shipment line. Otherwise, return 0
     */
    public int getC_ProjectTask_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getC_ProjectTask_ID();
    }
    
    /**
     * Get Activity
     * @return If based on shipment line, return C_Activity_ID from shipment line. Otherwise, return 0
     */
    public int getC_Activity_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getC_Activity_ID();
    }
    
    /**
     * Get Campaign
     * @return If based on shipment line, return C_Campaign_ID from shipment line. Otherwise, return 0
     */
    public int getC_Campaign_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getC_Campaign_ID();
    }
    
    /**
     * Get Org Trx
     * @return If based on shipment line, return AD_OrgTrx_ID from shipment line. Otherwise, return 0
     */
    public int getAD_OrgTrx_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getAD_OrgTrx_ID();
    }
    
    /**
     * Get User1
     * @return If based on shipment line, return User1_ID from shipment line. Otherwise, return 0
     */
    public int getUser1_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getUser1_ID();
    }
    
    /**
     * Get User2
     * @return If based on shipment line, return User2_ID from shipment line. Otherwise, return 0
     */
    public int getUser2_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getUser2_ID();
    }
    
    /**
     * Get Attribute Set Instance
     * @return If based on shipment line, return M_AttributeSetInstance_ID from shipment line. Otherwise, return 0
     */
    public int getM_AttributeSetInstance_ID()
    {
        if (m_ioLine == null)
            return 0;
        return m_ioLine.getM_AttributeSetInstance_ID();
    }
    
    /**
     * Get Locator
     * @return M_Locator_ID <br/>
     * - if based on shipment line, return M_Locator_ID from shipment line <br/>
     * - otherwise, return 0 for charge line or default locator for product line
     */
    public int getM_Locator_ID()
    {
        if (m_ioLine == null && getC_Charge_ID() != 0)
            return 0;
        else if (m_ioLine == null && getM_Product_ID() != 0)
        {
        	MInOut shipment = getParent().getShipment();
        	MWarehouse warehouse = new MWarehouse (getCtx(), shipment.getM_Warehouse_ID(), get_TrxName());
        	MLocator locator = MLocator.getDefault(warehouse);
        	return locator.getM_Locator_ID();
        }
        return m_ioLine.getM_Locator_ID();
    }

    /**
     * Reset {@link #m_parent} to null 
     */
	public void clearParent()
	{
		this.m_parent = null;
	}
}	//	MRMALine
