已添加1个文件
已修改10个文件
559 ■■■■■ 文件已修改
doc/20260829_alter_sales_purchase_amount_scale_6.sql 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPurchasePaymentServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/sales/AccountSalesCollectionServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceRepair.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260829_alter_sales_purchase_amount_scale_6.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
-- é”€å”®/采购相关页面金额、单价、数量、总价统一保留 6 ä½å°æ•°
-- æ¶‰åŠé¡µé¢ï¼šé”€å”®æŠ¥ä»·ã€é”€å”®å°è´¦ã€å®¢æˆ·é€€è´§ã€å®¢æˆ·å¾€æ¥ã€é‡‡è´­å°è´¦ã€ä¾›åº”商往来、采购退货、采购报表
-- ç»Ÿä¸€ä½¿ç”¨ decimal(18, 6):整数位保留 12 ä½ï¼Œé¿å…ç”± decimal(10, 2) ç›´æŽ¥æ”¹å°æ•°ä½å¯¼è‡´çš„æº¢å‡º
-- ----------------------------
-- é”€å”®æŠ¥ä»·ï¼ˆsales_quotation / sales_quotation_product)
-- ----------------------------
ALTER TABLE `sales_quotation`
    MODIFY COLUMN `total_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '报价金额';
ALTER TABLE `sales_quotation_product`
    MODIFY COLUMN `quantity` decimal(18, 6) NULL DEFAULT NULL COMMENT '数量',
    MODIFY COLUMN `unit_price` decimal(18, 6) NULL DEFAULT NULL COMMENT '单价',
    MODIFY COLUMN `amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '金额';
-- ----------------------------
-- é”€å”®å°è´¦ï¼ˆsales_ledger / sales_ledger_product)
-- ----------------------------
ALTER TABLE `sales_ledger`
    MODIFY COLUMN `contract_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '合同金额(产品含税总价)',
    MODIFY COLUMN `net_contract_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '实际合同金额(扣减退货后)';
ALTER TABLE `sales_ledger_product`
    MODIFY COLUMN `quantity` decimal(18, 6) NOT NULL COMMENT '数量',
    MODIFY COLUMN `min_stock` decimal(18, 6) NULL DEFAULT NULL COMMENT '最低库存',
    MODIFY COLUMN `tax_inclusive_unit_price` decimal(18, 6) NOT NULL COMMENT '含税单价',
    MODIFY COLUMN `tax_inclusive_total_price` decimal(18, 6) NOT NULL COMMENT '含税总价',
    MODIFY COLUMN `tax_exclusive_total_price` decimal(18, 6) NOT NULL COMMENT '不含税总价',
    MODIFY COLUMN `warn_num` decimal(18, 6) NULL DEFAULT NULL COMMENT '预警数量';
-- å°è´¦æ¨¡æ¿ï¼ˆä¿å­˜/套用模板时与产品表结构保持一致)
ALTER TABLE `sales_ledger_product_template`
    MODIFY COLUMN `quantity` decimal(18, 6) NULL DEFAULT NULL COMMENT '数量',
    MODIFY COLUMN `min_stock` decimal(18, 6) NULL DEFAULT NULL COMMENT '最低库存',
    MODIFY COLUMN `tax_inclusive_unit_price` decimal(18, 6) NULL DEFAULT NULL COMMENT '含税单价',
    MODIFY COLUMN `tax_inclusive_total_price` decimal(18, 6) NULL DEFAULT NULL COMMENT '含税总价',
    MODIFY COLUMN `tax_exclusive_total_price` decimal(18, 6) NULL DEFAULT NULL COMMENT '不含税总价',
    MODIFY COLUMN `warn_num` decimal(18, 6) NULL DEFAULT NULL COMMENT '预警数量';
-- ----------------------------
-- å®¢æˆ·é€€è´§ï¼ˆreturn_management / return_sale_product / sales_refund_amount_order)
-- ----------------------------
ALTER TABLE `return_management`
    MODIFY COLUMN `refund_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '退款总额';
ALTER TABLE `return_sale_product`
    MODIFY COLUMN `num` decimal(18, 6) NOT NULL COMMENT '退货产品数量',
    MODIFY COLUMN `price` decimal(18, 6) NULL DEFAULT NULL COMMENT '单价',
    MODIFY COLUMN `amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '金额';
ALTER TABLE `sales_refund_amount_order`
    MODIFY COLUMN `refund_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '应退款金额',
    MODIFY COLUMN `refunded_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '已退款金额',
    MODIFY COLUMN `not_refunded_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '未退款金额';
-- ----------------------------
-- å®¢æˆ·å¾€æ¥ï¼ˆå›žæ¬¾é‡‘额来源:account_sales_collection、receipt_payment)
-- ----------------------------
ALTER TABLE `account_sales_collection`
    MODIFY COLUMN `collection_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '收款金额';
ALTER TABLE `receipt_payment`
    MODIFY COLUMN `receipt_payment_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '回款金额';
-- ----------------------------
-- é‡‡è´­å°è´¦ï¼ˆpurchase_ledger / purchase_ledger_template)
-- ----------------------------
ALTER TABLE `purchase_ledger`
    MODIFY COLUMN `contract_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '合同金额',
    MODIFY COLUMN `net_contract_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '实际合同金额(扣减退货后)';
ALTER TABLE `purchase_ledger_template`
    MODIFY COLUMN `contract_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '合同金额';
-- ----------------------------
-- ä¾›åº”商往来(付款金额来源:account_purchase_payment)
-- ----------------------------
ALTER TABLE `account_purchase_payment`
    MODIFY COLUMN `payment_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '付款金额';
-- ----------------------------
-- é‡‡è´­é€€è´§ï¼ˆpurchase_return_orders / purchase_return_order_products)
-- ----------------------------
ALTER TABLE `purchase_return_orders`
    MODIFY COLUMN `total_discount_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '整单折扣额',
    MODIFY COLUMN `total_discount_rate` decimal(18, 6) NULL DEFAULT NULL COMMENT '整单折扣率',
    MODIFY COLUMN `total_amount` decimal(18, 6) NULL DEFAULT NULL COMMENT '成交金额';
ALTER TABLE `purchase_return_order_products`
    MODIFY COLUMN `return_quantity` decimal(18, 6) NOT NULL DEFAULT 0.000000 COMMENT '退货数量';
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPurchasePaymentServiceImpl.java
@@ -97,7 +97,7 @@
        if (dto.getPaymentDate() == null) {
            throw new ServiceException("请选择付款日期");
        }
        if (dto.getPaymentAmount() == null || dto.getPaymentAmount().compareTo(BigDecimal.valueOf(0.01)) < 0) {
        if (dto.getPaymentAmount() == null || dto.getPaymentAmount().compareTo(new BigDecimal("0.000001")) < 0) {
            throw new ServiceException("请输入付款金额");
        }
        if (StringUtils.isEmpty(dto.getPurchaseContractNumber())) {
src/main/java/com/ruoyi/account/service/impl/sales/AccountSalesCollectionServiceImpl.java
@@ -102,7 +102,7 @@
        if (dto.getPaymentDate() == null) {
            throw new ServiceException("请选择回款日期");
        }
        if (dto.getPaymentAmount() == null || dto.getPaymentAmount().compareTo(BigDecimal.valueOf(0.01)) < 0) {
        if (dto.getPaymentAmount() == null || dto.getPaymentAmount().compareTo(new BigDecimal("0.000001")) < 0) {
            throw new ServiceException("请输入回款金额");
        }
        if (StringUtils.isEmpty(dto.getSalesContractNo())) {
src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
@@ -27,6 +27,8 @@
    private String deviceModel;
    @Schema(description = "报修时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date repairTime;
    @Schema(description = "报修人")
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
@@ -116,4 +116,7 @@
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @Schema(description = "状态 0=待巡检, 1=已完成")
    private Integer status;
}
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -911,20 +911,37 @@
            List<SysUser> sysUsers = sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>().in(SysUser::getNickName,
                    salesLedgerImportDtoList.stream().map(PurchaseLedgerImportDto::getRecorderName).collect(Collectors.toList())));
            for (PurchaseLedgerImportDto salesLedgerImportDto : salesLedgerImportDtoList) {
                PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                PurchaseLedger existPurchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                        .eq(PurchaseLedger::getPurchaseContractNumber, salesLedgerImportDto.getPurchaseContractNumber())
                        .last("limit 1"));
                if(purchaseLedger != null){
                // å·²å®¡æ ¸é€šè¿‡çš„采购单跳过,不再重复导入
                if (existPurchaseLedger != null && ApprovalStatusEnum.APPROVED.getCode().equals(existPurchaseLedger.getApprovalStatus())) {
                    continue;
                }
                PurchaseLedger salesLedger = new PurchaseLedger();
                BeanUtils.copyProperties(salesLedgerImportDto, salesLedger);
                // é€šè¿‡ä¾›åº”商名称查询ID
                salesLedger.setSupplierId(customers.stream()
                        .filter(customer -> customer.getSupplierName().equals(salesLedger.getSupplierName()))
                        .findFirst()
                        .map(SupplierManage::getId)
                        .orElse(null));
                // å¾…审核状态的采购单:删除该单下的产品详情和审批实例,重新录入详情
                if (existPurchaseLedger != null) {
                    salesLedgerProductMapper.delete(new LambdaQueryWrapper<SalesLedgerProduct>()
                            .eq(SalesLedgerProduct::getSalesLedgerId, existPurchaseLedger.getId())
                            .eq(SalesLedgerProduct::getType, 2));
                    // æ¸…理该单下的质检记录及质检参数
                    deleteQualityInspectByPurchaseLedger(existPurchaseLedger.getId());
                    ApprovalInstance existApprovalInstance = approvalInstanceService.getOne(
                            Wrappers.<ApprovalInstance>lambdaQuery()
                                    .eq(ApprovalInstance::getBusinessId, existPurchaseLedger.getId())
                                    .eq(ApprovalInstance::getBusinessType, 5L)
                                    .eq(ApprovalInstance::getDeleted, 0)
                                    .orderByDesc(ApprovalInstance::getId)
                                    .last("limit 1"));
                    if (existApprovalInstance != null) {
                        approvalInstanceService.delete(Collections.singletonList(existApprovalInstance.getId()));
                    }
                    salesLedger.setId(existPurchaseLedger.getId());
                    salesLedger.setApprovalStatus(ApprovalStatusEnum.PENDING.getCode());
                }
                // é€šè¿‡ä¾›åº”商名称查询ID,不存在则自动新增
                salesLedger.setSupplierId(findOrCreateSupplier(salesLedger.getSupplierName(), customers));
                Long aLong = sysUsers.stream()
                        .filter(sysUser -> sysUser.getNickName().equals(salesLedger.getRecorderName()))
                        .findFirst()
@@ -964,7 +981,12 @@
                    String collect = ids.stream().map(Object::toString).collect(Collectors.joining(","));
                    salesLedger.setApproveUserIds(collect);
                }
                purchaseLedgerMapper.insert(salesLedger);
                if (salesLedger.getId() == null) {
                    purchaseLedgerMapper.insert(salesLedger);
                } else {
                    // å¾…审核状态重新录入时,更新主表
                    purchaseLedgerMapper.updateById(salesLedger);
                }
                for (PurchaseLedgerProductImportDto salesLedgerProductImportDto : salesLedgerProductImportDtos) {
                    SalesLedgerProduct salesLedgerProduct = new SalesLedgerProduct();
@@ -972,14 +994,21 @@
                    salesLedgerProduct.setSalesLedgerId(salesLedger.getId());
                    salesLedgerProduct.setType(2);
                    // è®¡ç®—不含税总价
                    salesLedgerProduct.setTaxExclusiveTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice().divide(new BigDecimal(1).add(salesLedgerProduct.getTaxRate().divide(new BigDecimal(100))), 2, RoundingMode.HALF_UP));
                    list.stream()
                            .filter(map -> map.get("productName").equals(salesLedgerProduct.getProductCategory()) && map.get("model").equals(salesLedgerProduct.getSpecificationModel()))
                            .findFirst()
                            .ifPresent(map -> {
                                salesLedgerProduct.setProductModelId(Long.parseLong(map.get("modelId").toString()));
                                salesLedgerProduct.setProductId(Long.parseLong(map.get("id").toString()));
                            });
                    salesLedgerProduct.setTaxExclusiveTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice().divide(new BigDecimal(1).add(salesLedgerProduct.getTaxRate().divide(new BigDecimal(100))), 6, RoundingMode.HALF_UP));
                    // äº§å“å¤§ç±»/规格型号不存在时自动创建
                    String productName = salesLedgerProduct.getProductCategory();
                    if (StringUtils.isNotBlank(productName)) {
                        // è§„格型号为空时用产品大类作为规格型号
                        String model = StringUtils.isNotBlank(salesLedgerProduct.getSpecificationModel())
                                ? salesLedgerProduct.getSpecificationModel()
                                : productName;
                        salesLedgerProduct.setSpecificationModel(model);
                        Long productId = findOrCreateProduct(productName, list);
                        Long modelId = findOrCreateProductModel(productId, productName, model,
                                salesLedgerProduct.getUnit(), list);
                        salesLedgerProduct.setProductId(productId);
                        salesLedgerProduct.setProductModelId(modelId);
                    }
                    salesLedgerProduct.setRegister(loginUser.getNickName());
                    salesLedgerProduct.setRegisterDate(LocalDateTime.now());
                    salesLedgerProduct.setApproveStatus(0);
@@ -993,8 +1022,132 @@
            return AjaxResult.success("导入成功");
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("导入失败:" + e.getMessage());
        }
        return AjaxResult.success("导入失败");
    }
    /**
     * æŸ¥æ‰¾æˆ–新增供应商:supplier_manage è¡¨æŒ‰ supplier_name åŒ¹é…ï¼Œä¸å­˜åœ¨åˆ™æ–°å¢žï¼Œç¼ºå¤±ä¿¡æ¯é»˜è®¤å†™"/"
     */
    private Long findOrCreateSupplier(String supplierName, List<SupplierManage> supplierList) {
        if (StringUtils.isBlank(supplierName)) {
            return null;
        }
        // å†…存列表中查找(本次导入已新增的也包含在内)
        for (SupplierManage supplier : supplierList) {
            if (supplierName.equals(supplier.getSupplierName())) {
                return supplier.getId();
            }
        }
        // æ•°æ®åº“查找
        SupplierManage existSupplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>()
                .eq(SupplierManage::getSupplierName, supplierName)
                .last("limit 1"));
        if (existSupplier != null) {
            supplierList.add(existSupplier);
            return existSupplier.getId();
        }
        // æ–°å¢žä¾›åº”商,缺失信息(税号、地址、电话等)默认写"/"
        SupplierManage newSupplier = new SupplierManage();
        newSupplier.setSupplierName(supplierName);
        newSupplier.setTaxpayerIdentificationNum("/");
        newSupplier.setCompanyAddress("/");
        newSupplier.setSupplierType("/");
        newSupplier.setCompanyPhone("/");
        newSupplier.setBankAccountName("/");
        newSupplier.setBankAccountNum("/");
        newSupplier.setContactUserName("/");
        newSupplier.setContactUserPhone("/");
        supplierManageMapper.insert(newSupplier);
        supplierList.add(newSupplier);
        return newSupplier.getId();
    }
    /**
     * æŸ¥æ‰¾æˆ–新增产品大类:product è¡¨æŒ‰ product_name åŒ¹é…ï¼Œä¸å­˜åœ¨åˆ™æ–°å¢žï¼Œé»˜è®¤çˆ¶ID为278
     */
    private Long findOrCreateProduct(String productName, List<Map<String, Object>> productAndModelList) {
        // å†…存列表中查找(本次导入已新增的也包含在内)
        for (Map<String, Object> map : productAndModelList) {
            if (map.get("productName") != null && productName.equals(map.get("productName"))) {
                return Long.valueOf(map.get("id").toString());
            }
        }
        // æ•°æ®åº“查找(已存在但暂无规格型号的产品不在内存列表中)
        Product existProduct = productMapper.selectOne(new LambdaQueryWrapper<Product>()
                .eq(Product::getProductName, productName)
                .last("limit 1"));
        if (existProduct != null) {
            return existProduct.getId();
        }
        // æ–°å¢žäº§å“å¤§ç±»ï¼Œé»˜è®¤çˆ¶ID为278
        Product product = new Product();
        product.setProductName(productName);
        product.setParentId(278L);
        productMapper.insert(product);
        return product.getId();
    }
    /**
     * æŸ¥æ‰¾æˆ–新增产品规格型号:product_model æŒ‰ äº§å“ID+规格型号 åŒ¹é…ï¼Œä¸å­˜åœ¨åˆ™æ–°å¢žã€‚
     * äº§å“ç¼–码 = è§„格型号 + "_" + ä¸‰ä½æµæ°´å·ï¼›è§„格型号为空时用产品大类作为规格型号。
     */
    private Long findOrCreateProductModel(Long productId, String productName, String model, String unit,
                                          List<Map<String, Object>> productAndModelList) {
        // å†…存列表中查找
        for (Map<String, Object> map : productAndModelList) {
            if (map.get("id") != null && Long.valueOf(map.get("id").toString()).equals(productId)
                    && model.equals(map.get("model"))) {
                return Long.valueOf(map.get("modelId").toString());
            }
        }
        // æ•°æ®åº“查找
        ProductModel existProductModel = productModelMapper.selectOne(new LambdaQueryWrapper<ProductModel>()
                .eq(ProductModel::getProductId, productId)
                .eq(ProductModel::getModel, model)
                .last("limit 1"));
        if (existProductModel != null) {
            return existProductModel.getId();
        }
        // æ–°å¢žè§„格型号,产品编码 = è§„格型号 + "_" + ä¸‰ä½æµæ°´å·ï¼ˆåŒäº§å“ä¸‹è‡ªå¢žï¼‰
        long count = productModelMapper.selectCount(new LambdaQueryWrapper<ProductModel>()
                .eq(ProductModel::getProductId, productId));
        ProductModel productModel = new ProductModel();
        productModel.setProductId(productId);
        productModel.setModel(model);
        productModel.setUnit(unit);
        productModel.setProductCode(model + "_" + String.format("%03d", count + 1));
        productModelMapper.insert(productModel);
        // è®°å½•到内存列表,避免本次导入内重复创建
        Map<String, Object> newMap = new HashMap<>();
        newMap.put("id", productId);
        newMap.put("modelId", productModel.getId());
        newMap.put("productName", productName);
        newMap.put("model", model);
        productAndModelList.add(newMap);
        return productModel.getId();
    }
    /**
     * åˆ é™¤é‡‡è´­å•下的质检记录及质检参数(已提交的检验单不允许删除)
     */
    private void deleteQualityInspectByPurchaseLedger(Long purchaseLedgerId) {
        LambdaQueryWrapper<QualityInspect> inspectWrapper = new LambdaQueryWrapper<>();
        inspectWrapper.eq(QualityInspect::getPurchaseLedgerId, purchaseLedgerId);
        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(inspectWrapper);
        for (QualityInspect qualityInspect : qualityInspects) {
            if (ObjectUtils.isNotEmpty(qualityInspect.getInspectState()) && qualityInspect.getInspectState().equals(1)) {
                throw new BaseException("已提交的检验单不能删除");
            }
        }
        List<Long> inspectIds = qualityInspects.stream()
                .map(QualityInspect::getId)
                .collect(Collectors.toList());
        if (!inspectIds.isEmpty()) {
            qualityInspectParamMapper.delete(new LambdaQueryWrapper<QualityInspectParam>()
                    .in(QualityInspectParam::getInspectId, inspectIds));
        }
        qualityInspectMapper.delete(inspectWrapper);
    }
    @Override
