I have a problem with fact collection DaySetting.
public class DaySetting {
String businessDate;
Integer weekNo;
Integer monthNo;
}
I have another class shift:
public class Shift {
@PlanningId
private String id;
private LocalDateTime start;
private LocalDateTime end;
public Long getShiftDuration() {
long minutes = ChronoUnit.MINUTES.between(start, end);
return minutes;
}
public String getStartDate() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String dateStr = getStart().toLocalDate().format(formatter);
return dateStr;
}
}
The day setting can be taken the values like:
public class DaySetting {
String businessDate = 2024-06-01;
Integer weekNo = 1; // first week of June
Integer monthNo = 6;//June
}
public class DaySetting {
String businessDate = 2024-06-02;
Integer weekNo = 1; // first week of June
Integer monthNo = 6;//June
}
public class DaySetting {
String businessDate = 2024-06-09;
Integer weekNo = 2; // Second week of June
Integer monthNo = 6;//June
}
public class DaySetting {
String businessDate = 2024-06-10;
Integer weekNo = 2; // second week of June
Integer monthNo = 6;//June
}
I have to sum up the duration of the work based on weekNo value. for WeekNo 1 there are 2 shifts, it will be summed up. The first two shift durations will be summed up for week 1,
The later two shifts will be summed up for the week 2.
So, far what I tried below:
Constraint workingHoursLessThanMaximumInContractPerWeek(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(DaySetting.class)
.join(Shift.class)
.groupBy((daySetting, shift) -> day.getWeekNo(),
ConstraintCollectors.sumLong((daySetting, shift) -> shift.getShiftDuration()))
//Shift don't have weekNo field. It only have getStartDate() to get the business date,
//so we have to take the date value from DaySettings class and match with the date of Shift class. How to achieve this with the constraints.
.penalize(HardMediumSoftScore.ONE_SOFT, ((weekNo, sumValue))->{
return sumValue;
})
.asConstraint("Working Hours Greater Than Maximum In Contract Per Week");
}
I would recommend putting DaySetting
in Shift
to avoid ambiguities arising from Shifts spanning multiple days (such as night shifts). Additionally, year should also be included, otherwise issues can arise when planning schedules across year boundaries. I would create a WeekIdentifier
record that maps a LocalDate
to a Year + Week of year:
public record WeekIdentifier(long year, long weekInYear) {
private final static WeekFields WEEK_DEFINITION = WeekFields.of(DayOfWeek.MONDAY, 7);
public static WeekIdentifier forDate(LocalDate date) {
return new WeekIdentifier(WEEK_DEFINITION.weekBasedYear().getFrom(date),
WEEK_DEFINITION.weekOfWeekBasedYear().getFrom(date));
}
}
Then I would add a method for getting the WeekIdentifer
of a Shift
:
// Assumption, Shift is a @PlanningEntity with a @PlanningVariable employee
@PlanningEntity
public class Shift {
@PlanningId
private String id;
private LocalDateTime start;
private LocalDateTime end;
@PlanningVariable
private Employee employee;
public Long getShiftDuration() {
long minutes = ChronoUnit.MINUTES.between(start, end);
return minutes;
}
public WeekIdentifier getWeekIdentifier() {
return WeekIdentifier.forDate(getStart().toLocalDate());
}
public Employee getEmployee() {
return employee;
}
}
Then I would write the Constriant
like this:
Constraint workingHoursLessThanMaximumInContractPerWeek(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Shift.class)
.groupBy(Shift::getEmployee,
Shift::getWeekIdentifier,
ConstraintCollectors.sumLong(Shift::getShiftDuration))
.filter((employee, week, minutes) -> minutes > employee.getMaximumMinutesPerWeek())
.penalize(HardMediumSoftScore.ONE_SOFT, (employee, week, minutes) -> {
return minutes - employee.getMaximumMinutesPerWeek();
})
.asConstraint("Working Hours Greater Than Maximum In Contract Per Week");
}