Browse Source

init

master
han\hanst 4 weeks ago
parent
commit
5a3c8d557f
  1. 113
      src/main/java/com/xujie/sys/modules/rack/controller/RackClosedLoopController.java
  2. 11
      src/main/java/com/xujie/sys/modules/rack/dto/RackPackagePrintRequest.java
  3. 21
      src/main/java/com/xujie/sys/modules/rack/dto/RackPlcIngestRequest.java
  4. 1
      src/main/java/com/xujie/sys/modules/rack/dto/RackProductionActionRequest.java
  5. 33
      src/main/java/com/xujie/sys/modules/rack/entity/RackExternalDeviceEntity.java
  6. 28
      src/main/java/com/xujie/sys/modules/rack/entity/RackLabelPrintLogEntity.java
  7. 25
      src/main/java/com/xujie/sys/modules/rack/entity/RackLabelTemplateEntity.java
  8. 34
      src/main/java/com/xujie/sys/modules/rack/entity/RackPackageOrderEntity.java
  9. 9
      src/main/java/com/xujie/sys/modules/rack/mapper/RackExternalDeviceMapper.java
  10. 9
      src/main/java/com/xujie/sys/modules/rack/mapper/RackLabelPrintLogMapper.java
  11. 9
      src/main/java/com/xujie/sys/modules/rack/mapper/RackLabelTemplateMapper.java
  12. 9
      src/main/java/com/xujie/sys/modules/rack/mapper/RackPackageOrderMapper.java
  13. 642
      src/main/java/com/xujie/sys/modules/rack/service/impl/RackClosedLoopAddonService.java

113
src/main/java/com/xujie/sys/modules/rack/controller/RackClosedLoopController.java

