diff --git a/modules/band-lineup/module.json b/modules/band-lineup/module.json index c144f92..df2cc60 100644 --- a/modules/band-lineup/module.json +++ b/modules/band-lineup/module.json @@ -1,7 +1,7 @@ { "id": "band-lineup", "name": "Band Lineup", - "version": "1.11.0", + "version": "1.11.1", "proddash": ">=1.4.0", "description": "Stage lineup photos with mic/pack labels — a drag-in photo library plus optional Planning Center auto-fill", "client": "client.js", diff --git a/modules/band-lineup/server.js b/modules/band-lineup/server.js index 4141122..92bcf63 100644 --- a/modules/band-lineup/server.js +++ b/modules/band-lineup/server.js @@ -247,7 +247,7 @@ module.exports = { return { configured: pco.configured, serviceTypeSet: Boolean(serviceTypeId), reachable: false, lastError: '', lastFetched: 0, columns, - plan: null, + plan: settings.lastPlan || null, // the last synced plan, so hand placements after a restart stamp right slots: effectiveSlots().map((label, index) => ({ index, label, person: null })), library: [], choir: choirOn, }; @@ -493,7 +493,8 @@ module.exports = { await syncPhotos(); // import the photo source (Drive folder or local path) on every sync if (pco.configured && serviceTypeId) { const plan = pickPlan(await listPlans(serviceTypeId)); - latest.plan = plan ? { title: plan.title, dates: plan.dates } : null; + latest.plan = plan ? { id: String(plan.id), title: plan.title, dates: plan.dates } : null; + if (plan) { settings.lastPlan = latest.plan; saveSettings(); } if (!plan) { latest.reachable = true; latest.lastError = 'No plan found for that service type.'; } else { @@ -521,12 +522,15 @@ module.exports = { const libEntries = Object.entries(library).map(([id, v]) => ({ id, name: v.name, file: v.file })); - // Slots a person is sitting in by hand — don't fill them, and don't place that same person twice. + // Slots a person was placed in by hand FOR THIS PLAN — don't fill them, and don't place that + // same person twice. A hand placement made for an earlier plan (last week's lineup, or one + // from before placements were stamped) is stale: Sync replaces it like any synced slot. const eff = effectiveSlots(); + const planId = String(plan.id); 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)) { + if (e && (e.personId || e.name) && e.plan === planId) { const nm = e.personId ? (library[e.personId] && library[e.personId].name) : e.name; if (nm) manualNames.add(normName(nm)); } else openIdx.push(i); @@ -540,8 +544,8 @@ module.exports = { const lib = bestLibraryMatch(m.name, libEntries); // folder photo wins over the PCO headshot const file = lib && lib.file ? lib.file : await cachePcoPhoto(m.personId, m.photoUrl); board[String(i)] = { pco: { name: m.name, file, birthday: m.birthday || '', personId: m.personId } }; - } else if (board[String(i)] && board[String(i)].pco) { - delete board[String(i)]; // was synced, no longer on the plan + } else if (board[String(i)]) { + delete board[String(i)]; // synced or stale, and no longer on the plan } } saveBoard(); @@ -580,6 +584,7 @@ module.exports = { current = { streams, getLatest: () => latest, pco, listPlans, photoDir, library, board, saveLibrary, saveBoard, rebuildLocal, refresh: () => syncNow(), slots, + planId: () => (latest.plan && latest.plan.id) || '', photoUrlFor, libraryList, setChoir, choirOn: () => choirOn, }; @@ -684,7 +689,7 @@ module.exports = { const personId = String(q.get('personId') || '').trim(); if (!/^\d+$/.test(slot)) return json(res, 400, { error: 'Bad slot.' }); if (personId && !current.library[personId]) return json(res, 404, { error: 'No such library photo.' }); - if (personId) current.board[slot] = { personId, name: '' }; + if (personId) current.board[slot] = { personId, name: '', plan: current.planId() }; else delete current.board[slot]; current.saveBoard(); current.rebuildLocal(); json(res, 200, { ok: true }); @@ -702,6 +707,7 @@ module.exports = { const name = String(q.get('name') || '').trim(); const existing = current.board[index] || { personId: '', name: '' }; existing.name = name; + existing.plan = current.planId(); if (!existing.personId && !name) delete current.board[index]; else current.board[index] = existing; current.saveBoard(); current.rebuildLocal();