Files
SGF/resources/js/workspace-overview.js
2026-11-11 15:04:01 +02:00

47 lines
11 KiB
JavaScript

// Read existing, authorized pages. No cross-module queries, writes or invented aggregates.
export const moduleGuides={
human_resources:{label:'Recursos Humanos',purpose:'Acompanhar cadastro, contratos, assiduidade, férias e remunerações internas com identidade e direitos próprios.',steps:['Cadastro e contrato revistos','Plano e declaração de tempos','Reserva e gozo de férias','Conferência do período','Remunerações e mapas conservados'],evidence:'Cada versão conserva fonte, autor, fundamento e resultado explicável; a exportação confere por SHA.',boundary:'Processamento interno QA não é salário pago nem cálculo legal IRPS/INSS. O calendário legal e os modelos oficiais permanecem pendentes; dias sem registos não são faltas presumidas.',next:'A ligação salarial a Financeiro e Contabilidade ainda não cria dívida, pagamentos ou lançamentos.'},
commercial:{label:'Comercial',purpose:'Acompanhar clientes, propostas, documentos e a passagem para recebimentos.',steps:['Cliente e serviço','Orçamento e aceitação','Rascunho e revisão','Documento de teste','Recebível e afectação'],evidence:'Número, cliente, versão, emissão e PDF identificam o documento. O recibo conserva a afectação de origem.',boundary:'Orçamento e rascunho não são receita. O saldo de uma factura pode aparecer em vários documentos ligados; não deve ser somado.',next:'A cobrança é acompanhada no Financeiro, conservando a factura de origem.'},
purchasing:{label:'Compras',purpose:'Acompanhar a aquisição desde a necessidade até à recepção e obrigação reconhecida.',steps:['Requisição','Propostas e avaliação','Adjudicação e ordem','Recepção e factura','Obrigação reconhecida'],evidence:'O dossiê mantém decisões, propostas, documentos recebidos, factura conferida e a origem da obrigação.',boundary:'Receber material não comprova pagamento. Uma factura pendente não reserva nem confirma automaticamente uma obrigação.',next:'A recepção alimenta o mesmo Stock; a obrigação segue para conferência financeira individual.'},
stock:{label:'Stock',purpose:'Conhecer o físico por artigo e armazém e rastrear entradas, saídas, trânsito e contagens.',steps:['Artigo e armazém','Recepção ou saída','Transferência e recepção','Lotes e contagem','Conferência da valorização'],evidence:'Cada movimento conserva quantidade, unidade, armazém e origem. Lotes e trânsito têm decisões próprias.',boundary:'Quantidades de unidades diferentes não são somadas. Físico, reservado, bloqueado e disponível não são equivalentes.',next:'Consumos e devoluções da Oficina mantêm a entrega original; valorização é conferida na Contabilidade.'},
workshop:{label:'Oficinas',purpose:'Organizar equipamento, cliente, agenda, autorização e trabalho efectivamente verificado.',steps:['Equipamento e cliente','Ordem e agenda','Autorização do trabalho','Materiais e horas','Conferência e facturação'],evidence:'A ordem liga equipamento, autorização do cliente, tarefa, técnico e apontamentos verificados.',boundary:'Agendar não autoriza trabalho. Horas apontadas não são horas verificadas; material declarado não substitui o movimento físico.',next:'O custo validado e o rascunho comercial conservam a ordem; não há emissão automática.'},
finance:{label:'Financeiro',purpose:'Conferir recebíveis, obrigações, Caixa, extractos e indicadores com as suas fontes.',steps:['Documento de origem','Preparação individual','Revisão distinta','Afectação ou conferência','Histórico e relatório'],evidence:'Recibos, afectações, contagens e correspondências bancárias permitem conferir quem decidiu e com que fundamento.',boundary:'Caixa, saldo de cliente e saldo de fornecedor são factos diferentes. Data de vencimento desconhecida não significa atraso.',next:'Os efeitos autorizados são conferidos no diário único; uma preparação não comprova execução bancária.'},
accounting:{label:'Contabilidade',purpose:'Consultar origens, diário, balancete, razão e os cálculos fiscais internos disponíveis.',steps:['Origem validada','Proposta contabilística','Revisão independente','Aplicação no diário','Relatório e período'],evidence:'Cada lançamento mantém origem e revisão; correcções conservam o original e o par de estorno/substituição.',boundary:'Só lançamentos aplicados entram nos montantes. Mapas e regras fiscais QA não são declarações ou modelos oficiais.',next:'A conferência do período reúne evidências e pendências; não reabre nem fecha um período por consultar.'},
};
export function overviewSources(context,can){
const unit=context?.unit?.id;if(!unit)return [];
const add=(id,label,permission,enabled,path,pageKey,target,href)=>({id,label,permission,enabled,path,pageKey,target,href});
const definitions={
commercial:[add('documents','Documentos comerciais','service_documents.view',context.service_documents_enabled,'/api/service-documents/register?per_page=25','documents','billing_all')],
purchasing:[add('requests','Requisições','purchasing_requests.view',context.single_installation,'/api/purchasing-requests','requests',null,'/purchasing/requests')],
stock:[add('balances','Existências físicas','stock.view',context.stock?.read===true||context.stock?.enabled===true||context.single_installation,'/api/stock-read?view=balances&per_page=25',null,'stock_balances')],
workshop:[add('orders','Ordens e apontamentos','workshop.view',context.single_installation,'/api/workshop/orders?per_page=25','orders','workshop_panel'),add('equipment','Equipamentos e clientes','workshop.view',context.workshop_catalogue_enabled,'/api/workshop-equipment?per_page=25','records','workshop_equipment')],
finance:[add('cash','Histórico de Caixa','cash.view',context.cash_enabled,'/api/cash?per_page=25','events',null,'/finance/cash'),add('indicators','Indicadores conservados','financial_analysis.view',context.analysis_qa_enabled,'/api/financial-indicators?view=results&per_page=25','records','financial_indicators')],
accounting:[add('journals','Lançamentos aplicados','general_ledger.view',context.accounting?.single_book,'/api/general-ledger',null,null,'/general-ledger'),add('calculations','Cálculos fiscais internos','fiscal_rules.view',context.fiscal_rules_qa_enabled,'/api/fiscal-rules?view=calculations&per_page=25','records','fiscal_rules')],
};
return(definitions[context.module_id]??[]).filter(s=>s.enabled===true&&can(s.permission,unit));
}
const moneyMinor=(n,currency='MZN')=>{if(n===null||n===undefined)return 'Indeterminado';if(typeof n==='number'&&!Number.isSafeInteger(n))throw Error('Montante sem representação inteira segura.');const x=BigInt(n),a=x<0n?-x:x;return`${x<0n?'-':''}${a/100n},${String(a%100n).padStart(2,'0')} ${currency}`;};
const quantity=n=>{const x=BigInt(n),a=x<0n?-x:x;return`${x<0n?'-':''}${a/1000n},${String(a%1000n).padStart(3,'0')}`;};
const states={draft:'Rascunho',pending:'Pendente',approved:'Aprovado',rejected:'Recusado',issued:'Emitido em teste',reviewed:'Revisto',prepared:'Preparado',closed:'Fechado',determined_qa:'Calculado QA',pending_representation:'Representação pendente'};
export function overviewPage(source,data){
const p=source.pageKey?data[source.pageKey]:data.pagination;
if(!p||!Number.isSafeInteger(p.total)||!Number.isSafeInteger(p.current_page??p.page)||!Number.isSafeInteger(p.last_page))throw Error('A fonte não devolveu uma página conferida. Abra a consulta completa.');
const input=source.pageKey?p.data:source.id==='journals'?data.journals:data.rows;
if(!Array.isArray(input)||input.length>25)throw Error('A página recebida não respeita o limite da consulta.');
const rows=input.map(r=>{
const state=states[r.document_state??r.status??r.state??r.result?.state]??(r.document_state??r.status??r.state??r.result?.state??'Consultar origem');
if(source.id==='documents')return{reference:r.reference,detail:r.client??'Cliente por determinar',state,value:r.amount===null?'Montante por determinar':`${r.amount} ${r.currency}`,evidence:`${r.kind} · versão ${r.version} · ${r.document_on??'Não emitido'}`};
if(source.id==='requests')return{reference:r.number,detail:'Requisição de aquisição',state,value:'Montante na requisição',evidence:`Versão ${r.process_version}`};
if(source.id==='balances')return{reference:r.code,detail:r.name,state:'Saldo físico observado',value:quantity(r.physical_milli)+' '+r.unit,evidence:`${r.warehouse_code} · ${r.warehouse_name} · disponível indeterminado`};
if(source.id==='orders')return{reference:r.reference,detail:`${r.client??'Cliente por conferir'} · ${r.equipment??'Equipamento por conferir'}`,state:({open:'Aberta',in_review:'Apontamentos em revisão',exception_review:'Ocorrências fora do âmbito',records_verified:'Registos verificados'})[r.state]??r.state,value:`${r.minutes_verified} min verificados / ${r.minutes_reported} min apontados`,evidence:`${r.technician??'Por atribuir'} · ${r.points_not_verified} registo(s) sem verificação favorável`};
if(source.id==='equipment')return{reference:r.identifier,detail:r.description,state:r.client_name,value:r.serial??'Série não registada',evidence:`Equipamento #${r.id} · cliente associado explicitamente`};
if(source.id==='journals')return{reference:'Lançamento #'+r.id,detail:r.reason,state:'Aplicado',value:`Débito ${moneyMinor(r.debit_minor,r.currency)} / crédito ${moneyMinor(r.credit_minor,r.currency)}`,evidence:`${r.journal_on} · ${r.currency} · revisão #${r.review_id}`};
if(source.id==='cash')return{reference:'Caixa · evento #'+r.id,detail:r.reason,state,value:r.data?.amount_minor===undefined?'Sem montante próprio':moneyMinor(r.data.amount_minor),evidence:`${r.kind} · ${r.action} · versão ${r.version} · ${r.data?.date??'Data no histórico'}`};
if(source.id==='indicators')return{reference:'Indicador #'+r.id,detail:r.metric,state,value:r.result?.value===null?'Indeterminado':`${r.result?.value??'Indeterminado'} ${r.result?.unit==='percent'?'%':r.result?.unit==='times'?'vezes':''}`,evidence:`Exacto ${r.result?.exact_fraction??'indeterminado'} · ${r.result?.as_of??'Corte no relatório'}`};
return{reference:'Cálculo #'+r.id,detail:r.reason,state,value:r.result?.total===null?'Total indeterminado':`${r.result?.total??'Indeterminado'} ${r.input?.currency??''}`,evidence:`${r.input?.date??'Data na origem'} · ${r.result?.price_mode??'Base simples'} · regra #${r.rule_event_id??'não escolhida'}`};
});
const facts=source.id==='cash'?[{label:'Saldo actual da Caixa QA',value:data.basis?moneyMinor(data.basis.balance_minor):'Por conferir'},{label:'Estado da sessão',value:data.basis?(data.basis.closed?'Fechada':data.basis.open?'Aberta':'Abertura por conferir'):'Fonte não conferida'}]:[];
return{rows,total:p.total,page:p.current_page??p.page,last:p.last_page,observed:data.observed_at??null,facts};
}