/****************************************************************************** * Product: Adempiere ERP & CRM Smart Business Solution * * Copyright (C) 1999-2007 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.io.File; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.logging.Level; import javax.swing.JDialog; import org.compiere.Adempiere; import org.compiere.apps.ADialog; import org.compiere.model.MAllocationHdr; import org.compiere.model.MAllocationLine; import org.compiere.model.MBPartner; import org.compiere.model.MDocType; import org.compiere.model.MInvoice; import org.compiere.model.MPayment; import org.compiere.model.MPaymentAllocate; import org.compiere.model.ModelValidationEngine; import org.compiere.model.ModelValidator; import org.compiere.model.Query; import org.compiere.process.DocAction; import org.compiere.process.DocOptions; import org.compiere.process.DocumentEngine; import org.compiere.util.DB; import org.compiere.util.Env; import org.globalqss.model.X_LCO_WithholdingType; /** * Payment Header * * @author Wagner Germán * * @contributor Marcos Zuñiga - http://www.comit.com.ar */ public class MLARPaymentHeader extends X_LAR_PaymentHeader implements DocAction, DocOptions { /** * */ private static final long serialVersionUID = -2698873064570244615L; /** Process Message */ private String m_processMsg = null; /** Just Prepared Flag */ private boolean m_justPrepared = false; /** * Recupera la cabecera de pagos relacionada con el id de la factura dada * * @deprecated Luego de la funcionalidad Varias Facturas vs varios Cobros/Pagos en las * cabeceras ya no se permite la carga de una factura en el Header, sino en la * pestaña facturas que se refleja en la tabla C_PaymentAllocate. * * @param ctx * contexto * @param C_Invoice_ID * id de factura * @param trxName * nombre de la transacción * @return Cabecera de pago asociada a la factura dada */ public static MLARPaymentHeader getFromInvoice(final Properties ctx, int C_Invoice_ID, final String trxName) { final String whereClause = COLUMNNAME_C_Invoice_ID + "=?"; final MLARPaymentHeader header = new Query(ctx, Table_Name, whereClause, trxName) .setParameters(C_Invoice_ID) .setClient_ID() .setOnlyActiveRecords(true) .firstOnly(); return header; } /** * Actualiza la retención y el total de pago en la cabecera via sql. * (Esta forma evita el disparo de la validación) * * @return verdadero si se actualiza la cabecera */ public static boolean updateHeaderWithholding(int LAR_PaymentHeader_ID, String trxName) { String sql = "UPDATE LAR_PaymentHeader" + " SET WithholdingAmt =" + " (SELECT COALESCE(SUM(TaxAmt),0)" + " FROM LAR_PaymentWithholding iw" + " WHERE iw.IsActive='Y'" + " AND LAR_PaymentHeader.LAR_PaymentHeader_ID=iw.LAR_PaymentHeader_ID)" + " , 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, trxName); return no == 1; } // updateHeaderWithholding /** * Establece un valor al campo retención directamente (via sql) * * @param header cabecera de pago * @param amt importe de retención * @return verdadero si se actualiza la cabecera */ public static boolean setWithholdingAmtDirectly(final MLARPaymentHeader header, final BigDecimal amt) { DB.executeUpdate("UPDATE LAR_PaymentHeader SET WithholdingAmt=? WHERE LAR_PaymentHeader_ID=?", new Object[] {amt, header.getLAR_PaymentHeader_ID()}, true, header.get_TrxName()); return true; } /** * Establece un valor al campo PayAmt un Pago directamente (via sql) * * @param pago Pago * @param imp Importe del Pago * @return verdadero si se actualiza el Pagocabecera */ public static boolean setPayAmtDirectly(final MPayment pago, final BigDecimal imp) { DB.executeUpdate("UPDATE C_Payment SET PayAmt=? WHERE C_Payment_ID=?", new Object[] {imp, pago.getC_Payment_ID()}, true, pago.get_TrxName()); return true; } //setPayAmtDirectly /** * Realiza el cálculo de la retención sobre la cabecera de pago dada. * Esto lo lleva a cabo creando por cada confiuración aplicable * un certificado de retención y un pago de tipo "retención". * * @return verdadero si se generó la retención correctamente */ public boolean recalcPaymentWithholding() { this.load(get_TrxName()); final MDocType dt = new MDocType(getCtx(), getC_DocType_ID(), get_TrxName()); String genwh = dt.get_ValueAsString("GenerateWithholding"); if (genwh == null || genwh.equals("N")) return true; // Se Borran los certificados y Pagos Retención del Header BorrarCertificadosdeRetenciondelHeader(); BorrarPagosRetenciondelHeader(); updateHeaderWithholding(getLAR_PaymentHeader_ID(), get_TrxName()); this.load(get_TrxName()); BigDecimal impTotalHeader = getPayHeaderTotalAmt(); // Recupera la configuración y calcula final MBPartner bp = new MBPartner(getCtx(), getC_BPartner_ID(), get_TrxName()); final WithholdingConfig[] configs = WithholdingConfig.getConfig(bp, dt.isSOTrx(), get_TrxName(), null, getDateTrx()); // Se recorren las configuraciones recuperadas // Se crean los pagos retención y sus respectivos certificados for (final WithholdingConfig wc : configs) { log.config("Withholding conf >> " + wc); BigDecimal impRetencion = Env.ZERO; // Se recupera el tipo de documento para el Pago Retención // a partir del tipo de retencion final int c_DocType_ID = wc.getC_DocType_ID(); int cargoRetencion; if (c_DocType_ID > 0) { final MDocType doc = new MDocType(getCtx(), c_DocType_ID, get_TrxName()); // Se recupera y valida el ID del cargo para retención desde el documento cargoRetencion = (Integer) doc.get_Value("LAR_Withholding_Charge_ID"); if (cargoRetencion < 0) { JDialog dialog = new JDialog(); dialog.setIconImage(Adempiere.getImage16()); ADialog.warn(1, dialog, "Error al crear la retenci\u00f3n (No existe cargo retenci\u00f3n configurado en el documento)"); return false; } } else { JDialog dialog = new JDialog(); dialog.setIconImage(Adempiere.getImage16()); ADialog.warn( 1, dialog, "Error al crear la retenci\u00f3n (No existe tipo de documento configurado para el Pago Retenci\u00f3n)"); return false; } if (wc.isCalcFromPayment()) { // Se calcula el importe a retener según el tipo de retención final MPaymentAllocate[] facturas = getInvoices(get_TrxName()); BigDecimal aliquot = wc.getAliquot(); BigDecimal impFijo = Env.ZERO; BigDecimal impNoSujeto = wc.getamountRefunded(); BigDecimal impRetMin = wc.getPaymentThresholdMin(); BigDecimal impExencion = Env.ZERO; BigDecimal porcExencion = Env.ZERO; // Es retención de Ganancias if (wc.usaTipoGananciasBP()) { final String tipoGanancias = (String) bp.get_Value("LAR_TipoGanancias"); // Si no existen facturas, es un pago a cuenta // Se toma el importe del pago sin IVA if (facturas.length <= 0) impTotalHeader = impTotalHeader.divide(BigDecimal.valueOf(1.21), 2, RoundingMode.HALF_EVEN); X_LAR_Concepto_Ret_Ganancias cg = null; final int concepto_id = bp.get_ValueAsInt("LAR_Concepto_Ret_Ganancias_ID"); // Si no tiene concepto configurado o es "Sin Especificar" if (concepto_id == 0 || concepto_id == 1000024) continue; else { try { // Recuperar todos la información asociada al concepto String sqlcg = "SELECT * " + " FROM LAR_Concepto_Ret_Ganancias " + " WHERE LAR_Concepto_Ret_Ganancias_ID = ?"; PreparedStatement pstmtcg = DB.prepareStatement(sqlcg, get_TrxName()); pstmtcg.setInt(1, concepto_id); ResultSet rscg = pstmtcg.executeQuery(); if (rscg.next()) { cg = new X_LAR_Concepto_Ret_Ganancias(Env.getCtx(), rscg, get_TrxName()); } else { log.warning("No existe configuraci\u00f3n para el concepto LAR_Concepto_Ret_Ganancias_ID = " + concepto_id); rscg.close(); pstmtcg.close(); continue; } rscg.close(); pstmtcg.close(); } catch (SQLException e) { log.log(Level.SEVERE, "", e); return false; } } // Recuperar toda la información asociada al concepto // Es cálculo por escala if (cg.isCalculo_Por_Escala() && tipoGanancias.equals("I")) { X_LAR_Escala_Ret_Ganancias eg = null; final List<X_LAR_Escala_Ret_Ganancias> escala = new ArrayList<X_LAR_Escala_Ret_Ganancias>(); // Recuperar la información de la escala try { // Recuperar la información de la escala String sqleg = "SELECT * " + " FROM LAR_Escala_Ret_Ganancias "; PreparedStatement pstmteg = DB.prepareStatement(sqleg, get_TrxName()); ResultSet rseg = pstmteg.executeQuery(); while (rseg.next()) { eg = new X_LAR_Escala_Ret_Ganancias(Env.getCtx(), rseg, get_TrxName()); if (!escala.add(eg)) { log.severe("Error al agregar configuración de escala a la lista"); continue; } } rseg.close(); pstmteg.close(); } catch (SQLException e) { log.log(Level.SEVERE, "", e); return false; } // Recuperar la información de la escala // Recorrer la escala para encontrar el rango del pago for (final X_LAR_Escala_Ret_Ganancias esc : escala) { if (impTotalHeader.compareTo(esc.getImporte_Desde()) >= 0 && impTotalHeader.compareTo(esc.getImporte_Hasta()) <= 0) { // obtener importe fijo, importe no sujeto y alicuota aliquot = esc.getAlicuota(); // Se corrige la alícuota e impuesto en la config // para que el certificado de retención quede correcto wc.setAliquot(aliquot); wc.setC_Tax_ID(0); impFijo = esc.getImporte_Fijo(); impNoSujeto = esc.getImporte_No_Sujeto(); break; } } }// Es cálculo por escala else // Es cáclulo directo { // Obtener importe importe no sujeto y alicuota según si el SdN es // Inscripto o No en el Impuesto a las Ganancias aliquot = tipoGanancias.equals("I") ? cg.getAlicuota_Inscripto() : cg .getAlicuota_No_Inscripto(); // Se corrige la alícuota e impuesto en la config // para que el certificado de retención quede correcto wc.setAliquot(aliquot); wc.setC_Tax_ID(0); impNoSujeto = tipoGanancias.equals("I") ? cg .getImporte_No_Sujeto_Inscripto() : cg .getImporte_No_Sujeto_No_Insc(); // Exención de Ganancias if (bp.get_ValueAsBoolean("LAR_Exento_Ret_Ganancias")) { Date fechaVenc = (Date) bp.get_Value("LAR_Vencimiento_Cert_Ganancias"); if (!fechaVenc.before(getDateTrx())) { impExencion = (BigDecimal) bp .get_Value("LAR_Importe_Exencion_Ganancias"); porcExencion = (BigDecimal) bp.get_Value("LAR_Exencion_Ganancias"); } } } } // Es retención de Ganancias // Considerar las Exenciones final X_LCO_WithholdingType wt = new X_LCO_WithholdingType(Env.getCtx(), wc.getWithholdingType_ID(), get_TrxName()); // Exención de IIBB if (wc.isUseBPISIC() && bp.get_ValueAsBoolean("LAR_Exento_Ret_IIBB")) { Date fechaVenc = (Date) bp.get_Value("LAR_Vencimiento_Cert_IIBB"); Date fechaInicio = (Date) bp.get_Value("LAR_Inicio_Cert_IIBB"); if (!fechaInicio.after(getDateTrx()) && !fechaVenc.before(getDateTrx())) { impExencion = (BigDecimal) bp.get_Value("LAR_Importe_Exencion_IIBB"); porcExencion = (BigDecimal) bp.get_Value("LAR_Exencion_IIBB"); } } // Exención de IVA if (wt.getName().contains("IVA") && bp.get_ValueAsBoolean("LAR_Exento_Ret_IVA")) { Date fechaVenc = (Date) bp.get_Value("LAR_Vencimiento_Cert_IVA"); if (!fechaVenc.before(getDateTrx())) { impExencion = (BigDecimal) bp.get_Value("LAR_Importe_Exencion_IVA"); porcExencion = (BigDecimal) bp.get_Value("LAR_Exencion_IVA"); } } // Exención de SUSS if (wt.getName().contains("SUSS") && bp.get_ValueAsBoolean("LAR_Exento_Retenciones_SUSS")) { Date fechaVenc = (Date) bp.get_Value("LAR_Vencimiento_Cert_SUSS"); Date fechaInicio = (Date) bp.get_Value("LAR_Inicio_Cert_SUSS"); if (!fechaInicio.after(getDateTrx()) && !fechaVenc.before(getDateTrx())) { impExencion = (BigDecimal) bp.get_Value("LAR_Importe_Exencion_SUSS"); porcExencion = (BigDecimal) bp.get_Value("LAR_Exencion_SUSS"); } } // Se chequea que el importe a retener sea mayor al mínimo BigDecimal baseRet = impTotalHeader.subtract(impNoSujeto); impRetencion = baseRet.multiply(aliquot).divide(new BigDecimal(100)) .setScale(2, BigDecimal.ROUND_HALF_EVEN); impRetencion = impRetencion.add(impFijo); // Exenciones % e importe fijo BigDecimal impExentoDesc = impRetencion.multiply(porcExencion) .divide(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_EVEN); impRetencion = impRetencion.subtract(impExentoDesc).subtract(impExencion); if (impRetencion.compareTo(impRetMin) < 0 || impRetencion.compareTo(Env.ZERO) == 0) continue; // Validar que si existen facturas, quede importe disponible a pagar if (facturas.length > 0) { BigDecimal sumaRemanente = Env.ZERO; for (final MPaymentAllocate mp : facturas) sumaRemanente = sumaRemanente.add(mp.getAmount()); if (sumaRemanente.compareTo(impRetencion) < 0) { JDialog dialog = new JDialog(); dialog.setIconImage(Adempiere.getImage16()); ADialog.warn( 1, dialog, "No existe suficiente importe pendiente de pago (Revisar las facturas cargadas en la Orden de Pago)."); return false; } } // @fchiappano verifico que existan pagos en los que se pueda // descontar el importe de la retención. MPayment pago = null; boolean compensar = false; for (MPayment payment : getPayments(get_TrxName())) { if (!payment.getTenderType().equals("Z") && !payment.get_ValueAsBoolean("EsRetencionIIBB") && payment.getPayAmt().compareTo(impRetencion) >= 0) { pago = payment; compensar = true; break; } } // Existe un pago que permite compensar el importe de la retención if (compensar) { // Se crea el Pago Retención final MPayment pagoRetencion = creaPagoRetencion(impRetencion, cargoRetencion, c_DocType_ID, pago, compensar); if (pagoRetencion == null) { JDialog dialog = new JDialog(); dialog.setIconImage(Adempiere.getImage16()); ADialog.warn(1, dialog, "Error al generar el Pago Retenci\u00f3n"); return false; } log.config("Pago Retenci\u00f3n: " + pagoRetencion.getC_Payment_ID()); // Se crea el Certificado de Retención final MLARPaymentWithholding certificado = creaCertificadodeRetencion( impRetencion, baseRet, wc, pagoRetencion.getC_Payment_ID()); if (certificado == null) { JDialog dialog = new JDialog(); dialog.setIconImage(Adempiere.getImage16()); ADialog.warn(1, dialog, "Error al generar el Certificado de Retenci\u00f3n"); return false; } log.config("Certificado Retenci\u00f3n: " + certificado.getLAR_PaymentWithholding_ID()); } else { // Se crea el Pago Retención sin compensar el importe final MPayment pagoRetencion = creaPagoRetencion(impRetencion, cargoRetencion, c_DocType_ID, pago, false); if (pagoRetencion == null) { JDialog dialog = new JDialog(); dialog.setIconImage(Adempiere.getImage16()); ADialog.warn(1, dialog, "Error al generar el Pago Retenci\u00f3n"); return false; } log.config("Pago Retenci\u00f3n: " + pagoRetencion.getC_Payment_ID()); // Se crea el Certificado de Retención final MLARPaymentWithholding certificado = creaCertificadodeRetencion( impRetencion, baseRet, wc, pagoRetencion.getC_Payment_ID()); if (certificado == null) { JDialog dialog = new JDialog(); dialog.setIconImage(Adempiere.getImage16()); ADialog.warn(1, dialog, "Error al generar el Certificado de Retenci\u00f3n"); return false; } log.config("Certificado Retenci\u00f3n: " + certificado.getLAR_PaymentWithholding_ID()); } } } // TODO: Refrescar el tab, ya que si existían pagos retención, estos fueron eliminados y se crearon nuevos // pero en la pestaña se visualizan los eliminados y es necesario refrescar manualmente para ver los nuevos. return true; } // recalcPaymentWithholding /** * Constructor tradicional para una cabecera de cobros/pagos * * @param ctx * contexto * @param LAR_PaymentHeader_ID * ID de la cabecera a crear o 0 si es nueva * @param trxName * nombre de la transacción */ public MLARPaymentHeader (Properties ctx, int LAR_PaymentHeader_ID, String trxName) { super (ctx, LAR_PaymentHeader_ID, trxName); } // MLARPaymentHeader /** * Constructor de carga para las cabecera de cobros/pagos * * @param ctx * contexto * @param rs * result conjunto de resultado (jdbc) * @param trxName * nombre de la transaction */ public MLARPaymentHeader (Properties ctx, ResultSet rs, String trxName) { super(ctx, rs, trxName); } // MLARPaymentHeader @Override protected boolean beforeSave(boolean newRecord) { if(!newRecord) { MPayment[] pays = getPayments(get_TrxName()); for (int i = 0; i < pays.length; i++) { if (!pays[i].get_ValueAsBoolean("EsRetencionIIBB")) pays[i].setC_DocType_ID(getC_DocType_ID()); pays[i].setDocumentNo(getDocumentNo()); pays[i].setDateTrx(getDateTrx()); pays[i].setDateAcct(getDateTrx()); pays[i].setC_BPartner_ID(getC_BPartner_ID()); pays[i].setIsReceipt(isReceipt()); pays[i].setIsActive(isActive()); if(!pays[i].save(get_TrxName())) { try { DB.rollback(false, get_TrxName()); } catch (SQLException e) { log.log(Level.SEVERE, e.getLocalizedMessage(), e); } return false; } } } else { // TODO: Chequear que no estén vencidos los certificados de Exención // caso contrario, despleagar un mensaje con los que están vencidos y la fecha. } return true; } // beforeSave /** * @param success */ @Override protected boolean afterDelete(boolean success) { if(success) { // Se eliminan los pagos asociados a la cabecera final MPayment[] pays = getPayments(get_TrxName()); for (int i = 0; i < pays.length; i++) { if (!pays[i].delete(false, get_TrxName())) { String msg = "No se pudo eliminar alguno de los pagos cargados en el documento que" + "se está eliminando. Se cancelará la operación"; log.severe(msg); ADialog.error(0, null, msg); return false; } } // Se eliminan los registros de facturas asociados a la cabecera final MPaymentAllocate[] facturas = getInvoices(get_TrxName()); for (int i = 0; i < facturas.length; i++) { if (!facturas[i].delete(false, get_TrxName())) { String msg = "No se pudo eliminar alguno de las facturas cargadas en el documento que" + "se está eliminando. Se cancelará la operación"; log.severe(msg); ADialog.error(0, null, msg); return false; } } } return success; } // afterDelete /** * Devuelve un array con todos los payments vinculados a la cabecera * @param trxName * @return MPayment[] array con los pagos vinculados al documento * @throws SQLException */ public MPayment[] getPayments(String trxName) { //TODO - Analize genereate a cache for this payments List<MPayment> pays = new ArrayList<MPayment>(); // @mzuniga - Se Agrega la condición de ordenamiento, recupera primero las retenciones (sufridas y efectuadas). String sql = "SELECT * FROM C_Payment WHERE LAR_PaymentHeader_ID = ? ORDER BY EsRetencionSufrida DESC, EsRetencionIIBB DESC, C_Payment_ID ASC"; PreparedStatement pstmt; pstmt = DB.prepareStatement(sql, trxName); ResultSet rs=null; try { pstmt.setInt(1, getLAR_PaymentHeader_ID()); rs = pstmt.executeQuery(); while(rs.next()) pays.add(new MPayment(getCtx(),rs,trxName)); return pays.toArray(new MPayment[pays.size()]); } catch (SQLException e) { log.log(Level.SEVERE, sql, e); return new MPayment[0]; } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } } // getPayments /** * Process document * @param processAction document action * @return true if performed */ public boolean processIt (String processAction) { m_processMsg = null; DocumentEngine engine = new DocumentEngine (this, getDocStatus()); return engine.processIt (processAction, getDocAction()); } // processIt @Override public boolean unlockIt() { // TODO Auto-generated method stub return false; } @Override public boolean invalidateIt() { // TODO Auto-generated method stub return false; } @Override public String prepareIt() { log.info(toString()); // Dispara la validación del documento m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_BEFORE_PREPARE); if (m_processMsg != null) return DocAction.STATUS_Invalid; // Validar que la cabecera tiene al menos un cobro/pago MPayment[] pays = getPayments(get_TrxName()); if (pays.length == 0) { ADialog.error(0, null, "La cabecera no tiene cobros/pagos"); return DocAction.STATUS_Invalid; } // Recibos: Validar que la suma de los Pagos Retención sea >= // que la suma del importe abierto (impago) de las facturas // @begin if (this.isReceipt()) { BigDecimal sumPagosRet = Env.ZERO; for (int p = 0; p < pays.length; p++) if (pays[p].get_ValueAsBoolean("EsRetencionSufrida")) sumPagosRet = sumPagosRet.add(pays[p].getWriteOffAmt()); // Si existen Cobros Retención if (!(sumPagosRet.compareTo(Env.ZERO) == 0)) { MPaymentAllocate[] invoices = getInvoices(get_TrxName()); BigDecimal sumaFacturas = Env.ZERO; for (int i = 0; i < invoices.length; i++) sumaFacturas = sumaFacturas.add(invoices[i].getAmount()); if (sumaFacturas.compareTo(sumPagosRet) == -1) { ADialog.error(0, null, "El importe de las Retenciones es mayor que el de las Facturas"); return DocAction.STATUS_Invalid; } } } // @end // Dispara la validación del documento m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_AFTER_PREPARE); if (m_processMsg != null) return DocAction.STATUS_Invalid; m_justPrepared = true; if (!DOCACTION_Complete.equals(getDocAction())) setDocAction(DOCACTION_Complete); return DocAction.STATUS_InProgress; } @Override public boolean approveIt() { // TODO Auto-generated method stub return false; } @Override public boolean rejectIt() { // TODO Auto-generated method stub return false; } @Override public String completeIt() { log.info(toString()); // Re-Check if (!m_justPrepared) { String status = prepareIt(); if (!DocAction.STATUS_InProgress.equals(status)) return status; } // Dispara la validación del documento m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_BEFORE_COMPLETE); if (m_processMsg != null) return DocAction.STATUS_Invalid; MPayment[] pays = getPayments(get_TrxName()); int p = 0; for (; p < pays.length; p++) { pays[p].setDateAcct(getDateTrx()); pays[p].setDateTrx(getCreated()); pays[p].setTrxType(MPayment.TRXTYPE_CreditPayment); pays[p].processIt(ACTION_Complete); pays[p].save(get_TrxName()); if (!DOCSTATUS_Completed.equals(pays[p].getDocStatus())) { m_processMsg = "@C_Payment_ID@: " + pays[p].getProcessMsg(); return DocAction.STATUS_Invalid; } } // Asigna los cobros/pagos a las facturas MPaymentAllocate[] invoices = getInvoices(get_TrxName()); if (invoices.length != 0) { p = 0; // Asignaciones for (int i = 0; (i < invoices.length && p < pays.length);) { MAllocationHdr alloc = new MAllocationHdr(getCtx(), false, getDateTrx(), getC_Currency_ID(), "Asignación Pagos a Facturas - Cabecera: " + getDocumentNo(), get_TrxName()); alloc.setAD_Org_ID(getAD_Org_ID()); if (!alloc.save()) { log.severe("La Cabecera de Asignacion no pudo crearse"); return DocAction.STATUS_Invalid; } MPaymentAllocate pa = invoices[i]; MInvoice invoice = new MInvoice(Env.getCtx(), pa.getC_Invoice_ID(), get_TrxName()); final BigDecimal impPago = pays[p].getPayAmt().add(pays[p].getWriteOffAmt()).subtract(pays[p].getAllocatedAmt()); final BigDecimal importeFactura = invoice.getOpenAmt().subtract(pa.getDiscountAmt()); int comp = impPago.compareTo(importeFactura); MAllocationLine aLine = null; BigDecimal alineOUAmt = Env.ZERO; BigDecimal alineAmt; // Evita Sobrepagos if (comp <= 0) { alineAmt = impPago; alineOUAmt = importeFactura.subtract(alineAmt); } else { alineAmt = importeFactura; alineOUAmt = alineAmt.subtract(impPago); } if (isReceipt()) aLine = new MAllocationLine(alloc, alineAmt, Env.ZERO, pa.getWriteOffAmt(), alineOUAmt); else aLine = new MAllocationLine(alloc, alineAmt.negate(), Env.ZERO , pa.getWriteOffAmt().negate(), alineOUAmt.negate()); aLine.setDocInfo(pa.getC_BPartner_ID(), 0, pa.getC_Invoice_ID()); aLine.setPaymentInfo(pays[p].getC_Payment_ID(), 0); if (!aLine.save(get_TrxName())) log.warning("Asignación: No se pudo guradar la línea"); else { pa.setC_AllocationLine_ID(aLine.getC_AllocationLine_ID()); pa.saveEx(); } if (comp >= 0) { i = i + 1; if (comp == 0) p = p + 1; } else p = p + 1; // Cabecera de Asignación: Comienzo de WF alloc.processIt(DocAction.ACTION_Complete); alloc.save(get_TrxName()); m_processMsg = "@C_AllocationHdr_ID@: " + alloc.getDocumentNo(); log.fine(m_processMsg); } } // setC_BankAccount_ID(C_BankAccount_ID); // Dispara la validación del documento m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_AFTER_COMPLETE); if (m_processMsg != null) return DocAction.STATUS_Invalid; // @mzuniga - Marca los cobros/pagos como asignados si corresponde for (p = 0; p < pays.length; p++) { pays[p].testAllocation(); pays[p].saveEx(); } setDocStatus(ACTION_Complete); setDocAction(DOCACTION_Close); setProcessed(true); // Marca los Certificados de Retención como Procesados if (!isReceipt()) { final MLARPaymentWithholding[] certificados = MLARPaymentWithholding.get(this); if (certificados.length > 0) for (final MLARPaymentWithholding c : certificados) c.setProcessed(true); } return DocAction.STATUS_Completed; } // completeIt @Override public boolean voidIt() { log.info(toString()); // Dispara la validación del documento m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_VOID); if (m_processMsg != null) return false; // Cheque si ya fue procesado if (DOCSTATUS_Closed.equals(getDocStatus()) || DOCSTATUS_Voided.equals(getDocStatus())) { m_processMsg = "Documento cerrado: " + getDocStatus(); setDocAction(DOCACTION_None); return false; } // Procesa la anulación // NOTA: No se tiene el concepto de "reversión" de las cabeceras de cobros/pagos. MPayment[] pays = getPayments(get_TrxName()); for(int i = 0; i < pays.length; i++) { pays[i].processIt(ACTION_Void); pays[i].save(get_TrxName()); if (!DOCSTATUS_Voided.equals(pays[i].getDocStatus())) { m_processMsg = "@C_Payment_ID@: " + pays[i].getProcessMsg(); return false; } } // Dispara la validación del documento m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_VOID); if (m_processMsg != null) return false; setProcessed(true); setDocStatus(ACTION_Void); setDocAction(DOCACTION_None); return true; } @Override public boolean closeIt() { // TODO Auto-generated method stub return false; } @Override public boolean reverseCorrectIt() { // TODO Auto-generated method stub return false; } @Override public boolean reverseAccrualIt() { // TODO Auto-generated method stub return false; } @Override public boolean reActivateIt() { // TODO Auto-generated method stub return false; } @Override public String getSummary() { // TODO Auto-generated method stub return null; } @Override public String getDocumentInfo() { return "Cabecera " + getDocumentNo(); } @Override public File createPDF() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Operación no soportada"); } @Override public String getProcessMsg() { return m_processMsg; } @Override public int getDoc_User_ID() { return getCreatedBy(); } @Override public int getC_Currency_ID() { // TODO - Si es necesario, agregar la columna C_Currency_ID a la // tabla LAR_PaymentHeader y generar el modelo nuevamente return Env.getContextAsInt(getCtx(), "$C_Currency_ID"); // ARS } @Override public BigDecimal getApprovalAmt() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Operación no soportada"); } @Override public int customizeValidActions(String docStatus, Object processing, String orderType, String isSOTrx, int AD_Table_ID, String[] docAction, String[] options, int index) { // Este método permite agregar las acciones necesarias para // operar con el documento "cabecera" if (AD_Table_ID == Table_ID) { // Complete if (docStatus.equals(DocumentEngine.STATUS_Completed)) options[index++] = DocumentEngine.ACTION_Void; } return index; } @Override public String toString() { StringBuffer sb = new StringBuffer ("MLARPaymentHeader["); sb.append(get_ID()).append("-").append(getDocumentNo()) .append(",Receipt=").append(isReceipt()) .append(",DocStatus=").append(getDocStatus()) .append ("]"); return sb.toString (); } public MPaymentAllocate[] getInvoices(String trxName) { // TODO - Analize genereate a cache for this invoices List<MPaymentAllocate> invoices = new ArrayList<MPaymentAllocate>(); String sql = "SELECT * FROM C_PaymentAllocate WHERE LAR_PaymentHeader_ID = ? ORDER BY C_PaymentAllocate_ID"; PreparedStatement pstmt; pstmt = DB.prepareStatement(sql, trxName); ResultSet rs = null; try { pstmt.setInt(1, getLAR_PaymentHeader_ID()); rs = pstmt.executeQuery(); while (rs.next()) invoices.add(new MPaymentAllocate(getCtx(), rs, trxName)); return invoices.toArray(new MPaymentAllocate[invoices.size()]); } catch (SQLException e) { log.log(Level.SEVERE, sql, e); return new MPaymentAllocate[0]; } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } } // getInvoices /* * Borra los registros de retención (Certificados) asociados a una cabecera. */ public String BorrarCertificadosdeRetenciondelHeader() { // Chequear que es una operación de Compras if (this.isReceipt()) return null; int mLARaymentHeader_ID = getLAR_PaymentHeader_ID(); log.info("Borrar los certificados de retenci\u00f3n de la Orden de Pago: " + mLARaymentHeader_ID); String sql = "DELETE FROM LAR_PaymentWithholding WHERE LAR_PaymentHeader_ID=?"; PreparedStatement pstmt = null; try { pstmt = DB.prepareStatement(sql, get_TrxName()); pstmt.setInt(1, mLARaymentHeader_ID); pstmt.executeUpdate(); } catch (Exception e) { log.log(Level.SEVERE, sql, e); return e.getMessage(); } finally { DB.close(pstmt); pstmt = null; } return null; } // BorrarCertificadosdeRetenciondelHeader /* * Borra los Pagos Retención asociados a una cabecera. */ public String BorrarPagosRetenciondelHeader() { // Chequear que es una operación de Compras if (this.isReceipt()) return null; int mLARaymentHeader_ID = getLAR_PaymentHeader_ID(); log.info("Borrar los pagos retenci/u00f3n de la cabecera: " + mLARaymentHeader_ID); String sql = "DELETE FROM C_Payment WHERE LAR_PaymentHeader_ID=? AND EsRetencionIIBB='Y'"; PreparedStatement pstmt = null; try { pstmt = DB.prepareStatement(sql, get_TrxName()); pstmt.setInt(1, mLARaymentHeader_ID); pstmt.executeUpdate(); } catch (Exception e) { log.log(Level.SEVERE, sql, e); return e.getMessage(); } finally { DB.close(pstmt); pstmt = null; } return null; } // BorrarPagosRetenciondelHeader /** * Crea el pago "retención" necesario para procesar la cabecera de pago * * @param impRetencion * Importe de la retención. * @param c_Charge_ID * Cargo del tipo de retención para utilizar en el pago. * @param c_DocType_ID * Tipo de documento (retención) para utilizar en el pago. * @param pago * Pago sobre el cual se descontará el importe de la retención. * @param compensa * Determina si se debe compensar el importe de la retención. * @return pago retencion. */ public MPayment creaPagoRetencion(BigDecimal impRetencion, int c_Charge_ID, int c_DocType, MPayment pago, Boolean compensa) { // Se compensa el importe de la retención en el pago recibido if (compensa) { if (pago == null) { JDialog dialog = new JDialog(); dialog.setIconImage(Adempiere.getImage16()); ADialog.warn(1, dialog, "No existe un pago que permita compensar el importe de la retenci\u00f3n"); return null; } // Se actualiza el total del pago compensado directamente vía SQL // para evitar que se dispare el recálculo de retenciones setPayAmtDirectly(pago, pago.getPayAmt().subtract(impRetencion)); } final MPayment pagoRetencion = new MPayment(getCtx(), 0, get_TrxName()); pagoRetencion.setC_DocType_ID(getC_DocType_ID()); pagoRetencion.setDocumentNo(getDocumentNo()); pagoRetencion.setC_Currency_ID(getC_Currency_ID()); pagoRetencion.setC_BankAccount_ID(getC_BankAccount_ID()); pagoRetencion.setC_BPartner_ID(getC_BPartner_ID()); pagoRetencion.setAD_Org_ID(getAD_Org_ID()); pagoRetencion.setIsReceipt(false); pagoRetencion.setIsAllocated(false); pagoRetencion.setIsReconciled(true); // Este campo determina que es una retención generada pagoRetencion.set_ValueOfColumn("EsRetencionIIBB", true); pagoRetencion.set_ValueOfColumn("LAR_PaymentHeader_ID", getLAR_PaymentHeader_ID()); pagoRetencion.setTenderType(MPayment.TENDERTYPE_Cash); pagoRetencion.setPayAmt(impRetencion); pagoRetencion.setC_Charge_ID(c_Charge_ID); if (!pagoRetencion.save(get_TrxName())) return null; return pagoRetencion; } // creaPagoRetencion /** * Crea el certificado de retención asociado a la retención aplicable * * @param impRetencion * Importe de la retención. * @param Configuración * de retención aplicable. * @return Certificado de Retención. */ public MLARPaymentWithholding creaCertificadodeRetencion(final BigDecimal impRetencion, final BigDecimal baseRet, final WithholdingConfig wc, final int c_Payment_ID) { final MLARPaymentWithholding pwh = new MLARPaymentWithholding(getCtx(), 0, get_TrxName()); pwh.setLAR_PaymentHeader_ID(getLAR_PaymentHeader_ID()); pwh.setC_Tax_ID(wc.getC_Tax_ID()); pwh.setDateAcct(getDateTrx()); pwh.setDateTrx(getDateTrx()); pwh.setLCO_WithholdingRule_ID(wc.getWithholdingRule_ID()); pwh.setLCO_WithholdingType_ID(wc.getWithholdingType_ID()); pwh.setPercent(wc.getAliquot()); pwh.setProcessed(false); pwh.setTaxAmt(impRetencion); pwh.setTaxBaseAmt(baseRet); // Se asocia el Pago Retención con el Certificado pwh.set_ValueOfColumn("C_Payment_ID", c_Payment_ID); // Cuando se guarda la retención, se actualiza la cabecera // de pago mediante MLARPaymentWithholding.afterSave() if (!pwh.save(get_TrxName())) return null; return pwh; } // creaCertificadodeRetencion } // MLARPaymentHeader