tuqin
2026-09-24 0891b6708cc09c2672a19661a8555aae0e742df1
feat: 新增多模块导入导出功能

- 反馈登记:新增 Excel 导入(客户名称/字典/销售单号校验)
- 临期售后管理:新增导入、导出
- 入库管理:新增导入(按产品名称+规格型号匹配产品)
- 出库台账:新增导入(生成待审批出库记录)
- 产品维护:新增按产品导出规格型号
已修改16个文件
已添加5个文件
793 ■■■■■ 文件已修改
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesNearExpiryController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesServiceController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesNearExpiryExportDto.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesNearExpiryImportDto.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesServiceImportDto.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/service/AfterSalesNearExpiryService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/service/AfterSalesServiceService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesNearExpiryServiceImpl.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/ProductController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/IProductModelService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInRecordImportDto.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockOutRecordImportDto.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInRecordService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockOutRecordService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesNearExpiryController.java
@@ -2,16 +2,25 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.aftersalesservice.dto.AfterSalesNearExpiryExportDto;
import com.ruoyi.aftersalesservice.dto.AfterSalesNearExpiryImportDto;
import com.ruoyi.aftersalesservice.pojo.AfterSalesNearExpiry;
import com.ruoyi.aftersalesservice.service.AfterSalesNearExpiryService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
/**
 * <br>
@@ -74,4 +83,46 @@
        return AjaxResult.success(listPage);
    }
    /**
     * å¯¼å…¥ä¸´æœŸå”®åŽ
     */
    @PostMapping("/import")
    @Operation(summary = "临期售后管理-导入")
    @Log(title = "临期售后管理-导入", businessType = BusinessType.IMPORT)
    public AjaxResult importData(MultipartFile file) {
        return afterSalesNearExpiryService.importData(file);
    }
    /**
     * ä¸‹è½½å¯¼å…¥æ¨¡æ¿
     */
    @PostMapping("/importTemplate")
    @Operation(summary = "临期售后管理-导入模板")
    public void importTemplate(HttpServletResponse response) {
        ExcelUtil<AfterSalesNearExpiryImportDto> util = new ExcelUtil<AfterSalesNearExpiryImportDto>(AfterSalesNearExpiryImportDto.class);
        util.importTemplateExcel(response, "临期售后管理导入模板");
    }
    /**
     * å¯¼å‡ºä¸´æœŸå”®åŽ
     */
    @PostMapping("/export")
    @Operation(summary = "临期售后管理-导出")
    @Log(title = "临期售后管理-导出", businessType = BusinessType.EXPORT)
    public void export(HttpServletResponse response) {
        Page<AfterSalesNearExpiry> page = new Page<>(-1, -1);
        List<AfterSalesNearExpiry> list = afterSalesNearExpiryService.listPage(page, new AfterSalesNearExpiry()).getRecords();
        List<AfterSalesNearExpiryExportDto> exportList = new ArrayList<>();
        for (AfterSalesNearExpiry item : list) {
            AfterSalesNearExpiryExportDto dto = new AfterSalesNearExpiryExportDto();
            BeanUtils.copyProperties(item, dto);
            if (item.getStatus() != null) {
                dto.setStatusName(String.valueOf(item.getStatus()));
            }
            exportList.add(dto);
        }
        ExcelUtil<AfterSalesNearExpiryExportDto> util = new ExcelUtil<AfterSalesNearExpiryExportDto>(AfterSalesNearExpiryExportDto.class);
        util.exportExcel(response, exportList, "临期售后管理");
    }
}
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesServiceController.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.aftersalesservice.dto.AfterSalesServiceExeclDto;
import com.ruoyi.aftersalesservice.dto.AfterSalesServiceImportDto;
import com.ruoyi.aftersalesservice.dto.AfterSalesServiceNewDto;
import com.ruoyi.aftersalesservice.pojo.AfterSalesService;
import com.ruoyi.aftersalesservice.service.AfterSalesServiceService;
@@ -18,6 +19,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
@@ -150,4 +152,18 @@
        return AjaxResult.success(afterSalesServiceService.countAfterSalesService());
    }
    @PostMapping("/import")
    @Operation(summary = "售后服务-反馈登记导入")
    @Log(title = "售后服务-反馈登记导入", businessType = BusinessType.IMPORT)
    public AjaxResult importData(MultipartFile file) {
        return afterSalesServiceService.importData(file);
    }
    @PostMapping("/importTemplate")
    @Operation(summary = "售后服务-反馈登记导入模板")
    public void importTemplate(HttpServletResponse response) {
        ExcelUtil<AfterSalesServiceImportDto> util = new ExcelUtil<AfterSalesServiceImportDto>(AfterSalesServiceImportDto.class);
        util.importTemplateExcel(response, "反馈登记导入模板");
    }
}
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesNearExpiryExportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.ruoyi.aftersalesservice.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
/**
 * ä¸´æœŸå”®åŽç®¡ç†-导出 DTO
 *
 * @author :yys
 */
