21 lines
5.5 KiB
PHP
21 lines
5.5 KiB
PHP
<?php
|
|
namespace App\Modules\HumanResources;
|
|
/** A human-readable derivation of one immutable approved version, never a new decision. */
|
|
final class PersonnelReadableCsv
|
|
{
|
|
public const VERSION='SGF-HR-READABLE-CSV-v1';
|
|
private static function time(mixed $value):string{return$value===null?'Desconhecido':sprintf('%dh %02dmin (%d minutos)',intdiv((int)$value,60),(int)$value%60,(int)$value);}
|
|
public static function render(array $row):string
|
|
{
|
|
$d=$row['data'];$i=$d['input'];$r=$d['result'];$states=['active'=>'Activo','terminated'=>'Terminado','cancelled'=>'Cancelado','reserved'=>'Reservado','taken'=>'Gozado'];$titles=['contract'=>'Contrato interno QA','day'=>'Plano e assiduidade QA','entitlement'=>'Condição de férias QA','leave'=>'Pedido e gozo de férias QA','report'=>'Conferência do período QA'];
|
|
$lines=[['SGF — RECURSOS HUMANOS — RESUMO INTERNO QA'],['Documento',$titles[$row['kind']]],['Natureza','Vista legível derivada da versão conservada; não é documento legal nem nova decisão.'],['Colaborador fictício',$d['dependencies']['worker']['code']],['Registo',$row['record_key']],['Versão revista',$row['version']],['Estado nesta versão',$states[$d['status']]??$d['status']],['Preparação','QA'.$row['prepared_by']],['Autor desta revisão','QA'.$row['actor_id']],['Data da revisão (origem UTC)',$row['created_at']],['Fundamento',$row['reason']],['Referência da evidência',$i['evidence']],['Formato de leitura',self::VERSION],['SHA do CSV original conservado',$d['artifact_sha256']],[]];
|
|
if($row['kind']==='contract'){$lines=[...$lines,['CONDIÇÕES DO CONTRATO'],['Referência',$i['reference']],['Modalidade interna',['fixed'=>'Prazo explícito','open'=>'Sem termo declarado','unknown_end'=>'Fim desconhecido'][$i['mode']]],['Início',$i['valid_from']],['Fim',$i['valid_to']??'Não confirmado'],['Bruto mensal declarado',$i['gross'].' MZN'],['Registos dependentes observados nesta decisão',$r['dependent_records']],['Validação jurídica','Não demonstrada']];}
|
|
if($row['kind']==='day'){$lines=[...$lines,['ASSIDUIDADE DO DIA'],['Dia local',$i['date']],['Zona',$r['zone']],['Referência da declaração',$i['reference_on']],['Tempo previsto',self::time($r['planned_minutes'])],['Tempo declarado',self::time($r['declared_minutes'])],['Férias conferidas',self::time($r['leave_minutes'])],['Tempo por conferir',self::time($r['unaccounted_minutes'])],['Fora do plano (sem autorização de horas extra)',self::time($r['outside_plan_minutes'])]];foreach(['planned'=>'Intervalos previstos','actual'=>'Intervalos declarados']as$key=>$title){$lines[]=[$title];if($i[$key]===null)$lines[]=['Informação','Desconhecida'];elseif($i[$key]===[])$lines[]=['Informação','Conjunto explicitamente vazio'];else foreach($i[$key]as$range)$lines[]=['Desde / até (hora local)',sprintf('%02d:%02d',intdiv($range['from'],60),$range['from']%60),sprintf('%02d:%02d',intdiv($range['to'],60),$range['to']%60)];}}
|
|
if(in_array($row['kind'],['entitlement','leave'],true)){$b=$r['balance_after']??$r;$lines[]=['FÉRIAS — VALORES CONSERVADOS NESTA VERSÃO'];if($row['kind']==='entitlement')$lines[]=['Vigência',$i['valid_from'].' a '.$i['valid_to']];else{$lines[]=['Dia',$i['date']];$lines[]=['Pedido',self::time($r['minutes'])];}$lines=[...$lines,['Orçamento explícito',self::time($b['entitlement_minutes']??null)],['Reservado',self::time($b['reserved_minutes'])],['Gozado',self::time($b['taken_minutes'])],['Disponível',self::time($b['available_minutes'])],['Limite','Orçamento fictício; não comprova direito legal anual. Não é saldo actual se decisões posteriores existirem.']];}
|
|
if($row['kind']==='report'){$lines=[...$lines,['CONFERÊNCIA DO PERÍODO'],['Período',$r['period']],['Dias com registos',$r['known_days']],['Dias conferidos',$r['known_conferred_days']],['Dias com conferência desconhecida',$r['days_with_unknown_conference']],['Outros dias sem cobertura comprovada',$r['other_calendar_days_unknown']],['Mês completo','Não demonstrado; dias sem registos não são faltas presumidas.'],[],['Dia local','Versão do dia','Previsto','Declarado','Férias conferidas','Por conferir']];foreach($r['days']as$day){$c=$day['calculation'];$lines[]=[$c['date'],$day['version'],self::time($c['planned_minutes']),self::time($c['declared_minutes']),self::time($c['leave_minutes']),self::time($c['unaccounted_minutes'])];}$lines[]=[];$lines[]=['SALDOS DE FÉRIAS NESTA CONFERÊNCIA','Versão da condição','Orçamento','Reservado','Gozado','Disponível'];foreach($r['leave_balances']as$b)$lines[]=['Condição QA',$b['entitlement_version'],self::time($b['entitlement_minutes']),self::time($b['reserved_minutes']),self::time($b['taken_minutes']),self::time($b['available_minutes'])];$lines[]=[];$lines[]=['PROCESSAMENTOS OBSERVADOS','Versão','Líquido interno (centavos)','Ligação contratual comprovada'];foreach($r['payroll']as$p)$lines[]=['Processamento QA',$p['version'],$p['net_cents']??'Indeterminado',$p['contract_link_proven']?'Sim, escolhida na origem':'Não; sem atribuição retroactiva'];}
|
|
$lines=[...$lines,[],['Limites','Sem salário, dívida, pagamento, lançamento ou submissão automáticos. Sem conformidade legal presumida.'],['Fonte','Apenas esta versão revista. Não consulta nem recalcula o estado actual.']];
|
|
// Apostrophe protects all free text, including formula prefixes after whitespace. BOM supports local Excel.
|
|
return "\xEF\xBB\xBF".implode("\r\n",array_map(fn($line)=>implode(';',array_map(fn($cell)=>'"'.str_replace('"','""',"'".(string)$cell).'"',$line)),$lines))."\r\n";
|
|
}
|
|
}
|