已添加17个文件
已重命名1个文件
已修改36个文件
2004 ■■■■ 文件已修改
doc/宁夏-中盛建材.sql 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/BaseParamController.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/BaseParamDto.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/BaseParamMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/BaseParam.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/Customer.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/BaseParamService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ICustomerService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/BaseParamServiceImpl.java 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/task/CustomerTask.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/AliDingConfig.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/util/AliDingUtils.java 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductMaterialController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductMaterialSkuController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductProcessParamController.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductStructureController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductMaterialDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductMaterialSkuDto.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductMaterialSkuImportDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductProcessParamDto.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductProcessParamSortDTO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductMaterialSkuMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductProcessParamMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductStructureMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProcessRoute.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProcessRouteItem.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductBom.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductMaterial.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductMaterialSku.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductProcess.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductProcessParam.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductProcessRoute.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductProcessRouteItem.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductStructure.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductMaterialSkuService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductProcessParamService.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductStructureService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java 212 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductMaterialSkuServiceImpl.java 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductProcessParamServiceImpl.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductStructureServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java 168 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityTestStandardBinding.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-zsjc.yml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/BaseParamMapper.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductBomMapper.xml 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductMaterialMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductMaterialSkuMapper.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductProcessParamMapper.xml 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductStructureMapper.xml 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/ÄþÏÄ-ÖÐÊ¢½¨²Ä.sql
@@ -175,4 +175,62 @@
ALTER TABLE `product-inventory-management-zsjc`.`production_plan`
    DROP COLUMN `data_sync_type`,
    MODIFY COLUMN `data_source_type` tinyint NULL DEFAULT 1 COMMENT '数据来源类型:1=钉钉同步 2=手动新增' AFTER `form_modified_time`;
    MODIFY COLUMN `data_source_type` tinyint NULL DEFAULT 1 COMMENT '数据来源类型:1=钉钉同步 2=手动新增' AFTER `form_modified_time`;
