package com.ampiere.web.struts.form;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.compiere.framework.Lookup;
import org.compiere.model.MInOutLine;
import org.compiere.model.MInvoiceLine;
import org.compiere.model.MLookupFactory;
import org.compiere.model.MMatchInv;
import org.compiere.model.MMatchPO;
import org.compiere.model.MOrderLine;
import org.compiere.model.MRole;
import org.compiere.model.MStorage;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.DisplayType;
import org.compiere.util.Env;
import org.compiere.util.KeyNamePair;
import org.compiere.util.WebSessionCtx;

import com.ampiere.util.Constants;

/**
 * @author siqinbilige
 */
public class MatchingAction extends Action {

    /** Logger. */
    private CLogger log = CLogger.getCLogger(this.getClass());
    
    /** Matching List Forward. */
    private static final String MATCHING_LIST = "matchinglist";

    /** Action Form. */
    private static final String ACTION_FORM = "MatchingForm";

    /** Global Error Forward. */
    private static final String ERROR_FORWARD = "error";

    /** Invoice. */
    private static final int        MATCH_INVOICE = 0;
    /** Shipment. */
    private static final int        MATCH_SHIPMENT = 1;
    /** Order. */
    private static final int        MATCH_ORDER = 2;

    private StringBuffer    m_sql = null;
    private String          m_dateColumn = "";
    private String          m_qtyColumn = "";
    private String          m_groupBy = "";
    /**
     * Action execute.
     * @param mapping mapping
     * @param form form
     * @param request request
     * @param response response
     * @throws Exception Exception
     * @return ActionForward
     * @see org.apache.struts.action.Action#execute(
     *      org.apache.struts.action.ActionMapping,
     *      org.apache.struts.action.ActionForm,
     *      javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    public final ActionForward execute(
            final ActionMapping mapping,
            final ActionForm form,
            final HttpServletRequest request,
            final HttpServletResponse response)
    throws Exception {
    	
		//201001 
        WebSessionCtx wscTest = WebSessionCtx.get(request); 
        if (wscTest == null) {
            log.log(Level.SEVERE, "Session Time Out.");
            request.setAttribute(Constants.SESSION_TIMEOUT_INFO, "Session Time Out. Please login again.");
            return mapping.findForward(Constants.SESSION_TIMEOUT);
        }
        //--201001    	

        log.fine("Begin " + this.getClass().getName() + ";execute");

        WebSessionCtx wsc = WebSessionCtx.get(request); 
        if (wsc == null) {
            log.log(Level.SEVERE, "Session Time Out.");
            return mapping.findForward(ERROR_FORWARD);
        }

        ActionForward actionForward = mapping.findForward(MATCHING_LIST);
        
        MatchingForm myForm =
            (MatchingForm) request.getAttribute(ACTION_FORM);

        String column = myForm.getChangedColumn();
        if (column.equals("matchFrom")) {
            // Match From changed.
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("matchTo")) {
            // Match To chenged.
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("matchMode")) {
            // Match Mode changed.
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("businessPartnerName")) {
            // Business Partner Name chenged.
            Lookup lookup = null;
            try {
                lookup = MLookupFactory.get(
                        wsc.ctx,
                        0,
                        3499,
                        DisplayType.TableDir,
                        wsc.language,
                        "C_BPartner_ID",
                        0,
                        false,
                        "UPPER(NAME) LIKE '" + myForm.getBusinessPartnerName().toUpperCase() + "%' ");

                Iterator keyNames = lookup.getData(true, false, true, false).iterator();
                KeyNamePair keyName;
                if (keyNames.hasNext()) {
                    keyName = (KeyNamePair) keyNames.next();
                    myForm.setBusinessPartnerId(keyName.getKey());
                    myForm.setBusinessPartnerName(keyName.getName());
                } else {
                    myForm.setBusinessPartnerId(999999999);
                }
            } catch (Exception e) {
                log.log(Level.SEVERE, "MLookupFactory.get AD_Column_ID=" + 3499, e);
            }

            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("productName")) {
            // product Name chenged.
            Lookup lookup = null;
            try {
                lookup = MLookupFactory.get(
                        wsc.ctx,
                        0,
                        3840,
                        DisplayType.TableDir,
                        wsc.language,
                        "M_Product_ID",
                        0,
                        false,
                        "UPPER(M_Product_Trl.NAME) LIKE '" + myForm.getProductName().toUpperCase() + "%' ");

                Iterator keyNames = lookup.getData(true, false, true, false).iterator();
                KeyNamePair keyName;
                if (keyNames.hasNext()) {
                    keyName = (KeyNamePair) keyNames.next();
                    myForm.setProductId(keyName.getKey());
                    myForm.setProductName(keyName.getName());
                } else {
                    myForm.setProductId(999999999);
                }
            } catch (Exception e) {
                log.log(Level.SEVERE, "MLookupFactory.get AD_Column_ID=" + 3840, e);
            }

            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("search")) {
            // 
            myForm.setMatchFromCurrentId(0);
            myForm.setMatchToIdList(null);
            myForm.setMatchFromCurrentBusinessPartnerId(-1);
            myForm.setMatchFromCurrentProductId(-1);
            search(wsc, myForm);
            if (myForm.getMatchFromDataList().size() > 0) {
                searchTo(wsc, myForm);
            }
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("matchFromRowChanged")) {
            // Search.
            myForm.setMatchToIdList(null);
            search(wsc, myForm);
            if (myForm.getMatchFromDataList().size() > 0) {
                searchTo(wsc, myForm);
            }
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("sameBusinessPartner")) {
            // Search.
            myForm.setMatchToIdList(null);
            search(wsc, myForm);
            if (myForm.getMatchFromDataList().size() > 0) {
                searchTo(wsc, myForm);
            }
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("sameProduct")) {
            // Search.
            myForm.setMatchToIdList(null);
            search(wsc, myForm);
            if (myForm.getMatchFromDataList().size() > 0) {
                searchTo(wsc, myForm);
            }
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("sameQuantity")) {
            // Search.
            myForm.setMatchToIdList(null);
            search(wsc, myForm);
            if (myForm.getMatchFromDataList().size() > 0) {
                searchTo(wsc, myForm);
            }
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("matchToIdList")) {
            // Search.
            search(wsc, myForm);
            if (myForm.getMatchFromDataList().size() > 0) {
                searchTo(wsc, myForm);
            }
            actionForward = mapping.findForward(MATCHING_LIST);
        } else if (column.equals("process")) {
            // Process
            process(wsc, myForm);
            // Search
            search(wsc, myForm);
            if (myForm.getMatchFromDataList().size() > 0) {
                searchTo(wsc, myForm);
            }
            actionForward = mapping.findForward(MATCHING_LIST);
        }
        
        myForm.setChangedColumn(null);
        // Save form to request.
        request.setAttribute(ACTION_FORM, myForm);

        log.fine("End " + this.getClass().getName() + ";execute");

        return actionForward;
    }
    
    /**
     * Search.
     * @param wsc context
     * @param form Action From
     */
    private void search(
            final WebSessionCtx wsc,
            final MatchingForm form) {

        //  ** Create SQL **
        makeSQL(form.getMatchFrom(), form.getMatchTo(), (form.getMatchMode() == 1));

        //  ** Add Where Clause **
        //  Product
        if (form.getProductId() > 0) {
            m_sql.append(" AND lin.M_Product_ID=").append(form.getProductId());
        }

        //  Business Partner
        if (form.getBusinessPartnerId() > 0) {
            m_sql.append(" AND hdr.C_BPartner_ID=").append(form.getBusinessPartnerId());
        }

        //  Date
        Timestamp from = null;
        if (form.getDateFrom() != null && !form.getDateFrom().equals("")) {
            from = new Timestamp(wsc.dateFormat.parse(form.getDateFrom(), new ParsePosition(0)).getTime());
        }

        Timestamp to = null;
        if (form.getDateTo() != null && !form.getDateTo().equals("")) {
            to = new Timestamp(wsc.dateFormat.parse(form.getDateTo(), new ParsePosition(0)).getTime());
        }

        if (from != null && to != null) {
            m_sql.append(" AND ")
                 .append(m_dateColumn)
                 .append(" BETWEEN ")
                 .append(DB.TO_DATE(from))
                 .append(" AND ")
                 .append(DB.TO_DATE(to));
        } else if (from != null) {
            m_sql.append(" AND ")
                 .append(m_dateColumn)
                 .append(" >= ")
                 .append(DB.TO_DATE(from));
        } else if (to != null) {
            m_sql.append(" AND ")
                 .append(m_dateColumn)
                 .append(" <= ")
                 .append(DB.TO_DATE(to));
        }

        ArrayList < HashMap > dataList = new ArrayList < HashMap > ();
        HashMap < String, Object > data = new HashMap < String, Object > ();

        String sql = MRole.getDefault().addAccessSQL(
                m_sql.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
                + m_groupBy;
        try {
            Statement stmt = DB.createStatement();
            ResultSet rs = stmt.executeQuery(sql);

            int count = 0;
            while (rs.next()) {
                count++;
                if (count == 1 && form.getMatchFromCurrentId() <= 0) {
                    form.setMatchFromCurrentId(rs.getInt(1));
                    form.setMatchFromCurrentLine(rs.getInt(7));
                    form.setMatchFromCurrentBusinessPartnerId(rs.getInt(5));
                    form.setMatchFromCurrentProductId(rs.getInt(9));
                    form.setMatchFromCurrentQuantity(rs.getInt(10));
                    form.setMatchFromCurrentMatchedQuantity(rs.getInt(11));
                }

                data = new HashMap < String, Object > ();
                // Row Type
                if ((count % 2) == 0) {
                    if (form.getMatchFromCurrentId() == rs.getInt(1) && form.getMatchFromCurrentLine() == rs.getInt(7)) {
                        data.put("RowType", "current");
                    } else {
                        data.put("RowType", "even");
                    }
                } else {
                    if (form.getMatchFromCurrentId() == rs.getInt(1) && form.getMatchFromCurrentLine() == rs.getInt(7)) {
                        data.put("RowType", "current");
                    } else {
                        data.put("RowType", "odd");
                    }
                }
                // ID
                data.put("ID", rs.getInt(1));
                // Document Number
                data.put("DocumentNo", rs.getString(2));
                // Date
                data.put("Date", wsc.dateFormat.format(rs.getTimestamp(3)));
                // Business Partner Name
                data.put("BusinessPartner", rs.getString(4));
                // Business Partner ID
                data.put("BusinessPartnerId", rs.getInt(5));
                // Line
                data.put("Line", rs.getInt(7));
                // Product Name
                data.put("Product", rs.getString(8));
                // Product ID
                data.put("ProductId", rs.getInt(9));
                // Quantity
                data.put("Quantity", wsc.numberFormat.format(rs.getDouble(10)));
                // Matched
                data.put("Matched", wsc.numberFormat.format(rs.getDouble(11)));
                dataList.add(data);
            }
            
            stmt.close();
        } catch (SQLException e) {
            log.log(Level.SEVERE, sql, e);
        }

        form.setMatchFromDataList(dataList);
    }   //  cmd_search

    /**
     * Search To.
     * @param wsc context
     * @param form Action From
     */
    private void searchTo(
            final WebSessionCtx wsc,
            final MatchingForm form) {

        //  ** Create SQL **
        makeSQL(form.getMatchTo(), form.getMatchFrom(), (form.getMatchMode() == 1));

        //  ** Add Where Clause **
        if (form.getSameBusinessPartner() != null) {
            m_sql.append(" AND hdr.C_BPartner_ID=").append(form.getMatchFromCurrentBusinessPartnerId());
        }

        if (form.getSameProduct() != null) {
            m_sql.append(" AND lin.M_Product_ID=").append(form.getMatchFromCurrentProductId());
        }

        if (form.getSameQuantity() != null) {
            m_sql.append(" AND ").append(m_qtyColumn).append("=").append(form.getMatchFromCurrentQuantity());
        }

        //  calculate qty
        double docQty = ((Double)form.getMatchFromCurrentQuantity()).doubleValue();
        double matchedQty = ((Double)form.getMatchFromCurrentMatchedQuantity()).doubleValue();
        double qty = docQty - matchedQty;

        form.setToBeMatchedCount(wsc.numberFormat.format(qty));

        ArrayList < HashMap > dataList = new ArrayList < HashMap > ();
        HashMap < String, Object > data = new HashMap < String, Object > ();

        String sql = MRole.getDefault().addAccessSQL(
                m_sql.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
                + m_groupBy;
        try
        {
            Statement stmt = DB.createStatement();
            ResultSet rs = stmt.executeQuery(sql);

            int count = 0;
            while (rs.next()) {
                count++;
                data = new HashMap < String, Object > ();
                // Row Type
                if ((count % 2) == 0) {
                    data.put("RowType", "even");
                } else {
                    data.put("RowType", "odd");
                }

                // ID
                data.put("ID", rs.getString(1) + "|"
                             + rs.getString(5) + "|"
                             + rs.getString(9) + "|"
                             + rs.getString(10) + "|"
                             + rs.getString(11) + "|"
                             + rs.getString(7));
                // Document Number
                data.put("DocumentNo", rs.getString(2));
                // Date
                data.put("Date", wsc.dateFormat.format(rs.getTimestamp(3)));
                // Business Partner Name
                data.put("BusinessPartner", rs.getString(4));
                // Business Partner ID
                data.put("BusinessPartnerId", rs.getInt(5));
                // Line
                data.put("Line", rs.getInt(7));
                // Product Name
                data.put("Product", rs.getString(8));
                // Product ID
                data.put("ProductId", rs.getInt(9));
                // Quantity
                data.put("Quantity", wsc.numberFormat.format(rs.getInt(10)));
                // Matched
                data.put("Matched", wsc.numberFormat.format(rs.getInt(11)));
                // 
                if (form.getMatchFromCurrentBusinessPartnerId() == rs.getInt(5)
                        && form.getMatchFromCurrentProductId() == rs.getInt(9)) {
                    data.put("disabled","false");
                } else {
                    data.put("disabled","true");
                }

                dataList.add(data);

            }
            
            stmt.close();
        }
        catch (SQLException e)
        {
            log.log(Level.SEVERE, sql, e);
        }

        form.setMatchToDataList(dataList);
        
        String [] ids = form.getMatchToIdList();
        double matching = 0;
        if (ids != null && ids.length != 0) {
            for ( int i = 0; i < ids.length; i++ ) {
                String [] id = ids[i].split("\\|");
                if (form.getMatchMode() == 0) {
                    matching += Double.parseDouble(id[3]);
                }
                matching -= Double.parseDouble(id[4]);
            }
            form.setProcessButtonDisabled("false");
        } else {
            form.setProcessButtonDisabled("true");
        }

        form.setMatchingCount(wsc.numberFormat.format(matching));
        form.setMatchDifferenceCount(wsc.numberFormat.format(qty-matching));
    }   //  seachTo

    /**
     * Process Button Pressed - Process Matching.
     * @param wsc context
     * @param form Action From
     */
    private void process(
            final WebSessionCtx wsc,
            final MatchingForm form) {

        double totalQty = form.getMatchFromCurrentQuantity();
        String [] ids = form.getMatchToIdList();
        for ( int i = 0; i < ids.length; i++ ) {
            String [] id = ids[i].split("\\|");

            //  Qty
            double qty = 0.0;
            if (form.getMatchMode() == 0) {
                qty = Double.parseDouble(id[3]);   //  doc
            }
            qty -= Double.parseDouble(id[4]);  //  matched
            if (qty > totalQty)
                qty = totalQty;
            totalQty -= qty;

            //  Invoice or PO
            boolean invoice = true;
            if (form.getMatchFrom() == MATCH_ORDER || form.getMatchTo() == MATCH_ORDER) {
                invoice = false;
            }

            //  Get Shipment_ID
            int inOutLineId = 0;
            int lineId = 0;
            if (form.getMatchFrom() == MATCH_SHIPMENT) {
                inOutLineId = form.getMatchFromCurrentLine();      //  upper table
                lineId = Integer.parseInt(id[5]);
            } else {
                inOutLineId = Integer.parseInt(id[5]);    //  lower table
                lineId = form.getMatchFromCurrentLine();
            }

            //  Create it
            createMatchRecord(wsc, invoice, inOutLineId, lineId, new BigDecimal(qty));
        }

    }   //  process

    /**
     *  Make SQL.
     *  @param display (Invoice, Shipment, Order) see MATCH_*
     *  @param matchToType (Invoice, Shipment, Order) see MATCH_*
     *  @param matched Match Mode
     */
    private void makeSQL(int display, int matchToType, boolean matched)
    {
        m_sql = new StringBuffer ();
        if (display == MATCH_INVOICE)
        {
            m_dateColumn = "hdr.DateInvoiced";
            m_qtyColumn = "lin.QtyInvoiced";
            m_sql.append("SELECT hdr.C_Invoice_ID,hdr.DocumentNo, hdr.DateInvoiced, bp.Name,hdr.C_BPartner_ID,"
                + " lin.Line,lin.C_InvoiceLine_ID, p.Name,lin.M_Product_ID,"
                + " lin.QtyInvoiced,SUM(NVL(mi.Qty,0)) "
                + "FROM C_Invoice hdr"
                + " INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)"
                + " INNER JOIN C_InvoiceLine lin ON (hdr.C_Invoice_ID=lin.C_Invoice_ID)"
                + " INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)"
                + " INNER JOIN C_DocType dt ON (hdr.C_DocType_ID=dt.C_DocType_ID AND dt.DocBaseType IN ('API','APC'))"
                + " FULL JOIN M_MatchInv mi ON (lin.C_InvoiceLine_ID=mi.C_InvoiceLine_ID) "
                + "WHERE hdr.DocStatus IN ('CO','CL')");
            m_groupBy = " GROUP BY hdr.C_Invoice_ID,hdr.DocumentNo,hdr.DateInvoiced,bp.Name,hdr.C_BPartner_ID,"
                + " lin.Line,lin.C_InvoiceLine_ID,p.Name,lin.M_Product_ID,lin.QtyInvoiced "
                + "HAVING "
                + (matched ? "0" : "lin.QtyInvoiced")
                + "<>SUM(NVL(mi.Qty,0))";
        }
        else if (display == MATCH_ORDER)
        {
            m_dateColumn = "hdr.DateOrdered";
            m_qtyColumn = "lin.QtyOrdered";
            m_sql.append("SELECT hdr.C_Order_ID,hdr.DocumentNo, hdr.DateOrdered, bp.Name,hdr.C_BPartner_ID,"
                + " lin.Line,lin.C_OrderLine_ID, p.Name,lin.M_Product_ID,"
                + " lin.QtyOrdered,SUM(COALESCE(mo.Qty,0)) "
                + "FROM C_Order hdr"
                + " INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)"
                + " INNER JOIN C_OrderLine lin ON (hdr.C_Order_ID=lin.C_Order_ID)"
                + " INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)"
                + " INNER JOIN C_DocType dt ON (hdr.C_DocType_ID=dt.C_DocType_ID AND dt.DocBaseType='POO')"
                + " FULL JOIN M_MatchPO mo ON (lin.C_OrderLine_ID=mo.C_OrderLine_ID) "
                + "WHERE mo.")
                .append(matchToType == MATCH_SHIPMENT ? "M_InOutLine_ID" : "C_InvoiceLine_ID")
                .append(matched ? " IS NOT NULL" : " IS NULL"
                + " AND hdr.DocStatus IN ('CO','CL')");
            m_groupBy = " GROUP BY hdr.C_Order_ID,hdr.DocumentNo,hdr.DateOrdered,bp.Name,hdr.C_BPartner_ID,"
                + " lin.Line,lin.C_OrderLine_ID,p.Name,lin.M_Product_ID,lin.QtyOrdered "
                + "HAVING "
                + (matched ? "0" : "lin.QtyOrdered")
                + "<>SUM(COALESCE(mo.Qty,0))";
        }
        else    //  Shipment
        {
            m_dateColumn = "hdr.MovementDate";
            m_qtyColumn = "lin.MovementQty";
            m_sql.append("SELECT hdr.M_InOut_ID,hdr.DocumentNo, hdr.MovementDate, bp.Name,hdr.C_BPartner_ID,"
                + " lin.Line,lin.M_InOutLine_ID, p.Name,lin.M_Product_ID,"
                + " lin.MovementQty,SUM(NVL(m.Qty,0)) "
                + "FROM M_InOut hdr"
                + " INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)"
                + " INNER JOIN M_InOutLine lin ON (hdr.M_InOut_ID=lin.M_InOut_ID)"
                + " INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)"
                + " INNER JOIN C_DocType dt ON (hdr.C_DocType_ID = dt.C_DocType_ID AND dt.DocBaseType='MMR')"
                + " FULL JOIN ")
                .append(matchToType == MATCH_ORDER ? "M_MatchPO" : "M_MatchInv")
                .append(" m ON (lin.M_InOutLine_ID=m.M_InOutLine_ID) "
                + "WHERE hdr.DocStatus IN ('CO','CL')");
            m_groupBy = " GROUP BY hdr.M_InOut_ID,hdr.DocumentNo,hdr.MovementDate,bp.Name,hdr.C_BPartner_ID,"
                + " lin.Line,lin.M_InOutLine_ID,p.Name,lin.M_Product_ID,lin.MovementQty "
                + "HAVING "
                + (matched ? "0" : "lin.MovementQty")
                + "<>SUM(NVL(m.Qty,0))";
        }
    //  Log.trace(7, "VMatch.tableInit", m_sql + "\n" + m_groupBy);
    }   //  tableInit

    /**
     *  Create Matching Record
     *  @param wsc context
     *  @param invoice true if matching invoice false if matching PO
     *  @param M_InOutLine_ID shipment line
     *  @param Line_ID C_InvoiceLine_ID or C_OrderLine_ID
     *  @param qty quantity
     *  @return true if created
     */
    private boolean createMatchRecord (
            WebSessionCtx wsc,
            boolean invoice,
            int M_InOutLine_ID,
            int Line_ID,
            BigDecimal qty) {

        if (qty.compareTo(Env.ZERO) == 0)
            return true;

        log.fine("IsInvoice=" + invoice
            + ", M_InOutLine_ID=" + M_InOutLine_ID + ", Line_ID=" + Line_ID
            + ", Qty=" + qty);

        //
        boolean success = false;
        MInOutLine sLine = new MInOutLine (wsc.ctx, M_InOutLine_ID, null);
        if (invoice) {    //  Shipment - Invoice
            //  Update Invoice Line
            MInvoiceLine iLine = new MInvoiceLine (wsc.ctx, Line_ID, null);
            iLine.setM_InOutLine_ID(M_InOutLine_ID);
            if (sLine.getC_OrderLine_ID() != 0)
                iLine.setC_OrderLine_ID(sLine.getC_OrderLine_ID());
            iLine.save();
            //  Create Shipment - Invoice Link
            if (iLine.getM_Product_ID() != 0) {
                MMatchInv match = new MMatchInv (iLine, null, qty);
                match.setM_InOutLine_ID(M_InOutLine_ID);
                if (match.save())
                    success = true;
                else
                    log.log(Level.SEVERE, "Inv Match not created: " + match);
            } else {
                success = true;
            }

            //  Create PO - Invoice Link = corrects PO
            if (iLine.getC_OrderLine_ID() != 0 && iLine.getM_Product_ID() != 0)
            {
                MMatchPO matchPO = MMatchPO.create(iLine, sLine, null, qty);
                matchPO.setC_InvoiceLine_ID(iLine);
                matchPO.setM_InOutLine_ID(M_InOutLine_ID);
                if (!matchPO.save()) {
                    log.log(Level.SEVERE, "PO(Inv) Match not created: " + matchPO);
                }
            }
        } else {   //  Shipment - Order
            //  Update Shipment Line
            sLine.setC_OrderLine_ID(Line_ID);
            sLine.save();
            //  Update Order Line
            MOrderLine oLine = new MOrderLine(Env.getCtx(), Line_ID, null);
            if (oLine.get_ID() != 0) {   //  other in MInOut.completeIt
                oLine.setQtyReserved(oLine.getQtyReserved().subtract(qty));
                if(!oLine.save()) {
                    log.severe("QtyReserved not updated - C_OrderLine_ID=" + Line_ID);
                }
            }

            //  Create PO - Shipment Link
            if (sLine.getM_Product_ID() != 0) {
                MMatchPO match = new MMatchPO (sLine, null, qty);
                if (!match.save()) {
                    log.log(Level.SEVERE, "PO Match not created: " + match);
                } else {
                    success = true;
                    //  Correct Ordered Qty for Stocked Products (see MOrder.reserveStock / MInOut.processIt)
                    if (sLine.getProduct() != null && sLine.getProduct().isStocked()) {
                        success = MStorage.add(
                                wsc.ctx,
                                sLine.getM_Warehouse_ID(), 
                                sLine.getM_Locator_ID(), 
                                sLine.getM_Product_ID(), 
                                sLine.getM_AttributeSetInstance_ID(),
                                oLine.getM_AttributeSetInstance_ID(), 
                                null,
                                null,
                                qty.negate(),
                                null
                                );
                    }
                }
            }
            else
                success = true;
        }
        return success;
    }   //  createMatchRecord

}
