refactor(api): extract project cost read procedures
This commit is contained in:
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
countPlanningEntries,
|
||||
listAssignmentBookings,
|
||||
} from "@capakraken/application";
|
||||
import { countPlanningEntries } from "@capakraken/application";
|
||||
import type { WeekdayAvailability } from "@capakraken/shared";
|
||||
import { BlueprintTarget, CreateProjectSchema, FieldType, PermissionKey, ProjectStatus, UpdateProjectSchema } from "@capakraken/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -11,6 +8,7 @@ import { findUniqueOrThrow } from "../db/helpers.js";
|
||||
import { paginate, paginateCursor, PaginationInputSchema, CursorInputSchema } from "../db/pagination.js";
|
||||
import { assertBlueprintDynamicFields } from "./blueprint-validation.js";
|
||||
import { buildDynamicFieldWhereClauses } from "./custom-field-filters.js";
|
||||
import { projectCostReadProcedures } from "./project-cost-read.js";
|
||||
import { projectCoverProcedures } from "./project-cover.js";
|
||||
import { projectIdentifierReadProcedures } from "./project-identifier-read.js";
|
||||
import { loadProjectPlanningReadModel } from "./project-planning-read-model.js";
|
||||
@@ -56,6 +54,7 @@ function dispatchProjectWebhookInBackground(
|
||||
}
|
||||
|
||||
export const projectRouter = createTRPCRouter({
|
||||
...projectCostReadProcedures,
|
||||
...projectCoverProcedures,
|
||||
...projectIdentifierReadProcedures,
|
||||
|
||||
@@ -385,74 +384,6 @@ export const projectRouter = createTRPCRouter({
|
||||
return { count: updated.length };
|
||||
}),
|
||||
|
||||
listWithCosts: controllerProcedure
|
||||
.input(
|
||||
CursorInputSchema.extend({
|
||||
status: z.nativeEnum(ProjectStatus).optional(),
|
||||
search: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { status, search, cursor } = input;
|
||||
const where = {
|
||||
...(status ? { status } : {}),
|
||||
...(search
|
||||
? {
|
||||
OR: [
|
||||
{ name: { contains: search, mode: "insensitive" as const } },
|
||||
{ shortCode: { contains: search, mode: "insensitive" as const } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
const whereWithCursor = cursor ? { ...where, id: { gt: cursor } } : where;
|
||||
|
||||
const result = await paginateCursor(
|
||||
({ take }) =>
|
||||
ctx.db.project.findMany({
|
||||
where: whereWithCursor,
|
||||
take,
|
||||
orderBy: [{ startDate: "asc" }, { id: "asc" }],
|
||||
}),
|
||||
input,
|
||||
);
|
||||
|
||||
const projectIds = result.items.map((project) => project.id);
|
||||
const bookings = projectIds.length
|
||||
? await listAssignmentBookings(ctx.db, {
|
||||
startDate: new Date("1900-01-01T00:00:00.000Z"),
|
||||
endDate: new Date("2100-12-31T23:59:59.999Z"),
|
||||
projectIds,
|
||||
})
|
||||
: [];
|
||||
|
||||
// Compute cost + person days per project
|
||||
const projects = result.items.map((p) => {
|
||||
const projectBookings = bookings.filter((booking) => booking.projectId === p.id);
|
||||
let totalCostCents = 0;
|
||||
let totalPersonDays = 0;
|
||||
for (const a of projectBookings) {
|
||||
const days =
|
||||
(new Date(a.endDate).getTime() - new Date(a.startDate).getTime()) /
|
||||
(1000 * 60 * 60 * 24) +
|
||||
1;
|
||||
totalCostCents += a.dailyCostCents * days;
|
||||
totalPersonDays += (a.hoursPerDay * days) / 8;
|
||||
}
|
||||
const utilizationPercent = p.budgetCents > 0
|
||||
? Math.round((totalCostCents / p.budgetCents) * 100)
|
||||
: 0;
|
||||
return {
|
||||
...p,
|
||||
totalCostCents: Math.round(totalCostCents),
|
||||
totalPersonDays: Math.round(totalPersonDays * 10) / 10,
|
||||
utilizationPercent,
|
||||
};
|
||||
});
|
||||
|
||||
return { projects, nextCursor: result.nextCursor };
|
||||
}),
|
||||
|
||||
delete: adminProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
|
||||
Reference in New Issue
Block a user