/** * 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; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.joda.time.LocalDate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.axelor.apps.account.db.Invoice; import com.axelor.apps.account.db.MoveLine; import com.axelor.apps.account.db.PaymentMode; import com.axelor.apps.account.db.PaymentSchedule; import com.axelor.apps.account.db.PaymentScheduleLine; import com.axelor.apps.account.db.repo.PaymentScheduleLineRepository; import com.axelor.apps.account.db.repo.PaymentScheduleRepository; import com.axelor.apps.account.exception.IExceptionMessage; import com.axelor.apps.base.db.BankDetails; import com.axelor.apps.base.db.Company; import com.axelor.apps.base.db.IAdministration; import com.axelor.apps.base.db.Partner; import com.axelor.apps.base.service.PartnerService; import com.axelor.apps.base.service.administration.GeneralService; import com.axelor.apps.base.service.administration.GeneralServiceImpl; import com.axelor.apps.base.service.administration.SequenceService; import com.axelor.exception.AxelorException; import com.axelor.exception.db.IException; import com.axelor.i18n.I18n; import com.google.inject.Inject; import com.google.inject.persist.Transactional; public class PaymentScheduleService { private final Logger log = LoggerFactory.getLogger( getClass() ); protected PaymentScheduleLineService paymentScheduleLineService; protected PaymentScheduleLineRepository paymentScheduleLineRepo; protected SequenceService sequenceService; protected PaymentScheduleRepository paymentScheduleRepo; protected PartnerService partnerService; protected LocalDate date; @Inject public PaymentScheduleService(GeneralService generalService, PaymentScheduleLineService paymentScheduleLineService, PaymentScheduleLineRepository paymentScheduleLineRepo, SequenceService sequenceService, PaymentScheduleRepository paymentScheduleRepo, PartnerService partnerService) { this.paymentScheduleLineService = paymentScheduleLineService; this.paymentScheduleLineRepo = paymentScheduleLineRepo; this.sequenceService = sequenceService; this.paymentScheduleRepo = paymentScheduleRepo; this.partnerService = partnerService; date = generalService.getTodayDate(); } /** * Création d'un échéancier sans ces lignes. * * @param partner * Le tiers. * @param invoices * Collection de factures. * @param company * La société. * @param startDate * Date de première échéance. * @param nbrTerm * Nombre d'échéances. * * @return * L'échéancier créé. * @throws AxelorException */ public PaymentSchedule createPaymentSchedule(Partner partner, Company company, Set<Invoice> invoices, LocalDate startDate, int nbrTerm) throws AxelorException{ Invoice invoice = null; PaymentSchedule paymentSchedule = this.createPaymentSchedule(partner, invoice, company, date, startDate, nbrTerm, partnerService.getDefaultBankDetails(partner), partner.getPaymentMode()); paymentSchedule.getInvoiceSet().addAll(invoices); return paymentSchedule; } /** * Création d'un échéancier sans ces lignes. * * @param partner * Le tiers. * @param invoice * Facture globale permettant de définir la facture pour une échéance. * L'échéancier est automatiquement associé à la facture si celle-ci existe. * @param company * La société. * @param date * Date de création. * @param startDate * Date de première échéance. * @param nbrTerm * Nombre d'échéances. * @param bankDetails * RIB. * @param paymentMode * Mode de paiement. * @param payerPartner * Tiers payeur. * @param type * Type de l'échéancier. * <code>0 = paiement</code> * <code>1 = mensu masse</code> * <code>2 = mensu grand-compte</code> * * @return * L'échéancier créé. * @throws AxelorException */ public PaymentSchedule createPaymentSchedule(Partner partner, Invoice invoice, Company company, LocalDate date, LocalDate startDate, int nbrTerm, BankDetails bankDetails, PaymentMode paymentMode) throws AxelorException{ PaymentSchedule paymentSchedule = new PaymentSchedule(); paymentSchedule.setCompany(company); paymentSchedule.setScheduleId(this.getPaymentScheduleSequence(company)); paymentSchedule.setCreationDate(date); paymentSchedule.setStartDate(startDate); paymentSchedule.setNbrTerm(nbrTerm); paymentSchedule.setBankDetails(bankDetails); paymentSchedule.setPaymentMode(paymentMode); paymentSchedule.setPartner(partner); if (paymentSchedule.getInvoiceSet() == null) { paymentSchedule.setInvoiceSet(new HashSet<Invoice>()); } else { paymentSchedule.getInvoiceSet().clear(); } if (invoice != null){ paymentSchedule.setInvoice(invoice); invoice.setPaymentSchedule(paymentSchedule); } return paymentSchedule; } /** * Fonction permettant de tester et de récupérer une séquence de prélèvement * @param company * Une société * @param journal * Un journal * @return * @throws AxelorException */ public String getPaymentScheduleSequence(Company company) throws AxelorException { String seq = sequenceService.getSequenceNumber(IAdministration.PAYMENT_SCHEDULE, company); if(seq == null) { throw new AxelorException(String.format("%s :\n"+ I18n.get(IExceptionMessage.PAYMENT_SCHEDULE_5)+" %s", GeneralServiceImpl.EXCEPTION,company.getName()), IException.CONFIGURATION_ERROR); } return seq; } /** * Obtenir le total des factures des lignes d'un échéancier. * * @param paymentSchedule * L'échéancier cible. * * @return * Le somme des montants TTC des lignes de l'échéancier. */ public BigDecimal getInvoiceTermTotal(PaymentSchedule paymentSchedule){ BigDecimal totalAmount = BigDecimal.ZERO; if (paymentSchedule != null && paymentSchedule.getPaymentScheduleLineList() != null && !paymentSchedule.getPaymentScheduleLineList().isEmpty()) { for (PaymentScheduleLine paymentScheduleLine : paymentSchedule.getPaymentScheduleLineList()){ if (paymentScheduleLine.getInTaxAmount() != null) { log.debug("Somme TTC des lignes de l'échéancier {} : total = {}, ajout = {}", new Object[] {paymentSchedule.getScheduleId(), totalAmount, paymentScheduleLine.getInTaxAmount()}); totalAmount = totalAmount.add(paymentScheduleLine.getInTaxAmount()); } } } log.debug("Obtention de la somme TTC des lignes de l'échéancier {} : {}", new Object[] {paymentSchedule.getScheduleId(), totalAmount}); return totalAmount; } /** * Mise à jour d'un échéancier avec un nouveau montant d'échéance. * * @param paymentSchedule * L'échéancier cible. * @param inTaxTotal * Nouveau montant d'une échéance. */ @Transactional public void updatePaymentSchedule(PaymentSchedule paymentSchedule, BigDecimal inTaxTotal){ log.debug("Mise à jour de l'échéancier {} : {}", new Object[] {paymentSchedule.getScheduleId(), inTaxTotal}); for (PaymentScheduleLine paymentScheduleLine : paymentSchedule.getPaymentScheduleLineList()){ if (paymentScheduleLine.getStatusSelect() == PaymentScheduleLineRepository.STATUS_IN_PROGRESS && !paymentScheduleLine.getRejectedOk()) { log.debug("Mise à jour de la ligne {} ", paymentScheduleLine.getName()); paymentScheduleLine.setInTaxAmount(inTaxTotal); } } paymentScheduleRepo.save(paymentSchedule); } /** * Création d'un échéancier avec ces lignes. * * @param company * La société. * @param date * Date de création. * @param firstTermDate * Date de première échéance. * @param initialInTaxAmount * Montant d'une échéance. * @param nbrTerm * Nombre d'échéances. * @param bankDetails * RIB. * @param paymentMode * Mode de paiement. * @param payerPartner * Tiers payeur. * * @return * L'échéancier créé. * @throws AxelorException */ public PaymentSchedule createPaymentSchedule(Partner partner, Company company, LocalDate date, LocalDate firstTermDate, BigDecimal initialInTaxAmount, int nbrTerm, BankDetails bankDetails, PaymentMode paymentMode) throws AxelorException{ Invoice invoice = null; PaymentSchedule paymentSchedule = this.createPaymentSchedule(partner, invoice, company, date, firstTermDate, nbrTerm, bankDetails, paymentMode); paymentSchedule.setPaymentScheduleLineList(new ArrayList<PaymentScheduleLine>()); for (int term = 1; term < nbrTerm + 1; term++){ paymentSchedule.getPaymentScheduleLineList().add(paymentScheduleLineService.createPaymentScheduleLine(paymentSchedule, initialInTaxAmount, term, firstTermDate.plusMonths(term-1))); } return paymentSchedule; } /** * This method is used to get the movelines to be paid based on a paymentSchedule * It loops on the invoice M2M content and gets the movelines which are to pay * @param ps * @return */ public List<MoveLine> getPaymentSchedulerMoveLineToPay(PaymentSchedule paymentSchedule){ log.debug("In getPaymentSchedulerMoveLineToPay ...."); List<MoveLine> moveLines = new ArrayList<MoveLine>(); for (Invoice invoice : paymentSchedule.getInvoiceSet()) { if (invoice.getCompanyInTaxTotalRemaining().compareTo(BigDecimal.ZERO) > 0 && invoice.getMove() != null && invoice.getMove().getMoveLineList() != null) { for (MoveLine moveLine : invoice.getMove().getMoveLineList()){ if (moveLine.getAccount().getReconcileOk() && moveLine.getAmountRemaining().compareTo(BigDecimal.ZERO) > 0 && moveLine.getDebit().compareTo(BigDecimal.ZERO) > 0){ moveLines.add(moveLine); } } } } log.debug("End getPaymentSchedulerMoveLineToPay."); return moveLines; } /** * Permet de valider un échéancier. * * @param paymentSchedule * @throws AxelorException */ @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public void validatePaymentSchedule(PaymentSchedule paymentSchedule) throws AxelorException { log.debug("Validation de l'échéancier {}", paymentSchedule.getScheduleId()); if(paymentSchedule.getPaymentScheduleLineList() == null || paymentSchedule.getPaymentScheduleLineList().size() == 0) { throw new AxelorException(String.format(I18n.get(IExceptionMessage.PAYMENT_SCHEDULE_6), GeneralServiceImpl.EXCEPTION, paymentSchedule.getScheduleId()), IException.INCONSISTENCY); } // this.updateInvoices(paymentSchedule); //TODO paymentSchedule.setStatusSelect(PaymentScheduleRepository.STATUS_CONFIRMED); paymentScheduleRepo.save(paymentSchedule); } public void updateInvoices(PaymentSchedule paymentSchedule) { if (paymentSchedule.getInvoiceSet() != null){ List<MoveLine> moveLineInvoiceToPay = this.getPaymentSchedulerMoveLineToPay(paymentSchedule); for (MoveLine moveLineInvoice : moveLineInvoiceToPay){ moveLineInvoice.getMove().setIgnoreInReminderOk(true); this.updateInvoice(moveLineInvoice.getMove().getInvoice(), paymentSchedule); } } } public void updateInvoice(Invoice invoice, PaymentSchedule paymentSchedule) { invoice.setSchedulePaymentOk(true); invoice.setPaymentSchedule(paymentSchedule); } /** * Methode qui annule un échéancier * * @param paymentSchedule */ public void cancelPaymentSchedule(PaymentSchedule paymentSchedule){ // L'échéancier est passé à annulé paymentSchedule.setStatusSelect(PaymentScheduleRepository.STATUS_CANCELED); for(PaymentScheduleLine paymentScheduleLine : paymentSchedule.getPaymentScheduleLineList()) { // Si l'échéance n'est pas complètement payée if(paymentScheduleLine.getInTaxAmountPaid().compareTo(paymentScheduleLine.getInTaxAmount()) != 0 ) { // L'échéance est passée à cloturé paymentScheduleLine.setStatusSelect(PaymentScheduleLineRepository.STATUS_CLOSED); } } for(Invoice invoice : paymentSchedule.getInvoiceSet()) { // L'échéancier n'est plus selectionné sur la facture invoice.setPaymentSchedule(null); // L'échéancier est assigné dans un nouveau champs afin de garder un lien invisble pour l'utilisateur, mais utilisé pour le passage en irrécouvrable invoice.setCanceledPaymentSchedule(paymentSchedule); invoice.setSchedulePaymentOk(false); } } /** * Methode permettant de savoir si l'échéance passée en paramètre est la dernière de l'échéancier * @param paymentScheduleLine * @return */ public boolean isLastSchedule(PaymentScheduleLine paymentScheduleLine) { if(paymentScheduleLine != null) { if(paymentScheduleLineRepo.all().filter("self.paymentSchedule = ?1 and self.scheduleDate > ?2 and self.statusSelect = ?3", paymentScheduleLine.getPaymentSchedule(), paymentScheduleLine.getScheduleDate(), PaymentScheduleLineRepository.STATUS_IN_PROGRESS).fetchOne() == null) { log.debug("Dernière échéance"); return true; } else { return false; } } else { return false; } } /** * Méthode permettant de passer les statuts des lignes d'échéances et de l'échéancier à 'clo' ie cloturé * @param invoice * Une facture de fin de cycle * @throws AxelorException */ public void closePaymentSchedule(PaymentSchedule paymentSchedule) throws AxelorException { log.debug("Cloture de l'échéancier"); //On récupère un statut cloturé, afin de pouvoir changer l'état des lignes d'échéanciers for(PaymentScheduleLine paymentScheduleLine : paymentSchedule.getPaymentScheduleLineList()) { paymentScheduleLine.setStatusSelect(PaymentScheduleLineRepository.STATUS_CLOSED); } paymentSchedule.setStatusSelect(PaymentScheduleRepository.STATUS_CLOSED); } public LocalDate getMostOldDatePaymentScheduleLine(List<PaymentScheduleLine> paymentScheduleLineList) { LocalDate minPaymentScheduleLineDate = new LocalDate(); if(paymentScheduleLineList != null && !paymentScheduleLineList.isEmpty()) { for(PaymentScheduleLine paymentScheduleLine : paymentScheduleLineList) { if(minPaymentScheduleLineDate.isAfter(paymentScheduleLine.getScheduleDate())) { minPaymentScheduleLineDate=paymentScheduleLine.getScheduleDate(); } } } else { minPaymentScheduleLineDate=null; } return minPaymentScheduleLineDate; } public LocalDate getMostRecentDatePaymentScheduleLine(List<PaymentScheduleLine> paymentScheduleLineList) { LocalDate minPaymentScheduleLineDate = new LocalDate(); if(paymentScheduleLineList != null && !paymentScheduleLineList.isEmpty()) { for(PaymentScheduleLine paymentScheduleLine : paymentScheduleLineList) { if(minPaymentScheduleLineDate.isBefore(paymentScheduleLine.getScheduleDate())) { minPaymentScheduleLineDate=paymentScheduleLine.getScheduleDate(); } } } else { minPaymentScheduleLineDate=null; } return minPaymentScheduleLineDate; } // Transactional /** * Créer des lignes d'échéancier à partir des lignes de factures de celui-ci. * * @param paymentSchedule * @throws AxelorException */ @Transactional public void createPaymentScheduleLines(PaymentSchedule paymentSchedule){ this.initCollection(paymentSchedule); paymentSchedule.getPaymentScheduleLineList().addAll(paymentScheduleLineService.createPaymentScheduleLines(paymentSchedule)); paymentScheduleRepo.save(paymentSchedule); } public void initCollection(PaymentSchedule paymentSchedule) { if (paymentSchedule.getPaymentScheduleLineList() == null) { paymentSchedule.setPaymentScheduleLineList(new ArrayList<PaymentScheduleLine>()); } else { paymentSchedule.getPaymentScheduleLineList().clear(); } } @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public void toCancelPaymentSchedule(PaymentSchedule paymentSchedule){ this.cancelPaymentSchedule(paymentSchedule); paymentScheduleRepo.save(paymentSchedule); } }