import type { WorksmobileComparisonItem } from "../../../lib/adminApi"; export type WorksmobileComparisonFilter = | "works_only" | "baron_only" | "needs_update" | "matched"; export type WorksmobileComparisonSummary = { total: number; matched: number; needsUpdate: number; missingInWorksmobile: number; missingInBaron: number; missingExternalKey: number; }; export type WorksmobileComparisonColumnKey = | "status" | "baronId" | "baron" | "baronOrg" | "worksmobileId" | "externalKey" | "worksmobileDomain" | "worksmobile" | "worksmobileOrg" | "manage"; export type WorksmobileComparisonColumnVisibility = Record< WorksmobileComparisonColumnKey, boolean >; export function getDefaultWorksmobileComparisonColumns(): WorksmobileComparisonColumnVisibility { return { status: true, baronId: false, baron: true, baronOrg: true, worksmobileId: false, externalKey: false, worksmobileDomain: true, worksmobile: true, worksmobileOrg: true, manage: true, }; } export function summarizeWorksmobileComparison( rows: WorksmobileComparisonItem[], ): WorksmobileComparisonSummary { return rows.reduce( (summary, row) => { if (row.status === "matched") { summary.matched += 1; } else if (row.status === "needs_update") { summary.needsUpdate += 1; } else if (row.status === "missing_in_worksmobile") { summary.missingInWorksmobile += 1; } else if (row.status === "missing_in_baron") { summary.missingInBaron += 1; } else if (row.status === "missing_external_key") { summary.missingExternalKey += 1; } return summary; }, { total: rows.length, matched: 0, needsUpdate: 0, missingInWorksmobile: 0, missingInBaron: 0, missingExternalKey: 0, }, ); } export function getWorksmobileComparisonStatusLabel(status: string) { switch (status) { case "matched": return "일치"; case "missing_in_worksmobile": return "WORKS 없음"; case "needs_update": return "업데이트 필요"; case "missing_in_baron": return "Baron 없음"; case "missing_external_key": return "ex_key 없음"; default: return status; } } export function canCreateWorksmobileRow(row: WorksmobileComparisonItem) { return row.status === "missing_in_worksmobile" && Boolean(row.baronId); } const immutableWorksmobileAccountEmails = new Set([ "cyhan@samaneng.com", "cyhan1@hanmaceng.co.kr", "cyhan2@baroncs.co.kr", "cyhan3@brsw.kr", "su-@samaneng.com", ]); const hiddenWorksmobileMemberEmails = new Set([ "su-@samaneng.com", "cyhan1@hanmaceng.co.kr", "cyhan2@baroncs.co.kr", "cyhan3@brsw.kr", ]); function normalizeWorksmobileEmail(email?: string) { return email?.trim().toLowerCase() ?? ""; } export function isImmutableWorksmobileAccount(row: WorksmobileComparisonItem) { return ( row.resourceType === "USER" && immutableWorksmobileAccountEmails.has( normalizeWorksmobileEmail(row.worksmobileEmail), ) ); } export function isHiddenWorksmobileMember(row: WorksmobileComparisonItem) { if (row.resourceType !== "USER") { return false; } return [row.worksmobileEmail, row.baronEmail].some((email) => hiddenWorksmobileMemberEmails.has(normalizeWorksmobileEmail(email)), ); } export function filterVisibleWorksmobileComparisonRows( rows: WorksmobileComparisonItem[], ) { return rows.filter((row) => !isHiddenWorksmobileMember(row)); } export function getWorksmobileRowSelectionKey(row: WorksmobileComparisonItem) { if (row.baronId) { return `${row.resourceType}:baron:${row.baronId}`; } if (row.worksmobileId) { return `${row.resourceType}:works:${row.worksmobileId}`; } if (row.externalKey) { return `${row.resourceType}:external:${row.externalKey}`; } return ""; } export function canSelectWorksmobileRow(row: WorksmobileComparisonItem) { return ( Boolean(getWorksmobileRowSelectionKey(row)) && !isImmutableWorksmobileAccount(row) ); } export function getWorksmobileSelectedActionIds( rows: WorksmobileComparisonItem[], selectedKeys: string[], ) { const selected = new Set(selectedKeys); return rows .filter((row) => selected.has(getWorksmobileRowSelectionKey(row))) .map((row) => row.baronId) .filter((id): id is string => Boolean(id)); } export function getWorksmobileSelectedMissingExternalKeyOrgUnitIds( rows: WorksmobileComparisonItem[], selectedKeys: string[], ) { return getWorksmobileSelectedWorksOnlyOrgUnitIds(rows, selectedKeys).filter( (id) => rows.some( (row) => row.worksmobileId === id && row.status === "missing_external_key", ), ); } export function getWorksmobileSelectedWorksOnlyOrgUnitIds( rows: WorksmobileComparisonItem[], selectedKeys: string[], ) { const selected = new Set(selectedKeys); return rows .filter( (row) => row.resourceType === "GROUP" && (row.status === "missing_external_key" || row.status === "missing_in_baron") && selected.has(getWorksmobileRowSelectionKey(row)), ) .map((row) => row.worksmobileId) .filter((id): id is string => Boolean(id)); } const worksmobileComparisonSearchFields: Array< keyof WorksmobileComparisonItem > = [ "baronId", "baronSlug", "baronName", "baronEmail", "baronPrimaryOrgId", "baronPrimaryOrgSlug", "baronPrimaryOrgName", "baronParentId", "baronParentSlug", "baronParentName", "worksmobileId", "externalKey", "worksmobileName", "worksmobileEmail", "worksmobileLevelId", "worksmobileLevelName", "worksmobileTask", "worksmobileDomainId", "worksmobileDomainName", "worksmobilePrimaryOrgId", "worksmobilePrimaryOrgName", "worksmobilePrimaryOrgPositionId", "worksmobilePrimaryOrgPositionName", "baronParentWorksmobileId", "baronParentWorksmobileName", "baronParentWorksmobileEmail", "worksmobileParentId", "worksmobileParentName", "worksmobileParentEmail", "worksmobileParentExternalKey", ]; export function filterWorksmobileComparisonRowsBySearch( rows: WorksmobileComparisonItem[], search: string, ) { const keyword = search.trim().toLowerCase(); if (!keyword) { return rows; } return rows.filter((row) => worksmobileComparisonSearchFields.some((field) => { const value = row[field]; if (value === undefined || value === null) { return false; } return String(value).toLowerCase().includes(keyword); }), ); } export function filterWorksmobileComparisonRows( rows: WorksmobileComparisonItem[], filters: WorksmobileComparisonFilter[], onlyMissingExternalKey = false, ) { const allowedStatuses = new Set( filters.flatMap((filter) => worksmobileFilterStatuses[filter]), ); if (filters.includes("works_only")) { if (onlyMissingExternalKey) { allowedStatuses.delete("missing_in_baron"); } allowedStatuses.add("missing_external_key"); } return rows.filter((row) => allowedStatuses.has(row.status)); } export function formatWorksmobilePersonName(row: WorksmobileComparisonItem) { return [ row.worksmobileName, row.worksmobileLevelName ?? row.worksmobileLevelId, ] .filter(Boolean) .join(" "); } export function formatWorksmobileOrgDetails(row: WorksmobileComparisonItem) { const details: string[] = []; const position = row.worksmobilePrimaryOrgPositionName ?? row.worksmobilePrimaryOrgPositionId; if (position) { details.push(`직책 ${position}`); } if (row.worksmobileTask) { details.push(`직무 ${row.worksmobileTask}`); } if (typeof row.worksmobilePrimaryOrgIsManager === "boolean") { details.push(row.worksmobilePrimaryOrgIsManager ? "조직장" : "조직장 아님"); } return details; } export function formatWorksmobileUpdateDetails(row: WorksmobileComparisonItem) { if (row.status === "missing_in_worksmobile" && row.worksmobileLastError) { return [`최근 실패: ${row.worksmobileLastError}`]; } if (row.status !== "needs_update") { return []; } const details: string[] = []; const baronName = row.baronName?.trim(); const worksmobileName = row.worksmobileName?.trim(); if (baronName && worksmobileName && baronName !== worksmobileName) { details.push(`이름: ${worksmobileName} -> ${baronName}`); } if (row.resourceType === "USER") { const expectedExternalKey = row.baronId?.trim() ?? ""; const actualExternalKey = row.externalKey?.trim() ?? ""; if (expectedExternalKey && expectedExternalKey !== actualExternalKey) { details.push( `external_key: ${actualExternalKey || "없음"} -> ${expectedExternalKey}`, ); } const expectedEmail = row.baronEmail?.trim().toLowerCase() ?? ""; const actualEmail = row.worksmobileEmail?.trim().toLowerCase() ?? ""; if (expectedEmail && actualEmail && expectedEmail !== actualEmail) { details.push(`이메일: ${actualEmail} -> ${expectedEmail}`); } return details; } const expectedParent = row.baronParentWorksmobileName ?? row.baronParentName ?? row.baronParentWorksmobileId ?? row.baronParentId ?? ""; const actualParent = row.worksmobileParentName ?? row.worksmobileParentExternalKey ?? row.worksmobileParentId ?? ""; const expectedParentKey = row.baronParentWorksmobileId ?? row.baronParentId ?? ""; const actualParentKey = row.worksmobileParentId ?? row.worksmobileParentExternalKey ?? ""; if (expectedParentKey !== actualParentKey) { details.push( `상위: ${actualParent || "없음"} -> ${expectedParent || "없음"}`, ); } return details; } export function buildWorksmobilePasswordManageUrl({ tenantId, domainId, userIdNo, }: { tenantId?: string; domainId?: number; userIdNo?: string; }) { const normalizedTenantId = tenantId?.trim(); const normalizedUserIdNo = userIdNo?.trim(); if ( !normalizedTenantId || !domainId || domainId <= 0 || !normalizedUserIdNo ) { return ""; } const url = new URL("https://auth.worksmobile.com/integrate/password/manage"); url.searchParams.set("usage", "admin"); url.searchParams.set("targetUserTenantId", normalizedTenantId); url.searchParams.set("targetUserDomainId", String(domainId)); url.searchParams.set("targetUserIdNo", normalizedUserIdNo); url.searchParams.set( "accessUrl", "https://admin.worksmobile.com/assets/self-close.html", ); return url.toString(); } export function canOpenWorksmobilePasswordManage( row: WorksmobileComparisonItem, tenantId?: string, ) { return ( row.resourceType === "USER" && !isImmutableWorksmobileAccount(row) && Boolean( buildWorksmobilePasswordManageUrl({ tenantId, domainId: row.worksmobileDomainId, userIdNo: row.worksmobileId, }), ) ); } export const comparisonFilterOptions: Array<{ value: WorksmobileComparisonFilter; label: string; }> = [ { value: "baron_only", label: "바론에만 있음" }, { value: "needs_update", label: "업데이트 필요" }, { value: "works_only", label: "웍스에만 있음" }, { value: "matched", label: "양쪽 다 있음" }, ]; export const userFilterOptions = comparisonFilterOptions; export function getDefaultUserComparisonFilters(): WorksmobileComparisonFilter[] { return ["baron_only", "needs_update", "works_only"]; } export function getDefaultGroupComparisonFilters(): WorksmobileComparisonFilter[] { return ["baron_only", "needs_update", "works_only"]; } const worksmobileFilterStatuses: Record = { baron_only: ["missing_in_worksmobile"], needs_update: ["needs_update"], works_only: ["missing_in_baron"], matched: ["matched"], };