refactor: drop klp_pocketfactory/cgldb/double_rack and related code

- Remove klp-pocket module entirely (acid/galvanize1/common)
- Remove klp-da OEE files (depended on pocket)
- Remove klp-wms Dr* files (double-rack)
- Strip double-rack code from WmsCoilPendingActionServiceImpl
- Remove acid/galvanize1/double-rack datasources from prod/dev yml
- Remove sql-server-api, da.oee, oee.acid config
This commit is contained in:
2026-06-08 15:42:29 +08:00
parent 5cd31efef1
commit e793b19c41
2532 changed files with 13606 additions and 25940 deletions

View File

@@ -19,12 +19,6 @@
<groupId>com.klp</groupId>
<artifactId>klp-framework</artifactId>
</dependency>
<!-- OEE 聚合依赖 pocket 原子能力(酸轧/镀锌一线) -->
<dependency>
<groupId>com.klp</groupId>
<artifactId>klp-pocket</artifactId>
<version>0.8.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@@ -1,884 +0,0 @@
package com.klp.da.controller;
import com.alibaba.fastjson2.JSON;
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.OeeWordAiAnalysisService;
import com.klp.da.service.OeeReportJobService;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo;
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
import com.klp.pocket.acid.service.IAcidOeeService;
import com.klp.pocket.galvanize1.service.IGalvanizeOeeService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.springframework.http.HttpHeaders;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.annotation.Validated;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* OEE 报表对外接口(聚合层)
*
* 当前阶段:主要暴露酸轧线相关接口,通过 `klp-pocket` 的 {@link IAcidOeeService} 取数。
* - 当月 summary / loss7 优先走 Redis 预计算缓存;
* - 任意日期范围通过异步任务接口实现。
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/oee/line")
public class OeeReportController extends BaseController {
private final IAcidOeeService acidOeeService;
private final IGalvanizeOeeService galvanizeOeeService;
private final StringRedisTemplate stringRedisTemplate;
private final OeeReportJobService oeeReportJobService;
private final OeeWordAiAnalysisService oeeWordAiAnalysisService;
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
* 说明:
* - 支持 startDate/endDate 参数yyyy-MM-dd
* - 若不传则默认查询当前月份1号~今天);
* - 仅实时计算,不走缓存。
*/
@GetMapping("/acid/summary")
public R<List<AcidOeeDailySummaryVo>> getAcidSummary(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate,
@RequestParam(required = false, defaultValue = "acid") String lineType
) {
String[] range = resolveDateRange(startDate, endDate);
List<AcidOeeDailySummaryVo> dailyList = isGalvanize(lineType)
? galvanizeOeeService.getDailySummary(range[0], range[1])
: 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,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate,
@RequestParam(required = false, defaultValue = "acid") String lineType
) {
String[] range = resolveDateRange(startDate, endDate);
List<AcidOeeLoss7Vo> lossList = isGalvanize(lineType)
? galvanizeOeeService.getLoss7Summary(range[0], range[1])
: 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,
@RequestParam(required = false, defaultValue = "acid") String lineType
) {
// 事件明细底层按「日期」查询,这里从时间字符串中截取日期部分
String startDate = extractDateOrDefault(startTime, true);
String endDate = extractDateOrDefault(endTime, false);
List<Klptcm1ProStoppageVo> events = isGalvanize(lineType)
? galvanizeOeeService.getStoppageEvents(startDate, endDate)
: 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/idealCycle
*/
@GetMapping("/acid/idealCycle")
public R<AcidOeeIdealCycleVo> getAcidIdealCycle(
@RequestParam String startDate,
@RequestParam String endDate,
@RequestParam(required = false, defaultValue = "acid") String lineType
) {
AcidOeeIdealCycleVo data = isGalvanize(lineType)
? galvanizeOeeService.getIdealCycle(startDate, endDate)
: acidOeeService.getIdealCycle(startDate, endDate);
return R.ok(data);
}
/**
* 导出酸轧线 OEE 报表 Worddocx
*
* 路由GET /oee/line/acid/exportWord?startDate=yyyy-MM-dd&endDate=yyyy-MM-dd
* 说明:
* - 文档结构尽量贴近前端页面(表格为主,图表不嵌入);
* - 将右侧“公式与口径说明”置于文档最上方。
*/
@GetMapping("/acid/exportWord")
public void exportAcidWord(
@RequestParam String startDate,
@RequestParam String endDate,
HttpServletResponse response
) throws IOException {
String[] range = resolveDateRange(startDate, endDate);
String start = range[0];
String end = range[1];
// 取数:按选择范围实时拉取(避免 summary/loss7 的“当月固定”逻辑)
List<AcidOeeDailySummaryVo> summary = acidOeeService.getDailySummary(start, end);
List<AcidOeeLoss7Vo> loss7 = acidOeeService.getLoss7Summary(start, end);
List<Klptcm1ProStoppageVo> events = acidOeeService.getStoppageEvents(start, end);
AcidOeeIdealCycleVo idealCycle = acidOeeService.getIdealCycle(start, end);
XWPFDocument doc = new XWPFDocument();
// 标题
addTitle(doc, String.format("酸轧线OEE报表%s ~ %s", start, end));
// 口径说明(置顶)
addSectionTitle(doc, "公式与口径说明");
addParagraph(doc, "OEE 总公式OEE = A × P × Q", true);
addBullet(doc, "A时间稼动率 = (负荷时间 停机时间) / 负荷时间");
addBullet(doc, "P性能稼动率吨维度 = (理论节拍 × 产量吨) / 实际运转时间");
addBullet(doc, "Q良品率 = 良品吨 / 总产量吨");
addParagraph(doc, "关键字段定义:", true);
addBullet(doc, "负荷时间:计划生产时间扣除计划停机后的时间");
addBullet(doc, "停机时间:所有停机/中断(按 stop_type 汇总)的总时长");
addBullet(doc, "实际运转时间:负荷时间 停机时间");
addBullet(doc, "理论节拍:按“优良日统计口径”得到的稳定节拍(分钟/吨)");
addBullet(doc, "良品/次品:按 WMS quality_status 判断C+/C/C-/D+/D/D- 为次品");
if (idealCycle != null) {
addParagraph(doc, "当前理论节拍(统计口径):", true);
XWPFTable t = doc.createTable(5, 2);
setTableHeaderRow(t.getRow(0), "指标", "");
setTableRow(t.getRow(1), "理论节拍 (min/吨)", fmtNum(getIdealCycleTime(idealCycle), 2));
setTableRow(t.getRow(2), "生产节拍中位数 (min/吨)", fmtNum(getMedianCycleTime(idealCycle), 2));
setTableRow(t.getRow(3), "样本天数", String.valueOf(getSampleDays(idealCycle)));
setTableRow(t.getRow(4), "吨数下限 (吨)", fmtNum(getMinWeightTon(idealCycle), 2));
}
// 一、KPI 总览
addSectionTitle(doc, "一、KPI 总览(酸轧线 SY");
addParagraph(doc, "展示所选期间酸轧线整体 OEE 及 A/P/Q 等关键指标,用于快速判断本期综合表现好坏。", false);
AcidKpiAgg kpi = calcKpi(summary);
XWPFTable kpiTable = doc.createTable(2, 11);
setTableRow(kpiTable.getRow(0),
"OEE (%)",
"时间稼动率 A (%)",
"性能稼动率 P_ton (%)",
"良品率 Q (%)",
"负荷时间 (min)",
"停机时间 (min)",
"运转时间 (min)",
"总产量 (吨)",
"总产量 (卷)",
"良品量 (吨)",
"次品量 (吨)"
);
setTableRow(kpiTable.getRow(1),
fmtPercent1(kpi.oee),
fmtPercent1(kpi.availability),
fmtPercent1(kpi.performanceTon),
fmtPercent1(kpi.quality),
fmtInt(kpi.loadingTimeMin),
fmtInt(kpi.downtimeMin),
fmtInt(kpi.runTimeMin),
fmtNum(kpi.totalOutputTon, 2),
fmtInt(kpi.totalOutputCoil),
fmtNum(kpi.goodOutputTon, 2),
fmtNum(kpi.defectOutputTon, 2)
);
addAiAnalysisIfEnabled(doc, "KPI 总览", buildMarkdownTable(
new String[]{"OEE(%)", "A(%)", "P_ton(%)", "Q(%)", "负荷(min)", "停机(min)", "运转(min)", "总产(吨)", "总产(卷)", "良品(吨)", "次品(吨)"},
new String[][]{{
fmtPercent1(kpi.oee),
fmtPercent1(kpi.availability),
fmtPercent1(kpi.performanceTon),
fmtPercent1(kpi.quality),
fmtInt(kpi.loadingTimeMin),
fmtInt(kpi.downtimeMin),
fmtInt(kpi.runTimeMin),
fmtNum(kpi.totalOutputTon, 2),
fmtInt(kpi.totalOutputCoil),
fmtNum(kpi.goodOutputTon, 2),
fmtNum(kpi.defectOutputTon, 2)
}}), "区间:" + start + " ~ " + end);
// 二、日明细
addSectionTitle(doc, "二、日明细(用于趋势分析)");
addParagraph(doc, "按天拆分 A/P/Q 及产量等指标,用于观察趋势、波动点以及与重大事件的对应关系。", false);
XWPFTable dailyTable = doc.createTable(Math.max(1, summary.size()) + 1, 11);
setTableRow(dailyTable.getRow(0),
"日期",
"OEE (%)",
"A (%)",
"P_ton (%)",
"Q (%)",
"负荷 (min)",
"停机 (min)",
"运转 (min)",
"总产量 (吨)",
"总产量 (卷)",
"良品 (吨)"
);
for (int i = 0; i < summary.size(); i++) {
AcidOeeDailySummaryVo row = summary.get(i);
setTableRow(dailyTable.getRow(i + 1),
safeStr(row.getStatDate()),
fmtPercent1(row.getOee()),
fmtPercent1(row.getAvailability()),
fmtPercent1(row.getPerformanceTon()),
fmtPercent1(row.getQuality()),
fmtInt(row.getLoadingTimeMin()),
fmtInt(row.getDowntimeMin()),
fmtInt(row.getRunTimeMin()),
fmtNum(row.getTotalOutputTon(), 2),
fmtInt(row.getTotalOutputCoil()),
fmtNum(row.getGoodOutputTon(), 2)
);
}
addAiAnalysisIfEnabled(doc, "日明细", buildDailySummaryMarkdown(summary, 31), "区间:" + start + " ~ " + end);
// 三、理论节拍(说明 + 表)
addSectionTitle(doc, "三、理论节拍(统计口径)");
addParagraph(doc, "基于历史“优良日”统计得到的理论节拍(中位数),用于作为性能稼动率计算的稳定标尺。", false);
if (idealCycle == null) {
addParagraph(doc, "(本区间未获取到理论节拍数据)", false);
}
// 四、7 大损失
addSectionTitle(doc, "四、7 大损失汇总(按 stop_type 分类)");
addParagraph(doc, "将所有停机事件按 stop_type 归类,统计时间占比和次数,用于确定“先从哪几类损失下手改善”。", false);
XWPFTable loss7Table = doc.createTable(Math.max(1, loss7.size()) + 1, 5);
setTableRow(loss7Table.getRow(0),
"损失类别",
"损失时间 (min)",
"占比 (%)",
"次数",
"平均时长 (min)"
);
for (int i = 0; i < loss7.size(); i++) {
AcidOeeLoss7Vo row = loss7.get(i);
setTableRow(loss7Table.getRow(i + 1),
safeStr(row.getLossCategoryName()),
fmtNum(row.getLossTimeMin(), 2),
fmtPercent1(row.getLossTimeRate()),
fmtInt(row.getCount()),
fmtNum(row.getAvgDurationMin(), 2)
);
}
addAiAnalysisIfEnabled(doc, "7 大损失汇总", buildLoss7Markdown(loss7, 30), "区间:" + start + " ~ " + end);
// 五、停机/损失事件明细
addSectionTitle(doc, "五、停机/损失事件明细");
addParagraph(doc, "罗列每一条停机/损失事件,包含时间段、区域、机组和备注,方便对照现场记录进行原因分析。", false);
int eventRows = Math.max(1, events.size()) + 1;
XWPFTable eventTable = doc.createTable(eventRows, 9);
setTableRow(eventTable.getRow(0),
"开始时间",
"结束时间",
"时长 (s)",
"时长 (min)",
"停机类型 (stop_type)",
"区域",
"机组",
"班次",
"备注"
);
for (int i = 0; i < events.size(); i++) {
Klptcm1ProStoppageVo e = events.get(i);
long durationSec = safeLong(e.getDuration());
double durationMin = durationSec / 60d;
setTableRow(eventTable.getRow(i + 1),
safeStr(e.getStartDate()),
safeStr(e.getEndDate()),
String.valueOf(durationSec),
fmtNum(durationMin, 3),
safeStr(e.getStopType()),
safeStr(e.getArea()),
safeStr(e.getUnit()),
safeStr(e.getShift()),
safeStr(e.getRemark())
);
}
addAiAnalysisIfEnabled(doc, "停机/损失事件明细", buildEventsMarkdown(events, 40), "区间:" + start + " ~ " + end);
String filename = String.format("酸轧线OEE报表_%s_%s.docx", start, end);
String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8.name());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename*=UTF-8''" + encoded);
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store, no-cache");
try (ServletOutputStream os = response.getOutputStream()) {
doc.write(os);
os.flush();
} finally {
doc.close();
}
}
/**
* 若未显式传入日期范围,则默认当前月 [1号, 今天]。
*/
private boolean isGalvanize(String lineType) {
return "galvanize1".equalsIgnoreCase(lineType) || "galvanize".equalsIgnoreCase(lineType) || "dx".equalsIgnoreCase(lineType);
}
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);
}
// ======================== Word 生成辅助方法 ========================
private void addTitle(XWPFDocument doc, String text) {
XWPFParagraph p = doc.createParagraph();
p.setAlignment(ParagraphAlignment.CENTER);
XWPFRun r = p.createRun();
r.setBold(true);
r.setFontSize(16);
r.setText(text);
}
private void addSectionTitle(XWPFDocument doc, String text) {
XWPFParagraph p = doc.createParagraph();
p.setSpacingBefore(200);
XWPFRun r = p.createRun();
r.setBold(true);
r.setFontSize(12);
r.setText(text);
}
private void addParagraph(XWPFDocument doc, String text, boolean bold) {
XWPFParagraph p = doc.createParagraph();
XWPFRun r = p.createRun();
r.setBold(bold);
r.setFontSize(11);
r.setText(text);
}
private void addBullet(XWPFDocument doc, String text) {
XWPFParagraph p = doc.createParagraph();
XWPFRun r = p.createRun();
r.setFontSize(11);
r.setText("" + text);
}
private void addAiAnalysisIfEnabled(XWPFDocument doc, String tableTitle, String tableMarkdown, String extraContext) {
String analysis = null;
try {
analysis = oeeWordAiAnalysisService.analyzeTable(tableTitle, tableMarkdown, extraContext);
} catch (Exception e) {
// 不影响导出
}
if (StringUtils.isNotBlank(analysis)) {
addParagraph(doc, "AI 分析:", true);
// 多行按段落输出
String[] lines = analysis.split("\\r?\\n");
for (String line : lines) {
if (StringUtils.isNotBlank(line)) {
addBullet(doc, line.trim().replaceFirst("^[\\-•\\*\\d\\.\\s]+", ""));
}
}
}
}
private String buildMarkdownTable(String[] headers, String[][] rows) {
StringBuilder sb = new StringBuilder();
sb.append("|");
for (String h : headers) sb.append(escapePipe(h)).append("|");
sb.append("\n|");
for (int i = 0; i < headers.length; i++) sb.append("---|");
sb.append("\n");
for (String[] r : rows) {
sb.append("|");
for (String c : r) sb.append(escapePipe(c)).append("|");
sb.append("\n");
}
return sb.toString();
}
private String escapePipe(String s) {
if (s == null) return "";
return s.replace("|", "\\|");
}
private String buildDailySummaryMarkdown(List<AcidOeeDailySummaryVo> list, int maxRows) {
List<AcidOeeDailySummaryVo> src = list == null ? new ArrayList<>() : list;
int n = Math.min(src.size(), Math.max(1, maxRows));
String[] headers = new String[]{"日期", "OEE(%)", "A(%)", "P_ton(%)", "Q(%)", "负荷(min)", "停机(min)", "运转(min)", "总产(吨)", "良品(吨)"};
String[][] rows = new String[n][headers.length];
for (int i = 0; i < n; i++) {
AcidOeeDailySummaryVo r = src.get(i);
rows[i] = new String[]{
safeStr(r.getStatDate()),
fmtPercent1(r.getOee()),
fmtPercent1(r.getAvailability()),
fmtPercent1(r.getPerformanceTon()),
fmtPercent1(r.getQuality()),
fmtInt(r.getLoadingTimeMin()),
fmtInt(r.getDowntimeMin()),
fmtInt(r.getRunTimeMin()),
fmtNum(r.getTotalOutputTon(), 2),
fmtNum(r.getGoodOutputTon(), 2)
};
}
String md = buildMarkdownTable(headers, rows);
if (src.size() > n) {
md += "\n仅展示前 " + n + " 行,已截断)\n";
}
return md;
}
private String buildLoss7Markdown(List<AcidOeeLoss7Vo> list, int maxRows) {
List<AcidOeeLoss7Vo> src = list == null ? new ArrayList<>() : list;
int n = Math.min(src.size(), Math.max(1, maxRows));
String[] headers = new String[]{"损失类别", "损失时间(min)", "占比(%)", "次数", "平均时长(min)"};
String[][] rows = new String[n][headers.length];
for (int i = 0; i < n; i++) {
AcidOeeLoss7Vo r = src.get(i);
rows[i] = new String[]{
safeStr(r.getLossCategoryName()),
fmtNum(r.getLossTimeMin(), 2),
fmtPercent1(r.getLossTimeRate()),
fmtInt(r.getCount()),
fmtNum(r.getAvgDurationMin(), 2)
};
}
String md = buildMarkdownTable(headers, rows);
if (src.size() > n) {
md += "\n仅展示前 " + n + " 行,已截断)\n";
}
return md;
}
private String buildEventsMarkdown(List<Klptcm1ProStoppageVo> list, int maxRows) {
List<Klptcm1ProStoppageVo> src = list == null ? new ArrayList<>() : list;
int n = Math.min(src.size(), Math.max(1, maxRows));
String[] headers = new String[]{"开始", "结束", "时长(s)", "时长(min)", "停机类型", "区域", "机组", "备注"};
String[][] rows = new String[n][headers.length];
for (int i = 0; i < n; i++) {
Klptcm1ProStoppageVo e = src.get(i);
long durationSec = safeLong(e.getDuration());
rows[i] = new String[]{
safeStr(e.getStartDate()),
safeStr(e.getEndDate()),
String.valueOf(durationSec),
fmtNum(durationSec / 60d, 3),
safeStr(e.getStopType()),
safeStr(e.getArea()),
safeStr(e.getUnit()),
safeStr(e.getRemark())
};
}
String md = buildMarkdownTable(headers, rows);
md += "\n总事件数" + src.size() + "\n";
if (src.size() > n) {
md += "(仅展示前 " + n + " 行,已截断)\n";
}
return md;
}
private void setTableHeaderRow(XWPFTableRow row, String c1, String c2) {
setCellText(row.getCell(0), c1, true);
setCellText(row.getCell(1), c2, true);
}
private void setTableRow(XWPFTableRow row, String... cols) {
for (int i = 0; i < cols.length; i++) {
XWPFTableCell cell = row.getCell(i);
if (cell == null) {
cell = row.createCell();
}
setCellText(cell, cols[i], false);
}
}
private void setCellText(XWPFTableCell cell, String text, boolean bold) {
cell.removeParagraph(0);
XWPFParagraph p = cell.addParagraph();
XWPFRun r = p.createRun();
r.setBold(bold);
r.setFontSize(10);
r.setText(text == null ? "" : text);
}
private String safeStr(Object o) {
return o == null ? "" : String.valueOf(o);
}
private long safeLong(Object o) {
if (o == null) return 0L;
try {
return Long.parseLong(String.valueOf(o));
} catch (Exception ex) {
return 0L;
}
}
private String fmtPercent1(Object value) {
Double v = toDouble(value);
if (v == null) return "-";
return String.format(Locale.ROOT, "%.1f", v);
}
private String fmtNum(Object value, int scale) {
Double v = toDouble(value);
if (v == null) return "-";
return String.format(Locale.ROOT, "%." + scale + "f", v);
}
private String fmtInt(Object value) {
Double v = toDouble(value);
if (v == null) return "0";
return String.valueOf((long) Math.round(v));
}
private Double toDouble(Object value) {
if (value == null) return null;
if (value instanceof Number) return ((Number) value).doubleValue();
String s = String.valueOf(value);
if (StringUtils.isBlank(s)) return null;
try {
return Double.parseDouble(s);
} catch (Exception e) {
return null;
}
}
@Data
private static class AcidKpiAgg {
private double oee;
private double availability;
private double performanceTon;
private double quality;
private double loadingTimeMin;
private double downtimeMin;
private double runTimeMin;
private double totalOutputTon;
private double totalOutputCoil;
private double goodOutputTon;
private double defectOutputTon;
}
private AcidKpiAgg calcKpi(List<AcidOeeDailySummaryVo> list) {
AcidKpiAgg k = new AcidKpiAgg();
if (list == null || list.isEmpty()) {
return k;
}
int n = list.size();
double sumOee = 0, sumA = 0, sumP = 0, sumQ = 0;
double sumLoading = 0, sumDown = 0, sumRun = 0;
double sumTotalTon = 0, sumGoodTon = 0, sumDefectTon = 0, sumCoil = 0;
for (AcidOeeDailySummaryVo r : list) {
sumLoading += safeNum(r.getLoadingTimeMin());
sumDown += safeNum(r.getDowntimeMin());
sumRun += safeNum(r.getRunTimeMin());
sumTotalTon += safeNum(r.getTotalOutputTon());
sumGoodTon += safeNum(r.getGoodOutputTon());
sumDefectTon += safeNum(r.getDefectOutputTon());
sumCoil += safeNum(r.getTotalOutputCoil());
sumOee += safeNum(r.getOee());
sumA += safeNum(r.getAvailability());
sumP += safeNum(r.getPerformanceTon());
sumQ += safeNum(r.getQuality());
}
double defectAgg = Math.max(0, sumTotalTon - sumGoodTon);
k.setOee(sumOee / n);
k.setAvailability(sumA / n);
k.setPerformanceTon(sumP / n);
k.setQuality(sumQ / n);
k.setLoadingTimeMin(sumLoading);
k.setDowntimeMin(sumDown);
k.setRunTimeMin(sumRun);
k.setTotalOutputTon(sumTotalTon);
k.setTotalOutputCoil(sumCoil);
k.setGoodOutputTon(sumGoodTon);
k.setDefectOutputTon(defectAgg > 0 ? defectAgg : sumDefectTon);
return k;
}
private double safeNum(Object v) {
Double d = toDouble(v);
return d == null ? 0d : d;
}
// idealCycle 字段做容错(避免 VO 字段名差异导致编译失败)
private Object getIdealCycleTime(AcidOeeIdealCycleVo vo) {
try {
return vo.getClass().getMethod("getIdealCycleTimeMinPerTon").invoke(vo);
} catch (Exception e) {
return null;
}
}
private Object getMedianCycleTime(AcidOeeIdealCycleVo vo) {
try {
return vo.getClass().getMethod("getMedianCycleTimeMinPerTon").invoke(vo);
} catch (Exception e) {
return null;
}
}
private int getSampleDays(AcidOeeIdealCycleVo vo) {
try {
Object v = vo.getClass().getMethod("getSampleDays").invoke(vo);
Double d = toDouble(v);
return d == null ? 0 : d.intValue();
} catch (Exception e) {
return 0;
}
}
private Object getMinWeightTon(AcidOeeIdealCycleVo vo) {
try {
return vo.getClass().getMethod("getMinWeightTon").invoke(vo);
} catch (Exception e) {
return null;
}
}
// ======================== 任意日期范围异步任务(酸轧线) ========================
/**
* 提交酸轧线 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;
}
}

View File

@@ -1,68 +0,0 @@
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;
}

View File

@@ -1,44 +0,0 @@
package com.klp.da.service;
import com.klp.common.core.page.TableDataInfo;
import com.klp.da.domain.bo.OeeQueryBo;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
import java.util.List;
/**
* OEE 聚合 Service 接口(酸轧线)
*
* @author klp
* @date 2026-01-31
*/
public interface IDaAcidOeeService {
/**
* 获取 OEE 日汇总(当月走缓存,历史查询走实时)
*
* @param bo 查询条件
* @return 日汇总列表
*/
List<AcidOeeDailySummaryVo> getDailySummary(OeeQueryBo bo);
/**
* 获取 7 大损失汇总(当月走缓存,历史查询走实时)
*
* @param bo 查询条件
* @return 7 大损失列表
*/
List<AcidOeeLoss7Vo> getLoss7Summary(OeeQueryBo bo);
/**
* 获取停机事件明细(实时分页查询)
*
* @param bo 查询条件
* @return 分页后的停机事件列表
*/
TableDataInfo<Klptcm1ProStoppageVo> getStoppageEvents(OeeQueryBo bo);
}

