feat: 멀티테넌시 지원을 위한 레이어 관리 기능 추가

- 레이어 목록 조회, 특정 레이어 레이아웃 조회, 레이어 삭제 및 조건 설정 업데이트 기능을 추가했습니다.
- 엔티티 참조 데이터 조회 및 공통 코드 데이터 조회에 멀티테넌시 필터를 적용하여 인증된 사용자의 회사 코드에 따라 데이터 접근을 제한했습니다.
- 레이어 관리 패널에서 기본 레이어와 조건부 레이어의 컴포넌트를 통합하여 조건부 영역의 표시를 개선했습니다.
- 레이아웃 저장 시 레이어 ID를 포함하여 레이어별로 저장할 수 있도록 변경했습니다.
This commit is contained in:
kjs
2026-02-09 13:21:56 +09:00
parent 84eb035069
commit 1c71b3aa83
14 changed files with 1571 additions and 598 deletions

View File

@@ -30,10 +30,13 @@ export class EntityReferenceController {
try {
const { tableName, columnName } = req.params;
const { limit = 100, search } = req.query;
// 멀티테넌시: 인증된 사용자의 회사 코드
const companyCode = (req as any).user?.companyCode;
logger.info(`엔티티 참조 데이터 조회 요청: ${tableName}.${columnName}`, {
limit,
search,
companyCode,
});
// 컬럼 정보 조회 (table_type_columns에서)
@@ -89,16 +92,34 @@ export class EntityReferenceController {
});
}
// 동적 쿼리로 참조 데이터 조회
let sqlQuery = `SELECT ${referenceColumn}, ${displayColumn} as display_name FROM ${referenceTable}`;
// 참조 테이블에 company_code 컬럼이 있는지 확인
const hasCompanyCode = await queryOne<any>(
`SELECT column_name FROM information_schema.columns
WHERE table_name = $1 AND column_name = 'company_code' AND table_schema = 'public'`,
[referenceTable]
);
// 동적 쿼리로 참조 데이터 조회 (멀티테넌시 필터 적용)
const whereConditions: string[] = [];
const queryParams: any[] = [];
// 멀티테넌시: company_code 필터링 (참조 테이블에 company_code가 있는 경우)
if (hasCompanyCode && companyCode && companyCode !== "*") {
queryParams.push(companyCode);
whereConditions.push(`company_code = $${queryParams.length}`);
logger.info(`멀티테넌시 필터 적용: company_code = ${companyCode}`, { referenceTable });
}
// 검색 조건 추가
if (search) {
sqlQuery += ` WHERE ${displayColumn} ILIKE $1`;
queryParams.push(`%${search}%`);
whereConditions.push(`${displayColumn} ILIKE $${queryParams.length}`);
}
let sqlQuery = `SELECT ${referenceColumn}, ${displayColumn} as display_name FROM ${referenceTable}`;
if (whereConditions.length > 0) {
sqlQuery += ` WHERE ${whereConditions.join(" AND ")}`;
}
sqlQuery += ` ORDER BY ${displayColumn} LIMIT $${queryParams.length + 1}`;
queryParams.push(Number(limit));
@@ -107,6 +128,7 @@ export class EntityReferenceController {
referenceTable,
referenceColumn,
displayColumn,
companyCode,
});
const referenceData = await query<any>(sqlQuery, queryParams);
@@ -119,7 +141,7 @@ export class EntityReferenceController {
})
);
logger.info(`엔티티 참조 데이터 조회 완료: ${options.length}개 항목`);
logger.info(`엔티티 참조 데이터 조회 완료: ${options.length}개 항목`, { companyCode });
return res.json({
success: true,
@@ -149,13 +171,16 @@ export class EntityReferenceController {
try {
const { codeCategory } = req.params;
const { limit = 100, search } = req.query;
// 멀티테넌시: 인증된 사용자의 회사 코드
const companyCode = (req as any).user?.companyCode;
logger.info(`공통 코드 데이터 조회 요청: ${codeCategory}`, {
limit,
search,
companyCode,
});
// code_info 테이블에서 코드 데이터 조회
// code_info 테이블에서 코드 데이터 조회 (멀티테넌시 필터 적용)
const queryParams: any[] = [codeCategory, 'Y'];
let sqlQuery = `
SELECT code_value, code_name
@@ -163,9 +188,16 @@ export class EntityReferenceController {
WHERE code_category = $1 AND is_active = $2
`;
// 멀티테넌시: company_code 필터링
if (companyCode && companyCode !== "*") {
queryParams.push(companyCode);
sqlQuery += ` AND company_code = $${queryParams.length}`;
logger.info(`공통 코드 멀티테넌시 필터 적용: company_code = ${companyCode}`);
}
if (search) {
sqlQuery += ` AND code_name ILIKE $3`;
queryParams.push(`%${search}%`);
sqlQuery += ` AND code_name ILIKE $${queryParams.length}`;
}
sqlQuery += ` ORDER BY code_name ASC LIMIT $${queryParams.length + 1}`;
@@ -174,12 +206,12 @@ export class EntityReferenceController {
const codeData = await query<any>(sqlQuery, queryParams);
// 옵션 형태로 변환
const options: EntityReferenceOption[] = codeData.map((code) => ({
const options: EntityReferenceOption[] = codeData.map((code: any) => ({
value: code.code_value,
label: code.code_name,
}));
logger.info(`공통 코드 데이터 조회 완료: ${options.length}개 항목`);
logger.info(`공통 코드 데이터 조회 완료: ${options.length}개 항목`, { companyCode });
return res.json({
success: true,

View File

@@ -732,6 +732,61 @@ export const saveLayoutV2 = async (req: AuthenticatedRequest, res: Response) =>
}
};
// 🆕 레이어 목록 조회
export const getScreenLayers = async (req: AuthenticatedRequest, res: Response) => {
try {
const { screenId } = req.params;
const { companyCode } = req.user as any;
const layers = await screenManagementService.getScreenLayers(parseInt(screenId), companyCode);
res.json({ success: true, data: layers });
} catch (error) {
console.error("레이어 목록 조회 실패:", error);
res.status(500).json({ success: false, message: "레이어 목록 조회에 실패했습니다." });
}
};
// 🆕 특정 레이어 레이아웃 조회
export const getLayerLayout = async (req: AuthenticatedRequest, res: Response) => {
try {
const { screenId, layerId } = req.params;
const { companyCode } = req.user as any;
const layout = await screenManagementService.getLayerLayout(parseInt(screenId), parseInt(layerId), companyCode);
res.json({ success: true, data: layout });
} catch (error) {
console.error("레이어 레이아웃 조회 실패:", error);
res.status(500).json({ success: false, message: "레이어 레이아웃 조회에 실패했습니다." });
}
};
// 🆕 레이어 삭제
export const deleteLayer = async (req: AuthenticatedRequest, res: Response) => {
try {
const { screenId, layerId } = req.params;
const { companyCode } = req.user as any;
await screenManagementService.deleteLayer(parseInt(screenId), parseInt(layerId), companyCode);
res.json({ success: true, message: "레이어가 삭제되었습니다." });
} catch (error: any) {
console.error("레이어 삭제 실패:", error);
res.status(400).json({ success: false, message: error.message || "레이어 삭제에 실패했습니다." });
}
};
// 🆕 레이어 조건 설정 업데이트
export const updateLayerCondition = async (req: AuthenticatedRequest, res: Response) => {
try {
const { screenId, layerId } = req.params;
const { companyCode } = req.user as any;
const { conditionConfig, layerName } = req.body;
await screenManagementService.updateLayerCondition(
parseInt(screenId), parseInt(layerId), companyCode, conditionConfig, layerName
);
res.json({ success: true, message: "레이어 조건이 업데이트되었습니다." });
} catch (error) {
console.error("레이어 조건 업데이트 실패:", error);
res.status(500).json({ success: false, message: "레이어 조건 업데이트에 실패했습니다." });
}
};
// 화면 코드 자동 생성
export const generateScreenCode = async (
req: AuthenticatedRequest,