feat(bom): 添加BOM管理功能模块,工序
管理,基础
- 新增CustomerPrivatePoolMapper.xml实现客户私池映射
- 添加SysLoginController支持登录验证和用户信息获取
- 创建TechnologyBomController提供BOM的增删改查接口
- 定义TechnologyBomDto数据传输对象
- 实现TechnologyBomMapper和相关XML映射文件
- 开发TechnologyBomService业务逻辑层
- 添加TechnologyBomStructure结构管理功能
- 集成Excel导入导出BOM数据功能
- 实现BOM结构树形展示和维护功能
已添加10个文件
已修改32个文件
1566 ■■■■■ 文件已修改
src/main/java/com/ruoyi/framework/config/FilterConfig.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysLoginController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyBomDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyBomStructureDto.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyOperationDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyOperationParamDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyParamDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingOperationDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomStructureVo.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomVo.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyOperationParamVo.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyOperationVo.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyParamVo.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingOperationVo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingVo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyBomController.java 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyBomStructureController.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyParamController.java 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyBomMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyBomStructureMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyOperationMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyOperationParamMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyRoutingMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyBomService.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyBomStructureService.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyOperationParamService.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyOperationService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyParamService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java 383 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomStructureServiceImpl.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerPrivatePoolMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyBomMapper.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyBomStructureMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyOperationMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyRoutingMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/FilterConfig.java
@@ -1,16 +1,17 @@
package com.ruoyi.framework.config;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils;
import javax.servlet.DispatcherType;
import java.util.HashMap;
import java.util.Map;
/**
 * Filter配置
src/main/java/com/ruoyi/project/system/controller/SysLoginController.java
@@ -1,18 +1,5 @@
package com.ruoyi.project.system.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.vo.SysUserDeptVo;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.service.ISysUserDeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginBody;
@@ -21,9 +8,25 @@
import com.ruoyi.framework.security.service.SysPermissionService;
import com.ruoyi.framework.security.service.TokenService;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysMenu;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.domain.vo.SysUserDeptVo;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.service.ISysMenuService;
import com.ruoyi.project.system.service.ISysUserDeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * ç™»å½•验证
src/main/java/com/ruoyi/technology/bean/dto/TechnologyBomDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.ruoyi.technology.bean.dto;
import com.ruoyi.technology.pojo.TechnologyBom;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class TechnologyBomDto extends TechnologyBom {
    @ApiModelProperty("产品名称")
    private String productName;
    @ApiModelProperty("规格型号")
    private String productModelName;
}
src/main/java/com/ruoyi/technology/bean/dto/TechnologyBomStructureDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package com.ruoyi.technology.bean.dto;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
public class TechnologyBomStructureDto extends TechnologyBomStructure {
    @ApiModelProperty("工序名称")
    private String operationName;
    @ApiModelProperty("产品名称")
    private String productName;
    @ApiModelProperty("产品ID")
    private Long productId;
    @ApiModelProperty("规格型号")
    private String model;
    private String tempId;
    private String parentTempId;
    private List<TechnologyBomStructureDto> children;
}
src/main/java/com/ruoyi/technology/bean/dto/TechnologyOperationDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.technology.bean.dto;
import com.ruoyi.technology.pojo.TechnologyOperation;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "TechnologyOperationDto对象", description = "工序查询参数")
public class TechnologyOperationDto extends TechnologyOperation {
}
src/main/java/com/ruoyi/technology/bean/dto/TechnologyOperationParamDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.technology.bean.dto;
import com.ruoyi.technology.pojo.TechnologyOperationParam;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class TechnologyOperationParamDto extends TechnologyOperationParam {
    @ApiModelProperty("参数编码")
    private String paramCode;
    @ApiModelProperty("参数名称")
    private String paramName;
    @ApiModelProperty("参数类型")
    private Byte paramType;
    @ApiModelProperty("参数格式")
    private String paramFormat;
    @ApiModelProperty("单位")
    private String unit;
    @ApiModelProperty("是否必填")
    private Byte isRequired;
}
src/main/java/com/ruoyi/technology/bean/dto/TechnologyParamDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
package com.ruoyi.technology.bean.dto;
import com.ruoyi.technology.pojo.TechnologyParam;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class TechnologyParamDto extends TechnologyParam {
}
src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingDto.java
@@ -3,7 +3,9 @@
import com.ruoyi.technology.pojo.TechnologyRouting;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "TechnologyRoutingDto对象", description = "工艺路线表")
public class TechnologyRoutingDto extends TechnologyRouting {
src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingOperationDto.java
@@ -3,7 +3,9 @@
import com.ruoyi.technology.pojo.TechnologyRoutingOperation;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "TechnologyRoutingOperationDto对象", description = "工艺路线工序查询参数")
public class TechnologyRoutingOperationDto extends TechnologyRoutingOperation {
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomStructureVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.ruoyi.technology.bean.vo;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
public class TechnologyBomStructureVo extends TechnologyBomStructure {
    @ApiModelProperty("工序名称")
    private String operationName;
    @ApiModelProperty("产品名称")
    private String productName;
    @ApiModelProperty("产品ID")
    private Long productId;
    @ApiModelProperty("规格型号")
    private String model;
    private List<TechnologyBomStructureVo> children;
}
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.technology.bean.vo;
import com.ruoyi.technology.pojo.TechnologyBom;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class TechnologyBomVo extends TechnologyBom {
    @ApiModelProperty("产品名称")
    private String productName;
    @ApiModelProperty("规格型号")
    private String productModelName;
}
src/main/java/com/ruoyi/technology/bean/vo/TechnologyOperationParamVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.technology.bean.vo;
import com.ruoyi.technology.pojo.TechnologyOperationParam;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class TechnologyOperationParamVo extends TechnologyOperationParam {
    @ApiModelProperty("参数编码")
    private String paramCode;
    @ApiModelProperty("参数名称")
    private String paramName;
    @ApiModelProperty("参数类型")
    private Byte paramType;
    @ApiModelProperty("参数格式")
    private String paramFormat;
    @ApiModelProperty("单位")
    private String unit;
    @ApiModelProperty("是否必填")
    private Byte isRequired;
}
src/main/java/com/ruoyi/technology/bean/vo/TechnologyOperationVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.technology.bean.vo;
import com.ruoyi.technology.pojo.TechnologyOperation;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "TechnologyOperationVo对象", description = "工序返回对象")
public class TechnologyOperationVo extends TechnologyOperation {
}
src/main/java/com/ruoyi/technology/bean/vo/TechnologyParamVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
package com.ruoyi.technology.bean.vo;
import com.ruoyi.technology.pojo.TechnologyParam;
import lombok.Data;
@Data
public class TechnologyParamVo extends TechnologyParam {
}
src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingOperationVo.java
@@ -3,7 +3,9 @@
import com.ruoyi.technology.pojo.TechnologyRoutingOperation;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "TechnologyRoutingOperationVo对象", description = "工艺路线工序返回对象")
public class TechnologyRoutingOperationVo extends TechnologyRoutingOperation {
src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingVo.java
@@ -3,7 +3,9 @@
import com.ruoyi.technology.pojo.TechnologyRouting;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "TechnologyRoutingVo对象", description = "工艺路线表")
public class TechnologyRoutingVo extends TechnologyRouting {
src/main/java/com/ruoyi/technology/controller/TechnologyBomController.java
@@ -1,18 +1,90 @@
package com.ruoyi.technology.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.production.dto.BomImportDto;
import com.ruoyi.technology.bean.dto.TechnologyBomDto;
import com.ruoyi.technology.bean.vo.TechnologyBomVo;
import com.ruoyi.technology.pojo.TechnologyBom;
import com.ruoyi.technology.service.TechnologyBomService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
 * <p>
 * BOM表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:05:55
 */
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/technologyBom")
@RequiredArgsConstructor
@Api(tags = "基础BOM")
public class TechnologyBomController {
    private final TechnologyBomService technologyBomService;
    @GetMapping("/listPage")
    @Log(title = "Technology BOM page", businessType = BusinessType.OTHER)
    @ApiOperation("BOM分页查询")
    public R<IPage<TechnologyBomVo>> listPage(Page<TechnologyBomDto> page, TechnologyBomDto technologyBomDto) {
        return R.ok(technologyBomService.listPage(page, technologyBomDto));
    }
    @PostMapping("/add")
    @Log(title = "Add technology BOM", businessType = BusinessType.INSERT)
    @ApiOperation("新增BOM")
    public R add(@RequestBody TechnologyBom technologyBom) {
        return technologyBomService.add(technologyBom);
    }
    @PutMapping("/update")
    @Log(title = "Update technology BOM", businessType = BusinessType.UPDATE)
    @ApiOperation("修改BOM")
    public R update(@RequestBody TechnologyBom technologyBom) {
        return technologyBomService.update(technologyBom);
    }
    @DeleteMapping("/batchDelete")
    @Log(title = "Delete technology BOM", businessType = BusinessType.DELETE)
    @ApiOperation("批量删除BOM")
    public R batchDelete(@RequestBody List<Integer> ids) {
        return R.ok(technologyBomService.batchDelete(ids));
    }
    @GetMapping("/getByModel")
    @Log(title = "List BOM by model", businessType = BusinessType.OTHER)
    @ApiOperation("根据规格查询BOM")
    public R<List<TechnologyBomVo>> getByModel(Long productModelId) {
        return R.ok(technologyBomService.listByModel(productModelId));
    }
    @PostMapping("/uploadBom")
    @PreAuthorize("@ss.hasPermi('product:bom:import')")
    @Log(title = "根据Excel导入BOM", businessType = BusinessType.IMPORT)
    @ApiOperation("根据Excel导入BOM")
    public R uploadBom(@RequestParam("file") MultipartFile file) {
        return technologyBomService.uploadBom(file);
    }
    @PostMapping("/exportBom")
    @PreAuthorize("@ss.hasPermi('product:bom:export')")
    @ApiOperation("导出BOM文件")
    @Log(title = "导出BOM文件", businessType = BusinessType.EXPORT)
    public void exportBom(HttpServletResponse response, @RequestParam Integer bomId) {
        technologyBomService.exportBom(response, bomId);
    }
    @GetMapping("/downloadTemplate")
    @Log(title = "下载BOM导入模板", businessType = BusinessType.EXPORT)
    @ApiOperation("下载BOM导入模板")
    public void importTemplate(HttpServletResponse response) {
        ExcelUtil<BomImportDto> excelUtil = new ExcelUtil<>(BomImportDto.class);
        excelUtil.importTemplateExcel(response, "BOM导入模板");
    }
}
src/main/java/com/ruoyi/technology/controller/TechnologyBomStructureController.java
@@ -1,18 +1,33 @@
package com.ruoyi.technology.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.technology.bean.dto.TechnologyBomStructureDto;
import com.ruoyi.technology.bean.vo.TechnologyBomStructureVo;
import com.ruoyi.technology.service.TechnologyBomStructureService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
 * <p>
 * BOM产品结构表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:06:17
 */
