org.openbravo.advpaymentmngt.process.FIN_PaymentProcess.java Source code

Java tutorial

Introduction

Here is the source code for org.openbravo.advpaymentmngt.process.FIN_PaymentProcess.java

Source

/*
 *************************************************************************
 * The contents of this file are subject to the Openbravo  Public  License
 * Version  1.0  (the  "License"),  being   the  Mozilla   Public  License
 * Version 1.1  with a permitted attribution clause; you may not  use this
 * file except in compliance with the License. You  may  obtain  a copy of
 * the License at http://www.openbravo.com/legal/license.html
 * Software distributed under the License  is  distributed  on  an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific  language  governing  rights  and  limitations
 * under the License.
 * The Original Code is Openbravo ERP.
 * The Initial Developer of the Original Code is Openbravo SLU
 * All portions are Copyright (C) 2010-2015 Openbravo SLU
 * All Rights Reserved.
 * Contributor(s):  ______________________________________.
 *************************************************************************
 */
package org.openbravo.advpaymentmngt.process;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.StringUtils;
import org.hibernate.criterion.Restrictions;
import org.openbravo.advpaymentmngt.dao.AdvPaymentMngtDao;
import org.openbravo.advpaymentmngt.dao.TransactionsDao;
import org.openbravo.advpaymentmngt.exception.NoExecutionProcessFoundException;
import org.openbravo.advpaymentmngt.utility.FIN_Utility;
import org.openbravo.base.exception.OBException;
import org.openbravo.base.provider.OBProvider;
import org.openbravo.base.weld.WeldUtils;
import org.openbravo.client.kernel.RequestContext;
import org.openbravo.dal.core.DalUtil;
import org.openbravo.dal.core.OBContext;
import org.openbravo.dal.service.OBCriteria;
import org.openbravo.dal.service.OBDal;
import org.openbravo.database.ConnectionProvider;
import org.openbravo.erpCommon.utility.OBDateUtils;
import org.openbravo.erpCommon.utility.OBError;
import org.openbravo.erpCommon.utility.OBMessageUtils;
import org.openbravo.model.common.businesspartner.BusinessPartner;
import org.openbravo.model.common.currency.ConversionRate;
import org.openbravo.model.common.currency.ConversionRateDoc;
import org.openbravo.model.common.currency.Currency;
import org.openbravo.model.common.enterprise.Organization;
import org.openbravo.model.common.invoice.Invoice;
import org.openbravo.model.financialmgmt.accounting.FIN_FinancialAccountAccounting;
import org.openbravo.model.financialmgmt.payment.FIN_FinaccTransaction;
import org.openbravo.model.financialmgmt.payment.FIN_Payment;
import org.openbravo.model.financialmgmt.payment.FIN_PaymentDetail;
import org.openbravo.model.financialmgmt.payment.FIN_PaymentPropDetail;
import org.openbravo.model.financialmgmt.payment.FIN_PaymentProposal;
import org.openbravo.model.financialmgmt.payment.FIN_PaymentScheduleDetail;
import org.openbravo.model.financialmgmt.payment.FIN_Payment_Credit;
import org.openbravo.model.financialmgmt.payment.FinAccPaymentMethod;
import org.openbravo.model.financialmgmt.payment.PaymentExecutionProcess;
import org.openbravo.scheduling.ProcessBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FIN_PaymentProcess implements org.openbravo.scheduling.Process {
    private static AdvPaymentMngtDao dao;

    public BigDecimal ZERO = BigDecimal.ZERO;
    static Logger log4j = LoggerFactory.getLogger(FIN_PaymentProcess.class);

    public void execute(ProcessBundle bundle) throws Exception {

        OBError msg = new OBError();
        msg.setType("Success");
        msg.setTitle(OBMessageUtils.messageBD("Success"));

        try {
            // retrieve custom params
            final String strAction = (String) bundle.getParams().get("action");
            final String comingFrom = (String) bundle.getParams().get("comingFrom");
            final String selectedCreditLineIds = (String) bundle.getParams().get("selectedCreditLineIds");
            // retrieve standard params
            final String recordID = (String) bundle.getParams().get("Fin_Payment_ID");
            final FIN_Payment payment = OBDal.getInstance().get(FIN_Payment.class, recordID);
            final Boolean isPosOrder;
            if (bundle.getParams().get("isPOSOrder") == null) {
                isPosOrder = false;
            } else {
                isPosOrder = bundle.getParams().get("isPOSOrder").equals("Y");
            }
            final String paymentDate = (String) bundle.getParams().get("paymentdate");
            processPayment(payment, strAction, isPosOrder, paymentDate, comingFrom, selectedCreditLineIds);
            bundle.setResult(msg);
        } catch (Exception e) {
            log4j.error(e.getMessage());
            msg.setType("Error");
            msg.setTitle(OBMessageUtils.messageBD("Error"));
            msg.setMessage(FIN_Utility.getExceptionMessage(e));
            bundle.setResult(msg);
            OBDal.getInstance().getConnection().rollback();
        }
    }

    // ProcessPayment without a return type
    public static void doProcessPayment(FIN_Payment payment, String strAction, Boolean isPosOrder,
            String paymentDate, String comingFrom) throws OBException {
        FIN_PaymentProcess fpp = WeldUtils.getInstanceFromStaticBeanManager(FIN_PaymentProcess.class);
        fpp.processPayment(payment, strAction, isPosOrder, paymentDate, comingFrom, null);
    }

    private void processPayment(FIN_Payment payment, String strAction, Boolean isPosOrder, String paymentDate,
            String comingFrom, String selectedCreditLineIds) throws OBException {
        dao = new AdvPaymentMngtDao();
        String msg = "";
        try {
            final boolean isReceipt = payment.isReceipt();
            if (strAction.equals("P") || strAction.equals("D")) {
                if (payment.getBusinessPartner() != null) {
                    if (FIN_Utility.isBlockedBusinessPartner(payment.getBusinessPartner().getId(), isReceipt, 4)) {
                        // If the Business Partner is blocked for Payments, the Payment will not be completed.
                        msg = OBMessageUtils.messageBD("ThebusinessPartner") + " "
                                + payment.getBusinessPartner().getIdentifier() + " "
                                + OBMessageUtils.messageBD("BusinessPartnerBlocked");
                        throw new OBException(msg);
                    }
                } else {
                    OBContext.setAdminMode(true);
                    try {
                        for (FIN_PaymentDetail pd : payment.getFINPaymentDetailList()) {
                            for (FIN_PaymentScheduleDetail psd : pd.getFINPaymentScheduleDetailList()) {
                                BusinessPartner bPartner = null;
                                if (psd.getInvoicePaymentSchedule() != null) {
                                    bPartner = psd.getInvoicePaymentSchedule().getInvoice().getBusinessPartner();
                                } else if (psd.getOrderPaymentSchedule() != null) {
                                    bPartner = psd.getOrderPaymentSchedule().getOrder().getBusinessPartner();
                                }
                                if (bPartner != null && FIN_Utility.isBlockedBusinessPartner(bPartner.getId(),
                                        payment.isReceipt(), 4)) {
                                    // If the Business Partner is blocked for Payments, the Payment will not be
                                    // completed.
                                    msg = OBMessageUtils.messageBD("ThebusinessPartner") + " "
                                            + bPartner.getIdentifier() + " "
                                            + OBMessageUtils.messageBD("BusinessPartnerBlocked");
                                    throw new OBException(msg);
                                }
                            }
                        }
                    } finally {
                        OBContext.restorePreviousMode();
                    }
                }
            }

            if (strAction.equals("P") || strAction.equals("D")) {
                // Guess if this is a refund payment
                boolean isRefund = false;
                OBContext.setAdminMode(false);
                try {
                    List<FIN_PaymentDetail> paymentDetailList = payment.getFINPaymentDetailList();
                    if (paymentDetailList.size() > 0) {
                        for (FIN_PaymentDetail det : paymentDetailList) {
                            if (det.isRefund()) {
                                isRefund = true;
                                break;
                            }
                        }
                    }
                } finally {
                    OBContext.restorePreviousMode();
                }
                if (!isRefund) {
                    // Undo Used credit as it will be calculated again
                    payment.setUsedCredit(BigDecimal.ZERO);
                    OBDal.getInstance().save(payment);
                }

                boolean documentEnabled = getDocumentConfirmation(null, payment.getId());
                boolean periodNotAvailable = documentEnabled
                        && !FIN_Utility.isPeriodOpen(payment.getClient().getId(),
                                payment.getDocumentType().getDocumentCategory(), payment.getOrganization().getId(),
                                OBDateUtils.formatDate(payment.getPaymentDate()))
                        && FIN_Utility.periodControlOpened(FIN_Payment.TABLE_NAME, payment.getId(),
                                FIN_Payment.TABLE_NAME + "_ID", "LE");
                if (periodNotAvailable) {
                    msg = OBMessageUtils.messageBD("PeriodNotAvailable");
                    throw new OBException(msg);
                }
                Set<String> documentOrganizations = OBContext.getOBContext()
                        .getOrganizationStructureProvider(payment.getClient().getId())
                        .getNaturalTree(payment.getOrganization().getId());
                if (!documentOrganizations.contains(payment.getAccount().getOrganization().getId())) {
                    msg = OBMessageUtils.messageBD("APRM_FinancialAccountNotInNaturalTree");
                    throw new OBException(msg);
                }
                Set<String> invoiceDocNos = new TreeSet<String>();
                Set<String> orderDocNos = new TreeSet<String>();
                Set<String> glitems = new TreeSet<String>();
                BigDecimal paymentAmount = BigDecimal.ZERO;
                BigDecimal paymentWriteOfAmount = BigDecimal.ZERO;

                // FIXME: added to access the FIN_PaymentSchedule and FIN_PaymentScheduleDetail tables to be
                // removed when new security implementation is done
                OBContext.setAdminMode();
                boolean flushDone = false;
                try {
                    String strRefundCredit = "";
                    // update payment schedule amount
                    List<FIN_PaymentDetail> paymentDetails = payment.getFINPaymentDetailList();

                    // Show error message when payment has no lines
                    if (paymentDetails.size() == 0) {
                        msg = OBMessageUtils.messageBD("APRM_PaymentNoLines");
                        log4j.debug(msg);
                        throw new OBException(msg, false);
                    }
                    for (FIN_PaymentDetail paymentDetail : paymentDetails) {
                        for (FIN_PaymentScheduleDetail paymentScheduleDetail : paymentDetail
                                .getFINPaymentScheduleDetailList()) {
                            paymentAmount = paymentAmount.add(paymentScheduleDetail.getAmount());
                            BigDecimal writeoff = paymentScheduleDetail.getWriteoffAmount();
                            if (writeoff == null)
                                writeoff = BigDecimal.ZERO;
                            paymentWriteOfAmount = paymentWriteOfAmount.add(writeoff);
                            if (paymentScheduleDetail.getInvoicePaymentSchedule() != null) {
                                final Invoice invoice = paymentScheduleDetail.getInvoicePaymentSchedule()
                                        .getInvoice();
                                invoiceDocNos
                                        .add(FIN_Utility.getDesiredDocumentNo(payment.getOrganization(), invoice));
                            }
                            if (paymentScheduleDetail.getOrderPaymentSchedule() != null) {
                                orderDocNos.add(
                                        paymentScheduleDetail.getOrderPaymentSchedule().getOrder().getDocumentNo());
                            }
                            if (paymentScheduleDetail.getInvoicePaymentSchedule() == null
                                    && paymentScheduleDetail.getOrderPaymentSchedule() == null
                                    && paymentScheduleDetail.getPaymentDetails().getGLItem() == null) {
                                if (paymentDetail.isRefund())
                                    strRefundCredit = OBMessageUtils.messageBD("APRM_RefundAmount");
                                else {
                                    strRefundCredit = OBMessageUtils.messageBD("APRM_CreditAmount");
                                    payment.setGeneratedCredit(paymentDetail.getAmount());
                                }
                                strRefundCredit += ": " + paymentDetail.getAmount().toString();
                            }
                        }
                        if (paymentDetail.getGLItem() != null)
                            glitems.add(paymentDetail.getGLItem().getName());
                    }
                    // Set description
                    if (!isPosOrder) {
                        StringBuffer description = new StringBuffer();

                        if (payment.getDescription() != null && !payment.getDescription().equals(""))
                            description.append(payment.getDescription()).append("\n");
                        if (!invoiceDocNos.isEmpty()) {
                            description.append(OBMessageUtils.messageBD("InvoiceDocumentno"));
                            description.append(": ").append(
                                    invoiceDocNos.toString().substring(1, invoiceDocNos.toString().length() - 1));
                            description.append("\n");
                        }
                        if (!orderDocNos.isEmpty()) {
                            description.append(OBMessageUtils.messageBD("OrderDocumentno"));
                            description.append(": ").append(
                                    orderDocNos.toString().substring(1, orderDocNos.toString().length() - 1));
                            description.append("\n");
                        }
                        if (!glitems.isEmpty()) {
                            description.append(OBMessageUtils.messageBD("APRM_GLItem"));
                            description.append(": ")
                                    .append(glitems.toString().substring(1, glitems.toString().length() - 1));
                            description.append("\n");
                        }
                        if (!"".equals(strRefundCredit))
                            description.append(strRefundCredit).append("\n");

                        String truncateDescription = (description.length() > 255)
                                ? description.substring(0, 251).concat("...").toString()
                                : description.toString();
                        payment.setDescription(truncateDescription);
                    }

                    if (paymentAmount.compareTo(payment.getAmount()) != 0) {
                        payment.setUsedCredit(paymentAmount.subtract(payment.getAmount()));
                    }
                    if (payment.getUsedCredit().compareTo(BigDecimal.ZERO) != 0) {
                        updateUsedCredit(payment, selectedCreditLineIds);
                    }

                    payment.setWriteoffAmount(paymentWriteOfAmount);
                    payment.setProcessed(true);
                    payment.setAPRMProcessPayment("RE");
                    if (payment.getGeneratedCredit() == null) {
                        payment.setGeneratedCredit(BigDecimal.ZERO);
                    }
                    if (BigDecimal.ZERO.compareTo(payment.getUsedCredit()) != 0
                            || BigDecimal.ZERO.compareTo(payment.getGeneratedCredit()) != 0) {
                        BusinessPartner businessPartner = payment.getBusinessPartner();
                        if (businessPartner == null) {
                            msg = OBMessageUtils.messageBD("APRM_CreditWithoutBPartner");
                            throw new OBException(msg);
                        }
                        String currency = null;
                        if (businessPartner.getCurrency() == null) {
                            currency = payment.getCurrency().getId();
                            businessPartner.setCurrency(payment.getCurrency());
                        } else {
                            currency = businessPartner.getCurrency().getId();
                        }
                        if (!payment.getCurrency().getId().equals(currency)) {
                            msg = String.format(OBMessageUtils.messageBD("APRM_CreditCurrency"),
                                    businessPartner.getCurrency().getISOCode());
                            throw new OBException(msg);
                        }
                    }
                    // Execution Process
                    if (!isPosOrder && dao.isAutomatedExecutionPayment(payment.getAccount(),
                            payment.getPaymentMethod(), payment.isReceipt())) {
                        try {
                            payment.setStatus("RPAE");

                            if (dao.hasNotDeferredExecutionProcess(payment.getAccount(), payment.getPaymentMethod(),
                                    payment.isReceipt())) {
                                PaymentExecutionProcess executionProcess = dao.getExecutionProcess(payment);
                                if (dao.isAutomaticExecutionProcess(executionProcess)) {
                                    final List<FIN_Payment> payments = new ArrayList<FIN_Payment>(1);
                                    payments.add(payment);
                                    FIN_ExecutePayment executePayment = new FIN_ExecutePayment();
                                    executePayment.init("APP", executionProcess, payments, null,
                                            payment.getOrganization());
                                    executePayment.addInternalParameter("comingFrom", comingFrom);
                                    OBError result = executePayment.execute();
                                    if ("Error".equals(result.getType())) {
                                        msg = OBMessageUtils.messageBD(result.getMessage());
                                    } else if (!"".equals(result.getMessage())) {
                                        String execProcessMsg = OBMessageUtils.messageBD(result.getMessage());
                                        if (!"".equals(msg)) {
                                            msg += "<br>";
                                        }
                                        msg += execProcessMsg;
                                    }
                                }
                            }
                        } catch (final NoExecutionProcessFoundException e) {
                            msg = OBMessageUtils.messageBD("NoExecutionProcessFound");
                            throw new OBException(msg);
                        } catch (final Exception e) {
                            msg = OBMessageUtils.messageBD("IssueOnExecutionProcess");
                            throw new OBException(msg);
                        }
                    } else {
                        BusinessPartner businessPartner = payment.getBusinessPartner();
                        // When credit is used (consumed) we compensate so_creditused as this amount is already
                        // included in the payment details. Credit consumed should not affect to so_creditused
                        if (payment.getGeneratedCredit().compareTo(BigDecimal.ZERO) == 0
                                && payment.getUsedCredit().compareTo(BigDecimal.ZERO) != 0) {
                            if (isReceipt) {
                                increaseCustomerCredit(businessPartner, payment.getUsedCredit());
                            } else {
                                decreaseCustomerCredit(businessPartner, payment.getUsedCredit());
                            }
                        }

                        for (FIN_PaymentDetail paymentDetail : payment.getFINPaymentDetailList()) {

                            List<FIN_PaymentScheduleDetail> orderPaymentScheduleDetails = new ArrayList<FIN_PaymentScheduleDetail>(
                                    paymentDetail.getFINPaymentScheduleDetailList());

                            // Get payment schedule detail list ordered by amount asc.
                            // First negative if they exist and then positives
                            if (orderPaymentScheduleDetails.size() > 1) {
                                Collections.sort(orderPaymentScheduleDetails,
                                        new Comparator<FIN_PaymentScheduleDetail>() {
                                            @Override
                                            public int compare(FIN_PaymentScheduleDetail o1,
                                                    FIN_PaymentScheduleDetail o2) {
                                                // TODO Auto-generated method stub
                                                return o1.getAmount().compareTo(o2.getAmount());
                                            }
                                        });
                            }

                            for (FIN_PaymentScheduleDetail paymentScheduleDetail : orderPaymentScheduleDetails) {
                                BigDecimal amount = paymentScheduleDetail.getAmount()
                                        .add(paymentScheduleDetail.getWriteoffAmount());
                                // Do not restore paid amounts if the payment is awaiting execution.
                                boolean invoicePaidAmounts = (FIN_Utility
                                        .seqnumberpaymentstatus(isReceipt ? "RPR" : "PPM")) >= (FIN_Utility
                                                .seqnumberpaymentstatus(FIN_Utility.invoicePaymentStatus(payment)));
                                paymentScheduleDetail.setInvoicePaid(false);
                                // Payment = 0 when the payment is generated by a invoice that consume credit
                                if (invoicePaidAmounts
                                        || (payment.getAmount().compareTo(new BigDecimal("0.00")) == 0)) {
                                    if (paymentScheduleDetail.getInvoicePaymentSchedule() != null) {
                                        // BP SO_CreditUsed
                                        businessPartner = paymentScheduleDetail.getInvoicePaymentSchedule()
                                                .getInvoice().getBusinessPartner();

                                        // Payments update credit opposite to invoices
                                        BigDecimal paidAmount = BigDecimal.ZERO;
                                        Invoice invoiceForConversion = paymentScheduleDetail
                                                .getInvoicePaymentSchedule() != null
                                                        ? paymentScheduleDetail.getInvoicePaymentSchedule()
                                                                .getInvoice()
                                                        : null;
                                        paidAmount = BigDecimal.ZERO;
                                        String fromCurrency = payment.getCurrency().getId();
                                        if (businessPartner.getCurrency() == null) {
                                            String errorMSG = OBMessageUtils.messageBD("InitBPCurrencyLnk", false);
                                            msg = String.format(errorMSG, businessPartner.getId(),
                                                    businessPartner.getName());
                                            throw new OBException(msg);
                                        }
                                        String toCurrency = businessPartner.getCurrency().getId();
                                        if (!fromCurrency.equals(toCurrency)) {
                                            BigDecimal exchangeRate = BigDecimal.ZERO;
                                            // check at invoice document level
                                            List<ConversionRateDoc> conversionRateDocumentForInvoice = getConversionRateDocumentForInvoice(
                                                    invoiceForConversion);
                                            if (conversionRateDocumentForInvoice.size() > 0) {
                                                exchangeRate = conversionRateDocumentForInvoice.get(0).getRate();
                                            } else {
                                                // global
                                                exchangeRate = getConversionRate(payment.getOrganization().getId(),
                                                        fromCurrency, toCurrency,
                                                        invoiceForConversion != null
                                                                ? invoiceForConversion.getInvoiceDate()
                                                                : payment.getPaymentDate());
                                            }
                                            if (exchangeRate == BigDecimal.ZERO) {
                                                msg = OBMessageUtils.messageBD("NoCurrencyConversion");
                                                throw new OBException(msg);
                                            }
                                            paidAmount = amount.multiply(exchangeRate);
                                        } else {
                                            paidAmount = amount;
                                        }
                                        if (isReceipt) {
                                            decreaseCustomerCredit(businessPartner, paidAmount);
                                        } else {
                                            increaseCustomerCredit(businessPartner, paidAmount);
                                        }
                                        FIN_AddPayment.updatePaymentScheduleAmounts(paymentDetail,
                                                paymentScheduleDetail.getInvoicePaymentSchedule(),
                                                paymentScheduleDetail.getAmount(),
                                                paymentScheduleDetail.getWriteoffAmount());
                                        paymentScheduleDetail.setInvoicePaid(true);
                                    }

                                    if (paymentScheduleDetail.getOrderPaymentSchedule() != null) {
                                        FIN_AddPayment.updatePaymentScheduleAmounts(paymentDetail,
                                                paymentScheduleDetail.getOrderPaymentSchedule(),
                                                paymentScheduleDetail.getAmount(),
                                                paymentScheduleDetail.getWriteoffAmount());
                                        paymentScheduleDetail.setInvoicePaid(true);
                                    }
                                    // when generating credit for a BP SO_CreditUsed is also updated
                                    if (paymentScheduleDetail.getInvoicePaymentSchedule() == null
                                            && paymentScheduleDetail.getOrderPaymentSchedule() == null
                                            && paymentScheduleDetail.getPaymentDetails().getGLItem() == null
                                            && !paymentDetail.isRefund()) {
                                        // BP SO_CreditUsed
                                        if (isReceipt) {
                                            decreaseCustomerCredit(businessPartner, amount);
                                        } else {
                                            increaseCustomerCredit(businessPartner, amount);
                                        }
                                    }
                                }
                            }
                        }
                        payment.setStatus(isReceipt ? "RPR" : "PPM");

                        if ((strAction.equals("D") || FIN_Utility.isAutomaticDepositWithdrawn(payment))
                                && payment.getAmount().compareTo(BigDecimal.ZERO) != 0
                                && !"TRANSACTION".equals(comingFrom)) {
                            triggerAutomaticFinancialAccountTransaction(payment);
                            flushDone = true;
                        }
                    }
                    if (!payment.getAccount().getCurrency().equals(payment.getCurrency())
                            && getConversionRateDocument(payment).size() == 0) {
                        insertConversionRateDocument(payment);
                        flushDone = true;
                    }
                } finally {
                    if (!flushDone) {
                        OBDal.getInstance().flush();
                    }
                    OBContext.restorePreviousMode();
                }

                // ***********************
                // Reverse Payment
                // ***********************
            } else if (strAction.equals("RV")) {
                FIN_Payment reversedPayment = (FIN_Payment) DalUtil.copy(payment, false);
                OBContext.setAdminMode();
                try {
                    if (BigDecimal.ZERO.compareTo(payment.getGeneratedCredit()) != 0
                            && BigDecimal.ZERO.compareTo(payment.getUsedCredit()) != 0) {
                        throw new OBException("@APRM_CreditConsumed@");
                    } else if (BigDecimal.ZERO.compareTo(payment.getGeneratedCredit()) != 0
                            && BigDecimal.ZERO.compareTo(payment.getUsedCredit()) == 0) {
                        reversedPayment.setUsedCredit(payment.getGeneratedCredit());
                        reversedPayment.setGeneratedCredit(BigDecimal.ZERO);
                    } else {
                        reversedPayment.setUsedCredit(BigDecimal.ZERO);
                        reversedPayment.setGeneratedCredit(BigDecimal.ZERO);
                    }
                    reversedPayment.setDocumentNo(
                            "*R*" + FIN_Utility.getDocumentNo(payment.getDocumentType(), "FIN_Payment"));
                    reversedPayment.setPaymentDate(FIN_Utility.getDate(paymentDate));
                    reversedPayment.setDescription("");
                    reversedPayment.setProcessed(false);
                    reversedPayment.setPosted("N");
                    reversedPayment.setProcessNow(false);
                    reversedPayment.setAPRMProcessPayment("P");
                    reversedPayment.setStatus("RPAP");
                    // Amounts
                    reversedPayment.setAmount(payment.getAmount().negate());
                    reversedPayment.setWriteoffAmount(payment.getWriteoffAmount().negate());
                    reversedPayment.setFinancialTransactionAmount(payment.getFinancialTransactionAmount().negate());
                    OBDal.getInstance().save(reversedPayment);

                    List<FIN_PaymentDetail> reversedDetails = new ArrayList<FIN_PaymentDetail>();

                    OBDal.getInstance().save(reversedPayment);
                    List<FIN_Payment_Credit> credits = payment.getFINPaymentCreditList();

                    for (FIN_PaymentDetail pd : payment.getFINPaymentDetailList()) {
                        FIN_PaymentDetail reversedPaymentDetail = (FIN_PaymentDetail) DalUtil.copy(pd, false);
                        reversedPaymentDetail.setFinPayment(reversedPayment);
                        reversedPaymentDetail.setAmount(pd.getAmount().negate());
                        reversedPaymentDetail.setWriteoffAmount(pd.getWriteoffAmount().negate());
                        if (pd.isRefund()) {
                            reversedPaymentDetail.setPrepayment(true);
                            reversedPaymentDetail.setRefund(false);
                            reversedPayment
                                    .setGeneratedCredit(reversedPayment.getGeneratedCredit().add(pd.getAmount()));
                            credits = new ArrayList<FIN_Payment_Credit>();
                            OBDal.getInstance().save(reversedPayment);
                        } else if (pd.isPrepayment()
                                && pd.getFINPaymentScheduleDetailList().get(0).getOrderPaymentSchedule() == null) {
                            reversedPaymentDetail.setPrepayment(true);
                            reversedPaymentDetail.setRefund(true);
                        }
                        List<FIN_PaymentScheduleDetail> reversedSchedDetails = new ArrayList<FIN_PaymentScheduleDetail>();
                        OBDal.getInstance().save(reversedPaymentDetail);
                        // Create or update PSD of orders and invoices to set the new outstanding amount
                        for (FIN_PaymentScheduleDetail psd : pd.getFINPaymentScheduleDetailList()) {
                            if (psd.getInvoicePaymentSchedule() != null || psd.getOrderPaymentSchedule() != null) {
                                OBCriteria<FIN_PaymentScheduleDetail> unpaidSchedDet = OBDal.getInstance()
                                        .createCriteria(FIN_PaymentScheduleDetail.class);
                                if (psd.getInvoicePaymentSchedule() != null)
                                    unpaidSchedDet.add(Restrictions.eq(
                                            FIN_PaymentScheduleDetail.PROPERTY_INVOICEPAYMENTSCHEDULE,
                                            psd.getInvoicePaymentSchedule()));
                                if (psd.getOrderPaymentSchedule() != null)
                                    unpaidSchedDet.add(
                                            Restrictions.eq(FIN_PaymentScheduleDetail.PROPERTY_ORDERPAYMENTSCHEDULE,
                                                    psd.getOrderPaymentSchedule()));
                                unpaidSchedDet.add(
                                        Restrictions.isNull(FIN_PaymentScheduleDetail.PROPERTY_PAYMENTDETAILS));
                                List<FIN_PaymentScheduleDetail> openPSDs = unpaidSchedDet.list();
                                // If invoice/order not fully paid, update outstanding amount
                                if (openPSDs.size() > 0) {
                                    FIN_PaymentScheduleDetail openPSD = openPSDs.get(0);
                                    BigDecimal openAmount = openPSD.getAmount()
                                            .add(psd.getAmount().add(psd.getWriteoffAmount()));
                                    if (openAmount.compareTo(BigDecimal.ZERO) == 0) {
                                        OBDal.getInstance().remove(openPSD);
                                    } else {
                                        openPSD.setAmount(openAmount);
                                    }
                                } else {
                                    // If invoice is fully paid create a new schedule detail.
                                    FIN_PaymentScheduleDetail openPSD = (FIN_PaymentScheduleDetail) DalUtil
                                            .copy(psd, false);
                                    openPSD.setPaymentDetails(null);
                                    // Amounts
                                    openPSD.setWriteoffAmount(BigDecimal.ZERO);
                                    openPSD.setAmount(psd.getAmount().add(psd.getWriteoffAmount()));

                                    openPSD.setCanceled(false);
                                    OBDal.getInstance().save(openPSD);
                                }
                            }

                            FIN_PaymentScheduleDetail reversedPaymentSchedDetail = (FIN_PaymentScheduleDetail) DalUtil
                                    .copy(psd, false);
                            reversedPaymentSchedDetail.setPaymentDetails(reversedPaymentDetail);
                            // Amounts
                            reversedPaymentSchedDetail.setWriteoffAmount(psd.getWriteoffAmount().negate());
                            reversedPaymentSchedDetail.setAmount(psd.getAmount().negate());
                            OBDal.getInstance().save(reversedPaymentSchedDetail);
                            reversedSchedDetails.add(reversedPaymentSchedDetail);

                            if ((FIN_Utility.invoicePaymentStatus(reversedPayment)
                                    .equals(reversedPayment.getStatus()))) {
                                reversedPaymentSchedDetail.setInvoicePaid(true);

                            } else {
                                reversedPaymentSchedDetail.setInvoicePaid(false);
                            }
                            OBDal.getInstance().save(reversedPaymentSchedDetail);

                        }

                        reversedPaymentDetail.setFINPaymentScheduleDetailList(reversedSchedDetails);
                        OBDal.getInstance().save(reversedPaymentDetail);
                        reversedDetails.add(reversedPaymentDetail);
                    }
                    reversedPayment.setFINPaymentDetailList(reversedDetails);
                    OBDal.getInstance().save(reversedPayment);

                    List<FIN_Payment_Credit> reversedCredits = new ArrayList<FIN_Payment_Credit>();
                    for (FIN_Payment_Credit pc : credits) {
                        FIN_Payment_Credit reversedPaymentCredit = (FIN_Payment_Credit) DalUtil.copy(pc, false);
                        reversedPaymentCredit.setAmount(pc.getAmount().negate());
                        reversedPaymentCredit.setCreditPaymentUsed(pc.getCreditPaymentUsed());
                        pc.getCreditPaymentUsed().setUsedCredit(
                                pc.getCreditPaymentUsed().getUsedCredit().add(pc.getAmount().negate()));
                        reversedPaymentCredit.setPayment(reversedPayment);
                        OBDal.getInstance().save(pc.getCreditPaymentUsed());
                        OBDal.getInstance().save(reversedPaymentCredit);
                        reversedCredits.add(reversedPaymentCredit);
                    }

                    reversedPayment.setFINPaymentCreditList(reversedCredits);
                    OBDal.getInstance().save(reversedPayment);

                    List<ConversionRateDoc> conversions = new ArrayList<ConversionRateDoc>();
                    for (ConversionRateDoc cr : payment.getCurrencyConversionRateDocList()) {
                        ConversionRateDoc reversedCR = (ConversionRateDoc) DalUtil.copy(cr, false);
                        reversedCR.setForeignAmount(cr.getForeignAmount().negate());
                        reversedCR.setPayment(reversedPayment);
                        OBDal.getInstance().save(reversedCR);
                        conversions.add(reversedCR);
                    }
                    reversedPayment.setCurrencyConversionRateDocList(conversions);
                    OBDal.getInstance().save(reversedPayment);

                    OBDal.getInstance().flush();
                } finally {
                    OBContext.restorePreviousMode();
                }

                payment.setReversedPayment(reversedPayment);
                OBDal.getInstance().save(payment);
                OBDal.getInstance().flush();

                String newStrAction = "P";
                FIN_PaymentProcess fpp = WeldUtils.getInstanceFromStaticBeanManager(FIN_PaymentProcess.class);
                fpp.processPayment(reversedPayment, newStrAction, isPosOrder, paymentDate, comingFrom,
                        selectedCreditLineIds);

                return;

                // ***********************
                // Reactivate Payment
                // ***********************
            } else if (strAction.equals("R") || strAction.equals("RE")) {
                // Already Posted Document
                if ("Y".equals(payment.getPosted())) {
                    msg = OBMessageUtils.messageBD("PostedDocument: " + payment.getDocumentNo());
                    throw new OBException(msg);
                }
                // Reversed Payment
                if (payment.getReversedPayment() != null) {
                    msg = OBMessageUtils.messageBD("APRM_PaymentReversed");
                    throw new OBException(msg);
                }
                // Reverse Payment
                if (strAction.equals("RE") && FIN_Utility.isReversePayment(payment)) {
                    msg = OBMessageUtils.messageBD("APRM_ReversePayment");
                    throw new OBException(msg);
                }

                // Do not reactive the payment if it is tax payment
                if (payment.getFinancialMgmtTaxPaymentList().size() != 0) {
                    msg = OBMessageUtils.messageBD("APRM_TaxPaymentReactivation");
                    throw new OBException(msg);
                }

                // Transaction exists
                if (hasTransaction(payment)) {
                    msg = OBMessageUtils.messageBD("APRM_TransactionExists");
                    throw new OBException(msg);
                }
                // Payment with generated credit already used on other payments.
                if (payment.getGeneratedCredit().compareTo(BigDecimal.ZERO) == 1
                        && payment.getUsedCredit().compareTo(BigDecimal.ZERO) == 1) {
                    msg = OBMessageUtils.messageBD("APRM_PaymentGeneratedCreditIsUsed");
                    throw new OBException(msg);
                }

                if (FIN_Utility.invoicePaymentStatus(payment) == null) {
                    msg = String.format(OBMessageUtils.messageBD("APRM_NoPaymentMethod"),
                            payment.getPaymentMethod().getIdentifier(), payment.getDocumentNo(),
                            payment.getAccount().getName());
                    throw new OBException(msg);
                }
                // Do not restore paid amounts if the payment is awaiting execution.
                boolean restorePaidAmounts = (FIN_Utility
                        .seqnumberpaymentstatus(payment.getStatus())) == (FIN_Utility
                                .seqnumberpaymentstatus(FIN_Utility.invoicePaymentStatus(payment)));
                // Initialize amounts
                payment.setProcessed(false);
                OBDal.getInstance().save(payment);
                OBDal.getInstance().flush();
                payment.setWriteoffAmount(BigDecimal.ZERO);

                payment.setDescription("");

                // if all line are deleted then update amount to zero
                if (strAction.equals("R")) {
                    payment.setAmount(BigDecimal.ZERO);
                }

                payment.setStatus("RPAP");
                payment.setAPRMProcessPayment("P");
                OBDal.getInstance().save(payment);
                OBDal.getInstance().flush();

                final List<FIN_PaymentDetail> removedPD = new ArrayList<FIN_PaymentDetail>();
                List<FIN_PaymentScheduleDetail> removedPDS = new ArrayList<FIN_PaymentScheduleDetail>();
                final List<String> removedPDIds = new ArrayList<String>();
                // FIXME: added to access the FIN_PaymentSchedule and FIN_PaymentScheduleDetail tables to be
                // removed when new security implementation is done
                OBContext.setAdminMode();
                try {
                    BusinessPartner businessPartner = payment.getBusinessPartner();
                    BigDecimal paidAmount = BigDecimal.ZERO;
                    if (!(businessPartner == null)) {
                        // When credit is used (consumed) we compensate so_creditused as this amount is already
                        // included in the payment details. Credit consumed should not affect to so_creditused
                        if (payment.getGeneratedCredit().compareTo(BigDecimal.ZERO) == 0
                                && payment.getUsedCredit().compareTo(BigDecimal.ZERO) != 0) {
                            if (isReceipt) {
                                decreaseCustomerCredit(businessPartner, payment.getUsedCredit());
                            } else {
                                increaseCustomerCredit(businessPartner, payment.getUsedCredit());
                            }
                        }
                    }
                    List<FIN_PaymentDetail> paymentDetails = payment.getFINPaymentDetailList();
                    List<ConversionRateDoc> conversionRates = payment.getCurrencyConversionRateDocList();
                    Set<String> invoiceDocNos = new HashSet<String>();
                    // Undo Reversed payment relationship
                    List<FIN_Payment> revPayments = new ArrayList<FIN_Payment>();
                    for (FIN_Payment reversedPayment : payment.getFINPaymentReversedPaymentList()) {
                        reversedPayment.setReversedPayment(null);
                        OBDal.getInstance().save(reversedPayment);
                    }
                    payment.setFINPaymentReversedPaymentList(revPayments);
                    OBDal.getInstance().save(payment);
                    for (FIN_PaymentDetail paymentDetail : paymentDetails) {
                        removedPDS = new ArrayList<FIN_PaymentScheduleDetail>();
                        for (FIN_PaymentScheduleDetail paymentScheduleDetail : paymentDetail
                                .getFINPaymentScheduleDetailList()) {
                            Boolean invoicePaidold = paymentScheduleDetail.isInvoicePaid();
                            if (invoicePaidold | paymentScheduleDetail.getInvoicePaymentSchedule() == null) {
                                BigDecimal psdWriteoffAmount = paymentScheduleDetail.getWriteoffAmount();
                                BigDecimal psdAmount = paymentScheduleDetail.getAmount();
                                BigDecimal amount = psdAmount.add(psdWriteoffAmount);
                                if (paymentScheduleDetail.getInvoicePaymentSchedule() != null) {
                                    // Remove invoice description related to the credit payments
                                    final Invoice invoice = paymentScheduleDetail.getInvoicePaymentSchedule()
                                            .getInvoice();
                                    invoiceDocNos.add(invoice.getDocumentNo());
                                    final String invDesc = invoice.getDescription();
                                    if (invDesc != null) {
                                        final String creditMsg = OBMessageUtils
                                                .messageBD("APRM_InvoiceDescUsedCredit");
                                        if (creditMsg != null) {
                                            StringBuffer newDesc = new StringBuffer();
                                            for (final String line : invDesc.split("\n")) {
                                                if (!line.startsWith(
                                                        creditMsg.substring(0, creditMsg.lastIndexOf("%s")))) {
                                                    newDesc.append(line);
                                                    if (!"".equals(line))
                                                        newDesc.append("\n");
                                                }
                                            }
                                            if (newDesc.length() > 255) {
                                                newDesc = newDesc.delete(251, newDesc.length());
                                                newDesc = newDesc.append("...\n");
                                            }
                                            invoice.setDescription(newDesc.toString());

                                        }
                                    }
                                    if (restorePaidAmounts) {
                                        FIN_AddPayment.updatePaymentScheduleAmounts(paymentDetail,
                                                paymentScheduleDetail.getInvoicePaymentSchedule(),
                                                psdAmount.negate(), psdWriteoffAmount.negate());
                                        paymentScheduleDetail.setInvoicePaid(false);
                                        OBDal.getInstance().save(paymentScheduleDetail);
                                        // BP SO_CreditUsed
                                        businessPartner = paymentScheduleDetail.getInvoicePaymentSchedule()
                                                .getInvoice().getBusinessPartner();
                                        Invoice invoiceForConversion = paymentScheduleDetail
                                                .getInvoicePaymentSchedule() != null
                                                        ? paymentScheduleDetail.getInvoicePaymentSchedule()
                                                                .getInvoice()
                                                        : null;
                                        paidAmount = BigDecimal.ZERO;
                                        if (!(businessPartner == null)) {
                                            final Currency fromCurrency = payment.getCurrency();
                                            if (businessPartner.getCurrency() == null) {
                                                String errorMSG = OBMessageUtils.messageBD("InitBPCurrencyLnk",
                                                        false);
                                                msg = String.format(errorMSG, businessPartner.getId(),
                                                        businessPartner.getName());
                                                throw new OBException(msg);
                                            }
                                            final Currency toCurrency = businessPartner.getCurrency();
                                            if (fromCurrency != null && toCurrency != null
                                                    && !fromCurrency.getId().equals(toCurrency.getId())) {
                                                BigDecimal exchangeRate = BigDecimal.ZERO;
                                                // check at invoice document level
                                                List<ConversionRateDoc> conversionRateDocumentForInvoice = getConversionRateDocumentForInvoice(
                                                        invoiceForConversion);
                                                if (conversionRateDocumentForInvoice.size() > 0) {
                                                    exchangeRate = conversionRateDocumentForInvoice.get(0)
                                                            .getRate();
                                                } else {
                                                    // global
                                                    exchangeRate = getConversionRate(
                                                            payment.getOrganization().getId(), fromCurrency.getId(),
                                                            toCurrency.getId(),
                                                            invoiceForConversion != null
                                                                    ? invoiceForConversion.getInvoiceDate()
                                                                    : payment.getPaymentDate());
                                                }
                                                if (exchangeRate == BigDecimal.ZERO) {
                                                    msg = OBMessageUtils.messageBD("NoCurrencyConversion");
                                                    throw new OBException(msg);
                                                }
                                                paidAmount = amount.multiply(exchangeRate);
                                            } else {
                                                paidAmount = amount;
                                            }
                                            if (isReceipt) {
                                                increaseCustomerCredit(businessPartner, paidAmount);
                                            } else {
                                                decreaseCustomerCredit(businessPartner, paidAmount);
                                            }
                                        }
                                    }
                                }
                                if (paymentScheduleDetail.getOrderPaymentSchedule() != null && restorePaidAmounts) {
                                    FIN_AddPayment.updatePaymentScheduleAmounts(paymentDetail,
                                            paymentScheduleDetail.getOrderPaymentSchedule(), psdAmount.negate(),
                                            psdWriteoffAmount.negate());
                                }
                                if (restorePaidAmounts) {
                                    // when generating credit for a BP SO_CreditUsed is also updated
                                    if (paymentScheduleDetail.getInvoicePaymentSchedule() == null
                                            && paymentScheduleDetail.getOrderPaymentSchedule() == null
                                            && paymentScheduleDetail.getPaymentDetails().getGLItem() == null
                                            && restorePaidAmounts && !paymentDetail.isRefund()) {
                                        // BP SO_CreditUsed
                                        if (isReceipt) {
                                            increaseCustomerCredit(businessPartner, amount);
                                        } else {
                                            decreaseCustomerCredit(businessPartner, amount);
                                        }
                                    }
                                }
                            }

                            if (strAction.equals("R") || (strAction.equals("RE")
                                    && paymentScheduleDetail.getInvoicePaymentSchedule() == null
                                    && paymentScheduleDetail.getOrderPaymentSchedule() == null
                                    && paymentScheduleDetail.getPaymentDetails().getGLItem() == null)) {
                                FIN_AddPayment.mergePaymentScheduleDetails(paymentScheduleDetail);
                                removedPDS.add(paymentScheduleDetail);
                            }

                        }
                        paymentDetail.getFINPaymentScheduleDetailList().removeAll(removedPDS);
                        if (strAction.equals("R")) {
                            OBDal.getInstance().getSession().refresh(paymentDetail);
                        }
                        // If there is any schedule detail with amount zero, those are deleted
                        // Besides it removes the payment proposal lines linked to the PSD when
                        // a) we are removing the PSD and
                        // b) if we are reactivating a payment (deleting lines only) and we don't come from
                        // payment proposal reactivation process
                        for (FIN_PaymentScheduleDetail psd : removedPDS) {
                            int proposalLinesRemoved = 0;
                            if (BigDecimal.ZERO.compareTo(psd.getAmount()) == 0
                                    && BigDecimal.ZERO.compareTo(psd.getWriteoffAmount()) == 0) {
                                paymentDetail.getFINPaymentScheduleDetailList().remove(psd);
                                OBDal.getInstance().getSession().refresh(paymentDetail);
                                if (psd.getInvoicePaymentSchedule() != null) {
                                    psd.getInvoicePaymentSchedule()
                                            .getFINPaymentScheduleDetailInvoicePaymentScheduleList().remove(psd);
                                }
                                if (psd.getOrderPaymentSchedule() != null) {
                                    psd.getOrderPaymentSchedule()
                                            .getFINPaymentScheduleDetailOrderPaymentScheduleList().remove(psd);
                                }

                                // Before deleting the PSD, we must delete any payment proposal line linked to it
                                proposalLinesRemoved = removePaymentProposalLines(psd);

                                OBDal.getInstance().remove(psd);
                            }

                            // Delete any payment proposal line linked to the PSD if we are reactivating a payment
                            // (deleting lines only), we haven't removed it in a previous step and we don't come
                            // from payment proposal reactivation process
                            if (strAction.equals("R") && proposalLinesRemoved == 0
                                    && !StringUtils.equals(comingFrom,
                                            FIN_PaymentProposalProcess.COMINGFROM_PAYMENTPROPOSALPROCESS)) {
                                removePaymentProposalLines(psd);
                            }
                        }
                        if (paymentDetail.getFINPaymentScheduleDetailList().size() == 0) {
                            removedPD.add(paymentDetail);
                            removedPDIds.add(paymentDetail.getId());
                        }
                        OBDal.getInstance().save(paymentDetail);
                    }
                    for (String pdToRm : removedPDIds) {
                        OBDal.getInstance().remove(OBDal.getInstance().get(FIN_PaymentDetail.class, pdToRm));
                    }
                    payment.getFINPaymentDetailList().removeAll(removedPD);
                    if (strAction.equals("R")) {
                        payment.getCurrencyConversionRateDocList().removeAll(conversionRates);
                        payment.setFinancialTransactionConvertRate(BigDecimal.ZERO);
                    }
                    OBDal.getInstance().save(payment);

                    if (payment.getGeneratedCredit().compareTo(BigDecimal.ZERO) == 0
                            && payment.getUsedCredit().compareTo(BigDecimal.ZERO) != 0) {
                        undoUsedCredit(payment, invoiceDocNos);
                    }

                    List<FIN_Payment> creditPayments = new ArrayList<FIN_Payment>();
                    for (final FIN_Payment_Credit pc : payment.getFINPaymentCreditList()) {
                        creditPayments.add(pc.getCreditPaymentUsed());
                    }
                    for (final FIN_Payment creditPayment : creditPayments) {
                        // Update Description
                        final String payDesc = creditPayment.getDescription();
                        if (payDesc != null) {
                            final String invoiceDocNoMsg = OBMessageUtils.messageBD("APRM_CreditUsedinInvoice");
                            if (invoiceDocNoMsg != null) {
                                final StringBuffer newDesc = new StringBuffer();
                                for (final String line : payDesc.split("\n")) {
                                    boolean include = true;
                                    if (line.startsWith(
                                            invoiceDocNoMsg.substring(0, invoiceDocNoMsg.lastIndexOf("%s")))) {
                                        for (final String docNo : invoiceDocNos) {
                                            if (line.indexOf(docNo) > 0) {
                                                include = false;
                                                break;
                                            }
                                        }
                                    }
                                    if (include) {
                                        newDesc.append(line);
                                        if (!"".equals(line))
                                            newDesc.append("\n");
                                    }
                                }
                                // Truncate Description to keep length as 255
                                creditPayment.setDescription(
                                        newDesc.toString().length() > 255 ? newDesc.toString().substring(0, 255)
                                                : newDesc.toString());
                            }
                        }
                    }

                    payment.getFINPaymentCreditList().clear();
                    if (payment.isReceipt() || strAction.equals("R")) {
                        payment.setGeneratedCredit(BigDecimal.ZERO);
                    }
                    if (strAction.equals("R")) {
                        payment.setUsedCredit(BigDecimal.ZERO);
                    }
                } finally {
                    OBDal.getInstance().flush();
                    OBContext.restorePreviousMode();
                }

            } else if (strAction.equals("V")) {
                // Void
                OBContext.setAdminMode();
                try {
                    if (payment.isProcessed()) {
                        // Already Posted Document
                        if ("Y".equals(payment.getPosted())) {
                            msg = OBMessageUtils.messageBD("PostedDocument: " + payment.getDocumentNo());
                            throw new OBException(msg);
                        }
                        // Transaction exists
                        if (hasTransaction(payment)) {
                            msg = OBMessageUtils.messageBD("APRM_TransactionExists");
                            throw new OBException(msg);
                        }
                        // Payment with generated credit already used on other payments.
                        if (payment.getGeneratedCredit().compareTo(BigDecimal.ZERO) == 1
                                && payment.getUsedCredit().compareTo(BigDecimal.ZERO) == 1) {
                            msg = OBMessageUtils.messageBD("APRM_PaymentGeneratedCreditIsUsed");
                            throw new OBException(msg);
                        }
                        // Payment not in Awaiting Execution
                        boolean restorePaidAmounts = (FIN_Utility
                                .seqnumberpaymentstatus(payment.getStatus())) < (FIN_Utility
                                        .seqnumberpaymentstatus(FIN_Utility.invoicePaymentStatus(payment)));
                        if (!restorePaidAmounts) {
                            msg = OBMessageUtils.messageBD("APRM_PaymentNotRPAE_NotVoid");
                            throw new OBException(msg);
                        }

                        /*
                         * Void the payment
                         */
                        payment.setStatus("RPVOID");

                        /*
                         * Cancel all payment schedule details related to the payment
                         */
                        final List<FIN_PaymentScheduleDetail> removedPDS = new ArrayList<FIN_PaymentScheduleDetail>();
                        Set<String> invoiceDocNos = new HashSet<String>();
                        for (final FIN_PaymentDetail paymentDetail : payment.getFINPaymentDetailList()) {
                            for (final FIN_PaymentScheduleDetail paymentScheduleDetail : paymentDetail
                                    .getFINPaymentScheduleDetailList()) {
                                Boolean invoicePaidold = paymentScheduleDetail.isInvoicePaid();
                                if (invoicePaidold | paymentScheduleDetail.getInvoicePaymentSchedule() == null) {
                                    paymentScheduleDetail.setInvoicePaid(false);
                                }
                                BigDecimal outStandingAmt = BigDecimal.ZERO;

                                if (paymentScheduleDetail.getInvoicePaymentSchedule() != null) {
                                    // Related to invoices
                                    for (final FIN_PaymentScheduleDetail invScheDetail : paymentScheduleDetail
                                            .getInvoicePaymentSchedule()
                                            .getFINPaymentScheduleDetailInvoicePaymentScheduleList()) {
                                        if (invScheDetail.isCanceled()) {
                                            continue;
                                        }
                                        if (invScheDetail.getPaymentDetails() == null) {
                                            outStandingAmt = outStandingAmt.add(invScheDetail.getAmount())
                                                    .add(invScheDetail.getWriteoffAmount());
                                            removedPDS.add(invScheDetail);
                                        } else if (invScheDetail.equals(paymentScheduleDetail)) {
                                            outStandingAmt = outStandingAmt.add(invScheDetail.getAmount())
                                                    .add(invScheDetail.getWriteoffAmount());
                                            paymentScheduleDetail.setCanceled(true);
                                        }
                                        invoiceDocNos.add(paymentScheduleDetail.getInvoicePaymentSchedule()
                                                .getInvoice().getDocumentNo());
                                    }
                                    // Create merged Payment Schedule Detail with the pending to be paid amount
                                    if (outStandingAmt.compareTo(BigDecimal.ZERO) != 0) {
                                        final FIN_PaymentScheduleDetail mergedScheduleDetail = dao
                                                .getNewPaymentScheduleDetail(payment.getOrganization(),
                                                        outStandingAmt);
                                        mergedScheduleDetail.setInvoicePaymentSchedule(
                                                paymentScheduleDetail.getInvoicePaymentSchedule());
                                        mergedScheduleDetail.setOrderPaymentSchedule(
                                                paymentScheduleDetail.getOrderPaymentSchedule());
                                        OBDal.getInstance().save(mergedScheduleDetail);
                                    }
                                } else if (paymentScheduleDetail.getOrderPaymentSchedule() != null) {
                                    // Related to orders
                                    for (final FIN_PaymentScheduleDetail ordScheDetail : paymentScheduleDetail
                                            .getOrderPaymentSchedule()
                                            .getFINPaymentScheduleDetailOrderPaymentScheduleList()) {
                                        if (ordScheDetail.isCanceled()) {
                                            continue;
                                        }
                                        if (ordScheDetail.getPaymentDetails() == null) {
                                            outStandingAmt = outStandingAmt.add(ordScheDetail.getAmount())
                                                    .add(ordScheDetail.getWriteoffAmount());
                                            removedPDS.add(ordScheDetail);
                                        } else if (ordScheDetail.equals(paymentScheduleDetail)) {
                                            outStandingAmt = outStandingAmt.add(ordScheDetail.getAmount())
                                                    .add(ordScheDetail.getWriteoffAmount());
                                            paymentScheduleDetail.setCanceled(true);
                                        }
                                    }
                                    // Create merged Payment Schedule Detail with the pending to be paid amount
                                    if (outStandingAmt.compareTo(BigDecimal.ZERO) != 0) {
                                        final FIN_PaymentScheduleDetail mergedScheduleDetail = dao
                                                .getNewPaymentScheduleDetail(payment.getOrganization(),
                                                        outStandingAmt);
                                        mergedScheduleDetail.setOrderPaymentSchedule(
                                                paymentScheduleDetail.getOrderPaymentSchedule());
                                        OBDal.getInstance().save(mergedScheduleDetail);
                                    }
                                } else if (paymentDetail.getGLItem() != null) {
                                    paymentScheduleDetail.setCanceled(true);
                                } else if (paymentScheduleDetail.getOrderPaymentSchedule() == null
                                        && paymentScheduleDetail.getInvoicePaymentSchedule() == null) {
                                    // Credit payment
                                    payment.setGeneratedCredit(payment.getGeneratedCredit()
                                            .subtract(paymentScheduleDetail.getAmount()));
                                    removedPDS.add(paymentScheduleDetail);
                                }

                                OBDal.getInstance().save(payment);
                                OBDal.getInstance().flush();
                            }
                            paymentDetail.getFINPaymentScheduleDetailList().removeAll(removedPDS);
                            for (FIN_PaymentScheduleDetail removedPD : removedPDS) {
                                if (removedPD.getOrderPaymentSchedule() != null) {
                                    removedPD.getOrderPaymentSchedule()
                                            .getFINPaymentScheduleDetailOrderPaymentScheduleList()
                                            .remove(removedPD);
                                    OBDal.getInstance().save(removedPD.getOrderPaymentSchedule());
                                }
                                if (removedPD.getInvoicePaymentSchedule() != null) {
                                    removedPD.getInvoicePaymentSchedule()
                                            .getFINPaymentScheduleDetailInvoicePaymentScheduleList()
                                            .remove(removedPD);
                                    OBDal.getInstance().save(removedPD.getInvoicePaymentSchedule());
                                }
                                OBDal.getInstance().remove(removedPD);
                            }
                            OBDal.getInstance().flush();
                            removedPDS.clear();

                        }
                        if (payment.getGeneratedCredit().compareTo(BigDecimal.ZERO) == 0
                                && payment.getUsedCredit().compareTo(BigDecimal.ZERO) == 1) {
                            undoUsedCredit(payment, invoiceDocNos);
                        }
                        payment.getFINPaymentCreditList().clear();
                        payment.setUsedCredit(BigDecimal.ZERO);
                    }
                    OBDal.getInstance().flush();
                } finally {
                    OBContext.restorePreviousMode();
                }
            }
        } catch (final Exception e) {
            log4j.error(e.getMessage());
            msg = OBMessageUtils.translateError(FIN_Utility.getExceptionMessage(e)).getMessage();
            throw new OBException(msg);
        }
    }

    /**
     * Method used to update the credit used when the user doing invoice processing or payment
     * processing
     * 
     * @param amount
     *          Payment amount
     */
    private void updateCustomerCredit(BusinessPartner businessPartner, BigDecimal amount, boolean add) {
        BigDecimal creditUsed = businessPartner.getCreditUsed();
        if (add) {
            creditUsed = creditUsed.add(amount);
        } else {
            creditUsed = creditUsed.subtract(amount);
        }
        businessPartner.setCreditUsed(creditUsed);
        OBDal.getInstance().save(businessPartner);
        // OBDal.getInstance().flush();
    }

    private void increaseCustomerCredit(BusinessPartner businessPartner, BigDecimal amount) {
        updateCustomerCredit(businessPartner, amount, true);
    }

    private void decreaseCustomerCredit(BusinessPartner businessPartner, BigDecimal amount) {
        updateCustomerCredit(businessPartner, amount, false);
    }

    private void triggerAutomaticFinancialAccountTransaction(FIN_Payment payment) throws Exception {
        FIN_FinaccTransaction transaction = TransactionsDao.createFinAccTransaction(payment);
        processTransaction("P", transaction);
    }

    private static boolean hasTransaction(FIN_Payment payment) {
        OBCriteria<FIN_FinaccTransaction> transaction = OBDal.getInstance()
                .createCriteria(FIN_FinaccTransaction.class);
        transaction.add(Restrictions.eq(FIN_FinaccTransaction.PROPERTY_FINPAYMENT, payment));
        List<FIN_FinaccTransaction> list = transaction.list();
        if (list == null || list.size() == 0)
            return false;
        return true;
    }

    private void updateUsedCredit(FIN_Payment newPayment, String selectedCreditLineIds) {
        if (newPayment.getFINPaymentCreditList().isEmpty()) {
            // We process the payment from the Payment In/Out window (not from the Process Invoice flow)
            final BigDecimal usedAmount = newPayment.getUsedCredit();
            final BusinessPartner bp = newPayment.getBusinessPartner();
            final boolean isReceipt = newPayment.isReceipt();
            final Organization Org = newPayment.getOrganization();
            List<FIN_Payment> selectedCreditPayments = null;
            if (selectedCreditLineIds != null) {
                selectedCreditPayments = FIN_Utility.getOBObjectList(FIN_Payment.class, selectedCreditLineIds);
            }
            final OBCriteria<FIN_Payment> reversepayment = OBDal.getInstance().createCriteria(FIN_Payment.class);
            reversepayment.add(Restrictions.eq(FIN_Payment.PROPERTY_REVERSEDPAYMENT, newPayment));
            final FIN_Payment reversepaymnt = (FIN_Payment) reversepayment.uniqueResult();

            List<FIN_Payment> creditPayments;
            if (reversepaymnt == null && selectedCreditLineIds == null) {
                // Normal scenario
                creditPayments = dao.getCustomerPaymentsWithCredit(Org, bp, isReceipt);
            } else if (selectedCreditLineIds != null) {
                creditPayments = selectedCreditPayments;
            } else {
                // If it is a reverse payment use its original payment
                creditPayments = new ArrayList<FIN_Payment>(1);
                creditPayments.add(reversepaymnt);
            }

            BigDecimal pendingToAllocateAmount = usedAmount;
            for (FIN_Payment creditPayment : creditPayments) {
                BigDecimal availableAmount = creditPayment.getGeneratedCredit()
                        .subtract(creditPayment.getUsedCredit());
                if (pendingToAllocateAmount.compareTo(availableAmount) == 1) {
                    creditPayment.setUsedCredit(creditPayment.getUsedCredit().add(availableAmount));
                    pendingToAllocateAmount = pendingToAllocateAmount.subtract(availableAmount);
                    linkCreditPayment(newPayment, availableAmount, creditPayment);
                    OBDal.getInstance().save(creditPayment);
                } else {
                    creditPayment.setUsedCredit(creditPayment.getUsedCredit().add(pendingToAllocateAmount));
                    linkCreditPayment(newPayment, pendingToAllocateAmount, creditPayment);
                    OBDal.getInstance().save(creditPayment);
                    break;
                }
            }
        }
    }

    public static void linkCreditPayment(FIN_Payment newPayment, BigDecimal usedAmount, FIN_Payment creditPayment) {
        final FIN_Payment_Credit creditInfo = OBProvider.getInstance().get(FIN_Payment_Credit.class);
        creditInfo.setPayment(newPayment);
        creditInfo.setAmount(usedAmount);
        creditInfo.setCurrency(newPayment.getCurrency());
        creditInfo.setCreditPaymentUsed(creditPayment);
        creditInfo.setOrganization(newPayment.getOrganization());
        creditInfo.setClient(newPayment.getClient());
        newPayment.getFINPaymentCreditList().add(creditInfo);
    }

    private void undoUsedCredit(FIN_Payment myPayment, Set<String> invoiceDocNos) {
        final List<FIN_Payment> payments = new ArrayList<FIN_Payment>();
        for (final FIN_Payment_Credit pc : myPayment.getFINPaymentCreditList()) {
            final FIN_Payment creditPaymentUsed = pc.getCreditPaymentUsed();
            creditPaymentUsed.setUsedCredit(creditPaymentUsed.getUsedCredit().subtract(pc.getAmount()));
            payments.add(creditPaymentUsed);
        }

        for (final FIN_Payment payment : payments) {
            // Update Description
            final String payDesc = payment.getDescription();
            if (payDesc != null) {
                final String invoiceDocNoMsg = OBMessageUtils.messageBD("APRM_CreditUsedinInvoice");
                if (invoiceDocNoMsg != null) {
                    final StringBuffer newDesc = new StringBuffer();
                    for (final String line : payDesc.split("\n")) {
                        boolean include = true;
                        if (line.startsWith(invoiceDocNoMsg.substring(0, invoiceDocNoMsg.lastIndexOf("%s")))) {
                            for (final String docNo : invoiceDocNos) {
                                if (line.indexOf(docNo) > 0) {
                                    include = false;
                                    break;
                                }
                            }
                        }
                        if (include) {
                            newDesc.append(line);
                            if (!"".equals(line))
                                newDesc.append("\n");
                        }
                    }
                    // Truncate Description to keep length as 255
                    payment.setDescription(newDesc.toString().length() > 255 ? newDesc.toString().substring(0, 255)
                            : newDesc.toString());
                }
            }
        }
    }

    private List<ConversionRateDoc> getConversionRateDocument(FIN_Payment payment) {
        OBContext.setAdminMode();
        try {
            OBCriteria<ConversionRateDoc> obc = OBDal.getInstance().createCriteria(ConversionRateDoc.class);
            obc.add(Restrictions.eq(ConversionRateDoc.PROPERTY_CURRENCY, payment.getCurrency()));
            obc.add(Restrictions.eq(ConversionRateDoc.PROPERTY_TOCURRENCY, payment.getAccount().getCurrency()));
            obc.add(Restrictions.eq(ConversionRateDoc.PROPERTY_PAYMENT, payment));
            return obc.list();
        } finally {
            OBContext.restorePreviousMode();
        }
    }

    private List<ConversionRateDoc> getConversionRateDocumentForInvoice(Invoice invoice) {
        OBContext.setAdminMode(true);
        try {
            OBCriteria<ConversionRateDoc> obc = OBDal.getInstance().createCriteria(ConversionRateDoc.class);
            obc.add(Restrictions.eq(ConversionRateDoc.PROPERTY_CURRENCY, invoice.getCurrency()));
            obc.add(Restrictions.eq(ConversionRateDoc.PROPERTY_TOCURRENCY,
                    invoice.getBusinessPartner().getCurrency()));
            obc.add(Restrictions.eq(ConversionRateDoc.PROPERTY_INVOICE, invoice));
            return obc.list();
        } finally {
            OBContext.restorePreviousMode();
        }
    }

    private ConversionRateDoc insertConversionRateDocument(FIN_Payment payment) {
        OBContext.setAdminMode();
        try {
            ConversionRateDoc newConversionRateDoc = OBProvider.getInstance().get(ConversionRateDoc.class);
            newConversionRateDoc.setOrganization(payment.getOrganization());
            newConversionRateDoc.setCurrency(payment.getCurrency());
            newConversionRateDoc.setToCurrency(payment.getAccount().getCurrency());
            newConversionRateDoc.setRate(payment.getFinancialTransactionConvertRate());
            newConversionRateDoc.setForeignAmount(payment.getFinancialTransactionAmount());
            newConversionRateDoc.setPayment(payment);
            newConversionRateDoc.setClient(payment.getClient());
            OBDal.getInstance().save(newConversionRateDoc);
            OBDal.getInstance().flush();
            return newConversionRateDoc;
        } finally {
            OBContext.restorePreviousMode();
        }
    }

    /**
     * It calls the Transaction Process for the given transaction and action.
     * 
     * @param strAction
     *          String with the action of the process. {P, D, R}
     * @param transaction
     *          FIN_FinaccTransaction that needs to be processed.
     * @throws OBException
     */
    private void processTransaction(String strAction, FIN_FinaccTransaction transaction) throws OBException {
        FIN_TransactionProcess.doTransactionProcess(strAction, transaction);
    }

    public boolean getDocumentConfirmation(ConnectionProvider conn, String strRecordId) {
        // Checks if this step is configured to generate accounting for the selected financial account
        boolean confirmation = false;
        OBContext.setAdminMode();
        try {
            FIN_Payment payment = OBDal.getInstance().get(FIN_Payment.class, strRecordId);
            OBCriteria<FinAccPaymentMethod> obCriteria = OBDal.getInstance()
                    .createCriteria(FinAccPaymentMethod.class);
            obCriteria.add(Restrictions.eq(FinAccPaymentMethod.PROPERTY_ACCOUNT, payment.getAccount()));
            obCriteria.add(Restrictions.eq(FinAccPaymentMethod.PROPERTY_PAYMENTMETHOD, payment.getPaymentMethod()));
            obCriteria.setFilterOnReadableClients(false);
            obCriteria.setFilterOnReadableOrganization(false);
            List<FinAccPaymentMethod> lines = obCriteria.list();
            List<FIN_FinancialAccountAccounting> accounts = payment.getAccount().getFINFinancialAccountAcctList();
            for (FIN_FinancialAccountAccounting account : accounts) {
                if (confirmation)
                    return confirmation;
                if (payment.isReceipt()) {
                    if (("INT").equals(lines.get(0).getUponReceiptUse())
                            && account.getInTransitPaymentAccountIN() != null)
                        confirmation = true;
                    else if (("DEP").equals(lines.get(0).getUponReceiptUse())
                            && account.getDepositAccount() != null)
                        confirmation = true;
                    else if (("CLE").equals(lines.get(0).getUponReceiptUse())
                            && account.getClearedPaymentAccount() != null)
                        confirmation = true;
                } else {
                    if (("INT").equals(lines.get(0).getUponPaymentUse())
                            && account.getFINOutIntransitAcct() != null)
                        confirmation = true;
                    else if (("WIT").equals(lines.get(0).getUponPaymentUse())
                            && account.getWithdrawalAccount() != null)
                        confirmation = true;
                    else if (("CLE").equals(lines.get(0).getUponPaymentUse())
                            && account.getClearedPaymentAccountOUT() != null)
                        confirmation = true;
                }
                // For payments with Amount ZERO always create an entry as no transaction will be created
                if (payment.getAmount().compareTo(ZERO) == 0) {
                    confirmation = true;
                }
            }
        } catch (Exception e) {
            // TODO no logging... ??
            return confirmation;
        } finally {
            OBContext.restorePreviousMode();
        }
        return confirmation;
    }

    private BigDecimal getConversionRate(String strOrgId, String strFromCurrencyId, String strToCurrencyId,
            Date conversionDate) {
        BigDecimal exchangeRate = BigDecimal.ZERO;
        // Apply default conversion rate
        int conversionRatePrecision = FIN_Utility
                .getConversionRatePrecision(RequestContext.get().getVariablesSecureApp());
        Organization organization = OBDal.getInstance().get(Organization.class, strOrgId);
        Currency fromCurrency = OBDal.getInstance().get(Currency.class, strFromCurrencyId);
        Currency toCurrency = OBDal.getInstance().get(Currency.class, strToCurrencyId);
        final ConversionRate conversionRate = FIN_Utility.getConversionRate(fromCurrency, toCurrency,
                conversionDate, organization);
        if (conversionRate != null) {
            exchangeRate = conversionRate.getMultipleRateBy().setScale(conversionRatePrecision,
                    RoundingMode.HALF_UP);
        } else {
            exchangeRate = BigDecimal.ZERO;
        }
        return exchangeRate;
    }

    /**
     * Removes the Payment Proposal Lines linked to the Payment Schedule Detail. If the payment
     * proposal is already processed, the record is unprocessed first, then affected lines are
     * deleted, and finally it gets back to processed status. This is done to avoid trigger validation
     * errors.
     * 
     * A common scenario where you want to delete a payment proposal line is when the payment schedule
     * detail has been deleted
     * 
     * @param psd
     *          Payment Schedule Detail
     * @return number of Payment Proposal Lines removed
     */
    public static int removePaymentProposalLines(FIN_PaymentScheduleDetail psd) {
        int proposalLinesRemoved = 0;
        for (FIN_PaymentPropDetail ppd : psd.getFINPaymentPropDetailList()) {
            final FIN_PaymentProposal paymentProposal = ppd.getFinPaymentProposal();
            if (paymentProposal.isProcessed()) {
                // Hack to delete Payment Proposal lines when you reactivate a payment linked to
                // a processed payment proposal.
                paymentProposal.setProcessed(false);
                OBDal.getInstance().save(paymentProposal);
                OBDal.getInstance().remove(ppd);
                OBDal.getInstance().flush();
                paymentProposal.setProcessed(true);
            } else {
                OBDal.getInstance().remove(ppd);
            }
            proposalLinesRemoved++;
        }
        return proposalLinesRemoved;
    }

}