seahaven-door-unlock-api/lambda/blf-sync/department.ts
Adam Moussa bbc113497a
Some checks failed
Deploy / deploy (push) Has been cancelled
feat(3cx): sync office department blfs via xapi (PLAT-116) (#80)
* feat(3cx): sync office department BLFs via XAPI

Keep unlock and lockdown keys in the templates and write colleague plus shared-parking BLFs per extension so phones skip their own line.

* fix(3cx): preserve parking BLF IDs and fail the job on PATCH errors
2026-08-27 14:58:55 -04:00

95 lines
2.3 KiB
TypeScript

export interface UserGroup {
Id?: number;
Name?: string;
}
export interface SyncUser {
Id: number;
Number: string;
FirstName?: string;
LastName?: string;
PrimaryGroupId?: number;
Groups?: UserGroup[];
Blfs?: string;
Phones?: Array<{
TemplateName?: string;
Name?: string;
}>;
}
const QUEUE_EXTENSION_MIN = 800;
export const ALWAYS_INCLUDE_EXTENSIONS = ["100"] as const;
export interface ColleagueRef {
id: number;
number: string;
firstName: string;
lastName: string;
}
export function splitPeerName(name: string | undefined): { firstName: string; lastName: string } {
const parts = (name ?? "").trim().split(/\s+/).filter(Boolean);
if (parts.length === 0) {
return { firstName: "", lastName: "" };
}
if (parts.length === 1) {
return { firstName: parts[0], lastName: "" };
}
return { firstName: parts.slice(0, -1).join(" "), lastName: parts[parts.length - 1] };
}
export function addAlwaysIncludedColleagues(
colleagues: ColleagueRef[],
extras: ColleagueRef[]
): ColleagueRef[] {
const next = [...colleagues];
for (const extra of extras) {
if (!next.some((c) => c.number === extra.number)) {
next.push(extra);
}
}
return next;
}
export function isEligibleColleague(user: SyncUser): boolean {
if (!/^\d+$/.test(user.Number)) {
return false;
}
if (Number.parseInt(user.Number, 10) >= QUEUE_EXTENSION_MIN) {
return false;
}
const first = (user.FirstName ?? "").trim();
const last = (user.LastName ?? "").trim();
if (!first && !last) {
return false;
}
const full = `${first} ${last}`.trim().toLowerCase();
if (full === "voicemail") {
return false;
}
return true;
}
export function primaryDepartmentName(user: SyncUser): string | undefined {
const groups = user.Groups ?? [];
const named = groups.find((g) => (g.Name ?? "").toUpperCase() !== "DEFAULT" && (g.Name ?? "").trim() !== "");
if (named?.Name) {
return named.Name;
}
return undefined;
}
export function groupUsersByDepartment(users: SyncUser[]): Map<string, SyncUser[]> {
const byDept = new Map<string, SyncUser[]>();
for (const user of users) {
const dept = primaryDepartmentName(user);
if (!dept) {
continue;
}
const list = byDept.get(dept) ?? [];
list.push(user);
byDept.set(dept, list);
}
return byDept;
}