src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java
@@ -4,6 +4,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@@ -27,11 +28,11 @@
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "单价")
    private Double unitPrice;
    private BigDecimal unitPrice;
    @Schema(description = "数量")
    private Integer quantity;
    private BigDecimal quantity;
    @Schema(description = "金额")
    private Double amount;
    private BigDecimal amount;
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -321,7 +321,7 @@
            BigDecimal remain = stockInventory.getQualitity()
                    .subtract(stockInventory.getLockedQuantity())
                    .subtract(required)
                    .divide(BigDecimal.ONE, 2, RoundingMode.CEILING);
                    .divide(BigDecimal.ONE, 6, RoundingMode.CEILING);
            if (remain.compareTo(BigDecimal.ZERO) < 0) {
                count++;
                stringBuffer.append(structure.getProductName())
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -15,9 +15,13 @@
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.CustomerUserMapper;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerUser;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.SaleEnum;
@@ -62,6 +66,8 @@
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -91,8 +97,13 @@
    private static final String LOCK_PREFIX = "contract_no_lock:";
    private static final long LOCK_WAIT_TIMEOUT = 10; // é”ç­‰å¾…超时时间(秒)
    private static final long LOCK_EXPIRE_TIME = 30;  // é”è‡ªåŠ¨è¿‡æœŸæ—¶é—´ï¼ˆç§’ï¼‰
    /** å¯¼å…¥è‡ªåŠ¨æ–°å¢žå®¢æˆ·æ—¶ï¼Œé™¤å®¢æˆ·åç§°å¤–å…¶ä½™ä¿¡æ¯çš„é»˜è®¤å¡«å……å€¼ */
    private static final String IMPORT_DEFAULT_VALUE = "/";
    /** å¯¼å…¥è‡ªåŠ¨æ–°å¢žäº§å“å¤§ç±»æ—¶æŒ‚è½½çš„é¡¶çº§äº§å“èŠ‚ç‚¹åç§° */
    private static final String FINISHED_PRODUCT_NAME = "成品";
    private final SalesLedgerMapper salesLedgerMapper;
    private final CustomerMapper customerMapper;
    private final CustomerUserMapper customerUserMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final SalesLedgerProductServiceImpl salesLedgerProductServiceImpl;
    private final CommonFileMapper commonFileMapper;
@@ -334,21 +345,9 @@
            // å®¢æˆ·æ•°æ® - å‚考 listPage æŸ¥è¯¢ç§æµ·å®¢æˆ·ï¼ˆtype = 0)
            // type = 0(私海客户)或者 type = 1(公海客户)且已被分配,并且是自己领用、自己创建或者共享给自己的客户
            Long loginUserId = loginUser.getUser().getUserId();
            List<Customer> customers = customerMapper.selectList(new QueryWrapper<Customer>()
                    .in("customer_name", salesLedgerImportDtoList.stream()
                            .map(SalesLedgerImportDto::getCustomerName).collect(Collectors.toList()))
                    .and(wrapper -> wrapper.eq("type", 0)
                            .or(wrapper2 -> wrapper2.eq("type", 1).eq("is_assigned", 1)))
                    .and(wrapper -> wrapper.eq("usage_user", loginUserId)
                            .or(wrapper2 -> wrapper2.eq("create_user", loginUserId)
                                    .or(wrapper3 -> wrapper3.exists("select 1 from customer_user cu where cu.customer_id = customer.id and cu.user_id = " + loginUserId)))));
//            // è§„格型号数据
//            List<ProductModel> productModels = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>().in(ProductModel::getModel,
//                    salesLedgerProductImportDtoList.stream().map(SalesLedgerImportDto::getSpecificationModel).collect(Collectors.toList())));
//            // äº§å“å¤§ç±»æ•°æ®
//            List<Product> productList = productMapper.selectList(new LambdaQueryWrapper<Product>().in(Product::getProductName,
//                    salesLedgerProductImportDtoList.stream().map(SalesLedgerImportDto::getProductCategory).collect(Collectors.toList())));
            List<Map<String, Object>> list = productModelMapper.getProductAndModelList();
            Map<String, Customer> customerCache = new HashMap<>();
            // äº§å“å¤§ç±»ã€è§„格型号缓存,key = äº§å“å¤§ç±» + | + è§„格型号,不存在时自动新增
            Map<String, ProductModel> productModelCache = new HashMap<>();
            // å½•入人数据
            List<SysUser> sysUsers = sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>().in(SysUser::getNickName,
                    salesLedgerImportDtoList.stream().map(SalesLedgerImportDto::getEntryPerson).collect(Collectors.toList())));
