S18 Added OR Tools rules for scheduling show
All checks were successful
Build, test and publish / verify (push) Successful in 6m12s

This commit is contained in:
2026-11-03 11:03:47 +01:00
parent c965f9b41d
commit a7f7730ea0
48 changed files with 2262 additions and 256 deletions

View File

@@ -17,6 +17,8 @@ import { apiError } from '../core/api-error';
import { SchoolApi } from '../core/school-api';
import { EntityEditor } from '../shared/entity-editor';
import { ConfirmDialog } from '../shared/confirm-dialog';
import { ScheduleEditor } from '../shared/schedule-editor';
import { CoachEditor } from '../shared/coach-editor';
import { GroupMembers } from '../shared/group-members';
interface DeleteTarget {
@@ -29,6 +31,8 @@ interface DeleteTarget {
@Component({
selector: 'app-school-page',
imports: [
ScheduleEditor,
CoachEditor,
SchoolTransfer,
DatePipe,
RouterLink,
@@ -50,6 +54,7 @@ export class SchoolPage implements OnInit {
readonly error = signal('');
readonly notice = signal('');
readonly editor = signal<EditorTarget | null>(null);
readonly scheduleEditing = signal<Show | null>(null);
readonly members = signal<DanceGroup | null>(null);
readonly deleting = signal<DeleteTarget | null>(null);
readonly scheduling = signal<number[]>([]);
@@ -86,6 +91,7 @@ export class SchoolPage implements OnInit {
this.search.set('');
this.editor.set(null);
this.members.set(null);
this.scheduleEditing.set(null);
this.deleting.set(null);
this.section.set(params.get('section') ?? 'overview');
return this.api.schoolData(Number(params.get('schoolId'))).pipe(
@@ -121,22 +127,9 @@ export class SchoolPage implements OnInit {
return !first ? 'No dates configured' : first === last ? first : `${first} – ${last}`;
}
scheduleValid(show: Show): boolean {
const groups = this.data()?.groups ?? [];
return (
!!groups.length &&
!!show.dayParts.length &&
groups.every(
(g) =>
show.dayParts.filter((p) => p.danceGroupIds.includes(g.id)).length ===
show.appearances[g.type],
) &&
show.dayParts.every(
(p) =>
new Set(p.danceGroupIds).size === p.danceGroupIds.length &&
p.danceGroupIds.every((id) => groups.some((g) => g.id === id)),
)
);
return show.violations?.length === 0;
}
groupNamesFor(ids: number[]): string {
return (
ids.map((id) => this.data()?.groups.find((g) => g.id === id)?.category ?? '?').join(', ') ||
@@ -163,7 +156,9 @@ export class SchoolPage implements OnInit {
try {
const result = await firstValueFrom(this.api.schedule(schoolId, show.id));
this.notice.set(
result.optimal ? 'Optimale balans.' : 'Geldige planning; optimale balans niet bewezen.',
result.optimal
? 'Optimaal conceptrooster.'
: 'Geldig conceptrooster; optimaliteit niet bewezen.',
);
await this.reload();
} catch (error: unknown) {
@@ -221,6 +216,7 @@ export class SchoolPage implements OnInit {
this.deleting.set(target);
}
membershipChanged(ids: number[]): void {
void this.reload();
const groupId = this.members()?.id;
this.data.update((data) =>
data