src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -27,6 +27,7 @@
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.FreightUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -42,6 +43,7 @@
import com.ruoyi.purchase.dto.PurchaseLedgerDto;
import com.ruoyi.purchase.dto.PurchaseLedgerImportDto;
import com.ruoyi.purchase.dto.PurchaseLedgerProductImportDto;
import com.ruoyi.purchase.dto.PurchaseStockInDto;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.service.IPurchaseLedgerService;
@@ -60,6 +62,7 @@
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.service.StockInRecordService;
import lombok.RequiredArgsConstructor;
@@ -609,7 +612,14 @@
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (!pendingIds.isEmpty()) {
            stockInRecordService.batchApprove(pendingIds, ReviewStatusEnum.APPROVED.getCode());
            List<StockInRecordDto.StockInRecordApproveItemDto> approveItems = pendingIds.stream()
                    .map(id -> {
                        StockInRecordDto.StockInRecordApproveItemDto item = new StockInRecordDto.StockInRecordApproveItemDto();
                        item.setId(id);
                        return item;
                    })
                    .collect(Collectors.toList());
            stockInRecordService.batchApprove(ReviewStatusEnum.APPROVED.getCode(), approveItems);
        }
    }
@@ -669,6 +679,7 @@
        // 执行更新操作
        if (!updateList.isEmpty()) {
            for (SalesLedgerProduct product : updateList) {
                FreightUtils.fillFreightAmount(product);
                product.setType(ledgerType);
                salesLedgerProductMapper.updateById(product);
            }
@@ -676,6 +687,7 @@
        // 执行插入操作
        if (!insertList.isEmpty()) {
            for (SalesLedgerProduct salesLedgerProduct : insertList) {
                FreightUtils.fillFreightAmount(salesLedgerProduct);
                salesLedgerProduct.setType(ledgerType);
                Date entryDate = purchaseLedger.getEntryDate();
@@ -965,6 +977,7 @@
                for (PurchaseLedgerProductImportDto salesLedgerProductImportDto : salesLedgerProductImportDtos) {
                    SalesLedgerProduct salesLedgerProduct = new SalesLedgerProduct();
                    BeanUtils.copyProperties(salesLedgerProductImportDto, salesLedgerProduct);
                    FreightUtils.fillFreightAmount(salesLedgerProduct);
                    salesLedgerProduct.setSalesLedgerId(salesLedger.getId());
                    salesLedgerProduct.setType(2);
                    // 计算不含税总价
@@ -1051,6 +1064,128 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int manualStockIn(PurchaseStockInDto purchaseStockInDto) {
        if (purchaseStockInDto == null || purchaseStockInDto.getPurchaseLedgerId() == null) {
            throw new BaseException("采购台账ID不能为空");
        }
        if (CollectionUtils.isEmpty(purchaseStockInDto.getDetails())) {
            throw new BaseException("请选择至少一个产品进行入库");
        }
        // 查询采购台账
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(purchaseStockInDto.getPurchaseLedgerId());
        if (purchaseLedger == null) {
            throw new BaseException("采购台账不存在");
        }
        // 验证采购台账状态是否为已审批
        if (!"3".equals(String.valueOf(purchaseLedger.getApprovalStatus()))) {
            throw new BaseException("只有已审批通过的采购台账才能入库");
        }
        int count = 0;
        for (PurchaseStockInDto.StockInProductItem item : purchaseStockInDto.getDetails()) {
            if (item.getId() == null) {
                continue;
            }
            // 查询采购产品
            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(item.getId());
            if (salesLedgerProduct == null) {
                throw new BaseException("采购产品不存在");
            }
            if (!purchaseLedger.getId().equals(salesLedgerProduct.getSalesLedgerId()) || !Integer.valueOf(2).equals(salesLedgerProduct.getType())) {
                throw new BaseException("采购产品与采购台账不匹配");
            }
            ProductModel productModel = productModelMapper.selectById(salesLedgerProduct.getProductModelId());
            if (Boolean.TRUE.equals(salesLedgerProduct.getIsChecked())) {
                throw new BaseException("产品【" + productModel.getModel() + "】需要质检,请走质检入库流程");
            }
            BigDecimal theoryStockInNum = item.getInboundQuantity();
            if (theoryStockInNum == null) {
                theoryStockInNum = salesLedgerProduct.getQuantity();
            }
            if (theoryStockInNum == null || theoryStockInNum.compareTo(BigDecimal.ZERO) <= 0) {
                throw new BaseException("理论入库数量必须大于0");
            }
            BigDecimal approvedStockInNum = getApprovedPurchaseStockInNum(
                    purchaseLedger.getId(),
                    purchaseLedger.getPurchaseContractNumber(),
                    salesLedgerProduct.getId(),
                    salesLedgerProduct.getProductModelId()
            );
            BigDecimal remainingStockInNum = salesLedgerProduct.getQuantity().subtract(approvedStockInNum);
            if (remainingStockInNum.compareTo(BigDecimal.ZERO) <= 0) {
                throw new BaseException("产品【" +  productModel.getModel() + "】已完成入库");
            }
            if (theoryStockInNum.compareTo(remainingStockInNum) > 0) {
                throw new BaseException("产品【" +  productModel.getModel() + "】理论入库数量不能大于待入库数量" + remainingStockInNum.stripTrailingZeros().toPlainString());
            }
            Boolean isContainsWater = Boolean.TRUE.equals(item.getIsContainsWater());
            BigDecimal waterContent = normalizeWaterContent(isContainsWater, item.getWaterContent());
            BigDecimal actualStockInNum = calculateActualStockInNum(theoryStockInNum, isContainsWater, waterContent);
            if (actualStockInNum.compareTo(BigDecimal.ZERO) <= 0) {
                throw new BaseException("实际入库数量必须大于0");
            }
            stockUtils.addStockWithBatchNo(
                    salesLedgerProduct.getProductModelId(),
                    actualStockInNum,
                    StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(),
                    purchaseLedger.getId(),
                    purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId(),
                    isContainsWater,
                    waterContent,
                    theoryStockInNum
            );
            count++;
        }
        return count;
    }
    private BigDecimal getApprovedPurchaseStockInNum(Long purchaseLedgerId, String purchaseContractNumber, Long productId, Long productModelId) {
        if (purchaseLedgerId == null || productId == null || productModelId == null || !StringUtils.hasText(purchaseContractNumber)) {
            return BigDecimal.ZERO;
        }
        List<StockInRecord> stockRecords = findDirectStockRecords(purchaseLedgerId, purchaseContractNumber, productModelId, productId);
        return stockRecords.stream()
                .filter(Objects::nonNull)
                .filter(item -> ReviewStatusEnum.APPROVED.getCode().equals(item.getApprovalStatus()))
                .map(StockInRecord::getStockInNum)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    private BigDecimal normalizeWaterContent(Boolean isContainsWater, BigDecimal waterContent) {
        if (!Boolean.TRUE.equals(isContainsWater)) {
            return BigDecimal.ZERO;
        }
        if (waterContent == null) {
            throw new BaseException("含水时必须填写含水量");
        }
        if (waterContent.compareTo(BigDecimal.ZERO) < 0 || waterContent.compareTo(new BigDecimal("100")) >= 0) {
            throw new BaseException("含水量必须在0到100之间");
        }
        return waterContent;
    }
    private BigDecimal calculateActualStockInNum(BigDecimal theoryStockInNum, Boolean isContainsWater, BigDecimal waterContent) {
        if (!Boolean.TRUE.equals(isContainsWater)) {
            return theoryStockInNum;
        }
        BigDecimal percent = waterContent.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP);
        BigDecimal actualStockInNum = theoryStockInNum.multiply(BigDecimal.ONE.subtract(percent));
        return actualStockInNum.setScale(4, RoundingMode.HALF_UP);
    }
    /**
     * 下划线命名转驼峰命名
     */