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

@@ -130,33 +130,22 @@ import { DialogFrame } from './dialog-frame';
</button>
</fieldset>
@if (dayParts.length) {
<fieldset>
<legend>Optredens per groepstype</legend>
@for (option of types; track option.value) {
<label
>{{ option.label
}}<input
type="number"
[name]="'count' + option.value"
[ngModel]="dayParts.length === 1 ? 1 : appearances[option.value]"
(ngModelChange)="appearances[option.value] = $event"
required
min="1"
[max]="dayParts.length"
step="1"
[disabled]="dayParts.length === 1"
/></label>
}
<p class="form-hint">
Bestaande aantallen blijven behouden bij wijzigingen. Corrigeer te hoge aantallen
vóór opslaan.
</p>
@if (dayParts.length === 1 && !validCounts()) {
<button type="button" class="button secondary" (click)="setSingleCounts()">
Alle aantallen op één zetten
</button>
}
</fieldset>
<label
>Gewenst aantal dagdelen (x)
<input
type="number"
name="preferredDayParts"
[(ngModel)]="preferredDayParts"
min="1"
max="6"
step="1"
required
/>
</label>
<p class="form-hint">
Gewone groepen dansen in precies x dagdelen. Kleuters eenmaal; showcases ieder
dagdeel. Is x groter dan het aantal dagdelen, corrigeer x vóór inplannen.
</p>
} @else {
<p class="form-hint">Zonder dagdelen blijft deze show ongepland.</p>
}
@@ -196,8 +185,7 @@ export class EntityEditor implements OnInit {
readonly today = new Date().toLocaleDateString('en-CA');
private readonly api = inject(SchoolApi);
dayParts: DayPart[] = [];
appearances: Partial<Record<GroupType, number>> = {};
private countsInitialized = false;
preferredDayParts = 2;
name = '';
date = '';
type: GroupType = 'REGULAR';
@@ -223,44 +211,23 @@ export class EntityEditor implements OnInit {
if (target.kind === 'dancer') this.date = target.value.birthDate;
if (target.kind === 'show') {
this.dayParts = target.value.dayParts.map((p) => ({ ...p }));
this.appearances = { ...target.value.appearances };
this.countsInitialized = Object.keys(this.appearances).length > 0;
this.preferredDayParts = target.value.preferredDayParts;
}
}
}
addPart(): void {
if (this.dayParts.length >= 6) return;
const previous = this.dayParts.length;
this.dayParts.push({ date: '', type: 'AFTERNOON', danceGroupIds: [] });
if (!this.countsInitialized) {
for (const { value } of this.types) {
const defaultCount = (n: number) =>
value === 'PRE_SCHOOLER'
? 1
: value === 'SHOW_CASE' || value === 'PRE_EDUCATION'
? n
: Math.min(2, n);
if (
this.appearances[value] === undefined ||
this.appearances[value] === defaultCount(previous)
)
this.appearances[value] = defaultCount(this.dayParts.length);
}
}
if (!this.target().value) this.preferredDayParts = Math.min(2, this.dayParts.length);
}
removePart(index: number): void {
this.dayParts.splice(index, 1);
if (!this.countsInitialized && !this.dayParts.length) this.appearances = {};
}
setSingleCounts(): void {
for (const option of this.types) this.appearances[option.value] = 1;
}
validCounts(): boolean {
return this.types.every(
(o) =>
Number.isInteger(this.appearances[o.value]) &&
this.appearances[o.value]! >= 1 &&
this.appearances[o.value]! <= this.dayParts.length,
return (
Number.isInteger(this.preferredDayParts) &&
this.preferredDayParts >= 1 &&
this.preferredDayParts <= 6
);
}
toggleStyle(style: GroupStyle, checked: boolean): void {
@@ -300,7 +267,7 @@ export class EntityEditor implements OnInit {
current.value &&
(JSON.stringify(current.value.dayParts.map((p) => ({ date: p.date, type: p.type }))) !==
JSON.stringify(this.dayParts.map((p) => ({ date: p.date, type: p.type }))) ||
JSON.stringify(current.value.appearances) !== JSON.stringify(this.appearances))
current.value.preferredDayParts !== this.preferredDayParts)
)
affected = [current.value];
const names = affected
@@ -349,7 +316,7 @@ export class EntityEditor implements OnInit {
{
name: this.name.trim(),
dayParts: this.dayParts.map((p) => ({ date: p.date, type: p.type })),
appearances: this.appearances,
preferredDayParts: this.preferredDayParts,
},
id,
);