import java.util.List;
@RestController
@RequestMapping("/technologyBomStructure")
@RequiredArgsConstructor
@Api(tags = "BOM结构")
public class TechnologyBomStructureController {
    private final TechnologyBomStructureService technologyBomStructureService;
    @PostMapping
    @ApiOperation("新增或修改BOM结构")
    public R addOrUpdate(@RequestBody TechnologyBomStructureDto technologyBomStructureDto) {
        return R.ok(technologyBomStructureService.addTechnologyBomStructure(technologyBomStructureDto));
    }
    @GetMapping("/listByBomId/{bomId}")
    @ApiOperation("根据BOM查询结构树")
    public R<List<TechnologyBomStructureVo>> listByBomId(@PathVariable("bomId") Long bomId) {
        return R.ok(technologyBomStructureService.listByBomId(bomId));
    }
}
src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java
@@ -1,18 +1,59 @@
package com.ruoyi.technology.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.technology.bean.dto.TechnologyOperationDto;
import com.ruoyi.technology.bean.vo.TechnologyOperationVo;
import com.ruoyi.technology.service.TechnologyOperationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
 * <p>
 * å·¥åºè¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:33:03
 */
import java.util.List;
@RestController
@Api(tags = "工序管理")
@RequestMapping("/technologyOperation")
@RequiredArgsConstructor
public class TechnologyOperationController {
    private final TechnologyOperationService technologyOperationService;
    @GetMapping("/listPage")
    @Log(title = "Technology operation page", businessType = BusinessType.OTHER)
    @ApiOperation("工序分页查询")
    public R<IPage<TechnologyOperationVo>> listPage(Page<TechnologyOperationDto> page, TechnologyOperationDto technologyOperationDto) {
        return R.ok(technologyOperationService.listPage(page, technologyOperationDto));
    }
    @PostMapping
    @Log(title = "Add technology operation", businessType = BusinessType.INSERT)
    @ApiOperation("新增工序")
    public R add(@RequestBody TechnologyOperationDto technologyOperationDto) {
        return technologyOperationService.add(technologyOperationDto);
    }
    @PutMapping("/update")
    @Log(title = "Update technology operation", businessType = BusinessType.UPDATE)
    @ApiOperation("修改工序")
    public R update(@RequestBody com.ruoyi.technology.pojo.TechnologyOperation technologyOperation) {
        return R.ok(technologyOperationService.updateById(technologyOperation));
    }
    @DeleteMapping("/batchDelete")
    @Log(title = "Delete technology operation", businessType = BusinessType.DELETE)
    @ApiOperation("批量删除工序")
    public R batchDelete(@RequestBody List<Long> ids) {
        return R.ok(technologyOperationService.batchDelete(ids));
    }
    @GetMapping("/list")
    @ApiOperation("查询全部工序")
    public R<List<TechnologyOperationVo>> list() {
        return R.ok(technologyOperationService.listVo());
    }
}
src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java
@@ -1,18 +1,44 @@
package com.ruoyi.technology.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.technology.bean.dto.TechnologyOperationParamDto;
import com.ruoyi.technology.bean.vo.TechnologyOperationParamVo;
import com.ruoyi.technology.pojo.TechnologyOperationParam;
import com.ruoyi.technology.service.TechnologyOperationParamService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
 * <p>
 * å·¥åºå‚æ•° å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:05:35
 */
import java.util.List;
@RestController
@RequestMapping("/technologyOperationParam")
@Api(tags = "工序参数")
@RequiredArgsConstructor
public class TechnologyOperationParamController {
    private final TechnologyOperationParamService technologyOperationParamService;
    @GetMapping("/list")
    @ApiOperation("工序参数列表查询")
    public R<List<TechnologyOperationParamVo>> list(TechnologyOperationParamDto technologyOperationParamDto) {
        return R.ok(technologyOperationParamService.listOperationParam(technologyOperationParamDto));
    }
    @PostMapping
    @ApiOperation("新增或修改工序参数")
    public R addOrUpdate(@RequestBody TechnologyOperationParam technologyOperationParam) {
        return R.ok(technologyOperationParamService.saveTechnologyOperationParam(technologyOperationParam));
    }
    @DeleteMapping("/batchDelete/{id}")
    @Log(title = "Delete technology operation param", businessType = BusinessType.DELETE)
    @ApiOperation("删除工序参数")
    public AjaxResult batchDelete(@PathVariable("id") Long id) {
        return AjaxResult.success(technologyOperationParamService.batchDelete(id));
    }
}
src/main/java/com/ruoyi/technology/controller/TechnologyParamController.java
@@ -1,18 +1,52 @@
package com.ruoyi.technology.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.technology.bean.dto.TechnologyParamDto;
import com.ruoyi.technology.bean.vo.TechnologyParamVo;
import com.ruoyi.technology.service.TechnologyParamService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
 * <p>
 * åŸºç¡€å‚数表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 09:45:20
 */
