酸轧OEE后端重构完成
This commit is contained in:
@@ -19,6 +19,12 @@
|
|||||||
<groupId>com.klp</groupId>
|
<groupId>com.klp</groupId>
|
||||||
<artifactId>klp-framework</artifactId>
|
<artifactId>klp-framework</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- OEE 聚合依赖 pocket 原子能力(酸轧/镀锌一线) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.klp</groupId>
|
||||||
|
<artifactId>klp-pocket</artifactId>
|
||||||
|
<version>0.8.3</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
|
|||||||
@@ -1,26 +1,43 @@
|
|||||||
package com.klp.da.controller;
|
package com.klp.da.controller;
|
||||||
|
|
||||||
import com.klp.common.annotation.Log;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.klp.common.core.controller.BaseController;
|
import com.klp.common.core.controller.BaseController;
|
||||||
|
import com.klp.common.core.domain.R;
|
||||||
|
import com.klp.common.core.page.TableDataInfo;
|
||||||
|
import com.klp.common.utils.StringUtils;
|
||||||
|
import com.klp.da.service.OeeReportJobService;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeRegressionVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
|
||||||
|
import com.klp.pocket.acid.service.IAcidOeeService;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OEE 报表聚合 Controller(方式 A:后端统一聚合多服务)
|
* OEE 报表对外接口(聚合层)
|
||||||
*
|
*
|
||||||
* 路由前缀与 docs/oee-report-design.md 设计文档保持一致:
|
* 当前阶段:主要暴露酸轧线相关接口,通过 `klp-pocket` 的 {@link IAcidOeeService} 取数。
|
||||||
* - /api/ems/oee/line/summary
|
* - 当月 summary / loss7 优先走 Redis 预计算缓存;
|
||||||
* - /api/ems/oee/line/loss7
|
* - 任意日期范围通过异步任务接口实现。
|
||||||
* - /api/ems/oee/line/events
|
|
||||||
* - /api/ems/oee/line/exportWord
|
|
||||||
*/
|
*/
|
||||||
@Validated
|
@Validated
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -28,6 +45,304 @@ import java.util.Map;
|
|||||||
@RequestMapping("/oee/line")
|
@RequestMapping("/oee/line")
|
||||||
public class OeeReportController extends BaseController {
|
public class OeeReportController extends BaseController {
|
||||||
|
|
||||||
|
private final IAcidOeeService acidOeeService;
|
||||||
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
|
private final OeeReportJobService oeeReportJobService;
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||||
|
|
||||||
|
private static final String JOB_META_KEY_PATTERN = "oee:report:job:meta:%s";
|
||||||
|
private static final String JOB_RESULT_KEY_PATTERN = "oee:report:job:result:%s";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线 OEE 日汇总(含趋势)
|
||||||
|
*
|
||||||
|
* 路由:GET /oee/line/acid/summary
|
||||||
|
* 说明:
|
||||||
|
* - 不接受 start/end 参数,固定返回“当前月份(1号~今天)”的当月预计算结果;
|
||||||
|
* - 优先从 Redis 当月缓存读取;若缓存缺失则实时计算一次当前月。
|
||||||
|
*/
|
||||||
|
@GetMapping("/acid/summary")
|
||||||
|
public R<List<AcidOeeDailySummaryVo>> getAcidSummary() {
|
||||||
|
String yyyyMM = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||||
|
String summaryKey = String.format("oee:report:month:summary:%s:SY", yyyyMM);
|
||||||
|
|
||||||
|
// 1. 优先从 Redis 读取当月预计算结果
|
||||||
|
String json = stringRedisTemplate.opsForValue().get(summaryKey);
|
||||||
|
if (StringUtils.isNotBlank(json)) {
|
||||||
|
List<AcidOeeDailySummaryVo> cached =
|
||||||
|
JSON.parseArray(json, AcidOeeDailySummaryVo.class);
|
||||||
|
return R.ok(cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 缓存缺失时,回退为实时计算当前月
|
||||||
|
String[] range = resolveDateRange(null, null);
|
||||||
|
List<AcidOeeDailySummaryVo> dailyList =
|
||||||
|
acidOeeService.getDailySummary(range[0], range[1]);
|
||||||
|
return R.ok(dailyList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线 7 大损失汇总
|
||||||
|
*
|
||||||
|
* 路由:GET /oee/line/acid/loss7
|
||||||
|
* 说明:
|
||||||
|
* - 不接受 start/end 参数,固定返回“当前月份(1号~今天)”的当月预计算结果;
|
||||||
|
* - {@code topN} 用于限制返回的损失类别条数(按损失时间降序截取)。
|
||||||
|
*/
|
||||||
|
@GetMapping("/acid/loss7")
|
||||||
|
public R<List<AcidOeeLoss7Vo>> getAcidLoss7(
|
||||||
|
@RequestParam(required = false, defaultValue = "50") Integer topN
|
||||||
|
) {
|
||||||
|
String yyyyMM = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||||
|
String loss7Key = String.format("oee:report:month:loss7:%s:SY", yyyyMM);
|
||||||
|
|
||||||
|
// 1. 优先从 Redis 读取当月 7 大损失预计算结果
|
||||||
|
String json = stringRedisTemplate.opsForValue().get(loss7Key);
|
||||||
|
List<AcidOeeLoss7Vo> lossList;
|
||||||
|
if (StringUtils.isNotBlank(json)) {
|
||||||
|
lossList = JSON.parseArray(json, AcidOeeLoss7Vo.class);
|
||||||
|
} else {
|
||||||
|
// 2. 缓存缺失时,回退为实时计算当前月
|
||||||
|
String[] range = resolveDateRange(null, null);
|
||||||
|
lossList = acidOeeService.getLoss7Summary(range[0], range[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topN != null && topN > 0 && lossList.size() > topN) {
|
||||||
|
lossList = new ArrayList<>(lossList.subList(0, topN));
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.ok(lossList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线停机/损失事件明细(分页)
|
||||||
|
*
|
||||||
|
* 路由:GET /oee/line/acid/events
|
||||||
|
* 说明:
|
||||||
|
* - 若未传入开始/结束时间,则默认查询当月。
|
||||||
|
* - 当前实现为在内存中进行简单过滤与分页,后续可按需下沉到 pocket 或 Mapper。
|
||||||
|
*/
|
||||||
|
@GetMapping("/acid/events")
|
||||||
|
public TableDataInfo<Klptcm1ProStoppageVo> getAcidEvents(
|
||||||
|
@RequestParam(required = false) String startTime,
|
||||||
|
@RequestParam(required = false) String endTime,
|
||||||
|
@RequestParam(required = false) String stopType,
|
||||||
|
@RequestParam(required = false) String keyword,
|
||||||
|
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||||||
|
@RequestParam(required = false, defaultValue = "10") Integer pageSize
|
||||||
|
) {
|
||||||
|
// 事件明细底层按「日期」查询,这里从时间字符串中截取日期部分
|
||||||
|
String startDate = extractDateOrDefault(startTime, true);
|
||||||
|
String endDate = extractDateOrDefault(endTime, false);
|
||||||
|
|
||||||
|
List<Klptcm1ProStoppageVo> events =
|
||||||
|
acidOeeService.getStoppageEvents(startDate, endDate);
|
||||||
|
|
||||||
|
// 业务筛选:stopType、关键字(目前对 stopType / remark 做 contains 匹配)
|
||||||
|
List<Klptcm1ProStoppageVo> filtered = events.stream()
|
||||||
|
.filter(e -> {
|
||||||
|
if (StringUtils.isNotBlank(stopType) &&
|
||||||
|
!StringUtils.equals(stopType, e.getStopType())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(keyword)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String remark = e.getRemark();
|
||||||
|
String type = e.getStopType();
|
||||||
|
return (StringUtils.isNotBlank(remark) && remark.contains(keyword))
|
||||||
|
|| (StringUtils.isNotBlank(type) && type.contains(keyword));
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
long total = filtered.size();
|
||||||
|
int page = (pageNum == null || pageNum < 1) ? 1 : pageNum;
|
||||||
|
int size = (pageSize == null || pageSize < 1) ? 10 : pageSize;
|
||||||
|
int fromIndex = (page - 1) * size;
|
||||||
|
int toIndex = Math.min(fromIndex + size, filtered.size());
|
||||||
|
|
||||||
|
List<Klptcm1ProStoppageVo> pageList;
|
||||||
|
if (fromIndex >= filtered.size()) {
|
||||||
|
pageList = new ArrayList<>();
|
||||||
|
} else {
|
||||||
|
pageList = filtered.subList(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableDataInfo<Klptcm1ProStoppageVo> rsp = TableDataInfo.build();
|
||||||
|
rsp.setRows(pageList);
|
||||||
|
rsp.setTotal(total);
|
||||||
|
return rsp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线理论节拍回归数据
|
||||||
|
*
|
||||||
|
* 路由:GET /oee/line/acid/regression
|
||||||
|
* 说明:
|
||||||
|
* - {@code startDate} / {@code endDate} 可选,若为空则由 pocket 按“近6个月”默认处理。
|
||||||
|
*/
|
||||||
|
@GetMapping("/acid/regression")
|
||||||
|
public R<AcidOeeRegressionVo> getAcidRegression(
|
||||||
|
@RequestParam(required = false) String startDate,
|
||||||
|
@RequestParam(required = false) String endDate
|
||||||
|
) {
|
||||||
|
AcidOeeRegressionVo data = acidOeeService.getRegressionData(startDate, endDate);
|
||||||
|
return R.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 若未显式传入日期范围,则默认当前月 [1号, 今天]。
|
||||||
|
*/
|
||||||
|
private String[] resolveDateRange(String startDate, String endDate) {
|
||||||
|
if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
|
||||||
|
return new String[]{startDate, endDate};
|
||||||
|
}
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
LocalDate firstDay = today.withDayOfMonth(1);
|
||||||
|
return new String[]{firstDay.format(DATE_FORMATTER), today.format(DATE_FORMATTER)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从完整时间字符串中截取 yyyy-MM-dd;若为空则回退到当月默认范围。
|
||||||
|
*/
|
||||||
|
private String extractDateOrDefault(String dateTime, boolean isStart) {
|
||||||
|
if (StringUtils.isNotBlank(dateTime)) {
|
||||||
|
// 期望格式 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss,统一截前 10 位
|
||||||
|
if (dateTime.length() >= 10) {
|
||||||
|
return dateTime.substring(0, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
LocalDate firstDay = today.withDayOfMonth(1);
|
||||||
|
return isStart ? firstDay.format(DATE_FORMATTER) : today.format(DATE_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 任意日期范围异步任务(酸轧线) ========================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交酸轧线 OEE 报表异步任务(任意日期范围)。
|
||||||
|
*
|
||||||
|
* 路由:POST /oee/line/acid/report-jobs
|
||||||
|
*/
|
||||||
|
@PostMapping("/acid/report-jobs")
|
||||||
|
public R<OeeReportJobMeta> submitAcidReportJob(@RequestBody OeeReportJobSubmitBo body) {
|
||||||
|
if (body == null || StringUtils.isBlank(body.getStartDate()) || StringUtils.isBlank(body.getEndDate())) {
|
||||||
|
return R.fail("startDate/endDate 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
String jobId = UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
|
OeeReportJobMeta meta = new OeeReportJobMeta();
|
||||||
|
meta.setJobId(jobId);
|
||||||
|
meta.setStatus("PENDING");
|
||||||
|
meta.setSubmittedAt(now.format(DATE_TIME_FORMATTER));
|
||||||
|
meta.setStartDate(body.getStartDate());
|
||||||
|
meta.setEndDate(body.getEndDate());
|
||||||
|
|
||||||
|
// 先写入 PENDING 状态
|
||||||
|
String metaKey = String.format(JOB_META_KEY_PATTERN, jobId);
|
||||||
|
stringRedisTemplate.opsForValue().set(metaKey, JSON.toJSONString(meta), 1, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
boolean includeSummary = body.getIncludeSummary() == null || Boolean.TRUE.equals(body.getIncludeSummary());
|
||||||
|
boolean includeLoss7 = body.getIncludeLoss7() == null || Boolean.TRUE.equals(body.getIncludeLoss7());
|
||||||
|
String resultKey = String.format(JOB_RESULT_KEY_PATTERN, jobId);
|
||||||
|
|
||||||
|
// 异步执行实际计算
|
||||||
|
oeeReportJobService.executeAcidReportJob(
|
||||||
|
jobId,
|
||||||
|
metaKey,
|
||||||
|
resultKey,
|
||||||
|
body.getStartDate(),
|
||||||
|
body.getEndDate(),
|
||||||
|
includeSummary,
|
||||||
|
includeLoss7
|
||||||
|
);
|
||||||
|
|
||||||
|
return R.ok(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询酸轧线 OEE 报表任务状态。
|
||||||
|
*
|
||||||
|
* 路由:GET /oee/line/acid/report-jobs/{jobId}
|
||||||
|
*/
|
||||||
|
@GetMapping("/acid/report-jobs/{jobId}")
|
||||||
|
public R<OeeReportJobMeta> getAcidReportJob(@PathVariable String jobId) {
|
||||||
|
String metaKey = String.format(JOB_META_KEY_PATTERN, jobId);
|
||||||
|
String json = stringRedisTemplate.opsForValue().get(metaKey);
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
return R.fail("任务不存在或已过期");
|
||||||
|
}
|
||||||
|
OeeReportJobMeta meta = JSON.parseObject(json, OeeReportJobMeta.class);
|
||||||
|
return R.ok(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取酸轧线 OEE 报表任务结果。
|
||||||
|
*
|
||||||
|
* 路由:GET /oee/line/acid/report-jobs/{jobId}/result
|
||||||
|
*/
|
||||||
|
@GetMapping("/acid/report-jobs/{jobId}/result")
|
||||||
|
public R<OeeReportJobResult> getAcidReportJobResult(@PathVariable String jobId) {
|
||||||
|
String metaKey = String.format(JOB_META_KEY_PATTERN, jobId);
|
||||||
|
String metaJson = stringRedisTemplate.opsForValue().get(metaKey);
|
||||||
|
if (StringUtils.isBlank(metaJson)) {
|
||||||
|
return R.fail("任务不存在或已过期");
|
||||||
|
}
|
||||||
|
OeeReportJobMeta meta = JSON.parseObject(metaJson, OeeReportJobMeta.class);
|
||||||
|
if (!"COMPLETED".equals(meta.getStatus())) {
|
||||||
|
return R.fail("结果未就绪,当前状态:" + meta.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
String resultKey = String.format(JOB_RESULT_KEY_PATTERN, jobId);
|
||||||
|
String resultJson = stringRedisTemplate.opsForValue().get(resultKey);
|
||||||
|
if (StringUtils.isBlank(resultJson)) {
|
||||||
|
return R.fail("任务结果不存在或已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
OeeReportJobResult result = JSON.parseObject(resultJson, OeeReportJobResult.class);
|
||||||
|
return R.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 内部 DTO ========================
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class OeeReportJobSubmitBo {
|
||||||
|
/** 开始日期(yyyy-MM-dd) */
|
||||||
|
private String startDate;
|
||||||
|
/** 结束日期(yyyy-MM-dd) */
|
||||||
|
private String endDate;
|
||||||
|
/** 是否计算 summary(默认 true) */
|
||||||
|
private Boolean includeSummary;
|
||||||
|
/** 是否计算 loss7(默认 true) */
|
||||||
|
private Boolean includeLoss7;
|
||||||
|
/**
|
||||||
|
* 口径开关等扩展选项(JSON 文本或键值对),当前实现暂不解析,仅作为预留字段。
|
||||||
|
*/
|
||||||
|
private String options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class OeeReportJobMeta {
|
||||||
|
private String jobId;
|
||||||
|
/** PENDING / RUNNING / COMPLETED / FAILED / EXPIRED */
|
||||||
|
private String status;
|
||||||
|
private String submittedAt;
|
||||||
|
private String startedAt;
|
||||||
|
private String completedAt;
|
||||||
|
private String startDate;
|
||||||
|
private String endDate;
|
||||||
|
private String errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class OeeReportJobResult {
|
||||||
|
/** 日汇总结果(用于 KPI + 趋势) */
|
||||||
|
private List<AcidOeeDailySummaryVo> summary;
|
||||||
|
/** 7 大损失结果 */
|
||||||
|
private List<AcidOeeLoss7Vo> loss7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
137
klp-da/src/main/java/com/klp/da/service/OeeReportJobService.java
Normal file
137
klp-da/src/main/java/com/klp/da/service/OeeReportJobService.java
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package com.klp.da.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||||
|
import com.klp.pocket.acid.service.IAcidOeeService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OEE 报表异步任务执行 Service(酸轧线)。
|
||||||
|
*
|
||||||
|
* 负责在后台线程中执行任意日期范围的 summary/loss7 计算,
|
||||||
|
* 并将任务状态与结果写入 Redis。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OeeReportJobService {
|
||||||
|
|
||||||
|
private final IAcidOeeService acidOeeService;
|
||||||
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步执行酸轧线报表任务。
|
||||||
|
*
|
||||||
|
* @param jobId 任务 ID
|
||||||
|
* @param metaKey 任务元信息 Redis key
|
||||||
|
* @param resultKey 任务结果 Redis key
|
||||||
|
* @param startDate 开始日期(yyyy-MM-dd)
|
||||||
|
* @param endDate 结束日期(yyyy-MM-dd)
|
||||||
|
* @param includeSummary 是否计算 summary
|
||||||
|
* @param includeLoss7 是否计算 loss7
|
||||||
|
*/
|
||||||
|
@Async
|
||||||
|
public void executeAcidReportJob(
|
||||||
|
String jobId,
|
||||||
|
String metaKey,
|
||||||
|
String resultKey,
|
||||||
|
String startDate,
|
||||||
|
String endDate,
|
||||||
|
boolean includeSummary,
|
||||||
|
boolean includeLoss7
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 更新状态为 RUNNING
|
||||||
|
OeeReportJobMeta meta = readMeta(metaKey);
|
||||||
|
if (meta == null) {
|
||||||
|
meta = new OeeReportJobMeta();
|
||||||
|
meta.setJobId(jobId);
|
||||||
|
meta.setStartDate(startDate);
|
||||||
|
meta.setEndDate(endDate);
|
||||||
|
}
|
||||||
|
meta.setStatus("RUNNING");
|
||||||
|
meta.setStartedAt(LocalDateTime.now().format(DATE_TIME_FORMATTER));
|
||||||
|
writeMeta(metaKey, meta);
|
||||||
|
|
||||||
|
// 实际计算
|
||||||
|
OeeReportJobResult result = new OeeReportJobResult();
|
||||||
|
if (includeSummary) {
|
||||||
|
List<AcidOeeDailySummaryVo> summary =
|
||||||
|
acidOeeService.getDailySummary(startDate, endDate);
|
||||||
|
result.setSummary(summary);
|
||||||
|
}
|
||||||
|
if (includeLoss7) {
|
||||||
|
List<AcidOeeLoss7Vo> loss7 =
|
||||||
|
acidOeeService.getLoss7Summary(startDate, endDate);
|
||||||
|
result.setLoss7(loss7);
|
||||||
|
}
|
||||||
|
|
||||||
|
stringRedisTemplate.opsForValue()
|
||||||
|
.set(resultKey, JSON.toJSONString(result), 1, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
// 标记为 COMPLETED
|
||||||
|
meta.setStatus("COMPLETED");
|
||||||
|
meta.setCompletedAt(LocalDateTime.now().format(DATE_TIME_FORMATTER));
|
||||||
|
writeMeta(metaKey, meta);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[OeeReportJobService] executeAcidReportJob error, jobId={}", jobId, e);
|
||||||
|
OeeReportJobMeta meta = readMeta(metaKey);
|
||||||
|
if (meta == null) {
|
||||||
|
meta = new OeeReportJobMeta();
|
||||||
|
meta.setJobId(jobId);
|
||||||
|
meta.setStartDate(startDate);
|
||||||
|
meta.setEndDate(endDate);
|
||||||
|
}
|
||||||
|
meta.setStatus("FAILED");
|
||||||
|
meta.setCompletedAt(LocalDateTime.now().format(DATE_TIME_FORMATTER));
|
||||||
|
meta.setErrorMessage(e.getMessage());
|
||||||
|
writeMeta(metaKey, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OeeReportJobMeta readMeta(String metaKey) {
|
||||||
|
String json = stringRedisTemplate.opsForValue().get(metaKey);
|
||||||
|
if (json == null || json.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.parseObject(json, OeeReportJobMeta.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMeta(String metaKey, OeeReportJobMeta meta) {
|
||||||
|
stringRedisTemplate.opsForValue()
|
||||||
|
.set(metaKey, JSON.toJSONString(meta), 1, TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class OeeReportJobMeta {
|
||||||
|
private String jobId;
|
||||||
|
private String status;
|
||||||
|
private String submittedAt;
|
||||||
|
private String startedAt;
|
||||||
|
private String completedAt;
|
||||||
|
private String startDate;
|
||||||
|
private String endDate;
|
||||||
|
private String errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class OeeReportJobResult {
|
||||||
|
private List<AcidOeeDailySummaryVo> summary;
|
||||||
|
private List<AcidOeeLoss7Vo> loss7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
148
klp-da/src/main/java/com/klp/da/task/AcidOeeMonthTask.java
Normal file
148
klp-da/src/main/java/com/klp/da/task/AcidOeeMonthTask.java
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package com.klp.da.task;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||||
|
import com.klp.pocket.acid.service.IAcidOeeService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线 OEE 当月预计算任务
|
||||||
|
*
|
||||||
|
* 需求对应 docs/oee-report-design.md 第 12.2 节:
|
||||||
|
* - 项目启动完成后即计算当月 OEE 聚合结果并写入 Redis;
|
||||||
|
* - 每天凌晨 04:00 重新计算当月数据并覆盖缓存。
|
||||||
|
*
|
||||||
|
* 当前仅实现酸轧线(SY)的当月日汇总 & 7 大损失预计算;
|
||||||
|
* key 约定:
|
||||||
|
* - 汇总结果:oee:report:month:summary:{yyyyMM}:SY
|
||||||
|
* - 7 大损失:oee:report:month:loss7:{yyyyMM}:SY
|
||||||
|
* - 元信息: oee:report:month:meta:{yyyyMM}:SY
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
public class AcidOeeMonthTask {
|
||||||
|
|
||||||
|
/** Redis 缓存 key 模板:当月 OEE 汇总(酸轧线) */
|
||||||
|
private static final String SUMMARY_KEY_PATTERN = "oee:report:month:summary:%s:SY";
|
||||||
|
|
||||||
|
/** Redis 缓存 key 模板:当月 7 大损失(酸轧线) */
|
||||||
|
private static final String LOSS7_KEY_PATTERN = "oee:report:month:loss7:%s:SY";
|
||||||
|
|
||||||
|
/** Redis 缓存 key 模板:当月元信息(酸轧线) */
|
||||||
|
private static final String META_KEY_PATTERN = "oee:report:month:meta:%s:SY";
|
||||||
|
|
||||||
|
private static final DateTimeFormatter YEAR_MONTH_FMT = DateTimeFormatter.ofPattern("yyyyMM");
|
||||||
|
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ISO_DATE;
|
||||||
|
private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||||
|
|
||||||
|
private final IAcidOeeService acidOeeService;
|
||||||
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目启动完成后立即计算一次当月酸轧 OEE 汇总并写入 Redis。
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
try {
|
||||||
|
computeCurrentMonth("startup");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[AcidOeeMonthTask] startup compute failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每天凌晨 04:00 重新计算当月酸轧 OEE 汇总并覆盖 Redis 缓存。
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0 0 4 * * ?")
|
||||||
|
public void scheduleDaily() {
|
||||||
|
try {
|
||||||
|
computeCurrentMonth("schedule-04");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[AcidOeeMonthTask] 4am compute failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算当前月份(从当月1号到今天)的酸轧 OEE 日汇总,并写入 Redis。
|
||||||
|
*
|
||||||
|
* @param trigger 触发来源标记(startup / schedule-04 等)
|
||||||
|
*/
|
||||||
|
private void computeCurrentMonth(String trigger) {
|
||||||
|
long startNs = System.nanoTime();
|
||||||
|
|
||||||
|
LocalDate now = LocalDate.now();
|
||||||
|
String yyyyMM = now.format(YEAR_MONTH_FMT);
|
||||||
|
|
||||||
|
LocalDate startDate = now.withDayOfMonth(1);
|
||||||
|
LocalDate endDate = now;
|
||||||
|
|
||||||
|
String startStr = startDate.format(DATE_FMT);
|
||||||
|
String endStr = endDate.format(DATE_FMT);
|
||||||
|
|
||||||
|
log.info("[AcidOeeMonthTask] trigger={}, computing acid OEE month summary for {} ({} ~ {})",
|
||||||
|
trigger, yyyyMM, startStr, endStr);
|
||||||
|
|
||||||
|
// 1. 调用 pocket 的 AcidOeeService 获取当月日汇总 & 7 大损失
|
||||||
|
List<AcidOeeDailySummaryVo> dailySummaryList = acidOeeService.getDailySummary(startStr, endStr);
|
||||||
|
List<AcidOeeLoss7Vo> loss7List = acidOeeService.getLoss7Summary(startStr, endStr);
|
||||||
|
|
||||||
|
// 2. 写入 Redis(summary)
|
||||||
|
String summaryKey = String.format(SUMMARY_KEY_PATTERN, yyyyMM);
|
||||||
|
String summaryJson = JSON.toJSONString(dailySummaryList);
|
||||||
|
stringRedisTemplate.opsForValue().set(summaryKey, summaryJson, 1, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
// 2.1 写入 Redis(loss7)
|
||||||
|
String loss7Key = String.format(LOSS7_KEY_PATTERN, yyyyMM);
|
||||||
|
String loss7Json = JSON.toJSONString(loss7List);
|
||||||
|
stringRedisTemplate.opsForValue().set(loss7Key, loss7Json, 1, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
long durationMs = (System.nanoTime() - startNs) / 1_000_000L;
|
||||||
|
|
||||||
|
// 3. 写入 Redis(meta)
|
||||||
|
Meta meta = new Meta();
|
||||||
|
meta.setComputedAt(LocalDateTime.now().format(DATE_TIME_FMT));
|
||||||
|
meta.setDurationMs(durationMs);
|
||||||
|
meta.setStartDate(startStr);
|
||||||
|
meta.setEndDate(endStr);
|
||||||
|
meta.setTrigger(trigger);
|
||||||
|
|
||||||
|
String metaKey = String.format(META_KEY_PATTERN, yyyyMM);
|
||||||
|
stringRedisTemplate.opsForValue().set(metaKey, JSON.toJSONString(meta), 1, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
log.info("[AcidOeeMonthTask] compute finish for {} dailySize={}, loss7Size={}, durationMs={}ms, summaryKey={}",
|
||||||
|
yyyyMM, dailySummaryList.size(), loss7List.size(), durationMs, summaryKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当月预计算元信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
private static class Meta {
|
||||||
|
/** 计算完成时间(ISO-8601 字符串) */
|
||||||
|
private String computedAt;
|
||||||
|
/** 计算耗时(毫秒) */
|
||||||
|
private long durationMs;
|
||||||
|
/** 统计起始日期(yyyy-MM-dd) */
|
||||||
|
private String startDate;
|
||||||
|
/** 统计结束日期(yyyy-MM-dd) */
|
||||||
|
private String endDate;
|
||||||
|
/** 触发来源(startup / schedule-04 等) */
|
||||||
|
private String trigger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -24,5 +24,11 @@
|
|||||||
<artifactId>klp-system</artifactId>
|
<artifactId>klp-system</artifactId>
|
||||||
<version>0.8.3</version>
|
<version>0.8.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 用于 OEE 口径:按“当前钢卷号 + 成品库库区ID”获取 quality_status 以区分良品/次品 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.klp</groupId>
|
||||||
|
<artifactId>klp-wms</artifactId>
|
||||||
|
<version>0.8.3</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.klp.pocket.acid.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线OEE日汇总视图对象
|
||||||
|
* 用于按日、按产线聚合的KPI与趋势数据
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AcidOeeDailySummaryVo {
|
||||||
|
|
||||||
|
/** 统计日期(yyyy-MM-dd) */
|
||||||
|
private String statDate;
|
||||||
|
|
||||||
|
/** 产线ID(固定为 SY) */
|
||||||
|
private String lineId;
|
||||||
|
|
||||||
|
/** 产线名称(酸轧线) */
|
||||||
|
private String lineName;
|
||||||
|
|
||||||
|
/** 计划时间(min,可选) */
|
||||||
|
private Long plannedTimeMin;
|
||||||
|
|
||||||
|
/** 计划停机(min,可选;若无则置 0) */
|
||||||
|
private Long plannedDowntimeMin;
|
||||||
|
|
||||||
|
/** 负荷时间(min)= planned_time_min - planned_downtime_min */
|
||||||
|
private Long loadingTimeMin;
|
||||||
|
|
||||||
|
/** 停机时间(min,来自停机事件汇总) */
|
||||||
|
private Long downtimeMin;
|
||||||
|
|
||||||
|
/** 实际运转时间(min)= loading_time_min - downtime_min */
|
||||||
|
private Long runTimeMin;
|
||||||
|
|
||||||
|
/** 总产量(吨) */
|
||||||
|
private BigDecimal totalOutputTon;
|
||||||
|
|
||||||
|
/** 总产量(卷) */
|
||||||
|
private Long totalOutputCoil;
|
||||||
|
|
||||||
|
/** 良品量(吨) */
|
||||||
|
private BigDecimal goodOutputTon;
|
||||||
|
|
||||||
|
/** 良品量(卷) */
|
||||||
|
private Long goodOutputCoil;
|
||||||
|
|
||||||
|
/** 不良量(吨)= total_output_ton - good_output_ton */
|
||||||
|
private BigDecimal defectOutputTon;
|
||||||
|
|
||||||
|
/** 不良量(卷)= total_output_coil - good_output_coil */
|
||||||
|
private Long defectOutputCoil;
|
||||||
|
|
||||||
|
/** 理论节拍(min/吨;回归斜率) */
|
||||||
|
private BigDecimal idealCycleTimeMinPerTon;
|
||||||
|
|
||||||
|
/** 理论节拍(min/卷;回归斜率) */
|
||||||
|
private BigDecimal idealCycleTimeMinPerCoil;
|
||||||
|
|
||||||
|
/** 派生指标:时间稼动率(0~1 或 0~100) */
|
||||||
|
private BigDecimal availability;
|
||||||
|
|
||||||
|
/** 派生指标:性能稼动率(吨维度) */
|
||||||
|
private BigDecimal performanceTon;
|
||||||
|
|
||||||
|
/** 派生指标:性能稼动率(卷维度) */
|
||||||
|
private BigDecimal performanceCoil;
|
||||||
|
|
||||||
|
/** 派生指标:良品率 */
|
||||||
|
private BigDecimal quality;
|
||||||
|
|
||||||
|
/** 派生指标:OEE(建议以吨维度为主) */
|
||||||
|
private BigDecimal oee;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.klp.pocket.acid.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线OEE 7大损失汇总视图对象
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AcidOeeLoss7Vo {
|
||||||
|
|
||||||
|
/** 损失类别编码(1-7,或直接使用 stop_type 名称) */
|
||||||
|
private String lossCategoryCode;
|
||||||
|
|
||||||
|
/** 损失类别名称 */
|
||||||
|
private String lossCategoryName;
|
||||||
|
|
||||||
|
/** 损失时间(分钟) */
|
||||||
|
private Long lossTimeMin;
|
||||||
|
|
||||||
|
/** 损失时间占比(%) */
|
||||||
|
private BigDecimal lossTimeRate;
|
||||||
|
|
||||||
|
/** 发生次数(部分分类可能为空) */
|
||||||
|
private Integer count;
|
||||||
|
|
||||||
|
/** 平均时长(分钟,部分分类可能为空) */
|
||||||
|
private BigDecimal avgDurationMin;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.klp.pocket.acid.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线OEE回归数据视图对象
|
||||||
|
* 用于理论节拍计算和前端散点图展示
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AcidOeeRegressionVo {
|
||||||
|
|
||||||
|
/** 产线ID(固定为 SY) */
|
||||||
|
private String lineId;
|
||||||
|
|
||||||
|
/** 产线名称(酸轧线) */
|
||||||
|
private String lineName;
|
||||||
|
|
||||||
|
/** 回归斜率:分钟/吨(核心值,可作为理论节拍) */
|
||||||
|
private BigDecimal slopeMinPerTon;
|
||||||
|
|
||||||
|
/** 回归斜率:分钟/卷(核心值,可作为理论节拍) */
|
||||||
|
private BigDecimal slopeMinPerCoil;
|
||||||
|
|
||||||
|
/** 截距(分钟) */
|
||||||
|
private BigDecimal interceptMin;
|
||||||
|
|
||||||
|
/** 拟合优度(R²) */
|
||||||
|
private BigDecimal r2;
|
||||||
|
|
||||||
|
/** 参与回归样本数 */
|
||||||
|
private Integer sampleCount;
|
||||||
|
|
||||||
|
/** 回归数据开始时间 */
|
||||||
|
private String startTime;
|
||||||
|
|
||||||
|
/** 回归数据结束时间 */
|
||||||
|
private String endTime;
|
||||||
|
|
||||||
|
/** 散点列表 */
|
||||||
|
private List<RegressionPointVo> points;
|
||||||
|
|
||||||
|
/** 拟合线两个端点(前端可直接画线) */
|
||||||
|
private List<RegressionLinePointVo> linePoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 散点数据
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class RegressionPointVo {
|
||||||
|
/** 重量(吨,X轴) */
|
||||||
|
private BigDecimal weightTon;
|
||||||
|
/** 卷数(X轴) */
|
||||||
|
private Long coilCount;
|
||||||
|
/** 时长(分钟,Y轴) */
|
||||||
|
private Long durationMin;
|
||||||
|
/** 关联的actionId(可选) */
|
||||||
|
private String actionId;
|
||||||
|
/** 创建时间 */
|
||||||
|
private String createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拟合线端点
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class RegressionLinePointVo {
|
||||||
|
/** 重量(吨,X轴) */
|
||||||
|
private BigDecimal weightTon;
|
||||||
|
/** 卷数(X轴) */
|
||||||
|
private Long coilCount;
|
||||||
|
/** 时长(分钟,Y轴) */
|
||||||
|
private BigDecimal durationMin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.klp.pocket.acid.mapper;
|
||||||
|
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeRegressionVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线OEE Mapper接口
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AcidOeeMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询OEE日汇总(按日期范围)
|
||||||
|
* 聚合产量(吨/卷)、停机时间等
|
||||||
|
*
|
||||||
|
* @param startDate 开始日期(yyyy-MM-dd)
|
||||||
|
* @param endDate 结束日期(yyyy-MM-dd)
|
||||||
|
* @return 日汇总列表
|
||||||
|
*/
|
||||||
|
List<AcidOeeDailySummaryVo> selectDailySummary(@Param("startDate") String startDate,
|
||||||
|
@Param("endDate") String endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询回归数据散点(用于理论节拍计算)
|
||||||
|
* 返回:重量(吨)、卷数、时长(分钟)等
|
||||||
|
*
|
||||||
|
* @param startDate 开始日期(yyyy-MM-dd,可选)
|
||||||
|
* @param endDate 结束日期(yyyy-MM-dd,可选)
|
||||||
|
* @return 散点列表
|
||||||
|
*/
|
||||||
|
List<AcidOeeRegressionVo.RegressionPointVo> selectRegressionPoints(@Param("startDate") String startDate,
|
||||||
|
@Param("endDate") String endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询每日的钢卷号和重量(用于良品/次品判定)
|
||||||
|
*
|
||||||
|
* @param startDate 开始日期(yyyy-MM-dd)
|
||||||
|
* @param endDate 结束日期(yyyy-MM-dd)
|
||||||
|
* @return Map列表,key为日期,value为卷号和重量信息
|
||||||
|
*/
|
||||||
|
List<CoilInfoByDate> selectCoilInfoByDate(@Param("startDate") String startDate,
|
||||||
|
@Param("endDate") String endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卷号信息内部类(用于Mapper返回)
|
||||||
|
*/
|
||||||
|
class CoilInfoByDate {
|
||||||
|
private String statDate;
|
||||||
|
private String coilNo;
|
||||||
|
private java.math.BigDecimal weight;
|
||||||
|
|
||||||
|
public String getStatDate() { return statDate; }
|
||||||
|
public void setStatDate(String statDate) { this.statDate = statDate; }
|
||||||
|
public String getCoilNo() { return coilNo; }
|
||||||
|
public void setCoilNo(String coilNo) { this.coilNo = coilNo; }
|
||||||
|
public java.math.BigDecimal getWeight() { return weight; }
|
||||||
|
public void setWeight(java.math.BigDecimal weight) { this.weight = weight; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.klp.pocket.acid.service;
|
||||||
|
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeRegressionVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线OEE Service接口
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
public interface IAcidOeeService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询OEE日汇总(按日期范围)
|
||||||
|
* 包含:产量(吨/卷)、停机时间、良品/次品、派生指标等
|
||||||
|
*
|
||||||
|
* @param startDate 开始日期(yyyy-MM-dd)
|
||||||
|
* @param endDate 结束日期(yyyy-MM-dd)
|
||||||
|
* @return 日汇总列表(按日期排序)
|
||||||
|
*/
|
||||||
|
List<AcidOeeDailySummaryVo> getDailySummary(String startDate, String endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询停机事件列表(用于OEE明细和7大损失)
|
||||||
|
*
|
||||||
|
* @param startDate 开始日期(yyyy-MM-dd)
|
||||||
|
* @param endDate 结束日期(yyyy-MM-dd)
|
||||||
|
* @return 停机事件列表
|
||||||
|
*/
|
||||||
|
List<Klptcm1ProStoppageVo> getStoppageEvents(String startDate, String endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询理论节拍回归数据(吨和卷两个维度)
|
||||||
|
* 用于性能稼动率计算和前端散点图展示
|
||||||
|
*
|
||||||
|
* @param startDate 开始日期(yyyy-MM-dd,可选,默认近6个月)
|
||||||
|
* @param endDate 结束日期(yyyy-MM-dd,可选)
|
||||||
|
* @return 回归数据(包含斜率、截距、散点等)
|
||||||
|
*/
|
||||||
|
AcidOeeRegressionVo getRegressionData(String startDate, String endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询7大损失汇总(按日期范围)
|
||||||
|
*
|
||||||
|
* @param startDate 开始日期(yyyy-MM-dd)
|
||||||
|
* @param endDate 结束日期(yyyy-MM-dd)
|
||||||
|
* @return 7大损失汇总列表
|
||||||
|
*/
|
||||||
|
List<AcidOeeLoss7Vo> getLoss7Summary(String startDate, String endDate);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,503 @@
|
|||||||
|
package com.klp.pocket.acid.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||||
|
import com.klp.common.utils.StringUtils;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.AcidOeeRegressionVo;
|
||||||
|
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
|
||||||
|
import com.klp.pocket.acid.domain.bo.Klptcm1ProStoppageBo;
|
||||||
|
import com.klp.pocket.acid.mapper.AcidOeeMapper;
|
||||||
|
import com.klp.pocket.acid.service.IAcidOeeService;
|
||||||
|
import com.klp.pocket.acid.service.IKlptcm1ProStoppageService;
|
||||||
|
import com.klp.pocket.common.service.ICoilQualityJudgeService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酸轧线OEE Service实现类
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@DS("acid")
|
||||||
|
@Service
|
||||||
|
public class AcidOeeServiceImpl implements IAcidOeeService {
|
||||||
|
|
||||||
|
/** 酸轧成品库库区ID */
|
||||||
|
private static final Long ACID_FINISHED_WAREHOUSE_ID = 1988150099140866050L;
|
||||||
|
|
||||||
|
private final AcidOeeMapper acidOeeMapper;
|
||||||
|
private final IKlptcm1ProStoppageService stoppageService;
|
||||||
|
private final ICoilQualityJudgeService coilQualityJudgeService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AcidOeeDailySummaryVo> getDailySummary(String startDate, String endDate) {
|
||||||
|
// 1. 查询基础日汇总(产量、停机时间等)
|
||||||
|
List<AcidOeeDailySummaryVo> summaries = acidOeeMapper.selectDailySummary(startDate, endDate);
|
||||||
|
|
||||||
|
if (summaries == null || summaries.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查询停机事件,按日期聚合停机时间
|
||||||
|
Map<String, Long> downtimeByDate = aggregateDowntimeByDate(startDate, endDate);
|
||||||
|
|
||||||
|
// 3. 查询产量明细,用于良品/次品判定
|
||||||
|
Map<String, List<CoilInfo>> coilInfoByDate = getCoilNosByDate(startDate, endDate);
|
||||||
|
|
||||||
|
// 4. 填充每个日汇总的完整数据
|
||||||
|
for (AcidOeeDailySummaryVo summary : summaries) {
|
||||||
|
String statDate = summary.getStatDate();
|
||||||
|
summary.setLineId("SY");
|
||||||
|
summary.setLineName("酸轧线");
|
||||||
|
|
||||||
|
// 填充停机时间
|
||||||
|
Long downtime = downtimeByDate.getOrDefault(statDate, 0L);
|
||||||
|
summary.setDowntimeMin(downtime);
|
||||||
|
|
||||||
|
// 计算运转时间
|
||||||
|
Long loadingTime = summary.getLoadingTimeMin() != null ? summary.getLoadingTimeMin() : 0L;
|
||||||
|
Long runTime = Math.max(0, loadingTime - downtime);
|
||||||
|
summary.setRunTimeMin(runTime);
|
||||||
|
|
||||||
|
// 良品/次品判定(通过WMS)
|
||||||
|
if (coilInfoByDate.containsKey(statDate)) {
|
||||||
|
List<CoilInfo> coilInfos = coilInfoByDate.get(statDate);
|
||||||
|
calculateQualityOutput(summary, coilInfos);
|
||||||
|
} else {
|
||||||
|
// 如果没有卷号,默认全部为良品(或根据业务规则处理)
|
||||||
|
summary.setGoodOutputTon(summary.getTotalOutputTon());
|
||||||
|
summary.setGoodOutputCoil(summary.getTotalOutputCoil());
|
||||||
|
summary.setDefectOutputTon(BigDecimal.ZERO);
|
||||||
|
summary.setDefectOutputCoil(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充理论节拍(从回归数据或缓存获取,这里暂时留空,由调用方填充)
|
||||||
|
// summary.setIdealCycleTimeMinPerTon(...);
|
||||||
|
// summary.setIdealCycleTimeMinPerCoil(...);
|
||||||
|
|
||||||
|
// 计算派生指标
|
||||||
|
calculateDerivedMetrics(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return summaries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Klptcm1ProStoppageVo> getStoppageEvents(String startDate, String endDate) {
|
||||||
|
Klptcm1ProStoppageBo bo = new Klptcm1ProStoppageBo();
|
||||||
|
bo.setStartDate(parseDate(startDate));
|
||||||
|
bo.setEndDate(parseDate(endDate));
|
||||||
|
return stoppageService.queryList(bo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AcidOeeRegressionVo getRegressionData(String startDate, String endDate) {
|
||||||
|
// 1. 查询散点数据
|
||||||
|
List<AcidOeeRegressionVo.RegressionPointVo> points = acidOeeMapper.selectRegressionPoints(startDate, endDate);
|
||||||
|
|
||||||
|
AcidOeeRegressionVo result = new AcidOeeRegressionVo();
|
||||||
|
result.setLineId("SY");
|
||||||
|
result.setLineName("酸轧线");
|
||||||
|
result.setStartTime(startDate);
|
||||||
|
result.setEndTime(endDate);
|
||||||
|
result.setPoints(points);
|
||||||
|
result.setSampleCount(points != null ? points.size() : 0);
|
||||||
|
|
||||||
|
if (points == null || points.isEmpty()) {
|
||||||
|
// 没有数据时返回空结果
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 计算回归(吨维度)
|
||||||
|
RegressionResult tonResult = calculateRegression(
|
||||||
|
points.stream().map(AcidOeeRegressionVo.RegressionPointVo::getWeightTon).filter(Objects::nonNull).collect(Collectors.toList()),
|
||||||
|
points.stream().map(AcidOeeRegressionVo.RegressionPointVo::getDurationMin).filter(Objects::nonNull).collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
if (tonResult != null) {
|
||||||
|
result.setSlopeMinPerTon(tonResult.slope);
|
||||||
|
result.setInterceptMin(tonResult.intercept);
|
||||||
|
result.setR2(tonResult.r2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 计算回归(卷维度)
|
||||||
|
RegressionResult coilResult = calculateRegression(
|
||||||
|
points.stream().map(p -> p.getCoilCount() != null ? BigDecimal.valueOf(p.getCoilCount()) : null).filter(Objects::nonNull).collect(Collectors.toList()),
|
||||||
|
points.stream().map(AcidOeeRegressionVo.RegressionPointVo::getDurationMin).filter(Objects::nonNull).collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
if (coilResult != null) {
|
||||||
|
result.setSlopeMinPerCoil(coilResult.slope);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 生成拟合线端点(用于前端画线)
|
||||||
|
if (tonResult != null && !points.isEmpty()) {
|
||||||
|
List<AcidOeeRegressionVo.RegressionLinePointVo> linePoints = generateLinePoints(points, tonResult);
|
||||||
|
result.setLinePoints(linePoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AcidOeeLoss7Vo> getLoss7Summary(String startDate, String endDate) {
|
||||||
|
// 1. 查询停机事件(含 stopType、duration 等)
|
||||||
|
List<Klptcm1ProStoppageVo> events = getStoppageEvents(startDate, endDate);
|
||||||
|
if (events == null || events.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 按 stopType 分组汇总
|
||||||
|
Map<String, LossStats> statsByType = new HashMap<>();
|
||||||
|
long totalLossMin = 0L;
|
||||||
|
|
||||||
|
for (Klptcm1ProStoppageVo event : events) {
|
||||||
|
String stopType = event.getStopType();
|
||||||
|
if (StringUtils.isBlank(stopType)) {
|
||||||
|
// 没有类型的记录暂时忽略
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Long durationSec = event.getDuration();
|
||||||
|
if (durationSec == null || durationSec <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long durationMin = durationSec / 60;
|
||||||
|
if (durationMin <= 0) {
|
||||||
|
durationMin = 1; // 最小记 1 分钟,避免全为 0
|
||||||
|
}
|
||||||
|
|
||||||
|
LossStats stats = statsByType.computeIfAbsent(stopType, k -> new LossStats());
|
||||||
|
stats.totalMin += durationMin;
|
||||||
|
stats.count++;
|
||||||
|
totalLossMin += durationMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statsByType.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 组装 VO 列表
|
||||||
|
List<AcidOeeLoss7Vo> result = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, LossStats> entry : statsByType.entrySet()) {
|
||||||
|
String stopType = entry.getKey();
|
||||||
|
LossStats stats = entry.getValue();
|
||||||
|
|
||||||
|
AcidOeeLoss7Vo vo = new AcidOeeLoss7Vo();
|
||||||
|
vo.setLossCategoryCode(stopType);
|
||||||
|
vo.setLossCategoryName(stopType);
|
||||||
|
vo.setLossTimeMin(stats.totalMin);
|
||||||
|
|
||||||
|
if (totalLossMin > 0) {
|
||||||
|
BigDecimal rate = BigDecimal.valueOf(stats.totalMin)
|
||||||
|
.divide(BigDecimal.valueOf(totalLossMin), 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(BigDecimal.valueOf(100));
|
||||||
|
vo.setLossTimeRate(rate);
|
||||||
|
} else {
|
||||||
|
vo.setLossTimeRate(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
vo.setCount(stats.count);
|
||||||
|
if (stats.count > 0) {
|
||||||
|
BigDecimal avg = BigDecimal.valueOf(stats.totalMin)
|
||||||
|
.divide(BigDecimal.valueOf(stats.count), 2, RoundingMode.HALF_UP);
|
||||||
|
vo.setAvgDurationMin(avg);
|
||||||
|
} else {
|
||||||
|
vo.setAvgDurationMin(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 按损失时间从大到小排序
|
||||||
|
result.sort(Comparator.comparingLong(AcidOeeLoss7Vo::getLossTimeMin).reversed());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按日期聚合停机时间
|
||||||
|
*/
|
||||||
|
private Map<String, Long> aggregateDowntimeByDate(String startDate, String endDate) {
|
||||||
|
List<Klptcm1ProStoppageVo> events = getStoppageEvents(startDate, endDate);
|
||||||
|
Map<String, Long> downtimeMap = new HashMap<>();
|
||||||
|
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
for (Klptcm1ProStoppageVo event : events) {
|
||||||
|
if (event.getStartDate() != null && event.getDuration() != null) {
|
||||||
|
String date = dateFormat.format(event.getStartDate());
|
||||||
|
// duration单位是秒,转换为分钟
|
||||||
|
Long minutes = event.getDuration() / 60;
|
||||||
|
downtimeMap.merge(date, minutes, Long::sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return downtimeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取每日的钢卷号和重量(用于良品/次品判定)
|
||||||
|
*/
|
||||||
|
private Map<String, List<CoilInfo>> getCoilNosByDate(String startDate, String endDate) {
|
||||||
|
List<AcidOeeMapper.CoilInfoByDate> coilInfoList = acidOeeMapper.selectCoilInfoByDate(startDate, endDate);
|
||||||
|
Map<String, List<CoilInfo>> result = new HashMap<>();
|
||||||
|
|
||||||
|
for (AcidOeeMapper.CoilInfoByDate info : coilInfoList) {
|
||||||
|
String date = info.getStatDate();
|
||||||
|
result.computeIfAbsent(date, k -> new ArrayList<>())
|
||||||
|
.add(new CoilInfo(info.getCoilNo(), info.getWeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卷号信息内部类
|
||||||
|
*/
|
||||||
|
private static class CoilInfo {
|
||||||
|
final String coilNo;
|
||||||
|
final BigDecimal weight;
|
||||||
|
|
||||||
|
CoilInfo(String coilNo, BigDecimal weight) {
|
||||||
|
this.coilNo = coilNo;
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算良品/次品产量
|
||||||
|
*/
|
||||||
|
private void calculateQualityOutput(AcidOeeDailySummaryVo summary, List<CoilInfo> coilInfos) {
|
||||||
|
BigDecimal goodTon = BigDecimal.ZERO;
|
||||||
|
long goodCoil = 0L;
|
||||||
|
BigDecimal defectTon = BigDecimal.ZERO;
|
||||||
|
long defectCoil = 0L;
|
||||||
|
|
||||||
|
for (CoilInfo coilInfo : coilInfos) {
|
||||||
|
String coilNo = coilInfo.coilNo;
|
||||||
|
BigDecimal coilWeight = coilInfo.weight != null ? coilInfo.weight : BigDecimal.ZERO;
|
||||||
|
|
||||||
|
// 通过WMS判定良品/次品
|
||||||
|
Boolean isScrap = coilQualityJudgeService.isScrap(ACID_FINISHED_WAREHOUSE_ID, coilNo);
|
||||||
|
if (isScrap == null) {
|
||||||
|
// 匹配不到,忽略不计
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(isScrap)) {
|
||||||
|
// 次品
|
||||||
|
defectTon = defectTon.add(coilWeight);
|
||||||
|
defectCoil++;
|
||||||
|
} else {
|
||||||
|
// 良品
|
||||||
|
goodTon = goodTon.add(coilWeight);
|
||||||
|
goodCoil++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.setGoodOutputTon(goodTon);
|
||||||
|
summary.setGoodOutputCoil(goodCoil);
|
||||||
|
summary.setDefectOutputTon(defectTon);
|
||||||
|
summary.setDefectOutputCoil(defectCoil);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算派生指标(时间稼动率、性能稼动率、良品率、OEE)
|
||||||
|
*/
|
||||||
|
private void calculateDerivedMetrics(AcidOeeDailySummaryVo summary) {
|
||||||
|
// 时间稼动率
|
||||||
|
Long loadingTime = summary.getLoadingTimeMin() != null ? summary.getLoadingTimeMin() : 0L;
|
||||||
|
Long downtime = summary.getDowntimeMin() != null ? summary.getDowntimeMin() : 0L;
|
||||||
|
if (loadingTime > 0) {
|
||||||
|
BigDecimal availability = BigDecimal.valueOf(loadingTime - downtime)
|
||||||
|
.divide(BigDecimal.valueOf(loadingTime), 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(BigDecimal.valueOf(100));
|
||||||
|
summary.setAvailability(availability);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能稼动率(吨维度)
|
||||||
|
Long runTime = summary.getRunTimeMin() != null ? summary.getRunTimeMin() : 0L;
|
||||||
|
BigDecimal idealCycleTon = summary.getIdealCycleTimeMinPerTon();
|
||||||
|
BigDecimal totalOutputTon = summary.getTotalOutputTon();
|
||||||
|
if (runTime > 0 && idealCycleTon != null && totalOutputTon != null && totalOutputTon.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
BigDecimal idealTime = idealCycleTon.multiply(totalOutputTon);
|
||||||
|
BigDecimal performanceTon = idealTime.divide(BigDecimal.valueOf(runTime), 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(BigDecimal.valueOf(100));
|
||||||
|
summary.setPerformanceTon(performanceTon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能稼动率(卷维度)
|
||||||
|
Long totalOutputCoil = summary.getTotalOutputCoil() != null ? summary.getTotalOutputCoil() : 0L;
|
||||||
|
BigDecimal idealCycleCoil = summary.getIdealCycleTimeMinPerCoil();
|
||||||
|
if (runTime > 0 && idealCycleCoil != null && totalOutputCoil > 0) {
|
||||||
|
BigDecimal idealTime = idealCycleCoil.multiply(BigDecimal.valueOf(totalOutputCoil));
|
||||||
|
BigDecimal performanceCoil = idealTime.divide(BigDecimal.valueOf(runTime), 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(BigDecimal.valueOf(100));
|
||||||
|
summary.setPerformanceCoil(performanceCoil);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 良品率
|
||||||
|
if (totalOutputTon != null && totalOutputTon.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
BigDecimal goodOutputTon = summary.getGoodOutputTon() != null ? summary.getGoodOutputTon() : BigDecimal.ZERO;
|
||||||
|
BigDecimal quality = goodOutputTon.divide(totalOutputTon, 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(BigDecimal.valueOf(100));
|
||||||
|
summary.setQuality(quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OEE(以吨维度为主)
|
||||||
|
BigDecimal availability = summary.getAvailability();
|
||||||
|
BigDecimal performanceTon = summary.getPerformanceTon();
|
||||||
|
BigDecimal quality = summary.getQuality();
|
||||||
|
if (availability != null && performanceTon != null && quality != null) {
|
||||||
|
BigDecimal oee = availability.multiply(performanceTon).multiply(quality)
|
||||||
|
.divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP);
|
||||||
|
summary.setOee(oee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算线性回归(最小二乘法)
|
||||||
|
*/
|
||||||
|
private RegressionResult calculateRegression(List<BigDecimal> xValues, List<Long> yValues) {
|
||||||
|
if (xValues.size() != yValues.size() || xValues.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = xValues.size();
|
||||||
|
BigDecimal sumX = BigDecimal.ZERO;
|
||||||
|
BigDecimal sumY = BigDecimal.ZERO;
|
||||||
|
BigDecimal sumXY = BigDecimal.ZERO;
|
||||||
|
BigDecimal sumX2 = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
BigDecimal x = xValues.get(i);
|
||||||
|
BigDecimal y = BigDecimal.valueOf(yValues.get(i));
|
||||||
|
sumX = sumX.add(x);
|
||||||
|
sumY = sumY.add(y);
|
||||||
|
sumXY = sumXY.add(x.multiply(y));
|
||||||
|
sumX2 = sumX2.add(x.multiply(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal nDecimal = BigDecimal.valueOf(n);
|
||||||
|
BigDecimal denominator = nDecimal.multiply(sumX2).subtract(sumX.multiply(sumX));
|
||||||
|
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// slope = (n*ΣXY - ΣX*ΣY) / (n*ΣX² - (ΣX)²)
|
||||||
|
BigDecimal slope = nDecimal.multiply(sumXY).subtract(sumX.multiply(sumY))
|
||||||
|
.divide(denominator, 6, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
|
// intercept = (ΣY - slope*ΣX) / n
|
||||||
|
BigDecimal intercept = sumY.subtract(slope.multiply(sumX))
|
||||||
|
.divide(nDecimal, 6, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
|
// 计算R²
|
||||||
|
BigDecimal meanY = sumY.divide(nDecimal, 6, RoundingMode.HALF_UP);
|
||||||
|
BigDecimal ssTotal = BigDecimal.ZERO;
|
||||||
|
BigDecimal ssResidual = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
BigDecimal x = xValues.get(i);
|
||||||
|
BigDecimal y = BigDecimal.valueOf(yValues.get(i));
|
||||||
|
BigDecimal predictedY = slope.multiply(x).add(intercept);
|
||||||
|
BigDecimal diff = y.subtract(meanY);
|
||||||
|
ssTotal = ssTotal.add(diff.multiply(diff));
|
||||||
|
BigDecimal residual = y.subtract(predictedY);
|
||||||
|
ssResidual = ssResidual.add(residual.multiply(residual));
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal r2 = BigDecimal.ONE;
|
||||||
|
if (ssTotal.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
r2 = BigDecimal.ONE.subtract(ssResidual.divide(ssTotal, 6, RoundingMode.HALF_UP));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegressionResult(slope, intercept, r2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成拟合线端点
|
||||||
|
*/
|
||||||
|
private List<AcidOeeRegressionVo.RegressionLinePointVo> generateLinePoints(
|
||||||
|
List<AcidOeeRegressionVo.RegressionPointVo> points,
|
||||||
|
RegressionResult result) {
|
||||||
|
if (points.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到X轴的最小值和最大值
|
||||||
|
BigDecimal minX = points.stream()
|
||||||
|
.map(AcidOeeRegressionVo.RegressionPointVo::getWeightTon)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.min(BigDecimal::compareTo)
|
||||||
|
.orElse(BigDecimal.ZERO);
|
||||||
|
|
||||||
|
BigDecimal maxX = points.stream()
|
||||||
|
.map(AcidOeeRegressionVo.RegressionPointVo::getWeightTon)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.max(BigDecimal::compareTo)
|
||||||
|
.orElse(BigDecimal.ZERO);
|
||||||
|
|
||||||
|
// 计算对应的Y值
|
||||||
|
BigDecimal y1 = result.slope.multiply(minX).add(result.intercept);
|
||||||
|
BigDecimal y2 = result.slope.multiply(maxX).add(result.intercept);
|
||||||
|
|
||||||
|
AcidOeeRegressionVo.RegressionLinePointVo p1 = new AcidOeeRegressionVo.RegressionLinePointVo();
|
||||||
|
p1.setWeightTon(minX);
|
||||||
|
p1.setDurationMin(y1);
|
||||||
|
|
||||||
|
AcidOeeRegressionVo.RegressionLinePointVo p2 = new AcidOeeRegressionVo.RegressionLinePointVo();
|
||||||
|
p2.setWeightTon(maxX);
|
||||||
|
p2.setDurationMin(y2);
|
||||||
|
|
||||||
|
return Arrays.asList(p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析日期字符串为Date对象
|
||||||
|
*/
|
||||||
|
private Date parseDate(String dateStr) {
|
||||||
|
if (StringUtils.isBlank(dateStr)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
return sdf.parse(dateStr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析日期失败: {}", dateStr, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回归结果内部类
|
||||||
|
*/
|
||||||
|
private static class RegressionResult {
|
||||||
|
final BigDecimal slope;
|
||||||
|
final BigDecimal intercept;
|
||||||
|
final BigDecimal r2;
|
||||||
|
|
||||||
|
RegressionResult(BigDecimal slope, BigDecimal intercept, BigDecimal r2) {
|
||||||
|
this.slope = slope;
|
||||||
|
this.intercept = intercept;
|
||||||
|
this.r2 = r2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部统计类:某一 stopType 的总损失时间与次数
|
||||||
|
*/
|
||||||
|
private static class LossStats {
|
||||||
|
long totalMin = 0L;
|
||||||
|
int count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.klp.pocket.common.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 钢卷良品/次品判定(OEE 口径用)。
|
||||||
|
*
|
||||||
|
* 规则:
|
||||||
|
* - 以 WMS 的 wms_material_coil 为准
|
||||||
|
* - 按“当前钢卷号(current_coil_no) + 所在库区ID(warehouse_id)”精确匹配
|
||||||
|
* - quality_status 命中 {"C+","C","C-","D+","D","D-"} => 次品;否则 => 良品
|
||||||
|
* - 若匹配不到(WMS 无记录),返回 null,调用方按“忽略不计”处理
|
||||||
|
*/
|
||||||
|
public interface ICoilQualityJudgeService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param warehouseId 所在库区ID(成品库库区ID)
|
||||||
|
* @param currentCoilNo 当前钢卷号(注意:pocket 侧查询到的“钢卷id/卷号”口径等同于该字段)
|
||||||
|
* @return Boolean:true=次品,false=良品,null=匹配不到(忽略不计)
|
||||||
|
*/
|
||||||
|
Boolean isScrap(Long warehouseId, String currentCoilNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.klp.pocket.common.service.impl;
|
||||||
|
|
||||||
|
import com.klp.common.utils.StringUtils;
|
||||||
|
import com.klp.pocket.common.service.ICoilQualityJudgeService;
|
||||||
|
import com.klp.service.IWmsMaterialCoilService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class CoilQualityJudgeServiceImpl implements ICoilQualityJudgeService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 次品状态枚举:命中这些 quality_status 则判定为次品,否则判定为良品。
|
||||||
|
*/
|
||||||
|
private static final Set<String> SCRAP_QUALITY_STATUS = new HashSet<>(
|
||||||
|
Arrays.asList("C+", "C", "C-", "D+", "D", "D-")
|
||||||
|
);
|
||||||
|
|
||||||
|
private final IWmsMaterialCoilService wmsMaterialCoilService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean isScrap(Long warehouseId, String currentCoilNo) {
|
||||||
|
if (warehouseId == null || StringUtils.isBlank(currentCoilNo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String qualityStatus = wmsMaterialCoilService
|
||||||
|
.queryQualityStatusByWarehouseIdAndCurrentCoilNo(warehouseId, currentCoilNo);
|
||||||
|
if (StringUtils.isBlank(qualityStatus)) {
|
||||||
|
// WMS 匹配不到或字段为空:按“忽略不计”
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return SCRAP_QUALITY_STATUS.contains(qualityStatus.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
119
klp-pocket/src/main/resources/mapper/pocket/AcidOeeMapper.xml
Normal file
119
klp-pocket/src/main/resources/mapper/pocket/AcidOeeMapper.xml
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.klp.pocket.acid.mapper.AcidOeeMapper">
|
||||||
|
|
||||||
|
<!-- OEE日汇总结果映射 -->
|
||||||
|
<resultMap id="AcidOeeDailySummaryResultMap" type="com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo">
|
||||||
|
<result column="stat_date" property="statDate" jdbcType="VARCHAR"/>
|
||||||
|
<result column="line_id" property="lineId" jdbcType="VARCHAR"/>
|
||||||
|
<result column="line_name" property="lineName" jdbcType="VARCHAR"/>
|
||||||
|
<result column="planned_time_min" property="plannedTimeMin" jdbcType="BIGINT"/>
|
||||||
|
<result column="planned_downtime_min" property="plannedDowntimeMin" jdbcType="BIGINT"/>
|
||||||
|
<result column="loading_time_min" property="loadingTimeMin" jdbcType="BIGINT"/>
|
||||||
|
<result column="downtime_min" property="downtimeMin" jdbcType="BIGINT"/>
|
||||||
|
<result column="run_time_min" property="runTimeMin" jdbcType="BIGINT"/>
|
||||||
|
<result column="total_output_ton" property="totalOutputTon" jdbcType="DECIMAL"/>
|
||||||
|
<result column="total_output_coil" property="totalOutputCoil" jdbcType="BIGINT"/>
|
||||||
|
<result column="good_output_ton" property="goodOutputTon" jdbcType="DECIMAL"/>
|
||||||
|
<result column="good_output_coil" property="goodOutputCoil" jdbcType="BIGINT"/>
|
||||||
|
<result column="defect_output_ton" property="defectOutputTon" jdbcType="DECIMAL"/>
|
||||||
|
<result column="defect_output_coil" property="defectOutputCoil" jdbcType="BIGINT"/>
|
||||||
|
<result column="ideal_cycle_time_min_per_ton" property="idealCycleTimeMinPerTon" jdbcType="DECIMAL"/>
|
||||||
|
<result column="ideal_cycle_time_min_per_coil" property="idealCycleTimeMinPerCoil" jdbcType="DECIMAL"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 查询OEE日汇总 -->
|
||||||
|
<select id="selectDailySummary" resultMap="AcidOeeDailySummaryResultMap">
|
||||||
|
SELECT
|
||||||
|
DATE_FORMAT(e.INSDATE, '%Y-%m-%d') AS stat_date,
|
||||||
|
'SY' AS line_id,
|
||||||
|
'酸轧线' AS line_name,
|
||||||
|
-- 计划时间:暂时使用24小时(1440分钟),后续可从计划表获取
|
||||||
|
1440 AS planned_time_min,
|
||||||
|
-- 计划停机:暂时为0,后续可从停机事件表中筛选stop_type='计划停机'的汇总
|
||||||
|
0 AS planned_downtime_min,
|
||||||
|
-- 负荷时间 = 计划时间 - 计划停机
|
||||||
|
1440 AS loading_time_min,
|
||||||
|
-- 停机时间:在Service层通过停机事件表聚合填充
|
||||||
|
0 AS downtime_min,
|
||||||
|
-- 总产量(吨):出口重量总和
|
||||||
|
COALESCE(SUM(e.EXIT_WEIGHT), 0) AS total_output_ton,
|
||||||
|
-- 总产量(卷):记录数
|
||||||
|
COUNT(*) AS total_output_coil,
|
||||||
|
-- 良品/次品:在Service层通过WMS判定后填充
|
||||||
|
0 AS good_output_ton,
|
||||||
|
0 AS good_output_coil,
|
||||||
|
0 AS defect_output_ton,
|
||||||
|
0 AS defect_output_coil,
|
||||||
|
-- 理论节拍:在Service层通过回归数据填充
|
||||||
|
NULL AS ideal_cycle_time_min_per_ton,
|
||||||
|
NULL AS ideal_cycle_time_min_per_coil
|
||||||
|
FROM klptcm1_pdo_excoil e
|
||||||
|
WHERE DATE(e.INSDATE) BETWEEN #{startDate} AND #{endDate}
|
||||||
|
GROUP BY DATE_FORMAT(e.INSDATE, '%Y-%m-%d')
|
||||||
|
ORDER BY stat_date ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 回归数据散点结果映射 -->
|
||||||
|
<resultMap id="RegressionPointResultMap" type="com.klp.pocket.acid.domain.vo.AcidOeeRegressionVo$RegressionPointVo">
|
||||||
|
<result column="weight_ton" property="weightTon" jdbcType="DECIMAL"/>
|
||||||
|
<result column="coil_count" property="coilCount" jdbcType="BIGINT"/>
|
||||||
|
<result column="duration_min" property="durationMin" jdbcType="BIGINT"/>
|
||||||
|
<result column="action_id" property="actionId" jdbcType="VARCHAR"/>
|
||||||
|
<result column="create_time" property="createTime" jdbcType="VARCHAR"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 查询回归数据散点 -->
|
||||||
|
<select id="selectRegressionPoints" resultMap="RegressionPointResultMap">
|
||||||
|
SELECT
|
||||||
|
-- 重量(吨):出口重量
|
||||||
|
e.EXIT_WEIGHT AS weight_ton,
|
||||||
|
-- 卷数:固定为1(每条记录代表一卷)
|
||||||
|
1 AS coil_count,
|
||||||
|
-- 时长(分钟):结束时间 - 开始时间,转换为分钟
|
||||||
|
CASE
|
||||||
|
WHEN e.START_DATE IS NOT NULL AND e.END_DATE IS NOT NULL THEN
|
||||||
|
TIMESTAMPDIFF(MINUTE, e.START_DATE, e.END_DATE)
|
||||||
|
ELSE NULL
|
||||||
|
END AS duration_min,
|
||||||
|
-- 关联ID:使用卷号作为标识
|
||||||
|
e.ENCOILID AS action_id,
|
||||||
|
-- 创建时间
|
||||||
|
DATE_FORMAT(e.INSDATE, '%Y-%m-%d %H:%i:%s') AS create_time
|
||||||
|
FROM klptcm1_pdo_excoil e
|
||||||
|
WHERE 1=1
|
||||||
|
<if test="startDate != null and startDate != ''">
|
||||||
|
AND DATE(e.INSDATE) >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null and endDate != ''">
|
||||||
|
AND DATE(e.INSDATE) <= #{endDate}
|
||||||
|
</if>
|
||||||
|
-- 过滤掉无效数据:必须有开始和结束时间,且时长大于0
|
||||||
|
AND e.START_DATE IS NOT NULL
|
||||||
|
AND e.END_DATE IS NOT NULL
|
||||||
|
AND e.END_DATE > e.START_DATE
|
||||||
|
AND e.EXIT_WEIGHT IS NOT NULL
|
||||||
|
AND e.EXIT_WEIGHT > 0
|
||||||
|
ORDER BY e.INSDATE ASC
|
||||||
|
-- 限制最多返回最近6个月的数据(避免数据量过大)
|
||||||
|
LIMIT 10000
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询每日的钢卷号和重量(用于良品/次品判定) -->
|
||||||
|
<select id="selectCoilInfoByDate" resultType="com.klp.pocket.acid.mapper.AcidOeeMapper$CoilInfoByDate">
|
||||||
|
SELECT
|
||||||
|
DATE_FORMAT(e.INSDATE, '%Y-%m-%d') AS statDate,
|
||||||
|
-- 当前钢卷号:使用出口卷号(excoilid)或成品卷号(encoilid)
|
||||||
|
COALESCE(e.EXCOILID, e.ENCOILID) AS coilNo,
|
||||||
|
-- 重量(吨):出口重量
|
||||||
|
e.EXIT_WEIGHT AS weight
|
||||||
|
FROM klptcm1_pdo_excoil e
|
||||||
|
WHERE DATE(e.INSDATE) BETWEEN #{startDate} AND #{endDate}
|
||||||
|
AND e.EXIT_WEIGHT IS NOT NULL
|
||||||
|
AND e.EXIT_WEIGHT > 0
|
||||||
|
AND (e.EXCOILID IS NOT NULL OR e.ENCOILID IS NOT NULL)
|
||||||
|
ORDER BY e.INSDATE ASC, e.ENCOILID ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
||||||
@@ -108,6 +108,19 @@ public interface IWmsMaterialCoilService {
|
|||||||
*/
|
*/
|
||||||
List<WmsMaterialCoilDeliveryExportVo> queryDeliveryExportList(WmsMaterialCoilBo bo);
|
List<WmsMaterialCoilDeliveryExportVo> queryDeliveryExportList(WmsMaterialCoilBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按“所在库区 + 当前钢卷号”精确查询钢卷质量状态(用于良品/次品口径判定)。
|
||||||
|
*
|
||||||
|
* 说明:
|
||||||
|
* - 仅匹配 data_type=1 且 del_flag=0 的当前有效数据
|
||||||
|
* - 若匹配不到,返回 null(调用方按“忽略不计”处理)
|
||||||
|
*
|
||||||
|
* @param warehouseId 所在库区ID(wms_material_coil.warehouse_id)
|
||||||
|
* @param currentCoilNo 当前钢卷号(wms_material_coil.current_coil_no)
|
||||||
|
* @return qualityStatus(如 "C+","C","C-","D+","D","D-"),或 null
|
||||||
|
*/
|
||||||
|
String queryQualityStatusByWarehouseIdAndCurrentCoilNo(Long warehouseId, String currentCoilNo);
|
||||||
|
|
||||||
int exportCoil(@NotEmpty(message = "主键不能为空") Long coilId);
|
int exportCoil(@NotEmpty(message = "主键不能为空") Long coilId);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.klp.service.impl;
|
|||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.esotericsoftware.minlog.Log;
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.klp.common.core.domain.entity.SysUser;
|
import com.klp.common.core.domain.entity.SysUser;
|
||||||
import com.klp.common.core.page.TableDataInfo;
|
import com.klp.common.core.page.TableDataInfo;
|
||||||
@@ -12,11 +11,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.klp.common.exception.ServiceException;
|
|
||||||
import com.klp.common.helper.LoginHelper;
|
import com.klp.common.helper.LoginHelper;
|
||||||
import com.klp.common.utils.DateUtils;
|
import com.klp.common.utils.DateUtils;
|
||||||
import com.klp.common.utils.StringUtils;
|
import com.klp.common.utils.StringUtils;
|
||||||
import com.klp.common.utils.spring.SpringUtils;
|
|
||||||
import com.klp.domain.*;
|
import com.klp.domain.*;
|
||||||
import com.klp.domain.bo.*;
|
import com.klp.domain.bo.*;
|
||||||
import com.klp.domain.vo.*;
|
import com.klp.domain.vo.*;
|
||||||
@@ -842,6 +839,22 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String queryQualityStatusByWarehouseIdAndCurrentCoilNo(Long warehouseId, String currentCoilNo) {
|
||||||
|
if (warehouseId == null || StringUtils.isBlank(currentCoilNo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
|
||||||
|
lqw.select(WmsMaterialCoil::getQualityStatus);
|
||||||
|
lqw.eq(WmsMaterialCoil::getDelFlag, 0);
|
||||||
|
lqw.eq(WmsMaterialCoil::getDataType, 1);
|
||||||
|
lqw.eq(WmsMaterialCoil::getWarehouseId, warehouseId);
|
||||||
|
lqw.eq(WmsMaterialCoil::getCurrentCoilNo, currentCoilNo);
|
||||||
|
lqw.last("LIMIT 1");
|
||||||
|
WmsMaterialCoil one = baseMapper.selectOne(lqw);
|
||||||
|
return one == null ? null : one.getQualityStatus();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从联查结果中构建物品对象(产品或原材料)
|
* 从联查结果中构建物品对象(产品或原材料)
|
||||||
* 直接从VO的临时字段中获取数据构建对象,避免单独查询数据库
|
* 直接从VO的临时字段中获取数据构建对象,避免单独查询数据库
|
||||||
|
|||||||
Reference in New Issue
Block a user