From fe4d8530e45569a8b17886f4bd050e96e187fb8d Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期一, 30 三月 2026 16:26:24 +0800
Subject: [PATCH] feat: 生产成品核算报表接口,生产订单绑定工艺路线

---
 src/main/java/com/ruoyi/production/dto/ProductionCostAccountDto.java                    |   42 ++
 src/main/java/com/ruoyi/production/utils/UnitUtils.java                                 |   66 ++++
 src/main/java/com/ruoyi/production/mapper/ProductionOrderRouteMapper.java               |   16 +
 src/main/java/com/ruoyi/production/controller/ProductionCostAccountController.java      |   72 ++++
 src/main/java/com/ruoyi/production/vo/ProductionCostSummaryVo.java                      |   33 ++
 src/main/java/com/ruoyi/production/controller/ProductionOrderRouteController.java       |   20 +
 src/main/resources/mapper/production/ProductionOrderRouteMapper.xml                     |   20 +
 src/main/java/com/ruoyi/production/service/impl/ProductionCostServiceImpl.java          |  194 ++++++++++++
 src/main/java/com/ruoyi/production/dto/GroupKeyDto.java                                 |   61 +++
 src/main/java/com/ruoyi/production/vo/ProductionCostAggregationVo.java                  |   46 ++
 src/main/java/com/ruoyi/production/vo/ProductionCostDetailVo.java                       |   51 +++
 src/main/resources/mapper/production/ProductOrderMapper.xml                             |    2 
 doc/宁夏-中盛建材.sql                                                                         |   24 +
 src/main/java/com/ruoyi/production/mapper/ProductionCostMapper.java                     |   31 +
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderRouteServiceImpl.java    |   20 +
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderAppendixServiceImpl.java |   56 ++
 src/main/java/com/ruoyi/production/service/ProductionCostService.java                   |   46 ++
 src/main/java/com/ruoyi/production/service/IProductionOrderRouteService.java            |   16 +
 src/main/resources/mapper/production/ProductionCostMapper.xml                           |   89 +++++
 19 files changed, 889 insertions(+), 16 deletions(-)

diff --git "a/doc/\345\256\201\345\244\217-\344\270\255\347\233\233\345\273\272\346\235\220.sql" "b/doc/\345\256\201\345\244\217-\344\270\255\347\233\233\345\273\272\346\235\220.sql"
index 7ea4756..9f95867 100644
--- "a/doc/\345\256\201\345\244\217-\344\270\255\347\233\233\345\273\272\346\235\220.sql"
+++ "b/doc/\345\256\201\345\244\217-\344\270\255\347\233\233\345\273\272\346\235\220.sql"
@@ -446,4 +446,26 @@
     DROP COLUMN `dict_code`;
 
 ALTER TABLE `product-inventory-management-zsjc`.`production_product_output`
-    ADD COLUMN `total_quantity` decimal(20, 15) NULL COMMENT '鎬绘暟閲�' AFTER `scrap_qty`;
\ No newline at end of file
+    ADD COLUMN `total_quantity` decimal(20, 15) NULL COMMENT '鎬绘暟閲�' AFTER `scrap_qty`;
+
+CREATE TABLE `production_order_route`
+(
+    `id`                 bigint                                                        NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+    `order_id`           bigint                                                        NOT NULL COMMENT '鍏宠仈鐢熶骇璁㈠崟ID (production_order.id)',
+    `process_route_id`   bigint                                                                 DEFAULT NULL COMMENT '鍘熷宸ヨ壓璺嚎ID (process_route.id)',
+    `product_model_id`   bigint                                                                 DEFAULT '0' COMMENT '浜у搧id',
+    `process_route_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci          DEFAULT NULL COMMENT '宸ヨ壓璺嚎缂栫爜',
+    `bom_id`             int                                                                    DEFAULT NULL COMMENT '鍏宠仈bom鐨刬d',
+    `dict_code`          bigint                                                        NOT NULL COMMENT '浜у搧绫诲瀷瀛楀吀缂栫爜',
+    `description`        varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '鎻忚堪',
+    `tenant_id`          bigint                                                        NOT NULL COMMENT '绉熸埛id',
+    `create_by`          varchar(64)                                                            DEFAULT '' COMMENT '鍒涘缓鑰�',
+    `create_time`        datetime                                                               DEFAULT NULL COMMENT '褰曞叆鏃堕棿',
+    `update_by`          varchar(64)                                                            DEFAULT '' COMMENT '鏇存柊鑰�',
+    `update_time`        datetime                                                               DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+    `remark`             varchar(500)                                                           DEFAULT NULL COMMENT '澶囨敞',
+    PRIMARY KEY (`id`) USING BTREE,
+    KEY `idx_order_id` (`order_id`) USING BTREE
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COLLATE = utf8mb4_0900_ai_ci COMMENT ='鐢熶骇璁㈠崟缁戝畾鐨勫伐鑹鸿矾绾胯〃';
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/production/controller/ProductionCostAccountController.java b/src/main/java/com/ruoyi/production/controller/ProductionCostAccountController.java
new file mode 100644
index 0000000..359bd5c
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/controller/ProductionCostAccountController.java
@@ -0,0 +1,72 @@
+package com.ruoyi.production.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.production.dto.ProductionCostAccountDto;
+import com.ruoyi.production.service.ProductionCostService;
+import com.ruoyi.production.vo.ProductionCostAggregationVo;
+import com.ruoyi.production.vo.ProductionCostDetailVo;
+import com.ruoyi.production.vo.ProductionCostSummaryVo;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import java.util.List;
+
+/**
+ * <br>
+ * 鎴愭湰鏍哥畻鎺у埗灞�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30
+ */
+@RestController
+@RequestMapping("/cost")
+@ApiModel(value = "ProductionCostAccountController绫�", description = "鎴愭湰鏍哥畻鎺у埗灞�")
+public class ProductionCostAccountController {
+
+    @Autowired
+    private ProductionCostService productionCostService;
+
+    //****************************************************          鐢熶骇鎴愬搧鏍哥畻         *****************************************************  //
+
+    @GetMapping("/productionCost/summary")
+    @ApiOperation("椤堕儴缁熻鍗$墖鏁版嵁")
+    public AjaxResult getCostSummary(ProductionCostAccountDto dto) {
+        ProductionCostSummaryVo costSummary = productionCostService.getCostSummary(dto);
+        return AjaxResult.success(costSummary);
+    }
+
+    @GetMapping("/productionCost/aggregate/product")
+    @ApiOperation("鎸変骇鍝佺墿鏂欐眹鎬�(鍒嗛〉)")
+    public AjaxResult getProductAggregation(Page<ProductionCostAggregationVo> page, ProductionCostAccountDto dto) {
+        IPage<ProductionCostAggregationVo> aggregation = productionCostService.getProductAggregationPage(page, dto);
+        return AjaxResult.success(aggregation);
+    }
+
+    @GetMapping("/productionCost/aggregate/order")
+    @ApiOperation("鎸夌敓浜ц鍗曟眹鎬�(鍒嗛〉)")
+    public AjaxResult getOrderAggregation(Page<ProductionCostAggregationVo> page, ProductionCostAccountDto dto) {
+        IPage<ProductionCostAggregationVo> aggregation = productionCostService.getOrderAggregationPage(page, dto);
+        return AjaxResult.success(aggregation);
+    }
+
+    @GetMapping("/productionCost/top/product")
+    @ApiOperation("娑堣�楁帓琛屾-浜у搧鐗╂枡Top10")
+    public AjaxResult getProductTop10(ProductionCostAccountDto dto) {
+        List<ProductionCostAggregationVo> list = productionCostService.getProductTop(dto);
+        return AjaxResult.success(list);
+    }
+
+    @GetMapping("/productionCost/top/order")
+    @ApiOperation("鎴愭湰鎺掕姒�-鐢熶骇璁㈠崟Top10")
+    public AjaxResult getOrderTop10(ProductionCostAccountDto dto) {
+        List<ProductionCostAggregationVo> list = productionCostService.getOrderTop(dto);
+        return AjaxResult.success(list);
+    }
+}
diff --git a/src/main/java/com/ruoyi/production/controller/ProductionOrderRouteController.java b/src/main/java/com/ruoyi/production/controller/ProductionOrderRouteController.java
new file mode 100644
index 0000000..4d12d2e
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/controller/ProductionOrderRouteController.java
@@ -0,0 +1,20 @@
+package com.ruoyi.production.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 鐢熶骇璁㈠崟缁戝畾鐨勫伐鑹鸿矾绾胯〃 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author deslrey
+ * @since 2026-03-30
+ */
+@RestController
+@RequestMapping("/production-order-route")
+public class ProductionOrderRouteController {
+
+}
diff --git a/src/main/java/com/ruoyi/production/dto/GroupKeyDto.java b/src/main/java/com/ruoyi/production/dto/GroupKeyDto.java
new file mode 100644
index 0000000..afda6c4
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/dto/GroupKeyDto.java
@@ -0,0 +1,61 @@
+package com.ruoyi.production.dto;
+
+import com.ruoyi.production.service.impl.ProductionCostServiceImpl;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+/**
+ * <br>
+ * 鑱氬悎鍒嗙粍 Key 瀵硅薄
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30 16:16
+ */
+@ApiModel(value = "GroupKeyDto绫�", description = "鑱氬悎鍒嗙粍 Key 瀵硅薄")
+public class GroupKeyDto {
+
+    @ApiModelProperty("鏃ユ湡")
+    private final LocalDateTime date;
+
+    @ApiModelProperty("绫诲埆/璁㈠崟鍚嶇О")
+    private final String name;
+
+    @ApiModelProperty("瑙勬牸鍨嬪彿")
+    private final String model;
+
+    @ApiModelProperty("浜у搧绫诲瀷/寮哄害")
+    private final String strength;
+
+    @ApiModelProperty("鍗曚綅")
+    private final String unit;
+
+    public GroupKeyDto(LocalDateTime date, String name, String model, String strength, String unit) {
+        this.date = date;
+        this.name = name;
+        this.model = model;
+        this.strength = strength;
+        this.unit = unit;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        GroupKeyDto groupKey = (GroupKeyDto) o;
+        return Objects.equals(date, groupKey.date) &&
+                Objects.equals(name, groupKey.name) &&
+                Objects.equals(model, groupKey.model) &&
+                Objects.equals(strength, groupKey.strength) &&
+                Objects.equals(unit, groupKey.unit);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(date, name, model, strength, unit);
+    }
+}
diff --git a/src/main/java/com/ruoyi/production/dto/ProductionCostAccountDto.java b/src/main/java/com/ruoyi/production/dto/ProductionCostAccountDto.java
new file mode 100644
index 0000000..e0db06c
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/dto/ProductionCostAccountDto.java
@@ -0,0 +1,42 @@
+package com.ruoyi.production.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+
+/**
+ * <br>
+ * 鎴愭湰鏍哥畻Dto
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30
+ */
+@Data
+@ApiModel(value = "ProductionCostAccountDto绫�", description = "鎴愭湰鏍哥畻Dto")
+public class ProductionCostAccountDto {
+
+    @ApiModelProperty("璧峰鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private LocalDate startDate;
+
+    @ApiModelProperty("鎴鏃ユ湡")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private LocalDate endDate;
+
+    @ApiModelProperty("浜у搧绫诲埆瀛楀吀")
+    private Long dictCode;
+
+    @ApiModelProperty("鐢熶骇璁㈠崟Id")
+    private Long productOrderId;
+
+    @ApiModelProperty("鍒嗙粍绫诲瀷锛�1: 鎸変骇鍝佺被鍒眹鎬�, 2: 鎸夌敓浜ц鍗曟眹鎬伙級")
+    private Integer groupType;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/production/mapper/ProductionCostMapper.java b/src/main/java/com/ruoyi/production/mapper/ProductionCostMapper.java
new file mode 100644
index 0000000..2ca31d9
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/mapper/ProductionCostMapper.java
@@ -0,0 +1,31 @@
+package com.ruoyi.production.mapper;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.ruoyi.production.dto.ProductionCostAccountDto;
+import com.ruoyi.production.vo.ProductionCostAggregationVo;
+import com.ruoyi.production.vo.ProductionCostDetailVo;
+import com.ruoyi.production.vo.ProductionCostSummaryVo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface ProductionCostMapper {
+
+    /**
+     * 鏌ヨ鐢熶骇鎴愭湰姹囨��
+     */
+    ProductionCostSummaryVo selectCostSummary(@Param("dto") ProductionCostAccountDto dto);
+
+    /**
+     * 鎸変骇鍝佺被鍒眹鎬绘垚鏈�
+     */
+    List<ProductionCostAggregationVo> selectCostAggregationByCategory(@Param("dto") ProductionCostAccountDto dto);
+
+    /**
+     * 鎸夌敓浜ц鍗曟眹鎬绘垚鏈�
+     */
+    List<ProductionCostAggregationVo> selectCostAggregationByOrder(@Param("dto") ProductionCostAccountDto dto);
+
+}
diff --git a/src/main/java/com/ruoyi/production/mapper/ProductionOrderRouteMapper.java b/src/main/java/com/ruoyi/production/mapper/ProductionOrderRouteMapper.java
new file mode 100644
index 0000000..54e595e
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/mapper/ProductionOrderRouteMapper.java
@@ -0,0 +1,16 @@
+package com.ruoyi.production.mapper;
+
+import com.ruoyi.production.pojo.ProductionOrderRoute;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 鐢熶骇璁㈠崟缁戝畾鐨勫伐鑹鸿矾绾胯〃 Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author deslrey
+ * @since 2026-03-30
+ */
+public interface ProductionOrderRouteMapper extends BaseMapper<ProductionOrderRoute> {
+
+}
diff --git a/src/main/java/com/ruoyi/production/service/IProductionOrderRouteService.java b/src/main/java/com/ruoyi/production/service/IProductionOrderRouteService.java
new file mode 100644
index 0000000..2fbcb67
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/service/IProductionOrderRouteService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.production.service;
+
+import com.ruoyi.production.pojo.ProductionOrderRoute;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 鐢熶骇璁㈠崟缁戝畾鐨勫伐鑹鸿矾绾胯〃 鏈嶅姟绫�
+ * </p>
+ *
+ * @author deslrey
+ * @since 2026-03-30
+ */
+public interface IProductionOrderRouteService extends IService<ProductionOrderRoute> {
+
+}
diff --git a/src/main/java/com/ruoyi/production/service/ProductionCostService.java b/src/main/java/com/ruoyi/production/service/ProductionCostService.java
new file mode 100644
index 0000000..2c9e5e3
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/service/ProductionCostService.java
@@ -0,0 +1,46 @@
+package com.ruoyi.production.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.production.dto.ProductionCostAccountDto;
+import com.ruoyi.production.vo.ProductionCostAggregationVo;
+import com.ruoyi.production.vo.ProductionCostDetailVo;
+import com.ruoyi.production.vo.ProductionCostSummaryVo;
+import java.util.List;
+
+/**
+ * <br>
+ * 鐢熶骇鎴愭湰鏍哥畻鏈嶅姟鎺ュ彛
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30 11:20
+ */
+public interface ProductionCostService {
+
+    /**
+     * 鑾峰彇姹囨�诲崱鐗囨暟鎹�
+     */
+    ProductionCostSummaryVo getCostSummary(ProductionCostAccountDto dto);
+
+    /**
+     * 鎸変骇鍝佺被鍒垎椤佃幏鍙栬仛鍚堟眹鎬绘暟鎹�
+     */
+    IPage<ProductionCostAggregationVo> getProductAggregationPage(Page<ProductionCostAggregationVo> page, ProductionCostAccountDto dto);
+
+    /**
+     * 鎸夎鍗曞垎椤佃幏鍙栬仛鍚堟眹鎬绘暟鎹�
+     */
+    IPage<ProductionCostAggregationVo> getOrderAggregationPage(Page<ProductionCostAggregationVo> page, ProductionCostAccountDto dto);
+
+    /**
+     * 鑾峰彇娑堣�楁渶楂樼殑鍓�10鍚嶄骇鍝佺墿鏂�
+     */
+    List<ProductionCostAggregationVo> getProductTop(ProductionCostAccountDto dto);
+
+    /**
+     * 鑾峰彇鎴愭湰鏈�楂樼殑鍓�10涓敓浜ц鍗�
+     */
+    List<ProductionCostAggregationVo> getOrderTop(ProductionCostAccountDto dto);
+}
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionCostServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionCostServiceImpl.java
new file mode 100644
index 0000000..25a32aa
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionCostServiceImpl.java
@@ -0,0 +1,194 @@
+package com.ruoyi.production.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.production.dto.GroupKeyDto;
+import com.ruoyi.production.dto.ProductionCostAccountDto;
+import com.ruoyi.production.mapper.ProductionCostMapper;
+import com.ruoyi.production.service.ProductionCostService;
+import com.ruoyi.production.utils.UnitUtils;
+import com.ruoyi.production.vo.ProductionCostAggregationVo;
+import com.ruoyi.production.vo.ProductionCostSummaryVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <br>
+ * 鐢熶骇鎴愭湰鏍哥畻鏈嶅姟鎺ュ彛瀹炵幇绫�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30 11:21
+ */
+@Slf4j
+@Service
+public class ProductionCostServiceImpl implements ProductionCostService {
+
+    @Autowired
+    private ProductionCostMapper productionCostMapper;
+
+    @Override
+    public ProductionCostSummaryVo getCostSummary(ProductionCostAccountDto dto) {
+        if (dto.getEndDate() != null) {
+            dto.setEndDate(dto.getEndDate().plusDays(1));
+        }
+        return productionCostMapper.selectCostSummary(dto);
+    }
+
+    @Override
+    public IPage<ProductionCostAggregationVo> getProductAggregationPage(Page<ProductionCostAggregationVo> page, ProductionCostAccountDto dto) {
+        dto.setGroupType(1); // 鎸変骇鍝佹眹鎬�
+        List<ProductionCostAggregationVo> fullList = getCostAggregationList(dto);
+        return getMemoryPage(page, fullList);
+    }
+
+    @Override
+    public IPage<ProductionCostAggregationVo> getOrderAggregationPage(Page<ProductionCostAggregationVo> page, ProductionCostAccountDto dto) {
+        dto.setGroupType(2); // 鎸夎鍗曟眹鎬�
+        List<ProductionCostAggregationVo> fullList = getCostAggregationList(dto);
+        return getMemoryPage(page, fullList);
+    }
+
+    @Override
+    public List<ProductionCostAggregationVo> getProductTop(ProductionCostAccountDto dto) {
+        dto.setGroupType(1);
+        List<ProductionCostAggregationVo> fullList = getCostAggregationList(dto);
+        if (fullList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Map<String, ProductionCostAggregationVo> topMap = new HashMap<>();
+        for (ProductionCostAggregationVo vo : fullList) {
+            String name = vo.getName();
+            if (topMap.containsKey(name)) {
+                ProductionCostAggregationVo existing = topMap.get(name);
+                existing.setTotalCost(existing.getTotalCost().add(vo.getTotalCost()));
+                existing.setQuantity(existing.getQuantity().add(vo.getQuantity()));
+            } else {
+                ProductionCostAggregationVo clone = new ProductionCostAggregationVo();
+                clone.setName(name);
+                clone.setTotalCost(vo.getTotalCost());
+                clone.setQuantity(vo.getQuantity());
+                clone.setUnit(vo.getUnit());
+                topMap.put(name, clone);
+            }
+        }
+
+        return topMap.values().stream()
+                .sorted(Comparator.comparing(ProductionCostAggregationVo::getTotalCost).reversed())
+                .limit(10)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<ProductionCostAggregationVo> getOrderTop(ProductionCostAccountDto dto) {
+        dto.setGroupType(2);
+        List<ProductionCostAggregationVo> fullList = getCostAggregationList(dto);
+        if (fullList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Map<String, ProductionCostAggregationVo> topMap = new HashMap<>();
+        for (ProductionCostAggregationVo vo : fullList) {
+            String orderNo = vo.getName();
+            if (topMap.containsKey(orderNo)) {
+                ProductionCostAggregationVo existing = topMap.get(orderNo);
+                existing.setTotalCost(existing.getTotalCost().add(vo.getTotalCost()));
+            } else {
+                ProductionCostAggregationVo clone = new ProductionCostAggregationVo();
+                clone.setName(orderNo);
+                clone.setTotalCost(vo.getTotalCost());
+                clone.setStrength(vo.getStrength());
+                topMap.put(orderNo, clone);
+            }
+        }
+
+        return topMap.values().stream()
+                .sorted(Comparator.comparing(ProductionCostAggregationVo::getTotalCost).reversed())
+                .limit(10)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 鑾峰彇鍏ㄩ噺鑱氬悎姹囨�绘暟鎹�
+     */
+    private List<ProductionCostAggregationVo> getCostAggregationList(ProductionCostAccountDto dto) {
+        if (dto.getEndDate() != null) {
+            dto.setEndDate(dto.getEndDate().plusDays(1));
+        }
+
+        List<ProductionCostAggregationVo> rawList;
+        boolean isOrderAggregation = (dto.getGroupType() != null && dto.getGroupType() == 2);
+        if (isOrderAggregation) {
+            rawList = productionCostMapper.selectCostAggregationByOrder(dto);
+        } else {
+            rawList = productionCostMapper.selectCostAggregationByCategory(dto);
+        }
+
+        if (rawList == null || rawList.isEmpty()) {
+            return rawList != null ? rawList : new ArrayList<>();
+        }
+
+        Map<GroupKeyDto, ProductionCostAggregationVo> aggregationMap = new LinkedHashMap<>();
+        for (ProductionCostAggregationVo vo : rawList) {
+            String originalUnit = vo.getUnit();
+            String normalizedUnit = UnitUtils.normalizeUnit(originalUnit);
+            BigDecimal convertedQty = UnitUtils.convertValueToTon(vo.getQuantity(), originalUnit);
+
+            // 鏍规嵁姹囨�绘ā寮忚缃� Key 鍜屾樉绀哄垪
+            GroupKeyDto key;
+            if (isOrderAggregation) {
+                // 鎸夎鍗曟眹鎬伙細Key = 鏃ユ湡 + 璁㈠崟鍙� + 鍘熸枡鍚� + 鍘熸枡瑙勬牸 + 鍗曚綅
+                key = new GroupKeyDto(vo.getDate(), vo.getName(), vo.getModel(), vo.getStrength(), normalizedUnit);
+            } else {
+                // 鎸変骇鍝佹眹鎬伙細Key = 鏃ユ湡 + 鍘熸枡鍚� + 鍘熸枡瑙勬牸 + 鍗曚綅
+                key = new GroupKeyDto(vo.getDate(), vo.getName(), vo.getModel(), null, normalizedUnit);
+                vo.setStrength(null);
+            }
+
+            if (aggregationMap.containsKey(key)) {
+                ProductionCostAggregationVo existing = aggregationMap.get(key);
+                existing.setQuantity(existing.getQuantity().add(convertedQty));
+                existing.setTotalCost(existing.getTotalCost().add(vo.getTotalCost()));
+            } else {
+                vo.setUnit(normalizedUnit);
+                vo.setQuantity(convertedQty);
+                aggregationMap.put(key, vo);
+            }
+        }
+
+        List<ProductionCostAggregationVo> resultList = new ArrayList<>(aggregationMap.values());
+        for (ProductionCostAggregationVo vo : resultList) {
+            if (vo.getQuantity() != null) {
+                vo.setQuantity(vo.getQuantity().setScale(2, RoundingMode.HALF_UP));
+            }
+            if (vo.getTotalCost() != null) {
+                vo.setTotalCost(vo.getTotalCost().setScale(2, RoundingMode.HALF_UP));
+            }
+        }
+
+        return resultList;
+    }
+
+    private <T> IPage<T> getMemoryPage(Page<T> page, List<T> list) {
+        int total = list.size();
+        int size = (int) page.getSize();
+        int current = (int) page.getCurrent();
+        int fromIndex = (current - 1) * size;
+        int toIndex = Math.min(fromIndex + size, total);
+
+        List<T> subList = (fromIndex < total && fromIndex >= 0) ? list.subList(fromIndex, toIndex) : new ArrayList<>();
+
+        page.setTotal(total);
+        page.setRecords(subList);
+        return page;
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderAppendixServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderAppendixServiceImpl.java
index bc6d03e..65b79bb 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderAppendixServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderAppendixServiceImpl.java
@@ -57,6 +57,9 @@
     @Resource
     private ProductOrderService productOrderService;
 
+    @Resource
+    private IProductionOrderRouteService productionOrderRouteService;
+
     @Override
     public Long populateBlocks(Long orderId, ProductionPlanDto productionPlanDto) {
         if (productionPlanDto == null) {
@@ -70,8 +73,12 @@
             log.info("涓嬪彂浜у搧銆恵}銆戞湭鏌ヨ鍑哄伐鑹鸿矾绾�", productionPlanDto.getProductName());
             return null;
         }
-        migration(orderId, processRoute);
-        return processRoute.getId();
+
+        //  鍒涘缓宸ヨ壓璺嚎
+        ProductionOrderRoute productionOrderRoute = createOrderRouteSnapshot(orderId, processRoute);
+        migration(orderId, processRoute, productionOrderRoute.getId());
+
+        return productionOrderRoute.getId();
     }
 
     @Override
@@ -85,8 +92,12 @@
             log.info("涓嬪彂浜у搧銆恵}銆戞湭鏌ヨ鍑哄伐鑹鸿矾绾�", productionPlanDto.getProductName());
             return null;
         }
-        migration(orderId, processRoute);
-        return processRoute.getId();
+
+        //  鍒涘缓宸ヨ壓璺嚎
+        ProductionOrderRoute productionOrderRoute = createOrderRouteSnapshot(orderId, processRoute);
+        migration(orderId, processRoute, productionOrderRoute.getId());
+
+        return productionOrderRoute.getId();
     }
 
     @Override
@@ -101,14 +112,27 @@
         if (processRoute == null) {
             throw new ServiceException("璇ュ伐鑹鸿矾绾夸笉瀛樺湪,缁戝畾澶辫触");
         }
-        migration(productOrder.getId(), processRoute);
 
-        //  鍥炲啓宸ヨ壓璺嚎id鍒扮敓浜ц鍗�
+        //  鍒涘缓宸ヨ壓璺嚎
+        ProductionOrderRoute productionOrderRoute = createOrderRouteSnapshot(productOrder.getId(), processRoute);
+        migration(productOrder.getId(), processRoute, productionOrderRoute.getId());
+
+        //  鍥炲啓鏂扮殑宸ヨ壓璺嚎ID鍒扮敓浜ц鍗�
+        productOrder.setRouteId(productionOrderRoute.getId());
         productOrderService.updateById(productOrder);
     }
 
+    private ProductionOrderRoute createOrderRouteSnapshot(Long orderId, ProcessRoute processRoute) {
+        ProductionOrderRoute snapshot = new ProductionOrderRoute();
+        BeanUtils.copyProperties(processRoute, snapshot, "id", "createTime", "updateTime", "tenantId");
+        snapshot.setOrderId(orderId);
+        snapshot.setProcessRouteId(processRoute.getId());
+        productionOrderRouteService.save(snapshot);
+        return snapshot;
+    }
+
     @Override
-    public void deleteData(Long orderId, Long processRouteId) {
+    public void deleteData(Long orderId, Long snapshotRouteId) {
         //  鍒犻櫎宸ヨ壓璺嚎宸ュ簭鍙傛暟闄勮〃
         productionOrderRouteItemParamService.remove(new LambdaQueryWrapper<ProductionOrderRouteItemParam>()
                 .eq(ProductionOrderRouteItemParam::getOrderId, orderId));
@@ -116,25 +140,28 @@
         //  鍒犻櫎宸ヨ壓璺嚎瀛愰泦闄勮〃
         productionOrderRouteItemService.remove(new LambdaQueryWrapper<ProductionOrderRouteItem>()
                 .eq(ProductionOrderRouteItem::getOrderId, orderId)
-                .eq(ProductionOrderRouteItem::getRouteId, processRouteId));
+                .eq(ProductionOrderRouteItem::getRouteId, snapshotRouteId));
+
+        //  鍒犻櫎宸ヨ壓璺嚎
+        productionOrderRouteService.removeById(snapshotRouteId);
 
         //  鍒犻櫎BOM瀛愰泦闄勮〃
-        ProcessRoute processRoute = processRouteService.getById(processRouteId);
-        if (processRoute != null && processRoute.getBomId() != null) {
+        ProductionOrderRoute snapshot = productionOrderRouteService.getById(snapshotRouteId);
+        if (snapshot != null && snapshot.getBomId() != null) {
             productionOrderStructureService.remove(new LambdaQueryWrapper<ProductionOrderStructure>()
                     .eq(ProductionOrderStructure::getOrderId, orderId)
-                    .eq(ProductionOrderStructure::getBomId, processRoute.getBomId()));
+                    .eq(ProductionOrderStructure::getBomId, snapshot.getBomId().longValue()));
         }
     }
 
     /**
      * 鏍规嵁宸ヨ壓璺嚎杩佺Щ闄勮〃鏁版嵁
      */
-    private void migration(Long orderId, ProcessRoute processRoute) {
+    private void migration(Long orderId, ProcessRoute processRoute, Long newRouteId) {
         //  杩佺Щ宸ヨ壓璺嚎瀛愰泦琛ㄦ暟鎹紝杩斿洖鏃d->鏂癷nstance鏄犲皠
         List<ProcessRouteItem> processRouteItemList = processRouteItemService.list(
                 new LambdaQueryWrapper<ProcessRouteItem>().eq(ProcessRouteItem::getRouteId, processRoute.getId()));
-        Map<Long, ProductionOrderRouteItem> routeItemOldIdMap = migrationProcessRouteItem(orderId, processRouteItemList);
+        Map<Long, ProductionOrderRouteItem> routeItemOldIdMap = migrationProcessRouteItem(orderId, newRouteId, processRouteItemList);
 
         //  杩佺Щ宸ヨ壓璺嚎鍐呯粦瀹氱殑宸ュ簭鍙傛暟
         if (processRouteItemList != null && !processRouteItemList.isEmpty()) {
@@ -156,7 +183,7 @@
         }
     }
 
-    private Map<Long, ProductionOrderRouteItem> migrationProcessRouteItem(Long orderId, List<ProcessRouteItem> list) {
+    private Map<Long, ProductionOrderRouteItem> migrationProcessRouteItem(Long orderId, Long newRouteId, List<ProcessRouteItem> list) {
         Map<Long, ProductionOrderRouteItem> oldIdMap = new HashMap<>();
         if (list == null || list.isEmpty()) {
             return oldIdMap;
@@ -166,6 +193,7 @@
             BeanUtils.copyProperties(item, instance, "id");
             instance.setIsQuality(item.getIsQuality() != null && item.getIsQuality() ? 1 : 0);
             instance.setOrderId(orderId);
+            instance.setRouteId(newRouteId);
             if (item.getId() != null) {
                 oldIdMap.put(item.getId(), instance);
             }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRouteServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRouteServiceImpl.java
new file mode 100644
index 0000000..d11b470
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRouteServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.production.service.impl;
+
+import com.ruoyi.production.pojo.ProductionOrderRoute;
+import com.ruoyi.production.mapper.ProductionOrderRouteMapper;
+import com.ruoyi.production.service.IProductionOrderRouteService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 鐢熶骇璁㈠崟缁戝畾鐨勫伐鑹鸿矾绾胯〃 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author deslrey
+ * @since 2026-03-30
+ */
+@Service
+public class ProductionOrderRouteServiceImpl extends ServiceImpl<ProductionOrderRouteMapper, ProductionOrderRoute> implements IProductionOrderRouteService {
+
+}
diff --git a/src/main/java/com/ruoyi/production/utils/UnitUtils.java b/src/main/java/com/ruoyi/production/utils/UnitUtils.java
new file mode 100644
index 0000000..6faf806
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/utils/UnitUtils.java
@@ -0,0 +1,66 @@
+package com.ruoyi.production.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <br>
+ * 鍗曚綅杞崲宸ュ叿绫�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30 15:07
+ */
+public class UnitUtils {
+
+    private static final Set<String> WEIGHT_UNITS = new HashSet<>(Arrays.asList(
+            "鍗冨厠", "鍏枻", "kg", "KG", "kG",
+            "鍏�", "g", "G",
+            "鍚�", "t", "T"
+    ));
+
+    private static final Set<String> KG_UNITS = new HashSet<>(Arrays.asList("鍗冨厠", "鍏枻", "kg", "KG", "kG"));
+    private static final Set<String> G_UNITS = new HashSet<>(Arrays.asList("鍏�", "g", "G"));
+    private static final Set<String> TON_UNITS = new HashSet<>(Arrays.asList("鍚�", "t", "T"));
+
+    /**
+     * 鍒ゆ柇鏄惁涓洪噸閲忓崟浣�
+     */
+    public static boolean isWeightUnit(String unit) {
+        return unit != null && WEIGHT_UNITS.contains(unit.trim());
+    }
+
+    /**
+     * 濡傛灉鏄噸閲忓崟浣嶏紝缁熶竴杩斿洖鈥滃惃鈥濓紝鍚﹀垯杩斿洖鍘熷崟浣�
+     */
+    public static String normalizeUnit(String unit) {
+        if (isWeightUnit(unit)) {
+            return "鍚�";
+        }
+        return unit != null ? unit.trim() : "鏈煡鍗曚綅";
+    }
+
+    /**
+     * 濡傛灉鏄噸閲忓崟浣嶏紝鎶樼畻涓衡�滃惃鈥濓紝鍚﹀垯杩斿洖鍘熷��
+     */
+    public static BigDecimal convertValueToTon(BigDecimal value, String unit) {
+        if (value == null || unit == null) {
+            return value != null ? value : BigDecimal.ZERO;
+        }
+
+        String trimmedUnit = unit.trim();
+        if (KG_UNITS.contains(trimmedUnit)) {
+            return value.divide(new BigDecimal("1000"), 6, RoundingMode.HALF_UP);
+        } else if (G_UNITS.contains(trimmedUnit)) {
+            return value.divide(new BigDecimal("1000000"), 6, RoundingMode.HALF_UP);
+        } else if (TON_UNITS.contains(trimmedUnit)) {
+            return value;
+        }
+
+        return value;
+    }
+}
diff --git a/src/main/java/com/ruoyi/production/vo/ProductionCostAggregationVo.java b/src/main/java/com/ruoyi/production/vo/ProductionCostAggregationVo.java
new file mode 100644
index 0000000..3366770
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/vo/ProductionCostAggregationVo.java
@@ -0,0 +1,46 @@
+package com.ruoyi.production.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+
+/**
+ * <br>
+ * 鐢熶骇鎴愭湰鑱氬悎姹囨�籚O
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30
+ */
+@Data
+@ApiModel("鐢熶骇鎴愭湰鑱氬悎姹囨�籚O")
+public class ProductionCostAggregationVo {
+
+    @ApiModelProperty("鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDateTime date;
+
+    @ApiModelProperty("绫诲埆/璁㈠崟鍚嶇О")
+    private String name;
+
+    @ApiModelProperty("瑙勬牸鍨嬪彿")
+    private String model;
+
+    @ApiModelProperty("浜у搧绫诲瀷/寮哄害")
+    private String strength;
+
+    @ApiModelProperty("鐢ㄩ噺")
+    private BigDecimal quantity;
+
+    @ApiModelProperty("鍗曚綅")
+    private String unit;
+
+    @ApiModelProperty("鎬绘垚鏈�(鍏�)")
+    private BigDecimal totalCost;
+}
diff --git a/src/main/java/com/ruoyi/production/vo/ProductionCostDetailVo.java b/src/main/java/com/ruoyi/production/vo/ProductionCostDetailVo.java
new file mode 100644
index 0000000..2d442be
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/vo/ProductionCostDetailVo.java
@@ -0,0 +1,51 @@
+package com.ruoyi.production.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * <br>
+ * 鐢熶骇鎴愭湰鍒嗛〉鏄庣粏VO
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30
+ */
+@Data
+@ApiModel("鐢熶骇鎴愭湰鍒嗛〉鏄庣粏VO")
+public class ProductionCostDetailVo {
+
+    @ApiModelProperty("鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDateTime date;
+
+    @ApiModelProperty("浜у搧鍚嶇О")
+    private String productName;
+
+    @ApiModelProperty("浜у搧绫诲瀷")
+    private String categoryLabel;
+
+    @ApiModelProperty("瑙勬牸鍨嬪彿")
+    private String model;
+
+    @ApiModelProperty("浜у搧绫诲瀷/寮哄害")
+    private String strength;
+
+    @ApiModelProperty("鐢熶骇璁㈠崟")
+    private String orderNo;
+
+    @ApiModelProperty("鐢ㄩ噺")
+    private BigDecimal quantity;
+
+    @ApiModelProperty("鍗曚綅")
+    private String unit;
+
+    @ApiModelProperty("鎴愭湰(鍏�)")
+    private BigDecimal cost;
+}
diff --git a/src/main/java/com/ruoyi/production/vo/ProductionCostSummaryVo.java b/src/main/java/com/ruoyi/production/vo/ProductionCostSummaryVo.java
new file mode 100644
index 0000000..61e3b3b
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/vo/ProductionCostSummaryVo.java
@@ -0,0 +1,33 @@
+package com.ruoyi.production.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * <br>
+ * 鐢熶骇鎴愭湰缁熻姹囨�籚O
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/30
+ */
+@Data
+@ApiModel("鐢熶骇鎴愭湰缁熻姹囨�籚O")
+public class ProductionCostSummaryVo {
+
+    @ApiModelProperty("鎬荤敓浜ф垚鏈�")
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private BigDecimal totalCost;
+
+    @ApiModelProperty("姣忚鍗曞钩鍧囨垚鏈�")
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private BigDecimal averageOrderCost;
+
+    @ApiModelProperty("璁㈠崟鏁伴噺")
+    private Long orderCount;
+}
\ No newline at end of file
diff --git a/src/main/resources/mapper/production/ProductOrderMapper.xml b/src/main/resources/mapper/production/ProductOrderMapper.xml
index 5f8d566..201652f 100644
--- a/src/main/resources/mapper/production/ProductOrderMapper.xml
+++ b/src/main/resources/mapper/production/ProductOrderMapper.xml
@@ -52,7 +52,7 @@
         pms.material_code AS materialCode,
         pms.id AS productId
         FROM product_order po
-        LEFT JOIN process_route pr ON po.route_id = pr.id
+        LEFT JOIN production_order_route pr ON po.route_id = pr.id
         LEFT JOIN product_bom pb ON pr.bom_id = pb.id
         LEFT JOIN product_material_sku pms ON pms.id = po.product_material_sku_id
         LEFT JOIN product_material pm ON pm.id = pms.product_id
diff --git a/src/main/resources/mapper/production/ProductionCostMapper.xml b/src/main/resources/mapper/production/ProductionCostMapper.xml
new file mode 100644
index 0000000..dcf503d
--- /dev/null
+++ b/src/main/resources/mapper/production/ProductionCostMapper.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.production.mapper.ProductionCostMapper">
+
+    <sql id="baseCostQuery">
+        SELECT
+        ppi.quantity,
+        pos.unit_price,
+        (ppi.quantity * IFNULL(pos.unit_price, 0)) as calculated_cost,
+        ppi.unit as unit,
+        ppm.product_order_id,
+        ppm.reporting_time as log_date,
+        po.nps_no as order_no,
+        pm_mat.product_name as material_name,
+        pms_mat.model as material_model,
+        po.strength as strength,
+        sdd_type.dict_label as category_label
+        FROM production_product_input ppi
+        JOIN production_product_main ppm ON ppi.product_main_id = ppm.id
+        JOIN product_order po ON ppm.product_order_id = po.id
+        LEFT JOIN production_order_route pr ON po.route_id = pr.id
+        LEFT JOIN sys_dict_data sdd_type ON pr.dict_code = sdd_type.dict_code
+        LEFT JOIN production_order_structure pos ON ppm.product_order_id = pos.order_id
+        AND ppi.product_id = pos.product_model_id
+        AND ppi.bom_id = pos.bom_id
+        LEFT JOIN product_material_sku pms_mat ON ppi.product_id = pms_mat.id
+        LEFT JOIN product_material pm_mat ON pms_mat.product_id = pm_mat.id
+        <where>
+            <if test="dto.startDate != null">
+                AND ppm.reporting_time &gt;= #{dto.startDate}
+            </if>
+            <if test="dto.endDate != null">
+                AND ppm.reporting_time &lt; #{dto.endDate}
+            </if>
+            <if test="dto.dictCode != null">
+                AND pr.dict_code = #{dto.dictCode}
+            </if>
+            <if test="dto.productOrderId != null">
+                AND ppm.product_order_id = #{dto.productOrderId}
+            </if>
+        </where>
+    </sql>
+
+    <select id="selectCostSummary" resultType="com.ruoyi.production.vo.ProductionCostSummaryVo">
+        SELECT
+        ROUND(IFNULL(SUM(calculated_cost), 0), 2) as totalCost,
+        COUNT(DISTINCT product_order_id) as orderCount,
+        CASE
+        WHEN COUNT(DISTINCT product_order_id) > 0
+        THEN ROUND(SUM(calculated_cost) / COUNT(DISTINCT product_order_id), 2)
+        ELSE 0
+        END as averageOrderCost
+        FROM (
+        <include refid="baseCostQuery"/>
+        ) t
+    </select>
+
+    <select id="selectCostAggregationByCategory" resultType="com.ruoyi.production.vo.ProductionCostAggregationVo">
+        SELECT
+        log_date as date,
+        IFNULL(material_name, '鏈煡浜у搧') as name,
+        material_model as model,
+        unit,
+        ROUND(IFNULL(SUM(quantity), 0), 6) as quantity,
+        ROUND(IFNULL(SUM(calculated_cost), 0), 2) as totalCost
+        FROM (
+        <include refid="baseCostQuery"/>
+        ) t
+        GROUP BY log_date, material_name, material_model, unit
+    </select>
+
+    <select id="selectCostAggregationByOrder" resultType="com.ruoyi.production.vo.ProductionCostAggregationVo">
+        SELECT
+        log_date as date,
+        IFNULL(order_no, '鏈煡鍗曞彿') as name,
+        material_name as model,
+        category_label as strength,
+        unit,
+        ROUND(IFNULL(SUM(quantity), 0), 6) as quantity,
+        ROUND(IFNULL(SUM(calculated_cost), 0), 2) as totalCost
+        FROM (
+        <include refid="baseCostQuery"/>
+        ) t
+        GROUP BY log_date, order_no, material_name, category_label, unit
+    </select>
+
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/production/ProductionOrderRouteMapper.xml b/src/main/resources/mapper/production/ProductionOrderRouteMapper.xml
new file mode 100644
index 0000000..e35e54e
--- /dev/null
+++ b/src/main/resources/mapper/production/ProductionOrderRouteMapper.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.production.mapper.ProductionOrderRouteMapper">
+
+    <resultMap id="BaseResultMap" type="com.ruoyi.production.pojo.ProductionOrderRoute">
+        <id column="id" property="id"/>
+        <result column="order_id" property="orderId"/>
+        <result column="process_route_id" property="processRouteId"/>
+        <result column="product_model_id" property="productModelId"/>
+        <result column="process_route_code" property="processRouteCode"/>
+        <result column="bom_id" property="bomId"/>
+        <result column="dict_code" property="dictCode"/>
+        <result column="description" property="description"/>
+        <result column="tenant_id" property="tenantId"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="remark" property="remark"/>
+    </resultMap>
+
+</mapper>

--
Gitblit v1.9.3