platform-assistant/apps/api/src/product/product.service.ts
Sisyphus 672a4b41b2 feat(F002-P0-A): 商品上架 - Mac 端 publish-to-direv API + web 按钮
- 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)
2026-06-24 12:20:22 +08:00

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 || [],
}
}
}