diff --git a/app/api/warehouse/route.ts b/app/api/warehouse/route.ts new file mode 100644 index 0000000..ddecab2 --- /dev/null +++ b/app/api/warehouse/route.ts @@ -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 = { + "1": "一车间", + "2": "二车间", + "3": "温度计车间", + "4": "四车间", + "5": "五车间", +}; + +// Map frontend sort keys to SQL column aliases in the subquery +const SORT_MAP: Record = { + paichan: "车间号", + zongpai: "总排号", + workshop: "车间", + model: "产品型号", + range: "量程", + qty: "数量", + boxNo: "box_no", + boxQty: "item_quantity", + workOrder: "工令号", + shelf: "location_code", + handler: "经办人", + orderNo: "订单号", + inbound: "入库日期", +}; + +function formatRows(rows: sql.IRecordSet): 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 } + ); + } +}