添加OEE内容

This commit is contained in:
2026-01-27 19:07:25 +08:00
parent bc31d5cda7
commit 655023b91f
14 changed files with 1522 additions and 1 deletions

View File

@@ -7,6 +7,14 @@ klp:
# 开发环境文件存储目录
directory-path: testDirectory
--- # OEE 聚合klp-da多服务地址配置
da:
oee:
# 酸轧线klp-pocket在同一套服务内时可指向本服务端口
acid-line-base-url: http://localhost:${server.port}
# 镀锌一线Fizz
galvanize-line-base-url: http://140.143.206.120:18081
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关

View File

@@ -7,6 +7,14 @@ klp:
# 生产环境文件存储目录
directory-path: /home/ubuntu/oa/folder
--- # OEE 聚合klp-da多服务地址配置
da:
oee:
# 酸轧线klp-pocket在同一套服务内时可指向本服务端口也可改成内网域名
acid-line-base-url: http://127.0.0.1:${server.port}
# 镀锌一线Fizz
galvanize-line-base-url: http://140.143.206.120:18081
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关

View File

@@ -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);
}
}

View 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;
}

View 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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,47 @@
package com.klp.da.service;
import com.klp.common.core.domain.PageQuery;
import com.klp.common.core.page.TableDataInfo;
import com.klp.da.domain.bo.OeeQueryBo;
import com.klp.da.domain.vo.OeeEventVo;
import com.klp.da.domain.vo.OeeLineSummaryVo;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* OEE 报表聚合 Service 接口(方式 A后端统一聚合多服务
*
* 实现类负责:
* - 调用酸轧线、镀锌一线等外部服务
* - 做数据汇总、口径统一和格式转换
*/
public interface IOeeReportService {
/**
* KPI + 趋势汇总
*/
List<OeeLineSummaryVo> summary(OeeQueryBo queryBo);
/**
* 7 大损失汇总
*
* 返回 Map 以便后续扩展:
* - byLine: List<LineLossSummary>
* - topReasons: List<OeeLossReasonVo>
* 等
*/
Map<String, Object> lossSummary(OeeQueryBo queryBo);
/**
* 事件明细
*/
TableDataInfo<OeeEventVo> events(OeeQueryBo queryBo, PageQuery pageQuery);
/**
* 导出 Word 报表
*/
void exportWord(OeeQueryBo queryBo, HttpServletResponse response);
}

View File

@@ -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 导出模板生成 .docx3) 写入 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;
}
}

39
klp-ui/src/api/da/oee.js Normal file
View File

@@ -0,0 +1,39 @@
import request from '@/utils/request'
// OEE 汇总(两条产线 KPI + 日趋势)
export function fetchOeeSummary(query) {
return request({
url: '/oee/line/summary',
method: 'get',
params: query
})
}
// 7 大损失汇总
export function fetchOeeLoss7(query) {
return request({
url: '/oee/line/loss7',
method: 'get',
params: query
})
}
// 停机/损失事件明细
export function fetchOeeEvents(query) {
return request({
url: '/oee/line/events',
method: 'get',
params: query
})
}
// 导出 Word 报表
export function exportOeeWord(query) {
return request({
url: '/oee/line/exportWord',
method: 'get',
params: query,
responseType: 'blob'
})
}

View File

