시퀀스 관리 메뉴 + 테이블 타입관리 코멘트/검증 + 설계 문서
Build and Push Images / build-and-push (push) Has been cancelled
Build and Push Images / build-and-push (push) Has been cancelled
- 시스템 관리 > 시퀀스 관리 신규 메뉴 + 페이지(채번 룰 빌더) - ensureSequenceMngMenu 부팅 시드 - 테이블 타입관리 → 채번 룰 드롭다운 + 의존성 자동 검증 - 표시명/코멘트 UX 개선 - 설계 문서 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -539,6 +539,14 @@ async function initializeServices() {
|
||||
logger.error(`❌ wace_plm 공통코드 시드 실패:`, error);
|
||||
}
|
||||
|
||||
// 시퀀스 관리 메뉴(시스템 관리 > 시퀀스 관리) 등록 — menu_info 멱등 시드
|
||||
try {
|
||||
const { ensureSequenceMngMenu } = await import("./services/sequenceMngMenuMigration");
|
||||
await ensureSequenceMngMenu();
|
||||
} catch (error) {
|
||||
logger.error(`❌ 시퀀스 관리 메뉴 시드 실패:`, error);
|
||||
}
|
||||
|
||||
// 고객 CS 관리 테이블 점검 (customer_cs_mng + 공통코드 카테고리)
|
||||
try {
|
||||
const { ensureCustomerCsTables } = await import("./services/customerCsTableMigration");
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 시퀀스 관리 메뉴(시스템 관리 > 시퀀스 관리) idempotent 시드.
|
||||
*
|
||||
* - 부팅 시 1회 실행.
|
||||
* - menu_url='/admin/systemMng/sequenceMng' 이 이미 있으면 skip(정규화만).
|
||||
* - 부모(시스템 관리) 찾기 — 다음 순서로 fallback:
|
||||
* (1) menu_name_kor = '시스템 관리' / '시스템관리'
|
||||
* (2) menu_url LIKE '/admin/systemMng/%' 자식들의 가장 빈도 높은 parent_obj_id
|
||||
* (3) menu_url LIKE '/admin/system%' 자식들의 가장 빈도 높은 parent_obj_id
|
||||
* (4) 같은 회사 코드의 메뉴 중 menu_name_kor LIKE '%시스템%' (느슨한 매칭)
|
||||
* - 부모를 찾지 못하면 등록을 건너뛰고 진단 로그 남김(운영 환경에서 수동 등록 가능).
|
||||
*/
|
||||
import { getPool } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
const TARGET_URL = "/admin/systemMng/sequenceMng";
|
||||
const MENU_NAME_KOR = "시퀀스 관리";
|
||||
const MENU_NAME_ENG = "Sequence Management";
|
||||
|
||||
async function findSystemMngParentObjId(pool: ReturnType<typeof getPool>): Promise<number | null> {
|
||||
// (1) 이름이 정확히 '시스템 관리' or '시스템관리'
|
||||
const byName = await pool.query<{ objid: number }>(
|
||||
`SELECT objid FROM menu_info
|
||||
WHERE COALESCE(status,'active') = 'active'
|
||||
AND (menu_name_kor = '시스템 관리' OR menu_name_kor = '시스템관리')
|
||||
ORDER BY objid ASC
|
||||
LIMIT 1`,
|
||||
);
|
||||
if (byName.rowCount && byName.rowCount > 0) {
|
||||
return Number(byName.rows[0].objid);
|
||||
}
|
||||
|
||||
// (2) /admin/systemMng/* 자식들의 부모 빈도 1위
|
||||
const byChildren1 = await pool.query<{ parent_obj_id: number; cnt: string }>(
|
||||
`SELECT parent_obj_id, COUNT(*)::text AS cnt
|
||||
FROM menu_info
|
||||
WHERE menu_url LIKE '/admin/systemMng/%'
|
||||
AND parent_obj_id IS NOT NULL
|
||||
GROUP BY parent_obj_id
|
||||
ORDER BY cnt DESC NULLS LAST
|
||||
LIMIT 1`,
|
||||
);
|
||||
if (byChildren1.rowCount && byChildren1.rowCount > 0) {
|
||||
return Number(byChildren1.rows[0].parent_obj_id);
|
||||
}
|
||||
|
||||
// (3) /admin/system* 자식들 (대소문자/언더스코어 변형 대비)
|
||||
const byChildren2 = await pool.query<{ parent_obj_id: number; cnt: string }>(
|
||||
`SELECT parent_obj_id, COUNT(*)::text AS cnt
|
||||
FROM menu_info
|
||||
WHERE (menu_url ILIKE '/admin/system%' OR menu_url ILIKE '/admin/sys%')
|
||||
AND parent_obj_id IS NOT NULL
|
||||
GROUP BY parent_obj_id
|
||||
ORDER BY cnt DESC NULLS LAST
|
||||
LIMIT 1`,
|
||||
);
|
||||
if (byChildren2.rowCount && byChildren2.rowCount > 0) {
|
||||
return Number(byChildren2.rows[0].parent_obj_id);
|
||||
}
|
||||
|
||||
// (4) 느슨한 이름 매칭
|
||||
const byNameLike = await pool.query<{ objid: number }>(
|
||||
`SELECT objid FROM menu_info
|
||||
WHERE COALESCE(status,'active') = 'active'
|
||||
AND menu_name_kor LIKE '%시스템%'
|
||||
ORDER BY objid ASC
|
||||
LIMIT 1`,
|
||||
);
|
||||
if (byNameLike.rowCount && byNameLike.rowCount > 0) {
|
||||
return Number(byNameLike.rows[0].objid);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function ensureSequenceMngMenu(): Promise<void> {
|
||||
const pool = getPool();
|
||||
try {
|
||||
// (1) 이미 있으면 종료
|
||||
const existing = await pool.query(
|
||||
`SELECT objid FROM menu_info WHERE menu_url = $1 LIMIT 1`,
|
||||
[TARGET_URL],
|
||||
);
|
||||
if (existing.rowCount && existing.rowCount > 0) {
|
||||
await pool.query(
|
||||
`UPDATE menu_info
|
||||
SET menu_name_kor = COALESCE(NULLIF(menu_name_kor,''), $1),
|
||||
menu_name_eng = COALESCE(NULLIF(menu_name_eng,''), $2),
|
||||
status = 'active'
|
||||
WHERE menu_url = $3`,
|
||||
[MENU_NAME_KOR, MENU_NAME_ENG, TARGET_URL],
|
||||
);
|
||||
logger.info(`✅ 시퀀스 관리 메뉴 이미 존재 — 정규화만 수행`);
|
||||
return;
|
||||
}
|
||||
|
||||
// (2) 부모 찾기 (다중 fallback)
|
||||
const parentObjId = await findSystemMngParentObjId(pool);
|
||||
if (parentObjId == null) {
|
||||
logger.warn(
|
||||
`[sequenceMngMenu] '시스템 관리' 메뉴를 찾지 못해 시퀀스 관리 시드를 건너뜁니다. ` +
|
||||
`메뉴 관리 UI 에서 수동 등록하거나, menu_info 에 다음 행을 추가하세요:\n` +
|
||||
` parent_obj_id = (시스템 관리 OBJID), menu_name_kor = '시퀀스 관리',\n` +
|
||||
` menu_url = '/admin/systemMng/sequenceMng', status = 'active'.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// (3) seq 와 objid 산출
|
||||
const maxSeq = await pool.query<{ max_seq: number | null }>(
|
||||
`SELECT COALESCE(MAX(seq), 0)::int AS max_seq
|
||||
FROM menu_info WHERE parent_obj_id = $1`,
|
||||
[parentObjId],
|
||||
);
|
||||
const nextSeq = (Number(maxSeq.rows[0]?.max_seq) || 0) + 1;
|
||||
|
||||
const nextObjid = await pool.query<{ next: number }>(
|
||||
`SELECT (COALESCE(MAX(objid::bigint), 100000) + 1)::int AS next FROM menu_info`,
|
||||
);
|
||||
const objid = Number(nextObjid.rows[0].next);
|
||||
|
||||
// (4) 부모 행에서 company_code/writer 승계 (참조 일관성)
|
||||
const parentRow = await pool.query<{ company_code: string | null; writer: string | null }>(
|
||||
`SELECT company_code, writer FROM menu_info WHERE objid = $1`,
|
||||
[parentObjId],
|
||||
);
|
||||
const companyCode = parentRow.rows[0]?.company_code ?? "*";
|
||||
const writer = parentRow.rows[0]?.writer ?? "system-seed";
|
||||
|
||||
// (5) INSERT
|
||||
await pool.query(
|
||||
`INSERT INTO menu_info
|
||||
(objid, parent_obj_id, menu_name_kor, menu_name_eng, menu_url, seq,
|
||||
menu_type, company_code, writer, regdate, status)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, 2, $7, $8, NOW(), 'active')`,
|
||||
[objid, parentObjId, MENU_NAME_KOR, MENU_NAME_ENG, TARGET_URL, nextSeq, companyCode, writer],
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`🌱 시퀀스 관리 메뉴 등록 완료: objid=${objid}, parent=${parentObjId}, seq=${nextSeq}, company=${companyCode}`,
|
||||
);
|
||||
} catch (e: any) {
|
||||
logger.warn(`[sequenceMngMenu] 메뉴 시드 실패(계속 진행): ${e?.message?.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user