319 lines
9.4 KiB
TypeScript
319 lines
9.4 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { getPayload } from 'payload'
|
||
import config from '@payload-config'
|
||
|
||
/**
|
||
* 获取单个预购产品详情
|
||
* GET /api/preorders/:id
|
||
*/
|
||
export async function GET(
|
||
req: NextRequest,
|
||
{ params }: { params: Promise<{ id: string }> }
|
||
) {
|
||
try {
|
||
const payload = await getPayload({ config })
|
||
const { id } = await params
|
||
|
||
// 尝试通过 Payload ID 查找
|
||
let product: any = null
|
||
|
||
try {
|
||
product = await payload.findByID({
|
||
collection: 'preorder-products',
|
||
id,
|
||
depth: 2,
|
||
})
|
||
} catch (err) {
|
||
// 如果不是 Payload ID,尝试通过 medusaId 或 seedId 查找
|
||
const result = await payload.find({
|
||
collection: 'preorder-products',
|
||
where: {
|
||
or: [
|
||
{ medusaId: { equals: id } },
|
||
{ seedId: { equals: id } },
|
||
],
|
||
},
|
||
limit: 1,
|
||
depth: 2,
|
||
})
|
||
|
||
if (result.docs.length > 0) {
|
||
product = result.docs[0]
|
||
}
|
||
}
|
||
|
||
if (!product) {
|
||
return NextResponse.json(
|
||
{ error: 'Preorder product not found' },
|
||
{ status: 404 }
|
||
)
|
||
}
|
||
|
||
// 格式化变体数据
|
||
const variants = (product.variants || []).map((variant: any) => {
|
||
const currentOrders = parseInt(variant.currentOrders || '0', 10) || 0
|
||
const maxOrders = parseInt(variant.maxOrders || '0', 10) || 0
|
||
const availableSlots = maxOrders > 0 ? maxOrders - currentOrders : 0
|
||
const soldOut = maxOrders > 0 && currentOrders >= maxOrders
|
||
const utilization = maxOrders > 0 ? Math.round((currentOrders / maxOrders) * 100) : 0
|
||
|
||
return {
|
||
id: variant.id,
|
||
title: variant.title,
|
||
sku: variant.sku,
|
||
current_orders: currentOrders,
|
||
max_orders: maxOrders,
|
||
available_slots: availableSlots,
|
||
sold_out: soldOut,
|
||
utilization_percentage: utilization,
|
||
prices: variant.prices || [],
|
||
options: variant.options || {},
|
||
metadata: variant.metadata || {},
|
||
}
|
||
})
|
||
|
||
// 计算统计数据
|
||
const totalOrders = variants.reduce((sum: number, v: any) => sum + v.current_orders, 0)
|
||
const totalMaxOrders = variants.reduce((sum: number, v: any) => sum + v.max_orders, 0)
|
||
const totalAvailable = variants.reduce((sum: number, v: any) => sum + v.available_slots, 0)
|
||
|
||
const fundingGoal = parseInt(product.fundingGoal || '0', 10) || totalMaxOrders
|
||
const completionPercentage = fundingGoal > 0
|
||
? Math.round((totalOrders / fundingGoal) * 100)
|
||
: 0
|
||
|
||
const allSoldOut = variants.every((v: any) => v.sold_out)
|
||
const someSoldOut = variants.some((v: any) => v.sold_out)
|
||
|
||
return NextResponse.json({
|
||
preorder: {
|
||
id: product.id,
|
||
title: product.title,
|
||
description: product.description,
|
||
status: product._status,
|
||
thumbnail: product.thumbnail,
|
||
images: product.images || [],
|
||
|
||
// IDs
|
||
seed_id: product.seedId || product.medusaId,
|
||
medusa_id: product.medusaId,
|
||
|
||
// 预购元数据(从 Payload 管理)
|
||
is_preorder: true,
|
||
preorder_type: product.preorderType || 'standard',
|
||
preorder_end_date: product.preorderEndDate || null,
|
||
funding_goal: fundingGoal,
|
||
|
||
// 订单计数
|
||
order_count: parseInt(product.orderCount || '0', 10) || 0,
|
||
fake_order_count: parseInt(product.fakeOrderCount || '0', 10) || 0,
|
||
total_display_count: (parseInt(product.orderCount || '0', 10) || 0) + (parseInt(product.fakeOrderCount || '0', 10) || 0),
|
||
|
||
// 统计数据
|
||
current_orders: totalOrders,
|
||
total_max_orders: totalMaxOrders,
|
||
total_available_slots: totalAvailable,
|
||
completion_percentage: completionPercentage,
|
||
|
||
// 可用性状态
|
||
all_variants_sold_out: allSoldOut,
|
||
some_variants_sold_out: someSoldOut,
|
||
is_available: !allSoldOut && totalAvailable > 0,
|
||
|
||
// 详细信息
|
||
variants,
|
||
variants_count: variants.length,
|
||
categories: product.categories || [],
|
||
collection: product.collection || null,
|
||
metadata: product.metadata || {},
|
||
|
||
// 时间戳
|
||
created_at: product.createdAt,
|
||
updated_at: product.updatedAt,
|
||
last_synced_at: product.lastSyncedAt,
|
||
},
|
||
})
|
||
} catch (error: any) {
|
||
console.error('[Payload Preorder Detail API] Error:', error?.message || error)
|
||
return NextResponse.json(
|
||
{ error: 'Failed to fetch preorder product', message: error?.message },
|
||
{ status: 500 }
|
||
)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新预购产品
|
||
* PATCH /api/preorders/:id
|
||
*
|
||
* Body:
|
||
* - variant_id?: string - 如果提供,更新变体计数
|
||
* - current_orders?: number - 直接设置订单数
|
||
* - max_orders?: number - 更新最大订单数
|
||
* - increment?: number - 增加订单数
|
||
* - decrement?: number - 减少订单数
|
||
*
|
||
* - preorder_end_date?: string - 更新预购结束日期
|
||
* - funding_goal?: number - 更新众筹目标
|
||
* - preorder_type?: string - 更新预购类型
|
||
* - fake_order_count?: number - 更新 Fake 订单计数
|
||
*/
|
||
export async function PATCH(
|
||
req: NextRequest,
|
||
{ params }: { params: Promise<{ id: string }> }
|
||
) {
|
||
try {
|
||
const payload = await getPayload({ config })
|
||
const { id } = await params
|
||
const body = await req.json()
|
||
|
||
const {
|
||
variant_id,
|
||
current_orders,
|
||
max_orders,
|
||
increment,
|
||
decrement,
|
||
preorder_end_date,
|
||
funding_goal,
|
||
preorder_type,
|
||
fake_order_count,
|
||
} = body
|
||
|
||
// 获取产品
|
||
let product: any = null
|
||
|
||
try {
|
||
product = await payload.findByID({
|
||
collection: 'preorder-products',
|
||
id,
|
||
depth: 2,
|
||
})
|
||
} catch (err) {
|
||
const result = await payload.find({
|
||
collection: 'preorder-products',
|
||
where: {
|
||
or: [
|
||
{ medusaId: { equals: id } },
|
||
{ seedId: { equals: id } },
|
||
],
|
||
},
|
||
limit: 1,
|
||
depth: 2,
|
||
})
|
||
|
||
if (result.docs.length > 0) {
|
||
product = result.docs[0]
|
||
}
|
||
}
|
||
|
||
if (!product) {
|
||
return NextResponse.json(
|
||
{ error: 'Preorder product not found' },
|
||
{ status: 404 }
|
||
)
|
||
}
|
||
|
||
// 模式1: 更新变体预购计数
|
||
if (variant_id) {
|
||
// 预购变体数据存储在 Medusa 中,直接更新 Medusa
|
||
const medusaUrl = process.env.MEDUSA_BACKEND_URL || 'http://localhost:9000'
|
||
|
||
// 获取当前变体数据
|
||
const variantResponse = await fetch(`${medusaUrl}/admin/product-variants/${variant_id}`, {
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
})
|
||
|
||
if (!variantResponse.ok) {
|
||
return NextResponse.json(
|
||
{ error: 'Variant not found in Medusa' },
|
||
{ status: 404 }
|
||
)
|
||
}
|
||
|
||
const { variant } = await variantResponse.json()
|
||
const currentMeta = variant.metadata || {}
|
||
let newCurrentOrders = parseInt(currentMeta.current_orders || '0', 10) || 0
|
||
let newMaxOrders = parseInt(currentMeta.max_orders || '0', 10) || 0
|
||
|
||
// 处理更新逻辑
|
||
if (typeof current_orders === 'number') {
|
||
newCurrentOrders = Math.max(0, current_orders)
|
||
} else if (typeof increment === 'number') {
|
||
newCurrentOrders = Math.max(0, newCurrentOrders + increment)
|
||
} else if (typeof decrement === 'number') {
|
||
newCurrentOrders = Math.max(0, newCurrentOrders - decrement)
|
||
}
|
||
|
||
if (typeof max_orders === 'number') {
|
||
newMaxOrders = Math.max(0, max_orders)
|
||
}
|
||
|
||
// 更新 Medusa 变体 metadata
|
||
const updateResponse = await fetch(`${medusaUrl}/admin/product-variants/${variant_id}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
metadata: {
|
||
...currentMeta,
|
||
current_orders: String(newCurrentOrders),
|
||
max_orders: String(newMaxOrders),
|
||
},
|
||
}),
|
||
})
|
||
|
||
if (!updateResponse.ok) {
|
||
throw new Error('Failed to update variant in Medusa')
|
||
}
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
variant_id,
|
||
current_orders: newCurrentOrders,
|
||
max_orders: newMaxOrders,
|
||
available_slots: newMaxOrders - newCurrentOrders,
|
||
sold_out: newMaxOrders > 0 && newCurrentOrders >= newMaxOrders,
|
||
})
|
||
}
|
||
|
||
// 模式2: 更新产品级别预购元数据(在 Payload 中管理)
|
||
const updateData: any = {}
|
||
|
||
if (preorder_end_date !== undefined) {
|
||
updateData.preorderEndDate = preorder_end_date
|
||
}
|
||
if (funding_goal !== undefined) {
|
||
updateData.fundingGoal = String(funding_goal)
|
||
}
|
||
if (preorder_type !== undefined) {
|
||
updateData.preorderType = preorder_type
|
||
}
|
||
if (fake_order_count !== undefined) {
|
||
updateData.fakeOrderCount = Math.max(0, fake_order_count)
|
||
}
|
||
|
||
if (Object.keys(updateData).length > 0) {
|
||
await payload.update({
|
||
collection: 'preorder-products',
|
||
id: product.id,
|
||
data: updateData,
|
||
})
|
||
}
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
message: 'Preorder product updated successfully',
|
||
updated_fields: Object.keys(updateData),
|
||
})
|
||
} catch (error: any) {
|
||
console.error('[Payload Preorder Update API] Error:', error?.message || error)
|
||
return NextResponse.json(
|
||
{ error: 'Failed to update preorder product', message: error?.message },
|
||
{ status: 500 }
|
||
)
|
||
}
|
||
}
|