/**
* ***************************************************************************
* 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.operationTimeCalculations;
import static com.qcadoo.mes.technologies.constants.TechnologyOperationComponentFields.TECHNOLOGY;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.qcadoo.localization.api.utils.DateUtils;
import com.qcadoo.mes.basic.ParameterService;
import com.qcadoo.mes.technologies.ProductQuantitiesService;
import com.qcadoo.mes.technologies.ProductionLinesService;
import com.qcadoo.mes.technologies.constants.TechnologiesConstants;
import com.qcadoo.mes.technologies.constants.TechnologyFields;
import com.qcadoo.mes.technologies.constants.TechnologyOperationComponentEntityType;
import com.qcadoo.mes.technologies.constants.TechnologyOperationComponentFields;
import com.qcadoo.mes.technologies.dto.OperationProductComponentWithQuantityContainer;
import com.qcadoo.mes.timeNormsForOperations.constants.TechnologyOperationComponentFieldsTNFO;
import com.qcadoo.model.api.BigDecimalUtils;
import com.qcadoo.model.api.Entity;
import com.qcadoo.model.api.EntityTreeNode;
import com.qcadoo.model.api.NumberService;
@Service
public class OrderRealizationTimeServiceImpl implements OrderRealizationTimeService {
private static final String L_ORDER = "order";
@Autowired
private ProductQuantitiesService productQuantitiesService;
@Autowired
private NumberService numberService;
@Autowired
private ProductionLinesService productionLinesService;
@Autowired
private ParameterService parameterService;
@Override
public Object setDateToField(final Date date) {
return new SimpleDateFormat(DateUtils.L_DATE_TIME_FORMAT, Locale.getDefault()).format(date);
}
@Override
@Transactional
public int estimateOperationTimeConsumption(final EntityTreeNode operationComponent, final BigDecimal plannedQuantity,
final boolean includeTpz, final boolean includeAdditionalTime, final Entity productionLine) {
Entity technology = operationComponent.getBelongsToField(TECHNOLOGY);
Map<Long, BigDecimal> operationRunsFromProductionQuantities = Maps.newHashMap();
OperationProductComponentWithQuantityContainer productComponentQuantities = productQuantitiesService
.getProductComponentQuantities(technology, plannedQuantity, operationRunsFromProductionQuantities);
return evaluateOperationTime(operationComponent, includeTpz, includeAdditionalTime,
operationRunsFromProductionQuantities, productionLine, false, productComponentQuantities);
}
@Override
@Transactional
public int estimateMaxOperationTimeConsumptionForWorkstation(final EntityTreeNode operationComponent,
final BigDecimal plannedQuantity, final boolean includeTpz, final boolean includeAdditionalTime,
final Entity productionLine) {
Entity technology = operationComponent.getBelongsToField(TECHNOLOGY);
Map<Long, BigDecimal> operationRunsFromProductionQuantities = Maps.newHashMap();
OperationProductComponentWithQuantityContainer productComponentQuantities = productQuantitiesService
.getProductComponentQuantities(technology, plannedQuantity, operationRunsFromProductionQuantities);
return evaluateOperationTime(operationComponent, includeTpz, includeAdditionalTime,
operationRunsFromProductionQuantities, productionLine, true, productComponentQuantities);
}
@Override
public Map<Entity, Integer> estimateOperationTimeConsumptions(final Entity entity, final BigDecimal plannedQuantity,
final boolean includeTpz, final boolean includeAdditionalTime, final Entity productionLine) {
return estimateOperationTimeConsumptions(entity, plannedQuantity, includeTpz, includeAdditionalTime, productionLine,
false);
}
@Override
public Map<Entity, Integer> estimateMaxOperationTimeConsumptionsForWorkstations(final Entity entity,
final BigDecimal plannedQuantity, final boolean includeTpz, final boolean includeAdditionalTime,
final Entity productionLine) {
return estimateOperationTimeConsumptions(entity, plannedQuantity, includeTpz, includeAdditionalTime, productionLine, true);
}
private Map<Entity, Integer> estimateOperationTimeConsumptions(final Entity entity, final BigDecimal plannedQuantity,
final boolean includeTpz, final boolean includeAdditionalTime, final Entity productionLine,
final boolean maxForWorkstation) {
Map<Entity, Integer> operationDurations = new HashMap<Entity, Integer>();
String entityType = entity.getDataDefinition().getName();
Entity technology;
List<Entity> operationComponents;
if (TechnologiesConstants.MODEL_TECHNOLOGY.equals(entityType)) {
technology = entity;
operationComponents = technology.getTreeField(TechnologyFields.OPERATION_COMPONENTS);
} else if (L_ORDER.equals(entityType)) {
technology = entity.getBelongsToField(TECHNOLOGY);
operationComponents = technology.getTreeField(TechnologyFields.OPERATION_COMPONENTS);
} else {
throw new IllegalStateException("Entity has to be either order or technology");
}
Map<Long, BigDecimal> operationRunsFromProductionQuantities = Maps.newHashMap();
productQuantitiesService
.getProductComponentQuantities(technology, plannedQuantity, operationRunsFromProductionQuantities);
for (Entity operationComponent : operationComponents) {
evaluateTimesConsideringOperationCanBeReferencedTechnology(operationDurations, operationComponent, includeTpz,
includeAdditionalTime, operationRunsFromProductionQuantities, productionLine, maxForWorkstation);
}
return operationDurations;
}
private void evaluateTimesConsideringOperationCanBeReferencedTechnology(final Map<Entity, Integer> operationDurations,
final Entity operationComponent, final boolean includeTpz, final boolean includeAdditionalTime,
final Map<Long, BigDecimal> operationRuns, final Entity productionLine, final boolean maxForWorkstation) {
String entityType = operationComponent.getStringField(TechnologyOperationComponentFields.ENTITY_TYPE);
if (TechnologyOperationComponentEntityType.REFERENCE_TECHNOLOGY.getStringValue().equals(entityType)) {
for (Entity operComp : operationComponent.getBelongsToField(TechnologyOperationComponentFields.REFERENCE_TECHNOLOGY)
.getTreeField(TechnologyFields.OPERATION_COMPONENTS)) {
evaluateTimesConsideringOperationCanBeReferencedTechnology(operationDurations, operComp, includeTpz,
includeAdditionalTime, operationRuns, productionLine, maxForWorkstation);
}
} else {
int duration = evaluateSingleOperationTime(operationComponent, includeTpz, includeAdditionalTime, operationRuns,
productionLine, maxForWorkstation);
operationDurations.put(operationComponent, duration);
}
}
private int evaluateOperationTime(final Entity operationComponent, final boolean includeTpz,
final boolean includeAdditionalTime, final Map<Long, BigDecimal> operationRuns, final Entity productionLine,
final boolean maxForWorkstation, final OperationProductComponentWithQuantityContainer productComponentQuantities) {
String entityType = operationComponent.getStringField(TechnologyOperationComponentFields.ENTITY_TYPE);
if (TechnologyOperationComponentEntityType.REFERENCE_TECHNOLOGY.getStringValue().equals(entityType)) {
EntityTreeNode actualOperationComponent = operationComponent
.getBelongsToField(TechnologyOperationComponentFields.REFERENCE_TECHNOLOGY)
.getTreeField(TechnologyFields.OPERATION_COMPONENTS).getRoot();
return evaluateOperationTime(actualOperationComponent, includeTpz, includeAdditionalTime, operationRuns,
productionLine, maxForWorkstation, productComponentQuantities);
} else if (TechnologyOperationComponentEntityType.OPERATION.getStringValue().equals(entityType)) {
int operationTime = evaluateSingleOperationTime(operationComponent, includeTpz, includeAdditionalTime, operationRuns,
productionLine, maxForWorkstation);
int offset = 0;
List<Entity> childs = Lists.newArrayList(operationComponent.getHasManyField("children"));
for (Entity child : childs) {
int childTime = evaluateOperationTime(child, includeTpz, includeAdditionalTime, operationRuns, productionLine,
maxForWorkstation, productComponentQuantities);
if ("02specified".equals(child.getStringField("nextOperationAfterProducedType"))) {
int childTimeTotal = evaluateSingleOperationTime(child, includeTpz, includeAdditionalTime, operationRuns,
productionLine, true);
int childTimeForQuantity = evaluateSingleOperationTimeIncludedNextOperationAfterProducedQuantity(child,
includeTpz, false, operationRuns, productionLine, true, productComponentQuantities);
int difference = childTimeTotal - childTimeForQuantity;
childTime -= difference;
}
if (childTime > offset) {
offset = childTime;
}
}
if (TechnologiesConstants.MODEL_TECHNOLOGY_OPERATION_COMPONENT.equals(operationComponent.getDataDefinition()
.getName())) {
Entity techOperCompTimeCalculation = operationComponent
.getBelongsToField(TechnologyOperationComponentFieldsTNFO.TECH_OPER_COMP_TIME_CALCULATION);
if (techOperCompTimeCalculation != null) {
techOperCompTimeCalculation.setField("operationOffSet", offset);
techOperCompTimeCalculation.setField("effectiveOperationRealizationTime", operationTime);
techOperCompTimeCalculation.getDataDefinition().save(techOperCompTimeCalculation);
}
}
return offset + operationTime;
}
throw new IllegalStateException("entityType has to be either operation or referenceTechnology");
}
private Integer retrieveWorkstationTypesCount(final Entity operationComponent, final Entity productionLine) {
if (StringUtils.isEmpty(operationComponent.getBelongsToField(TechnologyOperationComponentFields.TECHNOLOGY)
.getStringField(TechnologyFields.TECHNOLOGY_TYPE))) {
if (parameterService.getParameter().getBooleanField("workstationsQuantityFromProductionLine")) {
return productionLinesService.getWorkstationTypesCount(operationComponent, productionLine);
} else {
return getIntegerValue(operationComponent
.getIntegerField(TechnologyOperationComponentFields.QUANTITY_OF_WORKSTATIONS));
}
} else {
return getIntegerValue(operationComponent
.getIntegerField(TechnologyOperationComponentFields.QUANTITY_OF_WORKSTATIONS));
}
}
@Override
public int evaluateSingleOperationTime(Entity operationComponent, final boolean includeTpz,
final boolean includeAdditionalTime, final Map<Long, BigDecimal> operationRuns, final Entity productionLine,
final boolean maxForWorkstation) {
operationComponent = operationComponent.getDataDefinition().get(operationComponent.getId());
BigDecimal cycles = operationRuns.get(operationComponent.getId());
if (cycles == null) {
Map<Long, BigDecimal> operationRunsFromProductionQuantities = Maps.newHashMap();
OperationProductComponentWithQuantityContainer productComponentQuantities = productQuantitiesService
.getProductComponentQuantities(operationComponent
.getBelongsToField(TechnologyOperationComponentFields.TECHNOLOGY),
new BigDecimal("56", numberService.getMathContext()), operationRunsFromProductionQuantities);
cycles = operationRunsFromProductionQuantities.get(operationComponent.getId());
}
return evaluateOperationDurationOutOfCycles(cycles, operationComponent, productionLine, maxForWorkstation, includeTpz,
includeAdditionalTime);
}
@Override
public int evaluateSingleOperationTimeIncludedNextOperationAfterProducedQuantity(Entity operationComponent,
final boolean includeTpz, final boolean includeAdditionalTime, final Map<Long, BigDecimal> operationRuns,
final Entity productionLine, final boolean maxForWorkstation,
final OperationProductComponentWithQuantityContainer productComponentQuantities) {
operationComponent = operationComponent.getDataDefinition().get(operationComponent.getId());
BigDecimal cycles = BigDecimal.ONE;
BigDecimal nextOperationAfterProducedQuantity = BigDecimalUtils.convertNullToZero(operationComponent
.getDecimalField("nextOperationAfterProducedQuantity"));
BigDecimal productComponentQuantity = productComponentQuantities.get(getOutputProduct(operationComponent));
Entity technologyOperationComponent = getTechnologyOperationComponent(operationComponent);
if (nextOperationAfterProducedQuantity.compareTo(productComponentQuantity) != 1) {
cycles = getQuantityCyclesNeededToProducedNextOperationAfterProducedQuantity(technologyOperationComponent,
nextOperationAfterProducedQuantity);
} else {
cycles = operationRuns.get(technologyOperationComponent.getId());
}
return evaluateOperationDurationOutOfCycles(cycles, operationComponent, productionLine, maxForWorkstation, includeTpz,
includeAdditionalTime);
}
private Entity getTechnologyOperationComponent(final Entity operationComponent) {
return operationComponent;
}
private Entity getOutputProduct(final Entity operationComponent) {
return productQuantitiesService.getOutputProductsFromOperationComponent(operationComponent);
}
private BigDecimal getQuantityCyclesNeededToProducedNextOperationAfterProducedQuantity(final Entity operationComponent,
final BigDecimal nextOperationAfterProducedQuantity) {
MathContext mc = numberService.getMathContext();
Entity technology = operationComponent.getBelongsToField("technology");
Map<Long, BigDecimal> operationRunsFromProductionQuantities = Maps.newHashMap();
OperationProductComponentWithQuantityContainer productQuantities = productQuantitiesService
.getProductComponentQuantities(technology, BigDecimal.ONE, operationRunsFromProductionQuantities);
BigDecimal operationsRunsForOneMainProduct = operationRunsFromProductionQuantities.get(operationComponent.getId());
BigDecimal quantityOutputProductProduced = productQuantities.get(getOutputProduct(operationComponent));
BigDecimal cycles = operationsRunsForOneMainProduct.multiply(nextOperationAfterProducedQuantity, mc).divide(
quantityOutputProductProduced, mc);
return numberService.setScale(cycles);
}
@Override
public int evaluateOperationDurationOutOfCycles(final BigDecimal cycles, final Entity operationComponent,
final Entity productionLine, final boolean maxForWorkstation, final boolean includeTpz,
final boolean includeAdditionalTime) {
boolean isTjDivisable = operationComponent.getBooleanField("isTjDivisible");
Integer workstationsCount = retrieveWorkstationTypesCount(operationComponent, productionLine);
BigDecimal cyclesPerOperation = cycles;
if (maxForWorkstation) {
cyclesPerOperation = cycles.divide(BigDecimal.valueOf(workstationsCount), numberService.getMathContext());
if (!isTjDivisable) {
cyclesPerOperation = cyclesPerOperation.setScale(0, RoundingMode.CEILING);
}
}
int tj = getIntegerValue(operationComponent.getField("tj"));
int operationTime = cyclesPerOperation.multiply(BigDecimal.valueOf(tj), numberService.getMathContext()).intValue();
if (includeTpz) {
int tpz = getIntegerValue(operationComponent.getField("tpz"));
operationTime += (maxForWorkstation ? tpz : (tpz * workstationsCount));
}
if (includeAdditionalTime) {
int additionalTime = getIntegerValue(operationComponent.getField("timeNextOperation"));
operationTime += (maxForWorkstation ? additionalTime : (additionalTime * workstationsCount));
}
return operationTime;
}
@Override
public BigDecimal getBigDecimalFromField(final Object value, final Locale locale) {
try {
DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(locale);
format.setParseBigDecimal(true);
return new BigDecimal(format.parse(value.toString()).doubleValue());
} catch (ParseException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
private Integer getIntegerValue(final Object value) {
return value == null ? Integer.valueOf(0) : (Integer) value;
}
@Override
public int estimateOperationTimeConsumption(EntityTreeNode operationComponent, BigDecimal plannedQuantity,
Entity productionLine) {
return estimateOperationTimeConsumption(operationComponent, plannedQuantity, true, true, productionLine);
}
}