- 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
15 KiB
需求: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 实现)
平台助手端需要做的:
- 部署文档(此文档)给 MCP server 维护者
- 按 §3.3-3.5 改 Lead 模型 + crawler.service + publishToDirev
- LLM 提取商务要点(参考 §3.4)