feat: Enhance approval request handling and user management
- Updated the approval request controller to include target_record_id in query parameters for improved filtering. - Refactored the approval request creation logic to merge approval_mode into target_record_data, allowing for better handling of approval processes. - Enhanced the user search functionality in the approval request modal to accommodate additional user attributes such as position and department. - Improved error handling messages for clarity regarding required fields in the approval request modal. - Added new menu item for accessing the approval box directly from user dropdown and app layout. Made-with: Cursor
This commit is contained in:
@@ -481,7 +481,7 @@ export class ApprovalRequestController {
|
||||
return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
|
||||
}
|
||||
|
||||
const { status, target_table, requester_id, my_approvals, page = "1", limit = "20" } = req.query;
|
||||
const { status, target_table, target_record_id, requester_id, my_approvals, page = "1", limit = "20" } = req.query;
|
||||
|
||||
const conditions: string[] = ["r.company_code = $1"];
|
||||
const params: any[] = [companyCode];
|
||||
@@ -495,6 +495,10 @@ export class ApprovalRequestController {
|
||||
conditions.push(`r.target_table = $${idx++}`);
|
||||
params.push(target_table);
|
||||
}
|
||||
if (target_record_id) {
|
||||
conditions.push(`r.target_record_id = $${idx++}`);
|
||||
params.push(target_record_id);
|
||||
}
|
||||
if (requester_id) {
|
||||
conditions.push(`r.requester_id = $${idx++}`);
|
||||
params.push(requester_id);
|
||||
@@ -595,10 +599,11 @@ export class ApprovalRequestController {
|
||||
title, description, definition_id, target_table, target_record_id,
|
||||
target_record_data, screen_id, button_component_id,
|
||||
approvers, // [{ approver_id, approver_name, approver_position, approver_dept, approver_label }]
|
||||
approval_mode, // "sequential" | "parallel"
|
||||
} = req.body;
|
||||
|
||||
if (!title || !target_table || !target_record_id) {
|
||||
return res.status(400).json({ success: false, message: "제목, 대상 테이블, 대상 레코드 ID는 필수입니다." });
|
||||
if (!title || !target_table) {
|
||||
return res.status(400).json({ success: false, message: "제목과 대상 테이블은 필수입니다." });
|
||||
}
|
||||
|
||||
if (!Array.isArray(approvers) || approvers.length === 0) {
|
||||
@@ -609,6 +614,15 @@ export class ApprovalRequestController {
|
||||
const userName = req.user?.userName || "";
|
||||
const deptName = req.user?.deptName || "";
|
||||
|
||||
const isParallel = approval_mode === "parallel";
|
||||
const totalSteps = approvers.length;
|
||||
|
||||
// approval_mode를 target_record_data에 병합 저장
|
||||
const mergedRecordData = {
|
||||
...(target_record_data || {}),
|
||||
approval_mode: approval_mode || "sequential",
|
||||
};
|
||||
|
||||
let result: any;
|
||||
await transaction(async (client) => {
|
||||
// 결재 요청 생성
|
||||
@@ -621,18 +635,21 @@ export class ApprovalRequestController {
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, 'requested', 1, $7, $8, $9, $10, $11, $12, $13)
|
||||
RETURNING *`,
|
||||
[
|
||||
title, description, definition_id, target_table, target_record_id,
|
||||
target_record_data ? JSON.stringify(target_record_data) : null,
|
||||
approvers.length,
|
||||
title, description, definition_id, target_table, target_record_id || null,
|
||||
JSON.stringify(mergedRecordData),
|
||||
totalSteps,
|
||||
userId, userName, deptName,
|
||||
screen_id, button_component_id, companyCode,
|
||||
]
|
||||
);
|
||||
result = reqRows[0];
|
||||
|
||||
// 결재 라인 생성 (첫 번째 단계는 pending, 나머지는 waiting)
|
||||
// 결재 라인 생성
|
||||
// 동시결재: 모든 결재자 pending (step_order는 고유값) / 다단결재: 첫 번째만 pending
|
||||
for (let i = 0; i < approvers.length; i++) {
|
||||
const approver = approvers[i];
|
||||
const lineStatus = isParallel ? "pending" : (i === 0 ? "pending" : "waiting");
|
||||
|
||||
await client.query(
|
||||
`INSERT INTO approval_lines (
|
||||
request_id, step_order, approver_id, approver_name, approver_position,
|
||||
@@ -645,8 +662,8 @@ export class ApprovalRequestController {
|
||||
approver.approver_name || null,
|
||||
approver.approver_position || null,
|
||||
approver.approver_dept || null,
|
||||
approver.approver_label || `${i + 1}차 결재`,
|
||||
i === 0 ? "pending" : "waiting",
|
||||
approver.approver_label || (isParallel ? "동시 결재" : `${i + 1}차 결재`),
|
||||
lineStatus,
|
||||
companyCode,
|
||||
]
|
||||
);
|
||||
@@ -777,29 +794,58 @@ export class ApprovalLineController {
|
||||
WHERE request_id = $3`,
|
||||
[userId, comment || null, line.request_id]
|
||||
);
|
||||
// 남은 pending/waiting 라인도 skipped 처리
|
||||
await client.query(
|
||||
`UPDATE approval_lines SET status = 'skipped'
|
||||
WHERE request_id = $1 AND status IN ('pending', 'waiting') AND line_id != $2`,
|
||||
[line.request_id, lineId]
|
||||
);
|
||||
} else {
|
||||
// 승인: 다음 단계 활성화 또는 최종 완료
|
||||
const nextStep = line.step_order + 1;
|
||||
// 승인: 동시결재 vs 다단결재 분기
|
||||
const recordData = request.target_record_data;
|
||||
const isParallelMode = recordData?.approval_mode === "parallel";
|
||||
|
||||
if (nextStep <= request.total_steps) {
|
||||
// 다음 결재자를 pending으로 변경
|
||||
await client.query(
|
||||
`UPDATE approval_lines SET status = 'pending'
|
||||
WHERE request_id = $1 AND step_order = $2 AND company_code = $3`,
|
||||
[line.request_id, nextStep, companyCode]
|
||||
);
|
||||
await client.query(
|
||||
`UPDATE approval_requests SET current_step = $1, updated_at = NOW() WHERE request_id = $2`,
|
||||
[nextStep, line.request_id]
|
||||
if (isParallelMode) {
|
||||
// 동시결재: 남은 pending 라인이 있는지 확인
|
||||
const { rows: remainingLines } = await client.query(
|
||||
`SELECT COUNT(*) as cnt FROM approval_lines
|
||||
WHERE request_id = $1 AND status = 'pending' AND line_id != $2 AND company_code = $3`,
|
||||
[line.request_id, lineId, companyCode]
|
||||
);
|
||||
const remaining = parseInt(remainingLines[0]?.cnt || "0");
|
||||
|
||||
if (remaining === 0) {
|
||||
// 모든 동시 결재자 승인 완료 → 최종 승인
|
||||
await client.query(
|
||||
`UPDATE approval_requests SET status = 'approved', final_approver_id = $1, final_comment = $2,
|
||||
completed_at = NOW(), updated_at = NOW()
|
||||
WHERE request_id = $3`,
|
||||
[userId, comment || null, line.request_id]
|
||||
);
|
||||
}
|
||||
// 아직 남은 결재자 있으면 대기 (상태 변경 없음)
|
||||
} else {
|
||||
// 마지막 단계 승인 → 최종 완료
|
||||
await client.query(
|
||||
`UPDATE approval_requests SET status = 'approved', final_approver_id = $1, final_comment = $2,
|
||||
completed_at = NOW(), updated_at = NOW()
|
||||
WHERE request_id = $3`,
|
||||
[userId, comment || null, line.request_id]
|
||||
);
|
||||
// 다단결재: 다음 단계 활성화 또는 최종 완료
|
||||
const nextStep = line.step_order + 1;
|
||||
|
||||
if (nextStep <= request.total_steps) {
|
||||
await client.query(
|
||||
`UPDATE approval_lines SET status = 'pending'
|
||||
WHERE request_id = $1 AND step_order = $2 AND company_code = $3`,
|
||||
[line.request_id, nextStep, companyCode]
|
||||
);
|
||||
await client.query(
|
||||
`UPDATE approval_requests SET current_step = $1, updated_at = NOW() WHERE request_id = $2`,
|
||||
[nextStep, line.request_id]
|
||||
);
|
||||
} else {
|
||||
await client.query(
|
||||
`UPDATE approval_requests SET status = 'approved', final_approver_id = $1, final_comment = $2,
|
||||
completed_at = NOW(), updated_at = NOW()
|
||||
WHERE request_id = $3`,
|
||||
[userId, comment || null, line.request_id]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user