gbmake-payload/src/app/api/preorders/[id]/route.ts

319 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 }
)
}
}