fix(mes): 工艺路线产品补齐报工入仓库/库区/库位,修复编辑不回显
问题:工艺路线产品表单里「报工仓库 / 报工库区 / 报工库位」三级联动早就做好了,
列表也展示 warehouseName / locationName / areaName,但整条后端链路都没这几个字段:
· mes_pro_route_product 表没有 warehouse_id / location_id / area_id 三列
· MesProRouteProductDO 没有对应属性
· MesProRouteProductSaveReqVO 没有入参 → 前端选了也存不进库
· MesProRouteProductRespVO 没有出参 → 编辑时下拉框拿不到 id,无法回显
表现为:填了保存不报错但重开就空,列表三列永远空白。
改动:
- 新增迁移 sql/mysql/route_product_warehouse_20261030.sql(幂等)
补三列 warehouse_id / location_id / area_id,各带普通索引
对应主数据:mes_wm_warehouse → mes_wm_warehouse_location → mes_wm_warehouse_area
- DO / SaveReqVO 补三个 id;RespVO 补三个 id + 三个 name
- Controller 批量查三级名称回填(getWarehouseMap / getWarehouseLocationMap / getWarehouseAreaMap)
- Service 新增 validateWarehouseChain:
· 仓库填了就校验存在
· 库位填了就校验「库区→库位」「仓库→库区」父子关系有没有串
· 只填仓库不填库区库位是允许的(部分场景只需知道入哪个仓)
验证(先在本地 IDEA 环境跑通,再上线):
改前 warehouseId=null → 更新 → 回查 warehouseId=16 生产车间线边库
列表返回「生产车间线边库 / 生产车间线边库通用库区 / 生产车间线边库默认库位」
传错误层级返回「库位不属于所选库区」
同类问题排查:写脚本比对了前端 data.ts 里所有 fieldName 与 API 类型,
未发现其它「前端有字段、后端没有」的情况(其余差异均为 keyword/file 等前端临时字段)。
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================================ |
| | | -- å·¥èºè·¯çº¿äº§åï¼è¡¥é½ãæ¥å·¥å
¥ä»åº / å
¥åºåºåº / å
¥åºåºä½ãä¸å |
| | | -- çææ¶é´ï¼2026-10-30 |
| | | -- |
| | | -- é®é¢ï¼å端工èºè·¯çº¿äº§åè¡¨åæ©å·²åäºä¸çº§èå¨ï¼warehouseId â locationId â areaIdï¼ï¼ |
| | | -- å表ä¹å±ç¤º warehouseName / locationName / areaNameï¼ |
| | | -- 使°æ®åºè¡¨ãDOãSaveReqVOãRespVO å
¨é½æ²¡æè¿ä¸ä¸ªåæ®µï¼ |
| | | -- 导è´ï¼1) ç¼è¾æ¶ä¸ææ¡ä¸åæ¾ 2) å¡«äºä¹åä¸è¿åº 3) å表ä¸åæ°¸è¿ä¸ºç©º |
| | | -- |
| | | -- åæ³ï¼è¡¥ä¸åï¼å
³è MES ä»å¨ä¸»æ°æ® |
| | | -- warehouse_id â mes_wm_warehouse.id ï¼æ¥å·¥å
¥ä»åºï¼ |
| | | -- location_id â mes_wm_warehouse_location.idï¼å
¥åºåºåºï¼ |
| | | -- area_id â mes_wm_warehouse_area.id ï¼å
¥åºåºä½ï¼ |
| | | -- å±çº§å
³ç³»ï¼ä»åº â åºåº â åºä½ï¼åºåºæå¨ä»åºä¸ï¼åºä½æå¨åºåºä¸ |
| | | -- |
| | | -- å¹çï¼å
æ¥ information_schema å ALTERï¼å¯é夿§è¡ |
| | | -- ============================================================ |
| | | |
| | | DROP PROCEDURE IF EXISTS sp_route_product_add_col; |
| | | DELIMITER $$ |
| | | CREATE PROCEDURE sp_route_product_add_col(IN p_col VARCHAR(64), IN p_comment VARCHAR(255), IN p_after VARCHAR(64)) |
| | | BEGIN |
| | | IF NOT EXISTS (SELECT 1 FROM information_schema.columns |
| | | WHERE table_schema = DATABASE() AND table_name = 'mes_pro_route_product' AND column_name = p_col) THEN |
| | | SET @s = CONCAT('ALTER TABLE `mes_pro_route_product` ADD COLUMN `', p_col, '` bigint NULL COMMENT ''', p_comment, ''' AFTER `', p_after, '`'); |
| | | PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st; |
| | | END IF; |
| | | IF NOT EXISTS (SELECT 1 FROM information_schema.statistics |
| | | WHERE table_schema = DATABASE() AND table_name = 'mes_pro_route_product' AND index_name = CONCAT('idx_', p_col)) THEN |
| | | SET @s2 = CONCAT('CREATE INDEX `idx_', p_col, '` ON `mes_pro_route_product` (`', p_col, '`)'); |
| | | PREPARE st2 FROM @s2; EXECUTE st2; DEALLOCATE PREPARE st2; |
| | | END IF; |
| | | END$$ |
| | | DELIMITER ; |
| | | |
| | | CALL sp_route_product_add_col('warehouse_id', 'æ¥å·¥å
¥ä»åºï¼å
³è mes_wm_warehouse.idï¼', 'item_id'); |
| | | CALL sp_route_product_add_col('location_id', 'å
¥åºåºåºï¼å
³è mes_wm_warehouse_location.idï¼', 'warehouse_id'); |
| | | CALL sp_route_product_add_col('area_id', 'å
¥åºåºä½ï¼å
³è mes_wm_warehouse_area.idï¼', 'location_id'); |
| | | |
| | | DROP PROCEDURE IF EXISTS sp_route_product_add_col; |
| | | |
| | | -- ---------- æ ¡éª ---------- |
| | | SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT |
| | | FROM information_schema.columns |
| | | WHERE table_schema = DATABASE() AND table_name = 'mes_pro_route_product' |
| | | AND COLUMN_NAME IN ('warehouse_id','location_id','area_id') |
| | | ORDER BY ORDINAL_POSITION; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | @Resource |
| | | private MesWmWarehouseService warehouseService; |
| | | @Resource |
| | | private MesWmWarehouseLocationService warehouseLocationService; |
| | | @Resource |
| | | private MesWmWarehouseAreaService warehouseAreaService; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建工èºè·¯çº¿äº§å") |
| | |
| | | convertSet(list, MesProRouteProductDO::getItemId)); |
| | | Map<Long, MesMdUnitMeasureDO> unitMap = unitMeasureService.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); |
| | | // æ¥å·¥å
¥åºä¸çº§çåç§°ï¼ä»åº â åºåº â åºä½ |
| | | Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap( |
| | | convertSet(list, MesProRouteProductDO::getWarehouseId)); |
| | | Map<Long, MesWmWarehouseLocationDO> locationMap = warehouseLocationService.getWarehouseLocationMap( |
| | | convertSet(list, MesProRouteProductDO::getLocationId)); |
| | | Map<Long, MesWmWarehouseAreaDO> areaMap = warehouseAreaService.getWarehouseAreaMap( |
| | | convertSet(list, MesProRouteProductDO::getAreaId)); |
| | | // 2. æ¼æ¥ VO |
| | | return BeanUtils.toBean(list, MesProRouteProductRespVO.class, vo -> { |
| | | MapUtils.findAndThen(itemMap, vo.getItemId(), item -> { |
| | |
| | | MapUtils.findAndThen(unitMap, item.getUnitMeasureId(), |
| | | unit -> vo.setUnitName(unit.getName())); |
| | | }); |
| | | MapUtils.findAndThen(warehouseMap, vo.getWarehouseId(), |
| | | warehouse -> vo.setWarehouseName(warehouse.getName())); |
| | | MapUtils.findAndThen(locationMap, vo.getLocationId(), |
| | | location -> vo.setLocationName(location.getName())); |
| | | MapUtils.findAndThen(areaMap, vo.getAreaId(), |
| | | area -> vo.setAreaName(area.getName())); |
| | | }); |
| | | } |
| | | |
| | |
| | | @Schema(description = "产åç©æåç§°") |
| | | private String itemName; |
| | | |
| | | @Schema(description = "æ¥å·¥å
¥ä»åºç¼å·", example = "1") |
| | | private Long warehouseId; |
| | | |
| | | @Schema(description = "æ¥å·¥å
¥ä»åºåç§°") |
| | | private String warehouseName; |
| | | |
| | | @Schema(description = "å
¥åºåºåºç¼å·", example = "1") |
| | | private Long locationId; |
| | | |
| | | @Schema(description = "å
¥åºåºåºåç§°") |
| | | private String locationName; |
| | | |
| | | @Schema(description = "å
¥åºåºä½ç¼å·", example = "1") |
| | | private Long areaId; |
| | | |
| | | @Schema(description = "å
¥åºåºä½åç§°") |
| | | private String areaName; |
| | | |
| | | @Schema(description = "è§æ ¼åå·") |
| | | private String specification; |
| | | |
| | |
| | | @NotNull(message = "产åç©æç¼å·ä¸è½ä¸ºç©º") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "æ¥å·¥å
¥ä»åºç¼å·ï¼å
³è mes_wm_warehouseï¼", example = "1") |
| | | private Long warehouseId; |
| | | |
| | | @Schema(description = "å
¥åºåºåºç¼å·ï¼å
³è mes_wm_warehouse_locationï¼", example = "1") |
| | | private Long locationId; |
| | | |
| | | @Schema(description = "å
¥åºåºä½ç¼å·ï¼å
³è mes_wm_warehouse_areaï¼", example = "1") |
| | | private Long areaId; |
| | | |
| | | @Schema(description = "ç产æ°é", example = "100") |
| | | private Integer quantity; |
| | | |
| | |
| | | */ |
| | | private Long itemId; |
| | | /** |
| | | * æ¥å·¥å
¥ä»åºç¼å· |
| | | * |
| | | * å
³è {@link cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO#getId()} |
| | | */ |
| | | private Long warehouseId; |
| | | /** |
| | | * å
¥åºåºåºç¼å· |
| | | * |
| | | * å
³è {@link cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO#getId()} |
| | | */ |
| | | private Long locationId; |
| | | /** |
| | | * å
¥åºåºä½ç¼å· |
| | | * |
| | | * å
³è {@link cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO#getId()} |
| | | */ |
| | | private Long areaId; |
| | | /** |
| | | * ç产æ°é |
| | | */ |
| | | private Integer quantity; |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.product.MesProRouteProductSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.MesProRouteProductMapper; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | @Resource |
| | | @Lazy |
| | | private MesProRouteProductBomService routeProductBomService; |
| | | @Resource |
| | | private MesWmWarehouseAreaService warehouseAreaService; |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService warehouseService; |
| | | |
| | | @Override |
| | | public Long createRouteProduct(MesProRouteProductSaveReqVO createReqVO) { |
| | |
| | | routeService.validateRouteNotEnable(createReqVO.getRouteId()); |
| | | // 1.1 æ ¡éªäº§åå¯ä¸æ§ï¼ä¸ä¸ªäº§ååªè½å
³è䏿¡å·¥èºè·¯çº¿ï¼ |
| | | validateItemUnique(null, createReqVO.getItemId()); |
| | | // 1.2 æ ¡éªæ¥å·¥å
¥åºä¸çº§ï¼ä»åº â åºåº â åºä½ï¼çç¶åå
³ç³» |
| | | validateWarehouseChain(createReqVO); |
| | | |
| | | // 2. æå
¥ |
| | | MesProRouteProductDO routeProduct = BeanUtils.toBean(createReqVO, MesProRouteProductDO.class); |
| | |
| | | validateRouteProductExists(updateReqVO.getId()); |
| | | // 1.2 æ ¡éªäº§åå¯ä¸æ§ |
| | | validateItemUnique(updateReqVO.getId(), updateReqVO.getItemId()); |
| | | // 1.3 æ ¡éªæ¥å·¥å
¥åºä¸çº§çç¶åå
³ç³» |
| | | validateWarehouseChain(updateReqVO); |
| | | |
| | | // 2. æ´æ° |
| | | MesProRouteProductDO updateObj = BeanUtils.toBean(updateReqVO, MesProRouteProductDO.class); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªæ¥å·¥å
¥åºä¸çº§ï¼ä»åº â åºåº â åºä½ çç¶åå
³ç³» |
| | | * |
| | | * <p>ä¸ä¸ªé½å¡«æ¶ææ ¡éªå±çº§æ¯å¦å¹é
ï¼é¿å
å端èå¨é使¶æåºä½æå°å«çä»åºä¸ã |
| | | * å
许åªå¡«ä»åºï¼é¨ååºæ¯åªè¦ç¥éå
¥åªä¸ªä»ï¼ï¼æä»¥ä¸æ¯ä¸ä¸ªé½å¿
å¡«ã</p> |
| | | */ |
| | | private void validateWarehouseChain(MesProRouteProductSaveReqVO reqVO) { |
| | | // 1. ä»åºå¡«äºå°±æ ¡éªåå¨ï¼åªå¡«ä»åºãä¸å¡«åºåºåºä½æ¯å
许çï¼ |
| | | if (reqVO.getWarehouseId() != null) { |
| | | warehouseService.validateWarehouseExists(reqVO.getWarehouseId()); |
| | | } |
| | | // 2. åºä½å¡«äºå°±æ ¡éªãåºåºâåºä½ããä»åºâåºåºãçç¶åå
³ç³»æ¯å¦ä¸²äº |
| | | warehouseAreaService.validateWarehouseAreaExists( |
| | | reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId()); |
| | | } |
| | | |
| | | private void validateItemUnique(Long id, Long itemId) { |
| | | MesProRouteProductDO existing = routeProductMapper.selectByItemId(itemId); |
| | | if (existing == null) { |