feat: add main warehouse query API with server-side filtering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-06-04 09:40:47 +08:00
parent baa339025a
commit 425fbbea28

200
app/api/warehouse/route.ts Normal file
View File

@@ -0,0 +1,200 @@
import { NextRequest, NextResponse } from "next/server";
import sql from "mssql";
import { getPool } from "@/lib/db";
import type { QueryParams, QueryResult, WarehouseRow } from "@/types/warehouse";
const WORKSHOP_MAP: Record<string, string> = {
"1": "一车间",
"2": "二车间",
"3": "温度计车间",
"4": "四车间",
"5": "五车间",
};
// Map frontend sort keys to SQL column aliases in the subquery
const SORT_MAP: Record<string, string> = {
paichan: "车间号",
zongpai: "总排号",
workshop: "车间",
model: "产品型号",
range: "量程",
qty: "数量",
boxNo: "box_no",
boxQty: "item_quantity",
workOrder: "工令号",
shelf: "location_code",
handler: "经办人",
orderNo: "订单号",
inbound: "入库日期",
};
function formatRows(rows: sql.IRecordSet<any>): WarehouseRow[] {
return rows.map((r) => {
const inboundDate = r.;
const inbound = inboundDate
? new Date(inboundDate).toISOString().slice(0, 10).replace(/-/g, "/")
: "";
return {
paichan: r.车间号 || "",
zongpai: r.总排号 || "",
workshop: WORKSHOP_MAP[(r. || "").trim()] || (r. || "").trim(),
model: r.产品型号 || "",
range: r.量程 || "",
qty: r.数量 ?? null,
boxNo: r.box_no != null ? String(r.box_no) : "",
boxQty: r.item_quantity ?? null,
workOrder: r.工令号 || "",
shelf: r.location_code || "",
handler: r.经办人 || "",
orderNo: r.订单号 || "",
inbound,
status: inboundDate ? ("IN" as const) : ("WAIT" as const),
};
});
}
export async function GET(request: NextRequest) {
try {
const params: QueryParams = {
search: request.nextUrl.searchParams.get("search") || undefined,
workshop: request.nextUrl.searchParams.get("workshop") || undefined,
handler: request.nextUrl.searchParams.get("handler") || undefined,
status:
(request.nextUrl.searchParams.get("status") as QueryParams["status"]) ||
undefined,
dateFrom: request.nextUrl.searchParams.get("dateFrom") || undefined,
dateTo: request.nextUrl.searchParams.get("dateTo") || undefined,
sortKey: request.nextUrl.searchParams.get("sortKey") || undefined,
sortDir:
(request.nextUrl.searchParams.get("sortDir") as QueryParams["sortDir"]) ||
undefined,
page: Number(request.nextUrl.searchParams.get("page")) || 1,
pageSize: Number(request.nextUrl.searchParams.get("pageSize")) || 20,
};
const pool = await getPool();
// --- Base CTE: UNION ALL of both contract tables ---
const baseCTE = `
WITH contract AS (
SELECT 总排号, 车间号, 车间, 产品型号, 量程, 数量, 工令号, 经办人, 订单号
FROM productionContractData.[26年压力表合同数据]
UNION ALL
SELECT 总排号, 车间号, 车间, 产品型号, 量程, 数量, 工令号, 经办人, 订单号
FROM productionContractData.[26年温度计合同数据]
)
`;
// --- Main FROM with LEFT JOINs ---
const mainFrom = `
FROM contract c
LEFT JOIN CargoTrace.finished_goods_box_item i ON c.总排号 = i.zongpai_no
LEFT JOIN CargoTrace.finished_goods_box b ON i.box_id = b.id
LEFT JOIN CargoTrace.finished_goods_location l ON c.总排号 = l.zongpai_no
LEFT JOIN productWarehousing.成品入库记录_YEAR2026 wh ON c.总排号 = wh.总排号
`;
// --- Build WHERE clause ---
const conditions: string[] = [];
if (params.workshop && params.workshop !== "ALL") {
conditions.push("c.车间 = @workshop");
}
if (params.handler && params.handler !== "ALL") {
conditions.push("c.经办人 = @handler");
}
if (params.status === "IN") {
conditions.push("wh.日期 IS NOT NULL");
} else if (params.status === "WAIT") {
conditions.push("wh.日期 IS NULL");
}
if (params.dateFrom) {
conditions.push("wh.日期 >= @dateFrom");
}
if (params.dateTo) {
conditions.push("wh.日期 < DATEADD(DAY, 1, @dateTo)");
}
if (params.search) {
conditions.push(`(
c.总排号 LIKE @kw OR c.车间号 LIKE @kw OR c.产品型号 LIKE @kw
OR c.量程 LIKE @kw OR ISNULL(CAST(b.box_no AS NVARCHAR(50)), '') LIKE @kw
OR c.工令号 LIKE @kw OR ISNULL(l.location_code, '') LIKE @kw
OR c.经办人 LIKE @kw OR c.订单号 LIKE @kw
)`);
}
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
// --- Helper to add params to a request ---
function addParams(req: sql.Request) {
if (params.workshop && params.workshop !== "ALL")
req.input("workshop", sql.NVarChar, params.workshop);
if (params.handler && params.handler !== "ALL")
req.input("handler", sql.NVarChar, params.handler);
if (params.dateFrom)
req.input("dateFrom", sql.DateTime2, new Date(params.dateFrom));
if (params.dateTo)
req.input("dateTo", sql.DateTime2, new Date(params.dateTo));
if (params.search)
req.input("kw", sql.NVarChar, `%${params.search}%`);
}
// --- Count query ---
const countReq = pool.request();
addParams(countReq);
const countSQL = `${baseCTE} SELECT COUNT(*) AS total FROM (SELECT c.* ${mainFrom} ${where}) sub`;
const countResult = await countReq.query(countSQL);
const total = countResult.recordset[0].total;
// --- Data query with sorting & pagination ---
const sortCol = SORT_MAP[params.sortKey || ""] || "c.总排号";
const sortDir = params.sortDir === "desc" ? "DESC" : "ASC";
const page = Math.max(1, params.page || 1);
const pageSize = params.pageSize || 20;
const offset = (page - 1) * pageSize;
const dataReq = pool.request();
addParams(dataReq);
dataReq.input("offset", sql.Int, offset);
dataReq.input("pageSize", sql.Int, pageSize);
const dataSQL = `
${baseCTE}
SELECT * FROM (
SELECT
c.车间号, c.总排号, c.车间, c.产品型号, c.量程, c.数量,
ISNULL(CAST(b.box_no AS NVARCHAR(50)), '') AS box_no,
ISNULL(CAST(i.quantity AS NVARCHAR(50)), '') AS item_quantity,
ISNULL(c.工令号, '') AS 工令号,
ISNULL(l.location_code, '') AS location_code,
ISNULL(c.经办人, '') AS 经办人,
ISNULL(c.订单号, '') AS 订单号,
wh.日期 AS 入库日期,
ROW_NUMBER() OVER (ORDER BY ${sortCol} ${sortDir}) AS _rownum
${mainFrom}
${where}
) numbered
WHERE _rownum > @offset AND _rownum <= @offset + @pageSize
ORDER BY _rownum
`;
const dataResult = await dataReq.query(dataSQL);
const rows = formatRows(dataResult.recordset);
const result: QueryResult = {
rows,
total,
page,
pageSize,
totalPages: Math.max(1, Math.ceil(total / pageSize)),
};
return NextResponse.json(result);
} catch (error) {
console.error("Warehouse query failed:", error);
return NextResponse.json(
{ error: "Query failed", detail: String(error) },
{ status: 500 }
);
}
}