Project Job plus a sparkle of Task Assigning

24 views
Skip to first unread message

Andrea Perdonò

unread,
Jan 30, 2024, 5:47:36 AM1/30/24
to OptaPlanner development
Hello everyone!

I am working on building a scheduler that operates through REST calls. The objective is to handle N projects, each having temporal constraints for start and end dates, and consisting of N tasks with a predefined sequence (no need to order the tasks). The goal is to determine the start and end dates for individual tasks, considering their expected duration, delayed duration, and potential temporal buffer to be applied to the subsequent task. Additionally, once the scheduling is defined, the next step is to assign the optimal resource to each task based on department, required skills, while attempting to balance the overall workload.

To implement this, I'm contemplating whether to create a hybrid solution that combines aspects of Project Job Scheduling (PJ) and Task Assignment Scheduling (TA). Currently, I have defined a class SchedulerPJ as the @PlanningSolution, a class Project as @ProblemFact,  a class Task as @PlanningEntity. The Task has startDate and resource as @PlanningVariable:

  1. @PlanningVariable(valueRangeProviderRefs = {"risorsaRange"})
  2. private Risorsa risorsa;
  3. @JsonIgnore
  4. private Task taskPrecedente;
  5. @JsonProperty("idTaskPrecedente")
  6. private Long idTaskPrecedente;
  7. @JsonIgnore
  8. private Task taskSuccessiva;
  9. @JsonProperty("idTaskSuccessiva")
  10. private Long idTaskSuccessiva;
  11. @ShadowVariable(variableListenerClass = PredecessorDoneDateUpdatingVariableListener.class,
  12. sourceVariableName = "risorsa")
  13. public LocalDate dataInizioBuffer;
  14. @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
  15. @PiggybackShadowVariable(shadowVariableName = "dataInizioBuffer")
  16. private LocalDate dataFine;
  17. @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
  18. @PiggybackShadowVariable(shadowVariableName = "dataInizioBuffer")
  19. private LocalDate dataInizio;
  20. private LocalDate dataDiFineIdeale;

 A class named Resource which has an 

  1. @InverseRelationShadowVariable(sourceVariableName = "risorsa")
  2. private List<Task> tasks = new ArrayList<>();

