已添加18个文件
已修改14个文件
1086 ■■■■■ 文件已修改
doc/宁夏-中盛建材.sql 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/AliDingConfig.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductMaterialController.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductOrderController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/enums/MaterialConfigTypeEnum.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductMaterialConfigMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductMaterialMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductMaterial.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductMaterialConfig.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductOrder.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductMaterialConfigService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductMaterialService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductOrderService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductMaterialConfigServiceImpl.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java 306 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/task/ProductMaterialTask.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/controller/ProductOrderPlanController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/controller/ProductionPlanController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/dto/ProductionPlanDto.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/mapper/ProductOrderPlanMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/pojo/ProductOrderPlan.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/pojo/ProductionPlan.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/ProductOrderPlanService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/ProductionPlanService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/impl/ProductOrderPlanServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java 130 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-zsjc.yml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductMaterialMapper.xml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/productionPlan/ProductOrderPlanMapper.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/productionPlan/ProductionPlanMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/ÄþÏÄ-ÖÐÊ¢½¨²Ä.sql
@@ -92,5 +92,61 @@
    add assigned_quantity DECIMAL(10, 4) default 0 not null COMMENT '下发数量';
alter table product_order
    add plan_complete_time datetime(0) NULL DEFAULT NULL COMMENT '计划完成时间',
    add combine_production_plan_ids varchar(500) default '合并生产计划id' not null;
    add plan_complete_time date NULL DEFAULT NULL COMMENT '计划完成时间';
# ç”Ÿäº§è®¢å•与生产计划关联表
drop table if exists product_order_plan;
create table product_order_plan
(
    id                       bigint auto_increment primary key,
    product_order_id bigint not null default 0 comment '生产订单id',
    production_plan_id  bigint not null default 0 comment '生产计划id',
    create_time              datetime null comment '录入时间',
    update_time              datetime null comment '更新时间',
    assigned_quantity DECIMAL(10, 4) default 0 not null comment '下发数量',
    index idx_product_order_id (product_order_id),
    index idx_production_plan_id (production_plan_id),
    unique idx_product_order_production_plan (product_order_id, production_plan_id)
);
CREATE TABLE `product_material`
(
    `id`                    INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
    `tenant_id`             BIGINT       DEFAULT NULL COMMENT '租户ID',
    `material_type_id`      INT          DEFAULT NULL COMMENT '关联物料类型ID',
    `inventory_category_id` INT          DEFAULT NULL COMMENT '关联存货类别ID',
    `identifier_code`       VARCHAR(100) DEFAULT NULL COMMENT '标识编码',
    `material_code`         VARCHAR(100) DEFAULT NULL COMMENT '物料代码',
    `product_name`          VARCHAR(255) DEFAULT NULL COMMENT '产品名称',
    `material_name`         VARCHAR(255) DEFAULT NULL COMMENT '物料品名',
    `specification`         VARCHAR(255) DEFAULT NULL COMMENT '规格型号',
    `base_unit`             VARCHAR(50)  DEFAULT NULL COMMENT '基本单位',
    `material_attribute`    VARCHAR(100) DEFAULT NULL COMMENT '物料属性',
    `finished_product_name` VARCHAR(100) DEFAULT NULL COMMENT '成品品名',
    `originator_name`       VARCHAR(100) DEFAULT NULL COMMENT '提交人姓名',
    `originator_org`        VARCHAR(255) DEFAULT '宁夏中创绿能实业集团有限公司',
    `remark`                TEXT COMMENT '备注',
    `create_time`           DATETIME     DEFAULT CURRENT_TIMESTAMP,
    `update_time`           DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_type_id` (`material_type_id`),
    INDEX `idx_cat_id` (`inventory_category_id`)
) ENGINE = INNODB
  DEFAULT CHARSET = utf8mb4 COMMENT = '物料信息表';
ALTER TABLE product_material
    ADD COLUMN form_instance_id   VARCHAR(100) DEFAULT NULL COMMENT '宜搭表单实例ID',
    ADD COLUMN form_modified_time DATETIME     DEFAULT NULL COMMENT '宜搭修改时间';
CREATE TABLE `product_material_config`
(
    `id`          int          NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `config_type` varchar(50)  NOT NULL COMMENT '区分类型: MATERIAL_TYPE æˆ– INVENTORY_CAT',
    `config_name` varchar(100) NOT NULL COMMENT '显示的名称'
) ENGINE = InnoDB COMMENT ='物料信息表配置表';
ALTER TABLE `production_plan`
    ADD COLUMN `product_material_id` int DEFAULT NULL COMMENT '关联物料信息表ID' AFTER `material_code`;
-- å»ºè®®é¡ºä¾¿åŠ ä¸Šç´¢å¼•ï¼Œæå‡å…³è”æŸ¥è¯¢é€Ÿåº¦
ALTER TABLE `production_plan`
    ADD INDEX `idx_product_material_id` (`product_material_id`);
src/main/java/com/ruoyi/framework/config/AliDingConfig.java
@@ -42,9 +42,15 @@
    private String appType;
    /**
     * å®œæ­è¡¨å•ID
     * é”€å”®ç”Ÿäº§éœ€æ±‚-宜搭表单ID
     */
    private String formUuid;
    private String producePlanFormUuid;
    /**
     * ç‰©æ–™ç¼–码-宜搭表单ID
     */
    private String materialCodeFormUuid;
    /**
     * å®œæ­åº”用密钥
src/main/java/com/ruoyi/production/controller/ProductMaterialController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
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.service.ProductMaterialService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <br>
 * äº§å“ç‰©æ–™ä¿¡æ¯æŽ§åˆ¶å±‚
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:38
 */
@RestController
@RequestMapping("/productMaterial")
public class ProductMaterialController {
    @Autowired
    private ProductMaterialService productMaterialService;
    @GetMapping("/loadData")
    @ApiOperation("拉取物料编码数据")
    @Log(title = "拉取物料编码数据", businessType = BusinessType.INSERT)
    public AjaxResult loadProductMaterialData() {
        productMaterialService.loadProductMaterialData();
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/production/controller/ProductOrderController.java
@@ -46,6 +46,12 @@
        return R.ok(productOrderService.listProcessRoute(productModelId));
    }
    @PostMapping("/revoke")
    @ApiOperation("撤回生产计划")
    public R revoke(@RequestBody ProductOrder productOrder) {
        return R.ok(productOrderService.revoke(productOrder));
    }
    /**
     * å¯¼å‡ºç”Ÿäº§è®¢å•
     */
src/main/java/com/ruoyi/production/enums/MaterialConfigTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.production.enums;
/**
 * <br>
 * ç‰©æ–™é…ç½®ç±»åž‹æžšä¸¾
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 17:24
 */
public enum MaterialConfigTypeEnum {
    /**
     * ç‰©æ–™ç±»åž‹
     * å¯¹åº”数据库 config_type = MATERIAL_TYPE
     */
    MATERIAL_TYPE,
    /**
     * å­˜è´§ç±»åˆ«
     * å¯¹åº”数据库 config_type = INVENTORY_CAT
     */
    INVENTORY_CAT
}
src/main/java/com/ruoyi/production/mapper/ProductMaterialConfigMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.production.pojo.ProductMaterialConfig;
/**
 * <br>
 * ç‰©æ–™ä¿¡æ¯è¡¨é…ç½®Mapper
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:58
 */
public interface ProductMaterialConfigMapper extends BaseMapper<ProductMaterialConfig> {
}
src/main/java/com/ruoyi/production/mapper/ProductMaterialMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.production.pojo.ProductMaterial;
/**
 * <br>
 * äº§å“ç‰©æ–™ä¿¡æ¯Mapper
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:35
 */
public interface ProductMaterialMapper extends BaseMapper<ProductMaterial> {
}
src/main/java/com/ruoyi/production/pojo/ProductMaterial.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
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.time.LocalDateTime;
/**
 * <br>
 * äº§å“ç‰©æ–™ä¿¡æ¯è¡¨
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:31
 */
@Data
@TableName("product_material")
@ApiModel(value = "ProductMaterial", description = "产品物料信息表")
public class ProductMaterial {
    @TableId(type = IdType.AUTO)
    @ApiModelProperty("主键ID")
    private Integer id;
    @ApiModelProperty("租户ID")
    private Long tenantId;
    @ApiModelProperty("物料类型ID")
    private Integer materialTypeId;
    @ApiModelProperty("存货类别ID")
    private Integer inventoryCategoryId;
    @ApiModelProperty("标识编码")
    private String identifierCode;
    @ApiModelProperty("物料代码")
    private String materialCode;
    @ApiModelProperty("物料品名")
    private String materialName;
    @ApiModelProperty("规格型号")
    private String specification;
    @ApiModelProperty("基本单位")
    private String baseUnit;
    @ApiModelProperty("物料属性")
    private String materialAttribute;
    @ApiModelProperty("成品品名")
    private String finishedProductName;
    @ApiModelProperty("提交人姓名")
    private String originatorName;
    @ApiModelProperty("提交人组织")
    private String originatorOrg;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty("修改时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    @ApiModelProperty("宜搭表单实例ID")
    private String formInstanceId;
    @ApiModelProperty("宜搭修改时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime formModifiedTime;
}
src/main/java/com/ruoyi/production/pojo/ProductMaterialConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.production.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * <br>
 * ç‰©æ–™ä¿¡æ¯è¡¨é…ç½®è¡¨
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:57
 */
@Data
@TableName("product_material_config")
@ApiModel(value = "ProductMaterialConfig", description = "物料信息配置表")
public class ProductMaterialConfig {
    @TableId(type = IdType.AUTO)
    @ApiModelProperty("主键ID")
    private Integer id;
    @ApiModelProperty("区分类型: MATERIAL_TYPE æˆ– INVENTORY_CAT")
    private String configType;
    @ApiModelProperty("配置名称")
    private String configName;
}
src/main/java/com/ruoyi/production/pojo/ProductOrder.java
@@ -10,6 +10,7 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@@ -36,18 +37,12 @@
    private String npsNo;
    /**
     * åˆå¹¶ç”Ÿäº§è®¡åˆ’ids
     */
    @ApiModelProperty(value = "合并生产计划ids")
    private String combineProductionPlanIds;
    /**
     * è®¡åˆ’完成时间
     */
    @ApiModelProperty(value = "计划完成时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime planCompleteTime;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate planCompleteTime;
    /**
     * ç§Ÿæˆ·id
src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
@@ -32,6 +32,9 @@
    @ApiModelProperty(value = "工单id")
    private Long workOrderId;
    @ApiModelProperty(value = "生产订单id")
    private Long productOrderId;
    @ApiModelProperty(value = "报工状态")
    private Integer status;
src/main/java/com/ruoyi/production/service/ProductMaterialConfigService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.production.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.production.pojo.ProductMaterialConfig;
/**
 * <br>
 * ç‰©æ–™ä¿¡æ¯è¡¨é…ç½®æŽ¥å£
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:58
 */
public interface ProductMaterialConfigService extends IService<ProductMaterialConfig> {
}
src/main/java/com/ruoyi/production/service/ProductMaterialService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.production.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.production.pojo.ProductMaterial;
/**
 * <br>
 * äº§å“ç‰©æ–™ä¿¡æ¯æŽ¥å£
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:35
 */
public interface ProductMaterialService extends IService<ProductMaterial> {
    void loadProductMaterialData();
    void syncProductMaterialJob();
}
src/main/java/com/ruoyi/production/service/ProductOrderService.java
@@ -18,6 +18,11 @@
    int bindingRoute(ProductOrder productOrder);
    /**
     * æ’¤å›žç”Ÿäº§è®¡åˆ’
     */
    Boolean revoke(ProductOrder productOrder);
    List<ProcessRoute> listProcessRoute(Long productModelId);
    List<ProductStructureDto> listProcessBom(Long orderId);
src/main/java/com/ruoyi/production/service/impl/ProductMaterialConfigServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.production.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.production.mapper.ProductMaterialConfigMapper;
import com.ruoyi.production.pojo.ProductMaterialConfig;
import com.ruoyi.production.service.ProductMaterialConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
 * <br>
 * ç‰©æ–™ä¿¡æ¯è¡¨é…ç½®æŽ¥å£å®žçŽ°ç±»
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:59
 */
@Slf4j
@Service
public class ProductMaterialConfigServiceImpl extends ServiceImpl<ProductMaterialConfigMapper, ProductMaterialConfig> implements ProductMaterialConfigService {
}
src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,306 @@
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;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.framework.config.AliDingConfig;
import com.ruoyi.production.enums.MaterialConfigTypeEnum;
import com.ruoyi.production.mapper.ProductMaterialMapper;
import com.ruoyi.production.pojo.ProductMaterial;
import com.ruoyi.production.pojo.ProductMaterialConfig;
import com.ruoyi.production.service.ProductMaterialConfigService;
import com.ruoyi.production.service.ProductMaterialService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
/**
 * <br>
 * äº§å“ç‰©æ–™ä¿¡æ¯æŽ¥å£å®žçŽ°ç±»
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:36
 */
@Slf4j
@Service
public class ProductMaterialServiceImpl extends ServiceImpl<ProductMaterialMapper, ProductMaterial> implements ProductMaterialService {
    @Autowired
    private AliDingConfig aliDingConfig;
    @Autowired
    private ProductMaterialConfigService productMaterialConfigService;
    /**
     * åŒæ­¥é”ï¼Œé˜²æ­¢æ‰‹åŠ¨å’Œå®šæ—¶ä»»åŠ¡åŒæ—¶æ‰§è¡Œ
     */
    private final ReentrantLock syncLock = new ReentrantLock();
    @Override
    public void loadProductMaterialData() {
        syncProductMaterialData(1);
    }
    @Override
    public void syncProductMaterialJob() {
        syncProductMaterialData(2);
    }
    /**
     * åŒæ­¥æ•°æ®
     */
    @Transactional(rollbackFor = Exception.class)
    public void syncProductMaterialData(Integer dataSyncType) {
        if (!syncLock.tryLock()) {
            log.warn("同步正在进行中,本次 {} åŒæ­¥è¯·æ±‚被跳过", dataSyncType == 1 ? "手动" : "定时任务");
            return;
        }
        try {
            // èŽ·å– AccessToken
            String accessToken = getAccessToken();
            if (StringUtils.isEmpty(accessToken)) {
                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<ProductMaterial> list = parseProductMaterials(dataArr, totalCount);
                if (!list.isEmpty()) {
                    // å¤„理更新或新增
                    int affected = processSaveOrUpdate(list);
                    totalSynced += affected;
                }
                // åˆ¤æ–­æ˜¯å¦è¿˜æœ‰ä¸‹ä¸€é¡µ
                hasMore = (pageNumber * pageSize) < totalCount;
                pageNumber++;
                log.info("正在同步第 {} é¡µï¼Œå½“前已同步 {}/{}", pageNumber - 1, totalSynced, totalCount);
            }
            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<ProductMaterial> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(ProductMaterial::getFormModifiedTime).last("LIMIT 1");
        ProductMaterial 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.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<ProductMaterial> parseProductMaterials(JSONArray dataArr, Integer totalCount) {
        List<ProductMaterial> list = new ArrayList<>();
        LocalDateTime now = LocalDateTime.now();
        for (int i = 0; i < dataArr.size(); i++) {
            JSONObject item = dataArr.getJSONObject(i);
            String formInstanceId = item.getString("formInstanceId");
            JSONObject originator = item.getJSONObject("originator");
            String originatorName = originator != null && originator.containsKey("userName")
                    ? originator.getJSONObject("userName").getString("nameInChinese") : "未知";
            JSONObject formData = item.getJSONObject("formData");
            ProductMaterial material = new ProductMaterial();
            material.setFormInstanceId(formInstanceId);
            material.setIdentifierCode(formData.getString("textField_l92h77ju"));
            material.setMaterialCode(formData.getString("textField_l92f36f2"));
            material.setMaterialName(formData.getString("textField_l92f36f5"));
            material.setSpecification(formData.getString("textField_l92f36f6"));
            material.setBaseUnit(formData.getString("textField_la147lnw"));
            material.setMaterialAttribute(formData.getString("selectField_la14k51j"));
            material.setFinishedProductName(formData.getString("radioField_lbkk2nn2"));
            material.setRemark(formData.getString("textareaField_l92f36f9"));
            // å¤„理物料类型和存货类别
            String materialType = formData.getString("selectField_l92f36fb");
            String inventoryCat = formData.getString("selectField_la154noy");
            material.setMaterialTypeId(getOrCreateConfigId(materialType, MaterialConfigTypeEnum.MATERIAL_TYPE.name()));
            material.setInventoryCategoryId(getOrCreateConfigId(inventoryCat, MaterialConfigTypeEnum.INVENTORY_CAT.name()));
            material.setOriginatorName(originatorName);
            material.setOriginatorOrg("宁夏中创绿能实业集团有限公司");
            material.setFormModifiedTime(parseUtcTime(item.getString("modifiedTimeGMT")));
            material.setCreateTime(now);
            material.setUpdateTime(now);
            list.add(material);
        }
        return list;
    }
    private Integer getOrCreateConfigId(String name, String type) {
        if (StringUtils.isEmpty(name)) {
            return null;
        }
        ProductMaterialConfig config = productMaterialConfigService.getOne(new LambdaQueryWrapper<ProductMaterialConfig>()
                .eq(ProductMaterialConfig::getConfigName, name)
                .eq(ProductMaterialConfig::getConfigType, type));
        if (config == null) {
            config = new ProductMaterialConfig();
            config.setConfigName(name);
            config.setConfigType(type);
            productMaterialConfigService.save(config);
        }
        return config.getId();
    }
    private int processSaveOrUpdate(List<ProductMaterial> list) {
        if (list == null || list.isEmpty()) {
            return 0;
        }
        int affected = 0;
        for (ProductMaterial material : list) {
            ProductMaterial exist = this.getOne(new LambdaQueryWrapper<ProductMaterial>()
                    .eq(ProductMaterial::getFormInstanceId, material.getFormInstanceId()));
            if (exist == null) {
                this.save(material);
                affected++;
                log.info("新增物料数据 formInstanceId={}", material.getFormInstanceId());
            } else {
                if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(material.getFormModifiedTime())) {
                    material.setId(exist.getId());
                    material.setCreateTime(exist.getCreateTime());
                    this.updateById(material);
                    affected++;
                    log.info("更新物料数据 formInstanceId={}", material.getFormInstanceId());
                }
            }
        }
        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;
        }
    }
}
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java
@@ -15,10 +15,15 @@
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.ProductOrderService;
import com.ruoyi.productionPlan.mapper.ProductOrderPlanMapper;
import com.ruoyi.productionPlan.mapper.ProductionPlanMapper;
import com.ruoyi.productionPlan.pojo.ProductOrderPlan;
import com.ruoyi.productionPlan.pojo.ProductionPlan;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
@@ -49,6 +54,12 @@
    @Autowired
    private ProductionProductMainMapper productionProductMainMapper;
    @Autowired
    private ProductOrderPlanMapper productOrderPlanMapper;
    @Autowired
    private ProductionPlanMapper productionPlanMapper;
    @Autowired
    private ProductionProductOutputMapper productionProductOutputMapper;
@@ -123,6 +134,29 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean revoke(ProductOrder productOrder) {
        // todo åˆ¤æ–­æ˜¯å¦äº§ç”ŸæŠ¥å·¥ä¿¡æ¯
        // æŸ¥è¯¢åˆå¹¶çš„生产计划
        List<ProductOrderPlan> productOrderPlans = productOrderPlanMapper.selectList(Wrappers.<ProductOrderPlan>lambdaQuery().in(ProductOrderPlan::getProductOrderId, productOrder.getId()));
        if (productOrderPlans.isEmpty()) {
            throw new RuntimeException("合并的生产计划不存在");
        }
        for (ProductOrderPlan productOrderPlan : productOrderPlans) {
            ProductionPlan productionPlan = productionPlanMapper.selectById(productOrderPlan.getProductionPlanId());
            productionPlan.setAssignedQuantity(productionPlan.getAssignedQuantity().subtract(productOrderPlan.getAssignedQuantity()));
            productionPlanMapper.updateById(productionPlan);
        }
        // åˆ é™¤å…³è”关系
        productOrderPlanMapper.delete(Wrappers.<ProductOrderPlan>lambdaQuery().in(ProductOrderPlan::getProductOrderId, productOrder.getId()));
        // åˆ é™¤è®¢å•
        productOrderMapper.deleteById(productOrder.getId());
        // todo åˆ é™¤è®¢å•下的工艺路线子表
        return null;
    }
    @Override
    public List<ProcessRoute> listProcessRoute(Long productModelId) {
        return productOrderMapper.listProcessRoute(productModelId);
    }
src/main/java/com/ruoyi/production/task/ProductMaterialTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.ruoyi.production.task;
import com.ruoyi.production.service.ProductMaterialService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
 * <br>
 *
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 16:39
 */
@Component
public class ProductMaterialTask {
    @Autowired
    private ProductMaterialService productMaterialService;
    @Scheduled(cron = "0 0 * * * ?")
    public void syncProdDataJob() {
        productMaterialService.syncProductMaterialJob();
    }
}
src/main/java/com/ruoyi/productionPlan/controller/ProductOrderPlanController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.productionPlan.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 *  å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-11 03:02:58
 */
@RestController
@RequestMapping("/productOrderPlan")
public class ProductOrderPlanController {
}
src/main/java/com/ruoyi/productionPlan/controller/ProductionPlanController.java
@@ -67,7 +67,7 @@
    @Log(title = "更新生产计划", businessType = BusinessType.UPDATE)
    @ApiOperation("更新生产计划")
    public AjaxResult update(@RequestBody ProductionPlanDto productionPlanDto) {
        return AjaxResult.success(productionPlanService.updateById(productionPlanDto));
        return AjaxResult.success(productionPlanService.update(productionPlanDto));
    }
    @DeleteMapping("")
src/main/java/com/ruoyi/productionPlan/dto/ProductionPlanDto.java
@@ -8,7 +8,7 @@
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.util.List;
@Data
@@ -20,7 +20,7 @@
    private BigDecimal totalAssignedQuantity;
    @ApiModelProperty(value = "计划完成时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime planCompleteTime;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate planCompleteTime;
}
src/main/java/com/ruoyi/productionPlan/mapper/ProductOrderPlanMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.productionPlan.mapper;
import com.ruoyi.productionPlan.pojo.ProductOrderPlan;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 *  Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-11 03:02:58
 */
@Mapper
public interface ProductOrderPlanMapper extends BaseMapper<ProductOrderPlan> {
}
src/main/java/com/ruoyi/productionPlan/pojo/ProductOrderPlan.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package com.ruoyi.productionPlan.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
 * <p>
 *
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-11 03:02:58
 */
@Getter
@Setter
@TableName("product_order_plan")
@ApiModel(value = "ProductOrderPlan对象", description = "")
public class ProductOrderPlan implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("生产订单id")
    private Long productOrderId;
    @ApiModelProperty("生产计划id")
    private Long productionPlanId;
    @ApiModelProperty(value = "下发数量")
    @Excel(name = "下发数量")
    private BigDecimal assignedQuantity;
    @ApiModelProperty("录入时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
src/main/java/com/ruoyi/productionPlan/pojo/ProductionPlan.java
@@ -7,6 +7,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -63,6 +64,13 @@
    @ApiModelProperty("物料编码")
    @Excel(name = "物料编码")
    private String materialCode;
    /**
     * å…³è”物料信息表ID
     */
    @ApiModelProperty("关联物料信息表ID")
    private Integer productMaterialId;
    /**
     * äº§å“åç§°
@@ -126,6 +134,7 @@
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @ApiModelProperty("开始日期")
    @Excel(name = "开始日期", width = 20, dateFormat = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    /**
@@ -134,6 +143,7 @@
    @ApiModelProperty("结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @Excel(name = "结束日期", width = 20, dateFormat = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    /**
@@ -227,4 +237,20 @@
    @ApiModelProperty(value = "下发数量")
    @Excel(name = "下发数量")
    private BigDecimal assignedQuantity;
    /**
     * è®¡ç®—剩余方数
     *
     * @return å‰©ä½™æ–¹æ•°
     */
    @ApiModelProperty(value = "剩余方数")
    public BigDecimal getRemainingVolume() {
        if (volume == null) {
            return BigDecimal.ZERO;
        }
        if (assignedQuantity == null) {
            return volume;
        }
        return volume.subtract(assignedQuantity);
    }
}
src/main/java/com/ruoyi/productionPlan/service/ProductOrderPlanService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.productionPlan.service;
import com.ruoyi.productionPlan.pojo.ProductOrderPlan;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 *  æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-11 03:02:58
 */
public interface ProductOrderPlanService extends IService<ProductOrderPlan> {
}
src/main/java/com/ruoyi/productionPlan/service/ProductionPlanService.java
@@ -44,6 +44,16 @@
    boolean add(ProductionPlanDto productionPlanDto);
    /**
     * æ›´æ–°ç”Ÿäº§è®¡åˆ’
     */
    boolean update(ProductionPlanDto productionPlanDto);
    /**
     * åˆ é™¤ç”Ÿäº§è®¡åˆ’
     */
    boolean delete(List<Long> ids);
    /**
     * æŒ‰ç…§äº§å“ç±»åˆ«æ±‡æ€»ç»Ÿè®¡éœ€æ±‚量
     */
    List<ProductionPlanSummaryDto> summaryByProductType(ProductionPlanSummaryDto query);
src/main/java/com/ruoyi/productionPlan/service/impl/ProductOrderPlanServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.productionPlan.service.impl;
import com.ruoyi.productionPlan.pojo.ProductOrderPlan;
import com.ruoyi.productionPlan.mapper.ProductOrderPlanMapper;
import com.ruoyi.productionPlan.service.ProductOrderPlanService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 *  æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-11 03:02:58
 */
@Service
public class ProductOrderPlanServiceImpl extends ServiceImpl<ProductOrderPlanMapper, ProductOrderPlan> implements ProductOrderPlanService {
}
src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java
@@ -5,20 +5,26 @@
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
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.production.pojo.ProductOrder;
import com.ruoyi.production.service.ProductMaterialService;
import com.ruoyi.production.service.ProductOrderService;
import com.ruoyi.productionPlan.dto.ProductionPlanDto;
import com.ruoyi.productionPlan.dto.ProductionPlanImportDto;
import com.ruoyi.productionPlan.dto.ProductionPlanSummaryDto;
import com.ruoyi.productionPlan.mapper.ProductOrderPlanMapper;
import com.ruoyi.productionPlan.mapper.ProductionPlanMapper;
import com.ruoyi.productionPlan.pojo.ProductOrderPlan;
import com.ruoyi.productionPlan.pojo.ProductionPlan;
import com.ruoyi.productionPlan.service.ProductionPlanService;
import lombok.extern.slf4j.Slf4j;
@@ -62,6 +68,12 @@
    @Autowired
    private ProductOrderService productOrderService;
    @Autowired
    private ProductOrderPlanMapper productOrderPlanMapper;
    @Autowired
    private ProductMaterialService productMaterialService;
    /**
     * åŒæ­¥é”ï¼Œç¡®ä¿æ‰‹åŠ¨å’Œå®šæ—¶ä»»åŠ¡ä¸åŒæ—¶æ‰§è¡Œ
     */
@@ -100,32 +112,36 @@
        //  æŸ¥è¯¢ä¸»ç”Ÿäº§è®¡åˆ’
        List<ProductionPlan> plans = productionPlanMapper.selectBatchIds(productionPlanDto.getIds());
        plans.sort(Comparator.comparingLong(ProductionPlan::getId));
        //  æ ¡éªŒæ˜¯å¦å­˜åœ¨ä¸åŒçš„产品名称
        String firstProductName = plans.get(0).getProductName();
        if (plans.stream().anyMatch(p -> !p.getProductName().equals(firstProductName))) {
            log.warn("合并失败,存在不同的产品名称");
            return false;
            throw new BaseException("合并失败,存在不同的产品名称");
        }
        // æ ¡éªŒæ˜¯å¦å­˜åœ¨ä¸åŒçš„产品规格
        String firstProductSpec = plans.get(0).getProductSpec();
        if (plans.stream().anyMatch(p -> !p.getProductSpec().equals(firstProductSpec))) {
            log.warn("合并失败,存在不同的产品规格");
            return false;
            throw new BaseException("合并失败,存在不同的产品规格");
        }
        // å åŠ æ–¹æ•°
        BigDecimal totalVolume = plans.stream()
                .map(ProductionPlan::getVolume)
        // å åŠ å‰©ä½™æ–¹æ•°
        BigDecimal totalRemainingVolume = plans.stream()
                .map(ProductionPlan::getRemainingVolume)
                .filter(v -> v != null)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // åˆ¤æ–­ä¸‹å‘数量是否大于等于方数
        if (productionPlanDto.getTotalAssignedQuantity().compareTo(totalVolume) > 0) {
            log.warn("操作失败,下发数量不能大于方数");
            return false;
        // åˆ¤æ–­ä¸‹å‘数量是否大于等于剩余方数
        if (productionPlanDto.getTotalAssignedQuantity().compareTo(totalRemainingVolume) > 0) {
            throw new BaseException("操作失败,下发数量不能大于剩余方数");
        }
        // åˆ›å»ºç”Ÿäº§è®¢å•
        ProductOrder productOrder = new ProductOrder();
        productOrder.setQuantity(productionPlanDto.getTotalAssignedQuantity());
        productOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime());
        productOrderService.addProductOrder(productOrder);
        // æ ¹æ®ä¸‹å‘数量,从第一个生产计划开始分配方数
        BigDecimal assignedVolume = BigDecimal.ZERO;
@@ -134,26 +150,35 @@
            if (volume == null) {
                continue;
            }
            // è®¡ç®—剩余方数
            BigDecimal remainingVolume = plan.getRemainingVolume();
            if (remainingVolume.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            if (assignedVolume.add(volume).compareTo(productionPlanDto.getTotalAssignedQuantity()) >= 0) {
            ProductOrderPlan productOrderPlan = new ProductOrderPlan();
            productOrderPlan.setProductOrderId(productOrder.getId());
            productOrderPlan.setProductionPlanId(plan.getId());
            if (assignedVolume.add(remainingVolume).compareTo(productionPlanDto.getTotalAssignedQuantity()) >= 0) {
                // æœ€åŽä¸€ä¸ªè®¡åˆ’,分配剩余方数
                plan.setAssignedQuantity(productionPlanDto.getTotalAssignedQuantity().subtract(assignedVolume));
                BigDecimal lastRemainingVolume = productionPlanDto.getTotalAssignedQuantity().subtract(assignedVolume);
                plan.setAssignedQuantity(plan.getAssignedQuantity().add(lastRemainingVolume));
                productOrderPlan.setAssignedQuantity(lastRemainingVolume);
                productionPlanMapper.updateById(plan);
                productOrderPlanMapper.insert(productOrderPlan);
                break;
            }
            // åˆ†é…å½“前计划方数
            plan.setAssignedQuantity(volume);
            plan.setAssignedQuantity(plan.getAssignedQuantity().add(remainingVolume));
            productOrderPlan.setAssignedQuantity(remainingVolume);
            // æ›´æ–°ç”Ÿäº§è®¡åˆ’
            productionPlanMapper.updateById(plan);
            assignedVolume = assignedVolume.add(volume);
            // åˆ›å»ºå…³è”关系
            productOrderPlanMapper.insert(productOrderPlan);
            assignedVolume = assignedVolume.add(remainingVolume);
        }
        // åˆ›å»ºç”Ÿäº§è®¢å•
        ProductOrder productOrder = new ProductOrder();
        String combineIds = StringUtils.join(productionPlanDto.getIds(), ",");
        productOrder.setCombineProductionPlanIds(combineIds);
        productOrder.setQuantity(productionPlanDto.getTotalAssignedQuantity());
        productOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime());
        productOrderService.addProductOrder(productOrder);
        return true;
    }
@@ -163,6 +188,33 @@
        productionPlanDto.setDataSourceType(PRODUCTION_FORECAST.getCode());
        productionPlanMapper.insert(productionPlanDto);
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean update(ProductionPlanDto productionPlanDto) {
        // æŸ¥è¯¢æ˜¯å¦æœ‰å…³è”订单
        boolean hasProductOrderPlan = productOrderPlanMapper.selectList(Wrappers.<ProductOrderPlan>lambdaQuery().eq(ProductOrderPlan::getProductionPlanId, productionPlanDto.getId())).stream().anyMatch(p -> p.getProductOrderId() != null);
        if (hasProductOrderPlan) {
            //  å¦‚果关联,方数只能递增
            ProductionPlan currentPlan = productionPlanMapper.selectById(productionPlanDto.getId());
            if (productionPlanDto.getVolume().compareTo(currentPlan.getVolume()) < 0) {
                throw new BaseException("方数不能递减");
            }
        }
        return productionPlanMapper.updateById(productionPlanDto) > 0;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(List<Long> ids) {
        // å¦‚果有关联订单,则不能删除
        if (productOrderPlanMapper.selectList(Wrappers.<ProductOrderPlan>lambdaQuery().in(ProductOrderPlan::getProductionPlanId, ids)).stream().anyMatch(p -> p.getProductOrderId() != null)) {
            throw new BaseException("删除失败,存在关联订单");
        }
        return productionPlanMapper.deleteBatchIds(ids) > 0;
    }
    /**
@@ -267,9 +319,23 @@
        searchParam.put("appType", aliDingConfig.getAppType());
        searchParam.put("systemToken", aliDingConfig.getSystemToken());
        searchParam.put("userId", aliDingConfig.getUserId());
        searchParam.put("formUuid", aliDingConfig.getFormUuid());
        searchParam.put("formUuid", aliDingConfig.getProducePlanFormUuid());
        searchParam.put("currentPage", pageNumber);
        searchParam.put("pageSize", pageSize);
        searchParam.put("pageNumber", pageNumber);
        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" æ˜¯å®˜æ–¹å†…置字段
@@ -319,7 +385,19 @@
                plan.setApplyNo(formData.getString("textField_l7fytfco"));
                plan.setCustomerName(formData.getString("textField_lbkozohg"));
                plan.setMaterialCode(row.getString("textField_l9xo62q5"));
                String materialCode = row.getString("textField_l9xo62q5");
                plan.setMaterialCode(materialCode);
                // æ ¹æ®ç‰©æ–™ç¼–码查询物料信息表,关联物料ID
                if (StringUtils.isNotEmpty(materialCode)) {
                    LambdaQueryWrapper<ProductMaterial> queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(ProductMaterial::getMaterialCode, materialCode);
                    ProductMaterial productMaterial = productMaterialService.getOne(queryWrapper);
                    if (productMaterial != null) {
                        plan.setProductMaterialId(productMaterial.getId());
                    }
                }
                plan.setProductName(row.getString("textField_l9xo62q7"));
                plan.setProductSpec(row.getString("textField_l9xo62q8"));
                plan.setLength(row.getInteger("numberField_lb7lgatg_value"));
src/main/resources/application-zsjc.yml
@@ -30,7 +30,8 @@
    app-secret: "fO07qSZC5SMLw9It3Ydd3BuoFyVbRlsWXUnVr2kwPJXz0OpUntCAO5dqnr8G7zq5"
    user-id: "290166234126410562"
    app-type: "APP_UY8XMO7GNA8OF08EFNVQ"
    form-uuid: "FORM-4IA66891C5H3QWMDBSGO05C0OX9628GRPYF7L8"
    produce-plan-form-uuid: "FORM-4IA66891C5H3QWMDBSGO05C0OX9628GRPYF7L8"
    material-code-form-uuid: "FORM-8I666N718RW4ALO4ETYDDB0U6U8T3EIWTQ0ALL1"
    system-token: "4J766L91OFH3V612E7LG5B4DI8M13MQF9VF7LG4"
    # æŽ¥å£åœ°å€
    access-token-url: "https://oapi.dingtalk.com/gettoken"
src/main/resources/mapper/production/ProductMaterialMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
<?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.ProductMaterialMapper">
    <resultMap id="ProductMaterialResultMap" type="com.ruoyi.production.pojo.ProductMaterial">
        <id property="id" column="id"/>
        <result property="tenantId" column="tenant_id"/>
        <result property="materialTypeId" column="material_type_id"/>
        <result property="inventoryCategoryId" column="inventory_category_id"/>
        <result property="identifierCode" column="identifier_code"/>
        <result property="materialCode" column="material_code"/>
        <result property="materialName" column="material_name"/>
        <result property="specification" column="specification"/>
        <result property="baseUnit" column="base_unit"/>
        <result property="materialAttribute" column="material_attribute"/>
        <result property="finishedProductName" column="finished_product_name"/>
        <result property="originatorName" column="originator_name"/>
        <result property="originatorOrg" column="originator_org"/>
        <result property="remark" column="remark"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>
</mapper>
src/main/resources/mapper/productionPlan/ProductOrderPlanMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
<?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.productionPlan.mapper.ProductOrderPlanMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.productionPlan.pojo.ProductOrderPlan">
        <id column="id" property="id" />
        <result column="product_order_id" property="productOrderId" />
        <result column="production_plan_id" property="productionPlanId" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
    </resultMap>
</mapper>
src/main/resources/mapper/productionPlan/ProductionPlanMapper.xml
@@ -12,6 +12,7 @@
        <result property="applyNo" column="apply_no"/>
        <result property="customerName" column="customer_name"/>
        <result property="materialCode" column="material_code"/>
        <result property="productMaterialId" column="product_material_id"/>
        <result property="productName" column="product_name"/>
        <result property="productSpec" column="product_spec"/>
        <result property="length" column="length"/>
@@ -54,6 +55,12 @@
        <if test="c.materialCode != null and c.materialCode != '' ">
            AND pp.material_code LIKE CONCAT('%',#{c.materialCode},'%')
        </if>
        <if test="c.startDate != null">
            AND pp.start_date &gt;= DATE_FORMAT(#{c.startDate},'%Y-%m-%d')
        </if>
        <if test="c.endDate != null">
            AND pp.end_date &lt;= DATE_FORMAT(#{c.endDate},'%Y-%m-%d')
        </if>
    </select>
    <select id="selectSummaryByProductType" resultType="com.ruoyi.productionPlan.dto.ProductionPlanSummaryDto">