/** Scoped opt-in proxy. Never intercept the existing V1 schedule. */ export function createPlannerV4Bridge({configuredPrognosisPlant,roleAllowed,bodyJson,json, deviceActivation,checkDeviceRate,ownedLicenseState,serviceToken, upstreamUrl='http://netplan-v4:9100',fetchImpl=fetch}){ const base=new URL(upstreamUrl); if(!['http:','https:'].includes(base.protocol)||base.username||base.password)throw new Error('Invalid V4 URL'); async function forward(req,res,installation,suffix,method){ if(!serviceToken){json(res,503,{error:'V4 ist nicht angebunden.'});return;} const url=new URL(`/internal/v2/prognosis/${encodeURIComponent(installation)}/planner${suffix}`,base); let response; const payload=method==='GET'?undefined:await bodyJson(req); try{response=await fetchImpl(url,{method,headers:{'X-Enelix-Service-Token':serviceToken,'Content-Type':'application/json'},body:payload===undefined?undefined:JSON.stringify(payload),signal:AbortSignal.timeout(10000),redirect:'error'});} catch{json(res,503,{error:'V4 nicht erreichbar. Bestehende Regelung bleibt unveraendert.'});return;} const data=await response.json().catch(()=>null); if(!response.ok){const code=[400,401,403,404,409,413].includes(response.status)?response.status:503;json(res,code,{error:code===409?'Versionskonflikt: Ansicht neu laden.':'V4-Anfrage abgelehnt oder Dienst nicht verfuegbar.'});return;} if(!data||typeof data!=='object'){json(res,503,{error:'Ungueltige V4-Antwort.'});return;} json(res,200,data); } return async function(req,res,url){ const c=url.pathname.match(/^\/api\/plants\/([0-9a-f-]{36})\/prognosis\/planner-v4(?:\/(settings|replan))?$/i); if(c){ const action=c[2]||'',method={'':'GET',settings:'PUT',replan:'POST'}[action]; if(req.method!==method){json(res,405,{error:'Methode nicht erlaubt.'});return true;} const access=configuredPrognosisPlant(req,res,c[1],method!=='GET');if(!access)return true; if(Number(access.license.quantities.grid_schedule||0)<1){json(res,403,{error:'Netzfahrplan nicht lizenziert.'});return true;} if(method!=='GET'&&!roleAllowed(access.session,['owner','admin','operator'])){json(res,403,{error:'Keine Aenderungsberechtigung.'});return true;} await forward(req,res,access.plant.installation_id,action?`/${action}`:'',method);return true; } const d=url.pathname.match(/^\/api\/v1\/installations\/([0-9a-f-]{36})\/prognosis\/planner-v4(?:\/(operation|ack|measurements))?$/i); if(d){ const action=d[2]||'';if(req.method!==(action?'POST':'GET')){json(res,405,{error:'Methode nicht erlaubt.'});return true;} if(!checkDeviceRate(req,d[1],'planner-v4')){json(res,429,{error:'Zu viele Anfragen.'});return true;} const activation=deviceActivation(req,res,d[1]);if(!activation)return true; if(Number(ownedLicenseState(activation.plant_id).quantities.grid_schedule||0)<1){json(res,403,{error:'Netzfahrplan nicht lizenziert.'});return true;} await forward(req,res,d[1],action==='operation'?'/inputs/operation':action==='measurements'?'/measurements':action?'/ack':'',req.method);return true; } return false; }; }