상단마스터정보+하단품목정보 수정시 오류 수정

This commit is contained in:
hjjeong 2026-01-12 17:24:25 +09:00
parent 8aa6008351
commit 472fc8633c
2 changed files with 169 additions and 18 deletions

View File

@ -285,11 +285,14 @@ const RepeaterFieldGroupComponent: React.FC<ComponentRendererProps> = (props) =>
// onChange 호출하여 부모에게 알림
if (onChange && items.length > 0) {
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
const dataWithMeta = items.map((item: any) => ({
...item,
_targetTable: targetTable,
_originalItemIds: itemIds, // 🆕 원본 ID 목록도 함께 전달
_existingRecord: !!item.id, // 🆕 기존 레코드 플래그 (id가 있으면 기존 레코드)
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
}));
onChange(dataWithMeta);
}
@ -388,10 +391,13 @@ const RepeaterFieldGroupComponent: React.FC<ComponentRendererProps> = (props) =>
// onChange 호출 (effectiveTargetTable 사용)
if (onChange) {
if (items.length > 0) {
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
const dataWithMeta = items.map((item: any) => ({
...item,
_targetTable: effectiveTargetTable,
_existingRecord: !!item.id,
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
}));
onChange(dataWithMeta);
} else {
@ -673,26 +679,25 @@ const RepeaterFieldGroupComponent: React.FC<ComponentRendererProps> = (props) =>
// 🆕 RepeaterInput에서 항목 변경 시 SplitPanelContext의 addedItemIds 동기화
const handleRepeaterChange = useCallback(
(newValue: any[]) => {
// 🆕 분할 패널에서 우측인 경우, 새 항목에 FK 값과 targetTable 추가
let valueWithMeta = newValue;
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
// 🆕 모든 항목에 메타데이터 추가
let valueWithMeta = newValue.map((item: any) => ({
...item,
_targetTable: effectiveTargetTable || targetTable,
_existingRecord: !!item.id,
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
}));
if (isRightPanel && effectiveTargetTable) {
valueWithMeta = newValue.map((item: any) => {
const itemWithMeta = {
...item,
_targetTable: effectiveTargetTable,
};
// 🆕 FK 값이 있고 새 항목이면 FK 컬럼에 값 추가
if (fkColumn && fkValue && item._isNewItem) {
itemWithMeta[fkColumn] = fkValue;
console.log("🔗 [RepeaterFieldGroup] 새 항목에 FK 값 추가:", {
fkColumn,
fkValue,
});
// 🆕 분할 패널에서 우측인 경우, FK 값 추가
if (isRightPanel && fkColumn && fkValue) {
valueWithMeta = valueWithMeta.map((item: any) => {
if (item._isNewItem) {
console.log("🔗 [RepeaterFieldGroup] 새 항목에 FK 값 추가:", { fkColumn, fkValue });
return { ...item, [fkColumn]: fkValue };
}
return itemWithMeta;
return item;
});
}
@ -754,6 +759,7 @@ const RepeaterFieldGroupComponent: React.FC<ComponentRendererProps> = (props) =>
screenContext?.updateFormData,
isRightPanel,
effectiveTargetTable,
targetTable,
fkColumn,
fkValue,
fieldName,

View File

@ -690,6 +690,151 @@ export class ButtonActionExecutor {
console.log("⚠️ [handleSave] formData 전체 내용:", context.formData);
}
// 🆕 RepeaterFieldGroup JSON 문자열 파싱 및 저장 처리
// formData에 JSON 배열 문자열이 저장된 경우 처리 (반복_필드_그룹 등)
const repeaterJsonKeys = Object.keys(context.formData).filter((key) => {
const value = context.formData[key];
if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) && parsed.length > 0 && parsed[0]._targetTable;
} catch {
return false;
}
}
return false;
});
if (repeaterJsonKeys.length > 0) {
console.log("🔄 [handleSave] RepeaterFieldGroup JSON 문자열 감지:", repeaterJsonKeys);
// 🆕 상단 폼 데이터(마스터 정보) 추출
// RepeaterFieldGroup JSON과 컴포넌트 키를 제외한 나머지가 마스터 정보
const masterFields: Record<string, any> = {};
Object.keys(context.formData).forEach((fieldKey) => {
// 제외 조건
if (fieldKey.startsWith("comp_")) return;
if (fieldKey.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) return;
if (fieldKey.endsWith("_label") || fieldKey.endsWith("_value_label")) return;
const value = context.formData[fieldKey];
// JSON 배열 문자열 제외 (RepeaterFieldGroup 데이터)
if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) return;
// 객체 타입인 경우 (범용_폼_모달 등) 내부 필드를 펼쳐서 추가
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
Object.entries(value).forEach(([innerKey, innerValue]) => {
if (innerKey.endsWith("_label") || innerKey.endsWith("_value_label")) return;
if (innerValue !== undefined && innerValue !== null && innerValue !== "") {
masterFields[innerKey] = innerValue;
}
});
return;
}
// 유효한 값만 포함
if (value !== undefined && value !== null && value !== "") {
masterFields[fieldKey] = value;
}
});
console.log("📋 [handleSave] 상단 마스터 정보 (모든 품목에 적용):", masterFields);
for (const key of repeaterJsonKeys) {
try {
const parsedData = JSON.parse(context.formData[key]);
const repeaterTargetTable = parsedData[0]?._targetTable;
if (!repeaterTargetTable) {
console.warn(`⚠️ [handleSave] RepeaterFieldGroup targetTable 없음 (key: ${key})`);
continue;
}
console.log(`📦 [handleSave] RepeaterFieldGroup 저장 시작: ${repeaterTargetTable}, ${parsedData.length}`);
// 🆕 품목 고유 필드 목록 (RepeaterFieldGroup 설정에서 가져옴)
// 첫 번째 아이템의 _repeaterFields에서 추출
const repeaterFields: string[] = parsedData[0]?._repeaterFields || [];
const itemOnlyFields = new Set([...repeaterFields, 'id']); // id는 항상 포함
console.log("📋 [handleSave] RepeaterFieldGroup 품목 필드:", repeaterFields);
for (const item of parsedData) {
// 메타 필드 제거
const { _targetTable, _isNewItem, _existingRecord, _originalItemIds, _deletedItemIds, _repeaterFields, ...itemData } = item;
// 🔧 품목 고유 필드만 추출 (RepeaterFieldGroup 설정 기반)
const itemOnlyData: Record<string, any> = {};
Object.keys(itemData).forEach((field) => {
if (itemOnlyFields.has(field)) {
itemOnlyData[field] = itemData[field];
}
});
// 🔧 마스터 정보 + 품목 고유 정보 병합
// masterFields: 상단 폼에서 수정한 최신 마스터 정보
// itemOnlyData: 품목 고유 필드만 (품번, 품명, 수량 등)
const dataWithMeta: Record<string, unknown> = {
...masterFields, // 상단 마스터 정보 (최신)
...itemOnlyData, // 품목 고유 필드만
created_by: context.userId,
updated_by: context.userId,
company_code: context.companyCode,
};
// 불필요한 필드 제거
Object.keys(dataWithMeta).forEach((field) => {
if (field.endsWith("_label") || field.endsWith("_value_label") || field.endsWith("_numberingRuleId")) {
delete dataWithMeta[field];
}
});
// 새 레코드 vs 기존 레코드 판단
const isNewRecord = _isNewItem || !item.id || item.id === "" || item.id === undefined;
console.log(`📦 [handleSave] 저장할 데이터 (${isNewRecord ? 'INSERT' : 'UPDATE'}):`, {
id: item.id,
dataWithMeta,
});
if (isNewRecord) {
// INSERT - DynamicFormApi 사용하여 제어관리 실행
delete dataWithMeta.id;
const insertResult = await DynamicFormApi.saveFormData({
screenId: context.screenId || 0,
tableName: repeaterTargetTable,
data: dataWithMeta as Record<string, any>,
});
console.log("✅ [handleSave] RepeaterFieldGroup INSERT 완료:", insertResult.data);
} else if (item.id && _existingRecord === true) {
// UPDATE - 기존 레코드
const originalData = { id: item.id };
const updatedData = { ...dataWithMeta, id: item.id };
const updateResult = await apiClient.put(`/table-management/tables/${repeaterTargetTable}/edit`, {
originalData,
updatedData,
});
console.log("✅ [handleSave] RepeaterFieldGroup UPDATE 완료:", updateResult.data);
}
}
} catch (err) {
console.error(`❌ [handleSave] RepeaterFieldGroup 저장 실패 (key: ${key}):`, err);
}
}
// RepeaterFieldGroup 저장 완료 후 새로고침
console.log("✅ [handleSave] RepeaterFieldGroup 저장 완료");
context.onRefresh?.();
context.onFlowRefresh?.();
window.dispatchEvent(new CustomEvent("closeEditModal"));
window.dispatchEvent(new CustomEvent("saveSuccessInModal"));
return true;
}
// 🆕 Universal Form Modal 테이블 섹션 병합 저장 처리
// 범용_폼_모달 내부에 _tableSection_ 데이터가 있는 경우 공통 필드 + 개별 품목 병합 저장
const universalFormModalResult = await this.handleUniversalFormModalTableSectionSave(config, context, formData);