forked from baron/baron-sso
525 lines
14 KiB
TypeScript
525 lines
14 KiB
TypeScript
import type { WorksmobileComparisonItem } from "../../../lib/adminApi";
|
|
|
|
export type WorksmobileComparisonFilter =
|
|
| "works_only"
|
|
| "baron_only"
|
|
| "needs_update"
|
|
| "matched";
|
|
|
|
export type WorksmobileAccountStatusFilter =
|
|
| "all"
|
|
| "active"
|
|
| "invited"
|
|
| "suspended"
|
|
| "inactive"
|
|
| "deleted";
|
|
|
|
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<WorksmobileComparisonSummary>(
|
|
(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 getWorksmobileSelectedCreateUserIds(
|
|
rows: WorksmobileComparisonItem[],
|
|
selectedKeys: string[],
|
|
) {
|
|
const selected = new Set(selectedKeys);
|
|
return rows
|
|
.filter(
|
|
(row) =>
|
|
row.resourceType === "USER" &&
|
|
row.status === "missing_in_worksmobile" &&
|
|
selected.has(getWorksmobileRowSelectionKey(row)),
|
|
)
|
|
.map((row) => row.baronId)
|
|
.filter((id): id is string => Boolean(id));
|
|
}
|
|
|
|
export function getWorksmobileSelectedUpdateUserIds(
|
|
rows: WorksmobileComparisonItem[],
|
|
selectedKeys: string[],
|
|
) {
|
|
const selected = new Set(selectedKeys);
|
|
return rows
|
|
.filter(
|
|
(row) =>
|
|
row.resourceType === "USER" &&
|
|
row.status === "needs_update" &&
|
|
selected.has(getWorksmobileRowSelectionKey(row)),
|
|
)
|
|
.map((row) => row.baronId)
|
|
.filter((id): id is string => Boolean(id));
|
|
}
|
|
|
|
export function formatWorksmobileSelectionFailureDescription(
|
|
successCount: number,
|
|
failures: string[],
|
|
) {
|
|
const summary = `성공 ${successCount}건, 실패 ${failures.length}건`;
|
|
const visibleFailures = failures.slice(0, 3);
|
|
if (failures.length <= visibleFailures.length) {
|
|
return [summary, ...visibleFailures].join("\n");
|
|
}
|
|
return [
|
|
summary,
|
|
...visibleFailures,
|
|
`외 ${failures.length - visibleFailures.length}건 실패`,
|
|
].join("\n");
|
|
}
|
|
|
|
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",
|
|
"worksmobileAccountStatus",
|
|
"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,
|
|
accountStatus: WorksmobileAccountStatusFilter = "all",
|
|
) {
|
|
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) => {
|
|
if (accountStatus !== "all") {
|
|
return row.worksmobileAccountStatus === accountStatus;
|
|
}
|
|
if (!allowedStatuses.has(row.status)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
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}`);
|
|
}
|
|
const expectedPhone = row.baronPhone?.trim() ?? "";
|
|
const actualPhone = row.worksmobilePhone?.trim() ?? "";
|
|
if (expectedPhone && actualPhone && expectedPhone !== actualPhone) {
|
|
details.push(`전화번호: ${actualPhone} -> ${expectedPhone}`);
|
|
}
|
|
const expectedEmployeeNumber = row.baronEmployeeNumber?.trim() ?? "";
|
|
const actualEmployeeNumber = row.worksmobileEmployeeNumber?.trim() ?? "";
|
|
if (
|
|
expectedEmployeeNumber &&
|
|
actualEmployeeNumber &&
|
|
expectedEmployeeNumber !== actualEmployeeNumber
|
|
) {
|
|
details.push(
|
|
`사번: ${actualEmployeeNumber} -> ${expectedEmployeeNumber}`,
|
|
);
|
|
}
|
|
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 const worksmobileAccountStatusFilterOptions: Array<{
|
|
value: WorksmobileAccountStatusFilter;
|
|
label: string;
|
|
}> = [
|
|
{ value: "all", label: "WORKS 전체" },
|
|
{ value: "active", label: "active" },
|
|
{ value: "invited", label: "invited" },
|
|
{ value: "suspended", label: "suspended" },
|
|
{ value: "inactive", label: "inactive" },
|
|
{ value: "deleted", label: "deleted" },
|
|
];
|
|
|
|
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<WorksmobileComparisonFilter, string[]> =
|
|
{
|
|
baron_only: ["missing_in_worksmobile"],
|
|
needs_update: ["needs_update"],
|
|
works_only: ["missing_in_baron"],
|
|
matched: ["matched"],
|
|
};
|