| | |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.energy.pojo.Energy; |
| | | import com.ruoyi.energy.pojo.EnergyConsumptionDetail; |
| | | import com.ruoyi.energy.service.EnergyConsumptionDetailService; |
| | | import com.ruoyi.energy.service.EnergyService; |
| | | import com.ruoyi.production.dto.ProductionSettlementDetailsDto; |
| | | import com.ruoyi.production.dto.ProductionSettlementDto; |
| | | import com.ruoyi.production.dto.ProductionSettlementTotalDto; |
| | | import com.ruoyi.production.dto.SettlementImportDto; |
| | | import com.ruoyi.production.pojo.ProductionSettlementBatches; |
| | | import com.ruoyi.production.enums.ProductionSettlementEnum; |
| | | import com.ruoyi.production.pojo.*; |
| | | import com.ruoyi.production.mapper.ProductionSettlementBatchesMapper; |
| | | import com.ruoyi.production.pojo.ProductionSettlementDetails; |
| | | import com.ruoyi.production.service.IProductionSettlementBatchesService; |
| | | import com.ruoyi.production.service.*; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.production.service.IProductionSettlementDetailsService; |
| | | import com.ruoyi.project.system.domain.SysDictData; |
| | | import com.ruoyi.project.system.mapper.SysDictDataMapper; |
| | | import com.ruoyi.production.utils.UnitUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | 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.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | * @since 2026-03-30 |
| | | */ |
| | | @Service |
| | | @Slf4j |
| | | public class ProductionSettlementBatchesServiceImpl extends ServiceImpl<ProductionSettlementBatchesMapper, ProductionSettlementBatches> implements IProductionSettlementBatchesService { |
| | | |
| | | @Autowired |
| | | private IProductionSettlementDetailsService productionSettlementDetailsService; |
| | | |
| | | @Autowired |
| | | private ProductMaterialService productMaterialService; |
| | | |
| | | @Autowired |
| | | private ProductMaterialSkuService productMaterialSkuService; |
| | | |
| | | @Autowired |
| | | private ProductionProductMainService productionProductMainService; |
| | | |
| | | @Autowired |
| | | private ProductionProductInputService productionProductInputService; |
| | | |
| | | @Autowired |
| | | private IProductionOrderStructureService productionOrderStructureService; |
| | | |
| | | @Autowired |
| | | private ProductOrderService productOrderService; |
| | | |
| | | @Autowired |
| | | private IProductionOrderRouteService productionOrderRouteService; |
| | | |
| | | @Autowired |
| | | private EnergyService energyService; |
| | | |
| | | @Autowired |
| | | private EnergyConsumptionDetailService energyConsumptionDetailService; |
| | | |
| | | @Autowired |
| | | private SysDictDataMapper sysDictDataMapper; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void importProductionSettlement(MultipartFile file, LocalDate periodTime, String batchName) { |
| | | public void importProductionSettlement(MultipartFile file, ProductionSettlementDto dto) { |
| | | if (file == null || file.isEmpty()) { |
| | | throw new ServiceException("导入失败,文件不能为空"); |
| | | } |
| | | // 核算月份,如果不填则默认为当前月 |
| | | String finalPeriodTime = parsePeriodTime(dto != null ? dto.getPeriodTime() : null); |
| | | |
| | | List<SettlementImportDto> list; |
| | | try { |
| | | ExcelUtil<SettlementImportDto> util = new ExcelUtil<>(SettlementImportDto.class); |
| | | list = util.importExcel(file.getInputStream()); |
| | | list = new ExcelUtil<>(SettlementImportDto.class).importExcel(file.getInputStream()); |
| | | } catch (Exception e) { |
| | | log.error("导入生产成本核算失败", e); |
| | | log.error("解析Excel失败", e); |
| | | throw new ServiceException("解析Excel文件失败:" + e.getMessage()); |
| | | } |
| | | |
| | | if (StringUtils.isEmpty(list)) { |
| | | if (list == null || list.isEmpty()) { |
| | | throw new ServiceException("导入数据不能为空!"); |
| | | } |
| | | |
| | | // 如果该月批次已存在,先清除旧批次及其明细,再重新导入 |
| | | ProductionSettlementBatches existBatch = this.lambdaQuery() |
| | | .eq(ProductionSettlementBatches::getPeriodTime, finalPeriodTime) |
| | | .one(); |
| | | if (existBatch != null) { |
| | | productionSettlementDetailsService.lambdaUpdate() |
| | | .eq(ProductionSettlementDetails::getBatchId, existBatch.getId()) |
| | | .remove(); |
| | | this.removeById(existBatch.getId()); |
| | | log.info("已清除 {} 月份的旧核算数据,批次ID:{}", finalPeriodTime, existBatch.getId()); |
| | | } |
| | | |
| | | 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.setPeriodTime(finalPeriodTime); |
| | | batch.setCreateUser(SecurityUtils.getUsername()); |
| | | this.save(batch); |
| | | |
| | | List<ProductionSettlementDetails> detailList = list.stream().map(dto -> { |
| | | String lastProductType = ""; |
| | | String lastCategory = ""; |
| | | |
| | | List<ProductionSettlementDetails> detailList = new ArrayList<>(); |
| | | int rowIndex = 2; |
| | | for (SettlementImportDto importDto : list) { |
| | | 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()); |
| | | if (StringUtils.isNotBlank(importDto.getProductType())) { |
| | | lastProductType = importDto.getProductType(); |
| | | } |
| | | if (StringUtils.isNotBlank(importDto.getCategory())) { |
| | | lastCategory = importDto.getCategory(); |
| | | } |
| | | |
| | | detail.setProductType(lastProductType); |
| | | detail.setCategory(lastCategory); |
| | | detail.setSubjectName(importDto.getSubjectName()); |
| | | detail.setBudgetQty(importDto.getBudgetQty() != null ? importDto.getBudgetQty() : BigDecimal.ZERO); |
| | | detail.setBudgetPrice(importDto.getBudgetPrice() != null ? importDto.getBudgetPrice() : BigDecimal.ZERO); |
| | | detail.setBudgetTotal(importDto.getBudgetTotal() != null ? importDto.getBudgetTotal() : BigDecimal.ZERO); |
| | | |
| | | if (ProductionSettlementEnum.isMaterialCost(detail.getCategory())) { |
| | | ProductMaterial product = productMaterialService.lambdaQuery() |
| | | .eq(ProductMaterial::getProductName, importDto.getSubjectName()) |
| | | .one(); |
| | | if (product != null) { |
| | | detail.setProductId(product.getId()); |
| | | } else { |
| | | log.warn("第 {} 行,未找到科目产品:{}", rowIndex, importDto.getSubjectName()); |
| | | } |
| | | } |
| | | detailList.add(detail); |
| | | rowIndex++; |
| | | } |
| | | |
| | | productionSettlementDetailsService.saveBatch(detailList); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, List<ProductionSettlementDetailsDto>> getSettlement(ProductionSettlementDto dto) { |
| | | String finalPeriodTime = parsePeriodTime(dto != null ? dto.getPeriodTime() : null); |
| | | |
| | | YearMonth yearMonth = YearMonth.parse(finalPeriodTime, DateTimeFormatter.ofPattern("yyyy-MM")); |
| | | LocalDate date = yearMonth.atDay(1); |
| | | ProductionSettlementBatches batch = this.lambdaQuery() |
| | | .eq(ProductionSettlementBatches::getPeriodTime, finalPeriodTime) |
| | | .one(); |
| | | |
| | | if (batch == null) { |
| | | return new HashMap<>(); |
| | | } |
| | | |
| | | String filterProductType = dto != null ? dto.getProductType() : null; |
| | | String filterSubjectName = dto != null ? dto.getSubjectName() : null; |
| | | String filterCostType = dto != null ? dto.getCostType() : null; |
| | | |
| | | List<ProductionSettlementDetails> details = productionSettlementDetailsService.lambdaQuery() |
| | | .eq(ProductionSettlementDetails::getBatchId, batch.getId()) |
| | | .eq(StringUtils.isNotBlank(filterProductType), ProductionSettlementDetails::getProductType, filterProductType) |
| | | .eq(StringUtils.isNotBlank(filterSubjectName), ProductionSettlementDetails::getSubjectName, filterSubjectName) |
| | | .eq(StringUtils.isNotBlank(filterCostType), ProductionSettlementDetails::getCategory, filterCostType) |
| | | .list(); |
| | | |
| | | List<ProductionSettlementDetailsDto> dtoList = details.stream().map(d -> { |
| | | ProductionSettlementDetailsDto detailDto = new ProductionSettlementDetailsDto(); |
| | | detailDto.setBatchId(d.getBatchId()); |
| | | detailDto.setProductId(d.getProductId()); |
| | | detailDto.setProductType(d.getProductType()); |
| | | detailDto.setCategory(d.getCategory()); |
| | | detailDto.setSubjectName(d.getSubjectName()); |
| | | detailDto.setBudgetQty(d.getBudgetQty()); |
| | | detailDto.setBudgetPrice(d.getBudgetPrice()); |
| | | detailDto.setBudgetTotal(d.getBudgetTotal()); |
| | | return detailDto; |
| | | }).collect(Collectors.toList()); |
| | | |
| | | LocalDateTime start = date.atStartOfDay(); |
| | | LocalDateTime end = start.plusMonths(1); |
| | | |
| | | // 获取产品分类 |
| | | List<SysDictData> sysDictDataList = sysDictDataMapper.selectDictDataByType("product_type"); |
| | | Map<Long, String> dictCodeMap = sysDictDataList.stream() |
| | | .collect(Collectors.toMap(SysDictData::getDictCode, SysDictData::getDictLabel, (k1, k2) -> k1)); |
| | | |
| | | // 获取物料消耗数据 |
| | | List<ProductionProductMain> mains = productionProductMainService.lambdaQuery() |
| | | .ge(ProductionProductMain::getCreateTime, start) |
| | | .lt(ProductionProductMain::getCreateTime, end) |
| | | .list(); |
| | | |
| | | List<Long> mainIds = mains.stream().map(ProductionProductMain::getId).collect(Collectors.toList()); |
| | | List<Long> orderIds = mains.stream().map(ProductionProductMain::getProductOrderId).distinct().collect(Collectors.toList()); |
| | | |
| | | Map<Long, List<ProductionProductInput>> inputsByMain = new HashMap<>(); |
| | | Map<Long, ProductOrder> orderMap = new HashMap<>(); |
| | | Map<Long, ProductionOrderRoute> orderRouteMap = new HashMap<>(); |
| | | Map<Long, List<ProductionOrderStructure>> structuresByOrder = new HashMap<>(); |
| | | |
| | | if (!mainIds.isEmpty()) { |
| | | inputsByMain = productionProductInputService.lambdaQuery() |
| | | .in(ProductionProductInput::getProductMainId, mainIds) |
| | | .list() |
| | | .stream() |
| | | .collect(Collectors.groupingBy(ProductionProductInput::getProductMainId)); |
| | | } |
| | | |
| | | if (!orderIds.isEmpty()) { |
| | | orderMap = productOrderService.lambdaQuery() |
| | | .in(ProductOrder::getId, orderIds) |
| | | .list() |
| | | .stream() |
| | | .collect(Collectors.toMap(ProductOrder::getId, o -> o)); |
| | | |
| | | orderRouteMap = productionOrderRouteService.lambdaQuery() |
| | | .in(ProductionOrderRoute::getOrderId, orderIds) |
| | | .list() |
| | | .stream() |
| | | .collect(Collectors.toMap(ProductionOrderRoute::getOrderId, r -> r, (k1, k2) -> k1)); |
| | | |
| | | structuresByOrder = productionOrderStructureService.lambdaQuery() |
| | | .in(ProductionOrderStructure::getOrderId, orderIds) |
| | | .list() |
| | | .stream() |
| | | .collect(Collectors.groupingBy(ProductionOrderStructure::getOrderId)); |
| | | } |
| | | |
| | | // 获取能耗数据 |
| | | LocalDate startDate = date; |
| | | LocalDate endDate = date.plusMonths(1); |
| | | List<EnergyConsumptionDetail> energyDetails = energyConsumptionDetailService.lambdaQuery() |
| | | .ge(EnergyConsumptionDetail::getMeterReadingDate, startDate) |
| | | .lt(EnergyConsumptionDetail::getMeterReadingDate, endDate) |
| | | .list(); |
| | | |
| | | List<Energy> energies = energyService.list(); |
| | | Map<String, Energy> energyMap = new HashMap<>(); |
| | | for (Energy energy : energies) { |
| | | if (energy.getEnergyName() != null) { |
| | | energyMap.put(energy.getEnergyName(), energy); |
| | | } |
| | | } |
| | | |
| | | List<ProductMaterialSku> allSkus = productMaterialSkuService.list(); |
| | | Map<Long, Long> skuToMaterialMap = allSkus.stream() |
| | | .filter(sku -> sku.getProductId() != null) |
| | | .collect(Collectors.toMap(ProductMaterialSku::getId, ProductMaterialSku::getProductId, (k1, k2) -> k1)); |
| | | |
| | | // 计算实际值 |
| | | for (ProductionSettlementDetailsDto detail : dtoList) { |
| | | BigDecimal actualQty = BigDecimal.ZERO; |
| | | BigDecimal actualTotalCost = BigDecimal.ZERO; |
| | | |
| | | if (ProductionSettlementEnum.isMaterialCost(detail.getCategory())) { |
| | | if (detail.getProductId() != null) { |
| | | for (ProductionProductMain main : mains) { |
| | | ProductOrder order = orderMap.get(main.getProductOrderId()); |
| | | if (order == null) continue; |
| | | |
| | | String targetType = detail.getProductType() != null ? detail.getProductType().trim() : ""; |
| | | boolean typeMatch = "综合".equals(targetType); |
| | | |
| | | if (!typeMatch) { |
| | | ProductionOrderRoute route = orderRouteMap.get(main.getProductOrderId()); |
| | | if (route != null && route.getDictCode() != null) { |
| | | String dictLabel = dictCodeMap.get(route.getDictCode()); |
| | | if (dictLabel != null && dictLabel.trim().equals(targetType)) { |
| | | typeMatch = true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (!typeMatch && order.getStrength() != null && order.getStrength().trim().equals(targetType)) { |
| | | typeMatch = true; |
| | | } |
| | | |
| | | if (typeMatch) { |
| | | List<ProductionProductInput> mainInputs = inputsByMain.get(main.getId()); |
| | | if (mainInputs != null) { |
| | | for (ProductionProductInput input : mainInputs) { |
| | | Long inputMaterialId = skuToMaterialMap.get(input.getProductId()); |
| | | if (Objects.equals(inputMaterialId, detail.getProductId())) { |
| | | BigDecimal rawQty = input.getQuantity() != null ? input.getQuantity() : BigDecimal.ZERO; |
| | | |
| | | List<ProductionOrderStructure> orderStructures = structuresByOrder.get(order.getId()); |
| | | BigDecimal unitPrice = BigDecimal.ZERO; |
| | | String unit = ""; |
| | | if (orderStructures != null) { |
| | | for (ProductionOrderStructure structure : orderStructures) { |
| | | Long structureMaterialId = skuToMaterialMap.get(structure.getProductModelId()); |
| | | if (Objects.equals(structureMaterialId, detail.getProductId())) { |
| | | unitPrice = structure.getUnitPrice() != null ? structure.getUnitPrice() : BigDecimal.ZERO; |
| | | unit = structure.getUnit(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | actualTotalCost = actualTotalCost.add(rawQty.multiply(unitPrice)); |
| | | BigDecimal tonnage = UnitUtils.convertValueToTon(rawQty, unit); |
| | | actualQty = actualQty.add(tonnage); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | detail.setActualQty(actualQty); |
| | | detail.setActualTotal(actualTotalCost); |
| | | if (actualQty.compareTo(BigDecimal.ZERO) > 0) { |
| | | detail.setActualPrice(actualTotalCost.divide(actualQty, 15, RoundingMode.HALF_UP)); |
| | | } else { |
| | | detail.setActualPrice(BigDecimal.ZERO); |
| | | } |
| | | |
| | | } else if ("能耗成本".equals(detail.getCategory())) { |
| | | Energy energy = energyMap.get(detail.getSubjectName()); |
| | | if (energy != null) { |
| | | BigDecimal unitPrice = energy.getUnitPrice() != null ? energy.getUnitPrice() : BigDecimal.ZERO; |
| | | for (EnergyConsumptionDetail eDetail : energyDetails) { |
| | | if (Objects.equals(eDetail.getEnergyId(), energy.getId())) { |
| | | actualQty = actualQty.add(eDetail.getDosage() != null ? eDetail.getDosage() : BigDecimal.ZERO); |
| | | } |
| | | } |
| | | detail.setActualQty(actualQty); |
| | | detail.setActualPrice(unitPrice); |
| | | detail.setActualTotal(actualQty.multiply(unitPrice)); |
| | | } else { |
| | | detail.setActualQty(BigDecimal.ZERO); |
| | | detail.setActualPrice(BigDecimal.ZERO); |
| | | detail.setActualTotal(BigDecimal.ZERO); |
| | | } |
| | | } else { |
| | | detail.setActualQty(BigDecimal.ZERO); |
| | | detail.setActualPrice(BigDecimal.ZERO); |
| | | detail.setActualTotal(BigDecimal.ZERO); |
| | | } |
| | | |
| | | // 差异值百分比计算 |
| | | detail.setDiffQty(calculatePercentage(detail.getActualQty(), detail.getBudgetQty())); |
| | | detail.setDiffPrice(calculatePercentage(detail.getActualPrice(), detail.getBudgetPrice())); |
| | | detail.setDiffTotal(calculatePercentage(detail.getActualTotal(), detail.getBudgetTotal())); |
| | | } |
| | | |
| | | return dtoList.stream().collect(Collectors.groupingBy(ProductionSettlementDetailsDto::getCategory)); |
| | | } |
| | | |
| | | @Override |
| | | public List<String> getProductTypes(ProductionSettlementDto dto) { |
| | | String finalPeriodTime = parsePeriodTime(dto != null ? dto.getPeriodTime() : null); |
| | | |
| | | ProductionSettlementBatches batch = this.lambdaQuery() |
| | | .eq(ProductionSettlementBatches::getPeriodTime, finalPeriodTime) |
| | | .one(); |
| | | if (batch == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | return productionSettlementDetailsService.lambdaQuery() |
| | | .eq(ProductionSettlementDetails::getBatchId, batch.getId()) |
| | | .list() |
| | | .stream() |
| | | .map(ProductionSettlementDetails::getProductType) |
| | | .filter(StringUtils::isNotBlank) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public List<String> getSubjectNames(ProductionSettlementDto dto) { |
| | | String finalPeriodTime = parsePeriodTime(dto != null ? dto.getPeriodTime() : null); |
| | | |
| | | ProductionSettlementBatches batch = this.lambdaQuery() |
| | | .eq(ProductionSettlementBatches::getPeriodTime, finalPeriodTime) |
| | | .one(); |
| | | if (batch == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | return productionSettlementDetailsService.lambdaQuery() |
| | | .eq(ProductionSettlementDetails::getBatchId, batch.getId()) |
| | | .list() |
| | | .stream() |
| | | .map(ProductionSettlementDetails::getSubjectName) |
| | | .filter(StringUtils::isNotBlank) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | /** |
| | | * 核算月份,为空时默认取当前月,格式为 yyyy-MM |
| | | */ |
| | | private String parsePeriodTime(String periodTime) { |
| | | if (StringUtils.isBlank(periodTime)) { |
| | | return YearMonth.now().format(DateTimeFormatter.ofPattern("yyyy-MM")); |
| | | } |
| | | try { |
| | | return YearMonth.parse(periodTime, DateTimeFormatter.ofPattern("yyyy-MM")) |
| | | .format(DateTimeFormatter.ofPattern("yyyy-MM")); |
| | | } catch (Exception e) { |
| | | throw new ServiceException("日期格式错误,请使用 yyyy-MM 格式"); |
| | | } |
| | | } |
| | | |
| | | private String calculatePercentage(BigDecimal actual, BigDecimal budget) { |
| | | if (budget == null || budget.compareTo(BigDecimal.ZERO) == 0) { |
| | | return actual.compareTo(BigDecimal.ZERO) > 0 ? "100%" : "0%"; |
| | | } |
| | | BigDecimal diff = actual.subtract(budget); |
| | | BigDecimal percentage = diff.divide(budget, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100")); |
| | | return percentage.stripTrailingZeros().toPlainString() + "%"; |
| | | } |
| | | |
| | | @Override |
| | | public ProductionSettlementTotalDto getTotalCosts(ProductionSettlementDto dto) { |
| | | // 复用 getSettlement 获取含实际值的明细列表 |
| | | Map<String, List<ProductionSettlementDetailsDto>> settlementMap = getSettlement(dto); |
| | | |
| | | List<ProductionSettlementDetailsDto> allDetails = settlementMap.values().stream() |
| | | .flatMap(List::stream) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 按 dto 中的其余条件在内存中过滤 |
| | | if (dto != null) { |
| | | if (StringUtils.isNotBlank(dto.getProductType())) { |
| | | String targetType = dto.getProductType().trim(); |
| | | allDetails = allDetails.stream() |
| | | .filter(d -> targetType.equals(d.getProductType() != null ? d.getProductType().trim() : "")) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | if (StringUtils.isNotBlank(dto.getSubjectName())) { |
| | | String targetSubject = dto.getSubjectName().trim(); |
| | | allDetails = allDetails.stream() |
| | | .filter(d -> targetSubject.equals(d.getSubjectName() != null ? d.getSubjectName().trim() : "")) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | if (StringUtils.isNotBlank(dto.getCostType())) { |
| | | String targetCostType = dto.getCostType().trim(); |
| | | allDetails = allDetails.stream() |
| | | .filter(d -> targetCostType.equals(d.getCategory() != null ? d.getCategory().trim() : "")) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | } |
| | | |
| | | // 汇总标准成本与实际成本 |
| | | BigDecimal budgetTotal = allDetails.stream() |
| | | .map(d -> d.getBudgetTotal() != null ? d.getBudgetTotal() : BigDecimal.ZERO) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | |
| | | BigDecimal actualTotal = allDetails.stream() |
| | | .map(d -> d.getActualTotal() != null ? d.getActualTotal() : BigDecimal.ZERO) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | |
| | | BigDecimal diffTotal = actualTotal.subtract(budgetTotal); |
| | | |
| | | // 差异率:带正负号 |
| | | String diffRate; |
| | | if (budgetTotal.compareTo(BigDecimal.ZERO) == 0) { |
| | | diffRate = actualTotal.compareTo(BigDecimal.ZERO) > 0 ? "+100%" : "0%"; |
| | | } else { |
| | | BigDecimal rate = diffTotal.divide(budgetTotal, 4, RoundingMode.HALF_UP) |
| | | .multiply(new BigDecimal("100")); |
| | | String sign = rate.compareTo(BigDecimal.ZERO) >= 0 ? "+" : ""; |
| | | diffRate = sign + rate.stripTrailingZeros().toPlainString() + "%"; |
| | | } |
| | | |
| | | ProductionSettlementTotalDto result = new ProductionSettlementTotalDto(); |
| | | result.setBudgetTotal(budgetTotal); |
| | | result.setActualTotal(actualTotal); |
| | | result.setDiffTotal(diffTotal); |
| | | result.setDiffRate(diffRate); |
| | | return result; |
| | | } |
| | | } |