2025-11-04 13:58:21 +09:00
|
|
|
/**
|
|
|
|
|
* 채번 규칙 관리 컨트롤러
|
|
|
|
|
*/
|
|
|
|
|
|
2025-11-04 17:35:02 +09:00
|
|
|
import { Router, Response } from "express";
|
|
|
|
|
import { authenticateToken, AuthenticatedRequest } from "../middleware/authMiddleware";
|
2025-11-04 13:58:21 +09:00
|
|
|
import { numberingRuleService } from "../services/numberingRuleService";
|
|
|
|
|
import { logger } from "../utils/logger";
|
|
|
|
|
|
|
|
|
|
const router = Router();
|
|
|
|
|
|
2025-11-04 17:35:02 +09:00
|
|
|
// 규칙 목록 조회 (전체)
|
|
|
|
|
router.get("/", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-04 13:58:21 +09:00
|
|
|
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 });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-04 17:35:02 +09:00
|
|
|
// 메뉴별 사용 가능한 규칙 조회
|
|
|
|
|
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;
|
|
|
|
|
|
2025-11-11 14:32:00 +09:00
|
|
|
logger.info("📥 메뉴별 채번 규칙 조회 요청", { companyCode, menuObjid });
|
|
|
|
|
|
2025-11-04 17:35:02 +09:00
|
|
|
try {
|
|
|
|
|
const rules = await numberingRuleService.getAvailableRulesForMenu(companyCode, menuObjid);
|
2025-11-11 14:32:00 +09:00
|
|
|
|
|
|
|
|
logger.info("✅ 메뉴별 채번 규칙 조회 성공 (컨트롤러)", {
|
|
|
|
|
companyCode,
|
|
|
|
|
menuObjid,
|
|
|
|
|
rulesCount: rules.length
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-04 17:35:02 +09:00
|
|
|
return res.json({ success: true, data: rules });
|
|
|
|
|
} catch (error: any) {
|
2025-11-11 14:32:00 +09:00
|
|
|
logger.error("❌ 메뉴별 사용 가능한 규칙 조회 실패 (컨트롤러)", {
|
2025-11-04 17:35:02 +09:00
|
|
|
error: error.message,
|
2025-11-11 14:32:00 +09:00
|
|
|
errorCode: error.code,
|
|
|
|
|
errorStack: error.stack,
|
|
|
|
|
companyCode,
|
2025-11-04 17:35:02 +09:00
|
|
|
menuObjid,
|
|
|
|
|
});
|
|
|
|
|
return res.status(500).json({ success: false, error: error.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-07 14:27:07 +09:00
|
|
|
// 화면용 채번 규칙 조회 (테이블 기반 필터링 - 간소화)
|
|
|
|
|
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,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-04 13:58:21 +09:00
|
|
|
// 특정 규칙 조회
|
2025-11-04 17:35:02 +09:00
|
|
|
router.get("/:ruleId", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-04 13:58:21 +09:00
|
|
|
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 });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 규칙 생성
|
2025-11-04 17:35:02 +09:00
|
|
|
router.post("/", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-04 13:58:21 +09:00
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const ruleConfig = req.body;
|
|
|
|
|
|
2025-11-11 16:28:17 +09:00
|
|
|
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,
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-04 13:58:21 +09:00
|
|
|
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개 이상의 규칙 파트가 필요합니다" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newRule = await numberingRuleService.createRule(ruleConfig, companyCode, userId);
|
2025-11-11 16:28:17 +09:00
|
|
|
|
|
|
|
|
logger.info("✅ [POST /numbering-rules] 채번 규칙 생성 성공:", {
|
|
|
|
|
ruleId: newRule.ruleId,
|
|
|
|
|
menuObjid: newRule.menuObjid,
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-04 13:58:21 +09:00
|
|
|
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입니다" });
|
|
|
|
|
}
|
2025-11-11 16:28:17 +09:00
|
|
|
logger.error("❌ [POST /numbering-rules] 규칙 생성 실패:", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
stack: error.stack,
|
|
|
|
|
code: error.code,
|
|
|
|
|
});
|
2025-11-04 13:58:21 +09:00
|
|
|
return res.status(500).json({ success: false, error: error.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 규칙 수정
|
2025-11-04 17:35:02 +09:00
|
|
|
router.put("/:ruleId", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-04 13:58:21 +09:00
|
|
|
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 });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 규칙 삭제
|
2025-11-04 17:35:02 +09:00
|
|
|
router.delete("/:ruleId", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-04 13:58:21 +09:00
|
|
|
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 });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-04 17:35:02 +09:00
|
|
|
// 코드 미리보기 (순번 증가 없음)
|
|
|
|
|
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) => {
|
2025-11-04 13:58:21 +09:00
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { ruleId } = req.params;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const generatedCode = await numberingRuleService.generateCode(ruleId, companyCode);
|
2025-11-04 17:35:02 +09:00
|
|
|
return res.json({ success: true, data: { generatedCode } });
|
2025-11-04 13:58:21 +09:00
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error("코드 생성 실패", { error: error.message });
|
|
|
|
|
return res.status(500).json({ success: false, error: error.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 시퀀스 초기화
|
2025-11-04 17:35:02 +09:00
|
|
|
router.post("/:ruleId/reset", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-04 13:58:21 +09:00
|
|
|
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;
|