more workbench usability

This commit is contained in:
2026-03-19 07:38:08 -05:00
parent cf54e4ba58
commit 4949b6033f
4 changed files with 293 additions and 16 deletions

View File

@@ -2,6 +2,7 @@ import type {
GanttLinkDto,
GanttTaskDto,
PlanningReadinessState,
PlanningStationDayLoadDto,
PlanningStationLoadDto,
PlanningTaskActionDto,
PlanningTimelineDto,
@@ -94,6 +95,15 @@ type StationAccumulator = {
workingDays: number[];
};
type StationDayAccumulator = {
stationId: string;
dateKey: string;
plannedMinutes: number;
actualMinutes: number;
operationCount: number;
capacityMinutes: number;
};
function clampProgress(value: number) {
return Math.max(0, Math.min(100, Math.round(value)));
}
@@ -199,6 +209,23 @@ function createStationLoad(record: StationAccumulator): PlanningStationLoadDto {
};
}
function createStationDayLoad(record: StationDayAccumulator): PlanningStationDayLoadDto {
const capacityMinutes = Math.max(record.capacityMinutes, 1);
const utilizationPercent = Math.round((record.plannedMinutes / capacityMinutes) * 100);
const actualUtilizationPercent = Math.round((record.actualMinutes / capacityMinutes) * 100);
return {
stationId: record.stationId,
dateKey: record.dateKey,
plannedMinutes: record.plannedMinutes,
actualMinutes: record.actualMinutes,
capacityMinutes,
utilizationPercent,
actualUtilizationPercent,
operationCount: record.operationCount,
overloaded: utilizationPercent > 100,
};
}
function buildProjectTask(
project: PlanningProjectRecord,
projectWorkOrders: PlanningWorkOrderRecord[],
@@ -492,6 +519,7 @@ export async function getPlanningTimeline(): Promise<PlanningTimelineDto> {
}
const stationAccumulators = new Map<string, StationAccumulator>();
const stationDayAccumulators = new Map<string, StationDayAccumulator>();
for (const workOrder of openWorkOrders) {
const insight = workOrderInsights.get(workOrder.id);
for (const operation of workOrder.operations) {
@@ -525,7 +553,22 @@ export async function getPlanningTimeline(): Promise<PlanningTimelineDto> {
current.lateCount += 1;
}
for (let cursor = startOfDay(operation.plannedStart).getTime(); cursor <= startOfDay(operation.plannedEnd).getTime(); cursor += DAY_MS) {
current.dayKeys.add(dateKey(new Date(cursor)));
const currentDate = new Date(cursor);
const currentDateKey = dateKey(currentDate);
current.dayKeys.add(currentDateKey);
const dayAccumulatorKey = `${operation.station.id}:${currentDateKey}`;
const dayAccumulator = stationDayAccumulators.get(dayAccumulatorKey) ?? {
stationId: operation.station.id,
dateKey: currentDateKey,
plannedMinutes: 0,
actualMinutes: 0,
operationCount: 0,
capacityMinutes: Math.max(operation.station.dailyCapacityMinutes, 60) * Math.max(operation.station.parallelCapacity, 1),
};
dayAccumulator.plannedMinutes += operation.plannedMinutes;
dayAccumulator.actualMinutes += operation.actualMinutes;
dayAccumulator.operationCount += 1;
stationDayAccumulators.set(dayAccumulatorKey, dayAccumulator);
}
stationAccumulators.set(operation.station.id, current);
}
@@ -537,6 +580,14 @@ export async function getPlanningTimeline(): Promise<PlanningTimelineDto> {
}
return left.stationCode.localeCompare(right.stationCode);
});
const stationDayLoads = [...stationDayAccumulators.values()]
.map(createStationDayLoad)
.sort((left, right) => {
if (left.dateKey !== right.dateKey) {
return left.dateKey.localeCompare(right.dateKey);
}
return left.stationId.localeCompare(right.stationId);
});
const stationLoadById = new Map(stationLoads.map((load) => [load.stationId, load]));
const tasks: GanttTaskDto[] = [];
@@ -863,5 +914,6 @@ export async function getPlanningTimeline(): Promise<PlanningTimelineDto> {
})
.slice(0, 12),
stationLoads,
stationDayLoads,
};
}