|
|
|
@ -0,0 +1,642 @@ |
|
|
|
package com.xujie.sys.modules.rack.service.impl; |
|
|
|
|
|
|
|
import com.alibaba.fastjson2.JSON; |
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|
|
|
import com.xujie.sys.common.exception.XJException; |
|
|
|
import com.xujie.sys.modules.rack.constant.RackClosedLoopStatus; |
|
|
|
import com.xujie.sys.modules.rack.dto.RackPackagePrintRequest; |
|
|
|
import com.xujie.sys.modules.rack.dto.RackPlcIngestRequest; |
|
|
|
import com.xujie.sys.modules.rack.dto.RackProductionActionRequest; |
|
|
|
import com.xujie.sys.modules.rack.entity.*; |
|
|
|
import com.xujie.sys.modules.rack.mapper.*; |
|
|
|
import com.xujie.sys.modules.rack.service.RackClosedLoopService; |
|
|
|
import com.xujie.sys.modules.rack.support.RackIdGenerator; |
|
|
|
import org.apache.commons.lang3.StringUtils; |
|
|
|
import org.springframework.stereotype.Service; |
|
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
|
|
|
|
|
import java.text.SimpleDateFormat; |
|
|
|
import java.util.*; |
|
|
|
|
|
|
|
@Service |
|
|
|
public class RackClosedLoopAddonService { |
|
|
|
|
|
|
|
private static final int PLC_OFFLINE_MINUTES = 5; |
|
|
|
|
|
|
|
private final RackExternalDeviceMapper rackExternalDeviceMapper; |
|
|
|
private final RackStationEventMapper rackStationEventMapper; |
|
|
|
private final RackJobOrderMapper rackJobOrderMapper; |
|
|
|
private final RackJobInboundRelMapper rackJobInboundRelMapper; |
|
|
|
private final RackInboundOrderMapper rackInboundOrderMapper; |
|
|
|
private final RackBindingMapper rackBindingMapper; |
|
|
|
private final RackCarrierMapper rackCarrierMapper; |
|
|
|
private final RackPackageOrderMapper rackPackageOrderMapper; |
|
|
|
private final RackLabelTemplateMapper rackLabelTemplateMapper; |
|
|
|
private final RackLabelPrintLogMapper rackLabelPrintLogMapper; |
|
|
|
private final RackClosedLoopService rackClosedLoopService; |
|
|
|
private final RackIdGenerator rackIdGenerator; |
|
|
|
|
|
|
|
public RackClosedLoopAddonService( |
|
|
|
RackExternalDeviceMapper rackExternalDeviceMapper, |
|
|
|
RackStationEventMapper rackStationEventMapper, |
|
|
|
RackJobOrderMapper rackJobOrderMapper, |
|
|
|
RackJobInboundRelMapper rackJobInboundRelMapper, |
|
|
|
RackInboundOrderMapper rackInboundOrderMapper, |
|
|
|
RackBindingMapper rackBindingMapper, |
|
|
|
RackCarrierMapper rackCarrierMapper, |
|
|
|
RackPackageOrderMapper rackPackageOrderMapper, |
|
|
|
RackLabelTemplateMapper rackLabelTemplateMapper, |
|
|
|
RackLabelPrintLogMapper rackLabelPrintLogMapper, |
|
|
|
RackClosedLoopService rackClosedLoopService, |
|
|
|
RackIdGenerator rackIdGenerator |
|
|
|
) { |
|
|
|
this.rackExternalDeviceMapper = rackExternalDeviceMapper; |
|
|
|
this.rackStationEventMapper = rackStationEventMapper; |
|
|
|
this.rackJobOrderMapper = rackJobOrderMapper; |
|
|
|
this.rackJobInboundRelMapper = rackJobInboundRelMapper; |
|
|
|
this.rackInboundOrderMapper = rackInboundOrderMapper; |
|
|
|
this.rackBindingMapper = rackBindingMapper; |
|
|
|
this.rackCarrierMapper = rackCarrierMapper; |
|
|
|
this.rackPackageOrderMapper = rackPackageOrderMapper; |
|
|
|
this.rackLabelTemplateMapper = rackLabelTemplateMapper; |
|
|
|
this.rackLabelPrintLogMapper = rackLabelPrintLogMapper; |
|
|
|
this.rackClosedLoopService = rackClosedLoopService; |
|
|
|
this.rackIdGenerator = rackIdGenerator; |
|
|
|
} |
|
|
|
|
|
|
|
public List<RackExternalDeviceEntity> listExternalDevice(RackExternalDeviceEntity query) { |
|
|
|
LambdaQueryWrapper<RackExternalDeviceEntity> wrapper = new LambdaQueryWrapper<>(); |
|
|
|
if (query != null) { |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getDeviceCode()), RackExternalDeviceEntity::getDeviceCode, query.getDeviceCode()); |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getDeviceName()), RackExternalDeviceEntity::getDeviceName, query.getDeviceName()); |
|
|
|
wrapper.eq(StringUtils.isNotBlank(query.getDeviceType()), RackExternalDeviceEntity::getDeviceType, query.getDeviceType()); |
|
|
|
wrapper.eq(StringUtils.isNotBlank(query.getLineId()), RackExternalDeviceEntity::getLineId, query.getLineId()); |
|
|
|
wrapper.eq(StringUtils.isNotBlank(query.getStatus()), RackExternalDeviceEntity::getStatus, query.getStatus()); |
|
|
|
} |
|
|
|
wrapper.orderByDesc(RackExternalDeviceEntity::getCreateTime); |
|
|
|
return rackExternalDeviceMapper.selectList(wrapper); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void saveExternalDevice(RackExternalDeviceEntity entity) { |
|
|
|
if (entity == null || StringUtils.isBlank(entity.getDeviceCode())) { |
|
|
|
throw new XJException("设备编码不能为空"); |
|
|
|
} |
|
|
|
if (StringUtils.isBlank(entity.getStepCode())) { |
|
|
|
throw new XJException("工序编码不能为空"); |
|
|
|
} |
|
|
|
Date now = new Date(); |
|
|
|
entity.setDeviceId(rackIdGenerator.nextId()); |
|
|
|
entity.setDeviceCode(entity.getDeviceCode().trim()); |
|
|
|
entity.setDeviceType(StringUtils.defaultIfBlank(entity.getDeviceType(), "PLC")); |
|
|
|
entity.setSourceType(StringUtils.defaultIfBlank(entity.getSourceType(), "PLC")); |
|
|
|
entity.setStatus(StringUtils.defaultIfBlank(entity.getStatus(), "启用")); |
|
|
|
entity.setCreateTime(now); |
|
|
|
entity.setUpdateTime(now); |
|
|
|
rackExternalDeviceMapper.insert(entity); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void updateExternalDevice(RackExternalDeviceEntity entity) { |
|
|
|
if (entity == null || entity.getDeviceId() == null) { |
|
|
|
throw new XJException("device_id 不能为空"); |
|
|
|
} |
|
|
|
RackExternalDeviceEntity before = rackExternalDeviceMapper.selectById(entity.getDeviceId()); |
|
|
|
if (before == null) { |
|
|
|
throw new XJException("设备不存在"); |
|
|
|
} |
|
|
|
if (StringUtils.isNotBlank(entity.getDeviceCode())) { |
|
|
|
entity.setDeviceCode(entity.getDeviceCode().trim()); |
|
|
|
} |
|
|
|
if (StringUtils.isBlank(entity.getSourceType())) { |
|
|
|
entity.setSourceType(before.getSourceType()); |
|
|
|
} |
|
|
|
if (StringUtils.isBlank(entity.getDeviceType())) { |
|
|
|
entity.setDeviceType(before.getDeviceType()); |
|
|
|
} |
|
|
|
entity.setUpdateTime(new Date()); |
|
|
|
rackExternalDeviceMapper.updateById(entity); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void deleteExternalDevice(Long deviceId) { |
|
|
|
rackExternalDeviceMapper.deleteById(deviceId); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void deleteExternalDeviceByCode(String deviceCode) { |
|
|
|
if (StringUtils.isBlank(deviceCode)) { |
|
|
|
throw new XJException("设备编码不能为空"); |
|
|
|
} |
|
|
|
rackExternalDeviceMapper.delete( |
|
|
|
new LambdaQueryWrapper<RackExternalDeviceEntity>() |
|
|
|
.eq(RackExternalDeviceEntity::getDeviceCode, deviceCode.trim()) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public Map<String, Object> ingestPlcEvent(RackPlcIngestRequest request) { |
|
|
|
if (request == null || StringUtils.isBlank(request.getJobCode())) { |
|
|
|
throw new XJException("任务单号不能为空"); |
|
|
|
} |
|
|
|
RackExternalDeviceEntity device = null; |
|
|
|
if (StringUtils.isNotBlank(request.getDeviceCode())) { |
|
|
|
device = rackExternalDeviceMapper.selectOne( |
|
|
|
new LambdaQueryWrapper<RackExternalDeviceEntity>() |
|
|
|
.eq(RackExternalDeviceEntity::getDeviceCode, request.getDeviceCode().trim()) |
|
|
|
); |
|
|
|
if (device == null) { |
|
|
|
throw new XJException("未找到外采设备: " + request.getDeviceCode()); |
|
|
|
} |
|
|
|
if (!StringUtils.equals(device.getStatus(), "启用")) { |
|
|
|
throw new XJException("设备未启用: " + request.getDeviceCode()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
RackProductionActionRequest action = new RackProductionActionRequest(); |
|
|
|
action.setJobCode(request.getJobCode()); |
|
|
|
action.setRackCode(request.getRackCode()); |
|
|
|
action.setLineId(StringUtils.defaultIfBlank(request.getLineId(), device == null ? null : device.getLineId())); |
|
|
|
action.setStationId(StringUtils.defaultIfBlank(request.getStationId(), device == null ? null : device.getStationId())); |
|
|
|
action.setStepCode(StringUtils.defaultIfBlank(request.getStepCode(), device == null ? null : device.getStepCode())); |
|
|
|
action.setSourceType(StringUtils.defaultIfBlank(request.getSourceType(), device == null ? "PLC" : device.getSourceType())); |
|
|
|
action.setOperatorName(StringUtils.defaultIfBlank(request.getOperatorName(), "PLC采集")); |
|
|
|
action.setPayloadJson(StringUtils.defaultIfBlank(request.getPayloadJson(), "{}")); |
|
|
|
|
|
|
|
Date now = request.getEventTime() == null ? new Date() : request.getEventTime(); |
|
|
|
if (device != null) { |
|
|
|
touchExternalDeviceHeartbeat(device.getDeviceId(), now); |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
Map<String, Object> result = rackClosedLoopService.stationPassByCode(action); |
|
|
|
Map<String, Object> response = new LinkedHashMap<>(); |
|
|
|
response.put("ingestStatus", "PASS"); |
|
|
|
response.put("msg", "PLC采集过站成功"); |
|
|
|
response.put("result", result); |
|
|
|
return response; |
|
|
|
} catch (Exception e) { |
|
|
|
if (Boolean.TRUE.equals(request.getFallbackManual())) { |
|
|
|
Map<String, Object> response = fallbackToManualRecord(request, action, now, e); |
|
|
|
response.put("ingestStatus", "FALLBACK_MANUAL"); |
|
|
|
return response; |
|
|
|
} |
|
|
|
throw e; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public List<Map<String, Object>> listPlcStatus() { |
|
|
|
List<RackExternalDeviceEntity> deviceList = rackExternalDeviceMapper.selectList( |
|
|
|
new LambdaQueryWrapper<RackExternalDeviceEntity>() |
|
|
|
.eq(RackExternalDeviceEntity::getDeviceType, "PLC") |
|
|
|
.orderByAsc(RackExternalDeviceEntity::getDeviceCode) |
|
|
|
); |
|
|
|
Date now = new Date(); |
|
|
|
List<Map<String, Object>> rows = new ArrayList<>(); |
|
|
|
for (RackExternalDeviceEntity device : deviceList) { |
|
|
|
RackStationEventEntity latestEvent = queryLatestEventByDevice(device); |
|
|
|
Date heartbeat = device.getLastHeartbeatTime(); |
|
|
|
if (heartbeat == null && latestEvent != null) { |
|
|
|
heartbeat = latestEvent.getEventTime(); |
|
|
|
} |
|
|
|
boolean online = false; |
|
|
|
String offlineReason = ""; |
|
|
|
if (!StringUtils.equals(device.getStatus(), "启用")) { |
|
|
|
offlineReason = "设备未启用"; |
|
|
|
} else if (heartbeat == null) { |
|
|
|
offlineReason = "无心跳"; |
|
|
|
} else { |
|
|
|
long deltaMs = now.getTime() - heartbeat.getTime(); |
|
|
|
online = deltaMs <= PLC_OFFLINE_MINUTES * 60L * 1000L; |
|
|
|
if (!online) { |
|
|
|
offlineReason = "心跳超时"; |
|
|
|
} |
|
|
|
} |
|
|
|
Map<String, Object> row = new LinkedHashMap<>(); |
|
|
|
row.put("deviceId", device.getDeviceId()); |
|
|
|
row.put("deviceCode", device.getDeviceCode()); |
|
|
|
row.put("deviceName", device.getDeviceName()); |
|
|
|
row.put("lineId", device.getLineId()); |
|
|
|
row.put("stationId", device.getStationId()); |
|
|
|
row.put("stepCode", device.getStepCode()); |
|
|
|
row.put("status", device.getStatus()); |
|
|
|
row.put("heartbeatTime", heartbeat); |
|
|
|
row.put("latestEventTime", latestEvent == null ? null : latestEvent.getEventTime()); |
|
|
|
row.put("latestEventStatus", latestEvent == null ? null : latestEvent.getEventStatus()); |
|
|
|
row.put("online", online); |
|
|
|
row.put("offlineReason", offlineReason); |
|
|
|
rows.add(row); |
|
|
|
} |
|
|
|
return rows; |
|
|
|
} |
|
|
|
|
|
|
|
public String resolveDownHangRackCodeByBatch(String jobCode, String batchCode) { |
|
|
|
RackJobOrderEntity job = requireJobByCode(jobCode); |
|
|
|
if (StringUtils.isBlank(batchCode)) { |
|
|
|
throw new XJException("批次编码不能为空"); |
|
|
|
} |
|
|
|
RackInboundOrderEntity inbound = rackInboundOrderMapper.selectOne( |
|
|
|
new LambdaQueryWrapper<RackInboundOrderEntity>() |
|
|
|
.eq(RackInboundOrderEntity::getBatchCode, batchCode.trim()) |
|
|
|
.orderByDesc(RackInboundOrderEntity::getCreateTime) |
|
|
|
.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY") |
|
|
|
); |
|
|
|
if (inbound == null) { |
|
|
|
throw new XJException("未找到批次编码对应入库单: " + batchCode); |
|
|
|
} |
|
|
|
long relCount = rackJobInboundRelMapper.selectCount( |
|
|
|
new LambdaQueryWrapper<RackJobInboundRelEntity>() |
|
|
|
.eq(RackJobInboundRelEntity::getJobId, job.getJobId()) |
|
|
|
.eq(RackJobInboundRelEntity::getInboundId, inbound.getInboundId()) |
|
|
|
); |
|
|
|
if (relCount <= 0) { |
|
|
|
throw new XJException("批次不属于当前任务: " + batchCode); |
|
|
|
} |
|
|
|
List<RackBindingEntity> activeList = rackBindingMapper.selectList( |
|
|
|
new LambdaQueryWrapper<RackBindingEntity>() |
|
|
|
.eq(RackBindingEntity::getJobId, job.getJobId()) |
|
|
|
.eq(RackBindingEntity::getBindStatus, RackClosedLoopStatus.Binding.SHENG_XIAO_ZHONG) |
|
|
|
.orderByDesc(RackBindingEntity::getBindTime) |
|
|
|
); |
|
|
|
if (activeList.isEmpty()) { |
|
|
|
throw new XJException("当前任务不存在生效中的上挂关系"); |
|
|
|
} |
|
|
|
if (activeList.size() > 1) { |
|
|
|
throw new XJException("当前任务有多个挂具在制,请扫码挂具码执行下挂"); |
|
|
|
} |
|
|
|
RackCarrierEntity rack = rackCarrierMapper.selectById(activeList.get(0).getRackId()); |
|
|
|
if (rack == null || StringUtils.isBlank(rack.getRackCode())) { |
|
|
|
throw new XJException("生效挂具不存在,请检查绑定数据"); |
|
|
|
} |
|
|
|
return rack.getRackCode(); |
|
|
|
} |
|
|
|
|
|
|
|
public List<RackPackageOrderEntity> listPackageOrder(RackPackageOrderEntity query) { |
|
|
|
LambdaQueryWrapper<RackPackageOrderEntity> wrapper = new LambdaQueryWrapper<>(); |
|
|
|
if (query != null) { |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getPackageNo()), RackPackageOrderEntity::getPackageNo, query.getPackageNo()); |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getJobCode()), RackPackageOrderEntity::getJobCode, query.getJobCode()); |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getBatchCode()), RackPackageOrderEntity::getBatchCode, query.getBatchCode()); |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getRackCode()), RackPackageOrderEntity::getRackCode, query.getRackCode()); |
|
|
|
wrapper.eq(StringUtils.isNotBlank(query.getStatus()), RackPackageOrderEntity::getStatus, query.getStatus()); |
|
|
|
} |
|
|
|
wrapper.orderByDesc(RackPackageOrderEntity::getCreateTime); |
|
|
|
return rackPackageOrderMapper.selectList(wrapper); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void savePackageOrder(RackPackageOrderEntity entity) { |
|
|
|
if (entity == null) { |
|
|
|
throw new XJException("请求参数不能为空"); |
|
|
|
} |
|
|
|
if (StringUtils.isBlank(entity.getJobCode())) { |
|
|
|
throw new XJException("任务单号不能为空"); |
|
|
|
} |
|
|
|
RackJobOrderEntity job = requireJobByCode(entity.getJobCode()); |
|
|
|
Date now = new Date(); |
|
|
|
entity.setPackageId(rackIdGenerator.nextId()); |
|
|
|
entity.setPackageNo(StringUtils.isNotBlank(entity.getPackageNo()) ? entity.getPackageNo().trim() : generatePackageNo()); |
|
|
|
entity.setJobId(job.getJobId()); |
|
|
|
if (StringUtils.isBlank(entity.getBatchCode())) { |
|
|
|
entity.setBatchCode(resolveBatchCodeByJob(job.getJobId())); |
|
|
|
} |
|
|
|
entity.setQty(entity.getQty() == null ? 0 : entity.getQty()); |
|
|
|
entity.setPrintCount(entity.getPrintCount() == null ? 0 : entity.getPrintCount()); |
|
|
|
entity.setStatus(StringUtils.defaultIfBlank(entity.getStatus(), "待打印")); |
|
|
|
entity.setPackedTime(entity.getPackedTime() == null ? now : entity.getPackedTime()); |
|
|
|
entity.setCreateTime(now); |
|
|
|
entity.setUpdateTime(now); |
|
|
|
rackPackageOrderMapper.insert(entity); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void updatePackageOrder(RackPackageOrderEntity entity) { |
|
|
|
if (entity == null || entity.getPackageId() == null) { |
|
|
|
throw new XJException("package_id 不能为空"); |
|
|
|
} |
|
|
|
RackPackageOrderEntity before = rackPackageOrderMapper.selectById(entity.getPackageId()); |
|
|
|
if (before == null) { |
|
|
|
throw new XJException("包装单不存在"); |
|
|
|
} |
|
|
|
if (StringUtils.isNotBlank(entity.getJobCode())) { |
|
|
|
RackJobOrderEntity job = requireJobByCode(entity.getJobCode()); |
|
|
|
entity.setJobId(job.getJobId()); |
|
|
|
} |
|
|
|
entity.setUpdateTime(new Date()); |
|
|
|
rackPackageOrderMapper.updateById(entity); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void deletePackageOrderByNo(String packageNo) { |
|
|
|
if (StringUtils.isBlank(packageNo)) { |
|
|
|
throw new XJException("包装单号不能为空"); |
|
|
|
} |
|
|
|
RackPackageOrderEntity order = rackPackageOrderMapper.selectOne( |
|
|
|
new LambdaQueryWrapper<RackPackageOrderEntity>().eq(RackPackageOrderEntity::getPackageNo, packageNo.trim()) |
|
|
|
); |
|
|
|
if (order == null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
rackLabelPrintLogMapper.delete( |
|
|
|
new LambdaQueryWrapper<RackLabelPrintLogEntity>().eq(RackLabelPrintLogEntity::getPackageId, order.getPackageId()) |
|
|
|
); |
|
|
|
rackPackageOrderMapper.deleteById(order.getPackageId()); |
|
|
|
} |
|
|
|
|
|
|
|
public List<RackLabelTemplateEntity> listLabelTemplate(RackLabelTemplateEntity query) { |
|
|
|
LambdaQueryWrapper<RackLabelTemplateEntity> wrapper = new LambdaQueryWrapper<>(); |
|
|
|
if (query != null) { |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getTemplateCode()), RackLabelTemplateEntity::getTemplateCode, query.getTemplateCode()); |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getTemplateName()), RackLabelTemplateEntity::getTemplateName, query.getTemplateName()); |
|
|
|
wrapper.eq(StringUtils.isNotBlank(query.getStatus()), RackLabelTemplateEntity::getStatus, query.getStatus()); |
|
|
|
} |
|
|
|
wrapper.orderByDesc(RackLabelTemplateEntity::getCreateTime); |
|
|
|
return rackLabelTemplateMapper.selectList(wrapper); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void saveLabelTemplate(RackLabelTemplateEntity entity) { |
|
|
|
if (entity == null || StringUtils.isBlank(entity.getTemplateCode())) { |
|
|
|
throw new XJException("模板编码不能为空"); |
|
|
|
} |
|
|
|
Date now = new Date(); |
|
|
|
entity.setTemplateId(rackIdGenerator.nextId()); |
|
|
|
entity.setTemplateCode(entity.getTemplateCode().trim()); |
|
|
|
entity.setStatus(StringUtils.defaultIfBlank(entity.getStatus(), "启用")); |
|
|
|
entity.setCreateTime(now); |
|
|
|
entity.setUpdateTime(now); |
|
|
|
rackLabelTemplateMapper.insert(entity); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void updateLabelTemplate(RackLabelTemplateEntity entity) { |
|
|
|
if (entity == null || entity.getTemplateId() == null) { |
|
|
|
throw new XJException("template_id 不能为空"); |
|
|
|
} |
|
|
|
entity.setUpdateTime(new Date()); |
|
|
|
rackLabelTemplateMapper.updateById(entity); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public void deleteLabelTemplateByCode(String templateCode) { |
|
|
|
if (StringUtils.isBlank(templateCode)) { |
|
|
|
throw new XJException("模板编码不能为空"); |
|
|
|
} |
|
|
|
rackLabelTemplateMapper.delete( |
|
|
|
new LambdaQueryWrapper<RackLabelTemplateEntity>().eq(RackLabelTemplateEntity::getTemplateCode, templateCode.trim()) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional |
|
|
|
public Map<String, Object> printPackageLabel(RackPackagePrintRequest request) { |
|
|
|
if (request == null || StringUtils.isBlank(request.getPackageNo())) { |
|
|
|
throw new XJException("包装单号不能为空"); |
|
|
|
} |
|
|
|
if (StringUtils.isBlank(request.getTemplateCode())) { |
|
|
|
throw new XJException("标签模板不能为空"); |
|
|
|
} |
|
|
|
RackPackageOrderEntity order = requirePackageByNo(request.getPackageNo()); |
|
|
|
RackLabelTemplateEntity template = requireTemplateByCode(request.getTemplateCode()); |
|
|
|
int copies = request.getCopies() == null || request.getCopies() <= 0 ? 1 : request.getCopies(); |
|
|
|
Date now = new Date(); |
|
|
|
String operatorName = StringUtils.defaultIfBlank(request.getOperatorName(), "PC操作员"); |
|
|
|
String renderedContent = renderTemplate(template.getTemplateContent(), order); |
|
|
|
int oldPrintCount = order.getPrintCount() == null ? 0 : order.getPrintCount(); |
|
|
|
List<String> printNos = new ArrayList<>(); |
|
|
|
|
|
|
|
for (int i = 1; i <= copies; i++) { |
|
|
|
RackLabelPrintLogEntity log = new RackLabelPrintLogEntity(); |
|
|
|
log.setLogId(rackIdGenerator.nextId()); |
|
|
|
log.setPackageId(order.getPackageId()); |
|
|
|
log.setPackageNo(order.getPackageNo()); |
|
|
|
log.setTemplateCode(template.getTemplateCode()); |
|
|
|
log.setPrintNo(generatePrintNo()); |
|
|
|
log.setPrintSeq(oldPrintCount + i); |
|
|
|
log.setPrintStatus("成功"); |
|
|
|
log.setOperatorName(operatorName); |
|
|
|
log.setPayloadJson(JSON.toJSONString(buildPrintPayload(order, template, renderedContent, i, copies))); |
|
|
|
log.setPrintTime(now); |
|
|
|
log.setCreateTime(now); |
|
|
|
rackLabelPrintLogMapper.insert(log); |
|
|
|
printNos.add(log.getPrintNo()); |
|
|
|
} |
|
|
|
|
|
|
|
rackPackageOrderMapper.update( |
|
|
|
null, |
|
|
|
new LambdaUpdateWrapper<RackPackageOrderEntity>() |
|
|
|
.eq(RackPackageOrderEntity::getPackageId, order.getPackageId()) |
|
|
|
.set(RackPackageOrderEntity::getLabelTemplateCode, template.getTemplateCode()) |
|
|
|
.set(RackPackageOrderEntity::getLabelContent, renderedContent) |
|
|
|
.set(RackPackageOrderEntity::getPrintCount, oldPrintCount + copies) |
|
|
|
.set(RackPackageOrderEntity::getLastPrintTime, now) |
|
|
|
.set(RackPackageOrderEntity::getStatus, "已打印") |
|
|
|
.set(RackPackageOrderEntity::getOperatorName, operatorName) |
|
|
|
.set(RackPackageOrderEntity::getUpdateTime, now) |
|
|
|
); |
|
|
|
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>(); |
|
|
|
result.put("packageNo", order.getPackageNo()); |
|
|
|
result.put("templateCode", template.getTemplateCode()); |
|
|
|
result.put("copies", copies); |
|
|
|
result.put("printNos", printNos); |
|
|
|
result.put("labelPreview", renderedContent); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
public List<RackLabelPrintLogEntity> listLabelPrintLog(RackLabelPrintLogEntity query) { |
|
|
|
LambdaQueryWrapper<RackLabelPrintLogEntity> wrapper = new LambdaQueryWrapper<>(); |
|
|
|
if (query != null) { |
|
|
|
wrapper.eq(query.getPackageId() != null, RackLabelPrintLogEntity::getPackageId, query.getPackageId()); |
|
|
|
wrapper.like(StringUtils.isNotBlank(query.getPackageNo()), RackLabelPrintLogEntity::getPackageNo, query.getPackageNo()); |
|
|
|
wrapper.eq(StringUtils.isNotBlank(query.getTemplateCode()), RackLabelPrintLogEntity::getTemplateCode, query.getTemplateCode()); |
|
|
|
} |
|
|
|
wrapper.orderByDesc(RackLabelPrintLogEntity::getPrintTime); |
|
|
|
return rackLabelPrintLogMapper.selectList(wrapper); |
|
|
|
} |
|
|
|
|
|
|
|
private Map<String, Object> fallbackToManualRecord( |
|
|
|
RackPlcIngestRequest request, |
|
|
|
RackProductionActionRequest action, |
|
|
|
Date recordTime, |
|
|
|
Exception exception |
|
|
|
) { |
|
|
|
RackJobOrderEntity job = requireJobByCode(action.getJobCode()); |
|
|
|
RackManualRecordEntity manual = new RackManualRecordEntity(); |
|
|
|
manual.setRecordType("过站补录"); |
|
|
|
manual.setDocNo(buildManualDocNo(request, action)); |
|
|
|
manual.setBatchId(resolveInboundIdByJob(job.getJobId())); |
|
|
|
manual.setJobId(job.getJobId()); |
|
|
|
manual.setQty(1); |
|
|
|
manual.setOperatorName(StringUtils.defaultIfBlank(action.getOperatorName(), "PLC采集")); |
|
|
|
manual.setReviewerName("系统自动补录"); |
|
|
|
manual.setPayloadJson(JSON.toJSONString(buildFallbackPayload(request, action, exception))); |
|
|
|
manual.setRecordTime(recordTime); |
|
|
|
rackClosedLoopService.saveManualRecord(manual); |
|
|
|
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>(); |
|
|
|
result.put("msg", "PLC过站失败,已自动写入手工补录台账"); |
|
|
|
result.put("error", exception == null ? "未知异常" : exception.getMessage()); |
|
|
|
result.put("manualDocNo", manual.getDocNo()); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
private RackStationEventEntity queryLatestEventByDevice(RackExternalDeviceEntity device) { |
|
|
|
if (device == null) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
LambdaQueryWrapper<RackStationEventEntity> wrapper = new LambdaQueryWrapper<>(); |
|
|
|
wrapper.eq(StringUtils.isNotBlank(device.getStationId()), RackStationEventEntity::getStationId, device.getStationId()); |
|
|
|
wrapper.eq(StringUtils.isNotBlank(device.getStepCode()), RackStationEventEntity::getStepCode, device.getStepCode()); |
|
|
|
wrapper.orderByDesc(RackStationEventEntity::getEventTime); |
|
|
|
wrapper.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY"); |
|
|
|
List<RackStationEventEntity> list = rackStationEventMapper.selectList(wrapper); |
|
|
|
return list.isEmpty() ? null : list.get(0); |
|
|
|
} |
|
|
|
|
|
|
|
private void touchExternalDeviceHeartbeat(Long deviceId, Date heartbeatTime) { |
|
|
|
if (deviceId == null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
rackExternalDeviceMapper.update( |
|
|
|
null, |
|
|
|
new LambdaUpdateWrapper<RackExternalDeviceEntity>() |
|
|
|
.eq(RackExternalDeviceEntity::getDeviceId, deviceId) |
|
|
|
.set(RackExternalDeviceEntity::getLastHeartbeatTime, heartbeatTime == null ? new Date() : heartbeatTime) |
|
|
|
.set(RackExternalDeviceEntity::getUpdateTime, new Date()) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
private RackPackageOrderEntity requirePackageByNo(String packageNo) { |
|
|
|
RackPackageOrderEntity order = rackPackageOrderMapper.selectOne( |
|
|
|
new LambdaQueryWrapper<RackPackageOrderEntity>() |
|
|
|
.eq(RackPackageOrderEntity::getPackageNo, packageNo.trim()) |
|
|
|
); |
|
|
|
if (order == null) { |
|
|
|
throw new XJException("包装单不存在: " + packageNo); |
|
|
|
} |
|
|
|
return order; |
|
|
|
} |
|
|
|
|
|
|
|
private RackLabelTemplateEntity requireTemplateByCode(String templateCode) { |
|
|
|
RackLabelTemplateEntity template = rackLabelTemplateMapper.selectOne( |
|
|
|
new LambdaQueryWrapper<RackLabelTemplateEntity>() |
|
|
|
.eq(RackLabelTemplateEntity::getTemplateCode, templateCode.trim()) |
|
|
|
); |
|
|
|
if (template == null) { |
|
|
|
throw new XJException("标签模板不存在: " + templateCode); |
|
|
|
} |
|
|
|
if (!StringUtils.equals(template.getStatus(), "启用")) { |
|
|
|
throw new XJException("标签模板未启用: " + templateCode); |
|
|
|
} |
|
|
|
return template; |
|
|
|
} |
|
|
|
|
|
|
|
private RackJobOrderEntity requireJobByCode(String jobCode) { |
|
|
|
RackJobOrderEntity job = rackJobOrderMapper.selectOne( |
|
|
|
new LambdaQueryWrapper<RackJobOrderEntity>().eq(RackJobOrderEntity::getJobCode, StringUtils.trimToEmpty(jobCode)) |
|
|
|
); |
|
|
|
if (job == null) { |
|
|
|
throw new XJException("任务单不存在: " + jobCode); |
|
|
|
} |
|
|
|
return job; |
|
|
|
} |
|
|
|
|
|
|
|
private Long resolveInboundIdByJob(Long jobId) { |
|
|
|
if (jobId == null) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
List<RackJobInboundRelEntity> relList = rackJobInboundRelMapper.selectList( |
|
|
|
new LambdaQueryWrapper<RackJobInboundRelEntity>() |
|
|
|
.eq(RackJobInboundRelEntity::getJobId, jobId) |
|
|
|
.orderByAsc(RackJobInboundRelEntity::getCreateTime) |
|
|
|
.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY") |
|
|
|
); |
|
|
|
if (relList.isEmpty()) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
return relList.get(0).getInboundId(); |
|
|
|
} |
|
|
|
|
|
|
|
private String resolveBatchCodeByJob(Long jobId) { |
|
|
|
Long inboundId = resolveInboundIdByJob(jobId); |
|
|
|
if (inboundId == null) { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
RackInboundOrderEntity inbound = rackInboundOrderMapper.selectById(inboundId); |
|
|
|
return inbound == null ? "" : StringUtils.defaultString(inbound.getBatchCode()); |
|
|
|
} |
|
|
|
|
|
|
|
private String buildManualDocNo(RackPlcIngestRequest request, RackProductionActionRequest action) { |
|
|
|
String jobCode = StringUtils.defaultIfBlank(action.getJobCode(), "UNKNOWN"); |
|
|
|
String stepCode = StringUtils.defaultIfBlank(action.getStepCode(), "STEP"); |
|
|
|
String suffix = String.valueOf(Math.abs(rackIdGenerator.nextId()) % 10000); |
|
|
|
return "PLC-MANUAL-" + jobCode + "-" + stepCode + "-" + String.format("%04d", Integer.parseInt(suffix)); |
|
|
|
} |
|
|
|
|
|
|
|
private Map<String, Object> buildFallbackPayload( |
|
|
|
RackPlcIngestRequest request, |
|
|
|
RackProductionActionRequest action, |
|
|
|
Exception exception |
|
|
|
) { |
|
|
|
Map<String, Object> payload = new LinkedHashMap<>(); |
|
|
|
payload.put("source", "PLC"); |
|
|
|
payload.put("deviceCode", request.getDeviceCode()); |
|
|
|
payload.put("missingReason", StringUtils.defaultIfBlank(request.getMissingReason(), "PLC采集失败自动补录")); |
|
|
|
payload.put("jobCode", action.getJobCode()); |
|
|
|
payload.put("rackCode", action.getRackCode()); |
|
|
|
payload.put("stepCode", action.getStepCode()); |
|
|
|
payload.put("stationId", action.getStationId()); |
|
|
|
payload.put("lineId", action.getLineId()); |
|
|
|
payload.put("payloadJson", action.getPayloadJson()); |
|
|
|
payload.put("errorMsg", exception == null ? "" : exception.getMessage()); |
|
|
|
return payload; |
|
|
|
} |
|
|
|
|
|
|
|
private Map<String, Object> buildPrintPayload( |
|
|
|
RackPackageOrderEntity order, |
|
|
|
RackLabelTemplateEntity template, |
|
|
|
String renderedContent, |
|
|
|
int index, |
|
|
|
int total |
|
|
|
) { |
|
|
|
Map<String, Object> payload = new LinkedHashMap<>(); |
|
|
|
payload.put("packageNo", order.getPackageNo()); |
|
|
|
payload.put("templateCode", template.getTemplateCode()); |
|
|
|
payload.put("copyIndex", index); |
|
|
|
payload.put("copyTotal", total); |
|
|
|
payload.put("labelContent", renderedContent); |
|
|
|
return payload; |
|
|
|
} |
|
|
|
|
|
|
|
private String renderTemplate(String templateContent, RackPackageOrderEntity order) { |
|
|
|
String content = StringUtils.defaultString(templateContent); |
|
|
|
content = content.replace("{{packageNo}}", StringUtils.defaultString(order.getPackageNo())); |
|
|
|
content = content.replace("{{jobCode}}", StringUtils.defaultString(order.getJobCode())); |
|
|
|
content = content.replace("{{batchCode}}", StringUtils.defaultString(order.getBatchCode())); |
|
|
|
content = content.replace("{{rackCode}}", StringUtils.defaultString(order.getRackCode())); |
|
|
|
content = content.replace("{{qty}}", String.valueOf(order.getQty() == null ? 0 : order.getQty())); |
|
|
|
return content; |
|
|
|
} |
|
|
|
|
|
|
|
private synchronized String generatePackageNo() { |
|
|
|
String datePart = new SimpleDateFormat("yyyyMMdd").format(new Date()); |
|
|
|
for (int i = 0; i < 8; i++) { |
|
|
|
long suffix = Math.abs(rackIdGenerator.nextId()) % 100000; |
|
|
|
String candidate = "PKG" + datePart + String.format("%05d", suffix); |
|
|
|
long count = rackPackageOrderMapper.selectCount( |
|
|
|
new LambdaQueryWrapper<RackPackageOrderEntity>().eq(RackPackageOrderEntity::getPackageNo, candidate) |
|
|
|
); |
|
|
|
if (count == 0) { |
|
|
|
return candidate; |
|
|
|
} |
|
|
|
} |
|
|
|
throw new XJException("包装单号生成失败,请重试"); |
|
|
|
} |
|
|
|
|
|
|
|
private synchronized String generatePrintNo() { |
|
|
|
String datePart = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); |
|
|
|
long suffix = Math.abs(rackIdGenerator.nextId()) % 100000; |
|
|
|
return "PRN" + datePart + String.format("%05d", suffix); |
|
|
|
} |
|
|
|
} |