@@ -0,0 +1,384 @@
<template>
<div class="oee-page">
<!-- 查询条件 -->
<el-card class="oee-card query-card" shadow="never">
<el-form :inline="true" :model="queryParams" size="small">
<el-form-item label="日期范围">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="产线">
<el-select v-model="queryParams.lineIds" multiple collapse-tags style="min-width: 200px">
<el-option label="酸轧线" value="SY" />
<el-option label="镀锌一线" value="DX1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
<el-button type="success" icon="el-icon-printer" @click="handlePrint">打印报表</el-button>
<el-button type="warning" icon="el-icon-document" @click="handleExportWord">导出 Word</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 1. 指标总览报告形式表格 -->
<el-card class="oee-card kpi-card" shadow="never">
<div class="card-header">产线 OEE 指标总览</div>
<el-table :data="lineSummary" border size="small">
<el-table-column prop="lineName" label="产线" width="120" />
<el-table-column label="OEE">
<template slot-scope="scope">
{{ toPercent(scope.row.total && scope.row.total.oee) }}
</template>
</el-table-column>
<el-table-column label="时间稼动率 A">
<template slot-scope="scope">
{{ toPercent(scope.row.total && scope.row.total.availability) }}
</template>
</el-table-column>
<el-table-column label="性能稼动率 P">
<template slot-scope="scope">
{{ toPercent(scope.row.total && scope.row.total.performance) }}
</template>
</el-table-column>
<el-table-column label="良品率 Q">
<template slot-scope="scope">
{{ toPercent(scope.row.total && scope.row.total.quality) }}
</template>
</el-table-column>
<el-table-column label="负荷时间(min)" width="130">
<template slot-scope="scope">
{{ (scope.row.total && scope.row.total.loadingTimeMin) || 0 }}
</template>
</el-table-column>
<el-table-column label="停机时间(min)" width="130">
<template slot-scope="scope">
{{ (scope.row.total && scope.row.total.downtimeMin) || 0 }}
</template>
</el-table-column>
<el-table-column label="运转时间(min)" width="130">
<template slot-scope="scope">
{{ (scope.row.total && scope.row.total.runTimeMin) || 0 }}
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 2. 趋势报告中的图 1 占位 -->
<el-card class="oee-card chart-card" shadow="never">
<div class="card-header">OEE 与三大指标日趋势 1占位</div>
<div class="chart-placeholder">
<span> 1OEE / A / P / Q 日趋势曲线后续接入图表组件</span>
</div>
</el-card>
<!-- 3. 7 大损失 -->
<el-card class="oee-card loss-card" shadow="never">
<div class="card-header">7 大损失汇总</div>
<el-tabs v-model="activeLossLine">
<el-tab-pane
v-for="line in lossByLine"
:key="line.lineId"
:label="line.lineName"
:name="line.lineId"
>
<el-table :data="line.losses" border size="small">
<el-table-column prop="lossCategoryName" label="损失类别" />
<el-table-column prop="lossTimeMin" label="损失时间(min)" width="140" />
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 4. 事件明细 -->
<el-card class="oee-card event-card" shadow="never">
<div class="card-header">停机 / 损失事件明细</div>
<el-table :data="eventList" border size="small">
<el-table-column prop="lineName" label="产线" width="100" />
<el-table-column prop="eventStartTime" label="开始时间" width="160" />
<el-table-column prop="eventEndTime" label="结束时间" width="160" />
<el-table-column prop="durationMin" label="时长(min)" width="100" />
<el-table-column prop="lossCategoryName" label="损失类别" width="120" />
<el-table-column prop="rawReasonName" label="原因" />
</el-table>
<pagination
v-show="eventTotal > 0"
:total="eventTotal"
:page.sync="eventQuery.pageNum"
:limit.sync="eventQuery.pageSize"
@pagination="loadEvents"
/>
</el-card>
<!-- 5. 公式与口径说明MathType 风格 -->
<el-card class="oee-card formula-card" shadow="never">
<div class="card-header">OEE 公式与口径说明</div>
<div class="formula-content">
<div class="formula-row">
<span class="formula-label">总公式</span>
<div class="equation">
<span class="var">OEE</span>
<span>=</span>
<span class="var">A</span>
<span>×</span>
<span class="var">P</span>
<span>×</span>
<span class="var">Q</span>
</div>
</div>
<div class="formula-row">
<span class="formula-label">时间稼动率 A</span>
<div class="equation">
<span class="var">A</span>
<span>=</span>
<span class="frac">
<span class="num">负荷时间 停机时间</span>
<span class="line"></span>
<span class="den">负荷时间</span>
</span>
</div>
</div>
<div class="formula-row">
<span class="formula-label">性能稼动率 P</span>
<div class="equation">
<span class="var">P</span>
<span>=</span>
<span class="frac">
<span class="num">理论加工时间 × 加工数量</span>
<span class="line"></span>
<span class="den">实际运转时间</span>
</span>
</div>
<div class="formula-note">
当前阶段按 P = 1 处理待接入标准节拍 / 标准速度后启用上述公式
</div>
</div>
<div class="formula-row">
<span class="formula-label">良品率 Q</span>
<div class="equation">
<span class="var">Q</span>
<span>=</span>
<span class="frac">
<span class="num">良品数</span>
<span class="line"></span>
<span class="den">总产量</span>
</span>
</div>
<div class="formula-note">
本系统中良品数通过钢卷质量状态 <code>quality_status = 0</code> 统计总产量为对应库区内所有钢卷数量
</div>
</div>
</div>
</el-card>
</div>
</template>
<script>
import { fetchOeeSummary, fetchOeeLoss7, fetchOeeEvents, exportOeeWord } from '@/api/da/oee'
export default {
name: 'OeeReport',
data() {
const today = this.$moment ? this.$moment().format('YYYY-MM-DD') : ''
return {
dateRange: [today, today],
queryParams: {
startDate: today,
endDate: today,
lineIds: ['SY', 'DX1']
},
lineSummary: [],
lossByLine: [],
activeLossLine: 'SY',
eventList: [],
eventTotal: 0,
eventQuery: {
pageNum: 1,
pageSize: 10
},
loading: false
}
},
created() {
this.handleQuery()
},
methods: {
buildBaseQuery() {
const [start, end] = this.dateRange || []
return {
startDate: start,
endDate: end,
lineIds: (this.queryParams.lineIds || []).join(',')
}
},
handleQuery() {
const baseQuery = this.buildBaseQuery()
this.loading = true
Promise.all([
fetchOeeSummary(baseQuery),
fetchOeeLoss7(baseQuery)
])
.then(([summaryRes, lossRes]) => {
this.lineSummary = summaryRes.data || []
this.lossByLine = (lossRes.data && lossRes.data.byLine) || []
if (this.lossByLine.length && !this.lossByLine.find(l => l.lineId === this.activeLossLine)) {
this.activeLossLine = this.lossByLine[0].lineId
}
this.eventQuery.pageNum = 1
this.loadEvents()
})
.finally(() => {
this.loading = false
})
},
resetQuery() {
const today = this.$moment ? this.$moment().format('YYYY-MM-DD') : ''
this.dateRange = [today, today]
this.queryParams.lineIds = ['SY', 'DX1']
this.handleQuery()
},
loadEvents() {
const baseQuery = this.buildBaseQuery()
const params = Object.assign({}, baseQuery, this.eventQuery)
fetchOeeEvents(params).then(res => {
this.eventList = res.rows || []
this.eventTotal = res.total || 0
})
},
toPercent(v) {
if (v == null) return '-'
const num = Number(v)
if (isNaN(num)) return '-'
return (num * 100).toFixed(1) + '%'
},
handlePrint() {
window.print()
},
handleExportWord() {
const baseQuery = this.buildBaseQuery()
exportOeeWord(baseQuery).then(res => {
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `OEE报表_${baseQuery.startDate}_${baseQuery.endDate}.docx`
a.click()
window.URL.revokeObjectURL(url)
})
}
}
}
</script>
<style scoped>
.oee-page {
padding: 10px;
}
.oee-card {
margin-bottom: 12px;
}
.card-header {
font-weight: 600;
margin-bottom: 8px;
}
.line-kpi {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 8px 12px;
}
.line-title {
font-weight: 600;
margin-bottom: 6px;
}
.kpi-items {
display: flex;
flex-wrap: wrap;
}
.kpi-item {
min-width: 120px;
margin-right: 12px;
margin-bottom: 4px;
}
.kpi-label {
font-size: 12px;
color: #909399;
}
.kpi-value {
font-size: 14px;
font-weight: 600;
}
.kpi-value.primary {
color: #409eff;
font-size: 16px;
}
.kpi-footer {
margin-top: 4px;
font-size: 12px;
color: #909399;
display: flex;
flex-wrap: wrap;
}
.kpi-footer span {
margin-right: 12px;
}
.chart-placeholder {
height: 260px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
border: 1px dashed #e4e7ed;
border-radius: 4px;
}
.formula-content {
font-size: 13px;
line-height: 1.6;
}
.formula-row {
display: flex;
align-items: flex-start;
margin-bottom: 8px;
}
.formula-label {
min-width: 110px;
color: #606266;
}
.equation {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
}
.equation .var {
font-weight: 600;
margin: 0 2px;
}
.frac {
display: inline-flex;
flex-direction: column;
align-items: center;
margin: 0 4px;
font-family: 'Times New Roman', serif;
}
.frac .num,
.frac .den {
padding: 0 4px;
}
.frac .line {
border-top: 1px solid #303133;
width: 100%;
margin: 1px 0;
}
.formula-note {
margin-left: 110px;
font-size: 12px;
color: #909399;
}
</style>

