전체 카테고리 키 목록 조회 API 및 관련 기능 추가
- 카테고리 트리 컨트롤러에 전체 카테고리 키 목록 조회 라우트 추가: GET /api/category-tree/test/all-category-keys - 카테고리 트리 서비스에 전체 카테고리 키 목록 조회 메서드 구현: 모든 테이블과 컬럼 조합을 반환 - 채번규칙 컨트롤러에서 폼 데이터 처리 기능 추가: 코드 미리보기 시 카테고리 기반 폼 데이터 사용 - 관련 API 클라이언트 및 타입 정의 업데이트: 카테고리 키 조회 및 채번규칙 API에 대한 요청 처리 개선 이로 인해 카테고리 관리 및 채번규칙 테스트의 효율성이 향상되었습니다.
This commit is contained in:
@@ -16,6 +16,31 @@ interface AuthenticatedRequest extends Request {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 카테고리 키 목록 조회 (모든 테이블.컬럼 조합)
|
||||
* GET /api/category-tree/test/all-category-keys
|
||||
* 주의: 이 라우트는 /test/:tableName/:columnName 보다 먼저 정의되어야 함
|
||||
*/
|
||||
router.get("/test/all-category-keys", async (req: AuthenticatedRequest, res: Response) => {
|
||||
try {
|
||||
const companyCode = req.user?.companyCode || "*";
|
||||
|
||||
const keys = await categoryTreeService.getAllCategoryKeys(companyCode);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: keys,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const err = error as Error;
|
||||
logger.error("전체 카테고리 키 목록 조회 API 오류", { error: err.message });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 카테고리 트리 조회
|
||||
* GET /api/category-tree/test/:tableName/:columnName
|
||||
|
||||
@@ -202,9 +202,10 @@ router.delete("/:ruleId", authenticateToken, async (req: AuthenticatedRequest, r
|
||||
router.post("/:ruleId/preview", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { ruleId } = req.params;
|
||||
const { formData } = req.body; // 폼 데이터 (카테고리 기반 채번 시 사용)
|
||||
|
||||
try {
|
||||
const previewCode = await numberingRuleService.previewCode(ruleId, companyCode);
|
||||
const previewCode = await numberingRuleService.previewCode(ruleId, companyCode, formData);
|
||||
return res.json({ success: true, data: { generatedCode: previewCode } });
|
||||
} catch (error: any) {
|
||||
logger.error("코드 미리보기 실패", { error: error.message });
|
||||
@@ -321,4 +322,90 @@ router.post("/test/save", authenticateToken, async (req: AuthenticatedRequest, r
|
||||
}
|
||||
});
|
||||
|
||||
// [테스트] 테스트 테이블에서 채번규칙 삭제
|
||||
router.delete("/test/:ruleId", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { ruleId } = req.params;
|
||||
|
||||
try {
|
||||
await numberingRuleService.deleteRuleFromTest(ruleId, companyCode);
|
||||
return res.json({ success: true });
|
||||
} catch (error: any) {
|
||||
logger.error("테스트 테이블에서 채번규칙 삭제 실패", {
|
||||
error: error.message,
|
||||
companyCode,
|
||||
ruleId,
|
||||
});
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// [테스트] 카테고리 조건 포함 채번규칙 조회
|
||||
router.get("/test/by-column-with-category", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { tableName, columnName, categoryColumn, categoryValueId } = req.query;
|
||||
|
||||
try {
|
||||
if (!tableName || typeof tableName !== "string") {
|
||||
return res.status(400).json({ success: false, error: "tableName is required" });
|
||||
}
|
||||
if (!columnName || typeof columnName !== "string") {
|
||||
return res.status(400).json({ success: false, error: "columnName is required" });
|
||||
}
|
||||
|
||||
const rule = await numberingRuleService.getNumberingRuleByColumnWithCategory(
|
||||
companyCode,
|
||||
tableName,
|
||||
columnName,
|
||||
categoryColumn as string | undefined,
|
||||
categoryValueId ? Number(categoryValueId) : undefined
|
||||
);
|
||||
|
||||
if (!rule) {
|
||||
return res.status(404).json({ success: false, error: "규칙을 찾을 수 없습니다" });
|
||||
}
|
||||
|
||||
return res.json({ success: true, data: rule });
|
||||
} catch (error: any) {
|
||||
logger.error("카테고리 조건 포함 채번규칙 조회 실패", {
|
||||
error: error.message,
|
||||
companyCode,
|
||||
tableName,
|
||||
columnName,
|
||||
});
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// [테스트] 테이블.컬럼별 모든 채번규칙 조회 (카테고리 조건별)
|
||||
router.get("/test/rules-by-table-column", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { tableName, columnName } = req.query;
|
||||
|
||||
try {
|
||||
if (!tableName || typeof tableName !== "string") {
|
||||
return res.status(400).json({ success: false, error: "tableName is required" });
|
||||
}
|
||||
if (!columnName || typeof columnName !== "string") {
|
||||
return res.status(400).json({ success: false, error: "columnName is required" });
|
||||
}
|
||||
|
||||
const rules = await numberingRuleService.getRulesByTableColumn(
|
||||
companyCode,
|
||||
tableName,
|
||||
columnName
|
||||
);
|
||||
|
||||
return res.json({ success: true, data: rules });
|
||||
} catch (error: any) {
|
||||
logger.error("테이블.컬럼별 채번규칙 목록 조회 실패", {
|
||||
error: error.message,
|
||||
companyCode,
|
||||
tableName,
|
||||
columnName,
|
||||
});
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -507,6 +507,39 @@ class CategoryTreeService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 카테고리 키 목록 조회 (모든 테이블.컬럼 조합)
|
||||
* category_values_test 테이블에서 고유한 table_name, column_name 조합을 조회
|
||||
* 라벨 정보도 함께 반환
|
||||
*/
|
||||
async getAllCategoryKeys(companyCode: string): Promise<{ tableName: string; columnName: string; tableLabel: string; columnLabel: string }[]> {
|
||||
logger.info("getAllCategoryKeys 호출", { companyCode });
|
||||
const pool = getPool();
|
||||
|
||||
try {
|
||||
const query = `
|
||||
SELECT DISTINCT
|
||||
cv.table_name AS "tableName",
|
||||
cv.column_name AS "columnName",
|
||||
COALESCE(tl.table_label, cv.table_name) AS "tableLabel",
|
||||
COALESCE(cl.column_label, cv.column_name) AS "columnLabel"
|
||||
FROM category_values_test cv
|
||||
LEFT JOIN table_labels tl ON tl.table_name = cv.table_name
|
||||
LEFT JOIN column_labels cl ON cl.table_name = cv.table_name AND cl.column_name = cv.column_name
|
||||
WHERE cv.company_code = $1 OR cv.company_code = '*'
|
||||
ORDER BY cv.table_name, cv.column_name
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [companyCode]);
|
||||
logger.info("전체 카테고리 키 목록 조회 완료", { count: result.rows.length });
|
||||
return result.rows;
|
||||
} catch (error: unknown) {
|
||||
const err = error as Error;
|
||||
logger.error("전체 카테고리 키 목록 조회 실패", { error: err.message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const categoryTreeService = new CategoryTreeService();
|
||||
|
||||
@@ -29,6 +29,10 @@ interface NumberingRuleConfig {
|
||||
companyCode?: string;
|
||||
menuObjid?: number;
|
||||
scopeType?: string;
|
||||
// 카테고리 조건
|
||||
categoryColumn?: string;
|
||||
categoryValueId?: number;
|
||||
categoryValueLabel?: string; // 조회 시 조인해서 가져옴
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
createdBy?: string;
|
||||
@@ -882,8 +886,15 @@ class NumberingRuleService {
|
||||
|
||||
/**
|
||||
* 코드 미리보기 (순번 증가 없음)
|
||||
* @param ruleId 채번 규칙 ID
|
||||
* @param companyCode 회사 코드
|
||||
* @param formData 폼 데이터 (카테고리 기반 채번 시 사용)
|
||||
*/
|
||||
async previewCode(ruleId: string, companyCode: string): Promise<string> {
|
||||
async previewCode(
|
||||
ruleId: string,
|
||||
companyCode: string,
|
||||
formData?: Record<string, any>
|
||||
): Promise<string> {
|
||||
const rule = await this.getRuleById(ruleId, companyCode);
|
||||
if (!rule) throw new Error("규칙을 찾을 수 없습니다");
|
||||
|
||||
@@ -891,7 +902,8 @@ class NumberingRuleService {
|
||||
.sort((a: any, b: any) => a.order - b.order)
|
||||
.map((part: any) => {
|
||||
if (part.generationMethod === "manual") {
|
||||
return part.manualConfig?.value || "";
|
||||
// 수동 입력 - 플레이스홀더 표시 (실제 값은 사용자가 입력)
|
||||
return part.manualConfig?.placeholder || "____";
|
||||
}
|
||||
|
||||
const autoConfig = part.autoConfig || {};
|
||||
@@ -913,10 +925,23 @@ class NumberingRuleService {
|
||||
|
||||
case "date": {
|
||||
// 날짜 (다양한 날짜 형식)
|
||||
return this.formatDate(
|
||||
new Date(),
|
||||
autoConfig.dateFormat || "YYYYMMDD"
|
||||
);
|
||||
const dateFormat = autoConfig.dateFormat || "YYYYMMDD";
|
||||
|
||||
// 컬럼 기준 생성인 경우 폼 데이터에서 날짜 추출
|
||||
if (autoConfig.useColumnValue && autoConfig.sourceColumnName && formData) {
|
||||
const columnValue = formData[autoConfig.sourceColumnName];
|
||||
if (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);
|
||||
}
|
||||
|
||||
case "text": {
|
||||
@@ -924,6 +949,71 @@ class NumberingRuleService {
|
||||
return autoConfig.textValue || "TEXT";
|
||||
}
|
||||
|
||||
case "category": {
|
||||
// 카테고리 기반 코드 생성
|
||||
const categoryKey = autoConfig.categoryKey; // 예: "item_info.material"
|
||||
const categoryMappings = autoConfig.categoryMappings || [];
|
||||
|
||||
if (!categoryKey || !formData) {
|
||||
logger.warn("카테고리 키 또는 폼 데이터 없음", { categoryKey, hasFormData: !!formData });
|
||||
return "";
|
||||
}
|
||||
|
||||
// categoryKey에서 컬럼명 추출 (예: "item_info.material" -> "material")
|
||||
const columnName = categoryKey.includes(".")
|
||||
? categoryKey.split(".")[1]
|
||||
: categoryKey;
|
||||
|
||||
// 폼 데이터에서 해당 컬럼의 값 가져오기
|
||||
const selectedValue = formData[columnName];
|
||||
|
||||
logger.info("카테고리 파트 처리", {
|
||||
categoryKey,
|
||||
columnName,
|
||||
selectedValue,
|
||||
formDataKeys: Object.keys(formData),
|
||||
mappingsCount: categoryMappings.length
|
||||
});
|
||||
|
||||
if (!selectedValue) {
|
||||
logger.warn("카테고리 값이 선택되지 않음", { columnName, formDataKeys: Object.keys(formData) });
|
||||
return "";
|
||||
}
|
||||
|
||||
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
|
||||
// selectedValue는 valueCode일 수 있음 (UnifiedSelect에서 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;
|
||||
}
|
||||
);
|
||||
|
||||
if (mapping) {
|
||||
logger.info("카테고리 매핑 적용", {
|
||||
selectedValue,
|
||||
format: mapping.format,
|
||||
categoryValueLabel: mapping.categoryValueLabel
|
||||
});
|
||||
return mapping.format || "";
|
||||
}
|
||||
|
||||
logger.warn("카테고리 매핑을 찾을 수 없음", {
|
||||
selectedValue,
|
||||
availableMappings: categoryMappings.map((m: any) => ({
|
||||
id: m.categoryValueId,
|
||||
label: m.categoryValueLabel
|
||||
}))
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
default:
|
||||
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
||||
return "";
|
||||
@@ -931,7 +1021,7 @@ class NumberingRuleService {
|
||||
});
|
||||
|
||||
const previewCode = parts.join(rule.separator || "");
|
||||
logger.info("코드 미리보기 생성", { ruleId, previewCode, companyCode });
|
||||
logger.info("코드 미리보기 생성", { ruleId, previewCode, companyCode, hasFormData: !!formData });
|
||||
return previewCode;
|
||||
}
|
||||
|
||||
@@ -1119,22 +1209,27 @@ class NumberingRuleService {
|
||||
const pool = getPool();
|
||||
const query = `
|
||||
SELECT
|
||||
rule_id AS "ruleId",
|
||||
rule_name AS "ruleName",
|
||||
description,
|
||||
separator,
|
||||
reset_period AS "resetPeriod",
|
||||
current_sequence AS "currentSequence",
|
||||
table_name AS "tableName",
|
||||
column_name AS "columnName",
|
||||
company_code AS "companyCode",
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
created_by AS "createdBy"
|
||||
FROM numbering_rules_test
|
||||
WHERE company_code = $1
|
||||
AND table_name = $2
|
||||
AND column_name = $3
|
||||
r.rule_id AS "ruleId",
|
||||
r.rule_name AS "ruleName",
|
||||
r.description,
|
||||
r.separator,
|
||||
r.reset_period AS "resetPeriod",
|
||||
r.current_sequence AS "currentSequence",
|
||||
r.table_name AS "tableName",
|
||||
r.column_name AS "columnName",
|
||||
r.company_code AS "companyCode",
|
||||
r.category_column AS "categoryColumn",
|
||||
r.category_value_id AS "categoryValueId",
|
||||
cv.value_label AS "categoryValueLabel",
|
||||
r.created_at AS "createdAt",
|
||||
r.updated_at AS "updatedAt",
|
||||
r.created_by AS "createdBy"
|
||||
FROM numbering_rules_test r
|
||||
LEFT JOIN category_values_test cv ON r.category_value_id = cv.value_id
|
||||
WHERE r.company_code = $1
|
||||
AND r.table_name = $2
|
||||
AND r.column_name = $3
|
||||
AND r.category_value_id IS NULL
|
||||
LIMIT 1
|
||||
`;
|
||||
const params = [companyCode, tableName, columnName];
|
||||
@@ -1225,8 +1320,10 @@ class NumberingRuleService {
|
||||
reset_period = $4,
|
||||
table_name = $5,
|
||||
column_name = $6,
|
||||
category_column = $7,
|
||||
category_value_id = $8,
|
||||
updated_at = NOW()
|
||||
WHERE rule_id = $7 AND company_code = $8
|
||||
WHERE rule_id = $9 AND company_code = $10
|
||||
`;
|
||||
await client.query(updateQuery, [
|
||||
config.ruleName,
|
||||
@@ -1235,6 +1332,8 @@ class NumberingRuleService {
|
||||
config.resetPeriod || "none",
|
||||
config.tableName || "",
|
||||
config.columnName || "",
|
||||
config.categoryColumn || null,
|
||||
config.categoryValueId || null,
|
||||
config.ruleId,
|
||||
companyCode,
|
||||
]);
|
||||
@@ -1250,8 +1349,9 @@ class NumberingRuleService {
|
||||
INSERT INTO numbering_rules_test (
|
||||
rule_id, rule_name, description, separator, reset_period,
|
||||
current_sequence, table_name, column_name, company_code,
|
||||
category_column, category_value_id,
|
||||
created_at, updated_at, created_by
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW(), $10)
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW(), NOW(), $12)
|
||||
`;
|
||||
await client.query(insertQuery, [
|
||||
config.ruleId,
|
||||
@@ -1263,6 +1363,8 @@ class NumberingRuleService {
|
||||
config.tableName || "",
|
||||
config.columnName || "",
|
||||
companyCode,
|
||||
config.categoryColumn || null,
|
||||
config.categoryValueId || null,
|
||||
createdBy,
|
||||
]);
|
||||
}
|
||||
@@ -1309,6 +1411,266 @@ class NumberingRuleService {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [테스트] 테스트 테이블에서 채번규칙 삭제
|
||||
* numbering_rules_test 테이블 사용
|
||||
*/
|
||||
async deleteRuleFromTest(ruleId: string, companyCode: string): Promise<void> {
|
||||
const pool = getPool();
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
|
||||
logger.info("테스트 테이블에서 채번 규칙 삭제 시작", { ruleId, companyCode });
|
||||
|
||||
// 파트 먼저 삭제
|
||||
await client.query(
|
||||
"DELETE FROM numbering_rule_parts_test WHERE rule_id = $1 AND company_code = $2",
|
||||
[ruleId, companyCode]
|
||||
);
|
||||
|
||||
// 규칙 삭제
|
||||
const result = await client.query(
|
||||
"DELETE FROM numbering_rules_test WHERE rule_id = $1 AND company_code = $2",
|
||||
[ruleId, companyCode]
|
||||
);
|
||||
|
||||
await client.query("COMMIT");
|
||||
|
||||
logger.info("테스트 테이블에서 채번 규칙 삭제 완료", {
|
||||
ruleId,
|
||||
companyCode,
|
||||
deletedCount: result.rowCount,
|
||||
});
|
||||
} catch (error: any) {
|
||||
await client.query("ROLLBACK");
|
||||
logger.error("테스트 테이블에서 채번 규칙 삭제 실패", {
|
||||
error: error.message,
|
||||
ruleId,
|
||||
companyCode,
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [테스트] 카테고리 값에 따라 적절한 채번규칙 조회
|
||||
* 1. 해당 카테고리 값에 매칭되는 규칙 찾기
|
||||
* 2. 없으면 기본 규칙(category_value_id가 NULL인) 찾기
|
||||
*/
|
||||
async getNumberingRuleByColumnWithCategory(
|
||||
companyCode: string,
|
||||
tableName: string,
|
||||
columnName: string,
|
||||
categoryColumn?: string,
|
||||
categoryValueId?: number
|
||||
): Promise<NumberingRuleConfig | null> {
|
||||
try {
|
||||
logger.info("카테고리 조건 포함 채번 규칙 조회 시작", {
|
||||
companyCode,
|
||||
tableName,
|
||||
columnName,
|
||||
categoryColumn,
|
||||
categoryValueId,
|
||||
});
|
||||
|
||||
const pool = getPool();
|
||||
|
||||
// 1. 카테고리 값에 매칭되는 규칙 찾기
|
||||
if (categoryColumn && categoryValueId) {
|
||||
const categoryQuery = `
|
||||
SELECT
|
||||
r.rule_id AS "ruleId",
|
||||
r.rule_name AS "ruleName",
|
||||
r.description,
|
||||
r.separator,
|
||||
r.reset_period AS "resetPeriod",
|
||||
r.current_sequence AS "currentSequence",
|
||||
r.table_name AS "tableName",
|
||||
r.column_name AS "columnName",
|
||||
r.company_code AS "companyCode",
|
||||
r.category_column AS "categoryColumn",
|
||||
r.category_value_id AS "categoryValueId",
|
||||
cv.value_label AS "categoryValueLabel",
|
||||
r.created_at AS "createdAt",
|
||||
r.updated_at AS "updatedAt",
|
||||
r.created_by AS "createdBy"
|
||||
FROM numbering_rules_test r
|
||||
LEFT JOIN category_values_test cv ON r.category_value_id = cv.value_id
|
||||
WHERE r.company_code = $1
|
||||
AND r.table_name = $2
|
||||
AND r.column_name = $3
|
||||
AND r.category_column = $4
|
||||
AND r.category_value_id = $5
|
||||
LIMIT 1
|
||||
`;
|
||||
const categoryResult = await pool.query(categoryQuery, [
|
||||
companyCode,
|
||||
tableName,
|
||||
columnName,
|
||||
categoryColumn,
|
||||
categoryValueId,
|
||||
]);
|
||||
|
||||
if (categoryResult.rows.length > 0) {
|
||||
const rule = categoryResult.rows[0];
|
||||
// 파트 정보 조회
|
||||
const partsQuery = `
|
||||
SELECT
|
||||
id,
|
||||
part_order AS "order",
|
||||
part_type AS "partType",
|
||||
generation_method AS "generationMethod",
|
||||
auto_config AS "autoConfig",
|
||||
manual_config AS "manualConfig"
|
||||
FROM numbering_rule_parts_test
|
||||
WHERE rule_id = $1 AND company_code = $2
|
||||
ORDER BY part_order
|
||||
`;
|
||||
const partsResult = await pool.query(partsQuery, [rule.ruleId, companyCode]);
|
||||
rule.parts = partsResult.rows;
|
||||
|
||||
logger.info("카테고리 조건 매칭 채번 규칙 찾음", {
|
||||
ruleId: rule.ruleId,
|
||||
categoryValueLabel: rule.categoryValueLabel,
|
||||
});
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 기본 규칙 찾기 (category_value_id가 NULL인)
|
||||
const defaultQuery = `
|
||||
SELECT
|
||||
r.rule_id AS "ruleId",
|
||||
r.rule_name AS "ruleName",
|
||||
r.description,
|
||||
r.separator,
|
||||
r.reset_period AS "resetPeriod",
|
||||
r.current_sequence AS "currentSequence",
|
||||
r.table_name AS "tableName",
|
||||
r.column_name AS "columnName",
|
||||
r.company_code AS "companyCode",
|
||||
r.category_column AS "categoryColumn",
|
||||
r.category_value_id AS "categoryValueId",
|
||||
r.created_at AS "createdAt",
|
||||
r.updated_at AS "updatedAt",
|
||||
r.created_by AS "createdBy"
|
||||
FROM numbering_rules_test r
|
||||
WHERE r.company_code = $1
|
||||
AND r.table_name = $2
|
||||
AND r.column_name = $3
|
||||
AND r.category_value_id IS NULL
|
||||
LIMIT 1
|
||||
`;
|
||||
const defaultResult = await pool.query(defaultQuery, [companyCode, tableName, columnName]);
|
||||
|
||||
if (defaultResult.rows.length > 0) {
|
||||
const rule = defaultResult.rows[0];
|
||||
// 파트 정보 조회
|
||||
const partsQuery = `
|
||||
SELECT
|
||||
id,
|
||||
part_order AS "order",
|
||||
part_type AS "partType",
|
||||
generation_method AS "generationMethod",
|
||||
auto_config AS "autoConfig",
|
||||
manual_config AS "manualConfig"
|
||||
FROM numbering_rule_parts_test
|
||||
WHERE rule_id = $1 AND company_code = $2
|
||||
ORDER BY part_order
|
||||
`;
|
||||
const partsResult = await pool.query(partsQuery, [rule.ruleId, companyCode]);
|
||||
rule.parts = partsResult.rows;
|
||||
|
||||
logger.info("기본 채번 규칙 찾음 (카테고리 조건 없음)", {
|
||||
ruleId: rule.ruleId,
|
||||
});
|
||||
return rule;
|
||||
}
|
||||
|
||||
logger.info("채번 규칙을 찾을 수 없음", {
|
||||
companyCode,
|
||||
tableName,
|
||||
columnName,
|
||||
categoryColumn,
|
||||
categoryValueId,
|
||||
});
|
||||
return null;
|
||||
} catch (error: any) {
|
||||
logger.error("카테고리 조건 포함 채번 규칙 조회 실패", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [테스트] 특정 테이블.컬럼의 모든 채번 규칙 조회 (카테고리 조건별)
|
||||
*/
|
||||
async getRulesByTableColumn(
|
||||
companyCode: string,
|
||||
tableName: string,
|
||||
columnName: string
|
||||
): Promise<NumberingRuleConfig[]> {
|
||||
try {
|
||||
const pool = getPool();
|
||||
const query = `
|
||||
SELECT
|
||||
r.rule_id AS "ruleId",
|
||||
r.rule_name AS "ruleName",
|
||||
r.description,
|
||||
r.separator,
|
||||
r.reset_period AS "resetPeriod",
|
||||
r.current_sequence AS "currentSequence",
|
||||
r.table_name AS "tableName",
|
||||
r.column_name AS "columnName",
|
||||
r.company_code AS "companyCode",
|
||||
r.category_column AS "categoryColumn",
|
||||
r.category_value_id AS "categoryValueId",
|
||||
cv.value_label AS "categoryValueLabel",
|
||||
r.created_at AS "createdAt",
|
||||
r.updated_at AS "updatedAt",
|
||||
r.created_by AS "createdBy"
|
||||
FROM numbering_rules_test r
|
||||
LEFT JOIN category_values_test cv ON r.category_value_id = cv.value_id
|
||||
WHERE r.company_code = $1
|
||||
AND r.table_name = $2
|
||||
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]);
|
||||
|
||||
// 각 규칙의 파트 정보 조회
|
||||
for (const rule of result.rows) {
|
||||
const partsQuery = `
|
||||
SELECT
|
||||
id,
|
||||
part_order AS "order",
|
||||
part_type AS "partType",
|
||||
generation_method AS "generationMethod",
|
||||
auto_config AS "autoConfig",
|
||||
manual_config AS "manualConfig"
|
||||
FROM numbering_rule_parts_test
|
||||
WHERE rule_id = $1 AND company_code = $2
|
||||
ORDER BY part_order
|
||||
`;
|
||||
const partsResult = await pool.query(partsQuery, [rule.ruleId, companyCode]);
|
||||
rule.parts = partsResult.rows;
|
||||
}
|
||||
|
||||
return result.rows;
|
||||
} catch (error: any) {
|
||||
logger.error("테이블.컬럼별 채번 규칙 목록 조회 실패", {
|
||||
error: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const numberingRuleService = new NumberingRuleService();
|
||||
|
||||
@@ -207,48 +207,27 @@ class TableCategoryValueService {
|
||||
is_active AS "isActive",
|
||||
is_default AS "isDefault",
|
||||
company_code AS "companyCode",
|
||||
menu_objid AS "menuObjid",
|
||||
NULL::numeric AS "menuObjid",
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
created_by AS "createdBy",
|
||||
updated_by AS "updatedBy"
|
||||
FROM table_column_category_values
|
||||
FROM category_values_test
|
||||
WHERE table_name = $1
|
||||
AND column_name = $2
|
||||
`;
|
||||
|
||||
// category_values_test 테이블 사용 (menu_objid 없음)
|
||||
if (companyCode === "*") {
|
||||
// 최고 관리자: menuObjid가 있으면 해당 메뉴(및 형제 메뉴)의 값만 조회
|
||||
if (menuObjid && siblingObjids.length > 0) {
|
||||
query = baseSelect + ` AND menu_objid = ANY($3::numeric[])`;
|
||||
params = [tableName, columnName, siblingObjids];
|
||||
logger.info("최고 관리자 메뉴 스코프 카테고리 값 조회", { menuObjid, siblingObjids });
|
||||
} else if (menuObjid) {
|
||||
query = baseSelect + ` AND menu_objid = $3`;
|
||||
params = [tableName, columnName, menuObjid];
|
||||
logger.info("최고 관리자 단일 메뉴 카테고리 값 조회", { menuObjid });
|
||||
} else {
|
||||
// menuObjid 없으면 모든 값 조회 (중복 가능)
|
||||
query = baseSelect;
|
||||
params = [tableName, columnName];
|
||||
logger.info("최고 관리자 전체 카테고리 값 조회 (menuObjid 없음)");
|
||||
}
|
||||
// 최고 관리자: 모든 값 조회
|
||||
query = baseSelect;
|
||||
params = [tableName, columnName];
|
||||
logger.info("최고 관리자 전체 카테고리 값 조회 (category_values_test)");
|
||||
} else {
|
||||
// 일반 회사: 자신의 회사 + menuObjid로 필터링
|
||||
if (menuObjid && siblingObjids.length > 0) {
|
||||
query = baseSelect + ` AND company_code = $3 AND menu_objid = ANY($4::numeric[])`;
|
||||
params = [tableName, columnName, companyCode, siblingObjids];
|
||||
logger.info("회사별 메뉴 스코프 카테고리 값 조회", { companyCode, menuObjid, siblingObjids });
|
||||
} else if (menuObjid) {
|
||||
query = baseSelect + ` AND company_code = $3 AND menu_objid = $4`;
|
||||
params = [tableName, columnName, companyCode, menuObjid];
|
||||
logger.info("회사별 단일 메뉴 카테고리 값 조회", { companyCode, menuObjid });
|
||||
} else {
|
||||
// menuObjid 없으면 회사 전체 조회 (중복 가능하지만 회사별로 제한)
|
||||
query = baseSelect + ` AND company_code = $3`;
|
||||
params = [tableName, columnName, companyCode];
|
||||
logger.info("회사별 카테고리 값 조회 (menuObjid 없음)", { companyCode });
|
||||
}
|
||||
// 일반 회사: 자신의 회사 또는 공통(*) 카테고리 조회
|
||||
query = baseSelect + ` AND (company_code = $3 OR company_code = '*')`;
|
||||
params = [tableName, columnName, companyCode];
|
||||
logger.info("회사별 카테고리 값 조회 (category_values_test)", { companyCode });
|
||||
}
|
||||
|
||||
if (!includeInactive) {
|
||||
|
||||
Reference in New Issue
Block a user