feat(planning): ship holiday-aware planning and assistant upgrades
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
countPlanningEntries,
|
||||
listAssignmentBookings,
|
||||
} from "@capakraken/application";
|
||||
import type { WeekdayAvailability } from "@capakraken/shared";
|
||||
import { BlueprintTarget, CreateProjectSchema, FieldType, PermissionKey, ProjectStatus, UpdateProjectSchema } from "@capakraken/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
@@ -17,6 +18,10 @@ import { generateGeminiImage, isGeminiConfigured, parseGeminiError } from "../ge
|
||||
import { invalidateDashboardCache } from "../lib/cache.js";
|
||||
import { dispatchWebhooks } from "../lib/webhook-dispatcher.js";
|
||||
import { validateImageDataUrl } from "../lib/image-validation.js";
|
||||
import {
|
||||
calculateEffectiveBookedHours,
|
||||
loadResourceDailyAvailabilityContexts,
|
||||
} from "../lib/resource-capacity.js";
|
||||
|
||||
const MAX_COVER_SIZE = 4 * 1024 * 1024; // 4 MB base64 string length limit (client compresses before upload)
|
||||
|
||||
@@ -127,20 +132,53 @@ export const projectRouter = createTRPCRouter({
|
||||
|
||||
const assignments = await ctx.db.assignment.findMany({
|
||||
where: { projectId: input.projectId, status: { not: "CANCELLED" } },
|
||||
include: { resource: { include: { country: { select: { code: true } } } } },
|
||||
include: {
|
||||
resource: {
|
||||
include: {
|
||||
country: { select: { id: true, code: true } },
|
||||
metroCity: { select: { id: true, name: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const periodStart = assignments.length > 0
|
||||
? new Date(Math.min(...assignments.map((assignment) => assignment.startDate.getTime())))
|
||||
: new Date();
|
||||
const periodEnd = assignments.length > 0
|
||||
? new Date(Math.max(...assignments.map((assignment) => assignment.endDate.getTime())))
|
||||
: new Date();
|
||||
const contexts = await loadResourceDailyAvailabilityContexts(
|
||||
ctx.db,
|
||||
assignments.map((assignment) => ({
|
||||
id: assignment.resource.id,
|
||||
availability: assignment.resource.availability as unknown as WeekdayAvailability,
|
||||
countryId: assignment.resource.country?.id ?? assignment.resource.countryId,
|
||||
countryCode: assignment.resource.country?.code,
|
||||
federalState: assignment.resource.federalState,
|
||||
metroCityId: assignment.resource.metroCity?.id ?? assignment.resource.metroCityId,
|
||||
metroCityName: assignment.resource.metroCity?.name,
|
||||
})),
|
||||
periodStart,
|
||||
periodEnd,
|
||||
);
|
||||
|
||||
const mapped: ShoringAssignment[] = assignments.map((a) => {
|
||||
const start = new Date(a.startDate);
|
||||
const end = new Date(a.endDate);
|
||||
const diffMs = end.getTime() - start.getTime();
|
||||
const diffDays = Math.max(1, Math.round(diffMs / (1000 * 60 * 60 * 24)) + 1);
|
||||
const workingDays = Math.round(diffDays / 7 * 5);
|
||||
const workingDays = a.hoursPerDay > 0
|
||||
? calculateEffectiveBookedHours({
|
||||
availability: a.resource.availability as unknown as WeekdayAvailability,
|
||||
startDate: a.startDate,
|
||||
endDate: a.endDate,
|
||||
hoursPerDay: a.hoursPerDay,
|
||||
periodStart,
|
||||
periodEnd,
|
||||
context: contexts.get(a.resourceId ?? a.resource.id),
|
||||
}) / a.hoursPerDay
|
||||
: 0;
|
||||
return {
|
||||
resourceId: a.resourceId,
|
||||
countryCode: a.resource.country?.code ?? null,
|
||||
hoursPerDay: a.hoursPerDay,
|
||||
workingDays: Math.max(1, workingDays),
|
||||
workingDays: Math.max(0, workingDays),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user