package com.ruoyi.production.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.basic.mapper.ProductModelMapper; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.production.bean.dto.ProductionBomStructureDto; import com.ruoyi.production.bean.vo.ProductionBomStructureVo; import com.ruoyi.production.mapper.ProductionBomStructureMapper; import com.ruoyi.production.mapper.ProductionOperationTaskMapper; import com.ruoyi.production.mapper.ProductionOrderBomMapper; import com.ruoyi.production.mapper.ProductionOrderMapper; import com.ruoyi.production.mapper.ProductionOrderRoutingMapper; import com.ruoyi.production.mapper.ProductionOrderRoutingOperationMapper; import com.ruoyi.production.mapper.ProductionOrderRoutingOperationParamMapper; import com.ruoyi.production.mapper.ProductionProductMainMapper; import com.ruoyi.production.pojo.ProductionBomStructure; import com.ruoyi.production.pojo.ProductionOperationTask; import com.ruoyi.production.pojo.ProductionOrder; import com.ruoyi.production.pojo.ProductionOrderBom; import com.ruoyi.production.pojo.ProductionOrderRouting; import com.ruoyi.production.pojo.ProductionOrderRoutingOperation; import com.ruoyi.production.pojo.ProductionOrderRoutingOperationParam; import com.ruoyi.production.pojo.ProductionProductMain; import com.ruoyi.production.service.ProductionBomStructureService; import com.ruoyi.production.util.TaskPlanQuantityUtil; import com.ruoyi.technology.mapper.TechnologyOperationMapper; import com.ruoyi.technology.mapper.TechnologyOperationParamMapper; import com.ruoyi.technology.mapper.TechnologyParamMapper; import com.ruoyi.technology.pojo.TechnologyOperation; import com.ruoyi.technology.pojo.TechnologyOperationParam; import com.ruoyi.technology.pojo.TechnologyParam; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** *

* 生产订单BOM产品结构 服务实现类 *

