# 需求:F002 MCP publish_rfq 第 2 轮字段补充(基于老 direv-publish 脚本对比) **提出方**:平台助手(nest-api) **接收方**:devbox 产品网络(renren-supplier-server + mcp-server) **优先级**:P1 **时间**:2026-06-22 ## 一、背景 W29.7 已加 11 个商务字段,但跟老 GUI 流程(`direv-publish` skill)对比,仍缺关键信息,导致发布的询价前端展示质量低。 参考老 GUI 脚本: - `/Users/zou/Downloads/巨变自动招标公告发布/direv-publish/batch_publish_v4.py`(带备注 + PDF 上传的最新版) - `/Users/zou/Downloads/巨变自动招标公告发布/direv-publish/extract_notes.py`(PDF → 6 条要点提取) - `/Users/zou/Downloads/巨变自动招标公告发布/direv-publish/SKILL.md`(15 字段表单映射) ## 二、老 GUI 表单 vs 当前 MCP 字段对比 ### 2.1 老 GUI 必填字段(SKILL.md §表单字段映射) | 序号 | direv 表单字段 | 来源 | 当前 MCP 状态 | |------|--------------|------|--------------| | 1 | 询价单标题 | `lead.title` | ✅ title | | 2 | 询价主体 | 固定:郑州巨变网络科技有限公司 | ✅ inquiryParty | | 3 | 询价截止日期 | `lead.deadline` | ✅ deadline | | 4 | 采购类型 | 固定:定量采购 | ✅ procurementType | | 5 | 采购联系人 | 固定:郑钊 | ⚠️ 平台助手未传 | | 6 | 联系方式 | 固定:15532027557 | ⚠️ 平台助手未传 | | 7 | 报价要求 | 固定:报价包含运费 | ✅ freightFlag | | 8 | 付款方式 | 固定:货到付款 | ✅ paymentMethod | | 9 | 要求交货日期 | `lead.delivery` | ✅ deliveryDate | | 10 | 开票要求 | 固定:增值税专票 | ✅ invoiceType | | 11 | 收货地址 | `lead.region` / `regionDetail` | ✅ shippingAddress | | 12 | 商品名称 | `lead.category` | ❌ 完全缺 | | 13 | 规格型号 | `lead.spec`(可选) | ❌ 完全缺 | | 14 | 单位 | `lead.unit` | ❌ 完全缺 | | 15 | 数量 | `lead.quantity` | ✅ qty | | 16 | 报价方式 | 固定:公开报价 | ⚠️ hardcode quotationType=1(接口未暴露) | | 17 | **备注** | **DeepSeek 提取的 6 条要点** | ❌ **完全缺,最关键** | | 18 | **PDF 上传** | **公告 PDF** | ❌ 完全缺 | | 提交 | 发起询价 | — | ✅ | ### 2.2 关键缺漏(按影响优先级) #### ❌ 缺漏 1:**备注(note 字段)** —— 最严重 - **老 GUI**:从 PDF/HTML 用 DeepSeek 提取 6 条要点(项目背景/付款/交付/包装/特殊要求/保证金),填到 tb_inquiry.note - **当前 MCP**:description 字段接的是 `lead.summary`,但 `lead.summary` 实际只是 `"来源: 电建\n公告ID: xxx\n链接: xxx"`(元数据,不是商务要点) - **结果**:tb_inquiry.note 里只是 "来源: 电建...",前端招采广场显示无商务信息 - **影响**:买家看不到付款条件、交付要求、保证金等关键信息,无法判断是否投标 #### ❌ 缺漏 2:商品明细(category/spec/unit) - **老 GUI**:商品名称 + 规格型号 + 单位 + 数量 是询价单的核心,单独一行展示 - **当前 MCP**:只有 qty(数量),category/spec/unit 都没字段 - **tb_inquiry 表**:没有 category/spec/unit 字段(这些在 tb_inquiry_detail 表,单询价单 - 多商品模型) - **影响**:发布的询价没有商品分类信息,买家不知道买的什么 #### ⚠️ 缺漏 3:enterpriseId / userId 平台助手未传 - **当前**:publishToDirev 函数 hardcode 不传 enterpriseId,导致 paymentMethod 字典查询不到 - **结果**:tb_inquiry.payment_method = NULL(虽然 schema 加了字段但实际没填) - **影响**:前端询价详情看不到付款方式("货到付款" 等) #### ⚠️ 缺漏 4:contactName/contactPhone 平台助手偶尔不传 - **当前**:publishToDirev 函数只在 lead.contact/lead.phone 有值时才传 - **问题**:老 GUI 固定传"郑钊/15532027557",MCP 经常 NULL - **影响**:前端询价详情看不到联系人 #### ❌ 缺漏 5:PDF 上传 - **老 GUI**:电建公告 PDF 上传到备注区 - **当前 MCP**:attachments 只支持 URL 列表,不支持二进制上传 - **影响**:买家看不到原 PDF 公告(但 description 里有原始链接,可点) ### 2.3 tb_inquiry 表字段映射(devbox 已有) | 表字段 | 老 GUI | 当前 MCP | 备注 | |--------|--------|----------|------| | title | ✓ | ✓ title | | | note | ✓ 备注 | ❌ 没暴露 | **5000 字容量,老 GUI 填 6 条要点** | | inquiry_party | ✓ 固定 | ✓ inquiryParty | | | contact_name | ✓ 固定 | ⚠️ 平台助手偶尔不传 | | | contact_phone | ✓ 固定 | ⚠️ 平台助手偶尔不传 | | | shipping_address | ✓ region | ✓ shippingAddress | | | inquiry_valid_date | ✓ deadline | ✓ deadline | | | deliver_date | ✓ delivery (auto+30) | ✓ deliveryDate | | | purchase_quantity | ✓ quantity | ✓ qty | | | urls | ✓ PDF/HTML URL | ✓ attachments | | | image_names | ✓ 封面图 | ❌ MCP 不支持图片生成 | | | procurement_type | ✓ 定量 | ✓ procurementType | | | freight_flag | ✓ 含运费 | ✓ freightFlag | | | invoice_type | ✓ 增值税专票 | ✓ invoiceType | | | payment_method | ✓ 货到付款 | ⚠️ 平台助手未传 enterpriseId 字典查不到 | | | quotation_demand | ✓ 允许全部 | ❌ 没暴露(hardcode 2) | | | quotation_type | ✓ 公开报价 | ❌ 没暴露(hardcode 1) | | | appr_status | — 免审核 | ✅ 默认 2 (W29.8) | | | name / mobile | — | ❌ 不在本轮范围 | 老需求文档 §五 | | province_id / city_id / area_id | ✓ 地址编码 | ❌ MCP 完全没字段 | 老 GUI 用前端 JS 选 | ## 三、需求 ### 3.1 【P0】MCP `publish_rfq` schema 加 2 字段 在 `product_network_mcp/src/tools/inquiry.ts` 的 `publish_rfq` zod schema 里增加: ```typescript { // ===== W29.7 已有字段 ===== // ... (保持不变) // ===== W29.10 新增字段 ===== /** 备注:从公告 PDF/HTML 用 LLM 提取的 6 条商务要点 * 例: "- 项目背景: ...\n- 付款周期: ...\n- 交付要求: ...\n- 包装要求: ...\n- 特殊技术要求: ...\n- 保证金: ..." */ notes: z.string().optional().describe("备注 (LLM 提取的 6 条商务要点摘要, 填到 tb_inquiry.note)"), /** 询价类型 (1=公开/2=邀请), 老 GUI 固定传 1 公开报价 */ quotationType: z.number().int().min(1).max(2).optional().describe("报价方式: 1=公开报价 2=邀请报价 (默认 1)"), /** 报价要求 (1=必须全部/2=允许全部), 老 GUI 固定传 2 */ quotationDemand: z.number().int().min(1).max(2).optional().describe("报价要求: 1=必须全部报价 2=允许全部报价 (默认 2)"), /** 询价单位 (id), 来自 sys_user 表里 type=99 的公司账号 */ userId: z.number().optional().describe("代发用户 ID (F002 公司账号对应 sys_user.id, 可选)"), // 已有但平台助手没传 — 强提醒 platform-assistant 必传 } ``` ### 3.2 【P0】Java 后端 `InquiryServiceImpl.publish()` 升级 `renren-supplier-server/src/main/java/io/renren/service/impl/InquiryServiceImpl.java` **新增字段映射**: ```java // 备注 (W29.10) - 优先级高于 description String notes = (String) params.get("notes"); if (StrUtil.isNotBlank(notes)) { inquiryEntity.setNote(notes); } else { inquiryEntity.setNote((String) params.get("description")); } // 报价方式 (1=公开/2=邀请) Object quotationTypeObj = params.get("quotationType"); if (quotationTypeObj != null) { inquiryEntity.setQuotationType(Integer.valueOf(quotationTypeObj.toString())); } // 报价要求 (1=必须全部/2=允许全部) Object quotationDemandObj = params.get("quotationDemand"); if (quotationDemandObj != null) { inquiryEntity.setQuotationDemand(quotationDemandObj.toString()); } else { inquiryEntity.setQuotationDemand("2"); // 默认允许全部 } // userId - 从 customerCode 关联的 sys_user 表查 (type=99 公司账号) Long userId = null; Object userIdObj = params.get("userId"); if (userIdObj != null) { userId = Long.valueOf(userIdObj.toString()); } if (userId == null && inquiryEntity.getInquiryEnterpriseId() != null) { // 自动查 sys_user 表, 取 enterprise_id 对应的公司账号 (type=99) userId = findCompanyUserId(inquiryEntity.getInquiryEnterpriseId()); } if (userId != null) { inquiryEntity.setUserId(userId); } ``` **新增辅助方法 `findCompanyUserId(enterpriseId)`**: ```java /** * 根据企业 ID 查询公司账号用户 ID (sys_user 表 type=99) * 这是 F002 公司账号代发模式 */ private Long findCompanyUserId(Long enterpriseId) { try { List users = sysUserService.list( Wrappers.lambdaQuery() .eq(SysUserEntity::getEnterpriseId, enterpriseId) .eq(SysUserEntity::getUserType, 99) // 99=公司账号 .last("LIMIT 1") ); if (!users.isEmpty()) { return users.get(0).getId(); } } catch (Exception e) { // log } return null; } ``` ### 3.3 【P1】平台助手 Lead 模型加 `notes` 字段(LLM 提取结果) `apps/api/prisma/schema.prisma`: ```prisma model Lead { // ... 现有字段 /// 备注:DeepSeek 从公告 PDF/HTML 提取的 6 条商务要点 /// 格式: "- 项目背景: ...\n- 付款周期: ...\n- 交付要求: ...\n- 包装要求: ...\n- 特殊技术要求: ...\n- 保证金: ..." notes String? } ``` 执行 `npx prisma db push` 迁移(SQLite,加列无破坏)。 ### 3.4 【P1】平台助手 `crawler.service.ts` 抓取时 LLM 提取 在抓取公告保存到 Lead 时,如果附件是 PDF 或 HTML,用 DeepSeek 提取商务要点填到 `lead.notes`: ```typescript // 现有抓取逻辑 (line 130-148) const lead = await this.prisma.lead.create({ data: { title: r.title, summary: `来源: ${r.source}\n公告ID: ${r.noticeId}\n链接: ${r.detailUrl}`, // ===== 新增 ===== notes: await this.extractBusinessNotes(r.detailUrl, r.source), // ===== 现有其他字段 ===== ... } } /** * 提取商务要点(参考 direv-publish/extract_notes.py) */ private async extractBusinessNotes(detailUrl: string, source: string): Promise { try { const content = await this.downloadAndParse(detailUrl, source); if (!content || content.length < 200) return null; // 调用 DeepSeek (复用 auth-profiles.json 配置) const deepseekKey = this.getLLMKey('deepseek'); if (!deepseekKey) return null; const prompt = `你是招标公告信息提取助手。请从以下公告内容提取关键商业要求,整理成简明要点。 排除项(不要出现):公司简介、联系人姓名/电话/邮箱、地址信息、招标人联系方式 保留项(有则提取):项目背景、付款周期/方式/条件、交付周期/时间、包装要求、快递/运输费用、特殊技术要求、资质要求、保证金要求、其他特殊要求 输出格式:纯要点,每条一行以 "- " 开头,最多 6 条,每条 20 字以内。不要标题、分类头、解释。 公告内容: ${content.slice(0, 4000)}`; const res = await axios.post('https://api.deepseek.com/v1/chat/completions', { model: 'deepseek-chat', messages: [ { role: 'system', content: '你是招标公告信息提取助手。' }, { role: 'user', content: prompt } ], temperature: 0.1, max_tokens: 500 }, { headers: { 'Authorization': `Bearer ${deepseekKey}` } }); return res.data.choices?.[0]?.message?.content?.trim() || null; } catch (e) { this.logger.warn(`提取商务要点失败: ${e.message}`); return null; } } ``` ### 3.5 【P1】平台助手 `publishToDirev()` 改:传 `notes` + `enterpriseId` `apps/api/src/lead/lead.service.ts` 第 215-290 行: ```typescript // 改 1: 优先用 lead.notes (W29.10 新字段) publishArgs.notes = lead.notes || lead.summary || lead.title || ''; // 改 2: 必传 enterpriseId (MCP 才能查字典) if (lead.organization) { // lead.organization 可能存的是企业 ID 或名称 // 如果是 ID 直接用, 是名称需要查表 publishArgs.enterpriseId = await this.getEnterpriseIdByName(lead.organization); } // 改 3: 必传 userId (用默认公司账号) publishArgs.userId = await this.getDefaultCompanyUserId(publishArgs.enterpriseId); ``` ### 3.6 【P2】前端 `AdminPage.tsx` 表单字段补全 如果 ConfigTab MCP 对接配置需要让用户填写 enterpriseId/userId,补到表单默认值。但 W29.7 已经加了这些字段,前端也支持输入,主要是平台助手 publish 链路默认要传。 ## 四、验收标准 升级后,平台助手发布一条电建/新矿询价,在 B2B 平台 PC 招采广场应能看到以下字段都正确填充: | 字段 | 期望 | 当前 | |------|------|------| | ✅ 标题 | 项目名 | OK | | ✅ 询价主体 | 郑州巨变网络科技有限公司 | OK | | ✅ 询价截止日期 | YYYY-MM-DD | OK | | ✅ 要求交货日期 | YYYY-MM-DD | OK | | ✅ 收货地址 | 详细地址 | OK | | ✅ 联系人/电话 | 郑钊 / 15532027557 | **部分 OK** | | ✅ 付款方式 | 货到付款 (dict id) | **缺失(无 enterpriseId)** | | ✅ 发票类型 | 增值税专票 | OK | | ✅ 运费 | 含运费 | OK | | 🆕 **备注** | **6 条商务要点摘要** | **缺失(只有元数据)** | | 🆕 **商品分类/规格/单位** | **品类 / 规格 / 件** | **完全缺失** | | ✅ 询价状态 | 长期有效 / 进行中 | OK (W29.8) | ## 五、不在本轮范围 - ❌ PDF 二进制文件上传(老 GUI 用 Playwright 上传,MCP 协议层支持有限,保留 attachments URL) - ❌ 商品封面图 AI 生成(MiniMax image-01,MCP 无对应机制) - ❌ 多商品(tb_inquiry_detail 表 1:N,老 GUI 单条 publish 也只发 1 商品) - ❌ 省市区编码(province_id/city_id/area_id,老 GUI 用前端 JS 选,区域单独字段) - ❌ 询价人姓名/电话(name/mobile,老需求文档 §五 不在范围) ## 六、测试用例 升级后请用以下 payload 测试: ```json { "title": "九峰山抽水蓄能电站工字钢槽钢镀锌扁铁采购", "notes": "- 项目背景: 九峰山抽水蓄能电站\n- 付款: 货到付款\n- 交付: 30 天\n- 包装: 标准捆扎\n- 资质: 钢材生产许可证\n- 保证金: 5%", "qty": 335, "deadline": "2026-05-26", "deliveryDate": "2026-06-25", "shippingAddress": "河南省新乡市辉县市九峰山抽水蓄能电站", "contactName": "郑钊", "contactPhone": "15532027557", "freightFlag": "含运费", "paymentMethod": "货到付款", "invoiceType": "增值税专票", "procurementType": "定量采购", "inquiryParty": "郑州巨变网络科技有限公司", "userId": 1067246875800000001, "enterpriseId": 23, "enterpriseName": "郑州巨变网络科技有限公司", "attachments": ["https://example.com/notice.pdf"], "quotationType": 1, "quotationDemand": 2 } ``` 调 `list_my_rfqs` 验证上述字段(特别是 `notes` / `paymentMethod` / `quotationType` / `quotationDemand`)都被持久化。 ## 七、devbox 已就绪(待平台助手升级) - ✅ `notes` 字段 MCP schema 已加(待 §3.1) - ✅ `quotationType` / `quotationDemand` 字段 MCP schema 已加 - ✅ `userId` 字段已存在 - ✅ `enterpriseId` 自动查字典(待 §3.2 findCompanyUserId 实现) 平台助手端需要做的: 1. 部署文档(此文档)给 MCP server 维护者 2. 按 §3.3-3.5 改 Lead 模型 + crawler.service + publishToDirev 3. LLM 提取商务要点(参考 §3.4)