import React,{useEffect,useMemo,useRef,useState}from'react'; import{WorkshopStockOperation}from'./workshop-stock-operation.js'; const PersonnelSourcePicker=React.lazy(()=>import('./PersonnelWorkspace.jsx').then(m=>({default:m.PersonnelSourcePicker}))); import{remunerationDownload}from'./statement-download.js'; const qs=q=>new URLSearchParams(Object.entries(q).filter(([,v])=>v!==''&&v!==null)).toString(); const states={prepared:'Preparado — sem aprovação',approved:'Revisto e aprovado',refused:'Recusado'},kinds={worker:'Cadastro fictício',rules:'Regras artificiais',payroll:'Processamento interno'}; export const remunerationMoney=c=>c===null||c===undefined?'Indeterminado':new Intl.NumberFormat('pt-MZ',{minimumFractionDigits:2,maximumFractionDigits:2}).format(Number(c)/100)+' MZN'; export function checkedRemunerations(data,q){if(data.policy!=='SGF-REMUNERATIONS-QA-v1'||data.synthetic_only!==true||data.legal_payroll_validated!==false||data.kind!==q.kind||data.records?.current_page!==q.page||!Array.isArray(data.records?.data)||data.records.data.length>Number(q.per_page)||data.records.data.some(r=>r.kind!==q.kind||(q.record_key&&r.record_key!==q.record_key)))throw Error('Resposta de outro âmbito/página. Consulte novamente.');return data;} function SourcePicker({kind,api,context,value,onChange,disabled}){ const[query,setQuery]=useState({kind,page:1,per_page:25}),[search,setSearch]=useState(''),[data,setData]=useState(null),[error,setError]=useState(''); useEffect(()=>{let active=true;setData(null);api('/api/remunerations?'+qs(query)).then(d=>{if(active)setData(checkedRemunerations(d,query));}).catch(e=>{if(active)setError(e.message);});return()=>{active=false;};},[context.token,query]); return
{kind==='worker'?'Escolher colaborador fictício revisto':'Escolher regras revistas'}{error&&

{error}

}{data&&<>Página{data.records.current_page}/{data.records.last_page}}
; } function Calculation({value}){if(!value)return null;return

Cálculo artificial explicável

Bruto{remunerationMoney(value.gross_cents)} · descontos{remunerationMoney(value.deductions_cents)} · líquido interno{remunerationMoney(value.net_cents)} · custo interno{remunerationMoney(value.employer_cost_cents)}.

{value.complete?'Parâmetros do exercício completos.':'Parâmetros em falta: aprovação completa bloqueada.'} Sem validação legal de IRPS/INSS.

{Object.entries(value.rubrics).map(([k,r])=>)}
Rubrica QAPercentagem artificialValor documentalExacto / diferença em centavos
{{worker_rate:'Desconto do trabalhador',employer_rate:'Encargo patronal',retention_rate:'Retenção artificial'}[k]}{r.rate??'Em falta'}{remunerationMoney(r.cents)}{r.exact??'Indeterminado'} / {r.difference??'Indeterminado'}
;} export default function RemunerationsWorkspace({api,context}){ const[query,setQuery]=useState({kind:'worker',page:1,per_page:25}),[filters,setFilters]=useState({search:'',period:'',state:''}),[data,setData]=useState(null),[selected,setSelected]=useState(null),[form,setForm]=useState(null),[preview,setPreview]=useState(null),[busy,setBusy]=useState(false),[error,setError]=useState(''),[notice,setNotice]=useState(''),[receiptKey,setReceiptKey]=useState(''),[receipt,setReceipt]=useState(null); const operation=useMemo(()=>new WorkshopStockOperation(api),[context.token]),mounted=useRef(true),generation=useRef(context.token);generation.current=context.token;useEffect(()=>{mounted.current=true;return()=>{mounted.current=false;};},[context.token]);const locked=busy||operation.uncertain,protectedForm=locked||Boolean(form); useEffect(()=>{let active=true;setBusy(true);setData(null);setError('');api('/api/remunerations?'+qs(query)).then(d=>{if(active)setData(checkedRemunerations(d,query));}).catch(e=>{if(active)setError(e.message);}).finally(()=>{if(active)setBusy(false);});return()=>{active=false;};},[query,context.token]); function start(operationName='establish',review=false,newRecord=false){const row=newRecord?null:selected,source=row?.effective?.data.input??row?.data.input,kind=query.kind;const defaults=kind==='worker'?{code:'QA-',status:'active'}:kind==='rules'?{label:'',valid_from:'',valid_to:'',worker_rate:'',employer_rate:'',retention_rate:'',source:''}:{worker_key:'',rules_key:'',period:'',gross:''};setPreview(null);setError('');setForm({kind,action:review?'review':'prepare',record_key:row?.record_key??crypto.randomUUID(),version:row?.version??0,reason:'',data:review?{decision:''}:{...(source??defaults),operation:operationName}});} const change=(key,value)=>{setPreview(null);setForm(f=>({...f,data:{...f.data,[key]:value,...(key==='worker_key'&&f.data.contract_key?{contract_key:''}:{})}}));}; async function prepare(e){e.preventDefault();setBusy(true);setError('');try{const input={...form,data:{...form.data}};if(!input.data.contract_key)delete input.data.contract_key;if(input.kind==='rules'&&input.action==='prepare')for(const k of['worker_rate','employer_rate','retention_rate'])if(input.data[k]==='')input.data[k]=null;const key=crypto.randomUUID(),p=await api('/api/remunerations/preview','POST',input,undefined,key);setPreview({...p,key});}catch(e){setError(e.message);}finally{setBusy(false);}} async function confirm(retry=false){setBusy(true);setError('');try{const out=retry?await operation.retry():await operation.send('/api/remunerations/confirm',{preview:preview.preview},preview.key);setReceiptKey(preview.key);setNotice('Decisão v'+out.version+' · '+states[out.state]+(out.replayed?' · recibo recuperado':''));setSelected(null);setForm(null);setPreview(null);setQuery(q=>({...q,page:1}));}catch(e){setError(e.message);if(!operation.uncertain)setPreview(null);}finally{setBusy(false);}} async function download(row){const token=context.token;setBusy(true);setError('');try{const out=await remunerationDownload({id:row.id,token,sha256:row.data.artifact_sha256,isCurrent:()=>mounted.current&&generation.current===token});const url=URL.createObjectURL(new Blob([out.bytes],{type:'text/csv;charset=utf-8'})),a=document.createElement('a');a.href=url;a.download='sgf-remuneracao-interna-QA-v'+row.version+'.csv';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);}catch(e){if(mounted.current&&generation.current===token)setError(e.message);}finally{if(mounted.current&&generation.current===token)setBusy(false);}} async function recover(){setBusy(true);setError('');try{setReceipt(await api('/api/remunerations/receipts/'+encodeURIComponent(receiptKey)));}catch(e){setError(e.message);}finally{setBusy(false);}} const textInput=(key,label,type='text',extra={})=>; return

