/**
* ***************************************************************************
* 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.costCalculation;
import static com.qcadoo.mes.costNormsForOperation.constants.CalculateOperationCostMode.HOURLY;
import static com.qcadoo.mes.costNormsForOperation.constants.CalculateOperationCostMode.PIECEWORK;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.qcadoo.mes.costCalculation.constants.CostCalculationFields;
import com.qcadoo.mes.costCalculation.constants.SourceOfOperationCosts;
import com.qcadoo.mes.costNormsForMaterials.ProductsCostCalculationService;
import com.qcadoo.mes.costNormsForOperation.constants.CalculateOperationCostMode;
import com.qcadoo.mes.operationCostCalculations.OperationsCostCalculationService;
import com.qcadoo.mes.technologies.constants.OperationProductOutComponentFields;
import com.qcadoo.mes.technologies.constants.TechnologyFields;
import com.qcadoo.mes.technologies.constants.TechnologyOperationComponentFields;
import com.qcadoo.mes.timeNormsForOperations.constants.TechnologyOperationComponentFieldsTNFO;
import com.qcadoo.model.api.BigDecimalUtils;
import com.qcadoo.model.api.Entity;
import com.qcadoo.model.api.NumberService;
@Service
public class CostCalculationServiceImpl implements CostCalculationService {
@Autowired
private OperationsCostCalculationService operationsCostCalculationService;
@Autowired
private ProductsCostCalculationService productsCostCalculationService;
@Autowired
private NumberService numberService;
private final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100);
@Override
public Entity calculateTotalCost(final Entity entity) {
entity.setField(CostCalculationFields.DATE, new Date());
// FIXME MAKU beware of side effects - order of computations matter!
calculateOperationsAndProductsCosts(entity);
final BigDecimal productionCosts = calculateProductionCost(entity);
calculateMarginsAndOverheads(entity, productionCosts);
final BigDecimal effectiveQuantity = getEffectiveQuantity(entity);
calculateTotalCosts(entity, productionCosts, effectiveQuantity);
return entity.getDataDefinition().save(entity);
}
@Override
public void calculateOperationsAndProductsCosts(final Entity entity) {
boolean hourlyCostFromOperation = true;
String sourceOfOperationCosts = entity.getStringField("sourceOfOperationCosts");
if (sourceOfOperationCosts != null
&& SourceOfOperationCosts.PARAMETERS.equals(SourceOfOperationCosts.parseString(sourceOfOperationCosts))) {
hourlyCostFromOperation = false;
}
operationsCostCalculationService.calculateOperationsCost(entity, hourlyCostFromOperation);
final String sourceOfMaterialCosts = entity.getStringField(CostCalculationFields.SOURCE_OF_MATERIAL_COSTS);
productsCostCalculationService.calculateTotalProductsCost(entity, sourceOfMaterialCosts);
}
@Override
public void calculateTotalCosts(final Entity entity, final BigDecimal productionCosts, final BigDecimal quantity) {
final BigDecimal materialCosts = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_MATERIAL_COSTS));
final BigDecimal totalOverhead = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_OVERHEAD));
final BigDecimal totalTechnicalProductionCosts = productionCosts.add(materialCosts, numberService.getMathContext());
final BigDecimal totalCosts = totalOverhead.add(totalTechnicalProductionCosts, numberService.getMathContext());
entity.setField(CostCalculationFields.TOTAL_TECHNICAL_PRODUCTION_COSTS,
numberService.setScale(totalTechnicalProductionCosts));
entity.setField(CostCalculationFields.TOTAL_COSTS, numberService.setScale(totalCosts, 2));
if (BigDecimal.ZERO.compareTo(BigDecimalUtils.convertNullToZero(quantity)) != 0) {
final BigDecimal totalCostsPerUnit = totalCosts.divide(quantity, numberService.getMathContext());
entity.setField(CostCalculationFields.TOTAL_COST_PER_UNIT, numberService.setScale(totalCostsPerUnit, 2));
}
}
private BigDecimal getEffectiveQuantity(final Entity entity) {
Entity technology = entity.getBelongsToField(CostCalculationFields.TECHNOLOGY);
Entity rootOperation = technology.getTreeField(TechnologyFields.OPERATION_COMPONENTS).getRoot();
BigDecimal effectiveQuantity = entity.getDecimalField(CostCalculationFields.QUANTITY);
boolean areProductQuantitiesDivisible = rootOperation
.getBooleanField(TechnologyOperationComponentFieldsTNFO.ARE_PRODUCT_QUANTITIES_DIVISIBLE);
if (!areProductQuantitiesDivisible) {
List<Entity> opocs = rootOperation
.getHasManyField(TechnologyOperationComponentFields.OPERATION_PRODUCT_OUT_COMPONENTS);
if (opocs.size() == 1) {
Entity opoc = opocs.get(0);
BigDecimal quantityInSingleCycle = opoc.getDecimalField(OperationProductOutComponentFields.QUANTITY);
BigDecimal quantity = effectiveQuantity.divide(quantityInSingleCycle, new MathContext(2, RoundingMode.UP));
effectiveQuantity = quantityInSingleCycle.multiply(quantity, numberService.getMathContext());
}
}
return effectiveQuantity;
}
private void calculateMarginsAndOverheads(final Entity entity, final BigDecimal productionCosts) {
final BigDecimal materialCostMargin = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.MATERIAL_COST_MARGIN));
final BigDecimal productionCostMargin = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.PRODUCTION_COST_MARGIN));
final BigDecimal materialCosts = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_MATERIAL_COSTS));
final BigDecimal productionCostMarginValue = productionCosts.multiply(productionCostMargin,
numberService.getMathContext()).divide(ONE_HUNDRED, numberService.getMathContext());
final BigDecimal materialCostMarginValue = materialCosts.multiply(materialCostMargin, numberService.getMathContext())
.divide(ONE_HUNDRED, numberService.getMathContext());
entity.setField(CostCalculationFields.PRODUCTION_COST_MARGIN_VALUE, numberService.setScale(productionCostMarginValue));
entity.setField(CostCalculationFields.MATERIAL_COST_MARGIN_VALUE, numberService.setScale(materialCostMarginValue));
calculateTotalOverhead(entity);
}
@Override
public void calculateTotalOverhead(final Entity entity) {
final BigDecimal productionCostMarginValue = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.PRODUCTION_COST_MARGIN_VALUE));
final BigDecimal materialCostMarginValue = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.MATERIAL_COST_MARGIN_VALUE));
final BigDecimal additionalOverhead = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.ADDITIONAL_OVERHEAD));
final BigDecimal totalOverhead = productionCostMarginValue.add(materialCostMarginValue, numberService.getMathContext())
.add(additionalOverhead, numberService.getMathContext());
entity.setField(CostCalculationFields.ADDITIONAL_OVERHEAD_VALUE, numberService.setScale(additionalOverhead));
entity.setField(CostCalculationFields.TOTAL_OVERHEAD, numberService.setScale(totalOverhead));
}
@Override
public BigDecimal calculateProductionCost(final Entity entity) {
BigDecimal productionCosts = null;
final CalculateOperationCostMode operationMode = CalculateOperationCostMode.parseString(entity
.getStringField(CostCalculationFields.CALCULATE_OPERATION_COSTS_MODE));
if (HOURLY.equals(operationMode)) {
BigDecimal totalMachine = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_MACHINE_HOURLY_COSTS));
BigDecimal totalLabor = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_LABOR_HOURLY_COSTS));
productionCosts = totalMachine.add(totalLabor, numberService.getMathContext());
} else if (PIECEWORK.equals(operationMode)) {
productionCosts = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_PIECEWORK_COSTS));
} else {
throw new IllegalStateException("Unsupported calculateOperationCostsMode");
}
return productionCosts;
}
@Override
public void calculateSellPriceOverhead(final Entity entity) {
final BigDecimal totalCostPerUnit = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_COST_PER_UNIT));
final BigDecimal registrationPriceOverhead = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.REGISTRATION_PRICE_OVERHEAD));
final BigDecimal profit = BigDecimalUtils.convertNullToZero(entity.getDecimalField(CostCalculationFields.PROFIT));
final BigDecimal registrationPriceOverheadValue = totalCostPerUnit.multiply(registrationPriceOverhead,
numberService.getMathContext()).divide(ONE_HUNDRED, numberService.getMathContext());
final BigDecimal profitValue = totalCostPerUnit.add(registrationPriceOverheadValue, numberService.getMathContext())
.multiply(profit, numberService.getMathContext()).divide(ONE_HUNDRED, numberService.getMathContext());
entity.setField(CostCalculationFields.REGISTRATION_PRICE_OVERHEAD_VALUE,
numberService.setScale(registrationPriceOverheadValue));
entity.setField(CostCalculationFields.PROFIT_VALUE, numberService.setScale(profitValue));
final BigDecimal totalCostsPerUnit = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_COST_PER_UNIT));
final BigDecimal technicalProductionCosts = totalCostsPerUnit.add(registrationPriceOverheadValue,
numberService.getMathContext());
entity.setField(CostCalculationFields.TECHNICAL_PRODUCTION_COSTS, numberService.setScale(technicalProductionCosts, 2));
}
@Override
public void calculateSellPrice(final Entity entity) {
final BigDecimal totalCostPerUnit = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.TOTAL_COST_PER_UNIT));
final BigDecimal registrationPriceOverheadValue = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.REGISTRATION_PRICE_OVERHEAD_VALUE));
final BigDecimal profitValue = BigDecimalUtils.convertNullToZero(entity
.getDecimalField(CostCalculationFields.PROFIT_VALUE));
final BigDecimal sellPriceValue = totalCostPerUnit.add(registrationPriceOverheadValue, numberService.getMathContext())
.add(profitValue, numberService.getMathContext());
entity.setField(CostCalculationFields.SELL_PRICE_VALUE, numberService.setScale(sellPriceValue, 2));
}
}