diff --git a/apps/api/src/product/product.controller.ts b/apps/api/src/product/product.controller.ts index c70d17a..e445c8a 100644 --- a/apps/api/src/product/product.controller.ts +++ b/apps/api/src/product/product.controller.ts @@ -40,6 +40,14 @@ export class ProductController { return this.productService.batchPublish(body.ids) } + /** + * F002 P0-A: 批量上架商品到 B2B 平台 (MCP import_products) + */ + @Post('publish-to-direv') + publishToDirev(@Body() body: { ids: string[] }) { + return this.productService.publishToDirev(body.ids) + } + @Delete(':id') remove(@Param('id') id: string) { return this.productService.remove(id) diff --git a/apps/api/src/product/product.module.ts b/apps/api/src/product/product.module.ts index 1446bd5..2feff7b 100644 --- a/apps/api/src/product/product.module.ts +++ b/apps/api/src/product/product.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common' import { ProductController } from './product.controller' import { ProductService } from './product.service' +import { IntegrationModule } from '../integration/integration.module' @Module({ + imports: [IntegrationModule], controllers: [ProductController], providers: [ProductService], exports: [ProductService], diff --git a/apps/api/src/product/product.service.ts b/apps/api/src/product/product.service.ts index e312604..addda0d 100644 --- a/apps/api/src/product/product.service.ts +++ b/apps/api/src/product/product.service.ts @@ -1,9 +1,15 @@ -import { Injectable } from '@nestjs/common' +import { Injectable, Logger, ServiceUnavailableException } from '@nestjs/common' import { PrismaService } from '../prisma/prisma.service' +import { IntegrationService } from '../integration/integration.service' @Injectable() export class ProductService { - constructor(private prisma: PrismaService) {} + private readonly logger = new Logger(ProductService.name) + + constructor( + private prisma: PrismaService, + private integrationService: IntegrationService, + ) {} async findAll() { return this.prisma.product.findMany({ orderBy: { createdAt: 'desc' } }) @@ -45,4 +51,81 @@ export class ProductService { async remove(id: string) { return this.prisma.product.delete({ where: { id } }) } + + /** + * F002 P0-A: 批量上架商品到 B2B 平台 (MCP import_products) + * 字段映射: Product → import_products schema + */ + async publishToDirev(ids: string[]) { + const b2bIntegration = await this.integrationService.findActiveMCP() + if (!b2bIntegration) { + throw new ServiceUnavailableException('未配置 B2B 集成') + } + + const products = await this.prisma.product.findMany({ + where: { id: { in: ids } }, + }) + + if (products.length === 0) { + throw new ServiceUnavailableException('未找到指定商品') + } + + // Category string → numeric ID mapping (B2B tb_good first_category_id) + const CATEGORY_MAP: Record = { + '电缆': 28, + '电力电缆': 28, + '配电箱': 29, + '开关柜': 29, + '变压器': 27, + '电线': 28, + '母线槽': 30, + '桥架': 31, + } + const fallbackCategoryId = 1 + + const items = products.map(p => { + const catId = CATEGORY_MAP[p.category] || fallbackCategoryId + const item: any = { + name: p.name, + category_id: catId, + price: p.price, + brandName: p.brand || '', + stock: p.quantity, + } + if (p.spec) item.sku = p.spec + if (p.imageUrl) item.images = [p.imageUrl] + if (p.supplier) item.remark = `供应商:${p.supplier}` + return item + }) + + const result = await this.integrationService.executeTool( + b2bIntegration.id, + 'import_products', + { products: items }, + { retries: 2, retryDelayMs: 1000 }, + ) + + if (!result.success) { + const errorMsg = result.error || '未知错误' + this.logger.error(`商品上架失败 (ids=${ids.join(',')}): ${errorMsg}`) + throw new ServiceUnavailableException(`上架失败: ${errorMsg}`) + } + + const data = result.data as any + const payload = data.data || data + const imported = payload.imported || 0 + const errors = payload.errors || [] + this.logger.log(`商品上架成功: 导入 ${imported} 条, 失败 ${errors.length} 条`) + + await this.prisma.product.updateMany({ + where: { id: { in: products.map(p => p.id) } }, + data: { status: 'published' }, + }) + + return { + imported, + errors, + ids: payload.ids || [], + } + } } diff --git a/apps/web/src/components/ProductPage.tsx b/apps/web/src/components/ProductPage.tsx index e83a597..4cef58d 100644 --- a/apps/web/src/components/ProductPage.tsx +++ b/apps/web/src/components/ProductPage.tsx @@ -182,6 +182,25 @@ export default function ProductPage() { showToast('批量同步完成') } + const batchPublishToDirev = async () => { + if (selectedIds.size === 0) return + showToast(`正在上架 ${selectedIds.size} 个商品到 B2B 平台...`) + try { + const res = await fetch(`${API}/publish-to-direv`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ids: Array.from(selectedIds) }), + }) + const data = await res.json() + if (!res.ok) throw new Error(data.message || data.error || '请求失败') + setSelectedIds(new Set()) + await loadProducts() + showToast(`上架成功: ${data.imported || 0} 条${data.errors?.length ? `, 失败 ${data.errors.length} 条` : ''}`) + } catch (e: any) { + showToast(`上架失败: ${e.message}`) + } + } + const removeProduct = async (id: string) => { await fetch(`${API}/${id}`, { method: 'DELETE' }) showToast('已删除') @@ -451,7 +470,12 @@ export default function ProductPage() { +