@Data
public class AfterSalesNearExpiryExportDto {
    @Schema(description = "临期产品名称")
    @Excel(name = "临期产品名称")
    private String productName;
    @Schema(description = "产品批号")
    @Excel(name = "产品批号")
    private String batchNumber;
    @Schema(description = "临期日期")
    @Excel(name = "临期日期", width = 20, dateFormat = "yyyy-MM-dd")
    private LocalDate expireDate;
    @Schema(description = "库存数量")
    @Excel(name = "库存数量")
    private Integer stockQuantity;
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    @Schema(description = "联系电话")
    @Excel(name = "联系电话")
    private String contactPhone;
    @Schema(description = "问题描述")
    @Excel(name = "问题描述")
    private String disRes;
    @Schema(description = "处理状态")
    @Excel(name = "处理状态", readConverterExp = "1=待处理,2=已处理")
    private String statusName;
    @Schema(description = "处理人")
    @Excel(name = "处理人")
    private String disposeNickName;
    @Schema(description = "处理日期")
    @Excel(name = "处理日期", width = 20, dateFormat = "yyyy-MM-dd")
    private LocalDate disDate;
    @Schema(description = "处理结果")
    @Excel(name = "处理结果")
    private String disposeResult;
}
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesNearExpiryImportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package com.ruoyi.aftersalesservice.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
/**
 * ä¸´æœŸå”®åŽç®¡ç†-导入 DTO
 *
 * @author :yys
 */
@Data
public class AfterSalesNearExpiryImportDto {
    @Schema(description = "临期产品名称")
    @Excel(name = "临期产品名称")
    private String productName;
    @Schema(description = "产品批号")
    @Excel(name = "产品批号")
    private String batchNumber;
    @Schema(description = "临期日期")
    @Excel(name = "临期日期", width = 20, dateFormat = "yyyy-MM-dd")
    private LocalDate expireDate;
    @Schema(description = "库存数量")
    @Excel(name = "库存数量")
    private Integer stockQuantity;
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    @Schema(description = "联系电话")
    @Excel(name = "联系电话")
    private String contactPhone;
    @Schema(description = "问题描述")
    @Excel(name = "问题描述")
    private String disRes;
}
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesServiceImportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package com.ruoyi.aftersalesservice.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/**
 * åé¦ˆç™»è®°-导入 DTO
 *
 * @author :yys
 */
