package cn.iocoder.yudao.module.mes.service.qc.ncr;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.detail.MesWmProductSalesDetailSaveReqVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
|
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
|
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService;
|
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesDetailService;
|
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
|
import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService;
|
import jakarta.annotation.Resource;
|
import org.springframework.stereotype.Component;
|
import java.util.Objects;
|
import java.math.BigDecimal;
|
import java.util.List;
|
|
/** 将让步决定回写原出库行;必须由 NCR 处置事务调用。 */
|
@Component
|
public class MesQcNcrOqcConcessionService {
|
@Resource private MesQcOqcMapper oqcMapper;
|
@Resource private MesWmProductSalesMapper salesMapper;
|
@Resource private MesWmProductSalesLineMapper lineMapper;
|
@Resource private MesWmProductSalesLineService lineService;
|
@Resource private MesWmTransactionMapper transactionMapper;
|
@Resource private MesWmMaterialStockService stockService;
|
@Resource private MesWmProductSalesDetailService detailService;
|
@Resource private MesWmProductSalesService salesService;
|
|
public void release(MesQcNcrDO ncr, MesQcNcrDisposeReqVO req) {
|
MesQcOqcDO qc = oqcMapper.selectById(ncr.getQcId());
|
if (qc == null || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES)
|
|| qc.getSourceDocId() == null || qc.getSourceLineId() == null || StrUtil.isBlank(qc.getBatchCode())
|
|| !Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus())
|
|| !Objects.equals(qc.getCheckResult(), 2) || !Objects.equals(qc.getItemId(), ncr.getItemId())
|
|| qc.getUnqualifiedQuantity() == null || ncr.getUnqualifiedQuantity() == null
|
|| qc.getUnqualifiedQuantity().signum() <= 0
|
|| qc.getUnqualifiedQuantity().compareTo(ncr.getUnqualifiedQuantity()) != 0) {
|
throw new ServiceException(400, "出货让步需关联已完成的不合格出货检验、原出库行及明确批次,物料和不合格数量须一致");
|
}
|
MesWmProductSalesDO sales = salesMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesDO>()
|
.eq(MesWmProductSalesDO::getId, qc.getSourceDocId()).last("FOR UPDATE"));
|
MesWmProductSalesLineDO line = lineMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesLineDO>()
|
.eq(MesWmProductSalesLineDO::getId, qc.getSourceLineId()).last("FOR UPDATE"));
|
if (sales == null || line == null || !Objects.equals(sales.getStatus(), MesWmProductSalesStatusEnum.CONFIRMED.getStatus())
|
|| !Objects.equals(line.getSalesId(), sales.getId()) || !Objects.equals(line.getOqcId(), qc.getId())
|
|| !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())
|
|| !Objects.equals(line.getItemId(), qc.getItemId()) || line.getQuantity() == null
|
|| qc.getCheckQuantity() == null || line.getQuantity().signum() <= 0
|
|| qc.getQualifiedQuantity() == null || qc.getQualifiedQuantity().signum() < 0
|
|| qc.getQualifiedQuantity().add(qc.getUnqualifiedQuantity()).compareTo(qc.getCheckQuantity()) != 0
|
|| (StrUtil.isNotBlank(line.getBatchCode()) && !Objects.equals(line.getBatchCode(), qc.getBatchCode()))
|
|| qc.getCheckQuantity().compareTo(line.getQuantity()) != 0) {
|
throw new ServiceException(400, "出货让步要求原出库单处于待检验状态,原行仍绑定本次不合格检验且检测数量覆盖整行");
|
}
|
MesWmMaterialStockDO stock = req.getMaterialStockId() == null ? null : stockService.getMaterialStock(req.getMaterialStockId());
|
if (stock == null || !Objects.equals(stock.getItemId(), qc.getItemId())
|
|| !Objects.equals(stock.getBatchCode(), qc.getBatchCode())
|
|| (line.getMaterialStockId() != null && !Objects.equals(line.getMaterialStockId(), stock.getId()))
|
|| stock.getWarehouseId() == null || stock.getLocationId() == null || stock.getAreaId() == null) {
|
throw new ServiceException(400, "请选择原检验批次的实际库存及完整库位;原行指定库存时必须选择该库存");
|
}
|
var currentDetails = detailService.getProductSalesDetailListByLineId(line.getId());
|
if (!currentDetails.isEmpty()) {
|
throw new ServiceException(400, "该出库行已有拣货明细,请先核对并清理后重新选择实际库存");
|
}
|
// 其他行也必须完整且可出库;本操作以整张销售出库单为事务边界。
|
for (MesWmProductSalesLineDO other : lineService.getProductSalesLineListBySalesId(sales.getId())) {
|
if (Objects.equals(other.getId(), line.getId())) continue;
|
if (Boolean.TRUE.equals(other.getOqcCheckFlag())
|
&& !Objects.equals(other.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus())) {
|
throw new ServiceException(400, "同一销售出库单还有未通过质检的行,不能整单出库");
|
}
|
var details = detailService.getProductSalesDetailListByLineId(other.getId());
|
BigDecimal total = details.stream().map(MesWmProductSalesDetailDO::getQuantity)
|
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
if (details.isEmpty() || other.getQuantity() == null || other.getQuantity().signum() <= 0
|
|| total.compareTo(other.getQuantity()) != 0
|
|| details.stream().anyMatch(d -> d.getQuantity() == null || d.getQuantity().signum() <= 0
|
|| !Objects.equals(d.getSalesId(), sales.getId()) || !Objects.equals(d.getLineId(), other.getId())
|
|| !Objects.equals(d.getItemId(), other.getItemId()))) {
|
throw new ServiceException(400, "同一销售出库单其他行的拣货明细未备齐,不能整单出库");
|
}
|
}
|
lineService.updateProductSalesLineWhenOqcFinish(line.getId(), qc.getId(), 1);
|
MesWmProductSalesDetailSaveReqVO detail = new MesWmProductSalesDetailSaveReqVO();
|
detail.setSalesId(sales.getId()); detail.setLineId(line.getId()); detail.setItemId(qc.getItemId());
|
detail.setMaterialStockId(stock.getId()); detail.setQuantity(line.getQuantity());
|
detail.setBatchId(stock.getBatchId()); detail.setBatchCode(stock.getBatchCode());
|
detail.setWarehouseId(stock.getWarehouseId()); detail.setLocationId(stock.getLocationId()); detail.setAreaId(stock.getAreaId());
|
detailService.createProductSalesDetail(detail);
|
if (!salesService.checkProductSalesQuantity(sales.getId())) {
|
throw new ServiceException(400, "销售出库单明细数量不完整,不能整单出库");
|
}
|
salesService.stockProductSales(sales.getId());
|
MesWmProductSalesShippingReqVO shipping = new MesWmProductSalesShippingReqVO();
|
shipping.setId(sales.getId());
|
salesService.shippingProductSales(shipping);
|
salesService.finishProductSales(sales.getId());
|
}
|
|
/** 实际出库后才能关闭让步单;不改变原始不合格检验结论。 */
|
public void validateBeforeClose(MesQcNcrDO ncr) {
|
MesQcOqcDO qc = oqcMapper.selectById(ncr.getQcId());
|
if (qc == null || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES)
|
|| qc.getSourceDocId() == null || qc.getSourceLineId() == null || StrUtil.isBlank(qc.getBatchCode())
|
|| !Objects.equals(qc.getItemId(), ncr.getItemId())
|
|| qc.getUnqualifiedQuantity() == null || ncr.getUnqualifiedQuantity() == null
|
|| qc.getUnqualifiedQuantity().signum() <= 0
|
|| qc.getUnqualifiedQuantity().compareTo(ncr.getUnqualifiedQuantity()) != 0) {
|
throw new ServiceException(400, "无法核验让步出货的来源检验单及原批次");
|
}
|
MesWmProductSalesDO sales = salesMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesDO>()
|
.eq(MesWmProductSalesDO::getId, qc.getSourceDocId()).last("FOR UPDATE"));
|
MesWmProductSalesLineDO line = lineMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesLineDO>()
|
.eq(MesWmProductSalesLineDO::getId, qc.getSourceLineId()).last("FOR UPDATE"));
|
if (sales == null || line == null || !Objects.equals(sales.getStatus(), MesWmProductSalesStatusEnum.FINISHED.getStatus())
|
|| !Objects.equals(line.getSalesId(), sales.getId()) || !Objects.equals(line.getOqcId(), qc.getId())
|
|| !Objects.equals(line.getItemId(), qc.getItemId()) || line.getQuantity() == null
|
|| qc.getCheckQuantity() == null || line.getQuantity().compareTo(qc.getCheckQuantity()) != 0) {
|
throw new ServiceException(400, "原销售出库单尚未完成,或出库行与检验来源不一致,不能关闭");
|
}
|
List<MesWmTransactionDO> flows = transactionMapper.selectList(new LambdaQueryWrapperX<MesWmTransactionDO>()
|
.eq(MesWmTransactionDO::getBizType, MesBizTypeConstants.WM_PRODUCT_SALES)
|
.eq(MesWmTransactionDO::getBizId, sales.getId()).eq(MesWmTransactionDO::getBizLineId, line.getId())
|
.eq(MesWmTransactionDO::getType, MesWmTransactionTypeEnum.OUT.getType()));
|
BigDecimal out = BigDecimal.ZERO;
|
for (MesWmTransactionDO flow : flows) {
|
if (!Objects.equals(flow.getItemId(), qc.getItemId()) || !Objects.equals(flow.getBatchCode(), qc.getBatchCode())
|
|| flow.getQuantity() == null || flow.getQuantity().signum() >= 0) {
|
throw new ServiceException(400, "出库流水的物料、批次或数量与让步检验不一致");
|
}
|
out = out.subtract(flow.getQuantity());
|
}
|
if (out.compareTo(line.getQuantity()) != 0) {
|
throw new ServiceException(400, "原销售出库流水数量不完整,不能关闭让步单");
|
}
|
}
|
}
|