Merge branch 'feature/v2-renewal' of http://39.117.244.52:3000/kjs/ERP-node into feature/v2-unified-renewal

This commit is contained in:
kjs
2026-02-06 10:20:45 +09:00
parent 4e2209bd5d
commit f2bee41336
8 changed files with 820 additions and 292 deletions

View File

@@ -73,20 +73,4 @@ router.get("/categories/:categoryCode/options", (req, res) =>
commonCodeController.getCodeOptions(req, res)
);
// 계층 구조 코드 조회 (트리 형태)
router.get("/categories/:categoryCode/hierarchy", (req, res) =>
commonCodeController.getCodesHierarchy(req, res)
);
// 자식 코드 조회 (연쇄 선택용)
router.get("/categories/:categoryCode/children", (req, res) =>
commonCodeController.getChildCodes(req, res)
);
// 카테고리 → 공통코드 호환 API (레거시 지원)
// 기존 카테고리 타입이 공통코드로 마이그레이션된 후에도 동작
router.get("/category-options/:tableName/:columnName", (req, res) =>
commonCodeController.getCategoryOptionsAsCode(req, res)
);
export default router;

View File

@@ -43,6 +43,7 @@ export interface CreateCategoryValueInput {
icon?: string;
isActive?: boolean;
isDefault?: boolean;
targetCompanyCode?: string; // 최고 관리자가 특정 회사를 선택할 때 사용
}
// 카테고리 값 수정 입력

View File