@Data
public class AfterSalesServiceImportDto {
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    @Schema(description = "关联销售单号")
    @Excel(name = "关联销售单号")
    private String salesContractNo;
    @Schema(description = "售后类型")
    @Excel(name = "售后类型")
    private String serviceType;
    @Schema(description = "紧急程度")
    @Excel(name = "紧急程度")
    private String urgency;
    @Schema(description = "反馈日期")
    @Excel(name = "反馈日期", width = 20, dateFormat = "yyyy-MM-dd")
    private Date feedbackDate;
    @Schema(description = "客户诉求")
    @Excel(name = "客户诉求")
    private String proDesc;
}
src/main/java/com/ruoyi/aftersalesservice/service/AfterSalesNearExpiryService.java
@@ -4,6 +4,8 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.aftersalesservice.pojo.AfterSalesNearExpiry;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
/**
 * <br>
@@ -23,4 +25,12 @@
    IPage<AfterSalesNearExpiry> listPage(Page<AfterSalesNearExpiry> page, AfterSalesNearExpiry entity);
    /**
     * ä¸´æœŸå”®åŽç®¡ç†-导入
     *
     * @param file excel æ–‡ä»¶
     * @return å¯¼å…¥ç»“果(成功/失败条数及错误明细)
     */
    AjaxResult importData(MultipartFile file);
}
src/main/java/com/ruoyi/aftersalesservice/service/AfterSalesServiceService.java
@@ -6,6 +6,8 @@
import com.ruoyi.aftersalesservice.dto.AfterSalesServiceNewDto;
import com.ruoyi.aftersalesservice.dto.CountDto;
import com.ruoyi.aftersalesservice.pojo.AfterSalesService;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -30,4 +32,12 @@
    List<CountDto> countAfterSalesService();
    /**
     * åé¦ˆç™»è®°-导入
     *
     * @param file excel æ–‡ä»¶
     * @return å¯¼å…¥ç»“果(成功/失败条数及错误明细)
     */
    AjaxResult importData(MultipartFile file);
}
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesNearExpiryServiceImpl.java
@@ -3,15 +3,24 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.aftersalesservice.dto.AfterSalesNearExpiryImportDto;
import com.ruoyi.aftersalesservice.mapper.AfterSalesNearExpiryMapper;
import com.ruoyi.aftersalesservice.pojo.AfterSalesNearExpiry;
import com.ruoyi.aftersalesservice.service.AfterSalesNearExpiryService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * <br>
@@ -22,6 +31,7 @@
 * @version 1.0
 * @since 2026/03/02 14:46
 */
