opt:西门子优化

This commit is contained in:
2026-03-10 13:37:58 +08:00
parent f76e7b1142
commit ff64d2d48c
19 changed files with 1136 additions and 23 deletions

View File

@@ -0,0 +1,38 @@
package org.nl.acs.agv_usage.domain;
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.math.BigDecimal;
import java.util.Date;
@Data
@TableName("acs_agv_daily_statistics")
public class AcsAgvDailyStatistics implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
private String device_code;
private String device_name;
private Date work_date;
private Long total_work_duration;
private Long total_idle_duration;
private BigDecimal usage_rate;
private Integer task_count;
private Date create_time;
private Date update_time;
}

View File

@@ -0,0 +1,43 @@
package org.nl.acs.agv_usage.domain;
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("acs_agv_usage_record")
public class AcsAgvUsageRecord implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
private String device_code;
private String device_name;
private String task_code;
private String instruction_code;
private Date start_time;
private Date end_time;
private Long work_duration;
private Date work_date;
private String status;
private Date create_time;
private Date update_time;
private String is_active;
}

View File

@@ -0,0 +1,19 @@
package org.nl.acs.agv_usage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics;
import java.util.Date;
import java.util.List;
@Mapper
public interface AcsAgvDailyStatisticsMapper extends BaseMapper<AcsAgvDailyStatistics> {
AcsAgvDailyStatistics selectByDeviceCodeAndDate(@Param("deviceCode") String deviceCode, @Param("workDate") Date workDate);
List<AcsAgvDailyStatistics> selectByDateRange(@Param("startDate") Date startDate, @Param("endDate") Date endDate);
List<AcsAgvDailyStatistics> selectByDeviceCodeAndDateRange(@Param("deviceCode") String deviceCode, @Param("startDate") Date startDate, @Param("endDate") Date endDate);
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.nl.acs.agv_usage.mapper.AcsAgvDailyStatisticsMapper">
<resultMap id="BaseResultMap" type="org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics">
<id column="id" property="id"/>
<result column="device_code" property="device_code"/>
<result column="device_name" property="device_name"/>
<result column="work_date" property="work_date"/>
<result column="total_work_duration" property="total_work_duration"/>
<result column="total_idle_duration" property="total_idle_duration"/>
<result column="usage_rate" property="usage_rate"/>
<result column="task_count" property="task_count"/>
<result column="create_time" property="create_time"/>
<result column="update_time" property="update_time"/>
</resultMap>
<select id="selectByDeviceCodeAndDate" resultMap="BaseResultMap">
SELECT * FROM acs_agv_daily_statistics
WHERE device_code = #{deviceCode}
AND work_date = #{workDate}
</select>
<select id="selectByDateRange" resultMap="BaseResultMap">
SELECT * FROM acs_agv_daily_statistics
WHERE work_date BETWEEN #{startDate} AND #{endDate}
ORDER BY device_code, work_date
</select>
<select id="selectByDeviceCodeAndDateRange" resultMap="BaseResultMap">
SELECT * FROM acs_agv_daily_statistics
WHERE device_code = #{deviceCode}
AND work_date BETWEEN #{startDate} AND #{endDate}
ORDER BY work_date
</select>
</mapper>

View File

@@ -0,0 +1,17 @@
package org.nl.acs.agv_usage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.nl.acs.agv_usage.domain.AcsAgvUsageRecord;
import java.util.Date;
import java.util.List;
@Mapper
public interface AcsAgvUsageRecordMapper extends BaseMapper<AcsAgvUsageRecord> {
AcsAgvUsageRecord selectActiveByDeviceCode(@Param("deviceCode") String deviceCode);
List<AcsAgvUsageRecord> selectByDeviceCodeAndDate(@Param("deviceCode") String deviceCode, @Param("workDate") Date workDate);
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.nl.acs.agv_usage.mapper.AcsAgvUsageRecordMapper">
<resultMap id="BaseResultMap" type="org.nl.acs.agv_usage.domain.AcsAgvUsageRecord">
<id column="id" property="id"/>
<result column="device_code" property="device_code"/>
<result column="device_name" property="device_name"/>
<result column="task_code" property="task_code"/>
<result column="instruction_code" property="instruction_code"/>
<result column="start_time" property="start_time"/>
<result column="end_time" property="end_time"/>
<result column="work_duration" property="work_duration"/>
<result column="work_date" property="work_date"/>
<result column="status" property="status"/>
<result column="create_time" property="create_time"/>
<result column="update_time" property="update_time"/>
<result column="is_active" property="is_active"/>
</resultMap>
<select id="selectActiveByDeviceCode" resultMap="BaseResultMap">
SELECT * FROM acs_agv_usage_record
WHERE device_code = #{deviceCode}
AND status = '0'
AND is_active = '1'
ORDER BY start_time DESC
LIMIT 1
</select>
<select id="selectByDeviceCodeAndDate" resultMap="BaseResultMap">
SELECT * FROM acs_agv_usage_record
WHERE device_code = #{deviceCode}
AND work_date = #{workDate}
AND is_active = '1'
ORDER BY start_time ASC
</select>
</mapper>

View File

@@ -0,0 +1,86 @@
package org.nl.acs.agv_usage.rest;
import cn.dev33.satoken.annotation.SaIgnore;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics;
import org.nl.acs.agv_usage.service.AcsAgvUsageService;
import org.nl.common.logging.annotation.Log;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequiredArgsConstructor
@Api(tags = "AGV使用率统计接口")
@RequestMapping("/api/agv_usage")
@SaIgnore
public class AgvUsageController {
private final AcsAgvUsageService agvUsageService;
@GetMapping("/statistics")
@Log("获取AGV使用率统计")
@ApiOperation("获取AGV使用率统计")
public ResponseEntity<Object> getStatistics(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
List<Map<String, Object>> result = agvUsageService.getAgvUsageRate(startDate, endDate);
return new ResponseEntity<>(result, HttpStatus.OK);
}
@GetMapping("/statistics/detail")
@Log("获取AGV详细统计")
@ApiOperation("获取AGV详细统计")
public ResponseEntity<Object> getStatisticsDetail(
@RequestParam(required = false) String deviceCode,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
List<AcsAgvDailyStatistics> result;
if (deviceCode != null && !deviceCode.isEmpty()) {
result = agvUsageService.getStatisticsByDeviceAndDateRange(deviceCode, startDate, endDate);
} else {
result = agvUsageService.getStatisticsByDateRange(startDate, endDate);
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
@GetMapping("/realtime")
@Log("获取AGV实时状态")
@ApiOperation("获取AGV实时状态")
public ResponseEntity<Object> getRealtimeStatus() {
Map<String, Object> result = agvUsageService.getAgvRealtimeStatus();
return new ResponseEntity<>(result, HttpStatus.OK);
}
@PostMapping("/start")
@Log("AGV开始工作")
@ApiOperation("AGV开始工作")
public ResponseEntity<Object> startWork(
@RequestParam String deviceCode,
@RequestParam(required = false) String deviceName,
@RequestParam(required = false) String taskCode,
@RequestParam(required = false) String instructionCode) {
agvUsageService.startWork(deviceCode, deviceName, taskCode, instructionCode);
return new ResponseEntity<>("success", HttpStatus.OK);
}
@PostMapping("/end")
@Log("AGV结束工作")
@ApiOperation("AGV结束工作")
public ResponseEntity<Object> endWork(
@RequestParam String deviceCode,
@RequestParam(required = false) String taskCode,
@RequestParam(required = false) String instructionCode) {
agvUsageService.endWork(deviceCode, taskCode, instructionCode);
return new ResponseEntity<>("success", HttpStatus.OK);
}
}

View File

@@ -0,0 +1,25 @@
package org.nl.acs.agv_usage.service;
import org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics;
import org.nl.acs.agv_usage.domain.AcsAgvUsageRecord;
import java.util.Date;
import java.util.List;
import java.util.Map;
public interface AcsAgvUsageService {
void startWork(String deviceCode, String deviceName, String taskCode, String instructionCode);
void endWork(String deviceCode, String taskCode, String instructionCode);
void updateDailyStatistics(String deviceCode, Date workDate);
List<AcsAgvDailyStatistics> getStatisticsByDateRange(Date startDate, Date endDate);
List<AcsAgvDailyStatistics> getStatisticsByDeviceAndDateRange(String deviceCode, Date startDate, Date endDate);
List<Map<String, Object>> getAgvUsageRate(Date startDate, Date endDate);
Map<String, Object> getAgvRealtimeStatus();
}

View File

@@ -0,0 +1,221 @@
package org.nl.acs.agv_usage.service.impl;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics;
import org.nl.acs.agv_usage.domain.AcsAgvUsageRecord;
import org.nl.acs.agv_usage.mapper.AcsAgvDailyStatisticsMapper;
import org.nl.acs.agv_usage.mapper.AcsAgvUsageRecordMapper;
import org.nl.acs.agv_usage.service.AcsAgvUsageService;
import org.nl.acs.device.domain.Device;
import org.nl.acs.device.service.DeviceService;
import org.nl.acs.opc.DeviceAppService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
@Slf4j
@Service
@RequiredArgsConstructor
public class AcsAgvUsageServiceImpl implements AcsAgvUsageService {
private final AcsAgvUsageRecordMapper usageRecordMapper;
private final AcsAgvDailyStatisticsMapper dailyStatisticsMapper;
private final DeviceAppService deviceAppService;
private final DeviceService deviceService;
private static final long DAY_SECONDS = 24 * 60 * 60L;
private static final long WORK_DAY_SECONDS = 8 * 60 * 60L;
@Override
@Transactional(rollbackFor = Exception.class)
public void startWork(String deviceCode, String deviceName, String taskCode, String instructionCode) {
AcsAgvUsageRecord existingRecord = usageRecordMapper.selectActiveByDeviceCode(deviceCode);
if (existingRecord != null) {
log.warn("AGV {} 已有工作中的记录,先结束上一条记录", deviceCode);
endWork(deviceCode, existingRecord.getTask_code(), existingRecord.getInstruction_code());
}
AcsAgvUsageRecord record = new AcsAgvUsageRecord();
record.setId(IdUtil.simpleUUID());
record.setDevice_code(deviceCode);
record.setDevice_name(deviceName);
record.setTask_code(taskCode);
record.setInstruction_code(instructionCode);
record.setStart_time(new Date());
record.setWork_date(new Date());
record.setStatus("0");
record.setWork_duration(0L);
record.setIs_active("1");
record.setCreate_time(new Date());
record.setUpdate_time(new Date());
usageRecordMapper.insert(record);
log.info("AGV {} 开始工作,任务号: {}, 指令号: {}", deviceCode, taskCode, instructionCode);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void endWork(String deviceCode, String taskCode, String instructionCode) {
AcsAgvUsageRecord record = usageRecordMapper.selectActiveByDeviceCode(deviceCode);
if (record == null) {
log.warn("AGV {} 没有找到工作中的记录", deviceCode);
return;
}
Date endTime = new Date();
long workDuration = (endTime.getTime() - record.getStart_time().getTime()) / 1000;
record.setEnd_time(endTime);
record.setWork_duration(workDuration);
record.setStatus("1");
record.setUpdate_time(new Date());
usageRecordMapper.updateById(record);
updateDailyStatistics(deviceCode, record.getWork_date());
log.info("AGV {} 结束工作,任务号: {}, 工作时长: {}秒", deviceCode, taskCode, workDuration);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateDailyStatistics(String deviceCode, Date workDate) {
List<AcsAgvUsageRecord> records = usageRecordMapper.selectByDeviceCodeAndDate(deviceCode, workDate);
long totalWorkDuration = records.stream()
.filter(r -> "1".equals(r.getStatus()))
.mapToLong(AcsAgvUsageRecord::getWork_duration)
.sum();
int taskCount = (int) records.stream()
.filter(r -> "1".equals(r.getStatus()))
.count();
String deviceName = null;
Device device = deviceAppService.findDeviceByCode(deviceCode);
if (device != null) {
deviceName = device.getDevice_name();
}
AcsAgvDailyStatistics existingStats = dailyStatisticsMapper.selectByDeviceCodeAndDate(deviceCode, workDate);
long totalIdleDuration = DAY_SECONDS - totalWorkDuration;
if (totalIdleDuration < 0) {
totalIdleDuration = 0;
}
BigDecimal usageRate = BigDecimal.valueOf(totalWorkDuration)
.divide(BigDecimal.valueOf(DAY_SECONDS), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.setScale(2, RoundingMode.HALF_UP);
if (existingStats != null) {
existingStats.setTotal_work_duration(totalWorkDuration);
existingStats.setTotal_idle_duration(totalIdleDuration);
existingStats.setUsage_rate(usageRate);
existingStats.setTask_count(taskCount);
existingStats.setUpdate_time(new Date());
dailyStatisticsMapper.updateById(existingStats);
} else {
AcsAgvDailyStatistics newStats = new AcsAgvDailyStatistics();
newStats.setId(IdUtil.simpleUUID());
newStats.setDevice_code(deviceCode);
newStats.setDevice_name(deviceName);
newStats.setWork_date(workDate);
newStats.setTotal_work_duration(totalWorkDuration);
newStats.setTotal_idle_duration(totalIdleDuration);
newStats.setUsage_rate(usageRate);
newStats.setTask_count(taskCount);
newStats.setCreate_time(new Date());
newStats.setUpdate_time(new Date());
dailyStatisticsMapper.insert(newStats);
}
}
@Override
public List<AcsAgvDailyStatistics> getStatisticsByDateRange(Date startDate, Date endDate) {
return dailyStatisticsMapper.selectByDateRange(startDate, endDate);
}
@Override
public List<AcsAgvDailyStatistics> getStatisticsByDeviceAndDateRange(String deviceCode, Date startDate, Date endDate) {
return dailyStatisticsMapper.selectByDeviceCodeAndDateRange(deviceCode, startDate, endDate);
}
@Override
public List<Map<String, Object>> getAgvUsageRate(Date startDate, Date endDate) {
List<AcsAgvDailyStatistics> statistics = getStatisticsByDateRange(startDate, endDate);
Map<String, Map<String, Object>> deviceStatsMap = new LinkedHashMap<>();
for (AcsAgvDailyStatistics stat : statistics) {
String deviceCode = stat.getDevice_code();
if (!deviceStatsMap.containsKey(deviceCode)) {
Map<String, Object> deviceData = new HashMap<>();
deviceData.put("deviceCode", deviceCode);
deviceData.put("deviceName", stat.getDevice_name());
deviceData.put("totalWorkDuration", 0L);
deviceData.put("totalTaskCount", 0);
deviceData.put("avgUsageRate", BigDecimal.ZERO);
deviceData.put("dayCount", 0);
deviceData.put("usageRateSum", BigDecimal.ZERO);
deviceStatsMap.put(deviceCode, deviceData);
}
Map<String, Object> deviceData = deviceStatsMap.get(deviceCode);
deviceData.put("totalWorkDuration", (Long) deviceData.get("totalWorkDuration") + stat.getTotal_work_duration());
deviceData.put("totalTaskCount", (Integer) deviceData.get("totalTaskCount") + stat.getTask_count());
deviceData.put("dayCount", (Integer) deviceData.get("dayCount") + 1);
deviceData.put("usageRateSum", ((BigDecimal) deviceData.get("usageRateSum")).add(stat.getUsage_rate()));
}
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> deviceData : deviceStatsMap.values()) {
int dayCount = (Integer) deviceData.get("dayCount");
if (dayCount > 0) {
BigDecimal avgUsageRate = ((BigDecimal) deviceData.get("usageRateSum"))
.divide(BigDecimal.valueOf(dayCount), 2, RoundingMode.HALF_UP);
deviceData.put("avgUsageRate", avgUsageRate);
}
deviceData.remove("usageRateSum");
deviceData.remove("dayCount");
result.add(deviceData);
}
return result;
}
@Override
public Map<String, Object> getAgvRealtimeStatus() {
Map<String, Object> result = new HashMap<>();
LambdaQueryWrapper<AcsAgvUsageRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AcsAgvUsageRecord::getStatus, "0")
.eq(AcsAgvUsageRecord::getIs_active, "1");
List<AcsAgvUsageRecord> workingRecords = usageRecordMapper.selectList(wrapper);
List<Map<String, Object>> workingAgvList = new ArrayList<>();
for (AcsAgvUsageRecord record : workingRecords) {
Map<String, Object> agvStatus = new HashMap<>();
agvStatus.put("deviceCode", record.getDevice_code());
agvStatus.put("deviceName", record.getDevice_name());
agvStatus.put("taskCode", record.getTask_code());
agvStatus.put("startTime", record.getStart_time());
agvStatus.put("currentDuration", (System.currentTimeMillis() - record.getStart_time().getTime()) / 1000);
workingAgvList.add(agvStatus);
}
result.put("workingAgvList", workingAgvList);
result.put("workingCount", workingAgvList.size());
return result;
}
}

View File

@@ -45,6 +45,7 @@ import org.nl.system.service.dict.dao.Dict;
import org.nl.system.service.param.ISysParamService;
import org.nl.config.SpringContextHolder;
import org.nl.system.service.param.impl.SysParamServiceImpl;
import org.nl.acs.agv_usage.service.AcsAgvUsageService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
@@ -70,6 +71,7 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
DeviceAppService deviceAppService = SpringContextHolder.getBean(DeviceAppService.class);
DeviceService deviceService = SpringContextHolder.getBean(DeviceService.class);
ISysDictService dictService = SpringContextHolder.getBean(ISysDictService.class);
AcsAgvUsageService agvUsageService = SpringContextHolder.getBean(AcsAgvUsageService.class);
int agvaddr = 0;
int agvaddr_copy = 0;
@@ -155,6 +157,19 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
transportOrder = inst.getTask_code();
logServer.deviceExecuteLog(this.device_code, "", "", "agvphase" + "反馈:" + data);
// AGV使用率统计 - 开始工作
try {
String agvDeviceCode = "agv" + carno;
String agvDeviceName = this.device_code;
Device agvDevice = deviceAppService.findDeviceByCode(agvDeviceCode);
if (agvDevice != null) {
agvDeviceName = agvDevice.getDevice_name();
}
agvUsageService.startWork(agvDeviceCode, agvDeviceName, inst.getTask_code(), inst.getInstruction_code());
} catch (Exception e) {
log.error("AGV使用率统计-开始工作失败: {}", e.getMessage());
}
//到达取货点
//(需要WCS反馈)
} else if (phase == 0x03) {
@@ -463,6 +478,17 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
return;
}
transportOrder = "";
// AGV使用率统计 - 结束工作
try {
String agvDeviceCode = "agv" + inst.getCarno();
if (StrUtil.isNotEmpty(agvDeviceCode)) {
agvUsageService.endWork(agvDeviceCode, inst.getTask_code(), inst.getInstruction_code());
}
} catch (Exception e) {
log.error("AGV使用率统计-结束工作失败: {}", e.getMessage());
}
if (device.getDeviceDriver() instanceof PlcToAgvDeviceDriver) {
PlcToAgvDeviceDriver plcToAgvDeviceDriver = (PlcToAgvDeviceDriver) device.getDeviceDriver();
if (device_code.contains("_01")) {

View File

@@ -425,11 +425,10 @@ public class HandheldServiceImpl implements HandheldService {
//是否需要从日志表重新入库的标志
Boolean reentry_flag = param.getBoolean("reentry_flag");
reentry_flag = true;
//如果当前点位是SD01点位且当前载具在组盘表中不存在
List<Map> groups = iSchBaseVehiclematerialgroupService.selectGroupByVehicleCode(param.getString("vehicle_code"));
if (reentry_flag!= null && reentry_flag && "SD01".equals(device_code) && CollectionUtil.isEmpty(groups) ) {
if (reentry_flag!= null && reentry_flag && "SD01".equals(device_code)) {
List<Dict> dictList = dictService.getDictByName("sd01_interval_hour");
if(CollectionUtil.isNotEmpty(dictList)){
@@ -445,15 +444,13 @@ public class HandheldServiceImpl implements HandheldService {
.apply("point_code = {0}", device_code)
// 3. 核心新增:筛选 hour 小时内删除的数据
.apply("delete_time >= DATE_SUB(NOW(), INTERVAL {0} HOUR)", hour)
.orderByDesc(SchBaseVehiclematerialgroupDeleteLog::getDelete_time)
.last("LIMIT 1"));
.orderByDesc(SchBaseVehiclematerialgroupDeleteLog::getDelete_time));
//则自动将log表中的数据库插入到组盘表中
if(CollectionUtil.isNotEmpty(logList)){
SchBaseVehiclematerialgroupDeleteLog log = logList.get(0);
SchBaseVehiclematerialgroup group = new SchBaseVehiclematerialgroup();
BeanUtils.copyProperties(log,group);
iSchBaseVehiclematerialgroupService.save(group);
for(SchBaseVehiclematerialgroupDeleteLog log : logList) {
iSchBaseVehiclematerialgroupDeleteLogService.removeById(log.getGroup_id());
}
}
}
}
@@ -482,7 +479,7 @@ public class HandheldServiceImpl implements HandheldService {
@Override
public SD01GroupLogRespondDto getSD01GroupLog(JSONObject param) {
String device_code = param.getString("device_code");
String device_code = param.getString("pointCode");
String vehicle_code = param.getString("vehicle_code");
if(!"SD01".equals(device_code) || StringUtils.isBlank(vehicle_code)){
@@ -510,23 +507,21 @@ public class HandheldServiceImpl implements HandheldService {
.apply("point_code = {0}", device_code)
// 3. 核心新增:筛选 hour 小时内删除的数据
.apply("delete_time >= DATE_SUB(NOW(), INTERVAL {0} HOUR)", hour)
.orderByDesc(SchBaseVehiclematerialgroupDeleteLog::getDelete_time)
.last("LIMIT 1"));
.orderByDesc(SchBaseVehiclematerialgroupDeleteLog::getDelete_time));
if(CollectionUtil.isEmpty(logList)){
return null;
}
SD01GroupLogRespondDto sd01GroupLogRespondDto = new SD01GroupLogRespondDto();
LogMaterialDto logMaterialDto = new LogMaterialDto();
logMaterialDto.setMaterial_qty(logList.get(0).getMaterial_qty());
logMaterialDto.setMaterial_code(logList.get(0).getMaterial_id());
logMaterialDto.setDue_date(logList.get(0).getDue_date());
logMaterialDto.setOrder_code(logList.get(0).getOrder_code());
List<LogMaterialDto> logMaterialDtos = new ArrayList<>();
SD01GroupLogRespondDto sd01GroupLogRespondDto = new SD01GroupLogRespondDto();
for (SchBaseVehiclematerialgroupDeleteLog log : logList) {
LogMaterialDto logMaterialDto = new LogMaterialDto();
logMaterialDto.setMaterial_qty(log.getMaterial_qty());
logMaterialDto.setMaterial_code(log.getMaterial_id());
logMaterialDto.setDue_date(log.getDue_date());
logMaterialDto.setOrder_code(log.getOrder_code());
logMaterialDtos.add(logMaterialDto);
}
sd01GroupLogRespondDto.setMaterial(logMaterialDtos);
sd01GroupLogRespondDto.setDevice_code(device_code);

View File

@@ -0,0 +1,63 @@
package org.nl.wms.sch.agv_usage.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.nl.common.logging.annotation.Log;
import org.nl.wms.sch.agv_usage.service.IAgvUsageService;
import org.nl.wms.sch.agv_usage.service.dto.AgvUsageQuery;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequiredArgsConstructor
@Api(tags = "AGV使用率统计")
@RequestMapping("/api/agv_usage")
public class AgvUsageController {
private final IAgvUsageService agvUsageService;
@GetMapping("/statistics")
@Log("获取AGV使用率统计")
@ApiOperation("获取AGV使用率统计")
public ResponseEntity<Object> getStatistics(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
AgvUsageQuery query = new AgvUsageQuery();
query.setStartDate(startDate);
query.setEndDate(endDate);
List<Map<String, Object>> result = agvUsageService.getAgvUsageStatistics(query);
return new ResponseEntity<>(result, HttpStatus.OK);
}
@GetMapping("/statistics/detail")
@Log("获取AGV详细统计")
@ApiOperation("获取AGV详细统计")
public ResponseEntity<Object> getStatisticsDetail(
@RequestParam(required = false) String deviceCode,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
AgvUsageQuery query = new AgvUsageQuery();
query.setDeviceCode(deviceCode);
query.setStartDate(startDate);
query.setEndDate(endDate);
List<Map<String, Object>> result = agvUsageService.getAgvUsageDetail(query);
return new ResponseEntity<>(result, HttpStatus.OK);
}
@GetMapping("/today")
@Log("获取今日AGV使用率")
@ApiOperation("获取今日AGV使用率")
public ResponseEntity<Object> getTodayUsageRate() {
Map<String, Object> result = agvUsageService.getAgvUsageRateToday();
return new ResponseEntity<>(result, HttpStatus.OK);
}
}

View File

@@ -0,0 +1,18 @@
package org.nl.wms.sch.agv_usage.service;
import com.alibaba.fastjson.JSONObject;
import org.nl.wms.sch.agv_usage.service.dto.AgvUsageQuery;
import java.util.List;
import java.util.Map;
public interface IAgvUsageService {
List<Map<String, Object>> getAgvUsageStatistics(AgvUsageQuery query);
List<Map<String, Object>> getAgvUsageDetail(AgvUsageQuery query);
Map<String, Object> getAgvRealtimeStatus();
Map<String, Object> getAgvUsageRateToday();
}

View File

@@ -0,0 +1,15 @@
package org.nl.wms.sch.agv_usage.service.dto;
import lombok.Data;
import java.util.Date;
@Data
public class AgvUsageQuery {
private String deviceCode;
private Date startDate;
private Date endDate;
}

View File

@@ -0,0 +1,180 @@
package org.nl.wms.sch.agv_usage.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.nl.config.SpringContextHolder;
import org.nl.system.service.param.dao.Param;
import org.nl.system.service.param.impl.SysParamServiceImpl;
import org.nl.wms.sch.agv_usage.service.IAgvUsageService;
import org.nl.wms.sch.agv_usage.service.dto.AgvUsageQuery;
import org.nl.wms.sch.task_manage.GeneralDefinition;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.*;
@Slf4j
@Service
public class AgvUsageServiceImpl implements IAgvUsageService {
private String getAcsUrl() {
SysParamServiceImpl sysParamService = SpringContextHolder.getBean(SysParamServiceImpl.class);
Param acsUrlObj = sysParamService.findByCode(GeneralDefinition.ACS_URL);
if (ObjectUtil.isEmpty(acsUrlObj)) {
return null;
}
return acsUrlObj.getValue();
}
private boolean isConnectAcs() {
SysParamServiceImpl sysParamService = SpringContextHolder.getBean(SysParamServiceImpl.class);
Param isConnectAcs = sysParamService.findByCode(GeneralDefinition.IS_CONNECT_ACS);
if (ObjectUtil.isEmpty(isConnectAcs)) {
return false;
}
return StrUtil.equals(GeneralDefinition.YES, isConnectAcs.getValue());
}
@Override
public List<Map<String, Object>> getAgvUsageStatistics(AgvUsageQuery query) {
if (!isConnectAcs()) {
log.warn("未连接ACS系统");
return new ArrayList<>();
}
String acsUrl = getAcsUrl();
if (StrUtil.isEmpty(acsUrl)) {
log.warn("ACS地址未配置");
return new ArrayList<>();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String startDate = sdf.format(query.getStartDate());
String endDate = sdf.format(query.getEndDate());
String url = acsUrl + "api/agv_usage/statistics?startDate=" + startDate + "&endDate=" + endDate;
try {
String result = HttpRequest.get(url)
.setConnectionTimeout(5000)
.execute()
.body();
// 修复点1使用TypeReference指定泛型类型解决List<Map>和List<Map<String, Object>>不兼容问题
return JSON.parseObject(result, new TypeReference<List<Map<String, Object>>>() {});
} catch (Exception e) {
log.error("调用ACS获取AGV使用率统计失败: {}", e.getMessage());
return new ArrayList<>();
}
}
@Override
public List<Map<String, Object>> getAgvUsageDetail(AgvUsageQuery query) {
if (!isConnectAcs()) {
log.warn("未连接ACS系统");
return new ArrayList<>();
}
String acsUrl = getAcsUrl();
if (StrUtil.isEmpty(acsUrl)) {
log.warn("ACS地址未配置");
return new ArrayList<>();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String startDate = sdf.format(query.getStartDate());
String endDate = sdf.format(query.getEndDate());
StringBuilder urlBuilder = new StringBuilder(acsUrl)
.append("/api/agv_usage/statistics/detail?startDate=")
.append(startDate)
.append("&endDate=")
.append(endDate);
if (StrUtil.isNotEmpty(query.getDeviceCode())) {
urlBuilder.append("&deviceCode=").append(query.getDeviceCode());
}
try {
String result = HttpRequest.get(urlBuilder.toString())
.setConnectionTimeout(5000)
.execute()
.body();
// 修复点2同上统一使用TypeReference保证泛型类型匹配
return JSON.parseObject(result, new TypeReference<List<Map<String, Object>>>() {});
} catch (Exception e) {
log.error("调用ACS获取AGV详细统计失败: {}", e.getMessage());
return new ArrayList<>();
}
}
@Override
public Map<String, Object> getAgvRealtimeStatus() {
Map<String, Object> result = new HashMap<>();
if (!isConnectAcs()) {
log.warn("未连接ACS系统");
result.put("workingAgvList", new ArrayList<>());
result.put("workingCount", 0);
return result;
}
String acsUrl = getAcsUrl();
if (StrUtil.isEmpty(acsUrl)) {
log.warn("ACS地址未配置");
result.put("workingAgvList", new ArrayList<>());
result.put("workingCount", 0);
return result;
}
String url = acsUrl + "api/agv_usage/realtime";
try {
String response = HttpRequest.get(url)
.setConnectionTimeout(5000)
.execute()
.body();
// 修复点3使用TypeReference指定Map<String, Object>移除不必要的SuppressWarnings
Map<String, Object> map = JSON.parseObject(response, new TypeReference<Map<String, Object>>() {});
return map != null ? map : result;
} catch (Exception e) {
log.error("调用ACS获取AGV实时状态失败: {}", e.getMessage());
result.put("workingAgvList", new ArrayList<>());
result.put("workingCount", 0);
return result;
}
}
@Override
public Map<String, Object> getAgvUsageRateToday() {
Map<String, Object> result = new HashMap<>();
if (!isConnectAcs()) {
log.warn("未连接ACS系统");
return result;
}
String acsUrl = getAcsUrl();
if (StrUtil.isEmpty(acsUrl)) {
log.warn("ACS地址未配置");
return result;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String today = sdf.format(new Date());
String url = acsUrl + "api/agv_usage/statistics?startDate=" + today + "&endDate=" + today;
try {
String response = HttpRequest.get(url)
.setConnectionTimeout(5000)
.execute()
.body();
// 修复点4同上使用TypeReference保证类型匹配
return JSON.parseObject(response, new TypeReference<Map<String, Object>>() {});
} catch (Exception e) {
log.error("调用ACS获取今日AGV使用率失败: {}", e.getMessage());
}
return result;
}
}

View File

@@ -45,7 +45,6 @@ public class SchBaseVehiclematerialgroupDeleteLog implements Serializable {
@ApiModelProperty(value = "来源载具编码")
private String source_vehicle_code;
@TableField(exist = false)
@ApiModelProperty(value = "点位编码")
private String point_code;

View File

@@ -2,6 +2,7 @@ package org.nl.wms.sch.point.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
@@ -35,6 +36,10 @@ import org.nl.system.service.param.dao.Param;
import org.nl.wms.database.vehicle.service.IMdBaseVehicleService;
import org.nl.wms.database.vehicle.service.dao.MdBaseVehicle;
import org.nl.wms.ext.fab.service.dto.OrderMater;
import org.nl.wms.sch.group.service.ISchBaseVehiclematerialgroupService;
import org.nl.wms.sch.group.service.dao.SchBaseVehiclematerialgroup;
import org.nl.wms.sch.group_delete_log.service.ISchBaseVehiclematerialgroupDeleteLogService;
import org.nl.wms.sch.group_delete_log.service.dao.SchBaseVehiclematerialgroupDeleteLog;
import org.nl.wms.sch.point.service.ISchBasePointService;
import org.nl.wms.sch.point.service.dao.SchBasePoint;
import org.nl.wms.sch.point.service.dao.mapper.SchBasePointMapper;
@@ -47,6 +52,7 @@ import org.nl.wms.sch.task_manage.enums.NoticeTypeEnum;
import org.nl.wms.sch.task_manage.enums.PointStatusEnum;
import org.nl.wms.sch.task_manage.task.core.TaskStatus;
import org.nl.wms.util.PointUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
@@ -76,6 +82,11 @@ public class SchBasePointServiceImpl extends ServiceImpl<SchBasePointMapper, Sch
private IMdBaseVehicleService iMdBaseVehicleService;
@Autowired
private ISysParamService iSysParamService;
@Autowired
private ISchBaseVehiclematerialgroupService schBaseVehiclematerialgroupService;
@Autowired
private ISchBaseVehiclematerialgroupDeleteLogService schBaseVehiclematerialgroupDeleteLogService;
@Autowired
private ISysDictService dictService;
@@ -188,6 +199,38 @@ public class SchBasePointServiceImpl extends ServiceImpl<SchBasePointMapper, Sch
String vehicle_code = entity.getVehicle_code();
// 根据点位状态来判断更新内容
if (ObjectUtil.isNotEmpty(pointStatus) && pointStatus.equals(GoodsEnum.OUT_OF_STOCK.getValue())) {
List<Dict> dictList = dictService.getDictByName("sd01_interval_hour");
if(CollectionUtil.isNotEmpty(dictList)) {
Dict dict = dictList.get(0);
if ("hour".equals(dict.getLabel())) {
String hour = dict.getValue();
//判断log中是否存在sd01 载具号=这个的单据
List<SchBaseVehiclematerialgroupDeleteLog> loglist = schBaseVehiclematerialgroupDeleteLogService.list(Wrappers.lambdaQuery(SchBaseVehiclematerialgroupDeleteLog.class)
// 1. 对数据库字段用Lambda + 字段名MyBatis-Plus会自动转下划线
.eq(SchBaseVehiclematerialgroupDeleteLog::getVehicle_code, vehicle_code)
// 2. 对非数据库字段(@TableField(exist=false)改用apply手动拼接
.apply("point_code = {0}", "SD01")
// 3. 核心新增:筛选 hour 小时内删除的数据
.apply("delete_time >= DATE_SUB(NOW(), INTERVAL {0} HOUR)", hour));
if (CollectionUtils.isEmpty(loglist)) {
//如果是设置为无货将组盘插入到日志表中默认point_code=SD01
List<SchBaseVehiclematerialgroup> schBaseVehiclematerialgroupList = schBaseVehiclematerialgroupService.list(Wrappers.lambdaQuery(SchBaseVehiclematerialgroup.class)
.eq(SchBaseVehiclematerialgroup::getVehicle_code, vehicle_code));
for (SchBaseVehiclematerialgroup schBaseVehiclematerialgroup : schBaseVehiclematerialgroupList) {
SchBaseVehiclematerialgroupDeleteLog deleteLog = new SchBaseVehiclematerialgroupDeleteLog();
BeanUtils.copyProperties(schBaseVehiclematerialgroup, deleteLog);
deleteLog.setDelete_time(DateUtil.now());
deleteLog.setPoint_code("SD01");
schBaseVehiclematerialgroupDeleteLogService.save(deleteLog);
}
}
}
}
vehicle_code = null;
} else if (ObjectUtil.isNotEmpty(pointStatus) && (pointStatus.equals(GoodsEnum.EMPTY_PALLETS.getValue()) || pointStatus.equals(GoodsEnum.IN_STOCK.getValue()))) {
if (StrUtil.isEmpty(entity.getVehicle_code())) throw new BadRequestException("载具编码不能为空");
@@ -195,6 +238,7 @@ public class SchBasePointServiceImpl extends ServiceImpl<SchBasePointMapper, Sch
pointMapper.update(entity, Wrappers.lambdaUpdate(SchBasePoint.class)
.eq(SchBasePoint::getPoint_code, entity.getPoint_code())
.set(SchBasePoint::getVehicle_code, vehicle_code));
}
@Override

View File

@@ -0,0 +1,33 @@
import request from '@/utils/request'
export function getAgvUsageStatistics(params) {
return request({
url: 'api/agv_usage/statistics',
method: 'get',
params
})
}
export function getAgvUsageDetail(params) {
return request({
url: 'api/agv_usage/statistics/detail',
method: 'get',
params
})
}
export function getAgvRealtimeStatus() {
return request({
url: 'api/agv_usage/realtime',
method: 'get'
})
}
export function getTodayUsageRate() {
return request({
url: 'api/agv_usage/today',
method: 'get'
})
}
export default { getAgvUsageStatistics, getAgvUsageDetail, getAgvRealtimeStatus, getTodayUsageRate }

View File

@@ -0,0 +1,216 @@
<template>
<div class="app-container">
<div class="head-container">
<el-form ref="queryForm" :inline="true" :model="query" size="small">
<el-form-item label="开始日期">
<el-date-picker
v-model="query.startDate"
type="date"
placeholder="选择开始日期"
style="width: 150px"
/>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker
v-model="query.endDate"
type="date"
placeholder="选择结束日期"
style="width: 150px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="toQuery">搜索</el-button>
<el-button type="info" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<el-row :gutter="20">
<el-col :span="24">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>AGV使用率统计</span>
</div>
<el-table
v-loading="loading"
:data="tableData"
border
style="width: 100%"
size="small"
>
<el-table-column prop="deviceCode" label="AGV编码" width="120" />
<el-table-column prop="deviceName" label="AGV名称" width="150" />
<el-table-column prop="totalWorkDuration" label="总工作时长(秒)" width="120">
<template slot-scope="scope">
{{ formatDuration(scope.row.totalWorkDuration) }}
</template>
</el-table-column>
<el-table-column prop="totalTaskCount" label="任务数量" width="100" />
<el-table-column prop="avgUsageRate" label="平均使用率(%)">
<template slot-scope="scope">
<el-progress
:percentage="scope.row.avgUsageRate || 0"
:color="getProgressColor(scope.row.avgUsageRate)"
/>
</template>
</el-table-column>
<!-- <el-table-column label="操作" width="100">
<template slot-scope="scope">
<el-button type="text" size="small" @click="showDetail(scope.row)">详情</el-button>
</template>
</el-table-column>-->
</el-table>
</el-card>
</el-col>
</el-row>
<el-dialog
title="AGV使用详情"
:visible.sync="detailVisible"
width="70%"
>
<el-table
v-loading="detailLoading"
:data="detailData"
border
style="width: 100%"
size="small"
>
<el-table-column prop="deviceCode" label="AGV编码" width="120" />
<el-table-column prop="deviceName" label="AGV名称" width="150" />
<el-table-column prop="workDate" label="日期" width="120" />
<el-table-column prop="totalWorkDuration" label="工作时长(秒)" width="120">
<template slot-scope="scope">
{{ formatDuration(scope.row.totalWorkDuration) }}
</template>
</el-table-column>
<el-table-column prop="totalIdleDuration" label="空闲时长(秒)" width="120">
<template slot-scope="scope">
{{ formatDuration(scope.row.totalIdleDuration) }}
</template>
</el-table-column>
<el-table-column prop="taskCount" label="任务数量" width="100" />
<el-table-column prop="usageRate" label="使用率(%)">
<template slot-scope="scope">
<el-progress
:percentage="scope.row.usageRate || 0"
:color="getProgressColor(scope.row.usageRate)"
/>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import crudAgvUsage from './agvUsage'
import { format } from 'date-fns'
export default {
name: 'AgvUsage',
data() {
return {
loading: false,
detailLoading: false,
detailVisible: false,
query: {
startDate: new Date(),
endDate: new Date()
},
tableData: [],
detailData: [],
currentDeviceCode: null
}
},
created() {
this.toQuery()
},
methods: {
toQuery() {
this.loading = true
const startDate = this.formatDate(this.query.startDate)
const endDate = this.formatDate(this.query.endDate)
const queryParams = {
startDate: startDate,
endDate: endDate
}
crudAgvUsage.getAgvUsageStatistics(queryParams).then(res => {
this.tableData = res || []
this.loading = false
}).catch(() => {
this.loading = false
})
},
resetQuery() {
this.query = {
startDate: new Date(),
endDate: new Date()
}
this.toQuery()
},
showDetail(row) {
this.currentDeviceCode = row.deviceCode
this.detailVisible = true
this.detailLoading = true
const params = {
deviceCode: row.deviceCode,
startDate: this.formatDate(this.query.startDate),
endDate: this.formatDate(this.query.endDate)
}
crudAgvUsage.getAgvUsageDetail(params).then(res => {
this.detailData = res || []
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
},
formatDuration(seconds) {
if (!seconds) return '0秒'
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
let result = ''
if (hours > 0) {
result += hours + '小时'
}
if (minutes > 0) {
result += minutes + '分钟'
}
if (secs > 0 || result === '') {
result += secs + '秒'
}
return result
},
getProgressColor(percentage) {
if (percentage >= 80) {
return '#67c23a'
} else if (percentage >= 50) {
return '#e6a23c'
} else {
return '#f56c6c'
}
},
formatDate(date) {
if (!date) return ''
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.head-container {
margin-bottom: 20px;
}
.box-card {
margin-bottom: 20px;
}
</style>