diff --git a/adminfront/src/features/tenants/utils/tenantCsvImport.ts b/adminfront/src/features/tenants/utils/tenantCsvImport.ts index 8d4746dd..133e1a1c 100644 --- a/adminfront/src/features/tenants/utils/tenantCsvImport.ts +++ b/adminfront/src/features/tenants/utils/tenantCsvImport.ts @@ -579,10 +579,37 @@ function suggestUniqueTenantSlug(value: string, tenants: TenantSummary[]) { } function slugify(value: string) { - return value - .trim() + // 한글 조직명을 영어로 유추하거나 정규화하는 맵 (자주 쓰이는 단어) + const commonMappings: Record = { + 총괄기획실: "gpd", + 기술개발센터: "tdc", + 경영기획: "planning", + 영업: "sales", + 인프라: "infra", + 건설: "construction", + 운영: "ops", + 환경: "env", + 사업: "biz", + 본부: "hq", + 부: "dept", + 팀: "team", + 지원: "support", + }; + + let result = value.trim(); + + // 1. 전체 매칭 확인 + if (commonMappings[result]) { + return commonMappings[result]; + } + + // 2. 부분 단어 치환 및 정규화 + return result .toLowerCase() - .replace(/[^a-z0-9가-힣ㄱ-ㅎㅏ-ㅣ]+/g, "-") + .replace(/[^a-z0-9가-힣ㄱ-ㅎㅏ-ㅣ]+/g, "-") // 특수문자 제거 + .split("-") + .map((part) => commonMappings[part] || part) // 부분 단어 변환 + .join("-") .replace(/^-+|-+$/g, ""); } diff --git a/adminfront/src/features/users/components/UserBulkUploadModal.tsx b/adminfront/src/features/users/components/UserBulkUploadModal.tsx index 10e477e7..dd95fe62 100644 --- a/adminfront/src/features/users/components/UserBulkUploadModal.tsx +++ b/adminfront/src/features/users/components/UserBulkUploadModal.tsx @@ -23,6 +23,7 @@ import { ScrollArea } from "../../../components/ui/scroll-area"; import { type BulkUserItem, type BulkUserResult, + type TenantSummary, bulkCreateUsers, createTenant, fetchAllTenants, @@ -124,6 +125,19 @@ function hanmacEmailStatusClass(preview?: HanmacImportEmailPreview) { return "text-muted-foreground"; } +function isUnderGeneralPlanningOffice( + tenantSlug: string, + tenants: TenantSummary[], +): boolean { + let current = tenants.find((t) => t.slug === tenantSlug); + while (current) { + if (current.name === "총괄기획실") return true; + if (!current.parentId) break; + current = tenants.find((t) => t.id === current?.parentId); + } + return false; +} + export const downloadUserTemplate = () => { const headers = "email,name,phone,role,tenant_slug,department,grade,position,jobTitle,employee_id,tenant_slug1,department1,grade1,position1,jobTitle1,employee_id1"; @@ -278,10 +292,73 @@ export function UserBulkUploadModal({ } return previewData.map((user, index) => { - const key = tenantImportKeyFromUser(user); + let finalUser = { ...user }; + + // [Issue 868] Swap logic for '총괄기획실' (General Planning Office) + // 만약 두 번째 테넌트가 '총괄기획실' 산하이고, 첫 번째 테넌트가 아니면 서로 맞바꿉니다. + if ( + finalUser.additionalAppointments && + finalUser.additionalAppointments.length > 0 + ) { + const firstAdditional = finalUser.additionalAppointments[0]; + const secondarySlug = firstAdditional.tenantSlug; + + if ( + secondarySlug && + isUnderGeneralPlanningOffice(secondarySlug, tenants) + ) { + if (!isUnderGeneralPlanningOffice(finalUser.tenantSlug || "", tenants)) { + // Perform swap + const primary = { + tenantSlug: finalUser.tenantSlug, + tenantImport: finalUser.tenantImport, + department: finalUser.department, + grade: finalUser.grade, + position: finalUser.position, + jobTitle: finalUser.jobTitle, + employee_id: finalUser.metadata?.employee_id, + }; + + finalUser.tenantSlug = firstAdditional.tenantSlug; + if (finalUser.tenantImport) { + finalUser.tenantImport = { + ...finalUser.tenantImport, + slug: firstAdditional.tenantSlug || "", + name: firstAdditional.tenantName || firstAdditional.tenantSlug || "", + }; + } + finalUser.department = firstAdditional.department; + finalUser.grade = firstAdditional.grade; + finalUser.position = firstAdditional.position; + finalUser.jobTitle = firstAdditional.jobTitle; + if (finalUser.metadata) { + finalUser.metadata.employee_id = + firstAdditional.metadata?.employee_id; + } + + finalUser.additionalAppointments = [ + { + ...firstAdditional, + tenantSlug: primary.tenantSlug, + department: primary.department, + grade: primary.grade, + position: primary.position, + jobTitle: primary.jobTitle, + metadata: { + ...firstAdditional.metadata, + employee_id: primary.employee_id, + }, + }, + ...finalUser.additionalAppointments.slice(1), + ]; + } + } + } + + const key = tenantImportKeyFromUser(finalUser); const resolvedTenant = key ? tenantByKey.get(key) : undefined; const emailPreview = hanmacEmailPreviews[index]; - const { tenantImport: _tenantImport, ...payload } = user; + const { tenantImport: _tenantImport, ...payload } = finalUser; return { ...payload, email: emailPreview?.finalEmail ?? payload.email,