From cb031240665b464f62ce9c00af26b0c22d948c1e Mon Sep 17 00:00:00 2001 From: HuangHuiKang Date: Tue, 30 Jun 2026 14:31:23 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/stock/ErpStockController.java | 8 + .../vo/stock/ErpStockStatisticsRespVO.java | 113 +++++++ .../erp/service/stock/ErpStockService.java | 3 + .../service/stock/ErpStockServiceImpl.java | 314 ++++++++++++++++++ .../module/mes/enums/ErrorCodeConstants.java | 1 + .../moldoperate/MoldOperateServiceImpl.java | 8 + 6 files changed, 447 insertions(+) create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockStatisticsRespVO.java diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java index 465f0b87e..3c0c69df0 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProduc import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductPackagingSchemeRespVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockStatisticsRespVO; import cn.iocoder.yudao.module.erp.dal.dataobject.pallet.ErpPalletDO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; @@ -93,6 +94,13 @@ public class ErpStockController { return success(stockService.getStockCount(productId)); } + @GetMapping("/statistics-cards") + @Operation(summary = "获得库存统计卡片") + @PreAuthorize("@ss.hasPermission('erp:stock:query')") + public CommonResult getStockStatistics() { + return success(stockService.getStockStatistics()); + } + @GetMapping("/page") @Operation(summary = "获得产品库存分页") @PreAuthorize("@ss.hasPermission('erp:stock:query')") diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockStatisticsRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockStatisticsRespVO.java new file mode 100644 index 000000000..36be1bfc7 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockStatisticsRespVO.java @@ -0,0 +1,113 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - ERP 库存统计卡片 Response VO") +@Data +public class ErpStockStatisticsRespVO { + + @Schema(description = "产品品库存") + private ProductStockCard productStock; + + @Schema(description = "原材料库存") + private UnitGroupedStockCard materialStock; + + @Schema(description = "备件库存") + private UnitGroupedStockCard sparePartStock; + + @Schema(description = "今日入库") + private TodayInCard todayIn; + + @Schema(description = "今日出库") + private TodayOutCard todayOut; + + @Schema(description = "产品品库存卡片") + @Data + public static class ProductStockCard { + + @Schema(description = "SKU 数", example = "36") + private Long skuCount; + + @Schema(description = "库存摘要", example = "128托42包560个") + private String stockDisplay; + + @Schema(description = "今日完工入库", example = "12托8包") + private String todayFinishedInDisplay; + } + + @Schema(description = "按单位分组库存卡片") + @Data + public static class UnitGroupedStockCard { + + @Schema(description = "SKU/种类数", example = "24") + private Long skuCount; + + @Schema(description = "库存摘要", example = "18.6吨 / 1250L / 320包") + private String stockDisplay; + + @Schema(description = "按库存单位分组明细") + private List unitStocks; + } + + @Schema(description = "库存单位分组明细") + @Data + public static class UnitStock { + + @Schema(description = "库存单位编号", example = "3") + private Long unitId; + + @Schema(description = "库存单位名称", example = "kg") + private String unitName; + + @Schema(description = "库存数量", example = "820") + private BigDecimal count; + + @Schema(description = "库存展示", example = "820kg") + private String stockDisplay; + } + + @Schema(description = "今日入库卡片") + @Data + public static class TodayInCard { + + @Schema(description = "入库单数", example = "18") + private Long orderCount; + + @Schema(description = "明细行数", example = "46") + private Long itemCount; + + @Schema(description = "产品品入库") + private UnitGroupedStockCard productStock; + + @Schema(description = "原材料入库") + private UnitGroupedStockCard materialStock; + + @Schema(description = "备件入库") + private UnitGroupedStockCard sparePartStock; + } + + @Schema(description = "今日出库卡片") + @Data + public static class TodayOutCard { + + @Schema(description = "出库单数", example = "15") + private Long orderCount; + + @Schema(description = "明细行数", example = "38") + private Long itemCount; + + @Schema(description = "原材料领用") + private UnitGroupedStockCard materialStock; + + @Schema(description = "备件领用") + private UnitGroupedStockCard sparePartStock; + + @Schema(description = "产品品发货") + private UnitGroupedStockCard productStock; + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java index 90c91f2b6..2e0bd87ac 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.erp.service.stock; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockStatisticsRespVO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; import java.math.BigDecimal; @@ -61,6 +62,8 @@ public interface ErpStockService { */ PageResult getStockPage(ErpStockPageReqVO pageReqVO); + ErpStockStatisticsRespVO getStockStatistics(); + /** * 增量更新产品库存数量 * diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java index 363838d90..d15042b41 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java @@ -4,17 +4,36 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductPackagingSchemeRespVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockStatisticsRespVO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInMapper; import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockOutItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockOutMapper; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.framework.bean.ProductTypeEnum; import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE; @@ -42,6 +61,14 @@ public class ErpStockServiceImpl implements ErpStockService { @Resource private ErpStockMapper stockMapper; + @Resource + private ErpStockInMapper stockInMapper; + @Resource + private ErpStockInItemMapper stockInItemMapper; + @Resource + private ErpStockOutMapper stockOutMapper; + @Resource + private ErpStockOutItemMapper stockOutItemMapper; @Override public ErpStockDO getStock(Long id) { @@ -93,6 +120,21 @@ public class ErpStockServiceImpl implements ErpStockService { return stockMapper.selectPage(pageReqVO); } + @Override + public ErpStockStatisticsRespVO getStockStatistics() { + ErpStockStatisticsRespVO respVO = new ErpStockStatisticsRespVO(); + List stockList = stockMapper.selectList(new LambdaQueryWrapper()); + Map productMap = productService.getProductVOMap(stockList.stream() + .map(ErpStockDO::getProductId).collect(Collectors.toSet())); + fillStockCards(respVO, stockList, productMap); + + LocalDateTime beginTime = LocalDate.now().atStartOfDay(); + LocalDateTime endTime = beginTime.plusDays(1); + fillTodayInCard(respVO, beginTime, endTime, productMap); + fillTodayOutCard(respVO, beginTime, endTime, productMap); + return respVO; + } + @Override public BigDecimal updateStockCountIncrement(Long categoryId, Long productId, Long warehouseId, Long areaId, String areaName, BigDecimal count) { @@ -140,6 +182,266 @@ public class ErpStockServiceImpl implements ErpStockService { return stock.getCount().add(count); } + private void fillStockCards(ErpStockStatisticsRespVO respVO, List stockList, + Map productMap) { + ErpStockStatisticsRespVO.ProductStockCard productStockCard = new ErpStockStatisticsRespVO.ProductStockCard(); + + List productStockList = filterStockByCategory(stockList, productMap, ProductTypeEnum.ITEM.getTypeId()); + List materialStockList = filterStockByCategory(stockList, productMap, ProductTypeEnum.PRODUCT.getTypeId()); + List sparePartStockList = filterStockByCategory(stockList, productMap, ProductTypeEnum.BEIJIAN.getTypeId()); + + productStockCard.setSkuCount(countDistinctProduct(productStockList)); + productStockCard.setStockDisplay(formatProductStockDisplay(productStockList)); + productStockCard.setTodayFinishedInDisplay(formatProductStockDisplay(Collections.emptyList())); + respVO.setProductStock(productStockCard); + + respVO.setMaterialStock(buildUnitGroupedStockCard(materialStockList)); + respVO.setSparePartStock(buildUnitGroupedStockCard(sparePartStockList)); + } + + private void fillTodayInCard(ErpStockStatisticsRespVO respVO, LocalDateTime beginTime, LocalDateTime endTime, + Map stockProductMap) { + ErpStockStatisticsRespVO.TodayInCard todayInCard = new ErpStockStatisticsRespVO.TodayInCard(); + List stockInList = stockInMapper.selectList(new LambdaQueryWrapper() + .eq(ErpStockInDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .between(ErpStockInDO::getInTime, beginTime, endTime)); + List stockInIds = stockInList.stream().map(ErpStockInDO::getId).collect(Collectors.toList()); + List itemList = stockInIds.isEmpty() ? Collections.emptyList() + : stockInItemMapper.selectListByInIds(stockInIds); + Map productMap = buildProductMap(itemList.stream() + .map(ErpStockInItemDO::getProductId).collect(Collectors.toSet()), stockProductMap); + + todayInCard.setOrderCount((long) stockInIds.size()); + todayInCard.setItemCount((long) itemList.size()); + todayInCard.setProductStock(buildInItemUnitGroupedStockCard(filterInItemByCategory(itemList, productMap, ProductTypeEnum.ITEM.getTypeId()), productMap)); + todayInCard.setMaterialStock(buildInItemUnitGroupedStockCard(filterInItemByCategory(itemList, productMap, ProductTypeEnum.PRODUCT.getTypeId()), productMap)); + todayInCard.setSparePartStock(buildInItemUnitGroupedStockCard(filterInItemByCategory(itemList, productMap, ProductTypeEnum.BEIJIAN.getTypeId()), productMap)); + respVO.setTodayIn(todayInCard); + respVO.getProductStock().setTodayFinishedInDisplay(todayInCard.getProductStock().getStockDisplay()); + } + + private void fillTodayOutCard(ErpStockStatisticsRespVO respVO, LocalDateTime beginTime, LocalDateTime endTime, + Map stockProductMap) { + ErpStockStatisticsRespVO.TodayOutCard todayOutCard = new ErpStockStatisticsRespVO.TodayOutCard(); + List stockOutList = stockOutMapper.selectList(new LambdaQueryWrapper() + .eq(ErpStockOutDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .between(ErpStockOutDO::getOutTime, beginTime, endTime)); + List stockOutIds = stockOutList.stream().map(ErpStockOutDO::getId).collect(Collectors.toList()); + List itemList = stockOutIds.isEmpty() ? Collections.emptyList() + : stockOutItemMapper.selectListByOutIds(stockOutIds); + Map productMap = buildProductMap(itemList.stream() + .map(ErpStockOutItemDO::getProductId).collect(Collectors.toSet()), stockProductMap); + + todayOutCard.setOrderCount((long) stockOutIds.size()); + todayOutCard.setItemCount((long) itemList.size()); + todayOutCard.setProductStock(buildOutItemUnitGroupedStockCard(filterOutItemByCategory(itemList, productMap, ProductTypeEnum.ITEM.getTypeId()), productMap)); + todayOutCard.setMaterialStock(buildOutItemUnitGroupedStockCard(filterOutItemByCategory(itemList, productMap, ProductTypeEnum.PRODUCT.getTypeId()), productMap)); + todayOutCard.setSparePartStock(buildOutItemUnitGroupedStockCard(filterOutItemByCategory(itemList, productMap, ProductTypeEnum.BEIJIAN.getTypeId()), productMap)); + respVO.setTodayOut(todayOutCard); + } + + private Map buildProductMap(Set productIds, Map stockProductMap) { + Set missingProductIds = new HashSet<>(productIds); + missingProductIds.removeAll(stockProductMap.keySet()); + if (missingProductIds.isEmpty()) { + return stockProductMap; + } + Map resultMap = productService.getProductVOMap(productIds); + resultMap.putAll(stockProductMap); + return resultMap; + } + + private List filterStockByCategory(List stockList, Map productMap, + Integer categoryType) { + return stockList.stream() + .filter(stock -> categoryType.equals(resolveCategoryType(stock, productMap))) + .collect(Collectors.toList()); + } + + private List filterInItemByCategory(List itemList, + Map productMap, + Integer categoryType) { + return itemList.stream() + .filter(item -> categoryType.equals(resolveCategoryType(item.getProductId(), productMap))) + .collect(Collectors.toList()); + } + + private List filterOutItemByCategory(List itemList, + Map productMap, + Integer categoryType) { + return itemList.stream() + .filter(item -> categoryType.equals(resolveCategoryType(item.getProductId(), productMap))) + .collect(Collectors.toList()); + } + + private ErpStockStatisticsRespVO.UnitGroupedStockCard buildUnitGroupedStockCard(List stockList) { + ErpStockStatisticsRespVO.UnitGroupedStockCard card = new ErpStockStatisticsRespVO.UnitGroupedStockCard(); + card.setSkuCount(countDistinctProduct(stockList)); + List unitStocks = stockList.stream() + .collect(Collectors.groupingBy(stock -> buildUnitKey(stock.getUnitId(), stock.getUnitName()))) + .values().stream() + .map(this::buildUnitStock) + .collect(Collectors.toList()); + card.setUnitStocks(unitStocks); + card.setStockDisplay(unitStocks.stream() + .map(ErpStockStatisticsRespVO.UnitStock::getStockDisplay) + .collect(Collectors.joining(" / "))); + return card; + } + + private ErpStockStatisticsRespVO.UnitStock buildUnitStock(List stockList) { + ErpStockDO firstStock = stockList.get(0); + BigDecimal count = stockList.stream().map(stock -> defaultZero(stock.getCount())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + return buildUnitStock(firstStock.getUnitId(), firstStock.getUnitName(), count); + } + + private String buildUnitKey(Long unitId, String unitName) { + return String.valueOf(unitId) + "_" + String.valueOf(unitName); + } + + private ErpStockStatisticsRespVO.UnitGroupedStockCard buildInItemUnitGroupedStockCard(List itemList, + Map productMap) { + ErpStockStatisticsRespVO.UnitGroupedStockCard card = new ErpStockStatisticsRespVO.UnitGroupedStockCard(); + card.setSkuCount(itemList.stream().map(ErpStockInItemDO::getProductId).distinct().count()); + List unitStocks = itemList.stream() + .collect(Collectors.groupingBy(item -> buildProductUnitKey(item.getProductId(), productMap))) + .values().stream() + .map(items -> buildInItemUnitStock(items, productMap)) + .collect(Collectors.toList()); + card.setUnitStocks(unitStocks); + card.setStockDisplay(unitStocks.stream() + .map(ErpStockStatisticsRespVO.UnitStock::getStockDisplay) + .collect(Collectors.joining(" / "))); + return card; + } + + private ErpStockStatisticsRespVO.UnitGroupedStockCard buildOutItemUnitGroupedStockCard(List itemList, + Map productMap) { + ErpStockStatisticsRespVO.UnitGroupedStockCard card = new ErpStockStatisticsRespVO.UnitGroupedStockCard(); + card.setSkuCount(itemList.stream().map(ErpStockOutItemDO::getProductId).distinct().count()); + List unitStocks = itemList.stream() + .collect(Collectors.groupingBy(item -> buildProductUnitKey(item.getProductId(), productMap))) + .values().stream() + .map(items -> buildOutItemUnitStock(items, productMap)) + .collect(Collectors.toList()); + card.setUnitStocks(unitStocks); + card.setStockDisplay(unitStocks.stream() + .map(ErpStockStatisticsRespVO.UnitStock::getStockDisplay) + .collect(Collectors.joining(" / "))); + return card; + } + + private ErpStockStatisticsRespVO.UnitStock buildInItemUnitStock(List itemList, + Map productMap) { + ErpStockInItemDO firstItem = itemList.get(0); + ErpProductRespVO product = productMap.get(firstItem.getProductId()); + BigDecimal count = itemList.stream().map(item -> defaultZero(item.getCount())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + return buildUnitStock(product != null ? product.getUnitId() : null, + product != null ? product.getUnitName() : null, count); + } + + private ErpStockStatisticsRespVO.UnitStock buildOutItemUnitStock(List itemList, + Map productMap) { + ErpStockOutItemDO firstItem = itemList.get(0); + ErpProductRespVO product = productMap.get(firstItem.getProductId()); + BigDecimal count = itemList.stream().map(item -> defaultZero(item.getCount())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + return buildUnitStock(product != null ? product.getUnitId() : null, + product != null ? product.getUnitName() : null, count); + } + + private ErpStockStatisticsRespVO.UnitStock buildUnitStock(Long unitId, String unitName, BigDecimal count) { + ErpStockStatisticsRespVO.UnitStock unitStock = new ErpStockStatisticsRespVO.UnitStock(); + unitStock.setUnitId(unitId); + unitStock.setUnitName(unitName); + unitStock.setCount(count); + unitStock.setStockDisplay(formatNumber(count) + nullToEmpty(unitName)); + return unitStock; + } + + private String buildProductUnitKey(Long productId, Map productMap) { + ErpProductRespVO product = productMap.get(productId); + return product != null ? buildUnitKey(product.getUnitId(), product.getUnitName()) : buildUnitKey(null, null); + } + + private Integer resolveCategoryType(ErpStockDO stock, Map productMap) { + if (stock.getCategoryType() != null) { + return stock.getCategoryType(); + } + return resolveCategoryType(stock.getProductId(), productMap); + } + + private Integer resolveCategoryType(Long productId, Map productMap) { + ErpProductRespVO product = productMap.get(productId); + return product != null ? product.getCategoryType() : null; + } + + private Long countDistinctProduct(List stockList) { + return stockList.stream().map(ErpStockDO::getProductId).distinct().count(); + } + + private String formatProductStockDisplay(List stockList) { + BigDecimal palletCount = BigDecimal.ZERO; + BigDecimal packageCount = BigDecimal.ZERO; + BigDecimal pieceCount = BigDecimal.ZERO; + for (ErpStockDO stock : stockList) { + ProductQuantity quantity = calculateProductQuantity(stock.getCount(), stock.getPalletTotalQuantity(), stock.getPackageQuantity()); + palletCount = palletCount.add(quantity.getPalletCount()); + packageCount = packageCount.add(quantity.getPackageCount()); + pieceCount = pieceCount.add(quantity.getPieceCount()); + } + return formatProductQuantity(palletCount, packageCount, pieceCount); + } + + private ProductQuantity calculateProductQuantity(BigDecimal count, BigDecimal palletTotalQuantity, BigDecimal packageQuantity) { + BigDecimal safeCount = defaultZero(count); + BigDecimal palletCount = BigDecimal.ZERO; + BigDecimal packageCount = BigDecimal.ZERO; + BigDecimal pieceCount = safeCount; + if (palletTotalQuantity != null && palletTotalQuantity.compareTo(BigDecimal.ZERO) > 0) { + palletCount = safeCount.divideToIntegralValue(palletTotalQuantity); + pieceCount = safeCount.remainder(palletTotalQuantity); + } + if (packageQuantity != null && packageQuantity.compareTo(BigDecimal.ZERO) > 0) { + packageCount = pieceCount.divideToIntegralValue(packageQuantity); + pieceCount = pieceCount.remainder(packageQuantity); + } + return new ProductQuantity(palletCount, packageCount, pieceCount); + } + + private String formatProductQuantity(BigDecimal palletCount, BigDecimal packageCount, BigDecimal pieceCount) { + StringBuilder display = new StringBuilder(); + appendDisplay(display, palletCount, "托", false); + appendDisplay(display, packageCount, "包", false); + appendDisplay(display, pieceCount, "个", display.length() == 0); + return display.toString(); + } + + private void appendDisplay(StringBuilder display, BigDecimal quantity, String unitName, boolean keepZero) { + if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { + if (!keepZero) { + return; + } + display.append("0").append(unitName); + return; + } + display.append(formatNumber(quantity)).append(unitName); + } + + private BigDecimal defaultZero(BigDecimal value) { + return value != null ? value : BigDecimal.ZERO; + } + + private String nullToEmpty(String value) { + return value != null ? value : ""; + } + + private String formatNumber(BigDecimal value) { + return value == null ? "0" : value.stripTrailingZeros().toPlainString(); + } + private void fillStockSnapshot(ErpStockDO stock, ErpProductRespVO product, ProductPackagingSchemeRespVO defaultPackagingScheme) { if (product == null) { @@ -196,4 +498,16 @@ public class ErpStockServiceImpl implements ErpStockService { .setPalletTotalQuantity(stock.getPalletTotalQuantity()); } + @lombok.Data + @lombok.AllArgsConstructor + private static class ProductQuantity { + + private BigDecimal palletCount; + + private BigDecimal packageCount; + + private BigDecimal pieceCount; + + } + } diff --git a/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java index dad9a7d0e..15f76c6ca 100644 --- a/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java +++ b/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java @@ -98,6 +98,7 @@ public interface ErrorCodeConstants { ErrorCode MOLD_GET_NOT_EXISTS = new ErrorCode(5_0087, "领模申请不存在"); ErrorCode MOLD_RETURN_NOT_EXISTS = new ErrorCode(5_0087, "模具入库不存在"); ErrorCode MOLD_OPERATE_NOT_EXISTS = new ErrorCode(5_0087, "模具上下模不存在"); + ErrorCode MOLD_OPERATE_DEVICE_HAS_MOLD = new ErrorCode(5_0089, "该设备已有上模模具组,请先下模后再上模"); ErrorCode MOLD_REPAIR_NOT_EXISTS = new ErrorCode(5_0087, "模具维修记录不存在"); ErrorCode MOLD_REPAIR_CODE_EXISTS = new ErrorCode(5_0087, "模具维修记录编码已存在"); diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/moldoperate/MoldOperateServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/moldoperate/MoldOperateServiceImpl.java index b3a6b19c1..2767c6567 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/moldoperate/MoldOperateServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/moldoperate/MoldOperateServiceImpl.java @@ -59,6 +59,7 @@ public class MoldOperateServiceImpl implements MoldOperateService { // 插入 MoldOperateDO moldOperate = BeanUtils.toBean(createReqVO, MoldOperateDO.class); DeviceLedgerDO deviceLedgerDO = deviceLedgerMapper.selectById(createReqVO.getDeviceId()); + validateDeviceMoldEmpty(createReqVO, deviceLedgerDO); fillDeviceAndLineInfo(moldOperate, deviceLedgerDO,createReqVO.getLineId()); MoldBrandDO moldDO = moldBrandMapper.selectById(createReqVO.getMoldId()); @@ -116,6 +117,13 @@ public class MoldOperateServiceImpl implements MoldOperateService { } } + private void validateDeviceMoldEmpty(MoldOperateSaveReqVO createReqVO, DeviceLedgerDO deviceLedgerDO) { + if (createReqVO.getOperateType() != null && createReqVO.getOperateType() == 1 + && deviceLedgerDO != null && StringUtil.isNotBlank(deviceLedgerDO.getMoldId())) { + throw exception(MOLD_OPERATE_DEVICE_HAS_MOLD); + } + } + @Override public MoldOperateDO getMoldOperate(Long id) { return moldOperateMapper.selectById(id); From 41c710296970cc49222036def4eef93b04924f0b Mon Sep 17 00:00:00 2001 From: HuangHuiKang Date: Tue, 30 Jun 2026 16:33:48 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9E=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E5=BA=93=E5=AD=98=E7=BB=9F=E8=AE=A1=E6=8E=A5=E5=8F=A3=E5=8F=8A?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0iot=E8=AE=BE=E5=A4=87=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/stock/ErpStockController.java | 14 +- .../admin/stock/ErpStockInController.java | 4 +- .../admin/stock/ErpStockOutController.java | 4 +- .../vo/stock/ErpStockStatisticsRespVO.java | 37 ++- .../dataobject/stock/ErpStockInItemDO.java | 4 + .../dataobject/stock/ErpStockOutItemDO.java | 4 + .../service/stock/ErpStockInServiceImpl.java | 1 + .../service/stock/ErpStockOutServiceImpl.java | 1 + .../service/stock/ErpStockServiceImpl.java | 251 ++++++++++++++++-- .../admin/device/vo/DevicePageReqVO.java | 5 +- .../iot/dal/mysql/device/DeviceMapper.java | 2 + .../iot/service/device/DeviceServiceImpl.java | 67 +++++ 12 files changed, 364 insertions(+), 30 deletions(-) diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java index 3c0c69df0..334ab92e9 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java @@ -356,8 +356,8 @@ public class ErpStockController { if (stock.getUnitName() == null || stock.getPurchaseUnitName() == null || stock.getPurchaseUnitConvertQuantity() == null) { return null; } - return "1" + stock.getUnitName() + "=" + formatNumber(stock.getPurchaseUnitConvertQuantity()) - + stock.getPurchaseUnitName(); + return "1" + stock.getPurchaseUnitName() + "=" + formatNumber(stock.getPurchaseUnitConvertQuantity()) + + stock.getUnitName(); } private String buildStockDisplay(ErpStockRespVO stock) { @@ -383,12 +383,12 @@ public class ErpStockController { private String buildPurchaseUnitDisplay(ErpStockRespVO stock, BigDecimal count) { if (stock.getPurchaseUnitConvertQuantity() == null || stock.getPurchaseUnitConvertQuantity().compareTo(BigDecimal.ZERO) <= 0 || stock.getUnitName() == null || stock.getPurchaseUnitName() == null) { - return formatNumber(count) + nullToEmpty(stock.getPurchaseUnitName()); + return formatNumber(count) + nullToEmpty(stock.getUnitName()); } - BigDecimal unitCount = defaultZero(count).divideToIntegralValue(stock.getPurchaseUnitConvertQuantity()); - BigDecimal purchaseUnitCount = defaultZero(count).remainder(stock.getPurchaseUnitConvertQuantity()); - return formatNumber(unitCount) + stock.getUnitName() - + formatNumber(purchaseUnitCount) + stock.getPurchaseUnitName(); + BigDecimal purchaseUnitCount = defaultZero(count).divideToIntegralValue(stock.getPurchaseUnitConvertQuantity()); + BigDecimal unitCount = defaultZero(count).remainder(stock.getPurchaseUnitConvertQuantity()); + return formatNumber(purchaseUnitCount) + stock.getPurchaseUnitName() + + formatNumber(unitCount) + stock.getUnitName(); } private void appendQuantity(StringBuilder display, BigDecimal quantity, String unitName, boolean keepZero) { diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java index 789bac398..1524b65d6 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java @@ -315,7 +315,9 @@ public class ErpStockInController { item.setPallets(buildStockInPalletRespList(itemPalletMap.get(item.getId()), palletMap)); MapUtils.findAndThen(productMap, item.getProductId(), product -> { item.setProductBarCode(product.getBarCode()); - item.setProductUnitName(product.getUnitName()); + if (StringUtils.isBlank(item.getProductUnitName())) { + item.setProductUnitName(product.getUnitName()); + } item.setCategoryType(product.getCategoryType()); if (item.getSupplierId() == null) { item.setSupplierId(product.getDefaultSupplierId()); diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockOutController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockOutController.java index e51995f0b..6d7104710 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockOutController.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockOutController.java @@ -329,7 +329,9 @@ public class ErpStockOutController { item.setPallets(buildStockOutPalletRespList(itemPalletMap.get(item.getId()), palletMap)); MapUtils.findAndThen(productMap, item.getProductId(), product -> { item.setProductBarCode(product.getBarCode()); - item.setProductUnitName(product.getUnitName()); + if (StringUtils.isBlank(item.getProductUnitName())) { + item.setProductUnitName(product.getUnitName()); + } item.setCategoryType(product.getCategoryType()); item.setSupplierId(product.getDefaultSupplierId()); if (product.getDefaultSupplierId() != null && CollUtil.isNotEmpty(product.getSuppliers())) { diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockStatisticsRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockStatisticsRespVO.java index 36be1bfc7..801e6afde 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockStatisticsRespVO.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockStatisticsRespVO.java @@ -36,9 +36,42 @@ public class ErpStockStatisticsRespVO { private String stockDisplay; @Schema(description = "今日完工入库", example = "12托8包") + private BigDecimal totalCount; + + private String unitName; + + private List packagingStocks; + private String todayFinishedInDisplay; } + @Schema(description = "产成品包装方案库存明细") + @Data + public static class ProductPackagingStock { + + private Long productId; + + private String productName; + + private Long packagingSchemeId; + + private String packagingSchemeName; + + private BigDecimal totalCount; + + private String unitName; + + private String stockDisplay; + + private String packagingDisplay; + + private BigDecimal packageQuantity; + + private BigDecimal palletPackageQuantity; + + private BigDecimal palletTotalQuantity; + } + @Schema(description = "按单位分组库存卡片") @Data public static class UnitGroupedStockCard { @@ -81,7 +114,7 @@ public class ErpStockStatisticsRespVO { private Long itemCount; @Schema(description = "产品品入库") - private UnitGroupedStockCard productStock; + private ProductStockCard productStock; @Schema(description = "原材料入库") private UnitGroupedStockCard materialStock; @@ -107,7 +140,7 @@ public class ErpStockStatisticsRespVO { private UnitGroupedStockCard sparePartStock; @Schema(description = "产品品发货") - private UnitGroupedStockCard productStock; + private ProductStockCard productStock; } } diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInItemDO.java index 32c59fbf4..9973c9a36 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInItemDO.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInItemDO.java @@ -70,6 +70,10 @@ public class ErpStockInItemDO extends BaseDO { * 冗余 {@link ErpProductDO#getUnitId()} */ private Long productUnitId; + /** + * 产品单位名称快照 + */ + private String productUnitName; /** * 产品单价 */ diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutItemDO.java index e207772c3..c5f6721e8 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutItemDO.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutItemDO.java @@ -101,6 +101,10 @@ public class ErpStockOutItemDO extends BaseDO { * 冗余 {@link ErpProductDO#getUnitId()} */ private Long productUnitId; + /** + * 产品单位名称快照 + */ + private String productUnitName; /** * 产品单价 */ diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java index 8f1bf3710..7f6719120 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java @@ -556,6 +556,7 @@ public class ErpStockInServiceImpl implements ErpStockInService { .setProductName(productMap.get(target.getProductId()).getName()) .setSupplierName(resolveStockInItemSupplierName(item, supplierMap, inType)) .setProductUnitId(productMap.get(target.getProductId()).getUnitId()) + .setProductUnitName(productMap.get(target.getProductId()).getUnitName()) .setTotalPrice(MoneyUtils.priceMultiply(target.getProductPrice(), target.getCount())))); } diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java index 17d80ffca..e268924bd 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java @@ -640,6 +640,7 @@ public class ErpStockOutServiceImpl implements ErpStockOutService { .setAreaName(areaMap.containsKey(target.getAreaId()) ? areaMap.get(target.getAreaId()).getAreaName() : null) .setProductName(productMap.get(target.getProductId()).getName()) .setProductUnitId(productMap.get(target.getProductId()).getUnitId()) + .setProductUnitName(productMap.get(target.getProductId()).getUnitName()) .setTotalPrice(MoneyUtils.priceMultiply(target.getProductPrice(), target.getCount())) .setOutUsageType(item.getOutUsageType()) .setOutUsageTypeName(ErpStockOutUsageTypeEnum.getNameByType(item.getOutUsageType())) diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java index d15042b41..e9718beb2 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java @@ -32,6 +32,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -184,16 +185,11 @@ public class ErpStockServiceImpl implements ErpStockService { private void fillStockCards(ErpStockStatisticsRespVO respVO, List stockList, Map productMap) { - ErpStockStatisticsRespVO.ProductStockCard productStockCard = new ErpStockStatisticsRespVO.ProductStockCard(); - List productStockList = filterStockByCategory(stockList, productMap, ProductTypeEnum.ITEM.getTypeId()); List materialStockList = filterStockByCategory(stockList, productMap, ProductTypeEnum.PRODUCT.getTypeId()); List sparePartStockList = filterStockByCategory(stockList, productMap, ProductTypeEnum.BEIJIAN.getTypeId()); - productStockCard.setSkuCount(countDistinctProduct(productStockList)); - productStockCard.setStockDisplay(formatProductStockDisplay(productStockList)); - productStockCard.setTodayFinishedInDisplay(formatProductStockDisplay(Collections.emptyList())); - respVO.setProductStock(productStockCard); + respVO.setProductStock(buildProductStockCard(productStockList, productMap)); respVO.setMaterialStock(buildUnitGroupedStockCard(materialStockList)); respVO.setSparePartStock(buildUnitGroupedStockCard(sparePartStockList)); @@ -213,7 +209,7 @@ public class ErpStockServiceImpl implements ErpStockService { todayInCard.setOrderCount((long) stockInIds.size()); todayInCard.setItemCount((long) itemList.size()); - todayInCard.setProductStock(buildInItemUnitGroupedStockCard(filterInItemByCategory(itemList, productMap, ProductTypeEnum.ITEM.getTypeId()), productMap)); + todayInCard.setProductStock(buildInItemProductStockCard(filterInItemByCategory(itemList, productMap, ProductTypeEnum.ITEM.getTypeId()), productMap)); todayInCard.setMaterialStock(buildInItemUnitGroupedStockCard(filterInItemByCategory(itemList, productMap, ProductTypeEnum.PRODUCT.getTypeId()), productMap)); todayInCard.setSparePartStock(buildInItemUnitGroupedStockCard(filterInItemByCategory(itemList, productMap, ProductTypeEnum.BEIJIAN.getTypeId()), productMap)); respVO.setTodayIn(todayInCard); @@ -234,7 +230,7 @@ public class ErpStockServiceImpl implements ErpStockService { todayOutCard.setOrderCount((long) stockOutIds.size()); todayOutCard.setItemCount((long) itemList.size()); - todayOutCard.setProductStock(buildOutItemUnitGroupedStockCard(filterOutItemByCategory(itemList, productMap, ProductTypeEnum.ITEM.getTypeId()), productMap)); + todayOutCard.setProductStock(buildOutItemProductStockCard(filterOutItemByCategory(itemList, productMap, ProductTypeEnum.ITEM.getTypeId()), productMap)); todayOutCard.setMaterialStock(buildOutItemUnitGroupedStockCard(filterOutItemByCategory(itemList, productMap, ProductTypeEnum.PRODUCT.getTypeId()), productMap)); todayOutCard.setSparePartStock(buildOutItemUnitGroupedStockCard(filterOutItemByCategory(itemList, productMap, ProductTypeEnum.BEIJIAN.getTypeId()), productMap)); respVO.setTodayOut(todayOutCard); @@ -296,6 +292,46 @@ public class ErpStockServiceImpl implements ErpStockService { return buildUnitStock(firstStock.getUnitId(), firstStock.getUnitName(), count); } + private ErpStockStatisticsRespVO.ProductStockCard buildProductStockCard(List stockList, + Map productMap) { + ErpStockStatisticsRespVO.ProductStockCard card = new ErpStockStatisticsRespVO.ProductStockCard(); + BigDecimal totalCount = sumStockCount(stockList); + String unitName = resolveStockUnitName(stockList, productMap); + card.setSkuCount(countDistinctProduct(stockList)); + card.setTotalCount(totalCount); + card.setUnitName(unitName); + card.setStockDisplay(formatNumber(totalCount) + nullToEmpty(unitName)); + card.setPackagingStocks(buildStockPackagingStocks(stockList, productMap)); + card.setTodayFinishedInDisplay(formatNumber(BigDecimal.ZERO) + nullToEmpty(unitName)); + return card; + } + + private ErpStockStatisticsRespVO.ProductStockCard buildInItemProductStockCard(List itemList, + Map productMap) { + ErpStockStatisticsRespVO.ProductStockCard card = new ErpStockStatisticsRespVO.ProductStockCard(); + BigDecimal totalCount = sumInItemCount(itemList); + String unitName = resolveInItemUnitName(itemList, productMap); + card.setSkuCount(itemList.stream().map(ErpStockInItemDO::getProductId).distinct().count()); + card.setTotalCount(totalCount); + card.setUnitName(unitName); + card.setStockDisplay(formatNumber(totalCount) + nullToEmpty(unitName)); + card.setPackagingStocks(buildInItemPackagingStocks(itemList, productMap)); + return card; + } + + private ErpStockStatisticsRespVO.ProductStockCard buildOutItemProductStockCard(List itemList, + Map productMap) { + ErpStockStatisticsRespVO.ProductStockCard card = new ErpStockStatisticsRespVO.ProductStockCard(); + BigDecimal totalCount = sumOutItemCount(itemList); + String unitName = resolveOutItemUnitName(itemList, productMap); + card.setSkuCount(itemList.stream().map(ErpStockOutItemDO::getProductId).distinct().count()); + card.setTotalCount(totalCount); + card.setUnitName(unitName); + card.setStockDisplay(formatNumber(totalCount) + nullToEmpty(unitName)); + card.setPackagingStocks(buildOutItemPackagingStocks(itemList, productMap)); + return card; + } + private String buildUnitKey(Long unitId, String unitName) { return String.valueOf(unitId) + "_" + String.valueOf(unitName); } @@ -305,7 +341,7 @@ public class ErpStockServiceImpl implements ErpStockService { ErpStockStatisticsRespVO.UnitGroupedStockCard card = new ErpStockStatisticsRespVO.UnitGroupedStockCard(); card.setSkuCount(itemList.stream().map(ErpStockInItemDO::getProductId).distinct().count()); List unitStocks = itemList.stream() - .collect(Collectors.groupingBy(item -> buildProductUnitKey(item.getProductId(), productMap))) + .collect(Collectors.groupingBy(item -> buildInItemUnitKey(item, productMap))) .values().stream() .map(items -> buildInItemUnitStock(items, productMap)) .collect(Collectors.toList()); @@ -321,7 +357,7 @@ public class ErpStockServiceImpl implements ErpStockService { ErpStockStatisticsRespVO.UnitGroupedStockCard card = new ErpStockStatisticsRespVO.UnitGroupedStockCard(); card.setSkuCount(itemList.stream().map(ErpStockOutItemDO::getProductId).distinct().count()); List unitStocks = itemList.stream() - .collect(Collectors.groupingBy(item -> buildProductUnitKey(item.getProductId(), productMap))) + .collect(Collectors.groupingBy(item -> buildOutItemUnitKey(item, productMap))) .values().stream() .map(items -> buildOutItemUnitStock(items, productMap)) .collect(Collectors.toList()); @@ -335,21 +371,17 @@ public class ErpStockServiceImpl implements ErpStockService { private ErpStockStatisticsRespVO.UnitStock buildInItemUnitStock(List itemList, Map productMap) { ErpStockInItemDO firstItem = itemList.get(0); - ErpProductRespVO product = productMap.get(firstItem.getProductId()); BigDecimal count = itemList.stream().map(item -> defaultZero(item.getCount())) .reduce(BigDecimal.ZERO, BigDecimal::add); - return buildUnitStock(product != null ? product.getUnitId() : null, - product != null ? product.getUnitName() : null, count); + return buildUnitStock(resolveInItemUnitId(firstItem, productMap), resolveInItemUnitName(firstItem, productMap), count); } private ErpStockStatisticsRespVO.UnitStock buildOutItemUnitStock(List itemList, Map productMap) { ErpStockOutItemDO firstItem = itemList.get(0); - ErpProductRespVO product = productMap.get(firstItem.getProductId()); BigDecimal count = itemList.stream().map(item -> defaultZero(item.getCount())) .reduce(BigDecimal.ZERO, BigDecimal::add); - return buildUnitStock(product != null ? product.getUnitId() : null, - product != null ? product.getUnitName() : null, count); + return buildUnitStock(resolveOutItemUnitId(firstItem, productMap), resolveOutItemUnitName(firstItem, productMap), count); } private ErpStockStatisticsRespVO.UnitStock buildUnitStock(Long unitId, String unitName, BigDecimal count) { @@ -361,9 +393,188 @@ public class ErpStockServiceImpl implements ErpStockService { return unitStock; } - private String buildProductUnitKey(Long productId, Map productMap) { + private String buildInItemUnitKey(ErpStockInItemDO item, Map productMap) { + return buildUnitKey(resolveInItemUnitId(item, productMap), resolveInItemUnitName(item, productMap)); + } + + private String buildOutItemUnitKey(ErpStockOutItemDO item, Map productMap) { + return buildUnitKey(resolveOutItemUnitId(item, productMap), resolveOutItemUnitName(item, productMap)); + } + + private Long resolveInItemUnitId(ErpStockInItemDO item, Map productMap) { + if (item.getProductUnitId() != null) { + return item.getProductUnitId(); + } + ErpProductRespVO product = productMap.get(item.getProductId()); + return product != null ? product.getUnitId() : null; + } + + private String resolveInItemUnitName(ErpStockInItemDO item, Map productMap) { + if (item.getProductUnitName() != null) { + return item.getProductUnitName(); + } + return resolveCurrentProductUnitName(item.getProductId(), item.getProductUnitId(), productMap); + } + + private Long resolveOutItemUnitId(ErpStockOutItemDO item, Map productMap) { + if (item.getProductUnitId() != null) { + return item.getProductUnitId(); + } + ErpProductRespVO product = productMap.get(item.getProductId()); + return product != null ? product.getUnitId() : null; + } + + private String resolveOutItemUnitName(ErpStockOutItemDO item, Map productMap) { + if (item.getProductUnitName() != null) { + return item.getProductUnitName(); + } + return resolveCurrentProductUnitName(item.getProductId(), item.getProductUnitId(), productMap); + } + + private String resolveCurrentProductUnitName(Long productId, Long unitId, Map productMap) { ErpProductRespVO product = productMap.get(productId); - return product != null ? buildUnitKey(product.getUnitId(), product.getUnitName()) : buildUnitKey(null, null); + if (product == null || (unitId != null && !Objects.equals(unitId, product.getUnitId()))) { + return null; + } + return product.getUnitName(); + } + + private List buildStockPackagingStocks(List stockList, + Map productMap) { + return stockList.stream() + .collect(Collectors.groupingBy(this::buildStockPackagingKey)) + .values().stream() + .map(items -> buildStockPackagingStock(items, productMap)) + .collect(Collectors.toList()); + } + + private ErpStockStatisticsRespVO.ProductPackagingStock buildStockPackagingStock(List stockList, + Map productMap) { + ErpStockDO firstStock = stockList.get(0); + BigDecimal totalCount = sumStockCount(stockList); + String unitName = firstStock.getUnitName(); + ErpStockStatisticsRespVO.ProductPackagingStock detail = buildProductPackagingStock( + firstStock.getProductId(), resolveProductName(firstStock.getProductId(), productMap), + firstStock.getDefaultPackagingSchemeId(), firstStock.getDefaultPackagingSchemeName(), + totalCount, unitName, firstStock.getPackageQuantity(), firstStock.getPalletPackageQuantity(), + firstStock.getPalletTotalQuantity()); + return detail; + } + + private List buildInItemPackagingStocks(List itemList, + Map productMap) { + return itemList.stream() + .collect(Collectors.groupingBy(this::buildInItemPackagingKey)) + .values().stream() + .map(items -> buildInItemPackagingStock(items, productMap)) + .collect(Collectors.toList()); + } + + private ErpStockStatisticsRespVO.ProductPackagingStock buildInItemPackagingStock(List itemList, + Map productMap) { + ErpStockInItemDO firstItem = itemList.get(0); + BigDecimal totalCount = sumInItemCount(itemList); + return buildProductPackagingStock(firstItem.getProductId(), resolveProductName(firstItem.getProductId(), productMap), + firstItem.getPackagingSchemeId(), firstItem.getPackagingSchemeName(), totalCount, + resolveInItemUnitName(firstItem, productMap), firstItem.getPackageQuantity(), + firstItem.getPalletPackageQuantity(), firstItem.getPalletTotalQuantity()); + } + + private List buildOutItemPackagingStocks(List itemList, + Map productMap) { + return itemList.stream() + .collect(Collectors.groupingBy(this::buildOutItemPackagingKey)) + .values().stream() + .map(items -> buildOutItemPackagingStock(items, productMap)) + .collect(Collectors.toList()); + } + + private ErpStockStatisticsRespVO.ProductPackagingStock buildOutItemPackagingStock(List itemList, + Map productMap) { + ErpStockOutItemDO firstItem = itemList.get(0); + BigDecimal totalCount = sumOutItemCount(itemList); + return buildProductPackagingStock(firstItem.getProductId(), resolveProductName(firstItem.getProductId(), productMap), + firstItem.getPackagingSchemeId(), firstItem.getPackagingSchemeName(), totalCount, + resolveOutItemUnitName(firstItem, productMap), firstItem.getPackageQuantity(), + firstItem.getPalletPackageQuantity(), firstItem.getPalletTotalQuantity()); + } + + private ErpStockStatisticsRespVO.ProductPackagingStock buildProductPackagingStock(Long productId, String productName, + Long packagingSchemeId, String packagingSchemeName, + BigDecimal totalCount, String unitName, + BigDecimal packageQuantity, + BigDecimal palletPackageQuantity, + BigDecimal palletTotalQuantity) { + ErpStockStatisticsRespVO.ProductPackagingStock detail = new ErpStockStatisticsRespVO.ProductPackagingStock(); + detail.setProductId(productId); + detail.setProductName(productName); + detail.setPackagingSchemeId(packagingSchemeId); + detail.setPackagingSchemeName(packagingSchemeName); + detail.setTotalCount(totalCount); + detail.setUnitName(unitName); + detail.setStockDisplay(formatNumber(totalCount) + nullToEmpty(unitName)); + detail.setPackagingDisplay(formatProductQuantity(calculateProductQuantity(totalCount, palletTotalQuantity, packageQuantity))); + detail.setPackageQuantity(packageQuantity); + detail.setPalletPackageQuantity(palletPackageQuantity); + detail.setPalletTotalQuantity(palletTotalQuantity); + return detail; + } + + private String buildStockPackagingKey(ErpStockDO stock) { + return buildProductPackagingKey(stock.getProductId(), stock.getDefaultPackagingSchemeId(), stock.getPackageQuantity(), + stock.getPalletPackageQuantity(), stock.getPalletTotalQuantity(), stock.getUnitId(), stock.getUnitName()); + } + + private String buildInItemPackagingKey(ErpStockInItemDO item) { + return buildProductPackagingKey(item.getProductId(), item.getPackagingSchemeId(), item.getPackageQuantity(), + item.getPalletPackageQuantity(), item.getPalletTotalQuantity(), item.getProductUnitId(), item.getProductUnitName()); + } + + private String buildOutItemPackagingKey(ErpStockOutItemDO item) { + return buildProductPackagingKey(item.getProductId(), item.getPackagingSchemeId(), item.getPackageQuantity(), + item.getPalletPackageQuantity(), item.getPalletTotalQuantity(), item.getProductUnitId(), item.getProductUnitName()); + } + + private String buildProductPackagingKey(Long productId, Long packagingSchemeId, BigDecimal packageQuantity, + BigDecimal palletPackageQuantity, BigDecimal palletTotalQuantity, + Long unitId, String unitName) { + return String.valueOf(productId) + "_" + String.valueOf(packagingSchemeId) + "_" + + String.valueOf(packageQuantity) + "_" + String.valueOf(palletPackageQuantity) + "_" + + String.valueOf(palletTotalQuantity) + "_" + buildUnitKey(unitId, unitName); + } + + private BigDecimal sumStockCount(List stockList) { + return stockList.stream().map(stock -> defaultZero(stock.getCount())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private BigDecimal sumInItemCount(List itemList) { + return itemList.stream().map(item -> defaultZero(item.getCount())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private BigDecimal sumOutItemCount(List itemList) { + return itemList.stream().map(item -> defaultZero(item.getCount())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private String resolveStockUnitName(List stockList, Map productMap) { + return stockList.stream().map(ErpStockDO::getUnitName).filter(Objects::nonNull).findFirst() + .orElseGet(() -> stockList.stream().map(stock -> resolveCurrentProductUnitName(stock.getProductId(), stock.getUnitId(), productMap)) + .filter(Objects::nonNull).findFirst().orElse(null)); + } + + private String resolveInItemUnitName(List itemList, Map productMap) { + return itemList.stream().map(item -> resolveInItemUnitName(item, productMap)).filter(Objects::nonNull).findFirst().orElse(null); + } + + private String resolveOutItemUnitName(List itemList, Map productMap) { + return itemList.stream().map(item -> resolveOutItemUnitName(item, productMap)).filter(Objects::nonNull).findFirst().orElse(null); + } + + private String resolveProductName(Long productId, Map productMap) { + ErpProductRespVO product = productMap.get(productId); + return product != null ? product.getName() : null; } private Integer resolveCategoryType(ErpStockDO stock, Map productMap) { @@ -419,6 +630,10 @@ public class ErpStockServiceImpl implements ErpStockService { return display.toString(); } + private String formatProductQuantity(ProductQuantity quantity) { + return formatProductQuantity(quantity.getPalletCount(), quantity.getPackageCount(), quantity.getPieceCount()); + } + private void appendDisplay(StringBuilder display, BigDecimal quantity, String unitName, boolean keepZero) { if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { if (!keepZero) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DevicePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DevicePageReqVO.java index 3b592f9c0..7823c8c49 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DevicePageReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DevicePageReqVO.java @@ -27,6 +27,9 @@ public class DevicePageReqVO extends PageParam { @Schema(description = "状态", example = "1") private String status; + @Schema(description = "运行状态", example = "1") + private String operatingStatus; + @Schema(description = "读主题") private String readTopic; @@ -80,4 +83,4 @@ public class DevicePageReqVO extends PageParam { @Schema(description = "mqtt订阅主题") private String topic; -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceMapper.java index d8f8f4295..a1d3fd804 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceMapper.java @@ -84,6 +84,7 @@ public interface DeviceMapper extends BaseMapperX { return selectList(new LambdaQueryWrapperX() .eqIfPresent(DeviceDO::getDeviceCode, reqVO.getDeviceCode()) .likeIfPresent(DeviceDO::getDeviceName, reqVO.getDeviceName()) + .likeIfPresent(DeviceDO::getTopic, reqVO.getTopic()) .eqIfPresent(DeviceDO::getDeviceType, reqVO.getDeviceType()) .eqIfPresent(DeviceDO::getStatus, reqVO.getStatus()) .eqIfPresent(DeviceDO::getReadTopic, reqVO.getReadTopic()) @@ -94,6 +95,7 @@ public interface DeviceMapper extends BaseMapperX { .betweenIfPresent(DeviceDO::getLastOnlineTime, reqVO.getLastOnlineTime()) .eqIfPresent(DeviceDO::getRemark, reqVO.getRemark()) .eqIfPresent(DeviceDO::getIsEnable, reqVO.getIsEnable()) + .betweenIfPresent(DeviceDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(DeviceDO::getId)); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java index 446faace5..a219ccd99 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java @@ -357,6 +357,10 @@ public class DeviceServiceImpl implements DeviceService { public PageResult getDevicePage(DevicePageReqVO pageReqVO) { // 1. 查询分页设备 + if (StringUtils.isNotBlank(pageReqVO.getOperatingStatus())) { + return getDevicePageByOperatingStatus(pageReqVO); + } + PageResult deviceDOPageResult = deviceMapper.selectPage(pageReqVO); List deviceIds = deviceDOPageResult.getList().stream() .map(DeviceDO::getId) @@ -414,6 +418,69 @@ public class DeviceServiceImpl implements DeviceService { return deviceRespVOPageResult; } + private PageResult getDevicePageByOperatingStatus(DevicePageReqVO pageReqVO) { + List deviceList = deviceMapper.selectList(pageReqVO); + List deviceRespVOList = BeanUtils.toBean(deviceList, DeviceRespVO.class); + + List deviceIds = deviceRespVOList.stream() + .map(DeviceRespVO::getId) + .collect(Collectors.toList()); + Map latestTsMap = tdengineService.newSelectLatestTsBatch(deviceIds); + Map latestRecordMap = tdengineService.selectLatestByDeviceAndRuleMinimal(deviceIds, null).stream() + .collect(Collectors.toMap( + DeviceOperationRecordDO::getDeviceId, + r -> r, + (r1, r2) -> r1.getCreateTime().isAfter(r2.getCreateTime()) ? r1 : r2 + )); + + for (DeviceRespVO deviceRespVO : deviceRespVOList) { + Long deviceId = deviceRespVO.getId(); + if (latestTsMap.get(deviceId) != null) { + deviceRespVO.setCollectionTime(latestTsMap.get(deviceId)); + } + deviceRespVO.setOperatingStatus(resolveDeviceOperatingStatus(latestRecordMap.get(deviceId))); + } + + List filteredList = deviceRespVOList.stream() + .filter(device -> matchesOperatingStatus(device.getOperatingStatus(), pageReqVO.getOperatingStatus())) + .collect(Collectors.toList()); + return new PageResult<>(subPage(filteredList, pageReqVO), (long) filteredList.size()); + } + + private String resolveDeviceOperatingStatus(DeviceOperationRecordDO record) { + if (record == null) { + return DeviceStatusEnum.OFFLINE.getName(); + } + DeviceStatusEnum status = DeviceStatusEnum.getByCode(record.getRule()); + return status != null ? status.getName() : DeviceStatusEnum.OFFLINE.getName(); + } + + private boolean matchesOperatingStatus(String actualStatus, String expectedStatus) { + if (StringUtils.equals(actualStatus, expectedStatus)) { + return true; + } + for (DeviceStatusEnum status : DeviceStatusEnum.values()) { + if ((StringUtils.equals(status.getCode(), expectedStatus) + || StringUtils.equals(status.getName(), expectedStatus) + || StringUtils.equalsIgnoreCase(status.name(), expectedStatus)) + && StringUtils.equals(status.getName(), actualStatus)) { + return true; + } + } + return false; + } + + private List subPage(List list, DevicePageReqVO pageReqVO) { + if (Objects.equals(pageReqVO.getPageSize(), PageParam.PAGE_SIZE_NONE)) { + return list; + } + int pageNo = pageReqVO.getPageNo() != null ? pageReqVO.getPageNo() : 1; + int pageSize = pageReqVO.getPageSize() != null ? pageReqVO.getPageSize() : 10; + int fromIndex = Math.min((pageNo - 1) * pageSize, list.size()); + int toIndex = Math.min(fromIndex + pageSize, list.size()); + return list.subList(fromIndex, toIndex); + } + @Override public List selectList(DevicePageReqVO reqVO){ return deviceMapper.selectList(reqVO); From 0758f2de3acae22a8e371c6cf39b2a0c8b4cfaeb Mon Sep 17 00:00:00 2001 From: HuangHuiKang Date: Wed, 1 Jul 2026 11:34:29 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Diot=E9=87=87=E9=9B=86?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeviceModelAttributeMapper.java | 2 +- .../iot/service/device/DeviceServiceImpl.java | 97 ++++++++++++++----- .../iot/service/device/TDengineService.java | 8 +- 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicemodelattribute/DeviceModelAttributeMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicemodelattribute/DeviceModelAttributeMapper.java index b0d111d3f..bccbd81e3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicemodelattribute/DeviceModelAttributeMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicemodelattribute/DeviceModelAttributeMapper.java @@ -47,7 +47,7 @@ public interface DeviceModelAttributeMapper extends BaseMapperXlambdaQuery().eq(DeviceDO::getTopic, topic)); + private void validTopicExists(String topic, Long currentDeviceId) { + if (StringUtils.isBlank(topic)) { + return; + } + boolean exists = deviceMapper.exists(Wrappers.lambdaQuery() + .eq(DeviceDO::getTopic, topic) + .ne(currentDeviceId != null, DeviceDO::getId, currentDeviceId)); if (exists){ throw exception(DEVICE_MQTT_TOPIC_ALREADY_EXIST); } } + private void disableGatewayByDevice(DeviceDO device) { + if (device == null) { + return; + } + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() + .set(GatewayDO::getIsEnable, false) + .set(GatewayDO::getUpdateTime, LocalDateTime.now()); + if (StringUtils.isNotBlank(device.getTopic())) { + updateWrapper.eq(GatewayDO::getTopic, device.getTopic()); + } else if (StringUtils.isNotBlank(device.getDeviceCode())) { + updateWrapper.eq(GatewayDO::getGatewayCode, device.getDeviceCode()); + } else { + return; + } + if (StringUtils.isNotBlank(device.getDeviceCode())) { + updateWrapper.or().eq(GatewayDO::getGatewayCode, device.getDeviceCode()); + } + gatewayMapper.update(null, updateWrapper); + } + + private void removeRuntimeTopic(String topic) { + if (StringUtils.isBlank(topic) || defaultBizTopicSet == null || defaultBizTopicSet.getTopicMap() == null) { + return; + } + synchronized (defaultBizTopicSet.getTopicMap()) { + defaultBizTopicSet.getTopicMap().removeIf(item -> topic.equals(item.getSubTopic())); + } + } + @Override @Transactional(rollbackFor = Exception.class) public void deleteDevice(List ids) { @@ -323,10 +362,12 @@ public class DeviceServiceImpl implements DeviceService { deviceContactModelMapper.delete(deviceContactModelDOLambdaQueryWrapper); } - private void validateDeviceExists(Long id) { - if (deviceMapper.selectById(id) == null) { + private DeviceDO validateDeviceExists(Long id) { + DeviceDO device = deviceMapper.selectById(id); + if (device == null) { throw exception(DEVICE_NOT_EXISTS); } + return device; } @Override @@ -795,6 +836,7 @@ public class DeviceServiceImpl implements DeviceService { } @Override +// @Transactional(rollbackFor = Exception.class) public Long copyDevice(Long id) { if (id == null){ throw exception(DEVICE_ID_MODEL_NOT_EXISTS); @@ -811,6 +853,7 @@ public class DeviceServiceImpl implements DeviceService { int randomNumber = random.nextInt(9000) + 1000; newDevice.setDeviceCode(deviceDO.getDeviceCode()+ "-" + randomNumber); newDevice.setDeviceName(deviceDO.getDeviceName() + "-副本"); + newDevice.setTopic(null); deviceMapper.insert(newDevice); //复制关联表 @@ -818,14 +861,20 @@ public class DeviceServiceImpl implements DeviceService { lambdaQueryWrapper.eq(DeviceContactModelDO::getDeviceId,deviceDO.getId()); List deviceContactModelDOS = deviceContactModelMapper.selectList(lambdaQueryWrapper); - + List newContactModelList = new ArrayList<>(); if (deviceContactModelDOS != null && !deviceContactModelDOS.isEmpty()){ - for (DeviceContactModelDO deviceModelAttributeDOS : deviceContactModelDOS) { - deviceModelAttributeDOS.setId(null); - deviceModelAttributeDOS.setDeviceId(newDevice.getId()); + for (DeviceContactModelDO deviceContactModelDO : deviceContactModelDOS) { + DeviceContactModelDO newContactModel = new DeviceContactModelDO(); + BeanUtils.copyProperties(deviceContactModelDO, newContactModel); + newContactModel.setId(null); + newContactModel.setDeviceId(newDevice.getId()); + newContactModel.setCreateTime(LocalDateTime.now()); + newContactModel.setUpdateTime(LocalDateTime.now()); + newContactModelList.add(newContactModel); } - deviceContactModelMapper.insertBatch(deviceContactModelDOS); + deviceContactModelMapper.insertBatch(newContactModelList); } + tdengineService.createTdengineTable(newDevice.getId(), newContactModelList); return newDevice.getId(); @@ -2032,12 +2081,7 @@ public class DeviceServiceImpl implements DeviceService { throw exception(DEVICE_MQTT_TOPIC_EXIST); } - // 2. 如果状态没有变化,直接返回 - if (Objects.equals(deviceDO.getIsEnable(), updateEnabledReqVO.getEnabled())) { - return; - } - - // 3. 执行状态更新操作 + // 2. 执行状态同步操作:重复启停也要同步 MQTT 与 gateway,保证幂等 executeEnableUpdate(deviceDO, updateEnabledReqVO.getEnabled()); @@ -2051,6 +2095,8 @@ public class DeviceServiceImpl implements DeviceService { private void executeEnableUpdate(DeviceDO deviceDO, Boolean enabled) { + MqttException syncException = null; + try { // 1. 更新设备启用状态 deviceDO.setIsEnable(enabled); @@ -2066,6 +2112,7 @@ public class DeviceServiceImpl implements DeviceService { mqttService.subscribeTopic(topic); log.info("MQTT订阅成功: {}", topic); } catch (MqttException e) { + syncException = e; log.error("MQTT订阅失败: {}", topic, e); } @@ -2091,6 +2138,8 @@ public class DeviceServiceImpl implements DeviceService { } else { //更新gateway状态 + gateway.setGatewayName(deviceDO.getDeviceName()); + gateway.setGatewayCode(deviceDO.getDeviceCode()); gateway.setIsEnable(true); gateway.setUpdateTime(LocalDateTime.now()); @@ -2109,17 +2158,14 @@ public class DeviceServiceImpl implements DeviceService { mqttService.unsubscribeTopic(topic); log.info("MQTT取消订阅成功: {}", topic); } catch (MqttException e) { + syncException = e; log.error("MQTT取消订阅失败: {}", topic, e); + } finally { + removeRuntimeTopic(topic); } // 3.2 更新gateway状态为禁用 - gatewayMapper.update( - null, - new LambdaUpdateWrapper() - .eq(GatewayDO::getTopic, topic) - .set(GatewayDO::getIsEnable, false) - .set(GatewayDO::getUpdateTime, LocalDateTime.now()) - ); + disableGatewayByDevice(deviceDO); log.info("gateway订阅记录已禁用 topic={}", topic); @@ -2130,9 +2176,14 @@ public class DeviceServiceImpl implements DeviceService { } + if (syncException != null) { + throw new RuntimeException("MQTT订阅状态同步失败", syncException); + } + } catch (Exception e) { log.error("更新设备状态失败 deviceCode={}", deviceDO.getDeviceCode(), e); + throw new RuntimeException("更新设备状态失败", e); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java index e2654f0b6..26b1972af 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java @@ -779,6 +779,8 @@ public class TDengineService { continue; } + validateColumnName(attributeCode); + String tdType = JavaToTdengineTypeEnum.getTdTypeByJavaType(dataType); if (tdType == null) { tdType = "DOUBLE"; @@ -1292,7 +1294,7 @@ public class TDengineService { Set reservedWords = new HashSet<>(Arrays.asList( "value", "timestamp", "current", "database", "table", "user", "password", "select", "insert", "update", "delete", - "create", "drop", "alter", "show", "describe", "use", "ts" + "create", "drop", "alter", "show", "describe", "use", "ts", "state" )); return reservedWords.contains(columnName.toLowerCase()); @@ -1312,7 +1314,7 @@ public class TDengineService { Set reservedKeywords = new HashSet<>(Arrays.asList( "value", "timestamp", "current", "database", "table", "user", "password", "select", "insert", "update", "delete", "create", "drop", "alter", - "show", "describe", "use", "ts", "now", "current_timestamp" + "show", "describe", "use", "ts", "now", "current_timestamp", "state" )); if (reservedKeywords.contains(columnName.toLowerCase())) { @@ -1359,7 +1361,7 @@ public class TDengineService { Set reservedKeywords = new HashSet<>(Arrays.asList( "value", "timestamp", "current", "database", "table", "user", "password", "select", "insert", "update", "delete", "create", "drop", "alter", - "show", "describe", "use", "ts", "now", "current_timestamp" + "show", "describe", "use", "ts", "now", "current_timestamp", "state" )); if (reservedKeywords.contains(tableName.toLowerCase())) {