Rotation exclusions: config rotationExcludeIds - excluded positions are never offered as the company-rotation target but their subtree is still traversed (founders reserving #4's slot for an incoming team)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -686,13 +686,16 @@ function memberIdByAccount(address) {
|
|||||||
// Company-rotation pick: breadth-first (matrix order, left→right) first
|
// Company-rotation pick: breadth-first (matrix order, left→right) first
|
||||||
// position under `rootId` that still needs directs — the "next open team
|
// position under `rootId` that still needs directs — the "next open team
|
||||||
// position" for the public rotation when publicRotationMode==='chain'.
|
// position" for the public rotation when publicRotationMode==='chain'.
|
||||||
function nextOpenPosition(rootId) {
|
function nextOpenPosition(rootId, exclude) {
|
||||||
if (!state || !state.snapshotAt) return null;
|
if (!state || !state.snapshotAt) return null;
|
||||||
|
const skip = exclude || new Set();
|
||||||
const q = [rootId]; const seen = new Set();
|
const q = [rootId]; const seen = new Set();
|
||||||
while (q.length) {
|
while (q.length) {
|
||||||
const id = q.shift(); if (seen.has(id)) continue; seen.add(id);
|
const id = q.shift(); if (seen.has(id)) continue; seen.add(id);
|
||||||
const m = state.members[id]; if (!m) continue;
|
const m = state.members[id]; if (!m) continue;
|
||||||
if ((m.directCount || 0) < 2) return { id, directCount: m.directCount || 0, level: levelName(m.level || 1) };
|
// excluded positions are never offered as the target, but their subtree
|
||||||
|
// is still traversed (founders can reserve a position's open slots)
|
||||||
|
if (!skip.has(id) && (m.directCount || 0) < 2) return { id, directCount: m.directCount || 0, level: levelName(m.level || 1) };
|
||||||
if (m.l) q.push(m.l); if (m.r) q.push(m.r);
|
if (m.l) q.push(m.l); if (m.r) q.push(m.r);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -553,7 +553,8 @@ async function handleApi(req,res,pathname){
|
|||||||
// links first, and his new right-leg position must be secured first).
|
// links first, and his new right-leg position must be secured first).
|
||||||
if((c.publicRotationMode||'queue')==='chain'){
|
if((c.publicRotationMode||'queue')==='chain'){
|
||||||
const root=Number(c.publicRotationRootId)||2;
|
const root=Number(c.publicRotationRootId)||2;
|
||||||
const pick=chain.nextOpenPosition(root);
|
const exclude=new Set(String(c.rotationExcludeIds||'').split(',').map(n=>Number(n.trim())).filter(n=>n>0));
|
||||||
|
const pick=chain.nextOpenPosition(root,exclude);
|
||||||
if(pick)return json(res,200,{sponsor:{id:String(pick.id),name:null,directs:pick.directCount,goal:2,level:pick.level,referralUrl:`${c.dappReferralBaseUrl}${encodeURIComponent(pick.id)}`},waitingCount:0,mode:'chain',message:'Company rotation — you join under the next open team position. It advances automatically as positions qualify.'});
|
if(pick)return json(res,200,{sponsor:{id:String(pick.id),name:null,directs:pick.directCount,goal:2,level:pick.level,referralUrl:`${c.dappReferralBaseUrl}${encodeURIComponent(pick.id)}`},waitingCount:0,mode:'chain',message:'Company rotation — you join under the next open team position. It advances automatically as positions qualify.'});
|
||||||
}
|
}
|
||||||
const sponsors=getSponsors(),a=activeSponsor(sponsors);if(!a)return json(res,404,{error:'No active sponsor is currently assigned.'});
|
const sponsors=getSponsors(),a=activeSponsor(sponsors);if(!a)return json(res,404,{error:'No active sponsor is currently assigned.'});
|
||||||
@@ -663,7 +664,7 @@ async function handleApi(req,res,pathname){
|
|||||||
const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors});
|
const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors});
|
||||||
}
|
}
|
||||||
if(req.method==='PATCH'&&pathname==='/api/admin/config'){
|
if(req.method==='PATCH'&&pathname==='/api/admin/config'){
|
||||||
const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next});
|
const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next});
|
||||||
}
|
}
|
||||||
const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/);
|
const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/);
|
||||||
if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});
|
if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});
|
||||||
|
|||||||
Reference in New Issue
Block a user