已添加32个文件
已修改6个文件
1661 ■■■■■ 文件已修改
doc/宁夏-中盛建材.sql 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/controller/HomeController.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/SalesDeliveryDto.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/SalesTotalDto.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/service/HomeService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionCostAccountController.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderRouteController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionSettlementBatchesController.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionSettlementDetailsController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/GroupKeyDto.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionCostAccountDto.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/SettlementImportDto.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionCostMapper.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderRouteMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionSettlementBatchesMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionSettlementDetailsMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderRoute.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionSettlementBatches.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionSettlementDetails.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/IProductionOrderRouteService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/IProductionSettlementBatchesService.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/IProductionSettlementDetailsService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionCostService.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionCostServiceImpl.java 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderAppendixServiceImpl.java 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRouteServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionSettlementBatchesServiceImpl.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionSettlementDetailsServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/utils/UnitUtils.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/vo/ProductionCostAggregationVo.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/vo/ProductionCostDetailVo.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/vo/ProductionCostSummaryVo.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductOrderMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionCostMapper.xml 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderRouteMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionSettlementBatchesMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionSettlementDetailsMapper.xml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/ÄþÏÄ-ÖÐÊ¢½¨²Ä.sql
@@ -446,4 +446,68 @@
    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`;
    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的id',
    `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 ='生产订单绑定的工艺路线表';
