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,16 @@
apply plugin: "com.axelor.app-module"
apply from: "../version.gradle"
apply {
version = openSuiteVersion
}
axelor {
title "Axelor Sale"
description "Axelor Sale Module"
}
dependencies {
compile project(":modules:axelor-crm")
}

View File

@ -0,0 +1,38 @@
/*
* 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.sale.db.repo;
import com.axelor.apps.sale.db.AdvancePayment;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeService;
import com.axelor.inject.Beans;
import javax.persistence.PersistenceException;
public class AdvancePaymentSaleRepository extends AdvancePaymentRepository {
@Override
public AdvancePayment save(AdvancePayment advancePayment) {
try {
SaleOrder saleOrder = advancePayment.getSaleOrder();
Beans.get(SaleOrderComputeService.class)._computeSaleOrder(saleOrder);
return super.save(advancePayment);
} catch (Exception e) {
throw new PersistenceException(e);
}
}
}

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.sale.db.repo;
import com.axelor.apps.sale.db.SaleBatch;
public class SaleBatchSaleRepository extends SaleBatchRepository {
@Override
public SaleBatch copy(SaleBatch entity, boolean deep) {
SaleBatch copy = super.copy(entity, deep);
copy.setBatchList(null);
return copy;
}
}

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.sale.db.repo;
import com.axelor.apps.base.db.AppSale;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.apps.sale.service.app.AppSaleService;
import com.axelor.apps.sale.service.saleorder.SaleOrderLineService;
import com.google.inject.Inject;
public class SaleOrderLineSaleRepository extends SaleOrderLineRepository {
@Inject private SaleOrderLineService solService;
@Inject private AppSaleService appSaleService;
@Override
public SaleOrderLine save(SaleOrderLine soLine) {
AppSale appSale = appSaleService.getAppSale();
soLine = super.save(soLine);
if (appSale.getActive() && appSale.getProductPackMgt()) {
soLine.setTotalPack(solService.computeTotalPack(soLine));
}
return soLine;
}
}

View File

@ -0,0 +1,123 @@
/*
* 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.sale.db.repo;
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.service.saleorder.SaleOrderLineService;
import com.axelor.apps.sale.service.saleorder.SaleOrderMarginService;
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowServiceImpl;
import com.axelor.exception.AxelorException;
import com.axelor.inject.Beans;
import com.google.common.base.Strings;
import java.math.BigDecimal;
import javax.persistence.PersistenceException;
public class SaleOrderManagementRepository extends SaleOrderRepository {
@Override
public SaleOrder copy(SaleOrder entity, boolean deep) {
SaleOrder copy = super.copy(entity, deep);
copy.setStatusSelect(SaleOrderRepository.STATUS_DRAFT_QUOTATION);
copy.setSaleOrderSeq(null);
copy.clearBatchSet();
copy.setImportId(null);
copy.setCreationDate(Beans.get(AppBaseService.class).getTodayDate());
copy.setConfirmationDateTime(null);
copy.setConfirmedByUser(null);
copy.setOrderDate(null);
copy.setOrderNumber(null);
copy.setVersionNumber(1);
copy.setTotalCostPrice(null);
copy.setTotalGrossMargin(null);
copy.setMarginRate(null);
copy.setEndOfValidityDate(null);
copy.setDeliveryDate(null);
copy.setOrderBeingEdited(false);
if (copy.getAdvancePaymentAmountNeeded().compareTo(copy.getAdvanceTotal()) <= 0) {
copy.setAdvancePaymentAmountNeeded(BigDecimal.ZERO);
copy.setAdvancePaymentNeeded(false);
copy.clearAdvancePaymentList();
}
if (copy.getSaleOrderLineList() != null) {
for (SaleOrderLine saleOrderLine : copy.getSaleOrderLineList()) {
saleOrderLine.setDesiredDelivDate(null);
saleOrderLine.setEstimatedDelivDate(null);
}
}
return copy;
}
@Override
public SaleOrder save(SaleOrder saleOrder) {
try {
computeSeq(saleOrder);
computeFullName(saleOrder);
computeSubMargin(saleOrder);
Beans.get(SaleOrderMarginService.class).computeMarginSaleOrder(saleOrder);
return super.save(saleOrder);
} catch (Exception e) {
throw new PersistenceException(e.getLocalizedMessage());
}
}
public void computeSeq(SaleOrder saleOrder) {
try {
if (saleOrder.getId() == null) {
saleOrder = super.save(saleOrder);
}
if (Strings.isNullOrEmpty(saleOrder.getSaleOrderSeq()) && !saleOrder.getTemplate()) {
if (saleOrder.getStatusSelect() == SaleOrderRepository.STATUS_DRAFT_QUOTATION) {
saleOrder.setSaleOrderSeq(
Beans.get(SaleOrderWorkflowServiceImpl.class).getSequence(saleOrder.getCompany()));
}
}
} catch (Exception e) {
throw new PersistenceException(e.getLocalizedMessage());
}
}
public void computeFullName(SaleOrder saleOrder) {
try {
if (saleOrder.getClientPartner() != null) {
String fullName = saleOrder.getClientPartner().getName();
if (!Strings.isNullOrEmpty(saleOrder.getSaleOrderSeq())) {
fullName = saleOrder.getSaleOrderSeq() + "-" + fullName;
}
saleOrder.setFullName(fullName);
}
} catch (Exception e) {
throw new PersistenceException(e.getLocalizedMessage());
}
}
public void computeSubMargin(SaleOrder saleOrder) throws AxelorException {
if (saleOrder.getSaleOrderLineList() != null) {
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
Beans.get(SaleOrderLineService.class).computeSubMargin(saleOrder, saleOrderLine);
}
}
}
}

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.sale.exception;
import com.axelor.db.Model;
import com.axelor.exception.AxelorException;
import com.axelor.exception.db.repo.TraceBackRepository;
public class BlockedSaleOrderException extends AxelorException {
private static final long serialVersionUID = 4628915492959133388L;
public BlockedSaleOrderException(Model model, String message) {
super(model, TraceBackRepository.CATEGORY_CONFIGURATION_ERROR, message);
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.sale.exception;
/**
* Interface of Exceptions.
*
* @author dubaux
*/
public interface IExceptionMessage {
/** Sales Order Stock Move Service */
static final String SALES_ORDER_STOCK_MOVE_1 = /*$$(*/
"Invoice by delivery impose that all sale order lines must have service or stockable product with provision from stock" /*)*/;
/** Sales Order Service Impl */
static final String SALES_ORDER_1 = /*$$(*/
"The company %s doesn't have any configured sequence for sale orders" /*)*/;
static final String SALES_ORDER_COMPLETED = /*$$(*/ "This sale order is completed." /*)*/;
/** Sale Config Service */
static final String SALE_CONFIG_1 = /*$$(*/
"%s : You must configure Sales module for company %s" /*)*/;
/** Merge sale order */
public static final String SALE_ORDER_MERGE_ERROR_CURRENCY = /*$$(*/
"The currency is required and must be the same for all sale orders" /*)*/;
public static final String SALE_ORDER_MERGE_ERROR_CLIENT_PARTNER = /*$$(*/
"The client Partner is required and must be the same for all sale orders" /*)*/;
public static final String SALE_ORDER_MERGE_ERROR_COMPANY = /*$$(*/
"The company is required and must be the same for all sale orders" /*)*/;
static final String SALE_ORDER_PRINT = /*$$(*/ "Please select the sale order(s) to print." /*)*/;
static final String SALE_ORDER_MISSING_PRINTING_SETTINGS = /*$$(*/
"Please fill printing settings on sale order %s." /*)*/;
/** Configurator creator */
String CONFIGURATOR_CREATOR_SCRIPT_ERROR = /*$$(*/
"This script has errors, please see server logs for more details." /*)*/;
String CONFIGURATOR_CREATOR_FORMULA_TYPE_ERROR = /*$$(*/
"This script returned value is of type %s, it should return a value of type %s instead." /*)*/;
String CONFIGURATOR_CREATOR_SCRIPT_WORKING = /*$$(*/ "The syntax of the script is correct." /*)*/;
/** Configurator Service */
String CONFIGURATOR_PRODUCT_MISSING_NAME = /*$$(*/
"You must configure a script to fill the created product name." /*)*/;
String CONFIGURATOR_PRODUCT_MISSING_CODE = /*$$(*/
"You must configure a script to fill the created product code." /*)*/;
String CONFIGURATOR_SALE_ORDER_LINE_MISSING_PRODUCT_NAME = /*$$(*/
"You must configure a script to fill the product name in the created sale order line." /*)*/;
String CONFIGURATOR_ON_GENERATING_TYPE_ERROR = /*$$(*/
"The field %s is of type %s, but the configured script returned value is of type %s." /*)*/;
static final String SALE_ORDER_EDIT_ORDER_NOTIFY = /*$$(*/
"At least one sale order line has a stock move with availability request." /*)*/;
}

View File

@ -0,0 +1,92 @@
/*
* 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.sale.module;
import com.axelor.app.AxelorModule;
import com.axelor.apps.base.db.repo.PartnerAddressRepository;
import com.axelor.apps.base.service.PartnerServiceImpl;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.repo.AdvancePaymentRepository;
import com.axelor.apps.sale.db.repo.AdvancePaymentSaleRepository;
import com.axelor.apps.sale.db.repo.SaleBatchRepository;
import com.axelor.apps.sale.db.repo.SaleBatchSaleRepository;
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
import com.axelor.apps.sale.db.repo.SaleOrderLineSaleRepository;
import com.axelor.apps.sale.db.repo.SaleOrderManagementRepository;
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
import com.axelor.apps.sale.service.AddressServiceSaleImpl;
import com.axelor.apps.sale.service.AdvancePaymentService;
import com.axelor.apps.sale.service.AdvancePaymentServiceImpl;
import com.axelor.apps.sale.service.PartnerSaleServiceImpl;
import com.axelor.apps.sale.service.app.AppSaleService;
import com.axelor.apps.sale.service.app.AppSaleServiceImpl;
import com.axelor.apps.sale.service.config.SaleConfigService;
import com.axelor.apps.sale.service.config.SaleConfigServiceImpl;
import com.axelor.apps.sale.service.configurator.ConfiguratorCreatorImportService;
import com.axelor.apps.sale.service.configurator.ConfiguratorCreatorImportServiceImpl;
import com.axelor.apps.sale.service.configurator.ConfiguratorCreatorService;
import com.axelor.apps.sale.service.configurator.ConfiguratorCreatorServiceImpl;
import com.axelor.apps.sale.service.configurator.ConfiguratorFormulaService;
import com.axelor.apps.sale.service.configurator.ConfiguratorFormulaServiceImpl;
import com.axelor.apps.sale.service.configurator.ConfiguratorService;
import com.axelor.apps.sale.service.configurator.ConfiguratorServiceImpl;
import com.axelor.apps.sale.service.saleorder.OpportunitySaleOrderService;
import com.axelor.apps.sale.service.saleorder.OpportunitySaleOrderServiceImpl;
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeService;
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeServiceImpl;
import com.axelor.apps.sale.service.saleorder.SaleOrderCreateService;
import com.axelor.apps.sale.service.saleorder.SaleOrderCreateServiceImpl;
import com.axelor.apps.sale.service.saleorder.SaleOrderLineService;
import com.axelor.apps.sale.service.saleorder.SaleOrderLineServiceImpl;
import com.axelor.apps.sale.service.saleorder.SaleOrderMarginService;
import com.axelor.apps.sale.service.saleorder.SaleOrderMarginServiceImpl;
import com.axelor.apps.sale.service.saleorder.SaleOrderService;
import com.axelor.apps.sale.service.saleorder.SaleOrderServiceImpl;
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowService;
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowServiceImpl;
import com.axelor.apps.sale.service.saleorder.print.SaleOrderPrintService;
import com.axelor.apps.sale.service.saleorder.print.SaleOrderPrintServiceImpl;
public class SaleModule extends AxelorModule {
@Override
protected void configure() {
bind(AddressServiceSaleImpl.class);
bind(PartnerServiceImpl.class).to(PartnerSaleServiceImpl.class);
bind(SaleOrderService.class).to(SaleOrderServiceImpl.class);
bind(SaleOrderLineService.class).to(SaleOrderLineServiceImpl.class);
bind(SaleOrderRepository.class).to(SaleOrderManagementRepository.class);
bind(SaleOrderWorkflowService.class).to(SaleOrderWorkflowServiceImpl.class);
bind(SaleOrderMarginService.class).to(SaleOrderMarginServiceImpl.class);
bind(SaleOrderCreateService.class).to(SaleOrderCreateServiceImpl.class);
bind(SaleOrderComputeService.class).to(SaleOrderComputeServiceImpl.class);
bind(OpportunitySaleOrderService.class).to(OpportunitySaleOrderServiceImpl.class);
bind(AdvancePaymentService.class).to(AdvancePaymentServiceImpl.class);
bind(AppSaleService.class).to(AppSaleServiceImpl.class);
bind(SaleOrderLineRepository.class).to(SaleOrderLineSaleRepository.class);
bind(SaleConfigService.class).to(SaleConfigServiceImpl.class);
bind(SaleBatchRepository.class).to(SaleBatchSaleRepository.class);
PartnerAddressRepository.modelPartnerFieldMap.put(SaleOrder.class.getName(), "clientPartner");
bind(AdvancePaymentRepository.class).to(AdvancePaymentSaleRepository.class);
bind(ConfiguratorCreatorService.class).to(ConfiguratorCreatorServiceImpl.class);
bind(ConfiguratorService.class).to(ConfiguratorServiceImpl.class);
bind(ConfiguratorFormulaService.class).to(ConfiguratorFormulaServiceImpl.class);
bind(ConfiguratorCreatorImportService.class).to(ConfiguratorCreatorImportServiceImpl.class);
bind(SaleOrderPrintService.class).to(SaleOrderPrintServiceImpl.class);
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.sale.report;
public interface IReport {
public static final String SALES_ORDER = "SaleOrder.rptdesign";
}

View File

@ -0,0 +1,82 @@
/*
* 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.sale.report;
public interface ITranslation {
public static final String SALES_ORDER_QUOTE = /*$$(*/ "SaleOrder.quote"; /*)*/
public static final String SALES_ORDER_ORDER = /*$$(*/ "SaleOrder.order"; /*)*/
public static final String SALES_ORDER_PROFORMA = /*$$(*/ "SaleOrder.proforma"; /*)*/
public static final String SALES_ORDER_NO = /*$$(*/ "SaleOrder.no"; /*)*/
public static final String SALES_ORDER_DATE = /*$$(*/ "SaleOrder.date"; /*)*/
public static final String SALES_ORDER_PAYMENT_CONDITION = /*$$(*/
"SaleOrder.paymentCondition"; /*)*/
public static final String SALES_ORDER_PAYMENT_MODE = /*$$(*/ "SaleOrder.paymentMode"; /*)*/
public static final String SALES_ORDER_SUPPLY_REF = /*$$(*/ "SaleOrder.supplyRef"; /*)*/
public static final String SALES_ORDER_CUSTOMER_REF = /*$$(*/ "SaleOrder.customerRef"; /*)*/
public static final String SALES_ORDER_CUSTOMER_CODE = /*$$(*/ "SaleOrder.customerCode"; /*)*/
public static final String SALES_ORDER_SUPPLIER = /*$$(*/ "SaleOrder.supplier"; /*)*/
public static final String SALES_ORDER_CUSTOMER = /*$$(*/ "SaleOrder.customer"; /*)*/
public static final String SALES_ORDER_INVOICING_ADDRS = /*$$(*/ "SaleOrder.invoicingAddrs"; /*)*/
public static final String SALES_ORDER_DELIVERY_ADDRESS = /*$$(*/
"SaleOrder.deliveryAddress"; /*)*/
public static final String SALES_ORDER_DESCRIPTION = /*$$(*/ "SaleOrder.description"; /*)*/
public static final String SALES_ORDER_TAX = /*$$(*/ "SaleOrder.tax"; /*)*/
public static final String SALES_ORDER_QTY_UNIT = /*$$(*/ "SaleOrder.qtyUnit"; /*)*/
public static final String SALES_ORDER_UNIT_PRICE = /*$$(*/ "SaleOrder.unitPrice"; /*)*/
public static final String SALES_ORDER_PRICE_EXCL_TAX = /*$$(*/ "SaleOrder.priceExclTax"; /*)*/
public static final String SALES_ORDER_PRICE_INCL_TAX = /*$$(*/ "SaleOrder.priceInclTax"; /*)*/
public static final String SALES_ORDER_BASE = /*$$(*/ "SaleOrder.base"; /*)*/
public static final String SALES_ORDER_TAX_AMOUNT = /*$$(*/ "SaleOrder.taxAmount"; /*)*/
public static final String SALES_ORDER_TOTAL_EXCL_TAX = /*$$(*/ "SaleOrder.totalExclTax"; /*)*/
public static final String SALES_ORDER_TOTAL_TAX = /*$$(*/ "SaleOrder.totalTax"; /*)*/
public static final String SALES_ORDER_TOTAL_INCL_TAX = /*$$(*/ "SaleOrder.totalInclTax"; /*)*/
public static final String SALES_ORDER_NOTE = /*$$(*/ "SaleOrder.note"; /*)*/
public static final String SALES_ORDER_CHEQUE = /*$$(*/ "SaleOrder.cheque"; /*)*/
public static final String SALES_ORDER_BANK = /*$$(*/ "SaleOrder.bank"; /*)*/
public static final String SALES_ORDER_SALEMAN_NAME = /*$$(*/ "SaleOrder.salemanName"; /*)*/
public static final String SALES_ORDER_SALEMAN_EMAIL = /*$$(*/ "SaleOrder.salemanEmail"; /*)*/
public static final String SALES_ORDER_SALEMAN_PHONE = /*$$(*/ "SaleOrder.salemanPhone"; /*)*/
public static final String SALES_ORDER_DELIVERY_DATE = /*$$(*/ "SaleOrder.deliveryDate"; /*)*/
public static final String SALES_ORDER_DELIVERY_CONDITION = /*$$(*/
"SaleOrder.deliveryCondition"; /*)*/
public static final String SALES_ORDER_PRODUCT_DESCRIPTION = /*$$(*/
"SaleOrder.productDescription"; /*)*/
public static final String SALES_ORDER_PRODUCT_CODE = /*$$(*/ "SaleOrder.productCode"; /*)*/
public static final String SALES_ORDER_PRODUCT_NAME = /*$$(*/ "SaleOrder.productName"; /*)*/
public static final String SALES_ORDER_DISCOUNT_AMOUNT = /*$$(*/ "SaleOrder.discountAmount"; /*)*/
public static final String SALES_ORDER_TOTAL_EXCL_TAX_WITHOUT_DISCOUNT = /*$$(*/
"SaleOrder.totalExclTaxWithoutDiscount"; /*)*/
public static final String SALES_ORDER_TOTAL_DISCOUNT = /*$$(*/ "SaleOrder.totalDiscount"; /*)*/
public static final String SALES_ORDER_AFTER_DISCOUNT = /*$$(*/ "SaleOrder.afterDiscount"; /*)*/
public static final String SALES_ORDER_OTHERS = /*$$(*/ "SaleOrder.others"; /*)*/
public static final String SALES_ORDER_SUBSCRIPTION_CONTRACT = /*$$(*/
"SaleOrder.subscriptionContract"; /*)*/
public static final String SALES_ORDER_SUBSCRIPTION_PERIODICITY = /*$$(*/
"SaleOrder.periodicity"; /*)*/
public static final String SALES_ORDER_DURATION = /*$$(*/ "SaleOrder.duration"; /*)*/;
public static final String SALES_ORDER_VALIDITY_DATE = /*$$(*/ "SaleOrder.validityDate"; /*)*/;
public static final String SALES_ORDER_ESTIMATED_DELIVERY_DATE = /*$$(*/
"SaleOrder.estimatedDeliveryDate"; /*)*/;
public static final String SALES_ORDER_IS_ISPM_REQUIRED = /*$$(*/
"SaleOrder.isIspmRequired"; /*)*/;
public static final String SALES_ORDER_PRODUCT_SEQUENCE = /*$$(*/
"SaleOrder.productSequence"; /*)*/
}

View File

@ -0,0 +1,134 @@
/*
* 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.sale.service;
import static com.axelor.apps.base.service.administration.AbstractBatch.FETCH_LIMIT;
import static com.axelor.apps.tool.date.DateTool.toDate;
import static com.axelor.apps.tool.date.DateTool.toLocalDateT;
import com.axelor.apps.base.db.ABCAnalysis;
import com.axelor.apps.base.db.ABCAnalysisLine;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.base.db.repo.ABCAnalysisClassRepository;
import com.axelor.apps.base.db.repo.ABCAnalysisLineRepository;
import com.axelor.apps.base.db.repo.ABCAnalysisRepository;
import com.axelor.apps.base.db.repo.ProductRepository;
import com.axelor.apps.base.service.ABCAnalysisServiceImpl;
import com.axelor.apps.base.service.UnitConversionService;
import com.axelor.apps.base.service.administration.SequenceService;
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.db.JPA;
import com.axelor.db.Query;
import com.axelor.exception.AxelorException;
import com.google.inject.Inject;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
public class ABCAnalysisServiceSaleImpl extends ABCAnalysisServiceImpl {
protected SaleOrderLineRepository saleOrderLineRepository;
private static final String SELLABLE_TRUE = " AND self.sellable = TRUE";
@Inject
public ABCAnalysisServiceSaleImpl(
ABCAnalysisLineRepository abcAnalysisLineRepository,
UnitConversionService unitConversionService,
ABCAnalysisRepository abcAnalysisRepository,
ProductRepository productRepository,
SaleOrderLineRepository saleOrderLineRepository,
ABCAnalysisClassRepository abcAnalysisClassRepository,
SequenceService sequenceService) {
super(
abcAnalysisLineRepository,
unitConversionService,
abcAnalysisRepository,
productRepository,
abcAnalysisClassRepository,
sequenceService);
this.saleOrderLineRepository = saleOrderLineRepository;
}
@Override
protected Optional<ABCAnalysisLine> createABCAnalysisLine(
ABCAnalysis abcAnalysis, Product product) throws AxelorException {
ABCAnalysisLine abcAnalysisLine = null;
BigDecimal productQty = BigDecimal.ZERO;
BigDecimal productWorth = BigDecimal.ZERO;
List<SaleOrderLine> saleOrderLineList;
int offset = 0;
Query<SaleOrderLine> saleOrderLineQuery =
saleOrderLineRepository
.all()
.filter(
"(self.saleOrder.statusSelect = :statusConfirmed OR self.saleOrder.statusSelect = :statusCompleted) AND self.saleOrder.confirmationDateTime >= :startDate AND self.saleOrder.confirmationDateTime <= :endDate AND self.product.id = :productId")
.bind("statusConfirmed", SaleOrderRepository.STATUS_ORDER_CONFIRMED)
.bind("statusCompleted", SaleOrderRepository.STATUS_ORDER_COMPLETED)
.bind("startDate", toLocalDateT(toDate(abcAnalysis.getStartDate())))
.bind(
"endDate",
toLocalDateT(toDate(abcAnalysis.getEndDate()))
.withHour(23)
.withMinute(59)
.withSecond(59))
.bind("productId", product.getId())
.order("id");
while (!(saleOrderLineList = saleOrderLineQuery.fetch(FETCH_LIMIT, offset)).isEmpty()) {
offset += saleOrderLineList.size();
abcAnalysis = abcAnalysisRepository.find(abcAnalysis.getId());
if (abcAnalysisLine == null) {
abcAnalysisLine = super.createABCAnalysisLine(abcAnalysis, product).get();
}
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
BigDecimal convertedQty =
unitConversionService.convert(
saleOrderLine.getUnit(), product.getUnit(), saleOrderLine.getQty(), 5, product);
productQty = productQty.add(convertedQty);
productWorth = productWorth.add(saleOrderLine.getCompanyExTaxTotal());
}
super.incTotalQty(productQty);
super.incTotalWorth(productWorth);
JPA.clear();
}
if (abcAnalysisLine != null) {
setQtyWorth(
abcAnalysisLineRepository.find(abcAnalysisLine.getId()), productQty, productWorth);
}
return Optional.ofNullable(abcAnalysisLine);
}
@Override
protected String getProductCategoryQuery() {
return super.getProductCategoryQuery() + SELLABLE_TRUE;
}
@Override
protected String getProductFamilyQuery() {
return super.getProductFamilyQuery() + SELLABLE_TRUE;
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.sale.service;
import com.axelor.apps.base.service.AddressServiceImpl;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.db.JPA;
public class AddressServiceSaleImpl extends AddressServiceImpl {
static {
registerCheckUsedFunc(AddressServiceSaleImpl::checkAddressUsedSale);
}
private static boolean checkAddressUsedSale(Long addressId) {
return JPA.all(SaleOrder.class)
.filter("self.mainInvoicingAddress.id = ?1 OR self.deliveryAddress.id = ?1", addressId)
.fetchOne()
!= null;
}
}

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.sale.service;
import com.axelor.apps.sale.db.AdvancePayment;
import com.google.inject.persist.Transactional;
public interface AdvancePaymentService {
@Transactional
public void cancelAdvancePayment(AdvancePayment advancePayment);
}

View File

@ -0,0 +1,38 @@
/*
* 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.sale.service;
import com.axelor.apps.sale.db.AdvancePayment;
import com.axelor.apps.sale.db.repo.AdvancePaymentRepository;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
public class AdvancePaymentServiceImpl implements AdvancePaymentService {
@Inject protected AdvancePaymentRepository advancePaymentRepository;
@Transactional
public void cancelAdvancePayment(AdvancePayment advancePayment) {
advancePayment.setStatusSelect(AdvancePaymentRepository.STATUS_CANCELED);
advancePaymentRepository.save(advancePayment);
// Relancer le calcul du montant d'acompte sur le devis.
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.sale.service;
import com.axelor.apps.base.db.Partner;
import com.axelor.apps.base.db.repo.PartnerRepository;
import com.axelor.apps.base.service.PartnerServiceImpl;
import com.axelor.apps.base.service.app.AppBaseService;
import com.axelor.db.JPA;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class PartnerSaleServiceImpl extends PartnerServiceImpl {
@Inject
public PartnerSaleServiceImpl(PartnerRepository partnerRepo, AppBaseService appBaseService) {
super(partnerRepo, appBaseService);
}
@Override
public List<Long> findPartnerMails(Partner partner) {
List<Long> idList = new ArrayList<Long>();
idList.addAll(this.findMailsFromPartner(partner));
idList.addAll(this.findMailsFromSaleOrder(partner));
Set<Partner> contactSet = partner.getContactPartnerSet();
if (contactSet != null && !contactSet.isEmpty()) {
for (Partner contact : contactSet) {
idList.addAll(this.findMailsFromPartner(contact));
idList.addAll(this.findMailsFromSaleOrderContact(contact));
}
}
return idList;
}
@Override
public List<Long> findContactMails(Partner partner) {
List<Long> idList = new ArrayList<Long>();
idList.addAll(this.findMailsFromPartner(partner));
idList.addAll(this.findMailsFromSaleOrderContact(partner));
return idList;
}
@SuppressWarnings("unchecked")
public List<Long> findMailsFromSaleOrder(Partner partner) {
String query =
"SELECT DISTINCT(email.id) FROM Message as email, SaleOrder as so, Partner as part"
+ " WHERE part.id = "
+ partner.getId()
+ " AND so.clientPartner = part.id AND email.mediaTypeSelect = 2 AND "
+ "((email.relatedTo1Select = 'com.axelor.apps.sale.db.SaleOrder' AND email.relatedTo1SelectId = so.id) "
+ "OR (email.relatedTo2Select = 'com.axelor.apps.sale.db.SaleOrder' AND email.relatedTo2SelectId = so.id))";
return JPA.em().createQuery(query).getResultList();
}
@SuppressWarnings("unchecked")
public List<Long> findMailsFromSaleOrderContact(Partner partner) {
String query =
"SELECT DISTINCT(email.id) FROM Message as email, SaleOrder as so, Partner as part"
+ " WHERE part.id = "
+ partner.getId()
+ " AND so.contactPartner = part.id AND email.mediaTypeSelect = 2 AND "
+ "((email.relatedTo1Select = 'com.axelor.apps.sale.db.SaleOrder' AND email.relatedTo1SelectId = so.id) "
+ "OR (email.relatedTo2Select = 'com.axelor.apps.sale.db.SaleOrder' AND email.relatedTo2SelectId = so.id))";
return JPA.em().createQuery(query).getResultList();
}
}

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.sale.service.app;
import com.axelor.apps.base.db.AppSale;
import com.axelor.apps.base.service.app.AppBaseService;
public interface AppSaleService extends AppBaseService {
public AppSale getAppSale();
public void generateSaleConfigurations();
}

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.sale.service.app;
import com.axelor.apps.base.db.AppSale;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.repo.AppSaleRepository;
import com.axelor.apps.base.db.repo.CompanyRepository;
import com.axelor.apps.base.service.app.AppBaseServiceImpl;
import com.axelor.apps.sale.db.SaleConfig;
import com.axelor.apps.sale.db.repo.SaleConfigRepository;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.persist.Transactional;
import java.util.List;
@Singleton
public class AppSaleServiceImpl extends AppBaseServiceImpl implements AppSaleService {
@Inject private AppSaleRepository appSaleRepo;
@Inject private CompanyRepository companyRepo;
@Inject private SaleConfigRepository saleConfigRepo;
@Override
public AppSale getAppSale() {
return appSaleRepo.all().fetchOne();
}
@Override
@Transactional
public void generateSaleConfigurations() {
List<Company> companies = companyRepo.all().filter("self.saleConfig is null").fetch();
for (Company company : companies) {
SaleConfig saleConfig = new SaleConfig();
saleConfig.setCompany(company);
saleConfigRepo.save(saleConfig);
}
}
}

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.sale.service.config;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.sale.db.SaleConfig;
import com.axelor.exception.AxelorException;
public interface SaleConfigService {
public SaleConfig getSaleConfig(Company company) throws AxelorException;
}

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.sale.service.config;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.sale.db.SaleConfig;
import com.axelor.apps.sale.exception.IExceptionMessage;
import com.axelor.exception.AxelorException;
import com.axelor.exception.db.repo.TraceBackRepository;
import com.axelor.i18n.I18n;
public class SaleConfigServiceImpl implements SaleConfigService {
public SaleConfig getSaleConfig(Company company) throws AxelorException {
SaleConfig saleConfig = company.getSaleConfig();
if (saleConfig == null) {
throw new AxelorException(
company,
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
I18n.get(IExceptionMessage.SALE_CONFIG_1),
I18n.get(com.axelor.apps.base.exceptions.IExceptionMessage.EXCEPTION),
company.getName());
}
return saleConfig;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.sale.service.configurator;
import java.io.IOException;
public interface ConfiguratorCreatorImportService {
/**
* Import configurator creators from given XML config file. Use the default path for XML config
* file.
*
* @param filePath the path to the data file.
* @return the import log file.
*/
String importConfiguratorCreators(String filePath) throws IOException;
/**
* Import configurator creators from given XML config file.
*
* @param filePath the path to the data file.
* @param configFilePath the path to XML config file.
* @return the import log file.
*/
String importConfiguratorCreators(String filePath, String configFilePath) throws IOException;
}

View File

