714 lines
23 KiB
TypeScript
714 lines
23 KiB
TypeScript
/**
|
|
* 공정 작업기준 컨트롤러
|
|
* 품목별 라우팅/공정에 대한 작업 항목 및 상세 관리
|
|
*/
|
|
|
|
import { Response } from "express";
|
|
import { getPool } from "../database/db";
|
|
import { logger } from "../utils/logger";
|
|
import { AuthenticatedRequest } from "../types/auth";
|
|
|
|
// ============================================================
|
|
// 품목/라우팅/공정 조회 (좌측 트리 데이터)
|
|
// ============================================================
|
|
|
|
/**
|
|
* 라우팅이 있는 품목 목록 조회
|
|
* 요청 쿼리: tableName(품목테이블), nameColumn, codeColumn
|
|
*/
|
|
export async function getItemsWithRouting(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const {
|
|
tableName = "item_info",
|
|
nameColumn = "item_name",
|
|
codeColumn = "item_number",
|
|
routingTable = "item_routing_version",
|
|
routingFkColumn = "item_code",
|
|
search = "",
|
|
} = req.query as Record<string, string>;
|
|
|
|
const searchCondition = search
|
|
? `AND (i.${nameColumn} ILIKE $2 OR i.${codeColumn} ILIKE $2)`
|
|
: "";
|
|
const params: any[] = [companyCode];
|
|
if (search) params.push(`%${search}%`);
|
|
|
|
const query = `
|
|
SELECT
|
|
i.id,
|
|
i.${nameColumn} AS item_name,
|
|
i.${codeColumn} AS item_code,
|
|
COUNT(rv.id) AS routing_count
|
|
FROM ${tableName} i
|
|
LEFT JOIN ${routingTable} rv ON rv.${routingFkColumn} = i.${codeColumn}
|
|
AND rv.company_code = i.company_code
|
|
WHERE i.company_code = $1
|
|
${searchCondition}
|
|
GROUP BY i.id, i.${nameColumn}, i.${codeColumn}, i.created_date
|
|
ORDER BY i.created_date DESC NULLS LAST
|
|
`;
|
|
|
|
const result = await getPool().query(query, params);
|
|
|
|
return res.json({ success: true, data: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("품목 목록 조회 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 품목별 라우팅 버전 + 공정 목록 조회 (트리 하위 데이터)
|
|
*/
|
|
export async function getRoutingsWithProcesses(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { itemCode } = req.params;
|
|
const {
|
|
routingVersionTable = "item_routing_version",
|
|
routingDetailTable = "item_routing_detail",
|
|
routingFkColumn = "item_code",
|
|
processTable = "process_mng",
|
|
processNameColumn = "process_name",
|
|
processCodeColumn = "process_code",
|
|
} = req.query as Record<string, string>;
|
|
|
|
// 라우팅 버전 목록
|
|
const versionsQuery = `
|
|
SELECT id, version_name, description, created_date, COALESCE(is_default, false) AS is_default
|
|
FROM ${routingVersionTable}
|
|
WHERE ${routingFkColumn} = $1 AND company_code = $2
|
|
ORDER BY is_default DESC, created_date DESC
|
|
`;
|
|
const versionsResult = await getPool().query(versionsQuery, [
|
|
itemCode,
|
|
companyCode,
|
|
]);
|
|
|
|
// 각 버전별 공정 목록
|
|
const routings = [];
|
|
for (const version of versionsResult.rows) {
|
|
const detailsQuery = `
|
|
SELECT
|
|
rd.id AS routing_detail_id,
|
|
rd.seq_no,
|
|
rd.process_code,
|
|
rd.is_required,
|
|
rd.work_type,
|
|
p.${processNameColumn} AS process_name
|
|
FROM ${routingDetailTable} rd
|
|
LEFT JOIN ${processTable} p ON p.${processCodeColumn} = rd.process_code
|
|
AND p.company_code = rd.company_code
|
|
WHERE rd.routing_version_id = $1 AND rd.company_code = $2
|
|
ORDER BY rd.seq_no::integer
|
|
`;
|
|
const detailsResult = await getPool().query(detailsQuery, [
|
|
version.id,
|
|
companyCode,
|
|
]);
|
|
|
|
routings.push({
|
|
...version,
|
|
processes: detailsResult.rows,
|
|
});
|
|
}
|
|
|
|
return res.json({ success: true, data: routings });
|
|
} catch (error: any) {
|
|
logger.error("라우팅/공정 조회 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 기본 버전 설정
|
|
// ============================================================
|
|
|
|
/**
|
|
* 라우팅 버전을 기본 버전으로 설정
|
|
* 같은 품목의 다른 버전은 기본 해제
|
|
*/
|
|
export async function setDefaultVersion(req: AuthenticatedRequest, res: Response) {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { versionId } = req.params;
|
|
const {
|
|
routingVersionTable = "item_routing_version",
|
|
routingFkColumn = "item_code",
|
|
} = req.body;
|
|
|
|
await client.query("BEGIN");
|
|
|
|
const versionResult = await client.query(
|
|
`SELECT ${routingFkColumn} AS item_code FROM ${routingVersionTable} WHERE id = $1 AND company_code = $2`,
|
|
[versionId, companyCode]
|
|
);
|
|
|
|
if (versionResult.rowCount === 0) {
|
|
await client.query("ROLLBACK");
|
|
return res.status(404).json({ success: false, message: "버전을 찾을 수 없습니다" });
|
|
}
|
|
|
|
const itemCode = versionResult.rows[0].item_code;
|
|
|
|
await client.query(
|
|
`UPDATE ${routingVersionTable} SET is_default = false WHERE ${routingFkColumn} = $1 AND company_code = $2`,
|
|
[itemCode, companyCode]
|
|
);
|
|
|
|
await client.query(
|
|
`UPDATE ${routingVersionTable} SET is_default = true WHERE id = $1 AND company_code = $2`,
|
|
[versionId, companyCode]
|
|
);
|
|
|
|
await client.query("COMMIT");
|
|
|
|
logger.info("기본 버전 설정", { companyCode, versionId, itemCode });
|
|
return res.json({ success: true, message: "기본 버전이 설정되었습니다" });
|
|
} catch (error: any) {
|
|
await client.query("ROLLBACK");
|
|
logger.error("기본 버전 설정 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 기본 버전 해제
|
|
*/
|
|
export async function unsetDefaultVersion(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { versionId } = req.params;
|
|
const { routingVersionTable = "item_routing_version" } = req.body;
|
|
|
|
await getPool().query(
|
|
`UPDATE ${routingVersionTable} SET is_default = false WHERE id = $1 AND company_code = $2`,
|
|
[versionId, companyCode]
|
|
);
|
|
|
|
logger.info("기본 버전 해제", { companyCode, versionId });
|
|
return res.json({ success: true, message: "기본 버전이 해제되었습니다" });
|
|
} catch (error: any) {
|
|
logger.error("기본 버전 해제 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 작업 항목 CRUD
|
|
// ============================================================
|
|
|
|
/**
|
|
* 공정별 작업 항목 목록 조회 (phase별 그룹)
|
|
*/
|
|
export async function getWorkItems(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { routingDetailId } = req.params;
|
|
|
|
const query = `
|
|
SELECT
|
|
wi.id,
|
|
wi.routing_detail_id,
|
|
wi.work_phase,
|
|
wi.title,
|
|
wi.is_required,
|
|
wi.sort_order,
|
|
wi.description,
|
|
wi.created_date,
|
|
(SELECT COUNT(*) FROM process_work_item_detail d
|
|
WHERE d.work_item_id = wi.id AND d.company_code = wi.company_code
|
|
)::integer AS detail_count
|
|
FROM process_work_item wi
|
|
WHERE wi.routing_detail_id = $1 AND wi.company_code = $2
|
|
ORDER BY wi.work_phase, wi.sort_order, wi.created_date
|
|
`;
|
|
|
|
const result = await getPool().query(query, [routingDetailId, companyCode]);
|
|
|
|
// phase별 그룹핑
|
|
const grouped: Record<string, any[]> = {};
|
|
for (const row of result.rows) {
|
|
const phase = row.work_phase;
|
|
if (!grouped[phase]) grouped[phase] = [];
|
|
grouped[phase].push(row);
|
|
}
|
|
|
|
return res.json({ success: true, data: grouped, items: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("작업 항목 조회 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 작업 항목 추가
|
|
*/
|
|
export async function createWorkItem(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
const writer = req.user?.userId;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { routing_detail_id, work_phase, title, is_required, sort_order, description } = req.body;
|
|
|
|
if (!routing_detail_id || !work_phase || !title) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "routing_detail_id, work_phase, title은 필수입니다",
|
|
});
|
|
}
|
|
|
|
const query = `
|
|
INSERT INTO process_work_item
|
|
(company_code, routing_detail_id, work_phase, title, is_required, sort_order, description, writer)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
RETURNING *
|
|
`;
|
|
|
|
const result = await getPool().query(query, [
|
|
companyCode,
|
|
routing_detail_id,
|
|
work_phase,
|
|
title,
|
|
is_required || "N",
|
|
sort_order || 0,
|
|
description || null,
|
|
writer,
|
|
]);
|
|
|
|
logger.info("작업 항목 생성", { companyCode, id: result.rows[0].id });
|
|
return res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("작업 항목 생성 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 작업 항목 수정
|
|
*/
|
|
export async function updateWorkItem(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { id } = req.params;
|
|
const { title, is_required, sort_order, description } = req.body;
|
|
|
|
const query = `
|
|
UPDATE process_work_item
|
|
SET title = COALESCE($1, title),
|
|
is_required = COALESCE($2, is_required),
|
|
sort_order = COALESCE($3, sort_order),
|
|
description = COALESCE($4, description),
|
|
updated_date = NOW()
|
|
WHERE id = $5 AND company_code = $6
|
|
RETURNING *
|
|
`;
|
|
|
|
const result = await getPool().query(query, [
|
|
title,
|
|
is_required,
|
|
sort_order,
|
|
description,
|
|
id,
|
|
companyCode,
|
|
]);
|
|
|
|
if (result.rowCount === 0) {
|
|
return res.status(404).json({ success: false, message: "항목을 찾을 수 없습니다" });
|
|
}
|
|
|
|
logger.info("작업 항목 수정", { companyCode, id });
|
|
return res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("작업 항목 수정 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 작업 항목 삭제 (상세도 함께 삭제)
|
|
*/
|
|
export async function deleteWorkItem(req: AuthenticatedRequest, res: Response) {
|
|
const client = await getPool().connect();
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { id } = req.params;
|
|
|
|
await client.query("BEGIN");
|
|
|
|
// 상세 먼저 삭제
|
|
await client.query(
|
|
"DELETE FROM process_work_item_detail WHERE work_item_id = $1 AND company_code = $2",
|
|
[id, companyCode]
|
|
);
|
|
|
|
// 항목 삭제
|
|
const result = await client.query(
|
|
"DELETE FROM process_work_item WHERE id = $1 AND company_code = $2 RETURNING id",
|
|
[id, companyCode]
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
await client.query("ROLLBACK");
|
|
return res.status(404).json({ success: false, message: "항목을 찾을 수 없습니다" });
|
|
}
|
|
|
|
await client.query("COMMIT");
|
|
logger.info("작업 항목 삭제", { companyCode, id });
|
|
return res.json({ success: true });
|
|
} catch (error: any) {
|
|
await client.query("ROLLBACK");
|
|
logger.error("작업 항목 삭제 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 작업 항목 상세 CRUD
|
|
// ============================================================
|
|
|
|
/**
|
|
* 작업 항목 상세 목록 조회
|
|
*/
|
|
export async function getWorkItemDetails(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { workItemId } = req.params;
|
|
|
|
const query = `
|
|
SELECT id, work_item_id, detail_type, content, is_required, sort_order, remark,
|
|
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
|
duration_minutes, input_type, lookup_target, display_fields,
|
|
created_date
|
|
FROM process_work_item_detail
|
|
WHERE work_item_id = $1 AND company_code = $2
|
|
ORDER BY sort_order, created_date
|
|
`;
|
|
|
|
const result = await getPool().query(query, [workItemId, companyCode]);
|
|
return res.json({ success: true, data: result.rows });
|
|
} catch (error: any) {
|
|
logger.error("작업 항목 상세 조회 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 작업 항목 상세 추가
|
|
*/
|
|
export async function createWorkItemDetail(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
const writer = req.user?.userId;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const {
|
|
work_item_id, detail_type, content, is_required, sort_order, remark,
|
|
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
|
duration_minutes, input_type, lookup_target, display_fields,
|
|
} = req.body;
|
|
|
|
if (!work_item_id || !content) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "work_item_id, content는 필수입니다",
|
|
});
|
|
}
|
|
|
|
// work_item이 같은 company_code인지 검증
|
|
const ownerCheck = await getPool().query(
|
|
"SELECT id FROM process_work_item WHERE id = $1 AND company_code = $2",
|
|
[work_item_id, companyCode]
|
|
);
|
|
if (ownerCheck.rowCount === 0) {
|
|
return res.status(403).json({ success: false, message: "권한이 없습니다" });
|
|
}
|
|
|
|
const query = `
|
|
INSERT INTO process_work_item_detail
|
|
(company_code, work_item_id, detail_type, content, is_required, sort_order, remark, writer,
|
|
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
|
duration_minutes, input_type, lookup_target, display_fields)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
|
RETURNING *
|
|
`;
|
|
|
|
const result = await getPool().query(query, [
|
|
companyCode,
|
|
work_item_id,
|
|
detail_type || null,
|
|
content,
|
|
is_required || "N",
|
|
sort_order || 0,
|
|
remark || null,
|
|
writer,
|
|
inspection_code || null,
|
|
inspection_method || null,
|
|
unit || null,
|
|
lower_limit || null,
|
|
upper_limit || null,
|
|
duration_minutes || null,
|
|
input_type || null,
|
|
lookup_target || null,
|
|
display_fields || null,
|
|
]);
|
|
|
|
logger.info("작업 항목 상세 생성", { companyCode, id: result.rows[0].id });
|
|
return res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("작업 항목 상세 생성 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 작업 항목 상세 수정
|
|
*/
|
|
export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { id } = req.params;
|
|
const {
|
|
detail_type, content, is_required, sort_order, remark,
|
|
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
|
duration_minutes, input_type, lookup_target, display_fields,
|
|
} = req.body;
|
|
|
|
const query = `
|
|
UPDATE process_work_item_detail
|
|
SET detail_type = COALESCE($1, detail_type),
|
|
content = COALESCE($2, content),
|
|
is_required = COALESCE($3, is_required),
|
|
sort_order = COALESCE($4, sort_order),
|
|
remark = COALESCE($5, remark),
|
|
inspection_code = $8,
|
|
inspection_method = $9,
|
|
unit = $10,
|
|
lower_limit = $11,
|
|
upper_limit = $12,
|
|
duration_minutes = $13,
|
|
input_type = $14,
|
|
lookup_target = $15,
|
|
display_fields = $16,
|
|
updated_date = NOW()
|
|
WHERE id = $6 AND company_code = $7
|
|
RETURNING *
|
|
`;
|
|
|
|
const result = await getPool().query(query, [
|
|
detail_type,
|
|
content,
|
|
is_required,
|
|
sort_order,
|
|
remark,
|
|
id,
|
|
companyCode,
|
|
inspection_code || null,
|
|
inspection_method || null,
|
|
unit || null,
|
|
lower_limit || null,
|
|
upper_limit || null,
|
|
duration_minutes || null,
|
|
input_type || null,
|
|
lookup_target || null,
|
|
display_fields || null,
|
|
]);
|
|
|
|
if (result.rowCount === 0) {
|
|
return res.status(404).json({ success: false, message: "상세를 찾을 수 없습니다" });
|
|
}
|
|
|
|
logger.info("작업 항목 상세 수정", { companyCode, id });
|
|
return res.json({ success: true, data: result.rows[0] });
|
|
} catch (error: any) {
|
|
logger.error("작업 항목 상세 수정 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 작업 항목 상세 삭제
|
|
*/
|
|
export async function deleteWorkItemDetail(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { id } = req.params;
|
|
|
|
const result = await getPool().query(
|
|
"DELETE FROM process_work_item_detail WHERE id = $1 AND company_code = $2 RETURNING id",
|
|
[id, companyCode]
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
return res.status(404).json({ success: false, message: "상세를 찾을 수 없습니다" });
|
|
}
|
|
|
|
logger.info("작업 항목 상세 삭제", { companyCode, id });
|
|
return res.json({ success: true });
|
|
} catch (error: any) {
|
|
logger.error("작업 항목 상세 삭제 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 전체 저장 (일괄)
|
|
// ============================================================
|
|
|
|
/**
|
|
* 전체 저장: 작업 항목 + 상세를 일괄 저장
|
|
* 기존 데이터를 삭제하고 새로 삽입하는 replace 방식
|
|
*/
|
|
export async function saveAll(req: AuthenticatedRequest, res: Response) {
|
|
const client = await getPool().connect();
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
const writer = req.user?.userId;
|
|
if (!companyCode) {
|
|
return res.status(401).json({ success: false, message: "인증 필요" });
|
|
}
|
|
|
|
const { routing_detail_id, items } = req.body;
|
|
|
|
if (!routing_detail_id || !Array.isArray(items)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "routing_detail_id와 items 배열이 필요합니다",
|
|
});
|
|
}
|
|
|
|
await client.query("BEGIN");
|
|
|
|
// 기존 상세 삭제
|
|
await client.query(
|
|
`DELETE FROM process_work_item_detail
|
|
WHERE work_item_id IN (
|
|
SELECT id FROM process_work_item
|
|
WHERE routing_detail_id = $1 AND company_code = $2
|
|
)`,
|
|
[routing_detail_id, companyCode]
|
|
);
|
|
|
|
// 기존 항목 삭제
|
|
await client.query(
|
|
"DELETE FROM process_work_item WHERE routing_detail_id = $1 AND company_code = $2",
|
|
[routing_detail_id, companyCode]
|
|
);
|
|
|
|
// 새 항목 + 상세 삽입
|
|
for (const item of items) {
|
|
const itemResult = await client.query(
|
|
`INSERT INTO process_work_item
|
|
(company_code, routing_detail_id, work_phase, title, is_required, sort_order, description, writer)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
RETURNING id`,
|
|
[
|
|
companyCode,
|
|
routing_detail_id,
|
|
item.work_phase,
|
|
item.title,
|
|
item.is_required || "N",
|
|
item.sort_order || 0,
|
|
item.description || null,
|
|
writer,
|
|
]
|
|
);
|
|
|
|
const workItemId = itemResult.rows[0].id;
|
|
|
|
if (Array.isArray(item.details)) {
|
|
for (const detail of item.details) {
|
|
await client.query(
|
|
`INSERT INTO process_work_item_detail
|
|
(company_code, work_item_id, detail_type, content, is_required, sort_order, remark, writer,
|
|
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
|
duration_minutes, input_type, lookup_target, display_fields)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)`,
|
|
[
|
|
companyCode,
|
|
workItemId,
|
|
detail.detail_type || null,
|
|
detail.content,
|
|
detail.is_required || "N",
|
|
detail.sort_order || 0,
|
|
detail.remark || null,
|
|
writer,
|
|
detail.inspection_code || null,
|
|
detail.inspection_method || null,
|
|
detail.unit || null,
|
|
detail.lower_limit || null,
|
|
detail.upper_limit || null,
|
|
detail.duration_minutes || null,
|
|
detail.input_type || null,
|
|
detail.lookup_target || null,
|
|
detail.display_fields || null,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
await client.query("COMMIT");
|
|
logger.info("작업기준 전체 저장", { companyCode, routing_detail_id, itemCount: items.length });
|
|
return res.json({ success: true, message: "저장 완료" });
|
|
} catch (error: any) {
|
|
await client.query("ROLLBACK");
|
|
logger.error("작업기준 전체 저장 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|