CREATE TABLE `production_settlement_batches`
(
    `id`          bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `period_time` date         DEFAULT NULL COMMENT '核算归属月份',
    `batch_name`  varchar(255) DEFAULT NULL COMMENT '批次名称',
    `status`      int          DEFAULT '0' COMMENT '状态:0-仅预算,1-结算计算中,2-已完成结算,3-已锁定',
    `create_user` varchar(255) DEFAULT NULL COMMENT '导入用户',
    `create_time` datetime     DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
    `tenant_id`   bigint       DEFAULT NULL COMMENT '租户ID',
    PRIMARY KEY (`id`),
    KEY `idx_period` (`period_time`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4 COMMENT ='生产成本核算批次主表';
CREATE TABLE `production_settlement_details`
(
    `id`           bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `batch_id`     bigint NOT NULL COMMENT '关联核算批次表ID',
    `product_id`   bigint          DEFAULT NULL COMMENT '产品ID',
    `product_type` varchar(100)    DEFAULT NULL COMMENT '产品类型名称',
    `category`     varchar(100)    DEFAULT NULL COMMENT '费用类别',
    `subject_name` varchar(100)    DEFAULT NULL COMMENT '科目名称',
    `budget_qty`   decimal(20, 15) DEFAULT '0.000000000000000' COMMENT '预算耗量',
    `budget_price` decimal(20, 15) DEFAULT '0.000000000000000' COMMENT '预算单价',
    `budget_total` decimal(20, 15) DEFAULT '0.000000000000000' COMMENT '预算总成本',
    `actual_qty`   decimal(20, 15) DEFAULT '0.000000000000000' COMMENT '实际耗量',
    `actual_price` decimal(20, 15) DEFAULT '0.000000000000000' COMMENT '实际单价',
    `actual_total` decimal(20, 15) DEFAULT '0.000000000000000' COMMENT '实际总成本',
    `diff_qty`     decimal(20, 15) DEFAULT '0.000000000000000' COMMENT '耗量差异',
    `diff_price`   decimal(20, 15) DEFAULT '0.000000000000000' COMMENT '单价差异',
    `diff_total`   DECIMAL(20, 15) DEFAULT '0.000000000000000' COMMENT '总成本差异',
    `tenant_id`    bigint          DEFAULT NULL COMMENT '租户ID',
    PRIMARY KEY (`id`),
    KEY `idx_batch_id` (`batch_id`),
    KEY `idx_product_id` (`product_id`) COMMENT '方便按产品查询历史成本对比'
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4 COMMENT ='生产成本核算对比明细表';
src/main/java/com/ruoyi/home/controller/HomeController.java
@@ -336,4 +336,34 @@
        return AjaxResult.success(homeService.total());
    }
    @GetMapping("/salesAnalysis")
    @ApiOperation("销售统计看板---销量分析趋势图")
    public AjaxResult salesAnalysis(SalesDeliveryDto salesDeliveryDto) {
        return AjaxResult.success(homeService.salesAnalysis(salesDeliveryDto));
    }
    @GetMapping("/salesRanking")
    @ApiOperation("销售统计看板---销量数据-排名分析")
    public AjaxResult salesRanking(SalesDeliveryDto salesDeliveryDto) {
        return AjaxResult.success(homeService.salesRanking(salesDeliveryDto));
    }
    @GetMapping("/salesAmount")
    @ApiOperation("销售统计看板---销售金额分析")
    public AjaxResult salesAmount(SalesDeliveryDto salesDeliveryDto) {
        return AjaxResult.success(homeService.salesAmount(salesDeliveryDto));
    }
    @GetMapping("/salesDataRanking")
    @ApiOperation("销售统计看板---销售额数据-排名分析")
    public AjaxResult salesDataRanking(SalesDeliveryDto salesDeliveryDto) {
        return AjaxResult.success(homeService.salesDataRanking(salesDeliveryDto));
    }
    @GetMapping("/customerTrends")
    @ApiOperation("销售统计看板---新增客户趋势分析")
    public AjaxResult customerTrends(SalesDeliveryDto salesDeliveryDto) {
        return AjaxResult.success(homeService.customerTrends(salesDeliveryDto));
    }
}
src/main/java/com/ruoyi/home/dto/SalesDeliveryDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.home.dto;
import lombok.Data;
@Data
//销售统计看板的传参
public class SalesDeliveryDto {
    //å¹´/月
    private String days;
    //产品类型(砌块/板材)
    private String type;
}
src/main/java/com/ruoyi/home/dto/SalesTotalDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.ruoyi.home.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
//销售区统计
@Data
public class SalesTotalDto {
    //时间坐标
    private List<LocalDate> dates;
    //客户区域分析
    private List<Map<String,Long>> customerTrends;
}
src/main/java/com/ruoyi/home/service/HomeService.java
@@ -97,4 +97,14 @@
    List<processDataProductionStatisticsDto> processDataProductionStatistics(Integer type, List<Long> processIds);
    Map<String,Long> total();
    String salesAnalysis(SalesDeliveryDto salesDeliveryDto);
    String salesRanking(SalesDeliveryDto salesDeliveryDto);
    String salesAmount(SalesDeliveryDto salesDeliveryDto);
    String salesDataRanking(SalesDeliveryDto salesDeliveryDto);
    SalesTotalDto customerTrends(SalesDeliveryDto salesDeliveryDto);
}
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
@@ -2535,4 +2535,90 @@
        map.put("customer",count);
        return map;
    }
    @Override
    public String salesAnalysis(SalesDeliveryDto salesDeliveryDto) {
        List<LocalDate> dates = convertDateList(salesDeliveryDto.getDays());
        return null;
    }
    @Override
    public String salesRanking(SalesDeliveryDto salesDeliveryDto) {
        List<LocalDate> dates = convertDateList(salesDeliveryDto.getDays());
        return null;
    }
    @Override
    public String salesAmount(SalesDeliveryDto salesDeliveryDto) {
        List<LocalDate> dates = convertDateList(salesDeliveryDto.getDays());
        return null;
    }
    @Override
    public String salesDataRanking(SalesDeliveryDto salesDeliveryDto) {
        List<LocalDate> dates = convertDateList(salesDeliveryDto.getDays());
        return null;
    }
    @Override
    public SalesTotalDto customerTrends(SalesDeliveryDto salesDeliveryDto) {
        SalesTotalDto salesTotalDto = new SalesTotalDto();
        List<LocalDate> dates = convertDateList(salesDeliveryDto.getDays());
        List<Map<String, Long>> maps = new ArrayList<>();
        for (LocalDate date : dates) {
            LocalDate firstDay = date.with(TemporalAdjusters.firstDayOfMonth());
            LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth());
            Date startDate = Date.from(firstDay.atStartOfDay(ZoneId.systemDefault()).toInstant());
            Date endDate = Date.from(lastDay.atTime(23, 59, 59).atZone(ZoneId.systemDefault()).toInstant());
            List<Customer> customers = customerMapper.selectList(Wrappers.<Customer>lambdaQuery()
                    .between(Customer::getMaintenanceTime, startDate, endDate));
            Map<String, Long> regionCountMap = Arrays.stream(AddressRegionEnum.values())
                    .filter(addressRegionEnum -> addressRegionEnum.getRegionName().equals("SELF_PICKUP"))
                    .collect(Collectors.toMap(
                            AddressRegionEnum::getRegionName, // åŒºåŸŸåä½œä¸ºkey
                            enumItem -> 0L                    // åˆå§‹å€¼å…¨éƒ¨ä¸º0
                    ));
            if (!CollectionUtils.isEmpty(customers)) {
                regionCountMap = customers.stream()
                        // è°ƒç”¨æ–¹æ³•将原始地址转换为目标区域
                        .map(customer -> AddressRegionEnum.matchRegion(customer.getCompanyAddress()).getRegionName())
                        // è¿‡æ»¤æŽ‰è½¬æ¢å¤±è´¥/空的区域(可选,根据业务需求)
                        .filter(region -> region != null && !region.isEmpty())
                        // æŒ‰åŒºåŸŸåˆ†ç»„,统计每个区域的数量
                        .collect(Collectors.groupingBy(
                                region -> region,       // åˆ†ç»„依据:转换后的区域
                                Collectors.counting()   // è®¡æ•°
                        ));
            }
            regionCountMap.put("ALLIN",customers.stream().count());
            maps.add(regionCountMap);
        }
        salesTotalDto.setDates(dates);
        salesTotalDto.setCustomerTrends(maps);
        return salesTotalDto;
    }
    /**
     * æ ¹æ®å‰ç«¯ä¼ å‚ å¹´/月 è½¬æ¢ä¸ºå¯¹åº”LocalDate列表
     * @param days å‰ç«¯å‚数:"å¹´" / "月"
     * @return List<LocalDate>
     */
    public static List<LocalDate> convertDateList(String days) {
        List<LocalDate> resultList = new ArrayList<>();
        LocalDate now = LocalDate.now();
        int currentYear = now.getYear();
        if ("å¹´".equals(days)) {
            // éœ€æ±‚:近5å¹´ â†’ æ¯å¹´çš„ 1月1日
            for (int i = 0; i < 5; i++) {
                resultList.add(LocalDate.of(currentYear - i, 1, 1));
            }
        } else if ("月".equals(days)) {
            // éœ€æ±‚:当年12个月 â†’ æ¯æœˆ1日
            for (int month = 1; month <= 12; month++) {
                resultList.add(LocalDate.of(currentYear, month, 1));
            }
        }
        return resultList;
    }
}
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);
    }
}
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 {
}
src/main/java/com/ruoyi/production/controller/ProductionSettlementBatchesController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.ruoyi.production.controller;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.production.service.IProductionSettlementBatchesService;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—批次主表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
@RestController
@RequestMapping("/productionSettlementBatches")
@ApiModel(value = "ProductionSettlementBatchesControllerç±»", description = "生产成本核算批次主表 å‰ç«¯æŽ§åˆ¶å™¨")
public class ProductionSettlementBatchesController {
    @Autowired
    private IProductionSettlementBatchesService productionSettlementBatchesService;
    @PostMapping("/import")
    @ApiOperation("导入生产成本核算表")
    public AjaxResult importProductionSettlement(@RequestParam("file") MultipartFile file, @RequestParam(required = false) LocalDate periodTime, @RequestParam(required = false) String batchName) {
        productionSettlementBatchesService.importProductionSettlement(file, periodTime, batchName);
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/production/controller/ProductionSettlementDetailsController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.production.controller;
import io.swagger.annotations.ApiModel;
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-settlement-details")
@ApiModel(value = "ProductionSettlementDetailsControllerç±»", description = "生产成本核算对比明细表 å‰ç«¯æŽ§åˆ¶å™¨")
public class ProductionSettlementDetailsController {
}
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);
    }
}
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;
}
src/main/java/com/ruoyi/production/dto/SettlementImportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package com.ruoyi.production.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
 * <br>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—对比文件导入Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/30
 */
