new functions
This commit is contained in:
@@ -1,134 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class DocumentIdentifier {
|
|
||||||
// Diccionarios para validar disciplinas y tipos
|
|
||||||
private $disciplinasValidas = [
|
|
||||||
'ENG' => 'Ingeniería',
|
|
||||||
'ARC' => 'Arquitectura',
|
|
||||||
'CIV' => 'Civil',
|
|
||||||
'MEC' => 'Mecánica',
|
|
||||||
'ELC' => 'Eléctrica',
|
|
||||||
'INS' => 'Instrumentación',
|
|
||||||
'PIP' => 'Piping',
|
|
||||||
'STR' => 'Estructural'
|
|
||||||
];
|
|
||||||
|
|
||||||
private $tiposDocumentoValidos = [
|
|
||||||
'DRW' => 'Dibujo',
|
|
||||||
'ESP' => 'Especificación',
|
|
||||||
'LST' => 'Lista de materiales',
|
|
||||||
'PRO' => 'Procedimiento',
|
|
||||||
'INF' => 'Informe',
|
|
||||||
'MAN' => 'Manual',
|
|
||||||
'CAL' => 'Cálculo',
|
|
||||||
'REP' => 'Reporte'
|
|
||||||
];
|
|
||||||
|
|
||||||
public function analizarDocumento($codigoCompleto) {
|
|
||||||
// Validar formato básico
|
|
||||||
if (strpos($codigoCompleto, ' - ') === false) {
|
|
||||||
return $this->crearResultadoError("Formato inválido: falta separador ' - '");
|
|
||||||
}
|
|
||||||
|
|
||||||
list($codigo, $nombre) = explode(' - ', $codigoCompleto, 2);
|
|
||||||
$segmentos = explode('-', $codigo);
|
|
||||||
|
|
||||||
// Validar número de segmentos
|
|
||||||
if (count($segmentos) != 5) {
|
|
||||||
return $this->crearResultadoError("Número incorrecto de segmentos");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extraer y validar cada parte
|
|
||||||
$codigoProyecto = $segmentos[0];
|
|
||||||
$codigoMaker = $segmentos[1];
|
|
||||||
$disciplina = $segmentos[2];
|
|
||||||
$tipoDocumento = $segmentos[3];
|
|
||||||
$revisionCompleta = $segmentos[4];
|
|
||||||
|
|
||||||
// Validar formato de revisión
|
|
||||||
if (strpos($revisionCompleta, 'REV.') !== 0) {
|
|
||||||
return $this->crearResultadoError("Formato de revisión inválido");
|
|
||||||
}
|
|
||||||
|
|
||||||
$numeroRevision = substr($revisionCompleta, 4); // Remover "REV."
|
|
||||||
|
|
||||||
// Validar número de revisión
|
|
||||||
if (!ctype_digit($numeroRevision) || strlen($numeroRevision) != 2) {
|
|
||||||
return $this->crearResultadoError("Número de revisión inválido");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validar disciplinas y tipos
|
|
||||||
$disciplinaValida = $this->validarDisciplina($disciplina);
|
|
||||||
$tipoValido = $this->validarTipoDocumento($tipoDocumento);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'codigo_completo' => $codigoCompleto,
|
|
||||||
'codigo_proyecto' => $codigoProyecto,
|
|
||||||
'codigo_maker' => $codigoMaker,
|
|
||||||
'disciplina' => $disciplina,
|
|
||||||
'disciplina_desc' => $disciplinaValida,
|
|
||||||
'tipo_documento' => $tipoDocumento,
|
|
||||||
'tipo_documento_desc' => $tipoValido,
|
|
||||||
'revision' => $numeroRevision,
|
|
||||||
'nombre_documento' => $nombre,
|
|
||||||
'estructura_valida' => true,
|
|
||||||
'errores' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validarDisciplina($codigo) {
|
|
||||||
return isset($this->disciplinasValidas[$codigo])
|
|
||||||
? $this->disciplinasValidas[$codigo]
|
|
||||||
: "Desconocida";
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validarTipoDocumento($codigo) {
|
|
||||||
return isset($this->tiposDocumentoValidos[$codigo])
|
|
||||||
? $this->tiposDocumentoValidos[$codigo]
|
|
||||||
: "Desconocido";
|
|
||||||
}
|
|
||||||
|
|
||||||
private function crearResultadoError($mensaje) {
|
|
||||||
return [
|
|
||||||
'estructura_valida' => false,
|
|
||||||
'errores' => [$mensaje]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Método para generar un nuevo código
|
|
||||||
public function generarCodigo($proyecto, $maker, $disciplina, $tipo, $revision, $nombre = '') {
|
|
||||||
$revisionFormateada = str_pad($revision, 2, '0', STR_PAD_LEFT);
|
|
||||||
$codigo = "{$proyecto}-{$maker}-{$disciplina}-{$tipo}-REV.{$revisionFormateada}";
|
|
||||||
|
|
||||||
if (!empty($nombre)) {
|
|
||||||
$codigo .= " - {$nombre}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $codigo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EJEMPLOS DE USO
|
|
||||||
/*
|
|
||||||
$analizador = new DocumentIdentifier();
|
|
||||||
|
|
||||||
// Analizar un código existente
|
|
||||||
$codigo1 = "MP00002-SOGOS-ENG-DRW-REV.01 - Plano principal";
|
|
||||||
$resultado1 = $analizador->analizarDocumento($codigo1);
|
|
||||||
|
|
||||||
echo "Análisis del documento:\n";
|
|
||||||
print_r($resultado1);
|
|
||||||
|
|
||||||
// Generar un nuevo código
|
|
||||||
$nuevoCodigo = $analizador->generarCodigo(
|
|
||||||
'MP00002',
|
|
||||||
'SOGOS',
|
|
||||||
'CIV',
|
|
||||||
'ESP',
|
|
||||||
'03',
|
|
||||||
'Especificación técnica de cimientos'
|
|
||||||
);
|
|
||||||
|
|
||||||
echo "\nNuevo código generado: " . $nuevoCodigo . "\n";*/
|
|
||||||
|
|
||||||
?>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Helpers;
|
|
||||||
|
|
||||||
class ProjectNamingSchema
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Generate a project name based on ISO 19650 schema.
|
|
||||||
*
|
|
||||||
* @param array $fields Associative array of required and optional fields.
|
|
||||||
* @return string Generated project name.
|
|
||||||
*/
|
|
||||||
public static function generate(array $fields): string
|
|
||||||
{
|
|
||||||
// Validate required fields
|
|
||||||
$requiredFields = ['project', 'creator', 'volume', 'level', 'documentType', 'discipline', 'number'];
|
|
||||||
foreach ($requiredFields as $field) {
|
|
||||||
if (empty($fields[$field])) {
|
|
||||||
throw new \InvalidArgumentException("The field '{$field}' is required.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the project name
|
|
||||||
$projectName = [
|
|
||||||
strtoupper($fields['project']),
|
|
||||||
strtoupper($fields['creator']),
|
|
||||||
strtoupper($fields['volume']),
|
|
||||||
strtoupper($fields['level']),
|
|
||||||
strtoupper($fields['documentType']),
|
|
||||||
strtoupper($fields['discipline']),
|
|
||||||
str_pad($fields['number'], 3, '0', STR_PAD_LEFT),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add optional fields if provided
|
|
||||||
if (!empty($fields['description'])) {
|
|
||||||
$projectName[] = $fields['description'];
|
|
||||||
}
|
|
||||||
if (!empty($fields['status'])) {
|
|
||||||
$projectName[] = strtoupper($fields['status']);
|
|
||||||
}
|
|
||||||
if (!empty($fields['revision'])) {
|
|
||||||
$projectName[] = str_pad($fields['revision'], 4, '0', STR_PAD_LEFT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return implode('-', $projectName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
317
app/Livewire/ProjectDocumentStatusManager.php
Normal file
317
app/Livewire/ProjectDocumentStatusManager.php
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\ProjectDocumentStatus;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class ProjectDocumentStatusManager extends Component
|
||||||
|
{
|
||||||
|
public $project;
|
||||||
|
public $statuses = [];
|
||||||
|
public $showForm = false;
|
||||||
|
|
||||||
|
public $formData = [
|
||||||
|
'id' => null,
|
||||||
|
'name' => '',
|
||||||
|
'color' => '#6b7280',
|
||||||
|
'text_color' => '#ffffff',
|
||||||
|
'description' => '',
|
||||||
|
'allow_upload' => true,
|
||||||
|
'allow_edit' => true,
|
||||||
|
'allow_delete' => false,
|
||||||
|
'requires_approval' => false,
|
||||||
|
'is_default' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
public $showDeleteModal = false;
|
||||||
|
public $statusToDelete = null;
|
||||||
|
public $orderedStatusIds = [];
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'formData.name' => 'required|string|min:2|max:100',
|
||||||
|
'formData.color' => 'required|string|max:7',
|
||||||
|
'formData.text_color' => 'nullable|string|max:7',
|
||||||
|
'formData.description' => 'nullable|string|max:500',
|
||||||
|
'formData.allow_upload' => 'boolean',
|
||||||
|
'formData.allow_edit' => 'boolean',
|
||||||
|
'formData.allow_delete' => 'boolean',
|
||||||
|
'formData.requires_approval' => 'boolean',
|
||||||
|
'formData.is_default' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount(Project $project)
|
||||||
|
{
|
||||||
|
$this->project = $project;
|
||||||
|
$this->loadStatuses();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadStatuses()
|
||||||
|
{
|
||||||
|
$this->statuses = $this->project->documentStatuses()
|
||||||
|
->orderBy('order')
|
||||||
|
->get()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$this->statuses = [];
|
||||||
|
|
||||||
|
$this->orderedStatusIds = collect($this->statuses)->pluck('id')->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function openForm($statusId = null)
|
||||||
|
{
|
||||||
|
$this->resetForm();
|
||||||
|
|
||||||
|
if ($statusId) {
|
||||||
|
$status = ProjectDocumentStatus::find($statusId);
|
||||||
|
if ($status && $status->project_id === $this->project->id) {
|
||||||
|
$this->formData = [
|
||||||
|
'id' => $status->id,
|
||||||
|
'name' => $status->name,
|
||||||
|
'color' => $status->color,
|
||||||
|
'text_color' => $status->text_color,
|
||||||
|
'description' => $status->description,
|
||||||
|
'allow_upload' => $status->allow_upload,
|
||||||
|
'allow_edit' => $status->allow_edit,
|
||||||
|
'allow_delete' => $status->allow_delete,
|
||||||
|
'requires_approval' => $status->requires_approval,
|
||||||
|
'is_default' => $status->is_default,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->showForm = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function closeForm()
|
||||||
|
{
|
||||||
|
$this->showForm = false;
|
||||||
|
$this->resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetForm()
|
||||||
|
{
|
||||||
|
$this->formData = [
|
||||||
|
'id' => null,
|
||||||
|
'name' => '',
|
||||||
|
'color' => '#6b7280',
|
||||||
|
'text_color' => '#ffffff',
|
||||||
|
'description' => '',
|
||||||
|
'allow_upload' => true,
|
||||||
|
'allow_edit' => true,
|
||||||
|
'allow_delete' => false,
|
||||||
|
'requires_approval' => false,
|
||||||
|
'is_default' => false,
|
||||||
|
];
|
||||||
|
$this->resetErrorBag();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveStatus()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
// Si se marca como default, quitar el default de otros estados
|
||||||
|
if ($this->formData['is_default']) {
|
||||||
|
$this->project->documentStatuses()->update(['is_default' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $this->formData['name'],
|
||||||
|
'color' => $this->formData['color'],
|
||||||
|
'text_color' => $this->formData['text_color'] ?: $this->calculateTextColor($this->formData['color']),
|
||||||
|
'description' => $this->formData['description'],
|
||||||
|
'allow_upload' => $this->formData['allow_upload'],
|
||||||
|
'allow_edit' => $this->formData['allow_edit'],
|
||||||
|
'allow_delete' => $this->formData['allow_delete'],
|
||||||
|
'requires_approval' => $this->formData['requires_approval'],
|
||||||
|
'is_default' => $this->formData['is_default'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->formData['id']) {
|
||||||
|
// Editar estado existente
|
||||||
|
$status = ProjectDocumentStatus::find($this->formData['id']);
|
||||||
|
if ($status && $status->project_id === $this->project->id) {
|
||||||
|
$status->update($data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Crear nuevo estado
|
||||||
|
$data['project_id'] = $this->project->id;
|
||||||
|
$data['slug'] = $this->generateUniqueSlug($this->formData['name']);
|
||||||
|
$data['order'] = $this->project->documentStatuses()->count();
|
||||||
|
|
||||||
|
ProjectDocumentStatus::create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
$this->loadStatuses();
|
||||||
|
$this->closeForm();
|
||||||
|
|
||||||
|
$this->dispatch('show-message', [
|
||||||
|
'type' => 'success',
|
||||||
|
'message' => $this->formData['id'] ? 'Estado actualizado correctamente.' : 'Estado creado correctamente.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
$this->dispatch('show-message', [
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => 'Error al guardar: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateUniqueSlug($name)
|
||||||
|
{
|
||||||
|
$slug = Str::slug($name);
|
||||||
|
$originalSlug = $slug;
|
||||||
|
$counter = 1;
|
||||||
|
|
||||||
|
while ($this->project->documentStatuses()->where('slug', $slug)->exists()) {
|
||||||
|
$slug = $originalSlug . '-' . $counter;
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculateTextColor($backgroundColor)
|
||||||
|
{
|
||||||
|
// Convertir hex a RGB
|
||||||
|
$hex = str_replace('#', '', $backgroundColor);
|
||||||
|
if (strlen($hex) == 3) {
|
||||||
|
$hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
$r = hexdec(substr($hex, 0, 2));
|
||||||
|
$g = hexdec(substr($hex, 2, 2));
|
||||||
|
$b = hexdec(substr($hex, 4, 2));
|
||||||
|
|
||||||
|
// Calcular luminosidad
|
||||||
|
$luminosity = (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
|
||||||
|
|
||||||
|
return $luminosity > 0.5 ? '#000000' : '#ffffff';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function confirmDelete($statusId)
|
||||||
|
{
|
||||||
|
$this->statusToDelete = ProjectDocumentStatus::find($statusId);
|
||||||
|
if ($this->statusToDelete && $this->statusToDelete->project_id === $this->project->id) {
|
||||||
|
$this->showDeleteModal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function closeDeleteModal()
|
||||||
|
{
|
||||||
|
$this->showDeleteModal = false;
|
||||||
|
$this->statusToDelete = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteStatus()
|
||||||
|
{
|
||||||
|
if (!$this->statusToDelete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Verificar que no haya documentos con este estado
|
||||||
|
if ($this->statusToDelete->documents()->exists()) {
|
||||||
|
$this->dispatch('show-message', [
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => 'No se puede eliminar el estado porque hay documentos asociados.'
|
||||||
|
]);
|
||||||
|
$this->closeDeleteModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si es el estado por defecto, establecer otro como default
|
||||||
|
if ($this->statusToDelete->is_default) {
|
||||||
|
$newDefault = $this->project->documentStatuses()
|
||||||
|
->where('id', '!=', $this->statusToDelete->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($newDefault) {
|
||||||
|
$newDefault->update(['is_default' => true]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->statusToDelete->delete();
|
||||||
|
$this->loadStatuses();
|
||||||
|
|
||||||
|
$this->dispatch('show-message', [
|
||||||
|
'type' => 'success',
|
||||||
|
'message' => 'Estado eliminado correctamente.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->dispatch('show-message', [
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => 'Error al eliminar: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->closeDeleteModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateOrder()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
foreach ($this->orderedStatusIds as $index => $statusId) {
|
||||||
|
$status = ProjectDocumentStatus::find($statusId);
|
||||||
|
if ($status && $status->project_id === $this->project->id) {
|
||||||
|
$status->update(['order' => $index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadStatuses();
|
||||||
|
|
||||||
|
$this->dispatch('show-message', [
|
||||||
|
'type' => 'success',
|
||||||
|
'message' => 'Orden actualizado correctamente.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->dispatch('show-message', [
|
||||||
|
'type' => 'error',
|
||||||
|
'message' => 'Error al actualizar el orden: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function moveUp($statusId)
|
||||||
|
{
|
||||||
|
$index = array_search($statusId, $this->orderedStatusIds);
|
||||||
|
|
||||||
|
if ($index > 0) {
|
||||||
|
$temp = $this->orderedStatusIds[$index];
|
||||||
|
$this->orderedStatusIds[$index] = $this->orderedStatusIds[$index - 1];
|
||||||
|
$this->orderedStatusIds[$index - 1] = $temp;
|
||||||
|
|
||||||
|
$this->updateOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function moveDown($statusId)
|
||||||
|
{
|
||||||
|
$index = array_search($statusId, $this->orderedStatusIds);
|
||||||
|
|
||||||
|
if ($index < count($this->orderedStatusIds) - 1) {
|
||||||
|
$temp = $this->orderedStatusIds[$index];
|
||||||
|
$this->orderedStatusIds[$index] = $this->orderedStatusIds[$index + 1];
|
||||||
|
$this->orderedStatusIds[$index + 1] = $temp;
|
||||||
|
|
||||||
|
$this->updateOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project-document-status-manager');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,11 +23,9 @@ class ProjectNameCoder extends Component
|
|||||||
// Inicializar con un componente vacío
|
// Inicializar con un componente vacío
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
|
|
||||||
// Si hay configuración inicial, cargarla
|
|
||||||
if ($project->codingConfig) {
|
if ($project->codingConfig) {
|
||||||
$this->loadDatabaseConfiguration();
|
$this->loadDatabaseConfiguration();
|
||||||
} else {
|
} else {
|
||||||
// Inicializar con un componente vacío
|
|
||||||
$this->addComponent();
|
$this->addComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +178,7 @@ class ProjectNameCoder extends Component
|
|||||||
// Preparar la configuración completa
|
// Preparar la configuración completa
|
||||||
$configData = [
|
$configData = [
|
||||||
'components' => $this->components,
|
'components' => $this->components,
|
||||||
//'total_components' => $this->componentsCount,
|
'total_components' => $this->componentsCount,
|
||||||
//'total_document_types' => $this->totalDocumentTypes,
|
//'total_document_types' => $this->totalDocumentTypes,
|
||||||
//'generated_format' => $this->generateFormatString(),
|
//'generated_format' => $this->generateFormatString(),
|
||||||
//'last_updated' => now()->toDateTimeString(),
|
//'last_updated' => now()->toDateTimeString(),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use App\Models\Document;
|
|||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
use App\Helpers\DocumentIdentifier;
|
use App\Services\ProjectCodeService;
|
||||||
|
|
||||||
class ProjectShow extends Component
|
class ProjectShow extends Component
|
||||||
{
|
{
|
||||||
@@ -35,10 +35,12 @@ class ProjectShow extends Component
|
|||||||
|
|
||||||
protected $listeners = ['documents-updated' => '$refresh'];
|
protected $listeners = ['documents-updated' => '$refresh'];
|
||||||
|
|
||||||
|
protected ProjectCodeService $codeService;
|
||||||
|
|
||||||
public function mount(Project $project)
|
public function mount(Project $project)
|
||||||
{
|
{
|
||||||
$this->project = $project->load('rootFolders');
|
$this->project = $project;
|
||||||
|
$this->project['javi'] = 'braña';
|
||||||
$this->currentFolder = $this->project->rootFolders->first() ?? null;
|
$this->currentFolder = $this->project->rootFolders->first() ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +131,13 @@ class ProjectShow extends Component
|
|||||||
$this->validate([
|
$this->validate([
|
||||||
'selectedFiles.*' => 'file|max:10240|mimes:pdf,docx,xlsx,jpg,png'
|
'selectedFiles.*' => 'file|max:10240|mimes:pdf,docx,xlsx,jpg,png'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$isValid = app(ProjectCodeService::class)->validate($this->project, $files[0]->getClientOriginalName());
|
||||||
|
if ($isValid) {
|
||||||
|
echo "✅ Código válido\n";
|
||||||
|
} else {
|
||||||
|
echo "❌ Código inválido\n";
|
||||||
|
}
|
||||||
|
|
||||||
$this->selectedFiles = array_merge($this->selectedFiles, $files);
|
$this->selectedFiles = array_merge($this->selectedFiles, $files);
|
||||||
}
|
}
|
||||||
@@ -142,10 +151,16 @@ class ProjectShow extends Component
|
|||||||
|
|
||||||
public function uploadFiles(): void
|
public function uploadFiles(): void
|
||||||
{
|
{
|
||||||
|
$validator = new ProjectCodeValidator($project->codingConfig);
|
||||||
foreach ($this->selectedFiles as $file) {
|
foreach ($this->selectedFiles as $file) {
|
||||||
//$analizador = analizarDocumento();
|
|
||||||
//print_r($analizador);
|
//print_r($analizador);
|
||||||
//$resultado1 = $analizador->analizarDocumento($file->getClientOriginalName());
|
//$resultado1 = $analizador->analizarDocumento($file->getClientOriginalName());
|
||||||
|
if ($validator->validate($file->getClientOriginalName())) {
|
||||||
|
echo "✅ Código válido\n";
|
||||||
|
} else {
|
||||||
|
echo "❌ Código inválido\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$code = $this->project->reference;
|
$code = $this->project->reference;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ use App\Livewire\PdfViewer;
|
|||||||
use App\Livewire\ProjectShow;
|
use App\Livewire\ProjectShow;
|
||||||
use App\Livewire\Toolbar;
|
use App\Livewire\Toolbar;
|
||||||
use App\View\Components\Multiselect;
|
use App\View\Components\Multiselect;
|
||||||
|
use App\Services\ProjectCodeService;
|
||||||
|
use App\Services\ProjectCodeValidator;
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
@@ -21,7 +24,16 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
//
|
// Registrar el Service
|
||||||
|
$this->app->singleton(ProjectCodeService::class, function ($app) {
|
||||||
|
return new ProjectCodeService(new ProjectCodeValidator([]));
|
||||||
|
});
|
||||||
|
|
||||||
|
// O si prefieres, registrar el validador por separado
|
||||||
|
$this->app->bind(ProjectCodeValidator::class, function ($app, $params) {
|
||||||
|
// $params[0] contendría los datos del proyecto
|
||||||
|
return new ProjectCodeValidator($params[0] ?? []);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
80
app/Services/ProjectCodeService.php
Normal file
80
app/Services/ProjectCodeService.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Services\ProjectCodeValidator;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class ProjectCodeService
|
||||||
|
{
|
||||||
|
protected ProjectCodeValidator $validator;
|
||||||
|
|
||||||
|
public function __construct(ProjectCodeValidator $validator)
|
||||||
|
{
|
||||||
|
$this->validator = $validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea un validador para un proyecto específico
|
||||||
|
*/
|
||||||
|
public function forProject(Project $project): ProjectCodeValidator
|
||||||
|
{
|
||||||
|
// Si es un modelo Eloquent, convertir a array
|
||||||
|
//$projectData = $project instanceof Project ? $project->toArray() : $project;
|
||||||
|
|
||||||
|
return new ProjectCodeValidator($project);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida un código rápidamente
|
||||||
|
*/
|
||||||
|
public function validate(Project $project, string $code): bool
|
||||||
|
{
|
||||||
|
$validator = $this->forProject($project);
|
||||||
|
return $validator->validate($code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analiza un código y devuelve detalles
|
||||||
|
*/
|
||||||
|
public function analyze(Project $project, string $code): array
|
||||||
|
{
|
||||||
|
$validator = $this->forProject($project);
|
||||||
|
return $validator->analyze($code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera un nuevo código para el proyecto
|
||||||
|
*/
|
||||||
|
public function generate(
|
||||||
|
Project $project,
|
||||||
|
array $components,
|
||||||
|
string $documentName
|
||||||
|
): string
|
||||||
|
{
|
||||||
|
$validator = $this->forProject($project);
|
||||||
|
return $validator->generateCode($components, $documentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene la configuración de codificación
|
||||||
|
*/
|
||||||
|
public function getConfiguration(Project $project): array
|
||||||
|
{
|
||||||
|
$validator = $this->forProject($project);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'format' => $validator->getFormat(),
|
||||||
|
'reference' => $validator->getReference(),
|
||||||
|
'separator' => $validator->getSeparator(),
|
||||||
|
'sequence_length' => $validator->getSequenceLength(),
|
||||||
|
'components' => [
|
||||||
|
'maker' => $validator->getAllowedCodesForComponent('maker'),
|
||||||
|
'system' => $validator->getAllowedCodesForComponent('system'),
|
||||||
|
'discipline' => $validator->getAllowedCodesForComponent('discipline'),
|
||||||
|
'document_type' => $validator->getAllowedCodesForComponent('document_type'),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
543
app/Services/ProjectCodeValidator.php
Normal file
543
app/Services/ProjectCodeValidator.php
Normal file
@@ -0,0 +1,543 @@
|
|||||||
|
<?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";
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
<div class="max-w-full">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">Estados de Documentos</h3>
|
||||||
|
<p class="text-sm text-gray-600">Configura los estados disponibles para los documentos de este proyecto</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
wire:click="openForm"
|
||||||
|
class="btn btn-primary">
|
||||||
|
<x-icons icon = "plus" class="w-4 h-4 mr-2" /> Nuevo Estado
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista de estados -->
|
||||||
|
<div class="bg-white rounded-lg shadow overflow-hidden mb-6">
|
||||||
|
@if(false)
|
||||||
|
@if(count($statuses) > 0)
|
||||||
|
<div class="divide-y divide-gray-200">
|
||||||
|
@foreach($statuses as $status)
|
||||||
|
<div class="p-4 hover:bg-gray-50 transition-colors"
|
||||||
|
wire:key="status-{{ $status['id'] }}">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<!-- Información del estado -->
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<!-- Badge de color -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-10 h-10 rounded-full border flex items-center justify-center"
|
||||||
|
style="background-color: {{ $status['color'] }}; color: {{ $status['text_color'] }}"
|
||||||
|
title="{{ $status['name'] }}">
|
||||||
|
<span class="font-bold text-sm">
|
||||||
|
{{ substr($status['name'], 0, 2) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detalles -->
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="font-medium text-gray-900">{{ $status['name'] }}</span>
|
||||||
|
@if($status['is_default'])
|
||||||
|
<span class="px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded-full">
|
||||||
|
Por defecto
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($status['description'])
|
||||||
|
<p class="text-sm text-gray-600 mt-1">{{ $status['description'] }}</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Permisos -->
|
||||||
|
<div class="flex flex-wrap gap-2 mt-2">
|
||||||
|
@if($status['allow_upload'])
|
||||||
|
<span class="inline-flex items-center px-2 py-1 rounded text-xs bg-green-50 text-green-700">
|
||||||
|
<x-icons icon = "upload" class="w-3 h-3 mr-1" /> Subir
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($status['allow_edit'])
|
||||||
|
<span class="inline-flex items-center px-2 py-1 rounded text-xs bg-blue-50 text-blue-700">
|
||||||
|
<x-icons icon = "pencil" class="w-3 h-3 mr-1" /> Editar
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($status['allow_delete'])
|
||||||
|
<span class="inline-flex items-center px-2 py-1 rounded text-xs bg-red-50 text-red-700">
|
||||||
|
<x-icons icon = "trash" class="w-3 h-3 mr-1" /> Eliminar
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($status['requires_approval'])
|
||||||
|
<span class="inline-flex items-center px-2 py-1 rounded text-xs bg-yellow-50 text-yellow-700">
|
||||||
|
<x-icons icon = "shield-check" class="w-3 h-3 mr-1" /> Aprobación
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Acciones -->
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<!-- Botones de orden -->
|
||||||
|
<div class="flex space-x-1">
|
||||||
|
<button type="button"
|
||||||
|
wire:click="moveUp({{ $status['id'] }})"
|
||||||
|
{{ $loop->first ? 'disabled' : '' }}
|
||||||
|
class="p-1 {{ $loop->first ? 'text-gray-300 cursor-not-allowed' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded' }}"
|
||||||
|
title="Mover hacia arriba">
|
||||||
|
<x-icons icon = "arrow-up" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
wire:click="moveDown({{ $status['id'] }})"
|
||||||
|
{{ $loop->last ? 'disabled' : '' }}
|
||||||
|
class="p-1 {{ $loop->last ? 'text-gray-300 cursor-not-allowed' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded' }}"
|
||||||
|
title="Mover hacia abajo">
|
||||||
|
<x-icons icon = "arrow-down" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botón editar -->
|
||||||
|
<button type="button"
|
||||||
|
wire:click="openForm({{ $status['id'] }})"
|
||||||
|
class="p-1 text-yellow-600 hover:text-yellow-900 hover:bg-yellow-50 rounded"
|
||||||
|
title="Editar estado">
|
||||||
|
<x-icons icon = "pencil" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Botón eliminar -->
|
||||||
|
@if(!$status['is_default'])
|
||||||
|
<button type="button"
|
||||||
|
wire:click="confirmDelete({{ $status['id'] }})"
|
||||||
|
class="p-1 text-red-600 hover:text-red-900 hover:bg-red-50 rounded"
|
||||||
|
title="Eliminar estado">
|
||||||
|
<x-icons icon = "trash" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="p-8 text-center text-gray-500">
|
||||||
|
<x-icons icon = "document-text" class="w-12 h-12 mx-auto text-gray-300 mb-4" />
|
||||||
|
<p class="mb-4">No hay estados configurados para este proyecto.</p>
|
||||||
|
<button type="button"
|
||||||
|
wire:click="openForm"
|
||||||
|
class="btn btn-primary">
|
||||||
|
<x-icons icon = "plus" class="w-4 h-4 mr-2" /> Crear primer estado
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Formulario para crear/editar estado -->
|
||||||
|
<div x-data="{ showForm: @entangle('showForm') }"
|
||||||
|
x-show="showForm"
|
||||||
|
x-transition:enter="transition ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 transform scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 transform scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 transform scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 transform scale-95"
|
||||||
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-md max-h-[90vh] overflow-y-auto">
|
||||||
|
<!-- Header del modal -->
|
||||||
|
<div class="border-b px-6 py-4">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">
|
||||||
|
{{ $formData['id'] ? 'Editar Estado' : 'Crear Nuevo Estado' }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formulario -->
|
||||||
|
<form wire:submit.prevent="saveStatus" class="p-6">
|
||||||
|
<!-- Nombre -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Nombre del Estado *
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="name"
|
||||||
|
wire:model="formData.name"
|
||||||
|
required
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
@error('formData.name')
|
||||||
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Colores -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Color de fondo
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<input type="color"
|
||||||
|
wire:model="formData.color"
|
||||||
|
class="w-10 h-10 p-1 border border-gray-300 rounded">
|
||||||
|
<input type="text"
|
||||||
|
wire:model="formData.color"
|
||||||
|
maxlength="7"
|
||||||
|
class="flex-1 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
</div>
|
||||||
|
@error('formData.color')
|
||||||
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Color del texto
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<input type="color"
|
||||||
|
wire:model="formData.text_color"
|
||||||
|
class="w-10 h-10 p-1 border border-gray-300 rounded">
|
||||||
|
<input type="text"
|
||||||
|
wire:model="formData.text_color"
|
||||||
|
maxlength="7"
|
||||||
|
class="flex-1 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
</div>
|
||||||
|
@error('formData.text_color')
|
||||||
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Previsualización -->
|
||||||
|
<div class="mb-4 p-3 bg-gray-50 rounded-lg">
|
||||||
|
<p class="text-sm font-medium text-gray-700 mb-2">Previsualización:</p>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="px-3 py-1 rounded-full text-sm font-medium"
|
||||||
|
style="background-color: {{ $formData['color'] }}; color: {{ $formData['text_color'] ?: '#ffffff' }}">
|
||||||
|
{{ $formData['name'] ?: 'Nombre del estado' }}
|
||||||
|
</span>
|
||||||
|
<span class="text-sm text-gray-500">(Esto es cómo se verá)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descripción -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="description" class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Descripción
|
||||||
|
</label>
|
||||||
|
<textarea id="description"
|
||||||
|
wire:model="formData.description"
|
||||||
|
rows="3"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"></textarea>
|
||||||
|
@error('formData.description')
|
||||||
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Permisos -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-sm font-medium text-gray-700 mb-2">Permisos:</p>
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<label class="flex items-center space-x-2">
|
||||||
|
<input type="checkbox"
|
||||||
|
wire:model="formData.allow_upload"
|
||||||
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||||
|
<span class="text-sm text-gray-700">Permitir subida</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="flex items-center space-x-2">
|
||||||
|
<input type="checkbox"
|
||||||
|
wire:model="formData.allow_edit"
|
||||||
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||||
|
<span class="text-sm text-gray-700">Permitir edición</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="flex items-center space-x-2">
|
||||||
|
<input type="checkbox"
|
||||||
|
wire:model="formData.allow_delete"
|
||||||
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||||
|
<span class="text-sm text-gray-700">Permitir eliminación</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="flex items-center space-x-2">
|
||||||
|
<input type="checkbox"
|
||||||
|
wire:model="formData.requires_approval"
|
||||||
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||||
|
<span class="text-sm text-gray-700">Requiere aprobación</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Estado por defecto -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<label class="flex items-center space-x-2">
|
||||||
|
<input type="checkbox"
|
||||||
|
wire:model="formData.is_default"
|
||||||
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||||
|
<span class="text-sm text-gray-700">Estado por defecto para nuevos documentos</span>
|
||||||
|
</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
|
Solo puede haber un estado por defecto. Si marcas este, se desmarcará automáticamente el anterior.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botones -->
|
||||||
|
<div class="flex justify-end space-x-3">
|
||||||
|
<button type="button"
|
||||||
|
wire:click="closeForm"
|
||||||
|
class="btn btn-secondary">
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
class="btn btn-primary">
|
||||||
|
{{ $formData['id'] ? 'Actualizar Estado' : 'Crear Estado' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de confirmación para eliminar -->
|
||||||
|
<div x-data="{ showDeleteModal: @entangle('showDeleteModal') }"
|
||||||
|
x-show="showDeleteModal"
|
||||||
|
x-transition
|
||||||
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
|
||||||
|
<div class="border-b px-6 py-4">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">Confirmar eliminación</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-6">
|
||||||
|
<p class="text-gray-700 mb-4">
|
||||||
|
¿Estás seguro de que deseas eliminar el estado
|
||||||
|
<strong>"{{ $statusToDelete->name ?? '' }}"</strong>?
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@if($statusToDelete && $statusToDelete->documents()->exists())
|
||||||
|
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded">
|
||||||
|
<p class="text-sm text-red-700">
|
||||||
|
<x-icons icon = "exclamation" class="w-4 h-4 inline mr-1" />
|
||||||
|
No se puede eliminar porque hay documentos asociados a este estado.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="flex justify-end space-x-3">
|
||||||
|
<button type="button"
|
||||||
|
wire:click="closeDeleteModal"
|
||||||
|
class="btn btn-secondary">
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@if(!($statusToDelete && $statusToDelete->documents()->exists()))
|
||||||
|
<button type="button"
|
||||||
|
wire:click="deleteStatus"
|
||||||
|
class="btn btn-danger">
|
||||||
|
Eliminar Estado
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -18,6 +18,9 @@
|
|||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
{{ $project->reference }}
|
{{ $project->reference }}
|
||||||
</p>
|
</p>
|
||||||
|
<p class="mx-2">
|
||||||
|
{{ $project['codingConfig'] }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($project->phone)
|
@if($project->phone)
|
||||||
|
|||||||
@@ -69,11 +69,6 @@
|
|||||||
<div id="statuses">
|
<div id="statuses">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h3 class="text-lg font-medium text-gray-900">Estados de Documentos</h3>
|
<h3 class="text-lg font-medium text-gray-900">Estados de Documentos</h3>
|
||||||
<button type="button"
|
|
||||||
onclick="openStatusModal()"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<x-icons icon="plus" class="w-4 h-4 mr-1" /> Nuevo Estado
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow overflow-hidden">
|
<div class="bg-white rounded-lg shadow overflow-hidden">
|
||||||
@@ -82,296 +77,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal para crear/editar estado -->
|
|
||||||
<div id="status-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50" style="display: none;">
|
|
||||||
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
|
|
||||||
<div class="border-b px-6 py-4">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900" id="modal-title">Nuevo Estado</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form id="status-form" method="POST" class="p-6">
|
|
||||||
@csrf
|
|
||||||
<div id="method-field"></div>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<!-- Nombre -->
|
|
||||||
<div>
|
|
||||||
<label for="modal-name" class="block text-sm font-medium text-gray-700">
|
|
||||||
Nombre del Estado *
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="modal-name"
|
|
||||||
name="name"
|
|
||||||
required
|
|
||||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Color -->
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<label for="modal-color" class="block text-sm font-medium text-gray-700">
|
|
||||||
Color de fondo
|
|
||||||
</label>
|
|
||||||
<div class="mt-1 flex items-center">
|
|
||||||
<input type="color"
|
|
||||||
id="modal-color"
|
|
||||||
name="color"
|
|
||||||
value="#6b7280"
|
|
||||||
class="h-10 w-16 rounded border">
|
|
||||||
<input type="text"
|
|
||||||
id="modal-color-text"
|
|
||||||
name="color_text"
|
|
||||||
value="#6b7280"
|
|
||||||
class="ml-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="modal-text-color" class="block text-sm font-medium text-gray-700">
|
|
||||||
Color del texto
|
|
||||||
</label>
|
|
||||||
<div class="mt-1 flex items-center">
|
|
||||||
<input type="color"
|
|
||||||
id="modal-text-color"
|
|
||||||
name="text_color"
|
|
||||||
value="#ffffff"
|
|
||||||
class="h-10 w-16 rounded border">
|
|
||||||
<input type="text"
|
|
||||||
id="modal-text-color-text"
|
|
||||||
name="text_color_text"
|
|
||||||
value="#ffffff"
|
|
||||||
class="ml-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Descripción -->
|
|
||||||
<div>
|
|
||||||
<label for="modal-description" class="block text-sm font-medium text-gray-700">
|
|
||||||
Descripción
|
|
||||||
</label>
|
|
||||||
<textarea id="modal-description"
|
|
||||||
name="description"
|
|
||||||
rows="3"
|
|
||||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Permisos -->
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
<label class="flex items-center">
|
|
||||||
<input type="checkbox"
|
|
||||||
name="allow_upload"
|
|
||||||
value="1"
|
|
||||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
|
|
||||||
<span class="ml-2 text-sm text-gray-700">Permitir subida</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="flex items-center">
|
|
||||||
<input type="checkbox"
|
|
||||||
name="allow_edit"
|
|
||||||
value="1"
|
|
||||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
|
|
||||||
<span class="ml-2 text-sm text-gray-700">Permitir edición</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="flex items-center">
|
|
||||||
<input type="checkbox"
|
|
||||||
name="allow_delete"
|
|
||||||
value="1"
|
|
||||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-indigo-200 focus:ring-opacity-50">
|
|
||||||
<span class="ml-2 text-sm text-gray-700">Permitir eliminación</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="flex items-center">
|
|
||||||
<input type="checkbox"
|
|
||||||
name="requires_approval"
|
|
||||||
value="1"
|
|
||||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
|
|
||||||
<span class="ml-2 text-sm text-gray-700">Requiere aprobación</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Estado por defecto -->
|
|
||||||
<div>
|
|
||||||
<label class="flex items-center">
|
|
||||||
<input type="checkbox"
|
|
||||||
name="is_default"
|
|
||||||
value="1"
|
|
||||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
|
|
||||||
<span class="ml-2 text-sm text-gray-700">Estado por defecto para nuevos documentos</span>
|
|
||||||
</label>
|
|
||||||
<p class="mt-1 text-xs text-gray-500">
|
|
||||||
Solo puede haber un estado por defecto
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6 flex justify-end space-x-3">
|
|
||||||
<button type="button" onclick="closeStatusModal()" class="btn btn-secondary">
|
|
||||||
Cancelar
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
Guardar Estado
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modal de confirmación para eliminar -->
|
|
||||||
<div id="delete-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50" style="display: none;">
|
|
||||||
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
|
|
||||||
<div class="border-b px-6 py-4">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900">Confirmar eliminación</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-6">
|
|
||||||
<p class="text-gray-700" id="delete-message">¿Estás seguro de que deseas eliminar este estado?</p>
|
|
||||||
|
|
||||||
<form id="delete-form" method="POST" class="mt-6">
|
|
||||||
@csrf
|
|
||||||
@method('DELETE')
|
|
||||||
|
|
||||||
<div class="flex justify-end space-x-3">
|
|
||||||
<button type="button" onclick="closeDeleteModal()" class="btn btn-secondary">
|
|
||||||
Cancelar
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-danger">
|
|
||||||
Eliminar Estado
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@push('scripts')
|
|
||||||
<script>
|
|
||||||
// Variables globales
|
|
||||||
let currentStatusId = null;
|
|
||||||
const statusModal = document.getElementById('status-modal');
|
|
||||||
const deleteModal = document.getElementById('delete-modal');
|
|
||||||
|
|
||||||
// Funciones para el modal de estados
|
|
||||||
function openStatusModal(status = null) {
|
|
||||||
const form = document.getElementById('status-form');
|
|
||||||
const title = document.getElementById('modal-title');
|
|
||||||
const methodField = document.getElementById('method-field');
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
// Modo edición
|
|
||||||
title.textContent = 'Editar Estado';
|
|
||||||
form.action = `/projects/{{ $project->id }}/settings/statuses/${status.id}`;
|
|
||||||
methodField.innerHTML = '<input type="hidden" name="_method" value="PUT">';
|
|
||||||
|
|
||||||
// Llenar los campos con los datos del estado
|
|
||||||
document.getElementById('modal-name').value = status.name;
|
|
||||||
document.getElementById('modal-color').value = status.color;
|
|
||||||
document.getElementById('modal-color-text').value = status.color;
|
|
||||||
document.getElementById('modal-text-color').value = status.text_color;
|
|
||||||
document.getElementById('modal-text-color-text').value = status.text_color;
|
|
||||||
document.getElementById('modal-description').value = status.description || '';
|
|
||||||
|
|
||||||
// Checkboxes
|
|
||||||
document.querySelector('input[name="allow_upload"]').checked = status.allow_upload;
|
|
||||||
document.querySelector('input[name="allow_edit"]').checked = status.allow_edit;
|
|
||||||
document.querySelector('input[name="allow_delete"]').checked = status.allow_delete;
|
|
||||||
document.querySelector('input[name="requires_approval"]').checked = status.requires_approval;
|
|
||||||
document.querySelector('input[name="is_default"]').checked = status.is_default;
|
|
||||||
|
|
||||||
currentStatusId = status.id;
|
|
||||||
} else {
|
|
||||||
// Modo creación
|
|
||||||
title.textContent = 'Nuevo Estado';
|
|
||||||
form.action = `/projects/{{ $project->id }}/settings/statuses`;
|
|
||||||
methodField.innerHTML = '';
|
|
||||||
|
|
||||||
// Resetear campos
|
|
||||||
form.reset();
|
|
||||||
document.getElementById('modal-color').value = '#6b7280';
|
|
||||||
document.getElementById('modal-color-text').value = '#6b7280';
|
|
||||||
document.getElementById('modal-text-color').value = '#ffffff';
|
|
||||||
document.getElementById('modal-text-color-text').value = '#ffffff';
|
|
||||||
|
|
||||||
currentStatusId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
statusModal.style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeStatusModal() {
|
|
||||||
statusModal.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Funciones para el modal de eliminación
|
|
||||||
function openDeleteModal(status) {
|
|
||||||
const message = document.getElementById('delete-message');
|
|
||||||
const form = document.getElementById('delete-form');
|
|
||||||
|
|
||||||
message.textContent = `¿Estás seguro de que deseas eliminar el estado "${status.name}"?`;
|
|
||||||
form.action = `/projects/{{ $project->id }}/settings/statuses/${status.id}`;
|
|
||||||
|
|
||||||
currentStatusId = status.id;
|
|
||||||
deleteModal.style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeDeleteModal() {
|
|
||||||
deleteModal.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sincronizar inputs de color
|
|
||||||
document.getElementById('modal-color').addEventListener('input', function(e) {
|
|
||||||
document.getElementById('modal-color-text').value = e.target.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('modal-color-text').addEventListener('input', function(e) {
|
|
||||||
document.getElementById('modal-color').value = e.target.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('modal-text-color').addEventListener('input', function(e) {
|
|
||||||
document.getElementById('modal-text-color-text').value = e.target.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('modal-text-color-text').addEventListener('input', function(e) {
|
|
||||||
document.getElementById('modal-text-color').value = e.target.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cerrar modales al hacer clic fuera
|
|
||||||
window.addEventListener('click', function(e) {
|
|
||||||
if (e.target === statusModal) {
|
|
||||||
closeStatusModal();
|
|
||||||
}
|
|
||||||
if (e.target === deleteModal) {
|
|
||||||
closeDeleteModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Funcionalidad de drag & drop para reordenar estados
|
|
||||||
new Sortable(document.getElementById('statuses-list'), {
|
|
||||||
animation: 150,
|
|
||||||
handle: '.drag-handle',
|
|
||||||
onEnd: function() {
|
|
||||||
// Obtener el nuevo orden
|
|
||||||
const order = Array.from(document.querySelectorAll('.status-item')).map(item => {
|
|
||||||
return item.dataset.statusId;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enviar el nuevo orden al servidor
|
|
||||||
fetch(`/projects/{{ $project->id }}/settings/statuses/reorder`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ order: order })
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endpush
|
|
||||||
</x-layouts.app>
|
</x-layouts.app>
|
||||||
Reference in New Issue
Block a user