-- åˆ é™¤æ—§è¡¨
DROP TABLE IF EXISTS `base_param`;
DROP TABLE IF EXISTS `product_process_param`;
CREATE TABLE `base_param`
(
    `id`           bigint                                                        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `param_key`    varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '参数唯一标识',
    `param_name`   varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '参数名称',
    `param_type`   tinyint                                                       NOT NULL COMMENT '参数类型(1数字 2文本 3下拉选择 4时间)',
    `param_format` varchar(255)                                                  DEFAULT NULL COMMENT '参数格式',
    `value_mode`   tinyint                                                       DEFAULT '1' COMMENT '值模式(1单值 2区间)',
    `unit`         varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  DEFAULT NULL COMMENT '单位',
    `is_required`  tinyint                                                       DEFAULT '0' COMMENT '是否必填',
    `remark`       varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
    `create_user`  varchar(64)                                                   DEFAULT NULL COMMENT '创建人',
    `create_time`  datetime                                                      DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_user`  varchar(64)                                                   DEFAULT NULL COMMENT '修改人',
    `update_time`  datetime                                                      DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    `tenant_id`    bigint                                                        DEFAULT NULL COMMENT '租户ID',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
  AUTO_INCREMENT = 10
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_0900_ai_ci COMMENT ='基础参数定义表';
CREATE TABLE `product_process_param`
(
    `id`             bigint  NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `process_id`     bigint  NOT NULL COMMENT '所属工序ID (product_process.id)',
    `param_id`       bigint  NOT NULL COMMENT '关联基础参数ID (base_param.id)',
    `standard_value` varchar(200)     DEFAULT NULL COMMENT '在此工序设定的标准值(单值模式)',
    `min_value`      decimal(10, 2)   DEFAULT NULL COMMENT '在此工序设定的标准最小值(区间模式)',
    `max_value`      decimal(10, 2)   DEFAULT NULL COMMENT '在此工序设定的标准最大值(区间模式)',
    `is_required`    tinyint NOT NULL DEFAULT '0' COMMENT '在此工序中是否必填(0-否, 1-是)',
    `sort`           int     NOT NULL DEFAULT '0' COMMENT '排序号',
    `tenant_id`      bigint           DEFAULT NULL COMMENT '租户ID',
    `create_time`    datetime         DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time`    datetime         DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`) USING BTREE,
    KEY `idx_process_id` (`process_id`) USING BTREE,
    KEY `idx_param_id` (`param_id`) USING BTREE
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_0900_ai_ci COMMENT ='工序绑定参数表';
ALTER TABLE `product_process` MODIFY COLUMN `name` varchar(255) COMMENT '工序名称';
ALTER TABLE `product_process` MODIFY COLUMN `no` varchar(255) COMMENT '工序编号';
ALTER TABLE `product_process` ADD COLUMN `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-停用,1-启用' AFTER `no`;
ALTER TABLE `product_process` MODIFY COLUMN `type` bigint COMMENT '类型:0-计时,1-计件';
ALTER TABLE `product_process` MODIFY COLUMN `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间';
ALTER TABLE `product_process` MODIFY COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间';
ALTER TABLE `product-inventory-management-zsjc`.`customer`
    ADD COLUMN `form_instance_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '宜搭表单实例ID' AFTER `customer_type`,
    ADD COLUMN `form_modified_time` datetime(0) NULL DEFAULT NULL COMMENT '宜搭修改时间' AFTER `form_instance_id`;
src/main/java/com/ruoyi/basic/controller/BaseParamController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.ruoyi.basic.controller;
import com.ruoyi.basic.pojo.BaseParam;
import com.ruoyi.basic.service.BaseParamService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <br>
 * åŸºç¡€å‚数定义控制层
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/13 16:42
 */
@RestController
@RequestMapping("/baseParam")
public class BaseParamController extends BaseController {
    @Autowired
    private BaseParamService baseParamService;
    @GetMapping("list")
    @Log(title = "基础参数数据集合", businessType = BusinessType.OTHER)
    @ApiOperation("基础参数数据集合")
    public AjaxResult baseParamList(BaseParam baseParam) {
        List<BaseParam> list = baseParamService.baseParamList(baseParam);
        return AjaxResult.success(list);
    }
    @PostMapping("/add")
    @Log(title = "新增基础参数", businessType = BusinessType.INSERT)
    @ApiOperation("新增基础参数")
    public AjaxResult baseParamAdd(@RequestBody BaseParam baseParam) {
        return toAjax(baseParamService.addBaseParam(baseParam));
    }
    @PutMapping("/edit")
    @Log(title = "修改基础参数", businessType = BusinessType.UPDATE)
    @ApiOperation("修改基础参数")
    public AjaxResult baseParamEdit(@RequestBody BaseParam baseParam) {
        return toAjax(baseParamService.updateBaseParam(baseParam));
    }
    @DeleteMapping("/remove/{ids}")
    @Log(title = "删除基础参数", businessType = BusinessType.DELETE)
    @ApiOperation("删除基础参数")
    public AjaxResult baseParamRemove(@PathVariable Long[] ids) {
        return toAjax(baseParamService.deleteBaseParamByIds(ids));
    }
}
src/main/java/com/ruoyi/basic/dto/BaseParamDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package com.ruoyi.basic.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <br>
 * åŸºç¡€å‚数定义Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/13 17:40
 */
@Data
@Api("基础参数定义Dto")
public class BaseParamDto {
    @ApiModelProperty("主键ID")
    private Long id;
    @ApiModelProperty("参数唯一标识")
    private String paramKey;
    @ApiModelProperty("参数名称")
    private String paramName;
    @ApiModelProperty("参数类型(1数字 2文本)")
    private Integer paramType;
    @ApiModelProperty("参数格式")
    private String paramFormat;
    @ApiModelProperty("值模式(1单值 2区间)")
    private Integer valueMode;
    @ApiModelProperty("单位")
    private String unit;
    @ApiModelProperty("默认值(单值参数)")
    private String defaultValue;
    @ApiModelProperty("默认最小值(区间参数)")
    private BigDecimal defaultMin;
    @ApiModelProperty("默认最大值(区间参数)")
    private BigDecimal defaultMax;
    @ApiModelProperty("是否必填(0否 1是)")
    private Integer isRequired;
    @ApiModelProperty("产品类型表ID名称")
    private String baseProductTypeName;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("创建人")
    private String createUser;
    @ApiModelProperty("创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty("修改人")
    private String updateUser;
    @ApiModelProperty("修改时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
}
src/main/java/com/ruoyi/basic/mapper/BaseParamMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.BaseParam;
/**
 * <br>
 * åŸºç¡€å‚数定义Mapper
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/13 16:39
 */
public interface BaseParamMapper extends BaseMapper<BaseParam> {
}
src/main/java/com/ruoyi/basic/pojo/BaseParam.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <br>
 * åŸºç¡€å‚数定义表
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/13 16:33
 */
@Data
@TableName("base_param")
@Api("基础参数定义")
public class BaseParam {
    @TableId(type = IdType.AUTO)
    @ApiModelProperty("主键ID")
    private Long id;
    @ApiModelProperty("参数唯一标识")
    private String paramKey;
    @ApiModelProperty("参数名称")
    private String paramName;
    @ApiModelProperty("参数类型(1数字 2文本 3下拉选择 4时间)")
    private Integer paramType;
    @ApiModelProperty("参数格式")
    private String paramFormat;
    @ApiModelProperty("值模式(1单值 2区间)")
    private Integer valueMode;
    @ApiModelProperty("单位")
    private String unit;
    @ApiModelProperty("是否必填(0否 1是)")
    private Integer isRequired;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("创建人")
    private String createUser;
    @ApiModelProperty("创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty("修改人")
    private String updateUser;
    @ApiModelProperty("修改时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    @ApiModelProperty("租户ID")
    private Long tenantId;
}
src/main/java/com/ruoyi/basic/pojo/Customer.java
@@ -115,4 +115,11 @@
    @ApiModelProperty(value = "开户行号")
    @Excel(name = "开户行号")
    private String bankCode;
    @ApiModelProperty("宜搭表单实例ID")
    private String formInstanceId;
    @ApiModelProperty("宜搭修改时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime formModifiedTime;
}
src/main/java/com/ruoyi/basic/service/BaseParamService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.pojo.BaseParam;
import java.util.List;
/**
 * <br>
 * åŸºç¡€å‚数定义接口
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/13 16:40
 */
public interface BaseParamService extends IService<BaseParam> {
    List<BaseParam> baseParamList(BaseParam baseParam);
    int addBaseParam(BaseParam baseParam);
    int updateBaseParam(BaseParam baseParam);
    int deleteBaseParamByIds(Long[] ids);
}
src/main/java/com/ruoyi/basic/service/ICustomerService.java
@@ -78,4 +78,8 @@
    List<Customer> selectCustomerLists(Customer customer);
    AjaxResult importData(MultipartFile file);
    //同步客户数据
    void syncCustomerJob();
}
src/main/java/com/ruoyi/basic/service/impl/BaseParamServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,166 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.BaseParamMapper;
import com.ruoyi.basic.pojo.BaseParam;
import com.ruoyi.basic.service.BaseParamService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * <br>
 * åŸºç¡€å‚数定义接口实现类
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/13 16:41
 */
@Slf4j
@Service
@Api("基础参数定义接口实现类")
public class BaseParamServiceImpl extends ServiceImpl<BaseParamMapper, BaseParam> implements BaseParamService {
    @Override
    public List<BaseParam> baseParamList(BaseParam baseParam) {
        LambdaQueryWrapper<BaseParam> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotEmpty(baseParam.getParamName())) {
            queryWrapper.like(BaseParam::getParamName, baseParam.getParamName());
        }
        List<BaseParam> list = list(queryWrapper);
        if (list == null || list.isEmpty()) {
            return new ArrayList<>(0);
        }
        // å¤„理日期格式展示
        list.forEach(item -> {
            if (Integer.valueOf(4).equals(item.getParamType()) && StringUtils.isNotEmpty(item.getParamFormat())) {
                item.setParamFormat(toUpperCasePattern(item.getParamFormat()));
            }
        });
        return list;
    }
    @Override
    public int addBaseParam(BaseParam baseParam) {
        if (baseParam == null) {
            throw new RuntimeException("新增参数不能为空");
        }
        // å‚数校验
        checkBaseParam(baseParam);
        // è‡ªåŠ¨ç”ŸæˆparamKey
        baseParam.setParamKey(generateParamKey());
        baseParam.setCreateUser(SecurityUtils.getUsername());
        baseParam.setCreateTime(LocalDateTime.now());
        // è®¾ç½®é»˜è®¤å€¼
        if (baseParam.getIsRequired() == null) baseParam.setIsRequired(0);
        if (baseParam.getValueMode() == null) baseParam.setValueMode(1);
        return baseMapper.insert(baseParam);
    }
    @Override
    public int updateBaseParam(BaseParam baseParam) {
        if (baseParam == null || baseParam.getId() == null) {
            throw new RuntimeException("修改参数ID不能为空");
        }
        // å‚数校验
        checkBaseParam(baseParam);
        baseParam.setUpdateUser(SecurityUtils.getUsername());
        baseParam.setUpdateTime(LocalDateTime.now());
        return baseMapper.updateById(baseParam);
    }
    /**
     * å‚数定义合法校验
     */
    private void checkBaseParam(BaseParam baseParam) {
        if (StringUtils.isEmpty(baseParam.getParamName())) {
            throw new RuntimeException("参数名称不能为空");
        }
        // ç±»åž‹æ ¡éªŒ (1:数字, 2:文本, 3:下拉选择, 4:日期时间)
        List<Integer> validTypes = Arrays.asList(1, 2, 3, 4);
        if (baseParam.getParamType() == null || !validTypes.contains(baseParam.getParamType())) {
            throw new RuntimeException("非法参数类型");
        }
        // å¦‚果是日期类型,校验日期格式配置
        if (Integer.valueOf(4).equals(baseParam.getParamType())) {
            if (StringUtils.isEmpty(baseParam.getParamFormat())) {
                throw new RuntimeException("日期类型必须配置参数格式(如: yyyy-MM-dd)");
            }
            try {
                String standardPattern = normalizePattern(baseParam.getParamFormat());
                DateTimeFormatter.ofPattern(standardPattern);
                baseParam.setParamFormat(standardPattern);
            } catch (Exception e) {
                throw new RuntimeException("日期格式非法: " + baseParam.getParamFormat());
            }
        }
    }
    /**
     * ç”Ÿæˆå‚数唯一key (PARAM_XXX)
     */
    private String generateParamKey() {
        String prefix = "PARAM_";
        LambdaQueryWrapper<BaseParam> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(BaseParam::getParamKey)
                .likeRight(BaseParam::getParamKey, prefix)
                .orderByDesc(BaseParam::getParamKey)
                .last("limit 1");
        BaseParam last = baseMapper.selectOne(wrapper);
        int nextNum = 1;
        if (last != null && StringUtils.isNotEmpty(last.getParamKey())) {
            try {
                String numStr = last.getParamKey().replace(prefix, "");
                nextNum = Integer.parseInt(numStr) + 1;
            } catch (Exception e) {
                log.error("解析ParamKey异常", e);
            }
        }
        return prefix + String.format("%04d", nextNum);
    }
    /**
     * æ—¥æœŸæ ¼å¼åŒ–
     */
    private String normalizePattern(String pattern) {
        if (StringUtils.isEmpty(pattern)) return "yyyy-MM-dd";
        return pattern.replace("YYYY", "yyyy")
                .replace("DD", "dd")
                .replace("SS", "ss");
    }
    /**
     * è½¬æ¢ä¸ºå…¨å¤§å†™æ˜¾ç¤º
     */
    private String toUpperCasePattern(String pattern) {
        if (StringUtils.isEmpty(pattern)) return "";
        return pattern.replace("yyyy", "YYYY")
                .replace("dd", "DD")
                .replace("ss", "SS");
    }
    @Override
    public int deleteBaseParamByIds(Long[] ids) {
        if (ids == null || ids.length == 0) {
            throw new RuntimeException("删除ID不能为空");
        }
        return baseMapper.deleteBatchIds(Arrays.asList(ids));
    }
}
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -1,6 +1,8 @@
package com.ruoyi.basic.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -20,23 +22,33 @@
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.config.AliDingConfig;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.util.AliDingUtils;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.production.enums.MaterialConfigTypeEnum;
import com.ruoyi.production.pojo.ProductMaterial;
import com.ruoyi.production.pojo.ProductMaterialSku;
import com.ruoyi.productionPlan.enums.DataSourceTypeEnum;
import com.ruoyi.productionPlan.pojo.ProductionPlan;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@@ -58,6 +70,13 @@
    private CustomerFollowUpFileService customerFollowUpFileService;
    private CustomerReturnVisitService customerReturnVisitService;
    private AliDingConfig aliDingConfig;
    /**
     * åŒæ­¥é”ï¼Œé˜²æ­¢æ‰‹åŠ¨å’Œå®šæ—¶ä»»åŠ¡åŒæ—¶æ‰§è¡Œ
     */
    private final ReentrantLock syncLock = new ReentrantLock();
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆ
@@ -256,6 +275,11 @@
    }
    @Override
    public void syncCustomerJob() {
        syncCustomerData(2);
    }
    @Override
    public List<Map<String, Object>> customerList(Customer customer) {
        LambdaQueryWrapper<Customer> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.select(Customer::getId, Customer::getCustomerName, Customer::getTaxpayerIdentificationNumber);
@@ -292,4 +316,114 @@
        }
        return sb.toString();
    }
    /**
     * åŒæ­¥æ•°æ®
     */
    @Transactional(rollbackFor = Exception.class)
    public void syncCustomerData(Integer dataSyncType) {
        if (!syncLock.tryLock()) {
            log.warn("同步正在进行中,本次 {} åŒæ­¥è¯·æ±‚被跳过", dataSyncType == 1 ? "手动" : "定时任务");
            return;
        }
        try {
            JSONArray searchConditions = new JSONArray();
            JSONObject statusCondition = new JSONObject();
            statusCondition.put("key", "processInstanceStatus");
            JSONArray statusValueArray = new JSONArray();
            statusValueArray.add("COMPLETED");
            statusCondition.put("value", statusValueArray);
            statusCondition.put("type", "ARRAY");
            statusCondition.put("operator", "in");
            statusCondition.put("componentName", "SelectField");
            searchConditions.add(statusCondition);
            JSONObject resultCondition = new JSONObject();
            resultCondition.put("key", "processApprovedResult");
            JSONArray resultValueArray = new JSONArray();
            resultValueArray.add("agree");
            resultCondition.put("value", resultValueArray);
            resultCondition.put("type", "ARRAY");
            resultCondition.put("operator", "in");
            resultCondition.put("componentName", "SelectField");
            searchConditions.add(resultCondition);
            String searchFieldJson = searchConditions.toJSONString();
            JSONArray dataArr = AliDingUtils.getFormDataList(aliDingConfig, aliDingConfig.getCustomerCodeFormUuid(), searchFieldJson, this, Customer::getFormModifiedTime);
            if (dataArr.isEmpty()) {
                log.info("没有更多新数据需要同步");
                return;
            }
            // è§£æžå¹¶ä¿å­˜æ•°æ®
            List<Customer> list = parseCustomer(dataArr);
            if (!list.isEmpty()) {
                // å¤„理更新或新增
                int affected = processSaveOrUpdate(list);
                log.info("客户数据同步完成,共同步 {} æ¡æ•°æ®", affected);
            }
        } catch (Exception e) {
            log.error("同步客户信息异常", e);
        } finally {
            // é‡Šæ”¾é”
            syncLock.unlock();
        }
    }
    private List<Customer> parseCustomer(JSONArray dataArr) {
        List<Customer> list = new ArrayList<>();
        for (int i = 0; i < dataArr.size(); i++) {
            JSONObject item = dataArr.getJSONObject(i);
            String formInstanceId = item.getString("formInstanceId");
            JSONObject formData = item.getJSONObject("formData");
            Customer customer = new Customer();
            customer.setCustomerName(formData.getString("textField_l7fwg8uh"));
            customer.setTaxpayerIdentificationNumber(formData.getString("textField_l88df8ae"));
            customer.setCompanyAddress(formData.getString("textField_l7fwg8uj"));
            customer.setCompanyPhone(formData.getString("textField_lb38bng2"));
            customer.setContactPerson(formData.getString("textField_l7fwg8ut"));
            customer.setContactPhone(formData.getString("textField_l7fwg8uu"));
            customer.setMaintainer("管理员账号");
            LocalDateTime dateTime = AliDingUtils.parseUtcTime(item.getString("modifiedTimeGMT"));
            ZoneId zoneId = ZoneId.of("Asia/Shanghai");
            Date date = Date.from(dateTime.atZone(zoneId).toInstant());
            customer.setMaintenanceTime(date);
            customer.setFormInstanceId(formInstanceId);
            customer.setFormModifiedTime(dateTime);
            list.add(customer);
        }
        return list;
    }
    private int processSaveOrUpdate(List<Customer> list) {
        if (list == null || list.isEmpty()) {
            return 0;
        }
        int affected = 0;
        for (Customer customer : list) {
            LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(Customer::getFormInstanceId, customer.getFormInstanceId());
            Customer exist = this.getOne(wrapper);
            if (exist == null) {
                this.save(customer);
                affected++;
                log.info("新增客户信息 {}", customer.getCustomerName());
            } else {
                if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(customer.getFormModifiedTime())) {
                    customer.setId(exist.getId());
                    this.updateById(customer);
                    affected++;
                    log.info("更新客户信息 {}", customer.getCustomerName());
                }
            }
        }
        return affected;
    }
}
src/main/java/com/ruoyi/basic/task/CustomerTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.basic.task;
import com.ruoyi.basic.service.ICustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class CustomerTask {
    @Autowired
    private ICustomerService customerService;
    @Scheduled(cron = "0 0 * * * ?")
    public void syncCustomerDataJob() {
        customerService.syncCustomerJob();
    }
}
src/main/java/com/ruoyi/framework/config/AliDingConfig.java
@@ -53,6 +53,11 @@
    private String materialCodeFormUuid;
    /**
     * å®¢æˆ·ä¿¡æ¯-宜搭表单ID
     */
    private String customerCodeFormUuid;
    /**
     * å®œæ­åº”用密钥
     * ç”¨äºŽè®¿é—®å®œæ­è¡¨å• API
     */
src/main/java/com/ruoyi/framework/util/AliDingUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,173 @@
package com.ruoyi.framework.util;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.framework.config.AliDingConfig;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
/**
 * <br>
 * æ ¹æ®å®œæ­è¡¨å• ID èŽ·å–æ•°æ®
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/14 11:11
 */