View File

@@ -1,137 +0,0 @@
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;
}
}

View File

@@ -1,132 +0,0 @@
package com.klp.da.service;
import com.klp.common.config.DeepseekConfig;
import com.klp.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* OEE Word 导出:表格内容大模型分析(可选)
*
* 默认关闭,通过配置开启:
* oee:
* word:
* ai:
* enabled: true
*
* 大模型连接配置复用 sales.script.aiDeepSeek
* sales:
* script:
* ai:
* api-key/base-url/model-name/max-retries/temperature
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class OeeWordAiAnalysisService {
@Value("${oee.word.ai.enabled:false}")
private boolean enabled;
@Qualifier("salesScriptRestTemplate")
private final RestTemplate restTemplate;
private final DeepseekConfig deepseekConfig;
public String analyzeTable(String tableTitle, String tableMarkdown, String extraContext) {
if (!enabled) {
return null;
}
if (StringUtils.isBlank(tableMarkdown)) {
return null;
}
if (deepseekConfig == null || StringUtils.isBlank(deepseekConfig.getApiKey())
|| StringUtils.isBlank(deepseekConfig.getBaseUrl())
|| StringUtils.isBlank(deepseekConfig.getModelName())) {
log.warn("OEE Word AI 已开启但 sales.script.ai 配置不完整,跳过分析");
return null;
}
String prompt = buildPrompt(tableTitle, tableMarkdown, extraContext);
return callAi(prompt);
}
private String buildPrompt(String tableTitle, String tableMarkdown, String extraContext) {
StringBuilder sb = new StringBuilder();
sb.append("你是一名制造业OEE报表分析专家。请基于下面表格数据给出简洁的分析结论。\n");
sb.append("输出要求:\n");
sb.append("1) 用中文输出;\n");
sb.append("2) 只输出 3~6 条要点(每条不超过 30 字),不要写长段落;\n");
sb.append("3) 可以指出异常/波动/占比最高项/可能原因与建议方向;\n");
sb.append("4) 不要复述表格标题不要输出Markdown表格。\n\n");
sb.append("【表格】").append(tableTitle).append("\n");
if (StringUtils.isNotBlank(extraContext)) {
sb.append("【上下文】").append(extraContext).append("\n");
}
sb.append("【数据】\n");
sb.append(tableMarkdown);
return sb.toString();
}
@SuppressWarnings("unchecked")
private String callAi(String prompt) {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", deepseekConfig.getModelName());
Map<String, String> systemMessage = new HashMap<>();
systemMessage.put("role", "system");
systemMessage.put("content", "你是一个严谨的数据分析助手");
Map<String, String> userMessage = new HashMap<>();
userMessage.put("role", "user");
userMessage.put("content", prompt);
requestBody.put("messages", Arrays.asList(systemMessage, userMessage));
requestBody.put("temperature", deepseekConfig.getTemperature() == null ? 0.2 : deepseekConfig.getTemperature());
requestBody.put("max_tokens", 800);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(deepseekConfig.getApiKey());
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
int retries = deepseekConfig.getMaxRetries() == null ? 1 : Math.max(1, deepseekConfig.getMaxRetries());
for (int i = 0; i < retries; i++) {
try {
ResponseEntity<Map> response = restTemplate.postForEntity(
deepseekConfig.getBaseUrl() + "/chat/completions", entity, Map.class);
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
Map<String, Object> body = response.getBody();
List<Map<String, Object>> choices = (List<Map<String, Object>>) body.get("choices");
if (choices != null && !choices.isEmpty()) {
Map<String, Object> choice = choices.get(0);
Map<String, Object> message = (Map<String, Object>) choice.get("message");
String content = message == null ? null : (String) message.get("content");
if (StringUtils.isNotBlank(content)) {
return content.trim();
}
}
}
} catch (Exception e) {
log.warn("OEE Word AI 调用失败,重试 {}/{}{}", i + 1, retries, e.getMessage());
}
}
return null;
}
}

View File

@@ -1,92 +0,0 @@
package com.klp.da.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.github.pagehelper.PageInfo;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.utils.DateUtils;
import com.klp.common.utils.StringUtils;
import com.klp.da.domain.bo.OeeQueryBo;
import com.klp.da.service.IDaAcidOeeService;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
import com.klp.pocket.acid.service.IAcidOeeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
@Slf4j
@RequiredArgsConstructor
@Service
public class DaAcidOeeServiceImpl implements IDaAcidOeeService {
private static final String SUMMARY_KEY_PATTERN = "oee:report:month:summary:%s:SY";
private static final DateTimeFormatter YEAR_MONTH_FMT = DateTimeFormatter.ofPattern("yyyyMM");
private final IAcidOeeService acidOeeService;
private final StringRedisTemplate stringRedisTemplate;
@Override
public List<AcidOeeDailySummaryVo> getDailySummary(OeeQueryBo bo) {
// 检查是否查询当月,如果是,则尝试从 Redis 缓存获取
if (isCurrentMonthQuery(bo.getStartDate(), bo.getEndDate())) {
String yyyyMM = LocalDate.now().format(YEAR_MONTH_FMT);
String summaryKey = String.format(SUMMARY_KEY_PATTERN, yyyyMM);
try {
String summaryJson = stringRedisTemplate.opsForValue().get(summaryKey);
if (StringUtils.isNotBlank(summaryJson)) {
log.info("[DaAcidOeeService] Hit cache for acid summary, key={}", summaryKey);
return JSON.parseObject(summaryJson, new TypeReference<List<AcidOeeDailySummaryVo>>() {});
}
} catch (Exception e) {
log.error("[DaAcidOeeService] Failed to get acid summary from redis cache, key={}", summaryKey, e);
}
}
// 缓存未命中或查询历史数据,则实时调用 pocket service
log.info("[DaAcidOeeService] Cache miss for acid summary, calling pocket service...");
return acidOeeService.getDailySummary(
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getStartDate())),
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getEndDate()))
);
}
@Override
public List<AcidOeeLoss7Vo> getLoss7Summary(OeeQueryBo bo) {
// 7大损失目前没有预计算直接实时调用
return acidOeeService.getLoss7Summary(
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getStartDate())),
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getEndDate()))
);
}
@Override
public TableDataInfo<Klptcm1ProStoppageVo> getStoppageEvents(OeeQueryBo bo) {
// 实时分页查询
List<Klptcm1ProStoppageVo> list = acidOeeService.getStoppageEvents(
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getStartDate())),
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getEndDate()))
);
return TableDataInfo.build(list);
}
/**
* 判断查询范围是否为当月
*/
private boolean isCurrentMonthQuery(LocalDate startDate, LocalDate endDate) {
if (startDate == null || endDate == null) {
return false;
}
LocalDate now = LocalDate.now();
LocalDate firstDayOfMonth = now.withDayOfMonth(1);
return !startDate.isBefore(firstDayOfMonth) && !endDate.isAfter(now);
}
}

