fix(UniversalFormModal): 반복 섹션 linkedFieldGroup 매핑 및 서브 테이블 저장 로직 개선

- renderFieldWithColumns()에 repeatContext 파라미터 추가

- linkedFieldGroup 선택 시 repeatContext 유무에 따라 formData/repeatSections 분기 저장

- multiTableSave: UPSERT 대신 SELECT-UPDATE/INSERT 명시적 분기로 변경

- ON CONFLICT 조건 불일치 에러 방지

- 서브 테이블 저장 상세 로그 추가
This commit is contained in:
SeongHyun Kim
2025-12-08 18:23:28 +09:00
parent a278ceca3f
commit b15b6e21ea
2 changed files with 138 additions and 31 deletions

View File

@@ -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] });
}
}