cf7bc2d68e7d86e7d4b896acf1400f534d594daa..423ac2a5e7e451248d8cdfc2cda3f32dba0ec8f8
3 天以前 gongchunyi
feat: 生产计划关联物料信息表
423ac2 对比 | 目录
3 天以前 gongchunyi
Merge remote-tracking branch 'origin/dev_宁夏_中盛建材' into dev_宁夏_中盛建材
831163 对比 | 目录
3 天以前 gongchunyi
feat: 同步物料编码数据
2f58dd 对比 | 目录
3 天以前 huminmin
主生产计划:优化合并下发,根据剩余下发数量对比需要下发的数量
e52dfc 对比 | 目录
3 天以前 huminmin
主生产计划:计划完成时间改成date类型
587a27 对比 | 目录
3 天以前 huminmin
主生产计划:修改计划日期范围搜索
30f2e9 对比 | 目录
3 天以前 huminmin
Merge branch 'dev_宁夏_中盛建材' of http://114.132.189.42:9002/r/product-inventor...
18c956 对比 | 目录
3 天以前 huminmin
撤回生产订单
950dae 对比 | 目录
3 天以前 gongchunyi
fix: 携带查询参数,只拉取审批节点通过的销售生产需求数据
343ee3 对比 | 目录
3 天以前 huminmin
生成生产订单与生产计划关联表
2495a8 对比 | 目录
3 天以前 gongchunyi
fix: 日期区间格式问题
d0193c 对比 | 目录
3 天以前 huminmin
更新删除生产计划
924105 对比 | 目录
3 天以前 gongchunyi
fix: 模板下载修改为POST请求
8240a8 对比 | 目录
3 天以前 gongchunyi
feat: 生产计划数据的导入、导出、模板下载
d1439e 对比 | 目录
3 天以前 huminmin
修改生产订单列表查询
ec3375 对比 | 目录
3 天以前 zss
Merge remote-tracking branch 'origin/dev_宁夏_中盛建材' into dev_宁夏_中盛建材
beb668 对比 | 目录
3 天以前 zss
能耗管理的统计
8546df 对比 | 目录
3 天以前 huminmin
创建生产计划
4d853e 对比 | 目录
3 天以前 huminmin
合并下发生产计划分配每个计划的下发数量
c545c8 对比 | 目录
3 天以前 huminmin
Merge branch 'dev_宁夏_中盛建材' of http://114.132.189.42:9002/r/product-inventor...
931bea 对比 | 目录
3 天以前 huminmin
合并下发生产计划
adb589 对比 | 目录
3 天以前 gongchunyi
feat: 产品类别汇总统计
965cd9 对比 | 目录
3 天以前 gongchunyi
Merge remote-tracking branch 'origin/dev_宁夏_中盛建材' into dev_宁夏_中盛建材
8c8d87 对比 | 目录
3 天以前 gongchunyi
fix: 数据重复添加
7822ae 对比 | 目录
3 天以前 huminmin
add ApiModelProperty
03e067 对比 | 目录
已添加23个文件
已修改26个文件
2095 ■■■■■ 文件已修改
doc/宁夏-中盛建材.sql 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/controller/EnergyConsumptionDetailController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/controller/EnergyController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/dto/EnergyConsumptionDetailDto.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/dto/EnergyConsumptionTypeDto.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/dto/EnergyCostDto.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/dto/EnergyStatisticsDto.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/mapper/EnergyConsumptionDetailMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/service/EnergyConsumptionDetailService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/service/impl/EnergyConsumptionDetailServiceImpl.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/energy/vo/EnergyStatisticsVo.java 23 ●●●●● 补丁 | 查看 | 原始文档 | 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 32 ●●●●● 补丁 | 查看 | 原始文档 | 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 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/dto/ProductionPlanDto.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/dto/ProductionPlanImportDto.java 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/dto/ProductionPlanSummaryDto.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/enums/DataSourceTypeEnum.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/mapper/ProductOrderPlanMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/mapper/ProductionPlanMapper.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/pojo/ProductOrderPlan.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/pojo/ProductionPlan.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/ProductOrderPlanService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/ProductionPlanService.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/impl/ProductOrderPlanServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-zsjc.yml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/energy/EnergyConsumptionDetailMapper.xml 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductMaterialMapper.xml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductOrderMapper.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/productionPlan/ProductOrderPlanMapper.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/productionPlan/ProductionPlanMapper.xml 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/ÄþÏÄ-ÖÐÊ¢½¨²Ä.sql
@@ -82,3 +82,71 @@
    `update_user` int NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) COMMENT = '能源类型-能耗抄表明细_附件';
alter table product_order
drop column sales_ledger_id,
drop column sale_ledger_product_id,
drop column product_model_id;
alter table production_plan
    add assigned_quantity DECIMAL(10, 4) default 0 not null COMMENT '下发数量';
alter table product_order
    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/energy/controller/EnergyConsumptionDetailController.java