@@ -370,15 +369,11 @@
                BeanUtils.copyProperties(salesLedgerImportDto, salesLedger);
                salesLedger.setExecutionDate(DateUtils.toLocalDate(salesLedgerImportDto.getExecutionDate()));
                salesLedger.setDeliveryDate(DateUtils.toLocalDate(salesLedgerImportDto.getDeliveryDate()));
                // é€šè¿‡å®¢æˆ·åç§°æŸ¥è¯¢å®¢æˆ·ID,客户合同号
                Optional<Customer> customerOptional = customers.stream()
                        .filter(customer -> customer.getCustomerName().equals(salesLedger.getCustomerName()))
                        .findFirst();
                if (customerOptional.isEmpty()) {
                    throw new RuntimeException("客户:" + salesLedger.getCustomerName() + "不存在!或者非私海用户");
                }
                salesLedger.setCustomerId(customerOptional.get().getId());
                salesLedger.setCustomerContractNo(customerOptional.get().getTaxpayerIdentificationNumber());
                // é€šè¿‡å®¢æˆ·åç§°æŸ¥è¯¢å®¢æˆ·ID,客户合同号,客户不存在时自动新增
                Customer customer = resolveImportCustomer(salesLedger.getCustomerName(), loginUserId,
                        loginUser.getNickName(), customerCache);
                salesLedger.setCustomerId(customer.getId());
                salesLedger.setCustomerContractNo(customer.getTaxpayerIdentificationNumber());
                Long aLong = sysUsers.stream()
                        .filter(sysUser -> sysUser.getNickName().equals(salesLedger.getEntryPerson()))
                        .findFirst()
