S8 apply Palantir formatting to maintained Java sources
All checks were successful
Build, test and publish / verify (push) Successful in 1m39s
All checks were successful
Build, test and publish / verify (push) Successful in 1m39s
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
package nl.agilecc.agiledancers.model;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -19,9 +18,7 @@ public abstract class AbstractEntity {
|
||||
}
|
||||
|
||||
private <T> Set<T> emptyIfNull(Set<T> set) {
|
||||
return set == null ?
|
||||
new HashSet<>() :
|
||||
set;
|
||||
return set == null ? new HashSet<>() : set;
|
||||
}
|
||||
|
||||
protected <T> Set<T> unmodifiableNullSafe(Set<T> set) {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package nl.agilecc.agiledancers.model;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import lombok.*;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@Builder
|
||||
@@ -15,21 +14,27 @@ public class DanceGroup extends AbstractEntity {
|
||||
@ToString.Include
|
||||
@Setter
|
||||
private String category;
|
||||
|
||||
@ToString.Include
|
||||
@Setter
|
||||
private DanceGroupType type;
|
||||
|
||||
@ToString.Include
|
||||
@Setter
|
||||
private int startAge;
|
||||
|
||||
@ToString.Include
|
||||
@Setter
|
||||
private int endAge;
|
||||
|
||||
@ToString.Include
|
||||
@Builder.Default
|
||||
private final Set<Dancer> dancers = new LinkedHashSet<>();
|
||||
|
||||
@ToString.Include
|
||||
@Builder.Default
|
||||
private final Set<DanceGroupStyle> styles = new LinkedHashSet<>();
|
||||
|
||||
@ToString.Include
|
||||
private Coach coach;
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package nl.agilecc.agiledancers.model;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@@ -12,14 +11,19 @@ import java.util.LinkedHashSet;
|
||||
public class DanceSchool extends AbstractEntity {
|
||||
@Builder.Default
|
||||
private final java.util.UUID key = java.util.UUID.randomUUID();
|
||||
|
||||
@Setter
|
||||
private String name;
|
||||
|
||||
@Builder.Default
|
||||
private final Set<Dancer> dancers = new LinkedHashSet<>();
|
||||
|
||||
@Builder.Default
|
||||
private final Set<DanceGroup> danceGroups = new LinkedHashSet<>();
|
||||
|
||||
@Builder.Default
|
||||
private final Set<Show> shows = new LinkedHashSet<>();
|
||||
|
||||
@Builder.Default
|
||||
private final Set<Coach> coaches = new LinkedHashSet<>();
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package nl.agilecc.agiledancers.model;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@@ -13,6 +12,7 @@ public class Dancer extends AbstractEntity {
|
||||
@ToString.Include
|
||||
@Setter
|
||||
private String name;
|
||||
|
||||
@ToString.Include
|
||||
@Setter
|
||||
private LocalDate birthDate;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package nl.agilecc.agiledancers.model;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.*;
|
||||
import nl.agilecc.agiledancers.model.specifications.DayPartType;
|
||||
|
||||
import java.util.List;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@Builder
|
||||
@@ -13,7 +12,9 @@ import java.time.LocalDate;
|
||||
public class DayPart extends AbstractEntity {
|
||||
@ToString.Include
|
||||
private final DayPartType type;
|
||||
|
||||
private final LocalDate date;
|
||||
|
||||
@ToString.Include
|
||||
@Setter
|
||||
private List<DanceGroup> danceGroups;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package nl.agilecc.agiledancers.model;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.*;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
|
||||
@Getter
|
||||
@@ -13,8 +12,10 @@ import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
public class Show extends AbstractEntity {
|
||||
@Setter
|
||||
private String name;
|
||||
|
||||
@Setter
|
||||
private Map<DanceGroupType, Integer> appearances;
|
||||
|
||||
@ToString.Include
|
||||
@Setter
|
||||
private List<DayPart> dayParts;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package nl.agilecc.agiledancers.model.specifications;
|
||||
|
||||
public enum DanceGroupStyle {
|
||||
HIPHOP, JAZZ, MODERN, STREETDANCE, CLASSIC, BREAKDANCE
|
||||
HIPHOP,
|
||||
JAZZ,
|
||||
MODERN,
|
||||
STREETDANCE,
|
||||
CLASSIC,
|
||||
BREAKDANCE
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package nl.agilecc.agiledancers.model.specifications;
|
||||
|
||||
public enum DanceGroupType {
|
||||
SHOW_CASE, PRE_EDUCATION, SELECTION, REGULAR, PRE_SCHOOLER
|
||||
SHOW_CASE,
|
||||
PRE_EDUCATION,
|
||||
SELECTION,
|
||||
REGULAR,
|
||||
PRE_SCHOOLER
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package nl.agilecc.agiledancers.model.specifications;
|
||||
|
||||
public enum DayPartType {
|
||||
MORNING, AFTERNOON, EVENING;
|
||||
MORNING,
|
||||
AFTERNOON,
|
||||
EVENING;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package nl.agilecc.agiledancers.repository;
|
||||
|
||||
import nl.agilecc.agiledancers.model.AbstractEntity;
|
||||
|
||||
import java.util.*;
|
||||
import nl.agilecc.agiledancers.model.AbstractEntity;
|
||||
|
||||
public abstract class AbstractRepository<T extends AbstractEntity> {
|
||||
private long sequence = 0L;
|
||||
@@ -28,6 +27,7 @@ public abstract class AbstractRepository<T extends AbstractEntity> {
|
||||
public T findById(long id) {
|
||||
return entities.get(id);
|
||||
}
|
||||
|
||||
public void delete(long id) {
|
||||
entities.remove(id);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,4 @@ package nl.agilecc.agiledancers.repository;
|
||||
|
||||
import nl.agilecc.agiledancers.model.Coach;
|
||||
|
||||
public class CoachRepository extends AbstractRepository<Coach> {
|
||||
|
||||
}
|
||||
public class CoachRepository extends AbstractRepository<Coach> {}
|
||||
|
||||
@@ -2,5 +2,4 @@ package nl.agilecc.agiledancers.repository;
|
||||
|
||||
import nl.agilecc.agiledancers.model.DanceGroup;
|
||||
|
||||
public class DanceGroupRepository extends AbstractRepository<DanceGroup> {
|
||||
}
|
||||
public class DanceGroupRepository extends AbstractRepository<DanceGroup> {}
|
||||
|
||||
@@ -2,5 +2,4 @@ package nl.agilecc.agiledancers.repository;
|
||||
|
||||
import nl.agilecc.agiledancers.model.DanceSchool;
|
||||
|
||||
public class DanceSchoolRepository extends AbstractRepository<DanceSchool> {
|
||||
}
|
||||
public class DanceSchoolRepository extends AbstractRepository<DanceSchool> {}
|
||||
|
||||
@@ -2,5 +2,4 @@ package nl.agilecc.agiledancers.repository;
|
||||
|
||||
import nl.agilecc.agiledancers.model.Dancer;
|
||||
|
||||
public class DancerRepository extends AbstractRepository<Dancer> {
|
||||
}
|
||||
public class DancerRepository extends AbstractRepository<Dancer> {}
|
||||
|
||||
@@ -2,5 +2,4 @@ package nl.agilecc.agiledancers.repository;
|
||||
|
||||
import nl.agilecc.agiledancers.model.DayPart;
|
||||
|
||||
public class DayPartRepository extends AbstractRepository<DayPart> {
|
||||
}
|
||||
public class DayPartRepository extends AbstractRepository<DayPart> {}
|
||||
|
||||
@@ -2,5 +2,4 @@ package nl.agilecc.agiledancers.repository;
|
||||
|
||||
import nl.agilecc.agiledancers.model.Show;
|
||||
|
||||
public class ShowRepository extends AbstractRepository<Show> {
|
||||
}
|
||||
public class ShowRepository extends AbstractRepository<Show> {}
|
||||
|
||||
@@ -5,12 +5,10 @@ import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.*;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nl.agilecc.agiledancers.rest.apimodel.GroupRequest;
|
||||
import nl.agilecc.agiledancers.rest.apimodel.GroupView;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
|
||||
@Path("/api/dance-schools/{schoolId}/dance-groups")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -35,15 +33,26 @@ public class DanceGroupResource {
|
||||
|
||||
@POST
|
||||
public Response create(@NotNull @Valid GroupRequest request, @Context UriInfo uriInfo) {
|
||||
var entity = service.createGroup(schoolId, request.category(), request.type(), request.startAge(), request.endAge(), request.styles());
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(entity.getId().toString()).build())
|
||||
.entity(GroupView.of(entity)).build();
|
||||
var entity = service.createGroup(
|
||||
schoolId, request.category(), request.type(), request.startAge(), request.endAge(), request.styles());
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder()
|
||||
.path(entity.getId().toString())
|
||||
.build())
|
||||
.entity(GroupView.of(entity))
|
||||
.build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
public GroupView update(@PathParam("id") long id, @NotNull @Valid GroupRequest request) {
|
||||
return GroupView.of(service.updateGroup(schoolId, id, request.category(), request.type(), request.startAge(), request.endAge(), request.styles()));
|
||||
return GroupView.of(service.updateGroup(
|
||||
schoolId,
|
||||
id,
|
||||
request.category(),
|
||||
request.type(),
|
||||
request.startAge(),
|
||||
request.endAge(),
|
||||
request.styles()));
|
||||
}
|
||||
|
||||
@DELETE
|
||||
|
||||
@@ -5,12 +5,10 @@ import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.*;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nl.agilecc.agiledancers.rest.apimodel.SchoolRequest;
|
||||
import nl.agilecc.agiledancers.rest.apimodel.SchoolView;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
|
||||
@Path("/api/dance-schools")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -33,8 +31,11 @@ public class DanceSchoolResource {
|
||||
@POST
|
||||
public Response create(@NotNull @Valid SchoolRequest request, @Context UriInfo uriInfo) {
|
||||
var entity = service.createSchool(request.name());
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(entity.getId().toString()).build())
|
||||
.entity(SchoolView.of(entity)).build();
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder()
|
||||
.path(entity.getId().toString())
|
||||
.build())
|
||||
.entity(SchoolView.of(entity))
|
||||
.build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
||||
@@ -5,12 +5,10 @@ import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.*;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nl.agilecc.agiledancers.rest.apimodel.DancerRequest;
|
||||
import nl.agilecc.agiledancers.rest.apimodel.DancerView;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
|
||||
@Path("/api/dance-schools/{schoolId}/dancers")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -36,8 +34,11 @@ public class DancerResource {
|
||||
@POST
|
||||
public Response create(@NotNull @Valid DancerRequest request, @Context UriInfo uriInfo) {
|
||||
var entity = service.createDancer(schoolId, request.name(), request.birthDate());
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(entity.getId().toString()).build())
|
||||
.entity(DancerView.of(entity)).build();
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder()
|
||||
.path(entity.getId().toString())
|
||||
.build())
|
||||
.entity(DancerView.of(entity))
|
||||
.build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
||||
@@ -4,8 +4,8 @@ import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import nl.agilecc.agiledancers.service.ManagementException;
|
||||
import nl.agilecc.agiledancers.rest.apimodel.ErrorView;
|
||||
import nl.agilecc.agiledancers.service.ManagementException;
|
||||
|
||||
@Provider
|
||||
public class ManagementExceptionMapper implements ExceptionMapper<ManagementException> {
|
||||
@@ -16,7 +16,9 @@ public class ManagementExceptionMapper implements ExceptionMapper<ManagementExce
|
||||
case INVALID_INPUT -> 400;
|
||||
case CONFLICT -> 409;
|
||||
};
|
||||
return Response.status(status).type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.entity(new ErrorView(exception.getMessage())).build();
|
||||
return Response.status(status)
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.entity(new ErrorView(exception.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,50 @@
|
||||
package nl.agilecc.agiledancers.rest;
|
||||
|
||||
import nl.agilecc.agiledancers.service.model.*;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.*;
|
||||
import nl.agilecc.agiledancers.service.*;
|
||||
import nl.agilecc.agiledancers.rest.apimodel.SchoolView;
|
||||
import nl.agilecc.agiledancers.service.*;
|
||||
import nl.agilecc.agiledancers.service.model.*;
|
||||
|
||||
@Path("/api/dance-schools")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class SchoolSnapshotResource {
|
||||
@Inject SchoolManagementService service;
|
||||
@Inject ObjectMapper mapper;
|
||||
@Inject
|
||||
SchoolManagementService service;
|
||||
|
||||
@GET @Path("/{id}/export")
|
||||
@Inject
|
||||
ObjectMapper mapper;
|
||||
|
||||
@GET
|
||||
@Path("/{id}/export")
|
||||
public Response export(@PathParam("id") long id) throws Exception {
|
||||
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(service.exportSchool(id));
|
||||
return Response.ok(json).header("Content-Disposition", "attachment; filename=school-" + id + ".json").build();
|
||||
return Response.ok(json)
|
||||
.header("Content-Disposition", "attachment; filename=school-" + id + ".json")
|
||||
.build();
|
||||
}
|
||||
@POST @Path("/import/preview") @Consumes(MediaType.APPLICATION_JSON)
|
||||
public ImportPreview preview(String json) { return service.preview(parse(json)); }
|
||||
|
||||
@POST @Path("/import") @Consumes(MediaType.APPLICATION_JSON)
|
||||
@POST
|
||||
@Path("/import/preview")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public ImportPreview preview(String json) {
|
||||
return service.preview(parse(json));
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/import")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public SchoolView apply(String json, @QueryParam("replaceId") Long replaceId) {
|
||||
return SchoolView.of(service.importSchool(parse(json), replaceId));
|
||||
}
|
||||
|
||||
private SchoolSnapshot parse(String json) {
|
||||
try {
|
||||
SchoolSnapshot snapshot = mapper.copy().enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
SchoolSnapshot snapshot = mapper.copy()
|
||||
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
|
||||
.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
|
||||
.disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
|
||||
@@ -38,7 +53,8 @@ public class SchoolSnapshotResource {
|
||||
if (snapshot == null) throw new IllegalArgumentException("Het bestand bevat geen school");
|
||||
return snapshot;
|
||||
} catch (Exception e) {
|
||||
throw new ManagementException(ManagementExceptionKind.INVALID_INPUT, "Ongeldig JSON-bestand: " + e.getMessage());
|
||||
throw new ManagementException(
|
||||
ManagementExceptionKind.INVALID_INPUT, "Ongeldig JSON-bestand: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,13 @@ import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.*;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
import nl.agilecc.agiledancers.service.SchedulingService;
|
||||
import java.util.List;
|
||||
import nl.agilecc.agiledancers.model.DayPart;
|
||||
import nl.agilecc.agiledancers.rest.apimodel.SchedulingView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nl.agilecc.agiledancers.rest.apimodel.ShowRequest;
|
||||
import nl.agilecc.agiledancers.rest.apimodel.ShowView;
|
||||
import nl.agilecc.agiledancers.service.SchedulingService;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
|
||||
@Path("/api/dance-schools/{schoolId}/shows")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -42,8 +40,11 @@ public class ShowResource {
|
||||
@POST
|
||||
public Response create(@NotNull @Valid ShowRequest request, @Context UriInfo uriInfo) {
|
||||
var entity = service.createShow(schoolId, request.name(), parts(request), request.appearances());
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(entity.getId().toString()).build())
|
||||
.entity(ShowView.of(entity)).build();
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder()
|
||||
.path(entity.getId().toString())
|
||||
.build())
|
||||
.entity(ShowView.of(entity))
|
||||
.build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@@ -53,8 +54,13 @@ public class ShowResource {
|
||||
}
|
||||
|
||||
private static List<DayPart> parts(ShowRequest request) {
|
||||
return request.dayParts().stream().map(p -> DayPart.builder()
|
||||
.date(p.date()).type(p.type()).danceGroups(List.of()).build()).toList();
|
||||
return request.dayParts().stream()
|
||||
.map(p -> DayPart.builder()
|
||||
.date(p.date())
|
||||
.type(p.type())
|
||||
.danceGroups(List.of())
|
||||
.build())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -62,8 +68,8 @@ public class ShowResource {
|
||||
@Consumes(MediaType.WILDCARD)
|
||||
public SchedulingView schedule(@PathParam("id") long id) {
|
||||
var result = scheduling.schedule(schoolId, id);
|
||||
return new SchedulingView(result.optimal(), result.optimal()
|
||||
? "Optimal balance" : "Valid result; optimal balance not proven");
|
||||
return new SchedulingView(
|
||||
result.optimal(), result.optimal() ? "Optimal balance" : "Valid result; optimal balance not proven");
|
||||
}
|
||||
|
||||
@DELETE
|
||||
|
||||
@@ -5,4 +5,5 @@ import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.PastOrPresent;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record DancerRequest(@NotBlank String name, @NotNull @PastOrPresent LocalDate birthDate) {}
|
||||
public record DancerRequest(
|
||||
@NotBlank String name, @NotNull @PastOrPresent LocalDate birthDate) {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package nl.agilecc.agiledancers.rest.apimodel;
|
||||
|
||||
import nl.agilecc.agiledancers.model.Dancer;
|
||||
import java.time.LocalDate;
|
||||
import nl.agilecc.agiledancers.model.Dancer;
|
||||
|
||||
public record DancerView(long id, String name, LocalDate birthDate) {
|
||||
public static DancerView of(Dancer dancer) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package nl.agilecc.agiledancers.rest.apimodel;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDate;
|
||||
import nl.agilecc.agiledancers.model.specifications.DayPartType;
|
||||
public record DayPartRequest(@NotNull LocalDate date, @NotNull DayPartType type) {}
|
||||
|
||||
public record DayPartRequest(
|
||||
@NotNull LocalDate date, @NotNull DayPartType type) {}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package nl.agilecc.agiledancers.rest.apimodel;
|
||||
import nl.agilecc.agiledancers.model.specifications.DayPartType;
|
||||
import java.util.List;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import nl.agilecc.agiledancers.model.specifications.DayPartType;
|
||||
|
||||
public record DayPartView(LocalDate date, DayPartType type, List<Long> danceGroupIds) {}
|
||||
|
||||
@@ -3,10 +3,13 @@ package nl.agilecc.agiledancers.rest.apimodel;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
||||
import java.util.Set;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
|
||||
public record GroupRequest(@NotBlank String category, @NotNull DanceGroupType type,
|
||||
@NotNull @Min(0) Integer startAge, @NotNull @Min(0) Integer endAge,
|
||||
@NotNull Set<@NotNull DanceGroupStyle> styles) {}
|
||||
public record GroupRequest(
|
||||
@NotBlank String category,
|
||||
@NotNull DanceGroupType type,
|
||||
@NotNull @Min(0) Integer startAge,
|
||||
@NotNull @Min(0) Integer endAge,
|
||||
@NotNull Set<@NotNull DanceGroupStyle> styles) {}
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
package nl.agilecc.agiledancers.rest.apimodel;
|
||||
|
||||
import nl.agilecc.agiledancers.model.DanceGroup;
|
||||
import nl.agilecc.agiledancers.model.Dancer;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import nl.agilecc.agiledancers.model.DanceGroup;
|
||||
import nl.agilecc.agiledancers.model.Dancer;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
|
||||
public record GroupView(long id, String category, DanceGroupType type, int startAge, int endAge,
|
||||
Set<DanceGroupStyle> styles, List<Long> dancerIds) {
|
||||
public record GroupView(
|
||||
long id,
|
||||
String category,
|
||||
DanceGroupType type,
|
||||
int startAge,
|
||||
int endAge,
|
||||
Set<DanceGroupStyle> styles,
|
||||
List<Long> dancerIds) {
|
||||
public static GroupView of(DanceGroup group) {
|
||||
return new GroupView(group.getId(), group.getCategory(), group.getType(), group.getStartAge(),
|
||||
group.getEndAge(), Set.copyOf(group.getStyles()),
|
||||
return new GroupView(
|
||||
group.getId(),
|
||||
group.getCategory(),
|
||||
group.getType(),
|
||||
group.getStartAge(),
|
||||
group.getEndAge(),
|
||||
Set.copyOf(group.getStyles()),
|
||||
group.getDancers().stream().map(Dancer::getId).toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
package nl.agilecc.agiledancers.rest.apimodel;
|
||||
|
||||
public record SchedulingView(boolean optimal, String message) {}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package nl.agilecc.agiledancers.rest.apimodel;
|
||||
|
||||
import java.util.List;
|
||||
import nl.agilecc.agiledancers.model.DanceGroup;
|
||||
import nl.agilecc.agiledancers.model.DanceSchool;
|
||||
import nl.agilecc.agiledancers.model.Dancer;
|
||||
import nl.agilecc.agiledancers.model.DanceGroup;
|
||||
import nl.agilecc.agiledancers.model.Show;
|
||||
import java.util.List;
|
||||
|
||||
public record SchoolView(long id, String name, List<Long> dancerIds,
|
||||
List<Long> danceGroupIds, List<Long> showIds) {
|
||||
public record SchoolView(long id, String name, List<Long> dancerIds, List<Long> danceGroupIds, List<Long> showIds) {
|
||||
public static SchoolView of(DanceSchool school) {
|
||||
return new SchoolView(school.getId(), school.getName(),
|
||||
return new SchoolView(
|
||||
school.getId(),
|
||||
school.getName(),
|
||||
school.getDancers().stream().map(Dancer::getId).toList(),
|
||||
school.getDanceGroups().stream().map(DanceGroup::getId).toList(),
|
||||
school.getShows().stream().map(Show::getId).toList());
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package nl.agilecc.agiledancers.rest.apimodel;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.util.*;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
public record ShowRequest(@NotBlank String name, @NotNull List<@NotNull @Valid DayPartRequest> dayParts,
|
||||
Map<DanceGroupType, Integer> appearances) {}
|
||||
|
||||
public record ShowRequest(
|
||||
@NotBlank String name,
|
||||
@NotNull List<@NotNull @Valid DayPartRequest> dayParts,
|
||||
Map<DanceGroupType, Integer> appearances) {}
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
package nl.agilecc.agiledancers.rest.apimodel;
|
||||
|
||||
import java.util.*;
|
||||
import nl.agilecc.agiledancers.model.*;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
import java.util.*;
|
||||
|
||||
public record ShowView(long id, String name, List<DayPartView> dayParts, Map<DanceGroupType, Integer> appearances) {
|
||||
public static ShowView of(Show show) {
|
||||
return new ShowView(show.getId(), show.getName(), show.getDayParts().stream()
|
||||
.map(p -> new DayPartView(p.getDate(), p.getType(), p.getDanceGroups().stream().map(DanceGroup::getId).toList())).toList(), show.getAppearances());
|
||||
return new ShowView(
|
||||
show.getId(),
|
||||
show.getName(),
|
||||
show.getDayParts().stream()
|
||||
.map(p -> new DayPartView(
|
||||
p.getDate(),
|
||||
p.getType(),
|
||||
p.getDanceGroups().stream()
|
||||
.map(DanceGroup::getId)
|
||||
.toList()))
|
||||
.toList(),
|
||||
show.getAppearances());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package nl.agilecc.agiledancers.service;
|
||||
|
||||
import java.util.Set;
|
||||
import nl.agilecc.agiledancers.model.Coach;
|
||||
import nl.agilecc.agiledancers.model.DanceGroup;
|
||||
import nl.agilecc.agiledancers.model.Dancer;
|
||||
@@ -7,12 +8,17 @@ import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
import nl.agilecc.agiledancers.repository.DanceGroupRepository;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class DanceGroupService {
|
||||
private final DanceGroupRepository danceGroupRepository = new DanceGroupRepository();
|
||||
|
||||
public DanceGroup add(String category, DanceGroupType type, int startAge, int endAge, Set<Dancer> dancers, Set<DanceGroupStyle> styles, Coach coach) {
|
||||
public DanceGroup add(
|
||||
String category,
|
||||
DanceGroupType type,
|
||||
int startAge,
|
||||
int endAge,
|
||||
Set<Dancer> dancers,
|
||||
Set<DanceGroupStyle> styles,
|
||||
Coach coach) {
|
||||
DanceGroup danceGroup = DanceGroup.builder()
|
||||
.category(category)
|
||||
.type(type)
|
||||
@@ -20,7 +26,8 @@ public class DanceGroupService {
|
||||
.endAge(endAge)
|
||||
.dancers(dancers)
|
||||
.styles(styles)
|
||||
.coach(coach).build();
|
||||
.coach(coach)
|
||||
.build();
|
||||
|
||||
return danceGroupRepository.add(danceGroup);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package nl.agilecc.agiledancers.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import nl.agilecc.agiledancers.model.Dancer;
|
||||
import nl.agilecc.agiledancers.repository.DancerRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class DancerService {
|
||||
private final DancerRepository dancerRepository = new DancerRepository();
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
package nl.agilecc.agiledancers.service;
|
||||
|
||||
public enum ManagementExceptionKind { NOT_FOUND, INVALID_INPUT, CONFLICT }
|
||||
public enum ManagementExceptionKind {
|
||||
NOT_FOUND,
|
||||
INVALID_INPUT,
|
||||
CONFLICT
|
||||
}
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
package nl.agilecc.agiledancers.service;
|
||||
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingResult;
|
||||
import nl.agilecc.agiledancers.service.port.SchedulingEngine;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingResult;
|
||||
import nl.agilecc.agiledancers.service.port.SchedulingEngine;
|
||||
|
||||
@Singleton
|
||||
public class SchedulingService {
|
||||
@Inject SchoolManagementService schools;
|
||||
@Inject SchedulingEngine engine;
|
||||
@Inject
|
||||
SchoolManagementService schools;
|
||||
|
||||
@Inject
|
||||
SchedulingEngine engine;
|
||||
|
||||
public SchedulingResult schedule(long schoolId, long showId) {
|
||||
var input = schools.beginScheduling(schoolId, showId);
|
||||
try {
|
||||
var result = engine.solve(input);
|
||||
schools.finishScheduling(schoolId, input, result);
|
||||
return result;
|
||||
} catch (ManagementException e) { throw e; }
|
||||
catch (RuntimeException | LinkageError e) {
|
||||
throw new ManagementException(ManagementExceptionKind.INVALID_INPUT, "Solver failed. Existing assignments were preserved. Please run scheduling again.");
|
||||
} finally { schools.endScheduling(showId); }
|
||||
} catch (ManagementException e) {
|
||||
throw e;
|
||||
} catch (RuntimeException | LinkageError e) {
|
||||
throw new ManagementException(
|
||||
ManagementExceptionKind.INVALID_INPUT,
|
||||
"Solver failed. Existing assignments were preserved. Please run scheduling again.");
|
||||
} finally {
|
||||
schools.endScheduling(showId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package nl.agilecc.agiledancers.service;
|
||||
|
||||
import static nl.agilecc.agiledancers.service.ManagementExceptionKind.*;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import nl.agilecc.agiledancers.model.*;
|
||||
import nl.agilecc.agiledancers.model.scheduling.GroupRequirement;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingInput;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingResult;
|
||||
import nl.agilecc.agiledancers.service.model.*;
|
||||
import jakarta.inject.Singleton;
|
||||
import nl.agilecc.agiledancers.model.*;
|
||||
import nl.agilecc.agiledancers.model.specifications.*;
|
||||
import nl.agilecc.agiledancers.repository.*;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import static nl.agilecc.agiledancers.service.ManagementExceptionKind.*;
|
||||
import nl.agilecc.agiledancers.service.model.*;
|
||||
|
||||
/** Owns the in-memory school aggregates; all writes go through this shared service. */
|
||||
@Singleton
|
||||
@@ -22,31 +23,52 @@ public class SchoolManagementService {
|
||||
|
||||
private final Map<Long, Long> revisions = new HashMap<>();
|
||||
private final Set<Long> running = new HashSet<>();
|
||||
private void touch(Show show) { revisions.merge(show.getId(), 1L, Long::sum); }
|
||||
|
||||
private void touch(Show show) {
|
||||
revisions.merge(show.getId(), 1L, Long::sum);
|
||||
}
|
||||
|
||||
public synchronized SchedulingInput beginScheduling(long schoolId, long showId) {
|
||||
Show show = show(schoolId, showId);
|
||||
if (running.contains(showId)) throw new ManagementException(CONFLICT, "Scheduling is already running for this show.");
|
||||
if (running.contains(showId))
|
||||
throw new ManagementException(CONFLICT, "Scheduling is already running for this show.");
|
||||
List<DanceGroup> current = groups(schoolId);
|
||||
if (current.isEmpty() || show.getDayParts().isEmpty()) throw new ManagementException(INVALID_INPUT, "Scheduling requires at least one school group and one daypart.");
|
||||
if (current.isEmpty() || show.getDayParts().isEmpty())
|
||||
throw new ManagementException(
|
||||
INVALID_INPUT, "Scheduling requires at least one school group and one daypart.");
|
||||
ShowConfiguration.validate(show.getDayParts(), show.getAppearances());
|
||||
running.add(showId);
|
||||
return new SchedulingInput(showId, revisions.getOrDefault(showId, 0L), show.getDayParts().size(),
|
||||
current.stream().map(g -> new GroupRequirement(g.getId(), show.getAppearances().get(g.getType()))).toList());
|
||||
return new SchedulingInput(
|
||||
showId,
|
||||
revisions.getOrDefault(showId, 0L),
|
||||
show.getDayParts().size(),
|
||||
current.stream()
|
||||
.map(g -> new GroupRequirement(
|
||||
g.getId(), show.getAppearances().get(g.getType())))
|
||||
.toList());
|
||||
}
|
||||
|
||||
public synchronized void finishScheduling(long schoolId, SchedulingInput input, SchedulingResult result) {
|
||||
Show show;
|
||||
try { show = show(schoolId, input.showId()); }
|
||||
catch (ManagementException e) { throw new ManagementException(CONFLICT, "Scheduling inputs changed. Run scheduling again."); }
|
||||
try {
|
||||
show = show(schoolId, input.showId());
|
||||
} catch (ManagementException e) {
|
||||
throw new ManagementException(CONFLICT, "Scheduling inputs changed. Run scheduling again.");
|
||||
}
|
||||
if (revisions.getOrDefault(input.showId(), 0L) != input.revision())
|
||||
throw new ManagementException(CONFLICT, "Scheduling inputs changed. Run scheduling again.");
|
||||
// Validate the complete solver output before mutating any assignments.
|
||||
if (result == null || result.assignments() == null || result.assignments().size() != input.dayParts())
|
||||
throw new ManagementException(INVALID_INPUT, "Solver returned an invalid result. Existing assignments were preserved.");
|
||||
if (result == null
|
||||
|| result.assignments() == null
|
||||
|| result.assignments().size() != input.dayParts())
|
||||
throw new ManagementException(
|
||||
INVALID_INPUT, "Solver returned an invalid result. Existing assignments were preserved.");
|
||||
Map<Long, DanceGroup> current = new HashMap<>();
|
||||
groups(schoolId).forEach(g -> current.put(g.getId(), g));
|
||||
for (var part : result.assignments()) {
|
||||
if (part == null || new HashSet<>(part).size() != part.size() || !current.keySet().containsAll(part))
|
||||
if (part == null
|
||||
|| new HashSet<>(part).size() != part.size()
|
||||
|| !current.keySet().containsAll(part))
|
||||
throw new ManagementException(INVALID_INPUT, "Solver returned invalid group assignments.");
|
||||
}
|
||||
for (GroupRequirement group : input.groups()) {
|
||||
@@ -57,12 +79,20 @@ public class SchoolManagementService {
|
||||
List<DayPart> replacement = new ArrayList<>();
|
||||
for (int p = 0; p < input.dayParts(); p++) {
|
||||
DayPart old = show.getDayParts().get(p);
|
||||
replacement.add(DayPart.builder().date(old.getDate()).type(old.getType())
|
||||
.danceGroups(result.assignments().get(p).stream().map(current::get).toList()).build());
|
||||
replacement.add(DayPart.builder()
|
||||
.date(old.getDate())
|
||||
.type(old.getType())
|
||||
.danceGroups(result.assignments().get(p).stream()
|
||||
.map(current::get)
|
||||
.toList())
|
||||
.build());
|
||||
}
|
||||
show.setDayParts(List.copyOf(replacement));
|
||||
}
|
||||
public synchronized void endScheduling(long showId) { running.remove(showId); }
|
||||
|
||||
public synchronized void endScheduling(long showId) {
|
||||
running.remove(showId);
|
||||
}
|
||||
|
||||
public synchronized List<DanceSchool> schools() {
|
||||
return schools.findAll();
|
||||
@@ -100,7 +130,8 @@ public class SchoolManagementService {
|
||||
|
||||
public synchronized Dancer createDancer(long schoolId, String name, LocalDate birthDate) {
|
||||
DanceSchool school = school(schoolId);
|
||||
Dancer dancer = dancers.add(Dancer.builder().name(name).birthDate(birthDate).build());
|
||||
Dancer dancer =
|
||||
dancers.add(Dancer.builder().name(name).birthDate(birthDate).build());
|
||||
school.addDancer(dancer);
|
||||
return dancer;
|
||||
}
|
||||
@@ -128,19 +159,35 @@ public class SchoolManagementService {
|
||||
return member(school(schoolId).getDanceGroups(), groupId, "Dance group");
|
||||
}
|
||||
|
||||
public synchronized DanceGroup createGroup(long schoolId, String category, DanceGroupType type,
|
||||
int startAge, int endAge, Set<DanceGroupStyle> styles) {
|
||||
public synchronized DanceGroup createGroup(
|
||||
long schoolId,
|
||||
String category,
|
||||
DanceGroupType type,
|
||||
int startAge,
|
||||
int endAge,
|
||||
Set<DanceGroupStyle> styles) {
|
||||
DanceSchool school = school(schoolId);
|
||||
validateAgeRange(startAge, endAge);
|
||||
DanceGroup group = groups.add(DanceGroup.builder().category(category).type(type)
|
||||
.startAge(startAge).endAge(endAge).styles(new LinkedHashSet<>(styles)).build());
|
||||
DanceGroup group = groups.add(DanceGroup.builder()
|
||||
.category(category)
|
||||
.type(type)
|
||||
.startAge(startAge)
|
||||
.endAge(endAge)
|
||||
.styles(new LinkedHashSet<>(styles))
|
||||
.build());
|
||||
school.addDanceGroup(group);
|
||||
school.getShows().forEach(this::touch);
|
||||
return group;
|
||||
}
|
||||
|
||||
public synchronized DanceGroup updateGroup(long schoolId, long groupId, String category, DanceGroupType type,
|
||||
int startAge, int endAge, Set<DanceGroupStyle> styles) {
|
||||
public synchronized DanceGroup updateGroup(
|
||||
long schoolId,
|
||||
long groupId,
|
||||
String category,
|
||||
DanceGroupType type,
|
||||
int startAge,
|
||||
int endAge,
|
||||
Set<DanceGroupStyle> styles) {
|
||||
DanceGroup group = group(schoolId, groupId);
|
||||
validateAgeRange(startAge, endAge);
|
||||
group.setCategory(category);
|
||||
@@ -178,23 +225,39 @@ public class SchoolManagementService {
|
||||
return member(school(schoolId).getShows(), showId, "Show");
|
||||
}
|
||||
|
||||
public synchronized Show createShow(long schoolId, String name, List<DayPart> parts, Map<DanceGroupType, Integer> counts) {
|
||||
public synchronized Show createShow(
|
||||
long schoolId, String name, List<DayPart> parts, Map<DanceGroupType, Integer> counts) {
|
||||
DanceSchool school = school(schoolId);
|
||||
if (counts == null) counts = ShowConfiguration.defaults(parts.size());
|
||||
ShowConfiguration.validate(parts, counts);
|
||||
Show show = shows.add(Show.builder().name(name).dayParts(List.copyOf(parts)).appearances(Map.copyOf(counts)).build());
|
||||
Show show = shows.add(Show.builder()
|
||||
.name(name)
|
||||
.dayParts(List.copyOf(parts))
|
||||
.appearances(Map.copyOf(counts))
|
||||
.build());
|
||||
school.addShow(show);
|
||||
return show;
|
||||
}
|
||||
|
||||
public synchronized Show updateShow(long schoolId, long showId, String name, List<DayPart> parts, Map<DanceGroupType, Integer> counts) {
|
||||
public synchronized Show updateShow(
|
||||
long schoolId, long showId, String name, List<DayPart> parts, Map<DanceGroupType, Integer> counts) {
|
||||
Show show = show(schoolId, showId);
|
||||
if (counts == null) counts = show.getAppearances().isEmpty() ? ShowConfiguration.defaults(parts.size()) : show.getAppearances();
|
||||
if (counts == null)
|
||||
counts = show.getAppearances().isEmpty() ? ShowConfiguration.defaults(parts.size()) : show.getAppearances();
|
||||
ShowConfiguration.validate(parts, counts);
|
||||
boolean changed = !counts.equals(show.getAppearances()) || !partKeys(parts).equals(partKeys(show.getDayParts()));
|
||||
List<DayPart> retained = parts.stream().map(p -> DayPart.builder().date(p.getDate()).type(p.getType())
|
||||
.danceGroups(show.getDayParts().stream().filter(old -> old.getDate().equals(p.getDate()) && old.getType() == p.getType())
|
||||
.findFirst().map(DayPart::getDanceGroups).orElse(List.of())).build()).toList();
|
||||
boolean changed =
|
||||
!counts.equals(show.getAppearances()) || !partKeys(parts).equals(partKeys(show.getDayParts()));
|
||||
List<DayPart> retained = parts.stream()
|
||||
.map(p -> DayPart.builder()
|
||||
.date(p.getDate())
|
||||
.type(p.getType())
|
||||
.danceGroups(show.getDayParts().stream()
|
||||
.filter(old -> old.getDate().equals(p.getDate()) && old.getType() == p.getType())
|
||||
.findFirst()
|
||||
.map(DayPart::getDanceGroups)
|
||||
.orElse(List.of()))
|
||||
.build())
|
||||
.toList();
|
||||
show.setName(name);
|
||||
show.setDayParts(retained);
|
||||
show.setAppearances(Map.copyOf(counts));
|
||||
@@ -212,16 +275,23 @@ public class SchoolManagementService {
|
||||
shows.delete(showId);
|
||||
}
|
||||
|
||||
|
||||
public synchronized SchoolSnapshot exportSchool(long id) {
|
||||
return SchoolSnapshot.of(school(id));
|
||||
}
|
||||
|
||||
public synchronized ImportPreview preview(SchoolSnapshot snapshot) {
|
||||
snapshot.restore();
|
||||
DanceSchool existing = schools.findAll().stream().filter(s -> s.getKey().equals(snapshot.key())).findFirst().orElse(null);
|
||||
return new ImportPreview(snapshot.name(), snapshot.dancers().size(), snapshot.groups().size(), snapshot.shows().size(),
|
||||
existing == null ? null : existing.getId(), existing == null ? null : existing.getName());
|
||||
DanceSchool existing = schools.findAll().stream()
|
||||
.filter(s -> s.getKey().equals(snapshot.key()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
return new ImportPreview(
|
||||
snapshot.name(),
|
||||
snapshot.dancers().size(),
|
||||
snapshot.groups().size(),
|
||||
snapshot.shows().size(),
|
||||
existing == null ? null : existing.getId(),
|
||||
existing == null ? null : existing.getName());
|
||||
}
|
||||
|
||||
public synchronized DanceSchool importSchool(SchoolSnapshot snapshot, Long replaceId) {
|
||||
@@ -244,21 +314,49 @@ public class SchoolManagementService {
|
||||
List<DanceGroup> rebuiltGroups = new ArrayList<>();
|
||||
Map<DanceGroup, DanceGroup> replacements = new IdentityHashMap<>();
|
||||
for (DanceGroup g : importedGroups) {
|
||||
DanceGroup rebuilt = DanceGroup.builder().category(g.getCategory()).type(g.getType())
|
||||
.startAge(g.getStartAge()).endAge(g.getEndAge()).styles(new LinkedHashSet<>(g.getStyles()))
|
||||
.coach(g.getCoach()).dancers(new LinkedHashSet<>(memberships.get(g))).build();
|
||||
rebuilt.setId(g.getId()); rebuiltGroups.add(rebuilt); replacements.put(g, rebuilt);
|
||||
DanceGroup rebuilt = DanceGroup.builder()
|
||||
.category(g.getCategory())
|
||||
.type(g.getType())
|
||||
.startAge(g.getStartAge())
|
||||
.endAge(g.getEndAge())
|
||||
.styles(new LinkedHashSet<>(g.getStyles()))
|
||||
.coach(g.getCoach())
|
||||
.dancers(new LinkedHashSet<>(memberships.get(g)))
|
||||
.build();
|
||||
rebuilt.setId(g.getId());
|
||||
rebuiltGroups.add(rebuilt);
|
||||
replacements.put(g, rebuilt);
|
||||
}
|
||||
List<Show> rebuiltShows = new ArrayList<>();
|
||||
for (Show show : importedShows) {
|
||||
Show rebuilt = Show.builder().name(show.getName()).appearances(show.getAppearances()).dayParts(show.getDayParts().stream()
|
||||
.map(p -> DayPart.builder().date(p.getDate()).type(p.getType()).danceGroups(p.getDanceGroups().stream().map(replacements::get).toList()).build()).toList()).build();
|
||||
rebuilt.setId(shows.nextId()); rebuiltShows.add(rebuilt);
|
||||
Show rebuilt = Show.builder()
|
||||
.name(show.getName())
|
||||
.appearances(show.getAppearances())
|
||||
.dayParts(show.getDayParts().stream()
|
||||
.map(p -> DayPart.builder()
|
||||
.date(p.getDate())
|
||||
.type(p.getType())
|
||||
.danceGroups(p.getDanceGroups().stream()
|
||||
.map(replacements::get)
|
||||
.toList())
|
||||
.build())
|
||||
.toList())
|
||||
.build();
|
||||
rebuilt.setId(shows.nextId());
|
||||
rebuiltShows.add(rebuilt);
|
||||
}
|
||||
DanceSchool imported = DanceSchool.builder()
|
||||
.key(staged.getKey())
|
||||
.name(staged.getName())
|
||||
.dancers(new LinkedHashSet<>(importedDancers))
|
||||
.danceGroups(new LinkedHashSet<>(rebuiltGroups))
|
||||
.shows(new LinkedHashSet<>(rebuiltShows))
|
||||
.coaches(new LinkedHashSet<>(staged.getCoaches()))
|
||||
.build();
|
||||
if (replaceId != null) {
|
||||
deleteSchool(replaceId);
|
||||
imported.setId(replaceId);
|
||||
}
|
||||
DanceSchool imported = DanceSchool.builder().key(staged.getKey()).name(staged.getName())
|
||||
.dancers(new LinkedHashSet<>(importedDancers)).danceGroups(new LinkedHashSet<>(rebuiltGroups))
|
||||
.shows(new LinkedHashSet<>(rebuiltShows)).coaches(new LinkedHashSet<>(staged.getCoaches())).build();
|
||||
if (replaceId != null) { deleteSchool(replaceId); imported.setId(replaceId); }
|
||||
importedDancers.forEach(dancers::add);
|
||||
rebuiltGroups.forEach(groups::add);
|
||||
rebuiltShows.forEach(shows::add);
|
||||
@@ -272,7 +370,9 @@ public class SchoolManagementService {
|
||||
}
|
||||
|
||||
private static <T extends AbstractEntity> T member(Collection<T> entities, long id, String type) {
|
||||
return entities.stream().filter(entity -> entity.getId() == id).findFirst()
|
||||
return entities.stream()
|
||||
.filter(entity -> entity.getId() == id)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ManagementException(NOT_FOUND, type + " " + id + " not found in this school"));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,51 @@
|
||||
package nl.agilecc.agiledancers.service;
|
||||
|
||||
import java.util.*;
|
||||
import nl.agilecc.agiledancers.model.DayPart;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
import java.util.*;
|
||||
|
||||
public final class ShowConfiguration {
|
||||
private ShowConfiguration() {}
|
||||
|
||||
public static Map<DanceGroupType, Integer> defaults(int n) {
|
||||
Map<DanceGroupType, Integer> counts = new EnumMap<>(DanceGroupType.class);
|
||||
if (n > 0) for (var type : DanceGroupType.values()) counts.put(type, switch (type) {
|
||||
case PRE_SCHOOLER -> 1;
|
||||
case SHOW_CASE, PRE_EDUCATION -> n;
|
||||
default -> Math.min(2, n);
|
||||
});
|
||||
if (n > 0)
|
||||
for (var type : DanceGroupType.values())
|
||||
counts.put(
|
||||
type,
|
||||
switch (type) {
|
||||
case PRE_SCHOOLER -> 1;
|
||||
case SHOW_CASE, PRE_EDUCATION -> n;
|
||||
default -> Math.min(2, n);
|
||||
});
|
||||
return counts;
|
||||
}
|
||||
|
||||
public static void validate(List<DayPart> parts, Map<DanceGroupType, Integer> counts) {
|
||||
if (parts == null || parts.size() > 6 || parts.stream().anyMatch(p -> p == null || p.getDate() == null || p.getType() == null)
|
||||
|| parts.stream().map(p -> p.getDate() + ":" + p.getType()).distinct().count() != parts.size())
|
||||
throw new ManagementException(ManagementExceptionKind.INVALID_INPUT, "Configure zero to six dayparts with unique date/type combinations.");
|
||||
if (parts == null
|
||||
|| parts.size() > 6
|
||||
|| parts.stream().anyMatch(p -> p == null || p.getDate() == null || p.getType() == null)
|
||||
|| parts.stream()
|
||||
.map(p -> p.getDate() + ":" + p.getType())
|
||||
.distinct()
|
||||
.count()
|
||||
!= parts.size())
|
||||
throw new ManagementException(
|
||||
ManagementExceptionKind.INVALID_INPUT,
|
||||
"Configure zero to six dayparts with unique date/type combinations.");
|
||||
if (counts == null) invalidCounts();
|
||||
if (parts.isEmpty() && counts.isEmpty()) return;
|
||||
if (counts.size() != DanceGroupType.values().length
|
||||
|| Arrays.stream(DanceGroupType.values()).anyMatch(t -> counts.get(t) == null || counts.get(t) < 1
|
||||
|| (!parts.isEmpty() && counts.get(t) > parts.size()))) invalidCounts();
|
||||
|| Arrays.stream(DanceGroupType.values())
|
||||
.anyMatch(t -> counts.get(t) == null
|
||||
|| counts.get(t) < 1
|
||||
|| (!parts.isEmpty() && counts.get(t) > parts.size()))) invalidCounts();
|
||||
}
|
||||
|
||||
private static void invalidCounts() {
|
||||
throw new ManagementException(ManagementExceptionKind.INVALID_INPUT,
|
||||
"Every type needs an integer appearance count between 1 and the number of dayparts. Saved counts are retained when dayparts are removed.");
|
||||
throw new ManagementException(
|
||||
ManagementExceptionKind.INVALID_INPUT,
|
||||
"Every type needs an integer appearance count between 1 and the number of dayparts. Saved counts are"
|
||||
+ " retained when dayparts are removed.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package nl.agilecc.agiledancers.service.model;
|
||||
|
||||
import java.util.List;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
|
||||
public record Group(String id, String category, DanceGroupType type, Integer startAge, Integer endAge,
|
||||
List<DanceGroupStyle> styles, List<String> dancerIds, String coachId) {}
|
||||
public record Group(
|
||||
String id,
|
||||
String category,
|
||||
DanceGroupType type,
|
||||
Integer startAge,
|
||||
Integer endAge,
|
||||
List<DanceGroupStyle> styles,
|
||||
List<String> dancerIds,
|
||||
String coachId) {}
|
||||
|
||||
@@ -1,31 +1,62 @@
|
||||
package nl.agilecc.agiledancers.service.model;
|
||||
|
||||
import nl.agilecc.agiledancers.service.ManagementExceptionKind;
|
||||
import nl.agilecc.agiledancers.service.ManagementException;
|
||||
import nl.agilecc.agiledancers.service.ShowConfiguration;
|
||||
import nl.agilecc.agiledancers.model.*;
|
||||
import nl.agilecc.agiledancers.model.specifications.*;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import nl.agilecc.agiledancers.model.*;
|
||||
import nl.agilecc.agiledancers.model.specifications.*;
|
||||
import nl.agilecc.agiledancers.service.ManagementException;
|
||||
import nl.agilecc.agiledancers.service.ManagementExceptionKind;
|
||||
import nl.agilecc.agiledancers.service.ShowConfiguration;
|
||||
|
||||
/** Version 2 file format. IDs are local to the file, never server identifiers. */
|
||||
public record SchoolSnapshot(Integer formatVersion, UUID key, String name, List<Person> dancers,
|
||||
List<Trainer> coaches, List<Group> groups, List<Performance> shows) {
|
||||
public record SchoolSnapshot(
|
||||
Integer formatVersion,
|
||||
UUID key,
|
||||
String name,
|
||||
List<Person> dancers,
|
||||
List<Trainer> coaches,
|
||||
List<Group> groups,
|
||||
List<Performance> shows) {
|
||||
public static SchoolSnapshot of(DanceSchool s) {
|
||||
Map<Dancer, String> dancers = ids(s.getDancers());
|
||||
Map<Coach, String> coaches = ids(s.getCoaches());
|
||||
Map<DanceGroup, String> groups = ids(s.getDanceGroups());
|
||||
Map<Show, String> shows = ids(s.getShows());
|
||||
return new SchoolSnapshot(2, s.getKey(), s.getName(),
|
||||
s.getDancers().stream().map(d -> new Person(dancers.get(d), d.getName(), d.getBirthDate())).toList(),
|
||||
s.getCoaches().stream().map(c -> new Trainer(coaches.get(c), c.getName())).toList(),
|
||||
s.getDanceGroups().stream().map(g -> new Group(groups.get(g), g.getCategory(), g.getType(),
|
||||
g.getStartAge(), g.getEndAge(), List.copyOf(g.getStyles()),
|
||||
g.getDancers().stream().map(dancers::get).toList(),
|
||||
g.getCoach() == null ? null : coaches.get(g.getCoach()))).toList(),
|
||||
s.getShows().stream().map(show -> new Performance(shows.get(show), show.getName(),
|
||||
show.getDayParts().stream().map(p -> new Part(p.getDate(), p.getType(),
|
||||
p.getDanceGroups().stream().map(groups::get).toList())).toList(), show.getAppearances())).toList());
|
||||
return new SchoolSnapshot(
|
||||
2,
|
||||
s.getKey(),
|
||||
s.getName(),
|
||||
s.getDancers().stream()
|
||||
.map(d -> new Person(dancers.get(d), d.getName(), d.getBirthDate()))
|
||||
.toList(),
|
||||
s.getCoaches().stream()
|
||||
.map(c -> new Trainer(coaches.get(c), c.getName()))
|
||||
.toList(),
|
||||
s.getDanceGroups().stream()
|
||||
.map(g -> new Group(
|
||||
groups.get(g),
|
||||
g.getCategory(),
|
||||
g.getType(),
|
||||
g.getStartAge(),
|
||||
g.getEndAge(),
|
||||
List.copyOf(g.getStyles()),
|
||||
g.getDancers().stream().map(dancers::get).toList(),
|
||||
g.getCoach() == null ? null : coaches.get(g.getCoach())))
|
||||
.toList(),
|
||||
s.getShows().stream()
|
||||
.map(show -> new Performance(
|
||||
shows.get(show),
|
||||
show.getName(),
|
||||
show.getDayParts().stream()
|
||||
.map(p -> new Part(
|
||||
p.getDate(),
|
||||
p.getType(),
|
||||
p.getDanceGroups().stream()
|
||||
.map(groups::get)
|
||||
.toList()))
|
||||
.toList(),
|
||||
show.getAppearances()))
|
||||
.toList());
|
||||
}
|
||||
|
||||
private static <T> Map<T, String> ids(Collection<T> values) {
|
||||
@@ -36,57 +67,129 @@ public record SchoolSnapshot(Integer formatVersion, UUID key, String name, List<
|
||||
|
||||
public DanceSchool restore() {
|
||||
check(Objects.equals(formatVersion, 2), "Niet-ondersteunde formatVersion: " + formatVersion);
|
||||
check(key != null, "key ontbreekt"); text(name, "schoolnaam");
|
||||
check(key != null, "key ontbreekt");
|
||||
text(name, "schoolnaam");
|
||||
DanceSchool school = DanceSchool.builder().key(key).name(name).build();
|
||||
Map<String, Dancer> dancerMap = new LinkedHashMap<>();
|
||||
for (Person d : list(dancers, "dancers")) {
|
||||
check(d != null, "Lege danser"); text(d.name(), "dansernaam");
|
||||
check(d.birthDate() != null && !d.birthDate().isAfter(LocalDate.now()), "Ongeldige geboortedatum voor " + d.name());
|
||||
Dancer dancer = Dancer.builder().name(d.name()).birthDate(d.birthDate()).build();
|
||||
check(d != null, "Lege danser");
|
||||
text(d.name(), "dansernaam");
|
||||
check(
|
||||
d.birthDate() != null && !d.birthDate().isAfter(LocalDate.now()),
|
||||
"Ongeldige geboortedatum voor " + d.name());
|
||||
Dancer dancer =
|
||||
Dancer.builder().name(d.name()).birthDate(d.birthDate()).build();
|
||||
// Assign temporary distinct IDs before inserting entities into sets.
|
||||
dancer.setId((long) dancerMap.size());
|
||||
put(dancerMap, d.id(), dancer); school.addDancer(dancer);
|
||||
put(dancerMap, d.id(), dancer);
|
||||
school.addDancer(dancer);
|
||||
}
|
||||
Map<String, Coach> coachMap = new LinkedHashMap<>();
|
||||
for (Trainer c : list(coaches, "coaches")) {
|
||||
check(c != null, "Lege coach"); text(c.name(), "coachnaam");
|
||||
Coach coach = Coach.builder().name(c.name()).build(); coach.setId((long) coachMap.size());
|
||||
put(coachMap, c.id(), coach); school.addCoach(coach);
|
||||
check(c != null, "Lege coach");
|
||||
text(c.name(), "coachnaam");
|
||||
Coach coach = Coach.builder().name(c.name()).build();
|
||||
coach.setId((long) coachMap.size());
|
||||
put(coachMap, c.id(), coach);
|
||||
school.addCoach(coach);
|
||||
}
|
||||
Map<String, DanceGroup> groupMap = new LinkedHashMap<>();
|
||||
for (Group g : list(groups, "groups")) {
|
||||
check(g != null, "Lege groep"); text(g.category(), "groepsnaam");
|
||||
check(g.type() != null && g.startAge() != null && g.endAge() != null && g.startAge() >= 0 && g.endAge() >= g.startAge(),
|
||||
check(g != null, "Lege groep");
|
||||
text(g.category(), "groepsnaam");
|
||||
check(
|
||||
g.type() != null
|
||||
&& g.startAge() != null
|
||||
&& g.endAge() != null
|
||||
&& g.startAge() >= 0
|
||||
&& g.endAge() >= g.startAge(),
|
||||
"Ongeldig type of leeftijdsbereik voor groep " + g.category());
|
||||
DanceGroup group = DanceGroup.builder().category(g.category()).type(g.type()).startAge(g.startAge()).endAge(g.endAge())
|
||||
.styles(new LinkedHashSet<>(list(g.styles(), "styles"))).coach(g.coachId() == null ? null : ref(coachMap, g.coachId(), "groep " + g.category() + " verwijst naar een onbekende coach" )).build();
|
||||
DanceGroup group = DanceGroup.builder()
|
||||
.category(g.category())
|
||||
.type(g.type())
|
||||
.startAge(g.startAge())
|
||||
.endAge(g.endAge())
|
||||
.styles(new LinkedHashSet<>(list(g.styles(), "styles")))
|
||||
.coach(
|
||||
g.coachId() == null
|
||||
? null
|
||||
: ref(
|
||||
coachMap,
|
||||
g.coachId(),
|
||||
"groep " + g.category() + " verwijst naar een onbekende coach"))
|
||||
.build();
|
||||
check(!group.getStyles().contains(null), "Ongeldige dansstijl");
|
||||
group.setId((long) groupMap.size());
|
||||
for (String id : unique(g.dancerIds(), "dancerIds")) group.addDancer(ref(dancerMap, id, "groep " + g.category() + " verwijst naar een onbekende danser"));
|
||||
put(groupMap, g.id(), group); school.addDanceGroup(group);
|
||||
for (String id : unique(g.dancerIds(), "dancerIds"))
|
||||
group.addDancer(ref(dancerMap, id, "groep " + g.category() + " verwijst naar een onbekende danser"));
|
||||
put(groupMap, g.id(), group);
|
||||
school.addDanceGroup(group);
|
||||
}
|
||||
Set<String> showIds = new HashSet<>();
|
||||
for (Performance show : list(shows, "shows")) {
|
||||
check(show != null, "Lege show"); text(show.id(), "show-id");
|
||||
check(showIds.add(show.id()), "Dubbele show-id " + show.id()); text(show.name(), "shownaam");
|
||||
List<DayPart> parts = new ArrayList<>(); Set<String> types = new HashSet<>();
|
||||
check(show != null, "Lege show");
|
||||
text(show.id(), "show-id");
|
||||
check(showIds.add(show.id()), "Dubbele show-id " + show.id());
|
||||
text(show.name(), "shownaam");
|
||||
List<DayPart> parts = new ArrayList<>();
|
||||
Set<String> types = new HashSet<>();
|
||||
for (Part part : list(show.dayParts(), "dayParts")) {
|
||||
check(part != null && part.type() != null && part.date() != null && types.add(part.date() + ":" + part.type()), "Ongeldig of dubbel dagdeel voor " + show.name());
|
||||
parts.add(DayPart.builder().date(part.date()).type(part.type()).danceGroups(unique(part.groupIds(), "groupIds").stream()
|
||||
.map(id -> ref(groupMap, id, "Show " + show.name() + " verwijst naar een onbekende groep")).toList()).build());
|
||||
check(
|
||||
part != null
|
||||
&& part.type() != null
|
||||
&& part.date() != null
|
||||
&& types.add(part.date() + ":" + part.type()),
|
||||
"Ongeldig of dubbel dagdeel voor " + show.name());
|
||||
parts.add(DayPart.builder()
|
||||
.date(part.date())
|
||||
.type(part.type())
|
||||
.danceGroups(unique(part.groupIds(), "groupIds").stream()
|
||||
.map(id ->
|
||||
ref(groupMap, id, "Show " + show.name() + " verwijst naar een onbekende groep"))
|
||||
.toList())
|
||||
.build());
|
||||
}
|
||||
ShowConfiguration.validate(parts, show.appearances());
|
||||
Show entity = Show.builder().name(show.name()).dayParts(parts).appearances(Map.copyOf(show.appearances())).build();
|
||||
entity.setId((long) showIds.size()); school.addShow(entity);
|
||||
Show entity = Show.builder()
|
||||
.name(show.name())
|
||||
.dayParts(parts)
|
||||
.appearances(Map.copyOf(show.appearances()))
|
||||
.build();
|
||||
entity.setId((long) showIds.size());
|
||||
school.addShow(entity);
|
||||
}
|
||||
return school;
|
||||
}
|
||||
private static void text(String value, String field) { check(value != null && !value.isBlank(), field + " ontbreekt"); }
|
||||
private static <T> List<T> list(List<T> value, String field) { check(value != null, field + " ontbreekt"); return value; }
|
||||
private static List<String> unique(List<String> value, String field) {
|
||||
list(value, field); check(value.stream().noneMatch(Objects::isNull) && new HashSet<>(value).size() == value.size(), "Ongeldige of dubbele verwijzing in " + field); return value;
|
||||
|
||||
private static void text(String value, String field) {
|
||||
check(value != null && !value.isBlank(), field + " ontbreekt");
|
||||
}
|
||||
|
||||
private static <T> List<T> list(List<T> value, String field) {
|
||||
check(value != null, field + " ontbreekt");
|
||||
return value;
|
||||
}
|
||||
|
||||
private static List<String> unique(List<String> value, String field) {
|
||||
list(value, field);
|
||||
check(
|
||||
value.stream().noneMatch(Objects::isNull) && new HashSet<>(value).size() == value.size(),
|
||||
"Ongeldige of dubbele verwijzing in " + field);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static <T> void put(Map<String, T> map, String id, T value) {
|
||||
text(id, "id");
|
||||
check(map.putIfAbsent(id, value) == null, "Dubbele id " + id);
|
||||
}
|
||||
|
||||
private static <T> T ref(Map<String, T> map, String id, String error) {
|
||||
T value = map.get(id);
|
||||
check(value != null, error);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void check(boolean valid, String message) {
|
||||
if (!valid) throw new ManagementException(ManagementExceptionKind.INVALID_INPUT, message);
|
||||
}
|
||||
private static <T> void put(Map<String, T> map, String id, T value) { text(id, "id"); check(map.putIfAbsent(id, value) == null, "Dubbele id " + id); }
|
||||
private static <T> T ref(Map<String,T> map, String id, String error) { T value = map.get(id); check(value != null, error); return value; }
|
||||
private static void check(boolean valid, String message) { if (!valid) throw new ManagementException(ManagementExceptionKind.INVALID_INPUT, message); }
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package nl.agilecc.agiledancers.solver;
|
||||
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingInput;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingResult;
|
||||
import nl.agilecc.agiledancers.service.port.SchedulingEngine;
|
||||
import com.google.ortools.Loader;
|
||||
import com.google.ortools.sat.*;
|
||||
import jakarta.inject.Singleton;
|
||||
import nl.agilecc.agiledancers.service.*;
|
||||
import java.util.*;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingInput;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingResult;
|
||||
import nl.agilecc.agiledancers.service.*;
|
||||
import nl.agilecc.agiledancers.service.port.SchedulingEngine;
|
||||
|
||||
@Singleton
|
||||
public class OrToolsSchedulingEngine implements SchedulingEngine {
|
||||
@Override
|
||||
@@ -30,7 +31,7 @@ public class OrToolsSchedulingEngine implements SchedulingEngine {
|
||||
IntVar max = model.newIntVar(0, groups, "max"), min = model.newIntVar(0, groups, "min");
|
||||
model.addMaxEquality(max, loads);
|
||||
model.addMinEquality(min, loads);
|
||||
model.minimize(LinearExpr.weightedSum(new IntVar[]{max, min}, new long[]{1, -1}));
|
||||
model.minimize(LinearExpr.weightedSum(new IntVar[] {max, min}, new long[] {1, -1}));
|
||||
CpSolver solver = new CpSolver();
|
||||
solver.getParameters().setMaxTimeInSeconds(1800);
|
||||
// Quarkus owns process shutdown; do not install a native SIGINT handler.
|
||||
@@ -39,15 +40,21 @@ public class OrToolsSchedulingEngine implements SchedulingEngine {
|
||||
solver.getParameters().setNumSearchWorkers(1).setRandomSeed(0);
|
||||
CpSolverStatus status = solver.solve(model);
|
||||
if (status != CpSolverStatus.OPTIMAL && status != CpSolverStatus.FEASIBLE)
|
||||
throw new ManagementException(ManagementExceptionKind.INVALID_INPUT, switch (status) {
|
||||
case INFEASIBLE -> "Scheduling is infeasible. Existing assignments were preserved.";
|
||||
case UNKNOWN -> "The 30-minute limit was reached without a valid result. Existing assignments were preserved.";
|
||||
default -> "Solver failed (" + status + "). Existing assignments were preserved.";
|
||||
});
|
||||
throw new ManagementException(
|
||||
ManagementExceptionKind.INVALID_INPUT,
|
||||
switch (status) {
|
||||
case INFEASIBLE -> "Scheduling is infeasible. Existing assignments were preserved.";
|
||||
case UNKNOWN ->
|
||||
"The 30-minute limit was reached without a valid result. Existing assignments were"
|
||||
+ " preserved.";
|
||||
default -> "Solver failed (" + status + "). Existing assignments were preserved.";
|
||||
});
|
||||
List<List<Long>> parts = new ArrayList<>();
|
||||
for (int p = 0; p < n; p++) {
|
||||
List<Long> ids = new ArrayList<>();
|
||||
for (int g = 0; g < groups; g++) if (solver.booleanValue(assigned[g][p])) ids.add(input.groups().get(g).groupId());
|
||||
for (int g = 0; g < groups; g++)
|
||||
if (solver.booleanValue(assigned[g][p]))
|
||||
ids.add(input.groups().get(g).groupId());
|
||||
parts.add(List.copyOf(ids));
|
||||
}
|
||||
return new SchedulingResult(status == CpSolverStatus.OPTIMAL, List.copyOf(parts));
|
||||
|
||||
Reference in New Issue
Block a user