Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/integrations/hrAudience.server.ts
T

103 lines
3.2 KiB
TypeScript

import { CatalogError, foundationErrors } from "@hivegrid/foundation";
import { withTenantRLS, type TenantRlsClient } from "~/utils/db/.server/tenantRls.server";
export type HrAudienceKind = "all" | "team" | "location";
export type HrAudienceViewer = {
team: string | null;
locationId: string | null;
};
export type HrAudienceOption = {
kind: Exclude<HrAudienceKind, "all">;
ref: string;
label: string;
};
export function isHrAudienceVisible(
kind: HrAudienceKind,
ref: string | null,
viewer: HrAudienceViewer,
): boolean {
if (kind === "all") return true;
if (kind === "team") return ref != null && ref === viewer.team;
return ref != null && ref === viewer.locationId;
}
export async function requireValidHrAudienceWithTx(
tx: TenantRlsClient,
input: { tenantId: string; kind: HrAudienceKind; ref: string | null },
): Promise<void> {
if (input.kind === "all") {
if (input.ref) {
throw new CatalogError(foundationErrors.notFound, { audienceRef: input.ref });
}
return;
}
if (!input.ref) {
throw new CatalogError(foundationErrors.notFound, { audienceKind: input.kind });
}
if (input.kind === "team") {
const team = await tx.hrEmployeeProfile.findFirst({
where: { tenantId: input.tenantId, team: input.ref },
select: { id: true },
});
if (!team) throw new CatalogError(foundationErrors.notFound, { audienceRef: input.ref });
return;
}
const location = await tx.hrLocation.findFirst({
where: { tenantId: input.tenantId, id: input.ref, archivedAt: null },
select: { id: true },
});
if (!location) throw new CatalogError(foundationErrors.notFound, { audienceRef: input.ref });
}
export async function requireValidHrAudience(input: {
tenantId: string;
kind: HrAudienceKind;
ref: string | null;
}): Promise<void> {
await withTenantRLS(input.tenantId, (tx) => requireValidHrAudienceWithTx(tx, input));
}
export async function requireHrAudienceViewer(input: {
tenantId: string;
userId: string;
kind: HrAudienceKind;
ref: string | null;
canManage: boolean;
}): Promise<void> {
if (input.canManage || input.kind === "all") return;
const profile = await withTenantRLS(input.tenantId, (tx) =>
tx.hrEmployeeProfile.findFirst({
where: { tenantId: input.tenantId, userId: input.userId },
select: { team: true, locationId: true },
}),
);
if (!profile || !isHrAudienceVisible(input.kind, input.ref, profile)) {
throw new CatalogError(foundationErrors.notFound, { audienceRef: input.ref });
}
}
export async function listHrAudienceOptions(tenantId: string): Promise<HrAudienceOption[]> {
return withTenantRLS(tenantId, async (tx) => {
const [teams, locations] = await Promise.all([
tx.hrEmployeeProfile.findMany({
where: { tenantId, team: { not: null } },
select: { team: true },
distinct: ["team"],
orderBy: { team: "asc" },
}),
tx.hrLocation.findMany({
where: { tenantId, archivedAt: null },
select: { id: true, name: true },
orderBy: { name: "asc" },
}),
]);
return [
...teams.flatMap(({ team }) => (team ? [{ kind: "team" as const, ref: team, label: team }] : [])),
...locations.map(({ id, name }) => ({ kind: "location" as const, ref: id, label: name })),
];
});
}