package com.ruoyi.energymeter.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.energymeter.constant.EnergyMeterConstants;
|
import com.ruoyi.energymeter.dto.EnergyMeterDeviceQuery;
|
import com.ruoyi.energymeter.dto.MeterArchive;
|
import com.ruoyi.energymeter.dto.MeterBindRequest;
|
import com.ruoyi.energymeter.dto.MeterManualBindRequest;
|
import com.ruoyi.energymeter.dto.MeterReadingRequest;
|
import com.ruoyi.energymeter.dto.MeterUpdateRequest;
|
import com.ruoyi.energymeter.dto.MeterUsageRow;
|
import com.ruoyi.energymeter.mapper.EnergyMeterDeviceMapper;
|
import com.ruoyi.energymeter.mapper.EnergyMeterMeasurementMapper;
|
import com.ruoyi.energymeter.mapper.EnergyMeterSyncTaskMapper;
|
import com.ruoyi.energymeter.pojo.EnergyMeterDevice;
|
import com.ruoyi.energymeter.pojo.EnergyMeterMeasurement;
|
import com.ruoyi.energymeter.pojo.EnergyMeterSyncTask;
|
import com.ruoyi.energymeter.provider.MeterProviderClient;
|
import com.ruoyi.energymeter.service.EnergyMeterDeviceService;
|
import com.ruoyi.energymeter.service.EnergyMeterSyncService;
|
import com.ruoyi.energymeter.util.EnergyMeterSupport;
|
import com.ruoyi.energymeter.vo.MeterArchiveVO;
|
import com.ruoyi.energymeter.vo.MeterReadingResultVO;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
/**
|
* 能耗表设备服务实现:第三方接入设备 + 手工抄表设备。
|
*/
|
@Slf4j
|
@Service
|
@RequiredArgsConstructor
|
public class EnergyMeterDeviceServiceImpl extends ServiceImpl<EnergyMeterDeviceMapper, EnergyMeterDevice>
|
implements EnergyMeterDeviceService {
|
|
private final EnergyMeterDeviceMapper deviceMapper;
|
private final EnergyMeterMeasurementMapper measurementMapper;
|
private final EnergyMeterSyncTaskMapper taskMapper;
|
private final MeterProviderClient provider;
|
private final EnergyMeterSyncService syncService;
|
|
@Override
|
public IPage<EnergyMeterDevice> pageDevices(Page<EnergyMeterDevice> page, EnergyMeterDeviceQuery query) {
|
LambdaQueryWrapper<EnergyMeterDevice> wrapper = new LambdaQueryWrapper<>();
|
Long tenantId = EnergyMeterSupport.currentTenantId();
|
if (tenantId != null) {
|
wrapper.eq(EnergyMeterDevice::getTenantId, tenantId);
|
}
|
if (query != null) {
|
if (StringUtils.isNotBlank(query.getMeterType())) {
|
wrapper.eq(EnergyMeterDevice::getMeterType, normalizeType(query.getMeterType()));
|
}
|
if (StringUtils.isNotBlank(query.getSourceType())) {
|
wrapper.eq(EnergyMeterDevice::getSourceType, query.getSourceType().trim().toUpperCase());
|
}
|
if (StringUtils.isNotBlank(query.getMeterAddress())) {
|
wrapper.eq(EnergyMeterDevice::getMeterAddress, query.getMeterAddress().trim());
|
}
|
if (query.getEnabled() != null) {
|
wrapper.eq(EnergyMeterDevice::getEnabled, query.getEnabled());
|
}
|
if (query.getSimulated() != null) {
|
wrapper.eq(EnergyMeterDevice::getSimulated, query.getSimulated());
|
}
|
if (StringUtils.isNotBlank(query.getCollectionMode())) {
|
String mode = query.getCollectionMode().trim().toUpperCase();
|
if ("AUTO".equals(mode)) {
|
// 第三方接入 + 系统每日自动生成,都属于自动采集
|
wrapper.and(w -> w.eq(EnergyMeterDevice::getSourceType,
|
EnergyMeterConstants.SOURCE_TYPE_PROVIDER)
|
.or().eq(EnergyMeterDevice::getSimulated, 1));
|
} else if ("MANUAL".equals(mode)) {
|
wrapper.eq(EnergyMeterDevice::getSourceType, EnergyMeterConstants.SOURCE_TYPE_MANUAL)
|
.eq(EnergyMeterDevice::getSimulated, 0);
|
}
|
}
|
if (StringUtils.isNotBlank(query.getBindStatus())) {
|
wrapper.eq(EnergyMeterDevice::getBindStatus, query.getBindStatus());
|
}
|
if (StringUtils.isNotBlank(query.getKeyword())) {
|
String keyword = query.getKeyword().trim();
|
wrapper.and(w -> w.like(EnergyMeterDevice::getDisplayName, keyword)
|
.or().like(EnergyMeterDevice::getProviderName, keyword)
|
.or().like(EnergyMeterDevice::getMeterAddress, keyword));
|
}
|
}
|
wrapper.orderByAsc(EnergyMeterDevice::getMeterType)
|
.orderByDesc(EnergyMeterDevice::getIsSeed)
|
.orderByAsc(EnergyMeterDevice::getId);
|
IPage<EnergyMeterDevice> result = deviceMapper.selectPage(page, wrapper);
|
enrichDevices(result.getRecords());
|
return result;
|
}
|
|
@Override
|
public List<EnergyMeterDevice> listDeviceOptions(String meterType) {
|
LambdaQueryWrapper<EnergyMeterDevice> wrapper = new LambdaQueryWrapper<>();
|
Long tenantId = EnergyMeterSupport.currentTenantId();
|
if (tenantId != null) {
|
wrapper.eq(EnergyMeterDevice::getTenantId, tenantId);
|
}
|
if (StringUtils.isNotBlank(meterType)) {
|
wrapper.eq(EnergyMeterDevice::getMeterType, normalizeType(meterType));
|
}
|
wrapper.eq(EnergyMeterDevice::getEnabled, 1)
|
.eq(EnergyMeterDevice::getBindStatus, EnergyMeterConstants.BIND_VERIFIED)
|
.orderByAsc(EnergyMeterDevice::getId);
|
return deviceMapper.selectList(wrapper);
|
}
|
|
@Override
|
public MeterArchiveVO verifyArchive(String meterType, String meterAddress) {
|
String normalizedType = normalizeType(meterType);
|
String address = StringUtils.trim(meterAddress);
|
if (StringUtils.isBlank(address)) {
|
throw new ServiceException("通信地址不能为空");
|
}
|
MeterArchive archive = provider.queryArchive(normalizedType, address);
|
MeterArchiveVO vo = new MeterArchiveVO();
|
vo.setMeterType(normalizedType);
|
vo.setMeterAddress(address);
|
vo.setProviderMeterId(archive.getProviderMeterId());
|
vo.setProviderName(archive.getProviderName());
|
vo.setAreaId(archive.getAreaId());
|
vo.setAreaName(archive.getAreaName());
|
vo.setOnlineStatus(archive.getOnlineStatus());
|
vo.setParentProviderId(archive.getParentProviderId());
|
vo.setMockMode(provider.mockMode());
|
|
EnergyMeterDevice existing = findBinding(normalizedType, address);
|
if (existing == null) {
|
vo.setBindable(true);
|
vo.setMessage("验证通过,可新增绑定");
|
} else {
|
vo.setExistingDeviceId(existing.getId());
|
vo.setExistingEnabled(Integer.valueOf(1).equals(existing.getEnabled()));
|
boolean enabled = Integer.valueOf(1).equals(existing.getEnabled());
|
vo.setBindable(!enabled);
|
vo.setMessage(enabled
|
? "该通信地址已在本地绑定,请勿重复新增"
|
: "该地址已存在停用设备,确认后将重新启用并补采停用期间数据");
|
}
|
return vo;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public EnergyMeterDevice bind(MeterBindRequest request) {
|
String normalizedType = normalizeType(request.getMeterType());
|
String address = StringUtils.trim(request.getMeterAddress());
|
if (StringUtils.isBlank(address)) {
|
throw new ServiceException("通信地址不能为空");
|
}
|
MeterArchive archive = provider.queryArchive(normalizedType, address);
|
EnergyMeterDevice existing = findBinding(normalizedType, address);
|
if (existing != null && Integer.valueOf(1).equals(existing.getEnabled())) {
|
throw new ServiceException("该通信地址已在本地绑定,请勿重复新增");
|
}
|
if (existing != null && EnergyMeterConstants.isManual(existing.getSourceType())) {
|
throw new ServiceException("该表号已用于手工抄表设备,请先删除原设备或改用其他表号");
|
}
|
LocalDate yesterday = EnergyMeterSupport.today().minusDays(1);
|
// 采集起点由系统自动确定:已停用设备沿用原起点,新设备默认上一自然年 1 月 1 日
|
LocalDate startDate = resolveStartDate(null,
|
existing != null ? existing.getSyncStartDate() : null, yesterday);
|
|
EnergyMeterDevice device = existing == null ? new EnergyMeterDevice() : existing;
|
device.setMeterType(normalizedType);
|
device.setSourceType(EnergyMeterConstants.SOURCE_TYPE_PROVIDER);
|
device.setMeterAddress(address);
|
device.setProviderMeterId(archive.getProviderMeterId());
|
device.setProviderName(archive.getProviderName());
|
device.setProviderAreaId(archive.getAreaId());
|
device.setProviderAreaName(archive.getAreaName());
|
device.setParentProviderId(archive.getParentProviderId());
|
device.setOnlineStatus(archive.getOnlineStatus());
|
device.setBindStatus(EnergyMeterConstants.BIND_VERIFIED);
|
device.setEnabled(1);
|
device.setSyncStartDate(startDate);
|
device.setDisplayName(StringUtils.isNotBlank(request.getDisplayName())
|
? request.getDisplayName().trim()
|
: StringUtils.defaultIfBlank(archive.getProviderName(), address));
|
device.setLocation(StringUtils.defaultIfBlank(request.getLocation(), device.getLocation()));
|
if (device.getIsSeed() == null) {
|
device.setIsSeed(0);
|
}
|
if (existing == null) {
|
deviceMapper.insert(device);
|
} else {
|
deviceMapper.updateById(device);
|
}
|
log.info("能耗表绑定成功 meterId={} type={} address={}", device.getId(), normalizedType, address);
|
|
try {
|
syncService.triggerBackfill(device, startDate, yesterday);
|
} catch (Exception e) {
|
log.warn("绑定成功但历史补采批次创建失败 meterId={}: {}", device.getId(), e.getMessage());
|
}
|
return device;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public EnergyMeterDevice addManual(MeterManualBindRequest request) {
|
String type = normalizeType(request.getMeterType());
|
String address = StringUtils.trim(request.getMeterAddress());
|
if (StringUtils.isBlank(address)) {
|
throw new ServiceException("表号不能为空");
|
}
|
if (request.getInitialReading() == null) {
|
throw new ServiceException("起始表码不能为空");
|
}
|
if (request.getInitialReading().signum() < 0) {
|
throw new ServiceException("起始表码不能为负数");
|
}
|
LocalDate today = EnergyMeterSupport.today();
|
LocalDate initialDate = request.getInitialReadingDate() == null ? today : request.getInitialReadingDate();
|
if (initialDate.isAfter(today)) {
|
throw new ServiceException("起始表码日期不能晚于今天");
|
}
|
|
EnergyMeterDevice existing = findBinding(type, address);
|
if (existing != null) {
|
if (Integer.valueOf(1).equals(existing.getEnabled())) {
|
throw new ServiceException("该表号已存在有效的" + EnergyMeterConstants.typeName(type) + "设备,请勿重复新增");
|
}
|
if (!EnergyMeterConstants.isManual(existing.getSourceType())) {
|
throw new ServiceException("该表号已被第三方接入设备占用,请先停用原设备或改用其他表号");
|
}
|
}
|
|
EnergyMeterDevice device = existing == null ? new EnergyMeterDevice() : existing;
|
device.setMeterType(type);
|
device.setSourceType(EnergyMeterConstants.SOURCE_TYPE_MANUAL);
|
device.setMeterAddress(address);
|
device.setDisplayName(StringUtils.isNotBlank(request.getDisplayName())
|
? request.getDisplayName().trim()
|
: address);
|
device.setLocation(StringUtils.defaultIfBlank(request.getLocation(), device.getLocation()));
|
device.setBindStatus(EnergyMeterConstants.BIND_VERIFIED);
|
device.setEnabled(1);
|
device.setIsSeed(0);
|
device.setInitialReading(request.getInitialReading());
|
device.setInitialReadingDate(initialDate);
|
device.setSyncStartDate(initialDate);
|
device.setOnlineStatus(null);
|
device.setLastErrorCode(null);
|
device.setLastErrorMessage(null);
|
if (existing == null) {
|
deviceMapper.insert(device);
|
} else {
|
deviceMapper.updateById(device);
|
}
|
|
// 起始表码作为基准记录写入,不计入用量
|
EnergyMeterMeasurement basePoint = newManualPoint(device, initialDate.atStartOfDay(),
|
request.getInitialReading(), "起始表码");
|
measurementMapper.upsertBatch(List.of(basePoint));
|
deviceMapper.markCollectSuccess(device.getId(), EnergyMeterSupport.now());
|
log.info("新增手工抄表设备 meterId={} type={} address={} initial={}",
|
device.getId(), type, address, request.getInitialReading());
|
return deviceMapper.selectById(device.getId());
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void removeDevice(Long id) {
|
EnergyMeterDevice device = getOwnedDevice(id);
|
if (Integer.valueOf(1).equals(device.getIsSeed())) {
|
throw new ServiceException("初始化设备不允许删除,如需停用请编辑设备状态");
|
}
|
if (!EnergyMeterConstants.isManual(device.getSourceType())) {
|
throw new ServiceException("第三方接入设备请使用“停用”,不允许删除");
|
}
|
measurementMapper.delete(new LambdaQueryWrapper<EnergyMeterMeasurement>()
|
.eq(EnergyMeterMeasurement::getMeterId, id));
|
removeById(id);
|
log.info("删除手工抄表设备 meterId={} address={}", id, device.getMeterAddress());
|
}
|
|
@Override
|
public IPage<EnergyMeterMeasurement> pageReadings(Long meterId, long current, long size) {
|
EnergyMeterDevice device = getOwnedDevice(meterId);
|
String readingMetric = EnergyMeterConstants.readingMetric(device.getMeterType());
|
String usageMetric = EnergyMeterConstants.usageMetric(device.getMeterType());
|
long safeSize = Math.min(Math.max(size, 1), 200);
|
Page<EnergyMeterMeasurement> page = new Page<>(Math.max(current, 1), safeSize);
|
LambdaQueryWrapper<EnergyMeterMeasurement> wrapper = new LambdaQueryWrapper<>();
|
wrapper.eq(EnergyMeterMeasurement::getMeterId, device.getId())
|
.eq(EnergyMeterMeasurement::getMetricCode, readingMetric)
|
.eq(EnergyMeterMeasurement::getGranularity, EnergyMeterConstants.GRANULARITY_POINT)
|
.orderByDesc(EnergyMeterMeasurement::getMeasureTime)
|
.orderByDesc(EnergyMeterMeasurement::getId);
|
IPage<EnergyMeterMeasurement> result = measurementMapper.selectPage(page, wrapper);
|
|
List<EnergyMeterMeasurement> records = result.getRecords();
|
if (!records.isEmpty()) {
|
LocalDateTime maxTime = records.get(0).getMeasureTime();
|
LocalDateTime minTime = records.get(records.size() - 1).getMeasureTime();
|
List<EnergyMeterMeasurement> usages = measurementMapper.selectList(
|
new LambdaQueryWrapper<EnergyMeterMeasurement>()
|
.eq(EnergyMeterMeasurement::getMeterId, device.getId())
|
.eq(EnergyMeterMeasurement::getMetricCode, usageMetric)
|
.eq(EnergyMeterMeasurement::getGranularity, EnergyMeterConstants.GRANULARITY_DAY)
|
.ge(EnergyMeterMeasurement::getMeasureTime, minTime)
|
.le(EnergyMeterMeasurement::getMeasureTime, maxTime));
|
Map<LocalDate, BigDecimal> usageMap = new HashMap<>();
|
for (EnergyMeterMeasurement usage : usages) {
|
usageMap.put(usage.getMeasureTime().toLocalDate(), usage.getMetricValue());
|
}
|
for (EnergyMeterMeasurement record : records) {
|
record.setUsageValue(usageMap.get(record.getMeasureTime().toLocalDate()));
|
record.setMeterName(device.getDisplayName());
|
record.setMeterType(device.getMeterType());
|
record.setMeterAddress(device.getMeterAddress());
|
}
|
}
|
return result;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public MeterReadingResultVO addReading(Long meterId, MeterReadingRequest request) {
|
EnergyMeterDevice device = requireManualDevice(meterId);
|
if (request.getReadingDate() == null) {
|
throw new ServiceException("抄表日期不能为空");
|
}
|
if (request.getReadingValue() == null) {
|
throw new ServiceException("本次表码不能为空");
|
}
|
LocalDate date = request.getReadingDate();
|
LocalDate today = EnergyMeterSupport.today();
|
if (date.isAfter(today)) {
|
throw new ServiceException("抄表日期不能晚于今天");
|
}
|
if (device.getInitialReadingDate() != null && date.isBefore(device.getInitialReadingDate())) {
|
throw new ServiceException("抄表日期不能早于起始表码日期("
|
+ device.getInitialReadingDate() + ")");
|
}
|
BigDecimal value = request.getReadingValue();
|
if (value.signum() < 0) {
|
throw new ServiceException("本次表码不能为负数");
|
}
|
|
BigDecimal previous = resolvePreviousReading(device, date.atStartOfDay());
|
if (previous != null && value.compareTo(previous) < 0) {
|
throw new ServiceException("本次表码不能小于上期表码(上期 " + previous.stripTrailingZeros().toPlainString() + ")");
|
}
|
|
EnergyMeterMeasurement point = newManualPoint(device, date.atStartOfDay(), value,
|
StringUtils.defaultIfBlank(request.getRemark(), null));
|
measurementMapper.upsertBatch(List.of(point));
|
|
BigDecimal usage = previous == null ? null : value.subtract(previous);
|
upsertUsage(device, date, usage);
|
|
// 若该日期之后已有抄表,重新计算下一期的用量
|
EnergyMeterMeasurement next = measurementMapper.selectNextPoint(device.getId(),
|
EnergyMeterConstants.readingMetric(device.getMeterType()), date.atStartOfDay());
|
if (next != null) {
|
recalcUsage(device, next);
|
}
|
deviceMapper.markCollectSuccess(device.getId(), EnergyMeterSupport.now());
|
|
MeterReadingResultVO result = new MeterReadingResultVO();
|
result.setMeterId(device.getId());
|
result.setMeasureTime(date.atStartOfDay());
|
result.setReadingValue(value);
|
result.setPreviousReading(previous);
|
result.setUsage(usage);
|
result.setUnit(EnergyMeterConstants.unit(device.getMeterType()));
|
return result;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void removeReading(Long meterId, Long measurementId) {
|
EnergyMeterDevice device = requireManualDevice(meterId);
|
EnergyMeterMeasurement record = measurementMapper.selectById(measurementId);
|
if (record == null || !meterId.equals(record.getMeterId())) {
|
throw new ServiceException("抄表记录不存在");
|
}
|
if (!EnergyMeterConstants.GRANULARITY_POINT.equals(record.getGranularity())) {
|
throw new ServiceException("只能删除抄表记录");
|
}
|
if (device.getInitialReadingDate() != null
|
&& device.getInitialReadingDate().equals(record.getMeasureTime().toLocalDate())) {
|
throw new ServiceException("起始表码不允许删除,如需修改请编辑设备");
|
}
|
|
EnergyMeterMeasurement next = measurementMapper.selectNextPoint(device.getId(),
|
EnergyMeterConstants.readingMetric(device.getMeterType()), record.getMeasureTime());
|
measurementMapper.deleteById(measurementId);
|
deleteUsage(device, record.getMeasureTime().toLocalDate());
|
if (next != null) {
|
recalcUsage(device, next);
|
}
|
log.info("删除抄表记录 meterId={} measurementId={} date={}",
|
meterId, measurementId, record.getMeasureTime().toLocalDate());
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public EnergyMeterDevice updateLocal(Long id, MeterUpdateRequest request) {
|
EnergyMeterDevice device = getOwnedDevice(id);
|
LambdaUpdateWrapper<EnergyMeterDevice> wrapper = new LambdaUpdateWrapper<>();
|
wrapper.eq(EnergyMeterDevice::getId, id);
|
if (request.getDisplayName() != null) {
|
if (StringUtils.isBlank(request.getDisplayName())) {
|
throw new ServiceException("显示名称不能为空");
|
}
|
wrapper.set(EnergyMeterDevice::getDisplayName, request.getDisplayName().trim());
|
}
|
if (request.getLocation() != null) {
|
wrapper.set(EnergyMeterDevice::getLocation, request.getLocation());
|
}
|
boolean reEnabled = false;
|
if (request.getEnabled() != null) {
|
if (request.getEnabled() != 0 && request.getEnabled() != 1) {
|
throw new ServiceException("启停用参数不合法");
|
}
|
reEnabled = request.getEnabled() == 1 && !Integer.valueOf(1).equals(device.getEnabled());
|
wrapper.set(EnergyMeterDevice::getEnabled, request.getEnabled());
|
}
|
deviceMapper.update(null, wrapper);
|
EnergyMeterDevice updated = getOwnedDevice(id);
|
if (reEnabled && !EnergyMeterConstants.isManual(updated.getSourceType())) {
|
try {
|
syncService.triggerBackfillGap(updated);
|
} catch (Exception e) {
|
log.warn("设备启用成功但补采批次创建失败 meterId={}: {}", id, e.getMessage());
|
}
|
}
|
enrichDevices(List.of(updated));
|
return updated;
|
}
|
|
@Override
|
public EnergyMeterDevice getOwnedDevice(Long id) {
|
EnergyMeterDevice device = deviceMapper.selectById(id);
|
if (device == null) {
|
throw new ServiceException("设备不存在或已删除");
|
}
|
Long tenantId = EnergyMeterSupport.currentTenantId();
|
if (tenantId != null && device.getTenantId() != null && !tenantId.equals(device.getTenantId())) {
|
throw new ServiceException("无权访问该设备");
|
}
|
return device;
|
}
|
|
@Override
|
public List<EnergyMeterDevice> listEnabledDevices(String meterType) {
|
LambdaQueryWrapper<EnergyMeterDevice> wrapper = new LambdaQueryWrapper<>();
|
Long tenantId = EnergyMeterSupport.currentTenantId();
|
if (tenantId != null) {
|
wrapper.eq(EnergyMeterDevice::getTenantId, tenantId);
|
}
|
if (StringUtils.isNotBlank(meterType)) {
|
wrapper.eq(EnergyMeterDevice::getMeterType, normalizeType(meterType));
|
}
|
wrapper.eq(EnergyMeterDevice::getEnabled, 1)
|
.eq(EnergyMeterDevice::getBindStatus, EnergyMeterConstants.BIND_VERIFIED)
|
.orderByAsc(EnergyMeterDevice::getId);
|
return deviceMapper.selectList(wrapper);
|
}
|
|
@Override
|
public boolean mockMode() {
|
return provider.mockMode();
|
}
|
|
/* ------------------------------------------------------------------ */
|
|
/**
|
* 计算指定日期之前的上一期表码:优先取抄表记录,其次取设备起始表码。
|
*/
|
private BigDecimal resolvePreviousReading(EnergyMeterDevice device, LocalDateTime time) {
|
EnergyMeterMeasurement previous = measurementMapper.selectPreviousPoint(device.getId(),
|
EnergyMeterConstants.readingMetric(device.getMeterType()), time);
|
if (previous != null) {
|
return previous.getMetricValue();
|
}
|
return device.getInitialReading();
|
}
|
|
/**
|
* 重算某条抄表记录对应的日用量(在插入/删除其它抄表记录后调用)。
|
*/
|
private void recalcUsage(EnergyMeterDevice device, EnergyMeterMeasurement point) {
|
BigDecimal previous = resolvePreviousReading(device, point.getMeasureTime());
|
if (previous == null) {
|
deleteUsage(device, point.getMeasureTime().toLocalDate());
|
return;
|
}
|
upsertUsage(device, point.getMeasureTime().toLocalDate(), point.getMetricValue().subtract(previous));
|
}
|
|
private void upsertUsage(EnergyMeterDevice device, LocalDate date, BigDecimal usage) {
|
if (usage == null) {
|
return;
|
}
|
EnergyMeterMeasurement record = new EnergyMeterMeasurement();
|
record.setTenantId(device.getTenantId());
|
record.setMeterId(device.getId());
|
record.setMetricCode(EnergyMeterConstants.usageMetric(device.getMeterType()));
|
record.setGranularity(EnergyMeterConstants.GRANULARITY_DAY);
|
record.setMeasureTime(date.atStartOfDay());
|
record.setMetricValue(usage);
|
record.setUnit(EnergyMeterConstants.unit(device.getMeterType()));
|
record.setSourceApi(EnergyMeterConstants.SOURCE_MANUAL);
|
record.setSourceRecordTime(date.atStartOfDay());
|
record.setCollectedAt(EnergyMeterSupport.now());
|
measurementMapper.upsertBatch(List.of(record));
|
}
|
|
private void deleteUsage(EnergyMeterDevice device, LocalDate date) {
|
measurementMapper.delete(new LambdaQueryWrapper<EnergyMeterMeasurement>()
|
.eq(EnergyMeterMeasurement::getMeterId, device.getId())
|
.eq(EnergyMeterMeasurement::getMetricCode, EnergyMeterConstants.usageMetric(device.getMeterType()))
|
.eq(EnergyMeterMeasurement::getGranularity, EnergyMeterConstants.GRANULARITY_DAY)
|
.eq(EnergyMeterMeasurement::getMeasureTime, date.atStartOfDay()));
|
}
|
|
private EnergyMeterMeasurement newManualPoint(EnergyMeterDevice device, LocalDateTime time,
|
BigDecimal value, String remark) {
|
EnergyMeterMeasurement point = new EnergyMeterMeasurement();
|
point.setTenantId(device.getTenantId());
|
point.setMeterId(device.getId());
|
point.setMetricCode(EnergyMeterConstants.readingMetric(device.getMeterType()));
|
point.setGranularity(EnergyMeterConstants.GRANULARITY_POINT);
|
point.setMeasureTime(time);
|
point.setMetricValue(value);
|
point.setUnit(EnergyMeterConstants.unit(device.getMeterType()));
|
point.setSourceApi(EnergyMeterConstants.SOURCE_MANUAL);
|
point.setSourceRecordTime(time);
|
point.setCollectedAt(EnergyMeterSupport.now());
|
point.setRemark(remark);
|
return point;
|
}
|
|
private EnergyMeterDevice requireManualDevice(Long meterId) {
|
EnergyMeterDevice device = getOwnedDevice(meterId);
|
if (!EnergyMeterConstants.isManual(device.getSourceType())) {
|
throw new ServiceException("该设备为第三方接入,请使用“查表”获取数据");
|
}
|
return device;
|
}
|
|
private void enrichDevices(List<EnergyMeterDevice> devices) {
|
if (devices == null || devices.isEmpty()) {
|
return;
|
}
|
List<Long> ids = devices.stream().map(EnergyMeterDevice::getId).collect(Collectors.toList());
|
Map<Long, EnergyMeterSyncTask> latestTask = new HashMap<>();
|
for (EnergyMeterSyncTask task : taskMapper.selectLatestByMeterIds(ids)) {
|
latestTask.put(task.getMeterId(), task);
|
}
|
LocalDate yesterday = EnergyMeterSupport.today().minusDays(1);
|
LocalDateTime start = yesterday.atStartOfDay();
|
LocalDateTime end = yesterday.plusDays(1).atStartOfDay();
|
|
Map<Long, BigDecimal> usageMap = new HashMap<>();
|
Set<String> types = devices.stream().map(EnergyMeterDevice::getMeterType)
|
.filter(StringUtils::isNotBlank).collect(Collectors.toSet());
|
for (String type : types) {
|
List<Long> typeIds = devices.stream()
|
.filter(d -> type.equals(d.getMeterType()))
|
.map(EnergyMeterDevice::getId).collect(Collectors.toList());
|
if (typeIds.isEmpty()) {
|
continue;
|
}
|
for (MeterUsageRow row : measurementMapper.sumUsageByMeter(typeIds,
|
EnergyMeterConstants.usageMetric(type), start, end)) {
|
usageMap.put(row.getMeterId(), row.getTotal());
|
}
|
}
|
|
for (EnergyMeterDevice device : devices) {
|
boolean manual = EnergyMeterConstants.isManual(device.getSourceType());
|
EnergyMeterSyncTask task = latestTask.get(device.getId());
|
if (task != null) {
|
device.setLastTaskStatus(task.getStatus());
|
device.setLastTaskTime(task.getFinishedAt() != null ? task.getFinishedAt() : task.getStartedAt());
|
}
|
BigDecimal dayUsage = usageMap.get(device.getId());
|
if (dayUsage != null) {
|
device.setLastDayUsage(dayUsage);
|
device.setLastDayUsageDate(yesterday);
|
} else {
|
// 昨天没有用量时(手工抄表按抄表期入账、第三方设备可能只有当天数据),
|
// 退回最近一条用量及其业务日期,避免列表长期显示 "--"
|
EnergyMeterMeasurement latestUsage = measurementMapper.selectLatestPoint(device.getId(),
|
EnergyMeterConstants.usageMetric(device.getMeterType()));
|
if (latestUsage != null) {
|
device.setLastDayUsage(latestUsage.getMetricValue());
|
device.setLastDayUsageDate(latestUsage.getMeasureTime().toLocalDate());
|
}
|
}
|
EnergyMeterMeasurement latest = measurementMapper.selectLatestPoint(device.getId(),
|
EnergyMeterConstants.readingMetric(device.getMeterType()));
|
if (latest != null) {
|
device.setLatestReading(latest.getMetricValue());
|
EnergyMeterMeasurement previous = measurementMapper.selectPreviousPoint(device.getId(),
|
EnergyMeterConstants.readingMetric(device.getMeterType()), latest.getMeasureTime());
|
if (previous != null) {
|
device.setPreviousReading(previous.getMetricValue());
|
} else if (device.getLastDayUsage() != null) {
|
// 只有一条读数时(新装表第一天),用“最新表码 - 当期用量”反推上期表码
|
device.setPreviousReading(latest.getMetricValue().subtract(device.getLastDayUsage()));
|
}
|
}
|
device.setCollectStateText(buildCollectState(device, task));
|
device.setMeterTypeName(EnergyMeterConstants.typeName(device.getMeterType()));
|
device.setSourceTypeName(EnergyMeterConstants.isAutoCollect(device.getSourceType(), device.getSimulated())
|
? "自动采集" : "手工抄表");
|
device.setUnit(EnergyMeterConstants.unit(device.getMeterType()));
|
device.setDeletable(!Integer.valueOf(1).equals(device.getIsSeed()) && manual);
|
}
|
}
|
|
private String buildCollectState(EnergyMeterDevice device, EnergyMeterSyncTask task) {
|
if (EnergyMeterConstants.isSimulated(device.getSimulated())) {
|
return "自动采集";
|
}
|
if (EnergyMeterConstants.isManual(device.getSourceType())) {
|
return device.getLastSuccessAt() == null ? "尚未抄表" : "手工抄表";
|
}
|
if (task == null) {
|
return "尚未采集";
|
}
|
return switch (StringUtils.defaultString(task.getStatus())) {
|
case "SUCCESS" -> "采集成功";
|
case "EMPTY" -> "确认无数据";
|
case "FAILED" -> "采集失败";
|
case "RETRY" -> "等待重试";
|
case "RUNNING" -> "采集中";
|
default -> "待执行";
|
};
|
}
|
|
private EnergyMeterDevice findBinding(String meterType, String address) {
|
LambdaQueryWrapper<EnergyMeterDevice> wrapper = new LambdaQueryWrapper<>();
|
wrapper.eq(EnergyMeterDevice::getMeterType, meterType)
|
.eq(EnergyMeterDevice::getMeterAddress, address);
|
Long tenantId = EnergyMeterSupport.currentTenantId();
|
if (tenantId != null) {
|
wrapper.eq(EnergyMeterDevice::getTenantId, tenantId);
|
}
|
List<EnergyMeterDevice> list = deviceMapper.selectList(wrapper);
|
return list.isEmpty() ? null : list.get(0);
|
}
|
|
private LocalDate resolveStartDate(LocalDate requested, LocalDate existingStart, LocalDate yesterday) {
|
LocalDate latestAllowed = yesterday;
|
LocalDate defaultStart = LocalDate.of(latestAllowed.getYear() - 1, 1, 1);
|
LocalDate start = requested != null ? requested : (existingStart != null ? existingStart : defaultStart);
|
if (start.isAfter(latestAllowed)) {
|
throw new ServiceException("补采起始日期不能晚于昨天");
|
}
|
if (start.isBefore(LocalDate.of(2000, 1, 1))) {
|
throw new ServiceException("补采起始日期过早,请确认供应商允许的历史范围");
|
}
|
return start;
|
}
|
|
|
private String normalizeType(String meterType) {
|
String type = EnergyMeterConstants.canonicalType(meterType);
|
if (type == null) {
|
throw new ServiceException("能源类型只支持水表(WATER)、电表(ELECTRIC)或气表(GAS)");
|
}
|
return type;
|
}
|
}
|