商品同步
This commit is contained in:
parent
c29ee4d0c3
commit
41f3eb5adf
|
|
@ -89,7 +89,7 @@ function mergeProductData(existingProduct: any, newData: any, forceUpdate: boole
|
|||
mergedData.seedId = newData.seedId
|
||||
}
|
||||
|
||||
// 只在字段为空时更新
|
||||
// 只在字段为空时更新基础字段
|
||||
if (!existingProduct.title) {
|
||||
mergedData.title = newData.title
|
||||
}
|
||||
|
|
@ -103,6 +103,23 @@ function mergeProductData(existingProduct: any, newData: any, forceUpdate: boole
|
|||
mergedData.status = newData.status
|
||||
}
|
||||
|
||||
// Medusa 属性字段:总是更新(以 Medusa 为准)
|
||||
mergedData.tags = newData.tags
|
||||
mergedData.type = newData.type
|
||||
mergedData.collection = newData.collection
|
||||
mergedData.category = newData.category
|
||||
|
||||
// 物理属性:总是更新
|
||||
mergedData.height = newData.height
|
||||
mergedData.width = newData.width
|
||||
mergedData.length = newData.length
|
||||
mergedData.weight = newData.weight
|
||||
|
||||
// 海关与物流:总是更新
|
||||
mergedData.midCode = newData.midCode
|
||||
mergedData.hsCode = newData.hsCode
|
||||
mergedData.countryOfOrigin = newData.countryOfOrigin
|
||||
|
||||
return mergedData
|
||||
}
|
||||
|
||||
|
|
@ -241,25 +258,12 @@ async function syncSingleProductByMedusaId(
|
|||
|
||||
// 如果存在且不强制更新,只更新空字段
|
||||
if (!forceUpdate) {
|
||||
console.log(`[Sync API] 🔄 模式: 只填充空字段`)
|
||||
// 合并数据(只曹新空字段)
|
||||
console.log(`[Sync API] 🔄 模式: 只填充空字段,但 Medusa 属性总是更新`)
|
||||
// 合并数据(只更新空字段,但 Medusa 属性总是更新)
|
||||
const mergedData = mergeProductData(existingProduct, productData, false)
|
||||
|
||||
// 如果没有需要更新的字段,跳过
|
||||
if (Object.keys(mergedData).length <= 2) {
|
||||
// 只有 lastSyncedAt 和 medusaId
|
||||
console.log(`[Sync API] ⏭️ 跳过: 所有字段都有值`)
|
||||
return {
|
||||
success: true,
|
||||
action: 'skipped',
|
||||
message: `商品 ${medusaId} 已存在于 ${targetCollection},且所有字段都有值`,
|
||||
productId: existingProduct.id,
|
||||
collection: targetCollection,
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Sync API] 📝 更新字段: ${Object.keys(mergedData).join(', ')}`)
|
||||
// 更新(只更新空字段)
|
||||
console.log(`[Sync API] 📝 更新字段(含 Medusa 属性): ${Object.keys(mergedData).join(', ')}`)
|
||||
// 更新(只更新空字段 + Medusa 属性)
|
||||
const updated = await payload.update({
|
||||
collection: targetCollection,
|
||||
id: existingProduct.id,
|
||||
|
|
@ -395,25 +399,11 @@ async function syncAllProducts(payload: any, forceUpdate: boolean) {
|
|||
if (found) {
|
||||
const existingProduct = found.product
|
||||
|
||||
// 如果不强制更新,只更新空字段
|
||||
// 如果不强制更新,只更新空字段,但 Medusa 属性总是更新
|
||||
if (!forceUpdate) {
|
||||
const mergedData = mergeProductData(existingProduct, productData, false)
|
||||
|
||||
// 如果没有需要更新的字段,跳过
|
||||
if (Object.keys(mergedData).length <= 2) {
|
||||
// 只有 lastSyncedAt 和 medusaId
|
||||
results.skipped++
|
||||
results.details.push({
|
||||
medusaId: medusaProduct.id,
|
||||
seedId: seedId,
|
||||
title: medusaProduct.title,
|
||||
action: 'skipped',
|
||||
collection: targetCollection,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 更新(只更新空字段)
|
||||
// 更新(只更新空字段 + Medusa 属性)
|
||||
await payload.update({
|
||||
collection: targetCollection,
|
||||
id: existingProduct.id,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export async function POST(request: Request) {
|
|||
}
|
||||
}
|
||||
|
||||
let action: 'created' | 'updated' | 'updated_partial' | 'moved' | 'skipped' = 'created'
|
||||
let action: 'created' | 'updated' | 'updated_partial' | 'moved' = 'created'
|
||||
let finalProduct: any
|
||||
|
||||
// 如果在错误的 collection 中,需要移动
|
||||
|
|
@ -134,12 +134,13 @@ export async function POST(request: Request) {
|
|||
// 如果找到了并且在正确的 collection 中
|
||||
else if (existingProduct) {
|
||||
if (!forceUpdate) {
|
||||
// 只更新空字段
|
||||
// 只更新空字段,但 Medusa 属性字段总是更新
|
||||
const mergedData: any = {
|
||||
lastSyncedAt: productData.lastSyncedAt,
|
||||
medusaId: productData.medusaId,
|
||||
}
|
||||
|
||||
// 基础字段:只更新空字段
|
||||
if (!existingProduct.seedId && productData.seedId) {
|
||||
mergedData.seedId = productData.seedId
|
||||
}
|
||||
|
|
@ -148,20 +149,30 @@ export async function POST(request: Request) {
|
|||
if (!existingProduct.thumbnail) mergedData.thumbnail = productData.thumbnail
|
||||
if (!existingProduct.status) mergedData.status = productData.status
|
||||
|
||||
// 如果没有需要更新的字段,跳过
|
||||
if (Object.keys(mergedData).length <= 2) {
|
||||
console.log(`[Sync Product API] ⏭️ 跳过: 所有字段都有值`)
|
||||
action = 'skipped'
|
||||
finalProduct = existingProduct
|
||||
} else {
|
||||
console.log(`[Sync Product API] 📝 更新字段: ${Object.keys(mergedData).join(', ')}`)
|
||||
finalProduct = await payload.update({
|
||||
collection: targetCollection,
|
||||
id: existingProduct.id,
|
||||
data: mergedData,
|
||||
})
|
||||
action = 'updated_partial'
|
||||
}
|
||||
// Medusa 属性字段:总是更新(以 Medusa 为准)
|
||||
mergedData.tags = productData.tags
|
||||
mergedData.type = productData.type
|
||||
mergedData.collection = productData.collection
|
||||
mergedData.category = productData.category
|
||||
|
||||
// 物理属性:总是更新
|
||||
mergedData.height = productData.height
|
||||
mergedData.width = productData.width
|
||||
mergedData.length = productData.length
|
||||
mergedData.weight = productData.weight
|
||||
|
||||
// 海关与物流:总是更新
|
||||
mergedData.midCode = productData.midCode
|
||||
mergedData.hsCode = productData.hsCode
|
||||
mergedData.countryOfOrigin = productData.countryOfOrigin
|
||||
|
||||
console.log(`[Sync Product API] 📝 更新字段(含 Medusa 属性): ${Object.keys(mergedData).join(', ')}`)
|
||||
finalProduct = await payload.update({
|
||||
collection: targetCollection,
|
||||
id: existingProduct.id,
|
||||
data: mergedData,
|
||||
})
|
||||
action = 'updated_partial'
|
||||
} else {
|
||||
// 强制更新所有字段
|
||||
console.log(`[Sync Product API] ⚡ 强制更新所有字段`)
|
||||
|
|
@ -207,7 +218,7 @@ export async function POST(request: Request) {
|
|||
fakeOrderCount: finalProduct.fakeOrderCount,
|
||||
}),
|
||||
},
|
||||
message: `产品已${action === 'created' ? '创建' : action === 'moved' ? '移动' : action === 'skipped' ? '跳过' : '更新'}于 ${targetCollection}`,
|
||||
message: `产品已${action === 'created' ? '创建' : action === 'moved' ? '移动' : '更新'}于 ${targetCollection}`,
|
||||
})
|
||||
|
||||
return addCorsHeaders(response, origin)
|
||||
|
|
|
|||
|
|
@ -92,43 +92,48 @@ export const ProductBaseFields: Field[] = [
|
|||
/**
|
||||
* Medusa 默认属性字段
|
||||
* 对应 Medusa 中的标签、类型、系列、分类、物理属性等
|
||||
* 这些字段从 Medusa 同步,在 Payload 中为只读
|
||||
*/
|
||||
export const MedusaAttributesFields: Field[] = [
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '产品标签(逗号分隔)',
|
||||
description: '产品标签(逗号分隔,从 Medusa 同步)',
|
||||
placeholder: '例如: 热门, 新品, 限量',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '产品类型',
|
||||
description: '产品类型(从 Medusa 同步)',
|
||||
placeholder: '例如: 外壳, PCB, 工具',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'collection',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '产品系列',
|
||||
description: '产品系列(从 Medusa 同步)',
|
||||
placeholder: '例如: Shell, Cartridge',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '产品分类',
|
||||
description: '产品分类(从 Medusa 同步)',
|
||||
placeholder: '例如: GBA, GBC, GB',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: '物理属性',
|
||||
label: '物理属性(只读)',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
|
|
@ -137,32 +142,36 @@ export const MedusaAttributesFields: Field[] = [
|
|||
name: 'height',
|
||||
type: 'number',
|
||||
admin: {
|
||||
description: '高度 (cm)',
|
||||
description: '高度 (cm),从 Medusa 同步',
|
||||
width: '25%',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
admin: {
|
||||
description: '宽度 (cm)',
|
||||
description: '宽度 (cm),从 Medusa 同步',
|
||||
width: '25%',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'length',
|
||||
type: 'number',
|
||||
admin: {
|
||||
description: '长度 (cm)',
|
||||
description: '长度 (cm),从 Medusa 同步',
|
||||
width: '25%',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'weight',
|
||||
type: 'number',
|
||||
admin: {
|
||||
description: '重量 (g)',
|
||||
description: '重量 (g),从 Medusa 同步',
|
||||
width: '25%',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -171,7 +180,7 @@ export const MedusaAttributesFields: Field[] = [
|
|||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: '海关与物流',
|
||||
label: '海关与物流(只读)',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
|
|
@ -180,27 +189,30 @@ export const MedusaAttributesFields: Field[] = [
|
|||
name: 'midCode',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: 'MID 代码(制造商识别码)',
|
||||
description: 'MID 代码(制造商识别码,从 Medusa 同步)',
|
||||
placeholder: '例如: 1234567890',
|
||||
width: '33%',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'hsCode',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: 'HS 代码(海关编码)',
|
||||
description: 'HS 代码(海关编码,从 Medusa 同步)',
|
||||
placeholder: '例如: 8523.49.00',
|
||||
width: '33%',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'countryOfOrigin',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '原产国',
|
||||
description: '原产国(从 Medusa 同步)',
|
||||
placeholder: '例如: CN, US, JP',
|
||||
width: '34%',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import { NextResponse } from 'next/server'
|
|||
*/
|
||||
const ALLOWED_ORIGINS = [
|
||||
'http://localhost:9000', // Medusa 开发服务器
|
||||
'http://localhost:7001', // Medusa Admin 默认端口
|
||||
'http://localhost:7000', // Storefront 默认 端口
|
||||
'http://localhost:8000', // Storefront 默认 端口
|
||||
process.env.MEDUSA_URL,
|
||||
process.env.ADMIN_URL,
|
||||
].filter(Boolean) as string[]
|
||||
|
|
|
|||
|
|
@ -36,6 +36,31 @@ interface MedusaProduct {
|
|||
id: string
|
||||
value: string
|
||||
}>
|
||||
// 完整的关联对象
|
||||
collection?: {
|
||||
id: string
|
||||
title: string
|
||||
handle: string
|
||||
}
|
||||
type?: {
|
||||
id: string
|
||||
value: string
|
||||
}
|
||||
categories?: Array<{
|
||||
id: string
|
||||
name: string
|
||||
handle: string
|
||||
}>
|
||||
// 物理属性
|
||||
height?: number
|
||||
width?: number
|
||||
length?: number
|
||||
weight?: number
|
||||
// 海关与物流
|
||||
mid_code?: string
|
||||
hs_code?: string
|
||||
origin_country?: string
|
||||
// 兼容旧字段
|
||||
collection_id?: string
|
||||
type_id?: string
|
||||
}
|
||||
|
|
@ -214,6 +239,15 @@ export function transformMedusaProductToPayload(product: MedusaProduct) {
|
|||
// 提取 tags(逗号分隔)
|
||||
const tags = product.tags?.map(tag => tag.value).join(', ') || null
|
||||
|
||||
// 提取 type(优先使用 type.value,否则使用 type_id 或 metadata)
|
||||
const type = product.type?.value || product.type_id || product.metadata?.type || null
|
||||
|
||||
// 提取 collection(优先使用 collection.title,否则使用 collection_id 或 metadata)
|
||||
const collection = product.collection?.title || product.collection_id || product.metadata?.collection || null
|
||||
|
||||
// 提取 category(从 categories 数组或 metadata)
|
||||
const category = product.categories?.[0]?.name || product.metadata?.category || null
|
||||
|
||||
return {
|
||||
medusaId: product.id,
|
||||
seedId: product.metadata?.seed_id || null,
|
||||
|
|
@ -225,20 +259,20 @@ export function transformMedusaProductToPayload(product: MedusaProduct) {
|
|||
|
||||
// Medusa 默认属性
|
||||
tags: tags,
|
||||
type: product.type_id || null,
|
||||
collection: product.collection_id || null,
|
||||
category: product.metadata?.category || null,
|
||||
type: type,
|
||||
collection: collection,
|
||||
category: category,
|
||||
|
||||
// 物理属性(从 metadata 中提取)
|
||||
height: product.metadata?.height ? Number(product.metadata.height) : null,
|
||||
width: product.metadata?.width ? Number(product.metadata.width) : null,
|
||||
length: product.metadata?.length ? Number(product.metadata.length) : null,
|
||||
weight: product.metadata?.weight ? Number(product.metadata.weight) : null,
|
||||
// 物理属性(优先使用直接字段,否则从 metadata 中提取)
|
||||
height: product.height || (product.metadata?.height ? Number(product.metadata.height) : null),
|
||||
width: product.width || (product.metadata?.width ? Number(product.metadata.width) : null),
|
||||
length: product.length || (product.metadata?.length ? Number(product.metadata.length) : null),
|
||||
weight: product.weight || (product.metadata?.weight ? Number(product.metadata.weight) : null),
|
||||
|
||||
// 海关与物流
|
||||
midCode: product.metadata?.mid_code || product.metadata?.midCode || null,
|
||||
hsCode: product.metadata?.hs_code || product.metadata?.hsCode || null,
|
||||
countryOfOrigin: product.metadata?.country_of_origin || product.metadata?.countryOfOrigin || null,
|
||||
// 海关与物流(优先使用直接字段,否则从 metadata 中提取)
|
||||
midCode: product.mid_code || product.metadata?.mid_code || product.metadata?.midCode || null,
|
||||
hsCode: product.hs_code || product.metadata?.hs_code || product.metadata?.hsCode || null,
|
||||
countryOfOrigin: product.origin_country || product.metadata?.country_of_origin || product.metadata?.countryOfOrigin || null,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -255,47 +255,47 @@ export interface Product {
|
|||
)[]
|
||||
| null;
|
||||
/**
|
||||
* 产品标签(逗号分隔)
|
||||
* 产品标签(逗号分隔,从 Medusa 同步)
|
||||
*/
|
||||
tags?: string | null;
|
||||
/**
|
||||
* 产品类型
|
||||
* 产品类型(从 Medusa 同步)
|
||||
*/
|
||||
type?: string | null;
|
||||
/**
|
||||
* 产品系列
|
||||
* 产品系列(从 Medusa 同步)
|
||||
*/
|
||||
collection?: string | null;
|
||||
/**
|
||||
* 产品分类
|
||||
* 产品分类(从 Medusa 同步)
|
||||
*/
|
||||
category?: string | null;
|
||||
/**
|
||||
* 高度 (cm)
|
||||
* 高度 (cm),从 Medusa 同步
|
||||
*/
|
||||
height?: number | null;
|
||||
/**
|
||||
* 宽度 (cm)
|
||||
* 宽度 (cm),从 Medusa 同步
|
||||
*/
|
||||
width?: number | null;
|
||||
/**
|
||||
* 长度 (cm)
|
||||
* 长度 (cm),从 Medusa 同步
|
||||
*/
|
||||
length?: number | null;
|
||||
/**
|
||||
* 重量 (g)
|
||||
* 重量 (g),从 Medusa 同步
|
||||
*/
|
||||
weight?: number | null;
|
||||
/**
|
||||
* MID 代码(制造商识别码)
|
||||
* MID 代码(制造商识别码,从 Medusa 同步)
|
||||
*/
|
||||
midCode?: string | null;
|
||||
/**
|
||||
* HS 代码(海关编码)
|
||||
* HS 代码(海关编码,从 Medusa 同步)
|
||||
*/
|
||||
hsCode?: string | null;
|
||||
/**
|
||||
* 原产国
|
||||
* 原产国(从 Medusa 同步)
|
||||
*/
|
||||
countryOfOrigin?: string | null;
|
||||
/**
|
||||
|
|
@ -406,47 +406,47 @@ export interface PreorderProduct {
|
|||
)[]
|
||||
| null;
|
||||
/**
|
||||
* 产品标签(逗号分隔)
|
||||
* 产品标签(逗号分隔,从 Medusa 同步)
|
||||
*/
|
||||
tags?: string | null;
|
||||
/**
|
||||
* 产品类型
|
||||
* 产品类型(从 Medusa 同步)
|
||||
*/
|
||||
type?: string | null;
|
||||
/**
|
||||
* 产品系列
|
||||
* 产品系列(从 Medusa 同步)
|
||||
*/
|
||||
collection?: string | null;
|
||||
/**
|
||||
* 产品分类
|
||||
* 产品分类(从 Medusa 同步)
|
||||
*/
|
||||
category?: string | null;
|
||||
/**
|
||||
* 高度 (cm)
|
||||
* 高度 (cm),从 Medusa 同步
|
||||
*/
|
||||
height?: number | null;
|
||||
/**
|
||||
* 宽度 (cm)
|
||||
* 宽度 (cm),从 Medusa 同步
|
||||
*/
|
||||
width?: number | null;
|
||||
/**
|
||||
* 长度 (cm)
|
||||
* 长度 (cm),从 Medusa 同步
|
||||
*/
|
||||
length?: number | null;
|
||||
/**
|
||||
* 重量 (g)
|
||||
* 重量 (g),从 Medusa 同步
|
||||
*/
|
||||
weight?: number | null;
|
||||
/**
|
||||
* MID 代码(制造商识别码)
|
||||
* MID 代码(制造商识别码,从 Medusa 同步)
|
||||
*/
|
||||
midCode?: string | null;
|
||||
/**
|
||||
* HS 代码(海关编码)
|
||||
* HS 代码(海关编码,从 Medusa 同步)
|
||||
*/
|
||||
hsCode?: string | null;
|
||||
/**
|
||||
* 原产国
|
||||
* 原产国(从 Medusa 同步)
|
||||
*/
|
||||
countryOfOrigin?: string | null;
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue