Merge branch 'feature/v2-unified-renewal' of http://39.117.244.52:3000/kjs/ERP-node into feature/v2-unified-renewal
This commit is contained in:
commit
c552f32370
|
|
@ -885,9 +885,9 @@ class NumberingRuleService {
|
||||||
const rule = await this.getRuleById(ruleId, companyCode);
|
const rule = await this.getRuleById(ruleId, companyCode);
|
||||||
if (!rule) throw new Error("규칙을 찾을 수 없습니다");
|
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)
|
.sort((a: any, b: any) => a.order - b.order)
|
||||||
.map((part: any) => {
|
.map(async (part: any) => {
|
||||||
if (part.generationMethod === "manual") {
|
if (part.generationMethod === "manual") {
|
||||||
// 수동 입력 - 항상 ____ 마커 사용 (프론트엔드에서 편집 가능하게 처리)
|
// 수동 입력 - 항상 ____ 마커 사용 (프론트엔드에서 편집 가능하게 처리)
|
||||||
// placeholder 텍스트는 프론트엔드에서 별도로 표시
|
// placeholder 텍스트는 프론트엔드에서 별도로 표시
|
||||||
|
|
@ -982,17 +982,52 @@ class NumberingRuleService {
|
||||||
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
|
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
|
||||||
// selectedValue는 valueCode일 수 있음 (V2Select에서 valueCode를 value로 사용)
|
// selectedValue는 valueCode일 수 있음 (V2Select에서 valueCode를 value로 사용)
|
||||||
const selectedValueStr = String(selectedValue);
|
const selectedValueStr = String(selectedValue);
|
||||||
const mapping = categoryMappings.find((m: any) => {
|
let mapping = categoryMappings.find((m: any) => {
|
||||||
// ID로 매칭
|
// ID로 매칭 (기존 방식: V2Select가 valueId를 사용하던 경우)
|
||||||
if (m.categoryValueId?.toString() === selectedValueStr)
|
if (m.categoryValueId?.toString() === selectedValueStr)
|
||||||
return true;
|
return true;
|
||||||
// 라벨로 매칭
|
// valueCode로 매칭 (매핑에 categoryValueCode가 있는 경우)
|
||||||
if (m.categoryValueLabel === selectedValueStr) return true;
|
if (m.categoryValueCode && m.categoryValueCode === selectedValueStr)
|
||||||
// valueCode로 매칭 (라벨과 동일할 수 있음)
|
return true;
|
||||||
|
// 라벨로 매칭 (폴백)
|
||||||
if (m.categoryValueLabel === selectedValueStr) return true;
|
if (m.categoryValueLabel === selectedValueStr) return true;
|
||||||
return false;
|
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) {
|
if (mapping) {
|
||||||
logger.info("카테고리 매핑 적용", {
|
logger.info("카테고리 매핑 적용", {
|
||||||
selectedValue,
|
selectedValue,
|
||||||
|
|
@ -1016,7 +1051,7 @@ class NumberingRuleService {
|
||||||
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
const previewCode = parts.join(rule.separator || "");
|
const previewCode = parts.join(rule.separator || "");
|
||||||
logger.info("코드 미리보기 생성", {
|
logger.info("코드 미리보기 생성", {
|
||||||
|
|
@ -1059,9 +1094,9 @@ class NumberingRuleService {
|
||||||
if (manualParts.length > 0 && userInputCode) {
|
if (manualParts.length > 0 && userInputCode) {
|
||||||
// 프리뷰 코드를 생성해서 ____ 위치 파악
|
// 프리뷰 코드를 생성해서 ____ 위치 파악
|
||||||
// 🔧 category 파트도 처리하여 올바른 템플릿 생성
|
// 🔧 category 파트도 처리하여 올바른 템플릿 생성
|
||||||
const previewParts = rule.parts
|
const previewParts = await Promise.all(rule.parts
|
||||||
.sort((a: any, b: any) => a.order - b.order)
|
.sort((a: any, b: any) => a.order - b.order)
|
||||||
.map((part: any) => {
|
.map(async (part: any) => {
|
||||||
if (part.generationMethod === "manual") {
|
if (part.generationMethod === "manual") {
|
||||||
return "____";
|
return "____";
|
||||||
}
|
}
|
||||||
|
|
@ -1077,36 +1112,57 @@ class NumberingRuleService {
|
||||||
return "DATEPART"; // 날짜 자리 표시
|
return "DATEPART"; // 날짜 자리 표시
|
||||||
case "category": {
|
case "category": {
|
||||||
// 카테고리 파트: formData에서 실제 값을 가져와서 매핑된 형식 사용
|
// 카테고리 파트: formData에서 실제 값을 가져와서 매핑된 형식 사용
|
||||||
const categoryKey = autoConfig.categoryKey;
|
const catKey2 = autoConfig.categoryKey;
|
||||||
const categoryMappings = autoConfig.categoryMappings || [];
|
const catMappings2 = autoConfig.categoryMappings || [];
|
||||||
|
|
||||||
if (!categoryKey || !formData) {
|
if (!catKey2 || !formData) {
|
||||||
return "CATEGORY"; // 폴백
|
return "CATEGORY"; // 폴백
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnName = categoryKey.includes(".")
|
const colName2 = catKey2.includes(".")
|
||||||
? categoryKey.split(".")[1]
|
? catKey2.split(".")[1]
|
||||||
: categoryKey;
|
: catKey2;
|
||||||
const selectedValue = formData[columnName];
|
const selVal2 = formData[colName2];
|
||||||
|
|
||||||
if (!selectedValue) {
|
if (!selVal2) {
|
||||||
return "CATEGORY"; // 폴백
|
return "CATEGORY"; // 폴백
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedValueStr = String(selectedValue);
|
const selValStr2 = String(selVal2);
|
||||||
const mapping = categoryMappings.find((m: any) => {
|
let catMapping2 = catMappings2.find((m: any) => {
|
||||||
if (m.categoryValueId?.toString() === selectedValueStr)
|
if (m.categoryValueId?.toString() === selValStr2) return true;
|
||||||
return true;
|
if (m.categoryValueCode && m.categoryValueCode === selValStr2) return true;
|
||||||
if (m.categoryValueLabel === selectedValueStr) return true;
|
if (m.categoryValueLabel === selValStr2) return true;
|
||||||
return false;
|
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:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
const separator = rule.separator || "";
|
const separator = rule.separator || "";
|
||||||
const previewTemplate = previewParts.join(separator);
|
const previewTemplate = previewParts.join(separator);
|
||||||
|
|
@ -1150,9 +1206,9 @@ class NumberingRuleService {
|
||||||
}
|
}
|
||||||
|
|
||||||
let manualPartIndex = 0;
|
let manualPartIndex = 0;
|
||||||
const parts = rule.parts
|
const parts = await Promise.all(rule.parts
|
||||||
.sort((a: any, b: any) => a.order - b.order)
|
.sort((a: any, b: any) => a.order - b.order)
|
||||||
.map((part: any) => {
|
.map(async (part: any) => {
|
||||||
if (part.generationMethod === "manual") {
|
if (part.generationMethod === "manual") {
|
||||||
// 추출된 수동 입력 값 사용, 없으면 기본값 사용
|
// 추출된 수동 입력 값 사용, 없으면 기본값 사용
|
||||||
const manualValue =
|
const manualValue =
|
||||||
|
|
@ -1267,28 +1323,53 @@ class NumberingRuleService {
|
||||||
|
|
||||||
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
|
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
|
||||||
const selectedValueStr = String(selectedValue);
|
const selectedValueStr = String(selectedValue);
|
||||||
const mapping = categoryMappings.find((m: any) => {
|
let allocMapping = categoryMappings.find((m: any) => {
|
||||||
// ID로 매칭
|
if (m.categoryValueId?.toString() === selectedValueStr) return true;
|
||||||
if (m.categoryValueId?.toString() === selectedValueStr)
|
if (m.categoryValueCode && m.categoryValueCode === selectedValueStr) return true;
|
||||||
return true;
|
|
||||||
// 라벨로 매칭
|
|
||||||
if (m.categoryValueLabel === selectedValueStr) return true;
|
if (m.categoryValueLabel === selectedValueStr) return true;
|
||||||
return false;
|
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: 카테고리 매핑 적용", {
|
logger.info("allocateCode: 카테고리 매핑 적용", {
|
||||||
selectedValue,
|
selectedValue,
|
||||||
format: mapping.format,
|
format: allocMapping.format,
|
||||||
categoryValueLabel: mapping.categoryValueLabel,
|
categoryValueLabel: allocMapping.categoryValueLabel,
|
||||||
});
|
});
|
||||||
return mapping.format || "";
|
return allocMapping.format || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn("allocateCode: 카테고리 매핑을 찾을 수 없음", {
|
logger.warn("allocateCode: 카테고리 매핑을 찾을 수 없음", {
|
||||||
selectedValue,
|
selectedValue,
|
||||||
availableMappings: categoryMappings.map((m: any) => ({
|
availableMappings: categoryMappings.map((m: any) => ({
|
||||||
id: m.categoryValueId,
|
id: m.categoryValueId,
|
||||||
|
code: m.categoryValueCode,
|
||||||
label: m.categoryValueLabel,
|
label: m.categoryValueLabel,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
@ -1299,7 +1380,7 @@ class NumberingRuleService {
|
||||||
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
const allocatedCode = parts.join(rule.separator || "");
|
const allocatedCode = parts.join(rule.separator || "");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -685,6 +685,7 @@ const CategoryConfigPanel: React.FC<CategoryConfigPanelProps> = ({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
valueId: selectedId,
|
valueId: selectedId,
|
||||||
|
valueCode: node.valueCode, // valueCode 추가 (V2Select 호환)
|
||||||
valueLabel: node.valueLabel,
|
valueLabel: node.valueLabel,
|
||||||
valuePath: pathParts.join(" > "),
|
valuePath: pathParts.join(" > "),
|
||||||
};
|
};
|
||||||
|
|
@ -698,6 +699,7 @@ const CategoryConfigPanel: React.FC<CategoryConfigPanelProps> = ({
|
||||||
|
|
||||||
const newMapping: CategoryFormatMapping = {
|
const newMapping: CategoryFormatMapping = {
|
||||||
categoryValueId: selectedInfo.valueId,
|
categoryValueId: selectedInfo.valueId,
|
||||||
|
categoryValueCode: selectedInfo.valueCode, // V2Select에서 valueCode를 value로 사용하므로 매칭용 저장
|
||||||
categoryValueLabel: selectedInfo.valueLabel,
|
categoryValueLabel: selectedInfo.valueLabel,
|
||||||
categoryValuePath: selectedInfo.valuePath,
|
categoryValuePath: selectedInfo.valuePath,
|
||||||
format: newFormat.trim(),
|
format: newFormat.trim(),
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ export type DateFormat =
|
||||||
*/
|
*/
|
||||||
export interface CategoryFormatMapping {
|
export interface CategoryFormatMapping {
|
||||||
categoryValueId: number; // 카테고리 값 ID
|
categoryValueId: number; // 카테고리 값 ID
|
||||||
|
categoryValueCode?: string; // 카테고리 값 코드 (V2Select에서 valueCode 사용 시 매칭용)
|
||||||
categoryValueLabel: string; // 카테고리 값 라벨 (표시용)
|
categoryValueLabel: string; // 카테고리 값 라벨 (표시용)
|
||||||
categoryValuePath?: string; // 전체 경로 (예: "원자재/벌크/가스켓")
|
categoryValuePath?: string; // 전체 경로 (예: "원자재/벌크/가스켓")
|
||||||
format: string; // 생성할 형식 (예: "ITM", "VLV")
|
format: string; // 생성할 형식 (예: "ITM", "VLV")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue