Преглед на файлове

pms 看板 最近 6个月的 运维成本 单位 元

zhangcl преди 5 часа
родител
ревизия
45c2e7b6ec

+ 17 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java

@@ -325,6 +325,23 @@ public class DateUtils {
         return dates;
     }
 
+    /**
+     * 最近6个自然月时间范围,包含当前月。
+     * 当前 2026-09 => 开始 2026-04-01 00:00:00,结束当前时间
+     */
+    public static LocalDateTime[] lastSixMonthsRange() {
+        // 截断到秒,去掉 .291 这种纳秒部分
+        LocalDateTime now = LocalDateTime.now().withNano(0);
+
+        YearMonth endMonth = YearMonth.from(now);
+        YearMonth startMonth = endMonth.minusMonths(5);
+
+        return new LocalDateTime[]{
+                startMonth.atDay(1).atStartOfDay(),
+                now
+        };
+    }
+
     /**
      * 获取指定日期的开始时间
      * @param dateStr 日期字符串,格式为yyyy-MM-dd

+ 4 - 10
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/stat/IotRepairController.java

@@ -4,12 +4,8 @@ import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
-import cn.iocoder.yudao.module.pms.controller.admin.inspect.order.vo.IotInspectOrderPageReqVO;
-import cn.iocoder.yudao.module.pms.controller.admin.inspect.order.vo.IotInspectOrderRespVO;
-import cn.iocoder.yudao.module.pms.controller.admin.inspect.order.vo.IotReportInspectOrderRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.maintain.vo.IotMaintainPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.stat.vo.ReportCost;
@@ -22,7 +18,6 @@ import com.google.common.collect.ImmutableMap;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.AllArgsConstructor;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -82,8 +77,7 @@ public class IotRepairController {
                 .map(fee -> Objects.isNull(fee) ? BigDecimal.ZERO : fee)
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
 
-        BigDecimal tbByFee = new BigDecimal("0");
-        BigDecimal hbByFee = new BigDecimal("0");
+        BigDecimal tbByFee = new BigDecimal("0");BigDecimal hbByFee = new BigDecimal("0");
         // 同比 去年同期相同统计数据对比 无数据
         tbByFee = tbByFee==null?BigDecimal.ZERO:tbByFee;
         //获取环比
@@ -174,12 +168,12 @@ public class IotRepairController {
                     .map(fee -> Objects.isNull(fee) ? BigDecimal.ZERO : fee)
                     .reduce(BigDecimal.ZERO, BigDecimal::add);
         }
+
         // 查询保养费用
         Map<String, Object> byMap = new HashMap<>();
         byMap.put("total", byFee);
         byMap.put("hb", hbByFee == null || (hbByFee.compareTo(BigDecimal.ZERO)==0) ? hbByFee :((byFee.subtract(hbByFee)).divide(hbByFee,2, RoundingMode.HALF_UP)));
         byMap.put("tb", tbByFee==null||(tbByFee.compareTo(BigDecimal.ZERO)==0)?tbByFee:((byFee.subtract(tbByFee)).divide(tbByFee,2, RoundingMode.HALF_UP)));
-
         Map<String, Object> totalMap = new HashMap<>();
         totalMap.put("total", totalFee);
         totalMap.put("hb", hbTotalFee==null|| hbTotalFee.equals(BigDecimal.ZERO) ? hbTotalFee :((totalFee.subtract(hbTotalFee)).divide(hbTotalFee,2, RoundingMode.HALF_UP)));
@@ -188,8 +182,8 @@ public class IotRepairController {
         outMap.put("total", outFee);
         outMap.put("hb", hbOutFee==null||hbOutFee.equals(BigDecimal.ZERO)?hbOutFee:((outFee.subtract(hbOutFee)).divide(hbOutFee,2, RoundingMode.HALF_UP)));
         outMap.put("tb", tbOutFee==null||tbOutFee.equals(BigDecimal.ZERO)?tbOutFee:((outFee.subtract(tbOutFee)).divide(tbOutFee,2, RoundingMode.HALF_UP)));
-        return CommonResult.success(ImmutableMap.of("repair", totalMap,
-                "out", outMap, "byFee", byMap));
+
+        return CommonResult.success(ImmutableMap.of("repair", totalMap, "out", outMap, "byFee", byMap));
     }
 
 

+ 115 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/stat/IotStaticController.java

@@ -127,6 +127,7 @@ import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -859,6 +860,120 @@ public class IotStaticController {
         return success(ImmutableMap.of("xAxis", xAxis, "series", outData));
     }
 
+    /**
+     * 最近6个月的运维成本 维修费用  保养费用
+     * @param dept
+     * @return
+     */
+    @GetMapping("/rh/month-ywcb/{dept}")
+    @PermitAll
+    public CommonResult<Map<String, Object>> getMonthYwcbStat(@PathVariable("dept") String dept) {
+        Set<Long> ids = getDeptIds(dept);
+
+        // 最近6个自然月,包含当前月:2026-09 => 2026-04 ~ 2026-09
+        LocalDateTime[] range = lastSixMonthsRange();
+
+        // 定义月份格式化器
+        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
+
+        // 查询维修成本
+        IotMaintainPageReqVO maintainPageReqVO = new IotMaintainPageReqVO();
+        maintainPageReqVO.setDeptIds(ids);
+        maintainPageReqVO.setCreateTime(range);
+        maintainPageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotMaintainDO> repairOrders = iotMaintainService.getIotMaintainPage(maintainPageReqVO).getList();
+        // 计算每个月的维修费用
+        // 2. 计算每个月的维修费用  key: 2026-04
+        LinkedHashMap<String, BigDecimal> repairMonthMap =
+                sumByMonth(repairOrders, IotMaintainDO::getCreateTime, IotMaintainDO::getMaintainFee);
+
+        // 查询保养成本
+        IotMainWorkOrderPageReqVO workOrderReqVO = new IotMainWorkOrderPageReqVO();
+        workOrderReqVO.setDeptIds(ids);
+        workOrderReqVO.setCreateTime(range);
+        List<IotMainWorkOrderDO> workOrders = iotMainWorkOrderService.workOrders(workOrderReqVO);
+        // 计算每个月的保养费用
+        // 4. 计算每个月的保养费用  key: 2026-04
+        LinkedHashMap<String, BigDecimal> maintainMonthMap =
+                sumByMonth(workOrders, IotMainWorkOrderDO::getCreateTime, IotMainWorkOrderDO::getCost);
+
+        // 按月份分组汇总保养费用
+        Map<String, BigDecimal> monthlyFeeMap = workOrders.stream()
+                .filter(Objects::nonNull) // 过滤掉 null 工单(可选)
+                .collect(Collectors.groupingBy(
+                        order -> order.getCreateTime().format(monthFormatter), // 分组键:yyyy-MM
+                        TreeMap::new, // 使用 TreeMap 保证月份有序(可选)
+                        Collectors.reducing(
+                                BigDecimal.ZERO, // 初始值
+                                order -> Optional.ofNullable(order.getCost()).orElse(BigDecimal.ZERO), // 处理 null
+                                BigDecimal::add // 累加
+                        )
+                ));
+
+        // 维修费用 + 保养费用,按月份合并
+        LinkedHashMap<String, BigDecimal> totalMap = new LinkedHashMap<>();
+        YearMonth now = YearMonth.now();
+        for (int i = 5; i >= 0; i--) {
+            totalMap.put(now.minusMonths(i).toString(), BigDecimal.ZERO);
+        }
+        repairMonthMap.forEach((month, cost) -> totalMap.merge(month, cost, BigDecimal::add));
+        maintainMonthMap.forEach((month, cost) -> totalMap.merge(month, cost, BigDecimal::add));
+
+        LinkedList<Object> xAxis = new LinkedList<>(totalMap.keySet());
+        LinkedList<Object> outData = new LinkedList<>(totalMap.values());
+
+        return success(ImmutableMap.of("xAxis", xAxis, "series", outData));
+    }
+
+    private <T> LinkedHashMap<String, BigDecimal> sumByMonth(List<T> list,
+                                                             Function<T, LocalDateTime> timeGetter,
+                                                             Function<T, BigDecimal> amountGetter) {
+        return list.stream()
+                .filter(o -> timeGetter.apply(o) != null)
+                .collect(Collectors.groupingBy(
+                        o -> YearMonth.from(timeGetter.apply(o)).toString(),
+                        LinkedHashMap::new,
+                        Collectors.reducing(BigDecimal.ZERO,
+                                o -> Optional.ofNullable(amountGetter.apply(o)).orElse(BigDecimal.ZERO),
+                                BigDecimal::add)));
+    }
+
+    /**
+     * 按月份统计成本,包含当前月。
+     */
+    private <T> LinkedHashMap<String, BigDecimal> countCostByMonth(
+            List<T> records,
+            Function<T, LocalDateTime> timeGetter,
+            Function<T, BigDecimal> amountGetter,
+            int months) {
+
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
+
+        YearMonth endMonth = YearMonth.now();
+        YearMonth startMonth = endMonth.minusMonths(months - 1L);
+
+        Map<String, BigDecimal> amountMap = records.stream()
+                .filter(r -> timeGetter.apply(r) != null)
+                .collect(Collectors.groupingBy(
+                        r -> timeGetter.apply(r).format(formatter),
+                        Collectors.reducing(
+                                BigDecimal.ZERO,
+                                r -> {
+                                    BigDecimal amount = amountGetter.apply(r);
+                                    return amount == null ? BigDecimal.ZERO : amount;
+                                },
+                                BigDecimal::add
+                        )
+                ));
+
+        LinkedHashMap<String, BigDecimal> result = new LinkedHashMap<>();
+        for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) {
+            String key = month.format(formatter);
+            result.put(key, amountMap.getOrDefault(key, BigDecimal.ZERO));
+        }
+        return result;
+    }
+
     @GetMapping("/rh/order/{dept}")
     @PermitAll
     public CommonResult<Map<String, Object>> getOrderNumberStat(@PathVariable("dept") String dept) {