Jelajahi Sumber

提交代码

zlwu 11 bulan lalu
induk
melakukan
e96ab0fdef

+ 110 - 14
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/service/impl/CrmQuoteServiceImpl.java

@@ -219,26 +219,43 @@ public class CrmQuoteServiceImpl implements ICrmQuoteService {
     public Boolean insertByBo(CrmQuoteBo bo) {
         CrmQuote entity = MapstructUtils.convert(bo, CrmQuote.class);
 
-        // 生成报价单编号
-        if (StringUtils.isBlank(entity.getQuoteNo())) {
-            entity.setQuoteNo(generateQuoteNo());
-        }
-
         // 查询客户信息
         CrmCustomer customer = null;
         if (entity.getCustomerId() != null) {
             customer = customerMapper.selectById(entity.getCustomerId());
         }
 
+        // 查询供方信息
+        String supplierShortName = "山峡";
+        if (entity.getSupplierId() != null) {
+            try {
+                var supplierInfo = supplierInfoService.queryById(entity.getSupplierId());
+                if (supplierInfo != null && StringUtils.isNotBlank(supplierInfo.getShortName())) {
+                    supplierShortName = supplierInfo.getShortName();
+                } else if (supplierInfo != null && StringUtils.isNotBlank(supplierInfo.getSupplierName())) {
+                    supplierShortName = supplierInfo.getSupplierName();
+                }
+            } catch (Exception e) {
+                log.warn("查询供方信息失败,使用默认简称,供方ID: {}", entity.getSupplierId(), e);
+            }
+        }
+
+        // 生成报价单编号(根据供方编码)
+        // 如果报价单号为空或包含预览标记(***),则重新生成
+        if (StringUtils.isBlank(entity.getQuoteNo()) ||
+                (StringUtils.isNotBlank(entity.getQuoteNo()) && entity.getQuoteNo().contains("***"))) {
+            entity.setQuoteNo(generateQuoteNo(entity.getSupplierId()));
+        }
+
         // 自动生成报价主题
         String quoteNo = entity.getQuoteNo();
-        String shortName = customer != null && StringUtils.isNotBlank(customer.getShortName())
+        String customerShortName = customer != null && StringUtils.isNotBlank(customer.getShortName())
                 ? customer.getShortName()
                 : "";
 
         if (StringUtils.isBlank(entity.getQuoteTitle())) {
-            // 用户未填写报价主题,自动生成:山峡-客户简称(报价单编号)
-            entity.setQuoteTitle("山峡-" + shortName + "(" + quoteNo + ")");
+            // 用户未填写报价主题,自动生成:供方简称-客户简称(报价单编号)
+            entity.setQuoteTitle(supplierShortName + "-" + customerShortName + "(" + quoteNo + ")");
         }
 
         // 设置默认值
@@ -337,18 +354,82 @@ public class CrmQuoteServiceImpl implements ICrmQuoteService {
             }
         }
 
-        // 填充报价主体名称
-        if (entity.getSupplierId() != null && StringUtils.isBlank(entity.getSupplierName())) {
+        // 检查客户或供方是否发生变化
+        boolean customerChanged = !java.util.Objects.equals(oldEntity.getCustomerId(), entity.getCustomerId());
+        boolean supplierChanged = !java.util.Objects.equals(oldEntity.getSupplierId(), entity.getSupplierId());
+
+        // 查询客户信息
+        CrmCustomer customer = null;
+        if (entity.getCustomerId() != null) {
+            customer = customerMapper.selectById(entity.getCustomerId());
+        }
+
+        // 查询供方信息
+        String supplierShortName = "山峡";
+        if (entity.getSupplierId() != null) {
             try {
                 var supplierInfo = supplierInfoService.queryById(entity.getSupplierId());
                 if (supplierInfo != null) {
                     entity.setSupplierName(supplierInfo.getSupplierName());
+                    if (StringUtils.isNotBlank(supplierInfo.getShortName())) {
+                        supplierShortName = supplierInfo.getShortName();
+                    } else if (StringUtils.isNotBlank(supplierInfo.getSupplierName())) {
+                        supplierShortName = supplierInfo.getSupplierName();
+                    }
                 }
             } catch (Exception e) {
                 log.warn("查询报价主体信息失败, supplierId: {}", entity.getSupplierId(), e);
             }
         }
 
+        // 如果客户或供方发生变化,更新报价主题和报价单号(只更新编码和简称,不改变日期和序列号)
+        if (customerChanged || supplierChanged) {
+            String quoteNo = oldEntity.getQuoteNo(); // 保持原报价单号不变
+
+            // 如果供方发生变化,只更新报价单号中的供方编码部分,保持日期和序列号不变
+            if (supplierChanged && StringUtils.isNotBlank(quoteNo)) {
+                // 报价单号格式:{供方编码}DD-YYYYMMDD001
+                // 提取日期和序列号部分(DD-YYYYMMDD001)
+                String dateAndSeq = null;
+                if (quoteNo.contains("DD-") && quoteNo.length() > 3) {
+                    int ddIndex = quoteNo.indexOf("DD-");
+                    if (ddIndex >= 0 && ddIndex + 3 < quoteNo.length()) {
+                        dateAndSeq = quoteNo.substring(ddIndex);
+                    }
+                }
+
+                // 获取新的供方编码
+                String newSupplierCode = "SX";
+                if (entity.getSupplierId() != null) {
+                    try {
+                        var supplierInfo = supplierInfoService.queryById(entity.getSupplierId());
+                        if (supplierInfo != null && StringUtils.isNotBlank(supplierInfo.getSupplierCode())) {
+                            newSupplierCode = supplierInfo.getSupplierCode();
+                        }
+                    } catch (Exception e) {
+                        log.warn("查询供方信息失败,使用默认编码,供方ID: {}", entity.getSupplierId(), e);
+                    }
+                }
+
+                // 如果成功提取了日期和序列号,则用新编码替换
+                if (dateAndSeq != null) {
+                    quoteNo = newSupplierCode + dateAndSeq;
+                    entity.setQuoteNo(quoteNo);
+                }
+                // 如果提取失败,保持原报价单号不变
+            }
+
+            // 更新报价主题(只更新简称部分,报价单号使用当前数据的报价单号,确保与entity.getQuoteNo()保持一致)
+            String customerShortName = customer != null && StringUtils.isNotBlank(customer.getShortName())
+                    ? customer.getShortName()
+                    : "";
+            // 使用entity.getQuoteNo()确保报价主题中的报价单号与当前数据的报价单号保持一致
+            // 如果供方变化且成功更新了报价单号,entity.getQuoteNo()就是更新后的值
+            // 如果供方没变化或提取失败,entity.getQuoteNo()就是原值
+            String currentQuoteNo = entity.getQuoteNo() != null ? entity.getQuoteNo() : oldEntity.getQuoteNo();
+            entity.setQuoteTitle(supplierShortName + "-" + customerShortName + "(" + currentQuoteNo + ")");
+        }
+
         // 计算到期日期
         if (entity.getValidityDays() != null && entity.getQuoteDate() != null) {
             entity.setExpireDate(entity.getQuoteDate().plusDays(entity.getValidityDays()));
@@ -749,11 +830,26 @@ public class CrmQuoteServiceImpl implements ICrmQuoteService {
 
     /**
      * 生成报价单编号
-     * 格式:SXDD-YYYYMMDD001(SXDD- + 年月日 + 3位序号)
+     * 格式:{供方编码}DD-YYYYMMDD001(供方编码DD- + 年月日 + 3位序号)
+     *
+     * @param supplierId 供方主体ID,如果为null则使用默认编码"SX"
      */
-    private String generateQuoteNo() {
+    private String generateQuoteNo(Long supplierId) {
+        // 获取供方编码,如果没有则使用默认值"SX"
+        String supplierCode = "SX";
+        if (supplierId != null) {
+            try {
+                var supplierInfo = supplierInfoService.queryById(supplierId);
+                if (supplierInfo != null && StringUtils.isNotBlank(supplierInfo.getSupplierCode())) {
+                    supplierCode = supplierInfo.getSupplierCode();
+                }
+            } catch (Exception e) {
+                log.warn("查询供方信息失败,使用默认编码,供方ID: {}", supplierId, e);
+            }
+        }
+
         String dateStr = DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDate.now());
-        String prefix = "SXDD-" + dateStr;
+        String prefix = supplierCode + "DD-" + dateStr;
 
         // 查询当天最大编号(只查询未删除的记录)
         LambdaQueryWrapper<CrmQuote> wrapper = Wrappers.lambdaQuery();
@@ -765,7 +861,7 @@ public class CrmQuoteServiceImpl implements ICrmQuoteService {
         int sequence = 1;
         if (lastQuote != null && StringUtils.isNotBlank(lastQuote.getQuoteNo())) {
             String lastNo = lastQuote.getQuoteNo();
-            // SXDD-20251017001,前缀长度是13(SXDD-yyyyMMdd),序号长度是3位
+            // {供方编码}DD-20251017001,前缀长度是{供方编码长度}+5(DD-yyyyMMdd),序号长度是3位
             if (lastNo.length() >= prefix.length() + 3) {
                 String seqStr = lastNo.substring(prefix.length());
                 try {

+ 82 - 0
web/src/views/crm/quote/form.vue

@@ -377,6 +377,7 @@ import { getContactByCustomerId } from '@/api/crm/customer';
 interface CustomerVO {
   id: number;
   customerName: string;
+  shortName?: string;
 }
 
 interface ContactVO {
@@ -424,6 +425,9 @@ const form = ref<QuoteForm>({
   products: []
 });
 
+// 客户简称(用于生成报价主题)
+const customerShortName = ref('');
+
 // 表单校验规则
 const rules = ref({
   customerId: [{ required: true, message: '请选择客户', trigger: 'change' }],
@@ -588,6 +592,19 @@ const getQuoteInfo = async (id: string | number) => {
     // 基本信息赋值
     Object.assign(form.value, quoteData);
 
+    // 加载客户简称(从报价主题中提取,或从客户信息中获取)
+    // 报价主题格式:供方简称-客户简称(报价单号)
+    if (quoteData.quoteTitle) {
+      const match = quoteData.quoteTitle.match(/^[^-]+-(.+?)(/);
+      if (match && match[1]) {
+        customerShortName.value = match[1];
+      }
+    }
+    // 如果没有从报价主题中提取到,则使用客户名称
+    if (!customerShortName.value && quoteData.customerName) {
+      customerShortName.value = quoteData.customerName;
+    }
+
     // 回显商机名称(如果有关联商机)
     if (quoteData.opportunityId) {
       // 直接使用后端返回的商机名称
@@ -636,6 +653,8 @@ const handleCustomerConfirm = async (customer: CustomerVO) => {
   if (customer) {
     form.value.customerId = customer.id;
     form.value.customerName = customer.customerName;
+    // 保存客户简称
+    customerShortName.value = customer.shortName || customer.customerName || '';
     // 清空联系人和商机
     form.value.contactId = undefined;
     form.value.contactName = '';
@@ -659,6 +678,9 @@ const handleCustomerConfirm = async (customer: CustomerVO) => {
       console.error('加载联系人列表失败:', error);
       // 加载失败不影响客户选择,只是不自动填充联系人
     }
+
+    // 更新报价主题和报价单号
+    updateQuoteTitleAndNo();
   }
 };
 
@@ -745,17 +767,65 @@ const loadSupplierList = async () => {
   }
 };
 
+/** 更新报价主题和报价单号 */
+const updateQuoteTitleAndNo = () => {
+  // 获取供方信息
+  const supplier = form.value.supplierId
+    ? supplierList.value.find((s) => s.id === form.value.supplierId)
+    : null;
+  const supplierShortName = supplier?.shortName || supplier?.supplierName || '山峡';
+  const supplierCode = supplier?.supplierCode || 'SX';
+
+  // 获取客户简称(优先使用保存的简称,否则使用客户名称)
+  const customerShortNameValue = customerShortName.value || form.value.customerName || '';
+
+  // 新建时:生成报价单号预览(格式:供方编码DD-日期***)
+  if (!form.value.id) {
+    const today = new Date();
+    const year = today.getFullYear();
+    const month = String(today.getMonth() + 1).padStart(2, '0');
+    const day = String(today.getDate()).padStart(2, '0');
+    const dateStr = `${year}${month}${day}`;
+    const quoteNoPreview = `${supplierCode}DD-${dateStr}***`;
+    
+    // 如果还没有报价单号,则显示预览
+    if (!form.value.quoteNo || form.value.quoteNo.includes('***')) {
+      form.value.quoteNo = quoteNoPreview;
+    }
+  } else {
+    // 编辑时:如果供方变化,只更新报价单号中的供方编码部分,保持日期和序列号不变
+    if (form.value.quoteNo && form.value.quoteNo.includes('DD-')) {
+      // 报价单号格式:{供方编码}DD-YYYYMMDD001
+      // 提取日期和序列号部分(DD-YYYYMMDD001)
+      const ddIndex = form.value.quoteNo.indexOf('DD-');
+      if (ddIndex >= 0 && ddIndex + 3 < form.value.quoteNo.length) {
+        const dateAndSeq = form.value.quoteNo.substring(ddIndex);
+        // 用新的供方编码替换编码部分,保持日期和序列号不变
+        form.value.quoteNo = supplierCode + dateAndSeq;
+      }
+    }
+  }
+
+  // 更新报价主题(格式:供方简称-客户简称(报价单号))
+  // 报价单号使用当前数据的报价单号(新建时是预览格式,编辑时如果供方变化则只更新编码部分)
+  if (form.value.quoteNo) {
+    form.value.quoteTitle = `${supplierShortName}-${customerShortNameValue}(${form.value.quoteNo})`;
+  }
+};
+
 /** 报价主体变化处理 */
 const handleSupplierChange = (supplierId: number | undefined) => {
   if (!supplierId) {
     form.value.supplierId = undefined;
     form.value.supplierName = '';
+    updateQuoteTitleAndNo();
     return;
   }
   const supplier = supplierList.value.find((s) => s.id === supplierId);
   if (supplier) {
     form.value.supplierId = supplier.id;
     form.value.supplierName = supplier.supplierName || '';
+    updateQuoteTitleAndNo();
   }
 };
 
@@ -1074,6 +1144,12 @@ const handleSaveDraft = () => {
           }));
         form.value.attachments = attachmentsWithCategory.length > 0 ? JSON.stringify(attachmentsWithCategory) : '';
 
+        // 新增时,如果报价单号包含预览标记(***),清空让后端重新生成
+        if (!form.value.id && form.value.quoteNo && form.value.quoteNo.includes('***')) {
+          form.value.quoteNo = '';
+          form.value.quoteTitle = ''; // 清空报价主题,让后端重新生成
+        }
+
         if (form.value.id) {
           await updateQuote(form.value);
           proxy?.$modal.msgSuccess('修改成功');
@@ -1178,6 +1254,12 @@ const handleConfirmApproval = async () => {
       }));
     form.value.attachments = attachmentsWithCategory.length > 0 ? JSON.stringify(attachmentsWithCategory) : '';
 
+    // 新增时,如果报价单号包含预览标记(***),清空让后端重新生成
+    if (!form.value.id && form.value.quoteNo && form.value.quoteNo.includes('***')) {
+      form.value.quoteNo = '';
+      form.value.quoteTitle = ''; // 清空报价主题,让后端重新生成
+    }
+
     let quoteId = form.value.id;
 
     // 先保存