[agent-pipeline] pipe-20260305162146-cqnu round-1
This commit is contained in:
@@ -1041,6 +1041,14 @@ export class ApprovalLineController {
|
||||
return res.status(400).json({ success: false, message: "액션은 approved 또는 rejected여야 합니다." });
|
||||
}
|
||||
|
||||
// 검증 에러를 트랜잭션 바깥으로 전달하기 위한 커스텀 에러 클래스
|
||||
class ValidationError extends Error {
|
||||
constructor(public statusCode: number, message: string) {
|
||||
super(message);
|
||||
this.name = "ValidationError";
|
||||
}
|
||||
}
|
||||
|
||||
await transaction(async (client) => {
|
||||
// FOR UPDATE로 결재 라인 잠금 (동시성 방어)
|
||||
const { rows: [line] } = await client.query(
|
||||
@@ -1049,13 +1057,11 @@ export class ApprovalLineController {
|
||||
);
|
||||
|
||||
if (!line) {
|
||||
res.status(404).json({ success: false, message: "결재 라인을 찾을 수 없습니다." });
|
||||
return;
|
||||
throw new ValidationError(404, "결재 라인을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
if (line.status !== "pending") {
|
||||
res.status(400).json({ success: false, message: "대기 중인 결재만 처리할 수 있습니다." });
|
||||
return;
|
||||
throw new ValidationError(400, "대기 중인 결재만 처리할 수 있습니다.");
|
||||
}
|
||||
|
||||
// 대결(proxy) 인증 로직
|
||||
@@ -1071,8 +1077,7 @@ export class ApprovalLineController {
|
||||
[line.approver_id, userId, companyCode]
|
||||
);
|
||||
if (proxyRows.length === 0) {
|
||||
res.status(403).json({ success: false, message: "본인이 결재자로 지정된 건만 처리할 수 있습니다." });
|
||||
return;
|
||||
throw new ValidationError(403, "본인이 결재자로 지정된 건만 처리할 수 있습니다.");
|
||||
}
|
||||
proxyFor = line.approver_id;
|
||||
proxyReasonVal = proxy_reason || proxyRows[0].reason || "대결 처리";
|
||||
@@ -1163,19 +1168,22 @@ export class ApprovalLineController {
|
||||
}
|
||||
});
|
||||
|
||||
// 트랜잭션이 res에 응답을 보내지 않은 경우 (정상 처리)
|
||||
if (!res.headersSent) {
|
||||
return res.json({ success: true, message: action === "approved" ? "승인 처리되었습니다." : "반려 처리되었습니다." });
|
||||
}
|
||||
return res.json({ success: true, message: action === "approved" ? "승인 처리되었습니다." : "반려 처리되었습니다." });
|
||||
} catch (error) {
|
||||
console.error("결재 처리 오류:", error);
|
||||
if (!res.headersSent) {
|
||||
return res.status(500).json({
|
||||
// ValidationError는 트랜잭션이 rollback된 후 적절한 HTTP 상태코드로 응답
|
||||
if (error instanceof Error && error.name === "ValidationError") {
|
||||
const validationErr = error as any;
|
||||
return res.status(validationErr.statusCode).json({
|
||||
success: false,
|
||||
message: "결재 처리 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
message: validationErr.message,
|
||||
});
|
||||
}
|
||||
console.error("결재 처리 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "결재 처리 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,211 +1217,3 @@ export class ApprovalLineController {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 대결 위임 설정 (Proxy Settings) CRUD
|
||||
// ============================================================
|
||||
|
||||
export class ApprovalProxyController {
|
||||
// 대결 위임 목록 조회
|
||||
static async getProxySettings(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const companyCode = req.user?.companyCode;
|
||||
if (!companyCode) {
|
||||
return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
|
||||
}
|
||||
|
||||
const { original_user_id, proxy_user_id, is_active } = req.query;
|
||||
|
||||
const conditions: string[] = ["company_code = $1"];
|
||||
const params: any[] = [companyCode];
|
||||
let idx = 2;
|
||||
|
||||
if (original_user_id) {
|
||||
conditions.push(`original_user_id = $${idx++}`);
|
||||
params.push(original_user_id);
|
||||
}
|
||||
if (proxy_user_id) {
|
||||
conditions.push(`proxy_user_id = $${idx++}`);
|
||||
params.push(proxy_user_id);
|
||||
}
|
||||
if (is_active) {
|
||||
conditions.push(`is_active = $${idx++}`);
|
||||
params.push(is_active);
|
||||
}
|
||||
|
||||
const rows = await query<any>(
|
||||
`SELECT * FROM approval_proxy_settings
|
||||
WHERE ${conditions.join(" AND ")}
|
||||
ORDER BY created_at DESC`,
|
||||
params
|
||||
);
|
||||
|
||||
return res.json({ success: true, data: rows });
|
||||
} catch (error) {
|
||||
console.error("대결 위임 목록 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "대결 위임 목록 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 대결 위임 생성
|
||||
static async createProxySetting(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const companyCode = req.user?.companyCode;
|
||||
if (!companyCode) {
|
||||
return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
|
||||
}
|
||||
|
||||
const { original_user_id, proxy_user_id, start_date, end_date, reason, is_active = "Y" } = req.body;
|
||||
|
||||
if (!original_user_id || !proxy_user_id) {
|
||||
return res.status(400).json({ success: false, message: "위임자와 대결자는 필수입니다." });
|
||||
}
|
||||
if (!start_date || !end_date) {
|
||||
return res.status(400).json({ success: false, message: "시작일과 종료일은 필수입니다." });
|
||||
}
|
||||
if (original_user_id === proxy_user_id) {
|
||||
return res.status(400).json({ success: false, message: "위임자와 대결자가 동일할 수 없습니다." });
|
||||
}
|
||||
|
||||
const [row] = await query<any>(
|
||||
`INSERT INTO approval_proxy_settings
|
||||
(original_user_id, proxy_user_id, start_date, end_date, reason, is_active, company_code)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *`,
|
||||
[original_user_id, proxy_user_id, start_date, end_date, reason || null, is_active, companyCode]
|
||||
);
|
||||
|
||||
return res.status(201).json({ success: true, data: row, message: "대결 위임이 생성되었습니다." });
|
||||
} catch (error) {
|
||||
console.error("대결 위임 생성 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "대결 위임 생성 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 대결 위임 수정
|
||||
static async updateProxySetting(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 existing = await queryOne<any>(
|
||||
"SELECT id FROM approval_proxy_settings WHERE id = $1 AND company_code = $2",
|
||||
[id, companyCode]
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
return res.status(404).json({ success: false, message: "대결 위임 설정을 찾을 수 없습니다." });
|
||||
}
|
||||
|
||||
const { original_user_id, proxy_user_id, start_date, end_date, reason, is_active } = req.body;
|
||||
|
||||
const fields: string[] = [];
|
||||
const params: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (original_user_id !== undefined) { fields.push(`original_user_id = $${idx++}`); params.push(original_user_id); }
|
||||
if (proxy_user_id !== undefined) { fields.push(`proxy_user_id = $${idx++}`); params.push(proxy_user_id); }
|
||||
if (start_date !== undefined) { fields.push(`start_date = $${idx++}`); params.push(start_date); }
|
||||
if (end_date !== undefined) { fields.push(`end_date = $${idx++}`); params.push(end_date); }
|
||||
if (reason !== undefined) { fields.push(`reason = $${idx++}`); params.push(reason); }
|
||||
if (is_active !== undefined) { fields.push(`is_active = $${idx++}`); params.push(is_active); }
|
||||
|
||||
fields.push(`updated_at = NOW()`);
|
||||
params.push(id, companyCode);
|
||||
|
||||
const [row] = await query<any>(
|
||||
`UPDATE approval_proxy_settings SET ${fields.join(", ")}
|
||||
WHERE id = $${idx++} AND company_code = $${idx++}
|
||||
RETURNING *`,
|
||||
params
|
||||
);
|
||||
|
||||
return res.json({ success: true, data: row, message: "대결 위임이 수정되었습니다." });
|
||||
} catch (error) {
|
||||
console.error("대결 위임 수정 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "대결 위임 수정 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 대결 위임 삭제
|
||||
static async deleteProxySetting(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 existing = await queryOne<any>(
|
||||
"SELECT id FROM approval_proxy_settings WHERE id = $1 AND company_code = $2",
|
||||
[id, companyCode]
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
return res.status(404).json({ success: false, message: "대결 위임 설정을 찾을 수 없습니다." });
|
||||
}
|
||||
|
||||
await query<any>(
|
||||
"DELETE FROM approval_proxy_settings WHERE id = $1 AND company_code = $2",
|
||||
[id, companyCode]
|
||||
);
|
||||
|
||||
return res.json({ success: true, message: "대결 위임이 삭제되었습니다." });
|
||||
} catch (error) {
|
||||
console.error("대결 위임 삭제 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "대결 위임 삭제 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 특정 사용자의 현재 활성 대결자 조회
|
||||
static async checkActiveProxy(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const companyCode = req.user?.companyCode;
|
||||
if (!companyCode) {
|
||||
return res.status(401).json({ success: false, message: "인증 정보가 없습니다." });
|
||||
}
|
||||
|
||||
const { userId: targetUserId } = req.query;
|
||||
|
||||
if (!targetUserId) {
|
||||
return res.status(400).json({ success: false, message: "userId 파라미터는 필수입니다." });
|
||||
}
|
||||
|
||||
const rows = await query<any>(
|
||||
`SELECT * FROM approval_proxy_settings
|
||||
WHERE original_user_id = $1 AND is_active = 'Y'
|
||||
AND start_date <= CURRENT_DATE AND end_date >= CURRENT_DATE
|
||||
AND company_code = $2`,
|
||||
[targetUserId, companyCode]
|
||||
);
|
||||
|
||||
return res.json({ success: true, data: rows });
|
||||
} catch (error) {
|
||||
console.error("활성 대결자 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "활성 대결자 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user