133 lines
3.8 KiB
TypeScript
133 lines
3.8 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { getDb } from "@/lib/db";
|
|
|
|
interface SourceInfo {
|
|
product_name: string;
|
|
sku: string;
|
|
qty_per_unit: number;
|
|
batch_size: number;
|
|
subtotal: number;
|
|
}
|
|
|
|
interface AcceptItem {
|
|
component_id: number;
|
|
part_number: string;
|
|
component_name: string;
|
|
category_name: string | null;
|
|
package_type: string | null;
|
|
shelf_location: string | null;
|
|
unit_price: number;
|
|
stock_quantity: number;
|
|
total_needed: number;
|
|
shortage: number;
|
|
is_internal: boolean;
|
|
manufacturer: string | null;
|
|
supplier: string | null;
|
|
supplier_part_number: string | null;
|
|
sources: SourceInfo[];
|
|
}
|
|
|
|
interface AcceptProduct {
|
|
product_name: string;
|
|
sku: string;
|
|
batch_size: number;
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const sql = getDb();
|
|
const body = await request.json();
|
|
const { name, items, products, summary, link_po_ids } = body as {
|
|
name: string;
|
|
items: AcceptItem[];
|
|
products: AcceptProduct[];
|
|
summary: {
|
|
totalProducts: number;
|
|
totalComponents: number;
|
|
totalParts: number;
|
|
totalCost: number;
|
|
shortageCount: number;
|
|
};
|
|
link_po_ids?: number[];
|
|
};
|
|
|
|
if (!items || items.length === 0) {
|
|
return NextResponse.json(
|
|
{ error: "No items to accept" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const planName =
|
|
name ||
|
|
`Production Plan ${new Date().toLocaleDateString("en-GB", { day: "2-digit", month: "2-digit", year: "numeric" })}`;
|
|
|
|
// 1. Create the planning history record
|
|
const phRows = await sql(
|
|
`INSERT INTO planning_history (name, status, products_json, summary_json)
|
|
VALUES ($1, $2, $3, $4) RETURNING *`,
|
|
[planName, "accepted", JSON.stringify(products || []), JSON.stringify(summary || {})]
|
|
);
|
|
const plan = phRows[0];
|
|
|
|
// 2. Insert all items with full data snapshots
|
|
for (const item of items) {
|
|
await sql(
|
|
`INSERT INTO planning_history_items
|
|
(planning_id, component_id, part_number, component_name, category_name,
|
|
package_type, shelf_location, unit_price, stock_at_time,
|
|
quantity_needed, shortage, is_internal,
|
|
manufacturer, supplier, supplier_part_number, sources_json)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)`,
|
|
[
|
|
plan.id,
|
|
item.component_id,
|
|
item.part_number,
|
|
item.component_name,
|
|
item.category_name || null,
|
|
item.package_type || null,
|
|
item.shelf_location || null,
|
|
item.unit_price || 0,
|
|
item.stock_quantity || 0,
|
|
item.total_needed,
|
|
item.shortage || 0,
|
|
item.is_internal,
|
|
item.manufacturer || null,
|
|
item.supplier || null,
|
|
item.supplier_part_number || null,
|
|
JSON.stringify(item.sources || []),
|
|
]
|
|
);
|
|
}
|
|
|
|
// 3. Decrement stock for ALL components (both internal and external)
|
|
for (const item of items) {
|
|
await sql(
|
|
`UPDATE components SET stock_quantity = GREATEST(0, stock_quantity - $1) WHERE id = $2`,
|
|
[item.total_needed, item.component_id]
|
|
);
|
|
}
|
|
|
|
// 4. Link selected Supplier POs to this planning record
|
|
if (link_po_ids && link_po_ids.length > 0) {
|
|
for (const poId of link_po_ids) {
|
|
await sql(
|
|
`UPDATE supplier_pos SET planning_id = $1 WHERE id = $2`,
|
|
[plan.id, poId]
|
|
);
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({
|
|
plan,
|
|
message: "Planning accepted. History saved and stock decremented.",
|
|
}, { status: 201 });
|
|
} catch (error) {
|
|
console.error("Error accepting production plan:", error);
|
|
return NextResponse.json(
|
|
{ error: "Failed to accept production plan" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|