Files
vexplor/backend-node/src/controllers/numberingRuleController.ts
kjs a1819e749c fix: 탭 컴포넌트 menuObjid 전달, 카테고리 필터 복원, 설정 초기화 문제 해결
주요 수정사항:

1. 탭 컴포넌트 내 자식 화면에 menuObjid와 tableName 전달
   - TabsWidget에 menuObjid prop 추가
   - InteractiveScreenViewerDynamic를 통해 자식 화면에 전달
   - 채번 규칙 생성 시 올바른 메뉴 스코프 및 테이블명 적용

2. 백엔드: 화면 레이아웃 API에 tableName 추가
   - screenManagementService.getLayout()에서 테이블명 반환
   - LayoutData 타입에 tableName 필드 추가
   - 채번 규칙 생성 시 tableName 검증 강화

3. 카테고리 필터링 기능 복원
   - DataFilterConfigPanel에 menuObjid 전달
   - getCategoryValues API 사용으로 메뉴 스코프 적용
   - 새로고침 후 카테고리 값 자동 재로드
   - SplitPanelLayoutConfigPanel에 menuObjid 전달

4. 선택항목 상세입력 설정 패널 포커스 문제 해결
   - 로컬 입력 상태 추가로 실시간 속성 편집 패턴 적용
   - 텍스트 및 라벨 입력 시 포커스 유지

5. 테이블 리스트 설정 초기화 문제 해결
   - handleChange 함수에서 기존 config와 병합하여 전달
   - 다른 속성 손실 방지 (columns, dataFilter 등)

버그 수정:
- 채번 규칙 생성 시 빈 문자열 대신 null 전달
- 필터 설정 변경 시 컬럼 설정 초기화 방지
- 카테고리 컬럼 선택 시 셀렉트박스 표시
2025-11-25 15:55:05 +09:00

258 lines
9.0 KiB
TypeScript

/**
* 채번 규칙 관리 컨트롤러
*/
import { Router, Response } from "express";
import { authenticateToken, AuthenticatedRequest } from "../middleware/authMiddleware";
import { numberingRuleService } from "../services/numberingRuleService";
import { logger } from "../utils/logger";
const router = Router();
// 규칙 목록 조회 (전체)
router.get("/", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
try {
const rules = await numberingRuleService.getRuleList(companyCode);
return res.json({ success: true, data: rules });
} catch (error: any) {
logger.error("규칙 목록 조회 실패", { error: error.message });
return res.status(500).json({ success: false, error: error.message });
}
});
// 메뉴별 사용 가능한 규칙 조회
router.get("/available/:menuObjid?", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const menuObjid = req.params.menuObjid ? parseInt(req.params.menuObjid) : undefined;
logger.info("메뉴별 채번 규칙 조회 요청", { menuObjid, companyCode });
try {
const rules = await numberingRuleService.getAvailableRulesForMenu(companyCode, menuObjid);
logger.info("✅ 메뉴별 채번 규칙 조회 성공 (컨트롤러)", {
companyCode,
menuObjid,
rulesCount: rules.length
});
return res.json({ success: true, data: rules });
} catch (error: any) {
logger.error("❌ 메뉴별 사용 가능한 규칙 조회 실패 (컨트롤러)", {
error: error.message,
errorCode: error.code,
errorStack: error.stack,
companyCode,
menuObjid,
});
return res.status(500).json({ success: false, error: error.message });
}
});
// 화면용 채번 규칙 조회 (테이블 기반 필터링 - 간소화)
router.get("/available-for-screen", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const { tableName } = req.query;
try {
// tableName 필수 검증
if (!tableName || typeof tableName !== "string") {
return res.status(400).json({
success: false,
error: "tableName is required",
});
}
const rules = await numberingRuleService.getAvailableRulesForScreen(
companyCode,
tableName
);
logger.info("화면용 채번 규칙 조회 성공", {
companyCode,
tableName,
count: rules.length,
});
return res.json({ success: true, data: rules });
} catch (error: any) {
logger.error("화면용 채번 규칙 조회 실패", {
error: error.message,
tableName,
});
return res.status(500).json({
success: false,
error: error.message,
});
}
});
// 특정 규칙 조회
router.get("/:ruleId", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const { ruleId } = req.params;
try {
const rule = await numberingRuleService.getRuleById(ruleId, companyCode);
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 });
return res.status(500).json({ success: false, error: error.message });
}
});
// 규칙 생성
router.post("/", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const userId = req.user!.userId;
const ruleConfig = req.body;
logger.info("🔍 [POST /numbering-rules] 채번 규칙 생성 요청:", {
companyCode,
userId,
ruleId: ruleConfig.ruleId,
ruleName: ruleConfig.ruleName,
scopeType: ruleConfig.scopeType,
menuObjid: ruleConfig.menuObjid,
tableName: ruleConfig.tableName,
partsCount: ruleConfig.parts?.length,
});
try {
if (!ruleConfig.ruleId || !ruleConfig.ruleName) {
return res.status(400).json({ success: false, error: "규칙 ID와 규칙명은 필수입니다" });
}
if (!Array.isArray(ruleConfig.parts) || ruleConfig.parts.length === 0) {
return res.status(400).json({ success: false, error: "최소 1개 이상의 규칙 파트가 필요합니다" });
}
// 🆕 scopeType이 'table'인 경우 tableName 필수 체크
if (ruleConfig.scopeType === "table") {
if (!ruleConfig.tableName || ruleConfig.tableName.trim() === "") {
return res.status(400).json({
success: false,
error: "테이블 범위 규칙은 테이블명(tableName)이 필수입니다",
});
}
}
const newRule = await numberingRuleService.createRule(ruleConfig, companyCode, userId);
logger.info("✅ [POST /numbering-rules] 채번 규칙 생성 성공:", {
ruleId: newRule.ruleId,
menuObjid: newRule.menuObjid,
});
return res.status(201).json({ success: true, data: newRule });
} catch (error: any) {
if (error.code === "23505") {
return res.status(409).json({ success: false, error: "이미 존재하는 규칙 ID입니다" });
}
logger.error("❌ [POST /numbering-rules] 규칙 생성 실패:", {
error: error.message,
stack: error.stack,
code: error.code,
});
return res.status(500).json({ success: false, error: error.message });
}
});
// 규칙 수정
router.put("/:ruleId", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const { ruleId } = req.params;
const updates = req.body;
try {
const updatedRule = await numberingRuleService.updateRule(ruleId, updates, companyCode);
return res.json({ success: true, data: updatedRule });
} catch (error: any) {
if (error.message.includes("찾을 수 없거나")) {
return res.status(404).json({ success: false, error: error.message });
}
logger.error("규칙 수정 실패", { error: error.message });
return res.status(500).json({ success: false, error: error.message });
}
});
// 규칙 삭제
router.delete("/:ruleId", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const { ruleId } = req.params;
try {
await numberingRuleService.deleteRule(ruleId, companyCode);
return res.json({ success: true, message: "규칙이 삭제되었습니다" });
} catch (error: any) {
if (error.message.includes("찾을 수 없거나")) {
return res.status(404).json({ success: false, error: error.message });
}
logger.error("규칙 삭제 실패", { error: error.message });
return res.status(500).json({ success: false, error: error.message });
}
});
// 코드 미리보기 (순번 증가 없음)
router.post("/:ruleId/preview", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const { ruleId } = req.params;
try {
const previewCode = await numberingRuleService.previewCode(ruleId, companyCode);
return res.json({ success: true, data: { generatedCode: previewCode } });
} catch (error: any) {
logger.error("코드 미리보기 실패", { error: error.message });
return res.status(500).json({ success: false, error: error.message });
}
});
// 코드 할당 (저장 시점에 실제 순번 증가)
router.post("/:ruleId/allocate", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const { ruleId } = req.params;
try {
const allocatedCode = await numberingRuleService.allocateCode(ruleId, companyCode);
return res.json({ success: true, data: { generatedCode: allocatedCode } });
} catch (error: any) {
logger.error("코드 할당 실패", { error: error.message });
return res.status(500).json({ success: false, error: error.message });
}
});
// 코드 생성 (기존 호환성 유지, deprecated)
router.post("/:ruleId/generate", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const { ruleId } = req.params;
try {
const generatedCode = await numberingRuleService.generateCode(ruleId, companyCode);
return res.json({ success: true, data: { generatedCode } });
} catch (error: any) {
logger.error("코드 생성 실패", { error: error.message });
return res.status(500).json({ success: false, error: error.message });
}
});
// 시퀀스 초기화
router.post("/:ruleId/reset", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
const companyCode = req.user!.companyCode;
const { ruleId } = req.params;
try {
await numberingRuleService.resetSequence(ruleId, companyCode);
return res.json({ success: true, message: "시퀀스가 초기화되었습니다" });
} catch (error: any) {
logger.error("시퀀스 초기화 실패", { error: error.message });
return res.status(500).json({ success: false, error: error.message });
}
});
export default router;