/** * 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.supplychain.service; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import javax.persistence.Query; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.axelor.apps.account.db.Invoice; import com.axelor.apps.account.db.InvoiceLine; import com.axelor.apps.account.db.repo.InvoiceRepository; import com.axelor.apps.account.service.invoice.InvoiceService; import com.axelor.apps.account.service.invoice.generator.InvoiceGenerator; import com.axelor.apps.base.db.Product; import com.axelor.apps.base.db.repo.ProductRepository; import com.axelor.apps.purchase.db.PurchaseOrder; import com.axelor.apps.purchase.db.PurchaseOrderLine; import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository; import com.axelor.apps.supplychain.exception.IExceptionMessage; import com.axelor.apps.supplychain.service.invoice.generator.InvoiceGeneratorSupplyChain; import com.axelor.apps.supplychain.service.invoice.generator.InvoiceLineGeneratorSupplyChain; import com.axelor.db.JPA; 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 PurchaseOrderInvoiceServiceImpl implements PurchaseOrderInvoiceService { private final Logger log = LoggerFactory.getLogger( getClass() ); @Inject private InvoiceService invoiceService; @Inject private InvoiceRepository invoiceRepo; @Inject private PurchaseOrderRepository purchaseOrderRepo; @Override @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public Invoice generateInvoice(PurchaseOrder purchaseOrder) throws AxelorException { Invoice invoice = this.createInvoice(purchaseOrder); invoiceRepo.save(invoice); invoiceService.setDraftSequence(invoice); if(invoice != null) { purchaseOrder.setInvoice(invoice); purchaseOrderRepo.save(purchaseOrder); } return invoice; } @Override public Invoice createInvoice(PurchaseOrder purchaseOrder) throws AxelorException{ InvoiceGenerator invoiceGenerator = this.createInvoiceGenerator(purchaseOrder); Invoice invoice = invoiceGenerator.generate(); invoiceGenerator.populate(invoice, this.createInvoiceLines(invoice, purchaseOrder.getPurchaseOrderLineList())); return invoice; } @Override public InvoiceGenerator createInvoiceGenerator(PurchaseOrder purchaseOrder) throws AxelorException { if(purchaseOrder.getCurrency() == null) { throw new AxelorException(String.format(I18n.get(IExceptionMessage.PO_INVOICE_1), purchaseOrder.getPurchaseOrderSeq()), IException.CONFIGURATION_ERROR); } InvoiceGenerator invoiceGenerator = new InvoiceGeneratorSupplyChain(purchaseOrder) { @Override public Invoice generate() throws AxelorException { return super.createInvoiceHeader(); } }; return invoiceGenerator; } @Override public List<InvoiceLine> createInvoiceLines(Invoice invoice, List<PurchaseOrderLine> purchaseOrderLineList) throws AxelorException { List<InvoiceLine> invoiceLineList = new ArrayList<InvoiceLine>(); for(PurchaseOrderLine purchaseOrderLine : purchaseOrderLineList) { //Lines of subscription type are invoiced directly from purchase order line or from the subscription batch if (!ProductRepository.PRODUCT_TYPE_SUBSCRIPTABLE.equals(purchaseOrderLine.getProduct().getProductTypeSelect())){ invoiceLineList.addAll(this.createInvoiceLine(invoice, purchaseOrderLine)); purchaseOrderLine.setInvoiced(true); } } return invoiceLineList; } @Override public List<InvoiceLine> createInvoiceLine(Invoice invoice, PurchaseOrderLine purchaseOrderLine) throws AxelorException { Product product = purchaseOrderLine.getProduct(); InvoiceLineGeneratorSupplyChain invoiceLineGenerator = new InvoiceLineGeneratorSupplyChain(invoice, product, purchaseOrderLine.getProductName(), purchaseOrderLine.getDescription(), purchaseOrderLine.getQty(), purchaseOrderLine.getUnit(), purchaseOrderLine.getSequence(), false, null, purchaseOrderLine, null) { @Override public List<InvoiceLine> creates() throws AxelorException { InvoiceLine invoiceLine = this.createInvoiceLine(); List<InvoiceLine> invoiceLines = new ArrayList<InvoiceLine>(); invoiceLines.add(invoiceLine); return invoiceLines; } }; return invoiceLineGenerator.creates(); } @Override public BigDecimal getInvoicedAmount(PurchaseOrder purchaseOrder){ return this.getInvoicedAmount(purchaseOrder, null, true); } /** * Return the remaining amount to invoice for the purchaseOrder in parameter * * @param purchaseOrder * * @param currentInvoiceId * In the case of invoice ventilation or cancellation, the invoice status isn't modify in database but it will be integrated in calculation * For ventilation, the invoice should be integrated in calculation * For cancellation, the invoice shouldn't be integrated in calculation * * @param includeInvoice * To know if the invoice should be or not integrated in calculation */ @Override public BigDecimal getInvoicedAmount(PurchaseOrder purchaseOrder, Long currentInvoiceId, boolean excludeCurrentInvoice) { BigDecimal invoicedAmount = BigDecimal.ZERO; BigDecimal purchaseAmount = this.getAmountVentilated(purchaseOrder, currentInvoiceId, excludeCurrentInvoice, InvoiceRepository.OPERATION_TYPE_SUPPLIER_PURCHASE); BigDecimal refundAmount = this.getAmountVentilated(purchaseOrder, currentInvoiceId, excludeCurrentInvoice, InvoiceRepository.OPERATION_TYPE_SUPPLIER_REFUND); if(purchaseAmount != null) { invoicedAmount = invoicedAmount.add(purchaseAmount); } if(refundAmount != null) { invoicedAmount = invoicedAmount.subtract(refundAmount); } if (!purchaseOrder.getCurrency().equals(purchaseOrder.getCompany().getCurrency()) && purchaseOrder.getCompanyExTaxTotal().compareTo(BigDecimal.ZERO) != 0){ BigDecimal rate = invoicedAmount.divide(purchaseOrder.getCompanyExTaxTotal(), 4, RoundingMode.HALF_UP); invoicedAmount = purchaseOrder.getExTaxTotal().multiply(rate); } log.debug("Compute the invoiced amount ({}) of the purchase order : {}", invoicedAmount, purchaseOrder.getPurchaseOrderSeq()); return invoicedAmount; } private BigDecimal getAmountVentilated(PurchaseOrder purchaseOrder, Long currentInvoiceId, boolean excludeCurrentInvoice, int invoiceOperationTypeSelect) { String query = "SELECT SUM(self.companyExTaxTotal)" + " FROM InvoiceLine as self" + " WHERE ((self.purchaseOrderLine.purchaseOrder.id = :purchaseOrderId AND self.invoice.purchaseOrder IS NULL)" + " OR self.invoice.purchaseOrder.id = :purchaseOrderId )" + " AND self.invoice.operationTypeSelect = :invoiceOperationTypeSelect" + " AND self.invoice.statusSelect = :statusVentilated"; if (currentInvoiceId != null) { if(excludeCurrentInvoice) { query += " AND self.invoice.id <> :invoiceId"; } else { query += " OR (self.invoice.id = :invoiceId AND self.invoice.operationTypeSelect = :invoiceOperationTypeSelect) "; } } Query q = JPA.em().createQuery(query, BigDecimal.class); q.setParameter("purchaseOrderId", purchaseOrder.getId()); q.setParameter("statusVentilated", InvoiceRepository.STATUS_VENTILATED); q.setParameter("invoiceOperationTypeSelect", invoiceOperationTypeSelect); if (currentInvoiceId != null){ q.setParameter("invoiceId", currentInvoiceId); } BigDecimal invoicedAmount = (BigDecimal) q.getSingleResult(); if(invoicedAmount != null) { return invoicedAmount; } else { return BigDecimal.ZERO; } } }