package cn.iocoder.yudao.module.mes.service.pro.feedback;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.*;
|
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
|
import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackTypeEnum;
|
import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
|
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
|
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum;
|
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
|
import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
|
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
|
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
|
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
|
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
|
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
|
import cn.iocoder.yudao.module.mes.service.wm.productissue.MesWmProductIssueService;
|
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
|
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
|
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
|
import cn.iocoder.yudao.module.mdm.api.item.MdmItemBatchConfigApi;
|
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemBatchConfigRespDTO;
|
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
|
import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsRespDTO;
|
import cn.iocoder.yudao.module.mes.api.event.ProductionFinishedEvent;
|
import jakarta.annotation.Resource;
|
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.annotation.Lazy;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.Collection;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.stream.Collectors;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
|
|
/**
|
* MES 生产报工 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
public class MesProFeedbackServiceImpl implements MesProFeedbackService {
|
|
@Resource
|
private MesProFeedbackMapper feedbackMapper;
|
@Resource
|
private MesFeedbackParamService feedbackParamService;
|
|
@Resource
|
private MesProWorkOrderService workOrderService;
|
@Resource
|
private MesProRouteProcessService routeProcessService;
|
@Resource
|
private MesProWorkOrderBomService workOrderBomService;
|
@Resource
|
private MesProWorkOrderProcessService workOrderProcessService;
|
@Resource
|
@Lazy // 避免循环依赖
|
private MesProTaskService taskService;
|
@Resource
|
private MesWmItemConsumeService itemConsumeService;
|
@Resource
|
private MesWmProductIssueService productIssueService;
|
@Resource
|
private MesWmProductProduceService productProduceService;
|
@Resource
|
private MesWmProductProduceLineService produceLineService;
|
@Resource
|
private MesProMpsApi mpsApi;
|
@Resource
|
private MesMdAutoCodeRecordService autoCodeRecordService;
|
@Resource
|
private ApplicationEventPublisher eventPublisher;
|
@Resource
|
private MesMdmItemService mdmItemService;
|
@Resource
|
private MdmItemBatchConfigApi itemBatchConfigApi;
|
@Resource
|
private MesQcTemplateItemService templateItemService;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) {
|
// 1. 校验
|
MesProTaskDO task = validateFeedbackData(createReqVO);
|
task = feedbackParamService.lockTask(task.getId());
|
validateLockedTaskDimensions(createReqVO, task);
|
|
// 2. 插入(主键由数据库自增,忽略请求中可能残留的 id;自动填充 itemId)
|
MesProFeedbackDO feedback = BeanUtils.toBean(createReqVO, MesProFeedbackDO.class)
|
.setId(null)
|
.setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus())
|
.setItemId(task.getItemId())
|
.setParamSnapshot(feedbackParamService.buildDraft(task, createReqVO.getParamValues()));
|
feedbackMapper.insert(feedback);
|
return feedback.getId();
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateFeedback(MesProFeedbackSaveReqVO updateReqVO) {
|
// 先锁报工再锁任务,与提交顺序一致。
|
MesProFeedbackDO original = feedbackMapper.selectOne(new LambdaQueryWrapperX<MesProFeedbackDO>()
|
.eq(MesProFeedbackDO::getId, updateReqVO.getId()).last("FOR UPDATE"));
|
if (original == null) throw exception(PRO_FEEDBACK_NOT_EXISTS);
|
if (!Objects.equals(original.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus()))
|
throw exception(PRO_FEEDBACK_NOT_PREPARE);
|
// 1.2 校验业务数据
|
MesProTaskDO task = validateFeedbackData(updateReqVO);
|
task = feedbackParamService.lockTask(task.getId());
|
validateLockedTaskDimensions(updateReqVO, task);
|
|
// 2. 更新(自动填充 itemId)
|
MesProFeedbackDO updateObj = BeanUtils.toBean(updateReqVO, MesProFeedbackDO.class)
|
.setItemId(task.getItemId());
|
if (updateReqVO.getParamValues() != null) {
|
String snapshot = feedbackParamService.buildDraft(task, updateReqVO.getParamValues());
|
updateObj.setParamSnapshot(snapshot == null ? "" : snapshot);
|
} else if (!Objects.equals(original.getTaskId(), task.getId())) {
|
updateObj.setParamSnapshot("");
|
}
|
feedbackMapper.updateById(updateObj);
|
}
|
|
private void validateLockedTaskDimensions(MesProFeedbackSaveReqVO req, MesProTaskDO task) {
|
if (!MesProTaskStatusEnum.canFeedback(task.getStatus())) throw exception(PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED);
|
if (!Objects.equals(req.getProcessId(), task.getProcessId())
|
|| !Objects.equals(req.getWorkOrderId(), task.getWorkOrderId())
|
|| !Objects.equals(req.getRouteId(), task.getRouteId()))
|
throw exception(PRO_FEEDBACK_PARAM_INVALID, "任务信息已变更,请重新选择任务");
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public MesProFeedbackImportRespVO importFeedbackList(List<MesProFeedbackImportExcelVO> importList,
|
boolean updateSupport) {
|
// 1. 校验非空
|
if (CollUtil.isEmpty(importList)) {
|
throw exception(MES_PRO_FEEDBACK_IMPORT_LIST_IS_EMPTY);
|
}
|
|
// 2. 逐行处理
|
MesProFeedbackImportRespVO respVO = MesProFeedbackImportRespVO.builder()
|
.createCodes(new ArrayList<>()).updateCodes(new ArrayList<>())
|
.failureCodes(new LinkedHashMap<>()).build();
|
AtomicInteger index = new AtomicInteger(1);
|
importList.forEach(importItem -> {
|
int currentIndex = index.getAndIncrement();
|
String key = StrUtil.blankToDefault(importItem.getCode(), "第 " + currentIndex + " 行");
|
if (StrUtil.isBlank(importItem.getCode())) {
|
respVO.getFailureCodes().put(key, "报工单编号不能为空");
|
return;
|
}
|
if (StrUtil.isBlank(importItem.getTaskCode())) {
|
respVO.getFailureCodes().put(key, "生产任务编码不能为空");
|
return;
|
}
|
// 2.1 解析生产任务
|
MesProTaskDO task = taskService.getTaskByCode(importItem.getTaskCode().trim());
|
if (task == null) {
|
respVO.getFailureCodes().put(key, "生产任务编码[" + importItem.getTaskCode() + "]不存在");
|
return;
|
}
|
// 2.2 构建请求并创建/更新(复用 create/update 服务,报工人/审核人取当前登录用户)
|
MesProFeedbackSaveReqVO reqVO = BeanUtils.toBean(importItem, MesProFeedbackSaveReqVO.class);
|
reqVO.setTaskId(task.getId());
|
Long loginUserId = getLoginUserId();
|
reqVO.setFeedbackUserId(ObjUtil.defaultIfNull(reqVO.getFeedbackUserId(), loginUserId));
|
reqVO.setApproveUserId(ObjUtil.defaultIfNull(reqVO.getApproveUserId(), loginUserId));
|
MesProFeedbackDO existFeedback = feedbackMapper.selectByCode(importItem.getCode());
|
try {
|
if (existFeedback == null) {
|
reqVO.setId(null);
|
createFeedback(reqVO);
|
respVO.getCreateCodes().add(importItem.getCode());
|
} else if (updateSupport) {
|
reqVO.setId(existFeedback.getId());
|
updateFeedback(reqVO);
|
respVO.getUpdateCodes().add(importItem.getCode());
|
} else {
|
respVO.getFailureCodes().put(key, "报工单编号已存在");
|
}
|
} catch (ServiceException e) {
|
respVO.getFailureCodes().put(key, e.getMessage());
|
}
|
});
|
return respVO;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void deleteFeedback(Long id) {
|
MesProFeedbackDO feedback = feedbackMapper.selectOne(new LambdaQueryWrapperX<MesProFeedbackDO>()
|
.eq(MesProFeedbackDO::getId, id).last("FOR UPDATE"));
|
if (feedback == null) throw exception(PRO_FEEDBACK_NOT_EXISTS);
|
if (!Objects.equals(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus()))
|
throw exception(PRO_FEEDBACK_NOT_PREPARE);
|
feedbackMapper.deleteById(id);
|
}
|
|
@Override
|
public MesProFeedbackDO getFeedback(Long id) {
|
return feedbackMapper.selectById(id);
|
}
|
|
@Override
|
public List<MesProFeedbackDO> getFeedbackListByTaskIds(Collection<Long> taskIds) {
|
if (CollUtil.isEmpty(taskIds)) {
|
return Collections.emptyList();
|
}
|
return feedbackMapper.selectListByTaskIds(taskIds);
|
}
|
|
@Override
|
public List<MesProFeedbackDO> getFeedbackListByTaskId(Long taskId) {
|
return feedbackMapper.selectListByTaskId(taskId);
|
}
|
|
@Override
|
public PageResult<MesProFeedbackDO> getFeedbackPage(MesProFeedbackPageReqVO pageReqVO) {
|
return feedbackMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void submitFeedback(Long id) {
|
MesProFeedbackDO feedback = feedbackMapper.selectOne(new LambdaQueryWrapperX<MesProFeedbackDO>()
|
.eq(MesProFeedbackDO::getId, id).last("FOR UPDATE"));
|
if (feedback == null) throw exception(PRO_FEEDBACK_NOT_EXISTS);
|
if (!Objects.equals(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus()))
|
throw exception(PRO_FEEDBACK_NOT_PREPARE);
|
MesProTaskDO task = feedbackParamService.lockTask(feedback.getTaskId());
|
validateTaskAssignee(task);
|
String snapshot = feedbackParamService.complete(task, feedback.getParamSnapshot());
|
MesProFeedbackDO update = new MesProFeedbackDO().setId(id)
|
.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
|
if (snapshot != null) update.setParamSnapshot(snapshot);
|
feedbackMapper.updateById(update);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void rejectFeedback(Long id) {
|
// 1. 校验存在 + 审批中状态
|
MesProFeedbackDO feedback = lockFeedbackStatusApproving(id);
|
// 1.1 校验当前登录人是该报工单的指定审核人
|
validateCurrentUserIsApprover(feedback);
|
|
// 2. 更新状态为草稿
|
feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
|
.setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus()));
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public boolean approveFeedback(MesProFeedbackApproveReqVO reqVO) {
|
// 1.1 校验存在 + 审批中状态
|
MesProFeedbackDO feedback = lockFeedbackStatusApproving(reqVO.getId());
|
// 1.1.1 校验当前登录人是该报工单的指定审核人
|
validateCurrentUserIsApprover(feedback);
|
// 1.2 校验报工数量 > 0
|
if (feedback.getFeedbackQuantity() == null
|
|| feedback.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
throw exception(PRO_FEEDBACK_QUANTITY_MUST_POSITIVE);
|
}
|
// 1.3 校验任务未完成
|
taskService.validateTaskNotFinished(feedback.getTaskId());
|
|
// 2.1 查询工序的检验标识(工单工序快照优先,需在 uncheckQuantity 校验之前,因为质检工序允许 uncheckQuantity > 0)
|
ProcessFlags processFlags = resolveProcessFlags(feedback.getRouteId(), feedback.getWorkOrderId(),
|
feedback.getProcessId());
|
boolean checkFlag = processFlags.checkFlag();
|
// 2.2 非质检工序:仍有待检数量时不能审批(质检工序的 uncheckQuantity > 0 是正常状态,不做拦截)
|
if (!checkFlag
|
&& feedback.getUncheckQuantity() != null
|
&& feedback.getUncheckQuantity().compareTo(BigDecimal.ZERO) > 0) {
|
throw exception(PRO_FEEDBACK_UNCHECK_QUANTITY_EXISTS, feedback.getUncheckQuantity());
|
}
|
|
if (checkFlag) {
|
try {
|
templateItemService.getRequiredTemplateByItemIdAndType(feedback.getItemId(), MesQcTypeEnum.IPQC.getType());
|
} catch (ServiceException e) {
|
if (!Objects.equals(e.getCode(), QC_NO_TEMPLATE.getCode())) throw e;
|
MesMdmItemDO item = mdmItemService.getItem(feedback.getItemId());
|
String name = item != null ? item.getName() + "(" + item.getCode() + ")" : "ID " + feedback.getItemId();
|
throw exception(PRO_FEEDBACK_PARAM_INVALID, "物料【" + name + "】未配置启用的过程检验模板,请先配置后再审批");
|
}
|
} else {
|
BigDecimal qualified = ObjectUtil.defaultIfNull(feedback.getQualifiedQuantity(), BigDecimal.ZERO);
|
BigDecimal unqualified = ObjectUtil.defaultIfNull(feedback.getUnqualifiedQuantity(), BigDecimal.ZERO);
|
if (qualified.signum() < 0 || unqualified.signum() < 0
|
|| qualified.add(unqualified).compareTo(feedback.getFeedbackQuantity()) != 0) {
|
throw exception(PRO_FEEDBACK_PARAM_INVALID, "合格数和不合格数必须非负且合计等于报工数量");
|
}
|
}
|
// 3. 物料消耗:倒冲工序按 BOM 自动扣料;非倒冲工序跳过(物料走手工领料/投料)
|
boolean backflushFlag = processFlags.backflushFlag();
|
if (backflushFlag) {
|
// 3.1 校验当前倒冲工序必须配置投料 BOM,避免静默不扣料
|
List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderIdAndProcessId(
|
feedback.getWorkOrderId(), feedback.getProcessId());
|
if (CollUtil.isEmpty(boms)) {
|
throw exception(PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED);
|
}
|
// 3.2 生成消耗记录并执行扣减(BOM × 报工数量,FIFO 扣线边库)
|
MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback);
|
if (itemConsume != null) {
|
itemConsumeService.finishItemConsume(itemConsume.getId());
|
// 消耗单负责实际扣库;领料单仅作为倒冲业务凭证,避免重复扣减库存
|
productIssueService.generateBackflushProductIssue(feedback, itemConsume);
|
}
|
}
|
|
// 4. 先生成产出;进度仅在产出完成且报工状态变为完成后更新。
|
if (checkFlag) {
|
// 6.1 需要检验:生成产出单(质量状态=待检验),更新报工状态为待检验,等质检完成回调后再入库
|
// 完成时回调见:MesQcIpqcServiceImpl#finishIpqc → splitPendingAndFinishProduce + updateProFeedbackWhenIpqcFinish
|
productProduceService.generateProductProduce(feedback, true, reqVO);
|
feedbackMapper.updateById(new MesProFeedbackDO().setId(reqVO.getId())
|
.setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus()));
|
return false;
|
} else {
|
// 6.2 无需检验:生成产出单(按合格/不合格拆行),直接完成入库
|
MesWmProductProduceDO produce = productProduceService.generateProductProduce(feedback, false, reqVO);
|
productProduceService.finishProductProduce(produce.getId());
|
feedbackMapper.updateById(new MesProFeedbackDO().setId(reqVO.getId())
|
.setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus()).setUncheckQuantity(BigDecimal.ZERO));
|
updateTaskAndWorkOrderByFeedback(feedback);
|
|
}
|
|
return true; // 已完成
|
}
|
|
/**
|
* 根据当前报工单的最终结果,更新生产任务和生产工单的进度
|
*
|
* <ul>
|
* <li>使用报工数量(feedbackQuantity),累加任务和工单的已生产数量</li>
|
* <li>使用产出单行按 qualityStatus 聚合的合格品/不合格品数量,累加任务的合格品/不合格品数量
|
* (不直接用 feedback 上的数量,确保质检回调场景下数量来源正确)</li>
|
* isPush 是否推送生产完成事件
|
* </ul>
|
*
|
* @param feedback 报工单
|
*/
|
private void updateTaskAndWorkOrderByFeedback(MesProFeedbackDO feedback) {
|
// 1. 查询该报工单关联的产出单行,只按主产出物料(报工物料)聚合合格/不合格数量,
|
// 联产出物料单独检验,不计入任务进度
|
BigDecimal qualifiedQty = BigDecimal.ZERO;
|
BigDecimal unqualifiedQty = BigDecimal.ZERO;
|
List<MesWmProductProduceLineDO> lines = produceLineService.getProductProduceLineListByFeedbackId(feedback.getId());
|
for (MesWmProductProduceLineDO line : lines) {
|
if (!ObjUtil.equal(line.getItemId(), feedback.getItemId())) {
|
continue;
|
}
|
if (ObjUtil.equal(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())) {
|
unqualifiedQty = unqualifiedQty.add(line.getQuantity());
|
}
|
if (ObjUtil.equal(line.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus())) {
|
qualifiedQty = qualifiedQty.add(line.getQuantity());
|
}
|
}
|
|
// 2. 更新任务的已生产/合格/不合格数量
|
taskService.updateProducedQuantity(feedback.getTaskId(),
|
feedback.getFeedbackQuantity(), qualifiedQty, unqualifiedQty);
|
// 3. 更新订单的已生产数量
|
// 新增逻辑:只有在该笔订单最后一个工序才更新生产订单的已生产数量
|
MesProWorkOrderProcessDO lastProcess = workOrderProcessService.getLastWorkOrderProcessByWorkOrderId(feedback.getWorkOrderId());
|
Long lastProcessId;
|
if (lastProcess != null) {
|
lastProcessId = lastProcess.getProcessId();
|
} else {
|
// 旧工单可能没有保存工单工序:与排产一致回退到工艺路线工序。
|
List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByRouteId(feedback.getRouteId());
|
if (CollUtil.isEmpty(routeProcesses) || routeProcesses.stream().anyMatch(process -> process.getSort() == null)) {
|
throw exception(PRO_FEEDBACK_PARAM_INVALID, "无法确定末道工序,请检查工单和工艺路线的工序顺序");
|
}
|
int maxSort = routeProcesses.stream().mapToInt(MesProRouteProcessDO::getSort).max().orElseThrow();
|
List<MesProRouteProcessDO> lastProcesses = routeProcesses.stream()
|
.filter(process -> process.getSort() == maxSort).toList();
|
if (lastProcesses.size() != 1 || lastProcesses.get(0).getProcessId() == null) {
|
throw exception(PRO_FEEDBACK_PARAM_INVALID, "无法唯一确定末道工序,请检查工艺路线的工序顺序");
|
}
|
lastProcessId = lastProcesses.get(0).getProcessId();
|
}
|
if (ObjUtil.equal(lastProcessId, feedback.getProcessId())) {
|
workOrderService.updateProducedQuantity(feedback.getWorkOrderId(),
|
feedback.getFeedbackQuantity());
|
// 4. 更新 MPS 的已生产数量(如果是销售订单来源)
|
MesProMpsRespDTO mps = mpsApi.getMpsByWorkOrderId(feedback.getWorkOrderId());
|
if (mps != null) {
|
mpsApi.updateMpsProducedQuantity(mps.getId(), feedback.getFeedbackQuantity());
|
}
|
}
|
|
|
// 5. 检查并发布生产完成事件
|
checkAndPublishProductionFinished(feedback.getWorkOrderId());
|
}
|
|
/**
|
* 检查并发布生产完成事件
|
*/
|
private void checkAndPublishProductionFinished(Long workOrderId) {
|
// 1. 获取工单关联的 MPS
|
MesProMpsRespDTO mps = mpsApi.getMpsByWorkOrderId(workOrderId);
|
if (mps == null || mps.getSaleOrderId() == null) {
|
return; // 非销售订单来源的工单,跳过
|
}
|
|
// 2. 检查并自动完成 MPS(如果满足条件)
|
mpsApi.checkAndFinishMps(mps.getId());
|
|
// 3. 检查销售订单所有生产是否完成
|
if (mpsApi.checkSaleOrderProductionFinished(mps.getSaleOrderId())) {
|
eventPublisher.publishEvent(new ProductionFinishedEvent(mps.getSaleOrderId(), java.time.LocalDateTime.now()));
|
}
|
}
|
|
// ==================== 校验方法 ====================
|
|
@Override
|
public MesProFeedbackDO validateFeedbackExists(Long id) {
|
MesProFeedbackDO feedback = feedbackMapper.selectById(id);
|
if (feedback == null) {
|
throw exception(PRO_FEEDBACK_NOT_EXISTS);
|
}
|
return feedback;
|
}
|
|
private MesProFeedbackDO validateFeedbackStatusPrepare(Long id) {
|
MesProFeedbackDO feedback = validateFeedbackExists(id);
|
if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus())) {
|
throw exception(PRO_FEEDBACK_NOT_PREPARE);
|
}
|
return feedback;
|
}
|
|
private MesProFeedbackDO lockFeedbackStatusApproving(Long id) {
|
MesProFeedbackDO feedback = feedbackMapper.selectOne(new LambdaQueryWrapperX<MesProFeedbackDO>()
|
.eq(MesProFeedbackDO::getId, id).last("FOR UPDATE"));
|
if (feedback == null) throw exception(PRO_FEEDBACK_NOT_EXISTS);
|
if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.APPROVING.getStatus()))
|
throw exception(PRO_FEEDBACK_NOT_APPROVING);
|
return feedback;
|
}
|
private MesProFeedbackDO validateFeedbackStatusApproving(Long id) {
|
MesProFeedbackDO feedback = validateFeedbackExists(id);
|
if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.APPROVING.getStatus())) {
|
throw exception(PRO_FEEDBACK_NOT_APPROVING);
|
}
|
return feedback;
|
}
|
|
/**
|
* 校验当前登录用户是否为报工单指定的审核人
|
*
|
* 报工单创建时必须指定 approveUserId,审批通过/驳回只能由该审核人操作,
|
* 避免任意拥有菜单权限的用户越权审批。
|
*
|
* @param feedback 报工单
|
*/
|
private void validateCurrentUserIsApprover(MesProFeedbackDO feedback) {
|
if (feedback.getApproveUserId() == null) {
|
throw exception(PRO_FEEDBACK_APPROVER_NOT_CONFIGURED);
|
}
|
if (!feedback.getApproveUserId().equals(getLoginUserId())) {
|
throw exception(PRO_FEEDBACK_NOT_APPROVER);
|
}
|
}
|
|
/**
|
* 校验当前登录人是否为任务的报工人;任务未配置报工人时无人可报工
|
*/
|
private void validateTaskAssignee(MesProTaskDO task) {
|
Long loginUserId = getLoginUserId();
|
if (loginUserId == null) {
|
throw exception(PRO_FEEDBACK_NOT_ASSIGNED);
|
}
|
String current = String.valueOf(loginUserId);
|
boolean assigned = StrUtil.split(task.getAssigneeUserIds(), ',')
|
.stream().map(String::trim).anyMatch(current::equals);
|
if (!assigned) {
|
throw exception(PRO_FEEDBACK_NOT_ASSIGNED);
|
}
|
}
|
|
/**
|
* 校验报工单的业务数据(创建 & 修改共用)
|
*
|
* <p>以生产任务为核心:前端只需传 taskId,workOrderId、routeId、processId、itemId、workstationId
|
* 从任务自动填充,无需前端重复传入。
|
*
|
* @param reqVO 报工请求
|
* @return 关联的生产任务
|
*/
|
private MesProTaskDO validateFeedbackData(MesProFeedbackSaveReqVO reqVO) {
|
// 1. 校验任务存在且未终态
|
MesProTaskDO task = taskService.validateTaskNotFinished(reqVO.getTaskId());
|
|
// 校验停工状态不能报工
|
if (!MesProTaskStatusEnum.canFeedback(task.getStatus())) {
|
throw exception(PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED);
|
}
|
// 校验当前登录人是该任务的报工人
|
validateTaskAssignee(task);
|
|
// 2. 从任务自动填充关联字段(前端无需传入,后端统一从任务获取)
|
reqVO.setWorkOrderId(task.getWorkOrderId());
|
reqVO.setRouteId(task.getRouteId());
|
reqVO.setProcessId(task.getProcessId());
|
reqVO.setItemId(task.getItemId());
|
reqVO.setWorkstationId(task.getWorkstationId());
|
|
// 3. 校验工单工序 / 工艺路线工序配置有效
|
boolean checkFlag = resolveProcessFlags(task.getRouteId(), task.getWorkOrderId(), task.getProcessId()).checkFlag();
|
// 4.1 校验数量
|
if (checkFlag) {
|
// 需要检验:只需填报工数量,且必须 > 0
|
if (reqVO.getFeedbackQuantity() == null
|
|| reqVO.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
throw exception(PRO_FEEDBACK_QUANTITY_MUST_POSITIVE);
|
}
|
} else {
|
// 不需检验:需填合格品 + 不良品数量,合计 > 0
|
BigDecimal qualified = ObjectUtil.defaultIfNull(reqVO.getQualifiedQuantity(), BigDecimal.ZERO);
|
BigDecimal unqualified = ObjectUtil.defaultIfNull(reqVO.getUnqualifiedQuantity(), BigDecimal.ZERO);
|
if (qualified.add(unqualified).compareTo(BigDecimal.ZERO) <= 0) {
|
throw exception(PRO_FEEDBACK_QUALIFIED_UNQUALIFIED_REQUIRED);
|
}
|
}
|
|
return task;
|
}
|
|
/**
|
* 解析工序的检验标识与倒冲标识
|
*
|
* <p>工单工序快照(mes_pro_work_order_process)是下单时确认的权威配置,优先使用;
|
* 工单未维护该工序时再回退到工艺路线模板(mes_pro_route_process)。
|
*/
|
private ProcessFlags resolveProcessFlags(Long routeId, Long workOrderId, Long processId) {
|
MesProWorkOrderProcessDO workOrderProcess = workOrderId != null && processId != null
|
? workOrderProcessService.getWorkOrderProcessByWorkOrderIdAndProcessId(workOrderId, processId) : null;
|
MesProRouteProcessDO routeProcess = routeId != null && processId != null
|
? routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId) : null;
|
if (workOrderProcess == null && routeProcess == null) {
|
throw exception(PRO_FEEDBACK_ROUTE_PROCESS_INVALID);
|
}
|
Boolean checkFlag = workOrderProcess != null && workOrderProcess.getCheckFlag() != null
|
? workOrderProcess.getCheckFlag() : routeProcess != null ? routeProcess.getCheckFlag() : null;
|
Boolean backflushFlag = workOrderProcess != null && workOrderProcess.getBackflushFlag() != null
|
? workOrderProcess.getBackflushFlag() : routeProcess != null ? routeProcess.getBackflushFlag() : null;
|
return new ProcessFlags(Boolean.TRUE.equals(checkFlag), Boolean.TRUE.equals(backflushFlag));
|
}
|
|
private record ProcessFlags(boolean checkFlag, boolean backflushFlag) {
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public MesProFeedbackQuickCreateRespVO quickCreateFeedback(MesProFeedbackQuickCreateReqVO reqVO) {
|
// 1. 校验任务存在且非终态
|
MesProTaskDO task = taskService.validateTaskNotFinished(reqVO.getTaskId());
|
|
// 校验停工状态不能报工
|
if (!MesProTaskStatusEnum.canFeedback(task.getStatus())) {
|
throw exception(PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED);
|
}
|
// 校验当前登录人是该任务的报工人
|
validateTaskAssignee(task);
|
|
// 2. 校验工单工序 / 工艺路线工序配置
|
boolean checkFlag = resolveProcessFlags(task.getRouteId(), task.getWorkOrderId(), task.getProcessId()).checkFlag();
|
// 3. 校验数量
|
if (checkFlag) {
|
if (reqVO.getFeedbackQuantity() == null
|
|| reqVO.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
throw exception(PRO_FEEDBACK_QUANTITY_MUST_POSITIVE);
|
}
|
} else {
|
BigDecimal qualified = ObjectUtil.defaultIfNull(reqVO.getQualifiedQuantity(), BigDecimal.ZERO);
|
BigDecimal unqualified = ObjectUtil.defaultIfNull(reqVO.getUnqualifiedQuantity(), BigDecimal.ZERO);
|
if (qualified.add(unqualified).compareTo(BigDecimal.ZERO) <= 0) {
|
throw exception(PRO_FEEDBACK_QUALIFIED_UNQUALIFIED_REQUIRED);
|
}
|
}
|
// 4. 构建报工DO
|
MesProFeedbackDO feedback = new MesProFeedbackDO()
|
.setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_FEEDBACK_CODE.getCode()))
|
.setType(MesProFeedbackTypeEnum.SELF.getType())
|
.setWorkstationId(task.getWorkstationId())
|
.setRouteId(task.getRouteId())
|
.setProcessId(task.getProcessId())
|
.setWorkOrderId(task.getWorkOrderId())
|
.setTaskId(reqVO.getTaskId())
|
.setItemId(task.getItemId())
|
.setFeedbackQuantity(reqVO.getFeedbackQuantity())
|
.setQualifiedQuantity(reqVO.getQualifiedQuantity())
|
.setUnqualifiedQuantity(reqVO.getUnqualifiedQuantity())
|
.setScheduledQuantity(task.getQuantity())
|
.setFeedbackTime(LocalDateTime.now())
|
.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus())
|
.setRemark(reqVO.getRemark());
|
MesProTaskDO lockedTask = feedbackParamService.lockTask(task.getId());
|
if (!MesProTaskStatusEnum.canFeedback(lockedTask.getStatus())) throw exception(PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED);
|
if (!Objects.equals(task.getProcessId(), lockedTask.getProcessId())
|
|| !Objects.equals(task.getWorkOrderId(), lockedTask.getWorkOrderId())
|
|| !Objects.equals(task.getRouteId(), lockedTask.getRouteId()))
|
throw exception(PRO_FEEDBACK_PARAM_INVALID, "任务信息已变更,请重新报工");
|
String draftParam = feedbackParamService.buildDraft(lockedTask, reqVO.getParamValues());
|
feedback.setParamSnapshot(feedbackParamService.complete(lockedTask, draftParam));
|
feedbackMapper.insert(feedback);
|
// 5. 返回结果
|
MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(task.getWorkOrderId());
|
return new MesProFeedbackQuickCreateRespVO()
|
.setFeedbackId(feedback.getId())
|
.setTaskStatus(task.getStatus())
|
.setWorkOrderStatus(workOrder != null ? workOrder.getStatus() : null);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public MesProFeedbackBatchCreateRespVO batchCreateFeedback(MesProFeedbackBatchCreateReqVO reqVO) {
|
List<MesProFeedbackBatchCreateRespVO.BatchResult> results = new java.util.ArrayList<>();
|
int successCount = 0;
|
int failCount = 0;
|
for (Long taskId : reqVO.getTaskIds()) {
|
try {
|
MesProTaskDO batchTask = feedbackParamService.lockTask(taskId);
|
if (Objects.equals(batchTask.getParamCollectMode(), 1)
|
|| (Objects.equals(batchTask.getParamCollectMode(), 2) && StrUtil.isBlank(batchTask.getParamSharedSnapshot()))) {
|
throw exception(PRO_FEEDBACK_PARAM_INVALID, "该任务需要单独填写参数,请使用单笔报工");
|
}
|
MesProFeedbackQuickCreateReqVO singleReq = new MesProFeedbackQuickCreateReqVO();
|
singleReq.setTaskId(taskId);
|
singleReq.setFeedbackQuantity(reqVO.getFeedbackQuantity());
|
singleReq.setQualifiedQuantity(reqVO.getQualifiedQuantity());
|
singleReq.setUnqualifiedQuantity(reqVO.getUnqualifiedQuantity());
|
singleReq.setRemark(reqVO.getRemark());
|
MesProFeedbackQuickCreateRespVO resp = quickCreateFeedback(singleReq);
|
results.add(new MesProFeedbackBatchCreateRespVO.BatchResult()
|
.setTaskId(taskId)
|
.setSuccess(true)
|
.setFeedbackId(resp.getFeedbackId()));
|
successCount++;
|
} catch (Exception e) {
|
results.add(new MesProFeedbackBatchCreateRespVO.BatchResult()
|
.setTaskId(taskId)
|
.setSuccess(false)
|
.setErrorMsg(e.getMessage()));
|
failCount++;
|
}
|
}
|
return new MesProFeedbackBatchCreateRespVO()
|
.setSuccessCount(successCount)
|
.setFailCount(failCount)
|
.setResults(results);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateProFeedbackWhenIpqcFinish(Long feedbackId, Long sourceLineId,
|
BigDecimal qualifiedQty, BigDecimal unqualifiedQty,
|
BigDecimal laborScrapQty, BigDecimal materialScrapQty, BigDecimal otherScrapQty) {
|
// 1. 校验报工单存在且为待检验状态
|
MesProFeedbackDO feedback = feedbackMapper.selectOne(new LambdaQueryWrapperX<MesProFeedbackDO>()
|
.eq(MesProFeedbackDO::getId, feedbackId).last("FOR UPDATE"));
|
if (feedback == null) throw exception(PRO_FEEDBACK_NOT_EXISTS);
|
if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.UNCHECK.getStatus())) {
|
throw exception(PRO_FEEDBACK_NOT_UNCHECK);
|
}
|
|
// 2. 拆分当前检验的产出行(合格/不合格),生成明细;全部物料检验完成才完成产出入库
|
productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId, qualifiedQty, unqualifiedQty);
|
|
// 3. 累加本次检验结果;报工单只记录主产出物料的合格/不合格/废品数量,
|
// 联产出物料各自检验,不并入报工数量;待全部产出物料检验完成才完成报工
|
List<MesWmProductProduceLineDO> produceLines = produceLineService.getProductProduceLineListByFeedbackId(feedbackId);
|
MesWmProductProduceLineDO sourceLine = produceLines.stream()
|
.filter(line -> ObjUtil.equal(line.getId(), sourceLineId)).findFirst().orElse(null);
|
boolean mainOutput = sourceLine == null || ObjUtil.equal(sourceLine.getItemId(), feedback.getItemId());
|
BigDecimal totalQualified = ObjectUtil.defaultIfNull(feedback.getQualifiedQuantity(), BigDecimal.ZERO)
|
.add(mainOutput ? qualifiedQty : BigDecimal.ZERO);
|
BigDecimal totalUnqualified = ObjectUtil.defaultIfNull(feedback.getUnqualifiedQuantity(), BigDecimal.ZERO)
|
.add(mainOutput ? unqualifiedQty : BigDecimal.ZERO);
|
BigDecimal totalLaborScrap = ObjectUtil.defaultIfNull(feedback.getLaborScrapQuantity(), BigDecimal.ZERO)
|
.add(mainOutput ? laborScrapQty : BigDecimal.ZERO);
|
BigDecimal totalMaterialScrap = ObjectUtil.defaultIfNull(feedback.getMaterialScrapQuantity(), BigDecimal.ZERO)
|
.add(mainOutput ? materialScrapQty : BigDecimal.ZERO);
|
BigDecimal totalOtherScrap = ObjectUtil.defaultIfNull(feedback.getOtherScrapQuantity(), BigDecimal.ZERO)
|
.add(mainOutput ? otherScrapQty : BigDecimal.ZERO);
|
boolean allInspected = produceLines.stream()
|
.noneMatch(line -> ObjUtil.equal(line.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus()));
|
MesProFeedbackDO updateObj = new MesProFeedbackDO().setId(feedbackId)
|
.setQualifiedQuantity(totalQualified)
|
.setUnqualifiedQuantity(totalUnqualified)
|
.setLaborScrapQuantity(totalLaborScrap)
|
.setMaterialScrapQuantity(totalMaterialScrap)
|
.setOtherScrapQuantity(totalOtherScrap)
|
.setStatus(allInspected ? MesProFeedbackStatusEnum.FINISHED.getStatus()
|
: MesProFeedbackStatusEnum.UNCHECK.getStatus());
|
if (allInspected) {
|
updateObj.setUncheckQuantity(BigDecimal.ZERO);
|
}
|
feedbackMapper.updateById(updateObj);
|
|
// 4. 全部检验完成后回写任务/工单进度
|
if (allInspected) {
|
updateTaskAndWorkOrderByFeedback(feedback);
|
}
|
}
|
|
@Override
|
public MesProFeedbackBatchCheckRespVO checkBatchRequirements(Long feedbackId) {
|
MesProFeedbackDO feedback = validateFeedbackExists(feedbackId);
|
MesProFeedbackBatchCheckRespVO respVO = new MesProFeedbackBatchCheckRespVO();
|
|
// 1. 检查物料是否启用批次管理
|
MesMdmItemDO item = mdmItemService.getItem(feedback.getItemId());
|
if (item == null || Boolean.FALSE.equals(item.getIsBatchManaged())) {
|
respVO.setNeedBatchInput(false);
|
respVO.setIsBatchManaged(false);
|
return respVO;
|
}
|
respVO.setIsBatchManaged(true);
|
|
// 2. 获取批次配置
|
MdmItemBatchConfigRespDTO config = itemBatchConfigApi.getItemBatchConfig(feedback.getItemId());
|
if (config == null) {
|
respVO.setNeedBatchInput(false);
|
return respVO;
|
}
|
|
// 3. 填充配置标志
|
respVO.setProduceDateFlag(config.getProduceDateFlag());
|
respVO.setExpireDateFlag(config.getExpireDateFlag());
|
respVO.setReceiptDateFlag(config.getReceiptDateFlag());
|
respVO.setVendorFlag(config.getVendorFlag());
|
respVO.setClientFlag(config.getClientFlag());
|
respVO.setSalesOrderCodeFlag(config.getSalesOrderCodeFlag());
|
respVO.setPurchaseOrderCodeFlag(config.getPurchaseOrderCodeFlag());
|
respVO.setWorkOrderFlag(config.getWorkOrderFlag());
|
respVO.setTaskFlag(config.getTaskFlag());
|
respVO.setWorkstationFlag(config.getWorkstationFlag());
|
respVO.setToolFlag(config.getToolFlag());
|
respVO.setMoldFlag(config.getMoldFlag());
|
respVO.setLotNumberFlag(config.getLotNumberFlag());
|
respVO.setQualityStatusFlag(config.getQualityStatusFlag());
|
|
// 4. 判断哪些字段已自动填充(报工和生产过程中必然有值的字段)
|
List<String> autoFilled = new ArrayList<>();
|
// 生产日期:审批时自动取当天
|
autoFilled.add("produceDate");
|
// 生产工单:从报工记录获取
|
autoFilled.add("workOrderId");
|
// 生产任务:从报工记录获取
|
autoFilled.add("taskId");
|
// 工作站:从报工记录获取
|
autoFilled.add("workstationId");
|
respVO.setAutoFilledFields(autoFilled);
|
|
// 5. 判断是否需要弹窗:存在必填但无法自动填充的字段
|
boolean needBatchInput = false;
|
if (Boolean.TRUE.equals(config.getExpireDateFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getReceiptDateFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getVendorFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getClientFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getSalesOrderCodeFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getPurchaseOrderCodeFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getToolFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getMoldFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getLotNumberFlag())) needBatchInput = true;
|
if (Boolean.TRUE.equals(config.getQualityStatusFlag())) needBatchInput = true;
|
respVO.setNeedBatchInput(needBatchInput);
|
|
return respVO;
|
}
|
|
@Override
|
public MesProFeedbackSummaryRespVO getFeedbackSummary(LocalDate beginDate, LocalDate endDate,
|
Integer workOrderType) {
|
// 1. 查询有效报工(已批准:待检验 + 已完成)
|
LambdaQueryWrapperX<MesProFeedbackDO> query = new LambdaQueryWrapperX<MesProFeedbackDO>()
|
.in(MesProFeedbackDO::getStatus, List.of(
|
MesProFeedbackStatusEnum.UNCHECK.getStatus(),
|
MesProFeedbackStatusEnum.FINISHED.getStatus()))
|
.betweenIfPresent(MesProFeedbackDO::getFeedbackTime,
|
beginDate != null ? beginDate.atStartOfDay() : null,
|
endDate != null ? endDate.plusDays(1).atStartOfDay() : null);
|
List<MesProFeedbackDO> feedbackList = feedbackMapper.selectList(query);
|
if (CollUtil.isEmpty(feedbackList)) {
|
MesProFeedbackSummaryRespVO empty = new MesProFeedbackSummaryRespVO();
|
empty.setTotalFeedbackQuantity(BigDecimal.ZERO);
|
empty.setTotalQualifiedQuantity(BigDecimal.ZERO);
|
empty.setTotalFeedbackCount(0);
|
empty.setByType(Collections.emptyList());
|
empty.setByDate(Collections.emptyList());
|
return empty;
|
}
|
|
// 2. 加载工单信息,映射 工单ID -> 工单类型
|
Set<Long> workOrderIds = feedbackList.stream()
|
.map(MesProFeedbackDO::getWorkOrderId).filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(workOrderIds);
|
|
// 3. 过滤工单类型
|
List<MesProFeedbackDO> filteredList = feedbackList.stream()
|
.filter(feedback -> {
|
if (workOrderType == null) {
|
return true;
|
}
|
MesProWorkOrderDO workOrder = workOrderMap.get(feedback.getWorkOrderId());
|
return workOrder != null && Objects.equals(workOrder.getType(), workOrderType);
|
})
|
.collect(Collectors.toList());
|
|
// 4. 汇总
|
BigDecimal totalFeedbackQuantity = BigDecimal.ZERO;
|
BigDecimal totalQualifiedQuantity = BigDecimal.ZERO;
|
int totalCount = 0;
|
for (MesProFeedbackDO feedback : filteredList) {
|
totalFeedbackQuantity = totalFeedbackQuantity.add(nullToZero(feedback.getFeedbackQuantity()));
|
totalQualifiedQuantity = totalQualifiedQuantity.add(nullToZero(feedback.getQualifiedQuantity()));
|
totalCount++;
|
}
|
|
// 5. 按工单类型分组
|
Map<Integer, MesProFeedbackSummaryRespVO.TypeSummaryItem> typeMap = new LinkedHashMap<>();
|
// 6. 按日期分组
|
Map<LocalDate, MesProFeedbackSummaryRespVO.DateSummaryItem> dateMap = new LinkedHashMap<>();
|
for (MesProFeedbackDO feedback : filteredList) {
|
MesProWorkOrderDO workOrder = workOrderMap.get(feedback.getWorkOrderId());
|
Integer type = workOrder != null ? workOrder.getType() : null;
|
if (type != null) {
|
MesProFeedbackSummaryRespVO.TypeSummaryItem item = typeMap.computeIfAbsent(type, key -> {
|
MesProFeedbackSummaryRespVO.TypeSummaryItem t = new MesProFeedbackSummaryRespVO.TypeSummaryItem();
|
t.setWorkOrderType(key);
|
t.setWorkOrderTypeName(MesProWorkOrderTypeEnum.valueOf(key) != null
|
? MesProWorkOrderTypeEnum.valueOf(key).getName() : String.valueOf(key));
|
t.setFeedbackQuantity(BigDecimal.ZERO);
|
t.setQualifiedQuantity(BigDecimal.ZERO);
|
t.setFeedbackCount(0);
|
return t;
|
});
|
item.setFeedbackQuantity(item.getFeedbackQuantity().add(nullToZero(feedback.getFeedbackQuantity())));
|
item.setQualifiedQuantity(item.getQualifiedQuantity().add(nullToZero(feedback.getQualifiedQuantity())));
|
item.setFeedbackCount(item.getFeedbackCount() + 1);
|
}
|
if (feedback.getFeedbackTime() != null) {
|
LocalDate date = feedback.getFeedbackTime().toLocalDate();
|
MesProFeedbackSummaryRespVO.DateSummaryItem item = dateMap.computeIfAbsent(date, key -> {
|
MesProFeedbackSummaryRespVO.DateSummaryItem d = new MesProFeedbackSummaryRespVO.DateSummaryItem();
|
d.setDate(key);
|
d.setFeedbackQuantity(BigDecimal.ZERO);
|
d.setQualifiedQuantity(BigDecimal.ZERO);
|
d.setFeedbackCount(0);
|
return d;
|
});
|
item.setFeedbackQuantity(item.getFeedbackQuantity().add(nullToZero(feedback.getFeedbackQuantity())));
|
item.setQualifiedQuantity(item.getQualifiedQuantity().add(nullToZero(feedback.getQualifiedQuantity())));
|
item.setFeedbackCount(item.getFeedbackCount() + 1);
|
}
|
}
|
|
// 7. 组装响应
|
MesProFeedbackSummaryRespVO respVO = new MesProFeedbackSummaryRespVO();
|
respVO.setTotalFeedbackQuantity(totalFeedbackQuantity);
|
respVO.setTotalQualifiedQuantity(totalQualifiedQuantity);
|
respVO.setTotalFeedbackCount(totalCount);
|
respVO.setByType(new ArrayList<>(typeMap.values()));
|
respVO.setByDate(new ArrayList<>(dateMap.values()));
|
return respVO;
|
}
|
|
private static BigDecimal nullToZero(BigDecimal value) {
|
return value == null ? BigDecimal.ZERO : value;
|
}
|
|
}
|