@@ -3,11 +3,9 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.energy.dto.EnergyConsumptionDetailDto;
import com.ruoyi.energy.dto.EnergyStatisticsDto;
import com.ruoyi.energy.pojo.Energy;
import com.ruoyi.energy.pojo.EnergyConsumptionDetail;
import com.ruoyi.energy.service.EnergyConsumptionDetailService;
import com.ruoyi.energy.service.EnergyService;
import com.ruoyi.energy.vo.EnergyStatisticsVo;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
@@ -56,11 +54,13 @@
    @Log(title = "能耗抄表明细-导入", businessType = BusinessType.IMPORT)
    @PostMapping("/importData")
    @ApiOperation("能耗抄表明细-导入")
    public R importData(MultipartFile file) throws Exception {
        return energyConsumptionDetailService.importData(file);
    }
    @PostMapping("/downloadTemplate")
    @ApiOperation("能耗抄表-下载模板")
    @Log(title = "能耗抄表-下载模板", businessType = BusinessType.EXPORT)
    public void downloadTemplate(HttpServletResponse response) {
        ExcelUtil<EnergyConsumptionDetailDto> util = new ExcelUtil<EnergyConsumptionDetailDto>(EnergyConsumptionDetailDto.class);
@@ -78,8 +78,8 @@
    @GetMapping("/statistics")
    @ApiOperation("按日月年汇总统计")
    public R statistics(EnergyStatisticsDto energyStatisticsDto) {
        return R.ok(energyConsumptionDetailService.statistics(energyStatisticsDto));
    public R statistics(EnergyStatisticsVo energyStatisticsVo) {
        return R.ok(energyConsumptionDetailService.statistics(energyStatisticsVo));
    }
}
src/main/java/com/ruoyi/energy/controller/EnergyController.java
@@ -57,11 +57,13 @@
    @Log(title = "能源类型-导入", businessType = BusinessType.IMPORT)
    @PostMapping("/importData")
    @ApiOperation("能源类型-导入")
    public R importData(MultipartFile file) throws Exception {
        return energyService.importData(file);
    }
    @PostMapping("/downloadTemplate")
    @ApiOperation("能源类型-下载模板")
    @Log(title = "能源类型-下载模板", businessType = BusinessType.EXPORT)
    public void downloadTemplate(HttpServletResponse response) {
        ExcelUtil<Energy> util = new ExcelUtil<Energy>(Energy.class);
src/main/java/com/ruoyi/energy/dto/EnergyConsumptionDetailDto.java
@@ -7,6 +7,8 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
//能耗抄表明细
@Data
@ExcelIgnoreUnannotated
@@ -26,4 +28,8 @@
    //创建人
    private String createUserName;
    //费用
    private BigDecimal cost;
}
src/main/java/com/ruoyi/energy/dto/EnergyConsumptionTypeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.energy.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@ApiModel("能耗类型占比")
public class EnergyConsumptionTypeDto {
    @ApiModelProperty("能源类型")
    private String energyTyep;
    @ApiModelProperty("能耗用量")
    private BigDecimal energyConsumption;
    @ApiModelProperty("能耗费用")
    private BigDecimal energyCost;
}
src/main/java/com/ruoyi/energy/dto/EnergyCostDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.ruoyi.energy.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.checkerframework.checker.units.qual.A;
import java.math.BigDecimal;
import java.time.LocalDate;
//能耗费用明细
@Data
@ApiModel("能耗费用明细")
public class EnergyCostDto {
    @ApiModelProperty("日期")
    private LocalDate meterReadingDate;
    @ApiModelProperty("用水量")
    private BigDecimal waterConsumption;
    @ApiModelProperty("æ°´è´¹")
    private BigDecimal waterCost;
    @ApiModelProperty("用电量")
    private BigDecimal electricityConsumption;
    @ApiModelProperty("电费")
    private BigDecimal electricityCost;
    @ApiModelProperty("用气量")
    private BigDecimal gasConsumption;
    @ApiModelProperty("气费")
    private BigDecimal gasCost;
    @ApiModelProperty("合计用量")
    private BigDecimal totalConsumption;
    @ApiModelProperty("合计费用")
    private BigDecimal totalCost;
}
src/main/java/com/ruoyi/energy/dto/EnergyStatisticsDto.java
@@ -1,20 +1,36 @@
package com.ruoyi.energy.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
//按日月年汇总统计能耗的传参
@Data
@ApiModel("按日月年汇总统计的能耗数据")
public class EnergyStatisticsDto {
    //开始日期
    private LocalDate startDate;
    @ApiModelProperty("总耗用量")
    private BigDecimal totalEnergyConsumption;
    //结束日期
    private LocalDate endDate;
    @ApiModelProperty("总能耗费用")
    private BigDecimal totalEnergyCost;
    //能耗场景(办公/生产)
    private String type;
    @ApiModelProperty("平均用量")
    private BigDecimal averageConsumption;
    @ApiModelProperty("环比变化")
    private BigDecimal changeVite;
    @ApiModelProperty("能耗类型占比")
    private List<EnergyConsumptionTypeDto> energyConsumptionTypeProportion;
    @ApiModelProperty("能耗明细")
    private List<EnergyCostDto> energyCostDtos;
}
src/main/java/com/ruoyi/energy/mapper/EnergyConsumptionDetailMapper.java
@@ -3,10 +3,17 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.energy.dto.EnergyConsumptionDetailDto;
import com.ruoyi.energy.dto.EnergyConsumptionTypeDto;
import com.ruoyi.energy.dto.EnergyCostDto;
import com.ruoyi.energy.pojo.EnergyConsumptionDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.energy.vo.EnergyStatisticsVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
 * <p>
@@ -20,4 +27,10 @@
public interface EnergyConsumptionDetailMapper extends BaseMapper<EnergyConsumptionDetail> {
    IPage<EnergyConsumptionDetailDto> pageEnergyConsumptionDetail(Page<EnergyConsumptionDetailDto> page, @Param("c") EnergyConsumptionDetailDto energyConsumptionDetailDto);
    Map<String, BigDecimal> calculateEnergy(@Param("c") EnergyStatisticsVo energyStatisticsVo);
    List<EnergyConsumptionTypeDto> energyConsumptionTypeProportion(@Param("c") EnergyStatisticsVo energyStatisticsVo);
    List<EnergyCostDto> energyCostDtos(@Param("c") EnergyStatisticsVo energyStatisticsVo);
}
src/main/java/com/ruoyi/energy/service/EnergyConsumptionDetailService.java
@@ -6,6 +6,7 @@
import com.ruoyi.energy.dto.EnergyStatisticsDto;
import com.ruoyi.energy.pojo.EnergyConsumptionDetail;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.energy.vo.EnergyStatisticsVo;
import com.ruoyi.framework.web.domain.R;
import org.springframework.web.multipart.MultipartFile;
@@ -27,6 +28,6 @@
    void export(HttpServletResponse response);
    Object statistics(EnergyStatisticsDto energyStatisticsDto);
    EnergyStatisticsDto statistics(EnergyStatisticsVo energyStatisticsVo);
}
src/main/java/com/ruoyi/energy/service/impl/EnergyConsumptionDetailServiceImpl.java
@@ -7,6 +7,8 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.energy.dto.EnergyConsumptionDetailDto;
import com.ruoyi.energy.dto.EnergyConsumptionTypeDto;
import com.ruoyi.energy.dto.EnergyCostDto;
import com.ruoyi.energy.dto.EnergyStatisticsDto;
import com.ruoyi.energy.mapper.EnergyMapper;
import com.ruoyi.energy.pojo.Energy;
@@ -14,6 +16,7 @@
import com.ruoyi.energy.mapper.EnergyConsumptionDetailMapper;
import com.ruoyi.energy.service.EnergyConsumptionDetailService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.energy.vo.EnergyStatisticsVo;
import com.ruoyi.framework.web.domain.R;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,7 +24,11 @@
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -91,7 +98,32 @@
    }
    @Override
    public Object statistics(EnergyStatisticsDto energyStatisticsDto) {
        return null;
    public EnergyStatisticsDto statistics(EnergyStatisticsVo energyStatisticsVo) {
        EnergyStatisticsDto energyStatisticsDto = new EnergyStatisticsDto();
        //计算总耗用量+总能耗费用
        Map<String, BigDecimal> map=energyConsumptionDetailMapper.calculateEnergy(energyStatisticsVo);
        energyStatisticsDto.setTotalEnergyConsumption(map.get("totalEnergyConsumption"));
        energyStatisticsDto.setTotalEnergyCost(map.get("totalEnergyCost"));
        //平均用量
        energyStatisticsDto.setAverageConsumption(energyStatisticsDto.getTotalEnergyConsumption().divide(new BigDecimal(energyStatisticsVo.getDays()),2, RoundingMode.HALF_UP));
        //环比变化=(本期值 - ä¸ŠæœŸå€¼) / ä¸ŠæœŸå€¼ * 100
        LocalDate minDays = energyStatisticsVo.getStartDate().minusDays(energyStatisticsVo.getDays());
        LocalDate masDays = energyStatisticsVo.getEndDate().minusDays(energyStatisticsVo.getDays());
        EnergyStatisticsVo oldenergyStatisticsVo = new EnergyStatisticsVo();
        BeanUtils.copyProperties(energyStatisticsVo,oldenergyStatisticsVo);
        oldenergyStatisticsVo.setStartDate(minDays);
        oldenergyStatisticsVo.setEndDate(masDays);
        Map<String, BigDecimal> oldmap=energyConsumptionDetailMapper.calculateEnergy(oldenergyStatisticsVo);
        if (ObjectUtils.isNotEmpty(oldmap)) {
            BigDecimal changeVite = (map.get("totalEnergyConsumption").subtract(oldmap.get("totalEnergyConsumption"))).divide(oldmap.get("totalEnergyConsumption"), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
            energyStatisticsDto.setChangeVite(changeVite);
        }
        //能耗类型占比
        List<EnergyConsumptionTypeDto> energyConsumptionTypeDtos=energyConsumptionDetailMapper.energyConsumptionTypeProportion(energyStatisticsVo);
        energyStatisticsDto.setEnergyConsumptionTypeProportion(energyConsumptionTypeDtos);
        //能耗明细
        List<EnergyCostDto> energyCostDtos=energyConsumptionDetailMapper.energyCostDtos(energyStatisticsVo);
        energyStatisticsDto.setEnergyCostDtos(energyCostDtos);
        return energyStatisticsDto;
    }
}
src/main/java/com/ruoyi/energy/vo/EnergyStatisticsVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.ruoyi.energy.vo;
import lombok.Data;
import java.time.LocalDate;
//按日月年汇总统计能耗的传参
@Data
public class EnergyStatisticsVo {
    //开始日期
    private LocalDate startDate;
    //结束日期
    private LocalDate endDate;
    //查询间隔天数
    private Long days;
    //能耗场景(办公/生产)
    private String type;
}
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,7 +10,9 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("product_order")
@@ -20,24 +22,6 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * é”€å”®å°è´¦id
     */
    @ApiModelProperty(value = "销售台账id")
    private Long salesLedgerId;
    /**
     * é”€å”®å°è´¦äº§å“id(sales_ledger_product)
     */
    @ApiModelProperty(value = "销售台账产品id")
    private Long saleLedgerProductId;
    /**
     * äº§å“è§„æ ¼id
     */
    @ApiModelProperty(value = "产品规格id")
    private Long productModelId;
    /**
     * æ¨¡ç‰ˆçš„工艺路线id
@@ -51,6 +35,14 @@
    @ApiModelProperty(value = "生产订单号")
    @Excel(name = "生产订单号")
    private String npsNo;
    /**
     * è®¡åˆ’完成时间
     */
    @ApiModelProperty(value = "计划完成时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate planCompleteTime;
    /**
     * ç§Ÿæˆ·id
@@ -73,7 +65,6 @@
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    /**
     * éœ€æ±‚数量
@@ -100,7 +91,4 @@
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime endTime;
}
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
@@ -1,18 +1,22 @@
package com.ruoyi.productionPlan.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.productionPlan.dto.ProductionPlanDto;
import com.ruoyi.productionPlan.dto.ProductionPlanImportDto;
import com.ruoyi.productionPlan.dto.ProductionPlanSummaryDto;
import com.ruoyi.productionPlan.service.ProductionPlanService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <br>
@@ -38,9 +42,70 @@
    }
    @GetMapping("/loadProdData")
    @ApiOperation("拉取销售生产计划")
    @Log(title = "拉取销售生产计划", businessType = BusinessType.INSERT)
    public AjaxResult loadProdData() {
        productionPlanService.loadProdData();
        return AjaxResult.success();
    }
    @PostMapping("/combine")
    @Log(title = "合并生产计划", businessType = BusinessType.INSERT)
    @ApiOperation("合并生产计划")
    public AjaxResult combine(@RequestBody ProductionPlanDto productionPlanDto) {
        return AjaxResult.success(productionPlanService.combine(productionPlanDto));
    }
    @PostMapping("")
    @Log(title = "创建生产计划", businessType = BusinessType.INSERT)
    @ApiOperation("创建生产计划")
    public AjaxResult add(@RequestBody ProductionPlanDto productionPlanDto) {
        return AjaxResult.success(productionPlanService.add(productionPlanDto));
    }
    @PutMapping("")
    @Log(title = "更新生产计划", businessType = BusinessType.UPDATE)
    @ApiOperation("更新生产计划")
    public AjaxResult update(@RequestBody ProductionPlanDto productionPlanDto) {
        return AjaxResult.success(productionPlanService.update(productionPlanDto));
    }
    @DeleteMapping("")
    @Log(title = "删除生产计划", businessType = BusinessType.DELETE)
    @ApiOperation("删除生产计划")
    public AjaxResult delete(@RequestBody List<Long> ids) {
        return AjaxResult.success(productionPlanService.removeByIds(ids));
    }
    @GetMapping("/summaryByProductType")
    @ApiOperation("按照产品类别汇总统计需求量")
    @Log(title = "按照产品类别汇总统计需求量", businessType = BusinessType.OTHER)
    public AjaxResult summaryByProductType(ProductionPlanSummaryDto query) {
        List<ProductionPlanSummaryDto> list = productionPlanService.summaryByProductType(query);
        return AjaxResult.success(list);
    }
    @PostMapping("/downloadTemplate")
    @Log(title = "下载主生产计划导入模板", businessType = BusinessType.EXPORT)
    @ApiOperation("下载主生产计划导入模板")
    public void importTemplate(HttpServletResponse response) {
        ExcelUtil<ProductionPlanImportDto> excelUtil = new ExcelUtil<>(ProductionPlanImportDto.class);
        excelUtil.importTemplateExcel(response, "主生产计划导入模板");
    }
    @PostMapping("/import")
    @ApiOperation("主生产计划数据导入")
    @Log(title = "主生产计划数据导入", businessType = BusinessType.IMPORT)
    public AjaxResult importProdData(@RequestParam("file") MultipartFile file) {
        productionPlanService.importProdData(file);
        return AjaxResult.success("导入成功");
    }
    @PostMapping("/export")
    @ApiOperation("主生产计划数据导出")
    @Log(title = "主生产计划数据导出", businessType = BusinessType.EXPORT)
    public void exportProdData(HttpServletResponse response, @RequestBody(required = false) List<Long> ids) {
        productionPlanService.exportProdData(response, ids);
    }
}
src/main/java/com/ruoyi/productionPlan/dto/ProductionPlanDto.java
@@ -1,6 +1,26 @@
package com.ruoyi.productionPlan.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.productionPlan.pojo.ProductionPlan;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
@Data
public class ProductionPlanDto extends ProductionPlan {
    @ApiModelProperty(value = "生产计划id集合")
    private List<Long> ids;
    @ApiModelProperty(value = "下发数量")
    private BigDecimal totalAssignedQuantity;
    @ApiModelProperty(value = "计划完成时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate planCompleteTime;
}
src/main/java/com/ruoyi/productionPlan/dto/ProductionPlanImportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,177 @@
package com.ruoyi.productionPlan.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
 * <br>
 * é”€å”®ç”Ÿäº§éœ€æ±‚ Excel导入导出DTO
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 11:28
 */
@Data
@ApiModel("销售生产需求 Excel导入导出DTO")
public class ProductionPlanImportDto {
    /**
     * ç”³è¯·å•编号
     */
    @ApiModelProperty("申请单编号")
    @Excel(name = "申请单编号")
    private String applyNo;
    /**
     * å®¢æˆ·åç§°
     */
    @ApiModelProperty("客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    /**
     * ç‰©æ–™ç¼–码
     */
    @ApiModelProperty("物料编码")
    @Excel(name = "物料编码")
    private String materialCode;
    /**
     * äº§å“åç§°
     */
    @ApiModelProperty("产品名称")
    @Excel(name = "产品名称")
    private String productName;
    /**
     * äº§å“è§„æ ¼
     */
    @ApiModelProperty("产品规格")
    @Excel(name = "产品规格")
    private String productSpec;
    /**
     * é•¿
     */
    @ApiModelProperty("长")
    @Excel(name = "长")
    private Integer length;
    /**
     * å®½
     */
    @ApiModelProperty("宽")
    @Excel(name = "宽")
    private Integer width;
    /**
     * é«˜
     */
    @ApiModelProperty("高")
    @Excel(name = "高")
    private Integer height;
    /**
     * å—æ•°
     */
    @ApiModelProperty("块数")
    @Excel(name = "块数")
    private Integer quantity;
    /**
     * æ–¹æ•°
     */
    @ApiModelProperty("方数")
    @Excel(name = "方数")
    private BigDecimal volume;
    /**
     * å¼ºåº¦
     */
    @ApiModelProperty("强度")
    @Excel(name = "强度")
    private String strength;
    /**
     * å¼€å§‹æ—¥æœŸ
     */
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @ApiModelProperty("开始日期")
    @Excel(name = "开始日期", width = 20, dateFormat = "yyyy-MM-dd")
    private Date startDate;
    /**
     * ç»“束日期
     */
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @ApiModelProperty("结束日期")
    @Excel(name = "结束日期", width = 20, dateFormat = "yyyy-MM-dd")
    private Date endDate;
    /**
     * æäº¤äºº
     */
    @ApiModelProperty("提交人")
    @Excel(name = "提交人")
    private String submitter;
    /**
     * æäº¤äººç»„织
     */
    @ApiModelProperty("提交人组织")
    @Excel(name = "提交人组织")
    private String submitOrg;
    /**
     * å¤‡æ³¨1
     */
    @ApiModelProperty("备注1")
    @Excel(name = "备注1")
    private String remarkOne;
    /**
     * å¤‡æ³¨2
     */
    @ApiModelProperty("备注2")
    @Excel(name = "备注2")
    private String remarkTwo;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @Excel(name = "创建人", type = Excel.Type.EXPORT)
    private String creatorName;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @Excel(name = "修改人", type = Excel.Type.EXPORT)
    private String modifierName;
    /**
     * æ•°æ®åŒæ­¥ç±»åž‹ï¼š1=手动 2=定时任务
     */
    @ApiModelProperty("数据同步类型:1=手动 2=定时任务")
    private Integer dataSyncType;
    /**
     * æ•°æ®æ¥æºç±»åž‹ï¼š1=同步 2=新增
     */
    @ApiModelProperty("数据来源类型:1=同步 2=新增")
    private Integer dataSourceType;
    /**
     * ä¸‹å‘数量
     */
    @ApiModelProperty("下发数量")
    @Excel(name = "下发数量", type = Excel.Type.EXPORT)
    private BigDecimal assignedQuantity;
}
src/main/java/com/ruoyi/productionPlan/dto/ProductionPlanSummaryDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package com.ruoyi.productionPlan.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
 * <br>
 * é”€å”®ç”Ÿäº§éœ€æ±‚ - äº§å“ç±»åž‹æ±‡æ€»DTO
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/11 10:24
 */
@Data
@ApiModel("生产需求产品汇总")
public class ProductionPlanSummaryDto {
    /**
     * ç‰©æ–™ç¼–码
     */
    @ApiModelProperty("物料编码")
    private String materialCode;
    /**
     * äº§å“åç§°
     */
    @ApiModelProperty("产品名称")
    private String productName;
    /**
     * äº§å“è§„æ ¼
     */
    @ApiModelProperty("产品规格")
    private String productSpec;
    /**
     * äº§å“é•¿åº¦
     */
    @ApiModelProperty("产品长度")
    private Integer length;
    /**
     * äº§å“å®½åº¦
     */
    @ApiModelProperty("产品宽度")
    private Integer width;
    /**
     * äº§å“é«˜åº¦
     */
    @ApiModelProperty("产品高度")
    private Integer height;
    /**
     * æ±‡æ€»å—æ•°
     */
    @ApiModelProperty("汇总块数")
    private Integer quantity;
    /**
     * æ±‡æ€»æ–¹æ•°
     */
    @ApiModelProperty("汇总方数")
    private BigDecimal volume;
}
src/main/java/com/ruoyi/productionPlan/enums/DataSourceTypeEnum.java
@@ -14,8 +14,8 @@
@Getter
public enum DataSourceTypeEnum {
    SALES_ORDER(1, "销售订单"),
    PRODUCTION_FORECAST(2, "生产预测");
    SALES_ORDER(1, "同步"),
    PRODUCTION_FORECAST(2, "新增");
    private final Integer code;
    private final String desc;
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/mapper/ProductionPlanMapper.java
@@ -4,9 +4,11 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.productionPlan.dto.ProductionPlanDto;
import com.ruoyi.productionPlan.dto.ProductionPlanSummaryDto;
import com.ruoyi.productionPlan.pojo.ProductionPlan;
import com.ruoyi.staff.dto.StaffLeaveDto;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <br>
@@ -19,4 +21,7 @@
 */
public interface ProductionPlanMapper extends BaseMapper<ProductionPlan> {
    IPage<ProductionPlanDto> listPage(Page page, @Param("c") ProductionPlanDto productionPlanDto);
    List<ProductionPlanSummaryDto> selectSummaryByProductType(ProductionPlanSummaryDto query);
}
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
@@ -4,10 +4,14 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
/**
 * <br>
@@ -31,146 +35,222 @@
    /**
     * è¡¨å•实例ID
     */
    @ApiModelProperty("表单实例ID")
    private String formInstanceId;
    /**
     * æµæ°´å·
     */
    @ApiModelProperty("流水号")
    private String serialNo;
    /**
     * ç”³è¯·å•编号
     */
    @ApiModelProperty("申请单编号")
    @Excel(name = "申请单编号")
    private String applyNo;
    /**
     * å®¢æˆ·åç§°
     */
    @ApiModelProperty("客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    /**
     * ç‰©æ–™ç¼–码
     */
    @ApiModelProperty("物料编码")
    @Excel(name = "物料编码")
    private String materialCode;
    /**
     * å…³è”物料信息表ID
     */
    @ApiModelProperty("关联物料信息表ID")
    private Integer productMaterialId;
    /**
     * äº§å“åç§°
     */
    @ApiModelProperty("产品名称")
    @Excel(name = "产品名称")
    private String productName;
    /**
     * äº§å“è§„æ ¼
     */
    @ApiModelProperty("产品规格")
    @Excel(name = "产品规格")
    private String productSpec;
    /**
     * é•¿
     */
    @ApiModelProperty("长")
    @Excel(name = "长")
    private Integer length;
    /**
     * å®½
     */
    @ApiModelProperty("宽")
    @Excel(name = "宽")
    private Integer width;
    /**
     * é«˜
     */
    @ApiModelProperty("高")
    @Excel(name = "高")
    private Integer height;
    /**
     * å—æ•°
     */
    @ApiModelProperty("块数")
    @Excel(name = "块数")
    private Integer quantity;
    /**
     * æ–¹æ•°
     */
    @ApiModelProperty("方数")
    @Excel(name = "方数")
    private BigDecimal volume;
    /**
     * å¼ºåº¦
     */
    @ApiModelProperty("强度")
    @Excel(name = "强度")
    private String strength;
    /**
     * å¼€å§‹æ—¥æœŸ
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime startDate;
    @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;
    /**
     * ç»“束日期
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime endDate;
    @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;
    /**
     * æäº¤äºº
     */
    @ApiModelProperty("提交人")
    @Excel(name = "提交人")
    private String submitter;
    /**
     * æäº¤äººç»„织
     */
    @ApiModelProperty("提交人组织")
    @Excel(name = "提交人组织")
    private String submitOrg;
    /**
     * å¤‡æ³¨1
     */
    @ApiModelProperty("备注1")
    @Excel(name = "备注1")
    private String remarkOne;
    /**
     * å¤‡æ³¨2
     */
    @ApiModelProperty("备注2")
    @Excel(name = "备注2")
    private String remarkTwo;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @Excel(name = "创建人")
    private String creatorName;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @Excel(name = "修改人")
    private String modifierName;
    /**
     * è¡¨å•创建时间
     */
    @ApiModelProperty("表单创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime formCreatedTime;
    /**
     * è¡¨å•修改时间
     */
    @ApiModelProperty("表单修改时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime formModifiedTime;
    /**
     * æ•°æ®åŒæ­¥ç±»åž‹ï¼š1=手动 2=定时任务
     */
    @ApiModelProperty("数据同步类型:1=手动 2=定时任务")
    private Integer dataSyncType;
    /**
     * æ•°æ®æ¥æºç±»åž‹ï¼š1=销售订单 2=销售预测
     * æ•°æ®æ¥æºç±»åž‹ï¼š1=同步 2=新增
     */
    @ApiModelProperty("数据来源类型:1=同步 2=新增")
    private Integer dataSourceType;
    /**
     * æ•°æ®åº“创建时间
     */
    @ApiModelProperty("数据库创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * æ•°æ®åº“æ›´æ–°æ—¶é—´
     */
    @ApiModelProperty("数据库更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * å½“前更新数量
     */
    @ApiModelProperty("totalCount")
    private Integer totalCount;
    @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
@@ -4,7 +4,12 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.productionPlan.dto.ProductionPlanDto;
import com.ruoyi.productionPlan.dto.ProductionPlanSummaryDto;
import com.ruoyi.productionPlan.pojo.ProductionPlan;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <br>
@@ -27,4 +32,39 @@
     * å®šæ—¶åŒæ­¥
     */
    void syncProdDataJob();
    /**
     * åˆå¹¶ç”Ÿäº§è®¡åˆ’
     */
    boolean combine(ProductionPlanDto productionPlanDto);
    /**
     * åˆ›å»ºç”Ÿäº§è®¡åˆ’
     */
    boolean add(ProductionPlanDto productionPlanDto);
    /**
     * æ›´æ–°ç”Ÿäº§è®¡åˆ’
     */
    boolean update(ProductionPlanDto productionPlanDto);
    /**
     * åˆ é™¤ç”Ÿäº§è®¡åˆ’
     */
    boolean delete(List<Long> ids);
    /**
     * æŒ‰ç…§äº§å“ç±»åˆ«æ±‡æ€»ç»Ÿè®¡éœ€æ±‚量
     */
    List<ProductionPlanSummaryDto> summaryByProductType(ProductionPlanSummaryDto query);
    /**
     * å¯¼å…¥æ•°æ®
     */
    void importProdData(MultipartFile file);
    /**
     * å¯¼å‡ºæ•°æ®
     */
    void exportProdData(HttpServletResponse response, List<Long> ids);
}
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,30 +5,45 @@
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import static com.ruoyi.productionPlan.enums.DataSourceTypeEnum.PRODUCTION_FORECAST;
/**
 * <br>
@@ -49,6 +64,15 @@
    @Autowired
    private ProductionPlanMapper productionPlanMapper;
    @Autowired
    private ProductOrderService productOrderService;
    @Autowired
    private ProductOrderPlanMapper productOrderPlanMapper;
    @Autowired
    private ProductMaterialService productMaterialService;
    /**
     * åŒæ­¥é”ï¼Œç¡®ä¿æ‰‹åŠ¨å’Œå®šæ—¶ä»»åŠ¡ä¸åŒæ—¶æ‰§è¡Œ
@@ -74,6 +98,123 @@
    @Override
    public void syncProdDataJob() {
        syncProdData(2);
    }
    /**
     * åˆå¹¶ç”Ÿäº§è®¡åˆ’
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean combine(ProductionPlanDto productionPlanDto) {
        if (productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) {
            return false;
        }
        //  æŸ¥è¯¢ä¸»ç”Ÿäº§è®¡åˆ’
        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))) {
            throw new BaseException("合并失败,存在不同的产品名称");
        }
        // æ ¡éªŒæ˜¯å¦å­˜åœ¨ä¸åŒçš„产品规格
        String firstProductSpec = plans.get(0).getProductSpec();
        if (plans.stream().anyMatch(p -> !p.getProductSpec().equals(firstProductSpec))) {
            throw new BaseException("合并失败,存在不同的产品规格");
        }
        // å åŠ å‰©ä½™æ–¹æ•°
        BigDecimal totalRemainingVolume = plans.stream()
                .map(ProductionPlan::getRemainingVolume)
                .filter(v -> v != null)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // åˆ¤æ–­ä¸‹å‘数量是否大于等于剩余方数
        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;
        for (ProductionPlan plan : plans) {
            BigDecimal volume = plan.getVolume();
            if (volume == null) {
                continue;
            }
            // è®¡ç®—剩余方数
            BigDecimal remainingVolume = plan.getRemainingVolume();
            if (remainingVolume.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            ProductOrderPlan productOrderPlan = new ProductOrderPlan();
            productOrderPlan.setProductOrderId(productOrder.getId());
            productOrderPlan.setProductionPlanId(plan.getId());
            if (assignedVolume.add(remainingVolume).compareTo(productionPlanDto.getTotalAssignedQuantity()) >= 0) {
                // æœ€åŽä¸€ä¸ªè®¡åˆ’,分配剩余方数
                BigDecimal lastRemainingVolume = productionPlanDto.getTotalAssignedQuantity().subtract(assignedVolume);
                plan.setAssignedQuantity(plan.getAssignedQuantity().add(lastRemainingVolume));
                productOrderPlan.setAssignedQuantity(lastRemainingVolume);
                productionPlanMapper.updateById(plan);
                productOrderPlanMapper.insert(productOrderPlan);
                break;
            }
            // åˆ†é…å½“前计划方数
            plan.setAssignedQuantity(plan.getAssignedQuantity().add(remainingVolume));
            productOrderPlan.setAssignedQuantity(remainingVolume);
            // æ›´æ–°ç”Ÿäº§è®¡åˆ’
            productionPlanMapper.updateById(plan);
            // åˆ›å»ºå…³è”关系
            productOrderPlanMapper.insert(productOrderPlan);
            assignedVolume = assignedVolume.add(remainingVolume);
        }
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean add(ProductionPlanDto productionPlanDto) {
        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;
    }
    /**
@@ -133,8 +274,8 @@
                List<ProductionPlan> list = parseProductionPlans(dataArr, dataSyncType, totalCount);
                if (!list.isEmpty()) {
                    //  å¤„理更新或新增
                    processSaveOrUpdate(list);
                    totalSynced += list.size();
                    int affected = processSaveOrUpdate(list);
                    totalSynced += affected;
                }
                //  åˆ¤æ–­æ˜¯å¦è¿˜æœ‰ä¸‹ä¸€é¡µ
@@ -178,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" æ˜¯å®˜æ–¹å†…置字段
@@ -230,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"));
@@ -245,8 +412,20 @@
                    try {
                        long start = Long.parseLong(dateArr.getString(0));
                        long end = Long.parseLong(dateArr.getString(1));
                        plan.setStartDate(Instant.ofEpochMilli(start).atZone(ZoneId.systemDefault()).toLocalDateTime());
                        plan.setEndDate(Instant.ofEpochMilli(end).atZone(ZoneId.systemDefault()).toLocalDateTime());
                        Date startDate = Date.from(Instant.ofEpochMilli(start)
                                .atZone(ZoneId.systemDefault())
                                .toLocalDate()
                                .atStartOfDay(ZoneId.systemDefault())
                                .toInstant());
                        Date endDate = Date.from(Instant.ofEpochMilli(end)
                                .atZone(ZoneId.systemDefault())
                                .toLocalDate()
                                .atStartOfDay(ZoneId.systemDefault())
                                .toInstant());
                        plan.setStartDate(startDate);
                        plan.setEndDate(endDate);
                    } catch (Exception e) {
                        log.warn("解析日期失败: {}", dateArr);
                    }
@@ -277,19 +456,48 @@
        return list;
    }
    private void processSaveOrUpdate(List<ProductionPlan> list) {
    private int processSaveOrUpdate(List<ProductionPlan> list) {
        if (list == null || list.isEmpty()) {
            return 0;
        }
        int affected = 0;
        //  åŽ»é‡ formInstanceId
        Set<String> formIds = list.stream()
                .map(ProductionPlan::getFormInstanceId)
                .collect(Collectors.toSet());
        //  æŸ¥è¯¢æ•°æ®åº“已有数据
        List<ProductionPlan> existList = this.list(new LambdaQueryWrapper<ProductionPlan>().in(ProductionPlan::getFormInstanceId, formIds));
        //  Map (formInstanceId + materialCode)
        Map<String, ProductionPlan> existMap = new HashMap<>();
        for (ProductionPlan p : existList) {
            String key = p.getFormInstanceId() + "_" + p.getMaterialCode();
            existMap.put(key, p);
        }
        //  éåŽ†åŒæ­¥æ•°æ®
        for (ProductionPlan plan : list) {
            LambdaQueryWrapper<ProductionPlan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(ProductionPlan::getFormInstanceId, plan.getFormInstanceId())
                    .eq(ProductionPlan::getMaterialCode, plan.getMaterialCode());
            ProductionPlan existing = this.getOne(queryWrapper);
            if (existing != null) {
                plan.setId(existing.getId());
                this.updateById(plan);
            } else {
            String key = plan.getFormInstanceId() + "_" + plan.getMaterialCode();
            ProductionPlan exist = existMap.get(key);
            if (exist == null) {
                // æ–°å¢ž
                this.save(plan);
                affected++;
                log.info("新增数据 formInstanceId={}, materialCode={}", plan.getFormInstanceId(), plan.getMaterialCode());
            } else {
                // åˆ¤æ–­æ˜¯å¦éœ€è¦æ›´æ–°
                if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(plan.getFormModifiedTime())) {
                    plan.setId(exist.getId());
                    plan.setCreateTime(exist.getCreateTime());
                    this.updateById(plan);
                    affected++;
                    log.info("更新数据 formInstanceId={}, materialCode={}", plan.getFormInstanceId(), plan.getMaterialCode());
                }
            }
        }
        return affected;
    }
    private LocalDateTime parseUtcTime(String utcString) {
@@ -304,4 +512,62 @@
            return null;
        }
    }
    @Override
    public List<ProductionPlanSummaryDto> summaryByProductType(ProductionPlanSummaryDto query) {
        return baseMapper.selectSummaryByProductType(query);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importProdData(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ServiceException("导入数据不能为空");
        }
        ExcelUtil<ProductionPlanImportDto> excelUtil = new ExcelUtil<>(ProductionPlanImportDto.class);
        List<ProductionPlanImportDto> list;
        try {
            list = excelUtil.importExcel(file.getInputStream());
        } catch (Exception e) {
            log.error("生产需求Excel导入失败", e);
            throw new ServiceException("Excel解析失败");
        }
        if (list == null || list.isEmpty()) {
            throw new ServiceException("Excel没有数据");
        }
        List<ProductionPlan> entityList = new ArrayList<>(list.size());
        ProductionPlan entity;
        for (ProductionPlanImportDto dto : list) {
            entity = new ProductionPlan();
            BeanUtils.copyProperties(dto, entity);
            entity.setAssignedQuantity(BigDecimal.ZERO);
            entity.setCreateTime(LocalDateTime.now());
            entity.setUpdateTime(LocalDateTime.now());
            entity.setDataSourceType(2);
            entity.setDataSyncType(1);
            entityList.add(entity);
        }
        this.saveBatch(entityList);
    }
    @Override
    public void exportProdData(HttpServletResponse response, List<Long> ids) {
        List<ProductionPlan> list;
        if (ids != null && !ids.isEmpty()) {
            list = baseMapper.selectBatchIds(ids);
        } else {
            list = baseMapper.selectList(null);
        }
        List<ProductionPlanImportDto> exportList = new ArrayList<>();
        for (ProductionPlan entity : list) {
            ProductionPlanImportDto dto = new ProductionPlanImportDto();
            BeanUtils.copyProperties(entity, dto);
            exportList.add(dto);
        }
        ExcelUtil<ProductionPlanImportDto> util = new ExcelUtil<>(ProductionPlanImportDto.class);
        util.exportExcel(response, exportList, "销售生产需求数据");
    }
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -267,9 +267,9 @@
     */
    public void addProductionData(SalesLedgerProduct salesLedgerProduct) {
        ProductOrder productOrder = new ProductOrder();
        productOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId());
        productOrder.setProductModelId(salesLedgerProduct.getProductModelId());
        productOrder.setSaleLedgerProductId(salesLedgerProduct.getId());
//        productOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId());
//        productOrder.setProductModelId(salesLedgerProduct.getProductModelId());
//        productOrder.setSaleLedgerProductId(salesLedgerProduct.getId());
        String string = productOrderServiceImpl.generateNextOrderNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        productOrder.setNpsNo(string);
        productOrder.setQuantity(salesLedgerProduct.getQuantity());//需求数量
@@ -340,7 +340,7 @@
        //批量查询productOrder
        List<ProductOrder> productOrders = productOrderMapper.selectList(
                new LambdaQueryWrapper<ProductOrder>()
                        .in(ProductOrder::getSaleLedgerProductId, productIds)
//                        .in(ProductOrder::getSaleLedgerProductId, productIds)
        );
        if (!org.springframework.util.CollectionUtils.isEmpty(productOrders)) {
            List<Long> orderIds = productOrders.stream()
@@ -421,8 +421,8 @@
                    .in(ProductProcessRoute::getProductOrderId, orderIds));
            // æ‰¹é‡åˆ é™¤productOrder
            productOrderMapper.delete(new LambdaQueryWrapper<ProductOrder>()
                    .in(ProductOrder::getSaleLedgerProductId, productIds));
//            productOrderMapper.delete(new LambdaQueryWrapper<ProductOrder>()
//                    .in(ProductOrder::getSaleLedgerProductId, productIds));
        }
    }
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/energy/EnergyConsumptionDetailMapper.xml
@@ -26,16 +26,92 @@
                       left join energy e on ecd.energy_id = e.id
                       left join sys_user su on ecd.create_user = su.user_id) A
         <where>
            <if test="c.energyTyep != null and c.energyTyep != ''">
             <if test="c.type != null and c.type != ''">
                and type =#{c.type}
             </if>
             <if test="c.energyTyep != null and c.energyTyep != ''">
                and energy_tyep like concat('%',#{c.energyTyep},'%')
            </if>
             </if>
             <if test="c.energyName != null and c.energyName != ''">
                and energy_name like concat('%',#{c.energyName},'%')
            </if>
             </if>
             <if test="c.meterReadingDate != null and c.meterReadingDate != ''">
                 and meter_reading_date =#{c.meterReadingDate}
             </if>
         </where>
    </select>
    <select id="calculateEnergy" resultType="java.util.Map">
        select SUM(ecd.dosage) totalEnergyConsumption,
               SUM(ecd.dosage * e.unit_price) totalEnergyCost
            from energy_consumption_detail ecd
            left join  energy e on ecd.energy_id = e.id
            where ecd.meter_reading_date between #{c.startDate} and #{c.endDate}
            <if test="c.type != null and c.type != ''">
                and ecd.type =#{c.type}
            </if>
        </where>
    </select>
    <select id="energyConsumptionTypeProportion"
            resultType="com.ruoyi.energy.dto.EnergyConsumptionTypeDto">
         select e.energy_tyep,
                SUM(ecd.dosage) energyConsumption,
               SUM(ecd.dosage * e.unit_price) energyCost
            from energy_consumption_detail ecd
            left join  energy e on ecd.energy_id = e.id
            where ecd.meter_reading_date between #{c.startDate} and #{c.endDate}
            <if test="c.type != null and c.type != ''">
                and ecd.type =#{c.type}
            </if>
            group by e.energy_tyep
    </select>
    <select id="energyCostDtos" resultType="com.ruoyi.energy.dto.EnergyCostDto">
    select A.meter_reading_date,
           A.waterConsumption,
           A.waterCost,
           B.electricityConsumption,
           B.electricityCost,
           C.gasConsumption,
           C.gasCost,
           sum(A.waterConsumption+B.electricityConsumption+C.gasConsumption) totalConsumption,
           sum(A.waterCost+B.electricityCost+C.gasCost) totalCost
    from
    (select ecd.meter_reading_date,
               sum(ecd.dosage) waterConsumption,
               sum(ecd.dosage * e1.unit_price) waterCost
            from energy_consumption_detail ecd
            left join  energy e1 on ecd.energy_id = e1.id
            where ecd.meter_reading_date between #{c.startDate} and #{c.endDate}
            and e1.energy_tyep='æ°´'
            <if test="c.type != null and c.type != ''">
                and ecd.type =#{c.type}
            </if>
            group by ecd.meter_reading_date)A
    join
    (select ecd.meter_reading_date,
               sum(ecd.dosage) electricityConsumption,
               sum(ecd.dosage * e2.unit_price) electricityCost
            from energy_consumption_detail ecd
            left join  energy e2 on ecd.energy_id = e2.id
            where ecd.meter_reading_date between #{c.startDate} and #{c.endDate}
            and e2.energy_tyep='电'
            <if test="c.type != null and c.type != ''">
                and ecd.type =#{c.type}
            </if>
            group by ecd.meter_reading_date)B
        on A.meter_reading_date=B.meter_reading_date
    join
    (select ecd.meter_reading_date,
               sum(ecd.dosage) gasConsumption,
               sum(ecd.dosage * e3.unit_price) gasCost
            from energy_consumption_detail ecd
            left join  energy e3 on ecd.energy_id = e3.id
            where ecd.meter_reading_date between #{c.startDate} and #{c.endDate}
            and e3.energy_tyep='气'
            <if test="c.type != null and c.type != ''">
                and ecd.type =#{c.type}
            </if>
            group by ecd.meter_reading_date)C
        on A.meter_reading_date=C.meter_reading_date
    </select>
</mapper>
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/production/ProductOrderMapper.xml
@@ -15,50 +15,16 @@
    </resultMap>
    <select id="pageProductOrder" resultType="com.ruoyi.production.dto.ProductOrderDto">
        select po.*,
        sl.sales_contract_no,
        sl.customer_name,
        p.product_name as product_category,
        pm.model as specification_model,
        pm.unit,
        ppr.process_route_code,
        pb.bom_no,
        ROUND(po.complete_quantity / po.quantity * 100, 2) AS completionStatus,
        DATEDIFF(sl.delivery_date, CURDATE()) AS delivery_days_diff,
        sl.delivery_date,
        CASE
        WHEN shipping_status_counts.total_count = 0 THEN false
        WHEN shipping_status_counts.unshipped_count = 0 THEN true
        ELSE false
        END AS is_fh
        ROUND(po.complete_quantity / po.quantity * 100, 2) AS completionStatus
        from product_order po
        left join sales_ledger sl on po.sales_ledger_id = sl.id
        LEFT JOIN (
        SELECT sales_ledger_id,
        COUNT(*) as total_count,
        SUM(CASE WHEN status != '已发货' THEN 1 ELSE 0 END) as unshipped_count
        FROM shipping_info
        GROUP BY sales_ledger_id
        ) shipping_status_counts ON sl.id = shipping_status_counts.sales_ledger_id
            left join product_model pm on po.product_model_id = pm.id
            left join product p on pm.product_id = p.id
        left join sales_ledger_product slp on po.sale_ledger_product_id = slp.id and slp.type = 1
        left join product_process_route ppr on po.id = ppr.product_order_id
        left join product_bom pb on pb.id = ppr.bom_id
        <where>
            <if test="c.npsNo != null and c.npsNo != ''">
                and po.nps_no like concat('%',#{c.npsNo},'%')
            </if>
            <if test="c.salesContractNo != null and c.salesContractNo != ''">
                and sl.sales_contract_no like concat('%',#{c.salesContractNo},'%')
            </if>
            <if test="c.customerName != null and c.customerName != ''">
                and sl.customer_name like concat('%',#{c.customerName},'%')
            </if>
            <if test="c.productCategory != null and c.productCategory != ''">
                and slp.product_category like concat('%',#{c.productCategory},'%')
            </if>
            <if test="c.specificationModel != null and c.specificationModel != ''">
                and slp.specification_model like concat('%',#{c.specificationModel},'%')
            </if>
            <if test="c.startTime != null and c.endTime != null">
                and po.create_time between #{c.startTime} and #{c.endTime}
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"/>
@@ -38,10 +39,57 @@
    </resultMap>
    <select id="listPage" resultMap="ProductionPlanResultMap">
    <select id="listPage" resultType="com.ruoyi.productionPlan.dto.ProductionPlanDto">
        SELECT *
        FROM production_plan
        FROM production_plan pp
        WHERE 1 = 1
        <if test="c.customerName != null and c.customerName != '' ">
            AND pp.customer_name LIKE CONCAT('%',#{c.customerName},'%')
        </if>
        <if test="c.productName != null and c.productName != '' ">
            AND pp.product_name LIKE CONCAT('%',#{c.productName},'%')
        </if>
        <if test="c.productSpec != null and c.productSpec != '' ">
            AND pp.product_spec LIKE CONCAT('%',#{c.productSpec},'%')
        </if>
        <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">
        SELECT
        material_code,
        product_name,
        product_spec,
        length,
        width,
        height,
        COALESCE(SUM(quantity),0) AS quantity,
        COALESCE(SUM(volume),0) AS volume
        FROM production_plan
        <where>
            <if test="materialCode != null and materialCode != ''">
                AND material_code LIKE CONCAT('%', #{materialCode}, '%')
            </if>
            <if test="productName != null and productName != ''">
                AND product_name LIKE CONCAT('%', #{productName}, '%')
            </if>
        </where>
        GROUP BY
        material_code,
        product_name,
        product_spec,
        length,
        width,
        height
    </select>
</mapper>