添加OEE内容
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package com.klp.da.controller;
|
||||
|
||||
import com.klp.common.annotation.Log;
|
||||
import com.klp.common.core.controller.BaseController;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
import com.klp.common.core.domain.R;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.enums.BusinessType;
|
||||
import com.klp.da.domain.bo.OeeQueryBo;
|
||||
import com.klp.da.domain.vo.OeeLineSummaryVo;
|
||||
import com.klp.da.domain.vo.OeeLossCategorySummaryVo;
|
||||
import com.klp.da.domain.vo.OeeLossReasonVo;
|
||||
import com.klp.da.domain.vo.OeeEventVo;
|
||||
import com.klp.da.service.IOeeReportService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* OEE 报表聚合 Controller(方式 A:后端统一聚合多服务)
|
||||
*
|
||||
* 当前仅提供接口“架子”,具体聚合逻辑在 {@link IOeeReportService} 中实现。
|
||||
*
|
||||
* 路由前缀与 docs/oee-report-design.md 设计文档保持一致:
|
||||
* - /api/ems/oee/line/summary
|
||||
* - /api/ems/oee/line/loss7
|
||||
* - /api/ems/oee/line/events
|
||||
* - /api/ems/oee/line/exportWord
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/oee/line")
|
||||
public class OeeReportController extends BaseController {
|
||||
|
||||
private final IOeeReportService oeeReportService;
|
||||
|
||||
/**
|
||||
* KPI + 趋势汇总
|
||||
*/
|
||||
@Log(title = "OEE 报表-汇总", businessType = BusinessType.OTHER)
|
||||
@GetMapping("/summary")
|
||||
public R<List<OeeLineSummaryVo>> summary(OeeQueryBo queryBo) {
|
||||
return R.ok(oeeReportService.summary(queryBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 7 大损失汇总
|
||||
*/
|
||||
@Log(title = "OEE 报表-7大损失", businessType = BusinessType.OTHER)
|
||||
@GetMapping("/loss7")
|
||||
public R<Map<String, Object>> lossSummary(OeeQueryBo queryBo) {
|
||||
Map<String, Object> result = oeeReportService.lossSummary(queryBo);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停机/损失事件明细
|
||||
*/
|
||||
@Log(title = "OEE 报表-事件明细", businessType = BusinessType.OTHER)
|
||||
@GetMapping("/events")
|
||||
public TableDataInfo<OeeEventVo> events(OeeQueryBo queryBo, PageQuery pageQuery) {
|
||||
return oeeReportService.events(queryBo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 Word 报表(整体版式由后端模板控制)
|
||||
*/
|
||||
@Log(title = "OEE 报表-导出Word", businessType = BusinessType.EXPORT)
|
||||
@GetMapping("/exportWord")
|
||||
public void exportWord(OeeQueryBo queryBo, HttpServletResponse response) {
|
||||
oeeReportService.exportWord(queryBo, response);
|
||||
}
|
||||
}
|
||||
|
||||
68
klp-da/src/main/java/com/klp/da/domain/bo/OeeQueryBo.java
Normal file
68
klp-da/src/main/java/com/klp/da/domain/bo/OeeQueryBo.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package com.klp.da.domain.bo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OEE 查询条件 Bo
|
||||
*
|
||||
* 主要用于两条产线(酸轧线、镀锌一线)的聚合查询。
|
||||
* 具体字段与 docs/oee-report-design.md 中“接口设计”保持一致。
|
||||
*/
|
||||
@Data
|
||||
public class OeeQueryBo {
|
||||
|
||||
/**
|
||||
* 开始日期(yyyy-MM-dd)
|
||||
*/
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate startDate;
|
||||
|
||||
/**
|
||||
* 结束日期(yyyy-MM-dd)
|
||||
*/
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate endDate;
|
||||
|
||||
/**
|
||||
* 产线 ID 列表,例如 ["SY", "DX1"]
|
||||
*/
|
||||
private List<String> lineIds;
|
||||
|
||||
/**
|
||||
* 事件查询:开始时间
|
||||
*/
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 事件查询:结束时间
|
||||
*/
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 7 大损失类别编码(可选)
|
||||
*/
|
||||
private String lossCategoryCode;
|
||||
|
||||
/**
|
||||
* 关键字(事件明细筛选,匹配原因/备注等)
|
||||
*/
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* TopN 设置(7 大损失 TOP 原因等,可选)
|
||||
*/
|
||||
private Integer topN;
|
||||
}
|
||||
|
||||
39
klp-da/src/main/java/com/klp/da/domain/vo/OeeEventVo.java
Normal file
39
klp-da/src/main/java/com/klp/da/domain/vo/OeeEventVo.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package com.klp.da.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* OEE 事件(停机/损失)明细 VO
|
||||
*/
|
||||
@Data
|
||||
public class OeeEventVo {
|
||||
|
||||
private String lineId;
|
||||
|
||||
private String lineName;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime eventStartTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime eventEndTime;
|
||||
|
||||
/**
|
||||
* 时长(分钟)
|
||||
*/
|
||||
private Integer durationMin;
|
||||
|
||||
private String rawReasonCode;
|
||||
|
||||
private String rawReasonName;
|
||||
|
||||
private String lossCategoryCode;
|
||||
|
||||
private String lossCategoryName;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.klp.da.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 产线 OEE 汇总 + 日趋势 VO
|
||||
*
|
||||
* 对应设计文档 7.1 返回结构中的一条 line 记录。
|
||||
*/
|
||||
@Data
|
||||
public class OeeLineSummaryVo {
|
||||
|
||||
/**
|
||||
* 产线 ID
|
||||
*/
|
||||
private String lineId;
|
||||
|
||||
/**
|
||||
* 产线名称
|
||||
*/
|
||||
private String lineName;
|
||||
|
||||
/**
|
||||
* 区间汇总
|
||||
*/
|
||||
private Summary total;
|
||||
|
||||
/**
|
||||
* 日粒度数据(用于趋势图)
|
||||
*/
|
||||
private List<Daily> daily;
|
||||
|
||||
@Data
|
||||
public static class Summary {
|
||||
private Integer loadingTimeMin;
|
||||
private Integer downtimeMin;
|
||||
private Integer runTimeMin;
|
||||
private BigDecimal totalOutput;
|
||||
private BigDecimal goodOutput;
|
||||
private BigDecimal defectOutput;
|
||||
|
||||
private BigDecimal availability;
|
||||
private BigDecimal performance;
|
||||
private BigDecimal quality;
|
||||
private BigDecimal oee;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Daily {
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate statDate;
|
||||
|
||||
private Integer loadingTimeMin;
|
||||
private Integer downtimeMin;
|
||||
private Integer runTimeMin;
|
||||
private BigDecimal totalOutput;
|
||||
private BigDecimal goodOutput;
|
||||
private BigDecimal defectOutput;
|
||||
|
||||
private BigDecimal availability;
|
||||
private BigDecimal performance;
|
||||
private BigDecimal quality;
|
||||
private BigDecimal oee;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.klp.da.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 7 大损失分类汇总 VO
|
||||
*/
|
||||
@Data
|
||||
public class OeeLossCategorySummaryVo {
|
||||
|
||||
/**
|
||||
* 损失类别编码(1~7 或枚举)
|
||||
*/
|
||||
private String lossCategoryCode;
|
||||
|
||||
/**
|
||||
* 损失类别名称
|
||||
*/
|
||||
private String lossCategoryName;
|
||||
|
||||
/**
|
||||
* 损失时间(分钟)
|
||||
*/
|
||||
private Integer lossTimeMin;
|
||||
|
||||
/**
|
||||
* 损失占比(0~1 或 0~100,随整体口径配置)
|
||||
*/
|
||||
private BigDecimal lossTimeRate;
|
||||
|
||||
/**
|
||||
* 事件次数(可选)
|
||||
*/
|
||||
private Integer count;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.klp.da.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 损失原因 TopN VO
|
||||
*/
|
||||
@Data
|
||||
public class OeeLossReasonVo {
|
||||
|
||||
private String lineId;
|
||||
|
||||
/**
|
||||
* 原因编码
|
||||
*/
|
||||
private String reasonCode;
|
||||
|
||||
/**
|
||||
* 原因名称
|
||||
*/
|
||||
private String reasonName;
|
||||
|
||||
/**
|
||||
* 所属损失类别编码
|
||||
*/
|
||||
private String lossCategoryCode;
|
||||
|
||||
/**
|
||||
* 损失时间(分钟)
|
||||
*/
|
||||
private Integer lossTimeMin;
|
||||
|
||||
/**
|
||||
* 时间占比
|
||||
*/
|
||||
private BigDecimal lossTimeRate;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.klp.da.service;
|
||||
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.da.domain.bo.OeeQueryBo;
|
||||
import com.klp.da.domain.vo.OeeEventVo;
|
||||
import com.klp.da.domain.vo.OeeLineSummaryVo;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* OEE 报表聚合 Service 接口(方式 A:后端统一聚合多服务)
|
||||
*
|
||||
* 实现类负责:
|
||||
* - 调用酸轧线、镀锌一线等外部服务
|
||||
* - 做数据汇总、口径统一和格式转换
|
||||
*/
|
||||
public interface IOeeReportService {
|
||||
|
||||
/**
|
||||
* KPI + 趋势汇总
|
||||
*/
|
||||
List<OeeLineSummaryVo> summary(OeeQueryBo queryBo);
|
||||
|
||||
/**
|
||||
* 7 大损失汇总
|
||||
*
|
||||
* 返回 Map 以便后续扩展:
|
||||
* - byLine: List<LineLossSummary>
|
||||
* - topReasons: List<OeeLossReasonVo>
|
||||
* 等
|
||||
*/
|
||||
Map<String, Object> lossSummary(OeeQueryBo queryBo);
|
||||
|
||||
/**
|
||||
* 事件明细
|
||||
*/
|
||||
TableDataInfo<OeeEventVo> events(OeeQueryBo queryBo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 导出 Word 报表
|
||||
*/
|
||||
void exportWord(OeeQueryBo queryBo, HttpServletResponse response);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,684 @@
|
||||
package com.klp.da.service.impl;
|
||||
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.da.domain.bo.OeeQueryBo;
|
||||
import com.klp.da.domain.vo.OeeEventVo;
|
||||
import com.klp.da.domain.vo.OeeLineSummaryVo;
|
||||
import com.klp.da.service.IOeeReportService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* OEE 报表聚合 Service 实现(方式 A)
|
||||
*
|
||||
* 仅搭建“架子”,并预先约定两条线的数据来源:
|
||||
* - {@link #acidLineBaseUrl}:酸轧线(TCM1)服务基础地址,本地 klp-pocket 模块,例如 http://localhost:8080
|
||||
* - {@link #galvanizeLineBaseUrl}:镀锌一线服务基础地址,Fizz 平台,例如 http://140.143.206.120:18081
|
||||
*
|
||||
* 配置示例(application-*.yml):
|
||||
* da:
|
||||
* oee:
|
||||
* acid-line-base-url: http://localhost:8080
|
||||
* galvanize-line-base-url: http://140.143.206.120:18081
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class OeeReportServiceImpl implements IOeeReportService {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Value("${da.oee.acid-line-base-url:}")
|
||||
private String acidLineBaseUrl;
|
||||
|
||||
@Value("${da.oee.galvanize-line-base-url:}")
|
||||
private String galvanizeLineBaseUrl;
|
||||
|
||||
@Override
|
||||
public List<OeeLineSummaryVo> summary(OeeQueryBo queryBo) {
|
||||
log.info("OEE summary query: {}", queryBo);
|
||||
LocalDate start = queryBo.getStartDate();
|
||||
LocalDate end = queryBo.getEndDate();
|
||||
if (start == null || end == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> lineIds = (queryBo.getLineIds() == null || queryBo.getLineIds().isEmpty())
|
||||
? java.util.Arrays.asList("SY", "DX1")
|
||||
: queryBo.getLineIds();
|
||||
|
||||
List<OeeLineSummaryVo> result = new ArrayList<>();
|
||||
for (String lineId : lineIds) {
|
||||
if (isAcidLine(lineId)) {
|
||||
result.add(buildAcidLineSummary(lineId, "酸轧线", start, end));
|
||||
} else if (isGalvanizeLine(lineId)) {
|
||||
result.add(buildGalvanizeLineSummary(lineId, "镀锌一线", start, end));
|
||||
} else {
|
||||
// 未识别的产线:跳过(后续可扩展配置化)
|
||||
log.warn("Unknown lineId: {}", lineId);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> lossSummary(OeeQueryBo queryBo) {
|
||||
log.info("OEE loss summary query: {}", queryBo);
|
||||
LocalDate start = queryBo.getStartDate();
|
||||
LocalDate end = queryBo.getEndDate();
|
||||
if (start == null || end == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
List<String> lineIds = (queryBo.getLineIds() == null || queryBo.getLineIds().isEmpty())
|
||||
? java.util.Arrays.asList("SY", "DX1")
|
||||
: queryBo.getLineIds();
|
||||
|
||||
List<Map<String, Object>> byLine = new ArrayList<>();
|
||||
for (String lineId : lineIds) {
|
||||
List<OeeEventVo> events = fetchEvents(lineId, start, end);
|
||||
Map<String, Integer> byStopType = events.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
e -> e.getLossCategoryCode() == null ? "UNKNOWN" : e.getLossCategoryCode(),
|
||||
Collectors.summingInt(e -> e.getDurationMin() == null ? 0 : e.getDurationMin())
|
||||
));
|
||||
List<Map<String, Object>> losses = byStopType.entrySet().stream()
|
||||
.map(e -> {
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("lossCategoryCode", e.getKey());
|
||||
m.put("lossCategoryName", e.getKey());
|
||||
m.put("lossTimeMin", e.getValue());
|
||||
return m;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, Object> lineBlock = new HashMap<>();
|
||||
lineBlock.put("lineId", lineId);
|
||||
lineBlock.put("lineName", isAcidLine(lineId) ? "酸轧线" : (isGalvanizeLine(lineId) ? "镀锌一线" : lineId));
|
||||
lineBlock.put("losses", losses);
|
||||
byLine.add(lineBlock);
|
||||
}
|
||||
|
||||
Map<String, Object> resp = new HashMap<>();
|
||||
resp.put("byLine", byLine);
|
||||
// topReasons:当前两套数据源只有 remark/stopType,先不做更细的 Pareto(后续可按 remark 做 TopN)
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<OeeEventVo> events(OeeQueryBo queryBo, PageQuery pageQuery) {
|
||||
log.info("OEE events query: {}, pageQuery: {}", queryBo, pageQuery);
|
||||
LocalDate start = queryBo.getStartDate();
|
||||
LocalDate end = queryBo.getEndDate();
|
||||
if (start == null || end == null) {
|
||||
TableDataInfo<OeeEventVo> empty = new TableDataInfo<>();
|
||||
empty.setRows(Collections.emptyList());
|
||||
empty.setTotal(0);
|
||||
return empty;
|
||||
}
|
||||
|
||||
List<String> lineIds = (queryBo.getLineIds() == null || queryBo.getLineIds().isEmpty())
|
||||
? java.util.Arrays.asList("SY", "DX1")
|
||||
: queryBo.getLineIds();
|
||||
|
||||
List<OeeEventVo> all = new ArrayList<>();
|
||||
for (String lineId : lineIds) {
|
||||
all.addAll(fetchEvents(lineId, start, end));
|
||||
}
|
||||
|
||||
// keyword / lossCategoryCode 简单过滤
|
||||
if (queryBo.getLossCategoryCode() != null && !queryBo.getLossCategoryCode().trim().isEmpty()) {
|
||||
all = all.stream()
|
||||
.filter(e -> Objects.equals(queryBo.getLossCategoryCode(), e.getLossCategoryCode()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
if (queryBo.getKeyword() != null && !queryBo.getKeyword().trim().isEmpty()) {
|
||||
String kw = queryBo.getKeyword().trim();
|
||||
all = all.stream()
|
||||
.filter(e -> (e.getRawReasonName() != null && e.getRawReasonName().contains(kw))
|
||||
|| (e.getRemark() != null && e.getRemark().contains(kw)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 默认按开始时间倒序
|
||||
all.sort((a, b) -> {
|
||||
LocalDateTime at = a.getEventStartTime();
|
||||
LocalDateTime bt = b.getEventStartTime();
|
||||
if (at == null && bt == null) return 0;
|
||||
if (at == null) return 1;
|
||||
if (bt == null) return -1;
|
||||
return bt.compareTo(at);
|
||||
});
|
||||
|
||||
int pageNum = pageQuery.getPageNum() == null ? 1 : pageQuery.getPageNum();
|
||||
int pageSize = pageQuery.getPageSize() == null ? 10 : pageQuery.getPageSize();
|
||||
int from = Math.max(0, (pageNum - 1) * pageSize);
|
||||
int to = Math.min(all.size(), from + pageSize);
|
||||
List<OeeEventVo> pageRows = from >= all.size() ? Collections.emptyList() : all.subList(from, to);
|
||||
|
||||
TableDataInfo<OeeEventVo> table = new TableDataInfo<>();
|
||||
table.setRows(pageRows);
|
||||
table.setTotal(all.size());
|
||||
return table;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportWord(OeeQueryBo queryBo, HttpServletResponse response) {
|
||||
// TODO: 1) 聚合数据;2) 调用 Word 导出模板生成 .docx;3) 写入 response 输出流
|
||||
log.info("OEE exportWord query: {}", queryBo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建镀锌一线 Fizz 报表汇总接口 URL:/api/report/summary
|
||||
* 对应 ReportController.getReportSummary(groupNo, shiftNo, startTime, endTime)
|
||||
*/
|
||||
private String buildGalvanizeSummaryUrl(OeeQueryBo queryBo) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(galvanizeLineBaseUrl).append("/api/report/summary");
|
||||
boolean hasParam = false;
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
if (queryBo.getStartTime() != null) {
|
||||
sb.append(hasParam ? "&" : "?")
|
||||
.append("startTime=").append(queryBo.getStartTime().format(formatter));
|
||||
hasParam = true;
|
||||
}
|
||||
if (queryBo.getEndTime() != null) {
|
||||
sb.append(hasParam ? "&" : "?")
|
||||
.append("endTime=").append(queryBo.getEndTime().format(formatter));
|
||||
}
|
||||
// groupNo / shiftNo 暂不使用,后续需要可从 queryBo 中扩展字段再拼接
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private boolean isAcidLine(String lineId) {
|
||||
if (lineId == null) return false;
|
||||
String v = lineId.trim().toUpperCase();
|
||||
return v.equals("SY") || v.equals("TCM1") || v.contains("酸") || v.contains("ACID");
|
||||
}
|
||||
|
||||
private boolean isGalvanizeLine(String lineId) {
|
||||
if (lineId == null) return false;
|
||||
String v = lineId.trim().toUpperCase();
|
||||
return v.equals("DX1") || v.equals("G1") || v.contains("镀") || v.contains("GALV");
|
||||
}
|
||||
|
||||
private OeeLineSummaryVo buildAcidLineSummary(String lineId, String lineName, LocalDate start, LocalDate end) {
|
||||
OeeLineSummaryVo vo = new OeeLineSummaryVo();
|
||||
vo.setLineId(lineId);
|
||||
vo.setLineName(lineName);
|
||||
|
||||
List<OeeLineSummaryVo.Daily> daily = new ArrayList<>();
|
||||
BigDecimal totalOutput = BigDecimal.ZERO;
|
||||
BigDecimal goodOutput = BigDecimal.ZERO;
|
||||
BigDecimal defectOutput = BigDecimal.ZERO;
|
||||
int downtimeSum = 0;
|
||||
int loadingSum = 0;
|
||||
int runSum = 0;
|
||||
|
||||
for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) {
|
||||
PocketProductionStats stats = fetchPocketProductionStats(d, d);
|
||||
BigDecimal qualityRate = fetchCoilQualityRateByDay(d, lineId);
|
||||
List<OeeEventVo> events = fetchPocketStoppageEvents(d, d);
|
||||
int downtime = events.stream().mapToInt(e -> e.getDurationMin() == null ? 0 : e.getDurationMin()).sum();
|
||||
int loading = (int) ChronoUnit.MINUTES.between(d.atStartOfDay(), d.plusDays(1).atStartOfDay());
|
||||
int run = Math.max(0, loading - downtime);
|
||||
|
||||
OeeLineSummaryVo.Daily day = new OeeLineSummaryVo.Daily();
|
||||
day.setStatDate(d);
|
||||
day.setLoadingTimeMin(loading);
|
||||
day.setDowntimeMin(downtime);
|
||||
day.setRunTimeMin(run);
|
||||
day.setTotalOutput(stats.totalExitWeight);
|
||||
BigDecimal good = nvl(stats.totalEntryWeight).multiply(qualityRate);
|
||||
BigDecimal defect = nvl(stats.totalEntryWeight).subtract(good).max(BigDecimal.ZERO);
|
||||
day.setGoodOutput(good);
|
||||
day.setDefectOutput(defect);
|
||||
|
||||
day.setAvailability(calcRate(run, loading));
|
||||
day.setQuality(qualityRate);
|
||||
day.setPerformance(BigDecimal.ONE);
|
||||
day.setOee(day.getAvailability().multiply(day.getQuality()));
|
||||
|
||||
daily.add(day);
|
||||
|
||||
totalOutput = totalOutput.add(nvl(stats.totalEntryWeight));
|
||||
goodOutput = goodOutput.add(good);
|
||||
defectOutput = defectOutput.add(defect);
|
||||
downtimeSum += downtime;
|
||||
loadingSum += loading;
|
||||
runSum += run;
|
||||
}
|
||||
|
||||
OeeLineSummaryVo.Summary total = new OeeLineSummaryVo.Summary();
|
||||
total.setLoadingTimeMin(loadingSum);
|
||||
total.setDowntimeMin(downtimeSum);
|
||||
total.setRunTimeMin(runSum);
|
||||
total.setTotalOutput(totalOutput);
|
||||
total.setGoodOutput(goodOutput);
|
||||
total.setDefectOutput(defectOutput);
|
||||
total.setAvailability(calcRate(runSum, loadingSum));
|
||||
BigDecimal qualityRate = calcRate(goodOutput, totalOutput);
|
||||
total.setQuality(qualityRate);
|
||||
total.setPerformance(BigDecimal.ONE);
|
||||
total.setOee(total.getAvailability().multiply(total.getQuality()));
|
||||
|
||||
vo.setDaily(daily);
|
||||
vo.setTotal(total);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private OeeLineSummaryVo buildGalvanizeLineSummary(String lineId, String lineName, LocalDate start, LocalDate end) {
|
||||
OeeLineSummaryVo vo = new OeeLineSummaryVo();
|
||||
vo.setLineId(lineId);
|
||||
vo.setLineName(lineName);
|
||||
|
||||
List<OeeLineSummaryVo.Daily> daily = new ArrayList<>();
|
||||
BigDecimal totalOutput = BigDecimal.ZERO;
|
||||
BigDecimal goodOutput = BigDecimal.ZERO;
|
||||
BigDecimal defectOutput = BigDecimal.ZERO;
|
||||
int downtimeSum = 0;
|
||||
int loadingSum = 0;
|
||||
int runSum = 0;
|
||||
|
||||
for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) {
|
||||
FizzReportSummary stats = fetchFizzReportSummary(d.atStartOfDay(), d.plusDays(1).atStartOfDay().minusSeconds(1));
|
||||
BigDecimal qualityRate = fetchCoilQualityRateByDay(d, lineId);
|
||||
List<OeeEventVo> events = fetchFizzStoppageEvents(d, d);
|
||||
int downtime = events.stream().mapToInt(e -> e.getDurationMin() == null ? 0 : e.getDurationMin()).sum();
|
||||
int loading = (int) ChronoUnit.MINUTES.between(d.atStartOfDay(), d.plusDays(1).atStartOfDay());
|
||||
int run = Math.max(0, loading - downtime);
|
||||
|
||||
OeeLineSummaryVo.Daily day = new OeeLineSummaryVo.Daily();
|
||||
day.setStatDate(d);
|
||||
day.setLoadingTimeMin(loading);
|
||||
day.setDowntimeMin(downtime);
|
||||
day.setRunTimeMin(run);
|
||||
day.setTotalOutput(stats.totalActualWeight);
|
||||
BigDecimal good = nvl(stats.totalEntryWeight).multiply(qualityRate);
|
||||
BigDecimal defect = nvl(stats.totalEntryWeight).subtract(good).max(BigDecimal.ZERO);
|
||||
day.setGoodOutput(good);
|
||||
day.setDefectOutput(defect);
|
||||
|
||||
day.setAvailability(calcRate(run, loading));
|
||||
day.setQuality(qualityRate);
|
||||
day.setPerformance(BigDecimal.ONE);
|
||||
day.setOee(day.getAvailability().multiply(day.getQuality()));
|
||||
|
||||
daily.add(day);
|
||||
|
||||
totalOutput = totalOutput.add(nvl(stats.totalEntryWeight));
|
||||
goodOutput = goodOutput.add(good);
|
||||
defectOutput = defectOutput.add(defect);
|
||||
downtimeSum += downtime;
|
||||
loadingSum += loading;
|
||||
runSum += run;
|
||||
}
|
||||
|
||||
OeeLineSummaryVo.Summary total = new OeeLineSummaryVo.Summary();
|
||||
total.setLoadingTimeMin(loadingSum);
|
||||
total.setDowntimeMin(downtimeSum);
|
||||
total.setRunTimeMin(runSum);
|
||||
total.setTotalOutput(totalOutput);
|
||||
total.setGoodOutput(goodOutput);
|
||||
total.setDefectOutput(defectOutput);
|
||||
total.setAvailability(calcRate(runSum, loadingSum));
|
||||
BigDecimal qualityRate = calcRate(goodOutput, totalOutput);
|
||||
total.setQuality(qualityRate);
|
||||
total.setPerformance(BigDecimal.ONE);
|
||||
total.setOee(total.getAvailability().multiply(total.getQuality()));
|
||||
|
||||
vo.setDaily(daily);
|
||||
vo.setTotal(total);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private List<OeeEventVo> fetchEvents(String lineId, LocalDate start, LocalDate end) {
|
||||
if (isAcidLine(lineId)) {
|
||||
return fetchPocketStoppageEvents(start, end);
|
||||
}
|
||||
if (isGalvanizeLine(lineId)) {
|
||||
return fetchFizzStoppageEvents(start, end);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 酸轧线(klp-pocket)生产统计汇总:GET /pocket/productionStatistics/summary
|
||||
* 响应为 com.klp.common.core.domain.R 包裹。
|
||||
*/
|
||||
private PocketProductionStats fetchPocketProductionStats(LocalDate startDate, LocalDate endDate) {
|
||||
try {
|
||||
String base = normalizeBaseUrl(acidLineBaseUrl);
|
||||
String url = base + "/pocket/productionStatistics/summary"
|
||||
+ "?startDate=" + startDate
|
||||
+ "&endDate=" + endDate;
|
||||
String json = restTemplate.getForObject(url, String.class);
|
||||
JsonNode root = objectMapper.readTree(json);
|
||||
JsonNode data = root.get("data");
|
||||
PocketProductionStats s = new PocketProductionStats();
|
||||
s.totalExitWeight = toBigDecimal(data, "totalExitWeight");
|
||||
s.totalEntryWeight = toBigDecimal(data, "totalEntryWeight");
|
||||
s.yieldRate = toBigDecimal(data, "yieldRate");
|
||||
return s;
|
||||
} catch (Exception e) {
|
||||
log.warn("fetchPocketProductionStats failed: {}~{}", startDate, endDate, e);
|
||||
return new PocketProductionStats();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 酸轧线(klp-pocket)停机事件:GET /pocket/proStoppage/list
|
||||
*
|
||||
* 该接口是 TableDataInfo 包裹,对应的 Bo 为 Klptcm1ProStoppageBo,
|
||||
* 其中查询字段名就是 startDate/endDate(已确认)。
|
||||
*/
|
||||
private List<OeeEventVo> fetchPocketStoppageEvents(LocalDate startDate, LocalDate endDate) {
|
||||
try {
|
||||
String base = normalizeBaseUrl(acidLineBaseUrl);
|
||||
String url = base + "/pocket/proStoppage/list"
|
||||
+ "?pageNum=1&pageSize=10000"
|
||||
+ "&startDate=" + startDate
|
||||
+ "&endDate=" + endDate;
|
||||
String json = restTemplate.getForObject(url, String.class);
|
||||
JsonNode root = objectMapper.readTree(json);
|
||||
JsonNode rows = root.get("rows");
|
||||
if (rows == null || !rows.isArray()) return Collections.emptyList();
|
||||
|
||||
List<OeeEventVo> list = new ArrayList<>();
|
||||
for (JsonNode n : rows) {
|
||||
OeeEventVo e = new OeeEventVo();
|
||||
e.setLineId("SY");
|
||||
e.setLineName("酸轧线");
|
||||
e.setEventStartTime(toLocalDateTime(n, "startDate"));
|
||||
e.setEventEndTime(toLocalDateTime(n, "endDate"));
|
||||
e.setDurationMin(toInt(n, "duration"));
|
||||
e.setRawReasonCode(toText(n, "stopType"));
|
||||
e.setRawReasonName(toText(n, "remark"));
|
||||
e.setLossCategoryCode(toText(n, "stopType"));
|
||||
e.setLossCategoryName(toText(n, "stopType"));
|
||||
e.setRemark(toText(n, "remark"));
|
||||
list.add(e);
|
||||
}
|
||||
return list;
|
||||
} catch (Exception e) {
|
||||
log.warn("fetchPocketStoppageEvents failed: {}~{}", startDate, endDate, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 镀锌一线(Fizz)生产实绩汇总:GET /api/report/summary
|
||||
* 响应为 ReportSummaryVO 结构(无 R 包裹)。
|
||||
*/
|
||||
private FizzReportSummary fetchFizzReportSummary(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
try {
|
||||
OeeQueryBo qb = new OeeQueryBo();
|
||||
qb.setStartTime(startTime);
|
||||
qb.setEndTime(endTime);
|
||||
String url = buildGalvanizeSummaryUrl(qb);
|
||||
String json = restTemplate.getForObject(url, String.class);
|
||||
JsonNode root = objectMapper.readTree(json);
|
||||
FizzReportSummary s = new FizzReportSummary();
|
||||
s.totalActualWeight = toBigDecimal(root, "totalActualWeight");
|
||||
s.totalEntryWeight = toBigDecimal(root, "totalEntryWeight");
|
||||
s.yieldRate = toBigDecimal(root, "yieldRate");
|
||||
return s;
|
||||
} catch (Exception e) {
|
||||
log.warn("fetchFizzReportSummary failed: {}~{}", startTime, endTime, e);
|
||||
return new FizzReportSummary();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用本地 WMS 钢卷物料接口,根据 quality_status + actual_warehouse_id 统计某一天某条线的良品率:
|
||||
* - 分母:当天该逻辑库区的钢卷数量(按创建时间 byCreateTimeStart/End + actualWarehouseId)
|
||||
* - 分子:quality_status=0(正常)的钢卷数量
|
||||
*
|
||||
* lineId -> 库区映射:
|
||||
* - 酸轧线:actualWarehouseId = 1988150099140866050
|
||||
* - 镀锌一线:actualWarehouseId = 1988150323162836993
|
||||
*/
|
||||
private BigDecimal fetchCoilQualityRateByDay(LocalDate day, String lineId) {
|
||||
try {
|
||||
Long actualWarehouseId = null;
|
||||
if (isAcidLine(lineId)) {
|
||||
actualWarehouseId = 1988150099140866050L;
|
||||
} else if (isGalvanizeLine(lineId)) {
|
||||
actualWarehouseId = 1988150323162836993L;
|
||||
}
|
||||
if (actualWarehouseId == null) {
|
||||
// 未知产线:退化为全厂口径
|
||||
return fetchCoilQualityRateByDay(day);
|
||||
}
|
||||
|
||||
String base = normalizeBaseUrl(acidLineBaseUrl);
|
||||
String start = day.atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
String end = day.plusDays(1).atStartOfDay().minusSeconds(1)
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
// 全部钢卷数量
|
||||
String allUrl = base + "/wms/materialCoil/list"
|
||||
+ "?pageNum=1&pageSize=1"
|
||||
+ "&byCreateTimeStart=" + start
|
||||
+ "&byCreateTimeEnd=" + end
|
||||
+ "&actualWarehouseId=" + actualWarehouseId;
|
||||
String allJson = restTemplate.getForObject(allUrl, String.class);
|
||||
JsonNode allRoot = objectMapper.readTree(allJson);
|
||||
long totalAll = allRoot.path("total").asLong(0);
|
||||
if (totalAll <= 0) {
|
||||
return BigDecimal.ONE;
|
||||
}
|
||||
|
||||
// 良品钢卷数量:quality_status = '0'
|
||||
String goodUrl = allUrl + "&qualityStatus=0";
|
||||
String goodJson = restTemplate.getForObject(goodUrl, String.class);
|
||||
JsonNode goodRoot = objectMapper.readTree(goodJson);
|
||||
long totalGood = goodRoot.path("total").asLong(0);
|
||||
|
||||
return calcRate(BigDecimal.valueOf(totalGood), BigDecimal.valueOf(totalAll));
|
||||
} catch (Exception e) {
|
||||
log.warn("fetchCoilQualityRateByDay failed for day {}: {}", day, e.getMessage());
|
||||
// 失败时默认按 100% 良品率处理,避免影响主逻辑
|
||||
return BigDecimal.ONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全厂整体良品率(仅在无法识别产线时兜底使用)。
|
||||
*/
|
||||
private BigDecimal fetchCoilQualityRateByDay(LocalDate day) {
|
||||
try {
|
||||
String base = normalizeBaseUrl(acidLineBaseUrl);
|
||||
String start = day.atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
String end = day.plusDays(1).atStartOfDay().minusSeconds(1)
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
String allUrl = base + "/wms/materialCoil/list"
|
||||
+ "?pageNum=1&pageSize=1"
|
||||
+ "&byCreateTimeStart=" + start
|
||||
+ "&byCreateTimeEnd=" + end;
|
||||
String allJson = restTemplate.getForObject(allUrl, String.class);
|
||||
JsonNode allRoot = objectMapper.readTree(allJson);
|
||||
long totalAll = allRoot.path("total").asLong(0);
|
||||
if (totalAll <= 0) {
|
||||
return BigDecimal.ONE;
|
||||
}
|
||||
|
||||
String goodUrl = allUrl + "&qualityStatus=0";
|
||||
String goodJson = restTemplate.getForObject(goodUrl, String.class);
|
||||
JsonNode goodRoot = objectMapper.readTree(goodJson);
|
||||
long totalGood = goodRoot.path("total").asLong(0);
|
||||
|
||||
return calcRate(BigDecimal.valueOf(totalGood), BigDecimal.valueOf(totalAll));
|
||||
} catch (Exception e) {
|
||||
log.warn("fetchCoilQualityRateByDay(all) failed for day {}: {}", day, e.getMessage());
|
||||
return BigDecimal.ONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 镀锌一线(Fizz)停机事件:POST /api/stoppage/list
|
||||
* 响应为 com.ruoyi.common.core.domain.R 包裹。
|
||||
*/
|
||||
private List<OeeEventVo> fetchFizzStoppageEvents(LocalDate startDate, LocalDate endDate) {
|
||||
try {
|
||||
String base = normalizeBaseUrl(galvanizeLineBaseUrl);
|
||||
String url = base + "/api/stoppage/list";
|
||||
Map<String, String> body = new HashMap<>();
|
||||
body.put("startDate", startDate.toString());
|
||||
body.put("endDate", endDate.toString());
|
||||
|
||||
String json = restTemplate.postForObject(url, body, String.class);
|
||||
JsonNode root = objectMapper.readTree(json);
|
||||
JsonNode data = root.get("data");
|
||||
if (data == null || !data.isArray()) return Collections.emptyList();
|
||||
|
||||
List<OeeEventVo> list = new ArrayList<>();
|
||||
for (JsonNode n : data) {
|
||||
OeeEventVo e = new OeeEventVo();
|
||||
e.setLineId("DX1");
|
||||
e.setLineName("镀锌一线");
|
||||
e.setEventStartTime(toLocalDateTime(n, "startDate"));
|
||||
e.setEventEndTime(toLocalDateTime(n, "endDate"));
|
||||
// duration 在 Fizz 为 BigDecimal,字段名 duration
|
||||
e.setDurationMin(toBigDecimal(n, "duration").intValue());
|
||||
e.setRawReasonCode(toText(n, "stopType"));
|
||||
e.setRawReasonName(toText(n, "remark"));
|
||||
e.setLossCategoryCode(toText(n, "stopType"));
|
||||
e.setLossCategoryName(toText(n, "stopType"));
|
||||
e.setRemark(toText(n, "remark"));
|
||||
list.add(e);
|
||||
}
|
||||
return list;
|
||||
} catch (Exception e) {
|
||||
log.warn("fetchFizzStoppageEvents failed: {}~{}", startDate, endDate, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeBaseUrl(String baseUrl) {
|
||||
if (baseUrl == null || baseUrl.trim().isEmpty()) {
|
||||
// 默认指向当前应用(同域调用)
|
||||
return "http://localhost:8080";
|
||||
}
|
||||
String s = baseUrl.trim();
|
||||
if (s.endsWith("/")) s = s.substring(0, s.length() - 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
private BigDecimal calcRate(int numerator, int denominator) {
|
||||
if (denominator <= 0) return BigDecimal.ZERO;
|
||||
return BigDecimal.valueOf(numerator)
|
||||
.divide(BigDecimal.valueOf(denominator), 6, BigDecimal.ROUND_HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal calcRate(BigDecimal numerator, BigDecimal denominator) {
|
||||
if (denominator == null || denominator.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return nvl(numerator).divide(denominator, 6, BigDecimal.ROUND_HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal nvl(BigDecimal v) {
|
||||
return v == null ? BigDecimal.ZERO : v;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将可能是 0~1 或 0~100 的比率统一规范到 0~1 区间。
|
||||
*/
|
||||
private BigDecimal normalizeRate(BigDecimal raw) {
|
||||
BigDecimal r = nvl(raw);
|
||||
if (r.compareTo(BigDecimal.ONE) > 0 && r.compareTo(BigDecimal.valueOf(100)) <= 0) {
|
||||
r = r.divide(BigDecimal.valueOf(100), 6, BigDecimal.ROUND_HALF_UP);
|
||||
}
|
||||
if (r.compareTo(BigDecimal.ZERO) < 0) {
|
||||
r = BigDecimal.ZERO;
|
||||
} else if (r.compareTo(BigDecimal.ONE) > 0) {
|
||||
r = BigDecimal.ONE;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private BigDecimal toBigDecimal(JsonNode node, String field) {
|
||||
if (node == null) return BigDecimal.ZERO;
|
||||
JsonNode v = node.get(field);
|
||||
if (v == null || v.isNull()) return BigDecimal.ZERO;
|
||||
if (v.isNumber()) return v.decimalValue();
|
||||
String s = v.asText();
|
||||
if (s == null || s.trim().isEmpty()) return BigDecimal.ZERO;
|
||||
try {
|
||||
return new BigDecimal(s);
|
||||
} catch (Exception e) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
private String toText(JsonNode node, String field) {
|
||||
if (node == null) return null;
|
||||
JsonNode v = node.get(field);
|
||||
if (v == null || v.isNull()) return null;
|
||||
String s = v.asText();
|
||||
return (s == null || s.trim().isEmpty()) ? null : s;
|
||||
}
|
||||
|
||||
private Integer toInt(JsonNode node, String field) {
|
||||
if (node == null) return 0;
|
||||
JsonNode v = node.get(field);
|
||||
if (v == null || v.isNull()) return 0;
|
||||
if (v.isNumber()) return v.asInt();
|
||||
try {
|
||||
return Integer.parseInt(v.asText());
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDateTime toLocalDateTime(JsonNode node, String field) {
|
||||
String s = toText(node, field);
|
||||
if (s == null) return null;
|
||||
try {
|
||||
// 兼容 "yyyy-MM-dd HH:mm:ss" / ISO
|
||||
if (s.contains("T")) {
|
||||
return LocalDateTime.parse(s);
|
||||
}
|
||||
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
return LocalDateTime.parse(s, f);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PocketProductionStats {
|
||||
BigDecimal totalEntryWeight = BigDecimal.ZERO;
|
||||
BigDecimal totalExitWeight = BigDecimal.ZERO;
|
||||
BigDecimal yieldRate = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private static class FizzReportSummary {
|
||||
BigDecimal totalEntryWeight = BigDecimal.ZERO;
|
||||
BigDecimal totalActualWeight = BigDecimal.ZERO;
|
||||
BigDecimal yieldRate = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user