云
昨天 ac2e2b5af3726ee64dc37c3ebf26989a0346f22a
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
@@ -12,14 +12,29 @@
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessOutputDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdProductBomService;
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductBomService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -30,7 +45,9 @@
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.Comparator;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -42,7 +59,14 @@
public class MesProWorkOrderBomController {
    @Resource
    private MesProTaskService taskService;
    @Resource
    private MesProWorkOrderBomService workOrderBomService;
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
    private MesProWorkOrderProcessService workOrderProcessService;
    @Resource
    private MesMdItemService itemService;
    @Resource
@@ -50,7 +74,15 @@
    @Resource
    private MesMdItemTypeService itemTypeService;
    @Resource
    private MesMdProductBomService productBomService;
    private MesProRouteProductService routeProductService;
    @Resource
    private MesProRouteProductBomService routeProductBomService;
    @Resource
    private MesProRouteProcessService routeProcessService;
    @Resource
    private MesWmMaterialStockMapper materialStockMapper;
    @Resource
    private PurchaseOrderApi purchaseOrderApi;
    @PostMapping("/create")
    @Operation(summary = "创建工单 BOM")
@@ -79,7 +111,6 @@
    @GetMapping("/get")
    @Operation(summary = "获得工单 BOM")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
    public CommonResult<MesProWorkOrderBomRespVO> getWorkOrderBom(@RequestParam("id") Long id) {
        MesProWorkOrderBomDO workOrderBom = workOrderBomService.getWorkOrderBom(id);
        if (workOrderBom == null) {
@@ -90,52 +121,89 @@
    @GetMapping("/page")
    @Operation(summary = "获得工单 BOM 分页")
    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
    public CommonResult<PageResult<MesProWorkOrderBomRespVO>> getWorkOrderBomPage(@Valid MesProWorkOrderBomPageReqVO pageReqVO) {
        PageResult<MesProWorkOrderBomDO> pageResult = workOrderBomService.getWorkOrderBomPage(pageReqVO);
        return success(new PageResult<>(buildWorkOrderBomRespVOList(pageResult.getList()), pageResult.getTotal()));
    }
    @GetMapping("/list-by-task-id")
    @Operation(summary = "获得生产任务工序的投入物料")
    public CommonResult<List<MesProWorkOrderBomRespVO>> getWorkOrderBomListByTaskId(@RequestParam("taskId") Long taskId) {
        MesProTaskDO task = taskService.validateTaskExists(taskId);
        if (task.getWorkOrderId() == null || task.getProcessId() == null) {
            return success(Collections.emptyList());
        }
        List<MesProWorkOrderBomDO> list = workOrderBomService.getWorkOrderBomListByWorkOrderId(task.getWorkOrderId())
                .stream().filter(bom -> Objects.equals(bom.getProcessId(), task.getProcessId())).toList();
        return success(buildWorkOrderBomRespVOList(list));
    }
    @GetMapping("/item-list-by-work-order-id")
    @Operation(summary = "获得工单物料需求列表")
    @Parameter(name = "workOrderId", description = "工单编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
    public CommonResult<List<MesProWorkOrderItemRespVO>> getWorkOrderBomItemListByWorkOrderId(
            @RequestParam("workOrderId") Long workOrderId) {
        // 1. 获取工单 BOM 行
        // 1. 获取工单
        MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(workOrderId);
        if (workOrder == null) {
            return success(Collections.emptyList());
        }
        // 2. 获取工单工序列表(含各工序的投料明细与产出品)
        List<MesProWorkOrderProcessDO> processList = workOrderProcessService
                .getWorkOrderProcessListByWorkOrderId(workOrderId);
        if (CollUtil.isEmpty(processList)) {
            return success(Collections.emptyList());
        }
        // 3. 获取工单 BOM 行(各工序的投料记录,按 processId 分组使用)
        List<MesProWorkOrderBomDO> bomList = workOrderBomService.getWorkOrderBomListByWorkOrderId(workOrderId);
        if (CollUtil.isEmpty(bomList)) {
            return success(Collections.emptyList());
        }
        // 2. 逐层展开,得到叶子物料 itemId → quantity
        Map<Long, BigDecimal> leafItems = buildWorkOrderItems(bomList);
        // 4. 通过产品ID反查工艺路线(工单 routeId 可能为空)
        Long routeId = null;
        if (workOrder.getProductId() != null) {
            MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(workOrder.getProductId());
            if (routeProduct != null) {
                routeId = routeProduct.getRouteId();
            }
        }
        // 5. 按工序产出链逐层展开 BOM,得到叶子物料(每单位成品的用量)
        Map<Long, BigDecimal> leafItems = buildWorkOrderItems(processList, bomList, routeId, workOrder.getProductId());
        if (CollUtil.isEmpty(leafItems)) {
            return success(Collections.emptyList());
        }
        // 3. 拼接 VO 字段
        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(leafItems.keySet());
        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
                convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
        Map<Long, MesMdItemTypeDO> itemTypeMap = itemTypeService.getItemTypeMap(
                convertSet(itemMap.values(), MesMdItemDO::getItemTypeId));
        List<MesProWorkOrderItemRespVO> result = new ArrayList<>(leafItems.size());
        // 6. 乘以工单生产数量,得到总需求
        BigDecimal orderQty = workOrder.getQuantity() != null ? workOrder.getQuantity() : BigDecimal.ONE;
        Map<Long, BigDecimal> totalRequirements = new LinkedHashMap<>();
        for (Map.Entry<Long, BigDecimal> entry : leafItems.entrySet()) {
            MesMdItemDO item = itemMap.get(entry.getKey());
            if (item == null) {
                continue;
            }
            MesProWorkOrderItemRespVO vo = new MesProWorkOrderItemRespVO()
                    .setItemId(item.getId()).setQuantity(entry.getValue())
                    .setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification());
            MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
                    unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
            MapUtils.findAndThen(itemTypeMap, item.getItemTypeId(),
                    itemType -> vo.setItemOrProduct(itemType.getItemOrProduct()));
            result.add(vo);
            totalRequirements.put(entry.getKey(), entry.getValue().multiply(orderQty));
        }
        return success(result);
        // 7. 查询来源为本工单的采购订单明细,按产品汇总在途采购数
        //    sourceType=1 表示来源类型为"生产工单"
        Map<Long, BigDecimal> purchaseOrderQtyMap = new HashMap<>();
        List<ErpPurchaseOrderItemRespDTO> poItems = purchaseOrderApi
                .getPurchaseOrderItemListBySource(1, workOrderId)
                .getCheckedData();
        if (CollUtil.isNotEmpty(poItems)) {
            for (ErpPurchaseOrderItemRespDTO poItem : poItems) {
                // 在途 = 订购数量 - 已入库数量
                BigDecimal inCount = poItem.getInCount() != null ? poItem.getInCount() : BigDecimal.ZERO;
                BigDecimal inTransit = poItem.getCount().subtract(inCount);
                if (inTransit.compareTo(BigDecimal.ZERO) > 0) {
                    purchaseOrderQtyMap.merge(poItem.getProductId(), inTransit, BigDecimal::add);
                }
            }
        }
        // 8. 查询库存,拼接 VO
        return success(buildItemRespVOList(totalRequirements, purchaseOrderQtyMap));
    }
    // ==================== 拼接 VO ====================
@@ -164,42 +232,115 @@
        });
    }
    // ==================== 物料需求逐层展开 ====================
    // ==================== 物料需求展开(工序产出链 + 路由产品 BOM) ====================
    /**
     * 逐层 IN 查询展开工单 BOM 到叶子物料,合并同物料的需求数量
     * 按工序产出链逐层展开物料需求。
     *
     * @param bomList 工单 BOM 列表
     * @return 叶子物料 Map:itemId → 合并后的需求数量
     * <p><b>核心逻辑:</b></p>
     * <ol>
     *   <li>找出关键工序(keyFlag=true),以它的投料为展开起点</li>
     *   <li>构建产出品→工序投料的映射({@code outputItemId → per-unit bomItems})</li>
     *   <li>逐层展开:若物料是某工序的产出品 → 用该工序的投料配方替代;
     *       否则查路由产品 BOM → 用 BOM 子项替代;
     *       两者都没有 → 叶子物料(外购件),累加到结果</li>
     * </ol>
     *
     * <p>这样避免了把中间工序的投料直接纳入起点导致的重复计算问题。</p>
     *
     * @param processList 工单工序列表(按 sort 升序)
     * @param bomList     工单 BOM 行(所有工序的投料明细)
     * @param routeId     工艺路线编号(可为 null,null 时跳过路由 BOM 查询与配比回查)
     * @param productId   产品编号(用于回查该产品的产出配比)
     * @return 叶子物料 Map:itemId → 需求数量
     */
    private Map<Long, BigDecimal> buildWorkOrderItems(List<MesProWorkOrderBomDO> bomList) {
        // 1. 初始化:收集第一层物料 ID 和对应数量
        Map<Long, BigDecimal> currentLayer = new LinkedHashMap<>();
    private Map<Long, BigDecimal> buildWorkOrderItems(List<MesProWorkOrderProcessDO> processList,
                                                       List<MesProWorkOrderBomDO> bomList,
                                                       Long routeId, Long productId) {
        // 1. 工序投料配方:processId → 投料明细
        Map<Long, List<MesProWorkOrderBomDO>> processBomMap = new HashMap<>();
        for (MesProWorkOrderBomDO bom : bomList) {
            currentLayer.merge(bom.getItemId(), bom.getQuantity(), BigDecimal::add);
            if (bom.getProcessId() != null) {
                processBomMap.computeIfAbsent(bom.getProcessId(), key -> new ArrayList<>()).add(bom);
            }
        }
        // 2. 产出物料 → 所属工序 + 每轮产出量(主产出按 1,联产出按路线配比)
        Map<Long, OutputProducer> outputProducerMap = buildOutputProducers(processList, routeId, productId);
        // 3. 取最后一道工序(sort 最大)作为展开起点,逐层向前递推
        MesProWorkOrderProcessDO lastProcess = processList.stream()
                .max(Comparator.comparing(MesProWorkOrderProcessDO::getSort))
                .orElse(null);
        if (lastProcess == null) {
            return Collections.emptyMap();
        }
        // 2. 逐层 IN 查询展开 BOM
        // 3.1 以最后一道工序的投料为起点
        Map<Long, BigDecimal> currentLayer = new LinkedHashMap<>();
        for (MesProWorkOrderBomDO bom : bomList) {
            if (bom.getProcessId().equals(lastProcess.getProcessId())) {
                currentLayer.merge(bom.getItemId(), bom.getQuantity(), BigDecimal::add);
            }
        }
        if (CollUtil.isEmpty(currentLayer)) {
            return Collections.emptyMap();
        }
        // 4. 逐层展开
        Map<Long, BigDecimal> leafItems = new LinkedHashMap<>();
        for (int i = 0; i < Byte.MAX_VALUE && CollUtil.isNotEmpty(currentLayer); i++) {
            // 2.1 批量查询当前层所有物料的子 BOM
            List<MesMdProductBomDO> subBomList = productBomService.getProductBomListByItemIds(currentLayer.keySet());
            // 2.2 按 itemId 分组
            Map<Long, List<MesMdProductBomDO>> subBomMap = convertMultiMap(subBomList, MesMdProductBomDO::getItemId);
            // 2.3 区分叶子节点和非叶子节点
            Map<Long, BigDecimal> nextLayer = new LinkedHashMap<>();
            // 4.1 同一工序的多个联产出先归并,再按轮次只展开一次,避免投料重复计算
            Map<Long, List<Map.Entry<Long, BigDecimal>>> processOutputs = new LinkedHashMap<>();
            // 4.2 批量查询路由产品 BOM(用于非工序产出的物料展开)
            Map<Long, List<MesProRouteProductBomDO>> routeSubBomMap;
            if (routeId != null) {
                List<MesProRouteProductBomDO> routeSubBomList = routeProductBomService
                        .getRouteProductBomListByProductIds(routeId, currentLayer.keySet());
                routeSubBomMap = convertMultiMap(routeSubBomList, MesProRouteProductBomDO::getProductId);
            } else {
                routeSubBomMap = Collections.emptyMap();
            }
            for (Map.Entry<Long, BigDecimal> entry : currentLayer.entrySet()) {
                Long itemId = entry.getKey();
                BigDecimal quantity = entry.getValue();
                List<MesMdProductBomDO> children = subBomMap.get(itemId);
                // 情况一:叶子节点,累加到结果
                if (CollUtil.isEmpty(children)) {
                    leafItems.merge(itemId, quantity, BigDecimal::add);
                BigDecimal qty = entry.getValue();
                // 情况一:物料是某工序的产出品 → 记入该工序,待整层归并后展开
                OutputProducer producer = outputProducerMap.get(itemId);
                if (producer != null && CollUtil.isNotEmpty(processBomMap.get(producer.processId()))) {
                    processOutputs.computeIfAbsent(producer.processId(), key -> new ArrayList<>()).add(entry);
                    continue;
                }
                // 情况二:非叶子,子物料进入下一层
                for (MesMdProductBomDO child : children) {
                    nextLayer.merge(child.getBomItemId(), quantity.multiply(child.getQuantity()), BigDecimal::add);
                // 情况二:路由产品 BOM 中有下级 → 展开
                List<MesProRouteProductBomDO> routeChildren = routeSubBomMap.get(itemId);
                if (CollUtil.isNotEmpty(routeChildren)) {
                    for (MesProRouteProductBomDO child : routeChildren) {
                        nextLayer.merge(child.getItemId(), qty.multiply(child.getQuantity()), BigDecimal::add);
                    }
                    continue;
                }
                // 情况三:叶子物料
                leafItems.merge(itemId, qty, BigDecimal::add);
            }
            // 情况一(续):轮次 = max(各产出需求量 / 该产出每轮产出量),用轮次展开一次该工序投料
            for (Map.Entry<Long, List<Map.Entry<Long, BigDecimal>>> entry : processOutputs.entrySet()) {
                BigDecimal runs = BigDecimal.ZERO;
                for (Map.Entry<Long, BigDecimal> output : entry.getValue()) {
                    OutputProducer producer = outputProducerMap.get(output.getKey());
                    BigDecimal perUnit = producer != null && producer.perUnit().signum() > 0
                            ? producer.perUnit() : BigDecimal.ONE;
                    BigDecimal outputRuns = output.getValue().divide(perUnit, 10, RoundingMode.HALF_UP);
                    if (outputRuns.compareTo(runs) > 0) {
                        runs = outputRuns;
                    }
                }
                for (MesProWorkOrderBomDO child : processBomMap.get(entry.getKey())) {
                    nextLayer.merge(child.getItemId(), runs.multiply(child.getQuantity()), BigDecimal::add);
                }
            }
            currentLayer = nextLayer;
@@ -207,4 +348,114 @@
        return leafItems;
    }
    /**
     * 构建产出物料 → 所属工序 + 每轮产出量。
     * 工序产出快照的第一位为主产出(每轮产出 1),其余产出从路线配置回查配比,缺失按 1。
     */
    private Map<Long, OutputProducer> buildOutputProducers(List<MesProWorkOrderProcessDO> processList,
                                                           Long routeId, Long productId) {
        Map<Long, OutputProducer> outputProducerMap = new HashMap<>();
        if (CollUtil.isEmpty(processList)) {
            return outputProducerMap;
        }
        Map<Long, Long> routeProcessIds = new HashMap<>();
        if (routeId != null) {
            for (MesProRouteProcessDO routeProcess : routeProcessService.getRouteProcessListByRouteId(routeId)) {
                if (routeProcess.getProcessId() != null && routeProcess.getId() != null) {
                    routeProcessIds.putIfAbsent(routeProcess.getProcessId(), routeProcess.getId());
                }
            }
        }
        for (MesProWorkOrderProcessDO process : processList) {
            List<Long> outputIds = parseItemIds(process.getOutputItemId());
            if (CollUtil.isEmpty(outputIds)) {
                continue;
            }
            Map<Long, BigDecimal> ratioMap = new HashMap<>();
            Long routeProcessId = routeProcessIds.get(process.getProcessId());
            if (routeProcessId != null) {
                for (MesProRouteProcessOutputDO config : routeProcessService.getProcessOutputs(routeProcessId, productId)) {
                    if (config.getItemId() != null && config.getQuantity() != null) {
                        ratioMap.putIfAbsent(config.getItemId(), config.getQuantity());
                    }
                }
            }
            for (int i = 0; i < outputIds.size(); i++) {
                Long itemId = outputIds.get(i);
                BigDecimal perUnit = i == 0 ? BigDecimal.ONE : ratioMap.getOrDefault(itemId, BigDecimal.ONE);
                outputProducerMap.putIfAbsent(itemId, new OutputProducer(process.getProcessId(), perUnit));
            }
        }
        return outputProducerMap;
    }
    private List<Long> parseItemIds(String csv) {
        if (csv == null || csv.isBlank()) {
            return Collections.emptyList();
        }
        List<Long> itemIds = new ArrayList<>();
        for (String part : csv.split(",")) {
            String value = part.trim();
            if (!value.isEmpty()) {
                itemIds.add(Long.valueOf(value));
            }
        }
        return itemIds;
    }
    private record OutputProducer(Long processId, BigDecimal perUnit) {
    }
    // ==================== 拼接物料需求 VO(含库存校验) ====================
    private List<MesProWorkOrderItemRespVO> buildItemRespVOList(Map<Long, BigDecimal> requirements,
                                                                  Map<Long, BigDecimal> purchaseOrderQtyMap) {
        // 1. 批量获取物料基础信息
        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(requirements.keySet());
        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
                convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
        Map<Long, MesMdItemTypeDO> itemTypeMap = itemTypeService.getItemTypeMap(
                convertSet(itemMap.values(), MesMdItemDO::getItemTypeId));
        // 2. 批量查询库存,按 itemId 汇总可用量(在库 - 冻结 - 占用)
        Map<Long, BigDecimal> stockMap = new HashMap<>();
        List<MesWmMaterialStockDO> stockList = materialStockMapper.selectListByItemIds(requirements.keySet());
        for (MesWmMaterialStockDO stock : stockList) {
            BigDecimal frozen = stock.getFrozenQuantity() != null ? stock.getFrozenQuantity() : BigDecimal.ZERO;
            BigDecimal reserved = stock.getReservedQuantity() != null ? stock.getReservedQuantity() : BigDecimal.ZERO;
            BigDecimal available = stock.getQuantity().subtract(frozen).subtract(reserved);
            if (available.compareTo(BigDecimal.ZERO) > 0) {
                stockMap.merge(stock.getItemId(), available, BigDecimal::add);
            }
        }
        // 3. 拼接 VO
        List<MesProWorkOrderItemRespVO> result = new ArrayList<>(requirements.size());
        for (Map.Entry<Long, BigDecimal> entry : requirements.entrySet()) {
            MesMdItemDO item = itemMap.get(entry.getKey());
            if (item == null) {
                continue;
            }
            BigDecimal requiredQty = entry.getValue();
            BigDecimal stockQty = stockMap.getOrDefault(item.getId(), BigDecimal.ZERO);
            BigDecimal purchaseQty = purchaseOrderQtyMap.getOrDefault(item.getId(), BigDecimal.ZERO);
            // 充足判断 = 可用库存 + 在途采购数 >= 需求数量
            BigDecimal totalSupply = stockQty.add(purchaseQty);
            MesProWorkOrderItemRespVO vo = new MesProWorkOrderItemRespVO()
                    .setItemId(item.getId()).setQuantity(requiredQty)
                    .setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification())
                    .setStockQuantity(stockQty)
                    .setPurchaseOrderQuantity(purchaseQty)
                    .setStockSufficient(totalSupply.compareTo(requiredQty) >= 0);
            MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
                    unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
            MapUtils.findAndThen(itemTypeMap, item.getItemTypeId(),
                    itemType -> vo.setItemOrProduct(itemType.getItemOrProduct()));
            result.add(vo);
        }
        return result;
    }
}