Merge conflict resolved: V2Media.tsx
원격 버전(7ec5a43) 채택 - 완전한 인라인 UI 코드 사용
This commit is contained in:
@@ -225,12 +225,12 @@ router.post("/:ruleId/preview", authenticateToken, async (req: AuthenticatedRequ
|
||||
router.post("/:ruleId/allocate", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { ruleId } = req.params;
|
||||
const { formData } = req.body; // 폼 데이터 (날짜 컬럼 기준 생성 시 사용)
|
||||
const { formData, userInputCode } = req.body; // 폼 데이터 + 사용자가 편집한 코드
|
||||
|
||||
logger.info("코드 할당 요청", { ruleId, companyCode, hasFormData: !!formData });
|
||||
logger.info("코드 할당 요청", { ruleId, companyCode, hasFormData: !!formData, userInputCode });
|
||||
|
||||
try {
|
||||
const allocatedCode = await numberingRuleService.allocateCode(ruleId, companyCode, formData);
|
||||
const allocatedCode = await numberingRuleService.allocateCode(ruleId, companyCode, formData, userInputCode);
|
||||
logger.info("코드 할당 성공", { ruleId, allocatedCode });
|
||||
return res.json({ success: true, data: { generatedCode: allocatedCode } });
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -886,8 +886,9 @@ class NumberingRuleService {
|
||||
.sort((a: any, b: any) => a.order - b.order)
|
||||
.map((part: any) => {
|
||||
if (part.generationMethod === "manual") {
|
||||
// 수동 입력 - 플레이스홀더 표시 (실제 값은 사용자가 입력)
|
||||
return part.manualConfig?.placeholder || "____";
|
||||
// 수동 입력 - 항상 ____ 마커 사용 (프론트엔드에서 편집 가능하게 처리)
|
||||
// placeholder 텍스트는 프론트엔드에서 별도로 표시
|
||||
return "____";
|
||||
}
|
||||
|
||||
const autoConfig = part.autoConfig || {};
|
||||
@@ -1014,11 +1015,13 @@ class NumberingRuleService {
|
||||
* @param ruleId 채번 규칙 ID
|
||||
* @param companyCode 회사 코드
|
||||
* @param formData 폼 데이터 (날짜 컬럼 기준 생성 시 사용)
|
||||
* @param userInputCode 사용자가 편집한 최종 코드 (수동 입력 부분 추출용)
|
||||
*/
|
||||
async allocateCode(
|
||||
ruleId: string,
|
||||
companyCode: string,
|
||||
formData?: Record<string, any>
|
||||
formData?: Record<string, any>,
|
||||
userInputCode?: string
|
||||
): Promise<string> {
|
||||
const pool = getPool();
|
||||
const client = await pool.connect();
|
||||
@@ -1029,11 +1032,77 @@ class NumberingRuleService {
|
||||
const rule = await this.getRuleById(ruleId, companyCode);
|
||||
if (!rule) throw new Error("규칙을 찾을 수 없습니다");
|
||||
|
||||
// 수동 입력 파트가 있고, 사용자가 입력한 코드가 있으면 수동 입력 부분 추출
|
||||
const manualParts = rule.parts.filter((p: any) => p.generationMethod === "manual");
|
||||
let extractedManualValues: string[] = [];
|
||||
|
||||
if (manualParts.length > 0 && userInputCode) {
|
||||
// 프리뷰 코드를 생성해서 ____ 위치 파악
|
||||
const previewParts = rule.parts
|
||||
.sort((a: any, b: any) => a.order - b.order)
|
||||
.map((part: any) => {
|
||||
if (part.generationMethod === "manual") {
|
||||
return "____";
|
||||
}
|
||||
const autoConfig = part.autoConfig || {};
|
||||
switch (part.partType) {
|
||||
case "sequence": {
|
||||
const length = autoConfig.sequenceLength || 3;
|
||||
return "X".repeat(length); // 순번 자리 표시
|
||||
}
|
||||
case "text":
|
||||
return autoConfig.textValue || "";
|
||||
case "date":
|
||||
return "DATEPART"; // 날짜 자리 표시
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
const separator = rule.separator || "";
|
||||
const previewTemplate = previewParts.join(separator);
|
||||
|
||||
// 사용자 입력 코드에서 수동 입력 부분 추출
|
||||
// 예: 템플릿 "R-____-XXX", 사용자입력 "R-MYVALUE-012" → "MYVALUE" 추출
|
||||
const templateParts = previewTemplate.split("____");
|
||||
if (templateParts.length > 1) {
|
||||
let remainingCode = userInputCode;
|
||||
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;
|
||||
if (manualEndIndex > 0) {
|
||||
extractedManualValues.push(remainingCode.slice(0, manualEndIndex));
|
||||
remainingCode = remainingCode.slice(manualEndIndex);
|
||||
}
|
||||
} else {
|
||||
extractedManualValues.push(remainingCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`수동 입력 값 추출: userInputCode=${userInputCode}, previewTemplate=${previewTemplate}, extractedManualValues=${JSON.stringify(extractedManualValues)}`);
|
||||
}
|
||||
|
||||
let manualPartIndex = 0;
|
||||
const parts = rule.parts
|
||||
.sort((a: any, b: any) => a.order - b.order)
|
||||
.map((part: any) => {
|
||||
if (part.generationMethod === "manual") {
|
||||
return part.manualConfig?.value || "";
|
||||
// 추출된 수동 입력 값 사용, 없으면 기본값 사용
|
||||
const manualValue = extractedManualValues[manualPartIndex] || part.manualConfig?.value || "";
|
||||
manualPartIndex++;
|
||||
return manualValue;
|
||||
}
|
||||
|
||||
const autoConfig = part.autoConfig || {};
|
||||
|
||||
@@ -1461,6 +1461,40 @@ export class TableManagementService {
|
||||
});
|
||||
}
|
||||
|
||||
// 🔧 파이프로 구분된 문자열 처리 (객체에서 추출한 actualValue도 처리)
|
||||
if (typeof actualValue === "string" && actualValue.includes("|")) {
|
||||
const columnInfo = await this.getColumnWebTypeInfo(
|
||||
tableName,
|
||||
columnName
|
||||
);
|
||||
|
||||
// 날짜 타입이면 날짜 범위로 처리
|
||||
if (
|
||||
columnInfo &&
|
||||
(columnInfo.webType === "date" || columnInfo.webType === "datetime")
|
||||
) {
|
||||
return this.buildDateRangeCondition(columnName, actualValue, paramIndex);
|
||||
}
|
||||
|
||||
// 그 외 타입이면 다중선택(IN 조건)으로 처리
|
||||
const multiValues = actualValue
|
||||
.split("|")
|
||||
.filter((v: string) => v.trim() !== "");
|
||||
if (multiValues.length > 0) {
|
||||
const placeholders = multiValues
|
||||
.map((_: string, idx: number) => `$${paramIndex + idx}`)
|
||||
.join(", ");
|
||||
logger.info(
|
||||
`🔍 다중선택 필터 적용 (객체에서 추출): ${columnName} IN (${multiValues.join(", ")})`
|
||||
);
|
||||
return {
|
||||
whereClause: `${columnName}::text IN (${placeholders})`,
|
||||
values: multiValues,
|
||||
paramCount: multiValues.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// "__ALL__" 값이거나 빈 값이면 필터 조건을 적용하지 않음
|
||||
if (
|
||||
actualValue === "__ALL__" ||
|
||||
@@ -3369,14 +3403,16 @@ export class TableManagementService {
|
||||
|
||||
if (options.search) {
|
||||
for (const [key, value] of Object.entries(options.search)) {
|
||||
// 검색값 추출 (객체 형태일 수 있음)
|
||||
// 검색값 및 operator 추출 (객체 형태일 수 있음)
|
||||
let searchValue = value;
|
||||
let operator = "contains"; // 기본값: 부분 일치
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"value" in value
|
||||
) {
|
||||
searchValue = value.value;
|
||||
operator = (value as any).operator || "contains";
|
||||
}
|
||||
|
||||
// 빈 값이면 스킵
|
||||
@@ -3428,15 +3464,49 @@ export class TableManagementService {
|
||||
// 기본 Entity 조인 컬럼인 경우: 조인된 테이블의 표시 컬럼에서 검색
|
||||
const aliasKey = `${joinConfig.referenceTable}:${joinConfig.sourceColumn}`;
|
||||
const alias = aliasMap.get(aliasKey);
|
||||
whereConditions.push(
|
||||
`${alias}.${joinConfig.displayColumn} ILIKE '%${safeValue}%'`
|
||||
);
|
||||
entitySearchColumns.push(
|
||||
`${key} (${joinConfig.referenceTable}.${joinConfig.displayColumn})`
|
||||
);
|
||||
logger.info(
|
||||
`🎯 Entity 조인 검색: ${key} → ${joinConfig.referenceTable}.${joinConfig.displayColumn} LIKE '%${safeValue}%' (별칭: ${alias})`
|
||||
);
|
||||
|
||||
// 🔧 파이프로 구분된 다중 선택값 처리
|
||||
if (safeValue.includes("|")) {
|
||||
const multiValues = safeValue
|
||||
.split("|")
|
||||
.filter((v: string) => v.trim() !== "");
|
||||
if (multiValues.length > 0) {
|
||||
const inClause = multiValues
|
||||
.map((v: string) => `'${v}'`)
|
||||
.join(", ");
|
||||
whereConditions.push(
|
||||
`${alias}.${joinConfig.displayColumn}::text IN (${inClause})`
|
||||
);
|
||||
entitySearchColumns.push(
|
||||
`${key} (${joinConfig.referenceTable}.${joinConfig.displayColumn})`
|
||||
);
|
||||
logger.info(
|
||||
`🎯 Entity 조인 다중선택 검색: ${key} → ${joinConfig.referenceTable}.${joinConfig.displayColumn} IN (${multiValues.join(", ")}) (별칭: ${alias})`
|
||||
);
|
||||
}
|
||||
} else if (operator === "equals") {
|
||||
// 🔧 equals 연산자: 정확히 일치
|
||||
whereConditions.push(
|
||||
`${alias}.${joinConfig.displayColumn}::text = '${safeValue}'`
|
||||
);
|
||||
entitySearchColumns.push(
|
||||
`${key} (${joinConfig.referenceTable}.${joinConfig.displayColumn})`
|
||||
);
|
||||
logger.info(
|
||||
`🎯 Entity 조인 정확히 일치 검색: ${key} → ${joinConfig.referenceTable}.${joinConfig.displayColumn} = '${safeValue}' (별칭: ${alias})`
|
||||
);
|
||||
} else {
|
||||
// 기본: 부분 일치 (ILIKE)
|
||||
whereConditions.push(
|
||||
`${alias}.${joinConfig.displayColumn} ILIKE '%${safeValue}%'`
|
||||
);
|
||||
entitySearchColumns.push(
|
||||
`${key} (${joinConfig.referenceTable}.${joinConfig.displayColumn})`
|
||||
);
|
||||
logger.info(
|
||||
`🎯 Entity 조인 검색: ${key} → ${joinConfig.referenceTable}.${joinConfig.displayColumn} LIKE '%${safeValue}%' (별칭: ${alias})`
|
||||
);
|
||||
}
|
||||
} else if (key === "writer_dept_code") {
|
||||
// writer_dept_code: user_info.dept_code에서 검색
|
||||
const userAliasKey = Array.from(aliasMap.keys()).find((k) =>
|
||||
@@ -3473,10 +3543,33 @@ export class TableManagementService {
|
||||
}
|
||||
} else {
|
||||
// 일반 컬럼인 경우: 메인 테이블에서 검색
|
||||
whereConditions.push(`main.${key} ILIKE '%${safeValue}%'`);
|
||||
logger.info(
|
||||
`🔍 일반 컬럼 검색: ${key} → main.${key} LIKE '%${safeValue}%'`
|
||||
);
|
||||
// 🔧 파이프로 구분된 다중 선택값 처리
|
||||
if (safeValue.includes("|")) {
|
||||
const multiValues = safeValue
|
||||
.split("|")
|
||||
.filter((v: string) => v.trim() !== "");
|
||||
if (multiValues.length > 0) {
|
||||
const inClause = multiValues
|
||||
.map((v: string) => `'${v}'`)
|
||||
.join(", ");
|
||||
whereConditions.push(`main.${key}::text IN (${inClause})`);
|
||||
logger.info(
|
||||
`🔍 다중선택 컬럼 검색: ${key} → main.${key} IN (${multiValues.join(", ")})`
|
||||
);
|
||||
}
|
||||
} else if (operator === "equals") {
|
||||
// 🔧 equals 연산자: 정확히 일치
|
||||
whereConditions.push(`main.${key}::text = '${safeValue}'`);
|
||||
logger.info(
|
||||
`🔍 정확히 일치 검색: ${key} → main.${key} = '${safeValue}'`
|
||||
);
|
||||
} else {
|
||||
// 기본: 부분 일치 (ILIKE)
|
||||
whereConditions.push(`main.${key} ILIKE '%${safeValue}%'`);
|
||||
logger.info(
|
||||
`🔍 일반 컬럼 검색: ${key} → main.${key} LIKE '%${safeValue}%'`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user