View File

@@ -2,11 +2,11 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsAutoScheduleMapper">
<mapper namespace="com.fad.aps.mapper.ApsAutoScheduleMapper">
<select id="selectPlanByIdForUpdate"
parameterType="long"
resultType="com.klp.aps.domain.row.ApsSchedulePlanRow">
resultType="com.fad.aps.domain.row.ApsSchedulePlanRow">
SELECT
plan_id AS planId,
plan_code AS planCode,
@@ -23,7 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectOrderDetailsByOrderId"
parameterType="long"
resultType="com.klp.aps.domain.row.ApsOrderDetailRow">
resultType="com.fad.aps.domain.row.ApsOrderDetailRow">
SELECT
detail_id AS detailId,
product_id AS productId,
@@ -36,7 +36,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectPlanDetailsByPlanId"
parameterType="long"
resultType="com.klp.aps.domain.row.ApsPlanDetailRow">
resultType="com.fad.aps.domain.row.ApsPlanDetailRow">
SELECT
detail_id AS detailId,
task_id AS taskId,
@@ -50,7 +50,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="selectLineCandidatesByProductAndLine"
resultType="com.klp.aps.domain.row.ApsLineCapabilityRow">
resultType="com.fad.aps.domain.row.ApsLineCapabilityRow">
SELECT
line_id AS lineId,
capacity_per_hour AS capacityPerHour,
@@ -63,14 +63,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY CASE WHEN product_id = #{productId} THEN 0 ELSE 1 END, priority ASC, capability_id ASC
</select>
<select id="selectLineMetaById" resultType="com.klp.aps.domain.row.ApsLineMetaRow">
<select id="selectLineMetaById" resultType="com.fad.aps.domain.row.ApsLineMetaRow">
SELECT line_id AS lineId, line_name AS lineName
FROM wms_production_line
WHERE line_id = #{lineId}
LIMIT 1
</select>
<select id="selectProductMetaById" resultType="com.klp.aps.domain.row.ApsProductMetaRow">
<select id="selectProductMetaById" resultType="com.fad.aps.domain.row.ApsProductMetaRow">
SELECT product_id AS productId, product_name AS productName, specification AS specification, material AS material
FROM wms_product
WHERE product_id = #{productId}
@@ -78,7 +78,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="selectAvailableShifts"
resultType="com.klp.aps.domain.row.ApsShiftSlotRow">
resultType="com.fad.aps.domain.row.ApsShiftSlotRow">
SELECT
cs.calendar_date AS calendarDate,
cs.line_id AS lineId,
@@ -146,7 +146,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<insert id="insertOperation"
parameterType="com.klp.aps.domain.entity.ApsScheduleOperationEntity"
parameterType="com.fad.aps.domain.entity.ApsScheduleOperationEntity"
useGeneratedKeys="true"
keyProperty="operationId"
keyColumn="operation_id">
@@ -193,7 +193,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</insert>
<insert id="insertChangeLog"
parameterType="com.klp.aps.domain.entity.ApsScheduleChangeLogEntity"
parameterType="com.fad.aps.domain.entity.ApsScheduleChangeLogEntity"
useGeneratedKeys="true"
keyProperty="logId"
keyColumn="log_id">

