From 3e53471f0504842d577dbb15513da0c1d0016063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Wed, 1 Apr 2026 07:38:03 +0200 Subject: [PATCH] refactor(api): split resource read models --- .../src/router/resource-identifier-read.ts | 2 +- .../api/src/router/resource-read-models.ts | 160 ++++++++++++++++ .../api/src/router/resource-read-shared.ts | 172 +----------------- ...resource-summary-read-procedure-support.ts | 3 +- 4 files changed, 169 insertions(+), 168 deletions(-) create mode 100644 packages/api/src/router/resource-read-models.ts diff --git a/packages/api/src/router/resource-identifier-read.ts b/packages/api/src/router/resource-identifier-read.ts index 0bdd18a..39d51a9 100644 --- a/packages/api/src/router/resource-identifier-read.ts +++ b/packages/api/src/router/resource-identifier-read.ts @@ -9,10 +9,10 @@ import { import { assertCanReadResource } from "../lib/resource-access.js"; import { protectedProcedure } from "../trpc.js"; import { - mapResourceDetail, readResourceByIdentifierDetailSnapshot, resolveResourceIdentifierSnapshot, } from "./resource-read-shared.js"; +import { mapResourceDetail } from "./resource-read-models.js"; export const resourceIdentifierReadProcedures = { resolveByIdentifier: protectedProcedure diff --git a/packages/api/src/router/resource-read-models.ts b/packages/api/src/router/resource-read-models.ts new file mode 100644 index 0000000..e980a51 --- /dev/null +++ b/packages/api/src/router/resource-read-models.ts @@ -0,0 +1,160 @@ +import { fmtEur } from "../lib/format-utils.js"; + +export const RESOURCE_SUMMARY_SELECT = { + id: true, + eid: true, + displayName: true, + chapter: true, + isActive: true, + areaRole: { select: { name: true } }, + country: { select: { code: true, name: true } }, + metroCity: { select: { name: true } }, + orgUnit: { select: { name: true } }, +} as const; + +export const RESOURCE_SUMMARY_DETAIL_SELECT = { + ...RESOURCE_SUMMARY_SELECT, + fte: true, + lcrCents: true, + chargeabilityTarget: true, +} as const; + +export const RESOURCE_IDENTIFIER_SELECT = { + id: true, + eid: true, + displayName: true, + chapter: true, + isActive: true, +} as const; + +export const RESOURCE_IDENTIFIER_DETAIL_SELECT = { + ...RESOURCE_IDENTIFIER_SELECT, + email: true, + fte: true, + lcrCents: true, + ucrCents: true, + chargeabilityTarget: true, + availability: true, + skills: true, + postalCode: true, + federalState: true, + areaRole: { select: { name: true, color: true } }, + country: { select: { code: true, name: true, dailyWorkingHours: true } }, + metroCity: { select: { name: true } }, + managementLevelGroup: { select: { name: true, targetPercentage: true } }, + orgUnit: { select: { name: true, level: true } }, + _count: { select: { assignments: true, vacations: true } }, +} as const; + +export const RESOURCE_DIRECTORY_SELECT = { + id: true, + eid: true, + displayName: true, + chapter: true, + isActive: true, +} as const; + +export function mapResourceSummary(resource: { + id: string; + eid: string; + displayName: string; + chapter: string | null; + isActive: boolean; + areaRole: { name: string } | null; + country: { code: string; name: string } | null; + metroCity: { name: string } | null; + orgUnit: { name: string } | null; +}) { + return { + id: resource.id, + eid: resource.eid, + name: resource.displayName, + chapter: resource.chapter, + role: resource.areaRole?.name ?? null, + country: resource.country?.name ?? resource.country?.code ?? null, + countryCode: resource.country?.code ?? null, + metroCity: resource.metroCity?.name ?? null, + orgUnit: resource.orgUnit?.name ?? null, + active: resource.isActive, + }; +} + +export function mapResourceSummaryDetail(resource: { + id: string; + eid: string; + displayName: string; + chapter: string | null; + fte: number | null; + lcrCents: number | null; + chargeabilityTarget: number | null; + isActive: boolean; + areaRole: { name: string } | null; + country: { code: string; name: string } | null; + metroCity: { name: string } | null; + orgUnit: { name: string } | null; +}) { + return { + id: resource.id, + eid: resource.eid, + name: resource.displayName, + chapter: resource.chapter, + role: resource.areaRole?.name ?? null, + country: resource.country?.name ?? resource.country?.code ?? null, + countryCode: resource.country?.code ?? null, + metroCity: resource.metroCity?.name ?? null, + orgUnit: resource.orgUnit?.name ?? null, + fte: resource.fte, + lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null, + chargeabilityTarget: resource.chargeabilityTarget != null ? `${resource.chargeabilityTarget}%` : null, + active: resource.isActive, + }; +} + +export function mapResourceDetail(resource: { + id: string; + eid: string; + displayName: string; + email: string | null; + chapter: string | null; + fte: number | null; + lcrCents: number | null; + ucrCents: number | null; + chargeabilityTarget: number | null; + isActive: boolean; + skills: unknown; + postalCode: string | null; + federalState: string | null; + areaRole: { name: string; color: string | null } | null; + country: { code: string; name: string; dailyWorkingHours: number | null } | null; + metroCity: { name: string } | null; + managementLevelGroup: { name: string; targetPercentage: number | null } | null; + orgUnit: { name: string; level: number } | null; + _count: { assignments: number; vacations: number }; +}) { + const skills = Array.isArray(resource.skills) ? resource.skills as { name?: string; level?: number }[] : []; + return { + id: resource.id, + eid: resource.eid, + name: resource.displayName, + email: resource.email, + chapter: resource.chapter, + role: resource.areaRole?.name ?? null, + country: resource.country?.name ?? resource.country?.code ?? null, + countryCode: resource.country?.code ?? null, + countryHours: resource.country?.dailyWorkingHours ?? 8, + metroCity: resource.metroCity?.name ?? null, + fte: resource.fte, + lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null, + ucr: resource.ucrCents != null ? fmtEur(resource.ucrCents) : null, + chargeabilityTarget: resource.chargeabilityTarget != null ? `${resource.chargeabilityTarget}%` : null, + managementLevel: resource.managementLevelGroup?.name ?? null, + orgUnit: resource.orgUnit?.name ?? null, + postalCode: resource.postalCode, + federalState: resource.federalState, + active: resource.isActive, + totalAssignments: resource._count.assignments, + totalVacations: resource._count.vacations, + skillCount: skills.length, + topSkills: skills.slice(0, 10).map((skill) => `${skill.name ?? "?"} (${skill.level ?? "?"})`), + }; +} diff --git a/packages/api/src/router/resource-read-shared.ts b/packages/api/src/router/resource-read-shared.ts index f4fcef7..c9346e5 100644 --- a/packages/api/src/router/resource-read-shared.ts +++ b/packages/api/src/router/resource-read-shared.ts @@ -9,13 +9,19 @@ import { getAnonymizationDirectory, resolveResourceIdsByDisplayedEids, } from "../lib/anonymization.js"; -import { fmtEur } from "../lib/format-utils.js"; import type { TRPCContext } from "../trpc.js"; import { assertCanReadResource, canReadAllResources, type ResourceReadContext, } from "../lib/resource-access.js"; +import { + RESOURCE_DIRECTORY_SELECT, + RESOURCE_IDENTIFIER_DETAIL_SELECT, + RESOURCE_IDENTIFIER_SELECT, + RESOURCE_SUMMARY_DETAIL_SELECT, + RESOURCE_SUMMARY_SELECT, +} from "./resource-read-models.js"; function parseResourceCursor(cursor: string | undefined): { displayName: string; id: string } | null { if (!cursor) return null; @@ -30,162 +36,6 @@ function parseResourceCursor(cursor: string | undefined): { displayName: string; return null; } -const RESOURCE_SUMMARY_SELECT = { - id: true, - eid: true, - displayName: true, - chapter: true, - isActive: true, - areaRole: { select: { name: true } }, - country: { select: { code: true, name: true } }, - metroCity: { select: { name: true } }, - orgUnit: { select: { name: true } }, -} as const; - -const RESOURCE_SUMMARY_DETAIL_SELECT = { - ...RESOURCE_SUMMARY_SELECT, - fte: true, - lcrCents: true, - chargeabilityTarget: true, -} as const; - -const RESOURCE_IDENTIFIER_SELECT = { - id: true, - eid: true, - displayName: true, - chapter: true, - isActive: true, -} as const; - -const RESOURCE_IDENTIFIER_DETAIL_SELECT = { - ...RESOURCE_IDENTIFIER_SELECT, - id: true, - eid: true, - displayName: true, - email: true, - chapter: true, - fte: true, - lcrCents: true, - ucrCents: true, - chargeabilityTarget: true, - isActive: true, - availability: true, - skills: true, - postalCode: true, - federalState: true, - areaRole: { select: { name: true, color: true } }, - country: { select: { code: true, name: true, dailyWorkingHours: true } }, - metroCity: { select: { name: true } }, - managementLevelGroup: { select: { name: true, targetPercentage: true } }, - orgUnit: { select: { name: true, level: true } }, - _count: { select: { assignments: true, vacations: true } }, -} as const; - -export function mapResourceSummary(resource: { - id: string; - eid: string; - displayName: string; - chapter: string | null; - isActive: boolean; - areaRole: { name: string } | null; - country: { code: string; name: string } | null; - metroCity: { name: string } | null; - orgUnit: { name: string } | null; -}) { - return { - id: resource.id, - eid: resource.eid, - name: resource.displayName, - chapter: resource.chapter, - role: resource.areaRole?.name ?? null, - country: resource.country?.name ?? resource.country?.code ?? null, - countryCode: resource.country?.code ?? null, - metroCity: resource.metroCity?.name ?? null, - orgUnit: resource.orgUnit?.name ?? null, - active: resource.isActive, - }; -} - -export function mapResourceSummaryDetail(resource: { - id: string; - eid: string; - displayName: string; - chapter: string | null; - fte: number | null; - lcrCents: number | null; - chargeabilityTarget: number | null; - isActive: boolean; - areaRole: { name: string } | null; - country: { code: string; name: string } | null; - metroCity: { name: string } | null; - orgUnit: { name: string } | null; -}) { - return { - id: resource.id, - eid: resource.eid, - name: resource.displayName, - chapter: resource.chapter, - role: resource.areaRole?.name ?? null, - country: resource.country?.name ?? resource.country?.code ?? null, - countryCode: resource.country?.code ?? null, - metroCity: resource.metroCity?.name ?? null, - orgUnit: resource.orgUnit?.name ?? null, - fte: resource.fte, - lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null, - chargeabilityTarget: resource.chargeabilityTarget != null ? `${resource.chargeabilityTarget}%` : null, - active: resource.isActive, - }; -} - -export function mapResourceDetail(resource: { - id: string; - eid: string; - displayName: string; - email: string | null; - chapter: string | null; - fte: number | null; - lcrCents: number | null; - ucrCents: number | null; - chargeabilityTarget: number | null; - isActive: boolean; - skills: unknown; - postalCode: string | null; - federalState: string | null; - areaRole: { name: string; color: string | null } | null; - country: { code: string; name: string; dailyWorkingHours: number | null } | null; - metroCity: { name: string } | null; - managementLevelGroup: { name: string; targetPercentage: number | null } | null; - orgUnit: { name: string; level: number } | null; - _count: { assignments: number; vacations: number }; -}) { - const skills = Array.isArray(resource.skills) ? resource.skills as { name?: string; level?: number }[] : []; - return { - id: resource.id, - eid: resource.eid, - name: resource.displayName, - email: resource.email, - chapter: resource.chapter, - role: resource.areaRole?.name ?? null, - country: resource.country?.name ?? resource.country?.code ?? null, - countryCode: resource.country?.code ?? null, - countryHours: resource.country?.dailyWorkingHours ?? 8, - metroCity: resource.metroCity?.name ?? null, - fte: resource.fte, - lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null, - ucr: resource.ucrCents != null ? fmtEur(resource.ucrCents) : null, - chargeabilityTarget: resource.chargeabilityTarget != null ? `${resource.chargeabilityTarget}%` : null, - managementLevel: resource.managementLevelGroup?.name ?? null, - orgUnit: resource.orgUnit?.name ?? null, - postalCode: resource.postalCode, - federalState: resource.federalState, - active: resource.isActive, - totalAssignments: resource._count.assignments, - totalVacations: resource._count.vacations, - skillCount: skills.length, - topSkills: skills.slice(0, 10).map((skill) => `${skill.name ?? "?"} (${skill.level ?? "?"})`), - }; -} - function isBroadResourceLookupAllowed(ctx: Pick): boolean { return canReadAllResources(ctx); } @@ -218,14 +68,6 @@ export const ResourceListQuerySchema = ResourceDirectoryQuerySchema.extend({ })).optional(), }); -const RESOURCE_DIRECTORY_SELECT = { - id: true, - eid: true, - displayName: true, - chapter: true, - isActive: true, -} as const; - export async function listStaffResources( ctx: Pick, input: z.infer, diff --git a/packages/api/src/router/resource-summary-read-procedure-support.ts b/packages/api/src/router/resource-summary-read-procedure-support.ts index b4b7a48..c22ee90 100644 --- a/packages/api/src/router/resource-summary-read-procedure-support.ts +++ b/packages/api/src/router/resource-summary-read-procedure-support.ts @@ -25,11 +25,10 @@ import { ResourceListQuerySchema, listResourceDirectoryEntries, listStaffResources, - mapResourceSummary, - mapResourceSummaryDetail, readResourceSummariesSnapshot, readResourceSummaryDetailsSnapshot, } from "./resource-read-shared.js"; +import { mapResourceSummary, mapResourceSummaryDetail } from "./resource-read-models.js"; type ResourceSummaryReadContext = Pick;