부서 read 기능 구현
This commit is contained in:
458
backend-node/src/controllers/departmentController.ts
Normal file
458
backend-node/src/controllers/departmentController.ts
Normal file
@@ -0,0 +1,458 @@
|
||||
import { Response } from "express";
|
||||
import { logger } from "../utils/logger";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { ApiResponse } from "../types/common";
|
||||
import { query, queryOne } from "../database/db";
|
||||
|
||||
/**
|
||||
* 부서 목록 조회 (회사별)
|
||||
*/
|
||||
export async function getDepartments(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
|
||||
logger.info("부서 목록 조회", { companyCode, userCompanyCode });
|
||||
|
||||
// 최고 관리자가 아니면 자신의 회사만 조회 가능
|
||||
if (userCompanyCode !== "*" && userCompanyCode !== companyCode) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 회사의 부서를 조회할 권한이 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 부서 목록 조회 (부서원 수 포함)
|
||||
const departments = await query<any>(`
|
||||
SELECT
|
||||
d.dept_code,
|
||||
d.dept_name,
|
||||
d.company_code,
|
||||
d.parent_dept_code,
|
||||
COUNT(DISTINCT ud.user_id) as member_count
|
||||
FROM dept_info d
|
||||
LEFT JOIN user_dept ud ON d.dept_code = ud.dept_code
|
||||
WHERE d.company_code = $1
|
||||
GROUP BY d.dept_code, d.dept_name, d.company_code, d.parent_dept_code
|
||||
ORDER BY d.dept_name
|
||||
`, [companyCode]);
|
||||
|
||||
// 응답 형식 변환
|
||||
const formattedDepartments = departments.map((dept) => ({
|
||||
dept_code: dept.dept_code,
|
||||
dept_name: dept.dept_name,
|
||||
company_code: dept.company_code,
|
||||
parent_dept_code: dept.parent_dept_code,
|
||||
memberCount: parseInt(dept.member_count || "0"),
|
||||
}));
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: formattedDepartments,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("부서 목록 조회 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "부서 목록 조회 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 상세 조회
|
||||
*/
|
||||
export async function getDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
|
||||
const department = await queryOne<any>(`
|
||||
SELECT
|
||||
dept_code,
|
||||
dept_name,
|
||||
company_code,
|
||||
parent_dept_code
|
||||
FROM dept_info
|
||||
WHERE dept_code = $1
|
||||
`, [deptCode]);
|
||||
|
||||
if (!department) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: "부서를 찾을 수 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: department,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("부서 상세 조회 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "부서 조회 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 생성
|
||||
*/
|
||||
export async function createDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
const { dept_name, parent_dept_code } = req.body;
|
||||
|
||||
if (!dept_name || !dept_name.trim()) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "부서명을 입력해주세요.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 부서 코드 생성 (DEPT_숫자)
|
||||
const codeResult = await queryOne<any>(`
|
||||
SELECT COALESCE(MAX(CAST(SUBSTRING(dept_code FROM 6) AS INTEGER)), 0) + 1 as next_number
|
||||
FROM dept_info
|
||||
WHERE company_code = $1 AND dept_code LIKE 'DEPT_%'
|
||||
`, [companyCode]);
|
||||
|
||||
const nextNumber = codeResult?.next_number || 1;
|
||||
const deptCode = `DEPT_${nextNumber}`;
|
||||
|
||||
// 부서 생성
|
||||
const result = await query<any>(`
|
||||
INSERT INTO dept_info (
|
||||
dept_code,
|
||||
dept_name,
|
||||
company_code,
|
||||
parent_dept_code
|
||||
) VALUES ($1, $2, $3, $4)
|
||||
RETURNING *
|
||||
`, [
|
||||
deptCode,
|
||||
dept_name.trim(),
|
||||
companyCode,
|
||||
parent_dept_code || null,
|
||||
]);
|
||||
|
||||
logger.info("부서 생성 성공", { deptCode, dept_name });
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: "부서가 생성되었습니다.",
|
||||
data: result[0],
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("부서 생성 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "부서 생성 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 수정
|
||||
*/
|
||||
export async function updateDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
const { dept_name, parent_dept_code } = req.body;
|
||||
|
||||
if (!dept_name || !dept_name.trim()) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "부서명을 입력해주세요.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await query<any>(`
|
||||
UPDATE dept_info
|
||||
SET
|
||||
dept_name = $1,
|
||||
parent_dept_code = $2
|
||||
WHERE dept_code = $3
|
||||
RETURNING *
|
||||
`, [dept_name.trim(), parent_dept_code || null, deptCode]);
|
||||
|
||||
if (result.length === 0) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: "부서를 찾을 수 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("부서 수정 성공", { deptCode });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "부서가 수정되었습니다.",
|
||||
data: result[0],
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("부서 수정 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "부서 수정 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 삭제
|
||||
*/
|
||||
export async function deleteDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
|
||||
// 하위 부서 확인
|
||||
const hasChildren = await queryOne<any>(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM dept_info
|
||||
WHERE parent_dept_code = $1
|
||||
`, [deptCode]);
|
||||
|
||||
if (parseInt(hasChildren?.count || "0") > 0) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "하위 부서가 있는 부서는 삭제할 수 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 부서원 확인
|
||||
const hasMembers = await queryOne<any>(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM user_dept
|
||||
WHERE dept_code = $1
|
||||
`, [deptCode]);
|
||||
|
||||
if (parseInt(hasMembers?.count || "0") > 0) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "부서원이 있는 부서는 삭제할 수 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 부서 삭제
|
||||
const result = await query<any>(`
|
||||
DELETE FROM dept_info
|
||||
WHERE dept_code = $1
|
||||
RETURNING dept_code, dept_name
|
||||
`, [deptCode]);
|
||||
|
||||
if (result.length === 0) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: "부서를 찾을 수 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("부서 삭제 성공", { deptCode });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "부서가 삭제되었습니다.",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("부서 삭제 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "부서 삭제 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서원 목록 조회
|
||||
*/
|
||||
export async function getDepartmentMembers(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
|
||||
const members = await query<any>(`
|
||||
SELECT
|
||||
u.user_id,
|
||||
u.user_name,
|
||||
u.email,
|
||||
u.tel as phone,
|
||||
u.cell_phone,
|
||||
u.position_name,
|
||||
ud.dept_code,
|
||||
d.dept_name,
|
||||
ud.is_primary
|
||||
FROM user_dept ud
|
||||
JOIN user_info u ON ud.user_id = u.user_id
|
||||
JOIN dept_info d ON ud.dept_code = d.dept_code
|
||||
WHERE ud.dept_code = $1
|
||||
ORDER BY ud.is_primary DESC, u.user_name
|
||||
`, [deptCode]);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: members,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("부서원 목록 조회 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "부서원 목록 조회 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서원 추가
|
||||
*/
|
||||
export async function addDepartmentMember(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
const { user_id } = req.body;
|
||||
|
||||
if (!user_id) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "사용자 ID를 입력해주세요.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 사용자 존재 확인
|
||||
const user = await queryOne<any>(`
|
||||
SELECT user_id, user_name
|
||||
FROM user_info
|
||||
WHERE user_id = $1
|
||||
`, [user_id]);
|
||||
|
||||
if (!user) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: "사용자를 찾을 수 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 이미 부서원인지 확인
|
||||
const existing = await queryOne<any>(`
|
||||
SELECT *
|
||||
FROM user_dept
|
||||
WHERE user_id = $1 AND dept_code = $2
|
||||
`, [user_id, deptCode]);
|
||||
|
||||
if (existing) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "이미 해당 부서의 부서원입니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 주 부서가 있는지 확인
|
||||
const hasPrimary = await queryOne<any>(`
|
||||
SELECT *
|
||||
FROM user_dept
|
||||
WHERE user_id = $1 AND is_primary = true
|
||||
`, [user_id]);
|
||||
|
||||
// 부서원 추가
|
||||
await query<any>(`
|
||||
INSERT INTO user_dept (user_id, dept_code, is_primary, created_at)
|
||||
VALUES ($1, $2, $3, NOW())
|
||||
`, [user_id, deptCode, !hasPrimary]);
|
||||
|
||||
logger.info("부서원 추가 성공", { user_id, deptCode });
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: "부서원이 추가되었습니다.",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("부서원 추가 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "부서원 추가 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서원 제거
|
||||
*/
|
||||
export async function removeDepartmentMember(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode, userId } = req.params;
|
||||
|
||||
const result = await query<any>(`
|
||||
DELETE FROM user_dept
|
||||
WHERE user_id = $1 AND dept_code = $2
|
||||
RETURNING *
|
||||
`, [userId, deptCode]);
|
||||
|
||||
if (result.length === 0) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: "해당 부서원을 찾을 수 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("부서원 제거 성공", { userId, deptCode });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "부서원이 제거되었습니다.",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("부서원 제거 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "부서원 제거 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 주 부서 설정
|
||||
*/
|
||||
export async function setPrimaryDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode, userId } = req.params;
|
||||
|
||||
// 다른 부서의 주 부서 해제
|
||||
await query<any>(`
|
||||
UPDATE user_dept
|
||||
SET is_primary = false
|
||||
WHERE user_id = $1
|
||||
`, [userId]);
|
||||
|
||||
// 해당 부서를 주 부서로 설정
|
||||
await query<any>(`
|
||||
UPDATE user_dept
|
||||
SET is_primary = true
|
||||
WHERE user_id = $1 AND dept_code = $2
|
||||
`, [userId, deptCode]);
|
||||
|
||||
logger.info("주 부서 설정 성공", { userId, deptCode });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "주 부서가 설정되었습니다.",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("주 부서 설정 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "주 부서 설정 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user