and I have also implementede a VAriableListener thats update the Stard and End date of a Task:

  1. @Override
  2. public void afterEntityAdded(ScoreDirector<SchedulerPJ> scoreDirector, Task task) {
  3. updateDataInizio(scoreDirector, task);
  4. }

  5. @Override
  6. public void afterEntityRemoved(ScoreDirector<SchedulerPJ> scoreDirector, Task task) {
  7. updateDataInizio(scoreDirector, task);
  8. }
  9. private void updateDataInizio(ScoreDirector<SchedulerPJ> scoreDirector, Task task) {
  10. Task taskPrecedente = task.getTaskPrecedente();
  11. Task taskSuccessiva = task.getTaskSuccessiva();
  12. LocalDate dataInizioBuffer;
  13. LocalDate dataInizio;
  14. if (taskPrecedente == null) {
  15. dataInizioBuffer = task.getDataInizioBuffer();
  16. dataInizio = task.getProgetto().getDataInizio();
  17. } else {
  18. dataInizioBuffer = taskPrecedente.getDataFine();
  19. dataInizio = taskPrecedente.getDurataBuffer() > 0 ? dataInizioBuffer.plusDays(taskPrecedente.getDurataBuffer()) : dataInizioBuffer;
  20. }
  21. for (Task shadowTaskCorrente = task;
  22. shadowTaskCorrente != null && (!Objects.equals(shadowTaskCorrente.getDataInizioBuffer(), dataInizioBuffer)
  23. || !Objects.equals(shadowTaskCorrente.getDataInizio(), dataInizio));) {
  24. scoreDirector.beforeVariableChanged(shadowTaskCorrente, "dataInizioBuffer");
  25. shadowTaskCorrente.setDataInizioBuffer(dataInizioBuffer);
  26. scoreDirector.afterVariableChanged(shadowTaskCorrente, "dataInizioBuffer");
  27. scoreDirector.beforeVariableChanged(shadowTaskCorrente, "dataInizio");
  28. // Se la dataInizio è un fine settimana (sabato o domenica), salta al prossimo lunedì
  29. assert dataInizio != null;
  30. if (dataInizio.getDayOfWeek() == DayOfWeek.SATURDAY) {
  31. dataInizio = dataInizio.plusDays(2); // Salta il sabato e la domenica
  32. } else if (dataInizio.getDayOfWeek() == DayOfWeek.SUNDAY) {
  33. dataInizio = dataInizio.plusDays(1); // Salta la domenica
  34. }
  35. shadowTaskCorrente.setDataInizio(dataInizio);
  36. scoreDirector.afterVariableChanged(shadowTaskCorrente, "dataInizio");
  37. scoreDirector.beforeVariableChanged(shadowTaskCorrente, "dataFine");
  38. shadowTaskCorrente.setDataFine(getProssimaDataLavorativa(dataInizio, shadowTaskCorrente.getDurataGiorni()));
  39. scoreDirector.afterVariableChanged(shadowTaskCorrente, "dataFine");
  40. LocalDate endDateTime = shadowTaskCorrente.getDataFine();
  41. taskPrecedente = shadowTaskCorrente; //Imposto la task precedente con la task attuale
  42. shadowTaskCorrente = shadowTaskCorrente.getTaskSuccessiva() != null ? shadowTaskCorrente.getTaskSuccessiva() : null ; //Imposto la task attuale con la task successiva
  43. if (shadowTaskCorrente == null || endDateTime == null) {
  44. dataInizioBuffer = null;
  45. dataInizio = null;
  46. } else {
  47. dataInizioBuffer = endDateTime;
  48. dataInizio = taskPrecedente.getDurataBuffer() > 0 ? dataInizioBuffer.plusDays(taskPrecedente.getDurataBuffer()) : dataInizioBuffer;
  49. if (dataInizio.getDayOfWeek() == DayOfWeek.SATURDAY) {
  50. dataInizio = dataInizio.plusDays(2); // Salta il sabato e la domenica
  51. } else if (dataInizio.getDayOfWeek() == DayOfWeek.SUNDAY) {
  52. dataInizio = dataInizio.plusDays(1); // Salta la domenica
  53. }
  54. shadowTaskCorrente.setDataInizio(dataInizio);
  55. Log.info("Task precedente: " + taskPrecedente.getId()
  56. + " (Inizio: " + taskPrecedente.getDataInizio()
  57. + " | Fine: " + taskPrecedente.getDataFine()
  58. + " | Fine ideale: " + taskPrecedente.getDataDiFineIdeale()
  59. + " | Durata Prevista: " + taskPrecedente.getDurataGiorni()
  60. + " | Durata Reale: " + Period.between(taskPrecedente.getDataInizio(), taskPrecedente.getDataFine()).getDays()
  61. + " | Buffer: " + taskPrecedente.getDurataBuffer()
  62. + ")"
  63. + " Task corrente: " + shadowTaskCorrente.getId()
  64. + " (Inizio: " + shadowTaskCorrente.getDataInizio()
  65. + " | Fine: " + shadowTaskCorrente.getDataFine()
  66. + " | Fine ideale: " + shadowTaskCorrente.getDataDiFineIdeale()
  67. + " | Durata Prevista: " + shadowTaskCorrente.getDurataGiorni()
  68. + " | Durata Reale: " + Period.between(taskPrecedente.getDataInizio(), taskPrecedente.getDataFine()).getDays()
  69. + " | Buffer: " + shadowTaskCorrente.getDurataBuffer()
  70. + ")");
  71. if(taskPrecedente.getRisorsa() != null){
  72. updateCaricoDiLavoro(scoreDirector, taskPrecedente);
  73. }
  74. }
  75. }
  76. }
  77. private void updateCaricoDiLavoro(ScoreDirector<SchedulerPJ> scoreDirector, Task task) {
  78. Risorsa risorsa = task.getRisorsa();
  79. if (risorsa == null) {
  80. return;
  81. }
  82. LocalDate dataInizio = task.getDataInizio();
  83. LocalDate dataFine = task.getDataFine();
  84. int durataGiorni = task.getDurataGiorni();
  85. for (CalendarioLavorativo calendarioLavorativo : risorsa.getCalendarioLavorativo()) {
  86. LocalDate dataInizioSettimana = calendarioLavorativo.getDataInizioSettimana();
  87. LocalDate dataFineSettimana = calendarioLavorativo.getDataFineSettimana();
  88. //Data di Inzio e data di Fine della Task ricadono nella stessa settimana
  89. if((dataInizio.isAfter(dataInizioSettimana) || dataInizio.isEqual(dataInizioSettimana))
  90. && (dataFine.isBefore(dataFineSettimana) || dataFine.isEqual(dataFineSettimana))){
  91. int numeroSettimana = calendarioLavorativo.getNumeroSettimana();
  92. risorsa.getCaricoDiLavoro().stream().filter(caricoDiLavoro -> caricoDiLavoro.getNumeroSettimana() == numeroSettimana).findFirst().ifPresent(caricoDiLavoro -> {
  93. scoreDirector.beforeVariableChanged(task, "risorsa");
  94. caricoDiLavoro.setCaricoDiLavoroSettimanale(caricoDiLavoro.getCaricoDiLavoroSettimanale() + colcalaCaricoDilavoro(durataGiorni));
  95. scoreDirector.afterVariableChanged(task, "risorsa");
  96. });
  97. }
  98. //
  99. }
  100. Log.info("Carico di lavoro della risorsa " + risorsa.getId() + " aggiornato: " +
  101. risorsa.getCaricoDiLavoro().stream()
  102. .filter(carico -> carico.getCaricoDiLavoroSettimanale() > 0)
  103. .map(carico -> carico.getCaricoDiLavoroSettimanale() + "%")
  104. .collect(Collectors.joining(", "))
  105. + " | Task: " + task.getId() + " | Durata: " + durataGiorni + " giorni");

  106. }
With the Idea of updating also the caricoDiLavoro variable of Resource (Risorsa). But honestly? I have no idea if I'm using this tool correctly. Do you have any suggestion on how I can procced to realize this project? Thank you!

Reply all
Reply to author
Forward
0 new messages