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