Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic added "Not started or quit" to consultants #519

Merged
merged 5 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 78 additions & 2 deletions backend/Api/StaffingController/ReadModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,83 @@
vacationsPrWeek));
}

var startDate = consultant.StartDate;
var endDate = consultant.EndDate;

var firstDayInScope = weekSet.First().FirstDayOfWorkWeek();
var firstWorkDayOutOfScope = weekSet.Last().LastWorkDayOfWeek();

if (startDate.HasValue && startDate > firstDayInScope)
{
var startWeeks = GetNonEmploymentHoursNotStartedOrQuit(startDate, weekSet, consultant, false);
detailedBookings = detailedBookings.Append(CreateNotStartedOrQuitDetailedBooking(startWeeks));
}

if (endDate.HasValue && endDate < firstWorkDayOutOfScope)
{
var endWeeks = GetNonEmploymentHoursNotStartedOrQuit(endDate, weekSet, consultant, true);
detailedBookings = detailedBookings.Append(CreateNotStartedOrQuitDetailedBooking(endWeeks));
}


var detailedBookingList = detailedBookings.ToList();

return detailedBookingList;
}

private static List<WeeklyHours> GetNonEmploymentHoursNotStartedOrQuit(DateOnly? date, List<Week> weekSet, Consultant consultant, bool quit)
{
return weekSet
.Select(week =>
{
var isTargetWeek = week.ContainsDate((DateOnly)date);

Check warning on line 198 in backend/Api/StaffingController/ReadModelFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (7.x.x)

Nullable value type may be null.

var maxWorkHoursForWeek = consultant.Department.Organization.HoursPerWorkday * 5 -
consultant.Department.Organization.GetTotalHolidayHoursOfWeek(week);

var hoursOutsideEmployment = quit
? GetNonEmployedHoursForWeekWhenQuitting(date, week, isTargetWeek, consultant)
: GetNonEmployedHoursForWeekWhenStarting(date, week, isTargetWeek, consultant);

return new WeeklyHours(
week.ToSortableInt(), Math.Min(hoursOutsideEmployment, maxWorkHoursForWeek)
);
})
.ToList();
}

private static double GetNonEmployedHoursForWeekWhenStarting(DateOnly? startDate, Week week, Boolean isStartWeek,
Consultant consultant)
{
var hasStarted = startDate < week.FirstDayOfWorkWeek();
var dayDifference = Math.Max((week.LastWorkDayOfWeek().ToDateTime(new TimeOnly()) - startDate.Value.ToDateTime(new TimeOnly())).Days, 0);

Check warning on line 218 in backend/Api/StaffingController/ReadModelFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (7.x.x)

Nullable value type may be null.

return isStartWeek ? dayDifference * consultant.Department.Organization.HoursPerWorkday :
hasStarted ? 0 : consultant.Department.Organization.HoursPerWorkday * 5 ;
}

private static double GetNonEmployedHoursForWeekWhenQuitting(DateOnly? endDate, Week week, bool isFinalWeek,
Consultant consultant)
{
var hasQuit = endDate < week.FirstDayOfWorkWeek();
var dayDifference = Math.Max((endDate.Value.ToDateTime(new TimeOnly()) - week.FirstDayOfWorkWeek().ToDateTime(new TimeOnly())).Days, 0);

Check warning on line 228 in backend/Api/StaffingController/ReadModelFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (7.x.x)

Nullable value type may be null.

return isFinalWeek ? dayDifference * consultant.Department.Organization.HoursPerWorkday :
hasQuit ? consultant.Department.Organization.HoursPerWorkday * 5 : 0;
}



private static DetailedBooking CreateNotStartedOrQuitDetailedBooking(List<WeeklyHours> weeks)
{
return new DetailedBooking(
new BookingDetails("Ikke startet eller sluttet", BookingType.NotStartedOrQuit,
"Ikke startet eller sluttet",
0, true), //Empty projectName as NotStartedOrQuit does not have a project, 0 as projectId as NotStartedOrQuit since its just used to mark not started or quit
weeks
);
}