@Slf4j
@Service
public class AfterSalesNearExpiryServiceImpl extends ServiceImpl<AfterSalesNearExpiryMapper, AfterSalesNearExpiry> implements AfterSalesNearExpiryService {
@@ -86,4 +96,74 @@
    public IPage<AfterSalesNearExpiry> listPage(Page<AfterSalesNearExpiry> page, AfterSalesNearExpiry entity) {
        return baseMapper.listPage(page, entity);
    }
    @Override
    public AjaxResult importData(MultipartFile file) {
        List<AfterSalesNearExpiryImportDto> rows;
        try {
            ExcelUtil<AfterSalesNearExpiryImportDto> util = new ExcelUtil<>(AfterSalesNearExpiryImportDto.class);
            rows = util.importExcel(file.getInputStream());
        } catch (Exception e) {
            log.error("临期售后管理导入-解析excel失败", e);
            return AjaxResult.error("解析Excel失败:" + e.getMessage());
        }
        if (CollectionUtils.isEmpty(rows)) {
            return AjaxResult.error("导入文件无数据");
        }
        int success = 0;
        List<String> errors = new ArrayList<>();
        for (int i = 0; i < rows.size(); i++) {
            AfterSalesNearExpiryImportDto row = rows.get(i);
            int rowNo = i + 2; // é¦–行为表头
            try {
                if (StringUtils.isEmpty(trim(row.getProductName()))) {
                    errors.add("第" + rowNo + "行:临期产品名称为空");
                    continue;
                }
                if (StringUtils.isEmpty(trim(row.getBatchNumber()))) {
                    errors.add("第" + rowNo + "行:产品批号为空");
                    continue;
                }
                if (row.getExpireDate() == null) {
                    errors.add("第" + rowNo + "行:临期日期为空");
                    continue;
                }
                if (row.getStockQuantity() == null) {
                    errors.add("第" + rowNo + "行:库存数量为空");
                    continue;
                }
                if (StringUtils.isEmpty(trim(row.getCustomerName()))) {
                    errors.add("第" + rowNo + "行:客户名称为空");
                    continue;
                }
                AfterSalesNearExpiry entity = new AfterSalesNearExpiry();
                entity.setProductName(trim(row.getProductName()));
                entity.setBatchNumber(trim(row.getBatchNumber()));
                entity.setExpireDate(row.getExpireDate());
                entity.setStockQuantity(row.getStockQuantity());
                entity.setCustomerName(trim(row.getCustomerName()));
                entity.setContactPhone(trim(row.getContactPhone()));
                entity.setDisRes(row.getDisRes());
                add(entity);
                success++;
            } catch (Exception e) {
                log.error("临期售后管理导入第{}行失败", rowNo, e);
                errors.add("第" + rowNo + "行:" + e.getMessage());
            }
        }
        AjaxResult result = AjaxResult.success();
        result.put("totalCount", rows.size());
        result.put("successCount", success);
        result.put("failCount", rows.size() - success);
        result.put("errors", errors);
        result.put("msg", "导入完成:成功 " + success + " æ¡ï¼Œå¤±è´¥ " + (rows.size() - success) + " æ¡");
        return result;
    }
    private String trim(String value) {
        return value == null ? null : value.trim();
    }
}
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
@@ -1,22 +1,31 @@
package com.ruoyi.aftersalesservice.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.aftersalesservice.dto.AfterSalesServiceImportDto;
import com.ruoyi.aftersalesservice.dto.AfterSalesServiceNewDto;
import com.ruoyi.aftersalesservice.dto.CountDto;
import com.ruoyi.aftersalesservice.mapper.AfterSalesServiceMapper;
import com.ruoyi.aftersalesservice.pojo.AfterSalesService;
import com.ruoyi.aftersalesservice.service.AfterSalesServiceService;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.domain.SysDictData;
import com.ruoyi.project.system.mapper.SysDictDataMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerProductService;
@@ -25,8 +34,12 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@@ -45,6 +58,9 @@
    private final ISalesLedgerProductService salesLedgerProductService;
    private final ISalesLedgerService salesLedgerService;
    private final com.ruoyi.sales.mapper.ShippingInfoMapper shippingInfoMapper;
    private final CustomerMapper customerMapper;
    private final SalesLedgerMapper salesLedgerMapper;
    private final SysDictDataMapper sysDictDataMapper;
    @Override
    public IPage<AfterSalesServiceNewDto> listPage(Page page, AfterSalesServiceNewDto afterSalesService) {
@@ -138,4 +154,136 @@
        return stringIntegerHashMap;
    }
    @Override
    public AjaxResult importData(MultipartFile file) {
        List<AfterSalesServiceImportDto> rows;
        try {
            ExcelUtil<AfterSalesServiceImportDto> util = new ExcelUtil<>(AfterSalesServiceImportDto.class);
            rows = util.importExcel(file.getInputStream());
        } catch (Exception e) {
            log.error("反馈登记导入-解析excel失败", e);
            return AjaxResult.error("解析Excel失败:" + e.getMessage());
        }
        if (CollectionUtils.isEmpty(rows)) {
            return AjaxResult.error("导入文件无数据");
        }
        Long tenantId = SecurityUtils.getLoginUser().getTenantId();
        Long userId = SecurityUtils.getUserId();
        int success = 0;
        List<String> errors = new ArrayList<>();
        for (int i = 0; i < rows.size(); i++) {
            AfterSalesServiceImportDto row = rows.get(i);
            int rowNo = i + 2; // é¦–行为表头
            try {
                String customerName = trim(row.getCustomerName());
                if (StringUtils.isEmpty(customerName)) {
                    errors.add("第" + rowNo + "行:客户名称为空");
                    continue;
                }
                Customer customer = findCustomerByName(customerName, tenantId);
                if (customer == null) {
                    errors.add("第" + rowNo + "行:客户[" + customerName + "]不存在");
                    continue;
                }
                String serviceTypeName = trim(row.getServiceType());
                String serviceTypeValue = dictValueByLabel("post_sale_waiting_list", serviceTypeName);
                if (StringUtils.isEmpty(serviceTypeValue)) {
                    errors.add("第" + rowNo + "行:售后类型[" + serviceTypeName + "]不存在");
                    continue;
                }
                String urgencyName = trim(row.getUrgency());
                String urgencyValue = dictValueByLabel("degree_of_urgency", urgencyName);
                if (StringUtils.isEmpty(urgencyValue)) {
                    errors.add("第" + rowNo + "行:紧急程度[" + urgencyName + "]不存在");
                    continue;
                }
                Long salesLedgerId = null;
                String contractNo = trim(row.getSalesContractNo());
                if (StringUtils.isNotEmpty(contractNo)) {
                    SalesLedger ledger = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>()
                            .eq(SalesLedger::getSalesContractNo, contractNo)
                            .eq(SalesLedger::getCustomerId, customer.getId())
                            .last("limit 1"));
                    if (ledger == null) {
                        ledger = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>()
                                .eq(SalesLedger::getSalesContractNo, contractNo)
                                .last("limit 1"));
                    }
                    if (ledger == null) {
                        errors.add("第" + rowNo + "行:销售单号[" + contractNo + "]不存在");
                        continue;
                    }
                    salesLedgerId = ledger.getId();
                }
                AfterSalesServiceNewDto dto = new AfterSalesServiceNewDto();
                dto.setCustomerName(customer.getCustomerName());
                dto.setSalesLedgerId(salesLedgerId);
                dto.setServiceType(serviceTypeValue);
                dto.setUrgency(urgencyValue);
                dto.setProDesc(row.getProDesc());
                dto.setFeedbackDate(row.getFeedbackDate() != null ? row.getFeedbackDate() : new Date());
                dto.setCheckUserId(userId);
                dto.setProductModelIds("");
                dto.setProductModelQuantities("");
                if (addAfterSalesServiceDto(dto)) {
                    success++;
                } else {
                    errors.add("第" + rowNo + "行:保存失败");
                }
            } catch (Exception e) {
                log.error("反馈登记导入第{}行失败", rowNo, e);
                errors.add("第" + rowNo + "行:" + e.getMessage());
            }
        }
        AjaxResult result = AjaxResult.success();
        result.put("totalCount", rows.size());
        result.put("successCount", success);
        result.put("failCount", rows.size() - success);
        result.put("errors", errors);
        result.put("msg", "导入完成:成功 " + success + " æ¡ï¼Œå¤±è´¥ " + (rows.size() - success) + " æ¡");
        return result;
    }
    private String trim(String value) {
        return value == null ? null : value.trim();
    }
    private Customer findCustomerByName(String name, Long tenantId) {
        List<Customer> customers = customerMapper.selectList(new LambdaQueryWrapper<Customer>()
                .eq(Customer::getCustomerName, name));
        if (CollectionUtils.isEmpty(customers)) {
            return null;
        }
        if (tenantId != null) {
            for (Customer c : customers) {
                if (tenantId.equals(c.getTenantId())) {
                    return c;
                }
            }
        }
        return customers.get(0);
    }
    private String dictValueByLabel(String dictType, String label) {
        if (StringUtils.isEmpty(label)) {
            return null;
        }
        List<SysDictData> datas = sysDictDataMapper.selectDictDataByType(dictType);
        if (CollectionUtils.isEmpty(datas)) {
            return null;
        }
        for (SysDictData d : datas) {
            if (label.equals(d.getDictLabel())) {
                return d.getDictValue();
            }
        }
        return null;
    }
}
src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -136,4 +136,14 @@
    public void importProduct(HttpServletResponse response) {
        productService.exportImportTemplate(response);
    }
    /**
     * å¯¼å‡ºäº§å“è§„格型号
     */
    @PostMapping("/exportModel")
    @Operation(summary = "导出产品规格型号")
    @Log(title = "产品规格型号导出", businessType = BusinessType.EXPORT)
    public void exportModel(HttpServletResponse response, ProductDto productDto) {
        productModelService.exportProductModel(response, productDto);
    }
}
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java
@@ -7,6 +7,7 @@
import com.ruoyi.basic.vo.ProductModelVo;
import com.ruoyi.procurementrecord.bean.dto.ProcurementPageDto;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
@@ -27,4 +28,17 @@
    ProductModel selectLatestRecord();
    List<Map<String, Object>> getProductAndModelList();
    /**
     * æŒ‰äº§å“åç§° + è§„格型号查询产品型号
     *
     * @param productName äº§å“åç§°
     * @param model       è§„格型号
     * @return äº§å“åž‹å·ï¼ˆå–第一条)
     */
    @Select("select pm.id, pm.product_id, pm.model, pm.unit from product_model pm " +
            "left join product p on pm.product_id = p.id " +
            "where p.product_name = #{productName} and pm.model = #{model} " +
            "order by pm.id limit 1")
    ProductModel selectByProductNameAndModel(@Param("productName") String productName, @Param("model") String model);
}
src/main/java/com/ruoyi/basic/service/IProductModelService.java
@@ -7,6 +7,7 @@
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.AjaxResult;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -35,4 +36,12 @@
    IPage<ProductModel> modelListPage(Page page , ProductDto productDto);
    AjaxResult importProductModel(MultipartFile file);
    /**
     * å¯¼å‡ºäº§å“è§„格型号
     *
     * @param response  å“åº”
     * @param productDto äº§å“ï¼ˆæŒ‰ id)
     */
    void exportProductModel(HttpServletResponse response, ProductDto productDto);
}
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -18,6 +18,7 @@
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.basic.dto.ProductModelExportDto;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
@@ -27,6 +28,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
@@ -368,4 +371,20 @@
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
    @Override
    public void exportProductModel(HttpServletResponse response, ProductDto productDto) {
        Page<ProductModel> page = new Page<>(-1, -1);
        List<ProductModel> list = modelListPage(page, productDto).getRecords();
        List<ProductModelExportDto> exportList = new ArrayList<>();
        for (ProductModel productModel : list) {
            ProductModelExportDto dto = new ProductModelExportDto();
            dto.setProductCode(productModel.getProductCode());
            dto.setModel(productModel.getModel());
            dto.setUnit(productModel.getUnit());
            exportList.add(dto);
        }
        ExcelUtil<ProductModelExportDto> util = new ExcelUtil<>(ProductModelExportDto.class);
        util.exportExcel(response, exportList, "产品规格型号");
    }
}
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java
@@ -4,8 +4,10 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.dto.StockInRecordImportDto;
import com.ruoyi.stock.service.StockInRecordService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
@@ -13,6 +15,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -81,4 +84,18 @@
        return AjaxResult.success();
    }
    @PostMapping("/import")
    @Log(title = "入库管理-导入", businessType = BusinessType.IMPORT)
    @Operation(summary = "入库管理-导入")
    public AjaxResult importData(MultipartFile file, String type) {
        return stockInRecordService.importData(file, type);
    }
    @PostMapping("/importTemplate")
    @Operation(summary = "入库管理-导入模板")
    public void importTemplate(HttpServletResponse response) {
        ExcelUtil<StockInRecordImportDto> util = new ExcelUtil<StockInRecordImportDto>(StockInRecordImportDto.class);
        util.importTemplateExcel(response, "入库导入模板");
    }
}
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
@@ -4,14 +4,17 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.dto.StockOutRecordImportDto;
import com.ruoyi.stock.service.StockOutRecordService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
@@ -87,4 +90,18 @@
        return AjaxResult.success();
    }
    @PostMapping("/import")
    @Log(title = "出库台账-导入", businessType = BusinessType.IMPORT)
    @Operation(summary = "出库台账-导入")
    public AjaxResult importData(MultipartFile file, String type) {
        return stockOutRecordService.importData(file, type);
    }
    @PostMapping("/importTemplate")
    @Operation(summary = "出库台账-导入模板")
    public void importTemplate(HttpServletResponse response) {
        ExcelUtil<StockOutRecordImportDto> util = new ExcelUtil<StockOutRecordImportDto>(StockOutRecordImportDto.class);
        util.importTemplateExcel(response, "出库导入模板");
    }
}
src/main/java/com/ruoyi/stock/dto/StockInRecordImportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.ruoyi.stock.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
 * å…¥åº“管理-导入 DTO
 *
 * @author :yys
 */