@RestController
@RequestMapping("/technologyParam")
@RequiredArgsConstructor
@Api(tags = "基础参数")
public class TechnologyParamController {
    private final TechnologyParamService technologyParamService;
    @GetMapping("list")
    @Log(title = "基础参数数据集合", businessType = BusinessType.OTHER)
    @ApiOperation("基础参数分页查询")
    public R<IPage<TechnologyParamVo>> TechnologyParamDtoList(Page<TechnologyParamDto> page, TechnologyParamDto technologyParamDto) {
        IPage<TechnologyParamVo> paramList = technologyParamService.baseParamList(page, technologyParamDto);
        return R.ok(paramList);
    }
    @PostMapping("/add")
    @Log(title = "新增基础参数", businessType = BusinessType.INSERT)
    @ApiOperation("新增基础参数")
    public R TechnologyParamDtoAdd(@RequestBody TechnologyParamDto TechnologyParamDto) {
        return R.ok(technologyParamService.addBaseParam(TechnologyParamDto));
    }
    @PutMapping("/edit")
    @Log(title = "修改基础参数", businessType = BusinessType.UPDATE)
    @ApiOperation("修改基础参数")
    public R TechnologyParamDtoEdit(@RequestBody TechnologyParamDto TechnologyParamDto) {
        return R.ok(technologyParamService.updateBaseParam(TechnologyParamDto));
    }
    @DeleteMapping("/remove/{ids}")
    @Log(title = "删除基础参数", businessType = BusinessType.DELETE)
    @ApiOperation("删除基础参数")
    public R TechnologyParamDtoRemove(@PathVariable Long[] ids) {
        return R.ok(technologyParamService.deleteBaseParamByIds(ids));
    }
}
src/main/java/com/ruoyi/technology/mapper/TechnologyBomMapper.java
@@ -1,8 +1,13 @@
package com.ruoyi.technology.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.technology.bean.dto.TechnologyBomDto;
import com.ruoyi.technology.bean.vo.TechnologyBomVo;
import com.ruoyi.technology.pojo.TechnologyBom;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
@@ -15,4 +20,5 @@
@Mapper
public interface TechnologyBomMapper extends BaseMapper<TechnologyBom> {
    IPage<TechnologyBomVo> listPage(Page<TechnologyBomDto> page, @Param("c") TechnologyBomDto technologyBomDto);
}
src/main/java/com/ruoyi/technology/mapper/TechnologyBomStructureMapper.java
@@ -1,8 +1,12 @@
package com.ruoyi.technology.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.technology.bean.vo.TechnologyBomStructureVo;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
@@ -15,4 +19,5 @@
@Mapper
public interface TechnologyBomStructureMapper extends BaseMapper<TechnologyBomStructure> {
    List<TechnologyBomStructureVo> listByBomId(@Param("bomId") Long bomId);
}
src/main/java/com/ruoyi/technology/mapper/TechnologyOperationMapper.java
@@ -1,8 +1,13 @@
package com.ruoyi.technology.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.technology.bean.dto.TechnologyOperationDto;
import com.ruoyi.technology.bean.vo.TechnologyOperationVo;
import com.ruoyi.technology.pojo.TechnologyOperation;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
@@ -15,4 +20,5 @@
@Mapper
public interface TechnologyOperationMapper extends BaseMapper<TechnologyOperation> {
    IPage<TechnologyOperationVo> listPage(Page<TechnologyOperationDto> page, @Param("c") TechnologyOperationDto technologyOperationDto);
}
src/main/java/com/ruoyi/technology/mapper/TechnologyOperationParamMapper.java
@@ -1,8 +1,12 @@
package com.ruoyi.technology.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.technology.bean.vo.TechnologyOperationParamVo;
import com.ruoyi.technology.pojo.TechnologyOperationParam;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
@@ -15,4 +19,6 @@
@Mapper
public interface TechnologyOperationParamMapper extends BaseMapper<TechnologyOperationParam> {
    List<TechnologyOperationParamVo> listOperationParam(@Param("technologyOperationId") Long technologyOperationId,
                                                        @Param("paramId") Long technologyParamId);
}
src/main/java/com/ruoyi/technology/mapper/TechnologyRoutingMapper.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.technology.pojo.TechnologyRouting;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
@@ -15,4 +16,5 @@
@Mapper
public interface TechnologyRoutingMapper extends BaseMapper<TechnologyRouting> {
    int updateProductModelByBomId(@Param("productModelId") Long productModelId, @Param("bomId") Long bomId);
}
src/main/java/com/ruoyi/technology/service/TechnologyBomService.java
@@ -1,16 +1,30 @@
package com.ruoyi.technology.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.technology.bean.dto.TechnologyBomDto;
import com.ruoyi.technology.bean.vo.TechnologyBomVo;
import com.ruoyi.technology.pojo.TechnologyBom;
import org.springframework.web.multipart.MultipartFile;
/**
 * <p>
 * BOM表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:05:55
 */
import javax.servlet.http.HttpServletResponse;
import java.util.List;
public interface TechnologyBomService extends IService<TechnologyBom> {
    IPage<TechnologyBomVo> listPage(Page<TechnologyBomDto> page, TechnologyBomDto technologyBomDto);
    List<TechnologyBomVo> listByModel(Long productModelId);
    R add(TechnologyBom technologyBom);
    R update(TechnologyBom technologyBom);
    boolean batchDelete(List<Integer> ids);
    R uploadBom(MultipartFile file);
    void exportBom(HttpServletResponse response, Integer bomId);
}
src/main/java/com/ruoyi/technology/service/TechnologyBomStructureService.java
@@ -1,16 +1,15 @@
package com.ruoyi.technology.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.technology.bean.dto.TechnologyBomStructureDto;
import com.ruoyi.technology.bean.vo.TechnologyBomStructureVo;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
/**
 * <p>
 * BOM产品结构表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:06:17
 */
import java.util.List;
public interface TechnologyBomStructureService extends IService<TechnologyBomStructure> {
    Boolean addTechnologyBomStructure(TechnologyBomStructureDto technologyBomStructureDto);
    List<TechnologyBomStructureVo> listByBomId(Long bomId);
}
src/main/java/com/ruoyi/technology/service/TechnologyOperationParamService.java
@@ -1,16 +1,17 @@
package com.ruoyi.technology.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.technology.bean.dto.TechnologyOperationParamDto;
import com.ruoyi.technology.bean.vo.TechnologyOperationParamVo;
import com.ruoyi.technology.pojo.TechnologyOperationParam;
/**
 * <p>
 * å·¥åºå‚æ•° æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:05:35
 */
import java.util.List;
public interface TechnologyOperationParamService extends IService<TechnologyOperationParam> {
    List<TechnologyOperationParamVo> listOperationParam(TechnologyOperationParamDto technologyOperationParamDto);
    boolean saveTechnologyOperationParam(TechnologyOperationParam technologyOperationParam);
    String batchDelete(Long id);
}
src/main/java/com/ruoyi/technology/service/TechnologyOperationService.java
@@ -1,16 +1,22 @@
package com.ruoyi.technology.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.technology.bean.dto.TechnologyOperationDto;
import com.ruoyi.technology.bean.vo.TechnologyOperationVo;
import com.ruoyi.technology.pojo.TechnologyOperation;
/**
 * <p>
 * å·¥åºè¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:33:03
 */
