/** * *************************************************************************** * Copyright (c) 2010 Qcadoo Limited * Project: Qcadoo MES * Version: 1.4 * * This file is part of Qcadoo. * * Qcadoo is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************** */ package com.qcadoo.mes.operationCostCalculations; import static com.google.common.base.Preconditions.checkArgument; import java.math.BigDecimal; import java.math.MathContext; import java.util.Map; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.qcadoo.mes.basic.ParameterService; import com.qcadoo.mes.costNormsForOperation.constants.CalculateOperationCostMode; import com.qcadoo.mes.costNormsForOperation.constants.CalculationOperationComponentFields; import com.qcadoo.mes.costNormsForOperation.constants.TechnologyOperationComponentFieldsCNFO; import com.qcadoo.mes.operationTimeCalculations.OperationWorkTime; import com.qcadoo.mes.operationTimeCalculations.OperationWorkTimeService; import com.qcadoo.mes.operationTimeCalculations.dto.OperationTimes; import com.qcadoo.mes.operationTimeCalculations.dto.OperationTimesContainer; import com.qcadoo.mes.technologies.ProductionLinesService; import com.qcadoo.mes.technologies.ProductQuantitiesService; import com.qcadoo.mes.technologies.constants.TechnologiesConstants; import com.qcadoo.mes.technologies.constants.TechnologyFields; import com.qcadoo.mes.technologies.constants.TechnologyOperationComponentFields; import com.qcadoo.mes.technologies.dto.ProductQuantitiesHolder; import com.qcadoo.model.api.BigDecimalUtils; import com.qcadoo.model.api.DataDefinition; import com.qcadoo.model.api.DataDefinitionService; import com.qcadoo.model.api.Entity; import com.qcadoo.model.api.EntityTree; import com.qcadoo.model.api.EntityTreeNode; import com.qcadoo.model.api.IntegerUtils; import com.qcadoo.model.api.NumberService; @Service public class OperationsCostCalculationServiceImpl implements OperationsCostCalculationService { private static final String L_PRODUCTION_BALANCE = "productionBalance"; private static final String L_COST_CALCULATION = "costCalculation"; private static final String L_CALCULATION_OPERATION_COMPONENTS = "calculationOperationComponents"; private static final String L_ORDER = "order"; private static final String L_TECHNOLOGY = "technology"; private static final String L_QUANTITY = "quantity"; private static final String L_PRODUCTION_COST_MARGIN = "productionCostMargin"; private static final String L_CALCULATE_OPERATION_COSTS_MODE = "calculateOperationCostsMode"; private static final String L_PRODUCTION_LINE = "productionLine"; private static final String L_INCLUDE_ADDITIONAL_TIME = "includeAdditionalTime"; private static final String L_INCLUDE_TPZ = "includeTPZ"; private static final String L_TOTAL_LABOR_HOURLY_COSTS = "totalLaborHourlyCosts"; private static final String L_TOTAL_MACHINE_HOURLY_COSTS = "totalMachineHourlyCosts"; private static final String L_TOTAL_PIECEWORK_COSTS = "totalPieceworkCosts"; private static final String L_MACHINE_HOURLY_COST = "machineHourlyCost"; private static final String L_LABOR_HOURLY_COST = "laborHourlyCost"; private static final String L_OPERATION_MACHINE_COST = "operationMachineCost"; private static final String L_OPERATION_LABOR_COST = "operationLaborCost"; private static final String L_OPERATION_COST = "operationCost"; private static final String L_OPERATION_MARGIN_COST = "operationMarginCost"; private static final String L_TOTAL_OPERATION_COST = "totalOperationCost"; private static final String L_PIECES = "pieces"; private static final String L_TOTAL_LABOR_OPERATION_COST_WITH_MARGIN = "totalLaborOperationCostWithMargin"; private static final String L_TOTAL_MACHINE_OPERATION_COST_WITH_MARGIN = "totalMachineOperationCostWithMargin"; private static final Set<String> L_COST_KEYS = Sets.newHashSet(CalculationOperationComponentFields.LABOR_HOURLY_COST, CalculationOperationComponentFields.MACHINE_HOURLY_COST); @Autowired private DataDefinitionService dataDefinitionService; @Autowired private NumberService numberService; @Autowired private ProductQuantitiesService productQuantitiesService; @Autowired private ProductionLinesService productionLinesService; @Autowired private OperationWorkTimeService operationWorkTimeService; @Autowired private OperationCostCalculationTreeBuilder operationCostCalculationTreeBuilder; @Autowired private ParameterService parameterService; @Override public void calculateOperationsCost(final Entity costCalculationOrProductionBalance, boolean hourlyCostFromOperation) { checkArgument(costCalculationOrProductionBalance != null, "entity is null"); String modelName = costCalculationOrProductionBalance.getDataDefinition().getName(); checkArgument(L_COST_CALCULATION.equals(modelName) || L_PRODUCTION_BALANCE.equals(modelName), "unsupported entity type"); DataDefinition costCalculationOrProductionBalanceDD = costCalculationOrProductionBalance.getDataDefinition(); Entity order = costCalculationOrProductionBalance.getBelongsToField(L_ORDER); Entity technology = costCalculationOrProductionBalance.getBelongsToField(L_TECHNOLOGY); BigDecimal quantity = BigDecimalUtils.convertNullToZero(costCalculationOrProductionBalance.getDecimalField(L_QUANTITY)); BigDecimal productionCostMargin = BigDecimalUtils.convertNullToZero(costCalculationOrProductionBalance .getDecimalField(L_PRODUCTION_COST_MARGIN)); CalculateOperationCostMode calculateOperationCostMode = CalculateOperationCostMode .parseString(costCalculationOrProductionBalance.getStringField(L_CALCULATE_OPERATION_COSTS_MODE)); if (order != null) { Entity technologyFromOrder = order.getBelongsToField(L_TECHNOLOGY); technology = dataDefinitionService.get(TechnologiesConstants.PLUGIN_IDENTIFIER, TechnologiesConstants.MODEL_TECHNOLOGY).get(technologyFromOrder.getId()); } ProductQuantitiesHolder productQuantitiesAndOperationRuns = getProductQuantitiesAndOperationRuns(technology, quantity, costCalculationOrProductionBalance); if (order != null) { order.setField(L_TECHNOLOGY, technology); } Entity copyCostCalculationOrProductionBalance = operationCostCalculationTreeBuilder .copyTechnologyTree(costCalculationOrProductionBalance); Entity yetAnotherCostCalculationOrProductionBalance = costCalculationOrProductionBalanceDD .save(copyCostCalculationOrProductionBalance); Entity newCostCalculationOrProductionBalance = costCalculationOrProductionBalanceDD .get(yetAnotherCostCalculationOrProductionBalance.getId()); EntityTree calculationOperationComponents = newCostCalculationOrProductionBalance .getTreeField(L_CALCULATION_OPERATION_COMPONENTS); checkArgument(calculationOperationComponents != null, "given operation components is null"); if (CalculateOperationCostMode.PIECEWORK.equals(calculateOperationCostMode)) { if (calculationOperationComponents.isEmpty()) { costCalculationOrProductionBalance.addError(costCalculationOrProductionBalanceDD.getField(L_ORDER), "costCalculation.lackOfTreeComponents"); costCalculationOrProductionBalance.addError(costCalculationOrProductionBalanceDD.getField(L_TECHNOLOGY), "costCalculation.lackOfTreeComponents"); return; } BigDecimal totalPieceworkCost = estimateCostCalculationForPieceWork(calculationOperationComponents.getRoot(), productionCostMargin, quantity, productQuantitiesAndOperationRuns.getOperationRuns()); costCalculationOrProductionBalance.setField(L_TOTAL_PIECEWORK_COSTS, numberService.setScale(totalPieceworkCost)); } else if (CalculateOperationCostMode.HOURLY.equals(calculateOperationCostMode)) { Entity productionLine = costCalculationOrProductionBalance.getBelongsToField(L_PRODUCTION_LINE); Boolean includeTPZ = costCalculationOrProductionBalance.getBooleanField(L_INCLUDE_TPZ); Boolean includeAdditionalTime = costCalculationOrProductionBalance.getBooleanField(L_INCLUDE_ADDITIONAL_TIME); Map<Long, Integer> workstations = getWorkstationsMapsForOperationsComponent(copyCostCalculationOrProductionBalance, productionLine); OperationTimesContainer operationTimes = operationWorkTimeService.estimateOperationsWorkTimes( calculationOperationComponents, productQuantitiesAndOperationRuns.getOperationRuns(), includeTPZ, includeAdditionalTime, workstations, true); Map<String, BigDecimal> resultsMap = estimateCostCalculationForHourly(calculationOperationComponents.getRoot(), productionCostMargin, quantity, operationTimes, hourlyCostFromOperation); costCalculationOrProductionBalance.setField(L_TOTAL_MACHINE_HOURLY_COSTS, numberService.setScale(resultsMap.get(CalculationOperationComponentFields.MACHINE_HOURLY_COST))); costCalculationOrProductionBalance.setField(L_TOTAL_LABOR_HOURLY_COSTS, numberService.setScale(resultsMap.get(CalculationOperationComponentFields.LABOR_HOURLY_COST))); } else { throw new IllegalStateException("Unsupported calculateOperationCostMode"); } costCalculationOrProductionBalance.setField(L_CALCULATION_OPERATION_COMPONENTS, calculationOperationComponents); } private ProductQuantitiesHolder getProductQuantitiesAndOperationRuns(final Entity technology, final BigDecimal quantity, final Entity costCalculationOrProductionBalance) { return productQuantitiesService.getProductComponentQuantities(technology, quantity); } @Override public Map<String, BigDecimal> estimateCostCalculationForHourly(final EntityTreeNode calculationOperationComponent, final BigDecimal productionCostMargin, final BigDecimal plannedQuantity, final OperationTimesContainer realizationTimes, final boolean hourlyCostFromOperation) { checkArgument(calculationOperationComponent != null, "given operationComponent is empty"); Map<String, BigDecimal> costs = Maps.newHashMapWithExpectedSize(L_COST_KEYS.size()); MathContext mathContext = numberService.getMathContext(); for (String costKey : L_COST_KEYS) { costs.put(costKey, BigDecimal.ZERO); } for (EntityTreeNode child : calculationOperationComponent.getChildren()) { Map<String, BigDecimal> unitCosts = estimateCostCalculationForHourly(child, productionCostMargin, plannedQuantity, realizationTimes, hourlyCostFromOperation); for (String costKey : L_COST_KEYS) { BigDecimal unitCost = costs.get(costKey).add(unitCosts.get(costKey), mathContext); costs.put(costKey, numberService.setScale(unitCost)); } } OperationTimes operationTimes = realizationTimes.get(calculationOperationComponent.getId()); Map<String, BigDecimal> costsForSingleOperation = estimateHourlyCostCalculationForSingleOperation(operationTimes, productionCostMargin, hourlyCostFromOperation); saveGeneratedValues(costsForSingleOperation, calculationOperationComponent, true, operationTimes.getTimes(), null); costs.put(L_MACHINE_HOURLY_COST, costs.get(L_MACHINE_HOURLY_COST).add(costsForSingleOperation.get(L_OPERATION_MACHINE_COST), mathContext)); costs.put(L_LABOR_HOURLY_COST, costs.get(L_LABOR_HOURLY_COST).add(costsForSingleOperation.get(L_OPERATION_LABOR_COST), mathContext)); return costs; } private Map<String, BigDecimal> estimateHourlyCostCalculationForSingleOperation(final OperationTimes operationTimes, final BigDecimal productionCostMargin, boolean hourlyCostFromOperation) { Map<String, BigDecimal> costs = Maps.newHashMap(); MathContext mathContext = numberService.getMathContext(); Entity calculationOperationComponent = operationTimes.getOperation(); Entity technologyOperationComponent = calculationOperationComponent .getBelongsToField(CalculationOperationComponentFields.TECHNOLOGY_OPERATION_COMPONENT); OperationWorkTime operationWorkTimes = operationTimes.getTimes(); BigDecimal machineHourlyCost = BigDecimal.ZERO; BigDecimal laborHourlyCost = BigDecimal.ZERO; if (hourlyCostFromOperation) { machineHourlyCost = BigDecimalUtils.convertNullToZero(technologyOperationComponent .getField(TechnologyOperationComponentFieldsCNFO.MACHINE_HOURLY_COST)); laborHourlyCost = BigDecimalUtils.convertNullToZero(technologyOperationComponent .getField(TechnologyOperationComponentFieldsCNFO.LABOR_HOURLY_COST)); } else { machineHourlyCost = BigDecimalUtils.convertNullToZero(parameterService.getParameter().getDecimalField( "averageMachineHourlyCostPB")); laborHourlyCost = BigDecimalUtils.convertNullToZero(parameterService.getParameter() .getDecimalField("averageLaborHourlyCostPB")); } BigDecimal durationMachine = BigDecimal.valueOf(operationWorkTimes.getMachineWorkTime()); BigDecimal durationLabor = BigDecimal.valueOf(operationWorkTimes.getLaborWorkTime()); BigDecimal durationMachineInHours = durationMachine.divide(BigDecimal.valueOf(3600), mathContext); BigDecimal durationLaborInHours = durationLabor.divide(BigDecimal.valueOf(3600), mathContext); BigDecimal operationMachineCost = durationMachineInHours.multiply(machineHourlyCost, mathContext); BigDecimal operationLaborCost = durationLaborInHours.multiply(laborHourlyCost, mathContext); BigDecimal totalMachineOperationCostWithMargin = operationMachineCost.add( operationMachineCost.multiply(productionCostMargin.divide(BigDecimal.valueOf(100), mathContext), mathContext), mathContext); BigDecimal totalLaborOperationCostWithMargin = operationLaborCost.add( operationLaborCost.multiply(productionCostMargin.divide(BigDecimal.valueOf(100), mathContext), mathContext), mathContext); BigDecimal operationCost = operationMachineCost.add(operationLaborCost, mathContext); BigDecimal operationMarginCost = operationCost.multiply( productionCostMargin.divide(BigDecimal.valueOf(100), mathContext), mathContext); costs.put(L_MACHINE_HOURLY_COST, numberService.setScale(machineHourlyCost)); costs.put(L_LABOR_HOURLY_COST, numberService.setScale(laborHourlyCost)); costs.put(L_OPERATION_MACHINE_COST, numberService.setScale(operationMachineCost)); costs.put(L_OPERATION_LABOR_COST, numberService.setScale(operationLaborCost)); costs.put(L_OPERATION_COST, numberService.setScale(operationCost)); costs.put(L_OPERATION_MARGIN_COST, numberService.setScale(operationMarginCost)); costs.put(L_TOTAL_MACHINE_OPERATION_COST_WITH_MARGIN, numberService.setScale(totalMachineOperationCostWithMargin)); costs.put(L_TOTAL_LABOR_OPERATION_COST_WITH_MARGIN, numberService.setScale(totalLaborOperationCostWithMargin)); return costs; } @Override public BigDecimal estimateCostCalculationForPieceWork(final EntityTreeNode calculationOperationComponent, final BigDecimal productionCostMargin, final BigDecimal plannedQuantity, final Map<Long, BigDecimal> operationRuns) { BigDecimal cost = BigDecimal.ZERO; for (EntityTreeNode child : calculationOperationComponent.getChildren()) { cost = cost.add(estimateCostCalculationForPieceWork(child, productionCostMargin, plannedQuantity, operationRuns), numberService.getMathContext()); } // FIXME MAKU unnecessary mapping of whole entity - we need only their id! We can increase performance by replacing line // below by a query projection Entity technologyOperationComponent = calculationOperationComponent .getBelongsToField(CalculationOperationComponentFields.TECHNOLOGY_OPERATION_COMPONENT); BigDecimal operationRunsForOperation = operationRuns.get(technologyOperationComponent.getId()); Map<String, BigDecimal> costsForSingleOperation = estimatePieceworkCostCalculationForSingleOperation( calculationOperationComponent, productionCostMargin, operationRunsForOperation); cost = cost.add(costsForSingleOperation.get(L_OPERATION_COST)); saveGeneratedValues(costsForSingleOperation, calculationOperationComponent, false, null, operationRunsForOperation); return cost; } private Map<String, BigDecimal> estimatePieceworkCostCalculationForSingleOperation( final EntityTreeNode calculationOperationComponent, final BigDecimal productionCostMargin, final BigDecimal operationRuns) { Map<String, BigDecimal> costs = Maps.newHashMap(); BigDecimal pieceworkCost = BigDecimalUtils.convertNullToZero(calculationOperationComponent .getDecimalField(CalculationOperationComponentFields.PIECEWORK_COST)); BigDecimal numberOfOperations = BigDecimalUtils.convertNullToOne(calculationOperationComponent .getField(CalculationOperationComponentFields.NUMBER_OF_OPERATIONS)); BigDecimal pieceworkCostPerOperation = pieceworkCost.divide(numberOfOperations, numberService.getMathContext()); BigDecimal operationCost = operationRuns.multiply(pieceworkCostPerOperation, numberService.getMathContext()); BigDecimal operationMarginCost = operationCost.multiply(productionCostMargin.divide(BigDecimal.valueOf(100), numberService.getMathContext())); BigDecimal totalOperationCost = numberService.setScale(operationCost.add(operationMarginCost, numberService.getMathContext())); costs.put(L_OPERATION_COST, numberService.setScale(operationCost)); costs.put(L_OPERATION_MARGIN_COST, numberService.setScale(operationMarginCost)); costs.put(L_PIECES, numberService.setScale(operationRuns)); costs.put(L_TOTAL_OPERATION_COST, totalOperationCost); return costs; } private void saveGeneratedValues(final Map<String, BigDecimal> costs, final Entity calculationOperationComponent, boolean areHourly, final OperationWorkTime operationWorkTimes, final BigDecimal operationRuns) { if (areHourly) { calculationOperationComponent.setField(CalculationOperationComponentFields.DURATION, new BigDecimal( operationWorkTimes.getDuration(), numberService.getMathContext())); calculationOperationComponent.setField(CalculationOperationComponentFields.MACHINE_HOURLY_COST, costs.get(L_MACHINE_HOURLY_COST)); calculationOperationComponent.setField(CalculationOperationComponentFields.LABOR_HOURLY_COST, costs.get(L_LABOR_HOURLY_COST)); calculationOperationComponent.setField(CalculationOperationComponentFields.TOTAL_MACHINE_OPERATION_COST, costs.get(L_OPERATION_MACHINE_COST)); calculationOperationComponent.setField(CalculationOperationComponentFields.TOTAL_LABOR_OPERATION_COST, costs.get(L_OPERATION_LABOR_COST)); calculationOperationComponent.setField(CalculationOperationComponentFields.TOTAL_MACHINE_OPERATION_COST_WITH_MARGIN, costs.get(L_TOTAL_MACHINE_OPERATION_COST_WITH_MARGIN)); calculationOperationComponent.setField(CalculationOperationComponentFields.TOTAL_LABOR_OPERATION_COST_WITH_MARGIN, costs.get(L_TOTAL_LABOR_OPERATION_COST_WITH_MARGIN)); } else { calculationOperationComponent.setField(CalculationOperationComponentFields.PIECES, numberService.setScale(operationRuns)); } BigDecimal operationCost = costs.get(L_OPERATION_COST); BigDecimal operationMarginCost = costs.get(L_OPERATION_MARGIN_COST); calculationOperationComponent.setField(CalculationOperationComponentFields.OPERATION_COST, numberService.setScale(operationCost)); calculationOperationComponent.setField(CalculationOperationComponentFields.OPERATION_MARGIN_COST, numberService.setScale(operationMarginCost)); calculationOperationComponent.setField(CalculationOperationComponentFields.TOTAL_OPERATION_COST, numberService.setScale(operationCost.add(operationMarginCost, numberService.getMathContext()))); calculationOperationComponent.getDataDefinition().save(calculationOperationComponent); } private Map<Long, Integer> getWorkstationsMapsForOperationsComponent(final Entity costCalculationOrProductionBalance, final Entity productionLine) { Entity order = costCalculationOrProductionBalance.getBelongsToField(L_ORDER); if (order == null) { return getWorkstationsFromTechnology(costCalculationOrProductionBalance.getBelongsToField(L_TECHNOLOGY), productionLine); } else { return getWorkstationsFromOrder(order); } } private Map<Long, Integer> getWorkstationsFromTechnology(final Entity technology, final Entity productionLine) { Map<Long, Integer> workstations = Maps.newHashMap(); if (parameterService.getParameter().getBooleanField("workstationsQuantityFromProductionLine")) { for (Entity operComp : technology.getHasManyField(TechnologyFields.OPERATION_COMPONENTS)) { workstations.put(operComp.getId(), productionLinesService.getWorkstationTypesCount(operComp, productionLine)); } } else { for (Entity operComp : technology.getHasManyField(TechnologyFields.OPERATION_COMPONENTS)) { workstations.put(operComp.getId(), IntegerUtils.convertNullToZero(operComp .getIntegerField(TechnologyOperationComponentFields.QUANTITY_OF_WORKSTATIONS))); } } return workstations; } private Map<Long, Integer> getWorkstationsFromOrder(final Entity order) { Map<Long, Integer> workstations = Maps.newHashMap(); for (Entity technologyOperationComponent : order.getBelongsToField(L_TECHNOLOGY).getHasManyField( TechnologyFields.OPERATION_COMPONENTS)) { workstations.put(technologyOperationComponent.getId(), IntegerUtils.convertNullToZero(technologyOperationComponent .getIntegerField(TechnologyOperationComponentFields.QUANTITY_OF_WORKSTATIONS))); } return workstations; } }