/**
* 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.math.RoundingMode;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.axelor.apps.account.db.AccountConfig;
import com.axelor.apps.account.db.Invoice;
import com.axelor.apps.account.db.MoveLine;
import com.axelor.apps.account.db.Reconcile;
import com.axelor.apps.account.db.repo.ReconcileRepository;
import com.axelor.apps.account.exception.IExceptionMessage;
import com.axelor.apps.account.service.config.AccountConfigService;
import com.axelor.apps.account.service.move.MoveAdjustementService;
import com.axelor.apps.account.service.move.MoveToolService;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.Partner;
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.beust.jcommander.internal.Lists;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
public class ReconcileServiceImpl implements ReconcileService {
private final Logger log = LoggerFactory.getLogger( getClass() );
protected MoveToolService moveToolService;
protected AccountCustomerService accountCustomerService;
protected AccountConfigService accountConfigService;
protected ReconcileRepository reconcileRepository;
protected MoveAdjustementService moveAdjustementService;
protected ReconcileSequenceService reconcileSequenceService;
@Inject
public ReconcileServiceImpl(MoveToolService moveToolService, AccountCustomerService accountCustomerService, AccountConfigService accountConfigService,
ReconcileRepository reconcileRepository, MoveAdjustementService moveAdjustementService, ReconcileSequenceService reconcileSequenceService) {
this.moveToolService = moveToolService;
this.accountCustomerService = accountCustomerService;
this.accountConfigService = accountConfigService;
this.reconcileRepository = reconcileRepository;
this.moveAdjustementService = moveAdjustementService;
this.reconcileSequenceService = reconcileSequenceService;
}
/**
* Permet de créer une réconciliation en passant les paramètres qu'il faut
* @param lineDebit
* Une ligne d'écriture au débit
* @param lineCredit
* Une ligne d'écriture au crédit
* @param amount
* Le montant à reconciler
* @param canBeZeroBalanceOk
* Peut être soldé?
* @return
* Une reconciliation
*/
@Transactional(rollbackOn = {AxelorException.class, Exception.class})
public Reconcile createReconcile(MoveLine debitMoveLine, MoveLine creditMoveLine, BigDecimal amount, boolean canBeZeroBalanceOk) {
log.debug("Create Reconcile (Debit MoveLine : {}, Credit MoveLine : {}, Amount : {}, Can be zero balance ? {} ",
new Object[]{debitMoveLine.getName(), creditMoveLine.getName(), amount, canBeZeroBalanceOk});
Reconcile reconcile = new Reconcile(
amount.setScale(2, RoundingMode.HALF_EVEN),
debitMoveLine, creditMoveLine,
ReconcileRepository.STATUS_DRAFT,
canBeZeroBalanceOk);
if(!moveToolService.isDebitMoveLine(debitMoveLine)) {
reconcile.setDebitMoveLine(creditMoveLine);
reconcile.setCreditMoveLine(debitMoveLine);
}
return reconcileRepository.save(reconcile);
}
/**
* Permet de confirmer une réconciliation
* On ne peut réconcilier que des moveLine ayant le même compte
* @param reconcile
* Une reconciliation
* @return
* L'etat de la reconciliation
* @throws AxelorException
*/
@Transactional(rollbackOn = {AxelorException.class, Exception.class})
public int confirmReconcile(Reconcile reconcile) throws AxelorException {
this.reconcilePreconditions(reconcile);
MoveLine debitMoveLine = reconcile.getDebitMoveLine();
MoveLine creditMoveLine = reconcile.getCreditMoveLine();
//Add the reconciled amount to the reconciled amount in the move line
creditMoveLine.setAmountPaid(creditMoveLine.getAmountPaid().add(reconcile.getAmount()));
debitMoveLine.setAmountPaid(debitMoveLine.getAmountPaid().add(reconcile.getAmount()));
this.updatePartnerAccountingSituation(reconcile);
this.updateInvoiceRemainingAmount(reconcile);
reconcile.setStatusSelect(ReconcileRepository.STATUS_CONFIRMED);
if(reconcile.getCanBeZeroBalanceOk()) {
// Alors nous utilisons la règle de gestion consitant à imputer l'écart sur un compte transitoire si le seuil est respecté
canBeZeroBalance(reconcile);
}
reconcileSequenceService.setSequence(reconcile);
reconcileRepository.save(reconcile);
return reconcile.getStatusSelect();
}
public void reconcilePreconditions(Reconcile reconcile) throws AxelorException {
MoveLine debitMoveLine = reconcile.getDebitMoveLine();
MoveLine creditMoveLine = reconcile.getCreditMoveLine();
if (debitMoveLine == null || creditMoveLine == null) {
throw new AxelorException(String.format(I18n.get(IExceptionMessage.RECONCILE_1),
GeneralServiceImpl.EXCEPTION), IException.CONFIGURATION_ERROR);
}
// Check if move lines accounts are the same (debit and credit)
if (!creditMoveLine.getAccount().equals(debitMoveLine.getAccount())){
log.debug("Compte ligne de credit : {} , Compte ligne de debit : {}", creditMoveLine.getAccount(), debitMoveLine.getAccount());
throw new AxelorException(String.format(I18n.get(IExceptionMessage.RECONCILE_2)+" " +
I18n.get(IExceptionMessage.RECONCILE_3),
GeneralServiceImpl.EXCEPTION, debitMoveLine.getName(), debitMoveLine.getAccount().getLabel(),
creditMoveLine.getName(), creditMoveLine.getAccount().getLabel()), IException.CONFIGURATION_ERROR);
}
// Check if the amount to reconcile is != zero
if (reconcile.getAmount() == null || reconcile.getAmount().compareTo(BigDecimal.ZERO) == 0) {
throw new AxelorException(String.format(I18n.get(IExceptionMessage.RECONCILE_4),
GeneralServiceImpl.EXCEPTION, reconcile.getReconcileSeq(), debitMoveLine.getName(), debitMoveLine.getAccount().getLabel(),
creditMoveLine.getName(), creditMoveLine.getAccount().getLabel()), IException.INCONSISTENCY);
}
if ((reconcile.getAmount().compareTo(creditMoveLine.getCredit().subtract(creditMoveLine.getAmountPaid())) > 0
|| (reconcile.getAmount().compareTo(debitMoveLine.getDebit().subtract(debitMoveLine.getAmountPaid())) > 0))){
throw new AxelorException(
String.format(I18n.get(IExceptionMessage.RECONCILE_5)+" " +
I18n.get(IExceptionMessage.RECONCILE_3),
GeneralServiceImpl.EXCEPTION, reconcile.getReconcileSeq(), debitMoveLine.getName(), debitMoveLine.getAccount().getLabel(),
creditMoveLine.getName(), creditMoveLine.getAccount().getLabel()), IException.INCONSISTENCY);
}
}
public void updatePartnerAccountingSituation(Reconcile reconcile) {
List<Partner> partnerList = this.getPartners(reconcile);
if(partnerList != null && !partnerList.isEmpty()) {
Company company = reconcile.getDebitMoveLine().getMove().getCompany();
if(AccountingService.getUpdateCustomerAccount()) {
accountCustomerService.updatePartnerAccountingSituation(partnerList, company, true, true, false);
}
else {
accountCustomerService.flagPartners(partnerList, company);
}
}
}
public List<Partner> getPartners(Reconcile reconcile) {
List<Partner> partnerList = Lists.newArrayList();
Partner debitPartner = reconcile.getDebitMoveLine().getPartner();
Partner creditPartner = reconcile.getCreditMoveLine().getPartner();
if(debitPartner != null && creditPartner != null && debitPartner.equals(creditPartner)) {
partnerList.add(debitPartner);
}
else if(debitPartner != null) { partnerList.add(debitPartner); }
else if(creditPartner != null) { partnerList.add(creditPartner); }
return partnerList;
}
public void updateInvoiceRemainingAmount(Reconcile reconcile) throws AxelorException {
Invoice debitInvoice = reconcile.getDebitMoveLine().getMove().getInvoice();
Invoice creditInvoice = reconcile.getCreditMoveLine().getMove().getInvoice();
// Update amount remaining on invoice or refund
if(debitInvoice != null) {
debitInvoice.setCompanyInTaxTotalRemaining( moveToolService.getInTaxTotalRemaining(debitInvoice) );
}
if(creditInvoice != null) {
creditInvoice.setCompanyInTaxTotalRemaining( moveToolService.getInTaxTotalRemaining(creditInvoice) );
}
}
/**
* Méthode permettant de lettrer une écriture au débit avec une écriture au crédit
* @param debitMoveLine
* @param creditMoveLine
* @throws AxelorException
*/
public Reconcile reconcile(MoveLine debitMoveLine, MoveLine creditMoveLine, boolean canBeZeroBalanceOk) throws AxelorException {
BigDecimal amount = debitMoveLine.getAmountRemaining().min(creditMoveLine.getAmountRemaining());
Reconcile reconcile = this.createReconcile(debitMoveLine, creditMoveLine, amount, canBeZeroBalanceOk);
this.confirmReconcile(reconcile);
return reconcile;
}
/**
* Permet de déréconcilier
* @param reconcile
* Une reconciliation
* @return
* L'etat de la réconciliation
* @throws AxelorException
*/
@Transactional(rollbackOn = {AxelorException.class, Exception.class})
public void unreconcile(Reconcile reconcile) throws AxelorException {
MoveLine debitMoveLine = reconcile.getDebitMoveLine();
MoveLine creditMoveLine = reconcile.getCreditMoveLine();
// Change the state
reconcile.setStatusSelect(ReconcileRepository.STATUS_CANCELED);
//Add the reconciled amount to the reconciled amount in the move line
creditMoveLine.setAmountPaid(creditMoveLine.getAmountPaid().subtract(reconcile.getAmount()));
debitMoveLine.setAmountPaid(debitMoveLine.getAmountPaid().subtract(reconcile.getAmount()));
// Update amount remaining on invoice or refund
this.updatePartnerAccountingSituation(reconcile);
this.updateInvoiceRemainingAmount(reconcile);
reconcileRepository.save(reconcile);
}
/**
* Procédure permettant de gérer les écarts de règlement, check sur la case à cocher 'Peut être soldé'
* Alors nous utilisons la règle de gestion consitant à imputer l'écart sur un compte transitoire si le seuil est respecté
* @param reconcile
* Une reconciliation
* @throws AxelorException
*/
@Transactional(rollbackOn = {AxelorException.class, Exception.class})
public void canBeZeroBalance(Reconcile reconcile) throws AxelorException {
MoveLine debitMoveLine = reconcile.getDebitMoveLine();
BigDecimal debitAmountRemaining = debitMoveLine.getAmountRemaining();
log.debug("Montant à payer / à lettrer au débit : {}", debitAmountRemaining);
if(debitAmountRemaining.compareTo(BigDecimal.ZERO) > 0) {
Company company = reconcile.getDebitMoveLine().getMove().getCompany();
AccountConfig accountConfig = accountConfigService.getAccountConfig(company);
if(debitAmountRemaining.plus().compareTo(accountConfig.getThresholdDistanceFromRegulation()) < 0 || reconcile.getMustBeZeroBalanceOk()) {
log.debug("Seuil respecté");
MoveLine creditAdjustMoveLine = moveAdjustementService.createAdjustmentCreditMove(debitMoveLine);
//Création de la réconciliation
Reconcile newReconcile = this.createReconcile(debitMoveLine, creditAdjustMoveLine, debitAmountRemaining, false);
this.confirmReconcile(newReconcile);
reconcileRepository.save(newReconcile);
}
}
reconcile.setCanBeZeroBalanceOk(false);
log.debug("Fin de la gestion des écarts de règlement");
}
/**
* Solder le trop-perçu si il respect les règles de seuil
* @param creditMoveLine
* @param company
* @throws AxelorException
*/
public void balanceCredit(MoveLine creditMoveLine) throws AxelorException {
if(creditMoveLine != null) {
BigDecimal creditAmountRemaining = creditMoveLine.getAmountRemaining();
log.debug("Montant à payer / à lettrer au crédit : {}", creditAmountRemaining);
if(creditAmountRemaining.compareTo(BigDecimal.ZERO) > 0) {
AccountConfig accountConfig = accountConfigService.getAccountConfig(creditMoveLine.getMove().getCompany());
if(creditAmountRemaining.plus().compareTo(accountConfig.getThresholdDistanceFromRegulation()) < 0) {
log.debug("Seuil respecté");
MoveLine debitAdjustmentMoveLine = moveAdjustementService.createAdjustmentCreditMove(creditMoveLine);
//Création de la réconciliation
Reconcile newReconcile = this.createReconcile(debitAdjustmentMoveLine, creditMoveLine, creditAmountRemaining, false);
this.confirmReconcile(newReconcile);
reconcileRepository.save(newReconcile);
}
}
}
}
public List<Reconcile> getReconciles(MoveLine moveLine) {
List<Reconcile> debitReconcileList = moveLine.getDebitReconcileList();
List<Reconcile> creditReconcileList = moveLine.getCreditReconcileList();
if(moveToolService.isDebitMoveLine(moveLine)) {
return debitReconcileList;
}
else if(debitReconcileList != null && !creditReconcileList.isEmpty()) {
return creditReconcileList;
}
return Lists.newArrayList();
}
}