import java.util.List;
public interface TechnologyOperationService extends IService<TechnologyOperation> {
    IPage<TechnologyOperationVo> listPage(Page<TechnologyOperationDto> page, TechnologyOperationDto technologyOperationDto);
    R add(TechnologyOperationDto technologyOperationDto);
    String batchDelete(List<Long> ids);
    List<TechnologyOperationVo> listVo();
}
src/main/java/com/ruoyi/technology/service/TechnologyParamService.java
@@ -1,6 +1,10 @@
package com.ruoyi.technology.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.technology.bean.dto.TechnologyParamDto;
import com.ruoyi.technology.bean.vo.TechnologyParamVo;
import com.ruoyi.technology.pojo.TechnologyParam;
/**
@@ -13,4 +17,11 @@
 */
public interface TechnologyParamService extends IService<TechnologyParam> {
    IPage<TechnologyParamVo> baseParamList(Page<TechnologyParamDto> page, TechnologyParamDto technologyParamDto);
    int addBaseParam(TechnologyParamDto technologyParamDto);
    int updateBaseParam(TechnologyParamDto technologyParamDto);
    int deleteBaseParamByIds(Long[] ids);
}
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java
@@ -1,20 +1,387 @@
package com.ruoyi.technology.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.production.dto.BomImportDto;
import com.ruoyi.production.dto.ProductStructureDto;
import com.ruoyi.production.pojo.ProductStructure;
import com.ruoyi.technology.bean.dto.TechnologyBomDto;
import com.ruoyi.technology.bean.vo.TechnologyBomVo;
import com.ruoyi.technology.mapper.TechnologyBomMapper;
import com.ruoyi.technology.mapper.TechnologyRoutingMapper;
import com.ruoyi.technology.pojo.TechnologyBom;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
import com.ruoyi.technology.pojo.TechnologyRouting;
import com.ruoyi.technology.service.TechnologyBomService;
import com.ruoyi.technology.service.TechnologyBomStructureService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
/**
 * <p>
 * BOM表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:05:55
 */
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class TechnologyBomServiceImpl extends ServiceImpl<TechnologyBomMapper, TechnologyBom> implements TechnologyBomService {
    private final TechnologyBomMapper technologyBomMapper;
    private final IProductModelService productModelService;
    private final TechnologyBomStructureService technologyBomStructureService;
    private final TechnologyRoutingMapper technologyRoutingMapper;
    private final IProductService productService;
    /**
     * åˆ†é¡µæŸ¥è¯¢BOM列表。
     */
    @Override
    public IPage<TechnologyBomVo> listPage(Page<TechnologyBomDto> page, TechnologyBomDto technologyBomDto) {
        return technologyBomMapper.listPage(page, technologyBomDto);
    }
    /**
     * æ ¹æ®è§„格查询BOM并转换为返回对象。
     */
    @Override
    public List<TechnologyBomVo> listByModel(Long productModelId) {
        List<TechnologyBom> list = this.list(Wrappers.<TechnologyBom>lambdaQuery()
                .eq(TechnologyBom::getProductModelId, productModelId));
        List<TechnologyBomVo> result = new ArrayList<>(list.size());
        for (TechnologyBom item : list) {
            TechnologyBomVo vo = new TechnologyBomVo();
            vo.setId(item.getId());
            vo.setProductModelId(item.getProductModelId());
            vo.setRemark(item.getRemark());
            vo.setVersion(item.getVersion());
            vo.setCreateTime(item.getCreateTime());
            vo.setUpdateTime(item.getUpdateTime());
            vo.setCreateUser(item.getCreateUser());
            vo.setUpdateUser(item.getUpdateUser());
            vo.setBomNo(item.getBomNo());
            vo.setDeptId(item.getDeptId());
            result.add(vo);
        }
        return result;
    }
    /**
     * æ–°å¢žBOM并初始化根结构节点。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R add(TechnologyBom technologyBom) {
        validateProductModel(technologyBom.getProductModelId());
        boolean saved = technologyBomMapper.insert(technologyBom) > 0;
        if (!saved) {
            return R.fail("Add BOM failed");
        }
        technologyBom.setBomNo("BM." + String.format("%05d", technologyBom.getId()));
        technologyBomMapper.updateById(technologyBom);
        initRootStructure(technologyBom.getId().longValue(), technologyBom.getProductModelId());
        return R.ok();
    }
    /**
     * ä¿®æ”¹BOM,规格变化时同步刷新关联结构与路线数据。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R update(TechnologyBom technologyBom) {
        if (technologyBom.getId() == null) {
            throw new ServiceException("BOM id is required");
        }
        validateProductModel(technologyBom.getProductModelId());
        TechnologyBom oldBom = technologyBomMapper.selectById(technologyBom.getId());
        if (oldBom == null) {
            throw new ServiceException("BOM not found");
        }
        if (oldBom.getProductModelId() != null && !oldBom.getProductModelId().equals(technologyBom.getProductModelId())) {
            technologyRoutingMapper.updateProductModelByBomId(technologyBom.getProductModelId(), technologyBom.getId().longValue());
            technologyBomStructureService.remove(Wrappers.<TechnologyBomStructure>lambdaQuery()
                    .eq(TechnologyBomStructure::getBomId, technologyBom.getId().longValue()));
            initRootStructure(technologyBom.getId().longValue(), technologyBom.getProductModelId());
        }
        if (technologyBom.getBomNo() == null) {
            technologyBom.setBomNo(oldBom.getBomNo());
        }
        technologyBomMapper.updateById(technologyBom);
        return R.ok();
    }
    /**
     * åˆ é™¤BOM前校验是否已被工艺路线引用。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDelete(List<Integer> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new ServiceException("Select at least one BOM");
        }
        List<TechnologyRouting> list = technologyRoutingMapper.selectList(Wrappers.<TechnologyRouting>lambdaQuery()
                .in(TechnologyRouting::getBomId, ids));
        if (!list.isEmpty()) {
            throw new ServiceException("BOM is referenced by routing");
        }
        technologyBomStructureService.remove(Wrappers.<TechnologyBomStructure>lambdaQuery()
                .in(TechnologyBomStructure::getBomId, ids));
        return this.removeBatchByIds(ids);
    }
    /**
     * æ ¡éªŒäº§å“è§„格是否存在。
     */
    private void validateProductModel(Long productModelId) {
        if (productModelId == null) {
            throw new ServiceException("Product model is required");
        }
        ProductModel productModel = productModelService.getById(productModelId);
        if (productModel == null) {
            throw new ServiceException("Product model not found");
        }
    }
    /**
     * åˆå§‹åŒ–BOM根节点结构。
     */
    private void initRootStructure(Long bomId, Long productModelId) {
        ProductModel productModel = productModelService.getById(productModelId);
        TechnologyBomStructure root = new TechnologyBomStructure();
        root.setBomId(bomId);
        root.setParentId(null);
        root.setProductModelId(productModelId);
        root.setUnit(productModel.getUnit());
        root.setUnitQuantity(BigDecimal.ONE);
        technologyBomStructureService.save(root);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R uploadBom(MultipartFile file) {
        ExcelUtil<BomImportDto> util = new ExcelUtil<>(BomImportDto.class);
        List<BomImportDto> list;
        try {
            list = util.importExcel(file.getInputStream());
        } catch (Exception e) {
            return R.fail("Excel解析失败");
        }
        if (list == null || list.isEmpty()) return R.fail("数据为空");
        //  å¤„理工序
        list.forEach(dto -> {
            dto.setParentName(clean(dto.getParentName()));
            dto.setParentSpec(clean(dto.getParentSpec()));
            dto.setChildName(clean(dto.getChildName()));
            dto.setChildSpec(clean(dto.getChildSpec()));
        });
        handleProcess(list);
//        Map<String, Long> processMap = productProcessService.list().stream()
//                .collect(Collectors.toMap(ProductProcess::getName, ProductProcess::getId, (k1, k2) -> k1));
        //  åˆ›å»º BOM æ•°æ®
        BomImportDto first = list.get(0);
        ProductModel rootModel = findModel(first.getParentName(), first.getParentSpec());
        TechnologyBom bom = new TechnologyBom();
        bom.setProductModelId(rootModel.getId());
        bom.setVersion("1.0");
        technologyBomMapper.insert(bom);
        bom.setBomNo("BM." + String.format("%05d", bom.getId()));
        technologyBomMapper.updateById(bom);
        // è®°å½•已经插入结构的节点:Key = "名称+规格", Value = structure_id
        Map<String, Long> treePathMap = new HashMap<>();
        for (int i = 0; i < list.size(); i++) {
            BomImportDto dto = list.get(i);
            String parentKey = dto.getParentName() + "|" + dto.getParentSpec();
            String childKey = dto.getChildName() + "|" + dto.getChildSpec();
            //处理根节点,第一行且子项为空
            if (i == 0 && StringUtils.isBlank(dto.getChildName())) {
                ProductStructure rootNode = new ProductStructure();
                rootNode.setBomId(bom.getId());
                rootNode.setParentId(null); // é¡¶å±‚没有父节点
                rootNode.setProductModelId(rootModel.getId());
                rootNode.setUnitQuantity(BigDecimal.ONE);
                rootNode.setUnit(rootModel.getUnit());
//                productStructureService.save(rootNode);
                treePathMap.put(parentKey, rootNode.getId());
                continue;
            }
            //  å¤„理子层级节点
            //  æ‰¾åˆ°çˆ¶èŠ‚ç‚¹åœ¨æ•°æ®åº“é‡Œçš„ ID
            Long parentStructureId = treePathMap.get(parentKey);
            if (parentStructureId == null) {
                // å¦‚æžœ Map é‡Œæ‰¾ä¸åˆ°ï¼Œè¯´æ˜Ž Excel é¡ºåºä¹±äº†æˆ–者数据有误
                throw new ServiceException("导入失败: çˆ¶é¡¹[" + dto.getParentName() + "]必须在其子项之前定义");
            }
            //  èŽ·å–å­é¡¹æ¨¡åž‹ä¿¡æ¯
            ProductModel childModel = findModel(dto.getChildName(), dto.getChildSpec());
            //  æ’入结构表
            ProductStructure node = new ProductStructure();
            node.setBomId(bom.getId());
            node.setParentId(parentStructureId); // çˆ¶èŠ‚ç‚¹ID
            node.setProductModelId(childModel.getId());
            node.setUnitQuantity(dto.getUnitQty());
            node.setUnit(childModel.getUnit());
//            if (processMap.containsKey(dto.getProcess())) {
//                node.setProcessId(processMap.get(dto.getProcess()));
//            }
//            productStructureService.save(node);
            //  æŠŠå½“前子项记录到 Map,作为以后更深层级的父项查找依据
            //  åŒä¸€çˆ¶é¡¹ä¸‹çš„同名子项不需要重复记录
            treePathMap.put(childKey, node.getId());
        }
        return R.ok("BOM导入成功");
    }
    @Override
    public void exportBom(HttpServletResponse response, Integer bomId) {
        if (bomId == null) {
            return;
        }
//        List<ProductStructureDto> treeData = productStructureService.listBybomId(bomId);
//        if (treeData == null || treeData.isEmpty()) {
//            return;
//        }
//
//        //  å°†æ ‘形结构扁平化 ä½¿ç”¨ BFS算法 å¯¼å‡º,按层级顺序
//        List<BomImportDto> exportList = new ArrayList<>();
//
//        // Map<ID, Node> idMap ç”¨äºŽæŸ¥æ‰¾çˆ¶èŠ‚ç‚¹
//        Map<Long, ProductStructureDto> idMap = new HashMap<>();
//        populateMap(treeData, idMap);
//
//        //  treeData çš„第一个是根节点
//        for (ProductStructureDto root : treeData) {
//            //  æ·»åŠ æ ¹èŠ‚ç‚¹
//            BomImportDto rootRow = new BomImportDto();
//            rootRow.setParentName(root.getProductName());
//            rootRow.setParentSpec(root.getModel());
//            rootRow.setUnitQty(root.getUnitQuantity());
//            rootRow.setRemark("");
//            exportList.add(rootRow);
//
//            //  BFS éåކ-队列
//            Queue<ProductStructureDto> queue = new LinkedList<>();
//            if (root.getChildren() != null) {
//                queue.addAll(root.getChildren());
//            }
//
//            while (!queue.isEmpty()) {
//                ProductStructureDto child = queue.poll();
//
//                // æŸ¥æ‰¾çˆ¶èŠ‚ç‚¹
//                ProductStructureDto parent = idMap.get(child.getParentId());
//                if (parent == null) {
//                    // é™¤äº†æœ€å¤–层节点,其他节点的父类肯定是不会为空的
//                    continue;
//                }
//
//                BomImportDto row = new BomImportDto();
//                // çˆ¶ç±»ä¿¡æ¯
//                row.setParentName(parent.getProductName());
//                row.setParentSpec(parent.getModel());
//                // å­ç±»ä¿¡æ¯
//                row.setChildName(child.getProductName());
//                row.setChildSpec(child.getModel());
//                row.setUnitQty(child.getUnitQuantity());
//                row.setProcess(child.getProcessName());
//
//                exportList.add(row);
//
//                //  å°†å­èŠ‚ç‚¹çš„å­èŠ‚ç‚¹åŠ å…¥é˜Ÿåˆ—-下一层
//                if (child.getChildren() != null && !child.getChildren().isEmpty()) {
//                    queue.addAll(child.getChildren());
//                }
//            }
//        }
        ExcelUtil<BomImportDto> util = new ExcelUtil<>(BomImportDto.class);
//        util.exportExcel(response, exportList, "BOM结构导出");
    }
    private ProductModel findModel(String name, String spec) {
        Product product = productService.getOne(new LambdaQueryWrapper<Product>()
                .eq(Product::getProductName, name).last("limit 1"));
        if (product == null) throw new ServiceException("产品未维护:" + name);
        ProductModel model = productModelService.getOne(new LambdaQueryWrapper<ProductModel>()
                .eq(ProductModel::getProductId, product.getId())
                .eq(ProductModel::getModel, spec).last("limit 1"));
        if (model == null) throw new ServiceException("规格未维护:" + name + "[" + spec + "]");
        return model;
    }
    private void handleProcess(List<BomImportDto> list) {
        Set<String> processNames = list.stream()
                .map(BomImportDto::getProcess)
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toSet());
        if (processNames.isEmpty()) {
            return;
        }
//        List<ProductProcess> exists = productProcessService.list(
//                new LambdaQueryWrapper<ProductProcess>().in(ProductProcess::getName, processNames)
//        );
//        Set<String> existNames = exists.stream()
//                .map(ProductProcess::getName)
//                .collect(Collectors.toSet());
//
//        List<ProductProcess> needSave = processNames.stream()
//                .filter(n -> !existNames.contains(n))
//                .map(n -> {
//                    ProductProcess p = new ProductProcess();
//                    p.setName(n);
//                    return p;
//                })
//                .collect(Collectors.toList());
//
//        if (!needSave.isEmpty()) {
//            productProcessService.saveBatch(needSave);
//            needSave.forEach(p -> p.setNo("GX" + String.format("%08d", p.getId())));
//            productProcessService.updateBatchById(needSave);
//        }
    }
    private String clean(String s) {
        if (s == null) return null;
        return s.replaceAll("[\\u00A0\\u3000]", "").trim();
    }
    private void populateMap(List<ProductStructureDto> nodes, Map<Long, ProductStructureDto> map) {
        if (nodes == null || nodes.isEmpty()) {
            return;
        }
        for (ProductStructureDto node : nodes) {
            map.put(node.getId(), node);
            populateMap(node.getChildren(), map);
        }
    }
}
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomStructureServiceImpl.java
@@ -1,20 +1,136 @@
package com.ruoyi.technology.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.technology.bean.dto.TechnologyBomStructureDto;
import com.ruoyi.technology.bean.vo.TechnologyBomStructureVo;
import com.ruoyi.technology.mapper.TechnologyBomStructureMapper;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
import com.ruoyi.technology.service.TechnologyBomStructureService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * <p>
 * BOM产品结构表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:06:17
 */
