fix(numbering-rule): 채번규칙 저장 시 allocateNumberingCode로 실제 순번 할당
- generateNumberingCode를 allocateNumberingCode로 변경 (순번 실제 증가) - saveSingleRow/saveMultipleRows/saveWithMultiTable 모두 적용 - NumberingRuleCard: 파트 타입 변경 시 defaultAutoConfig 적용 - NumberingRuleDesigner: 저장 시 partsWithDefaults로 기본값 병합 - sequenceLength/numberLength 기본값 4에서 3으로 변경 - 불필요한 console.log 제거
This commit is contained in:
parent
b15b6e21ea
commit
d908de7f66
|
|
@ -2010,8 +2010,6 @@ export async function multiTableSave(
|
|||
mainSubItem.company_code = companyCode;
|
||||
}
|
||||
|
||||
logger.info(`서브 테이블 ${tableName} 메인 데이터 저장 준비:`, JSON.stringify(mainSubItem));
|
||||
|
||||
// 먼저 기존 데이터 존재 여부 확인 (user_id + is_primary 조합)
|
||||
const checkQuery = `
|
||||
SELECT * FROM "${tableName}"
|
||||
|
|
@ -2027,9 +2025,6 @@ export async function multiTableSave(
|
|||
if (companyCode !== "*") {
|
||||
checkParams.push(companyCode);
|
||||
}
|
||||
|
||||
logger.info(`서브 테이블 ${tableName} 기존 데이터 확인 - 쿼리: ${checkQuery}`);
|
||||
logger.info(`서브 테이블 ${tableName} 기존 데이터 확인 - 파라미터: ${JSON.stringify(checkParams)}`);
|
||||
|
||||
const existingResult = await client.query(checkQuery, checkParams);
|
||||
|
||||
|
|
@ -2061,13 +2056,9 @@ export async function multiTableSave(
|
|||
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 {
|
||||
|
|
@ -2082,9 +2073,6 @@ export async function multiTableSave(
|
|||
RETURNING *
|
||||
`;
|
||||
|
||||
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] });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -897,13 +897,13 @@ class NumberingRuleService {
|
|||
switch (part.partType) {
|
||||
case "sequence": {
|
||||
// 순번 (현재 순번으로 미리보기, 증가 안 함)
|
||||
const length = autoConfig.sequenceLength || 4;
|
||||
const length = autoConfig.sequenceLength || 3;
|
||||
return String(rule.currentSequence || 1).padStart(length, "0");
|
||||
}
|
||||
|
||||
case "number": {
|
||||
// 숫자 (고정 자릿수)
|
||||
const length = autoConfig.numberLength || 4;
|
||||
const length = autoConfig.numberLength || 3;
|
||||
const value = autoConfig.numberValue || 1;
|
||||
return String(value).padStart(length, "0");
|
||||
}
|
||||
|
|
@ -957,13 +957,13 @@ class NumberingRuleService {
|
|||
switch (part.partType) {
|
||||
case "sequence": {
|
||||
// 순번 (자동 증가 숫자)
|
||||
const length = autoConfig.sequenceLength || 4;
|
||||
const length = autoConfig.sequenceLength || 3;
|
||||
return String(rule.currentSequence || 1).padStart(length, "0");
|
||||
}
|
||||
|
||||
case "number": {
|
||||
// 숫자 (고정 자릿수)
|
||||
const length = autoConfig.numberLength || 4;
|
||||
const length = autoConfig.numberLength || 3;
|
||||
const value = autoConfig.numberValue || 1;
|
||||
return String(value).padStart(length, "0");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,20 @@ export const NumberingRuleCard: React.FC<NumberingRuleCardProps> = ({
|
|||
<Label className="text-xs font-medium sm:text-sm">구분 유형</Label>
|
||||
<Select
|
||||
value={part.partType}
|
||||
onValueChange={(value) => onUpdate({ partType: value as CodePartType })}
|
||||
onValueChange={(value) => {
|
||||
const newPartType = value as CodePartType;
|
||||
// 타입 변경 시 해당 타입의 기본 autoConfig 설정
|
||||
const defaultAutoConfig: Record<string, any> = {
|
||||
sequence: { sequenceLength: 3, startFrom: 1 },
|
||||
number: { numberLength: 4, numberValue: 1 },
|
||||
date: { dateFormat: "YYYYMMDD" },
|
||||
text: { textValue: "CODE" },
|
||||
};
|
||||
onUpdate({
|
||||
partType: newPartType,
|
||||
autoConfig: defaultAutoConfig[newPartType] || {}
|
||||
});
|
||||
}}
|
||||
disabled={isPreview}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
|
||||
|
|
|
|||
|
|
@ -196,10 +196,31 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
|||
try {
|
||||
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
|
||||
|
||||
// 파트별 기본 autoConfig 정의
|
||||
const defaultAutoConfigs: Record<string, any> = {
|
||||
sequence: { sequenceLength: 3, startFrom: 1 },
|
||||
number: { numberLength: 4, numberValue: 1 },
|
||||
date: { dateFormat: "YYYYMMDD" },
|
||||
text: { textValue: "" },
|
||||
};
|
||||
|
||||
// 저장 전에 각 파트의 autoConfig에 기본값 채우기
|
||||
const partsWithDefaults = currentRule.parts.map((part) => {
|
||||
if (part.generationMethod === "auto") {
|
||||
const defaults = defaultAutoConfigs[part.partType] || {};
|
||||
return {
|
||||
...part,
|
||||
autoConfig: { ...defaults, ...part.autoConfig },
|
||||
};
|
||||
}
|
||||
return part;
|
||||
});
|
||||
|
||||
// 저장 전에 현재 화면의 테이블명과 menuObjid 자동 설정
|
||||
// 메뉴 기반으로 채번규칙 관리 (menuObjid로 필터링)
|
||||
const ruleToSave = {
|
||||
...currentRule,
|
||||
parts: partsWithDefaults,
|
||||
scopeType: "menu" as const, // 메뉴 기반 채번규칙
|
||||
tableName: currentTableName || currentRule.tableName || null, // 현재 테이블명 (참고용)
|
||||
menuObjid: menuObjid || currentRule.menuObjid || null, // 메뉴 OBJID (필터링 기준)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { ChevronDown, ChevronUp, Plus, Trash2, RefreshCw } from "lucide-react";
|
|||
import { toast } from "sonner";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import { generateNumberingCode } from "@/lib/api/numberingRule";
|
||||
import { generateNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule";
|
||||
|
||||
import {
|
||||
UniversalFormModalComponentProps,
|
||||
|
|
@ -123,14 +123,12 @@ export function UniversalFormModalComponent({
|
|||
useEffect(() => {
|
||||
// 이미 초기화되었으면 스킵
|
||||
if (hasInitialized.current) {
|
||||
console.log("[UniversalFormModal] 이미 초기화됨, 스킵");
|
||||
return;
|
||||
}
|
||||
|
||||
// 최초 initialData 캡처 (이후 변경되어도 이 값 사용)
|
||||
if (initialData && Object.keys(initialData).length > 0) {
|
||||
capturedInitialData.current = JSON.parse(JSON.stringify(initialData)); // 깊은 복사
|
||||
console.log("[UniversalFormModal] initialData 캡처:", capturedInitialData.current);
|
||||
}
|
||||
|
||||
hasInitialized.current = true;
|
||||
|
|
@ -142,7 +140,6 @@ export function UniversalFormModalComponent({
|
|||
useEffect(() => {
|
||||
if (!hasInitialized.current) return; // 최초 초기화 전이면 스킵
|
||||
|
||||
console.log("[UniversalFormModal] config 변경 감지, 재초기화");
|
||||
initializeForm();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [config]);
|
||||
|
|
@ -164,7 +161,6 @@ export function UniversalFormModalComponent({
|
|||
// 각 테이블 데이터 로드
|
||||
for (const tableName of tablesToLoad) {
|
||||
if (!linkedFieldDataCache[tableName]) {
|
||||
console.log(`[UniversalFormModal] linkedFieldGroup 데이터 로드: ${tableName}`);
|
||||
await loadLinkedFieldData(tableName);
|
||||
}
|
||||
}
|
||||
|
|
@ -178,7 +174,6 @@ export function UniversalFormModalComponent({
|
|||
const initializeForm = useCallback(async () => {
|
||||
// 캡처된 initialData 사용 (props로 전달된 initialData가 아닌)
|
||||
const effectiveInitialData = capturedInitialData.current || initialData;
|
||||
console.log("[UniversalFormModal] 폼 초기화 시작, effectiveInitialData:", effectiveInitialData);
|
||||
|
||||
const newFormData: FormDataState = {};
|
||||
const newRepeatSections: { [sectionId: string]: RepeatSectionItem[] } = {};
|
||||
|
|
@ -212,7 +207,6 @@ export function UniversalFormModalComponent({
|
|||
// receiveFromParent가 true이거나, effectiveInitialData에 값이 있으면 적용
|
||||
if (field.receiveFromParent || value === "" || value === undefined) {
|
||||
value = effectiveInitialData[parentField];
|
||||
console.log(`[UniversalFormModal] 필드 ${field.columnName}: initialData에서 값 적용 = ${value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -506,18 +500,26 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
});
|
||||
|
||||
// 저장 시점 채번규칙 처리
|
||||
// 저장 시점 채번규칙 처리 (allocateNumberingCode로 실제 순번 증가)
|
||||
for (const section of config.sections) {
|
||||
for (const field of section.fields) {
|
||||
if (
|
||||
field.numberingRule?.enabled &&
|
||||
field.numberingRule?.generateOnSave &&
|
||||
field.numberingRule?.ruleId &&
|
||||
!dataToSave[field.columnName]
|
||||
field.numberingRule?.ruleId
|
||||
) {
|
||||
const response = await generateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
dataToSave[field.columnName] = response.data.generatedCode;
|
||||
// generateOnSave: 저장 시 새로 생성
|
||||
// generateOnOpen: 열 때 미리보기로 표시했지만, 저장 시 실제 순번 할당 필요
|
||||
if (field.numberingRule.generateOnSave && !dataToSave[field.columnName]) {
|
||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
dataToSave[field.columnName] = response.data.generatedCode;
|
||||
}
|
||||
} else if (field.numberingRule.generateOnOpen && dataToSave[field.columnName]) {
|
||||
// generateOnOpen인 경우, 미리보기 값이 있더라도 실제 순번 할당
|
||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
dataToSave[field.columnName] = response.data.generatedCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -542,7 +544,6 @@ export function UniversalFormModalComponent({
|
|||
if (commonFields.length === 0) {
|
||||
const nonRepeatableSections = config.sections.filter((s) => !s.repeatable);
|
||||
commonFields = nonRepeatableSections.flatMap((s) => s.fields.map((f) => f.columnName));
|
||||
console.log("[UniversalFormModal] 공통 필드 자동 설정:", commonFields);
|
||||
}
|
||||
|
||||
// 반복 섹션 ID가 설정되지 않은 경우, 첫 번째 반복 섹션 사용
|
||||
|
|
@ -550,22 +551,9 @@ export function UniversalFormModalComponent({
|
|||
const repeatableSection = config.sections.find((s) => s.repeatable);
|
||||
if (repeatableSection) {
|
||||
repeatSectionId = repeatableSection.id;
|
||||
console.log("[UniversalFormModal] 반복 섹션 자동 설정:", repeatSectionId);
|
||||
}
|
||||
}
|
||||
|
||||
// 디버깅: 설정 확인
|
||||
console.log("[UniversalFormModal] 다중 행 저장 설정:", {
|
||||
commonFields,
|
||||
repeatSectionId,
|
||||
mainSectionFields,
|
||||
typeColumn,
|
||||
mainTypeValue,
|
||||
subTypeValue,
|
||||
repeatSections,
|
||||
formData,
|
||||
});
|
||||
|
||||
// 반복 섹션 데이터
|
||||
const repeatItems = repeatSections[repeatSectionId] || [];
|
||||
|
||||
|
|
@ -588,10 +576,6 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
});
|
||||
|
||||
console.log("[UniversalFormModal] 공통 데이터:", commonData);
|
||||
console.log("[UniversalFormModal] 메인 섹션 데이터:", mainSectionData);
|
||||
console.log("[UniversalFormModal] 반복 항목:", repeatItems);
|
||||
|
||||
// 메인 행 (공통 데이터 + 메인 섹션 필드)
|
||||
const mainRow: any = { ...commonData, ...mainSectionData };
|
||||
if (typeColumn) {
|
||||
|
|
@ -623,16 +607,20 @@ export function UniversalFormModalComponent({
|
|||
if (section.repeatable) continue;
|
||||
|
||||
for (const field of section.fields) {
|
||||
if (field.numberingRule?.enabled && field.numberingRule?.generateOnSave && field.numberingRule?.ruleId) {
|
||||
const response = await generateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
// 모든 행에 동일한 채번 값 적용 (공통 필드인 경우)
|
||||
if (commonFields.includes(field.columnName)) {
|
||||
rowsToSave.forEach((row) => {
|
||||
row[field.columnName] = response.data?.generatedCode;
|
||||
});
|
||||
} else {
|
||||
rowsToSave[0][field.columnName] = response.data?.generatedCode;
|
||||
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
||||
// generateOnSave 또는 generateOnOpen 모두 저장 시 실제 순번 할당
|
||||
const shouldAllocate = field.numberingRule.generateOnSave || field.numberingRule.generateOnOpen;
|
||||
if (shouldAllocate) {
|
||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
// 모든 행에 동일한 채번 값 적용 (공통 필드인 경우)
|
||||
if (commonFields.includes(field.columnName)) {
|
||||
rowsToSave.forEach((row) => {
|
||||
row[field.columnName] = response.data?.generatedCode;
|
||||
});
|
||||
} else {
|
||||
rowsToSave[0][field.columnName] = response.data?.generatedCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -640,16 +628,11 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
|
||||
// 모든 행 저장
|
||||
console.log("[UniversalFormModal] 저장할 행들:", rowsToSave);
|
||||
console.log("[UniversalFormModal] 저장 테이블:", config.saveConfig.tableName);
|
||||
|
||||
for (let i = 0; i < rowsToSave.length; i++) {
|
||||
const row = rowsToSave[i];
|
||||
console.log(`[UniversalFormModal] ${i + 1}번째 행 저장 시도:`, row);
|
||||
|
||||
// 빈 객체 체크
|
||||
if (Object.keys(row).length === 0) {
|
||||
console.warn(`[UniversalFormModal] ${i + 1}번째 행이 비어있습니다. 건너뜁니다.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -659,8 +642,6 @@ export function UniversalFormModalComponent({
|
|||
throw new Error(response.data?.message || `${i + 1}번째 행 저장 실패`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[UniversalFormModal] ${rowsToSave.length}개 행 저장 완료`);
|
||||
}, [config.sections, config.saveConfig, formData, repeatSections]);
|
||||
|
||||
// 다중 테이블 저장 (범용)
|
||||
|
|
@ -669,9 +650,6 @@ export function UniversalFormModalComponent({
|
|||
if (!customApiSave?.multiTable) return;
|
||||
|
||||
const { multiTable } = customApiSave;
|
||||
console.log("[UniversalFormModal] 다중 테이블 저장 시작:", multiTable);
|
||||
console.log("[UniversalFormModal] 현재 formData:", formData);
|
||||
console.log("[UniversalFormModal] 현재 repeatSections:", repeatSections);
|
||||
|
||||
// 1. 메인 테이블 데이터 구성
|
||||
const mainData: Record<string, any> = {};
|
||||
|
|
@ -685,6 +663,35 @@ export function UniversalFormModalComponent({
|
|||
});
|
||||
});
|
||||
|
||||
// 1-1. 채번규칙 처리 (저장 시점에 실제 순번 할당)
|
||||
for (const section of config.sections) {
|
||||
if (section.repeatable) continue;
|
||||
|
||||
for (const field of section.fields) {
|
||||
// 채번규칙이 활성화된 필드 처리
|
||||
if (
|
||||
field.numberingRule?.enabled &&
|
||||
field.numberingRule?.ruleId
|
||||
) {
|
||||
// 신규 생성이거나 값이 없는 경우에만 채번
|
||||
const isNewRecord = !initialData?.[multiTable.mainTable.primaryKeyColumn];
|
||||
const hasNoValue = !mainData[field.columnName];
|
||||
|
||||
if (isNewRecord || hasNoValue) {
|
||||
try {
|
||||
// allocateNumberingCode로 실제 순번 증가
|
||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
mainData[field.columnName] = response.data.generatedCode;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`채번규칙 할당 실패 (${field.columnName}):`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 서브 테이블 데이터 구성
|
||||
const subTablesData: Array<{
|
||||
tableName: string;
|
||||
|
|
@ -770,8 +777,6 @@ export function UniversalFormModalComponent({
|
|||
mainFieldMappings = mainFieldMappings.filter((m, idx, arr) =>
|
||||
arr.findIndex(x => x.targetColumn === m.targetColumn) === idx
|
||||
);
|
||||
|
||||
console.log("[UniversalFormModal] 메인 필드 매핑 생성:", mainFieldMappings);
|
||||
}
|
||||
|
||||
subTablesData.push({
|
||||
|
|
@ -786,12 +791,6 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
|
||||
// 3. 범용 다중 테이블 저장 API 호출
|
||||
console.log("[UniversalFormModal] 다중 테이블 저장 데이터:", {
|
||||
mainTable: multiTable.mainTable,
|
||||
mainData,
|
||||
subTablesData,
|
||||
});
|
||||
|
||||
const response = await apiClient.post("/table-management/multi-table-save", {
|
||||
mainTable: multiTable.mainTable,
|
||||
mainData,
|
||||
|
|
@ -802,8 +801,6 @@ export function UniversalFormModalComponent({
|
|||
if (!response.data?.success) {
|
||||
throw new Error(response.data?.message || "다중 테이블 저장 실패");
|
||||
}
|
||||
|
||||
console.log("[UniversalFormModal] 다중 테이블 저장 완료:", response.data);
|
||||
}, [config.sections, config.saveConfig, formData, repeatSections, initialData]);
|
||||
|
||||
// 커스텀 API 저장
|
||||
|
|
@ -811,8 +808,6 @@ export function UniversalFormModalComponent({
|
|||
const { customApiSave } = config.saveConfig;
|
||||
if (!customApiSave) return;
|
||||
|
||||
console.log("[UniversalFormModal] 커스텀 API 저장 시작:", customApiSave.apiType);
|
||||
|
||||
const saveWithGenericCustomApi = async () => {
|
||||
if (!customApiSave.customEndpoint) {
|
||||
throw new Error("커스텀 API 엔드포인트가 설정되지 않았습니다.");
|
||||
|
|
@ -856,12 +851,6 @@ export function UniversalFormModalComponent({
|
|||
|
||||
// 저장 처리
|
||||
const handleSave = useCallback(async () => {
|
||||
console.log("[UniversalFormModal] 저장 시작, saveConfig:", {
|
||||
tableName: config.saveConfig.tableName,
|
||||
customApiSave: config.saveConfig.customApiSave,
|
||||
multiRowSave: config.saveConfig.multiRowSave,
|
||||
});
|
||||
|
||||
// 커스텀 API 저장 모드가 아닌 경우에만 테이블명 체크
|
||||
if (!config.saveConfig.customApiSave?.enabled && !config.saveConfig.tableName) {
|
||||
toast.error("저장할 테이블이 설정되지 않았습니다.");
|
||||
|
|
|
|||
|
|
@ -728,7 +728,7 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor
|
|||
<div className="flex flex-col">
|
||||
<span>{table.label || table.name}</span>
|
||||
{table.label && <span className="text-[9px] text-muted-foreground">{table.name}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
|
|
@ -743,7 +743,7 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor
|
|||
<Select
|
||||
value={config.saveConfig.customApiSave?.multiTable?.mainTable?.primaryKeyColumn || "_none_"}
|
||||
onValueChange={(value) =>
|
||||
updateSaveConfig({
|
||||
updateSaveConfig({
|
||||
customApiSave: {
|
||||
...config.saveConfig.customApiSave,
|
||||
multiTable: {
|
||||
|
|
@ -893,7 +893,7 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor
|
|||
<div className="flex flex-col">
|
||||
<span>{table.label || table.name}</span>
|
||||
{table.label && <span className="text-[8px] text-muted-foreground">{table.name}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
|
|
|
|||
Loading…
Reference in New Issue