Files
joshuaandClaude Opus 5.5 1516e104df Add rooms & groups allocation
Supervisors can split an event's named attendees into allocation sets
(rooms, small groups, buses) with capacity, gender and leader per group,
using a drag-and-drop board or auto-allocate (keep bookings together or
split them). Adds printable group and master lists (medical view
audited), the group on tickets and in My Events, messaging by group,
a "My groups" page for leaders, and an optional per-event gender
question for named attendees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:44:58 +02:00

85 lines
3.9 KiB
JavaScript

const { joinBlocker, autoAllocate, cleanGender } = require('../src/utils/allocation');
const person = (id, registrationId, gender = null) => ({ id, name: id, registrationId, gender });
const groupsOf = (result) => {
const out = {};
for (const p of result.placements) (out[p.groupId] = out[p.groupId] || []).push(p.attendeeId);
return out;
};
describe('cleanGender', () => {
test('accepts male/female in any case, blank as null', () => {
expect(cleanGender('Male')).toBe('male');
expect(cleanGender('')).toBeNull();
expect(() => cleanGender('other')).toThrow();
});
});
describe('joinBlocker', () => {
test('capacity', () => {
expect(joinBlocker({ name: 'Cabin 1', capacity: 2 }, 1, [person('a', 'r')])).toBeNull();
expect(joinBlocker({ name: 'Cabin 1', capacity: 2 }, 2, [person('a', 'r')])).toMatch(/full/);
});
test('gender', () => {
expect(joinBlocker({ name: 'Boys', gender: 'male' }, 0, [person('a', 'r', 'male')])).toBeNull();
expect(joinBlocker({ name: 'Boys', gender: 'male' }, 0, [person('a', 'r', 'female')])).toMatch(/males only/);
expect(joinBlocker({ name: 'Boys', gender: 'male' }, 0, [person('a', 'r')])).toMatch(/no gender recorded/);
});
});
describe('autoAllocate', () => {
test('fills groups evenly', () => {
const groups = [{ id: 'g1', name: 'A' }, { id: 'g2', name: 'B' }];
const attendees = ['a', 'b', 'c', 'd'].map(id => person(id, id));
const r = autoAllocate({ groups, attendees });
const g = groupsOf(r);
expect(g.g1.length).toBe(2);
expect(g.g2.length).toBe(2);
expect(r.unplaced).toEqual([]);
});
test('respects capacity and reports who could not be placed', () => {
const groups = [{ id: 'g1', name: 'A', capacity: 1 }];
const r = autoAllocate({ groups, attendees: [person('a', 'r1'), person('b', 'r2')] });
expect(r.placements.length).toBe(1);
expect(r.unplaced.length).toBe(1);
});
test('respects gender', () => {
const groups = [{ id: 'boys', name: 'Boys', gender: 'male' }, { id: 'girls', name: 'Girls', gender: 'female' }];
const r = autoAllocate({ groups, attendees: [person('a', 'r1', 'male'), person('b', 'r2', 'female'), person('c', 'r3')] });
expect(groupsOf(r)).toEqual({ boys: ['a'], girls: ['b'] });
expect(r.unplaced).toEqual(['c']);
});
test('keeps a registration together when a group can take them all', () => {
const groups = [{ id: 'g1', name: 'A', capacity: 3 }, { id: 'g2', name: 'B', capacity: 3 }];
const attendees = [person('s1', 'fam'), person('s2', 'fam'), person('x', 'solo')];
const g = groupsOf(autoAllocate({ groups, attendees, mode: 'together' }));
const famGroup = Object.keys(g).find(k => g[k].includes('s1'));
expect(g[famGroup]).toContain('s2');
});
test('places siblings one by one when no group fits them all', () => {
const groups = [{ id: 'boys', name: 'Boys', gender: 'male' }, { id: 'girls', name: 'Girls', gender: 'female' }];
const r = autoAllocate({ groups, attendees: [person('bro', 'fam', 'male'), person('sis', 'fam', 'female')], mode: 'together' });
expect(groupsOf(r)).toEqual({ boys: ['bro'], girls: ['sis'] });
});
test('split mode spreads a registration across groups', () => {
const groups = [{ id: 'g1', name: 'A' }, { id: 'g2', name: 'B' }];
const attendees = [person('f1', 'friends'), person('f2', 'friends')];
const g = groupsOf(autoAllocate({ groups, attendees, mode: 'split' }));
expect(g.g1.length).toBe(1);
expect(g.g2.length).toBe(1);
});
test('counts people already placed and leaves them where they are', () => {
const groups = [{ id: 'g1', name: 'A', capacity: 2 }, { id: 'g2', name: 'B', capacity: 2 }];
const attendees = [person('a', 'r1'), person('b', 'r2'), person('c', 'r3')];
const r = autoAllocate({ groups, attendees, assigned: new Map([['a', 'g1'], ['b', 'g1']]) });
expect(r.placements).toEqual([{ attendeeId: 'c', groupId: 'g2' }]);
});
});