云
5 天以前 a82d847bbb39e2417d4c9773ebc4fc51fdb0b8fc
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java
@@ -8,17 +8,23 @@
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationOptionRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationRevisionRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.bidbond.CrmBidBondDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationRevisionDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.mysql.bidbond.CrmBidBondMapper;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum;
import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationDocumentRenderer;
import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationExcelRenderer;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationService;
import cn.iocoder.yudao.module.system.service.companyconfig.SystemCompanyConfigService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
@@ -45,8 +51,10 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.UPDATE;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
@@ -67,6 +75,9 @@
    private CrmSaleQuotationService saleQuotationService;
    @Resource
    private CrmBidBondMapper bidBondMapper;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private DeptApi deptApi;
@@ -76,6 +87,8 @@
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private CrmCustomerService customerService;
    @Resource
    private CrmContactService contactService;
    @Resource
    private CrmBusinessService businessService;
    @Resource
@@ -166,8 +179,8 @@
        ExcelUtils.write(response, "销售报价单.xls", "数据", CrmSaleQuotationRespVO.class, list);
    }
    @GetMapping("/export-word")
    @Operation(summary = "导出销售报价单 Word(带电子章)")
    @GetMapping({"/export-word", "/export-quotation-excel"})
    @Operation(summary = "导出销售报价单 Excel(带电子章)")
    @Parameter(name = "id", description = "报价单编号", required = true)
    @PreAuthorize("@ss.hasPermission('crm:sale-quotation:export')")
    @ApiAccessLog(operateType = EXPORT)
@@ -187,12 +200,17 @@
                saleQuotationService.getSaleQuotationProductListByQuotationId(id));
        var companyConfig = companyConfigService.getCompanyConfig();
        String companyName = companyConfig != null ? companyConfig.getCompanyName() : null;
        byte[] content = CrmSaleQuotationDocumentRenderer.render(respVO, companyName);
        var customer = saleQuotation.getCustomerId() == null ? null : customerService.getCustomer(saleQuotation.getCustomerId());
        var contact = saleQuotation.getContactId() == null ? null : contactService.getContact(saleQuotation.getContactId());
        String phone = contact == null ? null : (contact.getMobile() != null && !contact.getMobile().isBlank()
                ? contact.getMobile() : contact.getTelephone());
        byte[] content = CrmSaleQuotationExcelRenderer.render(respVO, companyName,
                customer == null ? null : customer.getDetailAddress(), contact == null ? null : contact.getName(), phone);
        String filename = "报价单-" + saleQuotation.getNo()
                + (respVO.getCustomerName() != null ? "-" + respVO.getCustomerName() : "") + ".docx";
                + (respVO.getCustomerName() != null ? "-" + respVO.getCustomerName() : "") + ".xlsx";
        String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20");
        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + encoded);
        response.getOutputStream().write(content);
    }
