feat: Implement default version management for routing versions

- Added functionality to set and unset default versions for routing items.
- Introduced new API endpoints for setting and unsetting default versions.
- Enhanced the ItemRoutingComponent to support toggling default versions with user feedback.
- Updated database queries to handle default version logic effectively.
- Improved the overall user experience by allowing easy management of routing versions.
This commit is contained in:
kjs
2026-02-24 18:22:54 +09:00
parent 0b6c305024
commit cb4fa2aaba
13 changed files with 1084 additions and 347 deletions

View File

@@ -39,16 +39,18 @@ export async function getItemsWithRouting(req: AuthenticatedRequest, res: Respon
if (search) params.push(`%${search}%`);
const query = `
SELECT DISTINCT
SELECT
i.id,
i.${nameColumn} AS item_name,
i.${codeColumn} AS item_code
i.${codeColumn} AS item_code,
COUNT(rv.id) AS routing_count
FROM ${tableName} i
INNER JOIN ${routingTable} rv ON rv.${routingFkColumn} = i.${codeColumn}
LEFT 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}
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);
@@ -82,10 +84,10 @@ export async function getRoutingsWithProcesses(req: AuthenticatedRequest, res: R
// 라우팅 버전 목록
const versionsQuery = `
SELECT id, version_name, description, created_date
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 created_date DESC
ORDER BY is_default DESC, created_date DESC
`;
const versionsResult = await getPool().query(versionsQuery, [
itemCode,
@@ -127,6 +129,92 @@ export async function getRoutingsWithProcesses(req: AuthenticatedRequest, res: R
}
}
// ============================================================
// 기본 버전 설정
// ============================================================
/**
* 라우팅 버전을 기본 버전으로 설정
* 같은 품목의 다른 버전은 기본 해제
*/
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
// ============================================================
@@ -330,7 +418,10 @@ export async function getWorkItemDetails(req: AuthenticatedRequest, res: Respons
const { workItemId } = req.params;
const query = `
SELECT id, work_item_id, detail_type, content, is_required, sort_order, remark, created_date
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
@@ -355,7 +446,11 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
return res.status(401).json({ success: false, message: "인증 필요" });
}
const { work_item_id, detail_type, content, is_required, sort_order, remark } = req.body;
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({
@@ -375,8 +470,10 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
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)
(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 *
`;
@@ -389,6 +486,15 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
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 });
@@ -410,7 +516,11 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
}
const { id } = req.params;
const { detail_type, content, is_required, sort_order, remark } = req.body;
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
@@ -419,6 +529,15 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
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 *
@@ -432,6 +551,15 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
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) {
@@ -544,8 +672,10 @@ export async function saveAll(req: AuthenticatedRequest, res: Response) {
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)`,
(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,
@@ -555,6 +685,15 @@ export async function saveAll(req: AuthenticatedRequest, res: Response) {
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,
]
);
}

View File

@@ -14,6 +14,10 @@ router.use(authenticateToken);
router.get("/items", ctrl.getItemsWithRouting);
router.get("/items/:itemCode/routings", ctrl.getRoutingsWithProcesses);
// 기본 버전 설정/해제
router.put("/versions/:versionId/set-default", ctrl.setDefaultVersion);
router.put("/versions/:versionId/unset-default", ctrl.unsetDefaultVersion);
// 작업 항목 CRUD
router.get("/routing-detail/:routingDetailId/work-items", ctrl.getWorkItems);
router.post("/work-items", ctrl.createWorkItem);