private static BookedHoursPerWeek GetBookedHours(Week week, IEnumerable<DetailedBooking> detailedBookings,
Consultant consultant)
{
Expand All @@ -192,6 +264,10 @@
var totalAbsence = DetailedBooking.GetTotalHoursPrBookingTypeAndWeek(detailedBookingsArray,
BookingType.PlannedAbsence,
week);

var totalNotStartedOrQuit =
DetailedBooking.GetTotalHoursPrBookingTypeAndWeek(detailedBookingsArray, BookingType.NotStartedOrQuit,
week);

var totalExludableAbsence = detailedBookingsArray
.Where(s => s.BookingDetails.Type == BookingType.PlannedAbsence && s.BookingDetails.ExcludeFromBilling )
Expand All @@ -202,7 +278,7 @@
BookingType.Vacation,
week);

var bookedTime = totalBillable + totalAbsence + totalVacations + totalHolidayHours + totalNonBillable;
var bookedTime = totalBillable + totalAbsence + totalVacations + totalHolidayHours + totalNonBillable + totalNotStartedOrQuit;
var hoursPrWorkDay = consultant.Department.Organization.HoursPerWorkday;

var totalSellableTime =
Expand All @@ -218,7 +294,7 @@
GetDatesForWeek(week),
new WeeklyBookingReadModel(totalBillable, totalOffered, totalAbsence, totalExludableAbsence, totalSellableTime,
totalHolidayHours, totalVacations,
totalOverbooked)
totalOverbooked, totalNotStartedOrQuit)
);
}

Expand Down
6 changes: 4 additions & 2 deletions backend/Api/StaffingController/StaffingReadModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public record WeeklyBookingReadModel(
[property: Required] double TotalSellableTime,
[property: Required] double TotalHolidayHours,
[property: Required] double TotalVacationHours,
[property: Required] double TotalOverbooking);
[property: Required] double TotalOverbooking,
[property: Required] double TotalNotStartedOrQuit);

public record BookingDetails(
[property: Required] string ProjectName,
Expand All @@ -104,5 +105,6 @@ public enum BookingType
Booking,
PlannedAbsence,
Vacation,
Available
Available,
NotStartedOrQuit
}
1 change: 1 addition & 0 deletions frontend/mockdata/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const MockWeeklyBookingReadModel: WeeklyBookingReadModel = {
totalOffered: 0,
totalVacationHours: 0,
totalExludableAbsence: 0,
totalNotStartedOrQuit: 0,
};

