- Updated the `getMoldSerialSummary` function to dynamically retrieve category values for mold statuses and operations, allowing for more flexible data aggregation. - Implemented a mapping mechanism to categorize status codes based on their labels, improving the clarity of the summary results. - Adjusted SQL queries to utilize the new category mappings for more accurate counts of mold statuses. - Refactored the packaging and loading unit deletion logic to handle company code checks more efficiently, ensuring proper data access control.
602 lines
21 KiB
TypeScript
602 lines
21 KiB
TypeScript
import { Response } from "express";
|
|
import { AuthenticatedRequest } from "../types/auth";
|
|
import { logger } from "../utils/logger";
|
|
import { getPool } from "../database/db";
|
|
|
|
// ──────────────────────────────────────────────
|
|
// 포장단위 (pkg_unit) CRUD
|
|
// ──────────────────────────────────────────────
|
|
|
|
export async function getPkgUnits(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const pool = getPool();
|
|
|
|
let sql: string;
|
|
let params: any[];
|
|
|
|
if (companyCode === "*") {
|
|
sql = `SELECT * FROM pkg_unit ORDER BY company_code, created_date DESC`;
|
|
params = [];
|
|
} else {
|
|
sql = `SELECT * FROM pkg_unit WHERE company_code = $1 ORDER BY created_date DESC`;
|
|
params = [companyCode];
|
|
}
|
|
|
|
const result = await pool.query(sql, params);
|
|
logger.info("포장단위 목록 조회", { companyCode, count: result.rowCount });
|
|
res.json({ success: true, data: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("포장단위 목록 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function createPkgUnit(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const pool = getPool();
|
|
const {
|
|
pkg_code, pkg_name, pkg_type, status,
|
|
width_mm, length_mm, height_mm,
|
|
self_weight_kg, max_load_kg, volume_l, remarks, item_number,
|
|
} = req.body;
|
|
|
|
if (!pkg_code || !pkg_name) {
|
|
res.status(400).json({ success: false, message: "포장코드와 포장명은 필수입니다." });
|
|
return;
|
|
}
|
|
|
|
const dup = await pool.query(
|
|
`SELECT id FROM pkg_unit WHERE pkg_code = $1 AND company_code = $2`,
|
|
[pkg_code, companyCode]
|
|
);
|
|
if (dup.rowCount && dup.rowCount > 0) {
|
|
res.status(409).json({ success: false, message: "이미 존재하는 포장코드입니다." });
|
|
return;
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO pkg_unit
|
|
(id, company_code, pkg_code, pkg_name, pkg_type, status,
|
|
width_mm, length_mm, height_mm, self_weight_kg, max_load_kg, volume_l, remarks, item_number, writer, created_date)
|
|
VALUES (gen_random_uuid()::text, $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14, NOW())
|
|
RETURNING *`,
|
|
[companyCode, pkg_code, pkg_name, pkg_type, status || "ACTIVE",
|
|
width_mm, length_mm, height_mm, self_weight_kg, max_load_kg, volume_l, remarks, item_number || pkg_code,
|
|
req.user!.userId]
|
|
);
|
|
|
|
logger.info("포장단위 등록", { companyCode, pkg_code });
|
|
res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("포장단위 등록 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function updatePkgUnit(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
const pool = getPool();
|
|
const {
|
|
pkg_name, pkg_type, status,
|
|
width_mm, length_mm, height_mm,
|
|
self_weight_kg, max_load_kg, volume_l, remarks, item_number,
|
|
} = req.body;
|
|
|
|
const result = await pool.query(
|
|
`UPDATE pkg_unit SET
|
|
pkg_name=$1, pkg_type=$2, status=$3,
|
|
width_mm=$4, length_mm=$5, height_mm=$6,
|
|
self_weight_kg=$7, max_load_kg=$8, volume_l=$9, remarks=$10,
|
|
item_number=COALESCE($11, item_number),
|
|
updated_date=NOW(), writer=$12
|
|
WHERE id=$13 AND company_code=$14
|
|
RETURNING *`,
|
|
[pkg_name, pkg_type, status,
|
|
width_mm, length_mm, height_mm,
|
|
self_weight_kg, max_load_kg, volume_l, remarks,
|
|
item_number,
|
|
req.user!.userId, id, companyCode]
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
res.status(404).json({ success: false, message: "데이터를 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("포장단위 수정", { companyCode, id });
|
|
res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("포장단위 수정 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function deletePkgUnit(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
|
|
await client.query("BEGIN");
|
|
await client.query(
|
|
`DELETE FROM pkg_unit_item WHERE pkg_code = (SELECT pkg_code FROM pkg_unit WHERE id=$1 AND company_code=$2) AND company_code=$2`,
|
|
[id, companyCode]
|
|
);
|
|
const result = await client.query(
|
|
`DELETE FROM pkg_unit WHERE id=$1 AND company_code=$2 RETURNING id`,
|
|
[id, companyCode]
|
|
);
|
|
await client.query("COMMIT");
|
|
|
|
if (result.rowCount === 0) {
|
|
res.status(404).json({ success: false, message: "데이터를 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("포장단위 삭제", { companyCode, id });
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
await client.query("ROLLBACK");
|
|
logger.error("포장단위 삭제 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// ──────────────────────────────────────────────
|
|
// 포장단위 매칭품목 (pkg_unit_item) CRUD
|
|
// ──────────────────────────────────────────────
|
|
|
|
export async function getPkgUnitItems(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { pkgCode } = req.params;
|
|
const pool = getPool();
|
|
|
|
const result = await pool.query(
|
|
`SELECT pui.*, ii.item_name, ii.size AS spec, ii.unit
|
|
FROM pkg_unit_item pui
|
|
LEFT JOIN item_info ii ON pui.item_number = ii.item_number AND pui.company_code = ii.company_code
|
|
WHERE pui.pkg_code=$1 AND pui.company_code=$2
|
|
ORDER BY pui.created_date DESC`,
|
|
[pkgCode, companyCode]
|
|
);
|
|
|
|
res.json({ success: true, data: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("매칭품목 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function createPkgUnitItem(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const pool = getPool();
|
|
const { pkg_code, item_number, pkg_qty } = req.body;
|
|
|
|
if (!pkg_code || !item_number) {
|
|
res.status(400).json({ success: false, message: "포장코드와 품번은 필수입니다." });
|
|
return;
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO pkg_unit_item (company_code, pkg_code, item_number, pkg_qty, writer)
|
|
VALUES ($1,$2,$3,$4,$5)
|
|
RETURNING *`,
|
|
[companyCode, pkg_code, item_number, pkg_qty, req.user!.userId]
|
|
);
|
|
|
|
logger.info("매칭품목 추가", { companyCode, pkg_code, item_number });
|
|
res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("매칭품목 추가 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function deletePkgUnitItem(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
const pool = getPool();
|
|
|
|
const query = companyCode === "*"
|
|
? `DELETE FROM pkg_unit_item WHERE id=$1 RETURNING id`
|
|
: `DELETE FROM pkg_unit_item WHERE id=$1 AND company_code=$2 RETURNING id`;
|
|
const params = companyCode === "*" ? [id] : [id, companyCode];
|
|
const result = await pool.query(query, params);
|
|
|
|
if (result.rowCount === 0) {
|
|
res.status(404).json({ success: false, message: "데이터를 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("매칭품목 삭제", { companyCode, id });
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
logger.error("매칭품목 삭제 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ──────────────────────────────────────────────
|
|
// 적재함 (loading_unit) CRUD
|
|
// ──────────────────────────────────────────────
|
|
|
|
export async function getLoadingUnits(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const pool = getPool();
|
|
|
|
let sql: string;
|
|
let params: any[];
|
|
|
|
if (companyCode === "*") {
|
|
sql = `SELECT * FROM loading_unit ORDER BY company_code, created_date DESC`;
|
|
params = [];
|
|
} else {
|
|
sql = `SELECT * FROM loading_unit WHERE company_code = $1 ORDER BY created_date DESC`;
|
|
params = [companyCode];
|
|
}
|
|
|
|
const result = await pool.query(sql, params);
|
|
logger.info("적재함 목록 조회", { companyCode, count: result.rowCount });
|
|
res.json({ success: true, data: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("적재함 목록 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function createLoadingUnit(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const pool = getPool();
|
|
const {
|
|
loading_code, loading_name, loading_type, status,
|
|
width_mm, length_mm, height_mm,
|
|
self_weight_kg, max_load_kg, max_stack, remarks, item_number,
|
|
} = req.body;
|
|
|
|
if (!loading_code || !loading_name) {
|
|
res.status(400).json({ success: false, message: "적재함코드와 적재함명은 필수입니다." });
|
|
return;
|
|
}
|
|
|
|
const dup = await pool.query(
|
|
`SELECT id FROM loading_unit WHERE loading_code=$1 AND company_code=$2`,
|
|
[loading_code, companyCode]
|
|
);
|
|
if (dup.rowCount && dup.rowCount > 0) {
|
|
res.status(409).json({ success: false, message: "이미 존재하는 적재함코드입니다." });
|
|
return;
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO loading_unit
|
|
(id, company_code, loading_code, loading_name, loading_type, status,
|
|
width_mm, length_mm, height_mm, self_weight_kg, max_load_kg, max_stack, remarks, item_number, writer, created_date)
|
|
VALUES (gen_random_uuid()::text, $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14, NOW())
|
|
RETURNING *`,
|
|
[companyCode, loading_code, loading_name, loading_type, status || "ACTIVE",
|
|
width_mm, length_mm, height_mm, self_weight_kg, max_load_kg, max_stack, remarks, item_number || loading_code,
|
|
req.user!.userId]
|
|
);
|
|
|
|
logger.info("적재함 등록", { companyCode, loading_code });
|
|
res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("적재함 등록 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function updateLoadingUnit(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
const pool = getPool();
|
|
const {
|
|
loading_name, loading_type, status,
|
|
width_mm, length_mm, height_mm,
|
|
self_weight_kg, max_load_kg, max_stack, remarks, item_number,
|
|
} = req.body;
|
|
|
|
const result = await pool.query(
|
|
`UPDATE loading_unit SET
|
|
loading_name=$1, loading_type=$2, status=$3,
|
|
width_mm=$4, length_mm=$5, height_mm=$6,
|
|
self_weight_kg=$7, max_load_kg=$8, max_stack=$9, remarks=$10,
|
|
item_number=COALESCE($11, item_number),
|
|
updated_date=NOW(), writer=$12
|
|
WHERE id=$13 AND company_code=$14
|
|
RETURNING *`,
|
|
[loading_name, loading_type, status,
|
|
width_mm, length_mm, height_mm,
|
|
self_weight_kg, max_load_kg, max_stack, remarks,
|
|
item_number,
|
|
req.user!.userId, id, companyCode]
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
res.status(404).json({ success: false, message: "데이터를 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("적재함 수정", { companyCode, id });
|
|
res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("적재함 수정 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function deleteLoadingUnit(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
|
|
await client.query("BEGIN");
|
|
await client.query(
|
|
`DELETE FROM loading_unit_pkg WHERE loading_code = (SELECT loading_code FROM loading_unit WHERE id=$1 AND company_code=$2) AND company_code=$2`,
|
|
[id, companyCode]
|
|
);
|
|
const result = await client.query(
|
|
`DELETE FROM loading_unit WHERE id=$1 AND company_code=$2 RETURNING id`,
|
|
[id, companyCode]
|
|
);
|
|
await client.query("COMMIT");
|
|
|
|
if (result.rowCount === 0) {
|
|
res.status(404).json({ success: false, message: "데이터를 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("적재함 삭제", { companyCode, id });
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
await client.query("ROLLBACK");
|
|
logger.error("적재함 삭제 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// ──────────────────────────────────────────────
|
|
// 적재함 포장구성 (loading_unit_pkg) CRUD
|
|
// ──────────────────────────────────────────────
|
|
|
|
export async function getLoadingUnitPkgs(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { loadingCode } = req.params;
|
|
const pool = getPool();
|
|
|
|
const result = await pool.query(
|
|
`SELECT lup.*, pu.pkg_name, pu.pkg_type
|
|
FROM loading_unit_pkg lup
|
|
LEFT JOIN pkg_unit pu ON lup.pkg_code = pu.pkg_code AND lup.company_code = pu.company_code
|
|
WHERE lup.loading_code=$1 AND lup.company_code=$2
|
|
ORDER BY lup.created_date DESC`,
|
|
[loadingCode, companyCode]
|
|
);
|
|
|
|
res.json({ success: true, data: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("적재구성 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function createLoadingUnitPkg(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const pool = getPool();
|
|
const { loading_code, pkg_code, max_load_qty, load_method } = req.body;
|
|
|
|
if (!loading_code || !pkg_code) {
|
|
res.status(400).json({ success: false, message: "적재함코드와 포장코드는 필수입니다." });
|
|
return;
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO loading_unit_pkg (company_code, loading_code, pkg_code, max_load_qty, load_method, writer)
|
|
VALUES ($1,$2,$3,$4,$5,$6)
|
|
RETURNING *`,
|
|
[companyCode, loading_code, pkg_code, max_load_qty, load_method, req.user!.userId]
|
|
);
|
|
|
|
logger.info("적재구성 추가", { companyCode, loading_code, pkg_code });
|
|
res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("적재구성 추가 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function deleteLoadingUnitPkg(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
const pool = getPool();
|
|
|
|
const query = companyCode === "*"
|
|
? `DELETE FROM loading_unit_pkg WHERE id=$1 RETURNING id`
|
|
: `DELETE FROM loading_unit_pkg WHERE id=$1 AND company_code=$2 RETURNING id`;
|
|
const params = companyCode === "*" ? [id] : [id, companyCode];
|
|
const result = await pool.query(query, params);
|
|
|
|
if (result.rowCount === 0) {
|
|
res.status(404).json({ success: false, message: "데이터를 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("적재구성 삭제", { companyCode, id });
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
logger.error("적재구성 삭제 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ──────────────────────────────────────────────
|
|
// 품목정보 연동 (division별 item_info 조회)
|
|
// ──────────────────────────────────────────────
|
|
|
|
export async function getItemsByDivision(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { divisionLabel } = req.params;
|
|
const { keyword } = req.query;
|
|
const pool = getPool();
|
|
|
|
// division 카테고리에서 해당 라벨의 코드 찾기
|
|
const catResult = await pool.query(
|
|
`SELECT value_code FROM category_values
|
|
WHERE table_name = 'item_info' AND column_name = 'division'
|
|
AND value_label = $1 AND company_code = $2
|
|
LIMIT 1`,
|
|
[divisionLabel, companyCode]
|
|
);
|
|
|
|
if (catResult.rows.length === 0) {
|
|
res.json({ success: true, data: [] });
|
|
return;
|
|
}
|
|
|
|
const divisionCode = catResult.rows[0].value_code;
|
|
|
|
const conditions: string[] = ["company_code = $1", `$2 = ANY(string_to_array(division, ','))`];
|
|
const params: any[] = [companyCode, divisionCode];
|
|
let paramIdx = 3;
|
|
|
|
if (keyword) {
|
|
conditions.push(`(item_number ILIKE $${paramIdx} OR item_name ILIKE $${paramIdx})`);
|
|
params.push(`%${keyword}%`);
|
|
paramIdx++;
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`SELECT id, item_number, item_name, size, material, unit, division
|
|
FROM item_info
|
|
WHERE ${conditions.join(" AND ")}
|
|
ORDER BY item_name`,
|
|
params
|
|
);
|
|
|
|
logger.info(`품목 조회 (division=${divisionLabel})`, { companyCode, count: result.rowCount });
|
|
res.json({ success: true, data: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("품목 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// 일반 품목 조회 (포장재/적재함 제외, 매칭용)
|
|
export async function getGeneralItems(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { keyword } = req.query;
|
|
const pool = getPool();
|
|
|
|
// 포장재/적재함 division 코드 조회
|
|
const catResult = await pool.query(
|
|
`SELECT value_code FROM category_values
|
|
WHERE table_name = 'item_info' AND column_name = 'division'
|
|
AND value_label IN ('포장재', '적재함') AND company_code = $1`,
|
|
[companyCode]
|
|
);
|
|
const excludeCodes = catResult.rows.map((r: any) => r.value_code);
|
|
|
|
const conditions: string[] = ["company_code = $1"];
|
|
const params: any[] = [companyCode];
|
|
let paramIdx = 2;
|
|
|
|
if (excludeCodes.length > 0) {
|
|
// 다중 값(콤마 구분) 지원: 포장재/적재함 코드가 포함된 품목 제외
|
|
const excludeConditions = excludeCodes.map((_: any, i: number) => `$${paramIdx + i} = ANY(string_to_array(division, ','))`);
|
|
conditions.push(`(division IS NULL OR division = '' OR NOT (${excludeConditions.join(" OR ")}))`);
|
|
params.push(...excludeCodes);
|
|
paramIdx += excludeCodes.length;
|
|
}
|
|
|
|
if (keyword) {
|
|
conditions.push(`(item_number ILIKE $${paramIdx} OR item_name ILIKE $${paramIdx})`);
|
|
params.push(`%${keyword}%`);
|
|
paramIdx++;
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`SELECT id, item_number, item_name, size AS spec, material, unit, division
|
|
FROM item_info
|
|
WHERE ${conditions.join(" AND ")}
|
|
ORDER BY item_name
|
|
LIMIT 200`,
|
|
params
|
|
);
|
|
|
|
res.json({ success: true, data: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("일반 품목 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|