10 小时以前 55d694aaae4e421b5e55cd941500e08f85cb5d4d
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -1,6 +1,7 @@
package com.ruoyi.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
@@ -49,6 +50,7 @@
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.ISalesLedgerService;
import com.ruoyi.sales.vo.FrameworkOrderVo;
import com.ruoyi.sales.vo.SalesLedgerVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -64,7 +66,6 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
@@ -421,8 +422,7 @@
                    salesLedgerProduct.setApproveStatus(0);
                    salesLedgerProduct.setIsProduction(salesLedgerProductImportDto.getIsProduction() == 1);
                    salesLedgerProductMapper.insert(salesLedgerProduct);
                    // 添加生产数据
                    salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
                    addProductionDataIfNeeded(salesLedgerProduct);
                }
            }
@@ -508,6 +508,11 @@
        if (CollectionUtils.isEmpty(idList)) {
            return 0;
        }
        long childCount = salesLedgerMapper.selectCount(new LambdaQueryWrapper<SalesLedger>()
                .in(SalesLedger::getParentContractId, idList));
        if (childCount > 0) {
            throw new BaseException("框架合同存在框架订单,不能删除");
        }
        // 删除销售管理数据
        LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(SalesLedgerProduct::getSalesLedgerId, idList)
@@ -551,6 +556,10 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
        log.info("addOrUpdateSalesLedger 收到 contractType={}, parentContractId={}, id={}",
                salesLedgerDto == null ? null : salesLedgerDto.getContractType(),
                salesLedgerDto == null ? null : salesLedgerDto.getParentContractId(),
                salesLedgerDto == null ? null : salesLedgerDto.getId());
        // 1. 校验客户信息
        Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId());
        if (customer == null) {
@@ -559,6 +568,7 @@
        // 2. DTO转Entity
        SalesLedger salesLedger = convertToEntity(salesLedgerDto);
        log.info("convertToEntity 后 contractType={}", salesLedger.getContractType());
        salesLedger.setCustomerName(customer.getCustomerName());
        salesLedger.setTenantId(customer.getTenantId());
        // 录入人:如果未传则使用当前登录用户
@@ -576,6 +586,9 @@
        } else {
            salesLedgerMapper.updateById(salesLedger);
        }
        // 诊断:insert/update 后回查数据库实际写入的 contract_type
        SalesLedger dbCheckLedger = salesLedgerMapper.selectById(salesLedger.getId());
        log.info("DB 实际 contract_type={}, id={}", dbCheckLedger == null ? null : dbCheckLedger.getContractType(), salesLedger.getId());
        // 4. 处理子表数据
