feat: Enhance Supply Chain Module with Analytic Move Line Features

- Added support for analytic move lines in InvoiceServiceSupplychainImpl.
- Implemented methods to check for missing analytic move lines in PurchaseOrderController and PurchaseRequestController.
- Introduced budget distribution line generation in PurchaseOrderController.
- Updated SaleOrderController to generate analytic move lines.
- Enhanced StockMoveController with budget distribution line generation.
- Modified StockMoveLineController to include analytic account and axis handling.
- Updated domain models to include references to analytic accounts and axes in Partner, StockLocation, and StockMoveLine.
- Created unit tests for StockMoveLineServiceSupplychainImpl to ensure proper functionality of new features.
- Added MockQuery class for testing purposes.
This commit is contained in:
BACHIR SOULDI
2026-02-17 15:13:17 +01:00
parent 9eb959f07a
commit 6881c439b2
74 changed files with 2975 additions and 930 deletions

View File

@@ -1,26 +1,36 @@
package com.axelor.apps.suppliermanagement.db.repo;
import com.axelor.apps.account.db.BudgetDistribution;
import com.axelor.apps.suppliermanagement.db.PurchaseOrderSupplierLine;
import com.axelor.apps.supplychain.service.BudgetSupplychainService;
import com.axelor.inject.Beans;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import com.axelor.apps.account.db.BudgetDistribution;
import com.axelor.apps.suppliermanagement.db.PurchaseOrderSupplierLine;
public class PurchaseOrderSupplierLineManagementRepository
extends PurchaseOrderSupplierLineRepository {
public class PurchaseOrderSupplierLineManagementRepository extends PurchaseOrderSupplierLineRepository {
@Override
public Map<String, Object> populate(Map<String, Object> json, Map<String, Object> context) {
Long supplierLine = (Long) json.get("id");
PurchaseOrderSupplierLine line = find(supplierLine);
List<BudgetDistribution> budgetDistributiList = line.getPurchaseOrderLine().getBudgetDistributionList();
BigDecimal totalBudgetAmountAvailable = BigDecimal.ZERO;
for (BudgetDistribution budgetDistribution : budgetDistributiList) {
totalBudgetAmountAvailable = totalBudgetAmountAvailable.add(budgetDistribution.getBudgetAmountAvailable());
}
json.put("budgetRemainingAmount", totalBudgetAmountAvailable);
return super.populate(json, context);
@Override
public Map<String, Object> populate(Map<String, Object> json, Map<String, Object> context) {
Long supplierLine = (Long) json.get("id");
PurchaseOrderSupplierLine line = find(supplierLine);
List<BudgetDistribution> budgetDistributiList =
line.getPurchaseOrderLine().getBudgetDistributionList();
BigDecimal totalBudgetAmountAvailable = BigDecimal.ZERO;
BigDecimal budgetRemainingAmountAfterValidation = BigDecimal.ZERO;
for (BudgetDistribution budgetDistribution : budgetDistributiList) {
Beans.get(BudgetSupplychainService.class)
.computeBudgetDistributionSumAmount(
budgetDistribution, line.getPurchaseOrderLine().getPurchaseOrder().getOrderDate());
totalBudgetAmountAvailable =
totalBudgetAmountAvailable.add(budgetDistribution.getBudgetAmountAvailable());
}
budgetRemainingAmountAfterValidation =
totalBudgetAmountAvailable.subtract(line.getInTaxTotal());
json.put("budgetRemainingAmount", totalBudgetAmountAvailable);
json.put("budgetRemainingAmountAfterValidation", budgetRemainingAmountAfterValidation);
return super.populate(json, context);
}
}

View File

@@ -99,16 +99,18 @@ public class PurchaseOrderSupplierLineService {
purchaseOrderSupplierLine.setAcceptanceDate(appPurchaseService.getTodayDate());
purchaseOrderSupplierLine.setAcceptedByUser(AuthUtils.getUser());
if(!purchaseOrderLine.getBudgetDistributionList().isEmpty()){
Beans.get(PurchaseOrderLineServiceSupplychainImpl.class)
.computeBudgetDistributionSumAmount(purchaseOrderLine, purchaseOrderLine.getPurchaseOrder());
}
poSupplierLineRepo.save(purchaseOrderSupplierLine);
// Budget Module
// if(!purchaseOrderLine.getBudgetDistributionList().isEmpty()){
// Beans.get(PurchaseOrderLineServiceSupplychainImpl.class)
// .computeBudgetDistributionSumAmount(purchaseOrderLine,purchaseOrderLine.getPurchaseOrder());
// }
if(!purchaseOrderLine.getAnalyticMoveLineList().isEmpty()){
Beans.get(PurchaseOrderLineServiceSupplychainImpl.class).computeAnalyticDistribution(purchaseOrderLine);
}
poSupplierLineRepo.save(purchaseOrderSupplierLine);
}
@Transactional(rollbackOn = {Exception.class})

View File

@@ -17,6 +17,11 @@
*/
package com.axelor.apps.suppliermanagement.service;
import com.axelor.apps.account.db.AnalyticMoveLine;
import com.axelor.apps.account.db.Budget;
import com.axelor.apps.account.db.BudgetDistribution;
import com.axelor.apps.account.db.repo.AnalyticMoveLineRepository;
import com.axelor.apps.account.db.repo.BudgetDistributionRepository;
import com.axelor.apps.base.db.Blocking;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.Partner;
@@ -33,7 +38,6 @@ import com.axelor.apps.purchase.db.SupplierCatalog;
import com.axelor.apps.purchase.db.repo.PurchaseOrderLineRepository;
import com.axelor.apps.purchase.db.repo.PurchaseOrderRepository;
import com.axelor.apps.purchase.db.repo.PurchaseRequestRepository;
import com.axelor.apps.purchase.service.PurchaseOrderLineService;
import com.axelor.apps.purchase.service.app.AppPurchaseService;
import com.axelor.apps.stock.service.StockLocationService;
import com.axelor.apps.suppliermanagement.db.PurchaseOrderSupplierLine;
@@ -231,9 +235,9 @@ public class PurchaseOrderSupplierService {
}
}
// PurchaseOrderNew.setStatusSelect(0);//Approuve
createPurchaseOrderSupplierLineTCO(
PurchaseOrderNew.getPurchaseOrderLineList(),
purchaseOrderLinesBySupplierPartner.get(supplierPartner));
// createPurchaseOrderSupplierLineTCO(
// PurchaseOrderNew.getPurchaseOrderLineList(),
// purchaseOrderLinesBySupplierPartner.get(supplierPartner));
}
purchaseRequest.setPurchaseOrderSet(hash_Set);
@@ -323,7 +327,8 @@ public class PurchaseOrderSupplierService {
"Création d'une ligne de commande fournisseur pour le produit : {}",
new Object[] {purchaseOrderLine.getProductName()});
return purchaseOrderLineServiceSupplychainImpl.createPurchaseOrderLineFromSplit(purchaseOrderLine);
return purchaseOrderLineServiceSupplychainImpl.createPurchaseOrderLineFromSplit(
purchaseOrderLine);
}
@Transactional(rollbackOn = {Exception.class})
@@ -392,8 +397,23 @@ public class PurchaseOrderSupplierService {
"SOPHAL Création d'une ligne de commande fournisseur pour le produit : {}",
new Object[] {purchaseOrderLine.getProductName()});
PurchaseOrderLine PurchaseOrderLineNew =
purchaseOrderLineServiceSupplychainImpl.createPurchaseOrderLineFromSplit(purchaseOrderLine);
PurchaseOrderLine PurchaseOrderLineNew = purchaseOrderLineServiceSupplychainImpl.createPurchaseOrderLineFromSplit(purchaseOrderLine);
// List<AnalyticMoveLine> analyticMoveLines = purchaseOrderLine.getAnalyticMoveLineList();
// List<BudgetDistribution> budgetDistributions = purchaseOrderLine.getBudgetDistributionList();
// if (analyticMoveLines.size() != 0) {
// for (AnalyticMoveLine analyticMoveLine : analyticMoveLines) {
// AnalyticMoveLine analyticMoveLineCopy = Beans.get(AnalyticMoveLineRepository.class).copy(analyticMoveLine, false);
// PurchaseOrderLineNew.addAnalyticMoveLineListItem(analyticMoveLineCopy);
// }
// }
// if (budgetDistributions.size() != 0) {
// for (BudgetDistribution budgetDistribution : budgetDistributions) {
// BudgetDistribution budgetDistributionCopy = Beans.get(BudgetDistributionRepository.class).copy(budgetDistribution, false);
// PurchaseOrderLineNew.addBudgetDistributionListItem(budgetDistributionCopy);
// }
// }
PurchaseOrderLineNew.setPrice(purchaseOrderLine.getPrice());
PurchaseOrderLineNew.setInTaxPrice(purchaseOrderLine.getInTaxPrice());
@@ -424,27 +444,29 @@ public class PurchaseOrderSupplierService {
.fetch();
for (PurchaseOrderSupplierLine poLineSupplierParnet : PurchaseOrderSupplierLineParnetList) {
PurchaseOrderSupplierLine po =
Beans.get(PurchaseOrderSupplierLineRepository.class).copy(poLineSupplierParnet, false);
PurchaseOrderSupplierLine po = new PurchaseOrderSupplierLine();
po.setPrice(poLineSupplierParnet.getPrice());
po.setExTaxTotal(poLineSupplierParnet.getExTaxTotal());
po.setInTaxTotal(poLineSupplierParnet.getInTaxTotal());
po.setTaxTotal(poLineSupplierParnet.getTaxTotal());
po.setAvailableQty(poLineSupplierParnet.getAvailableQty());
// PurchaseOrderSupplierLine po = new PurchaseOrderSupplierLine();
// po.setPrice(poLineSupplierParnet.getPrice());
// po.setExTaxTotal(poLineSupplierParnet.getExTaxTotal());
// po.setInTaxTotal(poLineSupplierParnet.getInTaxTotal());
// po.setTaxTotal(poLineSupplierParnet.getTaxTotal());
// po.setAvailableQty(poLineSupplierParnet.getAvailableQty());
po.setPurchaseOrderLine(purchaseOrderLineNew.get(index));
po.setStateSelect(poLineSupplierParnet.getStateSelect());
po.setTaxLine(poLineSupplierParnet.getTaxLine());
po.setSupplierPartner(poLineSupplierParnet.getSupplierPartner());
po.setComments(poLineSupplierParnet.getComments());
po.setArchived(poLineSupplierParnet.getArchived());
po.setEstimatedDelivDate(poLineSupplierParnet.getEstimatedDelivDate());
po.setAcceptanceDate(poLineSupplierParnet.getAcceptanceDate());
po.setAcceptedByUser(poLineSupplierParnet.getAcceptedByUser());
po.setRefusalDate(poLineSupplierParnet.getRefusalDate());
po.setRefusedByUser(poLineSupplierParnet.getRefusedByUser());
po.setAttrs(poLineSupplierParnet.getAttrs());
po.setCurrency(poLineSupplierParnet.getCurrency());
po.setPriceDiscounted(poLineSupplierParnet.getPriceDiscounted());
// po.setStateSelect(poLineSupplierParnet.getStateSelect());
// po.setTaxLine(poLineSupplierParnet.getTaxLine());
// po.setSupplierPartner(poLineSupplierParnet.getSupplierPartner());
// po.setComments(poLineSupplierParnet.getComments());
// po.setArchived(poLineSupplierParnet.getArchived());
// po.setEstimatedDelivDate(poLineSupplierParnet.getEstimatedDelivDate());
// po.setAcceptanceDate(poLineSupplierParnet.getAcceptanceDate());
// po.setAcceptedByUser(poLineSupplierParnet.getAcceptedByUser());
// po.setRefusalDate(poLineSupplierParnet.getRefusalDate());
// po.setRefusedByUser(poLineSupplierParnet.getRefusedByUser());
// po.setAttrs(poLineSupplierParnet.getAttrs());
// po.setCurrency(poLineSupplierParnet.getCurrency());
// po.setPriceDiscounted(poLineSupplierParnet.getPriceDiscounted());
// LOG.debug("purchaseOrderLineNew.getId(): {}",purchaseOrderLineNew.get(index).getId());
Beans.get(PurchaseOrderSupplierLineRepository.class).save(po);
}

View File

@@ -95,9 +95,8 @@ public class PurchaseOrderController {
PurchaseOrderLineRepository purchaseOrderLineRepo =
Beans.get(PurchaseOrderLineRepository.class);
for (HashMap map : selectedPurchaseOrderLineMapList) {
PurchaseOrderLine purchaseOrderLine =
(PurchaseOrderLine) Mapper.toBean(PurchaseOrderLine.class, map);
purchaseOrderLineList.add(purchaseOrderLineRepo.find(purchaseOrderLine.getId()));
PurchaseOrderLine poline = (PurchaseOrderLine) Mapper.toBean(PurchaseOrderLine.class, map);
purchaseOrderLineList.add(purchaseOrderLineRepo.find(poline.getId()));
}
if (purchaseOrderLineList.isEmpty()) {
@@ -121,6 +120,7 @@ public class PurchaseOrderController {
Beans.get(PurchaseOrderSupplierLineService.class)
.setPurchaseOrderLinesPartner(purchaseOrderLineList, partner);
response.setCanClose(true);
response.setAlert(String.format(I18n.get("Purchase order lines set successfully to partner %s"),partner.getName()));
} catch (Exception e) {
TraceBackService.trace(response, e);
}

View File

@@ -30,6 +30,7 @@ import com.axelor.apps.suppliermanagement.db.repo.PurchaseOrderSupplierLineRepos
import com.axelor.apps.suppliermanagement.service.PurchaseOrderSupplierLineService;
import com.axelor.apps.supplychain.service.PurchaseOrderServiceSupplychainImpl;
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;
@@ -66,6 +67,17 @@ public class PurchaseOrderSupplierLineController {
public void accept(ActionRequest request, ActionResponse response) {
// check if budgetRemainingAmountAfterValidation is less than zero then throw error
if (request.getContext().get("budgetRemainingAmountAfterValidation") != null) {
BigDecimal budgetRemainingAmountAfterValidation =
new BigDecimal(
String.valueOf(request.getContext().get("budgetRemainingAmountAfterValidation")));
if (budgetRemainingAmountAfterValidation.compareTo(BigDecimal.ZERO) < 0) {
response.setNotify(I18n.get(
"The budget remaining amount after validation is less than zero. Cannot accept the supplier line."));
}
}
PurchaseOrderSupplierLine purchaseOrderSupplierLine =
Beans.get(PurchaseOrderSupplierLineRepository.class)
.find(request.getContext().asType(PurchaseOrderSupplierLine.class).getId());
@@ -289,12 +301,13 @@ public class PurchaseOrderSupplierLineController {
}
}
public void computeBudgetDistribution(ActionRequest request, ActionResponse response) {
public void computeBudgetDistribution(ActionRequest request, ActionResponse response){
PurchaseOrderSupplierLine purchaseOrderSupplierLine = Beans.get(PurchaseOrderSupplierLineRepository.class)
PurchaseOrderSupplierLine purchaseOrderSupplierLine =
Beans.get(PurchaseOrderSupplierLineRepository.class)
.find(request.getContext().asType(PurchaseOrderSupplierLine.class).getId());
Beans.get(PurchaseOrderServiceSupplychainImpl.class).computeBudgetDistribution(purchaseOrderSupplierLine.getPurchaseOrderLine());
Beans.get(PurchaseOrderServiceSupplychainImpl.class)
.computeBudgetDistribution(purchaseOrderSupplierLine.getPurchaseOrderLine());
response.setReload(true);
}
}