refactor: 카테고리 매핑 로직 개선 및 valueCode 추가
- NumberingRuleService에서 카테고리 매핑 로직을 개선하여, valueCode를 사용한 매핑을 추가했습니다. - 카테고리 값 역변환 로직을 추가하여, category_values 테이블에서 valueCode를 통해 valueId를 조회할 수 있도록 하였습니다. - AutoConfigPanel에서 categoryValueCode를 추가하여 V2Select와의 호환성을 높였습니다. - numbering-rule.ts 타입 정의에 categoryValueCode를 추가하여, 카테고리 값 코드에 대한 매칭을 지원합니다.
This commit is contained in:
@@ -885,9 +885,9 @@ class NumberingRuleService {
|
||||
const rule = await this.getRuleById(ruleId, companyCode);
|
||||
if (!rule) throw new Error("규칙을 찾을 수 없습니다");
|
||||
|
||||
const parts = rule.parts
|
||||
const parts = await Promise.all(rule.parts
|
||||
.sort((a: any, b: any) => a.order - b.order)
|
||||
.map((part: any) => {
|
||||
.map(async (part: any) => {
|
||||
if (part.generationMethod === "manual") {
|
||||
// 수동 입력 - 항상 ____ 마커 사용 (프론트엔드에서 편집 가능하게 처리)
|
||||
// placeholder 텍스트는 프론트엔드에서 별도로 표시
|
||||
@@ -982,17 +982,52 @@ class NumberingRuleService {
|
||||
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
|
||||
// selectedValue는 valueCode일 수 있음 (V2Select에서 valueCode를 value로 사용)
|
||||
const selectedValueStr = String(selectedValue);
|
||||
const mapping = categoryMappings.find((m: any) => {
|
||||
// ID로 매칭
|
||||
let mapping = categoryMappings.find((m: any) => {
|
||||
// ID로 매칭 (기존 방식: V2Select가 valueId를 사용하던 경우)
|
||||
if (m.categoryValueId?.toString() === selectedValueStr)
|
||||
return true;
|
||||
// 라벨로 매칭
|
||||
if (m.categoryValueLabel === selectedValueStr) return true;
|
||||
// valueCode로 매칭 (라벨과 동일할 수 있음)
|
||||
// valueCode로 매칭 (매핑에 categoryValueCode가 있는 경우)
|
||||
if (m.categoryValueCode && m.categoryValueCode === selectedValueStr)
|
||||
return true;
|
||||
// 라벨로 매칭 (폴백)
|
||||
if (m.categoryValueLabel === selectedValueStr) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// 매핑을 못 찾았으면 category_values 테이블에서 valueCode → valueId 역변환 시도
|
||||
if (!mapping) {
|
||||
try {
|
||||
const pool = getPool();
|
||||
const [catTableName, catColumnName] = categoryKey.includes(".")
|
||||
? categoryKey.split(".")
|
||||
: [categoryKey, categoryKey];
|
||||
const cvResult = await pool.query(
|
||||
`SELECT value_id, value_code, value_label FROM category_values
|
||||
WHERE table_name = $1 AND column_name = $2 AND value_code = $3 LIMIT 1`,
|
||||
[catTableName, catColumnName, selectedValueStr]
|
||||
);
|
||||
if (cvResult.rows.length > 0) {
|
||||
const resolvedId = cvResult.rows[0].value_id;
|
||||
const resolvedLabel = cvResult.rows[0].value_label;
|
||||
mapping = categoryMappings.find((m: any) => {
|
||||
if (m.categoryValueId?.toString() === String(resolvedId)) return true;
|
||||
if (m.categoryValueLabel === resolvedLabel) return true;
|
||||
return false;
|
||||
});
|
||||
if (mapping) {
|
||||
logger.info("카테고리 매핑 역변환 성공 (valueCode→valueId)", {
|
||||
valueCode: selectedValueStr,
|
||||
resolvedId,
|
||||
resolvedLabel,
|
||||
format: mapping.format,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (lookupError: any) {
|
||||
logger.warn("카테고리 값 역변환 조회 실패", { error: lookupError.message });
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping) {
|
||||
logger.info("카테고리 매핑 적용", {
|
||||
selectedValue,
|
||||
@@ -1016,7 +1051,7 @@ class NumberingRuleService {
|
||||
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const previewCode = parts.join(rule.separator || "");
|
||||
logger.info("코드 미리보기 생성", {
|
||||
@@ -1059,9 +1094,9 @@ class NumberingRuleService {
|
||||
if (manualParts.length > 0 && userInputCode) {
|
||||
// 프리뷰 코드를 생성해서 ____ 위치 파악
|
||||
// 🔧 category 파트도 처리하여 올바른 템플릿 생성
|
||||
const previewParts = rule.parts
|
||||
const previewParts = await Promise.all(rule.parts
|
||||
.sort((a: any, b: any) => a.order - b.order)
|
||||
.map((part: any) => {
|
||||
.map(async (part: any) => {
|
||||
if (part.generationMethod === "manual") {
|
||||
return "____";
|
||||
}
|
||||
@@ -1077,36 +1112,57 @@ class NumberingRuleService {
|
||||
return "DATEPART"; // 날짜 자리 표시
|
||||
case "category": {
|
||||
// 카테고리 파트: formData에서 실제 값을 가져와서 매핑된 형식 사용
|
||||
const categoryKey = autoConfig.categoryKey;
|
||||
const categoryMappings = autoConfig.categoryMappings || [];
|
||||
const catKey2 = autoConfig.categoryKey;
|
||||
const catMappings2 = autoConfig.categoryMappings || [];
|
||||
|
||||
if (!categoryKey || !formData) {
|
||||
if (!catKey2 || !formData) {
|
||||
return "CATEGORY"; // 폴백
|
||||
}
|
||||
|
||||
const columnName = categoryKey.includes(".")
|
||||
? categoryKey.split(".")[1]
|
||||
: categoryKey;
|
||||
const selectedValue = formData[columnName];
|
||||
const colName2 = catKey2.includes(".")
|
||||
? catKey2.split(".")[1]
|
||||
: catKey2;
|
||||
const selVal2 = formData[colName2];
|
||||
|
||||
if (!selectedValue) {
|
||||
if (!selVal2) {
|
||||
return "CATEGORY"; // 폴백
|
||||
}
|
||||
|
||||
const selectedValueStr = String(selectedValue);
|
||||
const mapping = categoryMappings.find((m: any) => {
|
||||
if (m.categoryValueId?.toString() === selectedValueStr)
|
||||
return true;
|
||||
if (m.categoryValueLabel === selectedValueStr) return true;
|
||||
const selValStr2 = String(selVal2);
|
||||
let catMapping2 = catMappings2.find((m: any) => {
|
||||
if (m.categoryValueId?.toString() === selValStr2) return true;
|
||||
if (m.categoryValueCode && m.categoryValueCode === selValStr2) return true;
|
||||
if (m.categoryValueLabel === selValStr2) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
return mapping?.format || "CATEGORY";
|
||||
// valueCode → valueId 역변환 시도
|
||||
if (!catMapping2) {
|
||||
try {
|
||||
const pool2 = getPool();
|
||||
const [ct2, cc2] = catKey2.includes(".") ? catKey2.split(".") : [catKey2, catKey2];
|
||||
const cvr2 = await pool2.query(
|
||||
`SELECT value_id, value_label FROM category_values WHERE table_name = $1 AND column_name = $2 AND value_code = $3 LIMIT 1`,
|
||||
[ct2, cc2, selValStr2]
|
||||
);
|
||||
if (cvr2.rows.length > 0) {
|
||||
const rid2 = cvr2.rows[0].value_id;
|
||||
const rlabel2 = cvr2.rows[0].value_label;
|
||||
catMapping2 = catMappings2.find((m: any) => {
|
||||
if (m.categoryValueId?.toString() === String(rid2)) return true;
|
||||
if (m.categoryValueLabel === rlabel2) return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
return catMapping2?.format || "CATEGORY";
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const separator = rule.separator || "";
|
||||
const previewTemplate = previewParts.join(separator);
|
||||
@@ -1150,9 +1206,9 @@ class NumberingRuleService {
|
||||
}
|
||||
|
||||
let manualPartIndex = 0;
|
||||
const parts = rule.parts
|
||||
const parts = await Promise.all(rule.parts
|
||||
.sort((a: any, b: any) => a.order - b.order)
|
||||
.map((part: any) => {
|
||||
.map(async (part: any) => {
|
||||
if (part.generationMethod === "manual") {
|
||||
// 추출된 수동 입력 값 사용, 없으면 기본값 사용
|
||||
const manualValue =
|
||||
@@ -1267,28 +1323,53 @@ class NumberingRuleService {
|
||||
|
||||
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
|
||||
const selectedValueStr = String(selectedValue);
|
||||
const mapping = categoryMappings.find((m: any) => {
|
||||
// ID로 매칭
|
||||
if (m.categoryValueId?.toString() === selectedValueStr)
|
||||
return true;
|
||||
// 라벨로 매칭
|
||||
let allocMapping = categoryMappings.find((m: any) => {
|
||||
if (m.categoryValueId?.toString() === selectedValueStr) return true;
|
||||
if (m.categoryValueCode && m.categoryValueCode === selectedValueStr) return true;
|
||||
if (m.categoryValueLabel === selectedValueStr) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (mapping) {
|
||||
// valueCode → valueId 역변환 시도
|
||||
if (!allocMapping) {
|
||||
try {
|
||||
const pool3 = getPool();
|
||||
const [ct3, cc3] = categoryKey.includes(".") ? categoryKey.split(".") : [categoryKey, categoryKey];
|
||||
const cvr3 = await pool3.query(
|
||||
`SELECT value_id, value_label FROM category_values WHERE table_name = $1 AND column_name = $2 AND value_code = $3 LIMIT 1`,
|
||||
[ct3, cc3, selectedValueStr]
|
||||
);
|
||||
if (cvr3.rows.length > 0) {
|
||||
const rid3 = cvr3.rows[0].value_id;
|
||||
const rlabel3 = cvr3.rows[0].value_label;
|
||||
allocMapping = categoryMappings.find((m: any) => {
|
||||
if (m.categoryValueId?.toString() === String(rid3)) return true;
|
||||
if (m.categoryValueLabel === rlabel3) return true;
|
||||
return false;
|
||||
});
|
||||
if (allocMapping) {
|
||||
logger.info("allocateCode: 카테고리 매핑 역변환 성공", {
|
||||
valueCode: selectedValueStr, resolvedId: rid3, format: allocMapping.format,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
if (allocMapping) {
|
||||
logger.info("allocateCode: 카테고리 매핑 적용", {
|
||||
selectedValue,
|
||||
format: mapping.format,
|
||||
categoryValueLabel: mapping.categoryValueLabel,
|
||||
format: allocMapping.format,
|
||||
categoryValueLabel: allocMapping.categoryValueLabel,
|
||||
});
|
||||
return mapping.format || "";
|
||||
return allocMapping.format || "";
|
||||
}
|
||||
|
||||
logger.warn("allocateCode: 카테고리 매핑을 찾을 수 없음", {
|
||||
selectedValue,
|
||||
availableMappings: categoryMappings.map((m: any) => ({
|
||||
id: m.categoryValueId,
|
||||
code: m.categoryValueCode,
|
||||
label: m.categoryValueLabel,
|
||||
})),
|
||||
});
|
||||
@@ -1299,7 +1380,7 @@ class NumberingRuleService {
|
||||
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const allocatedCode = parts.join(rule.separator || "");
|
||||
|
||||
|
||||
@@ -685,6 +685,7 @@ const CategoryConfigPanel: React.FC<CategoryConfigPanelProps> = ({
|
||||
|
||||
return {
|
||||
valueId: selectedId,
|
||||
valueCode: node.valueCode, // valueCode 추가 (V2Select 호환)
|
||||
valueLabel: node.valueLabel,
|
||||
valuePath: pathParts.join(" > "),
|
||||
};
|
||||
@@ -698,6 +699,7 @@ const CategoryConfigPanel: React.FC<CategoryConfigPanelProps> = ({
|
||||
|
||||
const newMapping: CategoryFormatMapping = {
|
||||
categoryValueId: selectedInfo.valueId,
|
||||
categoryValueCode: selectedInfo.valueCode, // V2Select에서 valueCode를 value로 사용하므로 매칭용 저장
|
||||
categoryValueLabel: selectedInfo.valueLabel,
|
||||
categoryValuePath: selectedInfo.valuePath,
|
||||
format: newFormat.trim(),
|
||||
|
||||
@@ -37,6 +37,7 @@ export type DateFormat =
|
||||
*/
|
||||
export interface CategoryFormatMapping {
|
||||
categoryValueId: number; // 카테고리 값 ID
|
||||
categoryValueCode?: string; // 카테고리 값 코드 (V2Select에서 valueCode 사용 시 매칭용)
|
||||
categoryValueLabel: string; // 카테고리 값 라벨 (표시용)
|
||||
categoryValuePath?: string; // 전체 경로 (예: "원자재/벌크/가스켓")
|
||||
format: string; // 생성할 형식 (예: "ITM", "VLV")
|
||||
|
||||
Reference in New Issue
Block a user