import React, {useEffect,useRef,useState} from 'react'; const decisionLabel={approved:'Validado',refused:'Recusado'}; const money=v=>v===null?'Pendente':`${v} MZN`; export default function DirectCostsWorkspace({context,can,api,busy}) { const [data,setData]=useState(null),[error,setError]=useState(''),[working,setWorking]=useState(false),[orderId,setOrderId]=useState(''); const [selection,setSelection]=useState({}),[sourceId,setSourceId]=useState('');const retry=useRef(null); const allowed=r=>can(`direct_costs.${r}`,context.unit?.id??null); const load=async()=>{const d=await api('/api/direct-costs');setData(d);return d;}; useEffect(()=>{setSelection({});load().catch(e=>setError(e.message));},[context.token]); const order=data?.orders.find(o=>String(o.id)===orderId)??data?.orders[0]; async function send(path,input){setWorking(true);setError('');const signature=JSON.stringify([path,input]);if(retry.current?.signature!==signature)retry.current={signature,key:crypto.randomUUID()}; try{await api(path,'POST',input,context.token,retry.current.key);retry.current=null;await load();}catch(e){setError(e.message);}finally{setWorking(false);}} const disabled=busy||working; const latest=order?.calculations.at(-1); const source=order?.sources.find(s=>String(s.id)===sourceId)??order?.sources[0]; const decide=(kind,row,decision,reason)=>send(`/api/direct-costs/orders/${order.id}/decisions`,{kind,id:row.id,version:row.version,decision,reason}); return

ENSAIO DE CUSTO DIRECTO

Custos directos da ordem

ENSAIO — SEM UTILIZAÇÃO OPERACIONAL. Custo directo conhecido; não é custo completo ou margem. Sem stock, IVA, lançamentos ou facturação.
{error&&

{error}

}{!data?

A carregar…

:data.blocked?

O custeio permanece bloqueado nesta instância. Use apenas a demonstração isolada.

:<> {!order?

Sem ordens no âmbito autorizado.

:<>

Origens técnicas verificadas

Referências aos apontamentos existentes. A validação de custos não modifica nem verifica a execução técnica.

{allowed('prepare')&&source&&
{e.preventDefault();const f=new FormData(e.currentTarget);const previous=order.rates.filter(r=>r.identifier===f.get('identifier')).at(-1);send(`/api/direct-costs/orders/${order.id}/rates`,{entry_id:source.id,entry_version:source.version,identifier:f.get('identifier'),version:(previous?.version??0)+1,previous_id:previous?.id??null,currency:'MZN',unit:source.kind==='time'?'H':source.unit,amount:f.get('amount'),evidence:f.get('evidence')});}}>

Preparar taxa sintética versionada

Âmbito: esta ordem e versão da origem; MZN/{source.kind==='time'?'H':source.unit}. Zero é uma taxa explícita; campo vazio não vale zero.

}

Taxas e decisões

{order.rates.map(r=>
#{r.id} · {r.identifier} v{r.version}

Origem #{r.entry_id} v{r.entry_version} · {r.amount} {r.currency}/{r.unit}

{r.evidence}

{r.current?'Última versão da taxa':'Versão anterior'} · {r.decision?decisionLabel[r.decision.decision]:'Aguarda validação'}{r.decision&&` — ${r.decision.reason}`}

{allowed('validate')&&r.current&&!r.decision&&decide('rate',r,d,reason)}/>}
)}
{allowed('prepare')&&
{e.preventDefault();const lines=order.sources.map(s=>{const r=order.rates.find(r=>String(r.id)===String(selection[s.id]??''));return {entry_id:s.id,entry_version:s.version,rate_id:r?.id??null,rate_version:r?.version??null};});send(`/api/direct-costs/orders/${order.id}/calculations`,{currency:'MZN',version:(latest?.version??0)+1,previous_id:latest?.id??null,lines});}}>

Preparar cálculo com taxas explícitas

Escolher cada versão. Nenhuma taxa é escolhida automaticamente, mesmo que seja a única ou a mais recente.

{order.sources.map(s=>)}
}

Histórico de cálculos

{order.calculations.map(c=>

Cálculo #{c.id} · v{c.version}

{c.current?'Dependências actuais':'DESACTUALIZADO / HISTÓRICO — não usar como actual'} · {c.decision?decisionLabel[c.decision.decision]:'Aguarda validação'}{c.decision&&` — ${c.decision.reason}`}

{c.snapshot.label}

{c.snapshot.lines.map(l=>)}
Origem / versãoQuantidade exactaTaxa / versãoCusto exacto
#{l.entry_id} v{l.entry_version}{l.quantity} {l.source_unit}{l.kind==='time'&&` ÷ 60 = ${l.cost_quantity_exact} H`}{l.rate_id===null?'Em falta':`#${l.rate_id} v${l.rate_version} · ${l.rate_amount} MZN/${l.rate_unit}`}{money(l.cost_exact)}

Subtotal directo conhecido: {money(c.snapshot.known_subtotal_exact)}

{c.snapshot.incomplete?'TOTAL INCOMPLETO — taxas em falta':'Perímetro directo seleccionado valorizado; custo completo não apurado.'}

    {c.snapshot.pending.map(p=>
  • {p}
  • )}
{c.snapshot.policy} · {c.snapshot.scope}{allowed('validate')&&c.current&&!c.decision&&decide('calculation',c,d,reason)}/>}
)}
} }
; } function DecisionForm({disabled,onDecision}) {return
{e.preventDefault();const f=new FormData(e.currentTarget);onDecision(f.get('decision'),f.get('reason'));}}>
;}