import java.util.*;
@Service
@RequiredArgsConstructor
public class TechnologyBomStructureServiceImpl extends ServiceImpl<TechnologyBomStructureMapper, TechnologyBomStructure> implements TechnologyBomStructureService {
    private final TechnologyBomStructureMapper technologyBomStructureMapper;
    /**
     * ä¿å­˜BOM结构树,处理新增、更新和删除节点。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addTechnologyBomStructure(TechnologyBomStructureDto dto) {
        Long bomId = dto.getBomId();
        List<TechnologyBomStructureDto> flatDtoList = new ArrayList<>();
        flattenTree(dto.getChildren(), flatDtoList);
        List<TechnologyBomStructure> dbList = this.list(new LambdaQueryWrapper<TechnologyBomStructure>()
                .eq(TechnologyBomStructure::getBomId, bomId));
        Set<Long> frontendIds = new HashSet<>();
        for (TechnologyBomStructureDto item : flatDtoList) {
            if (item.getId() != null) {
                frontendIds.add(item.getId());
            }
        }
        Set<Long> deleteIds = new HashSet<>();
        for (TechnologyBomStructure dbItem : dbList) {
            if (!frontendIds.contains(dbItem.getId())) {
                deleteIds.add(dbItem.getId());
            }
        }
        if (!deleteIds.isEmpty()) {
            this.removeByIds(deleteIds);
        }
        List<TechnologyBomStructure> insertList = new ArrayList<>();
        List<TechnologyBomStructure> updateList = new ArrayList<>();
        Map<String, TechnologyBomStructure> tempEntityMap = new HashMap<>();
        for (TechnologyBomStructureDto item : flatDtoList) {
            TechnologyBomStructure entity = new TechnologyBomStructure();
            BeanUtils.copyProperties(item, entity);
            entity.setBomId(bomId);
            if (item.getId() == null) {
                entity.setParentId(null);
                insertList.add(entity);
                tempEntityMap.put(item.getTempId(), entity);
            } else {
                updateList.add(entity);
            }
        }
        if (!insertList.isEmpty()) {
            this.saveBatch(insertList);
        }
        List<TechnologyBomStructure> parentFixList = new ArrayList<>();
        for (TechnologyBomStructureDto item : flatDtoList) {
            if (item.getId() == null && item.getParentTempId() != null) {
                TechnologyBomStructure child = tempEntityMap.get(item.getTempId());
                if (child == null) {
                    continue;
                }
                TechnologyBomStructure parent = tempEntityMap.get(item.getParentTempId());
                Long realParentId = parent != null ? parent.getId() : Long.valueOf(item.getParentTempId());
                child.setParentId(realParentId);
                parentFixList.add(child);
            }
        }
        if (!parentFixList.isEmpty()) {
            this.updateBatchById(parentFixList);
        }
        if (!updateList.isEmpty()) {
            this.updateBatchById(updateList);
        }
        return true;
    }
    /**
     * æ ¹æ®BOM查询并组装结构树。
     */
    @Override
    public List<TechnologyBomStructureVo> listByBomId(Long bomId) {
        List<TechnologyBomStructureVo> list = technologyBomStructureMapper.listByBomId(bomId);
        Map<Long, TechnologyBomStructureVo> map = new HashMap<>();
        for (TechnologyBomStructureVo node : list) {
            node.setChildren(new ArrayList<>());
            map.put(node.getId(), node);
        }
        List<TechnologyBomStructureVo> tree = new ArrayList<>();
        for (TechnologyBomStructureVo node : list) {
            Long parentId = node.getParentId();
            if (parentId == null || parentId == 0L) {
                tree.add(node);
                continue;
            }
            TechnologyBomStructureVo parent = map.get(parentId);
            if (parent != null) {
                parent.getChildren().add(node);
            }
        }
        return tree;
    }
    /**
     * å°†æ ‘形结构拍平成列表,便于统一保存。
     */
    private void flattenTree(List<TechnologyBomStructureDto> source, List<TechnologyBomStructureDto> result) {
        if (source == null) {
            return;
        }
        for (TechnologyBomStructureDto node : source) {
            result.add(node);
            flattenTree(node.getChildren(), result);
        }
    }
}
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java
@@ -1,20 +1,75 @@
package com.ruoyi.technology.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.technology.bean.dto.TechnologyOperationParamDto;
import com.ruoyi.technology.bean.vo.TechnologyOperationParamVo;
import com.ruoyi.technology.mapper.TechnologyOperationMapper;
import com.ruoyi.technology.mapper.TechnologyOperationParamMapper;
import com.ruoyi.technology.mapper.TechnologyParamMapper;
import com.ruoyi.technology.pojo.TechnologyOperationParam;
import com.ruoyi.technology.pojo.TechnologyParam;
import com.ruoyi.technology.service.TechnologyOperationParamService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
 * <p>
 * å·¥åºå‚æ•° æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:05:35
 */
