1
0
forked from baron/baron-sso

Merge branch 'dev' into add/e2e_test

This commit is contained in:
2026-05-28 11:57:10 +09:00
3 changed files with 191 additions and 76 deletions

View File

@@ -23,7 +23,6 @@ import { ScrollArea } from "../../../components/ui/scroll-area";
import {
type BulkUserItem,
type BulkUserResult,
type TenantSummary,
bulkCreateUsers,
createTenant,
fetchAllTenants,
@@ -41,6 +40,7 @@ import {
type HanmacImportEmailPreview,
buildHanmacImportEmailPreview,
} from "../utils/hanmacImportEmail";
import { applyGeneralPlanningOfficePriority } from "../utils/generalPlanningOfficePriority";
interface UserBulkUploadModalProps {
onSuccess?: () => void;
@@ -125,19 +125,6 @@ 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";
@@ -292,68 +279,7 @@ export function UserBulkUploadModal({
}
return previewData.map((user, index) => {
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 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 finalUser = applyGeneralPlanningOfficePriority(user, tenants);
const key = tenantImportKeyFromUser(finalUser);
const resolvedTenant = key ? tenantByKey.get(key) : undefined;

View File

@@ -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",
});
});
});

View File

@@ -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;
}