Merge remote-tracking branch 'origin/main' into ksh

This commit is contained in:
SeongHyun Kim
2025-11-25 14:26:57 +09:00
62 changed files with 2521 additions and 1024 deletions

View File

@@ -1097,7 +1097,11 @@ export async function saveMenu(
let requestCompanyCode = menuData.companyCode || menuData.company_code;
// "none"이나 빈 값은 undefined로 처리하여 사용자 회사 코드 사용
if (requestCompanyCode === "none" || requestCompanyCode === "" || !requestCompanyCode) {
if (
requestCompanyCode === "none" ||
requestCompanyCode === "" ||
!requestCompanyCode
) {
requestCompanyCode = undefined;
}
@@ -1252,7 +1256,8 @@ export async function updateMenu(
}
}
const requestCompanyCode = menuData.companyCode || menuData.company_code || currentMenu.company_code;
const requestCompanyCode =
menuData.companyCode || menuData.company_code || currentMenu.company_code;
// company_code 변경 시도하는 경우 권한 체크
if (requestCompanyCode !== currentMenu.company_code) {
@@ -1268,7 +1273,10 @@ export async function updateMenu(
}
}
// 회사 관리자는 자기 회사로만 변경 가능
else if (userCompanyCode !== "*" && requestCompanyCode !== userCompanyCode) {
else if (
userCompanyCode !== "*" &&
requestCompanyCode !== userCompanyCode
) {
res.status(403).json({
success: false,
message: "해당 회사로 변경할 권한이 없습니다.",
@@ -1324,7 +1332,7 @@ export async function updateMenu(
if (!menuUrl) {
await query(
`UPDATE screen_menu_assignments
SET is_active = 'N', updated_date = NOW()
SET is_active = 'N'
WHERE menu_objid = $1 AND company_code = $2`,
[Number(menuId), companyCode]
);
@@ -1493,8 +1501,13 @@ export async function deleteMenusBatch(
);
// 권한 체크: 공통 메뉴 포함 여부 확인
const hasCommonMenu = menusToDelete.some((menu: any) => menu.company_code === "*");
if (hasCommonMenu && (userCompanyCode !== "*" || userType !== "SUPER_ADMIN")) {
const hasCommonMenu = menusToDelete.some(
(menu: any) => menu.company_code === "*"
);
if (
hasCommonMenu &&
(userCompanyCode !== "*" || userType !== "SUPER_ADMIN")
) {
res.status(403).json({
success: false,
message: "공통 메뉴는 최고 관리자만 삭제할 수 있습니다.",
@@ -1506,7 +1519,8 @@ export async function deleteMenusBatch(
// 회사 관리자는 자기 회사 메뉴만 삭제 가능
if (userCompanyCode !== "*") {
const unauthorizedMenus = menusToDelete.filter(
(menu: any) => menu.company_code !== userCompanyCode && menu.company_code !== "*"
(menu: any) =>
menu.company_code !== userCompanyCode && menu.company_code !== "*"
);
if (unauthorizedMenus.length > 0) {
res.status(403).json({
@@ -2674,7 +2688,10 @@ export const getCompanyByCode = async (
res.status(200).json(response);
} catch (error) {
logger.error("회사 정보 조회 실패", { error, companyCode: req.params.companyCode });
logger.error("회사 정보 조회 실패", {
error,
companyCode: req.params.companyCode,
});
res.status(500).json({
success: false,
message: "회사 정보 조회 중 오류가 발생했습니다.",
@@ -2740,7 +2757,9 @@ export const updateCompany = async (
// 사업자등록번호 중복 체크 및 유효성 검증 (자기 자신 제외)
if (business_registration_number && business_registration_number.trim()) {
// 유효성 검증
const businessNumberValidation = validateBusinessNumber(business_registration_number.trim());
const businessNumberValidation = validateBusinessNumber(
business_registration_number.trim()
);
if (!businessNumberValidation.isValid) {
res.status(400).json({
success: false,
@@ -3283,7 +3302,9 @@ export async function copyMenu(
// 권한 체크: 최고 관리자만 가능
if (!isSuperAdmin && userType !== "SUPER_ADMIN") {
logger.warn(`권한 없음: ${userId} (userType=${userType}, company_code=${userCompanyCode})`);
logger.warn(
`권한 없음: ${userId} (userType=${userType}, company_code=${userCompanyCode})`
);
res.status(403).json({
success: false,
message: "메뉴 복사는 최고 관리자(SUPER_ADMIN)만 가능합니다",

View File

@@ -148,11 +148,11 @@ export const updateScreenInfo = async (
try {
const { id } = req.params;
const { companyCode } = req.user as any;
const { screenName, description, isActive } = req.body;
const { screenName, tableName, description, isActive } = req.body;
await screenManagementService.updateScreenInfo(
parseInt(id),
{ screenName, description, isActive },
{ screenName, tableName, description, isActive },
companyCode
);
res.json({ success: true, message: "화면 정보가 수정되었습니다." });

View File

@@ -7,6 +7,7 @@ import { query, queryOne } from "../../database/db";
import { logger } from "../../utils/logger";
import { NodeFlowExecutionService } from "../../services/nodeFlowExecutionService";
import { AuthenticatedRequest } from "../../types/auth";
import { authenticateToken } from "../../middleware/authMiddleware";
const router = Router();
@@ -217,19 +218,29 @@ router.delete("/:flowId", async (req: Request, res: Response) => {
* 플로우 실행
* POST /api/dataflow/node-flows/:flowId/execute
*/
router.post("/:flowId/execute", async (req: Request, res: Response) => {
router.post("/:flowId/execute", authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
try {
const { flowId } = req.params;
const contextData = req.body;
logger.info(`🚀 플로우 실행 요청: flowId=${flowId}`, {
contextDataKeys: Object.keys(contextData),
userId: req.user?.userId,
companyCode: req.user?.companyCode,
});
// 사용자 정보를 contextData에 추가
const enrichedContextData = {
...contextData,
userId: req.user?.userId,
userName: req.user?.userName,
companyCode: req.user?.companyCode,
};
// 플로우 실행
const result = await NodeFlowExecutionService.executeFlow(
parseInt(flowId, 10),
contextData
enrichedContextData
);
return res.json({

View File

@@ -938,6 +938,30 @@ export class NodeFlowExecutionService {
insertedData[mapping.targetField] = value;
});
// 🆕 writer와 company_code 자동 추가 (필드 매핑에 없는 경우)
const hasWriterMapping = fieldMappings.some((m: any) => m.targetField === "writer");
const hasCompanyCodeMapping = fieldMappings.some((m: any) => m.targetField === "company_code");
// 컨텍스트에서 사용자 정보 추출
const userId = context.buttonContext?.userId;
const companyCode = context.buttonContext?.companyCode;
// writer 자동 추가 (매핑에 없고, 컨텍스트에 userId가 있는 경우)
if (!hasWriterMapping && userId) {
fields.push("writer");
values.push(userId);
insertedData.writer = userId;
console.log(` 🔧 자동 추가: writer = ${userId}`);
}
// company_code 자동 추가 (매핑에 없고, 컨텍스트에 companyCode가 있는 경우)
if (!hasCompanyCodeMapping && companyCode && companyCode !== "*") {
fields.push("company_code");
values.push(companyCode);
insertedData.company_code = companyCode;
console.log(` 🔧 자동 추가: company_code = ${companyCode}`);
}
const sql = `
INSERT INTO ${targetTable} (${fields.join(", ")})
VALUES (${fields.map((_, i) => `$${i + 1}`).join(", ")})

View File

@@ -321,7 +321,7 @@ export class ScreenManagementService {
*/
async updateScreenInfo(
screenId: number,
updateData: { screenName: string; description?: string; isActive: string },
updateData: { screenName: string; tableName?: string; description?: string; isActive: string },
userCompanyCode: string
): Promise<void> {
// 권한 확인
@@ -343,16 +343,18 @@ export class ScreenManagementService {
throw new Error("이 화면을 수정할 권한이 없습니다.");
}
// 화면 정보 업데이트
// 화면 정보 업데이트 (tableName 포함)
await query(
`UPDATE screen_definitions
SET screen_name = $1,
description = $2,
is_active = $3,
updated_date = $4
WHERE screen_id = $5`,
table_name = $2,
description = $3,
is_active = $4,
updated_date = $5
WHERE screen_id = $6`,
[
updateData.screenName,
updateData.tableName || null,
updateData.description || null,
updateData.isActive,
new Date(),