2026-04-16 6ad5c2cdc193cf7bb02884961b7434cd7f607a0d
feat(production): 扩展生产工艺路线支持多产品绑定及工单权限控制

- 给生产工单表增加报工人字段,用于工单级权限判断
- 在工艺路线表中添加BOM ID、工艺路线编号和多产品ID字段
- 在工艺路线明细表中添加多产品ID、拖拽排序和质检标识字段
- 实现产品规格多选绑定功能,支持单产品和多产品的灵活配置
- 增加工艺路线明细查询时的产品筛选功能
- 实现基于报工人ID的工单权限控制机制
- 优化生产订单分页查询,集成用户权限验证逻辑
- 添加BOM根据规格型号ID列表查询接口
- 重构生产订单绑定工艺路线逻辑,支持自定义工序配置
- 规范化产品模型ID处理,统一多产品ID的数据格式转换
已添加2个文件
已修改25个文件
830 ■■■■■ 文件已修改
doc/1767790800_add_report_user_ids_to_product_work_order.sql 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260414-process-route-multi-product.sql 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/create_table_process_route.sql 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/create_table_process_route_item.sql 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProcessRouteController.java 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProcessRouteItemController.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductBomController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductWorkOrderController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductOrderMapper.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductWorkOrderMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProcessRoute.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProcessRouteItem.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductOrder.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductProcessRouteItem.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductWorkOrder.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductWorkOrderService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java 118 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductStructureServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProcessRouteItemMapper.xml 83 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProcessRouteMapper.xml 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductOrderMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductWorkOrderMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/1767790800_add_report_user_ids_to_product_work_order.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
-- ç»™ç”Ÿäº§å·¥å•增加报工人字段,用于工单级权限判断
ALTER TABLE product_work_order
    ADD COLUMN report_user_ids varchar(1024) NULL COMMENT '报工人id,多选,逗号分隔';
doc/20260414-process-route-multi-product.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
-- å·¥è‰ºè·¯çº¿ã€å·¥è‰ºè·¯çº¿æ˜Žç»†æ”¯æŒç»‘定多个产品(2026-04-14)
ALTER TABLE process_route
    ADD COLUMN product_model_ids VARCHAR(1024) NULL COMMENT '多产品id,逗号分隔' AFTER product_model_id;
UPDATE process_route
SET product_model_ids = CAST(product_model_id AS CHAR)
WHERE (product_model_ids IS NULL OR product_model_ids = '')
  AND product_model_id IS NOT NULL
  AND product_model_id <> 0;
ALTER TABLE process_route_item
    ADD COLUMN product_model_ids VARCHAR(1024) NULL COMMENT '多产品id,逗号分隔' AFTER product_model_id;
UPDATE process_route_item
SET product_model_ids = CAST(product_model_id AS CHAR)
WHERE (product_model_ids IS NULL OR product_model_ids = '')
  AND product_model_id IS NOT NULL
  AND product_model_id <> 0;
doc/create_table_process_route.sql
@@ -3,7 +3,10 @@
create table process_route
(
    id          bigint auto_increment primary key,
    bom_id      bigint   not null default 0 comment 'BOMid',
    product_model_id  bigint       not null default 0 comment '产品id',
    process_route_code varchar(255) not null default '' comment '工艺路线编号',
    product_model_ids varchar(1024) null comment '多产品id,逗号分隔',
    description varchar(255) not null default '' comment '描述',
    tenant_id   bigint       not null comment '租户id',
    create_time datetime     null comment '录入时间',
doc/create_table_process_route_item.sql
@@ -5,7 +5,10 @@
    id               bigint auto_increment primary key,
    route_id         bigint   not null default 0 comment '工艺路线id',
    product_model_id bigint   not null default 0 comment '产品id',
    product_model_ids varchar(1024) null comment '多产品id,逗号分隔',
    drag_sort       bigint   not null default 0 comment '拖拽排序',
    process_id       bigint   not null default 0 comment '工序id',
    is_quality       tinyint not null default 0 comment '是否质检 0 ä¸æ˜¯ 1 æ˜¯',
    tenant_id        bigint   not null comment '租户id',
    create_time      datetime null comment '录入时间',
    update_time      datetime null comment '更新时间'
src/main/java/com/ruoyi/production/controller/ProcessRouteController.java
@@ -1,47 +1,78 @@
package com.ruoyi.production.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.production.dto.ProcessRouteDto;
import com.ruoyi.production.pojo.ProcessRoute;
import com.ruoyi.production.pojo.ProcessRouteItem;
import com.ruoyi.production.service.ProcessRouteItemService;
import com.ruoyi.production.service.ProcessRouteService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("processRoute")
@RequestMapping("/processRoute")
@Api(tags = "工艺路线")
public class ProcessRouteController {
    @Autowired
    private ProcessRouteService processRouteService;
    @GetMapping("page")
    @GetMapping("/page")
    @ApiOperation("分页查询")
    public R page(Page<ProcessRouteDto>  page, ProcessRouteDto processRouteDto) {
        return R.ok(processRouteService.pageProcessRouteDto(page, processRouteDto));
    }
    @ApiOperation("新增工艺路线")
    @PostMapping ()
    @PostMapping
    public R add(@RequestBody  ProcessRoute processRoute) {
        normalizeProductBinding(processRoute);
        return R.ok(processRouteService.saveProcessRoute(processRoute));
    }
    @ApiOperation("修改工艺路线")
    @PutMapping ()
    @PutMapping
    public R update(@RequestBody  ProcessRoute processRoute) {
        normalizeProductBinding(processRoute);
        return R.ok(processRouteService.updateById(processRoute));
    }
    @ApiOperation("删除工艺路线")
    @DeleteMapping("/{ids}")
    public R delete(@PathVariable("ids") Long[] ids) {
        return R.ok(processRouteService.batchDelete(Arrays.asList(ids)));
    }
    private void normalizeProductBinding(ProcessRoute processRoute) {
        if (processRoute == null) {
            return;
        }
        String ids = null;
        List<Long> idList = processRoute.getProductModelIdList();
        if (idList != null && !idList.isEmpty()) {
            ids = idList.stream()
                    .filter(item -> item != null && item > 0)
                    .distinct()
                    .map(String::valueOf)
                    .collect(Collectors.joining(","));
        } else if (StringUtils.hasText(processRoute.getProductModelIds())) {
            ids = Arrays.stream(processRoute.getProductModelIds().split(","))
                    .map(String::trim)
                    .filter(StringUtils::hasText)
                    .distinct()
                    .collect(Collectors.joining(","));
        } else if (processRoute.getProductModelId() != null) {
            ids = String.valueOf(processRoute.getProductModelId());
        }
        processRoute.setProductModelIds(ids);
        if (StringUtils.hasText(ids)) {
            processRoute.setProductModelId(Long.valueOf(ids.split(",")[0]));
        }
    }
}
src/main/java/com/ruoyi/production/controller/ProcessRouteItemController.java
@@ -1,29 +1,27 @@
package com.ruoyi.production.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
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.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.production.dto.ProcessRouteItemDto;
import com.ruoyi.production.pojo.ProcessRoute;
import com.ruoyi.production.pojo.ProcessRouteItem;
import com.ruoyi.production.service.ProcessRouteItemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("processRouteItem")
@Api(tags = "工艺路线明细")
public class ProcessRouteItemController {
    @Autowired
    private ProcessRouteItemService processRouteItemService;
@@ -32,9 +30,10 @@
        return R.ok(processRouteItemService.listProcessRouteItemDto(processRouteItemDto));
    }
    @PostMapping ()
    @PostMapping
    @ApiOperation("新增修改")
    public R addOrUpdate(@RequestBody ProcessRouteItem processRouteItem) {
        normalizeProductBinding(processRouteItem);
        return R.ok(processRouteItemService.saveOrUpdate(processRouteItem));
    }
@@ -50,4 +49,31 @@
    public AjaxResult batchDelete(@PathVariable("id") Long id) {
        return AjaxResult.success(processRouteItemService.batchDelete(id));
    }
    private void normalizeProductBinding(ProcessRouteItem processRouteItem) {
        if (processRouteItem == null) {
            return;
        }
        String ids = null;
        List<Long> idList = processRouteItem.getProductModelIdList();
        if (idList != null && !idList.isEmpty()) {
            ids = idList.stream()
                    .filter(item -> item != null && item > 0)
                    .distinct()
                    .map(String::valueOf)
                    .collect(Collectors.joining(","));
        } else if (StringUtils.hasText(processRouteItem.getProductModelIds())) {
            ids = Arrays.stream(processRouteItem.getProductModelIds().split(","))
                    .map(String::trim)
                    .filter(StringUtils::hasText)
                    .distinct()
                    .collect(Collectors.joining(","));
        } else if (processRouteItem.getProductModelId() != null) {
            ids = String.valueOf(processRouteItem.getProductModelId());
        }
        processRouteItem.setProductModelIds(ids);
        if (StringUtils.hasText(ids)) {
            processRouteItem.setProductModelId(Long.valueOf(ids.split(",")[0]));
        }
    }
}
src/main/java/com/ruoyi/production/controller/ProductBomController.java
@@ -101,6 +101,15 @@
        return AjaxResult.success(productBoms);
    }
    @PostMapping("/getByModelList")
    @Log(title = "BOM-根据选择的规格型号ids查询存在的bom", businessType = BusinessType.OTHER)
    @ApiOperation("BOM-根据选择的规格型号id查询存在的bom")
    public AjaxResult getByModelList(@RequestBody List<Long> productModelId) {
        if (CollectionUtils.isEmpty(productModelId)) return AjaxResult.error("请选择要查询的规格型号");
        List<ProductBom> productBoms = productBomService.list(Wrappers.<ProductBom>lambdaQuery().in(ProductBom::getProductModelId, productModelId));
        return AjaxResult.success(productBoms);
    }
    @PostMapping("uploadBom")
    @PreAuthorize("@ss.hasPermi('product:bom:import')")
