import { NextRequest, NextResponse } from "next/server"; import { getDb } from "@/lib/db"; export async function GET( _request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const sql = getDb(); const { id } = await params; const poRows = await sql( `SELECT sp.*, s.name AS supplier_name, s.code AS supplier_code, s.email AS supplier_email, s.phone AS supplier_phone, s.address AS supplier_address, s.contact_person AS supplier_contact, s.country AS supplier_country, s.currency AS supplier_currency FROM supplier_pos sp JOIN suppliers s ON s.id = sp.supplier_id WHERE sp.id = $1`, [Number(id)] ); if (poRows.length === 0) { return NextResponse.json({ error: "Supplier PO not found" }, { status: 404 }); } const items = await sql( `SELECT * FROM supplier_po_items WHERE supplier_po_id = $1 ORDER BY id ASC`, [Number(id)] ); return NextResponse.json({ ...poRows[0], items }); } catch (error) { console.error("Supplier PO GET error:", error); return NextResponse.json( { error: "Failed to fetch supplier PO" }, { status: 500 } ); } } export async function PUT( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const sql = getDb(); const { id } = await params; const body = await request.json(); const { status, notes, received_items } = body; // Validate forward-only status transitions (plus cancelled from ordered) if (status) { const STATUS_ORDER = ["draft", "ordered", "partially_received", "received"]; const currentRows = await sql(`SELECT status FROM supplier_pos WHERE id = $1`, [Number(id)]); if (currentRows.length > 0) { const currentStatus = currentRows[0].status as string; // Special case: cancellation is only allowed from "ordered" if (status === "cancelled") { if (currentStatus !== "ordered") { return NextResponse.json( { error: `Cannot cancel a PO with status "${currentStatus}". Only ordered POs (with no received quantities) can be cancelled.` }, { status: 400 } ); } // Verify no quantities have been received const receivedCheck = await sql( `SELECT COALESCE(SUM(received_quantity), 0) AS total_received FROM supplier_po_items WHERE supplier_po_id = $1`, [Number(id)] ); if (Number(receivedCheck[0].total_received) > 0) { return NextResponse.json( { error: "Cannot cancel a PO that has already received quantities." }, { status: 400 } ); } } else { const currentIdx = STATUS_ORDER.indexOf(currentStatus); const newIdx = STATUS_ORDER.indexOf(status); if (newIdx < 0 || newIdx <= currentIdx) { return NextResponse.json( { error: `Cannot change status from "${currentStatus}" to "${status}". Status can only move forward.` }, { status: 400 } ); } } } } // Update received quantities per item if provided, adjust stock and log transactions if (received_items && Array.isArray(received_items)) { // Fetch PO info for transaction records const poInfo = await sql( `SELECT sp.po_number, s.name AS supplier_name FROM supplier_pos sp JOIN suppliers s ON s.id = sp.supplier_id WHERE sp.id = $1`, [Number(id)] ); const poNumber = poInfo[0]?.po_number || ""; const supplierName = poInfo[0]?.supplier_name || ""; for (const ri of received_items) { const newReceivedQty = Number(ri.received_quantity); // Get current received qty and component_id before updating const currentItem = await sql( `SELECT received_quantity, component_id, unit_price FROM supplier_po_items WHERE id = $1 AND supplier_po_id = $2`, [Number(ri.id), Number(id)] ); if (currentItem.length === 0) continue; const prevReceivedQty = Number(currentItem[0].received_quantity) || 0; const componentId = currentItem[0].component_id; const unitPrice = Number(currentItem[0].unit_price) || 0; const delta = newReceivedQty - prevReceivedQty; // Update the received quantity on the PO item await sql( `UPDATE supplier_po_items SET received_quantity = $1 WHERE id = $2 AND supplier_po_id = $3`, [newReceivedQty, Number(ri.id), Number(id)] ); // If there's a positive delta and a valid component, update stock and log transaction if (delta > 0 && componentId) { console.log(`[PO Receive] Adding stock: component=${componentId}, delta=${delta}, prevReceived=${prevReceivedQty}, newReceived=${newReceivedQty}, PO=${poNumber}`); await sql( `UPDATE components SET stock_quantity = stock_quantity + $1, updated_at = NOW() WHERE id = $2`, [delta, Number(componentId)] ); await sql( `INSERT INTO stock_transactions (component_id, transaction_type, quantity, unit_cost, total_cost, supplier, supplier_order_ref, notes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [ Number(componentId), "purchase", delta, unitPrice, delta * unitPrice, supplierName, poNumber, `Received from PO ${poNumber}`, ] ); } else if (delta < 0 && componentId) { // If quantity was reduced (correction), reverse the stock await sql( `UPDATE components SET stock_quantity = GREATEST(0, stock_quantity + $1), updated_at = NOW() WHERE id = $2`, [delta, Number(componentId)] ); await sql( `INSERT INTO stock_transactions (component_id, transaction_type, quantity, unit_cost, total_cost, supplier, supplier_order_ref, notes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [ Number(componentId), "purchase", delta, unitPrice, delta * unitPrice, supplierName, poNumber, `Correction on PO ${poNumber}`, ] ); } } } // When status changes to "received", auto-receive all remaining items // This only triggers when NO received_items array is provided (e.g., status dropdown change) if (status === "received" && !(received_items && Array.isArray(received_items) && received_items.length > 0)) { console.log(`[PO Auto-Receive] Status changed to received without received_items, auto-completing remaining items for PO ${id}`); const poInfo = await sql( `SELECT sp.po_number, s.name AS supplier_name FROM supplier_pos sp JOIN suppliers s ON s.id = sp.supplier_id WHERE sp.id = $1`, [Number(id)] ); const poNumber = poInfo[0]?.po_number || ""; const supplierName = poInfo[0]?.supplier_name || ""; const allItems = await sql( `SELECT id, component_id, quantity, received_quantity, unit_price FROM supplier_po_items WHERE supplier_po_id = $1`, [Number(id)] ); for (const item of allItems) { const ordered = Number(item.quantity); const prevReceived = Number(item.received_quantity) || 0; const delta = ordered - prevReceived; const componentId = item.component_id; const unitPrice = Number(item.unit_price) || 0; if (delta > 0) { console.log(`[PO Auto-Receive] Item ${item.id}: ordered=${ordered}, prevReceived=${prevReceived}, delta=${delta}, component=${componentId}, PO=${poNumber}`); // Mark as fully received await sql( `UPDATE supplier_po_items SET received_quantity = $1 WHERE id = $2`, [ordered, Number(item.id)] ); if (componentId) { await sql( `UPDATE components SET stock_quantity = stock_quantity + $1, updated_at = NOW() WHERE id = $2`, [delta, Number(componentId)] ); await sql( `INSERT INTO stock_transactions (component_id, transaction_type, quantity, unit_cost, total_cost, supplier, supplier_order_ref, notes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [ Number(componentId), "purchase", delta, unitPrice, delta * unitPrice, supplierName, poNumber, `Received from PO ${poNumber}`, ] ); } } else { console.log(`[PO Auto-Receive] Item ${item.id}: no delta (ordered=${ordered}, prevReceived=${prevReceived}), skipping stock update`); } } } // Replace all items on a draft PO (full item edit) const { draft_items } = body; if (draft_items && Array.isArray(draft_items)) { const currentRows = await sql(`SELECT status FROM supplier_pos WHERE id = $1`, [Number(id)]); if (currentRows.length > 0 && currentRows[0].status !== "draft") { return NextResponse.json({ error: "Items can only be edited on draft POs" }, { status: 400 }); } await sql(`DELETE FROM supplier_po_items WHERE supplier_po_id = $1`, [Number(id)]); for (const item of draft_items) { await sql( `INSERT INTO supplier_po_items (supplier_po_id, component_id, part_number, component_name, supplier_part_number, quantity, unit_price, received_quantity) VALUES ($1, $2, $3, $4, $5, $6, $7, 0)`, [ Number(id), Number(item.component_id) || null, item.part_number || "", item.component_name || "", item.supplier_part_number || "", Number(item.quantity), Number(item.unit_price) || 0, ] ); } } const { shipping_cost } = body; const rows = await sql( `UPDATE supplier_pos SET status = COALESCE($1, status), notes = COALESCE($2, notes), shipping_cost = COALESCE($3, shipping_cost) WHERE id = $4 RETURNING *`, [status || null, notes !== undefined ? notes : null, shipping_cost !== undefined ? Number(shipping_cost) : null, Number(id)] ); if (rows.length === 0) { return NextResponse.json({ error: "Supplier PO not found" }, { status: 404 }); } // Return full PO with items const items = await sql( `SELECT * FROM supplier_po_items WHERE supplier_po_id = $1 ORDER BY id ASC`, [Number(id)] ); return NextResponse.json({ ...rows[0], items }); } catch (error) { console.error("Supplier PO PUT error:", error); return NextResponse.json( { error: "Failed to update supplier PO" }, { status: 500 } ); } } export async function DELETE( _request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const sql = getDb(); const { id } = await params; // Only allow deleting draft POs const currentRows = await sql(`SELECT status FROM supplier_pos WHERE id = $1`, [Number(id)]); if (currentRows.length > 0 && currentRows[0].status !== "draft") { return NextResponse.json( { error: "Only draft POs can be deleted" }, { status: 400 } ); } await sql(`DELETE FROM supplier_po_items WHERE supplier_po_id = $1`, [Number(id)]); await sql(`DELETE FROM supplier_pos WHERE id = $1`, [Number(id)]); return NextResponse.json({ success: true }); } catch (error) { console.error("Supplier PO DELETE error:", error); return NextResponse.json( { error: "Failed to delete supplier PO" }, { status: 500 } ); } }