package org.dromara.qa.analy.service.impl;
|
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
import org.dromara.common.core.utils.MapstructUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.dromara.qa.analy.domain.FeedmatchTimeData;
|
import org.dromara.qa.analy.domain.PackerTimeData;
|
import org.dromara.qa.analy.domain.RollerTimeData;
|
import org.dromara.qa.analy.domain.vo.StoreSilkDetailVo;
|
import org.dromara.qa.analy.service.IRollerTimeDataService;
|
import org.dromara.qa.analy.service.IPackerTimeDataService;
|
import org.dromara.qa.md.domain.MdShift;
|
import org.dromara.qa.md.domain.bo.MdShiftBo;
|
import org.dromara.qa.md.service.OracleShiftReader;
|
import org.springframework.stereotype.Service;
|
import org.dromara.qa.analy.domain.bo.StoreSilkInfoBo;
|
import org.dromara.qa.analy.domain.vo.StoreSilkInfoVo;
|
import org.dromara.qa.analy.domain.StoreSilkInfo;
|
import org.dromara.qa.analy.mapper.StoreSilkInfoMapper;
|
import org.dromara.qa.analy.service.IStoreSilkInfoService;
|
import org.dromara.qa.analy.service.IFeedmatchTimeDataService;
|
import java.lang.reflect.Field;
|
import java.sql.Timestamp;
|
import java.util.*;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.time.ZoneId;
|
import java.time.format.DateTimeFormatter;
|
|
/**
|
* 储丝柜产量Service业务层处理
|
*
|
* @author zhuguifei
|
* @date 2026-03-02
|
*/
|
@DS("oracle_zs")
|
@Slf4j
|
@RequiredArgsConstructor
|
@Service
|
public class StoreSilkInfoServiceImpl implements IStoreSilkInfoService {
|
|
private final StoreSilkInfoMapper baseMapper;
|
private final IFeedmatchTimeDataService feedmatchTimeDataService;
|
private final IRollerTimeDataService rollerTimeDataService;
|
private final IPackerTimeDataService packerTimeDataService;
|
private final OracleShiftReader oracleShiftReader;
|
|
/**
|
* 查询储丝柜产量
|
*
|
* @param id 主键
|
* @return 储丝柜产量
|
*/
|
@Override
|
public StoreSilkInfoVo queryById(Long id) {
|
return baseMapper.selectVoById(id);
|
}
|
|
/**
|
* 分页查询储丝柜产量列表
|
*
|
* @param bo 查询条件
|
* @param pageQuery 分页参数
|
* @return 储丝柜产量分页列表
|
*/
|
@Override
|
public TableDataInfo<StoreSilkInfoVo> queryPageList(StoreSilkInfoBo bo, PageQuery pageQuery) {
|
LambdaQueryWrapper<StoreSilkInfo> lqw = buildQueryWrapper(bo);
|
Page<StoreSilkInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
|
queryFeedmatchData(result);
|
return TableDataInfo.build(result);
|
}
|
|
private void queryFeedmatchData(Page<StoreSilkInfoVo> page) {
|
if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
|
return;
|
}
|
// 查询数采系统班次时间(独立使用 oracle 数据源)
|
List<MdShift> mdShifts = oracleShiftReader.listAll();
|
|
|
List<StoreSilkInfoVo> storeSilkInfoList = page.getRecords();
|
|
for (int i = 0; i < storeSilkInfoList.size(); i++) {
|
//储丝柜
|
StoreSilkInfoVo storeSilkInfoVo = storeSilkInfoList.get(i);
|
//出料开始时间
|
Date distimebegin = storeSilkInfoVo.getDistimebegin();
|
//出料结束时间
|
Date distimeend = storeSilkInfoVo.getDistimeend();
|
if (distimebegin == null) {
|
continue;
|
}
|
/**
|
* 统计“出料进行中”的关键点:
|
* - 出料结束时间 distimeend 可能为空(仍在出料中)
|
* - 也可能已经有结束时间(出料已结束)
|
*
|
* 因此这里统一计算一个“本次统计的有效结束时间” effectiveDistEnd:
|
* 1) distimeend == null :说明还在出料中,本次统计截止到当前时间 now
|
* 2) distimeend != null :说明已出料结束,截止到 distimeend
|
* 3) 若 distimeend 在未来(脏数据/时钟误差),也按 now 截止,避免统计到未来
|
*
|
* 后续所有查询、班次切分、扣减都基于 effectiveDistEnd,
|
* 这样同一套逻辑同时适配“出料已结束”和“出料未结束”两种场景。
|
*/
|
Date now = new Date();
|
Date effectiveDistEnd;
|
if (distimeend == null) {
|
effectiveDistEnd = now;
|
} else {
|
effectiveDistEnd = distimeend.after(now) ? now : distimeend;
|
}
|
if (!effectiveDistEnd.after(distimebegin)) {
|
continue;
|
}
|
|
//储丝柜柜号
|
String siloid = storeSilkInfoVo.getSiloid();
|
if (StringUtils.isEmpty(siloid)) continue;
|
int lastIndex = siloid.lastIndexOf("_");
|
String containerNum = siloid.substring(lastIndex + 1);
|
if (StringUtils.isEmpty(containerNum)) continue;
|
|
/**
|
* 根据出料开始时间查询“喂丝机 -> 储丝柜 -> 机台(管道)”对应关系(feedmatch_time_data)
|
*
|
* 说明:
|
* - 这里不是直接用 distimebegin,而是取出料开始后 10 分钟的第一条记录,
|
* 目的是避开出料刚开始时的波动/映射未稳定问题,保证映射准确性。
|
* - 查询上界使用 effectiveDistEnd:如果出料还没结束,就以当前时间作为上界,仍然能取到对应关系。
|
*/
|
Timestamp targetTime = new Timestamp(distimebegin.getTime() + 10 * 60 * 1000); // 出料开始后10分钟
|
String containerStr = StringUtils.isEmpty(containerNum) ? "" : containerNum.trim();
|
//小于10的柜号补0
|
if (containerStr.length() == 1) {
|
containerStr = "0" + containerStr;
|
}
|
LambdaQueryWrapper<FeedmatchTimeData> lqw = new LambdaQueryWrapper<>();
|
String finalContainerStr = containerStr;
|
lqw.ge(FeedmatchTimeData::getTime, targetTime)
|
.le(FeedmatchTimeData::getTime, effectiveDistEnd)
|
.and(wrapper -> wrapper
|
.like(FeedmatchTimeData::getFs11, finalContainerStr)
|
.or()
|
.like(FeedmatchTimeData::getFs12, finalContainerStr)
|
.or()
|
.like(FeedmatchTimeData::getFs21, finalContainerStr)
|
.or()
|
.like(FeedmatchTimeData::getFs22, finalContainerStr)
|
.or()
|
.like(FeedmatchTimeData::getFs31, finalContainerStr)
|
.or()
|
.like(FeedmatchTimeData::getFs32, finalContainerStr)
|
.or()
|
.like(FeedmatchTimeData::getFs41, finalContainerStr)
|
.or()
|
.like(FeedmatchTimeData::getFs42, finalContainerStr)
|
)
|
.orderByAsc(FeedmatchTimeData::getTime)
|
.last("LIMIT 1");
|
|
FeedmatchTimeData feedMatch = feedmatchTimeDataService.selectOne(lqw);
|
if (feedMatch == null) {
|
// TODO 添加提示
|
continue;
|
}
|
|
// feedMatch 转map:通过反向映射快速定位“该柜对应哪个喂丝机”、“该机组对应哪个管道”
|
// fsRevMap:key=储丝柜号后两位(如 01/09),value=字段名(如 fs11/fs12...)
|
Map<String, String> fsRevMap = new HashMap<>();
|
// pipeRevMap:key=喂丝机号+管道组合(如 1x / 2x...),value=字段名(如 pipe01/pipe02...)
|
Map<String, String> pipeRevMap = new HashMap<>();
|
// pipeMap:key=字段名(pipe01/pipe02...),value=字段值(用于后续解析具体管道号)
|
Map<String, String> pipeMap = new HashMap<>();
|
Field[] fields = feedMatch.getClass().getDeclaredFields();
|
for (Field field : fields) {
|
field.setAccessible(true);
|
Object value = null;
|
try {
|
value = field.get(feedMatch);
|
} catch (IllegalAccessException e) {
|
throw new RuntimeException(e);
|
}
|
if (field.getName().startsWith("fs") && value != null) {
|
String key = value.toString().trim();
|
if (key.length() == 1) {
|
key = "0" + key;
|
} else if (key.length() > 2) {
|
key = key.substring(key.length() - 2);
|
}
|
fsRevMap.put(key, field.getName());
|
} else if (field.getName().startsWith("pipe") && value != null) {
|
pipeRevMap.put(value.toString(), field.getName());
|
pipeMap.put(field.getName(),value.toString());
|
}
|
}
|
// 根据储丝柜号获取喂丝机号
|
String containerKey = StringUtils.isEmpty(containerNum) ? "" : containerNum.trim();
|
if (containerKey.length() == 1) {
|
containerKey = "0" + containerKey;
|
} else if (containerKey.length() > 2) {
|
containerKey = containerKey.substring(containerKey.length() - 2);
|
}
|
String fsNum = fsRevMap.get(containerKey);
|
if (StringUtils.isEmpty(fsNum)) {
|
// TODO 喂丝机号空返回信息
|
continue;
|
}
|
if (pipeRevMap.isEmpty()) {
|
// TODO 管道号空返回信息
|
continue;
|
}
|
/**
|
* pipeList 存放“该喂丝机对应的机组字段名”
|
* - 例如 pipe01、pipe02 代表 1#、2#卷接/包装机组
|
* - 后续会从 pipe01/pipe02 提取机组号 equNo,并用于拼接 key 查询卷接/包装表
|
*/
|
List<String> pipeList = new ArrayList<>();
|
for (Map.Entry<String, String> entry : pipeRevMap.entrySet()) {
|
//fsNum第三位是喂丝机序号
|
if (entry.getKey().length() > 1 && entry.getKey().startsWith(fsNum.substring(2, 3))) {
|
pipeList.add(entry.getValue());
|
}
|
}
|
if (pipeList.isEmpty()) {
|
//TODO 添加提示
|
continue;
|
}
|
|
/**
|
* 根据 [distimebegin, effectiveDistEnd] 计算涉及到的班次列表
|
* - 出料已结束:effectiveDistEnd=distimeend
|
* - 出料未结束:effectiveDistEnd=now
|
*/
|
List<MdShiftBo> distShiftList = calcShiftSpans(distimebegin, effectiveDistEnd, mdShifts);
|
storeSilkInfoVo.setDistShiftList(distShiftList);
|
if (distShiftList.isEmpty()) continue;
|
//查询日期和班次内卷接机组的产量
|
ZoneId zone = ZoneId.systemDefault();
|
// 卷包产量统计
|
Double rollerOutput = 0.0;
|
Double packerOutput = 0.0;
|
// 明细列表(仅存最终正数结果)
|
List<StoreSilkDetailVo> rollerDetailList = new ArrayList<>();
|
List<StoreSilkDetailVo> packerDetailList = new ArrayList<>();
|
// 操作记录列表(存所有增减过程)
|
List<StoreSilkDetailVo> rollerRecordList = new ArrayList<>();
|
List<StoreSilkDetailVo> packerRecordList = new ArrayList<>();
|
|
for (int s = 0; s < distShiftList.size(); s++) {
|
MdShiftBo shiftBo = distShiftList.get(s);
|
if (shiftBo.getDay() == null || StringUtils.isEmpty(shiftBo.getCode())) {
|
continue;
|
}
|
String shift = shiftBo.getCode();
|
|
// 解析班次时间
|
// 1) 获取班次配置的开始/结束时间 (可能是 "HH:mm" 或 "HH:mm:ss")
|
String stimStr = shiftBo.getStim();
|
String etimStr = shiftBo.getEtim();
|
if (StringUtils.isEmpty(stimStr) || StringUtils.isEmpty(etimStr)) {
|
continue;
|
}
|
// 2) 补全秒数(如果配置仅为 HH:mm)
|
if (stimStr.length() == 5) stimStr += ":00";
|
if (etimStr.length() == 5) etimStr += ":00";
|
|
// 3) 结合日期(shiftBo.getDay() 为班次归属日)解析为 LocalDateTime
|
String dateStr = shiftBo.getDay().toInstant().atZone(zone).toLocalDate().toString();
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
LocalDateTime shiftStart = LocalDateTime.parse(dateStr + " " + stimStr, formatter);
|
LocalDateTime shiftEnd = LocalDateTime.parse(dateStr + " " + etimStr, formatter);
|
|
// 4) 处理跨天班次:如果 结束时间 <= 开始时间,说明跨天,结束时间 +1 天
|
if (!shiftEnd.isAfter(shiftStart)) {
|
shiftEnd = shiftEnd.plusDays(1);
|
}
|
|
// 转换 Date 对象用于比较(兼容现有逻辑)
|
Date stimDate = Date.from(shiftStart.atZone(zone).toInstant());
|
Date etimDate = Date.from(shiftEnd.atZone(zone).toInstant());
|
|
/**
|
* 为了同时支持“出料未结束”,这里引入一个“本班次的统计窗口”:
|
*
|
* 统计结束时刻 calcEnd = min(班次结束, effectiveDistEnd)
|
* - 出料已结束:effectiveDistEnd=distimeend,可能早于班次结束,calcEnd=distimeend
|
* - 出料未结束:effectiveDistEnd=now,calcEnd=now 或 班次结束(取较小)
|
*
|
* 统计开始时刻 calcStart = max(班次开始, distimebegin)
|
* - 若出料开始晚于班次开始,则从出料开始统计
|
* - 若出料开始早于班次开始,则从班次开始统计
|
*
|
* 最终本班次有效产量口径为:
|
* output = Qty(calcEnd) - Qty(calcStart)
|
*
|
* 说明:这个口径已经“隐含了扣尾”
|
* - 如果 calcEnd < 班次结束(例如出料已结束或统计到当前时刻),尾巴自然不会被统计进去
|
* - 因此不需要再额外写一个“扣尾 if(...)”分支,逻辑更统一、更不容易出错
|
*/
|
LocalDateTime effectiveEndLdt = LocalDateTime.ofInstant(effectiveDistEnd.toInstant(), zone);
|
LocalDateTime calcEnd = shiftEnd.isBefore(effectiveEndLdt) ? shiftEnd : effectiveEndLdt;
|
Date calcEndDate = Date.from(calcEnd.atZone(zone).toInstant());
|
if (!calcEndDate.after(stimDate)) {
|
continue;
|
}
|
Date calcStartDate = distimebegin.after(stimDate) ? distimebegin : stimDate;
|
if (!calcEndDate.after(calcStartDate)) {
|
continue;
|
}
|
String calcEndStr = calcEnd.format(formatter);
|
String tenMinBeforeCalcEnd = calcEnd.minusMinutes(10).format(formatter);
|
|
// 根据卷接机组和班次获取产量(pipeList = 多少个机组)
|
for (int j = 0; j < pipeList.size(); j++) {
|
String pipe = pipeList.get(j);
|
// 提取机组号
|
String equNo = pipe.replaceAll("\\D+", "");
|
// 管道号(用于明细展示):从 feedMatch 的 pipe01/pipe02... 字段值里取末位
|
String channel = pipeMap.get("pipe" + equNo);
|
if (channel != null && channel.length() > 0) {
|
channel = channel.substring(channel.length() - 1);
|
}else {
|
channel = "";
|
}
|
|
/**
|
* 机组号映射规则(业务约定):
|
* - pipe04 对应的机组号在系统里是空缺的,需要跳到 5 号
|
* - 因此当管道号 >= 4 时,机组号需要 +1
|
* - 同时这里补齐为 2 位数字,便于拼 key(例如 01/02/05...)
|
*/
|
try {
|
int equ = Integer.parseInt(equNo);
|
equNo = String.format("%02d", equ >= 4 ? equ + 1 : equ);
|
} catch (Exception e) {
|
e.printStackTrace();
|
// TODO 添加提示
|
continue;
|
}
|
|
|
// key 拼接规则:
|
// - 卷接机:班次 + "1" + 机组号(例如 101、105...)
|
String key = shift + "1" + equNo;
|
// - 包装机:班次 + "2" + 机组号(例如 201、205...)
|
String packerKey = shift + "2" + equNo;
|
|
// ================= 卷接机产量统计 =================
|
Double currentRollerOutput = 0.0;
|
/**
|
* 取数策略说明(卷接/包装一致):
|
* - 表内的 qty 视为累计值(同一 key 下单调递增)
|
* - 由于数据是采样上报,某个时刻不一定刚好有记录
|
* - 因此采用“目标时刻前 10 分钟内,离目标最近的一条记录”作为快照值
|
*
|
* 注意:10分钟是经验窗口,如果现场采样更稀疏,可考虑放宽窗口
|
*/
|
// 1) 查询统计结束时刻 calcEnd 的快照值 Qty(calcEnd)
|
LambdaQueryWrapper<RollerTimeData> rlqw = new LambdaQueryWrapper<>();
|
rlqw.le(RollerTimeData::getTime, calcEndStr)
|
.eq(RollerTimeData::getKey, key)
|
.ge(RollerTimeData::getTime, tenMinBeforeCalcEnd)
|
.orderByDesc(RollerTimeData::getTime)
|
.isNotNull(RollerTimeData::getQty)
|
.gt(RollerTimeData::getQty, 0)
|
.last("LIMIT 1");
|
RollerTimeData rData = rollerTimeDataService.selectOne(rlqw);
|
if (rData != null) {
|
// 先把统计结束时刻累计值加进来:current = Qty(calcEnd)
|
currentRollerOutput += rData.getQty();
|
|
// 记录过程:班次截止累计
|
StoreSilkDetailVo endRecord = new StoreSilkDetailVo();
|
endRecord.setFsNum(fsNum.substring(2, 3));
|
endRecord.setSiloNum(containerNum);
|
endRecord.setPipeNum(channel);
|
endRecord.setEquNo(equNo);
|
endRecord.setShiftCode(shift);
|
endRecord.setShiftStartTime(calcStartDate);
|
endRecord.setShiftEndTime(calcEndDate);
|
endRecord.setOutput(rData.getQty());
|
endRecord.setCalcType("班次截止累计");
|
endRecord.setHitTime(rData.getTime());
|
rollerRecordList.add(endRecord);
|
|
// 2) 扣“头”:如果统计开始时刻晚于班次开始,则减去 Qty(calcStart)
|
if (calcStartDate.after(stimDate)) {
|
LocalDateTime calcStartLdt = LocalDateTime.ofInstant(calcStartDate.toInstant(), zone);
|
String calcStartStr = calcStartLdt.format(formatter);
|
String tenMinBeforeCalcStart = calcStartLdt.minusMinutes(10).format(formatter);
|
|
LambdaQueryWrapper<RollerTimeData> beginRlqw = new LambdaQueryWrapper<>();
|
beginRlqw.le(RollerTimeData::getTime, calcStartStr)
|
.eq(RollerTimeData::getKey, key)
|
.ge(RollerTimeData::getTime, tenMinBeforeCalcStart)
|
.orderByDesc(RollerTimeData::getTime)
|
.isNotNull(RollerTimeData::getQty)
|
.gt(RollerTimeData::getQty, 0)
|
.last("LIMIT 1");
|
|
RollerTimeData rBeginData = rollerTimeDataService.selectOne(beginRlqw);
|
if (rBeginData != null) {
|
currentRollerOutput -= rBeginData.getQty();
|
|
// 记录过程:扣除头部产量
|
StoreSilkDetailVo beginRecord = new StoreSilkDetailVo();
|
beginRecord.setFsNum(fsNum.substring(2, 3));
|
beginRecord.setSiloNum(containerNum);
|
beginRecord.setPipeNum(channel);
|
beginRecord.setEquNo(equNo);
|
beginRecord.setShiftCode(shift);
|
beginRecord.setShiftStartTime(calcStartDate);
|
beginRecord.setShiftEndTime(calcEndDate);
|
beginRecord.setOutput(-rBeginData.getQty()); // 负数表示扣除
|
beginRecord.setCalcType("扣除出料前累计");
|
beginRecord.setHitTime(rBeginData.getTime());
|
rollerRecordList.add(beginRecord);
|
}
|
}
|
}
|
// 累计总产量
|
rollerOutput += currentRollerOutput;
|
// 记录卷接机明细
|
if (currentRollerOutput > 0) {
|
StoreSilkDetailVo detail = new StoreSilkDetailVo();
|
detail.setFsNum(fsNum.substring(2, 3));
|
detail.setSiloNum(containerNum);
|
detail.setPipeNum(channel);
|
detail.setEquNo(equNo);
|
detail.setShiftCode(shift);
|
detail.setShiftStartTime(calcStartDate);
|
detail.setShiftEndTime(calcEndDate);
|
detail.setOutput(currentRollerOutput);
|
rollerDetailList.add(detail);
|
}
|
|
// ================= 包装机产量统计 =================
|
Double currentPackerOutput = 0.0;
|
// 1) 查询统计结束时刻 calcEnd 的快照值 Qty(calcEnd)
|
LambdaQueryWrapper<PackerTimeData> plqw = new LambdaQueryWrapper<>();
|
plqw.le(PackerTimeData::getTime, calcEndStr)
|
.eq(PackerTimeData::getKey, packerKey)
|
.ge(PackerTimeData::getTime, tenMinBeforeCalcEnd)
|
.orderByDesc(PackerTimeData::getTime)
|
.isNotNull(PackerTimeData::getQty)
|
.gt(PackerTimeData::getQty, 0)
|
.last("LIMIT 1");
|
|
PackerTimeData pData = packerTimeDataService.selectOne(plqw);
|
if (pData != null) {
|
// 先把统计结束时刻累计值加进来:current = Qty(calcEnd)
|
currentPackerOutput += pData.getQty();
|
|
// 记录过程:班次截止累计
|
StoreSilkDetailVo endRecord = new StoreSilkDetailVo();
|
endRecord.setFsNum(fsNum.substring(2, 3));
|
endRecord.setSiloNum(containerNum);
|
endRecord.setPipeNum(channel);
|
endRecord.setEquNo(equNo);
|
endRecord.setShiftCode(shift);
|
endRecord.setShiftStartTime(calcStartDate);
|
endRecord.setShiftEndTime(calcEndDate);
|
endRecord.setOutput(pData.getQty());
|
endRecord.setCalcType("班次截止累计");
|
endRecord.setHitTime(pData.getTime());
|
packerRecordList.add(endRecord);
|
|
// 2) 扣“头”:如果统计开始时刻晚于班次开始,则减去 Qty(calcStart)
|
if (calcStartDate.after(stimDate)) {
|
LocalDateTime calcStartLdt = LocalDateTime.ofInstant(calcStartDate.toInstant(), zone);
|
String calcStartStr = calcStartLdt.format(formatter);
|
String tenMinBeforeCalcStart = calcStartLdt.minusMinutes(10).format(formatter);
|
|
LambdaQueryWrapper<PackerTimeData> beginPlqw = new LambdaQueryWrapper<>();
|
beginPlqw.le(PackerTimeData::getTime, calcStartStr)
|
.eq(PackerTimeData::getKey, packerKey)
|
.ge(PackerTimeData::getTime, tenMinBeforeCalcStart)
|
.orderByDesc(PackerTimeData::getTime)
|
.isNotNull(PackerTimeData::getQty)
|
.gt(PackerTimeData::getQty, 0)
|
.last("LIMIT 1");
|
|
PackerTimeData pBeginData = packerTimeDataService.selectOne(beginPlqw);
|
if (pBeginData != null) {
|
currentPackerOutput -= pBeginData.getQty();
|
|
// 记录过程:扣除头部产量
|
StoreSilkDetailVo beginRecord = new StoreSilkDetailVo();
|
beginRecord.setFsNum(fsNum.substring(2, 3));
|
beginRecord.setSiloNum(containerNum);
|
beginRecord.setPipeNum(channel);
|
beginRecord.setEquNo(equNo);
|
beginRecord.setShiftCode(shift);
|
beginRecord.setShiftStartTime(calcStartDate);
|
beginRecord.setShiftEndTime(calcEndDate);
|
beginRecord.setOutput(-pBeginData.getQty()); // 负数表示扣除
|
beginRecord.setCalcType("扣除出料前累计");
|
beginRecord.setHitTime(pBeginData.getTime());
|
packerRecordList.add(beginRecord);
|
}
|
}
|
}
|
// 累计总产量
|
packerOutput += currentPackerOutput;
|
// 记录包装机明细
|
if (currentPackerOutput > 0) {
|
StoreSilkDetailVo detail = new StoreSilkDetailVo();
|
detail.setFsNum(fsNum.substring(2, 3));
|
detail.setSiloNum(containerNum);
|
detail.setPipeNum(channel);
|
detail.setEquNo(equNo);
|
detail.setShiftCode(shift);
|
detail.setShiftStartTime(calcStartDate);
|
detail.setShiftEndTime(calcEndDate);
|
detail.setOutput(currentPackerOutput);
|
packerDetailList.add(detail);
|
}
|
}
|
}
|
// 将汇总结果与明细结果回写到 StoreSilkInfoVo,供前端列表/抽屉明细展示使用
|
storeSilkInfoVo.setRollerOutput(rollerOutput);
|
storeSilkInfoVo.setPackerOutput(packerOutput);
|
storeSilkInfoVo.setRollerDetailList(rollerDetailList);
|
storeSilkInfoVo.setPackerDetailList(packerDetailList);
|
storeSilkInfoVo.setRollerRecordList(rollerRecordList);
|
storeSilkInfoVo.setPackerRecordList(packerRecordList);
|
|
}
|
|
}
|
|
/**
|
* 计算出料区间 [begin, end) 涉及到的班次(仅 code=1、2 的早/中班)
|
* 返回 Map<yyyy-MM-dd, code>,表示该日期上的该班次与出料区间存在时间重叠
|
* 若同一日期早/中班均有重叠,value 使用逗号拼接,如 "1,2"
|
* <p>
|
* 简化思路:
|
* 1. 将 begin/end 转为 LocalDateTime,取出覆盖的所有自然日
|
* 2. 对每一天按班次的 stim/etim 生成班次时间窗 [shiftStart, shiftEnd)
|
* - 若 etim <= stim,视为跨天班次:shiftEnd = 当天日期 + 1 天 + etim
|
* 3. 用简单的区间重叠判断:shiftEnd > begin && shiftStart < end
|
*/
|
private List<MdShiftBo> calcShiftSpans(Date begin, Date end, List<MdShift> mdShifts) {
|
List<MdShiftBo> result = new ArrayList<>();
|
|
// 基础校验
|
if (begin == null || end == null || !end.after(begin) || mdShifts == null || mdShifts.isEmpty()) {
|
return result;
|
}
|
|
// 仅保留早班(code=1)与中班(code=2)
|
// 同时忽略 stim/etim 为空的班次
|
ZoneId zone = ZoneId.systemDefault();
|
LocalDateTime intervalStart = LocalDateTime.ofInstant(begin.toInstant(), zone);
|
LocalDateTime intervalEnd = LocalDateTime.ofInstant(end.toInstant(), zone);
|
LocalDate day = intervalStart.toLocalDate();
|
LocalDate lastDay = intervalEnd.toLocalDate();
|
|
// 解析时间格式:支持 "H:mm" 或 "H:mm:ss"
|
DateTimeFormatter tf = DateTimeFormatter.ofPattern("H:mm[:ss]");
|
Set<String> seen = new HashSet<>();
|
|
// 按天遍历覆盖范围
|
while (!day.isAfter(lastDay)) {
|
for (MdShift s : mdShifts) {
|
// 仅早/中班
|
String code = s.getCode();
|
if (!"1".equals(code) && !"2".equals(code)) {
|
continue;
|
}
|
String st = s.getStim();
|
String et = s.getEtim();
|
if (st == null || et == null) {
|
continue;
|
}
|
// 解析班次起止时间
|
LocalTime stt;
|
LocalTime ett;
|
try {
|
stt = LocalTime.parse(st.trim(), tf);
|
ett = LocalTime.parse(et.trim(), tf);
|
} catch (Exception ignore) {
|
// 时间格式异常则跳过该班次
|
continue;
|
}
|
// 生成当天该班次的时间窗
|
LocalDateTime shiftStart = LocalDateTime.of(day, stt);
|
LocalDateTime shiftEnd = ett.isAfter(stt) || ett.equals(stt)
|
? LocalDateTime.of(day, ett)
|
: LocalDateTime.of(day.plusDays(1), ett); // 跨天处理
|
|
// 判断区间是否重叠: [shiftStart, shiftEnd) 与 [intervalStart, intervalEnd)
|
if (shiftEnd.isAfter(intervalStart) && shiftStart.isBefore(intervalEnd)) {
|
Date shiftDay = Date.from(day.atStartOfDay(zone).toInstant());
|
String dedupeKey = shiftDay.getTime() + "-" + code;
|
if (seen.add(dedupeKey)) {
|
MdShiftBo bo = new MdShiftBo();
|
bo.setId(s.getId());
|
bo.setWsId(s.getWsId());
|
bo.setCode(s.getCode());
|
bo.setName(s.getName());
|
bo.setStim(s.getStim());
|
bo.setEtim(s.getEtim());
|
bo.setSeq(s.getSeq());
|
bo.setEnable(s.getEnable());
|
bo.setDel(s.getDel());
|
bo.setCreateUserName(s.getCreateUserName());
|
bo.setCreateUserTime(s.getCreateUserTime());
|
bo.setUpdateUserName(s.getUpdateUserName());
|
bo.setUpdateUserTime(s.getUpdateUserTime());
|
bo.setDay(shiftDay);
|
result.add(bo);
|
}
|
}
|
}
|
day = day.plusDays(1);
|
}
|
result.sort(Comparator
|
.comparing(MdShiftBo::getDay, Comparator.nullsLast(Date::compareTo))
|
.thenComparing(MdShiftBo::getCode, Comparator.nullsLast(String::compareTo)));
|
return result;
|
}
|
|
/**
|
* 查询符合条件的储丝柜产量列表
|
*
|
* @param bo 查询条件
|
* @return 储丝柜产量列表
|
*/
|
@Override
|
public List<StoreSilkInfoVo> queryList(StoreSilkInfoBo bo) {
|
LambdaQueryWrapper<StoreSilkInfo> lqw = buildQueryWrapper(bo);
|
return baseMapper.selectVoList(lqw);
|
}
|
|
private LambdaQueryWrapper<StoreSilkInfo> buildQueryWrapper(StoreSilkInfoBo bo) {
|
Map<String, Object> params = bo.getParams();
|
LambdaQueryWrapper<StoreSilkInfo> lqw = Wrappers.lambdaQuery();
|
lqw.like(StringUtils.isNotBlank(bo.getMaterialname()), StoreSilkInfo::getMaterialname, bo.getMaterialname());
|
lqw.like(StringUtils.isNotBlank(bo.getBatchcode()), StoreSilkInfo::getBatchcode, bo.getBatchcode());
|
if (bo.getActualstarttime() != null) {
|
ZoneId zone = ZoneId.systemDefault();
|
LocalDate day = bo.getActualstarttime().toInstant().atZone(zone).toLocalDate();
|
Date dayStart = Date.from(day.atStartOfDay(zone).toInstant());
|
Date nextDayStart = Date.from(day.plusDays(1).atStartOfDay(zone).toInstant());
|
lqw.ge(StoreSilkInfo::getActualstarttime, dayStart);
|
lqw.lt(StoreSilkInfo::getActualstarttime, nextDayStart);
|
}
|
lqw.eq(bo.getDistimebegin() != null, StoreSilkInfo::getDistimebegin, bo.getDistimebegin());
|
lqw.eq(bo.getDistimeend() != null, StoreSilkInfo::getDistimeend, bo.getDistimeend());
|
if (StringUtils.isNotBlank(bo.getSiloid())) {
|
// 支持多个柜号查询,以逗号分隔
|
String[] siloids = bo.getSiloid().split(",");
|
lqw.and(wrapper -> {
|
for (String val : siloids) {
|
val = val.trim();
|
if (StringUtils.isBlank(val)) {
|
continue;
|
}
|
String finalVal = val;
|
// 使用 OR 连接多个柜号条件
|
wrapper.or(w -> {
|
try {
|
int num = Integer.parseInt(finalVal);
|
String padded = String.format("%02d", num);
|
// 匹配 _1 或 _01 (兼容不补零和补零的情况)
|
w.likeLeft(StoreSilkInfo::getSiloid, "_" + num)
|
.or()
|
.likeLeft(StoreSilkInfo::getSiloid, "_" + padded);
|
} catch (NumberFormatException e) {
|
// 非数字则按原值匹配
|
w.eq(StoreSilkInfo::getSiloid, finalVal);
|
}
|
});
|
}
|
});
|
}
|
if (params.get("beginTime") != null && params.get("endTime") != null) {
|
lqw.apply("distimeend BETWEEN TO_DATE({0}, 'YYYY-MM-DD HH24:MI:SS') AND TO_DATE({1}, 'YYYY-MM-DD HH24:MI:SS')",
|
params.get("beginTime"),
|
params.get("endTime"));
|
}
|
return lqw;
|
}
|
|
/**
|
* 新增储丝柜产量
|
*
|
* @param bo 储丝柜产量
|
* @return 是否新增成功
|
*/
|
@Override
|
public Boolean insertByBo(StoreSilkInfoBo bo) {
|
StoreSilkInfo add = MapstructUtils.convert(bo, StoreSilkInfo.class);
|
validEntityBeforeSave(add);
|
boolean flag = baseMapper.insert(add) > 0;
|
if (flag) {
|
}
|
return flag;
|
}
|
|
/**
|
* 修改储丝柜产量
|
*
|
* @param bo 储丝柜产量
|
* @return 是否修改成功
|
*/
|
@Override
|
public Boolean updateByBo(StoreSilkInfoBo bo) {
|
StoreSilkInfo update = MapstructUtils.convert(bo, StoreSilkInfo.class);
|
validEntityBeforeSave(update);
|
return baseMapper.updateById(update) > 0;
|
}
|
|
/**
|
* 保存前的数据校验
|
*/
|
private void validEntityBeforeSave(StoreSilkInfo entity) {
|
//TODO 做一些数据校验,如唯一约束
|
}
|
|
/**
|
* 校验并批量删除储丝柜产量信息
|
*
|
* @param ids 待删除的主键集合
|
* @param isValid 是否进行有效性校验
|
* @return 是否删除成功
|
*/
|
@Override
|
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
if (isValid) {
|
//TODO 做一些业务上的校验,判断是否需要校验
|
}
|
return baseMapper.deleteByIds(ids) > 0;
|
}
|
}
|