fix(types): replace structural DB types with Pick<PrismaClient> and remove Prisma boundary as any casts

Replace ~440 lines of hand-written structural DB client types across 7 lib files
with `Pick<PrismaClient, ...>` from @capakraken/db. This eliminates all `as any`
casts at Prisma boundaries (cron routes, allocation effects, vacation procedures)
and surfaces two pre-existing bugs:
- weekly-digest.ts: `db.allocation.count()` called non-existent model (fixed → demandRequirement)
- estimate-reminders.ts: `submittedAt` field doesn't exist on EstimateVersion (fixed → updatedAt)

Also adds root eslint.config.mjs so lint-staged can lint package files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 15:09:16 +02:00
parent 82acc56b8d
commit 9051ff73d0
17 changed files with 257 additions and 581 deletions
+13 -110
View File
@@ -1,3 +1,4 @@
import type { PrismaClient } from "@capakraken/db";
import { listAssignmentBookings } from "@capakraken/application";
import { rankResources } from "@capakraken/staffing";
import type { SkillEntry, WeekdayAvailability } from "@capakraken/shared";
@@ -8,107 +9,10 @@ import {
} from "./resource-capacity.js";
import { createNotificationsForUsers } from "./create-notification.js";
/**
* Minimal DB interface for auto-staffing — avoids importing the full PrismaClient.
* Follows the same pattern as budget-alerts.ts.
*/
type DbClient = Parameters<typeof listAssignmentBookings>[0] & {
demandRequirement: {
findUnique: (args: {
where: { id: string };
select: {
id: true;
projectId: true;
startDate: true;
endDate: true;
hoursPerDay: true;
role: true;
roleId: true;
headcount: true;
budgetCents: true;
};
}) => Promise<{
id: string;
projectId: string;
startDate: Date;
endDate: Date;
hoursPerDay: number;
role: string | null;
roleId: string | null;
headcount: number;
budgetCents: number;
} | null>;
};
project: {
findUnique: (args: {
where: { id: string };
select: { id: true; name: true };
}) => Promise<{ id: string; name: string } | null>;
};
role: {
findUnique: (args: {
where: { id: string };
select: { id: true; name: true };
}) => Promise<{ id: string; name: string } | null>;
};
resource: {
findMany: (args: {
where: { isActive: true };
select?: {
id?: true;
displayName?: true;
eid?: true;
skills?: true;
lcrCents?: true;
chargeabilityTarget?: true;
valueScore?: true;
availability?: true;
countryId?: true;
federalState?: true;
metroCityId?: true;
country?: { select: { code: true } };
metroCity?: { select: { name: true } };
};
take?: number;
}) => Promise<Array<{
id: string;
displayName: string;
eid: string | null;
skills: unknown;
lcrCents: number;
chargeabilityTarget: number;
availability: unknown;
valueScore: number | null;
countryId: string | null;
federalState: string | null;
metroCityId: string | null;
country: { code: string | null } | null;
metroCity: { name: string | null } | null;
}>>;
};
notification: {
create: (args: {
data: {
userId: string;
type: string;
category: string;
priority: string;
title: string;
body: string;
entityId: string;
entityType: string;
link: string;
channel: string;
};
}) => Promise<{ id: string; userId: string }>;
};
user: {
findMany: (args: {
where: { systemRole: { in: string[] } };
select: { id: true };
}) => Promise<Array<{ id: string }>>;
};
};
type DbClient = Pick<
PrismaClient,
"assignment" | "demandRequirement" | "project" | "role" | "resource" | "notification" | "user"
>;
const TOP_N = 3;
@@ -224,7 +128,8 @@ export async function generateAutoSuggestions(
});
const allocatedHours = resourceBookings.reduce(
(sum, booking) =>
sum + calculateEffectiveBookedHours({
sum +
calculateEffectiveBookedHours({
availability,
startDate: booking.startDate,
endDate: booking.endDate,
@@ -237,13 +142,12 @@ export async function generateAutoSuggestions(
);
const utilizationPercent =
totalAvailableHours > 0
? Math.min(100, (allocatedHours / totalAvailableHours) * 100)
: 0;
totalAvailableHours > 0 ? Math.min(100, (allocatedHours / totalAvailableHours) * 100) : 0;
const wouldExceedCapacity = totalAvailableHours > 0
? allocatedHours + demand.hoursPerDay > totalAvailableHours
: demand.hoursPerDay > 0;
const wouldExceedCapacity =
totalAvailableHours > 0
? allocatedHours + demand.hoursPerDay > totalAvailableHours
: demand.hoursPerDay > 0;
return {
id: resource.id,
@@ -260,8 +164,7 @@ export async function generateAutoSuggestions(
});
// 6. Rank resources using the staffing algorithm
const budgetLcrCentsPerHour =
demand.budgetCents > 0 ? demand.budgetCents : undefined;
const budgetLcrCentsPerHour = demand.budgetCents > 0 ? demand.budgetCents : undefined;
const ranked = rankResources({
requiredSkills,