화면관리 삭제기능구현
This commit is contained in:
@@ -50,8 +50,11 @@ export class ScreenManagementService {
|
||||
console.log(`사용자 회사 코드:`, userCompanyCode);
|
||||
|
||||
// 화면 코드 중복 확인
|
||||
const existingScreen = await prisma.screen_definitions.findUnique({
|
||||
where: { screen_code: screenData.screenCode },
|
||||
const existingScreen = await prisma.screen_definitions.findFirst({
|
||||
where: {
|
||||
screen_code: screenData.screenCode,
|
||||
is_active: { not: "D" }, // 삭제되지 않은 화면만 중복 검사
|
||||
},
|
||||
});
|
||||
|
||||
console.log(
|
||||
@@ -79,15 +82,18 @@ export class ScreenManagementService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 회사별 화면 목록 조회 (페이징 지원)
|
||||
* 회사별 화면 목록 조회 (페이징 지원) - 활성 화면만
|
||||
*/
|
||||
async getScreensByCompany(
|
||||
companyCode: string,
|
||||
page: number = 1,
|
||||
size: number = 20
|
||||
): Promise<PaginatedResponse<ScreenDefinition>> {
|
||||
const whereClause =
|
||||
companyCode === "*" ? {} : { company_code: companyCode };
|
||||
const whereClause: any = { is_active: { not: "D" } }; // 삭제된 화면 제외
|
||||
|
||||
if (companyCode !== "*") {
|
||||
whereClause.company_code = companyCode;
|
||||
}
|
||||
|
||||
const [screens, total] = await Promise.all([
|
||||
prisma.screen_definitions.findMany({
|
||||
@@ -111,11 +117,14 @@ export class ScreenManagementService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면 목록 조회 (간단 버전)
|
||||
* 화면 목록 조회 (간단 버전) - 활성 화면만
|
||||
*/
|
||||
async getScreens(companyCode: string): Promise<ScreenDefinition[]> {
|
||||
const whereClause =
|
||||
companyCode === "*" ? {} : { company_code: companyCode };
|
||||
const whereClause: any = { is_active: { not: "D" } }; // 삭제된 화면 제외
|
||||
|
||||
if (companyCode !== "*") {
|
||||
whereClause.company_code = companyCode;
|
||||
}
|
||||
|
||||
const screens = await prisma.screen_definitions.findMany({
|
||||
where: whereClause,
|
||||
@@ -126,31 +135,37 @@ export class ScreenManagementService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면 정의 조회
|
||||
* 화면 정의 조회 (활성 화면만)
|
||||
*/
|
||||
async getScreenById(screenId: number): Promise<ScreenDefinition | null> {
|
||||
const screen = await prisma.screen_definitions.findUnique({
|
||||
where: { screen_id: screenId },
|
||||
const screen = await prisma.screen_definitions.findFirst({
|
||||
where: {
|
||||
screen_id: screenId,
|
||||
is_active: { not: "D" }, // 삭제된 화면 제외
|
||||
},
|
||||
});
|
||||
|
||||
return screen ? this.mapToScreenDefinition(screen) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면 정의 조회 (회사 코드 검증 포함)
|
||||
* 화면 정의 조회 (회사 코드 검증 포함, 활성 화면만)
|
||||
*/
|
||||
async getScreen(
|
||||
screenId: number,
|
||||
companyCode: string
|
||||
): Promise<ScreenDefinition | null> {
|
||||
const whereClause: any = { screen_id: screenId };
|
||||
const whereClause: any = {
|
||||
screen_id: screenId,
|
||||
is_active: { not: "D" }, // 삭제된 화면 제외
|
||||
};
|
||||
|
||||
// 회사 코드가 '*'가 아닌 경우 회사별 필터링
|
||||
if (companyCode !== "*") {
|
||||
whereClause.company_code = companyCode;
|
||||
}
|
||||
|
||||
const screen = await prisma.screen_definitions.findUnique({
|
||||
const screen = await prisma.screen_definitions.findFirst({
|
||||
where: whereClause,
|
||||
});
|
||||
|
||||
@@ -196,9 +211,237 @@ export class ScreenManagementService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면 정의 삭제
|
||||
* 화면 의존성 체크 - 다른 화면에서 이 화면을 참조하는지 확인
|
||||
*/
|
||||
async deleteScreen(screenId: number, userCompanyCode: string): Promise<void> {
|
||||
async checkScreenDependencies(
|
||||
screenId: number,
|
||||
userCompanyCode: string
|
||||
): Promise<{
|
||||
hasDependencies: boolean;
|
||||
dependencies: Array<{
|
||||
screenId: number;
|
||||
screenName: string;
|
||||
screenCode: string;
|
||||
componentId: string;
|
||||
componentType: string;
|
||||
referenceType: string; // 'popup', 'navigate', 'targetScreen' 등
|
||||
}>;
|
||||
}> {
|
||||
// 권한 확인
|
||||
const targetScreen = await prisma.screen_definitions.findUnique({
|
||||
where: { screen_id: screenId },
|
||||
});
|
||||
|
||||
if (!targetScreen) {
|
||||
throw new Error("화면을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
if (
|
||||
userCompanyCode !== "*" &&
|
||||
targetScreen.company_code !== "*" &&
|
||||
targetScreen.company_code !== userCompanyCode
|
||||
) {
|
||||
throw new Error("이 화면에 접근할 권한이 없습니다.");
|
||||
}
|
||||
|
||||
// 같은 회사의 모든 활성 화면에서 이 화면을 참조하는지 확인
|
||||
const whereClause = {
|
||||
is_active: { not: "D" },
|
||||
...(userCompanyCode !== "*" && {
|
||||
company_code: { in: [userCompanyCode, "*"] },
|
||||
}),
|
||||
};
|
||||
|
||||
const allScreens = await prisma.screen_definitions.findMany({
|
||||
where: whereClause,
|
||||
include: {
|
||||
layouts: true,
|
||||
},
|
||||
});
|
||||
|
||||
const dependencies: Array<{
|
||||
screenId: number;
|
||||
screenName: string;
|
||||
screenCode: string;
|
||||
componentId: string;
|
||||
componentType: string;
|
||||
referenceType: string;
|
||||
}> = [];
|
||||
|
||||
// 각 화면의 레이아웃에서 버튼 컴포넌트들을 검사
|
||||
for (const screen of allScreens) {
|
||||
if (screen.screen_id === screenId) continue; // 자기 자신은 제외
|
||||
|
||||
try {
|
||||
// screen_layouts 테이블에서 버튼 컴포넌트 확인
|
||||
const buttonLayouts = screen.layouts.filter(
|
||||
(layout) => layout.component_type === "widget"
|
||||
);
|
||||
|
||||
for (const layout of buttonLayouts) {
|
||||
const properties = layout.properties as any;
|
||||
|
||||
// 버튼 컴포넌트인지 확인
|
||||
if (properties?.widgetType === "button") {
|
||||
const config = properties.webTypeConfig;
|
||||
if (!config) continue;
|
||||
|
||||
// popup 액션에서 popupScreenId 확인
|
||||
if (
|
||||
config.actionType === "popup" &&
|
||||
config.popupScreenId === screenId
|
||||
) {
|
||||
dependencies.push({
|
||||
screenId: screen.screen_id,
|
||||
screenName: screen.screen_name,
|
||||
screenCode: screen.screen_code,
|
||||
componentId: layout.component_id,
|
||||
componentType: "button",
|
||||
referenceType: "popup",
|
||||
});
|
||||
}
|
||||
|
||||
// navigate 액션에서 navigateScreenId 확인
|
||||
if (
|
||||
config.actionType === "navigate" &&
|
||||
config.navigateScreenId === screenId
|
||||
) {
|
||||
dependencies.push({
|
||||
screenId: screen.screen_id,
|
||||
screenName: screen.screen_name,
|
||||
screenCode: screen.screen_code,
|
||||
componentId: layout.component_id,
|
||||
componentType: "button",
|
||||
referenceType: "navigate",
|
||||
});
|
||||
}
|
||||
|
||||
// navigateUrl에서 화면 ID 패턴 확인 (예: /screens/123)
|
||||
if (
|
||||
config.navigateUrl &&
|
||||
config.navigateUrl.includes(`/screens/${screenId}`)
|
||||
) {
|
||||
dependencies.push({
|
||||
screenId: screen.screen_id,
|
||||
screenName: screen.screen_name,
|
||||
screenCode: screen.screen_code,
|
||||
componentId: layout.component_id,
|
||||
componentType: "button",
|
||||
referenceType: "url",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 기존 layout_metadata도 확인 (하위 호환성)
|
||||
const layoutMetadata = screen.layout_metadata as any;
|
||||
if (layoutMetadata?.components) {
|
||||
const components = layoutMetadata.components;
|
||||
|
||||
for (const component of components) {
|
||||
// 버튼 컴포넌트인지 확인
|
||||
if (
|
||||
component.type === "widget" &&
|
||||
component.widgetType === "button"
|
||||
) {
|
||||
const config = component.webTypeConfig;
|
||||
if (!config) continue;
|
||||
|
||||
// popup 액션에서 targetScreenId 확인
|
||||
if (
|
||||
config.actionType === "popup" &&
|
||||
config.targetScreenId === screenId
|
||||
) {
|
||||
dependencies.push({
|
||||
screenId: screen.screen_id,
|
||||
screenName: screen.screen_name,
|
||||
screenCode: screen.screen_code,
|
||||
componentId: component.id,
|
||||
componentType: "button",
|
||||
referenceType: "popup",
|
||||
});
|
||||
}
|
||||
|
||||
// navigate 액션에서 targetScreenId 확인
|
||||
if (
|
||||
config.actionType === "navigate" &&
|
||||
config.targetScreenId === screenId
|
||||
) {
|
||||
dependencies.push({
|
||||
screenId: screen.screen_id,
|
||||
screenName: screen.screen_name,
|
||||
screenCode: screen.screen_code,
|
||||
componentId: component.id,
|
||||
componentType: "button",
|
||||
referenceType: "navigate",
|
||||
});
|
||||
}
|
||||
|
||||
// navigateUrl에서 화면 ID 패턴 확인 (예: /screens/123)
|
||||
if (
|
||||
config.navigateUrl &&
|
||||
config.navigateUrl.includes(`/screens/${screenId}`)
|
||||
) {
|
||||
dependencies.push({
|
||||
screenId: screen.screen_id,
|
||||
screenName: screen.screen_name,
|
||||
screenCode: screen.screen_code,
|
||||
componentId: component.id,
|
||||
componentType: "button",
|
||||
referenceType: "url",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`화면 ${screen.screen_id}의 레이아웃 분석 중 오류:`,
|
||||
error
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 메뉴 할당 확인
|
||||
const menuAssignments = await prisma.screen_menu_assignments.findMany({
|
||||
where: {
|
||||
screen_id: screenId,
|
||||
is_active: "Y",
|
||||
},
|
||||
include: {
|
||||
menu_info: true, // 메뉴 정보도 함께 조회
|
||||
},
|
||||
});
|
||||
|
||||
// 메뉴에 할당된 경우 의존성에 추가
|
||||
for (const assignment of menuAssignments) {
|
||||
dependencies.push({
|
||||
screenId: 0, // 메뉴는 화면이 아니므로 0으로 설정
|
||||
screenName: assignment.menu_info?.menu_name_kor || "알 수 없는 메뉴",
|
||||
screenCode: `MENU_${assignment.menu_objid}`,
|
||||
componentId: `menu_${assignment.assignment_id}`,
|
||||
componentType: "menu",
|
||||
referenceType: "menu_assignment",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
hasDependencies: dependencies.length > 0,
|
||||
dependencies,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면 정의 삭제 (휴지통으로 이동 - 소프트 삭제)
|
||||
*/
|
||||
async deleteScreen(
|
||||
screenId: number,
|
||||
userCompanyCode: string,
|
||||
deletedBy: string,
|
||||
deleteReason?: string,
|
||||
force: boolean = false
|
||||
): Promise<void> {
|
||||
// 권한 확인
|
||||
const existingScreen = await prisma.screen_definitions.findUnique({
|
||||
where: { screen_id: screenId },
|
||||
@@ -215,11 +458,315 @@ export class ScreenManagementService {
|
||||
throw new Error("이 화면을 삭제할 권한이 없습니다.");
|
||||
}
|
||||
|
||||
// 이미 삭제된 화면인지 확인
|
||||
if (existingScreen.is_active === "D") {
|
||||
throw new Error("이미 삭제된 화면입니다.");
|
||||
}
|
||||
|
||||
// 강제 삭제가 아닌 경우 의존성 체크
|
||||
if (!force) {
|
||||
const dependencyCheck = await this.checkScreenDependencies(
|
||||
screenId,
|
||||
userCompanyCode
|
||||
);
|
||||
if (dependencyCheck.hasDependencies) {
|
||||
const error = new Error("다른 화면에서 사용 중인 화면입니다.") as any;
|
||||
error.code = "SCREEN_HAS_DEPENDENCIES";
|
||||
error.dependencies = dependencyCheck.dependencies;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 트랜잭션으로 화면 삭제와 메뉴 할당 정리를 함께 처리
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// 소프트 삭제 (휴지통으로 이동)
|
||||
await tx.screen_definitions.update({
|
||||
where: { screen_id: screenId },
|
||||
data: {
|
||||
is_active: "D",
|
||||
deleted_date: new Date(),
|
||||
deleted_by: deletedBy,
|
||||
delete_reason: deleteReason,
|
||||
updated_date: new Date(),
|
||||
updated_by: deletedBy,
|
||||
},
|
||||
});
|
||||
|
||||
// 메뉴 할당도 비활성화
|
||||
await tx.screen_menu_assignments.updateMany({
|
||||
where: {
|
||||
screen_id: screenId,
|
||||
is_active: "Y",
|
||||
},
|
||||
data: {
|
||||
is_active: "N",
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면 복원 (휴지통에서 복원)
|
||||
*/
|
||||
async restoreScreen(
|
||||
screenId: number,
|
||||
userCompanyCode: string,
|
||||
restoredBy: string
|
||||
): Promise<void> {
|
||||
// 권한 확인
|
||||
const existingScreen = await prisma.screen_definitions.findUnique({
|
||||
where: { screen_id: screenId },
|
||||
});
|
||||
|
||||
if (!existingScreen) {
|
||||
throw new Error("화면을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
if (
|
||||
userCompanyCode !== "*" &&
|
||||
existingScreen.company_code !== userCompanyCode
|
||||
) {
|
||||
throw new Error("이 화면을 복원할 권한이 없습니다.");
|
||||
}
|
||||
|
||||
// 삭제된 화면이 아닌 경우
|
||||
if (existingScreen.is_active !== "D") {
|
||||
throw new Error("삭제된 화면이 아닙니다.");
|
||||
}
|
||||
|
||||
// 화면 코드 중복 확인 (복원 시 같은 코드가 이미 존재하는지)
|
||||
const duplicateScreen = await prisma.screen_definitions.findFirst({
|
||||
where: {
|
||||
screen_code: existingScreen.screen_code,
|
||||
is_active: { not: "D" },
|
||||
screen_id: { not: screenId },
|
||||
},
|
||||
});
|
||||
|
||||
if (duplicateScreen) {
|
||||
throw new Error(
|
||||
"같은 화면 코드를 가진 활성 화면이 이미 존재합니다. 복원하려면 기존 화면의 코드를 변경하거나 삭제해주세요."
|
||||
);
|
||||
}
|
||||
|
||||
// 트랜잭션으로 화면 복원과 메뉴 할당 복원을 함께 처리
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// 화면 복원
|
||||
await tx.screen_definitions.update({
|
||||
where: { screen_id: screenId },
|
||||
data: {
|
||||
is_active: "Y",
|
||||
deleted_date: null,
|
||||
deleted_by: null,
|
||||
delete_reason: null,
|
||||
updated_date: new Date(),
|
||||
updated_by: restoredBy,
|
||||
},
|
||||
});
|
||||
|
||||
// 메뉴 할당도 다시 활성화
|
||||
await tx.screen_menu_assignments.updateMany({
|
||||
where: {
|
||||
screen_id: screenId,
|
||||
is_active: "N",
|
||||
},
|
||||
data: {
|
||||
is_active: "Y",
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴지통 화면들의 메뉴 할당 정리 (관리자용)
|
||||
*/
|
||||
async cleanupDeletedScreenMenuAssignments(): Promise<{
|
||||
updatedCount: number;
|
||||
message: string;
|
||||
}> {
|
||||
const result = await prisma.$executeRaw`
|
||||
UPDATE screen_menu_assignments
|
||||
SET is_active = 'N'
|
||||
WHERE screen_id IN (
|
||||
SELECT screen_id
|
||||
FROM screen_definitions
|
||||
WHERE is_active = 'D'
|
||||
) AND is_active = 'Y'
|
||||
`;
|
||||
|
||||
return {
|
||||
updatedCount: Number(result),
|
||||
message: `${result}개의 메뉴 할당이 정리되었습니다.`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면 영구 삭제 (휴지통에서 완전 삭제)
|
||||
*/
|
||||
async permanentDeleteScreen(
|
||||
screenId: number,
|
||||
userCompanyCode: string
|
||||
): Promise<void> {
|
||||
// 권한 확인
|
||||
const existingScreen = await prisma.screen_definitions.findUnique({
|
||||
where: { screen_id: screenId },
|
||||
});
|
||||
|
||||
if (!existingScreen) {
|
||||
throw new Error("화면을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
if (
|
||||
userCompanyCode !== "*" &&
|
||||
existingScreen.company_code !== userCompanyCode
|
||||
) {
|
||||
throw new Error("이 화면을 영구 삭제할 권한이 없습니다.");
|
||||
}
|
||||
|
||||
// 삭제된 화면이 아닌 경우 영구 삭제 불가
|
||||
if (existingScreen.is_active !== "D") {
|
||||
throw new Error("휴지통에 있는 화면만 영구 삭제할 수 있습니다.");
|
||||
}
|
||||
|
||||
// 물리적 삭제 (CASCADE로 관련 레이아웃과 메뉴 할당도 함께 삭제됨)
|
||||
await prisma.screen_definitions.delete({
|
||||
where: { screen_id: screenId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴지통 화면 목록 조회
|
||||
*/
|
||||
async getDeletedScreens(
|
||||
companyCode: string,
|
||||
page: number = 1,
|
||||
size: number = 20
|
||||
): Promise<
|
||||
PaginatedResponse<
|
||||
ScreenDefinition & {
|
||||
deletedDate?: Date;
|
||||
deletedBy?: string;
|
||||
deleteReason?: string;
|
||||
}
|
||||
>
|
||||
> {
|
||||
const whereClause: any = { is_active: "D" };
|
||||
|
||||
if (companyCode !== "*") {
|
||||
whereClause.company_code = companyCode;
|
||||
}
|
||||
|
||||
const [screens, total] = await Promise.all([
|
||||
prisma.screen_definitions.findMany({
|
||||
where: whereClause,
|
||||
skip: (page - 1) * size,
|
||||
take: size,
|
||||
orderBy: { deleted_date: "desc" },
|
||||
}),
|
||||
prisma.screen_definitions.count({ where: whereClause }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data: screens.map((screen) => ({
|
||||
...this.mapToScreenDefinition(screen),
|
||||
deletedDate: screen.deleted_date || undefined,
|
||||
deletedBy: screen.deleted_by || undefined,
|
||||
deleteReason: screen.delete_reason || undefined,
|
||||
})),
|
||||
pagination: {
|
||||
page,
|
||||
size,
|
||||
total,
|
||||
totalPages: Math.ceil(total / size),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴지통 화면 일괄 영구 삭제
|
||||
*/
|
||||
async bulkPermanentDeleteScreens(
|
||||
screenIds: number[],
|
||||
userCompanyCode: string
|
||||
): Promise<{
|
||||
deletedCount: number;
|
||||
skippedCount: number;
|
||||
errors: Array<{ screenId: number; error: string }>;
|
||||
}> {
|
||||
if (screenIds.length === 0) {
|
||||
throw new Error("삭제할 화면을 선택해주세요.");
|
||||
}
|
||||
|
||||
// 권한 확인 - 해당 회사의 휴지통 화면들만 조회
|
||||
const whereClause: any = {
|
||||
screen_id: { in: screenIds },
|
||||
is_active: "D", // 휴지통에 있는 화면만
|
||||
};
|
||||
|
||||
if (userCompanyCode !== "*") {
|
||||
whereClause.company_code = userCompanyCode;
|
||||
}
|
||||
|
||||
const screensToDelete = await prisma.screen_definitions.findMany({
|
||||
where: whereClause,
|
||||
});
|
||||
|
||||
let deletedCount = 0;
|
||||
let skippedCount = 0;
|
||||
const errors: Array<{ screenId: number; error: string }> = [];
|
||||
|
||||
// 각 화면을 개별적으로 삭제 처리
|
||||
for (const screenId of screenIds) {
|
||||
try {
|
||||
const screenToDelete = screensToDelete.find(
|
||||
(s) => s.screen_id === screenId
|
||||
);
|
||||
|
||||
if (!screenToDelete) {
|
||||
skippedCount++;
|
||||
errors.push({
|
||||
screenId,
|
||||
error: "화면을 찾을 수 없거나 삭제 권한이 없습니다.",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// 관련 레이아웃 데이터도 함께 삭제
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// screen_layouts 삭제
|
||||
await tx.screen_layouts.deleteMany({
|
||||
where: { screen_id: screenId },
|
||||
});
|
||||
|
||||
// screen_menu_assignments 삭제
|
||||
await tx.screen_menu_assignments.deleteMany({
|
||||
where: { screen_id: screenId },
|
||||
});
|
||||
|
||||
// screen_definitions 삭제
|
||||
await tx.screen_definitions.delete({
|
||||
where: { screen_id: screenId },
|
||||
});
|
||||
});
|
||||
|
||||
deletedCount++;
|
||||
} catch (error) {
|
||||
skippedCount++;
|
||||
errors.push({
|
||||
screenId,
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
console.error(`화면 ${screenId} 영구 삭제 실패:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deletedCount,
|
||||
skippedCount,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 테이블 관리
|
||||
// ========================================
|
||||
|
||||
Reference in New Issue
Block a user