/** * Axelor Business Solutions * * Copyright (C) 2016 Axelor (<http://axelor.com>). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.axelor.apps.production.service; import java.math.BigDecimal; import java.math.RoundingMode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.axelor.app.production.db.IWorkCenter; import com.axelor.apps.base.db.General; import com.axelor.apps.base.db.Product; import com.axelor.apps.base.db.Unit; import com.axelor.apps.base.service.UnitConversionService; import com.axelor.apps.base.service.administration.GeneralService; import com.axelor.apps.production.db.BillOfMaterial; import com.axelor.apps.production.db.CostSheet; import com.axelor.apps.production.db.CostSheetLine; import com.axelor.apps.production.db.ProdHumanResource; import com.axelor.apps.production.db.ProdProcess; import com.axelor.apps.production.db.ProdProcessLine; import com.axelor.apps.production.db.ProdResidualProduct; import com.axelor.apps.production.db.WorkCenter; import com.axelor.apps.production.db.repo.BillOfMaterialRepository; import com.axelor.exception.AxelorException; import com.google.inject.Inject; import com.google.inject.persist.Transactional; public class CostSheetServiceImpl implements CostSheetService { private final Logger logger = LoggerFactory.getLogger(getClass()); protected UnitConversionService unitConversionService; protected CostSheetLineService costSheetLineService; protected BillOfMaterialRepository billOfMaterialRepo; protected Unit hourUnit; protected Unit cycleUnit; protected boolean manageResidualProductOnBom; protected boolean subtractProdResidualOnCostSheet; protected CostSheet costSheet; @Inject public CostSheetServiceImpl(GeneralService generalService, UnitConversionService unitConversionService, CostSheetLineService costSheetLineService, BillOfMaterialRepository billOfMaterialRepo) { this.unitConversionService = unitConversionService; this.costSheetLineService = costSheetLineService; this.billOfMaterialRepo = billOfMaterialRepo; General general = generalService.getGeneral(); this.hourUnit = general.getUnitHours(); this.cycleUnit = general.getCycleUnit(); this.manageResidualProductOnBom = general.getManageResidualProductOnBom(); this.subtractProdResidualOnCostSheet = general.getSubtractProdResidualOnCostSheet(); } @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public CostSheet computeCostPrice(BillOfMaterial billOfMaterial) throws AxelorException { costSheet = new CostSheet(); CostSheetLine producedCostSheetLine = costSheetLineService.createProducedProductCostSheetLine(billOfMaterial.getProduct(), billOfMaterial.getUnit(), billOfMaterial.getQty()); costSheet.addCostSheetLineListItem(producedCostSheetLine); this._computeCostPrice(billOfMaterial, 0, producedCostSheetLine); this.computeResidualProduct(billOfMaterial); billOfMaterial.setCostPrice(this.computeCostPrice(costSheet)); billOfMaterial.addCostSheetListItem(costSheet); billOfMaterialRepo.save(billOfMaterial); return costSheet; } protected void computeResidualProduct(BillOfMaterial billOfMaterial) throws AxelorException { if(this.manageResidualProductOnBom && billOfMaterial.getProdResidualProductList() != null) { for(ProdResidualProduct prodResidualProduct : billOfMaterial.getProdResidualProductList()) { CostSheetLine costSheetLine = costSheetLineService.createResidualProductCostSheetLine(prodResidualProduct.getProduct(), prodResidualProduct.getUnit(), prodResidualProduct.getQty()); costSheet.addCostSheetLineListItem(costSheetLine); } } } protected BigDecimal computeCostPrice(CostSheet costSheet) { BigDecimal costPrice = BigDecimal.ZERO; if(costSheet.getCostSheetLineList() != null) { for(CostSheetLine costSheetLine : costSheet.getCostSheetLineList()) { if(costSheetLine.getCostSheetLineList() != null && !costSheetLine.getCostSheetLineList().isEmpty()) { costPrice = costPrice.add(this.computeCostPrice(costSheetLine)); } else { costPrice = costPrice.add(costSheetLine.getCostPrice()); } } } costSheet.setCostPrice(costPrice); return costPrice; } protected BigDecimal computeCostPrice(CostSheetLine parentCostSheetLine) { BigDecimal costPrice = BigDecimal.ZERO; if(parentCostSheetLine.getCostSheetLineList() != null) { for(CostSheetLine costSheetLine : parentCostSheetLine.getCostSheetLineList()) { if(costSheetLine.getCostSheetLineList() != null && !costSheetLine.getCostSheetLineList().isEmpty()) { costPrice = costPrice.add(this.computeCostPrice(costSheetLine)); } else { costPrice = costPrice.add(costSheetLine.getCostPrice()); } } } parentCostSheetLine.setCostPrice(costPrice); return costPrice; } protected void _computeCostPrice(BillOfMaterial billOfMaterial, int bomLevel, CostSheetLine parentCostSheetLine) throws AxelorException { bomLevel++; // Cout des composants this._computeToConsumeProduct(billOfMaterial, bomLevel, parentCostSheetLine); // Cout des operations this._computeProcess(billOfMaterial.getProdProcess(), billOfMaterial.getQty(), billOfMaterial.getProduct().getUnit(), bomLevel, parentCostSheetLine); } protected void _computeToConsumeProduct(BillOfMaterial billOfMaterial, int bomLevel, CostSheetLine parentCostSheetLine) throws AxelorException { if(billOfMaterial.getBillOfMaterialList() != null) { for(BillOfMaterial billOfMaterialLine : billOfMaterial.getBillOfMaterialList()) { Product product = billOfMaterialLine.getProduct(); if(product != null) { CostSheetLine costSheetLine = costSheetLineService.createConsumedProductCostSheetLine(product, billOfMaterialLine.getUnit(), bomLevel, parentCostSheetLine, billOfMaterialLine.getQty()); if(!billOfMaterialLine.getIsRawMaterial()) { this._computeCostPrice(billOfMaterialLine, bomLevel, costSheetLine); } } } } } protected void _computeProcess(ProdProcess prodProcess, BigDecimal producedQty, Unit pieceUnit, int bomLevel, CostSheetLine parentCostSheetLine) throws AxelorException { if(prodProcess != null && prodProcess.getProdProcessLineList() != null) { for(ProdProcessLine prodProcessLine : prodProcess.getProdProcessLineList()) { WorkCenter workCenter = prodProcessLine.getWorkCenter(); if(workCenter != null) { int workCenterTypeSelect = workCenter.getWorkCenterTypeSelect(); if(workCenterTypeSelect == IWorkCenter.WORK_CENTER_HUMAN || workCenterTypeSelect == IWorkCenter.WORK_CENTER_BOTH) { this._computeHumanResourceCost(workCenter, prodProcessLine.getPriority(), bomLevel, parentCostSheetLine); } if(workCenterTypeSelect == IWorkCenter.WORK_CENTER_MACHINE || workCenterTypeSelect == IWorkCenter.WORK_CENTER_BOTH) { this._computeMachineCost(prodProcessLine, producedQty, pieceUnit, bomLevel, parentCostSheetLine); } } } } } protected void _computeHumanResourceCost(WorkCenter workCenter, int priority, int bomLevel, CostSheetLine parentCostSheetLine) throws AxelorException { if(workCenter.getProdHumanResourceList() != null) { for(ProdHumanResource prodHumanResource : workCenter.getProdHumanResourceList()) { this._computeHumanResourceCost(prodHumanResource, priority, bomLevel, parentCostSheetLine); } } } protected void _computeHumanResourceCost(ProdHumanResource prodHumanResource, int priority, int bomLevel, CostSheetLine parentCostSheetLine) throws AxelorException { BigDecimal costPerHour = BigDecimal.ZERO; if(prodHumanResource.getProduct() != null) { Product product = prodHumanResource.getProduct(); costPerHour = unitConversionService.convert(hourUnit, product.getUnit(), product.getCostPrice()); } BigDecimal durationHours = BigDecimal.valueOf(prodHumanResource.getDuration()).divide(BigDecimal.valueOf(3600), 5, RoundingMode.HALF_EVEN); costSheetLineService.createWorkCenterCostSheetLine(prodHumanResource.getWorkCenter(), priority, bomLevel, parentCostSheetLine, durationHours, costPerHour.multiply(durationHours), hourUnit); } protected void _computeMachineCost(ProdProcessLine prodProcessLine, BigDecimal producedQty, Unit pieceUnit, int bomLevel, CostSheetLine parentCostSheetLine) { WorkCenter workCenter = prodProcessLine.getWorkCenter(); int costType = workCenter.getCostTypeSelect(); if(costType == IWorkCenter.COST_PER_CYCLE) { costSheetLineService.createWorkCenterCostSheetLine(workCenter, prodProcessLine.getPriority(), bomLevel, parentCostSheetLine, this.getNbCycle(producedQty, prodProcessLine.getMaxCapacityPerCycle()), workCenter.getCostAmount(), cycleUnit); } else if(costType == IWorkCenter.COST_PER_HOUR) { BigDecimal qty = new BigDecimal(prodProcessLine.getDurationPerCycle()).divide(new BigDecimal(3600), BigDecimal.ROUND_HALF_EVEN).multiply(this.getNbCycle(producedQty, prodProcessLine.getMaxCapacityPerCycle())); BigDecimal costPrice = workCenter.getCostAmount().multiply(qty); costSheetLineService.createWorkCenterCostSheetLine(workCenter, prodProcessLine.getPriority(), bomLevel, parentCostSheetLine, qty, costPrice, hourUnit); } else if(costType == IWorkCenter.COST_PER_PIECE) { BigDecimal costPrice = workCenter.getCostAmount().multiply(producedQty); costSheetLineService.createWorkCenterCostSheetLine(workCenter, prodProcessLine.getPriority(), bomLevel, parentCostSheetLine, producedQty, costPrice, pieceUnit); } } protected BigDecimal getNbCycle(BigDecimal producedQty, BigDecimal capacityPerCycle) { if(capacityPerCycle.compareTo(BigDecimal.ZERO) == 0) { return producedQty; } return producedQty.divide(capacityPerCycle, RoundingMode.CEILING); } }