View File

@@ -2,9 +2,9 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsCalendarMapper">
<mapper namespace="com.fad.aps.mapper.ApsCalendarMapper">
<resultMap type="com.klp.aps.domain.entity.ApsCalendarEntity" id="ApsCalendarResult">
<resultMap type="com.fad.aps.domain.entity.ApsCalendarEntity" id="ApsCalendarResult">
<result property="calendarId" column="calendar_id"/>
<result property="calendarDate" column="calendar_date"/>
<result property="calendarType" column="calendar_type"/>
@@ -22,7 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
FROM wms_calendar
</sql>
<select id="selectCalendarList" parameterType="com.klp.aps.domain.entity.ApsCalendarEntity" resultMap="ApsCalendarResult">
<select id="selectCalendarList" parameterType="com.fad.aps.domain.entity.ApsCalendarEntity" resultMap="ApsCalendarResult">
<include refid="selectCalendarVo"/>
<where>
<if test="calendarDate != null">

View File

@@ -2,9 +2,9 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsCalendarShiftMapper">
<mapper namespace="com.fad.aps.mapper.ApsCalendarShiftMapper">
<select id="selectByDateLineAndShift" resultType="com.klp.aps.domain.entity.ApsCalendarShiftEntity">
<select id="selectByDateLineAndShift" resultType="com.fad.aps.domain.entity.ApsCalendarShiftEntity">
SELECT *
FROM wms_calendar_shift
WHERE calendar_date = #{calendarDate}

