- product.service.ts: 新增 publishToDirev() 调 MCP import_products - product.controller.ts: 新增 POST /api/products/publish-to-direv - product.module.ts: 引入 IntegrationModule - ProductPage.tsx: 工具栏加 '上架 B2B' 按钮 (绿色) - 字段映射: Product(category/price/brand) → MCP(category_id/price/brandName)
132 lines
3.5 KiB
TypeScript
132 lines
3.5 KiB
TypeScript
import { Injectable, Logger, ServiceUnavailableException } from '@nestjs/common'
|
|
import { PrismaService } from '../prisma/prisma.service'
|
|
import { IntegrationService } from '../integration/integration.service'
|
|
|
|
@Injectable()
|
|
export class ProductService {
|
|
private readonly logger = new Logger(ProductService.name)
|
|
|
|
constructor(
|
|
private prisma: PrismaService,
|
|
private integrationService: IntegrationService,
|
|
) {}
|
|
|
|
async findAll() {
|
|
return this.prisma.product.findMany({ orderBy: { createdAt: 'desc' } })
|
|
}
|
|
|
|
async findOne(id: string) {
|
|
return this.prisma.product.findUnique({ where: { id } })
|
|
}
|
|
|
|
async create(data: {
|
|
name: string
|
|
spec: string
|
|
unit: string
|
|
price: number
|
|
quantity: number
|
|
category: string
|
|
}) {
|
|
return this.prisma.product.create({ data })
|
|
}
|
|
|
|
async update(id: string, data: any) {
|
|
return this.prisma.product.update({ where: { id }, data })
|
|
}
|
|
|
|
async publish(id: string) {
|
|
return this.prisma.product.update({
|
|
where: { id },
|
|
data: { status: 'published' },
|
|
})
|
|
}
|
|
|
|
async batchPublish(ids: string[]) {
|
|
return this.prisma.product.updateMany({
|
|
where: { id: { in: ids } },
|
|
data: { status: 'published' },
|
|
})
|
|
}
|
|
|
|
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<string, number> = {
|
|
'电缆': 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<any>(
|
|
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 || [],
|
|
}
|
|
}
|
|
}
|