/** * 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.move; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; 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.AccountManagement; import com.axelor.apps.account.db.AnalyticAccount; import com.axelor.apps.account.db.AnalyticDistributionLine; import com.axelor.apps.account.db.Invoice; import com.axelor.apps.account.db.InvoiceLine; import com.axelor.apps.account.db.InvoiceLineTax; 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.Tax; import com.axelor.apps.account.db.repo.AnalyticDistributionLineRepository; import com.axelor.apps.account.db.repo.InvoiceRepository; import com.axelor.apps.account.exception.IExceptionMessage; import com.axelor.apps.account.service.AccountManagementServiceAccountImpl; import com.axelor.apps.account.service.AnalyticDistributionLineService; import com.axelor.apps.account.service.AnalyticMoveLineService; import com.axelor.apps.account.service.FiscalPositionServiceAccountImpl; import com.axelor.apps.account.service.TaxAccountService; import com.axelor.apps.base.db.Company; import com.axelor.apps.base.db.Partner; import com.axelor.apps.base.db.Product; import com.axelor.apps.base.db.repo.GeneralRepository; import com.axelor.apps.base.service.administration.GeneralService; import com.axelor.exception.AxelorException; import com.axelor.exception.db.IException; import com.axelor.i18n.I18n; import com.axelor.inject.Beans; import com.google.inject.Inject; import com.google.inject.persist.Transactional; public class MoveLineService { private final Logger log = LoggerFactory.getLogger( getClass() ); protected AccountManagementServiceAccountImpl accountManagementService; protected TaxAccountService taxAccountService; protected FiscalPositionServiceAccountImpl fiscalPositionService; protected LocalDate today; protected AnalyticDistributionLineService analyticDistributionLineService; protected GeneralService generalService; protected AnalyticMoveLineService analyticMoveLineService; @Inject public MoveLineService(AccountManagementServiceAccountImpl accountManagementService, TaxAccountService taxAccountService, FiscalPositionServiceAccountImpl fiscalPositionService, GeneralService generalService, AnalyticDistributionLineService analyticDistributionLineService, AnalyticMoveLineService analyticMoveLineService) { this.accountManagementService = accountManagementService; this.taxAccountService = taxAccountService; this.fiscalPositionService = fiscalPositionService; this.analyticDistributionLineService = analyticDistributionLineService; this.generalService = generalService; this.analyticMoveLineService = analyticMoveLineService; today = generalService.getTodayDate(); } public MoveLine computeAnalyticDistribution(MoveLine moveLine){ List<AnalyticDistributionLine> analyticDistributionLineList = moveLine.getAnalyticDistributionLineList(); if(analyticDistributionLineList != null && generalService.getGeneral().getAnalyticDistributionTypeSelect() != GeneralRepository.DISTRIBUTION_TYPE_FREE){ for (AnalyticDistributionLine analyticDistributionLine : analyticDistributionLineList) { analyticDistributionLine.setMoveLine(moveLine); analyticDistributionLine.setAmount(analyticDistributionLineService.computeAmount(analyticDistributionLine)); analyticDistributionLine.setDate(generalService.getTodayDate()); } } return moveLine; } /** * Créer une ligne d'écriture comptable * * @param move * @param partner * @param account * @param amount * @param isDebit * <code>true = débit</code>, * <code>false = crédit</code> * @param isMinus * <code>true = moins</code>, * <code>false = plus</code> * @param dueDate * Date d'échécance * @param ref * @param ignoreInAccountingOk * <code>true = ignoré en compta</code> * @param ignoreInReminderOk * <code>true = ignoré en relance</code> * @param fromSchedulePaymentOk * <code>true = proviens d'un échéancier</code> * * @return */ public MoveLine createMoveLine(Move move, Partner partner, Account account, BigDecimal amount, boolean isDebit, LocalDate date, LocalDate dueDate, int counter, String descriptionOption){ log.debug("Création d'une ligne d'écriture comptable (compte comptable : {}, montant : {}, debit ? : {}, " + " date d'échéance : {}, référence : {}", new Object[]{account.getName(), amount, isDebit, dueDate, counter}); if(partner != null) { account = fiscalPositionService.getAccount(partner.getFiscalPosition(), account); } BigDecimal debit = BigDecimal.ZERO; BigDecimal credit = BigDecimal.ZERO; if(amount.compareTo(BigDecimal.ZERO) == -1) { isDebit = !isDebit; amount = amount.negate(); } if(isDebit) { debit = amount; } else { credit = amount; } return new MoveLine(move, partner, account, date, dueDate, counter, debit, credit, this.determineDescriptionMoveLine(move.getJournal(), descriptionOption)); } /** * Créer une ligne d'écriture comptable * * @param move * @param partner * @param account * @param amount * @param isDebit * <code>true = débit</code>, * <code>false = crédit</code> * @param isMinus * <code>true = moins</code>, * <code>false = plus</code> * @param dueDate * Date d'échécance * @param ref * @param ignoreInAccountingOk * <code>true = ignoré en compta</code> * @param ignoreInReminderOk * <code>true = ignoré en relance</code> * @param fromSchedulePaymentOk * <code>true = proviens d'un échéancier</code> * * @return */ public MoveLine createMoveLine(Move move, Partner partner, Account account, BigDecimal amount, boolean isDebit, LocalDate date, int ref, String descriptionOption){ return this.createMoveLine(move, partner, account, amount, isDebit, date, date, ref, descriptionOption); } /** * Créer les lignes d'écritures comptables d'une facture. * * @param invoice * @param move * @param consolidate * @return */ public List<MoveLine> createMoveLines(Invoice invoice, Move move, Company company, Partner partner, Account account, boolean consolidate, boolean isPurchase, boolean isDebitCustomer) throws AxelorException{ log.debug("Création des lignes d'écriture comptable de la facture/l'avoir {}", invoice.getInvoiceId()); Account account2 = account; List<MoveLine> moveLines = new ArrayList<MoveLine>(); AccountManagement accountManagement = null; Set<AnalyticAccount> analyticAccounts = new HashSet<AnalyticAccount>(); int moveLineId = 1; if (partner == null) { throw new AxelorException(I18n.get(IExceptionMessage.MOVE_LINE_1), IException.MISSING_FIELD, invoice.getInvoiceId()); } if (account2 == null) { throw new AxelorException(I18n.get(IExceptionMessage.MOVE_LINE_2), IException.MISSING_FIELD, invoice.getInvoiceId()); } moveLines.add( this.createMoveLine(move, partner, account2, invoice.getCompanyInTaxTotal(), isDebitCustomer, invoice.getInvoiceDate(), invoice.getDueDate(), moveLineId++, invoice.getInvoiceId())); // Traitement des lignes de facture for (InvoiceLine invoiceLine : invoice.getInvoiceLineList()){ if(invoiceLine.getProduct() != null){ BigDecimal exTaxTotal = invoiceLine.getCompanyExTaxTotal(); if(exTaxTotal.compareTo(BigDecimal.ZERO) != 0) { analyticAccounts.clear(); Product product = invoiceLine.getProduct(); if(product == null) { throw new AxelorException(I18n.get(IExceptionMessage.MOVE_LINE_3), IException.CONFIGURATION_ERROR, invoice.getInvoiceId(), company.getName()); } accountManagement = accountManagementService.getAccountManagement(product, company); account2 = accountManagementService.getProductAccount(accountManagement, isPurchase); if(account2 == null) { throw new AxelorException(I18n.get(IExceptionMessage.MOVE_LINE_4), IException.CONFIGURATION_ERROR, invoiceLine.getName(), company.getName()); } exTaxTotal = invoiceLine.getCompanyExTaxTotal(); log.debug("Traitement de la ligne de facture : compte comptable = {}, montant = {}", new Object[]{account2.getName(), exTaxTotal}); MoveLine moveLine = this.createMoveLine(move, partner, account2, exTaxTotal, !isDebitCustomer, invoice.getInvoiceDate(), null, moveLineId++, invoice.getInvoiceId()); if(invoiceLine.getAnalyticDistributionLineList() != null) { for (AnalyticDistributionLine analyticDistributionLineIt : invoiceLine.getAnalyticDistributionLineList()) { AnalyticDistributionLine analyticDistributionLine = Beans.get(AnalyticDistributionLineRepository.class).copy(analyticDistributionLineIt, false); moveLine.addAnalyticDistributionLineListItem(analyticDistributionLine); } } moveLine.setTaxLine(invoiceLine.getTaxLine()); moveLines.add(moveLine); } } } // Traitement des lignes de tva for (InvoiceLineTax invoiceLineTax : invoice.getInvoiceLineTaxList()){ BigDecimal exTaxTotal = invoiceLineTax.getCompanyTaxTotal(); if(exTaxTotal.compareTo(BigDecimal.ZERO) != 0) { Tax tax = invoiceLineTax.getTaxLine().getTax(); account2 = taxAccountService.getAccount(tax, company); if (account2 == null) { throw new AxelorException(I18n.get(IExceptionMessage.MOVE_LINE_6), IException.CONFIGURATION_ERROR, tax.getName(), company.getName() ); } MoveLine moveLine = this.createMoveLine(move, partner, account2, exTaxTotal, !isDebitCustomer, invoice.getInvoiceDate(), null, moveLineId++, invoice.getInvoiceId()); moveLine.setTaxLine(invoiceLineTax.getTaxLine()); moveLines.add(moveLine); } } if (consolidate) { this.consolidateMoveLines(moveLines); } return moveLines; } public MoveLine findConsolidateMoveLine(Map<List<Object>, MoveLine> map, MoveLine moveLine, List<Object> keys){ if(map != null && !map.isEmpty()){ Map<List<Object>, MoveLine> copyMap = new HashMap<List<Object>, MoveLine>(map); while(!copyMap.isEmpty()){ if(map.containsKey(keys)){ MoveLine moveLineIt = map.get(keys); int count = 0; if(moveLineIt.getAnalyticDistributionLineList() == null && moveLine.getAnalyticDistributionLineList() == null){ return moveLineIt; } else if(moveLineIt.getAnalyticDistributionLineList() == null || moveLine.getAnalyticDistributionLineList() == null){ break; } List<AnalyticDistributionLine> list1 = moveLineIt.getAnalyticDistributionLineList(); List<AnalyticDistributionLine> list2 = moveLine.getAnalyticDistributionLineList(); List<AnalyticDistributionLine> copyList = new ArrayList<AnalyticDistributionLine>(list1); if(list1.size() == list2.size()){ for (AnalyticDistributionLine analyticDistributionLine : list2) { for (AnalyticDistributionLine analyticDistributionLineIt : copyList) { if(analyticDistributionLine.getAnalyticAxis().equals(analyticDistributionLineIt.getAnalyticAxis()) && analyticDistributionLine.getAnalyticAccount().equals(analyticDistributionLineIt.getAnalyticAccount()) && analyticDistributionLine.getPercentage().equals(analyticDistributionLineIt.getPercentage()) && ((analyticDistributionLine.getAnalyticJournal() == null && analyticDistributionLineIt.getAnalyticJournal() == null) || analyticDistributionLine.getAnalyticJournal().equals(analyticDistributionLineIt.getAnalyticJournal()))){ copyList.remove(analyticDistributionLineIt); count++; break; } } } if(count == list1.size()){ return moveLineIt; } } } else{ return null; } } } return null; } /** * Consolider des lignes d'écritures par compte comptable. * * @param moveLines */ public List<MoveLine> consolidateMoveLines(List<MoveLine> moveLines){ Map<List<Object>, MoveLine> map = new HashMap<List<Object>, MoveLine>(); MoveLine consolidateMoveLine = null; List<Object> keys = new ArrayList<Object>(); for (MoveLine moveLine : moveLines){ keys.clear(); keys.add(moveLine.getAccount()); keys.add(moveLine.getTaxLine()); consolidateMoveLine = this.findConsolidateMoveLine(map, moveLine, keys); if (consolidateMoveLine != null){ consolidateMoveLine.setCredit(consolidateMoveLine.getCredit().add(moveLine.getCredit())); consolidateMoveLine.setDebit(consolidateMoveLine.getDebit().add(moveLine.getDebit())); if(consolidateMoveLine.getAnalyticDistributionLineList() != null && !consolidateMoveLine.getAnalyticDistributionLineList().isEmpty()){ for (AnalyticDistributionLine analyticDistributionLine : consolidateMoveLine.getAnalyticDistributionLineList()) { for (AnalyticDistributionLine analyticDistributionLineIt : moveLine.getAnalyticDistributionLineList()) { if(analyticDistributionLine.getAnalyticAxis().equals(analyticDistributionLineIt.getAnalyticAxis()) && analyticDistributionLine.getAnalyticAccount().equals(analyticDistributionLineIt.getAnalyticAccount()) && analyticDistributionLine.getPercentage().equals(analyticDistributionLineIt.getPercentage()) && ((analyticDistributionLine.getAnalyticJournal() == null && analyticDistributionLineIt.getAnalyticJournal() == null) || analyticDistributionLine.getAnalyticJournal().equals(analyticDistributionLineIt.getAnalyticJournal()))){ analyticDistributionLine.setAmount(analyticDistributionLine.getAmount().add(analyticDistributionLineIt.getAmount())); break; } } } } } else { map.put(keys, moveLine); } } BigDecimal credit = null; BigDecimal debit = null; int moveLineId = 1; moveLines.clear(); for (MoveLine moveLine : map.values()){ credit = moveLine.getCredit(); debit = moveLine.getDebit(); if (debit.compareTo(BigDecimal.ZERO) == 1 && credit.compareTo(BigDecimal.ZERO) == 1){ if (debit.compareTo(credit) == 1){ moveLine.setDebit(debit.subtract(credit)); moveLine.setCredit(BigDecimal.ZERO); moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } else if (credit.compareTo(debit) == 1){ moveLine.setCredit(credit.subtract(debit)); moveLine.setDebit(BigDecimal.ZERO); moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } } else if (debit.compareTo(BigDecimal.ZERO) == 1 || credit.compareTo(BigDecimal.ZERO) == 1){ moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } } return moveLines; } public List<MoveLine> consolidateMoveLinesWithoutAnalytic(List<MoveLine> moveLines){ Map<List<Object>, MoveLine> map = new HashMap<List<Object>, MoveLine>(); MoveLine consolidateMoveLine = null; List<Object> keys = new ArrayList<Object>(); for (MoveLine moveLine : moveLines){ keys.clear(); keys.add(moveLine.getAccount()); keys.add(moveLine.getTaxLine()); if (map.containsKey(keys)){ consolidateMoveLine = map.get(keys); consolidateMoveLine.setCredit(consolidateMoveLine.getCredit().add(moveLine.getCredit())); consolidateMoveLine.setDebit(consolidateMoveLine.getDebit().add(moveLine.getDebit())); } else { map.put(keys, moveLine); } } BigDecimal credit = null; BigDecimal debit = null; int moveLineId = 1; moveLines.clear(); for (MoveLine moveLine : map.values()){ credit = moveLine.getCredit(); debit = moveLine.getDebit(); if (debit.compareTo(BigDecimal.ZERO) == 1 && credit.compareTo(BigDecimal.ZERO) == 1){ if (debit.compareTo(credit) == 1){ moveLine.setDebit(debit.subtract(credit)); moveLine.setCredit(BigDecimal.ZERO); moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } else if (credit.compareTo(debit) == 1){ moveLine.setCredit(credit.subtract(debit)); moveLine.setDebit(BigDecimal.ZERO); moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } } else if (debit.compareTo(BigDecimal.ZERO) == 1 || credit.compareTo(BigDecimal.ZERO) == 1){ moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } } return moveLines; } public List<MoveLine> consolidateMoveLinesOnlyAnalytic(List<MoveLine> moveLines){ Map<List<Object>, MoveLine> map = new HashMap<List<Object>, MoveLine>(); MoveLine consolidateMoveLine = null; List<Object> keys = new ArrayList<Object>(); for (MoveLine moveLine : moveLines){ keys.clear(); keys.add(moveLine.getAccount()); consolidateMoveLine = this.findConsolidateMoveLine(map, moveLine, keys); if (consolidateMoveLine != null){ consolidateMoveLine.setCredit(consolidateMoveLine.getCredit().add(moveLine.getCredit())); consolidateMoveLine.setDebit(consolidateMoveLine.getDebit().add(moveLine.getDebit())); for (AnalyticDistributionLine analyticDistributionLine : consolidateMoveLine.getAnalyticDistributionLineList()) { for (AnalyticDistributionLine analyticDistributionLineIt : moveLine.getAnalyticDistributionLineList()) { if(analyticDistributionLine.getAnalyticAxis().equals(analyticDistributionLineIt.getAnalyticAxis()) && analyticDistributionLine.getAnalyticAccount().equals(analyticDistributionLineIt.getAnalyticAccount()) && analyticDistributionLine.getPercentage().equals(analyticDistributionLineIt.getPercentage()) && analyticDistributionLine.getAnalyticJournal().equals(analyticDistributionLineIt.getAnalyticJournal())){ analyticDistributionLine.setAmount(analyticDistributionLine.getAmount().add(analyticDistributionLineIt.getAmount())); break; } } } } else { map.put(keys, moveLine); } } BigDecimal credit = null; BigDecimal debit = null; int moveLineId = 1; moveLines.clear(); for (MoveLine moveLine : map.values()){ credit = moveLine.getCredit(); debit = moveLine.getDebit(); if (debit.compareTo(BigDecimal.ZERO) == 1 && credit.compareTo(BigDecimal.ZERO) == 1){ if (debit.compareTo(credit) == 1){ moveLine.setDebit(debit.subtract(credit)); moveLine.setCredit(BigDecimal.ZERO); moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } else if (credit.compareTo(debit) == 1){ moveLine.setCredit(credit.subtract(debit)); moveLine.setDebit(BigDecimal.ZERO); moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } } else if (debit.compareTo(BigDecimal.ZERO) == 1 || credit.compareTo(BigDecimal.ZERO) == 1){ moveLine.setCounter(moveLineId++); moveLines.add(moveLine); } } return moveLines; } /** * Fonction permettant de récuperer la ligne d'écriture (au credit et non complétement lettrée sur le compte client) de la facture * @param invoice * Une facture * @return */ public MoveLine getCreditCustomerMoveLine(Invoice invoice) { if(invoice.getMove() != null) { return this.getCreditCustomerMoveLine(invoice.getMove()); } return null; } /** * Fonction permettant de récuperer la ligne d'écriture (au credit et non complétement lettrée sur le compte client) de l'écriture de facture * @param move * Une écriture de facture * @return */ public MoveLine getCreditCustomerMoveLine(Move move) { for(MoveLine moveLine : move.getMoveLineList()) { if(moveLine.getAccount().getReconcileOk() && moveLine.getCredit().compareTo(BigDecimal.ZERO) > 0 && moveLine.getAmountRemaining().compareTo(BigDecimal.ZERO) > 0) { return moveLine; } } return null; } /** * Fonction permettant de récuperer la ligne d'écriture (au débit et non complétement lettrée sur le compte client) de la facture * @param invoice * Une facture * @return */ public MoveLine getDebitCustomerMoveLine(Invoice invoice) { if(invoice.getMove() != null) { return this.getDebitCustomerMoveLine(invoice.getMove()); } return null; } /** * Fonction permettant de récuperer la ligne d'écriture (au débit et non complétement lettrée sur le compte client) de l'écriture de facture * @param move * Une écriture de facture * @return */ public MoveLine getDebitCustomerMoveLine(Move move) { for(MoveLine moveLine : move.getMoveLineList()) { if(moveLine.getAccount().getReconcileOk() && moveLine.getDebit().compareTo(BigDecimal.ZERO) > 0 && moveLine.getAmountRemaining().compareTo(BigDecimal.ZERO) > 0) { return moveLine; } } return null; } /** * Fonction permettant de générér automatiquement la description des lignes d'écritures * @param journal * Le journal de l'écriture * @param descriptionOption * Le n° pièce réglée, facture, avoir ou de l'opération rejetée * @return */ public String determineDescriptionMoveLine(Journal journal, String descriptionOption) { String description = ""; if(journal != null) { if(journal.getDescriptionModel() != null) { description = String.format("%s", journal.getDescriptionModel()); } if(journal.getDescriptionIdentificationOk() && descriptionOption != null) { description += String.format(" %s", descriptionOption); } } return description; } /** * Procédure permettant d'impacter la case à cocher "Passage à l'huissier" sur la facture liée à l'écriture * @param moveLine * Une ligne d'écriture */ @Transactional public void usherProcess(MoveLine moveLine) { Invoice invoice = moveLine.getMove().getInvoice(); if(invoice != null) { if(moveLine.getUsherPassageOk()) { invoice.setUsherPassageOk(true); } else { invoice.setUsherPassageOk(false); } Beans.get(InvoiceRepository.class).save(invoice); } } }