RECURSOS HUMANOS

Remunerações internas QA

Área autónoma, com acessos próprios. Cadastro e regras fictícios, processamento e revisão distinta. Sem salário/dívida/pagamento/lançamento, recibo legal ou submissão. Contratos, assiduidade e férias estão na função própria de RH. A conformidade legal continua pendente.

{error&&

{error}

}{notice&&

{notice}

}
{e.preventDefault();setSelected(null);setQuery(q=>({kind:q.kind,per_page:q.per_page,...filters,page:1}));}}>
{query.kind==='payroll'&&}
{data&&<>{data.capabilities.prepare&&!query.record_key&&}

Página{data.records.current_page}/{data.records.last_page} · {data.records.total} versão/registo(s).

{data.records.data.map(r=>)}
Registo / períodoVersão e decisãoResultado revisto actualAutor / fundamentoEscolha
{r.data.input.code??r.data.input.label??r.data.dependencies.worker?.data.input.code}{r.data.input.period??''} · {r.record_key}v{r.version} · {states[r.state]} · {r.data.input.operation}{r.effective?<>v{r.effective.version} · {r.effective.data.status}{r.effective.data.calculation&&Líquido interno{remunerationMoney(r.effective.data.calculation.net_cents)}}:'Sem aprovação'}QA{r.actor_id} · {r.created_at}{r.reason}
{!data.records.data.length&&

Sem registos neste âmbito/filtro.

}{query.record_key&&}{query.kind==='payroll'&&!query.record_key&&

Somente aprovados activos desta página: {data.page_totals.approved_active_on_page}; bruto{remunerationMoney(data.page_totals.gross_cents)}, líquido{remunerationMoney(data.page_totals.net_cents)}, custo{remunerationMoney(data.page_totals.employer_cost_cents)}. Não são totais do universo nem salários pagos.

}} {selected&&

Registo escolhido · v{selected.version}

{JSON.stringify(selected.data.input,null,2)}

Estado anterior: v{selected.impact.before.version} · {selected.impact.before.state??'Sem registo'}. Estado posterior: v{selected.impact.after.version} · {states[selected.state]}.

{!query.record_key&&data.capabilities.prepare&&selected.effective?.data.status!=='cancelled'&&<>{query.kind==='payroll'&&selected.effective&&}}{!query.record_key&&data.capabilities.review&&selected.state==='prepared'&&}{selected.state==='approved'&&selected.kind==='payroll'&&}
} {form&&

{form.action==='review'?'Revisão distinta':'Preparação individual'} · {kinds[form.kind]} · v{form.version}

{form.action==='review'?:<>

Operação{form.data.operation}. A versão e as fontes serão conferidas outra vez na confirmação.

{form.kind==='worker'?<>{textInput('code','Código pseudónimo QA','text',{pattern:'QA-[A-Z0-9-]{2,60}',readOnly:form.version>0})}:form.kind==='rules'?<>{textInput('label','Designação das regras')}{textInput('valid_from','Vigência desde','date')}{textInput('valid_to','Vigência até','date')}{['worker_rate','employer_rate','retention_rate'].map((key,i)=>textInput(key,['Desconto trabalhador % artificial','Encargo patronal % artificial','Retenção % artificial'][i],'number',{required:false,min:0,max:100,step:'0.0001'}))}{textInput('source','Fonte do exercício QA (não legal)')}:<>change('worker_key',v)} disabled={locked||form.version>0}/>change('rules_key',v)} disabled={locked||form.data.operation==='cancel'}/>{context.hr_personnel_enabled&&A abrir contratos…

}>change('contract_key',key)} disabled={locked||form.data.operation==='cancel'}/>

Ligação opcional explícita. Introduza o bruto conforme o contrato revisto; não recalcula versões anteriores.

}{textInput('period','Período artificial','month',{readOnly:form.version>0})}{textInput('gross','Bruto MZN (duas casas decimais)','text',{pattern:'[0-9]{1,9}\\.[0-9]{2}',readOnly:form.data.operation==='cancel'})}}}