- 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
360 lines
15 KiB
Markdown
360 lines
15 KiB
Markdown
# 需求: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<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`:
|
||
|
||
```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<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 行:
|
||
|
||
```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) |