import java.util.List;
@Service
@RequiredArgsConstructor
public class TechnologyOperationParamServiceImpl extends ServiceImpl<TechnologyOperationParamMapper, TechnologyOperationParam> implements TechnologyOperationParamService {
    private final TechnologyOperationParamMapper technologyOperationParamMapper;
    private final TechnologyOperationMapper technologyOperationMapper;
    private final TechnologyParamMapper technologyParamMapper;
    /**
     * æŒ‰å·¥åºæˆ–参数条件查询工序参数列表。
     */
    @Override
    public List<TechnologyOperationParamVo> listOperationParam(TechnologyOperationParamDto technologyOperationParamDto) {
        Long operationId = technologyOperationParamDto == null ? null : technologyOperationParamDto.getTechnologyOperationId();
        Long paramId = technologyOperationParamDto == null ? null : technologyOperationParamDto.getTechnologyParamId();
        return technologyOperationParamMapper.listOperationParam(operationId, paramId);
    }
    /**
     * ä¿å­˜å·¥åºå‚数并校验工序、参数和唯一性。
     */
    @Override
    public boolean saveTechnologyOperationParam(TechnologyOperationParam technologyOperationParam) {
        if (technologyOperationParam.getTechnologyOperationId() == null
                || technologyOperationMapper.selectById(technologyOperationParam.getTechnologyOperationId()) == null) {
            throw new ServiceException("Operation not found");
        }
        if (technologyOperationParam.getTechnologyParamId() == null) {
            throw new ServiceException("Param is required");
        }
        TechnologyParam technologyParam = technologyParamMapper.selectById(technologyOperationParam.getTechnologyParamId());
        if (technologyParam == null) {
            throw new ServiceException("Param not found");
        }
        boolean duplicate = technologyOperationParamMapper.selectCount(Wrappers.<TechnologyOperationParam>lambdaQuery()
                .eq(TechnologyOperationParam::getTechnologyOperationId, technologyOperationParam.getTechnologyOperationId())
                .eq(TechnologyOperationParam::getTechnologyParamId, technologyOperationParam.getTechnologyParamId())
                .ne(technologyOperationParam.getId() != null, TechnologyOperationParam::getId, technologyOperationParam.getId())) > 0;
        if (duplicate) {
            throw new ServiceException("Duplicate param in operation");
        }
        return this.saveOrUpdate(technologyOperationParam);
    }
    /**
     * åˆ é™¤å•个工序参数。
     */
    @Override
    public String batchDelete(Long id) {
        TechnologyOperationParam deleteItem = technologyOperationParamMapper.selectById(id);
        if (deleteItem == null) {
            return "Delete failed";
        }
        technologyOperationParamMapper.deleteById(id);
        return "Success";
    }
}
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java
@@ -1,20 +1,89 @@
package com.ruoyi.technology.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.technology.bean.dto.TechnologyOperationDto;
import com.ruoyi.technology.bean.vo.TechnologyOperationVo;
import com.ruoyi.technology.mapper.TechnologyBomStructureMapper;
import com.ruoyi.technology.mapper.TechnologyOperationMapper;
import com.ruoyi.technology.mapper.TechnologyOperationParamMapper;
import com.ruoyi.technology.mapper.TechnologyRoutingOperationMapper;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
import com.ruoyi.technology.pojo.TechnologyOperation;
import com.ruoyi.technology.pojo.TechnologyOperationParam;
import com.ruoyi.technology.pojo.TechnologyRoutingOperation;
import com.ruoyi.technology.service.TechnologyOperationService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
/**
 * <p>
 * å·¥åºè¡¨ æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 10:33:03
 */
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class TechnologyOperationServiceImpl extends ServiceImpl<TechnologyOperationMapper, TechnologyOperation> implements TechnologyOperationService {
    private final TechnologyOperationMapper technologyOperationMapper;
    private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper;
    private final TechnologyBomStructureMapper technologyBomStructureMapper;
    private final TechnologyOperationParamMapper technologyOperationParamMapper;
    /**
     * åˆ†é¡µæŸ¥è¯¢å·¥åºåˆ—表。
     */
    @Override
    public IPage<TechnologyOperationVo> listPage(Page<TechnologyOperationDto> page, TechnologyOperationDto technologyOperationDto) {
        return technologyOperationMapper.listPage(page, technologyOperationDto);
    }
    /**
     * æ–°å¢žå·¥åºå¹¶è¡¥é½å·¥åºç¼–码。
     */
    @Override
    public R add(TechnologyOperationDto technologyOperationDto) {
        TechnologyOperation technologyOperation = new TechnologyOperation();
        BeanUtils.copyProperties(technologyOperationDto, technologyOperation);
        boolean saved = technologyOperationMapper.insert(technologyOperation) > 0;
        if (saved && ObjectUtils.isNull(technologyOperationDto.getNo())) {
            technologyOperation.setNo("GX" + String.format("%08d", technologyOperation.getId()));
            technologyOperationMapper.updateById(technologyOperation);
        }
        return R.ok();
    }
    /**
     * åˆ é™¤å·¥åºå‰æ ¡éªŒæ˜¯å¦å·²è¢«BOM结构或工艺路线引用。
     */
    @Override
    public String batchDelete(List<Long> ids) {
        List<TechnologyRoutingOperation> routingOperations = technologyRoutingOperationMapper.selectList(
                Wrappers.<TechnologyRoutingOperation>lambdaQuery().in(TechnologyRoutingOperation::getTechnologyOperationId, ids));
        List<TechnologyBomStructure> bomStructures = technologyBomStructureMapper.selectList(
                Wrappers.<TechnologyBomStructure>lambdaQuery().in(TechnologyBomStructure::getOperationId, ids));
        if (!CollectionUtils.isEmpty(routingOperations) || !CollectionUtils.isEmpty(bomStructures)) {
            throw new RuntimeException("Operation is referenced and cannot be deleted");
        }
        technologyOperationParamMapper.delete(Wrappers.<TechnologyOperationParam>lambdaQuery()
                .in(TechnologyOperationParam::getTechnologyOperationId, ids));
        technologyOperationMapper.deleteBatchIds(ids);
        return null;
    }
    /**
     * æŸ¥è¯¢å…¨éƒ¨å·¥åºå¹¶è½¬æ¢ä¸ºè¿”回对象。
     */
    @Override
    public List<TechnologyOperationVo> listVo() {
        return this.list().stream().map(item -> {
            TechnologyOperationVo vo = new TechnologyOperationVo();
            BeanUtils.copyProperties(item, vo);
            return vo;
        }).collect(Collectors.toList());
    }
}
src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java
@@ -1,20 +1,170 @@
package com.ruoyi.technology.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.technology.bean.dto.TechnologyParamDto;
import com.ruoyi.technology.bean.vo.TechnologyParamVo;
import com.ruoyi.technology.mapper.TechnologyParamMapper;
import com.ruoyi.technology.pojo.TechnologyParam;
import com.ruoyi.technology.service.TechnologyParamService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
/**
 * <p>
 * åŸºç¡€å‚数表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-20 09:45:20
 */
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
@RequiredArgsConstructor
public class TechnologyParamServiceImpl extends ServiceImpl<TechnologyParamMapper, TechnologyParam> implements TechnologyParamService {
    private static final List<Integer> VALID_PARAM_TYPES = Arrays.asList(1, 2, 3, 4);
    private static final String PARAM_CODE_PREFIX = "PARAM_";
    private static final Byte DATE_PARAM_TYPE = (byte) 4;
    /**
     * åˆ†é¡µæŸ¥è¯¢åŸºç¡€å‚数并格式化日期类型展示。
     */
    @Override
    public IPage<TechnologyParamVo> baseParamList(Page<TechnologyParamDto> page, TechnologyParamDto baseParam) {
        LambdaQueryWrapper<TechnologyParam> queryWrapper = new LambdaQueryWrapper<>();
        if (baseParam != null && StringUtils.isNotEmpty(baseParam.getParamName())) {
            queryWrapper.like(TechnologyParam::getParamName, baseParam.getParamName());
        }
        queryWrapper.orderByDesc(TechnologyParam::getId);
        Page<TechnologyParam> entityPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        Page<TechnologyParam> paramPage = page(entityPage, queryWrapper);
        Page<TechnologyParamVo> resultPage = new Page<>(paramPage.getCurrent(), paramPage.getSize(), paramPage.getTotal());
        List<TechnologyParamVo> records = new ArrayList<>(paramPage.getRecords().size());
        for (TechnologyParam item : paramPage.getRecords()) {
            TechnologyParamVo vo = new TechnologyParamVo();
            BeanUtils.copyProperties(item, vo);
            if (DATE_PARAM_TYPE.equals(item.getParamType()) && StringUtils.isNotEmpty(item.getParamFormat())) {
                vo.setParamFormat(toDisplayDatePattern(item.getParamFormat()));
            }
            records.add(vo);
        }
        resultPage.setRecords(records);
        return resultPage;
    }
    /**
     * æ–°å¢žåŸºç¡€å‚数并生成唯一参数编码。
     */
    @Override
    public int addBaseParam(TechnologyParamDto baseParam) {
        if (baseParam == null) {
            throw new RuntimeException("新增参数不能为空");
        }
        checkBaseParam(baseParam);
        baseParam.setParamCode(generateParamCode());
        baseParam.setCreateUser(SecurityUtils.getUsername());
        baseParam.setCreateTime(LocalDateTime.now());
        if (baseParam.getIsRequired() == null) {
            baseParam.setIsRequired((byte) 0);
        }
        return baseMapper.insert(baseParam);
    }
    /**
     * ä¿®æ”¹åŸºç¡€å‚数并保留原有参数编码。
     */
    @Override
    public int updateBaseParam(TechnologyParamDto baseParam) {
        if (baseParam == null || baseParam.getId() == null) {
            throw new RuntimeException("修改参数ID不能为空");
        }
        checkBaseParam(baseParam);
        TechnologyParam current = baseMapper.selectById(baseParam.getId());
        if (current == null) {
            throw new RuntimeException("参数不存在");
        }
        if (StringUtils.isEmpty(baseParam.getParamCode())) {
            baseParam.setParamCode(current.getParamCode());
        }
        baseParam.setUpdateUser(SecurityUtils.getUsername());
        baseParam.setUpdateTime(LocalDateTime.now());
        return baseMapper.updateById(baseParam);
    }
    /**
     * å‚数定义合法校验。
     */
    private void checkBaseParam(TechnologyParamDto baseParam) {
        if (StringUtils.isEmpty(baseParam.getParamName())) {
            throw new RuntimeException("参数名称不能为空");
        }
        if (baseParam.getParamType() == null || !VALID_PARAM_TYPES.contains(Integer.valueOf(baseParam.getParamType()))) {
            throw new RuntimeException("非法参数类型");
        }
        if (DATE_PARAM_TYPE.equals(baseParam.getParamType())) {
            if (StringUtils.isEmpty(baseParam.getParamFormat())) {
                throw new RuntimeException("日期类型必须配置参数格式(如: yyyy-MM-dd)");
            }
            try {
                String standardPattern = normalizeDatePattern(baseParam.getParamFormat());
                DateTimeFormatter.ofPattern(standardPattern);
                baseParam.setParamFormat(standardPattern);
            } catch (Exception e) {
                throw new RuntimeException("日期格式非法: " + baseParam.getParamFormat());
            }
        } else {
            baseParam.setParamFormat(baseParam.getParamFormat());
        }
    }
    /**
     * ç”Ÿæˆå‚数唯一编码,供工艺参数和生产订单快照引用。
     */
    private String generateParamCode() {
        LambdaQueryWrapper<TechnologyParam> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(TechnologyParam::getParamCode)
                .likeRight(TechnologyParam::getParamCode, PARAM_CODE_PREFIX)
                .orderByDesc(TechnologyParam::getParamCode)
                .last("limit 1");
        TechnologyParam last = baseMapper.selectOne(wrapper);
        int nextNum = 1;
        if (last != null && StringUtils.isNotEmpty(last.getParamCode())) {
            try {
                String numStr = last.getParamCode().replace(PARAM_CODE_PREFIX, "");
                nextNum = Integer.parseInt(numStr) + 1;
            } catch (Exception e) {
                log.error("解析 paramCode å¼‚常", e);
            }
        }
        return PARAM_CODE_PREFIX + String.format("%04d", nextNum);
    }
    private String normalizeDatePattern(String pattern) {
        return pattern.trim()
                .replace('Y', 'y')
                .replace('D', 'd')
                .replace('S', 's');
    }
    private String toDisplayDatePattern(String pattern) {
        return normalizeDatePattern(pattern);
    }
    /**
     * æ‰¹é‡åˆ é™¤åŸºç¡€å‚数。
     */
    @Override
    public int deleteBaseParamByIds(Long[] ids) {
        if (ids == null || ids.length == 0) {
            throw new RuntimeException("删除ID不能为空");
        }
        return baseMapper.deleteBatchIds(Arrays.asList(ids));
    }
}
src/main/resources/mapper/basic/CustomerPrivatePoolMapper.xml
@@ -41,8 +41,8 @@
            <if test="c.customerType != null">
                and c.customer_type = #{c.customerType}
            </if>
            <if test="c.boundId != null">
                and cpp.boundId = #{c.boundId}
            <if test="c.bound_id != null">
                and cpp.bound_id = #{c.boundId}
            </if>
        </where>
