nextRoundRobin(key, people, opts) returns the next person in a rotation and remembers that choice in Script Properties. A script lock wraps the read-and-write so two form submits in the same second cannot both get the same person. Pass {out: [...]} to skip people who are away.
When to use
- Assigning new leads, tickets, or reviews evenly
- An
onFormSubmitor append handler that must not skip or double-assign - A rotation that changes when someone is on vacation
How to use this snippet
/**
* Return the next person in a named rotation.
* Stores the last assignee in Script Properties under roundRobin:<key>.
* Uses LockService.getScriptLock() around the read-check-write.
* opts.out is a list of people to skip (case-insensitive).
*
* @param {string} key rotation id, e.g. 'leads'
* @param {string[]} people full roster, in order
* @param {{out?: string[], lockMs?: number}} [opts]
* @return {string}
*/
function nextRoundRobin(key, people, opts) {
opts = opts || {};
var outSet = {};
(opts.out || []).forEach(function (p) { outSet[String(p).toLowerCase()] = true; });
var lock = LockService.getScriptLock();
if (!lock.tryLock(opts.lockMs || 30000)) {
throw new Error('nextRoundRobin: could not acquire script lock');
}
try {
var props = PropertiesService.getScriptProperties();
var propKey = 'roundRobin:' + key;
var last = props.getProperty(propKey);
var start = 0;
if (last) {
for (var i = 0; i < people.length; i++) {
if (String(people[i]) === last) { start = i + 1; break; }
}
}
for (var n = 0; n < people.length; n++) {
var cand = people[(start + n) % people.length];
if (cand && !outSet[String(cand).toLowerCase()]) {
props.setProperty(propKey, String(cand));
return cand;
}
}
throw new Error('nextRoundRobin: no one available');
} finally {
lock.releaseLock();
}
}
Example
function assignLead(e) {
var team = ['Ann', 'Bob', 'Cam'];
var out = ['Cam']; // vacation
var owner = nextRoundRobin('leads', team, { out: out });
// write owner onto the new row...
}
Notes
- The lock is a script lock, because the cursor lives in Script Properties (project-wide), not in one spreadsheet. If the lock times out, the function throws — it does not silently assign.
- Walking starts just after the last stored person, wraps around, and skips anyone in
opts.out. If the last person left the roster, it starts from the top. - If everyone is out, it throws
nextRoundRobin: no one available. - Script Properties are per project. Two spreadsheets bound to the same project share the cursor; copies with their own project do not.
Happy Coding!
