refactor: 코드 정리 및 가독성 향상
- numberingRuleController.ts에서 API 엔드포인트의 코드 스타일을 일관되게 정리하여 가독성을 높였습니다. - 불필요한 줄바꿈을 제거하고, 코드 블록을 명확하게 정리하여 유지보수성을 개선했습니다. - tableManagementService.ts와 ButtonConfigPanel.tsx에서 코드 정리를 통해 일관성을 유지하고, 가독성을 향상시켰습니다. - 전반적으로 코드의 깔끔함을 유지하고, 향후 개발 시 이해하기 쉽게 개선했습니다.
This commit is contained in:
@@ -3,392 +3,545 @@
|
||||
*/
|
||||
|
||||
import { Router, Response } from "express";
|
||||
import { authenticateToken, AuthenticatedRequest } from "../middleware/authMiddleware";
|
||||
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;
|
||||
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 });
|
||||
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;
|
||||
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 });
|
||||
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 });
|
||||
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;
|
||||
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() === "") {
|
||||
try {
|
||||
// tableName 필수 검증
|
||||
if (!tableName || typeof tableName !== "string") {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "테이블 범위 규칙은 테이블명(tableName)이 필수입니다",
|
||||
error: "tableName is required",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const newRule = await numberingRuleService.createRule(ruleConfig, companyCode, userId);
|
||||
|
||||
logger.info("✅ [POST /numbering-rules] 채번 규칙 생성 성공:", {
|
||||
ruleId: newRule.ruleId,
|
||||
menuObjid: newRule.menuObjid,
|
||||
});
|
||||
const rules = await numberingRuleService.getAvailableRulesForScreen(
|
||||
companyCode,
|
||||
tableName
|
||||
);
|
||||
|
||||
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.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,
|
||||
});
|
||||
}
|
||||
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.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;
|
||||
router.put(
|
||||
"/:ruleId",
|
||||
authenticateToken,
|
||||
async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { ruleId } = req.params;
|
||||
const updates = req.body;
|
||||
|
||||
logger.info("채번 규칙 수정 요청", { ruleId, companyCode, updates });
|
||||
logger.info("채번 규칙 수정 요청", { ruleId, companyCode, updates });
|
||||
|
||||
try {
|
||||
const updatedRule = await numberingRuleService.updateRule(ruleId, updates, companyCode);
|
||||
logger.info("채번 규칙 수정 성공", { ruleId, companyCode });
|
||||
return res.json({ success: true, data: updatedRule });
|
||||
} catch (error: any) {
|
||||
logger.error("채번 규칙 수정 실패", {
|
||||
ruleId,
|
||||
companyCode,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
if (error.message.includes("찾을 수 없거나")) {
|
||||
return res.status(404).json({ success: false, error: error.message });
|
||||
try {
|
||||
const updatedRule = await numberingRuleService.updateRule(
|
||||
ruleId,
|
||||
updates,
|
||||
companyCode
|
||||
);
|
||||
logger.info("채번 규칙 수정 성공", { ruleId, companyCode });
|
||||
return res.json({ success: true, data: updatedRule });
|
||||
} catch (error: any) {
|
||||
logger.error("채번 규칙 수정 실패", {
|
||||
ruleId,
|
||||
companyCode,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
if (error.message.includes("찾을 수 없거나")) {
|
||||
return res.status(404).json({ success: false, error: error.message });
|
||||
}
|
||||
return res.status(500).json({ success: false, 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;
|
||||
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 });
|
||||
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 });
|
||||
}
|
||||
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;
|
||||
const { formData } = req.body; // 폼 데이터 (카테고리 기반 채번 시 사용)
|
||||
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, formData);
|
||||
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 });
|
||||
try {
|
||||
const previewCode = await numberingRuleService.previewCode(
|
||||
ruleId,
|
||||
companyCode,
|
||||
formData
|
||||
);
|
||||
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;
|
||||
const { formData, userInputCode } = req.body; // 폼 데이터 + 사용자가 편집한 코드
|
||||
router.post(
|
||||
"/:ruleId/allocate",
|
||||
authenticateToken,
|
||||
async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { ruleId } = req.params;
|
||||
const { formData, userInputCode } = req.body; // 폼 데이터 + 사용자가 편집한 코드
|
||||
|
||||
logger.info("코드 할당 요청", { ruleId, companyCode, hasFormData: !!formData, userInputCode });
|
||||
logger.info("코드 할당 요청", {
|
||||
ruleId,
|
||||
companyCode,
|
||||
hasFormData: !!formData,
|
||||
userInputCode,
|
||||
});
|
||||
|
||||
try {
|
||||
const allocatedCode = await numberingRuleService.allocateCode(ruleId, companyCode, formData, userInputCode);
|
||||
logger.info("코드 할당 성공", { ruleId, allocatedCode });
|
||||
return res.json({ success: true, data: { generatedCode: allocatedCode } });
|
||||
} catch (error: any) {
|
||||
logger.error("코드 할당 실패", { ruleId, companyCode, error: error.message });
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
try {
|
||||
const allocatedCode = await numberingRuleService.allocateCode(
|
||||
ruleId,
|
||||
companyCode,
|
||||
formData,
|
||||
userInputCode
|
||||
);
|
||||
logger.info("코드 할당 성공", { ruleId, allocatedCode });
|
||||
return res.json({
|
||||
success: true,
|
||||
data: { generatedCode: allocatedCode },
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("코드 할당 실패", {
|
||||
ruleId,
|
||||
companyCode,
|
||||
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;
|
||||
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 });
|
||||
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;
|
||||
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 });
|
||||
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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// ==================== 테스트 테이블용 API ====================
|
||||
|
||||
// [테스트] 테스트 테이블에서 채번 규칙 목록 조회
|
||||
router.get("/test/list/:menuObjid?", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const menuObjid = req.params.menuObjid ? parseInt(req.params.menuObjid) : undefined;
|
||||
router.get(
|
||||
"/test/list/:menuObjid?",
|
||||
authenticateToken,
|
||||
async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const menuObjid = req.params.menuObjid
|
||||
? parseInt(req.params.menuObjid)
|
||||
: undefined;
|
||||
|
||||
logger.info("[테스트] 채번 규칙 목록 조회 요청", { companyCode, menuObjid });
|
||||
logger.info("[테스트] 채번 규칙 목록 조회 요청", {
|
||||
companyCode,
|
||||
menuObjid,
|
||||
});
|
||||
|
||||
try {
|
||||
const rules = await numberingRuleService.getRulesFromTest(companyCode, menuObjid);
|
||||
logger.info("[테스트] 채번 규칙 목록 조회 성공", { companyCode, menuObjid, count: rules.length });
|
||||
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 });
|
||||
try {
|
||||
const rules = await numberingRuleService.getRulesFromTest(
|
||||
companyCode,
|
||||
menuObjid
|
||||
);
|
||||
logger.info("[테스트] 채번 규칙 목록 조회 성공", {
|
||||
companyCode,
|
||||
menuObjid,
|
||||
count: rules.length,
|
||||
});
|
||||
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("/test/by-column/:tableName/:columnName", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { tableName, columnName } = req.params;
|
||||
router.get(
|
||||
"/test/by-column/:tableName/:columnName",
|
||||
authenticateToken,
|
||||
async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { tableName, columnName } = req.params;
|
||||
|
||||
try {
|
||||
const rule = await numberingRuleService.getNumberingRuleByColumn(companyCode, tableName, columnName);
|
||||
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 });
|
||||
try {
|
||||
const rule = await numberingRuleService.getNumberingRuleByColumn(
|
||||
companyCode,
|
||||
tableName,
|
||||
columnName
|
||||
);
|
||||
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("/test/save", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const userId = req.user!.userId;
|
||||
const ruleConfig = req.body;
|
||||
router.post(
|
||||
"/test/save",
|
||||
authenticateToken,
|
||||
async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const userId = req.user!.userId;
|
||||
const ruleConfig = req.body;
|
||||
|
||||
logger.info("[테스트] 채번 규칙 저장 요청", {
|
||||
ruleId: ruleConfig.ruleId,
|
||||
ruleName: ruleConfig.ruleName,
|
||||
tableName: ruleConfig.tableName || "(미지정)",
|
||||
columnName: ruleConfig.columnName || "(미지정)",
|
||||
});
|
||||
logger.info("[테스트] 채번 규칙 저장 요청", {
|
||||
ruleId: ruleConfig.ruleId,
|
||||
ruleName: ruleConfig.ruleName,
|
||||
tableName: ruleConfig.tableName || "(미지정)",
|
||||
columnName: ruleConfig.columnName || "(미지정)",
|
||||
});
|
||||
|
||||
try {
|
||||
// ruleName만 필수, tableName/columnName은 선택 (나중에 테이블 타입 관리에서 연결)
|
||||
if (!ruleConfig.ruleName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "ruleName is required"
|
||||
});
|
||||
try {
|
||||
// ruleName만 필수, tableName/columnName은 선택 (나중에 테이블 타입 관리에서 연결)
|
||||
if (!ruleConfig.ruleName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "ruleName is required",
|
||||
});
|
||||
}
|
||||
|
||||
const savedRule = await numberingRuleService.saveRuleToTest(
|
||||
ruleConfig,
|
||||
companyCode,
|
||||
userId
|
||||
);
|
||||
return res.json({ success: true, data: savedRule });
|
||||
} catch (error: any) {
|
||||
logger.error("[테스트] 채번 규칙 저장 실패", { error: error.message });
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
|
||||
const savedRule = await numberingRuleService.saveRuleToTest(ruleConfig, companyCode, userId);
|
||||
return res.json({ success: true, data: savedRule });
|
||||
} catch (error: any) {
|
||||
logger.error("[테스트] 채번 규칙 저장 실패", { error: error.message });
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// [테스트] 테스트 테이블에서 채번 규칙 삭제
|
||||
router.delete("/test/:ruleId", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { ruleId } = req.params;
|
||||
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, message: "테스트 채번 규칙이 삭제되었습니다" });
|
||||
} catch (error: any) {
|
||||
logger.error("[테스트] 채번 규칙 삭제 실패", { error: error.message });
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
try {
|
||||
await numberingRuleService.deleteRuleFromTest(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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// [테스트] 코드 미리보기 (테스트 테이블 사용)
|
||||
router.post("/test/:ruleId/preview", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { ruleId } = req.params;
|
||||
const { formData } = req.body;
|
||||
router.post(
|
||||
"/test/: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, formData);
|
||||
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 });
|
||||
try {
|
||||
const previewCode = await numberingRuleService.previewCode(
|
||||
ruleId,
|
||||
companyCode,
|
||||
formData
|
||||
);
|
||||
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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// ==================== 회사별 채번규칙 복제 API ====================
|
||||
|
||||
// 회사별 채번규칙 복제
|
||||
router.post("/copy-for-company", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||
const userCompanyCode = req.user!.companyCode;
|
||||
const { sourceCompanyCode, targetCompanyCode } = req.body;
|
||||
router.post(
|
||||
"/copy-for-company",
|
||||
authenticateToken,
|
||||
async (req: AuthenticatedRequest, res: Response) => {
|
||||
const userCompanyCode = req.user!.companyCode;
|
||||
const { sourceCompanyCode, targetCompanyCode } = req.body;
|
||||
|
||||
// 최고 관리자만 사용 가능
|
||||
if (userCompanyCode !== "*") {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: "최고 관리자만 사용할 수 있습니다"
|
||||
});
|
||||
}
|
||||
// 최고 관리자만 사용 가능
|
||||
if (userCompanyCode !== "*") {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: "최고 관리자만 사용할 수 있습니다",
|
||||
});
|
||||
}
|
||||
|
||||
if (!sourceCompanyCode || !targetCompanyCode) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "sourceCompanyCode와 targetCompanyCode가 필요합니다"
|
||||
});
|
||||
}
|
||||
if (!sourceCompanyCode || !targetCompanyCode) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "sourceCompanyCode와 targetCompanyCode가 필요합니다",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await numberingRuleService.copyRulesForCompany(sourceCompanyCode, targetCompanyCode);
|
||||
return res.json({ success: true, data: result });
|
||||
} catch (error: any) {
|
||||
logger.error("회사별 채번규칙 복제 실패", { error: error.message });
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
try {
|
||||
const result = await numberingRuleService.copyRulesForCompany(
|
||||
sourceCompanyCode,
|
||||
targetCompanyCode
|
||||
);
|
||||
return res.json({ success: true, data: result });
|
||||
} catch (error: any) {
|
||||
logger.error("회사별 채번규칙 복제 실패", { error: error.message });
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -322,7 +322,9 @@ export class TableManagementService {
|
||||
});
|
||||
} else {
|
||||
// menu_objid 컬럼이 없는 경우 - 매핑 없이 진행
|
||||
logger.info("⚠️ getColumnList: menu_objid 컬럼이 없음, 카테고리 매핑 스킵");
|
||||
logger.info(
|
||||
"⚠️ getColumnList: menu_objid 컬럼이 없음, 카테고리 매핑 스킵"
|
||||
);
|
||||
}
|
||||
} catch (mappingError: any) {
|
||||
logger.warn("⚠️ getColumnList: 카테고리 매핑 조회 실패, 스킵", {
|
||||
@@ -488,7 +490,10 @@ export class TableManagementService {
|
||||
// table_type_columns에 모든 설정 저장 (멀티테넌시 지원)
|
||||
// detailSettings가 문자열이면 그대로, 객체면 JSON.stringify
|
||||
let detailSettingsStr = settings.detailSettings;
|
||||
if (typeof settings.detailSettings === "object" && settings.detailSettings !== null) {
|
||||
if (
|
||||
typeof settings.detailSettings === "object" &&
|
||||
settings.detailSettings !== null
|
||||
) {
|
||||
detailSettingsStr = JSON.stringify(settings.detailSettings);
|
||||
}
|
||||
|
||||
@@ -734,7 +739,7 @@ export class TableManagementService {
|
||||
inputType?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
// 🔥 'direct'나 'auto'는 프론트엔드의 입력 방식 구분값이므로
|
||||
// 🔥 'direct'나 'auto'는 프론트엔드의 입력 방식 구분값이므로
|
||||
// DB의 input_type(웹타입)으로 저장하면 안 됨 - 'text'로 변환
|
||||
let finalWebType = webType;
|
||||
if (webType === "direct" || webType === "auto") {
|
||||
@@ -749,7 +754,8 @@ export class TableManagementService {
|
||||
);
|
||||
|
||||
// 웹 타입별 기본 상세 설정 생성
|
||||
const defaultDetailSettings = this.generateDefaultDetailSettings(finalWebType);
|
||||
const defaultDetailSettings =
|
||||
this.generateDefaultDetailSettings(finalWebType);
|
||||
|
||||
// 사용자 정의 설정과 기본 설정 병합
|
||||
const finalDetailSettings = {
|
||||
@@ -768,7 +774,12 @@ export class TableManagementService {
|
||||
input_type = EXCLUDED.input_type,
|
||||
detail_settings = EXCLUDED.detail_settings,
|
||||
updated_date = NOW()`,
|
||||
[tableName, columnName, finalWebType, JSON.stringify(finalDetailSettings)]
|
||||
[
|
||||
tableName,
|
||||
columnName,
|
||||
finalWebType,
|
||||
JSON.stringify(finalDetailSettings),
|
||||
]
|
||||
);
|
||||
logger.info(
|
||||
`컬럼 입력 타입 설정 완료: ${tableName}.${columnName} = ${finalWebType}`
|
||||
@@ -796,7 +807,7 @@ export class TableManagementService {
|
||||
detailSettings?: Record<string, any>
|
||||
): Promise<void> {
|
||||
try {
|
||||
// 🔥 'direct'나 'auto'는 프론트엔드의 입력 방식 구분값이므로
|
||||
// 🔥 'direct'나 'auto'는 프론트엔드의 입력 방식 구분값이므로
|
||||
// DB의 input_type(웹타입)으로 저장하면 안 됨 - 'text'로 변환
|
||||
let finalInputType = inputType;
|
||||
if (inputType === "direct" || inputType === "auto") {
|
||||
@@ -1473,7 +1484,11 @@ export class TableManagementService {
|
||||
columnInfo &&
|
||||
(columnInfo.webType === "date" || columnInfo.webType === "datetime")
|
||||
) {
|
||||
return this.buildDateRangeCondition(columnName, actualValue, paramIndex);
|
||||
return this.buildDateRangeCondition(
|
||||
columnName,
|
||||
actualValue,
|
||||
paramIndex
|
||||
);
|
||||
}
|
||||
|
||||
// 그 외 타입이면 다중선택(IN 조건)으로 처리
|
||||
@@ -3464,7 +3479,7 @@ export class TableManagementService {
|
||||
// 기본 Entity 조인 컬럼인 경우: 조인된 테이블의 표시 컬럼에서 검색
|
||||
const aliasKey = `${joinConfig.referenceTable}:${joinConfig.sourceColumn}`;
|
||||
const alias = aliasMap.get(aliasKey);
|
||||
|
||||
|
||||
// 🔧 파이프로 구분된 다중 선택값 처리
|
||||
if (safeValue.includes("|")) {
|
||||
const multiValues = safeValue
|
||||
|
||||
Reference in New Issue
Block a user