@ -0,0 +1,179 @@
/*
* 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.sale.service.configurator;
import com.axelor.apps.sale.db.ConfiguratorCreator;
import com.axelor.apps.sale.db.ConfiguratorFormula;
import com.axelor.data.Listener;
import com.axelor.data.xml.XMLImporter;
import com.axelor.db.Model;
import com.axelor.exception.AxelorException;
import com.axelor.meta.MetaFiles;
import com.axelor.meta.db.MetaJsonField;
import com.google.common.io.Files;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.xmlbeans.impl.common.IOUtil;
public class ConfiguratorCreatorImportServiceImpl implements ConfiguratorCreatorImportService {
protected ConfiguratorCreatorService configuratorCreatorService;
@Inject
public ConfiguratorCreatorImportServiceImpl(
ConfiguratorCreatorService configuratorCreatorService) {
this.configuratorCreatorService = configuratorCreatorService;
}
private static final String CONFIG_FILE_PATH =
"/data-import/import-configurator-creator-config.xml";
@Transactional(rollbackOn = {Exception.class})
@Override
public String importConfiguratorCreators(String filePath) throws IOException {
return importConfiguratorCreators(filePath, CONFIG_FILE_PATH);
}
@Transactional(rollbackOn = {Exception.class})
@Override
public String importConfiguratorCreators(String filePath, String configFilePath)
throws IOException {
InputStream inputStream = this.getClass().getResourceAsStream(configFilePath);
File configFile = File.createTempFile("config", ".xml");
FileOutputStream fout = new FileOutputStream(configFile);
IOUtil.copyCompletely(inputStream, fout);
Path path = MetaFiles.getPath(filePath);
File tempDir = Files.createTempDir();
File importFile = new File(tempDir, "configurator-creator.xml");
Files.copy(path.toFile(), importFile);
XMLImporter importer = new XMLImporter(configFile.getAbsolutePath(), tempDir.getAbsolutePath());
final StringBuilder importLog = new StringBuilder();
Listener listener =
new Listener() {
@Override
public void imported(Integer imported, Integer total) {
importLog.append("Total records: " + total + ", Total imported: " + total);
}
@Override
public void imported(Model arg0) {
try {
completeAfterImport(arg0);
} catch (AxelorException e) {
importLog.append("Error in import: " + Arrays.toString(e.getStackTrace()));
}
}
@Override
public void handle(Model arg0, Exception err) {
importLog.append("Error in import: " + Arrays.toString(err.getStackTrace()));
}
};
importer.addListener(listener);
importer.run();
FileUtils.forceDelete(configFile);
FileUtils.forceDelete(tempDir);
return importLog.toString();
}
protected void completeAfterImport(Object arg0) throws AxelorException {
if (arg0.getClass().equals(ConfiguratorCreator.class)) {
completeAfterImport((ConfiguratorCreator) arg0);
}
}
protected void completeAfterImport(ConfiguratorCreator creator) throws AxelorException {
fixAttributesName(creator);
configuratorCreatorService.updateAttributes(creator);
configuratorCreatorService.updateIndicators(creator);
}
/**
* When exported, attribute name finish with '_XX' where XX is the id of the creator. After
* importing, we need to fix these values.
*
* @param creator
* @throws AxelorException
*/
protected void fixAttributesName(ConfiguratorCreator creator) throws AxelorException {
List<MetaJsonField> attributes = creator.getAttributes();
if (attributes == null) {
return;
}
for (MetaJsonField attribute : attributes) {
String name = attribute.getName();
if (name != null && name.contains("_")) {
attribute.setName(name.substring(0, name.lastIndexOf('_')) + '_' + creator.getId());
}
updateAttributeNameInFormulas(creator, name, attribute.getName());
}
}
/**
* Update the changed attribute in all formula O2M.
*
* @param creator
* @param oldName
* @param newName
*/
protected void updateAttributeNameInFormulas(
ConfiguratorCreator creator, String oldName, String newName) throws AxelorException {
if (creator.getConfiguratorProductFormulaList() != null) {
updateAttributeNameInFormulas(creator.getConfiguratorProductFormulaList(), oldName, newName);
}
if (creator.getConfiguratorSOLineFormulaList() != null) {
updateAttributeNameInFormulas(creator.getConfiguratorSOLineFormulaList(), oldName, newName);
}
}
/**
* Update the changed attribute in formulas.
*
* @param formulas
* @param oldAttributeName
* @param newAttributeName
*/
protected void updateAttributeNameInFormulas(
List<? extends ConfiguratorFormula> formulas,
String oldAttributeName,
String newAttributeName) {
formulas
.stream()
.forEach(
configuratorFormula ->
configuratorFormula.setFormula(
configuratorFormula.getFormula().replace(oldAttributeName, newAttributeName)));
}
}

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.sale.service.configurator;
import com.axelor.apps.sale.db.ConfiguratorCreator;
import com.axelor.apps.sale.db.ConfiguratorFormula;
import com.axelor.auth.db.User;
import com.axelor.exception.AxelorException;
import com.axelor.script.ScriptBindings;
public interface ConfiguratorCreatorService {
/**
* Add default view attrs for configurator attributes
*
* @param creator
* @return
*/
void updateAttributes(ConfiguratorCreator creator);
/**
* Add the {@link ConfiguratorFormula#metaField} that need to be shown in configurator in the
* {@link ConfiguratorCreator#indicators} many-to-one.
*
* @param creator
*/
void updateIndicators(ConfiguratorCreator creator);
/**
* Get the testing values in {@link ConfiguratorCreator#attributes}
*
* @param creator
* @return
* @throws AxelorException
*/
ScriptBindings getTestingValues(ConfiguratorCreator creator) throws AxelorException;
/**
* Compute the correct domain to filter creator that are not authorized for the current user.
*
* @return the domain
*/
String getConfiguratorCreatorDomain();
/**
* Add the current user to the authorized user list
*
* @param creator
*/
void authorizeUser(ConfiguratorCreator creator, User user);
/**
* Add required fields of Product to the formula list
*
* @param creator
*/
void addRequiredFormulas(ConfiguratorCreator creator);
/**
* Activates the creator and saves it.
*
* @param creator
*/
void activate(ConfiguratorCreator creator);
}

View File

