fix: SplitPanelLayout 그룹 삭제 시 groupByColumns 기준 삭제 및 멀티테넌시 보호 추가(영업관리_거래처별 품목 등록 등에서,,)
This commit is contained in:
parent
17498b1b2b
commit
3e9bf29bcf
|
|
@ -698,6 +698,7 @@ router.post(
|
||||||
try {
|
try {
|
||||||
const { tableName } = req.params;
|
const { tableName } = req.params;
|
||||||
const filterConditions = req.body;
|
const filterConditions = req.body;
|
||||||
|
const userCompany = req.user?.companyCode;
|
||||||
|
|
||||||
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
|
@ -706,11 +707,12 @@ router.post(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🗑️ 그룹 삭제:`, { tableName, filterConditions });
|
console.log(`🗑️ 그룹 삭제:`, { tableName, filterConditions, userCompany });
|
||||||
|
|
||||||
const result = await dataService.deleteGroupRecords(
|
const result = await dataService.deleteGroupRecords(
|
||||||
tableName,
|
tableName,
|
||||||
filterConditions
|
filterConditions,
|
||||||
|
userCompany // 회사 코드 전달
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
|
|
||||||
|
|
@ -1189,6 +1189,13 @@ class DataService {
|
||||||
[tableName]
|
[tableName]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(`🔍 테이블 ${tableName}의 Primary Key 조회 결과:`, {
|
||||||
|
pkColumns: pkResult.map((r) => r.attname),
|
||||||
|
pkCount: pkResult.length,
|
||||||
|
inputId: typeof id === "object" ? JSON.stringify(id).substring(0, 200) + "..." : id,
|
||||||
|
inputIdType: typeof id,
|
||||||
|
});
|
||||||
|
|
||||||
let whereClauses: string[] = [];
|
let whereClauses: string[] = [];
|
||||||
let params: any[] = [];
|
let params: any[] = [];
|
||||||
|
|
||||||
|
|
@ -1216,17 +1223,31 @@ class DataService {
|
||||||
params.push(typeof id === "object" ? id[pkColumn] : id);
|
params.push(typeof id === "object" ? id[pkColumn] : id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryText = `DELETE FROM "${tableName}" WHERE ${whereClauses.join(" AND ")}`;
|
const queryText = `DELETE FROM "${tableName}" WHERE ${whereClauses.join(" AND ")} RETURNING *`;
|
||||||
console.log(`🗑️ 삭제 쿼리:`, queryText, params);
|
console.log(`🗑️ 삭제 쿼리:`, queryText, params);
|
||||||
|
|
||||||
const result = await query<any>(queryText, params);
|
const result = await query<any>(queryText, params);
|
||||||
|
|
||||||
|
// 삭제된 행이 없으면 실패 처리
|
||||||
|
if (result.length === 0) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ 레코드 삭제 실패: ${tableName}, 해당 조건에 맞는 레코드가 없습니다.`,
|
||||||
|
{ whereClauses, params }
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "삭제할 레코드를 찾을 수 없습니다. 이미 삭제되었거나 권한이 없습니다.",
|
||||||
|
error: "RECORD_NOT_FOUND",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`✅ 레코드 삭제 완료: ${tableName}, 영향받은 행: ${result.length}`
|
`✅ 레코드 삭제 완료: ${tableName}, 영향받은 행: ${result.length}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
data: result[0], // 삭제된 레코드 정보 반환
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`레코드 삭제 오류 (${tableName}):`, error);
|
console.error(`레코드 삭제 오류 (${tableName}):`, error);
|
||||||
|
|
@ -1240,10 +1261,14 @@ class DataService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 조건에 맞는 모든 레코드 삭제 (그룹 삭제)
|
* 조건에 맞는 모든 레코드 삭제 (그룹 삭제)
|
||||||
|
* @param tableName 테이블명
|
||||||
|
* @param filterConditions 삭제 조건
|
||||||
|
* @param userCompany 사용자 회사 코드 (멀티테넌시 필터링)
|
||||||
*/
|
*/
|
||||||
async deleteGroupRecords(
|
async deleteGroupRecords(
|
||||||
tableName: string,
|
tableName: string,
|
||||||
filterConditions: Record<string, any>
|
filterConditions: Record<string, any>,
|
||||||
|
userCompany?: string
|
||||||
): Promise<ServiceResponse<{ deleted: number }>> {
|
): Promise<ServiceResponse<{ deleted: number }>> {
|
||||||
try {
|
try {
|
||||||
const validation = await this.validateTableAccess(tableName);
|
const validation = await this.validateTableAccess(tableName);
|
||||||
|
|
@ -1255,6 +1280,7 @@ class DataService {
|
||||||
const whereValues: any[] = [];
|
const whereValues: any[] = [];
|
||||||
let paramIndex = 1;
|
let paramIndex = 1;
|
||||||
|
|
||||||
|
// 사용자 필터 조건 추가
|
||||||
for (const [key, value] of Object.entries(filterConditions)) {
|
for (const [key, value] of Object.entries(filterConditions)) {
|
||||||
whereConditions.push(`"${key}" = $${paramIndex}`);
|
whereConditions.push(`"${key}" = $${paramIndex}`);
|
||||||
whereValues.push(value);
|
whereValues.push(value);
|
||||||
|
|
@ -1269,10 +1295,24 @@ class DataService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔒 멀티테넌시: company_code 필터링 (최고 관리자 제외)
|
||||||
|
const hasCompanyCode = await this.checkColumnExists(tableName, "company_code");
|
||||||
|
if (hasCompanyCode && userCompany && userCompany !== "*") {
|
||||||
|
whereConditions.push(`"company_code" = $${paramIndex}`);
|
||||||
|
whereValues.push(userCompany);
|
||||||
|
paramIndex++;
|
||||||
|
console.log(`🔒 멀티테넌시 필터 적용: company_code = ${userCompany}`);
|
||||||
|
}
|
||||||
|
|
||||||
const whereClause = whereConditions.join(" AND ");
|
const whereClause = whereConditions.join(" AND ");
|
||||||
const deleteQuery = `DELETE FROM "${tableName}" WHERE ${whereClause} RETURNING *`;
|
const deleteQuery = `DELETE FROM "${tableName}" WHERE ${whereClause} RETURNING *`;
|
||||||
|
|
||||||
console.log(`🗑️ 그룹 삭제:`, { tableName, conditions: filterConditions });
|
console.log(`🗑️ 그룹 삭제:`, {
|
||||||
|
tableName,
|
||||||
|
conditions: filterConditions,
|
||||||
|
userCompany,
|
||||||
|
whereClause,
|
||||||
|
});
|
||||||
|
|
||||||
const result = await pool.query(deleteQuery, whereValues);
|
const result = await pool.query(deleteQuery, whereValues);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1613,47 +1613,89 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
try {
|
try {
|
||||||
console.log("🗑️ 데이터 삭제:", { tableName, primaryKey });
|
console.log("🗑️ 데이터 삭제:", { tableName, primaryKey });
|
||||||
|
|
||||||
// 🔍 중복 제거 설정 디버깅
|
// 🔍 그룹 삭제 설정 확인 (editButton.groupByColumns 또는 deduplication)
|
||||||
console.log("🔍 중복 제거 디버깅:", {
|
const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || [];
|
||||||
|
const deduplication = componentConfig.rightPanel?.dataFilter?.deduplication;
|
||||||
|
|
||||||
|
console.log("🔍 삭제 설정 디버깅:", {
|
||||||
panel: deleteModalPanel,
|
panel: deleteModalPanel,
|
||||||
dataFilter: componentConfig.rightPanel?.dataFilter,
|
groupByColumns,
|
||||||
deduplication: componentConfig.rightPanel?.dataFilter?.deduplication,
|
deduplication,
|
||||||
enabled: componentConfig.rightPanel?.dataFilter?.deduplication?.enabled,
|
deduplicationEnabled: deduplication?.enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
// 🔧 중복 제거가 활성화된 경우, groupByColumn 기준으로 모든 관련 레코드 삭제
|
// 🔧 우측 패널 삭제 시 그룹 삭제 조건 확인
|
||||||
if (deleteModalPanel === "right" && componentConfig.rightPanel?.dataFilter?.deduplication?.enabled) {
|
if (deleteModalPanel === "right") {
|
||||||
const deduplication = componentConfig.rightPanel.dataFilter.deduplication;
|
// 1. groupByColumns가 설정된 경우 (패널 설정에서 선택된 컬럼들)
|
||||||
const groupByColumn = deduplication.groupByColumn;
|
if (groupByColumns.length > 0) {
|
||||||
|
const filterConditions: Record<string, any> = {};
|
||||||
if (groupByColumn && deleteModalItem[groupByColumn]) {
|
|
||||||
const groupValue = deleteModalItem[groupByColumn];
|
// 선택된 컬럼들의 값을 필터 조건으로 추가
|
||||||
console.log(`🔗 중복 제거 활성화: ${groupByColumn} = ${groupValue} 기준으로 모든 레코드 삭제`);
|
for (const col of groupByColumns) {
|
||||||
|
if (deleteModalItem[col] !== undefined && deleteModalItem[col] !== null) {
|
||||||
// groupByColumn 값으로 필터링하여 삭제
|
filterConditions[col] = deleteModalItem[col];
|
||||||
const filterConditions: Record<string, any> = {
|
}
|
||||||
[groupByColumn]: groupValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 좌측 패널의 선택된 항목 정보도 포함 (customer_id 등)
|
|
||||||
if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") {
|
|
||||||
const leftColumn = componentConfig.rightPanel.join.leftColumn;
|
|
||||||
const rightColumn = componentConfig.rightPanel.join.rightColumn;
|
|
||||||
filterConditions[rightColumn] = selectedLeftItem[leftColumn];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("🗑️ 그룹 삭제 조건:", filterConditions);
|
// 🔒 안전장치: 조인 모드에서 좌측 패널의 키 값도 필터 조건에 포함
|
||||||
|
// (다른 거래처의 같은 품목이 삭제되는 것을 방지)
|
||||||
|
if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") {
|
||||||
|
const leftColumn = componentConfig.rightPanel.join?.leftColumn;
|
||||||
|
const rightColumn = componentConfig.rightPanel.join?.rightColumn;
|
||||||
|
if (leftColumn && rightColumn && selectedLeftItem[leftColumn]) {
|
||||||
|
// rightColumn이 filterConditions에 없으면 추가
|
||||||
|
if (!filterConditions[rightColumn]) {
|
||||||
|
filterConditions[rightColumn] = selectedLeftItem[leftColumn];
|
||||||
|
console.log(`🔒 안전장치: ${rightColumn} = ${selectedLeftItem[leftColumn]} 추가`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 그룹 삭제 API 호출
|
// 필터 조건이 있으면 그룹 삭제
|
||||||
result = await dataApi.deleteGroupRecords(tableName, filterConditions);
|
if (Object.keys(filterConditions).length > 0) {
|
||||||
} else {
|
console.log(`🔗 그룹 삭제 (groupByColumns): ${groupByColumns.join(", ")} 기준`);
|
||||||
// 단일 레코드 삭제
|
console.log("🗑️ 그룹 삭제 조건:", filterConditions);
|
||||||
|
|
||||||
|
result = await dataApi.deleteGroupRecords(tableName, filterConditions);
|
||||||
|
} else {
|
||||||
|
// 필터 조건이 없으면 단일 삭제
|
||||||
|
console.log("⚠️ groupByColumns 값이 없어 단일 삭제로 전환");
|
||||||
|
result = await dataApi.deleteRecord(tableName, primaryKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. 중복 제거(deduplication)가 활성화된 경우
|
||||||
|
else if (deduplication?.enabled && deduplication?.groupByColumn) {
|
||||||
|
const groupByColumn = deduplication.groupByColumn;
|
||||||
|
const groupValue = deleteModalItem[groupByColumn];
|
||||||
|
|
||||||
|
if (groupValue) {
|
||||||
|
console.log(`🔗 중복 제거 활성화: ${groupByColumn} = ${groupValue} 기준으로 모든 레코드 삭제`);
|
||||||
|
|
||||||
|
const filterConditions: Record<string, any> = {
|
||||||
|
[groupByColumn]: groupValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 좌측 패널의 선택된 항목 정보도 포함 (customer_id 등)
|
||||||
|
if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") {
|
||||||
|
const leftColumn = componentConfig.rightPanel.join.leftColumn;
|
||||||
|
const rightColumn = componentConfig.rightPanel.join.rightColumn;
|
||||||
|
filterConditions[rightColumn] = selectedLeftItem[leftColumn];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🗑️ 그룹 삭제 조건:", filterConditions);
|
||||||
|
result = await dataApi.deleteGroupRecords(tableName, filterConditions);
|
||||||
|
} else {
|
||||||
|
result = await dataApi.deleteRecord(tableName, primaryKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. 그 외: 단일 레코드 삭제
|
||||||
|
else {
|
||||||
result = await dataApi.deleteRecord(tableName, primaryKey);
|
result = await dataApi.deleteRecord(tableName, primaryKey);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 단일 레코드 삭제
|
// 좌측 패널: 단일 레코드 삭제
|
||||||
result = await dataApi.deleteRecord(tableName, primaryKey);
|
result = await dataApi.deleteRecord(tableName, primaryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue