/******************************************************************************
* 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 ar.com.ergio.model;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import org.compiere.acct.Doc;
import org.compiere.acct.DocTax;
import org.compiere.acct.Fact;
import org.compiere.acct.FactLine;
import org.compiere.model.MAcctSchema;
import org.compiere.model.MAllocationHdr;
import org.compiere.model.MAllocationLine;
import org.compiere.model.MBPartner;
import org.compiere.model.MClient;
import org.compiere.model.MDocType;
import org.compiere.model.MInOut;
import org.compiere.model.MInvoice;
import org.compiere.model.MLocation;
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.MPaymentAllocate;
import org.compiere.model.MSequence;
import org.compiere.model.MTax;
import org.compiere.model.ModelValidationEngine;
import org.compiere.model.ModelValidator;
import org.compiere.model.PO;
import org.compiere.pos.PosOrderModel;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import static ar.com.ergio.model.LAR_TaxPayerType.RESPONSABLE_INSCRIPTO;
import ar.com.ergio.util.LAR_Utils;
/**
* Validator for Localization Argentina
*
* @author Emiliano Gonzalez - http://www.comit.com.ar
* @version $Id: LAR_Validator.java,v 1.0 2011/11/04 egonzalez Exp $
*/
public class LAR_Validator implements ModelValidator {
/**
* Constructor.
* The class is instantiated when logging in and client is selected/known
*/
public LAR_Validator()
{
super();
} // LAR_Validator
/** Logger */
private static CLogger log = CLogger.getCLogger(LAR_Validator.class);
/** Client */
private int m_AD_Client_ID = -1;
/**
* Initialize Validation
* @param engine validation engine
* @param client client
*/
public void initialize (ModelValidationEngine engine, MClient client)
{
//client = null for global validator
if (client != null) {
m_AD_Client_ID = client.getAD_Client_ID();
log.info(client.toString());
}
else {
log.info("Initializing global validator: "+this.toString());
}
// Tables to be monitored
engine.addModelChange(MBPartner.Table_Name, this);
engine.addModelChange(MOrderLine.Table_Name, this);
engine.addModelChange(MOrder.Table_Name, this);
engine.addModelChange(MPayment.Table_Name, this);
engine.addModelChange(MInvoice.Table_Name, this);
engine.addModelChange(MLARPaymentHeader.Table_Name, this);
engine.addModelChange(MInOut.Table_Name, this);
// Se aregan las facturas dentro de una cabecera
engine.addModelChange(MPaymentAllocate.Table_Name, this);
// Documents to be monitored
engine.addDocValidate(MPayment.Table_Name, this);
engine.addDocValidate(MInvoice.Table_Name, this);
engine.addDocValidate(MInOut.Table_Name, this);
engine.addDocValidate(MAllocationHdr.Table_Name, this);
engine.addDocValidate(MLARPaymentHeader.Table_Name, this);
engine.addDocValidate(PosOrderModel.Table_Name, this);
} // initialize
/**
* Model Change of a monitored Table. Called after
* PO.beforeSave/PO.beforeDelete when you called addModelChange for the
* table
*
* @param po
* persistent object
* @param type
* TYPE_
* @return error message or null
* @exception Exception
* if the recipient wishes the change to be not accept.
*/
public String modelChange(final PO po, int type) throws Exception
{
log.info(po.get_TableName() + " Type: " + type);
String msg;
// Changes on BPartners
if (po.get_TableName().equals(MBPartner.Table_Name) && type == TYPE_BEFORE_CHANGE)
{
MBPartner bp = (MBPartner) po;
LAR_TaxPayerType taxPayerType = LAR_TaxPayerType.getTaxPayerType(bp);
if (!taxPayerType.equals(LAR_TaxPayerType.CONSUMIDOR_FINAL)) {
// Check CUIT number
String cuit = bp.get_ValueAsString("TaxID");
if (!LAR_Utils.validateCUIT(cuit)) {
return "ERROR: CUIT invalido";
}
// Check IIBB number
msg = checkIIBBNumber(bp);
if (msg != null) {
return msg;
}
}
}
// Changes on OrderLines
if (po.get_TableName().equals(MOrderLine.Table_Name) &&
(type == TYPE_AFTER_NEW || type == TYPE_AFTER_DELETE || type == TYPE_AFTER_CHANGE))
{
MOrderLine ol = (MOrderLine) po;
int c_BPartner_ID = ol.getC_BPartner_ID();
MBPartner bp = new MBPartner(ol.getCtx(), c_BPartner_ID, ol.get_TrxName());
msg = calculatePerceptionLine(bp, ol, type);
if (msg != null) {
return msg;
}
}
// Changes on Order
if (po.get_TableName().equals(MOrder.Table_Name) && type == TYPE_BEFORE_DELETE)
{
MOrder order = (MOrder) po;
msg = deletePerceptionLine(order);
if (msg != null) {
return msg;
}
}
// Sincroniza el nro de documento de los remitos "manuales"
// con el asignado en su orden de remito origen
if (po.get_TableName().equals(MInOut.Table_Name) && type == TYPE_BEFORE_NEW)
{
msg = changeShipmentDocumentNo((MInOut) po);
if (msg != null)
return msg;
}
// Elimina la retención sobre los pagos cuando se modifica el header
if (po.get_TableName().equals(MLARPaymentHeader.Table_Name) && type == TYPE_AFTER_CHANGE)
{
msg = clearPaymentWithholdingFromHeader((MLARPaymentHeader) po);
if (msg != null) {
return msg;
}
}
// Determine letter for sales invoices (from PosOrders)
if (po.get_TableName().equals(MInvoice.Table_Name) && type == TYPE_AFTER_NEW)
{
MInvoice invoice = (MInvoice) po;
msg = changeDocTypeForInvoice(invoice);
if (msg != null) {
return msg;
}
}
// Despues de modificar un pago, se actualiza la retención y el total de al cabecera
if (po.get_TableName().equals(MPayment.Table_Name) &&
(type == TYPE_AFTER_NEW || type == TYPE_AFTER_CHANGE || type == TYPE_AFTER_DELETE)
)
{
msg = clearPaymentWithholdingFromPayments((MPayment) po, type);
if (msg != null)
return msg;
msg = updatePaymentHeaderTotalAmt((MPayment) po, type);
if (msg != null)
return msg;
}
// Despues de modificar/agregar una factura se actualiza la retención y el total de la cabecera
if (po.get_TableName().equals(MPaymentAllocate.Table_Name)
&& (type == TYPE_AFTER_NEW || type == TYPE_AFTER_CHANGE || type == TYPE_AFTER_DELETE))
{
msg = clearPaymentWithholdingFromPaymentAllocate((MPaymentAllocate) po, type);
if (msg != null)
return msg;
}
//german wagner custom
//{
if
(po.get_TableName().equals(MPayment.Table_Name) &&
( type == ModelValidator.TYPE_BEFORE_NEW || type == ModelValidator.TYPE_BEFORE_CHANGE))
{
MPayment pay = (MPayment) po;
Integer source = (Integer) pay.get_Value("LAR_PaymentSource_ID");
if((source==null)||(source <=0))
return null;
msg=setReconciled(source, "N", pay.get_TrxName());
if(msg!=null)
return msg;
// Marcos Zúñiga -Excludes payment source from drawer (when is not reversal payment)
if (pay.getReversal_ID() == 0)
{
msg=setIsOnDrawer(source, "N", pay.get_TrxName());
if(msg!=null)
return msg;
}
msg=setReconciled(pay.getC_Payment_ID(),"N", pay.get_TrxName());
if(msg!=null)
return msg;
}
if
(po.get_TableName().equals(MPayment.Table_Name) && (type == ModelValidator.TYPE_BEFORE_DELETE ))
{
MPayment pay = (MPayment) po;
Integer source = (Integer) pay.get_Value("LAR_PaymentSource_ID");
if((source==null)||(source <=0))
return null;
msg=setReconciled(source, "N", pay.get_TrxName());
if(msg!=null)
return msg;
// Marcos Zúñiga
msg=setIsOnDrawer(source, "Y", pay.get_TrxName());
if(msg!=null)
return msg;
}
//}
return null;
}
private String changeDocTypeForInvoice(final MInvoice invoice)
{
if (invoice.isSOTrx() && !invoice.isReversal())
{
log.info("Changing doctype for " + invoice);
final MBPartner bp = new MBPartner(invoice.getCtx(), invoice.getC_BPartner_ID(), invoice.get_TrxName());
// Utiliza la Organización del entorno, esto posibilita determinar correctamente el
// tipo de documento cuando se factura una orden creada en otra organización
int ad_Org_ID = Env.getAD_Org_ID(bp.getCtx());
/*
* @emmie - Siempre se recupera el ID del POS desde la factura, ya que el mismo o se
* asigna en las ventanas correspondientes, o se asigna en el contructor de
* la orden cuando se crea una factura desde una orden (venta desde POS)
*/
int c_POS_ID = invoice.get_ValueAsInt("C_POS_ID");
// @emmie @mzuniga Se abstrae la forma de recuperar el tipo de documento
// @mzuniga Se considera el tipo de documento base (NC o Factura)
MDocType dt_orig = new MDocType(invoice.getCtx(), invoice.getC_DocTypeTarget_ID(), invoice.get_TrxName());
final FindInvoiceDocType findDocType = new FindInvoiceDocType(bp, c_POS_ID, ad_Org_ID, dt_orig.getDocBaseType());
final MDocType docType = findDocType.getDocType();
// Check retrieved doctype
if (docType == null) {
return "No se encontró el tipo de documento";
}
/*
* Si la factura fue generada desde una POS Order, se asume que provino
* del POS y se le cambia el tipo de documento destino por el obtenido
* a partir del BP y la Letra.
*
* Algo similar sucede si la orden fue generada desde una Orden de Remito
*/
MOrder order = new MOrder(invoice.getCtx(), invoice.getC_Order_ID(), invoice.get_TrxName());
MDocType dt = new MDocType(invoice.getCtx(), order.getC_DocTypeTarget_ID(), invoice.get_TrxName());
// Marcos Zúñiga : Se consideran también las Warehouse Orders (WP) y las On Credit Orders (WI)
if (dt.getDocSubTypeSO() != null
&& (dt.getDocSubTypeSO().equals(MDocType.DOCSUBTYPESO_POSOrder)
|| dt.getDocSubTypeSO().equals(MDocType.DOCSUBTYPESO_WarehouseOrder) || (dt
.getDocSubTypeSO().equals(MDocType.DOCSUBTYPESO_OnCreditOrder))))
invoice.setC_DocTypeTarget_ID(docType.getC_DocType_ID());
invoice.set_ValueOfColumn("LAR_DocumentLetter_ID", findDocType.getLAR_DocumentLetter_ID());
if (!invoice.save()) {
return "No se pudo cambiar el tipo de documento";
}
}
return null;
}
/**
* Validate Document.
* Called as first step of DocAction.prepareIt
* when you called addDocValidate for the table.
* Note that totals, etc. may not be correct.
* @param po persistent object
* @param timing see TIMING_ constants
* @return error message or null
*/
public String docValidate(final PO po, int timing)
{
log.info(po.get_TableName() + " Timing: "+timing);
String msg;
// Antes de preparar la cabecera, se verifica si la retención fue generada
if (po.get_TableName().equals(MLARPaymentHeader.Table_Name) && timing == TIMING_BEFORE_PREPARE)
{
msg = preparePaymentWithholding((MLARPaymentHeader) po);
if (msg != null)
return msg;
}
// Después de anular la cabecera, se borran los certificados de retención
if (po.get_TableName().equals(MLARPaymentHeader.Table_Name) && timing == TIMING_AFTER_VOID)
{
msg = ((MLARPaymentHeader) po).BorrarCertificadosdeRetenciondelHeader();
if (msg != null)
return msg;
}
// Return source payment to drawer
if ((po.get_TableName().equals(MPayment.Table_Name))
&& (timing == TIMING_AFTER_VOID || timing == TIMING_AFTER_REVERSECORRECT))
{
MPayment payment = (MPayment) po;
Integer source = (Integer) payment.get_Value("LAR_PaymentSource_ID");
msg = setIsOnDrawer(source, "Y", payment.get_TrxName());
if (msg != null) {
return msg;
}
}
// Determine documentNo for voided invoices
if (po.get_TableName().equals(MInvoice.Table_Name) &&
(timing == TIMING_AFTER_REVERSECORRECT || timing == TIMING_AFTER_VOID))
{
msg = changeVoidDocumentNo(po);
if (msg != null) {
return msg;
}
}
// Después de anular remitos, procesa numeración y ordenes relacionea
if (po.get_TableName().equals(MInOut.Table_Name) &&
(timing == TIMING_AFTER_REVERSECORRECT || timing == TIMING_AFTER_VOID))
{
// Cambia el documentNo del remito
msg = changeVoidDocumentNo(po);
if (msg != null)
return msg;
msg = voidWarehouseOrder((MInOut) po);
if (msg != null)
return msg;
}
// before posting the allocation - post the payment withholdings vs writeoff amount
if (po.get_TableName().equals(MAllocationHdr.Table_Name) && timing == TIMING_BEFORE_POST) {
msg = accountingForWithholdingOnPayment((MAllocationHdr) po);
if (msg != null)
return msg;
}
// Antes de completar una Orde de Venta (POS), cambia el tipo de documento de la
// misma, dependiendo el medio de pago
if (po.get_TableName().equals(MOrder.Table_Name) && timing == TIMING_BEFORE_PREPARE)
{
msg = changeShipmentDocType((MOrder) po);
if (msg != null)
return msg;
}
return null;
} // docValidate
/**
* User Login.
* Called when preferences are set
* @param AD_Org_ID org
* @param AD_Role_ID role
* @param AD_User_ID user
* @return error message or null
*/
public String login (int AD_Org_ID, int AD_Role_ID, int AD_User_ID)
{
log.info("AD_User_ID=" + AD_User_ID);
return null;
} // login
/**
* Get Client to be monitored
* @return AD_Client_ID client
*/
public int getAD_Client_ID()
{
return m_AD_Client_ID;
} // getAD_Client_ID
private String checkIIBBNumber(final MBPartner bp)
{
String msg = null;
String nroIIBB = (bp.get_ValueAsString("DUNS")).replace("-", "").trim();
if (!LAR_Utils.validateIIBBNumber(nroIIBB, bp.get_ValueAsInt("LCO_ISIC_ID")))
msg = "ERROR: número de IIBB invalido";
return msg;
}
private String calculatePerceptionLine(final MBPartner bp, final MOrderLine line, int type)
{
if (type == TYPE_AFTER_NEW || type == TYPE_AFTER_DELETE
|| (type == TYPE_AFTER_CHANGE
&& (line.is_ValueChanged("LineNetAmt")
|| line.is_ValueChanged("M_Product_ID")
|| line.is_ValueChanged("IsActive")
|| line.is_ValueChanged("C_Tax_ID")
|| line.is_ValueChanged("C_BPartner_ID")
)
)
)
{
// Check if withholding on sales is needed
final MPOS pos = MPOS.get(Env.getCtx(), Env.getContextAsInt(Env.getCtx(),Env.POS_ID));
if (!pos.get_ValueAsBoolean("IsGenerateWithholdingOnSale")) {
return null;
}
final MOrder order = line.getParent();
// Check if is a sales transaction
if (!order.isSOTrx())
return null;
final WithholdingConfig[] configs = WithholdingConfig.getConfig(bp, true, order.get_TrxName(), order, order.getDateOrdered());
// Se recorren las configuraciones recuperadas
// Se crean las líneas de percepción
for (int i = 0; i < configs.length; i++)
{
final WithholdingConfig wc = configs[0];
log.info("Withholding conf >> " + wc);
// Calcula el subtotal y el importe de la percepción
BigDecimal taxAmt = BigDecimal.ZERO;
BigDecimal gravado = BigDecimal.ZERO;
BigDecimal perceptionAmt = BigDecimal.ZERO;
for (MOrderTax tax : order.getTaxes(true)) {
taxAmt = taxAmt.add(tax.getTaxAmt());
}
// Acumula la base imponible para calcular la Percepción de IIBB
for (MOrderLine oline : order.getLines()) {
if (oline.getM_Product().getC_TaxCategory_ID() == wc.getC_TaxCategory_ID()) {
gravado = gravado.add(oline.getLineNetAmt());
}
}
if (RESPONSABLE_INSCRIPTO.equals(LAR_TaxPayerType
.getTaxPayerType(bp))) {
perceptionAmt = (((gravado.multiply(wc.getAliquot())).divide(new BigDecimal (100))).setScale(2,
BigDecimal.ROUND_HALF_UP)).abs();
} else {
perceptionAmt = (((gravado.add(taxAmt).multiply(wc.getAliquot())).divide(new BigDecimal (100))).setScale(2,
BigDecimal.ROUND_HALF_UP)).abs();
}
// Crea la percepción de la orden
MLAROrderPerception perception = MLAROrderPerception.get(order, order.get_TrxName());
perception.setC_Order_ID(order.get_ID());
perception.setC_Tax_ID(wc.getC_Tax_ID());
perception.setLCO_WithholdingRule_ID(wc.getWithholdingRule_ID());
perception.setLCO_WithholdingType_ID(wc.getWithholdingType_ID());
perception.setTaxAmt(perceptionAmt);
perception.setTaxBaseAmt(gravado);
perception.setIsTaxIncluded(false);
if (!perception.save()) {
return "Error al craer percepción";
}
// Update order amounts
if (!order.save()) {
return "Error al intentar actualizar los importes de la orden";
}
} // Fin recorrido configuraciones
}
return null;
}
private String deletePerceptionLine(final MOrder order)
{
// Check if is a sales transaction
if (!order.isSOTrx())
return null;
// Check if withholding on sales is needed
final MPOS pos = MPOS.get(Env.getCtx(), Env.getContextAsInt(Env.getCtx(),Env.POS_ID));
if (!pos.get_ValueAsBoolean("IsGenerateWithholdingOnSale")) {
return null;
}
int c_Order_ID = order.get_ID();
log.info("Borrar las percepciones para la orden: " + c_Order_ID);
String sql = "DELETE FROM LAR_OrderPerception WHERE C_ORDER_ID=?";
PreparedStatement pstmt = null;
try {
pstmt = DB.prepareStatement(sql, order.get_TrxName());
pstmt.setInt(1, c_Order_ID);
pstmt.executeUpdate();
} catch (Exception e) {
log.log(Level.SEVERE, sql, e);
return e.getMessage();
} finally {
DB.close(pstmt);
pstmt = null;
}
return null;
}
/**
* Elimina los "Pagos Retención" y los Certificados de Retención
* asociados a una cabecera.
* (basado en el cálculo de la LCO)
*
* @param header cabecera de pago a eliminar
* @param type tipo de modificación
* @return mensaje de error o nulo
*/
private String clearPaymentWithholdingFromHeader(final MLARPaymentHeader header)
{
// Solo se procesan cabeceras de pago
if (header.isReceipt())
return null;
if (header.is_ValueChanged(MLARPaymentHeader.COLUMNNAME_AD_Org_ID)
|| header.is_ValueChanged(MLARPaymentHeader.COLUMNNAME_C_BPartner_ID)
|| header.is_ValueChanged(MLARPaymentHeader.COLUMNNAME_C_DocType_ID)
)
{
BigDecimal curWithholdingAmt = header.getWithholdingAmt();
// Solo se borran las retenciones si existen retenciones previas
if (!curWithholdingAmt.equals(Env.ZERO))
{
header.BorrarCertificadosdeRetenciondelHeader();
header.BorrarPagosRetenciondelHeader();
MLARPaymentHeader.updateHeaderWithholding(header.getLAR_PaymentHeader_ID(),
header.get_TrxName());
}
}
return null;
} // clearPaymentWithholdingFromHeader
/**
* Elimina las retenciones de la cabecera de pago cuando se modifican
* los pagos asociados a la misma.
*
* @param payment pago asociado a la cabecera
* @param type evento
* @return mensaje de error o nulo
*/
private String clearPaymentWithholdingFromPayments(final MPayment payment, int type)
{
// Solo se procesan pagos y con importe mayor a cero
// Si el importe es 0 no debería borrar las retenciones
// Si el importe es negativo es un pago reversión
if (payment.isReceipt() || payment.getPayAmt().compareTo(Env.ZERO) <= 0)
return null;
// No se procesan los pagos "retención"
// (Puede ser cualquier tipo de retención), se mantuvo el nombre
// de la columna por consistencia, debería llamarse "EsRetenciónEfectuada"
if (payment.get_ValueAsBoolean("EsRetencionIIBB"))
{
if (type == TYPE_AFTER_DELETE)
{
// Recupera y borra el Certificado de Retención asociado al Pago Retención que se está borrando
final int c_Payment_ID = payment.getC_Payment_ID();
log.info("Borra el certificado de retenci\u00f3n asociado al pago: " + c_Payment_ID);
String sql = "DELETE FROM C_PaymentWithholding WHERE C_Payment_ID=?";
PreparedStatement pstmt = null;
try
{
pstmt = DB.prepareStatement(sql, payment.get_TrxName());
pstmt.setInt(1, c_Payment_ID);
pstmt.executeUpdate();
} catch (Exception e)
{
log.log(Level.SEVERE, sql, e);
return e.getMessage();
} finally
{
DB.close(pstmt);
pstmt = null;
}
}
return null;
}
if (type == TYPE_AFTER_NEW
|| (type == TYPE_AFTER_CHANGE && (payment
.is_ValueChanged(MPayment.COLUMNNAME_PayAmt)
// Unicamente se considera el cambio si el nuevo TT es Cheque de Terceros
|| (payment.is_ValueChanged(MPayment.COLUMNNAME_TenderType) && payment
.getTenderType().equals("Z")))))
{
int lar_PaymentHeader_ID = payment.get_ValueAsInt("LAR_PaymentHeader_ID");
if (lar_PaymentHeader_ID == 0)
return null;
final MLARPaymentHeader header = new MLARPaymentHeader(payment.getCtx(),
lar_PaymentHeader_ID, payment.get_TrxName());
BigDecimal curWithholdingAmt = header.getWithholdingAmt();
// Si existe retención calculada se borran los Certificados y Pagos Retención
if (!curWithholdingAmt.equals(Env.ZERO))
{
header.BorrarCertificadosdeRetenciondelHeader();
header.BorrarPagosRetenciondelHeader();
MLARPaymentHeader.updateHeaderWithholding(header.getLAR_PaymentHeader_ID(), header.get_TrxName());
}
}
return null;
} // clearPaymentWithholdingFromPayments
/**
* Elimina las retenciones de la cabecera de pago cuando se modifican
* las facturas (registros en MPaymentAllocate) asociados a la misma.
*
* @param payAlloc Registro de PaymentAllocate asociado a la cabecera
* @param type Evento
* @return Mensaje de error o nulo
*/
private String clearPaymentWithholdingFromPaymentAllocate(final MPaymentAllocate payAlloc, int type)
{
final int lar_PaymentHeader_ID = payAlloc.get_ValueAsInt("LAR_PaymentHeader_ID");
// No se está trabajando en una cabecera
if (lar_PaymentHeader_ID < 0)
return null;
final MLARPaymentHeader header = new MLARPaymentHeader(payAlloc.getCtx(),
lar_PaymentHeader_ID, payAlloc.get_TrxName());
// Solo se procesan cabeceras de pago
if (header.isReceipt())
return null;
// Existe retención calculada
final BigDecimal curWithholdingAmt = header.getWithholdingAmt();
if ((!curWithholdingAmt.equals(Env.ZERO)
&& type == TYPE_AFTER_NEW)
|| type == TYPE_AFTER_DELETE
// seleccionó otra factura
|| (type == TYPE_AFTER_CHANGE && (payAlloc
.is_ValueChanged(MPayment.COLUMNNAME_C_Invoice_ID))))
{
// Si existe retención calculada se borran los Certificados y Pagos Retención
if (!curWithholdingAmt.equals(Env.ZERO))
{
header.BorrarCertificadosdeRetenciondelHeader();
header.BorrarPagosRetenciondelHeader();
MLARPaymentHeader.updateHeaderWithholding(header.getLAR_PaymentHeader_ID(), header.get_TrxName());
}
}
return null;
} // clearPaymentWithholdingFromPaymentAllocate
/**
* Actualiza el importe total de la cabecera de pago
*
* @param LAR_PaymentHeader_ID ID de la cabecera
* @param type evento
* @return mensaje de error o nulo
*/
private String updatePaymentHeaderTotalAmt(final MPayment payment, int type)
{
// Solo se procesan pagos
if (payment.isReceipt())
return null;
// No se procesan los pagos "retención"
if (payment.get_ValueAsBoolean("EsRetencionIIBB"))
return null;
if (type == TYPE_AFTER_NEW || type == TYPE_AFTER_DELETE
|| (type == TYPE_AFTER_CHANGE
&& ( payment.is_ValueChanged(MPayment.COLUMNNAME_PayAmt)
|| payment.is_ValueChanged(MPayment.COLUMNNAME_C_Invoice_ID)
|| payment.is_ValueChanged(MPayment.COLUMNNAME_TenderType)
)
)
)
{
int lar_PaymentHeader_ID = payment.get_ValueAsInt("LAR_PaymentHeader_ID");
if (lar_PaymentHeader_ID == 0)
return null;
String sql = "UPDATE LAR_PaymentHeader"
+ " SET PayHeaderTotalAmt="
+ " (SELECT COALESCE(SUM(PayAmt),0)"
+ " FROM C_Payment p"
+ " WHERE p.IsActive='Y'"
+ " AND LAR_PaymentHeader.LAR_PaymentHeader_ID=p.LAR_PaymentHeader_ID)"
+ " WHERE LAR_PaymentHeader_ID=?";
int no = DB.executeUpdate(sql, lar_PaymentHeader_ID, payment.get_TrxName());
if (no != 1)
return "Error al actualizar el total de la cabecera de pago";
}
return null;
} // updatePaymentHeaderTotalAmt
/**
* Dependiendo de la configuración del tipo de documento, genera o exige
* tener generada la retención sobre la cabecera de pago.
*
* @param header cabecera de pago
* @return mensaje de error o nulo
*/
private String preparePaymentWithholding(final MLARPaymentHeader header)
{
// Solo se procesan las cabeceras de pago
if (!header.isReceipt() && header.getWithholdingAmt().equals(Env.ZERO))
{
final MDocType dt = new MDocType(header.getCtx(), header.getC_DocType_ID(), header.get_TrxName());
String genwh = dt.get_ValueAsString("GenerateWithholding");
if (genwh != null) {
if (genwh.equals("Y")) {
// Recupera la configuraciones
final MBPartner bp = new MBPartner(header.getCtx(), header.getC_BPartner_ID(), header.get_TrxName());
final WithholdingConfig[] configs = WithholdingConfig.getConfig(bp, dt.isSOTrx(),
header.get_TrxName(), null, header.getDateTrx());
boolean generar = false;
for (final WithholdingConfig wc : configs)
if (wc.isCalcFromPayment())
{
generar = true;
break;
}
// tipo de documento configurado para obligar a la generación de retención
if (generar)
return "Retenci\u00f3n no generada, por favor genere la retenci\u00f3n antes de completar el documento";
}
if (genwh.equals("A")) {
// tipo de documento configurado para generar la retención automáticamente
if (!header.recalcPaymentWithholding())
return "No se pudo generar la retenci\u00f3n sobre la cabecera de pago";
}
}
}
return null;
}
/**
* Change DocumentNo in order to avoid modified sequence for given document
*/
// TODO - Improve and add this behavior to ADempiere and make it configurable (ideal)
private String changeVoidDocumentNo(final PO po)
{
final Properties ctx = po.getCtx();
PO revPo = null;
MSequence seq = null;
// Corrije el nro de documento de la factura anulada y su reversa asociada
if (po.get_TableName().equals(MInvoice.Table_Name))
{
final MInvoice invoice = (MInvoice) po;
// Si no se tiene la referencia a la reversión, no se procesa
if (invoice.getReversal_ID() == 0)
return null;
revPo = new MInvoice(ctx, invoice.getReversal_ID(), invoice.get_TrxName());
final MInvoice revInvoice = (MInvoice) revPo;
log.info("Change DocumentNo of " + revInvoice);
// Intenta recuperar la secuencia "definitiva". Si no tiene, intenta
// recupera la secuencia "normal". Si no tiene, no sea hace nada debido
// a que le documento NO tiene secuencia configurada
int ad_Sequence_ID = invoice.getC_DocType().getDefiniteSequence_ID();
if (ad_Sequence_ID == 0)
ad_Sequence_ID = invoice.getC_DocType().getDocNoSequence_ID();
if (ad_Sequence_ID != 0)
seq = new MSequence(ctx, ad_Sequence_ID, invoice.get_TrxName());
// Redefine los nros de documento y las descripciones de las facturas
String revDocumentNo = "Rev-" + invoice.getDocumentNo() + "-" + invoice.getC_Invoice_ID();
String voidDocumentNo = "Anu-" + invoice.getDocumentNo() + "-" + invoice.getC_Invoice_ID();
revInvoice.setDocumentNo(revDocumentNo);
revInvoice.setDescription("(" + voidDocumentNo + "<-)");
invoice.setDocumentNo(voidDocumentNo);
invoice.setDescription("(" + revDocumentNo + "<-)");
// Si la secuencia es automática, retrocede la numeración
if (seq != null && seq.isAutoSequence())
seq.setCurrentNext(seq.getCurrentNext() - 1);
}
// Corrije el nro de documento del remito anulado y su reverso asociado
if (po.get_TableName().equals(MInOut.Table_Name))
{
final MInOut shipment = (MInOut) po;
// Si no se tiene la referencia a la reversión, no se procesa
if (shipment.getReversal_ID() == 0)
return null;
revPo = new MInOut(ctx, shipment.getReversal_ID(), shipment.get_TrxName());
final MInOut revShipment = (MInOut) revPo;
log.info("Change DocumentNo of " + revShipment);
// Intenta recuperar la secuencia "definitiva". En caso que sea nula,
// recupera la secuencia "normal"
int AD_Sequence_ID = shipment.getC_DocType().getDefiniteSequence_ID();
if (AD_Sequence_ID == 0)
AD_Sequence_ID = shipment.getC_DocType().getDocNoSequence_ID();
if (AD_Sequence_ID != 0)
seq = new MSequence(ctx, AD_Sequence_ID, shipment.get_TrxName());
String revDocumentNo = "Rev-" + shipment.getDocumentNo() + "-" + shipment.getM_InOut_ID();
String voidDocumentNo = "Anu-" + shipment.getDocumentNo() + "-" + shipment.getM_InOut_ID();
revShipment.setDocumentNo(revDocumentNo);
revShipment.setDescription("(" + voidDocumentNo + "<-)");
shipment.setDocumentNo(voidDocumentNo);
shipment.setDescription("(" + revDocumentNo + "<-)");
// Si encontró una secuencia, y la misma es automática, retrocede la numeración
if (seq != null && seq.isAutoSequence())
seq.setCurrentNext(seq.getCurrentNext() - 1);
}
// Guarda los cambios realizados sobre los nros de documento
// y las reversiones de las secuencias.
// (siempre y cuando hayan cambiado y no sean nulos)
if (revPo != null && !revPo.save())
return "Error al guardar el documento inverso";
if (po.is_Changed() && !po.save())
return "Error al guardar el documento anulado";
if (seq != null && seq.is_Changed() && !seq.save())
return "Error al guardar la secuencia";
return null;
}
/**
* Anula la orden de remito relacionada con el remito dado
* (siempre y cuando el mismo tenga una orden de este tipo como origen)
*
* @param shipment remito a procesar
* @return mensaje de error o null
*/
private String voidWarehouseOrder(final MInOut shipment)
{
final String trx = shipment.get_TrxName();
final MOrder order = new MOrder(Env.getCtx(), shipment.getC_Order_ID(), trx);
final MDocType dt = new MDocType(Env.getCtx(), order.getC_DocType_ID(), trx);
// Controla si el tipo de la orden es "Orden de Remito"
// ("Orden de Remito" <=> "Warehouse Order")
if (dt.getDocSubTypeSO().equals(MDocType.DOCSUBTYPESO_WarehouseOrder))
{
if (order.processIt(MOrder.ACTION_Void))
order.saveEx(trx);
else
return "Falló la anulaci\u00f3n de la Orden de Remito";
}
return null;
} // voidWarehouseOrder
/**
* Process acounting for withholding on sales payment
*/
private String accountingForWithholdingOnPayment(final MAllocationHdr ah)
{
final Doc doc = ah.getDoc();
final List<Fact> facts = doc.getFacts();
// One fact per acctschema
for (int i = 0; i < facts.size(); i++)
{
Fact fact = facts.get(i);
MAcctSchema as = fact.getAcctSchema();
MAllocationLine[] allocLines = ah.getLines(false);
for (int j = 0; j < allocLines.length; j++)
{
MAllocationLine al = allocLines[j];
doc.setC_BPartner_ID(al.getC_BPartner_ID()); // TODO is this line necesary?
int c_Payment_ID = al.getC_Payment_ID();
if (c_Payment_ID <= 0)
continue;
MPayment payment = new MPayment(ah.getCtx(), c_Payment_ID, ah.get_TrxName());
if (payment == null || payment.getC_Payment_ID() == 0)
continue;
// Se determina si se procesan cobros o pagos
if (payment.isReceipt())
{
////////////////// PROCESA COBROS //////////////////
// Determine if is an withholding or not
int c_TaxWithholding_ID = payment.get_ValueAsInt("C_TaxWithholding_ID");
if (c_TaxWithholding_ID <= 0)
continue;
if (payment.getWriteOffAmt().compareTo(Env.ZERO) <= 0)
continue;
// Iterates over factlines, searching one with writeoff account
// in order to change it to the retrieved from processed payment
final FactLine[] factlines = fact.getLines();
for (int ifl = 0; ifl < factlines.length; ifl++)
{
final FactLine fl = factlines[ifl];
// if factline account is WriteOff, change it
if (fl.getAccount().equals(doc.getAccount(Doc.ACCTTYPE_WriteOff, as)))
{
// Creates factline with proper account (using c_taxwithholding_id from processed payment)
final BigDecimal withholdingAmt = payment.getWriteOffAmt();
final MTax tw = new MTax(ah.getCtx(), c_TaxWithholding_ID, ah.get_TrxName());
final DocTax taxLine = new DocTax(c_TaxWithholding_ID, tw.getName(), tw.getRate(), Env.ZERO,
withholdingAmt, tw.isSalesTax());
final FactLine newFactLine = fact.createLine(null, taxLine.getAccount(DocTax.ACCTTYPE_TaxCredit, as),
as.getC_Currency_ID(), withholdingAmt, null);
if (newFactLine != null)
newFactLine.setC_Tax_ID(c_TaxWithholding_ID);
// Removes factline with writeoff account from fact
log.info(String.format("Replace factline: %s -> %s", fl, newFactLine));
fact.remove(fl);
}
}
}
}
}
return null;
}
/**
* Si la orden POS fue pagada en CtaCte, se realiza el cambio del tipo de
* documento de la misma:
* <ul>
* <li>Contado -> PosOrderModel.C_DocTypeTarget_ID sin cambios
* <li>CtaCte -> PosOrderModel.C_DocTypeTarget_ID = MPOS.C_DocTypeOnCredit_ID
* </ul>
*
* @param order Orden a procesar
* @return null si el proceso fue correcto; caso contrario el mensaje de error
*/
private String changeShipmentDocType(final MOrder order)
{
// Solo se procesan las ordenes POS
if (order instanceof PosOrderModel)
{
final MPOS pos = new MPOS(order.getCtx(), order.getC_POS_ID(), order.get_TrxName());
if (!pos.get_ValueAsBoolean("IsShipment") &&
order.get_ValueAsBoolean("PrintShipment") &&
order.getC_PaymentTerm_ID() == PosOrderModel.PAYMENTTERMS_Account)
{
// Se cambia el tipo de orden para la operación en ctacte
order.setC_DocTypeTarget_ID(pos.get_ValueAsInt("C_DocTypeOnCredit_ID"));
if (!order.save())
return "No se pudo realizar el cambio de tipo de documento de la orden pdv";
}
}
return null;
} // changeShipmentDocType
/**
* Cambia la numeración <i>solo</i> a los remitos que no estan
* configurados para ser impresos en el controlador fiscal
*
* @param shipment remito
* @return null si el cambio de nro fue correcto; caso contrario,
* mensaje de error
*/
private String changeShipmentDocumentNo(final MInOut shipment)
{
if (!LAR_Utils.isFiscalDocType(shipment.getC_DocType_ID()))
{
final MOrder order = new MOrder(shipment.getCtx(), shipment.getC_Order_ID(), shipment.get_TrxName());
shipment.setDocumentNo(order.getDocumentNo());
}
return null;
} // changeShipmentDocumentNo
/**
* german wagner
* @param payID
* @param value valor en que se seteara el campo isreconcilied
* @param trxName
* @return
*/
private String setReconciled(Integer payID, String value, String trxName)
{
String sql="UPDATE C_Payment SET isReconciled='"+value+"' WHERE C_Payment_ID="+payID;
int result = DB.executeUpdate(sql, trxName);
if(result<0)
return "ERROR al setear los pagos como reconciliados";
return null;
}
//Marcos Zúñiga -begin
/**
* Marcos Zúñiga
* @param payID
* @param Value to be set on IsOnDrawer
* @param trxName
* @return
*/
private String setIsOnDrawer(Integer payID, String value, String trxName)
{
String sql="UPDATE C_Payment SET IsOnDrawer='"+value+"' WHERE C_Payment_ID="+payID;
int result = DB.executeUpdate(sql, trxName);
if(result<0)
return "ERROR al modificar la condición del Cheque (Cartera)";
return null;
}
//Marcos Zúñiga -end
// @fchiappano
/**
* Obtener Location del BPartner.
*
* @param bp
* @return MLocation
*/
private MLocation obtenerLocacion(final int c_BPartner_ID, final Properties ctx, final String trxName)
{
final String sql = "SELECT bl.C_Location_ID"
+ " FROM C_BPartner_Location bl "
+ " WHERE bl.C_BPartner_Location_ID = (SELECT MAX(bpl.C_BPartner_Location_ID) "
+ " FROM C_BPartner_Location bpl"
+ " WHERE bpl.C_BPartner_ID = ?"
+ " AND bpl.IsActive='Y' AND bpl.IsActive='Y')";
MLocation location = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement(sql, null);
pstmt.setInt(1, c_BPartner_ID);
rs = pstmt.executeQuery();
while (rs.next())
{
location = new MLocation(ctx, rs.getInt("C_Location_ID"), trxName);
}
}
catch (SQLException eSql)
{
log.log(Level.SEVERE, sql, eSql);
}
finally
{
DB.close(rs, pstmt);
rs = null;
pstmt = null;
}
return location;
} // obtenerLocacion
} // LAR_Validator