@ -0,0 +1,510 @@
/*
* 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.sale.service.configurator;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.base.service.app.AppBaseService;
import com.axelor.apps.sale.db.Configurator;
import com.axelor.apps.sale.db.ConfiguratorCreator;
import com.axelor.apps.sale.db.ConfiguratorFormula;
import com.axelor.apps.sale.db.ConfiguratorProductFormula;
import com.axelor.apps.sale.db.ConfiguratorSOLineFormula;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.apps.sale.db.repo.ConfiguratorCreatorRepository;
import com.axelor.apps.tool.StringTool;
import com.axelor.auth.AuthUtils;
import com.axelor.auth.db.Group;
import com.axelor.auth.db.User;
import com.axelor.common.Inflector;
import com.axelor.db.JPA;
import com.axelor.db.annotations.Widget;
import com.axelor.db.mapper.Mapper;
import com.axelor.db.mapper.Property;
import com.axelor.exception.service.TraceBackService;
import com.axelor.inject.Beans;
import com.axelor.meta.db.MetaField;
import com.axelor.meta.db.MetaJsonField;
import com.axelor.meta.db.MetaModel;
import com.axelor.meta.db.repo.MetaFieldRepository;
import com.axelor.meta.db.repo.MetaModelRepository;
import com.axelor.script.ScriptBindings;
import com.google.common.base.Strings;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.validation.constraints.NotNull;
public class ConfiguratorCreatorServiceImpl implements ConfiguratorCreatorService {
private ConfiguratorCreatorRepository configuratorCreatorRepo;
@Inject
public ConfiguratorCreatorServiceImpl(ConfiguratorCreatorRepository configuratorCreatorRepo) {
this.configuratorCreatorRepo = configuratorCreatorRepo;
}
@Override
@Transactional
public void updateAttributes(ConfiguratorCreator creator) {
if (creator == null) {
return;
}
for (MetaJsonField field : creator.getAttributes()) {
setContextToJsonField(creator, field);
// fill onChange if empty
if (Strings.isNullOrEmpty(field.getOnChange())) {
field.setOnChange("save,action-configurator-update-indicators,save");
}
}
configuratorCreatorRepo.save(creator);
}
@Transactional
public void updateIndicators(ConfiguratorCreator creator) {
List<MetaJsonField> indicators =
Optional.ofNullable(creator.getIndicators()).orElse(Collections.emptyList());
// add missing formulas
List<? extends ConfiguratorFormula> formulas;
if (creator.getGenerateProduct()) {
formulas = creator.getConfiguratorProductFormulaList();
} else {
formulas = creator.getConfiguratorSOLineFormulaList();
}
for (ConfiguratorFormula formula : formulas) {
addIfMissing(formula, creator);
}
// remove formulas
List<MetaJsonField> fieldsToRemove = new ArrayList<>();
for (MetaJsonField indicator : indicators) {
if (isNotInFormulas(indicator, creator, formulas)) {
fieldsToRemove.add(indicator);
}
}
for (MetaJsonField indicatorToRemove : fieldsToRemove) {
creator.removeIndicator(indicatorToRemove);
}
updateIndicatorsAttrs(creator, formulas);
configuratorCreatorRepo.save(creator);
}
@Override
public ScriptBindings getTestingValues(ConfiguratorCreator creator) {
Map<String, Object> attributesValues = new HashMap<>();
List<MetaJsonField> attributes = creator.getAttributes();
if (attributes != null) {
for (MetaJsonField attribute : attributes) {
Object defaultAttribute = getAttributesDefaultValue(attribute);
if (defaultAttribute != null) {
attributesValues.put(attribute.getName(), getAttributesDefaultValue(attribute));
}
}
}
return new ScriptBindings(attributesValues);
}
/**
* Get a default value to test a script.
*
* @param attribute
* @return
*/
protected Object getAttributesDefaultValue(MetaJsonField attribute) {
switch (attribute.getType()) {
case "string":
return "a";
case "integer":
return 1;
case "decimal":
return BigDecimal.ONE;
case "boolean":
return true;
case "datetime":
return LocalDateTime.of(LocalDate.now(), LocalTime.now());
case "date":
return LocalDate.now();
case "time":
return LocalTime.now();
case "panel":
return null;
case "enum":
return null;
case "button":
return null;
case "separator":
return null;
case "many-to-one":
return getAttributeRelationalField(attribute, "many-to-one");
case "many-to-many":
return getAttributeRelationalField(attribute, "many-to-many");
case "one-to-many":
return getAttributeRelationalField(attribute, "one-to-many");
case "json-many-to-one":
return null;
case "json-many-to-many":
return null;
case "json-one-to-many":
return null;
default:
return null;
}
}
/**
* Get a default value to test a script for a relational field.
*
* @param attribute
* @param relation
* @return
*/
protected Object getAttributeRelationalField(MetaJsonField attribute, String relation) {
try {
Class targetClass = Class.forName(attribute.getTargetModel());
if (relation.equals("many-to-one")) {
return JPA.all(targetClass).fetchOne();
} else if (relation.equals("one-to-many")) {
return JPA.all(targetClass).fetch(1);
} else if (relation.equals("many-to-many")) {
return new HashSet(JPA.all(targetClass).fetch(1));
} else {
return null;
}
} catch (Exception e) {
TraceBackService.trace(e);
return null;
}
}
/**
* Add the {@link ConfiguratorFormula} in {@link ConfiguratorCreator#indicators} if the formula is
* not represented by an existing indicator.
*
* @param formula
* @param creator
*/
protected void addIfMissing(ConfiguratorFormula formula, ConfiguratorCreator creator) {
MetaField formulaMetaField = formula.getMetaField();
List<MetaJsonField> fields =
Optional.ofNullable(creator.getIndicators()).orElse(Collections.emptyList());
for (MetaJsonField field : fields) {
if (field.getName().equals(formulaMetaField.getName() + "_" + creator.getId())) {
return;
}
}
String metaModelName = formulaMetaField.getMetaModel().getName();
MetaJsonField newField = new MetaJsonField();
newField.setModel(Configurator.class.getName());
newField.setModelField("indicators");
MetaField metaField =
Beans.get(MetaFieldRepository.class)
.all()
.filter("self.metaModel.name = :metaModelName AND self.name = :name")
.bind("metaModelName", metaModelName)
.bind("name", formulaMetaField.getName())
.fetchOne();
String typeName;
if (!Strings.isNullOrEmpty(metaField.getRelationship())) {
typeName = metaField.getRelationship();
completeDefaultGridAndForm(metaField, newField);
} else {
typeName = metaField.getTypeName();
}
completeSelection(metaField, newField);
newField.setType(typeToJsonType(typeName));
newField.setName(formulaMetaField.getName() + "_" + creator.getId());
newField.setTitle(formulaMetaField.getLabel());
creator.addIndicator(newField);
}
/**
* @param field
* @param creator
* @return false if field is represented in the creator formula list true if field is missing in
* the creator formula list
*/
protected boolean isNotInFormulas(
MetaJsonField field,
ConfiguratorCreator creator,
List<? extends ConfiguratorFormula> formulas) {
for (ConfiguratorFormula formula : formulas) {
MetaField formulaMetaField = formula.getMetaField();
if ((formulaMetaField.getName() + "_" + creator.getId()).equals(field.getName())) {
return false;
}
}
return true;
}
/**
* Fill {@link MetaJsonField#gridView} and {@link MetaJsonField#formView} in the given meta json
* field. The default views name are using the axelor naming convention, here product
*
* @param metaField a meta field which is a relational field.
* @param newField a meta json field which is a relational field.
*/
protected void completeDefaultGridAndForm(MetaField metaField, MetaJsonField newField) {
String name = metaField.getTypeName();
if (Strings.isNullOrEmpty(name)) {
return;
}
final Inflector inflector = Inflector.getInstance();
String prefix = inflector.dasherize(name);
newField.setGridView(prefix + "-grid");
newField.setFormView(prefix + "-form");
}
/**
* Fill {@link MetaJsonField#selection} searching in java class code.
*
* @param metaField a meta field.
* @param newField a meta json field.
*/
protected void completeSelection(MetaField metaField, MetaJsonField newField) {
try {
Field correspondingField =
Class.forName(
metaField.getMetaModel().getPackageName()
+ "."
+ metaField.getMetaModel().getName())
.getDeclaredField(metaField.getName());
Widget widget = correspondingField.getAnnotation(Widget.class);
if (widget == null) {
return;
}
String selection = widget.selection();
if (!Strings.isNullOrEmpty(selection)) {
newField.setSelection(selection);
}
} catch (ClassNotFoundException | NoSuchFieldException e) {
TraceBackService.trace(e);
}
}
/**
* Convert the type of a field to a type of a json field.
*
* @param nameType type of a field
* @return corresponding type of json field
*/
protected String typeToJsonType(String nameType) {
if (nameType.equals("BigDecimal")) {
return "decimal";
} else if (nameType.equals("ManyToOne")) {
return "many-to-one";
} else if (nameType.equals("OneToMany")) {
return "one-to-many";
} else if (nameType.equals("OneToOne")) {
return "one-to-one";
} else if (nameType.equals("ManyToMany")) {
return "many-to-many";
} else {
return nameType.toLowerCase();
}
}
/**
* Update the indicators views attrs using the formulas.
*
* @param creator
*/
protected void updateIndicatorsAttrs(
ConfiguratorCreator creator, List<? extends ConfiguratorFormula> formulas) {
List<MetaJsonField> indicators = creator.getIndicators();
for (MetaJsonField indicator : indicators) {
for (ConfiguratorFormula formula : formulas) {
updateIndicatorAttrs(creator, indicator, formula);
}
}
}
/**
* Update one indicator attrs in the view, using the corresponding formula. Do nothing if
* indicator and formula do not represent the same field.
*
* @param indicator
* @param formula
*/
protected void updateIndicatorAttrs(
ConfiguratorCreator creator, MetaJsonField indicator, ConfiguratorFormula formula) {
int scale = Beans.get(AppBaseService.class).getNbDecimalDigitForUnitPrice();
String fieldName = indicator.getName();
fieldName = fieldName.substring(0, fieldName.indexOf('_'));
MetaField metaField = formula.getMetaField();
if (!metaField.getName().equals(fieldName)) {
return;
}
if (formula.getShowOnConfigurator()) {
indicator.setHidden(false);
setContextToJsonField(creator, indicator);
} else {
indicator.setHidden(true);
}
if (metaField.getTypeName().equals("BigDecimal")) {
indicator.setPrecision(20);
indicator.setScale(scale);
} else if (!Strings.isNullOrEmpty(metaField.getRelationship())) {
indicator.setTargetModel(
Beans.get(MetaModelRepository.class).findByName(metaField.getTypeName()).getFullName());
}
}
public String getConfiguratorCreatorDomain() {
User user = AuthUtils.getUser();
Group group = user.getGroup();
List<ConfiguratorCreator> configuratorCreatorList =
configuratorCreatorRepo.all().filter("self.isActive = true").fetch();
if (configuratorCreatorList == null || configuratorCreatorList.isEmpty()) {
return "self.id in (0)";
}
configuratorCreatorList.removeIf(
creator ->
!creator.getAuthorizedUserSet().contains(user)
&& !creator.getAuthorizedGroupSet().contains(group));
return "self.id in (" + StringTool.getIdListString(configuratorCreatorList) + ")";
}
@Override
@Transactional
public void authorizeUser(ConfiguratorCreator creator, User user) {
creator.addAuthorizedUserSetItem(user);
}
@Override
@Transactional
public void addRequiredFormulas(ConfiguratorCreator creator) {
for (Field field : Product.class.getDeclaredFields()) {
if (field.getAnnotation(NotNull.class) != null) {
creator.addConfiguratorProductFormulaListItem(createProductFormula(field.getName()));
}
}
for (Field field : SaleOrderLine.class.getDeclaredFields()) {
if (field.getAnnotation(NotNull.class) != null) {
creator.addConfiguratorSOLineFormulaListItem(createSOLineFormula(field.getName()));
}
}
configuratorCreatorRepo.save(creator);
}
/**
* Create a configurator product formula with an empty formula for the given MetaField.
*
* @param name the name of the meta field.
* @return the created configurator formula.
*/
protected ConfiguratorProductFormula createProductFormula(String name) {
ConfiguratorProductFormula configuratorProductFormula = new ConfiguratorProductFormula();
completeFormula(configuratorProductFormula, name, "Product");
return configuratorProductFormula;
}
/**
* Create a configurator product formula with an empty formula for the given MetaField.
*
* @param name the meta field name.
* @return the created configurator formula.
*/
protected ConfiguratorSOLineFormula createSOLineFormula(String name) {
ConfiguratorSOLineFormula configuratorSOLineFormula = new ConfiguratorSOLineFormula();
completeFormula(configuratorSOLineFormula, name, "SaleOrderLine");
return configuratorSOLineFormula;
}
/**
* Complete the given configurator formula with correct metafields.
*
* @param configuratorFormula a configurator formula.
* @param name the meta field name.
* @param metaFieldType the name of the model owning the meta field.
*/
protected void completeFormula(
ConfiguratorFormula configuratorFormula, String name, String metaFieldType) {
configuratorFormula.setShowOnConfigurator(true);
configuratorFormula.setFormula("");
Long modelId =
JPA.all(MetaModel.class).filter("self.name = ?", metaFieldType).fetchOne().getId();
MetaField metaField =
JPA.all(MetaField.class)
.filter("self.name = ? AND self.metaModel.id = ?", name, modelId)
.fetchOne();
configuratorFormula.setMetaField(metaField);
}
@Override
@Transactional
public void activate(ConfiguratorCreator creator) {
creator.setIsActive(true);
}
/**
* Set the context field to a json field. Allows to limit the json field to the configurator
* having the right configurator creator.
*
* @param creator
* @param field
*/
protected void setContextToJsonField(ConfiguratorCreator creator, MetaJsonField field) {
final String fieldName = "configuratorCreator";
final Class<?> modelClass;
final String modelName = field.getModel();
try {
modelClass = Class.forName(modelName);
} catch (ClassNotFoundException e) {
// this should not happen
TraceBackService.trace(e);
return;
}
final Mapper mapper = Mapper.of(modelClass);
final Property property = mapper.getProperty(fieldName);
final String target = property == null ? null : property.getTarget().getName();
final String targetName = property == null ? null : property.getTargetName();
field.setContextField(fieldName);
field.setContextFieldTarget(target);
field.setContextFieldTargetName(targetName);
field.setContextFieldValue(creator.getId().toString());
field.setContextFieldTitle(creator.getName());
}
}

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.sale.service.configurator;
import com.axelor.apps.sale.db.ConfiguratorCreator;
import com.axelor.apps.sale.db.ConfiguratorFormula;
import com.axelor.exception.AxelorException;
public interface ConfiguratorFormulaService {
/**
* Check if the written formula is valid.
*
* @param formula
* @param creator
*/
void checkFormula(ConfiguratorFormula formula, ConfiguratorCreator creator)
throws AxelorException;
/**
* Get the name of the given object. Use EntityHelper to get the right class name for proxy
* classes.
*
* @param calculatedValue a result from groovy script.
* @return the name of the class.
*/
String getCalculatedClassName(Object calculatedValue);
}

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.sale.service.configurator;
import com.axelor.apps.sale.db.ConfiguratorCreator;
import com.axelor.apps.sale.db.ConfiguratorFormula;
import com.axelor.apps.sale.exception.IExceptionMessage;
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.axelor.script.GroovyScriptHelper;
import com.axelor.script.ScriptBindings;
public class ConfiguratorFormulaServiceImpl implements ConfiguratorFormulaService {
@Override
public void checkFormula(ConfiguratorFormula formula, ConfiguratorCreator creator)
throws AxelorException {
ScriptBindings defaultValueBindings =
Beans.get(ConfiguratorCreatorService.class).getTestingValues(creator);
Object result = new GroovyScriptHelper(defaultValueBindings).eval(formula.getFormula());
String wantedTypeName = formula.getMetaField().getTypeName();
if (result == null) {
throw new AxelorException(
formula,
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
I18n.get(IExceptionMessage.CONFIGURATOR_CREATOR_SCRIPT_ERROR));
} else {
if (!Beans.get(ConfiguratorService.class)
.areCompatible(wantedTypeName, getCalculatedClassName(result))) {
throw new AxelorException(
formula,
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
I18n.get(IExceptionMessage.CONFIGURATOR_CREATOR_FORMULA_TYPE_ERROR),
result.getClass().getSimpleName(),
wantedTypeName);
}
}
}
@Override
public String getCalculatedClassName(Object calculatedValue) {
if (calculatedValue instanceof Model) {
return EntityHelper.getEntityClass(calculatedValue).getSimpleName();
} else {
return calculatedValue.getClass().getSimpleName();
}
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.sale.service.configurator;
import com.axelor.apps.sale.db.Configurator;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.exception.AxelorException;
import com.axelor.meta.db.MetaJsonField;
import com.axelor.rpc.JsonContext;
import java.lang.reflect.InvocationTargetException;
import wslite.json.JSONException;
public interface ConfiguratorService {
/**
* Update the value of indicators using {@link
* com.axelor.apps.sale.db.ConfiguratorCreator#configuratorFormulaList} and the current values in
* {@link Configurator#attributes}
*
* @param configurator
* @param attributes
* @param indicators @return the new values of indicators
*/
void updateIndicators(Configurator configurator, JsonContext attributes, JsonContext indicators)
throws AxelorException;
/**
* Give the result of a formula, with the script variables defined in the values map.
*
* @param groovyFormula
* @param values
* @return
* @throws AxelorException
*/
Object computeFormula(String groovyFormula, JsonContext values) throws AxelorException;
/**
* Generate the product, and the bill of material if we are in the right module
*
* @param configurator
* @param jsonAttributes
* @param jsonIndicators
*/
void generate(Configurator configurator, JsonContext jsonAttributes, JsonContext jsonIndicators)
throws AxelorException, NoSuchMethodException;
/**
* Generate a product from the configurator
*
* @param configurator
* @param jsonAttributes
* @param jsonIndicators
*/
void generateProduct(
Configurator configurator, JsonContext jsonAttributes, JsonContext jsonIndicators)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException,
JSONException, ClassNotFoundException, AxelorException;
/**
* Generate a product, then generate a sale order line with the created product, then add this
* line to the sale order.
*
* @param configurator
* @param saleOrder
* @param jsonAttributes
* @param jsonIndicators
*/
void addLineToSaleOrder(
Configurator configurator,
SaleOrder saleOrder,
JsonContext jsonAttributes,
JsonContext jsonIndicators)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, AxelorException;
/**
* Check if the calculated value type is the same as the indicator type.
*
* @param calculatedValue the return value of a script.
* @param indicator an indicator.
* @throws AxelorException if the type don't match.
*/
void checkType(Object calculatedValue, MetaJsonField indicator) throws AxelorException;
/**
* Return true if {@code fromClassName} is a class that can be converted to {@code
* targetClassName}. <br>
* Else return false.
*/
boolean areCompatible(String targetClassName, String fromClassName);
}

View File

@ -0,0 +1,385 @@
/*
* 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.sale.service.configurator;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.base.db.repo.ProductRepository;
import com.axelor.apps.sale.db.Configurator;
import com.axelor.apps.sale.db.ConfiguratorCreator;
import com.axelor.apps.sale.db.ConfiguratorFormula;
import com.axelor.apps.sale.db.ConfiguratorSOLineFormula;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.apps.sale.db.repo.ConfiguratorRepository;
import com.axelor.apps.sale.db.repo.SaleOrderLineRepository;
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
import com.axelor.apps.sale.exception.IExceptionMessage;
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeService;
import com.axelor.apps.sale.service.saleorder.SaleOrderLineService;
import com.axelor.db.JPA;
import com.axelor.db.Model;
import com.axelor.db.mapper.Mapper;
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.MetaField;
import com.axelor.meta.db.MetaJsonField;
import com.axelor.meta.db.MetaSelectItem;
import com.axelor.meta.db.repo.MetaFieldRepository;
import com.axelor.meta.db.repo.MetaSelectItemRepository;
import com.axelor.rpc.JsonContext;
import com.axelor.script.GroovyScriptHelper;
import com.axelor.script.ScriptHelper;
import com.google.inject.persist.Transactional;
import groovy.lang.MissingPropertyException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ConfiguratorServiceImpl implements ConfiguratorService {
@Override
public void updateIndicators(
Configurator configurator, JsonContext jsonAttributes, JsonContext jsonIndicators)
throws AxelorException {
if (configurator.getConfiguratorCreator() == null) {
return;
}
List<MetaJsonField> indicators = configurator.getConfiguratorCreator().getIndicators();
for (MetaJsonField indicator : indicators) {
try {
Object calculatedValue =
computeIndicatorValue(configurator, indicator.getName(), jsonAttributes);
checkType(calculatedValue, indicator);
jsonIndicators.put(indicator.getName(), calculatedValue);
} catch (MissingPropertyException e) {
// if a field is missing, the value needs to be set to null
continue;
}
}
}
@Override
public void checkType(Object calculatedValue, MetaJsonField indicator) throws AxelorException {
if (calculatedValue == null) {
return;
}
String wantedClassName;
String wantedType = jsonTypeToType(indicator.getType());
String calculatedValueClassName =
Beans.get(ConfiguratorFormulaService.class).getCalculatedClassName(calculatedValue);
if (wantedType.equals("ManyToOne")
|| wantedType.equals("ManyToMany")
|| wantedType.equals("OneToMany")
|| wantedType.equals("Custom-ManyToOne")
|| wantedType.equals("Custom-ManyToMany")
|| wantedType.equals("Custom-OneToMany")) {
// it is a relational field so we get the target model class
String targetName = indicator.getTargetModel();
// get only the class without the package
wantedClassName = targetName.substring(targetName.lastIndexOf('.') + 1);
} else {
wantedClassName = wantedType;
}
if (!areCompatible(wantedClassName, calculatedValueClassName)) {
throw new AxelorException(
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
I18n.get(IExceptionMessage.CONFIGURATOR_ON_GENERATING_TYPE_ERROR),
indicator.getName().substring(0, indicator.getName().indexOf('_')),
wantedClassName,
calculatedValueClassName);
}
}
/**
* Convert the type of a json field to a type of a field.
*
* @param nameType type of a json field
* @return corresponding type of field
*/
protected String jsonTypeToType(String nameType) {
MetaSelectItem item =
Beans.get(MetaSelectItemRepository.class)
.all()
.filter("self.select.name = :_jsonFieldType AND self.value = :_value")
.bind("_jsonFieldType", "json.field.type")
.bind("_value", nameType)
.fetchOne();
if (item == null) {
return "";
} else {
return item.getTitle().equals("Decimal") ? "BigDecimal" : item.getTitle();
}
}
/**
* Here we only generate a product.
*
* @param configurator
* @param jsonAttributes
* @param jsonIndicators
*/
@Override
@Transactional(rollbackOn = {Exception.class})
public void generate(
Configurator configurator, JsonContext jsonAttributes, JsonContext jsonIndicators)
throws AxelorException {
generateProduct(configurator, jsonAttributes, jsonIndicators);
}
@Override
@Transactional(rollbackOn = {Exception.class})
public void generateProduct(
Configurator configurator, JsonContext jsonAttributes, JsonContext jsonIndicators)
throws AxelorException {
cleanIndicators(jsonIndicators);
Mapper mapper = Mapper.of(Product.class);
Product product = new Product();
for (String key : jsonIndicators.keySet()) {
mapper.set(product, key, jsonIndicators.get(key));
}
fixRelationalFields(product);
if (product.getProductTypeSelect() == null) {
product.setProductTypeSelect(ProductRepository.PRODUCT_TYPE_STORABLE);
}
if (product.getCode() == null) {
throw new AxelorException(
configurator,
TraceBackRepository.CATEGORY_MISSING_FIELD,
I18n.get(IExceptionMessage.CONFIGURATOR_PRODUCT_MISSING_CODE));
}
if (product.getName() == null) {
throw new AxelorException(
configurator,
TraceBackRepository.CATEGORY_MISSING_FIELD,
I18n.get(IExceptionMessage.CONFIGURATOR_PRODUCT_MISSING_NAME));
}
configurator.setProduct(product);
product.setConfigurator(configurator);
Beans.get(ProductRepository.class).save(product);
}
@Transactional(rollbackOn = {Exception.class})
@Override
public void addLineToSaleOrder(
Configurator configurator,
SaleOrder saleOrder,
JsonContext jsonAttributes,
JsonContext jsonIndicators)
throws AxelorException {
SaleOrderLine saleOrderLine;
if (configurator.getConfiguratorCreator().getGenerateProduct()) {
// generate sale order line from product
saleOrderLine = new SaleOrderLine();
saleOrderLine.setSaleOrder(saleOrder);
generateProduct(configurator, jsonAttributes, jsonIndicators);
saleOrderLine.setProduct(configurator.getProduct());
this.fillSaleOrderWithProduct(saleOrderLine);
Beans.get(SaleOrderLineService.class)
.computeValues(saleOrderLine.getSaleOrder(), saleOrderLine);
} else {
saleOrderLine =
generateSaleOrderLine(configurator, jsonAttributes, jsonIndicators, saleOrder);
}
Beans.get(SaleOrderComputeService.class).computeSaleOrder(saleOrder);
Beans.get(SaleOrderRepository.class).save(saleOrder);
}
/**
* Fill fields of sale order line from its product
*
* @param saleOrderLine
*/
protected void fillSaleOrderWithProduct(SaleOrderLine saleOrderLine) throws AxelorException {
SaleOrderLineService saleOrderLineService = Beans.get(SaleOrderLineService.class);
if (saleOrderLine.getProduct() != null) {
saleOrderLineService.computeProductInformation(
saleOrderLine, saleOrderLine.getSaleOrder(), null);
}
}
protected void overwriteFieldToUpdate(
Configurator configurator, SaleOrderLine saleOrderLine, JsonContext attributes)
throws AxelorException {
// update a field if its formula has updateFromSelect to update
// from configurator
List<ConfiguratorSOLineFormula> formulas =
configurator.getConfiguratorCreator().getConfiguratorSOLineFormulaList();
if (formulas != null) {
Mapper mapper = Mapper.of(SaleOrderLine.class);
for (ConfiguratorSOLineFormula formula : formulas) {
// exclude the product field
if (formula.getUpdateFromSelect() == ConfiguratorRepository.UPDATE_FROM_CONFIGURATOR) {
// we add "_1" because computeIndicatorValue expect an indicator name.
Object valueToUpdate =
computeIndicatorValue(
configurator, formula.getMetaField().getName() + "_1", attributes);
// if many to one, go search value in database.
if ("ManyToOne".equals(formula.getMetaField().getRelationship())) {
fixRelationalField(saleOrderLine, (Model) valueToUpdate, formula.getMetaField());
} else {
mapper.set(saleOrderLine, formula.getMetaField().getName(), valueToUpdate);
}
}
}
}
}
/**
* Compute the value of one indicator. Using the corresponding formula and the values in {@link
* Configurator#attributes}
*
* @param configurator
* @param indicatorName
* @param jsonAttributes
* @return
*/
protected Object computeIndicatorValue(
Configurator configurator, String indicatorName, JsonContext jsonAttributes) {
ConfiguratorCreator creator = configurator.getConfiguratorCreator();
List<? extends ConfiguratorFormula> formulas;
if (creator.getGenerateProduct()) {
formulas = creator.getConfiguratorProductFormulaList();
} else {
formulas = creator.getConfiguratorSOLineFormulaList();
}
String groovyFormula = null;
for (ConfiguratorFormula formula : formulas) {
String fieldName = indicatorName;
fieldName = fieldName.substring(0, fieldName.indexOf('_'));
MetaField metaField = formula.getMetaField();
if (metaField.getName().equals(fieldName)) {
groovyFormula = formula.getFormula();
break;
}
}
if (groovyFormula == null || jsonAttributes == null) {
return null;
}
return computeFormula(groovyFormula, jsonAttributes);
}
@Override
public Object computeFormula(String groovyFormula, JsonContext values) {
ScriptHelper scriptHelper = new GroovyScriptHelper(values);
return scriptHelper.eval(groovyFormula);
}
public boolean areCompatible(String targetClassName, String fromClassName) {
return targetClassName.equals(fromClassName)
|| (targetClassName.equals("BigDecimal") && fromClassName.equals("Integer"))
|| (targetClassName.equals("BigDecimal") && fromClassName.equals("String"));
}
/**
* Create a sale order line from the configurator
*
* @param configurator
* @param jsonAttributes
* @param jsonIndicators
* @param saleOrder
* @return
*/
protected SaleOrderLine generateSaleOrderLine(
Configurator configurator,
JsonContext jsonAttributes,
JsonContext jsonIndicators,
SaleOrder saleOrder)
throws AxelorException {
cleanIndicators(jsonIndicators);
SaleOrderLine saleOrderLine = Mapper.toBean(SaleOrderLine.class, jsonIndicators);
saleOrderLine.setSaleOrder(saleOrder);
fixRelationalFields(saleOrderLine);
this.fillSaleOrderWithProduct(saleOrderLine);
this.overwriteFieldToUpdate(configurator, saleOrderLine, jsonAttributes);
if (saleOrderLine.getProductName() == null) {
throw new AxelorException(
configurator,
TraceBackRepository.CATEGORY_MISSING_FIELD,
I18n.get(IExceptionMessage.CONFIGURATOR_SALE_ORDER_LINE_MISSING_PRODUCT_NAME));
}
saleOrderLine = Beans.get(SaleOrderLineRepository.class).save(saleOrderLine);
Beans.get(SaleOrderLineService.class)
.computeValues(saleOrderLine.getSaleOrder(), saleOrderLine);
return saleOrderLine;
}
/**
* Indicator keys have this pattern : {field name}_{id} Transform the keys to have only the {field
* name}.
*
* @param jsonIndicators
*/
protected void cleanIndicators(JsonContext jsonIndicators) {
Map<String, Object> newKeyMap = new HashMap<>();
for (Map.Entry entry : jsonIndicators.entrySet()) {
String oldKey = entry.getKey().toString();
newKeyMap.put(oldKey.substring(0, oldKey.indexOf('_')), entry.getValue());
}
jsonIndicators.clear();
jsonIndicators.putAll(newKeyMap);
}
/**
* Fix relational fields of a product or a sale order line generated from a configurator. This
* method may become useless on a future ADK update.
*
* @param model
*/
protected void fixRelationalFields(Model model) throws AxelorException {
// get all many to one fields
List<MetaField> manyToOneFields =
Beans.get(MetaFieldRepository.class)
.all()
.filter("self.metaModel.name = :name " + "AND self.relationship = 'ManyToOne'")
.bind("name", model.getClass().getSimpleName())
.fetch();
Mapper mapper = Mapper.of(model.getClass());
for (MetaField manyToOneField : manyToOneFields) {
Model manyToOneValue = (Model) mapper.get(model, manyToOneField.getName());
fixRelationalField(model, manyToOneValue, manyToOneField);
}
}
protected void fixRelationalField(Model parentModel, Model value, MetaField metaField)
throws AxelorException {
if (value != null) {
Mapper mapper = Mapper.of(parentModel.getClass());
try {
String className =
String.format("%s.%s", metaField.getPackageName(), metaField.getTypeName());
Model manyToOneDbValue = JPA.find((Class<Model>) Class.forName(className), value.getId());
mapper.set(parentModel, metaField.getName(), manyToOneDbValue);
} catch (Exception e) {
throw new AxelorException(
Configurator.class, TraceBackRepository.CATEGORY_CONFIGURATION_ERROR, e.getMessage());
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.crm.db.Opportunity;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.exception.AxelorException;
import com.google.inject.persist.Transactional;
public interface OpportunitySaleOrderService {
@Transactional(rollbackOn = {Exception.class})
public SaleOrder createSaleOrderFromOpportunity(Opportunity opportunity) throws AxelorException;
}

View File

@ -0,0 +1,84 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.base.db.Currency;
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.crm.db.Opportunity;
import com.axelor.apps.crm.db.repo.OpportunityRepository;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
import com.axelor.exception.AxelorException;
import com.axelor.inject.Beans;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
public class OpportunitySaleOrderServiceImpl implements OpportunitySaleOrderService {
@Inject protected SaleOrderCreateService saleOrderCreateService;
@Inject protected SaleOrderRepository saleOrderRepo;
@Inject protected AppBaseService appBaseService;
@Override
@Transactional(rollbackOn = {Exception.class})
public SaleOrder createSaleOrderFromOpportunity(Opportunity opportunity) throws AxelorException {
Currency currency;
if (opportunity.getCurrency() != null) {
currency = opportunity.getCurrency();
} else if (opportunity.getPartner() != null && opportunity.getPartner().getCurrency() != null) {
currency = opportunity.getPartner().getCurrency();
} else {
currency = opportunity.getCompany().getCurrency();
}
SaleOrder saleOrder = createSaleOrder(opportunity, currency);
opportunity.addSaleOrderListItem(saleOrder);
if (opportunity.getSalesStageSelect() < OpportunityRepository.SALES_STAGE_PROPOSITION) {
opportunity.setSalesStageSelect(OpportunityRepository.SALES_STAGE_PROPOSITION);
}
saleOrder.setTradingName(opportunity.getTradingName());
saleOrderRepo.save(saleOrder);
return saleOrder;
}
protected SaleOrder createSaleOrder(Opportunity opportunity, Currency currency)
throws AxelorException {
return saleOrderCreateService.createSaleOrder(
opportunity.getUser(),
opportunity.getCompany(),
null,
currency,
null,
opportunity.getName(),
null,
appBaseService.getTodayDate(),
Beans.get(PartnerPriceListService.class)
.getDefaultPriceList(opportunity.getPartner(), PriceListRepository.TYPE_SALE),
opportunity.getPartner(),
opportunity.getTeam());
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.exception.AxelorException;
import java.math.BigDecimal;
import java.util.List;
public interface SaleOrderComputeService {
public SaleOrder _computeSaleOrderLineList(SaleOrder saleOrder) throws AxelorException;
public SaleOrder computeSaleOrder(SaleOrder saleOrder) throws AxelorException;
/**
* Peupler un devis.
*
* <p>Cette fonction permet de déterminer les tva d'un devis.
*
* @param saleOrder
* @throws AxelorException
*/
public void _populateSaleOrder(SaleOrder saleOrder) throws AxelorException;
/**
* Calculer le montant d'une facture.
*
* <p>Le calcul est basé sur les lignes de TVA préalablement créées.
*
* @param invoice
* @param vatLines
* @throws AxelorException
*/
public void _computeSaleOrder(SaleOrder saleOrder) throws AxelorException;
/**
* Permet de réinitialiser la liste des lignes de TVA
*
* @param saleOrder Un devis
*/
public void initSaleOrderLineTaxList(SaleOrder saleOrder);
/**
* Return the total price, computed from the lines. This price is usually equals to {@link
* SaleOrder#exTaxTotal} but not in all cases.
*
* @param saleOrder
* @return total price from the sale order lines
*/
public BigDecimal getTotalSaleOrderPrice(SaleOrder saleOrder);
public List<SaleOrderLine> removeSubLines(List<SaleOrderLine> lines);
}

View File

@ -0,0 +1,245 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.base.db.AppSale;
import com.axelor.apps.sale.db.AdvancePayment;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.apps.sale.db.SaleOrderLineTax;
import com.axelor.apps.sale.service.app.AppSaleService;
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.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SaleOrderComputeServiceImpl implements SaleOrderComputeService {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected SaleOrderLineService saleOrderLineService;
protected SaleOrderLineTaxService saleOrderLineTaxService;
@Inject
public SaleOrderComputeServiceImpl(
SaleOrderLineService saleOrderLineService, SaleOrderLineTaxService saleOrderLineTaxService) {
this.saleOrderLineService = saleOrderLineService;
this.saleOrderLineTaxService = saleOrderLineTaxService;
}
@Override
public SaleOrder _computeSaleOrderLineList(SaleOrder saleOrder) throws AxelorException {
if (saleOrder.getSaleOrderLineList() != null) {
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
saleOrderLine.setCompanyExTaxTotal(
saleOrderLineService.getAmountInCompanyCurrency(
saleOrderLine.getExTaxTotal(), saleOrder));
}
}
return saleOrder;
}
@Override
public SaleOrder computeSaleOrder(SaleOrder saleOrder) throws AxelorException {
AppSale appSale = Beans.get(AppSaleService.class).getAppSale();
if (appSale != null && appSale.getActive() && appSale.getProductPackMgt()) {
this._addPackLines(saleOrder);
}
this.initSaleOrderLineTaxList(saleOrder);
this._computeSaleOrderLineList(saleOrder);
this._populateSaleOrder(saleOrder);
this._computeSaleOrder(saleOrder);
return saleOrder;
}
/**
* Peupler un devis.
*
* <p>Cette fonction permet de déterminer les tva d'un devis.
*
* @param saleOrder
* @throws AxelorException
*/
@Override
public void _populateSaleOrder(SaleOrder saleOrder) throws AxelorException {
if (saleOrder.getSaleOrderLineList() == null) {
saleOrder.setSaleOrderLineList(new ArrayList<>());
}
if (saleOrder.getSaleOrderLineTaxList() == null) {
saleOrder.setSaleOrderLineTaxList(new ArrayList<>());
}
logger.debug(
"Peupler un devis => lignes de devis: {} ",
new Object[] {saleOrder.getSaleOrderLineList().size()});
// create Tva lines
if (saleOrder.getClientPartner() != null) {
saleOrder
.getSaleOrderLineTaxList()
.addAll(
saleOrderLineTaxService.createsSaleOrderLineTax(
saleOrder, saleOrder.getSaleOrderLineList()));
}
}
/**
* Compute the sale order total amounts
*
* @param saleOrder
* @throws AxelorException
*/
@Override
public void _computeSaleOrder(SaleOrder saleOrder) throws AxelorException {
saleOrder.setExTaxTotal(BigDecimal.ZERO);
saleOrder.setCompanyExTaxTotal(BigDecimal.ZERO);
saleOrder.setTaxTotal(BigDecimal.ZERO);
saleOrder.setInTaxTotal(BigDecimal.ZERO);
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
saleOrder.setExTaxTotal(saleOrder.getExTaxTotal().add(saleOrderLine.getExTaxTotal()));
// In the company accounting currency
saleOrder.setCompanyExTaxTotal(
saleOrder.getCompanyExTaxTotal().add(saleOrderLine.getCompanyExTaxTotal()));
}
for (SaleOrderLineTax saleOrderLineVat : saleOrder.getSaleOrderLineTaxList()) {
// In the sale order currency
saleOrder.setTaxTotal(saleOrder.getTaxTotal().add(saleOrderLineVat.getTaxTotal()));
}
saleOrder.setInTaxTotal(saleOrder.getExTaxTotal().add(saleOrder.getTaxTotal()));
saleOrder.setAdvanceTotal(computeTotalAdvancePayment(saleOrder));
logger.debug(
"Montant de la facture: HTT = {}, HT = {}, Taxe = {}, TTC = {}",
new Object[] {
saleOrder.getExTaxTotal(), saleOrder.getTaxTotal(), saleOrder.getInTaxTotal()
});
}
protected BigDecimal computeTotalAdvancePayment(SaleOrder saleOrder) {
List<AdvancePayment> advancePaymentList = saleOrder.getAdvancePaymentList();
BigDecimal total = BigDecimal.ZERO;
if (advancePaymentList == null || advancePaymentList.isEmpty()) {
return total;
}
for (AdvancePayment advancePayment : advancePaymentList) {
total = total.add(advancePayment.getAmount());
}
return total;
}
private void _addPackLines(SaleOrder saleOrder) {
if (saleOrder.getSaleOrderLineList() == null) {
return;
}
List<SaleOrderLine> saleOrderLines = saleOrder.getSaleOrderLineList();
List<SaleOrderLine> lines = new ArrayList<SaleOrderLine>();
lines.addAll(saleOrderLines);
for (SaleOrderLine line : lines) {
if (line.getSubLineList() == null || line.getSubLineList().isEmpty()) {
continue;
}
for (SaleOrderLine subLine : line.getSubLineList()) {
if (subLine.getSaleOrder() == null) {
saleOrderLines.add(subLine);
}
}
}
}
/**
* Permet de réinitialiser la liste des lignes de TVA
*
* @param saleOrder Un devis
*/
@Override
public void initSaleOrderLineTaxList(SaleOrder saleOrder) {
if (saleOrder.getSaleOrderLineTaxList() == null) {
saleOrder.setSaleOrderLineTaxList(new ArrayList<SaleOrderLineTax>());
} else {
saleOrder.getSaleOrderLineTaxList().clear();
}
}
@Override
public BigDecimal getTotalSaleOrderPrice(SaleOrder saleOrder) {
BigDecimal price = BigDecimal.ZERO;
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
price = price.add(saleOrderLine.getQty().multiply(saleOrderLine.getPriceDiscounted()));
}
return price;
}
@Override
public List<SaleOrderLine> removeSubLines(List<SaleOrderLine> soLines) {
if (soLines == null) {
return soLines;
}
List<SaleOrderLine> subLines = new ArrayList<SaleOrderLine>();
for (SaleOrderLine packLine : soLines) {
if (packLine.getTypeSelect() == 2 && packLine.getSubLineList() != null) {
packLine.getSubLineList().removeIf(it -> it.getId() != null && !soLines.contains(it));
packLine.setTotalPack(
packLine
.getSubLineList()
.stream()
.map(it -> it.getExTaxTotal())
.reduce(BigDecimal.ZERO, BigDecimal::add));
subLines.addAll(packLine.getSubLineList());
}
}
Iterator<SaleOrderLine> lines = soLines.iterator();
while (lines.hasNext()) {
SaleOrderLine subLine = lines.next();
if (subLine.getId() != null
&& subLine.getParentLine() != null
&& !subLines.contains(subLine)) {
lines.remove();
}
}
return soLines;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.sale.service.saleorder;
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.sale.db.SaleOrder;
import com.axelor.auth.db.User;
import com.axelor.exception.AxelorException;
import com.axelor.team.db.Team;
import com.google.inject.persist.Transactional;
import java.time.LocalDate;
import java.util.List;
public interface SaleOrderCreateService {
/**
* Initialize a new sale order
*
* @param salemanUser User recorded as salesman on order, if <code>null</code>, will be set to
* current user.
* @param company Company bound to the order, if <code>null</code>, will be bound to salesman
* active company.
* @param contactPartner Customer contact to assign to the user, might be <code>null</code>.
* @param currency Order's currency, should not be <code>null</code>.
* @param deliveryDate Expected delivery date for order (might be <code>null</code>).
* @param internalReference Unused (…)
* @param externalReference Client reference for order, if any
* @param orderDate Date of order (if <code>null</code>, will be set to today's date).
* @param priceList Pricelist to use, if <code>null</code>, will default to partner's default
* price list.
* @param clientPartner Customer bound to the order, should not be <code>null</code>
* @param team Team managing the order, if <code>null</code>, will default to salesman active
* team.
* @return The created order
* @throws AxelorException
*/
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;
public SaleOrder createSaleOrder(Company company) throws AxelorException;
public SaleOrder mergeSaleOrders(
List<SaleOrder> saleOrderList,
Currency currency,
Partner clientPartner,
Company company,
Partner contactPartner,
PriceList priceList,
Team team)
throws AxelorException;
@Transactional
public SaleOrder createTemplate(SaleOrder context);
@Transactional(rollbackOn = {Exception.class})
public SaleOrder createSaleOrder(
SaleOrder context, Currency wizardCurrency, PriceList wizardPriceList) throws AxelorException;
public void updateSaleOrderLineList(SaleOrder saleOrder) throws AxelorException;
}

View File

@ -0,0 +1,266 @@
/*
* 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.sale.service.saleorder;
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.repo.PriceListRepository;
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.sale.db.SaleOrder;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
import com.axelor.apps.sale.service.app.AppSaleService;
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.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SaleOrderCreateServiceImpl implements SaleOrderCreateService {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected PartnerService partnerService;
protected SaleOrderRepository saleOrderRepo;
protected AppSaleService appSaleService;
protected SaleOrderService saleOrderService;
protected SaleOrderComputeService saleOrderComputeService;
@Inject
public SaleOrderCreateServiceImpl(
PartnerService partnerService,
SaleOrderRepository saleOrderRepo,
AppSaleService appSaleService,
SaleOrderService saleOrderService,
SaleOrderComputeService saleOrderComputeService) {
this.partnerService = partnerService;
this.saleOrderRepo = saleOrderRepo;
this.appSaleService = appSaleService;
this.saleOrderService = saleOrderService;
this.saleOrderComputeService = saleOrderComputeService;
}
@Override
public SaleOrder createSaleOrder(Company company) throws AxelorException {
SaleOrder saleOrder = new SaleOrder();
saleOrder.setCreationDate(appSaleService.getTodayDate());
if (company != null) {
saleOrder.setCompany(company);
saleOrder.setCurrency(company.getCurrency());
}
saleOrder.setSalemanUser(AuthUtils.getUser());
saleOrder.setTeam(saleOrder.getSalemanUser().getActiveTeam());
saleOrder.setStatusSelect(SaleOrderRepository.STATUS_DRAFT_QUOTATION);
saleOrderService.computeEndOfValidityDate(saleOrder);
return saleOrder;
}
@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 {
logger.debug(
"Création d'un devis client : Société = {}, Reference externe = {}, Client = {}",
new Object[] {company, externalReference, clientPartner.getFullName()});
SaleOrder saleOrder = new SaleOrder();
saleOrder.setClientPartner(clientPartner);
saleOrder.setCreationDate(appSaleService.getTodayDate());
saleOrder.setContactPartner(contactPartner);
saleOrder.setCurrency(currency);
saleOrder.setExternalReference(externalReference);
saleOrder.setDeliveryDate(deliveryDate);
saleOrder.setOrderDate(orderDate);
saleOrder.setPrintingSettings(
Beans.get(TradingNameService.class).getDefaultPrintingSettings(null, company));
if (salemanUser == null) {
salemanUser = AuthUtils.getUser();
}
saleOrder.setSalemanUser(salemanUser);
if (team == null) {
team = salemanUser.getActiveTeam();
}
saleOrder.setTeam(team);
if (company == null) {
company = salemanUser.getActiveCompany();
}
saleOrder.setCompany(company);
saleOrder.setMainInvoicingAddress(partnerService.getInvoicingAddress(clientPartner));
saleOrder.setDeliveryAddress(partnerService.getDeliveryAddress(clientPartner));
saleOrderService.computeAddressStr(saleOrder);
if (priceList == null) {
priceList =
Beans.get(PartnerPriceListService.class)
.getDefaultPriceList(clientPartner, PriceListRepository.TYPE_SALE);
}
saleOrder.setPriceList(priceList);
saleOrder.setSaleOrderLineList(new ArrayList<>());
saleOrder.setStatusSelect(SaleOrderRepository.STATUS_DRAFT_QUOTATION);
saleOrderService.computeEndOfValidityDate(saleOrder);
return saleOrder;
}
@Override
@Transactional(rollbackOn = {Exception.class})
public SaleOrder mergeSaleOrders(
List<SaleOrder> saleOrderList,
Currency currency,
Partner clientPartner,
Company company,
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,
LocalDate.now(),
priceList,
clientPartner,
team);
this.attachToNewSaleOrder(saleOrderList, saleOrderMerged);
saleOrderComputeService.computeSaleOrder(saleOrderMerged);
saleOrderRepo.save(saleOrderMerged);
this.removeOldSaleOrders(saleOrderList);
return saleOrderMerged;
}
// Attachment of all sale order lines to new sale order
protected void attachToNewSaleOrder(List<SaleOrder> saleOrderList, SaleOrder saleOrderMerged) {
for (SaleOrder saleOrder : saleOrderList) {
int countLine = 1;
for (SaleOrderLine saleOrderLine : saleOrder.getSaleOrderLineList()) {
saleOrderLine.setSequence(countLine * 10);
saleOrderMerged.addSaleOrderLineListItem(saleOrderLine);
countLine++;
}
}
}
// Remove old sale orders after merge
protected void removeOldSaleOrders(List<SaleOrder> saleOrderList) {
for (SaleOrder saleOrder : saleOrderList) {
saleOrderRepo.remove(saleOrder);
}
}
@Override
@Transactional(rollbackOn = {Exception.class})
public SaleOrder createSaleOrder(
SaleOrder context, Currency wizardCurrency, PriceList wizardPriceList)
throws AxelorException {
SaleOrder copy = saleOrderRepo.copy(context, true);
copy.setCreationDate(appSaleService.getTodayDate());
copy.setCurrency(wizardCurrency);
copy.setPriceList(wizardPriceList);
saleOrderService.computeEndOfValidityDate(copy);
this.updateSaleOrderLineList(copy);
saleOrderComputeService.computeSaleOrder(copy);
copy.setTemplate(false);
copy.setTemplateUser(null);
return copy;
}
public void updateSaleOrderLineList(SaleOrder saleOrder) throws AxelorException {
List<SaleOrderLine> saleOrderLineList = saleOrder.getSaleOrderLineList();
if (saleOrderLineList != null) {
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
Beans.get(SaleOrderLineService.class)
.fillPrice(saleOrderLine, saleOrder, saleOrderLine.getPackPriceSelect());
Beans.get(SaleOrderLineService.class).computeValues(saleOrder, saleOrderLine);
}
}
}
@Override
@Transactional
public SaleOrder createTemplate(SaleOrder context) {
SaleOrder copy = saleOrderRepo.copy(context, true);
copy.setTemplate(true);
copy.setTemplateUser(AuthUtils.getUser());
return copy;
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.account.db.TaxLine;
import com.axelor.apps.base.db.PriceList;
import com.axelor.apps.base.db.PriceListLine;
import com.axelor.apps.base.db.Unit;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.exception.AxelorException;
import com.axelor.rpc.ActionResponse;
import com.axelor.rpc.Context;
import java.math.BigDecimal;
import java.util.Map;
public interface SaleOrderLineService {
/**
* Update all fields of the sale order line from the product.
*
* @param saleOrderLine
* @param saleOrder
*/
void computeProductInformation(
SaleOrderLine saleOrderLine, SaleOrder saleOrder, Integer packPriceSelect)
throws AxelorException;
SaleOrderLine resetProductInformation(SaleOrderLine line);
/**
* Compute totals from a sale order line
*
* @param saleOrder
* @param saleOrderLine
* @return
* @throws AxelorException
*/
public Map<String, BigDecimal> computeValues(SaleOrder saleOrder, SaleOrderLine saleOrderLine)
throws AxelorException;
/**
* Compute the excluded tax total amount of a sale order line.
*
* @param saleOrderLine the sale order line which total amount you want to compute.
* @return The excluded tax total amount.
*/
public BigDecimal computeAmount(SaleOrderLine saleOrderLine);
/**
* Compute the excluded tax total amount of a sale order line.
*
* @param quantity The quantity.
* @param price The unit price.
* @return The excluded tax total amount.
*/
public BigDecimal computeAmount(BigDecimal quantity, BigDecimal price);
public BigDecimal getExTaxUnitPrice(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, TaxLine taxLine) throws AxelorException;
public BigDecimal getInTaxUnitPrice(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, TaxLine taxLine) throws AxelorException;
public TaxLine getTaxLine(SaleOrder saleOrder, SaleOrderLine saleOrderLine)
throws AxelorException;
public BigDecimal getAmountInCompanyCurrency(BigDecimal exTaxTotal, SaleOrder saleOrder)
throws AxelorException;
public BigDecimal getCompanyCostPrice(SaleOrder saleOrder, SaleOrderLine saleOrderLine)
throws AxelorException;
public PriceListLine getPriceListLine(
SaleOrderLine saleOrderLine, PriceList priceList, BigDecimal price);
/**
* Compute and return the discounted price of a sale order line.
*
* @param saleOrderLine the sale order line.
* @param inAti whether or not the sale order line (and thus the discounted price) includes taxes.
* @return the discounted price of the line, including taxes if inAti is true.
*/
public BigDecimal computeDiscount(SaleOrderLine saleOrderLine, Boolean inAti);
/**
* Convert a product's unit price from incl. tax to ex. tax or the other way round.
*
* <p>If the price is ati, it will be converted to ex. tax, and if it isn't it will be converted
* to ati.
*
* @param priceIsAti a boolean indicating if the price is ati.
* @param taxLine the tax to apply.
* @param price the unit price to convert.
* @return the converted price as a BigDecimal.
*/
public BigDecimal convertUnitPrice(Boolean inAti, TaxLine taxLine, BigDecimal price);
public Map<String, Object> getDiscountsFromPriceLists(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, BigDecimal price);
public int getDiscountTypeSelect(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, BigDecimal price);
public Unit getSaleUnit(SaleOrderLine saleOrderLine);
public BigDecimal computeTotalPack(SaleOrderLine saleOrderLine);
public SaleOrder getSaleOrder(Context context);
public Map<String, BigDecimal> computeSubMargin(SaleOrder saleOrder, SaleOrderLine saleOrderLine)
throws AxelorException;
public BigDecimal getAvailableStock(SaleOrder saleOrder, SaleOrderLine saleOrderLine);
public BigDecimal getAllocatedStock(SaleOrder saleOrder, SaleOrderLine saleOrderLine);
public void checkMultipleQty(SaleOrderLine saleOrderLine, ActionResponse response);
/**
* Fill price based on packPriceSelect only for packLine or subline. Works normal for standard
* line.
*
* @param saleOrderLine
* @param saleOrder
* @param packPriceSelect
* @throws AxelorException
*/
public void fillPrice(SaleOrderLine saleOrderLine, SaleOrder saleOrder, Integer packPriceSelect)
throws AxelorException;
public boolean checkTaxRequired(SaleOrderLine saleOrderLine, Integer packPriceSelect);
}

View File

@ -0,0 +1,647 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.account.db.FiscalPosition;
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.base.db.PriceList;
import com.axelor.apps.base.db.PriceListLine;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.base.db.Unit;
import com.axelor.apps.base.db.repo.PriceListLineRepository;
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.base.service.ProductMultipleQtyService;
import com.axelor.apps.base.service.app.AppBaseService;
import com.axelor.apps.base.service.tax.AccountManagementService;
import com.axelor.apps.base.service.tax.FiscalPositionService;
import com.axelor.apps.sale.db.PackLine;
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.app.AppSaleService;
import com.axelor.exception.AxelorException;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionResponse;
import com.axelor.rpc.Context;
import com.google.inject.Inject;
import java.lang.invoke.MethodHandles;
import java.math.BigDecimal;
import java.math.RoundingMode;
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 SaleOrderLineServiceImpl implements SaleOrderLineService {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Inject protected CurrencyService currencyService;
@Inject protected PriceListService priceListService;
@Inject protected ProductMultipleQtyService productMultipleQtyService;
@Inject protected AppSaleService appSaleService;
@Inject protected AccountManagementService accountManagementService;
@Override
public void computeProductInformation(
SaleOrderLine saleOrderLine, SaleOrder saleOrder, Integer packPriceSelect)
throws AxelorException {
Product product = saleOrderLine.getProduct();
saleOrderLine.setProductName(product.getName());
saleOrderLine.setPvg(product.getPvg());
saleOrderLine.setUg(product.getUg());
saleOrderLine.setStklim(product.getStklim());
saleOrderLine.setPpa(product.getPpa());
saleOrderLine.setShp(product.getShp());
saleOrderLine.setUnit(this.getSaleUnit(saleOrderLine));
if (appSaleService.getAppSale().getIsEnabledProductDescriptionCopy()) {
saleOrderLine.setDescription(product.getDescription());
}
saleOrderLine.setTypeSelect(SaleOrderLineRepository.TYPE_NORMAL);
saleOrderLine.setSubLineList(null);
saleOrderLine.setPackPriceSelect(null);
saleOrderLine.setDiscountTypeSelect(PriceListLineRepository.AMOUNT_TYPE_NONE);
if (appSaleService.getAppSale().getProductPackMgt()
&& saleOrderLine
.getProduct()
.getProductTypeSelect()
.equals(ProductRepository.PRODUCT_TYPE_PACK)
&& !saleOrderLine.getIsSubLine()) {
saleOrderLine.setTypeSelect(SaleOrderLineRepository.TYPE_PACK);
saleOrderLine.setPackPriceSelect(packPriceSelect);
saleOrderLine.setSubLineList(createPackLines(saleOrderLine, saleOrder));
}
fillPrice(saleOrderLine, saleOrder, packPriceSelect);
}
@Override
public void fillPrice(SaleOrderLine saleOrderLine, SaleOrder saleOrder, Integer packPriceSelect)
throws AxelorException {
boolean taxRequired = checkTaxRequired(saleOrderLine, packPriceSelect);
if (taxRequired) {
fillTaxInformation(saleOrderLine, saleOrder);
saleOrderLine.setCompanyCostPrice(this.getCompanyCostPrice(saleOrder, saleOrderLine));
BigDecimal exTaxPrice;
BigDecimal inTaxPrice;
if (saleOrderLine.getProduct().getInAti()) {
inTaxPrice = this.getInTaxUnitPrice(saleOrder, saleOrderLine, saleOrderLine.getTaxLine());
inTaxPrice = fillDiscount(saleOrderLine, saleOrder, inTaxPrice);
saleOrderLine.setInTaxPrice(inTaxPrice);
saleOrderLine.setPrice(convertUnitPrice(true, saleOrderLine.getTaxLine(), inTaxPrice));
} else {
exTaxPrice = this.getExTaxUnitPrice(saleOrder, saleOrderLine, saleOrderLine.getTaxLine());
exTaxPrice = fillDiscount(saleOrderLine, saleOrder, exTaxPrice);
saleOrderLine.setPrice(exTaxPrice);
saleOrderLine.setInTaxPrice(
convertUnitPrice(false, saleOrderLine.getTaxLine(), exTaxPrice));
}
} else {
saleOrderLine.setPrice(BigDecimal.ZERO);
saleOrderLine.setInTaxPrice(BigDecimal.ZERO);
saleOrderLine.setDiscountAmount(BigDecimal.ZERO);
saleOrderLine.setDiscountTypeSelect(PriceListLineRepository.AMOUNT_TYPE_NONE);
saleOrderLine.setCompanyCostPrice(BigDecimal.ZERO);
}
}
protected List<SaleOrderLine> createPackLines(SaleOrderLine saleOrderLine, SaleOrder saleOrder)
throws AxelorException {
List<SaleOrderLine> subLines = new ArrayList<SaleOrderLine>();
Integer sequence = saleOrderLine.getSequence();
if (sequence == null) {
sequence = 0;
}
if (saleOrder.getSaleOrderLineList() != null && sequence == 0) {
for (SaleOrderLine orderLine : saleOrder.getSaleOrderLineList()) {
if (orderLine.getSequence() > sequence) {
sequence = orderLine.getSequence();
}
}
}
if (saleOrderLine.getSequence() == null) {
saleOrderLine.setSequence(++sequence);
}
for (PackLine packLine : saleOrderLine.getProduct().getPackLines()) {
SaleOrderLine subLine = new SaleOrderLine();
Product subProduct = packLine.getProduct();
subLine.setProduct(subProduct);
subLine.setQty(new BigDecimal(packLine.getQuantity()).multiply(saleOrderLine.getQty()));
subLine.setIsSubLine(true);
computeProductInformation(subLine, saleOrder, saleOrderLine.getPackPriceSelect());
computeValues(saleOrder, subLine);
subLine.setSequence(++sequence);
subLines.add(subLine);
}
return subLines;
}
protected BigDecimal fillDiscount(
SaleOrderLine saleOrderLine, SaleOrder saleOrder, BigDecimal price) {
Map<String, Object> discounts =
this.getDiscountsFromPriceLists(saleOrder, saleOrderLine, price);
if (discounts != null) {
if (discounts.get("price") != null) {
price = (BigDecimal) discounts.get("price");
}
if (saleOrderLine.getProduct().getInAti() != saleOrder.getInAti()
&& (Integer) discounts.get("discountTypeSelect")
!= PriceListLineRepository.AMOUNT_TYPE_PERCENT) {
saleOrderLine.setDiscountAmount(
this.convertUnitPrice(
saleOrderLine.getProduct().getInAti(),
saleOrderLine.getTaxLine(),
(BigDecimal) discounts.get("discountAmount")));
} else {
saleOrderLine.setDiscountAmount((BigDecimal) discounts.get("discountAmount"));
}
saleOrderLine.setDiscountTypeSelect((Integer) discounts.get("discountTypeSelect"));
} else if (!saleOrder.getTemplate()) {
saleOrderLine.setDiscountAmount(BigDecimal.ZERO);
saleOrderLine.setDiscountTypeSelect(PriceListLineRepository.AMOUNT_TYPE_NONE);
}
return price;
}
protected void fillTaxInformation(SaleOrderLine saleOrderLine, SaleOrder saleOrder)
throws AxelorException {
if (saleOrder.getClientPartner() != null) {
TaxLine taxLine = this.getTaxLine(saleOrder, saleOrderLine);
saleOrderLine.setTaxLine(taxLine);
FiscalPosition fiscalPosition = saleOrder.getClientPartner().getFiscalPosition();
Tax tax =
accountManagementService.getProductTax(
saleOrderLine.getProduct(), saleOrder.getCompany(), fiscalPosition, false);
TaxEquiv taxEquiv = Beans.get(FiscalPositionService.class).getTaxEquiv(fiscalPosition, tax);
saleOrderLine.setTaxEquiv(taxEquiv);
} else {
saleOrderLine.setTaxLine(null);
saleOrderLine.setTaxEquiv(null);
}
}
@Override
public boolean checkTaxRequired(SaleOrderLine saleOrderLine, Integer packPriceSelect) {
if (appSaleService.getAppSale().getProductPackMgt()) {
if (saleOrderLine.getIsSubLine()
&& packPriceSelect != null
&& packPriceSelect == SaleOrderLineRepository.PACK_PRICE_ONLY) {
return false;
}
if (saleOrderLine.getTypeSelect() == SaleOrderLineRepository.TYPE_PACK
&& packPriceSelect != null
&& packPriceSelect == SaleOrderLineRepository.SUBLINE_PRICE_ONLY) {
return false;
}
}
return true;
}
@Override
public SaleOrderLine resetProductInformation(SaleOrderLine line) {
line.setTaxLine(null);
line.setTaxEquiv(null);
line.setProductName(null);
line.setUnit(null);
line.setCompanyCostPrice(null);
line.setDiscountAmount(null);
line.setDiscountTypeSelect(PriceListLineRepository.AMOUNT_TYPE_NONE);
line.setPrice(null);
line.setInTaxPrice(null);
line.setExTaxTotal(null);
line.setInTaxTotal(null);
line.setCompanyInTaxTotal(null);
line.setCompanyExTaxTotal(null);
if (appSaleService.getAppSale().getIsEnabledProductDescriptionCopy()) {
line.setDescription(null);
}
return line;
}
@Override
public Map<String, BigDecimal> computeValues(SaleOrder saleOrder, SaleOrderLine saleOrderLine)
throws AxelorException {
HashMap<String, BigDecimal> map = new HashMap<>();
if (saleOrder == null
|| saleOrderLine.getPrice() == null
|| saleOrderLine.getInTaxPrice() == null
|| saleOrderLine.getQty() == null) {
return map;
}
BigDecimal exTaxTotal;
BigDecimal companyExTaxTotal;
BigDecimal inTaxTotal;
BigDecimal companyInTaxTotal;
BigDecimal priceDiscounted = this.computeDiscount(saleOrderLine, saleOrder.getInAti());
BigDecimal taxRate = BigDecimal.ZERO;
BigDecimal subTotalCostPrice = BigDecimal.ZERO;
if (saleOrderLine.getTaxLine() != null) {
taxRate = saleOrderLine.getTaxLine().getValue();
}
if (!saleOrder.getInAti()) {
exTaxTotal = this.computeAmount(saleOrderLine.getQty(), priceDiscounted);
inTaxTotal = exTaxTotal.add(exTaxTotal.multiply(taxRate));
companyExTaxTotal = this.getAmountInCompanyCurrency(exTaxTotal, saleOrder);
companyInTaxTotal = companyExTaxTotal.add(companyExTaxTotal.multiply(taxRate));
} else {
inTaxTotal = this.computeAmount(saleOrderLine.getQty(), priceDiscounted);
exTaxTotal = inTaxTotal.divide(taxRate.add(BigDecimal.ONE), 2, BigDecimal.ROUND_HALF_UP);
companyInTaxTotal = this.getAmountInCompanyCurrency(inTaxTotal, saleOrder);
companyExTaxTotal =
companyInTaxTotal.divide(taxRate.add(BigDecimal.ONE), 2, BigDecimal.ROUND_HALF_UP);
}
if (saleOrderLine.getProduct() != null
&& saleOrderLine.getProduct().getCostPrice().compareTo(BigDecimal.ZERO) != 0) {
subTotalCostPrice =
saleOrderLine.getProduct().getCostPrice().multiply(saleOrderLine.getQty());
}
saleOrderLine.setInTaxTotal(inTaxTotal);
saleOrderLine.setExTaxTotal(exTaxTotal);
saleOrderLine.setPriceDiscounted(priceDiscounted);
saleOrderLine.setCompanyInTaxTotal(companyInTaxTotal);
saleOrderLine.setCompanyExTaxTotal(companyExTaxTotal);
saleOrderLine.setSubTotalCostPrice(subTotalCostPrice);
map.put("inTaxTotal", inTaxTotal);
map.put("exTaxTotal", exTaxTotal);
map.put("priceDiscounted", priceDiscounted);
map.put("companyExTaxTotal", companyExTaxTotal);
map.put("companyInTaxTotal", companyInTaxTotal);
map.put("subTotalCostPrice", subTotalCostPrice);
map.putAll(this.computeSubMargin(saleOrder, saleOrderLine));
return map;
}
/**
* Compute the excluded tax total amount of a sale order line.
*
* @param quantity The quantity.
* @param price The unit price.
* @return The excluded tax total amount.
*/
@Override
public BigDecimal computeAmount(SaleOrderLine saleOrderLine) {
BigDecimal price = this.computeDiscount(saleOrderLine, false);
return computeAmount(saleOrderLine.getQty(), price);
}
@Override
public BigDecimal computeAmount(BigDecimal quantity, BigDecimal price) {
BigDecimal amount =
quantity
.multiply(price)
.setScale(AppSaleService.DEFAULT_NB_DECIMAL_DIGITS, RoundingMode.HALF_EVEN);
logger.debug(
"Calcul du montant HT avec une quantité de {} pour {} : {}",
new Object[] {quantity, price, amount});
return amount;
}
@Override
public BigDecimal getExTaxUnitPrice(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, TaxLine taxLine) throws AxelorException {
return this.getUnitPrice(saleOrder, saleOrderLine, taxLine, false);
}
@Override
public BigDecimal getInTaxUnitPrice(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, TaxLine taxLine) throws AxelorException {
return this.getUnitPrice(saleOrder, saleOrderLine, taxLine, true);
}
/**
* A function used to get the unit price of a sale order line, either in ati or wt
*
* @param saleOrder the sale order containing the sale order line
* @param saleOrderLine
* @param taxLine the tax applied to the unit price
* @param resultInAti whether you want the result in ati or not
* @return the unit price of the sale order line
* @throws AxelorException
*/
private BigDecimal getUnitPrice(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, TaxLine taxLine, boolean resultInAti)
throws AxelorException {
Product product = saleOrderLine.getProduct();
BigDecimal price =
(product.getInAti() == resultInAti)
? product.getSalePrice()
: this.convertUnitPrice(product.getInAti(), taxLine, product.getSalePrice());
return currencyService
.getAmountCurrencyConvertedAtDate(
product.getSaleCurrency(), saleOrder.getCurrency(), price, saleOrder.getCreationDate())
.setScale(5, RoundingMode.HALF_UP);
}
@Override
public TaxLine getTaxLine(SaleOrder saleOrder, SaleOrderLine saleOrderLine)
throws AxelorException {
return Beans.get(AccountManagementService.class)
.getTaxLine(
saleOrder.getCreationDate(),
saleOrderLine.getProduct(),
saleOrder.getCompany(),
saleOrder.getClientPartner().getFiscalPosition(),
false);
}
@Override
public BigDecimal getAmountInCompanyCurrency(BigDecimal exTaxTotal, SaleOrder saleOrder)
throws AxelorException {
return currencyService
.getAmountCurrencyConvertedAtDate(
saleOrder.getCurrency(),
saleOrder.getCompany().getCurrency(),
exTaxTotal,
saleOrder.getCreationDate())
.setScale(AppSaleService.DEFAULT_NB_DECIMAL_DIGITS, RoundingMode.HALF_UP);
}
@Override
public BigDecimal getCompanyCostPrice(SaleOrder saleOrder, SaleOrderLine saleOrderLine)
throws AxelorException {
Product product = saleOrderLine.getProduct();
return currencyService
.getAmountCurrencyConvertedAtDate(
product.getPurchaseCurrency(),
saleOrder.getCompany().getCurrency(),
product.getCostPrice(),
saleOrder.getCreationDate())
.setScale(AppSaleService.DEFAULT_NB_DECIMAL_DIGITS, RoundingMode.HALF_UP);
}
@Override
public PriceListLine getPriceListLine(
SaleOrderLine saleOrderLine, PriceList priceList, BigDecimal price) {
return priceListService.getPriceListLine(
saleOrderLine.getProduct(), saleOrderLine.getQty(), priceList, price);
}
@Override
public BigDecimal computeDiscount(SaleOrderLine saleOrderLine, Boolean inAti) {
BigDecimal price = inAti ? saleOrderLine.getInTaxPrice() : saleOrderLine.getPrice();
int scale = Beans.get(AppBaseService.class).getNbDecimalDigitForSalePrice();
return priceListService
.computeDiscount(
price, saleOrderLine.getDiscountTypeSelect(), saleOrderLine.getDiscountAmount())
.setScale(scale);
}
@Override
public BigDecimal convertUnitPrice(Boolean priceIsAti, TaxLine taxLine, BigDecimal price) {
if (taxLine == null) {
return price;
}
if (priceIsAti) {
price = price.divide(taxLine.getValue().add(BigDecimal.ONE), 5, BigDecimal.ROUND_HALF_UP);
} else {
price = price.add(price.multiply(taxLine.getValue()));
}
return price;
}
@Override
public Map<String, Object> getDiscountsFromPriceLists(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, BigDecimal price) {
Map<String, Object> discounts = null;
PriceList priceList = saleOrder.getPriceList();
if (priceList != null) {
PriceListLine priceListLine = this.getPriceListLine(saleOrderLine, priceList, price);
discounts = priceListService.getReplacedPriceAndDiscounts(priceList, priceListLine, price);
if (saleOrder.getTemplate()) {
Integer manualDiscountAmountType = saleOrderLine.getDiscountTypeSelect();
BigDecimal manualDiscountAmount = saleOrderLine.getDiscountAmount();
Integer priceListDiscountAmountType = (Integer) discounts.get("discountTypeSelect");
BigDecimal priceListDiscountAmount = (BigDecimal) discounts.get("discountAmount");
if (!manualDiscountAmountType.equals(priceListDiscountAmountType)
&& manualDiscountAmountType.equals(PriceListLineRepository.AMOUNT_TYPE_PERCENT)
&& priceListDiscountAmountType.equals(PriceListLineRepository.AMOUNT_TYPE_FIXED)) {
priceListDiscountAmount =
priceListDiscountAmount
.multiply(new BigDecimal(100))
.divide(price, 2, RoundingMode.HALF_UP);
} else if (!manualDiscountAmountType.equals(priceListDiscountAmountType)
&& manualDiscountAmountType.equals(PriceListLineRepository.AMOUNT_TYPE_FIXED)
&& priceListDiscountAmountType.equals(PriceListLineRepository.AMOUNT_TYPE_PERCENT)) {
priceListDiscountAmount =
priceListDiscountAmount
.multiply(price)
.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
}
if (manualDiscountAmount.compareTo(priceListDiscountAmount) > 0) {
discounts.put("discountAmount", manualDiscountAmount);
discounts.put("discountTypeSelect", manualDiscountAmountType);
}
}
}
return discounts;
}
@Override
public int getDiscountTypeSelect(
SaleOrder saleOrder, SaleOrderLine saleOrderLine, BigDecimal price) {
PriceList priceList = saleOrder.getPriceList();
if (priceList != null) {
PriceListLine priceListLine = this.getPriceListLine(saleOrderLine, priceList, price);
return priceListLine.getTypeSelect();
}
return 0;
}
@Override
public Unit getSaleUnit(SaleOrderLine saleOrderLine) {
Unit unit = saleOrderLine.getProduct().getSalesUnit();
if (unit == null) {
unit = saleOrderLine.getProduct().getUnit();
}
return unit;
}
@Override
public BigDecimal computeTotalPack(SaleOrderLine saleOrderLine) {
BigDecimal totalPack = BigDecimal.ZERO;
if (saleOrderLine.getSubLineList() != null) {
for (SaleOrderLine subLine : saleOrderLine.getSubLineList()) {
totalPack = totalPack.add(subLine.getInTaxTotal());
}
}
return totalPack;
}
@Override
public SaleOrder getSaleOrder(Context context) {
Context parentContext = context.getParent();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
SaleOrder saleOrder = saleOrderLine.getSaleOrder();
if (parentContext != null && !parentContext.getContextClass().equals(SaleOrder.class)) {
parentContext = parentContext.getParent();
}
if (parentContext != null && parentContext.getContextClass().equals(SaleOrder.class)) {
saleOrder = parentContext.asType(SaleOrder.class);
}
return saleOrder;
}
@Override
public Map<String, BigDecimal> computeSubMargin(SaleOrder saleOrder, SaleOrderLine saleOrderLine)
throws AxelorException {
HashMap<String, BigDecimal> map = new HashMap<>();
BigDecimal subTotalCostPrice = BigDecimal.ZERO;
BigDecimal subTotalGrossProfit = BigDecimal.ZERO;
BigDecimal subMarginRate = BigDecimal.ZERO;
BigDecimal subTotalMarkup = BigDecimal.ZERO;
BigDecimal totalWT = BigDecimal.ZERO;
if (saleOrderLine.getProduct() != null
&& saleOrderLine.getProduct().getCostPrice().compareTo(BigDecimal.ZERO) != 0
&& saleOrderLine.getExTaxTotal().compareTo(BigDecimal.ZERO) != 0) {
totalWT =
currencyService.getAmountCurrencyConvertedAtDate(
saleOrder.getCurrency(),
saleOrder.getCompany().getCurrency(),
saleOrderLine.getExTaxTotal(),
null);
logger.debug("Total WT in company currency: {}", totalWT);
subTotalCostPrice = saleOrderLine.getSubTotalCostPrice();
logger.debug("Subtotal cost price: {}", subTotalCostPrice);
subTotalGrossProfit = totalWT.subtract(subTotalCostPrice);
logger.debug("Subtotal gross margin: {}", subTotalGrossProfit);
subMarginRate =
subTotalGrossProfit.divide(totalWT, RoundingMode.HALF_EVEN).multiply(new BigDecimal(100));
logger.debug("Subtotal gross margin rate: {}", subMarginRate);
if (subTotalCostPrice.compareTo(BigDecimal.ZERO) != 0) {
subTotalMarkup =
subTotalGrossProfit
.divide(subTotalCostPrice, RoundingMode.HALF_EVEN)
.multiply(new BigDecimal(100));
logger.debug("Subtotal markup: {}", subTotalMarkup);
}
}
saleOrderLine.setSubTotalGrossMargin(subTotalGrossProfit);
saleOrderLine.setSubMarginRate(subMarginRate);
saleOrderLine.setSubTotalMarkup(subTotalMarkup);
map.put("subTotalGrossMargin", subTotalGrossProfit);
map.put("subMarginRate", subMarginRate);
map.put("subTotalMarkup", subTotalMarkup);
return map;
}
@Override
public BigDecimal getAvailableStock(SaleOrder saleOrder, SaleOrderLine saleOrderLine) {
// defined in supplychain
return BigDecimal.ZERO;
}
@Override
public BigDecimal getAllocatedStock(SaleOrder saleOrder, SaleOrderLine saleOrderLine) {
// defined in supplychain
return BigDecimal.ZERO;
}
@Override
public void checkMultipleQty(SaleOrderLine saleOrderLine, ActionResponse response) {
Product product = saleOrderLine.getProduct();
if (product == null) {
return;
}
productMultipleQtyService.checkMultipleQty(
saleOrderLine.getQty(),
product.getSaleProductMultipleQtyList(),
product.getAllowToForceSaleQty(),
response);
}
}

View File

@ -0,0 +1,133 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.account.db.TaxEquiv;
import com.axelor.apps.account.db.TaxLine;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.apps.sale.db.SaleOrderLineTax;
import com.google.common.base.Joiner;
import com.google.inject.Inject;
import java.lang.invoke.MethodHandles;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SaleOrderLineTaxService {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Inject private SaleOrderToolService saleOrderToolService;
/**
* Créer les lignes de TVA du devis. La création des lignes de TVA se basent sur les lignes de
* devis ainsi que les sous-lignes de devis de celles-ci. Si une ligne de devis comporte des
* sous-lignes de devis, alors on se base uniquement sur celles-ci.
*
* @param invoice La facture.
* @param invoiceLines Les lignes de facture.
* @param invoiceLineTaxes Les lignes des taxes de la facture.
* @return La liste des lignes de taxe de la facture.
*/
public List<SaleOrderLineTax> createsSaleOrderLineTax(
SaleOrder saleOrder, List<SaleOrderLine> saleOrderLineList) {
List<SaleOrderLineTax> saleOrderLineTaxList = new ArrayList<SaleOrderLineTax>();
Map<TaxLine, SaleOrderLineTax> map = new HashMap<TaxLine, SaleOrderLineTax>();
Set<String> specificNotes = new HashSet<String>();
boolean customerSpecificNote = false;
if (saleOrder.getClientPartner().getFiscalPosition() != null) {
customerSpecificNote =
saleOrder.getClientPartner().getFiscalPosition().getCustomerSpecificNote();
}
if (saleOrderLineList != null && !saleOrderLineList.isEmpty()) {
LOG.debug("Création des lignes de tva pour les lignes de factures.");
for (SaleOrderLine saleOrderLine : saleOrderLineList) {
TaxLine taxLine = saleOrderLine.getTaxLine();
if (taxLine != null) {
LOG.debug("Tax {}", taxLine);
if (map.containsKey(taxLine)) {
SaleOrderLineTax saleOrderLineTax = map.get(taxLine);
saleOrderLineTax.setExTaxBase(
saleOrderLineTax.getExTaxBase().add(saleOrderLine.getExTaxTotal()));
} else {
SaleOrderLineTax saleOrderLineTax = new SaleOrderLineTax();
saleOrderLineTax.setSaleOrder(saleOrder);
saleOrderLineTax.setExTaxBase(saleOrderLine.getExTaxTotal());
saleOrderLineTax.setTaxLine(taxLine);
map.put(taxLine, saleOrderLineTax);
}
}
if (!customerSpecificNote) {
TaxEquiv taxEquiv = saleOrderLine.getTaxEquiv();
if (taxEquiv != null && taxEquiv.getSpecificNote() != null) {
specificNotes.add(taxEquiv.getSpecificNote());
}
}
}
}
for (SaleOrderLineTax saleOrderLineTax : map.values()) {
// Dans la devise de la facture
BigDecimal exTaxBase = saleOrderLineTax.getExTaxBase();
BigDecimal taxTotal = BigDecimal.ZERO;
if (saleOrderLineTax.getTaxLine() != null) {
taxTotal =
saleOrderToolService.computeAmount(exTaxBase, saleOrderLineTax.getTaxLine().getValue());
saleOrderLineTax.setTaxTotal(taxTotal);
}
saleOrderLineTax.setInTaxTotal(exTaxBase.add(taxTotal));
saleOrderLineTaxList.add(saleOrderLineTax);
LOG.debug(
"Ligne de TVA : Total TVA => {}, Total HT => {}",
new Object[] {saleOrderLineTax.getTaxTotal(), saleOrderLineTax.getInTaxTotal()});
}
if (!customerSpecificNote) {
saleOrder.setSpecificNotes(Joiner.on('\n').join(specificNotes));
} else {
saleOrder.setSpecificNotes(saleOrder.getClientPartner().getSpecificTaxNote());
}
return saleOrderLineTaxList;
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.sale.db.SaleOrder;
public interface SaleOrderMarginService {
public void computeMarginSaleOrder(SaleOrder saleOrder);
}

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.sale.service.saleorder;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.SaleOrderLine;
import java.lang.invoke.MethodHandles;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SaleOrderMarginServiceImpl implements SaleOrderMarginService {
protected final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public void computeMarginSaleOrder(SaleOrder saleOrder) {
BigDecimal accountedRevenue = BigDecimal.ZERO;
BigDecimal totalCostPrice = BigDecimal.ZERO;
BigDecimal totalGrossProfit = BigDecimal.ZERO;
BigDecimal marginRate = BigDecimal.ZERO;
BigDecimal markup = BigDecimal.ZERO;
if (saleOrder.getSaleOrderLineList() != null && !saleOrder.getSaleOrderLineList().isEmpty()) {
for (SaleOrderLine saleOrderLineList : saleOrder.getSaleOrderLineList()) {
if (saleOrderLineList.getProduct() == null
|| saleOrderLineList.getSubTotalCostPrice().compareTo(BigDecimal.ZERO) == 0
|| saleOrderLineList.getExTaxTotal().compareTo(BigDecimal.ZERO) == 0) {
} else {
accountedRevenue = accountedRevenue.add(saleOrderLineList.getCompanyExTaxTotal());
totalCostPrice = totalCostPrice.add(saleOrderLineList.getSubTotalCostPrice());
totalGrossProfit = totalGrossProfit.add(saleOrderLineList.getSubTotalGrossMargin());
marginRate =
totalGrossProfit
.divide(accountedRevenue, RoundingMode.HALF_EVEN)
.multiply(new BigDecimal(100));
markup =
totalGrossProfit
.divide(totalCostPrice, RoundingMode.HALF_EVEN)
.multiply(new BigDecimal(100));
}
}
}
saleOrder.setAccountedRevenue(accountedRevenue);
saleOrder.setTotalCostPrice(totalCostPrice);
saleOrder.setTotalGrossMargin(totalGrossProfit);
saleOrder.setMarginRate(marginRate);
saleOrder.setMarkup(markup);
}
}

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.sale.service.saleorder;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.exception.AxelorException;
public interface SaleOrderService {
public String getFileName(SaleOrder saleOrder);
public SaleOrder computeEndOfValidityDate(SaleOrder saleOrder);
@Deprecated
public String getReportLink(
SaleOrder saleOrder, String name, String language, boolean proforma, String format)
throws AxelorException;
/**
* Fill {@link SaleOrder#mainInvoicingAddressStr} and {@link SaleOrder#deliveryAddressStr}
*
* @param saleOrder
*/
public void computeAddressStr(SaleOrder saleOrder);
/**
* Enable edit order.
*
* @param saleOrder
* @throws AxelorException
*/
boolean enableEditOrder(SaleOrder saleOrder) throws AxelorException;
/**
* Check modified confirmed order before saving it.
*
* @param saleOrder
* @param saleOrderView
* @throws AxelorException
*/
void checkModifiedConfirmedOrder(SaleOrder saleOrder, SaleOrder saleOrderView)
throws AxelorException;
/**
* Validate changes.
*
* @param saleOrder
* @throws AxelorException
*/
void validateChanges(SaleOrder saleOrder) throws AxelorException;
/**
* Sort detail lines by sequence.
*
* @param saleOrder
*/
void sortSaleOrderLineList(SaleOrder saleOrder);
}

View File

@ -0,0 +1,117 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.ReportFactory;
import com.axelor.apps.base.service.AddressService;
import com.axelor.apps.base.service.DurationService;
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.exception.IExceptionMessage;
import com.axelor.apps.sale.report.IReport;
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.lang.invoke.MethodHandles;
import java.util.Comparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SaleOrderServiceImpl implements SaleOrderService {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public String getFileName(SaleOrder saleOrder) {
return I18n.get("Sale order")
+ " "
+ saleOrder.getSaleOrderSeq()
+ ((saleOrder.getVersionNumber() > 1) ? "-V" + saleOrder.getVersionNumber() : "");
}
@Override
public SaleOrder computeEndOfValidityDate(SaleOrder saleOrder) {
if (saleOrder.getDuration() != null && saleOrder.getCreationDate() != null) {
saleOrder.setEndOfValidityDate(
Beans.get(DurationService.class)
.computeDuration(saleOrder.getDuration(), saleOrder.getCreationDate()));
}
return saleOrder;
}
@Override
@Deprecated
public String getReportLink(
SaleOrder saleOrder, String name, String language, boolean proforma, String format)
throws AxelorException {
return ReportFactory.createReport(IReport.SALES_ORDER, name + "-${date}")
.addParam("Locale", language)
.addParam("SaleOrderId", saleOrder.getId())
.addParam("ProformaInvoice", proforma)
.addFormat(format)
.generate()
.getFileLink();
}
@Override
public void computeAddressStr(SaleOrder saleOrder) {
AddressService addressService = Beans.get(AddressService.class);
saleOrder.setMainInvoicingAddressStr(
addressService.computeAddressStr(saleOrder.getMainInvoicingAddress()));
saleOrder.setDeliveryAddressStr(
addressService.computeAddressStr(saleOrder.getDeliveryAddress()));
}
@Override
@Transactional(rollbackOn = {Exception.class})
public boolean enableEditOrder(SaleOrder saleOrder) throws AxelorException {
if (saleOrder.getStatusSelect() == SaleOrderRepository.STATUS_ORDER_COMPLETED) {
throw new AxelorException(
saleOrder,
TraceBackRepository.CATEGORY_INCONSISTENCY,
I18n.get(IExceptionMessage.SALES_ORDER_COMPLETED));
}
saleOrder.setOrderBeingEdited(true);
return false;
}
@Override
public void checkModifiedConfirmedOrder(SaleOrder saleOrder, SaleOrder saleOrderView)
throws AxelorException {
// Nothing to check if we don't have supplychain.
}
@Override
@Transactional(rollbackOn = {Exception.class})
public void validateChanges(SaleOrder saleOrder) throws AxelorException {
// Nothing to do if we don't have supplychain.
}
@Override
public void sortSaleOrderLineList(SaleOrder saleOrder) {
if (saleOrder.getSaleOrderLineList() != null) {
saleOrder.getSaleOrderLineList().sort(Comparator.comparing(SaleOrderLine::getSequence));
}
}
}

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.sale.service.saleorder;
import com.axelor.apps.base.service.CurrencyService;
import com.axelor.apps.base.service.app.AppBaseService;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.exception.AxelorException;
import com.google.inject.Inject;
import java.lang.invoke.MethodHandles;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SaleOrderToolService {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Inject protected CurrencyService currencyService;
/**
* Calculer le montant HT d'une ligne de devis.
*
* @param quantity Quantité.
* @param price Le prix.
* @return Le montant HT de la ligne.
*/
public BigDecimal computeAmount(BigDecimal quantity, BigDecimal price) {
BigDecimal amount =
quantity
.multiply(price)
.setScale(AppBaseService.DEFAULT_NB_DECIMAL_DIGITS, RoundingMode.HALF_EVEN);
LOG.debug(
"Calcul du montant HT avec une quantité de {} pour {} : {}",
new Object[] {quantity, price, amount});
return amount;
}
public BigDecimal getAccountingExTaxTotal(BigDecimal exTaxTotal, SaleOrder saleOrder)
throws AxelorException {
return currencyService
.getAmountCurrencyConvertedAtDate(
saleOrder.getCurrency(),
saleOrder.getClientPartner().getCurrency(),
exTaxTotal,
saleOrder.getCreationDate())
.setScale(AppBaseService.DEFAULT_NB_DECIMAL_DIGITS, RoundingMode.HALF_UP);
}
}

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.sale.service.saleorder;
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.sale.db.SaleOrder;
import com.axelor.exception.AxelorException;
import com.google.inject.persist.Transactional;
public interface SaleOrderWorkflowService {
@Transactional
public Partner validateCustomer(SaleOrder saleOrder);
public String getSequence(Company company) throws AxelorException;
public void cancelSaleOrder(
SaleOrder saleOrder, CancelReason cancelReason, String cancelReasonStr);
public void finalizeQuotation(SaleOrder saleOrder) throws AxelorException;
public void confirmSaleOrder(SaleOrder saleOrder) throws AxelorException;
public void saveSaleOrderPDFAsAttachment(SaleOrder saleOrder) throws AxelorException;
public String getFileName(SaleOrder saleOrder);
}

View File

@ -0,0 +1,241 @@
/*
* 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.sale.service.saleorder;
import com.axelor.apps.ReportFactory;
import com.axelor.apps.base.db.Blocking;
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.repo.BlockingRepository;
import com.axelor.apps.base.db.repo.PartnerRepository;
import com.axelor.apps.base.db.repo.SequenceRepository;
import com.axelor.apps.base.service.BlockingService;
import com.axelor.apps.base.service.administration.SequenceService;
import com.axelor.apps.base.service.user.UserService;
import com.axelor.apps.crm.db.Opportunity;
import com.axelor.apps.crm.db.repo.OpportunityRepository;
import com.axelor.apps.report.engine.ReportSettings;
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.exception.IExceptionMessage;
import com.axelor.apps.sale.report.IReport;
import com.axelor.apps.sale.service.app.AppSaleService;
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 javax.persistence.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SaleOrderWorkflowServiceImpl implements SaleOrderWorkflowService {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected SequenceService sequenceService;
protected PartnerRepository partnerRepo;
protected SaleOrderRepository saleOrderRepo;
protected AppSaleService appSaleService;
protected UserService userService;
@Inject
public SaleOrderWorkflowServiceImpl(
SequenceService sequenceService,
PartnerRepository partnerRepo,
SaleOrderRepository saleOrderRepo,
AppSaleService appSaleService,
UserService userService) {
this.sequenceService = sequenceService;
this.partnerRepo = partnerRepo;
this.saleOrderRepo = saleOrderRepo;
this.appSaleService = appSaleService;
this.userService = userService;
}
@Override
@Transactional
public Partner validateCustomer(SaleOrder saleOrder) {
Partner clientPartner = partnerRepo.find(saleOrder.getClientPartner().getId());
clientPartner.setIsCustomer(true);
clientPartner.setIsProspect(false);
return partnerRepo.save(clientPartner);
}
@Override
public String getSequence(Company company) throws AxelorException {
String seq = sequenceService.getSequenceNumber(SequenceRepository.SALES_ORDER, company);
if (seq == null) {
throw new AxelorException(
company,
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
I18n.get(IExceptionMessage.SALES_ORDER_1),
company.getName());
}
return seq;
}
@Override
@Transactional
public void cancelSaleOrder(
SaleOrder saleOrder, CancelReason cancelReason, String cancelReasonStr) {
Query q =
JPA.em()
.createQuery(
"select count(*) FROM SaleOrder as self WHERE self.statusSelect in (?1 , ?2) AND self.clientPartner = ?3 ");
q.setParameter(1, SaleOrderRepository.STATUS_ORDER_CONFIRMED);
q.setParameter(2, SaleOrderRepository.STATUS_ORDER_COMPLETED);
q.setParameter(3, saleOrder.getClientPartner());
if ((long) q.getSingleResult() == 0) {
saleOrder.getClientPartner().setIsCustomer(false);
saleOrder.getClientPartner().setIsProspect(true);
}
saleOrder.setStatusSelect(SaleOrderRepository.STATUS_CANCELED);
saleOrder.setCancelReason(cancelReason);
if (Strings.isNullOrEmpty(cancelReasonStr)) {
saleOrder.setCancelReasonStr(cancelReason.getName());
} else {
saleOrder.setCancelReasonStr(cancelReasonStr);
}
saleOrderRepo.save(saleOrder);
}
@Override
@Transactional(
rollbackOn = {Exception.class},
ignore = {BlockedSaleOrderException.class}
)
public void finalizeQuotation(SaleOrder saleOrder) throws AxelorException {
Partner partner = saleOrder.getClientPartner();
Blocking blocking =
Beans.get(BlockingService.class)
.getBlocking(partner, saleOrder.getCompany(), BlockingRepository.SALE_BLOCKING);
if (blocking != null && saleOrder.getInTaxTotal().compareTo(blocking.getMaxAmount()) > 0) {
System.out.println("************************************");
System.out.println(blocking);
System.out.println(saleOrder.getInTaxTotal().compareTo(blocking.getMaxAmount()) > 0);
System.out.println("************************************");
saleOrder.setBlockedOnCustCreditExceed(true);
// if (!saleOrder.getManualUnblock()) {
saleOrderRepo.save(saleOrder);
String reason =
blocking.getBlockingReason() != null ? blocking.getBlockingReason().getName() : "";
throw new BlockedSaleOrderException(
partner, I18n.get("Client is sale blocked:") + " " + reason);
// }
}
if (saleOrder.getVersionNumber() == 1
&& sequenceService.isEmptyOrDraftSequenceNumber(saleOrder.getSaleOrderSeq())) {
saleOrder.setSaleOrderSeq(this.getSequence(saleOrder.getCompany()));
}
saleOrder.setStatusSelect(SaleOrderRepository.STATUS_FINALIZED_QUOTATION);
if (appSaleService.getAppSale().getPrintingOnSOFinalization()) {
this.saveSaleOrderPDFAsAttachment(saleOrder);
}
saleOrderRepo.save(saleOrder);
}
@Override
@Transactional(rollbackOn = {Exception.class})
public void confirmSaleOrder(SaleOrder saleOrder) throws AxelorException {
saleOrder.setStatusSelect(SaleOrderRepository.STATUS_ORDER_CONFIRMED);
saleOrder.setConfirmationDateTime(appSaleService.getTodayDateTime().toLocalDateTime());
saleOrder.setConfirmedByUser(userService.getUser());
this.validateCustomer(saleOrder);
if (appSaleService.getAppSale().getCloseOpportunityUponSaleOrderConfirmation()) {
Opportunity opportunity = saleOrder.getOpportunity();
if (opportunity != null) {
opportunity.setSalesStageSelect(OpportunityRepository.SALES_STAGE_CLOSED_WON);
}
}
saleOrderRepo.save(saleOrder);
}
@Transactional
public void completeSaleOrder(SaleOrder saleOrder) throws AxelorException {
saleOrder.setStatusSelect(SaleOrderRepository.STATUS_ORDER_COMPLETED);
saleOrder.setOrderBeingEdited(false);
saleOrderRepo.save(saleOrder);
}
@Override
public void saveSaleOrderPDFAsAttachment(SaleOrder saleOrder) throws AxelorException {
if (saleOrder.getPrintingSettings() == null) {
if (saleOrder.getCompany().getPrintingSettings() != null) {
saleOrder.setPrintingSettings(saleOrder.getCompany().getPrintingSettings());
} else {
throw new AxelorException(
TraceBackRepository.CATEGORY_MISSING_FIELD,
String.format(
I18n.get(IExceptionMessage.SALE_ORDER_MISSING_PRINTING_SETTINGS),
saleOrder.getSaleOrderSeq()),
saleOrder);
}
}
ReportFactory.createReport(IReport.SALES_ORDER, this.getFileName(saleOrder) + "-${date}")
.addParam("Locale", ReportSettings.getPrintingLocale(saleOrder.getClientPartner()))
.addParam("SaleOrderId", saleOrder.getId())
.addParam("HeaderHeight", saleOrder.getPrintingSettings().getPdfHeaderHeight())
.addParam("FooterHeight", saleOrder.getPrintingSettings().getPdfFooterHeight())
.toAttach(saleOrder)
.generate()
.getFileLink();
// String relatedModel = generalService.getPersistentClass(saleOrder).getCanonicalName();
// required ?
}
@Override
public String getFileName(SaleOrder saleOrder) {
String fileNamePrefix;
if (saleOrder.getStatusSelect() == SaleOrderRepository.STATUS_DRAFT_QUOTATION
|| saleOrder.getStatusSelect() == SaleOrderRepository.STATUS_FINALIZED_QUOTATION) {
fileNamePrefix = "Sale quotation";
} else {
fileNamePrefix = "Sale order";
}
return I18n.get(fileNamePrefix)
+ " "
+ saleOrder.getSaleOrderSeq()
+ ((saleOrder.getVersionNumber() > 1) ? "-V" + saleOrder.getVersionNumber() : "");
}
}

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.sale.service.saleorder.print;
import com.axelor.apps.report.engine.ReportSettings;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.exception.AxelorException;
import java.io.File;
import java.io.IOException;
import java.util.List;
public interface SaleOrderPrintService {
/**
* Print a list of sale orders in the same output.
*
* @param ids ids of the sale order.
* @return the link to the generated file.
* @throws IOException
*/
String printSaleOrders(List<Long> ids) throws IOException;
ReportSettings prepareReportSettings(SaleOrder saleOrder, boolean proforma, String format)
throws AxelorException;
File print(SaleOrder saleOrder, boolean proforma, String format) throws AxelorException;
String printSaleOrder(SaleOrder saleOrder, boolean proforma, String format)
throws AxelorException, IOException;
}

View File

@ -0,0 +1,128 @@
/*
* 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.sale.service.saleorder.print;
import com.axelor.apps.ReportFactory;
import com.axelor.apps.base.service.ConvertNumberToFrenchWordsService;
import com.axelor.apps.base.service.app.AppBaseService;
import com.axelor.apps.report.engine.ReportSettings;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.exception.IExceptionMessage;
import com.axelor.apps.sale.report.IReport;
import com.axelor.apps.sale.service.saleorder.SaleOrderService;
import com.axelor.apps.tool.ModelTool;
import com.axelor.apps.tool.ThrowConsumer;
import com.axelor.apps.tool.file.PdfTool;
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.io.File;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
public class SaleOrderPrintServiceImpl implements SaleOrderPrintService {
@Inject SaleOrderService saleOrderService;
@Override
public String printSaleOrder(SaleOrder saleOrder, boolean proforma, String format)
throws AxelorException, IOException {
String fileName = getSaleOrderFilesName(false, format);
return PdfTool.getFileLinkFromPdfFile(print(saleOrder, proforma, format), fileName);
}
@Override
public String printSaleOrders(List<Long> ids) throws IOException {
List<File> printedSaleOrders = new ArrayList<>();
ModelTool.apply(
SaleOrder.class,
ids,
new ThrowConsumer<SaleOrder>() {
@Override
public void accept(SaleOrder saleOrder) throws Exception {
printedSaleOrders.add(print(saleOrder, false, ReportSettings.FORMAT_PDF));
}
});
String fileName = getSaleOrderFilesName(true, ReportSettings.FORMAT_PDF);
return PdfTool.mergePdfToFileLink(printedSaleOrders, fileName);
}
public File print(SaleOrder saleOrder, boolean proforma, String format) throws AxelorException {
ReportSettings reportSettings = prepareReportSettings(saleOrder, proforma, format);
return reportSettings.generate().getFile();
}
@Override
public ReportSettings prepareReportSettings(SaleOrder saleOrder, boolean proforma, String format)
throws AxelorException {
if (saleOrder.getPrintingSettings() == null) {
if (saleOrder.getCompany().getPrintingSettings() != null) {
saleOrder.setPrintingSettings(saleOrder.getCompany().getPrintingSettings());
} else {
throw new AxelorException(
TraceBackRepository.CATEGORY_MISSING_FIELD,
String.format(
I18n.get(IExceptionMessage.SALE_ORDER_MISSING_PRINTING_SETTINGS),
saleOrder.getSaleOrderSeq()),
saleOrder);
}
}
String locale = ReportSettings.getPrintingLocale(saleOrder.getClientPartner());
String title = saleOrderService.getFileName(saleOrder);
ReportSettings reportSetting =
ReportFactory.createReport(IReport.SALES_ORDER, title + " - ${date}");
String[] arrOfStr = saleOrder.getInTaxTotal().toString().split("\\.");
String left =
Beans.get(ConvertNumberToFrenchWordsService.class).convert(Long.parseLong(arrOfStr[0]));
String right =
Beans.get(ConvertNumberToFrenchWordsService.class).convert(Long.parseLong(arrOfStr[1]));
String number = left + " dinars algériens et " + right + " Cts";
return reportSetting
.addParam("NumberToWords", number)
.addParam("SaleOrderId", saleOrder.getId())
.addParam("Locale", locale)
.addParam("ProformaInvoice", proforma)
.addParam("HeaderHeight", saleOrder.getPrintingSettings().getPdfHeaderHeight())
.addParam("FooterHeight", saleOrder.getPrintingSettings().getPdfFooterHeight())
.addFormat(format);
}
/**
* Return the name for the printed sale order.
*
* @param plural if there is one or multiple sale orders.
*/
protected String getSaleOrderFilesName(boolean plural, String format) {
return I18n.get(plural ? "Sale orders" : "Sale order")
+ " - "
+ Beans.get(AppBaseService.class).getTodayDate().format(DateTimeFormatter.BASIC_ISO_DATE)
+ "."
+ format;
}
}

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.sale.translation;
public interface ITranslation {
public static final String SALE_APP_NAME = /*$$(*/ "value:Sale"; /*)*/
public static final String ABC_ANALYSIS_START_DATE = /*$$(*/ "AbcAnalysis.startDate"; /*)*/
public static final String ABC_ANALYSIS_END_DATE = /*$$(*/ "AbcAnalysis.endDate"; /*)*/
}

View File

@ -0,0 +1,41 @@
/*
* 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.sale.web;
import com.axelor.apps.sale.db.AdvancePayment;
import com.axelor.apps.sale.db.repo.AdvancePaymentRepository;
import com.axelor.apps.sale.service.AdvancePaymentService;
import com.axelor.exception.AxelorException;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.google.inject.Singleton;
@Singleton
public class AdvancePaymentController {
public void cancelAdvancePayment(ActionRequest request, ActionResponse response)
throws AxelorException {
AdvancePayment advancePayment = request.getContext().asType(AdvancePayment.class);
advancePayment = Beans.get(AdvancePaymentRepository.class).find(advancePayment.getId());
Beans.get(AdvancePaymentService.class).cancelAdvancePayment(advancePayment);
response.setReload(true);
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.sale.web;
import com.axelor.apps.sale.service.app.AppSaleService;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.google.inject.Singleton;
@Singleton
public class AppSaleController {
public void generateSaleConfigurations(ActionRequest request, ActionResponse response) {
Beans.get(AppSaleService.class).generateSaleConfigurations();
response.setReload(true);
}
}

View File

@ -0,0 +1,128 @@
/*
* 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.sale.web;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.sale.db.Configurator;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.repo.ConfiguratorRepository;
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
import com.axelor.apps.sale.service.configurator.ConfiguratorCreatorService;
import com.axelor.apps.sale.service.configurator.ConfiguratorService;
import com.axelor.exception.service.TraceBackService;
import com.axelor.i18n.I18n;
import com.axelor.inject.Beans;
import com.axelor.meta.schema.actions.ActionView;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.axelor.rpc.JsonContext;
import com.google.inject.Singleton;
@Singleton
public class ConfiguratorController {
/**
* Called from configurator form view, set values for the indicators JSON field. call {@link
* ConfiguratorService#updateIndicators(Configurator, JsonContext, JsonContext)}
*
* @param request
* @param response
*/
public void updateIndicators(ActionRequest request, ActionResponse response) {
Configurator configurator = request.getContext().asType(Configurator.class);
JsonContext jsonAttributes = (JsonContext) request.getContext().get("$attributes");
JsonContext jsonIndicators = (JsonContext) request.getContext().get("$indicators");
configurator = Beans.get(ConfiguratorRepository.class).find(configurator.getId());
try {
Beans.get(ConfiguratorService.class)
.updateIndicators(configurator, jsonAttributes, jsonIndicators);
response.setValue("indicators", request.getContext().get("indicators"));
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
/**
* Called from configurator form view, call {@link
* ConfiguratorService#generateProduct(Configurator, JsonContext, JsonContext)}
*
* @param request
* @param response
*/
public void generateProduct(ActionRequest request, ActionResponse response) {
Configurator configurator = request.getContext().asType(Configurator.class);
JsonContext jsonAttributes = (JsonContext) request.getContext().get("$attributes");
JsonContext jsonIndicators = (JsonContext) request.getContext().get("$indicators");
configurator = Beans.get(ConfiguratorRepository.class).find(configurator.getId());
try {
Beans.get(ConfiguratorService.class).generate(configurator, jsonAttributes, jsonIndicators);
response.setReload(true);
if (configurator.getProduct() != null) {
response.setView(
ActionView.define(I18n.get("Product generated"))
.model(Product.class.getName())
.add("form", "product-form")
.add("grid", "product-grid")
.context("_showRecord", configurator.getProduct().getId())
.map());
}
} catch (Exception e) {
TraceBackService.trace(e);
response.setError(e.getMessage());
}
}
/**
* Called from configurator wizard form view, call {@link
* ConfiguratorService#addLineToSaleOrder(Configurator, SaleOrder, JsonContext, JsonContext)}
*
* @param request
* @param response
*/
public void generateForSaleOrder(ActionRequest request, ActionResponse response) {
Configurator configurator = request.getContext().asType(Configurator.class);
long saleOrderId = ((Integer) request.getContext().get("_saleOrderId")).longValue();
JsonContext jsonAttributes = (JsonContext) request.getContext().get("$attributes");
JsonContext jsonIndicators = (JsonContext) request.getContext().get("$indicators");
configurator = Beans.get(ConfiguratorRepository.class).find(configurator.getId());
SaleOrder saleOrder = Beans.get(SaleOrderRepository.class).find(saleOrderId);
try {
Beans.get(ConfiguratorService.class)
.addLineToSaleOrder(configurator, saleOrder, jsonAttributes, jsonIndicators);
response.setCanClose(true);
} catch (Exception e) {
TraceBackService.trace(response, e);
response.setReload(true);
}
}
/**
* Called from configurator view on selecting {@link Configurator#configuratorCreator}.
*
* @param request
* @param response
*/
public void createDomainForCreator(ActionRequest request, ActionResponse response) {
response.setAttr(
"configuratorCreator",
"domain",
Beans.get(ConfiguratorCreatorService.class).getConfiguratorCreatorDomain());
}
}

View File

@ -0,0 +1,96 @@
/*
* 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.sale.web;
import com.axelor.apps.sale.db.ConfiguratorCreator;
import com.axelor.apps.sale.db.repo.ConfiguratorCreatorRepository;
import com.axelor.apps.sale.service.configurator.ConfiguratorCreatorImportService;
import com.axelor.apps.sale.service.configurator.ConfiguratorCreatorService;
import com.axelor.auth.AuthUtils;
import com.axelor.auth.db.User;
import com.axelor.exception.service.TraceBackService;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.google.inject.Singleton;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class ConfiguratorCreatorController {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* Called from the configurator creator form on formula changes
*
* @param request
* @param response
*/
public void updateAndActivate(ActionRequest request, ActionResponse response) {
ConfiguratorCreator creator = request.getContext().asType(ConfiguratorCreator.class);
ConfiguratorCreatorService configuratorCreatorService =
Beans.get(ConfiguratorCreatorService.class);
creator = Beans.get(ConfiguratorCreatorRepository.class).find(creator.getId());
configuratorCreatorService.updateAttributes(creator);
configuratorCreatorService.updateIndicators(creator);
configuratorCreatorService.activate(creator);
response.setSignal("refresh-app", true);
}
/**
* Called from the configurator creator form on new
*
* @param request
* @param response
*/
public void configure(ActionRequest request, ActionResponse response) {
ConfiguratorCreator creator = request.getContext().asType(ConfiguratorCreator.class);
ConfiguratorCreatorService configuratorCreatorService =
Beans.get(ConfiguratorCreatorService.class);
creator = Beans.get(ConfiguratorCreatorRepository.class).find(creator.getId());
User currentUser = AuthUtils.getUser();
configuratorCreatorService.authorizeUser(creator, currentUser);
try {
configuratorCreatorService.addRequiredFormulas(creator);
} catch (Exception e) {
TraceBackService.trace(e);
response.setError(e.getMessage());
}
response.setReload(true);
}
/**
* Called from configurator creator grid view, on clicking import button. Call {@link
* ConfiguratorCreatorService#importConfiguratorCreators(String)}.
*
* @param request
* @param response
*/
public void importConfiguratorCreators(ActionRequest request, ActionResponse response) {
try {
String pathDiff = (String) ((Map) request.getContext().get("dataFile")).get("filePath");
String importLog =
Beans.get(ConfiguratorCreatorImportService.class).importConfiguratorCreators(pathDiff);
response.setValue("importLog", importLog);
} catch (Exception e) {
TraceBackService.trace(e);
}
}
}

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.sale.web;
import com.axelor.apps.sale.db.ConfiguratorCreator;
import com.axelor.apps.sale.db.ConfiguratorFormula;
import com.axelor.apps.sale.exception.IExceptionMessage;
import com.axelor.apps.sale.service.configurator.ConfiguratorFormulaService;
import com.axelor.i18n.I18n;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.google.inject.Singleton;
@Singleton
public class ConfiguratorFormulaController {
/**
* Check the groovy script in the context
*
* @param request
* @param response
*/
public void checkGroovyFormula(ActionRequest request, ActionResponse response) {
ConfiguratorFormula configuratorFormula =
request.getContext().asType(ConfiguratorFormula.class);
ConfiguratorCreator creator =
request.getContext().getParent().asType(ConfiguratorCreator.class);
try {
Beans.get(ConfiguratorFormulaService.class).checkFormula(configuratorFormula, creator);
response.setAlert(I18n.get(IExceptionMessage.CONFIGURATOR_CREATOR_SCRIPT_WORKING));
} catch (Exception e) {
response.setError(e.getMessage());
}
}
}

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.sale.web;
import com.axelor.apps.crm.db.Opportunity;
import com.axelor.apps.crm.db.repo.OpportunityRepository;
import com.axelor.apps.crm.translation.ITranslation;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
import com.axelor.apps.sale.service.saleorder.OpportunitySaleOrderService;
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowService;
import com.axelor.exception.AxelorException;
import com.axelor.i18n.I18n;
import com.axelor.inject.Beans;
import com.axelor.meta.schema.actions.ActionView;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.google.inject.Singleton;
import java.util.List;
@Singleton
public class OpportunitySaleOrderController {
public void generateSaleOrder(ActionRequest request, ActionResponse response)
throws AxelorException {
Opportunity opportunity = request.getContext().asType(Opportunity.class);
opportunity = Beans.get(OpportunityRepository.class).find(opportunity.getId());
SaleOrder saleOrder =
Beans.get(OpportunitySaleOrderService.class).createSaleOrderFromOpportunity(opportunity);
response.setReload(true);
response.setView(
ActionView.define(I18n.get(ITranslation.SALE_QUOTATION))
.model(SaleOrder.class.getName())
.add("form", "sale-order-form")
.param("forceEdit", "true")
.param("forceTitle", "true")
.context("_showRecord", String.valueOf(saleOrder.getId()))
.map());
}
public void cancelSaleOrders(ActionRequest request, ActionResponse response) {
Opportunity opportunity = request.getContext().asType(Opportunity.class);
SaleOrderWorkflowService saleOrderWorkflowService = Beans.get(SaleOrderWorkflowService.class);
if (opportunity.getSalesStageSelect() == OpportunityRepository.SALES_STAGE_CLOSED_LOST) {
List<SaleOrder> saleOrderList = opportunity.getSaleOrderList();
if (saleOrderList != null && !saleOrderList.isEmpty()) {
for (SaleOrder saleOrder : saleOrderList) {
if (saleOrder.getStatusSelect() == SaleOrderRepository.STATUS_DRAFT_QUOTATION
|| saleOrder.getStatusSelect() == SaleOrderRepository.STATUS_FINALIZED_QUOTATION) {
saleOrderWorkflowService.cancelSaleOrder(saleOrder, null, opportunity.getName());
}
}
}
}
}
}

View File

@ -0,0 +1,734 @@
/*
* 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.sale.web;
import com.axelor.apps.account.db.PaymentMode;
import com.axelor.apps.base.db.BankDetails;
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.PrintingSettings;
import com.axelor.apps.base.db.Wizard;
import com.axelor.apps.base.db.repo.CurrencyRepository;
import com.axelor.apps.base.db.repo.PartnerRepository;
import com.axelor.apps.base.db.repo.PriceListRepository;
import com.axelor.apps.base.service.BankDetailsService;
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.report.engine.ReportSettings;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
import com.axelor.apps.sale.exception.IExceptionMessage;
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeService;
import com.axelor.apps.sale.service.saleorder.SaleOrderCreateService;
import com.axelor.apps.sale.service.saleorder.SaleOrderMarginService;
import com.axelor.apps.sale.service.saleorder.SaleOrderService;
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowService;
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowServiceImpl;
import com.axelor.apps.sale.service.saleorder.print.SaleOrderPrintService;
import com.axelor.apps.tool.StringTool;
import com.axelor.common.ObjectUtils;
import com.axelor.db.JPA;
import com.axelor.db.mapper.Mapper;
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.axelor.meta.schema.actions.ActionView;
import com.axelor.meta.schema.actions.ActionView.ActionViewBuilder;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.axelor.rpc.Context;
import com.axelor.team.db.Team;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.inject.Singleton;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.eclipse.birt.core.exception.BirtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class SaleOrderController {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public void compute(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
try {
saleOrder = Beans.get(SaleOrderComputeService.class).computeSaleOrder(saleOrder);
response.setValues(saleOrder);
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
public void computeMargin(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
try {
Beans.get(SaleOrderMarginService.class).computeMarginSaleOrder(saleOrder);
response.setValue("accountedRevenue", saleOrder.getAccountedRevenue());
response.setValue("totalCostPrice", saleOrder.getTotalCostPrice());
response.setValue("totalGrossMargin", saleOrder.getTotalGrossMargin());
response.setValue("marginRate", saleOrder.getMarginRate());
response.setValue("markup", saleOrder.getMarkup());
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
/**
* Method that print the sale order as a Pdf
*
* @param request
* @param response
* @return
* @throws BirtException
* @throws IOException
*/
public void showSaleOrder(ActionRequest request, ActionResponse response) throws AxelorException {
this.exportSaleOrder(request, response, false, ReportSettings.FORMAT_PDF);
}
/** Method that prints a proforma invoice as a PDF */
public void printProformaInvoice(ActionRequest request, ActionResponse response)
throws AxelorException {
this.exportSaleOrder(request, response, true, ReportSettings.FORMAT_PDF);
}
public void exportSaleOrderExcel(ActionRequest request, ActionResponse response)
throws AxelorException {
this.exportSaleOrder(request, response, false, ReportSettings.FORMAT_XLS);
}
public void exportSaleOrderWord(ActionRequest request, ActionResponse response)
throws AxelorException {
this.exportSaleOrder(request, response, false, ReportSettings.FORMAT_DOC);
}
@SuppressWarnings("unchecked")
public void exportSaleOrder(
ActionRequest request, ActionResponse response, boolean proforma, String format) {
Context context = request.getContext();
String fileLink;
String title;
SaleOrderPrintService saleOrderPrintService = Beans.get(SaleOrderPrintService.class);
try {
if (!ObjectUtils.isEmpty(request.getContext().get("_ids"))) {
List<Long> ids =
Lists.transform(
(List) request.getContext().get("_ids"),
new Function<Object, Long>() {
@Nullable
@Override
public Long apply(@Nullable Object input) {
return Long.parseLong(input.toString());
}
});
fileLink = saleOrderPrintService.printSaleOrders(ids);
title = I18n.get("Sale orders");
} else if (context.get("id") != null) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
title = Beans.get(SaleOrderService.class).getFileName(saleOrder);
fileLink = saleOrderPrintService.printSaleOrder(saleOrder, proforma, format);
logger.debug("Printing " + title);
} else {
throw new AxelorException(
TraceBackRepository.CATEGORY_MISSING_FIELD,
I18n.get(IExceptionMessage.SALE_ORDER_PRINT));
}
response.setView(ActionView.define(title).add("html", fileLink).map());
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
public void cancelSaleOrder(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
Beans.get(SaleOrderWorkflowService.class)
.cancelSaleOrder(
Beans.get(SaleOrderRepository.class).find(saleOrder.getId()),
saleOrder.getCancelReason(),
saleOrder.getCancelReasonStr());
response.setFlash(I18n.get("The sale order was canceled"));
response.setCanClose(true);
}
public void finalizeQuotation(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
saleOrder = Beans.get(SaleOrderRepository.class).find(saleOrder.getId());
try {
Beans.get(SaleOrderWorkflowService.class).finalizeQuotation(saleOrder);
} catch (Exception e) {
TraceBackService.trace(response, e);
}
response.setReload(true);
}
public void completeSaleOrder(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
saleOrder = Beans.get(SaleOrderRepository.class).find(saleOrder.getId());
try {
Beans.get(SaleOrderWorkflowServiceImpl.class).completeSaleOrder(saleOrder);
} catch (Exception e) {
TraceBackService.trace(response, e);
}
response.setReload(true);
}
public void confirmSaleOrder(ActionRequest request, ActionResponse response) {
try {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
Beans.get(SaleOrderWorkflowService.class)
.confirmSaleOrder(Beans.get(SaleOrderRepository.class).find(saleOrder.getId()));
response.setReload(true);
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
@SuppressWarnings("unchecked")
public void generateViewSaleOrder(ActionRequest request, ActionResponse response) {
LinkedHashMap<String, Object> saleOrderTemplateContext =
(LinkedHashMap<String, Object>) request.getContext().get("_saleOrderTemplate");
Integer saleOrderId = (Integer) saleOrderTemplateContext.get("id");
SaleOrder context = Beans.get(SaleOrderRepository.class).find(Long.valueOf(saleOrderId));
response.setView(
ActionView.define("Sale order")
.model(SaleOrder.class.getName())
.add("form", "sale-order-form-wizard")
.context("_idCopy", context.getId().toString())
.context("_wizardCurrency", request.getContext().get("currency"))
.context("_wizardPriceList", request.getContext().get("priceList"))
.map());
response.setCanClose(true);
}
public void generateViewTemplate(ActionRequest request, ActionResponse response) {
SaleOrder context = request.getContext().asType(SaleOrder.class);
response.setView(
ActionView.define("Template")
.model(SaleOrder.class.getName())
.add("form", "sale-order-template-form-wizard")
.context("_idCopy", context.getId().toString())
.map());
}
public void generateSaleOrderWizard(ActionRequest request, ActionResponse response) {
SaleOrder saleOrderTemplate = request.getContext().asType(SaleOrder.class);
Partner clientPartner = saleOrderTemplate.getClientPartner();
response.setView(
ActionView.define("Create the quotation")
.model(Wizard.class.getName())
.add("form", "sale-order-template-wizard-form")
.param("popup", "reload")
.param("show-toolbar", "false")
.param("show-confirm", "false")
.param("width", "large")
.param("popup-save", "false")
.context("_saleOrderTemplate", saleOrderTemplate)
.context("_clientPartnerCurrency", clientPartner.getCurrency())
.map());
}
@SuppressWarnings("unchecked")
public void createSaleOrder(ActionRequest request, ActionResponse response)
throws AxelorException {
SaleOrder origin =
Beans.get(SaleOrderRepository.class)
.find(Long.parseLong(request.getContext().get("_idCopy").toString()));
if (origin != null) {
LinkedHashMap<String, Object> wizardCurrencyContext =
(LinkedHashMap<String, Object>) request.getContext().get("_wizardCurrency");
Integer wizardCurrencyId = (Integer) wizardCurrencyContext.get("id");
Currency wizardCurrency =
Beans.get(CurrencyRepository.class).find(Long.valueOf(wizardCurrencyId));
PriceList wizardPriceList = null;
if (request.getContext().get("_wizardPriceList") != null) {
LinkedHashMap<String, Object> wizardPriceListContext =
(LinkedHashMap<String, Object>) request.getContext().get("_wizardPriceList");
Integer wizardPriceListId = (Integer) wizardPriceListContext.get("id");
wizardPriceList =
Beans.get(PriceListRepository.class).find(Long.valueOf(wizardPriceListId));
}
SaleOrder copy =
Beans.get(SaleOrderCreateService.class)
.createSaleOrder(origin, wizardCurrency, wizardPriceList);
response.setValues(Mapper.toMap(copy));
}
}
public void createTemplate(ActionRequest request, ActionResponse response) {
Context context = request.getContext();
if (context.get("_idCopy") != null) {
String idCopy = context.get("_idCopy").toString();
SaleOrder origin = Beans.get(SaleOrderRepository.class).find(Long.parseLong(idCopy));
SaleOrder copy = Beans.get(SaleOrderCreateService.class).createTemplate(origin);
response.setValues(Mapper.toMap(copy));
}
}
public void computeEndOfValidityDate(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
try {
saleOrder = Beans.get(SaleOrderService.class).computeEndOfValidityDate(saleOrder);
response.setValue("endOfValidityDate", saleOrder.getEndOfValidityDate());
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
public void mergeSaleOrder(ActionRequest request, ActionResponse response) {
List<SaleOrder> saleOrderList = new ArrayList<SaleOrder>();
List<Long> saleOrderIdList = new ArrayList<Long>();
boolean fromPopup = false;
String lineToMerge;
if (request.getContext().get("saleQuotationToMerge") != null) {
lineToMerge = "saleQuotationToMerge";
} else {
lineToMerge = "saleOrderToMerge";
}
if (request.getContext().get(lineToMerge) != null) {
if (request.getContext().get(lineToMerge) instanceof List) {
// No confirmation popup, sale orders are content in a parameter list
List<Map> saleOrderMap = (List<Map>) request.getContext().get(lineToMerge);
for (Map map : saleOrderMap) {
saleOrderIdList.add(new Long((Integer) map.get("id")));
}
} else {
// After confirmation popup, sale order's id are in a string separated by ","
String saleOrderIdListStr = (String) request.getContext().get(lineToMerge);
for (String saleOrderId : saleOrderIdListStr.split(",")) {
saleOrderIdList.add(new Long(saleOrderId));
}
fromPopup = true;
}
}
// Check if currency, clientPartner and company are the same for all selected sale orders
Currency commonCurrency = null;
Partner commonClientPartner = null;
Company commonCompany = null;
Partner commonContactPartner = null;
Team commonTeam = null;
// Useful to determine if a difference exists between teams of all sale orders
boolean existTeamDiff = false;
// Useful to determine if a difference exists between contact partners of all sale orders
boolean existContactPartnerDiff = false;
PriceList commonPriceList = null;
// Useful to determine if a difference exists between price lists of all sale orders
boolean existPriceListDiff = false;
SaleOrder saleOrderTemp;
int count = 1;
for (Long saleOrderId : saleOrderIdList) {
saleOrderTemp = JPA.em().find(SaleOrder.class, saleOrderId);
saleOrderList.add(saleOrderTemp);
if (count == 1) {
commonCurrency = saleOrderTemp.getCurrency();
commonClientPartner = saleOrderTemp.getClientPartner();
commonCompany = saleOrderTemp.getCompany();
commonContactPartner = saleOrderTemp.getContactPartner();
commonTeam = saleOrderTemp.getTeam();
commonPriceList = saleOrderTemp.getPriceList();
} else {
if (commonCurrency != null && !commonCurrency.equals(saleOrderTemp.getCurrency())) {
commonCurrency = null;
}
if (commonClientPartner != null
&& !commonClientPartner.equals(saleOrderTemp.getClientPartner())) {
commonClientPartner = null;
}
if (commonCompany != null && !commonCompany.equals(saleOrderTemp.getCompany())) {
commonCompany = null;
}
if (commonContactPartner != null
&& !commonContactPartner.equals(saleOrderTemp.getContactPartner())) {
commonContactPartner = null;
existContactPartnerDiff = true;
}
if (commonTeam != null && !commonTeam.equals(saleOrderTemp.getTeam())) {
commonTeam = null;
existTeamDiff = true;
}
if (commonPriceList != null && !commonPriceList.equals(saleOrderTemp.getPriceList())) {
commonPriceList = null;
existPriceListDiff = true;
}
}
count++;
}
StringBuilder fieldErrors = new StringBuilder();
if (commonCurrency == null) {
fieldErrors.append(I18n.get(IExceptionMessage.SALE_ORDER_MERGE_ERROR_CURRENCY));
}
if (commonClientPartner == null) {
if (fieldErrors.length() > 0) {
fieldErrors.append("<br/>");
}
fieldErrors.append(I18n.get(IExceptionMessage.SALE_ORDER_MERGE_ERROR_CLIENT_PARTNER));
}
if (commonCompany == null) {
if (fieldErrors.length() > 0) {
fieldErrors.append("<br/>");
}
fieldErrors.append(I18n.get(IExceptionMessage.SALE_ORDER_MERGE_ERROR_COMPANY));
}
if (fieldErrors.length() > 0) {
response.setFlash(fieldErrors.toString());
return;
}
// Check if priceList or contactPartner are content in parameters
if (request.getContext().get("priceList") != null) {
commonPriceList =
JPA.em()
.find(
PriceList.class,
new Long((Integer) ((Map) request.getContext().get("priceList")).get("id")));
}
if (request.getContext().get("contactPartner") != null) {
commonContactPartner =
JPA.em()
.find(
Partner.class,
new Long((Integer) ((Map) request.getContext().get("contactPartner")).get("id")));
}
if (request.getContext().get("team") != null) {
commonTeam =
JPA.em()
.find(
Team.class,
new Long((Integer) ((Map) request.getContext().get("team")).get("id")));
}
if (!fromPopup && (existContactPartnerDiff || existPriceListDiff || existTeamDiff)) {
// Need to display intermediate screen to select some values
ActionViewBuilder confirmView =
ActionView.define("Confirm merge sale order")
.model(Wizard.class.getName())
.add("form", "sale-order-merge-confirm-form")
.param("popup", "true")
.param("show-toolbar", "false")
.param("show-confirm", "false")
.param("popup-save", "false")
.param("forceEdit", "true");
if (existPriceListDiff) {
confirmView.context("contextPriceListToCheck", "true");
}
if (existContactPartnerDiff) {
confirmView.context("contextContactPartnerToCheck", "true");
confirmView.context("contextPartnerId", commonClientPartner.getId().toString());
}
if (existTeamDiff) {
confirmView.context("contextTeamToCheck", "true");
}
confirmView.context(lineToMerge, Joiner.on(",").join(saleOrderIdList));
response.setView(confirmView.map());
return;
}
try {
SaleOrder saleOrder =
Beans.get(SaleOrderCreateService.class)
.mergeSaleOrders(
saleOrderList,
commonCurrency,
commonClientPartner,
commonCompany,
commonContactPartner,
commonPriceList,
commonTeam);
if (saleOrder != null) {
// Open the generated sale order in a new tab
response.setView(
ActionView.define("Sale order")
.model(SaleOrder.class.getName())
.add("grid", "sale-order-grid")
.add("form", "sale-order-form")
.param("forceEdit", "true")
.context("_showRecord", String.valueOf(saleOrder.getId()))
.map());
response.setCanClose(true);
}
} catch (Exception e) {
response.setFlash(e.getLocalizedMessage());
}
}
/**
* Set the address string with their values.
*
* @param request
* @param response
*/
public void computeAddressStr(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
Beans.get(SaleOrderService.class).computeAddressStr(saleOrder);
response.setValues(saleOrder);
}
/**
* Called on partner, company or payment change. Fill the bank details with a default value.
*
* @param request
* @param response
* @throws AxelorException
*/
public void fillCompanyBankDetails(ActionRequest request, ActionResponse response)
throws AxelorException {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
PaymentMode paymentMode = (PaymentMode) request.getContext().get("paymentMode");
Company company = saleOrder.getCompany();
Partner partner = saleOrder.getClientPartner();
if (company == null) {
return;
}
if (partner != null) {
partner = Beans.get(PartnerRepository.class).find(partner.getId());
}
BankDetails defaultBankDetails =
Beans.get(BankDetailsService.class)
.getDefaultCompanyBankDetails(company, paymentMode, partner, null);
response.setValue("companyBankDetails", defaultBankDetails);
}
public void enableEditOrder(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder =
Beans.get(SaleOrderRepository.class)
.find(request.getContext().asType(SaleOrder.class).getId());
try {
boolean checkAvailabiltyRequest =
Beans.get(SaleOrderService.class).enableEditOrder(saleOrder);
response.setReload(true);
if (checkAvailabiltyRequest) {
response.setNotify(I18n.get(IExceptionMessage.SALE_ORDER_EDIT_ORDER_NOTIFY));
}
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
/**
* Called from sale order form view, on clicking validate change button. Call {@link
* SaleOrderService#validateChanges(SaleOrder)}.
*
* @param request
* @param response
*/
public void validateChanges(ActionRequest request, ActionResponse response) {
try {
SaleOrder saleOrderView = request.getContext().asType(SaleOrder.class);
SaleOrder saleOrder = Beans.get(SaleOrderRepository.class).find(saleOrderView.getId());
Beans.get(SaleOrderService.class).validateChanges(saleOrder);
response.setReload(true);
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
/**
* Called on printing settings select. Set the domain for {@link SaleOrder#printingSettings}
*
* @param request
* @param response
*/
public void filterPrintingSettings(ActionRequest request, ActionResponse response) {
try {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
List<PrintingSettings> printingSettingsList =
Beans.get(TradingNameService.class)
.getPrintingSettingsList(saleOrder.getTradingName(), saleOrder.getCompany());
String domain =
String.format(
"self.id IN (%s)",
!printingSettingsList.isEmpty()
? StringTool.getIdListString(printingSettingsList)
: "0");
response.setAttr("printingSettings", "domain", domain);
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
/**
* Called on trading name change. Set the default value for {@link SaleOrder#printingSettings}
*
* @param request
* @param response
*/
public void fillDefaultPrintingSettings(ActionRequest request, ActionResponse response) {
try {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
response.setValue(
"printingSettings",
Beans.get(TradingNameService.class)
.getDefaultPrintingSettings(saleOrder.getTradingName(), saleOrder.getCompany()));
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
/**
* Called from sale order form view on partner change. Get the default price list for the sale
* order. Call {@link PartnerPriceListService#getDefaultPriceList(Partner, int)}.
*
* @param request
* @param response
*/
@SuppressWarnings("unchecked")
public void fillPriceList(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder;
if (request.getContext().get("_saleOrderTemplate") != null) {
LinkedHashMap<String, Object> saleOrderTemplateContext =
(LinkedHashMap<String, Object>) request.getContext().get("_saleOrderTemplate");
Integer saleOrderId = (Integer) saleOrderTemplateContext.get("id");
saleOrder = Beans.get(SaleOrderRepository.class).find(Long.valueOf(saleOrderId));
} else {
saleOrder = request.getContext().asType(SaleOrder.class);
}
response.setValue(
"priceList",
saleOrder.getClientPartner() != null
? Beans.get(PartnerPriceListService.class)
.getDefaultPriceList(saleOrder.getClientPartner(), PriceListRepository.TYPE_SALE)
: null);
}
/**
* Called from sale order view on price list select. Call {@link
* PartnerPriceListService#getPriceListDomain(Partner, int)}.
*
* @param request
* @param response
*/
@SuppressWarnings("unchecked")
public void changePriceListDomain(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder;
if (request.getContext().get("_saleOrderTemplate") != null) {
LinkedHashMap<String, Object> saleOrderTemplateContext =
(LinkedHashMap<String, Object>) request.getContext().get("_saleOrderTemplate");
Integer saleOrderId = (Integer) saleOrderTemplateContext.get("id");
saleOrder = Beans.get(SaleOrderRepository.class).find(Long.valueOf(saleOrderId));
} else {
saleOrder = request.getContext().asType(SaleOrder.class);
}
String domain =
Beans.get(PartnerPriceListService.class)
.getPriceListDomain(saleOrder.getClientPartner(), PriceListRepository.TYPE_SALE);
response.setAttr("priceList", "domain", domain);
}
public void removeSubLines(ActionRequest request, ActionResponse response) {
try {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
response.setValue(
"saleOrderLineList",
Beans.get(SaleOrderComputeService.class)
.removeSubLines(saleOrder.getSaleOrderLineList()));
} catch (Exception e) {
TraceBackService.trace(response, e);
response.setReload(true);
}
}
public void updateSaleOrderLineTax(ActionRequest request, ActionResponse response)
throws AxelorException {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
Beans.get(SaleOrderCreateService.class).updateSaleOrderLineList(saleOrder);
response.setValue("saleOrderLineList", saleOrder.getSaleOrderLineList());
}
public void getSaleOrderPartnerDomain(ActionRequest request, ActionResponse response) {
SaleOrder saleOrder = request.getContext().asType(SaleOrder.class);
Company company = saleOrder.getCompany();
long companyId = company.getPartner().getId();
String domain =
String.format(
"self.id != %d AND self.isContact = false AND (self.isCustomer = true or self.isProspect = true)",
companyId);
domain += " AND :company member of self.companySet";
try {
if (!(saleOrder.getSaleOrderLineList() == null
|| saleOrder.getSaleOrderLineList().isEmpty())) {
domain += Beans.get(PartnerService.class).getPartnerDomain(saleOrder.getClientPartner());
}
} catch (Exception e) {
TraceBackService.trace(e);
response.setError(e.getMessage());
}
response.setAttr("clientPartner", "domain", domain);
}
}

View File

@ -0,0 +1,472 @@
/*
* 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.sale.web;
import com.axelor.apps.account.db.TaxLine;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.base.db.repo.PriceListLineRepository;
import com.axelor.apps.base.db.repo.ProductRepository;
import com.axelor.apps.base.service.app.AppBaseService;
import com.axelor.apps.base.service.tax.FiscalPositionService;
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.SaleOrderLineService;
import com.axelor.db.mapper.Mapper;
import com.axelor.exception.AxelorException;
import com.axelor.exception.service.TraceBackService;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.axelor.rpc.Context;
import com.google.inject.Singleton;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
@Singleton
public class SaleOrderLineController {
public void compute(ActionRequest request, ActionResponse response) {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
SaleOrder saleOrder = Beans.get(SaleOrderLineService.class).getSaleOrder(context);
try {
compute(response, saleOrder, saleOrderLine);
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
public void computeSubMargin(ActionRequest request, ActionResponse response)
throws AxelorException {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
SaleOrderLineService saleOrderLineService = Beans.get(SaleOrderLineService.class);
SaleOrder saleOrder = saleOrderLineService.getSaleOrder(context);
saleOrderLine.setSaleOrder(saleOrder);
Map<String, BigDecimal> map = saleOrderLineService.computeSubMargin(saleOrder, saleOrderLine);
response.setValues(map);
}
/**
* Called by the sale order line form. Update all fields when the product is changed.
*
* @param request
* @param response
*/
public void getProductInformation(ActionRequest request, ActionResponse response) {
try {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
SaleOrderLineService saleOrderLineService = Beans.get(SaleOrderLineService.class);
SaleOrder saleOrder = saleOrderLineService.getSaleOrder(context);
Product product = saleOrderLine.getProduct();
if (saleOrder == null || product == null) {
resetProductInformation(response, saleOrderLine);
return;
}
Integer packPriceSelect = product.getPackPriceSelect();
if (saleOrderLine.getIsSubLine()) {
if (context.getParent().getContextClass().equals(SaleOrderLine.class)) {
packPriceSelect = context.getParent().asType(SaleOrderLine.class).getPackPriceSelect();
} else if (saleOrderLine.getParentLine() != null) {
packPriceSelect = saleOrderLine.getParentLine().getPackPriceSelect();
}
}
try {
product = Beans.get(ProductRepository.class).find(product.getId());
saleOrderLineService.computeProductInformation(saleOrderLine, saleOrder, packPriceSelect);
response.setValue("saleSupplySelect", product.getSaleSupplySelect());
response.setValues(saleOrderLine);
} catch (Exception e) {
resetProductInformation(response, saleOrderLine);
TraceBackService.trace(response, e);
}
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
public void resetProductInformation(ActionResponse response, SaleOrderLine line) {
Beans.get(SaleOrderLineService.class).resetProductInformation(line);
response.setValue("saleSupplySelect", null);
response.setValue("typeSelect", SaleOrderLineRepository.TYPE_NORMAL);
response.setValue("packPriceSelect", null);
response.setValue("subLineList", null);
response.setValues(line);
}
public void getTaxEquiv(ActionRequest request, ActionResponse response) {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
SaleOrder saleOrder = Beans.get(SaleOrderLineService.class).getSaleOrder(context);
response.setValue("taxEquiv", null);
if (saleOrder == null
|| saleOrderLine == null
|| saleOrder.getClientPartner() == null
|| saleOrderLine.getTaxLine() == null) return;
response.setValue(
"taxEquiv",
Beans.get(FiscalPositionService.class)
.getTaxEquiv(
saleOrder.getClientPartner().getFiscalPosition(),
saleOrderLine.getTaxLine().getTax()));
}
public void getDiscount(ActionRequest request, ActionResponse response) {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
SaleOrderLineService saleOrderLineService = Beans.get(SaleOrderLineService.class);
SaleOrder saleOrder = saleOrderLineService.getSaleOrder(context);
if (saleOrder == null || saleOrderLine.getProduct() == null) {
return;
}
try {
Map<String, Object> discounts;
if (saleOrderLine.getProduct().getInAti()) {
discounts =
saleOrderLineService.getDiscountsFromPriceLists(
saleOrder,
saleOrderLine,
saleOrderLineService.getInTaxUnitPrice(
saleOrder, saleOrderLine, saleOrderLine.getTaxLine()));
} else {
discounts =
saleOrderLineService.getDiscountsFromPriceLists(
saleOrder,
saleOrderLine,
saleOrderLineService.getExTaxUnitPrice(
saleOrder, saleOrderLine, saleOrderLine.getTaxLine()));
}
if (discounts != null) {
BigDecimal price = (BigDecimal) discounts.get("price");
if (price != null
&& price.compareTo(
saleOrderLine.getProduct().getInAti()
? saleOrderLine.getInTaxPrice()
: saleOrderLine.getPrice())
!= 0) {
if (saleOrderLine.getProduct().getInAti()) {
response.setValue("inTaxPrice", price);
response.setValue(
"price",
saleOrderLineService.convertUnitPrice(true, saleOrderLine.getTaxLine(), price));
} else {
response.setValue("price", price);
response.setValue(
"inTaxPrice",
saleOrderLineService.convertUnitPrice(false, saleOrderLine.getTaxLine(), price));
}
}
if (saleOrderLine.getProduct().getInAti() != saleOrder.getInAti()
&& (Integer) discounts.get("discountTypeSelect")
!= PriceListLineRepository.AMOUNT_TYPE_PERCENT) {
response.setValue(
"discountAmount",
saleOrderLineService.convertUnitPrice(
saleOrderLine.getProduct().getInAti(),
saleOrderLine.getTaxLine(),
(BigDecimal) discounts.get("discountAmount")));
} else {
response.setValue("discountAmount", discounts.get("discountAmount"));
}
response.setValue("discountTypeSelect", discounts.get("discountTypeSelect"));
}
} catch (Exception e) {
response.setFlash(e.getMessage());
}
}
/**
* Update the ex. tax unit price of an invoice line from its in. tax unit price.
*
* @param request
* @param response
*/
public void updatePrice(ActionRequest request, ActionResponse response) {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
try {
BigDecimal inTaxPrice = saleOrderLine.getInTaxPrice();
TaxLine taxLine = saleOrderLine.getTaxLine();
response.setValue(
"price",
Beans.get(SaleOrderLineService.class).convertUnitPrice(true, taxLine, inTaxPrice));
} catch (Exception e) {
response.setFlash(e.getMessage());
}
}
/**
* Update the in. tax unit price of an invoice line from its ex. tax unit price.
*
* @param request
* @param response
*/
public void updateInTaxPrice(ActionRequest request, ActionResponse response) {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
try {
BigDecimal exTaxPrice = saleOrderLine.getPrice();
TaxLine taxLine = saleOrderLine.getTaxLine();
response.setValue(
"inTaxPrice",
Beans.get(SaleOrderLineService.class).convertUnitPrice(false, taxLine, exTaxPrice));
} catch (Exception e) {
response.setFlash(e.getMessage());
}
}
public void convertUnitPrice(ActionRequest request, ActionResponse response) {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
SaleOrder saleOrder = Beans.get(SaleOrderLineService.class).getSaleOrder(context);
if (saleOrder == null
|| saleOrderLine.getProduct() == null
|| saleOrderLine.getPrice() == null
|| saleOrderLine.getInTaxPrice() == null) {
return;
}
try {
BigDecimal price = saleOrderLine.getPrice();
BigDecimal inTaxPrice = price.add(price.multiply(saleOrderLine.getTaxLine().getValue()));
response.setValue("inTaxPrice", inTaxPrice);
} catch (Exception e) {
response.setFlash(e.getMessage());
}
}
public void emptyLine(ActionRequest request, ActionResponse response) {
SaleOrderLine saleOrderLine = request.getContext().asType(SaleOrderLine.class);
if (saleOrderLine.getTypeSelect() != SaleOrderLineRepository.TYPE_NORMAL) {
Map<String, Object> newSaleOrderLine = Mapper.toMap(new SaleOrderLine());
newSaleOrderLine.put("qty", BigDecimal.ZERO);
newSaleOrderLine.put("id", saleOrderLine.getId());
newSaleOrderLine.put("version", saleOrderLine.getVersion());
newSaleOrderLine.put("typeSelect", saleOrderLine.getTypeSelect());
response.setValues(newSaleOrderLine);
}
}
public void checkQty(ActionRequest request, ActionResponse response) {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
Beans.get(SaleOrderLineService.class).checkMultipleQty(saleOrderLine, response);
}
public void updateSubLineQty(ActionRequest request, ActionResponse response)
throws AxelorException {
SaleOrderLine newkitLine = request.getContext().asType(SaleOrderLine.class);
BigDecimal qty = BigDecimal.ZERO;
BigDecimal oldKitQty = BigDecimal.ZERO;
BigDecimal newKitQty = BigDecimal.ZERO;
BigDecimal exTaxTotal = BigDecimal.ZERO;
BigDecimal inTaxTotal = BigDecimal.ZERO;
BigDecimal priceDiscounted = BigDecimal.ZERO;
BigDecimal taxRate = BigDecimal.ZERO;
BigDecimal companyExTaxTotal = BigDecimal.ZERO;
BigDecimal companyInTaxTotal = BigDecimal.ZERO;
Context context = request.getContext();
SaleOrderLineService saleOrderLineService = Beans.get(SaleOrderLineService.class);
SaleOrder saleOrder = saleOrderLineService.getSaleOrder(context);
if (newkitLine.getTypeSelect() == SaleOrderLineRepository.TYPE_PACK) {
if (newkitLine.getOldQty().compareTo(BigDecimal.ZERO) == 0) {
oldKitQty = BigDecimal.ONE;
} else {
oldKitQty = newkitLine.getOldQty();
}
if (newkitLine.getQty().compareTo(BigDecimal.ZERO) != 0) {
newKitQty = newkitLine.getQty();
}
List<SaleOrderLine> orderLines = newkitLine.getSubLineList();
if (orderLines != null) {
if (newKitQty.compareTo(BigDecimal.ZERO) != 0) {
for (SaleOrderLine line : orderLines) {
qty = (line.getQty().divide(oldKitQty, 2, RoundingMode.HALF_EVEN)).multiply(newKitQty);
priceDiscounted = saleOrderLineService.computeDiscount(line, saleOrder.getInAti());
if (line.getTaxLine() != null) {
taxRate = line.getTaxLine().getValue();
}
if (!saleOrder.getInAti()) {
exTaxTotal = saleOrderLineService.computeAmount(qty, priceDiscounted);
inTaxTotal = exTaxTotal.add(exTaxTotal.multiply(taxRate));
companyExTaxTotal =
saleOrderLineService.getAmountInCompanyCurrency(exTaxTotal, saleOrder);
companyInTaxTotal = companyExTaxTotal.add(companyExTaxTotal.multiply(taxRate));
} else {
inTaxTotal = saleOrderLineService.computeAmount(qty, priceDiscounted);
exTaxTotal =
inTaxTotal.divide(taxRate.add(BigDecimal.ONE), 2, BigDecimal.ROUND_HALF_UP);
companyInTaxTotal =
saleOrderLineService.getAmountInCompanyCurrency(inTaxTotal, saleOrder);
companyExTaxTotal =
companyInTaxTotal.divide(
taxRate.add(BigDecimal.ONE), 2, BigDecimal.ROUND_HALF_UP);
}
line.setQty(qty.setScale(2, RoundingMode.HALF_EVEN));
line.setPriceDiscounted(priceDiscounted);
line.setExTaxTotal(exTaxTotal);
line.setInTaxTotal(inTaxTotal);
line.setCompanyExTaxTotal(companyExTaxTotal);
line.setCompanyInTaxTotal(companyInTaxTotal);
}
} else {
for (SaleOrderLine line : orderLines) {
line.setQty(qty.setScale(2, RoundingMode.HALF_EVEN));
}
}
response.setValue("oldQty", newKitQty);
response.setValue("subLineList", orderLines);
}
}
}
public void updateQtyUg(ActionRequest request, ActionResponse response) throws AxelorException {
Context context = request.getContext();
SaleOrderLine saleOrderLine = context.asType(SaleOrderLine.class);
int scale = Beans.get(AppBaseService.class).getNbDecimalDigitForSalePrice();
BigDecimal qtyUg = saleOrderLine.getProduct().getUg();
BigDecimal qty = saleOrderLine.getQty();
BigDecimal totalQty =
qty.add(qty.multiply(qtyUg).divide(new BigDecimal(100), 4, RoundingMode.HALF_EVEN));
Product product = saleOrderLine.getProduct();
if (product.getUg() != null && product.getUg().compareTo(BigDecimal.ZERO) > 0) {
BigDecimal ug =
saleOrderLine
.getProduct()
.getUg()
.divide(new BigDecimal(100), scale, RoundingMode.HALF_EVEN);
BigDecimal ugAmount =
ug.divide(ug.add(BigDecimal.ONE), scale, RoundingMode.HALF_EVEN)
.multiply(new BigDecimal(100));
response.setValue("discountTypeSelect", 1);
response.setValue("discountAmount", ugAmount);
response.setValue("qty", totalQty);
}
}
public void resetPackLine(ActionRequest request, ActionResponse response) throws AxelorException {
Context context = request.getContext();
SaleOrderLineService saleOrderLineService = Beans.get(SaleOrderLineService.class);
SaleOrder saleOrder = saleOrderLineService.getSaleOrder(context);
SaleOrderLine packLine = context.asType(SaleOrderLine.class);
try {
saleOrderLineService.fillPrice(packLine, saleOrder, packLine.getPackPriceSelect());
compute(response, saleOrder, packLine);
} catch (Exception e) {
e.printStackTrace();
TraceBackService.trace(response, e);
}
}
public void resetPackSubLine(ActionRequest request, ActionResponse response)
throws AxelorException {
Context context = request.getContext();
SaleOrderLineService saleOrderLineService = Beans.get(SaleOrderLineService.class);
SaleOrder saleOrder = saleOrderLineService.getSaleOrder(context);
SaleOrderLine packLine = context.asType(SaleOrderLine.class);
List<SaleOrderLine> subLines = packLine.getSubLineList();
try {
if (subLines != null) {
for (SaleOrderLine line : subLines) {
saleOrderLineService.fillPrice(line, saleOrder, packLine.getPackPriceSelect());
saleOrderLineService.computeValues(saleOrder, line);
}
response.setValue("subLineList", subLines);
}
} catch (Exception e) {
TraceBackService.trace(response, e);
}
}
private void compute(ActionResponse response, SaleOrder saleOrder, SaleOrderLine orderLine)
throws AxelorException {
Map<String, BigDecimal> map =
Beans.get(SaleOrderLineService.class).computeValues(saleOrder, orderLine);
map.put("price", orderLine.getPrice());
map.put("inTaxPrice", orderLine.getInTaxPrice());
map.put("companyCostPrice", orderLine.getCompanyCostPrice());
map.put("discountAmount", orderLine.getDiscountAmount());
response.setValues(map);
response.setAttr(
"priceDiscounted",
"hidden",
map.getOrDefault("priceDiscounted", BigDecimal.ZERO)
.compareTo(saleOrder.getInAti() ? orderLine.getInTaxPrice() : orderLine.getPrice())
== 0);
}
}

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.csv.script;
import com.axelor.apps.base.service.administration.SequenceService;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.repo.SaleOrderManagementRepository;
import com.axelor.apps.sale.service.saleorder.SaleOrderComputeService;
import com.axelor.apps.sale.service.saleorder.SaleOrderService;
import com.axelor.apps.sale.service.saleorder.SaleOrderWorkflowService;
import com.axelor.exception.AxelorException;
import com.google.inject.Inject;
import java.util.Map;
public class ImportSaleOrder {
@Inject SaleOrderManagementRepository saleOrderRepo;
protected SaleOrderService saleOrderService;
protected SaleOrderComputeService saleOrderComputeService;
protected SaleOrderWorkflowService saleOrderWorkflowService;
protected SequenceService sequenceService;
@Inject
public ImportSaleOrder(
SaleOrderService saleOrderService,
SaleOrderComputeService saleOrderComputeService,
SaleOrderWorkflowService saleOrderWorkflowService,
SequenceService sequenceService) {
this.saleOrderService = saleOrderService;
this.saleOrderComputeService = saleOrderComputeService;
this.saleOrderWorkflowService = saleOrderWorkflowService;
this.sequenceService = sequenceService;
}
public Object importSaleOrder(Object bean, Map<String, Object> values) throws AxelorException {
assert bean instanceof SaleOrder;
SaleOrder saleOrder = (SaleOrder) bean;
saleOrderService.computeAddressStr(saleOrder);
saleOrder = saleOrderComputeService.computeSaleOrder(saleOrder);
if (saleOrder.getStatusSelect() == 1) {
saleOrder.setSaleOrderSeq(sequenceService.getDraftSequenceNumber(saleOrder));
saleOrderRepo.computeFullName(saleOrder);
} else {
saleOrderWorkflowService.finalizeQuotation(saleOrder);
}
return saleOrder;
}
}

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.csv.script;
import com.axelor.apps.sale.db.SaleOrderLine;
import com.axelor.apps.sale.service.saleorder.SaleOrderLineService;
import com.axelor.exception.AxelorException;
import com.axelor.inject.Beans;
import java.util.Map;
public class ImportSaleOrderLine {
public Object importSaleOrderLine(Object bean, Map<String, Object> values)
throws AxelorException {
assert bean instanceof SaleOrderLine;
SaleOrderLine saleOrderLine = (SaleOrderLine) bean;
SaleOrderLineService saleOrderLineService = Beans.get(SaleOrderLineService.class);
saleOrderLine.setTaxLine(
saleOrderLineService.getTaxLine(saleOrderLine.getSaleOrder(), saleOrderLine));
saleOrderLineService.computeValues(saleOrderLine.getSaleOrder(), saleOrderLine);
return saleOrderLine;
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.web;
import com.axelor.apps.base.db.Country;
import com.axelor.apps.base.service.MapService;
import com.axelor.apps.sale.db.SaleOrder;
import com.axelor.apps.sale.db.repo.SaleOrderRepository;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.inject.Inject;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/map")
public class MapRestSale {
@Inject MapService mapService;
@Inject private SaleOrderRepository saleOrderRepo;
@Path("/geomap/turnover")
@GET
@Produces(MediaType.APPLICATION_JSON)
public JsonNode getGeoMapData() {
Map<String, BigDecimal> data = new HashMap<String, BigDecimal>();
List<? extends SaleOrder> orders = saleOrderRepo.all().filter("self.statusSelect=?", 3).fetch();
JsonNodeFactory factory = JsonNodeFactory.instance;
ObjectNode mainNode = factory.objectNode();
ArrayNode arrayNode = factory.arrayNode();
ArrayNode labelNode = factory.arrayNode();
labelNode.add("Country");
labelNode.add("Turnover");
arrayNode.add(labelNode);
for (SaleOrder so : orders) {
Country country = so.getMainInvoicingAddress().getAddressL7Country();
BigDecimal value = so.getExTaxTotal();
if (country != null) {
String key = country.getName();
if (data.containsKey(key)) {
BigDecimal oldValue = data.get(key);
oldValue = oldValue.add(value);
data.put(key, oldValue);
} else {
data.put(key, value);
}
}
}
Iterator<String> keys = data.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
ArrayNode dataNode = factory.arrayNode();
dataNode.add(key);
dataNode.add(data.get(key));
arrayNode.add(dataNode);
}
mainNode.put("status", 0);
mainNode.set("data", arrayNode);
return mainNode;
}
}

View File

@ -0,0 +1,235 @@
<?xml version="1.0" encoding="utf-8"?><%
configuratorCreatorRepository = 'com.axelor.apps.sale.db.repo.ConfiguratorCreatorRepository'
def cc = { com.axelor.inject.Beans.get(configuratorCreatorRepository as Class).find(it) }
def attributeMethod = {
attribute = ""
cc(it).attributes?.each( { att ->
attribute += """
<attribute>
<name>${att.name}</name>
<title>"""
if (att.title != null) {
attribute += "${att.title}"
}
attribute += """</title>
<type>${att.type}</type>
<defaultValue>"""
if (att.defaultValue != null) {
attribute += "${att.defaultValue}"
}
attribute += """</defaultValue>
<model>${att.model}</model>
<modelField>${att.modelField}</modelField>
<jsonModelCode>"""
if (att.jsonModel?.code != null) {
attribute += "${att.jsonModel?.code}"
}
attribute += """</jsonModelCode>
<selection>"""
if (att.selection != null) {
attribute += "${att.selection}"
}
attribute += """</selection>
<widget>"""
if (att.widget != null) {
attribute += "${att.widget}"
}
attribute += """</widget>
<help>"""
if (att.help != null) {
attribute += "${att.help}"
}
attribute += """</help>
<showIf>"""
if (att.showIf != null) {
attribute += "${att.showIf}"
}
attribute += """</showIf>
<hideIf>"""
if (att.hideIf != null) {
attribute += "${att.hideIf}"
}
attribute += """</hideIf>
<requiredIf>"""
if (att.requiredIf != null) {
attribute += "${att.requiredIf}"
}
attribute += """</requiredIf>
<hidden>${att.hidden}</hidden>
<required>${att.required}</required>
<nameField>${att.nameField}</nameField>
<minSize>${att.minSize}</minSize>
<maxSize>${att.maxSize}</maxSize>
<precision>${att.precision}</precision>
<scale>${att.scale}</scale>
<regex>"""
if (att.regex != null) {
attribute += "${att.regex}"
}
attribute += """</regex>
<targetModel>"""
if (att.targetModel != null) {
attribute += "${att.targetModel}"
}
attribute += """</targetModel>
<enumType>"""
if (att.enumType != null) {
attribute += "${att.enumType}"
}
attribute += """</enumType>
<formView>"""
if (att.formView != null) {
attribute += "${att.formView}"
}
attribute += """</formView>
<gridView>"""
if (att.gridView != null) {
attribute += "${att.gridView}"
}
attribute += """</gridView>
<domain>"""
if (att.domain) {
attribute += "${att.domain}"
}
attribute += """</domain>
<targetJsonModelCode>"""
if (att.targetJsonModel?.code != null) {
attribute += "${att.targetJsonModel?.code}"
}
attribute += """</targetJsonModelCode>
<sequence>${att.sequence}</sequence>
<onChange>"""
if (att.onChange != null) {
attribute += "${att.onChange}"
}
attribute += """</onChange>
<onClick>"""
if (att.onClick != null) {
attribute += "${att.onClick}"
}
attribute += """</onClick>
<widgetAttrs>"""
if (att.widgetAttrs != null) {
attribute += "${att.widgetAttrs}"
}
attribute += """</widgetAttrs>
<contextField>"""
if (att.contextField != null) {
attribute += "${att.contextField}"
}
attribute += """</contextField>
<contextFieldTarget>"""
if (att.contextFieldTarget != null) {
attribute += "${att.contextFieldTarget}"
}
attribute += """</contextFieldTarget>
<contextFieldTargetName>"""
if (att.contextFieldTargetName != null) {
attribute += "${att.contextFieldTargetName}"
}
attribute += """</contextFieldTargetName>
<contextFieldValue>"""
if (att.contextFieldValue != null) {
attribute += "${att.contextFieldValue}"
}
attribute += """</contextFieldValue>
</attribute>
"""
})
return attribute
}
def configuratorProductFormulaMethod = {
configuratorProductFormula = ""
cc(it).configuratorProductFormulaList?.each( { cf ->
configuratorProductFormula += """
<configuratorProductFormula>
"""
if (cf.metaField?.id != null) {
configuratorProductFormula += "<metaField>${cf.metaField?.name}</metaField>"
}
configuratorProductFormula += """
<formula>"""
if (cf.formula != null) {
configuratorProductFormula += "${cf.formula}"
}
configuratorProductFormula += """</formula>
<showOnConfigurator>${cf.showOnConfigurator}</showOnConfigurator>
<configuratorCreatorImportId>${cf.productCreator?.id}</configuratorCreatorImportId>
</configuratorProductFormula>
"""
})
return configuratorProductFormula
}
def configuratorSOLineFormulaMethod = {
configuratorSOLineFormula = ""
cc(it).configuratorSOLineFormulaList?.each( { cf ->
configuratorSOLineFormula += """
<configuratorSOLineFormula>
"""
if (cf.metaField?.id != null) {
configuratorSOLineFormula += "<metaField>${ cf.metaField?.name}</metaField>"
}
configuratorSOLineFormula += """
<formula>"""
if (cf.formula != null) {
configuratorSOLineFormula += "${cf.formula}"
}
configuratorSOLineFormula += """</formula>
<showOnConfigurator>${cf.showOnConfigurator}</showOnConfigurator>
<updateFromSelect>${cf.updateFromSelect}</updateFromSelect>
<configuratorCreatorImportId>${cf.soLineCreator?.id}</configuratorCreatorImportId>
</configuratorSOLineFormula>
"""
})
return configuratorSOLineFormula
}
def authorizedUserMethod = {
authorizedUser = ""
cc(it).authorizedUserSet?.each( { au ->
authorizedUser += """
<authorizedUser>${au.code}</authorizedUser>"""
})
return authorizedUser
}
def authorizedGroupMethod = {
authorizedGroup = ""
cc(it).authorizedGroupSet?.each( { ag ->
authorizedGroup += """
<authorizedGroup>${ag.code}</authorizedGroup>"""
})
return authorizedGroup
}
%>
<configurator-export>
<configurator-creators><% __ids__.each( { it -> %>
<configurator-creator>
<importId><% out << cc(it).id %></importId>
<name><% out << cc(it).name %></name>
<attributes><% out << attributeMethod(it) %></attributes>
<configuratorProductFormulaList><% out << configuratorProductFormulaMethod(it) %></configuratorProductFormulaList>
<configuratorSOLineFormulaList><% out << configuratorSOLineFormulaMethod(it) %></configuratorSOLineFormulaList>
<authorizedUserSet><% out << authorizedUserMethod(it) %>
</authorizedUserSet>
<authorizedGroupSet><% out << authorizedGroupMethod(it) %>
</authorizedGroupSet>
<generateProduct><% out << cc(it).generateProduct %></generateProduct>
<isActive><% out << cc(it).isActive %></isActive>
</configurator-creator><% })%>
</configurator-creators>
</configurator-export>

View File

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="utf-8"?>
<xml-inputs xmlns="http://axelor.com/xml/ns/data-import"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/data-import http://axelor.com/xml/ns/data-import/data-import_5.2.xsd">
<input file="configurator-creator.xml" root="configurator-creators">
<!-- Import configuratorBOMs -->
<bind node="configurator-boms/configurator-bom" type="com.axelor.apps.production.db.ConfiguratorBOM" update="true" search="self.importId = :importId">
<bind node="name" to="name"/>
<bind node="importId" to="importId"/>
<bind node="companyCode" to="company" search="self.code = :companyCode" if="companyCode">
<bind node="text()" to="code" alias="companyCode"/>
</bind>
<bind node="statusSelect" to="statusSelect"/>
<bind node="nameFormula" to="nameFormula"/>
<bind node="defNameAsFormula" to="defNameAsFormula"/>
<bind node="productCode" to="product" search="self.code = :productCode" if="productCode">
<bind node="text()" to="code" alias="productCode"/>
</bind>
<bind node="productFormula" to="productFormula"/>
<bind node="defProductAsFormula" to="defProductAsFormula"/>
<bind node="defProductFromConfigurator" to="defProductFromConfigurator"/>
<bind node="qty" to="qty"/>
<bind node="qtyFormula" to="qtyFormula"/>
<bind node="defQtyAsFormula" to="defQtyAsFormula"/>
<bind node="unitId" to="unit" search="self.id = :unitId" if="unitId">
<bind node="text()" to="id" alias="unitId"/>
</bind>
<bind node="unitFormula" to="unitFormula"/>
<bind node="defUnitAsFormula" to="defUnitAsFormula"/>
<bind node="prodProcessCode" to="prodProcess" search="self.code = :prodProcessCode" if="prodProcessCode">
<bind node="text()" to="code" alias="prodProcessCode"/>
</bind>
<bind node="configuratorProdProcess" to="configuratorProdProcess" update="true" search="self.importId = :importId" if="configProdProcessName">
<bind node="importId" to="importId"/>
<bind node="statusSelect" to="statusSelect"/>
<bind node="name" to="name" alias="configProdProcessName"/>
<bind node="code" to="code"/>
<bind node="codeFormula" to="codeFormula"/>
<bind node="defCodeAsFormula" to="defCodeAsFormula"/>
<bind node="companyCode" to="company" search="self.code = :companyCode" if="companyCode">
<bind node="text()" to="code" alias="companyCode"/>
</bind>
<bind node="stockLocation" to="stockLocation" search="self.id = :stockLocationId" if="stockLocationId">
<bind node="text()" to="id" alias="stockLocationId"/>
</bind>
<bind node="stockLocationFormula" to="stockLocationFormula"/>
<bind node="defStockLocationAsFormula" to="defStockLocationAsFormula"/>
<bind node="producedProductStockLocation" to="producedProductStockLocation" search="self.id = :producedProductStockLocationId" if="producedProductStockLocationId">
<bind node="text()" to="id" alias="producedProductStockLocationId"/>
</bind>
<bind node="producedProductStockLocationFormula" to="producedProductStockLocationFormula"/>
<bind node="defProducedProductStockLocationAsFormula" to="defProducedProductStockLocationAsFormula"/>
<bind node="workshopStockLocation" to="workshopStockLocation" search="self.id = :workshopStockLocationId" if="workshopStockLocationId">
<bind node="text()" to="id" alias="workshopStockLocationId"/>
</bind>
<bind node="workshopStockLocationFormula" to="workshopStockLocationFormula"/>
<bind node="defWorkshopStockLocationAsFormula" to="defWorkshopStockLocationAsFormula"/>
<bind node="configuratorProdProcessLineList/configuratorProdProcessLine" to="configuratorProdProcessLineList">
<bind node="name" to="name"/>
<bind node="priority" to="priority"/>
<bind node="workCenter" to="workCenter" search="self.id = :workCenterId" if="workCenterId">
<bind node="text()" to="id" alias="workCenterId"/>
</bind>
<bind node="stockLocation" to="stockLocation" search="self.id = :stockLocationId" if="stockLocationId">
<bind node="text()" to="id" alias="stockLocationId"/>
</bind>
<bind node="outsourcing" to="outsourcing"/>
<bind node="description" to="description"/>
<bind node="minCapacityPerCycle" to="minCapacityPerCycle"/>
<bind node="minCapacityPerCycleFormula" to="minCapacityPerCycleFormula"/>
<bind node="defMinCapacityFormula" to="defMinCapacityFormula"/>
<bind node="maxCapacityPerCycle" to="maxCapacityPerCycle"/>
<bind node="maxCapacityPerCycleFormula" to="maxCapacityPerCycleFormula"/>
<bind node="defMaxCapacityFormula" to="defMaxCapacityFormula"/>
<bind node="durationPerCycle" to="durationPerCycle"/>
<bind node="durationPerCycleFormula" to="durationPerCycleFormula"/>
<bind node="defDurationFormula" to="defDurationFormula"/>
</bind>
</bind>
<bind node="prodProcessFormula" to="prodProcessFormula"/>
<bind node="defProdProcessAsFormula" to="defProdProcessAsFormula"/>
<bind node="defProdProcessAsConfigurator" to="defProdProcessAsConfigurator"/>
<bind node="parentConfiguratorBOMId" to="parentConfiguratorBOM" update="true" search="self.importId = :parentConfigBOMImportId" create="false">
<bind node="text()" to="importId" alias="parentConfigBOMImportId"/>
</bind>
<bind node="useCondition" to="useCondition"/>
<bind node="billOfMaterialId" to="billOfMaterialId"/>
</bind>
<!-- Import configurator creators -->
<bind node="configurator-creators/configurator-creator" type="com.axelor.apps.sale.db.ConfiguratorCreator" update="true" search="self.importId = :importId">
<bind node="importId" to="importId"/>
<bind node="name" to="name"/>
<bind node="generateProduct" to="generateProduct"/>
<bind node="isActive" to="isActive"/>
<bind node="attributes/attribute" to="attributes">
<bind node="name" to="name"/>
<bind node="title" to="title"/>
<bind node="type" to="type"/>
<bind node="defaultValue" to="defaultValue"/>
<bind node="model" to="model"/>
<bind node="modelField" to="modelField"/>
<bind node="selection" to="selection"/>
<bind node="widget" to="widget"/>
<bind node="help" to="help"/>
<bind node="showIf" to="showIf"/>
<bind node="hideIf" to="hideIf"/>
<bind node="requiredIf" to="requiredIf"/>
<bind node="readonlyIf" to="readonlyIf"/>
<bind node="hidden" to="hidden"/>
<bind node="required" to="required"/>
<bind node="nameField" to="nameField"/>
<bind node="minSize" to="minSize"/>
<bind node="maxSize" to="maxSize"/>
<bind node="precision" to="precision"/>
<bind node="scale" to="scale"/>
<bind node="regex" to="regex"/>
<bind node="targetModel" to="targetModel"/>
<bind node="enumType" to="enumType"/>
<bind node="formView" to="formView"/>
<bind node="gridView" to="gridView"/>
<bind node="domain" to="domain"/>
<bind node="sequence" to="sequence"/>
<bind node="onChange" to="onChange"/>
<bind node="onClick" to="onClick"/>
<bind node="widgetAttrs" to="widgetAttrs"/>
<bind node="contextField" to="contextField"/>
<bind node="contextFieldTarget" to="contextFieldTarget"/>
<bind node="contextFieldTargetName" to="contextFieldTargetName"/>
<bind node="contextFieldValue" to="contextFieldValue"/>
</bind>
<bind node="configuratorProductFormulaList/configuratorProductFormula" to="configuratorProductFormulaList">
<bind node="metaField" to="metaField" search="self.name = :metaField and self.metaModel.name = 'Product'" />
<bind node="formula" to="formula"/>
<bind node="showOnConfigurator" to="showOnConfigurator"/>
<bind node="configuratorCreatorImportId" to="productCreator" search="self.importId = :configCreatorImportId" create="false">
<bind node="text()" to="importId" alias="configCreatorImportId"/>
</bind>
</bind>
<bind node="configuratorSOLineFormulaList/configuratorSOLineFormula" to="configuratorSOLineFormulaList">
<bind node="metaField" to="metaField" search="self.name = :metaField and self.metaModel.name = 'SaleOrderLine'"/>
<bind node="formula" to="formula"/>
<bind node="showOnConfigurator" to="showOnConfigurator"/>
<bind node="updateFromSelect" to="updateFromSelect"/>
<bind node="configuratorCreatorImportId" to="soLineCreator" search="self.importId = :configCreatorImportId" create="false">
<bind node="text()" to="importId" alias="configCreatorImportId"/>
</bind>
</bind>
<bind node="authorizedUserSet/authorizedUser" to="authorizedUserSet" search="self.code = :userCode">
<bind node="text()" to="code" alias="userCode"/>
</bind>
<bind node="authorizedGroupSet/authorizedGroup" to="authorizedGroupSet" search="self.code = :groupCode">
<bind node="text()" to="code" alias="groupCode"/>
</bind>
<!-- TODO : The link between the configurator creator and its configurator BOM is not created. Check three following lines below. -->
<bind node="configuratorBomImportId" to="configuratorBom" search="self.importId = :configBomId" create="false">
<bind node="text()" to="importId" alias="configBomId"/>
</bind>
</bind>
</input>
<!-- TODO : Build arborescence of configuratorBOMs. It's not working for now on. -->
<!-- <input file="configurator-creator.xml" root="configurator-creators">
<bind node="configurator-boms/configurator-bom" type="com.axelor.apps.production.db.ConfiguratorBOM" search="self.id = :configBomId" create="false">
<bind node="id" to="id">
<bind node="text()" to="id" alias="configBomId" />
</bind>
<bind node="parentConfiguratorBOMId" to="parentConfiguratorBOM" search="self.id = :parentConfigBOMId">
<bind node="text()" to="id" alias="parentConfigBOMId" />
</bind>
</bind>
</input> -->
</xml-inputs>

View File

@ -0,0 +1,2 @@
"importId";"code";"name";"nextNum";"padding";"prefixe";"suffixe";"toBeAdded";"yearlyResetOk"
2;"saleOrder";"Sale order / Customer Order";1;4;"SO";;1;0
1 importId code name nextNum padding prefixe suffixe toBeAdded yearlyResetOk
2 2 saleOrder Sale order / Customer Order 1 4 SO 1 0

View File

@ -0,0 +1,2 @@
"importId";"code";"name";"nextNum";"padding";"prefixe";"suffixe";"toBeAdded";"yearlyResetOk"
2;"saleOrder";"Devis / commandes clients";1;4;"SO";;1;0
1 importId code name nextNum padding prefixe suffixe toBeAdded yearlyResetOk
2 2 saleOrder Devis / commandes clients 1 4 SO 1 0

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<csv-inputs xmlns="http://axelor.com/xml/ns/data-import"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/data-import http://axelor.com/xml/ns/data-import/data-import_5.2.xsd">
<input file="base_sequence.csv" separator=";" type="com.axelor.apps.base.db.Sequence" search="self.importId = :importId">
<bind to="yearlyResetOk" column="yearlyResetOk" eval="yearlyResetOk == '1' ? true : false"/>
<bind to="nextNum" column="nextNum" eval="nextNum?.empty ? '1' : nextNum"/>
<bind to="padding" column="padding" eval="padding?.empty ? '1' : padding"/>
<bind to="toBeAdded" column="toBeAdded" eval="toBeAdded?.empty ? '1' : toBeAdded"/>
<bind to="resetDate" eval="call:com.axelor.apps.base.service.app.AppBaseService:getTodayDate()" />
</input>
</csv-inputs>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<csv-inputs xmlns="http://axelor.com/xml/ns/data-import"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/data-import http://axelor.com/xml/ns/data-import/data-import_5.2.xsd">
<input file="auth_permission.csv" separator=";" type="com.axelor.auth.db.Permission" search="self.name = :name" call="com.axelor.csv.script.ImportPermission:importPermissionToRole">
<bind to="canRead" eval="can_read == 'x' ? 'true' : 'false'"/>
<bind to="canWrite" eval="can_write == 'x' ? 'true' : 'false'"/>
<bind to="canCreate" eval="can_create == 'x' ? 'true' : 'false'"/>
<bind to="canRemove" eval="can_remove == 'x' ? 'true' : 'false'"/>
<bind to="canExport" eval="can_export == 'x' ? 'true' : 'false'"/>
</input>
<input file="base_appSale.csv" separator=";" type="com.axelor.apps.base.db.AppSale" call="com.axelor.csv.script.ImportApp:importApp">
<bind column="dependsOn" to="dependsOnSet" search="self.code in :dependsOn" eval="dependsOn.split(',') as List"/>
</input>
<input file="base_objectDataConfig.csv" separator=";" type="com.axelor.apps.base.db.DataConfigLine">
<bind to="objectDataConfig" search="self.modelSelect = :modelSelect">
<bind to="title" column="title"/>
<bind to="modelSelect" column="modelSelect" />
<bind to="statusSelect" column="status"/>
</bind>
<bind to="metaModel" column="metaModel" search="self.name = :metaModel" />
<bind to="toExportMetaFieldSet" column="fields" search="self.name in :fields and self.metaModel.name = :metaModel" eval="fields.split(',') as List" />
<bind to="toDeleteMetaFieldSet" column="toDeleteFields" search="self.name in :toDeleteFields and self.metaModel.name = :metaModel" eval="toDeleteFields.split(',') as List" />
</input>
<input file="meta_helpEN.csv" separator=";" type="com.axelor.meta.db.MetaHelp">
<bind to="language" eval="'en'" />
<bind to="type" eval="'tooltip'" />
<bind to="model" eval="com.axelor.inject.Beans.get(com.axelor.meta.db.repo.MetaModelRepository.class).findByName(object)?.getFullName()" column="object" />
</input>
<input file="meta_helpFR.csv" separator=";" type="com.axelor.meta.db.MetaHelp">
<bind to="language" eval="'fr'" />
<bind to="type" eval="'tooltip'" />
<bind to="model" eval="com.axelor.inject.Beans.get(com.axelor.meta.db.repo.MetaModelRepository.class).findByName(object)?.getFullName()" column="object" />
</input>
<input file="meta_metaMenu.csv" separator=";" type="com.axelor.meta.db.MetaMenu" search="self.name = :name" update="true" />
</csv-inputs>

View File

@ -0,0 +1,2 @@
"name";"object";"can_read";"can_write";"can_create";"can_remove";"can_export";"condition";"conditionParams";"roleName"
"perm.sale.all";"com.axelor.apps.sale.db.*";"x";"x";"x";"x";"x";;;"Admin"
1 name object can_read can_write can_create can_remove can_export condition conditionParams roleName
2 perm.sale.all com.axelor.apps.sale.db.* x x x x x Admin

View File

@ -0,0 +1,2 @@
"importId";"name";"code";"installOrder";"description";"imagePath";"modules";"dependsOn";"sequence"
5;"Sale";"sale";4;"Sales configuration";"app-sale.png";"axelor-sale";"crm";2
1 importId name code installOrder description imagePath modules dependsOn sequence
2 5 Sale sale 4 Sales configuration app-sale.png axelor-sale crm 2

View File

@ -0,0 +1,2 @@
"title";"modelSelect";"status";"metaModel";"typeSelect";"tabName";"path";"fields";"toDeleteFields"
"Partner data config";"com.axelor.apps.base.db.Partner";1;"SaleOrder";0;"SaleOrder";"clientPartner";"saleorderSeq,orderDate,statusSelect,contactPartner,mainInvoicingAddress,mainInvoicingAddressStr,deliveryAddress,deliveryAddressStr";"contactPartner,mainInvoicingAddress,mainInvoicingAddressStr,deliveryAddress,deliveryAddressStr"
1 title modelSelect status metaModel typeSelect tabName path fields toDeleteFields
2 Partner data config com.axelor.apps.base.db.Partner 1 SaleOrder 0 SaleOrder clientPartner saleorderSeq,orderDate,statusSelect,contactPartner,mainInvoicingAddress,mainInvoicingAddressStr,deliveryAddress,deliveryAddressStr contactPartner,mainInvoicingAddress,mainInvoicingAddressStr,deliveryAddress,deliveryAddressStr

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

View File

@ -0,0 +1,18 @@
"module";"object";"view";"field";"help"
"axelor-sale";"AppSale";"app-sale-config-form";"manageSalesUnits";"This function is useful when the units of purchase of a product from a supplier are different from your sales units (for example, if you buy a product per tonne and sell it to the kilo). In this way, when ordering, the selected product will be directly indicated with its sales unit, thus avoiding conversion."
"axelor-sale";"AppSale";"app-sale-config-form";"manageSaleOrderVersion";"This option enables the ability to create multiple versions of a quotation, allowing you to edit a quotation to create a new versions and track the history."
"axelor-sale";"AppSale";"app-sale-config-form";"productPackMgt";"This option allows you to create pack products in product form, a pack being a product composed of other products. Note that you can then modify the composition of a pack directly from a quotation/sale order line."
"axelor-sale";"AppSale";"app-sale-config-form";"enableConfigurator";"Allows to activate the business configurators on the quotations/sale orders.
"
"axelor-sale";"AppSale";"app-sale-config-form";"allowPendingOrderModification";"This option authorizes the modification of a confirmed sale order. When an order is being modified, the generation of invoices and customer delivery is blocked. If a customer delivery is in the planned status, it remains stuck at this status as long as the order is being modified."
"axelor-sale";"AppSale";"app-sale-config-form";"manageMultipleSaleQuantity";"This is an option that allows you to define a product's multiples sale quantities on the product form. You can in this way define a product that can only be sold in a certain quantity (for example for a quantity of 5 and 10). If the quantity selected on a sale order line is different than defined multiples, the line can not be validated, and a message will appear, displaying which are the authorized quantities .
"
"axelor-sale";"AppSale";"app-sale-config-form";"printingConfigPerSaleOrder";"If this option is not enabled, the print settings by default for quotations/orders will be those of the company. By enabling this option, you will be able to configure specific print parameters by quotation/order."
"axelor-sale";"AppSale";"app-sale-config-form";"closeOpportunityUponSaleOrderConfirmation";"When an order is linked to an opportunity, by enabling this option, the opportunity will be automatically closed when the order is confirmed."
"axelor-sale";"SaleConfig";"sale-config-form";"saleOrderInAtiSelect";"It is possible to choose if in sale quotations/orders the prices are always in WT, always in ATI, by default in WT or by default in ATI. The choices ""by default in WT"" and ""by default in ATI"" leave the possibility of modifying the type of price applied on a case by case basis (via a check box)."
"axelor-sale";"SaleConfig";"sale-config-form";"displaySalemanOnPrinting";"If the box is checked, the name of the salesman that is indicated on a quotation/order will appear on the printouts."
"axelor-sale";"SaleConfig";"sale-config-form";"displayDelCondOnPrinting";"On a quotation/order, in the tab ""Delivery Information"", there is a text field ""Delivery conditions"". If enabled, the contents of this field will appear on the printings.
"
"axelor-sale";"SaleConfig";"sale-config-form";"saleOrderClientBox";"It offers the possibility to personalize a customer box with information of your choice on the printing of quotations/orders thanks to a field html."
"axelor-sale";"SaleConfig";"sale-config-form";"saleOrderLegalNote";"You can enter here legal note that will appear on the prints of your orders."
"axelor-sale";"SaleOrder";"sale-order-form";"versionNumber";"Version number for the order. It increases when clicking on the ""new version"" button of a ""Requested"" order. "
1 module object view field help
2 axelor-sale AppSale app-sale-config-form manageSalesUnits This function is useful when the units of purchase of a product from a supplier are different from your sales units (for example, if you buy a product per tonne and sell it to the kilo). In this way, when ordering, the selected product will be directly indicated with its sales unit, thus avoiding conversion.
3 axelor-sale AppSale app-sale-config-form manageSaleOrderVersion This option enables the ability to create multiple versions of a quotation, allowing you to edit a quotation to create a new versions and track the history.
4 axelor-sale AppSale app-sale-config-form productPackMgt This option allows you to create pack products in product form, a pack being a product composed of other products. Note that you can then modify the composition of a pack directly from a quotation/sale order line.
5 axelor-sale AppSale app-sale-config-form enableConfigurator Allows to activate the business configurators on the quotations/sale orders.
6 axelor-sale AppSale app-sale-config-form allowPendingOrderModification This option authorizes the modification of a confirmed sale order. When an order is being modified, the generation of invoices and customer delivery is blocked. If a customer delivery is in the planned status, it remains stuck at this status as long as the order is being modified.
7 axelor-sale AppSale app-sale-config-form manageMultipleSaleQuantity This is an option that allows you to define a product's multiples sale quantities on the product form. You can in this way define a product that can only be sold in a certain quantity (for example for a quantity of 5 and 10). If the quantity selected on a sale order line is different than defined multiples, the line can not be validated, and a message will appear, displaying which are the authorized quantities .
8 axelor-sale AppSale app-sale-config-form printingConfigPerSaleOrder If this option is not enabled, the print settings by default for quotations/orders will be those of the company. By enabling this option, you will be able to configure specific print parameters by quotation/order.
9 axelor-sale AppSale app-sale-config-form closeOpportunityUponSaleOrderConfirmation When an order is linked to an opportunity, by enabling this option, the opportunity will be automatically closed when the order is confirmed.
10 axelor-sale SaleConfig sale-config-form saleOrderInAtiSelect It is possible to choose if in sale quotations/orders the prices are always in WT, always in ATI, by default in WT or by default in ATI. The choices "by default in WT" and "by default in ATI" leave the possibility of modifying the type of price applied on a case by case basis (via a check box).
11 axelor-sale SaleConfig sale-config-form displaySalemanOnPrinting If the box is checked, the name of the salesman that is indicated on a quotation/order will appear on the printouts.
12 axelor-sale SaleConfig sale-config-form displayDelCondOnPrinting On a quotation/order, in the tab "Delivery Information", there is a text field "Delivery conditions". If enabled, the contents of this field will appear on the printings.
13 axelor-sale SaleConfig sale-config-form saleOrderClientBox It offers the possibility to personalize a customer box with information of your choice on the printing of quotations/orders thanks to a field html.
14 axelor-sale SaleConfig sale-config-form saleOrderLegalNote You can enter here legal note that will appear on the prints of your orders.
15 axelor-sale SaleOrder sale-order-form versionNumber Version number for the order. It increases when clicking on the "new version" button of a "Requested" order.

View File

@ -0,0 +1,18 @@
"module";"object";"view";"field";"help"
"axelor-sale";"AdvancePayment";"advance-payment-form";"amountRemainingToUse";"??? le montant restant à utiliser est égal au montant rentré."
"axelor-sale";"AppSale";"app-sale-config-form";"manageSalesUnits";"Cette fonction est utile quand les unités d'achat d'un produit à un fournisseur sont différentes de vos unités de vente (par exemple si vous achetez un produit à la tonne et que vous le revendez au kilo). Ainsi lors des commandes clients, le produit sélectionné sera directement indiqué avec son unité de vente, évitant ainsi de faire des conversions."
"axelor-sale";"AppSale";"app-sale-config-form";"manageSaleOrderVersion";"Cette option active la possibilité de créer plusieurs versions d'un devis, permettant ainsi de modifier un devis pour en créer une nouvelle version et d'en suivre l'historique."
"axelor-sale";"AppSale";"app-sale-config-form";"productPackMgt";"Cette option permet à partir d'une fiche produit de créer des produits de type kit, composés d'autres produits. A noter que vous pouvez ensuite modifier la composition d'un kit directement depuis une ligne de commande/devis."
"axelor-sale";"AppSale";"app-sale-config-form";"enableConfigurator";"Permet d'activer les configurateurs commerciaux sur les devis/commandes."
"axelor-sale";"AppSale";"app-sale-config-form";"allowPendingOrderModification";"Cette option autorise la modification d'une commande confirmée. Quand une commande est en cours de modification, la génération de facture et de bons de livraison est bloquée. Si un bon de livraison est au statut planifié, il restera bloqué à ce statut tant que la commande sera en cours de modification."
"axelor-sale";"AppSale";"app-sale-config-form";"manageMultipleSaleQuantity";"Cette option permet de définir sur la fiche d'un produit des quantités multiples de ventes. Vous pourrez ainsi définir qu'un produit ne peut être vendu que sous certains multiples (par exemple que pour une quantité de 5 et 10). Si la quantité sélectionnée sur une ligne de commande est différente, cette ligne ne pourra être validée et un message apparaitra, indiquant quelles sont les quantités autorisées.
"
"axelor-sale";"AppSale";"app-sale-config-form";"printingConfigPerSaleOrder";"Si cette option n'est pas activée, les paramètres d'impression pour les devis/commandes par défaut seront ceux de la société. En activant cette option, vous pourrez configurer des paramètres d'impressions spécifiques par devis/commande. "
"axelor-sale";"AppSale";"app-sale-config-form";"closeOpportunityUponSaleOrderConfirmation";"Lorsqu'une commande est liée à une opportunité, en activant cette option, l'opportunité sera automatiquement fermée quand la commande est confirmée."
"axelor-sale";"SaleConfig";"sale-config-form";"saleOrderInAtiSelect";"Il est possible de choisir si dans les devis/commandes les prix sont toujours en H.T., toujours en T.T.C, par défaut en H.T. ou par défaut en T.T.C. Les choix ""par défaut en H.T."" et ""par défaut en T.T.C."" laissent la possibilité de modifier au cas par cas (via une case à cocher) le type de prix pratiqué.
"
"axelor-sale";"SaleConfig";"sale-config-form";"displaySalemanOnPrinting";"Si la case est cochée, le nom du commercial qui est indiqué sur un devis/commande apparaitra sur les impressions."
"axelor-sale";"SaleConfig";"sale-config-form";"displayDelCondOnPrinting";"Sur un devis/commande, dans l'onglet ""Informations de livraison"", il y a un champ texte ""Conditions de livraison"". Si l'option est activée, le contenu de ce champ apparaitra sur les impressions."
"axelor-sale";"SaleConfig";"sale-config-form";"saleOrderClientBox";"Cela vous offre la possibilité de personnaliser un encadré client avec des informations de votre choix sur les impressions de devis/commandes grâce à un champ html."
"axelor-sale";"SaleConfig";"sale-config-form";"saleOrderLegalNote";"Vous pouvez entrer ici des mentions légales qui apparaitront sur les impressions de vos commandes."
"axelor-sale";"SaleOrder";"sale-order-form";"versionNumber";"Numéro de version de la commande. Celui-ci s'incrémente lorsque l'on utilise le bouton ""nouvelle version"" sur une commande au statut "" demandé """
1 module object view field help
2 axelor-sale AdvancePayment advance-payment-form amountRemainingToUse ??? le montant restant à utiliser est égal au montant rentré.
3 axelor-sale AppSale app-sale-config-form manageSalesUnits Cette fonction est utile quand les unités d'achat d'un produit à un fournisseur sont différentes de vos unités de vente (par exemple si vous achetez un produit à la tonne et que vous le revendez au kilo). Ainsi lors des commandes clients, le produit sélectionné sera directement indiqué avec son unité de vente, évitant ainsi de faire des conversions.
4 axelor-sale AppSale app-sale-config-form manageSaleOrderVersion Cette option active la possibilité de créer plusieurs versions d'un devis, permettant ainsi de modifier un devis pour en créer une nouvelle version et d'en suivre l'historique.
5 axelor-sale AppSale app-sale-config-form productPackMgt Cette option permet à partir d'une fiche produit de créer des produits de type kit, composés d'autres produits. A noter que vous pouvez ensuite modifier la composition d'un kit directement depuis une ligne de commande/devis.
6 axelor-sale AppSale app-sale-config-form enableConfigurator Permet d'activer les configurateurs commerciaux sur les devis/commandes.
7 axelor-sale AppSale app-sale-config-form allowPendingOrderModification Cette option autorise la modification d'une commande confirmée. Quand une commande est en cours de modification, la génération de facture et de bons de livraison est bloquée. Si un bon de livraison est au statut planifié, il restera bloqué à ce statut tant que la commande sera en cours de modification.
8 axelor-sale AppSale app-sale-config-form manageMultipleSaleQuantity Cette option permet de définir sur la fiche d'un produit des quantités multiples de ventes. Vous pourrez ainsi définir qu'un produit ne peut être vendu que sous certains multiples (par exemple que pour une quantité de 5 et 10). Si la quantité sélectionnée sur une ligne de commande est différente, cette ligne ne pourra être validée et un message apparaitra, indiquant quelles sont les quantités autorisées.
9 axelor-sale AppSale app-sale-config-form printingConfigPerSaleOrder Si cette option n'est pas activée, les paramètres d'impression pour les devis/commandes par défaut seront ceux de la société. En activant cette option, vous pourrez configurer des paramètres d'impressions spécifiques par devis/commande.
10 axelor-sale AppSale app-sale-config-form closeOpportunityUponSaleOrderConfirmation Lorsqu'une commande est liée à une opportunité, en activant cette option, l'opportunité sera automatiquement fermée quand la commande est confirmée.
11 axelor-sale SaleConfig sale-config-form saleOrderInAtiSelect Il est possible de choisir si dans les devis/commandes les prix sont toujours en H.T., toujours en T.T.C, par défaut en H.T. ou par défaut en T.T.C. Les choix "par défaut en H.T." et "par défaut en T.T.C." laissent la possibilité de modifier au cas par cas (via une case à cocher) le type de prix pratiqué.
12 axelor-sale SaleConfig sale-config-form displaySalemanOnPrinting Si la case est cochée, le nom du commercial qui est indiqué sur un devis/commande apparaitra sur les impressions.
13 axelor-sale SaleConfig sale-config-form displayDelCondOnPrinting Sur un devis/commande, dans l'onglet "Informations de livraison", il y a un champ texte "Conditions de livraison". Si l'option est activée, le contenu de ce champ apparaitra sur les impressions.
14 axelor-sale SaleConfig sale-config-form saleOrderClientBox Cela vous offre la possibilité de personnaliser un encadré client avec des informations de votre choix sur les impressions de devis/commandes grâce à un champ html.
15 axelor-sale SaleConfig sale-config-form saleOrderLegalNote Vous pouvez entrer ici des mentions légales qui apparaitront sur les impressions de vos commandes.
16 axelor-sale SaleOrder sale-order-form versionNumber Numéro de version de la commande. Celui-ci s'incrémente lorsque l'on utilise le bouton "nouvelle version" sur une commande au statut " demandé "

View File

@ -0,0 +1,34 @@
"name";"roles.name"
"sc-root-sale";"Admin"
"sc-root-sale-customers";"Admin"
"sc-root-sale-contacts";"Admin"
"sc-root-sale-products";"Admin"
"sc-root-sale-quotations";"Admin"
"sc-root-sale-orders";"Admin"
"sc-root-sale-historical";"Admin"
"sc-historical-completed-sale-orders";"Admin"
"sc-historical-canceled-sale-orders";"Admin"
"sc-root-sale-templates";"Admin"
"sc-root-sale-report";"Admin"
"sc-root-sale-maps";"Admin"
"sale-maps-partner-customers";"Admin"
"sale-maps-partner-prospects";"Admin"
"sc-root-sale-conf";"Admin"
"sale-conf-cancelreason";"Admin"
"sale-conf-duration";"Admin"
"sale-conf-shipping-costs";"Admin"
"sc-root-sale-conf-partner-price-list";"Admin"
"sc-root-sale-conf-price-list";"Admin"
"sale.conf.tax";"Admin"
"sale-conf-configurators";"Admin"
"sale-conf-configurators-creator";"Admin"
"sale-conf-configurators-configurators";"Admin"
"admin-root-batch-sale";"Admin"
"top-menu-sales";"Admin"
"top-menu-sales-quotations";"Admin"
"top-menu-sales-sale-orders";"Admin"
"menu-saleman-dashboard-sample";"Admin"
"menu-sale-manager-dashboard-sample";"Admin"
"menu-sale-dashboard-1";"Admin"
"menu-sale-dashboard-2";"Admin"
"menu-dashboards-customers";"Admin"
1 name roles.name
2 sc-root-sale Admin
3 sc-root-sale-customers Admin
4 sc-root-sale-contacts Admin
5 sc-root-sale-products Admin
6 sc-root-sale-quotations Admin
7 sc-root-sale-orders Admin
8 sc-root-sale-historical Admin
9 sc-historical-completed-sale-orders Admin
10 sc-historical-canceled-sale-orders Admin
11 sc-root-sale-templates Admin
12 sc-root-sale-report Admin
13 sc-root-sale-maps Admin
14 sale-maps-partner-customers Admin
15 sale-maps-partner-prospects Admin
16 sc-root-sale-conf Admin
17 sale-conf-cancelreason Admin
18 sale-conf-duration Admin
19 sale-conf-shipping-costs Admin
20 sc-root-sale-conf-partner-price-list Admin
21 sc-root-sale-conf-price-list Admin
22 sale.conf.tax Admin
23 sale-conf-configurators Admin
24 sale-conf-configurators-creator Admin
25 sale-conf-configurators-configurators Admin
26 admin-root-batch-sale Admin
27 top-menu-sales Admin
28 top-menu-sales-quotations Admin
29 top-menu-sales-sale-orders Admin
30 menu-saleman-dashboard-sample Admin
31 menu-sale-manager-dashboard-sample Admin
32 menu-sale-dashboard-1 Admin
33 menu-sale-dashboard-2 Admin
34 menu-dashboards-customers Admin

View File

@ -0,0 +1,2 @@
"code";"manageSalesUnits";"manageSaleOrderVersion";"productPackMgt";"printingOnSOFinalization"
"sale";"true";"true";"true";"true"
1 code manageSalesUnits manageSaleOrderVersion productPackMgt printingOnSOFinalization
2 sale true true true true

View File

@ -0,0 +1,2 @@
"importId";"company.importId"
2;1
1 importId company.importId
2 2 1

View File

@ -0,0 +1,26 @@
"name";"metaModel.name";"language.importId";"target";"birtTemplate.name";"filePath";"isDefault";"templateContext";"subject";"content";"toRecipients";"ccRecipients";"bccRecipients";"mediaTypeSelect"
"Envoi Devis Client";"SaleOrder";2;;;;"true";;"Votre Devis N° $SaleOrder.saleOrderSeq$";"<p>Bonjour,</p>
<p>Suite à nos précédents échanges, je vous prie de bien vouloir trouver ci-joint le devis $SaleOrder.saleOrderSeq$.</p>
<p>Voici la liste des articles que vous avez commandés :</p>
<ul>
$SaleOrder.saleOrderLineList:{ line | <li>$line.productName$ x$line.qty$ pour $line.exTaxTotal$ $SaleOrder.currency.symbol$ </li>}$
</ul>
<p>Je reste à votre disposition pour des informations complémentaires sur ce devis.</p>
";"$SaleOrder.clientPartner.emailAddress.address$";;;2
"Sale order";"SaleOrder";1;;;;"true";;"Quote N° $SaleOrder.saleOrderSeq$";"<p>Good morning,</p>
<p>Following our conversations, please find attached our quote $SaleOrder.saleOrderSeq$.</p>
<p>Here is the list of items you ordered:</p>
<ul>
$SaleOrder.saleOrderLineList:{ line | <li>$line.productName$ x$line.qty$ for $line.exTaxTotal$ $SaleOrder.currency.symbol$ </li>}$
</ul>
<p>Please do not hesitate to contact me for any further explanation regarding the quote.</p>
<p>Best regards<br/>
$SaleOrder.user.partner.firstName$ $SaleOrder.user.partner.name$</p>
";"$SaleOrder.clientPartner.emailAddress.address$";;;2
1 name metaModel.name language.importId target birtTemplate.name filePath isDefault templateContext subject content toRecipients ccRecipients bccRecipients mediaTypeSelect
2 Envoi Devis Client SaleOrder 2 true Votre Devis N° $SaleOrder.saleOrderSeq$ <p>Bonjour,</p> <p>Suite à nos précédents échanges, je vous prie de bien vouloir trouver ci-joint le devis $SaleOrder.saleOrderSeq$.</p> <p>Voici la liste des articles que vous avez commandés :</p> <ul> $SaleOrder.saleOrderLineList:{ line | <li>$line.productName$ x$line.qty$ pour $line.exTaxTotal$ $SaleOrder.currency.symbol$ </li>}$ </ul> <p>Je reste à votre disposition pour des informations complémentaires sur ce devis.</p> $SaleOrder.clientPartner.emailAddress.address$ 2
3 Sale order SaleOrder 1 true Quote N° $SaleOrder.saleOrderSeq$ <p>Good morning,</p> <p>Following our conversations, please find attached our quote $SaleOrder.saleOrderSeq$.</p> <p>Here is the list of items you ordered:</p> <ul> $SaleOrder.saleOrderLineList:{ line | <li>$line.productName$ x$line.qty$ for $line.exTaxTotal$ $SaleOrder.currency.symbol$ </li>}$ </ul> <p>Please do not hesitate to contact me for any further explanation regarding the quote.</p> <p>Best regards<br/> $SaleOrder.user.partner.firstName$ $SaleOrder.user.partner.name$</p> $SaleOrder.clientPartner.emailAddress.address$ 2

View File

@ -0,0 +1,5 @@
"name";"freeText"
"Amount too high";"false"
"Abandoned project";"false"
"Do not meet the need";"false"
"Other";"true"
1 name freeText
2 Amount too high false
3 Abandoned project false
4 Do not meet the need false
5 Other true

View File

@ -0,0 +1,2 @@
"code";"actionSelect";"company.importId";"description"
"AX_GEN_SUBSC_INV";1;1;"Permet de générer les factures d'abonnement à partir des devis."
1 code actionSelect company.importId description
2 AX_GEN_SUBSC_INV 1 1 Permet de générer les factures d'abonnement à partir des devis.

View File

@ -0,0 +1,2 @@
"importId";"name";"code";"company.importId";"acceptedCredit";"defaultValidityDuration.importId";"displaySalemanOnPrinting";"displayDelCondOnPrinting";"displayProductCodeOnPrinting";"displayTaxDetailOnPrinting";"displayEstimDelivDateOnPrinting";"displayCustomerCodeOnPrinting";"displayProductPictureOnPrinting";"saleOrderClientBox";"saleOrderLegalNote"
"1";"Axelor";"AXE";1;"100000";"1";"true";"true";"true";"true";"true";"true";"true";"We wish you good reception of your order and we thank you for choosing Axelor.";"Any late payment will result in late penalties at a rate calculated on the basis of three times the legal interest rate in force in France and a lump sum indemnity of 40 euros for recovery costs, due by law, without a reminder being necessary."
1 importId name code company.importId acceptedCredit defaultValidityDuration.importId displaySalemanOnPrinting displayDelCondOnPrinting displayProductCodeOnPrinting displayTaxDetailOnPrinting displayEstimDelivDateOnPrinting displayCustomerCodeOnPrinting displayProductPictureOnPrinting saleOrderClientBox saleOrderLegalNote
2 1 Axelor AXE 1 100000 1 true true true true true true true We wish you good reception of your order and we thank you for choosing Axelor. Any late payment will result in late penalties at a rate calculated on the basis of three times the legal interest rate in force in France and a lump sum indemnity of 40 euros for recovery costs, due by law, without a reminder being necessary.

View File

@ -0,0 +1,5 @@
"importId";"externalReference";"clientPartner.importId";"contactPartner.importId";"mainInvoicingAddress.importId";"deliveryAddress.importId";"salemanUser.importId";"team.importId";"paymentMode.importId";"paymentCondition.importId";"currency.code";"creationDate";"statusSelect";"company.importId";"confirmedByUser.importId";"confirmationDateTime";"shipmentDate";"printingSettings.importId"
6;"CF-2014-342";88;89;590;590;9;1;6;2;"EUR";"TODAY[-1M-7d]";2;1;;;;1
8;;132;133;940;940;8;3;6;2;"EUR";"TODAY[-14d]";2;1;;;;1
9;;136;139;950;950;8;3;6;2;"EUR";"TODAY[-7d]";2;1;;;;1
11;;224;225;10010;10010;13;3;8;5;"USD";"TODAY";1;1;;;;
1 importId externalReference clientPartner.importId contactPartner.importId mainInvoicingAddress.importId deliveryAddress.importId salemanUser.importId team.importId paymentMode.importId paymentCondition.importId currency.code creationDate statusSelect company.importId confirmedByUser.importId confirmationDateTime shipmentDate printingSettings.importId
2 6 CF-2014-342 88 89 590 590 9 1 6 2 EUR TODAY[-1M-7d] 2 1 1
3 8 132 133 940 940 8 3 6 2 EUR TODAY[-14d] 2 1 1
4 9 136 139 950 950 8 3 6 2 EUR TODAY[-7d] 2 1 1
5 11 224 225 10010 10010 13 3 8 5 USD TODAY 1 1

View File

@ -0,0 +1,13 @@
"importId";"saleOrder.importId";"sequence";"product.importId";"productName";"qty";"unit.importId";"price";"inTaxPrice";"exTaxTotal";"hasToCreateTask";"isOrdered";"saleSupplySelect"
14;6;1;411;"Consultant";15;103;800;960;"12000";"true";"true";3
18;8;1;410;"Project Manager";10;103;1000;1200;"10000";"true";"true";3
19;8;2;111;"High Performance Server";2;1;2500;3000;"5000";"true";"true";1
20;8;3;110;"Classic server";1;1;1500;1800;"1500";"true";"true";1
21;8;4;121;"Laser Printer";4;1;429;"514.8";"1716";"true";"true";1
22;8;5;2;"Annual Maintenance";1;106;2000;2400;"2000";"true";"true";1
23;9;1;400;"Project Study";1;1;8000;9600;"8000";"true";"true";3
24;9;2;410;"Project Manager";20;103;1000;1200;"20000";"true";"true";3
27;11;1;120;"InkJet Printer";5;1;329;"394.8";"1645";"true";"true";1
28;11;2;302;"Ink Cartridge";15;1;"32.9";"39.48";"493.5";"true";"true";1
29;11;3;121;"Laser Printer";3;1;429;"514.8";"1287";"true";"true";1
30;11;4;303;"Laser Cartridge";6;1;"84.5";"101.4";"507";"true";"true";1
1 importId saleOrder.importId sequence product.importId productName qty unit.importId price inTaxPrice exTaxTotal hasToCreateTask isOrdered saleSupplySelect
2 14 6 1 411 Consultant 15 103 800 960 12000 true true 3
3 18 8 1 410 Project Manager 10 103 1000 1200 10000 true true 3
4 19 8 2 111 High Performance Server 2 1 2500 3000 5000 true true 1
5 20 8 3 110 Classic server 1 1 1500 1800 1500 true true 1
6 21 8 4 121 Laser Printer 4 1 429 514.8 1716 true true 1
7 22 8 5 2 Annual Maintenance 1 106 2000 2400 2000 true true 1
8 23 9 1 400 Project Study 1 1 8000 9600 8000 true true 3
9 24 9 2 410 Project Manager 20 103 1000 1200 20000 true true 3
10 27 11 1 120 InkJet Printer 5 1 329 394.8 1645 true true 1
11 28 11 2 302 Ink Cartridge 15 1 32.9 39.48 493.5 true true 1
12 29 11 3 121 Laser Printer 3 1 429 514.8 1287 true true 1
13 30 11 4 303 Laser Cartridge 6 1 84.5 101.4 507 true true 1

View File

@ -0,0 +1,2 @@
"code";"manageSalesUnits";"manageSaleOrderVersion";"productPackMgt";"printingOnSOFinalization"
"sale";"true";"true";"true";"true"
1 code manageSalesUnits manageSaleOrderVersion productPackMgt printingOnSOFinalization
2 sale true true true true

View File

@ -0,0 +1,2 @@
"importId";"company.importId"
2;1
1 importId company.importId
2 2 1

View File

@ -0,0 +1,26 @@
"name";"metaModel.name";"language.importId";"target";"birtTemplate.name";"filePath";"isDefault";"templateContext";"subject";"content";"toRecipients";"ccRecipients";"bccRecipients";"mediaTypeSelect"
"Envoi Devis Client";"SaleOrder";2;;;;"true";;"Votre Devis N° $SaleOrder.saleOrderSeq$";"<p>Bonjour,</p>
<p>Suite à nos précédents échanges, je vous prie de bien vouloir trouver ci-joint le devis $SaleOrder.saleOrderSeq$.</p>
<p>Voici la liste des articles que vous avez commandés :</p>
<ul>
$SaleOrder.saleOrderLineList:{ line | <li>$line.productName$ x$line.qty$ pour $line.exTaxTotal$ $SaleOrder.currency.symbol$ </li>}$
</ul>
<p>Je reste à votre disposition pour des informations complémentaires sur ce devis.</p>
";"$SaleOrder.clientPartner.emailAddress.address$";;;2
"Sale order";"SaleOrder";1;;;;"true";;"Quote N° $SaleOrder.saleOrderSeq$";"<p>Good morning,</p>
<p>Following our conversations, please find attached our quote $SaleOrder.saleOrderSeq$.</p>
<p>Here is the list of items you ordered:</p>
<ul>
$SaleOrder.saleOrderLineList:{ line | <li>$line.productName$ x$line.qty$ for $line.exTaxTotal$ $SaleOrder.currency.symbol$ </li>}$
</ul>
<p>Please do not hesitate to contact me for any further explanation regarding the quote.</p>
<p>Best regards<br/>
$SaleOrder.user.partner.firstName$ $SaleOrder.user.partner.name$</p>
";"$SaleOrder.clientPartner.emailAddress.address$";;;2
1 name metaModel.name language.importId target birtTemplate.name filePath isDefault templateContext subject content toRecipients ccRecipients bccRecipients mediaTypeSelect
2 Envoi Devis Client SaleOrder 2 true Votre Devis N° $SaleOrder.saleOrderSeq$ <p>Bonjour,</p> <p>Suite à nos précédents échanges, je vous prie de bien vouloir trouver ci-joint le devis $SaleOrder.saleOrderSeq$.</p> <p>Voici la liste des articles que vous avez commandés :</p> <ul> $SaleOrder.saleOrderLineList:{ line | <li>$line.productName$ x$line.qty$ pour $line.exTaxTotal$ $SaleOrder.currency.symbol$ </li>}$ </ul> <p>Je reste à votre disposition pour des informations complémentaires sur ce devis.</p> $SaleOrder.clientPartner.emailAddress.address$ 2
3 Sale order SaleOrder 1 true Quote N° $SaleOrder.saleOrderSeq$ <p>Good morning,</p> <p>Following our conversations, please find attached our quote $SaleOrder.saleOrderSeq$.</p> <p>Here is the list of items you ordered:</p> <ul> $SaleOrder.saleOrderLineList:{ line | <li>$line.productName$ x$line.qty$ for $line.exTaxTotal$ $SaleOrder.currency.symbol$ </li>}$ </ul> <p>Please do not hesitate to contact me for any further explanation regarding the quote.</p> <p>Best regards<br/> $SaleOrder.user.partner.firstName$ $SaleOrder.user.partner.name$</p> $SaleOrder.clientPartner.emailAddress.address$ 2

View File

@ -0,0 +1,5 @@
"name";"freeText"
"Montant trop élevé";"false"
"Abandon du projet";"false"
"Ne répond pas au besoin";"false"
"Autre";"true"
1 name freeText
2 Montant trop élevé false
3 Abandon du projet false
4 Ne répond pas au besoin false
5 Autre true

View File

@ -0,0 +1,2 @@
"code";"actionSelect";"company.importId";"description"
"AX_GEN_SUBSC_INV";1;1;"Permet de générer les factures d'abonnement à partir des devis."
1 code actionSelect company.importId description
2 AX_GEN_SUBSC_INV 1 1 Permet de générer les factures d'abonnement à partir des devis.

View File

@ -0,0 +1,2 @@
"importId";"name";"code";"company.importId";"acceptedCredit";"defaultValidityDuration.importId";"displaySalemanOnPrinting";"displayDelCondOnPrinting";"displayProductCodeOnPrinting";"displayTaxDetailOnPrinting";"displayEstimDelivDateOnPrinting";"displayCustomerCodeOnPrinting";"displayProductPictureOnPrinting";"saleOrderClientBox";"saleOrderLegalNote"
"1";"Axelor";"AXE";1;"100000";"1";"true";"true";"true";"true";"true";"true";"true";"Nous vous souhaitons bonne réception de votre commande et nous vous remercions d'avoir choisi Axelor.";"Tout retard de paiement entraînera lexigibilité de pénalités de retard à un taux calculé sur la base de trois fois le taux d'intérêt légal en vigueur en France et dune indemnité forfaitaire minimale de 40 euros pour frais de recouvrement, dues de plein droit, sans qu'un rappel soit nécessaire."
1 importId name code company.importId acceptedCredit defaultValidityDuration.importId displaySalemanOnPrinting displayDelCondOnPrinting displayProductCodeOnPrinting displayTaxDetailOnPrinting displayEstimDelivDateOnPrinting displayCustomerCodeOnPrinting displayProductPictureOnPrinting saleOrderClientBox saleOrderLegalNote
2 1 Axelor AXE 1 100000 1 true true true true true true true Nous vous souhaitons bonne réception de votre commande et nous vous remercions d'avoir choisi Axelor. Tout retard de paiement entraînera l’exigibilité de pénalités de retard à un taux calculé sur la base de trois fois le taux d'intérêt légal en vigueur en France et d’une indemnité forfaitaire minimale de 40 euros pour frais de recouvrement, dues de plein droit, sans qu'un rappel soit nécessaire.

View File

@ -0,0 +1,5 @@
"importId";"externalReference";"clientPartner.importId";"contactPartner.importId";"mainInvoicingAddress.importId";"deliveryAddress.importId";"salemanUser.importId";"team.importId";"paymentMode.importId";"paymentCondition.importId";"currency.code";"creationDate";"statusSelect";"company.importId";"confirmedByUser.importId";"confirmationDateTime";"shipmentDate";"printingSettings.importId"
6;"CF-2014-342";88;89;590;590;9;1;6;2;"EUR";"TODAY[-1M-7d]";2;1;;;;1
8;;132;133;940;940;8;3;6;2;"EUR";"TODAY[-14d]";2;1;;;;1
9;;136;139;950;950;8;3;6;2;"EUR";"TODAY[-7d]";2;1;;;;1
11;;224;225;10010;10010;13;3;8;5;"USD";"TODAY";1;1;;;;
1 importId externalReference clientPartner.importId contactPartner.importId mainInvoicingAddress.importId deliveryAddress.importId salemanUser.importId team.importId paymentMode.importId paymentCondition.importId currency.code creationDate statusSelect company.importId confirmedByUser.importId confirmationDateTime shipmentDate printingSettings.importId
2 6 CF-2014-342 88 89 590 590 9 1 6 2 EUR TODAY[-1M-7d] 2 1 1
3 8 132 133 940 940 8 3 6 2 EUR TODAY[-14d] 2 1 1
4 9 136 139 950 950 8 3 6 2 EUR TODAY[-7d] 2 1 1
5 11 224 225 10010 10010 13 3 8 5 USD TODAY 1 1

View File

@ -0,0 +1,13 @@
"importId";"saleOrder.importId";"sequence";"product.importId";"productName";"qty";"unit.importId";"price";"inTaxPrice";"exTaxTotal";"hasToCreateTask";"isOrdered";"saleSupplySelect"
14;6;1;411;"Consultant";15;103;800;960;"12000";"true";"true";3
18;8;1;410;"Chef de projet";10;103;1000;1200;"10000";"true";"true";3
19;8;2;111;"Serveur haute performance";2;1;2500;3000;"5000";"true";"true";1
20;8;3;110;"Serveur classique";1;1;1500;1800;"1500";"true";"true";1
21;8;4;121;"Imprimante Laser";4;1;429;"514.8";"1716";"true";"true";1
22;8;5;2;"Maintenance annuelle";1;106;2000;2400;"2000";"true";"true";1
23;9;1;400;"Etude";1;1;8000;9600;"8000";"true";"true";3
24;9;2;410;"Chef de projet";20;103;1000;1200;"20000";"true";"true";3
27;11;1;120;"Imprimante Jet d'encre";5;1;329;394;"1645";"true";"true";1
28;11;2;302;"Cartouche jet d'encre";15;1;"32.9";"39.48";"493.5";"true";"true";1
29;11;3;121;"Imprimante Laser";3;1;429;"514.8";"1287";"true";"true";1
30;11;4;303;"Cartouche encre laser";6;1;"84.5";"101.4";"507";"true";"true";1
1 importId saleOrder.importId sequence product.importId productName qty unit.importId price inTaxPrice exTaxTotal hasToCreateTask isOrdered saleSupplySelect
2 14 6 1 411 Consultant 15 103 800 960 12000 true true 3
3 18 8 1 410 Chef de projet 10 103 1000 1200 10000 true true 3
4 19 8 2 111 Serveur haute performance 2 1 2500 3000 5000 true true 1
5 20 8 3 110 Serveur classique 1 1 1500 1800 1500 true true 1
6 21 8 4 121 Imprimante Laser 4 1 429 514.8 1716 true true 1
7 22 8 5 2 Maintenance annuelle 1 106 2000 2400 2000 true true 1
8 23 9 1 400 Etude 1 1 8000 9600 8000 true true 3
9 24 9 2 410 Chef de projet 20 103 1000 1200 20000 true true 3
10 27 11 1 120 Imprimante Jet d'encre 5 1 329 394 1645 true true 1
11 28 11 2 302 Cartouche jet d'encre 15 1 32.9 39.48 493.5 true true 1
12 29 11 3 121 Imprimante Laser 3 1 429 514.8 1287 true true 1
13 30 11 4 303 Cartouche encre laser 6 1 84.5 101.4 507 true true 1

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<csv-inputs xmlns="http://axelor.com/xml/ns/data-import"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/data-import http://axelor.com/xml/ns/data-import/data-import_5.2.xsd">
<input file="base_appSale.csv" separator=";" type="com.axelor.apps.base.db.AppSale" search="self.code = :code" update="true" />
<input file="base_sequence.csv" separator=";" type="com.axelor.apps.base.db.Sequence" search="self.importId = :importId" call="com.axelor.csv.script.SequenceScript:computeFullname">
<bind to="yearlyResetOk" column="yearlyResetOk" eval="yearlyResetOk == '1' ? true : false"/>
<bind to="nextNum" column="nextNum" eval="nextNum?.empty ? '1' : nextNum"/>
<bind to="padding" column="padding" eval="padding?.empty ? '1' : padding"/>
<bind to="toBeAdded" column="toBeAdded" eval="toBeAdded?.empty ? '1' : toBeAdded"/>
<bind to="resetDate" eval="call:com.axelor.apps.base.service.app.AppBaseService:getTodayDate()" />
</input>
<input file="sale_cancelReason.csv" separator=";" type="com.axelor.apps.base.db.CancelReason">
<bind to="freeText" eval="freeText == 'true' ? true : false" />
<bind to="applicationType" eval="'com.axelor.apps.sale.db.SaleOrder'"/>
</input>
<input file="sale_saleBatch.csv" separator=";" type="com.axelor.apps.sale.db.SaleBatch" />
<input file="sale_saleConfig.csv" separator=";" type="com.axelor.apps.sale.db.SaleConfig" search="self.importId = :importId"/>
<input file="sale_saleOrder.csv" separator=";" search="self.importId = :importId" type="com.axelor.apps.sale.db.SaleOrder">
<bind to="creationDate" eval="call:com.axelor.csv.script.ImportDateTime:importDate(creationDate)" column="creationDate"/>
<bind to="confirmationDateTime" eval="call:com.axelor.csv.script.ImportDateTime:importDateTime(confirmationDateTime)" column="confirmationDateTime"/>
<bind to="shipmentDate" eval="call:com.axelor.csv.script.ImportDateTime:importDate(shipmentDate)" column="shipmentDate"/>
</input>
<input file="sale_saleOrderLine.csv" separator=";" type="com.axelor.apps.sale.db.SaleOrderLine" search="self.importId = :importId" call="com.axelor.csv.script.ImportSaleOrderLine:importSaleOrderLine">
<bind to="priceDiscounted" eval="price" />
</input>
<input file="sale_saleOrder.csv" separator=";" search="self.importId = :importId" type="com.axelor.apps.sale.db.SaleOrder" call="com.axelor.csv.script.ImportSaleOrder:importSaleOrder">
<bind to="creationDate" eval="call:com.axelor.csv.script.ImportDateTime:importDate(creationDate)" column="creationDate"/>
<bind to="confirmationDateTime" eval="call:com.axelor.csv.script.ImportDateTime:importDateTime(confirmationDateTime)" column="confirmationDateTime"/>
<bind to="shipmentDate" eval="call:com.axelor.csv.script.ImportDateTime:importDate(shipmentDate)" column="shipmentDate"/>
</input>
<input file="base_template.csv" separator=";" type="com.axelor.apps.message.db.Template" search="self.name = :name" >
<bind to="language" search="self.code = :languageCode"/>
</input>
</csv-inputs>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="base" package="com.axelor.apps.base.db"/>
<entity name="ABCAnalysis" lang="java">
<date name="startDate" title="Start date"/>
<date name="endDate" title="End date"/>
</entity>
</domain-models>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="sale" package="com.axelor.apps.sale.db"/>
<entity name="AdvancePayment" lang="java">
<decimal name="amount" title="Amount" required="true" default="0"/>
<date name="advancePaymentDate" title="Date" required="true"/>
<many-to-one name="saleOrder" ref="com.axelor.apps.sale.db.SaleOrder"/>
<many-to-one name="currency" ref="com.axelor.apps.base.db.Currency" title="Currency" required="true"/>
<integer name="statusSelect" title="Status" selection="advance.payment.status.select"/>
<extra-code>
<![CDATA[
public static final int STATUS_DRAFT = 0;
public static final int STATUS_VALIDATED = 1;
public static final int STATUS_CANCELED = 2;
]]>
</extra-code>
<track>
<field name="amount" on="UPDATE"/>
<field name="advancePaymentDate" on="UPDATE"/>
<field name="saleOrder" on="UPDATE"/>
<field name="currency" on="UPDATE"/>
<field name="statusSelect" on="UPDATE"/>
</track>
</entity>
</domain-models>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="base" package="com.axelor.apps.base.db"/>
<entity name="AppSale" lang="java" extends="App">
<boolean name="manageSaleOrderVersion" title="Manage sale order versions" default="false"/>
<boolean name="printingOnSOFinalization" title="Generate the pdf printing during sale order finalization" default="false"/>
<boolean name="manageSalesUnits" title="Manage sales unit on products"/>
<boolean name="productPackMgt" title="Product Pack Management" />
<boolean name="enableConfigurator" title="Enable business configurator"/>
<boolean name="allowPendingOrderModification" />
<boolean name="manageMultipleSaleQuantity" title="Manage multiple sale quantity"/>
<boolean name="printingConfigPerSaleOrder" title="Printing config per Sale Order"/>
<boolean name="closeOpportunityUponSaleOrderConfirmation"
title="Close opportunity when one of the linked sale orders is confirmed"/><boolean name="isEnabledProductDescriptionCopy" title="Enable product description copy"/>
<integer name="salemanSelect" title="User to fill saleman" selection="sale.order.fill.saleman.select" default="1"/>
<boolean name="enableCustomerCatalogMgt" title="Enable customer catalog management"/>
<boolean name="isDisplaySaleOrderLineNumber" title="Display sale order line number"/>
<track>
<field name="manageSaleOrderVersion" on="UPDATE"/>
<field name="printingOnSOFinalization" on="UPDATE"/>
<field name="manageSalesUnits" on="UPDATE"/>
<field name="productPackMgt" on="UPDATE"/>
<field name="enableConfigurator" on="UPDATE"/>
<field name="allowPendingOrderModification" on="UPDATE"/>
<field name="manageMultipleSaleQuantity" on="UPDATE"/>
<field name="printingConfigPerSaleOrder" on="UPDATE"/>
<field name="closeOpportunityUponSaleOrderConfirmation" on="UPDATE"/>
<field name="salemanSelect" on="UPDATE"/>
<field name="enableCustomerCatalogMgt" on="UPDATE"/>
<field name="isDisplaySaleOrderLineNumber" on="UPDATE"/>
<field name="isEnabledProductDescriptionCopy" on="UPDATE"/>
</track>
</entity>
</domain-models>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="base" package="com.axelor.apps.base.db"/>
<entity name="Batch" lang="java" sequential="true">
<!-- NOT DISPLAY -->
<many-to-one name="saleBatch" ref="com.axelor.apps.sale.db.SaleBatch"/>
</entity>
</domain-models>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="base" package="com.axelor.apps.base.db"/>
<entity name="Blocking">
<decimal name="maxAmount" title="Total A.T.I." scale="2" precision="20"/>
<extra-code>
<![CDATA[
public static final Integer SALE_BLOCKING = 5;
]]>
</extra-code>
</entity>
</domain-models>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="base" package="com.axelor.apps.base.db"/>
<entity name="Company" lang="java" cacheable="true">
<one-to-one name="saleConfig" ref="com.axelor.apps.sale.db.SaleConfig" title="Sale config" mappedBy="company"/>
<string name="orderBloquedMessage" multiline="true"/>
</entity>
</domain-models>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="sale" package="com.axelor.apps.sale.db"/>
<entity name="Configurator" lang="java">
<many-to-one name="configuratorCreator" ref="com.axelor.apps.sale.db.ConfiguratorCreator" title="Configurator type" required="true" />
<string name="configuratorCreatorName" hidden="true" namecolumn="true" />
<string name="attributes" title="Attributes" json="true" />
<string name="indicators" title="Indicators" json="true" />
<one-to-one name="product" ref="com.axelor.apps.base.db.Product" title="Product"/>
<extra-code><![CDATA[
//update sale order line
public static final int UPDATE_FROM_PRODUCT = 0;
public static final int UPDATE_FROM_CONFIGURATOR = 1;
]]></extra-code>
</entity>
</domain-models>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="sale" package="com.axelor.apps.sale.db"/>
<entity name="ConfiguratorCreator" lang="java">
<string name="name" title="Name" required="true"/>
<one-to-many name="attributes" ref="com.axelor.meta.db.MetaJsonField" title="Attributes" orphanRemoval="true" />
<one-to-many name="indicators" ref="com.axelor.meta.db.MetaJsonField" title="Indicators" orphanRemoval="true" />
<one-to-many name="configuratorProductFormulaList" mappedBy="productCreator"
title="Configurator formula list"
ref="com.axelor.apps.sale.db.ConfiguratorProductFormula"/>
<one-to-many name="configuratorSOLineFormulaList" mappedBy="soLineCreator"
title="Configurator formula list"
ref="com.axelor.apps.sale.db.ConfiguratorSOLineFormula"/>
<many-to-many name="authorizedUserSet" title="Authorized users"
ref="com.axelor.auth.db.User"/>
<many-to-many name="authorizedGroupSet" title="Authorized groups"
ref="com.axelor.auth.db.Group"/>
<boolean name="generateProduct" title="Generate product" default="true"/>
<boolean name="isActive" title="Is Active" default="false"/>
</entity>
</domain-models>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="sale" package="com.axelor.apps.sale.db"/>
<entity name="ConfiguratorFormula" lang="java" strategy="JOINED">
<many-to-one name="metaField" ref="com.axelor.meta.db.MetaField"/>
<string name="formula" title="Formula" large="true"/>
<boolean name="showOnConfigurator" title="Show on configurator"/>
</entity>
</domain-models>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="sale" package="com.axelor.apps.sale.db"/>
<entity name="ConfiguratorProductFormula" extends="ConfiguratorFormula">
<many-to-one name="productCreator" ref="com.axelor.apps.sale.db.ConfiguratorCreator"/>
</entity>
</domain-models>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="sale" package="com.axelor.apps.sale.db"/>
<entity name="ConfiguratorSOLineFormula" extends="ConfiguratorFormula">
<integer name="updateFromSelect" title="Update from" default="0" selection="configurator.update.sale.order.line"/>
<many-to-one name="soLineCreator" ref="com.axelor.apps.sale.db.ConfiguratorCreator"/>
</entity>
</domain-models>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="sale" package="com.axelor.apps.sale.db"/>
<entity name="CustomerCatalog" lang="java">
<many-to-one name="product" ref="com.axelor.apps.base.db.Product" title="Product" required="true"/>
<many-to-one name="customerPartner" ref="com.axelor.apps.base.db.Partner" required="true" title="Customer"/>
<string name="productCustomerName" title="Product name on catalog"/>
<string name="productCustomerCode" title="Product code on catalog"/>
</entity>
</domain-models>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="account" package="com.axelor.apps.account.db"/>
<entity name="FiscalPosition" lang="java">
<boolean name="customerSpecificNote" title="Customer specific note"/>
</entity>
</domain-models>

Some files were not shown because too many files have changed in this diff Show More