feat: Enhance master-detail Excel upload functionality with detail update tracking
- Added support for tracking updated detail records during the Excel upload process, improving feedback to users on the number of records inserted and updated. - Updated response messages to provide clearer information about the processing results, including the number of newly inserted and updated detail records. - Refactored related components to ensure consistency in handling detail updates and improve overall user experience during uploads.
This commit is contained in:
@@ -453,6 +453,48 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 채번 정보 병합: table_type_columns에서 inputType 가져오기
|
||||
try {
|
||||
const { getTableColumns } = await import("@/lib/api/tableManagement");
|
||||
const targetTables = isMasterDetail && masterDetailRelation
|
||||
? [masterDetailRelation.masterTable, masterDetailRelation.detailTable]
|
||||
: [tableName];
|
||||
|
||||
// 테이블별 채번 컬럼 수집
|
||||
const numberingColSet = new Set<string>();
|
||||
for (const tbl of targetTables) {
|
||||
const typeResponse = await getTableColumns(tbl);
|
||||
if (typeResponse.success && typeResponse.data?.columns) {
|
||||
for (const tc of typeResponse.data.columns) {
|
||||
if (tc.inputType === "numbering") {
|
||||
try {
|
||||
const settings = typeof tc.detailSettings === "string"
|
||||
? JSON.parse(tc.detailSettings) : tc.detailSettings;
|
||||
if (settings?.numberingRuleId) {
|
||||
numberingColSet.add(tc.columnName);
|
||||
}
|
||||
} catch { /* 파싱 실패 무시 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// systemColumns에 isNumbering 플래그 추가
|
||||
if (numberingColSet.size > 0) {
|
||||
allColumns = allColumns.map((col) => {
|
||||
const rawName = (col as any).originalName || col.name;
|
||||
const colName = rawName.includes(".") ? rawName.split(".")[1] : rawName;
|
||||
if (numberingColSet.has(colName)) {
|
||||
return { ...col, isNumbering: true } as any;
|
||||
}
|
||||
return col;
|
||||
});
|
||||
console.log("✅ 채번 컬럼 감지:", Array.from(numberingColSet));
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("채번 정보 로드 실패 (무시):", error);
|
||||
}
|
||||
|
||||
console.log("✅ 시스템 컬럼 로드 완료 (자동 생성 컬럼 제외):", allColumns);
|
||||
setSystemColumns(allColumns);
|
||||
|
||||
@@ -613,6 +655,34 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 2단계 → 3단계 전환 시: NOT NULL 컬럼 매핑 필수 검증
|
||||
if (currentStep === 2) {
|
||||
// 매핑된 시스템 컬럼 (원본 이름 그대로 + dot 뒤 이름 둘 다 저장)
|
||||
const mappedSystemCols = new Set<string>();
|
||||
columnMappings.filter((m) => m.systemColumn).forEach((m) => {
|
||||
const colName = m.systemColumn!;
|
||||
mappedSystemCols.add(colName); // 원본 (예: user_info.user_id)
|
||||
if (colName.includes(".")) {
|
||||
mappedSystemCols.add(colName.split(".")[1]); // dot 뒤 (예: user_id)
|
||||
}
|
||||
});
|
||||
|
||||
const unmappedRequired = systemColumns.filter((col) => {
|
||||
const rawName = col.name.includes(".") ? col.name.split(".")[1] : col.name;
|
||||
if (AUTO_GENERATED_COLUMNS.includes(rawName.toLowerCase())) return false;
|
||||
if (col.nullable) return false;
|
||||
if (mappedSystemCols.has(col.name) || mappedSystemCols.has(rawName)) return false;
|
||||
if ((col as any).isNumbering) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (unmappedRequired.length > 0) {
|
||||
const colNames = unmappedRequired.map((c) => c.label || c.name).join(", ");
|
||||
toast.error(`필수(NOT NULL) 컬럼이 매핑되지 않았습니다: ${colNames}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentStep((prev) => Math.min(prev + 1, 3));
|
||||
};
|
||||
|
||||
@@ -1397,15 +1467,19 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
|
||||
<SelectItem value="none" className="text-xs sm:text-sm">
|
||||
매핑 안함
|
||||
</SelectItem>
|
||||
{systemColumns.map((col) => (
|
||||
{systemColumns.map((col) => {
|
||||
const isRequired = !col.nullable && !AUTO_GENERATED_COLUMNS.includes(col.name.toLowerCase()) && !(col as any).isNumbering;
|
||||
return (
|
||||
<SelectItem
|
||||
key={col.name}
|
||||
value={col.name}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
{isRequired && <span className="text-destructive mr-1">*</span>}
|
||||
{col.label || col.name} ({col.type})
|
||||
</SelectItem>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/* 중복 체크 체크박스 */}
|
||||
@@ -1427,6 +1501,38 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 미매핑 필수(NOT NULL) 컬럼 경고 */}
|
||||
{(() => {
|
||||
const mappedCols = new Set<string>();
|
||||
columnMappings.filter((m) => m.systemColumn).forEach((m) => {
|
||||
const n = m.systemColumn!;
|
||||
mappedCols.add(n);
|
||||
if (n.includes(".")) mappedCols.add(n.split(".")[1]);
|
||||
});
|
||||
const missing = systemColumns.filter((col) => {
|
||||
const rawName = col.name.includes(".") ? col.name.split(".")[1] : col.name;
|
||||
if (AUTO_GENERATED_COLUMNS.includes(rawName.toLowerCase())) return false;
|
||||
if (col.nullable) return false;
|
||||
if (mappedCols.has(col.name) || mappedCols.has(rawName)) return false;
|
||||
if ((col as any).isNumbering) return false;
|
||||
return true;
|
||||
});
|
||||
if (missing.length === 0) return null;
|
||||
return (
|
||||
<div className="rounded-md border border-destructive/50 bg-destructive/10 p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="mt-0.5 h-4 w-4 text-destructive" />
|
||||
<div className="text-[10px] text-destructive sm:text-xs">
|
||||
<p className="font-medium">필수(NOT NULL) 컬럼이 매핑되지 않았습니다:</p>
|
||||
<p className="mt-1">
|
||||
{missing.map((c) => c.label || c.name).join(", ")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* 중복 체크 안내 */}
|
||||
{duplicateCheckCount > 0 ? (
|
||||
<div className="rounded-md border border-blue-200 bg-blue-50 p-3">
|
||||
|
||||
Reference in New Issue
Block a user