export const MockConsultants: ConsultantReadModel[] = [
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export enum BookingType {
PlannedAbsence = "PlannedAbsence",
Vacation = "Vacation",
Available = "Available",
NotStartedOrQuit = "NotStartedOrQuit",
}

export interface ConsultantReadModel {
Expand Down Expand Up @@ -273,7 +274,10 @@ export interface WeeklyBookingReadModel {
totalVacationHours: number;
/** @format double */
totalOverbooking: number;
/** @format double */
totalExludableAbsence: number;
/** @format double */
totalNotStartedOrQuit: number;
}

export interface WeeklyHours {
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/components/Staffing/DetailedBookingRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,9 @@ function DetailedBookingCell({
>
{isChangingHours &&
numWeeks <= 12 &&
detailedBooking.bookingDetails.type != BookingType.Vacation && (
detailedBooking.bookingDetails.type != BookingType.Vacation &&
detailedBooking.bookingDetails.type !=
BookingType.NotStartedOrQuit && (
<button
tabIndex={-1}
disabled={hours == 0}
Expand Down Expand Up @@ -368,7 +370,10 @@ function DetailedBookingCell({
step={workHoursPerDay}
value={hours}
draggable={true}
disabled={detailedBooking.bookingDetails.type == BookingType.Vacation}
disabled={
detailedBooking.bookingDetails.type == BookingType.Vacation ||
detailedBooking.bookingDetails.type == BookingType.NotStartedOrQuit
}
onChange={(e) =>
hourDragValue == undefined && setHours(Number(e.target.value))
}
Expand Down Expand Up @@ -401,7 +406,9 @@ function DetailedBookingCell({
></input>
{isChangingHours &&
numWeeks <= 12 &&
detailedBooking.bookingDetails.type != BookingType.Vacation && (
detailedBooking.bookingDetails.type != BookingType.Vacation &&
detailedBooking.bookingDetails.type !=
BookingType.NotStartedOrQuit && (
<button
tabIndex={-1}
className={`my-1 p-1 rounded-full hover:bg-primary/10 hidden ${
Expand Down
38 changes: 26 additions & 12 deletions frontend/src/components/Staffing/WeekCell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { BookedHoursPerWeek, ConsultantReadModel } from "@/api-types";
import { HoveredWeek } from "@/components/Staffing/HoveredWeek";
import InfoPill from "@/components/Staffing/InfoPill";
import { AlertTriangle, Coffee, FileText, Moon, Sun } from "react-feather";
import {
AlertTriangle,
Calendar,
Coffee,
FileText,
Moon,
Sun,
} from "react-feather";
import { getInfopillVariantByColumnCount } from "@/components/Staffing/helpers/utils";
import React from "react";

Expand Down Expand Up @@ -148,14 +155,27 @@ export function WeekCell(props: {
variant={getInfopillVariantByColumnCount(columnCount)}
/>
)}
{bookedHoursPerWeek.bookingModel.totalNotStartedOrQuit > 0 && (
<InfoPill
text={bookedHoursPerWeek.bookingModel.totalNotStartedOrQuit.toLocaleString(
"nb-No",
{
maximumFractionDigits: 1,
minimumFractionDigits: 0,
},
)}
colors="bg-absence/60 text-absence_darker border-absence_darker"
icon={<Calendar size="12" />}
variant={getInfopillVariantByColumnCount(columnCount)}
/>
)}
</div>
<p
className={`text-right ${
isListElementVisible ? "normal-medium" : "normal"
}`}
>
{bookedHoursPerWeek.bookingModel.totalPlannedAbsences > 0 &&
checkIfNotStartedOrQuit(consultant, bookedHoursPerWeek, numWorkHours)
{checkIfNotStartedOrQuit(consultant, bookedHoursPerWeek, numWorkHours)
? "-"
: bookedHoursPerWeek.bookingModel.totalBillable.toLocaleString(
"nb-No",
Expand All @@ -171,17 +191,11 @@ function checkIfNotStartedOrQuit(
bookedHoursPerWeek: BookedHoursPerWeek,
numWorkHours: number,
) {
const project = consultant.detailedBooking.find(
(b) => b.bookingDetails.projectName == "Ikke startet eller sluttet",
);
const hours = project?.hours.find(
(h) => h.week == bookedHoursPerWeek.sortableWeek,
);

if (!hours?.hours) return false;
const notStartedOrQuitHours =
bookedHoursPerWeek.bookingModel.totalNotStartedOrQuit;

return (
hours?.hours ==
notStartedOrQuitHours ==
numWorkHours - bookedHoursPerWeek.bookingModel.totalHolidayHours
);
}
13 changes: 12 additions & 1 deletion frontend/src/components/Staffing/helpers/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import {
EngagementState,
} from "@/api-types";
import React, { ReactElement } from "react";
import { Briefcase, Coffee, FileText, Moon, Sun } from "react-feather";
import {
Briefcase,
Calendar,
Coffee,
FileText,
Moon,
Sun,
} from "react-feather";
import { InfoPillVariant } from "@/components/Staffing/InfoPill";

export function getColorByStaffingType(type: BookingType): string {
Expand All @@ -20,6 +27,8 @@ export function getColorByStaffingType(type: BookingType): string {
return "bg-absence";
case BookingType.Available:
return "bg-available";
case BookingType.NotStartedOrQuit:
return "bg-absence/60";
default:
return "";
}
Expand Down Expand Up @@ -69,6 +78,8 @@ export function getIconByBookingType(
return <Moon size={size} className="text-absence_darker" />;
case BookingType.Available:
return <Coffee size={size} className="text-available_darker" />;
case BookingType.NotStartedOrQuit:
return <Calendar size={size} className="text-absence_darker/70" />;
default:
return <></>;
}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/staffing/useConsultantsFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ function setWeeklyInvoiceRate(
numWorkHours -
booking.bookingModel.totalHolidayHours -
booking.bookingModel.totalExludableAbsence -
booking.bookingModel.totalNotStartedOrQuit -
booking.bookingModel.totalVacationHours;

totalAvailableWeekHours += consultantAvailableWeekHours;
Expand Down
Loading