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.ProductionCostingDeptMapper;
|
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.ProductionCostingDept;
|
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;
|
|
/**
|
* 生产核算服务实现
|
* <p>
|
* 计算规则(严格按需求):
|
* 白盒部门:折算时长 = 月产量 × 标准数量 × 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 ProductionCostingDeptMapper deptMapper;
|
private final ProductionCostingWorkdayMapper workdayMapper;
|
|
@Override
|
public List<ProductionCostingDetailVo> detailList(ProductionCostingQueryDto dto) {
|
ProductionCostingQueryDto query = dto == null ? new ProductionCostingQueryDto() : dto;
|
List<ProductionCostingRawVo> rows = productionCostingMapper.selectCostingRows(query);
|
|
// 标准数量:成品(1) 按工序;白盒(2) 按 工序+型号
|
Map<Long, BigDecimal> standardByProcess = new HashMap<>();
|
Map<String, BigDecimal> standardByProcessModel = new HashMap<>();
|
List<ProductionCostingStandard> 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<String, ProductionCostingFactor> factorMap = new HashMap<>();
|
for (ProductionCostingFactor f : factorMapper.selectList(null)) {
|
factorMap.put(key(f.getProcessId(), f.getStaffId()), f);
|
}
|
|
List<ProductionCostingDetailVo> result = new ArrayList<>();
|
|
// ---------- 成品部门:按 员工 + 月份 + 工序 聚合 ----------
|
Map<String, ProductionCostingDetailVo> finishedGroup = new LinkedHashMap<>();
|
// ---------- 白盒部门:按 员工 + 月份 + 工序 + 型号 聚合 ----------
|
Map<String, ProductionCostingDetailVo> 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<ProductionCostingSummaryVo> 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<ProductionCostingDetailVo> details = detailList(query);
|
Map<Long, ProductionCostingSummaryVo> 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<ProductionCostingSummaryVo> 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<String, Object> 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<String, Object> 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<ProductionCostingDept> listDept() {
|
return deptMapper.selectDeptConfigs();
|
}
|
|
@Override
|
public boolean saveDept(ProductionCostingDept dept) {
|
return deptMapper.insert(dept) > 0;
|
}
|
|
@Override
|
public boolean updateDept(ProductionCostingDept dept) {
|
return deptMapper.updateById(dept) > 0;
|
}
|
|
@Override
|
public boolean removeDept(Long id) {
|
return deptMapper.deleteById(id) > 0;
|
}
|
|
@Override
|
public List<ProductionCostingStandard> 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<ProductionCostingFactor> 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<ProductionCostingWorkday> 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<ProductionCostingWorkday>()
|
.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();
|
}
|
}
|