fix(UniversalFormModal): 반복 섹션 linkedFieldGroup 매핑 및 서브 테이블 저장 로직 개선
- renderFieldWithColumns()에 repeatContext 파라미터 추가 - linkedFieldGroup 선택 시 repeatContext 유무에 따라 formData/repeatSections 분기 저장 - multiTableSave: UPSERT 대신 SELECT-UPDATE/INSERT 명시적 분기로 변경 - ON CONFLICT 조건 불일치 에러 방지 - 서브 테이블 저장 상세 로그 추가
This commit is contained in:
parent
a278ceca3f
commit
b15b6e21ea
|
|
@ -2010,37 +2010,83 @@ export async function multiTableSave(
|
|||
mainSubItem.company_code = companyCode;
|
||||
}
|
||||
|
||||
const mainSubColumns = Object.keys(mainSubItem).map(col => `"${col}"`).join(", ");
|
||||
const mainSubPlaceholders = Object.keys(mainSubItem).map((_, idx) => `$${idx + 1}`).join(", ");
|
||||
const mainSubValues = Object.values(mainSubItem);
|
||||
logger.info(`서브 테이블 ${tableName} 메인 데이터 저장 준비:`, JSON.stringify(mainSubItem));
|
||||
|
||||
// UPSERT 쿼리 (PK가 있다면)
|
||||
const mainSubInsertQuery = `
|
||||
INSERT INTO "${tableName}" (${mainSubColumns})
|
||||
VALUES (${mainSubPlaceholders})
|
||||
ON CONFLICT ("${linkColumn.subColumn}"${options.mainMarkerColumn ? `, "${options.mainMarkerColumn}"` : ""})
|
||||
DO UPDATE SET
|
||||
${Object.keys(mainSubItem)
|
||||
.filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn)
|
||||
.map(col => `"${col}" = EXCLUDED."${col}"`)
|
||||
.join(", ") || "updated_at = NOW()"}
|
||||
RETURNING *
|
||||
// 먼저 기존 데이터 존재 여부 확인 (user_id + is_primary 조합)
|
||||
const checkQuery = `
|
||||
SELECT * FROM "${tableName}"
|
||||
WHERE "${linkColumn.subColumn}" = $1
|
||||
${options.mainMarkerColumn ? `AND "${options.mainMarkerColumn}" = $2` : ""}
|
||||
${companyCode !== "*" ? `AND company_code = $${options.mainMarkerColumn ? 3 : 2}` : ""}
|
||||
LIMIT 1
|
||||
`;
|
||||
const checkParams: any[] = [savedPkValue];
|
||||
if (options.mainMarkerColumn) {
|
||||
checkParams.push(options.mainMarkerValue ?? true);
|
||||
}
|
||||
if (companyCode !== "*") {
|
||||
checkParams.push(companyCode);
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(`서브 테이블 ${tableName} 메인 데이터 저장:`, { mainSubInsertQuery, mainSubValues });
|
||||
const mainSubResult = await client.query(mainSubInsertQuery, mainSubValues);
|
||||
subTableResults.push({ tableName, type: "main", data: mainSubResult.rows[0] });
|
||||
} catch (err: any) {
|
||||
// ON CONFLICT 실패 시 일반 INSERT 시도
|
||||
logger.warn(`서브 테이블 ${tableName} UPSERT 실패, 일반 INSERT 시도:`, err.message);
|
||||
const simpleInsertQuery = `
|
||||
logger.info(`서브 테이블 ${tableName} 기존 데이터 확인 - 쿼리: ${checkQuery}`);
|
||||
logger.info(`서브 테이블 ${tableName} 기존 데이터 확인 - 파라미터: ${JSON.stringify(checkParams)}`);
|
||||
|
||||
const existingResult = await client.query(checkQuery, checkParams);
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
// UPDATE
|
||||
const updateColumns = Object.keys(mainSubItem)
|
||||
.filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn && col !== "company_code")
|
||||
.map((col, idx) => `"${col}" = $${idx + 1}`)
|
||||
.join(", ");
|
||||
|
||||
const updateValues = Object.keys(mainSubItem)
|
||||
.filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn && col !== "company_code")
|
||||
.map(col => mainSubItem[col]);
|
||||
|
||||
if (updateColumns) {
|
||||
const updateQuery = `
|
||||
UPDATE "${tableName}"
|
||||
SET ${updateColumns}
|
||||
WHERE "${linkColumn.subColumn}" = $${updateValues.length + 1}
|
||||
${options.mainMarkerColumn ? `AND "${options.mainMarkerColumn}" = $${updateValues.length + 2}` : ""}
|
||||
${companyCode !== "*" ? `AND company_code = $${updateValues.length + (options.mainMarkerColumn ? 3 : 2)}` : ""}
|
||||
RETURNING *
|
||||
`;
|
||||
const updateParams = [...updateValues, savedPkValue];
|
||||
if (options.mainMarkerColumn) {
|
||||
updateParams.push(options.mainMarkerValue ?? true);
|
||||
}
|
||||
if (companyCode !== "*") {
|
||||
updateParams.push(companyCode);
|
||||
}
|
||||
|
||||
logger.info(`서브 테이블 ${tableName} 메인 데이터 UPDATE - 쿼리: ${updateQuery}`);
|
||||
logger.info(`서브 테이블 ${tableName} 메인 데이터 UPDATE - 값: ${JSON.stringify(updateParams)}`);
|
||||
|
||||
const updateResult = await client.query(updateQuery, updateParams);
|
||||
subTableResults.push({ tableName, type: "main", data: updateResult.rows[0] });
|
||||
} else {
|
||||
logger.info(`서브 테이블 ${tableName} 메인 데이터 - 업데이트할 컬럼 없음, 기존 데이터 유지`);
|
||||
subTableResults.push({ tableName, type: "main", data: existingResult.rows[0] });
|
||||
}
|
||||
} else {
|
||||
// INSERT
|
||||
const mainSubColumns = Object.keys(mainSubItem).map(col => `"${col}"`).join(", ");
|
||||
const mainSubPlaceholders = Object.keys(mainSubItem).map((_, idx) => `$${idx + 1}`).join(", ");
|
||||
const mainSubValues = Object.values(mainSubItem);
|
||||
|
||||
const insertQuery = `
|
||||
INSERT INTO "${tableName}" (${mainSubColumns})
|
||||
VALUES (${mainSubPlaceholders})
|
||||
RETURNING *
|
||||
`;
|
||||
const simpleResult = await client.query(simpleInsertQuery, mainSubValues);
|
||||
subTableResults.push({ tableName, type: "main", data: simpleResult.rows[0] });
|
||||
|
||||
logger.info(`서브 테이블 ${tableName} 메인 데이터 INSERT - 쿼리: ${insertQuery}`);
|
||||
logger.info(`서브 테이블 ${tableName} 메인 데이터 INSERT - 값: ${JSON.stringify(mainSubValues)}`);
|
||||
|
||||
const insertResult = await client.query(insertQuery, mainSubValues);
|
||||
subTableResults.push({ tableName, type: "main", data: insertResult.rows[0] });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -734,11 +734,54 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
}
|
||||
|
||||
// saveMainAsFirst가 활성화된 경우, 메인 데이터를 서브 테이블에 저장하기 위한 매핑 생성
|
||||
let mainFieldMappings: Array<{ formField: string; targetColumn: string }> | undefined;
|
||||
if (subTableConfig.options?.saveMainAsFirst) {
|
||||
mainFieldMappings = [];
|
||||
|
||||
// 메인 섹션(비반복)의 필드들을 서브 테이블에 매핑
|
||||
// 서브 테이블의 fieldMappings에서 targetColumn을 찾아서 매핑
|
||||
for (const mapping of subTableConfig.fieldMappings || []) {
|
||||
if (mapping.targetColumn) {
|
||||
// 메인 데이터에서 동일한 컬럼명이 있으면 매핑
|
||||
if (mainData[mapping.targetColumn] !== undefined) {
|
||||
mainFieldMappings.push({
|
||||
formField: mapping.targetColumn,
|
||||
targetColumn: mapping.targetColumn,
|
||||
});
|
||||
}
|
||||
// 또는 메인 섹션의 필드 중 같은 이름이 있으면 매핑
|
||||
else {
|
||||
config.sections.forEach((section) => {
|
||||
if (section.repeatable) return;
|
||||
const matchingField = section.fields.find(f => f.columnName === mapping.targetColumn);
|
||||
if (matchingField && mainData[matchingField.columnName] !== undefined) {
|
||||
mainFieldMappings!.push({
|
||||
formField: matchingField.columnName,
|
||||
targetColumn: mapping.targetColumn,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 중복 제거
|
||||
mainFieldMappings = mainFieldMappings.filter((m, idx, arr) =>
|
||||
arr.findIndex(x => x.targetColumn === m.targetColumn) === idx
|
||||
);
|
||||
|
||||
console.log("[UniversalFormModal] 메인 필드 매핑 생성:", mainFieldMappings);
|
||||
}
|
||||
|
||||
subTablesData.push({
|
||||
tableName: subTableConfig.tableName,
|
||||
linkColumn: subTableConfig.linkColumn,
|
||||
items: subItems,
|
||||
options: subTableConfig.options,
|
||||
options: {
|
||||
...subTableConfig.options,
|
||||
mainFieldMappings, // 메인 데이터 매핑 추가
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -885,12 +928,14 @@ export function UniversalFormModalComponent({
|
|||
}, [initializeForm]);
|
||||
|
||||
// 필드 요소 렌더링 (입력 컴포넌트만)
|
||||
// repeatContext: 반복 섹션인 경우 { sectionId, itemId }를 전달
|
||||
const renderFieldElement = (
|
||||
field: FormFieldConfig,
|
||||
value: any,
|
||||
onChangeHandler: (value: any) => void,
|
||||
fieldKey: string,
|
||||
isDisabled: boolean,
|
||||
repeatContext?: { sectionId: string; itemId: string },
|
||||
) => {
|
||||
return (() => {
|
||||
switch (field.fieldType) {
|
||||
|
|
@ -969,11 +1014,24 @@ export function UniversalFormModalComponent({
|
|||
lfg.mappings.forEach((mapping) => {
|
||||
if (mapping.sourceColumn && mapping.targetColumn) {
|
||||
const mappedValue = selectedRow[mapping.sourceColumn];
|
||||
// formData에 직접 저장
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[mapping.targetColumn]: mappedValue,
|
||||
}));
|
||||
|
||||
// 반복 섹션인 경우 repeatSections에 저장, 아니면 formData에 저장
|
||||
if (repeatContext) {
|
||||
setRepeatSections((prev) => {
|
||||
const items = prev[repeatContext.sectionId] || [];
|
||||
const newItems = items.map((item) =>
|
||||
item._id === repeatContext.itemId
|
||||
? { ...item, [mapping.targetColumn]: mappedValue }
|
||||
: item
|
||||
);
|
||||
return { ...prev, [repeatContext.sectionId]: newItems };
|
||||
});
|
||||
} else {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[mapping.targetColumn]: mappedValue,
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1116,12 +1174,14 @@ export function UniversalFormModalComponent({
|
|||
};
|
||||
|
||||
// 필드 렌더링 (섹션 열 수 적용)
|
||||
// repeatContext: 반복 섹션인 경우 { sectionId, itemId }를 전달
|
||||
const renderFieldWithColumns = (
|
||||
field: FormFieldConfig,
|
||||
value: any,
|
||||
onChangeHandler: (value: any) => void,
|
||||
fieldKey: string,
|
||||
sectionColumns: number = 2,
|
||||
repeatContext?: { sectionId: string; itemId: string },
|
||||
) => {
|
||||
// 섹션 열 수에 따른 기본 gridSpan 계산 (섹션 열 수가 우선)
|
||||
const defaultSpan = getDefaultGridSpan(sectionColumns);
|
||||
|
|
@ -1135,7 +1195,7 @@ export function UniversalFormModalComponent({
|
|||
return null;
|
||||
}
|
||||
|
||||
const fieldElement = renderFieldElement(field, value, onChangeHandler, fieldKey, isDisabled);
|
||||
const fieldElement = renderFieldElement(field, value, onChangeHandler, fieldKey, isDisabled, repeatContext);
|
||||
|
||||
if (field.fieldType === "checkbox") {
|
||||
return (
|
||||
|
|
@ -1275,6 +1335,7 @@ export function UniversalFormModalComponent({
|
|||
(value) => handleRepeatFieldChange(section.id, item._id, field.columnName, value),
|
||||
`${section.id}-${item._id}-${field.id}`,
|
||||
sectionColumns,
|
||||
{ sectionId: section.id, itemId: item._id }, // 반복 섹션 컨텍스트 전달
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue