Merge pull request 'fix/daejin-bugs' (#323) from fix/daejin-bugs into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/323
This commit is contained in:
commit
338c885cfa
|
|
@ -2409,11 +2409,19 @@ export class TableManagementService {
|
|||
}
|
||||
|
||||
// SET 절 생성 (수정할 데이터) - 먼저 생성
|
||||
// 🔧 테이블에 존재하는 컬럼만 UPDATE (가상 컬럼 제외)
|
||||
const setConditions: string[] = [];
|
||||
const setValues: any[] = [];
|
||||
let paramIndex = 1;
|
||||
const skippedColumns: string[] = [];
|
||||
|
||||
Object.keys(updatedData).forEach((column) => {
|
||||
// 테이블에 존재하지 않는 컬럼은 스킵
|
||||
if (!columnTypeMap.has(column)) {
|
||||
skippedColumns.push(column);
|
||||
return;
|
||||
}
|
||||
|
||||
const dataType = columnTypeMap.get(column) || "text";
|
||||
setConditions.push(
|
||||
`"${column}" = $${paramIndex}::${this.getPostgreSQLType(dataType)}`
|
||||
|
|
@ -2424,6 +2432,10 @@ export class TableManagementService {
|
|||
paramIndex++;
|
||||
});
|
||||
|
||||
if (skippedColumns.length > 0) {
|
||||
logger.info(`⚠️ 테이블에 존재하지 않는 컬럼 스킵: ${skippedColumns.join(", ")}`);
|
||||
}
|
||||
|
||||
// WHERE 조건 생성 (PRIMARY KEY 우선, 없으면 모든 원본 데이터 사용)
|
||||
let whereConditions: string[] = [];
|
||||
let whereValues: any[] = [];
|
||||
|
|
|
|||
|
|
@ -44,7 +44,42 @@ export function AutocompleteSearchInputComponent({
|
|||
const displayField = config?.displayField || propDisplayField || "";
|
||||
const displayFields = config?.displayFields || (displayField ? [displayField] : []); // 다중 표시 필드
|
||||
const displaySeparator = config?.displaySeparator || " → "; // 구분자
|
||||
const valueField = config?.valueField || propValueField || "";
|
||||
|
||||
// valueField 결정: fieldMappings 기반으로 추론 (config.valueField가 fieldMappings에 없으면 무시)
|
||||
const getValueField = () => {
|
||||
// fieldMappings가 있으면 그 안에서 추론 (가장 신뢰할 수 있는 소스)
|
||||
if (config?.fieldMappings && config.fieldMappings.length > 0) {
|
||||
// config.valueField가 fieldMappings의 sourceField에 있으면 사용
|
||||
if (config?.valueField) {
|
||||
const hasValueFieldInMappings = config.fieldMappings.some(
|
||||
(m: any) => m.sourceField === config.valueField
|
||||
);
|
||||
if (hasValueFieldInMappings) {
|
||||
return config.valueField;
|
||||
}
|
||||
// fieldMappings에 없으면 무시하고 추론
|
||||
}
|
||||
|
||||
// _code 또는 _id로 끝나는 필드 우선 (보통 PK나 코드 필드)
|
||||
const codeMapping = config.fieldMappings.find(
|
||||
(m: any) => m.sourceField?.endsWith("_code") || m.sourceField?.endsWith("_id")
|
||||
);
|
||||
if (codeMapping) {
|
||||
return codeMapping.sourceField;
|
||||
}
|
||||
|
||||
// 없으면 첫 번째 매핑 사용
|
||||
return config.fieldMappings[0].sourceField || "";
|
||||
}
|
||||
|
||||
// fieldMappings가 없으면 기존 방식
|
||||
if (config?.valueField) return config.valueField;
|
||||
if (propValueField) return propValueField;
|
||||
|
||||
return "";
|
||||
};
|
||||
const valueField = getValueField();
|
||||
|
||||
const searchFields = config?.searchFields || propSearchFields || displayFields; // 검색 필드도 다중 표시 필드 사용
|
||||
const placeholder = config?.placeholder || propPlaceholder || "검색...";
|
||||
|
||||
|
|
@ -76,11 +111,39 @@ export function AutocompleteSearchInputComponent({
|
|||
// 선택된 데이터를 ref로도 유지 (리렌더링 시 초기화 방지)
|
||||
const selectedDataRef = useRef<EntitySearchResult | null>(null);
|
||||
const inputValueRef = useRef<string>("");
|
||||
const initialValueLoadedRef = useRef<string | null>(null); // 초기값 로드 추적
|
||||
|
||||
// formData에서 현재 값 가져오기 (isInteractive 모드)
|
||||
const currentValue = isInteractive && formData && component?.columnName
|
||||
? formData[component.columnName]
|
||||
: value;
|
||||
// 우선순위: 1) component.columnName, 2) fieldMappings에서 valueField에 매핑된 targetField
|
||||
const getCurrentValue = () => {
|
||||
if (!isInteractive || !formData) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 1. component.columnName으로 직접 바인딩된 경우
|
||||
if (component?.columnName && formData[component.columnName] !== undefined) {
|
||||
return formData[component.columnName];
|
||||
}
|
||||
|
||||
// 2. fieldMappings에서 valueField와 매핑된 targetField에서 값 가져오기
|
||||
if (config?.fieldMappings && Array.isArray(config.fieldMappings)) {
|
||||
const valueFieldMapping = config.fieldMappings.find(
|
||||
(mapping: any) => mapping.sourceField === valueField
|
||||
);
|
||||
|
||||
if (valueFieldMapping) {
|
||||
const targetField = valueFieldMapping.targetField || valueFieldMapping.targetColumn;
|
||||
|
||||
if (targetField && formData[targetField] !== undefined) {
|
||||
return formData[targetField];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const currentValue = getCurrentValue();
|
||||
|
||||
// selectedData 변경 시 ref도 업데이트
|
||||
useEffect(() => {
|
||||
|
|
@ -98,6 +161,79 @@ export function AutocompleteSearchInputComponent({
|
|||
}
|
||||
}, []);
|
||||
|
||||
// 초기값이 있을 때 해당 값의 표시 텍스트를 조회하여 설정
|
||||
useEffect(() => {
|
||||
const loadInitialDisplayValue = async () => {
|
||||
// 이미 로드된 값이거나, 값이 없거나, 이미 선택된 데이터가 있으면 스킵
|
||||
if (!currentValue || selectedData || selectedDataRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 이미 같은 값을 로드한 적이 있으면 스킵
|
||||
if (initialValueLoadedRef.current === currentValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 테이블명과 필드 정보가 없으면 스킵
|
||||
if (!tableName || !valueField) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🔄 AutocompleteSearchInput 초기값 로드:", {
|
||||
currentValue,
|
||||
tableName,
|
||||
valueField,
|
||||
displayFields,
|
||||
});
|
||||
|
||||
try {
|
||||
// API를 통해 해당 값의 표시 텍스트 조회
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
const filterConditionWithValue = {
|
||||
...filterCondition,
|
||||
[valueField]: currentValue,
|
||||
};
|
||||
|
||||
const params = new URLSearchParams({
|
||||
searchText: "",
|
||||
searchFields: searchFields.join(","),
|
||||
filterCondition: JSON.stringify(filterConditionWithValue),
|
||||
page: "1",
|
||||
limit: "10",
|
||||
});
|
||||
|
||||
const response = await apiClient.get<{ success: boolean; data: EntitySearchResult[] }>(
|
||||
`/entity-search/${tableName}?${params.toString()}`
|
||||
);
|
||||
|
||||
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||
const matchedItem = response.data.data.find((item: EntitySearchResult) =>
|
||||
String(item[valueField]) === String(currentValue)
|
||||
);
|
||||
|
||||
if (matchedItem) {
|
||||
const displayText = getDisplayValue(matchedItem);
|
||||
console.log("✅ 초기값 표시 텍스트 로드 성공:", {
|
||||
currentValue,
|
||||
displayText,
|
||||
matchedItem,
|
||||
});
|
||||
|
||||
setSelectedData(matchedItem);
|
||||
setInputValue(displayText);
|
||||
selectedDataRef.current = matchedItem;
|
||||
inputValueRef.current = displayText;
|
||||
initialValueLoadedRef.current = currentValue;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 초기값 표시 텍스트 로드 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadInitialDisplayValue();
|
||||
}, [currentValue, tableName, valueField, displayFields, filterCondition, searchFields, selectedData]);
|
||||
|
||||
// value가 변경되면 표시값 업데이트 - 단, selectedData가 있으면 유지
|
||||
useEffect(() => {
|
||||
// selectedData가 있으면 표시값 유지 (사용자가 방금 선택한 경우)
|
||||
|
|
@ -107,6 +243,7 @@ export function AutocompleteSearchInputComponent({
|
|||
|
||||
if (!currentValue) {
|
||||
setInputValue("");
|
||||
initialValueLoadedRef.current = null; // 값이 없어지면 초기화
|
||||
}
|
||||
}, [currentValue, selectedData]);
|
||||
|
||||
|
|
|
|||
|
|
@ -995,6 +995,40 @@ export class ButtonActionExecutor {
|
|||
console.log("📋 [handleSave] 범용 폼 모달 공통 필드:", commonFields);
|
||||
}
|
||||
|
||||
// 🆕 루트 레벨 formData에서 RepeaterFieldGroup에 전달할 공통 필드 추출
|
||||
// 주문번호, 발주번호 등 마스터-디테일 관계에서 필요한 필드만 명시적으로 지정
|
||||
const masterDetailFields = [
|
||||
// 번호 필드
|
||||
"order_no", // 발주번호
|
||||
"sales_order_no", // 수주번호
|
||||
"shipment_no", // 출하번호
|
||||
"receipt_no", // 입고번호
|
||||
"work_order_no", // 작업지시번호
|
||||
// 거래처 필드
|
||||
"supplier_code", // 공급처 코드
|
||||
"supplier_name", // 공급처 이름
|
||||
"customer_code", // 고객 코드
|
||||
"customer_name", // 고객 이름
|
||||
// 날짜 필드
|
||||
"order_date", // 발주일
|
||||
"sales_date", // 수주일
|
||||
"shipment_date", // 출하일
|
||||
"receipt_date", // 입고일
|
||||
"due_date", // 납기일
|
||||
// 담당자/메모 필드
|
||||
"manager", // 담당자
|
||||
"memo", // 메모
|
||||
"remark", // 비고
|
||||
];
|
||||
|
||||
for (const fieldName of masterDetailFields) {
|
||||
const value = context.formData[fieldName];
|
||||
if (value !== undefined && value !== "" && value !== null && !(fieldName in commonFields)) {
|
||||
commonFields[fieldName] = value;
|
||||
}
|
||||
}
|
||||
console.log("📋 [handleSave] 최종 공통 필드 (마스터-디테일 필드 포함):", commonFields);
|
||||
|
||||
for (const item of parsedData) {
|
||||
// 메타 필드 제거 (eslint 경고 무시 - 의도적으로 분리)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue