feat: 生产成品核算报表接口,生产订单绑定工艺路线
| | |
| | | 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 ='ç产订åç»å®çå·¥èºè·¯çº¿è¡¨'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 { |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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> { |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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> { |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
| | |
| | | @Resource |
| | | private ProductOrderService productOrderService; |
| | | |
| | | @Resource |
| | | private IProductionOrderRouteService productionOrderRouteService; |
| | | |
| | | @Override |
| | | public Long populateBlocks(Long orderId, ProductionPlanDto productionPlanDto) { |
| | | if (productionPlanDto == null) { |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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)); |
| | |
| | | // å é¤å·¥èºè·¯çº¿åéé表 |
| | | 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()) { |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | |
| | | 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); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 { |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| | |
| | | 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 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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 >= #{dto.startDate} |
| | | </if> |
| | | <if test="dto.endDate != null"> |
| | | AND ppm.reporting_time < #{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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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> |