import React,{useEffect,useRef,useState} from 'react'; import {workshopBillingProposal} from './workshopBillingProposal.js'; export default function WorkshopBillingWorkspace({context,can,api,busy}){ const [data,setData]=useState(null),[orderId,setOrderId]=useState(''),[error,setError]=useState(''),[working,setWorking]=useState(false),[notice,setNotice]=useState('');const retry=useRef(null);const feedback=useRef(null); const allowed=r=>can(`workshop_billing.${r}`,context.unit?.id??null);const load=async()=>setData(await api('/api/workshop-billing')); useEffect(()=>{setData(null);setOrderId('');retry.current=null;load().catch(e=>setError(e.message));},[context.token]); const order=data?.orders.find(o=>String(o.id)===orderId)??data?.orders[0],disabled=busy||working; useEffect(()=>{if(error||notice)feedback.current?.scrollIntoView({behavior:'auto',block:'center'});},[error,notice]); async function send(action,input){setWorking(true);setError('');setNotice('');const path=`/api/workshop-billing/orders/${order.id}/${action}`,signature=JSON.stringify([path,input]);if(retry.current?.signature!==signature)retry.current={signature,key:crypto.randomUUID()};try{const result=await api(path,'POST',input,context.token,retry.current.key);retry.current=null;setNotice(action==='proposals'?`Proposta #${result.id} · v${result.version} registada. Aguarda revisão distinta.`:action==='reviews'?'Revisão comercial registada.':'Rascunho gerado.');try{await load();}catch{setError('O registo foi guardado, mas a lista não actualizou. Use Actualizar versões explicitamente; não volte a submeter.');}}catch(e){setError(e.message);}finally{setWorking(false);}} return

WORKSHOP-BILLING-DRAFT-SIM-v1

Oficina → rascunho de facturação

ENSAIO — SEM UTILIZAÇÃO OPERACIONAL. Preços comerciais explícitos; custo interno não determina preço de venda. Sem emissão ou confirmação automática.
{notice&&

{notice}

}{error&&

{error}

}
{!data?

A carregar…

:data.blocked?

Conversão real bloqueada nesta instância.

:<>{order?<>

Cliente #{order.client_id} · MZN explícito · Seleccionar origens integrais verificadas.

{allowed('prepare')&&!order.generation&&
{e.preventDefault();try{const input=workshopBillingProposal(order,data.services,new FormData(e.currentTarget));send("proposals",input);}catch(error){setError(error.message);}}}>

Seleccionar e preparar proposta comercial

{order.entries.map(r=>
Origem #{r.id} v{r.version} · {r.kind==='time'?`${r.minutes} MIN verificados`:`${r.quantity} ${r.data.unit} consumidos — ${r.data.resource}`}
)}
} {order.proposals.map(p=>

Proposta #{p.id} · v{p.version}

{p.current?'Versão actual':'HISTÓRICO / DESACTUALIZADA'} · {p.decision?`${p.decision.decision==='approved'?'Revista e aprovada':'Recusada'} — ${p.decision.reason}`:'Aguarda revisão distinta'}

{p.snapshot.evidence}

{p.snapshot.lines.map(l=>)}
Origem exactaServiço / versãoQuantidade comercialPreço MZNFonte
#{l.entry_id} v{l.entry_version}#{l.service_id} v{l.service_version}{l.quantity} {l.unit}{l.unit_price}{l.price_source}

Soma sem imposto: {p.snapshot.base_exact} MZN · Fiscalidade pendente.

{allowed('review')&&p.current&&!p.decision&&
{e.preventDefault();const f=new FormData(e.currentTarget);send('reviews',{id:p.id,version:p.version,decision:f.get('decision'),reason:f.get('reason')});}}>
}{allowed('generate')&&p.current&&p.decision?.decision==='approved'&&!order.generation&&}
)} {order.generation&&

Rascunho original gerado — sem validade fiscal

{order.generation.commercial_state === "superseded" &&

Original substituído; consultar a cadeia em Correcção e cancelamento.

}{order.generation.latest_commercial_change?.kind === "cancel" &&

Preparação comercial cancelada. Origens não libertadas.

}{order.generation.latest_commercial_change?.new_billing_id &&

Destino sucessor: prestação #{order.generation.latest_commercial_change.new_service_id} · rascunho #{order.generation.latest_commercial_change.new_billing_id}

}

Prestação #{order.generation.service_draft_id} · Rascunho de facturação #{order.generation.billing_draft_id} · Proposta #{order.generation.proposal_id}

Origens utilizadas uma única vez. Edição directa bloqueada; correcção exige circuito próprio, fora deste recorte.

}:

Sem ordens autorizadas neste contexto.

}}
; }