@@ -219,6 +237,54 @@
    @PreAuthorize("@ss.hasPermission('crm:sale-quotation:convert-contract')")
    public CommonResult<Long> convertToContract(@RequestParam("id") Long id) {
        return success(saleQuotationService.convertToContract(id, getLoginUserId()));
    }
    @PutMapping("/void")
    @Operation(summary = "作废销售报价单")
    @Parameter(name = "id", description = "报价单编号", required = true)
    @Parameter(name = "reason", description = "作废原因", required = true)
    @PreAuthorize("@ss.hasPermission('crm:sale-quotation:void')")
    @ApiAccessLog(operateType = UPDATE)
    public CommonResult<Boolean> voidSaleQuotation(@RequestParam("id") Long id,
                                                   @RequestParam("reason") String reason) {
        saleQuotationService.voidSaleQuotation(id, reason);
        return success(true);
    }
    @GetMapping("/revision/list")
    @Operation(summary = "获得报价单轮次流水列表")
    @Parameter(name = "quotationId", description = "报价单编号", required = true)
    @PreAuthorize("@ss.hasPermission('crm:sale-quotation:query')")
    public CommonResult<List<CrmSaleQuotationRevisionRespVO>> getRevisionList(
            @RequestParam("quotationId") Long quotationId) {
        List<CrmSaleQuotationRevisionDO> list = saleQuotationService.getSaleQuotationRevisionList(quotationId);
        return success(BeanUtils.toBean(list, CrmSaleQuotationRevisionRespVO.class));
    }
    @GetMapping("/revision/export-excel")
    @Operation(summary = "导出报价单轮次流水 Excel")
    @Parameter(name = "quotationId", description = "报价单编号", required = true)
    @PreAuthorize("@ss.hasPermission('crm:sale-quotation:export')")
    @ApiAccessLog(operateType = EXPORT)
    public void exportRevisionExcel(@RequestParam("quotationId") Long quotationId,
                                    HttpServletResponse response) throws IOException {
        List<CrmSaleQuotationRevisionRespVO> list = BeanUtils.toBean(
                saleQuotationService.getSaleQuotationRevisionList(quotationId),
                CrmSaleQuotationRevisionRespVO.class);
        ExcelUtils.write(response, "报价轮次流水.xls", "数据", CrmSaleQuotationRevisionRespVO.class, list);
    }
    @GetMapping("/bindable-list")
    @Operation(summary = "获得客户下可绑定投标的报价单列表")
    @Parameter(name = "customerId", description = "客户编号", required = true)
    @Parameter(name = "bidBondId", description = "当前投标保证金编号(编辑时用于保留自身绑定)", required = false)
    @PreAuthorize("@ss.hasPermission('crm:sale-quotation:query')")
    public CommonResult<List<CrmSaleQuotationOptionRespVO>> getBindableList(
            @RequestParam("customerId") Long customerId,
            @RequestParam(value = "bidBondId", required = false) Long bidBondId) {
        return success(BeanUtils.toBean(
                saleQuotationService.getBindableQuotationList(customerId, bidBondId),
                CrmSaleQuotationOptionRespVO.class));
    }
    // ==================== 构建响应 VO ====================
@@ -268,6 +334,13 @@
        }
        // 3. 填充关联信息
        fillSaleQuotationRelatedFields(respVO);
        // 3.1 填充关联投标的中标结果
        if (saleQuotation.getBidBondId() != null) {
            CrmBidBondDO bidBond = bidBondMapper.selectById(saleQuotation.getBidBondId());
            if (bidBond != null) {
                respVO.setBidResult(bidBond.getBidResult());
            }
        }
        // 4. 填充附件列表
        respVO.setAttachmentList(storageAttachmentApi.listAttachments("crm_sale_quotation", saleQuotation.getId()));
        return respVO;
@@ -304,6 +377,11 @@
        // 1.6 商机信息(过滤 null,businessId 为可选字段)
        Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
                convertSet(pageResult.getList(), CrmSaleQuotationDO::getBusinessId, vo -> vo.getBusinessId() != null));
        // 1.7 投标信息(过滤 null,bidBondId 为可选字段)
        Set<Long> bidBondIds = convertSet(pageResult.getList(), CrmSaleQuotationDO::getBidBondId,
                vo -> vo.getBidBondId() != null);
        Map<Long, CrmBidBondDO> bidBondMap = bidBondIds.isEmpty() ? Map.of()
                : convertMap(bidBondMapper.selectByIds(bidBondIds), CrmBidBondDO::getId);
        // 2. 开始拼接
        return BeanUtils.toBean(pageResult, CrmSaleQuotationRespVO.class, vo -> {
@@ -324,6 +402,7 @@
            MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
            MapUtils.findAndThen(deptMap, vo.getOwnerUserId(), dept -> vo.setOwnerUserDeptName(dept.getName()));
            MapUtils.findAndThen(businessMap, vo.getBusinessId(), business -> vo.setBusinessName(business.getName()));
            MapUtils.findAndThen(bidBondMap, vo.getBidBondId(), bidBond -> vo.setBidResult(bidBond.getBidResult()));
        });
    }