chore: upload parte 1 - logica
DevSecOps Enterprise Pipeline / security-gate-and-deploy (push) Failing after 41s

This commit is contained in:
2026-06-01 15:35:53 +01:00
parent a63625d228
commit 9c14a19a87
127 changed files with 22255 additions and 0 deletions
+314
View File
@@ -0,0 +1,314 @@
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 }
);
}
}