'use server'; import * as cron from 'node-cron'; import db from '@/db/db'; import { employee as employeeTable } from '@/db/schema/knox/employee'; import { searchEmployees, getDepartmentsByCompany, Employee, } from '@/lib/knox-api/employee/employee'; import { sql } from 'drizzle-orm'; // 동기화 대상 회사 코드 (쉼표로 구분된 ENV) const COMPANIES = (process.env.KNOX_COMPANY_CODES || 'P2') .split(',') .map((c) => c.trim()) .filter(Boolean); const CRON_STRING = process.env.KNOX_EMPLOYEE_SYNC_CRON || '0 4 * * *'; const DO_FIRST_RUN = process.env.KNOX_EMPLOYEE_SYNC_FIRST_RUN === 'true'; async function upsertEmployees(employees: Employee[]) { if (!employees.length) return; const rows = employees.map((e) => ({ epId: e.epId, employeeNumber: e.employeeNumber, userId: e.userId, fullName: e.fullName, givenName: e.givenName, sirName: e.sirName, companyCode: e.companyCode, companyName: e.companyName, departmentCode: e.departmentCode, departmentName: e.departmentName, titleCode: e.titleCode, titleName: e.titleName, emailAddress: e.emailAddress, mobile: e.mobile, employeeStatus: e.employeeStatus, employeeType: e.employeeType, accountStatus: e.accountStatus, securityLevel: e.securityLevel, preferredLanguage: e.preferredLanguage, description: e.description, raw: e as unknown as Record, enCompanyName: e.enCompanyName, enDepartmentName: e.enDepartmentName, enDiscription: e.enDiscription, enFullName: e.enFullName, enGivenName: e.enGivenName, enGradeName: e.enGradeName, enSirName: e.enSirName, enTitleName: e.enTitleName, gradeName: e.gradeName, gradeTitleIndiCode: e.gradeTitleIndiCode, jobName: e.jobName, realNameYn: e.realNameYn, serverLocation: e.serverLocation, titleSortOrder: e.titleSortOrder, })); await db .insert(employeeTable) .values(rows) .onConflictDoUpdate({ target: employeeTable.epId, set: { fullName: sql.raw('excluded.full_name'), givenName: sql.raw('excluded.given_name'), sirName: sql.raw('excluded.sir_name'), companyCode: sql.raw('excluded.company_code'), companyName: sql.raw('excluded.company_name'), departmentCode: sql.raw('excluded.department_code'), departmentName: sql.raw('excluded.department_name'), titleCode: sql.raw('excluded.title_code'), titleName: sql.raw('excluded.title_name'), emailAddress: sql.raw('excluded.email_address'), mobile: sql.raw('excluded.mobile'), employeeStatus: sql.raw('excluded.employee_status'), employeeType: sql.raw('excluded.employee_type'), accountStatus: sql.raw('excluded.account_status'), securityLevel: sql.raw('excluded.security_level'), preferredLanguage: sql.raw('excluded.preferred_language'), description: sql.raw('excluded.description'), raw: sql.raw('excluded.raw'), updatedAt: sql.raw('CURRENT_TIMESTAMP'), }, }); } export async function syncKnoxEmployees(): Promise { console.log('[KNOX-SYNC] 임직원 동기화 시작'); for (const companyCode of COMPANIES) { try { const departments = await getDepartmentsByCompany(companyCode); console.log(`[KNOX-SYNC] ${companyCode}: 부서 ${departments.length}개`); for (const dept of departments) { let page = 1; let totalPage = 1; do { const resp = await searchEmployees({ companyCode, departmentCode: dept.departmentCode, page: String(page), resultType: 'basic', }); if (resp.result === 'success') { await upsertEmployees(resp.employees); totalPage = resp.totalPage; console.log( `[KNOX-SYNC] 임직원 동기화 ${companyCode}/${dept.departmentCode} ${page}/${totalPage} 페이지 처리` ); } else { console.warn( `[KNOX-SYNC] 임직원 동기화 실패: ${companyCode}/${dept.departmentCode} page ${page}` ); break; } page += 1; } while (page <= totalPage); } } catch (err) { console.error( `[KNOX-SYNC] 임직원 동기화 오류 (company ${companyCode})`, err ); } } console.log('[KNOX-SYNC] 임직원 동기화 완료'); } export async function startKnoxEmployeeSyncScheduler() { // 환경 변수에 따라 실행시 즉시 실행 여부 결정 (없으면 false) if (DO_FIRST_RUN) { syncKnoxEmployees().catch(console.error); } // CRON JOB 등록 cron.schedule(CRON_STRING, () => { syncKnoxEmployees().catch(console.error); }); console.log(`[KNOX-SYNC] 임직원 동기화 스케줄러 등록 (${CRON_STRING})`); }