@@ -609,6 +622,86 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int createFrameworkOrder(SalesLedgerDto dto) {
        if (dto == null || dto.getId() == null) {
            throw new BaseException("请选择框架合同");
        }
        SalesLedger parent = salesLedgerMapper.selectById(dto.getId());
        if (parent == null || !Objects.equals(parent.getContractType(), 2)) {
            throw new BaseException("父台账不是框架合同");
        }
        if (CollectionUtils.isEmpty(dto.getProductData())) {
            throw new BaseException("请填写框架订单产品");
        }
        SalesLedger order = new SalesLedger();
        order.setCustomerContractNo(parent.getCustomerContractNo());
        order.setProjectName(parent.getProjectName());
        order.setSalesman(parent.getSalesman());
        order.setCustomerId(parent.getCustomerId());
        order.setCustomerName(parent.getCustomerName());
        order.setEntryPerson(String.valueOf(SecurityUtils.getUserId()));
        order.setRemarks(dto.getRemarks());
        order.setTenantId(parent.getTenantId());
        order.setExecutionDate(parent.getExecutionDate());
        order.setPaymentMethod(parent.getPaymentMethod());
        order.setEntryDate(parent.getEntryDate());
        order.setId(null);
        order.setContractType(3);
        order.setParentContractId(parent.getId());
        order.setSalesContractNo(generateSalesContractNo(new Date()));
        order.setDeliveryDate(null);
        order.setContractAmount(BigDecimal.ZERO);
        order.setNetContractAmount(BigDecimal.ZERO);
        salesLedgerMapper.insert(order);
        Set<Long> parentProductIds = dto.getProductData().stream()
                .map(SalesLedgerProduct::getParentProductId)
                .collect(Collectors.toSet());
        if (parentProductIds.contains(null) || parentProductIds.size() != dto.getProductData().size()) {
            throw new BaseException("框架订单产品必须关联不同的父产品");
        }
        Map<Long, SalesLedgerProduct> parentProducts = salesLedgerProductMapper.selectBatchIds(parentProductIds).stream()
                .collect(Collectors.toMap(SalesLedgerProduct::getId, Function.identity()));
        for (SalesLedgerProduct requestProduct : dto.getProductData()) {
            if (requestProduct.getDeliveryDate() == null) {
                throw new BaseException("框架订单产品必须关联父产品并填写交货日期");
            }
            SalesLedgerProduct parentProduct = parentProducts.get(requestProduct.getParentProductId());
            if (parentProduct == null || !Objects.equals(parentProduct.getSalesLedgerId(), parent.getId())
                    || !Objects.equals(parentProduct.getType(), SaleEnum.SALE.getCode())) {
                throw new BaseException("框架订单产品不属于该框架合同");
            }
            SalesLedgerProduct product = new SalesLedgerProduct();
            BeanUtils.copyProperties(parentProduct, product);
            product.setId(null);
            product.setSalesLedgerId(order.getId());
            product.setParentProductId(parentProduct.getId());
            product.setQuantity(null);
            product.setDeliveryDate(requestProduct.getDeliveryDate());
            product.setIsProduction(false);
            product.setType(SaleEnum.SALE.getCode());
            if (product.getTaxRate() == null) {
                product.setTaxRate(BigDecimal.ZERO);
            }
            salesLedgerProductMapper.insert(product);
            // 框架订单创建时生成生产计划(数量留空,下发时填写)
            salesLedgerProductServiceImpl.addProductionData(product);
        }
        return 1;
    }
    @Override
    public List<FrameworkOrderVo> listFrameworkOrders(Long parentContractId) {
        return salesLedgerMapper.listFrameworkOrders(parentContractId);
    }
    @Override
    public BigDecimal getFrameworkContractShippedQuantity(Long parentContractId) {
        return salesLedgerMapper.sumShippedByParentContract(parentContractId);
    }
    @Override
    public void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type) {
        // 按ID分组,区分新增和更新的记录
        Map<Boolean, List<SalesLedgerProduct>> partitionedProducts = products.stream()
@@ -629,10 +722,20 @@
        if (!insertList.isEmpty()) {
            for (SalesLedgerProduct salesLedgerProduct : insertList) {
                salesLedgerProduct.setType(type.getCode());
                salesLedgerProductMapper.insert(salesLedgerProduct);
                // 添加生产数据
                salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
                if (salesLedgerProduct.getTaxRate() == null) {
                    salesLedgerProduct.setTaxRate(BigDecimal.ZERO);
            }
                salesLedgerProductMapper.insert(salesLedgerProduct);
                addProductionDataIfNeeded(salesLedgerProduct);
            }
        }
    }
    private void addProductionDataIfNeeded(SalesLedgerProduct product) {
        SalesLedger ledger = salesLedgerMapper.selectById(product.getSalesLedgerId());
        if (ledger != null && !Objects.equals(ledger.getContractType(), 2)
                && !Objects.equals(ledger.getContractType(), 3)) {
            salesLedgerProductServiceImpl.addProductionData(product);
        }
    }
@@ -729,30 +832,22 @@
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 构造主表更新对象
        try {
            S entity = mainEntityClass.getDeclaredConstructor().newInstance();
            Field idField = mainEntityClass.getDeclaredField("id");
            idField.setAccessible(true);
            idField.set(entity, mainId);
            Field amountField = mainEntityClass.getDeclaredField("contractAmount");
            amountField.setAccessible(true);
            amountField.set(entity, totalAmount);
        // 仅更新金额列,避免整实体 updateById 把其它非空字段(如 contract_type 默认值 1)一并覆盖
        UpdateWrapper<S> wrapper = Wrappers.update();
        wrapper.eq("id", mainId)
                .set("contract_amount", totalAmount);
            // 实际合同金额初始值等于合同金额
        boolean hasNetAmount;
            try {
                Field netAmountField = mainEntityClass.getDeclaredField("netContractAmount");
                netAmountField.setAccessible(true);
                netAmountField.set(entity, totalAmount);
            } catch (NoSuchFieldException ignored) {
                // 子类可能没有该字段,忽略
            mainEntityClass.getDeclaredField("netContractAmount");
            hasNetAmount = true;
        } catch (NoSuchFieldException e) {
            hasNetAmount = false;
            }
            mainMapper.updateById(entity);
        } catch (Exception e) {
            throw new RuntimeException("动态更新主表金额失败", e);
        if (hasNetAmount) {
            wrapper.set("net_contract_amount", totalAmount);
        }
        mainMapper.update(null, wrapper);
    }
    // 内部类用于存储聚合结果