@@ -405,27 +400,17 @@
                    salesLedgerProduct.setSalesLedgerId(salesLedger.getId());
                    salesLedgerProduct.setType(1);
                    // è®¡ç®—不含税总价
                    salesLedgerProduct.setTaxExclusiveTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice().divide(new BigDecimal(1).add(salesLedgerProduct.getTaxRate().divide(new BigDecimal(100))), 2, RoundingMode.HALF_UP));
                    // æ ¡éªŒäº§å“è§„格是否存在
                    Optional<Map<String, Object>> productModelOptional = list.stream()
                            .filter(map -> Objects.equals(map.get("productName"), salesLedgerProduct.getProductCategory()) && Objects.equals(map.get("model"), salesLedgerProduct.getSpecificationModel()))
                            .findFirst();
                    if (productModelOptional.isEmpty()) {
                        throw new RuntimeException("产品大类:" + salesLedgerProduct.getProductCategory() + ",规格型号:" + salesLedgerProduct.getSpecificationModel() + "不存在!");
                    salesLedgerProduct.setTaxExclusiveTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice().divide(new BigDecimal(1).add(salesLedgerProduct.getTaxRate().divide(new BigDecimal(100))), 6, RoundingMode.HALF_UP));
                    // æ ¡éªŒäº§å“è§„格是否存在,产品大类、规格型号不存在时自动新增
                    ProductModel productModel = resolveImportProductModel(salesLedgerProduct.getProductCategory(),
                            salesLedgerProduct.getSpecificationModel(), salesLedgerProduct.getUnit(), productModelCache);
                    salesLedgerProduct.setProductModelId(productModel.getId());
                    salesLedgerProduct.setProductId(productModel.getProductId());
                    // è§„格型号为空时取产品大类,单位为空时取规格型号上的单位
                    salesLedgerProduct.setSpecificationModel(productModel.getModel());
                    if (StringUtils.isEmpty(salesLedgerProduct.getUnit())) {
                        salesLedgerProduct.setUnit(productModel.getUnit());
                    }
                    Map<String, Object> productModelMap = productModelOptional.get();
                    salesLedgerProduct.setProductModelId(Long.parseLong(productModelMap.get("modelId").toString()));
                    salesLedgerProduct.setProductId(Long.parseLong(productModelMap.get("id").toString()));
