platform-assistant/docs/devbox-requirements/MCP-publish_rfq-字段补充需求.md
Sisyphus afac18e843 feat(F002): W26 + W29.x + W30 + W31 平台助手全部改动首次 commit
- W26: NestJS API + Next.js Web + Prisma + SQLite scaffold
- W26: Lead 模型 + Crawler + 询价发布助手 (线索池 + 一键发布)
- W29.x: MCP 集成 (Integration 模型 + connectMcp + publish_rfq 5 工具端到端跑通)
- W30: 3 字段补充 (notes / quotationType / quotationDemand)
- W31 P1: 自动填 enterpriseId / userId (Prisma companyUserId 字段 + get_app_identity tool + lead.service.ts publishArgs)
- W31: detail 详情页 + admin UI 集成管理 (OpenAppListView + AuditLogListView)

关联: ~/Documents/02-平台开发/产品开发/F002-W31-交接文档.md
GIT-AUDIT-REPORT-2026-06-24.md
2026-06-24 11:07:11 +08:00

15 KiB
Raw Blame History

需求: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 里增加:

{
  // ===== 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

新增字段映射:

// 备注 (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):

/**
 * 根据企业 ID 查询公司账号用户 ID (sys_user 表 type=99)
 * 这是 F002 公司账号代发模式
 */
private Long findCompanyUserId(Long enterpriseId) {
    try {
        List<SysUserEntity> users = sysUserService.list(
            Wrappers.<SysUserEntity>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:

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:

// 现有抓取逻辑 (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<string | null> {
  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 行:

// 改 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 测试:

{
  "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)