- 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.
556 lines
21 KiB
TypeScript
556 lines
21 KiB
TypeScript
import { Response } from "express";
|
|
import { AuthenticatedRequest } from "../types/auth";
|
|
import { query } from "../database/db";
|
|
import { logger } from "../utils/logger";
|
|
|
|
// ============================================
|
|
// 금형 마스터 CRUD
|
|
// ============================================
|
|
|
|
export async function getMoldList(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { mold_code, mold_name, mold_type, operation_status } = req.query;
|
|
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
let paramIndex = 1;
|
|
|
|
if (companyCode === "*") {
|
|
// 최고 관리자: 전체 조회
|
|
} else {
|
|
conditions.push(`company_code = $${paramIndex}`);
|
|
params.push(companyCode);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (mold_code) {
|
|
conditions.push(`mold_code ILIKE $${paramIndex}`);
|
|
params.push(`%${mold_code}%`);
|
|
paramIndex++;
|
|
}
|
|
if (mold_name) {
|
|
conditions.push(`mold_name ILIKE $${paramIndex}`);
|
|
params.push(`%${mold_name}%`);
|
|
paramIndex++;
|
|
}
|
|
if (mold_type) {
|
|
conditions.push(`mold_type = $${paramIndex}`);
|
|
params.push(mold_type);
|
|
paramIndex++;
|
|
}
|
|
if (operation_status) {
|
|
conditions.push(`operation_status = $${paramIndex}`);
|
|
params.push(operation_status);
|
|
paramIndex++;
|
|
}
|
|
|
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
const sql = `SELECT * FROM mold_mng ${whereClause} ORDER BY created_date DESC`;
|
|
const result = await query(sql, params);
|
|
|
|
logger.info("금형 목록 조회", { companyCode, count: result.length });
|
|
res.json({ success: true, data: result });
|
|
} catch (error: any) {
|
|
logger.error("금형 목록 조회 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function getMoldDetail(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { moldCode } = req.params;
|
|
|
|
let sql: string;
|
|
let params: any[];
|
|
|
|
if (companyCode === "*") {
|
|
sql = `SELECT * FROM mold_mng WHERE mold_code = $1 LIMIT 1`;
|
|
params = [moldCode];
|
|
} else {
|
|
sql = `SELECT * FROM mold_mng WHERE mold_code = $1 AND company_code = $2 LIMIT 1`;
|
|
params = [moldCode, companyCode];
|
|
}
|
|
|
|
const result = await query(sql, params);
|
|
if (result.length === 0) {
|
|
res.status(404).json({ success: false, message: "금형을 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, data: result[0] });
|
|
} catch (error: any) {
|
|
logger.error("금형 상세 조회 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function createMold(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const userId = req.user!.userId;
|
|
const {
|
|
mold_code, mold_name, mold_type, category, manufacturer,
|
|
manufacturing_number, manufacturing_date, cavity_count,
|
|
shot_count, mold_quantity, base_input_qty, operation_status,
|
|
remarks, image_path, memo, warranty_shot_count,
|
|
} = req.body;
|
|
|
|
if (!mold_code || !mold_name) {
|
|
res.status(400).json({ success: false, message: "금형코드와 금형명은 필수입니다." });
|
|
return;
|
|
}
|
|
|
|
const sql = `
|
|
INSERT INTO mold_mng (
|
|
id, company_code, mold_code, mold_name, mold_type, category,
|
|
manufacturer, manufacturing_number, manufacturing_date,
|
|
cavity_count, shot_count, mold_quantity, base_input_qty,
|
|
operation_status, remarks, image_path, memo, warranty_shot_count, writer, created_date
|
|
) VALUES (gen_random_uuid()::text,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,NOW())
|
|
RETURNING *
|
|
`;
|
|
const params = [
|
|
companyCode, mold_code, mold_name, mold_type || null, category || null,
|
|
manufacturer || null, manufacturing_number || null, manufacturing_date || null,
|
|
cavity_count || 0, shot_count || 0, mold_quantity || 1, base_input_qty || 0,
|
|
operation_status || "ACTIVE", remarks || null, image_path || null, memo || null,
|
|
warranty_shot_count || 0, userId,
|
|
];
|
|
|
|
const result = await query(sql, params);
|
|
logger.info("금형 생성", { companyCode, moldCode: mold_code });
|
|
res.json({ success: true, data: result[0], message: "금형이 등록되었습니다." });
|
|
} catch (error: any) {
|
|
if (error.code === "23505") {
|
|
res.status(409).json({ success: false, message: "이미 존재하는 금형코드입니다." });
|
|
return;
|
|
}
|
|
logger.error("금형 생성 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function updateMold(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { moldCode } = req.params;
|
|
const {
|
|
mold_name, mold_type, category, manufacturer,
|
|
manufacturing_number, manufacturing_date, cavity_count,
|
|
shot_count, mold_quantity, base_input_qty, operation_status,
|
|
remarks, image_path, memo, warranty_shot_count,
|
|
} = req.body;
|
|
|
|
const sql = `
|
|
UPDATE mold_mng SET
|
|
mold_name = COALESCE($1, mold_name),
|
|
mold_type = $2, category = $3, manufacturer = $4,
|
|
manufacturing_number = $5, manufacturing_date = $6,
|
|
cavity_count = COALESCE($7, cavity_count),
|
|
shot_count = COALESCE($8, shot_count),
|
|
mold_quantity = COALESCE($9, mold_quantity),
|
|
base_input_qty = COALESCE($10, base_input_qty),
|
|
operation_status = COALESCE($11, operation_status),
|
|
remarks = $12, image_path = $13, memo = $14,
|
|
warranty_shot_count = $15,
|
|
updated_date = NOW()
|
|
WHERE mold_code = $16 AND company_code = $17
|
|
RETURNING *
|
|
`;
|
|
const params = [
|
|
mold_name, mold_type, category, manufacturer,
|
|
manufacturing_number, manufacturing_date,
|
|
cavity_count, shot_count, mold_quantity, base_input_qty,
|
|
operation_status, remarks, image_path, memo,
|
|
warranty_shot_count || 0, moldCode, companyCode,
|
|
];
|
|
|
|
const result = await query(sql, params);
|
|
if (result.length === 0) {
|
|
res.status(404).json({ success: false, message: "금형을 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("금형 수정", { companyCode, moldCode });
|
|
res.json({ success: true, data: result[0], message: "금형이 수정되었습니다." });
|
|
} catch (error: any) {
|
|
logger.error("금형 수정 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function deleteMold(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { moldCode } = req.params;
|
|
|
|
// 관련 데이터 먼저 삭제
|
|
await query(`DELETE FROM mold_serial WHERE mold_code = $1 AND company_code = $2`, [moldCode, companyCode]);
|
|
await query(`DELETE FROM mold_inspection_item WHERE mold_code = $1 AND company_code = $2`, [moldCode, companyCode]);
|
|
await query(`DELETE FROM mold_part WHERE mold_code = $1 AND company_code = $2`, [moldCode, companyCode]);
|
|
|
|
const result = await query(
|
|
`DELETE FROM mold_mng WHERE mold_code = $1 AND company_code = $2 RETURNING id`,
|
|
[moldCode, companyCode]
|
|
);
|
|
|
|
if (result.length === 0) {
|
|
res.status(404).json({ success: false, message: "금형을 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("금형 삭제", { companyCode, moldCode });
|
|
res.json({ success: true, message: "금형이 삭제되었습니다." });
|
|
} catch (error: any) {
|
|
logger.error("금형 삭제 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 일련번호 CRUD
|
|
// ============================================
|
|
|
|
export async function getMoldSerials(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { moldCode } = req.params;
|
|
|
|
const sql = `SELECT * FROM mold_serial WHERE mold_code = $1 AND company_code = $2 ORDER BY serial_number`;
|
|
const result = await query(sql, [moldCode, companyCode]);
|
|
|
|
res.json({ success: true, data: result });
|
|
} catch (error: any) {
|
|
logger.error("일련번호 목록 조회 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function createMoldSerial(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const userId = req.user!.userId;
|
|
const { moldCode } = req.params;
|
|
const { serial_number, status, progress, work_description, manager, completion_date, remarks, current_shot_count, storage_location } = req.body;
|
|
|
|
let finalSerialNumber = serial_number;
|
|
|
|
// 일련번호가 비어있으면 채번 규칙으로 자동 생성
|
|
if (!finalSerialNumber) {
|
|
try {
|
|
const { numberingRuleService } = await import("../services/numberingRuleService");
|
|
const rule = await numberingRuleService.getNumberingRuleByColumn(
|
|
companyCode,
|
|
"mold_serial",
|
|
"serial_number"
|
|
);
|
|
|
|
if (rule) {
|
|
// formData에 mold_code를 포함 (reference 파트에서 참조)
|
|
const formData = { mold_code: moldCode, ...req.body };
|
|
finalSerialNumber = await numberingRuleService.allocateCode(
|
|
rule.ruleId,
|
|
companyCode,
|
|
formData
|
|
);
|
|
logger.info("일련번호 자동 채번 완료", { serialNumber: finalSerialNumber, ruleId: rule.ruleId });
|
|
}
|
|
} catch (numError: any) {
|
|
logger.error("일련번호 자동 채번 실패", { error: numError.message });
|
|
}
|
|
}
|
|
|
|
if (!finalSerialNumber) {
|
|
res.status(400).json({ success: false, message: "일련번호를 생성할 수 없습니다. 채번 규칙을 확인해주세요." });
|
|
return;
|
|
}
|
|
|
|
const sql = `
|
|
INSERT INTO mold_serial (id, company_code, mold_code, serial_number, status, progress, work_description, manager, completion_date, remarks, current_shot_count, storage_location, writer, created_date)
|
|
VALUES (gen_random_uuid()::text,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,NOW())
|
|
RETURNING *
|
|
`;
|
|
const params = [
|
|
companyCode, moldCode, finalSerialNumber, status || "STORED",
|
|
progress || 0, work_description || null, manager || null,
|
|
completion_date || null, remarks || null, current_shot_count || 0,
|
|
storage_location || null, userId,
|
|
];
|
|
|
|
const result = await query(sql, params);
|
|
res.json({ success: true, data: result[0], message: "일련번호가 등록되었습니다." });
|
|
} catch (error: any) {
|
|
if (error.code === "23505") {
|
|
res.status(409).json({ success: false, message: "이미 존재하는 일련번호입니다." });
|
|
return;
|
|
}
|
|
logger.error("일련번호 생성 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function updateMoldSerial(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
const { status, current_shot_count, storage_location, remarks } = req.body;
|
|
|
|
const sql = `
|
|
UPDATE mold_serial SET
|
|
status = COALESCE($1, status),
|
|
current_shot_count = COALESCE($2, current_shot_count),
|
|
storage_location = $3,
|
|
remarks = $4,
|
|
updated_date = NOW()
|
|
WHERE id = $5 AND company_code = $6
|
|
RETURNING *
|
|
`;
|
|
const params = [status, current_shot_count, storage_location || null, remarks || null, id, companyCode];
|
|
const result = await query(sql, params);
|
|
|
|
if (result.length === 0) {
|
|
res.status(404).json({ success: false, message: "일련번호를 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
logger.info("일련번호 수정", { companyCode, id });
|
|
res.json({ success: true, data: result[0], message: "일련번호가 수정되었습니다." });
|
|
} catch (error: any) {
|
|
logger.error("일련번호 수정 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function deleteMoldSerial(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
|
|
const result = await query(
|
|
`DELETE FROM mold_serial WHERE id = $1 AND company_code = $2 RETURNING id`,
|
|
[id, companyCode]
|
|
);
|
|
|
|
if (result.length === 0) {
|
|
res.status(404).json({ success: false, message: "일련번호를 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, message: "일련번호가 삭제되었습니다." });
|
|
} catch (error: any) {
|
|
logger.error("일련번호 삭제 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 점검항목 CRUD
|
|
// ============================================
|
|
|
|
export async function getMoldInspections(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { moldCode } = req.params;
|
|
|
|
const sql = `SELECT * FROM mold_inspection_item WHERE mold_code = $1 AND company_code = $2 ORDER BY created_date`;
|
|
const result = await query(sql, [moldCode, companyCode]);
|
|
|
|
res.json({ success: true, data: result });
|
|
} catch (error: any) {
|
|
logger.error("점검항목 조회 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function createMoldInspection(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const userId = req.user!.userId;
|
|
const { moldCode } = req.params;
|
|
const {
|
|
inspection_item, inspection_cycle, inspection_method,
|
|
inspection_content, lower_limit, upper_limit, unit,
|
|
is_active, checklist, remarks,
|
|
} = req.body;
|
|
|
|
if (!inspection_item) {
|
|
res.status(400).json({ success: false, message: "점검항목명은 필수입니다." });
|
|
return;
|
|
}
|
|
|
|
const sql = `
|
|
INSERT INTO mold_inspection_item (
|
|
id, company_code, mold_code, inspection_item, inspection_cycle,
|
|
inspection_method, inspection_content, lower_limit, upper_limit,
|
|
unit, is_active, checklist, remarks, writer, created_date
|
|
) VALUES (gen_random_uuid()::text,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,NOW())
|
|
RETURNING *
|
|
`;
|
|
const params = [
|
|
companyCode, moldCode, inspection_item, inspection_cycle || null,
|
|
inspection_method || null, inspection_content || null,
|
|
lower_limit || null, upper_limit || null, unit || null,
|
|
is_active || "Y", checklist || null, remarks || null, userId,
|
|
];
|
|
|
|
const result = await query(sql, params);
|
|
res.json({ success: true, data: result[0], message: "점검항목이 등록되었습니다." });
|
|
} catch (error: any) {
|
|
logger.error("점검항목 생성 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function deleteMoldInspection(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
|
|
const result = await query(
|
|
`DELETE FROM mold_inspection_item WHERE id = $1 AND company_code = $2 RETURNING id`,
|
|
[id, companyCode]
|
|
);
|
|
|
|
if (result.length === 0) {
|
|
res.status(404).json({ success: false, message: "점검항목을 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, message: "점검항목이 삭제되었습니다." });
|
|
} catch (error: any) {
|
|
logger.error("점검항목 삭제 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 부품 CRUD
|
|
// ============================================
|
|
|
|
export async function getMoldParts(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { moldCode } = req.params;
|
|
|
|
const sql = `SELECT * FROM mold_part WHERE mold_code = $1 AND company_code = $2 ORDER BY created_date`;
|
|
const result = await query(sql, [moldCode, companyCode]);
|
|
|
|
res.json({ success: true, data: result });
|
|
} catch (error: any) {
|
|
logger.error("부품 목록 조회 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function createMoldPart(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const userId = req.user!.userId;
|
|
const { moldCode } = req.params;
|
|
const {
|
|
part_name, replacement_cycle, unit, specification,
|
|
manufacturer, manufacturer_code, image_path, remarks,
|
|
} = req.body;
|
|
|
|
if (!part_name) {
|
|
res.status(400).json({ success: false, message: "부품명은 필수입니다." });
|
|
return;
|
|
}
|
|
|
|
const sql = `
|
|
INSERT INTO mold_part (
|
|
id, company_code, mold_code, part_name, replacement_cycle,
|
|
unit, specification, manufacturer, manufacturer_code,
|
|
image_path, remarks, writer, created_date
|
|
) VALUES (gen_random_uuid()::text,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,NOW())
|
|
RETURNING *
|
|
`;
|
|
const params = [
|
|
companyCode, moldCode, part_name, replacement_cycle || null,
|
|
unit || null, specification || null, manufacturer || null,
|
|
manufacturer_code || null, image_path || null, remarks || null, userId,
|
|
];
|
|
|
|
const result = await query(sql, params);
|
|
res.json({ success: true, data: result[0], message: "부품이 등록되었습니다." });
|
|
} catch (error: any) {
|
|
logger.error("부품 생성 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
export async function deleteMoldPart(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { id } = req.params;
|
|
|
|
const result = await query(
|
|
`DELETE FROM mold_part WHERE id = $1 AND company_code = $2 RETURNING id`,
|
|
[id, companyCode]
|
|
);
|
|
|
|
if (result.length === 0) {
|
|
res.status(404).json({ success: false, message: "부품을 찾을 수 없습니다." });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, message: "부품이 삭제되었습니다." });
|
|
} catch (error: any) {
|
|
logger.error("부품 삭제 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 일련번호 현황 집계
|
|
// ============================================
|
|
|
|
export async function getMoldSerialSummary(req: AuthenticatedRequest, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const { moldCode } = req.params;
|
|
|
|
// 카테고리 코드/영문코드/한글라벨 모두 대응
|
|
// 먼저 카테고리 값 조회하여 매핑
|
|
// mold_serial.status + mold_mng.operation_status 양쪽 카테고리 모두 조회
|
|
const catSql = `SELECT value_code, value_label FROM category_values
|
|
WHERE ((table_name='mold_serial' AND column_name='status') OR (table_name='mold_mng' AND column_name='operation_status'))
|
|
AND company_code=$1`;
|
|
const catRows = await query(catSql, [companyCode]);
|
|
|
|
// 카테고리 라벨 기준으로 그룹핑할 코드 목록 생성
|
|
const codesByLabel: Record<string, string[]> = { "사용중": ["IN_USE"], "수리중": ["REPAIR"], "보관중": ["STORED"], "폐기": ["DISPOSED"] };
|
|
for (const cat of catRows) {
|
|
const label = cat.value_label || "";
|
|
if (label.includes("사용")) (codesByLabel["사용중"] = codesByLabel["사용중"] || []).push(cat.value_code);
|
|
else if (label.includes("수리")) (codesByLabel["수리중"] = codesByLabel["수리중"] || []).push(cat.value_code);
|
|
else if (label.includes("보관") || label.includes("미사용")) (codesByLabel["보관중"] = codesByLabel["보관중"] || []).push(cat.value_code);
|
|
else if (label.includes("폐기")) (codesByLabel["폐기"] = codesByLabel["폐기"] || []).push(cat.value_code);
|
|
}
|
|
|
|
const inUseCodes = codesByLabel["사용중"].map(c => `'${c}'`).join(",");
|
|
const repairCodes = codesByLabel["수리중"].map(c => `'${c}'`).join(",");
|
|
const storedCodes = codesByLabel["보관중"].map(c => `'${c}'`).join(",");
|
|
const disposedCodes = codesByLabel["폐기"].map(c => `'${c}'`).join(",");
|
|
|
|
const sql = `
|
|
SELECT
|
|
COUNT(*) as total,
|
|
COUNT(*) FILTER (WHERE status IN (${inUseCodes})) as in_use,
|
|
COUNT(*) FILTER (WHERE status IN (${repairCodes})) as repair,
|
|
COUNT(*) FILTER (WHERE status IN (${storedCodes})) as stored,
|
|
COUNT(*) FILTER (WHERE status IN (${disposedCodes})) as disposed
|
|
FROM mold_serial
|
|
WHERE mold_code = $1 AND company_code = $2
|
|
`;
|
|
const result = await query(sql, [moldCode, companyCode]);
|
|
|
|
res.json({ success: true, data: result[0] });
|
|
} catch (error: any) {
|
|
logger.error("일련번호 현황 조회 오류", error);
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|