First commit waiting for Budget Alert
This commit is contained in:
19
modules/axelor-open-suite/axelor-supplychain/build.gradle
Normal file
19
modules/axelor-open-suite/axelor-supplychain/build.gradle
Normal file
@ -0,0 +1,19 @@
|
||||
apply plugin: "com.axelor.app-module"
|
||||
|
||||
apply from: "../version.gradle"
|
||||
|
||||
apply {
|
||||
version = openSuiteVersion
|
||||
}
|
||||
|
||||
axelor {
|
||||
title "Axelor Supply Chain"
|
||||
description "Axelor Supply Chain Module"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(":modules:axelor-purchase")
|
||||
compile project(":modules:axelor-sale")
|
||||
compile project(":modules:axelor-stock")
|
||||
compile project(":modules:axelor-account")
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.db.repo;
|
||||
|
||||
import com.axelor.apps.base.service.app.AppService;
|
||||
import com.axelor.apps.sale.db.AdvancePayment;
|
||||
import com.axelor.apps.sale.db.repo.AdvancePaymentSaleRepository;
|
||||
import com.axelor.apps.supplychain.service.AdvancePaymentServiceSupplychainImpl;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
public class AdvancePaymentSupplychainRepository extends AdvancePaymentSaleRepository {
|
||||
|
||||
@Inject private AppService appService;
|
||||
|
||||
@Override
|
||||
public AdvancePayment save(AdvancePayment advancePayment) {
|
||||
try {
|
||||
if (appService.isApp("supplychain")) {
|
||||
Beans.get(AdvancePaymentServiceSupplychainImpl.class).validate(advancePayment);
|
||||
}
|
||||
return super.save(advancePayment);
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.db.repo;
|
||||
|
||||
import com.axelor.apps.account.db.AnalyticMoveLine;
|
||||
import com.axelor.apps.account.db.repo.AnalyticMoveLineMngtRepository;
|
||||
|
||||
public class AnalyticMoveLineSupplychainRepository extends AnalyticMoveLineMngtRepository {
|
||||
@Override
|
||||
public AnalyticMoveLine copy(AnalyticMoveLine entity, boolean deep) {
|
||||
AnalyticMoveLine copy = super.copy(entity, deep);
|
||||
copy.setPurchaseOrderLine(null);
|
||||
copy.setSaleOrderLine(null);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.db.repo;
|
||||
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.repo.SequenceRepository;
|
||||
import com.axelor.apps.base.service.administration.SequenceService;
|
||||
import com.axelor.apps.supplychain.db.Mrp;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.MrpService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.Strings;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
public class MrpManagementRepository extends MrpRepository {
|
||||
|
||||
@Override
|
||||
public void remove(Mrp entity) {
|
||||
|
||||
Beans.get(MrpService.class).reset(entity);
|
||||
|
||||
super.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mrp save(Mrp entity) {
|
||||
|
||||
try {
|
||||
if (Strings.isNullOrEmpty(entity.getMrpSeq())) {
|
||||
Company company = entity.getStockLocation().getCompany();
|
||||
String seq =
|
||||
Beans.get(SequenceService.class)
|
||||
.getSequenceNumber(SequenceRepository.SUPPLYCHAIN_MRP, company);
|
||||
|
||||
if (seq == null) {
|
||||
throw new AxelorException(
|
||||
company,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.SUPPLYCHAIN_MRP_SEQUENCE_ERROR),
|
||||
company.getName());
|
||||
}
|
||||
|
||||
entity.setMrpSeq(seq);
|
||||
}
|
||||
} catch (AxelorException e) {
|
||||
throw new PersistenceException(e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
return super.save(entity);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.db.repo;
|
||||
|
||||
import com.axelor.apps.base.service.app.AppService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderManagementRepository;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.supplychain.service.PurchaseOrderServiceSupplychainImpl;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
public class PurchaseOrderSupplychainRepository extends PurchaseOrderManagementRepository {
|
||||
|
||||
@Inject private AppService appService;
|
||||
|
||||
@Override
|
||||
public PurchaseOrder copy(PurchaseOrder entity, boolean deep) {
|
||||
|
||||
PurchaseOrder copy = super.copy(entity, deep);
|
||||
|
||||
if (!appService.isApp("supplychain")) {
|
||||
return copy;
|
||||
}
|
||||
|
||||
copy.setReceiptState(PurchaseOrderRepository.STATE_NOT_RECEIVED);
|
||||
copy.setAmountInvoiced(null);
|
||||
|
||||
for (PurchaseOrderLine purchaseOrderLine : copy.getPurchaseOrderLineList()) {
|
||||
purchaseOrderLine.setReceiptState(null);
|
||||
purchaseOrderLine.setReceivedQty(null);
|
||||
purchaseOrderLine.setAmountInvoiced(null);
|
||||
purchaseOrderLine.setInvoiced(null);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PurchaseOrder save(PurchaseOrder purchaseOrder) {
|
||||
|
||||
if (appService.isApp("supplychain")) {
|
||||
Beans.get(PurchaseOrderServiceSupplychainImpl.class)
|
||||
.generateBudgetDistribution(purchaseOrder);
|
||||
}
|
||||
return super.save(purchaseOrder);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.db.repo;
|
||||
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.service.app.AppService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderManagementRepository;
|
||||
import com.axelor.apps.supplychain.service.AccountingSituationSupplychainService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class SaleOrderSupplychainRepository extends SaleOrderManagementRepository {
|
||||
|
||||
@Override
|
||||
public SaleOrder copy(SaleOrder entity, boolean deep) {
|
||||
|
||||
SaleOrder copy = super.copy(entity, deep);
|
||||
|
||||
if (!Beans.get(AppService.class).isApp("supplychain")) {
|
||||
return copy;
|
||||
}
|
||||
|
||||
copy.setShipmentDate(null);
|
||||
copy.setDeliveryState(DELIVERY_STATE_NOT_DELIVERED);
|
||||
copy.setAmountInvoiced(null);
|
||||
copy.setStockMoveList(null);
|
||||
|
||||
if (copy.getSaleOrderLineList() != null) {
|
||||
for (SaleOrderLine saleOrderLine : copy.getSaleOrderLineList()) {
|
||||
saleOrderLine.setDeliveryState(null);
|
||||
saleOrderLine.setDeliveredQty(null);
|
||||
saleOrderLine.setAmountInvoiced(null);
|
||||
saleOrderLine.setInvoiced(null);
|
||||
saleOrderLine.setInvoicingDate(null);
|
||||
saleOrderLine.setIsInvoiceControlled(null);
|
||||
saleOrderLine.setReservedQty(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(SaleOrder order) {
|
||||
|
||||
Partner partner = order.getClientPartner();
|
||||
|
||||
super.remove(order);
|
||||
|
||||
try {
|
||||
Beans.get(AccountingSituationSupplychainService.class).updateUsedCredit(partner);
|
||||
} catch (AxelorException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.db.repo;
|
||||
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineStockRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.supplychain.service.StockMoveLineServiceSupplychain;
|
||||
import com.axelor.inject.Beans;
|
||||
import java.util.Map;
|
||||
|
||||
public class StockMoveLineSupplychainRepository extends StockMoveLineStockRepository {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> populate(Map<String, Object> json, Map<String, Object> context) {
|
||||
Long stockMoveLineId = (Long) json.get("id");
|
||||
StockMoveLine stockMoveLine = find(stockMoveLineId);
|
||||
StockMove stockMove = stockMoveLine.getStockMove();
|
||||
|
||||
Map<String, Object> stockMoveLineMap = super.populate(json, context);
|
||||
if (stockMove != null && stockMove.getStatusSelect() == StockMoveRepository.STATUS_REALIZED) {
|
||||
Beans.get(StockMoveLineServiceSupplychain.class).setInvoiceStatus(stockMoveLine);
|
||||
json.put(
|
||||
"availableStatus",
|
||||
stockMoveLine.getProduct() != null && stockMoveLine.getProduct().getStockManaged()
|
||||
? stockMoveLine.getAvailableStatus()
|
||||
: null);
|
||||
json.put("availableStatusSelect", stockMoveLine.getAvailableStatusSelect());
|
||||
}
|
||||
return stockMoveLineMap;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.db.repo;
|
||||
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveManagementRepository;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class StockMoveSupplychainRepository extends StockMoveManagementRepository {
|
||||
|
||||
@Override
|
||||
public StockMove copy(StockMove entity, boolean deep) {
|
||||
|
||||
StockMove copy = super.copy(entity, deep);
|
||||
|
||||
copy.setInvoiceSet(null);
|
||||
copy.setOriginTypeSelect(null);
|
||||
if (copy.getStockMoveLineList() != null) {
|
||||
for (StockMoveLine stockMoveLine : copy.getStockMoveLineList()) {
|
||||
stockMoveLine.setReservedQty(BigDecimal.ZERO);
|
||||
stockMoveLine.setQtyInvoiced(null);
|
||||
}
|
||||
}
|
||||
copy.setReservationDateTime(null);
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.db.repo;
|
||||
|
||||
import com.axelor.apps.supplychain.db.SupplychainBatch;
|
||||
|
||||
public class SupplychainBatchSupplychainRepository extends SupplychainBatchRepository {
|
||||
|
||||
@Override
|
||||
public SupplychainBatch copy(SupplychainBatch entity, boolean deep) {
|
||||
SupplychainBatch copy = super.copy(entity, deep);
|
||||
copy.setBatchList(null);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.exception;
|
||||
|
||||
/** @author axelor */
|
||||
public interface IExceptionMessage {
|
||||
/** Purchase order Invoice Service and controller */
|
||||
static final String PO_INVOICE_1 = /*$$(*/ "Please, select a currency for the order %s" /*)*/;
|
||||
|
||||
static final String PO_INVOICE_2 = /*$$(*/ "Invoice created" /*)*/;
|
||||
|
||||
/** Purchase order Service */
|
||||
static final String PURCHASE_ORDER_1 = /*$$(*/
|
||||
"%s please configure a virtual supplier stock location for the company %s" /*)*/;
|
||||
|
||||
static final String PURCHASE_ORDER_2 = /*$$(*/
|
||||
"Error : you have exceeded the budget %s for this period" /*)*/;
|
||||
|
||||
/** Sale order Invoice Service */
|
||||
static final String SO_INVOICE_6 = /*$$(*/ "Please, select a currency for the order %s" /*)*/;
|
||||
|
||||
static final String SO_INVOICE_NO_LINES_SELECTED = /*$$(*/ "There are no lines to invoice" /*)*/;
|
||||
static final String SO_INVOICE_NO_TIMETABLES_SELECTED = /*$$(*/
|
||||
"There are no selected timetables to invoice" /*)*/;
|
||||
static final String SO_INVOICE_QTY_MAX = /*$$(*/
|
||||
"The quantity to invoice is greater than the quantity in the sale order" /*)*/;
|
||||
static final String SO_INVOICE_AMOUNT_MAX = /*$$(*/
|
||||
"The amount to invoice is superior than the amount in the sale order" /*)*/;
|
||||
static final String SO_INVOICE_MISSING_INVOICING_PRODUCT = /*$$(*/
|
||||
"Please configure the sale order invoicing product" /*)*/;
|
||||
static final String SO_INVOICE_MISSING_ADVANCE_PAYMENT_PRODUCT = /*$$(*/
|
||||
"Please configure the advance payment product" /*)*/;
|
||||
static final String SO_INVOICE_MISSING_ADVANCE_PAYMENT_ACCOUNT = /*$$(*/
|
||||
"You must configure an advance payment account for the company %s" /*)*/;
|
||||
static final String SO_INVOICE_TOO_MUCH_INVOICED = /*$$(*/
|
||||
"The sale order %s invoiced amount cannot be greater than its total amount." /*)*/;
|
||||
static final String SO_INVOICE_GENERATE_ALL_INVOICES = /*$$(*/
|
||||
"All invoices have been generated for this sale order." /*)*/;
|
||||
|
||||
/** Sale order Purchase Service */
|
||||
static final String SO_PURCHASE_1 = /*$$(*/ "Please, select a supplier for the line %s" /*)*/;
|
||||
|
||||
static final String SO_LINE_PURCHASE_AT_LEAST_ONE = /*$$(*/
|
||||
"At least one sale order line must be selected" /*)*/;
|
||||
|
||||
/** Stock Move Invoice Service */
|
||||
static final String STOCK_MOVE_INVOICE_1 = /*$$(*/ "Incorrect product in the stock move %s" /*)*/;
|
||||
|
||||
static final String STOCK_MOVE_MULTI_INVOICE_CURRENCY = /*$$(*/
|
||||
"The currency is required and must be the same for all sale orders" /*)*/;
|
||||
static final String STOCK_MOVE_MULTI_INVOICE_CLIENT_PARTNER = /*$$(*/
|
||||
"The client is required and must be the same for all sale orders" /*)*/;
|
||||
static final String STOCK_MOVE_MULTI_INVOICE_SUPPLIER_PARTNER = /*$$(*/
|
||||
"The supplier is required and must be the same for all purchase orders" /*)*/;
|
||||
static final String STOCK_MOVE_MULTI_INVOICE_COMPANY_SO = /*$$(*/
|
||||
"The company is required and must be the same for all sale orders" /*)*/;
|
||||
static final String STOCK_MOVE_MULTI_INVOICE_COMPANY_PO = /*$$(*/
|
||||
"The company is required and must be the same for all purchase orders" /*)*/;
|
||||
static final String STOCK_MOVE_MULTI_INVOICE_TRADING_NAME_SO = /*$$(*/
|
||||
"The trading name must be the same for all sale orders." /*)*/;
|
||||
static final String STOCK_MOVE_MULTI_INVOICE_TRADING_NAME_PO = /*$$(*/
|
||||
"The trading name must be the same for all purchase orders." /*)*/;
|
||||
static final String STOCK_MOVE_MULTI_INVOICE_IN_ATI = /*$$(*/
|
||||
"Unit prices in A.T.I and in W.T. can't be mix" /*)*/;
|
||||
static final String STOCK_MOVE_NO_INVOICE_GENERATED = /*$$(*/ "No invoice was generated" /*)*/;
|
||||
static final String STOCK_MOVE_GENERATE_INVOICE = /*$$(*/
|
||||
"The invoice for the stock move %s can't be generated because of this following error : %s" /*)*/;
|
||||
static final String OUTGOING_STOCK_MOVE_INVOICE_EXISTS = /*$$(*/
|
||||
"An invoice not canceled already exists for the outgoing stock move %s" /*)*/;
|
||||
static final String INCOMING_STOCK_MOVE_INVOICE_EXISTS = /*$$(*/
|
||||
"An invoice not canceled already exists for the incoming stock move %s" /*)*/;
|
||||
static final String STOCK_MOVE_AVAILABILITY_REQUEST_NOT_UPDATABLE = /*$$(*/
|
||||
"Please uncheck picking order edited box from this stock move from Cust. Shipment to prepare menu entry." /*)*/;
|
||||
|
||||
/** Stock move line service */
|
||||
static final String STOCK_MOVE_MISSING_SALE_ORDER = /*$$(*/
|
||||
"Missing link to sale order line (from sale order id = %s) for stock move line %s" /*)*/;
|
||||
|
||||
static final String STOCK_MOVE_MISSING_PURCHASE_ORDER = /*$$(*/
|
||||
"Missing purchase order with id %s for stock move line %s" /*)*/;
|
||||
|
||||
/** Batch Invoicing */
|
||||
static final String BATCH_INVOICING_1 = /*$$(*/ "Subscription invoice generation report :" /*)*/;
|
||||
|
||||
static final String BATCH_INVOICING_2 = /*$$(*/ "Order(s) processed" /*)*/;
|
||||
|
||||
/** Batch Outgoing stock move invoicing */
|
||||
String BATCH_OUTGOING_STOCK_MOVE_INVOICING_REPORT = /*$$(*/
|
||||
"Outgoing stock move invoicing report:" /*)*/;
|
||||
|
||||
String BATCH_OUTGOING_STOCK_MOVE_INVOICING_DONE_SINGULAR = /*$$(*/
|
||||
"%d outgoing stock move processed successfully," /*)*/;
|
||||
String BATCH_OUTGOING_STOCK_MOVE_INVOICING_DONE_PLURAL = /*$$(*/
|
||||
"%d outgoing stock moves processed successfully," /*)*/;
|
||||
|
||||
/** Batch Order invoicing */
|
||||
String BATCH_ORDER_INVOICING_REPORT = /*$$(*/ "Order invoicing report:" /*)*/;
|
||||
|
||||
String BATCH_ORDER_INVOICING_DONE_SINGULAR = /*$$(*/ "%d order invoiced successfully," /*)*/;
|
||||
String BATCH_ORDER_INVOICING_DONE_PLURAL = /*$$(*/ "%d orders invoiced successfully," /*)*/;
|
||||
|
||||
/** Mrp Line Service */
|
||||
static final String MRP_LINE_1 = /*$$(*/
|
||||
"No default supplier is defined for the product %s" /*)*/;
|
||||
|
||||
static final String MRP_MISSING_MRP_LINE_TYPE = /*$$(*/
|
||||
"No move type found for element : %s" /*)*/;
|
||||
|
||||
static final String MRP_MISSING_STOCK_LOCATION_VALID = /*$$(*/
|
||||
"No stock location valid. Please uncheck the chosen stock location 'is not in MRP'." /*)*/;
|
||||
|
||||
static final String MRP_NO_PRODUCT = /*$$(*/ "Please select an element to run calculation" /*)*/;
|
||||
|
||||
/** Sale order Stock Service Implement */
|
||||
static final String SO_NO_DELIVERY_STOCK_MOVE_TO_GENERATE = /*$$(*/
|
||||
"No delivery stock move to generate for this sale order" /*)*/;
|
||||
|
||||
static final String SO_ACTIVE_DELIVERY_STOCK_MOVE_ALREADY_EXISTS = /*$$(*/
|
||||
"An active stock move (%s) already exists for the sale order %s." /*)*/;
|
||||
static final String SO_CANT_REMOVED_DELIVERED_LINE = /*$$(*/
|
||||
"Can't remove delivered detail line %s." /*)*/;
|
||||
static final String SO_CANT_DECREASE_QTY_ON_DELIVERED_LINE = /*$$(*/
|
||||
"Quantity cannot be lower than already delivered quantity on detail line %s." /*)*/;
|
||||
static final String SO_MISSING_STOCK_LOCATION = /*$$(*/
|
||||
"Stock location is missing for the sale order %s." /*)*/;
|
||||
|
||||
/** Sale order Stock Service Implement */
|
||||
static final String PO_NO_DELIVERY_STOCK_MOVE_TO_GENERATE = /*$$(*/
|
||||
"No delivery stock move to generate for this purchase order" /*)*/;
|
||||
|
||||
/** Purchase Order Stock Service Implement */
|
||||
static final String PO_MISSING_STOCK_LOCATION = /*$$(*/
|
||||
"Stock location is missing for the purchase order %s." /*)*/;
|
||||
|
||||
/** Timetable Controller */
|
||||
static final String TIMETABLE_INVOICE_ALREADY_GENERATED = /*$$(*/
|
||||
"The invoice has already been generated." /*)*/;
|
||||
|
||||
static final String TIMETABLE_SALE_ORDER_NOT_CONFIRMED = /*$$(*/
|
||||
"Please confirm the sale order before invoicing." /*)*/;
|
||||
|
||||
/** Ventilate State Service */
|
||||
String VENTILATE_STATE_MISSING_ADVANCE_ACCOUNT = /*$$(*/
|
||||
"Please configure the advance payment account for the company %s" /*)*/;
|
||||
|
||||
/** Supply Chain Config */
|
||||
static final String SUPPLY_CHAIN_CONFIG = /*$$(*/
|
||||
"You must configure a Supply chain module for the company %s" /*)*/;
|
||||
|
||||
String SUPPLYCHAIN_MISSING_CANCEL_REASON_ON_CHANGING_SALE_ORDER = /*$$(*/
|
||||
"You must configure a cancel reason on changing sale order in app supplychain." /*)*/;
|
||||
|
||||
/** Subscription invoice */
|
||||
static final String TOTAL_SUBSCRIPTION_INVOICE_GENERATED = /*$$(*/
|
||||
"Total subscription invoice(s) generated: %s" /*)*/;
|
||||
|
||||
static final String SUBSCRIPTION_INVOICE_GENERATION_ERROR = /*$$(*/
|
||||
"Error generating subscription invoice(s): '%s'" /*)*/;
|
||||
|
||||
/** Interco Service */
|
||||
static final String INVOICE_MISSING_TYPE = /*$$(*/ "Invoice %s type is not filled." /*)*/;
|
||||
|
||||
/** Stock location line service supplychain impl */
|
||||
static final String LOCATION_LINE_RESERVED_QTY = /*$$(*/
|
||||
"Not enough quantity are available for reservation for product %s (%s)" /*)*/;
|
||||
|
||||
/** Reserved qty service */
|
||||
static final String LOCATION_LINE_NOT_ENOUGH_AVAILABLE_QTY = /*$$(*/
|
||||
"This operation cannot be performed. Available stock for product %s: %s, stock needed: %s. Please deallocate." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_NO_STOCK_MOVE = /*$$(*/
|
||||
"Please generate a stock move for this sale order before modifying allocated quantity." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_REQUEST_QTY_NEGATIVE = /*$$(*/
|
||||
"You cannot request reservation with a negative quantity." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_RESERVATION_QTY_NEGATIVE = /*$$(*/
|
||||
"Please do not enter negative quantity for reservation." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_REQUESTED_QTY_TOO_HIGH = /*$$(*/
|
||||
"The requested quantity must not be greater than the quantity in the stock move line %s." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_ALLOCATED_QTY_TOO_HIGH = /*$$(*/
|
||||
"The allocated quantity must not be greater than the quantity in the stock move line %s." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_QTY_NOT_AVAILABLE = /*$$(*/
|
||||
"This quantity is not available in stock." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_AVAILABILITY_REQUEST = /*$$(*/
|
||||
"The reservation for an availability requested stock move cannot be lowered." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_REQUESTED_QTY_TOO_LOW = /*$$(*/
|
||||
"The requested quantity must be greater than the already delivered quantity." /*)*/;
|
||||
|
||||
static final String SALE_ORDER_LINE_PRODUCT_NOT_STOCK_MANAGED = /*$$(*/
|
||||
"This product is not stock managed." /*)*/;
|
||||
|
||||
/** Account config supplychain service */
|
||||
static final String FORECASTED_INVOICE_CUSTOMER_ACCOUNT = /*$$(*/
|
||||
"You must configure a forecasted invoiced customer account for the company %s" /*)*/;
|
||||
|
||||
static final String FORECASTED_INVOICE_SUPPLIER_ACCOUNT = /*$$(*/
|
||||
"You must configure a forecasted invoiced supplier account for the company %s" /*)*/;
|
||||
|
||||
/** Accounting cut off service */
|
||||
static final String ACCOUNTING_CUT_OFF_GENERATION_REPORT = /*$$(*/
|
||||
"Accounting cut off generation report :" /*)*/;
|
||||
|
||||
static final String ACCOUNTING_CUT_OFF_STOCK_MOVE_PROCESSED = /*$$(*/
|
||||
"Stock move(s) processed" /*)*/;
|
||||
|
||||
public static final String SALE_ORDER_STOCK_MOVE_CREATED = /*$$(*/
|
||||
"Stock move %s has been created for this sale order" /*)*/;
|
||||
|
||||
static final String SUPPLYCHAIN_MRP_SEQUENCE_ERROR = /*$$(*/
|
||||
"The company %s doesn't have any configured sequence for MRP" /*)*/;
|
||||
|
||||
static final String STOCK_MOVE_VERIFY_PRODUCT_STOCK_ERROR = /*$$(*/
|
||||
"Product stock for %s is not enough for availability request" /*)*/;
|
||||
|
||||
static final String SALE_ORDER_ANALYTIC_DISTRIBUTION_ERROR = /*$$(*/
|
||||
"There is no analytic distribution on %s sale order line" /*)*/;
|
||||
|
||||
static final String PURCHASE_ORDER_ANALYTIC_DISTRIBUTION_ERROR = /*$$(*/
|
||||
"There is no analytic distribution on %s purchase order line" /*)*/;
|
||||
|
||||
static final String STOCK_MOVE_INVOICE_ERROR = /*$$(*/
|
||||
"No stockMoveLine remains to invoice" /*)*/;
|
||||
|
||||
static final String STOCK_MOVE_INVOICE_QTY_MAX = /*$$(*/
|
||||
"The quantity to invoice is greater than the quantity in the stock move" /*)*/;
|
||||
|
||||
static final String SALE_ORDER_COMPLETE_MANUALLY = /*$$(*/
|
||||
"There is at least one draft or planned stock move for this sale order." /*)*/;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.job;
|
||||
|
||||
import com.axelor.apps.base.job.ThreadedJob;
|
||||
import com.axelor.apps.base.job.UncheckedJobExecutionException;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplychainBatchRepository;
|
||||
import com.axelor.apps.supplychain.service.batch.SupplychainBatchService;
|
||||
import com.axelor.inject.Beans;
|
||||
import org.quartz.JobExecutionContext;
|
||||
|
||||
public class BillSubJob extends ThreadedJob {
|
||||
@Override
|
||||
public void executeInThread(JobExecutionContext context) {
|
||||
try {
|
||||
Beans.get(SupplychainBatchService.class).run(SupplychainBatchRepository.CODE_BATCH_BILL_SUB);
|
||||
} catch (Exception e) {
|
||||
throw new UncheckedJobExecutionException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.module;
|
||||
|
||||
import com.axelor.app.AxelorModule;
|
||||
import com.axelor.apps.account.db.repo.AnalyticMoveLineMngtRepository;
|
||||
import com.axelor.apps.account.service.AccountCustomerService;
|
||||
import com.axelor.apps.account.service.AccountingSituationServiceImpl;
|
||||
import com.axelor.apps.account.service.BudgetService;
|
||||
import com.axelor.apps.account.service.FixedAssetServiceImpl;
|
||||
import com.axelor.apps.account.service.invoice.InvoiceLineServiceImpl;
|
||||
import com.axelor.apps.account.service.invoice.InvoiceServiceImpl;
|
||||
import com.axelor.apps.account.service.invoice.workflow.cancel.WorkflowCancelServiceImpl;
|
||||
import com.axelor.apps.account.service.invoice.workflow.validate.WorkflowValidationServiceImpl;
|
||||
import com.axelor.apps.account.service.invoice.workflow.ventilate.WorkflowVentilationServiceImpl;
|
||||
import com.axelor.apps.account.service.payment.invoice.payment.InvoicePaymentToolServiceImpl;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderManagementRepository;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderLineServiceImpl;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderServiceImpl;
|
||||
import com.axelor.apps.purchase.service.PurchaseProductService;
|
||||
import com.axelor.apps.purchase.service.PurchaseProductServiceImpl;
|
||||
import com.axelor.apps.purchase.service.PurchaseRequestServiceImpl;
|
||||
import com.axelor.apps.purchase.service.SupplierCatalogService;
|
||||
import com.axelor.apps.purchase.service.SupplierCatalogServiceImpl;
|
||||
import com.axelor.apps.sale.db.repo.AdvancePaymentSaleRepository;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderManagementRepository;
|
||||
import com.axelor.apps.sale.service.AdvancePaymentServiceImpl;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeServiceImpl;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderCreateServiceImpl;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderLineServiceImpl;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderServiceImpl;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowServiceImpl;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineStockRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveManagementRepository;
|
||||
import com.axelor.apps.stock.service.LogisticalFormServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockCorrectionServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockLocationLineServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockLocationServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockMoveLineServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockMoveService;
|
||||
import com.axelor.apps.stock.service.StockMoveServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockRulesService;
|
||||
import com.axelor.apps.stock.service.StockRulesServiceImpl;
|
||||
import com.axelor.apps.supplychain.db.repo.AdvancePaymentSupplychainRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.AnalyticMoveLineSupplychainRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.MrpManagementRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.MrpRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.PurchaseOrderSupplychainRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.SaleOrderSupplychainRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.StockMoveLineSupplychainRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.StockMoveSupplychainRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplychainBatchRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplychainBatchSupplychainRepository;
|
||||
import com.axelor.apps.supplychain.service.AccountCustomerServiceSupplyChain;
|
||||
import com.axelor.apps.supplychain.service.AccountingCutOffService;
|
||||
import com.axelor.apps.supplychain.service.AccountingCutOffServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.AccountingSituationSupplychainService;
|
||||
import com.axelor.apps.supplychain.service.AccountingSituationSupplychainServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.AdvancePaymentServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.BudgetSupplychainService;
|
||||
import com.axelor.apps.supplychain.service.FixedAssetServiceSupplyChainImpl;
|
||||
import com.axelor.apps.supplychain.service.IntercoService;
|
||||
import com.axelor.apps.supplychain.service.IntercoServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.InvoiceLineSupplychainService;
|
||||
import com.axelor.apps.supplychain.service.InvoicePaymentToolServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.LogisticalFormSupplychainService;
|
||||
import com.axelor.apps.supplychain.service.LogisticalFormSupplychainServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.MrpLineService;
|
||||
import com.axelor.apps.supplychain.service.MrpLineServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.MrpService;
|
||||
import com.axelor.apps.supplychain.service.MrpServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.ProductStockLocationService;
|
||||
import com.axelor.apps.supplychain.service.ProductStockLocationServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.ProjectedStockService;
|
||||
import com.axelor.apps.supplychain.service.ProjectedStockServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.PurchaseOrderInvoiceService;
|
||||
import com.axelor.apps.supplychain.service.PurchaseOrderInvoiceServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.PurchaseOrderLineServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.PurchaseOrderServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.PurchaseOrderStockService;
|
||||
import com.axelor.apps.supplychain.service.PurchaseOrderStockServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.PurchaseRequestServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.ReservedQtyService;
|
||||
import com.axelor.apps.supplychain.service.ReservedQtyServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderComputeServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderCreateServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderInvoiceService;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderInvoiceServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderLineServiceSupplyChain;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderLineServiceSupplyChainImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderPurchaseService;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderPurchaseServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderReservedQtyService;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderReservedQtyServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderStockService;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderStockServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderWorkflowServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.StockCorrectionServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.StockLocationLineReservationService;
|
||||
import com.axelor.apps.supplychain.service.StockLocationLineReservationServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.StockLocationLineServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.StockLocationServiceSupplychain;
|
||||
import com.axelor.apps.supplychain.service.StockLocationServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.StockMoveInvoiceService;
|
||||
import com.axelor.apps.supplychain.service.StockMoveInvoiceServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.StockMoveLineServiceSupplychain;
|
||||
import com.axelor.apps.supplychain.service.StockMoveLineServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.StockMoveMultiInvoiceService;
|
||||
import com.axelor.apps.supplychain.service.StockMoveMultiInvoiceServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.StockMoveServiceSupplychain;
|
||||
import com.axelor.apps.supplychain.service.StockMoveServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.StockRulesServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.SupplychainSaleConfigService;
|
||||
import com.axelor.apps.supplychain.service.SupplychainSaleConfigServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.TimetableService;
|
||||
import com.axelor.apps.supplychain.service.TimetableServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.config.SupplyChainConfigService;
|
||||
import com.axelor.apps.supplychain.service.config.SupplyChainConfigServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.declarationofexchanges.DeclarationOfExchangesService;
|
||||
import com.axelor.apps.supplychain.service.declarationofexchanges.DeclarationOfExchangesServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.invoice.InvoiceServiceSupplychain;
|
||||
import com.axelor.apps.supplychain.service.invoice.InvoiceServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.invoice.SubscriptionInvoiceService;
|
||||
import com.axelor.apps.supplychain.service.invoice.SubscriptionInvoiceServiceImpl;
|
||||
import com.axelor.apps.supplychain.service.workflow.WorkflowCancelServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.workflow.WorkflowValidationServiceSupplychainImpl;
|
||||
import com.axelor.apps.supplychain.service.workflow.WorkflowVentilationServiceSupplychainImpl;
|
||||
|
||||
public class SupplychainModule extends AxelorModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(StockRulesService.class).to(StockRulesServiceImpl.class);
|
||||
bind(StockRulesServiceImpl.class).to(StockRulesServiceSupplychainImpl.class);
|
||||
bind(StockMoveService.class).to(StockMoveServiceImpl.class);
|
||||
bind(PurchaseOrderServiceImpl.class).to(PurchaseOrderServiceSupplychainImpl.class);
|
||||
bind(SaleOrderServiceImpl.class).to(SaleOrderServiceSupplychainImpl.class);
|
||||
bind(SaleOrderCreateServiceImpl.class).to(SaleOrderCreateServiceSupplychainImpl.class);
|
||||
bind(SaleOrderComputeServiceImpl.class).to(SaleOrderComputeServiceSupplychainImpl.class);
|
||||
bind(SaleOrderWorkflowServiceImpl.class).to(SaleOrderWorkflowServiceSupplychainImpl.class);
|
||||
bind(PurchaseOrderInvoiceService.class).to(PurchaseOrderInvoiceServiceImpl.class);
|
||||
bind(SaleOrderInvoiceService.class).to(SaleOrderInvoiceServiceImpl.class);
|
||||
bind(SaleOrderPurchaseService.class).to(SaleOrderPurchaseServiceImpl.class);
|
||||
bind(StockMoveInvoiceService.class).to(StockMoveInvoiceServiceImpl.class);
|
||||
bind(SaleOrderManagementRepository.class).to(SaleOrderSupplychainRepository.class);
|
||||
bind(StockMoveServiceImpl.class).to(StockMoveServiceSupplychainImpl.class);
|
||||
bind(SaleOrderLineServiceImpl.class).to(SaleOrderLineServiceSupplyChainImpl.class);
|
||||
bind(AdvancePaymentSaleRepository.class).to(AdvancePaymentSupplychainRepository.class);
|
||||
bind(AdvancePaymentServiceImpl.class).to(AdvancePaymentServiceSupplychainImpl.class);
|
||||
bind(MrpService.class).to(MrpServiceImpl.class);
|
||||
bind(MrpLineService.class).to(MrpLineServiceImpl.class);
|
||||
bind(AnalyticMoveLineMngtRepository.class).to(AnalyticMoveLineSupplychainRepository.class);
|
||||
bind(StockMoveLineServiceImpl.class).to(StockMoveLineServiceSupplychainImpl.class);
|
||||
bind(BudgetService.class).to(BudgetSupplychainService.class);
|
||||
bind(InvoiceLineServiceImpl.class).to(InvoiceLineSupplychainService.class);
|
||||
bind(SaleOrderStockService.class).to(SaleOrderStockServiceImpl.class);
|
||||
bind(PurchaseOrderManagementRepository.class).to(PurchaseOrderSupplychainRepository.class);
|
||||
bind(AppSupplychainService.class).to(AppSupplychainServiceImpl.class);
|
||||
bind(SupplychainSaleConfigService.class).to(SupplychainSaleConfigServiceImpl.class);
|
||||
bind(AccountCustomerService.class).to(AccountCustomerServiceSupplyChain.class);
|
||||
bind(AccountingSituationServiceImpl.class).to(AccountingSituationSupplychainServiceImpl.class);
|
||||
bind(AccountingSituationSupplychainService.class)
|
||||
.to(AccountingSituationSupplychainServiceImpl.class);
|
||||
bind(StockLocationLineServiceImpl.class).to(StockLocationLineServiceSupplychainImpl.class);
|
||||
bind(InvoiceServiceImpl.class).to(InvoiceServiceSupplychainImpl.class);
|
||||
bind(InvoicePaymentToolServiceImpl.class).to(InvoicePaymentToolServiceSupplychainImpl.class);
|
||||
bind(WorkflowVentilationServiceImpl.class).to(WorkflowVentilationServiceSupplychainImpl.class);
|
||||
bind(WorkflowCancelServiceImpl.class).to(WorkflowCancelServiceSupplychainImpl.class);
|
||||
bind(WorkflowValidationServiceImpl.class).to(WorkflowValidationServiceSupplychainImpl.class);
|
||||
bind(IntercoService.class).to(IntercoServiceImpl.class);
|
||||
bind(LogisticalFormServiceImpl.class).to(LogisticalFormSupplychainServiceImpl.class);
|
||||
bind(LogisticalFormSupplychainService.class).to(LogisticalFormSupplychainServiceImpl.class);
|
||||
bind(PurchaseProductService.class).to(PurchaseProductServiceImpl.class);
|
||||
bind(StockLocationLineServiceImpl.class).to(StockLocationLineServiceSupplychainImpl.class);
|
||||
bind(SaleOrderLineServiceSupplyChain.class).to(SaleOrderLineServiceSupplyChainImpl.class);
|
||||
bind(SupplyChainConfigService.class).to(SupplyChainConfigServiceImpl.class);
|
||||
bind(SupplychainBatchRepository.class).to(SupplychainBatchSupplychainRepository.class);
|
||||
bind(SubscriptionInvoiceService.class).to(SubscriptionInvoiceServiceImpl.class);
|
||||
bind(TimetableService.class).to(TimetableServiceImpl.class);
|
||||
bind(InvoiceServiceSupplychain.class).to(InvoiceServiceSupplychainImpl.class);
|
||||
bind(StockMoveServiceSupplychain.class).to(StockMoveServiceSupplychainImpl.class);
|
||||
bind(StockMoveLineServiceSupplychain.class).to(StockMoveLineServiceSupplychainImpl.class);
|
||||
bind(StockLocationServiceImpl.class).to(StockLocationServiceSupplychainImpl.class);
|
||||
bind(StockLocationServiceSupplychain.class).to(StockLocationServiceSupplychainImpl.class);
|
||||
bind(SupplierCatalogService.class).to(SupplierCatalogServiceImpl.class);
|
||||
bind(ReservedQtyService.class).to(ReservedQtyServiceImpl.class);
|
||||
bind(PurchaseOrderLineServiceImpl.class).to(PurchaseOrderLineServiceSupplychainImpl.class);
|
||||
bind(PurchaseOrderStockService.class).to(PurchaseOrderStockServiceImpl.class);
|
||||
bind(AccountingCutOffService.class).to(AccountingCutOffServiceImpl.class);
|
||||
bind(DeclarationOfExchangesService.class).to(DeclarationOfExchangesServiceImpl.class);
|
||||
bind(StockMoveManagementRepository.class).to(StockMoveSupplychainRepository.class);
|
||||
bind(StockCorrectionServiceImpl.class).to(StockCorrectionServiceSupplychainImpl.class);
|
||||
bind(StockMoveMultiInvoiceService.class).to(StockMoveMultiInvoiceServiceImpl.class);
|
||||
bind(MrpRepository.class).to(MrpManagementRepository.class);
|
||||
bind(SaleOrderReservedQtyService.class).to(SaleOrderReservedQtyServiceImpl.class);
|
||||
bind(StockLocationLineReservationService.class)
|
||||
.to(StockLocationLineReservationServiceImpl.class);
|
||||
bind(PurchaseRequestServiceImpl.class).to(PurchaseRequestServiceSupplychainImpl.class);
|
||||
bind(ProductStockLocationService.class).to(ProductStockLocationServiceImpl.class);
|
||||
bind(ProjectedStockService.class).to(ProjectedStockServiceImpl.class);
|
||||
bind(FixedAssetServiceImpl.class).to(FixedAssetServiceSupplyChainImpl.class);
|
||||
bind(StockMoveLineStockRepository.class).to(StockMoveLineSupplychainRepository.class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.report;
|
||||
|
||||
public interface IReport {
|
||||
|
||||
public static final String MRP_WEEKS = "MrpWeeks.rptdesign";
|
||||
public static final String MRP_LIST = "MrpList.rptdesign";
|
||||
public static final String PACKING_LIST = "PackingList.rptdesign";
|
||||
public static final String DECLARATION_OF_EXCHANGES_OF_GOODS =
|
||||
"DeclarationOfExchangesOfGoods.rptdesign";
|
||||
public static final String DECLARATION_OF_SERVICES = "DeclarationOfServices.rptdesign";
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.report;
|
||||
|
||||
public class ITranslation {
|
||||
|
||||
/** MrpWeeks.rptdesign */
|
||||
public static final String MRP_WEEKS_TITLE = /*$$(*/ "MrpWeeks.title"; /*)*/
|
||||
|
||||
public static final String MRP_WEEKS_STOCK_LOCATION = /*$$(*/ "MrpWeeks.stockLocation"; /*)*/
|
||||
public static final String MRP_WEEKS_WEEK = /*$$(*/ "MrpWeeks.week"; /*)*/
|
||||
public static final String MRP_WEEKS_PRODUCT = /*$$(*/ "MrpWeeks.product"; /*)*/
|
||||
public static final String MRP_WEEKS_UNIT = /*$$(*/ "MrpWeeks.unit"; /*)*/
|
||||
public static final String MRP_WEEKS_MAX_LEVEL = /*$$(*/ "MrpWeeks.maxLevel"; /*)*/
|
||||
public static final String MRP_WEEKS_CREATED_ON = /*$$(*/ "MrpWeeks.createdOn"; /*)*/
|
||||
public static final String MRP_WEEKS_END_DATE = /*$$(*/ "MrpWeeks.endDate"; /*)*/
|
||||
|
||||
/** MrpList.rptdesign */
|
||||
public static final String MRP_LIST_TITLE = /*$$(*/ "MrpList.title"; /*)*/
|
||||
|
||||
public static final String MRP_LIST_CREATED_ON = /*$$(*/ "MrpList.createdOn"; /*)*/
|
||||
public static final String MRP_LIST_END_DATE = /*$$(*/ "MrpList.endDate"; /*)*/
|
||||
public static final String MRP_LIST_STOCK_LOCATION = /*$$(*/ "MrpList.stockLocation"; /*)*/
|
||||
|
||||
public static final String MRP_LIST_PRODUCTS_TITLE = /*$$(*/ "MrpList.productsTitle"; /*)*/
|
||||
public static final String MRP_LIST_PRODUCT_CATEGORIES_TITLE = /*$$(*/
|
||||
"MrpList.productCategoriesTitle"; /*)*/
|
||||
public static final String MRP_LIST_PRODUCT_FAMILIES_TITLE = /*$$(*/
|
||||
"MrpList.productFamiliesTitle"; /*)*/
|
||||
public static final String MRP_LIST_SALE_ORDERS_TITLE = /*$$(*/ "MrpList.saleOrdersTitle"; /*)*/
|
||||
public static final String MRP_LIST_FORECASTS_TITLE = /*$$(*/ "MrpList.forecastsTitle"; /*)*/
|
||||
|
||||
public static final String MRP_LIST_CODE = /*$$(*/ "MrpList.code"; /*)*/
|
||||
public static final String MRP_LIST_NAME = /*$$(*/ "MrpList.name"; /*)*/
|
||||
public static final String MRP_LIST_PRODUCT_CATEGORY = /*$$(*/ "MrpList.productCategory"; /*)*/
|
||||
public static final String MRP_LIST_PRODUCT_FAMILY = /*$$(*/ "MrpList.productFamily"; /*)*/
|
||||
public static final String MRP_LIST_PRODUCT_TYPE = /*$$(*/ "MrpList.productType"; /*)*/
|
||||
public static final String MRP_LIST_SALE_PRICE = /*$$(*/ "MrpList.salePrice"; /*)*/
|
||||
public static final String MRP_LIST_UNIT = /*$$(*/ "MrpList.unit"; /*)*/
|
||||
public static final String MRP_LIST_PARENT_PRODUCT_CATEGORY = /*$$(*/
|
||||
"MrpList.parentProductCategory"; /*)*/
|
||||
public static final String MRP_LIST_PRODUCT = /*$$(*/ "MrpList.product"; /*)*/
|
||||
public static final String MRP_LIST_QTY = /*$$(*/ "MrpList.qty"; /*)*/
|
||||
public static final String MRP_LIST_PRICE = /*$$(*/ "MrpList.price"; /*)*/
|
||||
public static final String MRP_LIST_TOTAL_WT = /*$$(*/ "MrpList.totalWT"; /*)*/
|
||||
public static final String MRP_LIST_TOTAL_ATI = /*$$(*/ "MrpList.totalATI"; /*)*/
|
||||
public static final String MRP_LIST_FORECAST_DATE = /*$$(*/ "MrpList.forecastDate"; /*)*/
|
||||
public static final String MRP_LIST_PARTNER = /*$$(*/ "MrpList.partner"; /*)*/
|
||||
|
||||
public static final String MRP_LIST_TYPE = /*$$(*/ "MrpList.type"; /*)*/
|
||||
public static final String MRP_LIST_MATURITY_DATE = /*$$(*/ "MrpList.maturityDate"; /*)*/
|
||||
public static final String MRP_LIST_CUMULATIVE_QTY = /*$$(*/ "MrpList.cumulativeQty"; /*)*/
|
||||
public static final String MRP_LIST_MIN_QTY = /*$$(*/ "MrpList.minQty"; /*)*/
|
||||
public static final String MRP_LIST_MAX_LEVEL = /*$$(*/ "MrpList.maxLevel"; /*)*/
|
||||
public static final String MRP_LIST_RELATED_TO = /*$$(*/ "MrpList.relatedTo"; /*)*/
|
||||
|
||||
/*
|
||||
* Packing list
|
||||
*/
|
||||
public static final String PACKING_LIST = /*$$(*/ "LogisticalForm.packingList"; /*)*/
|
||||
public static final String PACKING_LIST_PACKAGING_NUMBER = /*$$(*/
|
||||
"LogisticalForm.packagingNumber"; /*)*/
|
||||
public static final String PACKING_LIST_DATE = /*$$(*/ "LogisticalForm.date"; /*)*/
|
||||
public static final String PACKING_LIST_CUSTOMER_CODE = /*$$(*/
|
||||
"LogisticalForm.customerCode"; /*)*/
|
||||
public static final String PACKING_LIST_CUSTOMER_NAME = /*$$(*/
|
||||
"LogisticalForm.customerName"; /*)*/
|
||||
public static final String PACKING_LIST_ITEM = /*$$(*/ "LogisticalForm.item"; /*)*/
|
||||
public static final String PACKING_LIST_ITEM_DESCRIPTION = /*$$(*/
|
||||
"LogisticalForm.itemDescription"; /*)*/
|
||||
public static final String PACKING_LIST_PARCEL_LINE_FORMAT = /*$$(*/
|
||||
"LogisticalFormLine.parcelNo <strong>{0}</strong> - LogisticalFormLine.dimensions <strong>{1}</strong> - LogisticalFormLine.grossMass ({2}): <strong>{3}</strong>" /*)*/;
|
||||
public static final String PACKING_LIST_PALLET_LINE_FORMAT = /*$$(*/
|
||||
"LogisticalFormLine.palletNo <strong>{0}</strong> - LogisticalFormLine.dimensions <strong>{1}</strong> - LogisticalFormLine.grossMass ({2}): <strong>{3}</strong>" /*)*/;
|
||||
public static final String PACKING_LIST_DETAIL_LINE_FORMAT = /*$$(*/
|
||||
"LogisticalFormLine.stockMoveNo {0} / LogisticalFormLine.refNo {1}" /*)*/;
|
||||
public static final String PACKING_LIST_TOTAL_NET_MASS = /*$$(*/
|
||||
"LogisticalForm.totalNetMass" /*)*/;
|
||||
public static final String PACKING_LIST_TOTAL_FORMAT = /*$$(*/
|
||||
"LogisticalFormLine.numberOfParcels/Pallets: {0} - LogisticalForm.totalGrossMass ({1}): {2}" /*)*/;
|
||||
public static final String PACKING_LIST_SHIPPING_COMMENTS = /*$$(*/
|
||||
"LogisticalForm.shippingComments"; /*)*/
|
||||
public static final String PACKING_LIST_QTY = /*$$(*/ "LogisticalFormLine.qty"; /*)*/
|
||||
|
||||
/*
|
||||
* Declaration of Exchanges
|
||||
*/
|
||||
public static final String DECLARATION_OF_EXCHANGES_INTRODUCTION = /*$$(*/ "Introduction"; /*)*/
|
||||
public static final String DECLARATION_OF_EXCHANGES_EXPEDITION = /*$$(*/ "Expedition"; /*)*/
|
||||
public static final String
|
||||
DECLARATION_OF_EXCHANGES_OF_GOODS_BETWEEN_MEMBER_STATES_OF_THE_EUROPEAN_COMMUNITY = /*$$(*/
|
||||
"DECLARATION OF EXCHANGES OF GOODS BETWEEN MEMBER STATES OF THE EUROPEAN COMMUNITY"; /*)*/
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AccountingSituation;
|
||||
import com.axelor.apps.account.db.repo.AccountingSituationRepository;
|
||||
import com.axelor.apps.account.service.AccountCustomerService;
|
||||
import com.axelor.apps.account.service.AccountingSituationService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
|
||||
public class AccountCustomerServiceSupplyChain extends AccountCustomerService {
|
||||
|
||||
@Inject
|
||||
public AccountCustomerServiceSupplyChain(
|
||||
AccountingSituationService accountingSituationService,
|
||||
AccountingSituationRepository accSituationRepo,
|
||||
AppBaseService appBaseService) {
|
||||
super(accountingSituationService, accSituationRepo, appBaseService);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public AccountingSituation updateAccountingSituationCustomerAccount(
|
||||
AccountingSituation accountingSituation,
|
||||
boolean updateCustAccount,
|
||||
boolean updateDueCustAccount,
|
||||
boolean updateDueDebtRecoveryCustAccount)
|
||||
throws AxelorException {
|
||||
|
||||
accountingSituation =
|
||||
super.updateAccountingSituationCustomerAccount(
|
||||
accountingSituation,
|
||||
updateCustAccount,
|
||||
updateDueCustAccount,
|
||||
updateDueDebtRecoveryCustAccount);
|
||||
|
||||
if (updateCustAccount) {
|
||||
accountingSituationService.updateCustomerCredit(accountingSituation.getPartner());
|
||||
}
|
||||
|
||||
return accountingSituation;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Move;
|
||||
import com.axelor.apps.base.db.Batch;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.meta.CallMethod;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface AccountingCutOffService {
|
||||
|
||||
public List<StockMove> getStockMoves(
|
||||
Company company,
|
||||
int accountingCutOffTypeSelect,
|
||||
LocalDate moveDate,
|
||||
Integer limit,
|
||||
Integer offset);
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public List<Move> generateCutOffMoves(
|
||||
StockMove stockMove,
|
||||
LocalDate moveDate,
|
||||
LocalDate reverseMoveDate,
|
||||
int accountingCutOffTypeSelect,
|
||||
boolean recoveredTax,
|
||||
boolean ati,
|
||||
String moveDescription,
|
||||
boolean includeNotStockManagedProduct)
|
||||
throws AxelorException;
|
||||
|
||||
public Move generateCutOffMove(
|
||||
StockMove stockMove,
|
||||
List<StockMoveLine> sortedStockMoveLine,
|
||||
LocalDate moveDate,
|
||||
LocalDate originDate,
|
||||
boolean isPurchase,
|
||||
boolean recoveredTax,
|
||||
boolean ati,
|
||||
String moveDescription,
|
||||
boolean includeNotStockManagedProduct,
|
||||
boolean isReverse)
|
||||
throws AxelorException;
|
||||
|
||||
@CallMethod
|
||||
List<Long> getStockMoveLines(Batch batch);
|
||||
}
|
||||
@ -0,0 +1,991 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 static com.axelor.apps.base.service.administration.AbstractBatch.FETCH_LIMIT;
|
||||
|
||||
import com.axelor.apps.account.db.Account;
|
||||
import com.axelor.apps.account.db.AccountConfig;
|
||||
import com.axelor.apps.account.db.AccountManagement;
|
||||
import com.axelor.apps.account.db.AnalyticDistributionTemplate;
|
||||
import com.axelor.apps.account.db.AnalyticMoveLine;
|
||||
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.TaxLine;
|
||||
import com.axelor.apps.account.db.repo.AccountManagementRepository;
|
||||
import com.axelor.apps.account.db.repo.AccountRepository;
|
||||
import com.axelor.apps.account.db.repo.AnalyticMoveLineRepository;
|
||||
import com.axelor.apps.account.db.repo.JournalRepository;
|
||||
import com.axelor.apps.account.db.repo.MoveRepository;
|
||||
import com.axelor.apps.account.exception.IExceptionMessage;
|
||||
import com.axelor.apps.account.service.AccountManagementAccountService;
|
||||
import com.axelor.apps.account.service.AnalyticMoveLineService;
|
||||
import com.axelor.apps.account.service.ReconcileService;
|
||||
import com.axelor.apps.account.service.TaxAccountService;
|
||||
import com.axelor.apps.account.service.app.AppAccountService;
|
||||
import com.axelor.apps.account.service.invoice.generator.line.InvoiceLineManagement;
|
||||
import com.axelor.apps.account.service.move.MoveCreateService;
|
||||
import com.axelor.apps.account.service.move.MoveLineService;
|
||||
import com.axelor.apps.account.service.move.MoveToolService;
|
||||
import com.axelor.apps.account.service.move.MoveValidateService;
|
||||
import com.axelor.apps.base.db.Batch;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Currency;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.repo.AppAccountRepository;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
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.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.stock.db.InventoryLine;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.InventoryLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplychainBatchRepository;
|
||||
import com.axelor.apps.supplychain.service.config.AccountConfigSupplychainService;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AccountingCutOffServiceImpl implements AccountingCutOffService {
|
||||
|
||||
protected StockMoveRepository stockMoverepository;
|
||||
protected StockMoveLineRepository stockMoveLineRepository;
|
||||
protected MoveCreateService moveCreateService;
|
||||
protected MoveLineService moveLineService;
|
||||
protected AccountConfigSupplychainService accountConfigSupplychainService;
|
||||
protected SaleOrderRepository saleOrderRepository;
|
||||
protected PurchaseOrderRepository purchaseOrderRepository;
|
||||
protected MoveToolService moveToolService;
|
||||
protected AccountManagementAccountService accountManagementAccountService;
|
||||
protected TaxAccountService taxAccountService;
|
||||
protected AppAccountService appAccountService;
|
||||
protected AnalyticMoveLineService analyticMoveLineService;
|
||||
protected MoveRepository moveRepository;
|
||||
protected MoveValidateService moveValidateService;
|
||||
protected UnitConversionService unitConversionService;
|
||||
protected AnalyticMoveLineRepository analyticMoveLineRepository;
|
||||
protected ReconcileService reconcileService;
|
||||
protected int counter = 0;
|
||||
|
||||
@Inject
|
||||
public AccountingCutOffServiceImpl(
|
||||
StockMoveRepository stockMoverepository,
|
||||
StockMoveLineRepository stockMoveLineRepository,
|
||||
MoveCreateService moveCreateService,
|
||||
MoveLineService moveLineService,
|
||||
AccountConfigSupplychainService accountConfigSupplychainService,
|
||||
SaleOrderRepository saleOrderRepository,
|
||||
PurchaseOrderRepository purchaseOrderRepository,
|
||||
MoveToolService moveToolService,
|
||||
AccountManagementAccountService accountManagementAccountService,
|
||||
TaxAccountService taxAccountService,
|
||||
AppAccountService appAccountService,
|
||||
AnalyticMoveLineService analyticMoveLineService,
|
||||
MoveRepository moveRepository,
|
||||
MoveValidateService moveValidateService,
|
||||
UnitConversionService unitConversionService,
|
||||
AnalyticMoveLineRepository analyticMoveLineRepository,
|
||||
ReconcileService reconcileService) {
|
||||
|
||||
this.stockMoverepository = stockMoverepository;
|
||||
this.stockMoveLineRepository = stockMoveLineRepository;
|
||||
this.moveCreateService = moveCreateService;
|
||||
this.moveLineService = moveLineService;
|
||||
this.accountConfigSupplychainService = accountConfigSupplychainService;
|
||||
this.saleOrderRepository = saleOrderRepository;
|
||||
this.purchaseOrderRepository = purchaseOrderRepository;
|
||||
this.moveToolService = moveToolService;
|
||||
this.accountManagementAccountService = accountManagementAccountService;
|
||||
this.taxAccountService = taxAccountService;
|
||||
this.appAccountService = appAccountService;
|
||||
this.analyticMoveLineService = analyticMoveLineService;
|
||||
this.moveRepository = moveRepository;
|
||||
this.moveValidateService = moveValidateService;
|
||||
this.unitConversionService = unitConversionService;
|
||||
this.analyticMoveLineRepository = analyticMoveLineRepository;
|
||||
this.reconcileService = reconcileService;
|
||||
}
|
||||
|
||||
public List<StockMove> getStockMoves(
|
||||
Company company,
|
||||
int accountingCutOffTypeSelect,
|
||||
LocalDate moveDate,
|
||||
Integer limit,
|
||||
Integer offset) {
|
||||
|
||||
int stockMoveTypeSelect = 0;
|
||||
|
||||
if (accountingCutOffTypeSelect
|
||||
== SupplychainBatchRepository.ACCOUNTING_CUT_OFF_TYPE_SUPPLIER_INVOICES) {
|
||||
stockMoveTypeSelect = StockMoveRepository.TYPE_INCOMING;
|
||||
} else if (accountingCutOffTypeSelect
|
||||
== SupplychainBatchRepository.ACCOUNTING_CUT_OFF_TYPE_CUSTOMER_INVOICES) {
|
||||
stockMoveTypeSelect = StockMoveRepository.TYPE_OUTGOING;
|
||||
}
|
||||
|
||||
String queryStr =
|
||||
" self.statusSelect = :stockMoveStatusRealized and self.realDate <= :moveDate "
|
||||
+ "AND self.typeSelect = :stockMoveType ";
|
||||
|
||||
if (company != null) {
|
||||
queryStr += "AND self.company.id = :companyId";
|
||||
}
|
||||
|
||||
Query<StockMove> query =
|
||||
stockMoverepository
|
||||
.all()
|
||||
.filter(queryStr)
|
||||
.bind("stockMoveStatusRealized", StockMoveRepository.STATUS_REALIZED)
|
||||
.bind("stockMoveType", stockMoveTypeSelect)
|
||||
.bind("moveDate", moveDate);
|
||||
|
||||
if (company != null) {
|
||||
query.bind("companyId", company.getId());
|
||||
}
|
||||
|
||||
if (limit != null && offset != null) {
|
||||
return query.order("id").fetch(limit, offset);
|
||||
}
|
||||
|
||||
return query.order("id").fetch();
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public List<Move> generateCutOffMoves(
|
||||
StockMove stockMove,
|
||||
LocalDate moveDate,
|
||||
LocalDate reverseMoveDate,
|
||||
int accountingCutOffTypeSelect,
|
||||
boolean recoveredTax,
|
||||
boolean ati,
|
||||
String moveDescription,
|
||||
boolean includeNotStockManagedProduct)
|
||||
throws AxelorException {
|
||||
|
||||
List<Move> moveList = new ArrayList<>();
|
||||
|
||||
List<StockMoveLine> stockMoveLineSortedList = stockMove.getStockMoveLineList();
|
||||
Collections.sort(stockMoveLineSortedList, Comparator.comparing(StockMoveLine::getSequence));
|
||||
|
||||
Move move =
|
||||
generateCutOffMove(
|
||||
stockMove,
|
||||
stockMoveLineSortedList,
|
||||
moveDate,
|
||||
moveDate,
|
||||
accountingCutOffTypeSelect
|
||||
== SupplychainBatchRepository.ACCOUNTING_CUT_OFF_TYPE_SUPPLIER_INVOICES,
|
||||
recoveredTax,
|
||||
ati,
|
||||
moveDescription,
|
||||
includeNotStockManagedProduct,
|
||||
false);
|
||||
|
||||
if (move == null) {
|
||||
return null;
|
||||
}
|
||||
moveList.add(move);
|
||||
|
||||
Move reverseMove =
|
||||
generateCutOffMove(
|
||||
stockMove,
|
||||
stockMoveLineSortedList,
|
||||
reverseMoveDate,
|
||||
moveDate,
|
||||
accountingCutOffTypeSelect
|
||||
== SupplychainBatchRepository.ACCOUNTING_CUT_OFF_TYPE_SUPPLIER_INVOICES,
|
||||
recoveredTax,
|
||||
ati,
|
||||
moveDescription,
|
||||
includeNotStockManagedProduct,
|
||||
true);
|
||||
|
||||
if (reverseMove == null) {
|
||||
return null;
|
||||
}
|
||||
moveList.add(reverseMove);
|
||||
|
||||
reconcile(move, reverseMove);
|
||||
|
||||
return moveList;
|
||||
}
|
||||
|
||||
public Move generateCutOffMove(
|
||||
StockMove stockMove,
|
||||
List<StockMoveLine> sortedStockMoveLine,
|
||||
LocalDate moveDate,
|
||||
LocalDate originDate,
|
||||
boolean isPurchase,
|
||||
boolean recoveredTax,
|
||||
boolean ati,
|
||||
String moveDescription,
|
||||
boolean includeNotStockManagedProduct,
|
||||
boolean isReverse)
|
||||
throws AxelorException {
|
||||
|
||||
if (moveDate == null
|
||||
|| stockMove.getOriginTypeSelect() == null
|
||||
|| stockMove.getOriginId() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Company company = stockMove.getCompany();
|
||||
|
||||
AccountConfig accountConfig = accountConfigSupplychainService.getAccountConfig(company);
|
||||
|
||||
Partner partner = stockMove.getPartner();
|
||||
Account partnerAccount = null;
|
||||
|
||||
Currency currency = null;
|
||||
if (StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())
|
||||
&& stockMove.getOriginId() != null) {
|
||||
SaleOrder saleOrder = saleOrderRepository.find(stockMove.getOriginId());
|
||||
currency = saleOrder.getCurrency();
|
||||
if (partner == null) {
|
||||
partner = saleOrder.getClientPartner();
|
||||
}
|
||||
partnerAccount = accountConfigSupplychainService.getForecastedInvCustAccount(accountConfig);
|
||||
}
|
||||
if (StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())
|
||||
&& stockMove.getOriginId() != null) {
|
||||
PurchaseOrder purchaseOrder = purchaseOrderRepository.find(stockMove.getOriginId());
|
||||
currency = purchaseOrder.getCurrency();
|
||||
if (partner == null) {
|
||||
partner = purchaseOrder.getSupplierPartner();
|
||||
}
|
||||
partnerAccount = accountConfigSupplychainService.getForecastedInvSuppAccount(accountConfig);
|
||||
}
|
||||
|
||||
String origin = stockMove.getStockMoveSeq();
|
||||
|
||||
Move move =
|
||||
moveCreateService.createMove(
|
||||
accountConfigSupplychainService.getAutoMiscOpeJournal(accountConfig),
|
||||
company,
|
||||
currency,
|
||||
partner,
|
||||
moveDate,
|
||||
null,
|
||||
MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);
|
||||
|
||||
counter = 0;
|
||||
|
||||
this.generateMoveLines(
|
||||
move,
|
||||
stockMove.getStockMoveLineList(),
|
||||
origin,
|
||||
isPurchase,
|
||||
recoveredTax,
|
||||
ati,
|
||||
moveDescription,
|
||||
isReverse,
|
||||
originDate,
|
||||
includeNotStockManagedProduct);
|
||||
|
||||
this.generatePartnerMoveLine(move, origin, partnerAccount, moveDescription, originDate);
|
||||
|
||||
if (move.getMoveLineList() != null && !move.getMoveLineList().isEmpty()) {
|
||||
move.setStockMove(stockMove);
|
||||
moveValidateService.validate(move);
|
||||
} else {
|
||||
moveRepository.remove(move);
|
||||
return null;
|
||||
}
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
protected List<MoveLine> generateMoveLines(
|
||||
Move move,
|
||||
List<StockMoveLine> stockMoveLineList,
|
||||
String origin,
|
||||
boolean isPurchase,
|
||||
boolean recoveredTax,
|
||||
boolean ati,
|
||||
String moveDescription,
|
||||
boolean isReverse,
|
||||
LocalDate originDate,
|
||||
boolean includeNotStockManagedProduct)
|
||||
throws AxelorException {
|
||||
|
||||
if (stockMoveLineList != null) {
|
||||
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
|
||||
Product product = stockMoveLine.getProduct();
|
||||
|
||||
if (checkStockMoveLine(stockMoveLine, product, includeNotStockManagedProduct)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
generateProductMoveLine(
|
||||
move,
|
||||
stockMoveLine,
|
||||
origin,
|
||||
isPurchase,
|
||||
recoveredTax,
|
||||
ati,
|
||||
moveDescription,
|
||||
isReverse,
|
||||
originDate);
|
||||
}
|
||||
}
|
||||
|
||||
return move.getMoveLineList();
|
||||
}
|
||||
|
||||
protected boolean checkStockMoveLine(
|
||||
StockMoveLine stockMoveLine, Product product, boolean includeNotStockManagedProduct) {
|
||||
return (stockMoveLine.getRealQty().compareTo(BigDecimal.ZERO) == 0
|
||||
|| product == null
|
||||
|| (!includeNotStockManagedProduct && !product.getStockManaged()))
|
||||
|| (stockMoveLine.getRealQty().compareTo(stockMoveLine.getQtyInvoiced()) == 0);
|
||||
}
|
||||
|
||||
protected MoveLine generateProductMoveLine(
|
||||
Move move,
|
||||
StockMoveLine stockMoveLine,
|
||||
String origin,
|
||||
boolean isPurchase,
|
||||
boolean recoveredTax,
|
||||
boolean ati,
|
||||
String moveDescription,
|
||||
boolean isReverse,
|
||||
LocalDate originDate)
|
||||
throws AxelorException {
|
||||
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
PurchaseOrderLine purchaseOrderLine = stockMoveLine.getPurchaseOrderLine();
|
||||
Company company = move.getCompany();
|
||||
LocalDate moveDate = move.getDate();
|
||||
Partner partner = move.getPartner();
|
||||
|
||||
boolean isFixedAssets = false;
|
||||
BigDecimal amountInCurrency = null;
|
||||
BigDecimal totalQty = null;
|
||||
BigDecimal notInvoicedQty = null;
|
||||
|
||||
if (isPurchase && purchaseOrderLine != null) {
|
||||
totalQty = purchaseOrderLine.getQty();
|
||||
|
||||
notInvoicedQty =
|
||||
unitConversionService.convert(
|
||||
stockMoveLine.getUnit(),
|
||||
purchaseOrderLine.getUnit(),
|
||||
stockMoveLine.getRealQty().subtract(stockMoveLine.getQtyInvoiced()),
|
||||
stockMoveLine.getRealQty().scale(),
|
||||
purchaseOrderLine.getProduct());
|
||||
|
||||
isFixedAssets = purchaseOrderLine.getFixedAssets();
|
||||
if (ati && !recoveredTax) {
|
||||
amountInCurrency = purchaseOrderLine.getInTaxTotal();
|
||||
} else {
|
||||
amountInCurrency = purchaseOrderLine.getExTaxTotal();
|
||||
}
|
||||
}
|
||||
if (!isPurchase && saleOrderLine != null) {
|
||||
totalQty = saleOrderLine.getQty();
|
||||
|
||||
notInvoicedQty =
|
||||
unitConversionService.convert(
|
||||
stockMoveLine.getUnit(),
|
||||
saleOrderLine.getUnit(),
|
||||
stockMoveLine.getRealQty().subtract(stockMoveLine.getQtyInvoiced()),
|
||||
stockMoveLine.getRealQty().scale(),
|
||||
saleOrderLine.getProduct());
|
||||
if (ati) {
|
||||
amountInCurrency = saleOrderLine.getInTaxTotal();
|
||||
} else {
|
||||
amountInCurrency = saleOrderLine.getExTaxTotal();
|
||||
}
|
||||
}
|
||||
if (totalQty == null || BigDecimal.ZERO.compareTo(totalQty) == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BigDecimal qtyRate = notInvoicedQty.divide(totalQty, 10, RoundingMode.HALF_EVEN);
|
||||
amountInCurrency = amountInCurrency.multiply(qtyRate).setScale(2, RoundingMode.HALF_EVEN);
|
||||
|
||||
if (amountInCurrency == null || amountInCurrency.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Product product = stockMoveLine.getProduct();
|
||||
|
||||
Account account =
|
||||
accountManagementAccountService.getProductAccount(
|
||||
product, company, partner.getFiscalPosition(), isPurchase, isFixedAssets);
|
||||
|
||||
boolean isDebit = false;
|
||||
if ((isPurchase && amountInCurrency.compareTo(BigDecimal.ZERO) == 1)
|
||||
|| !isPurchase && amountInCurrency.compareTo(BigDecimal.ZERO) == -1) {
|
||||
isDebit = true;
|
||||
}
|
||||
if (isReverse) {
|
||||
isDebit = !isDebit;
|
||||
}
|
||||
|
||||
MoveLine moveLine =
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
partner,
|
||||
account,
|
||||
amountInCurrency,
|
||||
isDebit,
|
||||
originDate,
|
||||
++counter,
|
||||
origin,
|
||||
moveDescription);
|
||||
moveLine.setDate(moveDate);
|
||||
moveLine.setDueDate(moveDate);
|
||||
|
||||
getAndComputeAnalyticDistribution(product, move, moveLine);
|
||||
|
||||
move.addMoveLineListItem(moveLine);
|
||||
|
||||
if (recoveredTax) {
|
||||
TaxLine taxLine =
|
||||
accountManagementAccountService.getTaxLine(
|
||||
originDate, product, company, partner.getFiscalPosition(), isPurchase);
|
||||
if (taxLine != null) {
|
||||
moveLine.setTaxLine(taxLine);
|
||||
moveLine.setTaxRate(taxLine.getValue());
|
||||
moveLine.setTaxCode(taxLine.getTax().getCode());
|
||||
|
||||
if (taxLine.getValue().compareTo(BigDecimal.ZERO) != 0) {
|
||||
generateTaxMoveLine(move, moveLine, origin, isPurchase, isFixedAssets, moveDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moveLine;
|
||||
}
|
||||
|
||||
protected void generateTaxMoveLine(
|
||||
Move move,
|
||||
MoveLine productMoveLine,
|
||||
String origin,
|
||||
boolean isPurchase,
|
||||
boolean isFixedAssets,
|
||||
String moveDescription)
|
||||
throws AxelorException {
|
||||
|
||||
TaxLine taxLine = productMoveLine.getTaxLine();
|
||||
|
||||
Tax tax = taxLine.getTax();
|
||||
|
||||
Account taxAccount =
|
||||
taxAccountService.getAccount(tax, move.getCompany(), isPurchase, isFixedAssets);
|
||||
|
||||
BigDecimal currencyTaxAmount =
|
||||
InvoiceLineManagement.computeAmount(
|
||||
productMoveLine.getCurrencyAmount(), taxLine.getValue());
|
||||
|
||||
MoveLine taxMoveLine =
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
move.getPartner(),
|
||||
taxAccount,
|
||||
currencyTaxAmount,
|
||||
productMoveLine.getDebit().compareTo(BigDecimal.ZERO) == 1,
|
||||
productMoveLine.getOriginDate(),
|
||||
++counter,
|
||||
origin,
|
||||
moveDescription);
|
||||
taxMoveLine.setDate(move.getDate());
|
||||
taxMoveLine.setDueDate(move.getDate());
|
||||
|
||||
move.addMoveLineListItem(taxMoveLine);
|
||||
}
|
||||
|
||||
protected MoveLine generatePartnerMoveLine(
|
||||
Move move, String origin, Account account, String moveDescription, LocalDate originDate)
|
||||
throws AxelorException {
|
||||
LocalDate moveDate = move.getDate();
|
||||
|
||||
BigDecimal currencyBalance = moveToolService.getBalanceCurrencyAmount(move.getMoveLineList());
|
||||
BigDecimal balance = moveToolService.getBalanceAmount(move.getMoveLineList());
|
||||
|
||||
if (balance.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MoveLine moveLine =
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
move.getPartner(),
|
||||
account,
|
||||
currencyBalance.abs(),
|
||||
balance.abs(),
|
||||
null,
|
||||
balance.compareTo(BigDecimal.ZERO) == -1,
|
||||
moveDate,
|
||||
moveDate,
|
||||
originDate,
|
||||
++counter,
|
||||
origin,
|
||||
moveDescription);
|
||||
|
||||
move.addMoveLineListItem(moveLine);
|
||||
|
||||
return moveLine;
|
||||
}
|
||||
|
||||
protected void getAndComputeAnalyticDistribution(Product product, Move move, MoveLine moveLine) {
|
||||
|
||||
if (appAccountService.getAppAccount().getAnalyticDistributionTypeSelect()
|
||||
== AppAccountRepository.DISTRIBUTION_TYPE_FREE) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnalyticDistributionTemplate analyticDistributionTemplate =
|
||||
analyticMoveLineService.getAnalyticDistributionTemplate(
|
||||
move.getPartner(), product, move.getCompany());
|
||||
|
||||
moveLine.setAnalyticDistributionTemplate(analyticDistributionTemplate);
|
||||
|
||||
List<AnalyticMoveLine> analyticMoveLineList =
|
||||
moveLineService.createAnalyticDistributionWithTemplate(moveLine).getAnalyticMoveLineList();
|
||||
for (AnalyticMoveLine analyticMoveLine : analyticMoveLineList) {
|
||||
analyticMoveLine.setMoveLine(moveLine);
|
||||
}
|
||||
analyticMoveLineList.stream().forEach(analyticMoveLineRepository::save);
|
||||
}
|
||||
|
||||
protected void reconcile(Move move, Move reverseMove) throws AxelorException {
|
||||
|
||||
List<MoveLine> moveLineSortedList = move.getMoveLineList();
|
||||
Collections.sort(moveLineSortedList, Comparator.comparing(MoveLine::getCounter));
|
||||
|
||||
List<MoveLine> reverseMoveLineSortedList = reverseMove.getMoveLineList();
|
||||
Collections.sort(reverseMoveLineSortedList, Comparator.comparing(MoveLine::getCounter));
|
||||
|
||||
Iterator<MoveLine> reverseMoveLinesIt = reverseMoveLineSortedList.iterator();
|
||||
|
||||
for (MoveLine moveLine : moveLineSortedList) {
|
||||
|
||||
MoveLine reverseMoveLine = reverseMoveLinesIt.next();
|
||||
|
||||
reconcileService.reconcile(moveLine, reverseMoveLine, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Long> getStockMoveLines(Batch batch) {
|
||||
int offset = 0;
|
||||
Boolean includeNotStockManagedProduct =
|
||||
batch.getSupplychainBatch().getIncludeNotStockManagedProduct();
|
||||
|
||||
List<StockMoveLine> stockMoveLineList;
|
||||
List<Long> stockMoveLineIdList = new ArrayList<>();
|
||||
|
||||
Query<StockMove> stockMoveQuery =
|
||||
stockMoverepository.all().filter(":batch MEMBER OF self.batchSet").bind("batch", batch);
|
||||
List<Long> stockMoveIdList =
|
||||
stockMoveQuery
|
||||
.select("id")
|
||||
.fetch(0, 0)
|
||||
.stream()
|
||||
.map(m -> (Long) m.get("id"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (stockMoveIdList.isEmpty()) {
|
||||
stockMoveLineIdList.add(0L);
|
||||
} else {
|
||||
Query<StockMoveLine> stockMoveLineQuery =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter("self.stockMove.id IN :stockMoveIdList")
|
||||
.bind("stockMoveIdList", stockMoveIdList)
|
||||
.order("id");
|
||||
|
||||
while (!(stockMoveLineList = stockMoveLineQuery.fetch(FETCH_LIMIT, offset)).isEmpty()) {
|
||||
offset += stockMoveLineList.size();
|
||||
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
Product product = stockMoveLine.getProduct();
|
||||
if (!checkStockMoveLine(stockMoveLine, product, includeNotStockManagedProduct)) {
|
||||
stockMoveLineIdList.add(stockMoveLine.getId());
|
||||
}
|
||||
}
|
||||
|
||||
JPA.clear();
|
||||
}
|
||||
}
|
||||
return stockMoveLineIdList;
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Move generateStockAccountMove(StockMove stockMove) throws AxelorException {
|
||||
List<StockMoveLine> stockMoveLines = stockMove.getStockMoveLineList();
|
||||
Map<StockMoveLine, Map<String, Account>> stockAccounts = new HashMap<>();
|
||||
Company company = stockMove.getCompany();
|
||||
Currency currency = stockMove.getCompany().getCurrency();
|
||||
Partner partner = stockMove.getPartner();
|
||||
LocalDate moveDate = stockMove.getEstimatedDate();
|
||||
Journal journal = this.setJournal(stockMove);
|
||||
for (StockMoveLine line : stockMoveLines) {
|
||||
Map<String, Account> accountMap = new HashMap<String, Account>();
|
||||
AccountManagement accountManagement =
|
||||
Beans.get(AccountManagementRepository.class)
|
||||
.all()
|
||||
.filter("self.product = ?1 and self.company = ?2", line.getProduct(), company)
|
||||
.fetchOne();
|
||||
if (stockMove.getToStockLocation().getTypeSelect() == StockLocationRepository.TYPE_EXTERNAL) {
|
||||
Account stockAccount = null;
|
||||
if (line.getProduct().getFamilleProduit().getId() == 67L
|
||||
|| line.getProduct().getFamilleProduit().getId() == 68L) {
|
||||
stockAccount = Beans.get(AccountRepository.class).find(595L);
|
||||
|
||||
} else if (line.getProduct().getFamilleProduit().getId() == 59L) {
|
||||
stockAccount = Beans.get(AccountRepository.class).find(3518L);
|
||||
} else {
|
||||
stockAccount = Beans.get(AccountRepository.class).find(4113L);
|
||||
}
|
||||
accountMap.put("stockAccount", stockAccount);
|
||||
} else {
|
||||
accountMap.put("stockAccount", accountManagement.getStockAccount());
|
||||
}
|
||||
|
||||
accountMap.put("purchaseAccount", accountManagement.getPurchaseAccount());
|
||||
accountMap.put("consumptionAccount", accountManagement.getConsumptionAccount());
|
||||
stockAccounts.put(line, accountMap);
|
||||
}
|
||||
|
||||
Move move =
|
||||
moveCreateService.createMove(
|
||||
journal,
|
||||
company,
|
||||
currency,
|
||||
partner,
|
||||
moveDate,
|
||||
null,
|
||||
MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);
|
||||
|
||||
int counter = 0;
|
||||
for (StockMoveLine line : stockMoveLines) {
|
||||
|
||||
Account acc = stockAccounts.get(line).get("purchaseAccount");
|
||||
Account acc2 = stockAccounts.get(line).get("stockAccount");
|
||||
|
||||
if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING
|
||||
&& stockMove.getPartner() == stockMove.getCompany().getPartner()) {
|
||||
acc = stockAccounts.get(line).get("consumptionAccount");
|
||||
acc2 = stockAccounts.get(line).get("stockAccount");
|
||||
} else if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING
|
||||
&& stockMove.getPartner() != stockMove.getCompany().getPartner()) {
|
||||
acc2 = stockAccounts.get(line).get("stockAccount");
|
||||
acc = stockAccounts.get(line).get("purchaseAccount");
|
||||
} else if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING
|
||||
&& stockMove.getPartner() == stockMove.getCompany().getPartner()) {
|
||||
acc = stockAccounts.get(line).get("consumptionAccount");
|
||||
acc2 = stockAccounts.get(line).get("stockAccount");
|
||||
}
|
||||
|
||||
BigDecimal amountInCurrency = line.getUnitPriceUntaxed().multiply(line.getRealQty());
|
||||
String description = stockMove.getStockMoveSeq() + "-" + line.getProduct().getCode();
|
||||
if (line.getTrackingNumber() != null) {
|
||||
description += "-" + line.getTrackingNumber().getTrackingNumberSeq();
|
||||
}
|
||||
|
||||
if (line.getRealQty().compareTo(BigDecimal.ZERO) > 0) {
|
||||
|
||||
MoveLine moveLine =
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
partner,
|
||||
acc,
|
||||
amountInCurrency,
|
||||
isDebit(stockMove),
|
||||
moveDate,
|
||||
++counter,
|
||||
stockMove.getStockMoveSeq(),
|
||||
description);
|
||||
moveLine.setDate(moveDate);
|
||||
moveLine.setDueDate(moveDate);
|
||||
moveLine.setAccountId(acc.getId());
|
||||
moveLine.setAccountCode(acc.getCode());
|
||||
|
||||
MoveLine moveLine2 =
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
partner,
|
||||
acc2,
|
||||
amountInCurrency,
|
||||
!isDebit(stockMove),
|
||||
moveDate,
|
||||
++counter,
|
||||
stockMove.getStockMoveSeq(),
|
||||
description);
|
||||
moveLine2.setDate(moveDate);
|
||||
moveLine2.setDueDate(moveDate);
|
||||
moveLine2.setAccountId(acc2.getId());
|
||||
moveLine2.setAccountCode(acc2.getCode());
|
||||
|
||||
move.addMoveLineListItem(moveLine);
|
||||
move.addMoveLineListItem(moveLine2);
|
||||
}
|
||||
}
|
||||
stockMove.setIsVentilated(true);
|
||||
move.setIgnoreInAccountingOk(false);
|
||||
move.setStatusSelect(MoveRepository.STATUS_DAYBOOK);
|
||||
move.setStockMove(stockMove);
|
||||
return move;
|
||||
}
|
||||
|
||||
public List<Long> massGenerationMove(List<Long> ids) throws AxelorException {
|
||||
|
||||
List<Long> movesId = new ArrayList<>();
|
||||
|
||||
for (Long id : ids) {
|
||||
StockMove stockMove = Beans.get(StockMoveRepository.class).find(id);
|
||||
Move move = this.generateStockAccountMove(stockMove);
|
||||
movesId.add(move.getId());
|
||||
}
|
||||
;
|
||||
return movesId;
|
||||
}
|
||||
|
||||
public boolean isDebit(StockMove stockMove) throws AxelorException {
|
||||
boolean isDebit;
|
||||
// stockMove.getToStockLocation().getTypeSelect() == StockLocationRepository.TYPE_VIRTUAL
|
||||
if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING) {
|
||||
isDebit = false;
|
||||
} else if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING) {
|
||||
isDebit = true;
|
||||
} else
|
||||
throw new AxelorException(
|
||||
stockMove,
|
||||
TraceBackRepository.CATEGORY_MISSING_FIELD,
|
||||
I18n.get(IExceptionMessage.MOVE_1),
|
||||
stockMove.getId());
|
||||
return isDebit;
|
||||
}
|
||||
|
||||
public Journal setJournal(StockMove stockMove) throws AxelorException {
|
||||
Journal journal;
|
||||
// stockMove.getToStockLocation().getTypeSelect() == StockLocationRepository.TYPE_VIRTUAL
|
||||
if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING) {
|
||||
journal = Beans.get(JournalRepository.class).find(10L);
|
||||
} else if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING) {
|
||||
journal = Beans.get(JournalRepository.class).find(30L);
|
||||
} else
|
||||
throw new AxelorException(
|
||||
stockMove,
|
||||
TraceBackRepository.CATEGORY_MISSING_FIELD,
|
||||
I18n.get(IExceptionMessage.MOVE_1),
|
||||
stockMove.getId());
|
||||
return journal;
|
||||
}
|
||||
|
||||
public boolean isDebitInventoryLine(InventoryLine inventoryLine) throws AxelorException {
|
||||
boolean isDebit;
|
||||
// stockMove.getToStockLocation().getTypeSelect() == StockLocationRepository.TYPE_VIRTUAL
|
||||
if (inventoryLine.getGap().compareTo(BigDecimal.ZERO) < 0) {
|
||||
isDebit = false;
|
||||
} else if (inventoryLine.getGap().compareTo(BigDecimal.ZERO) > 0) {
|
||||
isDebit = true;
|
||||
} else
|
||||
throw new AxelorException(
|
||||
inventoryLine,
|
||||
TraceBackRepository.CATEGORY_MISSING_FIELD,
|
||||
"Ecart = 0",
|
||||
inventoryLine.getId());
|
||||
return isDebit;
|
||||
}
|
||||
|
||||
public Boolean isGapPositive(InventoryLine line) {
|
||||
if (line.getGap().compareTo(BigDecimal.ZERO) > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Move generateInventoryLineMove(InventoryLine inventoryLine) throws AxelorException {
|
||||
|
||||
Map<InventoryLine, Map<String, Account>> stockAccounts = new HashMap<>();
|
||||
Company company = inventoryLine.getInventory().getCompany();
|
||||
Currency currency = inventoryLine.getInventory().getCompany().getCurrency();
|
||||
Partner partner = inventoryLine.getInventory().getCompany().getPartner();
|
||||
LocalDate moveDate = inventoryLine.getInventory().getPlannedEndDateT().toLocalDate();
|
||||
Journal journal = Beans.get(JournalRepository.class).find(10L);
|
||||
|
||||
Map<String, Account> accountMap = new HashMap<String, Account>();
|
||||
AccountManagement accountManagement =
|
||||
Beans.get(AccountManagementRepository.class)
|
||||
.all()
|
||||
.filter("self.product = ?1 and self.company = ?2", inventoryLine.getProduct(), company)
|
||||
.fetchOne();
|
||||
|
||||
if (accountManagement.getStockAccount() == null
|
||||
|| accountManagement.getConsumptionAccount() == null) {
|
||||
throw new AxelorException(
|
||||
inventoryLine,
|
||||
TraceBackRepository.CATEGORY_MISSING_FIELD,
|
||||
I18n.get(IExceptionMessage.VENTILATE_STATE_6),
|
||||
inventoryLine.getProduct().getFullName());
|
||||
}
|
||||
|
||||
accountMap.put("stockAccount", accountManagement.getStockAccount());
|
||||
accountMap.put("consumptionAccount", accountManagement.getConsumptionAccount());
|
||||
|
||||
stockAccounts.put(inventoryLine, accountMap);
|
||||
|
||||
Move move =
|
||||
moveCreateService.createMove(
|
||||
journal,
|
||||
company,
|
||||
currency,
|
||||
partner,
|
||||
moveDate,
|
||||
null,
|
||||
MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);
|
||||
|
||||
int counter = 0;
|
||||
|
||||
Account acc = stockAccounts.get(inventoryLine).get("consumptionAccount");
|
||||
Account acc2 = stockAccounts.get(inventoryLine).get("stockAccount");
|
||||
|
||||
if (inventoryLine.getJustifiedGap()) {
|
||||
if (isGapPositive(inventoryLine)) {
|
||||
acc = stockAccounts.get(inventoryLine).get("consumptionAccount");
|
||||
acc2 = stockAccounts.get(inventoryLine).get("stockAccount");
|
||||
} else if (!isGapPositive(inventoryLine)) {
|
||||
acc = stockAccounts.get(inventoryLine).get("stockAccount");
|
||||
acc2 = stockAccounts.get(inventoryLine).get("consumptionAccount");
|
||||
}
|
||||
} else {
|
||||
if (isGapPositive(inventoryLine)) {
|
||||
acc = Beans.get(AccountRepository.class).find(1360L);
|
||||
acc2 = stockAccounts.get(inventoryLine).get("stockAccount");
|
||||
} else if (!isGapPositive(inventoryLine)) {
|
||||
acc = stockAccounts.get(inventoryLine).get("stockAccount");
|
||||
acc2 = Beans.get(AccountRepository.class).find(1400L);
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal amountInCurrency = inventoryLine.getGapValue().multiply(inventoryLine.getGap());
|
||||
String description =
|
||||
inventoryLine.getInventory().getInventorySeq() + "-" + inventoryLine.getProduct().getCode();
|
||||
if (inventoryLine.getTrackingNumber() != null) {
|
||||
description += "-" + inventoryLine.getTrackingNumber().getTrackingNumberSeq();
|
||||
}
|
||||
|
||||
MoveLine moveLine =
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
partner,
|
||||
acc,
|
||||
amountInCurrency,
|
||||
isDebitInventoryLine(inventoryLine),
|
||||
moveDate,
|
||||
++counter,
|
||||
inventoryLine.getInventory().getInventorySeq(),
|
||||
description);
|
||||
moveLine.setDescription(description);
|
||||
moveLine.setDate(moveDate);
|
||||
moveLine.setDueDate(moveDate);
|
||||
moveLine.setAccountId(acc.getId());
|
||||
moveLine.setAccountCode(acc.getCode());
|
||||
moveLine.setAccountName(acc.getName());
|
||||
|
||||
MoveLine moveLine2 =
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
partner,
|
||||
acc2,
|
||||
amountInCurrency,
|
||||
!isDebitInventoryLine(inventoryLine),
|
||||
moveDate,
|
||||
++counter,
|
||||
inventoryLine.getInventory().getInventorySeq(),
|
||||
description);
|
||||
moveLine2.setDescription(description);
|
||||
moveLine2.setDate(moveDate);
|
||||
moveLine2.setDueDate(moveDate);
|
||||
moveLine2.setAccountId(acc2.getId());
|
||||
moveLine2.setAccountCode(acc2.getCode());
|
||||
moveLine2.setAccountName(acc2.getName());
|
||||
|
||||
move.addMoveLineListItem(moveLine);
|
||||
move.addMoveLineListItem(moveLine2);
|
||||
move.setInventoryLine(inventoryLine);
|
||||
inventoryLine.setIsVentilated(true);
|
||||
|
||||
move.setIgnoreInAccountingOk(false);
|
||||
move.setStatusSelect(MoveRepository.STATUS_DAYBOOK);
|
||||
return move;
|
||||
}
|
||||
|
||||
public List<Long> massGenerationInventoryLineMove(List<Long> ids) throws AxelorException {
|
||||
|
||||
List<Long> movesId = new ArrayList<>();
|
||||
|
||||
for (Long id : ids) {
|
||||
InventoryLine line = Beans.get(InventoryLineRepository.class).find(id);
|
||||
Move move = this.generateInventoryLineMove(line);
|
||||
movesId.add(move.getId());
|
||||
}
|
||||
return movesId;
|
||||
}
|
||||
|
||||
// public boolean isPurchase(StockMove stockMove){
|
||||
// boolean isPurchase;
|
||||
// Partner partner = stockMove.getPartner() ;
|
||||
// if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING && stockMove.getPartner()
|
||||
// == stockMove.getCompany().getPartner()) {
|
||||
// isPurchase = false;
|
||||
// }
|
||||
// else if(stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING &&
|
||||
// stockMove.getPartner() != stockMove.getCompany().getPartner()){
|
||||
// isPurchase = true;
|
||||
// }else if(stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING &&
|
||||
// stockMove.getPartner() == stockMove.getCompany().getPartner()){
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AccountingSituation;
|
||||
import com.axelor.apps.account.service.AccountingSituationService;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.exception.AxelorException;
|
||||
|
||||
public interface AccountingSituationSupplychainService extends AccountingSituationService {
|
||||
|
||||
public void updateCustomerCreditFromSaleOrder(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
// public boolean checkBlockedPartner(Partner partner, Company company) throws AxelorException;
|
||||
|
||||
public AccountingSituation computeUsedCredit(AccountingSituation accountingSituation)
|
||||
throws AxelorException;
|
||||
|
||||
public void updateUsedCredit(Partner partner) throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AccountingSituation;
|
||||
import com.axelor.apps.account.db.InvoicePayment;
|
||||
import com.axelor.apps.account.db.repo.AccountingSituationRepository;
|
||||
import com.axelor.apps.account.db.repo.InvoicePaymentRepository;
|
||||
import com.axelor.apps.account.service.AccountingSituationServiceImpl;
|
||||
import com.axelor.apps.account.service.app.AppAccountService;
|
||||
import com.axelor.apps.account.service.config.AccountConfigService;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.service.administration.SequenceService;
|
||||
import com.axelor.apps.sale.db.SaleConfig;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.sale.exception.BlockedSaleOrderException;
|
||||
import com.axelor.apps.sale.service.config.SaleConfigService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class AccountingSituationSupplychainServiceImpl extends AccountingSituationServiceImpl
|
||||
implements AccountingSituationSupplychainService {
|
||||
|
||||
private SaleConfigService saleConfigService;
|
||||
|
||||
@Inject private AppAccountService appAccountService;
|
||||
|
||||
@Inject
|
||||
public AccountingSituationSupplychainServiceImpl(
|
||||
AccountConfigService accountConfigService,
|
||||
SequenceService sequenceService,
|
||||
AccountingSituationRepository accountingSituationRepo,
|
||||
SaleConfigService saleConfigService) {
|
||||
super(accountConfigService, sequenceService, accountingSituationRepo);
|
||||
this.saleConfigService = saleConfigService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountingSituation createAccountingSituation(Partner partner, Company company)
|
||||
throws AxelorException {
|
||||
|
||||
AccountingSituation accountingSituation = super.createAccountingSituation(partner, company);
|
||||
|
||||
if (appAccountService.getAppAccount().getManageCustomerCredit()) {
|
||||
SaleConfig config = saleConfigService.getSaleConfig(accountingSituation.getCompany());
|
||||
if (config != null) {
|
||||
accountingSituation.setAcceptedCredit(config.getAcceptedCredit());
|
||||
}
|
||||
}
|
||||
|
||||
return accountingSituation;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void updateUsedCredit(Partner partner) throws AxelorException {
|
||||
if (appAccountService.getAppAccount().getManageCustomerCredit()) {
|
||||
List<AccountingSituation> accountingSituationList =
|
||||
accountingSituationRepo.all().filter("self.partner = ?1", partner).fetch();
|
||||
for (AccountingSituation accountingSituation : accountingSituationList) {
|
||||
accountingSituationRepo.save(this.computeUsedCredit(accountingSituation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void updateCustomerCredit(Partner partner) throws AxelorException {
|
||||
if (!appAccountService.getAppAccount().getManageCustomerCredit()
|
||||
|| partner.getIsContact()
|
||||
|| !partner.getIsCustomer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<AccountingSituation> accountingSituationList = partner.getAccountingSituationList();
|
||||
|
||||
for (AccountingSituation accountingSituation : accountingSituationList) {
|
||||
computeUsedCredit(accountingSituation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(
|
||||
rollbackOn = {AxelorException.class, Exception.class},
|
||||
ignore = {BlockedSaleOrderException.class}
|
||||
)
|
||||
public void updateCustomerCreditFromSaleOrder(SaleOrder saleOrder) throws AxelorException {
|
||||
|
||||
if (!appAccountService.getAppAccount().getManageCustomerCredit()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Partner partner = saleOrder.getClientPartner();
|
||||
List<AccountingSituation> accountingSituationList = partner.getAccountingSituationList();
|
||||
for (AccountingSituation accountingSituation : accountingSituationList) {
|
||||
if (accountingSituation.getCompany().equals(saleOrder.getCompany())) {
|
||||
// Update UsedCredit
|
||||
accountingSituation = this.computeUsedCredit(accountingSituation);
|
||||
if (saleOrder.getStatusSelect() == SaleOrderRepository.STATUS_DRAFT_QUOTATION) {
|
||||
BigDecimal inTaxInvoicedAmount =
|
||||
Beans.get(SaleOrderInvoiceService.class).getInTaxInvoicedAmount(saleOrder);
|
||||
|
||||
BigDecimal usedCredit =
|
||||
accountingSituation
|
||||
.getUsedCredit()
|
||||
.add(saleOrder.getInTaxTotal())
|
||||
.subtract(inTaxInvoicedAmount);
|
||||
|
||||
accountingSituation.setUsedCredit(usedCredit);
|
||||
}
|
||||
boolean usedCreditExceeded = isUsedCreditExceeded(accountingSituation);
|
||||
if (usedCreditExceeded) {
|
||||
saleOrder.setBlockedOnCustCreditExceed(true);
|
||||
if (!saleOrder.getManualUnblock()) {
|
||||
String message = accountingSituation.getCompany().getOrderBloquedMessage();
|
||||
if (Strings.isNullOrEmpty(message)) {
|
||||
message = I18n.get("Client blocked : maximal accepted credit exceeded.");
|
||||
}
|
||||
throw new BlockedSaleOrderException(accountingSituation, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountingSituation computeUsedCredit(AccountingSituation accountingSituation)
|
||||
throws AxelorException {
|
||||
BigDecimal sum = BigDecimal.ZERO;
|
||||
List<SaleOrder> saleOrderList =
|
||||
Beans.get(SaleOrderRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.company = ?1 AND self.clientPartner = ?2 AND self.statusSelect > ?3 AND self.statusSelect < ?4",
|
||||
accountingSituation.getCompany(),
|
||||
accountingSituation.getPartner(),
|
||||
SaleOrderRepository.STATUS_DRAFT_QUOTATION,
|
||||
SaleOrderRepository.STATUS_CANCELED)
|
||||
.fetch();
|
||||
for (SaleOrder saleOrder : saleOrderList) {
|
||||
sum =
|
||||
sum.add(
|
||||
saleOrder
|
||||
.getInTaxTotal()
|
||||
.subtract(
|
||||
Beans.get(SaleOrderInvoiceService.class).getInTaxInvoicedAmount(saleOrder)));
|
||||
}
|
||||
// subtract the amount of payments if there is no move created for
|
||||
// invoice payments
|
||||
if (!accountConfigService
|
||||
.getAccountConfig(accountingSituation.getCompany())
|
||||
.getGenerateMoveForInvoicePayment()) {
|
||||
List<InvoicePayment> invoicePaymentList =
|
||||
Beans.get(InvoicePaymentRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.invoice.company = :company"
|
||||
+ " AND self.invoice.partner = :partner"
|
||||
+ " AND self.statusSelect = :validated"
|
||||
+ " AND self.typeSelect != :imputation")
|
||||
.bind("company", accountingSituation.getCompany())
|
||||
.bind("partner", accountingSituation.getPartner())
|
||||
.bind("validated", InvoicePaymentRepository.STATUS_VALIDATED)
|
||||
.bind("imputation", InvoicePaymentRepository.TYPE_ADV_PAYMENT_IMPUTATION)
|
||||
.fetch();
|
||||
if (invoicePaymentList != null) {
|
||||
for (InvoicePayment invoicePayment : invoicePaymentList) {
|
||||
sum = sum.subtract(invoicePayment.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
sum = accountingSituation.getBalanceCustAccount().add(sum);
|
||||
accountingSituation.setUsedCredit(sum.setScale(2, RoundingMode.HALF_EVEN));
|
||||
|
||||
return accountingSituation;
|
||||
}
|
||||
|
||||
private boolean isUsedCreditExceeded(AccountingSituation accountingSituation) {
|
||||
return accountingSituation.getUsedCredit().compareTo(accountingSituation.getAcceptedCredit())
|
||||
> 0;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// @Transactional(rollbackOn = {AxelorException.class, Exception.class})
|
||||
// public boolean checkBlockedPartner(Partner partner, Company company) throws AxelorException {
|
||||
// AccountingSituation accountingSituation = accountingSituationRepo.all().filter("self.company =
|
||||
// ?1 AND self.partner = ?2", company, partner).fetchOne();
|
||||
// accountingSituation = this.computeUsedCredit(accountingSituation);
|
||||
// accountingSituationRepo.save(accountingSituation);
|
||||
//
|
||||
// return this.isUsedCreditExceeded(accountingSituation);
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AccountConfig;
|
||||
import com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.InvoicePayment;
|
||||
import com.axelor.apps.account.db.Journal;
|
||||
import com.axelor.apps.account.db.Move;
|
||||
import com.axelor.apps.account.db.PaymentMode;
|
||||
import com.axelor.apps.account.db.repo.InvoicePaymentRepository;
|
||||
import com.axelor.apps.account.db.repo.MoveRepository;
|
||||
import com.axelor.apps.account.service.config.AccountConfigService;
|
||||
import com.axelor.apps.account.service.move.MoveCancelService;
|
||||
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.base.db.BankDetails;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.service.CurrencyService;
|
||||
import com.axelor.apps.sale.db.AdvancePayment;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.repo.AdvancePaymentRepository;
|
||||
import com.axelor.apps.sale.service.AdvancePaymentServiceImpl;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AdvancePaymentServiceSupplychainImpl extends AdvancePaymentServiceImpl {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Inject protected PaymentModeService paymentModeService;
|
||||
|
||||
@Inject protected MoveService moveService;
|
||||
|
||||
@Inject protected MoveLineService moveLineService;
|
||||
|
||||
@Inject protected CurrencyService currencyService;
|
||||
|
||||
@Inject protected AccountConfigService accountConfigService;
|
||||
|
||||
@Inject protected InvoicePaymentRepository invoicePaymentRepository;
|
||||
|
||||
@Inject protected AdvancePaymentRepository advancePaymentRepository;
|
||||
|
||||
@Inject protected MoveCancelService moveCancelService;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void validate(AdvancePayment advancePayment) throws AxelorException {
|
||||
|
||||
if (advancePayment.getStatusSelect() != AdvancePaymentRepository.STATUS_DRAFT) {
|
||||
return;
|
||||
}
|
||||
|
||||
advancePayment.setStatusSelect(AdvancePaymentRepository.STATUS_VALIDATED);
|
||||
|
||||
Company company = advancePayment.getSaleOrder().getCompany();
|
||||
|
||||
if (accountConfigService.getAccountConfig(company).getGenerateMoveForAdvancePayment()
|
||||
&& advancePayment.getAmount().compareTo(BigDecimal.ZERO) != 0) {
|
||||
this.createMoveForAdvancePayment(advancePayment);
|
||||
}
|
||||
|
||||
advancePaymentRepository.save(advancePayment);
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void cancel(AdvancePayment advancePayment) throws AxelorException {
|
||||
|
||||
moveCancelService.cancel(advancePayment.getMove());
|
||||
advancePayment.setStatusSelect(AdvancePaymentRepository.STATUS_CANCELED);
|
||||
advancePaymentRepository.save(advancePayment);
|
||||
}
|
||||
|
||||
public void createInvoicePayments(Invoice invoice, SaleOrder saleOrder) throws AxelorException {
|
||||
if (saleOrder.getAdvancePaymentList() == null || saleOrder.getAdvancePaymentList().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BigDecimal total = saleOrder.getInTaxTotal();
|
||||
|
||||
// for (AdvancePayment advancePayment : saleOrder.getAdvancePaymentList()) {
|
||||
//
|
||||
// if(advancePayment.getAmountRemainingToUse().compareTo(BigDecimal.ZERO) != 0 &&
|
||||
// total.compareTo(BigDecimal.ZERO) != 0) {
|
||||
// if(total.max(advancePayment.getAmountRemainingToUse()) == total) {
|
||||
// total = total.subtract(advancePayment.getAmountRemainingToUse());
|
||||
// InvoicePayment invoicePayment = createInvoicePayment(advancePayment, invoice,
|
||||
// advancePayment.getAmountRemainingToUse(), saleOrder);
|
||||
// invoice.addInvoicePaymentListItem(invoicePayment);
|
||||
// advancePayment.setAmountRemainingToUse(BigDecimal.ZERO);
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// advancePayment.setAmountRemainingToUse(advancePayment.getAmountRemainingToUse().subtract(total));
|
||||
// InvoicePayment invoicePayment = createInvoicePayment(advancePayment, invoice, total,
|
||||
// saleOrder);
|
||||
// invoicePayment.setInvoice(invoice);
|
||||
// invoice.addInvoicePaymentListItem(invoicePayment);
|
||||
// total = BigDecimal.ZERO;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Move createMoveForAdvancePayment(AdvancePayment advancePayment) throws AxelorException {
|
||||
|
||||
SaleOrder saleOrder = advancePayment.getSaleOrder();
|
||||
Company company = saleOrder.getCompany();
|
||||
PaymentMode paymentMode = advancePayment.getPaymentMode();
|
||||
Partner clientPartner = saleOrder.getClientPartner();
|
||||
LocalDate advancePaymentDate = advancePayment.getAdvancePaymentDate();
|
||||
BankDetails bankDetails = saleOrder.getCompanyBankDetails();
|
||||
String ref = saleOrder.getSaleOrderSeq();
|
||||
|
||||
AccountConfig accountConfig = accountConfigService.getAccountConfig(company);
|
||||
|
||||
Journal journal = paymentModeService.getPaymentModeJournal(paymentMode, company, bankDetails);
|
||||
|
||||
Move move =
|
||||
moveService
|
||||
.getMoveCreateService()
|
||||
.createMove(
|
||||
journal,
|
||||
company,
|
||||
advancePayment.getCurrency(),
|
||||
clientPartner,
|
||||
advancePaymentDate,
|
||||
paymentMode,
|
||||
MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);
|
||||
|
||||
BigDecimal amountConverted =
|
||||
currencyService.getAmountCurrencyConvertedAtDate(
|
||||
advancePayment.getCurrency(),
|
||||
saleOrder.getCurrency(),
|
||||
advancePayment.getAmount(),
|
||||
advancePaymentDate);
|
||||
|
||||
move.addMoveLineListItem(
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
clientPartner,
|
||||
paymentModeService.getPaymentModeAccount(paymentMode, company, bankDetails),
|
||||
amountConverted,
|
||||
true,
|
||||
advancePaymentDate,
|
||||
null,
|
||||
1,
|
||||
ref,
|
||||
null));
|
||||
|
||||
move.addMoveLineListItem(
|
||||
moveLineService.createMoveLine(
|
||||
move,
|
||||
clientPartner,
|
||||
accountConfigService.getAdvancePaymentAccount(accountConfig),
|
||||
amountConverted,
|
||||
false,
|
||||
advancePaymentDate,
|
||||
null,
|
||||
2,
|
||||
ref,
|
||||
null));
|
||||
|
||||
moveService.getMoveValidateService().validate(move);
|
||||
|
||||
advancePayment.setMove(move);
|
||||
advancePaymentRepository.save(advancePayment);
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public InvoicePayment createInvoicePayment(
|
||||
AdvancePayment advancePayment, Invoice invoice, BigDecimal amount) throws AxelorException {
|
||||
|
||||
log.debug("Creating InvoicePayment from SaleOrder AdvancePayment");
|
||||
InvoicePayment invoicePayment = new InvoicePayment();
|
||||
|
||||
invoicePayment.setAmount(amount);
|
||||
invoicePayment.setPaymentDate(advancePayment.getAdvancePaymentDate());
|
||||
invoicePayment.setCurrency(advancePayment.getCurrency());
|
||||
invoicePayment.setInvoice(invoice);
|
||||
invoicePayment.setPaymentMode(advancePayment.getPaymentMode());
|
||||
invoicePayment.setTypeSelect(InvoicePaymentRepository.TYPE_ADVANCEPAYMENT);
|
||||
invoicePayment.setMove(advancePayment.getMove());
|
||||
|
||||
invoicePaymentRepository.save(invoicePayment);
|
||||
|
||||
invoice.addInvoicePaymentListItem(invoicePayment);
|
||||
|
||||
return invoicePayment;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Budget;
|
||||
import com.axelor.apps.account.db.BudgetDistribution;
|
||||
import com.axelor.apps.account.db.BudgetLine;
|
||||
import com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.InvoiceLine;
|
||||
import com.axelor.apps.account.db.repo.BudgetDistributionRepository;
|
||||
import com.axelor.apps.account.db.repo.BudgetLineRepository;
|
||||
import com.axelor.apps.account.db.repo.BudgetRepository;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.service.BudgetService;
|
||||
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.tool.date.DateTool;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BudgetSupplychainService extends BudgetService {
|
||||
|
||||
@Inject
|
||||
public BudgetSupplychainService(
|
||||
BudgetLineRepository budgetLineRepository, BudgetRepository budgetRepository) {
|
||||
super(budgetLineRepository, budgetRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<BudgetLine> updateLines(Budget budget) {
|
||||
if (budget.getBudgetLineList() != null && !budget.getBudgetLineList().isEmpty()) {
|
||||
for (BudgetLine budgetLine : budget.getBudgetLineList()) {
|
||||
budgetLine.setAmountCommitted(BigDecimal.ZERO);
|
||||
budgetLine.setAmountRealized(BigDecimal.ZERO);
|
||||
}
|
||||
List<BudgetDistribution> budgetDistributionList = null;
|
||||
budgetDistributionList =
|
||||
Beans.get(BudgetDistributionRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.budget.id = ?1 AND self.purchaseOrderLine.purchaseOrder.statusSelect in (?2,?3)",
|
||||
budget.getId(),
|
||||
PurchaseOrderRepository.STATUS_VALIDATED,
|
||||
PurchaseOrderRepository.STATUS_FINISHED)
|
||||
.fetch();
|
||||
for (BudgetDistribution budgetDistribution : budgetDistributionList) {
|
||||
LocalDate orderDate =
|
||||
budgetDistribution.getPurchaseOrderLine().getPurchaseOrder().getOrderDate();
|
||||
if (orderDate != null) {
|
||||
for (BudgetLine budgetLine : budget.getBudgetLineList()) {
|
||||
LocalDate fromDate = budgetLine.getFromDate();
|
||||
LocalDate toDate = budgetLine.getToDate();
|
||||
if ((fromDate != null && toDate != null)
|
||||
&& (fromDate.isBefore(orderDate) || fromDate.isEqual(orderDate))
|
||||
&& (toDate.isAfter(orderDate) || toDate.isEqual(orderDate))) {
|
||||
budgetLine.setAmountCommitted(
|
||||
budgetLine.getAmountCommitted().add(budgetDistribution.getAmount()));
|
||||
budgetLineRepository.save(budgetLine);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
budgetDistributionList =
|
||||
Beans.get(BudgetDistributionRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.budget.id = ?1 AND (self.invoiceLine.invoice.statusSelect = ?2 OR self.invoiceLine.invoice.statusSelect = ?3)",
|
||||
budget.getId(),
|
||||
InvoiceRepository.STATUS_VALIDATED,
|
||||
InvoiceRepository.STATUS_VENTILATED)
|
||||
.fetch();
|
||||
for (BudgetDistribution budgetDistribution : budgetDistributionList) {
|
||||
Optional<LocalDate> optionaldate = getDate(budgetDistribution);
|
||||
optionaldate.ifPresent(
|
||||
date -> {
|
||||
for (BudgetLine budgetLine : budget.getBudgetLineList()) {
|
||||
LocalDate fromDate = budgetLine.getFromDate();
|
||||
LocalDate toDate = budgetLine.getToDate();
|
||||
if ((fromDate.isBefore(date) || fromDate.isEqual(date))
|
||||
&& (toDate.isAfter(date) || toDate.isEqual(date))) {
|
||||
budgetLine.setAmountRealized(
|
||||
budgetLine.getAmountRealized().add(budgetDistribution.getAmount()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return budget.getBudgetLineList();
|
||||
}
|
||||
|
||||
public void computeBudgetDistributionSumAmount(
|
||||
BudgetDistribution budgetDistribution, LocalDate computeDate) {
|
||||
|
||||
if (budgetDistribution.getBudget() != null
|
||||
&& budgetDistribution.getBudget().getBudgetLineList() != null
|
||||
&& computeDate != null) {
|
||||
List<BudgetLine> budgetLineList = budgetDistribution.getBudget().getBudgetLineList();
|
||||
BigDecimal budgetAmountAvailable = BigDecimal.ZERO;
|
||||
|
||||
for (BudgetLine budgetLine : budgetLineList) {
|
||||
LocalDate fromDate = budgetLine.getFromDate();
|
||||
LocalDate toDate = budgetLine.getToDate();
|
||||
|
||||
if (fromDate != null && DateTool.isBetween(fromDate, toDate, computeDate)) {
|
||||
BigDecimal amount =
|
||||
budgetLine.getAmountExpected().subtract(budgetLine.getAmountCommitted());
|
||||
budgetAmountAvailable = budgetAmountAvailable.add(amount);
|
||||
}
|
||||
}
|
||||
budgetDistribution.setBudgetAmountAvailable(budgetAmountAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<LocalDate> getDate(BudgetDistribution budgetDistribution) {
|
||||
InvoiceLine invoiceLine = budgetDistribution.getInvoiceLine();
|
||||
|
||||
if (invoiceLine == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Invoice invoice = invoiceLine.getInvoice();
|
||||
|
||||
if (invoice.getPurchaseOrder() != null) {
|
||||
return Optional.of(invoice.getPurchaseOrder().getOrderDate());
|
||||
}
|
||||
|
||||
return super.getDate(budgetDistribution);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public BigDecimal computeTotalAmountCommitted(Budget budget) {
|
||||
List<BudgetLine> budgetLineList = budget.getBudgetLineList();
|
||||
|
||||
if (budgetLineList == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal totalAmountCommitted =
|
||||
budgetLineList
|
||||
.stream()
|
||||
.map(BudgetLine::getAmountCommitted)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
budget.setTotalAmountCommitted(totalAmountCommitted);
|
||||
|
||||
return totalAmountCommitted;
|
||||
}
|
||||
|
||||
public void updateBudgetLinesFromPurchaseOrder(PurchaseOrder purchaseOrder) {
|
||||
List<PurchaseOrderLine> purchaseOrderLineList = purchaseOrder.getPurchaseOrderLineList();
|
||||
|
||||
if (purchaseOrderLineList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean idbudgetDistExist = true;
|
||||
for (PurchaseOrderLine line : purchaseOrderLineList) {
|
||||
if (line.getBudgetDistributionList() == null) {
|
||||
idbudgetDistExist = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (idbudgetDistExist) {
|
||||
purchaseOrderLineList
|
||||
.stream()
|
||||
.flatMap(x -> x.getBudgetDistributionList().stream())
|
||||
.forEach(
|
||||
budgetDistribution -> {
|
||||
Budget budget = budgetDistribution.getBudget();
|
||||
updateLines(budget);
|
||||
computeTotalAmountCommitted(budget);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.FixedAsset;
|
||||
import com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.service.FixedAssetServiceImpl;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.util.List;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
||||
public class FixedAssetServiceSupplyChainImpl extends FixedAssetServiceImpl {
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public List<FixedAsset> createFixedAssets(Invoice invoice) throws AxelorException {
|
||||
|
||||
List<FixedAsset> fixedAssetList = super.createFixedAssets(invoice);
|
||||
|
||||
if (CollectionUtils.isEmpty(fixedAssetList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StockLocation stockLocation =
|
||||
invoice.getPurchaseOrder() != null ? invoice.getPurchaseOrder().getStockLocation() : null;
|
||||
|
||||
for (FixedAsset fixedAsset : fixedAssetList) {
|
||||
|
||||
PurchaseOrderLine pol = fixedAsset.getInvoiceLine().getPurchaseOrderLine();
|
||||
|
||||
fixedAsset.setStockLocation(stockLocation);
|
||||
|
||||
if (fixedAsset.getInvoiceLine().getIncomingStockMove() != null
|
||||
&& CollectionUtils.isNotEmpty(
|
||||
fixedAsset.getInvoiceLine().getIncomingStockMove().getStockMoveLineList())) {
|
||||
fixedAsset.setTrackingNumber(
|
||||
fixedAsset
|
||||
.getInvoiceLine()
|
||||
.getIncomingStockMove()
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.filter(l -> pol.equals(l.getPurchaseOrderLine()))
|
||||
.findFirst()
|
||||
.map(StockMoveLine::getTrackingNumber)
|
||||
.orElse(null));
|
||||
fixedAsset.setStockLocation(
|
||||
fixedAsset.getInvoiceLine().getIncomingStockMove().getToStockLocation());
|
||||
}
|
||||
}
|
||||
|
||||
return fixedAssetList;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,670 @@
|
||||
package com.axelor.apps.supplychain.service;
|
||||
|
||||
import com.axelor.apps.account.db.Account;
|
||||
import com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.InvoiceLine;
|
||||
import com.axelor.apps.account.db.InvoiceTemplate;
|
||||
import com.axelor.apps.account.db.TaxLine;
|
||||
import com.axelor.apps.account.db.repo.AccountRepository;
|
||||
import com.axelor.apps.account.db.repo.InvoiceLineRepository;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.db.repo.TaxLineRepository;
|
||||
import com.axelor.apps.account.service.InvoiceTemplateService;
|
||||
import com.axelor.apps.account.service.invoice.generator.InvoiceLineGenerator;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.purchase.db.ImportationFolder;
|
||||
import com.axelor.apps.purchase.db.repo.ImportationFolderRepository;
|
||||
import com.axelor.apps.stock.db.ImportationFolderCostPrice;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.ImportationFolderCostPriceRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.supplychain.service.invoice.generator.InvoiceLineGeneratorSupplyChain;
|
||||
import com.axelor.auth.AuthUtils;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.MalformedURLException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import wslite.json.JSONException;
|
||||
|
||||
public class ImportationFolderServiceImpl {
|
||||
|
||||
@Inject protected ImportationFolderRepository importationFolderRepository;
|
||||
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Transactional
|
||||
public List<Long> calculateAvgPriceAndGenerateInvoice(
|
||||
List<StockMoveLine> stockMoveLines, ImportationFolder importationFolder)
|
||||
throws MalformedURLException, JSONException, AxelorException {
|
||||
|
||||
Set<StockMove> stockMovesSet = new HashSet<>();
|
||||
HashMap<String, List<StockMove>> stockMovesMap = new HashMap<>();
|
||||
|
||||
for (StockMoveLine stockMoveLine : stockMoveLines) {
|
||||
stockMovesSet.add(stockMoveLine.getStockMove());
|
||||
}
|
||||
|
||||
List<Long> invoiceIds = new ArrayList<>();
|
||||
|
||||
for (StockMove stockMove : stockMovesSet) {
|
||||
// Use the 'ref' as the key, and add the object to the corresponding list
|
||||
stockMovesMap
|
||||
.computeIfAbsent(stockMove.getSupplierShipmentRef(), k -> new ArrayList<>())
|
||||
.add(stockMove);
|
||||
}
|
||||
|
||||
stockMovesMap.forEach(
|
||||
(ref, stockMoveList) -> {
|
||||
// List<StockMove> stockMoveList = new ArrayList<>(stockMovesSet);
|
||||
if (stockMoveList.size() > 0) {
|
||||
|
||||
Optional<Invoice> invoiceOpt;
|
||||
try {
|
||||
invoiceOpt =
|
||||
Beans.get(StockMoveMultiInvoiceService.class)
|
||||
.createInvoiceFromMultiIncomingStockMove(stockMoveList);
|
||||
|
||||
Invoice invoice = invoiceOpt.get();
|
||||
Product product = Beans.get(ProductRepository.class).find(8931L);
|
||||
// BigDecimal freightPrice = importationFolder.getFreight();
|
||||
|
||||
BigDecimal freightPrice =
|
||||
stockMoveLines
|
||||
.stream()
|
||||
.filter(line -> line.getStockMove() == stockMovesMap.get(ref).get(0))
|
||||
.findFirst()
|
||||
.get()
|
||||
.getFreight()
|
||||
.setScale(2);
|
||||
StockLocation stockLocation =
|
||||
importationFolder
|
||||
.getStockMoveLineList()
|
||||
.get(0)
|
||||
.getStockMove()
|
||||
.getToStockLocation();
|
||||
TaxLine taxLine = null;
|
||||
if (stockLocation.getId() == 86) {
|
||||
// if (stockLocation.getId() == 61 || stockLocation.getId() == 60) {
|
||||
taxLine = Beans.get(TaxLineRepository.class).find(27L);
|
||||
} else {
|
||||
if (importationFolder.getStockMoveLineList().get(0).getPurchaseOrderLine()
|
||||
== null) {
|
||||
throw new AxelorException(
|
||||
importationFolder,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
"Purchase order missing",
|
||||
I18n.get(com.axelor.apps.base.exceptions.IExceptionMessage.EXCEPTION));
|
||||
}
|
||||
|
||||
taxLine =
|
||||
importationFolder
|
||||
.getStockMoveLineList()
|
||||
.get(0)
|
||||
.getPurchaseOrderLine()
|
||||
.getTaxLine();
|
||||
}
|
||||
|
||||
InvoiceLineGenerator invoiceLineGenerator =
|
||||
new InvoiceLineGeneratorSupplyChain(
|
||||
invoice,
|
||||
product,
|
||||
product.getName(),
|
||||
freightPrice,
|
||||
freightPrice,
|
||||
freightPrice,
|
||||
"",
|
||||
BigDecimal.ONE,
|
||||
product.getUnit(),
|
||||
taxLine,
|
||||
100,
|
||||
BigDecimal.ZERO,
|
||||
0,
|
||||
freightPrice,
|
||||
freightPrice,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null) {
|
||||
@Override
|
||||
public List<InvoiceLine> creates() throws AxelorException {
|
||||
|
||||
InvoiceLine invoiceLine = this.createInvoiceLine();
|
||||
invoiceLine.setPrice(freightPrice);
|
||||
invoiceLine.setPriceDiscounted(freightPrice);
|
||||
Long categoryId =
|
||||
importationFolder
|
||||
.getStockMoveLineList()
|
||||
.get(0)
|
||||
.getProduct()
|
||||
.getFamilleProduit()
|
||||
.getId();
|
||||
Account account = null;
|
||||
switch (categoryId.intValue()) {
|
||||
case 67:
|
||||
account = Beans.get(AccountRepository.class).find(1430L);
|
||||
break;
|
||||
case 68:
|
||||
account = Beans.get(AccountRepository.class).find(1430L);
|
||||
break;
|
||||
case 59:
|
||||
account = Beans.get(AccountRepository.class).find(1431L);
|
||||
break;
|
||||
default:
|
||||
account = Beans.get(AccountRepository.class).find(1431L);
|
||||
break;
|
||||
}
|
||||
invoiceLine.setAccount(account);
|
||||
List<InvoiceLine> invoiceLines = new ArrayList<InvoiceLine>();
|
||||
invoiceLines.add(invoiceLine);
|
||||
|
||||
return invoiceLines;
|
||||
}
|
||||
};
|
||||
|
||||
List<InvoiceLine> invoiceLines = invoiceLineGenerator.creates();
|
||||
invoice.addInvoiceLineListItem(invoiceLines.get(0));
|
||||
|
||||
logger.debug(
|
||||
"invoice.getInvoiceLineList() ********** ", invoice.getInvoiceLineList());
|
||||
|
||||
BigDecimal inTaxTotal = BigDecimal.ZERO;
|
||||
BigDecimal exTaxTotal = BigDecimal.ZERO;
|
||||
BigDecimal taxTotal = BigDecimal.ZERO;
|
||||
|
||||
for (InvoiceLine invoiceLine : invoice.getInvoiceLineList()) {
|
||||
BigDecimal currencyRate = BigDecimal.ZERO;
|
||||
// if(invoiceLine.getProduct().getId() == product.getId()){
|
||||
currencyRate = importationFolder.getStockMoveLineList().get(0).getCurrencyRate();
|
||||
// }else{
|
||||
// currencyRate = invoiceLine.getStockMoveLine().getCurrencyRate();
|
||||
// }
|
||||
|
||||
inTaxTotal =
|
||||
inTaxTotal.add(
|
||||
invoiceLine
|
||||
.getInTaxTotal()
|
||||
.multiply(currencyRate)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
exTaxTotal =
|
||||
exTaxTotal.add(
|
||||
invoiceLine
|
||||
.getExTaxTotal()
|
||||
.multiply(currencyRate)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
taxTotal = taxTotal.add(inTaxTotal.subtract(exTaxTotal));
|
||||
|
||||
BigDecimal price =
|
||||
(invoiceLine.getPrice().multiply(currencyRate))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
invoiceLine.setPrice(price);
|
||||
invoiceLine.setPriceDiscounted(
|
||||
invoiceLine
|
||||
.getPriceDiscounted()
|
||||
.multiply(currencyRate)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
invoiceLine.setInTaxTotal(
|
||||
invoiceLine
|
||||
.getInTaxTotal()
|
||||
.multiply(currencyRate)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
invoiceLine.setExTaxTotal(
|
||||
invoiceLine
|
||||
.getExTaxTotal()
|
||||
.multiply(currencyRate)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
invoiceLine.setInTaxPrice(
|
||||
invoiceLine
|
||||
.getInTaxPrice()
|
||||
.multiply(currencyRate)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
invoiceLine.setCompanyExTaxTotal(
|
||||
invoiceLine
|
||||
.getCompanyExTaxTotal()
|
||||
.multiply(currencyRate)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
invoiceLine.setCompanyInTaxTotal(
|
||||
invoiceLine
|
||||
.getCompanyInTaxTotal()
|
||||
.multiply(currencyRate)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
|
||||
Beans.get(InvoiceLineRepository.class).save(invoiceLine);
|
||||
|
||||
logger.debug("currencyRate****** {}", currencyRate);
|
||||
logger.debug("Invoice line in importation folder {}", invoiceLine.toString());
|
||||
}
|
||||
|
||||
invoice.setInTaxTotal(inTaxTotal);
|
||||
invoice.setExTaxTotal(exTaxTotal);
|
||||
invoice.setCompanyExTaxTotal(exTaxTotal);
|
||||
invoice.setCompanyInTaxTotal(inTaxTotal);
|
||||
invoice.setAmountRemaining(inTaxTotal);
|
||||
invoice.setCurrency(invoice.getCompany().getCurrency());
|
||||
invoice.setTaxTotal(taxTotal);
|
||||
invoice.setImportationFolder(importationFolder);
|
||||
invoice.setIsImportationPartnerInvoice(true);
|
||||
|
||||
importationFolder.setInvoicedFolder(true);
|
||||
|
||||
Invoice generatedInvoice = Beans.get(InvoiceRepository.class).save(invoice);
|
||||
invoiceIds.add(generatedInvoice.getId());
|
||||
} catch (AxelorException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
return invoiceIds;
|
||||
}
|
||||
|
||||
public String computeCostPrice(ImportationFolder importationFolder) {
|
||||
|
||||
List<StockMoveLine> stockMoveLines = importationFolder.getStockMoveLineList();
|
||||
String message = "";
|
||||
BigDecimal costPrice = BigDecimal.ZERO;
|
||||
Set<Product> productSet = new HashSet<>();
|
||||
// int scale = Beans.get(AppBaseService.class).getNbDecimalDigitForUnitPrice();
|
||||
int scale = 20;
|
||||
|
||||
for (StockMoveLine line : stockMoveLines) {
|
||||
productSet.add(line.getProduct());
|
||||
}
|
||||
|
||||
this.resetCostPriceList(importationFolder);
|
||||
|
||||
if (productSet.size() > 1) {
|
||||
List<Invoice> supplierInvoices =
|
||||
importationFolder
|
||||
.getInvoices()
|
||||
.stream()
|
||||
.filter(t -> t.getIsImportationPartnerInvoice())
|
||||
.collect(Collectors.toList());
|
||||
// BigDecimal totalQty =
|
||||
// stockMoveLines.stream().map(StockMoveLine::getRealQty).reduce(BigDecimal.ZERO,
|
||||
// BigDecimal::add);
|
||||
BigDecimal totalNetMass =
|
||||
stockMoveLines
|
||||
.stream()
|
||||
.map(StockMoveLine::getNetMass)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal totalVolume =
|
||||
stockMoveLines
|
||||
.stream()
|
||||
.map(StockMoveLine::getVolume)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
BigDecimal freight = BigDecimal.ZERO;
|
||||
|
||||
Set<StockMove> stockMovesSet = new HashSet<>();
|
||||
HashMap<String, List<StockMove>> stockMovesMap = new HashMap<>();
|
||||
|
||||
for (StockMoveLine stockMoveLine : stockMoveLines) {
|
||||
stockMovesSet.add(stockMoveLine.getStockMove());
|
||||
}
|
||||
|
||||
for (StockMove stockMove : stockMovesSet) {
|
||||
stockMovesMap
|
||||
.computeIfAbsent(stockMove.getSupplierShipmentRef(), k -> new ArrayList<>())
|
||||
.add(stockMove);
|
||||
}
|
||||
|
||||
freight =
|
||||
new BigDecimal(
|
||||
stockMovesMap
|
||||
.values()
|
||||
.stream()
|
||||
.filter(t ->
|
||||
t.get(0)
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.filter(line -> line.getRealQty() != null && line.getRealQty().compareTo(BigDecimal.ZERO) > 0)
|
||||
.findFirst()
|
||||
.isPresent())
|
||||
.mapToDouble(t ->
|
||||
t.get(0)
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.filter(line -> line.getRealQty() != null && line.getRealQty().compareTo(BigDecimal.ZERO) > 0)
|
||||
.findFirst()
|
||||
.map(line -> line.getFreight()
|
||||
.multiply(stockMoveLines.get(0).getCurrencyRate())
|
||||
.setScale(2, RoundingMode.HALF_EVEN)
|
||||
.doubleValue())
|
||||
.orElse(0.0)) // In case no valid line found, fallback to 0.0
|
||||
.sum());
|
||||
|
||||
|
||||
System.out.println("*****************freight**************************************");
|
||||
System.out.println(freight);
|
||||
System.out.println("*******************************************************");
|
||||
|
||||
Map<Product, Double> splitting = new HashMap<>();
|
||||
Map<Product, Double> splittingQty = new HashMap<>();
|
||||
List<InvoiceLine> invoiceLines = new ArrayList<>();
|
||||
BigDecimal totalInvoiceWithoutFreight = BigDecimal.ZERO;
|
||||
BigDecimal totalQty = BigDecimal.ZERO;
|
||||
|
||||
for (Invoice supplierInvoice : supplierInvoices) {
|
||||
BigDecimal freightInvoice = BigDecimal.ZERO;
|
||||
Optional<InvoiceLine> invoiceLine =
|
||||
supplierInvoice
|
||||
.getInvoiceLineList()
|
||||
.stream()
|
||||
.filter(t -> t.getProduct().getId() == 8931L)
|
||||
.findAny();
|
||||
if (invoiceLine.isPresent()) {
|
||||
freightInvoice = invoiceLine.get().getInTaxTotal();
|
||||
}
|
||||
BigDecimal invoiceWithoutFreight =
|
||||
supplierInvoice
|
||||
.getInTaxTotal()
|
||||
.subtract(freightInvoice)
|
||||
.setScale(2, RoundingMode.HALF_EVEN);
|
||||
totalInvoiceWithoutFreight = totalInvoiceWithoutFreight.add(invoiceWithoutFreight);
|
||||
invoiceLines.addAll(supplierInvoice.getInvoiceLineList());
|
||||
// total qty without freight qty = 1
|
||||
}
|
||||
|
||||
totalQty =
|
||||
invoiceLines
|
||||
.stream()
|
||||
.filter(t -> t.getProduct().getId() != 8931L)
|
||||
.map(InvoiceLine::getQty)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
splittingQty =
|
||||
invoiceLines
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
t -> t.getProduct(),
|
||||
Collectors.summingDouble(o -> o.getQty().doubleValue())));
|
||||
|
||||
if (importationFolder.getValorisation() == 1) {
|
||||
splitting =
|
||||
invoiceLines
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
t -> t.getProduct(),
|
||||
Collectors.summingDouble(o -> o.getInTaxTotal().doubleValue())));
|
||||
} else if (importationFolder.getValorisation() == 2) {
|
||||
splitting =
|
||||
invoiceLines
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
t -> t.getProduct(),
|
||||
Collectors.summingDouble(o -> o.getQty().doubleValue())));
|
||||
} else if (importationFolder.getValorisation() == 3) {
|
||||
splitting =
|
||||
stockMoveLines
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
t -> t.getProduct(),
|
||||
Collectors.summingDouble(o -> o.getNetMass().doubleValue())));
|
||||
|
||||
} else if (importationFolder.getValorisation() == 4) {
|
||||
splitting =
|
||||
stockMoveLines
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
t -> t.getProduct(),
|
||||
Collectors.summingDouble(o -> o.getVolume().doubleValue())));
|
||||
}
|
||||
|
||||
List<Invoice> invoices =
|
||||
importationFolder
|
||||
.getInvoices()
|
||||
.stream()
|
||||
.filter(t -> !t.getIsImportationPartnerInvoice())
|
||||
.collect(Collectors.toList());
|
||||
BigDecimal totalApproachs =
|
||||
invoices.stream().map(Invoice::getInTaxTotal).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
for (Product product : productSet) {
|
||||
Stream<InvoiceLine> invoiceLineStream =
|
||||
invoiceLines.stream().filter(t -> t.getProduct() == product);
|
||||
|
||||
BigDecimal productQty = new BigDecimal(splitting.get(product));
|
||||
BigDecimal productQty2 = new BigDecimal(splittingQty.get(product));
|
||||
BigDecimal freightSplit = BigDecimal.ZERO;
|
||||
// BigDecimal freightPerInvoiceForProduct=
|
||||
// invoiceLineStream.findFirst().get().getInvoice().getInvoiceLineList().stream().filter(t
|
||||
// -> t.getProduct().getId() == 8931L).findFirst().get().getInTaxTotal();
|
||||
|
||||
// total per product
|
||||
BigDecimal totalInvoiceProduct =
|
||||
invoiceLineStream
|
||||
.map(InvoiceLine::getInTaxTotal)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
BigDecimal percent = BigDecimal.ZERO;
|
||||
|
||||
if (totalInvoiceWithoutFreight.compareTo(BigDecimal.ZERO) == 0) {
|
||||
percent = productQty.divide(totalQty, scale, RoundingMode.HALF_EVEN);
|
||||
} else {
|
||||
switch (importationFolder.getValorisation()) {
|
||||
case 1:
|
||||
percent =
|
||||
totalInvoiceProduct.divide(
|
||||
totalInvoiceWithoutFreight, scale, RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
case 2:
|
||||
percent = productQty.divide(totalQty, scale, RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
case 3:
|
||||
percent = productQty.divide(totalNetMass, scale, RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
case 4:
|
||||
percent = productQty.divide(totalVolume, scale, RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
freightSplit = freight.multiply(percent).setScale(scale, RoundingMode.HALF_EVEN);
|
||||
|
||||
BigDecimal totalApproachsPerProduct =
|
||||
totalApproachs.multiply(percent).setScale(scale, RoundingMode.HALF_EVEN);
|
||||
|
||||
BigDecimal allPerProduct =
|
||||
totalApproachsPerProduct.add(totalInvoiceProduct).add(freightSplit);
|
||||
|
||||
costPrice = allPerProduct.divide(productQty2, scale, RoundingMode.HALF_EVEN);
|
||||
|
||||
message += "Cost price of " + product.getName() + " is " + costPrice + " || ";
|
||||
|
||||
List<StockMoveLine> lines =
|
||||
stockMoveLines
|
||||
.stream()
|
||||
.filter(t -> t.getProduct() == product)
|
||||
.collect(Collectors.toList());
|
||||
for (StockMoveLine line : lines) {
|
||||
this.computeCostPriceAndPersist(
|
||||
importationFolder,
|
||||
line,
|
||||
freightSplit,
|
||||
percent,
|
||||
totalApproachsPerProduct,
|
||||
totalInvoiceProduct,
|
||||
costPrice);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
List<Invoice> invoices = importationFolder.getInvoices();
|
||||
BigDecimal percent = BigDecimal.ZERO;
|
||||
BigDecimal inTaxTotal =
|
||||
invoices
|
||||
.stream()
|
||||
.map(Invoice::getInTaxTotal)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(scale, RoundingMode.HALF_UP);
|
||||
BigDecimal totalQty =
|
||||
stockMoveLines
|
||||
.stream()
|
||||
.map(StockMoveLine::getRealQty)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
costPrice = inTaxTotal.divide(totalQty, scale, RoundingMode.HALF_UP);
|
||||
|
||||
// this.resetCostPriceList(importationFolder);
|
||||
|
||||
for (StockMoveLine stockMoveLine : stockMoveLines) {
|
||||
this.computeCostPriceAndPersist(
|
||||
importationFolder,
|
||||
stockMoveLine,
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ONE,
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
costPrice);
|
||||
}
|
||||
|
||||
message = "CostPrice is : " + costPrice.toString();
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setStockMoveLineCurrenyRate(
|
||||
List<StockMoveLine> stockMoveLineList, BigDecimal currencyRate) {
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
stockMoveLine.setCurrencyRate(currencyRate);
|
||||
Beans.get(StockMoveLineRepository.class).save(stockMoveLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setStockMoveLineFreight(List<StockMoveLine> stockMoveLineList, BigDecimal freight) {
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
stockMoveLine.setFreight(freight);
|
||||
Beans.get(StockMoveLineRepository.class).save(stockMoveLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void resetCostPriceList(ImportationFolder importationFolder) {
|
||||
importationFolder.getImportationFolderCostPriceList().clear();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void computeCostPriceAndPersist(
|
||||
ImportationFolder importationFolder,
|
||||
StockMoveLine stockMoveLine,
|
||||
BigDecimal freightSplit,
|
||||
BigDecimal precent,
|
||||
BigDecimal totalApproachsPerProduct,
|
||||
BigDecimal totalInvoiceProduct,
|
||||
BigDecimal costPrice) {
|
||||
|
||||
ImportationFolderCostPrice importationFolderCostPrice = new ImportationFolderCostPrice();
|
||||
importationFolderCostPrice.setImportationFolder(importationFolder);
|
||||
importationFolderCostPrice.setStockMoveLine(stockMoveLine);
|
||||
importationFolderCostPrice.setCostPrice(costPrice.setScale(4, RoundingMode.HALF_EVEN));
|
||||
importationFolderCostPrice.setValorisation(importationFolder.getValorisation());
|
||||
importationFolderCostPrice.setPercent(
|
||||
precent.multiply(new BigDecimal(100)).setScale(2, RoundingMode.HALF_EVEN));
|
||||
importationFolderCostPrice.setFreightSplit(freightSplit.setScale(2, RoundingMode.HALF_EVEN));
|
||||
importationFolderCostPrice.setTotalApproach(
|
||||
totalApproachsPerProduct.setScale(2, RoundingMode.HALF_EVEN));
|
||||
importationFolderCostPrice.setTotalInvoice(
|
||||
totalInvoiceProduct.setScale(2, RoundingMode.HALF_EVEN));
|
||||
importationFolderCostPrice.setProduct(stockMoveLine.getProduct());
|
||||
importationFolder.addImportationFolderCostPriceListItem(importationFolderCostPrice);
|
||||
importationFolderRepository.save(importationFolder);
|
||||
}
|
||||
|
||||
// public void valisateCostPrice(ActionRequest request, ActionResponse response) {
|
||||
@Transactional
|
||||
public void validateCostPrice(ImportationFolderCostPrice importationFolderCostPrice) {
|
||||
|
||||
ImportationFolderCostPriceRepository importationFolderCostPriceRepository =
|
||||
Beans.get(ImportationFolderCostPriceRepository.class);
|
||||
|
||||
StockMove stockMove = importationFolderCostPrice.getStockMoveLine().getStockMove();
|
||||
Long productId = importationFolderCostPrice.getProduct().getId();
|
||||
Long stockMoveId = stockMove.getId();
|
||||
LocalDate toDate = LocalDate.now();
|
||||
BigDecimal costPrice = importationFolderCostPrice.getCostPrice();
|
||||
|
||||
try {
|
||||
Beans.get(StockMoveLineServiceSupplychainImpl.class)
|
||||
.resetValorization(productId, stockMoveId, toDate, false);
|
||||
Beans.get(StockMoveLineServiceSupplychainImpl.class)
|
||||
.fillStockMoveLines(productId, toDate, stockMoveId, false);
|
||||
Beans.get(StockMoveLineServiceSupplychainImpl.class)
|
||||
.valorize(productId, stockMoveId, toDate, costPrice, false, false);
|
||||
importationFolderCostPrice.setStatusSelect(2);
|
||||
importationFolderCostPrice.setValidationDate(LocalDate.now());
|
||||
importationFolderCostPriceRepository.save(importationFolderCostPrice);
|
||||
// response.setReload(true);
|
||||
} catch (Exception e) {
|
||||
logger.debug(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setStockMoveLineNetMass(List<StockMoveLine> stockMoveLineList, BigDecimal netMass) {
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
stockMoveLine.setNetMass(netMass);
|
||||
Beans.get(StockMoveLineRepository.class).save(stockMoveLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setStockMoveLineVolume(List<StockMoveLine> stockMoveLineList, BigDecimal volume) {
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
stockMoveLine.setVolume(volume);
|
||||
Beans.get(StockMoveLineRepository.class).save(stockMoveLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void rejectImportationFolder(ImportationFolder importationFolder, String rejectionRaison) {
|
||||
|
||||
importationFolder.setStatusSelect(ImportationFolderRepository.STATUS_REJECTED);
|
||||
importationFolder.setRejectionRaison(rejectionRaison);
|
||||
importationFolder.setRejectedDate(LocalDate.now());
|
||||
importationFolder.setRejectedByUser(AuthUtils.getUser());
|
||||
importationFolder.setRejectedInstanceSelect(1);
|
||||
importationFolderRepository.save(importationFolder);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Invoice generateFromModel(
|
||||
ImportationFolder importationFolder, InvoiceTemplate invoiceTemplate) throws AxelorException {
|
||||
Invoice invoice =
|
||||
Beans.get(InvoiceTemplateService.class).generateInvoiceFromTemplate(invoiceTemplate);
|
||||
invoice.setImportationFolder(importationFolder);
|
||||
invoice.setIsInvoiceApproach(true);
|
||||
return Beans.get(InvoiceRepository.class).save(invoice);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.exception.AxelorException;
|
||||
|
||||
public interface IntercoService {
|
||||
|
||||
/**
|
||||
* Given a purchase order, generate the sale order counterpart for the other company
|
||||
*
|
||||
* @param purchaseOrder
|
||||
* @return the generated sale order
|
||||
*/
|
||||
SaleOrder generateIntercoSaleFromPurchase(PurchaseOrder purchaseOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Given a sale order, generate the purchase order counterpart for the other company
|
||||
*
|
||||
* @param saleOrder
|
||||
* @return the generated purchase order
|
||||
*/
|
||||
PurchaseOrder generateIntercoPurchaseFromSale(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Given an invoice, generate the invoice counterpart for the other company.
|
||||
*
|
||||
* @param invoice
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
Invoice generateIntercoInvoice(Invoice invoice) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Find the interco company from the partner
|
||||
*
|
||||
* @param partner
|
||||
*/
|
||||
Company findIntercoCompany(Partner partner);
|
||||
}
|
||||
@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Account;
|
||||
import com.axelor.apps.account.db.AccountingSituation;
|
||||
import com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.InvoiceLine;
|
||||
import com.axelor.apps.account.db.PaymentMode;
|
||||
import com.axelor.apps.account.db.Tax;
|
||||
import com.axelor.apps.account.db.TaxEquiv;
|
||||
import com.axelor.apps.account.db.TaxLine;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.service.AccountManagementAccountService;
|
||||
import com.axelor.apps.account.service.AccountingSituationService;
|
||||
import com.axelor.apps.account.service.invoice.InvoiceLineService;
|
||||
import com.axelor.apps.account.service.invoice.InvoiceService;
|
||||
import com.axelor.apps.account.service.payment.PaymentModeService;
|
||||
import com.axelor.apps.base.db.Address;
|
||||
import com.axelor.apps.base.db.BankDetails;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.PriceList;
|
||||
import com.axelor.apps.base.db.repo.CompanyRepository;
|
||||
import com.axelor.apps.base.db.repo.PriceListRepository;
|
||||
import com.axelor.apps.base.service.AddressService;
|
||||
import com.axelor.apps.base.service.PartnerPriceListService;
|
||||
import com.axelor.apps.base.service.PartnerService;
|
||||
import com.axelor.apps.base.service.TradingNameService;
|
||||
import com.axelor.apps.base.service.tax.FiscalPositionService;
|
||||
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.purchase.service.PurchaseOrderLineService;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeService;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderCreateService;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderLineService;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowService;
|
||||
import com.axelor.apps.stock.service.StockLocationService;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class IntercoServiceImpl implements IntercoService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public SaleOrder generateIntercoSaleFromPurchase(PurchaseOrder purchaseOrder)
|
||||
throws AxelorException {
|
||||
|
||||
SaleOrderCreateService saleOrderCreateService = Beans.get(SaleOrderCreateService.class);
|
||||
SaleOrderComputeService saleOrderComputeService = Beans.get(SaleOrderComputeService.class);
|
||||
Company intercoCompany = findIntercoCompany(purchaseOrder.getSupplierPartner());
|
||||
// create sale order
|
||||
SaleOrder saleOrder =
|
||||
saleOrderCreateService.createSaleOrder(
|
||||
null,
|
||||
intercoCompany,
|
||||
purchaseOrder.getContactPartner(),
|
||||
purchaseOrder.getCurrency(),
|
||||
purchaseOrder.getDeliveryDate(),
|
||||
null,
|
||||
null,
|
||||
purchaseOrder.getOrderDate(),
|
||||
purchaseOrder.getPriceList(),
|
||||
purchaseOrder.getCompany().getPartner(),
|
||||
null);
|
||||
|
||||
// in ati
|
||||
saleOrder.setInAti(purchaseOrder.getInAti());
|
||||
|
||||
// copy date
|
||||
saleOrder.setOrderDate(purchaseOrder.getOrderDate());
|
||||
|
||||
// copy payments
|
||||
PaymentMode intercoPaymentMode =
|
||||
Beans.get(PaymentModeService.class).reverseInOut(purchaseOrder.getPaymentMode());
|
||||
saleOrder.setPaymentMode(intercoPaymentMode);
|
||||
saleOrder.setPaymentCondition(purchaseOrder.getPaymentCondition());
|
||||
|
||||
// copy delivery info
|
||||
saleOrder.setDeliveryDate(purchaseOrder.getDeliveryDate());
|
||||
saleOrder.setShipmentMode(purchaseOrder.getShipmentMode());
|
||||
saleOrder.setFreightCarrierMode(purchaseOrder.getFreightCarrierMode());
|
||||
|
||||
// get stock location
|
||||
saleOrder.setStockLocation(
|
||||
Beans.get(StockLocationService.class).getPickupDefaultStockLocation(intercoCompany));
|
||||
|
||||
// copy timetable info
|
||||
saleOrder.setExpectedRealisationDate(purchaseOrder.getExpectedRealisationDate());
|
||||
saleOrder.setAmountToBeSpreadOverTheTimetable(
|
||||
purchaseOrder.getAmountToBeSpreadOverTheTimetable());
|
||||
|
||||
// create lines
|
||||
List<PurchaseOrderLine> purchaseOrderLineList = purchaseOrder.getPurchaseOrderLineList();
|
||||
if (purchaseOrderLineList != null) {
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLineList) {
|
||||
this.createIntercoSaleLineFromPurchaseLine(purchaseOrderLine, saleOrder);
|
||||
}
|
||||
}
|
||||
|
||||
saleOrder.setPrintingSettings(intercoCompany.getPrintingSettings());
|
||||
|
||||
// compute the sale order
|
||||
saleOrderComputeService.computeSaleOrder(saleOrder);
|
||||
|
||||
saleOrder.setCreatedByInterco(true);
|
||||
Beans.get(SaleOrderRepository.class).save(saleOrder);
|
||||
if (Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getIntercoSaleOrderCreateFinalized()) {
|
||||
Beans.get(SaleOrderWorkflowService.class).finalizeQuotation(saleOrder);
|
||||
}
|
||||
purchaseOrder.setExternalReference(saleOrder.getSaleOrderSeq());
|
||||
saleOrder.setExternalReference(purchaseOrder.getPurchaseOrderSeq());
|
||||
return saleOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public PurchaseOrder generateIntercoPurchaseFromSale(SaleOrder saleOrder) throws AxelorException {
|
||||
|
||||
PurchaseOrderService purchaseOrderService = Beans.get(PurchaseOrderService.class);
|
||||
|
||||
Company intercoCompany = findIntercoCompany(saleOrder.getClientPartner());
|
||||
// create purchase order
|
||||
PurchaseOrder purchaseOrder = new PurchaseOrder();
|
||||
purchaseOrder.setCompany(intercoCompany);
|
||||
purchaseOrder.setContactPartner(saleOrder.getContactPartner());
|
||||
purchaseOrder.setCurrency(saleOrder.getCurrency());
|
||||
purchaseOrder.setDeliveryDate(saleOrder.getDeliveryDate());
|
||||
purchaseOrder.setOrderDate(saleOrder.getCreationDate());
|
||||
purchaseOrder.setPriceList(saleOrder.getPriceList());
|
||||
purchaseOrder.setTradingName(saleOrder.getTradingName());
|
||||
purchaseOrder.setPurchaseOrderLineList(new ArrayList<>());
|
||||
|
||||
purchaseOrder.setPrintingSettings(
|
||||
Beans.get(TradingNameService.class).getDefaultPrintingSettings(null, intercoCompany));
|
||||
|
||||
purchaseOrder.setStatusSelect(PurchaseOrderRepository.STATUS_DRAFT);
|
||||
purchaseOrder.setSupplierPartner(saleOrder.getCompany().getPartner());
|
||||
purchaseOrder.setTradingName(saleOrder.getTradingName());
|
||||
|
||||
// in ati
|
||||
purchaseOrder.setInAti(saleOrder.getInAti());
|
||||
|
||||
// copy payments
|
||||
PaymentMode intercoPaymentMode =
|
||||
Beans.get(PaymentModeService.class).reverseInOut(saleOrder.getPaymentMode());
|
||||
purchaseOrder.setPaymentMode(intercoPaymentMode);
|
||||
purchaseOrder.setPaymentCondition(saleOrder.getPaymentCondition());
|
||||
|
||||
// copy delivery info
|
||||
purchaseOrder.setDeliveryDate(saleOrder.getDeliveryDate());
|
||||
purchaseOrder.setStockLocation(
|
||||
Beans.get(StockLocationService.class).getDefaultReceiptStockLocation(intercoCompany));
|
||||
purchaseOrder.setShipmentMode(saleOrder.getShipmentMode());
|
||||
purchaseOrder.setFreightCarrierMode(saleOrder.getFreightCarrierMode());
|
||||
|
||||
// copy timetable info
|
||||
purchaseOrder.setExpectedRealisationDate(saleOrder.getExpectedRealisationDate());
|
||||
purchaseOrder.setAmountToBeSpreadOverTheTimetable(
|
||||
saleOrder.getAmountToBeSpreadOverTheTimetable());
|
||||
|
||||
// create lines
|
||||
List<SaleOrderLine> saleOrderLineList = saleOrder.getSaleOrderLineList();
|
||||
if (saleOrderLineList != null) {
|
||||
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
|
||||
this.createIntercoPurchaseLineFromSaleLine(saleOrderLine, purchaseOrder);
|
||||
}
|
||||
}
|
||||
purchaseOrder.setPrintingSettings(intercoCompany.getPrintingSettings());
|
||||
|
||||
// compute the purchase order
|
||||
purchaseOrderService.computePurchaseOrder(purchaseOrder);
|
||||
|
||||
purchaseOrder.setCreatedByInterco(true);
|
||||
Beans.get(PurchaseOrderRepository.class).save(purchaseOrder);
|
||||
if (Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getIntercoPurchaseOrderCreateRequested()) {
|
||||
Beans.get(PurchaseOrderService.class).requestPurchaseOrder(purchaseOrder);
|
||||
}
|
||||
saleOrder.setExternalReference(purchaseOrder.getPurchaseOrderSeq());
|
||||
purchaseOrder.setExternalReference(saleOrder.getSaleOrderSeq());
|
||||
return purchaseOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param saleOrderLine the sale order line needed to create the purchase order line
|
||||
* @param purchaseOrder the purchase order line belongs to this purchase order
|
||||
* @return the created purchase order line
|
||||
*/
|
||||
protected PurchaseOrderLine createIntercoPurchaseLineFromSaleLine(
|
||||
SaleOrderLine saleOrderLine, PurchaseOrder purchaseOrder) throws AxelorException {
|
||||
PurchaseOrderLine purchaseOrderLine =
|
||||
Beans.get(PurchaseOrderLineService.class)
|
||||
.createPurchaseOrderLine(
|
||||
purchaseOrder,
|
||||
saleOrderLine.getProduct(),
|
||||
saleOrderLine.getProductName(),
|
||||
saleOrderLine.getDescription(),
|
||||
saleOrderLine.getQty(),
|
||||
saleOrderLine.getUnit());
|
||||
// compute amount
|
||||
purchaseOrderLine.setPrice(saleOrderLine.getPrice());
|
||||
purchaseOrderLine.setInTaxPrice(saleOrderLine.getInTaxPrice());
|
||||
purchaseOrderLine.setExTaxTotal(saleOrderLine.getExTaxTotal());
|
||||
purchaseOrderLine.setDiscountTypeSelect(saleOrderLine.getDiscountTypeSelect());
|
||||
purchaseOrderLine.setDiscountAmount(saleOrderLine.getDiscountAmount());
|
||||
|
||||
// delivery
|
||||
purchaseOrderLine.setEstimatedDelivDate(saleOrderLine.getEstimatedDelivDate());
|
||||
|
||||
// compute price discounted
|
||||
BigDecimal priceDiscounted =
|
||||
Beans.get(PurchaseOrderLineService.class)
|
||||
.computeDiscount(purchaseOrderLine, purchaseOrder.getInAti());
|
||||
purchaseOrderLine.setPriceDiscounted(priceDiscounted);
|
||||
|
||||
// tax
|
||||
purchaseOrderLine.setTaxLine(saleOrderLine.getTaxLine());
|
||||
|
||||
purchaseOrder.addPurchaseOrderLineListItem(purchaseOrderLine);
|
||||
return purchaseOrderLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param purchaseOrderLine the purchase order line needed to create the sale order line
|
||||
* @param saleOrder the sale order line belongs to this purchase order
|
||||
* @return the created purchase order line
|
||||
*/
|
||||
protected SaleOrderLine createIntercoSaleLineFromPurchaseLine(
|
||||
PurchaseOrderLine purchaseOrderLine, SaleOrder saleOrder) {
|
||||
SaleOrderLine saleOrderLine = new SaleOrderLine();
|
||||
|
||||
saleOrderLine.setSaleOrder(saleOrder);
|
||||
saleOrderLine.setProduct(purchaseOrderLine.getProduct());
|
||||
saleOrderLine.setProductName(purchaseOrderLine.getProductName());
|
||||
|
||||
saleOrderLine.setDescription(purchaseOrderLine.getDescription());
|
||||
saleOrderLine.setQty(purchaseOrderLine.getQty());
|
||||
saleOrderLine.setUnit(purchaseOrderLine.getUnit());
|
||||
|
||||
// compute amount
|
||||
saleOrderLine.setPrice(purchaseOrderLine.getPrice());
|
||||
saleOrderLine.setInTaxPrice(purchaseOrderLine.getInTaxPrice());
|
||||
saleOrderLine.setExTaxTotal(purchaseOrderLine.getExTaxTotal());
|
||||
saleOrderLine.setDiscountTypeSelect(purchaseOrderLine.getDiscountTypeSelect());
|
||||
saleOrderLine.setDiscountAmount(purchaseOrderLine.getDiscountAmount());
|
||||
|
||||
// compute price discounted
|
||||
BigDecimal priceDiscounted =
|
||||
Beans.get(SaleOrderLineService.class).computeDiscount(saleOrderLine, saleOrder.getInAti());
|
||||
saleOrderLine.setPriceDiscounted(priceDiscounted);
|
||||
|
||||
// delivery
|
||||
saleOrderLine.setEstimatedDelivDate(purchaseOrderLine.getEstimatedDelivDate());
|
||||
|
||||
// tax
|
||||
saleOrderLine.setTaxLine(purchaseOrderLine.getTaxLine());
|
||||
|
||||
saleOrder.addSaleOrderLineListItem(saleOrderLine);
|
||||
return saleOrderLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Invoice generateIntercoInvoice(Invoice invoice) throws AxelorException {
|
||||
PartnerService partnerService = Beans.get(PartnerService.class);
|
||||
InvoiceRepository invoiceRepository = Beans.get(InvoiceRepository.class);
|
||||
InvoiceService invoiceService = Beans.get(InvoiceService.class);
|
||||
|
||||
boolean isPurchase;
|
||||
// set the status
|
||||
int generatedOperationTypeSelect;
|
||||
int priceListRepositoryType;
|
||||
switch (invoice.getOperationTypeSelect()) {
|
||||
case InvoiceRepository.OPERATION_TYPE_SUPPLIER_PURCHASE:
|
||||
generatedOperationTypeSelect = InvoiceRepository.OPERATION_TYPE_CLIENT_SALE;
|
||||
priceListRepositoryType = PriceListRepository.TYPE_SALE;
|
||||
isPurchase = false;
|
||||
break;
|
||||
case InvoiceRepository.OPERATION_TYPE_SUPPLIER_REFUND:
|
||||
generatedOperationTypeSelect = InvoiceRepository.OPERATION_TYPE_CLIENT_REFUND;
|
||||
priceListRepositoryType = PriceListRepository.TYPE_SALE;
|
||||
isPurchase = false;
|
||||
break;
|
||||
case InvoiceRepository.OPERATION_TYPE_CLIENT_SALE:
|
||||
generatedOperationTypeSelect = InvoiceRepository.OPERATION_TYPE_SUPPLIER_PURCHASE;
|
||||
priceListRepositoryType = PriceListRepository.TYPE_PURCHASE;
|
||||
isPurchase = true;
|
||||
break;
|
||||
case InvoiceRepository.OPERATION_TYPE_CLIENT_REFUND:
|
||||
generatedOperationTypeSelect = InvoiceRepository.OPERATION_TYPE_SUPPLIER_REFUND;
|
||||
priceListRepositoryType = PriceListRepository.TYPE_PURCHASE;
|
||||
isPurchase = true;
|
||||
break;
|
||||
default:
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_MISSING_FIELD,
|
||||
I18n.get(IExceptionMessage.INVOICE_MISSING_TYPE),
|
||||
invoice);
|
||||
}
|
||||
Company intercoCompany = findIntercoCompany(invoice.getPartner());
|
||||
Partner intercoPartner = invoice.getCompany().getPartner();
|
||||
PaymentMode intercoPaymentMode =
|
||||
Beans.get(PaymentModeService.class).reverseInOut(invoice.getPaymentMode());
|
||||
Address intercoAddress = partnerService.getInvoicingAddress(intercoPartner);
|
||||
BankDetails intercoBankDetails = partnerService.getDefaultBankDetails(intercoPartner);
|
||||
AccountingSituation accountingSituation =
|
||||
Beans.get(AccountingSituationService.class)
|
||||
.getAccountingSituation(intercoPartner, intercoCompany);
|
||||
PriceList intercoPriceList =
|
||||
Beans.get(PartnerPriceListService.class)
|
||||
.getDefaultPriceList(intercoPartner, priceListRepositoryType);
|
||||
|
||||
Invoice intercoInvoice = invoiceRepository.copy(invoice, true);
|
||||
|
||||
intercoInvoice.setOperationTypeSelect(generatedOperationTypeSelect);
|
||||
intercoInvoice.setCompany(intercoCompany);
|
||||
intercoInvoice.setPartner(intercoPartner);
|
||||
intercoInvoice.setAddress(intercoAddress);
|
||||
intercoInvoice.setAddressStr(Beans.get(AddressService.class).computeAddressStr(intercoAddress));
|
||||
intercoInvoice.setPaymentMode(intercoPaymentMode);
|
||||
intercoInvoice.setBankDetails(intercoBankDetails);
|
||||
Set<Invoice> invoices = invoiceService.getDefaultAdvancePaymentInvoice(intercoInvoice);
|
||||
intercoInvoice.setAdvancePaymentInvoiceSet(invoices);
|
||||
if (accountingSituation != null) {
|
||||
intercoInvoice.setInvoiceAutomaticMail(accountingSituation.getInvoiceAutomaticMail());
|
||||
intercoInvoice.setInvoiceMessageTemplate(accountingSituation.getInvoiceMessageTemplate());
|
||||
intercoInvoice.setPfpValidatorUser(accountingSituation.getPfpValidatorUser());
|
||||
}
|
||||
intercoInvoice.setPriceList(intercoPriceList);
|
||||
intercoInvoice.setInvoicesCopySelect(intercoPartner.getInvoicesCopySelect());
|
||||
intercoInvoice.setCreatedByInterco(true);
|
||||
intercoInvoice.setInterco(false);
|
||||
|
||||
intercoInvoice.setPrintingSettings(intercoCompany.getPrintingSettings());
|
||||
|
||||
if (intercoInvoice.getInvoiceLineList() != null) {
|
||||
for (InvoiceLine invoiceLine : intercoInvoice.getInvoiceLineList()) {
|
||||
invoiceLine.setInvoice(intercoInvoice);
|
||||
createIntercoInvoiceLine(invoiceLine, isPurchase);
|
||||
}
|
||||
}
|
||||
|
||||
invoiceService.compute(intercoInvoice);
|
||||
intercoInvoice.setExternalReference(invoice.getInvoiceId());
|
||||
intercoInvoice = invoiceRepository.save(intercoInvoice);
|
||||
if (Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getIntercoInvoiceCreateValidated()) {
|
||||
Beans.get(InvoiceService.class).validate(intercoInvoice);
|
||||
}
|
||||
invoice.setExternalReference(intercoInvoice.getInvoiceId());
|
||||
return intercoInvoice;
|
||||
}
|
||||
|
||||
protected InvoiceLine createIntercoInvoiceLine(InvoiceLine invoiceLine, boolean isPurchase)
|
||||
throws AxelorException {
|
||||
AccountManagementAccountService accountManagementAccountService =
|
||||
Beans.get(AccountManagementAccountService.class);
|
||||
InvoiceLineService invoiceLineService = Beans.get(InvoiceLineService.class);
|
||||
Invoice intercoInvoice = invoiceLine.getInvoice();
|
||||
Partner partner = intercoInvoice.getPartner();
|
||||
if (intercoInvoice.getCompany() != null) {
|
||||
Account account =
|
||||
accountManagementAccountService.getProductAccount(
|
||||
invoiceLine.getProduct(),
|
||||
intercoInvoice.getCompany(),
|
||||
partner.getFiscalPosition(),
|
||||
isPurchase,
|
||||
false);
|
||||
invoiceLine.setAccount(account);
|
||||
|
||||
TaxLine taxLine = invoiceLineService.getTaxLine(intercoInvoice, invoiceLine, isPurchase);
|
||||
invoiceLine.setTaxLine(taxLine);
|
||||
invoiceLine.setTaxRate(taxLine.getValue());
|
||||
invoiceLine.setTaxCode(taxLine.getTax().getCode());
|
||||
Tax tax =
|
||||
accountManagementAccountService.getProductTax(
|
||||
invoiceLine.getProduct(), intercoInvoice.getCompany(), null, isPurchase);
|
||||
TaxEquiv taxEquiv =
|
||||
Beans.get(FiscalPositionService.class)
|
||||
.getTaxEquiv(intercoInvoice.getPartner().getFiscalPosition(), tax);
|
||||
invoiceLine.setTaxEquiv(taxEquiv);
|
||||
invoiceLine.setCompanyExTaxTotal(
|
||||
invoiceLineService.getCompanyExTaxTotal(invoiceLine.getExTaxTotal(), intercoInvoice));
|
||||
invoiceLine.setCompanyInTaxTotal(
|
||||
invoiceLineService.getCompanyExTaxTotal(invoiceLine.getInTaxTotal(), intercoInvoice));
|
||||
}
|
||||
return invoiceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Company findIntercoCompany(Partner partner) {
|
||||
return Beans.get(CompanyRepository.class).all().filter("self.partner = ?", partner).fetchOne();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Account;
|
||||
import com.axelor.apps.account.db.BudgetDistribution;
|
||||
import com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.InvoiceLine;
|
||||
import com.axelor.apps.account.db.TaxLine;
|
||||
import com.axelor.apps.account.db.repo.InvoiceLineRepository;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.service.AccountManagementAccountService;
|
||||
import com.axelor.apps.account.service.AnalyticMoveLineService;
|
||||
import com.axelor.apps.account.service.app.AppAccountService;
|
||||
import com.axelor.apps.account.service.invoice.InvoiceLineServiceImpl;
|
||||
import com.axelor.apps.account.service.invoice.InvoiceToolService;
|
||||
import com.axelor.apps.account.service.invoice.generator.InvoiceLineGenerator;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Unit;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.base.service.CurrencyService;
|
||||
import com.axelor.apps.base.service.PriceListService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.service.PurchaseProductService;
|
||||
import com.axelor.apps.purchase.service.SupplierCatalogService;
|
||||
import com.axelor.apps.sale.db.PackLine;
|
||||
import com.axelor.apps.sale.service.app.AppSaleService;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.apps.supplychain.service.invoice.generator.InvoiceLineGeneratorSupplyChain;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class InvoiceLineSupplychainService extends InvoiceLineServiceImpl {
|
||||
|
||||
protected PurchaseProductService purchaseProductService;
|
||||
|
||||
@Inject private AppSupplychainService appSupplychainService;
|
||||
|
||||
@Inject private AppSaleService appSaleService;
|
||||
|
||||
@Inject protected SupplierCatalogService supplierCatalogService;
|
||||
|
||||
@Inject
|
||||
public InvoiceLineSupplychainService(
|
||||
CurrencyService currencyService,
|
||||
PriceListService priceListService,
|
||||
AppAccountService appAccountService,
|
||||
AnalyticMoveLineService analyticMoveLineService,
|
||||
AccountManagementAccountService accountManagementAccountService,
|
||||
PurchaseProductService purchaseProductService) {
|
||||
|
||||
super(
|
||||
currencyService,
|
||||
priceListService,
|
||||
appAccountService,
|
||||
analyticMoveLineService,
|
||||
accountManagementAccountService);
|
||||
this.purchaseProductService = purchaseProductService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unit getUnit(Product product, boolean isPurchase) {
|
||||
if (isPurchase) {
|
||||
if (product.getPurchasesUnit() != null) {
|
||||
return product.getPurchasesUnit();
|
||||
} else {
|
||||
return product.getUnit();
|
||||
}
|
||||
} else {
|
||||
if (product.getSalesUnit() != null) {
|
||||
return product.getPurchasesUnit();
|
||||
} else {
|
||||
return product.getUnit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getDiscount(Invoice invoice, InvoiceLine invoiceLine, BigDecimal price)
|
||||
throws AxelorException {
|
||||
|
||||
Map<String, Object> discounts = new HashMap<>();
|
||||
|
||||
if (invoice.getOperationTypeSelect() < InvoiceRepository.OPERATION_TYPE_CLIENT_SALE) {
|
||||
Map<String, Object> catalogInfo = this.updateInfoFromCatalog(invoice, invoiceLine);
|
||||
|
||||
if (catalogInfo != null) {
|
||||
if (catalogInfo.get("price") != null) {
|
||||
price = (BigDecimal) catalogInfo.get("price");
|
||||
}
|
||||
discounts.put("productName", catalogInfo.get("productName"));
|
||||
}
|
||||
}
|
||||
|
||||
discounts.putAll(super.getDiscount(invoice, invoiceLine, price));
|
||||
|
||||
return discounts;
|
||||
}
|
||||
|
||||
private Map<String, Object> updateInfoFromCatalog(Invoice invoice, InvoiceLine invoiceLine)
|
||||
throws AxelorException {
|
||||
|
||||
return supplierCatalogService.updateInfoFromCatalog(
|
||||
invoiceLine.getProduct(),
|
||||
invoiceLine.getQty(),
|
||||
invoice.getPartner(),
|
||||
invoice.getCurrency(),
|
||||
invoice.getInvoiceDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> fillPriceAndAccount(
|
||||
Invoice invoice, InvoiceLine invoiceLine, boolean isPurchase) throws AxelorException {
|
||||
|
||||
try {
|
||||
return super.fillPriceAndAccount(invoice, invoiceLine, isPurchase);
|
||||
} catch (AxelorException e) {
|
||||
if (checkTaxRequired(invoiceLine, invoiceLine.getPackPriceSelect())) {
|
||||
throw e;
|
||||
} else {
|
||||
Map<String, Object> productInformation = new HashMap<>();
|
||||
productInformation.put("taxLine", null);
|
||||
productInformation.put("taxRate", BigDecimal.ZERO);
|
||||
productInformation.put("taxCode", null);
|
||||
productInformation.put("taxEquiv", null);
|
||||
productInformation.put("account", null);
|
||||
productInformation.put("discountAmount", BigDecimal.ZERO);
|
||||
productInformation.put("discountTypeSelect", 0);
|
||||
productInformation.put("price", BigDecimal.ZERO);
|
||||
return productInformation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkTaxRequired(InvoiceLine invoiceLine, Integer packPriceSelect) {
|
||||
|
||||
if (appSupplychainService.getAppSupplychain().getActive()
|
||||
&& appSaleService.getAppSale().getProductPackMgt()) {
|
||||
|
||||
if (invoiceLine.getIsSubLine() && packPriceSelect == InvoiceLineRepository.PACK_PRICE_ONLY) {
|
||||
return false;
|
||||
}
|
||||
if (invoiceLine.getTypeSelect() == InvoiceLineRepository.TYPE_PACK
|
||||
&& packPriceSelect == InvoiceLineRepository.SUBLINE_PRICE_ONLY) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> fillProductInformation(Invoice invoice, InvoiceLine invoiceLine)
|
||||
throws AxelorException {
|
||||
|
||||
Map<String, Object> productInformation = new HashMap<>();
|
||||
|
||||
boolean isPurchase = InvoiceToolService.isPurchase(invoice);
|
||||
Integer sequence = invoiceLine.getSequence();
|
||||
if (sequence == null) {
|
||||
sequence = 0;
|
||||
}
|
||||
|
||||
if (sequence == 0 && invoice.getInvoiceLineList() != null) {
|
||||
sequence = invoice.getInvoiceLineList().size();
|
||||
invoiceLine.setSequence(sequence);
|
||||
}
|
||||
|
||||
if (appSupplychainService.getAppSupplychain().getActive()
|
||||
&& appSaleService.getAppSale().getProductPackMgt()
|
||||
&& invoiceLine.getProduct() != null
|
||||
&& invoiceLine
|
||||
.getProduct()
|
||||
.getProductTypeSelect()
|
||||
.equals(ProductRepository.PRODUCT_TYPE_PACK)
|
||||
&& invoiceLine.getProduct().getPackLines() != null) {
|
||||
|
||||
List<InvoiceLine> subLineList = new ArrayList<>();
|
||||
Integer packPriceSelect = invoiceLine.getProduct().getPackPriceSelect();
|
||||
invoiceLine.setTypeSelect(InvoiceLineRepository.TYPE_PACK);
|
||||
invoiceLine.setPackPriceSelect(packPriceSelect);
|
||||
for (PackLine packLine : invoiceLine.getProduct().getPackLines()) {
|
||||
|
||||
InvoiceLine subLine = new InvoiceLine();
|
||||
subLine.setProduct(packLine.getProduct());
|
||||
subLine.setUnit(this.getUnit(packLine.getProduct(), isPurchase));
|
||||
subLine.setProductName(packLine.getProduct().getName());
|
||||
subLine.setQty(new BigDecimal(packLine.getQuantity()));
|
||||
subLine.setIsSubLine(true);
|
||||
subLine.setPackPriceSelect(packPriceSelect);
|
||||
String description = null;
|
||||
if ((isPurchase
|
||||
&& appAccountService
|
||||
.getAppInvoice()
|
||||
.getIsEnabledProductDescriptionCopyForCustomers())
|
||||
|| (!isPurchase
|
||||
&& appAccountService
|
||||
.getAppInvoice()
|
||||
.getIsEnabledProductDescriptionCopyForSuppliers())) {
|
||||
description = invoiceLine.getProduct().getDescription();
|
||||
}
|
||||
Map<String, Object> accountInfo = super.fillPriceAndAccount(invoice, subLine, isPurchase);
|
||||
subLine.setAccount((Account) accountInfo.get("account"));
|
||||
if (packPriceSelect != InvoiceLineRepository.PACK_PRICE_ONLY) {
|
||||
subLine.setPrice((BigDecimal) accountInfo.get("price"));
|
||||
subLine.setInTaxPrice((BigDecimal) accountInfo.get("inTaxPrice"));
|
||||
subLine.setPriceDiscounted(computeDiscount(subLine, invoice.getInAti()));
|
||||
}
|
||||
|
||||
int discountTypeSelect = 0;
|
||||
if (accountInfo.get("discountTypeSelect") != null) {
|
||||
discountTypeSelect = (Integer) accountInfo.get("discountTypeSelect");
|
||||
}
|
||||
|
||||
BigDecimal qty = new BigDecimal(packLine.getQuantity());
|
||||
|
||||
if (invoiceLine.getQty() != null) {
|
||||
qty = qty.multiply(invoiceLine.getQty()).setScale(2, RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
InvoiceLineGenerator invoiceLineGenerator =
|
||||
new InvoiceLineGeneratorSupplyChain(
|
||||
invoice,
|
||||
subLine.getProduct(),
|
||||
subLine.getProductName(),
|
||||
subLine.getPrice(),
|
||||
subLine.getInTaxPrice(),
|
||||
subLine.getPriceDiscounted(),
|
||||
description,
|
||||
qty,
|
||||
subLine.getUnit(),
|
||||
(TaxLine) accountInfo.get("taxLine"),
|
||||
++sequence,
|
||||
(BigDecimal) accountInfo.get("discountAmount"),
|
||||
discountTypeSelect,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
packPriceSelect) {
|
||||
|
||||
@Override
|
||||
public List<InvoiceLine> creates() throws AxelorException {
|
||||
|
||||
InvoiceLine invoiceLine = this.createInvoiceLine();
|
||||
invoiceLine.setInvoice(null); // Enable line to be added on main o2m
|
||||
List<InvoiceLine> lines = new ArrayList<>();
|
||||
lines.add(invoiceLine);
|
||||
|
||||
return lines;
|
||||
}
|
||||
};
|
||||
subLineList.addAll(invoiceLineGenerator.creates());
|
||||
}
|
||||
productInformation.put("typeSelect", InvoiceLineRepository.TYPE_PACK);
|
||||
productInformation.put("packPriceSelect", packPriceSelect);
|
||||
productInformation.put("subLineList", subLineList);
|
||||
} else {
|
||||
productInformation.put("typeSelect", InvoiceLineRepository.TYPE_NORMAL);
|
||||
productInformation.put("packPriceSelect", InvoiceLineRepository.PACK_PRICE_ONLY);
|
||||
productInformation.put("subLineList", null);
|
||||
productInformation.put("totalPack", BigDecimal.ZERO);
|
||||
invoiceLine.setTypeSelect(InvoiceLineRepository.TYPE_NORMAL);
|
||||
}
|
||||
|
||||
productInformation.putAll(super.fillProductInformation(invoice, invoiceLine));
|
||||
|
||||
return productInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountRequired(InvoiceLine invoiceLine) {
|
||||
|
||||
if (appSaleService.getAppSale().getProductPackMgt()) {
|
||||
|
||||
if (invoiceLine.getIsSubLine()
|
||||
&& invoiceLine.getPackPriceSelect() == InvoiceLineRepository.PACK_PRICE_ONLY) {
|
||||
return false;
|
||||
}
|
||||
if (invoiceLine.getTypeSelect() == InvoiceLineRepository.TYPE_PACK
|
||||
&& invoiceLine.getPackPriceSelect() == InvoiceLineRepository.SUBLINE_PRICE_ONLY) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void computeBudgetDistributionSumAmount(InvoiceLine invoiceLine, Invoice invoice) {
|
||||
List<BudgetDistribution> budgetDistributionList = invoiceLine.getBudgetDistributionList();
|
||||
PurchaseOrderLine purchaseOrderLine = invoiceLine.getPurchaseOrderLine();
|
||||
BigDecimal budgetDistributionSumAmount = BigDecimal.ZERO;
|
||||
LocalDate computeDate = invoice.getInvoiceDate();
|
||||
|
||||
if (purchaseOrderLine != null && purchaseOrderLine.getPurchaseOrder().getOrderDate() != null) {
|
||||
computeDate = purchaseOrderLine.getPurchaseOrder().getOrderDate();
|
||||
}
|
||||
|
||||
if (budgetDistributionList != null && !budgetDistributionList.isEmpty()) {
|
||||
|
||||
for (BudgetDistribution budgetDistribution : budgetDistributionList) {
|
||||
budgetDistributionSumAmount =
|
||||
budgetDistributionSumAmount.add(budgetDistribution.getAmount());
|
||||
Beans.get(BudgetSupplychainService.class)
|
||||
.computeBudgetDistributionSumAmount(budgetDistribution, computeDate);
|
||||
}
|
||||
}
|
||||
invoiceLine.setBudgetDistributionSumAmount(budgetDistributionSumAmount);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.service.move.MoveToolService;
|
||||
import com.axelor.apps.account.service.payment.invoice.payment.InvoicePaymentToolServiceImpl;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
|
||||
public class InvoicePaymentToolServiceSupplychainImpl extends InvoicePaymentToolServiceImpl {
|
||||
|
||||
@Inject
|
||||
public InvoicePaymentToolServiceSupplychainImpl(
|
||||
InvoiceRepository invoiceRepo, MoveToolService moveToolService) {
|
||||
super(invoiceRepo, moveToolService);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void updateAmountPaid(Invoice invoice) throws AxelorException {
|
||||
super.updateAmountPaid(invoice);
|
||||
SaleOrder saleOrder = invoice.getSaleOrder();
|
||||
if (saleOrder != null) {
|
||||
// compute sale order totals
|
||||
Beans.get(SaleOrderComputeService.class)._computeSaleOrder(saleOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.stock.service.LogisticalFormService;
|
||||
|
||||
public interface LogisticalFormSupplychainService extends LogisticalFormService {}
|
||||
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.stock.db.LogisticalForm;
|
||||
import com.axelor.apps.stock.db.LogisticalFormLine;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.stock.service.LogisticalFormServiceImpl;
|
||||
import com.axelor.inject.Beans;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class LogisticalFormSupplychainServiceImpl extends LogisticalFormServiceImpl
|
||||
implements LogisticalFormSupplychainService {
|
||||
|
||||
@Override
|
||||
protected boolean testForDetailLine(StockMoveLine stockMoveLine) {
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
return saleOrderLine == null
|
||||
|| saleOrderLine.getTypeSelect() == SaleOrderLineRepository.TYPE_NORMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LogisticalFormLine createLogisticalFormLine(
|
||||
LogisticalForm logisticalForm, StockMoveLine stockMoveLine, BigDecimal qty) {
|
||||
LogisticalFormLine logisticalFormLine =
|
||||
super.createLogisticalFormLine(logisticalForm, stockMoveLine, qty);
|
||||
|
||||
StockMove stockMove =
|
||||
logisticalFormLine.getStockMoveLine() != null
|
||||
? logisticalFormLine.getStockMoveLine().getStockMove()
|
||||
: null;
|
||||
|
||||
if (stockMove != null
|
||||
&& stockMove.getOriginId() != null
|
||||
&& stockMove.getOriginId() != 0
|
||||
&& stockMove.getOriginTypeSelect().equals(StockMoveRepository.ORIGIN_SALE_ORDER)) {
|
||||
logisticalFormLine.setSaleOrder(
|
||||
Beans.get(SaleOrderRepository.class).find(stockMove.getOriginId()));
|
||||
}
|
||||
|
||||
return logisticalFormLine;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.supplychain.db.Mrp;
|
||||
import com.axelor.apps.supplychain.db.MrpLine;
|
||||
import com.axelor.apps.supplychain.db.MrpLineOrigin;
|
||||
import com.axelor.apps.supplychain.db.MrpLineType;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
public interface MrpLineService {
|
||||
|
||||
void generateProposal(MrpLine mrpLine) throws AxelorException;
|
||||
|
||||
void generateProposal(
|
||||
MrpLine mrpLine,
|
||||
Map<Pair<Partner, LocalDate>, PurchaseOrder> purchaseOrders,
|
||||
Map<Partner, PurchaseOrder> purchaseOrdersPerSupplier,
|
||||
boolean isProposalsPerSupplier)
|
||||
throws AxelorException;
|
||||
|
||||
MrpLine createMrpLine(
|
||||
Mrp mrp,
|
||||
Product product,
|
||||
int maxLevel,
|
||||
MrpLineType mrpLineType,
|
||||
BigDecimal qty,
|
||||
LocalDate maturityDate,
|
||||
BigDecimal cumulativeQty,
|
||||
StockLocation stockLocation,
|
||||
Model model);
|
||||
|
||||
MrpLineOrigin createMrpLineOrigin(Model model);
|
||||
|
||||
MrpLineOrigin copyMrpLineOrigin(MrpLineOrigin mrpLineOrigin);
|
||||
}
|
||||
@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 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.Unit;
|
||||
import com.axelor.apps.base.db.repo.PriceListRepository;
|
||||
import com.axelor.apps.base.service.PartnerPriceListService;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
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.purchase.service.PurchaseOrderLineService;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockRules;
|
||||
import com.axelor.apps.stock.db.repo.StockRulesRepository;
|
||||
import com.axelor.apps.stock.service.StockRulesService;
|
||||
import com.axelor.apps.supplychain.db.Mrp;
|
||||
import com.axelor.apps.supplychain.db.MrpForecast;
|
||||
import com.axelor.apps.supplychain.db.MrpLine;
|
||||
import com.axelor.apps.supplychain.db.MrpLineOrigin;
|
||||
import com.axelor.apps.supplychain.db.MrpLineType;
|
||||
import com.axelor.apps.supplychain.db.repo.MrpLineTypeRepository;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.auth.AuthUtils;
|
||||
import com.axelor.auth.db.AuditableModel;
|
||||
import com.axelor.db.EntityHelper;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MrpLineServiceImpl implements MrpLineService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected AppBaseService appBaseService;
|
||||
protected PurchaseOrderServiceSupplychainImpl purchaseOrderServiceSupplychainImpl;
|
||||
protected PurchaseOrderLineService purchaseOrderLineService;
|
||||
protected PurchaseOrderRepository purchaseOrderRepo;
|
||||
protected StockRulesService stockRulesService;
|
||||
|
||||
@Inject
|
||||
public MrpLineServiceImpl(
|
||||
AppBaseService appBaseService,
|
||||
PurchaseOrderServiceSupplychainImpl purchaseOrderServiceSupplychainImpl,
|
||||
PurchaseOrderLineService purchaseOrderLineService,
|
||||
PurchaseOrderRepository purchaseOrderRepo,
|
||||
StockRulesService stockRulesService) {
|
||||
|
||||
this.appBaseService = appBaseService;
|
||||
this.purchaseOrderServiceSupplychainImpl = purchaseOrderServiceSupplychainImpl;
|
||||
this.purchaseOrderLineService = purchaseOrderLineService;
|
||||
this.purchaseOrderRepo = purchaseOrderRepo;
|
||||
this.stockRulesService = stockRulesService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateProposal(MrpLine mrpLine) throws AxelorException {
|
||||
generateProposal(mrpLine, null, null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateProposal(
|
||||
MrpLine mrpLine,
|
||||
Map<Pair<Partner, LocalDate>, PurchaseOrder> purchaseOrders,
|
||||
Map<Partner, PurchaseOrder> purchaseOrdersPerSupplier,
|
||||
boolean isProposalsPerSupplier)
|
||||
throws AxelorException {
|
||||
|
||||
if (mrpLine.getMrpLineType().getElementSelect()
|
||||
== MrpLineTypeRepository.ELEMENT_PURCHASE_PROPOSAL) {
|
||||
|
||||
this.generatePurchaseProposal(
|
||||
mrpLine, purchaseOrders, purchaseOrdersPerSupplier, isProposalsPerSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
protected void generatePurchaseProposal(
|
||||
MrpLine mrpLine,
|
||||
Map<Pair<Partner, LocalDate>, PurchaseOrder> purchaseOrders,
|
||||
Map<Partner, PurchaseOrder> purchaseOrdersPerSupplier,
|
||||
boolean isProposalsPerSupplier)
|
||||
throws AxelorException {
|
||||
|
||||
Product product = mrpLine.getProduct();
|
||||
StockLocation stockLocation = mrpLine.getStockLocation();
|
||||
LocalDate maturityDate = mrpLine.getMaturityDate();
|
||||
|
||||
Partner supplierPartner = product.getDefaultSupplierPartner();
|
||||
|
||||
if (supplierPartner == null) {
|
||||
throw new AxelorException(
|
||||
mrpLine,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.MRP_LINE_1),
|
||||
product.getFullName());
|
||||
}
|
||||
|
||||
Company company = stockLocation.getCompany();
|
||||
|
||||
Pair<Partner, LocalDate> key = null;
|
||||
PurchaseOrder purchaseOrder = null;
|
||||
|
||||
if (isProposalsPerSupplier) {
|
||||
if (purchaseOrdersPerSupplier != null) {
|
||||
purchaseOrder = purchaseOrdersPerSupplier.get(supplierPartner);
|
||||
}
|
||||
} else {
|
||||
if (purchaseOrders != null) {
|
||||
key = Pair.of(supplierPartner, maturityDate);
|
||||
purchaseOrder = purchaseOrders.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (purchaseOrder == null) {
|
||||
purchaseOrder =
|
||||
purchaseOrderRepo.save(
|
||||
purchaseOrderServiceSupplychainImpl.createPurchaseOrder(
|
||||
AuthUtils.getUser(),
|
||||
company,
|
||||
null,
|
||||
supplierPartner.getCurrency(),
|
||||
maturityDate,
|
||||
"MRP-" + appBaseService.getTodayDate().toString(), // TODO sequence on mrp
|
||||
null,
|
||||
"",
|
||||
stockLocation,
|
||||
appBaseService.getTodayDate(),
|
||||
Beans.get(PartnerPriceListService.class)
|
||||
.getDefaultPriceList(supplierPartner, PriceListRepository.TYPE_PURCHASE),
|
||||
supplierPartner,
|
||||
null));
|
||||
if (isProposalsPerSupplier) {
|
||||
if (purchaseOrdersPerSupplier != null) {
|
||||
purchaseOrdersPerSupplier.put(supplierPartner, purchaseOrder);
|
||||
}
|
||||
} else {
|
||||
if (purchaseOrders != null) {
|
||||
purchaseOrders.put(key, purchaseOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
Unit unit = product.getPurchasesUnit();
|
||||
BigDecimal qty = mrpLine.getQty();
|
||||
if (unit == null) {
|
||||
unit = product.getUnit();
|
||||
} else {
|
||||
qty =
|
||||
Beans.get(UnitConversionService.class)
|
||||
.convert(product.getUnit(), unit, qty, qty.scale(), product);
|
||||
}
|
||||
PurchaseOrderLine poLine =
|
||||
purchaseOrderLineService.createPurchaseOrderLine(
|
||||
purchaseOrder, product, null, null, qty, unit);
|
||||
poLine.setDesiredDelivDate(maturityDate);
|
||||
purchaseOrder.addPurchaseOrderLineListItem(poLine);
|
||||
|
||||
purchaseOrderServiceSupplychainImpl.computePurchaseOrder(purchaseOrder);
|
||||
|
||||
linkToOrder(mrpLine, purchaseOrder);
|
||||
}
|
||||
|
||||
protected void linkToOrder(MrpLine mrpLine, AuditableModel order) {
|
||||
mrpLine.setProposalSelect(order.getClass().getName());
|
||||
mrpLine.setProposalSelectId(order.getId());
|
||||
mrpLine.setProposalGenerated(true);
|
||||
}
|
||||
|
||||
public MrpLine createMrpLine(
|
||||
Mrp mrp,
|
||||
Product product,
|
||||
int maxLevel,
|
||||
MrpLineType mrpLineType,
|
||||
BigDecimal qty,
|
||||
LocalDate maturityDate,
|
||||
BigDecimal cumulativeQty,
|
||||
StockLocation stockLocation,
|
||||
Model model) {
|
||||
|
||||
MrpLine mrpLine = new MrpLine();
|
||||
|
||||
mrpLine.setMrp(mrp);
|
||||
mrpLine.setProduct(product);
|
||||
mrpLine.setMaxLevel(maxLevel);
|
||||
mrpLine.setMrpLineType(mrpLineType);
|
||||
if (mrpLineType.getTypeSelect() == MrpLineTypeRepository.TYPE_OUT) {
|
||||
mrpLine.setQty(qty.negate());
|
||||
} else {
|
||||
mrpLine.setQty(qty);
|
||||
}
|
||||
mrpLine.setMaturityDate(maturityDate);
|
||||
mrpLine.setCumulativeQty(cumulativeQty);
|
||||
mrpLine.setStockLocation(stockLocation);
|
||||
|
||||
mrpLine.setMinQty(this.getMinQty(product, stockLocation));
|
||||
|
||||
this.updatePartner(mrpLine, model);
|
||||
|
||||
this.createMrpLineOrigins(mrpLine, model);
|
||||
|
||||
log.debug(
|
||||
"Create mrp line for the product {}, level {}, mrpLineType {}, qty {}, maturity date {}, cumulative qty {}, stock location {}, related to {}",
|
||||
product.getCode(),
|
||||
maxLevel,
|
||||
mrpLineType.getCode(),
|
||||
qty,
|
||||
maturityDate,
|
||||
cumulativeQty,
|
||||
stockLocation.getName(),
|
||||
mrpLine.getRelatedToSelectName());
|
||||
|
||||
return mrpLine;
|
||||
}
|
||||
|
||||
protected BigDecimal getMinQty(Product product, StockLocation stockLocation) {
|
||||
|
||||
StockRules stockRules =
|
||||
stockRulesService.getStockRules(
|
||||
product,
|
||||
stockLocation,
|
||||
StockRulesRepository.TYPE_FUTURE,
|
||||
StockRulesRepository.USE_CASE_USED_FOR_MRP);
|
||||
|
||||
if (stockRules != null) {
|
||||
return stockRules.getMinQty();
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
protected void createMrpLineOrigins(MrpLine mrpLine, Model model) {
|
||||
|
||||
if (model != null) {
|
||||
|
||||
mrpLine.addMrpLineOriginListItem(this.createMrpLineOrigin(model));
|
||||
mrpLine.setRelatedToSelectName(this.computeRelatedName(model));
|
||||
}
|
||||
}
|
||||
|
||||
public MrpLineOrigin createMrpLineOrigin(Model model) {
|
||||
|
||||
Class<?> klass = EntityHelper.getEntityClass(model);
|
||||
|
||||
MrpLineOrigin mrpLineOrigin = new MrpLineOrigin();
|
||||
mrpLineOrigin.setRelatedToSelect(klass.getCanonicalName());
|
||||
mrpLineOrigin.setRelatedToSelectId(model.getId());
|
||||
|
||||
return mrpLineOrigin;
|
||||
}
|
||||
|
||||
public MrpLineOrigin copyMrpLineOrigin(MrpLineOrigin mrpLineOrigin) {
|
||||
|
||||
MrpLineOrigin copyMrpLineOrigin = new MrpLineOrigin();
|
||||
copyMrpLineOrigin.setRelatedToSelect(mrpLineOrigin.getRelatedToSelect());
|
||||
copyMrpLineOrigin.setRelatedToSelectId(mrpLineOrigin.getRelatedToSelectId());
|
||||
|
||||
return copyMrpLineOrigin;
|
||||
}
|
||||
|
||||
protected String computeRelatedName(Model model) {
|
||||
|
||||
if (model instanceof SaleOrderLine) {
|
||||
|
||||
return ((SaleOrderLine) model).getSaleOrder().getSaleOrderSeq();
|
||||
|
||||
} else if (model instanceof PurchaseOrderLine) {
|
||||
|
||||
return ((PurchaseOrderLine) model).getPurchaseOrder().getPurchaseOrderSeq();
|
||||
|
||||
} else if (model instanceof MrpForecast) {
|
||||
|
||||
MrpForecast mrpForecast = (MrpForecast) model;
|
||||
return mrpForecast.getId() + "-" + mrpForecast.getForecastDate();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void updatePartner(MrpLine mrpLine, Model model) {
|
||||
|
||||
if (model != null) {
|
||||
mrpLine.setPartner(this.getPartner(model));
|
||||
}
|
||||
}
|
||||
|
||||
protected Partner getPartner(Model model) {
|
||||
|
||||
if (model instanceof SaleOrderLine) {
|
||||
|
||||
return ((SaleOrderLine) model).getSaleOrder().getClientPartner();
|
||||
|
||||
} else if (model instanceof PurchaseOrderLine) {
|
||||
|
||||
return ((PurchaseOrderLine) model).getPurchaseOrder().getSupplierPartner();
|
||||
|
||||
} else if (model instanceof MrpForecast) {
|
||||
|
||||
return ((MrpForecast) model).getPartner();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.supplychain.db.Mrp;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public interface MrpService {
|
||||
|
||||
public void runCalculation(Mrp mrp) throws AxelorException;
|
||||
|
||||
public void generateProposals(Mrp mrp, boolean isProposalsPerSupplier) throws AxelorException;
|
||||
|
||||
public void reset(Mrp mrp);
|
||||
|
||||
/**
|
||||
* Search for the end date of the mrp. If the end date field in mrp is blank, search in the lines
|
||||
* the last date.
|
||||
*
|
||||
* @param mrp
|
||||
* @return the mrp end date
|
||||
*/
|
||||
public LocalDate findMrpEndDate(Mrp mrp);
|
||||
|
||||
public Mrp createProjectedStock(
|
||||
Mrp mrp, Product product, Company company, StockLocation stockLocation)
|
||||
throws AxelorException;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.exception.AxelorException;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ProductStockLocationService {
|
||||
|
||||
Map<String, Object> computeIndicators(Long productId, Long companyId, Long stockLocationId)
|
||||
throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Unit;
|
||||
import com.axelor.apps.base.db.repo.CompanyRepository;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderLineRepository;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.apps.stock.service.StockLocationLineService;
|
||||
import com.axelor.apps.stock.service.StockLocationService;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ProductStockLocationServiceImpl implements ProductStockLocationService {
|
||||
|
||||
protected UnitConversionService unitConversionService;
|
||||
protected AppSupplychainService appSupplychainService;
|
||||
protected ProductRepository productRepository;
|
||||
protected CompanyRepository companyRepository;
|
||||
protected StockLocationRepository stockLocationRepository;
|
||||
protected StockLocationService stockLocationService;
|
||||
protected StockLocationLineService stockLocationLineService;
|
||||
protected StockLocationLineRepository stockLocationLineRepository;
|
||||
protected StockLocationServiceSupplychain stockLocationServiceSupplychain;
|
||||
|
||||
@Inject
|
||||
public ProductStockLocationServiceImpl(
|
||||
UnitConversionService unitConversionService,
|
||||
AppSupplychainService appSupplychainService,
|
||||
ProductRepository productRepository,
|
||||
CompanyRepository companyRepository,
|
||||
StockLocationRepository stockLocationRepository,
|
||||
StockLocationService stockLocationService,
|
||||
StockLocationServiceSupplychain stockLocationServiceSupplychain,
|
||||
StockLocationLineService stockLocationLineService,
|
||||
StockLocationLineRepository stockLocationLineRepository) {
|
||||
super();
|
||||
this.unitConversionService = unitConversionService;
|
||||
this.appSupplychainService = appSupplychainService;
|
||||
this.productRepository = productRepository;
|
||||
this.companyRepository = companyRepository;
|
||||
this.stockLocationRepository = stockLocationRepository;
|
||||
this.stockLocationService = stockLocationService;
|
||||
this.stockLocationServiceSupplychain = stockLocationServiceSupplychain;
|
||||
this.stockLocationLineService = stockLocationLineService;
|
||||
this.stockLocationLineRepository = stockLocationLineRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> computeIndicators(Long productId, Long companyId, Long stockLocationId)
|
||||
throws AxelorException {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
Product product = productRepository.find(productId);
|
||||
Company company = companyRepository.find(companyId);
|
||||
StockLocation stockLocation = stockLocationRepository.find(stockLocationId);
|
||||
if (stockLocationId != 0L && companyId != 0L) {
|
||||
List<StockLocation> stockLocationList =
|
||||
stockLocationService.getAllLocationAndSubLocation(stockLocation, false);
|
||||
if (!stockLocationList.isEmpty()) {
|
||||
BigDecimal realQty = BigDecimal.ZERO;
|
||||
BigDecimal futureQty = BigDecimal.ZERO;
|
||||
BigDecimal reservedQty = BigDecimal.ZERO;
|
||||
BigDecimal requestedReservedQty = BigDecimal.ZERO;
|
||||
BigDecimal saleOrderQty = BigDecimal.ZERO;
|
||||
BigDecimal purchaseOrderQty = BigDecimal.ZERO;
|
||||
BigDecimal availableQty = BigDecimal.ZERO;
|
||||
|
||||
saleOrderQty = this.getSaleOrderQty(product, company, stockLocation);
|
||||
purchaseOrderQty = this.getPurchaseOrderQty(product, company, stockLocation);
|
||||
availableQty = this.getAvailableQty(product, company, stockLocation);
|
||||
requestedReservedQty = this.getRequestedReservedQty(product, company, stockLocation);
|
||||
|
||||
for (StockLocation sl : stockLocationList) {
|
||||
realQty = realQty.add(stockLocationService.getRealQty(productId, sl.getId(), companyId));
|
||||
futureQty =
|
||||
futureQty.add(stockLocationService.getFutureQty(productId, sl.getId(), companyId));
|
||||
reservedQty =
|
||||
reservedQty.add(
|
||||
stockLocationServiceSupplychain.getReservedQty(productId, sl.getId(), companyId));
|
||||
}
|
||||
|
||||
map.put("$realQty", realQty.setScale(2));
|
||||
map.put("$futureQty", futureQty.setScale(2));
|
||||
map.put("$reservedQty", reservedQty.setScale(2));
|
||||
map.put("$requestedReservedQty", requestedReservedQty.setScale(2));
|
||||
map.put("$saleOrderQty", saleOrderQty.setScale(2));
|
||||
map.put("$purchaseOrderQty", purchaseOrderQty.setScale(2));
|
||||
map.put("$availableQty", availableQty.subtract(reservedQty).setScale(2));
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
BigDecimal reservedQty =
|
||||
stockLocationServiceSupplychain
|
||||
.getReservedQty(productId, stockLocationId, companyId)
|
||||
.setScale(2);
|
||||
map.put(
|
||||
"$realQty",
|
||||
stockLocationService.getRealQty(productId, stockLocationId, companyId).setScale(2));
|
||||
map.put(
|
||||
"$futureQty",
|
||||
stockLocationService.getFutureQty(productId, stockLocationId, companyId).setScale(2));
|
||||
map.put("$reservedQty", reservedQty);
|
||||
map.put(
|
||||
"$requestedReservedQty", this.getRequestedReservedQty(product, company, null).setScale(2));
|
||||
map.put("$saleOrderQty", this.getSaleOrderQty(product, company, null).setScale(2));
|
||||
map.put("$purchaseOrderQty", this.getPurchaseOrderQty(product, company, null).setScale(2));
|
||||
map.put(
|
||||
"$availableQty",
|
||||
this.getAvailableQty(product, company, null).subtract(reservedQty).setScale(2));
|
||||
return map;
|
||||
}
|
||||
|
||||
protected BigDecimal getRequestedReservedQty(
|
||||
Product product, Company company, StockLocation stockLocation) throws AxelorException {
|
||||
if (product == null || product.getUnit() == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
Long companyId = 0L;
|
||||
Long stockLocationId = 0L;
|
||||
if (company != null) {
|
||||
companyId = company.getId();
|
||||
}
|
||||
if (stockLocation != null) {
|
||||
stockLocationId = stockLocation.getId();
|
||||
}
|
||||
String query =
|
||||
stockLocationLineService.getStockLocationLineListForAProduct(
|
||||
product.getId(), companyId, stockLocationId);
|
||||
|
||||
List<StockLocationLine> stockLocationLineList =
|
||||
stockLocationLineRepository.all().filter(query).fetch();
|
||||
|
||||
// Compute
|
||||
BigDecimal sumRequestedReservedQty = BigDecimal.ZERO;
|
||||
if (!stockLocationLineList.isEmpty()) {
|
||||
Unit unitConversion = product.getUnit();
|
||||
|
||||
for (StockLocationLine stockLocationLine : stockLocationLineList) {
|
||||
BigDecimal requestedReservedQty = stockLocationLine.getRequestedReservedQty();
|
||||
requestedReservedQty =
|
||||
unitConversionService.convert(
|
||||
stockLocationLine.getUnit(),
|
||||
unitConversion,
|
||||
requestedReservedQty,
|
||||
requestedReservedQty.scale(),
|
||||
product);
|
||||
sumRequestedReservedQty = sumRequestedReservedQty.add(requestedReservedQty);
|
||||
}
|
||||
}
|
||||
|
||||
return sumRequestedReservedQty;
|
||||
}
|
||||
|
||||
protected BigDecimal getSaleOrderQty(
|
||||
Product product, Company company, StockLocation stockLocation) throws AxelorException {
|
||||
if (product == null || product.getUnit() == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
Long companyId = 0L;
|
||||
Long stockLocationId = 0L;
|
||||
if (company != null) {
|
||||
companyId = company.getId();
|
||||
}
|
||||
if (stockLocation != null) {
|
||||
stockLocationId = stockLocation.getId();
|
||||
}
|
||||
String query =
|
||||
Beans.get(SaleOrderLineServiceSupplyChain.class)
|
||||
.getSaleOrderLineListForAProduct(product.getId(), companyId, stockLocationId);
|
||||
List<SaleOrderLine> saleOrderLineList =
|
||||
Beans.get(SaleOrderLineRepository.class).all().filter(query).fetch();
|
||||
|
||||
// Compute
|
||||
BigDecimal sumSaleOrderQty = BigDecimal.ZERO;
|
||||
if (!saleOrderLineList.isEmpty()) {
|
||||
Unit unitConversion = product.getUnit();
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
|
||||
BigDecimal productSaleOrderQty = saleOrderLine.getQty();
|
||||
if (saleOrderLine.getDeliveryState()
|
||||
== SaleOrderLineRepository.DELIVERY_STATE_PARTIALLY_DELIVERED) {
|
||||
productSaleOrderQty = productSaleOrderQty.subtract(saleOrderLine.getDeliveredQty());
|
||||
}
|
||||
productSaleOrderQty =
|
||||
unitConversionService.convert(
|
||||
saleOrderLine.getUnit(),
|
||||
unitConversion,
|
||||
productSaleOrderQty,
|
||||
productSaleOrderQty.scale(),
|
||||
product);
|
||||
sumSaleOrderQty = sumSaleOrderQty.add(productSaleOrderQty);
|
||||
}
|
||||
}
|
||||
|
||||
return sumSaleOrderQty;
|
||||
}
|
||||
|
||||
public BigDecimal getPurchaseOrderQty(
|
||||
Product product, Company company, StockLocation stockLocation) throws AxelorException {
|
||||
if (product == null || product.getUnit() == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
Long companyId = 0L;
|
||||
Long stockLocationId = 0L;
|
||||
if (company != null) {
|
||||
companyId = company.getId();
|
||||
}
|
||||
if (stockLocation != null) {
|
||||
stockLocationId = stockLocation.getId();
|
||||
}
|
||||
String query =
|
||||
Beans.get(PurchaseOrderStockService.class)
|
||||
.getPurchaseOrderLineListForAProduct(product.getId(), companyId, stockLocationId);
|
||||
|
||||
List<PurchaseOrderLine> purchaseOrderLineList =
|
||||
Beans.get(PurchaseOrderLineRepository.class).all().filter(query).fetch();
|
||||
|
||||
// Compute
|
||||
BigDecimal sumPurchaseOrderQty = BigDecimal.ZERO;
|
||||
if (!purchaseOrderLineList.isEmpty()) {
|
||||
Unit unitConversion = product.getUnit();
|
||||
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLineList) {
|
||||
BigDecimal productPurchaseOrderQty = purchaseOrderLine.getQty();
|
||||
if (purchaseOrderLine.getReceiptState()
|
||||
== PurchaseOrderLineRepository.RECEIPT_STATE_PARTIALLY_RECEIVED) {
|
||||
productPurchaseOrderQty =
|
||||
productPurchaseOrderQty.subtract(purchaseOrderLine.getReceivedQty());
|
||||
}
|
||||
productPurchaseOrderQty =
|
||||
unitConversionService.convert(
|
||||
purchaseOrderLine.getUnit(),
|
||||
unitConversion,
|
||||
productPurchaseOrderQty,
|
||||
productPurchaseOrderQty.scale(),
|
||||
product);
|
||||
sumPurchaseOrderQty = sumPurchaseOrderQty.add(productPurchaseOrderQty);
|
||||
}
|
||||
}
|
||||
return sumPurchaseOrderQty;
|
||||
}
|
||||
|
||||
protected BigDecimal getAvailableQty(
|
||||
Product product, Company company, StockLocation stockLocation) throws AxelorException {
|
||||
if (product == null || product.getUnit() == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
Long companyId = 0L;
|
||||
Long stockLocationId = 0L;
|
||||
if (company != null) {
|
||||
companyId = company.getId();
|
||||
}
|
||||
if (stockLocation != null) {
|
||||
stockLocationId = stockLocation.getId();
|
||||
}
|
||||
String query =
|
||||
stockLocationLineService.getAvailableStockForAProduct(
|
||||
product.getId(), companyId, stockLocationId);
|
||||
List<StockLocationLine> stockLocationLineList =
|
||||
stockLocationLineRepository.all().filter(query).fetch();
|
||||
|
||||
// Compute
|
||||
BigDecimal sumAvailableQty = BigDecimal.ZERO;
|
||||
if (!stockLocationLineList.isEmpty()) {
|
||||
|
||||
Unit unitConversion = product.getUnit();
|
||||
for (StockLocationLine stockLocationLine : stockLocationLineList) {
|
||||
BigDecimal productAvailableQty = stockLocationLine.getCurrentQty();
|
||||
unitConversionService.convert(
|
||||
stockLocationLine.getUnit(),
|
||||
unitConversion,
|
||||
productAvailableQty,
|
||||
productAvailableQty.scale(),
|
||||
product);
|
||||
sumAvailableQty = sumAvailableQty.add(productAvailableQty);
|
||||
}
|
||||
}
|
||||
return sumAvailableQty;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.supplychain.db.MrpLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.rpc.Context;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ProjectedStockService {
|
||||
|
||||
List<MrpLine> createProjectedStock(Long productId, Long companyId, Long stockLocationId)
|
||||
throws AxelorException;
|
||||
|
||||
Map<String, Long> getProductIdCompanyIdStockLocationIdFromContext(Context context);
|
||||
|
||||
void removeMrpAndMrpLine(List<MrpLine> mrpLineList);
|
||||
}
|
||||
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.repo.CompanyRepository;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.apps.supplychain.db.Mrp;
|
||||
import com.axelor.apps.supplychain.db.MrpLine;
|
||||
import com.axelor.apps.supplychain.db.repo.MrpLineRepository;
|
||||
import com.axelor.apps.supplychain.db.repo.MrpRepository;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.rpc.Context;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ProjectedStockServiceImpl implements ProjectedStockService {
|
||||
|
||||
@Inject StockLocationRepository stockLocationRepository;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
@Override
|
||||
public List<MrpLine> createProjectedStock(Long productId, Long companyId, Long stockLocationId)
|
||||
throws AxelorException {
|
||||
Product product = Beans.get(ProductRepository.class).find(productId);
|
||||
Company company = Beans.get(CompanyRepository.class).find(companyId);
|
||||
StockLocation stockLocation = stockLocationRepository.find(stockLocationId);
|
||||
Mrp mrp = new Mrp();
|
||||
mrp.setStockLocation(findStockLocation(company, stockLocation));
|
||||
// If a company has no stockLocation
|
||||
if (mrp.getStockLocation() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
mrp.addProductSetItem(product);
|
||||
mrp = Beans.get(MrpRepository.class).save(mrp);
|
||||
mrp = Beans.get(MrpService.class).createProjectedStock(mrp, product, company, stockLocation);
|
||||
|
||||
List<MrpLine> mrpLineList =
|
||||
Beans.get(MrpLineRepository.class)
|
||||
.all()
|
||||
.filter("self.mrp = ?1 AND self.product = ?2 AND self.qty != 0", mrp, product)
|
||||
.order("maturityDate")
|
||||
.order("mrpLineType.typeSelect")
|
||||
.order("mrpLineType.sequence")
|
||||
.order("id")
|
||||
.fetch();
|
||||
|
||||
if (mrpLineList.isEmpty()) {
|
||||
List<MrpLine> mrpLineListToDelete =
|
||||
Beans.get(MrpLineRepository.class).all().filter("self.mrp = ?1", mrp).fetch();
|
||||
removeMrpAndMrpLine(mrpLineListToDelete);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
for (MrpLine mrpLine : mrpLineList) {
|
||||
mrpLine.setCompany(mrpLine.getStockLocation().getCompany());
|
||||
mrpLine.setUnit(mrpLine.getProduct().getUnit());
|
||||
}
|
||||
return mrpLineList;
|
||||
}
|
||||
|
||||
protected StockLocation findStockLocation(Company company, StockLocation stockLocation) {
|
||||
if (stockLocation != null) {
|
||||
return stockLocation;
|
||||
} else if (company != null) {
|
||||
return stockLocationRepository
|
||||
.all()
|
||||
.filter("self.company.id = ?1", company.getId())
|
||||
.fetchOne();
|
||||
}
|
||||
return stockLocationRepository.all().fetchOne();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, Long> getProductIdCompanyIdStockLocationIdFromContext(Context context) {
|
||||
Long productId = 0L;
|
||||
Long companyId = 0L;
|
||||
Long stockLocationId = 0L;
|
||||
Map<String, Long> mapId = new HashMap<>();
|
||||
|
||||
LinkedHashMap<String, Object> productHashMap =
|
||||
(LinkedHashMap<String, Object>) context.get("product");
|
||||
if (productHashMap != null) {
|
||||
productId = Long.valueOf(productHashMap.get("id").toString());
|
||||
} else {
|
||||
productHashMap = (LinkedHashMap<String, Object>) context.get("$product");
|
||||
if (productHashMap != null) {
|
||||
productId = Long.valueOf(productHashMap.get("id").toString());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
LinkedHashMap<String, Object> companyHashMap =
|
||||
(LinkedHashMap<String, Object>) context.get("company");
|
||||
if (companyHashMap != null) {
|
||||
companyId = Long.valueOf(companyHashMap.get("id").toString());
|
||||
} else {
|
||||
companyHashMap = (LinkedHashMap<String, Object>) context.get("$company");
|
||||
if (companyHashMap != null) {
|
||||
companyId = Long.valueOf(companyHashMap.get("id").toString());
|
||||
}
|
||||
}
|
||||
LinkedHashMap<String, Object> stockLocationHashMap =
|
||||
(LinkedHashMap<String, Object>) context.get("stockLocation");
|
||||
if (stockLocationHashMap != null) {
|
||||
stockLocationId = Long.valueOf(stockLocationHashMap.get("id").toString());
|
||||
} else {
|
||||
stockLocationHashMap = (LinkedHashMap<String, Object>) context.get("$stockLocation");
|
||||
if (stockLocationHashMap != null) {
|
||||
stockLocationId = Long.valueOf(stockLocationHashMap.get("id").toString());
|
||||
}
|
||||
}
|
||||
|
||||
mapId.put("productId", productId);
|
||||
mapId.put("companyId", companyId);
|
||||
mapId.put("stockLocationId", stockLocationId);
|
||||
return mapId;
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
@Override
|
||||
public void removeMrpAndMrpLine(List<MrpLine> mrpLineList) {
|
||||
if (mrpLineList != null && !mrpLineList.isEmpty()) {
|
||||
Long mrpId = mrpLineList.get(0).getMrp().getId();
|
||||
Beans.get(MrpLineRepository.class).all().filter("self.mrp.id = ?1", mrpId).remove();
|
||||
Beans.get(MrpRepository.class).all().filter("self.id = ?1", mrpId).remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.InvoiceLine;
|
||||
import com.axelor.apps.account.service.invoice.generator.InvoiceGenerator;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public interface PurchaseOrderInvoiceService {
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice generateInvoice(PurchaseOrder purchaseOrder) throws AxelorException;
|
||||
|
||||
public Invoice createInvoice(PurchaseOrder purchaseOrder) throws AxelorException;
|
||||
|
||||
public InvoiceGenerator createInvoiceGenerator(PurchaseOrder purchaseOrder)
|
||||
throws AxelorException;
|
||||
|
||||
public InvoiceGenerator createInvoiceGenerator(PurchaseOrder purchaseOrder, boolean isRefund)
|
||||
throws AxelorException;
|
||||
|
||||
public List<InvoiceLine> createInvoiceLines(
|
||||
Invoice invoice, List<PurchaseOrderLine> purchaseOrderLineList) throws AxelorException;
|
||||
|
||||
public List<InvoiceLine> createInvoiceLine(Invoice invoice, PurchaseOrderLine purchaseOrderLine)
|
||||
throws AxelorException;
|
||||
|
||||
public BigDecimal getInvoicedAmount(PurchaseOrder purchaseOrder);
|
||||
|
||||
public BigDecimal getInvoicedAmount(
|
||||
PurchaseOrder purchaseOrder, Long currentInvoiceId, boolean excludeCurrentInvoice);
|
||||
}
|
||||
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 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.service.AddressService;
|
||||
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.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
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;
|
||||
|
||||
public class PurchaseOrderInvoiceServiceImpl implements PurchaseOrderInvoiceService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Inject private InvoiceService invoiceService;
|
||||
|
||||
@Inject private InvoiceRepository invoiceRepo;
|
||||
|
||||
@Inject private PurchaseOrderRepository purchaseOrderRepo;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice generateInvoice(PurchaseOrder purchaseOrder) throws AxelorException {
|
||||
|
||||
Invoice invoice = this.createInvoice(purchaseOrder);
|
||||
invoice = invoiceRepo.save(invoice);
|
||||
invoiceService.setDraftSequence(invoice);
|
||||
invoice.setAddressStr(Beans.get(AddressService.class).computeAddressStr(invoice.getAddress()));
|
||||
|
||||
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();
|
||||
|
||||
List<InvoiceLine> invoiceLineList =
|
||||
this.createInvoiceLines(invoice, purchaseOrder.getPurchaseOrderLineList());
|
||||
|
||||
invoiceGenerator.populate(invoice, invoiceLineList);
|
||||
|
||||
invoice.setPurchaseOrder(purchaseOrder);
|
||||
return invoice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvoiceGenerator createInvoiceGenerator(PurchaseOrder purchaseOrder)
|
||||
throws AxelorException {
|
||||
return createInvoiceGenerator(purchaseOrder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvoiceGenerator createInvoiceGenerator(PurchaseOrder purchaseOrder, boolean isRefund)
|
||||
throws AxelorException {
|
||||
|
||||
if (purchaseOrder.getCurrency() == null) {
|
||||
throw new AxelorException(
|
||||
purchaseOrder,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.PO_INVOICE_1),
|
||||
purchaseOrder.getPurchaseOrderSeq());
|
||||
}
|
||||
|
||||
return new InvoiceGeneratorSupplyChain(purchaseOrder, isRefund) {
|
||||
@Override
|
||||
public Invoice generate() throws AxelorException {
|
||||
return super.createInvoiceHeader();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InvoiceLine> createInvoiceLines(
|
||||
Invoice invoice, List<PurchaseOrderLine> purchaseOrderLineList) throws AxelorException {
|
||||
|
||||
List<InvoiceLine> invoiceLineList = new ArrayList<InvoiceLine>();
|
||||
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLineList) {
|
||||
|
||||
processPurchaseOrderLine(invoice, invoiceLineList, purchaseOrderLine);
|
||||
}
|
||||
return invoiceLineList;
|
||||
}
|
||||
|
||||
protected void processPurchaseOrderLine(
|
||||
Invoice invoice, List<InvoiceLine> invoiceLineList, PurchaseOrderLine purchaseOrderLine)
|
||||
throws AxelorException {
|
||||
invoiceLineList.addAll(this.createInvoiceLine(invoice, purchaseOrderLine));
|
||||
purchaseOrderLine.setInvoiced(true);
|
||||
}
|
||||
|
||||
@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,
|
||||
false,
|
||||
0) {
|
||||
@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
|
||||
*
|
||||
* <p>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
|
||||
*
|
||||
* <p>To know if the invoice should be or not integrated in calculation
|
||||
*
|
||||
* @param purchaseOrder
|
||||
* @param currentInvoiceId
|
||||
* @param excludeCurrentInvoice
|
||||
* @return
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package com.axelor.apps.supplychain.service;
|
||||
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLineFile;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderLineFileRepository;
|
||||
import com.axelor.apps.purchase.service.app.AppPurchaseService;
|
||||
import com.axelor.auth.AuthUtils;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
|
||||
public class PurchaseOrderLineFileServiceService {
|
||||
|
||||
@Inject
|
||||
protected AppPurchaseService appPurchaseService;
|
||||
|
||||
@Transactional(rollbackOn = { Exception.class })
|
||||
public void refuseCoa(PurchaseOrderLineFile purchaseOrderLineFile, String raison, String refusalOrigin)
|
||||
throws AxelorException {
|
||||
|
||||
System.out.println("purchaseOrderLineFile************************************");
|
||||
System.out.println(refusalOrigin);
|
||||
System.out.println(raison);
|
||||
System.out.println("purchaseOrderLineFile************************************");
|
||||
|
||||
switch (refusalOrigin) {
|
||||
case PurchaseOrderLineFileRepository.DT_STATUS_SELECT:
|
||||
purchaseOrderLineFile.setDtStatusSelect(4);
|
||||
purchaseOrderLineFile.setDt_refusal_date(appPurchaseService.getTodayDateTime().toLocalDateTime());
|
||||
purchaseOrderLineFile.setDt_validator_refusal_user(AuthUtils.getUser());
|
||||
purchaseOrderLineFile.setDt_refusal_reason(raison);
|
||||
break;
|
||||
case PurchaseOrderLineFileRepository.RD_STATUS_SELECT:
|
||||
purchaseOrderLineFile.setRdStatusSelect(4);
|
||||
purchaseOrderLineFile.setRd_refusal_date(appPurchaseService.getTodayDateTime().toLocalDateTime());
|
||||
purchaseOrderLineFile.setRd_refusal_user(AuthUtils.getUser());
|
||||
purchaseOrderLineFile.setRd_refusal_reason(raison);
|
||||
break;
|
||||
|
||||
case PurchaseOrderLineFileRepository.QA_STATUS_SELECT:
|
||||
purchaseOrderLineFile.setQaStatusSelect(4);
|
||||
purchaseOrderLineFile.setQa_refusal_date(appPurchaseService.getTodayDateTime().toLocalDateTime());
|
||||
purchaseOrderLineFile.setQa_validator_refusal_user(AuthUtils.getUser());
|
||||
purchaseOrderLineFile.setQa_refusal_reason(raison);
|
||||
break;
|
||||
|
||||
case PurchaseOrderLineFileRepository.QC_STATUS_SELECT:
|
||||
purchaseOrderLineFile.setQcStatusSelect(4);
|
||||
purchaseOrderLineFile.setQc_refusal_date(appPurchaseService.getTodayDateTime().toLocalDateTime());
|
||||
purchaseOrderLineFile.setQc_validator_refusal_user(AuthUtils.getUser());
|
||||
purchaseOrderLineFile.setQc_refusal_reason(raison);
|
||||
break;
|
||||
|
||||
case PurchaseOrderLineFileRepository.PRODUCTION_STATUS_SELECT:
|
||||
purchaseOrderLineFile.setProductionStatusSelect(4);
|
||||
purchaseOrderLineFile.setProduction_refusal_date(appPurchaseService.getTodayDateTime().toLocalDateTime());
|
||||
purchaseOrderLineFile.setProduction_validator_refusal_user(AuthUtils.getUser());
|
||||
purchaseOrderLineFile.setProduction_refusal_reason(raison);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Beans.get(PurchaseOrderLineFileRepository.class).save(purchaseOrderLineFile);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AnalyticAccount;
|
||||
import com.axelor.apps.account.db.AnalyticDistributionTemplate;
|
||||
import com.axelor.apps.account.db.AnalyticMoveLine;
|
||||
import com.axelor.apps.account.db.Budget;
|
||||
import com.axelor.apps.account.db.BudgetDistribution;
|
||||
import com.axelor.apps.account.db.repo.AnalyticMoveLineRepository;
|
||||
import com.axelor.apps.account.db.repo.BudgetDistributionRepository;
|
||||
import com.axelor.apps.account.db.repo.BudgetRepository;
|
||||
import com.axelor.apps.account.service.AnalyticMoveLineService;
|
||||
import com.axelor.apps.account.service.app.AppAccountService;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Unit;
|
||||
import com.axelor.apps.base.db.repo.AppAccountRepository;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.db.PurchaseRequestLine;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderLineRepository;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderLineServiceImpl;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.inject.Inject;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PurchaseOrderLineServiceSupplychainImpl extends PurchaseOrderLineServiceImpl {
|
||||
|
||||
@Inject protected AnalyticMoveLineService analyticMoveLineService;
|
||||
|
||||
@Inject protected UnitConversionService unitConversionService;
|
||||
|
||||
@Inject protected AppAccountService appAccountService;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
public PurchaseOrderLine fill(PurchaseOrderLine purchaseOrderLine, PurchaseOrder purchaseOrder)
|
||||
throws AxelorException {
|
||||
|
||||
purchaseOrderLine = super.fill(purchaseOrderLine, purchaseOrder);
|
||||
|
||||
this.getAndComputeAnalyticDistribution(purchaseOrderLine, purchaseOrder);
|
||||
|
||||
return purchaseOrderLine;
|
||||
}
|
||||
|
||||
public PurchaseOrderLine createPurchaseOrderLine(
|
||||
PurchaseOrder purchaseOrder, SaleOrderLine saleOrderLine) throws AxelorException {
|
||||
|
||||
LOG.debug(
|
||||
"Création d'une ligne de commande fournisseur pour le produit : {}",
|
||||
new Object[] {saleOrderLine.getProductName()});
|
||||
|
||||
Unit unit = null;
|
||||
BigDecimal qty = BigDecimal.ZERO;
|
||||
|
||||
if (saleOrderLine.getTypeSelect() != SaleOrderLineRepository.TYPE_TITLE) {
|
||||
|
||||
if (saleOrderLine.getProduct() != null) {
|
||||
unit = saleOrderLine.getProduct().getPurchasesUnit();
|
||||
}
|
||||
qty = saleOrderLine.getQty();
|
||||
if (unit == null) {
|
||||
unit = saleOrderLine.getUnit();
|
||||
} else {
|
||||
qty =
|
||||
unitConversionService.convert(
|
||||
saleOrderLine.getUnit(), unit, qty, qty.scale(), saleOrderLine.getProduct());
|
||||
}
|
||||
}
|
||||
|
||||
PurchaseOrderLine purchaseOrderLine =
|
||||
super.createPurchaseOrderLine(
|
||||
purchaseOrder,
|
||||
saleOrderLine.getProduct(),
|
||||
saleOrderLine.getProductName(),
|
||||
saleOrderLine.getDescription(),
|
||||
qty,
|
||||
unit);
|
||||
|
||||
purchaseOrderLine.setIsTitleLine(
|
||||
saleOrderLine.getTypeSelect() == SaleOrderLineRepository.TYPE_TITLE);
|
||||
this.getAndComputeAnalyticDistribution(purchaseOrderLine, purchaseOrder);
|
||||
return purchaseOrderLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PurchaseOrderLine createPurchaseOrderLine(
|
||||
PurchaseOrder purchaseOrder,
|
||||
Product product,
|
||||
String productName,
|
||||
String description,
|
||||
BigDecimal qty,
|
||||
Unit unit,
|
||||
PurchaseRequestLine purchaseRequestLine)
|
||||
throws AxelorException {
|
||||
|
||||
PurchaseOrderLine purchaseOrderLine =
|
||||
super.createPurchaseOrderLine(purchaseOrder, product, productName, description, qty, unit,purchaseRequestLine);
|
||||
|
||||
List<AnalyticMoveLine> analyticMoveLines = purchaseRequestLine.getAnalyticMoveLineList();
|
||||
|
||||
// if(analyticMoveLines.size() == 0){
|
||||
// throw new AxelorException(
|
||||
// purchaseRequestLine.getPurchaseRequest(),
|
||||
// TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
// "Vous avez une lignes expression des besoins sans Compte analytique");
|
||||
// }
|
||||
|
||||
if(analyticMoveLines.size() != 0){
|
||||
for (AnalyticMoveLine analyticMoveLine : analyticMoveLines) {
|
||||
AnalyticMoveLine analyticMoveLineCopy = Beans.get(AnalyticMoveLineRepository.class).copy(analyticMoveLine, false);
|
||||
System.out.println("**************************************************");
|
||||
System.out.println(analyticMoveLine.getAnalyticAccount());
|
||||
System.out.println(analyticMoveLine.getAnalyticAccount().getParent());
|
||||
// Budget budget = Beans.get(BudgetRepository.class).findByAnalyticAccount(analyticMoveLine.getAnalyticAccount());
|
||||
Budget budget = findBudgetRecursive(analyticMoveLine.getAnalyticAccount());
|
||||
System.out.println(budget);
|
||||
System.out.println("**************************************************");
|
||||
if(budget != null){
|
||||
BudgetDistribution newBudgetDistribution = new BudgetDistribution();
|
||||
newBudgetDistribution.setAmount(purchaseOrderLine.getCompanyExTaxTotal().multiply(analyticMoveLine.getPercentage()).divide(new BigDecimal("100")).setScale(2, RoundingMode.HALF_EVEN));
|
||||
newBudgetDistribution.setBudget(budget);
|
||||
newBudgetDistribution.setPurchaseOrderLine(purchaseOrderLine);
|
||||
newBudgetDistribution.setAnalyticMoveLine(analyticMoveLineCopy);
|
||||
Beans.get(BudgetDistributionRepository.class).save(newBudgetDistribution);
|
||||
Beans.get(PurchaseOrderLineServiceSupplychainImpl.class).computeBudgetDistributionSumAmount(purchaseOrderLine, purchaseOrder);
|
||||
}
|
||||
purchaseOrderLine.addAnalyticMoveLineListItem(analyticMoveLineCopy);
|
||||
}
|
||||
}
|
||||
|
||||
// purchaseOrderLine.setAmountInvoiced(BigDecimal.ZERO);
|
||||
//
|
||||
// purchaseOrderLine.setIsInvoiced(false);
|
||||
|
||||
return purchaseOrderLine;
|
||||
}
|
||||
|
||||
public PurchaseOrderLine createPurchaseOrderLineFromSplit(
|
||||
PurchaseOrderLine purchaseOrderLine)
|
||||
throws AxelorException {
|
||||
|
||||
PurchaseOrderLine purchaseOrderLineNew = Beans.get(PurchaseOrderLineRepository.class).copy(purchaseOrderLine, true);
|
||||
|
||||
List<AnalyticMoveLine> analyticMoveLines = purchaseOrderLine.getAnalyticMoveLineList();
|
||||
|
||||
// if(analyticMoveLines.size() == 0){
|
||||
// throw new AxelorException(
|
||||
// purchaseRequestLine.getPurchaseRequest(),
|
||||
// TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
// "Vous avez une lignes expression des besoins sans Compte analytique");
|
||||
// }
|
||||
|
||||
// for (AnalyticMoveLine analyticMoveLine : analyticMoveLines) {
|
||||
// AnalyticMoveLine analyticMoveLineCopy = Beans.get(AnalyticMoveLineRepository.class).copy(analyticMoveLine, false);
|
||||
// purchaseOrderLineNew.addAnalyticMoveLineListItem(analyticMoveLineCopy);
|
||||
// }
|
||||
|
||||
// purchaseOrderLine.setAmountInvoiced(BigDecimal.ZERO);
|
||||
//
|
||||
// purchaseOrderLine.setIsInvoiced(false);
|
||||
|
||||
return purchaseOrderLineNew;
|
||||
}
|
||||
|
||||
public PurchaseOrderLine getAndComputeAnalyticDistribution(
|
||||
PurchaseOrderLine purchaseOrderLine, PurchaseOrder purchaseOrder) {
|
||||
|
||||
if (appAccountService.getAppAccount().getAnalyticDistributionTypeSelect()
|
||||
== AppAccountRepository.DISTRIBUTION_TYPE_FREE) {
|
||||
return purchaseOrderLine;
|
||||
}
|
||||
|
||||
AnalyticDistributionTemplate analyticDistributionTemplate =
|
||||
analyticMoveLineService.getAnalyticDistributionTemplate(
|
||||
purchaseOrder.getSupplierPartner(),
|
||||
purchaseOrderLine.getProduct(),
|
||||
purchaseOrder.getCompany());
|
||||
|
||||
purchaseOrderLine.setAnalyticDistributionTemplate(analyticDistributionTemplate);
|
||||
|
||||
if (purchaseOrderLine.getAnalyticMoveLineList() != null) {
|
||||
purchaseOrderLine.getAnalyticMoveLineList().clear();
|
||||
}
|
||||
|
||||
this.computeAnalyticDistribution(purchaseOrderLine);
|
||||
|
||||
return purchaseOrderLine;
|
||||
}
|
||||
|
||||
public PurchaseOrderLine computeAnalyticDistribution(PurchaseOrderLine purchaseOrderLine) {
|
||||
|
||||
List<AnalyticMoveLine> analyticMoveLineList = purchaseOrderLine.getAnalyticMoveLineList();
|
||||
|
||||
if ((analyticMoveLineList == null || analyticMoveLineList.isEmpty())) {
|
||||
createAnalyticDistributionWithTemplate(purchaseOrderLine);
|
||||
} else {
|
||||
LocalDate date = appAccountService.getTodayDate();
|
||||
for (AnalyticMoveLine analyticMoveLine : analyticMoveLineList) {
|
||||
analyticMoveLineService.updateAnalyticMoveLine(
|
||||
analyticMoveLine, purchaseOrderLine.getCompanyExTaxTotal(), date);
|
||||
}
|
||||
}
|
||||
return purchaseOrderLine;
|
||||
}
|
||||
|
||||
public PurchaseOrderLine createAnalyticDistributionWithTemplate(
|
||||
PurchaseOrderLine purchaseOrderLine) {
|
||||
|
||||
List<AnalyticMoveLine> analyticMoveLineList =
|
||||
analyticMoveLineService.generateLines(
|
||||
purchaseOrderLine.getAnalyticDistributionTemplate(),
|
||||
purchaseOrderLine.getExTaxTotal(),
|
||||
AnalyticMoveLineRepository.STATUS_FORECAST_ORDER,
|
||||
appBaseService.getTodayDate());
|
||||
|
||||
purchaseOrderLine.clearAnalyticMoveLineList();
|
||||
analyticMoveLineList.forEach(purchaseOrderLine::addAnalyticMoveLineListItem);
|
||||
return purchaseOrderLine;
|
||||
}
|
||||
|
||||
public BigDecimal computeUndeliveredQty(PurchaseOrderLine purchaseOrderLine) {
|
||||
Preconditions.checkNotNull(purchaseOrderLine);
|
||||
|
||||
BigDecimal undeliveryQty =
|
||||
purchaseOrderLine.getQty().subtract(purchaseOrderLine.getReceivedQty());
|
||||
|
||||
if (undeliveryQty.signum() > 0) {
|
||||
return undeliveryQty;
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
public void computeBudgetDistributionSumAmount(
|
||||
PurchaseOrderLine purchaseOrderLine, PurchaseOrder purchaseOrder) {
|
||||
List<BudgetDistribution> budgetDistributionList = purchaseOrderLine.getBudgetDistributionList();
|
||||
BigDecimal budgetDistributionSumAmount = BigDecimal.ZERO;
|
||||
LocalDate computeDate = purchaseOrder.getOrderDate();
|
||||
|
||||
if (budgetDistributionList != null && !budgetDistributionList.isEmpty()) {
|
||||
|
||||
for (BudgetDistribution budgetDistribution : budgetDistributionList) {
|
||||
budgetDistributionSumAmount =
|
||||
budgetDistributionSumAmount.add(budgetDistribution.getAmount());
|
||||
Beans.get(BudgetSupplychainService.class)
|
||||
.computeBudgetDistributionSumAmount(budgetDistribution, computeDate);
|
||||
}
|
||||
}
|
||||
purchaseOrderLine.setBudgetDistributionSumAmount(budgetDistributionSumAmount);
|
||||
}
|
||||
|
||||
|
||||
private Budget findBudgetRecursive(AnalyticAccount account) {
|
||||
while (account != null) {
|
||||
System.out.println("???????????????????????????????? while loop");
|
||||
Budget budget = Beans.get(BudgetRepository.class)
|
||||
.findByAnalyticAccount(account);
|
||||
if (budget != null) {
|
||||
System.out.println("???????????????????????????????? while loop inside if");
|
||||
System.out.println(account);
|
||||
System.out.println(budget);
|
||||
|
||||
return budget; // found budget, stop here
|
||||
}
|
||||
account = account.getParent(); // go up one level
|
||||
}
|
||||
return null; // no budget found in hierarchy
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,454 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Budget;
|
||||
import com.axelor.apps.account.db.BudgetDistribution;
|
||||
import com.axelor.apps.account.db.BudgetLine;
|
||||
import com.axelor.apps.account.db.repo.BudgetDistributionRepository;
|
||||
import com.axelor.apps.account.service.app.AppAccountService;
|
||||
import com.axelor.apps.account.service.config.AccountConfigService;
|
||||
import com.axelor.apps.base.db.CancelReason;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Currency;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.PriceList;
|
||||
import com.axelor.apps.base.db.TradingName;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.db.PurchaseRequest;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderServiceImpl;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.supplychain.db.Timetable;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.apps.tool.date.DateTool;
|
||||
import com.axelor.auth.AuthUtils;
|
||||
import com.axelor.auth.db.User;
|
||||
import com.axelor.db.EntityHelper;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.dms.db.DMSFile;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.MalformedURLException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import wslite.json.JSONException;
|
||||
|
||||
public class PurchaseOrderServiceSupplychainImpl extends PurchaseOrderServiceImpl {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected AppSupplychainService appSupplychainService;
|
||||
protected AccountConfigService accountConfigService;
|
||||
protected AppAccountService appAccountService;
|
||||
protected AppBaseService appBaseService;
|
||||
protected PurchaseOrderStockService purchaseOrderStockService;
|
||||
protected BudgetSupplychainService budgetSupplychainService;
|
||||
|
||||
@Inject
|
||||
public PurchaseOrderServiceSupplychainImpl(
|
||||
AppSupplychainService appSupplychainService,
|
||||
AccountConfigService accountConfigService,
|
||||
AppAccountService appAccountService,
|
||||
AppBaseService appBaseService,
|
||||
PurchaseOrderStockService purchaseOrderStockService,
|
||||
BudgetSupplychainService budgetSupplychainService) {
|
||||
|
||||
this.appSupplychainService = appSupplychainService;
|
||||
this.accountConfigService = accountConfigService;
|
||||
this.appAccountService = appAccountService;
|
||||
this.appBaseService = appBaseService;
|
||||
this.purchaseOrderStockService = purchaseOrderStockService;
|
||||
this.budgetSupplychainService = budgetSupplychainService;
|
||||
}
|
||||
|
||||
public PurchaseOrder createPurchaseOrder(
|
||||
User buyerUser,
|
||||
Company company,
|
||||
Partner contactPartner,
|
||||
Currency currency,
|
||||
LocalDate deliveryDate,
|
||||
String internalReference,
|
||||
String externalReference,
|
||||
String notes,
|
||||
StockLocation stockLocation,
|
||||
LocalDate orderDate,
|
||||
PriceList priceList,
|
||||
Partner supplierPartner,
|
||||
TradingName tradingName)
|
||||
throws AxelorException {
|
||||
|
||||
LOG.debug(
|
||||
"Création d'une commande fournisseur : Société = {}, Reference externe = {}, Fournisseur = {}",
|
||||
company.getName(),
|
||||
externalReference,
|
||||
supplierPartner.getFullName());
|
||||
|
||||
|
||||
System.out.println("Création d'une commande fournisseur : Société = {}, Reference externe = {}, Fournisseur = {}");
|
||||
|
||||
PurchaseOrder purchaseOrder =
|
||||
super.createPurchaseOrder(
|
||||
buyerUser,
|
||||
company,
|
||||
contactPartner,
|
||||
currency,
|
||||
deliveryDate,
|
||||
internalReference,
|
||||
externalReference,
|
||||
notes, // notes
|
||||
orderDate,
|
||||
priceList,
|
||||
supplierPartner,
|
||||
tradingName);
|
||||
|
||||
purchaseOrder.setStockLocation(stockLocation);
|
||||
|
||||
purchaseOrder.setPaymentMode(supplierPartner.getInPaymentMode());
|
||||
purchaseOrder.setPaymentCondition(supplierPartner.getPaymentCondition());
|
||||
|
||||
if (purchaseOrder.getPaymentMode() == null) {
|
||||
purchaseOrder.setPaymentMode(
|
||||
this.accountConfigService.getAccountConfig(company).getInPaymentMode());
|
||||
}
|
||||
|
||||
if (purchaseOrder.getPaymentCondition() == null) {
|
||||
purchaseOrder.setPaymentCondition(
|
||||
this.accountConfigService.getAccountConfig(company).getDefPaymentCondition());
|
||||
}
|
||||
|
||||
purchaseOrder.setTradingName(tradingName);
|
||||
|
||||
return purchaseOrder;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void generateBudgetDistribution(PurchaseOrder purchaseOrder) {
|
||||
if (purchaseOrder.getPurchaseOrderLineList() != null) {
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrder.getPurchaseOrderLineList()) {
|
||||
if (purchaseOrderLine.getBudget() != null
|
||||
&& (purchaseOrderLine.getBudgetDistributionList() == null
|
||||
|| purchaseOrderLine.getBudgetDistributionList().isEmpty())) {
|
||||
BudgetDistribution budgetDistribution = new BudgetDistribution();
|
||||
budgetDistribution.setBudget(purchaseOrderLine.getBudget());
|
||||
budgetDistribution.setAmount(purchaseOrderLine.getCompanyExTaxTotal());
|
||||
purchaseOrderLine.addBudgetDistributionListItem(budgetDistribution);
|
||||
}
|
||||
}
|
||||
// purchaseOrderRepo.save(purchaseOrder);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public PurchaseOrder mergePurchaseOrders(
|
||||
List<PurchaseOrder> purchaseOrderList,
|
||||
Currency currency,
|
||||
Partner supplierPartner,
|
||||
Company company,
|
||||
StockLocation stockLocation,
|
||||
Partner contactPartner,
|
||||
PriceList priceList,
|
||||
TradingName tradingName)
|
||||
throws AxelorException {
|
||||
String numSeq = "";
|
||||
String externalRef = "";
|
||||
String notes = "";
|
||||
int statusSelect = 1;
|
||||
Set<PurchaseRequest> purchaseRequestSet = new HashSet<PurchaseRequest>();
|
||||
|
||||
for (PurchaseOrder purchaseOrderLocal : purchaseOrderList) {
|
||||
if (!numSeq.isEmpty()) {
|
||||
numSeq += "-";
|
||||
}
|
||||
numSeq +=
|
||||
purchaseOrderLocal.getPurchaseOrderSeq()
|
||||
+ (purchaseOrderLocal.getInternalReference() == null
|
||||
? ""
|
||||
: purchaseOrderLocal.getInternalReference() + "-");
|
||||
|
||||
if (!externalRef.isEmpty()) {
|
||||
externalRef += "|";
|
||||
}
|
||||
if (purchaseOrderLocal.getExternalReference() != null) {
|
||||
externalRef += purchaseOrderLocal.getExternalReference();
|
||||
}
|
||||
purchaseRequestSet.addAll(
|
||||
purchaseOrderLocal.getPurchaseRequestSet().stream().collect(Collectors.toSet()));
|
||||
if (purchaseOrderLocal.getNotes() != null) {
|
||||
notes += purchaseOrderLocal.getNotes() + " ";
|
||||
}
|
||||
statusSelect = purchaseOrderLocal.getStatusSelect();
|
||||
}
|
||||
|
||||
PurchaseOrder purchaseOrderMerged =
|
||||
this.createPurchaseOrder(
|
||||
AuthUtils.getUser(),
|
||||
company,
|
||||
contactPartner,
|
||||
currency,
|
||||
null,
|
||||
numSeq,
|
||||
externalRef,
|
||||
notes,
|
||||
stockLocation,
|
||||
LocalDate.now(),
|
||||
priceList,
|
||||
supplierPartner,
|
||||
tradingName);
|
||||
|
||||
super.attachPurchaseRequestToNewOrder(
|
||||
purchaseRequestSet, purchaseOrderMerged, purchaseOrderList);
|
||||
|
||||
super.attachToNewPurchaseOrder(purchaseOrderList, purchaseOrderMerged);
|
||||
|
||||
this.computePurchaseOrder(purchaseOrderMerged);
|
||||
|
||||
purchaseOrderMerged.setStatusSelect(statusSelect);
|
||||
|
||||
purchaseOrderRepo.save(purchaseOrderMerged);
|
||||
|
||||
this.attachAttachment(purchaseOrderList, purchaseOrderMerged);
|
||||
|
||||
super.removeOldPurchaseOrders(purchaseOrderList);
|
||||
|
||||
return purchaseOrderMerged;
|
||||
}
|
||||
|
||||
public Set<MetaFile> getMetaFiles(PurchaseOrder po) throws AxelorException, IOException {
|
||||
|
||||
List<DMSFile> metaAttachments =
|
||||
Query.of(DMSFile.class)
|
||||
.filter(
|
||||
"self.relatedId = ?1 AND self.relatedModel = ?2",
|
||||
po.getId(),
|
||||
EntityHelper.getEntityClass(po).getName())
|
||||
.fetch();
|
||||
Set<MetaFile> metaFiles = Sets.newHashSet();
|
||||
for (DMSFile metaAttachment : metaAttachments) {
|
||||
if (!metaAttachment.getIsDirectory()) metaFiles.add(metaAttachment.getMetaFile());
|
||||
}
|
||||
|
||||
return metaFiles;
|
||||
}
|
||||
|
||||
public void updateAmountToBeSpreadOverTheTimetable(PurchaseOrder purchaseOrder) {
|
||||
List<Timetable> timetableList = purchaseOrder.getTimetableList();
|
||||
BigDecimal totalHT = purchaseOrder.getExTaxTotal();
|
||||
BigDecimal sumTimetableAmount = BigDecimal.ZERO;
|
||||
if (timetableList != null) {
|
||||
for (Timetable timetable : timetableList) {
|
||||
sumTimetableAmount = sumTimetableAmount.add(timetable.getAmount());
|
||||
}
|
||||
}
|
||||
purchaseOrder.setAmountToBeSpreadOverTheTimetable(totalHT.subtract(sumTimetableAmount));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void applyToallBudgetDistribution(PurchaseOrder purchaseOrder) {
|
||||
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrder.getPurchaseOrderLineList()) {
|
||||
BudgetDistribution newBudgetDistribution = new BudgetDistribution();
|
||||
newBudgetDistribution.setAmount(purchaseOrderLine.getCompanyExTaxTotal());
|
||||
newBudgetDistribution.setBudget(purchaseOrder.getBudget());
|
||||
newBudgetDistribution.setPurchaseOrderLine(purchaseOrderLine);
|
||||
Beans.get(BudgetDistributionRepository.class).save(newBudgetDistribution);
|
||||
Beans.get(PurchaseOrderLineServiceSupplychainImpl.class)
|
||||
.computeBudgetDistributionSumAmount(purchaseOrderLine, purchaseOrder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void requestPurchaseOrder(PurchaseOrder purchaseOrder) throws AxelorException {
|
||||
// budget control
|
||||
if (appAccountService.isApp("budget")
|
||||
&& appAccountService.getAppBudget().getCheckAvailableBudget()) {
|
||||
List<PurchaseOrderLine> purchaseOrderLines = purchaseOrder.getPurchaseOrderLineList();
|
||||
|
||||
Map<Budget, BigDecimal> amountPerBudget = new HashMap<>();
|
||||
if (appAccountService.getAppBudget().getManageMultiBudget()) {
|
||||
for (PurchaseOrderLine pol : purchaseOrderLines) {
|
||||
for (BudgetDistribution bd : pol.getBudgetDistributionList()) {
|
||||
Budget budget = bd.getBudget();
|
||||
|
||||
if (!amountPerBudget.containsKey(budget)) {
|
||||
amountPerBudget.put(budget, bd.getAmount());
|
||||
} else {
|
||||
BigDecimal oldAmount = amountPerBudget.get(budget);
|
||||
amountPerBudget.put(budget, oldAmount.add(bd.getAmount()));
|
||||
}
|
||||
|
||||
isBudgetExceeded(budget, amountPerBudget.get(budget));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (PurchaseOrderLine pol : purchaseOrderLines) {
|
||||
// getting Budget associated to POL
|
||||
Budget budget = pol.getBudget();
|
||||
|
||||
if (!amountPerBudget.containsKey(budget)) {
|
||||
amountPerBudget.put(budget, pol.getExTaxTotal());
|
||||
} else {
|
||||
BigDecimal oldAmount = amountPerBudget.get(budget);
|
||||
amountPerBudget.put(budget, oldAmount.add(pol.getExTaxTotal()));
|
||||
}
|
||||
|
||||
isBudgetExceeded(budget, amountPerBudget.get(budget));
|
||||
}
|
||||
}
|
||||
}
|
||||
super.requestPurchaseOrder(purchaseOrder);
|
||||
int intercoPurchaseCreatingStatus =
|
||||
Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getIntercoPurchaseCreatingStatusSelect();
|
||||
if (purchaseOrder.getInterco()
|
||||
&& intercoPurchaseCreatingStatus == PurchaseOrderRepository.STATUS_REQUESTED) {
|
||||
Beans.get(IntercoService.class).generateIntercoSaleFromPurchase(purchaseOrder);
|
||||
}
|
||||
if (purchaseOrder.getCreatedByInterco()) {
|
||||
fillIntercompanySaleOrderCounterpart(purchaseOrder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill interco sale order counterpart is the sale order exist.
|
||||
*
|
||||
* @param purchaseOrder
|
||||
*/
|
||||
protected void fillIntercompanySaleOrderCounterpart(PurchaseOrder purchaseOrder) {
|
||||
SaleOrder saleOrder =
|
||||
Beans.get(SaleOrderRepository.class)
|
||||
.all()
|
||||
.filter("self.saleOrderSeq = :saleOrderSeq")
|
||||
.bind("saleOrderSeq", purchaseOrder.getExternalReference())
|
||||
.fetchOne();
|
||||
if (saleOrder != null) {
|
||||
saleOrder.setExternalReference(purchaseOrder.getPurchaseOrderSeq());
|
||||
}
|
||||
}
|
||||
|
||||
public void isBudgetExceeded(Budget budget, BigDecimal amount) throws AxelorException {
|
||||
if (budget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// getting BudgetLine of the period
|
||||
BudgetLine bl = null;
|
||||
for (BudgetLine budgetLine : budget.getBudgetLineList()) {
|
||||
if (DateTool.isBetween(
|
||||
budgetLine.getFromDate(), budgetLine.getToDate(), appAccountService.getTodayDate())) {
|
||||
bl = budgetLine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// checking budget excess
|
||||
if (bl != null) {
|
||||
if (amount.add(bl.getAmountCommitted()).compareTo(bl.getAmountExpected()) > 0) {
|
||||
throw new AxelorException(
|
||||
budget,
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.PURCHASE_ORDER_2),
|
||||
budget.getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void validatePurchaseOrder(PurchaseOrder purchaseOrder)
|
||||
throws AxelorException, MalformedURLException, JSONException {
|
||||
super.validatePurchaseOrder(purchaseOrder);
|
||||
|
||||
if (appSupplychainService.getAppSupplychain().getSupplierStockMoveGenerationAuto()
|
||||
&& !purchaseOrderStockService.existActiveStockMoveForPurchaseOrder(purchaseOrder.getId())) {
|
||||
purchaseOrderStockService.createStockMoveFromPurchaseOrder(purchaseOrder);
|
||||
}
|
||||
|
||||
if (appAccountService.getAppBudget().getActive()
|
||||
&& !appAccountService.getAppBudget().getManageMultiBudget()) {
|
||||
generateBudgetDistribution(purchaseOrder);
|
||||
}
|
||||
int intercoPurchaseCreatingStatus =
|
||||
Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getIntercoPurchaseCreatingStatusSelect();
|
||||
if (purchaseOrder.getInterco()
|
||||
&& intercoPurchaseCreatingStatus == PurchaseOrderRepository.STATUS_VALIDATED) {
|
||||
Beans.get(IntercoService.class).generateIntercoSaleFromPurchase(purchaseOrder);
|
||||
}
|
||||
|
||||
budgetSupplychainService.updateBudgetLinesFromPurchaseOrder(purchaseOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void cancelPurchaseOrder(PurchaseOrder purchaseOrder) {
|
||||
super.cancelPurchaseOrder(purchaseOrder);
|
||||
budgetSupplychainService.updateBudgetLinesFromPurchaseOrder(purchaseOrder);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setPurchaseOrderLineBudget(PurchaseOrder purchaseOrder) {
|
||||
|
||||
Budget budget = purchaseOrder.getBudget();
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrder.getPurchaseOrderLineList()) {
|
||||
purchaseOrderLine.setBudget(budget);
|
||||
}
|
||||
}
|
||||
|
||||
public void finishSockMoves(
|
||||
PurchaseOrder purchaseOrder, CancelReason raison, String cancelReasonStr)
|
||||
throws AxelorException {
|
||||
Beans.get(PurchaseOrderStockServiceImpl.class).cancelReceiptWithRaison(purchaseOrder, raison, cancelReasonStr);
|
||||
this.finishPurchaseOrder(purchaseOrder, cancelReasonStr);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void computeBudgetDistribution(PurchaseOrderLine purchaseOrderLine) {
|
||||
List<BudgetDistribution> budgetDistributionList = purchaseOrderLine.getBudgetDistributionList();
|
||||
for (BudgetDistribution budgetDistribution : budgetDistributionList) {
|
||||
BigDecimal percent = budgetDistribution.getAnalyticMoveLine().getPercentage();
|
||||
budgetDistribution.setAmount(purchaseOrderLine.getCompanyExTaxTotal().multiply(percent).setScale(2).divide(new BigDecimal("100")));
|
||||
Beans.get(BudgetDistributionRepository.class).save(budgetDistribution);
|
||||
Beans.get(PurchaseOrderLineServiceSupplychainImpl.class).computeBudgetDistributionSumAmount(purchaseOrderLine, purchaseOrderLine.getPurchaseOrder());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public interface PurchaseOrderStockService {
|
||||
|
||||
/**
|
||||
* Méthode permettant de créer un StockMove à partir d'un PurchaseOrder.
|
||||
*
|
||||
* @param purchaseOrder une commande
|
||||
* @throws AxelorException Aucune séquence de StockMove n'a été configurée
|
||||
*/
|
||||
public List<Long> createStockMoveFromPurchaseOrder(PurchaseOrder purchaseOrder)
|
||||
throws AxelorException;
|
||||
|
||||
public StockMoveLine createStockMoveLine(
|
||||
StockMove stockMove,
|
||||
StockMove qualityStockMove,
|
||||
PurchaseOrderLine purchaseOrderLine,
|
||||
BigDecimal qty)
|
||||
throws AxelorException;
|
||||
|
||||
public void cancelReceipt(PurchaseOrder purchaseOrder) throws AxelorException;
|
||||
|
||||
public boolean isStockMoveProduct(PurchaseOrderLine purchaseOrderLine) throws AxelorException;
|
||||
|
||||
public boolean isStockMoveProduct(
|
||||
PurchaseOrderLine purchaseOrderLine, PurchaseOrder purchaseOrder) throws AxelorException;
|
||||
|
||||
// Check if existing at least one stockMove not canceled for the purchaseOrder
|
||||
public boolean existActiveStockMoveForPurchaseOrder(Long purchaseOrderId);
|
||||
|
||||
public void updateReceiptState(PurchaseOrder purchaseOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Create a query to find purchase order line of a product of a specific/all company and a
|
||||
* specific/all stock location
|
||||
*
|
||||
* @param productId
|
||||
* @param companyId
|
||||
* @param stockLocationId
|
||||
* @return the query.
|
||||
*/
|
||||
public String getPurchaseOrderLineListForAProduct(
|
||||
Long productId, Long companyId, Long stockLocationId);
|
||||
}
|
||||
@ -0,0 +1,631 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AnalyticMoveLine;
|
||||
import com.axelor.apps.account.db.TaxLine;
|
||||
import com.axelor.apps.account.db.repo.AnalyticMoveLineRepository;
|
||||
import com.axelor.apps.base.db.Address;
|
||||
import com.axelor.apps.base.db.CancelReason;
|
||||
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.Unit;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.base.service.PartnerService;
|
||||
import com.axelor.apps.base.service.ShippingCoefService;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderLineRepository;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.stock.db.StockConfig;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.stock.service.StockLocationService;
|
||||
import com.axelor.apps.stock.service.StockMoveLineService;
|
||||
import com.axelor.apps.stock.service.StockMoveService;
|
||||
import com.axelor.apps.stock.service.config.StockConfigService;
|
||||
import com.axelor.apps.supplychain.db.SupplyChainConfig;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplyChainConfigRepository;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.apps.supplychain.service.config.SupplyChainConfigService;
|
||||
import com.axelor.apps.tool.StringTool;
|
||||
import com.axelor.common.StringUtils;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PurchaseOrderStockServiceImpl implements PurchaseOrderStockService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected UnitConversionService unitConversionService;
|
||||
protected StockMoveLineRepository stockMoveLineRepository;
|
||||
protected PurchaseOrderLineServiceSupplychainImpl purchaseOrderLineServiceSupplychainImpl;
|
||||
protected AppBaseService appBaseService;
|
||||
protected ShippingCoefService shippingCoefService;
|
||||
protected StockMoveLineServiceSupplychain stockMoveLineServiceSupplychain;
|
||||
protected StockMoveService stockMoveService;
|
||||
|
||||
@Inject
|
||||
public PurchaseOrderStockServiceImpl(
|
||||
UnitConversionService unitConversionService,
|
||||
StockMoveLineRepository stockMoveLineRepository,
|
||||
PurchaseOrderLineServiceSupplychainImpl purchaseOrderLineServiceSupplychainImpl,
|
||||
AppBaseService appBaseService,
|
||||
ShippingCoefService shippingCoefService,
|
||||
StockMoveLineServiceSupplychain stockMoveLineServiceSupplychain,
|
||||
StockMoveService stockMoveService) {
|
||||
|
||||
this.unitConversionService = unitConversionService;
|
||||
this.stockMoveLineRepository = stockMoveLineRepository;
|
||||
this.purchaseOrderLineServiceSupplychainImpl = purchaseOrderLineServiceSupplychainImpl;
|
||||
this.appBaseService = appBaseService;
|
||||
this.shippingCoefService = shippingCoefService;
|
||||
this.stockMoveLineServiceSupplychain = stockMoveLineServiceSupplychain;
|
||||
this.stockMoveService = stockMoveService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de créer un StockMove à partir d'un PurchaseOrder.
|
||||
*
|
||||
* @param purchaseOrder une commande
|
||||
* @throws AxelorException Aucune séquence de StockMove n'a été configurée
|
||||
*/
|
||||
public List<Long> createStockMoveFromPurchaseOrder(PurchaseOrder purchaseOrder)
|
||||
throws AxelorException {
|
||||
|
||||
List<Long> stockMoveIdList = new ArrayList<>();
|
||||
|
||||
if (purchaseOrder.getPurchaseOrderLineList() == null || purchaseOrder.getCompany() == null) {
|
||||
return stockMoveIdList;
|
||||
}
|
||||
|
||||
if (purchaseOrder.getStockLocation() == null) {
|
||||
throw new AxelorException(
|
||||
purchaseOrder,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.PO_MISSING_STOCK_LOCATION),
|
||||
purchaseOrder.getPurchaseOrderSeq());
|
||||
}
|
||||
|
||||
Map<LocalDate, List<PurchaseOrderLine>> purchaseOrderLinePerDateMap =
|
||||
getAllPurchaseOrderLinePerDate(purchaseOrder);
|
||||
|
||||
for (LocalDate estimatedDeliveryDate :
|
||||
purchaseOrderLinePerDateMap
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(x -> x != null)
|
||||
.sorted((x, y) -> x.compareTo(y))
|
||||
.collect(Collectors.toList())) {
|
||||
|
||||
List<PurchaseOrderLine> purchaseOrderLineList =
|
||||
purchaseOrderLinePerDateMap.get(estimatedDeliveryDate);
|
||||
|
||||
List<Long> stockMoveId =
|
||||
createStockMove(purchaseOrder, estimatedDeliveryDate, purchaseOrderLineList);
|
||||
|
||||
if (stockMoveId != null && !stockMoveId.isEmpty()) {
|
||||
|
||||
stockMoveIdList.addAll(stockMoveId);
|
||||
}
|
||||
}
|
||||
Optional<List<PurchaseOrderLine>> purchaseOrderLineListDeliveryDateNull =
|
||||
Optional.ofNullable(purchaseOrderLinePerDateMap.get(null));
|
||||
if (purchaseOrderLineListDeliveryDateNull.isPresent()) {
|
||||
|
||||
List<Long> stockMoveId =
|
||||
createStockMove(purchaseOrder, null, purchaseOrderLineListDeliveryDateNull.get());
|
||||
|
||||
if (stockMoveId != null && !stockMoveId.isEmpty()) {
|
||||
|
||||
stockMoveIdList.addAll(stockMoveId);
|
||||
}
|
||||
}
|
||||
return stockMoveIdList;
|
||||
}
|
||||
|
||||
protected List<Long> createStockMove(
|
||||
PurchaseOrder purchaseOrder,
|
||||
LocalDate estimatedDeliveryDate,
|
||||
List<PurchaseOrderLine> purchaseOrderLineList)
|
||||
throws AxelorException {
|
||||
|
||||
List<Long> stockMoveIdList = new ArrayList<>();
|
||||
|
||||
Partner supplierPartner = purchaseOrder.getSupplierPartner();
|
||||
Company company = purchaseOrder.getCompany();
|
||||
|
||||
Address address = Beans.get(PartnerService.class).getDeliveryAddress(supplierPartner);
|
||||
|
||||
StockLocation startLocation = getStartStockLocation(purchaseOrder);
|
||||
|
||||
StockMove stockMove =
|
||||
stockMoveService.createStockMove(
|
||||
address,
|
||||
null,
|
||||
company,
|
||||
supplierPartner,
|
||||
startLocation,
|
||||
purchaseOrder.getStockLocation(),
|
||||
null,
|
||||
estimatedDeliveryDate,
|
||||
purchaseOrder.getNotes(),
|
||||
purchaseOrder.getShipmentMode(),
|
||||
purchaseOrder.getFreightCarrierMode(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
StockMoveRepository.TYPE_INCOMING);
|
||||
|
||||
StockMove qualityStockMove =
|
||||
stockMoveService.createStockMove(
|
||||
address,
|
||||
null,
|
||||
company,
|
||||
supplierPartner,
|
||||
startLocation,
|
||||
company.getStockConfig().getQualityControlDefaultStockLocation(),
|
||||
null,
|
||||
estimatedDeliveryDate,
|
||||
purchaseOrder.getNotes(),
|
||||
purchaseOrder.getShipmentMode(),
|
||||
purchaseOrder.getFreightCarrierMode(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
StockMoveRepository.TYPE_INCOMING);
|
||||
|
||||
stockMove.setOriginId(purchaseOrder.getId());
|
||||
stockMove.setOriginTypeSelect(StockMoveRepository.ORIGIN_PURCHASE_ORDER);
|
||||
stockMove.setOrigin(purchaseOrder.getPurchaseOrderSeq());
|
||||
stockMove.setTradingName(purchaseOrder.getTradingName());
|
||||
stockMove.setStamp(purchaseOrder.getStamp());
|
||||
stockMove.setFixTax(purchaseOrder.getFixTax());
|
||||
stockMove.setPayerPartner(purchaseOrder.getPayerPartner());
|
||||
stockMove.setDeliveryPartner(purchaseOrder.getDeliveryPartner());
|
||||
stockMove.setAnalyticAccount(purchaseOrder.getAnalyticAccount());
|
||||
stockMove.setAnalyticAxis(purchaseOrder.getAnalyticAxis());
|
||||
|
||||
qualityStockMove.setOriginId(purchaseOrder.getId());
|
||||
qualityStockMove.setOriginTypeSelect(StockMoveRepository.ORIGIN_PURCHASE_ORDER);
|
||||
qualityStockMove.setOrigin(purchaseOrder.getPurchaseOrderSeq());
|
||||
qualityStockMove.setTradingName(purchaseOrder.getTradingName());
|
||||
|
||||
SupplyChainConfig supplychainConfig =
|
||||
Beans.get(SupplyChainConfigService.class).getSupplyChainConfig(purchaseOrder.getCompany());
|
||||
if (supplychainConfig.getDefaultEstimatedDateForPurchaseOrder()
|
||||
== SupplyChainConfigRepository.CURRENT_DATE
|
||||
&& stockMove.getEstimatedDate() == null) {
|
||||
stockMove.setEstimatedDate(appBaseService.getTodayDate());
|
||||
} else if (supplychainConfig.getDefaultEstimatedDateForPurchaseOrder()
|
||||
== SupplyChainConfigRepository.CURRENT_DATE_PLUS_DAYS
|
||||
&& stockMove.getEstimatedDate() == null) {
|
||||
stockMove.setEstimatedDate(
|
||||
appBaseService
|
||||
.getTodayDate()
|
||||
.plusDays(supplychainConfig.getNumberOfDaysForPurchaseOrder().longValue()));
|
||||
}
|
||||
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLineList) {
|
||||
BigDecimal qty =
|
||||
purchaseOrderLineServiceSupplychainImpl.computeUndeliveredQty(purchaseOrderLine);
|
||||
|
||||
if (qty.signum() > 0 && !existActiveStockMoveForPurchaseOrderLine(purchaseOrderLine)) {
|
||||
this.createStockMoveLine(stockMove, qualityStockMove, purchaseOrderLine, qty);
|
||||
}
|
||||
}
|
||||
if (stockMove.getStockMoveLineList() != null && !stockMove.getStockMoveLineList().isEmpty()) {
|
||||
stockMoveService.plan(stockMove);
|
||||
stockMoveIdList.add(stockMove.getId());
|
||||
}
|
||||
if (qualityStockMove.getStockMoveLineList() != null
|
||||
&& !qualityStockMove.getStockMoveLineList().isEmpty()) {
|
||||
stockMoveService.plan(qualityStockMove);
|
||||
stockMoveIdList.add(qualityStockMove.getId());
|
||||
}
|
||||
|
||||
return stockMoveIdList;
|
||||
}
|
||||
|
||||
protected StockLocation getStartStockLocation(PurchaseOrder purchaseOrder)
|
||||
throws AxelorException {
|
||||
|
||||
Company company = purchaseOrder.getCompany();
|
||||
|
||||
StockLocation startLocation =
|
||||
Beans.get(StockLocationRepository.class).findByPartner(purchaseOrder.getSupplierPartner());
|
||||
|
||||
if (startLocation == null) {
|
||||
StockConfigService stockConfigService = Beans.get(StockConfigService.class);
|
||||
StockConfig stockConfig = stockConfigService.getStockConfig(company);
|
||||
startLocation = stockConfigService.getSupplierVirtualStockLocation(stockConfig);
|
||||
}
|
||||
if (startLocation == null) {
|
||||
throw new AxelorException(
|
||||
purchaseOrder,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.PURCHASE_ORDER_1),
|
||||
company.getName());
|
||||
}
|
||||
|
||||
return startLocation;
|
||||
}
|
||||
|
||||
protected Map<LocalDate, List<PurchaseOrderLine>> getAllPurchaseOrderLinePerDate(
|
||||
PurchaseOrder purchaseOrder) {
|
||||
|
||||
Map<LocalDate, List<PurchaseOrderLine>> purchaseOrderLinePerDateMap = new HashMap<>();
|
||||
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrder.getPurchaseOrderLineList()) {
|
||||
|
||||
if (purchaseOrderLineServiceSupplychainImpl.computeUndeliveredQty(purchaseOrderLine).signum()
|
||||
<= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate dateKey = purchaseOrderLine.getEstimatedDelivDate();
|
||||
|
||||
if (dateKey == null) {
|
||||
dateKey = purchaseOrderLine.getPurchaseOrder().getDeliveryDate();
|
||||
}
|
||||
|
||||
List<PurchaseOrderLine> purchaseOrderLineLists = purchaseOrderLinePerDateMap.get(dateKey);
|
||||
|
||||
if (purchaseOrderLineLists == null) {
|
||||
purchaseOrderLineLists = new ArrayList<>();
|
||||
purchaseOrderLinePerDateMap.put(dateKey, purchaseOrderLineLists);
|
||||
}
|
||||
|
||||
purchaseOrderLineLists.add(purchaseOrderLine);
|
||||
}
|
||||
|
||||
return purchaseOrderLinePerDateMap;
|
||||
}
|
||||
|
||||
public StockMoveLine createStockMoveLine(
|
||||
StockMove stockMove,
|
||||
StockMove qualityStockMove,
|
||||
PurchaseOrderLine purchaseOrderLine,
|
||||
BigDecimal qty)
|
||||
throws AxelorException {
|
||||
|
||||
StockMoveLine stockMoveLine = null;
|
||||
|
||||
if (this.isStockMoveProduct(purchaseOrderLine)) {
|
||||
|
||||
stockMoveLine =
|
||||
createProductStockMoveLine(
|
||||
purchaseOrderLine,
|
||||
qty,
|
||||
needControlOnReceipt(purchaseOrderLine) ? qualityStockMove : stockMove);
|
||||
|
||||
} else if (purchaseOrderLine.getIsTitleLine()) {
|
||||
stockMoveLine = createTitleStockMoveLine(purchaseOrderLine, stockMove);
|
||||
}
|
||||
stockMoveLine.setAnalyticDistributionTemplate(purchaseOrderLine.getAnalyticDistributionTemplate());
|
||||
List<AnalyticMoveLine> analyticMoveLines = purchaseOrderLine.getAnalyticMoveLineList();
|
||||
if(analyticMoveLines.size() != 0){
|
||||
for (AnalyticMoveLine analyticMoveLine : analyticMoveLines) {
|
||||
AnalyticMoveLine analyticMoveLineCopy = Beans.get(AnalyticMoveLineRepository.class).copy(analyticMoveLine, false);
|
||||
stockMoveLine.addAnalyticMoveLineListItem(analyticMoveLineCopy);
|
||||
}
|
||||
}
|
||||
return stockMoveLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param product
|
||||
* @param purchaseOrder
|
||||
* @return true if product needs a control on receipt and if the purchase order is not a direct
|
||||
* order
|
||||
*/
|
||||
protected boolean needControlOnReceipt(PurchaseOrderLine purchaseOrderLine) {
|
||||
|
||||
Product product = purchaseOrderLine.getProduct();
|
||||
PurchaseOrder purchaseOrder = purchaseOrderLine.getPurchaseOrder();
|
||||
|
||||
if (product.getControlOnReceipt()
|
||||
&& !purchaseOrder.getStockLocation().getDirectOrderLocation()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected StockMoveLine createProductStockMoveLine(
|
||||
PurchaseOrderLine purchaseOrderLine, BigDecimal qty, StockMove stockMove)
|
||||
throws AxelorException {
|
||||
|
||||
PurchaseOrder purchaseOrder = purchaseOrderLine.getPurchaseOrder();
|
||||
Product product = purchaseOrderLine.getProduct();
|
||||
Unit unit = product.getUnit();
|
||||
BigDecimal priceDiscounted = purchaseOrderLine.getPriceDiscounted();
|
||||
BigDecimal companyUnitPriceUntaxed = purchaseOrderLine.getCompanyExTaxTotal();
|
||||
|
||||
if (purchaseOrderLine.getQty().compareTo(BigDecimal.ZERO) != 0) {
|
||||
companyUnitPriceUntaxed =
|
||||
purchaseOrderLine
|
||||
.getCompanyExTaxTotal()
|
||||
.divide(
|
||||
purchaseOrderLine.getQty(),
|
||||
appBaseService.getNbDecimalDigitForUnitPrice(),
|
||||
RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
if (unit != null && !unit.equals(purchaseOrderLine.getUnit())) {
|
||||
qty =
|
||||
unitConversionService.convert(
|
||||
purchaseOrderLine.getUnit(), unit, qty, qty.scale(), product);
|
||||
|
||||
priceDiscounted =
|
||||
unitConversionService.convert(
|
||||
unit,
|
||||
purchaseOrderLine.getUnit(),
|
||||
priceDiscounted,
|
||||
appBaseService.getNbDecimalDigitForUnitPrice(),
|
||||
product);
|
||||
|
||||
companyUnitPriceUntaxed =
|
||||
unitConversionService.convert(
|
||||
unit,
|
||||
purchaseOrderLine.getUnit(),
|
||||
companyUnitPriceUntaxed,
|
||||
appBaseService.getNbDecimalDigitForUnitPrice(),
|
||||
product);
|
||||
}
|
||||
|
||||
BigDecimal shippingCoef =
|
||||
shippingCoefService.getShippingCoef(
|
||||
product, purchaseOrder.getSupplierPartner(), purchaseOrder.getCompany(), qty);
|
||||
priceDiscounted = priceDiscounted.multiply(shippingCoef);
|
||||
companyUnitPriceUntaxed = companyUnitPriceUntaxed.multiply(shippingCoef);
|
||||
|
||||
BigDecimal taxRate = BigDecimal.ZERO;
|
||||
TaxLine taxLine = purchaseOrderLine.getTaxLine();
|
||||
if (taxLine != null) {
|
||||
taxRate = taxLine.getValue();
|
||||
}
|
||||
|
||||
return stockMoveLineServiceSupplychain.createStockMoveLine(
|
||||
product,
|
||||
purchaseOrderLine.getProductName(),
|
||||
purchaseOrderLine.getDescription(),
|
||||
qty,
|
||||
BigDecimal.ZERO,
|
||||
priceDiscounted,
|
||||
companyUnitPriceUntaxed,
|
||||
unit,
|
||||
stockMove,
|
||||
StockMoveLineService.TYPE_PURCHASES,
|
||||
purchaseOrder.getInAti(),
|
||||
taxRate,
|
||||
null,
|
||||
purchaseOrderLine);
|
||||
}
|
||||
|
||||
protected StockMoveLine createTitleStockMoveLine(
|
||||
PurchaseOrderLine purchaseOrderLine, StockMove stockMove) throws AxelorException {
|
||||
|
||||
return stockMoveLineServiceSupplychain.createStockMoveLine(
|
||||
purchaseOrderLine.getProduct(),
|
||||
purchaseOrderLine.getProductName(),
|
||||
purchaseOrderLine.getDescription(),
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
null,
|
||||
stockMove,
|
||||
2,
|
||||
purchaseOrderLine.getPurchaseOrder().getInAti(),
|
||||
null,
|
||||
null,
|
||||
purchaseOrderLine);
|
||||
}
|
||||
|
||||
public void cancelReceipt(PurchaseOrder purchaseOrder) throws AxelorException {
|
||||
|
||||
List<StockMove> stockMoveList =
|
||||
Beans.get(StockMoveRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.originTypeSelect = ? AND self.originId = ? AND self.statusSelect = 2",
|
||||
StockMoveRepository.ORIGIN_PURCHASE_ORDER,
|
||||
purchaseOrder.getId())
|
||||
.fetch();
|
||||
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
|
||||
stockMoveService.cancel(stockMove);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelReceiptWithRaison(
|
||||
PurchaseOrder purchaseOrder, CancelReason cancelReason, String cancelReasonStr)
|
||||
throws AxelorException {
|
||||
|
||||
List<StockMove> stockMoveList =
|
||||
Beans.get(StockMoveRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.originTypeSelect = ? AND self.originId = ? AND self.statusSelect = 2",
|
||||
StockMoveRepository.ORIGIN_PURCHASE_ORDER,
|
||||
purchaseOrder.getId())
|
||||
.fetch();
|
||||
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
|
||||
stockMoveService.cancel(stockMove, cancelReason, cancelReasonStr);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStockMoveProduct(PurchaseOrderLine purchaseOrderLine) throws AxelorException {
|
||||
return isStockMoveProduct(purchaseOrderLine, purchaseOrderLine.getPurchaseOrder());
|
||||
}
|
||||
|
||||
public boolean isStockMoveProduct(
|
||||
PurchaseOrderLine purchaseOrderLine, PurchaseOrder purchaseOrder) throws AxelorException {
|
||||
|
||||
Company company = purchaseOrder.getCompany();
|
||||
|
||||
SupplyChainConfig supplyChainConfig =
|
||||
Beans.get(SupplyChainConfigService.class).getSupplyChainConfig(company);
|
||||
|
||||
Product product = purchaseOrderLine.getProduct();
|
||||
|
||||
return (product != null
|
||||
&& ((ProductRepository.PRODUCT_TYPE_SERVICE.equals(product.getProductTypeSelect())
|
||||
&& supplyChainConfig.getHasInSmForNonStorableProduct()
|
||||
&& !product.getIsShippingCostsProduct())
|
||||
|| (ProductRepository.PRODUCT_TYPE_STORABLE.equals(product.getProductTypeSelect())
|
||||
&& supplyChainConfig.getHasInSmForStorableProduct())));
|
||||
}
|
||||
|
||||
protected boolean existActiveStockMoveForPurchaseOrderLine(PurchaseOrderLine purchaseOrderLine) {
|
||||
|
||||
long stockMoveLineCount =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.purchaseOrderLine.id = ?1 AND self.stockMove.statusSelect in (?2,?3)",
|
||||
purchaseOrderLine.getId(),
|
||||
StockMoveRepository.STATUS_DRAFT,
|
||||
StockMoveRepository.STATUS_PLANNED)
|
||||
.count();
|
||||
|
||||
return stockMoveLineCount > 0;
|
||||
}
|
||||
|
||||
// Check if existing at least one stockMove not canceled for the purchaseOrder
|
||||
public boolean existActiveStockMoveForPurchaseOrder(Long purchaseOrderId) {
|
||||
long nbStockMove =
|
||||
Beans.get(StockMoveRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.originTypeSelect LIKE ? AND self.originId = ? AND self.statusSelect <> ?",
|
||||
StockMoveRepository.ORIGIN_PURCHASE_ORDER,
|
||||
purchaseOrderId,
|
||||
StockMoveRepository.STATUS_CANCELED)
|
||||
.count();
|
||||
return nbStockMove > 0;
|
||||
}
|
||||
|
||||
public void updateReceiptState(PurchaseOrder purchaseOrder) throws AxelorException {
|
||||
purchaseOrder.setReceiptState(computeReceiptState(purchaseOrder));
|
||||
}
|
||||
|
||||
private int computeReceiptState(PurchaseOrder purchaseOrder) throws AxelorException {
|
||||
|
||||
if (purchaseOrder.getPurchaseOrderLineList() == null
|
||||
|| purchaseOrder.getPurchaseOrderLineList().isEmpty()) {
|
||||
return PurchaseOrderRepository.STATE_NOT_RECEIVED;
|
||||
}
|
||||
|
||||
int receiptState = -1;
|
||||
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrder.getPurchaseOrderLineList()) {
|
||||
|
||||
if (this.isStockMoveProduct(purchaseOrderLine, purchaseOrder)) {
|
||||
|
||||
if (purchaseOrderLine.getReceiptState() == PurchaseOrderRepository.STATE_RECEIVED) {
|
||||
if (receiptState == PurchaseOrderRepository.STATE_NOT_RECEIVED
|
||||
|| receiptState == PurchaseOrderRepository.STATE_PARTIALLY_RECEIVED) {
|
||||
return PurchaseOrderRepository.STATE_PARTIALLY_RECEIVED;
|
||||
} else {
|
||||
receiptState = PurchaseOrderRepository.STATE_RECEIVED;
|
||||
}
|
||||
} else if (purchaseOrderLine.getReceiptState()
|
||||
== PurchaseOrderRepository.STATE_NOT_RECEIVED) {
|
||||
if (receiptState == PurchaseOrderRepository.STATE_RECEIVED
|
||||
|| receiptState == PurchaseOrderRepository.STATE_PARTIALLY_RECEIVED) {
|
||||
return PurchaseOrderRepository.STATE_PARTIALLY_RECEIVED;
|
||||
} else {
|
||||
receiptState = PurchaseOrderRepository.STATE_NOT_RECEIVED;
|
||||
}
|
||||
} else if (purchaseOrderLine.getReceiptState()
|
||||
== PurchaseOrderRepository.STATE_PARTIALLY_RECEIVED) {
|
||||
return PurchaseOrderRepository.STATE_PARTIALLY_RECEIVED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return receiptState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPurchaseOrderLineListForAProduct(
|
||||
Long productId, Long companyId, Long stockLocationId) {
|
||||
List<Integer> statusList = new ArrayList<>();
|
||||
statusList.add(PurchaseOrderRepository.STATUS_VALIDATED);
|
||||
String status =
|
||||
Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getpOFilterOnStockDetailStatusSelect();
|
||||
if (!StringUtils.isBlank(status)) {
|
||||
statusList = StringTool.getIntegerList(status);
|
||||
}
|
||||
String statusListQuery =
|
||||
statusList.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||
String query =
|
||||
"self.product.id = "
|
||||
+ productId
|
||||
+ " AND self.receiptState != "
|
||||
+ PurchaseOrderLineRepository.RECEIPT_STATE_RECEIVED
|
||||
+ " AND self.purchaseOrder.statusSelect IN ("
|
||||
+ statusListQuery
|
||||
+ ")";
|
||||
if (companyId != 0L) {
|
||||
query += " AND self.purchaseOrder.company.id = " + companyId;
|
||||
if (stockLocationId != 0L) {
|
||||
StockLocation stockLocation =
|
||||
Beans.get(StockLocationRepository.class).find(stockLocationId);
|
||||
List<StockLocation> stockLocationList =
|
||||
Beans.get(StockLocationService.class)
|
||||
.getAllLocationAndSubLocation(stockLocation, false);
|
||||
if (!stockLocationList.isEmpty() && stockLocation.getCompany().getId() == companyId) {
|
||||
query +=
|
||||
" AND self.purchaseOrder.stockLocation.id IN ("
|
||||
+ StringTool.getIdListString(stockLocationList)
|
||||
+ ") ";
|
||||
}
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.util.List;
|
||||
|
||||
import com.axelor.apps.account.db.AnalyticMoveLine;
|
||||
import com.axelor.apps.account.db.repo.AnalyticMoveLineRepository;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.db.PurchaseRequest;
|
||||
import com.axelor.apps.purchase.db.PurchaseRequestLine;
|
||||
import com.axelor.apps.purchase.service.PurchaseRequestServiceImpl;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
public class PurchaseRequestServiceSupplychainImpl extends PurchaseRequestServiceImpl {
|
||||
|
||||
@Inject StockLocationRepository stockLocationRepo;
|
||||
|
||||
@Override
|
||||
protected PurchaseOrder createPurchaseOrder(PurchaseRequest purchaseRequest)
|
||||
throws AxelorException {
|
||||
|
||||
PurchaseOrder purchaseOrder = super.createPurchaseOrder(purchaseRequest);
|
||||
purchaseOrder.setStockLocation(purchaseRequest.getStockLocation());
|
||||
purchaseOrder.setAnalyticAccount(purchaseRequest.getAnalyticAccount());
|
||||
purchaseOrder.setAnalyticAxis(purchaseRequest.getAnalyticAxis());
|
||||
return purchaseOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPurchaseOrderGroupBySupplierKey(PurchaseRequest purchaseRequest) {
|
||||
String key = super.getPurchaseOrderGroupBySupplierKey(purchaseRequest);
|
||||
StockLocation stockLocation = purchaseRequest.getStockLocation();
|
||||
if (stockLocation != null) {
|
||||
key = key + "_" + stockLocation.getId().toString();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setPurchaseOrderLineAnalyticMoveLine(PurchaseRequestLine purchaseRequestLine,PurchaseOrderLine purchaseOrderLine){
|
||||
List<AnalyticMoveLine> analyticMoveLines = purchaseRequestLine.getAnalyticMoveLineList();
|
||||
|
||||
for (AnalyticMoveLine analyticMoveLine : analyticMoveLines) {
|
||||
AnalyticMoveLine aml = Beans.get(AnalyticMoveLineRepository.class).copy(analyticMoveLine, true);
|
||||
purchaseOrderLine.addAnalyticMoveLineListItem(aml);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Unit;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* A service which contains all methods managing the reservation feature. The purpose of this
|
||||
* service is to update accordingly all reservedQty and requestedReservedQty fields in
|
||||
* SaleOrderLine, StockMoveLine and StockLocationLine. The reservation is computed from stock move
|
||||
* lines then fields in sale order lines and stock location lines are updated.
|
||||
*/
|
||||
public interface ReservedQtyService {
|
||||
|
||||
/**
|
||||
* Called on stock move cancel, plan and realization to update requested reserved qty and reserved
|
||||
* qty.
|
||||
*
|
||||
* @param stockMove
|
||||
*/
|
||||
void updateReservedQuantity(StockMove stockMove, int status) throws AxelorException;
|
||||
|
||||
/**
|
||||
* For lines with duplicate product, fill all the reserved qty in one line and empty the others.
|
||||
*
|
||||
* @param stockMove
|
||||
*/
|
||||
void consolidateReservedQtyInStockMoveLineByProduct(StockMove stockMove);
|
||||
|
||||
/**
|
||||
* Update requested quantity for internal or external location.
|
||||
*
|
||||
* @param stockMoveLine
|
||||
* @param fromStockLocation
|
||||
* @param toStockLocation
|
||||
* @param product
|
||||
* @param qty the quantity in stock move unit.
|
||||
* @param requestedReservedQty the requested reserved quantity in stock move unit.
|
||||
* @param toStatus
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void updateRequestedQuantityInLocations(
|
||||
StockMoveLine stockMoveLine,
|
||||
StockLocation fromStockLocation,
|
||||
StockLocation toStockLocation,
|
||||
Product product,
|
||||
BigDecimal qty,
|
||||
BigDecimal requestedReservedQty,
|
||||
int toStatus)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update location line and stock move line with computed allocated quantity, where the location
|
||||
* is {@link com.axelor.apps.stock.db.StockMove#fromStockLocation}
|
||||
*
|
||||
* @param stockMoveLine a stock move line
|
||||
* @param stockLocation a stock location
|
||||
* @param product the product of the line. If the product is not managed in stock, this method
|
||||
* does nothing.
|
||||
* @param toStatus target status for the stock move
|
||||
* @param requestedReservedQty the requested reserved quantity in stock move unit
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void updateRequestedQuantityInFromStockLocation(
|
||||
StockMoveLine stockMoveLine,
|
||||
StockLocation stockLocation,
|
||||
Product product,
|
||||
int toStatus,
|
||||
BigDecimal requestedReservedQty)
|
||||
throws AxelorException;
|
||||
/**
|
||||
* Update location line, stock move line and sale order line with computed allocated quantity,
|
||||
* where the location is {@link com.axelor.apps.stock.db.StockMove#toStockLocation}.
|
||||
*
|
||||
* @param stockMoveLine a stock move line.
|
||||
* @param stockLocation a stock location.
|
||||
* @param product the product of the line. If the product is not managed in stock, this method
|
||||
* does nothing.
|
||||
* @param toStatus target status for the stock move.
|
||||
* @param qty the quantity in stock move unit.
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void updateRequestedQuantityInToStockLocation(
|
||||
StockMoveLine stockMoveLine,
|
||||
StockLocation stockLocation,
|
||||
Product product,
|
||||
int toStatus,
|
||||
BigDecimal qty)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Allocate a given quantity in stock move lines and sale order lines corresponding to the given
|
||||
* product and stock location. The first stock move to have the reservation will be the first to
|
||||
* have the quantity allocated.
|
||||
*
|
||||
* @param qtyToAllocate the quantity available to be allocated.
|
||||
* @param stockLocation a stock location.
|
||||
* @param product a product.
|
||||
* @param stockLocationLineUnit Unit of the stock location line.
|
||||
* @return The quantity that was allocated (in stock location line unit).
|
||||
*/
|
||||
BigDecimal allocateReservedQuantityInSaleOrderLines(
|
||||
BigDecimal qtyToAllocate,
|
||||
StockLocation stockLocation,
|
||||
Product product,
|
||||
Unit stockLocationLineUnit)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* From the requested reserved quantity, return the quantity that can in fact be reserved.
|
||||
*
|
||||
* @param stockLocationLine the location line.
|
||||
* @param requestedReservedQty the quantity that can be added to the real quantity
|
||||
* @return the quantity really added.
|
||||
*/
|
||||
BigDecimal computeRealReservedQty(
|
||||
StockLocationLine stockLocationLine, BigDecimal requestedReservedQty);
|
||||
|
||||
/**
|
||||
* Update allocated quantity in sale order line with a new quantity, updating location and moves.
|
||||
* If the allocated quantity become bigger than the requested quantity, we also change the
|
||||
* requested quantity to match the allocated quantity.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @param newReservedQty
|
||||
* @throws AxelorException if there is no stock move generated or if we cannot allocate more
|
||||
* quantity.
|
||||
*/
|
||||
void updateReservedQty(SaleOrderLine saleOrderLine, BigDecimal newReservedQty)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update requested quantity in sale order line. If the requested quantity become lower than the
|
||||
* allocated quantity, we also change the allocated quantity to match the requested quantity.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @param newReservedQty
|
||||
*/
|
||||
void updateRequestedReservedQty(SaleOrderLine saleOrderLine, BigDecimal newReservedQty)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update reserved quantity in stock move lines and sale order lines from stock move lines.
|
||||
*
|
||||
* @param stockMoveLine
|
||||
* @param product
|
||||
* @param reservedQtyToAdd
|
||||
*/
|
||||
void updateReservedQuantityFromStockMoveLine(
|
||||
StockMoveLine stockMoveLine, Product product, BigDecimal reservedQtyToAdd)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update reserved quantity in stock move lines from sale order line. Manage the case of split
|
||||
* stock move lines.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @param product
|
||||
* @param newReservedQty
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void updateReservedQuantityInStockMoveLineFromSaleOrderLine(
|
||||
SaleOrderLine saleOrderLine, Product product, BigDecimal newReservedQty)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update requested reserved quantity in stock move lines from sale order line. Manage the case of
|
||||
* split stock move lines.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @param product
|
||||
* @param newReservedQty
|
||||
* @return the new allocated quantity
|
||||
* @throws AxelorException
|
||||
*/
|
||||
BigDecimal updateRequestedReservedQuantityInStockMoveLines(
|
||||
SaleOrderLine saleOrderLine, Product product, BigDecimal newReservedQty)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* In a partially realized stock move line, call this method to deallocate the quantity that will
|
||||
* be allocated to the newly generated stock move line.
|
||||
*
|
||||
* @param stockMoveLine
|
||||
* @param amountToDeallocate
|
||||
*/
|
||||
void deallocateStockMoveLineAfterSplit(StockMoveLine stockMoveLine, BigDecimal amountToDeallocate)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update requested reserved qty for stock location line from already updated stock move.
|
||||
*
|
||||
* @param stockLocationLine
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void updateRequestedReservedQty(StockLocationLine stockLocationLine) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Request quantity for a sale order line.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void requestQty(SaleOrderLine saleOrderLine) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Cancel the reservation for a sale order line.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void cancelReservation(SaleOrderLine saleOrderLine) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update reserved qty for sale order line from already updated stock move.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void updateReservedQty(SaleOrderLine saleOrderLine) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update reserved qty for stock location line from already updated stock move.
|
||||
*
|
||||
* @param stockLocationLine
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void updateReservedQty(StockLocationLine stockLocationLine) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Create a reservation and allocate as much quantity as we can.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
*/
|
||||
void allocateAll(SaleOrderLine saleOrderLine) throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,945 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.CancelReason;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Unit;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.stock.service.StockLocationLineService;
|
||||
import com.axelor.apps.supplychain.db.SupplyChainConfig;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.apps.supplychain.service.config.SupplyChainConfigService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** This is the main implementation for {@link ReservedQtyService}. */
|
||||
public class ReservedQtyServiceImpl implements ReservedQtyService {
|
||||
|
||||
protected StockLocationLineService stockLocationLineService;
|
||||
protected StockMoveLineRepository stockMoveLineRepository;
|
||||
protected UnitConversionService unitConversionService;
|
||||
protected SupplyChainConfigService supplychainConfigService;
|
||||
|
||||
@Inject
|
||||
public ReservedQtyServiceImpl(
|
||||
StockLocationLineService stockLocationLineService,
|
||||
StockMoveLineRepository stockMoveLineRepository,
|
||||
UnitConversionService unitConversionService,
|
||||
SupplyChainConfigService supplyChainConfigService) {
|
||||
this.stockLocationLineService = stockLocationLineService;
|
||||
this.stockMoveLineRepository = stockMoveLineRepository;
|
||||
this.unitConversionService = unitConversionService;
|
||||
this.supplychainConfigService = supplyChainConfigService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReservedQuantity(StockMove stockMove, int status) throws AxelorException {
|
||||
List<StockMoveLine> stockMoveLineList = stockMove.getStockMoveLineList();
|
||||
if (stockMoveLineList != null) {
|
||||
stockMoveLineList =
|
||||
stockMoveLineList
|
||||
.stream()
|
||||
.filter(
|
||||
smLine -> smLine.getProduct() != null && smLine.getProduct().getStockManaged())
|
||||
.collect(Collectors.toList());
|
||||
// check quantities in stock move lines
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
if (status == StockMoveRepository.STATUS_PLANNED) {
|
||||
changeRequestedQtyLowerThanQty(stockMoveLine);
|
||||
}
|
||||
checkRequestedAndReservedQty(stockMoveLine);
|
||||
}
|
||||
if (status == StockMoveRepository.STATUS_REALIZED) {
|
||||
consolidateReservedQtyInStockMoveLineByProduct(stockMove);
|
||||
}
|
||||
stockMoveLineList.sort(Comparator.comparing(StockMoveLine::getId));
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
BigDecimal qty = stockMoveLine.getRealQty();
|
||||
// requested quantity is quantity requested is the line subtracted by the quantity already
|
||||
// allocated
|
||||
BigDecimal requestedReservedQty =
|
||||
stockMoveLine.getRequestedReservedQty().subtract(stockMoveLine.getReservedQty());
|
||||
updateRequestedQuantityInLocations(
|
||||
stockMoveLine,
|
||||
stockMove.getFromStockLocation(),
|
||||
stockMove.getToStockLocation(),
|
||||
stockMoveLine.getProduct(),
|
||||
qty,
|
||||
requestedReservedQty,
|
||||
status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On planning, we want the requested quantity to be equal or lower to the quantity of the line.
|
||||
* So, if the requested quantity is greater than the quantity, we change it to be equal.
|
||||
*
|
||||
* @param stockMoveLine
|
||||
* @throws AxelorException
|
||||
*/
|
||||
protected void changeRequestedQtyLowerThanQty(StockMoveLine stockMoveLine)
|
||||
throws AxelorException {
|
||||
BigDecimal qty = stockMoveLine.getRealQty().max(BigDecimal.ZERO);
|
||||
BigDecimal requestedReservedQty = stockMoveLine.getRequestedReservedQty();
|
||||
if (requestedReservedQty.compareTo(qty) > 0) {
|
||||
Product product = stockMoveLine.getProduct();
|
||||
BigDecimal diffRequestedQty = requestedReservedQty.subtract(qty);
|
||||
stockMoveLine.setRequestedReservedQty(qty);
|
||||
// update in stock location line
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getOrCreateStockLocationLine(
|
||||
stockMoveLine.getStockMove().getFromStockLocation(), product);
|
||||
BigDecimal diffRequestedQuantityLocation =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(), stockLocationLine.getUnit(), diffRequestedQty, product);
|
||||
stockLocationLine.setRequestedReservedQty(
|
||||
stockLocationLine.getRequestedReservedQty().add(diffRequestedQuantityLocation));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check value of requested and reserved qty in stock move line.
|
||||
*
|
||||
* @param stockMoveLine the stock move line to be checked
|
||||
* @throws AxelorException if the quantities are negative or superior to the planned qty.
|
||||
*/
|
||||
protected void checkRequestedAndReservedQty(StockMoveLine stockMoveLine) throws AxelorException {
|
||||
BigDecimal plannedQty = stockMoveLine.getQty().max(BigDecimal.ZERO);
|
||||
BigDecimal requestedReservedQty = stockMoveLine.getRequestedReservedQty();
|
||||
BigDecimal reservedQty = stockMoveLine.getReservedQty();
|
||||
|
||||
String stockMoveLineSeq =
|
||||
stockMoveLine.getStockMove() == null
|
||||
? stockMoveLine.getId().toString()
|
||||
: stockMoveLine.getStockMove().getStockMoveSeq() + "-" + stockMoveLine.getSequence();
|
||||
|
||||
if (reservedQty.signum() < 0 || requestedReservedQty.signum() < 0) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_RESERVATION_QTY_NEGATIVE));
|
||||
}
|
||||
if (requestedReservedQty.compareTo(plannedQty) > 0) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_REQUESTED_QTY_TOO_HIGH),
|
||||
stockMoveLineSeq);
|
||||
}
|
||||
if (reservedQty.compareTo(plannedQty) > 0) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_ALLOCATED_QTY_TOO_HIGH),
|
||||
stockMoveLineSeq);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consolidateReservedQtyInStockMoveLineByProduct(StockMove stockMove) {
|
||||
if (stockMove.getStockMoveLineList() == null) {
|
||||
return;
|
||||
}
|
||||
List<Product> productList =
|
||||
stockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.map(StockMoveLine::getProduct)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(Product::getStockManaged)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
for (Product product : productList) {
|
||||
if (product != null) {
|
||||
List<StockMoveLine> stockMoveLineListToConsolidate =
|
||||
stockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.filter(stockMoveLine1 -> product.equals(stockMoveLine1.getProduct()))
|
||||
.collect(Collectors.toList());
|
||||
if (stockMoveLineListToConsolidate.size() > 1) {
|
||||
stockMoveLineListToConsolidate.sort(Comparator.comparing(StockMoveLine::getId));
|
||||
BigDecimal reservedQtySum =
|
||||
stockMoveLineListToConsolidate
|
||||
.stream()
|
||||
.map(StockMoveLine::getReservedQty)
|
||||
.reduce(BigDecimal::add)
|
||||
.orElse(BigDecimal.ZERO);
|
||||
stockMoveLineListToConsolidate.forEach(
|
||||
toConsolidateStockMoveLine ->
|
||||
toConsolidateStockMoveLine.setReservedQty(BigDecimal.ZERO));
|
||||
stockMoveLineListToConsolidate.get(0).setReservedQty(reservedQtySum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRequestedQuantityInLocations(
|
||||
StockMoveLine stockMoveLine,
|
||||
StockLocation fromStockLocation,
|
||||
StockLocation toStockLocation,
|
||||
Product product,
|
||||
BigDecimal qty,
|
||||
BigDecimal requestedReservedQty,
|
||||
int toStatus)
|
||||
throws AxelorException {
|
||||
if (fromStockLocation.getTypeSelect() != StockLocationRepository.TYPE_VIRTUAL) {
|
||||
updateRequestedQuantityInFromStockLocation(
|
||||
stockMoveLine, fromStockLocation, product, toStatus, requestedReservedQty);
|
||||
}
|
||||
if (toStockLocation.getTypeSelect() != StockLocationRepository.TYPE_VIRTUAL) {
|
||||
updateRequestedQuantityInToStockLocation(
|
||||
stockMoveLine, toStockLocation, product, toStatus, qty);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRequestedQuantityInFromStockLocation(
|
||||
StockMoveLine stockMoveLine,
|
||||
StockLocation stockLocation,
|
||||
Product product,
|
||||
int toStatus,
|
||||
BigDecimal requestedReservedQty)
|
||||
throws AxelorException {
|
||||
if (product == null || !product.getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
Unit stockMoveLineUnit = stockMoveLine.getUnit();
|
||||
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getStockLocationLine(stockLocation, product);
|
||||
if (stockLocationLine == null) {
|
||||
return;
|
||||
}
|
||||
Unit stockLocationLineUnit = stockLocationLine.getUnit();
|
||||
// the quantity that will be allocated in stock location line
|
||||
BigDecimal realReservedQty;
|
||||
|
||||
// the quantity that will be allocated in stock move line
|
||||
BigDecimal realReservedStockMoveQty;
|
||||
|
||||
// if we cancel, subtract the quantity using the previously allocated quantity.
|
||||
if (toStatus == StockMoveRepository.STATUS_CANCELED
|
||||
|| toStatus == StockMoveRepository.STATUS_REALIZED) {
|
||||
realReservedStockMoveQty = stockMoveLine.getReservedQty();
|
||||
|
||||
// convert the quantity for stock location line
|
||||
|
||||
realReservedQty =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLineUnit,
|
||||
stockLocationLineUnit,
|
||||
realReservedStockMoveQty,
|
||||
stockMoveLine.getProduct());
|
||||
|
||||
// reallocate quantity in other stock move lines
|
||||
if (isReallocatingQtyOnCancel(stockMoveLine)) {
|
||||
reallocateQty(stockMoveLine, stockLocation, stockLocationLine, product, realReservedQty);
|
||||
}
|
||||
|
||||
// no more reserved qty in stock move and sale order lines
|
||||
updateReservedQuantityFromStockMoveLine(
|
||||
stockMoveLine, product, stockMoveLine.getReservedQty().negate());
|
||||
|
||||
// update requested quantity in sale order line
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
if (saleOrderLine != null) {
|
||||
// requested quantity should never be below delivered quantity.
|
||||
if (toStatus == StockMoveRepository.STATUS_REALIZED) {
|
||||
saleOrderLine.setRequestedReservedQty(
|
||||
saleOrderLine.getRequestedReservedQty().max(saleOrderLine.getDeliveredQty()));
|
||||
} else if (!saleOrderLine.getIsQtyRequested()) {
|
||||
// if we cancel and do not want to request quantity, the requested quantity become the new
|
||||
// delivered quantity.
|
||||
saleOrderLine.setRequestedReservedQty(saleOrderLine.getDeliveredQty());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
BigDecimal requestedReservedQtyInLocation =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLineUnit, stockLocationLine.getUnit(), requestedReservedQty, product);
|
||||
realReservedQty = computeRealReservedQty(stockLocationLine, requestedReservedQtyInLocation);
|
||||
// convert back the quantity for the stock move line
|
||||
realReservedStockMoveQty =
|
||||
convertUnitWithProduct(
|
||||
stockLocationLineUnit,
|
||||
stockMoveLineUnit,
|
||||
realReservedQty,
|
||||
stockMoveLine.getProduct());
|
||||
updateReservedQuantityFromStockMoveLine(stockMoveLine, product, realReservedStockMoveQty);
|
||||
|
||||
// reallocate quantity in other stock move lines
|
||||
if (supplychainConfigService
|
||||
.getSupplyChainConfig(stockLocation.getCompany())
|
||||
.getAutoAllocateOnAllocation()) {
|
||||
BigDecimal availableQuantityInLocation =
|
||||
stockLocationLine.getCurrentQty().subtract(stockLocationLine.getReservedQty());
|
||||
availableQuantityInLocation =
|
||||
convertUnitWithProduct(
|
||||
stockLocationLineUnit, stockMoveLineUnit, availableQuantityInLocation, product);
|
||||
BigDecimal qtyRemainingToAllocate =
|
||||
availableQuantityInLocation.subtract(realReservedStockMoveQty);
|
||||
reallocateQty(
|
||||
stockMoveLine, stockLocation, stockLocationLine, product, qtyRemainingToAllocate);
|
||||
}
|
||||
}
|
||||
|
||||
updateReservedQty(stockLocationLine);
|
||||
updateRequestedReservedQty(stockLocationLine);
|
||||
checkReservedQtyStocks(stockLocationLine, stockMoveLine, toStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in the stock move for cancel reason and return the config in cancel reason.
|
||||
*
|
||||
* @param stockMoveLine
|
||||
* @return the value of the boolean field on cancel reason if found else false.
|
||||
*/
|
||||
protected boolean isReallocatingQtyOnCancel(StockMoveLine stockMoveLine) {
|
||||
return Optional.of(stockMoveLine)
|
||||
.map(StockMoveLine::getStockMove)
|
||||
.map(StockMove::getCancelReason)
|
||||
.map(CancelReason::getCancelQuantityAllocation)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRequestedQuantityInToStockLocation(
|
||||
StockMoveLine stockMoveLine,
|
||||
StockLocation stockLocation,
|
||||
Product product,
|
||||
int toStatus,
|
||||
BigDecimal qty)
|
||||
throws AxelorException {
|
||||
if (product == null || !product.getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getStockLocationLine(stockLocation, product);
|
||||
if (stockLocationLine == null) {
|
||||
return;
|
||||
}
|
||||
Company company = stockLocationLine.getStockLocation().getCompany();
|
||||
SupplyChainConfig supplyChainConfig = supplychainConfigService.getSupplyChainConfig(company);
|
||||
if (toStatus == StockMoveRepository.STATUS_REALIZED
|
||||
&& supplyChainConfig.getAutoAllocateOnReceipt()) {
|
||||
reallocateQty(stockMoveLine, stockLocation, stockLocationLine, product, qty);
|
||||
}
|
||||
updateRequestedReservedQty(stockLocationLine);
|
||||
checkReservedQtyStocks(stockLocationLine, stockMoveLine, toStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reallocate quantity in stock location line after entry into storage.
|
||||
*
|
||||
* @param stockMoveLine
|
||||
* @param stockLocation
|
||||
* @param stockLocationLine
|
||||
* @param product
|
||||
* @param qty the quantity in stock move line unit.
|
||||
* @throws AxelorException
|
||||
*/
|
||||
protected void reallocateQty(
|
||||
StockMoveLine stockMoveLine,
|
||||
StockLocation stockLocation,
|
||||
StockLocationLine stockLocationLine,
|
||||
Product product,
|
||||
BigDecimal qty)
|
||||
throws AxelorException {
|
||||
|
||||
Unit stockMoveLineUnit = stockMoveLine.getUnit();
|
||||
Unit stockLocationLineUnit = stockLocationLine.getUnit();
|
||||
|
||||
BigDecimal stockLocationQty =
|
||||
convertUnitWithProduct(stockMoveLineUnit, stockLocationLineUnit, qty, product);
|
||||
// the quantity that will be allocated in stock location line
|
||||
BigDecimal realReservedQty;
|
||||
|
||||
// the quantity that will be allocated in stock move line
|
||||
BigDecimal leftToAllocate =
|
||||
stockLocationLine.getRequestedReservedQty().subtract(stockLocationLine.getReservedQty());
|
||||
realReservedQty = stockLocationQty.min(leftToAllocate);
|
||||
|
||||
allocateReservedQuantityInSaleOrderLines(
|
||||
realReservedQty, stockLocation, product, stockLocationLineUnit, Optional.of(stockMoveLine));
|
||||
updateReservedQty(stockLocationLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal allocateReservedQuantityInSaleOrderLines(
|
||||
BigDecimal qtyToAllocate,
|
||||
StockLocation stockLocation,
|
||||
Product product,
|
||||
Unit stockLocationLineUnit)
|
||||
throws AxelorException {
|
||||
if (product == null || !product.getStockManaged()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return allocateReservedQuantityInSaleOrderLines(
|
||||
qtyToAllocate, stockLocation, product, stockLocationLineUnit, Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* The new parameter allocated stock move line is used if we are allocating a stock move line.
|
||||
* This method will reallocate the lines with the same stock move (and the same product) before
|
||||
* other stock move lines.
|
||||
*
|
||||
* <p>We are using an optional because in the basic use of the method, the argument is empty.
|
||||
*/
|
||||
protected BigDecimal allocateReservedQuantityInSaleOrderLines(
|
||||
BigDecimal qtyToAllocate,
|
||||
StockLocation stockLocation,
|
||||
Product product,
|
||||
Unit stockLocationLineUnit,
|
||||
Optional<StockMoveLine> allocatedStockMoveLine)
|
||||
throws AxelorException {
|
||||
List<StockMoveLine> stockMoveLineListToAllocate =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.stockMove.fromStockLocation.id = :stockLocationId "
|
||||
+ "AND self.product.id = :productId "
|
||||
+ "AND self.stockMove.statusSelect = :planned "
|
||||
+ "AND self.stockMove.reservationDateTime IS NOT NULL "
|
||||
+ "AND self.reservedQty < self.requestedReservedQty")
|
||||
.bind("stockLocationId", stockLocation.getId())
|
||||
.bind("productId", product.getId())
|
||||
.bind("planned", StockMoveRepository.STATUS_PLANNED)
|
||||
.order("stockMove.reservationDateTime")
|
||||
.order("stockMove.estimatedDate")
|
||||
.fetch();
|
||||
|
||||
if (allocatedStockMoveLine.isPresent()) {
|
||||
// put stock move lines with the same stock move on the beginning of the list.
|
||||
stockMoveLineListToAllocate.sort(
|
||||
// Note: this comparator imposes orderings that are inconsistent with equals.
|
||||
(sml1, sml2) -> {
|
||||
if (sml1.getStockMove().equals(sml2.getStockMove())) {
|
||||
return 0;
|
||||
} else if (sml1.getStockMove().equals(allocatedStockMoveLine.get().getStockMove())) {
|
||||
return -1;
|
||||
} else if (sml2.getStockMove().equals(allocatedStockMoveLine.get().getStockMove())) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
BigDecimal leftQtyToAllocate = qtyToAllocate;
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineListToAllocate) {
|
||||
BigDecimal leftQtyToAllocateStockMove =
|
||||
convertUnitWithProduct(
|
||||
stockLocationLineUnit, stockMoveLine.getUnit(), leftQtyToAllocate, product);
|
||||
BigDecimal neededQtyToAllocate =
|
||||
stockMoveLine.getRequestedReservedQty().subtract(stockMoveLine.getReservedQty());
|
||||
BigDecimal allocatedStockMoveQty = leftQtyToAllocateStockMove.min(neededQtyToAllocate);
|
||||
|
||||
BigDecimal allocatedQty =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(), stockLocationLineUnit, allocatedStockMoveQty, product);
|
||||
|
||||
// update reserved qty in stock move line and sale order line
|
||||
updateReservedQuantityFromStockMoveLine(stockMoveLine, product, allocatedStockMoveQty);
|
||||
// update left qty to allocate
|
||||
leftQtyToAllocate = leftQtyToAllocate.subtract(allocatedQty);
|
||||
}
|
||||
|
||||
return qtyToAllocate.subtract(leftQtyToAllocate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReservedQuantityFromStockMoveLine(
|
||||
StockMoveLine stockMoveLine, Product product, BigDecimal reservedQtyToAdd)
|
||||
throws AxelorException {
|
||||
if (product == null || !product.getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
stockMoveLine.setReservedQty(stockMoveLine.getReservedQty().add(reservedQtyToAdd));
|
||||
if (saleOrderLine != null) {
|
||||
updateReservedQty(saleOrderLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReservedQuantityInStockMoveLineFromSaleOrderLine(
|
||||
SaleOrderLine saleOrderLine, Product product, BigDecimal newReservedQty)
|
||||
throws AxelorException {
|
||||
if (product == null || !product.getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<StockMoveLine> stockMoveLineList = getPlannedStockMoveLines(saleOrderLine);
|
||||
BigDecimal allocatedQty = newReservedQty;
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
BigDecimal stockMoveAllocatedQty =
|
||||
convertUnitWithProduct(
|
||||
saleOrderLine.getUnit(), stockMoveLine.getUnit(), allocatedQty, product);
|
||||
BigDecimal reservedQtyInStockMoveLine =
|
||||
stockMoveLine.getRequestedReservedQty().min(stockMoveAllocatedQty);
|
||||
stockMoveLine.setReservedQty(reservedQtyInStockMoveLine);
|
||||
BigDecimal saleOrderReservedQtyInStockMoveLine =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(),
|
||||
saleOrderLine.getUnit(),
|
||||
reservedQtyInStockMoveLine,
|
||||
product);
|
||||
allocatedQty = allocatedQty.subtract(saleOrderReservedQtyInStockMoveLine);
|
||||
}
|
||||
updateReservedQty(saleOrderLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal updateRequestedReservedQuantityInStockMoveLines(
|
||||
SaleOrderLine saleOrderLine, Product product, BigDecimal newReservedQty)
|
||||
throws AxelorException {
|
||||
if (product == null || !product.getStockManaged()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
List<StockMoveLine> stockMoveLineList = getPlannedStockMoveLines(saleOrderLine);
|
||||
BigDecimal deliveredQty = saleOrderLine.getDeliveredQty();
|
||||
BigDecimal allocatedRequestedQty = newReservedQty.subtract(deliveredQty);
|
||||
if (allocatedRequestedQty.signum() < 0) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_REQUESTED_QTY_TOO_LOW));
|
||||
}
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
BigDecimal stockMoveRequestedQty =
|
||||
convertUnitWithProduct(
|
||||
saleOrderLine.getUnit(), stockMoveLine.getUnit(), allocatedRequestedQty, product);
|
||||
BigDecimal requestedQtyInStockMoveLine = stockMoveLine.getQty().min(stockMoveRequestedQty);
|
||||
stockMoveLine.setRequestedReservedQty(requestedQtyInStockMoveLine);
|
||||
BigDecimal saleOrderRequestedQtyInStockMoveLine =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(),
|
||||
saleOrderLine.getUnit(),
|
||||
requestedQtyInStockMoveLine,
|
||||
product);
|
||||
allocatedRequestedQty = allocatedRequestedQty.subtract(saleOrderRequestedQtyInStockMoveLine);
|
||||
}
|
||||
saleOrderLine.setRequestedReservedQty(newReservedQty.subtract(allocatedRequestedQty));
|
||||
return saleOrderLine.getRequestedReservedQty().subtract(deliveredQty);
|
||||
}
|
||||
|
||||
protected List<StockMoveLine> getPlannedStockMoveLines(SaleOrderLine saleOrderLine) {
|
||||
return stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.saleOrderLine.id = :saleOrderLineId "
|
||||
+ "AND self.stockMove.statusSelect = :planned")
|
||||
.bind("saleOrderLineId", saleOrderLine.getId())
|
||||
.bind("planned", StockMoveRepository.STATUS_PLANNED)
|
||||
.order("id")
|
||||
.fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocated qty cannot be greater than available qty.
|
||||
*
|
||||
* @param stockLocationLine
|
||||
* @param stockMoveLine
|
||||
* @throws AxelorException
|
||||
*/
|
||||
protected void checkReservedQtyStocks(
|
||||
StockLocationLine stockLocationLine, StockMoveLine stockMoveLine, int toStatus)
|
||||
throws AxelorException {
|
||||
|
||||
if (((toStatus == StockMoveRepository.STATUS_REALIZED)
|
||||
|| toStatus == StockMoveRepository.STATUS_CANCELED)
|
||||
&& stockLocationLine.getReservedQty().compareTo(stockLocationLine.getCurrentQty()) > 0) {
|
||||
BigDecimal convertedAvailableQtyInStockMove =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(),
|
||||
stockLocationLine.getUnit(),
|
||||
stockMoveLine.getRealQty(),
|
||||
stockLocationLine.getProduct());
|
||||
BigDecimal convertedReservedQtyInStockMove =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(),
|
||||
stockLocationLine.getUnit(),
|
||||
stockMoveLine.getReservedQty(),
|
||||
stockLocationLine.getProduct());
|
||||
|
||||
BigDecimal availableQty =
|
||||
convertedAvailableQtyInStockMove
|
||||
.add(stockLocationLine.getCurrentQty())
|
||||
.subtract(convertedReservedQtyInStockMove.add(stockLocationLine.getReservedQty()));
|
||||
BigDecimal neededQty =
|
||||
convertedAvailableQtyInStockMove.subtract(convertedReservedQtyInStockMove);
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.LOCATION_LINE_NOT_ENOUGH_AVAILABLE_QTY),
|
||||
stockLocationLine.getProduct().getFullName(),
|
||||
availableQty,
|
||||
neededQty);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal computeRealReservedQty(
|
||||
StockLocationLine stockLocationLine, BigDecimal requestedReservedQty) {
|
||||
|
||||
BigDecimal qtyLeftToBeAllocated =
|
||||
stockLocationLine.getCurrentQty().subtract(stockLocationLine.getReservedQty());
|
||||
return qtyLeftToBeAllocated.min(requestedReservedQty).max(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void updateReservedQty(SaleOrderLine saleOrderLine, BigDecimal newReservedQty)
|
||||
throws AxelorException {
|
||||
if (saleOrderLine.getProduct() == null || !saleOrderLine.getProduct().getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
StockMoveLine stockMoveLine = getPlannedStockMoveLine(saleOrderLine);
|
||||
|
||||
checkBeforeUpdatingQties(stockMoveLine, newReservedQty);
|
||||
if (Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getBlockDeallocationOnAvailabilityRequest()) {
|
||||
checkAvailabilityRequest(stockMoveLine, newReservedQty, false);
|
||||
}
|
||||
|
||||
BigDecimal newRequestedReservedQty = newReservedQty.add(saleOrderLine.getDeliveredQty());
|
||||
// update requested reserved qty
|
||||
if (newRequestedReservedQty.compareTo(saleOrderLine.getRequestedReservedQty()) > 0
|
||||
&& newReservedQty.compareTo(BigDecimal.ZERO) > 0) {
|
||||
requestQty(saleOrderLine);
|
||||
}
|
||||
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getOrCreateStockLocationLine(
|
||||
stockMoveLine.getStockMove().getFromStockLocation(), stockMoveLine.getProduct());
|
||||
BigDecimal availableQtyToBeReserved =
|
||||
stockLocationLine.getCurrentQty().subtract(stockLocationLine.getReservedQty());
|
||||
BigDecimal diffReservedQuantity = newReservedQty.subtract(saleOrderLine.getReservedQty());
|
||||
Product product = stockMoveLine.getProduct();
|
||||
BigDecimal diffReservedQuantityLocation =
|
||||
convertUnitWithProduct(
|
||||
saleOrderLine.getUnit(), stockLocationLine.getUnit(), diffReservedQuantity, product);
|
||||
if (availableQtyToBeReserved.compareTo(diffReservedQuantityLocation) < 0) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_QTY_NOT_AVAILABLE));
|
||||
}
|
||||
// update in stock move line and sale order line
|
||||
updateReservedQuantityInStockMoveLineFromSaleOrderLine(
|
||||
saleOrderLine, stockMoveLine.getProduct(), newReservedQty);
|
||||
|
||||
// update in stock location line
|
||||
updateReservedQty(stockLocationLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void updateRequestedReservedQty(SaleOrderLine saleOrderLine, BigDecimal newReservedQty)
|
||||
throws AxelorException {
|
||||
if (saleOrderLine.getProduct() == null || !saleOrderLine.getProduct().getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StockMoveLine stockMoveLine = getPlannedStockMoveLine(saleOrderLine);
|
||||
|
||||
if (stockMoveLine == null) {
|
||||
// only change requested quantity in sale order line
|
||||
saleOrderLine.setRequestedReservedQty(newReservedQty);
|
||||
return;
|
||||
}
|
||||
|
||||
checkBeforeUpdatingQties(stockMoveLine, newReservedQty);
|
||||
if (Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getBlockDeallocationOnAvailabilityRequest()) {
|
||||
checkAvailabilityRequest(stockMoveLine, newReservedQty, true);
|
||||
}
|
||||
|
||||
BigDecimal diffReservedQuantity =
|
||||
newReservedQty.subtract(saleOrderLine.getRequestedReservedQty());
|
||||
|
||||
// update in stock move line and sale order line
|
||||
BigDecimal newAllocatedQty =
|
||||
updateRequestedReservedQuantityInStockMoveLines(
|
||||
saleOrderLine, stockMoveLine.getProduct(), newReservedQty);
|
||||
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getOrCreateStockLocationLine(
|
||||
stockMoveLine.getStockMove().getFromStockLocation(), stockMoveLine.getProduct());
|
||||
|
||||
Product product = stockMoveLine.getProduct();
|
||||
// update in stock location line
|
||||
BigDecimal diffReservedQuantityLocation =
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(), stockLocationLine.getUnit(), diffReservedQuantity, product);
|
||||
stockLocationLine.setRequestedReservedQty(
|
||||
stockLocationLine.getRequestedReservedQty().add(diffReservedQuantityLocation));
|
||||
|
||||
// update reserved qty
|
||||
if (newAllocatedQty.compareTo(saleOrderLine.getReservedQty()) < 0) {
|
||||
updateReservedQty(saleOrderLine, newAllocatedQty);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* StockMoveLine cannot be null and quantity cannot be negative. Throws {@link AxelorException} if
|
||||
* these conditions are false.
|
||||
*/
|
||||
protected void checkBeforeUpdatingQties(StockMoveLine stockMoveLine, BigDecimal qty)
|
||||
throws AxelorException {
|
||||
if (stockMoveLine == null) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_NO_STOCK_MOVE));
|
||||
}
|
||||
if (qty.signum() < 0) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_RESERVATION_QTY_NEGATIVE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the stock move is planned and with an availability request, we cannot lower its quantity.
|
||||
*
|
||||
* @param stockMoveLine a stock move line.
|
||||
* @param qty the quantity that can be requested or reserved.
|
||||
* @param isRequested whether the quantity is requested or reserved.
|
||||
* @throws AxelorException if we try to change the quantity of a stock move with availability
|
||||
* request equals to true.
|
||||
*/
|
||||
protected void checkAvailabilityRequest(
|
||||
StockMoveLine stockMoveLine, BigDecimal qty, boolean isRequested) throws AxelorException {
|
||||
BigDecimal stockMoveLineQty =
|
||||
isRequested ? stockMoveLine.getRequestedReservedQty() : stockMoveLine.getReservedQty();
|
||||
if (stockMoveLine.getStockMove().getAvailabilityRequest()
|
||||
&& stockMoveLineQty.compareTo(qty) > 0) {
|
||||
throw new AxelorException(
|
||||
stockMoveLine.getStockMove(),
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_AVAILABILITY_REQUEST));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deallocateStockMoveLineAfterSplit(
|
||||
StockMoveLine stockMoveLine, BigDecimal amountToDeallocate) throws AxelorException {
|
||||
|
||||
if (stockMoveLine.getProduct() == null || !stockMoveLine.getProduct().getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
// deallocate in sale order line
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
if (saleOrderLine != null) {
|
||||
updateReservedQty(saleOrderLine);
|
||||
}
|
||||
// deallocate in stock location line
|
||||
if (stockMoveLine.getStockMove() != null) {
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getStockLocationLine(
|
||||
stockMoveLine.getStockMove().getFromStockLocation(), stockMoveLine.getProduct());
|
||||
if (stockLocationLine != null) {
|
||||
updateReservedQty(stockLocationLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected StockMoveLine getPlannedStockMoveLine(SaleOrderLine saleOrderLine) {
|
||||
return stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.saleOrderLine = :saleOrderLine " + "AND self.stockMove.statusSelect = :planned")
|
||||
.bind("saleOrderLine", saleOrderLine)
|
||||
.bind("planned", StockMoveRepository.STATUS_PLANNED)
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
/** Convert but with null check. Return start value if one unit is null. */
|
||||
private BigDecimal convertUnitWithProduct(
|
||||
Unit startUnit, Unit endUnit, BigDecimal qtyToConvert, Product product)
|
||||
throws AxelorException {
|
||||
if (startUnit != null && !startUnit.equals(endUnit)) {
|
||||
return unitConversionService.convert(
|
||||
startUnit, endUnit, qtyToConvert, qtyToConvert.scale(), product);
|
||||
} else {
|
||||
return qtyToConvert;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRequestedReservedQty(StockLocationLine stockLocationLine)
|
||||
throws AxelorException {
|
||||
// compute from stock move lines
|
||||
List<StockMoveLine> stockMoveLineList =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.product.id = :productId "
|
||||
+ "AND self.stockMove.fromStockLocation.id = :stockLocationId "
|
||||
+ "AND self.stockMove.statusSelect = :planned")
|
||||
.bind("productId", stockLocationLine.getProduct().getId())
|
||||
.bind("stockLocationId", stockLocationLine.getStockLocation().getId())
|
||||
.bind("planned", StockMoveRepository.STATUS_PLANNED)
|
||||
.fetch();
|
||||
BigDecimal requestedReservedQty = BigDecimal.ZERO;
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
requestedReservedQty =
|
||||
requestedReservedQty.add(
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(),
|
||||
stockLocationLine.getUnit(),
|
||||
stockMoveLine.getRequestedReservedQty(),
|
||||
stockLocationLine.getProduct()));
|
||||
}
|
||||
stockLocationLine.setRequestedReservedQty(requestedReservedQty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReservedQty(SaleOrderLine saleOrderLine) throws AxelorException {
|
||||
// compute from stock move lines
|
||||
List<StockMoveLine> stockMoveLineList =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.saleOrderLine.id = :saleOrderLineId "
|
||||
+ "AND self.stockMove.statusSelect = :planned")
|
||||
.bind("saleOrderLineId", saleOrderLine.getId())
|
||||
.bind("planned", StockMoveRepository.STATUS_PLANNED)
|
||||
.fetch();
|
||||
BigDecimal reservedQty = BigDecimal.ZERO;
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
reservedQty =
|
||||
reservedQty.add(
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(),
|
||||
saleOrderLine.getUnit(),
|
||||
stockMoveLine.getReservedQty(),
|
||||
saleOrderLine.getProduct()));
|
||||
}
|
||||
saleOrderLine.setReservedQty(reservedQty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReservedQty(StockLocationLine stockLocationLine) throws AxelorException {
|
||||
// compute from stock move lines
|
||||
List<StockMoveLine> stockMoveLineList =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.product.id = :productId "
|
||||
+ "AND self.stockMove.fromStockLocation.id = :stockLocationId "
|
||||
+ "AND self.stockMove.statusSelect = :planned")
|
||||
.bind("productId", stockLocationLine.getProduct().getId())
|
||||
.bind("stockLocationId", stockLocationLine.getStockLocation().getId())
|
||||
.bind("planned", StockMoveRepository.STATUS_PLANNED)
|
||||
.fetch();
|
||||
BigDecimal reservedQty = BigDecimal.ZERO;
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
reservedQty =
|
||||
reservedQty.add(
|
||||
convertUnitWithProduct(
|
||||
stockMoveLine.getUnit(),
|
||||
stockLocationLine.getUnit(),
|
||||
stockMoveLine.getReservedQty(),
|
||||
stockLocationLine.getProduct()));
|
||||
}
|
||||
stockLocationLine.setReservedQty(reservedQty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allocateAll(SaleOrderLine saleOrderLine) throws AxelorException {
|
||||
if (saleOrderLine.getProduct() == null || !saleOrderLine.getProduct().getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
// request the maximum quantity
|
||||
requestQty(saleOrderLine);
|
||||
StockMoveLine stockMoveLine = getPlannedStockMoveLine(saleOrderLine);
|
||||
|
||||
if (stockMoveLine == null) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_NO_STOCK_MOVE));
|
||||
}
|
||||
// search for the maximum quantity that can be allocated.
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getOrCreateStockLocationLine(
|
||||
stockMoveLine.getStockMove().getFromStockLocation(), stockMoveLine.getProduct());
|
||||
BigDecimal availableQtyToBeReserved =
|
||||
stockLocationLine.getCurrentQty().subtract(stockLocationLine.getReservedQty());
|
||||
Product product = stockMoveLine.getProduct();
|
||||
BigDecimal availableQtyToBeReservedSaleOrderLine =
|
||||
convertUnitWithProduct(
|
||||
saleOrderLine.getUnit(),
|
||||
stockLocationLine.getUnit(),
|
||||
availableQtyToBeReserved,
|
||||
product)
|
||||
.add(saleOrderLine.getReservedQty());
|
||||
BigDecimal qtyThatWillBeAllocated =
|
||||
saleOrderLine.getQty().min(availableQtyToBeReservedSaleOrderLine);
|
||||
|
||||
// allocate it
|
||||
if (qtyThatWillBeAllocated.compareTo(saleOrderLine.getReservedQty()) > 0) {
|
||||
updateReservedQty(saleOrderLine, qtyThatWillBeAllocated);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void requestQty(SaleOrderLine saleOrderLine) throws AxelorException {
|
||||
if (saleOrderLine.getProduct() == null || !saleOrderLine.getProduct().getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
saleOrderLine.setIsQtyRequested(true);
|
||||
if (saleOrderLine.getQty().signum() < 0) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_LINE_REQUEST_QTY_NEGATIVE));
|
||||
}
|
||||
this.updateRequestedReservedQty(saleOrderLine, saleOrderLine.getQty());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void cancelReservation(SaleOrderLine saleOrderLine) throws AxelorException {
|
||||
if (saleOrderLine.getProduct() == null || !saleOrderLine.getProduct().getStockManaged()) {
|
||||
return;
|
||||
}
|
||||
saleOrderLine.setIsQtyRequested(false);
|
||||
this.updateRequestedReservedQty(saleOrderLine, saleOrderLine.getDeliveredQty());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.service.app.AppAccountService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeServiceImpl;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderLineService;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderLineTaxService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SaleOrderComputeServiceSupplychainImpl extends SaleOrderComputeServiceImpl {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Inject
|
||||
public SaleOrderComputeServiceSupplychainImpl(
|
||||
SaleOrderLineService saleOrderLineService, SaleOrderLineTaxService saleOrderLineTaxService) {
|
||||
|
||||
super(saleOrderLineService, saleOrderLineTaxService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void _computeSaleOrder(SaleOrder saleOrder) throws AxelorException {
|
||||
|
||||
super._computeSaleOrder(saleOrder);
|
||||
|
||||
int maxDelay = 0;
|
||||
|
||||
if (saleOrder.getSaleOrderLineList() != null && !saleOrder.getSaleOrderLineList().isEmpty()) {
|
||||
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
|
||||
|
||||
if ((saleOrderLine.getSaleSupplySelect() == SaleOrderLineRepository.SALE_SUPPLY_PRODUCE
|
||||
|| saleOrderLine.getSaleSupplySelect()
|
||||
== SaleOrderLineRepository.SALE_SUPPLY_PURCHASE)) {
|
||||
maxDelay =
|
||||
Integer.max(
|
||||
maxDelay,
|
||||
saleOrderLine.getStandardDelay() == null ? 0 : saleOrderLine.getStandardDelay());
|
||||
}
|
||||
}
|
||||
}
|
||||
saleOrder.setStandardDelay(maxDelay);
|
||||
|
||||
if (Beans.get(AppAccountService.class).getAppAccount().getManageAdvancePaymentInvoice()) {
|
||||
saleOrder.setAdvanceTotal(computeTotalInvoiceAdvancePayment(saleOrder));
|
||||
}
|
||||
Beans.get(SaleOrderServiceSupplychainImpl.class)
|
||||
.updateAmountToBeSpreadOverTheTimetable(saleOrder);
|
||||
}
|
||||
|
||||
protected BigDecimal computeTotalInvoiceAdvancePayment(SaleOrder saleOrder) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
|
||||
if (saleOrder.getId() == null) {
|
||||
return total;
|
||||
}
|
||||
|
||||
List<Invoice> advancePaymentInvoiceList =
|
||||
Beans.get(InvoiceRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.saleOrder.id = :saleOrderId AND self.operationSubTypeSelect = :operationSubTypeSelect")
|
||||
.bind("saleOrderId", saleOrder.getId())
|
||||
.bind("operationSubTypeSelect", InvoiceRepository.OPERATION_SUB_TYPE_ADVANCE)
|
||||
.fetch();
|
||||
if (advancePaymentInvoiceList == null || advancePaymentInvoiceList.isEmpty()) {
|
||||
return total;
|
||||
}
|
||||
for (Invoice advance : advancePaymentInvoiceList) {
|
||||
total = total.add(advance.getAmountPaid());
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.service.config.AccountConfigService;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Currency;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.PriceList;
|
||||
import com.axelor.apps.base.service.PartnerService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.sale.service.app.AppSaleService;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeService;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderCreateServiceImpl;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderService;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.service.StockLocationService;
|
||||
import com.axelor.auth.AuthUtils;
|
||||
import com.axelor.auth.db.User;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.team.db.Team;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SaleOrderCreateServiceSupplychainImpl extends SaleOrderCreateServiceImpl {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected AccountConfigService accountConfigService;
|
||||
protected SaleOrderRepository saleOrderRepository;
|
||||
|
||||
@Inject
|
||||
public SaleOrderCreateServiceSupplychainImpl(
|
||||
PartnerService partnerService,
|
||||
SaleOrderRepository saleOrderRepo,
|
||||
AppSaleService appSaleService,
|
||||
SaleOrderService saleOrderService,
|
||||
SaleOrderComputeService saleOrderComputeService,
|
||||
AccountConfigService accountConfigService,
|
||||
SaleOrderRepository saleOrderRepository) {
|
||||
|
||||
super(partnerService, saleOrderRepo, appSaleService, saleOrderService, saleOrderComputeService);
|
||||
|
||||
this.accountConfigService = accountConfigService;
|
||||
this.saleOrderRepository = saleOrderRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaleOrder createSaleOrder(
|
||||
User salemanUser,
|
||||
Company company,
|
||||
Partner contactPartner,
|
||||
Currency currency,
|
||||
LocalDate deliveryDate,
|
||||
String internalReference,
|
||||
String externalReference,
|
||||
LocalDate orderDate,
|
||||
PriceList priceList,
|
||||
Partner clientPartner,
|
||||
Team team)
|
||||
throws AxelorException {
|
||||
return createSaleOrder(
|
||||
salemanUser,
|
||||
company,
|
||||
contactPartner,
|
||||
currency,
|
||||
deliveryDate,
|
||||
internalReference,
|
||||
externalReference,
|
||||
null,
|
||||
orderDate,
|
||||
priceList,
|
||||
clientPartner,
|
||||
team);
|
||||
}
|
||||
|
||||
public SaleOrder createSaleOrder(
|
||||
User salemanUser,
|
||||
Company company,
|
||||
Partner contactPartner,
|
||||
Currency currency,
|
||||
LocalDate deliveryDate,
|
||||
String internalReference,
|
||||
String externalReference,
|
||||
StockLocation stockLocation,
|
||||
LocalDate orderDate,
|
||||
PriceList priceList,
|
||||
Partner clientPartner,
|
||||
Team team)
|
||||
throws AxelorException {
|
||||
|
||||
logger.debug(
|
||||
"Création d'une commande fournisseur : Société = {}, Reference externe = {}, Client = {}",
|
||||
company.getName(),
|
||||
externalReference,
|
||||
clientPartner.getFullName());
|
||||
|
||||
SaleOrder saleOrder =
|
||||
super.createSaleOrder(
|
||||
salemanUser,
|
||||
company,
|
||||
contactPartner,
|
||||
currency,
|
||||
deliveryDate,
|
||||
internalReference,
|
||||
externalReference,
|
||||
orderDate,
|
||||
priceList,
|
||||
clientPartner,
|
||||
team);
|
||||
|
||||
if (stockLocation == null) {
|
||||
stockLocation = Beans.get(StockLocationService.class).getPickupDefaultStockLocation(company);
|
||||
}
|
||||
|
||||
saleOrder.setStockLocation(stockLocation);
|
||||
|
||||
saleOrder.setPaymentMode(clientPartner.getInPaymentMode());
|
||||
saleOrder.setPaymentCondition(clientPartner.getPaymentCondition());
|
||||
|
||||
if (saleOrder.getPaymentMode() == null) {
|
||||
saleOrder.setPaymentMode(
|
||||
this.accountConfigService.getAccountConfig(company).getInPaymentMode());
|
||||
}
|
||||
|
||||
if (saleOrder.getPaymentCondition() == null) {
|
||||
saleOrder.setPaymentCondition(
|
||||
this.accountConfigService.getAccountConfig(company).getDefPaymentCondition());
|
||||
}
|
||||
|
||||
saleOrder.setShipmentMode(clientPartner.getShipmentMode());
|
||||
saleOrder.setFreightCarrierMode(clientPartner.getFreightCarrierMode());
|
||||
|
||||
return saleOrder;
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public SaleOrder mergeSaleOrders(
|
||||
List<SaleOrder> saleOrderList,
|
||||
Currency currency,
|
||||
Partner clientPartner,
|
||||
Company company,
|
||||
StockLocation stockLocation,
|
||||
Partner contactPartner,
|
||||
PriceList priceList,
|
||||
Team team)
|
||||
throws AxelorException {
|
||||
String numSeq = "";
|
||||
String externalRef = "";
|
||||
for (SaleOrder saleOrderLocal : saleOrderList) {
|
||||
if (!numSeq.isEmpty()) {
|
||||
numSeq += "-";
|
||||
}
|
||||
numSeq += saleOrderLocal.getSaleOrderSeq();
|
||||
|
||||
if (!externalRef.isEmpty()) {
|
||||
externalRef += "|";
|
||||
}
|
||||
if (saleOrderLocal.getExternalReference() != null) {
|
||||
externalRef += saleOrderLocal.getExternalReference();
|
||||
}
|
||||
}
|
||||
|
||||
SaleOrder saleOrderMerged =
|
||||
this.createSaleOrder(
|
||||
AuthUtils.getUser(),
|
||||
company,
|
||||
contactPartner,
|
||||
currency,
|
||||
null,
|
||||
numSeq,
|
||||
externalRef,
|
||||
stockLocation,
|
||||
LocalDate.now(),
|
||||
priceList,
|
||||
clientPartner,
|
||||
team);
|
||||
|
||||
super.attachToNewSaleOrder(saleOrderList, saleOrderMerged);
|
||||
|
||||
saleOrderComputeService.computeSaleOrder(saleOrderMerged);
|
||||
|
||||
saleOrderRepository.save(saleOrderMerged);
|
||||
|
||||
super.removeOldSaleOrders(saleOrderList);
|
||||
|
||||
return saleOrderMerged;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.InvoiceLine;
|
||||
import com.axelor.apps.account.db.PaymentCondition;
|
||||
import com.axelor.apps.account.db.PaymentMode;
|
||||
import com.axelor.apps.account.service.invoice.generator.InvoiceGenerator;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Currency;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.PriceList;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.SaleOrderLineTax;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface SaleOrderInvoiceService {
|
||||
|
||||
/**
|
||||
* Generate an invoice from a sale order. call {@link
|
||||
* SaleOrderInvoiceService#createInvoice(SaleOrder)} to create the invoice.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
Invoice generateInvoice(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Generate an invoice from a sale order. call {@link
|
||||
* SaleOrderInvoiceService#createInvoice(SaleOrder, List)} to create the invoice.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @param saleOrderLinesSelected
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
Invoice generateInvoice(SaleOrder saleOrder, List<SaleOrderLine> saleOrderLinesSelected)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Generate an invoice from a sale order. call {@link
|
||||
* SaleOrderInvoiceService#createInvoice(SaleOrder, List, Map)} to create the invoice.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @param saleOrderLinesSelected
|
||||
* @param qtyToInvoiceMap
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
Invoice generateInvoice(
|
||||
SaleOrder saleOrder,
|
||||
List<SaleOrderLine> saleOrderLinesSelected,
|
||||
Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Generate invoice from the sale order wizard.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @param operationSelect
|
||||
* @param amount
|
||||
* @param isPercent
|
||||
* @param qtyToInvoiceMap
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
Invoice generateInvoice(
|
||||
SaleOrder saleOrder,
|
||||
int operationSelect,
|
||||
BigDecimal amount,
|
||||
boolean isPercent,
|
||||
Map<Long, BigDecimal> qtyToInvoiceMap,
|
||||
List<Long> timetableIdList)
|
||||
throws AxelorException;
|
||||
|
||||
SaleOrder fillSaleOrder(SaleOrder saleOrder, Invoice invoice);
|
||||
|
||||
/**
|
||||
* Create invoice from a sale order.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
Invoice createInvoice(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Create invoice from a sale order.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
Invoice createInvoice(SaleOrder saleOrder, List<SaleOrderLine> saleOrderLineList)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Create an invoice.
|
||||
*
|
||||
* @param saleOrder the sale order used to create the invoice
|
||||
* @param saleOrderLineList the lines that will be used to create the invoice lines
|
||||
* @param qtyToInvoiceMap the quantity used to create the invoice lines
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
Invoice createInvoice(
|
||||
SaleOrder saleOrder,
|
||||
List<SaleOrderLine> saleOrderLineList,
|
||||
Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Allows to create an advance payment from a sale order. Creates a one line invoice with the
|
||||
* advance payment product.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @param amountToInvoice
|
||||
* @param isPercent
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
Invoice generateAdvancePayment(SaleOrder saleOrder, BigDecimal amountToInvoice, boolean isPercent)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Create sale order lines from tax line a percent to invoice, and a product. Called by {@link
|
||||
* #generatePartialInvoice} and {@link #generateAdvancePayment}
|
||||
*
|
||||
* @param invoice
|
||||
* @param taxLineList
|
||||
* @param invoicingProduct
|
||||
* @param percentToInvoice
|
||||
* @return
|
||||
*/
|
||||
List<InvoiceLine> createInvoiceLinesFromTax(
|
||||
Invoice invoice,
|
||||
List<SaleOrderLineTax> taxLineList,
|
||||
Product invoicingProduct,
|
||||
BigDecimal percentToInvoice)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Allows to create an invoice from lines with given quantity in sale order. This function checks
|
||||
* that the map contains at least one value and convert percent to quantity if necessary.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @param qtyToInvoiceMap This map links the sale order lines with desired quantities.
|
||||
* @param isPercent
|
||||
* @return the generated invoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
Invoice generateInvoiceFromLines(
|
||||
SaleOrder saleOrder, Map<Long, BigDecimal> qtyToInvoiceMap, boolean isPercent)
|
||||
throws AxelorException;
|
||||
|
||||
InvoiceGenerator createInvoiceGenerator(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
InvoiceGenerator createInvoiceGenerator(SaleOrder saleOrder, boolean isRefund)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Creates an invoice line.
|
||||
*
|
||||
* @param invoice the created line will be linked to this invoice
|
||||
* @param saleOrderLine the sale order line used to generate the invoice line.
|
||||
* @param qtyToInvoice the quantity invoiced for this line
|
||||
* @return the generated invoice line
|
||||
* @throws AxelorException
|
||||
*/
|
||||
List<InvoiceLine> createInvoiceLine(
|
||||
Invoice invoice, SaleOrderLine saleOrderLine, BigDecimal qtyToInvoice) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Create the lines for the invoice by calling {@link
|
||||
* SaleOrderInvoiceService#createInvoiceLine(Invoice, SaleOrderLine, BigDecimal)}
|
||||
*
|
||||
* @param invoice the created lines will be linked to this invoice
|
||||
* @param saleOrderLineList the candidate lines used to generate the invoice lines.
|
||||
* @param qtyToInvoiceMap the quantities to invoice for each sale order lines. If equals to zero,
|
||||
* the invoice line will not be created
|
||||
* @return the generated invoice lines
|
||||
* @throws AxelorException
|
||||
*/
|
||||
List<InvoiceLine> createInvoiceLines(
|
||||
Invoice invoice, List<SaleOrderLine> saleOrderLineList, Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Check if a sale order line is a pack line and add all its sublines to qtyToInvoiceMap with
|
||||
* amount qtyToInvoiceItem.
|
||||
*
|
||||
* @param qtyToInvoiceMap
|
||||
* @param qtyToInvoiceItem
|
||||
* @param soLineId
|
||||
*/
|
||||
public void addSubLineQty(
|
||||
Map<Long, BigDecimal> qtyToInvoiceMap, BigDecimal qtyToInvoiceItem, Long soLineId);
|
||||
|
||||
/**
|
||||
* Use the different parameters to have the price in % of the created invoice.
|
||||
*
|
||||
* @param saleOrder the sale order used to get the total price
|
||||
* @param amount the amount to invoice
|
||||
* @param isPercent true if the amount to invoice is in %
|
||||
* @return The % in price which will be used in the created invoice
|
||||
* @throws AxelorException if the amount to invoice is larger than the total amount
|
||||
*/
|
||||
BigDecimal computeAmountToInvoicePercent(
|
||||
SaleOrder saleOrder, BigDecimal amount, boolean isPercent) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Set the updated sale order amount invoiced without checking.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @param currentInvoiceId
|
||||
* @param excludeCurrentInvoice
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void update(SaleOrder saleOrder, Long currentInvoiceId, boolean excludeCurrentInvoice)
|
||||
throws AxelorException;
|
||||
|
||||
BigDecimal getInvoicedAmount(SaleOrder saleOrder);
|
||||
|
||||
BigDecimal getInvoicedAmount(
|
||||
SaleOrder saleOrder, Long currentInvoiceId, boolean excludeCurrentInvoice);
|
||||
|
||||
/**
|
||||
* Return all invoices for the given sale order. Beware that some of them may be bound to several
|
||||
* orders (through the lines).
|
||||
*
|
||||
* @param saleOrder Sale order to get invoices for
|
||||
* @return A possibly empty list of invoices related to this order.
|
||||
*/
|
||||
List<Invoice> getInvoices(SaleOrder saleOrder);
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
Invoice mergeInvoice(
|
||||
List<Invoice> invoiceList,
|
||||
Company cmpany,
|
||||
Currency currency,
|
||||
Partner partner,
|
||||
Partner contactPartner,
|
||||
PriceList priceList,
|
||||
PaymentMode paymentMode,
|
||||
PaymentCondition paymentCondition,
|
||||
SaleOrder saleOrder)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Compute the invoiced amount of the taxed amount of the invoice.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @return the tax invoiced amount
|
||||
*/
|
||||
BigDecimal getInTaxInvoicedAmount(SaleOrder saleOrder);
|
||||
|
||||
/**
|
||||
* @param saleOrder the sale order from context
|
||||
* @return the domain for the operation select field in the invoicing wizard form
|
||||
*/
|
||||
Map<String, Integer> getInvoicingWizardOperationDomain(SaleOrder saleOrder);
|
||||
|
||||
/**
|
||||
* throw exception if all invoices amount generated from the sale order and amountToInvoice is
|
||||
* greater than saleOrder's amount
|
||||
*
|
||||
* @param saleOrder
|
||||
* @param amountToInvoice
|
||||
* @param isPercent
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void displayErrorMessageIfSaleOrderIsInvoiceable(
|
||||
SaleOrder saleOrder, BigDecimal amountToInvoice, boolean isPercent) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Display error message if all invoices have been generated for the sale order
|
||||
*
|
||||
* @param saleOrder
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void displayErrorMessageBtnGenerateInvoice(SaleOrder saleOrder) throws AxelorException;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderLineService;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public interface SaleOrderLineServiceSupplyChain extends SaleOrderLineService {
|
||||
/**
|
||||
* Compute undelivered quantity.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @return
|
||||
*/
|
||||
BigDecimal computeUndeliveredQty(SaleOrderLine saleOrderLine);
|
||||
|
||||
/**
|
||||
* Get a list of supplier partner ids available for the product in the sale order line.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
* @return the list of ids
|
||||
*/
|
||||
List<Long> getSupplierPartnerList(SaleOrderLine saleOrderLine);
|
||||
|
||||
/**
|
||||
* Update delivery state.
|
||||
*
|
||||
* @param saleOrderLine
|
||||
*/
|
||||
void updateDeliveryState(SaleOrderLine saleOrderLine);
|
||||
|
||||
/**
|
||||
* Update delivery states.
|
||||
*
|
||||
* @param saleOrderLineList
|
||||
*/
|
||||
void updateDeliveryStates(List<SaleOrderLine> saleOrderLineList);
|
||||
|
||||
/**
|
||||
* Create a query to find sale order line of a product of a specific/all company and a
|
||||
* specific/all stock location
|
||||
*
|
||||
* @param productId
|
||||
* @param companyId
|
||||
* @param stockLocationId
|
||||
* @return the query.
|
||||
*/
|
||||
String getSaleOrderLineListForAProduct(Long productId, Long companyId, Long stockLocationId);
|
||||
|
||||
/**
|
||||
* check qty when modifying saleOrderLine which is invoiced or delivered
|
||||
*
|
||||
* @param saleOrderLine
|
||||
*/
|
||||
BigDecimal checkInvoicedOrDeliveredOrderQty(SaleOrderLine saleOrderLine);
|
||||
}
|
||||
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AnalyticDistributionTemplate;
|
||||
import com.axelor.apps.account.db.AnalyticMoveLine;
|
||||
import com.axelor.apps.account.db.repo.AnalyticMoveLineRepository;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.service.AnalyticMoveLineService;
|
||||
import com.axelor.apps.account.service.app.AppAccountService;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.repo.AppAccountRepository;
|
||||
import com.axelor.apps.purchase.db.SupplierCatalog;
|
||||
import com.axelor.apps.purchase.service.app.AppPurchaseService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderLineServiceImpl;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.apps.stock.service.StockLocationLineService;
|
||||
import com.axelor.apps.stock.service.StockLocationService;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.apps.tool.StringTool;
|
||||
import com.axelor.common.ObjectUtils;
|
||||
import com.axelor.common.StringUtils;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.Query;
|
||||
|
||||
public class SaleOrderLineServiceSupplyChainImpl extends SaleOrderLineServiceImpl
|
||||
implements SaleOrderLineServiceSupplyChain {
|
||||
|
||||
@Inject protected AppAccountService appAccountService;
|
||||
|
||||
@Inject protected AnalyticMoveLineService analyticMoveLineService;
|
||||
|
||||
@Override
|
||||
public void computeProductInformation(
|
||||
SaleOrderLine saleOrderLine, SaleOrder saleOrder, Integer packPriceSelect)
|
||||
throws AxelorException {
|
||||
super.computeProductInformation(saleOrderLine, saleOrder, packPriceSelect);
|
||||
saleOrderLine.setSaleSupplySelect(saleOrderLine.getProduct().getSaleSupplySelect());
|
||||
|
||||
this.getAndComputeAnalyticDistribution(saleOrderLine, saleOrder);
|
||||
}
|
||||
|
||||
public SaleOrderLine getAndComputeAnalyticDistribution(
|
||||
SaleOrderLine saleOrderLine, SaleOrder saleOrder) {
|
||||
|
||||
if (appAccountService.getAppAccount().getAnalyticDistributionTypeSelect()
|
||||
== AppAccountRepository.DISTRIBUTION_TYPE_FREE) {
|
||||
return saleOrderLine;
|
||||
}
|
||||
|
||||
AnalyticDistributionTemplate analyticDistributionTemplate =
|
||||
analyticMoveLineService.getAnalyticDistributionTemplate(
|
||||
saleOrder.getClientPartner(), saleOrderLine.getProduct(), saleOrder.getCompany());
|
||||
|
||||
saleOrderLine.setAnalyticDistributionTemplate(analyticDistributionTemplate);
|
||||
|
||||
if (saleOrderLine.getAnalyticMoveLineList() != null) {
|
||||
saleOrderLine.getAnalyticMoveLineList().clear();
|
||||
}
|
||||
|
||||
this.computeAnalyticDistribution(saleOrderLine);
|
||||
|
||||
return saleOrderLine;
|
||||
}
|
||||
|
||||
public SaleOrderLine computeAnalyticDistribution(SaleOrderLine saleOrderLine) {
|
||||
|
||||
List<AnalyticMoveLine> analyticMoveLineList = saleOrderLine.getAnalyticMoveLineList();
|
||||
|
||||
if ((analyticMoveLineList == null || analyticMoveLineList.isEmpty())) {
|
||||
createAnalyticDistributionWithTemplate(saleOrderLine);
|
||||
}
|
||||
if (analyticMoveLineList != null) {
|
||||
LocalDate date = appAccountService.getTodayDate();
|
||||
for (AnalyticMoveLine analyticMoveLine : analyticMoveLineList) {
|
||||
analyticMoveLineService.updateAnalyticMoveLine(
|
||||
analyticMoveLine, saleOrderLine.getCompanyExTaxTotal(), date);
|
||||
}
|
||||
}
|
||||
return saleOrderLine;
|
||||
}
|
||||
|
||||
public SaleOrderLine createAnalyticDistributionWithTemplate(SaleOrderLine saleOrderLine) {
|
||||
List<AnalyticMoveLine> analyticMoveLineList =
|
||||
analyticMoveLineService.generateLines(
|
||||
saleOrderLine.getAnalyticDistributionTemplate(),
|
||||
saleOrderLine.getCompanyExTaxTotal(),
|
||||
AnalyticMoveLineRepository.STATUS_FORECAST_ORDER,
|
||||
appAccountService.getTodayDate());
|
||||
|
||||
saleOrderLine.setAnalyticMoveLineList(analyticMoveLineList);
|
||||
return saleOrderLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getAvailableStock(SaleOrder saleOrder, SaleOrderLine saleOrderLine) {
|
||||
StockLocationLine stockLocationLine =
|
||||
Beans.get(StockLocationLineService.class)
|
||||
.getStockLocationLine(saleOrder.getStockLocation(), saleOrderLine.getProduct());
|
||||
|
||||
if (stockLocationLine == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return stockLocationLine.getCurrentQty().subtract(stockLocationLine.getReservedQty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getAllocatedStock(SaleOrder saleOrder, SaleOrderLine saleOrderLine) {
|
||||
StockLocationLine stockLocationLine =
|
||||
Beans.get(StockLocationLineService.class)
|
||||
.getStockLocationLine(saleOrder.getStockLocation(), saleOrderLine.getProduct());
|
||||
|
||||
if (stockLocationLine == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return stockLocationLine.getReservedQty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal computeUndeliveredQty(SaleOrderLine saleOrderLine) {
|
||||
Preconditions.checkNotNull(saleOrderLine);
|
||||
|
||||
BigDecimal undeliveryQty = saleOrderLine.getQty().subtract(saleOrderLine.getDeliveredQty());
|
||||
|
||||
if (undeliveryQty.signum() > 0) {
|
||||
return undeliveryQty;
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getSupplierPartnerList(SaleOrderLine saleOrderLine) {
|
||||
Product product = saleOrderLine.getProduct();
|
||||
if (!Beans.get(AppPurchaseService.class).getAppPurchase().getManageSupplierCatalog()
|
||||
|| product == null
|
||||
|| product.getSupplierCatalogList() == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return product
|
||||
.getSupplierCatalogList()
|
||||
.stream()
|
||||
.map(SupplierCatalog::getSupplierPartner)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Partner::getId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDeliveryStates(List<SaleOrderLine> saleOrderLineList) {
|
||||
if (ObjectUtils.isEmpty(saleOrderLineList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
|
||||
updateDeliveryState(saleOrderLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDeliveryState(SaleOrderLine saleOrderLine) {
|
||||
if (saleOrderLine.getDeliveredQty().signum() == 0) {
|
||||
saleOrderLine.setDeliveryState(SaleOrderLineRepository.DELIVERY_STATE_NOT_DELIVERED);
|
||||
} else if (saleOrderLine.getDeliveredQty().compareTo(saleOrderLine.getQty()) < 0) {
|
||||
saleOrderLine.setDeliveryState(SaleOrderLineRepository.DELIVERY_STATE_PARTIALLY_DELIVERED);
|
||||
} else {
|
||||
saleOrderLine.setDeliveryState(SaleOrderLineRepository.DELIVERY_STATE_DELIVERED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSaleOrderLineListForAProduct(
|
||||
Long productId, Long companyId, Long stockLocationId) {
|
||||
List<Integer> statusList = new ArrayList<>();
|
||||
statusList.add(SaleOrderRepository.STATUS_ORDER_CONFIRMED);
|
||||
String status =
|
||||
Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getsOFilterOnStockDetailStatusSelect();
|
||||
if (!StringUtils.isBlank(status)) {
|
||||
statusList = StringTool.getIntegerList(status);
|
||||
}
|
||||
String statusListQuery =
|
||||
statusList.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||
String query =
|
||||
"self.product.id = "
|
||||
+ productId
|
||||
+ " AND self.deliveryState != "
|
||||
+ SaleOrderLineRepository.DELIVERY_STATE_DELIVERED
|
||||
+ " AND self.saleOrder.statusSelect IN ("
|
||||
+ statusListQuery
|
||||
+ ")";
|
||||
|
||||
if (companyId != 0L) {
|
||||
query += " AND self.saleOrder.company.id = " + companyId;
|
||||
if (stockLocationId != 0L) {
|
||||
StockLocation stockLocation =
|
||||
Beans.get(StockLocationRepository.class).find(stockLocationId);
|
||||
List<StockLocation> stockLocationList =
|
||||
Beans.get(StockLocationService.class)
|
||||
.getAllLocationAndSubLocation(stockLocation, false);
|
||||
if (!stockLocationList.isEmpty() && stockLocation.getCompany().getId() == companyId) {
|
||||
query +=
|
||||
" AND self.saleOrder.stockLocation.id IN ("
|
||||
+ StringTool.getIdListString(stockLocationList)
|
||||
+ ") ";
|
||||
}
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal checkInvoicedOrDeliveredOrderQty(SaleOrderLine saleOrderLine) {
|
||||
BigDecimal qty = saleOrderLine.getQty();
|
||||
BigDecimal deliveredQty = saleOrderLine.getDeliveredQty();
|
||||
BigDecimal invoicedQty = BigDecimal.ZERO;
|
||||
|
||||
Query query =
|
||||
JPA.em()
|
||||
.createQuery(
|
||||
"SELECT SUM(self.qty) FROM InvoiceLine self WHERE self.invoice.statusSelect = :statusSelect AND self.saleOrderLine.id = :saleOrderLineId");
|
||||
query.setParameter("statusSelect", InvoiceRepository.STATUS_VENTILATED);
|
||||
query.setParameter("saleOrderLineId", saleOrderLine.getId());
|
||||
|
||||
invoicedQty = (BigDecimal) query.getSingleResult();
|
||||
|
||||
if (invoicedQty != null
|
||||
&& qty.compareTo(invoicedQty) == -1
|
||||
&& invoicedQty.compareTo(deliveredQty) > 0) {
|
||||
return invoicedQty;
|
||||
} else if (deliveredQty.compareTo(BigDecimal.ZERO) > 0 && qty.compareTo(deliveredQty) == -1) {
|
||||
return deliveredQty;
|
||||
}
|
||||
|
||||
return qty;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface SaleOrderPurchaseService {
|
||||
|
||||
public void createPurchaseOrders(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
public Map<Partner, List<SaleOrderLine>> splitBySupplierPartner(
|
||||
List<SaleOrderLine> saleOrderLineList) throws AxelorException;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public PurchaseOrder createPurchaseOrder(
|
||||
Partner supplierPartner, List<SaleOrderLine> saleOrderLineList, SaleOrder saleOrder)
|
||||
throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.repo.AccountConfigRepository;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.repo.PriceListRepository;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.base.service.PartnerPriceListService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.purchase.service.config.PurchaseConfigService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.service.StockLocationService;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.auth.AuthUtils;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SaleOrderPurchaseServiceImpl implements SaleOrderPurchaseService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected PurchaseOrderServiceSupplychainImpl purchaseOrderServiceSupplychainImpl;
|
||||
protected PurchaseOrderLineServiceSupplychainImpl purchaseOrderLineServiceSupplychainImpl;
|
||||
|
||||
@Inject
|
||||
public SaleOrderPurchaseServiceImpl(
|
||||
PurchaseOrderServiceSupplychainImpl purchaseOrderServiceSupplychainImpl,
|
||||
PurchaseOrderLineServiceSupplychainImpl purchaseOrderLineServiceSupplychainImpl) {
|
||||
this.purchaseOrderServiceSupplychainImpl = purchaseOrderServiceSupplychainImpl;
|
||||
this.purchaseOrderLineServiceSupplychainImpl = purchaseOrderLineServiceSupplychainImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPurchaseOrders(SaleOrder saleOrder) throws AxelorException {
|
||||
|
||||
Map<Partner, List<SaleOrderLine>> saleOrderLinesBySupplierPartner =
|
||||
this.splitBySupplierPartner(saleOrder.getSaleOrderLineList());
|
||||
|
||||
for (Partner supplierPartner : saleOrderLinesBySupplierPartner.keySet()) {
|
||||
|
||||
this.createPurchaseOrder(
|
||||
supplierPartner, saleOrderLinesBySupplierPartner.get(supplierPartner), saleOrder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Partner, List<SaleOrderLine>> splitBySupplierPartner(
|
||||
List<SaleOrderLine> saleOrderLineList) throws AxelorException {
|
||||
|
||||
Map<Partner, List<SaleOrderLine>> saleOrderLinesBySupplierPartner = new HashMap<>();
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
|
||||
|
||||
if (saleOrderLine.getSaleSupplySelect() == ProductRepository.SALE_SUPPLY_PURCHASE) {
|
||||
|
||||
Partner supplierPartner = saleOrderLine.getSupplierPartner();
|
||||
|
||||
if (supplierPartner == null) {
|
||||
throw new AxelorException(
|
||||
saleOrderLine,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.SO_PURCHASE_1),
|
||||
saleOrderLine.getProductName());
|
||||
}
|
||||
|
||||
if (!saleOrderLinesBySupplierPartner.containsKey(supplierPartner)) {
|
||||
saleOrderLinesBySupplierPartner.put(supplierPartner, new ArrayList<SaleOrderLine>());
|
||||
}
|
||||
|
||||
saleOrderLinesBySupplierPartner.get(supplierPartner).add(saleOrderLine);
|
||||
}
|
||||
}
|
||||
|
||||
return saleOrderLinesBySupplierPartner;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public PurchaseOrder createPurchaseOrder(
|
||||
Partner supplierPartner, List<SaleOrderLine> saleOrderLineList, SaleOrder saleOrder)
|
||||
throws AxelorException {
|
||||
|
||||
LOG.debug(
|
||||
"Création d'une commande fournisseur pour le devis client : {}",
|
||||
saleOrder.getSaleOrderSeq());
|
||||
|
||||
PurchaseOrder purchaseOrder =
|
||||
purchaseOrderServiceSupplychainImpl.createPurchaseOrder(
|
||||
AuthUtils.getUser(),
|
||||
saleOrder.getCompany(),
|
||||
supplierPartner.getContactPartnerSet().size() == 1
|
||||
? supplierPartner.getContactPartnerSet().iterator().next()
|
||||
: null,
|
||||
supplierPartner.getCurrency(),
|
||||
null,
|
||||
saleOrder.getSaleOrderSeq(),
|
||||
saleOrder.getExternalReference(),
|
||||
saleOrder.getDescription(),
|
||||
saleOrder.getDirectOrderLocation()
|
||||
? saleOrder.getStockLocation()
|
||||
: Beans.get(StockLocationService.class)
|
||||
.getDefaultReceiptStockLocation(saleOrder.getCompany()),
|
||||
Beans.get(AppBaseService.class).getTodayDate(),
|
||||
Beans.get(PartnerPriceListService.class)
|
||||
.getDefaultPriceList(supplierPartner, PriceListRepository.TYPE_PURCHASE),
|
||||
supplierPartner,
|
||||
saleOrder.getTradingName());
|
||||
|
||||
purchaseOrder.setGeneratedSaleOrderId(saleOrder.getId());
|
||||
|
||||
Integer atiChoice =
|
||||
Beans.get(PurchaseConfigService.class)
|
||||
.getPurchaseConfig(saleOrder.getCompany())
|
||||
.getPurchaseOrderInAtiSelect();
|
||||
if (atiChoice == AccountConfigRepository.INVOICE_ATI_ALWAYS
|
||||
|| atiChoice == AccountConfigRepository.INVOICE_ATI_DEFAULT) {
|
||||
purchaseOrder.setInAti(true);
|
||||
} else {
|
||||
purchaseOrder.setInAti(false);
|
||||
}
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
|
||||
purchaseOrder.addPurchaseOrderLineListItem(
|
||||
purchaseOrderLineServiceSupplychainImpl.createPurchaseOrderLine(
|
||||
purchaseOrder, saleOrderLine));
|
||||
}
|
||||
|
||||
purchaseOrderServiceSupplychainImpl.computePurchaseOrder(purchaseOrder);
|
||||
|
||||
purchaseOrder.setNotes(supplierPartner.getPurchaseOrderComments());
|
||||
|
||||
Beans.get(PurchaseOrderRepository.class).save(purchaseOrder);
|
||||
|
||||
return purchaseOrder;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.exception.AxelorException;
|
||||
|
||||
public interface SaleOrderReservedQtyService {
|
||||
|
||||
/**
|
||||
* Try to allocate every line, meaning the allocated quantity of the line will be changed to match
|
||||
* the requested quantity.
|
||||
*
|
||||
* @param saleOrder a confirmed sale order.
|
||||
* @throws AxelorException if the sale order does not have a stock move.
|
||||
*/
|
||||
void allocateAll(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Deallocate every line, meaning the allocated quantity of the line will be changed to 0.
|
||||
*
|
||||
* @param saleOrder a confirmed sale order.
|
||||
* @throws AxelorException if the sale order does not have a stock move.
|
||||
*/
|
||||
void deallocateAll(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Reserve the quantity for every line, meaning we change both requested and allocated quantity to
|
||||
* the quantity of the line.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @throws AxelorException if the sale order does not have a stock move.
|
||||
*/
|
||||
void reserveAll(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Cancel the reservation for every line, meaning we change both requested and allocated quantity
|
||||
* to 0.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @throws AxelorException if the sale order does not have a stock move.
|
||||
*/
|
||||
void cancelReservation(SaleOrder saleOrder) throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SaleOrderReservedQtyServiceImpl implements SaleOrderReservedQtyService {
|
||||
|
||||
protected ReservedQtyService reservedQtyService;
|
||||
protected StockMoveLineRepository stockMoveLineRepository;
|
||||
|
||||
@Inject
|
||||
public SaleOrderReservedQtyServiceImpl(
|
||||
ReservedQtyService reservedQtyService, StockMoveLineRepository stockMoveLineRepository) {
|
||||
this.reservedQtyService = reservedQtyService;
|
||||
this.stockMoveLineRepository = stockMoveLineRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void allocateAll(SaleOrder saleOrder) throws AxelorException {
|
||||
for (SaleOrderLine saleOrderLine : getNonDeliveredLines(saleOrder)) {
|
||||
reservedQtyService.allocateAll(saleOrderLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void deallocateAll(SaleOrder saleOrder) throws AxelorException {
|
||||
for (SaleOrderLine saleOrderLine : getNonDeliveredLines(saleOrder)) {
|
||||
reservedQtyService.updateReservedQty(saleOrderLine, BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void reserveAll(SaleOrder saleOrder) throws AxelorException {
|
||||
for (SaleOrderLine saleOrderLine : getNonDeliveredLines(saleOrder)) {
|
||||
reservedQtyService.requestQty(saleOrderLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void cancelReservation(SaleOrder saleOrder) throws AxelorException {
|
||||
for (SaleOrderLine saleOrderLine : getNonDeliveredLines(saleOrder)) {
|
||||
reservedQtyService.cancelReservation(saleOrderLine);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<SaleOrderLine> getNonDeliveredLines(SaleOrder saleOrder) {
|
||||
List<SaleOrderLine> saleOrderLineList =
|
||||
saleOrder.getSaleOrderLineList() == null
|
||||
? new ArrayList<>()
|
||||
: saleOrder.getSaleOrderLineList();
|
||||
return saleOrderLineList
|
||||
.stream()
|
||||
.filter(saleOrderLine -> getPlannedStockMoveLine(saleOrderLine) != null)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected StockMoveLine getPlannedStockMoveLine(SaleOrderLine saleOrderLine) {
|
||||
return stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.saleOrderLine = :saleOrderLine " + "AND self.stockMove.statusSelect = :planned")
|
||||
.bind("saleOrderLine", saleOrderLine)
|
||||
.bind("planned", StockMoveRepository.STATUS_PLANNED)
|
||||
.fetchOne();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.AppSupplychain;
|
||||
import com.axelor.apps.base.db.CancelReason;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.repo.PriceListRepository;
|
||||
import com.axelor.apps.base.service.PartnerPriceListService;
|
||||
import com.axelor.apps.base.service.PartnerService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderServiceImpl;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.stock.service.StockMoveService;
|
||||
import com.axelor.apps.supplychain.db.Timetable;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SaleOrderServiceSupplychainImpl extends SaleOrderServiceImpl {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected AppSupplychain appSupplychain;
|
||||
protected SaleOrderStockService saleOrderStockService;
|
||||
|
||||
@Inject
|
||||
public SaleOrderServiceSupplychainImpl(
|
||||
AppSupplychainService appSupplychainService, SaleOrderStockService saleOrderStockService) {
|
||||
|
||||
this.appSupplychain = appSupplychainService.getAppSupplychain();
|
||||
this.saleOrderStockService = saleOrderStockService;
|
||||
}
|
||||
|
||||
public SaleOrder getClientInformations(SaleOrder saleOrder) {
|
||||
Partner client = saleOrder.getClientPartner();
|
||||
PartnerService partnerService = Beans.get(PartnerService.class);
|
||||
if (client != null) {
|
||||
saleOrder.setPaymentCondition(client.getPaymentCondition());
|
||||
saleOrder.setPaymentMode(client.getInPaymentMode());
|
||||
saleOrder.setMainInvoicingAddress(partnerService.getInvoicingAddress(client));
|
||||
this.computeAddressStr(saleOrder);
|
||||
saleOrder.setDeliveryAddress(partnerService.getDeliveryAddress(client));
|
||||
saleOrder.setPriceList(
|
||||
Beans.get(PartnerPriceListService.class)
|
||||
.getDefaultPriceList(client, PriceListRepository.TYPE_SALE));
|
||||
}
|
||||
return saleOrder;
|
||||
}
|
||||
|
||||
public void updateAmountToBeSpreadOverTheTimetable(SaleOrder saleOrder) {
|
||||
List<Timetable> timetableList = saleOrder.getTimetableList();
|
||||
BigDecimal totalHT = saleOrder.getExTaxTotal();
|
||||
BigDecimal sumTimetableAmount = BigDecimal.ZERO;
|
||||
if (timetableList != null) {
|
||||
for (Timetable timetable : timetableList) {
|
||||
sumTimetableAmount = sumTimetableAmount.add(timetable.getAmount());
|
||||
}
|
||||
}
|
||||
saleOrder.setAmountToBeSpreadOverTheTimetable(totalHT.subtract(sumTimetableAmount));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public boolean enableEditOrder(SaleOrder saleOrder) throws AxelorException {
|
||||
boolean checkAvailabiltyRequest = super.enableEditOrder(saleOrder);
|
||||
|
||||
List<StockMove> allStockMoves =
|
||||
Beans.get(StockMoveRepository.class)
|
||||
.findAllBySaleOrderAndStatus(
|
||||
StockMoveRepository.ORIGIN_SALE_ORDER,
|
||||
saleOrder.getId(),
|
||||
StockMoveRepository.STATUS_PLANNED)
|
||||
.fetch();
|
||||
List<StockMove> stockMoves =
|
||||
!allStockMoves.isEmpty()
|
||||
? allStockMoves
|
||||
.stream()
|
||||
.filter(stockMove -> !stockMove.getAvailabilityRequest())
|
||||
.collect(Collectors.toList())
|
||||
: allStockMoves;
|
||||
checkAvailabiltyRequest =
|
||||
stockMoves.size() != allStockMoves.size() ? true : checkAvailabiltyRequest;
|
||||
if (!stockMoves.isEmpty()) {
|
||||
StockMoveService stockMoveService = Beans.get(StockMoveService.class);
|
||||
StockMoveRepository stockMoveRepository = Beans.get(StockMoveRepository.class);
|
||||
CancelReason cancelReason = appSupplychain.getCancelReasonOnChangingSaleOrder();
|
||||
if (cancelReason == null) {
|
||||
throw new AxelorException(
|
||||
appSupplychain,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
IExceptionMessage.SUPPLYCHAIN_MISSING_CANCEL_REASON_ON_CHANGING_SALE_ORDER);
|
||||
}
|
||||
for (StockMove stockMove : stockMoves) {
|
||||
stockMoveService.cancel(stockMove, cancelReason, "");
|
||||
stockMoveRepository.remove(stockMove);
|
||||
}
|
||||
}
|
||||
return checkAvailabiltyRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the supplychain implementation, we check if the user has deleted already delivered qty.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @param saleOrderView
|
||||
* @throws AxelorException if the user tried to remove already delivered qty.
|
||||
*/
|
||||
@Override
|
||||
public void checkModifiedConfirmedOrder(SaleOrder saleOrder, SaleOrder saleOrderView)
|
||||
throws AxelorException {
|
||||
|
||||
List<SaleOrderLine> saleOrderLineList =
|
||||
MoreObjects.firstNonNull(saleOrder.getSaleOrderLineList(), Collections.emptyList());
|
||||
List<SaleOrderLine> saleOrderViewLineList =
|
||||
MoreObjects.firstNonNull(saleOrderView.getSaleOrderLineList(), Collections.emptyList());
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
|
||||
if (saleOrderLine.getDeliveryState()
|
||||
<= SaleOrderLineRepository.DELIVERY_STATE_NOT_DELIVERED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Optional<SaleOrderLine> optionalNewSaleOrderLine =
|
||||
saleOrderViewLineList.stream().filter(saleOrderLine::equals).findFirst();
|
||||
|
||||
if (optionalNewSaleOrderLine.isPresent()) {
|
||||
SaleOrderLine newSaleOrderLine = optionalNewSaleOrderLine.get();
|
||||
|
||||
if (newSaleOrderLine.getQty().compareTo(saleOrderLine.getDeliveredQty()) < 0) {
|
||||
throw new AxelorException(
|
||||
saleOrder,
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SO_CANT_DECREASE_QTY_ON_DELIVERED_LINE),
|
||||
saleOrderLine.getFullName());
|
||||
}
|
||||
} else {
|
||||
throw new AxelorException(
|
||||
saleOrder,
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.SO_CANT_REMOVED_DELIVERED_LINE),
|
||||
saleOrderLine.getFullName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void validateChanges(SaleOrder saleOrder) throws AxelorException {
|
||||
super.validateChanges(saleOrder);
|
||||
|
||||
saleOrderStockService.fullyUpdateDeliveryState(saleOrder);
|
||||
saleOrder.setOrderBeingEdited(false);
|
||||
|
||||
if (appSupplychain.getCustomerStockMoveGenerationAuto()) {
|
||||
saleOrderStockService.createStocksMovesFromSaleOrder(saleOrder);
|
||||
}
|
||||
}
|
||||
|
||||
// same move
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public StockMove splitInto2SameMove(
|
||||
StockMove originalStockMove, List<StockMoveLine> modifiedStockMoveLines)
|
||||
throws AxelorException {
|
||||
|
||||
StockMoveRepository stockMoveRepo = Beans.get(StockMoveRepository.class);
|
||||
StockMoveLineRepository stockMoveLineRepo = Beans.get(StockMoveLineRepository.class);
|
||||
|
||||
modifiedStockMoveLines =
|
||||
modifiedStockMoveLines
|
||||
.stream()
|
||||
.filter(stockMoveLine -> stockMoveLine.getQty().compareTo(BigDecimal.ZERO) != 0)
|
||||
.collect(Collectors.toList());
|
||||
for (StockMoveLine moveLine : modifiedStockMoveLines) {
|
||||
StockMoveLine newStockMoveLine = new StockMoveLine();
|
||||
// Set quantity in new stock move line
|
||||
newStockMoveLine = stockMoveLineRepo.copy(moveLine, false);
|
||||
newStockMoveLine.setQty(moveLine.getQty());
|
||||
newStockMoveLine.setRealQty(moveLine.getQty());
|
||||
newStockMoveLine.setProductTypeSelect(moveLine.getProductTypeSelect());
|
||||
newStockMoveLine.setSaleOrderLine(moveLine.getSaleOrderLine());
|
||||
// add stock move line
|
||||
originalStockMove.addStockMoveLineListItem(newStockMoveLine);
|
||||
|
||||
// find the original move line to update it
|
||||
Optional<StockMoveLine> correspondingMoveLine =
|
||||
originalStockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.filter(stockMoveLine -> stockMoveLine.getId().equals(moveLine.getId()))
|
||||
.findFirst();
|
||||
if (BigDecimal.ZERO.compareTo(moveLine.getQty()) > 0
|
||||
|| (correspondingMoveLine.isPresent()
|
||||
&& moveLine.getQty().compareTo(correspondingMoveLine.get().getRealQty()) > 0)) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY, I18n.get(""), originalStockMove);
|
||||
}
|
||||
|
||||
if (correspondingMoveLine.isPresent()) {
|
||||
// Update quantity in original stock move.
|
||||
// If the remaining quantity is 0, remove the stock move line
|
||||
BigDecimal remainingQty = correspondingMoveLine.get().getQty().subtract(moveLine.getQty());
|
||||
if (BigDecimal.ZERO.compareTo(remainingQty) == 0) {
|
||||
// Remove the stock move line
|
||||
originalStockMove.removeStockMoveLineListItem(correspondingMoveLine.get());
|
||||
} else {
|
||||
correspondingMoveLine.get().setQty(remainingQty);
|
||||
correspondingMoveLine.get().setRealQty(remainingQty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!originalStockMove.getStockMoveLineList().isEmpty()) {
|
||||
return stockMoveRepo.save(originalStockMove);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SaleOrderStockService {
|
||||
|
||||
/**
|
||||
* Create a delivery stock move from a sale order.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @return
|
||||
* @throws AxelorException
|
||||
*/
|
||||
public List<Long> createStocksMovesFromSaleOrder(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
public StockMove createStockMove(
|
||||
SaleOrder saleOrder, Company company, LocalDate estimatedDeliveryDate) throws AxelorException;
|
||||
|
||||
public StockMoveLine createStockMoveLine(StockMove stockMove, SaleOrderLine saleOrderLine)
|
||||
throws AxelorException;
|
||||
|
||||
public StockMoveLine createStockMoveLine(
|
||||
StockMove stockMove, SaleOrderLine saleOrderLine, BigDecimal qty) throws AxelorException;
|
||||
|
||||
public boolean isStockMoveProduct(SaleOrderLine saleOrderLine) throws AxelorException;
|
||||
|
||||
boolean isStockMoveProduct(SaleOrderLine saleOrderLine, SaleOrder saleOrder)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update delivery state by checking delivery states on the sale order lines.
|
||||
*
|
||||
* @param saleOrder
|
||||
*/
|
||||
void updateDeliveryState(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Update delivery states in sale order and sale order lines.
|
||||
*
|
||||
* @param saleOrder
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void fullyUpdateDeliveryState(SaleOrder saleOrder) throws AxelorException;
|
||||
|
||||
/**
|
||||
* Find the sale order linked to the stock move.
|
||||
*
|
||||
* @param stockMove a stock move
|
||||
* @return the found sale order, or an empty optional if no sale order was found.
|
||||
*/
|
||||
Optional<SaleOrder> findSaleOrder(StockMove stockMove);
|
||||
}
|
||||
@ -0,0 +1,549 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.TaxLine;
|
||||
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.Unit;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.sale.service.app.AppSaleService;
|
||||
import com.axelor.apps.stock.db.PartnerStockSettings;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.stock.service.PartnerStockSettingsService;
|
||||
import com.axelor.apps.stock.service.StockMoveLineService;
|
||||
import com.axelor.apps.stock.service.StockMoveService;
|
||||
import com.axelor.apps.stock.service.config.StockConfigService;
|
||||
import com.axelor.apps.supplychain.db.SupplyChainConfig;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplyChainConfigRepository;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.config.SupplyChainConfigService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SaleOrderStockServiceImpl implements SaleOrderStockService {
|
||||
|
||||
protected StockMoveService stockMoveService;
|
||||
protected StockMoveLineService stockMoveLineService;
|
||||
protected StockConfigService stockConfigService;
|
||||
protected UnitConversionService unitConversionService;
|
||||
protected SaleOrderLineServiceSupplyChain saleOrderLineServiceSupplyChain;
|
||||
protected StockMoveLineServiceSupplychain stockMoveLineSupplychainService;
|
||||
protected StockMoveLineRepository stockMoveLineRepository;
|
||||
protected AppBaseService appBaseService;
|
||||
protected SaleOrderRepository saleOrderRepository;
|
||||
|
||||
@Inject
|
||||
public SaleOrderStockServiceImpl(
|
||||
StockMoveService stockMoveService,
|
||||
StockMoveLineService stockMoveLineService,
|
||||
StockConfigService stockConfigService,
|
||||
UnitConversionService unitConversionService,
|
||||
SaleOrderLineServiceSupplyChain saleOrderLineServiceSupplyChain,
|
||||
StockMoveLineServiceSupplychain stockMoveLineSupplychainService,
|
||||
StockMoveLineRepository stockMoveLineRepository,
|
||||
AppBaseService appBaseService,
|
||||
SaleOrderRepository saleOrderRepository) {
|
||||
|
||||
this.stockMoveService = stockMoveService;
|
||||
this.stockMoveLineService = stockMoveLineService;
|
||||
this.stockConfigService = stockConfigService;
|
||||
this.unitConversionService = unitConversionService;
|
||||
this.saleOrderLineServiceSupplyChain = saleOrderLineServiceSupplyChain;
|
||||
this.stockMoveLineSupplychainService = stockMoveLineSupplychainService;
|
||||
this.stockMoveLineRepository = stockMoveLineRepository;
|
||||
this.appBaseService = appBaseService;
|
||||
this.saleOrderRepository = saleOrderRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public List<Long> createStocksMovesFromSaleOrder(SaleOrder saleOrder) throws AxelorException {
|
||||
|
||||
if (!this.isSaleOrderWithProductsToDeliver(saleOrder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (saleOrder.getStockLocation() == null) {
|
||||
throw new AxelorException(
|
||||
saleOrder,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.SO_MISSING_STOCK_LOCATION),
|
||||
saleOrder.getSaleOrderSeq());
|
||||
}
|
||||
|
||||
List<Long> stockMoveList = new ArrayList<>();
|
||||
|
||||
Map<LocalDate, List<SaleOrderLine>> saleOrderLinePerDateMap =
|
||||
getAllSaleOrderLinePerDate(saleOrder);
|
||||
|
||||
for (LocalDate estimatedDeliveryDate :
|
||||
saleOrderLinePerDateMap
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(x -> x != null)
|
||||
.sorted((x, y) -> x.compareTo(y))
|
||||
.collect(Collectors.toList())) {
|
||||
|
||||
List<SaleOrderLine> saleOrderLineList = saleOrderLinePerDateMap.get(estimatedDeliveryDate);
|
||||
|
||||
Optional<StockMove> stockMove =
|
||||
createStockMove(saleOrder, estimatedDeliveryDate, saleOrderLineList);
|
||||
|
||||
stockMove.map(StockMove::getId).ifPresent(stockMoveList::add);
|
||||
}
|
||||
Optional<List<SaleOrderLine>> saleOrderLineList =
|
||||
Optional.ofNullable(saleOrderLinePerDateMap.get(null));
|
||||
if (saleOrderLineList.isPresent()) {
|
||||
|
||||
Optional<StockMove> stockMove = createStockMove(saleOrder, null, saleOrderLineList.get());
|
||||
|
||||
stockMove.map(StockMove::getId).ifPresent(stockMoveList::add);
|
||||
}
|
||||
return stockMoveList;
|
||||
}
|
||||
|
||||
protected Optional<StockMove> createStockMove(
|
||||
SaleOrder saleOrder, LocalDate estimatedDeliveryDate, List<SaleOrderLine> saleOrderLineList)
|
||||
throws AxelorException {
|
||||
|
||||
StockMove stockMove =
|
||||
this.createStockMove(saleOrder, saleOrder.getCompany(), estimatedDeliveryDate);
|
||||
stockMove.setDeliveryCondition(saleOrder.getDeliveryCondition());
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
|
||||
if (saleOrderLine.getProduct() != null
|
||||
|| saleOrderLine.getTypeSelect().equals(SaleOrderLineRepository.TYPE_PACK)) {
|
||||
BigDecimal qty = saleOrderLineServiceSupplyChain.computeUndeliveredQty(saleOrderLine);
|
||||
|
||||
if (qty.signum() > 0 && !existActiveStockMoveForSaleOrderLine(saleOrderLine)) {
|
||||
createStockMoveLine(stockMove, saleOrderLine, qty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stockMove.getStockMoveLineList() == null || stockMove.getStockMoveLineList().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (stockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.noneMatch(
|
||||
stockMoveLine ->
|
||||
stockMoveLine.getSaleOrderLine() != null
|
||||
&& stockMoveLine.getSaleOrderLine().getTypeSelect()
|
||||
== SaleOrderLineRepository.TYPE_NORMAL)) {
|
||||
stockMove.setFullySpreadOverLogisticalFormsFlag(true);
|
||||
}
|
||||
|
||||
boolean isNeedingConformityCertificate = saleOrder.getIsNeedingConformityCertificate();
|
||||
stockMove.setIsNeedingConformityCertificate(isNeedingConformityCertificate);
|
||||
|
||||
if (isNeedingConformityCertificate) {
|
||||
stockMove.setSignatoryUser(
|
||||
stockConfigService.getStockConfig(stockMove.getCompany()).getSignatoryUser());
|
||||
}
|
||||
|
||||
SupplyChainConfig supplychainConfig =
|
||||
Beans.get(SupplyChainConfigService.class).getSupplyChainConfig(saleOrder.getCompany());
|
||||
|
||||
if (supplychainConfig.getDefaultEstimatedDate() != null
|
||||
&& supplychainConfig.getDefaultEstimatedDate() == SupplyChainConfigRepository.CURRENT_DATE
|
||||
&& stockMove.getEstimatedDate() == null) {
|
||||
stockMove.setEstimatedDate(appBaseService.getTodayDate());
|
||||
} else if (supplychainConfig.getDefaultEstimatedDate()
|
||||
== SupplyChainConfigRepository.CURRENT_DATE_PLUS_DAYS
|
||||
&& stockMove.getEstimatedDate() == null) {
|
||||
stockMove.setEstimatedDate(
|
||||
appBaseService.getTodayDate().plusDays(supplychainConfig.getNumberOfDays().longValue()));
|
||||
}
|
||||
|
||||
stockMoveService.plan(stockMove);
|
||||
|
||||
if (Beans.get(AppSaleService.class).getAppSale().getProductPackMgt()) {
|
||||
setParentStockMoveLine(stockMove);
|
||||
}
|
||||
|
||||
return Optional.of(stockMove);
|
||||
}
|
||||
|
||||
protected Map<LocalDate, List<SaleOrderLine>> getAllSaleOrderLinePerDate(SaleOrder saleOrder) {
|
||||
|
||||
Map<LocalDate, List<SaleOrderLine>> saleOrderLinePerDateMap = new HashMap<>();
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
|
||||
|
||||
if (saleOrderLineServiceSupplyChain.computeUndeliveredQty(saleOrderLine).signum() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate dateKey = saleOrderLine.getEstimatedDelivDate();
|
||||
|
||||
if (dateKey == null) {
|
||||
dateKey = saleOrderLine.getSaleOrder().getDeliveryDate();
|
||||
}
|
||||
|
||||
List<SaleOrderLine> saleOrderLineLists = saleOrderLinePerDateMap.get(dateKey);
|
||||
|
||||
if (saleOrderLineLists == null) {
|
||||
saleOrderLineLists = new ArrayList<>();
|
||||
saleOrderLinePerDateMap.put(dateKey, saleOrderLineLists);
|
||||
}
|
||||
|
||||
saleOrderLineLists.add(saleOrderLine);
|
||||
}
|
||||
|
||||
return saleOrderLinePerDateMap;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setParentStockMoveLine(StockMove stockMove) {
|
||||
|
||||
for (StockMoveLine line : stockMove.getStockMoveLineList()) {
|
||||
if (line.getSaleOrderLine() != null) {
|
||||
line.setPackPriceSelect(line.getSaleOrderLine().getPackPriceSelect());
|
||||
StockMoveLine parentStockMoveLine =
|
||||
Beans.get(StockMoveLineRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.saleOrderLine = ?1 and self.stockMove = ?2",
|
||||
line.getSaleOrderLine().getParentLine(),
|
||||
stockMove)
|
||||
.fetchOne();
|
||||
line.setParentLine(parentStockMoveLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSaleOrderWithProductsToDeliver(SaleOrder saleOrder) throws AxelorException {
|
||||
|
||||
if (saleOrder.getSaleOrderLineList() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
|
||||
|
||||
if (this.isStockMoveProduct(saleOrderLine)) {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StockMove createStockMove(
|
||||
SaleOrder saleOrder, Company company, LocalDate estimatedDeliveryDate)
|
||||
throws AxelorException {
|
||||
StockLocation toStockLocation =
|
||||
stockConfigService.getCustomerVirtualStockLocation(
|
||||
stockConfigService.getStockConfig(company));
|
||||
|
||||
StockMove stockMove =
|
||||
stockMoveService.createStockMove(
|
||||
null,
|
||||
saleOrder.getDeliveryAddress(),
|
||||
company,
|
||||
saleOrder.getClientPartner(),
|
||||
saleOrder.getStockLocation(),
|
||||
toStockLocation,
|
||||
null,
|
||||
estimatedDeliveryDate,
|
||||
saleOrder.getDescription(),
|
||||
saleOrder.getShipmentMode(),
|
||||
saleOrder.getFreightCarrierMode(),
|
||||
saleOrder.getCarrierPartner(),
|
||||
saleOrder.getForwarderPartner(),
|
||||
saleOrder.getIncoterm(),
|
||||
StockMoveRepository.TYPE_OUTGOING);
|
||||
|
||||
stockMove.setToAddressStr(saleOrder.getDeliveryAddressStr());
|
||||
stockMove.setOriginId(saleOrder.getId());
|
||||
stockMove.setOriginTypeSelect(StockMoveRepository.ORIGIN_SALE_ORDER);
|
||||
stockMove.setOrigin(saleOrder.getSaleOrderSeq());
|
||||
stockMove.setStockMoveLineList(new ArrayList<>());
|
||||
stockMove.setTradingName(saleOrder.getTradingName());
|
||||
stockMove.setSpecificPackage(saleOrder.getSpecificPackage());
|
||||
setReservationDateTime(stockMove, saleOrder);
|
||||
stockMove.setNote(saleOrder.getDeliveryComments());
|
||||
stockMove.setPickingOrderComments(saleOrder.getPickingOrderComments());
|
||||
if (stockMove.getPartner() != null) {
|
||||
setDefaultAutoMailSettings(stockMove);
|
||||
}
|
||||
return stockMove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill reservation date time in stock move with sale order confirmation date time.
|
||||
*
|
||||
* @param stockMove
|
||||
* @param saleOrder
|
||||
*/
|
||||
protected void setReservationDateTime(StockMove stockMove, SaleOrder saleOrder) {
|
||||
LocalDateTime reservationDateTime = saleOrder.getConfirmationDateTime();
|
||||
if (reservationDateTime == null) {
|
||||
reservationDateTime = Beans.get(AppBaseService.class).getTodayDateTime().toLocalDateTime();
|
||||
}
|
||||
stockMove.setReservationDateTime(reservationDateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set automatic mail configuration from the partner.
|
||||
*
|
||||
* @param stockMove
|
||||
*/
|
||||
protected void setDefaultAutoMailSettings(StockMove stockMove) throws AxelorException {
|
||||
Partner partner = stockMove.getPartner();
|
||||
Company company = stockMove.getCompany();
|
||||
|
||||
PartnerStockSettings mailSettings =
|
||||
Beans.get(PartnerStockSettingsService.class).getOrCreateMailSettings(partner, company);
|
||||
|
||||
stockMove.setRealStockMoveAutomaticMail(mailSettings.getRealStockMoveAutomaticMail());
|
||||
stockMove.setRealStockMoveMessageTemplate(mailSettings.getRealStockMoveMessageTemplate());
|
||||
stockMove.setPlannedStockMoveAutomaticMail(mailSettings.getPlannedStockMoveAutomaticMail());
|
||||
stockMove.setPlannedStockMoveMessageTemplate(mailSettings.getPlannedStockMoveMessageTemplate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StockMoveLine createStockMoveLine(StockMove stockMove, SaleOrderLine saleOrderLine)
|
||||
throws AxelorException {
|
||||
return createStockMoveLine(
|
||||
stockMove,
|
||||
saleOrderLine,
|
||||
saleOrderLineServiceSupplyChain.computeUndeliveredQty(saleOrderLine));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StockMoveLine createStockMoveLine(
|
||||
StockMove stockMove, SaleOrderLine saleOrderLine, BigDecimal qty) throws AxelorException {
|
||||
int scale = Beans.get(AppBaseService.class).getNbDecimalDigitForSalePrice();
|
||||
|
||||
if (this.isStockMoveProduct(saleOrderLine)) {
|
||||
|
||||
Unit unit = saleOrderLine.getProduct().getUnit();
|
||||
BigDecimal priceDiscounted = saleOrderLine.getPriceDiscounted();
|
||||
BigDecimal requestedReservedQty =
|
||||
saleOrderLine.getRequestedReservedQty().subtract(saleOrderLine.getDeliveredQty());
|
||||
|
||||
BigDecimal companyUnitPriceUntaxed = saleOrderLine.getProduct().getCostPrice();
|
||||
if (unit != null && !unit.equals(saleOrderLine.getUnit())) {
|
||||
qty =
|
||||
unitConversionService.convert(
|
||||
saleOrderLine.getUnit(), unit, qty, qty.scale(), saleOrderLine.getProduct());
|
||||
priceDiscounted =
|
||||
unitConversionService.convert(
|
||||
unit, saleOrderLine.getUnit(), priceDiscounted, scale, saleOrderLine.getProduct());
|
||||
requestedReservedQty =
|
||||
unitConversionService.convert(
|
||||
saleOrderLine.getUnit(),
|
||||
unit,
|
||||
requestedReservedQty,
|
||||
requestedReservedQty.scale(),
|
||||
saleOrderLine.getProduct());
|
||||
}
|
||||
|
||||
BigDecimal taxRate = BigDecimal.ZERO;
|
||||
TaxLine taxLine = saleOrderLine.getTaxLine();
|
||||
if (taxLine != null) {
|
||||
taxRate = taxLine.getValue();
|
||||
}
|
||||
if (saleOrderLine.getQty().signum() != 0) {
|
||||
companyUnitPriceUntaxed =
|
||||
saleOrderLine
|
||||
.getCompanyExTaxTotal()
|
||||
.divide(saleOrderLine.getQty(), scale, RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
StockMoveLine stockMoveLine =
|
||||
stockMoveLineSupplychainService.createStockMoveLine(
|
||||
saleOrderLine.getProduct(),
|
||||
saleOrderLine.getProductName(),
|
||||
saleOrderLine.getDescription(),
|
||||
qty,
|
||||
requestedReservedQty,
|
||||
priceDiscounted,
|
||||
companyUnitPriceUntaxed,
|
||||
unit,
|
||||
stockMove,
|
||||
StockMoveLineService.TYPE_SALES,
|
||||
saleOrderLine.getSaleOrder().getInAti(),
|
||||
taxRate,
|
||||
saleOrderLine,
|
||||
null);
|
||||
|
||||
if (saleOrderLine.getDeliveryState() == 0) {
|
||||
saleOrderLine.setDeliveryState(SaleOrderLineRepository.DELIVERY_STATE_NOT_DELIVERED);
|
||||
}
|
||||
|
||||
if (stockMoveLine != null) {
|
||||
updatePackInfo(saleOrderLine, stockMoveLine);
|
||||
}
|
||||
|
||||
return stockMoveLine;
|
||||
} else if (saleOrderLine.getTypeSelect() == SaleOrderLineRepository.TYPE_PACK) {
|
||||
StockMoveLine stockMoveLine =
|
||||
stockMoveLineService.createStockMoveLine(
|
||||
null,
|
||||
saleOrderLine.getProductName(),
|
||||
saleOrderLine.getDescription(),
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
null,
|
||||
stockMove,
|
||||
StockMoveLineService.TYPE_SALES,
|
||||
saleOrderLine.getSaleOrder().getInAti(),
|
||||
null);
|
||||
|
||||
saleOrderLine.setDeliveryState(SaleOrderLineRepository.DELIVERY_STATE_NOT_DELIVERED);
|
||||
updatePackInfo(saleOrderLine, stockMoveLine);
|
||||
stockMoveLine.setSaleOrderLine(saleOrderLine);
|
||||
|
||||
return stockMoveLine;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updatePackInfo(SaleOrderLine saleOrderLine, StockMoveLine stockMoveLine) {
|
||||
stockMoveLine.setLineTypeSelect(saleOrderLine.getTypeSelect());
|
||||
stockMoveLine.setPackPriceSelect(saleOrderLine.getPackPriceSelect());
|
||||
stockMoveLine.setIsSubLine(saleOrderLine.getIsSubLine());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStockMoveProduct(SaleOrderLine saleOrderLine) throws AxelorException {
|
||||
return isStockMoveProduct(saleOrderLine, saleOrderLine.getSaleOrder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStockMoveProduct(SaleOrderLine saleOrderLine, SaleOrder saleOrder)
|
||||
throws AxelorException {
|
||||
|
||||
Company company = saleOrder.getCompany();
|
||||
|
||||
SupplyChainConfig supplyChainConfig =
|
||||
Beans.get(SupplyChainConfigService.class).getSupplyChainConfig(company);
|
||||
|
||||
Product product = saleOrderLine.getProduct();
|
||||
|
||||
return (product != null
|
||||
&& ((ProductRepository.PRODUCT_TYPE_SERVICE.equals(product.getProductTypeSelect())
|
||||
&& supplyChainConfig.getHasOutSmForNonStorableProduct()
|
||||
&& !product.getIsShippingCostsProduct())
|
||||
|| (ProductRepository.PRODUCT_TYPE_STORABLE.equals(product.getProductTypeSelect())
|
||||
&& supplyChainConfig.getHasOutSmForStorableProduct())));
|
||||
}
|
||||
|
||||
protected boolean existActiveStockMoveForSaleOrderLine(SaleOrderLine saleOrderLine) {
|
||||
|
||||
long stockMoveLineCount =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter(
|
||||
"self.saleOrderLine.id = ?1 AND self.stockMove.statusSelect in (?2,?3)",
|
||||
saleOrderLine.getId(),
|
||||
StockMoveRepository.STATUS_DRAFT,
|
||||
StockMoveRepository.STATUS_PLANNED)
|
||||
.count();
|
||||
|
||||
return stockMoveLineCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDeliveryState(SaleOrder saleOrder) throws AxelorException {
|
||||
saleOrder.setDeliveryState(computeDeliveryState(saleOrder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullyUpdateDeliveryState(SaleOrder saleOrder) throws AxelorException {
|
||||
saleOrderLineServiceSupplyChain.updateDeliveryStates(saleOrder.getSaleOrderLineList());
|
||||
updateDeliveryState(saleOrder);
|
||||
}
|
||||
|
||||
private int computeDeliveryState(SaleOrder saleOrder) throws AxelorException {
|
||||
|
||||
if (saleOrder.getSaleOrderLineList() == null || saleOrder.getSaleOrderLineList().isEmpty()) {
|
||||
return SaleOrderRepository.DELIVERY_STATE_NOT_DELIVERED;
|
||||
}
|
||||
|
||||
int deliveryState = -1;
|
||||
|
||||
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
|
||||
|
||||
if (this.isStockMoveProduct(saleOrderLine, saleOrder)) {
|
||||
|
||||
if (saleOrderLine.getDeliveryState() == SaleOrderLineRepository.DELIVERY_STATE_DELIVERED) {
|
||||
if (deliveryState == SaleOrderRepository.DELIVERY_STATE_NOT_DELIVERED
|
||||
|| deliveryState == SaleOrderRepository.DELIVERY_STATE_PARTIALLY_DELIVERED) {
|
||||
return SaleOrderRepository.DELIVERY_STATE_PARTIALLY_DELIVERED;
|
||||
} else {
|
||||
deliveryState = SaleOrderRepository.DELIVERY_STATE_DELIVERED;
|
||||
}
|
||||
} else if (saleOrderLine.getDeliveryState()
|
||||
== SaleOrderLineRepository.DELIVERY_STATE_NOT_DELIVERED) {
|
||||
if (deliveryState == SaleOrderRepository.DELIVERY_STATE_DELIVERED
|
||||
|| deliveryState == SaleOrderRepository.DELIVERY_STATE_PARTIALLY_DELIVERED) {
|
||||
return SaleOrderRepository.DELIVERY_STATE_PARTIALLY_DELIVERED;
|
||||
} else {
|
||||
deliveryState = SaleOrderRepository.DELIVERY_STATE_NOT_DELIVERED;
|
||||
}
|
||||
} else if (saleOrderLine.getDeliveryState()
|
||||
== SaleOrderLineRepository.DELIVERY_STATE_PARTIALLY_DELIVERED) {
|
||||
return SaleOrderRepository.DELIVERY_STATE_PARTIALLY_DELIVERED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return deliveryState;
|
||||
}
|
||||
|
||||
public Optional<SaleOrder> findSaleOrder(StockMove stockMove) {
|
||||
if (StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())
|
||||
&& stockMove.getOriginId() != null) {
|
||||
return Optional.ofNullable(saleOrderRepository.find(stockMove.getOriginId()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.AppSupplychain;
|
||||
import com.axelor.apps.base.db.CancelReason;
|
||||
import com.axelor.apps.base.db.repo.PartnerRepository;
|
||||
import com.axelor.apps.base.service.administration.SequenceService;
|
||||
import com.axelor.apps.base.service.user.UserService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.sale.exception.BlockedSaleOrderException;
|
||||
import com.axelor.apps.sale.service.app.AppSaleService;
|
||||
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowServiceImpl;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SaleOrderWorkflowServiceSupplychainImpl extends SaleOrderWorkflowServiceImpl {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected SaleOrderStockService saleOrderStockService;
|
||||
protected SaleOrderPurchaseService saleOrderPurchaseService;
|
||||
protected AppSupplychain appSupplychain;
|
||||
protected AccountingSituationSupplychainService accountingSituationSupplychainService;
|
||||
|
||||
@Inject
|
||||
public SaleOrderWorkflowServiceSupplychainImpl(
|
||||
SequenceService sequenceService,
|
||||
PartnerRepository partnerRepo,
|
||||
SaleOrderRepository saleOrderRepo,
|
||||
AppSaleService appSaleService,
|
||||
UserService userService,
|
||||
SaleOrderStockService saleOrderStockService,
|
||||
SaleOrderPurchaseService saleOrderPurchaseService,
|
||||
AppSupplychainService appSupplychainService,
|
||||
AccountingSituationSupplychainService accountingSituationSupplychainService) {
|
||||
|
||||
super(sequenceService, partnerRepo, saleOrderRepo, appSaleService, userService);
|
||||
|
||||
this.saleOrderStockService = saleOrderStockService;
|
||||
this.saleOrderPurchaseService = saleOrderPurchaseService;
|
||||
this.appSupplychain = appSupplychainService.getAppSupplychain();
|
||||
this.accountingSituationSupplychainService = accountingSituationSupplychainService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void confirmSaleOrder(SaleOrder saleOrder) throws AxelorException {
|
||||
|
||||
super.confirmSaleOrder(saleOrder);
|
||||
|
||||
if (appSupplychain.getPurchaseOrderGenerationAuto()) {
|
||||
saleOrderPurchaseService.createPurchaseOrders(saleOrder);
|
||||
}
|
||||
if (appSupplychain.getCustomerStockMoveGenerationAuto()) {
|
||||
saleOrderStockService.createStocksMovesFromSaleOrder(saleOrder);
|
||||
}
|
||||
int intercoSaleCreatingStatus =
|
||||
Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getIntercoSaleCreatingStatusSelect();
|
||||
if (saleOrder.getInterco()
|
||||
&& intercoSaleCreatingStatus == SaleOrderRepository.STATUS_ORDER_CONFIRMED) {
|
||||
Beans.get(IntercoService.class).generateIntercoPurchaseFromSale(saleOrder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void cancelSaleOrder(
|
||||
SaleOrder saleOrder, CancelReason cancelReason, String cancelReasonStr) {
|
||||
super.cancelSaleOrder(saleOrder, cancelReason, cancelReasonStr);
|
||||
try {
|
||||
accountingSituationSupplychainService.updateUsedCredit(saleOrder.getClientPartner());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(
|
||||
rollbackOn = {AxelorException.class, RuntimeException.class},
|
||||
ignore = {BlockedSaleOrderException.class}
|
||||
)
|
||||
public void finalizeQuotation(SaleOrder saleOrder) throws AxelorException {
|
||||
accountingSituationSupplychainService.updateCustomerCreditFromSaleOrder(saleOrder);
|
||||
super.finalizeQuotation(saleOrder);
|
||||
int intercoSaleCreatingStatus =
|
||||
Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getIntercoSaleCreatingStatusSelect();
|
||||
if (saleOrder.getInterco()
|
||||
&& intercoSaleCreatingStatus == SaleOrderRepository.STATUS_FINALIZED_QUOTATION) {
|
||||
Beans.get(IntercoService.class).generateIntercoPurchaseFromSale(saleOrder);
|
||||
}
|
||||
if (saleOrder.getCreatedByInterco()) {
|
||||
fillIntercompanyPurchaseOrderCounterpart(saleOrder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill interco purchase order counterpart is the sale order exist.
|
||||
*
|
||||
* @param saleOrder
|
||||
*/
|
||||
protected void fillIntercompanyPurchaseOrderCounterpart(SaleOrder saleOrder) {
|
||||
PurchaseOrder purchaseOrder =
|
||||
Beans.get(PurchaseOrderRepository.class)
|
||||
.all()
|
||||
.filter("self.purchaseOrderSeq = :purchaseOrderSeq")
|
||||
.bind("purchaseOrderSeq", saleOrder.getExternalReference())
|
||||
.fetchOne();
|
||||
if (purchaseOrder != null) {
|
||||
purchaseOrder.setExternalReference(saleOrder.getSaleOrderSeq());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {AxelorException.class, RuntimeException.class})
|
||||
public void completeSaleOrder(SaleOrder saleOrder) throws AxelorException {
|
||||
List<StockMove> stockMoves =
|
||||
Beans.get(StockMoveRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.originId = ? AND self.originTypeSelect = ?",
|
||||
saleOrder.getId(),
|
||||
"com.axelor.apps.sale.db.SaleOrder")
|
||||
.fetch();
|
||||
if (!stockMoves.isEmpty()) {
|
||||
for (StockMove stockMove : stockMoves) {
|
||||
if (stockMove.getStatusSelect() == 1 || stockMove.getStatusSelect() == 2) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.TYPE_FUNCTIONNAL,
|
||||
I18n.get(IExceptionMessage.SALE_ORDER_COMPLETE_MANUALLY));
|
||||
}
|
||||
}
|
||||
}
|
||||
super.completeSaleOrder(saleOrder);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.axelor.apps.supplychain.service;
|
||||
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.stock.db.StockConfig;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.exception.IExceptionMessage;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
|
||||
public class StockConfigService {
|
||||
public StockConfig getStockConfig(Company company) throws AxelorException {
|
||||
|
||||
StockConfig stockConfig = company.getStockConfig();
|
||||
|
||||
if (stockConfig == null) {
|
||||
throw new AxelorException(
|
||||
company,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.STOCK_CONFIG_1),
|
||||
company.getName());
|
||||
}
|
||||
|
||||
return stockConfig;
|
||||
}
|
||||
|
||||
public StockLocation getWarehouseNonCompliant(StockConfig stockConfig)
|
||||
throws AxelorException {
|
||||
if (stockConfig.getPickupDefaultStockLocation() == null) {
|
||||
throw new AxelorException(
|
||||
stockConfig,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.STOCK_CONFIG_1),
|
||||
stockConfig.getCompany().getName());
|
||||
}
|
||||
return stockConfig.getWarehouseNonCompliant();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.service.StockCorrectionServiceImpl;
|
||||
import java.util.Map;
|
||||
|
||||
public class StockCorrectionServiceSupplychainImpl extends StockCorrectionServiceImpl {
|
||||
|
||||
@Override
|
||||
public void getDefaultQtys(
|
||||
StockLocationLine stockLocationLine, Map<String, Object> stockCorrectionQtys) {
|
||||
super.getDefaultQtys(stockLocationLine, stockCorrectionQtys);
|
||||
stockCorrectionQtys.put("reservedQty", stockLocationLine.getReservedQty());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
|
||||
public interface StockLocationLineReservationService {
|
||||
|
||||
/**
|
||||
* If the requested quantity is greater than the allocated quantity, will allocate the requested
|
||||
* quantity in requesting stock move lines.
|
||||
*
|
||||
* @param stockLocationLine
|
||||
*/
|
||||
void allocateAll(StockLocationLine stockLocationLine) throws AxelorException;
|
||||
|
||||
/**
|
||||
* For every stock move lines, put reserved quantity at 0 without changing requested quantity.
|
||||
*
|
||||
* @param stockLocationLine
|
||||
* @throws AxelorException
|
||||
*/
|
||||
void deallocateAll(StockLocationLine stockLocationLine) throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class StockLocationLineReservationServiceImpl
|
||||
implements StockLocationLineReservationService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void allocateAll(StockLocationLine stockLocationLine) throws AxelorException {
|
||||
// qty to allocate is the minimum value between requested and current qty subtracted by reserved
|
||||
// qty
|
||||
BigDecimal qtyToAllocate =
|
||||
stockLocationLine
|
||||
.getRequestedReservedQty()
|
||||
.min(stockLocationLine.getCurrentQty())
|
||||
.subtract(stockLocationLine.getReservedQty());
|
||||
BigDecimal qtyAllocated =
|
||||
Beans.get(ReservedQtyService.class)
|
||||
.allocateReservedQuantityInSaleOrderLines(
|
||||
qtyToAllocate,
|
||||
stockLocationLine.getStockLocation(),
|
||||
stockLocationLine.getProduct(),
|
||||
stockLocationLine.getUnit());
|
||||
stockLocationLine.setReservedQty(stockLocationLine.getReservedQty().add(qtyAllocated));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void deallocateAll(StockLocationLine stockLocationLine) throws AxelorException {
|
||||
List<StockMoveLine> stockMoveLineList =
|
||||
Beans.get(StockMoveLineRepository.class)
|
||||
.all()
|
||||
.filter(
|
||||
"self.product = :product "
|
||||
+ "AND self.stockMove.fromStockLocation = :stockLocation "
|
||||
+ "AND self.stockMove.statusSelect = :planned "
|
||||
+ "AND (self.stockMove.availabilityRequest IS FALSE "
|
||||
+ "OR self.stockMove.availabilityRequest IS NULL) "
|
||||
+ "AND self.reservedQty > 0")
|
||||
.bind("product", stockLocationLine.getProduct())
|
||||
.bind("stockLocation", stockLocationLine.getStockLocation())
|
||||
.bind("planned", StockMoveRepository.STATUS_PLANNED)
|
||||
.fetch();
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
stockMoveLine.setReservedQty(BigDecimal.ZERO);
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
if (saleOrderLine != null) {
|
||||
saleOrderLine.setReservedQty(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
Beans.get(ReservedQtyService.class).updateReservedQty(stockLocationLine);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.WapHistoryRepository;
|
||||
import com.axelor.apps.stock.service.StockLocationLineServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockRulesService;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@RequestScoped
|
||||
public class StockLocationLineServiceSupplychainImpl extends StockLocationLineServiceImpl {
|
||||
|
||||
@Inject
|
||||
public StockLocationLineServiceSupplychainImpl(
|
||||
StockLocationLineRepository stockLocationLineRepo,
|
||||
StockRulesService stockRulesService,
|
||||
StockMoveLineRepository stockMoveLineRepository,
|
||||
AppBaseService appBaseService,
|
||||
WapHistoryRepository wapHistoryRepo) {
|
||||
super(
|
||||
stockLocationLineRepo,
|
||||
stockRulesService,
|
||||
stockMoveLineRepository,
|
||||
appBaseService,
|
||||
wapHistoryRepo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkIfEnoughStock(StockLocation stockLocation, Product product, BigDecimal qty)
|
||||
throws AxelorException {
|
||||
super.checkIfEnoughStock(stockLocation, product, qty);
|
||||
|
||||
if (Beans.get(AppSupplychainService.class).getAppSupplychain().getManageStockReservation()
|
||||
&& product.getStockManaged()) {
|
||||
StockLocationLine stockLocationLine = this.getStockLocationLine(stockLocation, product);
|
||||
if (stockLocationLine != null
|
||||
&& stockLocationLine
|
||||
.getCurrentQty()
|
||||
.subtract(stockLocationLine.getReservedQty())
|
||||
.compareTo(qty)
|
||||
< 0) {
|
||||
throw new AxelorException(
|
||||
stockLocationLine,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.LOCATION_LINE_RESERVED_QTY),
|
||||
stockLocationLine.getProduct().getName(),
|
||||
stockLocationLine.getProduct().getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getAvailableQty(StockLocation stockLocation, Product product) {
|
||||
StockLocationLine stockLocationLine = getStockLocationLine(stockLocation, product);
|
||||
BigDecimal availableQty = BigDecimal.ZERO;
|
||||
if (stockLocationLine != null) {
|
||||
availableQty = stockLocationLine.getCurrentQty().subtract(stockLocationLine.getReservedQty());
|
||||
}
|
||||
return availableQty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StockLocationLine updateLocationFromProduct(
|
||||
StockLocationLine stockLocationLine, Product product) throws AxelorException {
|
||||
stockLocationLine = super.updateLocationFromProduct(stockLocationLine, product);
|
||||
Beans.get(ReservedQtyService.class).updateRequestedReservedQty(stockLocationLine);
|
||||
return stockLocationLine;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.stock.service.StockLocationService;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.meta.CallMethod;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public interface StockLocationServiceSupplychain extends StockLocationService {
|
||||
|
||||
@CallMethod
|
||||
public BigDecimal getReservedQty(Long productId, Long locationId, Long companyId)
|
||||
throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Unit;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.apps.stock.service.StockLocationLineService;
|
||||
import com.axelor.apps.stock.service.StockLocationServiceImpl;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class StockLocationServiceSupplychainImpl extends StockLocationServiceImpl
|
||||
implements StockLocationServiceSupplychain {
|
||||
|
||||
@Inject
|
||||
public StockLocationServiceSupplychainImpl(
|
||||
StockLocationRepository stockLocationRepo,
|
||||
StockLocationLineService stockLocationLineService,
|
||||
ProductRepository productRepo) {
|
||||
super(stockLocationRepo, stockLocationLineService, productRepo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getReservedQty(Long productId, Long locationId, Long companyId)
|
||||
throws AxelorException {
|
||||
if (productId != null) {
|
||||
Product product = productRepo.find(productId);
|
||||
Unit productUnit = product.getUnit();
|
||||
UnitConversionService unitConversionService = Beans.get(UnitConversionService.class);
|
||||
|
||||
if (locationId == null || locationId == 0L) {
|
||||
List<StockLocation> stockLocations = getNonVirtualStockLocations(companyId);
|
||||
if (!stockLocations.isEmpty()) {
|
||||
BigDecimal reservedQty = BigDecimal.ZERO;
|
||||
for (StockLocation stockLocation : stockLocations) {
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getOrCreateStockLocationLine(
|
||||
stockLocationRepo.find(stockLocation.getId()), productRepo.find(productId));
|
||||
|
||||
if (stockLocationLine != null) {
|
||||
Unit stockLocationLineUnit = stockLocationLine.getUnit();
|
||||
reservedQty = reservedQty.add(stockLocationLine.getReservedQty());
|
||||
|
||||
if (productUnit != null && !productUnit.equals(stockLocationLineUnit)) {
|
||||
reservedQty =
|
||||
unitConversionService.convert(
|
||||
stockLocationLineUnit,
|
||||
productUnit,
|
||||
reservedQty,
|
||||
reservedQty.scale(),
|
||||
product);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reservedQty;
|
||||
}
|
||||
} else {
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getOrCreateStockLocationLine(
|
||||
stockLocationRepo.find(locationId), productRepo.find(productId));
|
||||
|
||||
if (stockLocationLine != null) {
|
||||
Unit stockLocationLineUnit = stockLocationLine.getUnit();
|
||||
|
||||
if (productUnit != null && !productUnit.equals(stockLocationLineUnit)) {
|
||||
return unitConversionService.convert(
|
||||
stockLocationLineUnit,
|
||||
productUnit,
|
||||
stockLocationLine.getReservedQty(),
|
||||
stockLocationLine.getReservedQty().scale(),
|
||||
product);
|
||||
}
|
||||
return stockLocationLine.getReservedQty();
|
||||
}
|
||||
}
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.InvoiceLine;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.meta.CallMethod;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface StockMoveInvoiceService {
|
||||
|
||||
Invoice createInvoice(
|
||||
StockMove stockMove,
|
||||
Integer operationSelect,
|
||||
List<Map<String, Object>> stockMoveLineListContext)
|
||||
throws AxelorException;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice createInvoiceFromSaleOrder(
|
||||
StockMove stockMove, SaleOrder saleOrder, Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice createInvoiceFromPurchaseOrder(
|
||||
StockMove stockMove, PurchaseOrder purchaseOrder, Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice createInvoiceFromOrderlessStockMove(
|
||||
StockMove stockMove, Map<Long, BigDecimal> qtyToInvoiceMap) throws AxelorException;
|
||||
|
||||
public Invoice extendInternalReference(StockMove stockMove, Invoice invoice);
|
||||
|
||||
public List<InvoiceLine> createInvoiceLines(
|
||||
Invoice invoice, List<StockMoveLine> stockMoveLineList, Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException;
|
||||
|
||||
public List<InvoiceLine> createInvoiceLine(
|
||||
Invoice invoice, StockMoveLine stockMoveLine, BigDecimal qty) throws AxelorException;
|
||||
|
||||
public List<Map<String, Object>> getStockMoveLinesToInvoice(StockMove stockMove);
|
||||
|
||||
@CallMethod
|
||||
public int getStockMoveInvoiceStatus(StockMove stockMove);
|
||||
}
|
||||
@ -0,0 +1,594 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AccountConfig;
|
||||
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.config.AccountConfigService;
|
||||
import com.axelor.apps.account.service.invoice.InvoiceService;
|
||||
import com.axelor.apps.account.service.invoice.generator.InvoiceGenerator;
|
||||
import com.axelor.apps.account.service.invoice.generator.InvoiceLineGenerator;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Sequence;
|
||||
import com.axelor.apps.base.service.AddressService;
|
||||
import com.axelor.apps.base.service.administration.SequenceService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
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.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.sale.service.app.AppSaleService;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
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.apps.tool.StringTool;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.CallMethod;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class StockMoveInvoiceServiceImpl implements StockMoveInvoiceService {
|
||||
|
||||
private SaleOrderInvoiceService saleOrderInvoiceService;
|
||||
private PurchaseOrderInvoiceService purchaseOrderInvoiceService;
|
||||
private StockMoveLineServiceSupplychain stockMoveLineServiceSupplychain;
|
||||
private InvoiceRepository invoiceRepository;
|
||||
private SaleOrderRepository saleOrderRepo;
|
||||
private PurchaseOrderRepository purchaseOrderRepo;
|
||||
private StockMoveLineRepository stockMoveLineRepository;
|
||||
|
||||
@Inject
|
||||
public StockMoveInvoiceServiceImpl(
|
||||
SaleOrderInvoiceService saleOrderInvoiceService,
|
||||
PurchaseOrderInvoiceService purchaseOrderInvoiceService,
|
||||
StockMoveLineServiceSupplychain stockMoveLineServiceSupplychain,
|
||||
InvoiceRepository invoiceRepository,
|
||||
SaleOrderRepository saleOrderRepo,
|
||||
PurchaseOrderRepository purchaseOrderRepo,
|
||||
StockMoveLineRepository stockMoveLineRepository) {
|
||||
this.saleOrderInvoiceService = saleOrderInvoiceService;
|
||||
this.purchaseOrderInvoiceService = purchaseOrderInvoiceService;
|
||||
this.stockMoveLineServiceSupplychain = stockMoveLineServiceSupplychain;
|
||||
this.invoiceRepository = invoiceRepository;
|
||||
this.saleOrderRepo = saleOrderRepo;
|
||||
this.purchaseOrderRepo = purchaseOrderRepo;
|
||||
this.stockMoveLineRepository = stockMoveLineRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice createInvoice(
|
||||
StockMove stockMove,
|
||||
Integer operationSelect,
|
||||
List<Map<String, Object>> stockMoveLineListContext)
|
||||
throws AxelorException {
|
||||
Invoice invoice = null;
|
||||
Map<Long, BigDecimal> qtyToInvoiceMap = new HashMap<>();
|
||||
if (operationSelect == StockMoveRepository.INVOICE_PARTILLY) {
|
||||
for (Map<String, Object> map : stockMoveLineListContext) {
|
||||
if (map.get("qtyToInvoice") != null) {
|
||||
BigDecimal qtyToInvoiceItem = new BigDecimal(map.get("qtyToInvoice").toString());
|
||||
BigDecimal remainingQty = new BigDecimal(map.get("remainingQty").toString());
|
||||
if (qtyToInvoiceItem.compareTo(BigDecimal.ZERO) != 0) {
|
||||
if (qtyToInvoiceItem.compareTo(remainingQty) == 1) {
|
||||
qtyToInvoiceItem = remainingQty;
|
||||
}
|
||||
Long stockMoveLineId = Long.parseLong(map.get("stockMoveLineId").toString());
|
||||
StockMoveLine stockMoveLine = stockMoveLineRepository.find(stockMoveLineId);
|
||||
addSubLineQty(qtyToInvoiceMap, qtyToInvoiceItem, stockMoveLineId);
|
||||
qtyToInvoiceMap.put(stockMoveLine.getId(), qtyToInvoiceItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (StockMoveLine stockMoveLine : stockMove.getStockMoveLineList()) {
|
||||
qtyToInvoiceMap.put(
|
||||
stockMoveLine.getId(),
|
||||
stockMoveLine.getRealQty().subtract(stockMoveLine.getQtyInvoiced()));
|
||||
}
|
||||
}
|
||||
|
||||
Long origin = stockMove.getOriginId();
|
||||
|
||||
if (StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
invoice = createInvoiceFromSaleOrder(stockMove, saleOrderRepo.find(origin), qtyToInvoiceMap);
|
||||
} else if (StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
invoice =
|
||||
createInvoiceFromPurchaseOrder(
|
||||
stockMove, purchaseOrderRepo.find(origin), qtyToInvoiceMap);
|
||||
} else {
|
||||
invoice = createInvoiceFromOrderlessStockMove(stockMove, qtyToInvoiceMap);
|
||||
}
|
||||
return invoice;
|
||||
}
|
||||
|
||||
private void addSubLineQty(
|
||||
Map<Long, BigDecimal> qtyToInvoiceMap, BigDecimal qtyToInvoiceItem, Long stockMoveLineId) {
|
||||
StockMoveLine stockMoveLine = stockMoveLineRepository.find(stockMoveLineId);
|
||||
|
||||
if (stockMoveLine.getProductTypeSelect().equals("pack")) {
|
||||
for (StockMoveLine subLine : stockMoveLine.getSubLineList()) {
|
||||
BigDecimal qty = BigDecimal.ZERO;
|
||||
if (stockMoveLine.getQty().compareTo(BigDecimal.ZERO) != 0) {
|
||||
qty =
|
||||
qtyToInvoiceItem
|
||||
.multiply(subLine.getQty())
|
||||
.divide(stockMoveLine.getQty(), 2, RoundingMode.HALF_EVEN);
|
||||
}
|
||||
qty = qty.setScale(2, RoundingMode.HALF_EVEN);
|
||||
qtyToInvoiceMap.put(subLine.getId(), qty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice createInvoiceFromSaleOrder(
|
||||
StockMove stockMove, SaleOrder saleOrder, Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException {
|
||||
|
||||
InvoiceGenerator invoiceGenerator =
|
||||
saleOrderInvoiceService.createInvoiceGenerator(saleOrder, stockMove.getIsReversion());
|
||||
|
||||
Invoice invoice = invoiceGenerator.generate();
|
||||
|
||||
invoiceGenerator.populate(
|
||||
invoice,
|
||||
this.createInvoiceLines(invoice, stockMove.getStockMoveLineList(), qtyToInvoiceMap));
|
||||
|
||||
if (invoice != null) {
|
||||
invoice.setSaleOrder(saleOrder);
|
||||
this.extendInternalReference(stockMove, invoice);
|
||||
invoice.setAddressStr(saleOrder.getMainInvoicingAddressStr());
|
||||
|
||||
// fill default advance payment invoice
|
||||
if (invoice.getOperationSubTypeSelect() != InvoiceRepository.OPERATION_SUB_TYPE_ADVANCE) {
|
||||
invoice.setAdvancePaymentInvoiceSet(
|
||||
Beans.get(InvoiceService.class).getDefaultAdvancePaymentInvoice(invoice));
|
||||
}
|
||||
|
||||
invoice.setPartnerTaxNbr(saleOrder.getClientPartner().getTaxNbr());
|
||||
invoice.setNote(saleOrder.getInvoiceComments());
|
||||
invoice.setProformaComments(saleOrder.getProformaComments());
|
||||
|
||||
// todo sophal
|
||||
invoice.setInvoiceId(
|
||||
Beans.get(SequenceService.class)
|
||||
.getSequenceNumber(
|
||||
getSequence(invoice), Beans.get(AppBaseService.class).getTodayDate()));
|
||||
|
||||
if (invoice != null) {
|
||||
Set<StockMove> stockMoveSet = invoice.getStockMoveSet();
|
||||
if (stockMoveSet == null) {
|
||||
stockMoveSet = new HashSet<>();
|
||||
invoice.setStockMoveSet(stockMoveSet);
|
||||
}
|
||||
stockMoveSet.add(stockMove);
|
||||
}
|
||||
|
||||
invoiceRepository.save(invoice);
|
||||
}
|
||||
|
||||
return invoice;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice createInvoiceFromPurchaseOrder(
|
||||
StockMove stockMove, PurchaseOrder purchaseOrder, Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException {
|
||||
|
||||
InvoiceGenerator invoiceGenerator =
|
||||
purchaseOrderInvoiceService.createInvoiceGenerator(
|
||||
purchaseOrder, stockMove.getIsReversion());
|
||||
|
||||
Invoice invoice = invoiceGenerator.generate();
|
||||
|
||||
invoiceGenerator.populate(
|
||||
invoice,
|
||||
this.createInvoiceLines(invoice, stockMove.getStockMoveLineList(), qtyToInvoiceMap));
|
||||
|
||||
if (invoice != null) {
|
||||
|
||||
this.extendInternalReference(stockMove, invoice);
|
||||
invoice.setAddressStr(
|
||||
Beans.get(AddressService.class).computeAddressStr(invoice.getAddress()));
|
||||
invoice.setPayerPartner(stockMove.getPayerPartner());
|
||||
invoice.setDeliveryPartner(stockMove.getDeliveryPartner());
|
||||
invoice.setStamp(stockMove.getStamp());
|
||||
invoice.setFixTax(stockMove.getFixTax());
|
||||
if (invoice != null) {
|
||||
Set<StockMove> stockMoveSet = invoice.getStockMoveSet();
|
||||
if (stockMoveSet == null) {
|
||||
stockMoveSet = new HashSet<>();
|
||||
invoice.setStockMoveSet(stockMoveSet);
|
||||
}
|
||||
stockMoveSet.add(stockMove);
|
||||
}
|
||||
|
||||
invoiceRepository.save(invoice);
|
||||
}
|
||||
return invoice;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice createInvoiceFromOrderlessStockMove(
|
||||
StockMove stockMove, Map<Long, BigDecimal> qtyToInvoiceMap) throws AxelorException {
|
||||
|
||||
int stockMoveType = stockMove.getTypeSelect();
|
||||
int invoiceOperationType;
|
||||
|
||||
if (stockMove.getIsReversion()) {
|
||||
if (stockMoveType == StockMoveRepository.TYPE_INCOMING) {
|
||||
invoiceOperationType = InvoiceRepository.OPERATION_TYPE_CLIENT_REFUND;
|
||||
} else if (stockMoveType == StockMoveRepository.TYPE_OUTGOING) {
|
||||
invoiceOperationType = InvoiceRepository.OPERATION_TYPE_SUPPLIER_REFUND;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (stockMoveType == StockMoveRepository.TYPE_INCOMING) {
|
||||
invoiceOperationType = InvoiceRepository.OPERATION_TYPE_SUPPLIER_PURCHASE;
|
||||
} else if (stockMoveType == StockMoveRepository.TYPE_OUTGOING) {
|
||||
invoiceOperationType = InvoiceRepository.OPERATION_TYPE_CLIENT_SALE;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
InvoiceGenerator invoiceGenerator =
|
||||
new InvoiceGeneratorSupplyChain(stockMove, invoiceOperationType) {
|
||||
|
||||
@Override
|
||||
public Invoice generate() throws AxelorException {
|
||||
|
||||
return super.createInvoiceHeader();
|
||||
}
|
||||
};
|
||||
|
||||
Invoice invoice = invoiceGenerator.generate();
|
||||
|
||||
invoiceGenerator.populate(
|
||||
invoice,
|
||||
this.createInvoiceLines(invoice, stockMove.getStockMoveLineList(), qtyToInvoiceMap));
|
||||
|
||||
if (invoice != null) {
|
||||
|
||||
this.extendInternalReference(stockMove, invoice);
|
||||
invoice.setAddressStr(
|
||||
Beans.get(AddressService.class).computeAddressStr(invoice.getAddress()));
|
||||
if (stockMoveType == StockMoveRepository.TYPE_OUTGOING) {
|
||||
invoice.setHeadOfficeAddress(stockMove.getPartner().getHeadOfficeAddress());
|
||||
}
|
||||
invoiceRepository.save(invoice);
|
||||
|
||||
if (invoice != null) {
|
||||
Set<StockMove> stockMoveSet = invoice.getStockMoveSet();
|
||||
if (stockMoveSet == null) {
|
||||
stockMoveSet = new HashSet<>();
|
||||
invoice.setStockMoveSet(stockMoveSet);
|
||||
}
|
||||
stockMoveSet.add(stockMove);
|
||||
}
|
||||
|
||||
invoiceRepository.save(invoice);
|
||||
}
|
||||
|
||||
return invoice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Invoice extendInternalReference(StockMove stockMove, Invoice invoice) {
|
||||
|
||||
invoice.setInternalReference(
|
||||
StringTool.cutTooLongString(
|
||||
stockMove.getStockMoveSeq() + ":" + invoice.getInternalReference()));
|
||||
|
||||
return invoice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InvoiceLine> createInvoiceLines(
|
||||
Invoice invoice, List<StockMoveLine> stockMoveLineList, Map<Long, BigDecimal> qtyToInvoiceMap)
|
||||
throws AxelorException {
|
||||
|
||||
List<InvoiceLine> invoiceLineList = new ArrayList<InvoiceLine>();
|
||||
|
||||
Map<StockMoveLine, InvoiceLine> packLineMap = Maps.newHashMap();
|
||||
boolean setPack = Beans.get(AppSaleService.class).getAppSale().getProductPackMgt();
|
||||
for (StockMoveLine stockMoveLine : getConsolidatedStockMoveLineList(stockMoveLineList)) {
|
||||
|
||||
List<InvoiceLine> invoiceLineListCreated = null;
|
||||
Long id = stockMoveLine.getId();
|
||||
if (qtyToInvoiceMap != null) {
|
||||
if (qtyToInvoiceMap.containsKey(id)) {
|
||||
invoiceLineListCreated =
|
||||
this.createInvoiceLine(invoice, stockMoveLine, qtyToInvoiceMap.get(id));
|
||||
}
|
||||
} else {
|
||||
invoiceLineListCreated =
|
||||
this.createInvoiceLine(
|
||||
invoice,
|
||||
stockMoveLine,
|
||||
stockMoveLine.getRealQty().subtract(stockMoveLine.getQtyInvoiced()));
|
||||
}
|
||||
|
||||
if (invoiceLineListCreated != null) {
|
||||
invoiceLineList.addAll(invoiceLineListCreated);
|
||||
if (setPack
|
||||
&& !invoiceLineListCreated.isEmpty()
|
||||
&& (stockMoveLine.getLineTypeSelect() == StockMoveLineRepository.TYPE_PACK
|
||||
|| stockMoveLine.getIsSubLine())) {
|
||||
packLineMap.put(stockMoveLine, invoiceLineListCreated.get(0));
|
||||
}
|
||||
}
|
||||
// Depending on stockMove type
|
||||
if (stockMoveLine.getSaleOrderLine() != null) {
|
||||
stockMoveLine.getSaleOrderLine().setInvoiced(true);
|
||||
} else if (stockMoveLine.getPurchaseOrderLine() != null) {
|
||||
stockMoveLine.getPurchaseOrderLine().setInvoiced(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (setPack) {
|
||||
for (StockMoveLine stockMoveLine : packLineMap.keySet()) {
|
||||
if (stockMoveLine.getLineTypeSelect() == StockMoveLineRepository.TYPE_PACK) {
|
||||
InvoiceLine invoiceLine = packLineMap.get(stockMoveLine);
|
||||
if (invoiceLine == null) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal totalPack = BigDecimal.ZERO;
|
||||
for (StockMoveLine subLine : stockMoveLine.getSubLineList()) {
|
||||
InvoiceLine subInvoiceLine = packLineMap.get(subLine);
|
||||
if (subInvoiceLine != null) {
|
||||
totalPack = totalPack.add(subInvoiceLine.getExTaxTotal());
|
||||
subInvoiceLine.setParentLine(invoiceLine);
|
||||
}
|
||||
}
|
||||
invoiceLine.setTotalPack(totalPack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return invoiceLineList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InvoiceLine> createInvoiceLine(
|
||||
Invoice invoice, StockMoveLine stockMoveLine, BigDecimal qty) throws AxelorException {
|
||||
|
||||
Product product = stockMoveLine.getProduct();
|
||||
boolean isTitleLine = false;
|
||||
|
||||
int sequence = InvoiceLineGenerator.DEFAULT_SEQUENCE;
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
PurchaseOrderLine purchaseOrderLine = stockMoveLine.getPurchaseOrderLine();
|
||||
|
||||
if (saleOrderLine != null) {
|
||||
if (saleOrderLine.getTypeSelect() == SaleOrderLineRepository.TYPE_PACK) {
|
||||
isTitleLine = true;
|
||||
}
|
||||
sequence = saleOrderLine.getSequence();
|
||||
} else if (purchaseOrderLine != null) {
|
||||
if (purchaseOrderLine.getIsTitleLine()) {
|
||||
isTitleLine = true;
|
||||
}
|
||||
sequence = purchaseOrderLine.getSequence();
|
||||
}
|
||||
|
||||
if (stockMoveLine.getRealQty().compareTo(BigDecimal.ZERO) == 0 && !isTitleLine) {
|
||||
return new ArrayList<InvoiceLine>();
|
||||
}
|
||||
if (product == null && !isTitleLine) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_INVOICE_1),
|
||||
stockMoveLine.getStockMove().getStockMoveSeq());
|
||||
}
|
||||
|
||||
InvoiceLineGenerator invoiceLineGenerator =
|
||||
new InvoiceLineGeneratorSupplyChain(
|
||||
invoice,
|
||||
product,
|
||||
stockMoveLine.getProductName(),
|
||||
stockMoveLine.getDescription(),
|
||||
qty,
|
||||
stockMoveLine.getUnit(),
|
||||
sequence,
|
||||
false,
|
||||
stockMoveLine.getSaleOrderLine(),
|
||||
stockMoveLine.getPurchaseOrderLine(),
|
||||
stockMoveLine,
|
||||
stockMoveLine.getIsSubLine(),
|
||||
stockMoveLine.getPackPriceSelect()) {
|
||||
@Override
|
||||
public List<InvoiceLine> creates() throws AxelorException {
|
||||
|
||||
InvoiceLine invoiceLine = this.createInvoiceLine();
|
||||
|
||||
List<InvoiceLine> invoiceLines = new ArrayList<InvoiceLine>();
|
||||
invoiceLines.add(invoiceLine);
|
||||
|
||||
return invoiceLines;
|
||||
}
|
||||
};
|
||||
|
||||
List<InvoiceLine> invoiceLines = invoiceLineGenerator.creates();
|
||||
for (InvoiceLine invoiceLine : invoiceLines) {
|
||||
invoiceLine.setStockMoveLine(stockMoveLine);
|
||||
invoiceLine.setTrackingNumber(stockMoveLine.getTrackingNumber());
|
||||
invoiceLine.setPvg(stockMoveLine.getPvg());
|
||||
invoiceLine.setPpa(stockMoveLine.getPpa());
|
||||
invoiceLine.setUg(stockMoveLine.getUg());
|
||||
invoiceLine.setShp(stockMoveLine.getShp());
|
||||
invoiceLine.setStklim(stockMoveLine.getStklim());
|
||||
}
|
||||
return invoiceLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of stock move lines consolidated by parent line (sale or purchase order).
|
||||
*
|
||||
* @param stockMoveLineList
|
||||
* @return
|
||||
* @throws AxelorException
|
||||
*/
|
||||
private List<StockMoveLine> getConsolidatedStockMoveLineList(
|
||||
List<StockMoveLine> stockMoveLineList) throws AxelorException {
|
||||
|
||||
Map<SaleOrderLine, List<StockMoveLine>> stockMoveLineSaleMap = new LinkedHashMap<>();
|
||||
Map<PurchaseOrderLine, List<StockMoveLine>> stockMoveLinePurchaseMap = new LinkedHashMap<>();
|
||||
List<StockMoveLine> resultList = new ArrayList<>();
|
||||
|
||||
List<StockMoveLine> list;
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
|
||||
if (stockMoveLine.getSaleOrderLine() != null) {
|
||||
list = stockMoveLineSaleMap.get(stockMoveLine.getSaleOrderLine());
|
||||
// if (list == null) {
|
||||
// list = new ArrayList<>();
|
||||
// stockMoveLineSaleMap.put(stockMoveLine.getSaleOrderLine(), list);
|
||||
// }
|
||||
// list.add(stockMoveLine);
|
||||
resultList.add(stockMoveLine);
|
||||
} else if (stockMoveLine.getPurchaseOrderLine() != null) {
|
||||
list = stockMoveLinePurchaseMap.get(stockMoveLine.getPurchaseOrderLine());
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
stockMoveLinePurchaseMap.put(stockMoveLine.getPurchaseOrderLine(), list);
|
||||
}
|
||||
list.add(stockMoveLine);
|
||||
} else { // if the stock move line does not have a parent line (sale or purchase order line)
|
||||
resultList.add(stockMoveLine);
|
||||
}
|
||||
}
|
||||
|
||||
for (List<StockMoveLine> stockMoveLines : stockMoveLineSaleMap.values()) {
|
||||
resultList.add(stockMoveLineServiceSupplychain.getMergedStockMoveLine(stockMoveLines));
|
||||
}
|
||||
for (List<StockMoveLine> stockMoveLines : stockMoveLinePurchaseMap.values()) {
|
||||
resultList.add(stockMoveLineServiceSupplychain.getMergedStockMoveLine(stockMoveLines));
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getStockMoveLinesToInvoice(StockMove stockMove) {
|
||||
List<Map<String, Object>> stockMoveLines = new ArrayList<Map<String, Object>>();
|
||||
|
||||
for (StockMoveLine stockMoveLine : stockMove.getStockMoveLineList()) {
|
||||
|
||||
if (stockMoveLine.getIsSubLine()) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal qty = stockMoveLine.getRealQty().subtract(stockMoveLine.getQtyInvoiced());
|
||||
if (qty.compareTo(BigDecimal.ZERO) != 0) {
|
||||
Map<String, Object> stockMoveLineMap = new HashMap<>();
|
||||
stockMoveLineMap.put("productCode", stockMoveLine.getProduct().getCode());
|
||||
stockMoveLineMap.put("productName", stockMoveLine.getProductName());
|
||||
stockMoveLineMap.put("remainingQty", qty);
|
||||
stockMoveLineMap.put("realQty", stockMoveLine.getRealQty());
|
||||
stockMoveLineMap.put("qtyInvoiced", stockMoveLine.getQtyInvoiced());
|
||||
stockMoveLineMap.put("qtyToInvoice", BigDecimal.ZERO);
|
||||
stockMoveLineMap.put("invoiceAll", false);
|
||||
stockMoveLineMap.put("isSubline", stockMoveLine.getIsSubLine());
|
||||
stockMoveLineMap.put("stockMoveLineId", stockMoveLine.getId());
|
||||
stockMoveLines.add(stockMoveLineMap);
|
||||
}
|
||||
}
|
||||
return stockMoveLines;
|
||||
}
|
||||
|
||||
@CallMethod
|
||||
public int getStockMoveInvoiceStatus(StockMove stockMove) {
|
||||
Integer invoiceStatus = 0;
|
||||
if (stockMove != null && stockMove.getId() != null) {
|
||||
stockMove = Beans.get(StockMoveRepository.class).find(stockMove.getId());
|
||||
|
||||
if (stockMove.getInvoiceSet() != null && !stockMove.getInvoiceSet().isEmpty()) {
|
||||
Double totalInvoicedQty =
|
||||
stockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.mapToDouble(sml -> Double.parseDouble(sml.getQtyInvoiced().toString()))
|
||||
.sum();
|
||||
Double totalRealQty =
|
||||
stockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.mapToDouble(sml -> Double.parseDouble(sml.getRealQty().toString()))
|
||||
.sum();
|
||||
if (totalRealQty.compareTo(totalInvoicedQty) == 1) {
|
||||
invoiceStatus = 1;
|
||||
} else if (totalRealQty.compareTo(totalInvoicedQty) == 0) {
|
||||
invoiceStatus = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return invoiceStatus;
|
||||
}
|
||||
|
||||
protected Sequence getSequence(Invoice invoice) throws AxelorException {
|
||||
|
||||
AccountConfigService accountConfigService = Beans.get(AccountConfigService.class);
|
||||
|
||||
AccountConfig accountConfig = accountConfigService.getAccountConfig(invoice.getCompany());
|
||||
switch (invoice.getOperationTypeSelect()) {
|
||||
case InvoiceRepository.OPERATION_TYPE_SUPPLIER_PURCHASE:
|
||||
return accountConfigService.getSuppInvSequence(accountConfig);
|
||||
|
||||
case InvoiceRepository.OPERATION_TYPE_SUPPLIER_REFUND:
|
||||
return accountConfigService.getSuppRefSequence(accountConfig);
|
||||
|
||||
case InvoiceRepository.OPERATION_TYPE_CLIENT_SALE:
|
||||
return accountConfigService.getCustInvSequence(accountConfig);
|
||||
case InvoiceRepository.OPERATION_TYPE_CLIENT_REFUND:
|
||||
return accountConfigService.getCustRefSequence(accountConfig);
|
||||
|
||||
default:
|
||||
throw new AxelorException(
|
||||
invoice,
|
||||
TraceBackRepository.CATEGORY_MISSING_FIELD,
|
||||
I18n.get("Invoice type missing %s"),
|
||||
invoice.getInvoiceId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Unit;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public interface StockMoveLineServiceSupplychain {
|
||||
|
||||
/**
|
||||
* Compared to the method in module stock, it adds the requested reserved qty. Allows to create
|
||||
* stock move from supplychain module with requested reserved qty. We also add sale order line and
|
||||
* purchase order line to create the link.
|
||||
*
|
||||
* @param product
|
||||
* @param productName
|
||||
* @param description
|
||||
* @param quantity
|
||||
* @param requestedReservedQty
|
||||
* @param unitPrice
|
||||
* @param unit
|
||||
* @param stockMove
|
||||
* @param type
|
||||
* @param taxed
|
||||
* @param taxRate
|
||||
* @param saleOrderLine
|
||||
* @param purchaseOrderLine
|
||||
* @return the created stock move line.
|
||||
* @throws AxelorException
|
||||
*/
|
||||
public StockMoveLine createStockMoveLine(
|
||||
Product product,
|
||||
String productName,
|
||||
String description,
|
||||
BigDecimal quantity,
|
||||
BigDecimal requestedReservedQty,
|
||||
BigDecimal unitPrice,
|
||||
BigDecimal companyUnitPriceUntaxed,
|
||||
Unit unit,
|
||||
StockMove stockMove,
|
||||
int type,
|
||||
boolean taxed,
|
||||
BigDecimal taxRate,
|
||||
SaleOrderLine saleOrderLine,
|
||||
PurchaseOrderLine purchaseOrderLine)
|
||||
throws AxelorException;
|
||||
|
||||
/**
|
||||
* Get a merged stock move line.
|
||||
*
|
||||
* @param stockMoveLineList
|
||||
* @return
|
||||
* @throws AxelorException
|
||||
*/
|
||||
StockMoveLine getMergedStockMoveLine(List<StockMoveLine> stockMoveLineList)
|
||||
throws AxelorException;
|
||||
|
||||
boolean isAvailableProduct(StockMove stockMove, StockMoveLine stockMoveLine);
|
||||
|
||||
void setInvoiceStatus(StockMoveLine stockMoveLine);
|
||||
}
|
||||
@ -0,0 +1,957 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AnalyticMoveLine;
|
||||
import com.axelor.apps.account.db.repo.AnalyticMoveLineRepository;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.base.db.Unit;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.base.service.PriceListService;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
import com.axelor.apps.base.service.tax.AccountManagementService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderLineRepository;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderLineService;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderLineServiceImpl;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderServiceImpl;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.stock.db.InternalTrackingNumber;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.TrackingNumber;
|
||||
import com.axelor.apps.stock.db.TrackingNumberConfiguration;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockLocationRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.stock.db.repo.TrackingNumberRepository;
|
||||
import com.axelor.apps.stock.service.StockLocationLineService;
|
||||
import com.axelor.apps.stock.service.StockLocationLineServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockMoveLineServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockMoveToolService;
|
||||
import com.axelor.apps.stock.service.TrackingNumberService;
|
||||
import com.axelor.apps.stock.service.WeightedAveragePriceService;
|
||||
import com.axelor.apps.stock.service.app.AppStockService;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequestScoped
|
||||
public class StockMoveLineServiceSupplychainImpl extends StockMoveLineServiceImpl
|
||||
implements StockMoveLineServiceSupplychain {
|
||||
|
||||
protected AccountManagementService accountManagementService;
|
||||
protected PriceListService priceListService;
|
||||
protected PurchaseOrderLineRepository purchaseOrderLineRepo;
|
||||
protected PurchaseOrderLineService purchaseOrderLineService;
|
||||
protected List<StockMoveLine> stockMoveLines;
|
||||
|
||||
@Inject
|
||||
public StockMoveLineServiceSupplychainImpl(
|
||||
TrackingNumberService trackingNumberService,
|
||||
AppBaseService appBaseService,
|
||||
AppStockService appStockService,
|
||||
StockMoveToolService stockMoveToolService,
|
||||
StockMoveLineRepository stockMoveLineRepository,
|
||||
StockLocationLineService stockLocationLineService,
|
||||
UnitConversionService unitConversionService,
|
||||
WeightedAveragePriceService weightedAveragePriceService,
|
||||
TrackingNumberRepository trackingNumberRepo,
|
||||
AccountManagementService accountManagementService,
|
||||
PurchaseOrderLineRepository purchaseOrderLineRepository,
|
||||
PurchaseOrderLineService purchaseOrderLineService,
|
||||
PriceListService priceListService) {
|
||||
super(
|
||||
trackingNumberService,
|
||||
appBaseService,
|
||||
appStockService,
|
||||
stockMoveToolService,
|
||||
stockMoveLineRepository,
|
||||
stockLocationLineService,
|
||||
unitConversionService,
|
||||
weightedAveragePriceService,
|
||||
trackingNumberRepo);
|
||||
this.accountManagementService = accountManagementService;
|
||||
this.priceListService = priceListService;
|
||||
this.stockMoveLines = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StockMoveLine createStockMoveLine(
|
||||
Product product,
|
||||
String productName,
|
||||
String description,
|
||||
BigDecimal quantity,
|
||||
BigDecimal requestedReservedQty,
|
||||
BigDecimal unitPrice,
|
||||
BigDecimal companyUnitPriceUntaxed,
|
||||
Unit unit,
|
||||
StockMove stockMove,
|
||||
int type,
|
||||
boolean taxed,
|
||||
BigDecimal taxRate,
|
||||
SaleOrderLine saleOrderLine,
|
||||
PurchaseOrderLine purchaseOrderLine)
|
||||
throws AxelorException {
|
||||
if (product != null) {
|
||||
|
||||
StockMoveLine stockMoveLine =
|
||||
generateStockMoveLineConvertingUnitPrice(
|
||||
product,
|
||||
productName,
|
||||
description,
|
||||
quantity,
|
||||
unitPrice,
|
||||
companyUnitPriceUntaxed,
|
||||
unit,
|
||||
stockMove,
|
||||
taxed,
|
||||
taxRate);
|
||||
stockMoveLine.setRequestedReservedQty(BigDecimal.ZERO);
|
||||
stockMoveLine.setSaleOrderLine(saleOrderLine);
|
||||
stockMoveLine.setPurchaseOrderLine(purchaseOrderLine);
|
||||
if (saleOrderLine != null) {
|
||||
stockMoveLine.setShp(saleOrderLine.getShp());
|
||||
stockMoveLine.setPpa(saleOrderLine.getPpa());
|
||||
stockMoveLine.setPvg(saleOrderLine.getPvg());
|
||||
stockMoveLine.setStklim(saleOrderLine.getStklim());
|
||||
stockMoveLine.setUg(saleOrderLine.getUg());
|
||||
}
|
||||
TrackingNumberConfiguration trackingNumberConfiguration =
|
||||
product.getTrackingNumberConfiguration();
|
||||
|
||||
if(purchaseOrderLine != null){
|
||||
System.out.println("Stepper loading ::::::::::::::::");
|
||||
// this.inheretAccountAnalyticMoveLine(purchaseOrderLine,stockMoveLine);
|
||||
}
|
||||
|
||||
return assignOrGenerateTrackingNumber(
|
||||
stockMoveLine, stockMove, product, trackingNumberConfiguration, type);
|
||||
} else {
|
||||
return this.createStockMoveLine(
|
||||
product,
|
||||
productName,
|
||||
description,
|
||||
quantity,
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
companyUnitPriceUntaxed,
|
||||
unit,
|
||||
stockMove,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
private void inheretAccountAnalyticMoveLine(PurchaseOrderLine purchaseOrderLine, StockMoveLine stockMoveLine) {
|
||||
List<AnalyticMoveLine> analyticMoveLines = purchaseOrderLine.getAnalyticMoveLineList();
|
||||
if(analyticMoveLines.size() != 0){
|
||||
for (AnalyticMoveLine analyticMoveLine : analyticMoveLines) {
|
||||
AnalyticMoveLine aml = Beans.get(AnalyticMoveLineRepository.class).copy(analyticMoveLine, false);
|
||||
aml.setStockMoveLine(stockMoveLine);
|
||||
stockMoveLine.addAnalyticMoveLineListItem(aml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StockMoveLine compute(StockMoveLine stockMoveLine, StockMove stockMove)
|
||||
throws AxelorException {
|
||||
|
||||
// the case when stockMove is null is made in super.
|
||||
if (stockMove == null) {
|
||||
return super.compute(stockMoveLine, null);
|
||||
}
|
||||
|
||||
// if this is a pack do not compute price
|
||||
if (stockMoveLine.getProduct() == null
|
||||
|| (stockMoveLine.getLineTypeSelect() != null
|
||||
&& stockMoveLine.getLineTypeSelect() == StockMoveLineRepository.TYPE_PACK)) {
|
||||
return stockMoveLine;
|
||||
}
|
||||
|
||||
if (stockMove.getOriginId() != null && stockMove.getOriginId() != 0L) {
|
||||
// the stock move comes from a sale or purchase order, we take the price from the order.
|
||||
stockMoveLine = computeFromOrder(stockMoveLine, stockMove);
|
||||
} else {
|
||||
stockMoveLine = super.compute(stockMoveLine, stockMove);
|
||||
}
|
||||
return stockMoveLine;
|
||||
}
|
||||
|
||||
protected StockMoveLine computeFromOrder(StockMoveLine stockMoveLine, StockMove stockMove)
|
||||
throws AxelorException {
|
||||
BigDecimal unitPriceUntaxed = stockMoveLine.getUnitPriceUntaxed();
|
||||
BigDecimal unitPriceTaxed = stockMoveLine.getUnitPriceTaxed();
|
||||
Unit orderUnit = null;
|
||||
if (StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
if (saleOrderLine == null) {
|
||||
// log the exception
|
||||
TraceBackService.trace(
|
||||
new AxelorException(
|
||||
TraceBackRepository.TYPE_TECHNICAL,
|
||||
IExceptionMessage.STOCK_MOVE_MISSING_SALE_ORDER,
|
||||
stockMove.getOriginId(),
|
||||
stockMove.getName()));
|
||||
} else {
|
||||
unitPriceUntaxed = saleOrderLine.getPriceDiscounted();
|
||||
unitPriceTaxed = saleOrderLine.getInTaxPrice();
|
||||
orderUnit = saleOrderLine.getUnit();
|
||||
}
|
||||
} else if (StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
PurchaseOrderLine purchaseOrderLine = stockMoveLine.getPurchaseOrderLine();
|
||||
if (purchaseOrderLine == null) {
|
||||
// log the exception
|
||||
TraceBackService.trace(
|
||||
new AxelorException(
|
||||
TraceBackRepository.TYPE_TECHNICAL,
|
||||
IExceptionMessage.STOCK_MOVE_MISSING_PURCHASE_ORDER,
|
||||
stockMove.getOriginId(),
|
||||
stockMove.getName()));
|
||||
} else {
|
||||
unitPriceUntaxed = purchaseOrderLine.getPrice();
|
||||
unitPriceTaxed = purchaseOrderLine.getInTaxPrice();
|
||||
orderUnit = purchaseOrderLine.getUnit();
|
||||
}
|
||||
}
|
||||
|
||||
stockMoveLine.setUnitPriceUntaxed(unitPriceUntaxed);
|
||||
stockMoveLine.setUnitPriceTaxed(unitPriceTaxed);
|
||||
|
||||
Unit stockUnit = getStockUnit(stockMoveLine);
|
||||
return convertUnitPrice(stockMoveLine, orderUnit, stockUnit);
|
||||
}
|
||||
|
||||
protected StockMoveLine convertUnitPrice(StockMoveLine stockMoveLine, Unit fromUnit, Unit toUnit)
|
||||
throws AxelorException {
|
||||
// convert units
|
||||
if (toUnit != null && fromUnit != null) {
|
||||
BigDecimal unitPriceUntaxed =
|
||||
unitConversionService.convert(
|
||||
fromUnit,
|
||||
toUnit,
|
||||
stockMoveLine.getUnitPriceUntaxed(),
|
||||
appBaseService.getNbDecimalDigitForSalePrice(),
|
||||
null);
|
||||
BigDecimal unitPriceTaxed =
|
||||
unitConversionService.convert(
|
||||
fromUnit,
|
||||
toUnit,
|
||||
stockMoveLine.getUnitPriceTaxed(),
|
||||
appBaseService.getNbDecimalDigitForSalePrice(),
|
||||
null);
|
||||
stockMoveLine.setUnitPriceUntaxed(unitPriceUntaxed);
|
||||
stockMoveLine.setUnitPriceTaxed(unitPriceTaxed);
|
||||
}
|
||||
return stockMoveLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StockMoveLine splitStockMoveLine(
|
||||
StockMoveLine stockMoveLine, BigDecimal qty, TrackingNumber trackingNumber)
|
||||
throws AxelorException {
|
||||
|
||||
StockMoveLine newStockMoveLine = super.splitStockMoveLine(stockMoveLine, qty, trackingNumber);
|
||||
|
||||
BigDecimal reservedQtyAfterSplit =
|
||||
BigDecimal.ZERO.max(stockMoveLine.getRequestedReservedQty().subtract(qty));
|
||||
BigDecimal reservedQtyInNewLine = stockMoveLine.getRequestedReservedQty().min(qty);
|
||||
stockMoveLine.setRequestedReservedQty(reservedQtyAfterSplit);
|
||||
newStockMoveLine.setRequestedReservedQty(reservedQtyInNewLine);
|
||||
newStockMoveLine.setSaleOrderLine(stockMoveLine.getSaleOrderLine());
|
||||
newStockMoveLine.setPurchaseOrderLine(stockMoveLine.getPurchaseOrderLine());
|
||||
return newStockMoveLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAvailableQty(StockMoveLine stockMoveLine, StockLocation stockLocation) {
|
||||
BigDecimal availableQty = BigDecimal.ZERO;
|
||||
BigDecimal availableQtyForProduct = BigDecimal.ZERO;
|
||||
|
||||
if (stockMoveLine.getProduct() != null) {
|
||||
if (stockMoveLine.getProduct().getTrackingNumberConfiguration() != null) {
|
||||
|
||||
if (stockMoveLine.getTrackingNumber() != null) {
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getDetailLocationLine(
|
||||
stockLocation, stockMoveLine.getProduct(), stockMoveLine.getTrackingNumber());
|
||||
|
||||
if (stockLocationLine != null) {
|
||||
availableQty =
|
||||
stockLocationLine
|
||||
.getCurrentQty()
|
||||
.add(stockMoveLine.getReservedQty())
|
||||
.subtract(stockLocationLine.getReservedQty());
|
||||
}
|
||||
}
|
||||
|
||||
if (availableQty.compareTo(stockMoveLine.getRealQty()) < 0) {
|
||||
StockLocationLine stockLocationLineForProduct =
|
||||
stockLocationLineService.getStockLocationLine(
|
||||
stockLocation, stockMoveLine.getProduct());
|
||||
|
||||
if (stockLocationLineForProduct != null) {
|
||||
availableQtyForProduct =
|
||||
stockLocationLineForProduct
|
||||
.getCurrentQty()
|
||||
.add(stockMoveLine.getReservedQty())
|
||||
.subtract(stockLocationLineForProduct.getReservedQty());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
StockLocationLine stockLocationLine =
|
||||
stockLocationLineService.getStockLocationLine(
|
||||
stockLocation, stockMoveLine.getProduct());
|
||||
|
||||
if (stockLocationLine != null) {
|
||||
availableQty =
|
||||
stockLocationLine
|
||||
.getCurrentQty()
|
||||
.add(stockMoveLine.getReservedQty())
|
||||
.subtract(stockLocationLine.getReservedQty());
|
||||
}
|
||||
}
|
||||
}
|
||||
stockMoveLine.setAvailableQty(availableQty);
|
||||
stockMoveLine.setAvailableQtyForProduct(availableQtyForProduct);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StockMoveLine getMergedStockMoveLine(List<StockMoveLine> stockMoveLineList)
|
||||
throws AxelorException {
|
||||
|
||||
if (stockMoveLineList == null || stockMoveLineList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stockMoveLineList.size() == 1) {
|
||||
return stockMoveLineList.get(0);
|
||||
}
|
||||
|
||||
SaleOrderLine saleOrderLine = stockMoveLineList.get(0).getSaleOrderLine();
|
||||
PurchaseOrderLine purchaseOrderLine = stockMoveLineList.get(0).getPurchaseOrderLine();
|
||||
|
||||
Product product;
|
||||
String productName;
|
||||
String description;
|
||||
BigDecimal quantity = BigDecimal.ZERO;
|
||||
Unit unit;
|
||||
|
||||
if (saleOrderLine != null) {
|
||||
product = saleOrderLine.getProduct();
|
||||
productName = saleOrderLine.getProductName();
|
||||
description = saleOrderLine.getDescription();
|
||||
unit = saleOrderLine.getUnit();
|
||||
|
||||
} else if (purchaseOrderLine != null) {
|
||||
product = purchaseOrderLine.getProduct();
|
||||
productName = purchaseOrderLine.getProductName();
|
||||
description = purchaseOrderLine.getDescription();
|
||||
unit = purchaseOrderLine.getUnit();
|
||||
|
||||
} else {
|
||||
return null; // shouldn't ever happen or you misused this function
|
||||
}
|
||||
|
||||
for (StockMoveLine stockMoveLine : stockMoveLineList) {
|
||||
quantity = quantity.add(stockMoveLine.getRealQty());
|
||||
}
|
||||
|
||||
StockMoveLine generatedStockMoveLine =
|
||||
createStockMoveLine(
|
||||
product,
|
||||
productName,
|
||||
description,
|
||||
quantity,
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
BigDecimal.ZERO,
|
||||
unit,
|
||||
null,
|
||||
null);
|
||||
|
||||
generatedStockMoveLine.setSaleOrderLine(saleOrderLine);
|
||||
generatedStockMoveLine.setPurchaseOrderLine(purchaseOrderLine);
|
||||
return generatedStockMoveLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation copy fields from sale order line and purchase order line if the link
|
||||
* exists.
|
||||
*
|
||||
* @param stockMove
|
||||
* @param stockMoveLine
|
||||
* @param company
|
||||
*/
|
||||
@Override
|
||||
public void setProductInfo(StockMove stockMove, StockMoveLine stockMoveLine, Company company)
|
||||
throws AxelorException {
|
||||
Preconditions.checkNotNull(stockMoveLine);
|
||||
Preconditions.checkNotNull(company);
|
||||
|
||||
Product product = stockMoveLine.getProduct();
|
||||
|
||||
if (product == null) {
|
||||
return;
|
||||
}
|
||||
super.setProductInfo(stockMove, stockMoveLine, company);
|
||||
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
PurchaseOrderLine purchaseOrderLine = stockMoveLine.getPurchaseOrderLine();
|
||||
|
||||
if (saleOrderLine != null) {
|
||||
setProductInfoFromSaleOrder(stockMoveLine, saleOrderLine);
|
||||
}
|
||||
if (purchaseOrderLine != null) {
|
||||
setProductInfoFromPurchaseOrder(stockMoveLine, purchaseOrderLine);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setProductInfoFromSaleOrder(
|
||||
StockMoveLine stockMoveLine, SaleOrderLine saleOrderLine) {
|
||||
|
||||
stockMoveLine.setUnit(saleOrderLine.getUnit());
|
||||
stockMoveLine.setProductName(saleOrderLine.getProductName());
|
||||
if (Strings.isNullOrEmpty(stockMoveLine.getDescription())) {
|
||||
stockMoveLine.setDescription(saleOrderLine.getDescription());
|
||||
}
|
||||
stockMoveLine.setQty(saleOrderLine.getQty());
|
||||
}
|
||||
|
||||
protected void setProductInfoFromPurchaseOrder(
|
||||
StockMoveLine stockMoveLine, PurchaseOrderLine purchaseOrderLine) {
|
||||
|
||||
stockMoveLine.setUnit(purchaseOrderLine.getUnit());
|
||||
stockMoveLine.setProductName(purchaseOrderLine.getProductName());
|
||||
if (Strings.isNullOrEmpty(stockMoveLine.getDescription())) {
|
||||
stockMoveLine.setDescription(purchaseOrderLine.getDescription());
|
||||
}
|
||||
stockMoveLine.setQty(purchaseOrderLine.getQty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailableProduct(StockMove stockMove, StockMoveLine stockMoveLine) {
|
||||
if (stockMoveLine.getProduct() == null
|
||||
|| (stockMoveLine.getProduct() != null && !stockMoveLine.getProduct().getStockManaged())) {
|
||||
return true;
|
||||
}
|
||||
updateAvailableQty(stockMoveLine, stockMove.getFromStockLocation());
|
||||
BigDecimal availableQty = stockMoveLine.getAvailableQty();
|
||||
if (stockMoveLine.getProduct().getTrackingNumberConfiguration() != null
|
||||
&& stockMoveLine.getTrackingNumber() == null) {
|
||||
availableQty = stockMoveLine.getAvailableQtyForProduct();
|
||||
}
|
||||
BigDecimal realQty = stockMoveLine.getRealQty();
|
||||
if (availableQty.compareTo(realQty) < 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInvoiceStatus(StockMoveLine stockMoveLine) {
|
||||
if (stockMoveLine.getQtyInvoiced().compareTo(BigDecimal.ZERO) == 0) {
|
||||
stockMoveLine.setAvailableStatus(I18n.get("Not invoiced"));
|
||||
stockMoveLine.setAvailableStatusSelect(3);
|
||||
} else if (stockMoveLine.getQtyInvoiced().compareTo(stockMoveLine.getRealQty()) == -1) {
|
||||
stockMoveLine.setAvailableStatus(I18n.get("Partially invoiced"));
|
||||
stockMoveLine.setAvailableStatusSelect(4);
|
||||
} else if (stockMoveLine.getQtyInvoiced().compareTo(stockMoveLine.getRealQty()) == 0) {
|
||||
stockMoveLine.setAvailableStatus(I18n.get("Invoiced"));
|
||||
stockMoveLine.setAvailableStatusSelect(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void resetValorization(
|
||||
Long productId,
|
||||
Long stockMoveId,
|
||||
LocalDate toDateTime,
|
||||
Boolean excludeValotisationStocklocation) {
|
||||
StockMove stockMove = Beans.get(StockMoveRepository.class).find(stockMoveId);
|
||||
Product product = Beans.get(ProductRepository.class).find(productId);
|
||||
|
||||
if (excludeValotisationStocklocation) {
|
||||
this.stockMoveLines = getStockMoveLines(product, toDateTime, stockMove, true);
|
||||
} else {
|
||||
this.stockMoveLines = getStockMoveLines(product, toDateTime, stockMove, false);
|
||||
}
|
||||
|
||||
List<StockMoveLine> lines = new ArrayList<>();
|
||||
|
||||
if (this.stockMoveLines.size() > 1) {
|
||||
lines = sliceStockMoveLines(this.stockMoveLines, stockMoveId, product);
|
||||
} else {
|
||||
lines.addAll(stockMoveLines);
|
||||
}
|
||||
|
||||
for (StockMoveLine stockMoveLine : lines) {
|
||||
stockMoveLine.setIsWapUpdated(false);
|
||||
Beans.get(StockMoveLineRepository.class).save(stockMoveLine);
|
||||
}
|
||||
}
|
||||
|
||||
public void fillStockMoveLines(
|
||||
Long productId,
|
||||
LocalDate toDateTime,
|
||||
Long stockMoveId,
|
||||
Boolean excludeValotisationStocklocation) {
|
||||
StockMove stockMove = Beans.get(StockMoveRepository.class).find(stockMoveId);
|
||||
Product product = Beans.get(ProductRepository.class).find(productId);
|
||||
this.stockMoveLines =
|
||||
getStockMoveLines(product, toDateTime, stockMove, excludeValotisationStocklocation);
|
||||
|
||||
System.out.println("***********************stockMoveLines****************************");
|
||||
System.out.println(this.stockMoveLines);
|
||||
System.out.println("***********************stockMoveLines****************************");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void valorize(
|
||||
Long productId,
|
||||
Long stockMoveId,
|
||||
LocalDate toDateTime,
|
||||
BigDecimal price,
|
||||
Boolean updatePo,
|
||||
Boolean excludeValotisationStocklocation)
|
||||
throws AxelorException {
|
||||
StockMove stockMove = Beans.get(StockMoveRepository.class).find(stockMoveId);
|
||||
Product product = Beans.get(ProductRepository.class).find(productId);
|
||||
int scale = appBaseService.getNbDecimalDigitForUnitPrice();
|
||||
|
||||
this.updateStockMoveLinePrices(product, price, stockMove, false, null);
|
||||
|
||||
// if(updatePo){
|
||||
// this.updatePurchaseOrderPrices(stockMove, product, price);
|
||||
// }
|
||||
|
||||
List<StockMoveLine> lines = sliceStockMoveLines(this.stockMoveLines, stockMoveId, product);
|
||||
|
||||
BigDecimal currentQty = this.getTotalQty(product, stockMove.getCompany(), false);
|
||||
BigDecimal oldWap = this.getPreviousWap(lines.get(0), false);
|
||||
|
||||
if (excludeValotisationStocklocation) {
|
||||
currentQty = this.getTotalQty(product, stockMove.getCompany(), true);
|
||||
oldWap = this.getPreviousWap(lines.get(0), true);
|
||||
}
|
||||
|
||||
for (StockMoveLine stockMoveLine : lines) {
|
||||
if (stockMoveLine.getStockMove().getFromStockLocation().getTypeSelect()
|
||||
== StockLocationRepository.TYPE_VIRTUAL
|
||||
&& stockMoveLine.getStockMove().getToStockLocation().getId() != 6L) {
|
||||
currentQty = currentQty.subtract(stockMoveLine.getRealQty());
|
||||
} else if (stockMoveLine.getStockMove().getToStockLocation().getTypeSelect()
|
||||
== StockLocationRepository.TYPE_VIRTUAL
|
||||
|| (stockMoveLine.getStockMove().getToStockLocation().getId() == 6L
|
||||
&& stockMoveLine.getStockMove().getFromStockLocation().getTypeSelect()
|
||||
!= StockLocationRepository.TYPE_VIRTUAL)) {
|
||||
currentQty = currentQty.add(stockMoveLine.getRealQty());
|
||||
}
|
||||
System.out.println("..................>>>>>>>>>>>>>>>><<<<<" + currentQty.toString());
|
||||
}
|
||||
|
||||
BigDecimal newQty = lines.get(0).getRealQty();
|
||||
BigDecimal newPrice = price;
|
||||
|
||||
BigDecimal newWap =
|
||||
newQty
|
||||
.multiply(newPrice)
|
||||
.add(currentQty.multiply(oldWap))
|
||||
.divide(currentQty.add(newQty), scale, BigDecimal.ROUND_HALF_UP);
|
||||
|
||||
this.updateStockMoveLinePrices(product, price, stockMove, true, newWap);
|
||||
|
||||
int index = 1;
|
||||
List<StockMoveLine> lines2 = new ArrayList<>();
|
||||
if (lines.size() > 1) {
|
||||
lines2 = this.stockMoveLineStartingFromIndex(index, lines);
|
||||
}
|
||||
|
||||
for (StockMoveLine stockMoveLine : lines2) {
|
||||
// stockMoveLine.getStockMove().getFromStockLocation().getTypeSelect() !=
|
||||
// StockLocationRepository.TYPE_VIRTUAL &&
|
||||
|
||||
if (stockMoveLine.getStockMove().getPartner() == stockMove.getCompany().getPartner()
|
||||
|| stockMoveLine.getStockMove().getPartner() == null) {
|
||||
stockMoveLine.setUnitPriceTaxed(newWap);
|
||||
stockMoveLine.setUnitPriceUntaxed(newWap);
|
||||
stockMoveLine.setCompanyUnitPriceUntaxed(newWap);
|
||||
stockMoveLine.setWapPrice(newWap);
|
||||
stockMoveLine.setIsWapUpdated(true);
|
||||
Beans.get(StockMoveLineRepository.class).save(stockMoveLine);
|
||||
} else if (stockMoveLine.getStockMove().getPartner().getId()
|
||||
!= stockMove.getCompany().getPartner().getId()
|
||||
&& stockMoveLine.getIsWapUpdated() == false) {
|
||||
index++;
|
||||
this.valorize(
|
||||
productId,
|
||||
stockMoveLine.getStockMove().getId(),
|
||||
toDateTime,
|
||||
stockMoveLine.getUnitPriceUntaxed(),
|
||||
updatePo,
|
||||
excludeValotisationStocklocation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePurchaseOrderPrices(StockMove stockMove, Product product, BigDecimal price)
|
||||
throws AxelorException {
|
||||
if (StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())
|
||||
&& stockMove.getOriginId() != null) {
|
||||
PurchaseOrder purchaseOrder =
|
||||
Beans.get(PurchaseOrderRepository.class).find(stockMove.getOriginId());
|
||||
purchaseOrder.setCurrency(purchaseOrder.getCompany().getCurrency());
|
||||
List<PurchaseOrderLine> purchaseOrderLines =
|
||||
purchaseOrder
|
||||
.getPurchaseOrderLineList()
|
||||
.stream()
|
||||
.filter(t -> t.getProduct().getId() == product.getId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// if(purchaseOrderLines == null || purchaseOrderLines.isEmpty()){
|
||||
// return;
|
||||
// }
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLines) {
|
||||
this.updatePrice(purchaseOrderLine, price);
|
||||
}
|
||||
Beans.get(PurchaseOrderRepository.class).save(purchaseOrder);
|
||||
Beans.get(PurchaseOrderServiceImpl.class)._computePurchaseOrder(purchaseOrder);
|
||||
}
|
||||
}
|
||||
|
||||
public List<StockMoveLine> stockMoveLineStartingFromIndex(
|
||||
int index, List<StockMoveLine> stockMoveLines) {
|
||||
List<StockMoveLine> newStockMoveLines = new ArrayList<>();
|
||||
if (index > stockMoveLines.size()) {
|
||||
return newStockMoveLines;
|
||||
}
|
||||
for (int i = index; i < stockMoveLines.size(); i++) {
|
||||
newStockMoveLines.add(stockMoveLines.get(i));
|
||||
}
|
||||
return newStockMoveLines;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
private void updateStockMoveLinePrices(
|
||||
Product product,
|
||||
BigDecimal price,
|
||||
StockMove stockMove,
|
||||
boolean updatePmp,
|
||||
BigDecimal newWapPrice) {
|
||||
List<StockMoveLine> stockMoveLines =
|
||||
// stockMoveLineRepository
|
||||
// .all()
|
||||
// .filter(
|
||||
// "self.stockMove.id = ?1 and self.product.id = ?2 and self.stockMove.statusSelect
|
||||
// = 3 and self.realQty > 0",
|
||||
// stockMoveId,
|
||||
// productId)
|
||||
// .fetch();
|
||||
stockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.filter(
|
||||
t ->
|
||||
(t.getProduct().getId() == product.getId()
|
||||
&& t.getRealQty().compareTo(BigDecimal.ZERO) > 0))
|
||||
.collect(Collectors.toList());
|
||||
if (!updatePmp) {
|
||||
for (StockMoveLine stockMoveLine : stockMoveLines) {
|
||||
stockMoveLine.setCompanyUnitPriceUntaxed(price);
|
||||
stockMoveLine.setUnitPriceTaxed(price);
|
||||
stockMoveLine.setUnitPriceUntaxed(price);
|
||||
Beans.get(StockMoveLineRepository.class).save(stockMoveLine);
|
||||
}
|
||||
stockMove.setExTaxTotal(stockMoveToolService.compute(stockMove));
|
||||
Beans.get(StockMoveRepository.class).save(stockMove);
|
||||
} else {
|
||||
// Product product = Beans.get(ProductRepository.class).find(productId);
|
||||
for (StockMoveLine stockMoveLine : stockMoveLines) {
|
||||
stockMoveLine.setWapPrice(newWapPrice);
|
||||
stockMoveLine.setIsWapUpdated(true);
|
||||
product.setAvgPrice(newWapPrice);
|
||||
List<StockLocationLine> locationLines =
|
||||
Beans.get(StockLocationLineServiceImpl.class).getStockLocationLines(product);
|
||||
for (StockLocationLine stockLocationLine : locationLines) {
|
||||
stockLocationLine.setAvgPrice(newWapPrice);
|
||||
Beans.get(StockLocationLineRepository.class).save(stockLocationLine);
|
||||
}
|
||||
Beans.get(StockMoveLineRepository.class).save(stockMoveLine);
|
||||
Beans.get(ProductRepository.class).save(product);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updatePrice(PurchaseOrderLine purchaseOrderLine, BigDecimal price)
|
||||
throws AxelorException {
|
||||
System.out.println("updateOrderLinePrices......................");
|
||||
System.out.println(purchaseOrderLine.toString());
|
||||
purchaseOrderLine.setPrice(price);
|
||||
purchaseOrderLine.setPriceDiscounted(price);
|
||||
Beans.get(PurchaseOrderLineServiceImpl.class)
|
||||
.compute(purchaseOrderLine, purchaseOrderLine.getPurchaseOrder());
|
||||
Beans.get(PurchaseOrderLineRepository.class).save(purchaseOrderLine);
|
||||
}
|
||||
|
||||
public List<StockMoveLine> getStockMoveLines(
|
||||
Product product,
|
||||
LocalDate toDate,
|
||||
StockMove stockMove,
|
||||
Boolean excludeValotisationStocklocation) {
|
||||
|
||||
String query =
|
||||
"self.stockMove.statusSelect = ?1 AND self.stockMove.realDate <= ?2 AND self.product = ?3 AND self.stockMove.realDate >= ?4 AND self.realQty > 0 AND (self.archived is null OR self.archived is false)";
|
||||
|
||||
if (excludeValotisationStocklocation) {
|
||||
query +=
|
||||
" AND (self.stockMove.toStockLocation.excludeValorisation is true or self.stockMove.fromStockLocation.excludeValorisation is true)";
|
||||
} else {
|
||||
query +=
|
||||
" AND ((self.stockMove.toStockLocation.excludeValorisation is false or self.stockMove.toStockLocation.excludeValorisation is null) AND (self.stockMove.fromStockLocation.excludeValorisation is false or self.stockMove.fromStockLocation.excludeValorisation is null))";
|
||||
}
|
||||
|
||||
System.out.println("***************getStockMoveLines************");
|
||||
System.out.println(query);
|
||||
System.out.println("***************getStockMoveLines************");
|
||||
|
||||
List<Object> params = Lists.newArrayList();
|
||||
params.add(StockMoveRepository.STATUS_REALIZED);
|
||||
params.add(toDate);
|
||||
params.add(product);
|
||||
params.add(stockMove.getRealDate());
|
||||
// params.add(stockMove.getRealDate());
|
||||
// params.add(stockMove.getId());
|
||||
List<StockMoveLine> lines =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter(query, params.toArray())
|
||||
.order("stockMove.realDate")
|
||||
// .order("id")
|
||||
.fetch();
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
public BigDecimal getPreviousWap(
|
||||
StockMoveLine stockMoveLine, Boolean excludeValotisationStocklocation) {
|
||||
String query =
|
||||
"self.stockMove.statusSelect = ?1 AND self.product = ?2 and self.stockMove.realDate < ?3 AND self.realQty > 0";
|
||||
|
||||
if (excludeValotisationStocklocation) {
|
||||
query +=
|
||||
" AND (self.stockMove.toStockLocation.excludeValorisation is true or self.stockMove.fromStockLocation.excludeValorisation is true)";
|
||||
}
|
||||
|
||||
StockMoveLine l = stockMoveLineRepository.find(stockMoveLine.getId());
|
||||
List<Object> params = Lists.newArrayList();
|
||||
params.add(StockMoveRepository.STATUS_REALIZED);
|
||||
params.add(l.getProduct());
|
||||
params.add(l.getStockMove().getRealDate());
|
||||
|
||||
StockMoveLine line =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.order("-stockMove.realDate")
|
||||
.order("-id")
|
||||
.filter(query, params.toArray())
|
||||
.fetchOne();
|
||||
|
||||
if (line == null || line.getWapPrice() == null) {
|
||||
return stockMoveLine.getUnitPriceUntaxed();
|
||||
}
|
||||
return line.getWapPrice();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public BigDecimal getTotalQty(
|
||||
Product product, Company company, Boolean excludeValotisationStocklocation) {
|
||||
Long productId = product.getId();
|
||||
String query =
|
||||
"SELECT new list(self.id, self.avgPrice, self.currentQty) FROM StockLocationLine as self "
|
||||
+ "WHERE self.product.id = "
|
||||
+ productId
|
||||
+ "AND self.stockLocation.id not in (6) AND self.stockLocation.typeSelect != "
|
||||
+ StockLocationRepository.TYPE_VIRTUAL;
|
||||
|
||||
if (excludeValotisationStocklocation) {
|
||||
query += " AND (self.stockLocation.excludeValorisation is true)";
|
||||
} else {
|
||||
query +=
|
||||
" AND (self.stockLocation.excludeValorisation is false or self.stockLocation.excludeValorisation is null)";
|
||||
}
|
||||
|
||||
if (company != null) {
|
||||
query += " AND self.stockLocation.company = " + company.getId();
|
||||
}
|
||||
|
||||
BigDecimal qtyTot = BigDecimal.ZERO;
|
||||
List<List<Object>> results = JPA.em().createQuery(query).getResultList();
|
||||
if (results.isEmpty()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
for (List<Object> result : results) {
|
||||
BigDecimal qty = (BigDecimal) result.get(2);
|
||||
qtyTot = qtyTot.add(qty);
|
||||
}
|
||||
if (qtyTot.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
System.out.println("***************query*************");
|
||||
System.out.println(query);
|
||||
System.out.println("***************qtyTot*************");
|
||||
System.out.println(qtyTot);
|
||||
|
||||
return qtyTot;
|
||||
}
|
||||
|
||||
// lines
|
||||
public List<StockMoveLine> lines = new ArrayList<>();
|
||||
|
||||
public void showLines(Long productId, Long stockMoveId, LocalDate toDateTime, BigDecimal price) {
|
||||
StockMove stockMove = Beans.get(StockMoveRepository.class).find(stockMoveId);
|
||||
Product product = Beans.get(ProductRepository.class).find(productId);
|
||||
List<StockMoveLine> lines = getStockMoveLines(product, toDateTime, stockMove, false);
|
||||
System.out.println("*****************************************************");
|
||||
for (StockMoveLine line : lines) {
|
||||
System.out.println(line.getStockMove().getStockMoveSeq());
|
||||
}
|
||||
System.out.println("*****************************************************");
|
||||
int index = 1;
|
||||
List<StockMoveLine> lines2 = this.stockMoveLineStartingFromIndex(index, lines);
|
||||
System.out.println("********************lines2*********************************");
|
||||
for (StockMoveLine stockMoveLine : lines2) {
|
||||
if (stockMoveLine.getStockMove().getPartner() == stockMove.getCompany().getPartner()
|
||||
|| stockMoveLine.getStockMove().getPartner() == null) {
|
||||
System.out.println("Interne ***** " + stockMoveLine.getStockMove().getStockMoveSeq());
|
||||
} else if (stockMoveLine.getStockMove().getPartner().getId()
|
||||
!= stockMove.getCompany().getPartner().getId()) {
|
||||
index++;
|
||||
System.out.println(
|
||||
"Externe ***** "
|
||||
+ stockMoveLine.getStockMove().getStockMoveSeq()
|
||||
+ " ,id : "
|
||||
+ stockMoveLine.getStockMove().getId());
|
||||
this.showLines(productId, stockMoveLine.getStockMove().getId(), toDateTime, price);
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println("***********************lines2******************************");
|
||||
}
|
||||
|
||||
public void valorizeAll(LocalDate fromDateTime, Long productCategoryId) throws AxelorException {
|
||||
// ,67
|
||||
String query =
|
||||
"self.stockMove.statusSelect = ?1 "
|
||||
+ "and self.stockMove.realDate >= ?2 and self.realQty > 0 and "
|
||||
+ "self.product.familleProduit.id in ("
|
||||
+ String.valueOf(productCategoryId)
|
||||
+ ") and self.stockMove.fromStockLocation.id = 12 and self.stockMove.partner.id != 853";
|
||||
List<Object> params = Lists.newArrayList();
|
||||
params.add(StockMoveRepository.STATUS_REALIZED);
|
||||
params.add(fromDateTime);
|
||||
|
||||
List<StockMoveLine> lines =
|
||||
stockMoveLineRepository
|
||||
.all()
|
||||
.filter(query, params.toArray())
|
||||
.order("stockMove.realDate")
|
||||
.order("id")
|
||||
.fetch();
|
||||
|
||||
List<StockMoveLine> distinctLines = new ArrayList<>();
|
||||
List<Product> products = new ArrayList<>();
|
||||
|
||||
for (StockMoveLine line : lines) {
|
||||
if (products.indexOf(line.getProduct()) == -1) {
|
||||
distinctLines.add(line);
|
||||
products.add(line.getProduct());
|
||||
}
|
||||
}
|
||||
|
||||
int index = lines.size();
|
||||
for (StockMoveLine line : distinctLines) {
|
||||
System.out.println("Starting ............... " + String.valueOf(index));
|
||||
System.out.println("Size ............... " + String.valueOf(distinctLines.size()));
|
||||
System.out.println(line.getStockMove().getStockMoveSeq());
|
||||
System.out.println(line.getProductName());
|
||||
System.out.println(line.getProduct().getId());
|
||||
this.resetValorization(
|
||||
line.getProduct().getId(), line.getStockMove().getId(), LocalDate.now(), false);
|
||||
this.fillStockMoveLines(
|
||||
line.getProduct().getId(), LocalDate.now(), line.getStockMove().getId(), false);
|
||||
this.valorize(
|
||||
line.getProduct().getId(),
|
||||
line.getStockMove().getId(),
|
||||
LocalDate.now(),
|
||||
line.getUnitPriceUntaxed(),
|
||||
false,
|
||||
false);
|
||||
System.out.println("End .....................");
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
public List<StockMoveLine> sliceStockMoveLines(
|
||||
List<StockMoveLine> lines, Long stockMoveId, Product product) {
|
||||
|
||||
StockMoveLine emp =
|
||||
lines
|
||||
.stream()
|
||||
.filter(
|
||||
(t) -> {
|
||||
return t.getStockMove().getId() == stockMoveId && t.getProduct() == product;
|
||||
})
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
int index = lines.indexOf(emp);
|
||||
|
||||
List<StockMoveLine> slicedLines = new ArrayList<>();
|
||||
|
||||
for (int i = index; i < lines.size(); i++) {
|
||||
slicedLines.add(lines.get(i));
|
||||
}
|
||||
return slicedLines;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.PaymentCondition;
|
||||
import com.axelor.apps.account.db.PaymentMode;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface StockMoveMultiInvoiceService {
|
||||
|
||||
/**
|
||||
* Generate multiple invoices and manage JPA cache from stock moves.
|
||||
*
|
||||
* @param stockMoveIdList a list of stock move ids.
|
||||
* @return an entry with the list of id of generated invoices as key, and error message as key.
|
||||
*/
|
||||
Entry<List<Long>, String> generateMultipleInvoices(List<Long> stockMoveIdList);
|
||||
|
||||
Map<String, Object> areFieldsConflictedToGenerateCustInvoice(List<StockMove> stockMoveList)
|
||||
throws AxelorException;
|
||||
|
||||
Map<String, Object> areFieldsConflictedToGenerateSupplierInvoice(List<StockMove> stockMoveList)
|
||||
throws AxelorException;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
Optional<Invoice> createInvoiceFromMultiOutgoingStockMove(List<StockMove> stockMoveList)
|
||||
throws AxelorException;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
Optional<Invoice> createInvoiceFromMultiOutgoingStockMove(
|
||||
List<StockMove> stockMoveList,
|
||||
PaymentCondition paymentCondition,
|
||||
PaymentMode paymentMode,
|
||||
Partner contactPartner)
|
||||
throws AxelorException;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
Optional<Invoice> createInvoiceFromMultiIncomingStockMove(List<StockMove> stockMoveList)
|
||||
throws AxelorException;
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
Optional<Invoice> createInvoiceFromMultiIncomingStockMove(
|
||||
List<StockMove> stockMoveList,
|
||||
PaymentCondition paymentConditionIn,
|
||||
PaymentMode paymentModeIn,
|
||||
Partner contactPartnerIn)
|
||||
throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,867 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 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.PaymentCondition;
|
||||
import com.axelor.apps.account.db.PaymentMode;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.service.invoice.generator.InvoiceGenerator;
|
||||
import com.axelor.apps.account.service.invoice.generator.invoice.RefundInvoice;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.service.AddressService;
|
||||
import com.axelor.apps.base.service.PartnerService;
|
||||
import com.axelor.apps.base.service.administration.AbstractBatch;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.tool.StringTool;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StockMoveMultiInvoiceServiceImpl implements StockMoveMultiInvoiceService {
|
||||
|
||||
private InvoiceRepository invoiceRepository;
|
||||
private SaleOrderRepository saleOrderRepository;
|
||||
private PurchaseOrderRepository purchaseOrderRepository;
|
||||
private StockMoveInvoiceService stockMoveInvoiceService;
|
||||
|
||||
@Inject
|
||||
public StockMoveMultiInvoiceServiceImpl(
|
||||
InvoiceRepository invoiceRepository,
|
||||
SaleOrderRepository saleOrderRepository,
|
||||
PurchaseOrderRepository purchaseOrderRepository,
|
||||
StockMoveInvoiceService stockMoveInvoiceService) {
|
||||
this.invoiceRepository = invoiceRepository;
|
||||
this.saleOrderRepository = saleOrderRepository;
|
||||
this.purchaseOrderRepository = purchaseOrderRepository;
|
||||
this.stockMoveInvoiceService = stockMoveInvoiceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<List<Long>, String> generateMultipleInvoices(List<Long> stockMoveIdList) {
|
||||
StockMoveRepository stockMoveRepository = Beans.get(StockMoveRepository.class);
|
||||
List<Long> invoiceIdList = new ArrayList<>();
|
||||
|
||||
StringBuilder stockMovesInError = new StringBuilder();
|
||||
List<StockMove> stockMoveList;
|
||||
Query<StockMove> stockMoveQuery =
|
||||
stockMoveRepository
|
||||
.all()
|
||||
.filter("self.id IN :stockMoveIdList")
|
||||
.bind("stockMoveIdList", stockMoveIdList)
|
||||
.order("id");
|
||||
int offset = 0;
|
||||
|
||||
while (!(stockMoveList = stockMoveQuery.fetch(AbstractBatch.FETCH_LIMIT, offset)).isEmpty()) {
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
offset++;
|
||||
try {
|
||||
Invoice invoice = stockMoveInvoiceService.createInvoice(stockMove, 0, null);
|
||||
if (invoice != null) {
|
||||
invoiceIdList.add(invoice.getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (stockMovesInError.length() > 0) {
|
||||
stockMovesInError.append("<br/>");
|
||||
}
|
||||
stockMovesInError.append(
|
||||
String.format(
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_GENERATE_INVOICE),
|
||||
stockMove.getName(),
|
||||
e.getLocalizedMessage()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
JPA.clear();
|
||||
}
|
||||
|
||||
return new SimpleImmutableEntry<>(invoiceIdList, stockMovesInError.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> areFieldsConflictedToGenerateCustInvoice(List<StockMove> stockMoveList)
|
||||
throws AxelorException {
|
||||
Map<String, Object> mapResult = new HashMap<>();
|
||||
|
||||
boolean paymentConditionToCheck = false;
|
||||
boolean paymentModeToCheck = false;
|
||||
boolean contactPartnerToCheck = false;
|
||||
|
||||
checkForAlreadyInvoicedStockMove(stockMoveList);
|
||||
List<Invoice> dummyInvoiceList =
|
||||
stockMoveList.stream().map(this::createDummyOutInvoice).collect(Collectors.toList());
|
||||
checkOutStockMoveRequiredFieldsAreTheSame(dummyInvoiceList);
|
||||
|
||||
if (!dummyInvoiceList.isEmpty()) {
|
||||
PaymentCondition firstPaymentCondition = dummyInvoiceList.get(0).getPaymentCondition();
|
||||
PaymentMode firstPaymentMode = dummyInvoiceList.get(0).getPaymentMode();
|
||||
Partner firstContactPartner = dummyInvoiceList.get(0).getContactPartner();
|
||||
paymentConditionToCheck =
|
||||
!dummyInvoiceList
|
||||
.stream()
|
||||
.map(Invoice::getPaymentCondition)
|
||||
.allMatch(
|
||||
paymentCondition -> Objects.equals(paymentCondition, firstPaymentCondition));
|
||||
paymentModeToCheck =
|
||||
!dummyInvoiceList
|
||||
.stream()
|
||||
.map(Invoice::getPaymentMode)
|
||||
.allMatch(paymentMode -> Objects.equals(paymentMode, firstPaymentMode));
|
||||
contactPartnerToCheck =
|
||||
!dummyInvoiceList
|
||||
.stream()
|
||||
.map(Invoice::getContactPartner)
|
||||
.allMatch(contactPartner -> Objects.equals(contactPartner, firstContactPartner));
|
||||
|
||||
mapResult.put("paymentCondition", firstPaymentCondition);
|
||||
mapResult.put("paymentMode", firstPaymentMode);
|
||||
mapResult.put("contactPartner", firstContactPartner);
|
||||
}
|
||||
|
||||
mapResult.put("paymentConditionToCheck", paymentConditionToCheck);
|
||||
mapResult.put("paymentModeToCheck", paymentModeToCheck);
|
||||
mapResult.put("contactPartnerToCheck", contactPartnerToCheck);
|
||||
|
||||
return mapResult;
|
||||
}
|
||||
|
||||
protected void checkOutStockMoveRequiredFieldsAreTheSame(List<Invoice> dummyInvoiceList)
|
||||
throws AxelorException {
|
||||
if (dummyInvoiceList == null || dummyInvoiceList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Invoice firstDummyInvoice = dummyInvoiceList.get(0);
|
||||
|
||||
for (Invoice dummyInvoice : dummyInvoiceList) {
|
||||
if (firstDummyInvoice.getCurrency() != null
|
||||
&& !firstDummyInvoice.getCurrency().equals(dummyInvoice.getCurrency())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_CURRENCY));
|
||||
}
|
||||
|
||||
if (firstDummyInvoice.getPartner() != null
|
||||
&& !firstDummyInvoice.getPartner().equals(dummyInvoice.getPartner())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_CLIENT_PARTNER));
|
||||
}
|
||||
|
||||
if (firstDummyInvoice.getCompany() != null
|
||||
&& !firstDummyInvoice.getCompany().equals(dummyInvoice.getCompany())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_COMPANY_SO));
|
||||
}
|
||||
|
||||
if ((firstDummyInvoice.getTradingName() != null
|
||||
&& !firstDummyInvoice.getTradingName().equals(dummyInvoice.getTradingName()))
|
||||
|| (firstDummyInvoice.getTradingName() == null
|
||||
&& dummyInvoice.getTradingName() != null)) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_TRADING_NAME_SO));
|
||||
}
|
||||
|
||||
if (firstDummyInvoice.getInAti() != null
|
||||
&& !firstDummyInvoice.getInAti().equals(dummyInvoice.getInAti())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_IN_ATI));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> areFieldsConflictedToGenerateSupplierInvoice(
|
||||
List<StockMove> stockMoveList) throws AxelorException {
|
||||
Map<String, Object> mapResult = new HashMap<>();
|
||||
|
||||
boolean paymentConditionToCheck = false;
|
||||
boolean paymentModeToCheck = false;
|
||||
boolean contactPartnerToCheck = false;
|
||||
|
||||
checkForAlreadyInvoicedStockMove(stockMoveList);
|
||||
List<Invoice> dummyInvoiceList =
|
||||
stockMoveList.stream().map(this::createDummyInInvoice).collect(Collectors.toList());
|
||||
checkInStockMoveRequiredFieldsAreTheSame(dummyInvoiceList);
|
||||
|
||||
if (!dummyInvoiceList.isEmpty()) {
|
||||
PaymentCondition firstPaymentCondition = dummyInvoiceList.get(0).getPaymentCondition();
|
||||
PaymentMode firstPaymentMode = dummyInvoiceList.get(0).getPaymentMode();
|
||||
Partner firstContactPartner = dummyInvoiceList.get(0).getContactPartner();
|
||||
paymentConditionToCheck =
|
||||
!dummyInvoiceList
|
||||
.stream()
|
||||
.map(Invoice::getPaymentCondition)
|
||||
.allMatch(
|
||||
paymentCondition -> Objects.equals(paymentCondition, firstPaymentCondition));
|
||||
paymentModeToCheck =
|
||||
!dummyInvoiceList
|
||||
.stream()
|
||||
.map(Invoice::getPaymentMode)
|
||||
.allMatch(paymentMode -> Objects.equals(paymentMode, firstPaymentMode));
|
||||
contactPartnerToCheck =
|
||||
!dummyInvoiceList
|
||||
.stream()
|
||||
.map(Invoice::getContactPartner)
|
||||
.allMatch(contactPartner -> Objects.equals(contactPartner, firstContactPartner));
|
||||
mapResult.put("paymentCondition", firstPaymentCondition);
|
||||
mapResult.put("paymentMode", firstPaymentMode);
|
||||
mapResult.put("contactPartner", firstContactPartner);
|
||||
}
|
||||
|
||||
mapResult.put("paymentConditionToCheck", paymentConditionToCheck);
|
||||
mapResult.put("paymentModeToCheck", paymentModeToCheck);
|
||||
mapResult.put("contactPartnerToCheck", contactPartnerToCheck);
|
||||
|
||||
return mapResult;
|
||||
}
|
||||
|
||||
protected void checkInStockMoveRequiredFieldsAreTheSame(List<Invoice> dummyInvoiceList)
|
||||
throws AxelorException {
|
||||
if (dummyInvoiceList == null || dummyInvoiceList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Invoice firstDummyInvoice = dummyInvoiceList.get(0);
|
||||
|
||||
for (Invoice dummyInvoice : dummyInvoiceList) {
|
||||
|
||||
if (firstDummyInvoice.getCurrency() != null
|
||||
&& !firstDummyInvoice.getCurrency().equals(dummyInvoice.getCurrency())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_CURRENCY));
|
||||
}
|
||||
|
||||
if (firstDummyInvoice.getPartner() != null
|
||||
&& !firstDummyInvoice.getPartner().equals(dummyInvoice.getPartner())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_SUPPLIER_PARTNER));
|
||||
}
|
||||
|
||||
if (firstDummyInvoice.getCompany() != null
|
||||
&& !firstDummyInvoice.getCompany().equals(dummyInvoice.getCompany())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_COMPANY_PO));
|
||||
}
|
||||
|
||||
if ((firstDummyInvoice.getTradingName() != null
|
||||
&& !firstDummyInvoice.getTradingName().equals(dummyInvoice.getTradingName()))
|
||||
|| (firstDummyInvoice.getTradingName() == null
|
||||
&& dummyInvoice.getTradingName() != null)) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_TRADING_NAME_PO));
|
||||
}
|
||||
|
||||
if (firstDummyInvoice.getInAti() != null
|
||||
&& !firstDummyInvoice.getInAti().equals(dummyInvoice.getInAti())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_MULTI_INVOICE_IN_ATI));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Optional<Invoice> createInvoiceFromMultiOutgoingStockMove(
|
||||
List<StockMove> stockMoveList,
|
||||
PaymentCondition paymentConditionIn,
|
||||
PaymentMode paymentModeIn,
|
||||
Partner contactPartnerIn)
|
||||
throws AxelorException {
|
||||
Optional<Invoice> invoiceOpt = this.createInvoiceFromMultiOutgoingStockMove(stockMoveList);
|
||||
invoiceOpt.ifPresent(
|
||||
invoice ->
|
||||
fillInvoiceFromMultiStockMoveDefaultValues(
|
||||
invoice, paymentConditionIn, paymentModeIn, contactPartnerIn));
|
||||
return invoiceOpt;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Optional<Invoice> createInvoiceFromMultiOutgoingStockMove(List<StockMove> stockMoveList)
|
||||
throws AxelorException {
|
||||
|
||||
if (stockMoveList == null || stockMoveList.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// create dummy invoice from the first stock move
|
||||
Invoice dummyInvoice = createDummyOutInvoice(stockMoveList.get(0));
|
||||
|
||||
// Check if field constraints are respected
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
completeInvoiceInMultiOutgoingStockMove(dummyInvoice, stockMove);
|
||||
}
|
||||
|
||||
/* check if some other fields are different and assign a default value */
|
||||
if (dummyInvoice.getAddress() == null) {
|
||||
dummyInvoice.setAddress(
|
||||
Beans.get(PartnerService.class).getInvoicingAddress(dummyInvoice.getPartner()));
|
||||
dummyInvoice.setAddressStr(
|
||||
Beans.get(AddressService.class).computeAddressStr(dummyInvoice.getAddress()));
|
||||
}
|
||||
|
||||
fillReferenceInvoiceFromMultiOutStockMove(stockMoveList, dummyInvoice);
|
||||
|
||||
InvoiceGenerator invoiceGenerator =
|
||||
new InvoiceGenerator(
|
||||
InvoiceRepository.OPERATION_TYPE_CLIENT_SALE,
|
||||
dummyInvoice.getCompany(),
|
||||
dummyInvoice.getPaymentCondition(),
|
||||
dummyInvoice.getPaymentMode(),
|
||||
dummyInvoice.getAddress(),
|
||||
dummyInvoice.getPartner(),
|
||||
dummyInvoice.getContactPartner(),
|
||||
dummyInvoice.getCurrency(),
|
||||
dummyInvoice.getPriceList(),
|
||||
dummyInvoice.getInternalReference(),
|
||||
dummyInvoice.getExternalReference(),
|
||||
dummyInvoice.getInAti(),
|
||||
null,
|
||||
dummyInvoice.getTradingName()) {
|
||||
|
||||
@Override
|
||||
public Invoice generate() throws AxelorException {
|
||||
|
||||
return super.createInvoiceHeader();
|
||||
}
|
||||
};
|
||||
|
||||
Invoice invoice = invoiceGenerator.generate();
|
||||
invoice.setAddressStr(dummyInvoice.getAddressStr());
|
||||
|
||||
List<InvoiceLine> invoiceLineList = new ArrayList<>();
|
||||
|
||||
for (StockMove stockMoveLocal : stockMoveList) {
|
||||
List<InvoiceLine> createdInvoiceLines =
|
||||
stockMoveInvoiceService.createInvoiceLines(
|
||||
invoice, stockMoveLocal.getStockMoveLineList(), null);
|
||||
if (stockMoveLocal.getTypeSelect() == StockMoveRepository.TYPE_INCOMING) {
|
||||
createdInvoiceLines.forEach(this::negateInvoiceLinePrice);
|
||||
}
|
||||
invoiceLineList.addAll(createdInvoiceLines);
|
||||
}
|
||||
|
||||
invoiceGenerator.populate(invoice, invoiceLineList);
|
||||
|
||||
invoiceRepository.save(invoice);
|
||||
if (invoice.getExTaxTotal().signum() < 0) {
|
||||
Invoice refund = transformToRefund(invoice);
|
||||
stockMoveList.forEach(
|
||||
stockMove -> {
|
||||
if (stockMove.getInvoiceSet() != null) {
|
||||
stockMove.getInvoiceSet().add(refund);
|
||||
} else {
|
||||
Set<Invoice> invoiceSet = new HashSet<>();
|
||||
invoiceSet.add(refund);
|
||||
stockMove.setInvoiceSet(invoiceSet);
|
||||
}
|
||||
});
|
||||
invoiceRepository.remove(invoice);
|
||||
return Optional.of(refund);
|
||||
} else {
|
||||
stockMoveList.forEach(
|
||||
stockMove -> {
|
||||
if (stockMove.getInvoiceSet() != null) {
|
||||
stockMove.getInvoiceSet().add(invoice);
|
||||
} else {
|
||||
Set<Invoice> invoiceSet = new HashSet<>();
|
||||
invoiceSet.add(invoice);
|
||||
stockMove.setInvoiceSet(invoiceSet);
|
||||
}
|
||||
});
|
||||
return Optional.of(invoice);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Optional<Invoice> createInvoiceFromMultiIncomingStockMove(
|
||||
List<StockMove> stockMoveList,
|
||||
PaymentCondition paymentConditionIn,
|
||||
PaymentMode paymentModeIn,
|
||||
Partner contactPartnerIn)
|
||||
throws AxelorException {
|
||||
Optional<Invoice> invoiceOpt = createInvoiceFromMultiIncomingStockMove(stockMoveList);
|
||||
invoiceOpt.ifPresent(
|
||||
invoice ->
|
||||
fillInvoiceFromMultiStockMoveDefaultValues(
|
||||
invoice, paymentConditionIn, paymentModeIn, contactPartnerIn));
|
||||
return invoiceOpt;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Optional<Invoice> createInvoiceFromMultiIncomingStockMove(List<StockMove> stockMoveList)
|
||||
throws AxelorException {
|
||||
if (stockMoveList == null || stockMoveList.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// create dummy invoice from the first stock move
|
||||
Invoice dummyInvoice = createDummyInInvoice(stockMoveList.get(0));
|
||||
|
||||
// Check if field constraints are respected
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
completeInvoiceInMultiIncomingStockMove(dummyInvoice, stockMove);
|
||||
}
|
||||
|
||||
/* check if some other fields are different and assign a default value */
|
||||
if (dummyInvoice.getAddress() == null) {
|
||||
dummyInvoice.setAddress(
|
||||
Beans.get(PartnerService.class).getInvoicingAddress(dummyInvoice.getPartner()));
|
||||
dummyInvoice.setAddressStr(
|
||||
Beans.get(AddressService.class).computeAddressStr(dummyInvoice.getAddress()));
|
||||
}
|
||||
|
||||
fillReferenceInvoiceFromMultiInStockMove(stockMoveList, dummyInvoice);
|
||||
|
||||
InvoiceGenerator invoiceGenerator =
|
||||
new InvoiceGenerator(
|
||||
InvoiceRepository.OPERATION_TYPE_SUPPLIER_PURCHASE,
|
||||
dummyInvoice.getCompany(),
|
||||
dummyInvoice.getPaymentCondition(),
|
||||
dummyInvoice.getPaymentMode(),
|
||||
dummyInvoice.getAddress(),
|
||||
dummyInvoice.getPartner(),
|
||||
dummyInvoice.getContactPartner(),
|
||||
dummyInvoice.getCurrency(),
|
||||
dummyInvoice.getPriceList(),
|
||||
dummyInvoice.getInternalReference(),
|
||||
dummyInvoice.getExternalReference(),
|
||||
dummyInvoice.getInAti(),
|
||||
null,
|
||||
dummyInvoice.getTradingName()) {
|
||||
|
||||
@Override
|
||||
public Invoice generate() throws AxelorException {
|
||||
|
||||
return super.createInvoiceHeader();
|
||||
}
|
||||
};
|
||||
|
||||
Invoice invoice = invoiceGenerator.generate();
|
||||
invoice.setAddressStr(dummyInvoice.getAddressStr());
|
||||
|
||||
List<InvoiceLine> invoiceLineList = new ArrayList<>();
|
||||
|
||||
for (StockMove stockMoveLocal : stockMoveList) {
|
||||
List<InvoiceLine> createdInvoiceLines =
|
||||
stockMoveInvoiceService.createInvoiceLines(
|
||||
invoice, stockMoveLocal.getStockMoveLineList(), null);
|
||||
if (stockMoveLocal.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING) {
|
||||
createdInvoiceLines.forEach(this::negateInvoiceLinePrice);
|
||||
}
|
||||
invoiceLineList.addAll(createdInvoiceLines);
|
||||
}
|
||||
|
||||
invoiceGenerator.populate(invoice, invoiceLineList);
|
||||
|
||||
invoiceRepository.save(invoice);
|
||||
if (invoice.getExTaxTotal().signum() < 0) {
|
||||
Invoice refund = transformToRefund(invoice);
|
||||
stockMoveList.forEach(
|
||||
stockMove -> {
|
||||
if (stockMove.getInvoiceSet() != null) {
|
||||
stockMove.getInvoiceSet().add(refund);
|
||||
} else {
|
||||
Set<Invoice> invoiceSet = new HashSet<>();
|
||||
invoiceSet.add(refund);
|
||||
stockMove.setInvoiceSet(invoiceSet);
|
||||
}
|
||||
});
|
||||
|
||||
invoiceRepository.remove(invoice);
|
||||
return Optional.of(refund);
|
||||
} else {
|
||||
stockMoveList.forEach(
|
||||
stockMove -> {
|
||||
Set<Invoice> invoiceSet = new HashSet<>();
|
||||
invoiceSet.add(invoice);
|
||||
stockMove.setInvoiceSet(invoiceSet);
|
||||
|
||||
String sqlString =
|
||||
String.format(
|
||||
"INSERT INTO public.account_invoice_stock_move_set( invoice_set, stock_move_set) VALUES (:invoiceSet, :stockMoveSet)");
|
||||
javax.persistence.Query query = JPA.em().createNativeQuery(sqlString);
|
||||
query.setParameter("invoiceSet", invoice.getId());
|
||||
query.setParameter("stockMoveSet", stockMove.getId());
|
||||
JPA.runInTransaction(query::executeUpdate);
|
||||
});
|
||||
return Optional.of(invoice);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the operation type and invert all prices in the invoice.
|
||||
*
|
||||
* @param invoice an invoice
|
||||
* @return the refund invoice
|
||||
*/
|
||||
protected Invoice transformToRefund(Invoice invoice) throws AxelorException {
|
||||
Invoice refund = new RefundInvoice(invoice).generate();
|
||||
if (refund.getInvoiceLineList() != null) {
|
||||
for (InvoiceLine invoiceLine : refund.getInvoiceLineList()) {
|
||||
invoiceLine.setPrice(invoiceLine.getPrice().negate());
|
||||
invoiceLine.setPriceDiscounted(invoiceLine.getPriceDiscounted().negate());
|
||||
invoiceLine.setInTaxPrice(invoiceLine.getInTaxPrice().negate());
|
||||
invoiceLine.setExTaxTotal(invoiceLine.getExTaxTotal().negate());
|
||||
invoiceLine.setInTaxTotal(invoiceLine.getInTaxTotal().negate());
|
||||
invoiceLine.setCompanyExTaxTotal(invoiceLine.getCompanyExTaxTotal().negate());
|
||||
invoiceLine.setCompanyInTaxTotal(invoiceLine.getCompanyInTaxTotal().negate());
|
||||
}
|
||||
}
|
||||
if (refund.getInvoiceLineTaxList() != null) {
|
||||
for (InvoiceLineTax invoiceLineTax : refund.getInvoiceLineTaxList()) {
|
||||
invoiceLineTax.setExTaxBase(invoiceLineTax.getExTaxBase().negate());
|
||||
invoiceLineTax.setTaxTotal(invoiceLineTax.getTaxTotal().negate());
|
||||
invoiceLineTax.setCompanyExTaxBase(invoiceLineTax.getCompanyExTaxBase().negate());
|
||||
invoiceLineTax.setInTaxTotal(invoiceLineTax.getInTaxTotal().negate());
|
||||
invoiceLineTax.setCompanyInTaxTotal(invoiceLineTax.getCompanyInTaxTotal().negate());
|
||||
}
|
||||
}
|
||||
refund.setExTaxTotal(refund.getExTaxTotal().negate());
|
||||
refund.setInTaxTotal(refund.getInTaxTotal().negate());
|
||||
refund.setCompanyExTaxTotal(refund.getCompanyExTaxTotal().negate());
|
||||
refund.setCompanyInTaxTotal(refund.getCompanyInTaxTotal().negate());
|
||||
refund.setTaxTotal(refund.getTaxTotal().negate());
|
||||
refund.setAmountRemaining(refund.getAmountRemaining().negate());
|
||||
refund.setCompanyTaxTotal(refund.getCompanyTaxTotal().negate());
|
||||
return invoiceRepository.save(refund);
|
||||
}
|
||||
|
||||
protected void fillInvoiceFromMultiStockMoveDefaultValues(
|
||||
Invoice invoice,
|
||||
PaymentCondition paymentConditionIn,
|
||||
PaymentMode paymentModeIn,
|
||||
Partner contactPartnerIn) {
|
||||
invoice.setPaymentCondition(paymentConditionIn);
|
||||
invoice.setPaymentMode(paymentModeIn);
|
||||
invoice.setContactPartner(contactPartnerIn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dummy invoice to hold fields used to generate the invoice which will be saved.
|
||||
*
|
||||
* @param stockMove an out stock move.
|
||||
* @return the created dummy invoice.
|
||||
*/
|
||||
protected Invoice createDummyOutInvoice(StockMove stockMove) {
|
||||
Invoice dummyInvoice = new Invoice();
|
||||
|
||||
if (stockMove.getOriginId() != null
|
||||
&& StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
SaleOrder saleOrder = saleOrderRepository.find(stockMove.getOriginId());
|
||||
dummyInvoice.setCurrency(saleOrder.getCurrency());
|
||||
dummyInvoice.setPartner(saleOrder.getClientPartner());
|
||||
dummyInvoice.setCompany(saleOrder.getCompany());
|
||||
dummyInvoice.setTradingName(saleOrder.getTradingName());
|
||||
dummyInvoice.setPaymentCondition(saleOrder.getPaymentCondition());
|
||||
dummyInvoice.setPaymentMode(saleOrder.getPaymentMode());
|
||||
dummyInvoice.setAddress(saleOrder.getMainInvoicingAddress());
|
||||
dummyInvoice.setAddressStr(saleOrder.getMainInvoicingAddressStr());
|
||||
dummyInvoice.setContactPartner(saleOrder.getContactPartner());
|
||||
dummyInvoice.setPriceList(saleOrder.getPriceList());
|
||||
dummyInvoice.setInAti(saleOrder.getInAti());
|
||||
} else {
|
||||
dummyInvoice.setCurrency(stockMove.getCompany().getCurrency());
|
||||
dummyInvoice.setPartner(stockMove.getPartner());
|
||||
dummyInvoice.setCompany(stockMove.getCompany());
|
||||
dummyInvoice.setTradingName(stockMove.getTradingName());
|
||||
dummyInvoice.setAddress(stockMove.getToAddress());
|
||||
dummyInvoice.setAddressStr(stockMove.getToAddressStr());
|
||||
}
|
||||
return dummyInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dummy invoice to hold fields used to generate the invoice which will be saved.
|
||||
*
|
||||
* @param stockMove an in stock move.
|
||||
* @return the created dummy invoice.
|
||||
*/
|
||||
protected Invoice createDummyInInvoice(StockMove stockMove) {
|
||||
Invoice dummyInvoice = new Invoice();
|
||||
|
||||
if (stockMove.getOriginId() != null
|
||||
&& StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
PurchaseOrder purchaseOrder = purchaseOrderRepository.find(stockMove.getOriginId());
|
||||
dummyInvoice.setCurrency(purchaseOrder.getCurrency());
|
||||
dummyInvoice.setPartner(purchaseOrder.getSupplierPartner());
|
||||
dummyInvoice.setCompany(purchaseOrder.getCompany());
|
||||
dummyInvoice.setTradingName(purchaseOrder.getTradingName());
|
||||
dummyInvoice.setPaymentCondition(purchaseOrder.getPaymentCondition());
|
||||
dummyInvoice.setPaymentMode(purchaseOrder.getPaymentMode());
|
||||
dummyInvoice.setContactPartner(purchaseOrder.getContactPartner());
|
||||
dummyInvoice.setPriceList(purchaseOrder.getPriceList());
|
||||
dummyInvoice.setInAti(purchaseOrder.getInAti());
|
||||
} else {
|
||||
dummyInvoice.setCurrency(stockMove.getCompany().getCurrency());
|
||||
dummyInvoice.setPartner(stockMove.getPartner());
|
||||
dummyInvoice.setCompany(stockMove.getCompany());
|
||||
dummyInvoice.setTradingName(stockMove.getTradingName());
|
||||
dummyInvoice.setAddress(stockMove.getFromAddress());
|
||||
dummyInvoice.setAddressStr(stockMove.getFromAddressStr());
|
||||
}
|
||||
return dummyInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will throw an exception if a stock move has already been invoiced. The exception
|
||||
* message will give every already invoiced stock move.
|
||||
*/
|
||||
protected void checkForAlreadyInvoicedStockMove(List<StockMove> stockMoveList)
|
||||
throws AxelorException {
|
||||
StringBuilder invoiceAlreadyGeneratedMessage = new StringBuilder();
|
||||
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
try {
|
||||
checkIfAlreadyInvoiced(stockMove);
|
||||
} catch (AxelorException e) {
|
||||
if (invoiceAlreadyGeneratedMessage.length() > 0) {
|
||||
invoiceAlreadyGeneratedMessage.append("<br/>");
|
||||
}
|
||||
invoiceAlreadyGeneratedMessage.append(e.getMessage());
|
||||
}
|
||||
}
|
||||
if (invoiceAlreadyGeneratedMessage.length() > 0) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY, invoiceAlreadyGeneratedMessage.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/** This method will throw an exception if the given stock move is already invoiced. */
|
||||
protected void checkIfAlreadyInvoiced(StockMove stockMove) throws AxelorException {
|
||||
if (stockMove.getInvoiceSet() != null
|
||||
&& stockMove
|
||||
.getInvoiceSet()
|
||||
.stream()
|
||||
.anyMatch(invoice -> invoice.getStatusSelect() != InvoiceRepository.STATUS_CANCELED)) {
|
||||
String templateMessage;
|
||||
if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING) {
|
||||
templateMessage = IExceptionMessage.OUTGOING_STOCK_MOVE_INVOICE_EXISTS;
|
||||
} else {
|
||||
templateMessage = IExceptionMessage.INCOMING_STOCK_MOVE_INVOICE_EXISTS;
|
||||
}
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(templateMessage),
|
||||
stockMove.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to complete a dummy invoice. If some fields are in conflict, empty them.
|
||||
*
|
||||
* @param dummyInvoice a dummy invoice used to store some fields that will be used to generate the
|
||||
* real invoice.
|
||||
* @param stockMove a stock move to invoice.
|
||||
*/
|
||||
protected void completeInvoiceInMultiOutgoingStockMove(
|
||||
Invoice dummyInvoice, StockMove stockMove) {
|
||||
|
||||
if (stockMove.getOriginId() != null
|
||||
&& StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Invoice comparedDummyInvoice = createDummyOutInvoice(stockMove);
|
||||
|
||||
if (dummyInvoice.getPaymentCondition() != null
|
||||
&& !dummyInvoice.getPaymentCondition().equals(comparedDummyInvoice.getPaymentCondition())) {
|
||||
dummyInvoice.setPaymentCondition(null);
|
||||
}
|
||||
|
||||
if (dummyInvoice.getPaymentMode() != null
|
||||
&& !dummyInvoice.getPaymentMode().equals(comparedDummyInvoice.getPaymentMode())) {
|
||||
dummyInvoice.setPaymentMode(null);
|
||||
}
|
||||
|
||||
if (dummyInvoice.getAddress() != null
|
||||
&& !dummyInvoice.getAddress().equals(comparedDummyInvoice.getAddress())) {
|
||||
dummyInvoice.setAddress(null);
|
||||
dummyInvoice.setAddressStr(null);
|
||||
}
|
||||
|
||||
if (dummyInvoice.getContactPartner() != null
|
||||
&& !dummyInvoice.getContactPartner().equals(comparedDummyInvoice.getContactPartner())) {
|
||||
dummyInvoice.setContactPartner(null);
|
||||
}
|
||||
|
||||
if (dummyInvoice.getPriceList() != null
|
||||
&& !dummyInvoice.getPriceList().equals(comparedDummyInvoice.getPriceList())) {
|
||||
dummyInvoice.setPriceList(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to complete a dummy invoice. If some fields are in conflict, empty them.
|
||||
*
|
||||
* @param dummyInvoice a dummy invoice used to store some fields that will be used to generate the
|
||||
* real invoice.
|
||||
* @param stockMove a stock move to invoice.
|
||||
*/
|
||||
protected void completeInvoiceInMultiIncomingStockMove(
|
||||
Invoice dummyInvoice, StockMove stockMove) {
|
||||
|
||||
if (stockMove.getOriginId() != null
|
||||
&& StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Invoice comparedDummyInvoice = createDummyInInvoice(stockMove);
|
||||
|
||||
if (dummyInvoice.getPaymentCondition() != null
|
||||
&& !dummyInvoice.getPaymentCondition().equals(comparedDummyInvoice.getPaymentCondition())) {
|
||||
dummyInvoice.setPaymentCondition(null);
|
||||
}
|
||||
|
||||
if (dummyInvoice.getPaymentMode() != null
|
||||
&& !dummyInvoice.getPaymentMode().equals(comparedDummyInvoice.getPaymentMode())) {
|
||||
dummyInvoice.setPaymentMode(null);
|
||||
}
|
||||
|
||||
if (dummyInvoice.getContactPartner() != null
|
||||
&& !dummyInvoice.getContactPartner().equals(comparedDummyInvoice.getContactPartner())) {
|
||||
dummyInvoice.setContactPartner(null);
|
||||
}
|
||||
|
||||
if (dummyInvoice.getPriceList() != null
|
||||
&& !dummyInvoice.getPriceList().equals(comparedDummyInvoice.getPriceList())) {
|
||||
dummyInvoice.setPriceList(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill external and internal reference in the given invoice, from the list of stock moves.
|
||||
*
|
||||
* @param stockMoveList
|
||||
* @param dummyInvoice
|
||||
*/
|
||||
protected void fillReferenceInvoiceFromMultiOutStockMove(
|
||||
List<StockMove> stockMoveList, Invoice dummyInvoice) {
|
||||
// Concat sequence, internal ref and external ref from all saleOrder
|
||||
List<String> externalRefList = new ArrayList<>();
|
||||
List<String> internalRefList = new ArrayList<>();
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
SaleOrder saleOrder =
|
||||
StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())
|
||||
&& stockMove.getOriginId() != null
|
||||
? saleOrderRepository.find(stockMove.getOriginId())
|
||||
: null;
|
||||
if (saleOrder != null) {
|
||||
externalRefList.add(saleOrder.getExternalReference());
|
||||
}
|
||||
internalRefList.add(
|
||||
stockMove.getStockMoveSeq()
|
||||
+ (saleOrder != null ? (":" + saleOrder.getSaleOrderSeq()) : ""));
|
||||
}
|
||||
|
||||
String externalRef = String.join("|", externalRefList);
|
||||
String internalRef = String.join("|", internalRefList);
|
||||
|
||||
dummyInvoice.setExternalReference(StringTool.cutTooLongString(externalRef));
|
||||
dummyInvoice.setInternalReference(StringTool.cutTooLongString(internalRef));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill external and internal reference in the given invoice, from the list of stock moves.
|
||||
*
|
||||
* @param stockMoveList
|
||||
* @param dummyInvoice
|
||||
*/
|
||||
protected void fillReferenceInvoiceFromMultiInStockMove(
|
||||
List<StockMove> stockMoveList, Invoice dummyInvoice) {
|
||||
// Concat sequence, internal ref and external ref from all saleOrder
|
||||
|
||||
List<String> externalRefList = new ArrayList<>();
|
||||
List<String> internalRefList = new ArrayList<>();
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
PurchaseOrder purchaseOrder =
|
||||
StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())
|
||||
&& stockMove.getOriginId() != null
|
||||
? purchaseOrderRepository.find(stockMove.getOriginId())
|
||||
: null;
|
||||
if (purchaseOrder != null) {
|
||||
if (purchaseOrder.getExternalReference() != null) {
|
||||
if (!purchaseOrder.getExternalReference().isEmpty()) {
|
||||
externalRefList.add(purchaseOrder.getExternalReference());
|
||||
}
|
||||
}
|
||||
}
|
||||
internalRefList.add(
|
||||
stockMove.getStockMoveSeq()
|
||||
+ (purchaseOrder != null ? (":" + purchaseOrder.getPurchaseOrderSeq()) : ""));
|
||||
}
|
||||
|
||||
String externalRef = String.join("|", externalRefList);
|
||||
String internalRef = String.join("|", internalRefList);
|
||||
|
||||
dummyInvoice.setExternalReference(StringTool.cutTooLongString(externalRef));
|
||||
dummyInvoice.setInternalReference(StringTool.cutTooLongString(internalRef));
|
||||
}
|
||||
|
||||
/**
|
||||
* Negate all price fields in invoice line.
|
||||
*
|
||||
* @param invoiceLine
|
||||
*/
|
||||
protected void negateInvoiceLinePrice(InvoiceLine invoiceLine) {
|
||||
// price
|
||||
invoiceLine.setPrice(invoiceLine.getPrice().negate());
|
||||
invoiceLine.setPriceDiscounted(invoiceLine.getPriceDiscounted().negate());
|
||||
invoiceLine.setInTaxPrice(invoiceLine.getInTaxPrice().negate());
|
||||
invoiceLine.setDiscountAmount(invoiceLine.getDiscountAmount().negate());
|
||||
|
||||
// totals
|
||||
invoiceLine.setInTaxTotal(invoiceLine.getInTaxTotal().negate());
|
||||
invoiceLine.setCompanyInTaxTotal(invoiceLine.getCompanyInTaxTotal().negate());
|
||||
invoiceLine.setExTaxTotal(invoiceLine.getExTaxTotal().negate());
|
||||
invoiceLine.setCompanyExTaxTotal(invoiceLine.getCompanyExTaxTotal().negate());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import java.util.List;
|
||||
|
||||
public interface StockMoveServiceSupplychain {
|
||||
|
||||
public List<StockMoveLine> addSubLines(List<StockMoveLine> list);
|
||||
|
||||
public List<StockMoveLine> removeSubLines(List<StockMoveLine> lines);
|
||||
|
||||
/**
|
||||
* For all lines in this stock move with quantity equal to 0, we empty the link to sale order
|
||||
* lines, allowing to delete non delivered sale order lines.
|
||||
*
|
||||
* @param stockMove
|
||||
*/
|
||||
void detachNonDeliveredStockMoveLines(StockMove stockMove);
|
||||
|
||||
void verifyProductStock(StockMove stockMove) throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,511 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.base.db.AppSupplychain;
|
||||
import com.axelor.apps.base.db.repo.ProductRepository;
|
||||
import com.axelor.apps.base.service.UnitConversionService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
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.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.SaleOrderLine;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.TrackingNumber;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.stock.service.PartnerProductQualityRatingService;
|
||||
import com.axelor.apps.stock.service.StockMoveLineService;
|
||||
import com.axelor.apps.stock.service.StockMoveServiceImpl;
|
||||
import com.axelor.apps.stock.service.StockMoveToolService;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.app.AppSupplychainService;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.Query;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class StockMoveServiceSupplychainImpl extends StockMoveServiceImpl
|
||||
implements StockMoveServiceSupplychain {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected AppSupplychainService appSupplyChainService;
|
||||
protected PurchaseOrderRepository purchaseOrderRepo;
|
||||
protected SaleOrderRepository saleOrderRepo;
|
||||
protected UnitConversionService unitConversionService;
|
||||
protected ReservedQtyService reservedQtyService;
|
||||
|
||||
@Inject private StockMoveLineServiceSupplychain stockMoveLineServiceSupplychain;
|
||||
|
||||
@Inject
|
||||
public StockMoveServiceSupplychainImpl(
|
||||
StockMoveLineService stockMoveLineService,
|
||||
StockMoveToolService stockMoveToolService,
|
||||
StockMoveLineRepository stockMoveLineRepository,
|
||||
AppBaseService appBaseService,
|
||||
StockMoveRepository stockMoveRepository,
|
||||
PartnerProductQualityRatingService partnerProductQualityRatingService,
|
||||
AppSupplychainService appSupplyChainService,
|
||||
PurchaseOrderRepository purchaseOrderRepo,
|
||||
SaleOrderRepository saleOrderRepo,
|
||||
UnitConversionService unitConversionService,
|
||||
ReservedQtyService reservedQtyService,
|
||||
ProductRepository productRepository) {
|
||||
super(
|
||||
stockMoveLineService,
|
||||
stockMoveToolService,
|
||||
stockMoveLineRepository,
|
||||
appBaseService,
|
||||
stockMoveRepository,
|
||||
partnerProductQualityRatingService,
|
||||
productRepository);
|
||||
this.appSupplyChainService = appSupplyChainService;
|
||||
this.purchaseOrderRepo = purchaseOrderRepo;
|
||||
this.saleOrderRepo = saleOrderRepo;
|
||||
this.unitConversionService = unitConversionService;
|
||||
this.reservedQtyService = reservedQtyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public String realize(StockMove stockMove, boolean check) throws AxelorException {
|
||||
LOG.debug("Réalisation du mouvement de stock : {} ", stockMove.getStockMoveSeq());
|
||||
String newStockSeq = super.realize(stockMove, check);
|
||||
AppSupplychain appSupplychain = appSupplyChainService.getAppSupplychain();
|
||||
|
||||
if (StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
updateSaleOrderLinesDeliveryState(stockMove, !stockMove.getIsReversion());
|
||||
// Update linked saleOrder delivery state depending on BackOrder's existence
|
||||
SaleOrder saleOrder = saleOrderRepo.find(stockMove.getOriginId());
|
||||
if (newStockSeq != null) {
|
||||
saleOrder.setDeliveryState(SaleOrderRepository.DELIVERY_STATE_PARTIALLY_DELIVERED);
|
||||
} else {
|
||||
Beans.get(SaleOrderStockService.class).updateDeliveryState(saleOrder);
|
||||
|
||||
if (appSupplychain.getTerminateSaleOrderOnDelivery()) {
|
||||
terminateOrConfirmSaleOrderStatus(saleOrder);
|
||||
}
|
||||
}
|
||||
|
||||
Beans.get(SaleOrderRepository.class).save(saleOrder);
|
||||
} else if (StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
updatePurchaseOrderLines(stockMove, !stockMove.getIsReversion());
|
||||
// Update linked purchaseOrder receipt state depending on BackOrder's existence
|
||||
PurchaseOrder purchaseOrder = purchaseOrderRepo.find(stockMove.getOriginId());
|
||||
if (newStockSeq != null) {
|
||||
purchaseOrder.setReceiptState(PurchaseOrderRepository.STATE_PARTIALLY_RECEIVED);
|
||||
} else {
|
||||
Beans.get(PurchaseOrderStockService.class).updateReceiptState(purchaseOrder);
|
||||
|
||||
if (appSupplychain.getTerminatePurchaseOrderOnReceipt()) {
|
||||
finishOrValidatePurchaseOrderStatus(purchaseOrder);
|
||||
}
|
||||
}
|
||||
|
||||
Beans.get(PurchaseOrderRepository.class).save(purchaseOrder);
|
||||
}
|
||||
if (appSupplyChainService.getAppSupplychain().getManageStockReservation()) {
|
||||
Beans.get(ReservedQtyService.class)
|
||||
.updateReservedQuantity(stockMove, StockMoveRepository.STATUS_REALIZED);
|
||||
}
|
||||
|
||||
detachNonDeliveredStockMoveLines(stockMove);
|
||||
|
||||
List<Long> trackingNumberIds =
|
||||
stockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.map(StockMoveLine::getTrackingNumber)
|
||||
.filter(Objects::nonNull)
|
||||
.map(TrackingNumber::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isNotEmpty(trackingNumberIds)) {
|
||||
Query update =
|
||||
JPA.em()
|
||||
.createQuery(
|
||||
"UPDATE FixedAsset self SET self.stockLocation = :stockLocation WHERE self.trackingNumber.id IN (:trackingNumber)");
|
||||
update.setParameter("stockLocation", stockMove.getToStockLocation());
|
||||
update.setParameter("trackingNumber", trackingNumberIds);
|
||||
update.executeUpdate();
|
||||
}
|
||||
|
||||
return newStockSeq;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachNonDeliveredStockMoveLines(StockMove stockMove) {
|
||||
if (stockMove.getStockMoveLineList() == null) {
|
||||
return;
|
||||
}
|
||||
stockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.filter(line -> line.getRealQty().signum() == 0)
|
||||
.forEach(line -> line.setSaleOrderLine(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void cancel(StockMove stockMove) throws AxelorException {
|
||||
if (stockMove.getStatusSelect() == StockMoveRepository.STATUS_REALIZED) {
|
||||
if (StockMoveRepository.ORIGIN_SALE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
updateSaleOrderOnCancel(stockMove);
|
||||
}
|
||||
if (StockMoveRepository.ORIGIN_PURCHASE_ORDER.equals(stockMove.getOriginTypeSelect())) {
|
||||
updatePurchaseOrderOnCancel(stockMove);
|
||||
}
|
||||
}
|
||||
super.cancel(stockMove);
|
||||
if (appSupplyChainService.getAppSupplychain().getManageStockReservation()) {
|
||||
Beans.get(ReservedQtyService.class)
|
||||
.updateReservedQuantity(stockMove, StockMoveRepository.STATUS_CANCELED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void plan(StockMove stockMove) throws AxelorException {
|
||||
super.plan(stockMove);
|
||||
if (appSupplyChainService.getAppSupplychain().getManageStockReservation()) {
|
||||
Beans.get(ReservedQtyService.class)
|
||||
.updateReservedQuantity(stockMove, StockMoveRepository.STATUS_PLANNED);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void updateSaleOrderOnCancel(StockMove stockMove) throws AxelorException {
|
||||
SaleOrder so = saleOrderRepo.find(stockMove.getOriginId());
|
||||
|
||||
updateSaleOrderLinesDeliveryState(stockMove, stockMove.getIsReversion());
|
||||
Beans.get(SaleOrderStockService.class).updateDeliveryState(so);
|
||||
|
||||
if (Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getTerminateSaleOrderOnDelivery()) {
|
||||
terminateOrConfirmSaleOrderStatus(so);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update saleOrder status from or to terminated status, from or to confirm status, depending on
|
||||
* its delivery state. Should be called only if we terminate sale order on receipt.
|
||||
*
|
||||
* @param saleOrder
|
||||
*/
|
||||
protected void terminateOrConfirmSaleOrderStatus(SaleOrder saleOrder) {
|
||||
if (saleOrder.getDeliveryState() == SaleOrderRepository.DELIVERY_STATE_DELIVERED) {
|
||||
saleOrder.setStatusSelect(SaleOrderRepository.STATUS_ORDER_COMPLETED);
|
||||
} else {
|
||||
saleOrder.setStatusSelect(SaleOrderRepository.STATUS_ORDER_CONFIRMED);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateSaleOrderLinesDeliveryState(StockMove stockMove, boolean qtyWasDelivered)
|
||||
throws AxelorException {
|
||||
for (StockMoveLine stockMoveLine : stockMove.getStockMoveLineList()) {
|
||||
if (stockMoveLine.getSaleOrderLine() != null) {
|
||||
SaleOrderLine saleOrderLine = stockMoveLine.getSaleOrderLine();
|
||||
|
||||
BigDecimal realQty =
|
||||
unitConversionService.convert(
|
||||
stockMoveLine.getUnit(),
|
||||
saleOrderLine.getUnit(),
|
||||
stockMoveLine.getRealQty(),
|
||||
stockMoveLine.getRealQty().scale(),
|
||||
saleOrderLine.getProduct());
|
||||
|
||||
if (stockMove.getTypeSelect() != StockMoveRepository.TYPE_INTERNAL) {
|
||||
if (qtyWasDelivered) {
|
||||
saleOrderLine.setDeliveredQty(saleOrderLine.getDeliveredQty().add(realQty));
|
||||
} else {
|
||||
saleOrderLine.setDeliveredQty(saleOrderLine.getDeliveredQty().subtract(realQty));
|
||||
}
|
||||
}
|
||||
if (saleOrderLine.getDeliveredQty().signum() == 0) {
|
||||
saleOrderLine.setDeliveryState(SaleOrderLineRepository.DELIVERY_STATE_NOT_DELIVERED);
|
||||
} else if (saleOrderLine.getDeliveredQty().compareTo(saleOrderLine.getQty()) < 0) {
|
||||
saleOrderLine.setDeliveryState(
|
||||
SaleOrderLineRepository.DELIVERY_STATE_PARTIALLY_DELIVERED);
|
||||
} else {
|
||||
saleOrderLine.setDeliveryState(SaleOrderLineRepository.DELIVERY_STATE_DELIVERED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void updatePurchaseOrderOnCancel(StockMove stockMove) throws AxelorException {
|
||||
PurchaseOrder po = purchaseOrderRepo.find(stockMove.getOriginId());
|
||||
|
||||
updatePurchaseOrderLines(stockMove, stockMove.getIsReversion());
|
||||
Beans.get(PurchaseOrderStockService.class).updateReceiptState(po);
|
||||
if (Beans.get(AppSupplychainService.class)
|
||||
.getAppSupplychain()
|
||||
.getTerminatePurchaseOrderOnReceipt()) {
|
||||
finishOrValidatePurchaseOrderStatus(po);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update purchaseOrder status from or to finished status, from or to validated status, depending
|
||||
* on its state. Should be called only if we terminate purchase order on receipt.
|
||||
*
|
||||
* @param purchaseOrder a purchase order.
|
||||
*/
|
||||
protected void finishOrValidatePurchaseOrderStatus(PurchaseOrder purchaseOrder) {
|
||||
|
||||
if (purchaseOrder.getReceiptState() == PurchaseOrderRepository.STATE_RECEIVED) {
|
||||
purchaseOrder.setStatusSelect(PurchaseOrderRepository.STATUS_FINISHED);
|
||||
} else {
|
||||
purchaseOrder.setStatusSelect(PurchaseOrderRepository.STATUS_VALIDATED);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updatePurchaseOrderLines(StockMove stockMove, boolean qtyWasReceived)
|
||||
throws AxelorException {
|
||||
for (StockMoveLine stockMoveLine : stockMove.getStockMoveLineList()) {
|
||||
if (stockMoveLine.getPurchaseOrderLine() != null) {
|
||||
PurchaseOrderLine purchaseOrderLine = stockMoveLine.getPurchaseOrderLine();
|
||||
|
||||
BigDecimal realQty =
|
||||
unitConversionService.convert(
|
||||
stockMoveLine.getUnit(),
|
||||
purchaseOrderLine.getUnit(),
|
||||
stockMoveLine.getRealQty(),
|
||||
stockMoveLine.getRealQty().scale(),
|
||||
purchaseOrderLine.getProduct());
|
||||
|
||||
if (qtyWasReceived) {
|
||||
purchaseOrderLine.setReceivedQty(purchaseOrderLine.getReceivedQty().add(realQty));
|
||||
} else {
|
||||
purchaseOrderLine.setReceivedQty(purchaseOrderLine.getReceivedQty().subtract(realQty));
|
||||
}
|
||||
if (purchaseOrderLine.getReceivedQty().signum() == 0) {
|
||||
purchaseOrderLine.setReceiptState(PurchaseOrderRepository.STATE_NOT_RECEIVED);
|
||||
} else if (purchaseOrderLine.getReceivedQty().compareTo(purchaseOrderLine.getQty()) < 0) {
|
||||
purchaseOrderLine.setReceiptState(PurchaseOrderRepository.STATE_PARTIALLY_RECEIVED);
|
||||
} else {
|
||||
purchaseOrderLine.setReceiptState(PurchaseOrderRepository.STATE_RECEIVED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StockMoveLine> addSubLines(List<StockMoveLine> moveLines) {
|
||||
|
||||
if (moveLines == null) {
|
||||
return moveLines;
|
||||
}
|
||||
|
||||
List<StockMoveLine> lines = new ArrayList<StockMoveLine>();
|
||||
lines.addAll(moveLines);
|
||||
for (StockMoveLine line : lines) {
|
||||
if (line.getSubLineList() == null) {
|
||||
continue;
|
||||
}
|
||||
for (StockMoveLine subLine : line.getSubLineList()) {
|
||||
if (subLine.getStockMove() == null) {
|
||||
moveLines.add(subLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
return moveLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StockMoveLine> removeSubLines(List<StockMoveLine> moveLines) {
|
||||
|
||||
if (moveLines == null) {
|
||||
return moveLines;
|
||||
}
|
||||
|
||||
List<StockMoveLine> subLines = new ArrayList<StockMoveLine>();
|
||||
for (StockMoveLine packLine : moveLines) {
|
||||
if (packLine != null
|
||||
&& packLine.getLineTypeSelect() != null
|
||||
&& packLine.getLineTypeSelect() == 2
|
||||
&& packLine.getSubLineList() != null) {
|
||||
packLine.getSubLineList().removeIf(it -> it.getId() != null && !moveLines.contains(it));
|
||||
subLines.addAll(packLine.getSubLineList());
|
||||
}
|
||||
}
|
||||
Iterator<StockMoveLine> lines = moveLines.iterator();
|
||||
|
||||
while (lines.hasNext()) {
|
||||
StockMoveLine subLine = lines.next();
|
||||
if (subLine.getId() != null
|
||||
&& subLine.getParentLine() != null
|
||||
&& !subLines.contains(subLine)) {
|
||||
lines.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return moveLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* The splitted stock move line needs an allocation and will be planned before the previous stock
|
||||
* move line is realized. To solve this issue, we deallocate here in the previous stock move line
|
||||
* the quantity that will be allocated in the generated stock move line. The quantity will be
|
||||
* reallocated when the generated stock move is planned.
|
||||
*
|
||||
* @param stockMoveLine the previous stock move line
|
||||
* @return the generated stock move line
|
||||
* @throws AxelorException
|
||||
*/
|
||||
@Override
|
||||
protected StockMoveLine copySplittedStockMoveLine(StockMoveLine stockMoveLine)
|
||||
throws AxelorException {
|
||||
StockMoveLine newStockMoveLine = super.copySplittedStockMoveLine(stockMoveLine);
|
||||
|
||||
if (appSupplyChainService.getAppSupplychain().getManageStockReservation()) {
|
||||
BigDecimal requestedReservedQty =
|
||||
stockMoveLine
|
||||
.getRequestedReservedQty()
|
||||
.subtract(stockMoveLine.getRealQty())
|
||||
.max(BigDecimal.ZERO);
|
||||
|
||||
newStockMoveLine.setRequestedReservedQty(requestedReservedQty);
|
||||
newStockMoveLine.setReservedQty(BigDecimal.ZERO);
|
||||
|
||||
reservedQtyService.deallocateStockMoveLineAfterSplit(
|
||||
stockMoveLine, stockMoveLine.getReservedQty());
|
||||
stockMoveLine.setReservedQty(BigDecimal.ZERO);
|
||||
}
|
||||
return newStockMoveLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyProductStock(StockMove stockMove) throws AxelorException {
|
||||
AppSupplychain appSupplychain = appSupplyChainService.getAppSupplychain();
|
||||
if (stockMove.getAvailabilityRequest()
|
||||
&& stockMove.getStockMoveLineList() != null
|
||||
&& appSupplychain.getIsVerifyProductStock()
|
||||
&& stockMove.getFromStockLocation() != null) {
|
||||
StringJoiner notAvailableProducts = new StringJoiner(",");
|
||||
int counter = 1;
|
||||
for (StockMoveLine stockMoveLine : stockMove.getStockMoveLineList()) {
|
||||
boolean isAvailableProduct =
|
||||
stockMoveLineServiceSupplychain.isAvailableProduct(stockMove, stockMoveLine);
|
||||
if (!isAvailableProduct && counter <= 10) {
|
||||
notAvailableProducts.add(stockMoveLine.getProduct().getFullName());
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(notAvailableProducts.toString())) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
String.format(
|
||||
I18n.get(IExceptionMessage.STOCK_MOVE_VERIFY_PRODUCT_STOCK_ERROR),
|
||||
notAvailableProducts.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// same move
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public StockMove splitInto2SameMove(
|
||||
StockMove originalStockMove, List<StockMoveLine> modifiedStockMoveLines)
|
||||
throws AxelorException {
|
||||
|
||||
StockMoveRepository stockMoveRepo = Beans.get(StockMoveRepository.class);
|
||||
StockMoveLineRepository stockMoveLineRepo = Beans.get(StockMoveLineRepository.class);
|
||||
|
||||
modifiedStockMoveLines =
|
||||
modifiedStockMoveLines
|
||||
.stream()
|
||||
.filter(stockMoveLine -> stockMoveLine.getQty().compareTo(BigDecimal.ZERO) != 0)
|
||||
.collect(Collectors.toList());
|
||||
for (StockMoveLine moveLine : modifiedStockMoveLines) {
|
||||
StockMoveLine newStockMoveLine = new StockMoveLine();
|
||||
// Set quantity in new stock move line
|
||||
newStockMoveLine = stockMoveLineRepo.copy(moveLine, false);
|
||||
newStockMoveLine.setQty(moveLine.getQty());
|
||||
newStockMoveLine.setRealQty(moveLine.getQty());
|
||||
newStockMoveLine.setProductTypeSelect(moveLine.getProductTypeSelect());
|
||||
newStockMoveLine.setSaleOrderLine(moveLine.getSaleOrderLine());
|
||||
newStockMoveLine.setPurchaseOrderLine(moveLine.getPurchaseOrderLine());
|
||||
newStockMoveLine.setCompanyUnitPriceUntaxed(moveLine.getCompanyUnitPriceUntaxed());
|
||||
newStockMoveLine.setUg(moveLine.getUg());
|
||||
newStockMoveLine.setPpa(moveLine.getPpa());
|
||||
newStockMoveLine.setPvg(moveLine.getPvg());
|
||||
newStockMoveLine.setStklim(moveLine.getStklim());
|
||||
newStockMoveLine.setShp(moveLine.getShp());
|
||||
// add stock move line
|
||||
originalStockMove.addStockMoveLineListItem(newStockMoveLine);
|
||||
|
||||
// find the original move line to update it
|
||||
Optional<StockMoveLine> correspondingMoveLine =
|
||||
originalStockMove
|
||||
.getStockMoveLineList()
|
||||
.stream()
|
||||
.filter(stockMoveLine -> stockMoveLine.getId().equals(moveLine.getId()))
|
||||
.findFirst();
|
||||
if (BigDecimal.ZERO.compareTo(moveLine.getQty()) > 0
|
||||
|| (correspondingMoveLine.isPresent()
|
||||
&& moveLine.getQty().compareTo(correspondingMoveLine.get().getRealQty()) > 0)) {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY, I18n.get(""), originalStockMove);
|
||||
}
|
||||
|
||||
if (correspondingMoveLine.isPresent()) {
|
||||
// Update quantity in original stock move.
|
||||
// If the remaining quantity is 0, remove the stock move line
|
||||
BigDecimal remainingQty = correspondingMoveLine.get().getQty().subtract(moveLine.getQty());
|
||||
if (BigDecimal.ZERO.compareTo(remainingQty) == 0) {
|
||||
// Remove the stock move line
|
||||
originalStockMove.removeStockMoveLineListItem(correspondingMoveLine.get());
|
||||
} else {
|
||||
correspondingMoveLine.get().setQty(remainingQty);
|
||||
correspondingMoveLine.get().setRealQty(remainingQty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!originalStockMove.getStockMoveLineList().isEmpty()) {
|
||||
return stockMoveRepo.save(originalStockMove);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 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.PriceListRepository;
|
||||
import com.axelor.apps.base.service.PartnerPriceListService;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
import com.axelor.apps.message.db.Message;
|
||||
import com.axelor.apps.message.db.Template;
|
||||
import com.axelor.apps.message.db.repo.MessageRepository;
|
||||
import com.axelor.apps.message.db.repo.TemplateRepository;
|
||||
import com.axelor.apps.message.service.TemplateMessageService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.SupplierCatalog;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.purchase.service.PurchaseOrderLineService;
|
||||
import com.axelor.apps.purchase.service.app.AppPurchaseService;
|
||||
import com.axelor.apps.stock.db.StockConfig;
|
||||
import com.axelor.apps.stock.db.StockLocation;
|
||||
import com.axelor.apps.stock.db.StockLocationLine;
|
||||
import com.axelor.apps.stock.db.StockRules;
|
||||
import com.axelor.apps.stock.db.repo.StockConfigRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockRulesRepository;
|
||||
import com.axelor.apps.stock.service.StockRulesServiceImpl;
|
||||
import com.axelor.auth.AuthUtils;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
public class StockRulesServiceSupplychainImpl extends StockRulesServiceImpl {
|
||||
|
||||
protected PurchaseOrderLineService purchaseOrderLineService;
|
||||
protected PurchaseOrderRepository purchaseOrderRepo;
|
||||
protected TemplateRepository templateRepo;
|
||||
protected TemplateMessageService templateMessageService;
|
||||
protected MessageRepository messageRepo;
|
||||
protected StockConfigRepository stockConfigRepo;
|
||||
|
||||
@Inject
|
||||
public StockRulesServiceSupplychainImpl(
|
||||
StockRulesRepository stockRuleRepo,
|
||||
PurchaseOrderLineService purchaseOrderLineService,
|
||||
PurchaseOrderRepository purchaseOrderRepo,
|
||||
TemplateRepository templateRepo,
|
||||
TemplateMessageService templateMessageService,
|
||||
MessageRepository messageRepo,
|
||||
StockConfigRepository stockConfigRepo) {
|
||||
super(stockRuleRepo);
|
||||
this.purchaseOrderLineService = purchaseOrderLineService;
|
||||
this.purchaseOrderRepo = purchaseOrderRepo;
|
||||
this.templateRepo = templateRepo;
|
||||
this.templateMessageService = templateMessageService;
|
||||
this.messageRepo = messageRepo;
|
||||
this.stockConfigRepo = stockConfigRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public void generatePurchaseOrder(
|
||||
Product product, BigDecimal qty, StockLocationLine stockLocationLine, int type)
|
||||
throws AxelorException {
|
||||
|
||||
StockLocation stockLocation = stockLocationLine.getStockLocation();
|
||||
|
||||
// TODO à supprimer après suppression des variantes
|
||||
if (stockLocation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
StockRules stockRules =
|
||||
this.getStockRules(
|
||||
product, stockLocation, type, StockRulesRepository.USE_CASE_STOCK_CONTROL);
|
||||
|
||||
if (stockRules == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.useMinStockRules(stockLocationLine, stockRules, qty, type)) {
|
||||
|
||||
if (stockRules.getOrderAlertSelect() == StockRulesRepository.ORDER_ALERT_ALERT) {
|
||||
|
||||
Template template = stockRules.getStockRuleMessageTemplate();
|
||||
|
||||
if (template == null) {
|
||||
StockConfig stockConfig =
|
||||
stockConfigRepo
|
||||
.all()
|
||||
.filter(
|
||||
"self.company = ?1 AND self.stockRuleMessageTemplate IS NOT NULL",
|
||||
stockRules.getStockLocation().getCompany())
|
||||
.fetchOne();
|
||||
if (stockConfig != null) {
|
||||
template = stockConfig.getStockRuleMessageTemplate();
|
||||
} else {
|
||||
template =
|
||||
templateRepo
|
||||
.all()
|
||||
.filter(
|
||||
"self.metaModel.fullName = ?1 AND self.isSystem != true",
|
||||
StockRules.class.getName())
|
||||
.fetchOne();
|
||||
}
|
||||
}
|
||||
|
||||
if (template != null) {
|
||||
try {
|
||||
Message message = templateMessageService.generateAndSendMessage(stockRules, template);
|
||||
} catch (ClassNotFoundException
|
||||
| InstantiationException
|
||||
| IllegalAccessException
|
||||
| MessagingException
|
||||
| IOException e) {
|
||||
throw new AxelorException(e, TraceBackRepository.TYPE_TECHNICAL);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (stockRules.getOrderAlertSelect()
|
||||
== StockRulesRepository.ORDER_ALERT_PRODUCTION_ORDER) {
|
||||
|
||||
} else if (stockRules.getOrderAlertSelect()
|
||||
== StockRulesRepository.ORDER_ALERT_PURCHASE_ORDER) {
|
||||
|
||||
BigDecimal minReorderQty = getDefaultSupplierMinQty(product);
|
||||
BigDecimal qtyToOrder =
|
||||
this.getQtyToOrder(qty, stockLocationLine, type, stockRules, minReorderQty);
|
||||
Partner supplierPartner = product.getDefaultSupplierPartner();
|
||||
|
||||
if (supplierPartner != null) {
|
||||
|
||||
Company company = stockLocation.getCompany();
|
||||
LocalDate today = Beans.get(AppBaseService.class).getTodayDate();
|
||||
|
||||
PurchaseOrderServiceSupplychainImpl purchaseOrderServiceSupplychainImpl =
|
||||
Beans.get(PurchaseOrderServiceSupplychainImpl.class);
|
||||
|
||||
PurchaseOrder purchaseOrder =
|
||||
purchaseOrderRepo.save(
|
||||
purchaseOrderServiceSupplychainImpl.createPurchaseOrder(
|
||||
AuthUtils.getUser(),
|
||||
company,
|
||||
null,
|
||||
supplierPartner.getCurrency(),
|
||||
today.plusDays(supplierPartner.getDeliveryDelay()),
|
||||
stockRules.getName(),
|
||||
"",
|
||||
null,
|
||||
stockLocation,
|
||||
today,
|
||||
Beans.get(PartnerPriceListService.class)
|
||||
.getDefaultPriceList(supplierPartner, PriceListRepository.TYPE_PURCHASE),
|
||||
supplierPartner,
|
||||
null));
|
||||
|
||||
purchaseOrder.addPurchaseOrderLineListItem(
|
||||
purchaseOrderLineService.createPurchaseOrderLine(
|
||||
purchaseOrder, product, null, null, qtyToOrder, product.getUnit()));
|
||||
|
||||
purchaseOrderServiceSupplychainImpl.computePurchaseOrder(purchaseOrder);
|
||||
|
||||
purchaseOrderRepo.save(purchaseOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minimum quantity from default supplier.
|
||||
*
|
||||
* @param product
|
||||
* @return
|
||||
*/
|
||||
private BigDecimal getDefaultSupplierMinQty(Product product) {
|
||||
Partner defaultSupplierPartner = product.getDefaultSupplierPartner();
|
||||
if (Beans.get(AppPurchaseService.class).getAppPurchase().getManageSupplierCatalog()) {
|
||||
List<SupplierCatalog> supplierCatalogList = product.getSupplierCatalogList();
|
||||
if (defaultSupplierPartner != null && supplierCatalogList != null) {
|
||||
for (SupplierCatalog supplierCatalog : supplierCatalogList) {
|
||||
if (supplierCatalog.getSupplierPartner().equals(defaultSupplierPartner)) {
|
||||
return supplierCatalog.getMinQty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.supplychain.db.SupplierRating;
|
||||
|
||||
public interface SupplierRatingService {
|
||||
|
||||
public void calculatRating(SupplierRating supplierRating);
|
||||
|
||||
public void createRating(StockMove stockMove);
|
||||
}
|
||||
@ -0,0 +1,426 @@
|
||||
package com.axelor.apps.supplychain.service;
|
||||
|
||||
import com.axelor.apps.account.db.PaymentCondition;
|
||||
import com.axelor.apps.base.db.Partner;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrderLine;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderLineRepository;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.supplychain.db.SupplierRating;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplierRatingRepository;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SupplierRatingServiceImpl implements SupplierRatingService {
|
||||
|
||||
public final int COEF_PAYMENT_DELAY = 1;
|
||||
public final int COEF_DELIVERY_TIME = 1;
|
||||
public final int COEF_COMPLIANCE_WITH_TECHNICAL_SPECIFICATIONS = 3;
|
||||
public final int COEF_PRICE = 3;
|
||||
public final int COEF_QUANTITY_COMPLIANCE = 1;
|
||||
public final int COEF_COMPLAINT_MANAGEMENT = 1;
|
||||
public final int COEF_PRICE_STABILITY = 1;
|
||||
public final int SUM_COEF =
|
||||
COEF_PAYMENT_DELAY
|
||||
+ COEF_DELIVERY_TIME
|
||||
+ COEF_COMPLIANCE_WITH_TECHNICAL_SPECIFICATIONS
|
||||
+ COEF_PRICE
|
||||
+ COEF_QUANTITY_COMPLIANCE
|
||||
+ COEF_COMPLAINT_MANAGEMENT
|
||||
+ COEF_PRICE_STABILITY;
|
||||
|
||||
public void calculatRating(SupplierRating supplierRating) {
|
||||
Integer paymentDelayRatingSelect = supplierRating.getPaymentDelayRatingSelect();
|
||||
Integer deliveryTimeRatingSelect = supplierRating.getDeliveryTimeRatingSelect();
|
||||
Integer complianceWithTechnicalSpecificationsRatingSelect =
|
||||
supplierRating.getComplianceWithTechnicalSpecificationsRatingSelect();
|
||||
Integer priceRatingSelect = supplierRating.getPriceRatingSelect();
|
||||
Integer quantityComplianceRatingSelect = supplierRating.getQuantityComplianceRatingSelect();
|
||||
Integer complaintManagementRatingSelect = supplierRating.getComplaintManagementRatingSelect();
|
||||
Integer priceStabilityRatingSelect = supplierRating.getPriceStabilityRatingSelect();
|
||||
|
||||
// Calculate the overall score based on ratings
|
||||
int score =
|
||||
(paymentDelayRatingSelect * COEF_PAYMENT_DELAY)
|
||||
+ (deliveryTimeRatingSelect * COEF_DELIVERY_TIME)
|
||||
+ (complianceWithTechnicalSpecificationsRatingSelect
|
||||
* COEF_COMPLIANCE_WITH_TECHNICAL_SPECIFICATIONS)
|
||||
+ (priceRatingSelect * COEF_PRICE)
|
||||
+ (quantityComplianceRatingSelect * COEF_QUANTITY_COMPLIANCE)
|
||||
+ (complaintManagementRatingSelect * COEF_COMPLAINT_MANAGEMENT)
|
||||
+ (priceStabilityRatingSelect * COEF_PRICE_STABILITY);
|
||||
|
||||
// Set the overall score
|
||||
BigDecimal overallScore =
|
||||
BigDecimal.valueOf(score * 20)
|
||||
.divide(BigDecimal.valueOf(SUM_COEF * 3), 2, BigDecimal.ROUND_HALF_UP);
|
||||
supplierRating.setOverallScore(overallScore);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void createRating(StockMove stockMove) {
|
||||
|
||||
StockMove stockMoveOrigin = stockMove.getStockMoveOrigin();
|
||||
Boolean isWithBackorder = stockMove.getIsWithBackorder();
|
||||
|
||||
if (stockMoveOrigin == null) {
|
||||
LocalDateTime realDate;
|
||||
if (isWithBackorder == true) {
|
||||
realDate = stockMove.getRealDate();
|
||||
} else {
|
||||
realDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
Partner partner = stockMove.getPartner();
|
||||
|
||||
// Stock Move Lines
|
||||
List<StockMoveLine> stockMoveLinesList = stockMove.getStockMoveLineList();
|
||||
|
||||
// Purchase Order
|
||||
Long originId = stockMove.getOriginId();
|
||||
PurchaseOrder purchaseOrder = Beans.get(PurchaseOrderRepository.class).find(originId);
|
||||
|
||||
// Purchase Order Lines
|
||||
List<PurchaseOrderLine> purchaseOrderLinesList = purchaseOrder.getPurchaseOrderLineList();
|
||||
|
||||
// Délai de payment
|
||||
PaymentCondition paymentCondition = purchaseOrder.getPaymentCondition();
|
||||
Integer paymentTime = paymentCondition.getPaymentTime();
|
||||
Integer paymentDelayRatingSelect = calculatPaymentDelayRatingSelect(paymentTime);
|
||||
|
||||
// Date
|
||||
LocalDate estimatedDate = purchaseOrder.getDeliveryDate();
|
||||
Integer deliveryTimeRatingSelect = calculatDeliveryTimeRatingSelect(estimatedDate, realDate);
|
||||
|
||||
// Quantité
|
||||
Integer quantityComplianceRatingSelect =
|
||||
calculatQuantityComplianceRatingSelect(stockMoveLinesList);
|
||||
|
||||
// Stabilité des prix
|
||||
Integer priceStabilityRatingSelect =
|
||||
calculatPriceStabilityRatingSelectLastPrice(purchaseOrderLinesList, partner);
|
||||
|
||||
// Creation
|
||||
SupplierRating supplierRating = new SupplierRating();
|
||||
supplierRating.setPaymentDelayRatingSelect(paymentDelayRatingSelect);
|
||||
supplierRating.setDeliveryTimeRatingSelect(deliveryTimeRatingSelect);
|
||||
supplierRating.setQuantityComplianceRatingSelect(quantityComplianceRatingSelect);
|
||||
supplierRating.setPriceStabilityRatingSelect(priceStabilityRatingSelect);
|
||||
supplierRating.setPurchaseOrder(purchaseOrder);
|
||||
supplierRating.setSupplierPartner(partner);
|
||||
Beans.get(SupplierRatingRepository.class).save(supplierRating);
|
||||
|
||||
} else {
|
||||
// Purchase Order
|
||||
Long originId = stockMove.getOriginId();
|
||||
PurchaseOrder purchaseOrder = Beans.get(PurchaseOrderRepository.class).find(originId);
|
||||
|
||||
List<StockMove> originStockMoves =
|
||||
Beans.get(StockMoveRepository.class)
|
||||
.all()
|
||||
.fetch()
|
||||
.stream()
|
||||
.filter(t -> t.getOriginId() == originId && t.getStatusSelect() == 3)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Purchase Order Lines
|
||||
List<PurchaseOrderLine> purchaseOrderLinesList = purchaseOrder.getPurchaseOrderLineList();
|
||||
|
||||
// Related Stock Moves Lines
|
||||
List<StockMoveLine> originStockMovesLines = new ArrayList<>();
|
||||
for (StockMove originStockMove : originStockMoves) {
|
||||
// Stock Move Lines
|
||||
List<StockMoveLine> originStockMovesLinesList = originStockMove.getStockMoveLineList();
|
||||
originStockMovesLines.addAll(originStockMovesLinesList);
|
||||
}
|
||||
// Quantité
|
||||
Integer quantityComplianceRatingSelect =
|
||||
calculatQuantityComplianceRatingSelect(purchaseOrderLinesList, originStockMovesLines);
|
||||
|
||||
// update
|
||||
SupplierRating supplierRating = purchaseOrder.getSupplierRating();
|
||||
supplierRating.setQuantityComplianceRatingSelect(quantityComplianceRatingSelect);
|
||||
}
|
||||
}
|
||||
|
||||
private Integer calculatPaymentDelayRatingSelect(Integer paymentTime) {
|
||||
Integer paymentDelayRatingSelect;
|
||||
if (paymentTime == 0) {
|
||||
paymentDelayRatingSelect = 0;
|
||||
} else if (paymentTime <= 15) {
|
||||
paymentDelayRatingSelect = 1;
|
||||
} else if (paymentTime <= 30) {
|
||||
paymentDelayRatingSelect = 2;
|
||||
} else {
|
||||
paymentDelayRatingSelect = 3;
|
||||
}
|
||||
return paymentDelayRatingSelect;
|
||||
}
|
||||
|
||||
private Integer calculatDeliveryTimeRatingSelect(
|
||||
LocalDate estimatedDate, LocalDateTime realDate) {
|
||||
int deliveryTimeRatingSelect;
|
||||
if (realDate.toLocalDate().isBefore(estimatedDate.plusDays(1))) {
|
||||
deliveryTimeRatingSelect = 3;
|
||||
} else if (realDate.toLocalDate().isBefore(estimatedDate.plusDays(5))) {
|
||||
deliveryTimeRatingSelect = 2;
|
||||
} else if (realDate.toLocalDate().isBefore(estimatedDate.plusDays(15))) {
|
||||
deliveryTimeRatingSelect = 1;
|
||||
} else {
|
||||
deliveryTimeRatingSelect = 0;
|
||||
}
|
||||
return deliveryTimeRatingSelect;
|
||||
}
|
||||
|
||||
private Integer calculatQuantityComplianceRatingSelect(List<StockMoveLine> stockMoveLinesList) {
|
||||
// Calculate total ordered quantity
|
||||
BigDecimal totalOrderedQty = BigDecimal.ZERO;
|
||||
BigDecimal totalReceivedQty = BigDecimal.ZERO;
|
||||
for (StockMoveLine stockMoveLine : stockMoveLinesList) {
|
||||
BigDecimal orderedQty = stockMoveLine.getQty();
|
||||
totalOrderedQty = totalOrderedQty.add(orderedQty);
|
||||
BigDecimal receivedQty = stockMoveLine.getRealQty();
|
||||
totalReceivedQty = totalReceivedQty.add(receivedQty);
|
||||
}
|
||||
|
||||
// Calculate quantity compliance
|
||||
BigDecimal compliancePercentage;
|
||||
if (totalOrderedQty.compareTo(BigDecimal.ZERO) != 0) {
|
||||
compliancePercentage =
|
||||
totalReceivedQty
|
||||
.divide(totalOrderedQty, 2, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
} else {
|
||||
// Handle division by zero if necessary
|
||||
compliancePercentage = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
System.out.println("totalOrderedQty1:" + totalOrderedQty);
|
||||
System.out.println("totalReceivedQty1:" + totalReceivedQty);
|
||||
System.out.println("compliancePercentage1:" + compliancePercentage);
|
||||
|
||||
// Determine quantityComplianceRatingSelect based on compliancePercentage
|
||||
Integer quantityComplianceRatingSelect;
|
||||
if (compliancePercentage.compareTo(BigDecimal.valueOf(100)) >= 0) {
|
||||
quantityComplianceRatingSelect = 3;
|
||||
} else if (compliancePercentage.compareTo(BigDecimal.valueOf(80)) >= 0) {
|
||||
quantityComplianceRatingSelect = 2;
|
||||
} else if (compliancePercentage.compareTo(BigDecimal.valueOf(70)) >= 0) {
|
||||
quantityComplianceRatingSelect = 1;
|
||||
} else {
|
||||
quantityComplianceRatingSelect = 0;
|
||||
}
|
||||
return quantityComplianceRatingSelect;
|
||||
}
|
||||
|
||||
private Integer calculatQuantityComplianceRatingSelect(
|
||||
List<PurchaseOrderLine> purchaseOrderLineslist, List<StockMoveLine> stockMoveLinesList) {
|
||||
// Calculate total ordered quantity
|
||||
BigDecimal totalOrderedQty = BigDecimal.ZERO;
|
||||
BigDecimal totalReceivedQty = BigDecimal.ZERO;
|
||||
for (StockMoveLine stockMoveLine : stockMoveLinesList) {
|
||||
BigDecimal receivedQty = stockMoveLine.getRealQty();
|
||||
totalReceivedQty = totalReceivedQty.add(receivedQty);
|
||||
}
|
||||
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLineslist) {
|
||||
BigDecimal orderedQty = purchaseOrderLine.getQty();
|
||||
totalOrderedQty = totalOrderedQty.add(orderedQty);
|
||||
}
|
||||
// Calculate quantity compliance
|
||||
BigDecimal compliancePercentage;
|
||||
if (totalOrderedQty.compareTo(BigDecimal.ZERO) != 0) {
|
||||
compliancePercentage =
|
||||
totalReceivedQty
|
||||
.divide(totalOrderedQty, 2, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
} else {
|
||||
// Handle division by zero if necessary
|
||||
compliancePercentage = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
System.out.println("totalOrderedQty2:" + totalOrderedQty);
|
||||
System.out.println("totalReceivedQty2:" + totalReceivedQty);
|
||||
System.out.println("compliancePercentage2:" + compliancePercentage);
|
||||
|
||||
// Determine quantityComplianceRatingSelect based on compliancePercentage
|
||||
Integer quantityComplianceRatingSelect;
|
||||
if (compliancePercentage.compareTo(BigDecimal.valueOf(100)) >= 0) {
|
||||
quantityComplianceRatingSelect = 3;
|
||||
} else if (compliancePercentage.compareTo(BigDecimal.valueOf(80)) >= 0) {
|
||||
quantityComplianceRatingSelect = 2;
|
||||
} else if (compliancePercentage.compareTo(BigDecimal.valueOf(70)) >= 0) {
|
||||
quantityComplianceRatingSelect = 1;
|
||||
} else {
|
||||
quantityComplianceRatingSelect = 0;
|
||||
}
|
||||
return quantityComplianceRatingSelect;
|
||||
}
|
||||
|
||||
private Integer calculatPriceStabilityRatingSelect(
|
||||
List<PurchaseOrderLine> purchaseOrderLinesList, Partner partner) {
|
||||
List<Product> products = new ArrayList<>();
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLinesList) {
|
||||
Product product = purchaseOrderLine.getProduct();
|
||||
products.add(product);
|
||||
}
|
||||
|
||||
List<BigDecimal> averages = new ArrayList<>();
|
||||
for (Product product : products) {
|
||||
BigDecimal avg = calculateAvgProduct(product, partner);
|
||||
averages.add(avg);
|
||||
}
|
||||
|
||||
List<BigDecimal> differences = new ArrayList<>();
|
||||
for (int i = 0; i < purchaseOrderLinesList.size(); i++) {
|
||||
BigDecimal price = purchaseOrderLinesList.get(i).getPrice();
|
||||
BigDecimal avg = averages.get(i);
|
||||
|
||||
BigDecimal difference = price.subtract(avg);
|
||||
BigDecimal percentage =
|
||||
difference.divide(avg, 4, BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100));
|
||||
|
||||
differences.add(percentage.abs());
|
||||
}
|
||||
|
||||
BigDecimal totalDifference = BigDecimal.ZERO;
|
||||
for (BigDecimal difference : differences) {
|
||||
totalDifference = totalDifference.add(difference);
|
||||
}
|
||||
|
||||
// Calculate the average percentage difference
|
||||
BigDecimal averageDifference =
|
||||
totalDifference.divide(BigDecimal.valueOf(differences.size()), 2, BigDecimal.ROUND_HALF_UP);
|
||||
|
||||
// Determine priceStabilityRatingSelect based on the average percentage difference
|
||||
Integer priceStabilityRatingSelect;
|
||||
if (averageDifference.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
priceStabilityRatingSelect = 3; // Stable prices
|
||||
} else if (averageDifference.compareTo(BigDecimal.valueOf(5)) <= 0) {
|
||||
priceStabilityRatingSelect = 2; // Moderately stable prices
|
||||
} else if (averageDifference.compareTo(BigDecimal.valueOf(10)) <= 0) {
|
||||
priceStabilityRatingSelect = 1; // Unstable prices
|
||||
} else {
|
||||
priceStabilityRatingSelect = 0;
|
||||
}
|
||||
|
||||
return priceStabilityRatingSelect;
|
||||
}
|
||||
|
||||
private Integer calculatPriceStabilityRatingSelectLastPrice(
|
||||
List<PurchaseOrderLine> purchaseOrderLinesList, Partner partner) {
|
||||
List<Product> products = new ArrayList<>();
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLinesList) {
|
||||
Product product = purchaseOrderLine.getProduct();
|
||||
products.add(product);
|
||||
}
|
||||
|
||||
List<BigDecimal> lastPrices = new ArrayList<>();
|
||||
for (Product product : products) {
|
||||
BigDecimal lastPrice = getLastPriceProduct(product, partner);
|
||||
lastPrices.add(lastPrice);
|
||||
}
|
||||
|
||||
List<BigDecimal> differences = new ArrayList<>();
|
||||
for (int i = 0; i < purchaseOrderLinesList.size(); i++) {
|
||||
BigDecimal price = purchaseOrderLinesList.get(i).getPrice();
|
||||
BigDecimal lastPrice = lastPrices.get(i);
|
||||
|
||||
BigDecimal difference = price.subtract(lastPrice);
|
||||
BigDecimal percentage =
|
||||
difference
|
||||
.divide(lastPrice, 4, BigDecimal.ROUND_HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
|
||||
differences.add(percentage);
|
||||
}
|
||||
|
||||
BigDecimal totalDifference = BigDecimal.ZERO;
|
||||
for (BigDecimal difference : differences) {
|
||||
totalDifference = totalDifference.add(difference);
|
||||
}
|
||||
|
||||
// Calculate the average percentage difference
|
||||
BigDecimal averageDifference =
|
||||
totalDifference.divide(BigDecimal.valueOf(differences.size()), 2, BigDecimal.ROUND_HALF_UP);
|
||||
|
||||
// Determine priceStabilityRatingSelect based on the average percentage difference
|
||||
Integer priceStabilityRatingSelect;
|
||||
if (averageDifference.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
priceStabilityRatingSelect = 3; // Stable prices
|
||||
} else if (averageDifference.compareTo(BigDecimal.valueOf(5)) <= 0) {
|
||||
priceStabilityRatingSelect = 2; // Moderately stable prices
|
||||
} else if (averageDifference.compareTo(BigDecimal.valueOf(10)) <= 0) {
|
||||
priceStabilityRatingSelect = 1; // Unstable prices
|
||||
} else {
|
||||
priceStabilityRatingSelect = 0;
|
||||
}
|
||||
|
||||
return priceStabilityRatingSelect;
|
||||
}
|
||||
|
||||
private BigDecimal calculateAvgProduct(Product product, Partner partner) {
|
||||
// Beans.get(PurchaseOrderRepository.class).find(originId);
|
||||
/*List<PurchaseOrderLine> purchaseOrderLines = Beans.get(PurchaseOrderLineRepository.class)
|
||||
.all()
|
||||
.filter("self.purchaseOrder.supplierPartner = :partner AND self.product = :product")
|
||||
.bind("partner", partner)
|
||||
.bind("product", product)
|
||||
.fetch();*/
|
||||
|
||||
List<PurchaseOrderLine> purchaseOrderLines =
|
||||
Beans.get(PurchaseOrderLineRepository.class)
|
||||
.all()
|
||||
.fetch()
|
||||
.stream()
|
||||
.filter(
|
||||
t ->
|
||||
t.getPurchaseOrder().getSupplierPartner() == partner
|
||||
&& t.getProduct() == product)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (purchaseOrderLines != null && !purchaseOrderLines.isEmpty()) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (PurchaseOrderLine purchaseOrderLine : purchaseOrderLines) {
|
||||
BigDecimal price = purchaseOrderLine.getPrice();
|
||||
total = total.add(price);
|
||||
}
|
||||
BigDecimal avgPrice =
|
||||
total.divide(BigDecimal.valueOf(purchaseOrderLines.size()), 2, BigDecimal.ROUND_HALF_UP);
|
||||
return avgPrice;
|
||||
}
|
||||
// Handle the case where there are no purchase order lines for the given product and partner
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal getLastPriceProduct(Product product, Partner partner) {
|
||||
|
||||
List<PurchaseOrderLine> purchaseOrderLines =
|
||||
Beans.get(PurchaseOrderLineRepository.class)
|
||||
.all()
|
||||
.filter("self.purchaseOrder.supplierPartner = :partner AND self.product = :product")
|
||||
.bind("partner", partner)
|
||||
.bind("product", product)
|
||||
.fetch();
|
||||
|
||||
PurchaseOrderLine lastPurchaseOrderLine =
|
||||
purchaseOrderLines.isEmpty() ? null : purchaseOrderLines.get(purchaseOrderLines.size() - 1);
|
||||
if (lastPurchaseOrderLine != null) {
|
||||
BigDecimal price = lastPurchaseOrderLine.getPrice();
|
||||
return price;
|
||||
} else {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.sale.db.SaleConfig;
|
||||
import com.axelor.apps.sale.service.config.SaleConfigService;
|
||||
|
||||
public interface SupplychainSaleConfigService extends SaleConfigService {
|
||||
|
||||
public void updateCustomerCredit(SaleConfig saleConfig);
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.AccountingSituation;
|
||||
import com.axelor.apps.account.db.repo.AccountingSituationRepository;
|
||||
import com.axelor.apps.sale.db.SaleConfig;
|
||||
import com.axelor.apps.sale.service.config.SaleConfigServiceImpl;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.util.List;
|
||||
|
||||
public class SupplychainSaleConfigServiceImpl extends SaleConfigServiceImpl
|
||||
implements SupplychainSaleConfigService {
|
||||
|
||||
@Inject private AccountingSituationRepository accountingSituationRepo;
|
||||
|
||||
@Transactional
|
||||
public void updateCustomerCredit(SaleConfig saleConfig) {
|
||||
|
||||
List<AccountingSituation> accountingSituationList =
|
||||
accountingSituationRepo
|
||||
.all()
|
||||
.filter("self.partner.isContact = false and self.partner.isCustomer = true")
|
||||
.fetch();
|
||||
|
||||
for (AccountingSituation accountingSituation : accountingSituationList) {
|
||||
accountingSituation.setAcceptedCredit(saleConfig.getAcceptedCredit());
|
||||
accountingSituationRepo.save(accountingSituation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.supplychain.db.Timetable;
|
||||
import com.axelor.apps.supplychain.db.TimetableTemplate;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface TimetableService {
|
||||
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice generateInvoice(Timetable timetable) throws AxelorException;
|
||||
|
||||
public Invoice createInvoice(Timetable timetable) throws AxelorException;
|
||||
|
||||
public List<Timetable> applyTemplate(
|
||||
TimetableTemplate template, BigDecimal exTaxTotal, LocalDate computationDate)
|
||||
throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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 com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.account.service.invoice.InvoiceToolService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.supplychain.db.Timetable;
|
||||
import com.axelor.apps.supplychain.db.TimetableTemplate;
|
||||
import com.axelor.apps.supplychain.db.TimetableTemplateLine;
|
||||
import com.axelor.apps.supplychain.db.repo.TimetableRepository;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class TimetableServiceImpl implements TimetableService {
|
||||
|
||||
@Inject SaleOrderInvoiceService saleOrderInvoiceService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = {Exception.class})
|
||||
public Invoice generateInvoice(Timetable timetable) throws AxelorException {
|
||||
Invoice invoice = this.createInvoice(timetable);
|
||||
Beans.get(InvoiceRepository.class).save(invoice);
|
||||
timetable.setInvoice(invoice);
|
||||
Beans.get(TimetableRepository.class).save(timetable);
|
||||
return invoice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Invoice createInvoice(Timetable timetable) throws AxelorException {
|
||||
SaleOrder saleOrder = timetable.getSaleOrder();
|
||||
// PurchaseOrder purchaseOrder = timetable.getPurchaseOrder();
|
||||
|
||||
if (saleOrder != null) {
|
||||
if (saleOrder.getCurrency() == null) {
|
||||
throw new AxelorException(
|
||||
timetable,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.SO_INVOICE_6),
|
||||
saleOrder.getSaleOrderSeq());
|
||||
}
|
||||
List<Long> timetableId = new ArrayList<>();
|
||||
timetableId.add(timetable.getId());
|
||||
Invoice invoice =
|
||||
saleOrderInvoiceService.generateInvoice(
|
||||
saleOrder,
|
||||
SaleOrderRepository.INVOICE_TIMETABLES,
|
||||
BigDecimal.ZERO,
|
||||
true,
|
||||
null,
|
||||
timetableId);
|
||||
|
||||
return invoice;
|
||||
}
|
||||
|
||||
/*
|
||||
if (purchaseOrder != null) {
|
||||
TODO handle purchaseOrders the same way we handle saleOrders.
|
||||
Requires adding partial invoicing for purchaseOrders.
|
||||
}
|
||||
*/
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Timetable> applyTemplate(
|
||||
TimetableTemplate template, BigDecimal exTaxTotal, LocalDate computationDate) {
|
||||
List<Timetable> timetables = new ArrayList<>();
|
||||
|
||||
for (TimetableTemplateLine templateLine : template.getTimetableTemplateLineList()) {
|
||||
Timetable timetable = new Timetable();
|
||||
timetable.setEstimatedDate(
|
||||
InvoiceToolService.getDueDate(templateLine.getPaymentCondition(), computationDate));
|
||||
timetable.setPercentage(templateLine.getPercentage());
|
||||
timetable.setAmount(
|
||||
exTaxTotal.multiply(templateLine.getPercentage()).divide(BigDecimal.valueOf(100)));
|
||||
timetables.add(timetable);
|
||||
}
|
||||
|
||||
timetables.sort(Comparator.comparing(Timetable::getEstimatedDate));
|
||||
return timetables;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.app;
|
||||
|
||||
import com.axelor.apps.base.db.AppSupplychain;
|
||||
import com.axelor.apps.base.service.app.AppBaseService;
|
||||
|
||||
public interface AppSupplychainService extends AppBaseService {
|
||||
|
||||
public AppSupplychain getAppSupplychain();
|
||||
|
||||
public void generateSupplychainConfigurations();
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.app;
|
||||
|
||||
import com.axelor.apps.base.db.AppSupplychain;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.base.db.repo.AppSupplychainRepository;
|
||||
import com.axelor.apps.base.db.repo.CompanyRepository;
|
||||
import com.axelor.apps.base.service.app.AppBaseServiceImpl;
|
||||
import com.axelor.apps.supplychain.db.SupplyChainConfig;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplyChainConfigRepository;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class AppSupplychainServiceImpl extends AppBaseServiceImpl implements AppSupplychainService {
|
||||
|
||||
@Inject private AppSupplychainRepository appSupplychainRepo;
|
||||
|
||||
@Inject private CompanyRepository companyRepo;
|
||||
|
||||
@Inject private SupplyChainConfigRepository supplyChainConfigRepo;
|
||||
|
||||
@Override
|
||||
public AppSupplychain getAppSupplychain() {
|
||||
return appSupplychainRepo.all().fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void generateSupplychainConfigurations() {
|
||||
|
||||
List<Company> companies = companyRepo.all().filter("self.supplyChainConfig is null").fetch();
|
||||
|
||||
for (Company company : companies) {
|
||||
SupplyChainConfig supplyChainConfig = new SupplyChainConfig();
|
||||
supplyChainConfig.setCompany(company);
|
||||
supplyChainConfigRepo.save(supplyChainConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.account.db.Move;
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.supplychain.db.SupplychainBatch;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.AccountingCutOffService;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.ExceptionOriginRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.google.inject.Inject;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BatchAccountingCutOff extends BatchStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected static final int FETCH_LIMIT = 1;
|
||||
|
||||
protected AccountingCutOffService cutOffService;
|
||||
protected StockMoveRepository stockMoveRepository;
|
||||
|
||||
@Inject
|
||||
public BatchAccountingCutOff(
|
||||
AccountingCutOffService cutOffService, StockMoveRepository stockMoveRepository) {
|
||||
super();
|
||||
this.cutOffService = cutOffService;
|
||||
this.stockMoveRepository = stockMoveRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process() {
|
||||
|
||||
int offset = 0;
|
||||
|
||||
SupplychainBatch supplychainBatch = batch.getSupplychainBatch();
|
||||
|
||||
LocalDate moveDate = supplychainBatch.getMoveDate();
|
||||
LocalDate reverseMoveDate = supplychainBatch.getReverseMoveDate();
|
||||
boolean recoveredTax = supplychainBatch.getRecoveredTax();
|
||||
boolean ati = supplychainBatch.getAti();
|
||||
String moveDescription = supplychainBatch.getMoveDescription();
|
||||
int accountingCutOffTypeSelect = supplychainBatch.getAccountingCutOffTypeSelect();
|
||||
Company company = supplychainBatch.getCompany();
|
||||
boolean includeNotStockManagedProduct = supplychainBatch.getIncludeNotStockManagedProduct();
|
||||
|
||||
if (accountingCutOffTypeSelect == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<StockMove> stockMoveList;
|
||||
|
||||
while (!(stockMoveList =
|
||||
cutOffService.getStockMoves(
|
||||
company, accountingCutOffTypeSelect, moveDate, FETCH_LIMIT, offset))
|
||||
.isEmpty()) {
|
||||
|
||||
findBatch();
|
||||
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
++offset;
|
||||
|
||||
try {
|
||||
List<Move> moveList =
|
||||
cutOffService.generateCutOffMoves(
|
||||
stockMove,
|
||||
moveDate,
|
||||
reverseMoveDate,
|
||||
accountingCutOffTypeSelect,
|
||||
recoveredTax,
|
||||
ati,
|
||||
moveDescription,
|
||||
includeNotStockManagedProduct);
|
||||
|
||||
if (moveList != null && !moveList.isEmpty()) {
|
||||
updateStockMove(stockMove);
|
||||
|
||||
for (Move move : moveList) {
|
||||
updateAccountMove(move, false);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (AxelorException e) {
|
||||
TraceBackService.trace(
|
||||
new AxelorException(
|
||||
e, e.getCategory(), I18n.get("StockMove") + " %s", stockMove.getStockMoveSeq()),
|
||||
ExceptionOriginRepository.INVOICE_ORIGIN,
|
||||
batch.getId());
|
||||
incrementAnomaly();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(
|
||||
new Exception(
|
||||
String.format(I18n.get("StockMove") + " %s", stockMove.getStockMoveSeq()), e),
|
||||
ExceptionOriginRepository.INVOICE_ORIGIN,
|
||||
batch.getId());
|
||||
incrementAnomaly();
|
||||
LOG.error("Anomaly generated for the stock move {}", stockMove.getStockMoveSeq());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JPA.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* As {@code batch} entity can be detached from the session, call {@code Batch.find()} get the
|
||||
* entity in the persistant context. Warning : {@code batch} entity have to be saved before.
|
||||
*/
|
||||
@Override
|
||||
protected void stop() {
|
||||
|
||||
String comment = I18n.get(IExceptionMessage.ACCOUNTING_CUT_OFF_GENERATION_REPORT) + " ";
|
||||
comment +=
|
||||
String.format(
|
||||
"\t* %s " + I18n.get(IExceptionMessage.ACCOUNTING_CUT_OFF_STOCK_MOVE_PROCESSED) + "\n",
|
||||
batch.getDone());
|
||||
comment +=
|
||||
String.format(
|
||||
"\t" + I18n.get(com.axelor.apps.base.exceptions.IExceptionMessage.ALARM_ENGINE_BATCH_4),
|
||||
batch.getAnomaly());
|
||||
|
||||
super.stop();
|
||||
addComment(comment);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderInvoiceService;
|
||||
import com.axelor.apps.supplychain.service.invoice.SubscriptionInvoiceService;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.ExceptionOriginRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.google.inject.Inject;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BatchInvoicing extends BatchStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Inject private SubscriptionInvoiceService subscriptionInvoiceService;
|
||||
|
||||
@Inject
|
||||
public BatchInvoicing(SaleOrderInvoiceService saleOrderInvoiceService) {
|
||||
|
||||
super(saleOrderInvoiceService);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process() {
|
||||
|
||||
List<SaleOrder> saleOrders = subscriptionInvoiceService.getSubscriptionOrders(FETCH_LIMIT);
|
||||
|
||||
while (!saleOrders.isEmpty()) {
|
||||
for (SaleOrder saleOrder : saleOrders) {
|
||||
try {
|
||||
subscriptionInvoiceService.generateSubscriptionInvoice(saleOrder);
|
||||
updateSaleOrder(saleOrder);
|
||||
} catch (AxelorException e) {
|
||||
TraceBackService.trace(
|
||||
new AxelorException(
|
||||
e, e.getCategory(), I18n.get("Order %s"), saleOrder.getSaleOrderSeq()),
|
||||
ExceptionOriginRepository.INVOICE_ORIGIN,
|
||||
batch.getId());
|
||||
incrementAnomaly();
|
||||
} catch (Exception e) {
|
||||
TraceBackService.trace(
|
||||
new Exception(String.format(I18n.get("Order %s"), saleOrder.getSaleOrderSeq()), e),
|
||||
ExceptionOriginRepository.INVOICE_ORIGIN,
|
||||
batch.getId());
|
||||
incrementAnomaly();
|
||||
|
||||
LOG.error("Bug(Anomalie) généré(e) pour le devis {}", saleOrder.getSaleOrderSeq());
|
||||
}
|
||||
}
|
||||
JPA.clear();
|
||||
saleOrders = subscriptionInvoiceService.getSubscriptionOrders(FETCH_LIMIT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* As {@code batch} entity can be detached from the session, call {@code Batch.find()} get the
|
||||
* entity in the persistent context. Warning : {@code batch} entity have to be saved before.
|
||||
*/
|
||||
@Override
|
||||
protected void stop() {
|
||||
|
||||
String comment = I18n.get(IExceptionMessage.BATCH_INVOICING_1) + " ";
|
||||
comment +=
|
||||
String.format(
|
||||
"\t* %s " + I18n.get(IExceptionMessage.BATCH_INVOICING_2) + "\n", batch.getDone());
|
||||
comment +=
|
||||
String.format(
|
||||
"\t" + I18n.get(com.axelor.apps.base.exceptions.IExceptionMessage.ALARM_ENGINE_BATCH_4),
|
||||
batch.getAnomaly());
|
||||
|
||||
super.stop();
|
||||
addComment(comment);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.base.service.administration.AbstractBatch;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.i18n.I18n;
|
||||
|
||||
public abstract class BatchOrderInvoicing extends AbstractBatch {
|
||||
|
||||
@Override
|
||||
protected void stop() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(I18n.get(IExceptionMessage.BATCH_ORDER_INVOICING_REPORT));
|
||||
sb.append(
|
||||
String.format(
|
||||
I18n.get(
|
||||
IExceptionMessage.BATCH_ORDER_INVOICING_DONE_SINGULAR,
|
||||
IExceptionMessage.BATCH_ORDER_INVOICING_DONE_PLURAL,
|
||||
batch.getDone()),
|
||||
batch.getDone()));
|
||||
sb.append(
|
||||
String.format(
|
||||
I18n.get(
|
||||
com.axelor.apps.base.exceptions.IExceptionMessage.ABSTRACT_BATCH_ANOMALY_SINGULAR,
|
||||
com.axelor.apps.base.exceptions.IExceptionMessage.ABSTRACT_BATCH_ANOMALY_PLURAL,
|
||||
batch.getAnomaly()),
|
||||
batch.getAnomaly()));
|
||||
addComment(sb.toString());
|
||||
super.stop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.base.db.repo.BlockingRepository;
|
||||
import com.axelor.apps.base.service.BlockingService;
|
||||
import com.axelor.apps.purchase.db.PurchaseOrder;
|
||||
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
|
||||
import com.axelor.apps.supplychain.db.SupplychainBatch;
|
||||
import com.axelor.apps.supplychain.service.PurchaseOrderInvoiceService;
|
||||
import com.axelor.apps.tool.StringTool;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.exception.db.repo.ExceptionOriginRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BatchOrderInvoicingPurchase extends BatchOrderInvoicing {
|
||||
|
||||
@Override
|
||||
protected void process() {
|
||||
SupplychainBatch supplychainBatch = batch.getSupplychainBatch();
|
||||
List<String> filterList = new ArrayList<>();
|
||||
Query<PurchaseOrder> query = Beans.get(PurchaseOrderRepository.class).all();
|
||||
|
||||
if (supplychainBatch.getCompany() != null) {
|
||||
filterList.add("self.company = :company");
|
||||
query.bind("company", supplychainBatch.getCompany());
|
||||
}
|
||||
|
||||
if (supplychainBatch.getSalespersonOrBuyerSet() != null
|
||||
&& !supplychainBatch.getSalespersonOrBuyerSet().isEmpty()) {
|
||||
filterList.add("self.buyerUser IN (:buyerSet)");
|
||||
query.bind("buyerSet", supplychainBatch.getSalespersonOrBuyerSet());
|
||||
}
|
||||
|
||||
if (supplychainBatch.getTeam() != null) {
|
||||
filterList.add("self.buyerUser IS NOT NULL AND self.buyerUser.activeTeam = :team");
|
||||
query.bind("team", supplychainBatch.getTeam());
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(supplychainBatch.getDeliveryOrReceiptState())) {
|
||||
List<Integer> receiptStateList =
|
||||
StringTool.getIntegerList(supplychainBatch.getDeliveryOrReceiptState());
|
||||
filterList.add("self.receiptState IN (:receiptStateList)");
|
||||
query.bind("receiptStateList", receiptStateList);
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(supplychainBatch.getStatusSelect())) {
|
||||
List<Integer> statusSelectList =
|
||||
StringTool.getIntegerList(supplychainBatch.getStatusSelect());
|
||||
filterList.add("self.statusSelect IN (:statusSelectList)");
|
||||
query.bind("statusSelectList", statusSelectList);
|
||||
}
|
||||
|
||||
if (supplychainBatch.getOrderUpToDate() != null) {
|
||||
filterList.add("self.orderDate <= :orderUpToDate");
|
||||
query.bind("orderUpToDate", supplychainBatch.getOrderUpToDate());
|
||||
}
|
||||
|
||||
filterList.add("self.amountInvoiced < self.exTaxTotal");
|
||||
|
||||
filterList.add(
|
||||
"NOT EXISTS (SELECT 1 FROM Invoice invoice WHERE invoice.statusSelect != :invoiceStatusSelect "
|
||||
+ "AND (invoice.purchaseOrder = self "
|
||||
+ "OR invoice.purchaseOrder IS NULL AND EXISTS (SELECT 1 FROM invoice.invoiceLineList invoiceLine "
|
||||
+ "WHERE invoiceLine.purchaseOrderLine MEMBER OF self.purchaseOrderLineList)))");
|
||||
|
||||
filterList.add(
|
||||
"self.supplierPartner.id NOT IN ("
|
||||
+ Beans.get(BlockingService.class)
|
||||
.listOfBlockedPartner(
|
||||
supplychainBatch.getCompany(), BlockingRepository.INVOICING_BLOCKING)
|
||||
+ ")");
|
||||
|
||||
query.bind("invoiceStatusSelect", InvoiceRepository.STATUS_CANCELED);
|
||||
|
||||
List<Long> anomalyList = Lists.newArrayList(0L);
|
||||
filterList.add("self.id NOT IN (:anomalyList)");
|
||||
query.bind("anomalyList", anomalyList);
|
||||
|
||||
String filter =
|
||||
filterList
|
||||
.stream()
|
||||
.map(item -> String.format("(%s)", item))
|
||||
.collect(Collectors.joining(" AND "));
|
||||
query.filter(filter);
|
||||
|
||||
PurchaseOrderInvoiceService purchaseOrderInvoiceService =
|
||||
Beans.get(PurchaseOrderInvoiceService.class);
|
||||
Set<Long> treatedSet = new HashSet<>();
|
||||
|
||||
for (List<PurchaseOrder> purchaseOrderList;
|
||||
!(purchaseOrderList = query.fetch(FETCH_LIMIT)).isEmpty();
|
||||
JPA.clear()) {
|
||||
for (PurchaseOrder purchaseOrder : purchaseOrderList) {
|
||||
if (treatedSet.contains(purchaseOrder.getId())) {
|
||||
throw new IllegalArgumentException("Invoice generation error");
|
||||
}
|
||||
|
||||
treatedSet.add(purchaseOrder.getId());
|
||||
|
||||
try {
|
||||
purchaseOrderInvoiceService.generateInvoice(purchaseOrder);
|
||||
incrementDone();
|
||||
} catch (Exception e) {
|
||||
incrementAnomaly();
|
||||
anomalyList.add(purchaseOrder.getId());
|
||||
query.bind("anomalyList", anomalyList);
|
||||
TraceBackService.trace(e, ExceptionOriginRepository.INVOICE_ORIGIN, batch.getId());
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.base.db.repo.BlockingRepository;
|
||||
import com.axelor.apps.base.service.BlockingService;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.supplychain.db.SupplychainBatch;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderInvoiceService;
|
||||
import com.axelor.apps.tool.StringTool;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.db.Query;
|
||||
import com.axelor.exception.db.repo.ExceptionOriginRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BatchOrderInvoicingSale extends BatchOrderInvoicing {
|
||||
|
||||
@Override
|
||||
protected void process() {
|
||||
SupplychainBatch supplychainBatch = batch.getSupplychainBatch();
|
||||
List<String> filterList = new ArrayList<>();
|
||||
Query<SaleOrder> query = Beans.get(SaleOrderRepository.class).all();
|
||||
|
||||
if (supplychainBatch.getCompany() != null) {
|
||||
filterList.add("self.company = :company");
|
||||
query.bind("company", supplychainBatch.getCompany());
|
||||
}
|
||||
|
||||
if (supplychainBatch.getSalespersonOrBuyerSet() != null
|
||||
&& !supplychainBatch.getSalespersonOrBuyerSet().isEmpty()) {
|
||||
filterList.add("self.salemanUser IN (:salespersonSet)");
|
||||
query.bind("salespersonSet", supplychainBatch.getSalespersonOrBuyerSet());
|
||||
}
|
||||
|
||||
if (supplychainBatch.getTeam() != null) {
|
||||
filterList.add(
|
||||
"self.team = :team "
|
||||
+ "OR self.team IS NULL AND self.salemanUser IS NOT NULL AND self.salemanUser.activeTeam = :team");
|
||||
query.bind("team", supplychainBatch.getTeam());
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(supplychainBatch.getDeliveryOrReceiptState())) {
|
||||
List<Integer> delivereyStateList =
|
||||
StringTool.getIntegerList(supplychainBatch.getDeliveryOrReceiptState());
|
||||
filterList.add("self.deliveryState IN (:delivereyStateList)");
|
||||
query.bind("delivereyStateList", delivereyStateList);
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(supplychainBatch.getStatusSelect())) {
|
||||
List<Integer> statusSelectList =
|
||||
StringTool.getIntegerList(supplychainBatch.getStatusSelect());
|
||||
filterList.add("self.statusSelect IN (:statusSelectList)");
|
||||
query.bind("statusSelectList", statusSelectList);
|
||||
}
|
||||
|
||||
if (supplychainBatch.getOrderUpToDate() != null) {
|
||||
filterList.add("self.orderDate <= :orderUpToDate");
|
||||
query.bind("orderUpToDate", supplychainBatch.getOrderUpToDate());
|
||||
}
|
||||
|
||||
filterList.add("self.amountInvoiced < self.exTaxTotal");
|
||||
|
||||
filterList.add(
|
||||
"NOT EXISTS (SELECT 1 FROM Invoice invoice WHERE invoice.statusSelect != :invoiceStatusSelect "
|
||||
+ "AND (invoice.saleOrder = self "
|
||||
+ "OR invoice.saleOrder IS NULL AND EXISTS (SELECT 1 FROM invoice.invoiceLineList invoiceLine "
|
||||
+ "WHERE invoiceLine.saleOrderLine MEMBER OF self.saleOrderLineList)))");
|
||||
|
||||
filterList.add(
|
||||
"self.clientPartner.id NOT IN ("
|
||||
+ Beans.get(BlockingService.class)
|
||||
.listOfBlockedPartner(
|
||||
supplychainBatch.getCompany(), BlockingRepository.INVOICING_BLOCKING)
|
||||
+ ")");
|
||||
|
||||
query.bind("invoiceStatusSelect", InvoiceRepository.STATUS_CANCELED);
|
||||
|
||||
List<Long> anomalyList = Lists.newArrayList(0L);
|
||||
filterList.add("self.id NOT IN (:anomalyList)");
|
||||
query.bind("anomalyList", anomalyList);
|
||||
|
||||
String filter =
|
||||
filterList
|
||||
.stream()
|
||||
.map(item -> String.format("(%s)", item))
|
||||
.collect(Collectors.joining(" AND "));
|
||||
query.filter(filter);
|
||||
|
||||
SaleOrderInvoiceService saleOrderInvoiceService = Beans.get(SaleOrderInvoiceService.class);
|
||||
Set<Long> treatedSet = new HashSet<>();
|
||||
|
||||
for (List<SaleOrder> saleOrderList;
|
||||
!(saleOrderList = query.fetch(FETCH_LIMIT)).isEmpty();
|
||||
JPA.clear()) {
|
||||
for (SaleOrder saleOrder : saleOrderList) {
|
||||
if (treatedSet.contains(saleOrder.getId())) {
|
||||
throw new IllegalArgumentException("Invoice generation error");
|
||||
}
|
||||
|
||||
treatedSet.add(saleOrder.getId());
|
||||
|
||||
try {
|
||||
saleOrderInvoiceService.generateInvoice(saleOrder);
|
||||
incrementDone();
|
||||
} catch (Exception e) {
|
||||
incrementAnomaly();
|
||||
anomalyList.add(saleOrder.getId());
|
||||
query.bind("anomalyList", anomalyList);
|
||||
TraceBackService.trace(e, ExceptionOriginRepository.INVOICE_ORIGIN, batch.getId());
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.base.db.repo.BlockingRepository;
|
||||
import com.axelor.apps.base.service.BlockingService;
|
||||
import com.axelor.apps.base.service.administration.AbstractBatch;
|
||||
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.supplychain.db.SupplychainBatch;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.apps.supplychain.service.StockMoveInvoiceService;
|
||||
import com.axelor.db.JPA;
|
||||
import com.axelor.exception.db.repo.ExceptionOriginRepository;
|
||||
import com.axelor.exception.service.TraceBackService;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import java.util.List;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
public class BatchOutgoingStockMoveInvoicing extends AbstractBatch {
|
||||
|
||||
private StockMoveInvoiceService stockMoveInvoiceService;
|
||||
|
||||
@Inject
|
||||
public BatchOutgoingStockMoveInvoicing(StockMoveInvoiceService stockMoveInvoiceService) {
|
||||
this.stockMoveInvoiceService = stockMoveInvoiceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process() {
|
||||
SupplychainBatch supplychainBatch = batch.getSupplychainBatch();
|
||||
List<Long> anomalyList = Lists.newArrayList(0L);
|
||||
SaleOrderRepository saleRepo = Beans.get(SaleOrderRepository.class);
|
||||
|
||||
int start = 0;
|
||||
TypedQuery<StockMove> query =
|
||||
JPA.em()
|
||||
.createQuery(
|
||||
"SELECT self FROM StockMove self "
|
||||
+ "LEFT JOIN self.invoice invoice "
|
||||
+ "WHERE self.statusSelect = :statusSelect "
|
||||
+ "AND self.originTypeSelect LIKE :typeSaleOrder "
|
||||
+ "AND (invoice IS NULL OR self.invoice.statusSelect = :invoiceStatusSelect)"
|
||||
+ "AND self.id NOT IN (:anomalyList) "
|
||||
+ "AND self.partner.id NOT IN ("
|
||||
+ Beans.get(BlockingService.class)
|
||||
.listOfBlockedPartner(
|
||||
supplychainBatch.getCompany(), BlockingRepository.INVOICING_BLOCKING)
|
||||
+ ")",
|
||||
StockMove.class)
|
||||
.setParameter("statusSelect", StockMoveRepository.STATUS_REALIZED)
|
||||
.setParameter("typeSaleOrder", StockMoveRepository.ORIGIN_SALE_ORDER)
|
||||
.setParameter("invoiceStatusSelect", InvoiceRepository.STATUS_CANCELED)
|
||||
.setParameter("anomalyList", anomalyList)
|
||||
.setMaxResults(FETCH_LIMIT);
|
||||
|
||||
for (List<StockMove> stockMoveList;
|
||||
!(stockMoveList = query.getResultList()).isEmpty();
|
||||
JPA.clear(), start += FETCH_LIMIT, query.setFirstResult(start)) {
|
||||
for (StockMove stockMove : stockMoveList) {
|
||||
try {
|
||||
stockMoveInvoiceService.createInvoiceFromSaleOrder(
|
||||
stockMove, saleRepo.find(stockMove.getOriginId()), null);
|
||||
incrementDone();
|
||||
} catch (Exception e) {
|
||||
incrementAnomaly();
|
||||
anomalyList.add(stockMove.getId());
|
||||
query.setParameter("anomalyList", anomalyList);
|
||||
TraceBackService.trace(e, ExceptionOriginRepository.INVOICE_ORIGIN, batch.getId());
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stop() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(I18n.get(IExceptionMessage.BATCH_OUTGOING_STOCK_MOVE_INVOICING_REPORT));
|
||||
sb.append(
|
||||
String.format(
|
||||
I18n.get(
|
||||
IExceptionMessage.BATCH_OUTGOING_STOCK_MOVE_INVOICING_DONE_SINGULAR,
|
||||
IExceptionMessage.BATCH_OUTGOING_STOCK_MOVE_INVOICING_DONE_PLURAL,
|
||||
batch.getDone()),
|
||||
batch.getDone()));
|
||||
sb.append(
|
||||
String.format(
|
||||
I18n.get(
|
||||
com.axelor.apps.base.exceptions.IExceptionMessage.ABSTRACT_BATCH_ANOMALY_SINGULAR,
|
||||
com.axelor.apps.base.exceptions.IExceptionMessage.ABSTRACT_BATCH_ANOMALY_PLURAL,
|
||||
batch.getAnomaly()),
|
||||
batch.getAnomaly()));
|
||||
addComment(sb.toString());
|
||||
super.stop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.account.db.Move;
|
||||
import com.axelor.apps.base.db.repo.BatchRepository;
|
||||
import com.axelor.apps.base.service.administration.AbstractBatch;
|
||||
import com.axelor.apps.sale.db.SaleOrder;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.supplychain.service.SaleOrderInvoiceService;
|
||||
import com.axelor.inject.Beans;
|
||||
|
||||
public abstract class BatchStrategy extends AbstractBatch {
|
||||
|
||||
protected SaleOrderInvoiceService saleOrderInvoiceService;
|
||||
|
||||
protected BatchStrategy() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected BatchStrategy(SaleOrderInvoiceService saleOrderInvoiceService) {
|
||||
super();
|
||||
this.saleOrderInvoiceService = saleOrderInvoiceService;
|
||||
}
|
||||
|
||||
protected void updateSaleOrder(SaleOrder saleOrder) {
|
||||
|
||||
saleOrder.addBatchSetItem(Beans.get(BatchRepository.class).find(batch.getId()));
|
||||
|
||||
incrementDone();
|
||||
}
|
||||
|
||||
protected void updateStockMove(StockMove stockMove) {
|
||||
|
||||
stockMove.addBatchSetItem(Beans.get(BatchRepository.class).find(batch.getId()));
|
||||
|
||||
incrementDone();
|
||||
}
|
||||
|
||||
protected void updateAccountMove(Move move, boolean incrementDone) {
|
||||
|
||||
move.addBatchSetItem(Beans.get(BatchRepository.class).find(batch.getId()));
|
||||
|
||||
if (incrementDone) {
|
||||
incrementDone();
|
||||
} else {
|
||||
checkPoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.base.db.Batch;
|
||||
import com.axelor.apps.base.exceptions.IExceptionMessage;
|
||||
import com.axelor.apps.sale.db.SaleBatch;
|
||||
import com.axelor.apps.sale.db.repo.SaleBatchRepository;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
public class SaleBatchService {
|
||||
|
||||
@Inject private SaleBatchRepository saleBatchRepo;
|
||||
|
||||
/**
|
||||
* Lancer un batch à partir de son code.
|
||||
*
|
||||
* @param batchCode Le code du batch souhaité.
|
||||
* @throws AxelorException
|
||||
*/
|
||||
public Batch run(String batchCode) throws AxelorException {
|
||||
|
||||
SaleBatch saleBatch = saleBatchRepo.findByCode(batchCode);
|
||||
|
||||
if (saleBatch != null) {
|
||||
switch (saleBatch.getActionSelect()) {
|
||||
default:
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.BASE_BATCH_1),
|
||||
saleBatch.getActionSelect(),
|
||||
batchCode);
|
||||
}
|
||||
} else {
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.BASE_BATCH_1),
|
||||
batchCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.batch;
|
||||
|
||||
import com.axelor.apps.base.db.Batch;
|
||||
import com.axelor.apps.base.exceptions.IExceptionMessage;
|
||||
import com.axelor.apps.base.service.administration.AbstractBatchService;
|
||||
import com.axelor.apps.supplychain.db.SupplychainBatch;
|
||||
import com.axelor.apps.supplychain.db.repo.SupplychainBatchRepository;
|
||||
import com.axelor.db.Model;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
|
||||
public class SupplychainBatchService extends AbstractBatchService {
|
||||
|
||||
@Override
|
||||
protected Class<? extends Model> getModelClass() {
|
||||
return SupplychainBatch.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Batch run(Model batchModel) throws AxelorException {
|
||||
|
||||
Batch batch;
|
||||
SupplychainBatch supplychainBatch = (SupplychainBatch) batchModel;
|
||||
|
||||
switch (supplychainBatch.getActionSelect()) {
|
||||
case SupplychainBatchRepository.ACTION_ACCOUNTING_CUT_OFF:
|
||||
batch = accountingCutOff(supplychainBatch);
|
||||
break;
|
||||
case SupplychainBatchRepository.ACTION_INVOICE_OUTGOING_STOCK_MOVES:
|
||||
batch = invoiceOutgoingStockMoves(supplychainBatch);
|
||||
break;
|
||||
case SupplychainBatchRepository.ACTION_INVOICE_ORDERS:
|
||||
batch = invoiceOrders(supplychainBatch);
|
||||
break;
|
||||
default:
|
||||
throw new AxelorException(
|
||||
TraceBackRepository.CATEGORY_INCONSISTENCY,
|
||||
I18n.get(IExceptionMessage.BASE_BATCH_1),
|
||||
supplychainBatch.getActionSelect(),
|
||||
supplychainBatch.getCode());
|
||||
}
|
||||
|
||||
return batch;
|
||||
}
|
||||
|
||||
public Batch accountingCutOff(SupplychainBatch supplychainBatch) {
|
||||
return Beans.get(BatchAccountingCutOff.class).run(supplychainBatch);
|
||||
}
|
||||
|
||||
public Batch invoiceOutgoingStockMoves(SupplychainBatch supplychainBatch) {
|
||||
return Beans.get(BatchOutgoingStockMoveInvoicing.class).run(supplychainBatch);
|
||||
}
|
||||
|
||||
public Batch invoiceOrders(SupplychainBatch supplychainBatch) {
|
||||
switch (supplychainBatch.getInvoiceOrdersTypeSelect()) {
|
||||
case SupplychainBatchRepository.INVOICE_ORDERS_TYPE_SALE:
|
||||
return Beans.get(BatchOrderInvoicingSale.class).run(supplychainBatch);
|
||||
case SupplychainBatchRepository.INVOICE_ORDERS_TYPE_PURCHASE:
|
||||
return Beans.get(BatchOrderInvoicingPurchase.class).run(supplychainBatch);
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Unknown invoice orders type: %d", supplychainBatch.getInvoiceOrdersTypeSelect()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.config;
|
||||
|
||||
import com.axelor.apps.account.db.Account;
|
||||
import com.axelor.apps.account.db.AccountConfig;
|
||||
import com.axelor.apps.account.service.config.AccountConfigService;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
|
||||
public class AccountConfigSupplychainService extends AccountConfigService {
|
||||
|
||||
public Account getForecastedInvCustAccount(AccountConfig accountConfig) throws AxelorException {
|
||||
if (accountConfig.getForecastedInvCustAccount() == null) {
|
||||
throw new AxelorException(
|
||||
accountConfig,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.FORECASTED_INVOICE_CUSTOMER_ACCOUNT),
|
||||
accountConfig.getCompany().getName());
|
||||
}
|
||||
return accountConfig.getForecastedInvCustAccount();
|
||||
}
|
||||
|
||||
public Account getForecastedInvSuppAccount(AccountConfig accountConfig) throws AxelorException {
|
||||
if (accountConfig.getForecastedInvSuppAccount() == null) {
|
||||
throw new AxelorException(
|
||||
accountConfig,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.FORECASTED_INVOICE_SUPPLIER_ACCOUNT),
|
||||
accountConfig.getCompany().getName());
|
||||
}
|
||||
return accountConfig.getForecastedInvSuppAccount();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.config;
|
||||
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.supplychain.db.SupplyChainConfig;
|
||||
import com.axelor.exception.AxelorException;
|
||||
|
||||
public interface SupplyChainConfigService {
|
||||
|
||||
public SupplyChainConfig getSupplyChainConfig(Company company) throws AxelorException;
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.config;
|
||||
|
||||
import com.axelor.apps.base.db.Company;
|
||||
import com.axelor.apps.supplychain.db.SupplyChainConfig;
|
||||
import com.axelor.apps.supplychain.exception.IExceptionMessage;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
|
||||
public class SupplyChainConfigServiceImpl implements SupplyChainConfigService {
|
||||
|
||||
@Override
|
||||
public SupplyChainConfig getSupplyChainConfig(Company company) throws AxelorException {
|
||||
|
||||
SupplyChainConfig supplyChainConfig = company.getSupplyChainConfig();
|
||||
|
||||
if (supplyChainConfig == null) {
|
||||
|
||||
throw new AxelorException(
|
||||
company,
|
||||
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
|
||||
I18n.get(IExceptionMessage.SUPPLY_CHAIN_CONFIG),
|
||||
company.getName());
|
||||
}
|
||||
|
||||
return supplyChainConfig;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.declarationofexchanges;
|
||||
|
||||
public interface DeclarationOfExchangesColumnHeader {
|
||||
String getTitle();
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.declarationofexchanges;
|
||||
|
||||
import com.axelor.app.AppSettings;
|
||||
import com.axelor.apps.supplychain.db.DeclarationOfExchanges;
|
||||
import com.axelor.apps.tool.StringTool;
|
||||
import com.axelor.dms.db.DMSFile;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.axelor.meta.MetaFiles;
|
||||
import com.axelor.meta.db.MetaFile;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
public abstract class DeclarationOfExchangesExporter {
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowableSupplier<T, E extends Throwable> {
|
||||
|
||||
/**
|
||||
* Get a result.
|
||||
*
|
||||
* @return a result
|
||||
* @throws Exception
|
||||
*/
|
||||
T get() throws E;
|
||||
}
|
||||
|
||||
protected final Map<String, ThrowableSupplier<String, AxelorException>> exportFuncMap;
|
||||
protected final DeclarationOfExchanges declarationOfExchanges;
|
||||
protected final ResourceBundle bundle;
|
||||
protected final String name;
|
||||
|
||||
protected List<String> columnHeadersList;
|
||||
|
||||
public DeclarationOfExchangesExporter(
|
||||
DeclarationOfExchanges declarationOfExchanges,
|
||||
ResourceBundle bundle,
|
||||
String name,
|
||||
List<String> columnHeadersList) {
|
||||
exportFuncMap = ImmutableMap.of("csv", this::exportToCSV, "pdf", this::exportToPDF);
|
||||
this.declarationOfExchanges = declarationOfExchanges;
|
||||
this.bundle = bundle;
|
||||
this.name = name;
|
||||
this.columnHeadersList = columnHeadersList;
|
||||
}
|
||||
|
||||
public Pair<Path, String> export() throws AxelorException {
|
||||
Path path =
|
||||
Paths.get(
|
||||
exportFuncMap
|
||||
.getOrDefault(declarationOfExchanges.getFormatSelect(), this::exportToUnsupported)
|
||||
.get());
|
||||
return Pair.of(path, getTitle());
|
||||
}
|
||||
|
||||
protected abstract String exportToCSV() throws AxelorException;
|
||||
|
||||
protected abstract String exportToPDF() throws AxelorException;
|
||||
|
||||
protected String exportToUnsupported() {
|
||||
throw new UnsupportedOperationException(
|
||||
String.format("Unsupported format: %s", declarationOfExchanges.getFormatSelect()));
|
||||
}
|
||||
|
||||
protected String getTranslatedName() {
|
||||
return getTranslation(name);
|
||||
}
|
||||
|
||||
protected String[] getTranslatedHeaders() {
|
||||
String[] headers = new String[columnHeadersList.size()];
|
||||
|
||||
for (int i = 0; i < columnHeadersList.size(); ++i) {
|
||||
headers[i] = getTranslation(columnHeadersList.get(i));
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
protected String getExportDir() {
|
||||
AppSettings appSettings = AppSettings.get();
|
||||
String exportDir = appSettings.get("data.export.dir");
|
||||
|
||||
if (exportDir == null) {
|
||||
throw new IllegalArgumentException(I18n.get("Export directory is not configured."));
|
||||
}
|
||||
|
||||
return exportDir;
|
||||
}
|
||||
|
||||
protected String getTitle() {
|
||||
String translatedName = getTranslatedName();
|
||||
return String.format("%s %s", translatedName, declarationOfExchanges.getPeriod().getName());
|
||||
}
|
||||
|
||||
protected String getFileName() {
|
||||
String title = getTitle();
|
||||
String filename = String.format("%s.%s", title, declarationOfExchanges.getFormatSelect());
|
||||
return StringTool.getFilename(filename);
|
||||
}
|
||||
|
||||
protected Path getFilePath() {
|
||||
String fileName = getFileName();
|
||||
return Paths.get(getExportDir(), fileName);
|
||||
}
|
||||
|
||||
protected String getTranslation(String text) {
|
||||
return bundle.getString(text);
|
||||
}
|
||||
|
||||
protected String attach(String path) {
|
||||
DMSFile dmsFile;
|
||||
|
||||
try (InputStream is = new FileInputStream(path)) {
|
||||
dmsFile = Beans.get(MetaFiles.class).attach(is, getFileName(), declarationOfExchanges);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
|
||||
MetaFile metaFile = dmsFile.getMetaFile();
|
||||
return String.format(
|
||||
"ws/rest/com.axelor.meta.db.MetaFile/%d/content/download?v=%d/%s",
|
||||
metaFile.getId(), metaFile.getVersion(), path);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,318 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2019 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.declarationofexchanges;
|
||||
|
||||
import com.axelor.apps.ReportFactory;
|
||||
import com.axelor.apps.account.db.Invoice;
|
||||
import com.axelor.apps.account.db.repo.InvoiceRepository;
|
||||
import com.axelor.apps.base.db.Address;
|
||||
import com.axelor.apps.base.db.Period;
|
||||
import com.axelor.apps.base.db.Product;
|
||||
import com.axelor.apps.report.engine.ReportSettings;
|
||||
import com.axelor.apps.stock.db.ModeOfTransport;
|
||||
import com.axelor.apps.stock.db.NatureOfTransaction;
|
||||
import com.axelor.apps.stock.db.Regime;
|
||||
import com.axelor.apps.stock.db.StockMove;
|
||||
import com.axelor.apps.stock.db.StockMoveLine;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveLineRepository;
|
||||
import com.axelor.apps.stock.db.repo.StockMoveRepository;
|
||||
import com.axelor.apps.stock.service.StockMoveToolService;
|
||||
import com.axelor.apps.supplychain.db.DeclarationOfExchanges;
|
||||
import com.axelor.apps.supplychain.report.IReport;
|
||||
import com.axelor.apps.tool.file.CsvTool;
|
||||
import com.axelor.auth.AuthUtils;
|
||||
import com.axelor.common.StringUtils;
|
||||
import com.axelor.exception.AxelorException;
|
||||
import com.axelor.exception.db.repo.TraceBackRepository;
|
||||
import com.axelor.i18n.I18n;
|
||||
import com.axelor.inject.Beans;
|
||||
import com.google.common.io.MoreFiles;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
public class DeclarationOfExchangesExporterGoods extends DeclarationOfExchangesExporter {
|
||||
protected static final String NAME_GOODS = /*$$(*/ "Declaration of exchanges of goods" /*)*/;
|
||||
|
||||
protected static final String LINE_NUM = /*$$(*/ "Line number" /*$$(*/;
|
||||
protected static final String NOMENCLATURE = /*$$(*/ "Nomenclature" /*$$(*/;
|
||||
protected static final String SRC_DST_COUNTRY = /*$$(*/ "Source or destination country" /*$$(*/;
|
||||
protected static final String FISC_VAL = /*$$(*/ "Fiscal value" /*$$(*/;
|
||||
protected static final String REGIME = /*$$(*/ "Regime" /*$$(*/;
|
||||
protected static final String MASS = /*$$(*/ "Net mass" /*$$(*/;
|
||||
protected static final String UNITS = /*$$(*/ "Supplementary unit" /*$$(*/;
|
||||
protected static final String NAT_TRANS = /*$$(*/ "Nature of transaction" /*$$(*/;
|
||||
protected static final String TRANSP = /*$$(*/ "Mode of transport" /*$$(*/;
|
||||
protected static final String DEPT = /*$$(*/ "Department" /*$$(*/;
|
||||
protected static final String COUNTRY_ORIG = /*$$(*/ "Country of origin" /*$$(*/;
|
||||
protected static final String ACQUIRER = /*$$(*/ "Acquirer" /*$$(*/;
|
||||
protected static final String PRODUCT_CODE = /*$$(*/ "Product code" /*$$(*/;
|
||||
protected static final String PRODUCT_NAME = /*$$(*/ "Product name" /*$$(*/;
|
||||
protected static final String PARTNER_SEQ = /*$$(*/ "Partner" /*$$(*/;
|
||||
protected static final String INVOICE = /*$$(*/ "Invoice" /*$$(*/;
|
||||
|
||||
protected StockMoveToolService stockMoveToolService;
|
||||
|
||||
public DeclarationOfExchangesExporterGoods(
|
||||
DeclarationOfExchanges declarationOfExchanges, ResourceBundle bundle) {
|
||||
super(
|
||||
declarationOfExchanges,
|
||||
bundle,
|
||||
NAME_GOODS,
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
LINE_NUM,
|
||||
NOMENCLATURE,
|
||||
SRC_DST_COUNTRY,
|
||||
FISC_VAL,
|
||||
REGIME,
|
||||
MASS,
|
||||
UNITS,
|
||||
NAT_TRANS,
|
||||
TRANSP,
|
||||
DEPT,
|
||||
COUNTRY_ORIG,
|
||||
ACQUIRER,
|
||||
PRODUCT_CODE,
|
||||
PRODUCT_NAME,
|
||||
PARTNER_SEQ,
|
||||
INVOICE)));
|
||||
this.stockMoveToolService = Beans.get(StockMoveToolService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String exportToCSV() throws AxelorException {
|
||||
Path path = getFilePath();
|
||||
|
||||
Period period = declarationOfExchanges.getPeriod();
|
||||
|
||||
List<StockMoveLine> stockMoveLines =
|
||||
Beans.get(StockMoveLineRepository.class)
|
||||
.findForDeclarationOfExchanges(
|
||||
period.getFromDate(),
|
||||
period.getToDate(),
|
||||
declarationOfExchanges.getProductTypeSelect(),
|
||||
declarationOfExchanges.getStockMoveTypeSelect(),
|
||||
declarationOfExchanges.getCountry(),
|
||||
declarationOfExchanges.getCompany())
|
||||
.fetch();
|
||||
List<String[]> dataList = new ArrayList<>(stockMoveLines.size());
|
||||
int lineNum = 1;
|
||||
|
||||
for (StockMoveLine stockMoveLine : stockMoveLines) {
|
||||
|
||||
String[] data = exportLineToCsv(stockMoveLine, lineNum);
|
||||
if (data != null && data.length != 0) {
|
||||
dataList.add(data);
|
||||
lineNum++;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
MoreFiles.createParentDirectories(path);
|
||||
CsvTool.csvWriter(
|
||||
path.getParent().toString(),
|
||||
path.getFileName().toString(),
|
||||
';',
|
||||
getTranslatedHeaders(),
|
||||
dataList);
|
||||
} catch (IOException e) {
|
||||
throw new AxelorException(
|
||||
e, TraceBackRepository.CATEGORY_CONFIGURATION_ERROR, e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
return attach(path.toString());
|
||||
}
|
||||
|
||||
protected String[] exportLineToCsv(StockMoveLine stockMoveLine, int lineNum)
|
||||
throws AxelorException {
|
||||
|
||||
String[] data = new String[columnHeadersList.size()];
|
||||
|
||||
StockMove stockMove = stockMoveLine.getStockMove();
|
||||
|
||||
String customsCode = stockMoveLine.getCustomsCode();
|
||||
|
||||
Product product = stockMoveLine.getProduct();
|
||||
|
||||
if (StringUtils.isBlank(customsCode)) {
|
||||
if (product == null) {
|
||||
customsCode = I18n.get("Product is missing.");
|
||||
}
|
||||
|
||||
if (product != null && product.getCustomsCodeNomenclature() != null) {
|
||||
customsCode = product.getCustomsCodeNomenclature().getCode();
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(customsCode)) {
|
||||
customsCode =
|
||||
String.format(
|
||||
I18n.get("Customs code nomenclature is missing on product %s."), product.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal fiscalValue =
|
||||
stockMoveLine
|
||||
.getCompanyUnitPriceUntaxed()
|
||||
.multiply(stockMoveLine.getRealQty())
|
||||
.setScale(0, RoundingMode.HALF_UP);
|
||||
|
||||
// Only positive fiscal value should be take into account
|
||||
if (fiscalValue.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
Regime regime = stockMoveLine.getRegime();
|
||||
|
||||
if (regime == null) {
|
||||
if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING) {
|
||||
regime = Regime.EXONERATED_SHIPMENT_AND_TRANSFER;
|
||||
} else if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING) {
|
||||
regime = Regime.INTRACOMMUNITY_ACQUISITION_TAXABLE_IN_FRANCE;
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal totalNetMass = stockMoveLine.getTotalNetMass().setScale(0, RoundingMode.HALF_UP);
|
||||
|
||||
BigInteger supplementaryUnit =
|
||||
stockMoveLine.getRealQty().setScale(0, RoundingMode.CEILING).toBigInteger();
|
||||
|
||||
NatureOfTransaction natTrans = stockMoveLine.getNatureOfTransaction();
|
||||
|
||||
if (natTrans == null) {
|
||||
natTrans =
|
||||
stockMove.getIsReversion()
|
||||
? NatureOfTransaction.RETURN_OF_GOODS
|
||||
: NatureOfTransaction.FIRM_PURCHASE_OR_SALE;
|
||||
}
|
||||
|
||||
ModeOfTransport modeOfTransport = stockMove.getModeOfTransport();
|
||||
|
||||
if (modeOfTransport == null) {
|
||||
modeOfTransport = ModeOfTransport.CONSIGNMENTS_BY_POST;
|
||||
}
|
||||
|
||||
String srcDstCountry;
|
||||
String dept;
|
||||
try {
|
||||
Address partnerAddress = stockMoveToolService.getPartnerAddress(stockMoveLine.getStockMove());
|
||||
srcDstCountry = partnerAddress.getAddressL7Country().getAlpha2Code();
|
||||
} catch (AxelorException e) {
|
||||
srcDstCountry = e.getMessage();
|
||||
}
|
||||
try {
|
||||
Address companyAddress = stockMoveToolService.getCompanyAddress(stockMoveLine.getStockMove());
|
||||
dept = companyAddress.getCity().getDepartment().getCode();
|
||||
} catch (AxelorException e) {
|
||||
dept = e.getMessage();
|
||||
}
|
||||
|
||||
String countryOrigCode;
|
||||
|
||||
if (stockMoveLine.getCountryOfOrigin() != null) {
|
||||
countryOrigCode = stockMoveLine.getCountryOfOrigin().getAlpha2Code();
|
||||
} else {
|
||||
if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING) {
|
||||
countryOrigCode = srcDstCountry;
|
||||
} else {
|
||||
countryOrigCode = "";
|
||||
}
|
||||
}
|
||||
|
||||
String taxNbr;
|
||||
if (stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING
|
||||
&& stockMoveLine.getRegime() != Regime.OTHER_EXPEDITIONS) {
|
||||
|
||||
if (stockMove.getPartner() == null) {
|
||||
taxNbr =
|
||||
String.format(I18n.get("Partner is missing on stock move %s."), stockMove.getName());
|
||||
} else if (StringUtils.isBlank(stockMove.getPartner().getTaxNbr())) {
|
||||
taxNbr =
|
||||
String.format(
|
||||
I18n.get("Tax number is missing on partner %s."), stockMove.getPartner().getName());
|
||||
} else {
|
||||
taxNbr = stockMove.getPartner().getTaxNbr();
|
||||
}
|
||||
} else {
|
||||
taxNbr = "";
|
||||
}
|
||||
|
||||
String partnerSeq = "";
|
||||
if (stockMove.getPartner() != null) {
|
||||
partnerSeq = stockMove.getPartner().getPartnerSeq();
|
||||
}
|
||||
|
||||
String productCode = "";
|
||||
String productName = "";
|
||||
if (product != null) {
|
||||
productCode = product.getCode();
|
||||
productName = product.getName();
|
||||
}
|
||||
|
||||
String invoiceId = "";
|
||||
Set<Invoice> invoiceSet = stockMove.getInvoiceSet();
|
||||
if (invoiceSet != null) {
|
||||
for (Invoice invoice : invoiceSet) {
|
||||
if (invoice.getStatusSelect() == InvoiceRepository.STATUS_VENTILATED) {
|
||||
invoiceId += invoice.getInvoiceId() + "|";
|
||||
}
|
||||
}
|
||||
if (invoiceId != null && !invoiceId.isEmpty()) {
|
||||
invoiceId = invoiceId.substring(0, invoiceId.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
data[columnHeadersList.indexOf(LINE_NUM)] = String.valueOf(lineNum);
|
||||
data[columnHeadersList.indexOf(NOMENCLATURE)] = customsCode;
|
||||
data[columnHeadersList.indexOf(SRC_DST_COUNTRY)] = srcDstCountry;
|
||||
data[columnHeadersList.indexOf(FISC_VAL)] = String.valueOf(fiscalValue);
|
||||
data[columnHeadersList.indexOf(REGIME)] = String.valueOf(regime.getValue());
|
||||
data[columnHeadersList.indexOf(MASS)] = String.valueOf(totalNetMass);
|
||||
data[columnHeadersList.indexOf(UNITS)] = String.valueOf(supplementaryUnit);
|
||||
data[columnHeadersList.indexOf(NAT_TRANS)] = String.valueOf(natTrans.getValue());
|
||||
data[columnHeadersList.indexOf(TRANSP)] = String.valueOf(modeOfTransport.getValue());
|
||||
data[columnHeadersList.indexOf(DEPT)] = dept;
|
||||
data[columnHeadersList.indexOf(COUNTRY_ORIG)] = countryOrigCode;
|
||||
data[columnHeadersList.indexOf(ACQUIRER)] = taxNbr;
|
||||
data[columnHeadersList.indexOf(PRODUCT_CODE)] = productCode;
|
||||
data[columnHeadersList.indexOf(PRODUCT_NAME)] = productName;
|
||||
data[columnHeadersList.indexOf(PARTNER_SEQ)] = partnerSeq;
|
||||
data[columnHeadersList.indexOf(INVOICE)] = invoiceId;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String exportToPDF() throws AxelorException {
|
||||
return ReportFactory.createReport(IReport.DECLARATION_OF_EXCHANGES_OF_GOODS, getTitle())
|
||||
.addParam("DeclarationOfExchangesId", declarationOfExchanges.getId())
|
||||
.addParam("UserId", AuthUtils.getUser().getId())
|
||||
.addParam("Locale", ReportSettings.getPrintingLocale())
|
||||
.addFormat(declarationOfExchanges.getFormatSelect())
|
||||
.toAttach(declarationOfExchanges)
|
||||
.generate()
|
||||
.getFileLink();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user