import { NextRequest, NextResponse } from "next/server"; import { getDb } from "@/lib/db"; export async function GET(request: NextRequest) { try { const sql = getDb(); const { searchParams } = new URL(request.url); const search = searchParams.get("search"); const orderId = searchParams.get("orderId"); let query = ` SELECT pl.*, po.po_number, c.name AS customer_name, c.code AS customer_code, c.email AS customer_email, c.phone AS customer_phone, c.address AS customer_address, p.name AS product_name, p.sku AS product_sku, (SELECT COUNT(*) FROM packing_list_serials pls WHERE pls.packing_list_id = pl.id) AS serial_count FROM packing_lists pl JOIN purchase_orders po ON po.id = pl.purchase_order_id JOIN customers c ON c.id = po.customer_id JOIN purchase_order_items poi ON poi.id = pl.po_item_id JOIN products p ON p.id = poi.product_id `; const conditions: string[] = []; const params: (string | number)[] = []; if (orderId) { params.push(Number(orderId)); conditions.push(`pl.purchase_order_id = $${params.length}`); } if (search) { params.push(`%${search}%`); const i = params.length; conditions.push( `(pl.packing_number ILIKE $${i} OR po.po_number ILIKE $${i} OR c.name ILIKE $${i} OR p.name ILIKE $${i})` ); } if (conditions.length > 0) { query += ` WHERE ${conditions.join(" AND ")}`; } query += ` ORDER BY pl.created_at DESC`; const rows = await sql(query, params); return NextResponse.json(rows); } catch (error) { console.error("[v0] Packing list GET error:", error); return NextResponse.json( { error: error instanceof Error ? error.message : "Failed to fetch packing lists" }, { status: 500 } ); } } export async function POST(request: NextRequest) { try { const sql = getDb(); const body = await request.json(); const { purchase_order_id, po_item_id, serial_numbers, notes } = body; if (!purchase_order_id || !po_item_id || !serial_numbers?.length) { return NextResponse.json( { error: "purchase_order_id, po_item_id, and serial_numbers are required" }, { status: 400 } ); } const quantity = serial_numbers.length; // Fetch customer name for packing number generation const custRows = await sql( `SELECT c.name FROM purchase_orders po JOIN customers c ON c.id = po.customer_id WHERE po.id = $1`, [purchase_order_id] ); if (custRows.length === 0) { return NextResponse.json({ error: "Purchase order not found" }, { status: 404 }); } const customerName = String(custRows[0].name).replace(/[^a-zA-Z0-9]/g, "").toUpperCase(); // Generate packing number: PL-EACH-YYYY_MM_DD-RR-CUSTOMERNAME const now = new Date(); const yyyy = now.getFullYear(); const mm = String(now.getMonth() + 1).padStart(2, "0"); const dd = String(now.getDate()).padStart(2, "0"); const datePrefix = `PL-EACH-${yyyy}_${mm}_${dd}`; const dateSuffix = `-${customerName}`; const pattern = `${datePrefix}-__-${customerName}`; // Count existing packing lists with same date + customer to determine revision const existingRows = await sql( `SELECT COUNT(*) AS cnt FROM packing_lists WHERE packing_number LIKE $1`, [`${datePrefix}-%${dateSuffix}`] ); const revision = Number(existingRows[0].cnt) + 1; const packing_number = `${datePrefix}-${String(revision).padStart(2, "0")}${dateSuffix}`; // Check remaining capacity on the PO item const poItemRows = await sql( `SELECT poi.quantity, COALESCE(poi.shipped_quantity, 0) AS shipped_quantity, poi.product_id, p.name AS product_name FROM purchase_order_items poi JOIN products p ON p.id = poi.product_id WHERE poi.id = $1`, [po_item_id] ); if (poItemRows.length === 0) { return NextResponse.json({ error: "PO item not found" }, { status: 404 }); } const poItem = poItemRows[0]; const productId = poItemRows[0].product_id; const remaining = Number(poItem.quantity) - Number(poItem.shipped_quantity); if (quantity > remaining) { return NextResponse.json( { error: `Only ${remaining} units remaining to ship for ${poItem.product_name}` }, { status: 400 } ); } // Check for duplicate serial numbers within the same Purchase Order if (serial_numbers.length > 0) { const placeholders = serial_numbers.map((_: string, i: number) => `$${i + 2}`).join(", "); const dupes = await sql( `SELECT pls.serial_number FROM packing_list_serials pls JOIN packing_lists pl ON pl.id = pls.packing_list_id WHERE pl.purchase_order_id = $1 AND pls.serial_number IN (${placeholders})`, [purchase_order_id, ...serial_numbers] ); if (dupes.length > 0) { const dupeList = dupes.map((d: { serial_number: string }) => d.serial_number).join(", "); return NextResponse.json( { error: `Duplicate serial number(s) in this PO: ${dupeList}` }, { status: 400 } ); } } // Create packing list const plRows = await sql( `INSERT INTO packing_lists (purchase_order_id, po_item_id, packing_number, quantity, notes) VALUES ($1, $2, $3, $4, $5) RETURNING *`, [purchase_order_id, po_item_id, packing_number, quantity, notes || null] ); const packingList = plRows[0]; // Insert serial numbers for (const sn of serial_numbers) { await sql( `INSERT INTO packing_list_serials (packing_list_id, serial_number) VALUES ($1, $2)`, [packingList.id, sn] ); } // Update shipped_quantity on the PO item await sql( `UPDATE purchase_order_items SET shipped_quantity = COALESCE(shipped_quantity, 0) + $1 WHERE id = $2`, [quantity, po_item_id] ); return NextResponse.json(packingList, { status: 201 }); } catch (error) { console.error("[v0] Packing list POST error:", error); return NextResponse.json( { error: error instanceof Error ? error.message : "Failed to create packing list" }, { status: 500 } ); } }