45 lines
11 KiB
JavaScript
45 lines
11 KiB
JavaScript
export function confirmation(send){let frozen=null,busy=false,uncertain=false;return {
|
|
get pending(){return Boolean(frozen)},get uncertain(){return uncertain},get busy(){return busy},
|
|
async run(body){if(busy)throw Error('A confirmação está em curso.');if(frozen&&JSON.stringify(body)!==JSON.stringify(frozen))throw Error('Recupere a confirmação anterior.');frozen??=JSON.parse(JSON.stringify(body));busy=true;uncertain=true;try{const r=await send(frozen);if(r.status>=400&&r.status<500){frozen=null;uncertain=false;throw Error(r.value?.message??'Decisão recusada. Actualize e confira novamente.');}if(r.status<200||r.status>=300||!r.value||typeof r.value.replayed!=='boolean')throw Error('Resultado incerto. Recupere a mesma confirmação.');frozen=null;uncertain=false;return r.value;}finally{busy=false;}},
|
|
retry(){if(!frozen)throw Error('Sem confirmação pendente.');return this.run(frozen);}
|
|
};}
|
|
|
|
if(typeof document!=='undefined'&&document.getElementById('transfers')){
|
|
const $=id=>document.getElementById(id),text=(tag,value)=>{const el=document.createElement(tag);el.textContent=String(value);return el;};let state=null,preview=null,page=1,historyPage=1;
|
|
const api=async(path,body)=>{const r=await fetch(path,{method:body?'POST':'GET',credentials:'same-origin',headers:{Accept:'application/json',...(body?{'Content-Type':'application/json','X-CSRF-TOKEN':document.querySelector('meta[name=csrf-token]').content,'X-SGF-Context':document.querySelector('meta[name=sgf-context]').content}:{})},body:body?JSON.stringify(body):undefined});let value;try{value=await r.json();}catch{value=null;}return {status:r.status,value};};
|
|
const op=confirmation(body=>api('/api/stock-final/confirm',body));
|
|
const lock=()=>{const pending=op.pending||op.busy;document.body.dataset.sgfOperationUncertain=String(op.uncertain);$('fields').disabled=pending||Boolean(preview);$('refresh').disabled=pending;$('confirm').hidden=!preview||pending;$('retry').hidden=!op.uncertain||op.busy;$('cancel').hidden=!preview||pending;};
|
|
const failure=e=>{$('message').textContent=e.message;lock();};
|
|
const field=(id,label,type='text',value='')=>{const l=text('label',label);const el=document.createElement('input');el.id=id;el.type=type;el.value=value;el.required=true;l.append(el);$('inputs').append(l);return el;};
|
|
const select=(id,label,rows)=>{const l=text('label',label),el=document.createElement('select');el.id=id;el.required=true;el.append(new Option('Escolher…',''));for(const [value,name]of rows)el.append(new Option(name,String(value)));l.append(el);$('inputs').append(l);return el;};
|
|
async function history(){const r=await api('/api/stock-final/history?'+new URLSearchParams({page:historyPage,per_page:25}));if(r.status!==200)throw Error(r.value?.message??'Histórico indisponível.');const v=r.value;$('history').replaceChildren(...v.data.map(row=>text('p',row.created_at+' · autor QA '+row.host_author_id+' · '+row.result.impact.action+' · '+row.reason)));$('historyPage').textContent='Página '+v.current_page+' de '+v.last_page;$('historyPrevious').disabled=historyPage<=1;$('historyNext').disabled=historyPage>=v.last_page;}
|
|
const p=()=>state.policy,lotContext=()=>state.basis.lot_context;
|
|
const qty=v=>(v/1000).toLocaleString('pt-MZ',{maximumFractionDigits:3});
|
|
function renderFields(){preview=null;$('impact').replaceChildren();$('inputs').replaceChildren();const a=$('action').value;if(!a){lock();return;}
|
|
if(['dispatch','receive','return'].includes(a)){
|
|
if(a==='dispatch'){select('item','Artigo',state.items.filter(i=>i.id===p().transfer_item_id).map(i=>[i.id,i.code+' · '+i.name]));select('source','Armazém de origem',state.warehouses.filter(w=>w.id===p().source_warehouse_id).map(w=>[w.id,w.name]));select('destination','Armazém de destino',state.warehouses.filter(w=>w.id===p().destination_warehouse_id).map(w=>[w.id,w.name]));}
|
|
else select('transfer','Transferência da página actual',state.transfers.data.filter(t=>t.remaining_milli>0).map(t=>[t.transfer_key,t.transfer_key+' · por receber '+qty(t.remaining_milli)]));field('quantity','Quantidade (UN)','text');
|
|
}else if(a==='prepare'){
|
|
select('item','Artigo a controlar',state.items.filter(i=>i.id===p().lot_item_id).map(i=>[i.id,i.code+' · '+i.name]));select('warehouse','Armazém',state.warehouses.filter(w=>w.id===p().source_warehouse_id).map(w=>[w.id,w.name]));
|
|
field('days','Validade mínima restante (dias)','number','0');for(let n=1;n<=2;n++){field('code'+n,'Código do lote '+n);field('expires'+n,'Validade do lote '+n,'date');field('lotQty'+n,'Quantidade do lote '+n);}
|
|
}else if(a==='activate'){select('setup','Configuração preparada',state.lot_records.filter(r=>r.kind==='lot_setup'&&r.state==='draft').map(r=>[r.id,'Configuração #'+r.id+' · versão '+r.version]));}
|
|
else if(['identify','hold','release'].includes(a)){select('lot','Lote', (lotContext()?.lots??[]).map(l=>[l.id,(l.data.code??'Sem código')+' · '+l.state+' · '+qty(l.quantity_milli)+'UN']));if(a==='identify'){field('code','Código');field('expires','Validade','date');}}
|
|
else if(a==='count'){field('quantity','Total explicitamente contado (UN)');for(const l of lotContext()?.lots??[])field('count'+l.id,'Quantidade contada · '+(l.data.code??'#'+l.id));}
|
|
else if(a==='adjustment')select('count','Contagem por rever',state.counts.map(c=>[c.id,c.reference+' · '+qty(c.quantity_milli)+'UN']));lock();
|
|
}
|
|
function data(){const a=$('action').value,value=id=>$(id).value,num=id=>Number(value(id));if(['dispatch','receive','return'].includes(a)){const transfer=a==='dispatch'?null:state.transfers.data.find(t=>t.transfer_key===value('transfer'));if(a!=='dispatch'&&!transfer)throw Error('Escolha uma transferência.');return {quantity:value('quantity'),transfer_key:transfer?.transfer_key??crypto.randomUUID(),stock_item_id:a==='dispatch'?num('item'):transfer.stock_item_id,source_warehouse_id:a==='dispatch'?num('source'):transfer.source_warehouse_id,destination_warehouse_id:a==='dispatch'?num('destination'):transfer.destination_warehouse_id};}
|
|
if(a==='prepare')return {version:0,data:{stock_item_id:num('item'),warehouse_id:num('warehouse'),requires_expiry:true,minimum_remaining_days:num('days'),lots:[1,2].map(n=>({code:value('code'+n),expires_on:value('expires'+n),quantity:value('lotQty'+n)}))}};
|
|
if(a==='activate'){const r=state.lot_records.find(r=>r.id===num('setup'));return {record_key:r.record_key,version:r.version,data:{}};}
|
|
if(['identify','hold','release'].includes(a)){const l=lotContext().lots.find(l=>l.id===num('lot'));return {record_key:l.record_key,version:l.version,data:a==='identify'?{code:value('code'),expires_on:value('expires'),no_expiry:false}:{lot_id:l.id,warehouse_id:p().source_warehouse_id,state_version:l.state_version}};}
|
|
if(a==='count'){const context=state.lot_count_source;if(!context)throw Error('Active e confira os lotes antes da contagem.');return {quantity:value('quantity'),ledger_version:state.basis.lot_balance.version,lot_version:context.hash,lot_counts:context.lots.map(l=>({lot_id:l.lot_id,quantity:value('count'+l.lot_id)}))};}
|
|
if(a==='adjustment')return {quantity:'0.000',ledger_version:state.basis.lot_balance.version,count_id:num('count')};throw Error('Escolha uma operação.');}
|
|
async function load(){if(op.pending)throw Error('Recupere a confirmação pendente antes de actualizar.');const r=await api('/api/stock-final?'+new URLSearchParams({page,per_page:$('size').value,search:$('search').value}));if(r.status!==200)throw Error(r.value?.message??'Não foi possível consultar o contexto.');state=r.value;preview=null;$('message').textContent='';$('summary').replaceChildren(text('p','Artigo de transferência: origem '+qty(state.basis.transfer_source.quantity)+'UN; destino '+qty(state.basis.transfer_destination.quantity)+'UN. Trânsito total: '+qty(state.in_transit_milli)+'UN.'),text('p','Artigo por lotes: físico '+qty(state.basis.lot_balance.quantity)+'UN; disponível por qualidade '+(lotContext()?qty(lotContext().usable_milli)+'UN':'por conferir')+'.'));
|
|
$('action').replaceChildren(new Option('Escolher…',''));for(const [a,label]of(state.reviewer?[['receive','Receber transferência'],['activate','Activar controlo por lotes'],['release','Libertar lote'],['adjustment','Rever e aplicar diferença contada']]:[['dispatch','Expedir transferência'],['return','Receber retorno à origem'],['prepare','Preparar controlo por lotes'],['identify','Identificar/corrigir lote'],['hold','Reter lote'],['count','Registar contagem por lote']]))$('action').append(new Option(label,a));
|
|
$('transfers').replaceChildren();for(const t of state.transfers.data){const tr=document.createElement('tr');for(const v of[t.transfer_key,qty(t.quantity_milli),qty(t.remaining_milli)])tr.append(text('td',v));$('transfers').append(tr);}$('page').textContent='Página '+state.transfers.current_page+' de '+state.transfers.last_page;$('previous').disabled=page<=1;$('next').disabled=page>=state.transfers.last_page;
|
|
$('lots').replaceChildren(...(lotContext()?.lots??[]).map(l=>text('p',(l.data.code??'Sem código')+' · '+qty(l.quantity_milli)+'UN · '+l.state+' · validade '+(l.data.expires_on??'sem prazo')+' · versão '+l.version)));renderFields();await history();}
|
|
$('historyPrevious').onclick=()=>{historyPage--;history().catch(failure);};$('historyNext').onclick=()=>{historyPage++;history().catch(failure);};
|
|
$('action').onchange=renderFields;$('operation').onsubmit=async e=>{e.preventDefault();try{$('fields').disabled=true;const r=await api('/api/stock-final/preview',{action:$('action').value,operation_key:crypto.randomUUID(),revision:state.revision,reason:$('reason').value,data:data()});if(r.status!==200)throw Error(r.value?.message??'Conferência recusada.');preview=r.value.preview;const labels={action:'Operação',quantity_milli:'Quantidade em milésimos',physical_before_milli:'Físico anterior em milésimos',physical_after_milli:'Físico posterior em milésimos',in_transit_after_milli:'Em trânsito após decisão em milésimos',ownership_change_milli:'Alteração da propriedade em milésimos',physical_change_milli:'Diferença física em milésimos',counted_milli:'Contado em milésimos',state:'Estado após decisão',financial_posting:'Cria lançamento automático',stock_item_id:'Artigo',warehouse_id:'Armazém'};const dl=document.createElement('dl');for(const [k,v]of Object.entries(r.value.impact)){dl.append(text('dt',labels[k]??k),text('dd',typeof v==='boolean'?(v?'Sim':'Não'):v));}$('impact').replaceChildren(text('h3','Impacto desta decisão'),dl);lock();}catch(e){$('fields').disabled=false;failure(e);}};
|
|
async function submit(retry=false){try{$('message').textContent='';const promise=retry?op.retry():op.run({preview});lock();const result=await promise;$('receipt').textContent='Decisão registada. '+(result.replayed?'Recibo recuperado. ':'')+(result.event_id?'Evento #'+result.event_id:result.operation_id?'Operação #'+result.operation_id:'Registo #'+result.record_id);preview=null;await load();}catch(e){failure(e);}finally{lock();}}
|
|
$('confirm').onclick=()=>submit();$('retry').onclick=()=>submit(true);$('cancel').onclick=()=>{preview=null;$('impact').replaceChildren();lock();};$('refresh').onclick=()=>load().catch(failure);$('previous').onclick=()=>{page--;load().catch(failure);};$('next').onclick=()=>{page++;load().catch(failure);};$('size').onchange=$('search').onchange=()=>{page=1;load().catch(failure);};window.addEventListener('beforeunload',e=>{if(op.pending||preview){e.preventDefault();e.returnValue='';}});load().catch(failure);
|
|
}
|