Merge branch '0.8.X' of https://gitee.com/hdka/klp-oa into 0.8.X
This commit is contained in:
@@ -106,6 +106,10 @@
|
|||||||
<!-- <groupId>com.klp</groupId>-->
|
<!-- <groupId>com.klp</groupId>-->
|
||||||
<!-- <artifactId>klp-hrm</artifactId>-->
|
<!-- <artifactId>klp-hrm</artifactId>-->
|
||||||
<!-- </dependency>-->
|
<!-- </dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.klp</groupId>
|
||||||
|
<artifactId>klp-da</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.klp</groupId>
|
<groupId>com.klp</groupId>
|
||||||
<artifactId>klp-crm</artifactId>
|
<artifactId>klp-crm</artifactId>
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ klp:
|
|||||||
# 开发环境文件存储目录
|
# 开发环境文件存储目录
|
||||||
directory-path: testDirectory
|
directory-path: testDirectory
|
||||||
|
|
||||||
|
--- # OEE 聚合(klp-da)多服务地址配置
|
||||||
|
da:
|
||||||
|
oee:
|
||||||
|
# 酸轧线(klp-pocket)在同一套服务内时可指向本服务端口
|
||||||
|
acid-line-base-url: http://localhost:${server.port}
|
||||||
|
# 镀锌一线(Fizz)
|
||||||
|
galvanize-line-base-url: http://140.143.206.120:18081
|
||||||
|
|
||||||
--- # 监控中心配置
|
--- # 监控中心配置
|
||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
# 增加客户端开关
|
# 增加客户端开关
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ klp:
|
|||||||
# 生产环境文件存储目录
|
# 生产环境文件存储目录
|
||||||
directory-path: /home/ubuntu/oa/folder
|
directory-path: /home/ubuntu/oa/folder
|
||||||
|
|
||||||
|
--- # OEE 聚合(klp-da)多服务地址配置
|
||||||
|
da:
|
||||||
|
oee:
|
||||||
|
# 酸轧线(klp-pocket)在同一套服务内时可指向本服务端口(也可改成内网域名)
|
||||||
|
acid-line-base-url: http://127.0.0.1:${server.port}
|
||||||
|
# 镀锌一线(Fizz)
|
||||||
|
galvanize-line-base-url: http://140.143.206.120:18081
|
||||||
|
|
||||||
--- # 监控中心配置
|
--- # 监控中心配置
|
||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
# 增加客户端开关
|
# 增加客户端开关
|
||||||
|
|||||||
41
klp-da/pom.xml
Normal file
41
klp-da/pom.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.klp</groupId>
|
||||||
|
<artifactId>klp-oa</artifactId>
|
||||||
|
<version>0.8.3</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>klp-da</artifactId>
|
||||||
|
<name>Archetype - klp-da</name>
|
||||||
|
<url>http://maven.apache.org</url>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.klp</groupId>
|
||||||
|
<artifactId>klp-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-annotation</artifactId>
|
||||||
|
<version>3.5.9</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>1.2.83</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 阿里JSON解析器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
|
<artifactId>fastjson2</artifactId>
|
||||||
|
<version>2.0.35</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.klp.da.controller;
|
||||||
|
|
||||||
|
import com.klp.common.annotation.Log;
|
||||||
|
import com.klp.common.core.controller.BaseController;
|
||||||
|
import com.klp.common.core.domain.PageQuery;
|
||||||
|
import com.klp.common.core.domain.R;
|
||||||
|
import com.klp.common.core.page.TableDataInfo;
|
||||||
|
import com.klp.common.enums.BusinessType;
|
||||||
|
import com.klp.da.domain.bo.OeeQueryBo;
|
||||||
|
import com.klp.da.domain.vo.OeeLineSummaryVo;
|
||||||
|
import com.klp.da.domain.vo.OeeLossCategorySummaryVo;
|
||||||
|
import com.klp.da.domain.vo.OeeLossReasonVo;
|
||||||
|
import com.klp.da.domain.vo.OeeEventVo;
|
||||||
|
import com.klp.da.service.IOeeReportService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OEE 报表聚合 Controller(方式 A:后端统一聚合多服务)
|
||||||
|
*
|
||||||
|
* 当前仅提供接口“架子”,具体聚合逻辑在 {@link IOeeReportService} 中实现。
|
||||||
|
*
|
||||||
|
* 路由前缀与 docs/oee-report-design.md 设计文档保持一致:
|
||||||
|
* - /api/ems/oee/line/summary
|
||||||
|
* - /api/ems/oee/line/loss7
|
||||||
|
* - /api/ems/oee/line/events
|
||||||
|
* - /api/ems/oee/line/exportWord
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/oee/line")
|
||||||
|
public class OeeReportController extends BaseController {
|
||||||
|
|
||||||
|
private final IOeeReportService oeeReportService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KPI + 趋势汇总
|
||||||
|
*/
|
||||||
|
@Log(title = "OEE 报表-汇总", businessType = BusinessType.OTHER)
|
||||||
|
@GetMapping("/summary")
|
||||||
|
public R<List<OeeLineSummaryVo>> summary(OeeQueryBo queryBo) {
|
||||||
|
return R.ok(oeeReportService.summary(queryBo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 7 大损失汇总
|
||||||
|
*/
|
||||||
|
@Log(title = "OEE 报表-7大损失", businessType = BusinessType.OTHER)
|
||||||
|
@GetMapping("/loss7")
|
||||||
|
public R<Map<String, Object>> lossSummary(OeeQueryBo queryBo) {
|
||||||
|
Map<String, Object> result = oeeReportService.lossSummary(queryBo);
|
||||||
|
return R.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停机/损失事件明细
|
||||||
|
*/
|
||||||
|
@Log(title = "OEE 报表-事件明细", businessType = BusinessType.OTHER)
|
||||||
|
@GetMapping("/events")
|
||||||
|
public TableDataInfo<OeeEventVo> events(OeeQueryBo queryBo, PageQuery pageQuery) {
|
||||||
|
return oeeReportService.events(queryBo, pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出 Word 报表(整体版式由后端模板控制)
|
||||||
|
*/
|
||||||
|
@Log(title = "OEE 报表-导出Word", businessType = BusinessType.EXPORT)
|
||||||
|
@GetMapping("/exportWord")
|
||||||
|
public void exportWord(OeeQueryBo queryBo, HttpServletResponse response) {
|
||||||
|
oeeReportService.exportWord(queryBo, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
0
klp-da/src/main/java/com/klp/da/controller/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/controller/占位.txt
Normal file
68
klp-da/src/main/java/com/klp/da/domain/bo/OeeQueryBo.java
Normal file
68
klp-da/src/main/java/com/klp/da/domain/bo/OeeQueryBo.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package com.klp.da.domain.bo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OEE 查询条件 Bo
|
||||||
|
*
|
||||||
|
* 主要用于两条产线(酸轧线、镀锌一线)的聚合查询。
|
||||||
|
* 具体字段与 docs/oee-report-design.md 中“接口设计”保持一致。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OeeQueryBo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始日期(yyyy-MM-dd)
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束日期(yyyy-MM-dd)
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate endDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产线 ID 列表,例如 ["SY", "DX1"]
|
||||||
|
*/
|
||||||
|
private List<String> lineIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件查询:开始时间
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件查询:结束时间
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 7 大损失类别编码(可选)
|
||||||
|
*/
|
||||||
|
private String lossCategoryCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关键字(事件明细筛选,匹配原因/备注等)
|
||||||
|
*/
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TopN 设置(7 大损失 TOP 原因等,可选)
|
||||||
|
*/
|
||||||
|
private Integer topN;
|
||||||
|
}
|
||||||
|
|
||||||
0
klp-da/src/main/java/com/klp/da/domain/bo/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/domain/bo/占位.txt
Normal file
39
klp-da/src/main/java/com/klp/da/domain/vo/OeeEventVo.java
Normal file
39
klp-da/src/main/java/com/klp/da/domain/vo/OeeEventVo.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package com.klp.da.domain.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OEE 事件(停机/损失)明细 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OeeEventVo {
|
||||||
|
|
||||||
|
private String lineId;
|
||||||
|
|
||||||
|
private String lineName;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime eventStartTime;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime eventEndTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时长(分钟)
|
||||||
|
*/
|
||||||
|
private Integer durationMin;
|
||||||
|
|
||||||
|
private String rawReasonCode;
|
||||||
|
|
||||||
|
private String rawReasonName;
|
||||||
|
|
||||||
|
private String lossCategoryCode;
|
||||||
|
|
||||||
|
private String lossCategoryName;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.klp.da.domain.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产线 OEE 汇总 + 日趋势 VO
|
||||||
|
*
|
||||||
|
* 对应设计文档 7.1 返回结构中的一条 line 记录。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OeeLineSummaryVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产线 ID
|
||||||
|
*/
|
||||||
|
private String lineId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产线名称
|
||||||
|
*/
|
||||||
|
private String lineName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区间汇总
|
||||||
|
*/
|
||||||
|
private Summary total;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日粒度数据(用于趋势图)
|
||||||
|
*/
|
||||||
|
private List<Daily> daily;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Summary {
|
||||||
|
private Integer loadingTimeMin;
|
||||||
|
private Integer downtimeMin;
|
||||||
|
private Integer runTimeMin;
|
||||||
|
private BigDecimal totalOutput;
|
||||||
|
private BigDecimal goodOutput;
|
||||||
|
private BigDecimal defectOutput;
|
||||||
|
|
||||||
|
private BigDecimal availability;
|
||||||
|
private BigDecimal performance;
|
||||||
|
private BigDecimal quality;
|
||||||
|
private BigDecimal oee;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Daily {
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate statDate;
|
||||||
|
|
||||||
|
private Integer loadingTimeMin;
|
||||||
|
private Integer downtimeMin;
|
||||||
|
private Integer runTimeMin;
|
||||||
|
private BigDecimal totalOutput;
|
||||||
|
private BigDecimal goodOutput;
|
||||||
|
private BigDecimal defectOutput;
|
||||||
|
|
||||||
|
private BigDecimal availability;
|
||||||
|
private BigDecimal performance;
|
||||||
|
private BigDecimal quality;
|
||||||
|
private BigDecimal oee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.klp.da.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 7 大损失分类汇总 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OeeLossCategorySummaryVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 损失类别编码(1~7 或枚举)
|
||||||
|
*/
|
||||||
|
private String lossCategoryCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 损失类别名称
|
||||||
|
*/
|
||||||
|
private String lossCategoryName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 损失时间(分钟)
|
||||||
|
*/
|
||||||
|
private Integer lossTimeMin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 损失占比(0~1 或 0~100,随整体口径配置)
|
||||||
|
*/
|
||||||
|
private BigDecimal lossTimeRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件次数(可选)
|
||||||
|
*/
|
||||||
|
private Integer count;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.klp.da.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 损失原因 TopN VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OeeLossReasonVo {
|
||||||
|
|
||||||
|
private String lineId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原因编码
|
||||||
|
*/
|
||||||
|
private String reasonCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原因名称
|
||||||
|
*/
|
||||||
|
private String reasonName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属损失类别编码
|
||||||
|
*/
|
||||||
|
private String lossCategoryCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 损失时间(分钟)
|
||||||
|
*/
|
||||||
|
private Integer lossTimeMin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间占比
|
||||||
|
*/
|
||||||
|
private BigDecimal lossTimeRate;
|
||||||
|
}
|
||||||
|
|
||||||
0
klp-da/src/main/java/com/klp/da/domain/vo/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/domain/vo/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/domain/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/domain/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/mapper/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/mapper/占位.txt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package com.klp.da.service;
|
||||||
|
|
||||||
|
import com.klp.common.core.domain.PageQuery;
|
||||||
|
import com.klp.common.core.page.TableDataInfo;
|
||||||
|
import com.klp.da.domain.bo.OeeQueryBo;
|
||||||
|
import com.klp.da.domain.vo.OeeEventVo;
|
||||||
|
import com.klp.da.domain.vo.OeeLineSummaryVo;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OEE 报表聚合 Service 接口(方式 A:后端统一聚合多服务)
|
||||||
|
*
|
||||||
|
* 实现类负责:
|
||||||
|
* - 调用酸轧线、镀锌一线等外部服务
|
||||||
|
* - 做数据汇总、口径统一和格式转换
|
||||||
|
*/
|
||||||
|
public interface IOeeReportService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KPI + 趋势汇总
|
||||||
|
*/
|
||||||
|
List<OeeLineSummaryVo> summary(OeeQueryBo queryBo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 7 大损失汇总
|
||||||
|
*
|
||||||
|
* 返回 Map 以便后续扩展:
|
||||||
|
* - byLine: List<LineLossSummary>
|
||||||
|
* - topReasons: List<OeeLossReasonVo>
|
||||||
|
* 等
|
||||||
|
*/
|
||||||
|
Map<String, Object> lossSummary(OeeQueryBo queryBo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件明细
|
||||||
|
*/
|
||||||
|
TableDataInfo<OeeEventVo> events(OeeQueryBo queryBo, PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出 Word 报表
|
||||||
|
*/
|
||||||
|
void exportWord(OeeQueryBo queryBo, HttpServletResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,684 @@
|
|||||||
|
package com.klp.da.service.impl;
|
||||||
|
|
||||||
|
import com.klp.common.core.domain.PageQuery;
|
||||||
|
import com.klp.common.core.page.TableDataInfo;
|
||||||
|
import com.klp.da.domain.bo.OeeQueryBo;
|
||||||
|
import com.klp.da.domain.vo.OeeEventVo;
|
||||||
|
import com.klp.da.domain.vo.OeeLineSummaryVo;
|
||||||
|
import com.klp.da.service.IOeeReportService;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OEE 报表聚合 Service 实现(方式 A)
|
||||||
|
*
|
||||||
|
* 仅搭建“架子”,并预先约定两条线的数据来源:
|
||||||
|
* - {@link #acidLineBaseUrl}:酸轧线(TCM1)服务基础地址,本地 klp-pocket 模块,例如 http://localhost:8080
|
||||||
|
* - {@link #galvanizeLineBaseUrl}:镀锌一线服务基础地址,Fizz 平台,例如 http://140.143.206.120:18081
|
||||||
|
*
|
||||||
|
* 配置示例(application-*.yml):
|
||||||
|
* da:
|
||||||
|
* oee:
|
||||||
|
* acid-line-base-url: http://localhost:8080
|
||||||
|
* galvanize-line-base-url: http://140.143.206.120:18081
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class OeeReportServiceImpl implements IOeeReportService {
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Value("${da.oee.acid-line-base-url:}")
|
||||||
|
private String acidLineBaseUrl;
|
||||||
|
|
||||||
|
@Value("${da.oee.galvanize-line-base-url:}")
|
||||||
|
private String galvanizeLineBaseUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<OeeLineSummaryVo> summary(OeeQueryBo queryBo) {
|
||||||
|
log.info("OEE summary query: {}", queryBo);
|
||||||
|
LocalDate start = queryBo.getStartDate();
|
||||||
|
LocalDate end = queryBo.getEndDate();
|
||||||
|
if (start == null || end == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> lineIds = (queryBo.getLineIds() == null || queryBo.getLineIds().isEmpty())
|
||||||
|
? java.util.Arrays.asList("SY", "DX1")
|
||||||
|
: queryBo.getLineIds();
|
||||||
|
|
||||||
|
List<OeeLineSummaryVo> result = new ArrayList<>();
|
||||||
|
for (String lineId : lineIds) {
|
||||||
|
if (isAcidLine(lineId)) {
|
||||||
|
result.add(buildAcidLineSummary(lineId, "酸轧线", start, end));
|
||||||
|
} else if (isGalvanizeLine(lineId)) {
|
||||||
|
result.add(buildGalvanizeLineSummary(lineId, "镀锌一线", start, end));
|
||||||
|
} else {
|
||||||
|
// 未识别的产线:跳过(后续可扩展配置化)
|
||||||
|
log.warn("Unknown lineId: {}", lineId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> lossSummary(OeeQueryBo queryBo) {
|
||||||
|
log.info("OEE loss summary query: {}", queryBo);
|
||||||
|
LocalDate start = queryBo.getStartDate();
|
||||||
|
LocalDate end = queryBo.getEndDate();
|
||||||
|
if (start == null || end == null) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
List<String> lineIds = (queryBo.getLineIds() == null || queryBo.getLineIds().isEmpty())
|
||||||
|
? java.util.Arrays.asList("SY", "DX1")
|
||||||
|
: queryBo.getLineIds();
|
||||||
|
|
||||||
|
List<Map<String, Object>> byLine = new ArrayList<>();
|
||||||
|
for (String lineId : lineIds) {
|
||||||
|
List<OeeEventVo> events = fetchEvents(lineId, start, end);
|
||||||
|
Map<String, Integer> byStopType = events.stream()
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
e -> e.getLossCategoryCode() == null ? "UNKNOWN" : e.getLossCategoryCode(),
|
||||||
|
Collectors.summingInt(e -> e.getDurationMin() == null ? 0 : e.getDurationMin())
|
||||||
|
));
|
||||||
|
List<Map<String, Object>> losses = byStopType.entrySet().stream()
|
||||||
|
.map(e -> {
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("lossCategoryCode", e.getKey());
|
||||||
|
m.put("lossCategoryName", e.getKey());
|
||||||
|
m.put("lossTimeMin", e.getValue());
|
||||||
|
return m;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
Map<String, Object> lineBlock = new HashMap<>();
|
||||||
|
lineBlock.put("lineId", lineId);
|
||||||
|
lineBlock.put("lineName", isAcidLine(lineId) ? "酸轧线" : (isGalvanizeLine(lineId) ? "镀锌一线" : lineId));
|
||||||
|
lineBlock.put("losses", losses);
|
||||||
|
byLine.add(lineBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> resp = new HashMap<>();
|
||||||
|
resp.put("byLine", byLine);
|
||||||
|
// topReasons:当前两套数据源只有 remark/stopType,先不做更细的 Pareto(后续可按 remark 做 TopN)
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<OeeEventVo> events(OeeQueryBo queryBo, PageQuery pageQuery) {
|
||||||
|
log.info("OEE events query: {}, pageQuery: {}", queryBo, pageQuery);
|
||||||
|
LocalDate start = queryBo.getStartDate();
|
||||||
|
LocalDate end = queryBo.getEndDate();
|
||||||
|
if (start == null || end == null) {
|
||||||
|
TableDataInfo<OeeEventVo> empty = new TableDataInfo<>();
|
||||||
|
empty.setRows(Collections.emptyList());
|
||||||
|
empty.setTotal(0);
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> lineIds = (queryBo.getLineIds() == null || queryBo.getLineIds().isEmpty())
|
||||||
|
? java.util.Arrays.asList("SY", "DX1")
|
||||||
|
: queryBo.getLineIds();
|
||||||
|
|
||||||
|
List<OeeEventVo> all = new ArrayList<>();
|
||||||
|
for (String lineId : lineIds) {
|
||||||
|
all.addAll(fetchEvents(lineId, start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyword / lossCategoryCode 简单过滤
|
||||||
|
if (queryBo.getLossCategoryCode() != null && !queryBo.getLossCategoryCode().trim().isEmpty()) {
|
||||||
|
all = all.stream()
|
||||||
|
.filter(e -> Objects.equals(queryBo.getLossCategoryCode(), e.getLossCategoryCode()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
if (queryBo.getKeyword() != null && !queryBo.getKeyword().trim().isEmpty()) {
|
||||||
|
String kw = queryBo.getKeyword().trim();
|
||||||
|
all = all.stream()
|
||||||
|
.filter(e -> (e.getRawReasonName() != null && e.getRawReasonName().contains(kw))
|
||||||
|
|| (e.getRemark() != null && e.getRemark().contains(kw)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认按开始时间倒序
|
||||||
|
all.sort((a, b) -> {
|
||||||
|
LocalDateTime at = a.getEventStartTime();
|
||||||
|
LocalDateTime bt = b.getEventStartTime();
|
||||||
|
if (at == null && bt == null) return 0;
|
||||||
|
if (at == null) return 1;
|
||||||
|
if (bt == null) return -1;
|
||||||
|
return bt.compareTo(at);
|
||||||
|
});
|
||||||
|
|
||||||
|
int pageNum = pageQuery.getPageNum() == null ? 1 : pageQuery.getPageNum();
|
||||||
|
int pageSize = pageQuery.getPageSize() == null ? 10 : pageQuery.getPageSize();
|
||||||
|
int from = Math.max(0, (pageNum - 1) * pageSize);
|
||||||
|
int to = Math.min(all.size(), from + pageSize);
|
||||||
|
List<OeeEventVo> pageRows = from >= all.size() ? Collections.emptyList() : all.subList(from, to);
|
||||||
|
|
||||||
|
TableDataInfo<OeeEventVo> table = new TableDataInfo<>();
|
||||||
|
table.setRows(pageRows);
|
||||||
|
table.setTotal(all.size());
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportWord(OeeQueryBo queryBo, HttpServletResponse response) {
|
||||||
|
// TODO: 1) 聚合数据;2) 调用 Word 导出模板生成 .docx;3) 写入 response 输出流
|
||||||
|
log.info("OEE exportWord query: {}", queryBo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建镀锌一线 Fizz 报表汇总接口 URL:/api/report/summary
|
||||||
|
* 对应 ReportController.getReportSummary(groupNo, shiftNo, startTime, endTime)
|
||||||
|
*/
|
||||||
|
private String buildGalvanizeSummaryUrl(OeeQueryBo queryBo) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(galvanizeLineBaseUrl).append("/api/report/summary");
|
||||||
|
boolean hasParam = false;
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
if (queryBo.getStartTime() != null) {
|
||||||
|
sb.append(hasParam ? "&" : "?")
|
||||||
|
.append("startTime=").append(queryBo.getStartTime().format(formatter));
|
||||||
|
hasParam = true;
|
||||||
|
}
|
||||||
|
if (queryBo.getEndTime() != null) {
|
||||||
|
sb.append(hasParam ? "&" : "?")
|
||||||
|
.append("endTime=").append(queryBo.getEndTime().format(formatter));
|
||||||
|
}
|
||||||
|
// groupNo / shiftNo 暂不使用,后续需要可从 queryBo 中扩展字段再拼接
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAcidLine(String lineId) {
|
||||||
|
if (lineId == null) return false;
|
||||||
|
String v = lineId.trim().toUpperCase();
|
||||||
|
return v.equals("SY") || v.equals("TCM1") || v.contains("酸") || v.contains("ACID");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isGalvanizeLine(String lineId) {
|
||||||
|
if (lineId == null) return false;
|
||||||
|
String v = lineId.trim().toUpperCase();
|
||||||
|
return v.equals("DX1") || v.equals("G1") || v.contains("镀") || v.contains("GALV");
|
||||||
|
}
|
||||||
|
|
||||||
|
private OeeLineSummaryVo buildAcidLineSummary(String lineId, String lineName, LocalDate start, LocalDate end) {
|
||||||
|
OeeLineSummaryVo vo = new OeeLineSummaryVo();
|
||||||
|
vo.setLineId(lineId);
|
||||||
|
vo.setLineName(lineName);
|
||||||
|
|
||||||
|
List<OeeLineSummaryVo.Daily> daily = new ArrayList<>();
|
||||||
|
BigDecimal totalOutput = BigDecimal.ZERO;
|
||||||
|
BigDecimal goodOutput = BigDecimal.ZERO;
|
||||||
|
BigDecimal defectOutput = BigDecimal.ZERO;
|
||||||
|
int downtimeSum = 0;
|
||||||
|
int loadingSum = 0;
|
||||||
|
int runSum = 0;
|
||||||
|
|
||||||
|
for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) {
|
||||||
|
PocketProductionStats stats = fetchPocketProductionStats(d, d);
|
||||||
|
BigDecimal qualityRate = fetchCoilQualityRateByDay(d, lineId);
|
||||||
|
List<OeeEventVo> events = fetchPocketStoppageEvents(d, d);
|
||||||
|
int downtime = events.stream().mapToInt(e -> e.getDurationMin() == null ? 0 : e.getDurationMin()).sum();
|
||||||
|
int loading = (int) ChronoUnit.MINUTES.between(d.atStartOfDay(), d.plusDays(1).atStartOfDay());
|
||||||
|
int run = Math.max(0, loading - downtime);
|
||||||
|
|
||||||
|
OeeLineSummaryVo.Daily day = new OeeLineSummaryVo.Daily();
|
||||||
|
day.setStatDate(d);
|
||||||
|
day.setLoadingTimeMin(loading);
|
||||||
|
day.setDowntimeMin(downtime);
|
||||||
|
day.setRunTimeMin(run);
|
||||||
|
day.setTotalOutput(stats.totalExitWeight);
|
||||||
|
BigDecimal good = nvl(stats.totalEntryWeight).multiply(qualityRate);
|
||||||
|
BigDecimal defect = nvl(stats.totalEntryWeight).subtract(good).max(BigDecimal.ZERO);
|
||||||
|
day.setGoodOutput(good);
|
||||||
|
day.setDefectOutput(defect);
|
||||||
|
|
||||||
|
day.setAvailability(calcRate(run, loading));
|
||||||
|
day.setQuality(qualityRate);
|
||||||
|
day.setPerformance(BigDecimal.ONE);
|
||||||
|
day.setOee(day.getAvailability().multiply(day.getQuality()));
|
||||||
|
|
||||||
|
daily.add(day);
|
||||||
|
|
||||||
|
totalOutput = totalOutput.add(nvl(stats.totalEntryWeight));
|
||||||
|
goodOutput = goodOutput.add(good);
|
||||||
|
defectOutput = defectOutput.add(defect);
|
||||||
|
downtimeSum += downtime;
|
||||||
|
loadingSum += loading;
|
||||||
|
runSum += run;
|
||||||
|
}
|
||||||
|
|
||||||
|
OeeLineSummaryVo.Summary total = new OeeLineSummaryVo.Summary();
|
||||||
|
total.setLoadingTimeMin(loadingSum);
|
||||||
|
total.setDowntimeMin(downtimeSum);
|
||||||
|
total.setRunTimeMin(runSum);
|
||||||
|
total.setTotalOutput(totalOutput);
|
||||||
|
total.setGoodOutput(goodOutput);
|
||||||
|
total.setDefectOutput(defectOutput);
|
||||||
|
total.setAvailability(calcRate(runSum, loadingSum));
|
||||||
|
BigDecimal qualityRate = calcRate(goodOutput, totalOutput);
|
||||||
|
total.setQuality(qualityRate);
|
||||||
|
total.setPerformance(BigDecimal.ONE);
|
||||||
|
total.setOee(total.getAvailability().multiply(total.getQuality()));
|
||||||
|
|
||||||
|
vo.setDaily(daily);
|
||||||
|
vo.setTotal(total);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OeeLineSummaryVo buildGalvanizeLineSummary(String lineId, String lineName, LocalDate start, LocalDate end) {
|
||||||
|
OeeLineSummaryVo vo = new OeeLineSummaryVo();
|
||||||
|
vo.setLineId(lineId);
|
||||||
|
vo.setLineName(lineName);
|
||||||
|
|
||||||
|
List<OeeLineSummaryVo.Daily> daily = new ArrayList<>();
|
||||||
|
BigDecimal totalOutput = BigDecimal.ZERO;
|
||||||
|
BigDecimal goodOutput = BigDecimal.ZERO;
|
||||||
|
BigDecimal defectOutput = BigDecimal.ZERO;
|
||||||
|
int downtimeSum = 0;
|
||||||
|
int loadingSum = 0;
|
||||||
|
int runSum = 0;
|
||||||
|
|
||||||
|
for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) {
|
||||||
|
FizzReportSummary stats = fetchFizzReportSummary(d.atStartOfDay(), d.plusDays(1).atStartOfDay().minusSeconds(1));
|
||||||
|
BigDecimal qualityRate = fetchCoilQualityRateByDay(d, lineId);
|
||||||
|
List<OeeEventVo> events = fetchFizzStoppageEvents(d, d);
|
||||||
|
int downtime = events.stream().mapToInt(e -> e.getDurationMin() == null ? 0 : e.getDurationMin()).sum();
|
||||||
|
int loading = (int) ChronoUnit.MINUTES.between(d.atStartOfDay(), d.plusDays(1).atStartOfDay());
|
||||||
|
int run = Math.max(0, loading - downtime);
|
||||||
|
|
||||||
|
OeeLineSummaryVo.Daily day = new OeeLineSummaryVo.Daily();
|
||||||
|
day.setStatDate(d);
|
||||||
|
day.setLoadingTimeMin(loading);
|
||||||
|
day.setDowntimeMin(downtime);
|
||||||
|
day.setRunTimeMin(run);
|
||||||
|
day.setTotalOutput(stats.totalActualWeight);
|
||||||
|
BigDecimal good = nvl(stats.totalEntryWeight).multiply(qualityRate);
|
||||||
|
BigDecimal defect = nvl(stats.totalEntryWeight).subtract(good).max(BigDecimal.ZERO);
|
||||||
|
day.setGoodOutput(good);
|
||||||
|
day.setDefectOutput(defect);
|
||||||
|
|
||||||
|
day.setAvailability(calcRate(run, loading));
|
||||||
|
day.setQuality(qualityRate);
|
||||||
|
day.setPerformance(BigDecimal.ONE);
|
||||||
|
day.setOee(day.getAvailability().multiply(day.getQuality()));
|
||||||
|
|
||||||
|
daily.add(day);
|
||||||
|
|
||||||
|
totalOutput = totalOutput.add(nvl(stats.totalEntryWeight));
|
||||||
|
goodOutput = goodOutput.add(good);
|
||||||
|
defectOutput = defectOutput.add(defect);
|
||||||
|
downtimeSum += downtime;
|
||||||
|
loadingSum += loading;
|
||||||
|
runSum += run;
|
||||||
|
}
|
||||||
|
|
||||||
|
OeeLineSummaryVo.Summary total = new OeeLineSummaryVo.Summary();
|
||||||
|
total.setLoadingTimeMin(loadingSum);
|
||||||
|
total.setDowntimeMin(downtimeSum);
|
||||||
|
total.setRunTimeMin(runSum);
|
||||||
|
total.setTotalOutput(totalOutput);
|
||||||
|
total.setGoodOutput(goodOutput);
|
||||||
|
total.setDefectOutput(defectOutput);
|
||||||
|
total.setAvailability(calcRate(runSum, loadingSum));
|
||||||
|
BigDecimal qualityRate = calcRate(goodOutput, totalOutput);
|
||||||
|
total.setQuality(qualityRate);
|
||||||
|
total.setPerformance(BigDecimal.ONE);
|
||||||
|
total.setOee(total.getAvailability().multiply(total.getQuality()));
|
||||||
|
|
||||||
|
vo.setDaily(daily);
|
||||||
|
vo.setTotal(total);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<OeeEventVo> fetchEvents(String lineId, LocalDate start, LocalDate end) {
|
||||||
|
if (isAcidLine(lineId)) {
|
||||||
|
return fetchPocketStoppageEvents(start, end);
|
||||||
|
}
|
||||||
|
if (isGalvanizeLine(lineId)) {
|
||||||
|
return fetchFizzStoppageEvents(start, end);
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线(klp-pocket)生产统计汇总:GET /pocket/productionStatistics/summary
|
||||||
|
* 响应为 com.klp.common.core.domain.R 包裹。
|
||||||
|
*/
|
||||||
|
private PocketProductionStats fetchPocketProductionStats(LocalDate startDate, LocalDate endDate) {
|
||||||
|
try {
|
||||||
|
String base = normalizeBaseUrl(acidLineBaseUrl);
|
||||||
|
String url = base + "/pocket/productionStatistics/summary"
|
||||||
|
+ "?startDate=" + startDate
|
||||||
|
+ "&endDate=" + endDate;
|
||||||
|
String json = restTemplate.getForObject(url, String.class);
|
||||||
|
JsonNode root = objectMapper.readTree(json);
|
||||||
|
JsonNode data = root.get("data");
|
||||||
|
PocketProductionStats s = new PocketProductionStats();
|
||||||
|
s.totalExitWeight = toBigDecimal(data, "totalExitWeight");
|
||||||
|
s.totalEntryWeight = toBigDecimal(data, "totalEntryWeight");
|
||||||
|
s.yieldRate = toBigDecimal(data, "yieldRate");
|
||||||
|
return s;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("fetchPocketProductionStats failed: {}~{}", startDate, endDate, e);
|
||||||
|
return new PocketProductionStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线(klp-pocket)停机事件:GET /pocket/proStoppage/list
|
||||||
|
*
|
||||||
|
* 该接口是 TableDataInfo 包裹,对应的 Bo 为 Klptcm1ProStoppageBo,
|
||||||
|
* 其中查询字段名就是 startDate/endDate(已确认)。
|
||||||
|
*/
|
||||||
|
private List<OeeEventVo> fetchPocketStoppageEvents(LocalDate startDate, LocalDate endDate) {
|
||||||
|
try {
|
||||||
|
String base = normalizeBaseUrl(acidLineBaseUrl);
|
||||||
|
String url = base + "/pocket/proStoppage/list"
|
||||||
|
+ "?pageNum=1&pageSize=10000"
|
||||||
|
+ "&startDate=" + startDate
|
||||||
|
+ "&endDate=" + endDate;
|
||||||
|
String json = restTemplate.getForObject(url, String.class);
|
||||||
|
JsonNode root = objectMapper.readTree(json);
|
||||||
|
JsonNode rows = root.get("rows");
|
||||||
|
if (rows == null || !rows.isArray()) return Collections.emptyList();
|
||||||
|
|
||||||
|
List<OeeEventVo> list = new ArrayList<>();
|
||||||
|
for (JsonNode n : rows) {
|
||||||
|
OeeEventVo e = new OeeEventVo();
|
||||||
|
e.setLineId("SY");
|
||||||
|
e.setLineName("酸轧线");
|
||||||
|
e.setEventStartTime(toLocalDateTime(n, "startDate"));
|
||||||
|
e.setEventEndTime(toLocalDateTime(n, "endDate"));
|
||||||
|
e.setDurationMin(toInt(n, "duration"));
|
||||||
|
e.setRawReasonCode(toText(n, "stopType"));
|
||||||
|
e.setRawReasonName(toText(n, "remark"));
|
||||||
|
e.setLossCategoryCode(toText(n, "stopType"));
|
||||||
|
e.setLossCategoryName(toText(n, "stopType"));
|
||||||
|
e.setRemark(toText(n, "remark"));
|
||||||
|
list.add(e);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("fetchPocketStoppageEvents failed: {}~{}", startDate, endDate, e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 镀锌一线(Fizz)生产实绩汇总:GET /api/report/summary
|
||||||
|
* 响应为 ReportSummaryVO 结构(无 R 包裹)。
|
||||||
|
*/
|
||||||
|
private FizzReportSummary fetchFizzReportSummary(LocalDateTime startTime, LocalDateTime endTime) {
|
||||||
|
try {
|
||||||
|
OeeQueryBo qb = new OeeQueryBo();
|
||||||
|
qb.setStartTime(startTime);
|
||||||
|
qb.setEndTime(endTime);
|
||||||
|
String url = buildGalvanizeSummaryUrl(qb);
|
||||||
|
String json = restTemplate.getForObject(url, String.class);
|
||||||
|
JsonNode root = objectMapper.readTree(json);
|
||||||
|
FizzReportSummary s = new FizzReportSummary();
|
||||||
|
s.totalActualWeight = toBigDecimal(root, "totalActualWeight");
|
||||||
|
s.totalEntryWeight = toBigDecimal(root, "totalEntryWeight");
|
||||||
|
s.yieldRate = toBigDecimal(root, "yieldRate");
|
||||||
|
return s;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("fetchFizzReportSummary failed: {}~{}", startTime, endTime, e);
|
||||||
|
return new FizzReportSummary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用本地 WMS 钢卷物料接口,根据 quality_status + actual_warehouse_id 统计某一天某条线的良品率:
|
||||||
|
* - 分母:当天该逻辑库区的钢卷数量(按创建时间 byCreateTimeStart/End + actualWarehouseId)
|
||||||
|
* - 分子:quality_status=0(正常)的钢卷数量
|
||||||
|
*
|
||||||
|
* lineId -> 库区映射:
|
||||||
|
* - 酸轧线:actualWarehouseId = 1988150099140866050
|
||||||
|
* - 镀锌一线:actualWarehouseId = 1988150323162836993
|
||||||
|
*/
|
||||||
|
private BigDecimal fetchCoilQualityRateByDay(LocalDate day, String lineId) {
|
||||||
|
try {
|
||||||
|
Long actualWarehouseId = null;
|
||||||
|
if (isAcidLine(lineId)) {
|
||||||
|
actualWarehouseId = 1988150099140866050L;
|
||||||
|
} else if (isGalvanizeLine(lineId)) {
|
||||||
|
actualWarehouseId = 1988150323162836993L;
|
||||||
|
}
|
||||||
|
if (actualWarehouseId == null) {
|
||||||
|
// 未知产线:退化为全厂口径
|
||||||
|
return fetchCoilQualityRateByDay(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
String base = normalizeBaseUrl(acidLineBaseUrl);
|
||||||
|
String start = day.atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
String end = day.plusDays(1).atStartOfDay().minusSeconds(1)
|
||||||
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
|
||||||
|
// 全部钢卷数量
|
||||||
|
String allUrl = base + "/wms/materialCoil/list"
|
||||||
|
+ "?pageNum=1&pageSize=1"
|
||||||
|
+ "&byCreateTimeStart=" + start
|
||||||
|
+ "&byCreateTimeEnd=" + end
|
||||||
|
+ "&actualWarehouseId=" + actualWarehouseId;
|
||||||
|
String allJson = restTemplate.getForObject(allUrl, String.class);
|
||||||
|
JsonNode allRoot = objectMapper.readTree(allJson);
|
||||||
|
long totalAll = allRoot.path("total").asLong(0);
|
||||||
|
if (totalAll <= 0) {
|
||||||
|
return BigDecimal.ONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 良品钢卷数量:quality_status = '0'
|
||||||
|
String goodUrl = allUrl + "&qualityStatus=0";
|
||||||
|
String goodJson = restTemplate.getForObject(goodUrl, String.class);
|
||||||
|
JsonNode goodRoot = objectMapper.readTree(goodJson);
|
||||||
|
long totalGood = goodRoot.path("total").asLong(0);
|
||||||
|
|
||||||
|
return calcRate(BigDecimal.valueOf(totalGood), BigDecimal.valueOf(totalAll));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("fetchCoilQualityRateByDay failed for day {}: {}", day, e.getMessage());
|
||||||
|
// 失败时默认按 100% 良品率处理,避免影响主逻辑
|
||||||
|
return BigDecimal.ONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全厂整体良品率(仅在无法识别产线时兜底使用)。
|
||||||
|
*/
|
||||||
|
private BigDecimal fetchCoilQualityRateByDay(LocalDate day) {
|
||||||
|
try {
|
||||||
|
String base = normalizeBaseUrl(acidLineBaseUrl);
|
||||||
|
String start = day.atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
String end = day.plusDays(1).atStartOfDay().minusSeconds(1)
|
||||||
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
|
||||||
|
String allUrl = base + "/wms/materialCoil/list"
|
||||||
|
+ "?pageNum=1&pageSize=1"
|
||||||
|
+ "&byCreateTimeStart=" + start
|
||||||
|
+ "&byCreateTimeEnd=" + end;
|
||||||
|
String allJson = restTemplate.getForObject(allUrl, String.class);
|
||||||
|
JsonNode allRoot = objectMapper.readTree(allJson);
|
||||||
|
long totalAll = allRoot.path("total").asLong(0);
|
||||||
|
if (totalAll <= 0) {
|
||||||
|
return BigDecimal.ONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
String goodUrl = allUrl + "&qualityStatus=0";
|
||||||
|
String goodJson = restTemplate.getForObject(goodUrl, String.class);
|
||||||
|
JsonNode goodRoot = objectMapper.readTree(goodJson);
|
||||||
|
long totalGood = goodRoot.path("total").asLong(0);
|
||||||
|
|
||||||
|
return calcRate(BigDecimal.valueOf(totalGood), BigDecimal.valueOf(totalAll));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("fetchCoilQualityRateByDay(all) failed for day {}: {}", day, e.getMessage());
|
||||||
|
return BigDecimal.ONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 镀锌一线(Fizz)停机事件:POST /api/stoppage/list
|
||||||
|
* 响应为 com.ruoyi.common.core.domain.R 包裹。
|
||||||
|
*/
|
||||||
|
private List<OeeEventVo> fetchFizzStoppageEvents(LocalDate startDate, LocalDate endDate) {
|
||||||
|
try {
|
||||||
|
String base = normalizeBaseUrl(galvanizeLineBaseUrl);
|
||||||
|
String url = base + "/api/stoppage/list";
|
||||||
|
Map<String, String> body = new HashMap<>();
|
||||||
|
body.put("startDate", startDate.toString());
|
||||||
|
body.put("endDate", endDate.toString());
|
||||||
|
|
||||||
|
String json = restTemplate.postForObject(url, body, String.class);
|
||||||
|
JsonNode root = objectMapper.readTree(json);
|
||||||
|
JsonNode data = root.get("data");
|
||||||
|
if (data == null || !data.isArray()) return Collections.emptyList();
|
||||||
|
|
||||||
|
List<OeeEventVo> list = new ArrayList<>();
|
||||||
|
for (JsonNode n : data) {
|
||||||
|
OeeEventVo e = new OeeEventVo();
|
||||||
|
e.setLineId("DX1");
|
||||||
|
e.setLineName("镀锌一线");
|
||||||
|
e.setEventStartTime(toLocalDateTime(n, "startDate"));
|
||||||
|
e.setEventEndTime(toLocalDateTime(n, "endDate"));
|
||||||
|
// duration 在 Fizz 为 BigDecimal,字段名 duration
|
||||||
|
e.setDurationMin(toBigDecimal(n, "duration").intValue());
|
||||||
|
e.setRawReasonCode(toText(n, "stopType"));
|
||||||
|
e.setRawReasonName(toText(n, "remark"));
|
||||||
|
e.setLossCategoryCode(toText(n, "stopType"));
|
||||||
|
e.setLossCategoryName(toText(n, "stopType"));
|
||||||
|
e.setRemark(toText(n, "remark"));
|
||||||
|
list.add(e);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("fetchFizzStoppageEvents failed: {}~{}", startDate, endDate, e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeBaseUrl(String baseUrl) {
|
||||||
|
if (baseUrl == null || baseUrl.trim().isEmpty()) {
|
||||||
|
// 默认指向当前应用(同域调用)
|
||||||
|
return "http://localhost:8080";
|
||||||
|
}
|
||||||
|
String s = baseUrl.trim();
|
||||||
|
if (s.endsWith("/")) s = s.substring(0, s.length() - 1);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal calcRate(int numerator, int denominator) {
|
||||||
|
if (denominator <= 0) return BigDecimal.ZERO;
|
||||||
|
return BigDecimal.valueOf(numerator)
|
||||||
|
.divide(BigDecimal.valueOf(denominator), 6, BigDecimal.ROUND_HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal calcRate(BigDecimal numerator, BigDecimal denominator) {
|
||||||
|
if (denominator == null || denominator.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
return nvl(numerator).divide(denominator, 6, BigDecimal.ROUND_HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal nvl(BigDecimal v) {
|
||||||
|
return v == null ? BigDecimal.ZERO : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将可能是 0~1 或 0~100 的比率统一规范到 0~1 区间。
|
||||||
|
*/
|
||||||
|
private BigDecimal normalizeRate(BigDecimal raw) {
|
||||||
|
BigDecimal r = nvl(raw);
|
||||||
|
if (r.compareTo(BigDecimal.ONE) > 0 && r.compareTo(BigDecimal.valueOf(100)) <= 0) {
|
||||||
|
r = r.divide(BigDecimal.valueOf(100), 6, BigDecimal.ROUND_HALF_UP);
|
||||||
|
}
|
||||||
|
if (r.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
r = BigDecimal.ZERO;
|
||||||
|
} else if (r.compareTo(BigDecimal.ONE) > 0) {
|
||||||
|
r = BigDecimal.ONE;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal toBigDecimal(JsonNode node, String field) {
|
||||||
|
if (node == null) return BigDecimal.ZERO;
|
||||||
|
JsonNode v = node.get(field);
|
||||||
|
if (v == null || v.isNull()) return BigDecimal.ZERO;
|
||||||
|
if (v.isNumber()) return v.decimalValue();
|
||||||
|
String s = v.asText();
|
||||||
|
if (s == null || s.trim().isEmpty()) return BigDecimal.ZERO;
|
||||||
|
try {
|
||||||
|
return new BigDecimal(s);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toText(JsonNode node, String field) {
|
||||||
|
if (node == null) return null;
|
||||||
|
JsonNode v = node.get(field);
|
||||||
|
if (v == null || v.isNull()) return null;
|
||||||
|
String s = v.asText();
|
||||||
|
return (s == null || s.trim().isEmpty()) ? null : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer toInt(JsonNode node, String field) {
|
||||||
|
if (node == null) return 0;
|
||||||
|
JsonNode v = node.get(field);
|
||||||
|
if (v == null || v.isNull()) return 0;
|
||||||
|
if (v.isNumber()) return v.asInt();
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(v.asText());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime toLocalDateTime(JsonNode node, String field) {
|
||||||
|
String s = toText(node, field);
|
||||||
|
if (s == null) return null;
|
||||||
|
try {
|
||||||
|
// 兼容 "yyyy-MM-dd HH:mm:ss" / ISO
|
||||||
|
if (s.contains("T")) {
|
||||||
|
return LocalDateTime.parse(s);
|
||||||
|
}
|
||||||
|
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
return LocalDateTime.parse(s, f);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PocketProductionStats {
|
||||||
|
BigDecimal totalEntryWeight = BigDecimal.ZERO;
|
||||||
|
BigDecimal totalExitWeight = BigDecimal.ZERO;
|
||||||
|
BigDecimal yieldRate = BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FizzReportSummary {
|
||||||
|
BigDecimal totalEntryWeight = BigDecimal.ZERO;
|
||||||
|
BigDecimal totalActualWeight = BigDecimal.ZERO;
|
||||||
|
BigDecimal yieldRate = BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
0
klp-da/src/main/java/com/klp/da/service/impl/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/service/impl/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/service/占位.txt
Normal file
0
klp-da/src/main/java/com/klp/da/service/占位.txt
Normal file
0
klp-da/src/main/resources/mapper/占位.txt
Normal file
0
klp-da/src/main/resources/mapper/占位.txt
Normal file
@@ -133,7 +133,7 @@ public class EnergyCostReportServiceImpl implements IEnergyCostReportService {
|
|||||||
long offset = (pageNum - 1) * pageSize;
|
long offset = (pageNum - 1) * pageSize;
|
||||||
// 手工 count 与数据分页,避免复杂 SQL 自动 count 解析
|
// 手工 count 与数据分页,避免复杂 SQL 自动 count 解析
|
||||||
Long total = coilDailyMapper.selectCoilTotalMergedCount(bo);
|
Long total = coilDailyMapper.selectCoilTotalMergedCount(bo);
|
||||||
java.util.List<CoilTotalCostVo> rows = coilDailyMapper.selectCoilTotalMerged(bo, offset, pageSize);
|
List<CoilTotalCostVo> rows = coilDailyMapper.selectCoilTotalMerged(bo, offset, pageSize);
|
||||||
Page<CoilTotalCostVo> page = new Page<>(pageNum, pageSize);
|
Page<CoilTotalCostVo> page = new Page<>(pageNum, pageSize);
|
||||||
page.setTotal(total == null ? 0L : total);
|
page.setTotal(total == null ? 0L : total);
|
||||||
page.setRecords(rows);
|
page.setRecords(rows);
|
||||||
|
|||||||
@@ -29,6 +29,73 @@
|
|||||||
SELECT * FROM wms_energy_coil_daily WHERE task_id = #{taskId}
|
SELECT * FROM wms_energy_coil_daily WHERE task_id = #{taskId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- ========================= -->
|
||||||
|
<!-- 公共筛选片段(卷号/时间范围) -->
|
||||||
|
<!-- 说明:尽量避免使用 ${} 以免引入 SQL 注入风险,所以按常用别名拆分多个片段 -->
|
||||||
|
<!-- ========================= -->
|
||||||
|
<sql id="FilterCoilNo_Material_NoAlias">
|
||||||
|
<if test="bo != null and bo.enterCoilNo != null and bo.enterCoilNo != ''">
|
||||||
|
AND enter_coil_no LIKE CONCAT('%', #{bo.enterCoilNo}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="bo != null and bo.currentCoilNo != null and bo.currentCoilNo != ''">
|
||||||
|
AND current_coil_no LIKE CONCAT('%', #{bo.currentCoilNo}, '%')
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="FilterDate_CreateTime_NoAlias">
|
||||||
|
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
||||||
|
AND create_time <![CDATA[>=]]> CONCAT(#{bo.startDate}, ' 00:00:00')
|
||||||
|
</if>
|
||||||
|
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
||||||
|
AND create_time <![CDATA[<=]]> CONCAT(#{bo.endDate}, ' 23:59:59')
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="FilterCoilNo_Pa">
|
||||||
|
<if test="bo != null and bo.enterCoilNo != null and bo.enterCoilNo != ''">
|
||||||
|
AND c.enter_coil_no LIKE CONCAT('%', #{bo.enterCoilNo}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="bo != null and bo.currentCoilNo != null and bo.currentCoilNo != ''">
|
||||||
|
AND pa.current_coil_no LIKE CONCAT('%', #{bo.currentCoilNo}, '%')
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="FilterDate_PaCreateTime">
|
||||||
|
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
||||||
|
AND pa.create_time <![CDATA[>=]]> CONCAT(#{bo.startDate}, ' 00:00:00')
|
||||||
|
</if>
|
||||||
|
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
||||||
|
AND pa.create_time <![CDATA[<=]]> CONCAT(#{bo.endDate}, ' 23:59:59')
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="FilterDate_PaAllCreateTime">
|
||||||
|
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
||||||
|
AND pa_all.create_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
||||||
|
</if>
|
||||||
|
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
||||||
|
AND pa_all.create_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="FilterDate_ChangeTime">
|
||||||
|
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
||||||
|
AND change_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
||||||
|
</if>
|
||||||
|
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
||||||
|
AND change_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 统一的“有效分钟数”表达式:基于库区抄表结束时间/完工时间择优 -->
|
||||||
|
<sql id="ExprPaEffectiveMinutes">
|
||||||
|
CASE
|
||||||
|
WHEN pa.create_time <![CDATA[<]]> COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
||||||
|
THEN TIMESTAMPDIFF(MINUTE, pa.create_time, COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW()))
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
</sql>
|
||||||
|
|
||||||
<!-- 入场卷号维度能源+囤积成本汇总(分页) -->
|
<!-- 入场卷号维度能源+囤积成本汇总(分页) -->
|
||||||
<select id="selectCoilTotalMerged" parameterType="map"
|
<select id="selectCoilTotalMerged" parameterType="map"
|
||||||
resultType="com.klp.ems.domain.vo.CoilTotalCostVo">
|
resultType="com.klp.ems.domain.vo.CoilTotalCostVo">
|
||||||
@@ -54,18 +121,8 @@
|
|||||||
WHERE data_type = 1
|
WHERE data_type = 1
|
||||||
AND del_flag = 0
|
AND del_flag = 0
|
||||||
AND status = 0
|
AND status = 0
|
||||||
<if test="bo != null and bo.enterCoilNo != null and bo.enterCoilNo != ''">
|
<include refid="FilterCoilNo_Material_NoAlias"/>
|
||||||
AND enter_coil_no LIKE CONCAT('%', #{bo.enterCoilNo}, '%')
|
<include refid="FilterDate_CreateTime_NoAlias"/>
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.currentCoilNo != null and bo.currentCoilNo != ''">
|
|
||||||
AND current_coil_no LIKE CONCAT('%', #{bo.currentCoilNo}, '%')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
|
||||||
AND create_time <![CDATA[>=]]> CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND create_time <![CDATA[<=]]> CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY current_coil_no
|
GROUP BY current_coil_no
|
||||||
) m
|
) m
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -73,14 +130,10 @@
|
|||||||
pa.current_coil_no,
|
pa.current_coil_no,
|
||||||
COUNT(DISTINCT pa.coil_id) AS coil_count,
|
COUNT(DISTINCT pa.coil_id) AS coil_count,
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CAST(
|
||||||
WHEN pa.create_time <![CDATA[<]]> COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
<include refid="ExprPaEffectiveMinutes"/>
|
||||||
THEN CAST(TIMESTAMPDIFF(MINUTE,
|
AS DECIMAL(20,6)
|
||||||
pa.create_time,
|
) / 60.0
|
||||||
COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
) AS DECIMAL(20,6)) / 60.0
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
) AS total_duration,
|
) AS total_duration,
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CASE
|
||||||
@@ -89,14 +142,7 @@
|
|||||||
AND wd_prod.total_prod_minutes > 0
|
AND wd_prod.total_prod_minutes > 0
|
||||||
THEN wd.total_warehouse_consumption *
|
THEN wd.total_warehouse_consumption *
|
||||||
(CAST(
|
(CAST(
|
||||||
CASE
|
<include refid="ExprPaEffectiveMinutes"/> AS DECIMAL(20,6)
|
||||||
WHEN pa.create_time <![CDATA[<]]> COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
THEN TIMESTAMPDIFF(MINUTE,
|
|
||||||
pa.create_time,
|
|
||||||
COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
)
|
|
||||||
ELSE 0
|
|
||||||
END AS DECIMAL(20,6)
|
|
||||||
) / wd_prod.total_prod_minutes)
|
) / wd_prod.total_prod_minutes)
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END
|
||||||
@@ -108,14 +154,7 @@
|
|||||||
AND wd_prod.total_prod_minutes > 0
|
AND wd_prod.total_prod_minutes > 0
|
||||||
THEN wd.total_warehouse_cost *
|
THEN wd.total_warehouse_cost *
|
||||||
(CAST(
|
(CAST(
|
||||||
CASE
|
<include refid="ExprPaEffectiveMinutes"/> AS DECIMAL(20,6)
|
||||||
WHEN pa.create_time <![CDATA[<]]> COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
THEN TIMESTAMPDIFF(MINUTE,
|
|
||||||
pa.create_time,
|
|
||||||
COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
)
|
|
||||||
ELSE 0
|
|
||||||
END AS DECIMAL(20,6)
|
|
||||||
) / wd_prod.total_prod_minutes)
|
) / wd_prod.total_prod_minutes)
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END
|
||||||
@@ -160,18 +199,8 @@
|
|||||||
WHERE pa.action_status IN (0, 1, 2)
|
WHERE pa.action_status IN (0, 1, 2)
|
||||||
and pa.del_flag='0'
|
and pa.del_flag='0'
|
||||||
AND pa.warehouse_id IS NOT NULL
|
AND pa.warehouse_id IS NOT NULL
|
||||||
<if test="bo != null and bo.enterCoilNo != null and bo.enterCoilNo != ''">
|
<include refid="FilterCoilNo_Pa"/>
|
||||||
AND c.enter_coil_no LIKE CONCAT('%', #{bo.enterCoilNo}, '%')
|
<include refid="FilterDate_PaCreateTime"/>
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.currentCoilNo != null and bo.currentCoilNo != ''">
|
|
||||||
AND pa.current_coil_no LIKE CONCAT('%', #{bo.currentCoilNo}, '%')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
|
||||||
AND pa.create_time <![CDATA[>=]]> CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND pa.create_time <![CDATA[<=]]> CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY pa.current_coil_no
|
GROUP BY pa.current_coil_no
|
||||||
) e ON m.current_coil_no = e.current_coil_no
|
) e ON m.current_coil_no = e.current_coil_no
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -253,27 +282,12 @@
|
|||||||
) AS total_minutes
|
) AS total_minutes
|
||||||
FROM wms_coil_pending_action pa_all
|
FROM wms_coil_pending_action pa_all
|
||||||
WHERE pa_all.action_status IN (0,1,2)
|
WHERE pa_all.action_status IN (0,1,2)
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
<include refid="FilterDate_PaAllCreateTime"/>
|
||||||
AND pa_all.create_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND pa_all.create_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY DATE(pa_all.create_time)
|
GROUP BY DATE(pa_all.create_time)
|
||||||
) td ON td.day_key = DATE(pa.create_time)
|
) td ON td.day_key = DATE(pa.create_time)
|
||||||
WHERE pa.action_status IN (0,1,2)
|
WHERE pa.action_status IN (0,1,2)
|
||||||
<if test="bo != null and bo.enterCoilNo != null and bo.enterCoilNo != ''">
|
<include refid="FilterCoilNo_Pa"/>
|
||||||
AND c.enter_coil_no LIKE CONCAT('%', #{bo.enterCoilNo}, '%')
|
<include refid="FilterDate_PaCreateTime"/>
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.currentCoilNo != null and bo.currentCoilNo != ''">
|
|
||||||
AND pa.current_coil_no LIKE CONCAT('%', #{bo.currentCoilNo}, '%')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
|
||||||
AND pa.create_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND pa.create_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY DATE(pa.create_time), pa.current_coil_no, td.total_minutes
|
GROUP BY DATE(pa.create_time), pa.current_coil_no, td.total_minutes
|
||||||
) dm
|
) dm
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -283,12 +297,7 @@
|
|||||||
FROM eqp_auxiliary_material_change
|
FROM eqp_auxiliary_material_change
|
||||||
WHERE del_flag = '0'
|
WHERE del_flag = '0'
|
||||||
AND change_type = '减少'
|
AND change_type = '减少'
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
<include refid="FilterDate_ChangeTime"/>
|
||||||
AND change_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND change_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY DATE(change_time)
|
GROUP BY DATE(change_time)
|
||||||
) a ON dm.day_key = a.day_key
|
) a ON dm.day_key = a.day_key
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -298,17 +307,11 @@
|
|||||||
FROM eqp_spare_parts_change
|
FROM eqp_spare_parts_change
|
||||||
WHERE del_flag = '0'
|
WHERE del_flag = '0'
|
||||||
AND change_type = '减少'
|
AND change_type = '减少'
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
<include refid="FilterDate_ChangeTime"/>
|
||||||
AND change_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND change_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY DATE(change_time)
|
GROUP BY DATE(change_time)
|
||||||
) p ON dm.day_key = p.day_key
|
) p ON dm.day_key = p.day_key
|
||||||
GROUP BY dm.current_coil_no
|
GROUP BY dm.current_coil_no
|
||||||
) x ON m.current_coil_no = x.current_coil_no
|
) x ON m.current_coil_no = x.current_coil_no
|
||||||
WHERE s.current_coil_no IS NOT NULL
|
|
||||||
ORDER BY totalCost DESC
|
ORDER BY totalCost DESC
|
||||||
LIMIT #{offset}, #{pageSize}
|
LIMIT #{offset}, #{pageSize}
|
||||||
</select>
|
</select>
|
||||||
@@ -367,18 +370,8 @@
|
|||||||
WHERE data_type IN (0,1)
|
WHERE data_type IN (0,1)
|
||||||
AND del_flag = 0
|
AND del_flag = 0
|
||||||
AND status = 0
|
AND status = 0
|
||||||
<if test="bo != null and bo.enterCoilNo != null and bo.enterCoilNo != ''">
|
<include refid="FilterCoilNo_Material_NoAlias"/>
|
||||||
AND enter_coil_no LIKE CONCAT('%', #{bo.enterCoilNo}, '%')
|
<include refid="FilterDate_CreateTime_NoAlias"/>
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.currentCoilNo != null and bo.currentCoilNo != ''">
|
|
||||||
AND current_coil_no LIKE CONCAT('%', #{bo.currentCoilNo}, '%')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
|
||||||
AND create_time <![CDATA[>=]]> CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND create_time <![CDATA[<=]]> CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY current_coil_no
|
GROUP BY current_coil_no
|
||||||
) m
|
) m
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -386,14 +379,10 @@
|
|||||||
pa.current_coil_no,
|
pa.current_coil_no,
|
||||||
COUNT(DISTINCT pa.coil_id) AS coil_count,
|
COUNT(DISTINCT pa.coil_id) AS coil_count,
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CAST(
|
||||||
WHEN pa.create_time <![CDATA[<]]> COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
<include refid="ExprPaEffectiveMinutes"/>
|
||||||
THEN CAST(TIMESTAMPDIFF(MINUTE,
|
AS DECIMAL(20,6)
|
||||||
pa.create_time,
|
) / 60.0
|
||||||
COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
) AS DECIMAL(20,6)) / 60.0
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
) AS total_duration,
|
) AS total_duration,
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CASE
|
||||||
@@ -402,14 +391,7 @@
|
|||||||
AND wd_prod.total_prod_minutes > 0
|
AND wd_prod.total_prod_minutes > 0
|
||||||
THEN wd.total_warehouse_consumption *
|
THEN wd.total_warehouse_consumption *
|
||||||
(CAST(
|
(CAST(
|
||||||
CASE
|
<include refid="ExprPaEffectiveMinutes"/> AS DECIMAL(20,6)
|
||||||
WHEN pa.create_time <![CDATA[<]]> COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
THEN TIMESTAMPDIFF(MINUTE,
|
|
||||||
pa.create_time,
|
|
||||||
COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
)
|
|
||||||
ELSE 0
|
|
||||||
END AS DECIMAL(20,6)
|
|
||||||
) / wd_prod.total_prod_minutes)
|
) / wd_prod.total_prod_minutes)
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END
|
||||||
@@ -421,14 +403,7 @@
|
|||||||
AND wd_prod.total_prod_minutes > 0
|
AND wd_prod.total_prod_minutes > 0
|
||||||
THEN wd.total_warehouse_cost *
|
THEN wd.total_warehouse_cost *
|
||||||
(CAST(
|
(CAST(
|
||||||
CASE
|
<include refid="ExprPaEffectiveMinutes"/> AS DECIMAL(20,6)
|
||||||
WHEN pa.create_time <![CDATA[<]]> COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
THEN TIMESTAMPDIFF(MINUTE,
|
|
||||||
pa.create_time,
|
|
||||||
COALESCE(pa.complete_time, wmt.warehouse_end_time, NOW())
|
|
||||||
)
|
|
||||||
ELSE 0
|
|
||||||
END AS DECIMAL(20,6)
|
|
||||||
) / wd_prod.total_prod_minutes)
|
) / wd_prod.total_prod_minutes)
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END
|
END
|
||||||
@@ -472,6 +447,8 @@
|
|||||||
) wd_prod ON pa.warehouse_id = wd_prod.warehouse_id
|
) wd_prod ON pa.warehouse_id = wd_prod.warehouse_id
|
||||||
WHERE pa.action_status IN (0, 1, 2)
|
WHERE pa.action_status IN (0, 1, 2)
|
||||||
AND pa.warehouse_id IS NOT NULL
|
AND pa.warehouse_id IS NOT NULL
|
||||||
|
<include refid="FilterCoilNo_Pa"/>
|
||||||
|
<include refid="FilterDate_PaCreateTime"/>
|
||||||
GROUP BY c.enter_coil_no
|
GROUP BY c.enter_coil_no
|
||||||
) e ON m.enter_coil_no = e.enter_coil_no
|
) e ON m.enter_coil_no = e.enter_coil_no
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -541,21 +518,11 @@
|
|||||||
) AS total_minutes
|
) AS total_minutes
|
||||||
FROM wms_coil_pending_action pa_all
|
FROM wms_coil_pending_action pa_all
|
||||||
WHERE pa_all.action_status IN (0,1,2)
|
WHERE pa_all.action_status IN (0,1,2)
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
<include refid="FilterDate_PaAllCreateTime"/>
|
||||||
AND pa_all.create_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND pa_all.create_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY DATE(pa_all.create_time)
|
GROUP BY DATE(pa_all.create_time)
|
||||||
) td ON td.day_key = DATE(pa.create_time)
|
) td ON td.day_key = DATE(pa.create_time)
|
||||||
WHERE pa.action_status IN (0,1,2)
|
WHERE pa.action_status IN (0,1,2)
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
<include refid="FilterDate_PaCreateTime"/>
|
||||||
AND pa.create_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND pa.create_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY DATE(pa.create_time), c.enter_coil_no, td.total_minutes
|
GROUP BY DATE(pa.create_time), c.enter_coil_no, td.total_minutes
|
||||||
) dm
|
) dm
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -565,12 +532,7 @@
|
|||||||
FROM eqp_auxiliary_material_change
|
FROM eqp_auxiliary_material_change
|
||||||
WHERE del_flag = '0'
|
WHERE del_flag = '0'
|
||||||
AND change_type = '减少'
|
AND change_type = '减少'
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
<include refid="FilterDate_ChangeTime"/>
|
||||||
AND change_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND change_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY DATE(change_time)
|
GROUP BY DATE(change_time)
|
||||||
) a ON dm.day_key = a.day_key
|
) a ON dm.day_key = a.day_key
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -580,12 +542,7 @@
|
|||||||
FROM eqp_spare_parts_change
|
FROM eqp_spare_parts_change
|
||||||
WHERE del_flag = '0'
|
WHERE del_flag = '0'
|
||||||
AND change_type = '减少'
|
AND change_type = '减少'
|
||||||
<if test="bo != null and bo.startDate != null and bo.startDate != ''">
|
<include refid="FilterDate_ChangeTime"/>
|
||||||
AND change_time >= CONCAT(#{bo.startDate}, ' 00:00:00')
|
|
||||||
</if>
|
|
||||||
<if test="bo != null and bo.endDate != null and bo.endDate != ''">
|
|
||||||
AND change_time <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
|
||||||
</if>
|
|
||||||
GROUP BY DATE(change_time)
|
GROUP BY DATE(change_time)
|
||||||
) p ON dm.day_key = p.day_key
|
) p ON dm.day_key = p.day_key
|
||||||
GROUP BY dm.enter_coil_no
|
GROUP BY dm.enter_coil_no
|
||||||
|
|||||||
39
klp-ui/src/api/da/oee.js
Normal file
39
klp-ui/src/api/da/oee.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// OEE 汇总(两条产线 KPI + 日趋势)
|
||||||
|
export function fetchOeeSummary(query) {
|
||||||
|
return request({
|
||||||
|
url: '/oee/line/summary',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7 大损失汇总
|
||||||
|
export function fetchOeeLoss7(query) {
|
||||||
|
return request({
|
||||||
|
url: '/oee/line/loss7',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停机/损失事件明细
|
||||||
|
export function fetchOeeEvents(query) {
|
||||||
|
return request({
|
||||||
|
url: '/oee/line/events',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出 Word 报表
|
||||||
|
export function exportOeeWord(query) {
|
||||||
|
return request({
|
||||||
|
url: '/oee/line/exportWord',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -89,13 +89,13 @@ export function batchCalculateCost(calcDate) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按入场钢卷号维度计算成本
|
// 按当前钢卷号维度计算成本(单卷详情)
|
||||||
export function calculateCostByEnterCoilNo(enterCoilNo, calcDate) {
|
export function calculateCostByCurrentCoilNo(currentCoilNo, calcDate) {
|
||||||
return request({
|
return request({
|
||||||
url: '/wms/cost/coil/calculateByEnterCoilNo',
|
url: '/wms/cost/coil/calculateByCurrentCoilNo',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: {
|
params: {
|
||||||
enterCoilNo,
|
currentCoilNo,
|
||||||
calcDate
|
calcDate
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
384
klp-ui/src/views/da/oee/index.vue
Normal file
384
klp-ui/src/views/da/oee/index.vue
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
<template>
|
||||||
|
<div class="oee-page">
|
||||||
|
<!-- 查询条件 -->
|
||||||
|
<el-card class="oee-card query-card" shadow="never">
|
||||||
|
<el-form :inline="true" :model="queryParams" size="small">
|
||||||
|
<el-form-item label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
value-format="yyyy-MM-dd"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产线">
|
||||||
|
<el-select v-model="queryParams.lineIds" multiple collapse-tags style="min-width: 200px">
|
||||||
|
<el-option label="酸轧线" value="SY" />
|
||||||
|
<el-option label="镀锌一线" value="DX1" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="success" icon="el-icon-printer" @click="handlePrint">打印报表</el-button>
|
||||||
|
<el-button type="warning" icon="el-icon-document" @click="handleExportWord">导出 Word</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 1. 指标总览(报告形式表格) -->
|
||||||
|
<el-card class="oee-card kpi-card" shadow="never">
|
||||||
|
<div class="card-header">一、产线 OEE 指标总览</div>
|
||||||
|
<el-table :data="lineSummary" border size="small">
|
||||||
|
<el-table-column prop="lineName" label="产线" width="120" />
|
||||||
|
<el-table-column label="OEE">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ toPercent(scope.row.total && scope.row.total.oee) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="时间稼动率 A">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ toPercent(scope.row.total && scope.row.total.availability) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="性能稼动率 P">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ toPercent(scope.row.total && scope.row.total.performance) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="良品率 Q">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ toPercent(scope.row.total && scope.row.total.quality) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="负荷时间(min)" width="130">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ (scope.row.total && scope.row.total.loadingTimeMin) || 0 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="停机时间(min)" width="130">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ (scope.row.total && scope.row.total.downtimeMin) || 0 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="运转时间(min)" width="130">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ (scope.row.total && scope.row.total.runTimeMin) || 0 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 2. 趋势(报告中的图 1 占位) -->
|
||||||
|
<el-card class="oee-card chart-card" shadow="never">
|
||||||
|
<div class="card-header">二、OEE 与三大指标日趋势(图 1,占位)</div>
|
||||||
|
<div class="chart-placeholder">
|
||||||
|
<span>图 1:OEE / A / P / Q 日趋势曲线(后续接入图表组件)</span>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 3. 7 大损失 -->
|
||||||
|
<el-card class="oee-card loss-card" shadow="never">
|
||||||
|
<div class="card-header">三、7 大损失汇总</div>
|
||||||
|
<el-tabs v-model="activeLossLine">
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="line in lossByLine"
|
||||||
|
:key="line.lineId"
|
||||||
|
:label="line.lineName"
|
||||||
|
:name="line.lineId"
|
||||||
|
>
|
||||||
|
<el-table :data="line.losses" border size="small">
|
||||||
|
<el-table-column prop="lossCategoryName" label="损失类别" />
|
||||||
|
<el-table-column prop="lossTimeMin" label="损失时间(min)" width="140" />
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 4. 事件明细 -->
|
||||||
|
<el-card class="oee-card event-card" shadow="never">
|
||||||
|
<div class="card-header">停机 / 损失事件明细</div>
|
||||||
|
<el-table :data="eventList" border size="small">
|
||||||
|
<el-table-column prop="lineName" label="产线" width="100" />
|
||||||
|
<el-table-column prop="eventStartTime" label="开始时间" width="160" />
|
||||||
|
<el-table-column prop="eventEndTime" label="结束时间" width="160" />
|
||||||
|
<el-table-column prop="durationMin" label="时长(min)" width="100" />
|
||||||
|
<el-table-column prop="lossCategoryName" label="损失类别" width="120" />
|
||||||
|
<el-table-column prop="rawReasonName" label="原因" />
|
||||||
|
</el-table>
|
||||||
|
<pagination
|
||||||
|
v-show="eventTotal > 0"
|
||||||
|
:total="eventTotal"
|
||||||
|
:page.sync="eventQuery.pageNum"
|
||||||
|
:limit.sync="eventQuery.pageSize"
|
||||||
|
@pagination="loadEvents"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 5. 公式与口径说明(MathType 风格) -->
|
||||||
|
<el-card class="oee-card formula-card" shadow="never">
|
||||||
|
<div class="card-header">五、OEE 公式与口径说明</div>
|
||||||
|
<div class="formula-content">
|
||||||
|
<div class="formula-row">
|
||||||
|
<span class="formula-label">总公式:</span>
|
||||||
|
<div class="equation">
|
||||||
|
<span class="var">OEE</span>
|
||||||
|
<span>=</span>
|
||||||
|
<span class="var">A</span>
|
||||||
|
<span>×</span>
|
||||||
|
<span class="var">P</span>
|
||||||
|
<span>×</span>
|
||||||
|
<span class="var">Q</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="formula-row">
|
||||||
|
<span class="formula-label">时间稼动率 A:</span>
|
||||||
|
<div class="equation">
|
||||||
|
<span class="var">A</span>
|
||||||
|
<span>=</span>
|
||||||
|
<span class="frac">
|
||||||
|
<span class="num">负荷时间 − 停机时间</span>
|
||||||
|
<span class="line"></span>
|
||||||
|
<span class="den">负荷时间</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="formula-row">
|
||||||
|
<span class="formula-label">性能稼动率 P:</span>
|
||||||
|
<div class="equation">
|
||||||
|
<span class="var">P</span>
|
||||||
|
<span>=</span>
|
||||||
|
<span class="frac">
|
||||||
|
<span class="num">理论加工时间 × 加工数量</span>
|
||||||
|
<span class="line"></span>
|
||||||
|
<span class="den">实际运转时间</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="formula-note">
|
||||||
|
注:当前阶段按 P = 1 处理,待接入标准节拍 / 标准速度后启用上述公式。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="formula-row">
|
||||||
|
<span class="formula-label">良品率 Q:</span>
|
||||||
|
<div class="equation">
|
||||||
|
<span class="var">Q</span>
|
||||||
|
<span>=</span>
|
||||||
|
<span class="frac">
|
||||||
|
<span class="num">良品数</span>
|
||||||
|
<span class="line"></span>
|
||||||
|
<span class="den">总产量</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="formula-note">
|
||||||
|
注:本系统中,良品数通过钢卷质量状态 <code>quality_status = 0</code> 统计,总产量为对应库区内所有钢卷数量。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { fetchOeeSummary, fetchOeeLoss7, fetchOeeEvents, exportOeeWord } from '@/api/da/oee'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OeeReport',
|
||||||
|
data() {
|
||||||
|
const today = this.$moment ? this.$moment().format('YYYY-MM-DD') : ''
|
||||||
|
return {
|
||||||
|
dateRange: [today, today],
|
||||||
|
queryParams: {
|
||||||
|
startDate: today,
|
||||||
|
endDate: today,
|
||||||
|
lineIds: ['SY', 'DX1']
|
||||||
|
},
|
||||||
|
lineSummary: [],
|
||||||
|
lossByLine: [],
|
||||||
|
activeLossLine: 'SY',
|
||||||
|
eventList: [],
|
||||||
|
eventTotal: 0,
|
||||||
|
eventQuery: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.handleQuery()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buildBaseQuery() {
|
||||||
|
const [start, end] = this.dateRange || []
|
||||||
|
return {
|
||||||
|
startDate: start,
|
||||||
|
endDate: end,
|
||||||
|
lineIds: (this.queryParams.lineIds || []).join(',')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleQuery() {
|
||||||
|
const baseQuery = this.buildBaseQuery()
|
||||||
|
this.loading = true
|
||||||
|
Promise.all([
|
||||||
|
fetchOeeSummary(baseQuery),
|
||||||
|
fetchOeeLoss7(baseQuery)
|
||||||
|
])
|
||||||
|
.then(([summaryRes, lossRes]) => {
|
||||||
|
this.lineSummary = summaryRes.data || []
|
||||||
|
this.lossByLine = (lossRes.data && lossRes.data.byLine) || []
|
||||||
|
if (this.lossByLine.length && !this.lossByLine.find(l => l.lineId === this.activeLossLine)) {
|
||||||
|
this.activeLossLine = this.lossByLine[0].lineId
|
||||||
|
}
|
||||||
|
this.eventQuery.pageNum = 1
|
||||||
|
this.loadEvents()
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resetQuery() {
|
||||||
|
const today = this.$moment ? this.$moment().format('YYYY-MM-DD') : ''
|
||||||
|
this.dateRange = [today, today]
|
||||||
|
this.queryParams.lineIds = ['SY', 'DX1']
|
||||||
|
this.handleQuery()
|
||||||
|
},
|
||||||
|
loadEvents() {
|
||||||
|
const baseQuery = this.buildBaseQuery()
|
||||||
|
const params = Object.assign({}, baseQuery, this.eventQuery)
|
||||||
|
fetchOeeEvents(params).then(res => {
|
||||||
|
this.eventList = res.rows || []
|
||||||
|
this.eventTotal = res.total || 0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
toPercent(v) {
|
||||||
|
if (v == null) return '-'
|
||||||
|
const num = Number(v)
|
||||||
|
if (isNaN(num)) return '-'
|
||||||
|
return (num * 100).toFixed(1) + '%'
|
||||||
|
},
|
||||||
|
handlePrint() {
|
||||||
|
window.print()
|
||||||
|
},
|
||||||
|
handleExportWord() {
|
||||||
|
const baseQuery = this.buildBaseQuery()
|
||||||
|
exportOeeWord(baseQuery).then(res => {
|
||||||
|
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' })
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = `OEE报表_${baseQuery.startDate}_${baseQuery.endDate}.docx`
|
||||||
|
a.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.oee-page {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.oee-card {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.line-kpi {
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
.line-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.kpi-items {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.kpi-item {
|
||||||
|
min-width: 120px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.kpi-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
.kpi-value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.kpi-value.primary {
|
||||||
|
color: #409eff;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.kpi-footer {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.kpi-footer span {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.chart-placeholder {
|
||||||
|
height: 260px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #909399;
|
||||||
|
border: 1px dashed #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.formula-content {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.formula-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.formula-label {
|
||||||
|
min-width: 110px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.equation {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.equation .var {
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
.frac {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 4px;
|
||||||
|
font-family: 'Times New Roman', serif;
|
||||||
|
}
|
||||||
|
.frac .num,
|
||||||
|
.frac .den {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.frac .line {
|
||||||
|
border-top: 1px solid #303133;
|
||||||
|
width: 100%;
|
||||||
|
margin: 1px 0;
|
||||||
|
}
|
||||||
|
.formula-note {
|
||||||
|
margin-left: 110px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
<el-card class="block-card">
|
<el-card class="block-card">
|
||||||
<div slot="header" class="clearfix">
|
<div slot="header" class="clearfix">
|
||||||
<span class="card-title">成本汇总(按入场卷号)</span>
|
<span class="card-title">成本汇总</span>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="mergedRows" border stripe>
|
<el-table :data="mergedRows" border stripe>
|
||||||
<el-table-column prop="enterCoilNo" label="入场卷号"></el-table-column>
|
<el-table-column prop="enterCoilNo" label="入场卷号"></el-table-column>
|
||||||
@@ -257,6 +257,7 @@ export default {
|
|||||||
}
|
}
|
||||||
fetchCoilTotalMerged(params).then(res => {
|
fetchCoilTotalMerged(params).then(res => {
|
||||||
this.mergedRows = res.rows || []
|
this.mergedRows = res.rows || []
|
||||||
|
console.log(res)
|
||||||
this.mergedTotal = res.total || 0
|
this.mergedTotal = res.total || 0
|
||||||
|
|
||||||
const rows = this.mergedRows || []
|
const rows = this.mergedRows || []
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@sort-change="handleSortChange"
|
@sort-change="handleSortChange"
|
||||||
>
|
>
|
||||||
<el-table-column prop="enterCoilNo" label="入场钢卷号" />
|
<el-table-column prop="currentCoilNo" label="当前钢卷号" />
|
||||||
<el-table-column prop="coilCount" label="子钢卷数" align="right" />
|
<el-table-column prop="coilCount" label="子钢卷数" align="right" />
|
||||||
<el-table-column prop="totalGrossWeight" label="总毛重(吨)" align="right">
|
<el-table-column prop="totalGrossWeight" label="总毛重(吨)" align="right">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" fixed="right">
|
<el-table-column label="操作" fixed="right">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button type="text" size="small" @click="viewCoilDetail(scope.row)">查看子钢卷成本</el-button>
|
<el-button type="text" size="small" @click="viewCoilDetail(scope.row)">查看详情</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -126,56 +126,45 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 入场钢卷号下子钢卷详情对话框 -->
|
<!-- 当前钢卷成本详情对话框 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
title="入场钢卷号下子钢卷成本详情"
|
title="当前钢卷成本详情"
|
||||||
:visible.sync="showDetailDialog"
|
:visible.sync="showDetailDialog"
|
||||||
width="900px"
|
width="900px"
|
||||||
>
|
>
|
||||||
<div v-if="detailEnterCoilNo" style="margin-bottom: 10px;">
|
<div v-if="detailInfo">
|
||||||
入场钢卷号:<strong>{{ detailEnterCoilNo }}</strong>
|
<el-form label-width="140px" size="small">
|
||||||
</div>
|
<el-form-item label="当前钢卷号">
|
||||||
<el-table :data="detailList" stripe style="width: 100%">
|
<span>{{ detailCoilNo }}</span>
|
||||||
<el-table-column prop="currentCoilNo" label="当前钢卷号" />
|
</el-form-item>
|
||||||
<el-table-column prop="grossWeightTon" label="毛重(吨)" align="right">
|
<el-form-item label="毛重(吨)">
|
||||||
<template slot-scope="scope">
|
<span>{{ formatWeight(detailInfo.grossWeightTon) }}</span>
|
||||||
{{ formatWeight(scope.row.grossWeightTon) }}
|
</el-form-item>
|
||||||
</template>
|
<el-form-item label="净重(吨)">
|
||||||
</el-table-column>
|
<span>{{ formatWeight(detailInfo.netWeightTon) }}</span>
|
||||||
<el-table-column prop="netWeightTon" label="净重(吨)" align="right">
|
</el-form-item>
|
||||||
<template slot-scope="scope">
|
<el-form-item label="在库天数">
|
||||||
{{ formatWeight(scope.row.netWeightTon) }}
|
<span :class="getStorageDaysClass(detailInfo.storageDays)">
|
||||||
</template>
|
{{ detailInfo.storageDays || '-' }}
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="storageDays" label="在库天数" align="right">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<span :class="getStorageDaysClass(scope.row.storageDays)">
|
|
||||||
{{ scope.row.storageDays || '-' }}
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</el-form-item>
|
||||||
</el-table-column>
|
<el-form-item label="单位成本(元/吨/天)">
|
||||||
<el-table-column prop="unitCost" label="单位成本(元/吨/天)" align="right">
|
<span>{{ formatMoney(detailInfo.unitCost) }}</span>
|
||||||
<template slot-scope="scope">
|
</el-form-item>
|
||||||
{{ formatMoney(scope.row.unitCost) }}
|
<el-form-item label="日成本(元)">
|
||||||
</template>
|
<span>{{ formatMoney(detailInfo.dailyCost) }}</span>
|
||||||
</el-table-column>
|
</el-form-item>
|
||||||
<el-table-column prop="dailyCost" label="日成本(元)" align="right">
|
<el-form-item label="累计成本(元)">
|
||||||
<template slot-scope="scope">
|
<span class="cost-total">{{ formatMoney(detailInfo.totalCost) }}</span>
|
||||||
{{ formatMoney(scope.row.dailyCost) }}
|
</el-form-item>
|
||||||
</template>
|
</el-form>
|
||||||
</el-table-column>
|
</div>
|
||||||
<el-table-column prop="totalCost" label="累计成本(元)" align="right">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<span class="cost-total">{{ formatMoney(scope.row.totalCost) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { calculateCostByEnterCoilNo, getStockpileCostList } from '@/api/wms/cost'
|
import { calculateCostByCurrentCoilNo, getStockpileCostList } from '@/api/wms/cost'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CostStockpile',
|
name: 'CostStockpile',
|
||||||
@@ -197,8 +186,8 @@ export default {
|
|||||||
currentCoilNo: null
|
currentCoilNo: null
|
||||||
},
|
},
|
||||||
showDetailDialog: false,
|
showDetailDialog: false,
|
||||||
detailEnterCoilNo: null,
|
detailCoilNo: null,
|
||||||
detailList: []
|
detailInfo: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -272,10 +261,10 @@ export default {
|
|||||||
},
|
},
|
||||||
async viewCoilDetail(row) {
|
async viewCoilDetail(row) {
|
||||||
try {
|
try {
|
||||||
const res = await calculateCostByEnterCoilNo(row.enterCoilNo, null)
|
const res = await calculateCostByCurrentCoilNo(row.currentCoilNo, null)
|
||||||
if (res.code === 200 && res.data && !res.data.error) {
|
if (res.code === 200 && res.data && !res.data.error) {
|
||||||
this.detailEnterCoilNo = row.enterCoilNo
|
this.detailCoilNo = row.currentCoilNo
|
||||||
this.detailList = res.data.coilDetails || []
|
this.detailInfo = res.data
|
||||||
this.showDetailDialog = true
|
this.showDetailDialog = true
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.data?.error || '加载详情失败')
|
this.$message.error(res.data?.error || '加载详情失败')
|
||||||
|
|||||||
@@ -204,6 +204,22 @@ public class WmsCostCoilDailyController extends BaseController {
|
|||||||
return R.ok(result);
|
return R.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按当前钢卷号维度计算成本(单卷详情)
|
||||||
|
*
|
||||||
|
* @param currentCoilNo 当前钢卷号
|
||||||
|
* @param calcDate 计算日期(可选,默认当前日期)
|
||||||
|
*/
|
||||||
|
@PostMapping("/calculateByCurrentCoilNo")
|
||||||
|
public R<Map<String, Object>> calculateCostByCurrentCoilNo(@RequestParam String currentCoilNo,
|
||||||
|
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate calcDate) {
|
||||||
|
Map<String, Object> result = iWmsCostCoilDailyService.calculateCostByCurrentCoilNo(currentCoilNo, calcDate);
|
||||||
|
if (result.containsKey("error")) {
|
||||||
|
return R.fail(result.get("error").toString());
|
||||||
|
}
|
||||||
|
return R.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 现算成本检索(基于 wms_material_coil)
|
* 现算成本检索(基于 wms_material_coil)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -106,6 +106,15 @@ public interface IWmsCostCoilDailyService {
|
|||||||
*/
|
*/
|
||||||
Map<String, Object> calculateCostByEnterCoilNo(String enterCoilNo, LocalDate calcDate);
|
Map<String, Object> calculateCostByEnterCoilNo(String enterCoilNo, LocalDate calcDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按当前钢卷号维度计算成本(单卷详情)
|
||||||
|
*
|
||||||
|
* @param currentCoilNo 当前钢卷号
|
||||||
|
* @param calcDate 计算日期(可选,默认当前日期)
|
||||||
|
* @return 单卷成本详情
|
||||||
|
*/
|
||||||
|
Map<String, Object> calculateCostByCurrentCoilNo(String currentCoilNo, LocalDate calcDate);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量按入场钢卷号维度计算成本(定时任务使用)
|
* 批量按入场钢卷号维度计算成本(定时任务使用)
|
||||||
* 按入场钢卷号分组,计算每个入场钢卷号的总成本
|
* 按入场钢卷号分组,计算每个入场钢卷号的总成本
|
||||||
|
|||||||
@@ -573,6 +573,95 @@ public class WmsCostCoilDailyServiceImpl implements IWmsCostCoilDailyService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按当前钢卷号维度计算成本(单卷详情)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> calculateCostByCurrentCoilNo(String currentCoilNo, LocalDate calcDate) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("currentCoilNo", currentCoilNo);
|
||||||
|
|
||||||
|
if (calcDate == null) {
|
||||||
|
calcDate = LocalDate.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询单个钢卷
|
||||||
|
QueryWrapper<WmsMaterialCoil> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("current_coil_no", currentCoilNo)
|
||||||
|
.eq("data_type", 1)
|
||||||
|
.eq("del_flag", 0);
|
||||||
|
|
||||||
|
WmsMaterialCoil coil = coilMapper.selectOne(queryWrapper);
|
||||||
|
if (coil == null) {
|
||||||
|
result.put("error", "未找到当前钢卷号 " + currentCoilNo + " 的相关钢卷");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算在库天数
|
||||||
|
LocalDate startDate = coil.getCreateTime() != null
|
||||||
|
? coil.getCreateTime().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate()
|
||||||
|
: LocalDate.now();
|
||||||
|
|
||||||
|
LocalDate endDate;
|
||||||
|
if (coil.getExportTime() != null) {
|
||||||
|
endDate = coil.getExportTime().toInstant()
|
||||||
|
.atZone(java.time.ZoneId.systemDefault())
|
||||||
|
.toLocalDate()
|
||||||
|
.minusDays(1);
|
||||||
|
} else {
|
||||||
|
endDate = calcDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate.isBefore(startDate)) {
|
||||||
|
endDate = startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
|
||||||
|
if (days < 1) {
|
||||||
|
days = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成本标准
|
||||||
|
WmsCostStandardConfigVo costStandard = costStandardConfigService.queryEffectiveByDate(startDate);
|
||||||
|
if (costStandard == null) {
|
||||||
|
costStandard = costStandardConfigService.queryCurrentEffective();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (costStandard == null || costStandard.getUnitCost() == null) {
|
||||||
|
result.put("error", "未找到有效的成本标准配置");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal unitCost = costStandard.getUnitCost();
|
||||||
|
|
||||||
|
// 重量与成本基础
|
||||||
|
WeightContext weightContext = resolveWeightContext(coil);
|
||||||
|
if (!weightContext.isValid()) {
|
||||||
|
result.put("error", "钢卷缺少有效重量,无法计算");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal dailyCost = weightContext.getCostTon().multiply(unitCost).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
BigDecimal totalCost = dailyCost.multiply(BigDecimal.valueOf(days)).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
|
result.put("coilId", coil.getCoilId());
|
||||||
|
result.put("currentCoilNo", coil.getCurrentCoilNo());
|
||||||
|
result.put("isShipped", coil.getExportTime() != null);
|
||||||
|
result.put("netWeightTon", weightContext.getNetTon());
|
||||||
|
result.put("grossWeightTon", weightContext.getGrossTon());
|
||||||
|
result.put("weightBasis", weightContext.useGross() ? "gross" : "net");
|
||||||
|
result.put("storageDays", days);
|
||||||
|
result.put("unitCost", unitCost);
|
||||||
|
result.put("dailyCost", dailyCost);
|
||||||
|
result.put("totalCost", totalCost);
|
||||||
|
result.put("startDate", startDate);
|
||||||
|
result.put("endDate", endDate);
|
||||||
|
result.put("exportTime", coil.getExportTime());
|
||||||
|
result.put("calcDate", calcDate);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量按入场钢卷号维度计算成本(定时任务使用)
|
* 批量按入场钢卷号维度计算成本(定时任务使用)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -394,6 +394,20 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
|||||||
qw.eq(StringUtils.isNotBlank(bo.getTemperGrade()), "mc.temper_grade", bo.getTemperGrade());
|
qw.eq(StringUtils.isNotBlank(bo.getTemperGrade()), "mc.temper_grade", bo.getTemperGrade());
|
||||||
// 独占状态
|
// 独占状态
|
||||||
qw.eq(bo.getExclusiveStatus() != null, "mc.exclusive_status", bo.getExclusiveStatus());
|
qw.eq(bo.getExclusiveStatus() != null, "mc.exclusive_status", bo.getExclusiveStatus());
|
||||||
|
// 按创建时间范围筛选
|
||||||
|
if (bo.getByCreateTimeStart() != null) {
|
||||||
|
qw.ge("mc.create_time", bo.getByCreateTimeStart());
|
||||||
|
}
|
||||||
|
if (bo.getByCreateTimeEnd() != null) {
|
||||||
|
qw.le("mc.create_time", bo.getByCreateTimeEnd());
|
||||||
|
}
|
||||||
|
// 按发货时间范围筛选
|
||||||
|
if (bo.getByExportTimeStart() != null) {
|
||||||
|
qw.ge("mc.export_time", bo.getByExportTimeStart());
|
||||||
|
}
|
||||||
|
if (bo.getByExportTimeEnd() != null) {
|
||||||
|
qw.le("mc.export_time", bo.getByExportTimeEnd());
|
||||||
|
}
|
||||||
// 统一处理 warehouseId 与 warehouseIds:
|
// 统一处理 warehouseId 与 warehouseIds:
|
||||||
List<Long> warehouseIdList = new ArrayList<>();
|
List<Long> warehouseIdList = new ArrayList<>();
|
||||||
if (bo.getWarehouseId() != null) {
|
if (bo.getWarehouseId() != null) {
|
||||||
|
|||||||
@@ -237,9 +237,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 按入场钢卷号统计成本 -->
|
<!-- 按入场钢卷号统计成本 -->
|
||||||
|
<!-- 虽然方法名仍然包含 EnterCoilNo,但统计口径已统一为“当前钢卷号(current_coil_no)” -->
|
||||||
<select id="selectCostByEnterCoilNo" resultType="java.util.HashMap">
|
<select id="selectCostByEnterCoilNo" resultType="java.util.HashMap">
|
||||||
SELECT
|
SELECT
|
||||||
mc.enter_coil_no AS enterCoilNo,
|
mc.current_coil_no AS currentCoilNo,
|
||||||
COUNT(DISTINCT mc.coil_id) AS coilCount,
|
COUNT(DISTINCT mc.coil_id) AS coilCount,
|
||||||
COUNT(DISTINCT CASE WHEN mc.export_time IS NULL THEN mc.coil_id END) AS unshippedCount,
|
COUNT(DISTINCT CASE WHEN mc.export_time IS NULL THEN mc.coil_id END) AS unshippedCount,
|
||||||
COUNT(DISTINCT CASE WHEN mc.export_time IS NOT NULL THEN mc.coil_id END) AS shippedCount,
|
COUNT(DISTINCT CASE WHEN mc.export_time IS NOT NULL THEN mc.coil_id END) AS shippedCount,
|
||||||
@@ -253,15 +254,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
AND mc.data_type = 1
|
AND mc.data_type = 1
|
||||||
AND mc.del_flag = 0
|
AND mc.del_flag = 0
|
||||||
<if test="enterCoilNo != null and enterCoilNo != ''">
|
<if test="enterCoilNo != null and enterCoilNo != ''">
|
||||||
AND mc.enter_coil_no = #{enterCoilNo}
|
AND mc.current_coil_no = #{enterCoilNo}
|
||||||
</if>
|
</if>
|
||||||
GROUP BY mc.enter_coil_no
|
GROUP BY mc.current_coil_no
|
||||||
ORDER BY totalCost DESC
|
ORDER BY totalCost DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 囤积成本(按当前钢卷号聚合)列表 -->
|
||||||
<select id="selectStockpileByEnterCoilNo" resultType="java.util.HashMap">
|
<select id="selectStockpileByEnterCoilNo" resultType="java.util.HashMap">
|
||||||
SELECT
|
SELECT
|
||||||
t.enter_coil_no AS enterCoilNo,
|
t.current_coil_no AS currentCoilNo,
|
||||||
COUNT(*) AS coilCount,
|
COUNT(*) AS coilCount,
|
||||||
SUM(t.net_weight_ton) AS totalNetWeight,
|
SUM(t.net_weight_ton) AS totalNetWeight,
|
||||||
SUM(t.gross_weight_ton) AS totalGrossWeight,
|
SUM(t.gross_weight_ton) AS totalGrossWeight,
|
||||||
@@ -271,7 +273,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
MAX(CASE WHEN IFNULL(t.net_weight_ton, 0) = 0 THEN 1 ELSE 0 END) AS hasNetWeightZero
|
MAX(CASE WHEN IFNULL(t.net_weight_ton, 0) = 0 THEN 1 ELSE 0 END) AS hasNetWeightZero
|
||||||
FROM (
|
FROM (
|
||||||
SELECT
|
SELECT
|
||||||
m.enter_coil_no,
|
m.current_coil_no,
|
||||||
IFNULL(m.net_weight, 0) AS net_weight_ton,
|
IFNULL(m.net_weight, 0) AS net_weight_ton,
|
||||||
IFNULL(m.gross_weight, 0) AS gross_weight_ton,
|
IFNULL(m.gross_weight, 0) AS gross_weight_ton,
|
||||||
CASE
|
CASE
|
||||||
@@ -293,33 +295,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
AND m.del_flag = 0
|
AND m.del_flag = 0
|
||||||
AND m.export_time IS NULL
|
AND m.export_time IS NULL
|
||||||
<if test="enterCoilNo != null and enterCoilNo != ''">
|
<if test="enterCoilNo != null and enterCoilNo != ''">
|
||||||
AND m.enter_coil_no LIKE CONCAT(#{enterCoilNo}, '%')
|
AND m.current_coil_no LIKE CONCAT(#{enterCoilNo}, '%')
|
||||||
</if>
|
</if>
|
||||||
) t
|
) t
|
||||||
GROUP BY t.enter_coil_no
|
GROUP BY t.current_coil_no
|
||||||
ORDER BY t.enter_coil_no DESC
|
ORDER BY t.current_coil_no DESC
|
||||||
LIMIT #{offset}, #{pageSize}
|
LIMIT #{offset}, #{pageSize}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 囤积成本(按入场钢卷号聚合)列表总数 -->
|
<!-- 囤积成本(按当前钢卷号聚合)列表总数 -->
|
||||||
<select id="countStockpileByEnterCoilNo" resultType="long">
|
<select id="countStockpileByEnterCoilNo" resultType="long">
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) AS cnt
|
COUNT(*) AS cnt
|
||||||
FROM (
|
FROM (
|
||||||
SELECT m.enter_coil_no
|
SELECT m.current_coil_no
|
||||||
FROM wms_material_coil m
|
FROM wms_material_coil m
|
||||||
WHERE m.data_type = 1
|
WHERE m.data_type = 1
|
||||||
AND m.del_flag = 0
|
AND m.del_flag = 0
|
||||||
and m.status = 0
|
and m.status = 0
|
||||||
AND m.export_time IS NULL
|
AND m.export_time IS NULL
|
||||||
<if test="enterCoilNo != null and enterCoilNo != ''">
|
<if test="enterCoilNo != null and enterCoilNo != ''">
|
||||||
AND m.enter_coil_no LIKE CONCAT(#{enterCoilNo}, '%')
|
AND m.current_coil_no LIKE CONCAT(#{enterCoilNo}, '%')
|
||||||
</if>
|
</if>
|
||||||
GROUP BY m.enter_coil_no
|
GROUP BY m.current_coil_no
|
||||||
) t
|
) t
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 囤积成本(按入场钢卷号聚合)汇总 -->
|
<!-- 囤积成本(按当前钢卷号聚合)汇总 -->
|
||||||
<select id="selectStockpileSummaryByEnterCoilNo" resultType="java.util.HashMap">
|
<select id="selectStockpileSummaryByEnterCoilNo" resultType="java.util.HashMap">
|
||||||
SELECT
|
SELECT
|
||||||
SUM(t.net_weight_ton) AS totalNetWeight,
|
SUM(t.net_weight_ton) AS totalNetWeight,
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -389,6 +389,11 @@
|
|||||||
<!-- <artifactId>klp-hrm</artifactId>-->
|
<!-- <artifactId>klp-hrm</artifactId>-->
|
||||||
<!-- <version>${klp-flowable-plus.version}</version>-->
|
<!-- <version>${klp-flowable-plus.version}</version>-->
|
||||||
<!-- </dependency>-->
|
<!-- </dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.klp</groupId>
|
||||||
|
<artifactId>klp-da</artifactId>
|
||||||
|
<version>${klp-flowable-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.klp</groupId>
|
<groupId>com.klp</groupId>
|
||||||
<artifactId>klp-crm</artifactId>
|
<artifactId>klp-crm</artifactId>
|
||||||
@@ -418,6 +423,7 @@
|
|||||||
<module>klp-erp</module>
|
<module>klp-erp</module>
|
||||||
<!-- <module>klp-hrm</module>-->
|
<!-- <module>klp-hrm</module>-->
|
||||||
<module>klp-crm</module>
|
<module>klp-crm</module>
|
||||||
|
<module>klp-da</module>
|
||||||
</modules>
|
</modules>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ create table wms_material_coil
|
|||||||
item_id bigint not null comment '物品ID(指向原材料或产品主键)',
|
item_id bigint not null comment '物品ID(指向原材料或产品主键)',
|
||||||
item_type varchar(20) not null comment '物品类型(raw_material/product)',
|
item_type varchar(20) not null comment '物品类型(raw_material/product)',
|
||||||
material_type varchar(20) null comment '材料类型(废品,成品,原料)',
|
material_type varchar(20) null comment '材料类型(废品,成品,原料)',
|
||||||
quality_status varchar(20) null comment '质量状态(0=正常,1=待检,2=不合格)',
|
quality_status varchar(20) null comment '质量状态(C+,C,C-,D+,D,D-为次品,ABC*为良品)',
|
||||||
status tinyint(1) default 0 null comment '状态(0=在库,1=已出库)',
|
status tinyint(1) default 0 null comment '状态(0=在库,1=已出库)',
|
||||||
remark varchar(255) null comment '备注',
|
remark varchar(255) null comment '备注',
|
||||||
export_time datetime null comment '发货时间',
|
export_time datetime null comment '发货时间',
|
||||||
|
|||||||
Reference in New Issue
Block a user