* * @author 芯导软件(江苏)有限公司 * @since 2026-04-21 03:55:52 */ @Service @RequiredArgsConstructor() public class ProductionBomStructureServiceImpl extends ServiceImpl implements ProductionBomStructureService { private final ProductionBomStructureMapper productionBomStructureMapper; private final ProductionOrderBomMapper productionOrderBomMapper; private final ProductionOrderMapper productionOrderMapper; private final ProductionOrderRoutingMapper productionOrderRoutingMapper; private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper; private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper; private final ProductionOperationTaskMapper productionOperationTaskMapper; private final ProductionProductMainMapper productionProductMainMapper; private final ProductModelMapper productModelMapper; private final TechnologyOperationMapper technologyOperationMapper; private final TechnologyOperationParamMapper technologyOperationParamMapper; private final TechnologyParamMapper technologyParamMapper; /** * 按订单BOM查询扁平物料明细。 */ @Override public List listByBomId(Long bomId) { if (bomId == null) { return new ArrayList<>(); } return productionBomStructureMapper.listByBomId(bomId); } /** * 按生产订单查询订单BOM快照的扁平物料明细。 */ @Override public List listByOrderId(Long productionOrderId) { if (productionOrderId == null) { return new ArrayList<>(); } ProductionOrderBom orderBom = productionOrderBomMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductionOrderBom::getProductionOrderId, productionOrderId) .orderByDesc(ProductionOrderBom::getId) .last("limit 1")); if (orderBom == null || orderBom.getId() == null) { return new ArrayList<>(); } return listByBomId(orderBom.getId()); } /** * 全量同步订单BOM快照的扁平物料明细,并按其中的工序重建订单工艺路线快照与工单。 */ @Override @Transactional(rollbackFor = Exception.class) public Boolean addProductionBomStructure(ProductionBomStructureDto dto) { Long orderBomId = dto.getProductionOrderBomId(); if (orderBomId == null) { throw new ServiceException("订单BOM ID不能为空"); } ProductionOrderBom orderBom = productionOrderBomMapper.selectById(orderBomId); if (orderBom == null) { throw new ServiceException("订单BOM不存在"); } Long productionOrderId = dto.getProductionOrderId() != null ? dto.getProductionOrderId() : orderBom.getProductionOrderId(); List detailList = dto.getDetailList() == null ? Collections.emptyList() : dto.getDetailList(); List dbList = this.list(new LambdaQueryWrapper() .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId)); Set dbIds = dbList.stream().map(ProductionBomStructure::getId).collect(Collectors.toSet()); Set submittedIds = new HashSet<>(); Set relationKeys = new HashSet<>(); List insertList = new ArrayList<>(); List updateList = new ArrayList<>(); for (ProductionBomStructureDto item : detailList) { Long productModelId = item.getProductModelId(); Long technologyOperationId = item.getTechnologyOperationId(); if (productModelId == null) { throw new ServiceException("请选择物料规格"); } if (productModelMapper.selectById(productModelId) == null) { throw new ServiceException("物料规格不存在"); } if (technologyOperationId == null) { throw new ServiceException("请选择消耗工序"); } if (technologyOperationMapper.selectById(technologyOperationId) == null) { throw new ServiceException("消耗工序不存在"); } if (productModelId.equals(orderBom.getProductModelId())) { throw new ServiceException("成品规格不能作为自身的物料"); } if (!relationKeys.add(productModelId + "#" + technologyOperationId)) { throw new ServiceException("同一物料规格在同一工序下不能重复配置"); } ProductionBomStructure entity = new ProductionBomStructure(); entity.setProductionOrderBomId(orderBomId); entity.setProductionOrderId(productionOrderId); entity.setProductModelId(productModelId); entity.setTechnologyOperationId(technologyOperationId); // 扁平快照不再使用层级与数量语义 entity.setParentId(null); entity.setUnitQuantity(null); entity.setDemandedQuantity(null); entity.setUnit(null); if (item.getId() == null) { insertList.add(entity); } else { if (!dbIds.contains(item.getId())) { throw new ServiceException("订单BOM物料明细不存在或不属于当前订单BOM"); } entity.setId(item.getId()); submittedIds.add(item.getId()); updateList.add(entity); } } Set deleteIds = new HashSet<>(dbIds); deleteIds.removeAll(submittedIds); if (!deleteIds.isEmpty()) { this.removeByIds(deleteIds); } if (!updateList.isEmpty()) { this.updateBatchById(updateList); } if (!insertList.isEmpty()) { this.saveBatch(insertList); } syncRoutingAndTaskByStructure(orderBomId, productionOrderId); return true; } /** * 订单BOM物料变化后,按其工序集合重建订单工艺路线工序快照与工单计划数量。 */ private void syncRoutingAndTaskByStructure(Long orderBomId, Long productionOrderId) { if (orderBomId == null) { return; } ProductionOrderBom orderBom = productionOrderBomMapper.selectById(orderBomId); if (orderBom == null) { return; } Long currentProductionOrderId = productionOrderId != null ? productionOrderId : orderBom.getProductionOrderId(); if (currentProductionOrderId == null) { return; } ProductionOrder productionOrder = productionOrderMapper.selectById(currentProductionOrderId); if (productionOrder == null) { return; } List structureList = this.list( Wrappers.lambdaQuery() .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId) .orderByAsc(ProductionBomStructure::getId)); Long rootProductModelId = orderBom.getProductModelId() != null ? orderBom.getProductModelId() : productionOrder.getProductModelId(); // 同步生产工艺路线快照 syncRoutingOperationsByBom(currentProductionOrderId, productionOrder, orderBom, structureList, rootProductModelId); // 同步工单计划数量:扁平BOM无数量配置,计划数量统一取订单数量 syncTaskPlanQuantity(currentProductionOrderId, TaskPlanQuantityUtil.resolveTaskPlanQuantity(productionOrder)); } private void syncTaskPlanQuantity(Long productionOrderId, BigDecimal planQuantity) { List taskList = productionOperationTaskMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionOperationTask::getProductionOrderId, productionOrderId) .orderByAsc(ProductionOperationTask::getId)); if (taskList == null || taskList.isEmpty()) { return; } for (ProductionOperationTask task : taskList) { if (task == null || task.getId() == null) { continue; } if (compareDecimal(task.getPlanQuantity(), planQuantity) == 0) { continue; } ProductionOperationTask update = new ProductionOperationTask(); update.setId(task.getId()); update.setPlanQuantity(planQuantity); productionOperationTaskMapper.updateById(update); } } private void syncRoutingOperationsByBom(Long productionOrderId, ProductionOrder productionOrder, ProductionOrderBom orderBom, List structureList, Long rootProductModelId) { ProductionOrderRouting orderRouting = getOrCreateOrderRoutingSnapshot(productionOrderId, productionOrder, orderBom, rootProductModelId); List desiredOperationList = buildDesiredRoutingOperationList(structureList, rootProductModelId); List existingOperationList = productionOrderRoutingOperationMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionOrderRoutingOperation::getOrderRoutingId, orderRouting.getId()) .eq(ProductionOrderRoutingOperation::getProductionOrderId, productionOrderId) .orderByAsc(ProductionOrderRoutingOperation::getDragSort) .orderByAsc(ProductionOrderRoutingOperation::getId)); Map> existingBucketMap = buildExistingRoutingOperationBucketMap(existingOperationList); List finalOperationList = new ArrayList<>(); for (ProductionOrderRoutingOperation desiredOperation : desiredOperationList) { String bucketKey = buildRoutingOperationBucketKey( desiredOperation.getTechnologyOperationId(), desiredOperation.getProductModelId()); Deque matchedQueue = existingBucketMap.get(bucketKey); ProductionOrderRoutingOperation matchedOperation = matchedQueue == null ? null : matchedQueue.pollFirst(); if (matchedOperation == null) { matchedOperation = insertRoutingOperationSnapshot(orderRouting.getId(), productionOrderId, desiredOperation); } else { updateRoutingOperationSnapshotIfNecessary(matchedOperation, orderRouting.getId(), productionOrderId, desiredOperation); } finalOperationList.add(matchedOperation); } for (Deque queue : existingBucketMap.values()) { while (queue != null && !queue.isEmpty()) { removeRoutingOperationSnapshot(queue.pollFirst()); } } syncRoutingOperationTasks(productionOrderId, finalOperationList); } private ProductionOrderRouting getOrCreateOrderRoutingSnapshot(Long productionOrderId, ProductionOrder productionOrder, ProductionOrderBom orderBom, Long rootProductModelId) { ProductionOrderRouting orderRouting = productionOrderRoutingMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductionOrderRouting::getProductionOrderId, productionOrderId) .orderByDesc(ProductionOrderRouting::getId) .last("limit 1")); if (orderRouting == null) { orderRouting = new ProductionOrderRouting(); orderRouting.setProductionOrderId(productionOrderId); orderRouting.setProductModelId(rootProductModelId); orderRouting.setTechnologyRoutingId(productionOrder == null ? null : productionOrder.getTechnologyRoutingId()); orderRouting.setBomId(orderBom == null ? null : orderBom.getBomId()); orderRouting.setOrderBomId(orderBom == null ? null : orderBom.getId()); productionOrderRoutingMapper.insert(orderRouting); return orderRouting; } ProductionOrderRouting update = new ProductionOrderRouting(); update.setId(orderRouting.getId()); boolean changed = false; if (!Objects.equals(orderRouting.getProductModelId(), rootProductModelId)) { update.setProductModelId(rootProductModelId); orderRouting.setProductModelId(rootProductModelId); changed = true; } Long technologyRoutingId = productionOrder == null ? null : productionOrder.getTechnologyRoutingId(); if (!Objects.equals(orderRouting.getTechnologyRoutingId(), technologyRoutingId)) { update.setTechnologyRoutingId(technologyRoutingId); orderRouting.setTechnologyRoutingId(technologyRoutingId); changed = true; } Long bomId = orderBom == null ? null : orderBom.getBomId(); if (!Objects.equals(orderRouting.getBomId(), bomId)) { update.setBomId(bomId); orderRouting.setBomId(bomId); changed = true; } Long orderBomId = orderBom == null ? null : orderBom.getId(); if (!Objects.equals(orderRouting.getOrderBomId(), orderBomId)) { update.setOrderBomId(orderBomId); orderRouting.setOrderBomId(orderBomId); changed = true; } if (changed) { productionOrderRoutingMapper.updateById(update); } return orderRouting; } /** * 扁平BOM按工序去重生成期望的订单工序列表, * 顺序取物料明细中工序首次出现的顺序,产出规格固定为订单成品规格。 */ private List buildDesiredRoutingOperationList(List structureList, Long rootProductModelId) { if (structureList == null || structureList.isEmpty()) { return Collections.emptyList(); } Set uniqueOperationIdList = new LinkedHashSet<>(); for (ProductionBomStructure structure : structureList) { if (structure == null || structure.getTechnologyOperationId() == null) { continue; } uniqueOperationIdList.add(structure.getTechnologyOperationId()); } List desiredOperationList = new ArrayList<>(uniqueOperationIdList.size()); int dragSort = 1; for (Long operationId : uniqueOperationIdList) { TechnologyOperation technologyOperation = getTechnologyOperation(operationId); ProductionOrderRoutingOperation routingOperation = new ProductionOrderRoutingOperation(); routingOperation.setProductModelId(rootProductModelId); routingOperation.setTechnologyOperationId(operationId); routingOperation.setOperationName(technologyOperation == null ? null : technologyOperation.getName()); routingOperation.setIsQuality(technologyOperation == null ? null : technologyOperation.getIsQuality()); routingOperation.setIsProduction(technologyOperation == null ? null : technologyOperation.getIsProduction()); routingOperation.setType(technologyOperation == null ? null : technologyOperation.getType()); routingOperation.setDragSort(dragSort++); desiredOperationList.add(routingOperation); } return desiredOperationList; } private Map> buildExistingRoutingOperationBucketMap(List existingOperationList) { Map> existingBucketMap = new LinkedHashMap<>(); if (existingOperationList == null || existingOperationList.isEmpty()) { return existingBucketMap; } for (ProductionOrderRoutingOperation routingOperation : existingOperationList) { String bucketKey = buildRoutingOperationBucketKey( routingOperation.getTechnologyOperationId(), routingOperation.getProductModelId()); existingBucketMap.computeIfAbsent(bucketKey, key -> new ArrayDeque<>()).addLast(routingOperation); } return existingBucketMap; } private ProductionOrderRoutingOperation insertRoutingOperationSnapshot(Long orderRoutingId, Long productionOrderId, ProductionOrderRoutingOperation desiredOperation) { ProductionOrderRoutingOperation insert = new ProductionOrderRoutingOperation(); insert.setOrderRoutingId(orderRoutingId); insert.setProductionOrderId(productionOrderId); insert.setProductModelId(desiredOperation.getProductModelId()); insert.setTechnologyOperationId(desiredOperation.getTechnologyOperationId()); insert.setOperationName(desiredOperation.getOperationName()); insert.setIsQuality(desiredOperation.getIsQuality()); insert.setIsProduction(desiredOperation.getIsProduction()); insert.setType(desiredOperation.getType()); insert.setDragSort(desiredOperation.getDragSort()); productionOrderRoutingOperationMapper.insert(insert); syncRoutingOperationParams(insert.getId(), productionOrderId, insert.getTechnologyOperationId()); return insert; } private void updateRoutingOperationSnapshotIfNecessary(ProductionOrderRoutingOperation currentOperation, Long orderRoutingId, Long productionOrderId, ProductionOrderRoutingOperation desiredOperation) { if (currentOperation == null || currentOperation.getId() == null) { return; } ProductionOrderRoutingOperation update = new ProductionOrderRoutingOperation(); update.setId(currentOperation.getId()); boolean changed = false; if (!Objects.equals(currentOperation.getOrderRoutingId(), orderRoutingId)) { update.setOrderRoutingId(orderRoutingId); currentOperation.setOrderRoutingId(orderRoutingId); changed = true; } if (!Objects.equals(currentOperation.getProductionOrderId(), productionOrderId)) { update.setProductionOrderId(productionOrderId); currentOperation.setProductionOrderId(productionOrderId); changed = true; } if (!Objects.equals(currentOperation.getProductModelId(), desiredOperation.getProductModelId())) { update.setProductModelId(desiredOperation.getProductModelId()); currentOperation.setProductModelId(desiredOperation.getProductModelId()); changed = true; } if (!Objects.equals(currentOperation.getTechnologyOperationId(), desiredOperation.getTechnologyOperationId())) { update.setTechnologyOperationId(desiredOperation.getTechnologyOperationId()); currentOperation.setTechnologyOperationId(desiredOperation.getTechnologyOperationId()); changed = true; } if (!Objects.equals(currentOperation.getOperationName(), desiredOperation.getOperationName())) { update.setOperationName(desiredOperation.getOperationName()); currentOperation.setOperationName(desiredOperation.getOperationName()); changed = true; } if (!Objects.equals(currentOperation.getIsQuality(), desiredOperation.getIsQuality())) { update.setIsQuality(desiredOperation.getIsQuality()); currentOperation.setIsQuality(desiredOperation.getIsQuality()); changed = true; } if (!Objects.equals(currentOperation.getIsProduction(), desiredOperation.getIsProduction())) { update.setIsProduction(desiredOperation.getIsProduction()); currentOperation.setIsProduction(desiredOperation.getIsProduction()); changed = true; } if (!Objects.equals(currentOperation.getType(), desiredOperation.getType())) { update.setType(desiredOperation.getType()); currentOperation.setType(desiredOperation.getType()); changed = true; } if (!Objects.equals(currentOperation.getDragSort(), desiredOperation.getDragSort())) { update.setDragSort(desiredOperation.getDragSort()); currentOperation.setDragSort(desiredOperation.getDragSort()); changed = true; } if (changed) { productionOrderRoutingOperationMapper.updateById(update); } } private void removeRoutingOperationSnapshot(ProductionOrderRoutingOperation routingOperation) { if (routingOperation == null || routingOperation.getId() == null) { return; } ProductionOperationTask task = productionOperationTaskMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductionOperationTask::getProductionOrderRoutingOperationId, routingOperation.getId()) .last("limit 1")); if (task != null) { validateTaskCanRemove(task); productionOperationTaskMapper.deleteById(task.getId()); } productionOrderRoutingOperationParamMapper.delete( Wrappers.lambdaQuery() .eq(ProductionOrderRoutingOperationParam::getProductionOrderRoutingOperationId, routingOperation.getId())); productionOrderRoutingOperationMapper.deleteById(routingOperation.getId()); } private void syncRoutingOperationTasks(Long productionOrderId, List routingOperationList) { if (routingOperationList == null || routingOperationList.isEmpty()) { return; } List routingOperationIdList = routingOperationList.stream() .map(ProductionOrderRoutingOperation::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); if (routingOperationIdList.isEmpty()) { return; } Map taskByRoutingOperationId = productionOperationTaskMapper.selectList( Wrappers.lambdaQuery() .in(ProductionOperationTask::getProductionOrderRoutingOperationId, routingOperationIdList) .orderByAsc(ProductionOperationTask::getId)) .stream() .filter(item -> item != null && item.getProductionOrderRoutingOperationId() != null) .collect(Collectors.toMap( ProductionOperationTask::getProductionOrderRoutingOperationId, item -> item, (left, right) -> left, LinkedHashMap::new)); for (int i = 0; i < routingOperationList.size(); i++) { ProductionOrderRoutingOperation routingOperation = routingOperationList.get(i); if (routingOperation == null || routingOperation.getId() == null) { continue; } boolean shouldHaveTask = i == routingOperationList.size() - 1 || Boolean.TRUE.equals(routingOperation.getIsProduction()); ProductionOperationTask existingTask = taskByRoutingOperationId.get(routingOperation.getId()); if (shouldHaveTask) { if (existingTask == null) { ProductionOperationTask task = new ProductionOperationTask(); task.setProductionOrderId(productionOrderId); task.setProductionOrderRoutingOperationId(routingOperation.getId()); task.setPlanQuantity(BigDecimal.ZERO); task.setCompleteQuantity(BigDecimal.ZERO); task.setWorkOrderNo(generateNextTaskNo()); task.setStatus(2); productionOperationTaskMapper.insert(task); } continue; } if (existingTask != null) { validateTaskCanRemove(existingTask); productionOperationTaskMapper.deleteById(existingTask.getId()); } } } private void validateTaskCanRemove(ProductionOperationTask task) { if (task == null || task.getId() == null) { return; } if (defaultDecimal(task.getCompleteQuantity()).compareTo(BigDecimal.ZERO) > 0) { throw new ServiceException("工序已产生报工记录,无法根据 BOM 变更删除对应工序快照" + task.getWorkOrderNo()); } long reportCount = productionProductMainMapper.selectCount( Wrappers.lambdaQuery() .eq(ProductionProductMain::getProductionOperationTaskId, task.getId())); if (reportCount > 0) { throw new ServiceException("工序已产生报工记录,无法根据 BOM 变更删除对应工单"); } } private void syncRoutingOperationParams(Long routingOperationId, Long productionOrderId, Long technologyOperationId) { if (routingOperationId == null || technologyOperationId == null) { return; } List operationParamList = technologyOperationParamMapper.selectList( Wrappers.lambdaQuery() .eq(TechnologyOperationParam::getTechnologyOperationId, technologyOperationId) .orderByAsc(TechnologyOperationParam::getId)); for (TechnologyOperationParam operationParam : operationParamList) { TechnologyParam technologyParam = technologyParamMapper.selectById(operationParam.getTechnologyParamId()); if (technologyParam == null) { continue; } ProductionOrderRoutingOperationParam snapshot = new ProductionOrderRoutingOperationParam(); snapshot.setProductionOrderId(productionOrderId); snapshot.setProductionOrderRoutingOperationId(routingOperationId); snapshot.setTechnologyOperationId(operationParam.getTechnologyOperationId()); snapshot.setTechnologyOperationParamId(operationParam.getId()); snapshot.setParamId(technologyParam.getId()); snapshot.setParamCode(technologyParam.getParamCode()); snapshot.setParamName(technologyParam.getParamName()); snapshot.setParamType(technologyParam.getParamType()); snapshot.setParamFormat(technologyParam.getParamFormat()); snapshot.setUnit(technologyParam.getUnit()); snapshot.setIsRequired(technologyParam.getIsRequired()); snapshot.setRemark(technologyParam.getRemark()); snapshot.setStandardValue(operationParam.getStandardValue()); productionOrderRoutingOperationParamMapper.insert(snapshot); } } private String buildRoutingOperationBucketKey(Long operationId, Long outputProductModelId) { return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId); } private TechnologyOperation getTechnologyOperation(Long technologyOperationId) { if (technologyOperationId == null) { return null; } return technologyOperationMapper.selectById(technologyOperationId); } private String generateNextTaskNo() { String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); ProductionOperationTask latestTask = productionOperationTaskMapper.selectOne( Wrappers.lambdaQuery() .likeRight(ProductionOperationTask::getWorkOrderNo, "GD" + datePrefix) .orderByDesc(ProductionOperationTask::getWorkOrderNo) .last("limit 1")); int sequenceNumber = 1; if (latestTask != null && latestTask.getWorkOrderNo() != null && latestTask.getWorkOrderNo().startsWith("GD" + datePrefix)) { try { sequenceNumber = Integer.parseInt(latestTask.getWorkOrderNo().substring(("GD" + datePrefix).length())) + 1; } catch (NumberFormatException ignored) { sequenceNumber = 1; } } return "GD" + String.format("%s%03d", datePrefix, sequenceNumber); } private BigDecimal defaultDecimal(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } private int compareDecimal(BigDecimal left, BigDecimal right) { return defaultDecimal(left).compareTo(defaultDecimal(right)); } }