diff --git a/modules/band-lineup/matching.js b/modules/band-lineup/matching.js index cfea022..a12701d 100644 --- a/modules/band-lineup/matching.js +++ b/modules/band-lineup/matching.js @@ -128,7 +128,11 @@ function assignMembersToSlots(slots, members) { if (score > bestScore) { bestScore = score; bestIndex = i; } } if (bestIndex < 0) return null; - return pool.splice(bestIndex, 1)[0]; + const chosen = pool.splice(bestIndex, 1)[0]; + // One spot per person: someone scheduled twice (Leader + Acoustic) must not fill a second spot. + const key = chosen.personId || ''; + if (key) for (let i = pool.length - 1; i >= 0; i -= 1) if (pool[i].personId === key) pool.splice(i, 1); + return chosen; }); } diff --git a/modules/band-lineup/matching.test.js b/modules/band-lineup/matching.test.js index 516574c..90f30c8 100644 --- a/modules/band-lineup/matching.test.js +++ b/modules/band-lineup/matching.test.js @@ -65,3 +65,12 @@ console.log('assignment:', JSON.stringify(out)); assert.strictEqual(bestLibraryMatch('Chris Smith', [{ id: '1', name: 'Chris' }, { id: '2', name: 'Chris' }]), null, 'ambiguous tie → null'); console.log('bestLibraryMatch: all assertions passed ✓'); } + +// --- one spot per person: someone scheduled twice (Leader + Acoustic) fills one spot only --- +{ + const P = (label, name, personId) => ({ label, name, personId }); + const slots = ['[LEADER, VOX 1, ACOUSTIC] MIX 1', '[GUITAR, 2] MIX 7']; + const got = assignMembersToSlots(slots, [P('[Leader]', 'Mike', 'mp'), P('Guitar [Acoustic]', 'Mike', 'mp'), P('Guitar [Rhythm]', 'Craig', 'cs')]).map((m) => (m ? m.name : null)); + assert.deepStrictEqual(got, ['Mike', 'Craig'], 'Mike once, Craig gets the guitar spot'); + console.log('one spot per person: all assertions passed ✓'); +} diff --git a/modules/band-lineup/server.js b/modules/band-lineup/server.js index 92bcf63..abc1057 100644 --- a/modules/band-lineup/server.js +++ b/modules/band-lineup/server.js @@ -530,8 +530,8 @@ module.exports = { const openIdx = []; const manualNames = new Set(); for (let i = 0; i < eff.length; i += 1) { const e = board[String(i)]; - if (e && (e.personId || e.name) && e.plan === planId) { - const nm = e.personId ? (library[e.personId] && library[e.personId].name) : e.name; + if (e && (e.personId || e.name || e.pco) && e.plan === planId) { + const nm = e.pco ? e.pco.name : e.personId ? (library[e.personId] && library[e.personId].name) : e.name; if (nm) manualNames.add(normName(nm)); } else openIdx.push(i); } @@ -675,6 +675,10 @@ module.exports = { const from = String(p.get('from') || ''); const to = String(p.get('to') || ''); if (!/^\d+$/.test(from) || !/^\d+$/.test(to) || from === to) return json(res, 400, { error: 'Bad move.' }); const a = current.board[from]; const b = current.board[to]; + // Moving a spot is a hand placement for this plan: Sync must not move it back. + const plan = current.planId(); + if (a) a.plan = plan; + if (b) b.plan = plan; if (a === undefined) delete current.board[to]; else current.board[to] = a; if (b === undefined) delete current.board[from]; else current.board[from] = b; current.saveBoard(); current.rebuildLocal();