jskim-node #387

Merged
kjs merged 6 commits from jskim-node into main 2026-02-09 13:28:08 +09:00
3 changed files with 121 additions and 37 deletions
Showing only changes of commit 9d368b1864 - Show all commits

View File

@ -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 || "");

View File

@ -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(),

View File

@ -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")