View File

@@ -2,11 +2,11 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsGanttMapper">
<mapper namespace="com.fad.aps.mapper.ApsGanttMapper">
<select id="selectGanttItems"
parameterType="com.klp.aps.domain.dto.ApsGanttQueryReq"
resultType="com.klp.aps.domain.vo.ApsGanttItemVo">
parameterType="com.fad.aps.domain.dto.ApsGanttQueryReq"
resultType="com.fad.aps.domain.vo.ApsGanttItemVo">
SELECT
o.operation_id AS operationId,
o.plan_id AS planId,

View File

@@ -2,9 +2,9 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsLineCapabilityMapper">
<mapper namespace="com.fad.aps.mapper.ApsLineCapabilityMapper">
<select id="selectByLineProductAndProcess" resultType="com.klp.aps.domain.entity.ApsLineCapabilityEntity">
<select id="selectByLineProductAndProcess" resultType="com.fad.aps.domain.entity.ApsLineCapabilityEntity">
SELECT *
FROM wms_line_capability
WHERE line_id = #{lineId}

View File

@@ -2,10 +2,10 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsLockMapper">
<mapper namespace="com.fad.aps.mapper.ApsLockMapper">
<insert id="insertLock"
parameterType="com.klp.aps.domain.entity.ApsScheduleLockEntity"
parameterType="com.fad.aps.domain.entity.ApsScheduleLockEntity"
useGeneratedKeys="true"
keyProperty="lockId"
keyColumn="lock_id">

View File

@@ -2,10 +2,10 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsOperationMapper">
<mapper namespace="com.fad.aps.mapper.ApsOperationMapper">
<resultMap id="ApsScheduleOperationMap"
type="com.klp.aps.domain.entity.ApsScheduleOperationEntity">
type="com.fad.aps.domain.entity.ApsScheduleOperationEntity">
<id property="operationId" column="operation_id"/>
<result property="planId" column="plan_id"/>
<result property="detailId" column="detail_id"/>
@@ -54,7 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<update id="updateSlot"
parameterType="com.klp.aps.domain.entity.ApsScheduleOperationEntity">
parameterType="com.fad.aps.domain.entity.ApsScheduleOperationEntity">
UPDATE wms_schedule_operation
SET line_id = #{lineId},
start_time = #{startTime},
@@ -65,7 +65,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update>
<insert id="insertChangeLog"
parameterType="com.klp.aps.domain.entity.ApsScheduleChangeLogEntity"
parameterType="com.fad.aps.domain.entity.ApsScheduleChangeLogEntity"
useGeneratedKeys="true"
keyProperty="logId"
keyColumn="log_id">

View File

@@ -2,11 +2,11 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsPlanMapper">
<mapper namespace="com.fad.aps.mapper.ApsPlanMapper">
<select id="selectOrderDetailsByOrderId"
parameterType="long"
resultType="com.klp.aps.domain.row.ApsOrderDetailRow">
resultType="com.fad.aps.domain.row.ApsOrderDetailRow">
SELECT
detail_id AS detailId,
product_id AS productId,
@@ -17,7 +17,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY detail_id ASC
</select>
<select id="selectCrmOrderById" resultType="com.klp.aps.domain.row.ApsCrmOrderRow">
<select id="selectCrmOrderById" resultType="com.fad.aps.domain.row.ApsCrmOrderRow">
SELECT
order_id AS orderId,
order_code AS orderCode,
@@ -31,7 +31,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
LIMIT 1
</select>
<select id="selectCrmOrderItemsByOrderId" resultType="com.klp.aps.domain.row.ApsCrmOrderItemRow">
<select id="selectCrmOrderItemsByOrderId" resultType="com.fad.aps.domain.row.ApsCrmOrderItemRow">
SELECT
item_id AS itemId,
order_id AS orderId,
@@ -45,7 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY item_id ASC
</select>
<select id="selectWmsOrderByCode" resultType="com.klp.aps.domain.row.ApsWmsOrderRow">
<select id="selectWmsOrderByCode" resultType="com.fad.aps.domain.row.ApsWmsOrderRow">
SELECT order_id AS orderId, order_code AS orderCode
FROM wms_order
WHERE order_code = #{orderCode}
@@ -53,7 +53,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
LIMIT 1
</select>
<insert id="insertWmsOrder" parameterType="com.klp.aps.domain.dto.ApsWmsOrderCreateReq">
<insert id="insertWmsOrder" parameterType="com.fad.aps.domain.dto.ApsWmsOrderCreateReq">
INSERT INTO wms_order
(
order_code,
@@ -84,7 +84,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
)
</insert>
<insert id="insertWmsOrderDetail" parameterType="com.klp.aps.domain.dto.ApsWmsOrderDetailCreateReq">
<insert id="insertWmsOrderDetail" parameterType="com.fad.aps.domain.dto.ApsWmsOrderDetailCreateReq">
INSERT INTO wms_order_detail
(
order_id,
@@ -124,7 +124,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<insert id="insertSchedulePlan"
parameterType="com.klp.aps.domain.entity.ApsSchedulePlanEntity"
parameterType="com.fad.aps.domain.entity.ApsSchedulePlanEntity"
useGeneratedKeys="true"
keyProperty="planId"
keyColumn="plan_id">
@@ -159,7 +159,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</insert>
<insert id="insertSchedulePlanDetail"
parameterType="com.klp.aps.domain.entity.ApsSchedulePlanDetailEntity"
parameterType="com.fad.aps.domain.entity.ApsSchedulePlanDetailEntity"
useGeneratedKeys="true"
keyProperty="detailId"
keyColumn="detail_id">

View File

@@ -2,11 +2,11 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsScheduleSheetMapper">
<mapper namespace="com.fad.aps.mapper.ApsScheduleSheetMapper">
<select id="selectSheetRows"
parameterType="com.klp.aps.domain.dto.ApsScheduleSheetQueryReq"
resultType="com.klp.aps.domain.vo.ApsScheduleSheetRowVo">
parameterType="com.fad.aps.domain.dto.ApsScheduleSheetQueryReq"
resultType="com.fad.aps.domain.vo.ApsScheduleSheetRowVo">
SELECT
o.operation_id AS operationId,
o.line_id AS lineId,

View File

@@ -2,9 +2,9 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.aps.mapper.ApsShiftTemplateMapper">
<mapper namespace="com.fad.aps.mapper.ApsShiftTemplateMapper">
<select id="selectByShiftCode" resultType="com.klp.aps.domain.entity.ApsShiftTemplateEntity">
<select id="selectByShiftCode" resultType="com.fad.aps.domain.entity.ApsShiftTemplateEntity">
SELECT *
FROM wms_shift_template
WHERE shift_code = #{shiftCode}