@@ -47,11 +47,11 @@ class NumberingRuleService {
logger.info("채번 규칙 목록 조회 시작", { companyCode });
const pool = getPool();
// 멀티테넌시: 최고 관리자만 company_code="*" 데이터를 볼 수 있음
let query: string;
let params: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 회사 데이터 조회 가능
query = `
@@ -107,7 +107,7 @@ class NumberingRuleService {
for (const rule of result.rows) {
let partsQuery: string;
let partsParams: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 파트 조회
partsQuery = `
@@ -156,7 +156,7 @@ class NumberingRuleService {
/**
* 현재 메뉴에서 사용 가능한 규칙 목록 조회 (메뉴 스코프)
*
*
* 메뉴 스코프 규칙:
* - menuObjid가 제공되면 형제 메뉴의 채번 규칙 포함
* - 우선순위: menu (형제 메뉴) > table > global
@@ -166,7 +166,7 @@ class NumberingRuleService {
menuObjid?: number
): Promise<NumberingRuleConfig[]> {
let menuAndChildObjids: number[] = []; // catch 블록에서 접근 가능하도록 함수 최상단에 선언
try {
logger.info("메뉴별 사용 가능한 채번 규칙 조회 시작 (메뉴 스코프)", {
companyCode,
@@ -178,14 +178,17 @@ class NumberingRuleService {
// 1. 선택한 메뉴와 하위 메뉴 OBJID 조회 (형제 메뉴 제외)
if (menuObjid) {
menuAndChildObjids = await getMenuAndChildObjids(menuObjid);
logger.info("선택한 메뉴 및 하위 메뉴 OBJID 목록", { menuObjid, menuAndChildObjids });
logger.info("선택한 메뉴 및 하위 메뉴 OBJID 목록", {
menuObjid,
menuAndChildObjids,
});
}
// menuObjid가 없으면 global 규칙만 반환
if (!menuObjid || menuAndChildObjids.length === 0) {
let query: string;
let params: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 global 규칙 조회
query = `
@@ -239,7 +242,7 @@ class NumberingRuleService {
for (const rule of result.rows) {
let partsQuery: string;
let partsParams: any[];
if (companyCode === "*") {
partsQuery = `
SELECT
@@ -281,7 +284,7 @@ class NumberingRuleService {
// 우선순위: menu (형제 메뉴) > table > global
let query: string;
let params: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 규칙 조회
query = `
@@ -333,7 +336,7 @@ class NumberingRuleService {
logger.info("🔍 채번 규칙 쿼리 실행", {
queryPreview: query.substring(0, 200),
paramsTypes: params.map(p => typeof p),
paramsTypes: params.map((p) => typeof p),
paramsValues: params,
});
@@ -346,7 +349,7 @@ class NumberingRuleService {
try {
let partsQuery: string;
let partsParams: any[];
if (companyCode === "*") {
partsQuery = `
SELECT
@@ -379,7 +382,7 @@ class NumberingRuleService {
const partsResult = await pool.query(partsQuery, partsParams);
rule.parts = partsResult.rows;
logger.info("✅ 규칙 파트 조회 성공", {
ruleId: rule.ruleId,
ruleName: rule.ruleName,
@@ -537,11 +540,11 @@ class NumberingRuleService {
companyCode: string
): Promise<NumberingRuleConfig | null> {
const pool = getPool();
// 멀티테넌시: 최고 관리자만 company_code="*" 데이터를 볼 수 있음
let query: string;
let params: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 규칙 조회 가능
query = `
@@ -598,7 +601,7 @@ class NumberingRuleService {
// 파트 정보 조회
let partsQuery: string;
let partsParams: any[];
if (companyCode === "*") {
partsQuery = `
SELECT
@@ -836,12 +839,12 @@ class NumberingRuleService {
return { ...ruleResult.rows[0], parts };
} catch (error: any) {
await client.query("ROLLBACK");
logger.error("채번 규칙 수정 실패", {
logger.error("채번 규칙 수정 실패", {
ruleId,
companyCode,
error: error.message,
stack: error.stack,
updates
updates,
});
throw error;
} finally {
@@ -875,7 +878,7 @@ class NumberingRuleService {
* @param formData 폼 데이터 (카테고리 기반 채번 시 사용)
*/
async previewCode(
ruleId: string,
ruleId: string,
companyCode: string,
formData?: Record<string, any>
): Promise<string> {
@@ -911,21 +914,26 @@ class NumberingRuleService {
case "date": {
// 날짜 (다양한 날짜 형식)
const dateFormat = autoConfig.dateFormat || "YYYYMMDD";
// 컬럼 기준 생성인 경우 폼 데이터에서 날짜 추출
if (autoConfig.useColumnValue && autoConfig.sourceColumnName && formData) {
if (
autoConfig.useColumnValue &&
autoConfig.sourceColumnName &&
formData
) {
const columnValue = formData[autoConfig.sourceColumnName];
if (columnValue) {
const dateValue = columnValue instanceof Date
? columnValue
: new Date(columnValue);
const dateValue =
columnValue instanceof Date
? columnValue
: new Date(columnValue);
if (!isNaN(dateValue.getTime())) {
return this.formatDate(dateValue, dateFormat);
}
}
}
return this.formatDate(new Date(), dateFormat);
}
@@ -938,63 +946,68 @@ class NumberingRuleService {
// 카테고리 기반 코드 생성
const categoryKey = autoConfig.categoryKey; // 예: "item_info.material"
const categoryMappings = autoConfig.categoryMappings || [];
if (!categoryKey || !formData) {
logger.warn("카테고리 키 또는 폼 데이터 없음", { categoryKey, hasFormData: !!formData });
logger.warn("카테고리 키 또는 폼 데이터 없음", {
categoryKey,
hasFormData: !!formData,
});
return "";
}
// categoryKey에서 컬럼명 추출 (예: "item_info.material" -> "material")
const columnName = categoryKey.includes(".")
? categoryKey.split(".")[1]
const columnName = categoryKey.includes(".")
? categoryKey.split(".")[1]
: categoryKey;
// 폼 데이터에서 해당 컬럼의 값 가져오기
const selectedValue = formData[columnName];
logger.info("카테고리 파트 처리", {
categoryKey,
columnName,
logger.info("카테고리 파트 처리", {
categoryKey,
columnName,
selectedValue,
formDataKeys: Object.keys(formData),
mappingsCount: categoryMappings.length
mappingsCount: categoryMappings.length,
});
if (!selectedValue) {
logger.warn("카테고리 값이 선택되지 않음", { columnName, formDataKeys: Object.keys(formData) });
logger.warn("카테고리 값이 선택되지 않음", {
columnName,
formDataKeys: Object.keys(formData),
});
return "";
}
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
// selectedValue는 valueCode일 수 있음 (V2Select에서 valueCode를 value로 사용)
const selectedValueStr = String(selectedValue);
const mapping = categoryMappings.find(
(m: any) => {
// ID로 매칭
if (m.categoryValueId?.toString() === selectedValueStr) return true;
// 라벨로 매칭
if (m.categoryValueLabel === selectedValueStr) return true;
// valueCode로 매칭 (라벨과 동일할 수 있음)
if (m.categoryValueLabel === selectedValueStr) return true;
return false;
}
);
const mapping = categoryMappings.find((m: any) => {
// ID로 매칭
if (m.categoryValueId?.toString() === selectedValueStr)
return true;
// 라벨로 매칭
if (m.categoryValueLabel === selectedValueStr) return true;
// valueCode로 매칭 (라벨과 동일할 수 있음)
if (m.categoryValueLabel === selectedValueStr) return true;
return false;
});
if (mapping) {
logger.info("카테고리 매핑 적용", {
selectedValue,
logger.info("카테고리 매핑 적용", {
selectedValue,
format: mapping.format,
categoryValueLabel: mapping.categoryValueLabel
categoryValueLabel: mapping.categoryValueLabel,
});
return mapping.format || "";
}
logger.warn("카테고리 매핑을 찾을 수 없음", {
selectedValue,
availableMappings: categoryMappings.map((m: any) => ({
id: m.categoryValueId,
label: m.categoryValueLabel
}))
logger.warn("카테고리 매핑을 찾을 수 없음", {
selectedValue,
availableMappings: categoryMappings.map((m: any) => ({
id: m.categoryValueId,
label: m.categoryValueLabel,
})),
});
return "";
}
@@ -1006,7 +1019,12 @@ class NumberingRuleService {
});
const previewCode = parts.join(rule.separator || "");
logger.info("코드 미리보기 생성", { ruleId, previewCode, companyCode, hasFormData: !!formData });
logger.info("코드 미리보기 생성", {
ruleId,
previewCode,
companyCode,
hasFormData: !!formData,
});
return previewCode;
}
@@ -1018,8 +1036,8 @@ class NumberingRuleService {
* @param userInputCode 사용자가 편집한 최종 코드 (수동 입력 부분 추출용)
*/
async allocateCode(
ruleId: string,
companyCode: string,
ruleId: string,
companyCode: string,
formData?: Record<string, any>,
userInputCode?: string
): Promise<string> {
@@ -1033,9 +1051,11 @@ class NumberingRuleService {
if (!rule) throw new Error("규칙을 찾을 수 없습니다");
// 수동 입력 파트가 있고, 사용자가 입력한 코드가 있으면 수동 입력 부분 추출
const manualParts = rule.parts.filter((p: any) => p.generationMethod === "manual");
const manualParts = rule.parts.filter(
(p: any) => p.generationMethod === "manual"
);
let extractedManualValues: string[] = [];
if (manualParts.length > 0 && userInputCode) {
// 프리뷰 코드를 생성해서 ____ 위치 파악
// 🔧 category 파트도 처리하여 올바른 템플릿 생성
@@ -1059,39 +1079,38 @@ class NumberingRuleService {
// 카테고리 파트: formData에서 실제 값을 가져와서 매핑된 형식 사용
const categoryKey = autoConfig.categoryKey;
const categoryMappings = autoConfig.categoryMappings || [];
if (!categoryKey || !formData) {
return "CATEGORY"; // 폴백
}
const columnName = categoryKey.includes(".")
? categoryKey.split(".")[1]
const columnName = categoryKey.includes(".")
? categoryKey.split(".")[1]
: categoryKey;
const selectedValue = formData[columnName];
if (!selectedValue) {
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;
return false;
}
);
const mapping = categoryMappings.find((m: any) => {
if (m.categoryValueId?.toString() === selectedValueStr)
return true;
if (m.categoryValueLabel === selectedValueStr) return true;
return false;
});
return mapping?.format || "CATEGORY";
}
default:
return "";
}
});
const separator = rule.separator || "";
const previewTemplate = previewParts.join(separator);
// 사용자 입력 코드에서 수동 입력 부분 추출
// 예: 템플릿 "R-____-XXX", 사용자입력 "R-MYVALUE-012" → "MYVALUE" 추출
const templateParts = previewTemplate.split("____");
@@ -1100,19 +1119,23 @@ class NumberingRuleService {
for (let i = 0; i < templateParts.length - 1; i++) {
const prefix = templateParts[i];
const suffix = templateParts[i + 1];
// prefix 이후 부분 추출
if (prefix && remainingCode.startsWith(prefix)) {
remainingCode = remainingCode.slice(prefix.length);
}
// suffix 이전까지가 수동 입력 값
if (suffix) {
// suffix에서 순번(XXX)이나 날짜 부분을 제외한 실제 구분자 찾기
const suffixStart = suffix.replace(/X+|DATEPART/g, "");
const manualEndIndex = suffixStart ? remainingCode.indexOf(suffixStart) : remainingCode.length;
const manualEndIndex = suffixStart
? remainingCode.indexOf(suffixStart)
: remainingCode.length;
if (manualEndIndex > 0) {
extractedManualValues.push(remainingCode.slice(0, manualEndIndex));
extractedManualValues.push(
remainingCode.slice(0, manualEndIndex)
);
remainingCode = remainingCode.slice(manualEndIndex);
}
} else {
@@ -1120,8 +1143,10 @@ class NumberingRuleService {
}
}
}
logger.info(`수동 입력 값 추출: userInputCode=${userInputCode}, previewTemplate=${previewTemplate}, extractedManualValues=${JSON.stringify(extractedManualValues)}`);
logger.info(
`수동 입력 값 추출: userInputCode=${userInputCode}, previewTemplate=${previewTemplate}, extractedManualValues=${JSON.stringify(extractedManualValues)}`
);
}
let manualPartIndex = 0;
@@ -1130,7 +1155,10 @@ class NumberingRuleService {
.map((part: any) => {
if (part.generationMethod === "manual") {
// 추출된 수동 입력 값 사용, 없으면 기본값 사용
const manualValue = extractedManualValues[manualPartIndex] || part.manualConfig?.value || "";
const manualValue =
extractedManualValues[manualPartIndex] ||
part.manualConfig?.value ||
"";
manualPartIndex++;
return manualValue;
}
@@ -1155,16 +1183,21 @@ class NumberingRuleService {
case "date": {
// 날짜 (다양한 날짜 형식)
const dateFormat = autoConfig.dateFormat || "YYYYMMDD";
// 컬럼 기준 생성인 경우 폼 데이터에서 날짜 추출
if (autoConfig.useColumnValue && autoConfig.sourceColumnName && formData) {
if (
autoConfig.useColumnValue &&
autoConfig.sourceColumnName &&
formData
) {
const columnValue = formData[autoConfig.sourceColumnName];
if (columnValue) {
// 날짜 문자열 또는 Date 객체를 Date로 변환
const dateValue = columnValue instanceof Date
? columnValue
: new Date(columnValue);
const dateValue =
columnValue instanceof Date
? columnValue
: new Date(columnValue);
if (!isNaN(dateValue.getTime())) {
logger.info("컬럼 기준 날짜 생성", {
sourceColumn: autoConfig.sourceColumnName,
@@ -1185,7 +1218,7 @@ class NumberingRuleService {
});
}
}
// 기본: 현재 날짜 사용
return this.formatDate(new Date(), dateFormat);
}
@@ -1199,60 +1232,65 @@ class NumberingRuleService {
// 카테고리 기반 코드 생성 (allocateCode용)
const categoryKey = autoConfig.categoryKey; // 예: "item_info.material"
const categoryMappings = autoConfig.categoryMappings || [];
if (!categoryKey || !formData) {
logger.warn("allocateCode: 카테고리 키 또는 폼 데이터 없음", { categoryKey, hasFormData: !!formData });
logger.warn("allocateCode: 카테고리 키 또는 폼 데이터 없음", {
categoryKey,
hasFormData: !!formData,
});
return "";
}
// categoryKey에서 컬럼명 추출 (예: "item_info.material" -> "material")
const columnName = categoryKey.includes(".")
? categoryKey.split(".")[1]
const columnName = categoryKey.includes(".")
? categoryKey.split(".")[1]
: categoryKey;
// 폼 데이터에서 해당 컬럼의 값 가져오기
const selectedValue = formData[columnName];
logger.info("allocateCode: 카테고리 파트 처리", {
categoryKey,
columnName,
logger.info("allocateCode: 카테고리 파트 처리", {
categoryKey,
columnName,
selectedValue,
formDataKeys: Object.keys(formData),
mappingsCount: categoryMappings.length
mappingsCount: categoryMappings.length,
});
if (!selectedValue) {
logger.warn("allocateCode: 카테고리 값이 선택되지 않음", { columnName, formDataKeys: Object.keys(formData) });
logger.warn("allocateCode: 카테고리 값이 선택되지 않음", {
columnName,
formDataKeys: Object.keys(formData),
});
return "";
}
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
const selectedValueStr = String(selectedValue);
const mapping = categoryMappings.find(
(m: any) => {
// ID로 매칭
if (m.categoryValueId?.toString() === selectedValueStr) return true;
// 라벨로 매칭
if (m.categoryValueLabel === selectedValueStr) return true;
return false;
}
);
const mapping = categoryMappings.find((m: any) => {
// ID로 매칭
if (m.categoryValueId?.toString() === selectedValueStr)
return true;
// 라벨로 매칭
if (m.categoryValueLabel === selectedValueStr) return true;
return false;
});
if (mapping) {
logger.info("allocateCode: 카테고리 매핑 적용", {
selectedValue,
logger.info("allocateCode: 카테고리 매핑 적용", {
selectedValue,
format: mapping.format,
categoryValueLabel: mapping.categoryValueLabel
categoryValueLabel: mapping.categoryValueLabel,
});
return mapping.format || "";
}
logger.warn("allocateCode: 카테고리 매핑을 찾을 수 없음", {
selectedValue,
availableMappings: categoryMappings.map((m: any) => ({
id: m.categoryValueId,
label: m.categoryValueLabel
}))
logger.warn("allocateCode: 카테고리 매핑을 찾을 수 없음", {
selectedValue,
availableMappings: categoryMappings.map((m: any) => ({
id: m.categoryValueId,
label: m.categoryValueLabel,
})),
});
return "";
}
@@ -1344,14 +1382,17 @@ class NumberingRuleService {
menuObjid?: number
): Promise<NumberingRuleConfig[]> {
try {
logger.info("[테스트] 채번 규칙 목록 조회 시작", { companyCode, menuObjid });
logger.info("[테스트] 채번 규칙 목록 조회 시작", {
companyCode,
menuObjid,
});
const pool = getPool();
// 멀티테넌시: 최고 관리자 vs 일반 회사
let query: string;
let params: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 규칙 조회
query = `
@@ -1508,7 +1549,10 @@ class NumberingRuleService {
WHERE rule_id = $1 AND company_code = $2
ORDER BY part_order
`;
const partsResult = await pool.query(partsQuery, [rule.ruleId, companyCode]);
const partsResult = await pool.query(partsQuery, [
rule.ruleId,
companyCode,
]);
rule.parts = partsResult.rows;
logger.info("테이블+컬럼 기반 채번 규칙 조회 성공 (테스트)", {
@@ -1556,7 +1600,10 @@ class NumberingRuleService {
SELECT rule_id FROM numbering_rules
WHERE rule_id = $1 AND company_code = $2
`;
const existingResult = await client.query(existingQuery, [config.ruleId, companyCode]);
const existingResult = await client.query(existingQuery, [
config.ruleId,
companyCode,
]);
if (existingResult.rows.length > 0) {
// 업데이트
@@ -1671,7 +1718,10 @@ class NumberingRuleService {
try {
await client.query("BEGIN");
logger.info("테스트 테이블에서 채번 규칙 삭제 시작", { ruleId, companyCode });
logger.info("테스트 테이블에서 채번 규칙 삭제 시작", {
ruleId,
companyCode,
});
// 파트 먼저 삭제
await client.query(
@@ -1779,7 +1829,10 @@ class NumberingRuleService {
WHERE rule_id = $1 AND company_code = $2
ORDER BY part_order
`;
const partsResult = await pool.query(partsQuery, [rule.ruleId, companyCode]);
const partsResult = await pool.query(partsQuery, [
rule.ruleId,
companyCode,
]);
rule.parts = partsResult.rows;
logger.info("카테고리 조건 매칭 채번 규칙 찾음", {
@@ -1814,7 +1867,11 @@ class NumberingRuleService {
AND r.category_value_id IS NULL
LIMIT 1
`;
const defaultResult = await pool.query(defaultQuery, [companyCode, tableName, columnName]);
const defaultResult = await pool.query(defaultQuery, [
companyCode,
tableName,
columnName,
]);
if (defaultResult.rows.length > 0) {
const rule = defaultResult.rows[0];
@@ -1831,7 +1888,10 @@ class NumberingRuleService {
WHERE rule_id = $1 AND company_code = $2
ORDER BY part_order
`;
const partsResult = await pool.query(partsQuery, [rule.ruleId, companyCode]);
const partsResult = await pool.query(partsQuery, [
rule.ruleId,
companyCode,
]);
rule.parts = partsResult.rows;
logger.info("기본 채번 규칙 찾음 (카테고리 조건 없음)", {
@@ -1891,8 +1951,12 @@ class NumberingRuleService {
AND r.column_name = $3
ORDER BY r.category_value_id NULLS FIRST, r.created_at
`;
const result = await pool.query(query, [companyCode, tableName, columnName]);
const result = await pool.query(query, [
companyCode,
tableName,
columnName,
]);
// 각 규칙의 파트 정보 조회
for (const rule of result.rows) {
const partsQuery = `
@@ -1907,7 +1971,10 @@ class NumberingRuleService {
WHERE rule_id = $1 AND company_code = $2
ORDER BY part_order
`;
const partsResult = await pool.query(partsQuery, [rule.ruleId, companyCode]);
const partsResult = await pool.query(partsQuery, [
rule.ruleId,
companyCode,
]);
rule.parts = partsResult.rows;
}
@@ -1928,11 +1995,21 @@ class NumberingRuleService {
async copyRulesForCompany(
sourceCompanyCode: string,
targetCompanyCode: string
): Promise<{ copiedCount: number; skippedCount: number; details: string[]; ruleIdMap: Record<string, string> }> {
): Promise<{
copiedCount: number;
skippedCount: number;
details: string[];
ruleIdMap: Record<string, string>;
}> {
const pool = getPool();
const client = await pool.connect();
const result = { copiedCount: 0, skippedCount: 0, details: [] as string[], ruleIdMap: {} as Record<string, string> };
const result = {
copiedCount: 0,
skippedCount: 0,
details: [] as string[],
ruleIdMap: {} as Record<string, string>,
};
try {
await client.query("BEGIN");
@@ -1950,9 +2027,9 @@ class NumberingRuleService {
[targetCompanyCode]
);
if (deleteResult.rowCount && deleteResult.rowCount > 0) {
logger.info("기존 채번규칙 삭제", {
targetCompanyCode,
deletedCount: deleteResult.rowCount
logger.info("기존 채번규칙 삭제", {
targetCompanyCode,
deletedCount: deleteResult.rowCount,
});
}
@@ -1962,9 +2039,9 @@ class NumberingRuleService {
[sourceCompanyCode]
);
logger.info("원본 채번규칙 조회", {
sourceCompanyCode,
count: sourceRulesResult.rowCount
logger.info("원본 채번규칙 조회", {
sourceCompanyCode,
count: sourceRulesResult.rowCount,
});
// 2. 각 채번규칙 복제
@@ -2038,18 +2115,18 @@ class NumberingRuleService {
result.ruleIdMap[rule.rule_id] = newRuleId;
result.copiedCount++;
result.details.push(`복제 완료: ${rule.rule_name}`);
logger.info("채번규칙 복제 완료", {
ruleName: rule.rule_name,
logger.info("채번규칙 복제 완료", {
ruleName: rule.rule_name,
oldRuleId: rule.rule_id,
newRuleId
newRuleId,
});
}
// 3. 화면 레이아웃의 numberingRuleId 참조 업데이트
if (Object.keys(result.ruleIdMap).length > 0) {
logger.info("화면 레이아웃 numberingRuleId 참조 업데이트 시작", {
logger.info("화면 레이아웃 numberingRuleId 참조 업데이트 시작", {
targetCompanyCode,
mappingCount: Object.keys(result.ruleIdMap).length
mappingCount: Object.keys(result.ruleIdMap).length,
});
// 대상 회사의 모든 화면 레이아웃 조회
@@ -2069,9 +2146,13 @@ class NumberingRuleService {
let updated = false;
// 각 매핑에 대해 치환
for (const [oldRuleId, newRuleId] of Object.entries(result.ruleIdMap)) {
for (const [oldRuleId, newRuleId] of Object.entries(
result.ruleIdMap
)) {
if (propsStr.includes(`"${oldRuleId}"`)) {
propsStr = propsStr.split(`"${oldRuleId}"`).join(`"${newRuleId}"`);
propsStr = propsStr
.split(`"${oldRuleId}"`)
.join(`"${newRuleId}"`);
updated = true;
}
}
@@ -2085,27 +2166,33 @@ class NumberingRuleService {
}
}
logger.info("화면 레이아웃 numberingRuleId 참조 업데이트 완료", {
logger.info("화면 레이아웃 numberingRuleId 참조 업데이트 완료", {
targetCompanyCode,
updatedLayouts
updatedLayouts,
});
result.details.push(`화면 레이아웃 ${updatedLayouts}개의 채번규칙 참조 업데이트`);
result.details.push(
`화면 레이아웃 ${updatedLayouts}개의 채번규칙 참조 업데이트`
);
}
await client.query("COMMIT");
logger.info("회사별 채번규칙 복제 완료", {
sourceCompanyCode,
targetCompanyCode,
logger.info("회사별 채번규칙 복제 완료", {
sourceCompanyCode,
targetCompanyCode,
copiedCount: result.copiedCount,
skippedCount: result.skippedCount,
ruleIdMapCount: Object.keys(result.ruleIdMap).length
ruleIdMapCount: Object.keys(result.ruleIdMap).length,
});
return result;
} catch (error) {
await client.query("ROLLBACK");
logger.error("회사별 채번규칙 복제 실패", { error, sourceCompanyCode, targetCompanyCode });
logger.error("회사별 채번규칙 복제 실패", {
error,
sourceCompanyCode,
targetCompanyCode,
});
throw error;
} finally {
client.release();

View File

@@ -3869,6 +3869,7 @@ export class TableManagementService {
columnName: string;
displayName: string;
dataType: string;
inputType?: string;
}>
> {
return await entityJoinService.getReferenceTableColumns(tableName);