Merge branch 'main' into feature/screen-management

This commit is contained in:
kjs
2025-12-17 17:41:43 +09:00
18 changed files with 1047 additions and 57 deletions

View File

@@ -30,6 +30,29 @@ export const getCategoryColumns = async (req: AuthenticatedRequest, res: Respons
}
};
/**
* 모든 테이블의 카테고리 컬럼 목록 조회 (Select 옵션 설정용)
*/
export const getAllCategoryColumns = async (req: AuthenticatedRequest, res: Response) => {
try {
const companyCode = req.user!.companyCode;
const columns = await tableCategoryValueService.getAllCategoryColumns(companyCode);
return res.json({
success: true,
data: columns,
});
} catch (error: any) {
logger.error(`전체 카테고리 컬럼 조회 실패: ${error.message}`);
return res.status(500).json({
success: false,
message: "전체 카테고리 컬럼 조회 중 오류가 발생했습니다",
error: error.message,
});
}
};
/**
* 카테고리 값 목록 조회 (메뉴 스코프 적용)
*

View File

@@ -878,7 +878,17 @@ export async function addTableData(
const hasCompanyCodeColumn = await tableManagementService.hasColumn(tableName, "company_code");
if (hasCompanyCodeColumn) {
data.company_code = companyCode;
logger.info(`🔒 멀티테넌시: company_code 자동 추가 - ${companyCode}`);
logger.info(`멀티테넌시: company_code 자동 추가 - ${companyCode}`);
}
}
// 🆕 writer 컬럼 자동 추가 (테이블에 writer 컬럼이 있고 값이 없는 경우)
const userId = req.user?.userId;
if (userId && !data.writer) {
const hasWriterColumn = await tableManagementService.hasColumn(tableName, "writer");
if (hasWriterColumn) {
data.writer = userId;
logger.info(`writer 자동 추가 - ${userId}`);
}
}

View File

@@ -1,6 +1,7 @@
import { Router } from "express";
import {
getCategoryColumns,
getAllCategoryColumns,
getCategoryValues,
addCategoryValue,
updateCategoryValue,
@@ -22,6 +23,10 @@ const router = Router();
// 모든 라우트에 인증 미들웨어 적용
router.use(authenticateToken);
// 모든 테이블의 카테고리 컬럼 목록 조회 (Select 옵션 설정용)
// 주의: 더 구체적인 라우트보다 먼저 와야 함
router.get("/all-columns", getAllCategoryColumns);
// 테이블의 카테고리 컬럼 목록 조회
router.get("/:tableName/columns", getCategoryColumns);

View File

@@ -86,11 +86,12 @@ export class CommonCodeService {
}
// 회사별 필터링 (최고 관리자가 아닌 경우)
// company_code = '*'인 공통 데이터도 함께 조회
if (userCompanyCode && userCompanyCode !== "*") {
whereConditions.push(`company_code = $${paramIndex}`);
whereConditions.push(`(company_code = $${paramIndex} OR company_code = '*')`);
values.push(userCompanyCode);
paramIndex++;
logger.info(`회사별 코드 카테고리 필터링: ${userCompanyCode}`);
logger.info(`회사별 코드 카테고리 필터링: ${userCompanyCode} (공통 데이터 포함)`);
} else if (userCompanyCode === "*") {
// 최고 관리자는 모든 데이터 조회 가능
logger.info(`최고 관리자: 모든 코드 카테고리 조회`);
@@ -116,7 +117,7 @@ export class CommonCodeService {
const offset = (page - 1) * size;
// 카테고리 조회
// code_category 테이블에서만 조회 (comm_code 제거)
const categories = await query<CodeCategory>(
`SELECT * FROM code_category
${whereClause}
@@ -134,7 +135,7 @@ export class CommonCodeService {
const total = parseInt(countResult?.count || "0");
logger.info(
`카테고리 조회 완료: ${categories.length}개, 전체: ${total}개 (회사: ${userCompanyCode || "전체"})`
`카테고리 조회 완료: code_category ${categories.length}개, 전체: ${total}개 (회사: ${userCompanyCode || "전체"})`
);
return {
@@ -224,7 +225,7 @@ export class CommonCodeService {
paramIndex,
});
// 코드 조회
// code_info 테이블에서만 코드 조회 (comm_code fallback 제거)
const codes = await query<CodeInfo>(
`SELECT * FROM code_info
${whereClause}
@@ -242,20 +243,9 @@ export class CommonCodeService {
const total = parseInt(countResult?.count || "0");
logger.info(
`✅ [getCodes] 코드 조회 완료: ${categoryCode} - ${codes.length}개, 전체: ${total}개 (회사: ${userCompanyCode || "전체"}, menuObjid: ${menuObjid || "없음"})`
`코드 조회 완료: ${categoryCode} - ${codes.length}개, 전체: ${total}개 (회사: ${userCompanyCode || "전체"}, menuObjid: ${menuObjid || "없음"})`
);
logger.info(`📊 [getCodes] 조회된 코드 상세:`, {
categoryCode,
menuObjid,
codes: codes.map((c) => ({
code_value: c.code_value,
code_name: c.code_name,
menu_objid: c.menu_objid,
company_code: c.company_code,
})),
});
return { data: codes, total };
} catch (error) {
logger.error(`코드 조회 중 오류 (${categoryCode}):`, error);

View File

@@ -854,6 +854,11 @@ export class DynamicFormService {
if (tableColumns.includes("updated_at")) {
changedFields.updated_at = new Date();
}
// updated_date 컬럼도 지원 (sales_order_mng 등)
if (tableColumns.includes("updated_date")) {
changedFields.updated_date = new Date();
console.log("📅 updated_date 자동 추가:", changedFields.updated_date);
}
console.log("🎯 실제 업데이트할 필드들:", changedFields);

View File

@@ -79,6 +79,82 @@ class TableCategoryValueService {
}
}
/**
* 모든 테이블의 카테고리 컬럼 목록 조회 (Select 옵션 설정용)
* 테이블 선택 없이 등록된 모든 카테고리 컬럼을 조회합니다.
*/
async getAllCategoryColumns(
companyCode: string
): Promise<CategoryColumn[]> {
try {
logger.info("전체 카테고리 컬럼 목록 조회", { companyCode });
const pool = getPool();
let query: string;
let params: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 카테고리 컬럼 조회 (중복 제거)
query = `
SELECT
tc.table_name AS "tableName",
tc.column_name AS "columnName",
tc.column_name AS "columnLabel",
COALESCE(cv_count.cnt, 0) AS "valueCount"
FROM (
SELECT DISTINCT table_name, column_name, MIN(display_order) as display_order
FROM table_type_columns
WHERE input_type = 'category'
GROUP BY table_name, column_name
) tc
LEFT JOIN (
SELECT table_name, column_name, COUNT(*) as cnt
FROM table_column_category_values
WHERE is_active = true
GROUP BY table_name, column_name
) cv_count ON tc.table_name = cv_count.table_name AND tc.column_name = cv_count.column_name
ORDER BY tc.table_name, tc.display_order, tc.column_name
`;
params = [];
} else {
// 일반 회사: 자신의 카테고리 값만 카운트 (중복 제거)
query = `
SELECT
tc.table_name AS "tableName",
tc.column_name AS "columnName",
tc.column_name AS "columnLabel",
COALESCE(cv_count.cnt, 0) AS "valueCount"
FROM (
SELECT DISTINCT table_name, column_name, MIN(display_order) as display_order
FROM table_type_columns
WHERE input_type = 'category'
GROUP BY table_name, column_name
) tc
LEFT JOIN (
SELECT table_name, column_name, COUNT(*) as cnt
FROM table_column_category_values
WHERE is_active = true AND company_code = $1
GROUP BY table_name, column_name
) cv_count ON tc.table_name = cv_count.table_name AND tc.column_name = cv_count.column_name
ORDER BY tc.table_name, tc.display_order, tc.column_name
`;
params = [companyCode];
}
const result = await pool.query(query, params);
logger.info(`전체 카테고리 컬럼 ${result.rows.length}개 조회 완료`, {
companyCode,
});
return result.rows;
} catch (error: any) {
logger.error(`전체 카테고리 컬럼 조회 실패: ${error.message}`);
throw error;
}
}
/**
* 특정 컬럼의 카테고리 값 목록 조회 (메뉴 스코프)
*

View File

@@ -2289,6 +2289,13 @@ export class TableManagementService {
logger.info(`컬럼 타입 정보:`, Object.fromEntries(columnTypeMap));
// created_date 컬럼이 있고 값이 없으면 자동으로 현재 시간 추가
const hasCreatedDate = columnTypeMap.has("created_date");
if (hasCreatedDate && !data.created_date) {
data.created_date = new Date().toISOString();
logger.info(`created_date 자동 추가: ${data.created_date}`);
}
// 컬럼명과 값을 분리하고 타입에 맞게 변환
const columns = Object.keys(data);
const values = Object.values(data).map((value, index) => {
@@ -2394,6 +2401,13 @@ export class TableManagementService {
logger.info(`컬럼 타입 정보:`, Object.fromEntries(columnTypeMap));
logger.info(`PRIMARY KEY 컬럼들:`, primaryKeys);
// updated_date 컬럼이 있으면 자동으로 현재 시간 추가
const hasUpdatedDate = columnTypeMap.has("updated_date");
if (hasUpdatedDate && !updatedData.updated_date) {
updatedData.updated_date = new Date().toISOString();
logger.info(`updated_date 자동 추가: ${updatedData.updated_date}`);
}
// SET 절 생성 (수정할 데이터) - 먼저 생성
const setConditions: string[] = [];
const setValues: any[] = [];