@Slf4j
public class AliDingUtils {
    /**
     * æ ¹æ®è¡¨å• ID èŽ·å–å®œæ­æ•°æ®
     *
     * @param aliDingConfig   é’‰é’‰å®œæ­æŽ¥å£é…ç½®
     * @param formUuid        èŽ·å–æ•°æ®çš„è¡¨å•ID
     * @param searchFieldJson éœ€è¦æºå¸¦çš„æŸ¥è¯¢æ¡ä»¶
     * @param service         èŽ·å–æœ€è¿‘è¡¨å•æ›´æ–°çš„æ—¥æœŸService
     * @param timeField       è¡¨å•修改日期
     * @param <T>             å®žä½“ç±»
     * @return å¯¹åº”表单的数据
     */
    public static <T> JSONArray getFormDataList(AliDingConfig aliDingConfig, String formUuid, String searchFieldJson, IService<T> service, SFunction<T, ?> timeField) {
        //  èŽ·å– accessToken
        String accessToken = getAccessToken(aliDingConfig);
        //  èŽ·å–æœ€åŽåŒæ­¥æ—¶é—´
        LocalDateTime lastSyncTime = getLastSyncTime(service, timeField);
        log.info("开始同步数据,本地最后修改时间: {}", lastSyncTime);
        JSONArray allData = new JSONArray();
        int pageNumber = 1;
        int pageSize = 50;
        boolean hasMore = true;
        while (hasMore) {
            JSONObject searchParam = new JSONObject();
            searchParam.put("appType", aliDingConfig.getAppType());
            searchParam.put("systemToken", aliDingConfig.getSystemToken());
            searchParam.put("userId", aliDingConfig.getUserId());
            searchParam.put("formUuid", formUuid);
            searchParam.put("currentPage", pageNumber);
            searchParam.put("pageSize", pageSize);
            if (StringUtils.isNotEmpty(searchFieldJson)) {
                searchParam.put("searchFieldJson", searchFieldJson);
            }
            searchParam.put("orderConfigJson", "{\"gmt_modified\":\"+\"}");
            if (lastSyncTime != null) {
                String startTime = lastSyncTime.plusSeconds(1)
                        .atZone(ZoneId.systemDefault())
                        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                searchParam.put("modifiedFromTimeGMT", startTime);
            }
            String endTime = LocalDateTime.now()
                    .atZone(ZoneId.systemDefault())
                    .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            searchParam.put("modifiedToTimeGMT", endTime);
            String dataRes = HttpUtils.sendPostJson(
                    aliDingConfig.getSearchFormDataUrl(),
                    searchParam.toJSONString(),
                    StandardCharsets.UTF_8.name(),
                    null,
                    accessToken
            );
            if (StringUtils.isEmpty(dataRes)) {
                log.warn("第 {} é¡µæ‹‰å–数据为空", pageNumber);
                break;
            }
            JSONObject resultObj = JSON.parseObject(dataRes);
            JSONArray dataArr = resultObj.getJSONArray("data");
            Integer totalCount = resultObj.getInteger("totalCount");
            if (dataArr == null || dataArr.isEmpty()) {
                log.info("没有更多新数据需要同步");
                break;
            }
            allData.addAll(dataArr);
            hasMore = (pageNumber * pageSize) < totalCount;
            pageNumber++;
            log.info("正在提取宜搭分页数据,第 {} é¡µå·²å¤„理,当前提取数: {}/{}", pageNumber - 1, allData.size(), totalCount);
        }
        return allData;
    }
    /**
     * èŽ·å–é’‰é’‰ AccessToken
     */
    private static String getAccessToken(AliDingConfig aliDingConfig) {
        String params = "appkey=" + aliDingConfig.getAppKey() + "&appsecret=" + aliDingConfig.getAppSecret();
        String tokenRes = HttpUtils.sendGet(aliDingConfig.getAccessTokenUrl(), params);
        JSONObject tokenObj = JSON.parseObject(tokenRes);
        String accessToken = tokenObj.getString("access_token");
        if (StringUtils.isEmpty(accessToken)) {
            log.error("获取钉钉AccessToken失败: {}", tokenRes);
        }
        return accessToken;
    }
    /**
     * æ—¥æœŸæ ¼å¼åŒ–
     *
     * @param utcString æ—¥æœŸ
     * @return LocalDateTime
     */
    public static LocalDateTime parseUtcTime(String utcString) {
        if (StringUtils.isEmpty(utcString)) {
            return null;
        }
        try {
            OffsetDateTime odt = OffsetDateTime.parse(utcString);
            return odt.toLocalDateTime();
        } catch (DateTimeParseException ex) {
            log.warn("解析时间 {} å¤±è´¥: {}", utcString, ex.getMessage());
            return null;
        }
    }
    private static <T> LocalDateTime getLastSyncTime(IService<T> service, SFunction<T, ?> timeField) {
        LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(timeField).last("LIMIT 1");
        T lastRecord = service.getOne(queryWrapper);
        if (lastRecord == null) {
            return null;
        }
        try {
            Method writeReplace = timeField.getClass().getDeclaredMethod("writeReplace");
            writeReplace.setAccessible(true);
            SerializedLambda lambda = (SerializedLambda) writeReplace.invoke(timeField);
            // èŽ·å–æ–¹æ³•å
            String methodName = lambda.getImplMethodName();
            // è½¬å­—段名
            String fieldName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
            Field field = lastRecord.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            return (LocalDateTime) field.get(lastRecord);
        } catch (Exception e) {
            throw new RuntimeException("获取最后同步时间失败", e);
        }
    }
}
src/main/java/com/ruoyi/production/controller/ProductMaterialController.java
@@ -1,5 +1,8 @@
package com.ruoyi.production.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
src/main/java/com/ruoyi/production/controller/ProductMaterialSkuController.java
@@ -7,7 +7,7 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.production.dto.ProductMaterialSkuDto;
import com.ruoyi.production.pojo.ProductMaterialSku;
import com.ruoyi.production.pojo.ProductMaterialSkuImportDto;
import com.ruoyi.production.dto.ProductMaterialSkuImportDto;
import com.ruoyi.production.service.ProductMaterialSkuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -38,8 +38,8 @@
    @GetMapping("/list")
    @ApiOperation("物料规格数据集合")
    @Log(title = "物料规格数据集合", businessType = BusinessType.OTHER)
    public AjaxResult productMaterialSkuList(Page<ProductMaterialSku> page, ProductMaterialSkuDto dto) {
        Page<ProductMaterialSkuDto> list = productMaterialSkuService.productMaterialSkuList(page, dto);
    public AjaxResult productMaterialSkuList(Page<ProductMaterialSkuDto> page, ProductMaterialSkuDto dto, @RequestParam(value = "type", required = false) Integer type) {
        Page<ProductMaterialSkuDto> list = productMaterialSkuService.productMaterialSkuList(page, dto, type);
        return AjaxResult.success(list);
    }
src/main/java/com/ruoyi/production/controller/ProductProcessParamController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package com.ruoyi.production.controller;
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.production.dto.ProductProcessParamSortDTO;
import com.ruoyi.production.pojo.ProductProcessParam;
import com.ruoyi.production.service.ProductProcessParamService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <br>
 * å·¥åºç»‘定参数控制层
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/14 13:33
 */
@RestController
@RequestMapping("/productProcessParam")
@Api(tags = "工序参数绑定管理")
public class ProductProcessParamController {
    @Autowired
    private ProductProcessParamService productProcessParamService;
    @GetMapping("/list/{productProcessId}")
    @Log(title = "根据工序ID查询对应的参数", businessType = BusinessType.OTHER)
    @ApiOperation("根据工序ID查询对应的参数")
    public AjaxResult listByProcessId(@PathVariable("productProcessId") Long productProcessId) {
        return AjaxResult.success(productProcessParamService.listByProcessId(productProcessId));
    }
    @PostMapping("/add")
    @Log(title = "新增工序绑定参数", businessType = BusinessType.INSERT)
    @ApiOperation("新增工序绑定参数")
    public AjaxResult add(@RequestBody ProductProcessParam productProcessParam) {
        productProcessParamService.add(productProcessParam);
        return AjaxResult.success();
    }
    @PutMapping("/edit")
    @Log(title = "修改工序绑定参数", businessType = BusinessType.UPDATE)
    @ApiOperation("修改工序绑定参数")
    public AjaxResult edit(@RequestBody ProductProcessParam productProcessParam) {
        productProcessParamService.edit(productProcessParam);
        return AjaxResult.success();
    }
    @DeleteMapping("/{ids}")
    @Log(title = "删除工序绑定参数", businessType = BusinessType.DELETE)
    @ApiOperation("删除工序绑定参数")
    public AjaxResult remove(@PathVariable List<Long> ids) {
        productProcessParamService.deleteByIds(ids);
        return AjaxResult.success();
    }
    @PutMapping("/sort")
    @Log(title = "更新工序参数排序", businessType = BusinessType.UPDATE)
    @ApiOperation("更新工序参数排序")
    public AjaxResult updateSort(@RequestBody ProductProcessParamSortDTO dto) {
        productProcessParamService.updateSort(dto);
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/production/controller/ProductStructureController.java
@@ -34,7 +34,7 @@
    @ApiOperation("BOM查看子集详情")
    @GetMapping("/listBybomId/{bomId}")
    public R listBybomId( @PathVariable("bomId") Integer bomId){
        return R.ok(productStructureService.listBybomId(bomId));
    public R listByBomId( @PathVariable("bomId") Integer bomId){
        return R.ok(productStructureService.listByBomId(bomId));
    }
}
src/main/java/com/ruoyi/production/dto/ProductMaterialDto.java
@@ -33,7 +33,7 @@
    private Integer inventoryCategoryId;
    @ApiModelProperty("物料名称")
    private String materialName;
    private String productName;
}
src/main/java/com/ruoyi/production/dto/ProductMaterialSkuDto.java
@@ -18,22 +18,22 @@
public class ProductMaterialSkuDto {
    @ApiModelProperty("物料ID")
    private Long materialId;
    private Long productId;
    @ApiModelProperty("物料名称")
    private String materialName;
    private String productName;
    @ApiModelProperty("物料编码")
    private String materialCode;
    @ApiModelProperty("单位")
    private String baseUnit;
    private String unit;
    @ApiModelProperty("规格ID")
    private Long skuId;
    private Long id;
    @ApiModelProperty("规格型号")
    private String specification;
    private String model;
    @ApiModelProperty("供应方式")
    private String supplyType;
src/main/java/com/ruoyi/production/dto/ProductMaterialSkuImportDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/production/pojo/ProductMaterialSkuImportDto.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.production.pojo;
package com.ruoyi.production.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -25,7 +25,7 @@
    @ApiModelProperty("规格型号")
    @Excel(name = "规格型号")
    private String specification;
    private String model;
    @ApiModelProperty("供应方式(自制,外购)")
    @Excel(name = "供应方式")
src/main/java/com/ruoyi/production/dto/ProductProcessParamDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package com.ruoyi.production.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
 * <br>
 * å·¥åºç»‘定参数Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/14 15:33
 */
@Data
@ApiModel(value = "ProductProcessParamDto对象", description = "工序绑定参数Dto")
public class ProductProcessParamDto {
    @ApiModelProperty("主键ID")
    private Long id;
    @ApiModelProperty("所属工序ID (product_process.id)")
    private Long processId;
    @ApiModelProperty("关联基础参数ID (base_param.id)")
    private Long paramId;
    @ApiModelProperty("在此工序设定的标准值(单值模式使用)")
    private String standardValue;
    @ApiModelProperty("在此工序设定的标准最小值(区间模式使用)")
    private BigDecimal minValue;
    @ApiModelProperty("在此工序设定的标准最大值(区间模式使用)")
    private BigDecimal maxValue;
    @ApiModelProperty("在此工序中是否必填(0-否, 1-是)")
    private Integer isRequired;
    @ApiModelProperty("排序号")
    private Integer sort;
    @ApiModelProperty("参数名称")
    private String paramName;
    @ApiModelProperty("参数类型(1数字 2文本 3下拉选择 4时间)")
    private Integer paramType;
    @ApiModelProperty("参数格式")
    private String paramFormat;
    @ApiModelProperty("值模式(1单值 2区间)")
    private Integer valueMode;
    @ApiModelProperty("单位")
    private String unit;
    @ApiModelProperty("创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    @ApiModelProperty("更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
}
src/main/java/com/ruoyi/production/dto/ProductProcessParamSortDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.production.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
 * <br>
 * å·¥åºå‚数排序 DTO
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/14 14:25
 */
@Data
@ApiModel("工序参数排序入参")
public class ProductProcessParamSortDTO {
    @ApiModelProperty("排序项列表")
    private List<SortItem> items;
    @Data
    @ApiModel("排序项")
    public static class SortItem {
        @ApiModelProperty("记录ID")
        private Long id;
        @ApiModelProperty("排序号")
        private Integer sort;
    }
}
src/main/java/com/ruoyi/production/mapper/ProductMaterialSkuMapper.java
@@ -1,7 +1,10 @@
package com.ruoyi.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.production.dto.ProductMaterialSkuDto;
import com.ruoyi.production.pojo.ProductMaterialSku;
import org.apache.ibatis.annotations.Param;
/**
 * <br>
@@ -13,4 +16,10 @@
 * @since 2026/03/12 10:04
 */
public interface ProductMaterialSkuMapper extends BaseMapper<ProductMaterialSku> {
}
    /**
     * è¿žè¡¨æŸ¥è¯¢ç‰©æ–™å’Œè§„æ ¼
     */
    Page<ProductMaterialSkuDto> selectSkuWithMaterialPage(@Param("page") Page<ProductMaterialSkuDto> page, @Param("dto") ProductMaterialSkuDto dto, @Param("type") Integer type);
}
src/main/java/com/ruoyi/production/mapper/ProductProcessParamMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.production.dto.ProductProcessParamDto;
import com.ruoyi.production.pojo.ProductProcessParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <br>
 * å·¥åºç»‘定参数 Mapper æŽ¥å£
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/14 13:16
 */
public interface ProductProcessParamMapper extends BaseMapper<ProductProcessParam> {
    List<ProductProcessParamDto> selectDtoListByProcessId(@Param("processId") Long processId);
}
src/main/java/com/ruoyi/production/mapper/ProductStructureMapper.java
@@ -13,7 +13,7 @@
@Mapper
public interface ProductStructureMapper  extends BaseMapper<ProductStructure> {
    List<ProductStructureDto> listBybomId(@Param("bomId") Integer bomId);
    List<ProductStructureDto> listByBomId(@Param("bomId") Integer bomId);
    List<ProductStructureDto> listBybomAndProcess(@Param("bomId") Integer bomId, @Param("processId") Long processId);
    List<ProductStructureDto> listByBomAndProcess(@Param("bomId") Integer bomId, @Param("processId") Long processId);
}
src/main/java/com/ruoyi/production/pojo/ProcessRoute.java
@@ -18,7 +18,7 @@
    private Long id;
    @ApiModelProperty(value = "产品ID")
    //product_model
    //宁夏-中盛建材使用新的产品表关联product_material_sku
    private Long productModelId;
    @ApiModelProperty(value = "描述")
src/main/java/com/ruoyi/production/pojo/ProcessRouteItem.java
@@ -22,6 +22,7 @@
    private Long processId;
    @ApiModelProperty(value ="产品id")
    //宁夏-中盛建材使用新的产品表关联product_material_sku
    private Long productModelId;
    @ApiModelProperty(value = "租户ID")
src/main/java/com/ruoyi/production/pojo/ProductBom.java
@@ -38,6 +38,7 @@
    private String bomNo;
    @ApiModelProperty("产品规格id")
    //宁夏-中盛建材使用新的产品表关联product_material_sku
    private Long productModelId;
    @ApiModelProperty("备注")
src/main/java/com/ruoyi/production/pojo/ProductMaterial.java
@@ -38,10 +38,10 @@
    private Integer inventoryCategoryId;
    @ApiModelProperty("物料名称")
    private String materialName;
    private String productName;
    @ApiModelProperty("基本单位")
    private String baseUnit;
    private String unit;
    @ApiModelProperty("备注")
    private String remark;
src/main/java/com/ruoyi/production/pojo/ProductMaterialSku.java
@@ -29,7 +29,7 @@
    private Long id;
    @ApiModelProperty("物料ID")
    private Long materialId;
    private Long productId;
    @ApiModelProperty("标识编码")
    private String identifierCode;
@@ -38,7 +38,7 @@
    private String materialCode;
    @ApiModelProperty("规格型号")
    private String specification;
    private String model;
    @ApiModelProperty("供应方式(自制,外购)")
    private String supplyType;
src/main/java/com/ruoyi/production/pojo/ProductProcess.java
@@ -42,6 +42,11 @@
    @Excel(name = "备注")
    private String remark;
    /**
     * çŠ¶æ€ï¼š0-停用,1-启用
     */
    private Boolean status;
    /**
     * å·¥èµ„定额
@@ -75,7 +80,7 @@
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @ApiModelProperty(value ="是否质检")
    @ApiModelProperty(value = "是否质检")
    private Boolean isQuality;
src/main/java/com/ruoyi/production/pojo/ProductProcessParam.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package com.ruoyi.production.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
 * å·¥åºç»‘定参数实体类
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/14 13:14
 */
@Data
@TableName("product_process_param")
@ApiModel(value = "ProductProcessParam对象", description = "工序绑定参数表")
public class ProductProcessParam implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("所属工序ID (product_process.id)")
    private Long processId;
    @ApiModelProperty("关联基础参数ID (base_param.id)")
    private Long paramId;
    @ApiModelProperty("在此工序设定的标准值(单值模式使用)")
    private String standardValue;
    @ApiModelProperty("在此工序设定的标准最小值(区间模式使用)")
    private BigDecimal minValue;
    @ApiModelProperty("在此工序设定的标准最大值(区间模式使用)")
    private BigDecimal maxValue;
    @ApiModelProperty("在此工序中是否必填(0-否, 1-是)")
    private Integer isRequired;
    @ApiModelProperty("排序号")
    private Integer sort;
    @ApiModelProperty("租户ID")
    private Long tenantId;
    @ApiModelProperty("创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    @ApiModelProperty("更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
}
src/main/java/com/ruoyi/production/pojo/ProductProcessRoute.java
@@ -34,7 +34,7 @@
    private Long id;
    @ApiModelProperty("产品id")
    //product_model
    //宁夏-中盛建材使用新的产品表关联product_material_sku
    private Long productModelId;
    @ApiModelProperty("描述")
src/main/java/com/ruoyi/production/pojo/ProductProcessRouteItem.java
@@ -23,6 +23,7 @@
    private Long processId;
    @ApiModelProperty(value ="产品id")
    //宁夏-中盛建材使用新的产品表关联product_material_sku
    private Long productModelId;
    @ApiModelProperty(value = "租户ID")
src/main/java/com/ruoyi/production/pojo/ProductStructure.java
@@ -19,6 +19,7 @@
    /**
     * äº§å“åç§°
     */
    //宁夏-中盛建材使用新的产品表关联product_material_sku
    private Long productModelId;
    /**
src/main/java/com/ruoyi/production/service/ProductMaterialSkuService.java
@@ -18,7 +18,7 @@
 * @since 2026/03/12 10:04
 */
public interface ProductMaterialSkuService extends IService<ProductMaterialSku> {
    Page<ProductMaterialSkuDto> productMaterialSkuList(Page<ProductMaterialSku> page, ProductMaterialSkuDto dto);
    Page<ProductMaterialSkuDto> productMaterialSkuList(Page<ProductMaterialSkuDto> page, ProductMaterialSkuDto dto, Integer type);
    void addProductMaterialSku(ProductMaterialSku productMaterialSku);
src/main/java/com/ruoyi/production/service/ProductProcessParamService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.ruoyi.production.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.production.dto.ProductProcessParamDto;
import com.ruoyi.production.dto.ProductProcessParamSortDTO;
import com.ruoyi.production.pojo.ProductProcessParam;
import java.util.List;
/**
 * <br>
 * å·¥åºç»‘定参数接口
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/14 13:17
 */
public interface ProductProcessParamService extends IService<ProductProcessParam> {
    List<ProductProcessParamDto> listByProcessId(Long processId);
    void add(ProductProcessParam productProcessParam);
    void edit(ProductProcessParam productProcessParam);
    void deleteByIds(List<Long> ids);
    void updateSort(ProductProcessParamSortDTO dto);
}
src/main/java/com/ruoyi/production/service/ProductStructureService.java
@@ -12,6 +12,6 @@
    Boolean addProductStructureDto(ProductStructureDto productStructureDto);
    List<ProductStructureDto> listBybomId(Integer bomId);
    List<ProductStructureDto> listByBomId(Integer bomId);
}
src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java
@@ -4,10 +4,8 @@
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.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.production.pojo.ProductMaterial;
import com.ruoyi.production.pojo.ProductMaterialSku;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -19,9 +17,7 @@
import com.ruoyi.production.pojo.ProductBom;
import com.ruoyi.production.pojo.ProductProcess;
import com.ruoyi.production.pojo.ProductStructure;
import com.ruoyi.production.service.ProductBomService;
import com.ruoyi.production.service.ProductProcessService;
import com.ruoyi.production.service.ProductStructureService;
import com.ruoyi.production.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -44,20 +40,20 @@
public class ProductBomServiceImpl extends ServiceImpl<ProductBomMapper, ProductBom> implements ProductBomService {
    @Autowired
    private IProductService productService;
    @Autowired
    private ProductBomMapper productBomMapper;
    @Autowired
    private IProductModelService productModelService;
    @Autowired
    private ProductStructureService productStructureService;
    @Autowired
    private ProductProcessService productProcessService;
    @Autowired
    private ProductMaterialService productMaterialService;
    @Autowired
    private ProductMaterialSkuService productMaterialSkuService;
    @Override
    public IPage<ProductBomDto> listPage(Page page, ProductBomDto productBomDto) {
@@ -78,15 +74,16 @@
                throw new ServiceException("请选择产品模型");
            }
            ProductModel productModel = productModelService.getById(productBom.getProductModelId());
            ProductMaterialSku productModel = productMaterialSkuService.getById(productBom.getProductModelId());
            if (productModel == null) {
                throw new ServiceException("选择的产品模型不存在");
            }
            ProductMaterial productMaterial = productMaterialService.getById(productModel.getProductId());
            //  æ·»åŠ åˆå§‹çš„äº§å“ç»“æž„
            ProductStructure productStructure = new ProductStructure();
            productStructure.setProductModelId(productBom.getProductModelId());
            productStructure.setUnit(productModel.getUnit());
            productStructure.setUnit(productMaterial != null ? productMaterial.getUnit() : null);
            productStructure.setUnitQuantity(BigDecimal.valueOf(1));
            productStructure.setBomId(productBom.getId());
@@ -123,7 +120,7 @@
        //  åˆ›å»º BOM æ•°æ®
        BomImportDto first = list.get(0);
        ProductModel rootModel = findModel(first.getParentName(), first.getParentSpec());
        ProductMaterialSku rootModel = findModel(first.getParentName(), first.getParentSpec());
        ProductBom bom = new ProductBom();
        bom.setProductModelId(rootModel.getId());
        bom.setVersion("1.0");
@@ -146,7 +143,8 @@
                rootNode.setParentId(null); // é¡¶å±‚没有父节点
                rootNode.setProductModelId(rootModel.getId());
                rootNode.setUnitQuantity(BigDecimal.ONE);
                rootNode.setUnit(rootModel.getUnit());
                ProductMaterial rootMaterial = productMaterialService.getById(rootModel.getProductId());
                rootNode.setUnit(rootMaterial != null ? rootMaterial.getUnit() : null);
                productStructureService.save(rootNode);
                treePathMap.put(parentKey, rootNode.getId());
@@ -162,7 +160,7 @@
            }
            //  èŽ·å–å­é¡¹æ¨¡åž‹ä¿¡æ¯
            ProductModel childModel = findModel(dto.getChildName(), dto.getChildSpec());
            ProductMaterialSku childModel = findModel(dto.getChildName(), dto.getChildSpec());
            //  æ’入结构表
            ProductStructure node = new ProductStructure();
@@ -170,7 +168,8 @@
            node.setParentId(parentStructureId); // çˆ¶èŠ‚ç‚¹ID
            node.setProductModelId(childModel.getId());
            node.setUnitQuantity(dto.getUnitQty());
            node.setUnit(childModel.getUnit());
            ProductMaterial childMaterial = productMaterialService.getById(childModel.getProductId());
            node.setUnit(childMaterial != null ? childMaterial.getUnit() : null);
            if (processMap.containsKey(dto.getProcess())) {
                node.setProcessId(processMap.get(dto.getProcess()));
            }
@@ -191,7 +190,7 @@
            return;
        }
        List<ProductStructureDto> treeData = productStructureService.listBybomId(bomId);
        List<ProductStructureDto> treeData = productStructureService.listByBomId(bomId);
        if (treeData == null || treeData.isEmpty()) {
            return;
        }
@@ -262,14 +261,14 @@
        }
    }
    private ProductModel findModel(String name, String spec) {
        Product product = productService.getOne(new LambdaQueryWrapper<Product>()
                .eq(Product::getProductName, name).last("limit 1"));
    private ProductMaterialSku findModel(String name, String spec) {
        ProductMaterial product = productMaterialService.getOne(new LambdaQueryWrapper<ProductMaterial>()
                .eq(ProductMaterial::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"));
        ProductMaterialSku model = productMaterialSkuService.getOne(new LambdaQueryWrapper<ProductMaterialSku>()
                .eq(ProductMaterialSku::getProductId, product.getId())
                .eq(ProductMaterialSku::getModel, spec).last("limit 1"));
        if (model == null) throw new ServiceException("规格未维护:" + name + "[" + spec + "]");
        return model;
    }
src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java
@@ -1,6 +1,5 @@
package com.ruoyi.production.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -8,8 +7,8 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.framework.config.AliDingConfig;
import com.ruoyi.framework.util.AliDingUtils;
import com.ruoyi.production.dto.ProductMaterialDto;
import com.ruoyi.production.dto.ProductMaterialGroupDto;
import com.ruoyi.production.enums.MaterialConfigTypeEnum;
@@ -25,13 +24,11 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@@ -85,126 +82,52 @@
        }
        try {
            // èŽ·å– AccessToken
            String accessToken = getAccessToken();
            if (StringUtils.isEmpty(accessToken)) {
            JSONArray searchConditions = new JSONArray();
            JSONObject statusCondition = new JSONObject();
            statusCondition.put("key", "processInstanceStatus");
            JSONArray statusValueArray = new JSONArray();
            statusValueArray.add("COMPLETED");
            statusCondition.put("value", statusValueArray);
            statusCondition.put("type", "ARRAY");
            statusCondition.put("operator", "in");
            statusCondition.put("componentName", "SelectField");
            searchConditions.add(statusCondition);
            JSONObject resultCondition = new JSONObject();
            resultCondition.put("key", "processApprovedResult");
            JSONArray resultValueArray = new JSONArray();
            resultValueArray.add("agree");
            resultCondition.put("value", resultValueArray);
            resultCondition.put("type", "ARRAY");
            resultCondition.put("operator", "in");
            resultCondition.put("componentName", "SelectField");
            searchConditions.add(resultCondition);
            String searchFieldJson = searchConditions.toJSONString();
            JSONArray dataArr = AliDingUtils.getFormDataList(aliDingConfig, aliDingConfig.getMaterialCodeFormUuid(), searchFieldJson, productMaterialSkuService, ProductMaterialSku::getFormModifiedTime);
            if (dataArr.isEmpty()) {
                log.info("没有更多新数据需要同步");
                return;
            }
            // èŽ·å–æœ¬åœ°æœ€åŽåŒæ­¥æ—¶é—´
            LocalDateTime lastSyncTime = getLastSyncTime();
            log.info("开始物料编码增量同步,本地最后修改时间: {}", lastSyncTime);
            int pageNumber = 1;
            int pageSize = 50;
            boolean hasMore = true;
            int totalSynced = 0;
            while (hasMore) {
                // æŸ¥è¯¢å‚æ•°
                JSONObject searchParam = buildSearchParam(lastSyncTime, pageNumber, pageSize);
                // è°ƒç”¨å®œæ­æŽ¥å£æ‹‰å–数据
                String dataRes = HttpUtils.sendPostJson(aliDingConfig.getSearchFormDataUrl(), searchParam.toJSONString(), StandardCharsets.UTF_8.name(), null, accessToken);
                if (StringUtils.isEmpty(dataRes)) {
                    log.warn("第 {} é¡µæ‹‰å–数据为空", pageNumber);
                    break;
                }
                JSONObject resultObj = JSON.parseObject(dataRes);
                JSONArray dataArr = resultObj.getJSONArray("data");
                Integer totalCount = resultObj.getInteger("totalCount");
                if (dataArr == null || dataArr.isEmpty()) {
                    log.info("没有更多新数据需要同步");
                    break;
                }
                // è§£æžå¹¶ä¿å­˜æ•°æ®
                List<ProductMaterialSku> list = parseProductMaterials(dataArr, totalCount);
                if (!list.isEmpty()) {
                    // å¤„理更新或新增
                    int affected = processSaveOrUpdate(list);
                    totalSynced += affected;
                }
                // åˆ¤æ–­æ˜¯å¦è¿˜æœ‰ä¸‹ä¸€é¡µ
                hasMore = (pageNumber * pageSize) < totalCount;
                pageNumber++;
                log.info("正在同步第 {} é¡µï¼Œå½“前已同步 {}/{}", pageNumber - 1, totalSynced, totalCount);
            // è§£æžå¹¶ä¿å­˜æ•°æ®
            List<ProductMaterialSku> list = parseProductMaterials(dataArr, dataArr.size());
            if (!list.isEmpty()) {
                // å¤„理更新或新增
                int affected = processSaveOrUpdate(list);
                log.info("物料数据同步完成,共同步 {} æ¡æ•°æ®", affected);
            }
            log.info("物料数据同步完成,共同步 {} æ¡æ•°æ®", totalSynced);
        } catch (Exception e) {
            log.error("同步物料编码异常", e);
        } finally {
            // é‡Šæ”¾é”
            syncLock.unlock();
        }
    }
    private String getAccessToken() {
        String params = "appkey=" + aliDingConfig.getAppKey() + "&appsecret=" + aliDingConfig.getAppSecret();
        String tokenRes = HttpUtils.sendGet(aliDingConfig.getAccessTokenUrl(), params);
        JSONObject tokenObj = JSON.parseObject(tokenRes);
        String accessToken = tokenObj.getString("access_token");
        if (StringUtils.isEmpty(accessToken)) {
            log.error("获取钉钉AccessToken失败: {}", tokenRes);
        }
        return accessToken;
    }
    private LocalDateTime getLastSyncTime() {
        LambdaQueryWrapper<ProductMaterialSku> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(ProductMaterialSku::getFormModifiedTime).last("LIMIT 1");
        ProductMaterialSku lastRecord = productMaterialSkuService.getOne(queryWrapper);
        return lastRecord != null ? lastRecord.getFormModifiedTime() : null;
    }
    private JSONObject buildSearchParam(LocalDateTime lastSyncTime, int pageNumber, int pageSize) {
        JSONObject searchParam = new JSONObject();
        searchParam.put("appType", aliDingConfig.getAppType());
        searchParam.put("systemToken", aliDingConfig.getSystemToken());
        searchParam.put("userId", aliDingConfig.getUserId());
        searchParam.put("formUuid", aliDingConfig.getMaterialCodeFormUuid());
        searchParam.put("currentPage", pageNumber);
        searchParam.put("pageSize", pageSize);
        JSONArray searchConditions = new JSONArray();
        JSONObject statusCondition = new JSONObject();
        statusCondition.put("key", "processInstanceStatus");
        JSONArray statusValueArray = new JSONArray();
        statusValueArray.add("COMPLETED");
        statusCondition.put("value", statusValueArray);
        statusCondition.put("type", "ARRAY");
        statusCondition.put("operator", "in");
        statusCondition.put("componentName", "SelectField");
        searchConditions.add(statusCondition);
        JSONObject resultCondition = new JSONObject();
        resultCondition.put("key", "processApprovedResult");
        JSONArray resultValueArray = new JSONArray();
        resultValueArray.add("agree");
        resultCondition.put("value", resultValueArray);
        resultCondition.put("type", "ARRAY");
        resultCondition.put("operator", "in");
        resultCondition.put("componentName", "SelectField");
        searchConditions.add(resultCondition);
        searchParam.put("searchFieldJson", searchConditions.toJSONString());
        searchParam.put("orderConfigJson", "{\"gmt_modified\":\"+\"}");
        if (lastSyncTime != null) {
            String startTime = lastSyncTime.plusSeconds(1).atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            searchParam.put("modifiedFromTimeGMT", startTime);
        }
        String endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        searchParam.put("modifiedToTimeGMT", endTime);
        return searchParam;
    }
    private List<ProductMaterialSku> parseProductMaterials(JSONArray dataArr, Integer totalCount) {
@@ -220,8 +143,8 @@
            JSONObject formData = item.getJSONObject("formData");
            //  å¤„理物料主表数据
            ProductMaterial material = new ProductMaterial();
            material.setMaterialName(formData.getString("textField_l92f36f5"));
            material.setBaseUnit(formData.getString("textField_la147lnw"));
            material.setProductName(formData.getString("textField_l92f36f5"));
            material.setUnit(formData.getString("textField_la147lnw"));
            material.setRemark(formData.getString("textareaField_l92f36f9"));
            String materialType = formData.getString("selectField_l92f36fb");
@@ -233,15 +156,15 @@
            //  å¤„理物料规格数据
            ProductMaterialSku sku = new ProductMaterialSku();
            sku.setMaterialId(materialId);
            sku.setProductId(materialId);
            sku.setFormInstanceId(formInstanceId);
            sku.setIdentifierCode(formData.getString("textField_l92h77ju"));
            sku.setMaterialCode(formData.getString("textField_l92f36f2"));
            sku.setSpecification(formData.getString("textField_l92f36f6"));
            sku.setModel(formData.getString("textField_l92f36f6"));
            sku.setSupplyType(formData.getString("selectField_la14k51j"));
            sku.setOriginatorName(originatorName);
            sku.setOriginatorOrg("宁夏中创绿能实业集团有限公司");
            sku.setFormModifiedTime(parseUtcTime(item.getString("modifiedTimeGMT")));
            sku.setFormModifiedTime(AliDingUtils.parseUtcTime(item.getString("modifiedTimeGMT")));
            sku.setCreateTime(now);
            sku.setUpdateTime(now);
@@ -252,7 +175,7 @@
    private Long getOrCreateMaterial(ProductMaterial material) {
        LambdaQueryWrapper<ProductMaterial> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductMaterial::getMaterialName, material.getMaterialName());
        queryWrapper.eq(ProductMaterial::getProductName, material.getProductName());
        ProductMaterial exist = this.getOne(queryWrapper);
        if (exist == null) {
@@ -271,8 +194,8 @@
                exist.setInventoryCategoryId(material.getInventoryCategoryId());
                needUpdate = true;
            }
            if (StringUtils.isNotEmpty(material.getBaseUnit()) && !material.getBaseUnit().equals(exist.getBaseUnit())) {
                exist.setBaseUnit(material.getBaseUnit());
            if (StringUtils.isNotEmpty(material.getUnit()) && !material.getUnit().equals(exist.getUnit())) {
                exist.setUnit(material.getUnit());
                needUpdate = true;
            }
            if (needUpdate) {
@@ -306,8 +229,8 @@
        for (ProductMaterialSku sku : list) {
            LambdaQueryWrapper<ProductMaterialSku> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(ProductMaterialSku::getMaterialId, sku.getMaterialId())
                    .eq(ProductMaterialSku::getSpecification, sku.getSpecification());
            wrapper.eq(ProductMaterialSku::getProductId, sku.getProductId())
                    .eq(ProductMaterialSku::getModel, sku.getModel());
            if (StringUtils.isNotEmpty(sku.getMaterialCode())) {
                wrapper.eq(ProductMaterialSku::getMaterialCode, sku.getMaterialCode());
@@ -319,7 +242,7 @@
            if (exist == null) {
                productMaterialSkuService.save(sku);
                affected++;
                log.info("新增物料规格 {}", sku.getSpecification());
                log.info("新增物料规格 {}", sku.getModel());
            } else {
                if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(sku.getFormModifiedTime())) {
                    sku.setId(exist.getId());
@@ -327,24 +250,11 @@
                    productMaterialSkuService.updateById(sku);
                    affected++;
                    log.info("更新物料规格 {}", sku.getSpecification());
                    log.info("更新物料规格 {}", sku.getModel());
                }
            }
        }
        return affected;
    }
    private LocalDateTime parseUtcTime(String utcString) {
        if (StringUtils.isEmpty(utcString)) {
            return null;
        }
        try {
            OffsetDateTime odt = OffsetDateTime.parse(utcString);
            return odt.toLocalDateTime();
        } catch (DateTimeParseException ex) {
            log.warn("解析时间 {} å¤±è´¥: {}", utcString, ex.getMessage());
            return null;
        }
    }
    @Override
@@ -363,7 +273,7 @@
                            ProductMaterial::getId,
                            ProductMaterial::getMaterialTypeId,
                            ProductMaterial::getInventoryCategoryId,
                            ProductMaterial::getMaterialName
                            ProductMaterial::getProductName
                    )
            );
            materialMap = materialList.stream()
@@ -397,10 +307,10 @@
                ProductMaterial::getId,
                ProductMaterial::getMaterialTypeId,
                ProductMaterial::getInventoryCategoryId,
                ProductMaterial::getMaterialName
                ProductMaterial::getProductName
        );
        if (StringUtils.isNotEmpty(materialName)) {
            wrapper.like(ProductMaterial::getMaterialName, materialName);
            wrapper.like(ProductMaterial::getProductName, materialName);
        }
        if (materialTypeId != null) {
            wrapper.eq(ProductMaterial::getMaterialTypeId, materialTypeId);
@@ -435,7 +345,7 @@
    private ProductMaterialDto convert(ProductMaterial m) {
        ProductMaterialDto dto = new ProductMaterialDto();
        dto.setId(m.getId());
        dto.setMaterialName(m.getMaterialName());
        dto.setProductName(m.getProductName());
        dto.setMaterialTypeId(m.getMaterialTypeId());
        dto.setInventoryCategoryId(m.getInventoryCategoryId());
        return dto;
@@ -445,7 +355,7 @@
    @Transactional(rollbackFor = Exception.class)
    public void addProductMaterial(ProductMaterial productMaterial) {
        validateProductMaterial(productMaterial, false);
        if (existsMaterialName(productMaterial.getMaterialName(), null)) {
        if (existsMaterialName(productMaterial.getProductName(), null)) {
            throw new ServiceException("物料名称已存在");
        }
        LocalDateTime now = LocalDateTime.now();
@@ -456,7 +366,7 @@
        if (!this.save(productMaterial)) {
            throw new ServiceException("新增物料失败");
        }
        log.info("新增物料成功 materialName={}", productMaterial.getMaterialName());
        log.info("新增物料成功 materialName={}", productMaterial.getProductName());
    }
    @Override
@@ -467,7 +377,7 @@
        if (exist == null) {
            throw new ServiceException("物料不存在");
        }
        if (existsMaterialName(productMaterial.getMaterialName(), productMaterial.getId())) {
        if (existsMaterialName(productMaterial.getProductName(), productMaterial.getId())) {
            throw new ServiceException("物料名称已存在");
        }
        productMaterial.setUpdateTime(LocalDateTime.now());
@@ -496,7 +406,7 @@
        if (requireId && productMaterial.getId() == null) {
            throw new ServiceException("主键ID不能为空");
        }
        if (StringUtils.isEmpty(productMaterial.getMaterialName())) {
        if (StringUtils.isEmpty(productMaterial.getProductName())) {
            throw new ServiceException("物料名称不能为空");
        }
    }
@@ -506,7 +416,7 @@
            return false;
        }
        LambdaQueryWrapper<ProductMaterial> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductMaterial::getMaterialName, materialName);
        queryWrapper.eq(ProductMaterial::getProductName, materialName);
        if (excludeId != null) {
            queryWrapper.ne(ProductMaterial::getId, excludeId);
        }
src/main/java/com/ruoyi/production/service/impl/ProductMaterialSkuServiceImpl.java
@@ -11,7 +11,7 @@
import com.ruoyi.production.mapper.ProductMaterialSkuMapper;
import com.ruoyi.production.pojo.ProductMaterial;
import com.ruoyi.production.pojo.ProductMaterialSku;
import com.ruoyi.production.pojo.ProductMaterialSkuImportDto;
import com.ruoyi.production.dto.ProductMaterialSkuImportDto;
import com.ruoyi.production.service.ProductMaterialSkuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,7 +21,6 @@
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -39,9 +38,7 @@
 */
@Slf4j
@Service
public class ProductMaterialSkuServiceImpl
        extends ServiceImpl<ProductMaterialSkuMapper, ProductMaterialSku>
        implements ProductMaterialSkuService {
public class ProductMaterialSkuServiceImpl extends ServiceImpl<ProductMaterialSkuMapper, ProductMaterialSku> implements ProductMaterialSkuService {
    @Autowired
    private ProductMaterialMapper productMaterialMapper;
@@ -50,45 +47,8 @@
     * æŸ¥è¯¢ç‰©æ–™è§„格列表
     */
    @Override
    public Page<ProductMaterialSkuDto> productMaterialSkuList(Page<ProductMaterialSku> page, ProductMaterialSkuDto dto) {
        if (dto == null || dto.getMaterialId() == null) {
            return new Page<>();
        }
        LambdaQueryWrapper<ProductMaterialSku> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductMaterialSku::getMaterialId, dto.getMaterialId())
                .like(StringUtils.isNotBlank(dto.getSpecification()),
                        ProductMaterialSku::getSpecification, dto.getSpecification())
                .like(StringUtils.isNotBlank(dto.getMaterialCode()),
                        ProductMaterialSku::getMaterialCode, dto.getMaterialCode())
                .orderByAsc(ProductMaterialSku::getId);
        Page<ProductMaterialSku> skuPage = this.page(page, queryWrapper);
        List<ProductMaterialSku> skuList = skuPage.getRecords();
        if (skuList == null || skuList.isEmpty()) {
            return new Page<>();
        }
        ProductMaterial material = productMaterialMapper.selectById(dto.getMaterialId());
        String materialName = material != null ? material.getMaterialName() : null;
        String baseUnit = material != null ? material.getBaseUnit() : null;
        List<ProductMaterialSkuDto> result = new ArrayList<>(skuList.size());
        for (ProductMaterialSku sku : skuList) {
            ProductMaterialSkuDto productMaterialSkuDto = new ProductMaterialSkuDto();
            productMaterialSkuDto.setMaterialId(dto.getMaterialId());
            productMaterialSkuDto.setMaterialName(materialName);
            productMaterialSkuDto.setMaterialCode(sku.getMaterialCode());
            productMaterialSkuDto.setBaseUnit(baseUnit);
            productMaterialSkuDto.setSkuId(sku.getId());
            productMaterialSkuDto.setSpecification(sku.getSpecification());
            productMaterialSkuDto.setSupplyType(sku.getSupplyType());
            result.add(productMaterialSkuDto);
        }
        Page<ProductMaterialSkuDto> dtoPage = new Page<>();
        dtoPage.setCurrent(skuPage.getCurrent());
        dtoPage.setSize(skuPage.getSize());
        dtoPage.setTotal(skuPage.getTotal());
        dtoPage.setRecords(result);
        return dtoPage;
    public Page<ProductMaterialSkuDto> productMaterialSkuList(Page<ProductMaterialSkuDto> page, ProductMaterialSkuDto dto, Integer type) {
        return baseMapper.selectSkuWithMaterialPage(page, dto, type);
    }
    /**
@@ -98,12 +58,12 @@
    public void addProductMaterialSku(ProductMaterialSku sku) {
        validateProductMaterialSku(sku, false);
        // æ ¡éªŒç‰©æ–™æ˜¯å¦å­˜åœ¨
        ProductMaterial material = productMaterialMapper.selectById(sku.getMaterialId());
        ProductMaterial material = productMaterialMapper.selectById(sku.getProductId());
        if (material == null) {
            throw new ServiceException("物料不存在");
        }
        // æ ¡éªŒè§„格是否重复
        if (existsSameSpecification(sku.getMaterialId(), sku.getSpecification(), null)) {
        if (existsSameSpecification(sku.getProductId(), sku.getModel(), null)) {
            throw new ServiceException("该物料已存在相同规格");
        }
        LocalDateTime now = LocalDateTime.now();
@@ -115,7 +75,7 @@
        if (!this.save(sku)) {
            throw new ServiceException("新增物料规格失败");
        }
        log.info("新增物料规格成功 materialId={}, specification={}", sku.getMaterialId(), sku.getSpecification());
        log.info("新增物料规格成功 materialId={}, specification={}", sku.getProductId(), sku.getModel());
    }
    /**
@@ -125,7 +85,7 @@
    public void updateProductMaterialSku(ProductMaterialSku sku) {
        validateProductMaterialSku(sku, true);
        // æ ¡éªŒè§„格是否重复
        if (existsSameSpecification(sku.getMaterialId(), sku.getSpecification(), sku.getId())) {
        if (existsSameSpecification(sku.getProductId(), sku.getModel(), sku.getId())) {
            throw new ServiceException("该物料已存在相同规格");
        }
        sku.setUpdateTime(LocalDateTime.now());
@@ -159,10 +119,10 @@
        if (requireId && sku.getId() == null) {
            throw new ServiceException("主键ID不能为空");
        }
        if (sku.getMaterialId() == null) {
        if (sku.getProductId() == null) {
            throw new ServiceException("物料ID不能为空");
        }
        if (StringUtils.isEmpty(sku.getSpecification())) {
        if (StringUtils.isEmpty(sku.getModel())) {
            throw new ServiceException("规格不能为空");
        }
    }
@@ -172,8 +132,8 @@
     */
    private boolean existsSameSpecification(Long materialId, String specification, Long excludeId) {
        LambdaQueryWrapper<ProductMaterialSku> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductMaterialSku::getMaterialId, materialId)
                .eq(ProductMaterialSku::getSpecification, specification);
        queryWrapper.eq(ProductMaterialSku::getProductId, materialId)
                .eq(ProductMaterialSku::getModel, specification);
        if (excludeId != null) {
            queryWrapper.ne(ProductMaterialSku::getId, excludeId);
        }
@@ -211,10 +171,10 @@
        Map<String, ProductMaterialSkuImportDto> specMap = new LinkedHashMap<>();
        for (ProductMaterialSkuImportDto dto : importList) {
            if (dto == null || StringUtils.isEmpty(dto.getSpecification())) {
            if (dto == null || StringUtils.isEmpty(dto.getModel())) {
                continue;
            }
            String specification = dto.getSpecification().trim();
            String specification = dto.getModel().trim();
            if (specification.isEmpty()) {
                continue;
            }
@@ -228,10 +188,10 @@
        Set<String> specifications = specMap.keySet();
        List<ProductMaterialSku> existList = this.list(new LambdaQueryWrapper<ProductMaterialSku>()
                .eq(ProductMaterialSku::getMaterialId, materialId)
                .in(ProductMaterialSku::getSpecification, specifications));
                .eq(ProductMaterialSku::getProductId, materialId)
                .in(ProductMaterialSku::getModel, specifications));
        Map<String, ProductMaterialSku> existMap = existList.stream()
                .collect(Collectors.toMap(ProductMaterialSku::getSpecification, sku -> sku, (a, b) -> a));
                .collect(Collectors.toMap(ProductMaterialSku::getModel, sku -> sku, (a, b) -> a));
        LocalDateTime now = LocalDateTime.now();
        List<ProductMaterialSku> saveList = new ArrayList<>();
@@ -245,8 +205,8 @@
            ProductMaterialSku exist = existMap.get(specification);
            if (exist == null) {
                ProductMaterialSku sku = new ProductMaterialSku();
                sku.setMaterialId(materialId);
                sku.setSpecification(specification);
                sku.setProductId(materialId);
                sku.setModel(specification);
                sku.setSupplyType(supplyType);
                sku.setCreateTime(now);
                sku.setUpdateTime(now);
src/main/java/com/ruoyi/production/service/impl/ProductProcessParamServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
package com.ruoyi.production.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.service.BaseParamService;
import com.ruoyi.production.dto.ProductProcessParamDto;
import com.ruoyi.production.dto.ProductProcessParamSortDTO;
import com.ruoyi.production.mapper.ProductProcessParamMapper;
import com.ruoyi.production.pojo.ProductProcessParam;
import com.ruoyi.production.service.ProductProcessParamService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
 * <br>
 * å·¥åºç»‘定参数接口实现类
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/14 13:18
 */
@Slf4j
@Service
public class ProductProcessParamServiceImpl extends ServiceImpl<ProductProcessParamMapper, ProductProcessParam> implements ProductProcessParamService {
    @Autowired
    private BaseParamService baseParamService;
    @Override
    public List<ProductProcessParamDto> listByProcessId(Long processId) {
        if (processId == null) {
            throw new IllegalArgumentException("工序ID不能为空");
        }
        return baseMapper.selectDtoListByProcessId(processId);
    }
    @Override
    public void add(ProductProcessParam productProcessParam) {
        if (productProcessParam.getProcessId() == null) {
            throw new IllegalArgumentException("关联工序ID不能为空");
        }
        if (productProcessParam.getParamId() == null) {
            throw new IllegalArgumentException("关联基础参数ID不能为空");
        }
        productProcessParam.setCreateTime(new Date());
        if (!this.save(productProcessParam)) {
            throw new RuntimeException("新增失败");
        }
    }
    @Override
    public void edit(ProductProcessParam productProcessParam) {
        if (productProcessParam.getId() == null) {
            throw new IllegalArgumentException("ID不能为空");
        }
        productProcessParam.setUpdateTime(new Date());
        if (!this.updateById(productProcessParam)) {
            throw new RuntimeException("修改失败");
        }
    }
    @Override
    public void deleteByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new IllegalArgumentException("ID不能为空");
        }
        if (!this.removeByIds(ids)) {
            throw new RuntimeException("删除失败");
        }
    }
    @Override
    public void updateSort(ProductProcessParamSortDTO dto) {
        if (dto == null || dto.getItems() == null || dto.getItems().isEmpty()) {
            throw new IllegalArgumentException("排序数据不能为空");
        }
        List<ProductProcessParam> list = new ArrayList<>();
        for (ProductProcessParamSortDTO.SortItem item : dto.getItems()) {
            ProductProcessParam param = new ProductProcessParam();
            param.setId(item.getId());
            param.setSort(item.getSort());
            list.add(param);
        }
        this.updateBatchById(list);
    }
}
src/main/java/com/ruoyi/production/service/impl/ProductStructureServiceImpl.java
@@ -130,8 +130,8 @@
    @Override
    public List<ProductStructureDto> listBybomId(Integer bomId) {
        List<ProductStructureDto> list = productStructureMapper.listBybomId(bomId);
    public List<ProductStructureDto> listByBomId(Integer bomId) {
        List<ProductStructureDto> list = productStructureMapper.listByBomId(bomId);
        Map<Long, ProductStructureDto> map = new HashMap<>();
        for (ProductStructureDto node : list) {
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -140,7 +140,7 @@
        productionProductMain.setStatus(0);
        productionProductMainMapper.insert(productionProductMain);
        /*新增报工投入表*/
        List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomAndProcess(productProcessRoute.getBomId(), productProcess.getId());
        List<ProductStructureDto> productStructureDtos = productStructureMapper.listByBomAndProcess(productProcessRoute.getBomId(), productProcess.getId());
        if (productStructureDtos.size() == 0) {
            //如果该工序没有产品结构的投入品,那这个投入品和产出品是同一个
            ProductStructureDto productStructureDto = new ProductStructureDto();
src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java
@@ -1,6 +1,5 @@
package com.ruoyi.productionPlan.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -12,10 +11,9 @@
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.config.AliDingConfig;
import com.ruoyi.production.pojo.ProductMaterial;
import com.ruoyi.framework.util.AliDingUtils;
import com.ruoyi.production.pojo.ProductMaterialSku;
import com.ruoyi.production.pojo.ProductOrder;
import com.ruoyi.production.service.ProductMaterialService;
@@ -38,10 +36,9 @@
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@@ -248,133 +245,41 @@
        }
        try {
            //  èŽ·å– AccessToken
            String accessToken = getAccessToken();
            if (StringUtils.isEmpty(accessToken)) {
            JSONArray searchConditions = new JSONArray();
            JSONObject condition = new JSONObject();
            condition.put("key", "processApprovedResult");
            JSONArray valueArray = new JSONArray();
            valueArray.add("agree");
            condition.put("value", valueArray);
            condition.put("type", "ARRAY");
            condition.put("operator", "in");
            condition.put("componentName", "SelectField");
            searchConditions.add(condition);
            String searchFieldJson = searchConditions.toJSONString();
            JSONArray dataArr = AliDingUtils.getFormDataList(aliDingConfig, aliDingConfig.getProducePlanFormUuid(), searchFieldJson, this, ProductionPlan::getFormModifiedTime);
            if (dataArr.isEmpty()) {
                log.info("没有更多新数据需要同步");
                return;
            }
            //  èŽ·å–æœ¬åœ°æœ€åŽåŒæ­¥æ—¶é—´
            LocalDateTime lastSyncTime = getLastSyncTime();
            log.info("开始增量同步,本地最后修改时间: {}", lastSyncTime);
            int pageNumber = 1;
            int pageSize = 50;
            boolean hasMore = true;
            int totalSynced = 0;
            while (hasMore) {
                //  æŸ¥è¯¢å‚æ•°
                JSONObject searchParam = buildSearchParam(lastSyncTime, pageNumber, pageSize);
                //  è°ƒç”¨å®œæ­æŽ¥å£æ‹‰å–数据
                String dataRes = HttpUtils.sendPostJson(
                        aliDingConfig.getSearchFormDataUrl(),
                        searchParam.toJSONString(),
                        StandardCharsets.UTF_8.name(),
                        null,
                        accessToken
                );
                if (StringUtils.isEmpty(dataRes)) {
                    log.warn("第 {} é¡µæ‹‰å–数据为空", pageNumber);
                    break;
                }
                JSONObject resultObj = JSON.parseObject(dataRes);
                JSONArray dataArr = resultObj.getJSONArray("data");
                Integer totalCount = resultObj.getInteger("totalCount");
                if (dataArr == null || dataArr.isEmpty()) {
                    log.info("没有更多新数据需要同步");
                    break;
                }
                //  è§£æžå¹¶ä¿å­˜æ•°æ®
                List<ProductionPlan> list = parseProductionPlans(dataArr, dataSyncType, totalCount);
                if (!list.isEmpty()) {
                    //  å¤„理更新或新增
                    int affected = processSaveOrUpdate(list);
                    totalSynced += affected;
                }
                //  åˆ¤æ–­æ˜¯å¦è¿˜æœ‰ä¸‹ä¸€é¡µ
                hasMore = (pageNumber * pageSize) < totalCount;
                pageNumber++;
                log.info("正在同步第 {} é¡µï¼Œå½“前已同步 {}/{}", pageNumber - 1, totalSynced, totalCount);
            //  è§£æžå¹¶ä¿å­˜æ•°æ®
            List<ProductionPlan> list = parseProductionPlans(dataArr, dataSyncType, dataArr.size());
            if (!list.isEmpty()) {
                //  å¤„理更新或新增
                int affected = processSaveOrUpdate(list);
                log.info("数据同步完成,共同步 {} æ¡æ•°æ®", affected);
            }
            log.info("数据同步完成,共同步 {} æ¡æ•°æ®", totalSynced);
        } catch (Exception e) {
            log.error("同步生产计划异常", e);
        } finally {
            // é‡Šæ”¾é”
            syncLock.unlock();
        }
    }
    private String getAccessToken() {
        String params = "appkey=" + aliDingConfig.getAppKey()
                + "&appsecret=" + aliDingConfig.getAppSecret();
        String tokenRes = HttpUtils.sendGet(aliDingConfig.getAccessTokenUrl(), params);
        JSONObject tokenObj = JSON.parseObject(tokenRes);
        String accessToken = tokenObj.getString("access_token");
        if (StringUtils.isEmpty(accessToken)) {
            log.error("获取钉钉AccessToken失败: {}", tokenRes);
        }
        return accessToken;
    }
    private LocalDateTime getLastSyncTime() {
        // æŸ¥è¯¢æœ¬åœ°æ•°æ®åº“中 formModifiedTime æœ€å¤§çš„记录
        LambdaQueryWrapper<ProductionPlan> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(ProductionPlan::getFormModifiedTime).last("LIMIT 1");
        ProductionPlan lastRecord = this.getOne(queryWrapper);
        return lastRecord != null ? lastRecord.getFormModifiedTime() : null;
    }
    private JSONObject buildSearchParam(LocalDateTime lastSyncTime, int pageNumber, int pageSize) {
        JSONObject searchParam = new JSONObject();
        searchParam.put("appType", aliDingConfig.getAppType());
        searchParam.put("systemToken", aliDingConfig.getSystemToken());
        searchParam.put("userId", aliDingConfig.getUserId());
        searchParam.put("formUuid", aliDingConfig.getProducePlanFormUuid());
        searchParam.put("currentPage", pageNumber);
        searchParam.put("pageSize", pageSize);
        JSONArray searchConditions = new JSONArray();
        JSONObject condition = new JSONObject();
        condition.put("key", "processApprovedResult");
        JSONArray valueArray = new JSONArray();
        valueArray.add("agree");
        condition.put("value", valueArray);
        condition.put("type", "ARRAY");
        condition.put("operator", "in");
        condition.put("componentName", "SelectField");
        searchConditions.add(condition);
        searchParam.put("searchFieldJson", searchConditions.toJSONString());
        // é»˜è®¤æŒ‰ä¿®æ”¹æ—¶é—´å‡åºæŽ’序,确保分页拉取数据的连续性
        // "+" è¡¨ç¤ºå‡åºï¼Œ"gmt_modified" æ˜¯å®˜æ–¹å†…置字段
        searchParam.put("orderConfigJson", "{\"gmt_modified\":\"+\"}");
        // è®¾ç½®ä¿®æ”¹æ—¶é—´ç­›é€‰åŒºé—´ (格式必须为yyyy-MM-dd HH:mm:ss)
        if (lastSyncTime != null) {
            // èµ·å§‹æ—¶é—´ï¼šä¸Šæ¬¡åŒæ­¥åˆ°çš„æœ€åŽä¸€æ¡æ•°æ®çš„修改时间
            String startTime = lastSyncTime.plusSeconds(1).atZone(ZoneId.systemDefault())
                    .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            searchParam.put("modifiedFromTimeGMT", startTime);
        }
        // æˆªæ­¢æ—¶é—´ï¼šå½“前时间,确保获取最新的已修改/已新增数据
        String endTime = LocalDateTime.now().atZone(ZoneId.systemDefault())
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        searchParam.put("modifiedToTimeGMT", endTime);
        return searchParam;
    }
    private List<ProductionPlan> parseProductionPlans(JSONArray dataArr, Integer dataSyncType, Integer totalCount) {
@@ -458,8 +363,8 @@
                    plan.setModifierName(modifyUser.getJSONObject("userName").getString("nameInChinese"));
                }
                plan.setFormCreatedTime(parseUtcTime(item.getString("createdTimeGMT")));
                plan.setFormModifiedTime(parseUtcTime(item.getString("modifiedTimeGMT")));
                plan.setFormCreatedTime(AliDingUtils.parseUtcTime(item.getString("createdTimeGMT")));
                plan.setFormModifiedTime(AliDingUtils.parseUtcTime(item.getString("modifiedTimeGMT")));
                plan.setDataSourceType(DataSourceTypeEnum.DING_TALK.getCode());
                plan.setCreateTime(now);
                plan.setUpdateTime(now);
@@ -513,19 +418,6 @@
            }
        }
        return affected;
    }
    private LocalDateTime parseUtcTime(String utcString) {
        if (StringUtils.isEmpty(utcString)) {
            return null;
        }
        try {
            OffsetDateTime odt = OffsetDateTime.parse(utcString);
            return odt.toLocalDateTime();
        } catch (DateTimeParseException ex) {
            log.warn("解析时间 {} å¤±è´¥: {}", utcString, ex.getMessage());
            return null;
        }
    }
    @Override
src/main/java/com/ruoyi/quality/pojo/QualityTestStandardBinding.java
@@ -32,6 +32,7 @@
    private Integer id;
    @ApiModelProperty("产品ID")
    //宁夏-中盛建材使用新的产品表关联product_material_sku
    private Long productId;
    @ApiModelProperty("关联检测标准主表id")
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -495,7 +495,7 @@
        if (processRoute == null) {
            return R.fail("请先设置工艺路线");
        }
        List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomId(processRoute.getBomId());
        List<ProductStructureDto> productStructureDtos = productStructureMapper.listByBomId(processRoute.getBomId());
        if (productStructureDtos.isEmpty()) {
            return R.fail("请先设置产品结构");
        }
src/main/resources/application-zsjc.yml
@@ -32,6 +32,7 @@
    app-type: "APP_UY8XMO7GNA8OF08EFNVQ"
    produce-plan-form-uuid: "FORM-4IA66891C5H3QWMDBSGO05C0OX9628GRPYF7L8"
    material-code-form-uuid: "FORM-8I666N718RW4ALO4ETYDDB0U6U8T3EIWTQ0ALL1"
    customer-code-form-uuid: "FORM-4V966QC1M8B5W4Q89M1VL4ZURJ6C3FVA021ALE"
    system-token: "4J766L91OFH3V612E7LG5B4DI8M13MQF9VF7LG4"
    # æŽ¥å£åœ°å€
    access-token-url: "https://oapi.dingtalk.com/gettoken"
@@ -264,4 +265,4 @@
file:
  temp-dir: /javaWork/product-inventory-management/file/temp/uploads
  upload-dir: /javaWork/product-inventory-management/file/prod/uploads
  upload-dir: /javaWork/product-inventory-management/file/prod/uploads
src/main/resources/mapper/basic/BaseParamMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
<?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.basic.mapper.BaseParamMapper">
    <resultMap id="BaseResultMap" type="com.ruoyi.basic.pojo.BaseParam">
        <id column="id" property="id"/>
        <result column="param_key" property="paramKey"/>
        <result column="param_name" property="paramName"/>
        <result column="param_type" property="paramType"/>
        <result column="param_format" property="paramFormat"/>
        <result column="value_mode" property="valueMode"/>
        <result column="unit" property="unit"/>
        <result column="is_required" property="isRequired"/>
        <result column="remark" property="remark"/>
        <result column="create_user" property="createUser"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="tenant_id" property="tenantId"/>
    </resultMap>
</mapper>
src/main/resources/mapper/production/ProductBomMapper.xml
@@ -15,34 +15,40 @@
        <result column="update_user" property="updateUser"/>
        <result column="tenant_id" property="tenantId"/>
    </resultMap>
    <select id="listPage" resultType="com.ruoyi.production.dto.ProductBomDto">
        select * from (select pb.*,
        pm.model productModelName,
        p.product_name productName
        from product_bom pb
        left join product_model pm on pb.product_model_id = pm.id
        left join product p on pm.product_id = p.id)A
        where 1=1
        <if test="c.productModelName != null">
            and productModelName = #{c.productModelName}
        SELECT * FROM (
        SELECT
        pb.*,
        pms.model AS productModelName,
        pm.product_name AS productName
        FROM product_bom pb
        LEFT JOIN product_material_sku pms ON pb.product_model_id = pms.id
        LEFT JOIN product_material pm ON pms.product_id = pm.id
        ) A
        WHERE 1=1
        <if test="c.productModelName != null and c.productModelName != ''">
            AND productModelName LIKE CONCAT('%', #{c.productModelName}, '%')
        </if>
        <if test="c.productName != null">
            and productName = #{c.productName}
        <if test="c.productName != null and c.productName != ''">
            AND productName LIKE CONCAT('%', #{c.productName}, '%')
        </if>
        <if test="c.bomNo != null">
            and bom_no = #{c.bomNo}
        <if test="c.bomNo != null and c.bomNo != ''">
            AND bom_no = #{c.bomNo}
        </if>
        <if test="c.version != null">
            and version = #{c.version}
        <if test="c.version != null and c.version != ''">
            AND version = #{c.version}
        </if>
    </select>
    <select id="getById" resultType="com.ruoyi.production.dto.ProductBomDto">
        select pb.*,
               pm.model productModelName,
               p.product_name productName
               pms.model AS productModelName,
               pm.product_name   AS productName
        from product_bom pb
                 left join product_model pm on pb.product_model_id = pm.id
                 left join product p on pm.product_id = p.id
                 left join product_material_sku pms on pb.product_model_id = pms.id
                 left join product_material pm on pms.product_id = pm.id
        where pb.id = #{id}
    </select>
</mapper>
src/main/resources/mapper/production/ProductMaterialMapper.xml
@@ -9,8 +9,8 @@
        <result property="tenantId" column="tenant_id"/>
        <result property="materialTypeId" column="material_type_id"/>
        <result property="inventoryCategoryId" column="inventory_category_id"/>
        <result property="materialName" column="material_name"/>
        <result property="baseUnit" column="base_unit"/>
        <result property="productName" column="product_name"/>
        <result property="unit" column="unit"/>
        <result property="remark" column="remark"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
src/main/resources/mapper/production/ProductMaterialSkuMapper.xml
@@ -6,10 +6,10 @@
    <resultMap id="ProductMaterialSkuResultMap" type="com.ruoyi.production.pojo.ProductMaterialSku">
        <id property="id" column="id"/>
        <result property="materialId" column="material_id"/>
        <result property="productId" column="product_id"/>
        <result property="identifierCode" column="identifier_code"/>
        <result property="materialCode" column="material_code"/>
        <result property="specification" column="specification"/>
        <result property="model" column="model"/>
        <result property="supplyType" column="supply_type"/>
        <result property="originatorName" column="originator_name"/>
        <result property="originatorOrg" column="originator_org"/>
@@ -19,4 +19,33 @@
        <result property="updateTime" column="update_time"/>
    </resultMap>
    <select id="selectSkuWithMaterialPage" resultType="com.ruoyi.production.dto.ProductMaterialSkuDto">
        SELECT
        sku.id AS id,
        sku.product_id AS productId,
        sku.material_code AS materialCode,
        sku.model AS model,
        sku.supply_type AS supplyType,
        m.product_name AS productName,
        m.unit AS unit
        FROM product_material_sku sku
        LEFT JOIN product_material m ON sku.product_id = m.id
        <where>
            <if test="dto.productId != null">
                AND sku.product_id = #{dto.productId}
            </if>
            <if test="dto.model != null and dto.model != ''">
                AND sku.model LIKE CONCAT('%', #{dto.model}, '%')
            </if>
            <if test="dto.materialCode != null and dto.materialCode != ''">
                AND sku.material_code LIKE CONCAT('%', #{dto.materialCode}, '%')
            </if>
            <if test="type != null and type == 1 and dto.productName != null and dto.productName != ''">
                AND m.product_name LIKE CONCAT('%', #{dto.productName}, '%')
            </if>
        </where>
        ORDER BY sku.id ASC
    </select>
</mapper>
src/main/resources/mapper/production/ProductProcessParamMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
<?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.ProductProcessParamMapper">
    <resultMap id="ProductProcessParamResult" type="com.ruoyi.production.pojo.ProductProcessParam">
        <id property="id" column="id"/>
        <result property="processId" column="process_id"/>
        <result property="paramId" column="param_id"/>
        <result property="standardValue" column="standard_value"/>
        <result property="minValue" column="min_value"/>
        <result property="maxValue" column="max_value"/>
        <result property="isRequired" column="is_required"/>
        <result property="sort" column="sort"/>
        <result property="tenantId" column="tenant_id"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>
    <select id="selectDtoListByProcessId" resultType="com.ruoyi.production.dto.ProductProcessParamDto"
            parameterType="java.lang.Long">
        SELECT ppp.id,
               ppp.process_id,
               ppp.param_id,
               ppp.standard_value,
               ppp.min_value,
               ppp.max_value,
               ppp.is_required,
               ppp.sort,
               ppp.create_time,
               ppp.update_time,
               bp.param_name,
               bp.param_type,
               bp.param_format,
               bp.value_mode,
               bp.unit
        FROM product_process_param ppp
                 LEFT JOIN base_param bp ON ppp.param_id = bp.id
        WHERE ppp.process_id = #{processId}
        ORDER BY ppp.sort ASC
    </select>
</mapper>
src/main/resources/mapper/production/ProductStructureMapper.xml
@@ -11,33 +11,33 @@
        <result property="unit" column="unit"/>
        <result property="tenantId" column="tenant_id"/>
    </resultMap>
    <select id="listBybomId" resultType="com.ruoyi.production.dto.ProductStructureDto">
    <select id="listByBomId" resultType="com.ruoyi.production.dto.ProductStructureDto">
        select ps.*,
               p.product_name,
               pp.name as  process_name,
               pm.product_id,
               pm.model
        from
            product_structure ps
                left join product_model pm on ps.product_model_id = pm.id
                left join product p on pm.product_id = p.id
                left join product_process pp on ps.process_id = pp.id
               pm.product_name as product_name,
               pp.name         as process_name,
               pms.product_id as product_id,
               pms.model       as model
        from product_structure ps
                 left join product_material_sku pms on ps.product_model_id = pms.id
                 left join product_material pm on pms.product_id = pm.id
                 left join product_process pp on ps.process_id = pp.id
        where ps.bom_id = #{bomId}
        order by ps.id
    </select>
    <select id="listBybomAndProcess" resultType="com.ruoyi.production.dto.ProductStructureDto">
    <select id="listByBomAndProcess" resultType="com.ruoyi.production.dto.ProductStructureDto">
        select ps.*,
               p.product_name,
               pp.name as  process_name,
               pm.product_id,
               pm.model
        from
            product_structure ps
                left join product_model pm on ps.product_model_id = pm.id
                left join product p on pm.product_id = p.id
                left join product_process pp on ps.process_id = pp.id
               pm.product_name as product_name,
               pp.name         as process_name,
               pms.product_id as product_id,
               pms.model       as model
        from product_structure ps
                 left join product_material_sku pms on ps.product_model_id = pms.id
                 left join product_material pm on pms.product_id = pm.id
                 left join product_process pp on ps.process_id = pp.id
        where ps.bom_id = #{bomId}
        and ps.process_id=#{processId}
          and ps.process_id = #{processId}
        order by ps.id
    </select>
</mapper>