75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import { listAssignmentBookings } from "@capakraken/application";
|
|
import { ProjectStatus } from "@capakraken/shared";
|
|
import { z } from "zod";
|
|
import { CursorInputSchema, paginateCursor } from "../db/pagination.js";
|
|
import { controllerProcedure } from "../trpc.js";
|
|
|
|
export const projectCostReadProcedures = {
|
|
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,
|
|
})
|
|
: [];
|
|
|
|
const projects = result.items.map((project) => {
|
|
const projectBookings = bookings.filter((booking) => booking.projectId === project.id);
|
|
let totalCostCents = 0;
|
|
let totalPersonDays = 0;
|
|
for (const booking of projectBookings) {
|
|
const days =
|
|
(new Date(booking.endDate).getTime() - new Date(booking.startDate).getTime()) /
|
|
(1000 * 60 * 60 * 24) +
|
|
1;
|
|
totalCostCents += booking.dailyCostCents * days;
|
|
totalPersonDays += (booking.hoursPerDay * days) / 8;
|
|
}
|
|
const utilizationPercent = project.budgetCents > 0
|
|
? Math.round((totalCostCents / project.budgetCents) * 100)
|
|
: 0;
|
|
return {
|
|
...project,
|
|
totalCostCents: Math.round(totalCostCents),
|
|
totalPersonDays: Math.round(totalPersonDays * 10) / 10,
|
|
utilizationPercent,
|
|
};
|
|
});
|
|
|
|
return { projects, nextCursor: result.nextCursor };
|
|
}),
|
|
};
|