feat: dashboard overhaul, chargeability reports, dispo import enhancements, UI polish
Dashboard: expanded chargeability widget, resource/project table widgets with sorting and filters, stat cards with formatMoney integration. Chargeability: new report client with filtering, chargeability-bookings use case, updated dashboard overview logic. Dispo import: TBD project handling, parse-dispo-matrix improvements, stage-dispo-projects resource value scores, new tests. Estimates: CommercialTermsEditor component, commercial-terms engine module, expanded estimate schemas and types. UI: AppShell navigation updates, timeline filter/toolbar enhancements, role management improvements, signin page redesign, Tailwind/globals polish, SystemSettings SMTP section, anonymization support. Tests: new router tests (anonymization, chargeability, effort-rule, entitlement, estimate, experience-multiplier, notification, resource, staffing, vacation). Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
import type { PrismaClient } from "@planarchy/db";
|
||||
import { computeChargeability } from "@planarchy/engine";
|
||||
import type { WeekdayAvailability } from "@planarchy/shared";
|
||||
import {
|
||||
isChargeabilityActualBooking,
|
||||
isChargeabilityRelevantProject,
|
||||
} from "../allocation/chargeability-bookings.js";
|
||||
import { listAssignmentBookings } from "../allocation/list-assignment-bookings.js";
|
||||
|
||||
export interface GetDashboardChargeabilityOverviewInput {
|
||||
includeProposed?: boolean;
|
||||
topN: number;
|
||||
watchlistThreshold: number;
|
||||
countryIds?: string[];
|
||||
departed?: boolean;
|
||||
now?: Date;
|
||||
}
|
||||
|
||||
@@ -18,12 +25,20 @@ export async function getDashboardChargeabilityOverview(
|
||||
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
|
||||
const resources = await db.resource.findMany({
|
||||
where: { isActive: true },
|
||||
where: {
|
||||
isActive: true,
|
||||
...(input.countryIds && input.countryIds.length > 0
|
||||
? { countryId: { in: input.countryIds } }
|
||||
: {}),
|
||||
...(input.departed !== undefined ? { departed: input.departed } : {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
eid: true,
|
||||
displayName: true,
|
||||
chapter: true,
|
||||
countryId: true,
|
||||
departed: true,
|
||||
chargeabilityTarget: true,
|
||||
availability: true,
|
||||
},
|
||||
@@ -37,11 +52,11 @@ export async function getDashboardChargeabilityOverview(
|
||||
const stats = resources.map((resource) => {
|
||||
const availability = resource.availability as unknown as WeekdayAvailability;
|
||||
const resourceBookings = bookings.filter((booking) => booking.resourceId === resource.id);
|
||||
const actualAllocations = resourceBookings.filter(
|
||||
(booking) =>
|
||||
(booking.status === "CONFIRMED" || booking.status === "ACTIVE") &&
|
||||
booking.project.status !== "DRAFT" &&
|
||||
booking.project.status !== "CANCELLED",
|
||||
const actualAllocations = resourceBookings.filter((booking) =>
|
||||
isChargeabilityActualBooking(booking, input.includeProposed === true),
|
||||
);
|
||||
const expectedAllocations = resourceBookings.filter(
|
||||
(booking) => isChargeabilityRelevantProject(booking.project, true),
|
||||
);
|
||||
const actual = computeChargeability(
|
||||
availability,
|
||||
@@ -51,7 +66,7 @@ export async function getDashboardChargeabilityOverview(
|
||||
);
|
||||
const expected = computeChargeability(
|
||||
availability,
|
||||
resourceBookings,
|
||||
expectedAllocations,
|
||||
start,
|
||||
end,
|
||||
);
|
||||
@@ -61,6 +76,8 @@ export async function getDashboardChargeabilityOverview(
|
||||
eid: resource.eid,
|
||||
displayName: resource.displayName,
|
||||
chapter: resource.chapter,
|
||||
countryId: resource.countryId,
|
||||
departed: resource.departed,
|
||||
chargeabilityTarget: resource.chargeabilityTarget,
|
||||
actualChargeability: actual.chargeability,
|
||||
expectedChargeability: expected.chargeability,
|
||||
@@ -69,16 +86,14 @@ export async function getDashboardChargeabilityOverview(
|
||||
|
||||
return {
|
||||
top: [...stats]
|
||||
.sort((left, right) => right.actualChargeability - left.actualChargeability)
|
||||
.slice(0, input.topN),
|
||||
.sort((left, right) => right.actualChargeability - left.actualChargeability),
|
||||
watchlist: [...stats]
|
||||
.filter(
|
||||
(resource) =>
|
||||
resource.actualChargeability <
|
||||
resource.chargeabilityTarget - input.watchlistThreshold,
|
||||
)
|
||||
.sort((left, right) => left.actualChargeability - right.actualChargeability)
|
||||
.slice(0, input.topN),
|
||||
.sort((left, right) => left.actualChargeability - right.actualChargeability),
|
||||
month: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user