/** * Axelor Business Solutions * * Copyright (C) 2016 Axelor (<http://axelor.com>). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.axelor.apps.account.service.payment.paymentvoucher; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import org.joda.time.LocalDate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.axelor.apps.account.db.Account; import com.axelor.apps.account.db.Journal; import com.axelor.apps.account.db.Move; import com.axelor.apps.account.db.MoveLine; import com.axelor.apps.account.db.PaymentInvoiceToPay; import com.axelor.apps.account.db.PaymentMode; import com.axelor.apps.account.db.PaymentVoucher; import com.axelor.apps.account.db.Reconcile; import com.axelor.apps.account.db.repo.PaymentInvoiceToPayRepository; import com.axelor.apps.account.db.repo.PaymentVoucherRepository; import com.axelor.apps.account.exception.IExceptionMessage; import com.axelor.apps.account.service.AccountCustomerService; import com.axelor.apps.account.service.ReconcileService; import com.axelor.apps.account.service.move.MoveLineService; import com.axelor.apps.account.service.move.MoveService; import com.axelor.apps.account.service.payment.PaymentModeService; import com.axelor.apps.account.service.payment.PaymentService; import com.axelor.apps.base.db.Company; import com.axelor.apps.base.db.Currency; import com.axelor.apps.base.db.IAdministration; import com.axelor.apps.base.db.Partner; import com.axelor.apps.base.service.CurrencyService; import com.axelor.apps.base.service.administration.GeneralServiceImpl; import com.axelor.exception.AxelorException; import com.axelor.exception.db.IException; import com.axelor.i18n.I18n; import com.axelor.inject.Beans; import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.persist.Transactional; public class PaymentVoucherConfirmService { private final Logger log = LoggerFactory.getLogger( getClass() ); protected ReconcileService reconcileService; protected MoveLineService moveLineService; protected MoveService moveService; protected PaymentService paymentService; protected PaymentModeService paymentModeService; protected PaymentVoucherSequenceService paymentVoucherSequenceService; protected PaymentVoucherControlService paymentVoucherControlService; protected PaymentVoucherToolService paymentVoucherToolService; protected CurrencyService currencyService; protected PaymentInvoiceToPayRepository paymentInvoiceToPayRepo; protected PaymentVoucherRepository paymentVoucherRepository; @Inject public PaymentVoucherConfirmService(ReconcileService reconcileService, MoveLineService moveLineService, MoveService moveService, PaymentService paymentService, PaymentModeService paymentModeService, PaymentVoucherSequenceService paymentVoucherSequenceService, PaymentVoucherControlService paymentVoucherControlService, PaymentVoucherToolService paymentVoucherToolService, CurrencyService currencyService, PaymentInvoiceToPayRepository paymentInvoiceToPayRepo, PaymentVoucherRepository paymentVoucherRepository) { this.reconcileService = reconcileService; this.moveLineService = moveLineService; this.moveService = moveService; this.paymentService = paymentService; this.paymentModeService = paymentModeService; this.paymentVoucherSequenceService = paymentVoucherSequenceService; this.paymentVoucherControlService = paymentVoucherControlService; this.paymentVoucherToolService = paymentVoucherToolService; this.currencyService = currencyService; this.paymentInvoiceToPayRepo = paymentInvoiceToPayRepo; this.paymentVoucherRepository = paymentVoucherRepository; } /** * Confirms the payment voucher * if the selected lines PiToPay 2nd O2M belongs to different companies -> error * I - Payment with an amount * If we pay a classical moveLine (invoice, reject ..) -> just create a payment * If we pay a schedule 2 payments are created 1st reconciled with the invoice and the second reconciled with the schedule * II - Payment with an excess Payment * If we pay a moveLine having the same account, we just reconcile * If we pay a with different account -> 1- switch money to the good account 2- reconcile then * @param paymentVoucher */ @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public void confirmPaymentVoucher(PaymentVoucher paymentVoucher) throws AxelorException { log.debug("In confirmPaymentVoucherService ...."); paymentVoucherSequenceService.setReference(paymentVoucher); Partner payerPartner = paymentVoucher.getPartner(); PaymentMode paymentMode = paymentVoucher.getPaymentMode(); Company company = paymentVoucher.getCompany(); Journal journal = paymentModeService.getPaymentModeJournal(paymentMode, company); LocalDate paymentDate = paymentVoucher.getPaymentDate(); boolean scheduleToBePaid = false; Account paymentModeAccount = paymentModeService.getPaymentModeAccount(paymentMode, company); paymentVoucherControlService.checkPaymentVoucherField(paymentVoucher, company, paymentModeAccount, journal); if(paymentVoucher.getRemainingAmount().compareTo(BigDecimal.ZERO) > 0 && !journal.getExcessPaymentOk()) { throw new AxelorException(String.format(I18n.get(IExceptionMessage.PAYBOX_3), GeneralServiceImpl.EXCEPTION), IException.INCONSISTENCY); } if(paymentVoucher.getPayboxPaidOk()) { paymentVoucherControlService.checkPayboxAmount(paymentVoucher); } // TODO VEIRIFER QUE LES ELEMENTS A PAYER NE CONCERNE QU'UNE SEULE DEVISE // TODO RECUPERER DEVISE DE LA PREMIERE DETTE // Currency currencyToPay = null; // If paid by a moveline check if all the lines selected have the same account + company // Excess payment boolean allRight = paymentVoucherControlService.checkIfSameAccount(paymentVoucher.getPaymentInvoiceToPayList(), paymentVoucher.getMoveLine()); //Check if allright=true (means companies and accounts in lines are all the same and same as in move line selected for paying log.debug("allRight : {}", allRight); if (allRight){ scheduleToBePaid = this.toPayWithExcessPayment(paymentVoucher.getPaymentInvoiceToPayList(), paymentVoucher.getMoveLine(), scheduleToBePaid, paymentDate); } if(paymentVoucher.getMoveLine() == null || (paymentVoucher.getMoveLine() != null && !allRight) || (scheduleToBePaid && !allRight && paymentVoucher.getMoveLine() != null)) { //Manage all the cases in the same way. As if a move line (Excess payment) is selected, we cancel it first Move move = moveService.getMoveCreateService().createMove(journal, company, null, payerPartner, paymentDate, paymentMode, paymentVoucher.getCashRegister()); move.setPaymentVoucher(paymentVoucher); paymentVoucher.setGeneratedMove(move); // Create move lines for payment lines BigDecimal paidLineTotal = BigDecimal.ZERO; int moveLineNo=1; boolean isDebitToPay = paymentVoucherToolService.isDebitToPay(paymentVoucher); for (PaymentInvoiceToPay paymentInvoiceToPay : this.getPaymentInvoiceToPayList(paymentVoucher)) { MoveLine moveLineToPay = paymentInvoiceToPay.getMoveLine(); log.debug("PV moveLineToPay debit : {}", moveLineToPay.getDebit()); log.debug("PV moveLineToPay amountPaid : {}", moveLineToPay.getAmountPaid()); // BigDecimal amountToPay = paymentInvoiceToPay.getAmountToPay(); BigDecimal amountToPay = this.getAmountCurrencyConverted(moveLineToPay, paymentVoucher, paymentInvoiceToPay.getAmountToPay()); if (amountToPay.compareTo(BigDecimal.ZERO) > 0) { paidLineTotal = paidLineTotal.add(amountToPay); this.payMoveLine(move, moveLineNo, payerPartner, moveLineToPay, amountToPay, paymentInvoiceToPay, isDebitToPay, paymentDate); moveLineNo +=1; } } // Create move line for the payment amount MoveLine moveLine = null; // cancelling the moveLine (excess payment) by creating the balance of all the payments // on the same account as the moveLine (excess payment) // in the else case we create a classical balance on the bank account of the payment mode if (paymentVoucher.getMoveLine() != null){ moveLine = moveLineService.createMoveLine(move,paymentVoucher.getPartner(),paymentVoucher.getMoveLine().getAccount(), paymentVoucher.getPaidAmount(), isDebitToPay, paymentDate, moveLineNo, null); Reconcile reconcile = reconcileService.createReconcile(moveLine,paymentVoucher.getMoveLine(),moveLine.getDebit(), !isDebitToPay); reconcileService.confirmReconcile(reconcile); } else{ moveLine = moveLineService.createMoveLine(move, payerPartner, paymentModeAccount, paymentVoucher.getPaidAmount(), isDebitToPay, paymentDate, moveLineNo, null); } move.getMoveLineList().add(moveLine); // Check if the paid amount is > paid lines total // Then Use Excess payment on old invoices / moveLines if (paymentVoucher.getPaidAmount().compareTo(paidLineTotal) > 0){ BigDecimal remainingPaidAmount = paymentVoucher.getRemainingAmount(); //TODO rajouter le process d'imputation automatique // if(paymentVoucher.getHasAutoInput()) { // // List<MoveLine> debitMoveLines = Lists.newArrayList(pas.getDebitLinesToPay(contractLine, paymentVoucher.getPaymentScheduleToPay())); // pas.createExcessPaymentWithAmount(debitMoveLines, remainingPaidAmount, move, moveLineNo, // paymentVoucher.getPayerPartner(), company, contractLine, null, paymentDate, updateCustomerAccount); // } // else { Account partnerAccount = Beans.get(AccountCustomerService.class).getPartnerAccount(payerPartner, company, paymentVoucherToolService.isPurchase(paymentVoucher)); moveLine = moveLineService.createMoveLine(move,paymentVoucher.getPartner(), partnerAccount, remainingPaidAmount,!isDebitToPay, paymentDate, moveLineNo++, null); move.getMoveLineList().add(moveLine); if(isDebitToPay) { reconcileService.balanceCredit(moveLine); } } moveService.getMoveValidateService().validateMove(move); paymentVoucher.setGeneratedMove(move); } paymentVoucher.setStatusSelect(PaymentVoucherRepository.STATUS_CONFIRMED); paymentVoucherSequenceService.setReceiptNo(paymentVoucher, company, journal); this.deleteUnPaidLines(paymentVoucher); paymentVoucherRepository.save(paymentVoucher); } public void deleteUnPaidLines(PaymentVoucher paymentVoucher) { if(paymentVoucher.getPaymentInvoiceList() == null) { return; } paymentVoucher.getPaymentInvoiceList().clear(); List<PaymentInvoiceToPay> paymentInvoiceToPayToRemove = Lists.newArrayList(); for(PaymentInvoiceToPay paymentInvoiceToPay : paymentVoucher.getPaymentInvoiceToPayList()) { if(paymentInvoiceToPay.getAmountToPay().compareTo(BigDecimal.ZERO) == 0 && paymentInvoiceToPay.getMoveLineGenerated() == null) { paymentInvoiceToPayToRemove.add(paymentInvoiceToPay); } } paymentVoucher.getPaymentInvoiceToPayList().removeAll(paymentInvoiceToPayToRemove); } /** * Récupérer les éléments à payer dans le bon ordre * @return */ public List<? extends PaymentInvoiceToPay> getPaymentInvoiceToPayList(PaymentVoucher paymentVoucher) { return paymentInvoiceToPayRepo.all().filter("self.paymentVoucher = ?1 ORDER by self.sequence ASC", paymentVoucher).fetch(); } /** * If paid by a moveline check if all the lines selected have the same account + company * Excess payment * Check if allright=true (means companies and accounts in lines are all the same and same as in move line selected for paying * @param paymentInvoiceToPayList * Liste des paiement a réaliser * @param creditMoveLine * Le trop-perçu * @param scheduleToBePaid * @return * Une échéance doit-elle être payée? * @throws AxelorException */ public boolean toPayWithExcessPayment(List<PaymentInvoiceToPay> paymentInvoiceToPayList, MoveLine creditMoveLine, boolean scheduleToBePaid, LocalDate paymentDate) throws AxelorException { boolean scheduleToBePaid2 = scheduleToBePaid; List<MoveLine> debitMoveLines = new ArrayList<MoveLine>(); for (PaymentInvoiceToPay paymentInvoiceToPay : paymentInvoiceToPayList) { debitMoveLines.add(paymentInvoiceToPay.getMoveLine()); } List<MoveLine> creditMoveLines = new ArrayList<MoveLine>(); creditMoveLines.add(creditMoveLine); paymentService.useExcessPaymentOnMoveLines(debitMoveLines, creditMoveLines); return scheduleToBePaid2; } /** * * @param paymentMove * @param moveLineSeq * @param payerPartner * @param moveLineToPay * @param amountToPay * @param paymentInvoiceToPay * @return * @throws AxelorException */ public MoveLine payMoveLine(Move paymentMove, int moveLineSeq, Partner payerPartner, MoveLine moveLineToPay, BigDecimal amountToPay, PaymentInvoiceToPay paymentInvoiceToPay, boolean isDebitToPay, LocalDate paymentDate) throws AxelorException { String invoiceName = ""; if(moveLineToPay.getMove().getInvoice()!=null) { invoiceName = moveLineToPay.getMove().getInvoice().getInvoiceId(); } else { invoiceName = paymentInvoiceToPay.getPaymentVoucher().getRef(); } MoveLine moveLine = moveLineService.createMoveLine( paymentMove, payerPartner, moveLineToPay.getAccount(), amountToPay, !isDebitToPay, paymentDate, moveLineSeq, invoiceName); paymentMove.addMoveLineListItem(moveLine); paymentInvoiceToPay.setMoveLineGenerated(moveLine); // Should be replaced per InvoicePayment Reconcile reconcile = reconcileService.createReconcile(moveLineToPay, moveLine, amountToPay, true); log.debug("Reconcile : : : {}", reconcile); reconcileService.confirmReconcile(reconcile); return moveLine; } public BigDecimal getAmountCurrencyConverted(MoveLine moveLineToPay, PaymentVoucher paymentVoucher, BigDecimal amountToPay) throws AxelorException { Currency moveCurrency = moveLineToPay.getMove().getCurrency(); Currency paymentVoucherCurrency = paymentVoucher.getCurrency(); LocalDate paymentVoucherDate = paymentVoucher.getPaymentDate(); return currencyService.getAmountCurrencyConverted(paymentVoucherCurrency, moveCurrency, amountToPay, paymentVoucherDate).setScale(IAdministration.DEFAULT_NB_DECIMAL_DIGITS, RoundingMode.HALF_UP); } }