src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -9,6 +9,7 @@
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.procurementrecord.utils.StockUtils;
@@ -32,6 +33,9 @@
import com.ruoyi.sales.service.ISalesLedgerProductProcessBindService;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.pojo.StockInventory;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
@@ -88,6 +92,8 @@
    private ShippingInfoServiceImpl shippingInfoService;
    private StockUtils stockUtils;
    private StockInRecordMapper stockInRecordMapper;
    private final ISalesLedgerProductProcessBindService salesLedgerProductProcessBindService;
@@ -193,19 +199,27 @@
            if (item.getFutureTicketsAmount() == null || item.getFutureTicketsAmount().compareTo(BigDecimal.ZERO) == 0) {
                item.setFutureTicketsAmount(BigDecimal.ZERO);
            }
            if (item.getApproveStatus() == null || item.getApproveStatus() != 2) {
                Integer hasSufficientStock = item.getHasSufficientStock();
                if (hasSufficientStock != null && hasSufficientStock == 0) {
                    item.setApproveStatus(0);
                } else {
                    item.setApproveStatus(1);
                }
            }
            BigDecimal returnQuality = finalReturnOrderGroupDtoMap.getOrDefault(item.getId(), BigDecimal.ZERO);
            item.setReturnQuality(returnQuality);
            BigDecimal quantity = item.getQuantity() == null ? BigDecimal.ZERO : item.getQuantity();
            item.setAvailableQuality(quantity.subtract(returnQuality));
            BigDecimal shipped = item.getShippedQuantity() == null ? BigDecimal.ZERO : item.getShippedQuantity();
            BigDecimal available = quantity.subtract(returnQuality).subtract(shipped);
            item.setAvailableQuality(available.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : available);
            item.fillRemainingQuantity();
            if (item.getApproveStatus() == null || item.getApproveStatus() != 2) {
                BigDecimal remainingShipped = item.getRemainingShippedQuantity() == null ? BigDecimal.ZERO : item.getRemainingShippedQuantity();
                boolean hasOutbound = shipped.compareTo(BigDecimal.ZERO) > 0;
                if (hasOutbound && remainingShipped.compareTo(BigDecimal.ZERO) <= 0) {
                    item.setApproveStatus(0);
                } else {
                    Integer hasSufficientStock = item.getHasSufficientStock();
                    if (hasSufficientStock != null && hasSufficientStock == 0) {
                        item.setApproveStatus(0);
                    } else {
                        item.setApproveStatus(1);
                    }
                }
            }
            ProductModel productModel = finalProductModelMap.get(item.getProductModelId());
            if (productModel != null) {
                item.setThickness(productModel.getThickness());
@@ -286,9 +300,32 @@
            result = salesLedgerProductMapper.insert(salesLedgerProduct);
            addProductionData(salesLedgerProduct);
        } else {
            SalesLedgerProduct dbBefore = salesLedgerProductMapper.selectById(salesLedgerProduct.getId());
            if (dbBefore == null) {
                throw new RuntimeException("销售产品不存在,无法编辑");
            }
            // 不能把订单数量改到小于已发货数量,否则账实不一致
            BigDecimal shipped = dbBefore.getShippedQuantity() == null ? BigDecimal.ZERO : dbBefore.getShippedQuantity();
            BigDecimal newOrderQty = salesLedgerProduct.getQuantity() == null ? BigDecimal.ZERO : salesLedgerProduct.getQuantity();
            if (newOrderQty.compareTo(shipped) < 0) {
                throw new RuntimeException("编辑失败,订单数量不能小于已发货数量");
            }
            //查询原本的产品型号id
            salesLedgerProduct.setFutureTickets(salesLedgerProduct.getQuantity());
            result = salesLedgerProductMapper.updateById(salesLedgerProduct);
            // 允许“多入库”:当订单数量被下调时,只回退“下调差额”对应的入库数量
            // 例:原订单100,已入库120;改为50 -> 只回退50,最终已入库70(不强压到50)
            BigDecimal oldOrderQty = dbBefore.getQuantity() == null ? BigDecimal.ZERO : dbBefore.getQuantity();
            BigDecimal stocked = dbBefore.getStockedQuantity() == null ? BigDecimal.ZERO : dbBefore.getStockedQuantity();
            BigDecimal reduced = oldOrderQty.subtract(newOrderQty);
            if (reduced.compareTo(BigDecimal.ZERO) > 0) {
                BigDecimal rollbackQty = stocked.min(reduced);
                if (rollbackQty.compareTo(BigDecimal.ZERO) > 0) {
                    rollbackExcessStockIn(dbBefore, rollbackQty);
                }
            }
            /*删除对应的生产数据并重新新增*/
            deleteProductionData(Arrays.asList(salesLedgerProduct.getId()));
            // 删除生产核算数据
@@ -325,10 +362,108 @@
            //  清空销售产品绑定的加工
            salesLedgerProductProcessBindService.updateProductProcessBind(salesLedgerProduct.getSalesProductProcessList(), salesLedgerProduct.getId());
            // 新增/编辑产品后,刷新销售主单入库状态(避免因新增未入库行导致状态不准)
            refreshSalesLedgerStockStatus(salesLedgerId);
        }
        return result;
    }
    private void rollbackExcessStockIn(SalesLedgerProduct dbBefore, BigDecimal rollbackQtyTotal) {
        if (dbBefore == null || rollbackQtyTotal == null || rollbackQtyTotal.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        if (dbBefore.getProductModelId() == null) {
            throw new RuntimeException("回退入库失败,产品规格未维护");
        }
        Long productId = dbBefore.getId();
        //  倒序回退:优先回退“扫码入库”,再回退“手动入库”
        List<String> recordTypes = Arrays.asList(
                StockInQualifiedRecordTypeEnum.SALE_SCAN_STOCK_IN.getCode(),
                StockInQualifiedRecordTypeEnum.SALE_STOCK_IN.getCode()
        );
        List<StockInRecord> records = stockInRecordMapper.selectList(new LambdaQueryWrapper<StockInRecord>()
                .eq(StockInRecord::getSalesLedgerProductId, productId)
                .eq(StockInRecord::getType, "0")
                .in(StockInRecord::getRecordType, recordTypes)
                .orderByDesc(StockInRecord::getCreateTime));
        BigDecimal remaining = rollbackQtyTotal;
        for (StockInRecord r : records) {
            if (remaining.compareTo(BigDecimal.ZERO) <= 0) {
                break;
            }
            BigDecimal inNum = r.getStockInNum() == null ? BigDecimal.ZERO : r.getStockInNum();
            if (inNum.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            BigDecimal rollbackQty = inNum.min(remaining);
            StockInventoryDto stockInventoryDto = new StockInventoryDto();
            stockInventoryDto.setProductModelId(dbBefore.getProductModelId());
            stockInventoryDto.setQualitity(rollbackQty);
            int affectRows = stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
            if (affectRows <= 0) {
                throw new RuntimeException("回退入库失败,当前库存不足,无法扣回超入库数量");
            }
            BigDecimal newInNum = inNum.subtract(rollbackQty);
            if (newInNum.compareTo(BigDecimal.ZERO) <= 0) {
                stockInRecordMapper.deleteById(r.getId());
            } else {
                r.setStockInNum(newInNum);
                stockInRecordMapper.updateById(r);
            }
            remaining = remaining.subtract(rollbackQty);
        }
        if (remaining.compareTo(BigDecimal.ZERO) > 0) {
            throw new RuntimeException("回退入库失败,未找到足够的入库记录用于扣回超入库数量");
        }
        //  回写产品行已入库数量与状态
        SalesLedgerProduct fresh = salesLedgerProductMapper.selectById(dbBefore.getId());
        if (fresh == null) {
            return;
        }
        BigDecimal newStocked = (fresh.getStockedQuantity() == null ? BigDecimal.ZERO : fresh.getStockedQuantity()).subtract(rollbackQtyTotal);
        if (newStocked.compareTo(BigDecimal.ZERO) < 0) {
            newStocked = BigDecimal.ZERO;
        }
        BigDecimal orderQty = fresh.getQuantity() == null ? BigDecimal.ZERO : fresh.getQuantity();
        int stockStatus;
        if (newStocked.compareTo(BigDecimal.ZERO) <= 0) stockStatus = 0;
        else if (orderQty.compareTo(BigDecimal.ZERO) > 0 && newStocked.compareTo(orderQty) < 0) stockStatus = 1;
        else stockStatus = 2;
        fresh.setStockedQuantity(newStocked);
        fresh.setProductStockStatus(stockStatus);
        fresh.fillRemainingQuantity();
        salesLedgerProductMapper.updateById(fresh);
    }
    private void refreshSalesLedgerStockStatus(Long salesLedgerId) {
        if (salesLedgerId == null) {
            return;
        }
        SalesLedger ledger = salesLedgerMapper.selectById(salesLedgerId);
        if (ledger == null) {
            return;
        }
        List<SalesLedgerProduct> allProducts = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>()
                .eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId)
                .eq(SalesLedgerProduct::getType, 1));
        if (CollectionUtils.isEmpty(allProducts)) {
            ledger.setStockStatus(0);
            salesLedgerMapper.updateById(ledger);
            return;
        }
        boolean anyInbound = allProducts.stream().anyMatch(p -> {
            BigDecimal sq = p.getStockedQuantity();
            return sq != null && sq.compareTo(BigDecimal.ZERO) > 0;
        });
        boolean allFull = allProducts.stream().allMatch(p -> Objects.equals(p.getProductStockStatus(), 2));
        ledger.setStockStatus(allFull ? 2 : (anyInbound ? 1 : 0));
        salesLedgerMapper.updateById(ledger);
    }
    /**
     * 新增生产数据
     */