ERP-node/backend-node/src/controllers/processWorkStandardControll...

574 lines
18 KiB
TypeScript
Raw Normal View History

/**
*
* /
*/
import { Request, Response } from "express";
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
// ============================================================
// 품목/라우팅/공정 조회 (좌측 트리 데이터)
// ============================================================
/**
*
* 쿼리: tableName(), nameColumn, codeColumn
*/
export async function getItemsWithRouting(req: Request, 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 DISTINCT
i.id,
i.${nameColumn} AS item_name,
i.${codeColumn} AS item_code
FROM ${tableName} i
INNER JOIN ${routingTable} rv ON rv.${routingFkColumn} = i.${codeColumn}
AND rv.company_code = i.company_code
WHERE i.company_code = $1
${searchCondition}
ORDER BY i.${codeColumn}
`;
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: Request, 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
FROM ${routingVersionTable}
WHERE ${routingFkColumn} = $1 AND company_code = $2
ORDER BY 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 });
}
}
// ============================================================
// 작업 항목 CRUD
// ============================================================
/**
* (phase별 )
*/
export async function getWorkItems(req: Request, 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: Request, 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: Request, 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: Request, 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: Request, 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, 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: Request, 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 } = 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)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING *
`;
const result = await getPool().query(query, [
companyCode,
work_item_id,
detail_type || null,
content,
is_required || "N",
sort_order || 0,
remark || 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 updateWorkItemDetail(req: Request, 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 } = 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),
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,
]);
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: Request, 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: Request, 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)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
[
companyCode,
workItemId,
detail.detail_type || null,
detail.content,
detail.is_required || "N",
detail.sort_order || 0,
detail.remark || null,
writer,
]
);
}
}
}
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();
}
}