import React, {useEffect, useRef, useState} from 'react'; export function budgetMoney(value) { if (value === null || value === undefined) return 'Indisponível'; const n=BigInt(String(value)), sign=n<0n?'-':'', digits=(n<0n?-n:n).toString().padStart(3,'0'); return `${sign}${digits.slice(0,-2).replace(/\B(?=(\d{3})+(?!\d))/g,' ')}.${digits.slice(-2)} MZN`; } const kinds={reserve:'Reserva',award:'Ajuste de adjudicação',commit:'Compromisso',release:'Libertação'}; export default function PurchasingBudgetWorkspace({context,can,api,busy}) { const [data,setData]=useState(null),[error,setError]=useState(''),[notice,setNotice]=useState(''),[working,setWorking]=useState(false),[selection,setSelection]=useState(''); const retry=useRef(null); const activeContext=useRef(context.token);activeContext.current=context.token; const allowed=r=>can(`purchasing_budget.${r}`,context.unit?.id??null); const load=async()=>{const token=context.token;const next=await api('/api/purchasing-budget','GET',undefined,token);if(activeContext.current===token)setData(next);}; useEffect(()=>{setData(null);setSelection('');setError('');setNotice('');retry.current=null;load().catch(e=>setError(e.message));},[context.token]); const disabled=busy||working; const budget=data?.budgets.find(b=>String(b.id)===selection)??data?.budgets[0]; const availableOrigins=data?.origins.filter(o=>o.cost_center_id===budget?.cost_center_id)??[]; const canMove=!!budget?.approved_by&&Number(budget.year)===data?.current_year&&availableOrigins.length>0; async function send(action,input) { const signature=JSON.stringify([action,input,context.token]); if(retry.current?.signature!==signature)retry.current={signature,key:crypto.randomUUID()}; setWorking(true);setError('');setNotice(''); try {const result=await api(`/api/purchasing-budget/${action}`,'POST',input,context.token,retry.current.key);if(activeContext.current!==context.token)return;retry.current=null;await load();if(action==='create')setSelection(String(result.id));setNotice(result.replayed?'Resposta recuperada; nenhum efeito duplicado.':'Operação sintética registada. Saldos actualizados pelo núcleo comum.');} catch(e){setError(e.message);}finally{setWorking(false);} } const submit=(action,convert)=>event=>{event.preventDefault();send(action,convert(Object.fromEntries(new FormData(event.currentTarget))));}; const originOptions=availableOrigins.map(o=>{ const movements=data.entries.filter(e=>e.requisition_id===o.id).map(e=>e.kind); const stage=movements.includes('release')?'Libertada':movements.includes('commit')?'Comprometida':movements.includes('award')?'Adjudicação ajustada':movements.includes('reserve')?'Reservada':'Sem movimentos'; return ; }); const moneyInput=(name,label,value='')=>; return

DEMONSTRAÇÃO ORÇAMENTAL

Compras — controlo orçamental

ENSAIO SINTÉTICO — SEM UTILIZAÇÃO REAL. Dotações, reservas, compromissos e consumo usam o núcleo comum de Compras. Este percurso não é o orçamento industrial.

Empresa: {context.company.name} · Unidade: {context.unit?.name} · Moeda: MZN

Prepare a dotação, entre como validador para a aprovar e use a conta de operação para os movimentos. Decisões exigem identidade distinta. Consumo aqui é apenas um efeito orçamental simulado, sem factura, obrigação ou pagamento.

{error&&

{error}

}{notice&&

{notice}

} {!data?

A carregar o percurso…

:<> {allowed('create')&&

Preparar dotação

({...d,cost_center_id:Number(d.cost_center_id),year:Number(d.year),includes_tax:d.includes_tax==='true'}))}>
{moneyInput('amount','Dotação inicial (MZN)','1000.00')}