feat(staffing): enforce planning and cost audiences

This commit is contained in:
2026-03-30 09:36:38 +02:00
parent a960d43ed1
commit 3aac946443
4 changed files with 248 additions and 13 deletions
+4
View File
@@ -222,6 +222,7 @@ const COST_TOOLS = new Set([
"get_estimate_detail",
"get_estimate_version_snapshot",
"find_best_project_resource",
"get_staffing_suggestions",
]);
/** Tools that follow planningReadProcedure access rules in the main API. */
@@ -229,6 +230,9 @@ const PLANNING_READ_TOOLS = new Set([
"list_allocations",
"list_demands",
"check_resource_availability",
"get_staffing_suggestions",
"find_capacity",
"find_best_project_resource",
]);
/** Tools that follow controllerProcedure access rules in the main API. */
+19 -11
View File
@@ -1,6 +1,6 @@
import { rankResources } from "@capakraken/staffing";
import { listAssignmentBookings } from "@capakraken/application";
import type { WeekdayAvailability } from "@capakraken/shared";
import { PermissionKey, type WeekdayAvailability } from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import {
@@ -11,7 +11,7 @@ import {
type ResourceDailyAvailabilityContext,
} from "../lib/resource-capacity.js";
import { fmtEur } from "../lib/format-utils.js";
import { createTRPCRouter, protectedProcedure } from "../trpc.js";
import { createTRPCRouter, planningReadProcedure, requirePermission } from "../trpc.js";
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
"sunday",
@@ -860,7 +860,7 @@ export const staffingRouter = createTRPCRouter({
/**
* Get ranked resource suggestions for a staffing requirement.
*/
getSuggestions: protectedProcedure
getSuggestions: planningReadProcedure
.input(
z.object({
requiredSkills: z.array(z.string()),
@@ -875,9 +875,12 @@ export const staffingRouter = createTRPCRouter({
minProficiency: z.number().min(1).max(5).optional(),
}),
)
.query(async ({ ctx, input }) => queryStaffingSuggestions(ctx.db as unknown as StaffingSuggestionsDbClient, input)),
.query(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.VIEW_COSTS);
return queryStaffingSuggestions(ctx.db as unknown as StaffingSuggestionsDbClient, input);
}),
getProjectStaffingSuggestions: protectedProcedure
getProjectStaffingSuggestions: planningReadProcedure
.input(
z.object({
projectId: z.string().min(1),
@@ -888,6 +891,7 @@ export const staffingRouter = createTRPCRouter({
}),
)
.query(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.VIEW_COSTS);
const project = await findUniqueOrThrow(ctx.db.project.findUnique({
where: { id: input.projectId },
select: {
@@ -937,7 +941,7 @@ export const staffingRouter = createTRPCRouter({
};
}),
searchCapacity: protectedProcedure
searchCapacity: planningReadProcedure
.input(
z.object({
startDate: z.coerce.date(),
@@ -1064,7 +1068,7 @@ export const staffingRouter = createTRPCRouter({
/**
* Analyze utilization for a specific resource over a date range.
*/
analyzeUtilization: protectedProcedure
analyzeUtilization: planningReadProcedure
.input(
z.object({
resourceId: z.string(),
@@ -1179,7 +1183,7 @@ export const staffingRouter = createTRPCRouter({
/**
* Find capacity windows for a resource.
*/
findCapacity: protectedProcedure
findCapacity: planningReadProcedure
.input(
z.object({
resourceId: z.string(),
@@ -1307,7 +1311,7 @@ export const staffingRouter = createTRPCRouter({
return windows;
}),
findBestProjectResource: protectedProcedure
findBestProjectResource: planningReadProcedure
.input(
z.object({
projectId: z.string(),
@@ -1319,9 +1323,12 @@ export const staffingRouter = createTRPCRouter({
roleName: z.string().optional(),
}),
)
.query(async ({ ctx, input }) => queryBestProjectResource(ctx.db as unknown as BestProjectResourceDbClient, input)),
.query(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.VIEW_COSTS);
return queryBestProjectResource(ctx.db as unknown as BestProjectResourceDbClient, input);
}),
getBestProjectResourceDetail: protectedProcedure
getBestProjectResourceDetail: planningReadProcedure
.input(
z.object({
projectId: z.string().min(1),
@@ -1335,6 +1342,7 @@ export const staffingRouter = createTRPCRouter({
}),
)
.query(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.VIEW_COSTS);
const project = await findUniqueOrThrow(ctx.db.project.findUnique({
where: { id: input.projectId },
select: {