543 lines
17 KiB
PHP
543 lines
17 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
class ProjectCodeValidator
|
|
{
|
|
private array $projectData;
|
|
private array $components;
|
|
private string $separator;
|
|
private string $reference;
|
|
private int $sequenceLength;
|
|
|
|
/**
|
|
* Constructor de la clase
|
|
*
|
|
* @param array|string $project Los datos del proyecto (array o JSON string)
|
|
* @throws InvalidArgumentException Si los datos no son válidos
|
|
*/
|
|
public function __construct($project)
|
|
{
|
|
//print_r($project);
|
|
|
|
// Convertir string JSON a array si es necesario
|
|
if (is_string($project)) {
|
|
$project = json_decode($project, true);
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
throw new InvalidArgumentException('JSON inválido: ' . json_last_error_msg());
|
|
}
|
|
}
|
|
|
|
if (!is_array($project)) {
|
|
throw new InvalidArgumentException('Los datos del proyecto deben ser un array o JSON string');
|
|
}
|
|
|
|
$this->projectData = $project;
|
|
// Validar que exista la configuración de codificación
|
|
/*if (!isset($this->projectData['codingConfig'])) {
|
|
throw new InvalidArgumentException('El proyecto no tiene configuración de codificación');
|
|
}*/
|
|
|
|
$this->initializeConfiguration();
|
|
}
|
|
|
|
/**
|
|
* Inicializa la configuración a partir de los datos del proyecto
|
|
*/
|
|
private function initializeConfiguration(): void
|
|
{
|
|
$codingConfig = $this->projectData['codingConfig'];
|
|
|
|
// Obtener componentes ordenados por 'order'
|
|
$this->components = $codingConfig['elements']['components'] ?? [];
|
|
usort($this->components, fn($a, $b) => $a['order'] <=> $b['order']);
|
|
|
|
$this->separator = $codingConfig['separator'] ?? '-';
|
|
$this->reference = $this->projectData['reference'] ?? '';
|
|
$this->sequenceLength = $codingConfig['sequence_length'] ?? 4;
|
|
}
|
|
|
|
/**
|
|
* Valida si un código cumple con la configuración de codificación
|
|
*
|
|
* @param string $code El código a validar
|
|
* @return bool True si el código es válido
|
|
*/
|
|
public function validate(string $code): bool
|
|
{
|
|
// Validación básica: número de partes
|
|
$parts = explode($this->separator, $code);
|
|
if (count($parts) !== 7) {
|
|
return false;
|
|
}
|
|
|
|
// Validar referencia (primera parte)
|
|
if ($parts[0] !== $this->reference) {
|
|
return false;
|
|
}
|
|
|
|
// Validar cada componente (partes 2-6)
|
|
for ($i = 1; $i <= 5; $i++) {
|
|
if (!$this->validateComponent($i - 1, $parts[$i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// La parte 7 (DOCUMENT_NAME) no tiene validación específica
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Valida un componente específico del código
|
|
*
|
|
* @param int $componentIndex Índice del componente (0-4)
|
|
* @param string $value Valor a validar
|
|
* @return bool True si el valor es válido para el componente
|
|
*/
|
|
private function validateComponent(int $componentIndex, string $value): bool
|
|
{
|
|
if (!isset($this->components[$componentIndex])) {
|
|
return false;
|
|
}
|
|
|
|
$component = $this->components[$componentIndex];
|
|
$headerLabel = $component['headerLabel'] ?? '';
|
|
|
|
// Validar longitud máxima
|
|
$maxLength = $component['data']['maxLength'] ?? null;
|
|
if ($maxLength && strlen($value) > $maxLength) {
|
|
return false;
|
|
}
|
|
|
|
// Validar según el tipo de componente
|
|
if ($headerLabel === 'Version') {
|
|
return $this->validateVersionComponent($value);
|
|
}
|
|
|
|
// Validar contra códigos permitidos
|
|
return $this->validateAgainstAllowedCodes($component, $value);
|
|
}
|
|
|
|
/**
|
|
* Valida el componente de versión
|
|
*
|
|
* @param string $value Valor de la versión
|
|
* @return bool True si es válido
|
|
*/
|
|
private function validateVersionComponent(string $value): bool
|
|
{
|
|
// La versión debe ser numérica
|
|
if (!is_numeric($value)) {
|
|
return false;
|
|
}
|
|
|
|
// La versión debe tener la longitud especificada
|
|
if (strlen($value) !== $this->sequenceLength) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Valida un valor contra los códigos permitidos de un componente
|
|
*
|
|
* @param array $component Configuración del componente
|
|
* @param string $value Valor a validar
|
|
* @return bool True si el valor está en los códigos permitidos
|
|
*/
|
|
private function validateAgainstAllowedCodes(array $component, string $value): bool
|
|
{
|
|
$documentTypes = $component['data']['documentTypes'] ?? [];
|
|
|
|
if (empty($documentTypes)) {
|
|
return true; // No hay restricciones de códigos
|
|
}
|
|
|
|
$allowedCodes = array_column($documentTypes, 'code');
|
|
return in_array($value, $allowedCodes, true);
|
|
}
|
|
|
|
/**
|
|
* Realiza un análisis detallado de un código
|
|
*
|
|
* @param string $code El código a analizar
|
|
* @return array Array con información detallada de la validación
|
|
*/
|
|
public function analyze(string $code): array
|
|
{
|
|
$result = [
|
|
'is_valid' => false,
|
|
'code' => $code,
|
|
'parts' => [],
|
|
'errors' => [],
|
|
'warnings' => []
|
|
];
|
|
|
|
// Dividir en partes
|
|
$parts = explode($this->separator, $code);
|
|
|
|
// Verificar número de partes
|
|
$expectedParts = 7;
|
|
if (count($parts) !== $expectedParts) {
|
|
$result['errors'][] = sprintf(
|
|
'El código debe tener %d partes separadas por "%s", tiene %d',
|
|
$expectedParts,
|
|
$this->separator,
|
|
count($parts)
|
|
);
|
|
return $result;
|
|
}
|
|
|
|
// Nombres de los componentes según el orden esperado
|
|
$componentNames = [
|
|
'reference',
|
|
'maker',
|
|
'system',
|
|
'discipline',
|
|
'document_type',
|
|
'version',
|
|
'document_name'
|
|
];
|
|
|
|
// Analizar cada parte
|
|
foreach ($parts as $index => $value) {
|
|
$analysis = $this->analyzePart($index, $value);
|
|
$analysis['name'] = $componentNames[$index] ?? 'unknown';
|
|
|
|
$result['parts'][] = $analysis;
|
|
|
|
if (!$analysis['is_valid']) {
|
|
$result['errors'][] = sprintf(
|
|
'Parte %d (%s): %s',
|
|
$index + 1,
|
|
$componentNames[$index],
|
|
$analysis['error'] ?? 'Error desconocido'
|
|
);
|
|
}
|
|
}
|
|
|
|
$result['is_valid'] = empty($result['errors']);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Analiza una parte específica del código
|
|
*
|
|
* @param int $partIndex Índice de la parte (0-6)
|
|
* @param string $value Valor de la parte
|
|
* @return array Análisis detallado de la parte
|
|
*/
|
|
private function analyzePart(int $partIndex, string $value): array
|
|
{
|
|
$analysis = [
|
|
'index' => $partIndex,
|
|
'value' => $value,
|
|
'is_valid' => true,
|
|
'expected' => '',
|
|
'error' => '',
|
|
'notes' => []
|
|
];
|
|
|
|
switch ($partIndex) {
|
|
case 0: // Referencia
|
|
$analysis['expected'] = $this->reference;
|
|
if ($value !== $this->reference) {
|
|
$analysis['is_valid'] = false;
|
|
$analysis['error'] = sprintf(
|
|
'Referencia incorrecta. Esperado: %s',
|
|
$this->reference
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 1: // Maker (componente 0)
|
|
case 2: // System (componente 1)
|
|
case 3: // Discipline (componente 2)
|
|
case 4: // Document Type (componente 3)
|
|
$componentIndex = $partIndex - 1;
|
|
if (isset($this->components[$componentIndex])) {
|
|
$component = $this->components[$componentIndex];
|
|
$analysis = array_merge($analysis, $this->analyzeComponent($component, $value));
|
|
}
|
|
break;
|
|
|
|
case 5: // Version (componente 4)
|
|
if (isset($this->components[4])) {
|
|
$component = $this->components[4];
|
|
$analysis = array_merge($analysis, $this->analyzeComponent($component, $value));
|
|
|
|
// Información adicional para la versión
|
|
$analysis['notes'][] = sprintf(
|
|
'Longitud de secuencia: %d',
|
|
$this->sequenceLength
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 6: // Document Name
|
|
$analysis['notes'][] = 'Nombre del documento (sin restricciones específicas)';
|
|
break;
|
|
|
|
default:
|
|
$analysis['is_valid'] = false;
|
|
$analysis['error'] = 'Índice de parte no válido';
|
|
break;
|
|
}
|
|
|
|
return $analysis;
|
|
}
|
|
|
|
/**
|
|
* Analiza un componente específico
|
|
*
|
|
* @param array $component Configuración del componente
|
|
* @param string $value Valor a analizar
|
|
* @return array Análisis del componente
|
|
*/
|
|
private function analyzeComponent(array $component, string $value): array
|
|
{
|
|
$result = [
|
|
'is_valid' => true,
|
|
'expected' => '',
|
|
'error' => '',
|
|
'notes' => []
|
|
];
|
|
|
|
$headerLabel = $component['headerLabel'] ?? '';
|
|
$maxLength = $component['data']['maxLength'] ?? null;
|
|
$documentTypes = $component['data']['documentTypes'] ?? [];
|
|
|
|
// Validar longitud
|
|
if ($maxLength && strlen($value) > $maxLength) {
|
|
$result['is_valid'] = false;
|
|
$result['error'] = sprintf(
|
|
'Longitud máxima excedida: %d (actual: %d)',
|
|
$maxLength,
|
|
strlen($value)
|
|
);
|
|
}
|
|
|
|
// Validar tipo de componente
|
|
if ($headerLabel === 'Version') {
|
|
if (!is_numeric($value)) {
|
|
$result['is_valid'] = false;
|
|
$result['error'] = 'Debe ser numérico';
|
|
} elseif (strlen($value) !== $this->sequenceLength) {
|
|
$result['is_valid'] = false;
|
|
$result['error'] = sprintf(
|
|
'Longitud incorrecta. Esperado: %d, Actual: %d',
|
|
$this->sequenceLength,
|
|
strlen($value)
|
|
);
|
|
}
|
|
|
|
$result['expected'] = sprintf('Número de %d dígitos', $this->sequenceLength);
|
|
} else {
|
|
// Obtener códigos permitidos
|
|
$allowedCodes = array_column($documentTypes, 'code');
|
|
if (!empty($allowedCodes) && !in_array($value, $allowedCodes, true)) {
|
|
$result['is_valid'] = false;
|
|
$result['error'] = sprintf(
|
|
'Código no permitido. Permitidos: %s',
|
|
implode(', ', $allowedCodes)
|
|
);
|
|
}
|
|
|
|
$result['expected'] = $allowedCodes ? implode('|', $allowedCodes) : 'Sin restricciones';
|
|
$result['notes'][] = sprintf('Componente: %s', $headerLabel);
|
|
|
|
// Agregar etiquetas de los códigos permitidos
|
|
$labels = [];
|
|
foreach ($documentTypes as $type) {
|
|
if (isset($type['label'])) {
|
|
$labels[] = sprintf('%s = %s', $type['code'], $type['label']);
|
|
}
|
|
}
|
|
if (!empty($labels)) {
|
|
$result['notes'][] = 'Significados: ' . implode(', ', $labels);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Genera un código válido basado en la configuración
|
|
*
|
|
* @param array $componentsValues Valores para cada componente
|
|
* @param string $documentName Nombre del documento
|
|
* @return string Código generado
|
|
* @throws InvalidArgumentException Si los valores no son válidos
|
|
*/
|
|
public function generateCode(array $componentsValues, string $documentName): string
|
|
{
|
|
// Validar que tengamos valores para todos los componentes
|
|
$requiredComponents = 5; // Maker, System, Discipline, Document Type, Version
|
|
|
|
if (count($componentsValues) < $requiredComponents) {
|
|
throw new InvalidArgumentException(
|
|
sprintf('Se requieren valores para %d componentes', $requiredComponents)
|
|
);
|
|
}
|
|
|
|
// Construir las partes del código
|
|
$parts = [$this->reference];
|
|
|
|
// Agregar componentes validados
|
|
for ($i = 0; $i < $requiredComponents; $i++) {
|
|
$value = $componentsValues[$i];
|
|
|
|
if (!$this->validateComponent($i, $value)) {
|
|
throw new InvalidArgumentException(
|
|
sprintf('Valor inválido para componente %d: %s', $i, $value)
|
|
);
|
|
}
|
|
|
|
$parts[] = $value;
|
|
}
|
|
|
|
// Agregar nombre del documento
|
|
$parts[] = $this->sanitizeDocumentName($documentName);
|
|
|
|
return implode($this->separator, $parts);
|
|
}
|
|
|
|
/**
|
|
* Sanitiza el nombre del documento para usarlo en el código
|
|
*
|
|
* @param string $documentName Nombre del documento
|
|
* @return string Nombre sanitizado
|
|
*/
|
|
private function sanitizeDocumentName(string $documentName): string
|
|
{
|
|
// Reemplazar caracteres no deseados
|
|
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '_', $documentName);
|
|
|
|
// Limitar longitud si es necesario
|
|
return substr($sanitized, 0, 50);
|
|
}
|
|
|
|
/**
|
|
* Obtiene los códigos permitidos para un componente específico
|
|
*
|
|
* @param string $componentName Nombre del componente (maker, system, discipline, document_type)
|
|
* @return array Array de códigos permitidos
|
|
*/
|
|
public function getAllowedCodesForComponent(string $componentName): array
|
|
{
|
|
$componentMap = [
|
|
'maker' => 0,
|
|
'system' => 1,
|
|
'discipline' => 2,
|
|
'document_type' => 3
|
|
];
|
|
|
|
if (!isset($componentMap[$componentName])) {
|
|
return [];
|
|
}
|
|
|
|
$index = $componentMap[$componentName];
|
|
if (!isset($this->components[$index])) {
|
|
return [];
|
|
}
|
|
|
|
$component = $this->components[$index];
|
|
$documentTypes = $component['data']['documentTypes'] ?? [];
|
|
|
|
$result = [];
|
|
foreach ($documentTypes as $type) {
|
|
$result[] = [
|
|
'code' => $type['code'],
|
|
'label' => $type['label'] ?? $type['code'],
|
|
'max_length' => $type['max_length'] ?? $component['data']['maxLength'] ?? null
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Obtiene el formato de codificación
|
|
*
|
|
* @return string Formato de codificación
|
|
*/
|
|
public function getFormat(): string
|
|
{
|
|
return $this->projectData['coding_config']['format'] ?? '';
|
|
}
|
|
|
|
/**
|
|
* Obtiene la referencia del proyecto
|
|
*
|
|
* @return string Referencia del proyecto
|
|
*/
|
|
public function getReference(): string
|
|
{
|
|
return $this->reference;
|
|
}
|
|
|
|
/**
|
|
* Obtiene el separador usado en la codificación
|
|
*
|
|
* @return string Separador
|
|
*/
|
|
public function getSeparator(): string
|
|
{
|
|
return $this->separator;
|
|
}
|
|
|
|
/**
|
|
* Obtiene la longitud de secuencia para la versión
|
|
*
|
|
* @return int Longitud de secuencia
|
|
*/
|
|
public function getSequenceLength(): int
|
|
{
|
|
return $this->sequenceLength;
|
|
}
|
|
}
|
|
|
|
// Ejemplo de uso:
|
|
// Suponiendo que $projectDataVariable es la variable que contiene tus datos del proyecto
|
|
/*
|
|
$validator = new ProjectCodeValidator($projectDataVariable);
|
|
|
|
// Validar un código
|
|
$codigo = "SOGOS0001-SOGOS-PV0-CV-DWG-0001-InformeTecnico";
|
|
if ($validator->validate($codigo)) {
|
|
echo "✅ Código válido\n";
|
|
} else {
|
|
echo "❌ Código inválido\n";
|
|
}
|
|
|
|
// Analizar un código detalladamente
|
|
$analisis = $validator->analyze($codigo);
|
|
echo "Código analizado: " . $analisis['code'] . "\n";
|
|
echo "Válido: " . ($analisis['is_valid'] ? 'Sí' : 'No') . "\n";
|
|
|
|
if (!$analisis['is_valid']) {
|
|
echo "Errores:\n";
|
|
foreach ($analisis['errors'] as $error) {
|
|
echo "- $error\n";
|
|
}
|
|
}
|
|
|
|
// Generar un código nuevo
|
|
try {
|
|
$componentes = ['SOGOS', 'PV0', 'CV', 'DWG', '0014'];
|
|
$nuevoCodigo = $validator->generateCode($componentes, 'PlanosGenerales');
|
|
echo "Código generado: $nuevoCodigo\n";
|
|
} catch (InvalidArgumentException $e) {
|
|
echo "Error al generar código: " . $e->getMessage() . "\n";
|
|
}
|
|
|
|
// Obtener códigos permitidos para un componente
|
|
$systemCodes = $validator->getAllowedCodesForComponent('system');
|
|
echo "Códigos permitidos para System:\n";
|
|
foreach ($systemCodes as $code) {
|
|
echo "- {$code['code']}: {$code['label']}\n";
|
|
}
|
|
*/ |