Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management
This commit is contained in:
@@ -8,6 +8,7 @@ import config from "../config/environment";
|
||||
import { AdminService } from "../services/adminService";
|
||||
import { EncryptUtil } from "../utils/encryptUtil";
|
||||
import { FileSystemManager } from "../utils/fileSystemManager";
|
||||
import { validateBusinessNumber } from "../utils/businessNumberValidator";
|
||||
|
||||
/**
|
||||
* 관리자 메뉴 목록 조회
|
||||
@@ -609,9 +610,15 @@ export const getCompanyList = async (
|
||||
|
||||
// Raw Query로 회사 목록 조회
|
||||
const companies = await query<any>(
|
||||
`SELECT
|
||||
company_code,
|
||||
` SELECT
|
||||
company_code,
|
||||
company_name,
|
||||
business_registration_number,
|
||||
representative_name,
|
||||
representative_phone,
|
||||
email,
|
||||
website,
|
||||
address,
|
||||
status,
|
||||
writer,
|
||||
regdate
|
||||
@@ -1659,9 +1666,15 @@ export async function getCompanyListFromDB(
|
||||
|
||||
// Raw Query로 회사 목록 조회
|
||||
const companies = await query<any>(
|
||||
`SELECT
|
||||
company_code,
|
||||
` SELECT
|
||||
company_code,
|
||||
company_name,
|
||||
business_registration_number,
|
||||
representative_name,
|
||||
representative_phone,
|
||||
email,
|
||||
website,
|
||||
address,
|
||||
writer,
|
||||
regdate,
|
||||
status
|
||||
@@ -2440,6 +2453,25 @@ export const createCompany = async (
|
||||
[company_name.trim()]
|
||||
);
|
||||
|
||||
// 사업자등록번호 유효성 검증
|
||||
const businessNumberValidation = validateBusinessNumber(
|
||||
req.body.business_registration_number?.trim() || ""
|
||||
);
|
||||
if (!businessNumberValidation.isValid) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: businessNumberValidation.message,
|
||||
errorCode: "INVALID_BUSINESS_NUMBER",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Raw Query로 사업자등록번호 중복 체크
|
||||
const existingBusinessNumber = await queryOne<any>(
|
||||
`SELECT company_code FROM company_mng WHERE business_registration_number = $1`,
|
||||
[req.body.business_registration_number?.trim()]
|
||||
);
|
||||
|
||||
if (existingCompany) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
@@ -2449,6 +2481,15 @@ export const createCompany = async (
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingBusinessNumber) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "이미 등록된 사업자등록번호입니다.",
|
||||
errorCode: "DUPLICATE_BUSINESS_NUMBER",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// PostgreSQL 클라이언트 생성 (복잡한 코드 생성 쿼리용)
|
||||
const client = new Client({
|
||||
connectionString:
|
||||
@@ -2474,11 +2515,17 @@ export const createCompany = async (
|
||||
const insertQuery = `
|
||||
INSERT INTO company_mng (
|
||||
company_code,
|
||||
company_name,
|
||||
company_name,
|
||||
business_registration_number,
|
||||
representative_name,
|
||||
representative_phone,
|
||||
email,
|
||||
website,
|
||||
address,
|
||||
writer,
|
||||
regdate,
|
||||
status
|
||||
) VALUES ($1, $2, $3, $4, $5)
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
@@ -2488,6 +2535,12 @@ export const createCompany = async (
|
||||
const insertValues = [
|
||||
companyCode,
|
||||
company_name.trim(),
|
||||
req.body.business_registration_number?.trim() || null,
|
||||
req.body.representative_name?.trim() || null,
|
||||
req.body.representative_phone?.trim() || null,
|
||||
req.body.email?.trim() || null,
|
||||
req.body.website?.trim() || null,
|
||||
req.body.address?.trim() || null,
|
||||
writer,
|
||||
new Date(),
|
||||
"active",
|
||||
@@ -2552,7 +2605,16 @@ export const updateCompany = async (
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
const { company_name, status } = req.body;
|
||||
const {
|
||||
company_name,
|
||||
business_registration_number,
|
||||
representative_name,
|
||||
representative_phone,
|
||||
email,
|
||||
website,
|
||||
address,
|
||||
status,
|
||||
} = req.body;
|
||||
|
||||
logger.info("회사 정보 수정 요청", {
|
||||
companyCode,
|
||||
@@ -2586,13 +2648,61 @@ export const updateCompany = async (
|
||||
return;
|
||||
}
|
||||
|
||||
// 사업자등록번호 중복 체크 및 유효성 검증 (자기 자신 제외)
|
||||
if (business_registration_number && business_registration_number.trim()) {
|
||||
// 유효성 검증
|
||||
const businessNumberValidation = validateBusinessNumber(business_registration_number.trim());
|
||||
if (!businessNumberValidation.isValid) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: businessNumberValidation.message,
|
||||
errorCode: "INVALID_BUSINESS_NUMBER",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 중복 체크
|
||||
const duplicateBusinessNumber = await queryOne<any>(
|
||||
`SELECT company_code FROM company_mng
|
||||
WHERE business_registration_number = $1 AND company_code != $2`,
|
||||
[business_registration_number.trim(), companyCode]
|
||||
);
|
||||
|
||||
if (duplicateBusinessNumber) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "이미 등록된 사업자등록번호입니다.",
|
||||
errorCode: "DUPLICATE_BUSINESS_NUMBER",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Raw Query로 회사 정보 수정
|
||||
const result = await query<any>(
|
||||
`UPDATE company_mng
|
||||
SET company_name = $1, status = $2
|
||||
WHERE company_code = $3
|
||||
SET
|
||||
company_name = $1,
|
||||
business_registration_number = $2,
|
||||
representative_name = $3,
|
||||
representative_phone = $4,
|
||||
email = $5,
|
||||
website = $6,
|
||||
address = $7,
|
||||
status = $8
|
||||
WHERE company_code = $9
|
||||
RETURNING *`,
|
||||
[company_name.trim(), status || "active", companyCode]
|
||||
[
|
||||
company_name.trim(),
|
||||
business_registration_number?.trim() || null,
|
||||
representative_name?.trim() || null,
|
||||
representative_phone?.trim() || null,
|
||||
email?.trim() || null,
|
||||
website?.trim() || null,
|
||||
address?.trim() || null,
|
||||
status || "active",
|
||||
companyCode,
|
||||
]
|
||||
);
|
||||
|
||||
if (result.length === 0) {
|
||||
|
||||
534
backend-node/src/controllers/departmentController.ts
Normal file
534
backend-node/src/controllers/departmentController.ts
Normal file
@@ -0,0 +1,534 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// 같은 회사 내 중복 부서명 확인
|
||||
const duplicate = await queryOne<any>(`
|
||||
SELECT dept_code, dept_name
|
||||
FROM dept_info
|
||||
WHERE company_code = $1 AND dept_name = $2
|
||||
`, [companyCode, dept_name.trim()]);
|
||||
|
||||
if (duplicate) {
|
||||
res.status(409).json({
|
||||
success: false,
|
||||
message: `"${dept_name}" 부서가 이미 존재합니다.`,
|
||||
isDuplicate: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 회사 이름 조회
|
||||
const company = await queryOne<any>(`
|
||||
SELECT company_name FROM company_mng WHERE company_code = $1
|
||||
`, [companyCode]);
|
||||
|
||||
const companyName = company?.company_name || companyCode;
|
||||
|
||||
// 부서 코드 생성 (전역 카운트: DEPT_1, DEPT_2, ...)
|
||||
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 dept_code ~ '^DEPT_[0-9]+$'
|
||||
`);
|
||||
|
||||
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,
|
||||
company_name,
|
||||
parent_dept_code,
|
||||
status,
|
||||
regdate
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, NOW())
|
||||
RETURNING *
|
||||
`, [
|
||||
deptCode,
|
||||
dept_name.trim(),
|
||||
companyCode,
|
||||
companyName,
|
||||
parent_dept_code || null,
|
||||
'active',
|
||||
]);
|
||||
|
||||
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 deletedMembers = await query<any>(`
|
||||
DELETE FROM user_dept
|
||||
WHERE dept_code = $1
|
||||
RETURNING user_id
|
||||
`, [deptCode]);
|
||||
|
||||
const memberCount = deletedMembers.length;
|
||||
|
||||
// 부서 삭제
|
||||
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,
|
||||
deptName: result[0].dept_name,
|
||||
deletedMemberCount: memberCount
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: memberCount > 0
|
||||
? `부서가 삭제되었습니다. (부서원 ${memberCount}명 제외됨)`
|
||||
: "부서가 삭제되었습니다.",
|
||||
});
|
||||
} 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 searchUsers(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
const { search } = req.query;
|
||||
|
||||
if (!search || typeof search !== 'string') {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "검색어를 입력해주세요.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 사용자 검색 (ID 또는 이름)
|
||||
const users = await query<any>(`
|
||||
SELECT
|
||||
user_id,
|
||||
user_name,
|
||||
email,
|
||||
position_name,
|
||||
company_code
|
||||
FROM user_info
|
||||
WHERE company_code = $1
|
||||
AND (
|
||||
user_id ILIKE $2 OR
|
||||
user_name ILIKE $2
|
||||
)
|
||||
ORDER BY user_name
|
||||
LIMIT 20
|
||||
`, [companyCode, `%${search}%`]);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: users,
|
||||
});
|
||||
} 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(409).json({
|
||||
success: false,
|
||||
message: "이미 해당 부서의 부서원입니다.",
|
||||
isDuplicate: true,
|
||||
});
|
||||
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: "주 부서 설정 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,14 @@ export const saveFormData = async (
|
||||
const { companyCode, userId } = req.user as any;
|
||||
const { screenId, tableName, data } = req.body;
|
||||
|
||||
// 🔍 디버깅: 사용자 정보 확인
|
||||
console.log("🔍 [saveFormData] 사용자 정보:", {
|
||||
userId,
|
||||
companyCode,
|
||||
reqUser: req.user,
|
||||
dataWriter: data.writer,
|
||||
});
|
||||
|
||||
// 필수 필드 검증 (screenId는 0일 수 있으므로 undefined 체크)
|
||||
if (screenId === undefined || screenId === null || !tableName || !data) {
|
||||
return res.status(400).json({
|
||||
@@ -25,9 +33,12 @@ export const saveFormData = async (
|
||||
...data,
|
||||
created_by: userId,
|
||||
updated_by: userId,
|
||||
writer: data.writer || userId, // ✅ writer가 없으면 userId로 설정
|
||||
screen_id: screenId,
|
||||
};
|
||||
|
||||
console.log("✅ [saveFormData] 최종 writer 값:", formDataWithMeta.writer);
|
||||
|
||||
// company_code는 사용자가 명시적으로 입력한 경우에만 추가
|
||||
if (data.company_code !== undefined) {
|
||||
formDataWithMeta.company_code = data.company_code;
|
||||
@@ -86,6 +97,7 @@ export const saveFormDataEnhanced = async (
|
||||
...data,
|
||||
created_by: userId,
|
||||
updated_by: userId,
|
||||
writer: data.writer || userId, // ✅ writer가 없으면 userId로 설정
|
||||
screen_id: screenId,
|
||||
};
|
||||
|
||||
@@ -134,6 +146,7 @@ export const updateFormData = async (
|
||||
const formDataWithMeta = {
|
||||
...data,
|
||||
updated_by: userId,
|
||||
writer: data.writer || userId, // ✅ writer가 없으면 userId로 설정
|
||||
updated_at: new Date(),
|
||||
};
|
||||
|
||||
@@ -186,6 +199,7 @@ export const updateFormDataPartial = async (
|
||||
const newDataWithMeta = {
|
||||
...newData,
|
||||
updated_by: userId,
|
||||
writer: newData.writer || userId, // ✅ writer가 없으면 userId로 설정
|
||||
};
|
||||
|
||||
const result = await dynamicFormService.updateFormDataPartial(
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ColumnListResponse,
|
||||
ColumnSettingsResponse,
|
||||
} from "../types/tableManagement";
|
||||
import { query } from "../database/db"; // 🆕 query 함수 import
|
||||
|
||||
/**
|
||||
* 테이블 목록 조회
|
||||
@@ -506,7 +507,91 @@ export async function updateColumnInputType(
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 데이터 조회 (페이징 + 검색)
|
||||
* 단일 레코드 조회 (자동 입력용)
|
||||
*/
|
||||
export async function getTableRecord(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
const { filterColumn, filterValue, displayColumn } = req.body;
|
||||
|
||||
logger.info(`=== 단일 레코드 조회 시작: ${tableName} ===`);
|
||||
logger.info(`필터: ${filterColumn} = ${filterValue}`);
|
||||
logger.info(`표시 컬럼: ${displayColumn}`);
|
||||
|
||||
if (!tableName || !filterColumn || !filterValue || !displayColumn) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "필수 파라미터가 누락되었습니다.",
|
||||
error: {
|
||||
code: "MISSING_PARAMETERS",
|
||||
details:
|
||||
"tableName, filterColumn, filterValue, displayColumn이 필요합니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const tableManagementService = new TableManagementService();
|
||||
|
||||
// 단일 레코드 조회 (WHERE filterColumn = filterValue)
|
||||
const result = await tableManagementService.getTableData(tableName, {
|
||||
page: 1,
|
||||
size: 1,
|
||||
search: {
|
||||
[filterColumn]: filterValue,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.data || result.data.length === 0) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "데이터를 찾을 수 없습니다.",
|
||||
error: {
|
||||
code: "NOT_FOUND",
|
||||
details: `${filterColumn} = ${filterValue}에 해당하는 데이터가 없습니다.`,
|
||||
},
|
||||
};
|
||||
res.status(404).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const record = result.data[0];
|
||||
const displayValue = record[displayColumn];
|
||||
|
||||
logger.info(`레코드 조회 완료: ${displayColumn} = ${displayValue}`);
|
||||
|
||||
const response: ApiResponse<{ value: any; record: any }> = {
|
||||
success: true,
|
||||
message: "레코드를 성공적으로 조회했습니다.",
|
||||
data: {
|
||||
value: displayValue,
|
||||
record: record,
|
||||
},
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("레코드 조회 중 오류 발생:", error);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "레코드 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "RECORD_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 데이터 조회 (페이징 + 검색 + 필터링)
|
||||
*/
|
||||
export async function getTableData(
|
||||
req: AuthenticatedRequest,
|
||||
@@ -520,12 +605,14 @@ export async function getTableData(
|
||||
search = {},
|
||||
sortBy,
|
||||
sortOrder = "asc",
|
||||
autoFilter, // 🆕 자동 필터 설정 추가 (컴포넌트에서 직접 전달)
|
||||
} = req.body;
|
||||
|
||||
logger.info(`=== 테이블 데이터 조회 시작: ${tableName} ===`);
|
||||
logger.info(`페이징: page=${page}, size=${size}`);
|
||||
logger.info(`검색 조건:`, search);
|
||||
logger.info(`정렬: ${sortBy} ${sortOrder}`);
|
||||
logger.info(`자동 필터:`, autoFilter); // 🆕
|
||||
|
||||
if (!tableName) {
|
||||
const response: ApiResponse<null> = {
|
||||
@@ -542,11 +629,35 @@ export async function getTableData(
|
||||
|
||||
const tableManagementService = new TableManagementService();
|
||||
|
||||
// 🆕 현재 사용자 필터 적용
|
||||
let enhancedSearch = { ...search };
|
||||
if (autoFilter?.enabled && req.user) {
|
||||
const filterColumn = autoFilter.filterColumn || "company_code";
|
||||
const userField = autoFilter.userField || "companyCode";
|
||||
const userValue = (req.user as any)[userField];
|
||||
|
||||
if (userValue) {
|
||||
enhancedSearch[filterColumn] = userValue;
|
||||
|
||||
logger.info("🔍 현재 사용자 필터 적용:", {
|
||||
filterColumn,
|
||||
userField,
|
||||
userValue,
|
||||
tableName,
|
||||
});
|
||||
} else {
|
||||
logger.warn("⚠️ 사용자 정보 필드 값 없음:", {
|
||||
userField,
|
||||
user: req.user,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 데이터 조회
|
||||
const result = await tableManagementService.getTableData(tableName, {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
search,
|
||||
search: enhancedSearch, // 🆕 필터가 적용된 search 사용
|
||||
sortBy,
|
||||
sortOrder,
|
||||
});
|
||||
@@ -1216,9 +1327,7 @@ export async function getLogData(
|
||||
originalId: originalId as string,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`
|
||||
);
|
||||
logger.info(`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`);
|
||||
|
||||
const response: ApiResponse<typeof result> = {
|
||||
success: true,
|
||||
@@ -1254,7 +1363,9 @@ export async function toggleLogTable(
|
||||
const { tableName } = req.params;
|
||||
const { isActive } = req.body;
|
||||
|
||||
logger.info(`=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`);
|
||||
logger.info(
|
||||
`=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`
|
||||
);
|
||||
|
||||
if (!tableName) {
|
||||
const response: ApiResponse<null> = {
|
||||
@@ -1288,9 +1399,7 @@ export async function toggleLogTable(
|
||||
isActive === "Y" || isActive === true
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`
|
||||
);
|
||||
logger.info(`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user