/****************************************************************************** * Product: Adempiere ERP & CRM Smart Business Solution * * Copyright (C) 1999-2006 Adempiere, 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. * *****************************************************************************/ package org.compiere.pos; import java.math.BigDecimal; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Properties; import org.compiere.model.MAllocationHdr; import org.compiere.model.MAllocationLine; import org.compiere.model.MBPartner; import org.compiere.model.MOrder; import org.compiere.model.MOrderLine; import org.compiere.model.MOrderTax; import org.compiere.model.MPOS; import org.compiere.model.MPayment; import org.compiere.model.MPaymentProcessor; import org.compiere.model.MProduct; import org.compiere.model.MProductPricing; import org.compiere.model.MStorage; import org.compiere.process.DocAction; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.ValueNamePair; import ar.com.ergio.model.MLAROrderPerception; import ar.com.ergio.model.MLARPaymentHeader; /** * Wrapper for standard order * @author Paul Bowden - Adaxa Pty Ltd * * @contributor Emiliano Pereyra - http://www.ergio.com.ar */ public class PosOrderModel extends MOrder { private static final long serialVersionUID = 5253837037827124425L; //LAR payment terms. public static final int PAYMENTTERMS_Account = 3000000; public static final int PAYMENTTERMS_Cash = 3000001; private MPOS m_pos; private List<MPayment> payments = new ArrayList<MPayment>(); private MLARPaymentHeader paymentHeader; private boolean isPaidFromAccount = false; private BigDecimal discount = Env.ZERO; public PosOrderModel(Properties ctx, int C_Order_ID, String trxName, MPOS pos) { super(ctx, C_Order_ID, trxName); set_TrxName(trxName); m_pos = pos; } /** * Get/create Order * * @return order or null */ public static PosOrderModel createOrder(MPOS pos, MBPartner partner, int bpLocation_ID, String trxName) { PosOrderModel order = new PosOrderModel(Env.getCtx(), 0, trxName, pos); order.setAD_Org_ID(pos.getAD_Org_ID()); order.setIsSOTrx(true); order.setC_POS_ID(pos.getC_POS_ID()); if (pos.getC_DocType_ID() != 0) order.setC_DocTypeTarget_ID(pos.getC_DocType_ID()); else order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_POS); int m_PriceList_ID = pos.getM_PriceList_ID(); if (partner != null) m_PriceList_ID = partner.getM_PriceList_ID(); if (partner == null || partner.get_ID() == 0) partner = pos.getBPartner(); if (partner == null || partner.get_ID() == 0) { throw new AdempierePOSException("No BPartner for order"); } order.setBPartner(partner); order.setC_BPartner_Location_ID(bpLocation_ID); // order.setM_PriceList_ID(m_PriceList_ID); order.setM_Warehouse_ID(pos.getM_Warehouse_ID()); order.setSalesRep_ID(pos.getSalesRep_ID()); if (!order.save()) { order = null; throw new AdempierePOSException("Save order failed"); } return order; } // createOrder /** * Establece <code>solo</code> solo la dirección de envío de la orden * para se usada en la generacion del remito */ @Override public void setC_BPartner_Location_ID(int c_BPartner_Location_ID) { if (c_BPartner_Location_ID > 0) { int bill_Location_ID = getBill_Location_ID(); super.setC_BPartner_Location_ID(c_BPartner_Location_ID); super.setBill_Location_ID(bill_Location_ID); } } /** * @author Community Development OpenXpertya * *Based on Modified Original Code, Revised and Optimized: * *Copyright ConSerTi */ public void setBPartner(MBPartner partner) { if (getDocStatus().equals("DR") || getDocStatus().equals("IP")) { if (partner == null || partner.get_ID() == 0) { throw new AdempierePOSException("no BPartner"); } else { log.info("BPartner - " + partner); super.setBPartner(partner); MOrderLine[] lineas = getLines(); for (int i = 0; i < lineas.length; i++) { lineas[i].setC_BPartner_ID(partner.getC_BPartner_ID()); lineas[i].setTax(); lineas[i].save(); } saveEx(); } } } /** * Create new Line * * @return line or null */ public MOrderLine createLine(MProduct product, BigDecimal qtyOrdered, BigDecimal priceActual, int WindowNo) { if (!(getDocStatus().equals("DR") || getDocStatus().equals("IP"))) { return null; } String stockMsg = checkStockAvailable(product, qtyOrdered, WindowNo); if (stockMsg != null) { throw new AdempierePOSException(stockMsg); } String creditMsg = checkCreditAvailable(product, Env.ONE); if (creditMsg != null) { throw new AdempierePOSException(creditMsg); } //add new line or increase qty // catch Exceptions at order.getLines() int numLines = 0; MOrderLine[] lines = null; try { lines = getLines(null,"Line"); numLines = lines.length; for (int i = 0; i < numLines; i++) { if (lines[i].getM_Product_ID() == product.getM_Product_ID()) { //increase qty BigDecimal current = lines[i].getQtyEntered(); BigDecimal toadd = qtyOrdered; BigDecimal total = current.add(toadd); lines[i].setQty(total); lines[i].setPrice(); // sets List/limit if ( priceActual.compareTo(Env.ZERO) > 0 ) lines[i].setPrice(priceActual); lines[i].save(); return lines[i]; } } } catch (Exception e) { log.severe("Order lines cannot be created - " + e.getMessage()); } //create new line MOrderLine line = new MOrderLine(this); line.setProduct(product); line.setQty(qtyOrdered); line.setPrice(); // sets List/limit if ( priceActual.compareTo(Env.ZERO) > 0 ) line.setPrice(priceActual); line.save(); return line; } // createLine /** * Delete order from database * * @author Comunidad de Desarrollo OpenXpertya * Basado en Codigo Original Modificado, Revisado y Optimizado de: * Copyright (c) ConSerTi */ public boolean deleteOrder () { if (getDocStatus().equals("DR") || getDocStatus().equals("IP")) { MOrderLine[] lines = getLines(); if (lines != null) { int numLines = lines.length; if (numLines > 0) for (int i = numLines - 1; i >= 0; i--) { if (lines[i] != null) deleteLine(lines[i].getC_OrderLine_ID()); } } MOrderTax[] taxs = getTaxes(true); if (taxs != null) { int numTax = taxs.length; if (numTax > 0) for (int i = taxs.length - 1; i >= 0; i--) { if (taxs[i] != null) taxs[i].delete(true); taxs[i].save(); taxs[i] = null; } } getLines(true, null); // requery order setDocStatus("VO"); //delete(true); red1 -- should not delete but void the order setDocAction("--"); // emmie setProcessed(true); //red1 -- to avoid been in history during query save(); return true; } return false; } // deleteOrder /** * to erase the lines from order * @return true if deleted */ public void deleteLine (int C_OrderLine_ID) { if ( C_OrderLine_ID != -1 ) { for ( MOrderLine line : getLines(true, "M_Product_ID") ) { if ( line.getC_OrderLine_ID() == C_OrderLine_ID ) { line.delete(true); line.save(); } } } } // deleteLine /** * Process Order * @author Comunidad de Desarrollo OpenXpertya * *Basado en Codigo Original Modificado, Revisado y Optimizado de: * *Copyright (c) ConSerTi */ public boolean processOrder() { //Returning orderCompleted to check for order completeness boolean orderCompleted = false; // check if order completed OK if (getDocStatus().equals("DR") || getDocStatus().equals("IP") ) { setDocAction(DocAction.ACTION_Complete); try { if (processIt(DocAction.ACTION_Complete) ) { save(); } else { log.info( "Process Order FAILED"); } } catch (Exception e) { log.severe("Order can not be completed - " + e.getMessage()); } finally { // When order failed convert it back to draft so it can be processed if( getDocStatus().equals("IN") ) { setDocStatus("DR"); } else if( getDocStatus().equals("CO") ) { orderCompleted = true; log.info( "SubCheckout - processOrder OK"); } else { log.info( "SubCheckout - processOrder - unrecognized DocStatus"); } } // try-finally } return orderCompleted; } // processOrder @Override public String completeIt() { // Se ejecuta el completeIt() estándar de ADempiere if (super.completeIt().equals(DocAction.STATUS_Invalid)) return DocAction.STATUS_Invalid; /* =========================================================================== */ /* Se ejecuta las acciones propias para completar las Ordenes POS de LAR */ /* =========================================================================== */ // En caso de NO ser una transacción de ctacte, y NO ser un POS de remitos, // se crean y procesan las imputaciones de cobros y se relaciona la factura generada // con la cabecera de cobros creada por la orden de venta PDV if (!isPaidFromAccount && !m_pos.get_ValueAsBoolean("IsShipment")) { // Se crean las imputaciones para cada cobro de la orden final String desc = Msg.translate(Env.getCtx(), "C_Order_ID") + ": " + getDocumentNo(); final Timestamp today = Env.getContextAsDate(Env.getCtx(), "#Date"); final MAllocationHdr alloc = new MAllocationHdr(p_ctx, false, today, getC_Currency_ID(), desc, get_TrxName()); alloc.setAD_Org_ID(Env.getAD_Org_ID(Env.getCtx())); alloc.setDateAcct(today); alloc.saveEx(); // Se recorren los cobros creados para asignale la factura generada // y crear la imputación de pago de los mismos. for (final MPayment payment : getPayments()) { // Asignación de la factura al cobro payment.setC_Invoice_ID(getC_Invoice_ID()); payment.save(get_TrxName()); // Imputación del cobro final MAllocationLine line = new MAllocationLine(alloc, payment.getPayAmt(), payment.getDiscountAmt(), payment.getWriteOffAmt(), payment.getOverUnderAmt()); line.setDocInfo(payment.getC_BPartner_ID(), getC_Order_ID(), getC_Invoice_ID()); line.setC_Payment_ID(payment.getC_Payment_ID()); line.saveEx(get_TrxName()); } // Se completa la imputación de pago alloc.processIt(DocAction.ACTION_Complete); alloc.saveEx(get_TrxName()); // Se relaciona la factura generada con la cabecera de cobro paymentHeader.setC_Invoice_ID(getC_Invoice_ID()); paymentHeader.saveEx(get_TrxName()); } return DocAction.STATUS_Completed; } public BigDecimal getTaxAmt() { BigDecimal taxAmt = Env.ZERO; for (MOrderTax tax : getTaxes(true)) { taxAmt = taxAmt.add(tax.getTaxAmt()); } taxAmt = taxAmt.add(getPerceptionAmt());// TODO - Think about perceptions as "other tax" return taxAmt; } public BigDecimal getSubtotal() { return getGrandTotal().subtract(getTaxAmt()); } @Override public BigDecimal getGrandTotal() { return super.getGrandTotal().add(getPerceptionAmt()); } public BigDecimal getPaidAmt() { if (isPaidFromAccount) return getGrandTotal(); BigDecimal amt = BigDecimal.ZERO; for (final MPayment p : payments) { amt = amt.add(p.getPayAmt()); } return amt; } private BigDecimal getPerceptionAmt() { MLAROrderPerception perception = MLAROrderPerception.get(this, get_TrxName()); return perception == null ? BigDecimal.ZERO : perception.getTaxAmt(); } /** * Permite asignar el importe de descuento que cada cobro puede tener según * su medio de pago * * @param discount importe de descuento */ void setDiscount(final BigDecimal discount) { this.discount = discount == null ? Env.ZERO : discount; } public boolean payCash(BigDecimal amt) { MPayment payment = createPayment(MPayment.TENDERTYPE_Cash); payment.setC_CashBook_ID(m_pos.getC_CashBook_ID()); payment.setAmount(getC_Currency_ID(), amt); payment.setC_BankAccount_ID(m_pos.getC_BankAccount_ID()); setPaymentRule(MOrder.PAYMENTRULE_Cash); setC_PaymentTerm_ID(PAYMENTTERMS_Cash); payment.saveEx(); payments.add(payment); return true; } // payCash public boolean payCheck(BigDecimal amt, String accountNo, String routingNo, String checkNo, String accountName) { MPayment payment = createPayment(MPayment.TENDERTYPE_Check); payment.setAmount(getC_Currency_ID(), amt); payment.setC_BankAccount_ID(m_pos.getC_BankAccount_ID()); payment.setAccountNo(accountNo); payment.setRoutingNo(routingNo); payment.setCheckNo(checkNo); payment.setA_Name(accountName); setPaymentRule(MOrder.PAYMENTRULE_Check); setC_PaymentTerm_ID(PAYMENTTERMS_Cash); set_ValueOfColumn("IsOnDrawer", true); payment.saveEx(); payments.add(payment); return true; } // payCheck public boolean payCreditCard(BigDecimal amt, String accountName, int month, int year, String cardNo, String cvc, String cardtype) { MPayment payment = createPayment(MPayment.TENDERTYPE_CreditCard); payment.setAmount(getC_Currency_ID(), amt); payment.setC_BankAccount_ID(m_pos.getC_BankAccount_ID()); payment.setCreditCard(MPayment.TRXTYPE_Sales, cardtype, cardNo, cvc, month, year); payment.setA_Name(accountName); setPaymentRule(MOrder.PAYMENTRULE_CreditCard); setC_PaymentTerm_ID(PAYMENTTERMS_Cash); payment.saveEx(); payments.add(payment); return true; } // payCheck private MPayment createPayment(String tenderType) { if (paymentHeader == null) { paymentHeader = new MLARPaymentHeader(getCtx(), 0, get_TrxName()); paymentHeader.setAD_Org_ID(m_pos.getAD_Org_ID()); paymentHeader.setC_DocType_ID(m_pos.get_ValueAsInt("C_Payment_DocType_ID")); paymentHeader.setC_BPartner_ID(getC_BPartner_ID()); paymentHeader.setDateTrx(Env.getContextAsDate(getCtx(), "#Date")); paymentHeader.setDocStatus(DocAction.STATUS_Drafted); paymentHeader.setIsReceipt(true); paymentHeader.saveEx(); } MPayment payment = new MPayment(getCtx(), 0, get_TrxName()); payment.setAD_Org_ID(m_pos.getAD_Org_ID()); payment.setTenderType(tenderType); payment.setC_Order_ID(getC_Order_ID()); payment.setIsReceipt(true); payment.setC_BPartner_ID(getC_BPartner_ID()); payment.set_ValueOfColumn("LAR_PaymentHeader_ID", paymentHeader.getLAR_PaymentHeader_ID()); // Si tiene descuento asignado, lo registra como un writeOffAmt if (discount.compareTo(Env.ZERO) > 0) payment.setWriteOffAmt(discount); return payment; } MPayment[] getPayments() { return payments.toArray(new MPayment[payments.size()]); } boolean processPayments() { if (isPaidFromAccount) return true; return paymentHeader.processIt(DocAction.ACTION_Complete); } void clearPayments() { payments.clear(); paymentHeader = null; isPaidFromAccount = false; } void setPaidFromAccount() { isPaidFromAccount = true; setPaymentRule(PAYMENTRULE_OnCredit); setC_PaymentTerm_ID(PAYMENTTERMS_Account); saveEx(); } public void reload() { load( get_TrxName()); getLines(true, ""); } /** * Duplicated from MPayment * Get Accepted Credit Cards for amount * @param amt trx amount * @return credit cards */ public ValueNamePair[] getCreditCards (BigDecimal amt) { try { MPaymentProcessor[] m_mPaymentProcessors = MPaymentProcessor.find (getCtx (), null, null, getAD_Client_ID (), getAD_Org_ID(), getC_Currency_ID (), amt, get_TrxName()); // HashMap<String,ValueNamePair> map = new HashMap<String,ValueNamePair>(); // to eliminate duplicates for (int i = 0; i < m_mPaymentProcessors.length; i++) { if (m_mPaymentProcessors[i].isAcceptAMEX ()) map.put (MPayment.CREDITCARDTYPE_Amex, getCreditCardPair (MPayment.CREDITCARDTYPE_Amex)); if (m_mPaymentProcessors[i].isAcceptDiners ()) map.put (MPayment.CREDITCARDTYPE_Diners, getCreditCardPair (MPayment.CREDITCARDTYPE_Diners)); if (m_mPaymentProcessors[i].isAcceptDiscover ()) map.put (MPayment.CREDITCARDTYPE_Discover, getCreditCardPair (MPayment.CREDITCARDTYPE_Discover)); if (m_mPaymentProcessors[i].isAcceptMC ()) map.put (MPayment.CREDITCARDTYPE_MasterCard, getCreditCardPair (MPayment.CREDITCARDTYPE_MasterCard)); if (m_mPaymentProcessors[i].isAcceptCorporate ()) map.put (MPayment.CREDITCARDTYPE_PurchaseCard, getCreditCardPair (MPayment.CREDITCARDTYPE_PurchaseCard)); if (m_mPaymentProcessors[i].isAcceptVisa ()) map.put (MPayment.CREDITCARDTYPE_Visa, getCreditCardPair (MPayment.CREDITCARDTYPE_Visa)); } // for all payment processors // ValueNamePair[] retValue = new ValueNamePair[map.size ()]; map.values ().toArray (retValue); log.fine("getCreditCards - #" + retValue.length + " - Processors=" + m_mPaymentProcessors.length); return retValue; } catch (Exception ex) { ex.printStackTrace(); return null; } } // getCreditCards /** * * Duplicated from MPayment * Get Type and name pair * @param CreditCardType credit card Type * @return pair */ private ValueNamePair getCreditCardPair (String CreditCardType) { return new ValueNamePair (CreditCardType, getCreditCardName(CreditCardType)); } // getCreditCardPair /** * * Duplicated from MPayment * Get Name of Credit Card * @param CreditCardType credit card type * @return Name */ public String getCreditCardName(String CreditCardType) { if (CreditCardType == null) return "--"; else if (MPayment.CREDITCARDTYPE_MasterCard.equals(CreditCardType)) return "MasterCard"; else if (MPayment.CREDITCARDTYPE_Visa.equals(CreditCardType)) return "Visa"; else if (MPayment.CREDITCARDTYPE_Amex.equals(CreditCardType)) return "Amex"; else if (MPayment.CREDITCARDTYPE_ATM.equals(CreditCardType)) return "ATM"; else if (MPayment.CREDITCARDTYPE_Diners.equals(CreditCardType)) return "Diners"; else if (MPayment.CREDITCARDTYPE_Discover.equals(CreditCardType)) return "Discover"; else if (MPayment.CREDITCARDTYPE_PurchaseCard.equals(CreditCardType)) return "PurchaseCard"; return "?" + CreditCardType + "?"; } // getCreditCardName /** * Performs stock validation according to product depending of its * attributes set instance for it. * * @param product * @param attributes * @param count * @return null or error stock message */ String checkStockAvailable(final MProduct product, final BigDecimal count, int windowNo) { boolean isSaleWithoutStock = m_pos.get_ValueAsBoolean("IsSaleWithoutStock"); if (product.isStocked() && !isSaleWithoutStock) { // TODO - Review this id lookups (m_locator_id and m_attributesetinstance_id) int m_Locator_ID = Env.getContextAsInt(m_pos.getCtx(), windowNo, "M_Locator_ID"); int m_AttributeSetInstance_ID = Env.getContextAsInt(m_pos.getCtx(), windowNo, "M_AttributeSetInstance_ID"); String msg = String.format("Product=%s AttrSetInstance=%d Count=%d WindowNo=%d", product, m_AttributeSetInstance_ID, count.intValue(), windowNo); log.info(msg); BigDecimal available = MStorage.getQtyAvailable(m_pos.getM_Warehouse_ID(), m_Locator_ID, product.get_ID(), m_AttributeSetInstance_ID, get_TrxName()); if (available == null) { available = Env.ZERO; } if (available.signum() == 0) { return Msg.translate(p_ctx, "NoQtyAvailable") + " 0"; } else if (available.compareTo(count) < 0) { return Msg.translate(p_ctx, "InsufficientQtyAvailable") + " " +available.toString(); } } return null; } // checkStockAvailable /** * Performs credit check from BPartner */ String checkCreditAvailable(final MProduct product, final BigDecimal qty) { final MBPartner bp = new MBPartner(m_pos.getCtx(), getC_BPartner_ID(), get_TrxName()); // Limit $0.00 means Unlimited if (bp.getSO_CreditLimit().compareTo(BigDecimal.ZERO) == 0) return null; BigDecimal productPrice = getProductPricing(product.getM_Product_ID()).getPriceStd().multiply(qty); BigDecimal creditUsed = bp.getSO_CreditUsed().add(productPrice); BigDecimal creditAvailable = bp.getSO_CreditLimit().subtract(creditUsed); boolean allowCreditExceeded = m_pos.get_ValueAsBoolean("IsAllowCreditExceeded"); String msg = String.format("C_BPartner_ID=%d CreditUsed=%.2f ProductPrice=%.2f CreditAvailable=%.2f AllowCreditExceeded=%b", bp.getC_BPartner_ID(), bp.getSO_CreditUsed(), productPrice, creditAvailable, allowCreditExceeded); log.info(msg); if (allowCreditExceeded) return null; if (creditAvailable.compareTo(BigDecimal.ZERO) < 0) return Msg.translate(p_ctx, "CreditLimitOver") + String.format("\n Diferencia: %.2f", creditAvailable); return null; } /** * Get and calculate Product Pricing */ private MProductPricing getProductPricing (int M_Product_ID) { final MProductPricing m_productPrice = new MProductPricing (M_Product_ID, getC_BPartner_ID(), Env.ONE, true); m_productPrice.setM_PriceList_ID(m_pos.getM_PriceList_ID()); m_productPrice.setPriceDate(getDateOrdered()); // m_productPrice.calculatePrice(); return m_productPrice; } // getProductPrice } // PosOrderModel.class