First commit waiting for Budget Alert

This commit is contained in:
2025-09-04 13:37:35 +01:00
commit 2d681f27f5
4563 changed files with 1061534 additions and 0 deletions

View 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")
}

View File

@ -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());
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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." /*)*/;
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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";
}

View File

@ -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"; /*)*/
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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()){
// }
// }
}

View File

@ -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;
}

View File

@ -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);
// }
}

View File

@ -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;
}
}

View File

@ -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);
});
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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 {}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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();
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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());
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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()));
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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