View File

@@ -394,6 +394,20 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
qw.eq(StringUtils.isNotBlank(bo.getTemperGrade()), "mc.temper_grade", bo.getTemperGrade());
// 独占状态
qw.eq(bo.getExclusiveStatus() != null, "mc.exclusive_status", bo.getExclusiveStatus());
// 按创建时间范围筛选
if (bo.getByCreateTimeStart() != null) {
qw.ge("mc.create_time", bo.getByCreateTimeStart());
}
if (bo.getByCreateTimeEnd() != null) {
qw.le("mc.create_time", bo.getByCreateTimeEnd());
}
// 按发货时间范围筛选
if (bo.getByExportTimeStart() != null) {
qw.ge("mc.export_time", bo.getByExportTimeStart());
}
if (bo.getByExportTimeEnd() != null) {
qw.le("mc.export_time", bo.getByExportTimeEnd());
}
// 统一处理 warehouseId 与 warehouseIds
List<Long> warehouseIdList = new ArrayList<>();
if (bo.getWarehouseId() != null) {

View File

@@ -110,7 +110,7 @@ create table wms_material_coil
item_id bigint not null comment '物品ID指向原材料或产品主键',
item_type varchar(20) not null comment '物品类型raw_material/product',
material_type varchar(20) null comment '材料类型(废品,成品,原料)',
quality_status varchar(20) null comment '质量状态(0=正常1=待检2=不合格',
quality_status varchar(20) null comment '质量状态(C+,C,C-,D+,D,D-为次品ABC*为良品',
status tinyint(1) default 0 null comment '状态0=在库1=已出库)',
remark varchar(255) null comment '备注',
export_time datetime null comment '发货时间',