//                    salesLedgerProduct.setProductId(productList.stream()
//                            .filter(product -> product.getProductName().equals(salesLedgerProduct.getProductCategory()))
//                            .findFirst()
//                            .map(Product::getId)
//                            .orElse(null));
//                    salesLedgerProduct.setProductModelId(productModels.stream()
//                            .filter(productModel -> productModel.getModel().equals(salesLedgerProduct.getSpecificationModel()))
//                            .findFirst()
//                            .map(ProductModel::getId)
//                            .orElse(null));
                    salesLedgerProduct.setRegister(loginUser.getNickName());
                    salesLedgerProduct.setRegisterDate(LocalDateTime.now());
                    salesLedgerProduct.setApproveStatus(0);
@@ -439,10 +424,193 @@
            return AjaxResult.success("导入成功");
        } catch (Exception e) {
            e.printStackTrace();
            // å¯¼å…¥è¿‡ç¨‹ä¸­ä¼šè‡ªåŠ¨æ–°å¢žå®¢æˆ·ã€äº§å“å¤§ç±»ã€è§„æ ¼åž‹å·ï¼Œå¤±è´¥æ—¶éœ€è¦å›žæ»šï¼Œé¿å…æ®‹ç•™è„æ•°æ®
            if (TransactionSynchronizationManager.isActualTransactionActive()) {
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
            return AjaxResult.error("导入失败:" + e.getMessage());
        }
    }
    /**
     * å¯¼å…¥æ—¶èŽ·å–å®¢æˆ·ï¼Œå®¢æˆ·ä¸å­˜åœ¨åˆ™è‡ªåŠ¨æ–°å¢žå¹¶æŒ‚åˆ°å½“å‰ç™»å½•äººåä¸‹
     * <p>
     * 1、当前登录人可用的客户(私海,或公海已分配且自己领用/创建/共享):直接使用;
     * 2、同名公海客户未分配:领用到自己名下;
     * 3、同名公海客户已分配给别人:共享给自己;
     * 4、同名客户在别人私海:报错,不新建;
     * 5、客户不存在:新增私海客户挂到自己名下,纳税人识别号、电话等信息填 "/"。
     */
    private Customer resolveImportCustomer(String customerName, Long loginUserId, String nickName,
                                           Map<String, Customer> customerCache) {
        String name = StringUtils.trim(customerName);
        if (StringUtils.isEmpty(name)) {
            throw new RuntimeException("客户名称不能为空!");
        }
        Customer cachedCustomer = customerCache.get(name);
        if (cachedCustomer != null) {
            return cachedCustomer;
        }
        // å½“前登录人可用的客户
        Customer customer = customerMapper.selectOne(new QueryWrapper<Customer>()
                .eq("customer_name", name)
                .and(wrapper -> wrapper.eq("type", 0)
                        .or(wrapper2 -> wrapper2.eq("type", 1).eq("is_assigned", 1)))
                .and(wrapper -> wrapper.eq("usage_user", loginUserId)
                        .or(wrapper2 -> wrapper2.eq("create_user", loginUserId)
                                .or(wrapper3 -> wrapper3.exists("select 1 from customer_user cu where cu.customer_id = customer.id and cu.user_id = " + loginUserId))))
                .last("limit 1"));
        if (customer == null) {
            customer = claimOrCreateImportCustomer(name, loginUserId, nickName);
        }
        customerCache.put(name, customer);
        return customer;
    }
    /**
     * åŒåå®¢æˆ·ä¸å±žäºŽå½“前登录人时的处理:公海未分配的领用到自己名下,公海已分配的共享给自己,
     * åœ¨åˆ«äººç§æµ·çš„直接报错,都不存在则新增客户
     */
    private Customer claimOrCreateImportCustomer(String customerName, Long loginUserId, String nickName) {
        List<Customer> sameNameCustomers = customerMapper.selectList(new QueryWrapper<Customer>()
                .eq("customer_name", customerName)
                .orderByAsc("id"));
        // å…¬æµ·æœªåˆ†é…å®¢æˆ·ï¼Œé¢†ç”¨åˆ°è‡ªå·±åä¸‹
        Optional<Customer> publicUnassigned = sameNameCustomers.stream()
                .filter(item -> Objects.equals(item.getType(), 1)
                        && (item.getIsAssigned() == null || item.getIsAssigned() == 0))
                .findFirst();
        if (publicUnassigned.isPresent()) {
            Customer customer = publicUnassigned.get();
            customer.setIsAssigned(1);
            customer.setUsageStatus(1L);
            customer.setUsageUser(loginUserId);
            customerMapper.updateById(customer);
            return customer;
        }
        // å…¬æµ·å·²åˆ†é…ç»™åˆ«äººçš„客户,共享给自己
        Optional<Customer> publicAssigned = sameNameCustomers.stream()
                .filter(item -> Objects.equals(item.getType(), 1) && Objects.equals(item.getIsAssigned(), 1))
                .findFirst();
        if (publicAssigned.isPresent()) {
            Customer customer = publicAssigned.get();
            CustomerUser customerUser = new CustomerUser();
            customerUser.setCustomerId(customer.getId());
            customerUser.setUserId(loginUserId);
            customerUserMapper.insert(customerUser);
            return customer;
        }
        // å‰©ä¸‹çš„都是别人的私海客户
        if (!sameNameCustomers.isEmpty()) {
            throw new RuntimeException("客户:" + customerName + "已在其他人私海中,无法导入!");
        }
        // å®¢æˆ·ä¸å­˜åœ¨ï¼Œæ–°å¢žç§æµ·å®¢æˆ·å¹¶æŒ‚到自己名下
        Customer customer = new Customer();
        customer.setCustomerName(customerName);
        customer.setCustomerType(IMPORT_DEFAULT_VALUE);
        customer.setTaxpayerIdentificationNumber(IMPORT_DEFAULT_VALUE);
        customer.setCompanyAddress(IMPORT_DEFAULT_VALUE);
        customer.setCompanyPhone(IMPORT_DEFAULT_VALUE);
        customer.setContactPerson(IMPORT_DEFAULT_VALUE);
        customer.setContactPhone(IMPORT_DEFAULT_VALUE);
        customer.setBasicBankAccount(IMPORT_DEFAULT_VALUE);
        customer.setBankAccount(IMPORT_DEFAULT_VALUE);
        customer.setBankCode(IMPORT_DEFAULT_VALUE);
        customer.setMaintainer(nickName);
        customer.setMaintenanceTime(new Date());
        // ç§æµ·å®¢æˆ·ï¼Œé¢†ç”¨äººä¸ºå½“前登录人
        customer.setType(0);
        customer.setIsAssigned(0);
        customer.setUsageStatus(1L);
        customer.setUsageUser(loginUserId);
        customerMapper.insert(customer);
        return customer;
    }
    /**
     * å¯¼å…¥æ—¶èŽ·å–è§„æ ¼åž‹å·ï¼Œäº§å“å¤§ç±»ã€è§„æ ¼åž‹å·ä¸å­˜åœ¨åˆ™è‡ªåŠ¨æ–°å¢ž
     * <p>
     * è§„格型号为空时取产品大类作为规格型号;产品大类不存在时挂到顶级"成品"节点下;
     * æ–°å¢žè§„格型号时按"规格型号 + è§„æ ¼ID + _ + ä¸‰ä½æµæ°´å·"生成产品编码
     */
    private ProductModel resolveImportProductModel(String productCategory, String specificationModel, String unit,
                                                   Map<String, ProductModel> productModelCache) {
        String category = StringUtils.trim(productCategory);
        if (StringUtils.isEmpty(category)) {
            throw new RuntimeException("产品大类不能为空!");
        }
        // è§„格型号为空时取产品大类当产品规格
        String model = StringUtils.trim(specificationModel);
        if (StringUtils.isEmpty(model)) {
            model = category;
        }
        String cacheKey = category + "|" + model;
        ProductModel cachedProductModel = productModelCache.get(cacheKey);
        if (cachedProductModel != null) {
            return cachedProductModel;
        }
        // äº§å“å¤§ç±»ä¸å­˜åœ¨åˆ™æ–°å¢žåˆ°é¡¶çº§"成品"节点下
        Product product = productMapper.selectOne(new LambdaQueryWrapper<Product>()
                .eq(Product::getProductName, category)
                .orderByAsc(Product::getId)
                .last("limit 1"));
        if (product == null) {
            product = new Product();
            product.setParentId(getFinishedProductId());
            product.setProductName(category);
            productMapper.insert(product);
        }
        // è§„格型号不存在则新增
        ProductModel productModel = productModelMapper.selectOne(new LambdaQueryWrapper<ProductModel>()
                .eq(ProductModel::getProductId, product.getId())
                .eq(ProductModel::getModel, model)
                .orderByAsc(ProductModel::getId)
                .last("limit 1"));
        if (productModel == null) {
            productModel = new ProductModel();
            productModel.setProductId(product.getId());
            productModel.setModel(model);
            productModel.setUnit(StringUtils.isEmpty(unit) ? IMPORT_DEFAULT_VALUE : StringUtils.trim(unit));
            productModelMapper.insert(productModel);
            // äº§å“ç¼–码依赖自增的规格ID,插入后再回填
            productModel.setProductCode(generateProductCode(productModel.getModel(), productModel.getId()));
            productModelMapper.updateById(productModel);
        }
        productModelCache.put(cacheKey, productModel);
        return productModel;
    }
    /**
     * æŸ¥è¯¢é¡¶çº§"成品"产品节点(产品名称为成品且父ID为空)
     */
    private Long getFinishedProductId() {
        Product finishedProduct = productMapper.selectOne(new LambdaQueryWrapper<Product>()
                .eq(Product::getProductName, FINISHED_PRODUCT_NAME)
                .isNull(Product::getParentId)
                .orderByAsc(Product::getId)
                .last("limit 1"));
        if (finishedProduct == null) {
            throw new RuntimeException("产品档案中不存在顶级节点:" + FINISHED_PRODUCT_NAME + ",无法自动新增产品大类!");
        }
        return finishedProduct.getId();
    }
    /**
     * ç”Ÿæˆäº§å“ç¼–码,规则:规格型号 + è§„æ ¼ID + _ + ä¸‰ä½æµæ°´å·ï¼Œç¼–码已存在时流水号递增
     */
    private String generateProductCode(String model, Long productModelId) {
        String prefix = model + productModelId + "_";
        for (int sequence = 1; sequence < 1000; sequence++) {
            String productCode = prefix + String.format("%03d", sequence);
            Long count = productModelMapper.selectCount(new LambdaQueryWrapper<ProductModel>()
                    .eq(ProductModel::getProductCode, productCode));
            if (count == null || count == 0) {
                return productCode;
            }
        }
        throw new RuntimeException("规格型号:" + model + "的产品编码流水号已超出范围!");
    }
    @Override
    public List<LossProductModelDto> getSalesLedgerWithProductsLoss(Long salesLedgerId) {
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -326,6 +326,7 @@
                        stockInventoryDto.setQualitity(dto.getQualifiedQuantity());
                        stockInventoryDto.setRemark(dto.getRemark());
                        stockInventoryDto.setWarnNum(dto.getWarnNum());
                        stockInventoryDto.setBatchNo(dto.getBatchNo());
                        // éªŒè¯åˆæ ¼å†»ç»“数量
                        if (ObjectUtils.isNotEmpty(dto.getQualifiedLockedQuantity())) {
@@ -348,6 +349,7 @@
                        stockUninventoryDto.setRecordId(0L);
                        stockUninventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode());
                        stockUninventoryDto.setQualitity(dto.getUnQualifiedQuantity());
                        stockUninventoryDto.setBatchNo(dto.getBatchNo());
                        stockUninventoryDto.setRemark(dto.getRemark());
                        // éªŒè¯ä¸åˆæ ¼å†»ç»“数量
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -151,8 +151,8 @@
        SUM(slp.quantity) AS purchaseNum,
        SUM(slp.tax_inclusive_total_price) AS purchaseAmount,
        COUNT(DISTINCT slp.sales_ledger_id) AS purchaseTimes,
        <!-- å¹³å‡å•ä»· = æ€»é‡‡è´­é‡‘额/总采购数量,保留2位小数,避免除0 -->
        ROUND(IF(SUM(slp.quantity) = 0, 0, SUM(slp.tax_inclusive_total_price) / SUM(slp.quantity)), 2) AS averagePrice,
        <!-- å¹³å‡å•ä»· = æ€»é‡‡è´­é‡‘额/总采购数量,保留6位小数,避免除0 -->
        ROUND(IF(SUM(slp.quantity) = 0, 0, SUM(slp.tax_inclusive_total_price) / SUM(slp.quantity)), 6) AS averagePrice,
        <!-- è¯¥äº§å“å¤§ç±»ä¸‹æœ€åŽä¸€ä¸ªå½•入日期(取台账主表的entry_date) -->
        MAX(sl.entry_date) AS entryDate,
        COALESCE(NULLIF(SUM(t1.return_quantity), 0), 0) AS return_quantity,