diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-22 02:57:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-22 02:57:31 +0000 |
| commit | cb34c5e1a61a20c954e12a8219d82dbdfbe50e13 (patch) | |
| tree | 61fdfd81c3f42da6064c5ec2b661f1ef17ae8681 /lib | |
| parent | ee57cc221ff2edafd3c0f12a181214c602ed257e (diff) | |
(김준회) Knox API - 임직원 저장 구현
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/knox-sync/organization-sync-service.ts | 132 | ||||
| -rw-r--r-- | lib/knox-sync/title-sync-service.ts | 70 |
2 files changed, 202 insertions, 0 deletions
diff --git a/lib/knox-sync/organization-sync-service.ts b/lib/knox-sync/organization-sync-service.ts new file mode 100644 index 00000000..0b77174b --- /dev/null +++ b/lib/knox-sync/organization-sync-service.ts @@ -0,0 +1,132 @@ +'use server'; + +import * as cron from 'node-cron'; +import db from '@/db/db'; +import { organization as organizationTable } from '@/db/schema/knox/organization'; +import { + searchOrganizations, + Organization, +} 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); + +// CRON 스케줄 (기본: 매일 04:15) +const CRON_STRING = process.env.KNOX_ORGANIZATION_SYNC_CRON || '15 4 * * *'; + +// 애플리케이션 기동 시 최초 한 번 실행 여부 +const DO_FIRST_RUN = process.env.KNOX_ORGANIZATION_SYNC_FIRST_RUN === 'true'; + +async function upsertOrganizations(orgs: Organization[]) { + if (!orgs.length) return; + + const rows = orgs.map((o) => ({ + companyCode: o.companyCode, + departmentCode: o.departmentCode, + companyName: o.companyName, + departmentLevel: o.departmentLevel, + departmentName: o.departmentName, + departmentOrder: o.departmentOrder, + enCompanyName: o.enCompanyName, + enDepartmentName: o.enDepartmentName, + enManagerTitle: o.enManagerTitle, + enSubOrgCode: o.enSubOrgCode, + inDepartmentCode: o.inDepartmentCode, + lowDepartmentYn: o.lowDepartmentYn, + managerId: o.managerId, + managerName: o.managerName, + managerTitle: o.managerTitle, + preferredLanguage: o.preferredLanguage, + subOrgCode: o.subOrgCode, + subOrgName: o.subOrgName, + uprDepartmentCode: o.uprDepartmentCode, + enUprDepartmentName: o.enUprDepartmentName, + uprDepartmentName: o.uprDepartmentName, + hiddenDepartmentYn: o.hiddenDepartmentYn, + corpCode: o.corpCode, + corpName: o.corpName, + enCorpName: o.enCorpName, + raw: o as unknown as Record<string, unknown>, + })); + + await db + .insert(organizationTable) + .values(rows) + .onConflictDoUpdate({ + target: [organizationTable.companyCode, organizationTable.departmentCode], + set: { + companyName: sql.raw('excluded.company_name'), + departmentLevel: sql.raw('excluded.department_level'), + departmentName: sql.raw('excluded.department_name'), + departmentOrder: sql.raw('excluded.department_order'), + enCompanyName: sql.raw('excluded.en_company_name'), + enDepartmentName: sql.raw('excluded.en_department_name'), + enManagerTitle: sql.raw('excluded.en_manager_title'), + enSubOrgCode: sql.raw('excluded.en_sub_org_code'), + inDepartmentCode: sql.raw('excluded.in_department_code'), + lowDepartmentYn: sql.raw('excluded.low_department_yn'), + managerId: sql.raw('excluded.manager_id'), + managerName: sql.raw('excluded.manager_name'), + managerTitle: sql.raw('excluded.manager_title'), + preferredLanguage: sql.raw('excluded.preferred_language'), + subOrgCode: sql.raw('excluded.sub_org_code'), + subOrgName: sql.raw('excluded.sub_org_name'), + uprDepartmentCode: sql.raw('excluded.upr_department_code'), + enUprDepartmentName: sql.raw('excluded.en_upr_department_name'), + uprDepartmentName: sql.raw('excluded.upr_department_name'), + hiddenDepartmentYn: sql.raw('excluded.hidden_department_yn'), + corpCode: sql.raw('excluded.corp_code'), + corpName: sql.raw('excluded.corp_name'), + enCorpName: sql.raw('excluded.en_corp_name'), + raw: sql.raw('excluded.raw'), + updatedAt: sql.raw('CURRENT_TIMESTAMP'), + }, + }); +} + +export async function syncKnoxOrganizations(): Promise<void> { + console.log('[KNOX-SYNC] 조직 동기화 시작'); + + for (const companyCode of COMPANIES) { + try { + let page = 1; + let totalPage = 1; + do { + const resp = await searchOrganizations({ companyCode, page: String(page) }); + + if (resp.result === 'success') { + await upsertOrganizations(resp.organizations); + totalPage = resp.totalPage; + console.log( + `[KNOX-SYNC] 조직 동기화 ${companyCode} ${page}/${totalPage} 페이지 처리` + ); + } else { + console.warn(`[KNOX-SYNC] 조직 동기화 실패: ${companyCode} page ${page}`); + break; + } + + page += 1; + } while (page <= totalPage); + } catch (err) { + console.error(`[KNOX-SYNC] 조직 동기화 오류 (company ${companyCode})`, err); + } + } + + console.log('[KNOX-SYNC] 조직 동기화 완료'); +} + +export async function startKnoxOrganizationSyncScheduler() { + if (DO_FIRST_RUN) { + syncKnoxOrganizations().catch(console.error); + } + + cron.schedule(CRON_STRING, () => { + syncKnoxOrganizations().catch(console.error); + }); + + console.log(`[KNOX-SYNC] 조직 동기화 스케줄러 등록 (${CRON_STRING})`); +}
\ No newline at end of file diff --git a/lib/knox-sync/title-sync-service.ts b/lib/knox-sync/title-sync-service.ts new file mode 100644 index 00000000..e7bc13bd --- /dev/null +++ b/lib/knox-sync/title-sync-service.ts @@ -0,0 +1,70 @@ +'use server'; + +import * as cron from 'node-cron'; +import db from '@/db/db'; +import { title as titleTable } from '@/db/schema/knox/titles'; +import { getTitlesByCompany, Title } from '@/lib/knox-api/employee/employee'; +import { sql } from 'drizzle-orm'; + +const COMPANIES = (process.env.KNOX_COMPANY_CODES || 'P2') + .split(',') + .map((c) => c.trim()) + .filter(Boolean); + +const CRON_STRING = process.env.KNOX_TITLE_SYNC_CRON || '30 4 * * *'; +const DO_FIRST_RUN = process.env.KNOX_TITLE_SYNC_FIRST_RUN === 'true'; + +async function upsertTitles(titles: Title[]) { + if (!titles.length) return; + + const rows = titles.map((t) => ({ + companyCode: t.companyCode, + titleCode: t.titleCode, + titleName: t.titleName, + enTitleName: t.enTitleName, + sortOrder: t.sortOrder, + raw: t as unknown as Record<string, unknown>, + })); + + await db + .insert(titleTable) + .values(rows) + .onConflictDoUpdate({ + target: [titleTable.companyCode, titleTable.titleCode], + set: { + titleName: sql.raw('excluded.title_name'), + enTitleName: sql.raw('excluded.en_title_name'), + sortOrder: sql.raw('excluded.sort_order'), + raw: sql.raw('excluded.raw'), + updatedAt: sql.raw('CURRENT_TIMESTAMP'), + }, + }); +} + +export async function syncKnoxTitles(): Promise<void> { + console.log('[KNOX-SYNC] 직급 동기화 시작'); + + for (const companyCode of COMPANIES) { + try { + const titles = await getTitlesByCompany(companyCode); + await upsertTitles(titles); + console.log(`[KNOX-SYNC] 직급 동기화 완료 - ${companyCode}: ${titles.length}건`); + } catch (err) { + console.error(`[KNOX-SYNC] 직급 동기화 오류 (company ${companyCode})`, err); + } + } + + console.log('[KNOX-SYNC] 직급 동기화 전체 완료'); +} + +export async function startKnoxTitleSyncScheduler() { + if (DO_FIRST_RUN) { + syncKnoxTitles().catch(console.error); + } + + cron.schedule(CRON_STRING, () => { + syncKnoxTitles().catch(console.error); + }); + + console.log(`[KNOX-SYNC] 직급 동기화 스케줄러 등록 (${CRON_STRING})`); +}
\ No newline at end of file |