@ -5,11 +5,15 @@ import com.xujie.sys.modules.rack.dto.RackBindRequest;
import com.xujie.sys.modules.rack.dto.RackBatchCreateRequest;
import com.xujie.sys.modules.rack.dto.RackJobAvailableMaterialRequest;
import com.xujie.sys.modules.rack.dto.RackJobCreateRequest;
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.dto.RackRoutePoolConfigRequest;
import com.xujie.sys.modules.rack.dto.RackStationPassRequest;
import com.xujie.sys.modules.rack.dto.RackTraceQueryRequest;
import com.xujie.sys.modules.rack.entity.*;
import com.xujie.sys.modules.rack.service.impl.RackClosedLoopAddonService;
import org.apache.commons.lang3.StringUtils;
import com.xujie.sys.modules.rack.service.RackClosedLoopService;
import org.springframework.web.bind.annotation.*;
@ -20,9 +24,14 @@ import java.util.Map;
public class RackClosedLoopController {
private final RackClosedLoopService rackClosedLoopService;
private final RackClosedLoopAddonService rackClosedLoopAddonService;
public RackClosedLoopController(RackClosedLoopService rackClosedLoopService) {
public RackClosedLoopController(
RackClosedLoopService rackClosedLoopService,
RackClosedLoopAddonService rackClosedLoopAddonService
) {
this.rackClosedLoopService = rackClosedLoopService;
this.rackClosedLoopAddonService = rackClosedLoopAddonService;
}
@PostMapping("/part/list")
@ -402,6 +411,10 @@ public class RackClosedLoopController {
@PostMapping("/production/downhang")
public R bindDownHangByCode(@RequestBody RackProductionActionRequest request) {
if (StringUtils.isBlank(request.getRackCode()) && StringUtils.isNotBlank(request.getBatchCode())) {
String resolvedRackCode = rackClosedLoopAddonService.resolveDownHangRackCodeByBatch(request.getJobCode(), request.getBatchCode());
request.setRackCode(resolvedRackCode);
}
rackClosedLoopService.bindDownHangByCode(request);
return R.ok();
}
@ -444,6 +457,104 @@ public class RackClosedLoopController {
return R.ok();
}
@PostMapping("/device/list")
public R listExternalDevice(@RequestBody(required = false) RackExternalDeviceEntity query) {
return R.ok().put("rows", rackClosedLoopAddonService.listExternalDevice(query));
}
@PostMapping("/device/save")
public R saveExternalDevice(@RequestBody RackExternalDeviceEntity entity) {
rackClosedLoopAddonService.saveExternalDevice(entity);
return R.ok();
}
@PostMapping("/device/update")
public R updateExternalDevice(@RequestBody RackExternalDeviceEntity entity) {
rackClosedLoopAddonService.updateExternalDevice(entity);
return R.ok();
}
@PostMapping("/device/delete/{deviceId}")
public R deleteExternalDevice(@PathVariable Long deviceId) {
rackClosedLoopAddonService.deleteExternalDevice(deviceId);
return R.ok();
}
@PostMapping("/device/delete-by-code")
public R deleteExternalDeviceByCode(@RequestBody Map<String, Object> data) {
String deviceCode = data == null ? null : String.valueOf(data.getOrDefault("deviceCode", ""));
rackClosedLoopAddonService.deleteExternalDeviceByCode(deviceCode);
return R.ok();
}
@PostMapping("/plc/ingest")
public R ingestPlcEvent(@RequestBody RackPlcIngestRequest request) {
return R.ok().put("result", rackClosedLoopAddonService.ingestPlcEvent(request));
}
@GetMapping("/plc/status")
public R listPlcStatus() {
return R.ok().put("rows", rackClosedLoopAddonService.listPlcStatus());
}
@PostMapping("/package/order/list")
public R listPackageOrder(@RequestBody(required = false) RackPackageOrderEntity query) {
return R.ok().put("rows", rackClosedLoopAddonService.listPackageOrder(query));
}
@PostMapping("/package/order/save")
public R savePackageOrder(@RequestBody RackPackageOrderEntity entity) {
rackClosedLoopAddonService.savePackageOrder(entity);
return R.ok();
}
@PostMapping("/package/order/update")
public R updatePackageOrder(@RequestBody RackPackageOrderEntity entity) {
rackClosedLoopAddonService.updatePackageOrder(entity);
return R.ok();
}
@PostMapping("/package/order/delete-by-no")
public R deletePackageOrderByNo(@RequestBody Map<String, Object> data) {
String packageNo = data == null ? null : String.valueOf(data.getOrDefault("packageNo", ""));
rackClosedLoopAddonService.deletePackageOrderByNo(packageNo);
return R.ok();
}
@PostMapping("/package/template/list")
public R listLabelTemplate(@RequestBody(required = false) RackLabelTemplateEntity query) {
return R.ok().put("rows", rackClosedLoopAddonService.listLabelTemplate(query));
}
@PostMapping("/package/template/save")
public R saveLabelTemplate(@RequestBody RackLabelTemplateEntity entity) {
rackClosedLoopAddonService.saveLabelTemplate(entity);
return R.ok();
}
@PostMapping("/package/template/update")
public R updateLabelTemplate(@RequestBody RackLabelTemplateEntity entity) {
rackClosedLoopAddonService.updateLabelTemplate(entity);
return R.ok();
}
@PostMapping("/package/template/delete-by-code")
public R deleteLabelTemplateByCode(@RequestBody Map<String, Object> data) {
String templateCode = data == null ? null : String.valueOf(data.getOrDefault("templateCode", ""));
rackClosedLoopAddonService.deleteLabelTemplateByCode(templateCode);
return R.ok();
}
@PostMapping("/package/label/print")
public R printPackageLabel(@RequestBody RackPackagePrintRequest request) {
return R.ok().put("result", rackClosedLoopAddonService.printPackageLabel(request));
}
@PostMapping("/package/print-log/list")
public R listLabelPrintLog(@RequestBody(required = false) RackLabelPrintLogEntity query) {
return R.ok().put("rows", rackClosedLoopAddonService.listLabelPrintLog(query));
}
@PostMapping("/trace/query")
public R traceQuery(@RequestBody RackTraceQueryRequest request) {
return R.ok().put("result", rackClosedLoopService.traceQuery(request));

11
src/main/java/com/xujie/sys/modules/rack/dto/RackPackagePrintRequest.java

@ -0,0 +1,11 @@
package com.xujie.sys.modules.rack.dto;
import lombok.Data;
@Data
public class RackPackagePrintRequest {
private String packageNo;
private String templateCode;
private Integer copies;
private String operatorName;
}

21
src/main/java/com/xujie/sys/modules/rack/dto/RackPlcIngestRequest.java

@ -0,0 +1,21 @@
package com.xujie.sys.modules.rack.dto;
import lombok.Data;
import java.util.Date;
@Data
public class RackPlcIngestRequest {
private String deviceCode;
private String jobCode;
private String rackCode;
private String lineId;
private String stationId;
private String stepCode;
private String sourceType;
private String payloadJson;
private String operatorName;
private Date eventTime;
private Boolean fallbackManual;
private String missingReason;
}

1
src/main/java/com/xujie/sys/modules/rack/dto/RackProductionActionRequest.java

@ -6,6 +6,7 @@ import lombok.Data;
public class RackProductionActionRequest {
private String jobCode;
private String rackCode;
private String batchCode;
private String lineId;
private String stationId;
private String stepCode;

33
src/main/java/com/xujie/sys/modules/rack/entity/RackExternalDeviceEntity.java

@ -0,0 +1,33 @@
package com.xujie.sys.modules.rack.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("rack_external_device")
public class RackExternalDeviceEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.INPUT)
private Long deviceId;
private String deviceCode;
private String deviceName;
private String deviceType;
private String sourceType;
private String lineId;
private String stationId;
private String stepCode;
private String ip;
private Integer port;
private Integer unitId;
private String status;
private String remark;
private Date lastHeartbeatTime;
private Date createTime;
private Date updateTime;
}

28
src/main/java/com/xujie/sys/modules/rack/entity/RackLabelPrintLogEntity.java

@ -0,0 +1,28 @@
package com.xujie.sys.modules.rack.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("rack_label_print_log")
public class RackLabelPrintLogEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.INPUT)
private Long logId;
private Long packageId;
private String packageNo;
private String templateCode;
private String printNo;
private Integer printSeq;
private String printStatus;
private String operatorName;
private String payloadJson;
private Date printTime;
private Date createTime;
}

25
src/main/java/com/xujie/sys/modules/rack/entity/RackLabelTemplateEntity.java

@ -0,0 +1,25 @@
package com.xujie.sys.modules.rack.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("rack_label_template")
public class RackLabelTemplateEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.INPUT)
private Long templateId;
private String templateCode;
private String templateName;
private String templateContent;
private String status;
private String remark;
private Date createTime;
private Date updateTime;
}

34
src/main/java/com/xujie/sys/modules/rack/entity/RackPackageOrderEntity.java

@ -0,0 +1,34 @@
package com.xujie.sys.modules.rack.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("rack_package_order")
public class RackPackageOrderEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.INPUT)
private Long packageId;
private String packageNo;
private Long jobId;
private String jobCode;
private String batchCode;
private String rackCode;
private Integer qty;
private String labelTemplateCode;
private String labelContent;
private Integer printCount;
private Date lastPrintTime;
private String status;
private String operatorName;
private Date packedTime;
private String remark;
private Date createTime;
private Date updateTime;
}

9
src/main/java/com/xujie/sys/modules/rack/mapper/RackExternalDeviceMapper.java

@ -0,0 +1,9 @@
package com.xujie.sys.modules.rack.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xujie.sys.modules.rack.entity.RackExternalDeviceEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RackExternalDeviceMapper extends BaseMapper<RackExternalDeviceEntity> {
}

9
src/main/java/com/xujie/sys/modules/rack/mapper/RackLabelPrintLogMapper.java

@ -0,0 +1,9 @@
package com.xujie.sys.modules.rack.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xujie.sys.modules.rack.entity.RackLabelPrintLogEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RackLabelPrintLogMapper extends BaseMapper<RackLabelPrintLogEntity> {
}

9
src/main/java/com/xujie/sys/modules/rack/mapper/RackLabelTemplateMapper.java

@ -0,0 +1,9 @@
package com.xujie.sys.modules.rack.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xujie.sys.modules.rack.entity.RackLabelTemplateEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RackLabelTemplateMapper extends BaseMapper<RackLabelTemplateEntity> {
}

9
src/main/java/com/xujie/sys/modules/rack/mapper/RackPackageOrderMapper.java

@ -0,0 +1,9 @@
package com.xujie.sys.modules.rack.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xujie.sys.modules.rack.entity.RackPackageOrderEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RackPackageOrderMapper extends BaseMapper<RackPackageOrderEntity> {
}

642
src/main/java/com/xujie/sys/modules/rack/service/impl/RackClosedLoopAddonService.java

@ -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);
}
}
Loading…
Cancel
Save