Files
SGF/public/purchasing-focus.mjs
2026-11-04 09:37:20 +02:00

33 lines
3.7 KiB
JavaScript

const definitions={requests:['request'],suppliers:['supplier'],sourcing:['request'],orders:['award'],receipts:['order','receipt','movement'],invoices:['order','invoice'],obligations:['invoice'],dossiers:['request']};
export function readFocus(stage,search){
const keys=definitions[stage];if(!keys)throw Error('Etapa de consulta inválida.');const query=new URLSearchParams(search);if(!query.size)return null;const result={};
for(const [key,value]of query){if(!keys.includes(key)||query.getAll(key).length!==1||!/^[1-9][0-9]*$/.test(value)||!Number.isSafeInteger(Number(value)))throw Error('Atalho inválido. Seleccione o registo na lista autorizada.');result[key]=Number(value);}
if(stage==='receipts'&&result.movement){if(Object.keys(result).length!==1)throw Error('Atalho inválido. Seleccione o registo na lista autorizada.');return result;}
if(!result[keys[0]])throw Error('O atalho não identifica a origem. Seleccione o registo na lista autorizada.');return result;
}
export function focusHref(stage,ids){const query=new URLSearchParams(Object.entries(ids).map(([k,v])=>[k,String(v)]));readFocus(stage,query);return '/purchasing/'+stage+'?'+query;}
/** Keep the currently authorised document when explicitly choosing another QA function. */
export function rememberFocus(stage,ids){
const entry=document.querySelector('[data-purchasing-entry]');if(!entry)return;
const href=focusHref(stage,ids);globalThis.history?.replaceState(null,'',href);entry.href='/compras?'+new URLSearchParams({return_to:href});
}
export function continuationHref(stage,ids){
const href=focusHref(stage,ids);
if(document.querySelector('nav[aria-label="Etapas de Compras"] a[href="/purchasing/'+stage+'"]'))return href;
return document.querySelector('[data-purchasing-entry]')?'/compras?'+new URLSearchParams({return_to:href}):null;
}
export function appendProcessLinks(target,links,allowed=()=>true){
for(const link of links??[]){try{const match=/^\/purchasing\/([a-z]+)\?([^#]+)$/.exec(link.href);if(!match||focusHref(match[1],Object.fromEntries(new URLSearchParams(match[2])))!==link.href)continue;readFocus(match[1],match[2]);if(!allowed(match[1]))continue;const a=document.createElement('a');a.href=link.href;a.textContent=link.label;target.append(a);}catch{/* An invalid navigation hint must never become an external or ambiguous link. */}}
}
/** Menu visibility is a presentation hint. Destination APIs remain the authority. */
export function appendStageLinks(root,target,links){
if(root.dataset.continuityEnabled!=='true')return;
appendProcessLinks(target,links,stage=>Boolean(document.querySelector('nav[aria-label="Etapas de Compras"] a[href="/purchasing/'+stage+'"]')));
if(document.querySelector('[data-purchasing-entry]'))for(const link of links??[]){try{const match=/^\/purchasing\/([a-z]+)\?([^#]+)$/.exec(link.href);if(!match||focusHref(match[1],Object.fromEntries(new URLSearchParams(match[2])))!==link.href||document.querySelector('nav[aria-label="Etapas de Compras"] a[href="/purchasing/'+match[1]+'"]'))continue;const a=document.createElement('a');a.href='/compras?'+new URLSearchParams({return_to:link.href});a.textContent='Mudar função para '+link.label;target.append(a);}catch{/* Invalid hints never become navigation. */}}
}
/** Startup does reads only; it never calls a preview/confirmation or changes company/module. */
export async function beginWorkspace({stage,root,load,open,current,error,search=globalThis.location?.search??''}){
let focus=null,problem=null;if(root.dataset.focusEnabled==='true'){try{focus=readFocus(stage,search);}catch(e){problem=e.message;}}
const pending=load(),generation=current();await pending;if(current()!==generation)return;if(problem){error(problem);return;}if(focus)await open(focus);
}