src/main/java/com/ruoyi/production/controller/ProductWorkOrderController.java
@@ -5,10 +5,13 @@
import com.ruoyi.production.dto.ProductWorkOrderDto;
import com.ruoyi.production.pojo.ProductWorkOrder;
import com.ruoyi.production.service.ProductWorkOrderService;
import com.ruoyi.quality.pojo.QualityInspect;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@@ -17,44 +20,28 @@
@RequestMapping("/productWorkOrder")
public class ProductWorkOrderController {
    private ProductWorkOrderService productWorkOrderservice;
    private final ProductWorkOrderService productWorkOrderservice;
    /**
     * äº§å“å·¥å•实体类分页查询
     */
    @ApiOperation("产品工单实体类分页查询")
    @ApiOperation("产品工单分页查询")
    @GetMapping("/page")
    public R page(Page<ProductWorkOrderDto> page, ProductWorkOrderDto productWorkOrder) {
        return R.ok(productWorkOrderservice.listPage(page, productWorkOrder));
    }
    /**
     * äº§å“å·¥å•æ›´æ–°
     */
    @ApiOperation("产品工单更新")
    @PostMapping ("/updateProductWorkOrder")
    public R updateProductWorkOrder(@RequestBody ProductWorkOrderDto productWorkOrderDto) {
        return R.ok(productWorkOrderservice.updateProductWorkOrder(productWorkOrderDto));
    }
    /**
     * pda根据二维码的工单id查询数据
     */
    @ApiOperation("pda根据二维码的工单id查询数据")
    @ApiOperation("根据id查询工单")
    @GetMapping("/getProductWorkOrderById")
    public R getProductWorkOrderById(Long id) {
        return R.ok(productWorkOrderservice.getById(id));
        return R.ok(productWorkOrderservice.getProductWorkOrderById(id));
    }
    /**
     * å·¥å•流转卡下载
     * @param response
     * @param productWorkOrder
     */
    @PostMapping("/down")
    public void down(HttpServletResponse response, @RequestBody ProductWorkOrder productWorkOrder) {
        productWorkOrderservice.down(response, productWorkOrder);
    }
}
src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java
@@ -1,37 +1,28 @@
package com.ruoyi.production.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.production.pojo.ProductWorkOrder;
import com.ruoyi.production.pojo.ProductWorkOrderFile;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
public class ProductWorkOrderDto extends ProductWorkOrder {
    //产品名称
    @ApiModelProperty(value = "产品名称")
    private String productName;
    //规格
    @ApiModelProperty(value = "规格")
    private String model;
    //工序
    @ApiModelProperty(value = "工序")
    private String processName;
    //单位
    @ApiModelProperty(value = "单位")
    private String unit;
    //生产订单号
    @ApiModelProperty(value = "生产订单号")
    private String productOrderNpsNo;
@@ -43,4 +34,7 @@
    @ApiModelProperty(value = "工单类型 æ­£å¸¸ /返工返修")
    private String workOrderType;
    @ApiModelProperty(value = "报工人名称,多个逗号分隔")
    private String reportUserNames;
}
src/main/java/com/ruoyi/production/mapper/ProductOrderMapper.java
@@ -16,7 +16,14 @@
@Mapper
public interface ProductOrderMapper extends BaseMapper<ProductOrder> {
    IPage<ProductOrderDto> pageProductOrder(Page page, @Param("c") ProductOrderDto productOrder);
    default IPage<ProductOrderDto> pageProductOrder(Page page, ProductOrderDto productOrder) {
        return pageProductOrder(page, productOrder, null, true);
    }
    IPage<ProductOrderDto> pageProductOrder(Page page,
                                            @Param("c") ProductOrderDto productOrder,
                                            @Param("userId") Long userId,
                                            @Param("isAdmin") Boolean isAdmin);
    List<ProcessRoute> listProcessRoute(@Param("productModelId") Long productModelId);
src/main/java/com/ruoyi/production/mapper/ProductWorkOrderMapper.java
@@ -15,7 +15,16 @@
@Mapper
public interface ProductWorkOrderMapper extends BaseMapper<ProductWorkOrder> {
    IPage<ProductWorkOrderDto> pageProductWorkOrder(Page<ProductWorkOrderDto> page, @Param("c") ProductWorkOrderDto productWorkOrder);
    default IPage<ProductWorkOrderDto> pageProductWorkOrder(Page<ProductWorkOrderDto> page, ProductWorkOrderDto productWorkOrder) {
        return pageProductWorkOrder(page, productWorkOrder, null, true);
    }
    IPage<ProductWorkOrderDto> pageProductWorkOrder(Page<ProductWorkOrderDto> page,
                                                    @Param("c") ProductWorkOrderDto productWorkOrder,
                                                    @Param("userId") Long userId,
                                                    @Param("isAdmin") Boolean isAdmin);
    Integer checkUserCanAccess(@Param("workOrderId") Long workOrderId, @Param("userId") Long userId);
    ProductWorkOrderDto getProductWorkOrderFlowCard(@Param("id") Long id);
src/main/java/com/ruoyi/production/pojo/ProcessRoute.java
@@ -8,18 +8,25 @@
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@TableName("process_route")
@Data
@ApiModel(value = "processRoute", description = "工艺路线主表")
public class ProcessRoute {
    @ApiModelProperty(value = "序号")
    @ApiModelProperty(value = "ID")
    private Long id;
    @ApiModelProperty(value = "产品ID")
    //product_model
    @ApiModelProperty(value = "产品规格ID(兼容旧字段)")
    private Long productModelId;
    @ApiModelProperty(value = "多产品ID,逗号分隔")
    private String productModelIds;
    @TableField(exist = false)
    @ApiModelProperty(value = "多产品ID列表")
    private List<Long> productModelIdList;
    @ApiModelProperty(value = "描述")
    private String description;
@@ -39,6 +46,6 @@
    @ApiModelProperty(value = "工艺路线编码")
    private String processRouteCode;
    @ApiModelProperty(value = "BOM的ID")
    @ApiModelProperty(value = "BOM ID")
    private Integer bomId;
}
src/main/java/com/ruoyi/production/pojo/ProcessRouteItem.java
@@ -6,30 +6,40 @@
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("process_route_item")
@ApiModel(value = "processRouteItem", description = "工艺路线子表")
@ApiModel(value = "processRouteItem", description = "工艺路线明细")
public class ProcessRouteItem {
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "工艺路线id")
    @ApiModelProperty(value = "工艺路线ID")
    private Long routeId;
    @ApiModelProperty(value = "工序id")
    @ApiModelProperty(value = "工序ID")
    private Long processId;
    @ApiModelProperty(value ="产品id")
    @ApiModelProperty(value = "产品规格ID(兼容旧字段)")
    private Long productModelId;
    @ApiModelProperty(value = "多产品ID,逗号分隔")
    private String productModelIds;
    @TableField(exist = false)
    @ApiModelProperty(value = "多产品ID列表")
    private List<Long> productModelIdList;
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
@@ -37,8 +47,6 @@
    @ApiModelProperty(value ="拖动排序")
    private Integer dragSort;
    @ApiModelProperty(value ="是否质检")
    private Boolean isQuality;
}
src/main/java/com/ruoyi/production/pojo/ProductOrder.java
@@ -1,9 +1,12 @@
package com.ruoyi.production.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -11,6 +14,7 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("product_order")
@@ -21,45 +25,26 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * é”€å”®å°è´¦id
     */
    @ApiModelProperty(value = "销售台账id")
    private Long salesLedgerId;
    /**
     * é”€å”®å°è´¦äº§å“id(sales_ledger_product)
     */
    @ApiModelProperty(value = "销售台账产品id")
    private Long saleLedgerProductId;
    /**
     * äº§å“è§„æ ¼id
     */
    @ApiModelProperty(value = "产品规格id")
    private Long productModelId;
    /**
     * æ¨¡ç‰ˆçš„工艺路线id
     */
    @ApiModelProperty(value = "工艺路线id")
    private Long routeId;
    /**
     * ç”Ÿäº§è®¢å•号
     */
    @ApiModelProperty(value = "生产订单号")
    @Excel(name = "生产订单号")
    private String npsNo;
    /**
     * ç§Ÿæˆ·id
     */
    @ApiModelProperty(value = "租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    //创建时间
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@@ -67,24 +52,16 @@
    @Excel(name = "创建时间")
    private LocalDateTime createTime;
    //修改时间
    @ApiModelProperty(value = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    /**
     * éœ€æ±‚数量
     */
    @ApiModelProperty(value = "需求数量")
    @Excel(name = "需求数量")
    private BigDecimal quantity;
    /**
     * å®Œæˆæ•°é‡
     */
    @ApiModelProperty(value = "完成数量")
    @Excel(name = "完成数量")
    private BigDecimal completeQuantity;
@@ -101,6 +78,15 @@
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime endTime;
    @TableField(exist = false)
    @ApiModelProperty(value = "前端传入的工序列表")
    private List<ProductProcessRouteItem> processRouteItems;
    @TableField(exist = false)
    @ApiModelProperty(value = "前端传入的工艺路线编号")
    private String processRouteCode;
    @TableField(exist = false)
    @ApiModelProperty(value = "前端传入的BOM id")
    private Integer bomId;
}
src/main/java/com/ruoyi/production/pojo/ProductProcessRouteItem.java
@@ -1,6 +1,10 @@
package com.ruoyi.production.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -13,24 +17,26 @@
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "生产订单id(product_order_id)")
    @ApiModelProperty(value = "生产订单id")
    private Long productOrderId;
    @ApiModelProperty(value = "生产订单的工艺路线id(product_process_route)")
    @ApiModelProperty(value = "生产订单的工艺路线id")
    private Long productRouteId;
    @ApiModelProperty(value = "工序id")
    private Long processId;
    @ApiModelProperty(value ="产品id")
    @ApiModelProperty(value = "产品规格id")
    private Long productModelId;
    @ApiModelProperty(value = "租户ID")
    @ApiModelProperty(value = "租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
@@ -41,4 +47,7 @@
    @ApiModelProperty(value ="是否质检")
    private Boolean isQuality;
    @ApiModelProperty(value = "报工人id,多选,逗号分隔")
    @TableField(exist = false)
    private String reportUserIds;
}
src/main/java/com/ruoyi/production/pojo/ProductWorkOrder.java
@@ -121,5 +121,11 @@
    @ApiModelProperty(value = "完成数量")
    private BigDecimal completeQuantity;
    /**
     * æŠ¥å·¥äººid,多选,逗号分隔
     */
    @ApiModelProperty(value = "报工人id,多选,逗号分隔")
    private String reportUserIds;
}
src/main/java/com/ruoyi/production/service/ProductWorkOrderService.java
@@ -14,5 +14,7 @@
    int updateProductWorkOrder(ProductWorkOrderDto productWorkOrderDto);
    ProductWorkOrder getProductWorkOrderById(Long id);
    void down(HttpServletResponse response, ProductWorkOrder productWorkOrder);
}
src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java
@@ -19,11 +19,14 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
@@ -44,19 +47,19 @@
    @Override
    public IPage<ProcessRouteDto> pageProcessRouteDto(Page<ProcessRouteDto> page, ProcessRouteDto processRouteDto) {
        return processRouteMapper.pageProcessRouteDto(page, processRouteDto);
    }
    @Override
    public Integer saveProcessRoute(ProcessRoute processRoute) {
        normalizeProductModelIds(processRoute);
        this.save(processRoute);
        String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String idStr = String.format("%06d", processRoute.getId());
        String newProductCode = "GYLX" + dateStr + idStr;
        // æ›´æ–°æ•°æ®åº“中的productCode
        processRoute.setProcessRouteCode(newProductCode);
        // æŸ¥è¯¢bom清单
        String routeCode = "GYLX" + dateStr + idStr;
        processRoute.setProcessRouteCode(routeCode);
        List<ProductStructureDto> productStructureDtos = productStructureService.listDetailBybomId(processRoute.getBomId());
        if(CollectionUtils.isNotEmpty(productStructureDtos)){
            AtomicInteger i = new AtomicInteger(1);
@@ -64,6 +67,8 @@
                ProcessRouteItem processRouteItem = new ProcessRouteItem();
                processRouteItem.setRouteId(processRoute.getId());
                processRouteItem.setProcessId(productStructureDto.getProcessId());
                processRouteItem.setProductModelId(processRoute.getProductModelId());
                processRouteItem.setProductModelIds(processRoute.getProductModelIds());
                processRouteItem.setDragSort(i.get());
                processRouteItem.setIsQuality(false);
                processRouteItemMapper.insert(processRouteItem);
@@ -75,13 +80,59 @@
    @Override
    public int batchDelete(List<Long> ids) {
        //先判断是否已经引用了
        List<ProductOrder> productOrders = productOrderMapper.selectList(Wrappers.<ProductOrder>lambdaQuery().in(ProductOrder::getRouteId, ids));
        if (productOrders.size()>0){
            throw new RuntimeException("该工艺路线生产已引用,不能删除");
        List<ProductOrder> productOrders = productOrderMapper.selectList(
                Wrappers.<ProductOrder>lambdaQuery().in(ProductOrder::getRouteId, ids)
        );
        if (!productOrders.isEmpty()) {
            throw new RuntimeException("该工艺路线已被生产订单引用,不能删除");
        }
        //删除工艺路线详情
        processRouteItemMapper.delete(Wrappers.<ProcessRouteItem>lambdaQuery().in(ProcessRouteItem::getRouteId, ids));
        return processRouteMapper.deleteBatchIds(ids);
    }
    private void normalizeProductModelIds(ProcessRoute processRoute) {
        if (processRoute == null) {
            return;
        }
        String ids = joinProductModelIds(
                processRoute.getProductModelIdList(),
                processRoute.getProductModelIds(),
                processRoute.getProductModelId()
        );
        processRoute.setProductModelIds(ids);
        processRoute.setProductModelId(firstProductModelId(ids, processRoute.getProductModelId()));
    }
    private String joinProductModelIds(List<Long> idList, String ids, Long fallbackId) {
        if (CollectionUtils.isNotEmpty(idList)) {
            return idList.stream()
                    .filter(item -> item != null && item > 0)
                    .distinct()
                    .map(String::valueOf)
                    .collect(Collectors.joining(","));
        }
        if (StringUtils.hasText(ids)) {
            return Arrays.stream(ids.split(","))
                    .map(String::trim)
                    .filter(StringUtils::hasText)
                    .distinct()
                    .collect(Collectors.joining(","));
        }
        return fallbackId == null ? null : String.valueOf(fallbackId);
    }
    private Long firstProductModelId(String ids, Long fallbackId) {
        if (!StringUtils.hasText(ids)) {
            return fallbackId;
        }
        String first = ids.split(",")[0].trim();
        if (!StringUtils.hasText(first)) {
            return fallbackId;
        }
        try {
            return Long.parseLong(first);
        } catch (NumberFormatException ignore) {
            return fallbackId;
        }
    }
}
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java
@@ -7,16 +7,23 @@
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.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.production.dto.ProductOrderDto;
import com.ruoyi.production.dto.ProductStructureDto;
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.mapper.ProcessRouteMapper;
import com.ruoyi.production.mapper.ProductOrderMapper;
import com.ruoyi.production.mapper.ProductProcessRouteItemMapper;
import com.ruoyi.production.mapper.ProductProcessRouteMapper;
import com.ruoyi.production.mapper.ProductWorkOrderMapper;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import com.ruoyi.production.pojo.ProcessRoute;
import com.ruoyi.production.pojo.ProductOrder;
import com.ruoyi.production.pojo.ProductProcessRoute;
import com.ruoyi.production.pojo.ProductProcessRouteItem;
import com.ruoyi.production.pojo.ProductWorkOrder;
import com.ruoyi.production.pojo.ProductionProductMain;
import com.ruoyi.production.service.ProductOrderService;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -39,9 +46,6 @@
    private ProductProcessRouteMapper productProcessRouteMapper;
    @Autowired
    private ProcessRouteItemMapper processRouteItemMapper;
    @Autowired
    private ProductProcessRouteItemMapper productProcessRouteItemMapper;
    @Autowired
@@ -50,54 +54,48 @@
    @Autowired
    private ProductionProductMainMapper productionProductMainMapper;
    @Autowired
    private ProductionProductOutputMapper productionProductOutputMapper;
    @Autowired
    private ProductionProductInputMapper productionProductInputMapper;
    @Autowired
    private QualityInspectMapper qualityInspectMapper;
    @Autowired
    private SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
    @Autowired
    private StockUtils stockUtils;
    @Override
    public IPage<ProductOrderDto> pageProductOrder(Page page, ProductOrderDto productOrder) {
        return productOrderMapper.pageProductOrder(page, productOrder);
        Long userId = SecurityUtils.getUserId();
        boolean isAdmin = SecurityUtils.isAdmin(userId) || SecurityUtils.hasRole(Constants.SUPER_ADMIN);
        return productOrderMapper.pageProductOrder(page, productOrder, userId, isAdmin);
    }
    @Override
    public int bindingRoute(ProductOrder productOrder) {
        //新增生产订单下的工艺路线主表
        ProcessRoute processRoute = processRouteMapper.selectById(productOrder.getRouteId());
        List<ProductProcessRouteItem> processRouteItems = productOrder.getProcessRouteItems();
        if (ObjectUtils.isEmpty(processRouteItems)) {
            throw new RuntimeException("工序列表不能为空");
        }
        ProcessRoute processRoute = null;
        if (ObjectUtils.isNotEmpty(productOrder.getRouteId())) {
            processRoute = processRouteMapper.selectById(productOrder.getRouteId());
        }
        ProductProcessRoute productProcessRoute = new ProductProcessRoute();
        productProcessRoute.setProductModelId(processRoute.getProductModelId());
        productProcessRoute.setProcessRouteCode(processRoute.getProcessRouteCode());
        productProcessRoute.setProductModelId(productOrder.getProductModelId());
        productProcessRoute.setProcessRouteCode(processRoute != null ? processRoute.getProcessRouteCode() : productOrder.getProcessRouteCode());
        productProcessRoute.setProductOrderId(productOrder.getId());
        productProcessRoute.setBomId(processRoute.getBomId());
        productProcessRoute.setBomId(processRoute != null ? processRoute.getBomId() : productOrder.getBomId());
        productProcessRouteMapper.insert(productProcessRoute);
        //新增生产订单下的工艺路线子表
        List<ProcessRouteItem> processRouteItems = processRouteItemMapper.selectList(new QueryWrapper<ProcessRouteItem>().lambda().eq(ProcessRouteItem::getRouteId, processRoute.getId()));
        // ç”Ÿæˆå½“前日期的前缀:年月日
        String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        for (ProcessRouteItem processRouteItem : processRouteItems) {
        for (ProductProcessRouteItem processRouteItem : processRouteItems) {
            ProductProcessRouteItem productProcessRouteItem = new ProductProcessRouteItem();
            productProcessRouteItem.setProductModelId(processRouteItem.getProductModelId());
            productProcessRouteItem.setProductModelId(productOrder.getProductModelId());
            productProcessRouteItem.setProcessId(processRouteItem.getProcessId());
            productProcessRouteItem.setProductOrderId(productOrder.getId());
            productProcessRouteItem.setProductRouteId(productProcessRoute.getId());
            productProcessRouteItem.setDragSort(processRouteItem.getDragSort());
            productProcessRouteItem.setIsQuality(processRouteItem.getIsQuality());
            productProcessRouteItem.setReportUserIds(processRouteItem.getReportUserIds());
            int insert = productProcessRouteItemMapper.insert(productProcessRouteItem);
            if (insert > 0) {
                // æŸ¥è¯¢ä»Šæ—¥å·²å­˜åœ¨çš„æœ€å¤§å·¥å•号
                ProductWorkOrder lastWorkOrder = productWorkOrderMapper.selectMax(datePrefix);
                int sequenceNumber = 1; // é»˜è®¤åºå·
                int sequenceNumber = 1;
                if (lastWorkOrder != null && lastWorkOrder.getWorkOrderNo() != null) {
                    String lastNo = lastWorkOrder.getWorkOrderNo().toString();
                    String lastNo = lastWorkOrder.getWorkOrderNo();
                    if (lastNo.startsWith(datePrefix)) {
                        String seqStr = lastNo.substring(datePrefix.length());
                        try {
@@ -107,13 +105,14 @@
                        }
                    }
                }
                // ç”Ÿæˆå®Œæ•´çš„工单号
                String workOrderNoStr = "GD" + String.format("%s%03d", datePrefix, sequenceNumber);
                ProductWorkOrder productWorkOrder = new ProductWorkOrder();
                productWorkOrder.setProductProcessRouteItemId(productProcessRouteItem.getId());
                productWorkOrder.setProductOrderId(productOrder.getId());
                ProductOrder order = productOrderMapper.selectById(productOrder.getId());
                productWorkOrder.setPlanQuantity(order.getQuantity());
                // æ–°å¢žç”Ÿäº§è®¢å•时,将工序上的报工人同步到工单,后续按工单报工人做权限校验
                productWorkOrder.setReportUserIds(productProcessRouteItem.getReportUserIds());
                productWorkOrder.setWorkOrderNo(workOrderNoStr);
                productWorkOrder.setStatus(1);
                productWorkOrderMapper.insert(productWorkOrder);
@@ -134,11 +133,11 @@
    @Override
    public Boolean addProductOrder(ProductOrder productOrder) {
        String string = generateNextOrderNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        productOrder.setNpsNo(string);
        String orderNo = generateNextOrderNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        productOrder.setNpsNo(orderNo);
        productOrder.setCompleteQuantity(BigDecimal.ZERO);
        this.save(productOrder);
        if (ObjectUtils.isNotEmpty(productOrder.getRouteId())) {
        if (ObjectUtils.isNotEmpty(productOrder.getProcessRouteItems())) {
            this.bindingRoute(productOrder);
        }
        return true;
@@ -146,36 +145,32 @@
    @Override
    public Boolean delete(Long[] ids) {
        //如果已经开始生产,不能删除
        //查询生产订单下的工单
        List<ProductWorkOrder> productWorkOrders = productWorkOrderMapper.selectList(Wrappers.<ProductWorkOrder>lambdaQuery().in(ProductWorkOrder::getProductOrderId, ids));
        if (productWorkOrders.size()>0){
            //判断是否有报工数据
            List<ProductionProductMain> productionProductMains = productionProductMainMapper.selectList(Wrappers.<ProductionProductMain>lambdaQuery()
                    .in(ProductionProductMain::getWorkOrderId, productWorkOrders.stream().map(ProductWorkOrder::getId).collect(Collectors.toList())));
            if (productionProductMains.size()>0){
                throw new RuntimeException("生产订单已经开始生产,不能删除");
        List<ProductWorkOrder> productWorkOrders = productWorkOrderMapper.selectList(
                Wrappers.<ProductWorkOrder>lambdaQuery().in(ProductWorkOrder::getProductOrderId, ids)
        );
        if (!productWorkOrders.isEmpty()) {
            List<ProductionProductMain> productionProductMains = productionProductMainMapper.selectList(
                    Wrappers.<ProductionProductMain>lambdaQuery()
                            .in(ProductionProductMain::getWorkOrderId, productWorkOrders.stream().map(ProductWorkOrder::getId).collect(Collectors.toList()))
            );
            if (!productionProductMains.isEmpty()) {
                throw new RuntimeException("生产订单已经开始生产,不能删除");
            }
            //删除工单
            productWorkOrderMapper.delete(Wrappers.<ProductWorkOrder>lambdaQuery().in(ProductWorkOrder::getProductOrderId, ids));
        }
        //删除工艺路线
        productProcessRouteItemMapper.delete(new LambdaQueryWrapper<ProductProcessRouteItem>()
                .in(ProductProcessRouteItem::getProductOrderId, ids));
        productProcessRouteMapper.delete(new LambdaQueryWrapper<ProductProcessRoute>()
                .in(ProductProcessRoute::getProductOrderId, ids));
        //删除生产订单
        productOrderMapper.delete(new LambdaQueryWrapper<ProductOrder>()
                .in(ProductOrder::getId, ids));
        return true;
    }
    //获取当前生产订单号
    public String getMaxOrderNoByDate(String datePrefix) {
        QueryWrapper<ProductOrder> queryWrapper = new QueryWrapper<>();
        // åŒ¹é…ä»¥ SC + æ—¥æœŸå¼€å¤´çš„订单号
        queryWrapper.likeRight("nps_no", "SC" + datePrefix);
        // æŒ‰è®¢å•号倒序排列
        queryWrapper.orderByDesc("nps_no");
        queryWrapper.last("LIMIT 1");
@@ -185,20 +180,15 @@
    public String generateNextOrderNo(String datePrefix) {
        String maxOrderNo = getMaxOrderNoByDate(datePrefix);
        int sequence = 1; // é»˜è®¤èµ·å§‹åºå·
        int sequence = 1;
        if (maxOrderNo != null && !maxOrderNo.isEmpty()) {
            // æå–流水号部分(假设格式为 SC + æ—¥æœŸ + æµæ°´å·ï¼‰
            String sequenceStr = maxOrderNo.substring(("SC" + datePrefix).length());
            try {
                sequence = Integer.parseInt(sequenceStr) + 1;
            } catch (NumberFormatException e) {
                // å¼‚常情况下重置为1
                sequence = 1;
            }
        }
        // ç”Ÿæˆæ–°è®¢å•号
        return "SC" + datePrefix + String.format("%04d", sequence);
    }
}
src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java
@@ -161,6 +161,8 @@
            productWorkOrder.setProductProcessRouteItemId(productProcessRouteItem.getId());
            productWorkOrder.setProductOrderId(productProcessRouteItem.getProductOrderId());
            productWorkOrder.setPlanQuantity(productOrder.getQuantity());
            // æ‰‹åŠ¨æ–°å¢žå·¥åºå¹¶ç”Ÿæˆå·¥å•æ—¶ï¼ŒåŒæ­¥å·¥åºæŠ¥å·¥äººåˆ°å·¥å•
            productWorkOrder.setReportUserIds(productProcessRouteItem.getReportUserIds());
            productWorkOrder.setWorkOrderNo(workOrderNoStr);
            productWorkOrder.setStatus(1);
            productWorkOrderMapper.insert(productWorkOrder);
src/main/java/com/ruoyi/production/service/impl/ProductStructureServiceImpl.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.production.dto.ProductStructureDto;
import com.ruoyi.production.mapper.ProductStructureMapper;
import com.ruoyi.production.pojo.ProductStructure;
@@ -86,7 +87,7 @@
        //  çœŸå®žçš„父节点 ID
        Long realParentId;
        for (ProductStructureDto psDto : flatDtoList) {
            if (psDto.getId() == null && psDto.getParentTempId() != null) {
            if (psDto.getId() == null && StringUtils.isNotEmpty(psDto.getParentTempId())) {
                ProductStructure child = tempEntityMap.get(psDto.getTempId());
                if (tempEntityMap.containsKey(psDto.getParentTempId())) {
                    // çˆ¶èŠ‚ç‚¹æ˜¯æ–°èŠ‚ç‚¹
src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java
@@ -1,25 +1,22 @@
package com.ruoyi.production.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
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.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.Pictures;
import com.ruoyi.common.utils.HackLoopTableRenderPolicy;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.MatrixToImageWriter;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.production.dto.ProductWorkOrderDto;
import com.ruoyi.production.mapper.ProductWorkOrderFileMapper;
import com.ruoyi.production.mapper.ProductWorkOrderMapper;
import com.ruoyi.production.pojo.ProductWorkOrder;
import com.ruoyi.production.pojo.ProductWorkOrderFile;
import com.ruoyi.production.service.ProductWorkOrderService;
import com.ruoyi.quality.pojo.QualityInspectParam;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -48,16 +45,26 @@
    @Override
    public IPage<ProductWorkOrderDto> listPage(Page<ProductWorkOrderDto> page, ProductWorkOrderDto productWorkOrder) {
        return productWorkOrdermapper.pageProductWorkOrder(page, productWorkOrder);
        Long userId = SecurityUtils.getUserId();
        boolean isAdmin = SecurityUtils.isAdmin(userId) || SecurityUtils.hasRole(Constants.SUPER_ADMIN);
        return productWorkOrdermapper.pageProductWorkOrder(page, productWorkOrder, userId, isAdmin);
    }
    @Override
    public int updateProductWorkOrder(ProductWorkOrderDto productWorkOrderDto) {
        checkWorkOrderPermission(productWorkOrderDto.getId());
        return productWorkOrdermapper.updateById(productWorkOrderDto);
    }
    @Override
    public ProductWorkOrder getProductWorkOrderById(Long id) {
        checkWorkOrderPermission(id);
        return this.getById(id);
    }
    @Override
    public void down(HttpServletResponse response, ProductWorkOrder productWorkOrder) {
        checkWorkOrderPermission(productWorkOrder.getId());
        ProductWorkOrderDto productWorkOrderDto = productWorkOrdermapper.getProductWorkOrderFlowCard(productWorkOrder.getId());
        String codePath;
        try {
@@ -65,7 +72,6 @@
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        /*获取附件图片类型*/
        List<Map<String, Object>> images = new ArrayList<>();
        List<ProductWorkOrderFile> productWorkOrderFiles = productWorkOrderFileMapper.selectList(Wrappers.<ProductWorkOrderFile>lambdaQuery().eq(ProductWorkOrderFile::getWorkOrderId, productWorkOrder.getId()));
        if (CollectionUtils.isNotEmpty(productWorkOrderFiles)) {
@@ -97,11 +103,9 @@
        try {
            response.setContentType("application/msword");
            String fileName = URLEncoder.encode(
                    "流转卡", "UTF-8");
            String fileName = URLEncoder.encode("流转卡", "UTF-8");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.setHeader("Content-disposition",
                    "attachment;filename=" + fileName + ".docx");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".docx");
            OutputStream os = response.getOutputStream();
            template.write(os);
            os.flush();
@@ -113,4 +117,18 @@
        }
    }
    private void checkWorkOrderPermission(Long workOrderId) {
        if (workOrderId == null) {
            throw new RuntimeException("工单ID不能为空");
        }
        Long userId = SecurityUtils.getUserId();
        boolean isAdmin = SecurityUtils.isAdmin(userId) || SecurityUtils.hasRole(Constants.SUPER_ADMIN);
        if (isAdmin) {
            return;
        }
        Integer count = productWorkOrdermapper.checkUserCanAccess(workOrderId, userId);
        if (count == null || count == 0) {
            throw new RuntimeException("无权访问该工单");
        }
    }
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -6,6 +6,10 @@
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.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.framework.web.domain.R;
@@ -62,6 +66,10 @@
    private SalesLedgerMapper salesLedgerMapper;
    private PurchaseLedgerMapper purchaseLedgerMapper;
    private ProductMapper productMapper;
    private ProductModelMapper productModelMapper;
    private ProductOrderMapper productOrderMapper;
@@ -263,74 +271,103 @@
    }
    /**
     * é€šè¿‡äº§å“å¤§ç±»ï¼Œè§„格型号查询productModelId
     */
    public Long getProductModelId(String productCategory, String productSpecification) {
        Product product = productMapper.selectOne(new QueryWrapper<Product>()
                .lambda()
                .eq(Product::getProductName, productCategory)
                .orderByDesc(Product::getId)
                .last("LIMIT 1"));
        if(product == null){
            throw new RuntimeException("请先添加产品");
        }
        ProductModel productModel = productModelMapper.selectOne(new QueryWrapper<ProductModel>()
                .lambda()
                .eq(ProductModel::getProductId, product.getId())
                .eq(ProductModel::getModel, productSpecification)
                .orderByDesc(ProductModel::getId)
                .last("LIMIT 1"));
        return productModel == null ? null : productModel.getId();
    }
    /**
     * æ–°å¢žç”Ÿäº§æ•°æ®
     */
    public void addProductionData(SalesLedgerProduct salesLedgerProduct) {
        ProductOrder productOrder = new ProductOrder();
        productOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId());
        if(salesLedgerProduct.getProductModelId() == null){
            Long productModelId = getProductModelId(salesLedgerProduct.getProductCategory(), salesLedgerProduct.getSpecificationModel());
            productOrder.setProductModelId(productModelId);
        }else{
        productOrder.setProductModelId(salesLedgerProduct.getProductModelId());
        }
        productOrder.setSaleLedgerProductId(salesLedgerProduct.getId());
        String string = productOrderServiceImpl.generateNextOrderNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        productOrder.setNpsNo(string);
        productOrder.setQuantity(salesLedgerProduct.getQuantity());//需求数量
        productOrder.setCompleteQuantity(BigDecimal.ZERO);//完成数量
        productOrderMapper.insert(productOrder);
        List<ProcessRoute> processRoutes = processRouteMapper.selectList(new QueryWrapper<ProcessRoute>().lambda()
                .eq(ProcessRoute::getProductModelId, salesLedgerProduct.getProductModelId())
                .orderByDesc(ProcessRoute::getCreateTime));
        if (processRoutes.size()>0){
            ProcessRoute processRoute = processRoutes.get(0);
            //新增生产订单工艺路线主表
            ProductProcessRoute productProcessRoute = new ProductProcessRoute();
            productProcessRoute.setProductModelId(processRoute.getProductModelId());
            productProcessRoute.setProcessRouteCode(processRoute.getProcessRouteCode());
            productProcessRoute.setProductOrderId(productOrder.getId());
            productProcessRoute.setBomId(processRoute.getBomId());
            productProcessRouteMapper.insert(productProcessRoute);
            //新增生产订单工艺路线子表
            List<ProcessRouteItem> processRouteItems = processRouteItemMapper.selectList(new QueryWrapper<ProcessRouteItem>().lambda().eq(ProcessRouteItem::getRouteId, processRoute.getId()));
            // ç”Ÿæˆå½“前日期的前缀:年月日
            String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            for (ProcessRouteItem processRouteItem : processRouteItems) {
                ProductProcessRouteItem productProcessRouteItem = new ProductProcessRouteItem();
                productProcessRouteItem.setProductModelId(processRouteItem.getProductModelId());
                productProcessRouteItem.setProcessId(processRouteItem.getProcessId());
                productProcessRouteItem.setProductOrderId(productOrder.getId());
                productProcessRouteItem.setProductRouteId(productProcessRoute.getId());
                productProcessRouteItem.setDragSort(processRouteItem.getDragSort());
                int insert = productProcessRouteItemMapper.insert(productProcessRouteItem);
                if (insert > 0) {
                    // æŸ¥è¯¢ä»Šæ—¥å·²å­˜åœ¨çš„æœ€å¤§å·¥å•号
                    ProductWorkOrder lastWorkOrder = productWorkOrderMapper.selectMax(datePrefix);
                    int sequenceNumber = 1; // é»˜è®¤åºå·
                    if (lastWorkOrder != null && lastWorkOrder.getWorkOrderNo() != null) {
                        String lastNo = lastWorkOrder.getWorkOrderNo().toString();
                        if (lastNo.startsWith(datePrefix)) {
                            String seqStr = lastNo.substring(datePrefix.length());
                            try {
                                sequenceNumber = Integer.parseInt(seqStr) + 1;
                            } catch (NumberFormatException e) {
                                sequenceNumber = 1;
                            }
                        }
                    }
                    // ç”Ÿæˆå®Œæ•´çš„工单号
                    String workOrderNoStr ="GD"+ String.format("%s%03d", datePrefix, sequenceNumber);
                    ProductWorkOrder productWorkOrder = new ProductWorkOrder();
                    productWorkOrder.setProductProcessRouteItemId(productProcessRouteItem.getId());
                    productWorkOrder.setProductOrderId(productOrder.getId());
                    productWorkOrder.setPlanQuantity(salesLedgerProduct.getQuantity());
                    productWorkOrder.setWorkOrderNo(workOrderNoStr);
                    productWorkOrder.setStatus(1);
                    productWorkOrderMapper.insert(productWorkOrder);
                }
            }
            productOrder.setRouteId(processRoute.getId());
            productOrderMapper.updateById(productOrder);
        }
        // å–消默认的工艺路线
//        List<ProcessRoute> processRoutes = processRouteMapper.selectList(
//                new QueryWrapper<ProcessRoute>()
//                        .and(wrapper -> wrapper.eq("product_model_id", salesLedgerProduct.getProductModelId())
//                                .or()
//                                .apply("find_in_set({0}, product_model_ids)", salesLedgerProduct.getProductModelId()))
//                        .orderByDesc("create_time"));
//        if (processRoutes.size()>0){
//            ProcessRoute processRoute = processRoutes.get(0);
//            //新增生产订单工艺路线主表
//            ProductProcessRoute productProcessRoute = new ProductProcessRoute();
//            productProcessRoute.setProductModelId(salesLedgerProduct.getProductModelId());
//            productProcessRoute.setProcessRouteCode(processRoute.getProcessRouteCode());
//            productProcessRoute.setProductOrderId(productOrder.getId());
//            productProcessRoute.setBomId(processRoute.getBomId());
//            productProcessRouteMapper.insert(productProcessRoute);
//            //新增生产订单工艺路线子表
//            List<ProcessRouteItem> processRouteItems = processRouteItemMapper.selectList(new QueryWrapper<ProcessRouteItem>().lambda().eq(ProcessRouteItem::getRouteId, processRoute.getId()));
//            // ç”Ÿæˆå½“前日期的前缀:年月日
//            String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
//            for (ProcessRouteItem processRouteItem : processRouteItems) {
//                ProductProcessRouteItem productProcessRouteItem = new ProductProcessRouteItem();
//                productProcessRouteItem.setProductModelId(salesLedgerProduct.getProductModelId());
//                productProcessRouteItem.setProcessId(processRouteItem.getProcessId());
//                productProcessRouteItem.setProductOrderId(productOrder.getId());
//                productProcessRouteItem.setProductRouteId(productProcessRoute.getId());
//                productProcessRouteItem.setDragSort(processRouteItem.getDragSort());
//                int insert = productProcessRouteItemMapper.insert(productProcessRouteItem);
//                if (insert > 0) {
//                    // æŸ¥è¯¢ä»Šæ—¥å·²å­˜åœ¨çš„æœ€å¤§å·¥å•号
//                    ProductWorkOrder lastWorkOrder = productWorkOrderMapper.selectMax(datePrefix);
//                    int sequenceNumber = 1; // é»˜è®¤åºå·
//                    if (lastWorkOrder != null && lastWorkOrder.getWorkOrderNo() != null) {
//                        String lastNo = lastWorkOrder.getWorkOrderNo().toString();
//                        if (lastNo.startsWith(datePrefix)) {
//                            String seqStr = lastNo.substring(datePrefix.length());
//                            try {
//                                sequenceNumber = Integer.parseInt(seqStr) + 1;
//                            } catch (NumberFormatException e) {
//                                sequenceNumber = 1;
//                            }
//                        }
//                    }
//                    // ç”Ÿæˆå®Œæ•´çš„工单号
//                    String workOrderNoStr ="GD"+ String.format("%s%03d", datePrefix, sequenceNumber);
//                    ProductWorkOrder productWorkOrder = new ProductWorkOrder();
//                    productWorkOrder.setProductProcessRouteItemId(productProcessRouteItem.getId());
//                    productWorkOrder.setProductOrderId(productOrder.getId());
//                    productWorkOrder.setPlanQuantity(salesLedgerProduct.getQuantity());
//                    productWorkOrder.setWorkOrderNo(workOrderNoStr);
//                    productWorkOrder.setStatus(1);
//
//                    productWorkOrderMapper.insert(productWorkOrder);
//                }
//
//            }
//            productOrder.setRouteId(processRoute.getId());
//            productOrderMapper.updateById(productOrder);
//        }
    }
    /**
@@ -491,7 +528,13 @@
    @Override
    public R judgmentInventory(SalesLedgerProduct salesLedgerProduct) {
        //获取产品最新的工艺路线
        ProcessRoute processRoute = processRouteMapper.selectOne(new QueryWrapper<ProcessRoute>().lambda().eq(ProcessRoute::getProductModelId, salesLedgerProduct.getProductModelId()).orderByDesc(ProcessRoute::getCreateTime).last("LIMIT 1"));
        ProcessRoute processRoute = processRouteMapper.selectOne(
                new QueryWrapper<ProcessRoute>()
                        .and(wrapper -> wrapper.eq("product_model_id", salesLedgerProduct.getProductModelId())
                                .or()
                                .apply("find_in_set({0}, product_model_ids)", salesLedgerProduct.getProductModelId()))
                        .orderByDesc("create_time")
                        .last("LIMIT 1"));
        if (processRoute == null) {
            return R.fail("请先设置工艺路线");
        }
src/main/resources/mapper/production/ProcessRouteItemMapper.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.production.mapper.ProcessRouteItemMapper">
    <resultMap id="basicMap" type="com.ruoyi.production.pojo.ProcessRouteItem">
@@ -7,29 +7,84 @@
        <result property="routeId" column="route_id"/>
        <result property="processId" column="process_id"/>
        <result property="productModelId" column="product_model_id"/>
        <result property="productModelIds" column="product_model_ids"/>
        <result property="tenantId" column="tenant_id"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
        <result property="dragSort" column="drag_sort"/>
        <result property="isQuality" column="is_quality"/>
    </resultMap>
    <select id="listProcessRouteItemDto" resultType="com.ruoyi.production.dto.ProcessRouteItemDto">
        select pri.*,
               pr.description ,
               pr.description as route_name,
               pp.name as process_name,
               pm.speculative_trading_name,
               pm.product_id,
               pm.model,
               p.product_name,
               pm.unit
        from
            process_route_item pri
                left join product_model pm on pri.product_model_id = pm.id
               t.speculative_trading_name,
               t.product_id,
               t.model,
               t.product_name,
               t.unit
        from process_route_item pri
                left join product_process pp on pp.id = pri.process_id
                left join product p on p.id = pm.product_id
                left join process_route pr on pr.id = pri.route_id
        where
            pri.route_id = #{c.routeId}
        left join (
            select item.id as item_id,
                   group_concat(
                           distinct pm.speculative_trading_name
                           order by
                               case
                                   when item.product_model_ids is null or item.product_model_ids = '' then pm.id
                                   else find_in_set(pm.id, item.product_model_ids)
                               end
                           separator ','
                   ) as speculative_trading_name,
                   max(pm.product_id) as product_id,
                   group_concat(
                           distinct pm.model
                           order by
                               case
                                   when item.product_model_ids is null or item.product_model_ids = '' then pm.id
                                   else find_in_set(pm.id, item.product_model_ids)
                               end
                           separator ','
                   ) as model,
                   group_concat(
                           distinct p.product_name
                           order by
                               case
                                   when item.product_model_ids is null or item.product_model_ids = '' then pm.id
                                   else find_in_set(pm.id, item.product_model_ids)
                               end
                           separator ','
                   ) as product_name,
                   group_concat(
                           distinct pm.unit
                           order by
                               case
                                   when item.product_model_ids is null or item.product_model_ids = '' then pm.id
                                   else find_in_set(pm.id, item.product_model_ids)
                               end
                           separator ','
                   ) as unit
            from process_route_item item
            left join product_model pm
                   on (find_in_set(pm.id, item.product_model_ids) > 0
                       or ((item.product_model_ids is null or item.product_model_ids = '') and pm.id = item.product_model_id))
            left join product p on p.id = pm.product_id
            group by item.id
        ) t on t.item_id = pri.id
        where pri.route_id = #{c.routeId}
        <if test="c.productModelId != null">
            and exists (
                select 1
                from product_model pm_filter
                where (
                    find_in_set(pm_filter.id, pri.product_model_ids) > 0
                    or ((pri.product_model_ids is null or pri.product_model_ids = '') and pm_filter.id = pri.product_model_id)
                )
                and pm_filter.id = #{c.productModelId}
            )
        </if>
        order by pri.drag_sort
    </select>
</mapper>
src/main/resources/mapper/production/ProcessRouteMapper.xml
@@ -1,26 +1,58 @@
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.production.mapper.ProcessRouteMapper">
    <resultMap id="basicMap" type="com.ruoyi.production.pojo.ProcessRoute">
            <id property="id" column="id"/>
            <result property="productModelId" column="product_model_id"/>
        <result property="productModelIds" column="product_model_ids"/>
            <result property="description" column="description"/>
            <result property="tenantId" column="tenant_id"/>
            <result property="createTime" column="create_time"/>
            <result property="updateTime" column="update_time"/>
        <result property="processRouteCode" column="process_route_code"/>
        <result property="bomId" column="bom_id"/>
    </resultMap>
    <select id="pageProcessRouteDto" resultType="com.ruoyi.production.dto.ProcessRouteDto">
        select ps.*, p.product_name,pm.product_id,pm.model,pb.bom_no
        select ps.*,
               t.product_name,
               t.product_id,
               t.model,
               pb.bom_no
        from process_route ps
        left join product_bom pb on ps.bom_id = pb.id
        left join product_model pm on ps.product_model_id = pm.id
        left join (
            select pr.id as route_id,
                   group_concat(
                           distinct p.product_name
                           order by
                               case
                                   when pr.product_model_ids is null or pr.product_model_ids = '' then pm.id
                                   else find_in_set(pm.id, pr.product_model_ids)
                               end
                           separator ','
                   ) as product_name,
                   max(pm.product_id) as product_id,
                   group_concat(
                           distinct pm.model
                           order by
                               case
                                   when pr.product_model_ids is null or pr.product_model_ids = '' then pm.id
                                   else find_in_set(pm.id, pr.product_model_ids)
                               end
                           separator ','
                   ) as model
            from process_route pr
            left join product_model pm
                   on (find_in_set(pm.id, pr.product_model_ids) > 0
                       or ((pr.product_model_ids is null or pr.product_model_ids = '') and pm.id = pr.product_model_id))
        left join product p on pm.product_id = p.id
            group by pr.id
        ) t on t.route_id = ps.id
        <where>
            <if test="c.model != null and c.model != ''">
                and pm.model like concat('%',#{c.model},'%')
                and t.model like concat('%',#{c.model},'%')
            </if>
        </where>
        order by ps.id asc
src/main/resources/mapper/production/ProductOrderMapper.xml
@@ -63,6 +63,14 @@
            <if test="c.startTime != null and c.endTime != null">
                and po.create_time between #{c.startTime} and #{c.endTime}
            </if>
            <if test="isAdmin == false">
                and exists (
                    select 1
                    from product_work_order pwo_auth
                    where pwo_auth.product_order_id = po.id
                      and find_in_set(#{userId}, pwo_auth.report_user_ids)
                )
            </if>
        </where>
    </select>
    <select id="listProcessRoute" resultType="com.ruoyi.production.pojo.ProcessRoute">
src/main/resources/mapper/production/ProductWorkOrderMapper.xml
@@ -21,11 +21,18 @@
    <select id="pageProductWorkOrder" resultType="com.ruoyi.production.dto.ProductWorkOrderDto">
        SELECT
        pwo.*,
        pwo.report_user_ids,
        pp.NAME as processName,
        pm.model,
        pm.unit,
        p.product_name AS productName,
        po.nps_no AS productOrderNpsNo,
        (
        SELECT GROUP_CONCAT(COALESCE(NULLIF(su.nick_name, ''), su.user_name)
        ORDER BY FIND_IN_SET(su.user_id, pwo.report_user_ids) SEPARATOR ',')
        FROM sys_user su
        WHERE FIND_IN_SET(su.user_id, pwo.report_user_ids)
        ) AS reportUserNames,
        ROUND(pwo.complete_quantity / pwo.plan_quantity * 100, 2) AS completionStatus,
        CASE
        WHEN pwo.work_order_no LIKE 'FG%' THEN '返工返修'
@@ -48,6 +55,15 @@
            <if test="c.productOrderId != null and c.productOrderId != ''">
               and pwo.product_order_id = #{c.productOrderId}
            </if>
            <if test="isAdmin == false">
               and find_in_set(#{userId}, pwo.report_user_ids)
            </if>
    </select>
    <select id="checkUserCanAccess" resultType="java.lang.Integer">
        SELECT COUNT(1)
        FROM product_work_order pwo
        WHERE pwo.id = #{workOrderId}
          AND find_in_set(#{userId}, pwo.report_user_ids)
    </select>
    <select id="getProductWorkOrderFlowCard" resultType="com.ruoyi.production.dto.ProductWorkOrderDto">
        SELECT