src/main/resources/mapper/technology/TechnologyBomMapper.xml
@@ -16,4 +16,27 @@
        <result column="dept_id" property="deptId" />
    </resultMap>
    <select id="listPage" resultType="com.ruoyi.technology.bean.vo.TechnologyBomVo">
        select tb.*,
               pm.model as productModelName,
               p.product_name as productName
        from technology_bom tb
        left join product_model pm on tb.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        <where>
            <if test="c.productModelName != null and c.productModelName != ''">
                and pm.model like concat('%', #{c.productModelName}, '%')
            </if>
            <if test="c.productName != null and c.productName != ''">
                and p.product_name like concat('%', #{c.productName}, '%')
            </if>
            <if test="c.bomNo != null and c.bomNo != ''">
                and tb.bom_no like concat('%', #{c.bomNo}, '%')
            </if>
            <if test="c.version != null and c.version != ''">
                and tb.version = #{c.version}
            </if>
        </where>
        order by tb.id desc
    </select>
</mapper>
src/main/resources/mapper/technology/TechnologyBomStructureMapper.xml
@@ -16,4 +16,17 @@
        <result column="dept_id" property="deptId" />
    </resultMap>
    <select id="listByBomId" resultType="com.ruoyi.technology.bean.vo.TechnologyBomStructureVo">
        select tbs.*,
               p.product_name as productName,
               pm.product_id as productId,
               pm.model,
               top1.name as operationName
        from technology_bom_structure tbs
        left join product_model pm on tbs.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        left join technology_operation top1 on tbs.operation_id = top1.id
        where tbs.bom_id = #{bomId}
        order by tbs.id
    </select>
</mapper>
src/main/resources/mapper/technology/TechnologyOperationMapper.xml
@@ -18,4 +18,20 @@
        <result column="dept_id" property="deptId" />
    </resultMap>
    <select id="listPage" resultType="com.ruoyi.technology.bean.vo.TechnologyOperationVo">
        select *
        from technology_operation t
        <where>
            <if test="c.name != null and c.name != ''">
                and t.name like concat('%', #{c.name}, '%')
            </if>
            <if test="c.no != null and c.no != ''">
                and t.no like concat('%', #{c.no}, '%')
            </if>
            <if test="c.type != null">
                and t.type = #{c.type}
            </if>
        </where>
        order by t.id asc
    </select>
</mapper>
src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml
@@ -10,4 +10,24 @@
        <result column="standard_value" property="standardValue" />
    </resultMap>
    <select id="listOperationParam" resultType="com.ruoyi.technology.bean.vo.TechnologyOperationParamVo">
        select top1.*,
               tp.param_code as paramCode,
               tp.param_name as paramName,
               tp.param_type as paramType,
               tp.param_format as paramFormat,
               tp.unit,
               tp.is_required as isRequired
        from technology_operation_param top1
        left join technology_param tp on top1.technology_param_id = tp.id
        <where>
            <if test="technologyOperationId != null">
                and top1.technology_operation_id = #{technologyOperationId}
            </if>
            <if test="paramId != null">
                and top1.technology_param_id = #{paramId}
            </if>
        </where>
        order by top1.id asc
    </select>
</mapper>
src/main/resources/mapper/technology/TechnologyRoutingMapper.xml
@@ -15,4 +15,9 @@
        <result column="dept_id" property="deptId" />
    </resultMap>
    <update id="updateProductModelByBomId">
        update technology_routing
        set product_model_id = #{productModelId}
        where bom_id = #{bomId}
    </update>
</mapper>