Admin: award Suite capacity to any member as a team bonus
Marty asked for a way to grant impressions to anybody, labelled as an admin bonus. Team Grants already did this for leaders, but only within their own organisation and only out of a level pool. adminGrant() is deliberately a different thing: it comes from no pool (the team is not a position and has no allowance), it can reach ANY registered position rather than only someone downline, and it carries a reason that survives in the ledger. Recorded with by:0 so nothing downstream mistakes it for a leader's grant — the member's own page renders those as "the team" rather than "#0". The route verifies the position exists on-chain before crediting it; a typo would otherwise sit in the ledger crediting nobody, and it would be invisible until someone went looking. Awards land on top of whatever the level already allows and expire with the month like every other allowance. The admin page lists what has been awarded this month, so bonuses stay auditable rather than vanishing once given. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+39
-1
@@ -112,6 +112,44 @@ function status(memberId, level, preview) {
|
||||
};
|
||||
}
|
||||
|
||||
// An admin bonus. Distinct from a member grant in three ways: it comes from no
|
||||
// pool (the team is not a position and has no allowance), it can go to ANY
|
||||
// member rather than only someone in the granter's own organisation, and it
|
||||
// carries a note so the reason survives in the ledger. Recorded with by:0 so
|
||||
// nothing downstream mistakes it for a leader's grant.
|
||||
function adminGrant(opts) {
|
||||
const to = Number(opts.to);
|
||||
const tool = String(opts.tool || '');
|
||||
const n = Math.floor(Number(opts.n) || 0);
|
||||
if (!POOLS[tool]) throw new Error('That is not something that can be granted.');
|
||||
if (!to) throw new Error('Which position is this for?');
|
||||
if (n <= 0) throw new Error('How much?');
|
||||
if (n > 1000000) throw new Error('That is more than anyone could use in a month.');
|
||||
|
||||
const all = readAll();
|
||||
const mk = monthKey();
|
||||
if (!all[mk]) all[mk] = [];
|
||||
const rec = {
|
||||
by: 0, to: to, tool: tool, n: n,
|
||||
admin: true,
|
||||
note: String(opts.note || '').replace(/\s+/g, ' ').trim().slice(0, 80) || 'Team bonus',
|
||||
at: new Date().toISOString()
|
||||
};
|
||||
all[mk].push(rec);
|
||||
const keep = Object.keys(all).sort().slice(-3);
|
||||
const trimmed = {};
|
||||
keep.forEach(function (k) { trimmed[k] = all[k]; });
|
||||
writeAll(trimmed);
|
||||
return rec;
|
||||
}
|
||||
|
||||
// Everything the team has handed out this month, newest first — for the admin
|
||||
// view, so bonuses are auditable rather than invisible once given.
|
||||
function adminLog(limit) {
|
||||
return rows().filter(function (r) { return r.admin; })
|
||||
.slice(-(Math.max(1, Math.min(200, Number(limit) || 50)))).reverse();
|
||||
}
|
||||
|
||||
// Hand capacity to someone. The caller is responsible for having verified that
|
||||
// `to` is inside `by`'s organisation — that check needs the chain and lives in
|
||||
// the route, not here.
|
||||
@@ -148,4 +186,4 @@ function grant(opts) {
|
||||
return rec;
|
||||
}
|
||||
|
||||
module.exports = { init, MIN_LEVEL, POOLS, tools, poolFor, status, grant, receivedBy, givenBy, receivedDetail };
|
||||
module.exports = { init, MIN_LEVEL, POOLS, tools, poolFor, status, grant, adminGrant, adminLog, receivedBy, givenBy, receivedDetail };
|
||||
|
||||
Reference in New Issue
Block a user