chenhj
2026-04-20 6604ae13d86b1f22c0a7a3af536526102d85b77d
feat(sales): 新增销售报价及相关报价记录模块

- 添加IgnoreTableConfig配置忽略特定表
- 新增QuotationRecord实体及其Mapper、Service和Controller,实现报价关联表的增删改查
- 新增QuotationRecordJSON DTO,用于报价记录的JSON结构封装
- 实现SalesQuotationController,支持销售报价的分页查询、导入导出、增删改等操作
- 实现
已添加8个文件
已修改4个文件
486 ■■■■■ 文件已修改
src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/QuotationRecordController.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/QuotationRecordJSON.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/QuotationRecordMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/QuotationRecord.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/QuotationRecordService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/SalesQuotationService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/QuotationRecordServiceImpl.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/QuotationRecordMapper.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/销售报价导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java
@@ -37,5 +37,6 @@
        IGNORE_TABLES.add("sys_notice");
        IGNORE_TABLES.add("sys_user_client");
        IGNORE_TABLES.add("gen_table_column");
        IGNORE_TABLES.add("sales_quotation_record");
    }
}
src/main/java/com/ruoyi/sales/controller/QuotationRecordController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package com.ruoyi.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.pojo.QuotationRecord;
import com.ruoyi.sales.service.QuotationRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@AllArgsConstructor
@RequestMapping("/sales/quotationRecord")
@Api(value = "QuotationRecord", tags = "报价关联表")
public class QuotationRecordController {
    private QuotationRecordService quotationRecordservice;
    @GetMapping("/listPage")
    @ApiOperation("分页查询所有报价关联表")
    public AjaxResult listPage(Page page, QuotationRecord quotationRecord) {
        IPage<QuotationRecord> listPage = quotationRecordservice.listPage(page, quotationRecord);
        return AjaxResult.success(listPage);
    }
    @PostMapping("/add")
    @ApiOperation("新增报价关联表")
    public AjaxResult add(@RequestBody QuotationRecord quotationRecord) {
        return AjaxResult.success(quotationRecordservice.save(quotationRecord));
    }
    @PostMapping("/update")
    @ApiOperation("修改报价关联表")
    public AjaxResult update(@RequestBody QuotationRecord quotationRecord) {
        return AjaxResult.success(quotationRecordservice.updateById(quotationRecord));
    }
    @DeleteMapping("/delete")
    @ApiOperation("删除报价关联表")
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        return AjaxResult.success(quotationRecordservice.removeBatchByIds(ids));
    }
}
src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java
@@ -5,14 +5,22 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.dto.SalesQuotationDto;
import com.ruoyi.sales.service.SalesQuotationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
@RestController
@RequestMapping("/sales/quotation")
@Slf4j
public class SalesQuotationController {
    @Autowired
    private SalesQuotationService salesQuotationService;
@@ -44,4 +52,46 @@
    public AjaxResult delete(@RequestBody Long id) {
        return AjaxResult.success(salesQuotationService.delete(id));
    }
    @PostMapping("/downloadTemplate")
    public void exportTemplate(HttpServletResponse response) {
        // 1. æ¨¡æ¿æ–‡ä»¶åœ¨resources/static下的路径
        String templatePath = "static/销售报价导入模板.xlsx";
        // 2. èŽ·å–æ¨¡æ¿æ–‡ä»¶çš„è¾“å…¥æµ
        try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(templatePath)) {
            if (inputStream == null) {
                throw new FileNotFoundException("模板文件不存在:" + templatePath);
            }
            // 3. è®¾ç½®å“åº”头,触发浏览器下载
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode("销售报价导入模板.xlsx", "utf-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
            // 4. å°†æ¨¡æ¿æ–‡ä»¶å†™å…¥å“åº”输出流
            try (OutputStream outputStream = response.getOutputStream()) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = inputStream.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, len);
                }
                outputStream.flush();
            }
        } catch (IOException e) {
            log.error("导出销售报价模板失败", e);
            // è‹¥æ¨¡æ¿æ–‡ä»¶è¯»å–失败,返回错误提示
            try {
                response.getWriter().write("模板导出失败:" + e.getMessage());
            } catch (IOException ex) {
                log.error("响应输出错误", ex);
            }
        }
    }
    @PostMapping("/import")
    public AjaxResult importData(@RequestParam("file") MultipartFile file, @RequestParam("approveUserIdsJson") String approveUserIdsJson) {
        return AjaxResult.success(salesQuotationService.importData(file, approveUserIdsJson));
    }
}
src/main/java/com/ruoyi/sales/dto/QuotationRecordJSON.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.sales.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class QuotationRecordJSON {
    // å®¢æˆ·åç§°
    private String customer;
    // ä¸šåŠ¡å‘˜
    private String salesperson;
    // æŠ¥ä»·æ—¥æœŸ
    private String quotationDate;
    // æœ‰æ•ˆæœŸè‡³
    private String validDate;
    // ä»˜æ¬¾æ–¹å¼
    private String paymentMethod;
    // å¤‡æ³¨
    private String remark;
    private List<ProductJSON> products;
    @Data
    public static class ProductJSON {
        private String product;
        private String specification;
        private String unit;
        private BigDecimal unitPrice;
    }
}
src/main/java/com/ruoyi/sales/mapper/QuotationRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
package com.ruoyi.sales.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.sales.pojo.QuotationRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface QuotationRecordMapper extends BaseMapper<QuotationRecord> {
}
src/main/java/com/ruoyi/sales/pojo/QuotationRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package com.ruoyi.sales.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@TableName("sales_quotation_record")
@ApiModel(description="报价关联表")
public class QuotationRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ApiModelProperty(value="主键")
    private Long id;
    /**
     * æŠ¥ä»·å•id
     */
    @ApiModelProperty(value="报价单id")
    private Long quotationId;
    /**
     * json数据
     */
    @ApiModelProperty(value="json数据")
    private String info;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty(value="创建时间")
    private LocalDateTime createTime;
}
src/main/java/com/ruoyi/sales/service/QuotationRecordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.sales.dto.QuotationRecordJSON;
import com.ruoyi.sales.pojo.QuotationRecord;
public interface QuotationRecordService extends IService<QuotationRecord> {
    IPage<QuotationRecord> listPage(Page<QuotationRecord> page, QuotationRecord quotationRecord);
    Integer add(Long quotationId, QuotationRecordJSON quotationRecordJSON);
}
src/main/java/com/ruoyi/sales/service/SalesQuotationService.java
@@ -2,9 +2,11 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.sales.dto.QuotationRecordJSON;
import com.ruoyi.sales.dto.SalesQuotationDto;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.multipart.MultipartFile;
public interface SalesQuotationService extends IService<SalesQuotation> {
    IPage listPage(Page page, SalesQuotationDto salesQuotationDto);
@@ -14,4 +16,6 @@
    boolean delete(Long id);
    boolean edit(SalesQuotationDto salesQuotationDto);
    QuotationRecordJSON importData(MultipartFile file, String approveUserIdsJson);
}
src/main/java/com/ruoyi/sales/service/impl/QuotationRecordServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.sales.service.impl;
import cn.hutool.json.JSONUtil;
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.sales.dto.QuotationRecordJSON;
import com.ruoyi.sales.mapper.QuotationRecordMapper;
import com.ruoyi.sales.pojo.QuotationRecord;
import com.ruoyi.sales.service.QuotationRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class QuotationRecordServiceImpl extends ServiceImpl<QuotationRecordMapper, QuotationRecord> implements QuotationRecordService {
    private final QuotationRecordMapper quotationRecordmapper;
    @Override
    public IPage<QuotationRecord> listPage(Page<QuotationRecord> page, QuotationRecord quotationRecord) {
        return quotationRecordmapper.selectPage(page, new LambdaQueryWrapper<QuotationRecord>().orderByDesc(QuotationRecord::getCreateTime));
    }
    @Override
    public Integer add(Long quotationId, QuotationRecordJSON quotationRecordJSON) {
        QuotationRecord quotationRecord = new QuotationRecord();
        quotationRecord.setQuotationId(quotationId);
        quotationRecord.setInfo(JSONUtil.toJsonStr(quotationRecordJSON));
        return quotationRecordmapper.insert(quotationRecord);
    }
}
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
@@ -4,51 +4,65 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.IApproveProcessService;
import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
import com.ruoyi.approve.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.vo.ApproveProcessVO;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.uuid.UUID;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.sales.dto.QuotationRecordJSON;
import com.ruoyi.sales.dto.SalesQuotationDto;
import com.ruoyi.sales.mapper.QuotationRecordMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.SalesQuotationProductMapper;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.SalesQuotationProduct;
import com.ruoyi.sales.service.QuotationRecordService;
import com.ruoyi.sales.service.SalesQuotationProductService;
import com.ruoyi.sales.service.SalesQuotationService;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class SalesQuotationServiceImpl extends ServiceImpl<SalesQuotationMapper, SalesQuotation> implements SalesQuotationService {
    @Autowired
    private SalesQuotationMapper salesQuotationMapper;
    @Autowired
    private SalesQuotationProductMapper salesQuotationProductMapper;
    @Autowired
    private SalesQuotationProductService salesQuotationProductService;
    private final SalesQuotationMapper salesQuotationMapper;
    private final SalesQuotationProductMapper salesQuotationProductMapper;
    private final SalesQuotationProductService salesQuotationProductService;
    private final ApproveProcessServiceImpl approveProcessService;
    private final CustomerMapper customerMapper;
    private final SysUserMapper sysUserMapper;
    private final ProductModelMapper productModelMapper;
    private final QuotationRecordMapper quotationRecordMapper;
    private final QuotationRecordService quotationRecordsService;
    @Autowired
    private ApproveProcessServiceImpl approveProcessService;
    @Override
    public IPage<SalesQuotationDto> listPage(Page page, SalesQuotationDto salesQuotationDto) {
        IPage<SalesQuotationDto> salesQuotationDtoIPage = salesQuotationMapper.listPage(page, salesQuotationDto);
        if(CollectionUtils.isEmpty(salesQuotationDtoIPage.getRecords())){
        if (CollectionUtils.isEmpty(salesQuotationDtoIPage.getRecords())) {
            return salesQuotationDtoIPage;
        }
        salesQuotationDtoIPage.getRecords().forEach(record -> {
@@ -63,11 +77,11 @@
        LoginUser loginUser = SecurityUtils.getLoginUser();
        SalesQuotation salesQuotation = new SalesQuotation();
        BeanUtils.copyProperties(salesQuotationDto, salesQuotation);
        String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT","quotation_no");
        String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT", "quotation_no");
        salesQuotation.setQuotationNo(quotationNo);
        salesQuotation.setStatus("待审批");
        salesQuotationMapper.insert(salesQuotation);
        if(CollectionUtils.isEmpty(salesQuotationDto.getProducts())){
        if (CollectionUtils.isEmpty(salesQuotationDto.getProducts())) {
            return true;
        }
        List<SalesQuotationProduct> products = salesQuotationDto.getProducts().stream().map(product -> {
@@ -88,26 +102,27 @@
        approveProcessVO.setPrice(salesQuotationDto.getTotalAmount());
        try {
            approveProcessService.addApprove(approveProcessVO);
        }catch (Exception e){
        } catch (Exception e) {
            log.error("SalesQuotationServiceImpl error:{}", e);
            throw new RuntimeException("审批失败");
        }
        return true;
    }
    @Override
    public boolean edit(SalesQuotationDto salesQuotationDto) {
        SalesQuotation salesQuotation = new SalesQuotation();
        BeanUtils.copyProperties(salesQuotationDto, salesQuotation);
        ApproveGetAndUpdateVo vo = new ApproveGetAndUpdateVo();
        if("拒绝".equals(salesQuotationDto.getStatus())){
        if ("拒绝".equals(salesQuotationDto.getStatus())) {
            vo.setApproveStatus(0);
            salesQuotation.setStatus("待审批");
        }
        if(salesQuotationMapper.updateById(salesQuotation)!=1){
        if (salesQuotationMapper.updateById(salesQuotation) != 1) {
            return false;
        }
        salesQuotationProductMapper.delete(new LambdaQueryWrapper<SalesQuotationProduct>().eq(SalesQuotationProduct::getSalesQuotationId, salesQuotationDto.getId()));
        if(CollectionUtils.isEmpty(salesQuotationDto.getProducts())){
        if (CollectionUtils.isEmpty(salesQuotationDto.getProducts())) {
            return true;
        }
        List<SalesQuotationProduct> products = salesQuotationDto.getProducts().stream().map(product -> {
@@ -125,21 +140,205 @@
        approveProcessService.updateApproveUser(vo);
        return true;
    }
    @Override
    public boolean delete(Long id) {
        SalesQuotation salesQuotation = salesQuotationMapper.selectById(id);
        if(salesQuotation==null) return false;
        if (salesQuotation == null) return false;
        salesQuotationMapper.deleteById(id);
        salesQuotationProductMapper.delete(new LambdaQueryWrapper<SalesQuotationProduct>().eq(SalesQuotationProduct::getSalesQuotationId, id));
        // åˆ é™¤æŠ¥ä»·å®¡æ‰¹
        ApproveProcess one = approveProcessService.getOne(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveType, 6)
                .eq(ApproveProcess::getApproveReason, salesQuotation.getQuotationNo()));
        if(one != null){
        if (one != null) {
            approveProcessService.delByIds(Collections.singletonList(one.getId()));
        }
        return true;
    }
    @Override
    public QuotationRecordJSON importData(MultipartFile file, String approveUserIdsJson) {
        try (InputStream inputStream = file.getInputStream();
             Workbook workbook = WorkbookFactory.create(inputStream)) {
            Sheet sheet = workbook.getNumberOfSheets() > 1 ? workbook.getSheetAt(1) : workbook.getSheetAt(0);
            DataFormatter dataFormatter = new DataFormatter();
            QuotationRecordJSON result = new QuotationRecordJSON();
            // æ¨¡æ¿ä¸­å®¢æˆ·ä¿¡æ¯å›ºå®šåœ¨ç¬¬2行(A2-F2),且只有一行
            Row customerRow = sheet.getRow(1);
            List<Customer> customers = customerMapper.selectList(new LambdaQueryWrapper<Customer>()
                    .eq(Customer::getCustomerName, getStringCell(customerRow, 0, dataFormatter)));
            if (CollectionUtils.isEmpty(customers)) {
                throw new RuntimeException("客户不存在");
            }
            List<SysUser> sysUsers = sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>()
                    .eq(SysUser::getNickName, getStringCell(customerRow, 1, dataFormatter)));
            if (CollectionUtils.isEmpty(sysUsers)) {
                throw new RuntimeException("业务员不存在");
            }
            if (customerRow != null) {
                result.setCustomer(getStringCell(customerRow, 0, dataFormatter));
                result.setSalesperson(getStringCell(customerRow, 1, dataFormatter));
                result.setQuotationDate(getStringCell(customerRow, 2, dataFormatter));
                result.setValidDate(getStringCell(customerRow, 3, dataFormatter));
                result.setPaymentMethod(getStringCell(customerRow, 4, dataFormatter));
                result.setRemark(getStringCell(customerRow, 5, dataFormatter));
            }
            // æ¨¡æ¿ä¸­äº§å“è¡¨å¤´åœ¨ç¬¬7行,数据从第8行开始
            List<QuotationRecordJSON.ProductJSON> products = new ArrayList<>();
            int startRow = 7;
            for (int i = startRow; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row == null) {
                    continue;
                }
                String product = getStringCell(row, 0, dataFormatter);
                String specification = getStringCell(row, 1, dataFormatter);
                List<ProductModel> productModels = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
                        .eq(ProductModel::getModel, specification));
                if (CollectionUtils.isEmpty(productModels)) {
                    throw new RuntimeException("产品不存在");
                }
                ProductModel productModel = productModels.get(0);
                BigDecimal unitPrice = getDecimalCell(row, 2, dataFormatter);
                if (isBlank(product) && isBlank(specification) && unitPrice == null) {
                    continue;
                }
                QuotationRecordJSON.ProductJSON item = new QuotationRecordJSON.ProductJSON();
                item.setProduct(product);
                item.setSpecification(specification);
                item.setUnit(productModel.getUnit());
                item.setUnitPrice(unitPrice);
                products.add(item);
            }
            result.setProducts(products);
            // å¤„理报价单
            SalesQuotation salesQuotation = addOrUpdateApproveProcess(result, approveUserIdsJson);
            Integer insertResult = quotationRecordsService.add(salesQuotation.getId(), result);
            if (insertResult <= 0) {
                throw new RuntimeException("报价记录新增失败");
            }
            return result;
        } catch (Exception e) {
            throw new RuntimeException("导入销售报价模板失败:" + e.getMessage(), e);
        }
    }
    private SalesQuotation addOrUpdateApproveProcess(QuotationRecordJSON quotationRecordJSON, String approveUserIdsJson) {
        List<SalesQuotation> salesQuotations = salesQuotationMapper.selectList(new LambdaQueryWrapper<SalesQuotation>()
                .eq(SalesQuotation::getCustomer, quotationRecordJSON.getCustomer())
                .eq(SalesQuotation::getStatus, "待审批"));
        if (CollectionUtils.isEmpty(salesQuotations)) {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            SalesQuotation salesQuotation = new SalesQuotation();
            String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT", "quotation_no");
            salesQuotation.setQuotationNo(quotationNo);
            salesQuotation.setCustomer(quotationRecordJSON.getCustomer());
            salesQuotation.setSalesperson(quotationRecordJSON.getSalesperson());
            salesQuotation.setQuotationDate(LocalDate.parse(quotationRecordJSON.getQuotationDate()));
            salesQuotation.setValidDate(LocalDate.parse(quotationRecordJSON.getValidDate()));
            salesQuotation.setPaymentMethod(quotationRecordJSON.getPaymentMethod());
            salesQuotation.setStatus("待审批");
            BigDecimal totalAmount = quotationRecordJSON.getProducts().stream()
                    .map(QuotationRecordJSON.ProductJSON::getUnitPrice)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            salesQuotation.setTotalAmount(totalAmount);
            salesQuotationMapper.insert(salesQuotation);
            List<SalesQuotationProduct> products = quotationRecordJSON.getProducts().stream().map(product -> {
                SalesQuotationProduct salesQuotationProduct = new SalesQuotationProduct();
                salesQuotationProduct.setProduct(product.getProduct());
                salesQuotationProduct.setSpecification(product.getSpecification());
                salesQuotationProduct.setUnit(product.getUnit());
                salesQuotationProduct.setUnitPrice(product.getUnitPrice().doubleValue());
                salesQuotationProduct.setSalesQuotationId(salesQuotation.getId());
                return salesQuotationProduct;
            }).collect(Collectors.toList());
            salesQuotationProductService.saveBatch(products);
            // æŠ¥ä»·å®¡æ‰¹
            ApproveProcessVO approveProcessVO = new ApproveProcessVO();
            approveProcessVO.setApproveType(6);
            approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
            approveProcessVO.setApproveReason(quotationNo);
            approveProcessVO.setApproveUserIds(approveUserIdsJson);
            approveProcessVO.setApproveUser(loginUser.getUserId());
            approveProcessVO.setApproveTime(LocalDate.now().toString());
            approveProcessVO.setPrice(salesQuotation.getTotalAmount());
            try {
                approveProcessService.addApprove(approveProcessVO);
            } catch (Exception e) {
                log.error("SalesQuotationServiceImpl error:{}", e);
                throw new RuntimeException("审批失败");
            }
            return salesQuotation;
        } else {
            if (salesQuotations.size() > 1) {
                throw new RuntimeException("存在多个待审批的报价单");
            } else {
                SalesQuotation salesQuotation = salesQuotations.get(0);
                salesQuotation.setSalesperson(quotationRecordJSON.getSalesperson());
                salesQuotation.setQuotationDate(LocalDate.parse(quotationRecordJSON.getQuotationDate()));
                salesQuotation.setValidDate(LocalDate.parse(quotationRecordJSON.getValidDate()));
                salesQuotation.setPaymentMethod(quotationRecordJSON.getPaymentMethod());
                BigDecimal totalAmount = quotationRecordJSON.getProducts().stream()
                        .map(QuotationRecordJSON.ProductJSON::getUnitPrice)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                salesQuotation.setTotalAmount(totalAmount);
                salesQuotationMapper.updateById(salesQuotation);
                // åˆ é™¤åŽŸæ¥çš„æŠ¥ä»·äº§å“
                salesQuotationProductMapper.delete(new LambdaQueryWrapper<SalesQuotationProduct>()
                        .eq(SalesQuotationProduct::getSalesQuotationId, salesQuotation.getId()));
                List<SalesQuotationProduct> products = quotationRecordJSON.getProducts().stream().map(product -> {
                    SalesQuotationProduct salesQuotationProduct = new SalesQuotationProduct();
                    salesQuotationProduct.setProduct(product.getProduct());
                    salesQuotationProduct.setSpecification(product.getSpecification());
                    salesQuotationProduct.setUnit(product.getUnit());
                    salesQuotationProduct.setUnitPrice(product.getUnitPrice().doubleValue());
                    salesQuotationProduct.setSalesQuotationId(salesQuotation.getId());
                    return salesQuotationProduct;
                }).collect(Collectors.toList());
                salesQuotationProductService.saveBatch(products);
                return salesQuotation;
            }
        }
    }
    private String getStringCell(Row row, int columnIndex, DataFormatter dataFormatter) {
        if (row == null) {
            return null;
        }
        Cell cell = row.getCell(columnIndex);
        if (cell == null) {
            return null;
        }
        if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
            return cell.getLocalDateTimeCellValue().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
        }
        String value = dataFormatter.formatCellValue(cell);
        return isBlank(value) ? null : value.trim();
    }
    private BigDecimal getDecimalCell(Row row, int columnIndex, DataFormatter dataFormatter) {
        String value = getStringCell(row, columnIndex, dataFormatter);
        if (isBlank(value)) {
            return null;
        }
        try {
            return new BigDecimal(value.replace(",", ""));
        } catch (Exception e) {
            return null;
        }
    }
    private boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }
}
src/main/resources/mapper/sales/QuotationRecordMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.sales.mapper.QuotationRecordMapper">
    <resultMap id="BaseResultMap" type="com.ruoyi.sales.pojo.QuotationRecord">
        <id column="id" property="id"/>
        <result column="quotation_id" property="quotationId"/>
        <result column="info" property="info"/>
        <result column="create_time" property="createTime"/>
    </resultMap>
</mapper>
src/main/resources/static/ÏúÊÛ±¨¼Ûµ¼ÈëÄ£°å.xlsx
Binary files differ