forked from baron/baron-sso
Merge branch 'dev' into add/e2e_test
This commit is contained in:
@@ -23,7 +23,6 @@ import { ScrollArea } from "../../../components/ui/scroll-area";
|
|||||||
import {
|
import {
|
||||||
type BulkUserItem,
|
type BulkUserItem,
|
||||||
type BulkUserResult,
|
type BulkUserResult,
|
||||||
type TenantSummary,
|
|
||||||
bulkCreateUsers,
|
bulkCreateUsers,
|
||||||
createTenant,
|
createTenant,
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
@@ -41,6 +40,7 @@ import {
|
|||||||
type HanmacImportEmailPreview,
|
type HanmacImportEmailPreview,
|
||||||
buildHanmacImportEmailPreview,
|
buildHanmacImportEmailPreview,
|
||||||
} from "../utils/hanmacImportEmail";
|
} from "../utils/hanmacImportEmail";
|
||||||
|
import { applyGeneralPlanningOfficePriority } from "../utils/generalPlanningOfficePriority";
|
||||||
|
|
||||||
interface UserBulkUploadModalProps {
|
interface UserBulkUploadModalProps {
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
@@ -125,19 +125,6 @@ function hanmacEmailStatusClass(preview?: HanmacImportEmailPreview) {
|
|||||||
return "text-muted-foreground";
|
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 = () => {
|
export const downloadUserTemplate = () => {
|
||||||
const headers =
|
const headers =
|
||||||
"email,name,phone,role,tenant_slug,department,grade,position,jobTitle,employee_id,tenant_slug1,department1,grade1,position1,jobTitle1,employee_id1";
|
"email,name,phone,role,tenant_slug,department,grade,position,jobTitle,employee_id,tenant_slug1,department1,grade1,position1,jobTitle1,employee_id1";
|
||||||
@@ -292,68 +279,7 @@ export function UserBulkUploadModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return previewData.map((user, index) => {
|
return previewData.map((user, index) => {
|
||||||
let finalUser = { ...user };
|
const finalUser = applyGeneralPlanningOfficePriority(user, tenants);
|
||||||
|
|
||||||
// [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 as string) || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
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 key = tenantImportKeyFromUser(finalUser);
|
||||||
const resolvedTenant = key ? tenantByKey.get(key) : undefined;
|
const resolvedTenant = key ? tenantByKey.get(key) : undefined;
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import type { BulkUserItem, TenantSummary } from "../../../lib/adminApi";
|
||||||
|
import { applyGeneralPlanningOfficePriority } from "./generalPlanningOfficePriority";
|
||||||
|
|
||||||
|
function tenant(
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
slug: string,
|
||||||
|
parentId?: string,
|
||||||
|
): TenantSummary {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
type: "COMPANY",
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
description: "",
|
||||||
|
status: "active",
|
||||||
|
parentId,
|
||||||
|
memberCount: 0,
|
||||||
|
createdAt: "",
|
||||||
|
updatedAt: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("applyGeneralPlanningOfficePriority", () => {
|
||||||
|
it("promotes the general planning office appointment and preserves string employee IDs", () => {
|
||||||
|
const user: BulkUserItem = {
|
||||||
|
email: "dual@test.com",
|
||||||
|
name: "Dual User",
|
||||||
|
tenantSlug: "hanmac-tech",
|
||||||
|
department: "개발팀",
|
||||||
|
grade: "책임",
|
||||||
|
position: "팀장",
|
||||||
|
jobTitle: "Backend",
|
||||||
|
metadata: {
|
||||||
|
employee_id: "EMP001",
|
||||||
|
},
|
||||||
|
additionalAppointments: [
|
||||||
|
{
|
||||||
|
tenantSlug: "planning-team",
|
||||||
|
tenantName: "경영기획팀",
|
||||||
|
department: "센터",
|
||||||
|
grade: "수석",
|
||||||
|
jobTitle: "Architecture",
|
||||||
|
metadata: {
|
||||||
|
employee_id: "EMP002",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = applyGeneralPlanningOfficePriority(user, [
|
||||||
|
tenant("gpo", "총괄기획실", "gpo"),
|
||||||
|
tenant("planning", "경영기획팀", "planning-team", "gpo"),
|
||||||
|
tenant("tech", "한맥기술", "hanmac-tech"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.tenantSlug).toBe("planning-team");
|
||||||
|
expect(result.department).toBe("센터");
|
||||||
|
expect(result.grade).toBe("수석");
|
||||||
|
expect(result.jobTitle).toBe("Architecture");
|
||||||
|
expect(result.metadata.employee_id).toBe("EMP002");
|
||||||
|
expect(result.additionalAppointments?.[0]).toMatchObject({
|
||||||
|
tenantSlug: "hanmac-tech",
|
||||||
|
department: "개발팀",
|
||||||
|
grade: "책임",
|
||||||
|
position: "팀장",
|
||||||
|
jobTitle: "Backend",
|
||||||
|
metadata: {
|
||||||
|
employee_id: "EMP001",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not write non-string employee IDs into string metadata", () => {
|
||||||
|
const user: BulkUserItem = {
|
||||||
|
email: "dual@test.com",
|
||||||
|
name: "Dual User",
|
||||||
|
tenantSlug: "hanmac-tech",
|
||||||
|
metadata: {
|
||||||
|
employee_id: "EMP001",
|
||||||
|
},
|
||||||
|
additionalAppointments: [
|
||||||
|
{
|
||||||
|
tenantSlug: "gpo",
|
||||||
|
tenantName: "총괄기획실",
|
||||||
|
metadata: {
|
||||||
|
employee_id: 1002,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = applyGeneralPlanningOfficePriority(user, [
|
||||||
|
tenant("gpo", "총괄기획실", "gpo"),
|
||||||
|
tenant("tech", "한맥기술", "hanmac-tech"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.tenantSlug).toBe("gpo");
|
||||||
|
expect(result.metadata.employee_id).toBeUndefined();
|
||||||
|
expect(result.additionalAppointments?.[0].metadata).toMatchObject({
|
||||||
|
employee_id: "EMP001",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import type { BulkUserItem, TenantSummary } from "../../../lib/adminApi";
|
||||||
|
|
||||||
|
export function applyGeneralPlanningOfficePriority(
|
||||||
|
user: BulkUserItem,
|
||||||
|
tenants: TenantSummary[],
|
||||||
|
): BulkUserItem {
|
||||||
|
const firstAdditional = user.additionalAppointments?.[0];
|
||||||
|
const secondarySlug = firstAdditional?.tenantSlug;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!firstAdditional ||
|
||||||
|
!secondarySlug ||
|
||||||
|
!isUnderGeneralPlanningOffice(secondarySlug, tenants) ||
|
||||||
|
isUnderGeneralPlanningOffice(user.tenantSlug || "", tenants)
|
||||||
|
) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryEmployeeId = stringValue(user.metadata.employee_id);
|
||||||
|
const secondaryEmployeeId = stringValue(
|
||||||
|
firstAdditional.metadata?.employee_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const metadata = { ...user.metadata };
|
||||||
|
if (secondaryEmployeeId) {
|
||||||
|
metadata.employee_id = secondaryEmployeeId;
|
||||||
|
} else {
|
||||||
|
delete metadata.employee_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryAppointmentMetadata: Record<string, unknown> = {
|
||||||
|
...firstAdditional.metadata,
|
||||||
|
};
|
||||||
|
if (primaryEmployeeId) {
|
||||||
|
primaryAppointmentMetadata.employee_id = primaryEmployeeId;
|
||||||
|
} else {
|
||||||
|
delete primaryAppointmentMetadata.employee_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
tenantSlug: firstAdditional.tenantSlug,
|
||||||
|
tenantImport: user.tenantImport
|
||||||
|
? {
|
||||||
|
...user.tenantImport,
|
||||||
|
slug: firstAdditional.tenantSlug || "",
|
||||||
|
name: firstAdditional.tenantName || firstAdditional.tenantSlug || "",
|
||||||
|
}
|
||||||
|
: user.tenantImport,
|
||||||
|
department: firstAdditional.department,
|
||||||
|
grade: firstAdditional.grade,
|
||||||
|
position: firstAdditional.position,
|
||||||
|
jobTitle: firstAdditional.jobTitle,
|
||||||
|
metadata,
|
||||||
|
additionalAppointments: [
|
||||||
|
{
|
||||||
|
...firstAdditional,
|
||||||
|
tenantSlug: user.tenantSlug,
|
||||||
|
department: user.department,
|
||||||
|
grade: user.grade,
|
||||||
|
position: user.position,
|
||||||
|
jobTitle: user.jobTitle,
|
||||||
|
metadata: primaryAppointmentMetadata,
|
||||||
|
},
|
||||||
|
...(user.additionalAppointments?.slice(1) ?? []),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUnderGeneralPlanningOffice(
|
||||||
|
tenantSlug: string,
|
||||||
|
tenants: TenantSummary[],
|
||||||
|
): boolean {
|
||||||
|
let current = tenants.find((tenant) => tenant.slug === tenantSlug);
|
||||||
|
while (current) {
|
||||||
|
if (current.name === "총괄기획실") return true;
|
||||||
|
if (!current.parentId) break;
|
||||||
|
current = tenants.find((tenant) => tenant.id === current?.parentId);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringValue(value: unknown) {
|
||||||
|
return typeof value === "string" ? value : undefined;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user