@Data
public class StockInRecordImportDto {
    @Schema(description = "产品大类")
    @Excel(name = "产品大类")
    private String productName;
    @Schema(description = "规格型号")
    @Excel(name = "规格型号")
    private String model;
    @Schema(description = "批号")
    @Excel(name = "批号")
    private String batchNo;
    @Schema(description = "入库数量")
    @Excel(name = "入库数量")
    private BigDecimal stockInNum;
    @Schema(description = "备注")
    @Excel(name = "备注")
    private String remark;
}
src/main/java/com/ruoyi/stock/dto/StockOutRecordImportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.ruoyi.stock.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
 * å‡ºåº“台账-导入 DTO
 *
 * @author :yys
 */
@Data
public class StockOutRecordImportDto {
    @Schema(description = "产品大类")
    @Excel(name = "产品大类")
    private String productName;
    @Schema(description = "规格型号")
    @Excel(name = "规格型号")
    private String model;
    @Schema(description = "批号")
    @Excel(name = "批号")
    private String batchNo;
    @Schema(description = "出库数量")
    @Excel(name = "出库数量")
    private BigDecimal stockOutNum;
    @Schema(description = "备注")
    @Excel(name = "备注")
    private String remark;
}
src/main/java/com/ruoyi/stock/service/StockInRecordService.java
@@ -5,8 +5,10 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.framework.web.domain.AjaxResult;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface StockInRecordService extends IService<StockInRecord> {
@@ -25,4 +27,13 @@
    int batchApprove(List<Long> ids, Integer approvalStatus);
    int batchReAudit(List<Long> ids);
    /**
     * å…¥åº“管理-导入
     *
     * @param file excel æ–‡ä»¶
     * @param type å…¥åº“类型 0-合格 1-不合格
     * @return å¯¼å…¥ç»“果(成功/失败条数及错误明细)
     */
    AjaxResult importData(MultipartFile file, String type);
}
src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
@@ -5,8 +5,10 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.pojo.StockOutRecord;
import com.ruoyi.framework.web.domain.AjaxResult;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
@@ -31,4 +33,13 @@
    void exportStockOutRecord(HttpServletResponse response, StockOutRecordDto stockOutRecordDto);
    int batchApprove(List<Long> ids, Integer approvalStatus);
    /**
     * å‡ºåº“台账-导入
     *
     * @param file excel æ–‡ä»¶
     * @param type å‡ºåº“类型 0-合格 1-不合格
     * @return å¯¼å…¥ç»“果(成功/失败条数及错误明细)
     */
    AjaxResult importData(MultipartFile file, String type);
}
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -14,6 +14,10 @@
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.dto.StockInRecordImportDto;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockUninventoryDto;
import com.ruoyi.stock.execl.StockInRecordExportData;
@@ -28,11 +32,13 @@
import com.ruoyi.stock.service.StockInRecordService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Service
@@ -43,6 +49,7 @@
    private StockInventoryMapper stockInventoryMapper;
    private StockUninventoryMapper stockUninventoryMapper;
    private ProductionOrderPickMapper productionOrderPickMapper;
    private ProductModelMapper productModelMapper;
    @Override
    public IPage<StockInRecordDto> listPage(Page page, StockInRecordDto stockInRecordDto) {
@@ -369,4 +376,74 @@
        return ids.size();
    }
    @Override
    public AjaxResult importData(MultipartFile file, String type) {
        List<StockInRecordImportDto> rows;
        try {
            ExcelUtil<StockInRecordImportDto> util = new ExcelUtil<>(StockInRecordImportDto.class);
            rows = util.importExcel(file.getInputStream());
        } catch (Exception e) {
            return AjaxResult.error("解析Excel失败:" + e.getMessage());
        }
        if (CollectionUtils.isEmpty(rows)) {
            return AjaxResult.error("导入文件无数据");
        }
        String stockType = StringUtils.isEmpty(type) ? "0" : type.trim();
        String recordType = "1".equals(stockType)
                ? StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()
                : StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode();
        int success = 0;
        List<String> errors = new ArrayList<>();
        for (int i = 0; i < rows.size(); i++) {
            StockInRecordImportDto row = rows.get(i);
            int rowNo = i + 2; // é¦–行为表头
            try {
                String productName = trim(row.getProductName());
                String model = trim(row.getModel());
                if (StringUtils.isEmpty(productName)) {
                    errors.add("第" + rowNo + "行:产品大类为空");
                    continue;
                }
                if (StringUtils.isEmpty(model)) {
                    errors.add("第" + rowNo + "行:规格型号为空");
                    continue;
                }
                if (row.getStockInNum() == null) {
                    errors.add("第" + rowNo + "行:入库数量为空");
                    continue;
                }
                ProductModel productModel = productModelMapper.selectByProductNameAndModel(productName, model);
                if (productModel == null) {
                    errors.add("第" + rowNo + "行:未找到产品[" + productName + " / " + model + "]");
                    continue;
                }
                StockInRecordDto dto = new StockInRecordDto();
                dto.setProductModelId(productModel.getId());
                dto.setBatchNo(trim(row.getBatchNo()));
                dto.setStockInNum(row.getStockInNum());
                dto.setRemark(row.getRemark());
                dto.setType(stockType);
                dto.setRecordType(recordType);
                dto.setApprovalStatus(ReviewStatusEnum.PENDING_REVIEW.getCode());
                add(dto);
                success++;
            } catch (Exception e) {
                errors.add("第" + rowNo + "行:" + e.getMessage());
            }
        }
        AjaxResult result = AjaxResult.success();
        result.put("totalCount", rows.size());
        result.put("successCount", success);
        result.put("failCount", rows.size() - success);
        result.put("errors", errors);
        result.put("msg", "导入完成:成功 " + success + " æ¡ï¼Œå¤±è´¥ " + (rows.size() - success) + " æ¡");
        return result;
    }
    private String trim(String value) {
        return value == null ? null : value.trim();
    }
}
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
@@ -17,6 +17,10 @@
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.dto.StockOutRecordImportDto;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.stock.dto.StockUninventoryDto;
import com.ruoyi.stock.execl.StockOutRecordExportData;
import com.ruoyi.stock.mapper.StockInventoryMapper;
@@ -31,7 +35,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
/**
@@ -50,6 +56,7 @@
    private final StockUninventoryMapper stockUninventoryMapper;
    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
    private final AccountInvoiceApplicationMapper accountInvoiceApplicationMapper;
    private final ProductModelMapper productModelMapper;
    @Override
    public IPage<StockOutRecordDto> listPage(Page page, StockOutRecordDto stockOutRecordDto) {
@@ -230,4 +237,74 @@
        }
        return stockUninventoryMapper.selectOne(eq);
    }
    @Override
    public AjaxResult importData(MultipartFile file, String type) {
        List<StockOutRecordImportDto> rows;
        try {
            ExcelUtil<StockOutRecordImportDto> util = new ExcelUtil<>(StockOutRecordImportDto.class);
            rows = util.importExcel(file.getInputStream());
        } catch (Exception e) {
            return AjaxResult.error("解析Excel失败:" + e.getMessage());
        }
        if (CollectionUtils.isEmpty(rows)) {
            return AjaxResult.error("导入文件无数据");
        }
        String stockType = StringUtils.isEmpty(type) ? "0" : type.trim();
        String recordType = "1".equals(stockType)
                ? StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()
                : StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_OUT.getCode();
        int success = 0;
        List<String> errors = new ArrayList<>();
        for (int i = 0; i < rows.size(); i++) {
            StockOutRecordImportDto row = rows.get(i);
            int rowNo = i + 2; // é¦–行为表头
            try {
                String productName = trim(row.getProductName());
                String model = trim(row.getModel());
                if (StringUtils.isEmpty(productName)) {
                    errors.add("第" + rowNo + "行:产品大类为空");
                    continue;
                }
                if (StringUtils.isEmpty(model)) {
                    errors.add("第" + rowNo + "行:规格型号为空");
                    continue;
                }
                if (row.getStockOutNum() == null) {
                    errors.add("第" + rowNo + "行:出库数量为空");
                    continue;
                }
                ProductModel productModel = productModelMapper.selectByProductNameAndModel(productName, model);
                if (productModel == null) {
                    errors.add("第" + rowNo + "行:未找到产品[" + productName + " / " + model + "]");
                    continue;
                }
                StockOutRecordDto dto = new StockOutRecordDto();
                dto.setProductModelId(productModel.getId());
                dto.setBatchNo(trim(row.getBatchNo()));
                dto.setStockOutNum(row.getStockOutNum());
                dto.setRemark(row.getRemark());
                dto.setType(stockType);
                dto.setRecordType(recordType);
                dto.setApprovalStatus(ReviewStatusEnum.PENDING_REVIEW.getCode());
                add(dto);
                success++;
            } catch (Exception e) {
                errors.add("第" + rowNo + "行:" + e.getMessage());
            }
        }
        AjaxResult result = AjaxResult.success();
        result.put("totalCount", rows.size());
        result.put("successCount", success);
        result.put("failCount", rows.size() - success);
        result.put("errors", errors);
        result.put("msg", "导入完成:成功 " + success + " æ¡ï¼Œå¤±è´¥ " + (rows.size() - success) + " æ¡");
        return result;
    }
    private String trim(String value) {
        return value == null ? null : value.trim();
    }
}