@Data
@ApiModel(value = "SettlementImportDto", description = "生产成本核算对比文件导入Dto")
public class SettlementImportDto {
    @Excel(name = "产品类型")
    @ApiModelProperty("产品类型")
    private String productType;
    @Excel(name = "科目名称")
    @ApiModelProperty("科目名称")
    private String category;
    @Excel(name = "科目")
    @ApiModelProperty("科目")
    private String subjectName;
    @Excel(name = "成本预算")
    @ApiModelProperty("成本预算")
    private BigDecimal budgetTotal;
    @Excel(name = "耗量预算")
    @ApiModelProperty("耗量预算")
    private BigDecimal budgetQty;
    @Excel(name = "单价预算")
    @ApiModelProperty("单价预算")
    private BigDecimal budgetPrice;
}
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);
}
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> {
}
src/main/java/com/ruoyi/production/mapper/ProductionSettlementBatchesMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.production.mapper;
import com.ruoyi.production.pojo.ProductionSettlementBatches;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—批次主表 Mapper æŽ¥å£
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
public interface ProductionSettlementBatchesMapper extends BaseMapper<ProductionSettlementBatches> {
}
src/main/java/com/ruoyi/production/mapper/ProductionSettlementDetailsMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.production.mapper;
import com.ruoyi.production.pojo.ProductionSettlementDetails;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—对比明细表 Mapper æŽ¥å£
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
public interface ProductionSettlementDetailsMapper extends BaseMapper<ProductionSettlementDetails> {
}
src/main/java/com/ruoyi/production/pojo/ProductionOrderRoute.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
package com.ruoyi.production.pojo;
import com.baomidou.mybatisplus.annotation.*;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
/**
 * <p>
 * ç”Ÿäº§è®¢å•绑定的工艺路线表
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("production_order_route")
@ApiModel(value = "ProductionOrderRoute对象", description = "生产订单绑定的工艺路线表")
public class ProductionOrderRoute implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "关联生产订单ID (production_order.id)")
    private Long orderId;
    @ApiModelProperty(value = "原始工艺路线ID (process_route.id)")
    private Long processRouteId;
    @ApiModelProperty(value = "产品id")
    private Long productModelId;
    @ApiModelProperty(value = "工艺路线编码")
    private String processRouteCode;
    @ApiModelProperty(value = "关联bom的id")
    private Integer bomId;
    @ApiModelProperty(value = "产品类型字典编码")
    private Long dictCode;
    @ApiModelProperty(value = "描述")
    private String description;
    @ApiModelProperty(value = "租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @ApiModelProperty(value = "录入时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "备注")
    private String remark;
}
src/main/java/com/ruoyi/production/pojo/ProductionSettlementBatches.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package com.ruoyi.production.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDate;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—批次主表
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("production_settlement_batches")
@ApiModel(value="ProductionSettlementBatches对象", description="生产成本核算批次主表")
public class ProductionSettlementBatches implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "核算归属月份")
    private LocalDate periodTime;
    @ApiModelProperty(value = "批次名称")
    private String batchName;
    @ApiModelProperty(value = "状态:0-仅预算,1-结算计算中,2-已完成结算,3-已锁定")
    private Integer status;
    @ApiModelProperty(value = "导入用户")
    private String createUser;
    @ApiModelProperty(value = "创建日期")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "租户ID")
    private Long tenantId;
}
src/main/java/com/ruoyi/production/pojo/ProductionSettlementDetails.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package com.ruoyi.production.pojo;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—对比明细表
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("production_settlement_details")
@ApiModel(value="ProductionSettlementDetails对象", description="生产成本核算对比明细表")
public class ProductionSettlementDetails implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "关联核算批次表ID")
    private Long batchId;
    @ApiModelProperty(value = "产品ID")
    private Long productId;
    @ApiModelProperty(value = "产品类型名称")
    private String productType;
    @ApiModelProperty(value = "费用类别")
    private String category;
    @ApiModelProperty(value = "科目名称")
    private String subjectName;
    @ApiModelProperty(value = "预算耗量")
    private BigDecimal budgetQty;
    @ApiModelProperty(value = "预算单价")
    private BigDecimal budgetPrice;
    @ApiModelProperty(value = "预算总成本")
    private BigDecimal budgetTotal;
    @ApiModelProperty(value = "实际耗量")
    private BigDecimal actualQty;
    @ApiModelProperty(value = "实际单价")
    private BigDecimal actualPrice;
    @ApiModelProperty(value = "实际总成本")
    private BigDecimal actualTotal;
    @ApiModelProperty(value = "耗量差异")
    private BigDecimal diffQty;
    @ApiModelProperty(value = "单价差异")
    private BigDecimal diffPrice;
    @ApiModelProperty(value = "总成本差异")
    private BigDecimal diffTotal;
    @ApiModelProperty(value = "租户ID")
    private Long tenantId;
}
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> {
}
src/main/java/com/ruoyi/production/service/IProductionSettlementBatchesService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.ruoyi.production.service;
import com.ruoyi.production.pojo.ProductionSettlementBatches;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—批次主表 æœåŠ¡ç±»
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
public interface IProductionSettlementBatchesService extends IService<ProductionSettlementBatches> {
    void importProductionSettlement(MultipartFile file, LocalDate periodTime, String batchName);
}
src/main/java/com/ruoyi/production/service/IProductionSettlementDetailsService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.production.service;
import com.ruoyi.production.pojo.ProductionSettlementDetails;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—对比明细表 æœåŠ¡ç±»
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
public interface IProductionSettlementDetailsService extends IService<ProductionSettlementDetails> {
}
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);
}
src/main/java/com/ruoyi/production/service/impl/ProductionCostServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,206 @@
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();
        long size = page.getSize();
        long current = page.getCurrent();
        if (size == -1 || current == -1) {
            page.setTotal(total);
            page.setRecords(list);
            return page;
        }
        int fromIndex = (int) ((current - 1) * size);
        int toIndex = Math.min(fromIndex + (int) size, total);
        List<T> subList;
        if (fromIndex >= 0 && fromIndex < total) {
            subList = list.subList(fromIndex, toIndex);
        } else {
            subList = new ArrayList<>();
        }
        page.setTotal(total);
        page.setRecords(subList);
        return page;
    }
}
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) {
        //  è¿ç§»å·¥è‰ºè·¯çº¿å­é›†è¡¨æ•°æ®ï¼Œè¿”回旧id->新instance映射
        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);
            }
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 {
}
src/main/java/com/ruoyi/production/service/impl/ProductionSettlementBatchesServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
package com.ruoyi.production.service.impl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.production.dto.SettlementImportDto;
import com.ruoyi.production.pojo.ProductionSettlementBatches;
import com.ruoyi.production.mapper.ProductionSettlementBatchesMapper;
import com.ruoyi.production.pojo.ProductionSettlementDetails;
import com.ruoyi.production.service.IProductionSettlementBatchesService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.production.service.IProductionSettlementDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—批次主表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
@Service
public class ProductionSettlementBatchesServiceImpl extends ServiceImpl<ProductionSettlementBatchesMapper, ProductionSettlementBatches> implements IProductionSettlementBatchesService {
    @Autowired
    private IProductionSettlementDetailsService productionSettlementDetailsService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importProductionSettlement(MultipartFile file, LocalDate periodTime, String batchName) {
        if (file == null || file.isEmpty()) {
            throw new ServiceException("导入失败,文件不能为空");
        }
        List<SettlementImportDto> list;
        try {
            ExcelUtil<SettlementImportDto> util = new ExcelUtil<>(SettlementImportDto.class);
            list = util.importExcel(file.getInputStream());
        } catch (Exception e) {
            log.error("导入生产成本核算失败", e);
            throw new ServiceException("解析Excel文件失败:" + e.getMessage());
        }
        if (StringUtils.isEmpty(list)) {
            throw new ServiceException("导入数据不能为空!");
        }
        ProductionSettlementBatches batch = new ProductionSettlementBatches();
        batch.setBatchName(StringUtils.isNotEmpty(batchName) ? batchName : periodTime + "成本核算导入");
        batch.setPeriodTime(periodTime != null ? periodTime : LocalDate.now());
        batch.setStatus(0);
        batch.setCreateTime(LocalDateTime.now());
        batch.setCreateUser(SecurityUtils.getUsername());
        this.save(batch);
        List<ProductionSettlementDetails> detailList = list.stream().map(dto -> {
            ProductionSettlementDetails detail = new ProductionSettlementDetails();
            detail.setBatchId(batch.getId());
            detail.setProductType(dto.getProductType());
            detail.setCategory(dto.getCategory());
            detail.setSubjectName(dto.getSubjectName());
            detail.setBudgetQty(dto.getBudgetQty());
            detail.setBudgetPrice(dto.getBudgetPrice());
            detail.setBudgetTotal(dto.getBudgetTotal());
            detail.setActualQty(BigDecimal.ZERO);
            detail.setActualPrice(BigDecimal.ZERO);
            detail.setActualTotal(BigDecimal.ZERO);
            detail.setDiffQty(BigDecimal.ZERO);
            detail.setDiffPrice(BigDecimal.ZERO);
            detail.setDiffTotal(BigDecimal.ZERO);
            return detail;
        }).collect(Collectors.toList());
        productionSettlementDetailsService.saveBatch(detailList);
    }
}
src/main/java/com/ruoyi/production/service/impl/ProductionSettlementDetailsServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.production.service.impl;
import com.ruoyi.production.pojo.ProductionSettlementDetails;
import com.ruoyi.production.mapper.ProductionSettlementDetailsMapper;
import com.ruoyi.production.service.IProductionSettlementDetailsService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * ç”Ÿäº§æˆæœ¬æ ¸ç®—对比明细表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author deslrey
 * @since 2026-03-30
 */
@Service
public class ProductionSettlementDetailsServiceImpl extends ServiceImpl<ProductionSettlementDetailsMapper, ProductionSettlementDetails> implements IProductionSettlementDetailsService {
}
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;
    }
}
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>
 * ç”Ÿäº§æˆæœ¬èšåˆæ±‡æ€»VO
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/30
 */
@Data
@ApiModel("生产成本聚合汇总VO")
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;
}
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;
}
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>
 * ç”Ÿäº§æˆæœ¬ç»Ÿè®¡æ±‡æ€»VO
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/30
 */
@Data
@ApiModel("生产成本统计汇总VO")
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;
}
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
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>
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>
src/main/resources/mapper/production/ProductionSettlementBatchesMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
<?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.ProductionSettlementBatchesMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.production.pojo.ProductionSettlementBatches">
        <id column="id" property="id" />
        <result column="period_time" property="periodTime" />
        <result column="batch_name" property="batchName" />
        <result column="status" property="status" />
        <result column="create_user" property="createUser" />
        <result column="create_time" property="createTime" />
        <result column="tenant_id" property="tenantId" />
    </resultMap>
</mapper>
src/main/resources/mapper/production/ProductionSettlementDetailsMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
<?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.ProductionSettlementDetailsMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.production.pojo.ProductionSettlementDetails">
        <id column="id" property="id" />
        <result column="batch_id" property="batchId" />
        <result column="product_id" property="productId" />
        <result column="product_type" property="productType" />
        <result column="category" property="category" />
        <result column="subject_name" property="subjectName" />
        <result column="budget_qty" property="budgetQty" />
        <result column="budget_price" property="budgetPrice" />
        <result column="budget_total" property="budgetTotal" />
        <result column="actual_qty" property="actualQty" />
        <result column="actual_price" property="actualPrice" />
        <result column="actual_total" property="actualTotal" />
        <result column="diff_qty" property="diffQty" />
        <result column="diff_price" property="diffPrice" />
        <result column="diff_total" property="diffTotal" />
        <result column="tenant_id" property="tenantId" />
    </resultMap>
</mapper>