chore(repo): initialize planarchy workspace
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import type { PrismaClient } from "@planarchy/db";
|
||||
import { AllocationStatus } from "@planarchy/shared";
|
||||
import { buildSplitAllocationReadModel } from "../allocation/build-split-allocation-read-model.js";
|
||||
import { listAssignmentBookings } from "../allocation/list-assignment-bookings.js";
|
||||
import { calculateInclusiveDays } from "./shared.js";
|
||||
|
||||
export async function getDashboardOverview(db: PrismaClient) {
|
||||
const [
|
||||
totalResources,
|
||||
activeResources,
|
||||
totalProjects,
|
||||
allProjects,
|
||||
allDemandRequirements,
|
||||
allAssignments,
|
||||
budgetBookings,
|
||||
recentActivity,
|
||||
allResources,
|
||||
] = await Promise.all([
|
||||
db.resource.count(),
|
||||
db.resource.count({ where: { isActive: true } }),
|
||||
db.project.count(),
|
||||
db.project.findMany({ select: { status: true, budgetCents: true } }),
|
||||
db.demandRequirement.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
projectId: true,
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
hoursPerDay: true,
|
||||
percentage: true,
|
||||
role: true,
|
||||
roleId: true,
|
||||
headcount: true,
|
||||
status: true,
|
||||
metadata: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
}),
|
||||
db.assignment.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
demandRequirementId: true,
|
||||
resourceId: true,
|
||||
projectId: true,
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
hoursPerDay: true,
|
||||
percentage: true,
|
||||
role: true,
|
||||
roleId: true,
|
||||
dailyCostCents: true,
|
||||
status: true,
|
||||
metadata: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
}),
|
||||
listAssignmentBookings(db, {}),
|
||||
db.auditLog.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 10,
|
||||
select: { id: true, entityType: true, action: true, createdAt: true },
|
||||
}),
|
||||
db.resource.findMany({
|
||||
select: { chapter: true, chargeabilityTarget: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
const planningReadModel = buildSplitAllocationReadModel({
|
||||
demandRequirements: allDemandRequirements,
|
||||
assignments: allAssignments,
|
||||
});
|
||||
const totalAllocations = planningReadModel.allocations.length;
|
||||
const activeAllocations = planningReadModel.allocations.filter(
|
||||
(allocation) => allocation.status !== AllocationStatus.CANCELLED,
|
||||
).length;
|
||||
|
||||
const totalCostCents = budgetBookings.reduce(
|
||||
(sum, booking) =>
|
||||
sum +
|
||||
(booking.dailyCostCents ?? 0) *
|
||||
calculateInclusiveDays(booking.startDate, booking.endDate),
|
||||
0,
|
||||
);
|
||||
|
||||
const totalBudgetCents = allProjects.reduce(
|
||||
(sum, project) => sum + (project.budgetCents ?? 0),
|
||||
0,
|
||||
);
|
||||
|
||||
const avgUtilizationPercent =
|
||||
totalBudgetCents > 0
|
||||
? Math.round((totalCostCents / totalBudgetCents) * 100)
|
||||
: 0;
|
||||
|
||||
const statusCountMap = new Map<string, number>();
|
||||
for (const project of allProjects) {
|
||||
statusCountMap.set(
|
||||
project.status,
|
||||
(statusCountMap.get(project.status) ?? 0) + 1,
|
||||
);
|
||||
}
|
||||
|
||||
const chapterMap = new Map<
|
||||
string,
|
||||
{ resourceCount: number; chargeabilitySum: number }
|
||||
>();
|
||||
for (const resource of allResources) {
|
||||
const chapter = resource.chapter ?? "Unassigned";
|
||||
const existing = chapterMap.get(chapter) ?? {
|
||||
resourceCount: 0,
|
||||
chargeabilitySum: 0,
|
||||
};
|
||||
|
||||
chapterMap.set(chapter, {
|
||||
resourceCount: existing.resourceCount + 1,
|
||||
chargeabilitySum:
|
||||
existing.chargeabilitySum + (resource.chargeabilityTarget ?? 0),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
totalResources,
|
||||
activeResources,
|
||||
totalProjects,
|
||||
activeProjects: allProjects.filter((project) => project.status === "ACTIVE")
|
||||
.length,
|
||||
totalAllocations,
|
||||
activeAllocations,
|
||||
budgetSummary: {
|
||||
totalBudgetCents,
|
||||
totalCostCents,
|
||||
avgUtilizationPercent,
|
||||
},
|
||||
recentActivity: recentActivity.map((activity) => ({
|
||||
id: activity.id,
|
||||
entityType: activity.entityType,
|
||||
action: activity.action,
|
||||
createdAt: activity.createdAt,
|
||||
})),
|
||||
projectsByStatus: [...statusCountMap.entries()].map(([status, count]) => ({
|
||||
status,
|
||||
count,
|
||||
})),
|
||||
chapterUtilization: [...chapterMap.entries()].map(([chapter, data]) => ({
|
||||
chapter,
|
||||
resourceCount: data.resourceCount,
|
||||
avgChargeabilityTarget:
|
||||
data.resourceCount > 0
|
||||
? Math.round(data.chargeabilitySum / data.resourceCount)
|
||||
: 0,
|
||||
})),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user