forked from baron/baron-sso
동기화 기초구조 마련
This commit is contained in:
@@ -206,7 +206,10 @@ export function UserBulkUploadModal({ onSuccess }: UserBulkUploadModalProps) {
|
||||
|
||||
const resolveUserImportTenants = async () => {
|
||||
const tenants = tenantQuery.data?.items ?? [];
|
||||
const tenantSlugByKey = new Map<string, string>();
|
||||
const tenantByKey = new Map<
|
||||
string,
|
||||
{ id: string; slug: string; emailDomain: string }
|
||||
>();
|
||||
|
||||
for (const preview of tenantPreviewRows) {
|
||||
const key = tenantImportKeyFromRow(preview.row);
|
||||
@@ -215,7 +218,11 @@ export function UserBulkUploadModal({ onSuccess }: UserBulkUploadModalProps) {
|
||||
if (selected !== "__create__") {
|
||||
const tenant = tenants.find((item) => item.id === selected);
|
||||
if (tenant) {
|
||||
tenantSlugByKey.set(key, tenant.slug);
|
||||
tenantByKey.set(key, {
|
||||
id: tenant.id,
|
||||
slug: tenant.slug,
|
||||
emailDomain: preview.row.emailDomain,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -231,27 +238,33 @@ export function UserBulkUploadModal({ onSuccess }: UserBulkUploadModalProps) {
|
||||
domains: splitTenantImportDomains(preview.row.emailDomain),
|
||||
status: "active",
|
||||
});
|
||||
tenantSlugByKey.set(key, created.slug);
|
||||
tenantByKey.set(key, {
|
||||
id: created.id,
|
||||
slug: created.slug,
|
||||
emailDomain: preview.row.emailDomain,
|
||||
});
|
||||
}
|
||||
|
||||
return previewData.map((user, index) => {
|
||||
const key = tenantImportKeyFromUser(user);
|
||||
const tenantSlug = key ? tenantSlugByKey.get(key) : user.tenantSlug;
|
||||
const resolvedTenant = key ? tenantByKey.get(key) : undefined;
|
||||
const emailPreview = hanmacEmailPreviews[index];
|
||||
const { tenantImport: _tenantImport, ...payload } = user;
|
||||
return {
|
||||
...payload,
|
||||
email: emailPreview?.finalEmail ?? payload.email,
|
||||
tenantSlug,
|
||||
tenantId: resolvedTenant?.id ?? payload.tenantId,
|
||||
tenantSlug: resolvedTenant?.slug ?? payload.tenantSlug,
|
||||
emailDomain: resolvedTenant?.emailDomain ?? payload.emailDomain,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const downloadTemplate = () => {
|
||||
const headers =
|
||||
"email,name,phone,role,tenant_slug,department,grade,position,jobTitle,employee_id";
|
||||
"email,name,phone,role,tenant_slug,department,grade,position,jobTitle,employee_id,tenant_slug1,department1,grade1,position1,jobTitle1,employee_id1";
|
||||
const example =
|
||||
"user1@example.com,홍길동,010-1234-5678,user,tenant-slug,개발팀,수석,팀장,프론트엔드,EMP001";
|
||||
"user1@example.com,홍길동,010-1234-5678,user,tenant-slug,개발팀,수석,팀장,프론트엔드,EMP001,second-tenant,센터,책임,,Architecture,EMP002";
|
||||
const blob = new Blob([`${headers}\n${example}`], {
|
||||
type: "text/csv;charset=utf-8;",
|
||||
});
|
||||
|
||||
@@ -82,7 +82,9 @@ test@test.com,Test,local-tenant-id,missing-slug,Missing Tenant,COMPANY,parent-sl
|
||||
const result = parseUserCSV(csv);
|
||||
|
||||
expect(result[0]).toMatchObject({
|
||||
tenantId: "local-tenant-id",
|
||||
tenantSlug: "missing-slug",
|
||||
emailDomain: "missing.example.com",
|
||||
tenantImport: {
|
||||
sourceTenantId: "local-tenant-id",
|
||||
slug: "missing-slug",
|
||||
@@ -94,4 +96,36 @@ test@test.com,Test,local-tenant-id,missing-slug,Missing Tenant,COMPANY,parent-sl
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse one nullable additional appointment from numbered columns", () => {
|
||||
const csv = `email,name,phone,role,tenant_slug,department,grade,position,jobTitle,employee_id,tenant_slug1,department1,grade1,position1,jobTitle1,employee_id1
|
||||
dual@test.com,Dual User,010-0000-0000,user,primary-tenant,개발팀,책임,팀장,Backend,EMP001,second-tenant,센터,수석,,Architecture,EMP002
|
||||
nullable@test.com,Nullable User,010-1111-1111,user,primary-tenant,개발팀,책임,팀장,Backend,EMP003,,,,,,`;
|
||||
|
||||
const result = parseUserCSV(csv);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toMatchObject({
|
||||
tenantSlug: "primary-tenant",
|
||||
department: "개발팀",
|
||||
grade: "책임",
|
||||
position: "팀장",
|
||||
jobTitle: "Backend",
|
||||
metadata: {
|
||||
employee_id: "EMP001",
|
||||
},
|
||||
additionalAppointments: [
|
||||
{
|
||||
tenantSlug: "second-tenant",
|
||||
department: "센터",
|
||||
grade: "수석",
|
||||
jobTitle: "Architecture",
|
||||
metadata: {
|
||||
employee_id: "EMP002",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result[1].additionalAppointments).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BulkUserItem } from "../../../lib/adminApi";
|
||||
import type { BulkUserAppointment, BulkUserItem } from "../../../lib/adminApi";
|
||||
|
||||
export function parseUserCSV(text: string): BulkUserItem[] {
|
||||
const records = parseCSVRecords(text.replace(/^\uFEFF/, ""));
|
||||
@@ -15,6 +15,11 @@ export function parseUserCSV(text: string): BulkUserItem[] {
|
||||
const item: Partial<BulkUserItem> & { metadata: Record<string, string> } = {
|
||||
metadata: {},
|
||||
};
|
||||
const additionalAppointment: BulkUserAppointment & {
|
||||
metadata: Record<string, string>;
|
||||
} = {
|
||||
metadata: {},
|
||||
};
|
||||
|
||||
for (let index = 0; index < headers.length; index++) {
|
||||
const header = headers[index];
|
||||
@@ -38,6 +43,7 @@ export function parseUserCSV(text: string): BulkUserItem[] {
|
||||
slug: value,
|
||||
};
|
||||
} else if (header === "tenant_id") {
|
||||
item.tenantId = value;
|
||||
item.tenantImport = {
|
||||
...(item.tenantImport ?? {}),
|
||||
sourceTenantId: value,
|
||||
@@ -73,6 +79,7 @@ export function parseUserCSV(text: string): BulkUserItem[] {
|
||||
memo: value,
|
||||
};
|
||||
} else if (header === "email_domain" || header === "tenant_domain") {
|
||||
item.emailDomain = value;
|
||||
item.tenantImport = {
|
||||
...(item.tenantImport ?? {}),
|
||||
emailDomain: value,
|
||||
@@ -85,6 +92,20 @@ export function parseUserCSV(text: string): BulkUserItem[] {
|
||||
item.position = value;
|
||||
} else if (header === "jobtitle") {
|
||||
item.jobTitle = value;
|
||||
} else if (header === "employee_id") {
|
||||
item.metadata.employee_id = value;
|
||||
} else if (header === "tenant_slug1") {
|
||||
additionalAppointment.tenantSlug = value;
|
||||
} else if (header === "department1") {
|
||||
additionalAppointment.department = value;
|
||||
} else if (header === "grade1") {
|
||||
additionalAppointment.grade = value;
|
||||
} else if (header === "position1") {
|
||||
additionalAppointment.position = value;
|
||||
} else if (header === "jobtitle1") {
|
||||
additionalAppointment.jobTitle = value;
|
||||
} else if (header === "employee_id1") {
|
||||
additionalAppointment.metadata.employee_id = value;
|
||||
} else if (header === "lastname") {
|
||||
item.metadata.naverworks_last_name = value;
|
||||
} else if (header === "firstname") {
|
||||
@@ -149,6 +170,11 @@ export function parseUserCSV(text: string): BulkUserItem[] {
|
||||
}
|
||||
|
||||
applyNaverWorksFallbacks(item);
|
||||
if (additionalAppointment.tenantSlug) {
|
||||
item.additionalAppointments = [
|
||||
cleanAdditionalAppointment(additionalAppointment),
|
||||
];
|
||||
}
|
||||
|
||||
if (item.email && item.name) {
|
||||
data.push(item as BulkUserItem);
|
||||
@@ -158,6 +184,31 @@ export function parseUserCSV(text: string): BulkUserItem[] {
|
||||
return data;
|
||||
}
|
||||
|
||||
function cleanAdditionalAppointment(
|
||||
appointment: BulkUserAppointment & { metadata: Record<string, string> },
|
||||
) {
|
||||
const metadata =
|
||||
Object.keys(appointment.metadata).length > 0
|
||||
? appointment.metadata
|
||||
: undefined;
|
||||
return {
|
||||
...(appointment.tenantId ? { tenantId: appointment.tenantId } : {}),
|
||||
...(appointment.tenantSlug ? { tenantSlug: appointment.tenantSlug } : {}),
|
||||
...(appointment.tenantName ? { tenantName: appointment.tenantName } : {}),
|
||||
...(appointment.isPrimary !== undefined
|
||||
? { isPrimary: appointment.isPrimary }
|
||||
: {}),
|
||||
...(appointment.isOwner !== undefined
|
||||
? { isOwner: appointment.isOwner }
|
||||
: {}),
|
||||
...(appointment.department ? { department: appointment.department } : {}),
|
||||
...(appointment.grade ? { grade: appointment.grade } : {}),
|
||||
...(appointment.position ? { position: appointment.position } : {}),
|
||||
...(appointment.jobTitle ? { jobTitle: appointment.jobTitle } : {}),
|
||||
...(metadata ? { metadata } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeHeader(header: string) {
|
||||
return header
|
||||
.trim()
|
||||
|
||||
Reference in New Issue
Block a user