feat: Enhance numbering rule service with separator handling

- Introduced functionality to extract and manage individual separators for numbering rule parts.
- Added methods to join parts with their respective separators, improving code generation flexibility.
- Updated the numbering rule service to utilize the new separator logic during part processing.
- Enhanced the frontend components to support custom separators for each part, allowing for more granular control over numbering formats.
This commit is contained in:
kjs
2026-02-25 12:25:30 +09:00
parent 3ca511924e
commit 60b1ac1442
9 changed files with 328 additions and 191 deletions

View File

@@ -14,6 +14,35 @@ interface NumberingRulePart {
autoConfig?: any;
manualConfig?: any;
generatedValue?: string;
separatorAfter?: string;
}
/**
* 파트 배열에서 autoConfig.separatorAfter를 파트 레벨로 추출
*/
function extractSeparatorAfterFromParts(parts: any[]): any[] {
return parts.map((part) => {
if (part.autoConfig?.separatorAfter !== undefined) {
part.separatorAfter = part.autoConfig.separatorAfter;
}
return part;
});
}
/**
* 파트별 개별 구분자를 사용하여 코드 결합
* 마지막 파트의 separatorAfter는 무시됨
*/
function joinPartsWithSeparators(partValues: string[], sortedParts: any[], globalSeparator: string): string {
let result = "";
partValues.forEach((val, idx) => {
result += val;
if (idx < partValues.length - 1) {
const sep = sortedParts[idx].separatorAfter ?? sortedParts[idx].autoConfig?.separatorAfter ?? globalSeparator;
result += sep;
}
});
return result;
}
interface NumberingRuleConfig {
@@ -141,7 +170,7 @@ class NumberingRuleService {
}
const partsResult = await pool.query(partsQuery, partsParams);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
}
logger.info(`채번 규칙 목록 조회 완료: ${result.rows.length}`, {
@@ -274,7 +303,7 @@ class NumberingRuleService {
}
const partsResult = await pool.query(partsQuery, partsParams);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
}
return result.rows;
@@ -381,7 +410,7 @@ class NumberingRuleService {
}
const partsResult = await pool.query(partsQuery, partsParams);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
logger.info("✅ 규칙 파트 조회 성공", {
ruleId: rule.ruleId,
@@ -517,7 +546,7 @@ class NumberingRuleService {
companyCode === "*" ? rule.companyCode : companyCode,
]);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
}
logger.info(`화면용 채번 규칙 조회 완료: ${result.rows.length}`, {
@@ -633,7 +662,7 @@ class NumberingRuleService {
}
const partsResult = await pool.query(partsQuery, partsParams);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
return rule;
}
@@ -708,17 +737,25 @@ class NumberingRuleService {
manual_config AS "manualConfig"
`;
// auto_config에 separatorAfter 포함
const autoConfigWithSep = { ...(part.autoConfig || {}), separatorAfter: part.separatorAfter ?? "-" };
const partResult = await client.query(insertPartQuery, [
config.ruleId,
part.order,
part.partType,
part.generationMethod,
JSON.stringify(part.autoConfig || {}),
JSON.stringify(autoConfigWithSep),
JSON.stringify(part.manualConfig || {}),
companyCode,
]);
parts.push(partResult.rows[0]);
const savedPart = partResult.rows[0];
// autoConfig에서 separatorAfter를 추출하여 파트 레벨로 이동
if (savedPart.autoConfig?.separatorAfter !== undefined) {
savedPart.separatorAfter = savedPart.autoConfig.separatorAfter;
}
parts.push(savedPart);
}
await client.query("COMMIT");
@@ -820,17 +857,23 @@ class NumberingRuleService {
manual_config AS "manualConfig"
`;
const autoConfigWithSep = { ...(part.autoConfig || {}), separatorAfter: part.separatorAfter ?? "-" };
const partResult = await client.query(insertPartQuery, [
ruleId,
part.order,
part.partType,
part.generationMethod,
JSON.stringify(part.autoConfig || {}),
JSON.stringify(autoConfigWithSep),
JSON.stringify(part.manualConfig || {}),
companyCode,
]);
parts.push(partResult.rows[0]);
const savedPart = partResult.rows[0];
if (savedPart.autoConfig?.separatorAfter !== undefined) {
savedPart.separatorAfter = savedPart.autoConfig.separatorAfter;
}
parts.push(savedPart);
}
}
@@ -1053,7 +1096,8 @@ class NumberingRuleService {
}
}));
const previewCode = parts.join(rule.separator || "");
const sortedRuleParts = rule.parts.sort((a: any, b: any) => a.order - b.order);
const previewCode = joinPartsWithSeparators(parts, sortedRuleParts, rule.separator || "");
logger.info("코드 미리보기 생성", {
ruleId,
previewCode,
@@ -1164,8 +1208,8 @@ class NumberingRuleService {
}
}));
const separator = rule.separator || "";
const previewTemplate = previewParts.join(separator);
const sortedPartsForTemplate = rule.parts.sort((a: any, b: any) => a.order - b.order);
const previewTemplate = joinPartsWithSeparators(previewParts, sortedPartsForTemplate, rule.separator || "");
// 사용자 입력 코드에서 수동 입력 부분 추출
// 예: 템플릿 "R-____-XXX", 사용자입력 "R-MYVALUE-012" → "MYVALUE" 추출
@@ -1382,7 +1426,8 @@ class NumberingRuleService {
}
}));
const allocatedCode = parts.join(rule.separator || "");
const sortedPartsForAlloc = rule.parts.sort((a: any, b: any) => a.order - b.order);
const allocatedCode = joinPartsWithSeparators(parts, sortedPartsForAlloc, rule.separator || "");
// 순번이 있는 경우에만 증가
const hasSequence = rule.parts.some(
@@ -1541,7 +1586,7 @@ class NumberingRuleService {
rule.ruleId,
companyCode === "*" ? rule.companyCode : companyCode,
]);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
}
logger.info("[테스트] 채번 규칙 목록 조회 완료", {
@@ -1634,7 +1679,7 @@ class NumberingRuleService {
rule.ruleId,
companyCode,
]);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
logger.info("테이블+컬럼 기반 채번 규칙 조회 성공 (테스트)", {
ruleId: rule.ruleId,
@@ -1754,12 +1799,14 @@ class NumberingRuleService {
auto_config, manual_config, company_code, created_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
`;
const autoConfigWithSep = { ...(part.autoConfig || {}), separatorAfter: part.separatorAfter ?? "-" };
await client.query(partInsertQuery, [
config.ruleId,
part.order,
part.partType,
part.generationMethod,
JSON.stringify(part.autoConfig || {}),
JSON.stringify(autoConfigWithSep),
JSON.stringify(part.manualConfig || {}),
companyCode,
]);
@@ -1914,7 +1961,7 @@ class NumberingRuleService {
rule.ruleId,
companyCode,
]);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
logger.info("카테고리 조건 매칭 채번 규칙 찾음", {
ruleId: rule.ruleId,
@@ -1973,7 +2020,7 @@ class NumberingRuleService {
rule.ruleId,
companyCode,
]);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
logger.info("기본 채번 규칙 찾음 (카테고리 조건 없음)", {
ruleId: rule.ruleId,
@@ -2056,7 +2103,7 @@ class NumberingRuleService {
rule.ruleId,
companyCode,
]);
rule.parts = partsResult.rows;
rule.parts = extractSeparatorAfterFromParts(partsResult.rows);
}
return result.rows;

View File

@@ -1607,7 +1607,8 @@ export class TableManagementService {
tableName,
columnName,
actualValue,
paramIndex
paramIndex,
operator
);
case "entity":
@@ -1620,7 +1621,14 @@ export class TableManagementService {
);
default:
// 기본 문자열 검색 (actualValue 사용)
// operator에 따라 정확 일치 또는 부분 일치 검색
if (operator === "equals") {
return {
whereClause: `${columnName}::text = $${paramIndex}`,
values: [String(actualValue)],
paramCount: 1,
};
}
return {
whereClause: `${columnName}::text ILIKE $${paramIndex}`,
values: [`%${actualValue}%`],
@@ -1634,10 +1642,19 @@ export class TableManagementService {
);
// 오류 시 기본 검색으로 폴백
let fallbackValue = value;
let fallbackOperator = "contains";
if (typeof value === "object" && value !== null && "value" in value) {
fallbackValue = value.value;
fallbackOperator = value.operator || "contains";
}
if (fallbackOperator === "equals") {
return {
whereClause: `${columnName}::text = $${paramIndex}`,
values: [String(fallbackValue)],
paramCount: 1,
};
}
return {
whereClause: `${columnName}::text ILIKE $${paramIndex}`,
values: [`%${fallbackValue}%`],
@@ -1784,7 +1801,8 @@ export class TableManagementService {
tableName: string,
columnName: string,
value: any,
paramIndex: number
paramIndex: number,
operator: string = "contains"
): Promise<{
whereClause: string;
values: any[];
@@ -1794,7 +1812,14 @@ export class TableManagementService {
const codeTypeInfo = await this.getCodeTypeInfo(tableName, columnName);
if (!codeTypeInfo.isCodeType || !codeTypeInfo.codeCategory) {
// 코드 타입이 아니면 기본 검색
// 코드 타입이 아니면 operator에 따라 검색
if (operator === "equals") {
return {
whereClause: `${columnName}::text = $${paramIndex}`,
values: [String(value)],
paramCount: 1,
};
}
return {
whereClause: `${columnName}::text ILIKE $${paramIndex}`,
values: [`%${value}%`],
@@ -1802,6 +1827,15 @@ export class TableManagementService {
};
}
// select 필터(equals)인 경우 정확한 코드값 매칭만 수행
if (operator === "equals") {
return {
whereClause: `${columnName}::text = $${paramIndex}`,
values: [String(value)],
paramCount: 1,
};
}
if (typeof value === "string" && value.trim() !== "") {
// 코드값 또는 코드명으로 검색
return {