package com.ruoyi.production.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.production.bean.dto.ProductionCostingQueryDto; import com.ruoyi.production.bean.vo.ProductionCostingDetailVo; import com.ruoyi.production.bean.vo.ProductionCostingRawVo; import com.ruoyi.production.bean.vo.ProductionCostingSummaryVo; import com.ruoyi.production.mapper.ProductionCostingProcessMapper; import com.ruoyi.production.mapper.ProductionCostingFactorMapper; import com.ruoyi.production.mapper.ProductionCostingMapper; import com.ruoyi.production.mapper.ProductionCostingStandardMapper; import com.ruoyi.production.mapper.ProductionCostingWorkdayMapper; import com.ruoyi.production.pojo.ProductionCostingProcess; import com.ruoyi.production.pojo.ProductionCostingFactor; import com.ruoyi.production.pojo.ProductionCostingStandard; import com.ruoyi.production.pojo.ProductionCostingWorkday; import com.ruoyi.production.service.ProductionCostingService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.DayOfWeek; import java.time.YearMonth; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 生产核算服务实现 *

* 计算规则(严格按需求): * 白盒部门:折算时长 = 月产量 × 标准数量 × 8 * 成品部门:月产折合小提 = 小提琴 + 大提琴×大提折算率 + 贝斯×贝斯折算率 * 折算时长 = 月产折合小提 ÷ 标准数量 × 8 * 月时间合计 = 折算时长求和 * 折合倍数 = 月时间合计 ÷(当月工作日 × 8) */ @Service @RequiredArgsConstructor public class ProductionCostingServiceImpl implements ProductionCostingService { private static final BigDecimal EIGHT = new BigDecimal("8"); private static final BigDecimal ONE = new BigDecimal("1"); private static final String VIOLIN = "小提琴"; private static final String CELLO = "大提琴"; private static final String BASS = "贝斯"; private final ProductionCostingMapper productionCostingMapper; private final ProductionCostingStandardMapper standardMapper; private final ProductionCostingFactorMapper factorMapper; private final ProductionCostingProcessMapper processMapper; private final ProductionCostingWorkdayMapper workdayMapper; @Override public List detailList(ProductionCostingQueryDto dto) { ProductionCostingQueryDto query = dto == null ? new ProductionCostingQueryDto() : dto; List rows = productionCostingMapper.selectCostingRows(query); // 标准数量:成品(1) 按工序;白盒(2) 按 工序+型号 Map standardByProcess = new HashMap<>(); Map standardByProcessModel = new HashMap<>(); List standardList = standardMapper.selectList(null); for (ProductionCostingStandard s : standardList) { if (s.getProcessId() == null || s.getStandardQuantity() == null) { continue; } if (Integer.valueOf(2).equals(s.getDeptType())) { standardByProcessModel.put(key(s.getProcessId(), s.getProductModelId()), s.getStandardQuantity()); } else { standardByProcess.put(s.getProcessId(), s.getStandardQuantity()); } } // 折算率:工序 + 员工 Map factorMap = new HashMap<>(); for (ProductionCostingFactor f : factorMapper.selectList(null)) { factorMap.put(key(f.getProcessId(), f.getStaffId()), f); } List result = new ArrayList<>(); // ---------- 成品部门:按 员工 + 月份 + 工序 聚合 ---------- Map finishedGroup = new LinkedHashMap<>(); // ---------- 白盒部门:按 员工 + 月份 + 工序 + 型号 聚合 ---------- Map whiteGroup = new LinkedHashMap<>(); for (ProductionCostingRawVo row : rows) { BigDecimal qty = nz(row.getQuantity()); boolean white = Integer.valueOf(2).equals(row.getDeptType()); if (white) { String k = key(row.getStaffId(), row.getYearMonth(), row.getProcessId(), row.getProductModelId()); ProductionCostingDetailVo vo = whiteGroup.get(k); if (vo == null) { vo = new ProductionCostingDetailVo(); vo.setStaffId(row.getStaffId()); vo.setStaffName(row.getStaffName()); vo.setYearMonth(row.getYearMonth()); vo.setProcessId(row.getProcessId()); vo.setProcessName(row.getProcessName()); vo.setDeptType(row.getDeptType()); vo.setProductModelId(row.getProductModelId()); vo.setProductName(row.getProductName()); vo.setModelName(row.getModelName()); vo.setQuantity(BigDecimal.ZERO); whiteGroup.put(k, vo); } vo.setQuantity(vo.getQuantity().add(qty)); } else { String k = key(row.getStaffId(), row.getYearMonth(), row.getProcessId()); ProductionCostingDetailVo vo = finishedGroup.get(k); if (vo == null) { vo = new ProductionCostingDetailVo(); vo.setStaffId(row.getStaffId()); vo.setStaffName(row.getStaffName()); vo.setYearMonth(row.getYearMonth()); vo.setProcessId(row.getProcessId()); vo.setProcessName(row.getProcessName()); vo.setDeptType(row.getDeptType()); vo.setQuantity(BigDecimal.ZERO); vo.setViolinQty(BigDecimal.ZERO); vo.setCelloQty(BigDecimal.ZERO); vo.setBassQty(BigDecimal.ZERO); finishedGroup.put(k, vo); } vo.setQuantity(vo.getQuantity().add(qty)); if (VIOLIN.equals(row.getProductName())) { vo.setViolinQty(vo.getViolinQty().add(qty)); } else if (CELLO.equals(row.getProductName())) { vo.setCelloQty(vo.getCelloQty().add(qty)); } else if (BASS.equals(row.getProductName())) { vo.setBassQty(vo.getBassQty().add(qty)); } else { // 非乐器产品默认按小提琴当量 1:1 计入 vo.setViolinQty(vo.getViolinQty().add(qty)); } } } // 成品:折算 for (ProductionCostingDetailVo vo : finishedGroup.values()) { ProductionCostingFactor factor = factorMap.get(key(vo.getProcessId(), vo.getStaffId())); BigDecimal violinFactor = factor == null ? ONE : nzDefault(factor.getViolinFactor(), ONE); BigDecimal celloFactor = factor == null ? ONE : nzDefault(factor.getViolaFactor(), ONE); BigDecimal bassFactor = factor == null ? ONE : nzDefault(factor.getBassFactor(), ONE); vo.setViolinFactor(violinFactor); vo.setCelloFactor(celloFactor); vo.setBassFactor(bassFactor); BigDecimal folded = nz(vo.getViolinQty()).multiply(violinFactor) .add(nz(vo.getCelloQty()).multiply(celloFactor)) .add(nz(vo.getBassQty()).multiply(bassFactor)) .setScale(2, RoundingMode.HALF_UP); vo.setFoldedQuantity(folded); BigDecimal standard = standardByProcess.get(vo.getProcessId()); vo.setStandardQuantity(standard); vo.setConvertedHours(calcFinishedHours(folded, standard)); } // 白盒:折算时长 = 月产量 × 标准数量 × 8 for (ProductionCostingDetailVo vo : whiteGroup.values()) { BigDecimal standard = standardByProcessModel.get(key(vo.getProcessId(), vo.getProductModelId())); vo.setStandardQuantity(standard); BigDecimal hours = nz(standard).compareTo(BigDecimal.ZERO) <= 0 ? BigDecimal.ZERO : nz(vo.getQuantity()).multiply(standard).multiply(EIGHT); vo.setConvertedHours(hours.setScale(2, RoundingMode.HALF_UP)); } result.addAll(finishedGroup.values()); result.addAll(whiteGroup.values()); return result; } @Override public List summary(ProductionCostingQueryDto dto) { ProductionCostingQueryDto query = dto == null ? new ProductionCostingQueryDto() : dto; String yearMonth = (query.getYearMonth() == null || query.getYearMonth().isEmpty()) ? YearMonth.now().toString() : query.getYearMonth(); query.setYearMonth(yearMonth); int workDays = resolveWorkDays(yearMonth); BigDecimal fullHours = new BigDecimal(workDays).multiply(EIGHT); List details = detailList(query); Map map = new LinkedHashMap<>(); for (ProductionCostingDetailVo d : details) { ProductionCostingSummaryVo vo = map.get(d.getStaffId()); if (vo == null) { vo = new ProductionCostingSummaryVo(); vo.setStaffId(d.getStaffId()); vo.setStaffName(d.getStaffName()); vo.setYearMonth(yearMonth); vo.setOutput(BigDecimal.ZERO); vo.setMonthTimeTotal(BigDecimal.ZERO); vo.setWorkDays(workDays); vo.setMonthHours(fullHours); map.put(d.getStaffId(), vo); } vo.setOutput(vo.getOutput().add(nz(d.getQuantity())).setScale(2, RoundingMode.HALF_UP)); vo.setMonthTimeTotal(vo.getMonthTimeTotal().add(nz(d.getConvertedHours())).setScale(2, RoundingMode.HALF_UP)); } List result = new ArrayList<>(map.values()); for (ProductionCostingSummaryVo vo : result) { BigDecimal divisor = fullHours; BigDecimal ratio = divisor.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : vo.getMonthTimeTotal().divide(divisor, 2, RoundingMode.HALF_UP); vo.setConversionRatio(ratio); } return result; } @Override public Map workdayInfo(String yearMonth) { String ym = (yearMonth == null || yearMonth.isEmpty()) ? YearMonth.now().toString() : yearMonth; ProductionCostingWorkday cfg = findWorkday(ym); int workDays = (cfg != null && cfg.getWorkDays() != null) ? cfg.getWorkDays() : workDays(ym); Map map = new HashMap<>(); map.put("yearMonth", ym); map.put("workDays", workDays); map.put("dayHours", 8); map.put("monthHours", new BigDecimal(workDays).multiply(EIGHT).setScale(2, RoundingMode.HALF_UP)); map.put("source", (cfg != null && cfg.getWorkDays() != null) ? "CONFIG" : "DEFAULT"); return map; } /* ================= 配置 CRUD ================= */ @Override public List listProcess(ProductionCostingProcess query) { return processMapper.selectProcessList(query == null ? new ProductionCostingProcess() : query); } @Override public boolean saveProcess(ProductionCostingProcess process) { return processMapper.insert(process) > 0; } @Override public boolean updateProcess(ProductionCostingProcess process) { return processMapper.updateById(process) > 0; } @Override public boolean removeProcess(Long id) { return processMapper.deleteById(id) > 0; } @Override public List listStandard(ProductionCostingStandard query) { return standardMapper.selectStandardList(query == null ? new ProductionCostingStandard() : query); } @Override public boolean saveStandard(ProductionCostingStandard standard) { return standardMapper.insert(standard) > 0; } @Override public boolean updateStandard(ProductionCostingStandard standard) { return standardMapper.updateById(standard) > 0; } @Override public boolean removeStandard(Long id) { return standardMapper.deleteById(id) > 0; } @Override public List listFactor(ProductionCostingFactor query) { return factorMapper.selectFactorList(query == null ? new ProductionCostingFactor() : query); } @Override public boolean saveFactor(ProductionCostingFactor factor) { return factorMapper.insert(factor) > 0; } @Override public boolean updateFactor(ProductionCostingFactor factor) { return factorMapper.updateById(factor) > 0; } @Override public boolean removeFactor(Long id) { return factorMapper.deleteById(id) > 0; } @Override public List listWorkday(ProductionCostingWorkday query) { return workdayMapper.selectWorkdayList(query == null ? new ProductionCostingWorkday() : query); } @Override public boolean saveWorkday(ProductionCostingWorkday workday) { return workdayMapper.insert(workday) > 0; } @Override public boolean updateWorkday(ProductionCostingWorkday workday) { return workdayMapper.updateById(workday) > 0; } @Override public boolean removeWorkday(Long id) { return workdayMapper.deleteById(id) > 0; } /* ================= 工具方法 ================= */ /** 查月度工作日配置 */ private ProductionCostingWorkday findWorkday(String yearMonth) { if (yearMonth == null || yearMonth.isEmpty()) { return null; } return workdayMapper.selectOne(new LambdaQueryWrapper() .eq(ProductionCostingWorkday::getYearMonth, yearMonth) .last("limit 1")); } /** 工作日:优先取月度配置,未配置则按周一~周五 */ private int resolveWorkDays(String yearMonth) { ProductionCostingWorkday cfg = findWorkday(yearMonth); if (cfg != null && cfg.getWorkDays() != null) { return cfg.getWorkDays(); } return workDays(yearMonth); } /** 成品折算:折合小提琴 / 标准数量 * 8;标准数量<=0 返回 0 */ private BigDecimal calcFinishedHours(BigDecimal folded, BigDecimal standard) { if (standard == null || standard.compareTo(BigDecimal.ZERO) <= 0) { return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); } return folded.divide(standard, 8, RoundingMode.HALF_UP) .multiply(EIGHT) .setScale(2, RoundingMode.HALF_UP); } private int workDays(String yearMonth) { try { YearMonth ym = YearMonth.parse(yearMonth); int count = 0; for (int d = 1; d <= ym.lengthOfMonth(); d++) { DayOfWeek dow = ym.atDay(d).getDayOfWeek(); if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) { count++; } } return count; } catch (Exception e) { return 0; } } private BigDecimal nz(BigDecimal v) { return v == null ? BigDecimal.ZERO : v; } private BigDecimal nzDefault(BigDecimal v, BigDecimal def) { return v == null ? def : v; } private String key(Object... parts) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < parts.length; i++) { if (i > 0) { sb.append(':'); } sb.append(parts[i] == null ? "null" : parts[i]); } return sb.toString(); } }