feat: Implement smart factory schedule management functionality
- Added new API endpoints for managing smart factory schedules, including retrieval, creation, updating, and deletion of schedules. - Integrated schedule management into the smart factory log controller, enhancing the overall functionality. - Implemented a scheduler initialization process to automate daily plan generation and scheduled sends. - Developed a frontend page for monitoring equipment, production, and quality, with real-time data fetching and auto-refresh capabilities. These changes aim to provide comprehensive scheduling capabilities for smart factory operations, improving efficiency and operational visibility for users.
This commit is contained in:
@@ -5,6 +5,12 @@ import { Response } from "express";
|
||||
import { AuthenticatedRequest } from "../middleware/permissionMiddleware";
|
||||
import { query, queryOne } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
import { encryptionService } from "../services/encryptionService";
|
||||
import {
|
||||
runScheduleNow,
|
||||
getTodayPlanStatus,
|
||||
planDailySends,
|
||||
} from "../utils/smartFactoryLog";
|
||||
|
||||
/**
|
||||
* GET /api/admin/smart-factory-log
|
||||
@@ -216,3 +222,283 @@ export const getSmartFactoryLogStats = async (
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ─── 스케줄 관리 API ───
|
||||
|
||||
/**
|
||||
* GET /api/admin/smart-factory-log/schedules
|
||||
*/
|
||||
export const getSchedules = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const schedules = await query<any>(
|
||||
`SELECT s.*, cm.company_name
|
||||
FROM smart_factory_schedule s
|
||||
LEFT JOIN company_mng cm ON cm.company_code = s.company_code
|
||||
ORDER BY s.company_code`
|
||||
);
|
||||
res.json({ success: true, data: schedules });
|
||||
} catch (error) {
|
||||
logger.error("스케줄 조회 실패:", error);
|
||||
res.status(500).json({ success: false, message: "스케줄 조회 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/admin/smart-factory-log/schedules
|
||||
*/
|
||||
export const upsertSchedule = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode, isActive, timeStart, timeEnd, excludeWeekend, excludeHolidays } = req.body;
|
||||
|
||||
if (!companyCode) {
|
||||
res.status(400).json({ success: false, message: "회사코드는 필수입니다." });
|
||||
return;
|
||||
}
|
||||
|
||||
await query(
|
||||
`INSERT INTO smart_factory_schedule (company_code, is_active, time_start, time_end, exclude_weekend, exclude_holidays, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, NOW())
|
||||
ON CONFLICT (company_code) DO UPDATE SET
|
||||
is_active = $2, time_start = $3, time_end = $4,
|
||||
exclude_weekend = $5, exclude_holidays = $6, updated_at = NOW()`,
|
||||
[
|
||||
companyCode,
|
||||
isActive ?? false,
|
||||
timeStart || "08:30",
|
||||
timeEnd || "17:30",
|
||||
excludeWeekend ?? true,
|
||||
excludeHolidays ?? true,
|
||||
]
|
||||
);
|
||||
|
||||
// 스케줄 변경 시 오늘 계획 재생성
|
||||
await planDailySends();
|
||||
|
||||
res.json({ success: true, message: "스케줄이 저장되었습니다." });
|
||||
} catch (error) {
|
||||
logger.error("스케줄 저장 실패:", error);
|
||||
res.status(500).json({ success: false, message: "스케줄 저장 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE /api/admin/smart-factory-log/schedules/:companyCode
|
||||
*/
|
||||
export const deleteSchedule = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
await query("DELETE FROM smart_factory_schedule WHERE company_code = $1", [companyCode]);
|
||||
res.json({ success: true, message: "스케줄이 삭제되었습니다." });
|
||||
} catch (error) {
|
||||
logger.error("스케줄 삭제 실패:", error);
|
||||
res.status(500).json({ success: false, message: "스케줄 삭제 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/admin/smart-factory-log/schedules/:companyCode/run-now
|
||||
*/
|
||||
export const runScheduleNowHandler = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
const result = await runScheduleNow(companyCode);
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
logger.error("즉시 실행 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : "즉시 실행 실패",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* GET /api/admin/smart-factory-log/schedules/today-plan
|
||||
*/
|
||||
export const getTodayPlanHandler = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const plan = getTodayPlanStatus();
|
||||
res.json({ success: true, data: plan });
|
||||
} catch (error) {
|
||||
logger.error("오늘 계획 조회 실패:", error);
|
||||
res.status(500).json({ success: false, message: "오늘 계획 조회 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── 공휴일 관리 API ───
|
||||
|
||||
/**
|
||||
* GET /api/admin/smart-factory-log/holidays
|
||||
*/
|
||||
export const getHolidays = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const holidays = await query<any>(
|
||||
"SELECT id, holiday_date, holiday_name, created_at FROM smart_factory_holidays ORDER BY holiday_date"
|
||||
);
|
||||
res.json({ success: true, data: holidays });
|
||||
} catch (error) {
|
||||
logger.error("공휴일 조회 실패:", error);
|
||||
res.status(500).json({ success: false, message: "공휴일 조회 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/admin/smart-factory-log/holidays
|
||||
*/
|
||||
export const addHoliday = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { holidayDate, holidayName } = req.body;
|
||||
|
||||
if (!holidayDate || !holidayName) {
|
||||
res.status(400).json({ success: false, message: "날짜와 이름은 필수입니다." });
|
||||
return;
|
||||
}
|
||||
|
||||
await query(
|
||||
"INSERT INTO smart_factory_holidays (holiday_date, holiday_name) VALUES ($1, $2) ON CONFLICT (holiday_date) DO UPDATE SET holiday_name = $2",
|
||||
[holidayDate, holidayName]
|
||||
);
|
||||
|
||||
res.json({ success: true, message: "공휴일이 추가되었습니다." });
|
||||
} catch (error) {
|
||||
logger.error("공휴일 추가 실패:", error);
|
||||
res.status(500).json({ success: false, message: "공휴일 추가 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE /api/admin/smart-factory-log/holidays/:id
|
||||
*/
|
||||
export const deleteHoliday = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await query("DELETE FROM smart_factory_holidays WHERE id = $1", [id]);
|
||||
res.json({ success: true, message: "공휴일이 삭제되었습니다." });
|
||||
} catch (error) {
|
||||
logger.error("공휴일 삭제 실패:", error);
|
||||
res.status(500).json({ success: false, message: "공휴일 삭제 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── API 키 관리 ───
|
||||
|
||||
/**
|
||||
* GET /api/admin/smart-factory-log/api-keys
|
||||
* 전체 회사 목록 + API 키 상태 (DB키 여부, 환경변수 여부)
|
||||
*/
|
||||
export const getApiKeys = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const companies = await query<any>(
|
||||
`SELECT cm.company_code, cm.company_name, ak.api_key
|
||||
FROM company_mng cm
|
||||
LEFT JOIN smart_factory_api_keys ak ON ak.company_code = cm.company_code
|
||||
WHERE cm.company_code != '*'
|
||||
ORDER BY cm.company_code`
|
||||
);
|
||||
|
||||
const result = companies.map((c: any) => {
|
||||
let dbKeyDecrypted: string | null = null;
|
||||
if (c.api_key) {
|
||||
try {
|
||||
dbKeyDecrypted = encryptionService.decrypt(c.api_key);
|
||||
} catch {
|
||||
dbKeyDecrypted = "(복호화 실패)";
|
||||
}
|
||||
}
|
||||
return {
|
||||
companyCode: c.company_code,
|
||||
companyName: c.company_name,
|
||||
hasDbKey: !!c.api_key,
|
||||
dbKey: dbKeyDecrypted,
|
||||
hasEnvKey: !!process.env[`SMART_FACTORY_API_KEY_${c.company_code}`],
|
||||
};
|
||||
});
|
||||
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
logger.error("API 키 목록 조회 실패:", error);
|
||||
res.status(500).json({ success: false, message: "API 키 목록 조회 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/admin/smart-factory-log/api-keys
|
||||
* API 키 저장 (암호화)
|
||||
*/
|
||||
export const saveApiKey = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode, apiKey } = req.body;
|
||||
|
||||
if (!companyCode || !apiKey) {
|
||||
res.status(400).json({ success: false, message: "회사코드와 API 키는 필수입니다." });
|
||||
return;
|
||||
}
|
||||
|
||||
const encrypted = encryptionService.encrypt(apiKey);
|
||||
|
||||
await query(
|
||||
`INSERT INTO smart_factory_api_keys (company_code, api_key, updated_at)
|
||||
VALUES ($1, $2, NOW())
|
||||
ON CONFLICT (company_code) DO UPDATE SET api_key = $2, updated_at = NOW()`,
|
||||
[companyCode, encrypted]
|
||||
);
|
||||
|
||||
res.json({ success: true, message: "API 키가 저장되었습니다." });
|
||||
} catch (error) {
|
||||
logger.error("API 키 저장 실패:", error);
|
||||
res.status(500).json({ success: false, message: "API 키 저장 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE /api/admin/smart-factory-log/api-keys/:companyCode
|
||||
* API 키 삭제 (환경변수 폴백으로 전환)
|
||||
*/
|
||||
export const deleteApiKey = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
|
||||
await query(
|
||||
"DELETE FROM smart_factory_api_keys WHERE company_code = $1",
|
||||
[companyCode]
|
||||
);
|
||||
|
||||
res.json({ success: true, message: "API 키가 삭제되었습니다." });
|
||||
} catch (error) {
|
||||
logger.error("API 키 삭제 실패:", error);
|
||||
res.status(500).json({ success: false, message: "API 키 삭제 실패" });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user