{r.data.input.description??r.data.input.evidence}
{r.effective&&Versão efectiva: {r.effective.version}
}import React,{useEffect,useState,useRef} from 'react';
const kinds={asset:'Cadastro e custódia',plan:'Planos mensais',charge:'Quotas por competência',disposal:'Abate sem contraprestação'};
const fields={asset:{code:'Código QA',description:'Descrição',origin_reference:'Referência da abertura artificial',cost:'Custo MZN (desconhecido se vazio)',residual:'Residual MZN (desconhecido se vazio)',available_on:'Entrada em uso',location:'Localização interna',custodian:'Custódia interna'},plan:{asset_key:'Activo',life_months:'Vida artificial em meses',status:'Estado do plano'},charge:{asset_key:'Activo',plan_key:'Plano escolhido',month:'Competência'},disposal:{asset_key:'Activo',effect_on:'Data do abate'}};
const money=v=>v===null||v===undefined?'Indeterminado':`${BigInt(v)/100n},${String(BigInt(v)%100n).padStart(2,'0')} MZN`;
const label=r=>r?.data?.input?.code??r?.data?.input?.month??`${kinds[r?.kind]??'Registo'} · v${r?.version??''}`;
export default function FixedAssetsWorkspace({api,context,can,perform,busy}){
const[kind,setKind]=useState('asset'),[page,setPage]=useState(1),[size,setSize]=useState(25),[search,setSearch]=useState(''),[list,setList]=useState(null),[selected,setSelected]=useState(null),[form,setForm]=useState({}),[reason,setReason]=useState(''),[cut,setCut]=useState(null),[key,setKey]=useState(crypto.randomUUID()),[domain,setDomain]=useState(''),[result,setResult]=useState(null),[error,setError]=useState(''),[ledger,setLedger]=useState(null),[ledgerPage,setLedgerPage]=useState(1),[history,setHistory]=useState(false);
const reads=useRef({list:0,ledger:0});
const locked=busy||Boolean(cut);const unit=context.unit?.id;const chosenAsset=selected?(selected.kind==='asset'?selected.record_key:selected.data.input.asset_key):null;
async function load(){const q=new URLSearchParams({kind,page,per_page:size,...(search?{search}:{}),...(history&&selected?{record_key:selected.record_key}:{})});const n=++reads.current.list;const out=await api('/api/fixed-assets?'+q);if(n===reads.current.list){if(out.kind!==kind||out.records.per_page!==Number(size)||!Array.isArray(out.records.data)||out.records.data.length>size)throw Error('Resposta de consulta incompatível; actualize.');setList(out);}}
async function loadLedger(){const n=++reads.current.ledger;if(chosenAsset){const out=await api('/api/fixed-assets/ledger?'+new URLSearchParams({asset_key:chosenAsset,page:ledgerPage,per_page:25}));if(n===reads.current.ledger)setLedger(out);}else setLedger(null);}
useEffect(()=>{setList(null);load().catch(e=>setError(e.message));},[context.token,kind,page,size,search,history]);
useEffect(()=>{loadLedger().catch(e=>setError(e.message));},[chosenAsset,ledgerPage]);
function reset(k=kind){reads.current.list++;reads.current.ledger++;setKind(k);setSelected(null);setForm({operation:'establish',status:'active'});setHistory(false);setPage(1);setCut(null);setResult(null);setError('');setLedger(null);setKey(crypto.randomUUID());}
function choose(r){reads.current.ledger++;setLedger(null);setSelected(r);setForm({...((r.effective??r).data.input),operation:'amend'});setCut(null);setResult(null);setLedgerPage(1);setKey(crypto.randomUUID());}
async function preview(data,type='admin'){await perform(async()=>{setError('');setResult(null);setDomain(type);const out=await api('/api/fixed-assets'+(type==='ledger'?'/ledger':'')+'/preview','POST',data,context.token,key);setCut(out);},'Confira o impacto individual antes de confirmar.');}
async function confirm(){await perform(async()=>{try{const out=await api('/api/fixed-assets'+(domain==='ledger'?'/ledger':'')+'/confirm','POST',{preview:cut.preview});setResult({...out,command_key:key});setCut(null);setKey(crypto.randomUUID());await load();await loadLedger();setSelected(null);setForm({});}catch(e){setError(e.message);throw e;}},'Decisão registada e auditada.');}
async function recover(){await perform(async()=>{const out=await api(domain==='ledger'?'/api/general-ledger/receipts/'+key:'/api/fixed-assets/receipts/'+key);setResult(out.result??out);setCut(null);await load();await loadLedger();},'Comprovativo consultado sem repetir a operação.');}
const action=data=>preview({action:'effect-prepare',data},'ledger');
const source=selected?.effective??selected;const input=source?.data.input;const proposedAsset=source?.kind==='asset'?source.record_key:input?.asset_key;
return CONTABILIDADE Exercício artificial. Cadastro, plano, quota e lançamento exigem decisões próprias. Sem aquisição, pagamento ou tratamento fiscal legal presumidos. {error} {r.data.input.description??r.data.input.evidence} Versão efectiva: {r.effective.version} {label(selected)} · versão {selected.version} · autor #{selected.actor_id} · {selected.created_at} {selected.reason} Quota: {money(selected.data.calculation.quota_cents)} · líquido previsto: {money(selected.data.calculation.carrying_cents)} Preparar não aplica o lançamento. O diário exige outra revisão e aplicação explícita. Custo {money(ledger.balance.cost_minor)} · acumulado {money(ledger.balance.accumulated_minor)} · líquido {money(ledger.balance.carrying_minor)} · {ledger.balance.disposed?'Abatido':'Sem abate aplicado'} {p.snapshot.journal_on} · {money(p.snapshot.impact.debit_minor)} · {p.journal?'Lançamento #'+p.journal.id:p.review?'Revista':'Aguarda revisão'} {g.applied?'Aplicado integralmente':g.approved?'Revisto distintamente':'Aguarda revisão distinta'} Versão {cut.impact.before.version} → {cut.impact.after.version} · {cut.impact.after.state} {cut.impact.after.proposal.input.evidence} Quota {money(cut.impact.after.proposal.calculation.quota_cents)} · líquido previsto {money(cut.impact.after.proposal.calculation.carrying_cents)} Esta decisão administrativa cria zero lançamentos e zero pagamentos. {s.journal_on} · {money(s.impact.debit_minor)} Estado proposto: {cut.impact.state} · lançamentos nesta operação: {cut.impact.journal_count} Estado: {result.state} · {result.version?'versão '+result.version:''} · {result.journal_id?'lançamento #'+result.journal_id:''} Operação {result.command_key??key}. O histórico conserva autor, data, fundamento e estados.Activos e depreciações internas QA
{kinds[kind]}
{list?.records.data.map(r=>Versão seleccionada
{Object.entries(selected.data.input).filter(([k])=>fields[kind][k]).map(([k,v])=>
{selected.data.calculation?.complete&&{selected?'Preparar nova versão':'Preparar registo'}
Revisão independente
{['approved','refused'].map(decision=>)}Origem revista → diário comum
Diário do activo seleccionado
Proposta #{p.id} · {p.snapshot.source.effect_type}
Par de correcção #{g.id}
Impacto antes da confirmação
{domain==='admin'?<>{s.source.effect_type}
Comprovativo da operação
| Conta QA | Débito | Crédito |
|---|---|---|
| {l.account_code} | {money(l.debit_minor)} | {money(l.credit_minor)} |